gstreamer/subprojects/gst-plugins-bad/sys/v4l2codecs/gstv4l2codecallocator.c
Benjamin Gaignard 2753a8f0c1 v4l2codecs: Replace VIDIOC_REQBUFS calls by VIDIOC_CREATE_BUFS
Use VIDIOC_CREATE_BUFS ioctl to create buffers instead of VIDIOC_REQBUFS
because it allows to create buffers also while streaming.
To prepare the introduction of VIDIOC_REMOVE_BUFFERS create
the buffers one per one instead of a range of them. This way
it can, in the futur, fill the holes.
gst_v4l2_decoder_request_buffers() is stil used to remove all
the buffers of the queue.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7684>
2024-10-19 01:39:04 +00:00

381 lines
9.4 KiB
C

/* GStreamer
* Copyright (C) 2020 Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "gstv4l2codecallocator.h"
#include <gst/video/video.h>
#include <sys/types.h>
#define GST_CAT_DEFAULT allocator_debug
GST_DEBUG_CATEGORY_STATIC (allocator_debug);
typedef struct _GstV4l2CodecBuffer GstV4l2CodecBuffer;
struct _GstV4l2CodecBuffer
{
gint index;
GstMemory *mem[GST_VIDEO_MAX_PLANES];
guint num_mems;
guint outstanding_mems;
};
struct _GstV4l2CodecAllocator
{
GstDmaBufAllocator parent;
GQueue pool;
gint pool_size;
gboolean detached;
GCond buffer_cond;
gboolean flushing;
GstV4l2Decoder *decoder;
GstPadDirection direction;
};
G_DEFINE_TYPE_WITH_CODE (GstV4l2CodecAllocator, gst_v4l2_codec_allocator,
GST_TYPE_DMABUF_ALLOCATOR,
GST_DEBUG_CATEGORY_INIT (allocator_debug, "v4l2codecs-allocator", 0,
"V4L2 Codecs Allocator"));
static gboolean gst_v4l2_codec_allocator_release (GstMiniObject * mini_object);
static GQuark
gst_v4l2_codec_buffer_quark (void)
{
static gsize buffer_quark = 0;
if (g_once_init_enter (&buffer_quark)) {
GQuark quark = g_quark_from_string ("GstV4l2CodecBuffer");
g_once_init_leave (&buffer_quark, quark);
}
return buffer_quark;
}
static GstV4l2CodecBuffer *
gst_v4l2_codec_buffer_new (GstAllocator * allocator, GstV4l2Decoder * decoder,
GstPadDirection direction, gint index)
{
GstV4l2CodecBuffer *buf;
guint i, num_mems;
gint fds[GST_VIDEO_MAX_PLANES];
gsize sizes[GST_VIDEO_MAX_PLANES];
gsize offsets[GST_VIDEO_MAX_PLANES];
if (!gst_v4l2_decoder_export_buffer (decoder, direction, index, fds, sizes,
offsets, &num_mems))
return NULL;
buf = g_new0 (GstV4l2CodecBuffer, 1);
buf->index = index;
buf->num_mems = num_mems;
for (i = 0; i < buf->num_mems; i++) {
GstMemory *mem = gst_fd_allocator_alloc (allocator, fds[i], sizes[i],
GST_FD_MEMORY_FLAG_KEEP_MAPPED);
gst_memory_resize (mem, offsets[i], sizes[i] - offsets[i]);
GST_MINI_OBJECT (mem)->dispose = gst_v4l2_codec_allocator_release;
gst_mini_object_set_qdata (GST_MINI_OBJECT (mem),
gst_v4l2_codec_buffer_quark (), buf, NULL);
/* On outstanding memory keeps a reference on the allocator, this is
* needed to break the cycle. */
gst_object_unref (mem->allocator);
buf->mem[i] = mem;
}
GST_DEBUG_OBJECT (allocator, "Create buffer %i with %i memory fds",
buf->index, buf->num_mems);
return buf;
}
static void
gst_v4l2_codec_buffer_free (GstV4l2CodecBuffer * buf)
{
guint i;
g_warn_if_fail (buf->outstanding_mems == 0);
GST_DEBUG_OBJECT (buf->mem[0]->allocator, "Freeing buffer %i", buf->index);
for (i = 0; i < buf->num_mems; i++) {
GstMemory *mem = buf->mem[i];
GST_MINI_OBJECT (mem)->dispose = NULL;
g_object_ref (mem->allocator);
gst_memory_unref (mem);
}
g_free (buf);
}
static void
gst_v4l2_codec_buffer_acquire (GstV4l2CodecBuffer * buf)
{
buf->outstanding_mems += buf->num_mems;
}
static gboolean
gst_v4l2_codec_buffer_release_mem (GstV4l2CodecBuffer * buf)
{
return (--buf->outstanding_mems == 0);
}
static gboolean
gst_v4l2_codec_allocator_release (GstMiniObject * mini_object)
{
GstMemory *mem = GST_MEMORY_CAST (mini_object);
GstV4l2CodecAllocator *self = GST_V4L2_CODEC_ALLOCATOR (mem->allocator);
GstV4l2CodecBuffer *buf;
GST_OBJECT_LOCK (self);
buf = gst_mini_object_get_qdata (mini_object, gst_v4l2_codec_buffer_quark ());
gst_memory_ref (mem);
if (gst_v4l2_codec_buffer_release_mem (buf)) {
GST_DEBUG_OBJECT (self, "Placing back buffer %i into pool", buf->index);
g_queue_push_tail (&self->pool, buf);
g_cond_signal (&self->buffer_cond);
}
GST_OBJECT_UNLOCK (self);
/* Keep last in case we are holding on the last allocator ref */
g_object_unref (mem->allocator);
/* Returns FALSE so that our mini object isn't freed */
return FALSE;
}
static gboolean
gst_v4l2_codec_allocator_prepare (GstV4l2CodecAllocator * self)
{
GstV4l2Decoder *decoder = self->decoder;
GstPadDirection direction = self->direction;
guint i;
GST_DEBUG_OBJECT (self, "Try to create %d buffers", self->pool_size);
/* Allocate buffers one by one to avoid fragmentation issue and
* use the possible holes in v4l2 queue array */
for (i = 0; i < self->pool_size; i++) {
GstV4l2CodecBuffer *buf;
gint index = gst_v4l2_decoder_create_buffers (decoder, direction, 1);
if (index < 0) {
GST_ERROR_OBJECT (self,
"%i buffer was needed, but only %i could be allocated",
self->pool_size, i);
goto failed;
}
buf =
gst_v4l2_codec_buffer_new (GST_ALLOCATOR (self), decoder, direction,
index);
g_queue_push_tail (&self->pool, buf);
}
return TRUE;
failed:
gst_v4l2_decoder_request_buffers (decoder, direction, 0);
return FALSE;
}
static void
gst_v4l2_codec_allocator_init (GstV4l2CodecAllocator * self)
{
g_cond_init (&self->buffer_cond);
}
static void
gst_v4l2_codec_allocator_dispose (GObject * object)
{
GstV4l2CodecAllocator *self = GST_V4L2_CODEC_ALLOCATOR (object);
GstV4l2CodecBuffer *buf;
while ((buf = g_queue_pop_head (&self->pool)))
gst_v4l2_codec_buffer_free (buf);
if (self->decoder) {
gst_v4l2_codec_allocator_detach (self);
gst_clear_object (&self->decoder);
}
G_OBJECT_CLASS (gst_v4l2_codec_allocator_parent_class)->dispose (object);
}
static void
gst_v4l2_codec_allocator_finalize (GObject * object)
{
GstV4l2CodecAllocator *self = GST_V4L2_CODEC_ALLOCATOR (object);
g_cond_clear (&self->buffer_cond);
G_OBJECT_CLASS (gst_v4l2_codec_allocator_parent_class)->finalize (object);
}
static void
gst_v4l2_codec_allocator_class_init (GstV4l2CodecAllocatorClass * klass)
{
GstAllocatorClass *allocator_class = GST_ALLOCATOR_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gst_v4l2_codec_allocator_dispose;
object_class->finalize = gst_v4l2_codec_allocator_finalize;
allocator_class->alloc = NULL;
}
GstV4l2CodecAllocator *
gst_v4l2_codec_allocator_new (GstV4l2Decoder * decoder,
GstPadDirection direction, guint num_buffers)
{
GstV4l2CodecAllocator *self =
g_object_new (GST_TYPE_V4L2_CODEC_ALLOCATOR, NULL);
self->decoder = g_object_ref (decoder);
self->direction = direction;
self->pool_size = num_buffers;
if (!gst_v4l2_codec_allocator_prepare (self)) {
g_object_unref (self);
return NULL;
}
return self;
}
GstMemory *
gst_v4l2_codec_allocator_alloc (GstV4l2CodecAllocator * self)
{
GstV4l2CodecBuffer *buf;
GstMemory *mem = NULL;
GST_OBJECT_LOCK (self);
buf = g_queue_pop_head (&self->pool);
if (buf) {
GST_DEBUG_OBJECT (self, "Allocated buffer %i", buf->index);
g_warn_if_fail (buf->num_mems == 1);
mem = buf->mem[0];
g_object_ref (mem->allocator);
buf->outstanding_mems++;
}
GST_OBJECT_UNLOCK (self);
return mem;
}
gboolean
gst_v4l2_codec_allocator_create_buffer (GstV4l2CodecAllocator * self)
{
/* TODO implement */
return FALSE;
}
gboolean
gst_v4l2_codec_allocator_wait_for_buffer (GstV4l2CodecAllocator * self)
{
gboolean ret;
GST_OBJECT_LOCK (self);
while (self->pool.length == 0 && !self->flushing)
g_cond_wait (&self->buffer_cond, GST_OBJECT_GET_LOCK (self));
ret = !self->flushing;
GST_OBJECT_UNLOCK (self);
return ret;
}
gboolean
gst_v4l2_codec_allocator_prepare_buffer (GstV4l2CodecAllocator * self,
GstBuffer * gstbuf)
{
GstV4l2CodecBuffer *buf;
guint i;
GST_OBJECT_LOCK (self);
buf = g_queue_pop_head (&self->pool);
if (!buf) {
GST_OBJECT_UNLOCK (self);
return FALSE;
}
GST_DEBUG_OBJECT (self, "Allocated buffer %i", buf->index);
gst_v4l2_codec_buffer_acquire (buf);
for (i = 0; i < buf->num_mems; i++) {
gst_buffer_append_memory (gstbuf, buf->mem[i]);
g_object_ref (buf->mem[i]->allocator);
}
GST_OBJECT_UNLOCK (self);
return TRUE;
}
guint
gst_v4l2_codec_allocator_get_pool_size (GstV4l2CodecAllocator * self)
{
guint size;
GST_OBJECT_LOCK (self);
size = self->pool_size;
GST_OBJECT_UNLOCK (self);
return size;
}
void
gst_v4l2_codec_allocator_detach (GstV4l2CodecAllocator * self)
{
GST_OBJECT_LOCK (self);
if (!self->detached) {
self->detached = TRUE;
gst_v4l2_decoder_request_buffers (self->decoder, self->direction, 0);
}
GST_OBJECT_UNLOCK (self);
}
void
gst_v4l2_codec_allocator_set_flushing (GstV4l2CodecAllocator * self,
gboolean flushing)
{
GST_OBJECT_LOCK (self);
self->flushing = flushing;
if (flushing)
g_cond_broadcast (&self->buffer_cond);
GST_OBJECT_UNLOCK (self);
}
guint32
gst_v4l2_codec_memory_get_index (GstMemory * mem)
{
GstV4l2CodecBuffer *buf;
buf = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
gst_v4l2_codec_buffer_quark ());
g_return_val_if_fail (buf, G_MAXUINT32);
return buf->index;
}