mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-16 21:36:35 +00:00
82c0005793
Some decoding APIs support delayed output for performance reasons. One example would be to request decoding for multiple frames and then query for the oldest frame in the output queue. This also increases throughput for transcoding and improves seek performance when supported by the underlying backend. Introduce support in the vp8 base class, so that backends that support render delays can actually implement it. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2150>
467 lines
13 KiB
C
467 lines
13 KiB
C
/* GStreamer
|
|
* Copyright (C) 2020 Seungha Yang <seungha@centricular.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:gstvp8decoder
|
|
* @title: GstVp8Decoder
|
|
* @short_description: Base class to implement stateless VP8 decoders
|
|
* @sources:
|
|
* - gstvp8picture.h
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <gst/base/base.h>
|
|
#include "gstvp8decoder.h"
|
|
|
|
GST_DEBUG_CATEGORY (gst_vp8_decoder_debug);
|
|
#define GST_CAT_DEFAULT gst_vp8_decoder_debug
|
|
|
|
struct _GstVp8DecoderPrivate
|
|
{
|
|
gint width;
|
|
gint height;
|
|
|
|
gboolean had_sequence;
|
|
GstVp8Parser parser;
|
|
gboolean wait_keyframe;
|
|
guint preferred_output_delay;
|
|
/* for delayed output */
|
|
GstQueueArray *output_queue;
|
|
gboolean is_live;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
GstVideoCodecFrame *frame;
|
|
GstVp8Picture *picture;
|
|
GstVp8Decoder *self;
|
|
} GstVp8DecoderOutputFrame;
|
|
|
|
#define parent_class gst_vp8_decoder_parent_class
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstVp8Decoder, gst_vp8_decoder,
|
|
GST_TYPE_VIDEO_DECODER,
|
|
G_ADD_PRIVATE (GstVp8Decoder);
|
|
GST_DEBUG_CATEGORY_INIT (gst_vp8_decoder_debug, "vp8decoder", 0,
|
|
"VP8 Video Decoder"));
|
|
|
|
static gboolean gst_vp8_decoder_start (GstVideoDecoder * decoder);
|
|
static gboolean gst_vp8_decoder_stop (GstVideoDecoder * decoder);
|
|
static gboolean gst_vp8_decoder_set_format (GstVideoDecoder * decoder,
|
|
GstVideoCodecState * state);
|
|
static GstFlowReturn gst_vp8_decoder_finish (GstVideoDecoder * decoder);
|
|
static gboolean gst_vp8_decoder_flush (GstVideoDecoder * decoder);
|
|
static GstFlowReturn gst_vp8_decoder_drain (GstVideoDecoder * decoder);
|
|
static GstFlowReturn gst_vp8_decoder_handle_frame (GstVideoDecoder * decoder,
|
|
GstVideoCodecFrame * frame);
|
|
static void gst_vp8_decoder_clear_output_frame (GstVp8DecoderOutputFrame *
|
|
output_frame);
|
|
static void gst_vp8_decoder_drain_output_queue (GstVp8Decoder * self,
|
|
guint num);
|
|
|
|
|
|
static void
|
|
gst_vp8_decoder_class_init (GstVp8DecoderClass * klass)
|
|
{
|
|
GstVideoDecoderClass *decoder_class = GST_VIDEO_DECODER_CLASS (klass);
|
|
|
|
decoder_class->start = GST_DEBUG_FUNCPTR (gst_vp8_decoder_start);
|
|
decoder_class->stop = GST_DEBUG_FUNCPTR (gst_vp8_decoder_stop);
|
|
decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_vp8_decoder_set_format);
|
|
decoder_class->finish = GST_DEBUG_FUNCPTR (gst_vp8_decoder_finish);
|
|
decoder_class->flush = GST_DEBUG_FUNCPTR (gst_vp8_decoder_flush);
|
|
decoder_class->drain = GST_DEBUG_FUNCPTR (gst_vp8_decoder_drain);
|
|
decoder_class->handle_frame =
|
|
GST_DEBUG_FUNCPTR (gst_vp8_decoder_handle_frame);
|
|
}
|
|
|
|
static void
|
|
gst_vp8_decoder_init (GstVp8Decoder * self)
|
|
{
|
|
gst_video_decoder_set_packetized (GST_VIDEO_DECODER (self), TRUE);
|
|
|
|
self->priv = gst_vp8_decoder_get_instance_private (self);
|
|
}
|
|
|
|
static gboolean
|
|
gst_vp8_decoder_start (GstVideoDecoder * decoder)
|
|
{
|
|
GstVp8Decoder *self = GST_VP8_DECODER (decoder);
|
|
GstVp8DecoderPrivate *priv = self->priv;
|
|
|
|
gst_vp8_parser_init (&priv->parser);
|
|
priv->wait_keyframe = TRUE;
|
|
|
|
priv->output_queue =
|
|
gst_queue_array_new_for_struct (sizeof (GstVp8DecoderOutputFrame), 1);
|
|
gst_queue_array_set_clear_func (priv->output_queue,
|
|
(GDestroyNotify) gst_vp8_decoder_clear_output_frame);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_vp8_decoder_reset (GstVp8Decoder * self)
|
|
{
|
|
GstVp8DecoderPrivate *priv = self->priv;
|
|
|
|
gst_vp8_picture_clear (&self->last_picture);
|
|
gst_vp8_picture_clear (&self->golden_ref_picture);
|
|
gst_vp8_picture_clear (&self->alt_ref_picture);
|
|
|
|
priv->wait_keyframe = TRUE;
|
|
gst_queue_array_clear (priv->output_queue);
|
|
}
|
|
|
|
static gboolean
|
|
gst_vp8_decoder_stop (GstVideoDecoder * decoder)
|
|
{
|
|
GstVp8Decoder *self = GST_VP8_DECODER (decoder);
|
|
GstVp8DecoderPrivate *priv = self->priv;
|
|
|
|
if (self->input_state) {
|
|
gst_video_codec_state_unref (self->input_state);
|
|
self->input_state = NULL;
|
|
}
|
|
|
|
gst_vp8_decoder_reset (self);
|
|
gst_queue_array_free (priv->output_queue);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_vp8_decoder_check_codec_change (GstVp8Decoder * self,
|
|
const GstVp8FrameHdr * frame_hdr)
|
|
{
|
|
GstVp8DecoderPrivate *priv = self->priv;
|
|
gboolean ret = TRUE;
|
|
gboolean changed = FALSE;
|
|
|
|
if (priv->width != frame_hdr->width || priv->height != frame_hdr->height) {
|
|
GST_INFO_OBJECT (self, "resolution changed %dx%d", frame_hdr->width,
|
|
frame_hdr->height);
|
|
priv->width = frame_hdr->width;
|
|
priv->height = frame_hdr->height;
|
|
changed = TRUE;
|
|
}
|
|
|
|
if (changed || !priv->had_sequence) {
|
|
GstVp8DecoderClass *klass = GST_VP8_DECODER_GET_CLASS (self);
|
|
|
|
priv->had_sequence = TRUE;
|
|
|
|
if (klass->new_sequence)
|
|
ret = klass->new_sequence (self, frame_hdr);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_vp8_decoder_set_format (GstVideoDecoder * decoder,
|
|
GstVideoCodecState * state)
|
|
{
|
|
GstVp8Decoder *self = GST_VP8_DECODER (decoder);
|
|
GstVp8DecoderPrivate *priv = self->priv;
|
|
GstQuery *query;
|
|
|
|
GST_DEBUG_OBJECT (decoder, "Set format");
|
|
|
|
if (self->input_state)
|
|
gst_video_codec_state_unref (self->input_state);
|
|
|
|
self->input_state = gst_video_codec_state_ref (state);
|
|
|
|
priv->width = GST_VIDEO_INFO_WIDTH (&state->info);
|
|
priv->height = GST_VIDEO_INFO_HEIGHT (&state->info);
|
|
|
|
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);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_vp8_decoder_update_reference (GstVp8Decoder * self, GstVp8Picture * picture)
|
|
{
|
|
GstVp8FrameHdr *frame_hdr = &picture->frame_hdr;
|
|
|
|
if (frame_hdr->key_frame) {
|
|
gst_vp8_picture_replace (&self->last_picture, picture);
|
|
gst_vp8_picture_replace (&self->golden_ref_picture, picture);
|
|
gst_vp8_picture_replace (&self->alt_ref_picture, picture);
|
|
|
|
goto done;
|
|
}
|
|
|
|
if (frame_hdr->refresh_alternate_frame) {
|
|
gst_vp8_picture_replace (&self->alt_ref_picture, picture);
|
|
} else {
|
|
switch (frame_hdr->copy_buffer_to_alternate) {
|
|
case 0:
|
|
/* do nothing */
|
|
break;
|
|
case 1:
|
|
gst_vp8_picture_replace (&self->alt_ref_picture, self->last_picture);
|
|
break;
|
|
case 2:
|
|
gst_vp8_picture_replace (&self->alt_ref_picture,
|
|
self->golden_ref_picture);
|
|
break;
|
|
default:
|
|
GST_WARNING_OBJECT (self, "unrecognized copy_buffer_to_alternate %d",
|
|
frame_hdr->copy_buffer_to_alternate);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (frame_hdr->refresh_golden_frame) {
|
|
gst_vp8_picture_replace (&self->golden_ref_picture, picture);
|
|
} else {
|
|
switch (frame_hdr->copy_buffer_to_golden) {
|
|
case 0:
|
|
/* do nothing */
|
|
break;
|
|
case 1:
|
|
gst_vp8_picture_replace (&self->golden_ref_picture, self->last_picture);
|
|
break;
|
|
case 2:
|
|
gst_vp8_picture_replace (&self->golden_ref_picture,
|
|
self->alt_ref_picture);
|
|
break;
|
|
default:
|
|
GST_WARNING_OBJECT (self, "unrecognized copy_buffer_to_golden %d",
|
|
frame_hdr->copy_buffer_to_alternate);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (frame_hdr->refresh_last)
|
|
gst_vp8_picture_replace (&self->last_picture, picture);
|
|
|
|
done:
|
|
gst_vp8_picture_unref (picture);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_vp8_decoder_finish (GstVideoDecoder * decoder)
|
|
{
|
|
GstVp8Decoder *self = GST_VP8_DECODER (decoder);
|
|
|
|
GST_DEBUG_OBJECT (self, "finish");
|
|
|
|
gst_vp8_decoder_reset (self);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static gboolean
|
|
gst_vp8_decoder_flush (GstVideoDecoder * decoder)
|
|
{
|
|
GstVp8Decoder *self = GST_VP8_DECODER (decoder);
|
|
|
|
GST_DEBUG_OBJECT (self, "flush");
|
|
|
|
gst_vp8_decoder_reset (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_vp8_decoder_drain (GstVideoDecoder * decoder)
|
|
{
|
|
GstVp8Decoder *self = GST_VP8_DECODER (decoder);
|
|
|
|
GST_DEBUG_OBJECT (self, "drain");
|
|
|
|
gst_vp8_decoder_drain_output_queue (GST_VP8_DECODER (decoder), 0);
|
|
gst_vp8_decoder_reset (self);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static void
|
|
gst_vp8_decoder_clear_output_frame (GstVp8DecoderOutputFrame * 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_vp8_picture_clear (&output_frame->picture);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_vp8_decoder_handle_frame (GstVideoDecoder * decoder,
|
|
GstVideoCodecFrame * frame)
|
|
{
|
|
GstVp8Decoder *self = GST_VP8_DECODER (decoder);
|
|
GstVp8DecoderClass *klass = GST_VP8_DECODER_GET_CLASS (self);
|
|
GstVp8DecoderPrivate *priv = self->priv;
|
|
GstBuffer *in_buf = frame->input_buffer;
|
|
GstMapInfo map;
|
|
GstVp8FrameHdr frame_hdr;
|
|
GstVp8ParserResult pres;
|
|
GstVp8Picture *picture = NULL;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstVp8DecoderOutputFrame output_frame;
|
|
|
|
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)));
|
|
|
|
if (klass->get_preferred_output_delay)
|
|
priv->preferred_output_delay =
|
|
klass->get_preferred_output_delay (self, priv->is_live);
|
|
|
|
if (!gst_buffer_map (in_buf, &map, GST_MAP_READ)) {
|
|
GST_ERROR_OBJECT (self, "Cannot map buffer");
|
|
|
|
goto error;
|
|
}
|
|
|
|
pres = gst_vp8_parser_parse_frame_header (&priv->parser,
|
|
&frame_hdr, map.data, map.size);
|
|
|
|
if (pres != GST_VP8_PARSER_OK) {
|
|
GST_ERROR_OBJECT (self, "Cannot parser frame header");
|
|
|
|
goto unmap_and_error;
|
|
}
|
|
|
|
if (priv->wait_keyframe) {
|
|
if (!frame_hdr.key_frame) {
|
|
GST_DEBUG_OBJECT (self, "Waiting initial keyframe, drop buffer %"
|
|
GST_PTR_FORMAT, in_buf);
|
|
|
|
gst_buffer_unmap (in_buf, &map);
|
|
gst_video_decoder_drop_frame (decoder, frame);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
}
|
|
|
|
priv->wait_keyframe = FALSE;
|
|
|
|
if (frame_hdr.key_frame &&
|
|
!gst_vp8_decoder_check_codec_change (self, &frame_hdr)) {
|
|
GST_ERROR_OBJECT (self, "Subclass cannot handle codec change");
|
|
goto unmap_and_error;
|
|
}
|
|
|
|
picture = gst_vp8_picture_new ();
|
|
picture->frame_hdr = frame_hdr;
|
|
picture->pts = GST_BUFFER_PTS (in_buf);
|
|
picture->data = map.data;
|
|
picture->size = map.size;
|
|
picture->system_frame_number = frame->system_frame_number;
|
|
|
|
if (klass->new_picture) {
|
|
if (!klass->new_picture (self, frame, picture)) {
|
|
GST_ERROR_OBJECT (self, "subclass cannot handle new picture");
|
|
goto unmap_and_error;
|
|
}
|
|
}
|
|
|
|
if (klass->start_picture) {
|
|
if (!klass->start_picture (self, picture)) {
|
|
GST_ERROR_OBJECT (self, "subclass cannot handle start picture");
|
|
goto unmap_and_error;
|
|
}
|
|
}
|
|
|
|
if (klass->decode_picture) {
|
|
if (!klass->decode_picture (self, picture, &priv->parser)) {
|
|
GST_ERROR_OBJECT (self, "subclass cannot decode current picture");
|
|
goto unmap_and_error;
|
|
}
|
|
}
|
|
|
|
if (klass->end_picture) {
|
|
if (!klass->end_picture (self, picture)) {
|
|
GST_ERROR_OBJECT (self, "subclass cannot handle end picture");
|
|
goto unmap_and_error;
|
|
}
|
|
}
|
|
|
|
gst_buffer_unmap (in_buf, &map);
|
|
|
|
gst_vp8_decoder_update_reference (self, gst_vp8_picture_ref (picture));
|
|
|
|
if (!picture->frame_hdr.show_frame) {
|
|
GST_LOG_OBJECT (self, "Decode only picture %p", picture);
|
|
GST_VIDEO_CODEC_FRAME_SET_DECODE_ONLY (frame);
|
|
|
|
gst_vp8_picture_unref (picture);
|
|
|
|
ret = gst_video_decoder_finish_frame (GST_VIDEO_DECODER (self), frame);
|
|
} else {
|
|
output_frame.frame = frame;
|
|
output_frame.picture = picture;
|
|
output_frame.self = self;
|
|
gst_queue_array_push_tail_struct (priv->output_queue, &output_frame);
|
|
}
|
|
|
|
gst_vp8_decoder_drain_output_queue (self, priv->preferred_output_delay);
|
|
return ret;
|
|
|
|
unmap_and_error:
|
|
{
|
|
gst_buffer_unmap (in_buf, &map);
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
{
|
|
if (picture)
|
|
gst_vp8_picture_unref (picture);
|
|
|
|
gst_video_decoder_drop_frame (decoder, frame);
|
|
GST_VIDEO_DECODER_ERROR (self, 1, STREAM, DECODE,
|
|
("Failed to decode data"), (NULL), ret);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_vp8_decoder_drain_output_queue (GstVp8Decoder * self, guint num)
|
|
{
|
|
GstVp8DecoderPrivate *priv = self->priv;
|
|
GstVp8DecoderClass *klass = GST_VP8_DECODER_GET_CLASS (self);
|
|
g_assert (klass->output_picture);
|
|
|
|
while (gst_queue_array_get_length (priv->output_queue) > num) {
|
|
GstVp8DecoderOutputFrame *output_frame = (GstVp8DecoderOutputFrame *)
|
|
gst_queue_array_pop_head_struct (priv->output_queue);
|
|
klass->output_picture (self, output_frame->frame, output_frame->picture);
|
|
}
|
|
}
|