GstV4l2MemoryGroup * gst_v4l2_allocator_alloc_mmap (GstV4l2Allocator * allocator) { GstV4l2Object *obj = allocator->obj; 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 = obj->mmap (NULL, group->planes[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, obj->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; } }
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); }
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; } }
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; }
GstFlowReturn gst_v4l2_allocator_dqbuf (GstV4l2Allocator * allocator, GstV4l2MemoryGroup ** group_out) { GstV4l2Object *obj = allocator->obj; 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), GST_FLOW_ERROR); buffer.type = obj->type; buffer.memory = allocator->memory; if (V4L2_TYPE_IS_MULTIPLANAR (obj->type)) { buffer.length = obj->format.fmt.pix_mp.num_planes; buffer.m.planes = planes; } if (obj->ioctl (obj->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 GST_FLOW_ERROR; } 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 (obj->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 (obj->type)) { gst_v4l2_allocator_reset_size (allocator, group); } else { /* for capture, simply read the size */ for (i = 0; i < group->n_mem; i++) { gsize size, offset; GST_LOG_OBJECT (allocator, "Dequeued capture buffer, length: %u bytesused: %u data_offset: %u", group->planes[i].length, group->planes[i].bytesused, group->planes[i].data_offset); offset = group->planes[i].data_offset; if (group->planes[i].bytesused > group->planes[i].data_offset) { size = group->planes[i].bytesused - group->planes[i].data_offset; } else { GST_WARNING_OBJECT (allocator, "V4L2 provided buffer has bytesused %" G_GUINT32_FORMAT " which is too small to include data_offset %" G_GUINT32_FORMAT, group->planes[i].bytesused, group->planes[i].data_offset); size = group->planes[i].bytesused; } if (G_LIKELY (size + offset <= group->mem[i]->maxsize)) gst_memory_resize (group->mem[i], offset, size); else { GST_WARNING_OBJECT (allocator, "v4l2 provided buffer that is too big for the memory it was " "writing into. v4l2 claims %" G_GSIZE_FORMAT " bytes used but " "memory is only %" G_GSIZE_FORMAT "B. This is probably a driver " "bug.", size, group->mem[i]->maxsize); gst_memory_resize (group->mem[i], 0, group->mem[i]->maxsize); } } } /* Release the memory, possibly making it RW again */ for (i = 0; i < group->n_mem; i++) gst_memory_unref (group->mem[i]); *group_out = group; return GST_FLOW_OK; error: if (errno == EPIPE) { GST_DEBUG_OBJECT (allocator, "broken pipe signals last buffer"); return GST_FLOW_EOS; } 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, "reenqueueing 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 GST_FLOW_ERROR; }