mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 21:51:09 +00:00
462800e9c6
Instead of completely getting rid of the input buffer, copy the metadata, the flags and the timestamp into an empty buffer. This way the decoder base class can copy that information again to the output buffer. https://bugzilla.gnome.org/show_bug.cgi?id=758424
1035 lines
30 KiB
C
1035 lines
30 KiB
C
/*
|
|
* Copyright (C) 2014 Collabora Ltd.
|
|
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#include "gstv4l2videodec.h"
|
|
#include "v4l2_calls.h"
|
|
|
|
#include <string.h>
|
|
#include <gst/gst-i18n-plugin.h>
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_v4l2_video_dec_debug);
|
|
#define GST_CAT_DEFAULT gst_v4l2_video_dec_debug
|
|
|
|
static gboolean gst_v4l2_video_dec_flush (GstVideoDecoder * decoder);
|
|
|
|
typedef struct
|
|
{
|
|
gchar *device;
|
|
GstCaps *sink_caps;
|
|
GstCaps *src_caps;
|
|
} GstV4l2VideoDecCData;
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
V4L2_STD_OBJECT_PROPS
|
|
};
|
|
|
|
#define gst_v4l2_video_dec_parent_class parent_class
|
|
G_DEFINE_ABSTRACT_TYPE (GstV4l2VideoDec, gst_v4l2_video_dec,
|
|
GST_TYPE_VIDEO_DECODER);
|
|
|
|
static void
|
|
gst_v4l2_video_dec_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_OUTPUT_IO_MODE:
|
|
gst_v4l2_object_set_property_helper (self->v4l2output, prop_id, value,
|
|
pspec);
|
|
break;
|
|
case PROP_CAPTURE_IO_MODE:
|
|
gst_v4l2_object_set_property_helper (self->v4l2capture, prop_id, value,
|
|
pspec);
|
|
break;
|
|
|
|
/* By default, only set on output */
|
|
default:
|
|
if (!gst_v4l2_object_set_property_helper (self->v4l2output,
|
|
prop_id, value, pspec)) {
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_video_dec_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_OUTPUT_IO_MODE:
|
|
gst_v4l2_object_get_property_helper (self->v4l2output, prop_id, value,
|
|
pspec);
|
|
break;
|
|
case PROP_CAPTURE_IO_MODE:
|
|
gst_v4l2_object_get_property_helper (self->v4l2capture, prop_id, value,
|
|
pspec);
|
|
break;
|
|
|
|
/* By default read from output */
|
|
default:
|
|
if (!gst_v4l2_object_get_property_helper (self->v4l2output,
|
|
prop_id, value, pspec)) {
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_open (GstVideoDecoder * decoder)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
|
|
GST_DEBUG_OBJECT (self, "Opening");
|
|
|
|
if (!gst_v4l2_object_open (self->v4l2output))
|
|
goto failure;
|
|
|
|
if (!gst_v4l2_object_open_shared (self->v4l2capture, self->v4l2output))
|
|
goto failure;
|
|
|
|
self->probed_sinkcaps = gst_v4l2_object_get_caps (self->v4l2output,
|
|
gst_v4l2_object_get_codec_caps ());
|
|
|
|
if (gst_caps_is_empty (self->probed_sinkcaps))
|
|
goto no_encoded_format;
|
|
|
|
self->probed_srccaps = gst_v4l2_object_get_caps (self->v4l2capture,
|
|
gst_v4l2_object_get_raw_caps ());
|
|
|
|
if (gst_caps_is_empty (self->probed_srccaps))
|
|
goto no_raw_format;
|
|
|
|
return TRUE;
|
|
|
|
no_encoded_format:
|
|
GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS,
|
|
(_("Encoder on device %s has no supported input format"),
|
|
self->v4l2output->videodev), (NULL));
|
|
goto failure;
|
|
|
|
|
|
no_raw_format:
|
|
GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS,
|
|
(_("Encoder on device %s has no supported output format"),
|
|
self->v4l2output->videodev), (NULL));
|
|
goto failure;
|
|
|
|
failure:
|
|
if (GST_V4L2_IS_OPEN (self->v4l2output))
|
|
gst_v4l2_object_close (self->v4l2output);
|
|
|
|
if (GST_V4L2_IS_OPEN (self->v4l2capture))
|
|
gst_v4l2_object_close (self->v4l2capture);
|
|
|
|
gst_caps_replace (&self->probed_srccaps, NULL);
|
|
gst_caps_replace (&self->probed_sinkcaps, NULL);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_close (GstVideoDecoder * decoder)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
|
|
GST_DEBUG_OBJECT (self, "Closing");
|
|
|
|
gst_v4l2_object_close (self->v4l2output);
|
|
gst_v4l2_object_close (self->v4l2capture);
|
|
gst_caps_replace (&self->probed_srccaps, NULL);
|
|
gst_caps_replace (&self->probed_sinkcaps, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_start (GstVideoDecoder * decoder)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
|
|
GST_DEBUG_OBJECT (self, "Starting");
|
|
|
|
gst_v4l2_object_unlock (self->v4l2output);
|
|
g_atomic_int_set (&self->active, TRUE);
|
|
self->output_flow = GST_FLOW_OK;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_stop (GstVideoDecoder * decoder)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
|
|
GST_DEBUG_OBJECT (self, "Stopping");
|
|
|
|
gst_v4l2_object_unlock (self->v4l2output);
|
|
gst_v4l2_object_unlock (self->v4l2capture);
|
|
|
|
/* Wait for capture thread to stop */
|
|
gst_pad_stop_task (decoder->srcpad);
|
|
|
|
GST_VIDEO_DECODER_STREAM_LOCK (decoder);
|
|
self->output_flow = GST_FLOW_OK;
|
|
GST_VIDEO_DECODER_STREAM_UNLOCK (decoder);
|
|
|
|
/* Should have been flushed already */
|
|
g_assert (g_atomic_int_get (&self->active) == FALSE);
|
|
g_assert (g_atomic_int_get (&self->processing) == FALSE);
|
|
|
|
gst_v4l2_object_stop (self->v4l2output);
|
|
gst_v4l2_object_stop (self->v4l2capture);
|
|
|
|
if (self->input_state) {
|
|
gst_video_codec_state_unref (self->input_state);
|
|
self->input_state = NULL;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "Stopped");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_set_format (GstVideoDecoder * decoder,
|
|
GstVideoCodecState * state)
|
|
{
|
|
GstV4l2Error error = GST_V4L2_ERROR_INIT;
|
|
gboolean ret = TRUE;
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
|
|
GST_DEBUG_OBJECT (self, "Setting format: %" GST_PTR_FORMAT, state->caps);
|
|
|
|
if (self->input_state) {
|
|
if (gst_v4l2_object_caps_equal (self->v4l2output, state->caps)) {
|
|
GST_DEBUG_OBJECT (self, "Compatible caps");
|
|
goto done;
|
|
}
|
|
gst_video_codec_state_unref (self->input_state);
|
|
self->input_state = NULL;
|
|
|
|
/* FIXME we probably need to do more work if pools are active */
|
|
}
|
|
|
|
ret = gst_v4l2_object_set_format (self->v4l2output, state->caps, &error);
|
|
|
|
if (ret)
|
|
self->input_state = gst_video_codec_state_ref (state);
|
|
else
|
|
gst_v4l2_error (self, &error);
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_flush (GstVideoDecoder * decoder)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
|
|
GST_DEBUG_OBJECT (self, "Flushed");
|
|
|
|
/* Ensure the processing thread has stopped for the reverse playback
|
|
* discount case */
|
|
if (g_atomic_int_get (&self->processing)) {
|
|
GST_VIDEO_DECODER_STREAM_UNLOCK (decoder);
|
|
|
|
gst_v4l2_object_unlock (self->v4l2output);
|
|
gst_v4l2_object_unlock (self->v4l2capture);
|
|
gst_pad_stop_task (decoder->srcpad);
|
|
GST_VIDEO_DECODER_STREAM_LOCK (decoder);
|
|
}
|
|
|
|
self->output_flow = GST_FLOW_OK;
|
|
|
|
gst_v4l2_object_unlock_stop (self->v4l2output);
|
|
gst_v4l2_object_unlock_stop (self->v4l2capture);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_negotiate (GstVideoDecoder * decoder)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
|
|
/* We don't allow renegotiation without carefull disabling the pool */
|
|
if (self->v4l2capture->pool &&
|
|
gst_buffer_pool_is_active (GST_BUFFER_POOL (self->v4l2capture->pool)))
|
|
return TRUE;
|
|
|
|
return GST_VIDEO_DECODER_CLASS (parent_class)->negotiate (decoder);
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_decoder_cmd (GstV4l2Object * v4l2object, guint cmd, guint flags)
|
|
{
|
|
struct v4l2_decoder_cmd dcmd = { 0, };
|
|
|
|
GST_DEBUG_OBJECT (v4l2object->element,
|
|
"sending v4l2 decoder command %u with flags %u", cmd, flags);
|
|
|
|
if (!GST_V4L2_IS_OPEN (v4l2object))
|
|
return FALSE;
|
|
|
|
dcmd.cmd = cmd;
|
|
dcmd.flags = flags;
|
|
if (v4l2_ioctl (v4l2object->video_fd, VIDIOC_DECODER_CMD, &dcmd) < 0)
|
|
goto dcmd_failed;
|
|
|
|
return TRUE;
|
|
|
|
dcmd_failed:
|
|
if (errno == ENOTTY) {
|
|
GST_INFO_OBJECT (v4l2object->element,
|
|
"Failed to send decoder command %u with flags %u for '%s'. (%s)",
|
|
cmd, flags, v4l2object->videodev, g_strerror (errno));
|
|
} else {
|
|
GST_ERROR_OBJECT (v4l2object->element,
|
|
"Failed to send decoder command %u with flags %u for '%s'. (%s)",
|
|
cmd, flags, v4l2object->videodev, g_strerror (errno));
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_v4l2_video_dec_finish (GstVideoDecoder * decoder)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstBuffer *buffer;
|
|
|
|
if (!g_atomic_int_get (&self->processing))
|
|
goto done;
|
|
|
|
GST_DEBUG_OBJECT (self, "Finishing decoding");
|
|
|
|
GST_VIDEO_DECODER_STREAM_UNLOCK (decoder);
|
|
|
|
if (gst_v4l2_decoder_cmd (self->v4l2output, V4L2_DEC_CMD_STOP, 0)) {
|
|
GstTask *task = decoder->srcpad->task;
|
|
|
|
/* If the decoder stop command succeeded, just wait until processing is
|
|
* finished */
|
|
GST_OBJECT_LOCK (task);
|
|
while (GST_TASK_STATE (task) == GST_TASK_STARTED)
|
|
GST_TASK_WAIT (task);
|
|
GST_OBJECT_UNLOCK (task);
|
|
ret = GST_FLOW_FLUSHING;
|
|
} else {
|
|
/* otherwise keep queuing empty buffers until the processing thread has
|
|
* stopped, _pool_process() will return FLUSHING when that happened */
|
|
while (ret == GST_FLOW_OK) {
|
|
buffer = gst_buffer_new ();
|
|
ret =
|
|
gst_v4l2_buffer_pool_process (GST_V4L2_BUFFER_POOL (self->
|
|
v4l2output->pool), &buffer);
|
|
gst_buffer_unref (buffer);
|
|
}
|
|
}
|
|
|
|
/* and ensure the processing thread has stopped in case another error
|
|
* occured. */
|
|
gst_v4l2_object_unlock (self->v4l2capture);
|
|
gst_pad_stop_task (decoder->srcpad);
|
|
GST_VIDEO_DECODER_STREAM_LOCK (decoder);
|
|
|
|
if (ret == GST_FLOW_FLUSHING)
|
|
ret = self->output_flow;
|
|
|
|
GST_DEBUG_OBJECT (decoder, "Done draining buffers");
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static GstVideoCodecFrame *
|
|
gst_v4l2_video_dec_get_oldest_frame (GstVideoDecoder * decoder)
|
|
{
|
|
GstVideoCodecFrame *frame = NULL;
|
|
GList *frames, *l;
|
|
gint count = 0;
|
|
|
|
frames = gst_video_decoder_get_frames (decoder);
|
|
|
|
for (l = frames; l != NULL; l = l->next) {
|
|
GstVideoCodecFrame *f = l->data;
|
|
|
|
if (!frame || frame->pts > f->pts)
|
|
frame = f;
|
|
|
|
count++;
|
|
}
|
|
|
|
if (frame) {
|
|
GST_LOG_OBJECT (decoder,
|
|
"Oldest frame is %d %" GST_TIME_FORMAT " and %d frames left",
|
|
frame->system_frame_number, GST_TIME_ARGS (frame->pts), count - 1);
|
|
gst_video_codec_frame_ref (frame);
|
|
}
|
|
|
|
g_list_free_full (frames, (GDestroyNotify) gst_video_codec_frame_unref);
|
|
|
|
return frame;
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_video_dec_loop (GstVideoDecoder * decoder)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
GstV4l2BufferPool *v4l2_pool = GST_V4L2_BUFFER_POOL (self->v4l2capture->pool);
|
|
GstBufferPool *pool;
|
|
GstVideoCodecFrame *frame;
|
|
GstBuffer *buffer = NULL;
|
|
GstFlowReturn ret;
|
|
|
|
GST_LOG_OBJECT (decoder, "Allocate output buffer");
|
|
|
|
do {
|
|
/* We cannot use the base class allotate helper since it taking the internal
|
|
* stream lock. we know that the acquire may need to poll until more frames
|
|
* comes in and holding this lock would prevent that.
|
|
*/
|
|
pool = gst_video_decoder_get_buffer_pool (decoder);
|
|
|
|
/* Pool may be NULL if we started going to READY state */
|
|
if (pool == NULL) {
|
|
ret = GST_FLOW_FLUSHING;
|
|
goto beach;
|
|
}
|
|
|
|
ret = gst_buffer_pool_acquire_buffer (pool, &buffer, NULL);
|
|
g_object_unref (pool);
|
|
|
|
if (ret != GST_FLOW_OK)
|
|
goto beach;
|
|
|
|
GST_LOG_OBJECT (decoder, "Process output buffer");
|
|
ret = gst_v4l2_buffer_pool_process (v4l2_pool, &buffer);
|
|
|
|
} while (ret == GST_V4L2_FLOW_CORRUPTED_BUFFER);
|
|
|
|
if (ret != GST_FLOW_OK)
|
|
goto beach;
|
|
|
|
frame = gst_v4l2_video_dec_get_oldest_frame (decoder);
|
|
|
|
if (frame) {
|
|
frame->output_buffer = buffer;
|
|
buffer = NULL;
|
|
ret = gst_video_decoder_finish_frame (decoder, frame);
|
|
|
|
if (ret != GST_FLOW_OK)
|
|
goto beach;
|
|
} else {
|
|
GST_WARNING_OBJECT (decoder, "Decoder is producing too many buffers");
|
|
gst_buffer_unref (buffer);
|
|
}
|
|
|
|
return;
|
|
|
|
beach:
|
|
GST_DEBUG_OBJECT (decoder, "Leaving output thread: %s",
|
|
gst_flow_get_name (ret));
|
|
|
|
gst_buffer_replace (&buffer, NULL);
|
|
self->output_flow = ret;
|
|
g_atomic_int_set (&self->processing, FALSE);
|
|
gst_v4l2_object_unlock (self->v4l2output);
|
|
gst_pad_pause_task (decoder->srcpad);
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_video_dec_loop_stopped (GstV4l2VideoDec * self)
|
|
{
|
|
/* When flushing, decoding thread may never run */
|
|
if (g_atomic_int_get (&self->processing)) {
|
|
GST_DEBUG_OBJECT (self, "Early stop of decoding thread");
|
|
self->output_flow = GST_FLOW_FLUSHING;
|
|
g_atomic_int_set (&self->processing, FALSE);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "Decoding task destroyed: %s",
|
|
gst_flow_get_name (self->output_flow));
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_remove_padding (GstCapsFeatures * features,
|
|
GstStructure * structure, gpointer user_data)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (user_data);
|
|
GstVideoAlignment *align = &self->v4l2capture->align;
|
|
GstVideoInfo *info = &self->v4l2capture->info;
|
|
int width, height;
|
|
|
|
if (!gst_structure_get_int (structure, "width", &width))
|
|
return TRUE;
|
|
|
|
if (!gst_structure_get_int (structure, "height", &height))
|
|
return TRUE;
|
|
|
|
if (align->padding_left != 0 || align->padding_top != 0 ||
|
|
width != info->width + align->padding_right ||
|
|
height != info->height + align->padding_bottom)
|
|
return TRUE;
|
|
|
|
gst_structure_set (structure,
|
|
"width", G_TYPE_INT, width - align->padding_right,
|
|
"height", G_TYPE_INT, height - align->padding_bottom, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_v4l2_video_dec_handle_frame (GstVideoDecoder * decoder,
|
|
GstVideoCodecFrame * frame)
|
|
{
|
|
GstV4l2Error error = GST_V4L2_ERROR_INIT;
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
gboolean processed = FALSE;
|
|
GstBuffer *tmp;
|
|
|
|
GST_DEBUG_OBJECT (self, "Handling frame %d", frame->system_frame_number);
|
|
|
|
if (G_UNLIKELY (!g_atomic_int_get (&self->active)))
|
|
goto flushing;
|
|
|
|
if (G_UNLIKELY (!GST_V4L2_IS_ACTIVE (self->v4l2output))) {
|
|
if (!self->input_state)
|
|
goto not_negotiated;
|
|
if (!gst_v4l2_object_set_format (self->v4l2output, self->input_state->caps,
|
|
&error))
|
|
goto not_negotiated;
|
|
}
|
|
|
|
if (G_UNLIKELY (!GST_V4L2_IS_ACTIVE (self->v4l2capture))) {
|
|
GstBufferPool *pool = GST_BUFFER_POOL (self->v4l2output->pool);
|
|
GstVideoInfo info;
|
|
GstVideoCodecState *output_state;
|
|
GstBuffer *codec_data;
|
|
GstCaps *acquired_caps, *available_caps, *caps, *filter;
|
|
GstStructure *st;
|
|
|
|
GST_DEBUG_OBJECT (self, "Sending header");
|
|
|
|
codec_data = self->input_state->codec_data;
|
|
|
|
/* We are running in byte-stream mode, so we don't know the headers, but
|
|
* we need to send something, otherwise the decoder will refuse to
|
|
* intialize.
|
|
*/
|
|
if (codec_data) {
|
|
gst_buffer_ref (codec_data);
|
|
} else {
|
|
codec_data = gst_buffer_ref (frame->input_buffer);
|
|
processed = TRUE;
|
|
}
|
|
|
|
/* Ensure input internal pool is active */
|
|
if (!gst_buffer_pool_is_active (pool)) {
|
|
GstStructure *config = gst_buffer_pool_get_config (pool);
|
|
gst_buffer_pool_config_set_params (config, self->input_state->caps,
|
|
self->v4l2output->info.size, 2, 2);
|
|
|
|
/* There is no reason to refuse this config */
|
|
if (!gst_buffer_pool_set_config (pool, config))
|
|
goto activate_failed;
|
|
|
|
if (!gst_buffer_pool_set_active (pool, TRUE))
|
|
goto activate_failed;
|
|
}
|
|
|
|
GST_VIDEO_DECODER_STREAM_UNLOCK (decoder);
|
|
ret =
|
|
gst_v4l2_buffer_pool_process (GST_V4L2_BUFFER_POOL (self->
|
|
v4l2output->pool), &codec_data);
|
|
GST_VIDEO_DECODER_STREAM_LOCK (decoder);
|
|
|
|
gst_buffer_unref (codec_data);
|
|
|
|
/* For decoders G_FMT returns coded size, G_SELECTION returns visible size
|
|
* in the compose rectangle. gst_v4l2_object_acquire_format() checks both
|
|
* and returns the visible size as with/height and the coded size as
|
|
* padding. */
|
|
if (!gst_v4l2_object_acquire_format (self->v4l2capture, &info))
|
|
goto not_negotiated;
|
|
|
|
/* Create caps from the acquired format, remove the format field */
|
|
acquired_caps = gst_video_info_to_caps (&info);
|
|
st = gst_caps_get_structure (acquired_caps, 0);
|
|
gst_structure_remove_field (st, "format");
|
|
|
|
/* Probe currently available pixel formats */
|
|
available_caps = gst_v4l2_object_probe_caps (self->v4l2capture, NULL);
|
|
available_caps = gst_caps_make_writable (available_caps);
|
|
|
|
/* Replace coded size with visible size, we want to negotiate visible size
|
|
* with downstream, not coded size. */
|
|
gst_caps_map_in_place (available_caps, gst_v4l2_video_remove_padding, self);
|
|
|
|
filter = gst_caps_intersect_full (available_caps, acquired_caps,
|
|
GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (acquired_caps);
|
|
gst_caps_unref (available_caps);
|
|
caps = gst_pad_peer_query_caps (decoder->srcpad, filter);
|
|
gst_caps_unref (filter);
|
|
|
|
GST_DEBUG_OBJECT (self, "Possible decoded caps: %" GST_PTR_FORMAT, caps);
|
|
if (gst_caps_is_empty (caps)) {
|
|
gst_caps_unref (caps);
|
|
goto not_negotiated;
|
|
}
|
|
|
|
/* Fixate pixel format */
|
|
caps = gst_caps_fixate (caps);
|
|
|
|
GST_DEBUG_OBJECT (self, "Chosen decoded caps: %" GST_PTR_FORMAT, caps);
|
|
|
|
/* Try to set negotiated format, on success replace acquired format */
|
|
if (gst_v4l2_object_set_format (self->v4l2capture, caps, &error))
|
|
gst_video_info_from_caps (&info, caps);
|
|
else
|
|
gst_v4l2_clear_error (&error);
|
|
gst_caps_unref (caps);
|
|
|
|
output_state = gst_video_decoder_set_output_state (decoder,
|
|
info.finfo->format, info.width, info.height, self->input_state);
|
|
|
|
/* Copy the rest of the information, there might be more in the future */
|
|
output_state->info.interlace_mode = info.interlace_mode;
|
|
gst_video_codec_state_unref (output_state);
|
|
|
|
if (!gst_video_decoder_negotiate (decoder)) {
|
|
if (GST_PAD_IS_FLUSHING (decoder->srcpad))
|
|
goto flushing;
|
|
else
|
|
goto not_negotiated;
|
|
}
|
|
|
|
/* Ensure our internal pool is activated */
|
|
if (!gst_buffer_pool_set_active (GST_BUFFER_POOL (self->v4l2capture->pool),
|
|
TRUE))
|
|
goto activate_failed;
|
|
}
|
|
|
|
if (g_atomic_int_get (&self->processing) == FALSE) {
|
|
/* It's possible that the processing thread stopped due to an error */
|
|
if (self->output_flow != GST_FLOW_OK &&
|
|
self->output_flow != GST_FLOW_FLUSHING) {
|
|
GST_DEBUG_OBJECT (self, "Processing loop stopped with error, leaving");
|
|
ret = self->output_flow;
|
|
goto drop;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "Starting decoding thread");
|
|
|
|
/* Start the processing task, when it quits, the task will disable input
|
|
* processing to unlock input if draining, or prevent potential block */
|
|
g_atomic_int_set (&self->processing, TRUE);
|
|
if (!gst_pad_start_task (decoder->srcpad,
|
|
(GstTaskFunction) gst_v4l2_video_dec_loop, self,
|
|
(GDestroyNotify) gst_v4l2_video_dec_loop_stopped))
|
|
goto start_task_failed;
|
|
}
|
|
|
|
if (!processed) {
|
|
GST_VIDEO_DECODER_STREAM_UNLOCK (decoder);
|
|
ret =
|
|
gst_v4l2_buffer_pool_process (GST_V4L2_BUFFER_POOL (self->v4l2output->
|
|
pool), &frame->input_buffer);
|
|
GST_VIDEO_DECODER_STREAM_LOCK (decoder);
|
|
|
|
if (ret == GST_FLOW_FLUSHING) {
|
|
if (g_atomic_int_get (&self->processing) == FALSE)
|
|
ret = self->output_flow;
|
|
goto drop;
|
|
} else if (ret != GST_FLOW_OK) {
|
|
goto process_failed;
|
|
}
|
|
}
|
|
|
|
/* No need to keep input arround */
|
|
tmp = frame->input_buffer;
|
|
frame->input_buffer = gst_buffer_new ();
|
|
gst_buffer_copy_into (frame->input_buffer, tmp,
|
|
GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS |
|
|
GST_BUFFER_COPY_META, 0, 0);
|
|
gst_buffer_unref (tmp);
|
|
|
|
gst_video_codec_frame_unref (frame);
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
not_negotiated:
|
|
{
|
|
GST_ERROR_OBJECT (self, "not negotiated");
|
|
ret = GST_FLOW_NOT_NEGOTIATED;
|
|
gst_v4l2_error (self, &error);
|
|
goto drop;
|
|
}
|
|
activate_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS,
|
|
(_("Failed to allocate required memory.")),
|
|
("Buffer pool activation failed"));
|
|
ret = GST_FLOW_ERROR;
|
|
goto drop;
|
|
}
|
|
flushing:
|
|
{
|
|
ret = GST_FLOW_FLUSHING;
|
|
goto drop;
|
|
}
|
|
|
|
start_task_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
|
|
(_("Failed to start decoding thread.")), (NULL));
|
|
g_atomic_int_set (&self->processing, FALSE);
|
|
ret = GST_FLOW_ERROR;
|
|
goto drop;
|
|
}
|
|
process_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
|
|
(_("Failed to process frame.")),
|
|
("Maybe be due to not enough memory or failing driver"));
|
|
ret = GST_FLOW_ERROR;
|
|
goto drop;
|
|
}
|
|
drop:
|
|
{
|
|
gst_video_decoder_drop_frame (decoder, frame);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_decide_allocation (GstVideoDecoder * decoder,
|
|
GstQuery * query)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
GstClockTime latency;
|
|
gboolean ret = FALSE;
|
|
|
|
if (gst_v4l2_object_decide_allocation (self->v4l2capture, query))
|
|
ret = GST_VIDEO_DECODER_CLASS (parent_class)->decide_allocation (decoder,
|
|
query);
|
|
|
|
latency = self->v4l2capture->min_buffers * self->v4l2capture->duration;
|
|
gst_video_decoder_set_latency (decoder, latency, latency);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_src_query (GstVideoDecoder * decoder, GstQuery * query)
|
|
{
|
|
gboolean ret = TRUE;
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_CAPS:{
|
|
GstCaps *filter, *result = NULL;
|
|
GstPad *pad = GST_VIDEO_DECODER_SRC_PAD (decoder);
|
|
|
|
gst_query_parse_caps (query, &filter);
|
|
|
|
if (self->probed_srccaps)
|
|
result = gst_caps_ref (self->probed_srccaps);
|
|
else
|
|
result = gst_pad_get_pad_template_caps (pad);
|
|
|
|
if (filter) {
|
|
GstCaps *tmp = result;
|
|
result =
|
|
gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (tmp);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "Returning src caps %" GST_PTR_FORMAT, result);
|
|
|
|
gst_query_set_caps_result (query, result);
|
|
gst_caps_unref (result);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
ret = GST_VIDEO_DECODER_CLASS (parent_class)->src_query (decoder, query);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_v4l2_video_dec_sink_getcaps (GstVideoDecoder * decoder, GstCaps * filter)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
GstCaps *result;
|
|
|
|
result = gst_video_decoder_proxy_getcaps (decoder, self->probed_sinkcaps,
|
|
filter);
|
|
|
|
GST_DEBUG_OBJECT (self, "Returning sink caps %" GST_PTR_FORMAT, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2_video_dec_sink_event (GstVideoDecoder * decoder, GstEvent * event)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
|
|
gboolean ret;
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_FLUSH_START:
|
|
GST_DEBUG_OBJECT (self, "flush start");
|
|
gst_v4l2_object_unlock (self->v4l2output);
|
|
gst_v4l2_object_unlock (self->v4l2capture);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_VIDEO_DECODER_CLASS (parent_class)->sink_event (decoder, event);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_FLUSH_START:
|
|
/* The processing thread should stop now, wait for it */
|
|
gst_pad_stop_task (decoder->srcpad);
|
|
GST_DEBUG_OBJECT (self, "flush start done");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_v4l2_video_dec_change_state (GstElement * element,
|
|
GstStateChange transition)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (element);
|
|
GstVideoDecoder *decoder = GST_VIDEO_DECODER (element);
|
|
|
|
if (transition == GST_STATE_CHANGE_PAUSED_TO_READY) {
|
|
g_atomic_int_set (&self->active, FALSE);
|
|
gst_v4l2_object_unlock (self->v4l2output);
|
|
gst_v4l2_object_unlock (self->v4l2capture);
|
|
gst_pad_stop_task (decoder->srcpad);
|
|
}
|
|
|
|
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_video_dec_dispose (GObject * object)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (object);
|
|
|
|
gst_caps_replace (&self->probed_sinkcaps, NULL);
|
|
gst_caps_replace (&self->probed_srccaps, NULL);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_video_dec_finalize (GObject * object)
|
|
{
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (object);
|
|
|
|
gst_v4l2_object_destroy (self->v4l2capture);
|
|
gst_v4l2_object_destroy (self->v4l2output);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_video_dec_init (GstV4l2VideoDec * self)
|
|
{
|
|
/* V4L2 object are created in subinstance_init */
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_video_dec_subinstance_init (GTypeInstance * instance, gpointer g_class)
|
|
{
|
|
GstV4l2VideoDecClass *klass = GST_V4L2_VIDEO_DEC_CLASS (g_class);
|
|
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (instance);
|
|
GstVideoDecoder *decoder = GST_VIDEO_DECODER (instance);
|
|
|
|
gst_video_decoder_set_packetized (decoder, TRUE);
|
|
|
|
self->v4l2output = gst_v4l2_object_new (GST_ELEMENT (self),
|
|
V4L2_BUF_TYPE_VIDEO_OUTPUT, klass->default_device,
|
|
gst_v4l2_get_output, gst_v4l2_set_output, NULL);
|
|
self->v4l2output->no_initial_format = TRUE;
|
|
self->v4l2output->keep_aspect = FALSE;
|
|
|
|
self->v4l2capture = gst_v4l2_object_new (GST_ELEMENT (self),
|
|
V4L2_BUF_TYPE_VIDEO_CAPTURE, klass->default_device,
|
|
gst_v4l2_get_input, gst_v4l2_set_input, NULL);
|
|
self->v4l2capture->no_initial_format = TRUE;
|
|
self->v4l2output->keep_aspect = FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_video_dec_class_init (GstV4l2VideoDecClass * klass)
|
|
{
|
|
GstElementClass *element_class;
|
|
GObjectClass *gobject_class;
|
|
GstVideoDecoderClass *video_decoder_class;
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
element_class = (GstElementClass *) klass;
|
|
gobject_class = (GObjectClass *) klass;
|
|
video_decoder_class = (GstVideoDecoderClass *) klass;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_v4l2_video_dec_debug, "v4l2videodec", 0,
|
|
"V4L2 Video Decoder");
|
|
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"V4L2 Video Decoder",
|
|
"Codec/Decoder/Video",
|
|
"Decode video streams via V4L2 API",
|
|
"Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>");
|
|
|
|
gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_dispose);
|
|
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_finalize);
|
|
gobject_class->set_property =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_set_property);
|
|
gobject_class->get_property =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_get_property);
|
|
|
|
video_decoder_class->open = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_open);
|
|
video_decoder_class->close = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_close);
|
|
video_decoder_class->start = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_start);
|
|
video_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_stop);
|
|
video_decoder_class->finish = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_finish);
|
|
video_decoder_class->flush = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_flush);
|
|
video_decoder_class->set_format =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_set_format);
|
|
video_decoder_class->negotiate =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_negotiate);
|
|
video_decoder_class->decide_allocation =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_decide_allocation);
|
|
/* FIXME propose_allocation or not ? */
|
|
video_decoder_class->handle_frame =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_handle_frame);
|
|
video_decoder_class->getcaps =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_sink_getcaps);
|
|
video_decoder_class->src_query =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_src_query);
|
|
video_decoder_class->sink_event =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_sink_event);
|
|
|
|
element_class->change_state =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_change_state);
|
|
|
|
gst_v4l2_object_install_m2m_properties_helper (gobject_class);
|
|
}
|
|
|
|
static void
|
|
gst_v4l2_video_dec_subclass_init (gpointer g_class, gpointer data)
|
|
{
|
|
GstV4l2VideoDecClass *klass = GST_V4L2_VIDEO_DEC_CLASS (g_class);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
|
GstV4l2VideoDecCData *cdata = data;
|
|
|
|
klass->default_device = cdata->device;
|
|
|
|
/* Note: gst_pad_template_new() take the floating ref from the caps */
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
|
|
cdata->sink_caps));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
|
|
cdata->src_caps));
|
|
|
|
g_free (cdata);
|
|
}
|
|
|
|
/* Probing functions */
|
|
gboolean
|
|
gst_v4l2_is_video_dec (GstCaps * sink_caps, GstCaps * src_caps)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
if (gst_caps_is_subset (sink_caps, gst_v4l2_object_get_codec_caps ())
|
|
&& gst_caps_is_subset (src_caps, gst_v4l2_object_get_raw_caps ()))
|
|
ret = TRUE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gst_v4l2_video_dec_register (GstPlugin * plugin, const gchar * basename,
|
|
const gchar * device_path, GstCaps * sink_caps, GstCaps * src_caps)
|
|
{
|
|
GTypeQuery type_query;
|
|
GTypeInfo type_info = { 0, };
|
|
GType type, subtype;
|
|
gchar *type_name;
|
|
GstV4l2VideoDecCData *cdata;
|
|
|
|
cdata = g_new0 (GstV4l2VideoDecCData, 1);
|
|
cdata->device = g_strdup (device_path);
|
|
cdata->sink_caps = gst_caps_ref (sink_caps);
|
|
cdata->src_caps = gst_caps_ref (src_caps);
|
|
|
|
type = gst_v4l2_video_dec_get_type ();
|
|
g_type_query (type, &type_query);
|
|
memset (&type_info, 0, sizeof (type_info));
|
|
type_info.class_size = type_query.class_size;
|
|
type_info.instance_size = type_query.instance_size;
|
|
type_info.class_init = gst_v4l2_video_dec_subclass_init;
|
|
type_info.class_data = cdata;
|
|
type_info.instance_init = gst_v4l2_video_dec_subinstance_init;
|
|
|
|
type_name = g_strdup_printf ("v4l2%sdec", basename);
|
|
subtype = g_type_register_static (type, type_name, &type_info, 0);
|
|
|
|
gst_element_register (plugin, type_name, GST_RANK_PRIMARY + 1, subtype);
|
|
|
|
g_free (type_name);
|
|
|
|
return TRUE;
|
|
}
|