Skip to content

Commit dc97548

Browse files
committed
Added encoder
1 parent e906cfd commit dc97548

File tree

5 files changed

+285
-45
lines changed

5 files changed

+285
-45
lines changed

encoder.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package media
2+
3+
import (
4+
5+
// Packages
6+
"fmt"
7+
8+
ff "github.com/mutablelogic/go-media/sys/ffmpeg61"
9+
10+
// Namespace imports
11+
. "github.com/djthorpe/go-errors"
12+
)
13+
14+
////////////////////////////////////////////////////////////////////////////////
15+
// TYPES
16+
17+
type encoder struct {
18+
t MediaType
19+
ctx *ff.AVCodecContext
20+
stream *ff.AVStream
21+
packet *ff.AVPacket
22+
}
23+
24+
////////////////////////////////////////////////////////////////////////////////
25+
// LIFECYCLE
26+
27+
// Create an encoder with the given parameters
28+
func newEncoder(ctx *ff.AVFormatContext, stream_id int, param Parameters) (*encoder, error) {
29+
encoder := new(encoder)
30+
par := param.(*par)
31+
32+
// Get codec
33+
codec_id := ff.AV_CODEC_ID_NONE
34+
if param.Type().Is(CODEC) {
35+
codec_id = par.codecpar.Codec
36+
} else if par.Type().Is(AUDIO) {
37+
codec_id = ctx.Output().AudioCodec()
38+
} else if par.Type().Is(VIDEO) {
39+
codec_id = ctx.Output().VideoCodec()
40+
} else if par.Type().Is(SUBTITLE) {
41+
codec_id = ctx.Output().SubtitleCodec()
42+
}
43+
if codec_id == ff.AV_CODEC_ID_NONE {
44+
return nil, ErrBadParameter.With("no codec specified for stream")
45+
}
46+
47+
// Allocate codec
48+
codec := ff.AVCodec_find_encoder(codec_id)
49+
if codec == nil {
50+
return nil, ErrBadParameter.Withf("codec %q cannot encode", codec_id)
51+
}
52+
codecctx := ff.AVCodec_alloc_context(codec)
53+
if codecctx == nil {
54+
return nil, ErrInternalAppError.With("could not allocate audio codec context")
55+
} else {
56+
encoder.ctx = codecctx
57+
}
58+
59+
// Create the stream
60+
if stream := ff.AVFormat_new_stream(ctx, nil); stream == nil {
61+
ff.AVCodec_free_context(codecctx)
62+
return nil, ErrInternalAppError.With("could not allocate stream")
63+
} else {
64+
stream.SetId(stream_id)
65+
encoder.stream = stream
66+
}
67+
68+
// Set parameters
69+
switch codec.Type() {
70+
case ff.AVMEDIA_TYPE_AUDIO:
71+
encoder.t = AUDIO
72+
// TODO: Check codec supports this configuration
73+
74+
// Set codec parameters
75+
if err := codecctx.SetChannelLayout(par.audiopar.Ch); err != nil {
76+
ff.AVCodec_free_context(codecctx)
77+
return nil, err
78+
}
79+
codecctx.SetSampleFormat(par.audiopar.SampleFormat)
80+
codecctx.SetSampleRate(par.audiopar.Samplerate)
81+
82+
// Set stream parameters
83+
encoder.stream.SetTimeBase(ff.AVUtil_rational(1, par.audiopar.Samplerate))
84+
case ff.AVMEDIA_TYPE_VIDEO:
85+
encoder.t = VIDEO
86+
// TODO: Check codec supports this configuration
87+
88+
// Set codec parameters
89+
codecctx.SetPixFmt(par.videopar.PixelFormat)
90+
codecctx.SetWidth(par.videopar.Width)
91+
codecctx.SetHeight(par.videopar.Height)
92+
93+
// Set stream parameters
94+
encoder.stream.SetTimeBase(ff.AVUtil_rational_d2q(1/par.codecpar.Framerate, 1<<24))
95+
case ff.AVMEDIA_TYPE_SUBTITLE:
96+
encoder.t = SUBTITLE
97+
fmt.Println("TODO: Set encoding subtitle parameters")
98+
default:
99+
encoder.t = DATA
100+
}
101+
encoder.t |= OUTPUT
102+
103+
// copy parameters to the stream
104+
if err := ff.AVCodec_parameters_from_context(encoder.stream.CodecPar(), codecctx); err != nil {
105+
ff.AVCodec_free_context(codecctx)
106+
return nil, err
107+
}
108+
109+
// Some formats want stream headers to be separate.
110+
if ctx.Flags().Is(ff.AVFMT_GLOBALHEADER) {
111+
codecctx.SetFlags(codecctx.Flags() | ff.AV_CODEC_FLAG_GLOBAL_HEADER)
112+
}
113+
114+
// Open it
115+
if err := ff.AVCodec_open(codecctx, codec, nil); err != nil {
116+
ff.AVCodec_free_context(codecctx)
117+
return nil, ErrInternalAppError.Withf("codec_open: %v", err)
118+
}
119+
120+
// Allocate packet
121+
if packet := ff.AVCodec_packet_alloc(); packet == nil {
122+
ff.AVCodec_free_context(codecctx)
123+
return nil, ErrInternalAppError.With("could not allocate packet")
124+
} else {
125+
encoder.packet = packet
126+
}
127+
128+
// Return it
129+
return encoder, nil
130+
}
131+
132+
func (encoder *encoder) Close() error {
133+
// Free respurces
134+
if encoder.packet != nil {
135+
ff.AVCodec_packet_free(encoder.packet)
136+
}
137+
if encoder.ctx != nil {
138+
ff.AVCodec_free_context(encoder.ctx)
139+
}
140+
141+
// Release resources
142+
encoder.stream = nil
143+
encoder.packet = nil
144+
encoder.ctx = nil
145+
146+
// Return success
147+
return nil
148+
}

parameters.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ type par struct {
1616
t MediaType
1717
audiopar
1818
videopar
19+
codecpar
1920
planepar
2021
}
2122

2223
type codecpar struct {
23-
Framerate ff.AVRational
24+
Codec ff.AVCodecID `json:"codec"`
25+
26+
// For video (in fps)
27+
Framerate float64 `json:"framerate"`
2428
}
2529

2630
type audiopar struct {
@@ -39,12 +43,6 @@ type planepar struct {
3943
NumPlanes int `json:"num_video_planes"`
4044
}
4145

42-
type timingpar struct {
43-
Framerate ff.AVRational `json:"framerate"`
44-
Pts int64 `json:"pts"`
45-
TimeBase ff.AVRational `json:"time_base"`
46-
}
47-
4846
var _ Parameters = (*par)(nil)
4947

5048
////////////////////////////////////////////////////////////////////////////////

stream.go

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ type stream struct {
1313
}
1414

1515
type writerstream struct {
16-
*ff.AVStream
16+
t MediaType
17+
codec *ff.AVCodec
1718
}
1819

1920
var _ Stream = (*stream)(nil)
@@ -28,25 +29,6 @@ func newStream(ctx *ff.AVStream) *stream {
2829
return &stream{ctx}
2930
}
3031

31-
/*
32-
// Stream wrapper for encoding
33-
func newWriterStream(ctx *ff.AVFormatContext, param Parameters) (*writerstream, error) {
34-
// Parameters - Codec
35-
var codec_id ff.AVCodecID
36-
if param.Type().Is(CODEC) {
37-
codec_id = param.Codec().ID()
38-
} else if param.Type().Is(VIDEO) {
39-
codec_id = ctx.Input().VideoCodec()
40-
} else if param.Type().Is(AUDIO) {
41-
codec_id = ctx.Input().AudioCodec()
42-
} else {
43-
return nil, ErrBadParameter.With("invalid stream parameters")
44-
45-
}
46-
47-
return nil, ErrNotImplemented
48-
}
49-
*/
5032
////////////////////////////////////////////////////////////////////////////////
5133
// PUBLIC METHODS
5234

writer.go

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package media
22

33
import (
4+
"encoding/json"
45
"errors"
6+
"fmt"
57
"io"
68

79
// Packages
@@ -15,9 +17,12 @@ import (
1517
// TYPES
1618

1719
type writer struct {
18-
t MediaType
19-
output *ff.AVFormatContext
20-
avio *ff.AVIOContextEx
20+
t MediaType
21+
output *ff.AVFormatContext
22+
avio *ff.AVIOContextEx
23+
metadata *ff.AVDictionary
24+
header bool
25+
encoder map[int]*encoder
2126
}
2227

2328
type writer_callback struct {
@@ -33,6 +38,12 @@ var _ Media = (*writer)(nil)
3338
func createMedia(url string, format Format, metadata []Metadata, params ...Parameters) (*writer, error) {
3439
writer := new(writer)
3540
writer.t = OUTPUT
41+
writer.encoder = make(map[int]*encoder, len(params))
42+
43+
// If there are no streams, then return an error
44+
if len(params) == 0 {
45+
return nil, ErrBadParameter.With("no streams specified for encoder")
46+
}
3647

3748
// Guess the output format
3849
var ofmt *ff.AVOutputFormat
@@ -53,17 +64,22 @@ func createMedia(url string, format Format, metadata []Metadata, params ...Param
5364
writer.output = ctx
5465
}
5566

56-
// Add streams
57-
/*
58-
for _, param := range params {
59-
stream, err := newWriterStream(ctx, param)
60-
if err != nil {
61-
return nil, errors.Join(err, writer.Close())
62-
} else {
63-
fmt.Println("TODO: STREAM", stream)
64-
}
67+
// Add encoders and streams
68+
var result error
69+
for i, param := range params {
70+
encoder, err := newEncoder(ctx, i, param)
71+
if err != nil {
72+
result = errors.Join(result, err)
73+
} else {
74+
writer.encoder[i] = encoder
6575
}
66-
*/
76+
}
77+
78+
// Return any errors from creating the streams
79+
if result != nil {
80+
return nil, errors.Join(result, writer.Close())
81+
}
82+
6783
// Open the output file, if needed
6884
if !ctx.Flags().Is(ff.AVFMT_NOFILE) {
6985
w, err := ff.AVFormat_avio_open(url, ff.AVIO_FLAG_WRITE)
@@ -75,9 +91,32 @@ func createMedia(url string, format Format, metadata []Metadata, params ...Param
7591
}
7692
}
7793

78-
// TODO: Metadata
94+
// Set metadata
95+
if len(metadata) > 0 {
96+
writer.metadata = ff.AVUtil_dict_alloc()
97+
if writer.metadata == nil {
98+
return nil, errors.Join(errors.New("unable to allocate metadata dictionary"), writer.Close())
99+
}
100+
for _, m := range metadata {
101+
// Ignore duration and artwork fields
102+
key := m.Key()
103+
if key == MetaArtwork || key == MetaDuration {
104+
continue
105+
}
106+
// Set dictionary entry
107+
if err := ff.AVUtil_dict_set(writer.metadata, key, fmt.Sprint(m.Value()), ff.AV_DICT_APPEND); err != nil {
108+
return nil, errors.Join(err, writer.Close())
109+
}
110+
}
111+
// TODO: Create artwork streams
112+
}
79113

80-
// TODO: Write the header
114+
// Write the header
115+
if err := ff.AVFormat_write_header(ctx, nil); err != nil {
116+
return nil, errors.Join(err, writer.Close())
117+
} else {
118+
writer.header = true
119+
}
81120

82121
// Return success
83122
return writer, nil
@@ -92,18 +131,54 @@ func createWriter(w io.Writer, format Format, metadata []Metadata, params ...Par
92131
func (w *writer) Close() error {
93132
var result error
94133

95-
// TODO: Write the trailer
134+
// Write the trailer if the header was written
135+
if w.header {
136+
if err := ff.AVFormat_write_trailer(w.output); err != nil {
137+
result = errors.Join(result, err)
138+
}
139+
}
140+
141+
// Close encoders
142+
for _, encoder := range w.encoder {
143+
result = errors.Join(result, encoder.Close())
144+
}
96145

97146
// Free resources
147+
if w.metadata != nil {
148+
ff.AVUtil_dict_free(w.metadata)
149+
}
150+
if w.output != nil {
151+
result = errors.Join(result, ff.AVFormat_close_writer(w.output))
152+
}
98153
if w.avio != nil {
99-
result = errors.Join(result, ff.AVFormat_avio_close(w.avio))
154+
fmt.Println("TODO AVIO")
155+
// result = errors.Join(result, ff.AVFormat_avio_close(w.avio))
100156
}
101-
result = errors.Join(result, ff.AVFormat_close_writer(w.output))
157+
158+
// Release resources
159+
w.encoder = nil
160+
w.metadata = nil
161+
w.avio = nil
162+
w.output = nil
102163

103164
// Return any errors
104165
return result
105166
}
106167

168+
////////////////////////////////////////////////////////////////////////////////
169+
// STRINGIFY
170+
171+
// Display the reader as a string
172+
func (w *writer) MarshalJSON() ([]byte, error) {
173+
return json.Marshal(w.output)
174+
}
175+
176+
// Display the reader as a string
177+
func (w *writer) String() string {
178+
data, _ := json.MarshalIndent(w, "", " ")
179+
return string(data)
180+
}
181+
107182
////////////////////////////////////////////////////////////////////////////////
108183
// PUBLIC METHODS
109184

0 commit comments

Comments
 (0)