Skip to content

Commit 0402cad

Browse files
committed
Merge branch 'ffmpeg61' of github.com:mutablelogic/go-media into ffmpeg61
2 parents cbc6fc2 + 9c2694f commit 0402cad

23 files changed

+1334
-269
lines changed

pkg/ffmpeg/encoder.go

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
package ffmpeg
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"syscall"
9+
10+
// Packages
11+
ff "github.com/mutablelogic/go-media/sys/ffmpeg61"
12+
13+
// Namespace imports
14+
. "github.com/djthorpe/go-errors"
15+
)
16+
17+
////////////////////////////////////////////////////////////////////////////////
18+
// TYPES
19+
20+
type Encoder struct {
21+
ctx *ff.AVCodecContext
22+
stream *ff.AVStream
23+
packet *ff.AVPacket
24+
25+
// We are flushing the encoder
26+
eof bool
27+
28+
// The next presentation timestamp
29+
next_pts int64
30+
}
31+
32+
// EncoderFrameFn is a function which is called to receive a frame to encode. It should
33+
// return nil to continue encoding or io.EOF to stop encoding.
34+
type EncoderFrameFn func(int) (*ff.AVFrame, error)
35+
36+
// EncoderPacketFn is a function which is called for each packet encoded, with
37+
// the stream timebase.
38+
type EncoderPacketFn func(*ff.AVPacket, *ff.AVRational) error
39+
40+
////////////////////////////////////////////////////////////////////////////////
41+
// LIFECYCLE
42+
43+
// Create an encoder with the given parameters
44+
func NewEncoder(ctx *ff.AVFormatContext, stream int, par *Par) (*Encoder, error) {
45+
encoder := new(Encoder)
46+
47+
// Get codec
48+
codec_id := ff.AV_CODEC_ID_NONE
49+
switch par.CodecType() {
50+
case ff.AVMEDIA_TYPE_AUDIO:
51+
codec_id = ctx.Output().AudioCodec()
52+
case ff.AVMEDIA_TYPE_VIDEO:
53+
codec_id = ctx.Output().VideoCodec()
54+
case ff.AVMEDIA_TYPE_SUBTITLE:
55+
codec_id = ctx.Output().SubtitleCodec()
56+
}
57+
if codec_id == ff.AV_CODEC_ID_NONE {
58+
return nil, ErrBadParameter.Withf("no codec specified for stream %v", stream)
59+
}
60+
61+
// Allocate codec
62+
codec := ff.AVCodec_find_encoder(codec_id)
63+
if codec == nil {
64+
return nil, ErrBadParameter.Withf("codec %q cannot encode", codec_id)
65+
}
66+
if codecctx := ff.AVCodec_alloc_context(codec); codecctx == nil {
67+
return nil, ErrInternalAppError.With("could not allocate audio codec context")
68+
} else {
69+
encoder.ctx = codecctx
70+
}
71+
72+
// Check codec against parameters and set defaults as needed, then
73+
// copy back to codec
74+
if err := par.ValidateFromCodec(encoder.ctx); err != nil {
75+
ff.AVCodec_free_context(encoder.ctx)
76+
return nil, err
77+
} else if err := par.CopyToCodec(encoder.ctx); err != nil {
78+
ff.AVCodec_free_context(encoder.ctx)
79+
return nil, err
80+
}
81+
82+
// Create the stream
83+
if streamctx := ff.AVFormat_new_stream(ctx, nil); streamctx == nil {
84+
ff.AVCodec_free_context(encoder.ctx)
85+
return nil, ErrInternalAppError.With("could not allocate stream")
86+
} else {
87+
streamctx.SetId(stream)
88+
encoder.stream = streamctx
89+
}
90+
91+
// Some formats want stream headers to be separate.
92+
if ctx.Output().Flags().Is(ff.AVFMT_GLOBALHEADER) {
93+
encoder.ctx.SetFlags(encoder.ctx.Flags() | ff.AV_CODEC_FLAG_GLOBAL_HEADER)
94+
}
95+
96+
// Open it
97+
if err := ff.AVCodec_open(encoder.ctx, codec, nil); err != nil {
98+
ff.AVCodec_free_context(encoder.ctx)
99+
return nil, ErrInternalAppError.Withf("codec_open: %v", err)
100+
}
101+
102+
// Copy parameters to stream
103+
if err := ff.AVCodec_parameters_from_context(encoder.stream.CodecPar(), encoder.ctx); err != nil {
104+
ff.AVCodec_free_context(encoder.ctx)
105+
return nil, err
106+
}
107+
108+
// Create a packet
109+
packet := ff.AVCodec_packet_alloc()
110+
if packet == nil {
111+
ff.AVCodec_free_context(encoder.ctx)
112+
return nil, errors.New("failed to allocate packet")
113+
} else {
114+
encoder.packet = packet
115+
}
116+
117+
// Return it
118+
return encoder, nil
119+
}
120+
121+
func (encoder *Encoder) Close() error {
122+
// Free respurces
123+
if encoder.ctx != nil {
124+
ff.AVCodec_free_context(encoder.ctx)
125+
}
126+
if encoder.packet != nil {
127+
ff.AVCodec_packet_free(encoder.packet)
128+
}
129+
130+
// Release resources
131+
encoder.packet = nil
132+
encoder.stream = nil
133+
encoder.ctx = nil
134+
135+
// Return success
136+
return nil
137+
}
138+
139+
////////////////////////////////////////////////////////////////////////////////
140+
// STRINGIFY
141+
142+
func (e *Encoder) MarshalJSON() ([]byte, error) {
143+
type jsonEncoder struct {
144+
Codec *ff.AVCodecContext `json:"codec"`
145+
Stream *ff.AVStream `json:"stream"`
146+
}
147+
return json.Marshal(&jsonEncoder{
148+
Codec: e.ctx,
149+
Stream: e.stream,
150+
})
151+
}
152+
153+
func (e *Encoder) String() string {
154+
data, _ := json.MarshalIndent(e, "", " ")
155+
return string(data)
156+
}
157+
158+
//////////////////////////////////////////////////////////////////////////////
159+
// PUBLIC METHODS
160+
161+
// Encode a frame and pass packets to the EncoderPacketFn. If the frame is nil, then
162+
// the encoder will flush any remaining packets. If io.EOF is returned then
163+
// it indicates that the encoder has ended prematurely.
164+
func (e *Encoder) Encode(frame *ff.AVFrame, fn EncoderPacketFn) error {
165+
if fn == nil {
166+
return ErrBadParameter.With("nil fn")
167+
}
168+
// Encode a frame (or flush the encoder)
169+
return e.encode(frame, fn)
170+
}
171+
172+
// Return the codec parameters
173+
func (e *Encoder) Par() *Par {
174+
par := new(Par)
175+
if err := ff.AVCodec_parameters_from_context(&par.AVCodecParameters, e.ctx); err != nil {
176+
return nil
177+
} else {
178+
return par
179+
}
180+
}
181+
182+
// Return the codec type
183+
func (e *Encoder) nextPts(frame *ff.AVFrame) int64 {
184+
next_pts := int64(0)
185+
switch e.ctx.Codec().Type() {
186+
case ff.AVMEDIA_TYPE_AUDIO:
187+
next_pts = ff.AVUtil_rational_rescale_q(int64(frame.NumSamples()), ff.AVUtil_rational(1, frame.SampleRate()), e.stream.TimeBase())
188+
case ff.AVMEDIA_TYPE_VIDEO:
189+
next_pts = ff.AVUtil_rational_rescale_q(1, ff.AVUtil_rational_invert(e.ctx.Framerate()), e.stream.TimeBase())
190+
default:
191+
// Dunno what to do with subtitle and data streams yet
192+
fmt.Println("TODO: next_pts for subtitle and data streams")
193+
return 0
194+
}
195+
return next_pts
196+
}
197+
198+
//////////////////////////////////////////////////////////////////////////////
199+
// PRIVATE METHODS
200+
201+
func (e *Encoder) encode(frame *ff.AVFrame, fn EncoderPacketFn) error {
202+
timebase := e.stream.TimeBase()
203+
204+
// Send the frame to the encoder
205+
if err := ff.AVCodec_send_frame(e.ctx, frame); err != nil {
206+
return err
207+
}
208+
209+
// Write out the packets
210+
var result error
211+
for {
212+
// Receive the packet
213+
if err := ff.AVCodec_receive_packet(e.ctx, e.packet); errors.Is(err, syscall.EAGAIN) || errors.Is(err, io.EOF) {
214+
// Finished receiving packet or EOF
215+
break
216+
} else if err != nil {
217+
return err
218+
}
219+
220+
// rescale output packet timestamp values from codec to stream timebase
221+
ff.AVCodec_packet_rescale_ts(e.packet, e.ctx.TimeBase(), e.stream.TimeBase())
222+
e.packet.SetStreamIndex(e.stream.Index())
223+
e.packet.SetTimeBase(e.stream.TimeBase())
224+
225+
// Pass back to the caller
226+
if err := fn(e.packet, &timebase); errors.Is(err, io.EOF) {
227+
// End early, return EOF
228+
result = io.EOF
229+
break
230+
} else if err != nil {
231+
return err
232+
}
233+
234+
// Re-allocate frames for next iteration
235+
ff.AVCodec_packet_unref(e.packet)
236+
}
237+
238+
// Flush
239+
if result == nil {
240+
result = fn(nil, &timebase)
241+
}
242+
243+
// Return success or EOF
244+
return result
245+
}

pkg/ffmpeg/metadata.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package ffmpeg
2+
3+
import (
4+
"encoding/json"
5+
)
6+
7+
////////////////////////////////////////////////////////////////////////////////
8+
// TYPES
9+
10+
type Metadata struct {
11+
Key string `json:"key" writer:",width:30"`
12+
Value any `json:"value,omitempty" writer:",wrap,width:50"`
13+
}
14+
15+
const (
16+
MetaArtwork = "artwork" // Metadata key for artwork, set the value as []byte
17+
)
18+
19+
////////////////////////////////////////////////////////////////////////////////
20+
// LIFECYCLE
21+
22+
func NewMetadata(key string, value any) *Metadata {
23+
return &Metadata{
24+
Key: key,
25+
Value: value,
26+
}
27+
}
28+
29+
////////////////////////////////////////////////////////////////////////////////
30+
// STRINIGY
31+
32+
func (m *Metadata) String() string {
33+
data, _ := json.MarshalIndent(m, "", " ")
34+
return string(data)
35+
}

0 commit comments

Comments
 (0)