refactor(ffmpeg): refactor pointer and read stream

This commit is contained in:
Adrian Shum 2022-09-30 12:32:39 +08:00 committed by GitHub
parent fb93b23760
commit 69b69d342c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 11 additions and 281 deletions

View file

@ -3,19 +3,25 @@ package ffmpeg
// #include "ffmpeg.h" // #include "ffmpeg.h"
import "C" import "C"
import ( import (
"github.com/cshum/imagorvideo/ffmpeg/pointer" "github.com/cshum/imagor/vips/pointer"
"io" "io"
"reflect"
"unsafe" "unsafe"
) )
//export goPacketRead //export goPacketRead
func goPacketRead(opaque unsafe.Pointer, buf *C.uint8_t, bufSize C.int) C.int { func goPacketRead(opaque unsafe.Pointer, buffer *C.uint8_t, bufSize C.int) C.int {
ctx, ok := pointer.Restore(opaque).(*AVContext) ctx, ok := pointer.Restore(opaque).(*AVContext)
if !ok || ctx.reader == nil { if !ok || ctx.reader == nil {
return C.int(ErrUnknown) return C.int(ErrUnknown)
} }
p := (*[1 << 30]byte)(unsafe.Pointer(buf))[:bufSize:bufSize] sh := &reflect.SliceHeader{
n, err := ctx.reader.Read(p) Data: uintptr(unsafe.Pointer(buffer)),
Len: int(bufSize),
Cap: int(bufSize),
}
buf := *(*[]byte)(unsafe.Pointer(sh))
n, err := ctx.reader.Read(buf)
if err == io.EOF { if err == io.EOF {
return C.int(ErrEOF) return C.int(ErrEOF)
} else if err != nil { } else if err != nil {

View file

@ -7,7 +7,7 @@ package ffmpeg
import "C" import "C"
import ( import (
"context" "context"
"github.com/cshum/imagorvideo/ffmpeg/pointer" "github.com/cshum/imagor/vips/pointer"
"io" "io"
"time" "time"
"unsafe" "unsafe"

View file

@ -1,100 +0,0 @@
package pointer
// #include <stdlib.h>
import "C"
import (
"sync"
"unsafe"
)
const blockSize = 1024
var (
mutex sync.RWMutex
store = map[unsafe.Pointer]interface{}{}
free []unsafe.Pointer
blocks []unsafe.Pointer
)
func allocMem() {
mem := C.malloc(blockSize)
if mem == nil {
panic("can't allocate memory block for C pointers")
}
blocks = append(blocks, mem)
for i := 0; i < blockSize; i++ {
p := unsafe.Pointer(uintptr(mem) + uintptr(blockSize-1-i))
free = append(free, p)
}
}
func getPtr() unsafe.Pointer {
// Generate real fake C pointer.
// This pointer will not store any data, but will be used for indexing
// purposes. Since Go doesn't allow to cast dangling pointer to
// unsafe.Pointer, we do really allocate memory. Why we need indexing? Because
// Go doest allow C code to store pointers to Go data.
if len(free) == 0 {
allocMem()
}
n := len(free) - 1
p := free[n]
free = free[:n]
return p
}
// Save an object in the storage and return an index pointer to it.
func Save(v interface{}) unsafe.Pointer {
if v == nil {
return nil
}
mutex.Lock()
ptr := getPtr()
store[ptr] = v
mutex.Unlock()
return ptr
}
// Restore an object from the storage by its index pointer.
func Restore(ptr unsafe.Pointer) (v interface{}) {
if ptr == nil {
return nil
}
mutex.RLock()
v = store[ptr]
mutex.RUnlock()
return
}
// Unref removes an object from the storage and returns the index pointer to the
// pool for reuse.
func Unref(ptr unsafe.Pointer) {
if ptr == nil {
return
}
mutex.Lock()
if _, ok := store[ptr]; ok {
delete(store, ptr)
free = append(free, ptr)
}
mutex.Unlock()
}
// Clear storage and free all memory
func Clear() {
mutex.Lock()
for p := range store {
delete(store, p)
}
free = nil
for _, p := range blocks {
C.free(p)
}
blocks = nil
mutex.Unlock()
}

View file

@ -1,176 +0,0 @@
package pointer
import (
"sync"
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
)
func TestPointer(t *testing.T) {
t.Cleanup(Clear)
// assert := makeAssert(t)
assert.Len(t, store, 0)
assert.Len(t, free, 0)
assert.Len(t, blocks, 0)
mutex.Lock()
assert.Equal(t, unsafe.Pointer(nil), Save(nil))
assert.Nil(t, Restore(nil))
Unref(nil)
mutex.Unlock()
i1 := Save("foo")
i2 := Save("bar")
i3 := Save("baz")
assert.Len(t, store, 3)
assert.Len(t, free, blockSize-3)
assert.Len(t, blocks, 1)
var x interface{}
x = Restore(i1)
assert.NotNil(t, x)
if s, ok := x.(string); ok {
assert.Equal(t, "foo", s)
} else {
t.Fail()
}
x = Restore(unsafe.Pointer(&x))
assert.Nil(t, x)
x = Restore(i3)
assert.NotNil(t, x)
if s, ok := x.(string); ok {
assert.Equal(t, "baz", s)
} else {
t.Fail()
}
Unref(i3)
x = Restore(i3)
assert.Nil(t, x)
Unref(i2)
Unref(i1)
assert.Len(t, store, 0)
assert.Len(t, free, blockSize)
assert.Len(t, blocks, 1)
i3 = Save("baz")
assert.Len(t, store, 1)
assert.Len(t, free, blockSize-1)
Clear()
assert.Len(t, store, 0)
assert.Len(t, free, 0)
assert.Len(t, blocks, 0)
}
func TestPointerIndexing(t *testing.T) {
t.Cleanup(Clear)
assert.Len(t, store, 0)
assert.Len(t, free, 0)
assert.Len(t, blocks, 0)
i1 := Save("foo")
i2 := Save("bar")
_ = Save("baz")
_ = Save("wibble")
_ = Save("wabble")
assert.Len(t, store, 5)
assert.Len(t, free, blockSize-5)
// Check that when we remove the first items inserted into the map there are
// no subsequent issues
Unref(i1)
Unref(i2)
assert.Len(t, free, blockSize-3)
_ = Save("flim")
ilast := Save("flam")
assert.Len(t, store, 5)
assert.Len(t, free, blockSize-5)
x := Restore(ilast)
assert.NotNil(t, x)
if s, ok := x.(string); ok {
assert.Equal(t, "flam", s)
}
}
func TestCallbacksData(t *testing.T) {
t.Cleanup(Clear)
assert.Len(t, store, 0)
assert.Len(t, free, 0)
assert.Len(t, blocks, 0)
// insert a plain function
i1 := Save(func(v int) int { return v + 1 })
// insert a type "containing" a function, note that it doesn't
// actually have a callable function. Users of the type must
// check that themselves
type flup struct {
Stuff int
Junk func(int, int) error
}
i2 := Save(flup{
Stuff: 55,
})
// did we get a function back
x1 := Restore(i1)
if assert.NotNil(t, x1) {
if f, ok := x1.(func(v int) int); ok {
assert.Equal(t, 2, f(1))
} else {
t.Fatalf("conversion failed")
}
}
// did we get our data structure back
x2 := Restore(i2)
if assert.NotNil(t, x2) {
if d, ok := x2.(flup); ok {
assert.Equal(t, 55, d.Stuff)
assert.Nil(t, d.Junk)
} else {
t.Fatalf("conversion failed")
}
}
}
func BenchmarkPointer(t *testing.B) {
t.Cleanup(Clear)
assert.Len(t, store, 0)
assert.Len(t, free, 0)
assert.Len(t, blocks, 0)
const workers = 1000
var wg sync.WaitGroup
f := func() {
defer wg.Done()
var x interface{}
var i1, i2, i3 unsafe.Pointer
for i := 0; i < t.N/workers; i++ {
i1 = Save("foo")
i2 = Save("bar")
i3 = Save("baz")
x = Restore(i1)
assert.NotNil(t, x)
if s, ok := x.(string); ok {
assert.Equal(t, "foo", s)
} else {
t.Fail()
}
x = Restore(i3)
assert.NotNil(t, x)
if s, ok := x.(string); ok {
assert.Equal(t, "baz", s)
} else {
t.Fail()
}
Unref(i3)
Unref(i2)
Unref(i1)
}
}
for i := 0; i < workers; i++ {
wg.Add(1)
go f()
}
wg.Wait()
assert.Len(t, store, 0)
assert.Len(t, free, len(blocks)*blockSize)
}