refactor(ffmpeg): refactor pointer and read stream
This commit is contained in:
parent
fb93b23760
commit
69b69d342c
4 changed files with 11 additions and 281 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue