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.
This commit is contained in:
Wim Taymans 2011-07-18 18:54:49 +02:00
parent 39716c02a7
commit 55eb26f1e7
7 changed files with 810 additions and 475 deletions

View file

@ -83,24 +83,35 @@ static void
gst_v4l2_buffer_pool_free_buffer (GstBufferPool * bpool, GstBuffer * buffer) gst_v4l2_buffer_pool_free_buffer (GstBufferPool * bpool, GstBuffer * buffer)
{ {
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool); GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
gint index;
GstMetaV4l2 *meta;
GstV4l2Object *obj; GstV4l2Object *obj;
meta = GST_META_V4L2_GET (buffer);
g_assert (meta != NULL);
obj = pool->obj; obj = pool->obj;
index = meta->vbuffer.index; switch (obj->mode) {
GST_LOG_OBJECT (pool, "finalizing buffer %p %d", buffer, index); case GST_V4L2_IO_RW:
pool->buffers[index] = NULL; break;
case GST_V4L2_IO_MMAP:
{
GstMetaV4l2 *meta;
gint index;
GST_LOG_OBJECT (pool, meta = GST_META_V4L2_GET (buffer);
"buffer %p (data %p, len %u) freed, unmapping", g_assert (meta != NULL);
buffer, meta->mem, meta->vbuffer.length);
v4l2_munmap (meta->mem, meta->vbuffer.length);
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); gst_buffer_unref (buffer);
} }
@ -118,54 +129,70 @@ gst_v4l2_buffer_pool_alloc_buffer (GstBufferPool * bpool, GstBuffer ** buffer,
obj = pool->obj; obj = pool->obj;
info = &obj->info; info = &obj->info;
newbuf = gst_buffer_new (); switch (obj->mode) {
meta = GST_META_V4L2_ADD (newbuf); 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; index = pool->index;
GST_LOG_OBJECT (pool, "creating buffer %u, %p", index, newbuf, pool); GST_LOG_OBJECT (pool, "creating buffer %u, %p", index, newbuf, pool);
meta->vbuffer.index = index; meta->vbuffer.index = index;
meta->vbuffer.type = obj->type; meta->vbuffer.type = obj->type;
meta->vbuffer.memory = V4L2_MEMORY_MMAP; meta->vbuffer.memory = V4L2_MEMORY_MMAP;
if (v4l2_ioctl (pool->video_fd, VIDIOC_QUERYBUF, &meta->vbuffer) < 0) if (v4l2_ioctl (pool->video_fd, VIDIOC_QUERYBUF, &meta->vbuffer) < 0)
goto querybuf_failed; goto querybuf_failed;
GST_LOG_OBJECT (pool, " index: %u", meta->vbuffer.index); GST_LOG_OBJECT (pool, " index: %u", meta->vbuffer.index);
GST_LOG_OBJECT (pool, " type: %d", meta->vbuffer.type); GST_LOG_OBJECT (pool, " type: %d", meta->vbuffer.type);
GST_LOG_OBJECT (pool, " bytesused: %u", meta->vbuffer.bytesused); GST_LOG_OBJECT (pool, " bytesused: %u", meta->vbuffer.bytesused);
GST_LOG_OBJECT (pool, " flags: %08x", meta->vbuffer.flags); GST_LOG_OBJECT (pool, " flags: %08x", meta->vbuffer.flags);
GST_LOG_OBJECT (pool, " field: %d", meta->vbuffer.field); GST_LOG_OBJECT (pool, " field: %d", meta->vbuffer.field);
GST_LOG_OBJECT (pool, " memory: %d", meta->vbuffer.memory); GST_LOG_OBJECT (pool, " memory: %d", meta->vbuffer.memory);
if (meta->vbuffer.memory == V4L2_MEMORY_MMAP) if (meta->vbuffer.memory == V4L2_MEMORY_MMAP)
GST_LOG_OBJECT (pool, " MMAP offset: %u", meta->vbuffer.m.offset); GST_LOG_OBJECT (pool, " MMAP offset: %u", meta->vbuffer.m.offset);
GST_LOG_OBJECT (pool, " length: %u", meta->vbuffer.length); GST_LOG_OBJECT (pool, " length: %u", meta->vbuffer.length);
GST_LOG_OBJECT (pool, " input: %u", meta->vbuffer.input); GST_LOG_OBJECT (pool, " input: %u", meta->vbuffer.input);
meta->mem = v4l2_mmap (0, meta->vbuffer.length, meta->mem = v4l2_mmap (0, meta->vbuffer.length,
PROT_READ | PROT_WRITE, MAP_SHARED, pool->video_fd, PROT_READ | PROT_WRITE, MAP_SHARED, pool->video_fd,
meta->vbuffer.m.offset); meta->vbuffer.m.offset);
if (meta->mem == MAP_FAILED) if (meta->mem == MAP_FAILED)
goto mmap_failed; goto mmap_failed;
gst_buffer_take_memory (newbuf, -1, gst_buffer_take_memory (newbuf, -1,
gst_memory_new_wrapped (0, gst_memory_new_wrapped (0,
meta->mem, NULL, meta->vbuffer.length, 0, meta->vbuffer.length)); meta->mem, NULL, meta->vbuffer.length, 0, meta->vbuffer.length));
/* add metadata to raw video buffers */ /* add metadata to raw video buffers */
if (info->finfo) { if (info->finfo) {
gsize offset[GST_VIDEO_MAX_PLANES]; gsize offset[GST_VIDEO_MAX_PLANES];
gint stride[GST_VIDEO_MAX_PLANES]; gint stride[GST_VIDEO_MAX_PLANES];
offset[0] = 0; offset[0] = 0;
stride[0] = obj->bytesperline; stride[0] = obj->bytesperline;
GST_DEBUG_OBJECT (pool, "adding video meta"); GST_DEBUG_OBJECT (pool, "adding video meta, stride %d", stride[0]);
gst_buffer_add_meta_video_full (newbuf, info->flags, gst_buffer_add_meta_video_full (newbuf, info->flags,
GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info),
GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_N_PLANES (info), GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_N_PLANES (info),
offset, stride); offset, stride);
}
break;
}
case GST_V4L2_IO_USERPTR:
default:
g_assert_not_reached ();
break;
} }
pool->index++; pool->index++;
@ -212,8 +239,11 @@ gst_v4l2_buffer_pool_set_config (GstBufferPool * bpool, GstStructure * config)
GST_DEBUG_OBJECT (pool, "config %" GST_PTR_FORMAT, config); GST_DEBUG_OBJECT (pool, "config %" GST_PTR_FORMAT, config);
pool->size = size;
pool->min_buffers = min_buffers; pool->min_buffers = min_buffers;
pool->max_buffers = max_buffers; pool->max_buffers = max_buffers;
pool->prefix = prefix;
pool->align = align;
return TRUE; return TRUE;
@ -224,6 +254,39 @@ wrong_config:
} }
} }
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 static gboolean
gst_v4l2_buffer_pool_start (GstBufferPool * bpool) gst_v4l2_buffer_pool_start (GstBufferPool * bpool)
{ {
@ -233,38 +296,52 @@ gst_v4l2_buffer_pool_start (GstBufferPool * bpool)
struct v4l2_requestbuffers breq; struct v4l2_requestbuffers breq;
gint num_buffers; gint num_buffers;
num_buffers = pool->max_buffers; num_buffers = pool->min_buffers;
/* first, lets request buffers, and see how many we can get: */ switch (obj->mode) {
GST_DEBUG_OBJECT (pool, "starting, requesting %d MMAP buffers", num_buffers); 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)); memset (&breq, 0, sizeof (struct v4l2_requestbuffers));
breq.type = obj->type; breq.type = obj->type;
breq.count = num_buffers; breq.count = num_buffers;
breq.memory = V4L2_MEMORY_MMAP; breq.memory = V4L2_MEMORY_MMAP;
if (v4l2_ioctl (pool->video_fd, VIDIOC_REQBUFS, &breq) < 0) if (v4l2_ioctl (pool->video_fd, VIDIOC_REQBUFS, &breq) < 0)
goto reqbufs_failed; goto reqbufs_failed;
GST_LOG_OBJECT (pool, " count: %u", breq.count); GST_LOG_OBJECT (pool, " count: %u", breq.count);
GST_LOG_OBJECT (pool, " type: %d", breq.type); GST_LOG_OBJECT (pool, " type: %d", breq.type);
GST_LOG_OBJECT (pool, " memory: %d", breq.memory); GST_LOG_OBJECT (pool, " memory: %d", breq.memory);
if (breq.count < GST_V4L2_MIN_BUFFERS) if (breq.count < GST_V4L2_MIN_BUFFERS)
goto no_buffers; goto no_buffers;
if (num_buffers != breq.count) { if (num_buffers != breq.count) {
GST_WARNING_OBJECT (pool, "using %u buffers instead", breq.count); GST_WARNING_OBJECT (pool, "using %u buffers instead", breq.count);
num_buffers = breq.count; num_buffers = breq.count;
}
break;
}
case GST_V4L2_IO_USERPTR:
default:
g_assert_not_reached ();
break;
} }
pool->obj = obj; pool->obj = obj;
pool->requeuebuf = (obj->type == V4L2_BUF_TYPE_VIDEO_CAPTURE ? TRUE : FALSE);
pool->num_buffers = num_buffers; pool->num_buffers = num_buffers;
pool->buffers = g_new0 (GstBuffer *, num_buffers); pool->buffers = g_new0 (GstBuffer *, num_buffers);
pool->index = 0; pool->index = 0;
/* now, map the buffers: */ /* now, allocate the buffers: */
for (n = 0; n < num_buffers; n++) { for (n = 0; n < num_buffers; n++) {
GstBuffer *buffer; GstBuffer *buffer;
@ -273,6 +350,13 @@ gst_v4l2_buffer_pool_start (GstBufferPool * bpool)
gst_v4l2_buffer_pool_release_buffer (bpool, buffer); 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; return TRUE;
/* ERRORS */ /* ERRORS */
@ -294,6 +378,11 @@ buffer_new_failed:
GST_ERROR_OBJECT (pool, "failed to create a buffer"); GST_ERROR_OBJECT (pool, "failed to create a buffer");
return FALSE; return FALSE;
} }
start_failed:
{
GST_ERROR_OBJECT (pool, "failed to start streaming");
return FALSE;
}
} }
static gboolean static gboolean
@ -301,10 +390,29 @@ gst_v4l2_buffer_pool_stop (GstBufferPool * bpool)
{ {
gboolean ret; gboolean ret;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool); GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
guint n; guint n;
GST_DEBUG_OBJECT (pool, "stopping pool"); 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 */ /* first free the buffers in the queue */
ret = GST_BUFFER_POOL_CLASS (parent_class)->stop (bpool); ret = GST_BUFFER_POOL_CLASS (parent_class)->stop (bpool);
@ -314,16 +422,104 @@ gst_v4l2_buffer_pool_stop (GstBufferPool * bpool)
gst_v4l2_buffer_pool_free_buffer (bpool, pool->buffers[n]); gst_v4l2_buffer_pool_free_buffer (bpool, pool->buffers[n]);
} }
return ret; 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 static GstFlowReturn
gst_v4l2_buffer_pool_dqbuf (GstBufferPool * bpool, GstBuffer ** buffer) gst_v4l2_buffer_pool_dqbuf (GstBufferPool * bpool, GstBuffer ** buffer)
{ {
GstFlowReturn res;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool); GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstBuffer *outbuf; GstBuffer *outbuf;
struct v4l2_buffer vbuffer; struct v4l2_buffer vbuffer;
GstV4l2Object *obj = pool->obj; GstV4l2Object *obj = pool->obj;
if ((res = gst_v4l2_object_poll (obj)) != GST_FLOW_OK)
goto poll_error;
memset (&vbuffer, 0x00, sizeof (vbuffer)); memset (&vbuffer, 0x00, sizeof (vbuffer));
vbuffer.type = obj->type; vbuffer.type = obj->type;
vbuffer.memory = V4L2_MEMORY_MMAP; vbuffer.memory = V4L2_MEMORY_MMAP;
@ -340,15 +536,13 @@ gst_v4l2_buffer_pool_dqbuf (GstBufferPool * bpool, GstBuffer ** buffer)
/* mark the buffer outstanding */ /* mark the buffer outstanding */
pool->buffers[vbuffer.index] = NULL; pool->buffers[vbuffer.index] = NULL;
pool->num_queued--;
GST_LOG_OBJECT (pool, GST_LOG_OBJECT (pool,
"dequeued frame %d (ix=%d), used %d, flags %08x, pool-queued=%d, buffer=%p", "dequeued frame %d (ix=%d), used %d, flags %08x, pool-queued=%d, buffer=%p",
vbuffer.sequence, vbuffer.index, vbuffer.bytesused, vbuffer.flags, vbuffer.sequence, vbuffer.index, vbuffer.bytesused, vbuffer.flags,
pool->num_queued, outbuf); pool->num_queued, outbuf);
pool->num_queued--;
GST_DEBUG_OBJECT (pool, "num_queued: %d", pool->num_queued);
/* set top/bottom field first if v4l2_buffer has the information */ /* set top/bottom field first if v4l2_buffer has the information */
if (vbuffer.field == V4L2_FIELD_INTERLACED_TB) if (vbuffer.field == V4L2_FIELD_INTERLACED_TB)
GST_BUFFER_FLAG_SET (outbuf, GST_VIDEO_BUFFER_TFF); GST_BUFFER_FLAG_SET (outbuf, GST_VIDEO_BUFFER_TFF);
@ -366,6 +560,11 @@ gst_v4l2_buffer_pool_dqbuf (GstBufferPool * bpool, GstBuffer ** buffer)
return GST_FLOW_OK; return GST_FLOW_OK;
/* ERRORS */ /* ERRORS */
poll_error:
{
GST_DEBUG ("poll error %s", gst_flow_get_name (res));
return res;
}
error: error:
{ {
GST_WARNING_OBJECT (pool, GST_WARNING_OBJECT (pool,
@ -426,25 +625,68 @@ static GstFlowReturn
gst_v4l2_buffer_pool_acquire_buffer (GstBufferPool * bpool, GstBuffer ** buffer, gst_v4l2_buffer_pool_acquire_buffer (GstBufferPool * bpool, GstBuffer ** buffer,
GstBufferPoolParams * params) GstBufferPoolParams * params)
{ {
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstFlowReturn ret; GstFlowReturn ret;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
GST_DEBUG_OBJECT (pool, "acquire"); GST_DEBUG_OBJECT (pool, "acquire");
if (GST_BUFFER_POOL_IS_FLUSHING (bpool)) if (GST_BUFFER_POOL_IS_FLUSHING (bpool))
goto flushing; goto flushing;
if (pool->requeuebuf) switch (obj->type) {
ret = gst_v4l2_buffer_pool_dqbuf (bpool, buffer); case V4L2_BUF_TYPE_VIDEO_CAPTURE:
else { /* capture, This function should return a buffer with new captured data */
if (pool->num_queued == pool->num_buffers) { switch (obj->mode) {
ret = gst_v4l2_buffer_pool_dqbuf (bpool, buffer); case GST_V4L2_IO_RW:
} /* take empty buffer from the pool */
ret = ret = GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool,
GST_BUFFER_POOL_CLASS (parent_class)->acquire_buffer (bpool, buffer, buffer, params);
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; return ret;
/* ERRORS */ /* ERRORS */
@ -459,13 +701,41 @@ static void
gst_v4l2_buffer_pool_release_buffer (GstBufferPool * bpool, GstBuffer * buffer) gst_v4l2_buffer_pool_release_buffer (GstBufferPool * bpool, GstBuffer * buffer)
{ {
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool); GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstV4l2Object *obj = pool->obj;
GST_DEBUG_OBJECT (pool, "release"); GST_DEBUG_OBJECT (pool, "release");
if (pool->requeuebuf) switch (obj->type) {
gst_v4l2_buffer_pool_qbuf (bpool, buffer); case V4L2_BUF_TYPE_VIDEO_CAPTURE:
else /* capture, put the buffer back in the queue so that we can refill it
GST_BUFFER_POOL_CLASS (parent_class)->release_buffer (bpool, buffer); * 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 static void
@ -543,52 +813,184 @@ dup_failed:
} }
} }
/** static GstFlowReturn
* gst_v4l2_buffer_pool_qbuf: gst_v4l2_do_read (GstBufferPool * bpool, GstBuffer * buf)
* @pool: the pool
* @buf: the buffer to queue
*
* Queue a buffer to the driver
*
* Returns: %TRUE for success
*/
gboolean
gst_v4l2_buffer_pool_qbuf (GstBufferPool * bpool, GstBuffer * buf)
{ {
GstFlowReturn res;
GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool); GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool);
GstMetaV4l2 *meta; GstV4l2Object *obj = pool->obj;
gint index; gint amount;
gpointer data;
gint buffersize;
meta = GST_META_V4L2_GET (buf); buffersize = gst_buffer_get_size (buf);
g_assert (meta != NULL);
index = meta->vbuffer.index; GST_LOG_OBJECT (pool, "reading %d bytes into buffer %p", buffersize, buf);
GST_LOG_OBJECT (pool, "enqueue pool buffer %d", index); data = gst_buffer_map (buf, NULL, NULL, GST_MAP_WRITE);
if (pool->buffers[index] != NULL) do {
goto already_queued; if ((res = gst_v4l2_object_poll (obj)) != GST_FLOW_OK)
goto poll_error;
if (v4l2_ioctl (pool->video_fd, VIDIOC_QBUF, &meta->vbuffer) < 0) amount = v4l2_read (obj->video_fd, data, buffersize);
goto queue_failed;
pool->buffers[index] = buf; 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);
pool->num_queued++; GST_LOG_OBJECT (pool, "read %d bytes", amount);
GST_DEBUG_OBJECT (pool, "num_queued: %d", pool->num_queued); gst_buffer_unmap (buf, data, amount);
return TRUE; return GST_FLOW_OK;
/* ERRORS */ /* ERRORS */
already_queued: poll_error:
{ {
GST_WARNING_OBJECT (pool, "the buffer was already queued"); GST_DEBUG ("poll error %s", gst_flow_get_name (res));
return FALSE; goto cleanup;
} }
queue_failed: read_error:
{ {
GST_WARNING_OBJECT (pool, "could not queue a buffer"); GST_ELEMENT_ERROR (obj->element, RESOURCE, READ,
return FALSE; (_("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;
} }
} }

View file

@ -49,15 +49,20 @@ struct _GstV4l2BufferPool
GstV4l2Object *obj; /* the v4l2 object */ GstV4l2Object *obj; /* the v4l2 object */
gint video_fd; /* a dup(2) of the v4l2object's video_fd */ gint video_fd; /* a dup(2) of the v4l2object's video_fd */
gboolean requeuebuf; /* if true, unusued buffers are automatically re-QBUF'd */
GstAllocator *allocator;
guint size;
guint min_buffers; guint min_buffers;
guint max_buffers; guint max_buffers;
guint prefix;
guint align;
guint num_buffers; /* number of buffers allocated by the driver */ guint num_buffers; /* number of buffers allocated by the driver */
guint num_queued; /* number of buffers queued in the driver */ guint num_queued; /* number of buffers queued in the driver */
gint index; gint index;
gboolean streaming;
GstBuffer **buffers; GstBuffer **buffers;
}; };
@ -81,7 +86,7 @@ GType gst_v4l2_buffer_pool_get_type (void);
GstBufferPool * gst_v4l2_buffer_pool_new (GstV4l2Object *obj); GstBufferPool * gst_v4l2_buffer_pool_new (GstV4l2Object *obj);
gboolean gst_v4l2_buffer_pool_qbuf (GstBufferPool * bpool, GstBuffer * buf); GstFlowReturn gst_v4l2_buffer_pool_process (GstBufferPool * bpool, GstBuffer * buf);
gint gst_v4l2_buffer_pool_available_buffers (GstBufferPool *pool); gint gst_v4l2_buffer_pool_available_buffers (GstBufferPool *pool);

View file

@ -61,6 +61,7 @@ GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE);
#define DEFAULT_PROP_TV_NORM 0 #define DEFAULT_PROP_TV_NORM 0
#define DEFAULT_PROP_CHANNEL NULL #define DEFAULT_PROP_CHANNEL NULL
#define DEFAULT_PROP_FREQUENCY 0 #define DEFAULT_PROP_FREQUENCY 0
#define DEFAULT_PROP_IO_MODE GST_V4L2_IO_AUTO
enum enum
{ {
@ -370,6 +371,26 @@ gst_v4l2_tv_norm_get_type (void)
return v4l2_tv_norm; return v4l2_tv_norm;
} }
#define GST_TYPE_V4L2_IO_MODE (gst_v4l2_io_mode_get_type ())
static GType
gst_v4l2_io_mode_get_type (void)
{
static GType v4l2_io_mode = 0;
if (!v4l2_io_mode) {
static const GEnumValue io_modes[] = {
{GST_V4L2_IO_AUTO, "GST_V4L2_IO_AUTO", "auto"},
{GST_V4L2_IO_RW, "GST_V4L2_IO_RW", "rw"},
{GST_V4L2_IO_MMAP, "GST_V4L2_IO_MMAP", "mmap"},
{GST_V4L2_IO_USERPTR, "GST_V4L2_IO_USERPTR", "userptr"},
{0, NULL, NULL}
};
v4l2_io_mode = g_enum_register_static ("GstV4l2IOMode", io_modes);
}
return v4l2_io_mode;
}
void void
gst_v4l2_object_install_properties_helper (GObjectClass * gobject_class, gst_v4l2_object_install_properties_helper (GObjectClass * gobject_class,
const char *default_device) const char *default_device)
@ -451,6 +472,17 @@ gst_v4l2_object_install_properties_helper (GObjectClass * gobject_class,
"video standard", "video standard",
GST_TYPE_V4L2_TV_NORM, DEFAULT_PROP_TV_NORM, GST_TYPE_V4L2_TV_NORM, DEFAULT_PROP_TV_NORM,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstV4l2Src:io-mode
*
* IO Mode
*/
g_object_class_install_property (gobject_class, PROP_IO_MODE,
g_param_spec_enum ("io-mode", "IO mode",
"I/O mode",
GST_TYPE_V4L2_IO_MODE, DEFAULT_PROP_IO_MODE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
} }
GstV4l2Object * GstV4l2Object *
@ -612,6 +644,9 @@ gst_v4l2_object_set_property_helper (GstV4l2Object * v4l2object,
} }
break; break;
#endif #endif
case PROP_IO_MODE:
v4l2object->req_mode = g_value_get_enum (value);
break;
default: default:
return FALSE; return FALSE;
break; break;
@ -685,6 +720,9 @@ gst_v4l2_object_get_property_helper (GstV4l2Object * v4l2object,
case PROP_TV_NORM: case PROP_TV_NORM:
g_value_set_enum (value, v4l2object->tv_norm); g_value_set_enum (value, v4l2object->tv_norm);
break; break;
case PROP_IO_MODE:
g_value_set_enum (value, v4l2object->req_mode);
break;
default: default:
return FALSE; return FALSE;
break; break;
@ -2044,39 +2082,46 @@ gst_v4l2_object_get_nearest_size (GstV4l2Object * v4l2object,
static gboolean static gboolean
gst_v4l2_object_setup_pool (GstV4l2Object * v4l2object, GstCaps * caps) gst_v4l2_object_setup_pool (GstV4l2Object * v4l2object, GstCaps * caps)
{ {
guint num_buffers;
GstStructure *config;
GST_DEBUG_OBJECT (v4l2object->element, "initializing the capture system"); GST_DEBUG_OBJECT (v4l2object->element, "initializing the capture system");
GST_V4L2_CHECK_OPEN (v4l2object); GST_V4L2_CHECK_OPEN (v4l2object);
GST_V4L2_CHECK_NOT_ACTIVE (v4l2object); GST_V4L2_CHECK_NOT_ACTIVE (v4l2object);
if (v4l2object->vcap.capabilities & V4L2_CAP_STREAMING) { /* use specified mode */
guint num_buffers; v4l2object->mode = v4l2object->req_mode;
GstStructure *config;
/* keep track of current number of buffers */ if (v4l2object->req_mode == GST_V4L2_IO_AUTO) {
num_buffers = v4l2object->num_buffers; /* automatic mode, find transport */
if (v4l2object->vcap.capabilities & V4L2_CAP_READWRITE) {
/* Map the buffers */ GST_INFO_OBJECT (v4l2object->element,
GST_LOG_OBJECT (v4l2object->element, "initiating buffer pool"); "accessing buffers via read()/write()");
v4l2object->mode = GST_V4L2_IO_RW;
if (!(v4l2object->pool = gst_v4l2_buffer_pool_new (v4l2object))) }
goto buffer_pool_new_failed; if (v4l2object->vcap.capabilities & V4L2_CAP_STREAMING) {
GST_INFO_OBJECT (v4l2object->element, "accessing buffers via mmap()");
GST_INFO_OBJECT (v4l2object->element, "accessing buffers via mmap()"); v4l2object->mode = GST_V4L2_IO_MMAP;
v4l2object->mode = GST_V4L2_IO_MMAP; }
config = gst_buffer_pool_get_config (v4l2object->pool);
gst_buffer_pool_config_set (config, caps, v4l2object->info.size,
num_buffers, num_buffers, 0, 0);
gst_buffer_pool_set_config (v4l2object->pool, config);
} else if (v4l2object->vcap.capabilities & V4L2_CAP_READWRITE) {
GST_INFO_OBJECT (v4l2object->element,
"accessing buffers via read()/write()");
v4l2object->mode = GST_V4L2_IO_RW;
v4l2object->pool = NULL;
} else {
goto no_supported_capture_method;
} }
/* if still no transport selected, error out */
if (v4l2object->mode == GST_V4L2_IO_AUTO)
goto no_supported_capture_method;
/* keep track of current number of buffers */
num_buffers = v4l2object->num_buffers;
/* Map the buffers */
GST_LOG_OBJECT (v4l2object->element, "initiating buffer pool");
if (!(v4l2object->pool = gst_v4l2_buffer_pool_new (v4l2object)))
goto buffer_pool_new_failed;
config = gst_buffer_pool_get_config (v4l2object->pool);
gst_buffer_pool_config_set (config, caps, v4l2object->info.size,
num_buffers, num_buffers, 0, 0);
gst_buffer_pool_set_config (v4l2object->pool, config);
GST_V4L2_SET_ACTIVE (v4l2object); GST_V4L2_SET_ACTIVE (v4l2object);
@ -2140,7 +2185,8 @@ gst_v4l2_object_set_format (GstV4l2Object * v4l2object, GstCaps * caps)
} }
GST_DEBUG_OBJECT (v4l2object->element, "Desired format %dx%d, format " GST_DEBUG_OBJECT (v4l2object->element, "Desired format %dx%d, format "
"%" GST_FOURCC_FORMAT, width, height, GST_FOURCC_ARGS (pixelformat)); "%" GST_FOURCC_FORMAT " stride: %d", width, height,
GST_FOURCC_ARGS (pixelformat), stride);
GST_V4L2_CHECK_OPEN (v4l2object); GST_V4L2_CHECK_OPEN (v4l2object);
GST_V4L2_CHECK_NOT_ACTIVE (v4l2object); GST_V4L2_CHECK_NOT_ACTIVE (v4l2object);
@ -2192,9 +2238,10 @@ gst_v4l2_object_set_format (GstV4l2Object * v4l2object, GstCaps * caps)
if (format.fmt.pix.pixelformat != pixelformat) if (format.fmt.pix.pixelformat != pixelformat)
goto invalid_pixelformat; goto invalid_pixelformat;
v4l2object->bytesperline = format.fmt.pix.bytesperline;
} }
v4l2object->bytesperline = format.fmt.pix.bytesperline;
/* FIXME, size for only one plane */
v4l2object->size = v4l2object->bytesperline * height;
/* Is there a reason we require the caller to always specify a framerate? */ /* Is there a reason we require the caller to always specify a framerate? */
GST_DEBUG_OBJECT (v4l2object->element, "Desired framerate: %u/%u", fps_n, GST_DEBUG_OBJECT (v4l2object->element, "Desired framerate: %u/%u", fps_n,
@ -2334,50 +2381,21 @@ pool_failed:
} }
gboolean gboolean
gst_v4l2_object_start (GstV4l2Object * v4l2object) gst_v4l2_object_unlock (GstV4l2Object * v4l2object)
{ {
GST_DEBUG_OBJECT (v4l2object->element, "starting"); GST_LOG_OBJECT (v4l2object->element, "flush poll");
gst_poll_set_flushing (v4l2object->poll, TRUE);
GST_V4L2_CHECK_OPEN (v4l2object);
GST_V4L2_CHECK_ACTIVE (v4l2object);
if (v4l2object->pool)
if (!gst_buffer_pool_set_active (v4l2object->pool, TRUE))
goto activate_failed;
switch (v4l2object->mode) {
case GST_V4L2_IO_RW:
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
if (!v4l2object->streaming) {
GST_DEBUG_OBJECT (v4l2object->element, "STREAMON");
if (v4l2_ioctl (v4l2object->video_fd, VIDIOC_STREAMON,
&(v4l2object->type)) < 0)
goto start_failed;
v4l2object->streaming = TRUE;
}
break;
default:
g_assert_not_reached ();
break;
}
return TRUE; return TRUE;
}
/* ERRORS */ gboolean
activate_failed: gst_v4l2_object_unlock_stop (GstV4l2Object * v4l2object)
{ {
GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, OPEN_READ, GST_LOG_OBJECT (v4l2object->element, "flush stop poll");
(_("Error starting bufferpool")), (NULL)); gst_poll_set_flushing (v4l2object->poll, FALSE);
return FALSE;
} return TRUE;
start_failed:
{
GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, OPEN_READ,
(_("Error starting streaming on device '%s'."), v4l2object->videodev),
GST_ERROR_SYSTEM);
return FALSE;
}
} }
gboolean gboolean
@ -2390,27 +2408,6 @@ gst_v4l2_object_stop (GstV4l2Object * v4l2object)
if (!GST_V4L2_IS_ACTIVE (v4l2object)) if (!GST_V4L2_IS_ACTIVE (v4l2object))
goto done; goto done;
switch (v4l2object->mode) {
case GST_V4L2_IO_RW:
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
if (v4l2object->streaming) {
/* we actually need to sync on all queued buffers but not
* on the non-queued ones */
GST_DEBUG_OBJECT (v4l2object->element, "STREAMOFF");
if (v4l2_ioctl (v4l2object->video_fd, VIDIOC_STREAMOFF,
&(v4l2object->type)) < 0)
goto stop_failed;
v4l2object->streaming = FALSE;
}
break;
default:
g_assert_not_reached ();
break;
}
if (v4l2object->pool) { if (v4l2object->pool) {
GST_DEBUG_OBJECT (v4l2object->element, "deactivating pool"); GST_DEBUG_OBJECT (v4l2object->element, "deactivating pool");
gst_buffer_pool_set_active (v4l2object->pool, FALSE); gst_buffer_pool_set_active (v4l2object->pool, FALSE);
@ -2422,99 +2419,9 @@ gst_v4l2_object_stop (GstV4l2Object * v4l2object)
done: done:
return TRUE; return TRUE;
/* ERRORS */
stop_failed:
{
GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, OPEN_READ,
(_("Error stopping streaming on device '%s'."), v4l2object->videodev),
GST_ERROR_SYSTEM);
return FALSE;
}
}
static GstFlowReturn
gst_v4l2_object_get_read (GstV4l2Object * v4l2object, GstBuffer ** buf)
{
GstFlowReturn res;
gint amount;
gint ret;
gpointer data;
gint buffersize;
buffersize = v4l2object->size;
/* In case the size per frame is unknown assume it's a streaming format (e.g.
* mpegts) and grab a reasonable default size instead */
if (buffersize == 0)
buffersize = 4096;
*buf = gst_buffer_new_and_alloc (buffersize);
data = gst_buffer_map (*buf, NULL, NULL, GST_MAP_WRITE);
do {
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;
}
}
amount = v4l2_read (v4l2object->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_buffer_unmap (*buf, data, amount);
return GST_FLOW_OK;
/* ERRORS */
select_error:
{
GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ, (NULL),
("select error %d: %s (%d)", ret, g_strerror (errno), errno));
res = GST_FLOW_ERROR;
goto cleanup;
}
stopped:
{
GST_DEBUG ("stop called");
res = GST_FLOW_WRONG_STATE;
goto cleanup;
}
read_error:
{
GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ,
(_("Error reading %d bytes from device '%s'."),
buffersize, v4l2object->videodev), GST_ERROR_SYSTEM);
res = GST_FLOW_ERROR;
goto cleanup;
}
cleanup:
{
gst_buffer_unmap (*buf, data, 0);
gst_buffer_unref (*buf);
return res;
}
} }
#if 0
static GstFlowReturn static GstFlowReturn
gst_v4l2_object_get_mmap (GstV4l2Object * v4l2object, GstBuffer ** buf) gst_v4l2_object_get_mmap (GstV4l2Object * v4l2object, GstBuffer ** buf)
{ {
@ -2524,7 +2431,6 @@ gst_v4l2_object_get_mmap (GstV4l2Object * v4l2object, GstBuffer ** buf)
gint32 trials = NUM_TRIALS; gint32 trials = NUM_TRIALS;
GstBuffer *pool_buffer; GstBuffer *pool_buffer;
gboolean need_copy; gboolean need_copy;
gint ret;
pool = v4l2object->pool; pool = v4l2object->pool;
if (!pool) if (!pool)
@ -2533,21 +2439,8 @@ gst_v4l2_object_get_mmap (GstV4l2Object * v4l2object, GstBuffer ** buf)
GST_DEBUG_OBJECT (v4l2object->element, "grab frame"); GST_DEBUG_OBJECT (v4l2object->element, "grab frame");
for (;;) { for (;;) {
if (v4l2object->can_poll_device) { if ((res = gst_v4l2_object_poll (v4l2object)) != GST_FLOW_OK)
ret = gst_poll_wait (v4l2object->poll, GST_CLOCK_TIME_NONE); goto poll_error;
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;
}
}
}
res = gst_buffer_pool_acquire_buffer (pool, &pool_buffer, NULL); res = gst_buffer_pool_acquire_buffer (pool, &pool_buffer, NULL);
if (res != GST_FLOW_OK) if (res != GST_FLOW_OK)
@ -2618,16 +2511,9 @@ no_buffer_pool:
GST_DEBUG_OBJECT (v4l2object->element, "no buffer pool"); GST_DEBUG_OBJECT (v4l2object->element, "no buffer pool");
return GST_FLOW_WRONG_STATE; return GST_FLOW_WRONG_STATE;
} }
select_error: poll_error:
{ {
GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ, (NULL), return res;
("select error %d: %s (%d)", ret, g_strerror (errno), errno));
return GST_FLOW_ERROR;
}
stopped:
{
GST_DEBUG ("stop called");
return GST_FLOW_WRONG_STATE;
} }
too_many_trials: too_many_trials:
{ {
@ -2639,32 +2525,57 @@ too_many_trials:
return GST_FLOW_ERROR; return GST_FLOW_ERROR;
} }
} }
#endif
GstFlowReturn gboolean
gst_v4l2_object_get_buffer (GstV4l2Object * v4l2object, GstBuffer ** buf) gst_v4l2_object_copy (GstV4l2Object * v4l2object, GstBuffer * dest,
GstBuffer * src)
{ {
GstFlowReturn ret; guint8 *data;
gsize size;
switch (v4l2object->type) { if (v4l2object->info.finfo) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE: GstVideoFrame src_frame, dest_frame;
if (v4l2object->mode == GST_V4L2_IO_MMAP) {
ret = gst_v4l2_object_get_mmap (v4l2object, buf); GST_DEBUG_OBJECT (v4l2object->element, "copy video frame");
} else {
ret = gst_v4l2_object_get_read (v4l2object, buf); /* we have raw video, use videoframe copy to get strides right */
} if (!gst_video_frame_map (&src_frame, &v4l2object->info, src, GST_MAP_READ))
break; goto invalid_buffer;
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
ret = gst_buffer_pool_acquire_buffer (v4l2object->pool, buf, NULL); if (!gst_video_frame_map (&dest_frame, &v4l2object->info, dest,
break; GST_MAP_WRITE)) {
default: gst_video_frame_unmap (&src_frame);
ret = GST_FLOW_ERROR; goto invalid_buffer;
break; }
gst_video_frame_copy (&dest_frame, &src_frame);
gst_video_frame_unmap (&src_frame);
gst_video_frame_unmap (&dest_frame);
} else {
GST_DEBUG_OBJECT (v4l2object->element, "copy raw bytes");
data = gst_buffer_map (src, &size, NULL, GST_MAP_READ);
gst_buffer_fill (dest, 0, data, size);
gst_buffer_unmap (src, data, size);
}
GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, v4l2object->element,
"slow copy into buffer %p", dest);
return TRUE;
/* ERRORS */
invalid_buffer:
{
/* No Window available to put our image into */
GST_WARNING_OBJECT (v4l2object->element, "could not map image");
return FALSE;
} }
return ret;
} }
GstFlowReturn GstFlowReturn
gst_v4l2_object_output_buffer (GstV4l2Object * v4l2object, GstBuffer * buf) gst_v4l2_object_process_buffer (GstV4l2Object * v4l2object, GstBuffer * buf)
{ {
GstFlowReturn ret; GstFlowReturn ret;
GstBuffer *to_queue = NULL; GstBuffer *to_queue = NULL;
@ -2678,9 +2589,6 @@ gst_v4l2_object_output_buffer (GstV4l2Object * v4l2object, GstBuffer * buf)
to_queue = buf; to_queue = buf;
ret = GST_FLOW_OK; ret = GST_FLOW_OK;
} else { } else {
guint8 *data;
gsize size;
/* not our buffer */ /* not our buffer */
GST_LOG_OBJECT (v4l2object->element, "buffer not from our pool, copying"); GST_LOG_OBJECT (v4l2object->element, "buffer not from our pool, copying");
@ -2691,44 +2599,13 @@ gst_v4l2_object_output_buffer (GstV4l2Object * v4l2object, GstBuffer * buf)
if (ret != GST_FLOW_OK) if (ret != GST_FLOW_OK)
goto acquire_failed; goto acquire_failed;
if (v4l2object->info.finfo) { if (!gst_v4l2_object_copy (v4l2object, to_queue, buf))
GstVideoFrame src_frame, dest_frame; goto copy_failed;
GST_DEBUG_OBJECT (v4l2object->element, "copy video frame");
/* we have raw video, use videoframe copy to get strides right */
if (!gst_video_frame_map (&src_frame, &v4l2object->info, buf,
GST_MAP_READ))
goto invalid_buffer;
if (!gst_video_frame_map (&dest_frame, &v4l2object->info, to_queue,
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 {
GST_DEBUG_OBJECT (v4l2object->element, "copy raw bytes");
data = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
gst_buffer_fill (to_queue, 0, data, size);
gst_buffer_unmap (buf, data, size);
}
GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, v4l2object->element,
"slow copy into bufferpool buffer %p", to_queue);
} }
if (!gst_v4l2_buffer_pool_qbuf (v4l2object->pool, to_queue)) if (!gst_v4l2_buffer_pool_process (v4l2object->pool, to_queue))
goto queue_failed; goto queue_failed;
if (!v4l2object->streaming) {
if (!gst_v4l2_object_start (v4l2object)) {
goto start_failed;
}
}
done: done:
if (to_queue != buf) if (to_queue != buf)
gst_buffer_unref (to_queue); gst_buffer_unref (to_queue);
@ -2747,10 +2624,10 @@ acquire_failed:
GST_DEBUG_OBJECT (v4l2object->element, "could not get buffer from pool"); GST_DEBUG_OBJECT (v4l2object->element, "could not get buffer from pool");
return ret; return ret;
} }
invalid_buffer: copy_failed:
{ {
/* No Window available to put our image into */ /* No Window available to put our image into */
GST_WARNING_OBJECT (v4l2object->element, "could not map image"); GST_WARNING_OBJECT (v4l2object->element, "could not copy image");
ret = GST_FLOW_OK; ret = GST_FLOW_OK;
goto done; goto done;
} }
@ -2760,10 +2637,4 @@ queue_failed:
ret = GST_FLOW_ERROR; ret = GST_FLOW_ERROR;
goto done; goto done;
} }
start_failed:
{
GST_DEBUG_OBJECT (v4l2object->element, "failed to start streaming");
ret = GST_FLOW_ERROR;
goto done;
}
} }

View file

@ -72,7 +72,7 @@ G_BEGIN_DECLS
#define GST_V4L2_OBJECT(obj) (GstV4l2Object *)(obj) #define GST_V4L2_OBJECT(obj) (GstV4l2Object *)(obj)
typedef enum { typedef enum {
GST_V4L2_IO_NONE = 0, GST_V4L2_IO_AUTO = 0,
GST_V4L2_IO_RW = 1, GST_V4L2_IO_RW = 1,
GST_V4L2_IO_MMAP = 2, GST_V4L2_IO_MMAP = 2,
GST_V4L2_IO_USERPTR = 3 GST_V4L2_IO_USERPTR = 3
@ -121,6 +121,9 @@ struct _GstV4l2Object {
guint size; guint size;
GstClockTime duration; GstClockTime duration;
/* wanted mode */
GstV4l2IOMode req_mode;
/* optional pool */ /* optional pool */
guint32 num_buffers; guint32 num_buffers;
guint32 min_queued_bufs; guint32 min_queued_bufs;
@ -174,7 +177,8 @@ GType gst_v4l2_object_get_type (void);
PROP_CONTRAST, \ PROP_CONTRAST, \
PROP_SATURATION, \ PROP_SATURATION, \
PROP_HUE, \ PROP_HUE, \
PROP_TV_NORM PROP_TV_NORM, \
PROP_IO_MODE
/* create/destroy */ /* create/destroy */
GstV4l2Object * gst_v4l2_object_new (GstElement * element, GstV4l2Object * gst_v4l2_object_new (GstElement * element,
@ -223,14 +227,17 @@ GstStructure* gst_v4l2_object_v4l2fourcc_to_structure (guint32 fourcc);
gboolean gst_v4l2_object_set_format (GstV4l2Object *v4l2object, GstCaps * caps); gboolean gst_v4l2_object_set_format (GstV4l2Object *v4l2object, GstCaps * caps);
gboolean gst_v4l2_object_start (GstV4l2Object *v4l2object); gboolean gst_v4l2_object_unlock (GstV4l2Object *v4l2object);
gboolean gst_v4l2_object_unlock_stop (GstV4l2Object *v4l2object);
gboolean gst_v4l2_object_stop (GstV4l2Object *v4l2object); gboolean gst_v4l2_object_stop (GstV4l2Object *v4l2object);
/* capture returns a filled buffer, output returns an empty buffer */ gboolean gst_v4l2_object_copy (GstV4l2Object * v4l2object,
GstFlowReturn gst_v4l2_object_get_buffer (GstV4l2Object * v4l2object, GstBuffer ** buf); GstBuffer * dest, GstBuffer *src);
/* output the filled buffer */
GstFlowReturn gst_v4l2_object_output_buffer (GstV4l2Object * v4l2object, GstBuffer * buf); /* capture writes into the buffer, output renders the buffer */
GstFlowReturn gst_v4l2_object_process_buffer (GstV4l2Object * v4l2object, GstBuffer * buf);
#define GST_IMPLEMENT_V4L2_PROBE_METHODS(Type_Class, interface_as_function) \ #define GST_IMPLEMENT_V4L2_PROBE_METHODS(Type_Class, interface_as_function) \

View file

@ -747,7 +747,7 @@ gst_v4l2sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
GST_DEBUG_OBJECT (v4l2sink, "render buffer: %p", buf); GST_DEBUG_OBJECT (v4l2sink, "render buffer: %p", buf);
ret = gst_v4l2_object_output_buffer (obj, buf); ret = gst_v4l2_object_process_buffer (obj, buf);
return ret; return ret;
} }

View file

@ -64,7 +64,7 @@
GST_DEBUG_CATEGORY (v4l2src_debug); GST_DEBUG_CATEGORY (v4l2src_debug);
#define GST_CAT_DEFAULT v4l2src_debug #define GST_CAT_DEFAULT v4l2src_debug
#define PROP_DEF_QUEUE_SIZE 2 #define PROP_DEF_QUEUE_SIZE 4
#define PROP_DEF_ALWAYS_COPY TRUE #define PROP_DEF_ALWAYS_COPY TRUE
#define PROP_DEF_DECIMATE 1 #define PROP_DEF_DECIMATE 1
@ -123,7 +123,7 @@ static GstCaps *gst_v4l2src_get_caps (GstBaseSrc * src, GstCaps * filter);
static gboolean gst_v4l2src_query (GstBaseSrc * bsrc, GstQuery * query); static gboolean gst_v4l2src_query (GstBaseSrc * bsrc, GstQuery * query);
static gboolean gst_v4l2src_setup_allocation (GstBaseSrc * src, static gboolean gst_v4l2src_setup_allocation (GstBaseSrc * src,
GstQuery * query); GstQuery * query);
static GstFlowReturn gst_v4l2src_create (GstPushSrc * src, GstBuffer ** out); static GstFlowReturn gst_v4l2src_fill (GstPushSrc * src, GstBuffer * out);
static void gst_v4l2src_fixate (GstBaseSrc * basesrc, GstCaps * caps); static void gst_v4l2src_fixate (GstBaseSrc * basesrc, GstCaps * caps);
static gboolean gst_v4l2src_negotiate (GstBaseSrc * basesrc); static gboolean gst_v4l2src_negotiate (GstBaseSrc * basesrc);
@ -198,7 +198,7 @@ gst_v4l2src_class_init (GstV4l2SrcClass * klass)
basesrc_class->setup_allocation = basesrc_class->setup_allocation =
GST_DEBUG_FUNCPTR (gst_v4l2src_setup_allocation); GST_DEBUG_FUNCPTR (gst_v4l2src_setup_allocation);
pushsrc_class->create = GST_DEBUG_FUNCPTR (gst_v4l2src_create); pushsrc_class->fill = GST_DEBUG_FUNCPTR (gst_v4l2src_fill);
klass->v4l2_class_devices = NULL; klass->v4l2_class_devices = NULL;
@ -525,12 +525,6 @@ gst_v4l2src_set_caps (GstBaseSrc * src, GstCaps * caps)
/* error already posted */ /* error already posted */
return FALSE; return FALSE;
if (!gst_v4l2_object_start (obj))
return FALSE;
/* now store the expected output size */
v4l2src->frame_byte_size = obj->size;
return TRUE; return TRUE;
} }
@ -538,15 +532,71 @@ static gboolean
gst_v4l2src_setup_allocation (GstBaseSrc * bsrc, GstQuery * query) gst_v4l2src_setup_allocation (GstBaseSrc * bsrc, GstQuery * query)
{ {
GstV4l2Src *src; GstV4l2Src *src;
GstV4l2Object *obj;
GstBufferPool *pool; GstBufferPool *pool;
guint size, min, max, prefix, alignment; guint size, min, max, prefix, alignment;
src = GST_V4L2SRC (bsrc); src = GST_V4L2SRC (bsrc);
obj = src->v4l2object;
gst_query_parse_allocation_params (query, &size, &min, &max, &prefix, gst_query_parse_allocation_params (query, &size, &min, &max, &prefix,
&alignment, &pool); &alignment, &pool);
/* do something clever here */ GST_DEBUG_OBJECT (src, "allocation: size:%u min:%u max:%u prefix:%u "
"align:%u pool:%" GST_PTR_FORMAT, size, min, max, prefix, alignment,
pool);
if (min != 0) {
/* if there is a min-buffers suggestion, use it. We add 1 because we need 1
* buffer extra to capture while the other two buffers are downstream */
min += 1;
} else {
min = obj->num_buffers;
}
/* select a pool */
switch (obj->mode) {
case GST_V4L2_IO_RW:
if (pool == NULL) {
/* no downstream pool, use our own then */
GST_DEBUG_OBJECT (src,
"read/write mode: no downstream pool, using our own");
pool = obj->pool;
size = obj->size;
} else {
/* in READ/WRITE mode, prefer a downstream pool because our own pool
* doesn't help much, we have to write to it as well */
GST_DEBUG_OBJECT (src, "read/write mode: using downstream pool");
/* use the bigest size, when we use our own pool we can't really do any
* other size than what the hardware gives us but for downstream pools
* we can try */
size = MAX (size, obj->size);
}
break;
case GST_V4L2_IO_MMAP:
case GST_V4L2_IO_USERPTR:
/* in streaming mode, prefer our own pool */
pool = obj->pool;
size = obj->size;
GST_DEBUG_OBJECT (src,
"streaming mode: using our own pool %" GST_PTR_FORMAT, pool);
break;
case GST_V4L2_IO_AUTO:
default:
GST_WARNING_OBJECT (src, "unhandled mode");
break;
}
if (pool) {
GstStructure *config;
const GstCaps *caps;
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_get (config, &caps, NULL, NULL, NULL, NULL, NULL);
gst_buffer_pool_config_set (config, caps, size, min, max, prefix,
alignment);
gst_buffer_pool_set_config (pool, config);
}
gst_query_set_allocation_params (query, size, min, max, prefix, gst_query_set_allocation_params (query, size, min, max, prefix,
alignment, pool); alignment, pool);
@ -633,22 +683,14 @@ static gboolean
gst_v4l2src_unlock (GstBaseSrc * src) gst_v4l2src_unlock (GstBaseSrc * src)
{ {
GstV4l2Src *v4l2src = GST_V4L2SRC (src); GstV4l2Src *v4l2src = GST_V4L2SRC (src);
return gst_v4l2_object_unlock (v4l2src->v4l2object);
GST_LOG_OBJECT (src, "Flushing");
gst_poll_set_flushing (v4l2src->v4l2object->poll, TRUE);
return TRUE;
} }
static gboolean static gboolean
gst_v4l2src_unlock_stop (GstBaseSrc * src) gst_v4l2src_unlock_stop (GstBaseSrc * src)
{ {
GstV4l2Src *v4l2src = GST_V4L2SRC (src); GstV4l2Src *v4l2src = GST_V4L2SRC (src);
return gst_v4l2_object_unlock_stop (v4l2src->v4l2object);
GST_LOG_OBJECT (src, "No longer flushing");
gst_poll_set_flushing (v4l2src->v4l2object->poll, FALSE);
return TRUE;
} }
static gboolean static gboolean
@ -702,79 +744,89 @@ gst_v4l2src_change_state (GstElement * element, GstStateChange transition)
} }
static GstFlowReturn static GstFlowReturn
gst_v4l2src_create (GstPushSrc * src, GstBuffer ** buf) gst_v4l2src_fill (GstPushSrc * src, GstBuffer * buf)
{ {
GstV4l2Src *v4l2src = GST_V4L2SRC (src); GstV4l2Src *v4l2src = GST_V4L2SRC (src);
GstV4l2Object *obj = v4l2src->v4l2object; GstV4l2Object *obj = v4l2src->v4l2object;
int i;
GstFlowReturn ret; GstFlowReturn ret;
GstClock *clock;
GstClockTime timestamp, duration;
#if 0
int i;
/* decimate, just capture and throw away frames */ /* decimate, just capture and throw away frames */
for (i = 0; i < v4l2src->decimate - 1; i++) { for (i = 0; i < v4l2src->decimate - 1; i++) {
ret = gst_v4l2_object_get_buffer (obj, buf); ret = gst_v4l2_buffer_pool_process (obj, buf);
if (ret != GST_FLOW_OK) { if (ret != GST_FLOW_OK) {
return ret; return ret;
} }
gst_buffer_unref (*buf); gst_buffer_unref (*buf);
} }
#endif
ret = gst_v4l2_object_get_buffer (obj, buf); ret = gst_v4l2_buffer_pool_process (obj->pool, buf);
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto error;
/* set buffer metadata */ /* set buffer metadata */
if (G_LIKELY (ret == GST_FLOW_OK && *buf)) { GST_BUFFER_OFFSET (buf) = v4l2src->offset++;
GstClock *clock; GST_BUFFER_OFFSET_END (buf) = v4l2src->offset;
GstClockTime timestamp, duration;
GST_BUFFER_OFFSET (*buf) = v4l2src->offset++; /* timestamps, LOCK to get clock and base time. */
GST_BUFFER_OFFSET_END (*buf) = v4l2src->offset; /* FIXME: element clock and base_time is rarely changing */
GST_OBJECT_LOCK (v4l2src);
/* timestamps, LOCK to get clock and base time. */ if ((clock = GST_ELEMENT_CLOCK (v4l2src))) {
/* FIXME: element clock and base_time is rarely changing */ /* we have a clock, get base time and ref clock */
GST_OBJECT_LOCK (v4l2src); timestamp = GST_ELEMENT (v4l2src)->base_time;
if ((clock = GST_ELEMENT_CLOCK (v4l2src))) { gst_object_ref (clock);
/* we have a clock, get base time and ref clock */ } else {
timestamp = GST_ELEMENT (v4l2src)->base_time; /* no clock, can't set timestamps */
gst_object_ref (clock); timestamp = GST_CLOCK_TIME_NONE;
} else {
/* no clock, can't set timestamps */
timestamp = GST_CLOCK_TIME_NONE;
}
GST_OBJECT_UNLOCK (v4l2src);
duration = obj->duration;
if (G_LIKELY (clock)) {
/* the time now is the time of the clock minus the base time */
timestamp = gst_clock_get_time (clock) - timestamp;
gst_object_unref (clock);
/* if we have a framerate adjust timestamp for frame latency */
if (GST_CLOCK_TIME_IS_VALID (duration)) {
if (timestamp > duration)
timestamp -= duration;
else
timestamp = 0;
}
}
/* activate settings for next frame */
if (GST_CLOCK_TIME_IS_VALID (duration)) {
v4l2src->ctrl_time += duration;
} else {
/* this is not very good (as it should be the next timestamp),
* still good enough for linear fades (as long as it is not -1)
*/
v4l2src->ctrl_time = timestamp;
}
gst_object_sync_values (G_OBJECT (src), v4l2src->ctrl_time);
GST_INFO_OBJECT (src, "sync to %" GST_TIME_FORMAT,
GST_TIME_ARGS (v4l2src->ctrl_time));
/* FIXME: use the timestamp from the buffer itself! */
GST_BUFFER_TIMESTAMP (*buf) = timestamp;
GST_BUFFER_DURATION (*buf) = duration;
} }
GST_OBJECT_UNLOCK (v4l2src);
duration = obj->duration;
if (G_LIKELY (clock)) {
/* the time now is the time of the clock minus the base time */
timestamp = gst_clock_get_time (clock) - timestamp;
gst_object_unref (clock);
/* if we have a framerate adjust timestamp for frame latency */
if (GST_CLOCK_TIME_IS_VALID (duration)) {
if (timestamp > duration)
timestamp -= duration;
else
timestamp = 0;
}
}
/* activate settings for next frame */
if (GST_CLOCK_TIME_IS_VALID (duration)) {
v4l2src->ctrl_time += duration;
} else {
/* this is not very good (as it should be the next timestamp),
* still good enough for linear fades (as long as it is not -1)
*/
v4l2src->ctrl_time = timestamp;
}
gst_object_sync_values (G_OBJECT (src), v4l2src->ctrl_time);
GST_INFO_OBJECT (src, "sync to %" GST_TIME_FORMAT,
GST_TIME_ARGS (v4l2src->ctrl_time));
/* FIXME: use the timestamp from the buffer itself! */
GST_BUFFER_TIMESTAMP (buf) = timestamp;
GST_BUFFER_DURATION (buf) = duration;
return ret; return ret;
/* ERROR */
error:
{
GST_ERROR_OBJECT (src, "error processing buffer");
return ret;
}
} }

View file

@ -60,8 +60,6 @@ struct _GstV4l2Src
/* pads */ /* pads */
GstCaps *probed_caps; GstCaps *probed_caps;
guint32 frame_byte_size;
int decimate; int decimate;
guint64 offset; guint64 offset;