From 8bf2b046a288408942e0ce1fca3e70cb82299cf2 Mon Sep 17 00:00:00 2001 From: Quentin Renard Date: Tue, 10 Dec 2024 16:46:30 +0100 Subject: [PATCH] Added format/codec context private data + options --- codec_context.go | 5 ++++ codec_context_test.go | 1 + flags.go | 20 +++++++++++++ flags_test.go | 9 ++++++ format_context.go | 5 ++++ format_context_test.go | 1 + internal/cmd/flags/main.go | 1 + option.c | 12 ++++++++ option.go | 60 ++++++++++++++++++++++++++++++++++++++ option.h | 1 + option_search_flag.go | 12 ++++++++ option_test.go | 28 ++++++++++++++++++ private_data.go | 18 ++++++++++++ 13 files changed, 173 insertions(+) create mode 100644 option.c create mode 100644 option.go create mode 100644 option.h create mode 100644 option_search_flag.go create mode 100644 option_test.go create mode 100644 private_data.go diff --git a/codec_context.go b/codec_context.go index 7424110..d92215f 100644 --- a/codec_context.go +++ b/codec_context.go @@ -210,6 +210,11 @@ func (cc *CodecContext) SetPixelFormat(pixFmt PixelFormat) { cc.c.pix_fmt = C.enum_AVPixelFormat(pixFmt) } +// https://ffmpeg.org/doxygen/7.0/structAVCodecContext.html#af3379123060ad8cc9c321c29af4f8360 +func (cc *CodecContext) PrivateData() *PrivateData { + return newPrivateDataFromC(cc.c.priv_data) +} + // https://ffmpeg.org/doxygen/7.0/structAVCodecContext.html#a7abe7095de73df98df4895bf9e25fc6b func (cc *CodecContext) Profile() Profile { return Profile(cc.c.profile) diff --git a/codec_context_test.go b/codec_context_test.go index 3c3c4cc..6f18b6c 100644 --- a/codec_context_test.go +++ b/codec_context_test.go @@ -35,6 +35,7 @@ func TestCodecContext(t *testing.T) { require.Equal(t, Level(13), cc1.Level()) require.Equal(t, MediaTypeVideo, cc1.MediaType()) require.Equal(t, PixelFormatYuv420P, cc1.PixelFormat()) + require.NotNil(t, cc1.PrivateData()) require.Equal(t, ProfileH264ConstrainedBaseline, cc1.Profile()) require.Equal(t, NewRational(1, 1), cc1.SampleAspectRatio()) require.Equal(t, StrictStdComplianceNormal, cc1.StrictStdCompliance()) diff --git a/flags.go b/flags.go index 6007100..30cd6c9 100644 --- a/flags.go +++ b/flags.go @@ -245,6 +245,26 @@ func (fs IOFormatFlags) Del(f IOFormatFlag) IOFormatFlags { func (fs IOFormatFlags) Has(f IOFormatFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } +type OptionSearchFlags astikit.BitFlags + +func NewOptionSearchFlags(fs ...OptionSearchFlag) OptionSearchFlags { + o := OptionSearchFlags(0) + for _, f := range fs { + o = o.Add(f) + } + return o +} + +func (fs OptionSearchFlags) Add(f OptionSearchFlag) OptionSearchFlags { + return OptionSearchFlags(astikit.BitFlags(fs).Add(uint64(f))) +} + +func (fs OptionSearchFlags) Del(f OptionSearchFlag) OptionSearchFlags { + return OptionSearchFlags(astikit.BitFlags(fs).Del(uint64(f))) +} + +func (fs OptionSearchFlags) Has(f OptionSearchFlag) bool { return astikit.BitFlags(fs).Has(uint64(f)) } + type PacketFlags astikit.BitFlags func NewPacketFlags(fs ...PacketFlag) PacketFlags { diff --git a/flags_test.go b/flags_test.go index cc7acfd..2aaa75c 100644 --- a/flags_test.go +++ b/flags_test.go @@ -114,6 +114,15 @@ func TestIOFormatFlags(t *testing.T) { require.False(t, fs.Has(IOFormatFlag(2))) } +func TestOptionSearchFlags(t *testing.T) { + fs := NewOptionSearchFlags(OptionSearchFlag(1)) + require.True(t, fs.Has(OptionSearchFlag(1))) + fs = fs.Add(OptionSearchFlag(2)) + require.True(t, fs.Has(OptionSearchFlag(2))) + fs = fs.Del(OptionSearchFlag(2)) + require.False(t, fs.Has(OptionSearchFlag(2))) +} + func TestPacketFlags(t *testing.T) { fs := NewPacketFlags(PacketFlag(1)) require.True(t, fs.Has(PacketFlag(1))) diff --git a/format_context.go b/format_context.go index 385f94a..1c191f9 100644 --- a/format_context.go +++ b/format_context.go @@ -186,6 +186,11 @@ func (fc *FormatContext) SetPb(i *IOContext) { fc.c.pb = i.c } +// https://ffmpeg.org/doxygen/7.0/structAVFormatContext.html#ac4c0777e54085af2f3f1b27130e2b21b +func (fc *FormatContext) PrivateData() *PrivateData { + return newPrivateDataFromC(fc.c.priv_data) +} + // https://ffmpeg.org/doxygen/7.0/structAVFormatContext.html#a2590129e00adfa726ab2033a10e905e9 func (fc *FormatContext) StartTime() int64 { return int64(fc.c.start_time) diff --git a/format_context_test.go b/format_context_test.go index 6046c73..f65f680 100644 --- a/format_context_test.go +++ b/format_context_test.go @@ -24,6 +24,7 @@ func TestFormatContext(t *testing.T) { require.Equal(t, IOContextFlags(0), fc1.IOFlags()) require.Equal(t, int64(0), fc1.MaxAnalyzeDuration()) require.Equal(t, "isom", fc1.Metadata().Get("major_brand", nil, NewDictionaryFlags()).Value()) + require.NotNil(t, fc1.PrivateData()) require.Equal(t, int64(0), fc1.StartTime()) require.Equal(t, 2, fc1.NbStreams()) require.Len(t, fc1.Streams(), 2) diff --git a/internal/cmd/flags/main.go b/internal/cmd/flags/main.go index 4e968a3..72172c6 100644 --- a/internal/cmd/flags/main.go +++ b/internal/cmd/flags/main.go @@ -26,6 +26,7 @@ var list = []listItem{ {Name: "FormatEvent"}, {Name: "IOContext"}, {Name: "IOFormat"}, + {Name: "OptionSearch"}, {Name: "Packet"}, {Name: "Seek"}, {Name: "SoftwareScaleContext"}, diff --git a/option.c b/option.c new file mode 100644 index 0000000..969c1be --- /dev/null +++ b/option.c @@ -0,0 +1,12 @@ +#include + +int astiavOptionGet(void *obj, const char *name, const char **value, int flags) +{ + uint8_t *v = NULL; + int ret = av_opt_get(obj, name, flags, &v); + if (ret < 0) { + return ret; + } + *value = (const char *)v; + return 0; +} \ No newline at end of file diff --git a/option.go b/option.go new file mode 100644 index 0000000..4fb21f3 --- /dev/null +++ b/option.go @@ -0,0 +1,60 @@ +package astiav + +//#include +//#include "option.h" +import "C" +import ( + "unsafe" +) + +type Options struct { + c unsafe.Pointer +} + +func newOptionsFromC(c unsafe.Pointer) *Options { + if c == nil { + return nil + } + return &Options{c: c} +} + +type Option struct { + Name string +} + +// https://www.ffmpeg.org/doxygen/7.0/group__opt__mng.html#gabc75970cd87d1bf47a4ff449470e9225 +func (os *Options) List() (list []Option) { + var prev *C.AVOption + for { + o := C.av_opt_next(os.c, prev) + if o == nil { + return + } + list = append(list, Option{Name: C.GoString(o.name)}) + prev = o + } +} + +// https://www.ffmpeg.org/doxygen/7.0/group__opt__set__funcs.html#ga5fd4b92bdf4f392a2847f711676a7537 +func (os *Options) Set(name, value string, f OptionSearchFlags) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + cvalue := C.CString(value) + defer C.free(unsafe.Pointer(cvalue)) + return newError(C.av_opt_set(os.c, cname, cvalue, C.int(f))) +} + +// https://www.ffmpeg.org/doxygen/7.0/group__opt__get__funcs.html#gaf31144e60f9ce89dbe8cbea57a0b232c +func (os *Options) Get(name string, f OptionSearchFlags) (string, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + var cvalue *C.char = nil + if err := newError(C.astiavOptionGet(os.c, cname, &cvalue, C.int(f))); err != nil { + return "", err + } + if cvalue == nil { + return "", nil + } + defer C.av_freep(unsafe.Pointer(&cvalue)) + return C.GoString(cvalue), nil +} diff --git a/option.h b/option.h new file mode 100644 index 0000000..1f7d54f --- /dev/null +++ b/option.h @@ -0,0 +1 @@ +int astiavOptionGet(void *obj, const char *name, const char **value, int flags); \ No newline at end of file diff --git a/option_search_flag.go b/option_search_flag.go new file mode 100644 index 0000000..37ce413 --- /dev/null +++ b/option_search_flag.go @@ -0,0 +1,12 @@ +package astiav + +//#include +import "C" + +// https://ffmpeg.org/doxygen/7.0/group__opt__mng.html#ga25801ba4fc9b5313eb33ec84e082dd72 +type OptionSearchFlag int64 + +const ( + OptionSearchFlagChildren = CodecContextFlag(C.AV_OPT_SEARCH_CHILDREN) + OptionSearchFlagFakeObject = CodecContextFlag(C.AV_OPT_SEARCH_FAKE_OBJ) +) diff --git a/option_test.go b/option_test.go new file mode 100644 index 0000000..e5656fc --- /dev/null +++ b/option_test.go @@ -0,0 +1,28 @@ +package astiav + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOption(t *testing.T) { + fc, err := AllocOutputFormatContext(nil, "mp4", "") + require.NoError(t, err) + pd := fc.PrivateData() + require.NotNil(t, pd) + os := pd.Options() + require.NotNil(t, os) + require.Equal(t, []Option{{Name: "brand"}, {Name: "empty_hdlr_name"}, {Name: "encryption_key"}, {Name: "encryption_kid"}, {Name: "encryption_scheme"}, {Name: "frag_duration"}, {Name: "frag_interleave"}, {Name: "frag_size"}, {Name: "fragment_index"}, {Name: "iods_audio_profile"}, {Name: "iods_video_profile"}, {Name: "ism_lookahead"}, {Name: "movflags"}, {Name: "cmaf"}, {Name: "dash"}, {Name: "default_base_moof"}, {Name: "delay_moov"}, {Name: "disable_chpl"}, {Name: "empty_moov"}, {Name: "faststart"}, {Name: "frag_custom"}, {Name: "frag_discont"}, {Name: "frag_every_frame"}, {Name: "frag_keyframe"}, {Name: "global_sidx"}, {Name: "isml"}, {Name: "moov_size"}, {Name: "negative_cts_offsets"}, {Name: "omit_tfhd_offset"}, {Name: "prefer_icc"}, {Name: "rtphint"}, {Name: "separate_moof"}, {Name: "skip_sidx"}, {Name: "skip_trailer"}, {Name: "use_metadata_tags"}, {Name: "write_colr"}, {Name: "write_gama"}, {Name: "min_frag_duration"}, {Name: "mov_gamma"}, {Name: "movie_timescale"}, {Name: "rtpflags"}, {Name: "latm"}, {Name: "rfc2190"}, {Name: "skip_rtcp"}, {Name: "h264_mode0"}, {Name: "send_bye"}, {Name: "skip_iods"}, {Name: "use_editlist"}, {Name: "use_stream_ids_as_track_ids"}, {Name: "video_track_timescale"}, {Name: "write_btrt"}, {Name: "write_prft"}, {Name: "pts"}, {Name: "wallclock"}, {Name: "write_tmcd"}}, os.List()) + _, err = os.Get("invalid", NewOptionSearchFlags()) + require.Error(t, err) + v, err := os.Get("brand", NewOptionSearchFlags()) + require.NoError(t, err) + require.Equal(t, "", v) + require.Error(t, os.Set("invalid", "", NewOptionSearchFlags())) + const brand = "test" + require.NoError(t, os.Set("brand", brand, NewOptionSearchFlags())) + v, err = os.Get("brand", NewOptionSearchFlags()) + require.NoError(t, err) + require.Equal(t, brand, v) +} diff --git a/private_data.go b/private_data.go new file mode 100644 index 0000000..72d45a3 --- /dev/null +++ b/private_data.go @@ -0,0 +1,18 @@ +package astiav + +import "unsafe" + +type PrivateData struct { + c unsafe.Pointer +} + +func newPrivateDataFromC(c unsafe.Pointer) *PrivateData { + if c == nil { + return nil + } + return &PrivateData{c: c} +} + +func (pd *PrivateData) Options() *Options { + return newOptionsFromC(pd.c) +}