mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-10 16:32:36 +00:00
v4l2: Improve buffer management
Query the amount of available buffers when doing set_config(). This allows us to configure the parent bufferpool with the number of buffers to preallocate. Keep track of the provided allocator and use it when we need to allocate a buffer in RW mode. When we are can not allocate the requested max_buffers amount of buffers, make sure we keep 2 buffers around in the pool and copy them into an output buffer. This makes sure that we always have a buffer to capture into. We also need to detect those copied buffers and unref them when they return to the pool.
This commit is contained in:
parent
713ddbf541
commit
eecb9a96a6
3 changed files with 118 additions and 71 deletions
|
@ -243,9 +243,10 @@ gst_v4l2_buffer_pool_set_config (GstBufferPool * bpool, GstStructure * config)
|
||||||
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
|
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
|
||||||
GstV4l2Object *obj = pool->obj;
|
GstV4l2Object *obj = pool->obj;
|
||||||
GstCaps *caps;
|
GstCaps *caps;
|
||||||
guint size, min_buffers, max_buffers;
|
guint size, min_buffers, max_buffers, num_buffers, copy_threshold;
|
||||||
GstAllocator *allocator;
|
GstAllocator *allocator;
|
||||||
GstAllocationParams params;
|
GstAllocationParams params;
|
||||||
|
struct v4l2_requestbuffers breq;
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (pool, "set config");
|
GST_DEBUG_OBJECT (pool, "set config");
|
||||||
|
|
||||||
|
@ -276,8 +277,76 @@ gst_v4l2_buffer_pool_set_config (GstBufferPool * bpool, GstStructure * config)
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (pool, "config %" GST_PTR_FORMAT, config);
|
GST_DEBUG_OBJECT (pool, "config %" GST_PTR_FORMAT, config);
|
||||||
|
|
||||||
|
switch (obj->mode) {
|
||||||
|
case GST_V4L2_IO_RW:
|
||||||
|
/* we preallocate 1 buffer, this value also instructs the latency
|
||||||
|
* calculation to have 1 frame latency max */
|
||||||
|
num_buffers = 1;
|
||||||
|
copy_threshold = 0;
|
||||||
|
break;
|
||||||
|
case GST_V4L2_IO_MMAP:
|
||||||
|
{
|
||||||
|
/* request a reasonable number of buffers when no max specified. We will
|
||||||
|
* copy when we run out of buffers */
|
||||||
|
if (max_buffers == 0)
|
||||||
|
num_buffers = 4;
|
||||||
|
else
|
||||||
|
num_buffers = max_buffers;
|
||||||
|
|
||||||
|
/* first, lets request buffers, and see how many we can get: */
|
||||||
|
GST_DEBUG_OBJECT (pool, "starting, requesting %d MMAP buffers",
|
||||||
|
num_buffers);
|
||||||
|
|
||||||
|
memset (&breq, 0, sizeof (struct v4l2_requestbuffers));
|
||||||
|
breq.type = obj->type;
|
||||||
|
breq.count = num_buffers;
|
||||||
|
breq.memory = V4L2_MEMORY_MMAP;
|
||||||
|
|
||||||
|
if (v4l2_ioctl (pool->video_fd, VIDIOC_REQBUFS, &breq) < 0)
|
||||||
|
goto reqbufs_failed;
|
||||||
|
|
||||||
|
GST_LOG_OBJECT (pool, " count: %u", breq.count);
|
||||||
|
GST_LOG_OBJECT (pool, " type: %d", breq.type);
|
||||||
|
GST_LOG_OBJECT (pool, " memory: %d", breq.memory);
|
||||||
|
|
||||||
|
if (breq.count < GST_V4L2_MIN_BUFFERS)
|
||||||
|
goto no_buffers;
|
||||||
|
|
||||||
|
if (num_buffers != breq.count) {
|
||||||
|
GST_WARNING_OBJECT (pool, "using %u buffers instead", breq.count);
|
||||||
|
num_buffers = breq.count;
|
||||||
|
}
|
||||||
|
/* update min buffers with the amount of buffers we just reserved. We need
|
||||||
|
* to configure this value in the bufferpool so that the default start
|
||||||
|
* implementation calls our allocate function */
|
||||||
|
min_buffers = breq.count;
|
||||||
|
|
||||||
|
if (max_buffers == 0 || num_buffers < max_buffers) {
|
||||||
|
/* if we are asked to provide more buffers than we have allocated, start
|
||||||
|
* copying buffers when we only have 2 buffers left in the pool */
|
||||||
|
copy_threshold = 2;
|
||||||
|
} else {
|
||||||
|
/* we are certain that we have enough buffers so we don't need to
|
||||||
|
* copy */
|
||||||
|
copy_threshold = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GST_V4L2_IO_USERPTR:
|
||||||
|
default:
|
||||||
|
num_buffers = 0;
|
||||||
|
copy_threshold = 0;
|
||||||
|
g_assert_not_reached ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
pool->size = size;
|
pool->size = size;
|
||||||
pool->max_buffers = max_buffers;
|
pool->num_buffers = num_buffers;
|
||||||
|
pool->copy_threshold = copy_threshold;
|
||||||
|
if (pool->allocator)
|
||||||
|
gst_allocator_unref (pool->allocator);
|
||||||
|
if ((pool->allocator = allocator))
|
||||||
|
gst_allocator_ref (allocator);
|
||||||
pool->params = params;
|
pool->params = params;
|
||||||
|
|
||||||
gst_buffer_pool_config_set_params (config, caps, size, min_buffers,
|
gst_buffer_pool_config_set_params (config, caps, size, min_buffers,
|
||||||
|
@ -298,6 +367,19 @@ wrong_config:
|
||||||
GST_ERROR_OBJECT (pool, "invalid config %" GST_PTR_FORMAT, config);
|
GST_ERROR_OBJECT (pool, "invalid config %" GST_PTR_FORMAT, config);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
reqbufs_failed:
|
||||||
|
{
|
||||||
|
GST_ERROR_OBJECT (pool,
|
||||||
|
"error requesting %d buffers: %s", num_buffers, g_strerror (errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
no_buffers:
|
||||||
|
{
|
||||||
|
GST_ERROR_OBJECT (pool,
|
||||||
|
"we received %d from device '%s', we want at least %d",
|
||||||
|
breq.count, obj->videodev, GST_V4L2_MIN_BUFFERS);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
|
@ -337,55 +419,9 @@ gst_v4l2_buffer_pool_start (GstBufferPool * bpool)
|
||||||
{
|
{
|
||||||
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
|
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
|
||||||
GstV4l2Object *obj = pool->obj;
|
GstV4l2Object *obj = pool->obj;
|
||||||
struct v4l2_requestbuffers breq;
|
|
||||||
gint max_buffers;
|
|
||||||
|
|
||||||
max_buffers = pool->max_buffers;
|
|
||||||
|
|
||||||
switch (obj->mode) {
|
|
||||||
case GST_V4L2_IO_RW:
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case GST_V4L2_IO_MMAP:
|
|
||||||
{
|
|
||||||
/* first, lets request buffers, and see how many we can get: */
|
|
||||||
GST_DEBUG_OBJECT (pool, "starting, requesting %d MMAP buffers",
|
|
||||||
max_buffers);
|
|
||||||
|
|
||||||
if (max_buffers == 0)
|
|
||||||
max_buffers = 4;
|
|
||||||
|
|
||||||
memset (&breq, 0, sizeof (struct v4l2_requestbuffers));
|
|
||||||
breq.type = obj->type;
|
|
||||||
breq.count = max_buffers;
|
|
||||||
breq.memory = V4L2_MEMORY_MMAP;
|
|
||||||
|
|
||||||
if (v4l2_ioctl (pool->video_fd, VIDIOC_REQBUFS, &breq) < 0)
|
|
||||||
goto reqbufs_failed;
|
|
||||||
|
|
||||||
GST_LOG_OBJECT (pool, " count: %u", breq.count);
|
|
||||||
GST_LOG_OBJECT (pool, " type: %d", breq.type);
|
|
||||||
GST_LOG_OBJECT (pool, " memory: %d", breq.memory);
|
|
||||||
|
|
||||||
if (breq.count < GST_V4L2_MIN_BUFFERS)
|
|
||||||
goto no_buffers;
|
|
||||||
|
|
||||||
if (max_buffers != breq.count) {
|
|
||||||
GST_WARNING_OBJECT (pool, "using %u buffers instead", breq.count);
|
|
||||||
max_buffers = breq.count;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case GST_V4L2_IO_USERPTR:
|
|
||||||
default:
|
|
||||||
g_assert_not_reached ();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
pool->obj = obj;
|
pool->obj = obj;
|
||||||
pool->max_buffers = max_buffers;
|
pool->buffers = g_new0 (GstBuffer *, pool->num_buffers);
|
||||||
pool->buffers = g_new0 (GstBuffer *, max_buffers);
|
|
||||||
pool->num_allocated = 0;
|
pool->num_allocated = 0;
|
||||||
|
|
||||||
/* now, allocate the buffers: */
|
/* now, allocate the buffers: */
|
||||||
|
@ -403,19 +439,6 @@ gst_v4l2_buffer_pool_start (GstBufferPool * bpool)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
/* ERRORS */
|
/* ERRORS */
|
||||||
reqbufs_failed:
|
|
||||||
{
|
|
||||||
GST_ERROR_OBJECT (pool,
|
|
||||||
"error requesting %d buffers: %s", max_buffers, g_strerror (errno));
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
no_buffers:
|
|
||||||
{
|
|
||||||
GST_ERROR_OBJECT (pool,
|
|
||||||
"we received %d from device '%s', we want at least %d",
|
|
||||||
breq.count, obj->videodev, GST_V4L2_MIN_BUFFERS);
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
start_failed:
|
start_failed:
|
||||||
{
|
{
|
||||||
GST_ERROR_OBJECT (pool, "failed to start streaming");
|
GST_ERROR_OBJECT (pool, "failed to start streaming");
|
||||||
|
@ -519,16 +542,22 @@ gst_v4l2_buffer_pool_qbuf (GstV4l2BufferPool * pool, GstBuffer * buf)
|
||||||
gint index;
|
gint index;
|
||||||
|
|
||||||
meta = GST_V4L2_META_GET (buf);
|
meta = GST_V4L2_META_GET (buf);
|
||||||
g_assert (meta != NULL);
|
if (meta == NULL) {
|
||||||
|
GST_LOG_OBJECT (pool, "unref copied buffer %p", buf);
|
||||||
|
/* no meta, it was a copied buffer that we can unref */
|
||||||
|
gst_buffer_unref (buf);
|
||||||
|
return GST_FLOW_OK;
|
||||||
|
}
|
||||||
|
|
||||||
index = meta->vbuffer.index;
|
index = meta->vbuffer.index;
|
||||||
|
|
||||||
GST_LOG_OBJECT (pool, "enqueue buffer %p, index:%d, queued:%d", buf,
|
GST_LOG_OBJECT (pool, "enqueue buffer %p, index:%d, queued:%d, flags:%08x",
|
||||||
index, pool->num_queued);
|
buf, index, pool->num_queued, meta->vbuffer.flags);
|
||||||
|
|
||||||
if (pool->buffers[index] != NULL)
|
if (pool->buffers[index] != NULL)
|
||||||
goto already_queued;
|
goto already_queued;
|
||||||
|
|
||||||
|
GST_LOG_OBJECT (pool, "doing QBUF");
|
||||||
if (v4l2_ioctl (pool->video_fd, VIDIOC_QBUF, &meta->vbuffer) < 0)
|
if (v4l2_ioctl (pool->video_fd, VIDIOC_QBUF, &meta->vbuffer) < 0)
|
||||||
goto queue_failed;
|
goto queue_failed;
|
||||||
|
|
||||||
|
@ -699,6 +728,21 @@ gst_v4l2_buffer_pool_acquire_buffer (GstBufferPool * bpool, GstBuffer ** buffer,
|
||||||
* storage for our buffers. This function does poll first so we can
|
* storage for our buffers. This function does poll first so we can
|
||||||
* interrupt it fine. */
|
* interrupt it fine. */
|
||||||
ret = gst_v4l2_buffer_pool_dqbuf (pool, buffer);
|
ret = gst_v4l2_buffer_pool_dqbuf (pool, buffer);
|
||||||
|
if (G_UNLIKELY (ret != GST_FLOW_OK))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
/* start copying buffers when we are running low on buffers */
|
||||||
|
if (pool->num_queued < pool->copy_threshold) {
|
||||||
|
GstBuffer *copy;
|
||||||
|
|
||||||
|
/* copy the memory */
|
||||||
|
copy = gst_buffer_copy (*buffer);
|
||||||
|
GST_LOG_OBJECT (pool, "copy buffer %p->%p", *buffer, copy);
|
||||||
|
|
||||||
|
/* and requeue so that we can continue capturing */
|
||||||
|
ret = gst_v4l2_buffer_pool_qbuf (pool, *buffer);
|
||||||
|
*buffer = copy;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GST_V4L2_IO_USERPTR:
|
case GST_V4L2_IO_USERPTR:
|
||||||
|
@ -734,6 +778,7 @@ gst_v4l2_buffer_pool_acquire_buffer (GstBufferPool * bpool, GstBuffer ** buffer,
|
||||||
g_assert_not_reached ();
|
g_assert_not_reached ();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
/* ERRORS */
|
/* ERRORS */
|
||||||
|
@ -822,7 +867,8 @@ gst_v4l2_buffer_pool_finalize (GObject * object)
|
||||||
|
|
||||||
if (pool->video_fd >= 0)
|
if (pool->video_fd >= 0)
|
||||||
v4l2_close (pool->video_fd);
|
v4l2_close (pool->video_fd);
|
||||||
|
if (pool->allocator)
|
||||||
|
gst_allocator_unref (pool->allocator);
|
||||||
g_free (pool->buffers);
|
g_free (pool->buffers);
|
||||||
|
|
||||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
|
|
|
@ -54,11 +54,12 @@ struct _GstV4l2BufferPool
|
||||||
GstAllocator *allocator;
|
GstAllocator *allocator;
|
||||||
GstAllocationParams params;
|
GstAllocationParams params;
|
||||||
guint size;
|
guint size;
|
||||||
guint max_buffers;
|
|
||||||
gboolean add_videometa;
|
gboolean add_videometa;
|
||||||
|
|
||||||
|
guint num_buffers; /* number of buffers we use */
|
||||||
guint num_allocated; /* number of buffers allocated by the driver */
|
guint num_allocated; /* number of buffers allocated by the driver */
|
||||||
guint num_queued; /* number of buffers queued in the driver */
|
guint num_queued; /* number of buffers queued in the driver */
|
||||||
|
guint copy_threshold; /* when our pool runs lower, start handing out copies */
|
||||||
|
|
||||||
gboolean streaming;
|
gboolean streaming;
|
||||||
|
|
||||||
|
|
|
@ -638,7 +638,7 @@ gst_v4l2src_query (GstBaseSrc * bsrc, GstQuery * query)
|
||||||
case GST_QUERY_LATENCY:{
|
case GST_QUERY_LATENCY:{
|
||||||
GstClockTime min_latency, max_latency;
|
GstClockTime min_latency, max_latency;
|
||||||
guint32 fps_n, fps_d;
|
guint32 fps_n, fps_d;
|
||||||
guint max_buffers;
|
guint num_buffers;
|
||||||
|
|
||||||
/* device must be open */
|
/* device must be open */
|
||||||
if (!GST_V4L2_IS_OPEN (obj)) {
|
if (!GST_V4L2_IS_OPEN (obj)) {
|
||||||
|
@ -661,12 +661,12 @@ gst_v4l2src_query (GstBaseSrc * bsrc, GstQuery * query)
|
||||||
min_latency = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
|
min_latency = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
|
||||||
|
|
||||||
/* max latency is total duration of the frame buffer */
|
/* max latency is total duration of the frame buffer */
|
||||||
max_buffers = GST_V4L2_BUFFER_POOL_CAST (obj->pool)->max_buffers;
|
num_buffers = GST_V4L2_BUFFER_POOL_CAST (obj->pool)->num_buffers;
|
||||||
|
|
||||||
if (max_buffers == 0)
|
if (num_buffers == 0)
|
||||||
max_latency = -1;
|
max_latency = -1;
|
||||||
else
|
else
|
||||||
max_latency = max_buffers * min_latency;
|
max_latency = num_buffers * min_latency;
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (bsrc,
|
GST_DEBUG_OBJECT (bsrc,
|
||||||
"report latency min %" GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
|
"report latency min %" GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
|
||||||
|
|
Loading…
Reference in a new issue