Files
gst-nvvideo4linux2/gst-v4l2/gstv4l2videodec.c
svcmobrel-release 7d59d738a0 Updating prebuilts and/or headers
ad68a394611e57debf3d91f0c030d659d03138b5 - v4l2_nv_extensions.h
ed8273ff6102bb0b4fa7975a401b12b3e95a7187 - nvbufsurface.h
d27a433ddeaefb9f42d0312c23472514b0cd6a45 - gst-nvcustomevent.h
49a66f0ce02abc71f33e096a65645ddedf5c7f46 - gst-v4l2/gstv4l2bufferpool.c
5948d70c07e87f9b1dc403789dcbed6acfa47ad9 - gst-v4l2/gstv4l2av1enc.c
99d65d620807b5ba1ca29a838e032940c9b019cc - gst-v4l2/sei_parse.c
c2099692cdb374440c2a040cb6ad01bbc1549ce5 - gst-v4l2/gstv4l2h26xparser.h
d89a680415f6ff5acec2571cde0fce9054d8e81f - gst-v4l2/gstv4l2vp9enc.h
3465c61330f2f59d307b660aed6f2a65e053e2a2 - gst-v4l2/gstv4l2.c
9681f7b98dfdfbc4d845f9ce7f11c3692b923195 - gst-v4l2/gstv4l2videoenc.c
39fcb2f599e6906ab0fd7ab9a46fef3ea58a8cab - gst-v4l2/gstv4l2vp8enc.h
71be284b547ee68fb0e2cd14b0aeb14734a915a1 - gst-v4l2/gstv4l2bufferpool.h
65de802e5f162aa04518b7ade5841cc3ced01111 - gst-v4l2/Makefile
884e5b97b9fa8d07b6153e6efe6999884922b813 - gst-v4l2/gstv4l2object.c
e8e973c103725b65232d32817e0305d12d6ff309 - gst-v4l2/gstv4l2h264enc.c
b827fd6cb1e3b8ecebd6a07f8556e846e26cba17 - gst-v4l2/gstv4l2allocator.h
02d142337f4b96fcb0c9f2405a3cbe90c5917cca - gst-v4l2/gstv4l2vp9enc.c
3f7cafe5beb4395caf2e1591bf0a835e5076031a - gst-v4l2/gstv4l2object.h
d5952b0286c34bf13fbf5e09fe552ced0da49368 - gst-v4l2/gstv4l2videodec.h
c81eacb7d88c4fb839506dd70055e30d7a9feeec - gst-v4l2/v4l2-utils.h
cbc84dccd2506afa4c8f03849c95bb28c83ef4a3 - gst-v4l2/gstv4l2av1enc.h
d29e3a719400c3cb27314366d48ec792a3c12363 - gst-v4l2/gstv4l2h265enc.h
4e79cf75c4fa29791e1f5141318dc8aec13a7835 - gst-v4l2/nalutils.h
bb104683f5e4f7402e3f765a891e149edc794e02 - gst-v4l2/gstv4l2h264enc.h
b1cd923335aa60985ff9866fba91a2068e8671c7 - gst-v4l2/LICENSE.gst-nvvideo4linux2
c09037afe2c6b01dcb5ed56f30f07c3d6482e150 - gst-v4l2/gstv4l2videodec.c
a002edef13a3bbbdc41e42a7fca40e574ad1bb3e - gst-v4l2/v4l2-utils.c
489fde70531590e94d1d211a42f10f81ae68d2b9 - gst-v4l2/gstv4l2videoenc.h
73b03969d7ae0a8adb374c93999c43af88ea93b2 - gst-v4l2/v4l2_calls.c
398c24d1eef98ec9003a06587bc3784050602cd2 - gst-v4l2/gstv4l2h26xparser.c
5ecd059e5ef9be4014eface37e5e2f7598960f4e - gst-v4l2/nalutils.c
807bc9859585a540b0f85e98f147756aab24e1bd - gst-v4l2/gstv4l2vp8enc.c
9f726e4439379bb399f29c68736242f21dab3dd0 - gst-v4l2/gstv4l2allocator.c
dc1a3f7292873f1f71dc27300f97f3ab918ed79f - gst-v4l2/gstv4l2h265enc.c
499a9feb17ceabf1f1443923dffa1e0180bf5972 - gst-v4l2/gst/glib-compat-private.h
20c4f7c0cb89c83256650bc3353ed82154cf3a9d - gst-v4l2/gst/gst-i18n-plugin.h
e864ee6647f3572b144403d799f68152e9900da1 - gst-v4l2/gst/gettext.h
522ab8fc8531a2c758b9278d29642f5b763fd3e7 - gst-v4l2/ext/videodev2.h
a745675b051a2b8434a430c80fde3f245864ca89 - gst-v4l2/ext/v4l2-common.h
1636366b5a062e4bc1791b7bc3012ccf5635b363 - gst-v4l2/ext/v4l2-controls.h
72a34a694337f8f6da3bb94c9faced6730cbd2fc - gst-v4l2/ext/types-compat.h

Change-Id: I5578de0db34980a48c655a5079a792fd7de76270
2023-08-03 21:10:41 -07:00

2975 lines
93 KiB
C

/*
* Copyright (C) 2014-2023 Collabora Ltd.
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
* Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
*
* 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 "gstv4l2h26xparser.h"
#include "gstv4l2object.h"
#include "gstv4l2videodec.h"
#include "gstnvdsseimeta.h"
#include "gst-nvcustomevent.h"
#include "stdlib.h"
#include <string.h>
#include <stdint.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
#define ENABLE_DRAIN 1
#ifdef USE_V4L2_TARGET_NV
typedef enum {
CAP_BUF_DYNAMIC_ALLOC_DISABLED,
CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_FW_PLAYBACK,
CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_RW_PLAYBACK,
CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_FW_RW_PLAYBACK
} CaptureBufferDynamicAllocationModes;
#define DEFAULT_SKIP_FRAME_TYPE V4L2_SKIP_FRAMES_TYPE_NONE
#define DEFAULT_DISABLE_DPB FALSE
#define DEFAULT_FULL_FRAME FALSE
#define DEFAULT_FRAME_TYPR_REPORTING FALSE
#define DEFAULT_ERROR_CHECK FALSE
#define DEFAULT_MAX_PERFORMANCE FALSE
#define DEFAULT_CAP_BUF_DYNAMIC_ALLOCATION CAP_BUF_DYNAMIC_ALLOC_DISABLED
#define GST_TYPE_V4L2_VID_DEC_SKIP_FRAMES (gst_video_dec_skip_frames ())
#define GST_TYPE_V4L2_DEC_CAP_BUF_DYNAMIC_ALLOC (gst_video_dec_capture_buffer_dynamic_allocation ())
gboolean default_cudadec_low_latency;
gboolean default_sei_extract_data;
gint default_num_extra_surfaces;
static gboolean enable_latency_measurement = FALSE;
extern uint8_t *parse_sei_data (uint8_t *bs, guint size, uint32_t *payload_size, char *sei_uuid_string);
#ifdef USE_V4L2_TARGET_NV
GstVideoCodecFrame *
gst_v4l2_video_dec_find_nearest_frame (GstBuffer * buf, GList * frames);
static void
gst_v4l2_video_dec_clean_older_frames (GstV4l2VideoDec * self,
GstBuffer * buf, GList * frames);
#endif
static gdouble get_current_system_timestamp(void)
{
struct timeval t1;
double elapsedTime = 0;
gettimeofday(&t1, NULL);
elapsedTime = (t1.tv_sec) * 1000.0;
elapsedTime += (t1.tv_usec) / 1000.0;
return elapsedTime;
}
static GType
gst_video_dec_skip_frames (void)
{
static GType qtype = 0;
if (qtype == 0) {
static const GEnumValue values[] = {
{V4L2_SKIP_FRAMES_TYPE_NONE, "Decode all frames", "decode_all"},
{V4L2_SKIP_FRAMES_TYPE_NONREF, "Decode non-ref frames",
"decode_non_ref"},
{V4L2_SKIP_FRAMES_TYPE_DECODE_IDR_ONLY, "decode key frames",
"decode_key"},
{0, NULL, NULL}
};
qtype = g_enum_register_static ("SkipFrame", values);
}
return qtype;
}
static GType
gst_video_dec_capture_buffer_dynamic_allocation (void)
{
static GType qtype = 0;
if (qtype == 0) {
static const GEnumValue values[] = {
{CAP_BUF_DYNAMIC_ALLOC_DISABLED,
"Capture buffer dynamic allocation disabled", "cap_buf_dyn_alloc_disabled"},
{CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_FW_PLAYBACK,
"Capture buffer dynamic allocation enabled for forward playback", "fw_cap_buf_dyn_alloc_enabled"},
{CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_RW_PLAYBACK,
"Capture buffer dynamic allocation enabled for reverse playback", "rw_cap_buf_dyn_alloc_enabled"},
{CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_FW_RW_PLAYBACK,
"Capture buffer dynamic allocation enabled for forward and reverse playback", "fw_rw_cap_buf_dyn_alloc_enabled"},
{0, NULL, NULL}
};
qtype = g_enum_register_static ("CaptureBufferDynamicAllocationModes", values);
}
return qtype;
}
/* Properties specifically applicable on GPU*/
#define GST_TYPE_V4L2_VID_CUDADEC_MEM_TYPE (gst_video_cudadec_mem_type ())
#ifndef __aarch64__
#define DEFAULT_CUDADEC_MEM_TYPE V4L2_CUDA_MEM_TYPE_DEVICE
#else
#define DEFAULT_CUDADEC_MEM_TYPE V4L2_CUDA_MEM_TYPE_DEVICE
#endif
#define DEFAULT_CUDADEC_GPU_ID 0
#define MAX_CUDADEC_NUM_SURFACES 20
#define DEFAULT_CUDADEC_NUM_SURFACES 20
static GType
gst_video_cudadec_mem_type (void)
{
static GType qtype = 0;
if (qtype == 0) {
static const GEnumValue values[] = {
{V4L2_CUDA_MEM_TYPE_DEVICE, "Memory type Device", "memtype_device"},
{V4L2_CUDA_MEM_TYPE_PINNED, "Memory type Host Pinned",
"memtype_pinned"},
{V4L2_CUDA_MEM_TYPE_UNIFIED, "Memory type Unified",
"memtype_unified"},
{0, NULL, NULL}
};
qtype = g_enum_register_static ("CudaDecMemType", values);
}
return qtype;
}
#define CAPTURE_CAPS \
"video/x-raw(memory:NVMM), " \
"width = (gint) [ 1, MAX ], " \
"height = (gint) [ 1, MAX ], " \
"framerate = (fraction) [ 0, MAX ];"
//Caps on Tegra/dGPU
static GstStaticPadTemplate gst_v4l2dec_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("image/jpeg"
";"
"video/x-h264,"
"stream-format = (string) { byte-stream },"
"alignment = (string) { au }"
";"
"video/x-h265,"
"stream-format = (string) { byte-stream },"
"alignment = (string) { au }"
";"
"video/mpeg, "
"mpegversion= (int) 4, "
"systemstream=(boolean) false, "
"parsed=(boolean) true, "
"width=(gint) [1,MAX],"
"height=(gint) [1,MAX]"
";"
"video/mpeg, "
"mpegversion= (int) [1, 2], "
"systemstream=(boolean) false, "
"parsed=(boolean) true, "
"width=(gint) [1,MAX],"
"height=(gint) [1,MAX]"
";"
"video/x-divx, "
"divxversion=(int) [4, 5], "
"width=(int) [1,MAX], " "height=(int) [1,MAX]"
";"
"video/x-av1"
";"
"video/x-vp8"
";" "video/x-vp9," "width=(gint) [1,MAX]," "height=(gint) [1,MAX]" ";")
);
static GstStaticPadTemplate gst_v4l2dec_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (CAPTURE_CAPS));
#endif
typedef struct
{
gchar *device;
GstCaps *sink_caps;
GstCaps *src_caps;
const gchar *longname;
const gchar *description;
} GstV4l2VideoDecCData;
enum
{
PROP_0,
V4L2_STD_OBJECT_PROPS,
#ifdef USE_V4L2_TARGET_NV
/*Common properties*/
PROP_SKIP_FRAME,
PROP_DROP_FRAME_INTERVAL,
PROP_NUM_EXTRA_SURFACES,
/*Properties exposed on Tegra only */
PROP_DISABLE_DPB,
PROP_USE_FULL_FRAME,
PROP_ENABLE_FRAME_TYPE_REPORTING,
PROP_ENABLE_ERROR_CHECK,
PROP_ENABLE_MAX_PERFORMANCE,
PROP_OPEN_MJPEG_BLOCK,
/*Properties exposed on dGPU only*/
PROP_CUDADEC_MEM_TYPE,
PROP_CUDADEC_GPU_ID,
PROP_CUDADEC_LOW_LATENCY,
PROP_EXTRACT_SEI_TYPE5_DATA,
PROP_SEI_UUID_STRING,
PROP_CAP_BUF_DYNAMIC_ALLOCATION,
#endif
};
#define gst_v4l2_video_dec_parent_class parent_class
G_DEFINE_ABSTRACT_TYPE (GstV4l2VideoDec, gst_v4l2_video_dec,
GST_TYPE_VIDEO_DECODER);
#ifdef USE_V4L2_TARGET_NV
GType
gst_v4l2_dec_output_io_mode_get_type (void)
{
static GType v4l2_dec_output_io_mode = 0;
if (!v4l2_dec_output_io_mode) {
static const GEnumValue dec_output_io_modes[] = {
{GST_V4L2_IO_AUTO, "GST_V4L2_IO_AUTO", "auto"},
{GST_V4L2_IO_MMAP, "GST_V4L2_IO_MMAP", "mmap"},
{GST_V4L2_IO_USERPTR, "GST_V4L2_IO_USERPTR", "userptr"},
{0, NULL, NULL}
};
v4l2_dec_output_io_mode = g_enum_register_static ("GstNvV4l2DecOutputIOMode",
dec_output_io_modes);
}
return v4l2_dec_output_io_mode;
}
GType
gst_v4l2_dec_capture_io_mode_get_type (void)
{
static GType v4l2_dec_capture_io_mode = 0;
if (!v4l2_dec_capture_io_mode) {
static const GEnumValue dec_capture_io_modes[] = {
{GST_V4L2_IO_AUTO, "GST_V4L2_IO_AUTO", "auto"},
{GST_V4L2_IO_MMAP, "GST_V4L2_IO_MMAP", "mmap"},
{0, NULL, NULL}
};
v4l2_dec_capture_io_mode = g_enum_register_static ("GstNvV4l2DecCaptureIOMode",
dec_capture_io_modes);
}
return v4l2_dec_capture_io_mode;
}
#endif
/* prototypes */
static GstFlowReturn gst_v4l2_video_dec_finish (GstVideoDecoder * decoder);
#ifdef USE_V4L2_GST_HEADER_VER_1_8
/**
* TODO: This function gst_pad_get_task_state is introduced in newer gstreamer
* version which we use on Tegra but on Desktop we are still at 1.8.3 (16.04)
* At the moment I am copying the code here but eventually when we move to 1.14,
* this shouldn't be needed.
* gst_pad_get_task_state:
* @pad: the #GstPad to get task state from
*
* Get @pad task state. If no task is currently
* set, GST_TASK_STOPPED is returned.
*
* Returns: The current state of @pad's task.
*/
GstTaskState
gst_pad_get_task_state (GstPad * pad)
{
GstTask *task;
GstTaskState res;
g_return_val_if_fail (GST_IS_PAD (pad), GST_TASK_STOPPED);
GST_OBJECT_LOCK (pad);
task = GST_PAD_TASK (pad);
if (task == NULL)
goto no_task;
res = gst_task_get_state (task);
GST_OBJECT_UNLOCK (pad);
return res;
no_task:
{
GST_DEBUG_OBJECT (pad, "pad has no task");
GST_OBJECT_UNLOCK (pad);
return GST_TASK_STOPPED;
}
}
#endif
static void
gst_v4l2_video_dec_set_property_tegra (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (object);
switch (prop_id) {
case PROP_CAPTURE_IO_MODE:
if (!gst_v4l2_object_set_property_helper (self->v4l2capture,
prop_id, value, pspec)) {
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
break;
#ifdef USE_V4L2_TARGET_NV
case PROP_SKIP_FRAME:
self->skip_frames = g_value_get_enum (value);
break;
case PROP_DROP_FRAME_INTERVAL:
self->drop_frame_interval = g_value_get_uint (value);
break;
case PROP_NUM_EXTRA_SURFACES:
self->num_extra_surfaces = g_value_get_uint (value);
break;
case PROP_DISABLE_DPB:
self->disable_dpb = g_value_get_boolean (value);
break;
case PROP_USE_FULL_FRAME:
self->enable_full_frame = g_value_get_boolean (value);
break;
case PROP_ENABLE_FRAME_TYPE_REPORTING:
self->enable_frame_type_reporting = g_value_get_boolean (value);
self->v4l2capture->Enable_frame_type_reporting =
g_value_get_boolean (value);
break;
case PROP_ENABLE_ERROR_CHECK:
self->enable_error_check = g_value_get_boolean (value);
self->v4l2capture->Enable_error_check = g_value_get_boolean (value);
break;
case PROP_ENABLE_MAX_PERFORMANCE:
self->enable_max_performance = g_value_get_boolean (value);
break;
case PROP_OPEN_MJPEG_BLOCK:
self->v4l2output->open_mjpeg_block = g_value_get_boolean (value);
break;
case PROP_CAP_BUF_DYNAMIC_ALLOCATION:
self->cap_buf_dynamic_allocation = g_value_get_enum (value);
break;
#endif
/* 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_set_property_cuvid (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (object);
switch (prop_id) {
case PROP_CAPTURE_IO_MODE:
if (!gst_v4l2_object_set_property_helper (self->v4l2capture,
prop_id, value, pspec)) {
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
break;
#ifdef USE_V4L2_TARGET_NV
case PROP_SKIP_FRAME:
self->skip_frames = g_value_get_enum (value);
break;
case PROP_DROP_FRAME_INTERVAL:
self->drop_frame_interval = g_value_get_uint (value);
break;
case PROP_NUM_EXTRA_SURFACES:
self->num_extra_surfaces = g_value_get_uint (value);
break;
case PROP_CUDADEC_MEM_TYPE:
self->cudadec_mem_type = g_value_get_enum (value);
break;
case PROP_CUDADEC_GPU_ID:
self->cudadec_gpu_id = g_value_get_uint (value);
break;
case PROP_CUDADEC_LOW_LATENCY:
self->cudadec_low_latency = g_value_get_boolean (value);
break;
case PROP_EXTRACT_SEI_TYPE5_DATA:
self->extract_sei_type5_data = g_value_get_boolean (value);
break;
case PROP_SEI_UUID_STRING:
self->sei_uuid_string = (gchar *)g_value_dup_string (value);
break;
case PROP_CAP_BUF_DYNAMIC_ALLOCATION:
self->cap_buf_dynamic_allocation = g_value_get_enum (value);
break;
#endif
/* 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_tegra (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (object);
switch (prop_id) {
case PROP_CAPTURE_IO_MODE:
if (!gst_v4l2_object_get_property_helper (self->v4l2capture,
prop_id, value, pspec)) {
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
break;
#ifdef USE_V4L2_TARGET_NV
case PROP_SKIP_FRAME:
g_value_set_enum (value, self->skip_frames);
break;
case PROP_DROP_FRAME_INTERVAL:
g_value_set_uint (value, self->drop_frame_interval);
break;
case PROP_NUM_EXTRA_SURFACES:
g_value_set_uint (value, self->num_extra_surfaces);
break;
case PROP_DISABLE_DPB:
g_value_set_boolean (value, self->disable_dpb);
break;
case PROP_USE_FULL_FRAME:
g_value_set_boolean (value, self->enable_full_frame);
break;
case PROP_ENABLE_FRAME_TYPE_REPORTING:
g_value_set_boolean (value, self->enable_frame_type_reporting);
break;
case PROP_ENABLE_ERROR_CHECK:
g_value_set_boolean (value, self->enable_error_check);
break;
case PROP_ENABLE_MAX_PERFORMANCE:
g_value_set_boolean (value, self->enable_max_performance);
break;
case PROP_OPEN_MJPEG_BLOCK:
g_value_set_boolean (value, self->v4l2output->open_mjpeg_block);
break;
case PROP_CAP_BUF_DYNAMIC_ALLOCATION:
g_value_set_enum (value, self->cap_buf_dynamic_allocation);
break;
#endif
/* 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 void
gst_v4l2_video_dec_get_property_cuvid (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (object);
switch (prop_id) {
case PROP_CAPTURE_IO_MODE:
if (!gst_v4l2_object_get_property_helper (self->v4l2capture,
prop_id, value, pspec)) {
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
break;
#ifdef USE_V4L2_TARGET_NV
case PROP_SKIP_FRAME:
g_value_set_enum (value, self->skip_frames);
break;
case PROP_DROP_FRAME_INTERVAL:
g_value_set_uint (value, self->drop_frame_interval);
break;
case PROP_NUM_EXTRA_SURFACES:
g_value_set_uint (value, self->num_extra_surfaces);
break;
case PROP_CUDADEC_MEM_TYPE:
g_value_set_enum(value, self->cudadec_mem_type);
break;
case PROP_CUDADEC_GPU_ID:
g_value_set_uint(value, self->cudadec_gpu_id);
break;
case PROP_CUDADEC_LOW_LATENCY:
g_value_set_boolean (value, self->cudadec_low_latency);
break;
case PROP_EXTRACT_SEI_TYPE5_DATA:
g_value_set_boolean (value, self->extract_sei_type5_data);
break;
case PROP_SEI_UUID_STRING:
g_value_set_string (value, self->sei_uuid_string);
break;
case PROP_CAP_BUF_DYNAMIC_ALLOCATION:
g_value_set_enum (value, self->cap_buf_dynamic_allocation);
break;
#endif
/* 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);
GstCaps *codec_caps;
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;
#ifdef USE_V4L2_TARGET_NV
// Downstream plugins may export this environment variable
if (is_cuvid == TRUE) {
g_setenv ("DS_NEW_BUFAPI", "1", TRUE);
}
#endif
codec_caps = gst_pad_get_pad_template_caps (decoder->sinkpad);
self->probed_sinkcaps = gst_v4l2_object_probe_caps (self->v4l2output,
codec_caps);
gst_caps_unref (codec_caps);
if (gst_caps_is_empty (self->probed_sinkcaps))
goto no_encoded_format;
self->probed_srccaps = gst_v4l2_object_probe_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;
#if USE_V4L2_TARGET_NV
self->decoded_picture_cnt = 0;
#endif
self->hash_pts_systemtime = g_hash_table_new(NULL, NULL);
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 */
#ifdef USE_V4L2_TARGET_NV
set_v4l2_video_mpeg_class (self->v4l2capture,
V4L2_CID_MPEG_SET_POLL_INTERRUPT, 0);
#endif
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);
gst_v4l2_object_stop (self->v4l2output);
gst_v4l2_object_stop (self->v4l2capture);
g_hash_table_destroy (self->hash_pts_systemtime);
if (self->input_state) {
gst_video_codec_state_unref (self->input_state);
self->input_state = NULL;
}
GST_DEBUG_OBJECT (self, "Stopped");
return TRUE;
}
#ifdef USE_V4L2_TARGET_NV
gboolean set_v4l2_controls (GstV4l2VideoDec *self)
{
GST_DEBUG_OBJECT(self, "set_v4l2_controls");
{
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_DISABLE_COMPLETE_FRAME_INPUT, 0)) {
g_print ("S_EXT_CTRLS for DISABLE_COMPLETE_FRAME_INPUT failed\n");
return FALSE;
}
}
if (self->skip_frames != V4L2_SKIP_FRAMES_TYPE_NONE) {
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_SKIP_FRAMES,
self->skip_frames)) {
g_print ("S_EXT_CTRLS for SKIP_FRAMES failed\n");
return FALSE;
}
}
#if 0
/* *
* TODO: From low level library remove support of drop frame interval after
* analyzing high CPU utilization in initial implementation.
* */
if (self->drop_frame_interval != 0) {
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEODEC_DROP_FRAME_INTERVAL,
self->drop_frame_interval)) {
g_print ("S_EXT_CTRLS for DROP_FRAME_INTERVAL failed\n");
return FALSE;
}
}
#endif
if (self->disable_dpb != DEFAULT_DISABLE_DPB) {
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_DISABLE_DPB,
self->disable_dpb)) {
g_print ("S_EXT_CTRLS for DISABLE_DPB failed\n");
return FALSE;
}
}
if (self->enable_full_frame != DEFAULT_FULL_FRAME) {
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_DISABLE_COMPLETE_FRAME_INPUT,
self->enable_full_frame)) {
g_print ("S_EXT_CTRLS for DISABLE_COMPLETE_FRAME_INPUT failed\n");
return FALSE;
}
}
if (self->enable_frame_type_reporting) {
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_ERROR_REPORTING,
self->enable_frame_type_reporting)) {
g_print ("S_EXT_CTRLS for ERROR_REPORTING failed\n");
return FALSE;
}
}
if (self->enable_error_check) {
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_ERROR_REPORTING,
self->enable_error_check)) {
g_print ("S_EXT_CTRLS for ERROR_REPORTING failed\n");
return FALSE;
}
}
if (self->enable_max_performance != DEFAULT_MAX_PERFORMANCE) {
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_MAX_PERFORMANCE,
self->enable_max_performance)) {
g_print ("S_EXT_CTRLS for MAX_PERFORMANCE failed\n");
return FALSE;
}
}
if (is_cuvid == TRUE) {
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_CUDA_MEM_TYPE,
self->cudadec_mem_type)) {
g_print ("S_EXT_CTRLS for CUDA_MEM_TYPE failed\n");
return FALSE;
}
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_CUDA_GPU_ID,
self->cudadec_gpu_id)) {
g_print ("S_EXT_CTRLS for CUDA_GPU_ID failed\n");
return FALSE;
}
}
return TRUE;
}
#endif
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) {
#ifndef USE_V4L2_TARGET_NV
if (gst_v4l2_object_caps_equal (self->v4l2output, state->caps)) {
GST_DEBUG_OBJECT (self, "Compatible caps");
goto done;
}
#else
if (is_cuvid == TRUE) {
GstV4l2BufferPool *v4l2pool = GST_V4L2_BUFFER_POOL(self->v4l2output->pool);
GstV4l2Object *obj = v4l2pool->obj;
if ((GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_H264) ||
(GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_H265))
{
GST_INFO_OBJECT(self, "pix format is h264 or h265. skipping");
self->output_flow = GST_FLOW_OK;
self->set_format = true;
return TRUE;
}
} else if (is_cuvid == FALSE) {
{
GstStructure *config;
GstCaps *oldcaps;
GstStructure *structure;
gint width = 0;
config = gst_buffer_pool_get_config(self->v4l2output->pool);
gst_buffer_pool_config_get_params(config, &oldcaps, NULL, NULL, NULL);
structure = gst_caps_get_structure(oldcaps, 0);
if (gst_structure_get_int(structure, "width", &width))
self->is_drc = TRUE;
else
self->is_drc = FALSE;
gst_structure_free(config);
}
if (self->is_drc == FALSE)
return TRUE;
else
self->idr_received = FALSE;
}
#endif
gst_video_codec_state_unref (self->input_state);
self->input_state = NULL;
gst_v4l2_video_dec_finish (decoder);
gst_v4l2_object_stop (self->v4l2output);
/* The renegotiation flow don't blend with the base class flow. To
* properly stop the capture pool we need to reclaim our buffers, which
* will happend through the allocation query. The allocation query is
* triggered by gst_video_decoder_negotiate() which requires the output
* caps to be set, but we can't know this information as we rely on the
* decoder, which requires the capture queue to be stopped.
*
* To workaround this issue, we simply run an allocation query with the
* old negotiated caps in order to drain/reclaim our buffers. That breaks
* the complexity and should not have much impact in performance since the
* following allocation query will happen on a drained pipeline and won't
* block. */
{
GstCaps *caps = gst_pad_get_current_caps (decoder->srcpad);
if (caps) {
GstQuery *query = gst_query_new_allocation (caps, FALSE);
gst_pad_peer_query (decoder->srcpad, query);
gst_query_unref (query);
gst_caps_unref (caps);
}
}
gst_v4l2_object_stop (self->v4l2capture);
self->output_flow = GST_FLOW_OK;
#ifdef USE_V4L2_TARGET_NV
if (is_cuvid == FALSE) {
if (self->is_drc == TRUE)
{
g_mutex_lock(&self->v4l2capture->cplane_stopped_lock);
while (self->v4l2capture->capture_plane_stopped != TRUE)
{
g_cond_wait(&self->v4l2capture->cplane_stopped_cond,
&self->v4l2capture->cplane_stopped_lock);
}
self->v4l2capture->capture_plane_stopped = FALSE;
g_mutex_unlock(&self->v4l2capture->cplane_stopped_lock);
gst_v4l2_object_close(self->v4l2output);
gst_v4l2_object_close(self->v4l2capture);
gst_v4l2_object_open(self->v4l2output);
if (!gst_v4l2_object_open_shared(self->v4l2capture, self->v4l2output))
{
g_print("gstv4l2object open shared failed\n");
if (GST_V4L2_IS_OPEN(self->v4l2output))
gst_v4l2_object_close(self->v4l2output);
return FALSE;
}
}
}
#endif
}
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);
#ifdef USE_V4L2_TARGET_NV
ret = set_v4l2_controls(self);
#endif
#ifndef USE_V4L2_TARGET_NV
done:
#endif
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 (gst_pad_get_task_state (decoder->srcpad) == GST_TASK_STARTED) {
GST_VIDEO_DECODER_STREAM_UNLOCK (decoder);
gst_v4l2_object_unlock (self->v4l2output);
gst_v4l2_object_unlock (self->v4l2capture);
#ifdef USE_V4L2_TARGET_NV
set_v4l2_video_mpeg_class (self->v4l2capture,
V4L2_CID_MPEG_SET_POLL_INTERRUPT, 0);
#endif
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);
if (self->v4l2output->pool)
gst_v4l2_buffer_pool_flush (self->v4l2output->pool);
if (self->v4l2capture->pool)
gst_v4l2_buffer_pool_flush (self->v4l2capture->pool);
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 (v4l2object->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 (gst_pad_get_task_state (decoder->srcpad) != GST_TASK_STARTED)
goto done;
GST_DEBUG_OBJECT (self, "Finishing decoding");
GST_VIDEO_DECODER_STREAM_UNLOCK (decoder);
#ifndef USE_V4L2_TARGET_NV
if (gst_v4l2_decoder_cmd (self->v4l2output, V4L2_DEC_CMD_STOP, 0)) {
#else
if (gst_v4l2_decoder_cmd (self->v4l2output, V4L2_DEC_CMD_STOP,
V4L2_DEC_CMD_STOP_TO_BLACK)) {
#endif
GstTask *task = decoder->srcpad->task;
if (!task) {
goto stop_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. */
stop_task:
gst_v4l2_object_unlock (self->v4l2capture);
#ifdef USE_V4L2_TARGET_NV
set_v4l2_video_mpeg_class (self->v4l2capture,
V4L2_CID_MPEG_SET_POLL_INTERRUPT, 0);
#endif
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");
/* TODO Shall we cleanup any reffed frame to workaround broken decoders ? */
done:
return ret;
}
static GstFlowReturn
gst_v4l2_video_dec_drain (GstVideoDecoder * decoder)
{
#if ENABLE_DRAIN
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
GST_DEBUG_OBJECT (self, "Draining...");
gst_v4l2_video_dec_finish (decoder);
gst_v4l2_video_dec_flush (decoder);
return GST_FLOW_OK;
#else
return GST_FLOW_OK;
#endif
}
#ifdef USE_V4L2_TARGET_NV
GstVideoCodecFrame *
gst_v4l2_video_dec_find_nearest_frame (GstBuffer * buf, GList * frames)
{
GstVideoCodecFrame *best = NULL;
GstClockTimeDiff best_diff = G_MAXINT64;
GstClockTime timestamp;
GList *l;
timestamp = buf->pts;
for (l = frames; l; l = l->next) {
GstVideoCodecFrame *tmp = l->data;
GstClockTimeDiff diff = ABS (GST_CLOCK_DIFF (timestamp, tmp->pts));
if (diff < best_diff) {
best = tmp;
best_diff = diff;
if (diff == 0)
break;
}
}
if (best)
gst_video_codec_frame_ref (best);
g_list_foreach (frames, (GFunc) gst_video_codec_frame_unref, NULL);
g_list_free (frames);
return best;
}
static void
gst_v4l2_video_dec_clean_older_frames (GstV4l2VideoDec * self,
GstBuffer * buf, GList * frames)
{
GList *l;
GstClockTime timestamp;
timestamp = buf->pts;
if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
/* We could release all frames stored with pts < timestamp since the
* decoder will likely output frames in display order */
for (l = frames; l; l = l->next) {
GstVideoCodecFrame *tmp = l->data;
if (tmp->pts < timestamp) {
GST_LOG_OBJECT (self,
"discarding ghost frame %p (#%d) PTS:%" GST_TIME_FORMAT " DTS:%"
GST_TIME_FORMAT, tmp, tmp->system_frame_number,
GST_TIME_ARGS (tmp->pts), GST_TIME_ARGS (tmp->dts));
gst_video_decoder_release_frame (GST_VIDEO_DECODER (self), tmp);
} else {
gst_video_codec_frame_unref (tmp);
}
}
} else {
/* We will release all frames with invalid timestamp because we don't even
* know if they will be output some day. */
for (l = frames; l; l = l->next) {
GstVideoCodecFrame *tmp = l->data;
if (!GST_CLOCK_TIME_IS_VALID (tmp->pts)) {
GST_LOG_OBJECT (self,
"discarding frame %p (#%d) with invalid PTS:%" GST_TIME_FORMAT
" DTS:%" GST_TIME_FORMAT, tmp, tmp->system_frame_number,
GST_TIME_ARGS (tmp->pts), GST_TIME_ARGS (tmp->dts));
gst_video_decoder_release_frame (GST_VIDEO_DECODER (self), tmp);
} else {
gst_video_codec_frame_unref (tmp);
}
}
}
g_list_free (frames);
}
#endif
#ifndef USE_V4L2_TARGET_NV
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;
}
#endif
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");
self->output_flow = GST_FLOW_OK;
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;
#ifdef USE_V4L2_TARGET_NV
frame = gst_v4l2_video_dec_find_nearest_frame (buffer,
gst_video_decoder_get_frames (GST_VIDEO_DECODER (self)));
/* So we have a timestamped buffer and get, or not, corresponding frame.
* Assuming decoder output frames in display order, frames preceding this
* frame could be discarded as they seems useless due to e.g interlaced
* stream, corrupted input data...
* In any cases, not likely to be seen again. so drop it before they pile up
* and use all the memory. */
gst_v4l2_video_dec_clean_older_frames (self, buffer,
gst_video_decoder_get_frames (GST_VIDEO_DECODER (self)));
#else
frame = gst_v4l2_video_dec_get_oldest_frame (decoder);
#endif
#ifdef USE_V4L2_TARGET_NV
if (is_cuvid == FALSE) {
if (frame && self->enable_frame_type_reporting) {
g_print ("Frame %d\n", frame->system_frame_number);
}
}
#endif
if (frame) {
frame->output_buffer = buffer;
buffer = NULL;
if(enable_latency_measurement) /* TODO with better option */
{
gpointer in_time = g_hash_table_lookup (self->hash_pts_systemtime,
&frame->pts);
gdouble input_time = *((gdouble*)in_time);
gdouble output_time = get_current_system_timestamp ();
if (output_time < input_time)
{
gdouble time = G_MAXDOUBLE - input_time;
GST_DEBUG_OBJECT (self, "True Decode Latency = %f \n",
output_time + time);
}
else
{
GST_DEBUG_OBJECT (self, "True Decode Latency = %f \n",
(output_time - input_time));
}
GstCaps *reference = gst_caps_new_simple ("video/x-raw",
"component_name", G_TYPE_STRING, GST_ELEMENT_NAME(self),
"frame_num", G_TYPE_INT, self->frame_num++,
"in_timestamp", G_TYPE_DOUBLE, input_time,
"out_timestamp", G_TYPE_DOUBLE, output_time,
NULL);
GstReferenceTimestampMeta * dec_meta =
gst_buffer_add_reference_timestamp_meta (frame->output_buffer, reference,
0, 0);
if(dec_meta == NULL)
{
GST_DEBUG_OBJECT (decoder, "dec_meta: %p", dec_meta);
}
gst_caps_unref(reference);
}
#if USE_V4L2_TARGET_NV
if (!gst_buffer_copy_into (frame->output_buffer, frame->input_buffer,
(GstBufferCopyFlags)GST_BUFFER_COPY_METADATA, 0, -1)) {
GST_DEBUG_OBJECT (decoder, "Buffer metadata copy failed \n");
}
if ((self->drop_frame_interval == 0) ||
(self->decoded_picture_cnt % self->drop_frame_interval == 0))
ret = gst_video_decoder_finish_frame (decoder, frame);
else
ret = gst_video_decoder_drop_frame (GST_VIDEO_DECODER (self), frame);
if (ret != GST_FLOW_OK)
goto beach;
self->decoded_picture_cnt += 1;
#else
ret = gst_video_decoder_finish_frame (decoder, frame);
#endif
} 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;
gst_v4l2_object_unlock (self->v4l2output);
gst_pad_pause_task (decoder->srcpad);
}
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;
#ifndef USE_V4L2_TARGET_NV
int width, height;
if (!gst_structure_get_int (structure, "width", &width))
return TRUE;
if (!gst_structure_get_int (structure, "height", &height))
return TRUE;
#else
guint width, height;
if (!gst_structure_get_int (structure, "width", (gint *) & width))
return TRUE;
if (!gst_structure_get_int (structure, "height", (gint *) & height))
return TRUE;
#endif
if (align->padding_left != 0 || align->padding_top != 0 ||
height != info->height + align->padding_bottom)
return TRUE;
if (height == info->height + align->padding_bottom) {
/* Some drivers may round up width to the padded with */
if (width == info->width + align->padding_right)
gst_structure_set (structure,
"width", G_TYPE_INT, width - align->padding_right,
"height", G_TYPE_INT, height - align->padding_bottom, NULL);
/* Some drivers may keep visible width and only round up bytesperline */
#ifndef USE_V4L2_TARGET_NV
else if (width == info->width)
#else
else if (width == (guint) info->width)
#endif
gst_structure_set (structure,
"height", G_TYPE_INT, height - align->padding_bottom, NULL);
}
return TRUE;
}
static const char * const NvBufSurfaceMemType_names[] =
{
[NVBUF_MEM_DEFAULT] = "nvbuf-mem-default",
[NVBUF_MEM_CUDA_PINNED] = "nvbuf-mem-cuda-pinned",
[NVBUF_MEM_CUDA_DEVICE] = "nvbuf-mem-cuda-device",
[NVBUF_MEM_CUDA_UNIFIED] = "nvbuf-mem-cuda-unified",
[NVBUF_MEM_SURFACE_ARRAY] = "nvbuf-mem-surface-array",
[NVBUF_MEM_HANDLE] = "nvbuf-mem-handle",
[NVBUF_MEM_SYSTEM] = "nvbuf-mem-system",
};
static gboolean
gst_h265_parse_process_nal (GstV4l2VideoDec *self, H265NalUnit * nalu)
{
H265SPS sps = { 0, };
guint nal_type;
H265Parser nalparser;
H265ParserResult pres = H265_PARSER_ERROR;
/* nothing to do for broken input */
if (G_UNLIKELY (nalu->size < 2)) {
return TRUE;
}
/* we have a peek as well */
nal_type = nalu->type;
switch (nal_type) {
case H265_NAL_SPS:
/* reset state, everything else is obsolete */
pres = h265_parser_parse_sps (&nalparser, nalu, &sps, TRUE);
/* arranged for a fallback sps.id, so use that one and only warn */
if (pres != H265_PARSER_OK) {
/* try to not parse VUI */
pres = h265_parser_parse_sps (&nalparser, nalu, &sps, FALSE);
if (pres != H265_PARSER_OK) {
return FALSE;
}
}
self->current_width = sps.width;
self->current_height = sps.height;
break;
}
return TRUE;
}
static gboolean
gst_h264_parse_process_nal (GstV4l2VideoDec *self, H264NalUnit * nalu)
{
guint nal_type;
H264SPS sps = { 0, };
H264NalParser nalparser;
H264ParserResult pres;
/* nothing to do for broken input */
if (G_UNLIKELY (nalu->size < 2)) {
GST_DEBUG_OBJECT (self, "not processing nal size %u", nalu->size);
return TRUE;
}
/* we have a peek as well */
nal_type = nalu->type;
GST_DEBUG_OBJECT (self, "processing nal of type %u , size %u",
nal_type, nalu->size);
if (nal_type == H264_NAL_SPS)
{
GST_DEBUG_OBJECT (self, "GOT SPS frame\n");
}
switch (nal_type) {
case H264_NAL_SPS:
/* reset state, everything else is obsolete */
pres = h264_parser_parse_sps (&nalparser, nalu, &sps, TRUE);
/* arranged for a fallback sps.id, so use that one and only warn */
if (pres != H264_PARSER_OK) {
GST_WARNING_OBJECT (self, "failed to parse SPS:");
return FALSE;
}
self->current_width = (sps.pic_width_in_mbs_minus1 + 1) << 4;
self->current_height = (sps.pic_height_in_map_units_minus1 + 1) << 4;
break;
}
return TRUE;
}
static GstFlowReturn
gst_v4l2_h265_stream_parser (GstV4l2VideoDec *self, gpointer data, guint32 size)
{
H265NalUnit nalu;
H265ParserResult pres;
gint current_off = 0;
guint nalu_size = 0;
GstFlowReturn ret = GST_FLOW_OK;
while (TRUE)
{
pres = h265_parser_identify_nalu (NULL, data, current_off, size, &nalu);
nalu_size += nalu.size + 4;
if (nalu_size >= size)
break;
switch (pres) {
case H265_PARSER_OK:
GST_DEBUG_OBJECT (self, "complete nal (offset, size): (%u, %u) ",
nalu.offset, nalu.size);
break;
case H265_PARSER_NO_NAL_END:
GST_WARNING_OBJECT (self, "Start of the nal found, but not the end");
break;
case H265_PARSER_ERROR:
/* should not really occur either */
GST_ELEMENT_ERROR (self, STREAM, FORMAT,
("Error parsing H.265 stream"), ("Invalid H.265 stream"));
ret = GST_FLOW_ERROR;
break;
case H265_PARSER_NO_NAL:
GST_ELEMENT_ERROR (self, STREAM, FORMAT,
("Error parsing H.265 stream"), ("No H.265 NAL unit found"));
ret = GST_FLOW_ERROR;
break;
default:
ret = GST_FLOW_ERROR;
//g_assert_not_reached ();
break;
}
GST_DEBUG_OBJECT (self, "%p complete nal found. Off: %u, Size: %u",
data, nalu.offset, nalu.size);
if (!gst_h265_parse_process_nal (self, &nalu)) {
GST_WARNING_OBJECT (self,
"broken/invalid nal Type: %d, Size: %u will be dropped",
nalu.type, nalu.size);
}
current_off += nalu.size;
}
GST_DEBUG_OBJECT (self,
"sps width = %d height = %d \n", self->current_width, self->current_height);
return ret;
}
static GstFlowReturn
gst_v4l2_h264_stream_parser (GstV4l2VideoDec *self, gpointer data, guint32 size)
{
H264NalUnit nalu;
H264ParserResult pres;
gint current_off = 0;
guint nalu_size = 0;
GstFlowReturn ret = GST_FLOW_OK;
while (TRUE)
{
pres = h264_parser_identify_nalu (NULL, data, current_off, size, &nalu);
nalu_size += nalu.size + 4;
if (nalu_size >= size)
break;
switch (pres)
{
case H264_PARSER_OK:
GST_DEBUG_OBJECT(self, "complete nal (offset, size): (%u, %u) ",
nalu.offset, nalu.size);
break;
case H264_PARSER_NO_NAL_END:
GST_WARNING_OBJECT(self, "parser will assume that the end of the data is the end of the NAL unit");
break;
case H264_PARSER_ERROR:
/* should not really occur either */
GST_ELEMENT_ERROR(self, STREAM, FORMAT,
("Error parsing H.264 stream"), ("Invalid H.264 stream"));
ret = GST_FLOW_ERROR;
break;
case H264_PARSER_NO_NAL:
GST_ELEMENT_ERROR(self, STREAM, FORMAT,
("Error parsing H.264 stream"), ("No H.264 NAL unit found"));
ret = GST_FLOW_ERROR;
break;
default:
ret = GST_FLOW_ERROR;
// g_assert_not_reached ();
break;
}
GST_DEBUG_OBJECT (self, "%p complete nal found. Off: %u, Size: %u",
data, nalu.offset, nalu.size);
if (!gst_h264_parse_process_nal (self, &nalu)) {
GST_WARNING_OBJECT (self,
"broken/invalid nal Type: %d, Size: %u will be dropped",
nalu.type, nalu.size);
}
current_off += nalu.size;
}
GST_DEBUG_OBJECT (self,
"sps width = %d height = %d", self->current_width, self->current_height);
return ret;
}
static gboolean
findvpxStartCode(GstV4l2Object *obj, const uint8_t* data, int size)
{
for (int i = 0; i < size - 2; i++)
{
if (GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_VP8)
{
if (data[i] == VP8_START_BYTE_0 &&
data[i + 1] == VP8_START_BYTE_1)
{
return TRUE;
}
}
else if (GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_VP9)
{
if (data[i] == VP9_START_BYTE_0 &&
data[i + 1] == VP9_START_BYTE_1 &&
data[i + 2] == VP9_START_BYTE_2)
{
return TRUE;
}
}
}
return FALSE; // Start code not found
}
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 vpx_ret = FALSE;
gboolean processed = FALSE;
GstBuffer *tmp;
GstTaskState task_state;
#ifdef USE_V4L2_TARGET_NV
gboolean trigger_drc = false;
GstV4l2BufferPool *v4l2pool = GST_V4L2_BUFFER_POOL (self->v4l2output->pool);
GstV4l2Object *obj = v4l2pool->obj;
#endif
GST_DEBUG_OBJECT (self, "Handling frame %d", frame->system_frame_number);
#ifdef USE_V4L2_TARGET_NV
if (is_cuvid == TRUE) {
if (((GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_VP8) ||
(GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_VP9)) && self->valid_vpx == FALSE)
{
GstMapInfo map;
if (!gst_buffer_map(frame->input_buffer, &map, GST_MAP_READ))
{
GST_ERROR_OBJECT(self, "couldnt map frame input_buffer\n");
}
vpx_ret = findvpxStartCode(obj, map.data, map.size);
if (vpx_ret == FALSE)
{
gst_buffer_unmap (frame->input_buffer, &map);
goto drop;
}
else if (vpx_ret == TRUE)
{
self->valid_vpx = TRUE;
}
gst_buffer_unmap (frame->input_buffer, &map);
}
if (((GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_H264) ||
(GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_H265)) &&
!(GST_BUFFER_FLAG_IS_SET(GST_BUFFER_CAST(frame->input_buffer),
GST_BUFFER_FLAG_DELTA_UNIT)))
{
GstMapInfo map;
if (!gst_buffer_map(frame->input_buffer, &map, GST_MAP_READ))
{
GST_ERROR_OBJECT(self, "couldnt map frame input_buffer\n");
}
if (GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_H264)
{
if (gst_v4l2_h264_stream_parser(self, map.data, map.size) != GST_FLOW_OK)
GST_ERROR_OBJECT(self, "h264 stream parsing failed");
}
else if ((GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_H265))
{
if (gst_v4l2_h265_stream_parser(self, map.data, map.size) != GST_FLOW_OK)
GST_ERROR_OBJECT(self, "h265 stream parsing failed");
}
gst_buffer_unmap (frame->input_buffer, &map);
if (self->old_width || self->old_height)
{
if ((self->old_width != self->current_width) ||
(self->old_height != self->current_height))
{
GST_INFO_OBJECT(self, "Decoder found new resolution. triggering DRC seq.");
GST_INFO_OBJECT(self, "Old Resolution: [%d x %d], New Resolution: [%d x %d]",
self->old_width, self->old_height, self->current_width, self->current_height);
trigger_drc = true;
}
}
self->old_width = self->current_width;
self->old_height = self->current_height;
if (trigger_drc == true)
{
GstStructure *structure = NULL;
GstCaps *dec_sink_caps = gst_pad_get_current_caps(decoder->sinkpad);
if (dec_sink_caps != NULL)
{
dec_sink_caps = gst_caps_make_writable(dec_sink_caps);
structure = gst_caps_get_structure(dec_sink_caps, 0);
gst_structure_set(structure, "width", G_TYPE_INT, self->current_width,
"height", G_TYPE_INT, self->current_height,
NULL);
/* Replace coded size with visible size, we want to negotiate visible size
* with downstream, not coded size. */
gst_caps_map_in_place(dec_sink_caps, gst_v4l2_video_remove_padding, self);
GST_DEBUG_OBJECT(self, "dec_sink_caps: %s", gst_caps_to_string(dec_sink_caps));
}
self->idr_received = FALSE;
gst_v4l2_video_dec_finish(decoder);
gst_v4l2_object_stop(self->v4l2output);
{
GstCaps *caps = gst_pad_get_current_caps(decoder->srcpad);
if (caps)
{
GstQuery *query = gst_query_new_allocation(caps, FALSE);
gst_pad_peer_query(decoder->srcpad, query);
gst_query_unref(query);
gst_caps_unref(caps);
}
}
gst_v4l2_object_stop(self->v4l2capture);
self->output_flow = GST_FLOW_OK;
g_mutex_lock(&self->v4l2capture->cplane_stopped_lock);
while (self->v4l2capture->capture_plane_stopped != TRUE)
{
g_cond_wait(&self->v4l2capture->cplane_stopped_cond,
&self->v4l2capture->cplane_stopped_lock);
}
self->v4l2capture->capture_plane_stopped = FALSE;
g_mutex_unlock(&self->v4l2capture->cplane_stopped_lock);
gst_v4l2_object_close(self->v4l2output);
gst_v4l2_object_close(self->v4l2capture);
if (!gst_v4l2_object_open(self->v4l2output))
GST_ERROR_OBJECT(self, "gst_v4l2_object_open (self->v4l2output) failed\n");
if (!gst_v4l2_object_open_shared(self->v4l2capture, self->v4l2output))
{
GST_ERROR_OBJECT(self, "gstv4l2object open shared failed\n");
if (GST_V4L2_IS_OPEN(self->v4l2output))
gst_v4l2_object_close(self->v4l2output);
return GST_FLOW_ERROR;
}
if (dec_sink_caps != NULL)
{
ret = gst_v4l2_object_set_format(self->v4l2output, dec_sink_caps, &error);
gst_caps_unref(dec_sink_caps);
} else
{
GST_ERROR_OBJECT(self, "Decoder sink caps == NULL");
}
if (ret)
self->input_state = gst_video_codec_state_ref(self->input_state);
else
gst_v4l2_error(self, &error);
set_v4l2_controls(self);
GST_INFO_OBJECT(self, "Reset Done");
}
else if (self->set_format == true)
{
set_v4l2_controls(self);
}
}
}
/* CUVID and TEGRA decoders return format when SPS/PPS is received along with
* a frame. In case of RTSP inputs we drop the DELTA units which are not
* decodable independently until we receive I / IDR frame.
*/
if ((GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_H264) ||
(GST_V4L2_PIXELFORMAT(obj) == V4L2_PIX_FMT_H265))
{
if ((GST_BUFFER_FLAG_IS_SET (GST_BUFFER_CAST(frame->input_buffer),
GST_BUFFER_FLAG_DELTA_UNIT)) &&
(self->idr_received == FALSE))
{
GST_DEBUG_OBJECT (decoder, "Delta Unit Received, Dropping...");
gst_video_decoder_drop_frame (decoder, frame);
return GST_FLOW_OK;
}
self->idr_received = TRUE;
}
#endif
if (is_cuvid == TRUE) {
if (self->skip_frames == V4L2_SKIP_FRAMES_TYPE_DECODE_IDR_ONLY) {
// Decode only I Frames and drop others.
if (GST_BUFFER_FLAG_IS_SET (GST_BUFFER_CAST(frame->input_buffer),
GST_BUFFER_FLAG_DELTA_UNIT)) {
gst_video_decoder_drop_frame (decoder, frame);
return GST_FLOW_OK;
}
}
}
if (enable_latency_measurement)
{
self->buffer_in_time = get_current_system_timestamp ();
g_hash_table_insert (self->hash_pts_systemtime, &frame->pts, &self->buffer_in_time);
}
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);
#ifndef USE_V4L2_TARGET_NV
gst_buffer_pool_config_set_params (config, self->input_state->caps,
self->v4l2output->info.size, 2, 2);
#else
if (obj->mode != GST_V4L2_IO_USERPTR)
obj->min_buffers = 2;
if (V4L2_TYPE_IS_OUTPUT (obj->type)) {
gst_buffer_pool_config_set_params (config, self->input_state->caps,
self->v4l2output->info.size, obj->min_buffers, obj->min_buffers);
}
#endif
/* 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);
/* TODO: nvparser should return proper format of a stream with first
* few bytes of stream header*/
#ifdef USE_V4L2_TARGET_NV
if (!processed) {
if (is_cuvid == TRUE)
processed = TRUE;
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 (gst_pad_get_task_state (GST_VIDEO_DECODER_SRC_PAD (self)) !=
GST_TASK_STARTED)
ret = self->output_flow;
goto drop;
} else if (ret != GST_FLOW_OK) {
goto process_failed;
}
}
if (V4L2_TYPE_IS_OUTPUT (obj->type)) {
gint retval;
gint dqevent_loop_count = 0;
struct v4l2_event ev;
#ifndef USE_V4L2_TARGET_NV_X86
/* This is WAR for Bug 3544450 */
if(!processed)
{
processed = TRUE;
/* Wait for DQEVENT for 0.6 sec */
while (dqevent_loop_count < LOOP_COUNT_TO_WAIT_FOR_DQEVENT) {
memset (&ev, 0, sizeof (ev));
retval = obj->ioctl (obj->video_fd, VIDIOC_DQEVENT, &ev);
dqevent_loop_count ++;
if (retval != 0)
{
if (errno == EINVAL)
goto process_failed;
usleep (WAIT_TIME_PER_LOOP_FOR_DQEVENT); //TODO is this needed ?
continue;
}
else
break;
}
if (dqevent_loop_count == LOOP_COUNT_TO_WAIT_FOR_DQEVENT) {
g_print ("Stream format not found, dropping the frame\n");
goto drop;
}
}
else
#endif
{
while (1) {
memset (&ev, 0, sizeof (ev));
retval = obj->ioctl (obj->video_fd, VIDIOC_DQEVENT, &ev);
if (retval != 0)
{
if (errno == EINVAL)
goto process_failed;
if (is_cuvid != TRUE) {
if (ev.sequence == 0) {
g_print ("Stream format not found, dropping the frame\n");
goto drop;
}
}
usleep(100*1000); //TODO is this needed ?
continue;
}
else
break;
}
}
}
#endif
/* 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);
GST_DEBUG_OBJECT (self, "Acquired caps: %" GST_PTR_FORMAT, acquired_caps);
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);
GST_DEBUG_OBJECT (self, "Available caps: %" GST_PTR_FORMAT, 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_DEBUG_OBJECT (self, "Filtered caps: %" GST_PTR_FORMAT, filter);
gst_caps_unref (acquired_caps);
gst_caps_unref (available_caps);
#ifndef USE_V4L2_TARGET_NV
caps = gst_pad_peer_query_caps (decoder->srcpad, filter);
gst_caps_unref (filter);
#else
caps = gst_pad_peer_query_caps (decoder->srcpad,
gst_pad_get_pad_template_caps (GST_VIDEO_DECODER_SRC_PAD (decoder)));
gst_caps_unref (filter);
if (gst_caps_is_empty (caps)) {
GstPad *pad = GST_VIDEO_DECODER_SRC_PAD (decoder);
caps = gst_pad_get_pad_template_caps (pad);
}
#endif
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 */
#ifndef USE_V4L2_TARGET_NV
if (gst_v4l2_object_set_format (self->v4l2capture, caps, &error))
gst_video_info_from_caps (&info, caps);
else
gst_v4l2_clear_error (&error);
#endif
gst_caps_unref (caps);
output_state = gst_video_decoder_set_output_state (decoder,
info.finfo->format, info.width, info.height, self->input_state);
#ifdef USE_V4L2_TARGET_NV
if (output_state->caps)
gst_caps_unref (output_state->caps);
output_state->caps = gst_video_info_to_caps (&output_state->info);
GstCapsFeatures *features = gst_caps_features_new ("memory:NVMM", NULL);
gst_caps_set_features (output_state->caps, 0, features);
{
// convert "cudadec_mem_type" to NvBufSurfaceMemType
int buf_surface_mem_type = 0;
switch(self->cudadec_mem_type)
{
case 0:
{
if(is_cuvid == TRUE)
buf_surface_mem_type = NVBUF_MEM_CUDA_DEVICE;
else
buf_surface_mem_type = NVBUF_MEM_SURFACE_ARRAY;
break;
}
case 1:
buf_surface_mem_type = NVBUF_MEM_CUDA_PINNED;
break;
case 2:
buf_surface_mem_type = NVBUF_MEM_CUDA_UNIFIED;
break;
}
gst_caps_set_simple(output_state->caps, "nvbuf-memory-type", G_TYPE_STRING , NvBufSurfaceMemType_names[buf_surface_mem_type], NULL);
gst_caps_set_simple(output_state->caps, "gpu-id", G_TYPE_INT , self->cudadec_gpu_id, NULL);
}
#endif
/* 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;
#ifdef USE_V4L2_TARGET_NV
if (self->v4l2capture->pool) {
if (self->cap_buf_dynamic_allocation == CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_FW_RW_PLAYBACK) {
gst_v4l2_buffer_pool_enable_dynamic_allocation (GST_V4L2_BUFFER_POOL (self->v4l2capture->pool),
TRUE);
} else if (self->cap_buf_dynamic_allocation == CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_RW_PLAYBACK && self->rate < 0) {
gst_v4l2_buffer_pool_enable_dynamic_allocation (GST_V4L2_BUFFER_POOL (self->v4l2capture->pool),
TRUE);
} else if (self->cap_buf_dynamic_allocation == CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_FW_PLAYBACK && self->rate > 0) {
gst_v4l2_buffer_pool_enable_dynamic_allocation (GST_V4L2_BUFFER_POOL (self->v4l2capture->pool),
TRUE);
} else {
gst_v4l2_buffer_pool_enable_dynamic_allocation (GST_V4L2_BUFFER_POOL (self->v4l2capture->pool),
FALSE);
}
}
#endif
}
#ifdef USE_V4L2_TARGET_NV
if (is_cuvid == TRUE)
{
if ((trigger_drc == false) && (self->set_format == true))
{
GstStructure *sink_pad_st = NULL, *src_pad_st = NULL;
const GValue *framerate = NULL;
GstCaps *dec_sink_caps = gst_pad_get_current_caps(decoder->sinkpad);
GstCaps *dec_src_caps = gst_pad_get_current_caps(decoder->srcpad);
if (G_UNLIKELY (dec_sink_caps != NULL) && G_UNLIKELY (dec_src_caps != NULL))
{
GST_DEBUG_OBJECT(self, "dec_sink_caps: %s", gst_caps_to_string(dec_sink_caps));
dec_src_caps = gst_caps_make_writable(dec_src_caps);
sink_pad_st = gst_caps_get_structure(dec_sink_caps, 0);
src_pad_st = gst_caps_get_structure(dec_src_caps, 0);
framerate = gst_structure_get_value(sink_pad_st, "framerate");
if (framerate)
gst_structure_set_value(src_pad_st, "framerate", framerate);
GST_DEBUG_OBJECT(self, "dec_src_caps: %s", gst_caps_to_string(dec_src_caps));
gst_pad_set_caps(decoder->srcpad, dec_src_caps);
gst_caps_unref(dec_sink_caps);
gst_caps_unref(dec_src_caps);
}
}
trigger_drc = false;
self->set_format = false;
}
#endif
task_state = gst_pad_get_task_state (GST_VIDEO_DECODER_SRC_PAD (self));
if (task_state == GST_TASK_STOPPED || task_state == GST_TASK_PAUSED) {
/* 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;
}
#ifdef USE_V4L2_TARGET_NV
set_v4l2_video_mpeg_class (self->v4l2capture,
V4L2_CID_MPEG_SET_POLL_INTERRUPT, 1);
#endif
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 */
self->output_flow = GST_FLOW_FLUSHING;
if (!gst_pad_start_task (decoder->srcpad,
(GstTaskFunction) gst_v4l2_video_dec_loop, self, NULL))
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 (gst_pad_get_task_state (GST_VIDEO_DECODER_SRC_PAD (self)) !=
GST_TASK_STARTED)
ret = self->output_flow;
goto drop;
} else if (ret != GST_FLOW_OK) {
if (gst_pad_get_task_state (GST_VIDEO_DECODER_SINK_PAD(self)) ==
GST_TASK_STOPPED)
{
ret = GST_FLOW_OK;
goto drop;
}
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);
/* Parse SEI data from the bitsream */
if ((is_cuvid == TRUE) && (self->extract_sei_type5_data == TRUE))
{
uint8_t *sei_type5_payload = NULL;
GstMapInfo map;
if (!gst_buffer_map (tmp, &map, GST_MAP_READWRITE))
{
GST_DEBUG_OBJECT (self, "couldnt map\n");
goto process_failed;
}
else
{
uint32_t payload_size = 0;
uint8_t *stream_data = (uint8_t *)map.data;
sei_type5_payload = parse_sei_data (stream_data, map.size,
&payload_size, self->sei_uuid_string);
if (sei_type5_payload != NULL)
{
GST_DEBUG_OBJECT (self, "sei_type5_payload found\n");
GstVideoSEIMeta *video_sei_meta =
(GstVideoSEIMeta *)gst_buffer_add_meta(
frame->input_buffer, GST_VIDEO_SEI_META_INFO, NULL);
video_sei_meta->sei_metadata_type =
GST_USER_SEI_META;
video_sei_meta->sei_metadata_size = payload_size;
video_sei_meta->sei_metadata_ptr = sei_type5_payload;
}
gst_buffer_unmap (tmp, &map);
}
}
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));
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);
if (GST_CLOCK_TIME_IS_VALID (self->v4l2capture->duration)) {
latency = self->v4l2capture->min_buffers * self->v4l2capture->duration;
GST_DEBUG_OBJECT (self, "Setting latency: %" GST_TIME_FORMAT " (%"
G_GUINT32_FORMAT " * %" G_GUINT64_FORMAT, GST_TIME_ARGS (latency),
self->v4l2capture->min_buffers, self->v4l2capture->duration);
gst_video_decoder_set_latency (decoder, latency, latency);
} else {
GST_WARNING_OBJECT (self, "Duration invalid, not setting 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:
break;
#ifdef USE_V4L2_TARGET_NV
case GST_EVENT_GAP:
GST_DEBUG_OBJECT (self, "GAP event received");
if (GST_V4L2_IS_ACTIVE (self->v4l2capture))
{
GST_DEBUG_OBJECT (self, "CAPTURE PLANE buffer pool is active"
" so passing on the event to downstream");
break;
}
else
{
GST_DEBUG_OBJECT (self, "CAPTURE PLANE buffer pool is inactive"
" so ignore the gap event");
gst_event_unref(event);
return TRUE;
}
#endif
default:
break;
}
ret = GST_VIDEO_DECODER_CLASS (parent_class)->sink_event (decoder, event);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_START:
break;
default:
break;
}
#ifdef USE_V4L2_TARGET_NV
if ((GstNvCustomEventType)GST_EVENT_TYPE (event) == GST_NVEVENT_DEC_DROP_FRAME_INTERVAL_UPDATE) {
gchar* stream_id = NULL;
gst_nvevent_parse_dec_drop_frame_interval_update (event, &stream_id, &self->drop_frame_interval);
}
if ((GstNvCustomEventType)GST_EVENT_TYPE (event) == GST_NVEVENT_DEC_SKIP_FRAME_UPDATE) {
gchar* stream_id = NULL;
gst_nvevent_parse_dec_skip_frame_update (event, &stream_id, &self->skip_frames);
/* Handle skip-frame event */
if (self->skip_frames != V4L2_SKIP_FRAMES_TYPE_NONE) {
if (!set_v4l2_video_mpeg_class (self->v4l2output,
V4L2_CID_MPEG_VIDEO_SKIP_FRAMES,
self->skip_frames)) {
g_print ("S_EXT_CTRLS for SKIP_FRAMES failed\n");
return FALSE;
}
}
}
if ((GstNvCustomEventType)GST_EVENT_TYPE (event) == GST_NVEVENT_DEC_ENABLE_LOW_LATENCY_MODE) {
gchar* stream_id = NULL;
gst_nvevent_parse_dec_enable_low_latency_mode (event, &stream_id, &self->cudadec_low_latency);
/* TODO: Handle enable of low_latency mode */
}
#endif
return ret;
}
#ifdef USE_V4L2_TARGET_NV
static gboolean
gst_v4l2_video_dec_src_event (GstVideoDecoder * decoder, GstEvent * event)
{
GstV4l2VideoDec *self = GST_V4L2_VIDEO_DEC (decoder);
gboolean ret;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
{
GstFormat format;
gdouble rate;
GstSeekFlags flags;
GstSeekType start_type, stop_type;
gint64 start, stop;
gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
&stop_type, &stop);
self->rate = rate;
GST_DEBUG_OBJECT (self, "Seek event received with rate %f", rate);
if (self->v4l2capture->pool) {
if (self->cap_buf_dynamic_allocation == CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_FW_RW_PLAYBACK) {
gst_v4l2_buffer_pool_enable_dynamic_allocation (GST_V4L2_BUFFER_POOL (self->v4l2capture->pool),
TRUE);
} else if (self->cap_buf_dynamic_allocation == CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_RW_PLAYBACK && self->rate < 0) {
gst_v4l2_buffer_pool_enable_dynamic_allocation (GST_V4L2_BUFFER_POOL (self->v4l2capture->pool),
TRUE);
} else if (self->cap_buf_dynamic_allocation == CAP_BUF_DYNAMIC_ALLOC_ENABLED_FOR_FW_PLAYBACK && self->rate > 0) {
gst_v4l2_buffer_pool_enable_dynamic_allocation (GST_V4L2_BUFFER_POOL (self->v4l2capture->pool),
TRUE);
} else {
gst_v4l2_buffer_pool_enable_dynamic_allocation (GST_V4L2_BUFFER_POOL (self->v4l2capture->pool),
FALSE);
}
}
break;
}
default:
break;
}
ret = GST_VIDEO_DECODER_CLASS (parent_class)->src_event (decoder, event);
return ret;
}
#endif
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);
#ifdef USE_V4L2_TARGET_NV
set_v4l2_video_mpeg_class (self->v4l2capture,
V4L2_CID_MPEG_SET_POLL_INTERRUPT, 0);
#endif
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);
#ifdef USE_V4L2_TARGET_NV
g_cond_clear (&self->v4l2capture->cplane_stopped_cond);
g_mutex_clear (&self->v4l2capture->cplane_stopped_lock);
#endif
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 */
#ifdef USE_V4L2_TARGET_NV
if (is_cuvid == TRUE) {
default_num_extra_surfaces = 0; //default for dGPU
default_cudadec_low_latency = FALSE;
default_sei_extract_data = FALSE;
} else if (is_cuvid == FALSE) {
default_num_extra_surfaces = 1; //default for Tegra
}
self->skip_frames = DEFAULT_SKIP_FRAME_TYPE;
self->drop_frame_interval = 0;
self->decoded_picture_cnt = 0;
self->num_extra_surfaces = default_num_extra_surfaces;
self->valid_vpx = FALSE;
self->disable_dpb = DEFAULT_DISABLE_DPB;
self->enable_full_frame = DEFAULT_FULL_FRAME;
self->enable_frame_type_reporting = DEFAULT_FRAME_TYPR_REPORTING;
self->enable_error_check = DEFAULT_ERROR_CHECK;
self->enable_max_performance = DEFAULT_MAX_PERFORMANCE;
self->cudadec_mem_type = DEFAULT_CUDADEC_MEM_TYPE;
self->cudadec_gpu_id = DEFAULT_CUDADEC_GPU_ID;
self->cudadec_low_latency = default_cudadec_low_latency;
self->idr_received = FALSE;
self->rate = 1;
self->cap_buf_dynamic_allocation = DEFAULT_CAP_BUF_DYNAMIC_ALLOCATION;
#endif
const gchar * latency = g_getenv("NVDS_ENABLE_LATENCY_MEASUREMENT");
if(latency)
{
enable_latency_measurement = TRUE;
}
}
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),
GST_OBJECT (GST_VIDEO_DECODER_SINK_PAD (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),
GST_OBJECT (GST_VIDEO_DECODER_SRC_PAD (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;
#ifdef USE_V4L2_TARGET_NV
g_mutex_init (&self->v4l2capture->cplane_stopped_lock);
g_cond_init (&self->v4l2capture->cplane_stopped_cond);
#endif
}
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;
#ifndef USE_V4L2_TARGET_NV
GST_DEBUG_CATEGORY_INIT (gst_v4l2_video_dec_debug, "v4l2videodec", 0,
"V4L2 Video Decoder");
#else
GST_DEBUG_CATEGORY_INIT (gst_v4l2_video_dec_debug, "v4l2videodec", 0,
"NVIDIA V4L2 Video Decoder");
#endif
gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_dispose);
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_finalize);
#ifdef USE_V4L2_TARGET_NV
if (is_cuvid == FALSE) {
gobject_class->set_property =
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_set_property_tegra);
gobject_class->get_property =
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_get_property_tegra);
} else if (is_cuvid == TRUE) {
gobject_class->set_property =
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_set_property_cuvid);
gobject_class->get_property =
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_get_property_cuvid);
}
g_object_class_install_property (gobject_class, PROP_SKIP_FRAME,
g_param_spec_enum ("skip-frames",
"Skip frames",
"Type of frames to skip during decoding",
GST_TYPE_V4L2_VID_DEC_SKIP_FRAMES,
DEFAULT_SKIP_FRAME_TYPE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
GST_PARAM_MUTABLE_PLAYING));
g_object_class_install_property (gobject_class, PROP_DROP_FRAME_INTERVAL,
g_param_spec_uint ("drop-frame-interval",
"Drop frames interval",
"Interval to drop the frames,ex: value of 5 means every 5th frame will be given by decoder, rest all dropped",
0,
30, 30,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY));
g_object_class_install_property (gobject_class, PROP_NUM_EXTRA_SURFACES,
g_param_spec_uint ("num-extra-surfaces",
"Number of extra surfaces",
"Additional number of surfaces in addition to min decode surfaces given by the v4l2 driver",
0,
55, 55,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY));
if (is_cuvid == FALSE) {
g_object_class_install_property (gobject_class, PROP_DISABLE_DPB,
g_param_spec_boolean ("disable-dpb",
"Disable DPB buffer",
"Set to disable DPB buffer for low latency",
DEFAULT_DISABLE_DPB, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_USE_FULL_FRAME,
g_param_spec_boolean ("enable-full-frame",
"Full Frame",
"Whether or not the data is full framed",
DEFAULT_FULL_FRAME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_ENABLE_FRAME_TYPE_REPORTING,
g_param_spec_boolean ("enable-frame-type-reporting",
"enable-frame-type-reporting", "Set to enable frame type reporting",
DEFAULT_FRAME_TYPR_REPORTING,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_OPEN_MJPEG_BLOCK,
g_param_spec_boolean ("mjpeg",
"Open MJPEG Block",
"Set to open MJPEG block",
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_ENABLE_ERROR_CHECK,
g_param_spec_boolean ("enable-error-check",
"enable-error-check",
"Set to enable error check",
DEFAULT_ERROR_CHECK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_ENABLE_MAX_PERFORMANCE,
g_param_spec_boolean ("enable-max-performance",
"Enable max performance", "Set to enable max performance",
DEFAULT_MAX_PERFORMANCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_CAP_BUF_DYNAMIC_ALLOCATION,
g_param_spec_enum ("capture-buffer-dynamic-allocation",
"Enable capture buffer dynamic allocation",
"Set to enable capture buffer dynamic allocation",
GST_TYPE_V4L2_DEC_CAP_BUF_DYNAMIC_ALLOC,
DEFAULT_CAP_BUF_DYNAMIC_ALLOCATION,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
} else if (is_cuvid == TRUE) {
g_object_class_install_property (gobject_class, PROP_CUDADEC_MEM_TYPE,
g_param_spec_enum ("cudadec-memtype",
"Memory type for cuda decoder buffers",
"Set to specify memory type for cuda decoder buffers",
GST_TYPE_V4L2_VID_CUDADEC_MEM_TYPE, DEFAULT_CUDADEC_MEM_TYPE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY));
g_object_class_install_property (gobject_class, PROP_CUDADEC_GPU_ID,
g_param_spec_uint ("gpu-id",
"GPU Device ID",
"Set to GPU Device ID for decoder ",
0,
G_MAXUINT, DEFAULT_CUDADEC_GPU_ID,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY));
g_object_class_install_property (gobject_class, PROP_CUDADEC_LOW_LATENCY,
g_param_spec_boolean ("low-latency-mode",
"CUVID Decode Low Latency Mode",
"Set low latency mode for bitstreams having I and IPPP frames",
default_cudadec_low_latency,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_EXTRACT_SEI_TYPE5_DATA,
g_param_spec_boolean ("extract-sei-type5-data",
"extract-sei-type5-data",
"Set to extract and attach SEI type5 unregistered data on output buffer",
default_sei_extract_data,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_SEI_UUID_STRING,
g_param_spec_string ("sei-uuid", "SEI UUID String",
"Set 16 bytes UUID string for SEI Parsing, extract-sei-type5-data should be TRUE",
NULL,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
}
#endif
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->drain = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_drain);
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);
#ifdef USE_V4L2_TARGET_NV
video_decoder_class->src_event =
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_src_event);
#endif
element_class->change_state =
GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_change_state);
gst_v4l2_object_install_m2m_properties_helper (gobject_class);
#ifdef USE_V4L2_TARGET_NV
if (is_cuvid == FALSE)
gst_v4l2_object_install_m2m_dec_iomode_properties_helper (gobject_class);
#endif
}
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;
#ifndef USE_V4L2_TARGET_NV
/* 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));
gst_element_class_set_static_metadata (element_class, cdata->longname,
"Codec/Decoder/Video", cdata->description,
"Nicolas Dufresne <nicolas.dufresne@collabora.com>");
gst_caps_unref (cdata->sink_caps);
gst_caps_unref (cdata->src_caps);
#else
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_v4l2dec_sink_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_v4l2dec_src_template));
gst_element_class_set_static_metadata (element_class,
"NVIDIA v4l2 video decoder" /*cdata->longname */ ,
"Codec/Decoder/Video",
"Decode video streams via V4L2 API" /*cdata->description */ ,
"Nicolas Dufresne <nicolas.dufresne@collabora.com>, Viranjan Pagar <vpagar@nvidia.com>");
#endif
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;
}
#ifndef USE_V4L2_TARGET_NV
static gchar *
gst_v4l2_video_dec_set_metadata (GstStructure * s, GstV4l2VideoDecCData * cdata,
const gchar * basename)
{
gchar *codec_name = NULL;
gchar *type_name = NULL;
#define SET_META(codec) \
G_STMT_START { \
cdata->longname = "V4L2 " codec " Decoder"; \
cdata->description = "Decodes " codec " streams via V4L2 API"; \
codec_name = g_ascii_strdown (codec, -1); \
} G_STMT_END
if (gst_structure_has_name (s, "image/jpeg")) {
SET_META ("JPEG");
} else if (gst_structure_has_name (s, "video/mpeg")) {
gint mpegversion = 0;
gst_structure_get_int (s, "mpegversion", &mpegversion);
if (mpegversion == 2) {
SET_META ("MPEG2");
} else {
SET_META ("MPEG4");
}
} else if (gst_structure_has_name (s, "video/x-h263")) {
SET_META ("H263");
} else if (gst_structure_has_name (s, "video/x-h264")) {
SET_META ("H264");
} else if (gst_structure_has_name (s, "video/x-wmv")) {
SET_META ("VC1");
} else if (gst_structure_has_name (s, "video/x-vp8")) {
SET_META ("VP8");
} else if (gst_structure_has_name (s, "video/x-vp9")) {
SET_META ("VP9");
} else if (gst_structure_has_name (s, "video/x-bayer")) {
SET_META ("BAYER");
} else if (gst_structure_has_name (s, "video/x-sonix")) {
SET_META ("SONIX");
} else if (gst_structure_has_name (s, "video/x-pwc1")) {
SET_META ("PWC1");
} else if (gst_structure_has_name (s, "video/x-pwc2")) {
SET_META ("PWC2");
} else {
/* This code should be kept on sync with the exposed CODEC type of format
* from gstv4l2object.c. This warning will only occure in case we forget
* to also add a format here. */
gchar *s_str = gst_structure_to_string (s);
g_warning ("Missing fixed name mapping for caps '%s', this is a GStreamer "
"bug, please report at https://bugs.gnome.org", s_str);
g_free (s_str);
}
if (codec_name) {
type_name = g_strdup_printf ("v4l2%sdec", codec_name);
if (g_type_from_name (type_name) != 0) {
g_free (type_name);
type_name = g_strdup_printf ("v4l2%s%sdec", basename, codec_name);
}
g_free (codec_name);
}
return type_name;
#undef SET_META
}
void
gst_v4l2_video_dec_register (GstPlugin * plugin, const gchar * basename,
const gchar * device_path, GstCaps * sink_caps, GstCaps * src_caps)
{
gint i;
for (i = 0; i < gst_caps_get_size (sink_caps); i++) {
GstV4l2VideoDecCData *cdata;
GstStructure *s;
GTypeQuery type_query;
GTypeInfo type_info = { 0, };
GType type, subtype;
gchar *type_name;
s = gst_caps_get_structure (sink_caps, i);
cdata = g_new0 (GstV4l2VideoDecCData, 1);
cdata->device = g_strdup (device_path);
cdata->sink_caps = gst_caps_new_empty ();
gst_caps_append_structure (cdata->sink_caps, gst_structure_copy (s));
cdata->src_caps = gst_caps_ref (src_caps);
type_name = gst_v4l2_video_dec_set_metadata (s, cdata, basename);
/* Skip over if we hit an unmapped type */
if (!type_name) {
g_free (cdata);
continue;
}
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;
subtype = g_type_register_static (type, type_name, &type_info, 0);
if (!gst_element_register (plugin, type_name, GST_RANK_PRIMARY + 1,
subtype))
GST_WARNING ("Failed to register plugin '%s'", type_name);
g_free (type_name);
}
}
#else
void
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;
GstV4l2VideoDecCData *cdata;
cdata = g_new0 (GstV4l2VideoDecCData, 1);
cdata->device = g_strdup (device_path);
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;
subtype = g_type_register_static (type, "nvv4l2decoder", &type_info, 0);
gst_element_register (plugin, "nvv4l2decoder", GST_RANK_PRIMARY + 11, subtype);
}
#endif