Skip to content

Commit

Permalink
Review changes
Browse files Browse the repository at this point in the history
add HardwareConfigs to codec
create codec_hardware_config
create codec_hardware_config_method_flag
update example
update flags
better error handling in TransferHardwareData
update CreateHardwareDeviceContext, better null handling
remove hw ctx test because it fails when no nvidia is present
some reordering of conss in hardware_device_type
  • Loading branch information
Cacsjep committed Jan 24, 2024
1 parent a70e8da commit 68dc1e5
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 90 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ 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/n5.1.2/doc/examples/demuxing_decoding.c)
|Filtering|[see](examples/filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/filtering_video.c)
|Hardware Decoding|[see](examples/hardware_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/hw_decode.c)
|Remuxing|[see](examples/remuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/remuxing.c)
|Transcoding|[see](examples/transcoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/transcoding.c)
|Hardware Decoding|[see](examples/hardware_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/hw_decode.c)

*Tip: you can use the video sample located in the `testdata` directory for your tests*

Expand Down
25 changes: 25 additions & 0 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package astiav
//#include <libavutil/channel_layout.h>
import "C"
import (
"fmt"
"unsafe"
)

Expand Down Expand Up @@ -100,3 +101,27 @@ func FindEncoderByName(n string) *Codec {
defer C.free(unsafe.Pointer(cn))
return newCodecFromC(C.avcodec_find_encoder_by_name(cn))
}

func (c *Codec) HardwareConfigs(dt HardwareDeviceType) ([]CodecHardwareConfig, error) {
var configs []CodecHardwareConfig
var i int

for {
config := C.avcodec_get_hw_config(c.c, C.int(i))
if config == nil {
break
}

if HardwareDeviceType(config.device_type) == dt {
configs = append(configs, CodecHardwareConfig{c: config})
}

i++
}

if len(configs) == 0 {
return nil, fmt.Errorf("Decoder does not support device type %s", dt)
}

return configs, nil
}
22 changes: 22 additions & 0 deletions codec_hardware_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package astiav

//#cgo pkg-config: libavcodec
//#include <libavcodec/avcodec.h>
import "C"

// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavcodec/codec.h#L460
type CodecHardwareConfig struct {
c *C.AVCodecHWConfig
}

func (chc CodecHardwareConfig) HardwareDeviceType() HardwareDeviceType {
return HardwareDeviceType(chc.c.device_type)
}

func (chc CodecHardwareConfig) MethodFlags() CodecHardwareConfigMethodFlags {
return CodecHardwareConfigMethodFlags(chc.c.methods)
}

func (chc CodecHardwareConfig) PixelFormat() PixelFormat {
return PixelFormat(chc.c.pix_fmt)
}
15 changes: 15 additions & 0 deletions codec_hardware_config_method_flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package astiav

//#cgo pkg-config: libavcodec
//#include <libavcodec/avcodec.h>
import "C"

// https://github.com/FFmpeg/FFmpeg/blob/n5.0/libavcodec/codec.h#L420
type CodecHardwareConfigMethodFlag int

const (
CodecHardwareConfigMethodHwDeviceCtx = CodecHardwareConfigMethodFlag(C.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
CodecHardwareConfigMethodHwFramesCtx = CodecHardwareConfigMethodFlag(C.AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX)
CodecHardwareConfigMethodInternal = CodecHardwareConfigMethodFlag(C.AV_CODEC_HW_CONFIG_METHOD_INTERNAL)
CodecHardwareConfigMethodAdHoc = CodecHardwareConfigMethodFlag(C.AV_CODEC_HW_CONFIG_METHOD_AD_HOC)
)
85 changes: 45 additions & 40 deletions examples/hardware_decoding/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
)

var (
input = flag.String("i", "", "the input path")
device_type = flag.String("d", "", "the hardware device type like: cuda")
hardwareDeviceTypeName = flag.String("d", "", "the hardware device type like: cuda")
input = flag.String("i", "", "the input path")
)

type stream struct {
Expand All @@ -22,15 +22,6 @@ type stream struct {
inputStream *astiav.Stream
}

func hw_decoder_init(ctx *astiav.CodecContext, t astiav.HardwareDeviceType) (*astiav.HardwareDeviceContext, error) {
hdc, err := astiav.CreateHardwareDeviceContext(t, "", &astiav.Dictionary{})
if err != nil {
return nil, fmt.Errorf("Unable to create hardware device context: %w", err)
}
ctx.SetHardwareDeviceContext(hdc)
return hdc, nil
}

func main() {
// Handle ffmpeg logs
astiav.SetLogLevel(astiav.LogLevelDebug)
Expand All @@ -42,25 +33,28 @@ func main() {
flag.Parse()

// Usage
if *input == "" || *device_type == "" {
if *input == "" || *hardwareDeviceTypeName == "" {
log.Println("Usage: <binary path> -d <device type> -i <input path>")
return
}

hw_device := astiav.FindHardwareDeviceTypeByName(*device_type)
if hw_device == astiav.HardwareDeviceTypeNone {
// Get hardware device type
hardwareDeviceType := astiav.FindHardwareDeviceTypeByName(*hardwareDeviceTypeName)
if hardwareDeviceType == astiav.HardwareDeviceTypeNone {
log.Fatal(errors.New("main: hardware device not found"))
}

// Alloc packet
pkt := astiav.AllocPacket()
defer pkt.Free()

gpu_f := astiav.AllocFrame()
defer gpu_f.Free()
// Alloc hardware frame
hardwareFrame := astiav.AllocFrame()
defer hardwareFrame.Free()

sw_f := astiav.AllocFrame()
defer sw_f.Free()
// Alloc software frame
softwareFrame := astiav.AllocFrame()
defer softwareFrame.Free()

// Alloc input format context
inputFormatContext := astiav.AllocFormatContext()
Expand All @@ -80,9 +74,8 @@ func main() {
log.Fatal(fmt.Errorf("main: finding stream info failed: %w", err))
}

var hw_pix_fmt astiav.PixelFormat

// Loop through streams
hardwarePixelFormat := astiav.PixelFormatNone
streams := make(map[int]*stream) // Indexed by input stream index
for _, is := range inputFormatContext.Streams() {
var err error
Expand All @@ -106,21 +99,33 @@ func main() {
}
defer s.decCodecContext.Free()

hw_pix_fmt, err = astiav.FindSuitableHardwareFormat(s.decCodec, hw_device)
codec_hardware_configs, err := s.decCodec.HardwareConfigs(hardwareDeviceType)
if err != nil {
log.Fatal(fmt.Errorf("main: find decoder hw format fails: %w", err))
} else {
log.Printf("Using hw_pix_fmt: %s", hw_pix_fmt.Name())
log.Fatal(fmt.Errorf("main: no codec hardware config found: %w", err))
}

for _, p := range codec_hardware_configs {
if p.MethodFlags().Has(astiav.CodecHardwareConfigMethodHwDeviceCtx) && p.HardwareDeviceType() == hardwareDeviceType {
hardwarePixelFormat = p.PixelFormat()
break
}
}

if hardwarePixelFormat == astiav.PixelFormatNone {
log.Fatal(fmt.Errorf("main: Decoder %s does not support device type", hardwareDeviceType.String()))
}

// Update codec context
if err := is.CodecParameters().ToCodecContext(s.decCodecContext); err != nil {
log.Fatal(fmt.Errorf("main: updating codec context failed: %w", err))
}

if s.hwDeviceContext, err = hw_decoder_init(s.decCodecContext, hw_device); err != nil {
log.Fatal(fmt.Errorf("main: init hardware device failed: %w", err))
// Create hardware device context
hdc, err := astiav.CreateHardwareDeviceContext(hardwareDeviceType, "", nil)
if err != nil {
log.Fatal(fmt.Errorf("main: creating hardware device context failed: %w", err))
}
s.decCodecContext.SetHardwareDeviceContext(hdc)

// Open codec context
if err := s.decCodecContext.Open(s.decCodec, nil); err != nil {
Expand Down Expand Up @@ -155,33 +160,33 @@ func main() {
// Loop
for {
// Receive frame
if err := s.decCodecContext.ReceiveFrame(gpu_f); err != nil {
if err := s.decCodecContext.ReceiveFrame(hardwareFrame); err != nil {
if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) {
break
}
log.Fatal(fmt.Errorf("main: receiving frame failed: %w", err))
}

if gpu_f.PixelFormat() == hw_pix_fmt {
err := gpu_f.TransferHardwareData(sw_f)
if err != nil {
// Get frame
var finalFrame *astiav.Frame
if hardwareFrame.PixelFormat() == hardwarePixelFormat {
// Transfer hardware data
if err := hardwareFrame.TransferHardwareData(softwareFrame); err != nil {
log.Fatal(fmt.Errorf("main: Unable to transfer frame from gpu: %w", err))
}

data, err := sw_f.Data().Bytes(1)

if err != nil {
log.Fatal(fmt.Errorf("main: Unable to get frame bytes: %w", err))
}

sw_f.SetPts(gpu_f.Pts())
// Update pts
softwareFrame.SetPts(hardwareFrame.Pts())

// Do something with decoded frame
log.Printf("new frame: pts: %d gpu frame pix fmt: %s, sw frame (transfered) pix fmt: %s, size: %d", sw_f.Pts(), gpu_f.PixelFormat().Name(), sw_f.PixelFormat().Name(), len(data))
// Update final frame
finalFrame = softwareFrame
} else {
log.Fatal(fmt.Errorf("main: Mismatch in pixel format: gpu: %s sw (transfered): %s", gpu_f.PixelFormat().Name(), sw_f.PixelFormat().Name()))
// Update final frame
finalFrame = hardwareFrame
}

// Do something with decoded frame
log.Printf("new frame: stream %d - pts: %d - data transferred from hardware: %v", pkt.StreamIndex(), finalFrame.Pts(), finalFrame.PixelFormat() == hardwarePixelFormat)
}
}

Expand Down
20 changes: 20 additions & 0 deletions flags.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions flags_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,7 @@ func (f *Frame) SetWidth(w int) {
}

func (f *Frame) TransferHardwareData(dst *Frame) error {
ret := C.av_hwframe_transfer_data(dst.c, f.c, 0)
if ret < 0 {
return newError(ret)
}
return nil
return newError(C.av_hwframe_transfer_data(dst.c, f.c, 0))
}

func (f *Frame) Free() {
Expand Down
30 changes: 7 additions & 23 deletions hardware_device_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package astiav
//#include <libavutil/hwcontext.h>
import "C"
import (
"fmt"
"unsafe"
)

Expand All @@ -16,34 +15,19 @@ type HardwareDeviceContext struct {

func CreateHardwareDeviceContext(t HardwareDeviceType, device string, options *Dictionary) (*HardwareDeviceContext, error) {
hdc := HardwareDeviceContext{}

// Check for an emtpy string and pass NULL to av_hwdevice_ctx_create if its emtpy
var deviceC *C.char
deviceC := (*C.char)(nil)
if device != "" {
deviceC = C.CString(device)
defer C.free(unsafe.Pointer(deviceC))
} else {
deviceC = (*C.char)(nil)
}
errorCode := C.av_hwdevice_ctx_create(&hdc.c, (C.enum_AVHWDeviceType)(t), deviceC, options.c, 0)
if errorCode < 0 {
return nil, newError(errorCode)
var optionsC *C.struct_AVDictionary
if options != nil {
optionsC = options.c
}
return &hdc, nil
}

func FindSuitableHardwareFormat(decoder *Codec, deviceType HardwareDeviceType) (PixelFormat, error) {
var i int
for {
config := C.avcodec_get_hw_config(decoder.c, C.int(i))
if config == nil {
return 0, fmt.Errorf("Decoder %s does not support device type %s", decoder.Name(), deviceType.String())
}
if config.methods&C.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX != 0 && config.device_type == C.enum_AVHWDeviceType(deviceType) {
return PixelFormat(config.pix_fmt), nil
}
i++
if err := newError(C.av_hwdevice_ctx_create(&hdc.c, (C.enum_AVHWDeviceType)(t), deviceC, optionsC, 0)); err != nil {
return nil, err
}
return &hdc, nil
}

func (hdc *HardwareDeviceContext) Free() {
Expand Down
14 changes: 0 additions & 14 deletions hardware_device_context_test.go

This file was deleted.

14 changes: 7 additions & 7 deletions hardware_device_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ import (
type HardwareDeviceType C.enum_AVHWDeviceType

const (
HardwareDeviceTypeNone = HardwareDeviceType(C.AV_HWDEVICE_TYPE_NONE)
HardwareDeviceTypeVDPAU = HardwareDeviceType(C.AV_HWDEVICE_TYPE_VDPAU)
HardwareDeviceTypeCUDA = HardwareDeviceType(C.AV_HWDEVICE_TYPE_CUDA)
HardwareDeviceTypeVAAPI = HardwareDeviceType(C.AV_HWDEVICE_TYPE_VAAPI)
HardwareDeviceTypeDXVA2 = HardwareDeviceType(C.AV_HWDEVICE_TYPE_DXVA2)
HardwareDeviceTypeQSV = HardwareDeviceType(C.AV_HWDEVICE_TYPE_QSV)
HardwareDeviceTypeVideoToolbox = HardwareDeviceType(C.AV_HWDEVICE_TYPE_VIDEOTOOLBOX)
HardwareDeviceTypeD3D11VA = HardwareDeviceType(C.AV_HWDEVICE_TYPE_D3D11VA)
HardwareDeviceTypeDRM = HardwareDeviceType(C.AV_HWDEVICE_TYPE_DRM)
HardwareDeviceTypeOpenCL = HardwareDeviceType(C.AV_HWDEVICE_TYPE_OPENCL)
HardwareDeviceTypeDXVA2 = HardwareDeviceType(C.AV_HWDEVICE_TYPE_DXVA2)
HardwareDeviceTypeMediaCodec = HardwareDeviceType(C.AV_HWDEVICE_TYPE_MEDIACODEC)
HardwareDeviceTypeNone = HardwareDeviceType(C.AV_HWDEVICE_TYPE_NONE)
HardwareDeviceTypeOpenCL = HardwareDeviceType(C.AV_HWDEVICE_TYPE_OPENCL)
HardwareDeviceTypeQSV = HardwareDeviceType(C.AV_HWDEVICE_TYPE_QSV)
HardwareDeviceTypeVAAPI = HardwareDeviceType(C.AV_HWDEVICE_TYPE_VAAPI)
HardwareDeviceTypeVDPAU = HardwareDeviceType(C.AV_HWDEVICE_TYPE_VDPAU)
HardwareDeviceTypeVideoToolbox = HardwareDeviceType(C.AV_HWDEVICE_TYPE_VIDEOTOOLBOX)
HardwareDeviceTypeVulkan = HardwareDeviceType(C.AV_HWDEVICE_TYPE_VULKAN)
)

Expand Down
Loading

0 comments on commit 68dc1e5

Please sign in to comment.