From a83963baaac7fa85303bb597d7e4e3d2a8841eeb Mon Sep 17 00:00:00 2001 From: Quentin Renard Date: Thu, 16 May 2024 17:16:53 +0200 Subject: [PATCH] Added custom io demuxing + writable to AllocIOContext --- README.md | 1 + examples/custom_io_demuxing/main.go | 105 ++++++++++++++++++++++++++++ format_context.go | 7 +- io_context.go | 10 ++- io_context_test.go | 2 +- 5 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 examples/custom_io_demuxing/main.go diff --git a/README.md b/README.md index b3d4495..1f8751a 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Examples are located in the [examples](examples) directory and mirror as much as |name|astiav|ffmpeg| |---|---|---| |BitStream Filtering|[see](examples/bit_stream_filtering/main.go)|X +|Custom IO Demuxing|[see](examples/custom_io_demuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/avio_reading.c) |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) diff --git a/examples/custom_io_demuxing/main.go b/examples/custom_io_demuxing/main.go new file mode 100644 index 0000000..825a41f --- /dev/null +++ b/examples/custom_io_demuxing/main.go @@ -0,0 +1,105 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "log" + "os" + "strings" + + "github.com/asticode/go-astiav" +) + +var ( + input = flag.String("i", "", "the input path") +) + +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 == "" { + log.Println("Usage: -i ") + return + } + + // Alloc packet + pkt := astiav.AllocPacket() + defer pkt.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 file + f, err := os.Open(*input) + if err != nil { + log.Fatal(fmt.Errorf("main: opening %s failed: %w", *input, err)) + } + defer f.Close() + + // Alloc io context + ioContext, err := astiav.AllocIOContext( + 4096, + false, + func(b []byte) (n int, err error) { + return f.Read(b) + }, + func(offset int64, whence int) (n int64, err error) { + return f.Seek(offset, whence) + }, + nil, + ) + if err != nil { + log.Fatal(fmt.Errorf("main: allocating io context failed: %w", err)) + } + defer ioContext.Free() + + // Store io context + inputFormatContext.SetPb(ioContext) + + // Open input + if err := inputFormatContext.OpenInput("", 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 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)) + } + + // Do something with the packet + log.Printf("new packet: stream %d - pts: %d", pkt.StreamIndex(), pkt.Pts()) + } + + // Success + log.Println("success") +} diff --git a/format_context.go b/format_context.go index 363fea3..63304db 100644 --- a/format_context.go +++ b/format_context.go @@ -150,8 +150,11 @@ func (fc *FormatContext) SetStrictStdCompliance(strictStdCompliance StrictStdCom } func (fc *FormatContext) OpenInput(url string, fmt *InputFormat, d *Dictionary) error { - urlc := C.CString(url) - defer C.free(unsafe.Pointer(urlc)) + var urlc *C.char + if url != "" { + urlc = C.CString(url) + defer C.free(unsafe.Pointer(urlc)) + } var dc **C.struct_AVDictionary if d != nil { dc = &d.c diff --git a/io_context.go b/io_context.go index 8241169..ff2654f 100644 --- a/io_context.go +++ b/io_context.go @@ -34,7 +34,7 @@ type IOContextSeekFunc func(offset int64, whence int) (n int64, err error) type IOContextWriteFunc func(b []byte) (n int, err error) -func AllocIOContext(bufferSize int, readFunc IOContextReadFunc, seekFunc IOContextSeekFunc, writeFunc IOContextWriteFunc) (ic *IOContext, err error) { +func AllocIOContext(bufferSize int, writable bool, readFunc IOContextReadFunc, seekFunc IOContextSeekFunc, writeFunc IOContextWriteFunc) (ic *IOContext, err error) { // Invalid buffer size if bufferSize <= 0 { err = errors.New("astiav: buffer size <= 0") @@ -81,8 +81,14 @@ func AllocIOContext(bufferSize int, readFunc IOContextReadFunc, seekFunc IOConte cWriteFunc = (*[0]byte)(C.astiavIOContextWriteFunc) } + // Get write flag + wf := C.int(0) + if writable { + wf = C.int(1) + } + // Alloc io context - cic := C.avio_alloc_context((*C.uchar)(buffer), C.int(bufferSize), 1, handlerID, cReadFunc, cWriteFunc, cSeekFunc) + cic := C.avio_alloc_context((*C.uchar)(buffer), C.int(bufferSize), wf, handlerID, cReadFunc, cWriteFunc, cSeekFunc) if cic == nil { err = errors.New("astiav: allocating io context failed: %w") return diff --git a/io_context_test.go b/io_context_test.go index 8aaa71c..0f82882 100644 --- a/io_context_test.go +++ b/io_context_test.go @@ -13,7 +13,7 @@ func TestIOContext(t *testing.T) { rb := []byte("read") wb := []byte("write") var written []byte - c, err := AllocIOContext(8, func(b []byte) (int, error) { + c, err := AllocIOContext(8, true, func(b []byte) (int, error) { copy(b, rb) return len(rb), nil }, func(offset int64, whence int) (n int64, err error) {