Signed-off-by: James Almer <jamr...@gmail.com> --- Implemented as a completely separate API as suggested. Missing Changelog, APIChanges and version bump as usual.
libavutil/buffer.c | 159 ++++++++++++++++++++++++++++++++++++++++++++ libavutil/buffer.h | 53 +++++++++++++++ libavutil/buffer_internal.h | 14 ++++ 3 files changed, 226 insertions(+) diff --git a/libavutil/buffer.c b/libavutil/buffer.c index 8d1aa5fa84..c39a14c3c7 100644 --- a/libavutil/buffer.c +++ b/libavutil/buffer.c @@ -24,6 +24,7 @@ #include "common.h" #include "mem.h" #include "thread.h" +#include "tree.h" AVBufferRef *av_buffer_create(uint8_t *data, int size, void (*free)(void *opaque, uint8_t *data), @@ -355,3 +356,161 @@ AVBufferRef *av_buffer_pool_get(AVBufferPool *pool) return ret; } + +AVBufferDynPool *av_buffer_dyn_pool_init(AVBufferRef* (*alloc)(int size)) +{ + AVBufferDynPool *pool = av_mallocz(sizeof(*pool)); + if (!pool) + return NULL; + + ff_mutex_init(&pool->mutex, NULL); + + pool->alloc = alloc ? alloc : av_buffer_alloc; + + atomic_init(&pool->refcount, 1); + + return pool; +} + +static int free_node(void *opaque, void *elem) +{ + BufferPoolEntry *buf = elem; + + buf->free(buf->opaque, buf->data); + av_free(buf); + + return 0; +} + +static void buffer_dyn_pool_free(AVBufferDynPool *pool) +{ + av_tree_enumerate(pool->root, NULL, NULL, free_node); + av_tree_destroy(pool->root); + + ff_mutex_destroy(&pool->mutex); + + av_freep(&pool); +} + +void av_buffer_dyn_pool_uninit(AVBufferDynPool **ppool) +{ + AVBufferDynPool *pool; + + if (!ppool || !*ppool) + return; + pool = *ppool; + *ppool = NULL; + + if (atomic_fetch_add_explicit(&pool->refcount, -1, memory_order_acq_rel) == 1) + buffer_dyn_pool_free(pool); +} + +static int cmp_insert(const void *key, const void *node) +{ + int ret = ((const BufferPoolEntry *) key)->size - ((const BufferPoolEntry *) node)->size; + + if (!ret) + ret = ((const BufferPoolEntry *) key)->data - ((const BufferPoolEntry *) node)->data; + return ret; +} + +static void pool_release_dyn_buffer(void *opaque, uint8_t *data) +{ + BufferPoolEntry *buf = opaque; + AVBufferDynPool *pool = buf->dynpool; + + if(CONFIG_MEMORY_POISONING) + memset(buf->data, FF_MEMORY_POISON, buf->size); + + ff_mutex_lock(&pool->mutex); + /* Add the buffer into the pool, using the preallocated + * AVTreeNode stored in buf->node */ + av_tree_insert(&pool->root, buf, cmp_insert, &buf->node); + ff_mutex_unlock(&pool->mutex); + + if (atomic_fetch_add_explicit(&pool->refcount, -1, memory_order_acq_rel) == 1) + buffer_dyn_pool_free(pool); +} + +static AVBufferRef *pool_alloc_dyn_buffer(AVBufferDynPool *pool, int size) +{ + BufferPoolEntry *buf; + AVBufferRef *ret; + + ret = pool->alloc(size); + if (!ret) + return NULL; + + buf = av_mallocz(sizeof(*buf)); + if (!buf) { + av_buffer_unref(&ret); + return NULL; + } + + buf->node = av_tree_node_alloc(); + if (!buf->node) { + av_free(buf); + av_buffer_unref(&ret); + return NULL; + } + + buf->data = ret->buffer->data; + buf->opaque = ret->buffer->opaque; + buf->free = ret->buffer->free; + buf->size = size; + buf->dynpool = pool; + + ret->buffer->opaque = buf; + ret->buffer->free = pool_release_dyn_buffer; + + return ret; +} + +static int cmp_find(const void *key, const void *node) +{ + return *(const int *)key - ((const BufferPoolEntry *) node)->size; +} + +AVBufferRef *av_buffer_dyn_pool_get(AVBufferDynPool *pool, int size) +{ + AVBufferRef *ret; + BufferPoolEntry *buf, *next[2] = { NULL, NULL }; + + ff_mutex_lock(&pool->mutex); + /* Find a big enough buffer in the pool. */ + buf = av_tree_find(pool->root, &size, cmp_find, (void **)next); + + if (!buf) + /* If none of the requested size exists, use a bigger one. */ + buf = next[1]; + if (!buf && (buf = next[0])) { + /* If the pool also doesn't have a bigger buffer, but does + * have a smaller one, then replace it with a new buffer of + * the requested size. */ + av_tree_insert(&pool->root, buf, cmp_insert, &buf->node); + buf->free(buf->opaque, buf->data); + av_free(buf->node); + av_freep(&buf); + } + + if (buf) { + ret = av_buffer_create(buf->data, buf->size, pool_release_dyn_buffer, + buf, 0); + if (ret) { + /* Remove the buffer from the pool. Zero and store the + * AVTreeNode used for it in buf->node so we can use it + * again once the buffer is put back in the pool. */ + av_tree_insert(&pool->root, buf, cmp_insert, &buf->node); + memset(buf->node, 0, av_tree_node_size); + ret->size = size; + } + } else { + ret = pool_alloc_dyn_buffer(pool, size); + } + ff_mutex_unlock(&pool->mutex); + + if (ret) + atomic_fetch_add_explicit(&pool->refcount, 1, memory_order_relaxed); + + return ret; +} diff --git a/libavutil/buffer.h b/libavutil/buffer.h index 73b6bd0b14..d06b301fe5 100644 --- a/libavutil/buffer.h +++ b/libavutil/buffer.h @@ -284,6 +284,59 @@ void av_buffer_pool_uninit(AVBufferPool **pool); */ AVBufferRef *av_buffer_pool_get(AVBufferPool *pool); +/** + * @} + */ + +/** + * @defgroup lavu_bufferdynpool AVBufferDynPool + * @ingroup lavu_data + * + * @{ + * AVBufferDynPool is an API for a lock-free thread-safe pool of AVBuffers. + * + * Unlike AVBufferPool, AVBufferDynPool allows the user to request buffers + * of any arbitrary size. It is functionally the same otherwise. + */ + +/** + * The buffer pool. This structure is opaque and not meant to be accessed + * directly. It is allocated with av_buffer_dyn_pool_init() and freed with + * av_buffer_dyn_pool_uninit(). + */ +typedef struct AVBufferDynPool AVBufferDynPool; + +/** + * Allocate and initialize a buffer pool. + * + * @param alloc a function that will be used to allocate new buffers when the + * pool is empty. May be NULL, then the default allocator will be used + * (av_buffer_alloc()). + * @return newly created buffer pool on success, NULL on error. + */ +AVBufferDynPool *av_buffer_dyn_pool_init(AVBufferRef* (*alloc)(int size)); + +/** + * Mark the pool as being available for freeing. It will actually be freed only + * once all the allocated buffers associated with the pool are released. Thus it + * is safe to call this function while some of the allocated buffers are still + * in use. + * + * @param pool pointer to the pool to be freed. It will be set to NULL. + */ +void av_buffer_dyn_pool_uninit(AVBufferDynPool **pool); + +/** + * Allocate a new AVBuffer, reusing an old buffer from the pool when available. + * This function may be called simultaneously from multiple threads. + * + * @param pool pointer to an initialized pool. + * @param size Required buffer size in bytes. + * + * @return a reference to the new buffer on success, NULL on error. + */ +AVBufferRef *av_buffer_dyn_pool_get(AVBufferDynPool *pool, int size); + /** * @} */ diff --git a/libavutil/buffer_internal.h b/libavutil/buffer_internal.h index 54b67047e5..2c0e9ea063 100644 --- a/libavutil/buffer_internal.h +++ b/libavutil/buffer_internal.h @@ -61,6 +61,7 @@ struct AVBuffer { typedef struct BufferPoolEntry { uint8_t *data; + size_t size; /* * Backups of the original opaque/free of the AVBuffer corresponding to @@ -71,6 +72,9 @@ typedef struct BufferPoolEntry { AVBufferPool *pool; struct BufferPoolEntry *next; + + AVBufferDynPool *dynpool; + struct AVTreeNode *node; } BufferPoolEntry; struct AVBufferPool { @@ -95,4 +99,14 @@ struct AVBufferPool { void (*pool_free)(void *opaque); }; +struct AVBufferDynPool { + AVMutex mutex; + struct AVTreeNode *root; + + atomic_uint refcount; + + int size; + AVBufferRef* (*alloc)(int size); +}; + #endif /* AVUTIL_BUFFER_INTERNAL_H */ -- 2.16.2 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel