Files
go-astiav/io_context.go
Quentin Renard 948d39059d Oopsie poopsie
2025-02-16 15:24:22 +01:00

331 lines
7.7 KiB
Go

package astiav
//#include <libavformat/avformat.h>
//#include "io_context.h"
import "C"
import (
"errors"
"fmt"
"io"
"sync"
"unsafe"
)
// https://ffmpeg.org/doxygen/7.0/structAVIOContext.html
type IOContext struct {
c *C.AVIOContext
handlerID unsafe.Pointer
}
func newIOContextFromC(c *C.AVIOContext) *IOContext {
if c == nil {
return nil
}
ic := &IOContext{c: c}
classers.set(ic)
return ic
}
var _ Classer = (*IOContext)(nil)
type IOContextReadFunc func(b []byte) (n int, err error)
type IOContextSeekFunc func(offset int64, whence int) (n int64, err error)
type IOContextWriteFunc func(b []byte) (n int, err error)
// https://ffmpeg.org/doxygen/7.0/avio_8h.html#a50c588d3c44707784f3afde39e1c181c
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")
return
}
// Allocate buffer
buffer := C.av_malloc(C.size_t(bufferSize))
if buffer == nil {
err = errors.New("astiav: allocating buffer failed")
return
}
// Make sure buffer is freed in case of error
defer func() {
if err != nil {
C.av_free(buffer)
}
}()
// Since go doesn't allow c to store pointers to go data, we need to create this C pointer
handlerID := C.av_malloc(C.size_t(1))
if handlerID == nil {
err = errors.New("astiav: allocating handler id failed")
return
}
// Make sure handler id is freed in case of error
defer func() {
if err != nil {
C.av_free(handlerID)
}
}()
// Get callbacks
var cReadFunc, cSeekFunc, cWriteFunc *[0]byte
if readFunc != nil {
cReadFunc = (*[0]byte)(C.astiavIOContextReadFunc)
}
if seekFunc != nil {
cSeekFunc = (*[0]byte)(C.astiavIOContextSeekFunc)
}
if writeFunc != nil {
cWriteFunc = (*[0]byte)(C.astiavIOContextWriteFunc)
}
// Get write flag
wf := C.int(0)
if writable {
wf = C.int(1)
}
// Allocate io context
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
}
// Create io context
ic = newIOContextFromC(cic)
// Store handler
ic.handlerID = handlerID
ioContextHandlers.set(handlerID, &ioContextHandler{
r: readFunc,
s: seekFunc,
w: writeFunc,
})
return
}
// https://ffmpeg.org/doxygen/7.0/avio_8c.html#ae8589aae955d16ca228b6b9d66ced33d
func OpenIOContext(filename string, flags IOContextFlags, ii *IOInterrupter, d *Dictionary) (*IOContext, error) {
cfi := C.CString(filename)
defer C.free(unsafe.Pointer(cfi))
var dc **C.AVDictionary
if d != nil {
dc = &d.c
}
var cii *C.AVIOInterruptCB = nil
if ii != nil {
cii = ii.c
}
var c *C.AVIOContext
if err := newError(C.avio_open2(&c, cfi, C.int(flags), cii, dc)); err != nil {
return nil, err
}
return newIOContextFromC(c), nil
}
func (ic *IOContext) Class() *Class {
if ic.c == nil {
return nil
}
return newClassFromC(unsafe.Pointer(ic.c))
}
// https://ffmpeg.org/doxygen/7.0/avio_8c.html#ae118a1f37f1e48617609ead9910aac15
func (ic *IOContext) Close() error {
if ic.c != nil {
// Make sure to clone the classer before freeing the object since
// the C free method may reset the pointer
c := newClonedClasser(ic)
// Error is returned when closing the url but pointer has been freed at this point
// therefore we must make sure classers are cleaned up properly even on error
err := newError(C.avio_closep(&ic.c))
// Make sure to remove from classers after freeing the object since
// the C free method may use methods needing the classer
if c != nil && ic.c == nil {
classers.del(c)
}
return err
}
return nil
}
// https://ffmpeg.org/doxygen/7.0/avio_8h.html#ad1baf8cd6711f05a45d0339cafe2d21d
func (ic *IOContext) Free() {
if ic.c != nil {
if ic.c.buffer != nil {
C.av_freep(unsafe.Pointer(&ic.c.buffer))
}
if ic.handlerID != nil {
C.av_free(ic.handlerID)
ic.handlerID = nil
}
// Make sure to clone the classer before freeing the object since
// the C free method may reset the pointer
c := newClonedClasser(ic)
C.avio_context_free(&ic.c)
// Make sure to remove from classers after freeing the object since
// the C free method may use methods needing the classer
if c != nil {
classers.del(c)
}
}
return
}
// https://ffmpeg.org/doxygen/7.0/avio_8h.html#a53843d2cbe6282d994fcf59c03d59294
func (ic *IOContext) Read(b []byte) (n int, err error) {
// Nothing to read
if b == nil || len(b) <= 0 {
return
}
// Allocate buffer
buf := C.av_malloc(C.size_t(len(b)))
if buf == nil {
err = errors.New("astiav: allocating buffer failed")
return
}
// Make sure buffer is freed
defer C.av_free(buf)
// Read
ret := C.avio_read_partial(ic.c, (*C.uchar)(unsafe.Pointer(buf)), C.int(len(b)))
if err = newError(ret); err != nil {
err = fmt.Errorf("astiav: reading failed: %w", err)
return
}
// Copy
C.memcpy(unsafe.Pointer(&b[0]), unsafe.Pointer(buf), C.size_t(ret))
n = int(ret)
return
}
// https://ffmpeg.org/doxygen/7.0/avio_8h.html#a03e23bf0144030961c34e803c71f614f
func (ic *IOContext) Seek(offset int64, whence int) (int64, error) {
ret := C.avio_seek(ic.c, C.int64_t(offset), C.int(whence))
if err := newError(C.int(ret)); err != nil {
return 0, err
}
return int64(ret), nil
}
// https://ffmpeg.org/doxygen/7.0/avio_8h.html#acc3626afc6aa3964b75d02811457164e
func (ic *IOContext) Write(b []byte) {
// Nothing to write
if b == nil || len(b) <= 0 {
return
}
// Write
C.avio_write(ic.c, (*C.uchar)(unsafe.Pointer(&b[0])), C.int(len(b)))
}
// https://ffmpeg.org/doxygen/7.0/avio_8h.html#ad88b866a118c17c95663f7782b2e8946
func (ic *IOContext) Flush() {
C.avio_flush(ic.c)
}
type ioContextHandler struct {
r IOContextReadFunc
s IOContextSeekFunc
w IOContextWriteFunc
}
var ioContextHandlers = newIOContextHandlerPool()
type ioContextHandlerPool struct {
m sync.Mutex
p map[unsafe.Pointer]*ioContextHandler
}
func newIOContextHandlerPool() *ioContextHandlerPool {
return &ioContextHandlerPool{p: make(map[unsafe.Pointer]*ioContextHandler)}
}
func (p *ioContextHandlerPool) set(id unsafe.Pointer, h *ioContextHandler) {
p.m.Lock()
defer p.m.Unlock()
p.p[id] = h
}
func (p *ioContextHandlerPool) get(id unsafe.Pointer) (h *ioContextHandler, ok bool) {
p.m.Lock()
defer p.m.Unlock()
h, ok = p.p[id]
return
}
//export goAstiavIOContextReadFunc
func goAstiavIOContextReadFunc(opaque unsafe.Pointer, buf *C.uint8_t, bufSize C.int) C.int {
// Get handler
h, ok := ioContextHandlers.get(opaque)
if !ok {
return C.AVERROR_UNKNOWN
}
// Create go buffer
b := make([]byte, int(bufSize), int(bufSize))
// Read
n, err := h.r(b)
if err != nil {
var e Error
if errors.As(err, &e) {
return C.int(e)
} else if errors.Is(err, io.EOF) {
return C.AVERROR_EOF
}
return C.AVERROR_UNKNOWN
}
// Copy
C.memcpy(unsafe.Pointer(buf), unsafe.Pointer(&b[0]), C.size_t(n))
return C.int(n)
}
//export goAstiavIOContextSeekFunc
func goAstiavIOContextSeekFunc(opaque unsafe.Pointer, offset C.int64_t, whence C.int) C.int64_t {
// Get handler
h, ok := ioContextHandlers.get(opaque)
if !ok {
return C.AVERROR_UNKNOWN
}
// Seek
n, err := h.s(int64(offset), int(whence))
if err != nil {
var e Error
if errors.As(err, &e) {
return C.int64_t(e)
}
return C.int64_t(C.AVERROR_UNKNOWN)
}
return C.int64_t(n)
}
//export goAstiavIOContextWriteFunc
func goAstiavIOContextWriteFunc(opaque unsafe.Pointer, buf *C.uint8_t, bufSize C.int) C.int {
// Get handler
h, ok := ioContextHandlers.get(opaque)
if !ok {
return C.AVERROR_UNKNOWN
}
// Write
n, err := h.w(C.GoBytes(unsafe.Pointer(buf), bufSize))
if err != nil {
var e Error
if errors.As(err, &e) {
return C.int(e)
}
return C.AVERROR_UNKNOWN
}
return C.int(n)
}