diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index db26181..630f024 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -1,6 +1,7 @@ # v0.25.0 - `CodecParameters`.`CodecType` and `CodecParameters`.`SetCodecType` have been removed, use `CodecParameters`.`MediaType` and `CodecParameters`.`SetMediaType` instead +- `HardwareFrameContext`.`SetPixelFormat` has been replaced with `HardwareFrameContext`.`SetHardwarePixelFormat` # v0.24.0 diff --git a/README.md b/README.md index d0fe124..07feb88 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Examples are located in the [examples](examples) directory and mirror as much as |Demuxing/Decoding|[see](examples/demuxing_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/demuxing_decoding.c) |Filtering|[see](examples/filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/filtering_video.c) |Frame data manipulation|[see](examples/frame_data_manipulation/main.go)|X -|Hardware Decoding|[see](examples/hardware_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/hw_decode.c) +|Hardware Decoding/Filtering|[see](examples/hardware_decoding_filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/hw_decode.c) |Hardware Encoding|[see](examples/hardware_encoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/vaapi_encode.c) |Remuxing|[see](examples/remuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/remuxing.c) |Resampling audio|[see](examples/resampling_audio/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/resample_audio.c) diff --git a/buffersink_filter_context.go b/buffersink_filter_context.go new file mode 100644 index 0000000..ed774ab --- /dev/null +++ b/buffersink_filter_context.go @@ -0,0 +1,87 @@ +package astiav + +//#include +import "C" + +type BuffersinkFilterContext struct { + fc *FilterContext +} + +func newBuffersinkFilterContext(fc *FilterContext) *BuffersinkFilterContext { + return &BuffersinkFilterContext{fc: fc} +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gaad918036937648701c09f9612f42706e +func (bfc *BuffersinkFilterContext) ChannelLayout() ChannelLayout { + var cl C.AVChannelLayout + C.av_buffersink_get_ch_layout(bfc.fc.c, &cl) + return newChannelLayoutFromC(&cl) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gab80976e506ab88d23d94bb6d7a4051bd +func (bfc *BuffersinkFilterContext) ColorRange() ColorRange { + return ColorRange(C.av_buffersink_get_color_range(bfc.fc.c)) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gaad817cdcf5493c385126e8e17c5717f2 +func (bfc *BuffersinkFilterContext) ColorSpace() ColorSpace { + return ColorSpace(C.av_buffersink_get_colorspace(bfc.fc.c)) +} + +func (bfc *BuffersinkFilterContext) FilterContext() *FilterContext { + return bfc.fc +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga55614fd28de2fa05b04f427390061d5b +func (bfc *BuffersinkFilterContext) FrameRate() Rational { + return newRationalFromC(C.av_buffersink_get_frame_rate(bfc.fc.c)) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink.html#ga71ae9c529c8da51681e12faa37d1a395 +func (bfc *BuffersinkFilterContext) GetFrame(f *Frame, fs BuffersinkFlags) error { + var cf *C.AVFrame + if f != nil { + cf = f.c + } + return newError(C.av_buffersink_get_frame_flags(bfc.fc.c, cf, C.int(fs))) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga955ecf3680e71e10429d7500343be25c +func (bfc *BuffersinkFilterContext) Height() int { + return int(C.av_buffersink_get_h(bfc.fc.c)) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga1eb8bbf583ffb7cc29aaa1944b1e699c +func (bfc *BuffersinkFilterContext) MediaType() MediaType { + return MediaType(C.av_buffersink_get_type(bfc.fc.c)) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga402ddbef6f7347869725696846ac81eb +func (bfc *BuffersinkFilterContext) PixelFormat() PixelFormat { + return PixelFormat(C.av_buffersink_get_format(bfc.fc.c)) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gaa38ee33e1c7f6f7cb190bd2330e5f848 +func (bfc *BuffersinkFilterContext) SampleAspectRatio() Rational { + return newRationalFromC(C.av_buffersink_get_sample_aspect_ratio(bfc.fc.c)) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga402ddbef6f7347869725696846ac81eb +func (bfc *BuffersinkFilterContext) SampleFormat() SampleFormat { + return SampleFormat(C.av_buffersink_get_format(bfc.fc.c)) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga2af714e82f48759551acdbc4488ded4a +func (bfc *BuffersinkFilterContext) SampleRate() int { + return int(C.av_buffersink_get_sample_rate(bfc.fc.c)) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gabc82f65ec7f4fa47c5216260639258a1 +func (bfc *BuffersinkFilterContext) TimeBase() Rational { + return newRationalFromC(C.av_buffersink_get_time_base(bfc.fc.c)) +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gac8c86515d2ef56090395dfd74854c835 +func (bfc *BuffersinkFilterContext) Width() int { + return int(C.av_buffersink_get_w(bfc.fc.c)) +} diff --git a/buffersrc_filter_context.go b/buffersrc_filter_context.go new file mode 100644 index 0000000..b59f9ba --- /dev/null +++ b/buffersrc_filter_context.go @@ -0,0 +1,30 @@ +package astiav + +//#include +import "C" + +type BuffersrcFilterContext struct { + fc *FilterContext +} + +func newBuffersrcFilterContext(fc *FilterContext) *BuffersrcFilterContext { + return &BuffersrcFilterContext{fc: fc} +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersrc.html#ga73ed90c3c3407f36e54d65f91faaaed9 +func (bfc *BuffersrcFilterContext) AddFrame(f *Frame, fs BuffersrcFlags) error { + var cf *C.AVFrame + if f != nil { + cf = f.c + } + return newError(C.av_buffersrc_add_frame_flags(bfc.fc.c, cf, C.int(fs))) +} + +func (bfc *BuffersrcFilterContext) FilterContext() *FilterContext { + return bfc.fc +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersrc.html#ga398cd2a84f8b4a588197ab9d90135048 +func (bfc *BuffersrcFilterContext) SetParameters(bfcp *BuffersrcFilterContextParameters) error { + return newError(C.av_buffersrc_parameters_set(bfc.fc.c, bfcp.c)) +} diff --git a/buffersrc_filter_context_parameters.go b/buffersrc_filter_context_parameters.go new file mode 100644 index 0000000..bfd52d7 --- /dev/null +++ b/buffersrc_filter_context_parameters.go @@ -0,0 +1,43 @@ +package astiav + +//#include +import "C" +import "unsafe" + +// https://ffmpeg.org/doxygen/7.0/structAVBufferSrcParameters.html +type BuffersrcFilterContextParameters struct { + c *C.AVBufferSrcParameters +} + +func newBuffersrcFilterContextParametersFromC(c *C.AVBufferSrcParameters) *BuffersrcFilterContextParameters { + if c == nil { + return nil + } + return &BuffersrcFilterContextParameters{c: c} +} + +// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersrc.html#gaae82d4f8a69757ce01421dd3167861a5 +func AllocBuffersrcFilterContextParameters() *BuffersrcFilterContextParameters { + return newBuffersrcFilterContextParametersFromC(C.av_buffersrc_parameters_alloc()) +} + +func (bfcp *BuffersrcFilterContextParameters) Free() { + if bfcp.c != nil { + if bfcp.c.hw_frames_ctx != nil { + C.av_buffer_unref(&bfcp.c.hw_frames_ctx) + } + C.av_freep(unsafe.Pointer(&bfcp.c)) + } +} + +// https://ffmpeg.org/doxygen/7.0/structAVBufferSrcParameters.html#a86c49b4202433037c9e2b0b6ae541534 +func (bfcp *BuffersrcFilterContextParameters) SetHardwareFrameContext(hfc *HardwareFrameContext) { + if bfcp.c.hw_frames_ctx != nil { + C.av_buffer_unref(&bfcp.c.hw_frames_ctx) + } + if hfc != nil { + bfcp.c.hw_frames_ctx = C.av_buffer_ref(hfc.c) + } else { + bfcp.c.hw_frames_ctx = nil + } +} diff --git a/codec_context.go b/codec_context.go index 61fa3f5..7424110 100644 --- a/codec_context.go +++ b/codec_context.go @@ -10,9 +10,6 @@ import ( // https://ffmpeg.org/doxygen/7.0/structAVCodecContext.html type CodecContext struct { c *C.AVCodecContext - // We need to store this to unref it properly - hdc *HardwareDeviceContext - hfc *HardwareFrameContext } func newCodecContextFromC(c *C.AVCodecContext) *CodecContext { @@ -37,15 +34,13 @@ func AllocCodecContext(c *Codec) *CodecContext { // https://ffmpeg.org/doxygen/7.0/group__lavc__core.html#gaf869d0829ed607cec3a4a02a1c7026b3 func (cc *CodecContext) Free() { - if cc.hdc != nil { - C.av_buffer_unref(&cc.hdc.c) - cc.hdc = nil - } - if cc.hfc != nil { - C.av_buffer_unref(&cc.hfc.c) - cc.hfc = nil - } if cc.c != nil { + if cc.c.hw_device_ctx != nil { + C.av_buffer_unref(&cc.c.hw_device_ctx) + } + if cc.c.hw_frames_ctx != nil { + C.av_buffer_unref(&cc.c.hw_frames_ctx) + } // Make sure to clone the classer before freeing the object since // the C free method may reset the pointer c := newClonedClasser(cc) @@ -370,12 +365,13 @@ func (cc *CodecContext) FromCodecParameters(cp *CodecParameters) error { // https://ffmpeg.org/doxygen/7.0/structAVCodecContext.html#acf8113e490f9e7b57465e65af9c0c75c func (cc *CodecContext) SetHardwareDeviceContext(hdc *HardwareDeviceContext) { - if cc.hdc != nil { - C.av_buffer_unref(&cc.hdc.c) + if cc.c.hw_device_ctx != nil { + C.av_buffer_unref(&cc.c.hw_device_ctx) } - cc.hdc = hdc - if cc.hdc != nil { - cc.c.hw_device_ctx = C.av_buffer_ref(cc.hdc.c) + if hdc != nil { + cc.c.hw_device_ctx = C.av_buffer_ref(hdc.c) + } else { + cc.c.hw_device_ctx = nil } } @@ -386,12 +382,13 @@ func (cc *CodecContext) HardwareFrameContext() *HardwareFrameContext { // https://ffmpeg.org/doxygen/7.0/structAVCodecContext.html#a3bac44bb0b016ab838780cc19ac277d6 func (cc *CodecContext) SetHardwareFrameContext(hfc *HardwareFrameContext) { - if cc.hfc != nil { - C.av_buffer_unref(&cc.hfc.c) + if cc.c.hw_frames_ctx != nil { + C.av_buffer_unref(&cc.c.hw_frames_ctx) } - cc.hfc = hfc - if cc.hfc != nil { - cc.c.hw_frames_ctx = C.av_buffer_ref(cc.hfc.c) + if hfc != nil { + cc.c.hw_frames_ctx = C.av_buffer_ref(hfc.c) + } else { + cc.c.hw_frames_ctx = nil } } diff --git a/examples/hardware_decoding/main.go b/examples/hardware_decoding/main.go deleted file mode 100644 index c4ac528..0000000 --- a/examples/hardware_decoding/main.go +++ /dev/null @@ -1,242 +0,0 @@ -package main - -import ( - "errors" - "flag" - "fmt" - "log" - "strings" - - "github.com/asticode/go-astiav" -) - -var ( - decoderCodecName = flag.String("c", "", "the decoder codec name (e.g. h264_cuvid)") - hardwareDeviceName = flag.String("n", "", "the hardware device name (e.g. 0)") - hardwareDeviceTypeName = flag.String("t", "", "the hardware device type (e.g. cuda)") - input = flag.String("i", "", "the input path") -) - -type stream struct { - decCodec *astiav.Codec - decCodecContext *astiav.CodecContext - hardwareDeviceContext *astiav.HardwareDeviceContext - hardwarePixelFormat astiav.PixelFormat - inputStream *astiav.Stream -} - -func main() { - // Handle ffmpeg logs - astiav.SetLogLevel(astiav.LogLevelDebug) - astiav.SetLogCallback(func(c astiav.Classer, l astiav.LogLevel, fmt, msg string) { - var cs string - if c != nil { - if cl := c.Class(); cl != nil { - cs = " - class: " + cl.String() - } - } - log.Printf("ffmpeg log: %s%s - level: %d\n", strings.TrimSpace(msg), cs, l) - }) - - // Parse flags - flag.Parse() - - // Usage - if *input == "" || *hardwareDeviceTypeName == "" { - log.Println("Usage: -t -i [-n -c ]") - return - } - - // Get hardware device type - hardwareDeviceType := astiav.FindHardwareDeviceTypeByName(*hardwareDeviceTypeName) - if hardwareDeviceType == astiav.HardwareDeviceTypeNone { - log.Fatal(errors.New("main: hardware device not found")) - } - - // Allocate packet - pkt := astiav.AllocPacket() - defer pkt.Free() - - // Allocate hardware frame - hardwareFrame := astiav.AllocFrame() - defer hardwareFrame.Free() - - // Allocate software frame - softwareFrame := astiav.AllocFrame() - defer softwareFrame.Free() - - // Allocate input format context - inputFormatContext := astiav.AllocFormatContext() - if inputFormatContext == nil { - log.Fatal(errors.New("main: input format context is nil")) - } - defer inputFormatContext.Free() - - // Open input - if err := inputFormatContext.OpenInput(*input, nil, nil); err != nil { - log.Fatal(fmt.Errorf("main: opening input failed: %w", err)) - } - defer inputFormatContext.CloseInput() - - // Find stream info - if err := inputFormatContext.FindStreamInfo(nil); err != nil { - log.Fatal(fmt.Errorf("main: finding stream info failed: %w", err)) - } - - // Loop through streams - streams := make(map[int]*stream) // Indexed by input stream index - for _, is := range inputFormatContext.Streams() { - // Only process video - if is.CodecParameters().MediaType() != astiav.MediaTypeVideo { - continue - } - - // Create stream - s := &stream{ - inputStream: is, - hardwarePixelFormat: astiav.PixelFormatNone, - } - - // Find decoder - if *decoderCodecName != "" { - s.decCodec = astiav.FindDecoderByName(*decoderCodecName) - } else { - s.decCodec = astiav.FindDecoder(is.CodecParameters().CodecID()) - } - - // No codec - if s.decCodec == nil { - log.Fatal(errors.New("main: codec is nil")) - } - - // Allocate codec context - if s.decCodecContext = astiav.AllocCodecContext(s.decCodec); s.decCodecContext == nil { - log.Fatal(errors.New("main: codec context is nil")) - } - defer s.decCodecContext.Free() - - // Loop through codec hardware configs - for _, p := range s.decCodec.HardwareConfigs() { - // Valid hardware config - if p.MethodFlags().Has(astiav.CodecHardwareConfigMethodFlagHwDeviceCtx) && p.HardwareDeviceType() == hardwareDeviceType { - s.hardwarePixelFormat = p.PixelFormat() - break - } - } - - // No valid hardware pixel format - if s.hardwarePixelFormat == astiav.PixelFormatNone { - log.Fatal(errors.New("main: hardware device type not supported by decoder")) - } - - // Update codec context - if err := is.CodecParameters().ToCodecContext(s.decCodecContext); err != nil { - log.Fatal(fmt.Errorf("main: updating codec context failed: %w", err)) - } - - // Create hardware device context - var err error - if s.hardwareDeviceContext, err = astiav.CreateHardwareDeviceContext(hardwareDeviceType, *hardwareDeviceName, nil, 0); err != nil { - log.Fatal(fmt.Errorf("main: creating hardware device context failed: %w", err)) - } - defer s.hardwareDeviceContext.Free() - - // Update decoder context - s.decCodecContext.SetHardwareDeviceContext(s.hardwareDeviceContext) - s.decCodecContext.SetPixelFormatCallback(func(pfs []astiav.PixelFormat) astiav.PixelFormat { - for _, pf := range pfs { - if pf == s.hardwarePixelFormat { - return pf - } - } - log.Fatal(errors.New("main: using hardware pixel format failed")) - return astiav.PixelFormatNone - }) - - // Open codec context - if err := s.decCodecContext.Open(s.decCodec, nil); err != nil { - log.Fatal(fmt.Errorf("main: opening codec context failed: %w", err)) - } - - // Add stream - streams[is.Index()] = s - } - - // Loop through packets - for { - // We use a closure to ease unreferencing the packet - if stop := func() bool { - // Read frame - if err := inputFormatContext.ReadFrame(pkt); err != nil { - if errors.Is(err, astiav.ErrEof) { - return true - } - log.Fatal(fmt.Errorf("main: reading frame failed: %w", err)) - } - - // Make sure to unreference the packet - defer pkt.Unref() - - // Get stream - s, ok := streams[pkt.StreamIndex()] - if !ok { - return false - } - - // Send packet - if err := s.decCodecContext.SendPacket(pkt); err != nil { - log.Fatal(fmt.Errorf("main: sending packet failed: %w", err)) - } - - // Loop - for { - // We use a closure to ease unreferencing frames - if stop := func() bool { - // Receive frame - if err := s.decCodecContext.ReceiveFrame(hardwareFrame); err != nil { - if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) { - return true - } - log.Fatal(fmt.Errorf("main: receiving frame failed: %w", err)) - } - - // Make sure to unreference hardware frame - defer hardwareFrame.Unref() - - // Get final frame - var finalFrame *astiav.Frame - if hardwareFrame.PixelFormat() == s.hardwarePixelFormat { - // Transfer hardware data - if err := hardwareFrame.TransferHardwareData(softwareFrame); err != nil { - log.Fatal(fmt.Errorf("main: transferring hardware data failed: %w", err)) - } - - // Make sure to unreference software frame - defer softwareFrame.Unref() - - // Update pts - softwareFrame.SetPts(hardwareFrame.Pts()) - - // Update final frame - finalFrame = softwareFrame - } else { - // Update final frame - finalFrame = hardwareFrame - } - - // Do something with decoded frame - log.Printf("new frame: stream %d - pts: %d - transferred: %v", pkt.StreamIndex(), finalFrame.Pts(), hardwareFrame.PixelFormat() == s.hardwarePixelFormat) - return false - }(); stop { - break - } - } - return false - }(); stop { - break - } - } - - // Success - log.Println("success") -} diff --git a/examples/hardware_decoding_filtering/main.go b/examples/hardware_decoding_filtering/main.go new file mode 100644 index 0000000..9c7c716 --- /dev/null +++ b/examples/hardware_decoding_filtering/main.go @@ -0,0 +1,404 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "log" + "strconv" + "strings" + + "github.com/asticode/go-astiav" + "github.com/asticode/go-astikit" +) + +var ( + decoderCodecName = flag.String("c", "", "the decoder codec name (e.g. h264_cuvid)") + filter = flag.String("f", "", "the hardware filter") + hardwareDeviceName = flag.String("n", "", "the hardware device name (e.g. 0)") + hardwareDeviceTypeName = flag.String("t", "", "the hardware device type (e.g. cuda)") + input = flag.String("i", "", "the input path") +) + +var ( + buffersinkContext *astiav.BuffersinkFilterContext + buffersrcContext *astiav.BuffersrcFilterContext + c = astikit.NewCloser() + decCodec *astiav.Codec + decCodecContext *astiav.CodecContext + decodedHardwareFrame *astiav.Frame + filterGraph *astiav.FilterGraph + filteredHardwareFrame *astiav.Frame + inputStream *astiav.Stream + softwareFrame *astiav.Frame +) + +func main() { + // Handle ffmpeg logs + astiav.SetLogLevel(astiav.LogLevelDebug) + astiav.SetLogCallback(func(c astiav.Classer, l astiav.LogLevel, fmt, msg string) { + var cs string + if c != nil { + if cl := c.Class(); cl != nil { + cs = " - class: " + cl.String() + } + } + log.Printf("ffmpeg log: %s%s - level: %d\n", strings.TrimSpace(msg), cs, l) + }) + + // Parse flags + flag.Parse() + + // Usage + if *input == "" || *hardwareDeviceTypeName == "" { + log.Println("Usage: -t -i [-n -c -f ]") + return + } + + // We use an astikit.Closer to free all resources properly + defer c.Close() + + // Get hardware device type + hardwareDeviceType := astiav.FindHardwareDeviceTypeByName(*hardwareDeviceTypeName) + if hardwareDeviceType == astiav.HardwareDeviceTypeNone { + log.Fatal(errors.New("main: hardware device not found")) + } + + // Allocate packet + pkt := astiav.AllocPacket() + c.Add(pkt.Free) + + // Allocate decoded hardware frame + decodedHardwareFrame = astiav.AllocFrame() + c.Add(decodedHardwareFrame.Free) + + // Allocate software frame + softwareFrame = astiav.AllocFrame() + c.Add(softwareFrame.Free) + + // Allocate input format context + inputFormatContext := astiav.AllocFormatContext() + if inputFormatContext == nil { + log.Fatal(errors.New("main: input format context is nil")) + } + c.Add(inputFormatContext.Free) + + // Open input + if err := inputFormatContext.OpenInput(*input, nil, nil); err != nil { + log.Fatal(fmt.Errorf("main: opening input failed: %w", err)) + } + c.Add(inputFormatContext.CloseInput) + + // Find stream info + if err := inputFormatContext.FindStreamInfo(nil); err != nil { + log.Fatal(fmt.Errorf("main: finding stream info failed: %w", err)) + } + + // Loop through streams + var hdc *astiav.HardwareDeviceContext + hardwarePixelFormat := astiav.PixelFormatNone + for _, is := range inputFormatContext.Streams() { + // Only process video + if is.CodecParameters().MediaType() != astiav.MediaTypeVideo { + continue + } + + // Update input stream + inputStream = is + + // Find decoder + decCodec = astiav.FindDecoder(is.CodecParameters().CodecID()) + if *decoderCodecName != "" { + decCodec = astiav.FindDecoderByName(*decoderCodecName) + } + + // No codec + if decCodec == nil { + log.Fatal(errors.New("main: codec is nil")) + } + + // Allocate codec context + if decCodecContext = astiav.AllocCodecContext(decCodec); decCodecContext == nil { + log.Fatal(errors.New("main: codec context is nil")) + } + c.Add(decCodecContext.Free) + + // Loop through codec hardware configs + for _, p := range decCodec.HardwareConfigs() { + // Valid hardware config + if p.MethodFlags().Has(astiav.CodecHardwareConfigMethodFlagHwDeviceCtx) && p.HardwareDeviceType() == hardwareDeviceType { + hardwarePixelFormat = p.PixelFormat() + break + } + } + + // No valid hardware pixel format + if hardwarePixelFormat == astiav.PixelFormatNone { + log.Fatal(errors.New("main: hardware device type not supported by decoder")) + } + + // Update codec context + if err := is.CodecParameters().ToCodecContext(decCodecContext); err != nil { + log.Fatal(fmt.Errorf("main: updating codec context failed: %w", err)) + } + + // Create hardware device context + var err error + if hdc, err = astiav.CreateHardwareDeviceContext(hardwareDeviceType, *hardwareDeviceName, nil, 0); err != nil { + log.Fatal(fmt.Errorf("main: creating hardware device context failed: %w", err)) + } + c.Add(hdc.Free) + + // Update decoder context + decCodecContext.SetHardwareDeviceContext(hdc) + decCodecContext.SetPixelFormatCallback(func(pfs []astiav.PixelFormat) astiav.PixelFormat { + for _, pf := range pfs { + if pf == hardwarePixelFormat { + return pf + } + } + log.Fatal(errors.New("main: using hardware pixel format failed")) + return astiav.PixelFormatNone + }) + + // Open codec context + if err := decCodecContext.Open(decCodec, nil); err != nil { + log.Fatal(fmt.Errorf("main: opening codec context failed: %w", err)) + } + break + } + + // No video stream + if inputStream == nil { + log.Fatal("main: no video stream found") + } + + // Loop through packets + for { + // We use a closure to ease unreferencing the packet + if stop := func() bool { + // Read frame + if err := inputFormatContext.ReadFrame(pkt); err != nil { + if errors.Is(err, astiav.ErrEof) { + return true + } + log.Fatal(fmt.Errorf("main: reading frame failed: %w", err)) + } + + // Make sure to unreference the packet + defer pkt.Unref() + + // Invalid stream + if pkt.StreamIndex() != inputStream.Index() { + return false + } + + // Send packet + if err := decCodecContext.SendPacket(pkt); err != nil { + log.Fatal(fmt.Errorf("main: sending packet failed: %w", err)) + } + + // Loop + for { + // We use a closure to ease unreferencing frames + if stop := func() bool { + // Receive frame + if err := decCodecContext.ReceiveFrame(decodedHardwareFrame); err != nil { + if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) { + return true + } + log.Fatal(fmt.Errorf("main: receiving frame failed: %w", err)) + } + + // Make sure to unreference hardware frame + defer decodedHardwareFrame.Unref() + + // Invalid pixel format + if decodedHardwareFrame.PixelFormat() != hardwarePixelFormat { + log.Fatalf("main: invalid decoded pixel format %s, expected %s", decodedHardwareFrame.PixelFormat(), hardwarePixelFormat) + } + + // No filter requested + if *filter == "" { + // Do something with hardware frame + if err := doSomethingWithHardwareFrame(decodedHardwareFrame); err != nil { + log.Fatal(fmt.Errorf("main: doing something with hardware frame failed: %w", err)) + } + return false + } + + // Make sure the filter is initialized + // We need to wait for the first frame to be decoded before initializing the filter + // since the decoder codec context doesn't have a valid hardware frame context until then + if filterGraph == nil { + if err := initFilter(); err != nil { + log.Fatal(fmt.Errorf("main: initializing filter failed: %w", err)) + } + } + + // Filter frame + if err := filterFrame(); err != nil { + log.Fatal(fmt.Errorf("main: filtering frame failed: %w", err)) + } + return false + }(); stop { + break + } + } + return false + }(); stop { + break + } + } + + // Success + log.Println("success") +} + +func initFilter() (err error) { + // Allocate graph + if filterGraph = astiav.AllocFilterGraph(); filterGraph == nil { + err = errors.New("main: graph is nil") + return + } + c.Add(filterGraph.Free) + + // Allocate outputs + outputs := astiav.AllocFilterInOut() + if outputs == nil { + err = errors.New("main: outputs is nil") + return + } + c.Add(outputs.Free) + + // Allocate inputs + inputs := astiav.AllocFilterInOut() + if inputs == nil { + err = errors.New("main: inputs is nil") + return + } + c.Add(inputs.Free) + + // Create buffersrc + buffersrc := astiav.FindFilterByName("buffer") + if buffersrc == nil { + err = errors.New("main: buffersrc is nil") + return + } + + // Create buffersink + buffersink := astiav.FindFilterByName("buffersink") + if buffersink == nil { + err = errors.New("main: buffersink is nil") + return + } + + // Create filter contexts + if buffersrcContext, err = filterGraph.NewBuffersrcFilterContext(buffersrc, "in", astiav.FilterArgs{ + "pix_fmt": strconv.Itoa(int(decCodecContext.PixelFormat())), + "pixel_aspect": decCodecContext.SampleAspectRatio().String(), + "time_base": inputStream.TimeBase().String(), + "video_size": strconv.Itoa(decCodecContext.Width()) + "x" + strconv.Itoa(decCodecContext.Height()), + }); err != nil { + err = fmt.Errorf("main: creating buffersrc context failed: %w", err) + return + } + if buffersinkContext, err = filterGraph.NewBuffersinkFilterContext(buffersink, "in", nil); err != nil { + err = fmt.Errorf("main: creating buffersink context failed: %w", err) + return + } + + // Create buffersrc parameters + bfcp := astiav.AllocBuffersrcFilterContextParameters() + defer bfcp.Free() + bfcp.SetHardwareFrameContext(decCodecContext.HardwareFrameContext()) + + // Set buffersrc parameters + if err = buffersrcContext.SetParameters(bfcp); err != nil { + err = fmt.Errorf("main: setting buffersrc parameters failed: %w", err) + return + } + + // Update outputs + outputs.SetName("in") + outputs.SetFilterContext(buffersrcContext.FilterContext()) + outputs.SetPadIdx(0) + outputs.SetNext(nil) + + // Update inputs + inputs.SetName("out") + inputs.SetFilterContext(buffersinkContext.FilterContext()) + inputs.SetPadIdx(0) + inputs.SetNext(nil) + + // Parse + if err = filterGraph.Parse(*filter, inputs, outputs); err != nil { + err = fmt.Errorf("main: parsing filter failed: %w", err) + return + } + + // Configure + if err = filterGraph.Configure(); err != nil { + err = fmt.Errorf("main: configuring filter failed: %w", err) + return + } + + // Allocate frame + filteredHardwareFrame = astiav.AllocFrame() + c.Add(filteredHardwareFrame.Free) + return +} + +func filterFrame() (err error) { + // Add frame + if err = buffersrcContext.AddFrame(decodedHardwareFrame, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil { + err = fmt.Errorf("main: adding frame failed: %w", err) + return + } + + // Loop + for { + // We use a closure to ease unreferencing the frame + if stop, err := func() (bool, error) { + // Get frame + if err := buffersinkContext.GetFrame(filteredHardwareFrame, astiav.NewBuffersinkFlags()); err != nil { + if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) { + return true, nil + } + return false, fmt.Errorf("main: getting frame failed: %w", err) + } + + // Make sure to unrefernce the frame + defer filteredHardwareFrame.Unref() + + // Do something with hardware frame + if err := doSomethingWithHardwareFrame(filteredHardwareFrame); err != nil { + return false, fmt.Errorf("main: doing something with hardware frame failed: %w", err) + } + return false, nil + }(); err != nil { + return err + } else if stop { + break + } + + } + return +} + +func doSomethingWithHardwareFrame(hardwareFrame *astiav.Frame) error { + // Transfer hardware data + if err := hardwareFrame.TransferHardwareData(softwareFrame); err != nil { + return fmt.Errorf("main: transferring hardware data failed: %w", err) + } + + // Make sure to unreference software frame + defer softwareFrame.Unref() + + // Update pts + softwareFrame.SetPts(hardwareFrame.Pts()) + + // Do something with software frame + log.Printf("new software frame: pts: %d", softwareFrame.Pts()) + return nil +} diff --git a/examples/hardware_encoding/main.go b/examples/hardware_encoding/main.go index 7cd6ba9..0d96d3d 100644 --- a/examples/hardware_encoding/main.go +++ b/examples/hardware_encoding/main.go @@ -95,7 +95,7 @@ func main() { } // Set hardware frame content - hardwareFrameContext.SetPixelFormat(hardwarePixelFormat) + hardwareFrameContext.SetHardwarePixelFormat(hardwarePixelFormat) hardwareFrameContext.SetSoftwarePixelFormat(softwarePixelFormat) hardwareFrameContext.SetWidth(*width) hardwareFrameContext.SetHeight(*height) diff --git a/filter_context.go b/filter_context.go index 7a1eb68..c911a40 100644 --- a/filter_context.go +++ b/filter_context.go @@ -1,9 +1,6 @@ package astiav //#include -//#include -//#include -//#include import "C" import ( "unsafe" @@ -42,107 +39,3 @@ func (fc *FilterContext) Free() { func (fc *FilterContext) Class() *Class { return newClassFromC(unsafe.Pointer(fc.c)) } - -type BuffersinkFilterContext struct { - fc *FilterContext -} - -func newBuffersinkFilterContext(fc *FilterContext) *BuffersinkFilterContext { - return &BuffersinkFilterContext{fc: fc} -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gaad918036937648701c09f9612f42706e -func (bfc *BuffersinkFilterContext) ChannelLayout() ChannelLayout { - var cl C.AVChannelLayout - C.av_buffersink_get_ch_layout(bfc.fc.c, &cl) - return newChannelLayoutFromC(&cl) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gab80976e506ab88d23d94bb6d7a4051bd -func (bfc *BuffersinkFilterContext) ColorRange() ColorRange { - return ColorRange(C.av_buffersink_get_color_range(bfc.fc.c)) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gaad817cdcf5493c385126e8e17c5717f2 -func (bfc *BuffersinkFilterContext) ColorSpace() ColorSpace { - return ColorSpace(C.av_buffersink_get_colorspace(bfc.fc.c)) -} - -func (bfc *BuffersinkFilterContext) FilterContext() *FilterContext { - return bfc.fc -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga55614fd28de2fa05b04f427390061d5b -func (bfc *BuffersinkFilterContext) FrameRate() Rational { - return newRationalFromC(C.av_buffersink_get_frame_rate(bfc.fc.c)) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink.html#ga71ae9c529c8da51681e12faa37d1a395 -func (bfc *BuffersinkFilterContext) GetFrame(f *Frame, fs BuffersinkFlags) error { - var cf *C.AVFrame - if f != nil { - cf = f.c - } - return newError(C.av_buffersink_get_frame_flags(bfc.fc.c, cf, C.int(fs))) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga955ecf3680e71e10429d7500343be25c -func (bfc *BuffersinkFilterContext) Height() int { - return int(C.av_buffersink_get_h(bfc.fc.c)) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga1eb8bbf583ffb7cc29aaa1944b1e699c -func (bfc *BuffersinkFilterContext) MediaType() MediaType { - return MediaType(C.av_buffersink_get_type(bfc.fc.c)) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga402ddbef6f7347869725696846ac81eb -func (bfc *BuffersinkFilterContext) PixelFormat() PixelFormat { - return PixelFormat(C.av_buffersink_get_format(bfc.fc.c)) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gaa38ee33e1c7f6f7cb190bd2330e5f848 -func (bfc *BuffersinkFilterContext) SampleAspectRatio() Rational { - return newRationalFromC(C.av_buffersink_get_sample_aspect_ratio(bfc.fc.c)) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga402ddbef6f7347869725696846ac81eb -func (bfc *BuffersinkFilterContext) SampleFormat() SampleFormat { - return SampleFormat(C.av_buffersink_get_format(bfc.fc.c)) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#ga2af714e82f48759551acdbc4488ded4a -func (bfc *BuffersinkFilterContext) SampleRate() int { - return int(C.av_buffersink_get_sample_rate(bfc.fc.c)) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gabc82f65ec7f4fa47c5216260639258a1 -func (bfc *BuffersinkFilterContext) TimeBase() Rational { - return newRationalFromC(C.av_buffersink_get_time_base(bfc.fc.c)) -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersink__accessors.html#gac8c86515d2ef56090395dfd74854c835 -func (bfc *BuffersinkFilterContext) Width() int { - return int(C.av_buffersink_get_w(bfc.fc.c)) -} - -type BuffersrcFilterContext struct { - fc *FilterContext -} - -func newBuffersrcFilterContext(fc *FilterContext) *BuffersrcFilterContext { - return &BuffersrcFilterContext{fc: fc} -} - -// https://ffmpeg.org/doxygen/7.0/group__lavfi__buffersrc.html#ga73ed90c3c3407f36e54d65f91faaaed9 -func (bfc *BuffersrcFilterContext) AddFrame(f *Frame, fs BuffersrcFlags) error { - var cf *C.AVFrame - if f != nil { - cf = f.c - } - return newError(C.av_buffersrc_add_frame_flags(bfc.fc.c, cf, C.int(fs))) -} - -func (bfc *BuffersrcFilterContext) FilterContext() *FilterContext { - return bfc.fc -} diff --git a/hardware_frame_context.go b/hardware_frame_context.go index 7bfdc7c..06ba298 100644 --- a/hardware_frame_context.go +++ b/hardware_frame_context.go @@ -44,7 +44,7 @@ func (hfc *HardwareFrameContext) SetHeight(height int) { } // https://ffmpeg.org/doxygen/7.0/structAVHWFramesContext.html#a045bc1713932804f6ceef170a5578e0e -func (hfc *HardwareFrameContext) SetPixelFormat(format PixelFormat) { +func (hfc *HardwareFrameContext) SetHardwarePixelFormat(format PixelFormat) { hfc.data().format = C.enum_AVPixelFormat(format) }