X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=sys%2Fv4l2%2Fgstv4l2bufferpool.c;h=7dd862fab0adfbd99c7e45f919ef12a5cdaa9d4e;hb=9e79821b1dc0f3f97ac7bdd72b3394f646042280;hp=7a6f4a907abe7f655bb2f557ad19cddea5d35d84;hpb=f6259e96c7f08e9e10138d089dbfa79bcc82f2e3;p=platform%2Fupstream%2Fgst-plugins-good.git diff --git a/sys/v4l2/gstv4l2bufferpool.c b/sys/v4l2/gstv4l2bufferpool.c index 7a6f4a9..7dd862f 100644 --- a/sys/v4l2/gstv4l2bufferpool.c +++ b/sys/v4l2/gstv4l2bufferpool.c @@ -1,7 +1,7 @@ /* GStreamer * * Copyright (C) 2001-2002 Ronald Bultje - * 2006 Edgard Lima + * 2006 Edgard Lima * 2009 Texas Instruments, Inc - http://www.ti.com/ * * gstv4l2bufferpool.c V4L2 buffer pool class @@ -42,13 +42,13 @@ #include -#include "v4l2_calls.h" +#include "gstv4l2object.h" #include "gst/gst-i18n-plugin.h" #include -GST_DEBUG_CATEGORY_EXTERN (v4l2_debug); -GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE); -#define GST_CAT_DEFAULT v4l2_debug +GST_DEBUG_CATEGORY_STATIC (v4l2bufferpool_debug); +GST_DEBUG_CATEGORY_STATIC (CAT_PERFORMANCE); +#define GST_CAT_DEFAULT v4l2bufferpool_debug #define GST_V4L2_IMPORT_QUARK gst_v4l2_buffer_pool_import_quark () @@ -149,8 +149,10 @@ gst_v4l2_buffer_pool_copy_buffer (GstV4l2BufferPool * pool, GstBuffer * dest, gst_buffer_resize (dest, 0, gst_buffer_get_size (src)); } - GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, pool, "slow copy into buffer %p", - dest); + gst_buffer_copy_into (dest, src, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1); + + GST_CAT_LOG_OBJECT (CAT_PERFORMANCE, pool, "slow copy into buffer %p", dest); return GST_FLOW_OK; @@ -219,18 +221,58 @@ gst_v4l2_buffer_pool_import_userptr (GstV4l2BufferPool * pool, if (finfo && (finfo->format != GST_VIDEO_FORMAT_UNKNOWN && finfo->format != GST_VIDEO_FORMAT_ENCODED)) { + gsize size[GST_VIDEO_MAX_PLANES] = { 0, }; + gint i; + data->is_frame = TRUE; if (!gst_video_frame_map (&data->frame, &pool->caps_info, src, flags)) goto invalid_buffer; + for (i = 0; i < GST_VIDEO_FORMAT_INFO_N_PLANES (finfo); i++) { + if (GST_VIDEO_FORMAT_INFO_IS_TILED (finfo)) { + gint tinfo = GST_VIDEO_FRAME_PLANE_STRIDE (&data->frame, i); + gint pstride; + guint pheight; + + pstride = GST_VIDEO_TILE_X_TILES (tinfo) << + GST_VIDEO_FORMAT_INFO_TILE_WS (finfo); + + pheight = GST_VIDEO_TILE_Y_TILES (tinfo) << + GST_VIDEO_FORMAT_INFO_TILE_HS (finfo); + + size[i] = pstride * pheight; + } else { + size[i] = GST_VIDEO_FRAME_PLANE_STRIDE (&data->frame, i) * + GST_VIDEO_FRAME_COMP_HEIGHT (&data->frame, i); + } + } + + /* In the single planar API, planes must be contiguous in memory and + * therefore they must have expected size. ie: no padding. + * To check these conditions, we check that plane 'i' start address + * + plane 'i' size equals to plane 'i+1' start address */ + if (!V4L2_TYPE_IS_MULTIPLANAR (pool->obj->type)) { + for (i = 0; i < (GST_VIDEO_FORMAT_INFO_N_PLANES (finfo) - 1); i++) { + const struct v4l2_pix_format *pix_fmt = &pool->obj->format.fmt.pix; + gpointer tmp; + gint estride = gst_v4l2_object_extrapolate_stride (finfo, i, + pix_fmt->bytesperline); + guint eheight = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (finfo, i, + pix_fmt->height); + + tmp = ((guint8 *) data->frame.data[i]) + estride * eheight; + if (tmp != data->frame.data[i + 1]) + goto non_contiguous_mem; + } + } + if (!gst_v4l2_allocator_import_userptr (pool->vallocator, group, - data->frame.info.size, finfo->n_planes, data->frame.data, - data->frame.info.offset)) + data->frame.info.size, finfo->n_planes, data->frame.data, size)) goto import_failed; } else { - gsize offset[1] = { 0 }; gpointer ptr[1]; + gsize size[1]; data->is_frame = FALSE; @@ -238,9 +280,10 @@ gst_v4l2_buffer_pool_import_userptr (GstV4l2BufferPool * pool, goto invalid_buffer; ptr[0] = data->map.data; + size[0] = data->map.size; if (!gst_v4l2_allocator_import_userptr (pool->vallocator, group, - data->map.size, 1, ptr, offset)) + data->map.size, 1, ptr, size)) goto import_failed; } @@ -249,6 +292,9 @@ gst_v4l2_buffer_pool_import_userptr (GstV4l2BufferPool * pool, gst_mini_object_set_qdata (GST_MINI_OBJECT (dest), GST_V4L2_IMPORT_QUARK, data, (GDestroyNotify) _unmap_userptr_frame); + gst_buffer_copy_into (dest, src, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1); + return ret; not_our_buffer: @@ -262,6 +308,12 @@ invalid_buffer: g_slice_free (struct UserPtrData, data); return GST_FLOW_ERROR; } +non_contiguous_mem: + { + GST_ERROR_OBJECT (pool, "memory is not contiguous or plane size mismatch"); + _unmap_userptr_frame (data); + return GST_FLOW_ERROR; + } import_failed: { GST_ERROR_OBJECT (pool, "failed to import data"); @@ -297,6 +349,9 @@ gst_v4l2_buffer_pool_import_dmabuf (GstV4l2BufferPool * pool, gst_mini_object_set_qdata (GST_MINI_OBJECT (dest), GST_V4L2_IMPORT_QUARK, gst_buffer_ref (src), (GDestroyNotify) gst_buffer_unref); + gst_buffer_copy_into (dest, src, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1); + return GST_FLOW_OK; not_our_buffer: @@ -490,6 +545,10 @@ gst_v4l2_buffer_pool_set_config (GstBufferPool * bpool, GstStructure * config) GST_WARNING_OBJECT (pool, "libv4l2 converter detected, disabling CREATE_BUFS"); can_allocate = FALSE; + GST_OBJECT_FLAG_UNSET (pool->vallocator, + GST_V4L2_ALLOCATOR_FLAG_MMAP_CREATE_BUFS + | GST_V4L2_ALLOCATOR_FLAG_USERPTR_CREATE_BUFS + | GST_V4L2_ALLOCATOR_FLAG_DMABUF_CREATE_BUFS); } if (min_buffers < GST_V4L2_MIN_BUFFERS) { @@ -498,6 +557,13 @@ gst_v4l2_buffer_pool_set_config (GstBufferPool * bpool, GstStructure * config) GST_INFO_OBJECT (pool, "increasing minimum buffers to %u", min_buffers); } + /* respect driver requirements */ + if (min_buffers < obj->min_buffers) { + updated = TRUE; + min_buffers = obj->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; @@ -523,9 +589,9 @@ gst_v4l2_buffer_pool_set_config (GstBufferPool * bpool, GstStructure * config) GST_BUFFER_POOL_OPTION_VIDEO_META); } - if (updated) - gst_buffer_pool_config_set_params (config, caps, size, min_buffers, - max_buffers); + /* Always update the config to ensure the configured size matches */ + gst_buffer_pool_config_set_params (config, caps, obj->info.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); @@ -544,24 +610,60 @@ wrong_config: } } +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"); + + /* block recursive calls to this function */ + g_signal_handler_block (pool->vallocator, pool->group_released_handler); + + params.flags = + (GstBufferPoolAcquireFlags) GST_V4L2_BUFFER_POOL_ACQUIRE_FLAG_RESURRECT | + GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT; + ret = + gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL (pool), &buffer, ¶ms); + + if (ret == GST_FLOW_OK) + gst_buffer_unref (buffer); + + g_signal_handler_unblock (pool->vallocator, pool->group_released_handler); + + return ret; +} + static gboolean gst_v4l2_buffer_pool_streamon (GstV4l2BufferPool * pool) { GstV4l2Object *obj = pool->obj; + if (pool->streaming) + return TRUE; + 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; + if (!V4L2_TYPE_IS_OUTPUT (pool->obj->type)) { + /* For captures, we need to enqueue buffers before we start streaming, + * so the driver don't underflow immediatly. As we have put then back + * into the base class queue, resurect them, then releasing will queue + * them back. */ + while (gst_v4l2_buffer_pool_resurect_buffer (pool) == GST_FLOW_OK) + continue; + } - pool->streaming = TRUE; + if (obj->ioctl (pool->video_fd, VIDIOC_STREAMON, &obj->type) < 0) + goto streamon_failed; - GST_DEBUG_OBJECT (pool, "Started streaming"); - } + pool->streaming = TRUE; + + GST_DEBUG_OBJECT (pool, "Started streaming"); break; default: break; @@ -577,57 +679,53 @@ streamon_failed: } } -static gboolean +/* Call with streamlock held, or when streaming threads are down */ +static void gst_v4l2_buffer_pool_streamoff (GstV4l2BufferPool * pool) { + GstBufferPoolClass *pclass = GST_BUFFER_POOL_CLASS (parent_class); GstV4l2Object *obj = pool->obj; + gint i; + + if (!pool->streaming) + return; 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; + if (obj->ioctl (pool->video_fd, VIDIOC_STREAMOFF, &obj->type) < 0) + GST_WARNING_OBJECT (pool, "STREAMOFF failed with errno %d (%s)", + errno, g_strerror (errno)); - GST_DEBUG_OBJECT (pool, "Stopped streaming"); - } + pool->streaming = FALSE; + + GST_DEBUG_OBJECT (pool, "Stopped streaming"); + + if (pool->vallocator) + gst_v4l2_allocator_flush (pool->vallocator); 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"); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (pool->buffers[i]) { + GstBuffer *buffer = pool->buffers[i]; + GstBufferPool *bpool = GST_BUFFER_POOL (pool); - params.flags = - (GstBufferPoolAcquireFlags) GST_V4L2_BUFFER_POOL_ACQUIRE_FLAG_RESURRECT; - ret = - gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL (pool), &buffer, ¶ms); + pool->buffers[i] = NULL; - if (ret == GST_FLOW_OK) - gst_buffer_unref (buffer); + if (V4L2_TYPE_IS_OUTPUT (pool->obj->type)) + gst_v4l2_buffer_pool_release_buffer (bpool, buffer); + else /* Don't re-enqueue capture buffer on stop */ + pclass->release_buffer (bpool, buffer); - return ret; + g_atomic_int_add (&pool->num_queued, -1); + } + } } static gboolean @@ -640,7 +738,7 @@ gst_v4l2_buffer_pool_start (GstBufferPool * bpool) GstCaps *caps; guint size, min_buffers, max_buffers; guint max_latency, min_latency, copy_threshold = 0; - gboolean can_allocate = FALSE; + gboolean can_allocate = FALSE, ret = TRUE; GST_DEBUG_OBJECT (pool, "activating pool"); @@ -660,8 +758,8 @@ gst_v4l2_buffer_pool_start (GstBufferPool * bpool) * poll before that will always fail. Doing an empty read, forces the * queue to be initialized now. We only do this if we have a streaming * driver. */ - if (obj->vcap.capabilities & V4L2_CAP_STREAMING) - v4l2_read (obj->video_fd, NULL, 0); + if (obj->device_caps & V4L2_CAP_STREAMING) + obj->read (obj->video_fd, NULL, 0); #endif break; case GST_V4L2_IO_DMABUF: @@ -687,15 +785,11 @@ gst_v4l2_buffer_pool_start (GstBufferPool * bpool) * 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); + if (count != min_buffers || pool->enable_copy_threshold) { + GST_WARNING_OBJECT (pool, + "Uncertain or not enough buffers, enabling copy threshold"); min_buffers = count; copy_threshold = min_latency; - - /* The initial minimum could be provide either by GstBufferPool or - * driver needs. */ - min_buffers = count; } break; @@ -775,12 +869,17 @@ gst_v4l2_buffer_pool_start (GstBufferPool * bpool) if (!pclass->start (bpool)) goto start_failed; - if (!V4L2_TYPE_IS_OUTPUT (obj->type)) + if (!V4L2_TYPE_IS_OUTPUT (obj->type)) { + if (g_atomic_int_get (&pool->num_queued) < min_buffers) + goto queue_failed; + pool->group_released_handler = g_signal_connect_swapped (pool->vallocator, "group-released", G_CALLBACK (gst_v4l2_buffer_pool_resurect_buffer), pool); + ret = gst_v4l2_buffer_pool_streamon (pool); + } - return TRUE; + return ret; /* ERRORS */ wrong_config: @@ -799,7 +898,7 @@ no_buffers: } start_failed: { - GST_ERROR_OBJECT (pool, "failed to start streaming"); + GST_ERROR_OBJECT (pool, "allocate failed"); return FALSE; } other_pool_failed: @@ -808,15 +907,18 @@ other_pool_failed: GST_PTR_FORMAT, pool->other_pool); return FALSE; } +queue_failed: + { + GST_ERROR_OBJECT (pool, "failed to queue buffers into the capture queue"); + 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"); @@ -827,30 +929,12 @@ gst_v4l2_buffer_pool_stop (GstBufferPool * bpool) } if (pool->other_pool) { + gst_buffer_pool_set_active (pool->other_pool, FALSE); 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); - } - } + gst_v4l2_buffer_pool_streamoff (pool); ret = GST_BUFFER_POOL_CLASS (parent_class)->stop (bpool); @@ -866,11 +950,6 @@ gst_v4l2_buffer_pool_stop (GstBufferPool * bpool) } return ret; - - /* ERRORS */ -streamoff_failed: - GST_ERROR_OBJECT (pool, "device refused to stop streaming"); - return FALSE; } static void @@ -895,102 +974,53 @@ 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) +gst_v4l2_buffer_pool_poll (GstV4l2BufferPool * pool, gboolean wait) { gint ret; + GstClockTime timeout; + + if (wait) + timeout = GST_CLOCK_TIME_NONE; + else + timeout = 0; /* In RW mode there is no queue, hence no need to wait while the queue is * empty */ if (pool->obj->mode != GST_V4L2_IO_RW) { GST_OBJECT_LOCK (pool); + + if (!wait && pool->empty) { + GST_OBJECT_UNLOCK (pool); + goto no_buffers; + } + 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; + if (!pool->can_poll_device) { + if (wait) + goto done; + else + goto no_buffers; + } GST_LOG_OBJECT (pool, "polling device"); again: - ret = gst_poll_wait (pool->poll, GST_CLOCK_TIME_NONE); + ret = gst_poll_wait (pool->poll, timeout); if (G_UNLIKELY (ret < 0)) { switch (errno) { case EBUSY: @@ -1012,6 +1042,9 @@ again: if (gst_poll_fd_has_error (pool->poll, &pool->pollfd)) goto select_error; + if (ret == 0) + goto no_buffers; + done: return GST_FLOW_OK; @@ -1027,20 +1060,18 @@ select_error: ("poll error %d: %s (%d)", ret, g_strerror (errno), errno)); return GST_FLOW_ERROR; } +no_buffers: + return GST_FLOW_CUSTOM_SUCCESS; } static GstFlowReturn -gst_v4l2_buffer_pool_qbuf (GstV4l2BufferPool * pool, GstBuffer * buf) +gst_v4l2_buffer_pool_qbuf (GstV4l2BufferPool * pool, GstBuffer * buf, + GstV4l2MemoryGroup * group) { - GstV4l2MemoryGroup *group = NULL; + const GstV4l2Object *obj = pool->obj; + GstClockTime timestamp; 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) @@ -1048,13 +1079,40 @@ gst_v4l2_buffer_pool_qbuf (GstV4l2BufferPool * pool, GstBuffer * buf) GST_LOG_OBJECT (pool, "queuing buffer %i", index); + if (V4L2_TYPE_IS_OUTPUT (obj->type)) { + enum v4l2_field field; + + /* Except when field is set to alternate, buffer field is the same as + * the one defined in format */ + if (V4L2_TYPE_IS_MULTIPLANAR (obj->type)) + field = obj->format.fmt.pix_mp.field; + else + field = obj->format.fmt.pix.field; + + /* NB: At this moment, we can't have alternate mode because it not handled + * yet */ + if (field == V4L2_FIELD_ALTERNATE) { + if (GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_FRAME_FLAG_TFF)) + field = V4L2_FIELD_TOP; + else + field = V4L2_FIELD_BOTTOM; + } + + group->buffer.field = field; + } + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + timestamp = GST_BUFFER_TIMESTAMP (buf); + GST_TIME_TO_TIMEVAL (timestamp, group->buffer.timestamp); + } + + GST_OBJECT_LOCK (pool); 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); @@ -1073,27 +1131,38 @@ queue_failed: GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_TAG_MEMORY); g_atomic_int_add (&pool->num_queued, -1); pool->buffers[index] = NULL; + GST_OBJECT_UNLOCK (pool); return GST_FLOW_ERROR; } } static GstFlowReturn -gst_v4l2_buffer_pool_dqbuf (GstV4l2BufferPool * pool, GstBuffer ** buffer) +gst_v4l2_buffer_pool_dqbuf (GstV4l2BufferPool * pool, GstBuffer ** buffer, + gboolean wait) { GstFlowReturn res; - GstBuffer *outbuf; + GstBuffer *outbuf = NULL; GstV4l2Object *obj = pool->obj; GstClockTime timestamp; GstV4l2MemoryGroup *group; + GstVideoMeta *vmeta; + gsize size; gint i; - if ((res = gst_v4l2_buffer_pool_poll (pool)) != GST_FLOW_OK) + if ((res = gst_v4l2_buffer_pool_poll (pool, wait)) < GST_FLOW_OK) goto poll_failed; + if (res == GST_FLOW_CUSTOM_SUCCESS) { + GST_LOG_OBJECT (pool, "nothing to dequeue"); + goto done; + } + GST_LOG_OBJECT (pool, "dequeueing a buffer"); - group = gst_v4l2_allocator_dqbuf (pool->vallocator); - if (group == NULL) + res = gst_v4l2_allocator_dqbuf (pool->vallocator, &group); + if (res == GST_FLOW_EOS) + goto eos; + if (res != GST_FLOW_OK) goto dqbuf_failed; /* get our GstBuffer with that index from the pool, if the buffer was @@ -1113,7 +1182,8 @@ gst_v4l2_buffer_pool_dqbuf (GstV4l2BufferPool * pool, GstBuffer ** buffer) timestamp = GST_TIMEVAL_TO_TIME (group->buffer.timestamp); -#ifndef GST_DISABLE_GST_DEBUG + size = 0; + vmeta = gst_buffer_get_video_meta (outbuf); 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 %" @@ -1121,9 +1191,45 @@ gst_v4l2_buffer_pool_dqbuf (GstV4l2BufferPool * pool, GstBuffer ** buffer) 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); + + if (vmeta) { + vmeta->offset[i] = size; + size += gst_memory_get_sizes (group->mem[i], NULL, NULL); + } } + + /* Ignore timestamp and field for OUTPUT device */ + if (V4L2_TYPE_IS_OUTPUT (obj->type)) + goto done; + + /* Check for driver bug in reporting feild */ + if (group->buffer.field == V4L2_FIELD_ANY) { + /* Only warn once to avoid the spamming */ +#ifndef GST_DISABLE_GST_DEBUG + if (!pool->has_warned_on_buggy_field) { + pool->has_warned_on_buggy_field = TRUE; + GST_WARNING_OBJECT (pool, + "Driver should never set v4l2_buffer.field to ANY"); + } +#endif + + /* Use the value from the format (works for UVC bug) */ + group->buffer.field = obj->format.fmt.pix.field; + + /* If driver also has buggy S_FMT, assume progressive */ + if (group->buffer.field == V4L2_FIELD_ANY) { +#ifndef GST_DISABLE_GST_DEBUG + if (!pool->has_warned_on_buggy_field) { + pool->has_warned_on_buggy_field = TRUE; + GST_WARNING_OBJECT (pool, + "Driver should never set v4l2_format.pix.field to ANY"); + } #endif + group->buffer.field = V4L2_FIELD_NONE; + } + } + /* set top/bottom field first if v4l2_buffer has the information */ switch (group->buffer.field) { case V4L2_FIELD_NONE: @@ -1154,10 +1260,14 @@ gst_v4l2_buffer_pool_dqbuf (GstV4l2BufferPool * pool, GstBuffer ** buffer) GST_FIXME_OBJECT (pool, "Unhandled enum v4l2_field %d - treating as progressive", group->buffer.field); + break; } if (GST_VIDEO_INFO_FORMAT (&obj->info) == GST_VIDEO_FORMAT_ENCODED) { - if (group->buffer.flags & V4L2_BUF_FLAG_KEYFRAME) + if ((group->buffer.flags & V4L2_BUF_FLAG_KEYFRAME) || + GST_V4L2_PIXELFORMAT (obj) == V4L2_PIX_FMT_MJPEG || + GST_V4L2_PIXELFORMAT (obj) == V4L2_PIX_FMT_JPEG || + GST_V4L2_PIXELFORMAT (obj) == V4L2_PIX_FMT_PJPG) GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); else GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); @@ -1167,10 +1277,13 @@ gst_v4l2_buffer_pool_dqbuf (GstV4l2BufferPool * pool, GstBuffer ** buffer) GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_CORRUPTED); GST_BUFFER_TIMESTAMP (outbuf) = timestamp; + GST_BUFFER_OFFSET (outbuf) = group->buffer.sequence; + GST_BUFFER_OFFSET_END (outbuf) = group->buffer.sequence + 1; +done: *buffer = outbuf; - return GST_FLOW_OK; + return res; /* ERRORS */ poll_failed: @@ -1178,6 +1291,10 @@ poll_failed: GST_DEBUG_OBJECT (pool, "poll error %s", gst_flow_get_name (res)); return res; } +eos: + { + return GST_FLOW_EOS; + } dqbuf_failed: { return GST_FLOW_ERROR; @@ -1226,7 +1343,7 @@ gst_v4l2_buffer_pool_acquire_buffer (GstBufferPool * bpool, GstBuffer ** buffer, /* 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); + ret = gst_v4l2_buffer_pool_dqbuf (pool, buffer, TRUE); break; } default: @@ -1295,11 +1412,13 @@ gst_v4l2_buffer_pool_release_buffer (GstBufferPool * bpool, GstBuffer * buffer) case GST_V4L2_IO_USERPTR: case GST_V4L2_IO_DMABUF_IMPORT: { - if (gst_v4l2_is_buffer_valid (buffer, NULL)) { + GstV4l2MemoryGroup *group; + if (gst_v4l2_is_buffer_valid (buffer, &group)) { + gst_v4l2_allocator_reset_group (pool->vallocator, group); /* 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) + if (gst_v4l2_buffer_pool_qbuf (pool, buffer, group) != GST_FLOW_OK) pclass->release_buffer (bpool, buffer); } else { /* Simply release invalide/modified buffer, the allocator will @@ -1355,8 +1474,10 @@ gst_v4l2_buffer_pool_release_buffer (GstBufferPool * bpool, GstBuffer * buffer) /* 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 (); + /* the buffer is queued in the device but maybe not played yet. We just + * leave it there and not make it available for future calls to acquire + * for now. The buffer will be dequeued and reused later. */ + GST_LOG_OBJECT (pool, "buffer %u is queued", index); } break; } @@ -1377,12 +1498,6 @@ 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); @@ -1405,12 +1520,11 @@ gst_v4l2_buffer_pool_finalize (GObject * object) GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (object); if (pool->video_fd >= 0) - v4l2_close (pool->video_fd); + pool->obj->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 + /* 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); @@ -1448,6 +1562,10 @@ gst_v4l2_buffer_pool_class_init (GstV4l2BufferPoolClass * klass) 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_DEBUG_CATEGORY_INIT (v4l2bufferpool_debug, "v4l2bufferpool", 0, + "V4L2 Buffer Pool"); + GST_DEBUG_CATEGORY_GET (CAT_PERFORMANCE, "GST_PERFORMANCE"); } /** @@ -1466,7 +1584,7 @@ gst_v4l2_buffer_pool_new (GstV4l2Object * obj, GstCaps * caps) gchar *name, *parent_name; gint fd; - fd = v4l2_dup (obj->video_fd); + fd = obj->dup (obj->video_fd); if (fd < 0) goto dup_failed; @@ -1478,6 +1596,7 @@ gst_v4l2_buffer_pool_new (GstV4l2Object * obj, GstCaps * caps) pool = (GstV4l2BufferPool *) g_object_new (GST_TYPE_V4L2_BUFFER_POOL, "name", name, NULL); + g_object_ref_sink (pool); g_free (name); gst_poll_fd_init (&pool->pollfd); @@ -1492,8 +1611,7 @@ gst_v4l2_buffer_pool_new (GstV4l2Object * obj, GstCaps * caps) pool->obj = obj; pool->can_poll_device = TRUE; - pool->vallocator = - gst_v4l2_allocator_new (GST_OBJECT (pool), obj->video_fd, &obj->format); + pool->vallocator = gst_v4l2_allocator_new (GST_OBJECT (pool), obj); if (pool->vallocator == NULL) goto allocator_failed; @@ -1537,10 +1655,10 @@ gst_v4l2_do_read (GstV4l2BufferPool * pool, GstBuffer * buf) gst_buffer_map (buf, &map, GST_MAP_WRITE); do { - if ((res = gst_v4l2_buffer_pool_poll (pool)) != GST_FLOW_OK) + if ((res = gst_v4l2_buffer_pool_poll (pool, TRUE)) != GST_FLOW_OK) goto poll_error; - amount = v4l2_read (obj->video_fd, map.data, toread); + amount = obj->read (obj->video_fd, map.data, toread); if (amount == toread) { break; @@ -1603,8 +1721,6 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) 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; @@ -1625,13 +1741,15 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) if ((*buf)->pool == bpool) { guint num_queued; + gsize size = gst_buffer_get_size (*buf); - if (gst_buffer_get_size (*buf) == 0) { - if (GST_BUFFER_FLAG_IS_SET (*buf, GST_BUFFER_FLAG_CORRUPTED)) - goto buffer_corrupted; - else - goto eos; - } + /* Legacy M2M devices return empty buffer when drained */ + if (size == 0 && GST_V4L2_IS_M2M (obj->device_caps)) + goto eos; + + if (GST_VIDEO_INFO_FORMAT (&pool->caps_info) != + GST_VIDEO_FORMAT_ENCODED && size < pool->size) + goto buffer_truncated; num_queued = g_atomic_int_get (&pool->num_queued); GST_TRACE_OBJECT (pool, "Only %i buffer left in the capture queue.", @@ -1666,21 +1784,22 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) *buf = copy; } + ret = GST_FLOW_OK; /* 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) + if ((ret = gst_v4l2_buffer_pool_dqbuf (pool, &tmp, TRUE)) + != 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 + /* Legacy M2M devices return empty buffer when drained */ + if (GST_V4L2_IS_M2M (obj->device_caps)) goto eos; } @@ -1697,12 +1816,20 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) case GST_V4L2_IO_USERPTR: { struct UserPtrData *data; + GstBuffer *tmp; /* 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); + tmp = gst_buffer_ref (data->buffer); _unmap_userptr_frame (data); + + /* Now tmp is writable, copy the flags and timestamp */ + gst_buffer_copy_into (tmp, *buf, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1); + + gst_buffer_replace (buf, tmp); + gst_buffer_unref (tmp); break; } @@ -1713,6 +1840,10 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) /* Replace our buffer with downstream allocated buffer */ tmp = gst_mini_object_steal_qdata (GST_MINI_OBJECT (*buf), GST_V4L2_IMPORT_QUARK); + + gst_buffer_copy_into (tmp, *buf, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1); + gst_buffer_replace (buf, tmp); gst_buffer_unref (tmp); break; @@ -1739,6 +1870,7 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) case GST_V4L2_IO_MMAP: { GstBuffer *to_queue = NULL; + GstBuffer *buffer; GstV4l2MemoryGroup *group; gint index; @@ -1752,7 +1884,6 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) 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; @@ -1780,9 +1911,13 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) gst_buffer_unref (to_queue); goto prepare_failed; } + + /* retreive the group */ + gst_v4l2_is_buffer_valid (to_queue, &group); } - if ((ret = gst_v4l2_buffer_pool_qbuf (pool, to_queue)) != GST_FLOW_OK) + if ((ret = gst_v4l2_buffer_pool_qbuf (pool, to_queue, group)) + != GST_FLOW_OK) goto queue_failed; /* if we are not streaming yet (this is the first buffer, start @@ -1791,7 +1926,7 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) /* 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 + /* qbuf has stored to_queue buffer but we are not 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 */ @@ -1806,15 +1941,25 @@ gst_v4l2_buffer_pool_process (GstV4l2BufferPool * pool, GstBuffer ** buf) goto start_failed; } + /* Remove our ref, we will still hold this buffer in acquire as needed, + * otherwise the pool will think it is outstanding and will refuse to stop. */ + gst_buffer_unref (to_queue); + + /* release as many buffer as possible */ + while (gst_v4l2_buffer_pool_dqbuf (pool, &buffer, FALSE) == + GST_FLOW_OK) { + if (buffer->pool == NULL) + gst_v4l2_buffer_pool_release_buffer (bpool, buffer); + } + 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) + ret = gst_v4l2_buffer_pool_dqbuf (pool, &buffer, TRUE); + if (ret == GST_FLOW_OK && buffer->pool == NULL) /* release the rendered buffer back into the pool. This wakes up any * thread waiting for a buffer in _acquire(). */ - gst_buffer_unref (out); + gst_v4l2_buffer_pool_release_buffer (bpool, buffer); } break; } @@ -1836,9 +1981,10 @@ copy_failed: GST_ERROR_OBJECT (pool, "failed to copy buffer"); return ret; } -buffer_corrupted: +buffer_truncated: { - GST_WARNING_OBJECT (pool, "Dropping corrupted buffer without payload"); + GST_WARNING_OBJECT (pool, + "Dropping truncated buffer, this is likely a driver bug."); gst_buffer_unref (*buf); *buf = NULL; return GST_V4L2_FLOW_CORRUPTED_BUFFER; @@ -1886,3 +2032,25 @@ gst_v4l2_buffer_pool_set_other_pool (GstV4l2BufferPool * pool, gst_object_unref (pool->other_pool); pool->other_pool = gst_object_ref (other_pool); } + +void +gst_v4l2_buffer_pool_copy_at_threshold (GstV4l2BufferPool * pool, gboolean copy) +{ + GST_OBJECT_LOCK (pool); + pool->enable_copy_threshold = copy; + GST_OBJECT_UNLOCK (pool); +} + +gboolean +gst_v4l2_buffer_pool_flush (GstBufferPool * bpool) +{ + GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL (bpool); + gboolean ret = TRUE; + + gst_v4l2_buffer_pool_streamoff (pool); + + if (!V4L2_TYPE_IS_OUTPUT (pool->obj->type)) + ret = gst_v4l2_buffer_pool_streamon (pool); + + return ret; +}