From 65a0b5fdc609dac739f6ad1681f845b78589777b Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 12 Dec 2012 00:43:36 +0100 Subject: mp_image: refcounting helpers --- video/mp_image.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++----- video/mp_image.h | 52 ++++++++++-- 2 files changed, 265 insertions(+), 25 deletions(-) (limited to 'video') diff --git a/video/mp_image.c b/video/mp_image.c index e4e862208b..1de8abaed5 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -21,18 +21,83 @@ #include #include #include +#include #include "talloc.h" #include "video/img_format.h" #include "video/mp_image.h" #include "video/sws_utils.h" +#include "video/filter/vf.h" #include "video/memcpy_pic.h" #include "libavutil/mem.h" #include "libavutil/common.h" +struct m_refcount { + void *arg; + // free() is called if refcount reaches 0. + void (*free)(void *arg); + // External refcounted object (such as libavcodec DR buffers). This assumes + // that the actual data is managed by the external object, not by + // m_refcount. The .ext_* calls use that external object's refcount + // primitives. It usually doesn't make sense to set both .free and .ext_*. + void (*ext_ref)(void *arg); + void (*ext_unref)(void *arg); + bool (*ext_is_unique)(void *arg); + // Native refcount (there may be additional references if .ext_* are set) + int refcount; +}; + +// Only for checking API usage +static int m_refcount_destructor(void *ptr) +{ + struct m_refcount *ref = ptr; + assert(ref->refcount == 0); + return 0; +} + +// Starts out with refcount==1, caller can set .arg and .free and .ext_* +static struct m_refcount *m_refcount_new(void) +{ + struct m_refcount *ref = talloc_ptrtype(NULL, ref); + *ref = (struct m_refcount) { .refcount = 1 }; + talloc_set_destructor(ref, m_refcount_destructor); + return ref; +} + +static void m_refcount_ref(struct m_refcount *ref) +{ + ref->refcount++; + if (ref->ext_ref) + ref->ext_ref(ref->arg); +} + +static void m_refcount_unref(struct m_refcount *ref) +{ + assert(ref->refcount > 0); + if (ref->ext_unref) + ref->ext_unref(ref->arg); + ref->refcount--; + if (ref->refcount == 0) { + if (ref->free) + ref->free(ref->arg); + talloc_free(ref); + } +} + +static bool m_refcount_is_unique(struct m_refcount *ref) +{ + if (ref->refcount > 1) + return false; + if (ref->ext_is_unique) + return ref->ext_is_unique(ref->arg); // referenced only by us + return true; +} + + void mp_image_alloc_planes(mp_image_t *mpi) { + assert(!mpi->refcount); // IF09 - allocate space for 4. plane delta info - unused if (mpi->imgfmt == IMGFMT_IF09) { mpi->planes[0]=av_malloc(mpi->bpp*mpi->width*(mpi->height+2)/8+ @@ -75,19 +140,8 @@ void mp_image_alloc_planes(mp_image_t *mpi) { mpi->flags|=MP_IMGFLAG_ALLOCATED; } -mp_image_t* alloc_mpi(int w, int h, unsigned long int fmt) { - mp_image_t* mpi = new_mp_image(w,h); - - mpi->width=FFALIGN(w, MP_STRIDE_ALIGNMENT); - mp_image_setfmt(mpi,fmt); - mp_image_alloc_planes(mpi); - mpi->width=w; - mp_image_setfmt(mpi,fmt); // reset chroma size - - return mpi; -} - -void copy_mpi(mp_image_t *dmpi, mp_image_t *mpi) { +void mp_image_copy(struct mp_image *dmpi, struct mp_image *mpi) +{ if(mpi->flags&MP_IMGFLAG_PLANAR){ memcpy_pic(dmpi->planes[0],mpi->planes[0], MP_IMAGE_BYTES_PER_ROW_ON_PLANE(mpi, 0), mpi->h, dmpi->stride[0],mpi->stride[0]); @@ -102,6 +156,11 @@ void copy_mpi(mp_image_t *dmpi, mp_image_t *mpi) { } } +void mp_image_copy_attributes(struct mp_image *dmpi, struct mp_image *mpi) +{ + vf_clone_mpi_attributes(dmpi, mpi); +} + void mp_image_setfmt(mp_image_t* mpi,unsigned int out_fmt){ mpi->flags&=~(MP_IMGFLAG_PLANAR|MP_IMGFLAG_YUV|MP_IMGFLAG_SWAPPED); mpi->imgfmt=out_fmt; @@ -231,7 +290,11 @@ static int mp_image_destructor(void *ptr) { mp_image_t *mpi = ptr; - if(mpi->flags&MP_IMGFLAG_ALLOCATED){ + if (mpi->refcount) { + m_refcount_unref(mpi->refcount); + } + + if (mpi->flags & MP_IMGFLAG_ALLOCATED) { /* because we allocate the whole image at once */ av_free(mpi->planes[0]); if (mpi->flags & MP_IMGFLAG_RGB_PALETTE) @@ -241,16 +304,155 @@ static int mp_image_destructor(void *ptr) return 0; } -mp_image_t* new_mp_image(int w,int h){ - mp_image_t* mpi = talloc_zero(NULL, mp_image_t); +// Image without format or allocated image data +struct mp_image *mp_image_new_empty(int w, int h) +{ + struct mp_image *mpi = talloc_zero(NULL, struct mp_image); talloc_set_destructor(mpi, mp_image_destructor); mpi->width=mpi->w=w; mpi->height=mpi->h=h; return mpi; } -void free_mp_image(mp_image_t* mpi){ - talloc_free(mpi); +struct mp_image *mp_image_alloc(unsigned int imgfmt, int w, int h) +{ + struct mp_image *mpi = mp_image_new_empty(w, h); + + mpi->width = FFALIGN(w, MP_STRIDE_ALIGNMENT); + mp_image_setfmt(mpi, imgfmt); + mp_image_alloc_planes(mpi); + mpi->width = w; + mp_image_setfmt(mpi, imgfmt); // reset chroma size + + mpi->flags &= ~MP_IMGFLAG_ALLOCATED; + mpi->refcount = m_refcount_new(); + mpi->refcount->free = av_free; + mpi->refcount->arg = mpi->planes[0]; + // NOTE: palette isn't free'd. Palette handling should be fixed instead. + + return mpi; +} + +struct mp_image *mp_image_new_copy(struct mp_image *img) +{ + struct mp_image *new = mp_image_alloc(img->imgfmt, img->w, img->h); + mp_image_copy(new, img); + mp_image_copy_attributes(new, img); + + // Normally these are covered by the reference to the original image data + // (like the AVFrame in vd_lavc.c), but we can't manage it on our own. + new->qscale = NULL; + new->qstride = 0; + + return new; +} + +// Make dst take over the image data of src, and free src. +// This is basically a safe version of *dst = *src; free(src); +// Only works with ref-counted images, and can't change image size/format. +void mp_image_steal_data(struct mp_image *dst, struct mp_image *src) +{ + assert(dst->imgfmt == src->imgfmt && dst->w == src->w && dst->h == src->h); + assert(dst->refcount && src->refcount); + + for (int p = 0; p < MP_MAX_PLANES; p++) { + dst->planes[p] = src->planes[p]; + dst->stride[p] = src->stride[p]; + } + mp_image_copy_attributes(dst, src); + + m_refcount_unref(dst->refcount); + dst->refcount = src->refcount; + talloc_set_destructor(src, NULL); + talloc_free(src); +} + +// Return a new reference to img. The returned reference is owned by the caller, +// while img is left untouched. +struct mp_image *mp_image_new_ref(struct mp_image *img) +{ + if (!img->refcount) + return mp_image_new_copy(img); + + struct mp_image *new = talloc_ptrtype(NULL, new); + talloc_set_destructor(new, mp_image_destructor); + *new = *img; + + m_refcount_ref(new->refcount); + return new; +} + +// Return a reference counted reference to img. If the reference count reaches +// 0, call free(free_arg). The data passed by img must not be free'd before +// that. The new reference will be writeable. +struct mp_image *mp_image_new_custom_ref(struct mp_image *img, void *free_arg, + void (*free)(void *arg)) +{ + struct mp_image *new = talloc_ptrtype(NULL, new); + talloc_set_destructor(new, mp_image_destructor); + *new = *img; + + new->flags &= ~MP_IMGFLAG_ALLOCATED; + new->refcount = m_refcount_new(); + new->refcount->free = free; + new->refcount->arg = free_arg; + return new; +} + +// Return a reference counted reference to img. ref/unref/is_unique are used to +// connect to an external refcounting API. It is assumed that the new object +// has an initial reference to that external API. +struct mp_image *mp_image_new_external_ref(struct mp_image *img, void *arg, + void (*ref)(void *arg), + void (*unref)(void *arg), + bool (*is_unique)(void *arg)) +{ + struct mp_image *new = talloc_ptrtype(NULL, new); + talloc_set_destructor(new, mp_image_destructor); + *new = *img; + + new->flags &= ~MP_IMGFLAG_ALLOCATED; + new->refcount = m_refcount_new(); + new->refcount->ext_ref = ref; + new->refcount->ext_unref = unref; + new->refcount->ext_is_unique = is_unique; + new->refcount->arg = arg; + return new; +} + +bool mp_image_is_writeable(struct mp_image *img) +{ + // if non ref-counted, it's writeable if the caller allocated the image + if (!img->refcount) + return img->flags & MP_IMGFLAG_ALLOCATED; + return m_refcount_is_unique(img->refcount); +} + +// Make the image data referenced by img writeable. This allocates new data +// if the data wasn't already writeable, and img->planes[] and img->stride[] +// will be set to the copy. +void mp_image_make_writeable(struct mp_image *img) +{ + if (mp_image_is_writeable(img)) + return; + + mp_image_steal_data(img, mp_image_new_copy(img)); + assert(mp_image_is_writeable(img)); +} + +void mp_image_setrefp(struct mp_image **p_img, struct mp_image *new_value) +{ + if (*p_img != new_value) { + talloc_free(*p_img); + *p_img = new_value ? mp_image_new_ref(new_value) : NULL; + } +} + +// Mere helper function (mp_image can be directly free'd with talloc_free) +void mp_image_unrefp(struct mp_image **p_img) +{ + talloc_free(*p_img); + *p_img = NULL; } enum mp_csp mp_image_csp(struct mp_image *img) diff --git a/video/mp_image.h b/video/mp_image.h index 69e9c87c19..c57d0a63e9 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -19,6 +19,7 @@ #ifndef MPLAYER_MP_IMAGE_H #define MPLAYER_MP_IMAGE_H +#include #include #include #include @@ -104,10 +105,25 @@ #define MP_IMGFIELD_BOTTOM 0x10 #define MP_IMGFIELD_INTERLACED 0x20 +/* Memory management: + * - mp_image is a light-weight reference to the actual image data (pixels). + * The actual image data is reference counted and can outlive mp_image + * allocations. mp_image references can be created with mp_image_new_ref() + * and free'd with talloc_free() (the helpers mp_image_setrefp() and + * mp_image_unrefp() can also be used). The actual image data is free'd when + * the last mp_image reference to it is free'd. + * - Each mp_image has a clear owner. The owner can do anything with it, such + * as changing mp_image fields. Instead of making ownership ambiguous by + * sharing a mp_image reference, new references should be created. + * - Write access to the actual image data is allowed only after calling + * mp_image_make_writeable(), or if mp_image_is_writeable() returns true. + * Conceptually, images can be changed by their owner only, and copy-on-write + * is used to ensure that other references do not see any changes to the + * image data. mp_image_make_writeable() will do that copy if required. + */ typedef struct mp_image { unsigned int flags; unsigned char type; - int number; unsigned char bpp; // bits/pixel. NOT depth! for RGB it will be n*8 unsigned int imgfmt; int width,height; // internal to vf.c, do not use (stored dimensions) @@ -128,18 +144,40 @@ typedef struct mp_image { int chroma_y_shift; // vertical enum mp_csp colorspace; enum mp_csp_levels levels; - int usage_count; + /* memory management */ + int number, usage_count; // used by old VF/DR and vdpau code only + struct m_refcount *refcount; /* for private use by filter or vo driver (to store buffer id or dmpi) */ void* priv; } mp_image_t; +#define alloc_mpi(w, h, fmt) mp_image_alloc(fmt, w, h) +#define free_mp_image talloc_free +#define new_mp_image mp_image_new_empty +#define copy_mpi mp_image_copy + +struct mp_image *mp_image_alloc(unsigned int fmt, int w, int h); +void mp_image_copy(struct mp_image *dmpi, struct mp_image *mpi); +void mp_image_copy_attributes(struct mp_image *dmpi, struct mp_image *mpi); +struct mp_image *mp_image_new_copy(struct mp_image *img); +struct mp_image *mp_image_new_ref(struct mp_image *img); +bool mp_image_is_writeable(struct mp_image *img); +void mp_image_make_writeable(struct mp_image *img); +void mp_image_setrefp(struct mp_image **p_img, struct mp_image *new_value); +void mp_image_unrefp(struct mp_image **p_img); + +struct mp_image *mp_image_new_empty(int w, int h); void mp_image_setfmt(mp_image_t* mpi,unsigned int out_fmt); -mp_image_t* new_mp_image(int w,int h); -void free_mp_image(mp_image_t* mpi); +void mp_image_alloc_planes(struct mp_image *mpi); +void mp_image_steal_data(struct mp_image *dst, struct mp_image *src); + +struct mp_image *mp_image_new_custom_ref(struct mp_image *img, void *arg, + void (*free)(void *arg)); -mp_image_t* alloc_mpi(int w, int h, unsigned long int fmt); -void mp_image_alloc_planes(mp_image_t *mpi); -void copy_mpi(mp_image_t *dmpi, mp_image_t *mpi); +struct mp_image *mp_image_new_external_ref(struct mp_image *img, void *arg, + void (*ref)(void *arg), + void (*unref)(void *arg), + bool (*is_unique)(void *arg)); enum mp_csp mp_image_csp(struct mp_image *img); enum mp_csp_levels mp_image_levels(struct mp_image *img); -- cgit v1.2.3