Skip to content

Commit 47dcd33

Browse files
authored
Merge pull request #47 from mutablelogic/avfilter
Added AVFilter
2 parents 629d263 + 48fd906 commit 47dcd33

File tree

19 files changed

+1245
-33
lines changed

19 files changed

+1245
-33
lines changed

Makefile

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Paths to packages
22
GO=$(shell which go)
33
DOCKER=$(shell which docker)
4+
PKG_CONFIG=$(shell which pkg-config)
45

56
# Source version
67
FFMPEG_VERSION=ffmpeg-7.1.1
@@ -63,13 +64,12 @@ ${BUILD_DIR}/${FFMPEG_VERSION}:
6364

6465
# Configure ffmpeg
6566
.PHONY: ffmpeg-configure
66-
ffmpeg-configure: mkdir ${BUILD_DIR}/${FFMPEG_VERSION} ffmpeg-dep
67+
ffmpeg-configure: mkdir pkconfig-dep ${BUILD_DIR}/${FFMPEG_VERSION} ffmpeg-dep
6768
@echo "Configuring ${FFMPEG_VERSION} => ${PREFIX}"
6869
@cd ${BUILD_DIR}/${FFMPEG_VERSION} && ./configure \
69-
--enable-static --disable-doc --disable-programs \
70+
--disable-doc --disable-programs \
7071
--prefix="$(shell realpath ${PREFIX})" \
71-
--pkg-config-flags="--static" \
72-
--extra-libs="-lpthread" \
72+
--enable-static --pkg-config="${PKG_CONFIG}" --pkg-config-flags="--static" --extra-libs="-lpthread" \
7373
--enable-gpl --enable-nonfree ${FFMPEG_CONFIG}
7474

7575
# Build ffmpeg
@@ -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
@@ -198,6 +196,11 @@ go-dep:
198196
docker-dep:
199197
@test -f "${DOCKER}" && test -x "${DOCKER}" || (echo "Missing docker binary" && exit 1)
200198

199+
.PHONY: pkconfig-dep
200+
pkconfig-dep:
201+
@test -f "${PKG_CONFIG}" && test -x "${PKG_CONFIG}" || (echo "Missing pkg-config binary" && exit 1)
202+
203+
201204
.PHONY: mkdir
202205
mkdir:
203206
@echo Mkdir ${BUILD_DIR}
@@ -218,15 +221,18 @@ clean: go-tidy
218221
# Check for FFmpeg dependencies
219222
.PHONY: ffmpeg-dep
220223
ffmpeg-dep:
221-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists libass && echo "--enable-libass"))
222-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists fdk-aac && echo "--enable-libfdk-aac"))
223-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists lame && echo "--enable-libmp3lame"))
224-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists freetype2 && echo "--enable-libfreetype"))
225-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists theora && echo "--enable-libtheora"))
226-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists vorbis && echo "--enable-libvorbis"))
227-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists opus && echo "--enable-libopus"))
228-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists x264 && echo "--enable-libx264"))
229-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists x265 && echo "--enable-libx265"))
230-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists xvid && echo "--enable-libxvid"))
231-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists vpx && echo "--enable-libvpx"))
224+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists libass && echo "--enable-libass"))
225+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists fdk-aac && echo "--enable-libfdk-aac"))
226+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists lame && echo "--enable-libmp3lame"))
227+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists freetype2 && echo "--enable-libfreetype"))
228+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists vorbis && echo "--enable-libvorbis"))
229+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists opus && echo "--enable-libopus"))
230+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists x264 && echo "--enable-libx264"))
231+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists x265 && echo "--enable-libx265"))
232+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists xvid && echo "--enable-libxvid"))
233+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists vpx && echo "--enable-libvpx"))
234+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists libgcrypt && echo "--enable-gcrypt"))
235+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists aom && echo "--enable-libaom"))
236+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists libbluray && echo "--enable-libbluray"))
237+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell ${PKG_CONFIG} --exists dav1d && echo "--enable-libdav1d"))
232238
@echo "FFmpeg configuration: $(FFMPEG_CONFIG)"

pkg/avcodec/codec.go

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
package avcodec
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
// Packages
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+
// NewEncodingCodec returns an encoder by name. Options for encoding can be passed.
61+
// Call Close() to release the codec. Codec options are listed at
62+
// <https://ffmpeg.org/ffmpeg-codecs.html>
63+
func NewEncodingCodec(name string, opts ...Opt) (*Codec, error) {
64+
return newCodec(ff.AVCodec_find_encoder_by_name(name), opts...)
65+
}
66+
67+
// NewDecodingCodec returns a decoder by name. Options for decoding can be passed.
68+
// Call Close() to release the codec context. Codec options are listed at
69+
// <https://ffmpeg.org/ffmpeg-codecs.html>
70+
func NewDecodingCodec(name string, opts ...Opt) (*Codec, error) {
71+
return newCodec(ff.AVCodec_find_decoder_by_name(name), opts...)
72+
}
73+
74+
func newCodec(codec *ff.AVCodec, opts ...Opt) (*Codec, error) {
75+
ctx := new(Codec)
76+
77+
// Check parameters
78+
if codec == nil {
79+
return nil, media.ErrBadParameter.Withf("unknown codec %q", codec.Name())
80+
}
81+
82+
// Apply options
83+
o, err := applyOptions(opts)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
// Create a dictionary of codec options
89+
dict := ff.AVUtil_dict_alloc()
90+
defer ff.AVUtil_dict_free(dict)
91+
for _, opt := range o.meta {
92+
if err := ff.AVUtil_dict_set(dict, opt.Key(), opt.Value(), ff.AV_DICT_APPEND); err != nil {
93+
ff.AVUtil_dict_free(dict)
94+
return nil, err
95+
}
96+
}
97+
98+
// Codec context
99+
if context := ff.AVCodec_alloc_context(codec); context == nil {
100+
return nil, media.ErrInternalError.Withf("failed to allocate codec context for %q", codec.Name())
101+
} else if err := set_par(context, codec, o); err != nil {
102+
ff.AVCodec_free_context(context)
103+
return nil, err
104+
} else if err := ff.AVCodec_open(context, codec, dict); err != nil {
105+
ff.AVCodec_free_context(context)
106+
return nil, err
107+
} else {
108+
ctx.context = context
109+
}
110+
111+
// TODO: Get the options which were not consumed
112+
fmt.Println("TODO: Codec options not consumed:", dict)
113+
114+
// Return success
115+
return ctx, nil
116+
}
117+
118+
// Release the codec resources
119+
func (ctx *Codec) Close() error {
120+
ctx.codec = nil
121+
if ctx != nil && ctx.context != nil {
122+
ff.AVCodec_free_context(ctx.context)
123+
ctx.context = nil
124+
}
125+
return nil
126+
}
127+
128+
////////////////////////////////////////////////////////////////////////////////
129+
// STRINGIFY
130+
131+
func (ctx *Codec) MarshalJSON() ([]byte, error) {
132+
if ctx != nil && ctx.codec != nil {
133+
return ctx.codec.MarshalJSON()
134+
}
135+
if ctx != nil && ctx.context != nil {
136+
return ctx.context.MarshalJSON()
137+
}
138+
return []byte("null"), nil
139+
}
140+
141+
func (ctx *Codec) String() string {
142+
data, err := json.MarshalIndent(ctx, "", " ")
143+
if err != nil {
144+
return err.Error()
145+
}
146+
return string(data)
147+
}
148+
149+
////////////////////////////////////////////////////////////////////////////////
150+
// PUBLIC METHODS
151+
152+
func (ctx *Codec) Type() media.Type {
153+
var t media.Type
154+
switch {
155+
case ctx != nil && ctx.codec != nil:
156+
if ff.AVCodec_is_decoder(ctx.codec) {
157+
t |= media.INPUT
158+
}
159+
if ff.AVCodec_is_encoder(ctx.codec) {
160+
t |= media.OUTPUT
161+
}
162+
t |= type2type(ctx.codec.Type())
163+
case ctx != nil && ctx.context != nil:
164+
if ff.AVCodec_is_decoder(ctx.context.Codec()) {
165+
t |= media.INPUT
166+
}
167+
if ff.AVCodec_is_encoder(ctx.context.Codec()) {
168+
t |= media.OUTPUT
169+
}
170+
t |= type2type(ctx.context.Codec().Type())
171+
}
172+
return t
173+
}
174+
175+
func (ctx *Codec) Name() string {
176+
switch {
177+
case ctx != nil && ctx.codec != nil:
178+
return ctx.codec.Name()
179+
case ctx != nil && ctx.context != nil:
180+
return ctx.context.Codec().Name()
181+
}
182+
return ""
183+
}
184+
185+
////////////////////////////////////////////////////////////////////////////////
186+
// PRIVATE METHODS
187+
188+
func type2type(t ff.AVMediaType) media.Type {
189+
switch t {
190+
case ff.AVMEDIA_TYPE_AUDIO:
191+
return media.AUDIO
192+
case ff.AVMEDIA_TYPE_VIDEO:
193+
return media.VIDEO
194+
case ff.AVMEDIA_TYPE_SUBTITLE:
195+
return media.SUBTITLE
196+
case ff.AVMEDIA_TYPE_ATTACHMENT:
197+
return media.DATA
198+
case ff.AVMEDIA_TYPE_DATA:
199+
return media.DATA
200+
}
201+
return media.NONE
202+
}
203+
204+
func set_par(ctx *ff.AVCodecContext, codec *ff.AVCodec, opt *opt) error {
205+
switch codec.Type() {
206+
case ff.AVMEDIA_TYPE_AUDIO:
207+
return set_audio_par(ctx, codec, opt)
208+
case ff.AVMEDIA_TYPE_VIDEO:
209+
return set_video_par(ctx, codec, opt)
210+
case ff.AVMEDIA_TYPE_SUBTITLE:
211+
return set_subtitle_par(ctx, codec, opt)
212+
}
213+
return nil
214+
}
215+
216+
func set_audio_par(ctx *ff.AVCodecContext, codec *ff.AVCodec, opt *opt) error {
217+
// Channel layout
218+
if ff.AVUtil_channel_layout_check(&opt.channel_layout) {
219+
ctx.SetChannelLayout(opt.channel_layout)
220+
} else if supported_layouts := codec.ChannelLayouts(); len(supported_layouts) > 0 {
221+
ctx.SetChannelLayout(supported_layouts[0])
222+
} else {
223+
ctx.SetChannelLayout(ff.AV_CHANNEL_LAYOUT_MONO)
224+
}
225+
226+
// Sample format
227+
if opt.sample_format != ff.AV_SAMPLE_FMT_NONE {
228+
ctx.SetSampleFormat(opt.sample_format)
229+
} else if supported_formats := codec.SampleFormats(); len(supported_formats) > 0 {
230+
ctx.SetSampleFormat(supported_formats[0])
231+
}
232+
233+
// Sample rate
234+
if opt.sample_rate > 0 {
235+
ctx.SetSampleRate(opt.sample_rate)
236+
} else if supported_rates := codec.SupportedSamplerates(); len(supported_rates) > 0 {
237+
ctx.SetSampleRate(supported_rates[0])
238+
}
239+
240+
// TODO: Time base
241+
ctx.SetTimeBase(ff.AVUtil_rational(1, ctx.SampleRate()))
242+
243+
return nil
244+
}
245+
246+
func set_video_par(ctx *ff.AVCodecContext, codec *ff.AVCodec, opt *opt) error {
247+
// Pixel Format
248+
if opt.pixel_format != ff.AV_PIX_FMT_NONE {
249+
ctx.SetPixFmt(opt.pixel_format)
250+
} else if supported_formats := codec.PixelFormats(); len(supported_formats) > 0 {
251+
ctx.SetPixFmt(supported_formats[0])
252+
} else {
253+
ctx.SetPixFmt(ff.AV_PIX_FMT_YUV420P)
254+
}
255+
256+
// Frame size
257+
if opt.width > 0 {
258+
ctx.SetWidth(opt.width)
259+
}
260+
if opt.height > 0 {
261+
ctx.SetHeight(opt.height)
262+
}
263+
264+
// Frame rate
265+
if !opt.frame_rate.IsZero() {
266+
ctx.SetFramerate(opt.frame_rate)
267+
} else if supported_rates := codec.SupportedFramerates(); len(supported_rates) > 0 {
268+
ctx.SetFramerate(supported_rates[0])
269+
}
270+
271+
// Time base
272+
if frame_rate := ctx.Framerate(); !frame_rate.IsZero() {
273+
ctx.SetTimeBase(ff.AVUtil_rational_invert(frame_rate))
274+
}
275+
276+
return nil
277+
}
278+
279+
func set_subtitle_par(ctx *ff.AVCodecContext, codec *ff.AVCodec, opt *opt) error {
280+
return nil
281+
}

0 commit comments

Comments
 (0)