gstreamer/sys/v4l2/gstv4l2bufferpool.c
Wim Taymans 55eb26f1e7 v4l2: More work on bufferpools
Add different transport methods to the bufferpool (MMAP and READ/WRITE)
Do more parsing of the bufferpool config.
Start and stop streaming based on the bufferpool state.
Make separate methods for getting a buffer from the pool and filling it with
data. This allows us to fill buffers from other pools too. Either use copy or
read to fill up the target buffers.
Add property to force a transfer mode in v4l2src.
Increase default number of buffers to 4.
Negotiate bufferpool and its properties in v4l2src.
2011-07-18 18:54:49 +02:00

1012 lines
27 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include "gst/video/video.h"
#include "gst/video/gstmetavideo.h"
#include <gstv4l2bufferpool.h>
#include "gstv4l2src.h"
#include "gstv4l2sink.h"
#include "v4l2_calls.h"
#include "gst/gst-i18n-plugin.h"
/* videodev2.h is not versioned and we can't easily check for the presence
* of enum values at compile time, but the V4L2_CAP_VIDEO_OUTPUT_OVERLAY define
* was added in the same commit as V4L2_FIELD_INTERLACED_{TB,BT} (b2787845) */
#ifndef V4L2_CAP_VIDEO_OUTPUT_OVERLAY
#define V4L2_FIELD_INTERLACED_TB 8
#define V4L2_FIELD_INTERLACED_BT 9
#endif
GST_DEBUG_CATEGORY_EXTERN (v4l2_debug);
#define GST_CAT_DEFAULT v4l2_debug
/*
* GstV4l2Buffer:
*/
const GstMetaInfo *
gst_meta_v4l2_get_info (void)
{
static const GstMetaInfo *meta_info = NULL;
if (meta_info == NULL) {
meta_info =
gst_meta_register ("GstMetaV4l2", "GstMetaV4l2",
sizeof (GstMetaV4l2), (GstMetaInitFunction) NULL,
(GstMetaFreeFunction) NULL, (GstMetaCopyFunction) NULL,
(GstMetaTransformFunction) NULL);
}
return meta_info;
}
/*
* GstV4l2BufferPool:
*/
#define gst_v4l2_buffer_pool_parent_class parent_class
G_DEFINE_TYPE (GstV4l2BufferPool, gst_v4l2_buffer_pool, GST_TYPE_BUFFER_POOL);
static void gst_v4l2_buffer_pool_release_buffer (GstBufferPool * bpool,
GstBuffer * buffer);
static void
gst_v4l2_buffer_pool_free_buffer (GstBufferPool * bpool, GstBuffer * buffer)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj;
obj = pool->obj;
switch (obj->mode) {
case GST_V4L2_IO_RW:
break;
case GST_V4L2_IO_MMAP:
{
GstMetaV4l2 *meta;
gint index;
meta = GST_META_V4L2_GET (buffer);
g_assert (meta != NULL);
index = meta->vbuffer.index;
GST_LOG_OBJECT (pool,
"mmap buffer %p idx %d (data %p, len %u) freed, unmapping", buffer,
index, meta->mem, meta->vbuffer.length);
v4l2_munmap (meta->mem, meta->vbuffer.length);
pool->buffers[index] = NULL;
break;
}
case GST_V4L2_IO_USERPTR:
default:
g_assert_not_reached ();
break;
}
gst_buffer_unref (buffer);
}
static GstFlowReturn
gst_v4l2_buffer_pool_alloc_buffer (GstBufferPool * bpool, GstBuffer ** buffer,
GstBufferPoolParams * params)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstBuffer *newbuf;
GstMetaV4l2 *meta;
GstV4l2Object *obj;
GstVideoInfo *info;
guint index;
obj = pool->obj;
info = &obj->info;
switch (obj->mode) {
case GST_V4L2_IO_RW:
{
newbuf =
gst_buffer_new_allocate (pool->allocator, pool->size, pool->align);
break;
}
case GST_V4L2_IO_MMAP:
{
newbuf = gst_buffer_new ();
meta = GST_META_V4L2_ADD (newbuf);
index = pool->index;
GST_LOG_OBJECT (pool, "creating buffer %u, %p", index, newbuf, pool);
meta->vbuffer.index = index;
meta->vbuffer.type = obj->type;
meta->vbuffer.memory = V4L2_MEMORY_MMAP;
if (v4l2_ioctl (pool->video_fd, VIDIOC_QUERYBUF, &meta->vbuffer) < 0)
goto querybuf_failed;
GST_LOG_OBJECT (pool, " index: %u", meta->vbuffer.index);
GST_LOG_OBJECT (pool, " type: %d", meta->vbuffer.type);
GST_LOG_OBJECT (pool, " bytesused: %u", meta->vbuffer.bytesused);
GST_LOG_OBJECT (pool, " flags: %08x", meta->vbuffer.flags);
GST_LOG_OBJECT (pool, " field: %d", meta->vbuffer.field);
GST_LOG_OBJECT (pool, " memory: %d", meta->vbuffer.memory);
if (meta->vbuffer.memory == V4L2_MEMORY_MMAP)
GST_LOG_OBJECT (pool, " MMAP offset: %u", meta->vbuffer.m.offset);
GST_LOG_OBJECT (pool, " length: %u", meta->vbuffer.length);
GST_LOG_OBJECT (pool, " input: %u", meta->vbuffer.input);
meta->mem = v4l2_mmap (0, meta->vbuffer.length,
PROT_READ | PROT_WRITE, MAP_SHARED, pool->video_fd,
meta->vbuffer.m.offset);
if (meta->mem == MAP_FAILED)
goto mmap_failed;
gst_buffer_take_memory (newbuf, -1,
gst_memory_new_wrapped (0,
meta->mem, NULL, meta->vbuffer.length, 0, meta->vbuffer.length));
/* add metadata to raw video buffers */
if (info->finfo) {
gsize offset[GST_VIDEO_MAX_PLANES];
gint stride[GST_VIDEO_MAX_PLANES];
offset[0] = 0;
stride[0] = obj->bytesperline;
GST_DEBUG_OBJECT (pool, "adding video meta, stride %d", stride[0]);
gst_buffer_add_meta_video_full (newbuf, info->flags,
GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info),
GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_N_PLANES (info),
offset, stride);
}
break;
}
case GST_V4L2_IO_USERPTR:
default:
g_assert_not_reached ();
break;
}
pool->index++;
*buffer = newbuf;
return GST_FLOW_OK;
/* ERRORS */
querybuf_failed:
{
gint errnosave = errno;
GST_WARNING ("Failed QUERYBUF: %s", g_strerror (errnosave));
gst_buffer_unref (newbuf);
errno = errnosave;
return GST_FLOW_ERROR;
}
mmap_failed:
{
gint errnosave = errno;
GST_WARNING ("Failed to mmap: %s", g_strerror (errnosave));
gst_buffer_unref (newbuf);
errno = errnosave;
return GST_FLOW_ERROR;
}
}
static gboolean
gst_v4l2_buffer_pool_set_config (GstBufferPool * bpool, GstStructure * config)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
const GstCaps *caps;
guint size, min_buffers, max_buffers;
guint prefix, align;
GST_DEBUG_OBJECT (pool, "set config");
/* parse the config and keep around */
if (!gst_buffer_pool_config_get (config, &caps, &size, &min_buffers,
&max_buffers, &prefix, &align))
goto wrong_config;
GST_DEBUG_OBJECT (pool, "config %" GST_PTR_FORMAT, config);
pool->size = size;
pool->min_buffers = min_buffers;
pool->max_buffers = max_buffers;
pool->prefix = prefix;
pool->align = align;
return TRUE;
wrong_config:
{
GST_WARNING_OBJECT (pool, "invalid config %" GST_PTR_FORMAT, config);
return FALSE;
}
}
static gboolean
start_streaming (GstBufferPool * bpool)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
switch (obj->mode) {
case GST_V4L2_IO_RW:
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
GST_DEBUG_OBJECT (pool, "STREAMON");
if (v4l2_ioctl (pool->video_fd, VIDIOC_STREAMON, &obj->type) < 0)
goto start_failed;
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;
}
}
static gboolean
gst_v4l2_buffer_pool_start (GstBufferPool * bpool)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
gint n;
struct v4l2_requestbuffers breq;
gint num_buffers;
num_buffers = pool->min_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",
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;
}
break;
}
case GST_V4L2_IO_USERPTR:
default:
g_assert_not_reached ();
break;
}
pool->obj = obj;
pool->num_buffers = num_buffers;
pool->buffers = g_new0 (GstBuffer *, num_buffers);
pool->index = 0;
/* now, allocate the buffers: */
for (n = 0; n < num_buffers; n++) {
GstBuffer *buffer;
if (gst_v4l2_buffer_pool_alloc_buffer (bpool, &buffer, NULL) != GST_FLOW_OK)
goto buffer_new_failed;
gst_v4l2_buffer_pool_release_buffer (bpool, buffer);
}
/* we can start capturing now, we wait for the playback case until we queued
* the first buffer */
if (obj->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
if (!start_streaming (bpool))
goto start_failed;
return TRUE;
/* ERRORS */
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;
}
buffer_new_failed:
{
GST_ERROR_OBJECT (pool, "failed to create a buffer");
return FALSE;
}
start_failed:
{
GST_ERROR_OBJECT (pool, "failed to start streaming");
return FALSE;
}
}
static gboolean
gst_v4l2_buffer_pool_stop (GstBufferPool * bpool)
{
gboolean ret;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
guint n;
GST_DEBUG_OBJECT (pool, "stopping pool");
switch (obj->mode) {
case GST_V4L2_IO_RW:
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
/* we actually need to sync on all queued buffers but not
* on the non-queued ones */
GST_DEBUG_OBJECT (pool, "STREAMOFF");
if (v4l2_ioctl (pool->video_fd, VIDIOC_STREAMOFF, &obj->type) < 0)
goto stop_failed;
break;
default:
g_assert_not_reached ();
break;
}
pool->streaming = FALSE;
/* first free the buffers in the queue */
ret = GST_BUFFER_POOL_CLASS (parent_class)->stop (bpool);
/* then free the remaining buffers */
for (n = 0; n < pool->num_buffers; n++) {
if (pool->buffers[n])
gst_v4l2_buffer_pool_free_buffer (bpool, pool->buffers[n]);
}
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) {
ret = gst_poll_wait (v4l2object->poll, GST_CLOCK_TIME_NONE);
if (G_UNLIKELY (ret < 0)) {
if (errno == EBUSY)
goto stopped;
if (errno == ENXIO) {
GST_DEBUG_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_WRONG_STATE;
}
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 (GstBufferPool * bpool, GstBuffer * buf)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstMetaV4l2 *meta;
gint index;
meta = GST_META_V4L2_GET (buf);
g_assert (meta != NULL);
index = meta->vbuffer.index;
GST_LOG_OBJECT (pool, "enqueue pool buffer %d, queued: %d", index,
pool->num_queued);
if (pool->buffers[index] != NULL)
goto already_queued;
if (v4l2_ioctl (pool->video_fd, VIDIOC_QBUF, &meta->vbuffer) < 0)
goto queue_failed;
pool->buffers[index] = buf;
pool->num_queued++;
return GST_FLOW_OK;
/* ERRORS */
already_queued:
{
GST_WARNING_OBJECT (pool, "the buffer was already queued");
return GST_FLOW_ERROR;
}
queue_failed:
{
GST_WARNING_OBJECT (pool, "could not queue a buffer");
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_v4l2_buffer_pool_dqbuf (GstBufferPool * bpool, GstBuffer ** buffer)
{
GstFlowReturn res;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstBuffer *outbuf;
struct v4l2_buffer vbuffer;
GstV4l2Object *obj = pool->obj;
if ((res = gst_v4l2_object_poll (obj)) != GST_FLOW_OK)
goto poll_error;
memset (&vbuffer, 0x00, sizeof (vbuffer));
vbuffer.type = obj->type;
vbuffer.memory = V4L2_MEMORY_MMAP;
if (v4l2_ioctl (pool->video_fd, VIDIOC_DQBUF, &vbuffer) < 0)
goto error;
/* get our GstBuffer with that index from the pool, if the buffer was
* outstanding we have a serious problem.
*/
outbuf = pool->buffers[vbuffer.index];
if (outbuf == NULL)
goto no_buffers;
/* mark the buffer outstanding */
pool->buffers[vbuffer.index] = NULL;
pool->num_queued--;
GST_LOG_OBJECT (pool,
"dequeued frame %d (ix=%d), used %d, flags %08x, pool-queued=%d, buffer=%p",
vbuffer.sequence, vbuffer.index, vbuffer.bytesused, vbuffer.flags,
pool->num_queued, outbuf);
/* set top/bottom field first if v4l2_buffer has the information */
if (vbuffer.field == V4L2_FIELD_INTERLACED_TB)
GST_BUFFER_FLAG_SET (outbuf, GST_VIDEO_BUFFER_TFF);
if (vbuffer.field == V4L2_FIELD_INTERLACED_BT)
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_TFF);
/* this can change at every frame, esp. with jpeg */
if (obj->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
gst_buffer_resize (outbuf, 0, vbuffer.bytesused);
else
gst_buffer_resize (outbuf, 0, vbuffer.length);
*buffer = outbuf;
return GST_FLOW_OK;
/* ERRORS */
poll_error:
{
GST_DEBUG ("poll error %s", gst_flow_get_name (res));
return res;
}
error:
{
GST_WARNING_OBJECT (pool,
"problem dequeuing frame %d (ix=%d), pool-ct=%d, buf.flags=%d",
vbuffer.sequence, vbuffer.index,
GST_MINI_OBJECT_REFCOUNT (pool), vbuffer.flags);
switch (errno) {
case EAGAIN:
GST_WARNING_OBJECT (pool,
"Non-blocking I/O has been selected using O_NONBLOCK and"
" no buffer was in the outgoing queue. device %s", obj->videodev);
break;
case EINVAL:
GST_ERROR_OBJECT (pool,
"The buffer type is not supported, or the index is out of bounds, "
"or no buffers have been allocated yet, or the userptr "
"or length are invalid. device %s", obj->videodev);
break;
case ENOMEM:
GST_ERROR_OBJECT (pool,
"insufficient memory to enqueue a user pointer buffer");
break;
case EIO:
GST_INFO_OBJECT (pool,
"VIDIOC_DQBUF failed due to an internal error."
" Can also indicate temporary problems like signal loss."
" Note the driver might dequeue an (empty) buffer despite"
" returning an error, or even stop capturing."
" device %s", obj->videodev);
/* have we de-queued a buffer ? */
if (!(vbuffer.flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE))) {
GST_DEBUG_OBJECT (pool, "reenqueing buffer");
/* FIXME ... should we do something here? */
}
break;
case EINTR:
GST_WARNING_OBJECT (pool,
"could not sync on a buffer on device %s", obj->videodev);
break;
default:
GST_WARNING_OBJECT (pool,
"Grabbing frame got interrupted on %s unexpectedly. %d: %s.",
obj->videodev, errno, g_strerror (errno));
break;
}
return GST_FLOW_ERROR;
}
no_buffers:
{
GST_ERROR_OBJECT (pool, "No free buffers found in the pool at index %d.",
vbuffer.index);
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_v4l2_buffer_pool_acquire_buffer (GstBufferPool * bpool, GstBuffer ** buffer,
GstBufferPoolParams * 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;
switch (obj->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
/* 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_MMAP:
/* just dequeue a buffer, we basically use the queue of v4l2 as the
* storage for our buffers. */
ret = gst_v4l2_buffer_pool_dqbuf (bpool, buffer);
break;
case GST_V4L2_IO_USERPTR:
default:
g_assert_not_reached ();
break;
}
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
/* 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:
/* first try to get a free unqueued buffer */
ret = GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool,
buffer, params);
if (ret == GST_FLOW_UNEXPECTED) {
/* all buffers are queued, try to dequeue one */
ret = gst_v4l2_buffer_pool_dqbuf (bpool, buffer);
}
break;
case GST_V4L2_IO_USERPTR:
default:
g_assert_not_reached ();
break;
}
break;
default:
g_assert_not_reached ();
break;
}
return ret;
/* ERRORS */
flushing:
{
GST_DEBUG_OBJECT (bpool, "We are flushing");
return GST_FLOW_WRONG_STATE;
}
}
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");
switch (obj->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
/* 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_MMAP:
/* queue back in the device */
gst_v4l2_buffer_pool_qbuf (bpool, buffer);
break;
case GST_V4L2_IO_USERPTR:
default:
g_assert_not_reached ();
break;
}
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
/* playback, put the buffer back in the queue to refill later. */
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool, buffer);
break;
default:
g_assert_not_reached ();
break;
}
}
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);
if (pool->buffers) {
g_free (pool->buffers);
pool->buffers = NULL;
}
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;
bufferpool_class->free_buffer = gst_v4l2_buffer_pool_free_buffer;
}
/**
* gst_v4l2_buffer_pool_new:
* @obj: the v4l2 object owning the pool
* @num_buffers: the requested number of buffers in the pool
* @requeuebuf: if %TRUE, and if the pool is still in the running state, a
* buffer with no remaining references is immediately passed back to v4l2
* (VIDIOC_QBUF), otherwise it is returned to the pool of available buffers
* (which can be accessed via gst_v4l2_buffer_pool_get().
*
* Construct a new buffer pool.
*
* Returns: the new pool, use gst_v4l2_buffer_pool_destroy() to free resources
*/
GstBufferPool *
gst_v4l2_buffer_pool_new (GstV4l2Object * obj)
{
GstV4l2BufferPool *pool;
gint fd;
fd = v4l2_dup (obj->video_fd);
if (fd < 0)
goto dup_failed;
pool = (GstV4l2BufferPool *) g_object_new (GST_TYPE_V4L2_BUFFER_POOL, NULL);
pool->video_fd = fd;
pool->obj = obj;
return GST_BUFFER_POOL_CAST (pool);
/* ERRORS */
dup_failed:
{
GST_DEBUG ("failed to dup fd %d (%s)", errno, g_strerror (errno));
return NULL;
}
}
static GstFlowReturn
gst_v4l2_do_read (GstBufferPool * bpool, GstBuffer * buf)
{
GstFlowReturn res;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
gint amount;
gpointer data;
gint buffersize;
buffersize = gst_buffer_get_size (buf);
GST_LOG_OBJECT (pool, "reading %d bytes into buffer %p", buffersize, buf);
data = gst_buffer_map (buf, NULL, NULL, GST_MAP_WRITE);
do {
if ((res = gst_v4l2_object_poll (obj)) != GST_FLOW_OK)
goto poll_error;
amount = v4l2_read (obj->video_fd, data, buffersize);
if (amount == buffersize) {
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, data, 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'."),
buffersize, obj->videodev), GST_ERROR_SYSTEM);
res = GST_FLOW_ERROR;
goto cleanup;
}
cleanup:
{
gst_buffer_unmap (buf, data, 0);
return res;
}
}
/**
* gst_v4l2_buffer_pool_process:
* @bpool: a #GstBufferPool
* @buf: a #GstBuffer
*
* 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 (GstBufferPool * bpool, GstBuffer * buf)
{
GstFlowReturn ret = GST_FLOW_OK;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
switch (obj->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
/* capture */
switch (obj->mode) {
case GST_V4L2_IO_RW:
/* capture into the buffer */
ret = gst_v4l2_do_read (bpool, buf);
break;
case GST_V4L2_IO_MMAP:
{
GstBuffer *tmp;
if (buf->pool == bpool)
/* 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 (bpool, &tmp)) != GST_FLOW_OK)
goto done;
if (!gst_v4l2_object_copy (obj, buf, tmp))
goto copy_failed;
if ((ret = gst_v4l2_buffer_pool_qbuf (bpool, tmp)) != GST_FLOW_OK)
goto done;
break;
}
case GST_V4L2_IO_USERPTR:
default:
g_assert_not_reached ();
break;
}
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
/* playback */
switch (obj->mode) {
case GST_V4L2_IO_RW:
/* FIXME, do write() */
break;
case GST_V4L2_IO_MMAP:
{
GstBuffer *tmp;
if (buf->pool == bpool) {
/* nothing, we can queue directly */
ret = gst_v4l2_buffer_pool_qbuf (bpool, buf);
} else {
ret = GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool,
&tmp, NULL);
if (ret == GST_FLOW_UNEXPECTED) {
/* all buffers are queued, try to dequeue one */
ret = gst_v4l2_buffer_pool_dqbuf (bpool, &tmp);
}
if (ret != GST_FLOW_OK)
goto done;
/* copy into it and queue */
if (!gst_v4l2_object_copy (obj, tmp, buf))
goto copy_failed;
if ((ret = gst_v4l2_buffer_pool_qbuf (bpool, tmp)) != GST_FLOW_OK)
goto done;
/* if we are not streaming yet (this is the first buffer, start
* streaming now */
if (!pool->streaming)
if (!start_streaming (bpool))
goto start_failed;
}
break;
}
case GST_V4L2_IO_USERPTR:
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 data");
return GST_FLOW_ERROR;
}
start_failed:
{
GST_ERROR_OBJECT (obj->element, "failed to start streaming");
return GST_FLOW_ERROR;
}
}
/**
* gst_v4l2_buffer_pool_available_buffers:
* @pool: the pool
*
* Check the number of buffers available to the driver, ie. buffers that
* have been QBUF'd but not yet DQBUF'd.
*
* Returns: the number of buffers available.
*/
gint
gst_v4l2_buffer_pool_available_buffers (GstBufferPool * bpool)
{
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
return pool->num_queued;
}