mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 02:30:35 +00:00
fd665514ba
The payloader didn't copy anything so far, the depayloader copied every possible meta. Let's make it consistent and just copy all metas without tags or with only the video tag. https://bugzilla.gnome.org/show_bug.cgi?id=751774
1576 lines
51 KiB
C
1576 lines
51 KiB
C
/* GStreamer
|
|
* Copyright (C) <2006> Wim Taymans <wim.taymans@gmail.com>
|
|
* Copyright (C) <2014> Jurgen Slowack <jurgenslowack@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <gst/base/gstbitreader.h>
|
|
#include <gst/rtp/gstrtpbuffer.h>
|
|
#include <gst/video/video.h>
|
|
#include "gstrtph265depay.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (rtph265depay_debug);
|
|
#define GST_CAT_DEFAULT (rtph265depay_debug)
|
|
|
|
/* This is what we'll default to when downstream hasn't
|
|
* expressed a restriction or preference via caps */
|
|
#define DEFAULT_BYTE_STREAM TRUE
|
|
#define DEFAULT_ACCESS_UNIT FALSE
|
|
|
|
/* 3 zero bytes syncword */
|
|
static const guint8 sync_bytes[] = { 0, 0, 0, 1 };
|
|
|
|
static GstStaticPadTemplate gst_rtp_h265_depay_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (
|
|
/* FIXME - hvc1 and hev1 formats are not supported yet */
|
|
/*"video/x-h265, "
|
|
"stream-format = (string) hvc1, alignment = (string) au; "
|
|
"video/x-h265, "
|
|
"stream-format = (string) hev1, alignment = (string) au; " */
|
|
"video/x-h265, "
|
|
"stream-format = (string) byte-stream, alignment = (string) { nal, au }")
|
|
);
|
|
|
|
static GstStaticPadTemplate gst_rtp_h265_depay_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-rtp, "
|
|
"media = (string) \"video\", "
|
|
"clock-rate = (int) 90000, " "encoding-name = (string) \"H265\"")
|
|
/** optional parameters **/
|
|
/* "profile-space = (int) [ 0, 3 ], " */
|
|
/* "profile-id = (int) [ 0, 31 ], " */
|
|
/* "tier-flag = (int) [ 0, 1 ], " */
|
|
/* "level-id = (int) [ 0, 255 ], " */
|
|
/* "interop-constraints = (string) ANY, " */
|
|
/* "profile-compatibility-indicator = (string) ANY, " */
|
|
/* "sprop-sub-layer-id = (int) [ 0, 6 ], " */
|
|
/* "recv-sub-layer-id = (int) [ 0, 6 ], " */
|
|
/* "max-recv-level-id = (int) [ 0, 255 ], " */
|
|
/* "tx-mode = (string) {MST , SST}, " */
|
|
/* "sprop-vps = (string) ANY, " */
|
|
/* "sprop-sps = (string) ANY, " */
|
|
/* "sprop-pps = (string) ANY, " */
|
|
/* "sprop-sei = (string) ANY, " */
|
|
/* "max-lsr = (int) ANY, " *//* MUST be in the range of MaxLumaSR to 16 * MaxLumaSR, inclusive */
|
|
/* "max-lps = (int) ANY, " *//* MUST be in the range of MaxLumaPS to 16 * MaxLumaPS, inclusive */
|
|
/* "max-cpb = (int) ANY, " *//* MUST be in the range of MaxCPB to 16 * MaxCPB, inclusive */
|
|
/* "max-dpb = (int) [1, 16], " */
|
|
/* "max-br = (int) ANY, " *//* MUST be in the range of MaxBR to 16 * MaxBR, inclusive, for the highest level */
|
|
/* "max-tr = (int) ANY, " *//* MUST be in the range of MaxTileRows to 16 * MaxTileRows, inclusive, for the highest level */
|
|
/* "max-tc = (int) ANY, " *//* MUST be in the range of MaxTileCols to 16 * MaxTileCols, inclusive, for the highest level */
|
|
/* "max-fps = (int) ANY, " */
|
|
/* "sprop-max-don-diff = (int) [0, 32767], " */
|
|
/* "sprop-depack-buf-nalus = (int) [0, 32767], " */
|
|
/* "sprop-depack-buf-nalus = (int) [0, 4294967295], " */
|
|
/* "depack-buf-cap = (int) [1, 4294967295], " */
|
|
/* "sprop-segmentation-id = (int) [0, 3], " */
|
|
/* "sprop-spatial-segmentation-idc = (string) ANY, " */
|
|
/* "dec-parallel-cap = (string) ANY, " */
|
|
);
|
|
|
|
#define gst_rtp_h265_depay_parent_class parent_class
|
|
G_DEFINE_TYPE (GstRtpH265Depay, gst_rtp_h265_depay,
|
|
GST_TYPE_RTP_BASE_DEPAYLOAD);
|
|
|
|
static void gst_rtp_h265_depay_finalize (GObject * object);
|
|
|
|
static GstStateChangeReturn gst_rtp_h265_depay_change_state (GstElement *
|
|
element, GstStateChange transition);
|
|
|
|
static GstBuffer *gst_rtp_h265_depay_process (GstRTPBaseDepayload * depayload,
|
|
GstRTPBuffer * rtp);
|
|
static gboolean gst_rtp_h265_depay_setcaps (GstRTPBaseDepayload * filter,
|
|
GstCaps * caps);
|
|
static gboolean gst_rtp_h265_depay_handle_event (GstRTPBaseDepayload * depay,
|
|
GstEvent * event);
|
|
|
|
static void
|
|
gst_rtp_h265_depay_class_init (GstRtpH265DepayClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstRTPBaseDepayloadClass *gstrtpbasedepayload_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
gstrtpbasedepayload_class = (GstRTPBaseDepayloadClass *) klass;
|
|
|
|
gobject_class->finalize = gst_rtp_h265_depay_finalize;
|
|
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&gst_rtp_h265_depay_src_template));
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&gst_rtp_h265_depay_sink_template));
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class,
|
|
"RTP H265 depayloader", "Codec/Depayloader/Network/RTP",
|
|
"Extracts H265 video from RTP packets (draft-ietf-payload-rtp-h265-03.txt)",
|
|
"Jurgen Slowack <jurgenslowack@gmail.com>");
|
|
gstelement_class->change_state = gst_rtp_h265_depay_change_state;
|
|
|
|
gstrtpbasedepayload_class->process_rtp_packet = gst_rtp_h265_depay_process;
|
|
gstrtpbasedepayload_class->set_caps = gst_rtp_h265_depay_setcaps;
|
|
gstrtpbasedepayload_class->handle_event = gst_rtp_h265_depay_handle_event;
|
|
}
|
|
|
|
static void
|
|
gst_rtp_h265_depay_init (GstRtpH265Depay * rtph265depay)
|
|
{
|
|
rtph265depay->adapter = gst_adapter_new ();
|
|
rtph265depay->picture_adapter = gst_adapter_new ();
|
|
rtph265depay->byte_stream = DEFAULT_BYTE_STREAM;
|
|
rtph265depay->stream_format = (gchar *) g_malloc (10);
|
|
rtph265depay->merge = DEFAULT_ACCESS_UNIT;
|
|
rtph265depay->vps = g_ptr_array_new_with_free_func (
|
|
(GDestroyNotify) gst_buffer_unref);
|
|
rtph265depay->sps = g_ptr_array_new_with_free_func (
|
|
(GDestroyNotify) gst_buffer_unref);
|
|
rtph265depay->pps = g_ptr_array_new_with_free_func (
|
|
(GDestroyNotify) gst_buffer_unref);
|
|
}
|
|
|
|
static void
|
|
gst_rtp_h265_depay_reset (GstRtpH265Depay * rtph265depay)
|
|
{
|
|
gst_adapter_clear (rtph265depay->adapter);
|
|
rtph265depay->wait_start = TRUE;
|
|
gst_adapter_clear (rtph265depay->picture_adapter);
|
|
rtph265depay->picture_start = FALSE;
|
|
rtph265depay->last_keyframe = FALSE;
|
|
rtph265depay->last_ts = 0;
|
|
rtph265depay->current_fu_type = 0;
|
|
rtph265depay->new_codec_data = FALSE;
|
|
g_ptr_array_set_size (rtph265depay->vps, 0);
|
|
g_ptr_array_set_size (rtph265depay->sps, 0);
|
|
g_ptr_array_set_size (rtph265depay->pps, 0);
|
|
}
|
|
|
|
static void
|
|
gst_rtp_h265_depay_finalize (GObject * object)
|
|
{
|
|
GstRtpH265Depay *rtph265depay;
|
|
|
|
rtph265depay = GST_RTP_H265_DEPAY (object);
|
|
|
|
if (rtph265depay->codec_data)
|
|
gst_buffer_unref (rtph265depay->codec_data);
|
|
|
|
g_free (rtph265depay->stream_format);
|
|
|
|
g_object_unref (rtph265depay->adapter);
|
|
g_object_unref (rtph265depay->picture_adapter);
|
|
|
|
g_ptr_array_free (rtph265depay->vps, TRUE);
|
|
g_ptr_array_free (rtph265depay->sps, TRUE);
|
|
g_ptr_array_free (rtph265depay->pps, TRUE);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_rtp_h265_depay_negotiate (GstRtpH265Depay * rtph265depay)
|
|
{
|
|
GstCaps *caps;
|
|
gint byte_stream = -1;
|
|
gint merge = -1;
|
|
|
|
caps =
|
|
gst_pad_get_allowed_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (rtph265depay));
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay, "allowed caps: %" GST_PTR_FORMAT, caps);
|
|
|
|
if (caps) {
|
|
if (gst_caps_get_size (caps) > 0) {
|
|
GstStructure *s = gst_caps_get_structure (caps, 0);
|
|
const gchar *str = NULL;
|
|
|
|
if ((str = gst_structure_get_string (s, "stream-format"))) {
|
|
|
|
strcpy (rtph265depay->stream_format, str);
|
|
|
|
if (strcmp (str, "hev1") == 0) {
|
|
byte_stream = FALSE;
|
|
} else if (strcmp (str, "hvc1") == 0) {
|
|
byte_stream = FALSE;
|
|
} else if (strcmp (str, "byte-stream") == 0) {
|
|
byte_stream = TRUE;
|
|
} else {
|
|
GST_DEBUG_OBJECT (rtph265depay, "unknown stream-format: %s", str);
|
|
}
|
|
}
|
|
|
|
if ((str = gst_structure_get_string (s, "alignment"))) {
|
|
if (strcmp (str, "au") == 0) {
|
|
merge = TRUE;
|
|
} else if (strcmp (str, "nal") == 0) {
|
|
merge = FALSE;
|
|
} else {
|
|
GST_DEBUG_OBJECT (rtph265depay, "unknown alignment: %s", str);
|
|
}
|
|
}
|
|
}
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
if (byte_stream != -1) {
|
|
GST_DEBUG_OBJECT (rtph265depay, "downstream requires byte-stream %d",
|
|
byte_stream);
|
|
rtph265depay->byte_stream = byte_stream;
|
|
} else {
|
|
GST_DEBUG_OBJECT (rtph265depay, "defaulting to byte-stream %d",
|
|
DEFAULT_BYTE_STREAM);
|
|
strcpy (rtph265depay->stream_format, "byte-stream");
|
|
rtph265depay->byte_stream = DEFAULT_BYTE_STREAM;
|
|
}
|
|
if (merge != -1) {
|
|
GST_DEBUG_OBJECT (rtph265depay, "downstream requires merge %d", merge);
|
|
rtph265depay->merge = merge;
|
|
} else {
|
|
GST_DEBUG_OBJECT (rtph265depay, "defaulting to merge %d",
|
|
DEFAULT_ACCESS_UNIT);
|
|
rtph265depay->merge = DEFAULT_ACCESS_UNIT;
|
|
}
|
|
}
|
|
|
|
/* Stolen from bad/gst/mpegtsdemux/payloader_parsers.c */
|
|
/* variable length Exp-Golomb parsing according to H.265 spec section 9.2*/
|
|
static gboolean
|
|
read_golomb (GstBitReader * br, guint32 * value)
|
|
{
|
|
guint8 b, leading_zeros = -1;
|
|
*value = 1;
|
|
|
|
for (b = 0; !b; leading_zeros++) {
|
|
if (!gst_bit_reader_get_bits_uint8 (br, &b, 1))
|
|
return FALSE;
|
|
*value *= 2;
|
|
}
|
|
|
|
*value = (*value >> 1) - 1;
|
|
if (leading_zeros > 0) {
|
|
guint32 tmp = 0;
|
|
if (!gst_bit_reader_get_bits_uint32 (br, &tmp, leading_zeros))
|
|
return FALSE;
|
|
*value += tmp;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_sps (GstMapInfo * map, guint32 * sps_id)
|
|
{ /* To parse seq_parameter_set_id */
|
|
GstBitReader br = GST_BIT_READER_INIT (map->data + 15,
|
|
map->size - 15);
|
|
|
|
if (map->size < 16)
|
|
return FALSE;
|
|
|
|
if (!read_golomb (&br, sps_id))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_pps (GstMapInfo * map, guint32 * sps_id, guint32 * pps_id)
|
|
{ /* To parse picture_parameter_set_id */
|
|
GstBitReader br = GST_BIT_READER_INIT (map->data + 2,
|
|
map->size - 2);
|
|
|
|
if (map->size < 3)
|
|
return FALSE;
|
|
|
|
if (!read_golomb (&br, pps_id))
|
|
return FALSE;
|
|
if (!read_golomb (&br, sps_id))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gst_rtp_h265_set_src_caps (GstRtpH265Depay * rtph265depay)
|
|
{
|
|
gboolean res;
|
|
GstCaps *srccaps;
|
|
|
|
if (!rtph265depay->byte_stream &&
|
|
(!rtph265depay->new_codec_data ||
|
|
rtph265depay->vps->len == 0 || rtph265depay->sps->len == 0
|
|
|| rtph265depay->pps->len == 0))
|
|
return TRUE;
|
|
|
|
srccaps = gst_caps_new_simple ("video/x-h265",
|
|
"stream-format", G_TYPE_STRING,
|
|
rtph265depay->stream_format,
|
|
"alignment", G_TYPE_STRING, rtph265depay->merge ? "au" : "nal", NULL);
|
|
|
|
if (!rtph265depay->byte_stream) {
|
|
|
|
GstBuffer *codec_data;
|
|
gint i = 0;
|
|
gint len;
|
|
guint num_vps = rtph265depay->vps->len;
|
|
guint num_sps = rtph265depay->sps->len;
|
|
guint num_pps = rtph265depay->pps->len;
|
|
GstMapInfo map, nalmap;
|
|
guint8 *data;
|
|
gint nl;
|
|
guint8 num_arrays = 0;
|
|
guint new_size;
|
|
GstBitReader br;
|
|
guint32 tmp;
|
|
guint8 tmp8 = 0;
|
|
guint32 max_sub_layers_minus1, temporal_id_nesting_flag, chroma_format_idc,
|
|
bit_depth_luma_minus8, bit_depth_chroma_minus8,
|
|
min_spatial_segmentation_idc;
|
|
|
|
/* Fixme: Current implementation is not embedding SEI in codec_data */
|
|
|
|
if (num_sps == 0)
|
|
return FALSE;
|
|
|
|
/* start with 23 bytes header */
|
|
len = 23;
|
|
|
|
num_arrays = (num_vps > 0) + (num_sps > 0) + (num_pps > 0);
|
|
len += num_arrays;
|
|
|
|
/* add size of vps, sps & pps */
|
|
for (i = 0; i < num_vps; i++)
|
|
len += 2 + gst_buffer_get_size (g_ptr_array_index (rtph265depay->vps, i));
|
|
for (i = 0; i < num_sps; i++)
|
|
len += 2 + gst_buffer_get_size (g_ptr_array_index (rtph265depay->sps, i));
|
|
for (i = 0; i < num_pps; i++)
|
|
len += 2 + gst_buffer_get_size (g_ptr_array_index (rtph265depay->pps, i));
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay,
|
|
"constructing codec_data: num_vps =%d num_sps=%d, num_pps=%d", num_vps,
|
|
num_sps, num_pps);
|
|
|
|
codec_data = gst_buffer_new_and_alloc (len);
|
|
g_debug ("alloc_len: %u", len);
|
|
gst_buffer_map (codec_data, &map, GST_MAP_READWRITE);
|
|
data = map.data;
|
|
|
|
memset (data, 0, map.size);
|
|
|
|
/* Parsing sps to get the info required further on */
|
|
|
|
gst_buffer_map (g_ptr_array_index (rtph265depay->sps, 0), &nalmap,
|
|
GST_MAP_READ);
|
|
|
|
max_sub_layers_minus1 = ((nalmap.data[2]) >> 1) & 0x07;
|
|
temporal_id_nesting_flag = nalmap.data[2] & 0x01;
|
|
|
|
gst_bit_reader_init (&br, nalmap.data + 15, nalmap.size - 15);
|
|
|
|
read_golomb (&br, &tmp); /* sps_seq_parameter_set_id */
|
|
read_golomb (&br, &chroma_format_idc); /* chroma_format_idc */
|
|
|
|
if (chroma_format_idc == 3)
|
|
|
|
gst_bit_reader_get_bits_uint8 (&br, &tmp8, 1); /* separate_colour_plane_flag */
|
|
|
|
read_golomb (&br, &tmp); /* pic_width_in_luma_samples */
|
|
read_golomb (&br, &tmp); /* pic_height_in_luma_samples */
|
|
|
|
gst_bit_reader_get_bits_uint8 (&br, &tmp8, 1); /* conformance_window_flag */
|
|
if (tmp8) {
|
|
read_golomb (&br, &tmp); /* conf_win_left_offset */
|
|
read_golomb (&br, &tmp); /* conf_win_right_offset */
|
|
read_golomb (&br, &tmp); /* conf_win_top_offset */
|
|
read_golomb (&br, &tmp); /* conf_win_bottom_offset */
|
|
}
|
|
|
|
read_golomb (&br, &bit_depth_luma_minus8); /* bit_depth_luma_minus8 */
|
|
read_golomb (&br, &bit_depth_chroma_minus8); /* bit_depth_chroma_minus8 */
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay,
|
|
"Ignoring min_spatial_segmentation for now (assuming zero)");
|
|
|
|
min_spatial_segmentation_idc = 0; /* NOTE - we ignore this for now, but in a perfect world, we should continue parsing to obtain the real value */
|
|
|
|
nl = nalmap.size;
|
|
|
|
gst_buffer_unmap (g_ptr_array_index (rtph265depay->sps, 0), &nalmap);
|
|
|
|
/* HEVCDecoderConfigurationVersion = 1 */
|
|
data[0] = 1;
|
|
|
|
/* Copy from profile_tier_level (Rec. ITU-T H.265 (04/2013) section 7.3.3
|
|
*
|
|
* profile_space | tier_flat | profile_idc |
|
|
* profile_compatibility_flags | constraint_indicator_flags |
|
|
* level_idc | progressive_source_flag | interlaced_source_flag
|
|
* non_packed_constraint_flag | frame_only_constraint_flag
|
|
* reserved_zero_44bits | level_idc */
|
|
gst_buffer_map (g_ptr_array_index (rtph265depay->sps, 0), &nalmap,
|
|
GST_MAP_READ);
|
|
for (i = 0; i < 12; i++)
|
|
data[i + 1] = nalmap.data[i];
|
|
gst_buffer_unmap (g_ptr_array_index (rtph265depay->sps, 0), &nalmap);
|
|
|
|
/* min_spatial_segmentation_idc */
|
|
GST_WRITE_UINT16_BE (data + 13, min_spatial_segmentation_idc);
|
|
data[13] |= 0xf0;
|
|
data[15] = 0xfc; /* keeping parrallelismType as zero (unknown) */
|
|
data[16] = 0xfc | chroma_format_idc;
|
|
data[17] = 0xf8 | bit_depth_luma_minus8;
|
|
data[18] = 0xf8 | bit_depth_chroma_minus8;
|
|
data[19] = 0x00; /* keep avgFrameRate as unspecified */
|
|
data[20] = 0x00; /* keep avgFrameRate as unspecified */
|
|
/* constFrameRate(2 bits): 0, stream may or may not be of constant framerate
|
|
* numTemporalLayers (3 bits): number of temporal layers, value from SPS
|
|
* TemporalIdNested (1 bit): sps_temporal_id_nesting_flag from SPS
|
|
* lengthSizeMinusOne (2 bits): plus 1 indicates the length of the NALUnitLength */
|
|
data[21] =
|
|
0x00 | ((max_sub_layers_minus1 +
|
|
1) << 3) | (temporal_id_nesting_flag << 2) | (nl - 1);
|
|
GST_WRITE_UINT8 (data + 22, num_arrays); /* numOfArrays */
|
|
|
|
data += 23;
|
|
|
|
/* copy all VPS */
|
|
if (num_vps > 0) {
|
|
/* array_completeness | reserved_zero bit | nal_unit_type */
|
|
data[0] = 0x00 | 0x20;
|
|
data++;
|
|
|
|
GST_WRITE_UINT16_BE (data, num_vps);
|
|
data += 2;
|
|
|
|
for (i = 0; i < num_vps; i++) {
|
|
gsize nal_size =
|
|
gst_buffer_get_size (g_ptr_array_index (rtph265depay->vps, i));
|
|
GST_WRITE_UINT16_BE (data, nal_size);
|
|
gst_buffer_extract (g_ptr_array_index (rtph265depay->vps, i), 0,
|
|
data + 2, nal_size);
|
|
data += 2 + nal_size;
|
|
GST_DEBUG_OBJECT (rtph265depay, "Copied VPS %d of length %u", i,
|
|
(guint) nal_size);
|
|
}
|
|
}
|
|
|
|
/* copy all SPS */
|
|
if (num_sps > 0) {
|
|
/* array_completeness | reserved_zero bit | nal_unit_type */
|
|
data[0] = 0x00 | 0x21;
|
|
data++;
|
|
|
|
GST_WRITE_UINT16_BE (data, num_sps);
|
|
data += 2;
|
|
|
|
for (i = 0; i < num_sps; i++) {
|
|
gsize nal_size =
|
|
gst_buffer_get_size (g_ptr_array_index (rtph265depay->sps, i));
|
|
GST_WRITE_UINT16_BE (data, nal_size);
|
|
gst_buffer_extract (g_ptr_array_index (rtph265depay->sps, i), 0,
|
|
data + 2, nal_size);
|
|
data += 2 + nal_size;
|
|
GST_DEBUG_OBJECT (rtph265depay, "Copied SPS %d of length %u", i,
|
|
(guint) nal_size);
|
|
}
|
|
}
|
|
|
|
/* copy all PPS */
|
|
if (num_pps > 0) {
|
|
/* array_completeness | reserved_zero bit | nal_unit_type */
|
|
data[0] = 0x00 | 0x22;
|
|
data++;
|
|
|
|
GST_WRITE_UINT16_BE (data, num_pps);
|
|
data += 2;
|
|
|
|
for (i = 0; i < num_pps; i++) {
|
|
gsize nal_size =
|
|
gst_buffer_get_size (g_ptr_array_index (rtph265depay->pps, i));
|
|
GST_WRITE_UINT16_BE (data, nal_size);
|
|
gst_buffer_extract (g_ptr_array_index (rtph265depay->pps, i), 0,
|
|
data + 2, nal_size);
|
|
data += 2 + nal_size;
|
|
GST_DEBUG_OBJECT (rtph265depay, "Copied PPS %d of length %u", i,
|
|
(guint) nal_size);
|
|
}
|
|
}
|
|
|
|
new_size = data - map.data;
|
|
gst_buffer_unmap (codec_data, &map);
|
|
gst_buffer_set_size (codec_data, new_size);
|
|
|
|
gst_caps_set_simple (srccaps,
|
|
"codec_data", GST_TYPE_BUFFER, codec_data, NULL);
|
|
gst_buffer_unref (codec_data);
|
|
}
|
|
|
|
if (gst_pad_has_current_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (rtph265depay))) {
|
|
GstCaps *old_caps =
|
|
gst_pad_get_current_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (rtph265depay));
|
|
|
|
/* Only update the caps if they are not equal. For
|
|
* AVC we don't update caps if only the codec_data
|
|
* changes. This is the same behaviour as in h264parse
|
|
* and gstrtph264depay
|
|
*/
|
|
if (rtph265depay->byte_stream) {
|
|
if (!gst_caps_is_equal (srccaps, old_caps))
|
|
res =
|
|
gst_pad_set_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (rtph265depay),
|
|
srccaps);
|
|
else
|
|
res = TRUE;
|
|
} else {
|
|
GstCaps *tmp_caps = gst_caps_copy (srccaps);
|
|
GstStructure *old_s, *tmp_s;
|
|
|
|
old_s = gst_caps_get_structure (old_caps, 0);
|
|
tmp_s = gst_caps_get_structure (tmp_caps, 0);
|
|
if (gst_structure_has_field (old_s, "codec_data"))
|
|
gst_structure_set_value (tmp_s, "codec_data",
|
|
gst_structure_get_value (old_s, "codec_data"));
|
|
|
|
if (!gst_caps_is_equal (old_caps, tmp_caps))
|
|
res =
|
|
gst_pad_set_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (rtph265depay),
|
|
srccaps);
|
|
else
|
|
res = TRUE;
|
|
|
|
gst_caps_unref (tmp_caps);
|
|
}
|
|
} else {
|
|
res =
|
|
gst_pad_set_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (rtph265depay),
|
|
srccaps);
|
|
}
|
|
|
|
gst_caps_unref (srccaps);
|
|
|
|
/* Insert SPS and PPS into the stream on next opportunity */
|
|
if (rtph265depay->sps->len > 0 || rtph265depay->pps->len > 0) {
|
|
gint i;
|
|
GstBuffer *codec_data;
|
|
GstMapInfo map;
|
|
guint8 *data;
|
|
guint len = 0;
|
|
|
|
for (i = 0; i < rtph265depay->sps->len; i++) {
|
|
len += 4 + gst_buffer_get_size (g_ptr_array_index (rtph265depay->sps, i));
|
|
}
|
|
|
|
for (i = 0; i < rtph265depay->pps->len; i++) {
|
|
len += 4 + gst_buffer_get_size (g_ptr_array_index (rtph265depay->pps, i));
|
|
}
|
|
|
|
codec_data = gst_buffer_new_and_alloc (len);
|
|
gst_buffer_map (codec_data, &map, GST_MAP_WRITE);
|
|
data = map.data;
|
|
|
|
for (i = 0; i < rtph265depay->sps->len; i++) {
|
|
GstBuffer *sps_buf = g_ptr_array_index (rtph265depay->sps, i);
|
|
guint sps_size = gst_buffer_get_size (sps_buf);
|
|
|
|
if (rtph265depay->byte_stream)
|
|
memcpy (data, sync_bytes, sizeof (sync_bytes));
|
|
else
|
|
GST_WRITE_UINT32_BE (data, sps_size);
|
|
gst_buffer_extract (sps_buf, 0, data + 4, -1);
|
|
data += 4 + sps_size;
|
|
}
|
|
|
|
for (i = 0; i < rtph265depay->pps->len; i++) {
|
|
GstBuffer *pps_buf = g_ptr_array_index (rtph265depay->pps, i);
|
|
guint pps_size = gst_buffer_get_size (pps_buf);
|
|
|
|
if (rtph265depay->byte_stream)
|
|
memcpy (data, sync_bytes, sizeof (sync_bytes));
|
|
else
|
|
GST_WRITE_UINT32_BE (data, pps_size);
|
|
gst_buffer_extract (pps_buf, 0, data + 4, -1);
|
|
data += 4 + pps_size;
|
|
}
|
|
|
|
gst_buffer_unmap (codec_data, &map);
|
|
if (rtph265depay->codec_data)
|
|
gst_buffer_unref (rtph265depay->codec_data);
|
|
rtph265depay->codec_data = codec_data;
|
|
}
|
|
|
|
if (res)
|
|
rtph265depay->new_codec_data = FALSE;
|
|
|
|
return res;
|
|
}
|
|
|
|
gboolean
|
|
gst_rtp_h265_add_vps_sps_pps (GstElement * rtph265, GPtrArray * vps_array,
|
|
GPtrArray * sps_array, GPtrArray * pps_array, GstBuffer * nal)
|
|
{
|
|
GstMapInfo map;
|
|
guchar type;
|
|
guint i;
|
|
|
|
gst_buffer_map (nal, &map, GST_MAP_READ);
|
|
|
|
type = (map.data[0] >> 1) & 0x3f;
|
|
|
|
if (type == GST_H265_VPS_NUT) {
|
|
guint32 vps_id = (map.data[2] >> 4) & 0x0f;
|
|
|
|
for (i = 0; i < vps_array->len; i++) {
|
|
GstBuffer *vps = g_ptr_array_index (vps_array, i);
|
|
GstMapInfo vpsmap;
|
|
guint32 tmp_vps_id;
|
|
|
|
gst_buffer_map (vps, &vpsmap, GST_MAP_READ);
|
|
tmp_vps_id = (vpsmap.data[2] >> 4) & 0x0f;
|
|
|
|
if (vps_id == tmp_vps_id) {
|
|
if (map.size == vpsmap.size &&
|
|
memcmp (map.data, vpsmap.data, vpsmap.size) == 0) {
|
|
GST_LOG_OBJECT (rtph265, "Unchanged VPS %u, not updating", vps_id);
|
|
gst_buffer_unmap (vps, &vpsmap);
|
|
goto drop;
|
|
} else {
|
|
gst_buffer_unmap (vps, &vpsmap);
|
|
g_ptr_array_remove_index_fast (vps_array, i);
|
|
g_ptr_array_add (vps_array, nal);
|
|
GST_LOG_OBJECT (rtph265, "Modified VPS %u, replacing", vps_id);
|
|
goto done;
|
|
}
|
|
}
|
|
gst_buffer_unmap (vps, &vpsmap);
|
|
}
|
|
GST_LOG_OBJECT (rtph265, "Adding new VPS %u", vps_id);
|
|
g_ptr_array_add (vps_array, nal);
|
|
} else if (type == GST_H265_SPS_NUT) {
|
|
guint32 sps_id;
|
|
|
|
if (!parse_sps (&map, &sps_id)) {
|
|
GST_WARNING_OBJECT (rtph265, "Invalid SPS,"
|
|
" can't parse seq_parameter_set_id");
|
|
goto drop;
|
|
}
|
|
|
|
for (i = 0; i < sps_array->len; i++) {
|
|
GstBuffer *sps = g_ptr_array_index (sps_array, i);
|
|
GstMapInfo spsmap;
|
|
guint32 tmp_sps_id;
|
|
|
|
gst_buffer_map (sps, &spsmap, GST_MAP_READ);
|
|
parse_sps (&spsmap, &tmp_sps_id);
|
|
|
|
if (sps_id == tmp_sps_id) {
|
|
if (map.size == spsmap.size &&
|
|
memcmp (map.data, spsmap.data, spsmap.size) == 0) {
|
|
GST_LOG_OBJECT (rtph265, "Unchanged SPS %u, not updating", sps_id);
|
|
gst_buffer_unmap (sps, &spsmap);
|
|
goto drop;
|
|
} else {
|
|
gst_buffer_unmap (sps, &spsmap);
|
|
g_ptr_array_remove_index_fast (sps_array, i);
|
|
g_ptr_array_add (sps_array, nal);
|
|
GST_LOG_OBJECT (rtph265, "Modified SPS %u, replacing", sps_id);
|
|
goto done;
|
|
}
|
|
}
|
|
gst_buffer_unmap (sps, &spsmap);
|
|
}
|
|
GST_LOG_OBJECT (rtph265, "Adding new SPS %u", sps_id);
|
|
g_ptr_array_add (sps_array, nal);
|
|
} else if (type == GST_H265_PPS_NUT) {
|
|
guint32 sps_id;
|
|
guint32 pps_id;
|
|
|
|
if (!parse_pps (&map, &sps_id, &pps_id)) {
|
|
GST_WARNING_OBJECT (rtph265, "Invalid PPS,"
|
|
" can't parse seq_parameter_set_id or pic_parameter_set_id");
|
|
goto drop;
|
|
}
|
|
|
|
for (i = 0; i < pps_array->len; i++) {
|
|
GstBuffer *pps = g_ptr_array_index (pps_array, i);
|
|
GstMapInfo ppsmap;
|
|
guint32 tmp_sps_id;
|
|
guint32 tmp_pps_id;
|
|
|
|
|
|
gst_buffer_map (pps, &ppsmap, GST_MAP_READ);
|
|
parse_pps (&ppsmap, &tmp_sps_id, &tmp_pps_id);
|
|
|
|
if (pps_id == tmp_pps_id) {
|
|
if (map.size == ppsmap.size &&
|
|
memcmp (map.data, ppsmap.data, ppsmap.size) == 0) {
|
|
GST_LOG_OBJECT (rtph265, "Unchanged PPS %u:%u, not updating", sps_id,
|
|
pps_id);
|
|
gst_buffer_unmap (pps, &ppsmap);
|
|
goto drop;
|
|
} else {
|
|
gst_buffer_unmap (pps, &ppsmap);
|
|
g_ptr_array_remove_index_fast (pps_array, i);
|
|
g_ptr_array_add (pps_array, nal);
|
|
GST_LOG_OBJECT (rtph265, "Modified PPS %u:%u, replacing",
|
|
sps_id, pps_id);
|
|
goto done;
|
|
}
|
|
}
|
|
gst_buffer_unmap (pps, &ppsmap);
|
|
}
|
|
GST_LOG_OBJECT (rtph265, "Adding new PPS %u:%i", sps_id, pps_id);
|
|
g_ptr_array_add (pps_array, nal);
|
|
} else {
|
|
goto drop;
|
|
}
|
|
|
|
done:
|
|
gst_buffer_unmap (nal, &map);
|
|
|
|
return TRUE;
|
|
|
|
drop:
|
|
gst_buffer_unmap (nal, &map);
|
|
gst_buffer_unref (nal);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
gst_rtp_h265_depay_add_vps_sps_pps (GstRtpH265Depay * rtph265depay,
|
|
GstBuffer * nal)
|
|
{
|
|
if (gst_rtp_h265_add_vps_sps_pps (GST_ELEMENT (rtph265depay),
|
|
rtph265depay->vps, rtph265depay->sps, rtph265depay->pps, nal))
|
|
rtph265depay->new_codec_data = TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_rtp_h265_depay_setcaps (GstRTPBaseDepayload * depayload, GstCaps * caps)
|
|
{
|
|
gint clock_rate;
|
|
GstStructure *structure = gst_caps_get_structure (caps, 0);
|
|
GstRtpH265Depay *rtph265depay;
|
|
const gchar *ps;
|
|
GstBuffer *codec_data;
|
|
GstMapInfo map;
|
|
guint8 *ptr;
|
|
|
|
rtph265depay = GST_RTP_H265_DEPAY (depayload);
|
|
|
|
if (!gst_structure_get_int (structure, "clock-rate", &clock_rate))
|
|
clock_rate = 90000;
|
|
depayload->clock_rate = clock_rate;
|
|
|
|
/* Base64 encoded, comma separated config NALs */
|
|
ps = gst_structure_get_string (structure, "sprop-parameter-sets");
|
|
|
|
/* negotiate with downstream w.r.t. output format and alignment */
|
|
gst_rtp_h265_depay_negotiate (rtph265depay);
|
|
|
|
if (rtph265depay->byte_stream && ps != NULL) {
|
|
/* for bytestream we only need the parameter sets but we don't error out
|
|
* when they are not there, we assume they are in the stream. */
|
|
gchar **params;
|
|
guint len, total;
|
|
gint i;
|
|
|
|
params = g_strsplit (ps, ",", 0);
|
|
|
|
/* count total number of bytes in base64. Also include the sync bytes in
|
|
* front of the params. */
|
|
len = 0;
|
|
for (i = 0; params[i]; i++) {
|
|
len += strlen (params[i]);
|
|
len += sizeof (sync_bytes);
|
|
}
|
|
/* we seriously overshoot the length, but it's fine. */
|
|
codec_data = gst_buffer_new_and_alloc (len);
|
|
|
|
gst_buffer_map (codec_data, &map, GST_MAP_WRITE);
|
|
ptr = map.data;
|
|
total = 0;
|
|
for (i = 0; params[i]; i++) {
|
|
guint save = 0;
|
|
gint state = 0;
|
|
|
|
GST_DEBUG_OBJECT (depayload, "decoding param %d (%s)", i, params[i]);
|
|
memcpy (ptr, sync_bytes, sizeof (sync_bytes));
|
|
ptr += sizeof (sync_bytes);
|
|
len =
|
|
g_base64_decode_step (params[i], strlen (params[i]), ptr, &state,
|
|
&save);
|
|
GST_DEBUG_OBJECT (depayload, "decoded %d bytes", len);
|
|
total += len + sizeof (sync_bytes);
|
|
ptr += len;
|
|
}
|
|
gst_buffer_unmap (codec_data, &map);
|
|
gst_buffer_resize (codec_data, 0, total);
|
|
g_strfreev (params);
|
|
|
|
/* keep the codec_data, we need to send it as the first buffer. We cannot
|
|
* push it in the adapter because the adapter might be flushed on discont.
|
|
*/
|
|
if (rtph265depay->codec_data)
|
|
gst_buffer_unref (rtph265depay->codec_data);
|
|
rtph265depay->codec_data = codec_data;
|
|
} else if (!rtph265depay->byte_stream) {
|
|
gchar **params;
|
|
gint i;
|
|
|
|
if (ps == NULL)
|
|
goto incomplete_caps;
|
|
|
|
params = g_strsplit (ps, ",", 0);
|
|
|
|
GST_DEBUG_OBJECT (depayload, "we have %d params", g_strv_length (params));
|
|
|
|
/* start with 23 bytes header */
|
|
for (i = 0; params[i]; i++) {
|
|
GstBuffer *nal;
|
|
GstMapInfo nalmap;
|
|
gsize nal_len;
|
|
guint save = 0;
|
|
gint state = 0;
|
|
|
|
nal_len = strlen (params[i]);
|
|
nal = gst_buffer_new_and_alloc (nal_len);
|
|
gst_buffer_map (nal, &nalmap, GST_MAP_READWRITE);
|
|
|
|
nal_len =
|
|
g_base64_decode_step (params[i], nal_len, nalmap.data, &state, &save);
|
|
|
|
GST_DEBUG_OBJECT (depayload, "adding param %d as %s", i,
|
|
(((nalmap.data[0] >> 1) & 0x3f) ==
|
|
32) ? "VPS" : (((nalmap.data[0] >> 1) & 0x3f) ==
|
|
33) ? "SPS" : "PPS");
|
|
|
|
gst_buffer_unmap (nal, &nalmap);
|
|
gst_buffer_set_size (nal, nal_len);
|
|
|
|
gst_rtp_h265_depay_add_vps_sps_pps (rtph265depay, nal);
|
|
}
|
|
g_strfreev (params);
|
|
|
|
if (rtph265depay->sps->len == 0 || rtph265depay->pps->len == 0)
|
|
goto incomplete_caps;
|
|
}
|
|
|
|
return gst_rtp_h265_set_src_caps (rtph265depay);
|
|
|
|
/* ERRORS */
|
|
incomplete_caps:
|
|
{
|
|
GST_DEBUG_OBJECT (depayload, "we have incomplete caps,"
|
|
" doing setcaps later");
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_rtp_h265_complete_au (GstRtpH265Depay * rtph265depay,
|
|
GstClockTime * out_timestamp, gboolean * out_keyframe)
|
|
{
|
|
guint outsize;
|
|
GstBuffer *outbuf;
|
|
|
|
/* we had a picture in the adapter and we completed it */
|
|
GST_DEBUG_OBJECT (rtph265depay, "taking completed AU");
|
|
outsize = gst_adapter_available (rtph265depay->picture_adapter);
|
|
outbuf = gst_adapter_take_buffer (rtph265depay->picture_adapter, outsize);
|
|
|
|
*out_timestamp = rtph265depay->last_ts;
|
|
*out_keyframe = rtph265depay->last_keyframe;
|
|
|
|
rtph265depay->last_keyframe = FALSE;
|
|
rtph265depay->picture_start = FALSE;
|
|
|
|
return outbuf;
|
|
}
|
|
|
|
/* VPS/SPS/PPS/RADL/TSA/RASL/IDR/CRA is considered key, all others DELTA;
|
|
* so downstream waiting for keyframe can pick up at VPS/SPS/PPS/IDR */
|
|
|
|
#define NAL_TYPE_IS_PARAMETER_SET(nt) ( ((nt) == GST_H265_VPS_NUT)\
|
|
|| ((nt) == GST_H265_SPS_NUT)\
|
|
|| ((nt) == GST_H265_PPS_NUT) )
|
|
|
|
#define NAL_TYPE_IS_CODED_SLICE_SEGMENT(nt) ( ((nt) == GST_H265_NAL_SLICE_TRAIL_N)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_TRAIL_R)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_TSA_N)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_TSA_R)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_STSA_N)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_STSA_R)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_RASL_N)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_RASL_R)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_BLA_W_LP)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_BLA_W_RADL)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_BLA_N_LP)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_IDR_W_RADL)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_IDR_N_LP)\
|
|
|| ((nt) == GST_H265_NAL_SLICE_CRA_NUT) )
|
|
|
|
#define NAL_TYPE_IS_KEY(nt) (NAL_TYPE_IS_PARAMETER_SET(nt) || NAL_TYPE_IS_CODED_SLICE_SEGMENT(nt))
|
|
|
|
static gboolean
|
|
foreach_metadata_copy (GstBuffer * inbuf, GstMeta ** meta, gpointer user_data)
|
|
{
|
|
CopyMetaData *data = user_data;
|
|
GstElement *element = data->element;
|
|
GstBuffer *outbuf = data->outbuf;
|
|
const GstMetaInfo *info = (*meta)->info;
|
|
const gchar *const *tags = gst_meta_api_type_get_tags (info->api);
|
|
|
|
if (!tags || (g_strv_length ((gchar **) tags) == 1
|
|
&& gst_meta_api_type_has_tag (info->api,
|
|
g_quark_from_string (GST_META_TAG_VIDEO_STR)))) {
|
|
GstMetaTransformCopy copy_data = { FALSE, 0, -1 };
|
|
GST_DEBUG_OBJECT (element, "copy metadata %s", g_type_name (info->api));
|
|
/* simply copy then */
|
|
info->transform_func (outbuf, *meta, inbuf,
|
|
_gst_meta_transform_copy, ©_data);
|
|
} else {
|
|
GST_DEBUG_OBJECT (element, "not copying metadata %s",
|
|
g_type_name (info->api));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* TODO: Should probably make copy_tag an array at some point */
|
|
void
|
|
gst_rtp_copy_meta (GstElement * element, GstBuffer * outbuf, GstBuffer * inbuf,
|
|
GQuark copy_tag)
|
|
{
|
|
CopyMetaData data = { element, outbuf, copy_tag };
|
|
|
|
gst_buffer_foreach_meta (inbuf, foreach_metadata_copy, &data);
|
|
}
|
|
|
|
static gboolean
|
|
foreach_metadata_drop (GstBuffer * inbuf, GstMeta ** meta, gpointer user_data)
|
|
{
|
|
GstRtpH265Depay *depay = user_data;
|
|
const GstMetaInfo *info = (*meta)->info;
|
|
const gchar *const *tags = gst_meta_api_type_get_tags (info->api);
|
|
|
|
if (!tags || (g_strv_length ((gchar **) tags) == 1
|
|
&& gst_meta_api_type_has_tag (info->api,
|
|
g_quark_from_string (GST_META_TAG_VIDEO_STR)))) {
|
|
GST_DEBUG_OBJECT (depay, "keeping metadata %s", g_type_name (info->api));
|
|
} else {
|
|
GST_DEBUG_OBJECT (depay, "dropping metadata %s", g_type_name (info->api));
|
|
*meta = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_rtp_h265_depay_handle_nal (GstRtpH265Depay * rtph265depay, GstBuffer * nal,
|
|
GstClockTime in_timestamp, gboolean marker)
|
|
{
|
|
GstRTPBaseDepayload *depayload = GST_RTP_BASE_DEPAYLOAD (rtph265depay);
|
|
gint nal_type;
|
|
GstMapInfo map;
|
|
GstBuffer *outbuf = NULL;
|
|
GstClockTime out_timestamp;
|
|
gboolean keyframe, out_keyframe;
|
|
|
|
gst_buffer_map (nal, &map, GST_MAP_READ);
|
|
if (G_UNLIKELY (map.size < 5))
|
|
goto short_nal;
|
|
|
|
nal_type = (map.data[4] >> 1) & 0x3f;
|
|
GST_DEBUG_OBJECT (rtph265depay, "handle NAL type %d (RTP marker bit %d)",
|
|
nal_type, marker);
|
|
|
|
keyframe = NAL_TYPE_IS_KEY (nal_type);
|
|
|
|
out_keyframe = keyframe;
|
|
out_timestamp = in_timestamp;
|
|
|
|
if (!rtph265depay->byte_stream) {
|
|
if (NAL_TYPE_IS_PARAMETER_SET (nal_type)) {
|
|
gst_rtp_h265_depay_add_vps_sps_pps (rtph265depay,
|
|
gst_buffer_copy_region (nal, GST_BUFFER_COPY_ALL,
|
|
4, gst_buffer_get_size (nal) - 4));
|
|
gst_buffer_unmap (nal, &map);
|
|
gst_buffer_unref (nal);
|
|
return NULL;
|
|
} else if (rtph265depay->sps->len == 0 || rtph265depay->pps->len == 0) {
|
|
/* Down push down any buffer in non-bytestream mode if the SPS/PPS haven't
|
|
* go through yet
|
|
*/
|
|
gst_pad_push_event (GST_RTP_BASE_DEPAYLOAD_SINKPAD (depayload),
|
|
gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM,
|
|
gst_structure_new ("GstForceKeyUnit",
|
|
"all-headers", G_TYPE_BOOLEAN, TRUE, NULL)));
|
|
gst_buffer_unmap (nal, &map);
|
|
gst_buffer_unref (nal);
|
|
return NULL;
|
|
}
|
|
|
|
if (rtph265depay->new_codec_data &&
|
|
rtph265depay->sps->len > 0 && rtph265depay->pps->len > 0)
|
|
gst_rtp_h265_set_src_caps (rtph265depay);
|
|
}
|
|
|
|
if (rtph265depay->merge) {
|
|
gboolean start = FALSE, complete = FALSE;
|
|
|
|
/* marker bit isn't mandatory so in the following code we try to detect
|
|
* an AU boundary (see H.265 spec section 7.4.2.4.4) */
|
|
if (!marker) {
|
|
|
|
if (NAL_TYPE_IS_CODED_SLICE_SEGMENT (nal_type)) {
|
|
/* A NAL unit (X) ends an access unit if the next-occurring VCL NAL unit (Y) has the high-order bit of the first byte after its NAL unit header equal to 1 */
|
|
start = TRUE;
|
|
if (((map.data[6] >> 7) & 0x01) == 1) {
|
|
complete = TRUE;
|
|
}
|
|
complete = TRUE;
|
|
} else if ((nal_type >= 32 && nal_type <= 35)
|
|
|| nal_type == 39 || (nal_type >= 41 && nal_type <= 44)
|
|
|| (nal_type >= 48 && nal_type <= 55)) {
|
|
/* VPS, SPS, PPS, SEI, ... terminate an access unit */
|
|
complete = TRUE;
|
|
}
|
|
GST_DEBUG_OBJECT (depayload, "start %d, complete %d", start, complete);
|
|
|
|
if (complete && rtph265depay->picture_start)
|
|
outbuf = gst_rtp_h265_complete_au (rtph265depay, &out_timestamp,
|
|
&out_keyframe);
|
|
}
|
|
/* add to adapter */
|
|
gst_buffer_unmap (nal, &map);
|
|
|
|
GST_DEBUG_OBJECT (depayload, "adding NAL to picture adapter");
|
|
gst_adapter_push (rtph265depay->picture_adapter, nal);
|
|
rtph265depay->last_ts = in_timestamp;
|
|
rtph265depay->last_keyframe |= keyframe;
|
|
rtph265depay->picture_start |= start;
|
|
|
|
if (marker)
|
|
outbuf = gst_rtp_h265_complete_au (rtph265depay, &out_timestamp,
|
|
&out_keyframe);
|
|
} else {
|
|
/* no merge, output is input nal */
|
|
GST_DEBUG_OBJECT (depayload, "using NAL as output");
|
|
outbuf = nal;
|
|
gst_buffer_unmap (nal, &map);
|
|
}
|
|
|
|
if (outbuf) {
|
|
/* prepend codec_data */
|
|
if (rtph265depay->codec_data) {
|
|
GST_DEBUG_OBJECT (depayload, "prepending codec_data");
|
|
gst_rtp_copy_meta (GST_ELEMENT_CAST (rtph265depay),
|
|
rtph265depay->codec_data, outbuf,
|
|
g_quark_from_static_string (GST_META_TAG_VIDEO_STR));
|
|
outbuf = gst_buffer_append (rtph265depay->codec_data, outbuf);
|
|
rtph265depay->codec_data = NULL;
|
|
out_keyframe = TRUE;
|
|
}
|
|
outbuf = gst_buffer_make_writable (outbuf);
|
|
|
|
gst_buffer_foreach_meta (outbuf, foreach_metadata_drop, depayload);
|
|
|
|
GST_BUFFER_PTS (outbuf) = out_timestamp;
|
|
|
|
if (out_keyframe)
|
|
GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
else
|
|
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
}
|
|
|
|
return outbuf;
|
|
|
|
/* ERRORS */
|
|
short_nal:
|
|
{
|
|
GST_WARNING_OBJECT (depayload, "dropping short NAL");
|
|
gst_buffer_unmap (nal, &map);
|
|
gst_buffer_unref (nal);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_rtp_h265_push_fragmentation_unit (GstRtpH265Depay * rtph265depay,
|
|
gboolean send)
|
|
{
|
|
guint outsize;
|
|
GstMapInfo map;
|
|
GstBuffer *outbuf;
|
|
|
|
outsize = gst_adapter_available (rtph265depay->adapter);
|
|
outbuf = gst_adapter_take_buffer (rtph265depay->adapter, outsize);
|
|
|
|
gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
|
|
GST_DEBUG_OBJECT (rtph265depay, "output %d bytes", outsize);
|
|
|
|
if (rtph265depay->byte_stream) {
|
|
memcpy (map.data, sync_bytes, sizeof (sync_bytes));
|
|
} else {
|
|
goto not_implemented;
|
|
}
|
|
gst_buffer_unmap (outbuf, &map);
|
|
|
|
rtph265depay->current_fu_type = 0;
|
|
|
|
outbuf = gst_rtp_h265_depay_handle_nal (rtph265depay, outbuf,
|
|
rtph265depay->fu_timestamp, rtph265depay->fu_marker);
|
|
|
|
if (send && outbuf) {
|
|
gst_rtp_base_depayload_push (GST_RTP_BASE_DEPAYLOAD (rtph265depay), outbuf);
|
|
outbuf = NULL;
|
|
}
|
|
return outbuf;
|
|
|
|
not_implemented:
|
|
{
|
|
GST_ERROR_OBJECT (rtph265depay,
|
|
("Only bytestream format is currently supported."));
|
|
gst_buffer_unmap (outbuf, &map);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_rtp_h265_depay_process (GstRTPBaseDepayload * depayload, GstRTPBuffer * rtp)
|
|
{
|
|
GstRtpH265Depay *rtph265depay;
|
|
GstBuffer *buf;
|
|
GstBuffer *outbuf = NULL;
|
|
guint8 nal_unit_type;
|
|
|
|
rtph265depay = GST_RTP_H265_DEPAY (depayload);
|
|
|
|
/* flush remaining data on discont */
|
|
if (GST_BUFFER_IS_DISCONT (rtp->buffer)) {
|
|
gst_adapter_clear (rtph265depay->adapter);
|
|
rtph265depay->wait_start = TRUE;
|
|
rtph265depay->current_fu_type = 0;
|
|
}
|
|
|
|
{
|
|
gint payload_len;
|
|
guint8 *payload;
|
|
guint header_len;
|
|
GstMapInfo map;
|
|
guint outsize, nalu_size;
|
|
GstClockTime timestamp;
|
|
gboolean marker;
|
|
guint8 nuh_layer_id, nuh_temporal_id_plus1;
|
|
guint8 S, E;
|
|
guint16 nal_header;
|
|
#if 0
|
|
gboolean donl_present = FALSE;
|
|
#endif
|
|
|
|
timestamp = GST_BUFFER_PTS (rtp->buffer);
|
|
|
|
payload_len = gst_rtp_buffer_get_payload_len (rtp);
|
|
payload = gst_rtp_buffer_get_payload (rtp);
|
|
buf = gst_rtp_buffer_get_payload_buffer (rtp);
|
|
marker = gst_rtp_buffer_get_marker (rtp);
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay, "receiving %d bytes", payload_len);
|
|
|
|
if (payload_len == 0)
|
|
goto empty_packet;
|
|
|
|
/* +---------------+---------------+
|
|
* |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* |F| Type | LayerId | TID |
|
|
* +-------------+-----------------+
|
|
*
|
|
* F must be 0.
|
|
*
|
|
*/
|
|
nal_unit_type = (payload[0] >> 1) & 0x3f;
|
|
nuh_layer_id = ((payload[0] & 0x01) << 5) | (payload[1] >> 3); /* should be zero for now but this could change in future HEVC extensions */
|
|
nuh_temporal_id_plus1 = payload[1] & 0x03;
|
|
|
|
/* At least two byte header with type */
|
|
header_len = 2;
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay,
|
|
"NAL header nal_unit_type %d, nuh_temporal_id_plus1 %d", nal_unit_type,
|
|
nuh_temporal_id_plus1);
|
|
|
|
GST_FIXME_OBJECT (rtph265depay, "Assuming DONL field is not present");
|
|
|
|
/* FIXME - assuming DONL field is not present for now */
|
|
/*donl_present = (tx-mode == "MST") || (sprop-max-don-diff > 0); */
|
|
|
|
/* If FU unit was being processed, but the current nal is of a different
|
|
* type. Assume that the remote payloader is buggy (didn't set the end bit
|
|
* when the FU ended) and send out what we gathered thusfar */
|
|
if (G_UNLIKELY (rtph265depay->current_fu_type != 0 &&
|
|
nal_unit_type != rtph265depay->current_fu_type))
|
|
gst_rtp_h265_push_fragmentation_unit (rtph265depay, TRUE);
|
|
|
|
switch (nal_unit_type) {
|
|
case 48:
|
|
{
|
|
GST_DEBUG_OBJECT (rtph265depay, "Processing aggregation packet");
|
|
|
|
/* Aggregation packet (section 4.7) */
|
|
|
|
/* An example of an AP packet containing two aggregation units
|
|
without the DONL and DOND fields
|
|
|
|
0 1 2 3
|
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| RTP Header |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| PayloadHdr (Type=48) | NALU 1 Size |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| NALU 1 HDR | |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 1 Data |
|
|
| . . . |
|
|
| |
|
|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| . . . | NALU 2 Size | NALU 2 HDR |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| NALU 2 HDR | |
|
|
+-+-+-+-+-+-+-+-+ NALU 2 Data |
|
|
| . . . |
|
|
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| :...OPTIONAL RTP padding |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
*/
|
|
|
|
/* strip headers */
|
|
payload += header_len;
|
|
payload_len -= header_len;
|
|
|
|
rtph265depay->wait_start = FALSE;
|
|
|
|
#if 0
|
|
if (donl_present)
|
|
goto not_implemented_donl_present;
|
|
#endif
|
|
|
|
while (payload_len > 2) {
|
|
|
|
nalu_size = (payload[0] << 8) | payload[1];
|
|
|
|
/* dont include nalu_size */
|
|
if (nalu_size > (payload_len - 2))
|
|
nalu_size = payload_len - 2;
|
|
|
|
outsize = nalu_size + sizeof (sync_bytes);
|
|
outbuf = gst_buffer_new_and_alloc (outsize);
|
|
|
|
gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
|
|
if (rtph265depay->byte_stream) {
|
|
memcpy (map.data, sync_bytes, sizeof (sync_bytes));
|
|
} else {
|
|
goto not_implemented;
|
|
}
|
|
|
|
/* strip NALU size */
|
|
payload += 2;
|
|
payload_len -= 2;
|
|
|
|
memcpy (map.data + sizeof (sync_bytes), payload, nalu_size);
|
|
gst_buffer_unmap (outbuf, &map);
|
|
|
|
gst_rtp_copy_meta (GST_ELEMENT_CAST (rtph265depay), outbuf, buf,
|
|
g_quark_from_static_string (GST_META_TAG_VIDEO_STR));
|
|
|
|
gst_adapter_push (rtph265depay->adapter, outbuf);
|
|
|
|
payload += nalu_size;
|
|
payload_len -= nalu_size;
|
|
}
|
|
|
|
outsize = gst_adapter_available (rtph265depay->adapter);
|
|
if (outsize > 0) {
|
|
outbuf = gst_adapter_take_buffer (rtph265depay->adapter, outsize);
|
|
outbuf =
|
|
gst_rtp_h265_depay_handle_nal (rtph265depay, outbuf, timestamp,
|
|
marker);
|
|
}
|
|
break;
|
|
}
|
|
case 49:
|
|
{
|
|
GST_DEBUG_OBJECT (rtph265depay, "Processing Fragmentation Unit");
|
|
|
|
/* Fragmentation units (FUs) Section 4.8 */
|
|
|
|
/* The structure of a Fragmentation Unit (FU)
|
|
*
|
|
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| PayloadHdr (Type=49) | FU header | DONL (cond) |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
|
|
| DONL (cond) | |
|
|
|-+-+-+-+-+-+-+-+ |
|
|
| FU payload |
|
|
| |
|
|
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| :...OPTIONAL RTP padding |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
*
|
|
*
|
|
*/
|
|
|
|
/* strip headers */
|
|
payload += header_len;
|
|
payload_len -= header_len;
|
|
|
|
/* processing FU header */
|
|
S = (payload[0] & 0x80) == 0x80;
|
|
E = (payload[0] & 0x40) == 0x40;
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay,
|
|
"FU header with S %d, E %d, nal_unit_type %d", S, E,
|
|
payload[0] & 0x3f);
|
|
|
|
if (rtph265depay->wait_start && !S)
|
|
goto waiting_start;
|
|
|
|
#if 0
|
|
if (donl_present)
|
|
goto not_implemented_donl_present;
|
|
#endif
|
|
|
|
if (S) {
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay, "Start of Fragmentation Unit");
|
|
|
|
/* If a new FU unit started, while still processing an older one.
|
|
* Assume that the remote payloader is buggy (doesn't set the end
|
|
* bit) and send out what we've gathered thusfar */
|
|
if (G_UNLIKELY (rtph265depay->current_fu_type != 0))
|
|
gst_rtp_h265_push_fragmentation_unit (rtph265depay, TRUE);
|
|
|
|
rtph265depay->current_fu_type = nal_unit_type;
|
|
rtph265depay->fu_timestamp = timestamp;
|
|
|
|
rtph265depay->wait_start = FALSE;
|
|
|
|
/* reconstruct NAL header */
|
|
nal_header =
|
|
((payload[0] & 0x3f) << 9) | (nuh_layer_id << 3) |
|
|
nuh_temporal_id_plus1;
|
|
|
|
/* go back one byte so we can copy the payload + two bytes more in the front which
|
|
* will be overwritten by the nal_header
|
|
*/
|
|
payload -= 1;
|
|
payload_len += 1;
|
|
|
|
nalu_size = payload_len;
|
|
outsize = nalu_size + sizeof (sync_bytes);
|
|
outbuf = gst_buffer_new_and_alloc (outsize);
|
|
|
|
gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
|
|
memcpy (map.data + sizeof (sync_bytes), payload, nalu_size);
|
|
map.data[sizeof (sync_bytes)] = nal_header >> 8;
|
|
map.data[sizeof (sync_bytes) + 1] = nal_header & 0xff;
|
|
gst_buffer_unmap (outbuf, &map);
|
|
|
|
gst_rtp_copy_meta (GST_ELEMENT_CAST (rtph265depay), outbuf, buf,
|
|
g_quark_from_static_string (GST_META_TAG_VIDEO_STR));
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay, "queueing %d bytes", outsize);
|
|
|
|
/* and assemble in the adapter */
|
|
gst_adapter_push (rtph265depay->adapter, outbuf);
|
|
} else {
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay,
|
|
"Following part of Fragmentation Unit");
|
|
|
|
/* strip off FU header byte */
|
|
payload += 1;
|
|
payload_len -= 1;
|
|
|
|
outsize = payload_len;
|
|
outbuf = gst_buffer_new_and_alloc (outsize);
|
|
gst_buffer_fill (outbuf, 0, payload, outsize);
|
|
|
|
gst_rtp_copy_meta (GST_ELEMENT_CAST (rtph265depay), outbuf, buf,
|
|
g_quark_from_static_string (GST_META_TAG_VIDEO_STR));
|
|
|
|
GST_DEBUG_OBJECT (rtph265depay, "queueing %d bytes", outsize);
|
|
|
|
/* and assemble in the adapter */
|
|
gst_adapter_push (rtph265depay->adapter, outbuf);
|
|
}
|
|
|
|
outbuf = NULL;
|
|
rtph265depay->fu_marker = marker;
|
|
|
|
/* if NAL unit ends, flush the adapter */
|
|
if (E) {
|
|
outbuf = gst_rtp_h265_push_fragmentation_unit (rtph265depay, FALSE);
|
|
GST_DEBUG_OBJECT (rtph265depay, "End of Fragmentation Unit");
|
|
}
|
|
break;
|
|
}
|
|
case 50:
|
|
goto not_implemented; /* PACI packets Section 4.9 */
|
|
default:
|
|
{
|
|
rtph265depay->wait_start = FALSE;
|
|
|
|
/* All other cases: Single NAL unit packet Section 4.6 */
|
|
/* the entire payload is the output buffer */
|
|
|
|
#if 0
|
|
if (donl_present)
|
|
goto not_implemented_donl_present;
|
|
#endif
|
|
|
|
nalu_size = payload_len;
|
|
outsize = nalu_size + sizeof (sync_bytes);
|
|
outbuf = gst_buffer_new_and_alloc (outsize);
|
|
|
|
gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
|
|
if (rtph265depay->byte_stream) {
|
|
memcpy (map.data, sync_bytes, sizeof (sync_bytes));
|
|
} else {
|
|
goto not_implemented;
|
|
}
|
|
memcpy (map.data + sizeof (sync_bytes), payload, nalu_size);
|
|
gst_buffer_unmap (outbuf, &map);
|
|
|
|
gst_rtp_copy_meta (GST_ELEMENT_CAST (rtph265depay), outbuf, buf,
|
|
g_quark_from_static_string (GST_META_TAG_VIDEO_STR));
|
|
|
|
outbuf = gst_rtp_h265_depay_handle_nal (rtph265depay, outbuf, timestamp,
|
|
marker);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gst_buffer_unref (buf);
|
|
|
|
return outbuf;
|
|
|
|
/* ERRORS */
|
|
empty_packet:
|
|
{
|
|
GST_DEBUG_OBJECT (rtph265depay, "empty packet");
|
|
gst_buffer_unref (buf);
|
|
return NULL;
|
|
}
|
|
waiting_start:
|
|
{
|
|
GST_DEBUG_OBJECT (rtph265depay, "waiting for start");
|
|
gst_buffer_unref (buf);
|
|
return NULL;
|
|
}
|
|
#if 0
|
|
not_implemented_donl_present:
|
|
{
|
|
GST_ELEMENT_ERROR (rtph265depay, STREAM, FORMAT,
|
|
(NULL), ("DONL field present not supported yet"));
|
|
gst_buffer_unref (buf);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
not_implemented:
|
|
{
|
|
GST_ELEMENT_ERROR (rtph265depay, STREAM, FORMAT,
|
|
(NULL), ("NAL unit type %d not supported yet", nal_unit_type));
|
|
gst_buffer_unref (buf);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_rtp_h265_depay_handle_event (GstRTPBaseDepayload * depay, GstEvent * event)
|
|
{
|
|
GstRtpH265Depay *rtph265depay;
|
|
|
|
rtph265depay = GST_RTP_H265_DEPAY (depay);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_FLUSH_STOP:
|
|
gst_rtp_h265_depay_reset (rtph265depay);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return
|
|
GST_RTP_BASE_DEPAYLOAD_CLASS (parent_class)->handle_event (depay, event);
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_rtp_h265_depay_change_state (GstElement * element,
|
|
GstStateChange transition)
|
|
{
|
|
GstRtpH265Depay *rtph265depay;
|
|
GstStateChangeReturn ret;
|
|
|
|
rtph265depay = GST_RTP_H265_DEPAY (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
gst_rtp_h265_depay_reset (rtph265depay);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gst_rtp_h265_depay_plugin_init (GstPlugin * plugin)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (rtph265depay_debug, "rtph265depay", 0,
|
|
"H265 Video RTP Depayloader");
|
|
|
|
return gst_element_register (plugin, "rtph265depay",
|
|
GST_RANK_SECONDARY, GST_TYPE_RTP_H265_DEPAY);
|
|
}
|