gstreamer/sys/v4l2/gstv4l2bufferpool.c

1737 lines
46 KiB
C
Raw Normal View History

/* GStreamer
*
* Copyright (C) 2001-2002 Ronald Bultje <rbultje@ronald.bitfreak.net>
* 2006 Edgard Lima <edgard.lima@indt.org.br>
* 2009 Texas Instruments, Inc - http://www.ti.com/
*
* gstv4l2bufferpool.c V4L2 buffer pool class
*
* 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifndef _GNU_SOURCE
# define _GNU_SOURCE /* O_CLOEXEC */
#endif
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include "gst/video/video.h"
2011-10-31 01:24:04 +00:00
#include "gst/video/gstvideometa.h"
2011-07-29 15:21:03 +00:00
#include "gst/video/gstvideopool.h"
#include "gst/allocators/gstdmabuf.h"
#include <gstv4l2bufferpool.h>
#include "v4l2_calls.h"
#include "gst/gst-i18n-plugin.h"
#include <gst/glib-compat-private.h>
GST_DEBUG_CATEGORY_EXTERN (v4l2_debug);
GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE);
#define GST_CAT_DEFAULT v4l2_debug
#define GST_V4L2_IMPORT_QUARK gst_v4l2_buffer_pool_import_quark ()
/*
* GstV4l2BufferPool:
*/
#define gst_v4l2_buffer_pool_parent_class parent_class
G_DEFINE_TYPE (GstV4l2BufferPool, gst_v4l2_buffer_pool, GST_TYPE_BUFFER_POOL);
enum _GstV4l2BufferPoolAcquireFlags
{
GST_V4L2_POOL_ACQUIRE_FLAG_RESURECT = GST_BUFFER_POOL_ACQUIRE_FLAG_LAST,
GST_V4L2_BUFFER_POOL_ACQUIRE_FAG_LAST
};
static void gst_v4l2_buffer_pool_release_buffer (GstBufferPool * bpool,
GstBuffer * buffer);
static gboolean
gst_v4l2_is_buffer_valid (GstBuffer * buffer, GstV4l2MemoryGroup ** group)
{
GstMemory *mem = gst_buffer_peek_memory (buffer, 0);
gboolean valid = FALSE;
if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_TAG_MEMORY))
goto done;
if (gst_is_dmabuf_memory (mem))
mem = mem->parent;
if (gst_is_v4l2_memory (mem)) {
GstV4l2Memory *vmem = (GstV4l2Memory *) mem;
valid = TRUE;
if (group)
*group = vmem->group;
}
done:
return valid;
}
static GstFlowReturn
gst_v4l2_buffer_pool_copy_buffer (GstV4l2BufferPool * pool, GstBuffer * dest,
GstBuffer * src)
{
const GstVideoFormatInfo *finfo = pool->obj->info.finfo;
GST_LOG_OBJECT (pool, "copying buffer");
if (finfo && (finfo->format != GST_VIDEO_FORMAT_UNKNOWN &&
finfo->format != GST_VIDEO_FORMAT_ENCODED)) {
GstVideoFrame src_frame, dest_frame;
GST_DEBUG_OBJECT (pool, "copy video frame");
/* we have raw video, use videoframe copy to get strides right */
if (!gst_video_frame_map (&src_frame, &pool->obj->info, src, GST_MAP_READ))
goto invalid_buffer;
if (!gst_video_frame_map (&dest_frame, &pool->obj->info, dest,
GST_MAP_WRITE)) {
gst_video_frame_unmap (&src_frame);
goto invalid_buffer;
}
gst_video_frame_copy (&dest_frame, &src_frame);
gst_video_frame_unmap (&src_frame);
gst_video_frame_unmap (&dest_frame);
} else {
GstMapInfo map;
GST_DEBUG_OBJECT (pool, "copy raw bytes");
if (!gst_buffer_map (src, &map, GST_MAP_READ))
goto invalid_buffer;
gst_buffer_fill (dest, 0, map.data, gst_buffer_get_size (src));
gst_buffer_unmap (src, &map);
gst_buffer_resize (dest, 0, gst_buffer_get_size (src));
}
GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, pool, "slow copy into buffer %p",
dest);
return GST_FLOW_OK;
invalid_buffer:
{
GST_ERROR_OBJECT (pool, "could not map buffer");
return GST_FLOW_ERROR;
}
}
struct UserPtrData
{
GstBuffer *buffer;
gboolean is_frame;
GstVideoFrame frame;
GstMapInfo map;
};
static GQuark
gst_v4l2_buffer_pool_import_quark (void)
{
static GQuark quark = 0;
if (quark == 0)
quark = g_quark_from_string ("GstV4l2BufferPoolUsePtrData");
return quark;
}
static void
_unmap_userptr_frame (struct UserPtrData *data)
{
if (data->is_frame)
gst_video_frame_unmap (&data->frame);
else
gst_buffer_unmap (data->buffer, &data->map);
if (data->buffer)
gst_buffer_unref (data->buffer);
g_slice_free (struct UserPtrData, data);
}
static GstFlowReturn
gst_v4l2_buffer_pool_import_userptr (GstV4l2BufferPool * pool,
GstBuffer * dest, GstBuffer * src)
{
GstFlowReturn ret = GST_FLOW_OK;
GstV4l2MemoryGroup *group = NULL;
GstMapFlags flags;
const GstVideoFormatInfo *finfo = pool->obj->info.finfo;
struct UserPtrData *data = NULL;
GST_LOG_OBJECT (pool, "importing userptr");
/* get the group */
if (!gst_v4l2_is_buffer_valid (dest, &group))
goto not_our_buffer;
if (!V4L2_TYPE_IS_OUTPUT (pool->obj->type))
flags = GST_MAP_READ;
else
flags = GST_MAP_WRITE;
data = g_slice_new0 (struct UserPtrData);
if (finfo && (finfo->format != GST_VIDEO_FORMAT_UNKNOWN &&
finfo->format != GST_VIDEO_FORMAT_ENCODED)) {
data->is_frame = TRUE;
if (!gst_video_frame_map (&data->frame, &pool->obj->info, src, flags))
goto invalid_buffer;
if (!gst_v4l2_allocator_import_userptr (pool->vallocator, group,
data->frame.info.size, finfo->n_planes, data->frame.data,
data->frame.info.offset))
goto import_failed;
} else {
gsize offset[1] = { 0 };
gpointer ptr[1];
data->is_frame = FALSE;
if (!gst_buffer_map (src, &data->map, flags))
goto invalid_buffer;
ptr[0] = data->map.data;
if (!gst_v4l2_allocator_import_userptr (pool->vallocator, group,
data->map.size, 1, ptr, offset))
goto import_failed;
}
data->buffer = gst_buffer_ref (src);
gst_mini_object_set_qdata (GST_MINI_OBJECT (dest), GST_V4L2_IMPORT_QUARK,
data, (GDestroyNotify) _unmap_userptr_frame);
return ret;
not_our_buffer:
{
GST_ERROR_OBJECT (pool, "destination buffer invalid or not from our pool");
return GST_FLOW_ERROR;
}
invalid_buffer:
{
GST_ERROR_OBJECT (pool, "could not map buffer");
g_slice_free (struct UserPtrData, data);
return GST_FLOW_ERROR;
}
import_failed:
{
GST_ERROR_OBJECT (pool, "failed to import data");
_unmap_userptr_frame (data);
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_v4l2_buffer_pool_import_dmabuf (GstV4l2BufferPool * pool,
GstBuffer * dest, GstBuffer * src)
{
GstV4l2MemoryGroup *group = NULL;
GstMemory *dma_mem[GST_VIDEO_MAX_PLANES] = { 0 };
guint n_mem = gst_buffer_n_memory (src);
gint i;
GST_LOG_OBJECT (pool, "importing dmabuf");
if (!gst_v4l2_is_buffer_valid (dest, &group))
goto not_our_buffer;
if (n_mem > GST_VIDEO_MAX_PLANES)
goto too_many_mems;
for (i = 0; i < n_mem; i++)
dma_mem[i] = gst_buffer_peek_memory (src, i);
if (!gst_v4l2_allocator_import_dmabuf (pool->vallocator, group, n_mem,
dma_mem))
goto import_failed;
gst_mini_object_set_qdata (GST_MINI_OBJECT (dest), GST_V4L2_IMPORT_QUARK,
gst_buffer_ref (src), (GDestroyNotify) gst_buffer_unref);
return GST_FLOW_OK;
not_our_buffer:
{
GST_ERROR_OBJECT (pool, "destination buffer invalid or not from our pool");
return GST_FLOW_ERROR;
}
too_many_mems:
{
GST_ERROR_OBJECT (pool, "could not map buffer");
return GST_FLOW_ERROR;
}
import_failed:
{
GST_ERROR_OBJECT (pool, "failed to import dmabuf");
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_v4l2_buffer_pool_prepare_buffer (GstV4l2BufferPool * pool,
GstBuffer * dest, GstBuffer * src)
{
GstFlowReturn ret = GST_FLOW_OK;
gboolean own_src = FALSE;
if (src == NULL) {
if (pool->other_pool == NULL) {
GST_ERROR_OBJECT (pool, "can't prepare buffer, source buffer missing");
return GST_FLOW_ERROR;
}
ret = gst_buffer_pool_acquire_buffer (pool->other_pool, &src, NULL);
if (ret != GST_FLOW_OK) {
GST_ERROR_OBJECT (pool, "failed to acquire buffer from downstream pool");
goto done;
}
own_src = TRUE;
}
switch (pool->obj->mode) {
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_DMABUF:
ret = gst_v4l2_buffer_pool_copy_buffer (pool, dest, src);
break;
case GST_V4L2_IO_USERPTR:
ret = gst_v4l2_buffer_pool_import_userptr (pool, dest, src);
break;
case GST_V4L2_IO_DMABUF_IMPORT:
ret = gst_v4l2_buffer_pool_import_dmabuf (pool, dest, src);
break;
default:
break;
}
if (own_src)
gst_buffer_unref (src);
done:
return ret;
}
static GstFlowReturn
gst_v4l2_buffer_pool_alloc_buffer (GstBufferPool * bpool, GstBuffer ** buffer,
2012-03-15 13:06:40 +00:00
GstBufferPoolAcquireParams * params)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2MemoryGroup *group = NULL;
GstBuffer *newbuf = NULL;
GstV4l2Object *obj;
GstVideoInfo *info;
obj = pool->obj;
info = &obj->info;
switch (obj->mode) {
case GST_V4L2_IO_RW:
newbuf =
2012-03-15 12:36:17 +00:00
gst_buffer_new_allocate (pool->allocator, pool->size, &pool->params);
break;
case GST_V4L2_IO_MMAP:
group = gst_v4l2_allocator_alloc_mmap (pool->vallocator);
break;
case GST_V4L2_IO_DMABUF:
group = gst_v4l2_allocator_alloc_dmabuf (pool->vallocator,
pool->allocator);
break;
case GST_V4L2_IO_USERPTR:
group = gst_v4l2_allocator_alloc_userptr (pool->vallocator);
break;
2014-04-14 16:19:39 +00:00
case GST_V4L2_IO_DMABUF_IMPORT:
group = gst_v4l2_allocator_alloc_dmabufin (pool->vallocator);
break;
default:
newbuf = NULL;
g_assert_not_reached ();
break;
}
if (group != NULL) {
gint i;
newbuf = gst_buffer_new ();
for (i = 0; i < group->n_mem; i++)
gst_buffer_append_memory (newbuf, group->mem[i]);
} else if (newbuf == NULL) {
goto allocation_failed;
}
/* add metadata to raw video buffers */
if (pool->add_videometa)
gst_buffer_add_video_meta_full (newbuf, GST_VIDEO_FRAME_FLAG_NONE,
GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info),
GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_N_PLANES (info),
info->offset, info->stride);
*buffer = newbuf;
return GST_FLOW_OK;
/* ERRORS */
allocation_failed:
{
GST_ERROR_OBJECT (pool, "failed to allocate buffer");
return FALSE;
}
}
static gboolean
gst_v4l2_buffer_pool_set_config (GstBufferPool * bpool, GstStructure * config)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
GstCaps *caps;
guint size, min_buffers, max_buffers;
2012-03-15 21:11:17 +00:00
GstAllocator *allocator;
GstAllocationParams params;
gboolean can_allocate = FALSE;
gboolean updated = FALSE;
gboolean ret;
pool->add_videometa =
2011-07-29 15:21:03 +00:00
gst_buffer_pool_config_has_option (config,
2011-10-31 01:24:04 +00:00
GST_BUFFER_POOL_OPTION_VIDEO_META);
/* parse the config and keep around */
2012-03-15 21:11:17 +00:00
if (!gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers,
&max_buffers))
goto wrong_config;
if (!gst_buffer_pool_config_get_allocator (config, &allocator, &params))
goto wrong_config;
GST_DEBUG_OBJECT (pool, "config %" GST_PTR_FORMAT, config);
if (pool->allocator)
gst_object_unref (pool->allocator);
pool->allocator = NULL;
switch (obj->mode) {
case GST_V4L2_IO_DMABUF:
pool->allocator = gst_dmabuf_allocator_new ();
can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, MMAP);
break;
case GST_V4L2_IO_MMAP:
can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, MMAP);
break;
case GST_V4L2_IO_USERPTR:
can_allocate =
GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, USERPTR);
break;
case GST_V4L2_IO_DMABUF_IMPORT:
can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, DMABUF);
break;
case GST_V4L2_IO_RW:
pool->allocator = g_object_ref (allocator);
pool->params = params;
/* No need to change the configuration */
goto done;
break;
2014-04-14 16:19:39 +00:00
default:
g_assert_not_reached ();
break;
}
if (min_buffers < GST_V4L2_MIN_BUFFERS) {
updated = TRUE;
min_buffers = GST_V4L2_MIN_BUFFERS;
GST_INFO_OBJECT (pool, "increasing minimum buffers to %u", min_buffers);
}
if (max_buffers > VIDEO_MAX_FRAME || max_buffers == 0) {
updated = TRUE;
max_buffers = VIDEO_MAX_FRAME;
GST_INFO_OBJECT (pool, "reducing maximum buffers to %u", max_buffers);
}
if (min_buffers > max_buffers) {
updated = TRUE;
min_buffers = max_buffers;
GST_INFO_OBJECT (pool, "reducing minimum buffers to %u", min_buffers);
} else if (min_buffers != max_buffers) {
if (!can_allocate) {
updated = TRUE;
max_buffers = min_buffers;
GST_INFO_OBJECT (pool, "can't allocate, setting maximum to minimum");
}
}
if (!pool->add_videometa && obj->need_video_meta) {
GST_INFO_OBJECT (pool, "adding needed video meta");
updated = TRUE;
gst_buffer_pool_config_add_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
}
if (updated)
gst_buffer_pool_config_set_params (config, caps, size, min_buffers,
max_buffers);
done:
ret = GST_BUFFER_POOL_CLASS (parent_class)->set_config (bpool, config);
/* If anything was changed documentation recommand to return FALSE */
return !updated && ret;
/* ERRORS */
wrong_config:
{
GST_ERROR_OBJECT (pool, "invalid config %" GST_PTR_FORMAT, config);
return FALSE;
}
}
static gboolean
start_streaming (GstV4l2BufferPool * pool)
{
GstV4l2Object *obj = pool->obj;
2014-05-05 00:23:42 +00:00
GST_DEBUG_OBJECT (pool, "start streaming");
if (pool->streaming)
return TRUE;
switch (obj->mode) {
case GST_V4L2_IO_RW:
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
case GST_V4L2_IO_DMABUF:
2014-04-14 16:19:39 +00:00
case GST_V4L2_IO_DMABUF_IMPORT:
{
/* For capture device, we need to re-enqueue buffers before be can let
* the driver stream again */
if (!V4L2_TYPE_IS_OUTPUT (obj->type) && pool->vallocator) {
GstBufferPool *bpool = GST_BUFFER_POOL (pool);
GstBufferPoolAcquireParams params = { 0 };
gsize num_allocated, num_to_queue;
GstFlowReturn ret;
num_allocated = gst_v4l2_allocator_num_allocated (pool->vallocator);
num_to_queue = num_allocated - pool->num_queued;
while (num_to_queue > 0) {
GstBuffer *buf;
params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT;
ret = GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool,
&buf, &params);
if (ret != GST_FLOW_OK)
goto requeue_failed;
gst_v4l2_buffer_pool_release_buffer (bpool, buf);
num_to_queue--;
}
if (num_allocated != pool->num_queued)
goto requeue_failed;
}
if (v4l2_ioctl (pool->video_fd, VIDIOC_STREAMON, &obj->type) < 0)
goto start_failed;
2014-05-05 00:23:42 +00:00
GST_DEBUG_OBJECT (pool, "STREAMON");
break;
}
default:
g_assert_not_reached ();
break;
}
pool->streaming = TRUE;
return TRUE;
/* ERRORS */
start_failed:
{
GST_ERROR_OBJECT (pool, "error with STREAMON %d (%s)", errno,
g_strerror (errno));
return FALSE;
}
requeue_failed:
{
GST_ERROR_OBJECT (pool, "failed to re-enqueue buffers");
return FALSE;
}
}
static void
gst_v4l2_buffer_pool_group_released (GstV4l2BufferPool * pool)
{
GstBufferPoolAcquireParams params = { 0 };
GstBuffer *buffer = NULL;
GstFlowReturn ret;
GST_DEBUG_OBJECT (pool, "A buffer was lost, reallocating it");
params.flags = GST_V4L2_POOL_ACQUIRE_FLAG_RESURECT;
ret = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL (pool), &buffer,
&params);
if (ret == GST_FLOW_OK)
gst_buffer_unref (buffer);
}
static gboolean
gst_v4l2_buffer_pool_start (GstBufferPool * bpool)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
GstStructure *config;
GstCaps *caps;
guint size, min_buffers, max_buffers;
guint num_buffers = 0, copy_threshold = 0;
config = gst_buffer_pool_get_config (bpool);
if (!gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers,
&max_buffers))
goto wrong_config;
switch (obj->mode) {
case GST_V4L2_IO_RW:
/* this value also instructs the latency calculation to have min_buffers
* frame latency max */
num_buffers = min_buffers;
break;
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_MMAP:
{
guint count;
if (GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, MMAP)) {
num_buffers = min_buffers;
} else {
num_buffers = max_buffers;
}
/* first, lets request buffers, and see how many we can get: */
GST_DEBUG_OBJECT (pool, "requesting %d MMAP buffers", num_buffers);
count = gst_v4l2_allocator_start (pool->vallocator, num_buffers,
V4L2_MEMORY_MMAP);
if (count < GST_V4L2_MIN_BUFFERS) {
num_buffers = count;
goto no_buffers;
}
/* V4L2 buffer pool are often very limited in the amount of buffers it
* can offer. The copy_threshold will workaround this limitation by
* falling back to copy if the pipeline needed more buffers. This also
* prevent having to do REQBUFS(N)/REQBUFS(0) everytime configure is
* called. */
if (count != num_buffers) {
GST_WARNING_OBJECT (pool, "using %u buffers instead of %u",
count, num_buffers);
num_buffers = count;
copy_threshold =
MAX (GST_V4L2_MIN_BUFFERS, obj->min_buffers_for_capture);
/* Ensure GstBufferPool don't expect initial minimum */
if (min_buffers > count)
min_buffers = count;
}
break;
}
case GST_V4L2_IO_USERPTR:
{
guint count;
if (GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, USERPTR)) {
num_buffers = min_buffers;
} else {
num_buffers = max_buffers;
}
GST_DEBUG_OBJECT (pool, "requesting %d USERPTR buffers", num_buffers);
count = gst_v4l2_allocator_start (pool->vallocator, num_buffers,
V4L2_MEMORY_USERPTR);
/* There is no rational to not get what we asked */
if (count < num_buffers) {
num_buffers = count;
goto no_buffers;
}
break;
}
2014-04-14 16:19:39 +00:00
case GST_V4L2_IO_DMABUF_IMPORT:
{
guint count;
if (GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, DMABUF)) {
num_buffers = min_buffers;
} else {
num_buffers = max_buffers;
}
GST_DEBUG_OBJECT (pool, "requesting %d DMABUF buffers", num_buffers);
count = gst_v4l2_allocator_start (pool->vallocator, num_buffers,
V4L2_MEMORY_DMABUF);
/* There is no rational to not get what we asked */
if (count < num_buffers) {
num_buffers = count;
goto no_buffers;
}
break;
}
default:
num_buffers = 0;
copy_threshold = 0;
g_assert_not_reached ();
break;
}
pool->size = size;
pool->copy_threshold = copy_threshold;
pool->num_buffers = num_buffers;
pool->num_queued = 0;
2012-03-15 21:11:17 +00:00
gst_buffer_pool_config_set_params (config, caps, size, min_buffers,
max_buffers);
GST_BUFFER_POOL_CLASS (parent_class)->set_config (bpool, config);
gst_structure_free (config);
if (pool->other_pool)
if (!gst_buffer_pool_set_active (pool->other_pool, TRUE))
goto other_pool_failed;
/* now, allocate the buffers: */
if (!GST_BUFFER_POOL_CLASS (parent_class)->start (bpool))
goto start_failed;
/* we can start capturing now, we wait for the playback case until we queued
* the first buffer */
if (!V4L2_TYPE_IS_OUTPUT (obj->type))
if (!start_streaming (pool))
goto start_failed;
if (!V4L2_TYPE_IS_OUTPUT (obj->type))
pool->group_released_handler =
g_signal_connect_swapped (pool->vallocator, "group-released",
G_CALLBACK (gst_v4l2_buffer_pool_group_released), pool);
gst_poll_set_flushing (obj->poll, FALSE);
return TRUE;
/* ERRORS */
wrong_config:
{
GST_ERROR_OBJECT (pool, "invalid config %" GST_PTR_FORMAT, config);
gst_structure_free (config);
return FALSE;
}
no_buffers:
{
GST_ERROR_OBJECT (pool,
"we received %d buffer from device '%s', we want at least %d",
num_buffers, obj->videodev, GST_V4L2_MIN_BUFFERS);
gst_structure_free (config);
return FALSE;
}
start_failed:
{
GST_ERROR_OBJECT (pool, "failed to start streaming");
return FALSE;
}
other_pool_failed:
{
GST_ERROR_OBJECT (pool, "failed to active the other pool %"
GST_PTR_FORMAT, pool->other_pool);
return FALSE;
}
}
static gboolean
stop_streaming (GstV4l2BufferPool * pool)
{
GstV4l2Object *obj = pool->obj;
gint i;
GST_DEBUG_OBJECT (pool, "stopping stream");
gst_poll_set_flushing (obj->poll, TRUE);
if (!pool->streaming) {
/* it avoid error: STREAMOFF 22 (Invalid argument) when
* attempting to stop a stream not previously started */
GST_DEBUG_OBJECT (pool, "no need to stop, was not previously started");
return TRUE;
}
pool->flushing = TRUE;
switch (obj->mode) {
case GST_V4L2_IO_RW:
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
case GST_V4L2_IO_DMABUF:
2014-04-14 16:19:39 +00:00
case GST_V4L2_IO_DMABUF_IMPORT:
{
gsize num_allocated;
if (v4l2_ioctl (pool->video_fd, VIDIOC_STREAMOFF, &obj->type) < 0)
goto stop_failed;
2014-05-05 00:23:42 +00:00
GST_DEBUG_OBJECT (pool, "STREAMOFF");
gst_v4l2_allocator_flush (pool->vallocator);
num_allocated = gst_v4l2_allocator_num_allocated (pool->vallocator);
for (i = 0; i < num_allocated; i++) {
if (pool->buffers[i]) {
GstBufferPool *bpool = (GstBufferPool *) pool;
GstBuffer *buffer = pool->buffers[i];
pool->buffers[i] = NULL;
pool->num_queued--;
/* Remove qdata, this will unmap any map data in userptr */
gst_mini_object_set_qdata (GST_MINI_OBJECT (buffer),
GST_V4L2_IMPORT_QUARK, NULL, NULL);
if (V4L2_TYPE_IS_OUTPUT (obj->type))
gst_buffer_unref (buffer);
else
/* Give back the outstanding buffer to the pool */
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool,
buffer);
}
}
g_return_val_if_fail (pool->num_queued == 0, FALSE);
break;
}
default:
g_return_val_if_reached (FALSE);
break;
}
pool->flushing = FALSE;
pool->streaming = FALSE;
if (pool->other_pool)
gst_buffer_pool_set_active (pool->other_pool, FALSE);
return TRUE;
/* ERRORS */
stop_failed:
{
GST_ERROR_OBJECT (pool, "error with STREAMOFF %d (%s)", errno,
g_strerror (errno));
return FALSE;
}
}
static gboolean
gst_v4l2_buffer_pool_stop (GstBufferPool * bpool)
{
gboolean ret;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
GST_DEBUG_OBJECT (pool, "stopping pool");
if (pool->group_released_handler > 0) {
g_signal_handler_disconnect (pool->vallocator,
pool->group_released_handler);
pool->group_released_handler = 0;
}
gst_poll_set_flushing (obj->poll, TRUE);
if (!stop_streaming (pool))
goto stop_failed;
ret = GST_BUFFER_POOL_CLASS (parent_class)->stop (bpool);
if (ret) {
GstV4l2Return vret;
vret = gst_v4l2_allocator_stop (pool->vallocator);
if (vret == GST_V4L2_BUSY) {
GST_WARNING_OBJECT (pool, "allocated buffer need to be reclaimed");
/* FIXME deal with reclaiming */
} else if (vret == GST_V4L2_ERROR) {
ret = FALSE;
}
}
return ret;
/* ERRORS */
stop_failed:
{
GST_ERROR_OBJECT (pool, "error with STREAMOFF %d (%s)", errno,
g_strerror (errno));
return FALSE;
}
}
static GstFlowReturn
gst_v4l2_object_poll (GstV4l2Object * v4l2object)
{
gint ret;
if (v4l2object->can_poll_device) {
GST_LOG_OBJECT (v4l2object->element, "polling device");
ret = gst_poll_wait (v4l2object->poll, GST_CLOCK_TIME_NONE);
if (G_UNLIKELY (ret < 0)) {
if (errno == EBUSY)
goto stopped;
if (errno == ENXIO) {
GST_WARNING_OBJECT (v4l2object->element,
"v4l2 device doesn't support polling. Disabling");
v4l2object->can_poll_device = FALSE;
} else {
if (errno != EAGAIN && errno != EINTR)
goto select_error;
}
}
}
return GST_FLOW_OK;
/* ERRORS */
stopped:
{
GST_DEBUG ("stop called");
return GST_FLOW_FLUSHING;
}
select_error:
{
GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ, (NULL),
("poll error %d: %s (%d)", ret, g_strerror (errno), errno));
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_v4l2_buffer_pool_qbuf (GstV4l2BufferPool * pool, GstBuffer * buf)
{
GstV4l2MemoryGroup *group = NULL;
gint index;
if (!gst_v4l2_is_buffer_valid (buf, &group)) {
GST_LOG_OBJECT (pool, "unref copied/invalid buffer %p", buf);
gst_buffer_unref (buf);
return GST_FLOW_OK;
}
index = group->buffer.index;
if (pool->buffers[index] != NULL)
goto already_queued;
GST_LOG_OBJECT (pool, "queuing buffer %i", index);
if (!gst_v4l2_allocator_qbuf (pool->vallocator, group))
goto queue_failed;
pool->buffers[index] = buf;
pool->num_queued++;
return GST_FLOW_OK;
already_queued:
{
GST_ERROR_OBJECT (pool, "the buffer %i was already queued", index);
return GST_FLOW_ERROR;
}
queue_failed:
{
GST_ERROR_OBJECT (pool, "could not queue a buffer %i", index);
/* Mark broken buffer to the allocator */
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_TAG_MEMORY);
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_v4l2_buffer_pool_dqbuf (GstV4l2BufferPool * pool, GstBuffer ** buffer)
{
GstFlowReturn res;
GstBuffer *outbuf;
GstV4l2Object *obj = pool->obj;
GstClockTime timestamp;
GstV4l2MemoryGroup *group;
gint i;
if ((res = gst_v4l2_object_poll (obj)) != GST_FLOW_OK)
goto poll_failed;
GST_LOG_OBJECT (pool, "dequeueing a buffer");
group = gst_v4l2_allocator_dqbuf (pool->vallocator);
if (group == NULL)
goto dqbuf_failed;
/* get our GstBuffer with that index from the pool, if the buffer was
* outstanding we have a serious problem.
*/
outbuf = pool->buffers[group->buffer.index];
if (outbuf == NULL)
goto no_buffer;
/* mark the buffer outstanding */
pool->buffers[group->buffer.index] = NULL;
pool->num_queued--;
timestamp = GST_TIMEVAL_TO_TIME (group->buffer.timestamp);
#ifndef GST_DISABLE_GST_DEBUG
for (i = 0; i < group->n_mem; i++) {
GST_LOG_OBJECT (pool,
"dequeued buffer %p seq:%d (ix=%d), mem %p used %d, plane=%d, flags %08x, ts %"
GST_TIME_FORMAT ", pool-queued=%d, buffer=%p", outbuf,
group->buffer.sequence, group->buffer.index, group->mem[i],
group->planes[i].bytesused, i, group->buffer.flags,
GST_TIME_ARGS (timestamp), pool->num_queued, outbuf);
}
#endif
/* set top/bottom field first if v4l2_buffer has the information */
if (group->buffer.field == V4L2_FIELD_INTERLACED_TB) {
GST_BUFFER_FLAG_SET (outbuf, GST_VIDEO_BUFFER_FLAG_INTERLACED);
2011-08-25 14:41:23 +00:00
GST_BUFFER_FLAG_SET (outbuf, GST_VIDEO_BUFFER_FLAG_TFF);
} else if (group->buffer.field == V4L2_FIELD_INTERLACED_BT) {
GST_BUFFER_FLAG_SET (outbuf, GST_VIDEO_BUFFER_FLAG_INTERLACED);
2011-08-25 14:41:23 +00:00
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_FLAG_TFF);
} else {
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_FLAG_INTERLACED);
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_FLAG_TFF);
2011-12-19 18:03:52 +00:00
}
if (GST_VIDEO_INFO_FORMAT (&obj->info) == GST_VIDEO_FORMAT_ENCODED) {
if (group->buffer.flags & V4L2_BUF_FLAG_KEYFRAME)
GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT);
else
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT);
}
GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
*buffer = outbuf;
return GST_FLOW_OK;
/* ERRORS */
poll_failed:
{
GST_DEBUG_OBJECT (pool, "poll error %s", gst_flow_get_name (res));
return res;
}
dqbuf_failed:
{
return GST_FLOW_ERROR;
}
no_buffer:
{
GST_ERROR_OBJECT (pool, "No free buffer found in the pool at index %d.",
group->buffer.index);
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_v4l2_buffer_pool_acquire_buffer (GstBufferPool * bpool, GstBuffer ** buffer,
2012-03-15 13:06:40 +00:00
GstBufferPoolAcquireParams * params)
{
GstFlowReturn ret;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
GST_DEBUG_OBJECT (pool, "acquire");
if (GST_BUFFER_POOL_IS_FLUSHING (bpool))
goto flushing;
/* If this is being called to resurect a lost buffer */
if (params && params->flags & GST_V4L2_POOL_ACQUIRE_FLAG_RESURECT) {
ret = GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool, buffer,
params);
goto done;
}
switch (obj->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
/* capture, This function should return a buffer with new captured data */
switch (obj->mode) {
case GST_V4L2_IO_RW:
{
/* take empty buffer from the pool */
ret = GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool,
buffer, params);
break;
}
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_MMAP:
{
/* just dequeue a buffer, we basically use the queue of v4l2 as the
* storage for our buffers. This function does poll first so we can
* interrupt it fine. */
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;
if (GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, MMAP)) {
if (GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool,
&copy, params) == GST_FLOW_OK) {
gst_v4l2_buffer_pool_release_buffer (bpool, copy);
break;
}
}
/* copy the buffer */
copy = gst_buffer_copy_region (*buffer,
GST_BUFFER_COPY_ALL | GST_BUFFER_COPY_DEEP, 0, -1);
GST_LOG_OBJECT (pool, "copy buffer %p->%p", *buffer, copy);
/* and requeue so that we can continue capturing */
gst_v4l2_buffer_pool_release_buffer (bpool, *buffer);
*buffer = copy;
}
break;
}
case GST_V4L2_IO_USERPTR:
2014-04-14 16:19:39 +00:00
case GST_V4L2_IO_DMABUF_IMPORT:
{
/* dequeue filled buffer */
ret = gst_v4l2_buffer_pool_dqbuf (pool, buffer);
break;
}
default:
ret = GST_FLOW_ERROR;
g_assert_not_reached ();
break;
}
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
/* playback, This function should return an empty buffer */
switch (obj->mode) {
case GST_V4L2_IO_RW:
/* get an empty buffer */
ret = GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool,
buffer, params);
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_USERPTR:
case GST_V4L2_IO_DMABUF_IMPORT:
/* get a free unqueued buffer */
ret = GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool,
buffer, params);
break;
default:
ret = GST_FLOW_ERROR;
g_assert_not_reached ();
break;
}
break;
default:
ret = GST_FLOW_ERROR;
g_assert_not_reached ();
break;
}
done:
return ret;
/* ERRORS */
flushing:
{
GST_DEBUG_OBJECT (pool, "We are flushing");
return GST_FLOW_FLUSHING;
}
}
static void
gst_v4l2_buffer_pool_release_buffer (GstBufferPool * bpool, GstBuffer * buffer)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
GST_DEBUG_OBJECT (pool, "release buffer %p", buffer);
switch (obj->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
/* capture, put the buffer back in the queue so that we can refill it
* later. */
switch (obj->mode) {
case GST_V4L2_IO_RW:
/* release back in the pool */
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool, buffer);
break;
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
case GST_V4L2_IO_DMABUF_IMPORT:
{
if (pool->flushing) {
/* put back on outstanding list */
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool,
buffer);
} else if (gst_v4l2_is_buffer_valid (buffer, NULL)) {
/* queue back in the device */
if (pool->other_pool)
gst_v4l2_buffer_pool_prepare_buffer (pool, buffer, NULL);
if (gst_v4l2_buffer_pool_qbuf (pool, buffer) != GST_FLOW_OK)
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool,
buffer);
} else {
/* Simply release invalide/modified buffer, the allocator will
* give it back later */
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_TAG_MEMORY);
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool,
buffer);
}
break;
}
default:
g_assert_not_reached ();
break;
}
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
switch (obj->mode) {
case GST_V4L2_IO_RW:
/* release back in the pool */
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool, buffer);
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_USERPTR:
case GST_V4L2_IO_DMABUF_IMPORT:
{
GstV4l2MemoryGroup *group;
2013-04-03 09:09:37 +00:00
guint index;
if (!gst_v4l2_is_buffer_valid (buffer, &group)) {
/* Simply release invalide/modified buffer, the allocator will
* give it back later */
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_TAG_MEMORY);
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool,
buffer);
break;
}
index = group->buffer.index;
2013-04-03 09:09:37 +00:00
if (pool->buffers[index] == NULL) {
GST_LOG_OBJECT (pool, "buffer %u not queued, putting on free list",
index);
/* Remove qdata, this will unmap any map data in userptr */
gst_mini_object_set_qdata (GST_MINI_OBJECT (buffer),
GST_V4L2_IMPORT_QUARK, NULL, NULL);
/* reset to default size */
gst_v4l2_allocator_reset_group (pool->vallocator, group);
/* playback, put the buffer back in the queue to refill later. */
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool,
buffer);
} else {
/* We keep a ref on queued buffer, so this should never happen */
g_assert_not_reached ();
}
break;
}
default:
g_assert_not_reached ();
break;
}
break;
default:
g_assert_not_reached ();
break;
}
}
static void
gst_v4l2_buffer_pool_finalize (GObject * object)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (object);
gint i;
for (i = 0; i < VIDEO_MAX_FRAME; i++) {
if (pool->buffers[i])
gst_buffer_replace (&(pool->buffers[i]), NULL);
}
if (pool->video_fd >= 0)
v4l2_close (pool->video_fd);
if (pool->vallocator)
gst_object_unref (pool->vallocator);
if (pool->allocator)
2012-07-09 14:27:10 +00:00
gst_object_unref (pool->allocator);
if (pool->other_pool)
gst_object_unref (pool->other_pool);
/* FIXME Is this required to keep around ? */
gst_object_unref (pool->obj->element);
/* FIXME have we done enough here ? */
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_v4l2_buffer_pool_init (GstV4l2BufferPool * pool)
{
}
static void
gst_v4l2_buffer_pool_class_init (GstV4l2BufferPoolClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstBufferPoolClass *bufferpool_class = GST_BUFFER_POOL_CLASS (klass);
object_class->finalize = gst_v4l2_buffer_pool_finalize;
bufferpool_class->start = gst_v4l2_buffer_pool_start;
bufferpool_class->stop = gst_v4l2_buffer_pool_stop;
bufferpool_class->set_config = gst_v4l2_buffer_pool_set_config;
bufferpool_class->alloc_buffer = gst_v4l2_buffer_pool_alloc_buffer;
bufferpool_class->acquire_buffer = gst_v4l2_buffer_pool_acquire_buffer;
bufferpool_class->release_buffer = gst_v4l2_buffer_pool_release_buffer;
}
/**
* gst_v4l2_buffer_pool_new:
* @obj: the v4l2 object owning the pool
*
* Construct a new buffer pool.
*
* Returns: the new pool, use gst_object_unref() to free resources
*/
GstBufferPool *
gst_v4l2_buffer_pool_new (GstV4l2Object * obj, GstCaps * caps)
{
GstV4l2BufferPool *pool;
GstStructure *config;
gchar *name, *parent_name;
2011-07-18 08:51:21 +00:00
gint fd;
2011-07-18 08:51:21 +00:00
fd = v4l2_dup (obj->video_fd);
if (fd < 0)
goto dup_failed;
/* setting a significant unique name */
parent_name = gst_object_get_name (GST_OBJECT (obj->element));
name = g_strconcat (parent_name, ":", "pool:",
V4L2_TYPE_IS_OUTPUT (obj->type) ? "sink" : "src", NULL);
g_free (parent_name);
pool = (GstV4l2BufferPool *) g_object_new (GST_TYPE_V4L2_BUFFER_POOL,
"name", name, NULL);
g_free (name);
2011-07-18 08:51:21 +00:00
pool->video_fd = fd;
pool->obj = obj;
pool->vallocator =
gst_v4l2_allocator_new (GST_OBJECT (pool), obj->video_fd, &obj->format);
gst_object_ref (obj->element);
config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pool));
gst_buffer_pool_config_set_params (config, caps, obj->info.size, 0, 0);
/* This will simply set a default config, but will not configure the pool
* because min and max are not valid */
gst_buffer_pool_set_config (GST_BUFFER_POOL_CAST (pool), config);
return GST_BUFFER_POOL (pool);
/* ERRORS */
dup_failed:
{
GST_ERROR ("failed to dup fd %d (%s)", errno, g_strerror (errno));
return NULL;
}
}
static GstFlowReturn
gst_v4l2_do_read (GstV4l2BufferPool * pool, GstBuffer * buf)
{
GstFlowReturn res;
GstV4l2Object *obj = pool->obj;
gint amount;
2012-01-24 13:38:58 +00:00
GstMapInfo map;
gint toread;
toread = obj->info.size;
GST_LOG_OBJECT (pool, "reading %d bytes into buffer %p", toread, buf);
2012-01-24 13:38:58 +00:00
gst_buffer_map (buf, &map, GST_MAP_WRITE);
do {
if ((res = gst_v4l2_object_poll (obj)) != GST_FLOW_OK)
goto poll_error;
2012-01-24 13:38:58 +00:00
amount = v4l2_read (obj->video_fd, map.data, toread);
if (amount == toread) {
break;
} else if (amount == -1) {
if (errno == EAGAIN || errno == EINTR) {
continue;
} else
goto read_error;
} else {
/* short reads can happen if a signal interrupts the read */
continue;
}
} while (TRUE);
GST_LOG_OBJECT (pool, "read %d bytes", amount);
2012-01-24 13:38:58 +00:00
gst_buffer_unmap (buf, &map);
gst_buffer_resize (buf, 0, amount);
return GST_FLOW_OK;
/* ERRORS */
poll_error:
{
GST_DEBUG ("poll error %s", gst_flow_get_name (res));
goto cleanup;
}
read_error:
{
GST_ELEMENT_ERROR (obj->element, RESOURCE, READ,
(_("Error reading %d bytes from device '%s'."),
toread, obj->videodev), GST_ERROR_SYSTEM);
res = GST_FLOW_ERROR;
goto cleanup;
}
cleanup:
{
2012-01-24 13:38:58 +00:00
gst_buffer_unmap (buf, &map);
gst_buffer_resize (buf, 0, 0);
return res;
}
}
/**
* gst_v4l2_buffer_pool_process:
* @bpool: a #GstBufferPool
* @buf: a #GstBuffer, maybe be replaced
*
* Process @buf in @bpool. For capture devices, this functions fills @buf with
* data from the device. For output devices, this functions send the contents of
* @buf to the device for playback.
*
* Returns: %GST_FLOW_OK on success.
*/
GstFlowReturn
gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf)
{
GstFlowReturn ret = GST_FLOW_OK;
GstBufferPool *bpool = GST_BUFFER_POOL_CAST (pool);
GstV4l2Object *obj = pool->obj;
GST_DEBUG_OBJECT (pool, "process buffer %p", buf);
g_return_val_if_fail (gst_buffer_pool_is_active (bpool), GST_FLOW_ERROR);
switch (obj->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
/* capture */
switch (obj->mode) {
case GST_V4L2_IO_RW:
/* capture into the buffer */
ret = gst_v4l2_do_read (pool, *buf);
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_DMABUF:
{
GstBuffer *tmp;
if ((*buf)->pool == bpool) {
if (gst_buffer_get_size (*buf) == 0)
goto eos;
else
/* nothing, data was inside the buffer when we did _acquire() */
goto done;
}
/* buffer not from our pool, grab a frame and copy it into the target */
if ((ret = gst_v4l2_buffer_pool_dqbuf (pool, &tmp)) != GST_FLOW_OK)
goto done;
/* An empty buffer on capture indicates the end of stream */
if (gst_buffer_get_size (tmp) == 0) {
gst_v4l2_buffer_pool_release_buffer (bpool, tmp);
goto eos;
}
ret = gst_v4l2_buffer_pool_copy_buffer (pool, *buf, tmp);
/* an queue the buffer again after the copy */
gst_v4l2_buffer_pool_release_buffer (bpool, tmp);
if (ret != GST_FLOW_OK)
goto copy_failed;
break;
}
case GST_V4L2_IO_USERPTR:
{
struct UserPtrData *data;
/* Replace our buffer with downstream allocated buffer */
data = gst_mini_object_steal_qdata (GST_MINI_OBJECT (*buf),
GST_V4L2_IMPORT_QUARK);
gst_buffer_replace (buf, data->buffer);
_unmap_userptr_frame (data);
break;
}
2014-04-14 16:19:39 +00:00
case GST_V4L2_IO_DMABUF_IMPORT:
{
GstBuffer *tmp;
/* Replace our buffer with downstream allocated buffer */
tmp = gst_mini_object_steal_qdata (GST_MINI_OBJECT (*buf),
GST_V4L2_IMPORT_QUARK);
gst_buffer_replace (buf, tmp);
gst_buffer_unref (tmp);
break;
}
default:
g_assert_not_reached ();
break;
}
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
/* playback */
switch (obj->mode) {
case GST_V4L2_IO_RW:
/* FIXME, do write() */
GST_WARNING_OBJECT (pool, "implement write()");
break;
case GST_V4L2_IO_USERPTR:
case GST_V4L2_IO_DMABUF_IMPORT:
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_MMAP:
{
GstBuffer *to_queue;
if ((*buf)->pool == bpool) {
/* nothing, we can queue directly */
to_queue = gst_buffer_ref (*buf);
GST_LOG_OBJECT (pool, "processing buffer from our pool");
} else {
GstBufferPoolAcquireParams params = { 0 };
GST_LOG_OBJECT (pool, "alloc buffer from our pool");
/* this can return EOS if all buffers are outstanding which would
* be strange because we would expect the upstream element to have
* allocated them and returned to us.. */
params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT;
ret = gst_buffer_pool_acquire_buffer (bpool, &to_queue, &params);
if (ret != GST_FLOW_OK)
goto acquire_failed;
ret = gst_v4l2_buffer_pool_prepare_buffer (pool, to_queue, *buf);
if (ret != GST_FLOW_OK) {
gst_buffer_unref (to_queue);
goto prepare_failed;
}
}
if ((ret = gst_v4l2_buffer_pool_qbuf (pool, to_queue)) != GST_FLOW_OK)
goto queue_failed;
/* if we are not streaming yet (this is the first buffer, start
* streaming now */
if (!pool->streaming) {
if (!start_streaming (pool)) {
gst_buffer_unref (to_queue);
goto start_failed;
}
}
if (pool->num_queued ==
gst_v4l2_allocator_num_allocated (pool->vallocator)) {
v4l2: rework sink buffer refcounting This is a followup patch for #700781, which is not quite correct. The buffer handling is quite complicated here. The original code intended to the the following: - gst_v4l2_buffer_pool_process() calls QBUF and adds the buffer to the local list. - The sink calls gst_buffer_unref() which returns the buffer to the pool but not the 'free list'. - Some time later DQBUF returns the buffer and gst_v4l2_buffer_pool_release_buffer() puts in on the 'free list'. If the buffer must be copied then (parent_class)->acquire_buffer() is called directly to keep the buffer in the pool. This has two problems: 1. If gst_v4l2_buffer_pool_release_buffer() is called before the buffer is returned to the pool, then the buffer is put on the 'free list' twice. This can happen if a reference to the buffer is kept outside the sink, of if DQBUF returns the buffer, that was just queued with QBUF. 2. If buffers are copied, then all buffers are in the pool at all times. As a result gst_v4l2_buffer_pool_stop() and gst_v4l2_buffer_pool_dqbuf() can access pool->buffers at the same time, which can lead to memory corruption. The patch for #700781 fixes those problems, but with the side effect that there are always buffers outside the pool (because they are queued) and the pool is never stopped. This patch fixes this by releasing the reference to the buffer after handling it (to avoid problem 2.) so it can be returned to the pool. gst_v4l2_buffer_pool_release_buffer() is only called if the buffer is already in the pool (to avoid problem 1.). Fixes https://bugzilla.gnome.org/show_bug.cgi?id=701375
2013-05-28 17:14:15 +00:00
GstBuffer *out;
/* all buffers are queued, try to dequeue one and release it back
* into the pool so that _acquire can get to it again. */
v4l2: rework sink buffer refcounting This is a followup patch for #700781, which is not quite correct. The buffer handling is quite complicated here. The original code intended to the the following: - gst_v4l2_buffer_pool_process() calls QBUF and adds the buffer to the local list. - The sink calls gst_buffer_unref() which returns the buffer to the pool but not the 'free list'. - Some time later DQBUF returns the buffer and gst_v4l2_buffer_pool_release_buffer() puts in on the 'free list'. If the buffer must be copied then (parent_class)->acquire_buffer() is called directly to keep the buffer in the pool. This has two problems: 1. If gst_v4l2_buffer_pool_release_buffer() is called before the buffer is returned to the pool, then the buffer is put on the 'free list' twice. This can happen if a reference to the buffer is kept outside the sink, of if DQBUF returns the buffer, that was just queued with QBUF. 2. If buffers are copied, then all buffers are in the pool at all times. As a result gst_v4l2_buffer_pool_stop() and gst_v4l2_buffer_pool_dqbuf() can access pool->buffers at the same time, which can lead to memory corruption. The patch for #700781 fixes those problems, but with the side effect that there are always buffers outside the pool (because they are queued) and the pool is never stopped. This patch fixes this by releasing the reference to the buffer after handling it (to avoid problem 2.) so it can be returned to the pool. gst_v4l2_buffer_pool_release_buffer() is only called if the buffer is already in the pool (to avoid problem 1.). Fixes https://bugzilla.gnome.org/show_bug.cgi?id=701375
2013-05-28 17:14:15 +00:00
ret = gst_v4l2_buffer_pool_dqbuf (pool, &out);
if (ret == GST_FLOW_OK)
/* release the rendered buffer back into the pool. This wakes up any
* thread waiting for a buffer in _acquire(). */
gst_buffer_unref (out);
}
break;
}
default:
g_assert_not_reached ();
break;
}
break;
default:
g_assert_not_reached ();
break;
}
done:
return ret;
/* ERRORS */
copy_failed:
{
GST_ERROR_OBJECT (obj->element, "failed to copy buffer");
return ret;
}
eos:
{
GST_DEBUG_OBJECT (obj->element, "end of stream reached");
return GST_FLOW_EOS;
}
acquire_failed:
{
GST_WARNING_OBJECT (obj->element, "failed to acquire a buffer: %s",
gst_flow_get_name (ret));
return ret;
}
prepare_failed:
{
GST_ERROR_OBJECT (obj->element, "failed to prepare data");
return ret;
}
queue_failed:
{
GST_ERROR_OBJECT (obj->element, "failed to queue buffer");
return ret;
}
start_failed:
{
GST_ERROR_OBJECT (obj->element, "failed to start streaming");
return GST_FLOW_ERROR;
}
}
/**
* gst_v4l2_buffer_pool_flush:
* @bpool: a #GstBufferPool
*
* First, set obj->poll to be flushing
* Call STREAMOFF to clear QUEUED flag on every driver buffers.
* Then release all buffers that are in pool->buffers array.
* Finally call STREAMON if CAPTURE type
* The caller is responsible to unset flushing on obj->pool
*
* Returns: TRUE on success.
*/
gboolean
gst_v4l2_buffer_pool_flush (GstV4l2BufferPool * pool)
{
GstV4l2Object *obj = pool->obj;
GST_DEBUG_OBJECT (pool, "flush");
stop_streaming (pool);
/* we can start capturing now, we wait for the playback
* case until we queued the first buffer */
if (!V4L2_TYPE_IS_OUTPUT (obj->type))
if (!start_streaming (pool))
goto start_failed;
return TRUE;
/* ERRORS */
start_failed:
{
GST_ERROR_OBJECT (pool, "failed to start streaming");
return FALSE;
}
}
void
gst_v4l2_buffer_pool_set_other_pool (GstV4l2BufferPool * pool,
GstBufferPool * other_pool)
{
g_return_if_fail (!gst_buffer_pool_is_active (GST_BUFFER_POOL (pool)));
if (pool->other_pool)
gst_object_unref (pool->other_pool);
pool->other_pool = gst_object_ref (other_pool);
}