Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into sws
Browse files Browse the repository at this point in the history
  • Loading branch information
Cacsjep committed Jan 25, 2024
2 parents 08aa9f2 + 6e42185 commit 0b75de3
Show file tree
Hide file tree
Showing 17 changed files with 502 additions and 47 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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/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)
|Scaling|[see](examples/scaling/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/scaling_video.c)
Expand Down
13 changes: 8 additions & 5 deletions astiav_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import (
var globalHelper = newHelper()

func TestMain(m *testing.M) {
// Run
m.Run()
// Make sure to exit with the proper code
var code int
defer func(code *int) {
os.Exit(*code)
}(&code)

// Make sure to close global helper
globalHelper.close()
defer globalHelper.close()

// Exit
os.Exit(0)
// Run
code = m.Run()
}

type helper struct {
Expand Down
13 changes: 13 additions & 0 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,16 @@ 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) (configs []CodecHardwareConfig) {
var i int
for {
config := C.avcodec_get_hw_config(c.c, C.int(i))
if config == nil {
break
}
configs = append(configs, CodecHardwareConfig{c: config})
i++
}
return
}
4 changes: 4 additions & 0 deletions codec_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,7 @@ func (cc *CodecContext) SendFrame(f *Frame) error {
}
return newError(C.avcodec_send_frame(cc.c, fc))
}

func (cc *CodecContext) SetHardwareDeviceContext(hdc *HardwareDeviceContext) {
cc.c.hw_device_ctx = hdc.c
}
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 (
CodecHardwareConfigMethodFlagAdHoc = CodecHardwareConfigMethodFlag(C.AV_CODEC_HW_CONFIG_METHOD_AD_HOC)
CodecHardwareConfigMethodFlagHwDeviceCtx = CodecHardwareConfigMethodFlag(C.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
CodecHardwareConfigMethodFlagHwFramesCtx = CodecHardwareConfigMethodFlag(C.AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX)
CodecHardwareConfigMethodFlagInternal = CodecHardwareConfigMethodFlag(C.AV_CODEC_HW_CONFIG_METHOD_INTERNAL)
)
9 changes: 4 additions & 5 deletions codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,14 @@ func TestCodec(t *testing.T) {
require.Equal(t, "aac", c.Name())
require.Equal(t, "aac", c.String())

c = astiav.FindEncoder(astiav.CodecIDH264)
c = astiav.FindEncoder(astiav.CodecIDMjpeg)
require.NotNil(t, c)
require.False(t, c.IsDecoder())
require.True(t, c.IsEncoder())
require.Contains(t, c.PixelFormats(), astiav.PixelFormatNv12)
require.Contains(t, c.PixelFormats(), astiav.PixelFormatYuv420P)
require.Contains(t, c.PixelFormats(), astiav.PixelFormatYuvj420P)
require.Nil(t, c.SampleFormats())
require.Contains(t, c.Name(), "264")
require.Contains(t, c.String(), "264")
require.Contains(t, c.Name(), "mjpeg")
require.Contains(t, c.String(), "mjpeg")

c = astiav.FindEncoderByName("mjpeg")
require.NotNil(t, c)
Expand Down
192 changes: 192 additions & 0 deletions examples/hardware_decoding/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package main

import (
"errors"
"flag"
"fmt"
"log"
"strings"

"github.com/asticode/go-astiav"
)

var (
hardwareDeviceTypeName = flag.String("d", "", "the hardware device type like: 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(l astiav.LogLevel, fmt, msg, parent string) {
log.Printf("ffmpeg log: %s (level: %d)\n", strings.TrimSpace(msg), l)
})

// Parse flags
flag.Parse()

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

// 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()

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

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

// Alloc 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}

// Find decoder
if s.decCodec = astiav.FindDecoder(is.CodecParameters().CodecID()); s.decCodec == nil {
log.Fatal(errors.New("main: codec is nil"))
}

// Alloc 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()

// Get codec hardware configs
hardwareConfigs := s.decCodec.HardwareConfigs(hardwareDeviceType)

// Loop through codec hardware configs
for _, p := range 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
s.hardwareDeviceContext, err = astiav.CreateHardwareDeviceContext(hardwareDeviceType, "", nil)
if err != nil {
log.Fatal(fmt.Errorf("main: creating hardware device context failed: %w", err))
}
s.decCodecContext.SetHardwareDeviceContext(s.hardwareDeviceContext)

// 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 {
// Read frame
if err := inputFormatContext.ReadFrame(pkt); err != nil {
if errors.Is(err, astiav.ErrEof) {
break
}
log.Fatal(fmt.Errorf("main: reading frame failed: %w", err))
}

// Get stream
s, ok := streams[pkt.StreamIndex()]
if !ok {
continue
}

// Send packet
if err := s.decCodecContext.SendPacket(pkt); err != nil {
log.Fatal(fmt.Errorf("main: sending packet failed: %w", err))
}

// Loop
for {
// Receive frame
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))
}

// 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))
}

// 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", pkt.StreamIndex(), finalFrame.Pts())
}
}
}
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.

9 changes: 8 additions & 1 deletion frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ package astiav
//#include <libavutil/frame.h>
//#include <libavutil/imgutils.h>
//#include <libavutil/samplefmt.h>
//#include <libavutil/hwcontext.h>
import "C"
import "unsafe"
import (
"unsafe"
)

const NumDataPointers = uint(C.AV_NUM_DATA_POINTERS)

Expand Down Expand Up @@ -187,6 +190,10 @@ func (f *Frame) SetWidth(w int) {
f.c.width = C.int(w)
}

func (f *Frame) TransferHardwareData(dst *Frame) error {
return newError(C.av_hwframe_transfer_data(dst.c, f.c, 0))
}

func (f *Frame) Free() {
C.av_frame_free(&f.c)
}
Expand Down
Loading

0 comments on commit 0b75de3

Please sign in to comment.