mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-10 03:19:40 +00:00
ac3cb8817e
Offset are relative to the buffer and there is no guarantee substracting them will give us the plane size. So we let bufferpool make the math as it is more aware of video info than allocator and pass a size array to allocator import function. Pointed out by Nicolas Dufresne <nicolas.dufresne@collabora.com> https://bugzilla.gnome.org/show_bug.cgi?id=738013
1401 lines
36 KiB
C
1401 lines
36 KiB
C
/*
|
|
* Copyright (C) 2014 Collabora Ltd.
|
|
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#ifndef _GNU_SOURCE
|
|
# define _GNU_SOURCE /* O_CLOEXEC */
|
|
#endif
|
|
|
|
#include "ext/videodev2.h"
|
|
#include "gstv4l2allocator.h"
|
|
#include "v4l2_calls.h"
|
|
|
|
#include <gst/allocators/gstdmabuf.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/mman.h>
|
|
|
|
#define GST_V4L2_MEMORY_TYPE "V4l2Memory"
|
|
|
|
#define gst_v4l2_allocator_parent_class parent_class
|
|
G_DEFINE_TYPE (GstV4l2Allocator, gst_v4l2_allocator, GST_TYPE_ALLOCATOR);
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (v4l2allocator_debug);
|
|
#define GST_CAT_DEFAULT v4l2allocator_debug
|
|
|
|
#define UNSET_QUEUED(buffer) \
|
|
((buffer).flags &= ~(V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE))
|
|
|
|
#define SET_QUEUED(buffer) ((buffer).flags |= V4L2_BUF_FLAG_QUEUED)
|
|
|
|
#define IS_QUEUED(buffer) \
|
|
((buffer).flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE))
|
|
|
|
enum
|
|
{
|
|
GROUP_RELEASED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint gst_v4l2_allocator_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static void gst_v4l2_allocator_release (GstV4l2Allocator * allocator,
|
|
GstV4l2Memory * mem);
|
|
|
|
static const gchar *
|
|
memory_type_to_str (guint32 memory)
|
|
{
|
|
switch (memory) {
|
|
case V4L2_MEMORY_MMAP:
|
|
return "mmap";
|
|
case V4L2_MEMORY_USERPTR:
|
|
return "userptr";
|
|
case V4L2_MEMORY_DMABUF:
|
|
return "dmabuf";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
/*************************************/
|
|
/* GstV4lMemory implementation */
|
|
/*************************************/
|
|
|
|
static gpointer
|
|
_v4l2mem_map (GstV4l2Memory * mem, gsize maxsize, GstMapFlags flags)
|
|
{
|
|
gpointer data = NULL;
|
|
|
|
switch (mem->group->buffer.memory) {
|
|
case V4L2_MEMORY_MMAP:
|
|
case V4L2_MEMORY_USERPTR:
|
|
data = mem->data;
|
|
break;
|
|
case V4L2_MEMORY_DMABUF:
|
|
/* v4l2 dmabuf memory are not shared with downstream */
|
|
g_assert_not_reached ();
|
|
break;
|
|
default:
|
|
GST_WARNING ("Unknown memory type %i", mem->group->buffer.memory);
|
|
break;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
static gboolean
|
|
_v4l2mem_unmap (GstV4l2Memory * mem)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
switch (mem->group->buffer.memory) {
|
|
case V4L2_MEMORY_MMAP:
|
|
case V4L2_MEMORY_USERPTR:
|
|
ret = TRUE;
|
|
break;
|
|
case V4L2_MEMORY_DMABUF:
|
|
/* v4l2 dmabuf memory are not share with downstream */
|
|
g_assert_not_reached ();
|
|
break;
|
|
default:
|
|
GST_WARNING ("Unknown memory type %i", mem->group->buffer.memory);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
_v4l2mem_dispose (GstV4l2Memory * mem)
|
|
{
|
|
GstV4l2Allocator *allocator = (GstV4l2Allocator *) mem->mem.allocator;
|
|
GstV4l2MemoryGroup *group = mem->group;
|
|
gboolean ret;
|
|
|
|
if (group->mem[mem->plane]) {
|
|
/* We may have a dmabuf, replace it with returned original memory */
|
|
group->mem[mem->plane] = gst_memory_ref ((GstMemory *) mem);
|
|
gst_v4l2_allocator_release (allocator, mem);
|
|
ret = FALSE;
|
|
} else {
|
|
gst_object_ref (allocator);
|
|
ret = TRUE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline GstV4l2Memory *
|
|
_v4l2mem_new (GstMemoryFlags flags, GstAllocator * allocator,
|
|
GstMemory * parent, gsize maxsize, gsize align, gsize offset, gsize size,
|
|
gint plane, gpointer data, int dmafd, GstV4l2MemoryGroup * group)
|
|
{
|
|
GstV4l2Memory *mem;
|
|
|
|
mem = g_slice_new0 (GstV4l2Memory);
|
|
gst_memory_init (GST_MEMORY_CAST (mem),
|
|
flags, allocator, parent, maxsize, align, offset, size);
|
|
|
|
if (parent == NULL)
|
|
mem->mem.mini_object.dispose =
|
|
(GstMiniObjectDisposeFunction) _v4l2mem_dispose;
|
|
|
|
mem->plane = plane;
|
|
mem->data = data;
|
|
mem->dmafd = dmafd;
|
|
mem->group = group;
|
|
|
|
return mem;
|
|
}
|
|
|
|
static GstV4l2Memory *
|
|
_v4l2mem_share (GstV4l2Memory * mem, gssize offset, gsize size)
|
|
{
|
|
GstV4l2Memory *sub;
|
|
GstMemory *parent;
|
|
|
|
/* find the real parent */
|
|
if ((parent = mem->mem.parent) == NULL)
|
|
parent = (GstMemory *) mem;
|
|
|
|
if (size == -1)
|
|
size = mem->mem.size - offset;
|
|
|
|
/* the shared memory is always readonly */
|
|
sub = _v4l2mem_new (GST_MINI_OBJECT_FLAGS (parent) |
|
|
GST_MINI_OBJECT_FLAG_LOCK_READONLY, mem->mem.allocator, parent,
|
|
mem->mem.maxsize, mem->mem.align, offset, size, mem->plane, mem->data,
|
|
-1, mem->group);
|
|
|
|
return sub;
|
|
}
|
|
|
|
static gboolean
|
|
_v4l2mem_is_span (GstV4l2Memory * mem1, GstV4l2Memory * mem2, gsize * offset)
|
|
{
|
|
if (offset)
|
|
*offset = mem1->mem.offset - mem1->mem.parent->offset;
|
|
|
|
/* and memory is contiguous */
|
|
return mem1->mem.offset + mem1->mem.size == mem2->mem.offset;
|
|
}
|
|
|
|
gboolean
|
|
gst_is_v4l2_memory (GstMemory * mem)
|
|
{
|
|
return gst_memory_is_type (mem, GST_V4L2_MEMORY_TYPE);
|
|
}
|
|
|
|
GQuark
|
|
gst_v4l2_memory_quark (void)
|
|
{
|
|
static GQuark quark = 0;
|
|
|
|
if (quark == 0)
|
|
quark = g_quark_from_string ("GstV4l2Memory");
|
|
|
|
return quark;
|
|
}
|
|
|
|
|
|
/*************************************/
|
|
/* GstV4l2MemoryGroup implementation */
|
|
/*************************************/
|
|
|
|
static void
|
|
gst_v4l2_memory_group_free (GstV4l2MemoryGroup * group)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
GstMemory *mem = group->mem[i];
|
|
group->mem[i] = NULL;
|
|
if (mem)
|
|
gst_memory_unref (mem);
|
|
}
|
|
|
|
g_slice_free (GstV4l2MemoryGroup, group);
|
|
}
|
|
|
|
static GstV4l2MemoryGroup *
|
|
gst_v4l2_memory_group_new (GstV4l2Allocator * allocator, guint32 index)
|
|
{
|
|
gint video_fd = allocator->video_fd;
|
|
guint32 memory = allocator->memory;
|
|
struct v4l2_format *format = &allocator->format;
|
|
GstV4l2MemoryGroup *group;
|
|
gsize img_size, buf_size;
|
|
|
|
group = g_slice_new0 (GstV4l2MemoryGroup);
|
|
|
|
group->buffer.type = format->type;
|
|
group->buffer.index = index;
|
|
group->buffer.memory = memory;
|
|
|
|
if (V4L2_TYPE_IS_MULTIPLANAR (format->type)) {
|
|
group->n_mem = group->buffer.length = format->fmt.pix_mp.num_planes;
|
|
group->buffer.m.planes = group->planes;
|
|
} else {
|
|
group->n_mem = 1;
|
|
}
|
|
|
|
if (v4l2_ioctl (video_fd, VIDIOC_QUERYBUF, &group->buffer) < 0)
|
|
goto querybuf_failed;
|
|
|
|
if (group->buffer.index != index) {
|
|
GST_ERROR_OBJECT (allocator, "Buffer index returned by VIDIOC_QUERYBUF "
|
|
"didn't match, this indicate the presence of a bug in your driver or "
|
|
"libv4l2");
|
|
g_slice_free (GstV4l2MemoryGroup, group);
|
|
return NULL;
|
|
}
|
|
|
|
/* Check that provided size matches the format we have negotiation. Failing
|
|
* there usually means a driver of libv4l bug. */
|
|
if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
gint i;
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
img_size = allocator->format.fmt.pix_mp.plane_fmt[i].sizeimage;
|
|
buf_size = group->planes[i].length;
|
|
if (buf_size < img_size)
|
|
goto buffer_too_short;
|
|
}
|
|
} else {
|
|
img_size = allocator->format.fmt.pix.sizeimage;
|
|
buf_size = group->buffer.length;
|
|
if (buf_size < img_size)
|
|
goto buffer_too_short;
|
|
}
|
|
|
|
/* We save non planar buffer information into the multi-planar plane array
|
|
* to avoid duplicating the code later */
|
|
if (!V4L2_TYPE_IS_MULTIPLANAR (format->type)) {
|
|
group->planes[0].bytesused = group->buffer.bytesused;
|
|
group->planes[0].length = group->buffer.length;
|
|
g_assert (sizeof (group->planes[0].m) == sizeof (group->buffer.m));
|
|
memcpy (&group->planes[0].m, &group->buffer.m, sizeof (group->buffer.m));
|
|
}
|
|
|
|
GST_LOG_OBJECT (allocator, "Got %s buffer", memory_type_to_str (memory));
|
|
GST_LOG_OBJECT (allocator, " index: %u", group->buffer.index);
|
|
GST_LOG_OBJECT (allocator, " type: %d", group->buffer.type);
|
|
GST_LOG_OBJECT (allocator, " flags: %08x", group->buffer.flags);
|
|
GST_LOG_OBJECT (allocator, " field: %d", group->buffer.field);
|
|
GST_LOG_OBJECT (allocator, " memory: %d", group->buffer.memory);
|
|
GST_LOG_OBJECT (allocator, " planes: %d", group->n_mem);
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
if (memory == V4L2_MEMORY_MMAP) {
|
|
gint i;
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
GST_LOG_OBJECT (allocator, " [%u] bytesused: %u, length: %u", i,
|
|
group->planes[i].bytesused, group->planes[i].length);
|
|
GST_LOG_OBJECT (allocator, " [%u] MMAP offset: %u", i,
|
|
group->planes[i].m.mem_offset);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return group;
|
|
|
|
querybuf_failed:
|
|
{
|
|
GST_ERROR ("error querying buffer %d: %s", index, g_strerror (errno));
|
|
goto failed;
|
|
}
|
|
buffer_too_short:
|
|
{
|
|
GST_ERROR ("buffer size %" G_GSIZE_FORMAT
|
|
" is smaller then negotiated size %" G_GSIZE_FORMAT
|
|
", this is usually the result of a bug in the v4l2 driver or libv4l.",
|
|
buf_size, img_size);
|
|
goto failed;
|
|
}
|
|
failed:
|
|
gst_v4l2_memory_group_free (group);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*************************************/
|
|
/* GstV4lAllocator implementation */
|
|
/*************************************/
|
|
|
|
static void
|
|
gst_v4l2_allocator_release (GstV4l2Allocator * allocator, GstV4l2Memory * mem)
|
|
{
|
|
GstV4l2MemoryGroup *group = mem->group;
|
|
|
|
GST_LOG_OBJECT (allocator, "plane %i of buffer %u released",
|
|
mem->plane, group->buffer.index);
|
|
|
|
switch (allocator->memory) {
|
|
case V4L2_MEMORY_DMABUF:
|
|
close (mem->dmafd);
|
|
mem->dmafd = -1;
|
|
break;
|
|
case V4L2_MEMORY_USERPTR:
|
|
mem->data = NULL;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* When all memory are back, put the group back in the free queue */
|
|
if (g_atomic_int_dec_and_test (&group->mems_allocated)) {
|
|
GST_LOG_OBJECT (allocator, "buffer %u released", group->buffer.index);
|
|
gst_atomic_queue_push (allocator->free_queue, group);
|
|
g_signal_emit (allocator, gst_v4l2_allocator_signals[GROUP_RELEASED], 0);
|
|
}
|
|
|
|
/* Keep last, allocator may be freed after this call */
|
|
g_object_unref (allocator);
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_allocator_free (GstAllocator * gallocator, GstMemory * gmem)
|
|
{
|
|
GstV4l2Allocator *allocator = (GstV4l2Allocator *) gallocator;
|
|
GstV4l2Memory *mem = (GstV4l2Memory *) gmem;
|
|
GstV4l2MemoryGroup *group = mem->group;
|
|
|
|
/* Only free unparented memory */
|
|
if (mem->mem.parent == NULL) {
|
|
GST_LOG_OBJECT (allocator, "freeing plane %i of buffer %u",
|
|
mem->plane, group->buffer.index);
|
|
|
|
if (allocator->memory == V4L2_MEMORY_MMAP) {
|
|
if (mem->data)
|
|
v4l2_munmap (mem->data, group->planes[mem->plane].length);
|
|
}
|
|
|
|
/* This apply for both mmap with expbuf, and dmabuf imported memory */
|
|
if (mem->dmafd >= 0)
|
|
close (mem->dmafd);
|
|
}
|
|
|
|
g_slice_free (GstV4l2Memory, mem);
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_allocator_dispose (GObject * obj)
|
|
{
|
|
GstV4l2Allocator *allocator = (GstV4l2Allocator *) obj;
|
|
gint i;
|
|
|
|
GST_LOG_OBJECT (obj, "called");
|
|
|
|
for (i = 0; i < allocator->count; i++) {
|
|
GstV4l2MemoryGroup *group = allocator->groups[i];
|
|
allocator->groups[i] = NULL;
|
|
if (group)
|
|
gst_v4l2_memory_group_free (group);
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (obj);
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_allocator_finalize (GObject * obj)
|
|
{
|
|
GstV4l2Allocator *allocator = (GstV4l2Allocator *) obj;
|
|
|
|
GST_LOG_OBJECT (obj, "called");
|
|
|
|
v4l2_close (allocator->video_fd);
|
|
gst_atomic_queue_unref (allocator->free_queue);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_allocator_class_init (GstV4l2AllocatorClass * klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
GstAllocatorClass *allocator_class;
|
|
|
|
allocator_class = (GstAllocatorClass *) klass;
|
|
object_class = (GObjectClass *) klass;
|
|
|
|
allocator_class->alloc = NULL;
|
|
allocator_class->free = gst_v4l2_allocator_free;
|
|
|
|
object_class->dispose = gst_v4l2_allocator_dispose;
|
|
object_class->finalize = gst_v4l2_allocator_finalize;
|
|
|
|
gst_v4l2_allocator_signals[GROUP_RELEASED] = g_signal_new ("group-released",
|
|
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (v4l2allocator_debug, "v4l2allocator", 0,
|
|
"V4L2 Allocator");
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_allocator_init (GstV4l2Allocator * allocator)
|
|
{
|
|
GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
|
|
|
|
alloc->mem_type = GST_V4L2_MEMORY_TYPE;
|
|
alloc->mem_map = (GstMemoryMapFunction) _v4l2mem_map;
|
|
alloc->mem_unmap = (GstMemoryUnmapFunction) _v4l2mem_unmap;
|
|
alloc->mem_share = (GstMemoryShareFunction) _v4l2mem_share;
|
|
alloc->mem_is_span = (GstMemoryIsSpanFunction) _v4l2mem_is_span;
|
|
/* Use the default, fallback copy function */
|
|
|
|
allocator->free_queue = gst_atomic_queue_new (VIDEO_MAX_FRAME);
|
|
|
|
GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
|
|
}
|
|
|
|
#define GST_V4L2_ALLOCATOR_PROBE(obj,type) \
|
|
gst_v4l2_allocator_probe ((obj), V4L2_MEMORY_ ## type, \
|
|
GST_V4L2_ALLOCATOR_FLAG_ ## type ## _REQBUFS, \
|
|
GST_V4L2_ALLOCATOR_FLAG_ ## type ## _CREATE_BUFS)
|
|
static guint32
|
|
gst_v4l2_allocator_probe (GstV4l2Allocator * allocator, guint32 memory,
|
|
guint32 breq_flag, guint32 bcreate_flag)
|
|
{
|
|
struct v4l2_requestbuffers breq = { 0 };
|
|
guint32 flags = 0;
|
|
|
|
breq.type = allocator->type;
|
|
breq.count = 0;
|
|
breq.memory = memory;
|
|
|
|
if (v4l2_ioctl (allocator->video_fd, VIDIOC_REQBUFS, &breq) == 0) {
|
|
struct v4l2_create_buffers bcreate = { 0 };
|
|
|
|
flags |= breq_flag;
|
|
|
|
bcreate.memory = allocator->type;
|
|
bcreate.format = allocator->format;
|
|
|
|
if ((v4l2_ioctl (allocator->video_fd, VIDIOC_CREATE_BUFS, &bcreate) == 0))
|
|
flags |= bcreate_flag;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
static GstV4l2MemoryGroup *
|
|
gst_v4l2_allocator_create_buf (GstV4l2Allocator * allocator)
|
|
{
|
|
struct v4l2_create_buffers bcreate = { 0 };
|
|
GstV4l2MemoryGroup *group = NULL;
|
|
|
|
GST_OBJECT_LOCK (allocator);
|
|
|
|
if (!g_atomic_int_get (&allocator->active))
|
|
goto done;
|
|
|
|
bcreate.memory = allocator->memory;
|
|
bcreate.format = allocator->format;
|
|
bcreate.count = 1;
|
|
|
|
if (!allocator->can_allocate)
|
|
goto done;
|
|
|
|
if (v4l2_ioctl (allocator->video_fd, VIDIOC_CREATE_BUFS, &bcreate) < 0)
|
|
goto create_bufs_failed;
|
|
|
|
if (allocator->groups[bcreate.index] != NULL)
|
|
goto create_bufs_bug;
|
|
|
|
group = gst_v4l2_memory_group_new (allocator, bcreate.index);
|
|
|
|
if (group) {
|
|
allocator->groups[bcreate.index] = group;
|
|
allocator->count++;
|
|
}
|
|
|
|
done:
|
|
GST_OBJECT_UNLOCK (allocator);
|
|
return group;
|
|
|
|
create_bufs_failed:
|
|
{
|
|
GST_WARNING_OBJECT (allocator, "error creating a new buffer: %s",
|
|
g_strerror (errno));
|
|
goto done;
|
|
}
|
|
create_bufs_bug:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "created buffer has already used buffer "
|
|
"index %i, this means there is an bug in your driver or libv4l2",
|
|
bcreate.index);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
static GstV4l2MemoryGroup *
|
|
gst_v4l2_allocator_alloc (GstV4l2Allocator * allocator)
|
|
{
|
|
GstV4l2MemoryGroup *group;
|
|
|
|
if (!g_atomic_int_get (&allocator->active))
|
|
return NULL;
|
|
|
|
group = gst_atomic_queue_pop (allocator->free_queue);
|
|
|
|
if (group == NULL) {
|
|
if (allocator->can_allocate) {
|
|
group = gst_v4l2_allocator_create_buf (allocator);
|
|
|
|
/* Don't hammer on CREATE_BUFS */
|
|
if (group == NULL)
|
|
allocator->can_allocate = FALSE;
|
|
}
|
|
}
|
|
|
|
return group;
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_allocator_reset_size (GstV4l2Allocator * allocator,
|
|
GstV4l2MemoryGroup * group)
|
|
{
|
|
gsize size;
|
|
gboolean imported = FALSE;
|
|
|
|
switch (allocator->memory) {
|
|
case V4L2_MEMORY_USERPTR:
|
|
case V4L2_MEMORY_DMABUF:
|
|
imported = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
gint i;
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
size = allocator->format.fmt.pix_mp.plane_fmt[i].sizeimage;
|
|
|
|
if (imported)
|
|
group->mem[i]->maxsize = size;
|
|
|
|
gst_memory_resize (group->mem[i], 0, size);
|
|
}
|
|
|
|
} else {
|
|
size = allocator->format.fmt.pix.sizeimage;
|
|
|
|
if (imported)
|
|
group->mem[0]->maxsize = size;
|
|
|
|
gst_memory_resize (group->mem[0], 0, size);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cleanup_failed_alloc (GstV4l2Allocator * allocator, GstV4l2MemoryGroup * group)
|
|
{
|
|
if (group->mems_allocated > 0) {
|
|
gint i;
|
|
/* If one or more mmap worked, we need to unref the memory, otherwise
|
|
* they will keep a ref on the allocator and leak it. This will put back
|
|
* the group into the free_queue */
|
|
for (i = 0; i < group->n_mem; i++)
|
|
gst_memory_unref (group->mem[i]);
|
|
} else {
|
|
/* Otherwise, group has to be on free queue for _stop() to work */
|
|
gst_atomic_queue_push (allocator->free_queue, group);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
GstV4l2Allocator *
|
|
gst_v4l2_allocator_new (GstObject * parent, gint video_fd,
|
|
struct v4l2_format *format)
|
|
{
|
|
GstV4l2Allocator *allocator;
|
|
guint32 flags = 0;
|
|
gchar *name, *parent_name;
|
|
|
|
parent_name = gst_object_get_name (parent);
|
|
name = g_strconcat (parent_name, ":allocator", NULL);
|
|
g_free (parent_name);
|
|
|
|
allocator = g_object_new (GST_TYPE_V4L2_ALLOCATOR, "name", name, NULL);
|
|
g_free (name);
|
|
|
|
/* Save everything */
|
|
allocator->video_fd = v4l2_dup (video_fd);
|
|
allocator->type = format->type;
|
|
allocator->format = *format;
|
|
|
|
flags |= GST_V4L2_ALLOCATOR_PROBE (allocator, MMAP);
|
|
flags |= GST_V4L2_ALLOCATOR_PROBE (allocator, USERPTR);
|
|
flags |= GST_V4L2_ALLOCATOR_PROBE (allocator, DMABUF);
|
|
|
|
|
|
if (flags == 0) {
|
|
/* Drivers not ported from videobuf to videbuf2 don't allow freeing buffers
|
|
* using REQBUFS(0). This is a workaround to still support these drivers,
|
|
* which are known to have MMAP support. */
|
|
GST_WARNING_OBJECT (allocator, "Could not probe supported memory type, "
|
|
"assuming MMAP is supported, this is expected for older drivers not "
|
|
" yet ported to videobuf2 framework");
|
|
flags = GST_V4L2_ALLOCATOR_FLAG_MMAP_REQBUFS;
|
|
}
|
|
|
|
GST_OBJECT_FLAG_SET (allocator, flags);
|
|
|
|
return allocator;
|
|
}
|
|
|
|
guint
|
|
gst_v4l2_allocator_start (GstV4l2Allocator * allocator, guint32 count,
|
|
guint32 memory)
|
|
{
|
|
struct v4l2_requestbuffers breq = { count, allocator->type, memory };
|
|
gboolean can_allocate;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (count != 0, 0);
|
|
|
|
GST_OBJECT_LOCK (allocator);
|
|
|
|
if (g_atomic_int_get (&allocator->active))
|
|
goto already_active;
|
|
|
|
if (v4l2_ioctl (allocator->video_fd, VIDIOC_REQBUFS, &breq) < 0)
|
|
goto reqbufs_failed;
|
|
|
|
if (breq.count < 1)
|
|
goto out_of_memory;
|
|
|
|
switch (memory) {
|
|
case V4L2_MEMORY_MMAP:
|
|
can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (allocator, MMAP);
|
|
break;
|
|
case V4L2_MEMORY_USERPTR:
|
|
can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (allocator, USERPTR);
|
|
break;
|
|
case V4L2_MEMORY_DMABUF:
|
|
can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (allocator, DMABUF);
|
|
break;
|
|
default:
|
|
can_allocate = FALSE;
|
|
break;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (allocator, "allocated %u %s buffers out of %u requested",
|
|
breq.count, memory_type_to_str (memory), count);
|
|
|
|
allocator->can_allocate = can_allocate;
|
|
allocator->count = breq.count;
|
|
allocator->memory = memory;
|
|
|
|
/* Create memory groups */
|
|
for (i = 0; i < allocator->count; i++) {
|
|
allocator->groups[i] = gst_v4l2_memory_group_new (allocator, i);
|
|
if (allocator->groups[i] == NULL)
|
|
goto error;
|
|
|
|
gst_atomic_queue_push (allocator->free_queue, allocator->groups[i]);
|
|
}
|
|
|
|
g_atomic_int_set (&allocator->active, TRUE);
|
|
|
|
done:
|
|
GST_OBJECT_UNLOCK (allocator);
|
|
return breq.count;
|
|
|
|
already_active:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "allocator already active");
|
|
goto error;
|
|
}
|
|
reqbufs_failed:
|
|
{
|
|
GST_ERROR_OBJECT (allocator,
|
|
"error requesting %d buffers: %s", count, g_strerror (errno));
|
|
goto error;
|
|
}
|
|
out_of_memory:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "Not enough memory to allocate buffers");
|
|
goto error;
|
|
}
|
|
error:
|
|
{
|
|
breq.count = 0;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
GstV4l2Return
|
|
gst_v4l2_allocator_stop (GstV4l2Allocator * allocator)
|
|
{
|
|
struct v4l2_requestbuffers breq = { 0, allocator->type, allocator->memory };
|
|
gint i = 0;
|
|
GstV4l2Return ret = GST_V4L2_OK;
|
|
|
|
GST_DEBUG_OBJECT (allocator, "stop allocator");
|
|
|
|
GST_OBJECT_LOCK (allocator);
|
|
|
|
if (!g_atomic_int_get (&allocator->active))
|
|
goto done;
|
|
|
|
if (gst_atomic_queue_length (allocator->free_queue) != allocator->count) {
|
|
GST_DEBUG_OBJECT (allocator, "allocator is still in use");
|
|
ret = GST_V4L2_BUSY;
|
|
goto done;
|
|
}
|
|
|
|
while (gst_atomic_queue_pop (allocator->free_queue)) {
|
|
/* nothing */
|
|
};
|
|
|
|
for (i = 0; i < allocator->count; i++) {
|
|
GstV4l2MemoryGroup *group = allocator->groups[i];
|
|
allocator->groups[i] = NULL;
|
|
if (group)
|
|
gst_v4l2_memory_group_free (group);
|
|
}
|
|
|
|
/* Not all drivers support rebufs(0), so warn only */
|
|
if (v4l2_ioctl (allocator->video_fd, VIDIOC_REQBUFS, &breq) < 0)
|
|
GST_WARNING_OBJECT (allocator,
|
|
"error releasing buffers buffers: %s", g_strerror (errno));
|
|
|
|
allocator->count = 0;
|
|
|
|
g_atomic_int_set (&allocator->active, FALSE);
|
|
|
|
done:
|
|
GST_OBJECT_UNLOCK (allocator);
|
|
return ret;
|
|
}
|
|
|
|
GstV4l2MemoryGroup *
|
|
gst_v4l2_allocator_alloc_mmap (GstV4l2Allocator * allocator)
|
|
{
|
|
GstV4l2MemoryGroup *group;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (allocator->memory == V4L2_MEMORY_MMAP, NULL);
|
|
|
|
group = gst_v4l2_allocator_alloc (allocator);
|
|
|
|
if (group == NULL)
|
|
return NULL;
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
if (group->mem[i] == NULL) {
|
|
gpointer data;
|
|
data = v4l2_mmap (NULL, group->planes[i].length, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED, allocator->video_fd, group->planes[i].m.mem_offset);
|
|
|
|
if (data == MAP_FAILED)
|
|
goto mmap_failed;
|
|
|
|
GST_LOG_OBJECT (allocator,
|
|
"mmap buffer length %d, data offset %d, plane %d",
|
|
group->planes[i].length, group->planes[i].data_offset, i);
|
|
|
|
group->mem[i] = (GstMemory *) _v4l2mem_new (0, GST_ALLOCATOR (allocator),
|
|
NULL, group->planes[i].length, 0, 0, group->planes[i].length, i,
|
|
data, -1, group);
|
|
} else {
|
|
/* Take back the allocator reference */
|
|
gst_object_ref (allocator);
|
|
}
|
|
|
|
group->mems_allocated++;
|
|
}
|
|
|
|
/* Ensure group size. Unlike GST, v4l2 have size (bytesused) initially set
|
|
* to 0. As length might be bigger then the expected size exposed in the
|
|
* format, we simply set bytesused initially and reset it here for
|
|
* simplicity */
|
|
gst_v4l2_allocator_reset_size (allocator, group);
|
|
|
|
return group;
|
|
|
|
mmap_failed:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "Failed to mmap buffer: %s",
|
|
g_strerror (errno));
|
|
_cleanup_failed_alloc (allocator, group);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
GstV4l2MemoryGroup *
|
|
gst_v4l2_allocator_alloc_dmabuf (GstV4l2Allocator * allocator,
|
|
GstAllocator * dmabuf_allocator)
|
|
{
|
|
GstV4l2MemoryGroup *group;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (allocator->memory == V4L2_MEMORY_MMAP, NULL);
|
|
|
|
group = gst_v4l2_allocator_alloc (allocator);
|
|
|
|
if (group == NULL)
|
|
return NULL;
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
GstV4l2Memory *mem;
|
|
GstMemory *dma_mem;
|
|
gint dmafd;
|
|
|
|
if (group->mem[i] == NULL) {
|
|
struct v4l2_exportbuffer expbuf = { 0 };
|
|
|
|
expbuf.type = allocator->type;
|
|
expbuf.index = group->buffer.index;
|
|
expbuf.plane = i;
|
|
expbuf.flags = O_CLOEXEC | O_RDWR;
|
|
|
|
if (v4l2_ioctl (allocator->video_fd, VIDIOC_EXPBUF, &expbuf) < 0)
|
|
goto expbuf_failed;
|
|
|
|
GST_LOG_OBJECT (allocator, "exported DMABUF as fd %i plane %d",
|
|
expbuf.fd, i);
|
|
|
|
group->mem[i] = (GstMemory *) _v4l2mem_new (0, GST_ALLOCATOR (allocator),
|
|
NULL, group->planes[i].length, 0, 0, group->planes[i].length, i,
|
|
NULL, expbuf.fd, group);
|
|
} else {
|
|
/* Take back the allocator reference */
|
|
gst_object_ref (allocator);
|
|
}
|
|
|
|
g_assert (gst_is_v4l2_memory (group->mem[i]));
|
|
mem = (GstV4l2Memory *) group->mem[i];
|
|
|
|
if ((dmafd = dup (mem->dmafd)) < 0)
|
|
goto dup_failed;
|
|
|
|
dma_mem = gst_dmabuf_allocator_alloc (dmabuf_allocator, dmafd,
|
|
mem->mem.maxsize);
|
|
|
|
gst_mini_object_set_qdata (GST_MINI_OBJECT (dma_mem),
|
|
GST_V4L2_MEMORY_QUARK, mem, (GDestroyNotify) gst_memory_unref);
|
|
|
|
group->mem[i] = dma_mem;
|
|
group->mems_allocated++;
|
|
}
|
|
|
|
gst_v4l2_allocator_reset_size (allocator, group);
|
|
|
|
return group;
|
|
|
|
expbuf_failed:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "Failed to export DMABUF: %s",
|
|
g_strerror (errno));
|
|
goto cleanup;
|
|
}
|
|
dup_failed:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "Failed to dup DMABUF descriptor: %s",
|
|
g_strerror (errno));
|
|
goto cleanup;
|
|
}
|
|
cleanup:
|
|
{
|
|
_cleanup_failed_alloc (allocator, group);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_allocator_clear_dmabufin (GstV4l2Allocator * allocator,
|
|
GstV4l2MemoryGroup * group)
|
|
{
|
|
GstV4l2Memory *mem;
|
|
gint i;
|
|
|
|
g_return_if_fail (allocator->memory == V4L2_MEMORY_DMABUF);
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
|
|
mem = (GstV4l2Memory *) group->mem[i];
|
|
|
|
GST_LOG_OBJECT (allocator, "clearing DMABUF import, fd %i plane %d",
|
|
mem->dmafd, i);
|
|
|
|
if (mem->dmafd >= 0)
|
|
close (mem->dmafd);
|
|
|
|
/* Update memory */
|
|
mem->mem.maxsize = 0;
|
|
mem->mem.offset = 0;
|
|
mem->mem.size = 0;
|
|
mem->dmafd = -1;
|
|
|
|
/* Update v4l2 structure */
|
|
group->planes[i].length = 0;
|
|
group->planes[i].bytesused = 0;
|
|
group->planes[i].m.fd = -1;
|
|
group->planes[i].data_offset = 0;
|
|
}
|
|
|
|
if (!V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
group->buffer.bytesused = 0;
|
|
group->buffer.length = 0;
|
|
group->buffer.m.fd = -1;
|
|
}
|
|
}
|
|
|
|
GstV4l2MemoryGroup *
|
|
gst_v4l2_allocator_alloc_dmabufin (GstV4l2Allocator * allocator)
|
|
{
|
|
GstV4l2MemoryGroup *group;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (allocator->memory == V4L2_MEMORY_DMABUF, NULL);
|
|
|
|
group = gst_v4l2_allocator_alloc (allocator);
|
|
|
|
if (group == NULL)
|
|
return NULL;
|
|
|
|
GST_LOG_OBJECT (allocator, "allocating empty DMABUF import group");
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
if (group->mem[i] == NULL) {
|
|
group->mem[i] = (GstMemory *) _v4l2mem_new (0, GST_ALLOCATOR (allocator),
|
|
NULL, 0, 0, 0, 0, i, NULL, -1, group);
|
|
} else {
|
|
/* Take back the allocator reference */
|
|
gst_object_ref (allocator);
|
|
}
|
|
|
|
group->mems_allocated++;
|
|
}
|
|
|
|
gst_v4l2_allocator_clear_dmabufin (allocator, group);
|
|
|
|
return group;
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_allocator_clear_userptr (GstV4l2Allocator * allocator,
|
|
GstV4l2MemoryGroup * group)
|
|
{
|
|
GstV4l2Memory *mem;
|
|
gint i;
|
|
|
|
g_return_if_fail (allocator->memory == V4L2_MEMORY_USERPTR);
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
mem = (GstV4l2Memory *) group->mem[i];
|
|
|
|
GST_LOG_OBJECT (allocator, "clearing USERPTR %p plane %d size %"
|
|
G_GSIZE_FORMAT, mem->data, i, mem->mem.size);
|
|
|
|
mem->mem.maxsize = 0;
|
|
mem->mem.size = 0;
|
|
mem->data = NULL;
|
|
|
|
group->planes[i].length = 0;
|
|
group->planes[i].bytesused = 0;
|
|
group->planes[i].m.userptr = 0;
|
|
}
|
|
|
|
if (!V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
group->buffer.bytesused = 0;
|
|
group->buffer.length = 0;
|
|
group->buffer.m.userptr = 0;
|
|
}
|
|
}
|
|
|
|
GstV4l2MemoryGroup *
|
|
gst_v4l2_allocator_alloc_userptr (GstV4l2Allocator * allocator)
|
|
{
|
|
GstV4l2MemoryGroup *group;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (allocator->memory == V4L2_MEMORY_USERPTR, NULL);
|
|
|
|
group = gst_v4l2_allocator_alloc (allocator);
|
|
|
|
if (group == NULL)
|
|
return NULL;
|
|
|
|
GST_LOG_OBJECT (allocator, "allocating empty USERPTR group");
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
|
|
if (group->mem[i] == NULL) {
|
|
group->mem[i] = (GstMemory *) _v4l2mem_new (0, GST_ALLOCATOR (allocator),
|
|
NULL, 0, 0, 0, 0, i, NULL, -1, group);
|
|
} else {
|
|
/* Take back the allocator reference */
|
|
gst_object_ref (allocator);
|
|
}
|
|
|
|
group->mems_allocated++;
|
|
}
|
|
|
|
gst_v4l2_allocator_clear_userptr (allocator, group);
|
|
|
|
return group;
|
|
}
|
|
|
|
gboolean
|
|
gst_v4l2_allocator_import_dmabuf (GstV4l2Allocator * allocator,
|
|
GstV4l2MemoryGroup * group, gint n_mem, GstMemory ** dma_mem)
|
|
{
|
|
GstV4l2Memory *mem;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (allocator->memory == V4L2_MEMORY_DMABUF, FALSE);
|
|
|
|
if (group->n_mem != n_mem)
|
|
goto n_mem_missmatch;
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
gint dmafd;
|
|
gsize size, offset, maxsize;
|
|
|
|
if (!gst_is_dmabuf_memory (dma_mem[i]))
|
|
goto not_dmabuf;
|
|
|
|
size = gst_memory_get_sizes (dma_mem[i], &offset, &maxsize);
|
|
|
|
if ((dmafd = dup (gst_dmabuf_memory_get_fd (dma_mem[i]))) < 0)
|
|
goto dup_failed;
|
|
|
|
GST_LOG_OBJECT (allocator, "imported DMABUF as fd %i plane %d", dmafd, i);
|
|
|
|
mem = (GstV4l2Memory *) group->mem[i];
|
|
|
|
/* Update memory */
|
|
mem->mem.maxsize = maxsize;
|
|
mem->mem.offset = offset;
|
|
mem->mem.size = size;
|
|
mem->dmafd = dmafd;
|
|
|
|
/* Update v4l2 structure */
|
|
group->planes[i].length = maxsize;
|
|
group->planes[i].bytesused = size;
|
|
group->planes[i].m.fd = dmafd;
|
|
group->planes[i].data_offset = offset;
|
|
}
|
|
|
|
/* Copy into buffer structure if not using planes */
|
|
if (!V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
group->buffer.bytesused = group->planes[0].bytesused;
|
|
group->buffer.length = group->planes[0].length;
|
|
group->buffer.m.fd = group->planes[0].m.userptr;
|
|
} else {
|
|
group->buffer.length = group->n_mem;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
n_mem_missmatch:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "Got %i dmabuf but needed %i", n_mem,
|
|
group->n_mem);
|
|
return FALSE;
|
|
}
|
|
not_dmabuf:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "Memory %i is not of DMABUF", i);
|
|
return FALSE;
|
|
}
|
|
dup_failed:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "Failed to dup DMABUF descriptor: %s",
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gst_v4l2_allocator_import_userptr (GstV4l2Allocator * allocator,
|
|
GstV4l2MemoryGroup * group, gsize img_size, int n_planes,
|
|
gpointer * data, gsize * size)
|
|
{
|
|
GstV4l2Memory *mem;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (allocator->memory == V4L2_MEMORY_USERPTR, FALSE);
|
|
|
|
/* TODO Support passing N plane from 1 memory to MPLANE v4l2 format */
|
|
if (n_planes != group->n_mem)
|
|
goto n_mem_missmatch;
|
|
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
gsize maxsize;
|
|
|
|
if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
struct v4l2_pix_format_mplane *pix = &allocator->format.fmt.pix_mp;
|
|
maxsize = pix->plane_fmt[i].sizeimage;
|
|
} else {
|
|
maxsize = allocator->format.fmt.pix.sizeimage;
|
|
}
|
|
|
|
g_assert (size[i] <= img_size);
|
|
|
|
GST_LOG_OBJECT (allocator, "imported USERPTR %p plane %d size %"
|
|
G_GSIZE_FORMAT, data[i], i, size[i]);
|
|
|
|
mem = (GstV4l2Memory *) group->mem[i];
|
|
|
|
mem->mem.maxsize = maxsize;
|
|
mem->mem.size = size[i];
|
|
mem->data = data[i];
|
|
|
|
group->planes[i].length = maxsize;
|
|
group->planes[i].bytesused = size[i];
|
|
group->planes[i].m.userptr = (unsigned long) data[i];
|
|
group->planes[i].data_offset = 0;
|
|
}
|
|
|
|
/* Copy into buffer structure if not using planes */
|
|
if (!V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
group->buffer.bytesused = group->planes[0].bytesused;
|
|
group->buffer.length = group->planes[0].length;
|
|
group->buffer.m.userptr = group->planes[0].m.userptr;
|
|
} else {
|
|
group->buffer.length = group->n_mem;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
n_mem_missmatch:
|
|
{
|
|
GST_ERROR_OBJECT (allocator, "Got %i userptr plane while driver need %i",
|
|
n_planes, group->n_mem);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_v4l2_allocator_flush (GstV4l2Allocator * allocator)
|
|
{
|
|
gint i;
|
|
|
|
GST_OBJECT_LOCK (allocator);
|
|
|
|
if (!g_atomic_int_get (&allocator->active))
|
|
goto done;
|
|
|
|
for (i = 0; i < allocator->count; i++) {
|
|
GstV4l2MemoryGroup *group = allocator->groups[i];
|
|
gint n;
|
|
|
|
if (IS_QUEUED (group->buffer)) {
|
|
UNSET_QUEUED (group->buffer);
|
|
|
|
gst_v4l2_allocator_reset_group (allocator, group);
|
|
|
|
for (n = 0; n < group->n_mem; n++)
|
|
gst_memory_unref (group->mem[n]);
|
|
}
|
|
}
|
|
|
|
done:
|
|
GST_OBJECT_UNLOCK (allocator);
|
|
}
|
|
|
|
gboolean
|
|
gst_v4l2_allocator_qbuf (GstV4l2Allocator * allocator,
|
|
GstV4l2MemoryGroup * group)
|
|
{
|
|
gboolean ret = TRUE;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (g_atomic_int_get (&allocator->active), FALSE);
|
|
|
|
/* update sizes */
|
|
if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
for (i = 0; i < group->n_mem; i++)
|
|
group->planes[i].bytesused =
|
|
gst_memory_get_sizes (group->mem[i], NULL, NULL);
|
|
} else {
|
|
group->buffer.bytesused = gst_memory_get_sizes (group->mem[0], NULL, NULL);
|
|
}
|
|
|
|
if (v4l2_ioctl (allocator->video_fd, VIDIOC_QBUF, &group->buffer) < 0) {
|
|
GST_ERROR_OBJECT (allocator, "failed queing buffer %i: %s",
|
|
group->buffer.index, g_strerror (errno));
|
|
ret = FALSE;
|
|
if (IS_QUEUED (group->buffer)) {
|
|
GST_DEBUG_OBJECT (allocator,
|
|
"driver pretends buffer is queued even if queue failed");
|
|
UNSET_QUEUED (group->buffer);
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
GST_LOG_OBJECT (allocator, "queued buffer %i (flags 0x%X)",
|
|
group->buffer.index, group->buffer.flags);
|
|
|
|
if (!IS_QUEUED (group->buffer)) {
|
|
GST_DEBUG_OBJECT (allocator,
|
|
"driver pretends buffer is not queued even if queue succeeded");
|
|
SET_QUEUED (group->buffer);
|
|
}
|
|
|
|
/* Ensure the memory will stay around and is RO */
|
|
for (i = 0; i < group->n_mem; i++)
|
|
gst_memory_ref (group->mem[i]);
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
GstV4l2MemoryGroup *
|
|
gst_v4l2_allocator_dqbuf (GstV4l2Allocator * allocator)
|
|
{
|
|
struct v4l2_buffer buffer = { 0 };
|
|
struct v4l2_plane planes[VIDEO_MAX_PLANES] = { {0} };
|
|
gint i;
|
|
|
|
GstV4l2MemoryGroup *group = NULL;
|
|
|
|
g_return_val_if_fail (g_atomic_int_get (&allocator->active), FALSE);
|
|
|
|
buffer.type = allocator->type;
|
|
buffer.memory = allocator->memory;
|
|
|
|
if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
buffer.length = allocator->format.fmt.pix_mp.num_planes;
|
|
buffer.m.planes = planes;
|
|
}
|
|
|
|
if (v4l2_ioctl (allocator->video_fd, VIDIOC_DQBUF, &buffer) < 0)
|
|
goto error;
|
|
|
|
group = allocator->groups[buffer.index];
|
|
|
|
if (!IS_QUEUED (group->buffer)) {
|
|
GST_ERROR_OBJECT (allocator,
|
|
"buffer %i was not queued, this indicate a driver bug.", buffer.index);
|
|
return NULL;
|
|
}
|
|
|
|
group->buffer = buffer;
|
|
|
|
GST_LOG_OBJECT (allocator, "dequeued buffer %i (flags 0x%X)", buffer.index,
|
|
buffer.flags);
|
|
|
|
if (IS_QUEUED (group->buffer)) {
|
|
GST_DEBUG_OBJECT (allocator,
|
|
"driver pretends buffer is queued even if dequeue succeeded");
|
|
UNSET_QUEUED (group->buffer);
|
|
}
|
|
|
|
if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) {
|
|
group->buffer.m.planes = group->planes;
|
|
memcpy (group->planes, buffer.m.planes, sizeof (planes));
|
|
} else {
|
|
group->planes[0].bytesused = group->buffer.bytesused;
|
|
group->planes[0].length = group->buffer.length;
|
|
g_assert (sizeof (group->planes[0].m) == sizeof (group->buffer.m));
|
|
memcpy (&group->planes[0].m, &group->buffer.m, sizeof (group->buffer.m));
|
|
}
|
|
|
|
/* And update memory size */
|
|
if (V4L2_TYPE_IS_OUTPUT (allocator->type)) {
|
|
gst_v4l2_allocator_reset_size (allocator, group);
|
|
} else {
|
|
/* for capture, simply read the size */
|
|
for (i = 0; i < group->n_mem; i++) {
|
|
gst_memory_resize (group->mem[i], 0, group->planes[i].bytesused);
|
|
}
|
|
}
|
|
|
|
/* Release the memory, possibly making it RW again */
|
|
for (i = 0; i < group->n_mem; i++)
|
|
gst_memory_unref (group->mem[i]);
|
|
|
|
return group;
|
|
|
|
error:
|
|
GST_ERROR_OBJECT (allocator, "failed dequeuing a %s buffer: %s",
|
|
memory_type_to_str (allocator->memory), g_strerror (errno));
|
|
|
|
switch (errno) {
|
|
case EAGAIN:
|
|
GST_WARNING_OBJECT (allocator,
|
|
"Non-blocking I/O has been selected using O_NONBLOCK and"
|
|
" no buffer was in the outgoing queue.");
|
|
break;
|
|
case EINVAL:
|
|
GST_ERROR_OBJECT (allocator,
|
|
"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.");
|
|
break;
|
|
case ENOMEM:
|
|
GST_ERROR_OBJECT (allocator,
|
|
"insufficient memory to enqueue a user pointer buffer");
|
|
break;
|
|
case EIO:
|
|
GST_INFO_OBJECT (allocator,
|
|
"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.");
|
|
/* have we de-queued a buffer ? */
|
|
if (!IS_QUEUED (buffer)) {
|
|
GST_DEBUG_OBJECT (allocator, "reenqueing buffer");
|
|
/* FIXME ... should we do something here? */
|
|
}
|
|
break;
|
|
case EINTR:
|
|
GST_WARNING_OBJECT (allocator, "could not sync on a buffer on device");
|
|
break;
|
|
default:
|
|
GST_WARNING_OBJECT (allocator,
|
|
"Grabbing frame got interrupted unexpectedly. %d: %s.", errno,
|
|
g_strerror (errno));
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
gst_v4l2_allocator_reset_group (GstV4l2Allocator * allocator,
|
|
GstV4l2MemoryGroup * group)
|
|
{
|
|
switch (allocator->memory) {
|
|
case V4L2_MEMORY_USERPTR:
|
|
gst_v4l2_allocator_clear_userptr (allocator, group);
|
|
break;
|
|
case V4L2_MEMORY_DMABUF:
|
|
gst_v4l2_allocator_clear_dmabufin (allocator, group);
|
|
break;
|
|
case V4L2_MEMORY_MMAP:
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
gst_v4l2_allocator_reset_size (allocator, group);
|
|
}
|