Skip to content

Commit f31b51e

Browse files
committed
Added metadata and codec
1 parent 5b5d198 commit f31b51e

File tree

8 files changed

+637
-7
lines changed

8 files changed

+637
-7
lines changed

Makefile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,12 @@ test-ffmpeg: go-dep go-tidy ffmpeg chromaprint
161161
@${CGO_ENV} ${GO} test ./pkg/segmenter
162162
@echo ... test pkg/chromaprint
163163
@${CGO_ENV} ${GO} test ./pkg/chromaprint
164+
@echo ... test pkg/avcodec
165+
${CGO_ENV} ${GO} test ./pkg/avcodec
164166

165167

166168
# @echo ... test pkg/ffmpeg
167169
# @${GO} test -v ./pkg/ffmpeg
168-
# @echo ... test sys/chromaprint
169-
# @${GO} test ./sys/chromaprint
170-
# @echo ... test pkg/chromaprint
171-
# @${GO} test ./pkg/chromaprint
172170
# @echo ... test pkg/file
173171
# @${GO} test ./pkg/file
174172
# @echo ... test pkg/generator

pkg/avcodec/codec.go

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package avcodec
2+
3+
import (
4+
"encoding/json"
5+
6+
// Packages
7+
8+
media "github.com/mutablelogic/go-media"
9+
metadata "github.com/mutablelogic/go-media/pkg/metadata"
10+
ff "github.com/mutablelogic/go-media/sys/ffmpeg71"
11+
)
12+
13+
///////////////////////////////////////////////////////////////////////////////
14+
// TYPES
15+
16+
type Codec struct {
17+
codec *ff.AVCodec
18+
context *ff.AVCodecContext
19+
}
20+
21+
///////////////////////////////////////////////////////////////////////////////
22+
// LIFECYCLE
23+
24+
// Return all codecs. Codecs can be either encoders or decoders. The argument
25+
// can be ANY or any combination of INPUT, OUTPUT, VIDEO, AUDIO and SUBTITLE,
26+
// in order to return a subset of codecs.
27+
func Codecs(t media.Type) []media.Metadata {
28+
result := make([]media.Metadata, 0, 100)
29+
var opaque uintptr
30+
for {
31+
codec := ff.AVCodec_iterate(&opaque)
32+
if codec == nil {
33+
break
34+
}
35+
// Filter codecs by type
36+
if t.Is(media.INPUT) && !ff.AVCodec_is_decoder(codec) {
37+
continue
38+
}
39+
if t.Is(media.OUTPUT) && !ff.AVCodec_is_encoder(codec) {
40+
continue
41+
}
42+
if t.Is(media.VIDEO) && codec.Type() != ff.AVMEDIA_TYPE_VIDEO {
43+
continue
44+
}
45+
if t.Is(media.AUDIO) && codec.Type() != ff.AVMEDIA_TYPE_AUDIO {
46+
continue
47+
}
48+
if t.Is(media.SUBTITLE) && codec.Type() != ff.AVMEDIA_TYPE_SUBTITLE {
49+
continue
50+
}
51+
if codec.Capabilities().Is(ff.AV_CODEC_CAP_EXPERIMENTAL) {
52+
// Skip experimental codecs
53+
continue
54+
}
55+
result = append(result, metadata.New(codec.Name(), &Codec{codec, nil}))
56+
}
57+
return result
58+
}
59+
60+
// Return an encoder by name, with additional options. Call Close() to
61+
// release the codec context. Codec options are listed at
62+
// <https://ffmpeg.org/ffmpeg-codecs.html>
63+
func NewEncoder(name string, opts ...Opt) (*Codec, error) {
64+
ctx := new(Codec)
65+
66+
// Options
67+
o, err := applyOptions(opts)
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
// Codec context
73+
if codec := ff.AVCodec_find_encoder_by_name(name); codec == nil {
74+
return nil, media.ErrBadParameter.Withf("unknown codec %q", name)
75+
} else if context := ff.AVCodec_alloc_context(codec); context == nil {
76+
return nil, media.ErrInternalError.Withf("failed to allocate codec context for %q", name)
77+
} else if err := set_par(context, codec, o); err != nil {
78+
ff.AVCodec_free_context(context)
79+
return nil, err
80+
} else if ff.AVCodec_open(context, codec, nil); err != nil {
81+
ff.AVCodec_free_context(context)
82+
return nil, err
83+
} else {
84+
ctx.context = context
85+
}
86+
87+
// Return success
88+
return ctx, nil
89+
}
90+
91+
// Return a decoder by name, with additional options. Call Close() to
92+
// release the codec context. Codec options are listed at
93+
// <https://ffmpeg.org/ffmpeg-codecs.html>
94+
func NewDecoder(name string, opts ...Opt) (*Codec, error) {
95+
ctx := new(Codec)
96+
97+
// Options
98+
o, err := applyOptions(opts)
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
// Codec context
104+
if codec := ff.AVCodec_find_decoder_by_name(name); codec == nil {
105+
return nil, media.ErrBadParameter.Withf("unknown codec %q", name)
106+
} else if context := ff.AVCodec_alloc_context(codec); context == nil {
107+
return nil, media.ErrInternalError.Withf("failed to allocate codec context for %q", name)
108+
} else if err := set_par(context, codec, o); err != nil {
109+
ff.AVCodec_free_context(context)
110+
return nil, err
111+
} else if ff.AVCodec_open(context, codec, nil); err != nil {
112+
ff.AVCodec_free_context(context)
113+
return nil, err
114+
} else {
115+
ctx.context = context
116+
}
117+
118+
// Return success
119+
return ctx, nil
120+
}
121+
122+
// Release the codec resources
123+
func (ctx *Codec) Close() error {
124+
ctx.codec = nil
125+
if ctx != nil && ctx.context != nil {
126+
ff.AVCodec_free_context(ctx.context)
127+
ctx.context = nil
128+
}
129+
return nil
130+
}
131+
132+
////////////////////////////////////////////////////////////////////////////////
133+
// STRINGIFY
134+
135+
func (ctx *Codec) MarshalJSON() ([]byte, error) {
136+
if ctx != nil && ctx.codec != nil {
137+
return ctx.codec.MarshalJSON()
138+
}
139+
if ctx != nil && ctx.context != nil {
140+
return ctx.context.MarshalJSON()
141+
}
142+
return []byte("null"), nil
143+
}
144+
145+
func (ctx *Codec) String() string {
146+
data, err := json.MarshalIndent(ctx, "", " ")
147+
if err != nil {
148+
return err.Error()
149+
}
150+
return string(data)
151+
}
152+
153+
////////////////////////////////////////////////////////////////////////////////
154+
// PUBLIC METHODS
155+
156+
func (ctx *Codec) Type() media.Type {
157+
var t media.Type
158+
switch {
159+
case ctx != nil && ctx.codec != nil:
160+
if ff.AVCodec_is_decoder(ctx.codec) {
161+
t |= media.INPUT
162+
}
163+
if ff.AVCodec_is_encoder(ctx.codec) {
164+
t |= media.OUTPUT
165+
}
166+
t |= type2type(ctx.codec.Type())
167+
case ctx != nil && ctx.context != nil:
168+
if ff.AVCodec_is_decoder(ctx.context.Codec()) {
169+
t |= media.INPUT
170+
}
171+
if ff.AVCodec_is_encoder(ctx.context.Codec()) {
172+
t |= media.OUTPUT
173+
}
174+
t |= type2type(ctx.context.Codec().Type())
175+
}
176+
return t
177+
}
178+
179+
func (ctx *Codec) Name() string {
180+
switch {
181+
case ctx != nil && ctx.codec != nil:
182+
return ctx.codec.Name()
183+
case ctx != nil && ctx.context != nil:
184+
return ctx.context.Codec().Name()
185+
}
186+
return ""
187+
}
188+
189+
////////////////////////////////////////////////////////////////////////////////
190+
// PRIVATE METHODS
191+
192+
func type2type(t ff.AVMediaType) media.Type {
193+
switch t {
194+
case ff.AVMEDIA_TYPE_AUDIO:
195+
return media.AUDIO
196+
case ff.AVMEDIA_TYPE_VIDEO:
197+
return media.VIDEO
198+
case ff.AVMEDIA_TYPE_SUBTITLE:
199+
return media.SUBTITLE
200+
case ff.AVMEDIA_TYPE_ATTACHMENT:
201+
return media.DATA
202+
case ff.AVMEDIA_TYPE_DATA:
203+
return media.DATA
204+
}
205+
return media.NONE
206+
}
207+
208+
func set_par(ctx *ff.AVCodecContext, codec *ff.AVCodec, opt *opt) error {
209+
switch codec.Type() {
210+
case ff.AVMEDIA_TYPE_AUDIO:
211+
return set_audio_par(ctx, codec, opt)
212+
case ff.AVMEDIA_TYPE_VIDEO:
213+
return set_video_par(ctx, codec, opt)
214+
case ff.AVMEDIA_TYPE_SUBTITLE:
215+
return set_subtitle_par(ctx, codec, opt)
216+
}
217+
return nil
218+
}
219+
220+
func set_audio_par(ctx *ff.AVCodecContext, codec *ff.AVCodec, opt *opt) error {
221+
// Channel layout
222+
if ff.AVUtil_channel_layout_check(&opt.channel_layout) {
223+
ctx.SetChannelLayout(opt.channel_layout)
224+
} else if supported_layouts := codec.ChannelLayouts(); len(supported_layouts) > 0 {
225+
ctx.SetChannelLayout(supported_layouts[0])
226+
} else {
227+
ctx.SetChannelLayout(ff.AV_CHANNEL_LAYOUT_MONO)
228+
}
229+
230+
// Sample format
231+
if opt.sample_format != ff.AV_SAMPLE_FMT_NONE {
232+
ctx.SetSampleFormat(opt.sample_format)
233+
} else if supported_formats := codec.SampleFormats(); len(supported_formats) > 0 {
234+
ctx.SetSampleFormat(supported_formats[0])
235+
}
236+
237+
// Sample rate
238+
if opt.sample_rate > 0 {
239+
ctx.SetSampleRate(opt.sample_rate)
240+
} else if supported_rates := codec.SupportedSamplerates(); len(supported_rates) > 0 {
241+
ctx.SetSampleRate(supported_rates[0])
242+
}
243+
244+
// TODO: Time base
245+
ctx.SetTimeBase(ff.AVUtil_rational(1, ctx.SampleRate()))
246+
247+
return nil
248+
}
249+
250+
func set_video_par(ctx *ff.AVCodecContext, codec *ff.AVCodec, opt *opt) error {
251+
// Pixel Format
252+
if opt.pixel_format != ff.AV_PIX_FMT_NONE {
253+
ctx.SetPixFmt(opt.pixel_format)
254+
} else if supported_formats := codec.PixelFormats(); len(supported_formats) > 0 {
255+
ctx.SetPixFmt(supported_formats[0])
256+
} else {
257+
ctx.SetPixFmt(ff.AV_PIX_FMT_YUV420P)
258+
}
259+
260+
// Frame size
261+
if opt.width > 0 {
262+
ctx.SetWidth(opt.width)
263+
}
264+
if opt.height > 0 {
265+
ctx.SetHeight(opt.height)
266+
}
267+
268+
// Frame rate
269+
if !opt.frame_rate.IsZero() {
270+
ctx.SetFramerate(opt.frame_rate)
271+
} else if supported_rates := codec.SupportedFramerates(); len(supported_rates) > 0 {
272+
ctx.SetFramerate(supported_rates[0])
273+
}
274+
275+
// Time base
276+
if frame_rate := ctx.Framerate(); !frame_rate.IsZero() {
277+
ctx.SetTimeBase(ff.AVUtil_rational_invert(frame_rate))
278+
}
279+
280+
return nil
281+
}
282+
283+
func set_subtitle_par(ctx *ff.AVCodecContext, codec *ff.AVCodec, opt *opt) error {
284+
return nil
285+
}

pkg/avcodec/codec_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package avcodec_test
2+
3+
import (
4+
"testing"
5+
6+
// Packages
7+
media "github.com/mutablelogic/go-media"
8+
avcodec "github.com/mutablelogic/go-media/pkg/avcodec"
9+
assert "github.com/stretchr/testify/assert"
10+
)
11+
12+
func Test_codec_001(t *testing.T) {
13+
assert := assert.New(t)
14+
15+
codecs := avcodec.Codecs(media.OUTPUT | media.AUDIO)
16+
assert.NotNil(codecs)
17+
for _, meta := range codecs {
18+
assert.NotNil(meta)
19+
codec, err := avcodec.NewEncoder(meta.Key())
20+
defer codec.Close()
21+
22+
if assert.NoError(err) {
23+
assert.NotNil(codec)
24+
assert.Equal(media.AUDIO, codec.Type())
25+
t.Log(codec)
26+
}
27+
}
28+
}
29+
30+
func Test_codec_002(t *testing.T) {
31+
assert := assert.New(t)
32+
33+
codecs := avcodec.Codecs(media.OUTPUT | media.VIDEO)
34+
assert.NotNil(codecs)
35+
for _, meta := range codecs {
36+
assert.NotNil(meta)
37+
codec, err := avcodec.NewEncoder(meta.Key())
38+
defer codec.Close()
39+
40+
if assert.NoError(err) {
41+
assert.NotNil(codec)
42+
assert.Equal(media.VIDEO, codec.Type())
43+
t.Log(codec)
44+
}
45+
}
46+
}

pkg/avcodec/doc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/*
2+
avcodec is roughly a wrapper around FFmpeg's libavcodec library, which provides encoding,
3+
decoding and transcoding of audio and video streams.
4+
*/
5+
package avcodec

0 commit comments

Comments
 (0)