Buffer and Memory Optimizations (#6)

* Fixes for memory leaks when creating or mapping the contents of a buffer
* Unmap methods on Buffer and Memory objects for required use after utilizing their Map methods
* Optimize MapInfo.WriteData to use memcpy
* Re-implement BufferFromBytes using GBytes and `gst_buffer_new_wrapped_bytes` instead of `gst_buffer_new_wrapped`.
This commit is contained in:
Avi Zimmerman
2020-12-30 12:38:24 +02:00
committed by GitHub
parent 539f6ddd70
commit c045acdff1
8 changed files with 203 additions and 167 deletions

View File

@@ -70,6 +70,7 @@ func createPipeline() (*gst.Pipeline, error) {
// We also know what format to expect because we set it with the caps. So we convert // We also know what format to expect because we set it with the caps. So we convert
// the map directly to signed 16-bit integers. // the map directly to signed 16-bit integers.
samples := buffer.Map(gst.MapRead).AsInt16Slice() samples := buffer.Map(gst.MapRead).AsInt16Slice()
defer buffer.Unmap()
// Calculate the root mean square for the buffer // Calculate the root mean square for the buffer
// (https://en.wikipedia.org/wiki/Root_mean_square) // (https://en.wikipedia.org/wiki/Root_mean_square)

View File

@@ -71,11 +71,11 @@ func createPipeline() (*gst.Pipeline, error) {
fmt.Println("Producing frame:", i) fmt.Println("Producing frame:", i)
// Create a buffer that can hold exactly one video RGBA frame. // Create a buffer that can hold exactly one video RGBA frame.
buf := gst.NewBufferWithSize(videoInfo.Size()) buffer := gst.NewBufferWithSize(videoInfo.Size())
// For each frame we produce, we set the timestamp when it should be displayed // For each frame we produce, we set the timestamp when it should be displayed
// The autovideosink will use this information to display the frame at the right time. // The autovideosink will use this information to display the frame at the right time.
buf.SetPresentationTimestamp(time.Duration(i) * 500 * time.Millisecond) buffer.SetPresentationTimestamp(time.Duration(i) * 500 * time.Millisecond)
// Produce an image frame for this iteration. // Produce an image frame for this iteration.
pixels := produceImageFrame(palette[i]) pixels := produceImageFrame(palette[i])
@@ -87,10 +87,11 @@ func createPipeline() (*gst.Pipeline, error) {
// //
// There are convenience wrappers for building buffers directly from byte sequences as // There are convenience wrappers for building buffers directly from byte sequences as
// well. // well.
buf.Map(gst.MapWrite).WriteData(pixels) buffer.Map(gst.MapWrite).WriteData(pixels)
defer buffer.Unmap()
// Push the buffer onto the pipeline. // Push the buffer onto the pipeline.
self.PushBuffer(buf) self.PushBuffer(buffer)
i++ i++
}, },

View File

@@ -60,6 +60,7 @@ func padProbes(mainLoop *gst.MainLoop) error {
// So mapping the buffer makes the underlying memory region accessible to us. // So mapping the buffer makes the underlying memory region accessible to us.
// See: https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/allocation.html // See: https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/allocation.html
mapInfo := buffer.Map(gst.MapRead) mapInfo := buffer.Map(gst.MapRead)
defer buffer.Unmap()
// We know what format the data in the memory region has, since we requested // We know what format the data in the memory region has, since we requested
// it by setting the fakesink's caps. So what we do here is interpret the // it by setting the fakesink's caps. So what we do here is interpret the

View File

@@ -36,6 +36,7 @@ func GetMaxBufferMemory() uint64 { return uint64(C.gst_buffer_get_max_memory())
// Buffer is a go representation of a GstBuffer. // Buffer is a go representation of a GstBuffer.
type Buffer struct { type Buffer struct {
ptr *C.GstBuffer ptr *C.GstBuffer
mapInfo *MapInfo
} }
// NewEmptyBuffer returns a new empty buffer. // NewEmptyBuffer returns a new empty buffer.
@@ -74,11 +75,9 @@ func NewBufferAllocate(alloc *Allocator, params *AllocationParams, size int64) *
// NewBufferFromBytes returns a new buffer from the given byte slice. // NewBufferFromBytes returns a new buffer from the given byte slice.
func NewBufferFromBytes(b []byte) *Buffer { func NewBufferFromBytes(b []byte) *Buffer {
str := string(b) gbytes := C.g_bytes_new((C.gconstpointer)(unsafe.Pointer(&b[0])), C.gsize(len(b)))
p := unsafe.Pointer(C.CString(str)) defer C.g_bytes_unref(gbytes) // gstreamer takes another ref so we don't need ours after returning
// memory is freed by gstreamer after building the new buffer return wrapBuffer(C.gst_buffer_new_wrapped_bytes(gbytes))
buf := C.gst_buffer_new_wrapped((C.gpointer)(p), C.gsize(len(str)))
return wrapBuffer(buf)
} }
// NewBufferFromReader returns a new buffer from the given io.Reader. // NewBufferFromReader returns a new buffer from the given io.Reader.
@@ -144,6 +143,10 @@ func (b *Buffer) Reader() io.Reader { return bytes.NewBuffer(b.Bytes()) }
// Bytes returns a byte slice of the data inside this buffer. // Bytes returns a byte slice of the data inside this buffer.
func (b *Buffer) Bytes() []byte { func (b *Buffer) Bytes() []byte {
mapInfo := b.Map(MapRead) mapInfo := b.Map(MapRead)
if mapInfo == nil {
return nil
}
defer b.Unmap()
return mapInfo.Bytes() return mapInfo.Bytes()
} }
@@ -612,20 +615,29 @@ func (b *Buffer) IterateMetaFiltered(meta *Meta, apiType glib.Type) *Meta {
return wrapMeta(C.gst_buffer_iterate_meta_filtered(b.Instance(), (*C.gpointer)(&ptr), C.GType(apiType))) return wrapMeta(C.gst_buffer_iterate_meta_filtered(b.Instance(), (*C.gpointer)(&ptr), C.GType(apiType)))
} }
// Map will map the data inside this buffer. // Map will map the data inside this buffer. This function can return nil if the memory is not read or writable.
//
// Unmap the Buffer after usage.
func (b *Buffer) Map(flags MapFlags) *MapInfo { func (b *Buffer) Map(flags MapFlags) *MapInfo {
var mapInfo C.GstMapInfo mapInfo := C.malloc(C.sizeof_GstMapInfo)
C.gst_buffer_map( C.gst_buffer_map(
(*C.GstBuffer)(b.Instance()), (*C.GstBuffer)(b.Instance()),
(*C.GstMapInfo)(unsafe.Pointer(&mapInfo)), (*C.GstMapInfo)(mapInfo),
C.GstMapFlags(flags), C.GstMapFlags(flags),
) )
return wrapMapInfo(&mapInfo, func() { if mapInfo == C.NULL {
// This may not be necesesary return nil
// if gobool(C.isBuffer(b.Instance())) { }
// C.gst_buffer_unmap(b.Instance(), (*C.GstMapInfo)(unsafe.Pointer(&mapInfo))) b.mapInfo = wrapMapInfo((*C.GstMapInfo)(mapInfo))
// } return b.mapInfo
}) }
// Unmap will unmap the data inside this memory. Use this after calling Map on the buffer.
func (b *Buffer) Unmap() {
if b.mapInfo == nil {
return
}
C.gst_buffer_unmap(b.Instance(), (*C.GstMapInfo)(b.mapInfo.Instance()))
} }
// MapRange maps the info of length merged memory blocks starting at idx in buffer. // MapRange maps the info of length merged memory blocks starting at idx in buffer.
@@ -636,18 +648,20 @@ func (b *Buffer) Map(flags MapFlags) *MapInfo {
// When the buffer is writable but the memory isn't, a writable copy will automatically be // When the buffer is writable but the memory isn't, a writable copy will automatically be
// created and returned. The readonly copy of the buffer memory will then also be replaced with this writable copy. // created and returned. The readonly copy of the buffer memory will then also be replaced with this writable copy.
// //
// Unmap after usage. // Unmap the Buffer after usage.
func (b *Buffer) MapRange(idx uint, length int, flags MapFlags) *MapInfo { func (b *Buffer) MapRange(idx uint, length int, flags MapFlags) *MapInfo {
var mapInfo C.GstMapInfo mapInfo := C.malloc(C.sizeof_GstMapInfo)
C.gst_buffer_map_range( C.gst_buffer_map_range(
(*C.GstBuffer)(b.Instance()), (*C.GstBuffer)(b.Instance()),
C.guint(idx), C.gint(length), C.guint(idx), C.gint(length),
(*C.GstMapInfo)(unsafe.Pointer(&mapInfo)), (*C.GstMapInfo)(mapInfo),
C.GstMapFlags(flags), C.GstMapFlags(flags),
) )
return wrapMapInfo(&mapInfo, func() { if mapInfo == C.NULL {
C.gst_buffer_unmap(b.Instance(), (*C.GstMapInfo)(unsafe.Pointer(&mapInfo))) return nil
}) }
b.mapInfo = wrapMapInfo((*C.GstMapInfo)(mapInfo))
return b.mapInfo
} }
// Memset fills buf with size bytes with val starting from offset. It returns the // Memset fills buf with size bytes with val starting from offset. It returns the

135
gst/gst_mapinfo.go Normal file
View File

@@ -0,0 +1,135 @@
package gst
/*
#include "gst.go.h"
*/
import "C"
import (
"encoding/binary"
"unsafe"
)
// MapInfo is a go representation of a GstMapInfo.
type MapInfo struct {
ptr *C.GstMapInfo
}
// Instance returns the underlying GstMapInfo instance.
func (m *MapInfo) Instance() *C.GstMapInfo {
return m.ptr
}
// WriteData writes the given sequence directly to the map's memory.
func (m *MapInfo) WriteData(data []byte) {
C.memcpy(unsafe.Pointer(m.ptr.data), unsafe.Pointer(&data[0]), C.gsize(len(data)))
}
// Memory returns the underlying memory object.
func (m *MapInfo) Memory() *Memory {
return wrapMemory(m.ptr.memory)
}
// Data returns a pointer to the raw data inside this map.
func (m *MapInfo) Data() unsafe.Pointer {
return unsafe.Pointer(m.ptr.data)
}
// Flags returns the flags set on this map.
func (m *MapInfo) Flags() MapFlags {
return MapFlags(m.ptr.flags)
}
// Size returrns the size of this map.
func (m *MapInfo) Size() int64 {
return int64(m.ptr.size)
}
// MaxSize returns the maximum size of this map.
func (m *MapInfo) MaxSize() int64 {
return int64(m.ptr.maxsize)
}
// Bytes returns a byte slice of the data inside this map info.
func (m *MapInfo) Bytes() []byte {
return C.GoBytes(m.Data(), (C.int)(m.Size()))
}
// AsInt8Slice returns the contents of this map as a slice of signed 8-bit integers.
func (m *MapInfo) AsInt8Slice() []int8 {
uint8sl := m.AsUint8Slice()
out := make([]int8, m.Size())
for i := range out {
out[i] = int8(uint8sl[i])
}
return out
}
// AsInt16Slice returns the contents of this map as a slice of signed 16-bit integers.
func (m *MapInfo) AsInt16Slice() []int16 {
uint8sl := m.AsUint8Slice()
out := make([]int16, m.Size()/2)
for i := range out {
out[i] = int16(binary.LittleEndian.Uint16(uint8sl[i*2 : (i+1)*2]))
}
return out
}
// AsInt32Slice returns the contents of this map as a slice of signed 32-bit integers.
func (m *MapInfo) AsInt32Slice() []int32 {
uint8sl := m.AsUint8Slice()
out := make([]int32, m.Size()/4)
for i := range out {
out[i] = int32(binary.LittleEndian.Uint32(uint8sl[i*4 : (i+1)*4]))
}
return out
}
// AsInt64Slice returns the contents of this map as a slice of signed 64-bit integers.
func (m *MapInfo) AsInt64Slice() []int64 {
uint8sl := m.AsUint8Slice()
out := make([]int64, m.Size()/8)
for i := range out {
out[i] = int64(binary.LittleEndian.Uint64(uint8sl[i*8 : (i+1)*8]))
}
return out
}
// AsUint8Slice returns the contents of this map as a slice of unsigned 8-bit integers.
func (m *MapInfo) AsUint8Slice() []uint8 {
out := make([]uint8, m.Size())
for i, t := range (*[1 << 30]uint8)(m.Data())[:m.Size():m.Size()] {
out[i] = t
}
return out
}
// AsUint16Slice returns the contents of this map as a slice of unsigned 16-bit integers.
func (m *MapInfo) AsUint16Slice() []uint16 {
uint8sl := m.AsUint8Slice()
out := make([]uint16, m.Size()/2)
for i := range out {
out[i] = uint16(binary.LittleEndian.Uint16(uint8sl[i*2 : (i+1)*2]))
}
return out
}
// AsUint32Slice returns the contents of this map as a slice of unsigned 32-bit integers.
func (m *MapInfo) AsUint32Slice() []uint32 {
uint8sl := m.AsUint8Slice()
out := make([]uint32, m.Size()/4)
for i := range out {
out[i] = uint32(binary.LittleEndian.Uint32(uint8sl[i*4 : (i+1)*4]))
}
return out
}
// AsUint64Slice returns the contents of this map as a slice of unsigned 64-bit integers.
func (m *MapInfo) AsUint64Slice() []uint64 {
uint8sl := m.AsUint8Slice()
out := make([]uint64, m.Size()/8)
for i := range out {
out[i] = uint64(binary.LittleEndian.Uint64(uint8sl[i*8 : (i+1)*8]))
}
return out
}

View File

@@ -2,14 +2,10 @@ package gst
/* /*
#include "gst.go.h" #include "gst.go.h"
void writeMapData (GstMapInfo * mapInfo, gint idx, guint8 data) { mapInfo->data[idx] = data; }
*/ */
import "C" import "C"
import ( import (
"encoding/binary"
"runtime"
"unsafe" "unsafe"
"github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/glib"
@@ -22,6 +18,7 @@ import (
// Use the Buffer and its Map methods to interact with memory in both a read and writable way. // Use the Buffer and its Map methods to interact with memory in both a read and writable way.
type Memory struct { type Memory struct {
ptr *C.GstMemory ptr *C.GstMemory
mapInfo *MapInfo
} }
// NewMemoryWrapped allocates a new memory block that wraps the given data. // NewMemoryWrapped allocates a new memory block that wraps the given data.
@@ -83,151 +80,37 @@ func (m *Memory) Copy(offset, size int64) *Memory {
return wrapMemory(mem) return wrapMemory(mem)
} }
// Map the data inside the memory. This function can return nil if the memory is not readable. // Map the data inside the memory. This function can return nil if the memory is not read or writable.
func (m *Memory) Map() *MapInfo { //
var mapInfo C.GstMapInfo // Unmap the Memory after usage.
func (m *Memory) Map(flags MapFlags) *MapInfo {
mapInfo := C.malloc(C.sizeof_GstMapInfo)
C.gst_memory_map( C.gst_memory_map(
(*C.GstMemory)(m.Instance()), (*C.GstMemory)(m.Instance()),
(*C.GstMapInfo)(unsafe.Pointer(&mapInfo)), (*C.GstMapInfo)(mapInfo),
C.GST_MAP_READ, C.GstMapFlags(flags),
) )
return wrapMapInfo(&mapInfo, func() { if mapInfo == C.NULL {
C.gst_memory_unmap(m.Instance(), (*C.GstMapInfo)(unsafe.Pointer(&mapInfo))) return nil
}) }
m.mapInfo = wrapMapInfo((*C.GstMapInfo)(mapInfo))
return m.mapInfo
}
// Unmap will unmap the data inside this memory. Use this after calling Map on the Memory.
func (m *Memory) Unmap() {
if m.mapInfo == nil {
return
}
C.gst_memory_unmap(m.Instance(), (*C.GstMapInfo)(m.mapInfo.Instance()))
} }
// Bytes will return a byte slice of the data inside this memory if it is readable. // Bytes will return a byte slice of the data inside this memory if it is readable.
func (m *Memory) Bytes() []byte { func (m *Memory) Bytes() []byte {
mapInfo := m.Map() mapInfo := m.Map(MapRead)
if mapInfo.ptr == nil { if mapInfo == nil {
return nil return nil
} }
defer m.Unmap()
return mapInfo.Bytes() return mapInfo.Bytes()
} }
// MapInfo is a go representation of a GstMapInfo.
type MapInfo struct {
ptr *C.GstMapInfo
}
// Memory returns the underlying memory object.
func (m *MapInfo) Memory() *Memory {
return wrapMemory(m.ptr.memory)
}
// Data returns a pointer to the raw data inside this map.
func (m *MapInfo) Data() unsafe.Pointer {
return unsafe.Pointer(m.ptr.data)
}
// Flags returns the flags set on this map.
func (m *MapInfo) Flags() MapFlags {
return MapFlags(m.ptr.flags)
}
// Size returrns the size of this map.
func (m *MapInfo) Size() int64 {
return int64(m.ptr.size)
}
// MaxSize returns the maximum size of this map.
func (m *MapInfo) MaxSize() int64 {
return int64(m.ptr.maxsize)
}
// Bytes returns a byte slice of the data inside this map info.
func (m *MapInfo) Bytes() []byte {
return C.GoBytes(m.Data(), (C.int)(m.Size()))
}
// AsInt8Slice returns the contents of this map as a slice of signed 8-bit integers.
func (m *MapInfo) AsInt8Slice() []int8 {
uint8sl := m.AsUint8Slice()
out := make([]int8, m.Size())
for i := range out {
out[i] = int8(uint8sl[i])
}
return out
}
// AsInt16Slice returns the contents of this map as a slice of signed 16-bit integers.
func (m *MapInfo) AsInt16Slice() []int16 {
uint8sl := m.AsUint8Slice()
out := make([]int16, m.Size()/2)
for i := range out {
out[i] = int16(binary.LittleEndian.Uint16(uint8sl[i*2 : (i+1)*2]))
}
return out
}
// AsInt32Slice returns the contents of this map as a slice of signed 32-bit integers.
func (m *MapInfo) AsInt32Slice() []int32 {
uint8sl := m.AsUint8Slice()
out := make([]int32, m.Size()/4)
for i := range out {
out[i] = int32(binary.LittleEndian.Uint32(uint8sl[i*4 : (i+1)*4]))
}
return out
}
// AsInt64Slice returns the contents of this map as a slice of signed 64-bit integers.
func (m *MapInfo) AsInt64Slice() []int64 {
uint8sl := m.AsUint8Slice()
out := make([]int64, m.Size()/8)
for i := range out {
out[i] = int64(binary.LittleEndian.Uint64(uint8sl[i*8 : (i+1)*8]))
}
return out
}
// AsUint8Slice returns the contents of this map as a slice of unsigned 8-bit integers.
func (m *MapInfo) AsUint8Slice() []uint8 {
out := make([]uint8, m.Size())
for i, t := range (*[1 << 30]uint8)(m.Data())[:m.Size():m.Size()] {
out[i] = t
}
return out
}
// AsUint16Slice returns the contents of this map as a slice of unsigned 16-bit integers.
func (m *MapInfo) AsUint16Slice() []uint16 {
uint8sl := m.AsUint8Slice()
out := make([]uint16, m.Size()/2)
for i := range out {
out[i] = uint16(binary.LittleEndian.Uint16(uint8sl[i*2 : (i+1)*2]))
}
return out
}
// AsUint32Slice returns the contents of this map as a slice of unsigned 32-bit integers.
func (m *MapInfo) AsUint32Slice() []uint32 {
uint8sl := m.AsUint8Slice()
out := make([]uint32, m.Size()/4)
for i := range out {
out[i] = uint32(binary.LittleEndian.Uint32(uint8sl[i*4 : (i+1)*4]))
}
return out
}
// AsUint64Slice returns the contents of this map as a slice of unsigned 64-bit integers.
func (m *MapInfo) AsUint64Slice() []uint64 {
uint8sl := m.AsUint8Slice()
out := make([]uint64, m.Size()/8)
for i := range out {
out[i] = uint64(binary.LittleEndian.Uint64(uint8sl[i*8 : (i+1)*8]))
}
return out
}
// WriteData writes the given values directly to the map's memory.
func (m *MapInfo) WriteData(data []uint8) {
for i, x := range data {
C.writeMapData(m.ptr, C.gint(i), C.guint8(x))
}
}
func wrapMapInfo(mapInfo *C.GstMapInfo, unmapFunc func()) *MapInfo {
info := &MapInfo{ptr: mapInfo}
runtime.SetFinalizer(info, func(_ *MapInfo) { unmapFunc() })
return info
}

View File

@@ -169,6 +169,7 @@ func wrapEvent(ev *C.GstEvent) *Event { return &Event{ptr: e
func wrapGhostPad(obj *glib.Object) *GhostPad { return &GhostPad{wrapProxyPad(obj)} } func wrapGhostPad(obj *glib.Object) *GhostPad { return &GhostPad{wrapProxyPad(obj)} }
func wrapMainContext(ctx *C.GMainContext) *MainContext { return &MainContext{ptr: ctx} } func wrapMainContext(ctx *C.GMainContext) *MainContext { return &MainContext{ptr: ctx} }
func wrapMainLoop(loop *C.GMainLoop) *MainLoop { return &MainLoop{ptr: loop} } func wrapMainLoop(loop *C.GMainLoop) *MainLoop { return &MainLoop{ptr: loop} }
func wrapMapInfo(mapInfo *C.GstMapInfo) *MapInfo { return &MapInfo{ptr: mapInfo} }
func wrapMemory(mem *C.GstMemory) *Memory { return &Memory{ptr: mem} } func wrapMemory(mem *C.GstMemory) *Memory { return &Memory{ptr: mem} }
func wrapMessage(msg *C.GstMessage) *Message { return &Message{msg: msg} } func wrapMessage(msg *C.GstMessage) *Message { return &Message{msg: msg} }
func wrapMeta(meta *C.GstMeta) *Meta { return &Meta{ptr: meta} } func wrapMeta(meta *C.GstMeta) *Meta { return &Meta{ptr: meta} }

BIN
main Executable file

Binary file not shown.