[FFmpeg-devel] avutil/buffer: add a dynamic size buffer pool API

Submitted by James Almer on March 28, 2018, 2:11 a.m.

Details

Message ID 20180328021127.7296-1-jamrial@gmail.com
State New
Headers show

Commit Message

James Almer March 28, 2018, 2:11 a.m.
Signed-off-by: James Almer <jamrial@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(+)

Comments

wm4 March 28, 2018, 9:59 a.m.
On Tue, 27 Mar 2018 23:11:27 -0300
James Almer <jamrial@gmail.com> wrote:

> Signed-off-by: James Almer <jamrial@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));

No custom free pool free function like the fixed buffer API? (Not sure if neded.)
> +
> +/**
> + * 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 */
James Almer March 28, 2018, 1:42 p.m.
On 3/28/2018 6:59 AM, wm4 wrote:
> On Tue, 27 Mar 2018 23:11:27 -0300
> James Almer <jamrial@gmail.com> wrote:
> 
>> Signed-off-by: James Almer <jamrial@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));
> 
> No custom free pool free function like the fixed buffer API? (Not sure if neded.)

That's only in the second, more "complex" kind of pool alloc, when you
need to pass an opaque pointer to both the alloc and free buffer
functions. It was added afaik for some hwaccel stuff, like vaapi encoder.

It can be added later if needed, as another init2() for this new API,
but I'd rather not make it the default and only init function here as it
wouldn't allow the caller to use the standard signature functions like
av_buffer_alloc() as callback, forcing them to come up with custom
wrappers even if they need a simple pool.

>> +
>> +/**
>> + * 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 */
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
James Almer April 6, 2018, 3:05 p.m.
On 3/28/2018 10:42 AM, James Almer wrote:
> On 3/28/2018 6:59 AM, wm4 wrote:
>> On Tue, 27 Mar 2018 23:11:27 -0300
>> James Almer <jamrial@gmail.com> wrote:
>>
>>> 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));
>>
>> No custom free pool free function like the fixed buffer API? (Not sure if neded.)
> 
> That's only in the second, more "complex" kind of pool alloc, when you
> need to pass an opaque pointer to both the alloc and free buffer
> functions. It was added afaik for some hwaccel stuff, like vaapi encoder.
> 
> It can be added later if needed, as another init2() for this new API,
> but I'd rather not make it the default and only init function here as it
> wouldn't allow the caller to use the standard signature functions like
> av_buffer_alloc() as callback, forcing them to come up with custom
> wrappers even if they need a simple pool.

If there are no other comments, I'd like to push this soon.

Patch hide | download patch | download mbox

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 */