gstreamer/sys/v4l2/gstv4l2bufferpool.c
Nicolas Dufresne 319efc3e20 v4l2pool: Fix CREATE_BUFS support for capture
This patch fixes CREATE_BUFS support for capture devices. Initially we
would only try and allocate more buffers when the copy threshold
is reached. When the threshold was not set (needed) it would never
happen. Another problem is that on capture side, acquire returns
filled buffer, hence need to pool. We need to set a special flag to
force allocation to happen.

https://bugzilla.gnome.org/show_bug.cgi?id=741134
2014-12-04 17:00:25 -05:00

1863 lines
50 KiB
C

/* 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"
#include "gst/video/gstvideometa.h"
#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_BUFFER_POOL_ACQUIRE_FLAG_RESURRECT =
GST_BUFFER_POOL_ACQUIRE_FLAG_LAST,
GST_V4L2_BUFFER_POOL_ACQUIRE_FLAG_LAST
};
static void gst_v4l2_buffer_pool_release_buffer (GstBufferPool * bpool,
GstBuffer * buffer);
static gboolean
gst_v4l2_is_buffer_valid (GstBuffer * buffer, GstV4l2MemoryGroup ** out_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 = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
GST_V4L2_MEMORY_QUARK);
if (mem && gst_is_v4l2_memory (mem)) {
GstV4l2Memory *vmem = (GstV4l2Memory *) mem;
GstV4l2MemoryGroup *group = vmem->group;
gint i;
if (group->n_mem != gst_buffer_n_memory (buffer))
goto done;
for (i = 0; i < group->n_mem; i++) {
if (group->mem[i] != gst_buffer_peek_memory (buffer, i))
goto done;
if (!gst_memory_is_writable (group->mem[i]))
goto done;
}
valid = TRUE;
if (out_group)
*out_group = group;
}
done:
return valid;
}
static GstFlowReturn
gst_v4l2_buffer_pool_copy_buffer (GstV4l2BufferPool * pool, GstBuffer * dest,
GstBuffer * src)
{
const GstVideoFormatInfo *finfo = pool->caps_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->caps_info, src, GST_MAP_READ))
goto invalid_buffer;
if (!gst_video_frame_map (&dest_frame, &pool->caps_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->caps_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->caps_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,
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 =
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;
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 GST_FLOW_ERROR;
}
}
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;
GstAllocator *allocator;
GstAllocationParams params;
gboolean can_allocate = FALSE;
gboolean updated = FALSE;
gboolean ret;
pool->add_videometa =
gst_buffer_pool_config_has_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
/* parse the config and keep around */
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;
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);
/* keep a GstVideoInfo with defaults for the when we need to copy */
gst_video_info_from_caps (&pool->caps_info, caps);
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
gst_v4l2_buffer_pool_streamon (GstV4l2BufferPool * pool)
{
GstV4l2Object *obj = pool->obj;
switch (obj->mode) {
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_DMABUF_IMPORT:
if (!pool->streaming) {
if (v4l2_ioctl (pool->video_fd, VIDIOC_STREAMON, &obj->type) < 0)
goto streamon_failed;
pool->streaming = TRUE;
GST_DEBUG_OBJECT (pool, "Started streaming");
}
break;
default:
break;
}
return TRUE;
streamon_failed:
{
GST_ERROR_OBJECT (pool, "error with STREAMON %d (%s)", errno,
g_strerror (errno));
return FALSE;
}
}
static gboolean
gst_v4l2_buffer_pool_streamoff (GstV4l2BufferPool * pool)
{
GstV4l2Object *obj = pool->obj;
switch (obj->mode) {
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_DMABUF_IMPORT:
if (pool->streaming) {
if (v4l2_ioctl (pool->video_fd, VIDIOC_STREAMOFF, &obj->type) < 0)
goto streamoff_failed;
pool->streaming = FALSE;
GST_DEBUG_OBJECT (pool, "Stopped streaming");
}
break;
default:
break;
}
return TRUE;
streamoff_failed:
{
GST_ERROR_OBJECT (pool, "error with STREAMOFF %d (%s)", errno,
g_strerror (errno));
return FALSE;
}
}
static GstFlowReturn
gst_v4l2_buffer_pool_resurect_buffer (GstV4l2BufferPool * pool)
{
GstBufferPoolAcquireParams params = { 0 };
GstBuffer *buffer = NULL;
GstFlowReturn ret;
GST_DEBUG_OBJECT (pool, "A buffer was lost, reallocating it");
params.flags =
(GstBufferPoolAcquireFlags) GST_V4L2_BUFFER_POOL_ACQUIRE_FLAG_RESURRECT;
ret =
gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL (pool), &buffer, &params);
if (ret == GST_FLOW_OK)
gst_buffer_unref (buffer);
return ret;
}
static gboolean
gst_v4l2_buffer_pool_start (GstBufferPool * bpool)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstBufferPoolClass *pclass = GST_BUFFER_POOL_CLASS (parent_class);
GstV4l2Object *obj = pool->obj;
GstStructure *config;
GstCaps *caps;
guint size, min_buffers, max_buffers;
guint max_latency, min_latency, copy_threshold = 0;
gboolean can_allocate = FALSE;
GST_DEBUG_OBJECT (pool, "activating pool");
config = gst_buffer_pool_get_config (bpool);
if (!gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers,
&max_buffers))
goto wrong_config;
min_latency = MAX (GST_V4L2_MIN_BUFFERS, obj->min_buffers);
switch (obj->mode) {
case GST_V4L2_IO_RW:
can_allocate = TRUE;
break;
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_MMAP:
{
guint count;
can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, MMAP);
/* first, lets request buffers, and see how many we can get: */
GST_DEBUG_OBJECT (pool, "requesting %d MMAP buffers", min_buffers);
count = gst_v4l2_allocator_start (pool->vallocator, min_buffers,
V4L2_MEMORY_MMAP);
if (count < GST_V4L2_MIN_BUFFERS) {
min_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 != min_buffers) {
GST_WARNING_OBJECT (pool, "using %u buffers instead of %u",
count, min_buffers);
min_buffers = count;
copy_threshold = min_latency;
/* The initial minimum could be provide either by GstBufferPool or
* driver needs. */
min_buffers = count;
}
break;
}
case GST_V4L2_IO_USERPTR:
{
guint count;
can_allocate =
GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, USERPTR);
GST_DEBUG_OBJECT (pool, "requesting %d USERPTR buffers", min_buffers);
count = gst_v4l2_allocator_start (pool->vallocator, min_buffers,
V4L2_MEMORY_USERPTR);
/* There is no rational to not get what we asked */
if (count < min_buffers) {
min_buffers = count;
goto no_buffers;
}
min_buffers = count;
break;
}
case GST_V4L2_IO_DMABUF_IMPORT:
{
guint count;
can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, DMABUF);
GST_DEBUG_OBJECT (pool, "requesting %d DMABUF buffers", min_buffers);
count = gst_v4l2_allocator_start (pool->vallocator, min_buffers,
V4L2_MEMORY_DMABUF);
/* There is no rational to not get what we asked */
if (count < min_buffers) {
min_buffers = count;
goto no_buffers;
}
min_buffers = count;
break;
}
default:
min_buffers = 0;
copy_threshold = 0;
g_assert_not_reached ();
break;
}
if (can_allocate)
max_latency = max_buffers;
else
max_latency = min_buffers;
pool->size = size;
pool->copy_threshold = copy_threshold;
pool->max_latency = max_latency;
pool->min_latency = min_latency;
pool->num_queued = 0;
if (max_buffers < min_buffers)
max_buffers = min_buffers;
gst_buffer_pool_config_set_params (config, caps, size, min_buffers,
max_buffers);
pclass->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 (!pclass->start (bpool))
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_resurect_buffer), pool);
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",
min_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
gst_v4l2_buffer_pool_stop (GstBufferPool * bpool)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstBufferPoolClass *pclass = GST_BUFFER_POOL_CLASS (parent_class);
gboolean ret;
gint i;
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;
}
if (pool->other_pool) {
gst_object_unref (pool->other_pool);
pool->other_pool = NULL;
}
if (!gst_v4l2_buffer_pool_streamoff (pool))
goto streamoff_failed;
if (pool->vallocator)
gst_v4l2_allocator_flush (pool->vallocator);
for (i = 0; i < VIDEO_MAX_FRAME; i++) {
if (pool->buffers[i]) {
GstBuffer *buffer = pool->buffers[i];
pool->buffers[i] = NULL;
if (V4L2_TYPE_IS_OUTPUT (pool->obj->type))
gst_buffer_unref (buffer);
else
pclass->release_buffer (bpool, buffer);
g_atomic_int_add (&pool->num_queued, -1);
}
}
ret = GST_BUFFER_POOL_CLASS (parent_class)->stop (bpool);
if (ret && pool->vallocator) {
GstV4l2Return vret;
vret = gst_v4l2_allocator_stop (pool->vallocator);
if (vret == GST_V4L2_BUSY)
GST_WARNING_OBJECT (pool, "some buffers are still outstanding");
ret = (vret == GST_V4L2_OK);
}
return ret;
/* ERRORS */
streamoff_failed:
GST_ERROR_OBJECT (pool, "device refused to stop streaming");
return FALSE;
}
static void
gst_v4l2_buffer_pool_flush_start (GstBufferPool * bpool)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GST_DEBUG_OBJECT (pool, "start flushing");
gst_poll_set_flushing (pool->poll, TRUE);
GST_OBJECT_LOCK (pool);
pool->empty = FALSE;
g_cond_broadcast (&pool->empty_cond);
GST_OBJECT_UNLOCK (pool);
if (pool->other_pool)
gst_buffer_pool_set_flushing (pool->other_pool, TRUE);
}
static void
gst_v4l2_buffer_pool_flush_stop (GstBufferPool * bpool)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
gint i;
GST_DEBUG_OBJECT (pool, "stop flushing");
/* If we haven't started streaming yet, simply call streamon */
if (!pool->streaming)
goto streamon;
if (pool->other_pool)
gst_buffer_pool_set_flushing (pool->other_pool, FALSE);
if (!gst_v4l2_buffer_pool_streamoff (pool))
goto stop_failed;
gst_v4l2_allocator_flush (pool->vallocator);
/* Reset our state */
switch (obj->mode) {
case GST_V4L2_IO_RW:
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
case GST_V4L2_IO_DMABUF:
case GST_V4L2_IO_DMABUF_IMPORT:
{
gsize num_allocated;
num_allocated = gst_v4l2_allocator_num_allocated (pool->vallocator);
for (i = 0; i < num_allocated; i++) {
/* Re-enqueue buffers */
if (pool->buffers[i]) {
GstBufferPool *bpool = (GstBufferPool *) pool;
GstBuffer *buffer = pool->buffers[i];
pool->buffers[i] = NULL;
/* Remove qdata, this will unmap any map data in
* userptr/dmabuf-import */
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
gst_v4l2_buffer_pool_release_buffer (bpool, buffer);
g_atomic_int_add (&pool->num_queued, -1);
}
}
break;
}
default:
g_assert_not_reached ();
break;
}
streamon:
/* Start streaming on capture device only */
if (!V4L2_TYPE_IS_OUTPUT (obj->type))
gst_v4l2_buffer_pool_streamon (pool);
gst_poll_set_flushing (pool->poll, FALSE);
return;
/* ERRORS */
stop_failed:
{
GST_ERROR_OBJECT (pool, "device refused to flush");
}
}
static GstFlowReturn
gst_v4l2_buffer_pool_poll (GstV4l2BufferPool * pool)
{
gint ret;
GST_OBJECT_LOCK (pool);
while (pool->empty)
g_cond_wait (&pool->empty_cond, GST_OBJECT_GET_LOCK (pool));
GST_OBJECT_UNLOCK (pool);
if (!pool->can_poll_device)
goto done;
GST_LOG_OBJECT (pool, "polling device");
again:
ret = gst_poll_wait (pool->poll, GST_CLOCK_TIME_NONE);
if (G_UNLIKELY (ret < 0)) {
switch (errno) {
case EBUSY:
goto stopped;
case EAGAIN:
case EINTR:
goto again;
case ENXIO:
GST_WARNING_OBJECT (pool,
"v4l2 device doesn't support polling. Disabling"
" using libv4l2 in this case may cause deadlocks");
pool->can_poll_device = FALSE;
goto done;
default:
goto select_error;
}
}
if (gst_poll_fd_has_error (pool->poll, &pool->pollfd))
goto select_error;
done:
return GST_FLOW_OK;
/* ERRORS */
stopped:
{
GST_DEBUG_OBJECT (pool, "stop called");
return GST_FLOW_FLUSHING;
}
select_error:
{
GST_ELEMENT_ERROR (pool->obj->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);
g_atomic_int_inc (&pool->num_queued);
pool->buffers[index] = buf;
if (!gst_v4l2_allocator_qbuf (pool->vallocator, group))
goto queue_failed;
GST_OBJECT_LOCK (pool);
pool->empty = FALSE;
g_cond_signal (&pool->empty_cond);
GST_OBJECT_UNLOCK (pool);
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);
g_atomic_int_add (&pool->num_queued, -1);
pool->buffers[index] = NULL;
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_buffer_pool_poll (pool)) != 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;
if (g_atomic_int_dec_and_test (&pool->num_queued)) {
GST_OBJECT_LOCK (pool);
pool->empty = TRUE;
GST_OBJECT_UNLOCK (pool);
}
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 */
switch (group->buffer.field) {
case V4L2_FIELD_NONE:
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_FLAG_INTERLACED);
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_FLAG_TFF);
break;
case V4L2_FIELD_INTERLACED_TB:
GST_BUFFER_FLAG_SET (outbuf, GST_VIDEO_BUFFER_FLAG_INTERLACED);
GST_BUFFER_FLAG_SET (outbuf, GST_VIDEO_BUFFER_FLAG_TFF);
break;
case V4L2_FIELD_INTERLACED_BT:
GST_BUFFER_FLAG_SET (outbuf, GST_VIDEO_BUFFER_FLAG_INTERLACED);
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_FLAG_TFF);
break;
default:
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_FLAG_INTERLACED);
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_FLAG_TFF);
GST_FIXME_OBJECT (pool,
"Unhandled enum v4l2_field %d - treating as progressive",
group->buffer.field);
}
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);
}
if (group->buffer.flags & V4L2_BUF_FLAG_ERROR)
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_CORRUPTED);
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,
GstBufferPoolAcquireParams * params)
{
GstFlowReturn ret;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstBufferPoolClass *pclass = GST_BUFFER_POOL_CLASS (parent_class);
GstV4l2Object *obj = pool->obj;
GST_DEBUG_OBJECT (pool, "acquire");
/* If this is being called to resurect a lost buffer */
if (params && params->flags & GST_V4L2_BUFFER_POOL_ACQUIRE_FLAG_RESURRECT) {
ret = pclass->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 = pclass->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;
break;
}
case GST_V4L2_IO_USERPTR:
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 = pclass->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 = pclass->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;
}
static void
gst_v4l2_buffer_pool_release_buffer (GstBufferPool * bpool, GstBuffer * buffer)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstBufferPoolClass *pclass = GST_BUFFER_POOL_CLASS (parent_class);
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 */
pclass->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 (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)
pclass->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);
pclass->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 */
pclass->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;
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);
pclass->release_buffer (bpool, buffer);
break;
}
index = group->buffer.index;
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. */
pclass->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_dispose (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->vallocator)
gst_object_unref (pool->vallocator);
pool->vallocator = NULL;
if (pool->allocator)
gst_object_unref (pool->allocator);
pool->allocator = NULL;
if (pool->other_pool)
gst_object_unref (pool->other_pool);
pool->other_pool = NULL;
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_v4l2_buffer_pool_finalize (GObject * object)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (object);
if (pool->video_fd >= 0)
v4l2_close (pool->video_fd);
gst_poll_free (pool->poll);
/* FIXME Is this required to keep around ?
* This can't be done in dispose method because we must not set pointer
* to NULL as it is part of the v4l2object and dispose could be called
* multiple times */
gst_object_unref (pool->obj->element);
g_cond_clear (&pool->empty_cond);
/* FIXME have we done enough here ? */
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_v4l2_buffer_pool_init (GstV4l2BufferPool * pool)
{
pool->poll = gst_poll_new (TRUE);
pool->can_poll_device = TRUE;
g_cond_init (&pool->empty_cond);
pool->empty = TRUE;
}
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->dispose = gst_v4l2_buffer_pool_dispose;
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;
bufferpool_class->flush_start = gst_v4l2_buffer_pool_flush_start;
bufferpool_class->flush_stop = gst_v4l2_buffer_pool_flush_stop;
}
/**
* 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;
gint fd;
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);
gst_poll_fd_init (&pool->pollfd);
pool->pollfd.fd = fd;
gst_poll_add_fd (pool->poll, &pool->pollfd);
if (V4L2_TYPE_IS_OUTPUT (obj->type))
gst_poll_fd_ctl_write (pool->poll, &pool->pollfd, TRUE);
else
gst_poll_fd_ctl_read (pool->poll, &pool->pollfd, TRUE);
pool->video_fd = fd;
pool->obj = obj;
pool->can_poll_device = TRUE;
pool->vallocator =
gst_v4l2_allocator_new (GST_OBJECT (pool), obj->video_fd, &obj->format);
if (pool->vallocator == NULL)
goto allocator_failed;
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;
}
allocator_failed:
{
GST_ERROR_OBJECT (pool, "Failed to create V4L2 allocator");
gst_object_unref (pool);
return NULL;
}
}
static GstFlowReturn
gst_v4l2_do_read (GstV4l2BufferPool * pool, GstBuffer * buf)
{
GstFlowReturn res;
GstV4l2Object *obj = pool->obj;
gint amount;
GstMapInfo map;
gint toread;
toread = obj->info.size;
GST_LOG_OBJECT (pool, "reading %d bytes into buffer %p", toread, buf);
gst_buffer_map (buf, &map, GST_MAP_WRITE);
do {
if ((res = gst_v4l2_buffer_pool_poll (pool)) != GST_FLOW_OK)
goto poll_error;
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);
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:
{
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);
if (GST_BUFFER_POOL_IS_FLUSHING (pool))
return GST_FLOW_FLUSHING;
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) {
guint num_queued;
if (gst_buffer_get_size (*buf) == 0) {
if (GST_BUFFER_FLAG_IS_SET (*buf, GST_BUFFER_FLAG_CORRUPTED))
goto buffer_corrupted;
else
goto eos;
}
num_queued = g_atomic_int_get (&pool->num_queued);
GST_TRACE_OBJECT (pool, "Only %i buffer left in the capture queue.",
num_queued);
/* If we have no more buffer, and can allocate it time to do so */
if (num_queued == 0) {
if (GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, MMAP)) {
ret = gst_v4l2_buffer_pool_resurect_buffer (pool);
if (ret == GST_FLOW_OK)
goto done;
}
}
/* start copying buffers when we are running low on buffers */
if (num_queued < pool->copy_threshold) {
GstBuffer *copy;
if (GST_V4L2_ALLOCATOR_CAN_ALLOCATE (pool->vallocator, MMAP)) {
ret = gst_v4l2_buffer_pool_resurect_buffer (pool);
if (ret == GST_FLOW_OK)
goto done;
}
/* copy the buffer */
copy = gst_buffer_copy_region (*buf,
GST_BUFFER_COPY_ALL | GST_BUFFER_COPY_DEEP, 0, -1);
GST_LOG_OBJECT (pool, "copy buffer %p->%p", *buf, copy);
/* and requeue so that we can continue capturing */
gst_buffer_unref (*buf);
*buf = copy;
}
/* 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);
if (GST_BUFFER_FLAG_IS_SET (*buf, GST_BUFFER_FLAG_CORRUPTED))
goto buffer_corrupted;
else
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;
}
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 = NULL;
GstV4l2MemoryGroup *group;
gint index;
if ((*buf)->pool != bpool)
goto copying;
if (!gst_v4l2_is_buffer_valid (*buf, &group))
goto copying;
index = group->buffer.index;
GST_LOG_OBJECT (pool, "processing buffer %i from our pool", index);
index = group->buffer.index;
if (pool->buffers[index] != NULL) {
GST_LOG_OBJECT (pool, "buffer %i already queued, copying", index);
goto copying;
}
/* we can queue directly */
to_queue = gst_buffer_ref (*buf);
copying:
if (to_queue == NULL) {
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 (!gst_v4l2_buffer_pool_streamon (pool)) {
/* don't check return value because qbuf would have failed */
gst_v4l2_is_buffer_valid (to_queue, &group);
/* qbuf has taken the ref of the to_queue buffer but we are no in
* streaming state, so the flush logic won't be performed.
* To avoid leaks, flush the allocator and restore the queued
* buffer as non-queued */
gst_v4l2_allocator_flush (pool->vallocator);
pool->buffers[group->buffer.index] = NULL;
gst_mini_object_set_qdata (GST_MINI_OBJECT (to_queue),
GST_V4L2_IMPORT_QUARK, NULL, NULL);
gst_buffer_unref (to_queue);
g_atomic_int_add (&pool->num_queued, -1);
goto start_failed;
}
if (g_atomic_int_get (&pool->num_queued) >= pool->min_latency) {
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. */
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 (pool, "failed to copy buffer");
return ret;
}
buffer_corrupted:
{
GST_WARNING_OBJECT (pool, "Dropping corrupted buffer without payload");
gst_buffer_unref (*buf);
*buf = NULL;
return GST_V4L2_FLOW_CORRUPTED_BUFFER;
}
eos:
{
GST_DEBUG_OBJECT (pool, "end of stream reached");
gst_buffer_unref (*buf);
*buf = NULL;
return GST_V4L2_FLOW_LAST_BUFFER;
}
acquire_failed:
{
if (ret == GST_FLOW_FLUSHING)
GST_DEBUG_OBJECT (pool, "flushing");
else
GST_WARNING_OBJECT (pool, "failed to acquire a buffer: %s",
gst_flow_get_name (ret));
return ret;
}
prepare_failed:
{
GST_ERROR_OBJECT (pool, "failed to prepare data");
return ret;
}
queue_failed:
{
GST_ERROR_OBJECT (pool, "failed to queue buffer");
return ret;
}
start_failed:
{
GST_ERROR_OBJECT (pool, "failed to start streaming");
return GST_FLOW_ERROR;
}
}
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);
}