diff --git a/ffmpeg/callback.go b/ffmpeg/callback.go index 1fcee64..c811738 100644 --- a/ffmpeg/callback.go +++ b/ffmpeg/callback.go @@ -3,19 +3,25 @@ package ffmpeg // #include "ffmpeg.h" import "C" import ( - "github.com/cshum/imagorvideo/ffmpeg/pointer" + "github.com/cshum/imagor/vips/pointer" "io" + "reflect" "unsafe" ) //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) if !ok || ctx.reader == nil { return C.int(ErrUnknown) } - p := (*[1 << 30]byte)(unsafe.Pointer(buf))[:bufSize:bufSize] - n, err := ctx.reader.Read(p) + sh := &reflect.SliceHeader{ + 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 { return C.int(ErrEOF) } else if err != nil { diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index 42b7336..2408ce9 100644 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -7,7 +7,7 @@ package ffmpeg import "C" import ( "context" - "github.com/cshum/imagorvideo/ffmpeg/pointer" + "github.com/cshum/imagor/vips/pointer" "io" "time" "unsafe" diff --git a/ffmpeg/pointer/pointer.go b/ffmpeg/pointer/pointer.go deleted file mode 100644 index 5673d1d..0000000 --- a/ffmpeg/pointer/pointer.go +++ /dev/null @@ -1,100 +0,0 @@ -package pointer - -// #include -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() -} diff --git a/ffmpeg/pointer/pointer_test.go b/ffmpeg/pointer/pointer_test.go deleted file mode 100644 index a3ef460..0000000 --- a/ffmpeg/pointer/pointer_test.go +++ /dev/null @@ -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) -}