mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-22 14:06:23 +00:00
1652 lines
50 KiB
C
1652 lines
50 KiB
C
/* GStreamer
|
|
* Copyright (C) 2023 He Junyan <junyan.he@intel.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.
|
|
*/
|
|
/**
|
|
* SECTION:gsth266decoder
|
|
* @title: GstH266Decoder
|
|
* @short_description: Base class to implement stateless H.266 decoders
|
|
* @sources:
|
|
* - gsth266picture.h
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <gst/base/base.h>
|
|
#include "gsth266decoder.h"
|
|
|
|
GST_DEBUG_CATEGORY (gst_h266_decoder_debug);
|
|
#define GST_CAT_DEFAULT gst_h266_decoder_debug
|
|
|
|
typedef enum
|
|
{
|
|
GST_H266_DECODER_FORMAT_NONE,
|
|
GST_H266_DECODER_FORMAT_VVC1,
|
|
GST_H266_DECODER_FORMAT_VVI1,
|
|
GST_H266_DECODER_FORMAT_BYTE
|
|
} GstH266DecoderFormat;
|
|
|
|
typedef enum
|
|
{
|
|
GST_H266_DECODER_ALIGN_NONE,
|
|
GST_H266_DECODER_ALIGN_NAL,
|
|
GST_H266_DECODER_ALIGN_AU
|
|
} GstH266DecoderAlign;
|
|
|
|
struct _GstH266DecoderPrivate
|
|
{
|
|
/* state */
|
|
gint max_width, max_height;
|
|
guint8 conformance_window_flag;
|
|
gint crop_rect_width;
|
|
gint crop_rect_height;
|
|
gint crop_rect_x;
|
|
gint crop_rect_y;
|
|
|
|
GstH266DecoderFormat in_format;
|
|
GstH266DecoderAlign align;
|
|
guint nal_length_size;
|
|
|
|
GstH266Parser *parser;
|
|
GstH266Dpb *dpb;
|
|
|
|
/* 0: frame or field-pair interlaced stream
|
|
* 1: alternating, single field interlaced stream.
|
|
* When equal to 1, picture timing SEI shall be present in every AU */
|
|
guint8 field_seq_flag;
|
|
guint8 progressive_source_flag;
|
|
guint8 interlaced_source_flag;
|
|
|
|
/* Picture currently being processed/decoded */
|
|
GstH266Picture *current_picture;
|
|
GstVideoCodecFrame *current_frame;
|
|
|
|
GstH266Slice current_slice;
|
|
|
|
gboolean new_bitstream_or_got_eos;
|
|
gboolean no_output_before_recovery_flag;
|
|
gint gdr_recovery_point_poc;
|
|
gboolean no_output_of_prior_pics_flag;
|
|
gint prev_tid0_pic;
|
|
/* PicOrderCount of the previously outputted frame */
|
|
gint last_output_poc;
|
|
guint32 SpsMaxLatencyPictures;
|
|
|
|
GstH266FrameFieldInfo ff_info;
|
|
|
|
GArray *slices;
|
|
|
|
gboolean aps_added[GST_H266_APS_TYPE_MAX][8];
|
|
|
|
/* For delayed output */
|
|
guint preferred_output_delay;
|
|
gboolean is_live;
|
|
GstQueueArray *output_queue;
|
|
|
|
gboolean input_state_changed;
|
|
|
|
GstFlowReturn last_flow;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
/* Holds ref */
|
|
GstVideoCodecFrame *frame;
|
|
GstH266Picture *picture;
|
|
/* Without ref */
|
|
GstH266Decoder *self;
|
|
} GstH266DecoderOutputFrame;
|
|
|
|
#define UPDATE_FLOW_RETURN(ret,new_ret) G_STMT_START { \
|
|
if (*(ret) == GST_FLOW_OK) \
|
|
*(ret) = new_ret; \
|
|
} G_STMT_END
|
|
|
|
#define parent_class gst_h266_decoder_parent_class
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstH266Decoder, gst_h266_decoder,
|
|
GST_TYPE_VIDEO_DECODER,
|
|
G_ADD_PRIVATE (GstH266Decoder);
|
|
GST_DEBUG_CATEGORY_INIT (gst_h266_decoder_debug, "h266decoder", 0,
|
|
"H.266 Video Decoder"));
|
|
|
|
typedef struct
|
|
{
|
|
const gchar *level_name;
|
|
guint8 level_idc;
|
|
guint32 MaxLumaPs;
|
|
} GstH266LevelLimits;
|
|
|
|
/* *INDENT-OFF* */
|
|
/* Table A.2 - General tier and level limits */
|
|
static const GstH266LevelLimits level_limits[] = {
|
|
/* level idc MaxLumaPs */
|
|
{ "1.0", GST_H266_LEVEL_L1_0, 36864 },
|
|
{ "2.0", GST_H266_LEVEL_L2_0, 122880 },
|
|
{ "2.1", GST_H266_LEVEL_L2_1, 245760 },
|
|
{ "3.0", GST_H266_LEVEL_L3_0, 552960 },
|
|
{ "3.1", GST_H266_LEVEL_L3_1, 983040 },
|
|
{ "4.0", GST_H266_LEVEL_L4_0, 2228224 },
|
|
{ "4.1", GST_H266_LEVEL_L4_1, 2228224 },
|
|
{ "5.0", GST_H266_LEVEL_L5_0, 8912896 },
|
|
{ "5.1", GST_H266_LEVEL_L5_1, 8912896 },
|
|
{ "5.2", GST_H266_LEVEL_L5_2, 8912896 },
|
|
{ "6.0", GST_H266_LEVEL_L6_0, 35651584 },
|
|
{ "6.1", GST_H266_LEVEL_L6_1, 35651584 },
|
|
{ "6.2", GST_H266_LEVEL_L6_2, 35651584 },
|
|
{ "6.3", GST_H266_LEVEL_L6_3, 80216064 },
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
static gboolean
|
|
gst_h266_decoder_start (GstVideoDecoder * decoder)
|
|
{
|
|
GstH266Decoder *self = GST_H266_DECODER (decoder);
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
|
|
priv->parser = gst_h266_parser_new ();
|
|
priv->dpb = gst_h266_dpb_new ();
|
|
priv->new_bitstream_or_got_eos = TRUE;
|
|
priv->last_flow = GST_FLOW_OK;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_init_refs (GstH266Decoder * self)
|
|
{
|
|
guint i, j;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
self->NumRefIdxActive[i] = 0;
|
|
|
|
for (j = 0; j < GST_H266_MAX_REF_ENTRIES; j++) {
|
|
self->RefPicList[i][j] = NULL;
|
|
self->RefPicPocList[i][j] = G_MININT32;
|
|
self->RefPicLtPocList[i][j] = G_MININT32;
|
|
self->inter_layer_ref[i][j] = FALSE;
|
|
self->RefPicScale[i][j][0] = 0;
|
|
self->RefPicScale[i][j][1] = 0;
|
|
self->RprConstraintsActiveFlag[i][j] = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_h266_decoder_stop (GstVideoDecoder * decoder)
|
|
{
|
|
GstH266Decoder *self = GST_H266_DECODER (decoder);
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
|
|
if (self->input_state) {
|
|
gst_video_codec_state_unref (self->input_state);
|
|
self->input_state = NULL;
|
|
}
|
|
|
|
if (priv->parser) {
|
|
gst_h266_parser_free (priv->parser);
|
|
priv->parser = NULL;
|
|
}
|
|
|
|
if (priv->dpb) {
|
|
gst_h266_dpb_free (priv->dpb);
|
|
priv->dpb = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_format_from_caps (GstH266Decoder * self, GstCaps * caps,
|
|
GstH266DecoderFormat * format, GstH266DecoderAlign * align)
|
|
{
|
|
if (format)
|
|
*format = GST_H266_DECODER_FORMAT_NONE;
|
|
|
|
if (align)
|
|
*align = GST_H266_DECODER_ALIGN_NONE;
|
|
|
|
if (!gst_caps_is_fixed (caps)) {
|
|
GST_WARNING_OBJECT (self, "Caps wasn't fixed");
|
|
return;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "parsing caps: %" GST_PTR_FORMAT, caps);
|
|
|
|
if (caps && gst_caps_get_size (caps) > 0) {
|
|
GstStructure *s = gst_caps_get_structure (caps, 0);
|
|
const gchar *str = NULL;
|
|
|
|
if (format) {
|
|
if ((str = gst_structure_get_string (s, "stream-format"))) {
|
|
if (strcmp (str, "vvc1") == 0)
|
|
*format = GST_H266_DECODER_FORMAT_VVC1;
|
|
else if (strcmp (str, "vvi1") == 0)
|
|
*format = GST_H266_DECODER_FORMAT_VVI1;
|
|
else if (strcmp (str, "byte-stream") == 0)
|
|
*format = GST_H266_DECODER_FORMAT_BYTE;
|
|
}
|
|
}
|
|
|
|
if (align) {
|
|
if ((str = gst_structure_get_string (s, "alignment"))) {
|
|
if (strcmp (str, "au") == 0)
|
|
*align = GST_H266_DECODER_ALIGN_AU;
|
|
else if (strcmp (str, "nal") == 0)
|
|
*align = GST_H266_DECODER_ALIGN_NAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_h266_decoder_set_format (GstVideoDecoder * decoder,
|
|
GstVideoCodecState * state)
|
|
{
|
|
GstH266Decoder *self = GST_H266_DECODER (decoder);
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstQuery *query;
|
|
|
|
GST_DEBUG_OBJECT (decoder, "Set format");
|
|
|
|
priv->input_state_changed = TRUE;
|
|
|
|
if (self->input_state)
|
|
gst_video_codec_state_unref (self->input_state);
|
|
|
|
self->input_state = gst_video_codec_state_ref (state);
|
|
|
|
priv->is_live = FALSE;
|
|
query = gst_query_new_latency ();
|
|
if (gst_pad_peer_query (GST_VIDEO_DECODER_SINK_PAD (self), query))
|
|
gst_query_parse_latency (query, &priv->is_live, NULL, NULL);
|
|
gst_query_unref (query);
|
|
|
|
if (state->caps) {
|
|
GstH266DecoderFormat format;
|
|
GstH266DecoderAlign align;
|
|
|
|
gst_h266_decoder_format_from_caps (self, state->caps, &format, &align);
|
|
|
|
if (format == GST_H266_DECODER_FORMAT_NONE) {
|
|
/* codec_data implies packetized */
|
|
if (state->codec_data) {
|
|
GST_WARNING_OBJECT (self,
|
|
"video/x-h266 caps with codec_data but no stream-format=vvi1 or vvc1");
|
|
format = GST_H266_DECODER_FORMAT_VVC1;
|
|
} else {
|
|
/* otherwise assume bytestream input */
|
|
GST_WARNING_OBJECT (self,
|
|
"video/x-h266 caps without codec_data or stream-format");
|
|
format = GST_H266_DECODER_FORMAT_BYTE;
|
|
}
|
|
}
|
|
|
|
if (format == GST_H266_DECODER_FORMAT_VVC1 ||
|
|
format == GST_H266_DECODER_FORMAT_VVI1) {
|
|
if (!state->codec_data) {
|
|
/* Try it with size 4 anyway */
|
|
priv->nal_length_size = 4;
|
|
GST_WARNING_OBJECT (self,
|
|
"packetized format without codec data, assuming nal length size is 4");
|
|
}
|
|
|
|
/* VVC1 implies alignment=au */
|
|
if (align == GST_H266_DECODER_ALIGN_NONE)
|
|
align = GST_H266_DECODER_ALIGN_AU;
|
|
}
|
|
|
|
if (format == GST_H266_DECODER_FORMAT_BYTE && state->codec_data)
|
|
GST_WARNING_OBJECT (self, "bytestream with codec data");
|
|
|
|
priv->in_format = format;
|
|
priv->align = align;
|
|
}
|
|
|
|
if (state->codec_data) {
|
|
/* TODO: */
|
|
GST_WARNING_OBJECT (self, "vvc1 or vvi1 mode is not supported now.");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_h266_decoder_negotiate (GstVideoDecoder * decoder)
|
|
{
|
|
GstH266Decoder *self = GST_H266_DECODER (decoder);
|
|
|
|
/* output state must be updated by subclass using new input state already */
|
|
self->priv->input_state_changed = FALSE;
|
|
|
|
return GST_VIDEO_DECODER_CLASS (parent_class)->negotiate (decoder);
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_set_latency (GstH266Decoder * self, const GstH266SPS * sps,
|
|
gint max_dpb_size)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstCaps *caps;
|
|
GstClockTime min, max;
|
|
GstStructure *structure;
|
|
gint fps_d = 1, fps_n = 0;
|
|
guint frames_delay;
|
|
|
|
caps = gst_pad_get_current_caps (GST_VIDEO_DECODER_SRC_PAD (self));
|
|
if (!caps && self->input_state)
|
|
caps = gst_caps_ref (self->input_state->caps);
|
|
|
|
if (caps) {
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)) {
|
|
if (fps_n == 0) {
|
|
/* variable framerate: see if we have a max-framerate */
|
|
gst_structure_get_fraction (structure, "max-framerate", &fps_n, &fps_d);
|
|
}
|
|
}
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
/* if no fps or variable, then 25/1 */
|
|
if (fps_n == 0) {
|
|
fps_n = 25;
|
|
fps_d = 1;
|
|
}
|
|
|
|
/* Minimum possible latency could be calculated based on C.5.2.3
|
|
* 1) # of pictures (marked as "needed for output") in DPB > sps_max_num_reorder_pics
|
|
* - We will assume all pictures in DPB are marked as "needed for output"
|
|
* 2) sps_max_latency_increase_plus1 != 0 and
|
|
* PicLatencyCount >= SpsMaxLatencyPictures
|
|
* - SpsMaxLatencyPictures is equal to
|
|
* "sps_max_num_reorder_pics + sps_max_latency_increase_plus1 - 1"
|
|
* and PicLatencyCount of each picture in DPB is increased by 1 per
|
|
* decoding loop. Note that PicLatencyCount of the currently decoded
|
|
* picture is zero. So, in case that all pictures in DPB are marked as
|
|
* "needed for output", Only condition 1) will have an effect
|
|
* regardless of sps_max_latency_increase_plus1.
|
|
*
|
|
* For example, assume sps_max_num_reorder_pics is 2 and
|
|
* sps_max_latency_increase_plus1 is 1, then SpsMaxLatencyPictures is 2.
|
|
* For a picture in DPB to have PicLatencyCount >= SpsMaxLatencyPictures,
|
|
* there must be at least 3 pictures including current picture in DPB
|
|
* (current picture's PicLatencyCount is zero).
|
|
* This is already covered by the condition 1). So, this condition 2)
|
|
* will have effect only when there are pictures marked as
|
|
* "not needed for output" in DPB.
|
|
*
|
|
* Thus, we can take sps_max_num_reorder_pics as a min latency value
|
|
*/
|
|
frames_delay = sps->dpb.max_num_reorder_pics[sps->max_sublayers_minus1];
|
|
|
|
/* Consider output delay wanted by subclass */
|
|
frames_delay += priv->preferred_output_delay;
|
|
|
|
min = gst_util_uint64_scale_int (frames_delay * GST_SECOND, fps_d, fps_n);
|
|
max = gst_util_uint64_scale_int ((max_dpb_size + priv->preferred_output_delay)
|
|
* GST_SECOND, fps_d, fps_n);
|
|
|
|
GST_DEBUG_OBJECT (self,
|
|
"latency min %" GST_TIME_FORMAT " max %" GST_TIME_FORMAT
|
|
" min-frames-delay %d", GST_TIME_ARGS (min), GST_TIME_ARGS (max),
|
|
frames_delay);
|
|
|
|
gst_video_decoder_set_latency (GST_VIDEO_DECODER (self), min, max);
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_reset_frame_state (GstH266Decoder * self)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
gint i;
|
|
|
|
/* Clear picture struct information */
|
|
memset (&priv->ff_info, 0, sizeof (GstH266FrameFieldInfo));
|
|
priv->ff_info.source_scan_type = 2;
|
|
|
|
priv->current_frame = NULL;
|
|
|
|
g_array_set_size (priv->slices, 0);
|
|
|
|
for (i = 0; i < GST_H266_APS_TYPE_MAX; i++)
|
|
g_array_set_size (self->aps_list[i], 0);
|
|
memset (priv->aps_added, 0, sizeof (priv->aps_added));
|
|
|
|
gst_h266_decoder_init_refs (self);
|
|
}
|
|
|
|
static GstH266ParserResult
|
|
gst_h266_decoder_parse_sei (GstH266Decoder * self, GstH266NalUnit * nalu)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstH266ParserResult pres;
|
|
GArray *messages = NULL;
|
|
|
|
pres = gst_h266_parser_parse_sei (priv->parser, nalu, &messages);
|
|
if (pres != GST_H266_PARSER_OK) {
|
|
GST_WARNING_OBJECT (self, "Failed to parse SEI, result %d", pres);
|
|
|
|
/* XXX: Ignore error from SEI parsing, it might be malformed bitstream,
|
|
* or our fault. But shouldn't be critical */
|
|
g_clear_pointer (&messages, g_array_unref);
|
|
return GST_H266_PARSER_OK;
|
|
}
|
|
|
|
/* TODO: */
|
|
|
|
return GST_H266_PARSER_OK;
|
|
}
|
|
|
|
static GstH266ParserResult
|
|
gst_h266_decoder_parse_slice (GstH266Decoder * self, GstH266NalUnit * nalu)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstH266ParserResult pres;
|
|
GstH266Slice slice;
|
|
|
|
memset (&slice, 0, sizeof (GstH266Slice));
|
|
|
|
pres = gst_h266_parser_parse_slice_hdr (priv->parser, nalu, &slice.header);
|
|
if (pres != GST_H266_PARSER_OK)
|
|
return pres;
|
|
|
|
slice.nalu = *nalu;
|
|
|
|
if (slice.header.picture_header_in_slice_header_flag) {
|
|
slice.first_slice = TRUE;
|
|
|
|
if (priv->slices->len > 0) {
|
|
GST_WARNING_OBJECT (self,
|
|
"A problematic stream has internal PH for multi slices.");
|
|
slice.first_slice = FALSE;
|
|
}
|
|
} else if (priv->slices->len == 0) {
|
|
slice.first_slice = TRUE;
|
|
}
|
|
|
|
/* C.3.2 */
|
|
slice.no_output_of_prior_pics_flag =
|
|
slice.header.no_output_of_prior_pics_flag;
|
|
|
|
if (slice.first_slice) {
|
|
/* 8.1.1 */
|
|
if (GST_H266_IS_NAL_TYPE_IDR (slice.nalu.type)) {
|
|
priv->no_output_before_recovery_flag = FALSE;
|
|
} else if (GST_H266_IS_NAL_TYPE_CRA (slice.nalu.type) ||
|
|
GST_H266_IS_NAL_TYPE_GDR (slice.nalu.type)) {
|
|
priv->no_output_before_recovery_flag = priv->new_bitstream_or_got_eos;
|
|
}
|
|
|
|
priv->no_output_of_prior_pics_flag = slice.no_output_of_prior_pics_flag;
|
|
} else {
|
|
if (priv->no_output_of_prior_pics_flag !=
|
|
slice.no_output_of_prior_pics_flag)
|
|
GST_WARNING_OBJECT (self, "A problematic stream has different "
|
|
"no_output_of_prior_pics_flag within one AU.");
|
|
priv->no_output_of_prior_pics_flag |= slice.no_output_of_prior_pics_flag;
|
|
}
|
|
|
|
if (GST_H266_IS_NAL_TYPE_IRAP (slice.nalu.type) &&
|
|
!priv->new_bitstream_or_got_eos)
|
|
slice.clear_dpb = TRUE;
|
|
|
|
slice.no_output_before_recovery_flag = priv->no_output_before_recovery_flag;
|
|
|
|
priv->new_bitstream_or_got_eos = FALSE;
|
|
g_array_append_val (priv->slices, slice);
|
|
|
|
return GST_H266_PARSER_OK;
|
|
}
|
|
|
|
static GstH266ParserResult
|
|
gst_h266_decoder_parse_nalu (GstH266Decoder * self, GstH266NalUnit * nalu)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstH266VPS vps;
|
|
GstH266SPS sps;
|
|
GstH266PPS pps;
|
|
GstH266APS aps;
|
|
GstH266PicHdr ph;
|
|
GstH266ParserResult ret = GST_H266_PARSER_OK;
|
|
|
|
GST_LOG_OBJECT (self, "Parsed nal type: %d, offset %d, size %d",
|
|
nalu->type, nalu->offset, nalu->size);
|
|
|
|
switch (nalu->type) {
|
|
case GST_H266_NAL_VPS:
|
|
ret = gst_h266_parser_parse_vps (priv->parser, nalu, &vps);
|
|
break;
|
|
case GST_H266_NAL_SPS:
|
|
ret = gst_h266_parser_parse_sps (priv->parser, nalu, &sps);
|
|
break;
|
|
case GST_H266_NAL_PPS:
|
|
ret = gst_h266_parser_parse_pps (priv->parser, nalu, &pps);
|
|
break;
|
|
case GST_H266_NAL_PH:
|
|
ret = gst_h266_parser_parse_picture_hdr (priv->parser, nalu, &ph);
|
|
break;
|
|
case GST_H266_NAL_PREFIX_SEI:
|
|
case GST_H266_NAL_SUFFIX_SEI:
|
|
ret = gst_h266_decoder_parse_sei (self, nalu);
|
|
break;
|
|
case GST_H266_NAL_PREFIX_APS:
|
|
case GST_H266_NAL_SUFFIX_APS:
|
|
ret = gst_h266_parser_parse_aps (priv->parser, nalu, &aps);
|
|
break;
|
|
case GST_H266_NAL_SLICE_TRAIL:
|
|
case GST_H266_NAL_SLICE_STSA:
|
|
case GST_H266_NAL_SLICE_RADL:
|
|
case GST_H266_NAL_SLICE_RASL:
|
|
case GST_H266_NAL_SLICE_IDR_W_RADL:
|
|
case GST_H266_NAL_SLICE_IDR_N_LP:
|
|
case GST_H266_NAL_SLICE_CRA:
|
|
case GST_H266_NAL_SLICE_GDR:
|
|
ret = gst_h266_decoder_parse_slice (self, nalu);
|
|
break;
|
|
case GST_H266_NAL_EOB:
|
|
case GST_H266_NAL_EOS:
|
|
/* TODO: drain the DPB */
|
|
priv->new_bitstream_or_got_eos = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_preprocess_slice (GstH266Decoder * self, GstH266Slice * slice)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
|
|
if (priv->current_picture && slice->first_slice) {
|
|
GST_WARNING_OBJECT (self, "Current picture is not finished but slice "
|
|
"header has first_slice_segment_in_pic_flag");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static gint
|
|
gst_h266_decoder_get_max_dpb_size_from_sps (GstH266Decoder * self,
|
|
GstH266SPS * sps)
|
|
{
|
|
guint i;
|
|
guint PicSizeMaxInSamplesY;
|
|
/* Default is the worst case level 6.2 */
|
|
guint32 MaxLumaPs = G_MAXUINT32;
|
|
const gint maxDpbPicBuf = 8;
|
|
gint MaxDpbSize;
|
|
|
|
/* Unknown level */
|
|
if (sps->profile_tier_level.level_idc == 0)
|
|
return GST_H266_MAX_DPB_SIZE;
|
|
|
|
PicSizeMaxInSamplesY =
|
|
sps->pic_width_max_in_luma_samples * sps->pic_height_max_in_luma_samples;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (level_limits); i++) {
|
|
if (sps->profile_tier_level.level_idc <= level_limits[i].level_idc) {
|
|
if (PicSizeMaxInSamplesY <= level_limits[i].MaxLumaPs) {
|
|
MaxLumaPs = level_limits[i].MaxLumaPs;
|
|
} else {
|
|
GST_DEBUG_OBJECT (self,
|
|
"%u (%dx%d) exceeds allowed max luma sample for level \"%s\" %u",
|
|
PicSizeMaxInSamplesY, sps->pic_width_max_in_luma_samples,
|
|
sps->pic_height_max_in_luma_samples, level_limits[i].level_name,
|
|
level_limits[i].MaxLumaPs);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Unknown level */
|
|
if (MaxLumaPs == G_MAXUINT32)
|
|
return GST_H266_MAX_DPB_SIZE;
|
|
|
|
/* A.4.2 */
|
|
if (2 * PicSizeMaxInSamplesY <= MaxLumaPs)
|
|
MaxDpbSize = 2 * maxDpbPicBuf;
|
|
else if (3 * PicSizeMaxInSamplesY <= 2 * MaxLumaPs)
|
|
MaxDpbSize = 3 * maxDpbPicBuf / 2;
|
|
else
|
|
MaxDpbSize = maxDpbPicBuf;
|
|
|
|
return MIN (MaxDpbSize, GST_H266_MAX_DPB_SIZE);
|
|
}
|
|
|
|
static gboolean
|
|
gst_h266_decoder_is_crop_rect_changed (GstH266Decoder * self, GstH266SPS * sps)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
|
|
if (priv->conformance_window_flag != sps->conformance_window_flag)
|
|
return TRUE;
|
|
if (priv->crop_rect_width != sps->crop_rect_width)
|
|
return TRUE;
|
|
if (priv->crop_rect_height != sps->crop_rect_height)
|
|
return TRUE;
|
|
if (priv->crop_rect_x != sps->crop_rect_x)
|
|
return TRUE;
|
|
if (priv->crop_rect_y != sps->crop_rect_y)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_drain_output_queue (GstH266Decoder * self, guint num,
|
|
GstFlowReturn * ret)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstH266DecoderClass *klass = GST_H266_DECODER_GET_CLASS (self);
|
|
|
|
g_assert (klass->output_picture);
|
|
g_assert (ret != NULL);
|
|
|
|
while (gst_queue_array_get_length (priv->output_queue) > num) {
|
|
GstH266DecoderOutputFrame *output_frame = (GstH266DecoderOutputFrame *)
|
|
gst_queue_array_pop_head_struct (priv->output_queue);
|
|
GstFlowReturn flow_ret = klass->output_picture (self, output_frame->frame,
|
|
output_frame->picture);
|
|
|
|
UPDATE_FLOW_RETURN (ret, flow_ret);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_clear_output_frame (GstH266DecoderOutputFrame * output_frame)
|
|
{
|
|
if (!output_frame)
|
|
return;
|
|
|
|
if (output_frame->frame) {
|
|
gst_video_decoder_release_frame (GST_VIDEO_DECODER (output_frame->self),
|
|
output_frame->frame);
|
|
output_frame->frame = NULL;
|
|
}
|
|
|
|
gst_clear_h266_picture (&output_frame->picture);
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_clear_dpb (GstH266Decoder * self, gboolean flush)
|
|
{
|
|
GstVideoDecoder *decoder = GST_VIDEO_DECODER (self);
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstH266Picture *picture;
|
|
|
|
/* If we are not flushing now, videodecoder baseclass will hold
|
|
* GstVideoCodecFrame. Release frames manually */
|
|
if (!flush) {
|
|
while ((picture = gst_h266_dpb_bump (priv->dpb, TRUE)) != NULL) {
|
|
GstVideoCodecFrame *frame = gst_video_decoder_get_frame (decoder,
|
|
GST_CODEC_PICTURE_FRAME_NUMBER (picture));
|
|
|
|
if (frame)
|
|
gst_video_decoder_release_frame (decoder, frame);
|
|
gst_h266_picture_unref (picture);
|
|
}
|
|
}
|
|
|
|
gst_queue_array_clear (priv->output_queue);
|
|
gst_h266_dpb_clear (priv->dpb);
|
|
priv->last_output_poc = G_MININT32;
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_do_output_picture (GstH266Decoder * self,
|
|
GstH266Picture * picture, GstFlowReturn * ret)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstVideoCodecFrame *frame = NULL;
|
|
GstH266DecoderOutputFrame output_frame;
|
|
|
|
g_assert (ret != NULL);
|
|
|
|
GST_LOG_OBJECT (self, "Output picture %p (poc %d)", picture,
|
|
picture->pic_order_cnt);
|
|
|
|
if (picture->pic_order_cnt < priv->last_output_poc) {
|
|
GST_WARNING_OBJECT (self,
|
|
"Outputting out of order %d -> %d, likely a broken stream",
|
|
priv->last_output_poc, picture->pic_order_cnt);
|
|
}
|
|
|
|
priv->last_output_poc = picture->pic_order_cnt;
|
|
|
|
frame = gst_video_decoder_get_frame (GST_VIDEO_DECODER (self),
|
|
GST_CODEC_PICTURE_FRAME_NUMBER (picture));
|
|
|
|
if (!frame) {
|
|
GST_ERROR_OBJECT (self,
|
|
"No available codec frame with frame number %d",
|
|
GST_CODEC_PICTURE_FRAME_NUMBER (picture));
|
|
UPDATE_FLOW_RETURN (ret, GST_FLOW_ERROR);
|
|
|
|
gst_h266_picture_unref (picture);
|
|
return;
|
|
}
|
|
|
|
output_frame.frame = frame;
|
|
output_frame.picture = picture;
|
|
output_frame.self = self;
|
|
gst_queue_array_push_tail_struct (priv->output_queue, &output_frame);
|
|
|
|
gst_h266_decoder_drain_output_queue (self, priv->preferred_output_delay,
|
|
&priv->last_flow);
|
|
}
|
|
|
|
static gboolean
|
|
gst_h266_decoder_flush (GstVideoDecoder * decoder)
|
|
{
|
|
GstH266Decoder *self = GST_H266_DECODER (decoder);
|
|
|
|
gst_h266_decoder_clear_dpb (self, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_drain_internal (GstH266Decoder * self)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstH266Picture *picture;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
while ((picture = gst_h266_dpb_bump (priv->dpb, TRUE)) != NULL)
|
|
gst_h266_decoder_do_output_picture (self, picture, &ret);
|
|
|
|
gst_h266_decoder_drain_output_queue (self, 0, &ret);
|
|
|
|
gst_h266_dpb_clear (priv->dpb);
|
|
priv->last_output_poc = G_MININT32;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_drain (GstVideoDecoder * decoder)
|
|
{
|
|
GstH266Decoder *self = GST_H266_DECODER (decoder);
|
|
|
|
/* dpb will be cleared by this method */
|
|
return gst_h266_decoder_drain_internal (self);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_finish (GstVideoDecoder * decoder)
|
|
{
|
|
return gst_h266_decoder_drain (decoder);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_process_sps (GstH266Decoder * self, GstH266SPS * sps)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
gint max_dpb_size, prev_max_dpb_size;
|
|
guint8 field_seq_flag;
|
|
guint8 progressive_source_flag = 0, interlaced_source_flag = 0;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
max_dpb_size = gst_h266_decoder_get_max_dpb_size_from_sps (self, sps);
|
|
prev_max_dpb_size = gst_h266_dpb_get_max_num_pics (priv->dpb);
|
|
|
|
field_seq_flag = sps->field_seq_flag;
|
|
if (sps->vui_parameters_present_flag) {
|
|
progressive_source_flag = sps->vui_params.progressive_source_flag;
|
|
interlaced_source_flag = sps->vui_params.interlaced_source_flag;
|
|
}
|
|
|
|
if (priv->max_width != sps->max_width ||
|
|
priv->max_height != sps->max_height ||
|
|
prev_max_dpb_size != max_dpb_size ||
|
|
priv->field_seq_flag != field_seq_flag ||
|
|
priv->progressive_source_flag != progressive_source_flag ||
|
|
priv->interlaced_source_flag != interlaced_source_flag ||
|
|
gst_h266_decoder_is_crop_rect_changed (self, sps)) {
|
|
GstH266DecoderClass *klass = GST_H266_DECODER_GET_CLASS (self);
|
|
|
|
GST_DEBUG_OBJECT (self,
|
|
"SPS updated, resolution: %dx%d -> %dx%d, dpb size: %d -> %d, "
|
|
"field_seq_flag: %d -> %d, progressive_source_flag: %d -> %d, "
|
|
"interlaced_source_flag: %d -> %d",
|
|
priv->max_width, priv->max_height, sps->max_width, sps->max_height,
|
|
prev_max_dpb_size, max_dpb_size, priv->field_seq_flag, field_seq_flag,
|
|
priv->progressive_source_flag, progressive_source_flag,
|
|
priv->interlaced_source_flag, interlaced_source_flag);
|
|
|
|
if (priv->no_output_of_prior_pics_flag) {
|
|
gst_h266_decoder_drain_output_queue (self, 0, &ret);
|
|
gst_h266_decoder_clear_dpb (self, FALSE);
|
|
} else {
|
|
ret = gst_h266_decoder_drain_internal (self);
|
|
}
|
|
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
|
|
if (klass->get_preferred_output_delay) {
|
|
priv->preferred_output_delay =
|
|
klass->get_preferred_output_delay (self, priv->is_live);
|
|
} else {
|
|
priv->preferred_output_delay = 0;
|
|
}
|
|
|
|
g_assert (klass->new_sequence);
|
|
ret = klass->new_sequence (self,
|
|
sps, max_dpb_size + priv->preferred_output_delay);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "subclass does not want accept new sequence");
|
|
return ret;
|
|
}
|
|
|
|
priv->max_width = sps->max_width;
|
|
priv->max_height = sps->max_height;
|
|
priv->conformance_window_flag = sps->conformance_window_flag;
|
|
priv->crop_rect_width = sps->crop_rect_width;
|
|
priv->crop_rect_height = sps->crop_rect_height;
|
|
priv->crop_rect_x = sps->crop_rect_x;
|
|
priv->crop_rect_y = sps->crop_rect_y;
|
|
priv->field_seq_flag = field_seq_flag;
|
|
priv->progressive_source_flag = progressive_source_flag;
|
|
priv->interlaced_source_flag = interlaced_source_flag;
|
|
|
|
gst_h266_dpb_set_max_num_pics (priv->dpb, max_dpb_size);
|
|
gst_h266_decoder_set_latency (self, sps, max_dpb_size);
|
|
|
|
GST_DEBUG_OBJECT (self, "Set DPB max size %d", max_dpb_size);
|
|
}
|
|
|
|
if (sps->dpb.max_latency_increase_plus1[sps->max_sublayers_minus1]) {
|
|
priv->SpsMaxLatencyPictures =
|
|
sps->dpb.max_num_reorder_pics[sps->max_sublayers_minus1] +
|
|
sps->dpb.max_latency_increase_plus1[sps->max_sublayers_minus1] - 1;
|
|
} else {
|
|
priv->SpsMaxLatencyPictures = 0;
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_calculate_poc (GstH266Decoder * self,
|
|
const GstH266Slice * slice, GstH266Picture * picture)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
const GstH266SPS *sps = priv->parser->active_sps;
|
|
gint32 max_poc_lsb = 1 << (sps->log2_max_pic_order_cnt_lsb_minus4 + 4);
|
|
gint32 prev_poc_lsb = priv->prev_tid0_pic % max_poc_lsb;
|
|
gint32 prev_poc_msb = priv->prev_tid0_pic - prev_poc_lsb;
|
|
gint32 poc_lsb = slice->header.picture_header.pic_order_cnt_lsb;
|
|
gint32 poc_msb;
|
|
|
|
/* 8.3.1 Decoding process for picture order count */
|
|
if (slice->header.picture_header.poc_msb_cycle_present_flag) {
|
|
poc_msb = slice->header.picture_header.poc_msb_cycle_val * max_poc_lsb;
|
|
} else if (GST_H266_IS_NAL_TYPE_CVSS (slice->nalu.type)
|
|
&& slice->no_output_before_recovery_flag) {
|
|
poc_msb = 0;
|
|
} else {
|
|
if (poc_lsb < prev_poc_lsb && prev_poc_lsb - poc_lsb >= max_poc_lsb / 2)
|
|
poc_msb = prev_poc_msb + max_poc_lsb;
|
|
else if (poc_lsb > prev_poc_lsb && poc_lsb - prev_poc_lsb > max_poc_lsb / 2)
|
|
poc_msb = prev_poc_msb - max_poc_lsb;
|
|
else
|
|
poc_msb = prev_poc_msb;
|
|
}
|
|
|
|
picture->pic_order_cnt = poc_msb + poc_lsb;
|
|
picture->pic_order_cnt_msb = poc_msb;
|
|
picture->pic_order_cnt_lsb = poc_lsb;
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_set_buffer_flags (GstH266Decoder * self,
|
|
GstH266Picture * picture)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
|
|
if (!priv->ff_info.valid) {
|
|
if (priv->field_seq_flag) {
|
|
GST_FIXME_OBJECT (self, "When sps_field_seq_flag is equal to 1, a "
|
|
"frame-field information SEI message shall be present for every "
|
|
"coded picture in the CLVS.");
|
|
}
|
|
return;
|
|
}
|
|
|
|
picture->ff_info = priv->ff_info;
|
|
|
|
if (priv->ff_info.field_pic_flag) {
|
|
if (priv->ff_info.bottom_field_flag) {
|
|
picture->buffer_flags |= GST_VIDEO_BUFFER_FLAG_BOTTOM_FIELD;
|
|
} else {
|
|
picture->buffer_flags |= GST_VIDEO_BUFFER_FLAG_TOP_FIELD;
|
|
}
|
|
} else {
|
|
if (priv->ff_info.display_fields_from_frame_flag) {
|
|
picture->buffer_flags |= GST_VIDEO_BUFFER_FLAG_INTERLACED;
|
|
if (priv->ff_info.top_field_first_flag)
|
|
picture->buffer_flags |= GST_VIDEO_BUFFER_FLAG_TFF;
|
|
} else {
|
|
if (priv->field_seq_flag) {
|
|
GST_FIXME_OBJECT (self, "frame-field information SEI message indicate "
|
|
"a complete frame but sps_field_seq_flag indicate the field only "
|
|
"stream.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_h266_decoder_init_current_picture (GstH266Decoder * self)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
const GstH266Slice *slice = &priv->current_slice;
|
|
GstH266Picture *picture = priv->current_picture;
|
|
|
|
gst_h266_decoder_calculate_poc (self, slice, picture);
|
|
|
|
picture->NoOutputBeforeRecoveryFlag = slice->no_output_before_recovery_flag;
|
|
picture->NoOutputOfPriorPicsFlag = slice->no_output_of_prior_pics_flag;
|
|
picture->type = slice->header.slice_type;
|
|
picture->non_ref = slice->header.picture_header.non_ref_pic_flag;
|
|
|
|
gst_h266_decoder_set_buffer_flags (self, picture);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_prepare_rpl (GstH266Decoder * self, const GstH266Slice * slice,
|
|
GstH266Picture * picture, gboolean new_picture)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
const GstH266RefPicLists *rpls = &slice->header.ref_pic_lists;
|
|
const GstH266RefPicListStruct *ref_list;
|
|
gint32 max_poc_lsb =
|
|
1 << (priv->parser->active_sps->log2_max_pic_order_cnt_lsb_minus4 + 4);
|
|
guint32 prev_delta_poc_msb, delta_poc_msb_cycle_lt;
|
|
gint poc_base, poc;
|
|
guint collocated_list =
|
|
slice->header.picture_header.collocated_from_l0_flag ? 0 : 1;
|
|
guint i, j;
|
|
GstH266Picture *ref_pic;
|
|
|
|
if (new_picture)
|
|
gst_h266_dpb_mark_all_non_ref (priv->dpb);
|
|
|
|
gst_h266_decoder_init_refs (self);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
ref_list = &rpls->rpl_ref_list[i];
|
|
poc_base = picture->pic_order_cnt;
|
|
prev_delta_poc_msb = 0;
|
|
|
|
for (j = 0; j < ref_list->num_ref_entries; j++) {
|
|
if (ref_list->inter_layer_ref_pic_flag[j]) {
|
|
GST_WARNING_OBJECT (self,
|
|
"Inter layer reference is not supported now.");
|
|
return GST_FLOW_NOT_SUPPORTED;
|
|
}
|
|
|
|
ref_pic = NULL;
|
|
|
|
if (ref_list->st_ref_pic_flag[j]) {
|
|
poc = poc_base + ref_list->delta_poc_val_st[j];
|
|
self->RefPicPocList[i][j] = poc;
|
|
|
|
ref_pic = gst_h266_dpb_get_picture_by_poc (priv->dpb, poc);
|
|
|
|
if (!ref_pic) {
|
|
GST_WARNING_OBJECT (self,
|
|
"Missing a short term reference of poc: %d", poc);
|
|
} else {
|
|
if (ref_pic->non_ref) {
|
|
GST_WARNING_OBJECT (self, "non ref picture should not be "
|
|
"marked as reference");
|
|
}
|
|
|
|
ref_pic->ref = TRUE;
|
|
self->RefPicList[i][j] = ref_pic;
|
|
}
|
|
|
|
poc_base = poc;
|
|
} else {
|
|
if (!ref_list->ltrp_in_header_flag) {
|
|
poc = ref_list->rpls_poc_lsb_lt[j];
|
|
} else {
|
|
poc = rpls->poc_lsb_lt[i][j];
|
|
}
|
|
|
|
if (rpls->delta_poc_msb_cycle_present_flag[i][j]) {
|
|
delta_poc_msb_cycle_lt = rpls->delta_poc_msb_cycle_lt[i][j];
|
|
delta_poc_msb_cycle_lt += prev_delta_poc_msb;
|
|
poc += picture->pic_order_cnt - delta_poc_msb_cycle_lt * max_poc_lsb -
|
|
(picture->pic_order_cnt & (max_poc_lsb - 1));
|
|
prev_delta_poc_msb = delta_poc_msb_cycle_lt;
|
|
}
|
|
|
|
self->RefPicLtPocList[i][j] = poc;
|
|
|
|
if (rpls->delta_poc_msb_cycle_present_flag[i][j]) {
|
|
ref_pic = gst_h266_dpb_get_picture_by_poc (priv->dpb, poc);
|
|
} else {
|
|
ref_pic = gst_h266_dpb_get_picture_by_poc_lsb (priv->dpb, poc);
|
|
}
|
|
|
|
if (!ref_pic) {
|
|
GST_WARNING_OBJECT (self,
|
|
"Missing a long term reference of poc: %d", poc);
|
|
} else {
|
|
if (ref_pic->non_ref) {
|
|
GST_WARNING_OBJECT (self, "non ref picture should not be "
|
|
"marked as reference");
|
|
}
|
|
|
|
ref_pic->ref = TRUE;
|
|
ref_pic->long_term = TRUE;
|
|
self->RefPicList[i][j] = ref_pic;
|
|
}
|
|
}
|
|
|
|
if (ref_pic)
|
|
gst_h266_picture_unref (ref_pic);
|
|
}
|
|
|
|
/* the first NumRefIdxActive[i] entries in RefPicList[i] are
|
|
referred to as the active entries in RefPicList[i], and the
|
|
other entries in RefPicList[i] are referred to as the inactive
|
|
entries in RefPicList[i]. */
|
|
self->NumRefIdxActive[i] = slice->header.num_ref_idx_active[i];
|
|
|
|
if (collocated_list != i)
|
|
continue;
|
|
|
|
if (slice->header.picture_header.temporal_mvp_enabled_flag) {
|
|
if (slice->header.collocated_ref_idx > self->NumRefIdxActive[i] - 1 ||
|
|
self->RefPicList[i][slice->header.collocated_ref_idx] == NULL) {
|
|
GST_WARNING_OBJECT (self, "Missing the collocated reference of "
|
|
"index: %d in reference list: %d.",
|
|
slice->header.collocated_ref_idx, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
/* C.5.2.2 */
|
|
static GstFlowReturn
|
|
gst_h266_decoder_dpb_init (GstH266Decoder * self, const GstH266Slice * slice,
|
|
GstH266Picture * picture)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
const GstH266SPS *sps = priv->parser->active_sps;
|
|
GstH266Picture *to_output;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
/* C 3.2 */
|
|
if (slice->clear_dpb) {
|
|
if (picture->NoOutputOfPriorPicsFlag) {
|
|
GST_DEBUG_OBJECT (self, "Clear dpb");
|
|
gst_h266_decoder_drain_output_queue (self, 0, &priv->last_flow);
|
|
gst_h266_decoder_clear_dpb (self, FALSE);
|
|
} else {
|
|
gst_h266_dpb_delete_unused (priv->dpb);
|
|
|
|
while ((to_output = gst_h266_dpb_bump (priv->dpb, FALSE)) != NULL)
|
|
gst_h266_decoder_do_output_picture (self, to_output, &ret);
|
|
|
|
if (gst_h266_dpb_get_size (priv->dpb) > 0) {
|
|
/* For CRA with NoOutputOfPriorPicsFlag=0, the previous pictures
|
|
can still be references and following pictures may be RASL. */
|
|
if (!GST_H266_IS_NAL_TYPE_CRA (slice->nalu.type)) {
|
|
GST_WARNING_OBJECT (self, "IDR frame failed to clear the dpb, "
|
|
"there are still %d pictures in the dpb, last output poc is %d",
|
|
gst_h266_dpb_get_size (priv->dpb), priv->last_output_poc);
|
|
}
|
|
} else {
|
|
priv->last_output_poc = G_MININT32;
|
|
}
|
|
}
|
|
} else {
|
|
gst_h266_dpb_delete_unused (priv->dpb);
|
|
|
|
while (gst_h266_dpb_needs_bump (priv->dpb,
|
|
sps->dpb.max_num_reorder_pics[sps->max_sublayers_minus1],
|
|
priv->SpsMaxLatencyPictures,
|
|
sps->dpb.max_dec_pic_buffering_minus1[sps->max_sublayers_minus1] +
|
|
1)) {
|
|
to_output = gst_h266_dpb_bump (priv->dpb, FALSE);
|
|
|
|
/* Something wrong... */
|
|
if (!to_output) {
|
|
GST_WARNING_OBJECT (self, "Bumping is needed but no picture to output");
|
|
break;
|
|
}
|
|
|
|
gst_h266_decoder_do_output_picture (self, to_output, &ret);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_h266_decoder_add_aps (GstH266Decoder * self,
|
|
GstH266APSType aps_type, guint8 aps_id)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
const GstH266Parser *parser = priv->parser;
|
|
const GstH266APS *aps;
|
|
|
|
g_assert (aps_id <= 7);
|
|
|
|
aps = &parser->aps[aps_type][aps_id];
|
|
if (!aps->valid) {
|
|
GST_WARNING_OBJECT (self, "APS type %d, id %d is not valid.",
|
|
aps_type, aps_id);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!priv->aps_added[aps_type][aps_id]) {
|
|
priv->aps_added[aps_type][aps_id] = TRUE;
|
|
g_array_append_val (self->aps_list[aps_type], aps);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_h266_decoder_collect_aps_list (GstH266Decoder * self,
|
|
const GstH266Slice * slice)
|
|
{
|
|
guint8 aps_id;
|
|
guint i;
|
|
|
|
if (slice->header.alf_enabled_flag) {
|
|
for (i = 0; i < slice->header.num_alf_aps_ids_luma; i++) {
|
|
aps_id = slice->header.alf_aps_id_luma[i];
|
|
if (!gst_h266_decoder_add_aps (self, GST_H266_ALF_APS, aps_id))
|
|
return FALSE;
|
|
}
|
|
|
|
if (slice->header.alf_cb_enabled_flag || slice->header.alf_cr_enabled_flag) {
|
|
aps_id = slice->header.alf_aps_id_chroma;
|
|
if (!gst_h266_decoder_add_aps (self, GST_H266_ALF_APS, aps_id))
|
|
return FALSE;
|
|
}
|
|
|
|
if (slice->header.alf_cc_cb_enabled_flag) {
|
|
aps_id = slice->header.alf_cc_cb_aps_id;
|
|
if (!gst_h266_decoder_add_aps (self, GST_H266_ALF_APS, aps_id))
|
|
return FALSE;
|
|
}
|
|
|
|
if (slice->header.alf_cc_cr_enabled_flag) {
|
|
aps_id = slice->header.alf_cc_cr_aps_id;
|
|
if (!gst_h266_decoder_add_aps (self, GST_H266_ALF_APS, aps_id))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (slice->header.lmcs_used_flag) {
|
|
aps_id = slice->header.picture_header.lmcs_aps_id;
|
|
if (!gst_h266_decoder_add_aps (self, GST_H266_LMCS_APS, aps_id))
|
|
return FALSE;
|
|
}
|
|
|
|
if (slice->header.explicit_scaling_list_used_flag) {
|
|
aps_id = slice->header.picture_header.scaling_list_aps_id;
|
|
if (!gst_h266_decoder_add_aps (self, GST_H266_SCALING_APS, aps_id))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_start_current_picture (GstH266Decoder * self)
|
|
{
|
|
GstH266DecoderClass *klass;
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
const GstH266Slice *slice = &priv->current_slice;
|
|
GstH266Picture *picture = priv->current_picture;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
g_assert (priv->current_picture != NULL);
|
|
g_assert (priv->parser->active_vps != NULL);
|
|
g_assert (priv->parser->active_sps != NULL);
|
|
g_assert (priv->parser->active_pps != NULL);
|
|
|
|
if (!gst_h266_decoder_init_current_picture (self))
|
|
return GST_FLOW_ERROR;
|
|
|
|
picture->pps_width = priv->parser->active_pps->width;
|
|
picture->pps_height = priv->parser->active_pps->height;
|
|
picture->pps_conformance_window_flag =
|
|
priv->parser->active_pps->conformance_window_flag;
|
|
picture->pps_crop_rect_width = priv->parser->active_pps->crop_rect_width;
|
|
picture->pps_crop_rect_height = priv->parser->active_pps->crop_rect_height;
|
|
picture->pps_crop_rect_x = priv->parser->active_pps->crop_rect_x;
|
|
picture->pps_crop_rect_y = priv->parser->active_pps->crop_rect_y;
|
|
|
|
if (priv->no_output_before_recovery_flag) {
|
|
if (GST_H266_IS_NAL_TYPE_IRAP (slice->nalu.type)) {
|
|
priv->gdr_recovery_point_poc = G_MININT;
|
|
} else if (GST_H266_IS_NAL_TYPE_GDR (slice->nalu.type)) {
|
|
priv->gdr_recovery_point_poc = picture->pic_order_cnt +
|
|
slice->header.picture_header.recovery_poc_cnt;
|
|
}
|
|
|
|
if (priv->gdr_recovery_point_poc != G_MININT &&
|
|
priv->gdr_recovery_point_poc <= picture->pic_order_cnt)
|
|
priv->gdr_recovery_point_poc = G_MININT;
|
|
|
|
/* Drop all RASL pictures having NoRaslOutputFlag is TRUE. */
|
|
if (GST_H266_IS_NAL_TYPE_RASL (slice->nalu.type)) {
|
|
GST_DEBUG_OBJECT (self, "Drop current picture");
|
|
gst_clear_h266_picture (&priv->current_picture);
|
|
return GST_FLOW_OK;
|
|
}
|
|
}
|
|
|
|
if ((slice->nalu.temporal_id_plus1 - 1 == 0) &&
|
|
!slice->header.picture_header.non_ref_pic_flag &&
|
|
!(GST_H266_IS_NAL_TYPE_RASL (slice->nalu.type) ||
|
|
GST_H266_IS_NAL_TYPE_RADL (slice->nalu.type)))
|
|
priv->prev_tid0_pic = picture->pic_order_cnt;
|
|
|
|
if (priv->gdr_recovery_point_poc != G_MININT &&
|
|
picture->pic_order_cnt < priv->gdr_recovery_point_poc) {
|
|
g_assert (priv->no_output_before_recovery_flag);
|
|
picture->output_flag = FALSE;
|
|
} else if (slice->header.picture_header.pic_output_flag) {
|
|
picture->output_flag = TRUE;
|
|
} else {
|
|
picture->output_flag = FALSE;
|
|
}
|
|
|
|
ret = gst_h266_decoder_prepare_rpl (self, slice, picture, TRUE);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "Failed to prepare ref pic list");
|
|
gst_clear_h266_picture (&priv->current_picture);
|
|
return ret;
|
|
}
|
|
|
|
ret = gst_h266_decoder_dpb_init (self, slice, picture);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "Failed to init dpb");
|
|
gst_clear_h266_picture (&priv->current_picture);
|
|
return ret;
|
|
}
|
|
|
|
klass = GST_H266_DECODER_GET_CLASS (self);
|
|
|
|
if (klass->new_picture)
|
|
ret = klass->new_picture (self, priv->current_frame, picture);
|
|
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "subclass does not want accept new picture");
|
|
gst_clear_h266_picture (&priv->current_picture);
|
|
return ret;
|
|
}
|
|
|
|
if (klass->start_picture) {
|
|
ret = klass->start_picture (self, picture, &priv->current_slice, priv->dpb);
|
|
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "subclass does not want to start picture");
|
|
gst_clear_h266_picture (&priv->current_picture);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* If subclass didn't update output state at this point,
|
|
* marking this picture as a discont and stores current input state */
|
|
if (priv->input_state_changed) {
|
|
gst_h266_picture_set_discont_state (priv->current_picture,
|
|
self->input_state);
|
|
priv->input_state_changed = FALSE;
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_decode_slice (GstH266Decoder * self)
|
|
{
|
|
GstH266DecoderClass *klass = GST_H266_DECODER_GET_CLASS (self);
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstH266Slice *slice = &priv->current_slice;
|
|
GstH266Picture *picture = priv->current_picture;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
if (!picture) {
|
|
GST_ERROR_OBJECT (self, "No current picture");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
g_assert (klass->decode_slice);
|
|
|
|
ret = klass->decode_slice (self, picture, slice);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_process_slice (GstH266Decoder * self, GstH266Slice * slice)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
priv->current_slice = *slice;
|
|
|
|
ret = gst_h266_decoder_preprocess_slice (self, &priv->current_slice);
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
|
|
/* The used SPS may not be the latest parsed one, make
|
|
* sure we have updated it before decode the current frame */
|
|
ret = gst_h266_decoder_process_sps (self,
|
|
priv->current_slice.header.picture_header.pps->sps);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "Failed to process sps");
|
|
return ret;
|
|
}
|
|
|
|
if (!priv->current_picture) {
|
|
GstH266Picture *picture;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
g_assert (priv->current_frame);
|
|
|
|
picture = gst_h266_picture_new ();
|
|
/* This allows accessing the frame from the picture. */
|
|
GST_CODEC_PICTURE_FRAME_NUMBER (picture) =
|
|
priv->current_frame->system_frame_number;
|
|
|
|
priv->current_picture = picture;
|
|
|
|
ret = gst_h266_decoder_start_current_picture (self);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "start picture failed");
|
|
return ret;
|
|
}
|
|
|
|
/* this picture was dropped */
|
|
if (!priv->current_picture)
|
|
return GST_FLOW_OK;
|
|
} else {
|
|
ret = gst_h266_decoder_prepare_rpl (self, slice,
|
|
priv->current_picture, FALSE);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "Failed to prepare ref pic list");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return gst_h266_decoder_decode_slice (self);
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_finish_picture (GstH266Decoder * self,
|
|
GstH266Picture * picture, GstFlowReturn * ret)
|
|
{
|
|
GstVideoDecoder *decoder = GST_VIDEO_DECODER (self);
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
const GstH266SPS *sps = priv->parser->active_sps;
|
|
|
|
g_assert (ret != NULL);
|
|
|
|
GST_LOG_OBJECT (self, "Finishing picture %p (poc %d), entries in DPB %d",
|
|
picture, picture->pic_order_cnt, gst_h266_dpb_get_size (priv->dpb));
|
|
|
|
/* This picture is decode only, drop corresponding frame */
|
|
if (!picture->output_flag) {
|
|
GstVideoCodecFrame *frame = gst_video_decoder_get_frame (decoder,
|
|
GST_CODEC_PICTURE_FRAME_NUMBER (picture));
|
|
|
|
gst_video_decoder_release_frame (decoder, frame);
|
|
}
|
|
|
|
/* gst_h266_dpb_add() will take care of pic_latency_cnt increment and
|
|
* reference picture marking for this picture */
|
|
gst_h266_dpb_add (priv->dpb, picture);
|
|
|
|
/* NOTE: As per C.5.2.2, bumping by dpb_max_dec_pic_buffering_minus1 is
|
|
* applied only for the output and removal of pictures from the DPB before
|
|
* the decoding of the current picture. So pass zero here */
|
|
while (gst_h266_dpb_needs_bump (priv->dpb,
|
|
sps->dpb.max_num_reorder_pics[sps->max_sublayers_minus1],
|
|
priv->SpsMaxLatencyPictures, 0)) {
|
|
GstH266Picture *to_output = gst_h266_dpb_bump (priv->dpb, FALSE);
|
|
|
|
/* Something wrong... */
|
|
if (!to_output) {
|
|
GST_WARNING_OBJECT (self, "Bumping is needed but no picture to output");
|
|
break;
|
|
}
|
|
|
|
gst_h266_decoder_do_output_picture (self, to_output, ret);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_finish_current_picture (GstH266Decoder * self,
|
|
GstFlowReturn * ret)
|
|
{
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstH266DecoderClass *klass;
|
|
GstFlowReturn flow_ret = GST_FLOW_OK;
|
|
|
|
g_assert (ret != NULL);
|
|
|
|
if (!priv->current_picture)
|
|
return;
|
|
|
|
klass = GST_H266_DECODER_GET_CLASS (self);
|
|
|
|
if (klass->end_picture) {
|
|
flow_ret = klass->end_picture (self, priv->current_picture);
|
|
if (flow_ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "End picture failed");
|
|
|
|
/* continue to empty dpb */
|
|
UPDATE_FLOW_RETURN (ret, flow_ret);
|
|
}
|
|
}
|
|
|
|
/* finish picture takes ownership of the picture */
|
|
gst_h266_decoder_finish_picture (self, priv->current_picture, &flow_ret);
|
|
priv->current_picture = NULL;
|
|
|
|
UPDATE_FLOW_RETURN (ret, flow_ret);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_h266_decoder_handle_frame (GstVideoDecoder * decoder,
|
|
GstVideoCodecFrame * frame)
|
|
{
|
|
GstH266Decoder *self = GST_H266_DECODER (decoder);
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
GstBuffer *in_buf = frame->input_buffer;
|
|
GstH266NalUnit nalu;
|
|
GstH266ParserResult pres;
|
|
GstMapInfo map;
|
|
GstFlowReturn decode_ret = GST_FLOW_OK;
|
|
guint i;
|
|
|
|
GST_LOG_OBJECT (self,
|
|
"handle frame, PTS: %" GST_TIME_FORMAT ", DTS: %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_PTS (in_buf)),
|
|
GST_TIME_ARGS (GST_BUFFER_DTS (in_buf)));
|
|
|
|
gst_h266_decoder_reset_frame_state (self);
|
|
|
|
priv->last_flow = GST_FLOW_OK;
|
|
priv->current_frame = frame;
|
|
|
|
if (!gst_buffer_map (in_buf, &map, GST_MAP_READ)) {
|
|
GST_ELEMENT_ERROR (self, RESOURCE, READ,
|
|
("Failed to map memory for reading"), (NULL));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
if (priv->in_format == GST_H266_DECODER_FORMAT_VVC1 ||
|
|
priv->in_format == GST_H266_DECODER_FORMAT_VVI1) {
|
|
gst_buffer_unmap (in_buf, &map);
|
|
gst_h266_decoder_reset_frame_state (self);
|
|
return GST_FLOW_NOT_SUPPORTED;
|
|
} else {
|
|
pres = gst_h266_parser_identify_nalu (priv->parser,
|
|
map.data, 0, map.size, &nalu);
|
|
|
|
/* Should already aligned to AU. */
|
|
if (pres == GST_H266_PARSER_NO_NAL_END)
|
|
pres = GST_H266_PARSER_OK;
|
|
|
|
while (pres == GST_H266_PARSER_OK) {
|
|
pres = gst_h266_decoder_parse_nalu (self, &nalu);
|
|
if (pres != GST_H266_PARSER_OK)
|
|
break;
|
|
|
|
pres = gst_h266_parser_identify_nalu (priv->parser,
|
|
map.data, nalu.offset + nalu.size, map.size, &nalu);
|
|
|
|
if (pres == GST_H266_PARSER_NO_NAL_END)
|
|
pres = GST_H266_PARSER_OK;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < priv->slices->len && decode_ret == GST_FLOW_OK; i++) {
|
|
GstH266Slice *decoder_slice =
|
|
&g_array_index (priv->slices, GstH266Slice, i);
|
|
if (!gst_h266_decoder_collect_aps_list (self, decoder_slice))
|
|
decode_ret = GST_FLOW_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < priv->slices->len && decode_ret == GST_FLOW_OK; i++) {
|
|
GstH266Slice *decoder_slice =
|
|
&g_array_index (priv->slices, GstH266Slice, i);
|
|
decode_ret = gst_h266_decoder_process_slice (self, decoder_slice);
|
|
}
|
|
|
|
gst_buffer_unmap (in_buf, &map);
|
|
gst_h266_decoder_reset_frame_state (self);
|
|
|
|
if (decode_ret != GST_FLOW_OK) {
|
|
if (decode_ret == GST_FLOW_ERROR) {
|
|
GST_VIDEO_DECODER_ERROR (self, 1, STREAM, DECODE,
|
|
("Failed to decode data"), (NULL), decode_ret);
|
|
}
|
|
|
|
gst_video_decoder_release_frame (decoder, frame);
|
|
gst_clear_h266_picture (&priv->current_picture);
|
|
|
|
return decode_ret;
|
|
}
|
|
|
|
if (priv->current_picture) {
|
|
gst_h266_decoder_finish_current_picture (self, &decode_ret);
|
|
gst_video_codec_frame_unref (frame);
|
|
} else {
|
|
/* This picture was dropped */
|
|
gst_video_decoder_release_frame (decoder, frame);
|
|
}
|
|
|
|
if (priv->last_flow != GST_FLOW_OK) {
|
|
GST_DEBUG_OBJECT (self,
|
|
"Last flow %s", gst_flow_get_name (priv->last_flow));
|
|
return priv->last_flow;
|
|
}
|
|
|
|
if (decode_ret == GST_FLOW_ERROR) {
|
|
GST_VIDEO_DECODER_ERROR (self, 1, STREAM, DECODE,
|
|
("Failed to decode data"), (NULL), decode_ret);
|
|
}
|
|
|
|
return decode_ret;
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_finalize (GObject * object)
|
|
{
|
|
GstH266Decoder *self = GST_H266_DECODER (object);
|
|
GstH266DecoderPrivate *priv = self->priv;
|
|
gint i;
|
|
|
|
g_array_unref (priv->slices);
|
|
|
|
for (i = 0; i < GST_H266_APS_TYPE_MAX; i++)
|
|
g_array_unref (self->aps_list[i]);
|
|
|
|
gst_queue_array_free (priv->output_queue);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_init (GstH266Decoder * self)
|
|
{
|
|
GstH266DecoderPrivate *priv;
|
|
gint i;
|
|
|
|
gst_video_decoder_set_packetized (GST_VIDEO_DECODER (self), TRUE);
|
|
gst_video_decoder_set_needs_format (GST_VIDEO_DECODER (self), TRUE);
|
|
|
|
priv = gst_h266_decoder_get_instance_private (self);
|
|
self->priv = priv;
|
|
|
|
priv->last_output_poc = G_MININT32;
|
|
|
|
priv->slices = g_array_sized_new (FALSE, TRUE, sizeof (GstH266Slice), 8);
|
|
|
|
for (i = 0; i < GST_H266_APS_TYPE_MAX; i++)
|
|
self->aps_list[i] = g_array_new (FALSE, TRUE, sizeof (GstH266APS *));
|
|
|
|
priv->output_queue =
|
|
gst_queue_array_new_for_struct (sizeof (GstH266DecoderOutputFrame), 1);
|
|
gst_queue_array_set_clear_func (priv->output_queue,
|
|
(GDestroyNotify) gst_h266_decoder_clear_output_frame);
|
|
}
|
|
|
|
static void
|
|
gst_h266_decoder_class_init (GstH266DecoderClass * klass)
|
|
{
|
|
GstVideoDecoderClass *decoder_class = GST_VIDEO_DECODER_CLASS (klass);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = GST_DEBUG_FUNCPTR (gst_h266_decoder_finalize);
|
|
|
|
decoder_class->start = GST_DEBUG_FUNCPTR (gst_h266_decoder_start);
|
|
decoder_class->stop = GST_DEBUG_FUNCPTR (gst_h266_decoder_stop);
|
|
decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_h266_decoder_set_format);
|
|
decoder_class->negotiate = GST_DEBUG_FUNCPTR (gst_h266_decoder_negotiate);
|
|
decoder_class->finish = GST_DEBUG_FUNCPTR (gst_h266_decoder_finish);
|
|
decoder_class->flush = GST_DEBUG_FUNCPTR (gst_h266_decoder_flush);
|
|
decoder_class->drain = GST_DEBUG_FUNCPTR (gst_h266_decoder_drain);
|
|
decoder_class->handle_frame =
|
|
GST_DEBUG_FUNCPTR (gst_h266_decoder_handle_frame);
|
|
}
|