From 38803239c026163589dde04259b00c5fe33ad706 Mon Sep 17 00:00:00 2001 From: Alessandro Decina Date: Wed, 4 Apr 2012 20:43:23 +0200 Subject: [PATCH 01/14] audiodecoder: don't discard timestamps when consecutive input buffers have the same ts Avoid pushing out buffers with the same timestamp only if the out buffers are decoded from the same input buffer. Instead keep the timestamps when upstream pushes consecutive buffers with the same ts. --- gst-libs/gst/audio/gstaudiodecoder.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/gst-libs/gst/audio/gstaudiodecoder.c b/gst-libs/gst/audio/gstaudiodecoder.c index 5dfd278e1d..6bc054d47b 100644 --- a/gst-libs/gst/audio/gstaudiodecoder.c +++ b/gst-libs/gst/audio/gstaudiodecoder.c @@ -220,6 +220,7 @@ struct _GstAudioDecoderPrivate GstAdapter *adapter; /* tracking input ts for changes */ GstClockTime prev_ts; + guint64 prev_distance; /* frames obtained from input */ GQueue frames; /* collected output data */ @@ -445,6 +446,7 @@ gst_audio_decoder_reset (GstAudioDecoder * dec, gboolean full) dec->priv->out_ts = GST_CLOCK_TIME_NONE; dec->priv->out_dur = 0; dec->priv->prev_ts = GST_CLOCK_TIME_NONE; + dec->priv->prev_distance = 0; dec->priv->drained = TRUE; dec->priv->base_ts = GST_CLOCK_TIME_NONE; dec->priv->samples = 0; @@ -977,6 +979,7 @@ gst_audio_decoder_push_buffers (GstAudioDecoder * dec, gboolean force) if (G_LIKELY (av)) { gint len; GstClockTime ts; + guint64 distance; /* parse if needed */ if (klass->parse) { @@ -1016,12 +1019,13 @@ gst_audio_decoder_push_buffers (GstAudioDecoder * dec, gboolean force) len = av; } /* track upstream ts, but do not get stuck if nothing new upstream */ - ts = gst_adapter_prev_timestamp (priv->adapter, NULL); - if (ts == priv->prev_ts) { + ts = gst_adapter_prev_timestamp (priv->adapter, &distance); + if (ts != priv->prev_ts || distance <= priv->prev_distance) { + priv->prev_ts = ts; + priv->prev_distance = distance; + } else { GST_LOG_OBJECT (dec, "ts == prev_ts; discarding"); ts = GST_CLOCK_TIME_NONE; - } else { - priv->prev_ts = ts; } buffer = gst_adapter_take_buffer (priv->adapter, len); buffer = gst_buffer_make_metadata_writable (buffer); From c9ad084e291bd00d85b87e75bce4b63a9241260e Mon Sep 17 00:00:00 2001 From: Julian Scheel Date: Fri, 13 Apr 2012 16:29:50 +0200 Subject: [PATCH 02/14] uridecodebin: Never treat live sources as streaming sources. For streaming sources a queue is added before the demuxer, which can not be properly filled by live sources. As http source can be live sources, this caused issues for example with http live sources. Fixes https://bugzilla.gnome.org/show_bug.cgi?id=674057 --- gst/playback/gsturidecodebin.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gst/playback/gsturidecodebin.c b/gst/playback/gsturidecodebin.c index 30f80289a6..fbf14eee78 100644 --- a/gst/playback/gsturidecodebin.c +++ b/gst/playback/gsturidecodebin.c @@ -1994,6 +1994,21 @@ could_not_link: } } +static gboolean +is_live_source (GstElement * source) +{ + GObjectClass *source_class = NULL; + gboolean is_live = FALSE; + + source_class = G_OBJECT_GET_CLASS (source); + if (!g_object_class_find_property (source_class, "is-live")) + return FALSE; + + g_object_get (G_OBJECT (source), "is-live", &is_live, NULL); + + return is_live; +} + /* construct and run the source and decoder elements until we found * all the streams or until a preroll queue has been filled. */ @@ -2023,6 +2038,9 @@ setup_source (GstURIDecodeBin * decoder) g_signal_emit (decoder, gst_uri_decode_bin_signals[SIGNAL_SOURCE_SETUP], 0, decoder->source); + if (is_live_source (decoder->source)) + decoder->is_stream = FALSE; + /* remove the old decoders now, if any */ remove_decoders (decoder, FALSE); From 77db9a887c201ec248e8d070000619d1e45c7943 Mon Sep 17 00:00:00 2001 From: Akihiro Tsukada Date: Tue, 17 Apr 2012 09:54:09 +0200 Subject: [PATCH 03/14] playbin2: Don't hold the playbin lock in the autoplug-continue callback It's not necessary there as the group lock already protects everything we access here and causes deadlocks in some cases. Fixes bug #673708. --- gst/playback/gstplaybin2.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index 00743948a0..08cae9bc4d 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -3179,7 +3179,6 @@ autoplug_continue_cb (GstElement * element, GstPad * pad, GstCaps * caps, GstElement *sink; GstPad *sinkpad = NULL; - GST_PLAY_BIN_LOCK (group->playbin); GST_SOURCE_GROUP_LOCK (group); if ((sink = group->playbin->text_sink)) @@ -3256,7 +3255,6 @@ autoplug_continue_cb (GstElement * element, GstPad * pad, GstCaps * caps, done: GST_SOURCE_GROUP_UNLOCK (group); - GST_PLAY_BIN_UNLOCK (group->playbin); GST_DEBUG_OBJECT (group->playbin, "continue autoplugging group %p for %s:%s, %" GST_PTR_FORMAT ": %d", From f0b17b774fff6c826ca3c200dead0aca20bdcb05 Mon Sep 17 00:00:00 2001 From: Alessandro Decina Date: Thu, 19 Apr 2012 05:45:40 +0200 Subject: [PATCH 04/14] appsrc: reset is_eos flag after a succesful seek from _create --- gst-libs/gst/app/gstappsrc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gst-libs/gst/app/gstappsrc.c b/gst-libs/gst/app/gstappsrc.c index 24686b9531..2db8b1853d 100644 --- a/gst-libs/gst/app/gstappsrc.c +++ b/gst-libs/gst/app/gstappsrc.c @@ -974,6 +974,7 @@ gst_app_src_create (GstBaseSrc * bsrc, guint64 offset, guint size, goto seek_error; priv->offset = offset; + priv->is_eos = FALSE; } } From af71c29bcf019b8c5e5cfd1703744e15897b918d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 24 Apr 2012 10:13:08 +0200 Subject: [PATCH 05/14] subparse: Allow newlines/whitespace at the beginning of subrip files For example the Sintel subtitles have this and without this change they're detected as text/plain and not usable as subtitles. The parser itself already handles this just fine. --- gst/subparse/gstsubparse.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gst/subparse/gstsubparse.c b/gst/subparse/gstsubparse.c index 9c31b549fd..0b5df1f237 100644 --- a/gst/subparse/gstsubparse.c +++ b/gst/subparse/gstsubparse.c @@ -1244,7 +1244,8 @@ gst_sub_parse_data_format_autodetect_regex_once (GstSubParseRegex regtype) } break; case GST_SUB_PARSE_REGEX_SUBRIP: - result = (gpointer) g_regex_new ("^ {0,3}[ 0-9]{1,4}\\s*(\x0d)?\x0a" + result = (gpointer) + g_regex_new ("^[\\s\\n]*[\\n]? {0,3}[ 0-9]{1,4}\\s*(\x0d)?\x0a" " ?[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,3}" " +--> +[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,2}", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &gerr); From 8f00d76c9721a3d55824fafb2387c244ce54078f Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Thu, 8 Mar 2012 12:58:03 +0100 Subject: [PATCH 06/14] video: Backport GstVideoInfo/GstVideoFormatInfo from 0.11 --- docs/libs/gst-plugins-base-libs-sections.txt | 100 ++ gst-libs/gst/video/video.c | 1278 +++++++++++++----- gst-libs/gst/video/video.h | 467 +++++++ tests/check/libs/video.c | 14 +- 4 files changed, 1537 insertions(+), 322 deletions(-) diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 9e54a75c98..9fb1d08951 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -2277,7 +2277,85 @@ GST_VIDEO_BUFFER_TFF GST_VIDEO_BUFFER_RFF GST_VIDEO_BUFFER_ONEFIELD GST_VIDEO_BUFFER_PROGRESSIVE +GstVideoChromaSite +GstVideoColorMatrix +GstVideoColorPrimaries +GstVideoColorRange +GstVideoColorimetry +GstVideoFlags +GstVideoFormatFlags +GstVideoFormatPack +GstVideoFormatUnpack +GstVideoInterlaceMode +GstVideoTransferFunction GstVideoFormat +GstVideoFormatInfo +GST_VIDEO_FORMAT_INFO_BITS +GST_VIDEO_FORMAT_INFO_DATA +GST_VIDEO_FORMAT_INFO_DEPTH +GST_VIDEO_FORMAT_INFO_FLAGS +GST_VIDEO_FORMAT_INFO_FORMAT +GST_VIDEO_FORMAT_INFO_HAS_ALPHA +GST_VIDEO_FORMAT_INFO_HAS_PALETTE +GST_VIDEO_FORMAT_INFO_H_SUB +GST_VIDEO_FORMAT_INFO_IS_GRAY +GST_VIDEO_FORMAT_INFO_IS_LE +GST_VIDEO_FORMAT_INFO_IS_RGB +GST_VIDEO_FORMAT_INFO_IS_YUV +GST_VIDEO_FORMAT_INFO_NAME +GST_VIDEO_FORMAT_INFO_N_COMPONENTS +GST_VIDEO_FORMAT_INFO_N_PLANES +GST_VIDEO_FORMAT_INFO_OFFSET +GST_VIDEO_FORMAT_INFO_PLANE +GST_VIDEO_FORMAT_INFO_POFFSET +GST_VIDEO_FORMAT_INFO_PSTRIDE +GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT +GST_VIDEO_FORMAT_INFO_SCALE_WIDTH +GST_VIDEO_FORMAT_INFO_SHIFT +GST_VIDEO_FORMAT_INFO_STRIDE +GST_VIDEO_FORMAT_INFO_W_SUB +GstVideoInfo +GST_VIDEO_INFO_COMP_BITS +GST_VIDEO_INFO_COMP_DATA +GST_VIDEO_INFO_COMP_DEPTH +GST_VIDEO_INFO_COMP_HEIGHT +GST_VIDEO_INFO_COMP_OFFSET +GST_VIDEO_INFO_COMP_PLANE +GST_VIDEO_INFO_COMP_POFFSET +GST_VIDEO_INFO_COMP_PSTRIDE +GST_VIDEO_INFO_COMP_STRIDE +GST_VIDEO_INFO_COMP_WIDTH +GST_VIDEO_INFO_FLAGS +GST_VIDEO_INFO_FLAG_IS_SET +GST_VIDEO_INFO_FLAG_SET +GST_VIDEO_INFO_FLAG_UNSET +GST_VIDEO_INFO_FORMAT +GST_VIDEO_INFO_FPS_D +GST_VIDEO_INFO_FPS_N +GST_VIDEO_INFO_HAS_ALPHA +GST_VIDEO_INFO_HEIGHT +GST_VIDEO_INFO_INTERLACE_MODE +GST_VIDEO_INFO_IS_GRAY +GST_VIDEO_INFO_IS_INTERLACED +GST_VIDEO_INFO_IS_RGB +GST_VIDEO_INFO_IS_YUV +GST_VIDEO_INFO_NAME +GST_VIDEO_INFO_N_COMPONENTS +GST_VIDEO_INFO_N_PLANES +GST_VIDEO_INFO_PAR_D +GST_VIDEO_INFO_PAR_N +GST_VIDEO_INFO_PLANE_OFFSET +GST_VIDEO_INFO_PLANE_STRIDE +GST_VIDEO_INFO_SIZE +GST_VIDEO_INFO_WIDTH +GST_VIDEO_MAX_COMPONENTS +GST_VIDEO_MAX_PLANES +GST_VIDEO_SUB_SCALE +gst_video_info_convert +gst_video_info_from_caps +gst_video_info_init +gst_video_info_set_format +gst_video_info_to_caps gst_video_calculate_display_ratio gst_video_frame_rate gst_video_get_size @@ -2293,12 +2371,15 @@ gst_video_format_get_component_depth gst_video_format_get_pixel_stride gst_video_format_get_row_stride gst_video_format_get_size +gst_video_format_get_info gst_video_format_has_alpha gst_video_format_is_rgb gst_video_format_is_gray gst_video_format_is_yuv gst_video_format_to_fourcc gst_video_format_from_fourcc +gst_video_format_to_string +gst_video_format_from_masks gst_video_format_parse_caps gst_video_format_parse_caps_interlaced gst_video_parse_caps_framerate @@ -2306,6 +2387,9 @@ gst_video_parse_caps_pixel_aspect_ratio gst_video_parse_caps_color_matrix gst_video_parse_caps_chroma_site gst_video_parse_caps_palette +gst_video_colorimetry_from_string +gst_video_colorimetry_matches +gst_video_colorimetry_to_string GstVideoConvertFrameCallback gst_video_convert_frame gst_video_convert_frame_async @@ -2319,6 +2403,22 @@ gst_video_event_parse_downstream_force_key_unit gst_video_format_get_type GST_TYPE_VIDEO_FORMAT +GST_TYPE_VIDEO_CHROMA_SITE +GST_TYPE_VIDEO_COLOR_MATRIX +GST_TYPE_VIDEO_COLOR_PRIMARIES +GST_TYPE_VIDEO_COLOR_RANGE +GST_TYPE_VIDEO_FLAGS +GST_TYPE_VIDEO_FORMAT_FLAGS +GST_TYPE_VIDEO_INTERLACE_MODE +GST_TYPE_VIDEO_TRANSFER_FUNCTION +gst_video_chroma_site_get_type +gst_video_color_matrix_get_type +gst_video_color_primaries_get_type +gst_video_color_range_get_type +gst_video_flags_get_type +gst_video_format_flags_get_type +gst_video_interlace_mode_get_type +gst_video_transfer_function_get_type
diff --git a/gst-libs/gst/video/video.c b/gst-libs/gst/video/video.c index f68de1ce4e..1922832bbb 100644 --- a/gst-libs/gst/video/video.c +++ b/gst-libs/gst/video/video.c @@ -23,6 +23,7 @@ # include "config.h" #endif +#include #include "video.h" /** @@ -47,6 +48,218 @@ static GstVideoFormat gst_video_format_from_rgb16_masks (int red_mask, int green_mask, int blue_mask); +static int fill_planes (GstVideoInfo * info); + +typedef struct +{ + guint32 fourcc; + GstVideoFormatInfo info; +} VideoFormat; + +/* depths: bits, n_components, shift, depth */ +#define DPTH0 0, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } +#define DPTH8 8, 1, { 0, 0, 0, 0 }, { 8, 0, 0, 0 } +#define DPTH888 8, 3, { 0, 0, 0, 0 }, { 8, 8, 8, 0 } +#define DPTH8888 8, 4, { 0, 0, 0, 0 }, { 8, 8, 8, 8 } +#define DPTH10_10_10 10, 3, { 0, 0, 0, 0 }, { 10, 10, 10, 0 } +#define DPTH16 16, 1, { 0, 0, 0, 0 }, { 16, 0, 0, 0 } +#define DPTH16_16_16 16, 3, { 0, 0, 0, 0 }, { 16, 16, 16, 0 } +#define DPTH16_16_16_16 16, 4, { 0, 0, 0, 0 }, { 16, 16, 16, 16 } +#define DPTH555 16, 3, { 10, 5, 0, 0 }, { 5, 5, 5, 0 } +#define DPTH565 16, 3, { 11, 5, 0, 0 }, { 5, 6, 5, 0 } + +/* pixel strides */ +#define PSTR0 { 0, 0, 0, 0 } +#define PSTR1 { 1, 0, 0, 0 } +#define PSTR111 { 1, 1, 1, 0 } +#define PSTR1111 { 1, 1, 1, 1 } +#define PSTR122 { 1, 2, 2, 0 } +#define PSTR2 { 2, 0, 0, 0 } +#define PSTR222 { 2, 2, 2, 0 } +#define PSTR244 { 2, 4, 4, 0 } +#define PSTR444 { 4, 4, 4, 0 } +#define PSTR4444 { 4, 4, 4, 4 } +#define PSTR333 { 3, 3, 3, 0 } +#define PSTR488 { 4, 8, 8, 0 } +#define PSTR8888 { 8, 8, 8, 8 } + +/* planes */ +#define PLANE_NA 0, { 0, 0, 0, 0 } +#define PLANE0 1, { 0, 0, 0, 0 } +#define PLANE011 2, { 0, 1, 1, 0 } +#define PLANE012 3, { 0, 1, 2, 0 } +#define PLANE0123 4, { 0, 1, 2, 3 } +#define PLANE021 3, { 0, 2, 1, 0 } + +/* offsets */ +#define OFFS0 { 0, 0, 0, 0 } +#define OFFS013 { 0, 1, 3, 0 } +#define OFFS102 { 1, 0, 2, 0 } +#define OFFS1230 { 1, 2, 3, 0 } +#define OFFS012 { 0, 1, 2, 0 } +#define OFFS210 { 2, 1, 0, 0 } +#define OFFS123 { 1, 2, 3, 0 } +#define OFFS321 { 3, 2, 1, 0 } +#define OFFS0123 { 0, 1, 2, 3 } +#define OFFS2103 { 2, 1, 0, 3 } +#define OFFS3210 { 3, 2, 1, 0 } +#define OFFS031 { 0, 3, 1, 0 } +#define OFFS026 { 0, 2, 6, 0 } +#define OFFS001 { 0, 0, 1, 0 } +#define OFFS010 { 0, 1, 0, 0 } +#define OFFS104 { 1, 0, 4, 0 } +#define OFFS2460 { 2, 4, 6, 0 } + +/* subsampling */ +#define SUB410 { 0, 2, 2, 0 }, { 0, 2, 2, 0 } +#define SUB411 { 0, 2, 2, 0 }, { 0, 0, 0, 0 } +#define SUB420 { 0, 1, 1, 0 }, { 0, 1, 1, 0 } +#define SUB422 { 0, 1, 1, 0 }, { 0, 0, 0, 0 } +#define SUB4 { 0, 0, 0, 0 }, { 0, 0, 0, 0 } +#define SUB444 { 0, 0, 0, 0 }, { 0, 0, 0, 0 } +#define SUB4444 { 0, 0, 0, 0 }, { 0, 0, 0, 0 } +#define SUB4204 { 0, 1, 1, 0 }, { 0, 1, 1, 0 } + +#define MAKE_YUV_FORMAT(name, desc, fourcc, depth, pstride, plane, offs, sub ) \ + { fourcc, {GST_VIDEO_FORMAT_ ##name, G_STRINGIFY(name), desc, GST_VIDEO_FORMAT_FLAG_YUV, depth, pstride, plane, offs, sub } } +#define MAKE_YUVA_FORMAT(name, desc, fourcc, depth, pstride, plane, offs, sub) \ + { fourcc, {GST_VIDEO_FORMAT_ ##name, G_STRINGIFY(name), desc, GST_VIDEO_FORMAT_FLAG_YUV | GST_VIDEO_FORMAT_FLAG_ALPHA, depth, pstride, plane, offs, sub } } + +#define MAKE_RGB_FORMAT(name, desc, depth, pstride, plane, offs, sub) \ + { 0x00000000, {GST_VIDEO_FORMAT_ ##name, G_STRINGIFY(name), desc, GST_VIDEO_FORMAT_FLAG_RGB, depth, pstride, plane, offs, sub } } +#define MAKE_RGB_LE_FORMAT(name, desc, depth, pstride, plane, offs, sub) \ + { 0x00000000, {GST_VIDEO_FORMAT_ ##name, G_STRINGIFY(name), desc, GST_VIDEO_FORMAT_FLAG_RGB | GST_VIDEO_FORMAT_FLAG_LE, depth, pstride, plane, offs, sub } } +#define MAKE_RGBA_FORMAT(name, desc, depth, pstride, plane, offs, sub) \ + { 0x00000000, {GST_VIDEO_FORMAT_ ##name, G_STRINGIFY(name), desc, GST_VIDEO_FORMAT_FLAG_RGB | GST_VIDEO_FORMAT_FLAG_ALPHA, depth, pstride, plane, offs, sub } } + +#define MAKE_GRAY_FORMAT(name, desc, depth, pstride, plane, offs, sub) \ + { 0x00000000, {GST_VIDEO_FORMAT_ ##name, G_STRINGIFY(name), desc, GST_VIDEO_FORMAT_FLAG_GRAY, depth, pstride, plane, offs, sub } } +#define MAKE_GRAY_LE_FORMAT(name, desc, depth, pstride, plane, offs, sub) \ + { 0x00000000, {GST_VIDEO_FORMAT_ ##name, G_STRINGIFY(name), desc, GST_VIDEO_FORMAT_FLAG_GRAY | GST_VIDEO_FORMAT_FLAG_LE, depth, pstride, plane, offs, sub } } + +static VideoFormat formats[] = { + {0x00000000, {GST_VIDEO_FORMAT_UNKNOWN, "UNKNOWN", "unknown video", 0, DPTH0, + PSTR0, PLANE_NA, + OFFS0}}, + + MAKE_YUV_FORMAT (I420, "raw video", GST_MAKE_FOURCC ('I', '4', '2', '0'), + DPTH888, PSTR111, + PLANE012, OFFS0, SUB420), + MAKE_YUV_FORMAT (YV12, "raw video", GST_MAKE_FOURCC ('Y', 'V', '1', '2'), + DPTH888, PSTR111, + PLANE021, OFFS0, SUB420), + MAKE_YUV_FORMAT (YUY2, "raw video", GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'), + DPTH888, PSTR244, + PLANE0, OFFS013, SUB422), + MAKE_YUV_FORMAT (UYVY, "raw video", GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'), + DPTH888, PSTR244, + PLANE0, OFFS102, SUB422), + MAKE_YUVA_FORMAT (AYUV, "raw video", GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'), + DPTH8888, + PSTR4444, PLANE0, OFFS1230, SUB4444), + MAKE_RGB_FORMAT (RGBx, "raw video", DPTH888, PSTR444, PLANE0, OFFS012, + SUB444), + MAKE_RGB_FORMAT (BGRx, "raw video", DPTH888, PSTR444, PLANE0, OFFS210, + SUB444), + MAKE_RGB_FORMAT (xRGB, "raw video", DPTH888, PSTR444, PLANE0, OFFS123, + SUB444), + MAKE_RGB_FORMAT (xBGR, "raw video", DPTH888, PSTR444, PLANE0, OFFS321, + SUB444), + MAKE_RGBA_FORMAT (RGBA, "raw video", DPTH8888, PSTR4444, PLANE0, OFFS0123, + SUB4444), + MAKE_RGBA_FORMAT (BGRA, "raw video", DPTH8888, PSTR4444, PLANE0, OFFS2103, + SUB4444), + MAKE_RGBA_FORMAT (ARGB, "raw video", DPTH8888, PSTR4444, PLANE0, OFFS1230, + SUB4444), + MAKE_RGBA_FORMAT (ABGR, "raw video", DPTH8888, PSTR4444, PLANE0, OFFS3210, + SUB4444), + MAKE_RGB_FORMAT (RGB, "raw video", DPTH888, PSTR333, PLANE0, OFFS012, SUB444), + MAKE_RGB_FORMAT (BGR, "raw video", DPTH888, PSTR333, PLANE0, OFFS210, SUB444), + + MAKE_YUV_FORMAT (Y41B, "raw video", GST_MAKE_FOURCC ('Y', '4', '1', 'B'), + DPTH888, PSTR111, + PLANE012, OFFS0, SUB411), + MAKE_YUV_FORMAT (Y42B, "raw video", GST_MAKE_FOURCC ('Y', '4', '2', 'B'), + DPTH888, PSTR111, + PLANE012, OFFS0, SUB422), + MAKE_YUV_FORMAT (YVYU, "raw video", GST_MAKE_FOURCC ('Y', 'V', 'Y', 'U'), + DPTH888, PSTR244, + PLANE0, OFFS031, SUB422), + MAKE_YUV_FORMAT (Y444, "raw video", GST_MAKE_FOURCC ('Y', '4', '4', '4'), + DPTH888, PSTR111, + PLANE012, OFFS0, SUB444), + MAKE_YUV_FORMAT (v210, "raw video", GST_MAKE_FOURCC ('v', '2', '1', '0'), + DPTH10_10_10, + PSTR0, PLANE0, OFFS0, SUB422), + MAKE_YUV_FORMAT (v216, "raw video", GST_MAKE_FOURCC ('v', '2', '1', '6'), + DPTH16_16_16, + PSTR488, PLANE0, OFFS026, SUB422), + MAKE_YUV_FORMAT (NV12, "raw video", GST_MAKE_FOURCC ('N', 'V', '1', '2'), + DPTH888, PSTR122, + PLANE011, OFFS001, SUB420), + MAKE_YUV_FORMAT (NV21, "raw video", GST_MAKE_FOURCC ('N', 'V', '2', '1'), + DPTH888, PSTR122, + PLANE011, OFFS010, SUB420), + + MAKE_GRAY_FORMAT (GRAY8, "raw video", DPTH8, PSTR1, PLANE0, OFFS0, SUB4), + MAKE_GRAY_FORMAT (GRAY16_BE, "raw video", DPTH16, PSTR2, PLANE0, OFFS0, SUB4), + MAKE_GRAY_LE_FORMAT (GRAY16_LE, "raw video", DPTH16, PSTR2, PLANE0, OFFS0, + SUB4), + + MAKE_YUV_FORMAT (v308, "raw video", GST_MAKE_FOURCC ('v', '3', '0', '8'), + DPTH888, PSTR333, + PLANE0, OFFS012, SUB444), + MAKE_YUV_FORMAT (Y800, "raw video", GST_MAKE_FOURCC ('Y', '8', '0', '0'), + DPTH8, PSTR1, + PLANE0, OFFS0, SUB4), + MAKE_YUV_FORMAT (Y16, "raw video", GST_MAKE_FOURCC ('Y', '1', '6', ' '), + DPTH16, PSTR2, + PLANE0, OFFS0, SUB4), + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + MAKE_RGB_LE_FORMAT (RGB16, "raw video", DPTH565, PSTR222, PLANE0, OFFS0, + SUB444), + MAKE_RGB_LE_FORMAT (BGR16, "raw video", DPTH565, PSTR222, PLANE0, OFFS0, + SUB444), + MAKE_RGB_LE_FORMAT (RGB15, "raw video", DPTH555, PSTR222, PLANE0, OFFS0, + SUB444), + MAKE_RGB_LE_FORMAT (BGR15, "raw video", DPTH555, PSTR222, PLANE0, OFFS0, + SUB444), +#else + MAKE_RGB_FORMAT (RGB16, "raw video", DPTH565, PSTR222, PLANE0, OFFS0, SUB444), + MAKE_RGB_FORMAT (BGR16, "raw video", DPTH565, PSTR222, PLANE0, OFFS0, SUB444), + MAKE_RGB_FORMAT (RGB15, "raw video", DPTH555, PSTR222, PLANE0, OFFS0, SUB444), + MAKE_RGB_FORMAT (BGR15, "raw video", DPTH555, PSTR222, PLANE0, OFFS0, SUB444), +#endif + + MAKE_YUV_FORMAT (UYVP, "raw video", GST_MAKE_FOURCC ('U', 'Y', 'V', 'P'), + DPTH10_10_10, + PSTR0, PLANE0, OFFS0, SUB422), + MAKE_YUVA_FORMAT (A420, "raw video", GST_MAKE_FOURCC ('A', '4', '2', '0'), + DPTH8888, + PSTR1111, PLANE0123, OFFS0, SUB4204), + MAKE_RGBA_FORMAT (RGB8_PALETTED, "raw video", DPTH8888, PSTR1111, PLANE0, + OFFS0, SUB4444), + MAKE_YUV_FORMAT (YUV9, "raw video", GST_MAKE_FOURCC ('Y', 'U', 'V', '9'), + DPTH888, PSTR111, + PLANE012, OFFS0, SUB410), + MAKE_YUV_FORMAT (YVU9, "raw video", GST_MAKE_FOURCC ('Y', 'V', 'U', '9'), + DPTH888, PSTR111, + PLANE021, OFFS0, SUB410), + MAKE_YUV_FORMAT (IYU1, "raw video", GST_MAKE_FOURCC ('I', 'Y', 'U', '1'), + DPTH888, PSTR0, + PLANE0, OFFS104, SUB411), + MAKE_RGBA_FORMAT (ARGB64, "raw video", DPTH16_16_16_16, PSTR8888, PLANE0, + OFFS2460, + SUB444), + MAKE_YUVA_FORMAT (AYUV64, "raw video", 0x00000000, DPTH16_16_16_16, PSTR8888, + PLANE0, + OFFS2460, SUB444), + MAKE_YUV_FORMAT (r210, "raw video", GST_MAKE_FOURCC ('r', '2', '1', '0'), + DPTH10_10_10, + PSTR444, PLANE0, OFFS0, SUB444), +}; + /** * gst_video_frame_rate: * @pad: pointer to a #GstPad @@ -899,54 +1112,645 @@ gst_video_format_to_fourcc (GstVideoFormat format) { g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, 0); - switch (format) { - case GST_VIDEO_FORMAT_I420: - return GST_MAKE_FOURCC ('I', '4', '2', '0'); - case GST_VIDEO_FORMAT_YV12: - return GST_MAKE_FOURCC ('Y', 'V', '1', '2'); - case GST_VIDEO_FORMAT_YUY2: - return GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'); - case GST_VIDEO_FORMAT_YVYU: - return GST_MAKE_FOURCC ('Y', 'V', 'Y', 'U'); - case GST_VIDEO_FORMAT_UYVY: - return GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'); - case GST_VIDEO_FORMAT_AYUV: - return GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'); - case GST_VIDEO_FORMAT_Y41B: - return GST_MAKE_FOURCC ('Y', '4', '1', 'B'); - case GST_VIDEO_FORMAT_Y42B: - return GST_MAKE_FOURCC ('Y', '4', '2', 'B'); - case GST_VIDEO_FORMAT_Y444: - return GST_MAKE_FOURCC ('Y', '4', '4', '4'); - case GST_VIDEO_FORMAT_v210: - return GST_MAKE_FOURCC ('v', '2', '1', '0'); - case GST_VIDEO_FORMAT_v216: - return GST_MAKE_FOURCC ('v', '2', '1', '6'); - case GST_VIDEO_FORMAT_NV12: - return GST_MAKE_FOURCC ('N', 'V', '1', '2'); - case GST_VIDEO_FORMAT_NV21: - return GST_MAKE_FOURCC ('N', 'V', '2', '1'); - case GST_VIDEO_FORMAT_v308: - return GST_MAKE_FOURCC ('v', '3', '0', '8'); - case GST_VIDEO_FORMAT_Y800: - return GST_MAKE_FOURCC ('Y', '8', '0', '0'); - case GST_VIDEO_FORMAT_Y16: - return GST_MAKE_FOURCC ('Y', '1', '6', ' '); - case GST_VIDEO_FORMAT_UYVP: - return GST_MAKE_FOURCC ('U', 'Y', 'V', 'P'); - case GST_VIDEO_FORMAT_A420: - return GST_MAKE_FOURCC ('A', '4', '2', '0'); - case GST_VIDEO_FORMAT_YUV9: - return GST_MAKE_FOURCC ('Y', 'U', 'V', '9'); - case GST_VIDEO_FORMAT_YVU9: - return GST_MAKE_FOURCC ('Y', 'V', 'U', '9'); - case GST_VIDEO_FORMAT_IYU1: - return GST_MAKE_FOURCC ('I', 'Y', 'U', '1'); - case GST_VIDEO_FORMAT_AYUV64: - return GST_MAKE_FOURCC ('A', 'Y', '6', '4'); - default: - return 0; + if (format >= G_N_ELEMENTS (formats)) + return 0; + + return formats[format].fourcc; +} + +const gchar * +gst_video_format_to_string (GstVideoFormat format) +{ + g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, NULL); + + if (format >= G_N_ELEMENTS (formats)) + return NULL; + + return GST_VIDEO_FORMAT_INFO_NAME (&formats[format].info); +} + +/** + * gst_video_format_get_info: + * @format: a #GstVideoFormat + * + * Get the #GstVideoFormatInfo for @format + * + * Returns: The #GstVideoFormatInfo for @format. + */ +const GstVideoFormatInfo * +gst_video_format_get_info (GstVideoFormat format) +{ + g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, NULL); + g_return_val_if_fail (format < G_N_ELEMENTS (formats), NULL); + + return &formats[format].info; +} + +/** + * gst_video_info_init: + * @info: a #GstVideoInfo + * + * Initialize @info with default values. + */ +void +gst_video_info_init (GstVideoInfo * info) +{ + g_return_if_fail (info != NULL); + + memset (info, 0, sizeof (GstVideoInfo)); + + info->finfo = &formats[GST_VIDEO_FORMAT_UNKNOWN].info; + + /* arrange for sensible defaults, e.g. if turned into caps */ + info->fps_n = 0; + info->fps_d = 1; + info->par_n = 1; + info->par_d = 1; +} + +/** + * gst_video_info_set_format: + * @info: a #GstVideoInfo + * @format: the format + * @width: a width + * @height: a height + * + * Set the default info for a video frame of @format and @width and @height. + */ +void +gst_video_info_set_format (GstVideoInfo * info, GstVideoFormat format, + guint width, guint height) +{ + const GstVideoFormatInfo *finfo; + + g_return_if_fail (info != NULL); + g_return_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN); + + finfo = &formats[format].info; + + info->flags = 0; + info->finfo = finfo; + info->width = width; + info->height = height; + + fill_planes (info); +} + +#if 0 +static const gchar *interlace_mode[] = { + "progressive", + "interleaved", + "mixed", + "fields" +}; + +static const gchar * +gst_interlace_mode_to_string (GstVideoInterlaceMode mode) +{ + if (mode < 0 || mode >= G_N_ELEMENTS (interlace_mode)) + return NULL; + + return interlace_mode[mode]; +} + +static GstVideoInterlaceMode +gst_interlace_mode_from_string (const gchar * mode) +{ + gint i; + for (i = 0; i < G_N_ELEMENTS (interlace_mode); i++) { + if (g_str_equal (interlace_mode[i], mode)) + return i; } + return GST_VIDEO_INTERLACE_MODE_PROGRESSIVE; +} +#endif + +typedef struct +{ + const gchar *name; + GstVideoChromaSite site; +} ChromaSiteInfo; + +static const ChromaSiteInfo chromasite[] = { + {"jpeg", GST_VIDEO_CHROMA_SITE_JPEG}, + {"mpeg2", GST_VIDEO_CHROMA_SITE_MPEG2}, + {"dv", GST_VIDEO_CHROMA_SITE_DV} +}; + +static GstVideoChromaSite +gst_video_chroma_from_string (const gchar * s) +{ + gint i; + for (i = 0; i < G_N_ELEMENTS (chromasite); i++) { + if (g_str_equal (chromasite[i].name, s)) + return chromasite[i].site; + } + return GST_VIDEO_CHROMA_SITE_UNKNOWN; +} + +static const gchar * +gst_video_chroma_to_string (GstVideoChromaSite site) +{ + gint i; + for (i = 0; i < G_N_ELEMENTS (chromasite); i++) { + if (chromasite[i].site == site) + return chromasite[i].name; + } + return NULL; +} + +typedef struct +{ + const gchar *name; + GstVideoColorimetry color; +} ColorimetryInfo; + +#define MAKE_COLORIMETRY(n,r,m,t,p) { GST_VIDEO_COLORIMETRY_ ##n, \ + { GST_VIDEO_COLOR_RANGE ##r, GST_VIDEO_COLOR_MATRIX_ ##m, \ + GST_VIDEO_TRANSFER_ ##t, GST_VIDEO_COLOR_PRIMARIES_ ##p } } + +static const ColorimetryInfo colorimetry[] = { + MAKE_COLORIMETRY (BT601, _16_235, BT601, BT709, BT470M), + MAKE_COLORIMETRY (BT709, _16_235, BT709, BT709, BT709), + MAKE_COLORIMETRY (SMPTE240M, _16_235, SMPTE240M, SMPTE240M, SMPTE240M), +}; + +static const ColorimetryInfo * +gst_video_get_colorimetry (const gchar * s) +{ + gint i; + + for (i = 0; i < G_N_ELEMENTS (colorimetry); i++) { + if (g_str_equal (colorimetry[i].name, s)) + return &colorimetry[i]; + } + return NULL; +} + +#define IS_EQUAL(ci,i) (((ci)->color.range == (i)->range) && \ + ((ci)->color.matrix == (i)->matrix) && \ + ((ci)->color.transfer == (i)->transfer) && \ + ((ci)->color.primaries == (i)->primaries)) + + +/** + * gst_video_colorimetry_from_string + * @cinfo: a #GstVideoColorimetry + * @color: a colorimetry string + * + * Parse the colorimetry string and update @cinfo with the parsed + * values. + * + * Returns: #TRUE if @color points to valid colorimetry info. + */ +gboolean +gst_video_colorimetry_from_string (GstVideoColorimetry * cinfo, + const gchar * color) +{ + const ColorimetryInfo *ci; + + if ((ci = gst_video_get_colorimetry (color))) { + *cinfo = ci->color; + } else { + /* FIXME, split and parse */ + cinfo->range = GST_VIDEO_COLOR_RANGE_16_235; + cinfo->matrix = GST_VIDEO_COLOR_MATRIX_BT601; + cinfo->transfer = GST_VIDEO_TRANSFER_BT709; + cinfo->primaries = GST_VIDEO_COLOR_PRIMARIES_BT709; + } + return TRUE; +} + +static void +gst_video_caps_set_colorimetry (GstCaps * caps, GstVideoColorimetry * cinfo) +{ + gint i; + + for (i = 0; i < G_N_ELEMENTS (colorimetry); i++) { + if (IS_EQUAL (&colorimetry[i], cinfo)) { + gst_caps_set_simple (caps, "colorimetry", G_TYPE_STRING, + colorimetry[i].name, NULL); + return; + } + } + /* FIXME, construct colorimetry */ +} + +/** + * gst_video_colorimetry_matches: + * @cinfo: a #GstVideoInfo + * @color: a colorimetry string + * + * Check if the colorimetry information in @cinfo matches that of the + * string @color. + * + * Returns: #TRUE if @color conveys the same colorimetry info as the color + * information in @info. + */ +gboolean +gst_video_colorimetry_matches (GstVideoColorimetry * cinfo, const gchar * color) +{ + const ColorimetryInfo *ci; + + if ((ci = gst_video_get_colorimetry (color))) + return IS_EQUAL (ci, cinfo); + + return FALSE; +} + +/** + * gst_video_info_from_caps: + * @info: a #GstVideoInfo + * @caps: a #GstCaps + * + * Parse @caps and update @info. + * + * Returns: TRUE if @caps could be parsed + */ +gboolean +gst_video_info_from_caps (GstVideoInfo * info, const GstCaps * caps) +{ + GstStructure *structure; + const gchar *s; + GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN; + gint width, height; + gint fps_n, fps_d; + gint par_n, par_d; + gboolean interlaced; + + g_return_val_if_fail (info != NULL, FALSE); + g_return_val_if_fail (caps != NULL, FALSE); + g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); + + GST_DEBUG ("parsing caps %" GST_PTR_FORMAT, caps); + + structure = gst_caps_get_structure (caps, 0); + + if (gst_structure_has_name (structure, "video/x-raw-yuv")) { + guint32 fourcc; + + if (!gst_structure_get_fourcc (structure, "format", &fourcc)) + goto no_format; + + format = gst_video_format_from_fourcc (fourcc); + } else if (gst_structure_has_name (structure, "video/x-raw-rgb")) { + int depth; + int bpp; + int endianness = 0; + int red_mask = 0; + int green_mask = 0; + int blue_mask = 0; + int alpha_mask = 0; + + if (!gst_structure_get_int (structure, "depth", &depth) || + !gst_structure_get_int (structure, "bpp", &bpp)) + goto no_bpp_depth; + + if (bpp != 8) { + gst_structure_get_int (structure, "endianness", &endianness); + gst_structure_get_int (structure, "red_mask", &red_mask); + gst_structure_get_int (structure, "green_mask", &green_mask); + gst_structure_get_int (structure, "blue_mask", &blue_mask); + } + gst_structure_get_int (structure, "alpha_mask", &alpha_mask); + format = gst_video_format_from_masks (depth, bpp, endianness, + red_mask, green_mask, blue_mask, alpha_mask); + } else if (gst_structure_has_name (structure, "video/x-raw-gray")) { + int depth; + int bpp; + int endianness; + + if (!gst_structure_get_int (structure, "depth", &depth) || + !gst_structure_get_int (structure, "bpp", &bpp)) + goto no_bpp_depth; + + /* endianness is mandatory for bpp > 8 */ + if (bpp > 8 && + !gst_structure_get_int (structure, "endianness", &endianness)) + goto no_endianess; + + if (depth == 8 && bpp == 8) { + format = GST_VIDEO_FORMAT_GRAY8; + } else if (depth == 16 && bpp == 16 && endianness == G_BIG_ENDIAN) { + format = GST_VIDEO_FORMAT_GRAY16_BE; + } else if (depth == 16 && bpp == 16 && endianness == G_LITTLE_ENDIAN) { + format = GST_VIDEO_FORMAT_GRAY16_LE; + } + } + + if (format == GST_VIDEO_FORMAT_UNKNOWN) + goto unknown_format; + + if (!gst_structure_get_int (structure, "width", &width)) + goto no_width; + if (!gst_structure_get_int (structure, "height", &height)) + goto no_height; + + gst_video_info_set_format (info, format, width, height); + + if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)) { + if (fps_n == 0) { + /* variable framerate */ + info->flags |= GST_VIDEO_FLAG_VARIABLE_FPS; + /* see if we have a max-framerate */ + gst_structure_get_fraction (structure, "max-framerate", &fps_n, &fps_d); + } + info->fps_n = fps_n; + info->fps_d = fps_d; + } else { + /* unspecified is variable framerate */ + info->fps_n = 0; + info->fps_d = 1; + } + + if (gst_structure_get_boolean (structure, "interlaced", &interlaced) + && interlaced) + info->interlace_mode = GST_VIDEO_INTERLACE_MODE_INTERLEAVED; + else + info->interlace_mode = GST_VIDEO_INTERLACE_MODE_PROGRESSIVE; + + if ((s = gst_structure_get_string (structure, "chroma-site"))) + info->chroma_site = gst_video_chroma_from_string (s); + else + info->chroma_site = GST_VIDEO_CHROMA_SITE_UNKNOWN; + + if ((s = gst_structure_get_string (structure, "colorimetry"))) + gst_video_colorimetry_from_string (&info->colorimetry, s); + else + memset (&info->colorimetry, 0, sizeof (GstVideoColorimetry)); + + if (gst_structure_get_fraction (structure, "pixel-aspect-ratio", + &par_n, &par_d)) { + info->par_n = par_n; + info->par_d = par_d; + } else { + info->par_n = 1; + info->par_d = 1; + } + return TRUE; + + /* ERROR */ +no_format: + { + GST_ERROR ("no format given"); + return FALSE; + } +unknown_format: + { + GST_ERROR ("unknown format"); + return FALSE; + } +no_width: + { + GST_ERROR ("no width property given"); + return FALSE; + } +no_height: + { + GST_ERROR ("no height property given"); + return FALSE; + } + +no_bpp_depth: + { + GST_ERROR ("no bpp or depth given"); + return FALSE; + } + +no_endianess: + { + GST_ERROR ("no endianness given"); + return FALSE; + } +} + +/** + * gst_video_info_to_caps: + * @info: a #GstVideoInfo + * + * Convert the values of @info into a #GstCaps. + * + * Returns: a new #GstCaps containing the info of @info. + */ +GstCaps * +gst_video_info_to_caps (GstVideoInfo * info) +{ + GstCaps *caps; + const gchar *capsname = NULL; + + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (info->finfo != NULL, NULL); + g_return_val_if_fail (info->finfo->format != GST_VIDEO_FORMAT_UNKNOWN, NULL); + + if (GST_VIDEO_INFO_IS_YUV (info)) + capsname = "video/x-raw-yuv"; + else if (GST_VIDEO_INFO_IS_RGB (info)) + capsname = "video/x-raw-rgb"; + else if (GST_VIDEO_INFO_IS_GRAY (info)) + capsname = "video/x-raw-gray"; + + caps = gst_caps_new_simple (capsname, + "width", G_TYPE_INT, info->width, + "height", G_TYPE_INT, info->height, + "pixel-aspect-ratio", GST_TYPE_FRACTION, info->par_n, info->par_d, NULL); + + if (GST_VIDEO_INFO_IS_YUV (info)) + gst_caps_set_simple (caps, "format", GST_TYPE_FOURCC, + gst_video_format_to_fourcc (info->finfo->format), NULL); + + gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN, + GST_VIDEO_INFO_IS_INTERLACED (info), NULL); + + if (info->chroma_site != GST_VIDEO_CHROMA_SITE_UNKNOWN) + gst_caps_set_simple (caps, "chroma-site", G_TYPE_STRING, + gst_video_chroma_to_string (info->chroma_site), NULL); + + gst_video_caps_set_colorimetry (caps, &info->colorimetry); + + if (info->flags & GST_VIDEO_FLAG_VARIABLE_FPS && info->fps_n != 0) { + /* variable fps with a max-framerate */ + gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, 0, 1, + "max-framerate", GST_TYPE_FRACTION, info->fps_n, info->fps_d, NULL); + } else { + /* no variable fps or no max-framerate */ + gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, + info->fps_n, info->fps_d, NULL); + } + + return caps; +} + + +static int +fill_planes (GstVideoInfo * info) +{ + gint width, height; + + width = info->width; + height = info->height; + + switch (info->finfo->format) { + case GST_VIDEO_FORMAT_YUY2: + case GST_VIDEO_FORMAT_YVYU: + case GST_VIDEO_FORMAT_UYVY: + info->stride[0] = GST_ROUND_UP_4 (width * 2); + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_AYUV: + case GST_VIDEO_FORMAT_RGBx: + case GST_VIDEO_FORMAT_RGBA: + case GST_VIDEO_FORMAT_BGRx: + case GST_VIDEO_FORMAT_BGRA: + case GST_VIDEO_FORMAT_xRGB: + case GST_VIDEO_FORMAT_ARGB: + case GST_VIDEO_FORMAT_xBGR: + case GST_VIDEO_FORMAT_ABGR: + case GST_VIDEO_FORMAT_r210: + info->stride[0] = width * 4; + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_RGB16: + case GST_VIDEO_FORMAT_BGR16: + case GST_VIDEO_FORMAT_RGB15: + case GST_VIDEO_FORMAT_BGR15: + info->stride[0] = GST_ROUND_UP_4 (width * 2); + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_RGB: + case GST_VIDEO_FORMAT_BGR: + case GST_VIDEO_FORMAT_v308: + info->stride[0] = GST_ROUND_UP_4 (width * 3); + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_v210: + info->stride[0] = ((width + 47) / 48) * 128; + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_v216: + info->stride[0] = GST_ROUND_UP_8 (width * 4); + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_GRAY8: + case GST_VIDEO_FORMAT_Y800: + info->stride[0] = GST_ROUND_UP_4 (width); + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_GRAY16_BE: + case GST_VIDEO_FORMAT_GRAY16_LE: + case GST_VIDEO_FORMAT_Y16: + info->stride[0] = GST_ROUND_UP_4 (width * 2); + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_UYVP: + info->stride[0] = GST_ROUND_UP_4 ((width * 2 * 5 + 3) / 4); + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_RGB8_PALETTED: + info->stride[0] = GST_ROUND_UP_4 (width); + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_IYU1: + info->stride[0] = GST_ROUND_UP_4 (GST_ROUND_UP_4 (width) + + GST_ROUND_UP_4 (width) / 2); + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_ARGB64: + case GST_VIDEO_FORMAT_AYUV64: + info->stride[0] = width * 8; + info->offset[0] = 0; + info->size = info->stride[0] * height; + break; + case GST_VIDEO_FORMAT_I420: + case GST_VIDEO_FORMAT_YV12: /* same as I420, but plane 1+2 swapped */ + info->stride[0] = GST_ROUND_UP_4 (width); + info->stride[1] = GST_ROUND_UP_4 (GST_ROUND_UP_2 (width) / 2); + info->stride[2] = info->stride[1]; + info->offset[0] = 0; + info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); + info->offset[2] = info->offset[1] + + info->stride[1] * (GST_ROUND_UP_2 (height) / 2); + info->size = info->offset[2] + + info->stride[2] * (GST_ROUND_UP_2 (height) / 2); + break; + case GST_VIDEO_FORMAT_Y41B: + info->stride[0] = GST_ROUND_UP_4 (width); + info->stride[1] = GST_ROUND_UP_16 (width) / 4; + info->stride[2] = info->stride[1]; + info->offset[0] = 0; + info->offset[1] = info->stride[0] * height; + info->offset[2] = info->offset[1] + info->stride[1] * height; + /* simplification of ROUNDUP4(w)*h + 2*((ROUNDUP16(w)/4)*h */ + info->size = (info->stride[0] + (GST_ROUND_UP_16 (width) / 2)) * height; + break; + case GST_VIDEO_FORMAT_Y42B: + info->stride[0] = GST_ROUND_UP_4 (width); + info->stride[1] = GST_ROUND_UP_8 (width) / 2; + info->stride[2] = info->stride[1]; + info->offset[0] = 0; + info->offset[1] = info->stride[0] * height; + info->offset[2] = info->offset[1] + info->stride[1] * height; + /* simplification of ROUNDUP4(w)*h + 2*(ROUNDUP8(w)/2)*h */ + info->size = (info->stride[0] + GST_ROUND_UP_8 (width)) * height; + break; + case GST_VIDEO_FORMAT_Y444: + info->stride[0] = GST_ROUND_UP_4 (width); + info->stride[1] = info->stride[0]; + info->stride[2] = info->stride[0]; + info->offset[0] = 0; + info->offset[1] = info->stride[0] * height; + info->offset[2] = info->offset[1] * 2; + info->size = info->stride[0] * height * 3; + break; + case GST_VIDEO_FORMAT_NV12: + case GST_VIDEO_FORMAT_NV21: + info->stride[0] = GST_ROUND_UP_4 (width); + info->stride[1] = info->stride[0]; + info->offset[0] = 0; + info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); + info->size = info->stride[0] * GST_ROUND_UP_2 (height) * 3 / 2; + break; + case GST_VIDEO_FORMAT_A420: + info->stride[0] = GST_ROUND_UP_4 (width); + info->stride[1] = GST_ROUND_UP_4 (GST_ROUND_UP_2 (width) / 2); + info->stride[2] = info->stride[1]; + info->stride[3] = info->stride[0]; + info->offset[0] = 0; + info->offset[1] = info->stride[0] * GST_ROUND_UP_2 (height); + info->offset[2] = info->offset[1] + + info->stride[1] * (GST_ROUND_UP_2 (height) / 2); + info->offset[3] = info->offset[2] + + info->stride[2] * (GST_ROUND_UP_2 (height) / 2); + info->size = info->offset[3] + info->stride[0]; + break; + case GST_VIDEO_FORMAT_YUV9: + case GST_VIDEO_FORMAT_YVU9: + info->stride[0] = GST_ROUND_UP_4 (width); + info->stride[1] = GST_ROUND_UP_4 (GST_ROUND_UP_4 (width) / 4); + info->stride[2] = info->stride[1]; + info->offset[0] = 0; + info->offset[1] = info->stride[0] * height; + info->offset[2] = info->offset[1] + + info->stride[1] * (GST_ROUND_UP_4 (height) / 4); + info->size = info->offset[2] + + info->stride[2] * (GST_ROUND_UP_4 (height) / 4); + break; + case GST_VIDEO_FORMAT_UNKNOWN: + GST_ERROR ("invalid format"); + g_warning ("invalid format"); + break; + } + return 0; } /* @@ -1049,6 +1853,72 @@ gst_video_format_from_rgb16_masks (int red_mask, int green_mask, int blue_mask) return GST_VIDEO_FORMAT_UNKNOWN; } +/** + * gst_video_format_from_masks: + * @depth: the amount of bits used for a pixel + * @bpp: the amount of bits used to store a pixel. This value is bigger than + * @depth + * @endianness: the endianness of the masks + * @red_mask: the red mask + * @green_mask: the green mask + * @blue_mask: the blue mask + * @alpha_mask: the optional alpha mask + * + * Find the #GstVideoFormat for the given parameters. + * + * Returns: a #GstVideoFormat or GST_VIDEO_FORMAT_UNKNOWN when the parameters to + * not specify a known format. + */ +GstVideoFormat +gst_video_format_from_masks (gint depth, gint bpp, gint endianness, + gint red_mask, gint green_mask, gint blue_mask, gint alpha_mask) +{ + GstVideoFormat format; + + /* our caps system handles 24/32bpp RGB as big-endian. */ + if ((bpp == 24 || bpp == 32) && endianness == G_LITTLE_ENDIAN) { + red_mask = GUINT32_TO_BE (red_mask); + green_mask = GUINT32_TO_BE (green_mask); + blue_mask = GUINT32_TO_BE (blue_mask); + endianness = G_BIG_ENDIAN; + if (bpp == 24) { + red_mask >>= 8; + green_mask >>= 8; + blue_mask >>= 8; + } + } + + if (depth == 30 && bpp == 32) { + format = GST_VIDEO_FORMAT_r210; + } else if (depth == 24 && bpp == 32) { + format = gst_video_format_from_rgb32_masks (red_mask, green_mask, + blue_mask); + } else if (depth == 32 && bpp == 32 && alpha_mask) { + format = gst_video_format_from_rgba32_masks (red_mask, green_mask, + blue_mask, alpha_mask); + } else if (depth == 24 && bpp == 24) { + format = gst_video_format_from_rgb24_masks (red_mask, green_mask, + blue_mask); + } else if ((depth == 15 || depth == 16) && bpp == 16 && + endianness == G_BYTE_ORDER) { + format = gst_video_format_from_rgb16_masks (red_mask, green_mask, + blue_mask); + } else if (depth == 8 && bpp == 8) { + format = GST_VIDEO_FORMAT_RGB8_PALETTED; + } else if (depth == 64 && bpp == 64) { + format = gst_video_format_from_rgba32_masks (red_mask, green_mask, + blue_mask, alpha_mask); + if (format == GST_VIDEO_FORMAT_ARGB) { + format = GST_VIDEO_FORMAT_ARGB64; + } else { + format = GST_VIDEO_FORMAT_UNKNOWN; + } + } else { + format = GST_VIDEO_FORMAT_UNKNOWN; + } + return format; +} + /** * gst_video_format_is_rgb: * @format: a #GstVideoFormat @@ -1057,54 +1927,19 @@ gst_video_format_from_rgb16_masks (int red_mask, int green_mask, int blue_mask) * * Since: 0.10.16 * + * Deprecated: Use #GstVideoFormatInfo and #GST_VIDEO_FORMAT_INFO_IS_RGB + * * Returns: TRUE if @format represents RGB video */ gboolean gst_video_format_is_rgb (GstVideoFormat format) { - switch (format) { - case GST_VIDEO_FORMAT_I420: - case GST_VIDEO_FORMAT_YV12: - case GST_VIDEO_FORMAT_YUY2: - case GST_VIDEO_FORMAT_YVYU: - case GST_VIDEO_FORMAT_UYVY: - case GST_VIDEO_FORMAT_AYUV: - case GST_VIDEO_FORMAT_Y41B: - case GST_VIDEO_FORMAT_Y42B: - case GST_VIDEO_FORMAT_Y444: - case GST_VIDEO_FORMAT_v210: - case GST_VIDEO_FORMAT_v216: - case GST_VIDEO_FORMAT_NV12: - case GST_VIDEO_FORMAT_NV21: - case GST_VIDEO_FORMAT_v308: - case GST_VIDEO_FORMAT_UYVP: - case GST_VIDEO_FORMAT_A420: - case GST_VIDEO_FORMAT_YUV9: - case GST_VIDEO_FORMAT_YVU9: - case GST_VIDEO_FORMAT_IYU1: - case GST_VIDEO_FORMAT_AYUV64: - return FALSE; - case GST_VIDEO_FORMAT_RGBx: - case GST_VIDEO_FORMAT_BGRx: - case GST_VIDEO_FORMAT_xRGB: - case GST_VIDEO_FORMAT_xBGR: - case GST_VIDEO_FORMAT_RGBA: - case GST_VIDEO_FORMAT_BGRA: - case GST_VIDEO_FORMAT_ARGB: - case GST_VIDEO_FORMAT_ABGR: - case GST_VIDEO_FORMAT_RGB: - case GST_VIDEO_FORMAT_BGR: - case GST_VIDEO_FORMAT_RGB16: - case GST_VIDEO_FORMAT_BGR16: - case GST_VIDEO_FORMAT_RGB15: - case GST_VIDEO_FORMAT_BGR15: - case GST_VIDEO_FORMAT_RGB8_PALETTED: - case GST_VIDEO_FORMAT_ARGB64: - case GST_VIDEO_FORMAT_r210: - return TRUE; - default: - return FALSE; - } + g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, 0); + + if (format >= G_N_ELEMENTS (formats)) + return FALSE; + + return GST_VIDEO_FORMAT_INFO_IS_RGB (&formats[format].info); } /** @@ -1115,56 +1950,19 @@ gst_video_format_is_rgb (GstVideoFormat format) * * Since: 0.10.16 * + * Deprecated: Use #GstVideoFormatInfo and #GST_VIDEO_FORMAT_INFO_IS_YUV + * * Returns: TRUE if @format represents YUV video */ gboolean gst_video_format_is_yuv (GstVideoFormat format) { - switch (format) { - case GST_VIDEO_FORMAT_I420: - case GST_VIDEO_FORMAT_YV12: - case GST_VIDEO_FORMAT_YUY2: - case GST_VIDEO_FORMAT_YVYU: - case GST_VIDEO_FORMAT_UYVY: - case GST_VIDEO_FORMAT_AYUV: - case GST_VIDEO_FORMAT_Y41B: - case GST_VIDEO_FORMAT_Y42B: - case GST_VIDEO_FORMAT_Y444: - case GST_VIDEO_FORMAT_v210: - case GST_VIDEO_FORMAT_v216: - case GST_VIDEO_FORMAT_NV12: - case GST_VIDEO_FORMAT_NV21: - case GST_VIDEO_FORMAT_v308: - case GST_VIDEO_FORMAT_Y800: - case GST_VIDEO_FORMAT_Y16: - case GST_VIDEO_FORMAT_UYVP: - case GST_VIDEO_FORMAT_A420: - case GST_VIDEO_FORMAT_YUV9: - case GST_VIDEO_FORMAT_YVU9: - case GST_VIDEO_FORMAT_IYU1: - case GST_VIDEO_FORMAT_AYUV64: - return TRUE; - case GST_VIDEO_FORMAT_RGBx: - case GST_VIDEO_FORMAT_BGRx: - case GST_VIDEO_FORMAT_xRGB: - case GST_VIDEO_FORMAT_xBGR: - case GST_VIDEO_FORMAT_RGBA: - case GST_VIDEO_FORMAT_BGRA: - case GST_VIDEO_FORMAT_ARGB: - case GST_VIDEO_FORMAT_ABGR: - case GST_VIDEO_FORMAT_RGB: - case GST_VIDEO_FORMAT_BGR: - case GST_VIDEO_FORMAT_RGB16: - case GST_VIDEO_FORMAT_BGR16: - case GST_VIDEO_FORMAT_RGB15: - case GST_VIDEO_FORMAT_BGR15: - case GST_VIDEO_FORMAT_RGB8_PALETTED: - case GST_VIDEO_FORMAT_ARGB64: - case GST_VIDEO_FORMAT_r210: - return FALSE; - default: - return FALSE; - } + g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, 0); + + if (format >= G_N_ELEMENTS (formats)) + return FALSE; + + return GST_VIDEO_FORMAT_INFO_IS_YUV (&formats[format].info); } /** @@ -1175,21 +1973,19 @@ gst_video_format_is_yuv (GstVideoFormat format) * * Since: 0.10.29 * + * Deprecated: Use #GstVideoFormatInfo and #GST_VIDEO_FORMAT_INFO_IS_GRAY + * * Returns: TRUE if @format represents grayscale video */ gboolean gst_video_format_is_gray (GstVideoFormat format) { - switch (format) { - case GST_VIDEO_FORMAT_GRAY8: - case GST_VIDEO_FORMAT_GRAY16_BE: - case GST_VIDEO_FORMAT_GRAY16_LE: - case GST_VIDEO_FORMAT_Y800: - case GST_VIDEO_FORMAT_Y16: - return TRUE; - default: - return FALSE; - } + g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, 0); + + if (format >= G_N_ELEMENTS (formats)) + return FALSE; + + return GST_VIDEO_FORMAT_INFO_IS_GRAY (&formats[format].info); } /** @@ -1201,57 +1997,19 @@ gst_video_format_is_gray (GstVideoFormat format) * * Since: 0.10.16 * + * Deprecated: Use #GstVideoFormatInfo and #GST_VIDEO_FORMAT_INFO_HAS_ALPHA + * * Returns: TRUE if @format has an alpha channel */ gboolean gst_video_format_has_alpha (GstVideoFormat format) { - switch (format) { - case GST_VIDEO_FORMAT_I420: - case GST_VIDEO_FORMAT_YV12: - case GST_VIDEO_FORMAT_YUY2: - case GST_VIDEO_FORMAT_YVYU: - case GST_VIDEO_FORMAT_UYVY: - case GST_VIDEO_FORMAT_Y41B: - case GST_VIDEO_FORMAT_Y42B: - case GST_VIDEO_FORMAT_Y444: - case GST_VIDEO_FORMAT_v210: - case GST_VIDEO_FORMAT_v216: - case GST_VIDEO_FORMAT_NV12: - case GST_VIDEO_FORMAT_NV21: - case GST_VIDEO_FORMAT_v308: - case GST_VIDEO_FORMAT_Y800: - case GST_VIDEO_FORMAT_Y16: - case GST_VIDEO_FORMAT_UYVP: - case GST_VIDEO_FORMAT_YUV9: - case GST_VIDEO_FORMAT_YVU9: - case GST_VIDEO_FORMAT_IYU1: - return FALSE; - case GST_VIDEO_FORMAT_AYUV: - case GST_VIDEO_FORMAT_RGBA: - case GST_VIDEO_FORMAT_BGRA: - case GST_VIDEO_FORMAT_ARGB: - case GST_VIDEO_FORMAT_ABGR: - case GST_VIDEO_FORMAT_A420: - case GST_VIDEO_FORMAT_RGB8_PALETTED: - case GST_VIDEO_FORMAT_ARGB64: - case GST_VIDEO_FORMAT_AYUV64: - return TRUE; - case GST_VIDEO_FORMAT_RGBx: - case GST_VIDEO_FORMAT_BGRx: - case GST_VIDEO_FORMAT_xRGB: - case GST_VIDEO_FORMAT_xBGR: - case GST_VIDEO_FORMAT_RGB: - case GST_VIDEO_FORMAT_BGR: - case GST_VIDEO_FORMAT_RGB16: - case GST_VIDEO_FORMAT_BGR16: - case GST_VIDEO_FORMAT_RGB15: - case GST_VIDEO_FORMAT_BGR15: - case GST_VIDEO_FORMAT_r210: - return FALSE; - default: - return FALSE; - } + g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, 0); + + if (format >= G_N_ELEMENTS (formats)) + return FALSE; + + return GST_VIDEO_FORMAT_INFO_HAS_ALPHA (&formats[format].info); } /** @@ -1270,59 +2028,12 @@ gst_video_format_has_alpha (GstVideoFormat format) int gst_video_format_get_component_depth (GstVideoFormat format, int component) { - if (component == 3 && !gst_video_format_has_alpha (format)) + g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, 0); + + if (format >= G_N_ELEMENTS (formats)) return 0; - switch (format) { - case GST_VIDEO_FORMAT_RGB16: - case GST_VIDEO_FORMAT_BGR16: - if (component == 1) - return 6; - return 5; - case GST_VIDEO_FORMAT_RGB15: - case GST_VIDEO_FORMAT_BGR15: - return 5; - case GST_VIDEO_FORMAT_I420: - case GST_VIDEO_FORMAT_YV12: - case GST_VIDEO_FORMAT_YUY2: - case GST_VIDEO_FORMAT_YVYU: - case GST_VIDEO_FORMAT_UYVY: - case GST_VIDEO_FORMAT_Y41B: - case GST_VIDEO_FORMAT_Y42B: - case GST_VIDEO_FORMAT_Y444: - case GST_VIDEO_FORMAT_NV12: - case GST_VIDEO_FORMAT_NV21: - case GST_VIDEO_FORMAT_v308: - case GST_VIDEO_FORMAT_Y800: - case GST_VIDEO_FORMAT_YUV9: - case GST_VIDEO_FORMAT_YVU9: - case GST_VIDEO_FORMAT_IYU1: - case GST_VIDEO_FORMAT_AYUV: - case GST_VIDEO_FORMAT_RGBA: - case GST_VIDEO_FORMAT_BGRA: - case GST_VIDEO_FORMAT_ARGB: - case GST_VIDEO_FORMAT_ABGR: - case GST_VIDEO_FORMAT_A420: - case GST_VIDEO_FORMAT_RGB8_PALETTED: - case GST_VIDEO_FORMAT_RGBx: - case GST_VIDEO_FORMAT_BGRx: - case GST_VIDEO_FORMAT_xRGB: - case GST_VIDEO_FORMAT_xBGR: - case GST_VIDEO_FORMAT_RGB: - case GST_VIDEO_FORMAT_BGR: - default: - return 8; - case GST_VIDEO_FORMAT_v210: - case GST_VIDEO_FORMAT_UYVP: - case GST_VIDEO_FORMAT_r210: - return 10; - case GST_VIDEO_FORMAT_Y16: - case GST_VIDEO_FORMAT_v216: - case GST_VIDEO_FORMAT_ARGB64: - case GST_VIDEO_FORMAT_AYUV64: - return 16; - } - + return GST_VIDEO_FORMAT_INFO_DEPTH (&formats[format].info, component); } /** @@ -1459,82 +2170,10 @@ gst_video_format_get_pixel_stride (GstVideoFormat format, int component) g_return_val_if_fail (format != GST_VIDEO_FORMAT_UNKNOWN, 0); g_return_val_if_fail (component >= 0 && component <= 3, 0); - switch (format) { - case GST_VIDEO_FORMAT_I420: - case GST_VIDEO_FORMAT_YV12: - case GST_VIDEO_FORMAT_Y41B: - case GST_VIDEO_FORMAT_Y42B: - case GST_VIDEO_FORMAT_Y444: - case GST_VIDEO_FORMAT_A420: - case GST_VIDEO_FORMAT_YUV9: - case GST_VIDEO_FORMAT_YVU9: - return 1; - case GST_VIDEO_FORMAT_YUY2: - case GST_VIDEO_FORMAT_YVYU: - case GST_VIDEO_FORMAT_UYVY: - if (component == 0) { - return 2; - } else { - return 4; - } - case GST_VIDEO_FORMAT_IYU1: - /* doesn't make much sense for IYU1 because it's 1 or 3 - * for luma depending on position */ - return 0; - case GST_VIDEO_FORMAT_AYUV: - case GST_VIDEO_FORMAT_RGBx: - case GST_VIDEO_FORMAT_BGRx: - case GST_VIDEO_FORMAT_xRGB: - case GST_VIDEO_FORMAT_xBGR: - case GST_VIDEO_FORMAT_RGBA: - case GST_VIDEO_FORMAT_BGRA: - case GST_VIDEO_FORMAT_ARGB: - case GST_VIDEO_FORMAT_ABGR: - case GST_VIDEO_FORMAT_r210: - return 4; - case GST_VIDEO_FORMAT_RGB16: - case GST_VIDEO_FORMAT_BGR16: - case GST_VIDEO_FORMAT_RGB15: - case GST_VIDEO_FORMAT_BGR15: - return 2; - case GST_VIDEO_FORMAT_RGB: - case GST_VIDEO_FORMAT_BGR: - case GST_VIDEO_FORMAT_v308: - return 3; - case GST_VIDEO_FORMAT_v210: - /* v210 is packed at the bit level, so pixel stride doesn't make sense */ - return 0; - case GST_VIDEO_FORMAT_v216: - if (component == 0) { - return 4; - } else { - return 8; - } - case GST_VIDEO_FORMAT_NV12: - case GST_VIDEO_FORMAT_NV21: - if (component == 0) { - return 1; - } else { - return 2; - } - case GST_VIDEO_FORMAT_GRAY8: - case GST_VIDEO_FORMAT_Y800: - return 1; - case GST_VIDEO_FORMAT_GRAY16_BE: - case GST_VIDEO_FORMAT_GRAY16_LE: - case GST_VIDEO_FORMAT_Y16: - return 2; - case GST_VIDEO_FORMAT_UYVP: - /* UYVP is packed at the bit level, so pixel stride doesn't make sense */ - return 0; - case GST_VIDEO_FORMAT_RGB8_PALETTED: - return 1; - case GST_VIDEO_FORMAT_ARGB64: - case GST_VIDEO_FORMAT_AYUV64: - return 8; - default: - return 0; - } + if (format >= G_N_ELEMENTS (formats)) + return 0; + + return GST_VIDEO_FORMAT_INFO_PSTRIDE (&formats[format].info, component); } /** @@ -1972,7 +2611,8 @@ gst_video_format_get_component_offset (GstVideoFormat format, default: break; } - GST_WARNING ("unhandled format %d or component %d", format, component); + GST_WARNING ("unhandled format %s or component %d", + gst_video_format_to_string (format), component); return 0; } diff --git a/gst-libs/gst/video/video.h b/gst-libs/gst/video/video.h index 36750e9727..d1a19322d0 100644 --- a/gst-libs/gst/video/video.h +++ b/gst-libs/gst/video/video.h @@ -435,6 +435,467 @@ typedef enum { */ #define GST_VIDEO_BUFFER_PROGRESSIVE GST_BUFFER_FLAG_MEDIA4 +/* Backported from 0.11 */ + +#define GST_VIDEO_MAX_PLANES 4 +#define GST_VIDEO_MAX_COMPONENTS 4 + +typedef struct _GstVideoFormatInfo GstVideoFormatInfo; + +/** + * GstVideoFormatFlags: + * @GST_VIDEO_FORMAT_FLAG_YUV: The video format is YUV, components are numbered + * 0=Y, 1=U, 2=V. + * @GST_VIDEO_FORMAT_FLAG_RGB: The video format is RGB, components are numbered + * 0=R, 1=G, 2=B. + * @GST_VIDEO_FORMAT_FLAG_GRAY: The video is gray, there is one gray component + * with index 0. + * @GST_VIDEO_FORMAT_FLAG_ALPHA: The video format has an alpha components with + * the number 3. + * @GST_VIDEO_FORMAT_FLAG_LE: The video format has data stored in little + * endianness. + * @GST_VIDEO_FORMAT_FLAG_PALETTE: The video format has a palette. + * @GST_VIDEO_FORMAT_FLAG_COMPLEX: The video format has a complex layout that + * can't be described with the usual information in the #GstVideoFormatInfo. + * + * The different video flags that a format info can have. + */ +typedef enum +{ + GST_VIDEO_FORMAT_FLAG_YUV = (1 << 0), + GST_VIDEO_FORMAT_FLAG_RGB = (1 << 1), + GST_VIDEO_FORMAT_FLAG_GRAY = (1 << 2), + GST_VIDEO_FORMAT_FLAG_ALPHA = (1 << 3), + GST_VIDEO_FORMAT_FLAG_LE = (1 << 4), + GST_VIDEO_FORMAT_FLAG_PALETTE = (1 << 5), + GST_VIDEO_FORMAT_FLAG_COMPLEX = (1 << 6) +} GstVideoFormatFlags; + +#define GST_VIDEO_COMP_Y 0 +#define GST_VIDEO_COMP_U 1 +#define GST_VIDEO_COMP_V 2 + +#define GST_VIDEO_COMP_R 0 +#define GST_VIDEO_COMP_G 1 +#define GST_VIDEO_COMP_B 2 + +#define GST_VIDEO_COMP_A 3 + +/** + * GstVideoFormatUnpack: + * @info: a #GstVideoFormatInfo + * @dest: a destination array + * @data: pointers to the data planes + * @stride: strides of the planes + * @x: the x position in the image to start from + * @y: the y position in the image to start from + * @width: the amount of pixels to unpack. + * + * Unpacks @width pixels from the given planes and strides containing data of + * format @info. The pixels will be unpacked into @dest which each component + * interleaved. @dest should at least be big enough to hold @width * + * n_components * size(unpack_format) bytes. + */ +typedef void (*GstVideoFormatUnpack) (GstVideoFormatInfo *info, gpointer dest, + const gpointer data[GST_VIDEO_MAX_PLANES], + const gint stride[GST_VIDEO_MAX_PLANES], + gint x, gint y, gint width); +/** + * GstVideoFormatPack: + * @info: a #GstVideoFormatInfo + * @src: a source array + * @data: pointers to the destination data planes + * @stride: strides of the destination planes + * @x: the x position in the image to pack to + * @y: the y position in the image to pack to + * @width: the amount of pixels to pack. + * + * Packs @width pixels from @src to the given planes and strides in the + * format @info. The pixels from source have each component interleaved + * and will be packed into the planes in @data. + */ +typedef void (*GstVideoFormatPack) (GstVideoFormatInfo *info, const gpointer src, + gpointer data[GST_VIDEO_MAX_PLANES], + const gint stride[GST_VIDEO_MAX_PLANES], + gint x, gint y, gint width); + +/** + * GstVideoFormatInfo: + * @format: #GstVideoFormat + * @name: string representation of the format + * @description: use readable description of the format + * @flags: #GstVideoFormatFlags + * @bits: The number of bits used to pack data items. This can be less than 8 + * when multiple pixels are stored in a byte. for values > 8 multiple bytes + * should be read according to the endianness flag before applying the shift + * and mask. + * @n_components: the number of components in the video format. + * @shift: the number of bits to shift away to get the component data + * @depth: the depth in bits for each component + * @pixel_stride: the pixel stride of each component. This is the amount of + * bytes to the pixel immediately to the right. When bits < 8, the stride is + * expressed in bits. + * @n_planes: the number of planes for this format. The number of planes can be + * less than the amount of components when multiple components are packed into + * one plane. + * @plane: the plane number where a component can be found + * @poffset: the offset in the plane where the first pixel of the components + * can be found. If bits < 8 the amount is specified in bits. + * @w_sub: subsampling factor of the width for the component. Use + * GST_VIDEO_SUB_SCALE to scale a width. + * @h_sub: subsampling factor of the height for the component. Use + * GST_VIDEO_SUB_SCALE to scale a height. + * @unpack_format: the format of the unpacked pixels. + * @unpack_func: an unpack function for this format + * @pack_func: an pack function for this format + * + * Information for a video format. + */ +struct _GstVideoFormatInfo { + GstVideoFormat format; + const gchar *name; + const gchar *description; + GstVideoFormatFlags flags; + guint bits; + guint n_components; + guint shift[GST_VIDEO_MAX_COMPONENTS]; + guint depth[GST_VIDEO_MAX_COMPONENTS]; + gint pixel_stride[GST_VIDEO_MAX_COMPONENTS]; + guint n_planes; + guint plane[GST_VIDEO_MAX_COMPONENTS]; + guint poffset[GST_VIDEO_MAX_COMPONENTS]; + guint w_sub[GST_VIDEO_MAX_COMPONENTS]; + guint h_sub[GST_VIDEO_MAX_COMPONENTS]; + + GstVideoFormat unpack_format; + GstVideoFormatUnpack unpack_func; + GstVideoFormatPack pack_func; +}; + +#define GST_VIDEO_FORMAT_INFO_FORMAT(info) ((info)->format) +#define GST_VIDEO_FORMAT_INFO_NAME(info) ((info)->name) +#define GST_VIDEO_FORMAT_INFO_FLAGS(info) ((info)->flags) + +#define GST_VIDEO_FORMAT_INFO_IS_YUV(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_YUV) +#define GST_VIDEO_FORMAT_INFO_IS_RGB(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_RGB) +#define GST_VIDEO_FORMAT_INFO_IS_GRAY(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_GRAY) +#define GST_VIDEO_FORMAT_INFO_HAS_ALPHA(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_ALPHA) +#define GST_VIDEO_FORMAT_INFO_IS_LE(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_LE) +#define GST_VIDEO_FORMAT_INFO_HAS_PALETTE(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_PALETTE) + +#define GST_VIDEO_FORMAT_INFO_BITS(info) ((info)->bits) +#define GST_VIDEO_FORMAT_INFO_N_COMPONENTS(info) ((info)->n_components) +#define GST_VIDEO_FORMAT_INFO_SHIFT(info,c) ((info)->shift[c]) +#define GST_VIDEO_FORMAT_INFO_DEPTH(info,c) ((info)->depth[c]) +#define GST_VIDEO_FORMAT_INFO_PSTRIDE(info,c) ((info)->pixel_stride[c]) +#define GST_VIDEO_FORMAT_INFO_N_PLANES(info) ((info)->n_planes) +#define GST_VIDEO_FORMAT_INFO_PLANE(info,c) ((info)->plane[c]) +#define GST_VIDEO_FORMAT_INFO_POFFSET(info,c) ((info)->poffset[c]) +#define GST_VIDEO_FORMAT_INFO_W_SUB(info,c) ((info)->w_sub[c]) +#define GST_VIDEO_FORMAT_INFO_H_SUB(info,c) ((info)->h_sub[c]) + +#define GST_VIDEO_SUB_SCALE(scale,val) (-((-((gint)val))>>(scale))) + +#define GST_VIDEO_FORMAT_INFO_SCALE_WIDTH(info,c,w) GST_VIDEO_SUB_SCALE ((info)->w_sub[(c)],(w)) +#define GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(info,c,h) GST_VIDEO_SUB_SCALE ((info)->h_sub[(c)],(h)) + +#define GST_VIDEO_FORMAT_INFO_DATA(info,planes,comp) \ + (((guint8*)(planes)[info->plane[comp]]) + info->poffset[comp]) +#define GST_VIDEO_FORMAT_INFO_STRIDE(info,strides,comp) ((strides)[info->plane[comp]]) +#define GST_VIDEO_FORMAT_INFO_OFFSET(info,offsets,comp) \ + (((offsets)[info->plane[comp]]) + info->poffset[comp]) + +typedef struct _GstVideoInfo GstVideoInfo; + +const GstVideoFormatInfo * + gst_video_format_get_info (GstVideoFormat format) G_GNUC_CONST; +/** + * GstVideoInterlaceMode: + * @GST_VIDEO_INTERLACE_MODE_PROGRESSIVE: all frames are progressive + * @GST_VIDEO_INTERLACE_MODE_INTERLEAVED: video is interlaced and all fields + * are interlaced in one frame. + * @GST_VIDEO_INTERLACE_MODE_MIXED: video contains both interlaced and + * progressive frames, the buffer flags describe the frame and fields. + * @GST_VIDEO_INTERLACE_MODE_FIELDS: video is interlaced and fields are stored + * separately. Use the id property to get access to the required field. + * + * The possible values of the #GstVideoInterlaceMode describing the interlace + * mode of the stream. + */ +typedef enum { + GST_VIDEO_INTERLACE_MODE_PROGRESSIVE = 0, + GST_VIDEO_INTERLACE_MODE_INTERLEAVED, + GST_VIDEO_INTERLACE_MODE_MIXED, + GST_VIDEO_INTERLACE_MODE_FIELDS +} GstVideoInterlaceMode; + +/** + * GstVideoFlags: + * @GST_VIDEO_FLAG_NONE: no flags + * @GST_VIDEO_FLAG_VARIABLE_FPS: a variable fps is selected, fps_n and fps_d + * denote the maximum fps of the video + * + * Extra video flags + */ +typedef enum { + GST_VIDEO_FLAG_NONE = 0, + GST_VIDEO_FLAG_VARIABLE_FPS = (1 << 1) +} GstVideoFlags; + +/** + * GstVideoChroma: + * @GST_VIDEO_CHROMA_SITE_UNKNOWN: unknown cositing + * @GST_VIDEO_CHROMA_SITE_NONE: no cositing + * @GST_VIDEO_CHROMA_SITE_H_COSITED: chroma is horizontally cosited + * @GST_VIDEO_CHROMA_SITE_V_COSITED: chroma is vertically cosited + * @GST_VIDEO_CHROMA_SITE_ALT_LINE: choma samples are sited on alternate lines + * @GST_VIDEO_CHROMA_SITE_COSITED: chroma samples cosited with luma samples + * @GST_VIDEO_CHROMA_SITE_JPEG: jpeg style cositing, also for mpeg1 and mjpeg + * @GST_VIDEO_CHROMA_SITE_MPEG2: mpeg2 style cositing + * @GST_VIDEO_CHROMA_SITE_DV: DV style cositing + * + * Various Chroma sitings. + */ +typedef enum { + GST_VIDEO_CHROMA_SITE_UNKNOWN = 0, + GST_VIDEO_CHROMA_SITE_NONE = (1 << 0), + GST_VIDEO_CHROMA_SITE_H_COSITED = (1 << 1), + GST_VIDEO_CHROMA_SITE_V_COSITED = (1 << 2), + GST_VIDEO_CHROMA_SITE_ALT_LINE = (1 << 3), + /* some common chroma cositing */ + GST_VIDEO_CHROMA_SITE_COSITED = (GST_VIDEO_CHROMA_SITE_H_COSITED | GST_VIDEO_CHROMA_SITE_V_COSITED), + GST_VIDEO_CHROMA_SITE_JPEG = (GST_VIDEO_CHROMA_SITE_NONE), + GST_VIDEO_CHROMA_SITE_MPEG2 = (GST_VIDEO_CHROMA_SITE_H_COSITED), + GST_VIDEO_CHROMA_SITE_DV = (GST_VIDEO_CHROMA_SITE_COSITED | GST_VIDEO_CHROMA_SITE_ALT_LINE), +} GstVideoChromaSite; + + +/** + * GstVideoColorRange: + * @GST_VIDEO_COLOR_RANGE_UNKNOWN: unknown range + * @GST_VIDEO_COLOR_RANGE_0_255: [0..255] for 8 bit components + * @GST_VIDEO_COLOR_RANGE_16_235: [16..235] for 8 bit components. Chroma has + * [16..240] range. + * + * Possible color range values. These constants are defined for 8 bit color + * values and can be scaled for other bit depths. + */ +typedef enum { + GST_VIDEO_COLOR_RANGE_UNKNOWN = 0, + GST_VIDEO_COLOR_RANGE_0_255, + GST_VIDEO_COLOR_RANGE_16_235 +} GstVideoColorRange; + +/** + * GstVideoColorMatrix: + * @GST_VIDEO_COLOR_MATRIX_UNKNOWN: unknown matrix + * @GST_VIDEO_COLOR_MATRIX_RGB: identity matrix + * @GST_VIDEO_COLOR_MATRIX_FCC: FCC color matrix + * @GST_VIDEO_COLOR_MATRIX_BT709: ITU-R BT.709 color matrix + * @GST_VIDEO_COLOR_MATRIX_BT601: ITU-R BT.601 color matrix + * @GST_VIDEO_COLOR_MATRIX_SMPTE240M: SMPTE 240M color matrix + * + * The color matrix is used to convert between Y'PbPr and + * non-linear RGB (R'G'B') + */ +typedef enum { + GST_VIDEO_COLOR_MATRIX_UNKNOWN = 0, + GST_VIDEO_COLOR_MATRIX_RGB, + GST_VIDEO_COLOR_MATRIX_FCC, + GST_VIDEO_COLOR_MATRIX_BT709, + GST_VIDEO_COLOR_MATRIX_BT601, + GST_VIDEO_COLOR_MATRIX_SMPTE240M +} GstVideoColorMatrix; + +/** + * GstVideoTransferFunction: + * @GST_VIDEO_TRANSFER_UNKNOWN: unknown transfer function + * @GST_VIDEO_TRANSFER_GAMMA10: linear RGB, gamma 1.0 curve + * @GST_VIDEO_TRANSFER_GAMMA18: Gamma 1.8 curve + * @GST_VIDEO_TRANSFER_GAMMA20: Gamma 2.0 curve + * @GST_VIDEO_TRANSFER_GAMMA22: Gamma 2.2 curve + * @GST_VIDEO_TRANSFER_BT709: Gamma 2.2 curve with a linear segment in the lower + * range + * @GST_VIDEO_TRANSFER_SMPTE240M: Gamma 2.2 curve with a linear segment in the + * lower range + * @GST_VIDEO_TRANSFER_SRGB: Gamma 2.4 curve with a linear segment in the lower + * range + * @GST_VIDEO_TRANSFER_GAMMA28: Gamma 2.8 curve + * @GST_VIDEO_TRANSFER_LOG100: Logarithmic transfer characteristic + * 100:1 range + * @GST_VIDEO_TRANSFER_LOG316: Logarithmic transfer characteristic + * 316.22777:1 range + * + * The video transfer function defines the formula for converting between + * non-linear RGB (R'G'B') and linear RGB + */ +typedef enum { + GST_VIDEO_TRANSFER_UNKNOWN = 0, + GST_VIDEO_TRANSFER_GAMMA10, + GST_VIDEO_TRANSFER_GAMMA18, + GST_VIDEO_TRANSFER_GAMMA20, + GST_VIDEO_TRANSFER_GAMMA22, + GST_VIDEO_TRANSFER_BT709, + GST_VIDEO_TRANSFER_SMPTE240M, + GST_VIDEO_TRANSFER_SRGB, + GST_VIDEO_TRANSFER_GAMMA28, + GST_VIDEO_TRANSFER_LOG100, + GST_VIDEO_TRANSFER_LOG316 +} GstVideoTransferFunction; + +/** + * GstVideoColorPrimaries: + * @GST_VIDEO_COLOR_PRIMARIES_UNKNOWN: unknown color primaries + * @GST_VIDEO_COLOR_PRIMARIES_BT709: BT709 primaries + * @GST_VIDEO_COLOR_PRIMARIES_BT470M: BT470M primaries + * @GST_VIDEO_COLOR_PRIMARIES_BT470BG: BT470BG primaries + * @GST_VIDEO_COLOR_PRIMARIES_SMPTE170M: SMPTE170M primaries + * @GST_VIDEO_COLOR_PRIMARIES_SMPTE240M: SMPTE240M primaries + * + * The color primaries define the how to transform linear RGB values to and from + * the CIE XYZ colorspace. + */ +typedef enum { + GST_VIDEO_COLOR_PRIMARIES_UNKNOWN = 0, + GST_VIDEO_COLOR_PRIMARIES_BT709, + GST_VIDEO_COLOR_PRIMARIES_BT470M, + GST_VIDEO_COLOR_PRIMARIES_BT470BG, + GST_VIDEO_COLOR_PRIMARIES_SMPTE170M, + GST_VIDEO_COLOR_PRIMARIES_SMPTE240M +} GstVideoColorPrimaries; + +/** + * GstVideoColorimetry: + * @range: the color range. This is the valid range for the samples. + * It is used to convert the samples to Y'PbPr values. + * @matrix: the color matrix. Used to convert between Y'PbPr and + * non-linear RGB (R'G'B') + * @transfer: the transfer function. used to convert between R'G'B' and RGB + * @primaries: color primaries. used to convert between R'G'B' and CIE XYZ + * + * Structure describing the color info. + */ +typedef struct { + GstVideoColorRange range; + GstVideoColorMatrix matrix; + GstVideoTransferFunction transfer; + GstVideoColorPrimaries primaries; +} GstVideoColorimetry; + +/* predefined colorimetry */ +#define GST_VIDEO_COLORIMETRY_BT601 "bt601" +#define GST_VIDEO_COLORIMETRY_BT709 "bt709" +#define GST_VIDEO_COLORIMETRY_SMPTE240M "smpte240m" + +gboolean gst_video_colorimetry_matches (GstVideoColorimetry *cinfo, const gchar *color); +gboolean gst_video_colorimetry_from_string (GstVideoColorimetry *cinfo, const gchar *color); +gchar * gst_video_colorimetry_to_string (GstVideoColorimetry *cinfo); + +/** + * GstVideoInfo: + * @finfo: the format info of the video + * @interlace_mode: the interlace mode + * @flags: additional video flags + * @width: the width of the video + * @height: the height of the video + * @size: the default size of one frame + * @chroma_site: a #GstVideoChromaSite. + * @colorimetry: the colorimetry info + * @palette: a buffer with palette data + * @par_n: the pixel-aspect-ratio numerator + * @par_d: the pixel-aspect-ratio demnominator + * @fps_n: the framerate numerator + * @fps_d: the framerate demnominator + * @offset: offsets of the planes + * @stride: strides of the planes + * + * Information describing image properties. This information can be filled + * in from GstCaps with gst_video_info_from_caps(). The information is also used + * to store the specific video info when mapping a video frame with + * gst_video_frame_map(). + * + * Use the provided macros to access the info in this structure. + */ +struct _GstVideoInfo { + const GstVideoFormatInfo *finfo; + + GstVideoInterlaceMode interlace_mode; + GstVideoFlags flags; + gint width; + gint height; + gsize size; + + GstVideoChromaSite chroma_site; + GstVideoColorimetry colorimetry; + + GstBuffer *palette; + + gint par_n; + gint par_d; + gint fps_n; + gint fps_d; + + gsize offset[GST_VIDEO_MAX_PLANES]; + gint stride[GST_VIDEO_MAX_PLANES]; +}; + +/* general info */ +#define GST_VIDEO_INFO_FORMAT(i) (GST_VIDEO_FORMAT_INFO_FORMAT((i)->finfo)) +#define GST_VIDEO_INFO_NAME(i) (GST_VIDEO_FORMAT_INFO_NAME((i)->finfo)) +#define GST_VIDEO_INFO_IS_YUV(i) (GST_VIDEO_FORMAT_INFO_IS_YUV((i)->finfo)) +#define GST_VIDEO_INFO_IS_RGB(i) (GST_VIDEO_FORMAT_INFO_IS_RGB((i)->finfo)) +#define GST_VIDEO_INFO_IS_GRAY(i) (GST_VIDEO_FORMAT_INFO_IS_GRAY((i)->finfo)) +#define GST_VIDEO_INFO_HAS_ALPHA(i) (GST_VIDEO_FORMAT_INFO_HAS_ALPHA((i)->finfo)) + +#define GST_VIDEO_INFO_INTERLACE_MODE(i) ((i)->interlace_mode) +#define GST_VIDEO_INFO_IS_INTERLACED(i) ((i)->interlace_mode != GST_VIDEO_INTERLACE_MODE_PROGRESSIVE) +#define GST_VIDEO_INFO_FLAGS(i) ((i)->flags) +#define GST_VIDEO_INFO_WIDTH(i) ((i)->width) +#define GST_VIDEO_INFO_HEIGHT(i) ((i)->height) +#define GST_VIDEO_INFO_SIZE(i) ((i)->size) +#define GST_VIDEO_INFO_PAR_N(i) ((i)->par_n) +#define GST_VIDEO_INFO_PAR_D(i) ((i)->par_d) +#define GST_VIDEO_INFO_FPS_N(i) ((i)->fps_n) +#define GST_VIDEO_INFO_FPS_D(i) ((i)->fps_d) + +/* dealing with GstVideoInfo flags */ +#define GST_VIDEO_INFO_FLAG_IS_SET(i,flag) ((GST_VIDEO_INFO_FLAGS(i) & (flag)) == (flag)) +#define GST_VIDEO_INFO_FLAG_SET(i,flag) (GST_VIDEO_INFO_FLAGS(i) |= (flag)) +#define GST_VIDEO_INFO_FLAG_UNSET(i,flag) (GST_VIDEO_INFO_FLAGS(i) &= ~(flag)) + +/* dealing with planes */ +#define GST_VIDEO_INFO_N_PLANES(i) (GST_VIDEO_FORMAT_INFO_N_PLANES((i)->finfo)) +#define GST_VIDEO_INFO_PLANE_OFFSET(i,p) ((i)->offset[p]) +#define GST_VIDEO_INFO_PLANE_STRIDE(i,p) ((i)->stride[p]) + +/* dealing with components */ +#define GST_VIDEO_INFO_N_COMPONENTS(i) GST_VIDEO_FORMAT_INFO_N_COMPONENTS((i)->finfo) +#define GST_VIDEO_INFO_COMP_DEPTH(i,c) GST_VIDEO_FORMAT_INFO_DEPTH((i)->finfo,c) +#define GST_VIDEO_INFO_COMP_BITS(i,c) GST_VIDEO_FORMAT_INFO_BITS((i)->finfo,c) +#define GST_VIDEO_INFO_COMP_DATA(i,d,c) GST_VIDEO_FORMAT_INFO_DATA((i)->finfo,d,c) +#define GST_VIDEO_INFO_COMP_OFFSET(i,c) GST_VIDEO_FORMAT_INFO_OFFSET((i)->finfo,(i)->offset,c) +#define GST_VIDEO_INFO_COMP_STRIDE(i,c) GST_VIDEO_FORMAT_INFO_STRIDE((i)->finfo,(i)->stride,c) +#define GST_VIDEO_INFO_COMP_WIDTH(i,c) GST_VIDEO_FORMAT_INFO_SCALE_WIDTH((i)->finfo,c,(i)->width) +#define GST_VIDEO_INFO_COMP_HEIGHT(i,c) GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT((i)->finfo,c,(i)->height) +#define GST_VIDEO_INFO_COMP_PLANE(i,c) GST_VIDEO_FORMAT_INFO_PLANE((i)->finfo,c) +#define GST_VIDEO_INFO_COMP_PSTRIDE(i,c) GST_VIDEO_FORMAT_INFO_PSTRIDE((i)->finfo,c) +#define GST_VIDEO_INFO_COMP_POFFSET(i,c) GST_VIDEO_FORMAT_INFO_POFFSET((i)->finfo,c) + +void gst_video_info_init (GstVideoInfo *info); + +void gst_video_info_set_format (GstVideoInfo *info, GstVideoFormat format, + guint width, guint height); + +gboolean gst_video_info_from_caps (GstVideoInfo *info, const GstCaps * caps); + +GstCaps * gst_video_info_to_caps (GstVideoInfo *info); + +gboolean gst_video_info_convert (GstVideoInfo *info, + GstFormat src_format, + gint64 src_value, + GstFormat dest_format, + gint64 *dest_value); + /* functions */ const GValue * gst_video_frame_rate (GstPad * pad); @@ -493,11 +954,17 @@ GstCaps * gst_video_format_new_caps_interlaced (GstVideoFormat format, GstCaps * gst_video_format_new_template_caps (GstVideoFormat format); /* format properties */ +GstVideoFormat gst_video_format_from_masks (gint depth, gint bpp, gint endianness, + gint red_mask, gint green_mask, + gint blue_mask, gint alpha_mask) G_GNUC_CONST; + GstVideoFormat gst_video_format_from_fourcc (guint32 fourcc) G_GNUC_CONST; guint32 gst_video_format_to_fourcc (GstVideoFormat format) G_GNUC_CONST; +const gchar * gst_video_format_to_string (GstVideoFormat format) G_GNUC_CONST; + gboolean gst_video_format_is_rgb (GstVideoFormat format) G_GNUC_CONST; gboolean gst_video_format_is_yuv (GstVideoFormat format) G_GNUC_CONST; diff --git a/tests/check/libs/video.c b/tests/check/libs/video.c index 4abd0b37ab..02d552241b 100644 --- a/tests/check/libs/video.c +++ b/tests/check/libs/video.c @@ -369,6 +369,7 @@ GST_START_TEST (test_video_formats) guint i; for (i = 0; i < G_N_ELEMENTS (fourcc_list); ++i) { + const GstVideoFormatInfo *vf_info; GstVideoFormat fmt; const gchar *s; guint32 fourcc; @@ -378,8 +379,15 @@ GST_START_TEST (test_video_formats) fourcc = GST_MAKE_FOURCC (s[0], s[1], s[2], s[3]); fmt = gst_video_format_from_fourcc (fourcc); - if (fmt == GST_VIDEO_FORMAT_UNKNOWN) + if (fmt == GST_VIDEO_FORMAT_UNKNOWN) { + GST_DEBUG ("Unknown format %s, skipping tests", fourcc_list[i].fourcc); continue; + } + + vf_info = gst_video_format_get_info (fmt); + fail_unless (vf_info != NULL); + + fail_unless_equals_int (GST_VIDEO_FORMAT_INFO_FORMAT (vf_info), fmt); GST_INFO ("Fourcc %s, packed=%d", fourcc_list[i].fourcc, gst_video_format_is_packed (fmt)); @@ -410,8 +418,8 @@ GST_START_TEST (test_video_formats) fourcc_list[i].paint_setup (&paintinfo, NULL); fail_unless_equals_int (gst_video_format_get_row_stride (fmt, 0, w), paintinfo.ystride); - if (!gst_video_format_is_packed (fmt) - && !gst_video_format_is_gray (fmt)) { + if (GST_VIDEO_FORMAT_INFO_N_COMPONENTS (vf_info) > 1 && + !gst_video_format_is_packed (fmt)) { /* planar */ fail_unless_equals_int (gst_video_format_get_row_stride (fmt, 1, w), paintinfo.ustride); From 0a7d047b1f9fc43ecccec1eb52e0bafaf070a378 Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Tue, 24 Apr 2012 10:09:57 +0200 Subject: [PATCH 07/14] video: Add GST_VIDEO_FORMAT_ENCODED --- gst-libs/gst/video/video-blend.c | 2 +- gst-libs/gst/video/video.c | 24 ++++++++++++++++++------ gst-libs/gst/video/video.h | 10 +++++----- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/gst-libs/gst/video/video-blend.c b/gst-libs/gst/video/video-blend.c index 140e2bbb79..e156ed4e80 100644 --- a/gst-libs/gst/video/video-blend.c +++ b/gst-libs/gst/video/video-blend.c @@ -231,7 +231,7 @@ fill_planes (GstBlendVideoFormatInfo * info) info->offset[1] = info->offset[2] + info->stride[1] * (GST_ROUND_UP_4 (height) / 4); break; - case GST_VIDEO_FORMAT_UNKNOWN: + default: GST_ERROR ("invalid format"); g_warning ("invalid format"); break; diff --git a/gst-libs/gst/video/video.c b/gst-libs/gst/video/video.c index 1922832bbb..5cf9a75105 100644 --- a/gst-libs/gst/video/video.c +++ b/gst-libs/gst/video/video.c @@ -141,7 +141,6 @@ static VideoFormat formats[] = { {0x00000000, {GST_VIDEO_FORMAT_UNKNOWN, "UNKNOWN", "unknown video", 0, DPTH0, PSTR0, PLANE_NA, OFFS0}}, - MAKE_YUV_FORMAT (I420, "raw video", GST_MAKE_FOURCC ('I', '4', '2', '0'), DPTH888, PSTR111, PLANE012, OFFS0, SUB420), @@ -258,6 +257,8 @@ static VideoFormat formats[] = { MAKE_YUV_FORMAT (r210, "raw video", GST_MAKE_FOURCC ('r', '2', '1', '0'), DPTH10_10_10, PSTR444, PLANE0, OFFS0, SUB444), + {0x00000000, {GST_VIDEO_FORMAT_ENCODED, "ENCODED", "encoded video", + GST_VIDEO_FORMAT_FLAG_COMPLEX, DPTH0, PSTR0, PLANE_NA, OFFS0}}, }; /** @@ -1373,7 +1374,7 @@ gst_video_info_from_caps (GstVideoInfo * info, const GstCaps * caps) GstStructure *structure; const gchar *s; GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN; - gint width, height; + gint width = 0, height = 0; gint fps_n, fps_d; gint par_n, par_d; gboolean interlaced; @@ -1436,14 +1437,19 @@ gst_video_info_from_caps (GstVideoInfo * info, const GstCaps * caps) } else if (depth == 16 && bpp == 16 && endianness == G_LITTLE_ENDIAN) { format = GST_VIDEO_FORMAT_GRAY16_LE; } - } + } else if (g_str_has_prefix (gst_structure_get_name (structure), "video/") || + g_str_has_prefix (gst_structure_get_name (structure), "image/")) + format = GST_VIDEO_FORMAT_ENCODED; if (format == GST_VIDEO_FORMAT_UNKNOWN) goto unknown_format; - if (!gst_structure_get_int (structure, "width", &width)) + /* width and height are mandatory, except for non-raw-formats */ + if (!gst_structure_get_int (structure, "width", &width) && + format != GST_VIDEO_FORMAT_ENCODED) goto no_width; - if (!gst_structure_get_int (structure, "height", &height)) + if (!gst_structure_get_int (structure, "height", &height) && + format != GST_VIDEO_FORMAT_ENCODED) goto no_height; gst_video_info_set_format (info, format, width, height); @@ -1557,6 +1563,10 @@ gst_video_info_to_caps (GstVideoInfo * info) if (GST_VIDEO_INFO_IS_YUV (info)) gst_caps_set_simple (caps, "format", GST_TYPE_FOURCC, gst_video_format_to_fourcc (info->finfo->format), NULL); + else if (GST_VIDEO_INFO_IS_RGB (info) || GST_VIDEO_INFO_IS_GRAY (info)) + gst_caps_set_simple (caps, "depth", G_TYPE_INT, + info->finfo->bits * GST_VIDEO_INFO_N_COMPONENTS (info), NULL); + gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN, GST_VIDEO_INFO_IS_INTERLACED (info), NULL); @@ -1745,7 +1755,9 @@ fill_planes (GstVideoInfo * info) info->size = info->offset[2] + info->stride[2] * (GST_ROUND_UP_4 (height) / 4); break; - case GST_VIDEO_FORMAT_UNKNOWN: + default: + if (GST_VIDEO_FORMAT_INFO_IS_COMPLEX (info->finfo)) + break; GST_ERROR ("invalid format"); g_warning ("invalid format"); break; diff --git a/gst-libs/gst/video/video.h b/gst-libs/gst/video/video.h index d1a19322d0..73e4b473ad 100644 --- a/gst-libs/gst/video/video.h +++ b/gst-libs/gst/video/video.h @@ -30,6 +30,7 @@ G_BEGIN_DECLS /** * GstVideoFormat: * @GST_VIDEO_FORMAT_UNKNOWN: Unknown or unset video format id + * @GST_VIDEO_FORMAT_ENCODED: Encoded video format * @GST_VIDEO_FORMAT_I420: planar 4:2:0 YUV * @GST_VIDEO_FORMAT_YV12: planar 4:2:0 YVU (like I420 but UV planes swapped) * @GST_VIDEO_FORMAT_YUY2: packed 4:2:2 YUV (Y0-U0-Y1-V0 Y2-U2-Y3-V2 Y4 ...) @@ -118,7 +119,8 @@ typedef enum { GST_VIDEO_FORMAT_IYU1, GST_VIDEO_FORMAT_ARGB64, GST_VIDEO_FORMAT_AYUV64, - GST_VIDEO_FORMAT_r210 + GST_VIDEO_FORMAT_r210, + GST_VIDEO_FORMAT_ENCODED } GstVideoFormat; #define GST_VIDEO_BYTE1_MASK_32 "0xFF000000" @@ -582,6 +584,7 @@ struct _GstVideoFormatInfo { #define GST_VIDEO_FORMAT_INFO_HAS_ALPHA(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_ALPHA) #define GST_VIDEO_FORMAT_INFO_IS_LE(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_LE) #define GST_VIDEO_FORMAT_INFO_HAS_PALETTE(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_PALETTE) +#define GST_VIDEO_FORMAT_INFO_IS_COMPLEX(info) ((info)->flags & GST_VIDEO_FORMAT_FLAG_COMPLEX) #define GST_VIDEO_FORMAT_INFO_BITS(info) ((info)->bits) #define GST_VIDEO_FORMAT_INFO_N_COMPONENTS(info) ((info)->n_components) @@ -616,8 +619,6 @@ const GstVideoFormatInfo * * are interlaced in one frame. * @GST_VIDEO_INTERLACE_MODE_MIXED: video contains both interlaced and * progressive frames, the buffer flags describe the frame and fields. - * @GST_VIDEO_INTERLACE_MODE_FIELDS: video is interlaced and fields are stored - * separately. Use the id property to get access to the required field. * * The possible values of the #GstVideoInterlaceMode describing the interlace * mode of the stream. @@ -625,8 +626,7 @@ const GstVideoFormatInfo * typedef enum { GST_VIDEO_INTERLACE_MODE_PROGRESSIVE = 0, GST_VIDEO_INTERLACE_MODE_INTERLEAVED, - GST_VIDEO_INTERLACE_MODE_MIXED, - GST_VIDEO_INTERLACE_MODE_FIELDS + GST_VIDEO_INTERLACE_MODE_MIXED } GstVideoInterlaceMode; /** From a22545134d741bfce9187ac4818bdc9dd19dcc2f Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Tue, 24 Apr 2012 10:10:15 +0200 Subject: [PATCH 08/14] video: Add gst_video_info_is_equal --- gst-libs/gst/video/video.c | 36 ++++++++++++++++++++++++++++++++++++ gst-libs/gst/video/video.h | 3 ++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/video/video.c b/gst-libs/gst/video/video.c index 5cf9a75105..9231d01035 100644 --- a/gst-libs/gst/video/video.c +++ b/gst-libs/gst/video/video.c @@ -1530,6 +1530,42 @@ no_endianess: } } +/** + * gst_video_info_is_equal: + * @info: a #GstVideoInfo + * @other: a #GstVideoInfo + * + * Compares two #GstVideoInfo and returns whether they are equal or not + * + * Returns: %TRUE if @info and @other are equal, else %FALSE. + */ +gboolean +gst_video_info_is_equal (const GstVideoInfo * info, const GstVideoInfo * other) +{ + if (GST_VIDEO_INFO_FORMAT (info) != GST_VIDEO_INFO_FORMAT (other)) + return FALSE; + if (GST_VIDEO_INFO_INTERLACE_MODE (info) != + GST_VIDEO_INFO_INTERLACE_MODE (other)) + return FALSE; + if (GST_VIDEO_INFO_FLAGS (info) != GST_VIDEO_INFO_FLAGS (other)) + return FALSE; + if (GST_VIDEO_INFO_WIDTH (info) != GST_VIDEO_INFO_WIDTH (other)) + return FALSE; + if (GST_VIDEO_INFO_HEIGHT (info) != GST_VIDEO_INFO_HEIGHT (other)) + return FALSE; + if (GST_VIDEO_INFO_SIZE (info) != GST_VIDEO_INFO_SIZE (other)) + return FALSE; + if (GST_VIDEO_INFO_PAR_N (info) != GST_VIDEO_INFO_PAR_N (other)) + return FALSE; + if (GST_VIDEO_INFO_PAR_D (info) != GST_VIDEO_INFO_PAR_D (other)) + return FALSE; + if (GST_VIDEO_INFO_FPS_N (info) != GST_VIDEO_INFO_FPS_N (other)) + return FALSE; + if (GST_VIDEO_INFO_FPS_D (info) != GST_VIDEO_INFO_FPS_D (other)) + return FALSE; + return TRUE; +} + /** * gst_video_info_to_caps: * @info: a #GstVideoInfo diff --git a/gst-libs/gst/video/video.h b/gst-libs/gst/video/video.h index 73e4b473ad..f88d1583c1 100644 --- a/gst-libs/gst/video/video.h +++ b/gst-libs/gst/video/video.h @@ -895,7 +895,8 @@ gboolean gst_video_info_convert (GstVideoInfo *info, gint64 src_value, GstFormat dest_format, gint64 *dest_value); - +gboolean gst_video_info_is_equal (const GstVideoInfo *info, + const GstVideoInfo *other); /* functions */ const GValue * gst_video_frame_rate (GstPad * pad); From f6cfd763e4b38a15cf815606bb6b5f7dc5e9107f Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Wed, 7 Mar 2012 10:18:49 +0100 Subject: [PATCH 09/14] video: Base classes for video decoders and encoders --- docs/libs/gst-plugins-base-libs-docs.sgml | 3 + docs/libs/gst-plugins-base-libs-sections.txt | 118 + docs/libs/gst-plugins-base-libs.types | 7 + gst-libs/gst/video/Makefile.am | 9 +- gst-libs/gst/video/TODO | 51 + gst-libs/gst/video/gstvideodecoder.c | 2696 ++++++++++++++++++ gst-libs/gst/video/gstvideodecoder.h | 369 +++ gst-libs/gst/video/gstvideoencoder.c | 1652 +++++++++++ gst-libs/gst/video/gstvideoencoder.h | 306 ++ gst-libs/gst/video/gstvideoutils.c | 189 ++ gst-libs/gst/video/gstvideoutils.h | 277 ++ win32/common/libgstvideo.def | 39 + 12 files changed, 5714 insertions(+), 2 deletions(-) create mode 100644 gst-libs/gst/video/TODO create mode 100644 gst-libs/gst/video/gstvideodecoder.c create mode 100644 gst-libs/gst/video/gstvideodecoder.h create mode 100644 gst-libs/gst/video/gstvideoencoder.c create mode 100644 gst-libs/gst/video/gstvideoencoder.h create mode 100644 gst-libs/gst/video/gstvideoutils.c create mode 100644 gst-libs/gst/video/gstvideoutils.h diff --git a/docs/libs/gst-plugins-base-libs-docs.sgml b/docs/libs/gst-plugins-base-libs-docs.sgml index 1fc3e83ea7..523b2fe4a4 100644 --- a/docs/libs/gst-plugins-base-libs-docs.sgml +++ b/docs/libs/gst-plugins-base-libs-docs.sgml @@ -228,6 +228,9 @@ + + + diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 9fb1d08951..41f1ba6aa1 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -2302,6 +2302,7 @@ GST_VIDEO_FORMAT_INFO_IS_GRAY GST_VIDEO_FORMAT_INFO_IS_LE GST_VIDEO_FORMAT_INFO_IS_RGB GST_VIDEO_FORMAT_INFO_IS_YUV +GST_VIDEO_FORMAT_INFO_IS_COMPLEX GST_VIDEO_FORMAT_INFO_NAME GST_VIDEO_FORMAT_INFO_N_COMPONENTS GST_VIDEO_FORMAT_INFO_N_PLANES @@ -2509,6 +2510,123 @@ GST_IS_VIDEO_SINK_CLASS gst_video_sink_get_type
+
+gstvideodecoder +GstVideoDecoder +GST_VIDEO_DECODER_ERROR +GST_VIDEO_DECODER_FLOW_DROPPED +GST_VIDEO_DECODER_FLOW_NEED_DATA +GST_VIDEO_DECODER_MAX_ERRORS +GST_VIDEO_DECODER_SINK_NAME +GST_VIDEO_DECODER_SINK_PAD +GST_VIDEO_DECODER_SRC_NAME +GST_VIDEO_DECODER_SRC_PAD +GST_VIDEO_DECODER_STREAM_LOCK +GST_VIDEO_DECODER_STREAM_UNLOCK +GST_VIDEO_DECODER_INPUT_SEGMENT +GST_VIDEO_DECODER_OUTPUT_SEGMENT +GstVideoDecoder +GstVideoDecoderClass +gst_video_decoder_add_to_frame +gst_video_decoder_alloc_output_buffer +gst_video_decoder_alloc_output_frame +gst_video_decoder_drop_frame +gst_video_decoder_finish_frame +gst_video_decoder_get_frame +gst_video_decoder_get_max_decode_time +gst_video_decoder_get_max_errors +gst_video_decoder_get_oldest_frame +gst_video_decoder_get_packetized +gst_video_decoder_have_frame +gst_video_decoder_get_latency +gst_video_decoder_set_latency +gst_video_decoder_get_estimate_rate +gst_video_decoder_get_output_state +gst_video_decoder_set_estimate_rate +gst_video_decoder_set_output_state +gst_video_decoder_set_max_errors +gst_video_decoder_set_packetized + +GST_IS_VIDEO_DECODER +GST_IS_VIDEO_DECODER_CLASS +GST_TYPE_VIDEO_DECODER +GST_VIDEO_DECODER +GST_VIDEO_DECODER_CLASS +GST_VIDEO_DECODER_GET_CLASS +GstVideoDecoderPrivate +gst_video_decoder_get_type +
+ +
+gstvideoencoder +GstVideoEncoder +GST_VIDEO_ENCODER_CAST +GST_VIDEO_ENCODER_FLOW_DROPPED +GST_VIDEO_ENCODER_FLOW_NEED_DATA +GST_VIDEO_ENCODER_SINK_NAME +GST_VIDEO_ENCODER_SINK_PAD +GST_VIDEO_ENCODER_SRC_NAME +GST_VIDEO_ENCODER_SRC_PAD +GST_VIDEO_ENCODER_INPUT_SEGMENT +GST_VIDEO_ENCODER_OUTPUT_SEGMENT +GST_VIDEO_ENCODER_STREAM_LOCK +GST_VIDEO_ENCODER_STREAM_UNLOCK +GstVideoEncoder +GstVideoEncoderClass +gst_video_encoder_finish_frame +gst_video_encoder_get_frame +gst_video_encoder_get_oldest_frame +gst_video_encoder_set_headers +gst_video_encoder_get_latency +gst_video_encoder_set_latency +gst_video_encoder_get_discont +gst_video_encoder_set_discont +gst_video_encoder_set_output_state +gst_video_encoder_get_output_state +gst_video_encoder_proxy_getcaps + +GST_IS_VIDEO_ENCODER +GST_IS_VIDEO_ENCODER_CLASS +GST_TYPE_VIDEO_ENCODER +GST_VIDEO_ENCODER +GST_VIDEO_ENCODER_CLASS +GST_VIDEO_ENCODER_GET_CLASS +GstVideoEncoderPrivate +gst_video_encoder_get_type +
+ +
+gstvideoutils +GstVideoCodecFrame +GstVideoCodecFrameFlags +GST_VIDEO_CODEC_FRAME_FLAGS +GST_VIDEO_CODEC_FRAME_FLAG_IS_SET +GST_VIDEO_CODEC_FRAME_FLAG_SET +GST_VIDEO_CODEC_FRAME_FLAG_UNSET +GST_VIDEO_CODEC_FRAME_IS_DECODE_ONLY +GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME +GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME_HEADERS +GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT +GST_VIDEO_CODEC_FRAME_SET_DECODE_ONLY +GST_VIDEO_CODEC_FRAME_SET_FORCE_KEYFRAME +GST_VIDEO_CODEC_FRAME_SET_FORCE_KEYFRAME_HEADERS +GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT +GST_VIDEO_CODEC_FRAME_UNSET_FORCE_KEYFRAME +GST_VIDEO_CODEC_FRAME_UNSET_FORCE_KEYFRAME_HEADERS +GST_VIDEO_CODEC_FRAME_UNSET_SYNC_POINT +gst_video_codec_frame_ref +gst_video_codec_frame_unref +gst_video_codec_frame_set_hook +GstVideoCodecState +gst_video_codec_state_ref +gst_video_codec_state_unref + +GST_TYPE_VIDEO_CODEC_FRAME +GST_TYPE_VIDEO_CODEC_STATE +gst_video_codec_frame_get_type +gst_video_codec_state_get_type +
+
gstdiscoverer gst/pbutils/pbutils.h diff --git a/docs/libs/gst-plugins-base-libs.types b/docs/libs/gst-plugins-base-libs.types index 1ea70464ce..ea1cf665ed 100644 --- a/docs/libs/gst-plugins-base-libs.types +++ b/docs/libs/gst-plugins-base-libs.types @@ -63,6 +63,13 @@ gst_video_sink_get_type #include gst_video_overlay_rectangle_get_type gst_video_overlay_composition_get_type +#include +gst_video_decoder_get_type +#include +gst_video_encoder_get_type +#include +gst_video_codec_frame_get_type +gst_video_codec_state_get_type #include gst_discoverer_get_type diff --git a/gst-libs/gst/video/Makefile.am b/gst-libs/gst/video/Makefile.am index 730699540e..1b6d45e045 100644 --- a/gst-libs/gst/video/Makefile.am +++ b/gst-libs/gst/video/Makefile.am @@ -20,13 +20,17 @@ CLEANFILES = $(BUILT_SOURCES) # video-blend.h should be disted but not installed into the includedir libgstvideo_@GST_MAJORMINOR@_la_SOURCES = \ video.c gstvideosink.c gstvideofilter.c convertframe.c \ - video-blend.c video-blend.h video-overlay-composition.c + video-blend.c video-blend.h video-overlay-composition.c \ + gstvideodecoder.c gstvideoencoder.c \ + gstvideoutils.c nodist_libgstvideo_@GST_MAJORMINOR@_la_SOURCES = \ $(built_sources) $(built_headers) \ $(ORC_NODIST_SOURCES) libgstvideo_@GST_MAJORMINOR@includedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/video libgstvideo_@GST_MAJORMINOR@include_HEADERS = \ - video.h gstvideosink.h gstvideofilter.h video-overlay-composition.h + video.h gstvideosink.h gstvideofilter.h video-overlay-composition.h \ + gstvideodecoder.h gstvideoencoder.h \ + gstvideoutils.h nodist_libgstvideo_@GST_MAJORMINOR@include_HEADERS = $(built_headers) libgstvideo_@GST_MAJORMINOR@_la_CFLAGS = \ @@ -63,6 +67,7 @@ GstVideo-@GST_MAJORMINOR@.gir: $(INTROSPECTION_SCANNER) libgstvideo-@GST_MAJORMI $(INTROSPECTION_SCANNER) -v --namespace GstVideo \ --nsversion=@GST_MAJORMINOR@ \ --strip-prefix=Gst \ + --warn-all \ $(gir_cincludes) \ -I$(top_srcdir)/gst-libs \ -I$(top_builddir)/gst-libs \ diff --git a/gst-libs/gst/video/TODO b/gst-libs/gst/video/TODO new file mode 100644 index 0000000000..4770de1d6c --- /dev/null +++ b/gst-libs/gst/video/TODO @@ -0,0 +1,51 @@ +Base Video Classes TODO + +Main goal: + + Make the video encoder/decoder base classes more consistent with the +other GStreamer API, especially with the audio encoder/decoder base +classes. + +API: + + The API should be named similar, the base classes should be used +similar by the sub-classes, the behaviour of the base classes should +be similar. + Currently there are many, mostly small, differences between the audio +and video base classes API. Things like GstVideoState should be merged +with the stuff we have in 0.11 to make the API the same in 0.11 and +0.10 as far as possible, things like GstVideoInfo might make sense to +be backported (at least partially). + + +Specifics: + * Use a GInstancePrivate for extensability. + + * Try to move more common video objects to video.[ch] + +Known bugs: + https://bugzilla.gnome.org/show_bug.cgi?id=664127 + [basevideodecoder] Add separate drain vfunc and differentiate + between hard/soft reset in reset vfunc + + https://bugzilla.gnome.org/show_bug.cgi?id=660770 + Doesn't support handling of "partial" frames + + https://bugzilla.gnome.org/show_bug.cgi?id=654294 + Impossible to flush pending frames in ::set_format + + https://bugzilla.gnome.org/show_bug.cgi?id=658241 + add API to handle QoS events and dropping logic + + https://bugzilla.gnome.org/show_bug.cgi?id=667653 + Autodetect multicore/multithread processors + + https://bugzilla.gnome.org/show_bug.cgi?id=617021 + Add support for QoS messages to -bad decoders and other elements + + +Peripheral issues + https://bugzilla.gnome.org/show_bug.cgi?id=663262 + theoraenc: spurious encoder resets due to unstable upstream + timestamps cause quality issues + => Investigate how to specify/handle variable framerates. diff --git a/gst-libs/gst/video/gstvideodecoder.c b/gst-libs/gst/video/gstvideodecoder.c new file mode 100644 index 0000000000..4b71cb1b04 --- /dev/null +++ b/gst-libs/gst/video/gstvideodecoder.c @@ -0,0 +1,2696 @@ +/* GStreamer + * Copyright (C) 2008 David Schleef + * Copyright (C) 2011 Mark Nauwelaerts . + * Copyright (C) 2011 Nokia Corporation. All rights reserved. + * Contact: Stefan Kost + * Copyright (C) 2012 Collabora Ltd. + * Author : Edward Hervey + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gstvideodecoder + * @short_description: Base class for video decoders + * @see_also: + * + * This base class is for video decoders turning encoded data into raw video + * frames. + * + * GstVideoDecoder and subclass should cooperate as follows. + * + * + * Configuration + * + * Initially, GstVideoDecoder calls @start when the decoder element + * is activated, which allows subclass to perform any global setup. + * + * + * GstVideoDecoder calls @set_format to inform subclass of caps + * describing input video data that it is about to receive, including + * possibly configuration data. + * While unlikely, it might be called more than once, if changing input + * parameters require reconfiguration. + * + * + * GstVideoDecoder calls @stop at end of all processing. + * + * + * + * + * + * Data processing + * + * Base class gathers input data, and optionally allows subclass + * to parse this into subsequently manageable chunks, typically + * corresponding to and referred to as 'frames'. + * + * + * Input frame is provided to subclass' @handle_frame. The ownership of + * the frame is given to @handle_frame. + * + * + * If codec processing results in decoded data, subclass should call + * @gst_video_decoder_finish_frame to have decoded data pushed + * downstream. + * + * + * + * + * Shutdown phase + * + * GstVideoDecoder class calls @stop to inform the subclass that data + * parsing will be stopped. + * + * + * + * + * + * Subclass is responsible for providing pad template caps for + * source and sink pads. The pads need to be named "sink" and "src". It also + * needs to set the fixed caps on srcpad, when the format is ensured. This + * is typically when base class calls subclass' @set_format function, though + * it might be delayed until calling @gst_video_decoder_finish_frame. + * + * Subclass is also responsible for providing (presentation) timestamps + * (likely based on corresponding input ones). If that is not applicable + * or possible, baseclass provides limited framerate based interpolation. + * + * Similarly, the baseclass provides some limited (legacy) seeking support + * (upon explicit subclass request), as full-fledged support + * should rather be left to upstream demuxer, parser or alike. This simple + * approach caters for seeking and duration reporting using estimated input + * bitrates. + * + * Baseclass provides some support for reverse playback, in particular + * in case incoming data is not packetized or upstream does not provide + * fragments on keyframe boundaries. However, subclass should then be prepared + * for the parsing and frame processing stage to occur separately (rather + * than otherwise the latter immediately following the former), + * and should ensure the parsing stage properly marks keyframes or rely on + * upstream to do so properly for incoming data. + * + * Things that subclass need to take care of: + * + * Provide pad templates + * + * Set source pad caps when appropriate + * + * + * Configure some baseclass behaviour parameters. + * + * + * Optionally parse input data, if it is not considered packetized. + * Data will be provided to @parse which should invoke @gst_video_decoder_add_to_frame and + * @gst_video_decoder_have_frame as appropriate. + * + * + * Accept data in @handle_frame and provide decoded results to + * @gst_video_decoder_finish_frame. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* TODO + * + * * Add a flag/boolean for I-frame-only/image decoders so we can do extra + * features, like applying QoS on input (as opposed to after the frame is + * decoded). + * * Add a flag/boolean for decoders that require keyframes, so the base + * class can automatically discard non-keyframes before one has arrived + * * Detect reordered frame/timestamps and fix the pts/dts + * * Support for GstIndex (or shall we not care ?) + * * Calculate actual latency based on input/output timestamp/frame_number + * and if it exceeds the recorded one, save it and emit a GST_MESSAGE_LATENCY + * * Emit latency message when it changes + * + */ + +/* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex + * with newer GLib versions (>= 2.31.0) */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "gstvideodecoder.h" +#include "gstvideoutils.h" + +#include + +GST_DEBUG_CATEGORY (videodecoder_debug); +#define GST_CAT_DEFAULT videodecoder_debug + +#define GST_VIDEO_DECODER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_VIDEO_DECODER, \ + GstVideoDecoderPrivate)) + +struct _GstVideoDecoderPrivate +{ + /* FIXME introduce a context ? */ + + /* parse tracking */ + /* input data */ + GstAdapter *input_adapter; + /* assembles current frame */ + GstAdapter *output_adapter; + + /* Whether we attempt to convert newsegment from bytes to + * time using a bitrate estimation */ + gboolean do_estimate_rate; + + /* Whether input is considered packetized or not */ + gboolean packetized; + + /* Error handling */ + gint max_errors; + gint error_count; + + /* ... being tracked here; + * only available during parsing */ + GstVideoCodecFrame *current_frame; + /* events that should apply to the current frame */ + GList *current_frame_events; + + /* relative offset of input data */ + guint64 input_offset; + /* relative offset of frame */ + guint64 frame_offset; + /* tracking ts and offsets */ + GList *timestamps; + + /* combine to yield (presentation) ts */ + GstClockTime timestamp_offset; + + /* last outgoing ts */ + GstClockTime last_timestamp; + + /* reverse playback */ + /* collect input */ + GList *gather; + /* to-be-parsed */ + GList *parse; + /* collected parsed frames */ + GList *parse_gather; + /* frames to be handled == decoded */ + GList *decode; + /* collected output */ + GList *queued; + /* Used internally for avoiding processing of frames to flush */ + gboolean process; + + + /* FIXME : base_picture_number is never set */ + guint64 base_picture_number; + /* FIXME : reorder_depth is never set */ + int reorder_depth; + int distance_from_sync; + + guint64 system_frame_number; + guint64 decode_frame_number; + + GList *frames; /* Protected with OBJECT_LOCK */ + GstVideoCodecState *input_state; + GstVideoCodecState *output_state; + gboolean output_state_changed; + + /* QoS properties */ + gdouble proportion; + GstClockTime earliest_time; + gboolean discont; + /* qos messages: frames dropped/processed */ + guint dropped; + guint processed; + + /* Outgoing byte size ? */ + gint64 bytes_out; + gint64 time; + + gint64 min_latency; + gint64 max_latency; +}; + +static void gst_video_decoder_finalize (GObject * object); + +static gboolean gst_video_decoder_sink_setcaps (GstPad * pad, GstCaps * caps); +static gboolean gst_video_decoder_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_video_decoder_src_event (GstPad * pad, GstEvent * event); +static GstFlowReturn gst_video_decoder_chain (GstPad * pad, GstBuffer * buf); +static gboolean gst_video_decoder_sink_query (GstPad * pad, GstQuery * query); +static GstStateChangeReturn +gst_video_decoder_change_state (GstElement * element, + GstStateChange transition); +static const GstQueryType *gst_video_decoder_get_query_types (GstPad * pad); +static gboolean gst_video_decoder_src_query (GstPad * pad, GstQuery * query); +static void gst_video_decoder_reset (GstVideoDecoder * decoder, gboolean full); + +static GstFlowReturn gst_video_decoder_have_frame_2 (GstVideoDecoder * decoder); +static gboolean gst_video_decoder_set_src_caps (GstVideoDecoder * decoder); + +static guint64 +gst_video_decoder_get_timestamp (GstVideoDecoder * decoder, int picture_number); +static guint64 gst_video_decoder_get_frame_duration (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame); +static GstVideoCodecFrame *gst_video_decoder_new_frame (GstVideoDecoder * + decoder); + +static void gst_video_decoder_clear_queues (GstVideoDecoder * dec); + +GST_BOILERPLATE (GstVideoDecoder, gst_video_decoder, + GstElement, GST_TYPE_ELEMENT); + +static void +gst_video_decoder_base_init (gpointer g_class) +{ + GST_DEBUG_CATEGORY_INIT (videodecoder_debug, "videodecoder", 0, + "Base Video Decoder"); +} + +static void +gst_video_decoder_class_init (GstVideoDecoderClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GstVideoDecoderPrivate)); + + gobject_class->finalize = gst_video_decoder_finalize; + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_video_decoder_change_state); +} + +static void +gst_video_decoder_init (GstVideoDecoder * decoder, GstVideoDecoderClass * klass) +{ + GstPadTemplate *pad_template; + GstPad *pad; + + GST_DEBUG_OBJECT (decoder, "gst_video_decoder_init"); + + decoder->priv = GST_VIDEO_DECODER_GET_PRIVATE (decoder); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "sink"); + g_return_if_fail (pad_template != NULL); + + decoder->sinkpad = pad = gst_pad_new_from_template (pad_template, "sink"); + + gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_video_decoder_chain)); + gst_pad_set_event_function (pad, + GST_DEBUG_FUNCPTR (gst_video_decoder_sink_event)); + gst_pad_set_setcaps_function (pad, + GST_DEBUG_FUNCPTR (gst_video_decoder_sink_setcaps)); + gst_pad_set_query_function (pad, + GST_DEBUG_FUNCPTR (gst_video_decoder_sink_query)); + gst_element_add_pad (GST_ELEMENT (decoder), decoder->sinkpad); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "src"); + g_return_if_fail (pad_template != NULL); + + decoder->srcpad = pad = gst_pad_new_from_template (pad_template, "src"); + + gst_pad_set_event_function (pad, + GST_DEBUG_FUNCPTR (gst_video_decoder_src_event)); + gst_pad_set_query_type_function (pad, + GST_DEBUG_FUNCPTR (gst_video_decoder_get_query_types)); + gst_pad_set_query_function (pad, + GST_DEBUG_FUNCPTR (gst_video_decoder_src_query)); + gst_pad_use_fixed_caps (pad); + gst_element_add_pad (GST_ELEMENT (decoder), decoder->srcpad); + + gst_segment_init (&decoder->input_segment, GST_FORMAT_TIME); + gst_segment_init (&decoder->output_segment, GST_FORMAT_TIME); + + g_static_rec_mutex_init (&decoder->stream_lock); + + decoder->priv->input_adapter = gst_adapter_new (); + decoder->priv->output_adapter = gst_adapter_new (); + decoder->priv->packetized = TRUE; + + gst_video_decoder_reset (decoder, TRUE); +} + +static gboolean +gst_video_rawvideo_convert (GstVideoCodecState * state, + GstFormat src_format, gint64 src_value, + GstFormat * dest_format, gint64 * dest_value) +{ + gboolean res = FALSE; + guint vidsize; + guint fps_n, fps_d; + + g_return_val_if_fail (dest_format != NULL, FALSE); + g_return_val_if_fail (dest_value != NULL, FALSE); + + if (src_format == *dest_format || src_value == 0 || src_value == -1) { + *dest_value = src_value; + return TRUE; + } + + vidsize = GST_VIDEO_INFO_SIZE (&state->info); + fps_n = GST_VIDEO_INFO_FPS_N (&state->info); + fps_d = GST_VIDEO_INFO_FPS_D (&state->info); + + if (src_format == GST_FORMAT_BYTES && + *dest_format == GST_FORMAT_DEFAULT && vidsize) { + /* convert bytes to frames */ + *dest_value = gst_util_uint64_scale_int (src_value, 1, vidsize); + res = TRUE; + } else if (src_format == GST_FORMAT_DEFAULT && + *dest_format == GST_FORMAT_BYTES && vidsize) { + /* convert bytes to frames */ + *dest_value = src_value * vidsize; + res = TRUE; + } else if (src_format == GST_FORMAT_DEFAULT && + *dest_format == GST_FORMAT_TIME && fps_n) { + /* convert frames to time */ + /* FIXME add segment time? */ + *dest_value = gst_util_uint64_scale (src_value, GST_SECOND * fps_d, fps_n); + res = TRUE; + } else if (src_format == GST_FORMAT_TIME && + *dest_format == GST_FORMAT_DEFAULT && fps_d) { + /* convert time to frames */ + /* FIXME subtract segment time? */ + *dest_value = gst_util_uint64_scale (src_value, fps_n, GST_SECOND * fps_d); + res = TRUE; + } else if (src_format == GST_FORMAT_TIME && + *dest_format == GST_FORMAT_BYTES && fps_d && vidsize) { + /* convert time to frames */ + /* FIXME subtract segment time? */ + *dest_value = gst_util_uint64_scale (src_value, + fps_n * vidsize, GST_SECOND * fps_d); + res = TRUE; + } else if (src_format == GST_FORMAT_BYTES && + *dest_format == GST_FORMAT_TIME && fps_n && vidsize) { + /* convert frames to time */ + /* FIXME add segment time? */ + *dest_value = gst_util_uint64_scale (src_value, + GST_SECOND * fps_d, fps_n * vidsize); + res = TRUE; + } + + return res; +} + +static gboolean +gst_video_encoded_video_convert (gint64 bytes, gint64 time, + GstFormat src_format, gint64 src_value, GstFormat * dest_format, + gint64 * dest_value) +{ + gboolean res = FALSE; + + g_return_val_if_fail (dest_format != NULL, FALSE); + g_return_val_if_fail (dest_value != NULL, FALSE); + + if (G_UNLIKELY (src_format == *dest_format || src_value == 0 || + src_value == -1)) { + if (dest_value) + *dest_value = src_value; + return TRUE; + } + + if (bytes <= 0 || time <= 0) { + GST_DEBUG ("not enough metadata yet to convert"); + goto exit; + } + + switch (src_format) { + case GST_FORMAT_BYTES: + switch (*dest_format) { + case GST_FORMAT_TIME: + *dest_value = gst_util_uint64_scale (src_value, time, bytes); + res = TRUE; + break; + default: + res = FALSE; + } + break; + case GST_FORMAT_TIME: + switch (*dest_format) { + case GST_FORMAT_BYTES: + *dest_value = gst_util_uint64_scale (src_value, bytes, time); + res = TRUE; + break; + default: + res = FALSE; + } + break; + default: + GST_DEBUG ("unhandled conversion from %d to %d", src_format, + *dest_format); + res = FALSE; + } + +exit: + return res; +} + +static GstVideoCodecState * +_new_input_state (GstCaps * caps) +{ + GstVideoCodecState *state; + GstStructure *structure; + const GValue *codec_data; + + state = g_slice_new0 (GstVideoCodecState); + state->ref_count = 1; + gst_video_info_init (&state->info); + if (G_UNLIKELY (!gst_video_info_from_caps (&state->info, caps))) + goto parse_fail; + state->caps = gst_caps_ref (caps); + + structure = gst_caps_get_structure (caps, 0); + + codec_data = gst_structure_get_value (structure, "codec_data"); + if (codec_data && G_VALUE_TYPE (codec_data) == GST_TYPE_BUFFER) + state->codec_data = GST_BUFFER (gst_value_dup_mini_object (codec_data)); + + return state; + +parse_fail: + { + g_slice_free (GstVideoCodecState, state); + return NULL; + } +} + +static GstVideoCodecState * +_new_output_state (GstVideoFormat fmt, guint width, guint height, + GstVideoCodecState * reference) +{ + GstVideoCodecState *state; + + state = g_slice_new0 (GstVideoCodecState); + state->ref_count = 1; + gst_video_info_init (&state->info); + gst_video_info_set_format (&state->info, fmt, width, height); + + if (reference) { + GstVideoInfo *tgt, *ref; + + tgt = &state->info; + ref = &reference->info; + + /* Copy over extra fields from reference state */ + tgt->interlace_mode = ref->interlace_mode; + tgt->flags = ref->flags; + tgt->chroma_site = ref->chroma_site; + tgt->colorimetry = ref->colorimetry; + GST_DEBUG ("reference par %d/%d fps %d/%d", + ref->par_n, ref->par_d, ref->fps_n, ref->fps_d); + tgt->par_n = ref->par_n; + tgt->par_d = ref->par_d; + tgt->fps_n = ref->fps_n; + tgt->fps_d = ref->fps_d; + } + + GST_DEBUG ("reference par %d/%d fps %d/%d", + state->info.par_n, state->info.par_d, + state->info.fps_n, state->info.fps_d); + + return state; +} + +static gboolean +gst_video_decoder_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstVideoDecoder *decoder; + GstVideoDecoderClass *decoder_class; + GstVideoCodecState *state; + gboolean ret = TRUE; + + decoder = GST_VIDEO_DECODER (gst_pad_get_parent (pad)); + decoder_class = GST_VIDEO_DECODER_GET_CLASS (decoder); + + GST_DEBUG_OBJECT (decoder, "setcaps %" GST_PTR_FORMAT, caps); + + state = _new_input_state (caps); + + if (G_UNLIKELY (state == NULL)) + goto parse_fail; + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + + if (decoder_class->set_format) + ret = decoder_class->set_format (decoder, state); + + if (!ret) + goto refused_format; + + if (decoder->priv->input_state) + gst_video_codec_state_unref (decoder->priv->input_state); + decoder->priv->input_state = state; + + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + gst_object_unref (decoder); + + return ret; + + /* ERRORS */ + +parse_fail: + { + GST_WARNING_OBJECT (decoder, "Failed to parse caps"); + gst_object_unref (decoder); + return FALSE; + } + +refused_format: + { + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + GST_WARNING_OBJECT (decoder, "Subclass refused caps"); + gst_video_codec_state_unref (state); + return FALSE; + } +} + +static void +gst_video_decoder_finalize (GObject * object) +{ + GstVideoDecoder *decoder; + + decoder = GST_VIDEO_DECODER (object); + + GST_DEBUG_OBJECT (object, "finalize"); + + g_static_rec_mutex_free (&decoder->stream_lock); + + if (decoder->priv->input_adapter) { + g_object_unref (decoder->priv->input_adapter); + decoder->priv->input_adapter = NULL; + } + if (decoder->priv->output_adapter) { + g_object_unref (decoder->priv->output_adapter); + decoder->priv->output_adapter = NULL; + } + + if (decoder->priv->input_state) + gst_video_codec_state_unref (decoder->priv->input_state); + if (decoder->priv->output_state) + gst_video_codec_state_unref (decoder->priv->output_state); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* hard == FLUSH, otherwise discont */ +static GstFlowReturn +gst_video_decoder_flush (GstVideoDecoder * dec, gboolean hard) +{ + GstVideoDecoderClass *klass; + GstVideoDecoderPrivate *priv = dec->priv; + GstFlowReturn ret = GST_FLOW_OK; + + klass = GST_VIDEO_DECODER_GET_CLASS (dec); + + GST_LOG_OBJECT (dec, "flush hard %d", hard); + + /* Inform subclass */ + if (klass->reset) + klass->reset (dec, hard); + + /* FIXME make some more distinction between hard and soft, + * but subclass may not be prepared for that */ + /* FIXME perhaps also clear pending frames ?, + * but again, subclass may still come up with one of those */ + if (!hard) { + /* TODO ? finish/drain some stuff */ + } else { + gst_segment_init (&dec->input_segment, GST_FORMAT_UNDEFINED); + gst_segment_init (&dec->output_segment, GST_FORMAT_UNDEFINED); + gst_video_decoder_clear_queues (dec); + priv->error_count = 0; + g_list_foreach (priv->current_frame_events, (GFunc) gst_event_unref, NULL); + g_list_free (priv->current_frame_events); + priv->current_frame_events = NULL; + } + /* and get (re)set for the sequel */ + gst_video_decoder_reset (dec, FALSE); + + return ret; +} + +static gboolean +gst_video_decoder_sink_eventfunc (GstVideoDecoder * decoder, GstEvent * event) +{ + GstVideoDecoderClass *decoder_class; + GstVideoDecoderPrivate *priv; + gboolean handled = FALSE; + + priv = decoder->priv; + decoder_class = GST_VIDEO_DECODER_GET_CLASS (decoder); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + { + GstFlowReturn flow_ret = GST_FLOW_OK; + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + if (!priv->packetized) + while (flow_ret == GST_FLOW_OK && + gst_adapter_available (priv->input_adapter)) + flow_ret = + decoder_class->parse (decoder, priv->current_frame, + priv->input_adapter, TRUE); + + if (decoder_class->finish) { + flow_ret = decoder_class->finish (decoder); + } else { + flow_ret = GST_FLOW_OK; + } + + handled = (flow_ret == GST_VIDEO_DECODER_FLOW_DROPPED); + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + break; + } + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + double rate, arate; + GstFormat format; + gint64 start; + gint64 stop; + gint64 pos; + GstSegment *segment = &decoder->input_segment; + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + gst_event_parse_new_segment_full (event, &update, &rate, + &arate, &format, &start, &stop, &pos); + + if (format == GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (decoder, + "received TIME NEW_SEGMENT %" GST_TIME_FORMAT + " -- %" GST_TIME_FORMAT ", pos %" GST_TIME_FORMAT + ", rate %g, applied_rate %g", + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (pos), + rate, arate); + } else { + GstFormat dformat = GST_FORMAT_TIME; + + GST_DEBUG_OBJECT (decoder, + "received NEW_SEGMENT %" G_GINT64_FORMAT + " -- %" G_GINT64_FORMAT ", time %" G_GINT64_FORMAT + ", rate %g, applied_rate %g", start, stop, pos, rate, arate); + + /* handle newsegment as a result from our legacy simple seeking */ + /* note that initial 0 should convert to 0 in any case */ + if (priv->do_estimate_rate && + gst_pad_query_convert (decoder->sinkpad, GST_FORMAT_BYTES, start, + &dformat, &start)) { + /* best attempt convert */ + /* as these are only estimates, stop is kept open-ended to avoid + * premature cutting */ + GST_DEBUG_OBJECT (decoder, + "converted to TIME start %" GST_TIME_FORMAT, + GST_TIME_ARGS (start)); + pos = start; + stop = GST_CLOCK_TIME_NONE; + /* replace event */ + gst_event_unref (event); + event = gst_event_new_new_segment_full (update, rate, arate, + GST_FORMAT_TIME, start, stop, pos); + } else { + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + goto newseg_wrong_format; + } + } + + if (!update) { + gst_video_decoder_flush (decoder, FALSE); + } + + priv->timestamp_offset = start; + + gst_segment_set_newsegment_full (segment, + update, rate, arate, format, start, stop, pos); + + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + break; + } + case GST_EVENT_FLUSH_STOP: + { + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + /* well, this is kind of worse than a DISCONT */ + gst_video_decoder_flush (decoder, TRUE); + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + } + default: + break; + } + + return handled; + +newseg_wrong_format: + { + GST_DEBUG_OBJECT (decoder, "received non TIME newsegment"); + gst_event_unref (event); + /* SWALLOW EVENT */ + /* FIXME : Ideally we'd like to return FALSE in the event handler */ + return TRUE; + } +} + +static gboolean +gst_video_decoder_push_event (GstVideoDecoder * decoder, GstEvent * event) +{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + double rate; + double applied_rate; + GstFormat format; + gint64 start; + gint64 stop; + gint64 position; + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &position); + + GST_DEBUG_OBJECT (decoder, "newseg rate %g, applied rate %g, " + "format %d, start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, rate, applied_rate, format, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), + GST_TIME_ARGS (position)); + + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (decoder, "received non TIME newsegment"); + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + break; + } + + gst_segment_set_newsegment_full (&decoder->output_segment, update, rate, + applied_rate, format, start, stop, position); + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + break; + } + default: + break; + } + + return gst_pad_push_event (decoder->srcpad, event); +} + +static gboolean +gst_video_decoder_sink_event (GstPad * pad, GstEvent * event) +{ + GstVideoDecoder *decoder; + GstVideoDecoderClass *decoder_class; + gboolean ret = FALSE; + gboolean handled = FALSE; + + decoder = GST_VIDEO_DECODER (gst_pad_get_parent (pad)); + decoder_class = GST_VIDEO_DECODER_GET_CLASS (decoder); + + GST_DEBUG_OBJECT (decoder, "received event %d, %s", GST_EVENT_TYPE (event), + GST_EVENT_TYPE_NAME (event)); + + if (decoder_class->sink_event) + handled = decoder_class->sink_event (decoder, event); + + if (!handled) + handled = gst_video_decoder_sink_eventfunc (decoder, event); + + if (!handled) { + /* Forward non-serialized events and EOS/FLUSH_STOP immediately. + * For EOS this is required because no buffer or serialized event + * will come after EOS and nothing could trigger another + * _finish_frame() call. * + * If the subclass handles sending of EOS manually it can return + * _DROPPED from ::finish() and all other subclasses should have + * decoded/flushed all remaining data before this + * + * For FLUSH_STOP this is required because it is expected + * to be forwarded immediately and no buffers are queued anyway. + */ + if (!GST_EVENT_IS_SERIALIZED (event) + || GST_EVENT_TYPE (event) == GST_EVENT_EOS + || GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) { + ret = gst_video_decoder_push_event (decoder, event); + } else { + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + decoder->priv->current_frame_events = + g_list_prepend (decoder->priv->current_frame_events, event); + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + } + } + + gst_object_unref (decoder); + return ret; + +} + +/* perform upstream byte <-> time conversion (duration, seeking) + * if subclass allows and if enough data for moderately decent conversion */ +static inline gboolean +gst_video_decoder_do_byte (GstVideoDecoder * dec) +{ + return dec->priv->do_estimate_rate && (dec->priv->bytes_out > 0) + && (dec->priv->time > GST_SECOND); +} + +static gboolean +gst_video_decoder_do_seek (GstVideoDecoder * dec, GstEvent * event) +{ + GstSeekFlags flags; + GstSeekType start_type, end_type; + GstFormat format; + gdouble rate; + gint64 start, start_time, end_time; + GstSegment seek_segment; + guint32 seqnum; + + gst_event_parse_seek (event, &rate, &format, &flags, &start_type, + &start_time, &end_type, &end_time); + + /* we'll handle plain open-ended flushing seeks with the simple approach */ + if (rate != 1.0) { + GST_DEBUG_OBJECT (dec, "unsupported seek: rate"); + return FALSE; + } + + if (start_type != GST_SEEK_TYPE_SET) { + GST_DEBUG_OBJECT (dec, "unsupported seek: start time"); + return FALSE; + } + + if (end_type != GST_SEEK_TYPE_NONE || + (end_type == GST_SEEK_TYPE_SET && end_time != GST_CLOCK_TIME_NONE)) { + GST_DEBUG_OBJECT (dec, "unsupported seek: end time"); + return FALSE; + } + + if (!(flags & GST_SEEK_FLAG_FLUSH)) { + GST_DEBUG_OBJECT (dec, "unsupported seek: not flushing"); + return FALSE; + } + + memcpy (&seek_segment, &dec->output_segment, sizeof (seek_segment)); + gst_segment_set_seek (&seek_segment, rate, format, flags, start_type, + start_time, end_type, end_time, NULL); + start_time = seek_segment.last_stop; + + format = GST_FORMAT_BYTES; + if (!gst_pad_query_convert (dec->sinkpad, GST_FORMAT_TIME, start_time, + &format, &start)) { + GST_DEBUG_OBJECT (dec, "conversion failed"); + return FALSE; + } + + seqnum = gst_event_get_seqnum (event); + event = gst_event_new_seek (1.0, GST_FORMAT_BYTES, flags, + GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE, -1); + gst_event_set_seqnum (event, seqnum); + + GST_DEBUG_OBJECT (dec, "seeking to %" GST_TIME_FORMAT " at byte offset %" + G_GINT64_FORMAT, GST_TIME_ARGS (start_time), start); + + return gst_pad_push_event (dec->sinkpad, event); +} + +static gboolean +gst_video_decoder_src_event (GstPad * pad, GstEvent * event) +{ + GstVideoDecoder *decoder; + GstVideoDecoderPrivate *priv; + gboolean res = FALSE; + + decoder = GST_VIDEO_DECODER (gst_pad_get_parent (pad)); + priv = decoder->priv; + + GST_DEBUG_OBJECT (decoder, + "received event %d, %s", GST_EVENT_TYPE (event), + GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + GstFormat format, tformat; + gdouble rate; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop; + gint64 tcur, tstop; + guint32 seqnum; + + gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, + &stop_type, &stop); + seqnum = gst_event_get_seqnum (event); + + /* upstream gets a chance first */ + if ((res = gst_pad_push_event (decoder->sinkpad, event))) + break; + + /* if upstream fails for a time seek, maybe we can help if allowed */ + if (format == GST_FORMAT_TIME) { + if (gst_video_decoder_do_byte (decoder)) + res = gst_video_decoder_do_seek (decoder, event); + break; + } + + /* ... though a non-time seek can be aided as well */ + /* First bring the requested format to time */ + tformat = GST_FORMAT_TIME; + if (!(res = gst_pad_query_convert (pad, format, cur, &tformat, &tcur))) + goto convert_error; + if (!(res = gst_pad_query_convert (pad, format, stop, &tformat, &tstop))) + goto convert_error; + + /* then seek with time on the peer */ + event = gst_event_new_seek (rate, GST_FORMAT_TIME, + flags, cur_type, tcur, stop_type, tstop); + gst_event_set_seqnum (event, seqnum); + + res = gst_pad_push_event (decoder->sinkpad, event); + break; + } + case GST_EVENT_QOS: + { + gdouble proportion; + GstClockTimeDiff diff; + GstClockTime timestamp; + GstClockTime duration; + + gst_event_parse_qos (event, &proportion, &diff, ×tamp); + + GST_OBJECT_LOCK (decoder); + priv->proportion = proportion; + if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (timestamp))) { + if (G_UNLIKELY (diff > 0)) { + if (priv->output_state->info.fps_n > 0) + duration = + gst_util_uint64_scale (GST_SECOND, + priv->output_state->info.fps_d, priv->output_state->info.fps_n); + else + duration = 0; + priv->earliest_time = timestamp + 2 * diff + duration; + } else { + priv->earliest_time = timestamp + diff; + } + } else { + priv->earliest_time = GST_CLOCK_TIME_NONE; + } + GST_OBJECT_UNLOCK (decoder); + + GST_DEBUG_OBJECT (decoder, + "got QoS %" GST_TIME_FORMAT ", %" G_GINT64_FORMAT ", %g", + GST_TIME_ARGS (timestamp), diff, proportion); + + res = gst_pad_push_event (decoder->sinkpad, event); + break; + } + default: + res = gst_pad_push_event (decoder->sinkpad, event); + break; + } +done: + gst_object_unref (decoder); + return res; + +convert_error: + GST_DEBUG_OBJECT (decoder, "could not convert format"); + goto done; +} + +static const GstQueryType * +gst_video_decoder_get_query_types (GstPad * pad) +{ + static const GstQueryType query_types[] = { + GST_QUERY_POSITION, + GST_QUERY_DURATION, + GST_QUERY_CONVERT, + GST_QUERY_LATENCY, + 0 + }; + + return query_types; +} + +static gboolean +gst_video_decoder_src_query (GstPad * pad, GstQuery * query) +{ + GstVideoDecoder *dec; + gboolean res = TRUE; + + dec = GST_VIDEO_DECODER (gst_pad_get_parent (pad)); + + GST_LOG_OBJECT (dec, "handling query: %" GST_PTR_FORMAT, query); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + GstFormat format; + gint64 time, value; + + /* upstream gets a chance first */ + if ((res = gst_pad_peer_query (dec->sinkpad, query))) { + GST_LOG_OBJECT (dec, "returning peer response"); + break; + } + + /* we start from the last seen time */ + time = dec->priv->last_timestamp; + /* correct for the segment values */ + time = gst_segment_to_stream_time (&dec->output_segment, + GST_FORMAT_TIME, time); + + GST_LOG_OBJECT (dec, + "query %p: our time: %" GST_TIME_FORMAT, query, GST_TIME_ARGS (time)); + + /* and convert to the final format */ + gst_query_parse_position (query, &format, NULL); + if (!(res = gst_pad_query_convert (pad, GST_FORMAT_TIME, time, + &format, &value))) + break; + + gst_query_set_position (query, format, value); + + GST_LOG_OBJECT (dec, + "query %p: we return %" G_GINT64_FORMAT " (format %u)", query, value, + format); + break; + } + case GST_QUERY_DURATION: + { + GstFormat format; + + /* upstream in any case */ + if ((res = gst_pad_query_default (pad, query))) + break; + + gst_query_parse_duration (query, &format, NULL); + /* try answering TIME by converting from BYTE if subclass allows */ + if (format == GST_FORMAT_TIME && gst_video_decoder_do_byte (dec)) { + gint64 value; + + format = GST_FORMAT_BYTES; + if (gst_pad_query_peer_duration (dec->sinkpad, &format, &value)) { + GST_LOG_OBJECT (dec, "upstream size %" G_GINT64_FORMAT, value); + format = GST_FORMAT_TIME; + if (gst_pad_query_convert (dec->sinkpad, + GST_FORMAT_BYTES, value, &format, &value)) { + gst_query_set_duration (query, GST_FORMAT_TIME, value); + res = TRUE; + } + } + } + break; + } + case GST_QUERY_CONVERT: + { + GstFormat src_fmt, dest_fmt; + gint64 src_val, dest_val; + + GST_DEBUG_OBJECT (dec, "convert query"); + + gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); + res = gst_video_rawvideo_convert (dec->priv->output_state, + src_fmt, src_val, &dest_fmt, &dest_val); + if (!res) + goto error; + gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); + break; + } + case GST_QUERY_LATENCY: + { + gboolean live; + GstClockTime min_latency, max_latency; + + res = gst_pad_peer_query (dec->sinkpad, query); + if (res) { + gst_query_parse_latency (query, &live, &min_latency, &max_latency); + GST_DEBUG_OBJECT (dec, "Peer latency: live %d, min %" + GST_TIME_FORMAT " max %" GST_TIME_FORMAT, live, + GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); + + GST_OBJECT_LOCK (dec); + min_latency += dec->priv->min_latency; + if (max_latency != GST_CLOCK_TIME_NONE) { + max_latency += dec->priv->max_latency; + } + GST_OBJECT_UNLOCK (dec); + + gst_query_set_latency (query, live, min_latency, max_latency); + } + } + break; + default: + res = gst_pad_query_default (pad, query); + } + gst_object_unref (dec); + return res; + +error: + GST_ERROR_OBJECT (dec, "query failed"); + gst_object_unref (dec); + return res; +} + +static gboolean +gst_video_decoder_sink_query (GstPad * pad, GstQuery * query) +{ + GstVideoDecoder *decoder; + GstVideoDecoderPrivate *priv; + gboolean res = FALSE; + + decoder = GST_VIDEO_DECODER (gst_pad_get_parent (pad)); + priv = decoder->priv; + + GST_LOG_OBJECT (decoder, "handling query: %" GST_PTR_FORMAT, query); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONVERT: + { + GstFormat src_fmt, dest_fmt; + gint64 src_val, dest_val; + + gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); + res = + gst_video_encoded_video_convert (priv->bytes_out, priv->time, src_fmt, + src_val, &dest_fmt, &dest_val); + if (!res) + goto error; + gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } +done: + gst_object_unref (decoder); + + return res; +error: + GST_DEBUG_OBJECT (decoder, "query failed"); + goto done; +} + +typedef struct _Timestamp Timestamp; +struct _Timestamp +{ + guint64 offset; + GstClockTime timestamp; + GstClockTime duration; +}; + +static void +gst_video_decoder_add_timestamp (GstVideoDecoder * decoder, GstBuffer * buffer) +{ + GstVideoDecoderPrivate *priv = decoder->priv; + Timestamp *ts; + + ts = g_malloc (sizeof (Timestamp)); + + GST_LOG_OBJECT (decoder, + "adding timestamp %" GST_TIME_FORMAT " (offset:%" G_GUINT64_FORMAT ")", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), priv->input_offset); + + ts->offset = priv->input_offset; + ts->timestamp = GST_BUFFER_TIMESTAMP (buffer); + ts->duration = GST_BUFFER_DURATION (buffer); + + priv->timestamps = g_list_append (priv->timestamps, ts); +} + +static void +gst_video_decoder_get_timestamp_at_offset (GstVideoDecoder * + decoder, guint64 offset, GstClockTime * timestamp, GstClockTime * duration) +{ + Timestamp *ts; + GList *g; + + *timestamp = GST_CLOCK_TIME_NONE; + *duration = GST_CLOCK_TIME_NONE; + + g = decoder->priv->timestamps; + while (g) { + ts = g->data; + if (ts->offset <= offset) { + *timestamp = ts->timestamp; + *duration = ts->duration; + g_free (ts); + g = g->next; + decoder->priv->timestamps = g_list_remove (decoder->priv->timestamps, ts); + } else { + break; + } + } + + GST_LOG_OBJECT (decoder, + "got timestamp %" GST_TIME_FORMAT " (offset:%" G_GUINT64_FORMAT ")", + GST_TIME_ARGS (*timestamp), offset); +} + +static void +gst_video_decoder_clear_queues (GstVideoDecoder * dec) +{ + GstVideoDecoderPrivate *priv = dec->priv; + + g_list_foreach (priv->queued, (GFunc) gst_mini_object_unref, NULL); + g_list_free (priv->queued); + priv->queued = NULL; + g_list_foreach (priv->gather, (GFunc) gst_mini_object_unref, NULL); + g_list_free (priv->gather); + priv->gather = NULL; + g_list_foreach (priv->decode, (GFunc) gst_video_codec_frame_unref, NULL); + g_list_free (priv->decode); + priv->decode = NULL; + g_list_foreach (priv->parse, (GFunc) gst_mini_object_unref, NULL); + g_list_free (priv->parse); + priv->parse = NULL; + g_list_foreach (priv->parse_gather, (GFunc) gst_video_codec_frame_unref, + NULL); + g_list_free (priv->parse_gather); + priv->parse_gather = NULL; + g_list_foreach (priv->frames, (GFunc) gst_video_codec_frame_unref, NULL); + g_list_free (priv->frames); + priv->frames = NULL; +} + +static void +gst_video_decoder_reset (GstVideoDecoder * decoder, gboolean full) +{ + GstVideoDecoderPrivate *priv = decoder->priv; + GList *g; + + GST_DEBUG_OBJECT (decoder, "reset full %d", full); + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + + if (full) { + gst_segment_init (&decoder->input_segment, GST_FORMAT_UNDEFINED); + gst_segment_init (&decoder->output_segment, GST_FORMAT_UNDEFINED); + gst_video_decoder_clear_queues (decoder); + priv->error_count = 0; + priv->max_errors = GST_VIDEO_DECODER_MAX_ERRORS; + if (priv->input_state) + gst_video_codec_state_unref (priv->input_state); + priv->input_state = NULL; + if (priv->output_state) + gst_video_codec_state_unref (priv->output_state); + priv->output_state = NULL; + priv->min_latency = 0; + priv->max_latency = 0; + } + + priv->discont = TRUE; + + priv->timestamp_offset = GST_CLOCK_TIME_NONE; + priv->last_timestamp = GST_CLOCK_TIME_NONE; + + priv->input_offset = 0; + priv->frame_offset = 0; + gst_adapter_clear (priv->input_adapter); + gst_adapter_clear (priv->output_adapter); + g_list_foreach (priv->timestamps, (GFunc) g_free, NULL); + g_list_free (priv->timestamps); + priv->timestamps = NULL; + + if (priv->current_frame) { + gst_video_codec_frame_unref (priv->current_frame); + priv->current_frame = NULL; + } + + priv->dropped = 0; + priv->processed = 0; + + priv->decode_frame_number = 0; + priv->base_picture_number = 0; + for (g = priv->frames; g; g = g->next) { + gst_video_codec_frame_unref ((GstVideoCodecFrame *) g->data); + } + g_list_free (priv->frames); + priv->frames = NULL; + + priv->bytes_out = 0; + priv->time = 0; + + priv->earliest_time = GST_CLOCK_TIME_NONE; + priv->proportion = 0.5; + + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); +} + +static GstFlowReturn +gst_video_decoder_chain_forward (GstVideoDecoder * decoder, GstBuffer * buf) +{ + GstVideoDecoderPrivate *priv; + GstVideoDecoderClass *klass; + GstFlowReturn ret = GST_FLOW_OK; + + klass = GST_VIDEO_DECODER_GET_CLASS (decoder); + priv = decoder->priv; + + g_return_val_if_fail (priv->packetized || klass->parse, GST_FLOW_ERROR); + + if (priv->current_frame == NULL) { + priv->current_frame = gst_video_decoder_new_frame (decoder); + } + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + gst_video_decoder_add_timestamp (decoder, buf); + } + priv->input_offset += GST_BUFFER_SIZE (buf); + + if (priv->packetized) { + priv->current_frame->input_buffer = buf; + + if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) { + GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (priv->current_frame); + } + + ret = gst_video_decoder_have_frame_2 (decoder); + } else { + + gst_adapter_push (priv->input_adapter, buf); + + if (G_UNLIKELY (!gst_adapter_available (priv->input_adapter))) + goto beach; + + do { + ret = + klass->parse (decoder, priv->current_frame, priv->input_adapter, + FALSE); + } while (ret == GST_FLOW_OK && gst_adapter_available (priv->input_adapter)); + + } + +beach: + if (ret == GST_VIDEO_DECODER_FLOW_NEED_DATA) + return GST_FLOW_OK; + + return ret; +} + +static GstFlowReturn +gst_video_decoder_flush_decode (GstVideoDecoder * dec) +{ + GstVideoDecoderPrivate *priv = dec->priv; + GstFlowReturn res = GST_FLOW_OK; + GList *walk; + + walk = priv->decode; + + GST_DEBUG_OBJECT (dec, "flushing buffers to decode"); + + /* clear buffer and decoder state */ + gst_video_decoder_flush (dec, FALSE); + + /* signal have_frame it should not capture frames */ + priv->process = TRUE; + + while (walk) { + GList *next; + GstVideoCodecFrame *frame = (GstVideoCodecFrame *) (walk->data); + GstBuffer *buf = frame->input_buffer; + + GST_DEBUG_OBJECT (dec, "decoding frame %p, ts %" GST_TIME_FORMAT, + buf, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + + next = walk->next; + if (priv->current_frame) + gst_video_codec_frame_unref (priv->current_frame); + priv->current_frame = frame; + gst_video_codec_frame_ref (priv->current_frame); + + /* decode buffer, resulting data prepended to queue */ + res = gst_video_decoder_have_frame_2 (dec); + + walk = next; + } + + priv->process = FALSE; + + return res; +} + +static GstFlowReturn +gst_video_decoder_flush_parse (GstVideoDecoder * dec) +{ + GstVideoDecoderPrivate *priv = dec->priv; + GstFlowReturn res = GST_FLOW_OK; + GList *walk; + + walk = priv->parse; + + GST_DEBUG_OBJECT (dec, "flushing buffers to parsing"); + + /* clear buffer and decoder state */ + gst_video_decoder_flush (dec, FALSE); + + while (walk) { + GList *next; + GstBuffer *buf = GST_BUFFER_CAST (walk->data); + + GST_DEBUG_OBJECT (dec, "parsing buffer %p, ts %" GST_TIME_FORMAT, + buf, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + + next = walk->next; + /* parse buffer, resulting frames prepended to parse_gather queue */ + gst_buffer_ref (buf); + res = gst_video_decoder_chain_forward (dec, buf); + + /* if we generated output, we can discard the buffer, else we + * keep it in the queue */ + if (priv->parse_gather) { + GST_DEBUG_OBJECT (dec, "parsed buffer to %p", priv->parse_gather->data); + priv->parse = g_list_delete_link (priv->parse, walk); + gst_buffer_unref (buf); + } else { + GST_DEBUG_OBJECT (dec, "buffer did not decode, keeping"); + } + walk = next; + } + + /* now we can process frames */ + GST_DEBUG_OBJECT (dec, "checking frames"); + while (priv->parse_gather) { + GstVideoCodecFrame *frame; + + frame = (GstVideoCodecFrame *) (priv->parse_gather->data); + /* remove from the gather list */ + priv->parse_gather = + g_list_delete_link (priv->parse_gather, priv->parse_gather); + /* copy to decode queue */ + priv->decode = g_list_prepend (priv->decode, frame); + + /* if we copied a keyframe, flush and decode the decode queue */ + if (GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame)) { + GST_DEBUG_OBJECT (dec, "copied keyframe"); + res = gst_video_decoder_flush_decode (dec); + } + } + + /* now send queued data downstream */ + while (priv->queued) { + GstBuffer *buf = GST_BUFFER_CAST (priv->queued->data); + + if (G_LIKELY (res == GST_FLOW_OK)) { + GST_DEBUG_OBJECT (dec, "pushing buffer %p of size %u, " + "time %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT, buf, + GST_BUFFER_SIZE (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); + /* should be already, but let's be sure */ + buf = gst_buffer_make_metadata_writable (buf); + /* avoid stray DISCONT from forward processing, + * which have no meaning in reverse pushing */ + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + res = gst_pad_push (dec->srcpad, buf); + } else { + gst_buffer_unref (buf); + } + + priv->queued = g_list_delete_link (priv->queued, priv->queued); + } + + return res; +} + +static GstFlowReturn +gst_video_decoder_chain_reverse (GstVideoDecoder * dec, GstBuffer * buf) +{ + GstVideoDecoderPrivate *priv = dec->priv; + GstFlowReturn result = GST_FLOW_OK; + + /* if we have a discont, move buffers to the decode list */ + if (!buf || GST_BUFFER_IS_DISCONT (buf)) { + GST_DEBUG_OBJECT (dec, "received discont"); + while (priv->gather) { + GstBuffer *gbuf; + + gbuf = GST_BUFFER_CAST (priv->gather->data); + /* remove from the gather list */ + priv->gather = g_list_delete_link (priv->gather, priv->gather); + /* copy to parse queue */ + priv->parse = g_list_prepend (priv->parse, gbuf); + } + /* parse and decode stuff in the parse queue */ + gst_video_decoder_flush_parse (dec); + } + + if (G_LIKELY (buf)) { + GST_DEBUG_OBJECT (dec, "gathering buffer %p of size %u, " + "time %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT, buf, + GST_BUFFER_SIZE (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); + + /* add buffer to gather queue */ + priv->gather = g_list_prepend (priv->gather, buf); + } + + return result; +} + +static GstFlowReturn +gst_video_decoder_chain (GstPad * pad, GstBuffer * buf) +{ + GstVideoDecoder *decoder; + GstVideoDecoderPrivate *priv; + GstFlowReturn ret = GST_FLOW_OK; + + decoder = GST_VIDEO_DECODER (GST_PAD_PARENT (pad)); + priv = decoder->priv; + + GST_LOG_OBJECT (decoder, + "chain %" GST_TIME_FORMAT " duration %" GST_TIME_FORMAT " size %d", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), GST_BUFFER_SIZE (buf)); + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + + /* NOTE: + * requiring the pad to be negotiated makes it impossible to use + * oggdemux or filesrc ! decoder */ + + if (decoder->input_segment.format == GST_FORMAT_UNDEFINED) { + GstEvent *event; + + GST_WARNING_OBJECT (decoder, + "Received buffer without a new-segment. " + "Assuming timestamps start from 0."); + + gst_segment_set_newsegment_full (&decoder->input_segment, FALSE, 1.0, 1.0, + GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE, 0); + + event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0, + GST_CLOCK_TIME_NONE, 0); + + decoder->priv->current_frame_events = + g_list_prepend (decoder->priv->current_frame_events, event); + } + + if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (buf))) { + gint64 ts; + + GST_DEBUG_OBJECT (decoder, "received DISCONT buffer"); + + /* track present position */ + ts = priv->timestamp_offset; + + /* buffer may claim DISCONT loudly, if it can't tell us where we are now, + * we'll stick to where we were ... + * Particularly useful/needed for upstream BYTE based */ + if (decoder->input_segment.rate > 0.0 + && !GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + GST_DEBUG_OBJECT (decoder, "... but restoring previous ts tracking"); + priv->timestamp_offset = ts; + } + } + + if (decoder->input_segment.rate > 0.0) + ret = gst_video_decoder_chain_forward (decoder, buf); + else + ret = gst_video_decoder_chain_reverse (decoder, buf); + + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + return ret; +} + +static GstStateChangeReturn +gst_video_decoder_change_state (GstElement * element, GstStateChange transition) +{ + GstVideoDecoder *decoder; + GstVideoDecoderClass *decoder_class; + GstStateChangeReturn ret; + + decoder = GST_VIDEO_DECODER (element); + decoder_class = GST_VIDEO_DECODER_GET_CLASS (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + /* open device/library if needed */ + if (decoder_class->open && !decoder_class->open (decoder)) + goto open_failed; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + /* Initialize device/library if needed */ + if (decoder_class->start && !decoder_class->start (decoder)) + goto start_failed; + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + if (decoder_class->stop && !decoder_class->stop (decoder)) + goto stop_failed; + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + gst_video_decoder_reset (decoder, TRUE); + g_list_foreach (decoder->priv->current_frame_events, + (GFunc) gst_event_unref, NULL); + g_list_free (decoder->priv->current_frame_events); + decoder->priv->current_frame_events = NULL; + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + /* close device/library if needed */ + if (decoder_class->close && !decoder_class->close (decoder)) + goto close_failed; + break; + default: + break; + } + + return ret; + + /* Errors */ +open_failed: + { + GST_ELEMENT_ERROR (decoder, LIBRARY, INIT, (NULL), + ("Failed to open decoder")); + return GST_STATE_CHANGE_FAILURE; + } + +start_failed: + { + GST_ELEMENT_ERROR (decoder, LIBRARY, INIT, (NULL), + ("Failed to start decoder")); + return GST_STATE_CHANGE_FAILURE; + } + +stop_failed: + { + GST_ELEMENT_ERROR (decoder, LIBRARY, INIT, (NULL), + ("Failed to stop decoder")); + return GST_STATE_CHANGE_FAILURE; + } + +close_failed: + { + GST_ELEMENT_ERROR (decoder, LIBRARY, INIT, (NULL), + ("Failed to close decoder")); + return GST_STATE_CHANGE_FAILURE; + } +} + +static GstVideoCodecFrame * +gst_video_decoder_new_frame (GstVideoDecoder * decoder) +{ + GstVideoDecoderPrivate *priv = decoder->priv; + GstVideoCodecFrame *frame; + + frame = g_slice_new0 (GstVideoCodecFrame); + + frame->ref_count = 1; + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + frame->system_frame_number = priv->system_frame_number; + priv->system_frame_number++; + frame->decode_frame_number = priv->decode_frame_number; + priv->decode_frame_number++; + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + GST_LOG_OBJECT (decoder, "Created new frame %p (sfn:%d)", + frame, frame->system_frame_number); + + frame->dts = GST_CLOCK_TIME_NONE; + frame->pts = GST_CLOCK_TIME_NONE; + frame->duration = GST_CLOCK_TIME_NONE; + frame->events = priv->current_frame_events; + priv->current_frame_events = NULL; + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + return frame; +} + +static void +gst_video_decoder_prepare_finish_frame (GstVideoDecoder * + decoder, GstVideoCodecFrame * frame) +{ + GstVideoDecoderPrivate *priv = decoder->priv; + GList *l, *events = NULL; + +#ifndef GST_DISABLE_GST_DEBUG + GST_LOG_OBJECT (decoder, "n %d in %d out %d", + g_list_length (priv->frames), + gst_adapter_available (priv->input_adapter), + gst_adapter_available (priv->output_adapter)); +#endif + + GST_LOG_OBJECT (decoder, + "finish frame sync=%d pts=%" GST_TIME_FORMAT, + GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame), GST_TIME_ARGS (frame->pts)); + + /* Push all pending events that arrived before this frame */ + for (l = priv->frames; l; l = l->next) { + GstVideoCodecFrame *tmp = l->data; + + if (tmp->events) { + events = tmp->events; + tmp->events = NULL; + } + + if (tmp == frame) + break; + } + + for (l = g_list_last (events); l; l = l->prev) { + GST_LOG_OBJECT (decoder, "pushing %s event", GST_EVENT_TYPE_NAME (l->data)); + gst_video_decoder_push_event (decoder, l->data); + } + g_list_free (events); + + /* Check if the data should not be displayed. For example altref/invisible + * frame in vp8. In this case we should not update the timestamps. */ + if (GST_VIDEO_CODEC_FRAME_IS_DECODE_ONLY (frame)) + return; + + /* If the frame is meant to be outputted but we don't have an output buffer + * we have a problem :) */ + if (G_UNLIKELY (frame->output_buffer == NULL)) + goto no_output_buffer; + + if (GST_CLOCK_TIME_IS_VALID (frame->pts)) { + if (frame->pts != priv->timestamp_offset) { + GST_DEBUG_OBJECT (decoder, + "sync timestamp %" GST_TIME_FORMAT " diff %" GST_TIME_FORMAT, + GST_TIME_ARGS (frame->pts), + GST_TIME_ARGS (frame->pts - decoder->output_segment.start)); + priv->timestamp_offset = frame->pts; + } else { + /* This case is for one initial timestamp and no others, e.g., + * filesrc ! decoder ! xvimagesink */ + GST_WARNING_OBJECT (decoder, "sync timestamp didn't change, ignoring"); + frame->pts = GST_CLOCK_TIME_NONE; + } + } else { + if (GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame)) { + GST_WARNING_OBJECT (decoder, "sync point doesn't have timestamp"); + if (!GST_CLOCK_TIME_IS_VALID (priv->timestamp_offset)) { + GST_WARNING_OBJECT (decoder, + "No base timestamp. Assuming frames start at segment start"); + priv->timestamp_offset = decoder->output_segment.start; + } + } + } + if (frame->pts == GST_CLOCK_TIME_NONE) { + frame->pts = + gst_video_decoder_get_timestamp (decoder, frame->decode_frame_number); + frame->duration = GST_CLOCK_TIME_NONE; + } + if (frame->duration == GST_CLOCK_TIME_NONE) { + frame->duration = gst_video_decoder_get_frame_duration (decoder, frame); + } + + if (GST_CLOCK_TIME_IS_VALID (priv->last_timestamp)) { + if (frame->pts < priv->last_timestamp) { + GST_WARNING_OBJECT (decoder, + "decreasing timestamp (%" GST_TIME_FORMAT " < %" + GST_TIME_FORMAT ")", + GST_TIME_ARGS (frame->pts), GST_TIME_ARGS (priv->last_timestamp)); + } + } + priv->last_timestamp = frame->pts; + + return; + + /* ERRORS */ +no_output_buffer: + { + GST_ERROR_OBJECT (decoder, "No buffer to output !"); + } +} + +static void +gst_video_decoder_do_finish_frame (GstVideoDecoder * dec, + GstVideoCodecFrame * frame) +{ + dec->priv->frames = g_list_remove (dec->priv->frames, frame); + + gst_video_codec_frame_unref (frame); +} + +/** + * gst_video_decoder_drop_frame: + * @dec: a #GstVideoDecoder + * @frame: (transfer full): the #GstVideoCodecFrame to drop + * + * Similar to gst_video_decoder_finish_frame(), but drops @frame in any + * case and posts a QoS message with the frame's details on the bus. + * In any case, the frame is considered finished and released. + * + * Returns: a #GstFlowReturn, usually GST_FLOW_OK. + * + * Since: 0.10.36 + */ +GstFlowReturn +gst_video_decoder_drop_frame (GstVideoDecoder * dec, GstVideoCodecFrame * frame) +{ + GstClockTime stream_time, jitter, earliest_time, qostime, timestamp; + GstSegment *segment; + GstMessage *qos_msg; + gdouble proportion; + + GST_LOG_OBJECT (dec, "drop frame"); + + GST_VIDEO_DECODER_STREAM_LOCK (dec); + + gst_video_decoder_prepare_finish_frame (dec, frame); + + GST_DEBUG_OBJECT (dec, "dropping frame %" GST_TIME_FORMAT, + GST_TIME_ARGS (frame->pts)); + + dec->priv->dropped++; + + /* post QoS message */ + timestamp = frame->pts; + proportion = dec->priv->proportion; + segment = &dec->output_segment; + stream_time = + gst_segment_to_stream_time (segment, GST_FORMAT_TIME, timestamp); + qostime = gst_segment_to_running_time (segment, GST_FORMAT_TIME, timestamp); + earliest_time = dec->priv->earliest_time; + jitter = GST_CLOCK_DIFF (qostime, earliest_time); + qos_msg = + gst_message_new_qos (GST_OBJECT_CAST (dec), FALSE, qostime, stream_time, + timestamp, GST_CLOCK_TIME_NONE); + gst_message_set_qos_values (qos_msg, jitter, proportion, 1000000); + gst_message_set_qos_stats (qos_msg, GST_FORMAT_BUFFERS, + dec->priv->processed, dec->priv->dropped); + gst_element_post_message (GST_ELEMENT_CAST (dec), qos_msg); + + /* now free the frame */ + gst_video_decoder_do_finish_frame (dec, frame); + + GST_VIDEO_DECODER_STREAM_UNLOCK (dec); + + return GST_FLOW_OK; +} + +/** + * gst_video_decoder_finish_frame: + * @decoder: a #GstVideoDecoder + * @frame: (transfer full): a decoded #GstVideoCodecFrame + * + * @frame should have a valid decoded data buffer, whose metadata fields + * are then appropriately set according to frame data and pushed downstream. + * If no output data is provided, @frame is considered skipped. + * In any case, the frame is considered finished and released. + * + * Returns: a #GstFlowReturn resulting from sending data downstream + * + * Since: 0.10.36 + */ +GstFlowReturn +gst_video_decoder_finish_frame (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame) +{ + GstVideoDecoderPrivate *priv = decoder->priv; + GstVideoCodecState *state = priv->output_state; + GstBuffer *output_buffer; + GstFlowReturn ret = GST_FLOW_OK; + gint64 start, stop; + GstSegment *segment; + + GST_LOG_OBJECT (decoder, "finish frame"); + + if (G_UNLIKELY (priv->output_state_changed)) + gst_video_decoder_set_src_caps (decoder); + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + + gst_video_decoder_prepare_finish_frame (decoder, frame); + priv->processed++; + /* no buffer data means this frame is skipped */ + if (!frame->output_buffer || GST_VIDEO_CODEC_FRAME_IS_DECODE_ONLY (frame)) { + GST_DEBUG_OBJECT (decoder, "skipping frame %" GST_TIME_FORMAT, + GST_TIME_ARGS (frame->pts)); + goto done; + } + + output_buffer = gst_buffer_make_metadata_writable (frame->output_buffer); + frame->output_buffer = NULL; + + GST_BUFFER_FLAG_UNSET (output_buffer, GST_BUFFER_FLAG_DELTA_UNIT); + if (GST_VIDEO_INFO_IS_INTERLACED (&state->info)) { + if (GST_VIDEO_CODEC_FRAME_FLAG_IS_SET (frame, + GST_VIDEO_CODEC_FRAME_FLAG_TFF)) { + GST_BUFFER_FLAG_SET (output_buffer, GST_VIDEO_BUFFER_TFF); + } else { + GST_BUFFER_FLAG_UNSET (output_buffer, GST_VIDEO_BUFFER_TFF); + } + if (GST_VIDEO_CODEC_FRAME_FLAG_IS_SET (frame, + GST_VIDEO_CODEC_FRAME_FLAG_RFF)) { + GST_BUFFER_FLAG_SET (output_buffer, GST_VIDEO_BUFFER_RFF); + } else { + GST_BUFFER_FLAG_UNSET (output_buffer, GST_VIDEO_BUFFER_RFF); + } + if (GST_VIDEO_CODEC_FRAME_FLAG_IS_SET (frame, + GST_VIDEO_CODEC_FRAME_FLAG_ONEFIELD)) { + GST_BUFFER_FLAG_SET (output_buffer, GST_VIDEO_BUFFER_ONEFIELD); + } else { + GST_BUFFER_FLAG_UNSET (output_buffer, GST_VIDEO_BUFFER_ONEFIELD); + } + } + + if (priv->discont) { + GST_BUFFER_FLAG_SET (output_buffer, GST_BUFFER_FLAG_DISCONT); + priv->discont = FALSE; + } + + /* Check for clipping */ + start = frame->pts; + stop = frame->pts + frame->duration; + + segment = &decoder->output_segment; + if (gst_segment_clip (segment, GST_FORMAT_TIME, start, stop, &start, &stop)) { + GST_BUFFER_TIMESTAMP (output_buffer) = start; + GST_BUFFER_DURATION (output_buffer) = stop - start; + GST_LOG_OBJECT (decoder, + "accepting buffer inside segment: %" GST_TIME_FORMAT " %" + GST_TIME_FORMAT " seg %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT + " time %" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (output_buffer)), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (output_buffer) + + GST_BUFFER_DURATION (output_buffer)), + GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->stop), + GST_TIME_ARGS (segment->time)); + } else { + GST_LOG_OBJECT (decoder, + "dropping buffer outside segment: %" GST_TIME_FORMAT + " %" GST_TIME_FORMAT + " seg %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT + " time %" GST_TIME_FORMAT, + GST_TIME_ARGS (frame->pts), + GST_TIME_ARGS (frame->pts + frame->duration), + GST_TIME_ARGS (segment->start), + GST_TIME_ARGS (segment->stop), GST_TIME_ARGS (segment->time)); + gst_buffer_unref (output_buffer); + ret = GST_FLOW_OK; + goto done; + } + + GST_BUFFER_OFFSET (output_buffer) = GST_BUFFER_OFFSET_NONE; + GST_BUFFER_OFFSET_END (output_buffer) = GST_BUFFER_OFFSET_NONE; + + /* update rate estimate */ + priv->bytes_out += GST_BUFFER_SIZE (output_buffer); + if (GST_CLOCK_TIME_IS_VALID (frame->duration)) { + priv->time += frame->duration; + } else { + /* FIXME : Use difference between current and previous outgoing + * timestamp, and relate to difference between current and previous + * bytes */ + /* better none than nothing valid */ + priv->time = GST_CLOCK_TIME_NONE; + } + + gst_buffer_set_caps (output_buffer, GST_PAD_CAPS (decoder->srcpad)); + + GST_LOG_OBJECT (decoder, "pushing frame ts %" GST_TIME_FORMAT + ", duration %" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (output_buffer)), + GST_TIME_ARGS (GST_BUFFER_DURATION (output_buffer))); + + + + /* we got data, so note things are looking up again */ + /* FIXME : Shouldn't we avoid going under zero ? */ + if (G_UNLIKELY (priv->error_count)) + priv->error_count--; + if (decoder->output_segment.rate < 0.0) { + GST_LOG_OBJECT (decoder, "queued buffer"); + priv->queued = g_list_prepend (priv->queued, output_buffer); + } else { + ret = gst_pad_push (decoder->srcpad, output_buffer); + } + +done: + + gst_video_decoder_do_finish_frame (decoder, frame); + + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + return ret; +} + +/** + * gst_video_decoder_add_to_frame: + * @decoder: a #GstVideoDecoder + * @n_bytes: an encoded #GstVideoCodecFrame + * + * Removes next @n_bytes of input data and adds it to currently parsed frame. + * + * Since: 0.10.36 + */ +void +gst_video_decoder_add_to_frame (GstVideoDecoder * decoder, int n_bytes) +{ + GstVideoDecoderPrivate *priv = decoder->priv; + GstBuffer *buf; + + GST_LOG_OBJECT (decoder, "add %d bytes to frame", n_bytes); + + if (n_bytes == 0) + return; + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + if (gst_adapter_available (priv->output_adapter) == 0) { + priv->frame_offset = + priv->input_offset - gst_adapter_available (priv->input_adapter); + } + buf = gst_adapter_take_buffer (priv->input_adapter, n_bytes); + + gst_adapter_push (priv->output_adapter, buf); + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); +} + +static guint64 +gst_video_decoder_get_timestamp (GstVideoDecoder * decoder, int picture_number) +{ + GstVideoDecoderPrivate *priv = decoder->priv; + GstVideoCodecState *state = priv->output_state; + + if (state->info.fps_d == 0 || state->info.fps_n == 0) { + return -1; + } + if (picture_number < priv->base_picture_number) { + return priv->timestamp_offset - + (gint64) gst_util_uint64_scale (priv->base_picture_number + - picture_number, state->info.fps_d * GST_SECOND, state->info.fps_n); + } else { + return priv->timestamp_offset + + gst_util_uint64_scale (picture_number - + priv->base_picture_number, state->info.fps_d * GST_SECOND, + state->info.fps_n); + } +} + +static guint64 +gst_video_decoder_get_frame_duration (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame) +{ + GstVideoCodecState *state = decoder->priv->output_state; + gint fields; + + if (state->info.fps_d == 0 || state->info.fps_n == 0) { + return GST_CLOCK_TIME_NONE; + } + + if (GST_VIDEO_CODEC_FRAME_FLAG_IS_SET (frame, GST_VIDEO_CODEC_FRAME_FLAG_RFF)) + fields = 3; + else if (GST_VIDEO_CODEC_FRAME_FLAG_IS_SET (frame, + GST_VIDEO_CODEC_FRAME_FLAG_ONEFIELD)) + fields = 1; + else + fields = 2; + + return gst_util_uint64_scale (fields * GST_SECOND, state->info.fps_d, + state->info.fps_n); +} + +/** + * gst_video_decoder_have_frame: + * @decoder: a #GstVideoDecoder + * + * Gathers all data collected for currently parsed frame, gathers corresponding + * metadata and passes it along for further processing, i.e. @handle_frame. + * + * Returns: a #GstFlowReturn + * + * Since: 0.10.36 + */ +GstFlowReturn +gst_video_decoder_have_frame (GstVideoDecoder * decoder) +{ + GstBuffer *buffer; + int n_available; + GstClockTime timestamp; + GstClockTime duration; + GstFlowReturn ret = GST_FLOW_OK; + + GST_LOG_OBJECT (decoder, "have_frame"); + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + + n_available = gst_adapter_available (decoder->priv->output_adapter); + if (n_available) { + buffer = + gst_adapter_take_buffer (decoder->priv->output_adapter, n_available); + } else { + buffer = gst_buffer_new_and_alloc (0); + } + + decoder->priv->current_frame->input_buffer = buffer; + + gst_video_decoder_get_timestamp_at_offset (decoder, + decoder->priv->frame_offset, ×tamp, &duration); + + GST_BUFFER_TIMESTAMP (buffer) = timestamp; + GST_BUFFER_DURATION (buffer) = duration; + + GST_LOG_OBJECT (decoder, "collected frame size %d, " + "ts %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT, + n_available, GST_TIME_ARGS (timestamp), GST_TIME_ARGS (duration)); + + ret = gst_video_decoder_have_frame_2 (decoder); + + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + return ret; +} + +static GstFlowReturn +gst_video_decoder_have_frame_2 (GstVideoDecoder * decoder) +{ + GstVideoDecoderPrivate *priv = decoder->priv; + GstVideoCodecFrame *frame = priv->current_frame; + GstVideoDecoderClass *decoder_class; + GstFlowReturn ret = GST_FLOW_OK; + + decoder_class = GST_VIDEO_DECODER_GET_CLASS (decoder); + + /* FIXME : This should only have to be checked once (either the subclass has an + * implementation, or it doesn't) */ + g_return_val_if_fail (decoder_class->handle_frame != NULL, GST_FLOW_ERROR); + + /* capture frames and queue for later processing */ + if (decoder->output_segment.rate < 0.0 && !priv->process) { + priv->parse_gather = g_list_prepend (priv->parse_gather, frame); + goto exit; + } + + frame->distance_from_sync = priv->distance_from_sync; + priv->distance_from_sync++; + frame->pts = GST_BUFFER_TIMESTAMP (frame->input_buffer); + frame->duration = GST_BUFFER_DURATION (frame->input_buffer); + + /* For keyframes, DTS = PTS */ + if (GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame)) + frame->dts = frame->pts; + + GST_LOG_OBJECT (decoder, "pts %" GST_TIME_FORMAT, GST_TIME_ARGS (frame->pts)); + GST_LOG_OBJECT (decoder, "dts %" GST_TIME_FORMAT, GST_TIME_ARGS (frame->dts)); + GST_LOG_OBJECT (decoder, "dist %d", frame->distance_from_sync); + priv->frames = g_list_append (priv->frames, frame); + frame->deadline = + gst_segment_to_running_time (&decoder->input_segment, GST_FORMAT_TIME, + frame->pts); + + /* do something with frame */ + ret = decoder_class->handle_frame (decoder, frame); + if (ret != GST_FLOW_OK) + GST_DEBUG_OBJECT (decoder, "flow error %s", gst_flow_get_name (ret)); + +exit: + /* current frame has either been added to parse_gather or sent to + handle frame so there is no need to unref it */ + + /* create new frame */ + priv->current_frame = gst_video_decoder_new_frame (decoder); + return ret; +} + + +/** + * gst_video_decoder_get_output_state: + * @decoder: a #GstVideoDecoder + * + * Get the #GstVideoCodecState currently describing the output stream. + * + * Returns: (transfer full): #GstVideoCodecState describing format of video data. + * + * Since: 0.10.36 + */ +GstVideoCodecState * +gst_video_decoder_get_output_state (GstVideoDecoder * decoder) +{ + GstVideoCodecState *state = NULL; + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + if (decoder->priv->output_state) + state = gst_video_codec_state_ref (decoder->priv->output_state); + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + return state; +} + +/** + * gst_video_decoder_set_output_state: + * @decoder: a #GstVideoDecoder + * @fmt: a #GstVideoFormat + * @width: The width in pixels + * @height: The height in pixels + * @reference: (allow-none) (transfer none): An optional reference #GstVideoCodecState + * + * Creates a new #GstVideoCodecState with the specified @fmt, @width and @height + * as the output state for the decoder. + * Any previously set output state on @decoder will be replaced by the newly + * created one. + * + * If the subclass wishes to copy over existing fields (like pixel aspec ratio, + * or framerate) from an existing #GstVideoCodecState, it can be provided as a + * @reference. + * + * If the subclass wishes to override some fields from the output state (like + * pixel-aspect-ratio or framerate) it can do so on the returned #GstVideoCodecState. + * + * The new output state will only take effect (set on pads and buffers) starting + * from the next call to #gst_video_decoder_finish_frame(). + * + * Returns: (transfer full): the newly configured output state. + * + * Since: 0.10.36 + */ +GstVideoCodecState * +gst_video_decoder_set_output_state (GstVideoDecoder * decoder, + GstVideoFormat fmt, guint width, guint height, + GstVideoCodecState * reference) +{ + GstVideoDecoderPrivate *priv = decoder->priv; + GstVideoCodecState *state; + + GST_DEBUG_OBJECT (decoder, "fmt:%d, width:%d, height:%d, reference:%p", + fmt, width, height, reference); + + /* Create the new output state */ + state = _new_output_state (fmt, width, height, reference); + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + /* Replace existing output state by new one */ + if (priv->output_state) + gst_video_codec_state_unref (priv->output_state); + priv->output_state = gst_video_codec_state_ref (state); + + priv->output_state_changed = TRUE; + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + return state; +} + + +/** + * gst_video_decoder_get_oldest_frame: + * @decoder: a #GstVideoDecoder + * + * Get the oldest pending unfinished #GstVideoCodecFrame + * + * Returns: (transfer none): oldest pending unfinished #GstVideoCodecFrame. + * + * Since: 0.10.36 + */ +GstVideoCodecFrame * +gst_video_decoder_get_oldest_frame (GstVideoDecoder * decoder) +{ + GList *g; + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + g = decoder->priv->frames; + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + if (g == NULL) + return NULL; + return (GstVideoCodecFrame *) (g->data); +} + +/** + * gst_video_decoder_get_frame: + * @decoder: a #GstVideoDecoder + * @frame_number: system_frame_number of a frame + * + * Get a pending unfinished #GstVideoCodecFrame + * + * Returns: (transfer none): pending unfinished #GstVideoCodecFrame identified by @frame_number. + * + * Since: 0.10.36 + */ +GstVideoCodecFrame * +gst_video_decoder_get_frame (GstVideoDecoder * decoder, int frame_number) +{ + GList *g; + GstVideoCodecFrame *frame = NULL; + + GST_DEBUG_OBJECT (decoder, "frame_number : %d", frame_number); + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + for (g = decoder->priv->frames; g; g = g->next) { + GstVideoCodecFrame *tmp = g->data; + + if (tmp->system_frame_number == frame_number) { + frame = tmp; + break; + } + } + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + return frame; +} + +/** + * gst_video_decoder_set_src_caps: + * @decoder: a #GstVideoDecoder + * + * Sets src pad caps according to currently configured #GstVideoCodecState. + * + * Returns: #TRUE if the caps were accepted downstream, else #FALSE. + * + * Since: 0.10.36 + */ +static gboolean +gst_video_decoder_set_src_caps (GstVideoDecoder * decoder) +{ + GstVideoCodecState *state = decoder->priv->output_state; + gboolean ret; + + g_return_val_if_fail (GST_VIDEO_INFO_WIDTH (&state->info) != 0, FALSE); + g_return_val_if_fail (GST_VIDEO_INFO_HEIGHT (&state->info) != 0, FALSE); + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + + GST_DEBUG_OBJECT (decoder, "output_state par %d/%d fps %d/%d", + state->info.par_n, state->info.par_d, + state->info.fps_n, state->info.fps_d); + + if (G_UNLIKELY (state->caps == NULL)) + state->caps = gst_video_info_to_caps (&state->info); + + GST_DEBUG_OBJECT (decoder, "setting caps %" GST_PTR_FORMAT, state->caps); + + ret = gst_pad_set_caps (decoder->srcpad, state->caps); + decoder->priv->output_state_changed = FALSE; + + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + return ret; +} + +/** + * gst_video_decoder_alloc_output_buffer: + * @decoder: a #GstVideoDecoder + * + * Helper function that uses @gst_pad_alloc_buffer_and_set_caps() + * to allocate a buffer to hold a video frame for @decoder's + * current #GstVideoCodecState. + * + * Returns: (transfer full): allocated buffer + * + * Since: 0.10.36 + */ +GstBuffer * +gst_video_decoder_alloc_output_buffer (GstVideoDecoder * decoder) +{ + GstBuffer *buffer; + GstFlowReturn flow_ret; + GstVideoCodecState *state = decoder->priv->output_state; + int num_bytes = GST_VIDEO_INFO_SIZE (&state->info); + + GST_DEBUG ("alloc src buffer caps=%" GST_PTR_FORMAT, + GST_PAD_CAPS (decoder->srcpad)); + + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + if (G_UNLIKELY (decoder->priv->output_state_changed)) + gst_video_decoder_set_src_caps (decoder); + + flow_ret = + gst_pad_alloc_buffer_and_set_caps (decoder->srcpad, + GST_BUFFER_OFFSET_NONE, num_bytes, GST_PAD_CAPS (decoder->srcpad), + &buffer); + + if (flow_ret != GST_FLOW_OK) { + buffer = gst_buffer_new_and_alloc (num_bytes); + gst_buffer_set_caps (buffer, GST_PAD_CAPS (decoder->srcpad)); + } + + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + return buffer; +} + +/** + * gst_video_decoder_alloc_output_frame: + * @decoder: a #GstVideoDecoder + * @frame: a #GstVideoCodecFrame + * + * Helper function that uses @gst_pad_alloc_buffer_and_set_caps() + * to allocate a buffer to hold a video frame for @decoder's + * current #GstVideoCodecState. Subclass should already have configured video state + * and set src pad caps. + * + * Returns: result from pad alloc call + * + * Since: 0.10.36 + */ +GstFlowReturn +gst_video_decoder_alloc_output_frame (GstVideoDecoder * + decoder, GstVideoCodecFrame * frame) +{ + GstFlowReturn flow_ret; + GstVideoCodecState *state = decoder->priv->output_state; + int num_bytes = GST_VIDEO_INFO_SIZE (&state->info); + + g_return_val_if_fail (num_bytes != 0, GST_FLOW_ERROR); + + if (G_UNLIKELY (decoder->priv->output_state_changed)) + gst_video_decoder_set_src_caps (decoder); + + g_return_val_if_fail (GST_PAD_CAPS (decoder->srcpad) != NULL, GST_FLOW_ERROR); + + GST_LOG_OBJECT (decoder, "alloc buffer size %d", num_bytes); + GST_VIDEO_DECODER_STREAM_LOCK (decoder); + + flow_ret = + gst_pad_alloc_buffer_and_set_caps (decoder->srcpad, + GST_BUFFER_OFFSET_NONE, num_bytes, GST_PAD_CAPS (decoder->srcpad), + &frame->output_buffer); + + if (flow_ret != GST_FLOW_OK) { + GST_WARNING_OBJECT (decoder, "failed to get buffer %s", + gst_flow_get_name (flow_ret)); + } + + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); + + return flow_ret; +} + +/** + * gst_video_decoder_get_max_decode_time: + * @decoder: a #GstVideoDecoder + * @frame: a #GstVideoCodecFrame + * + * Determines maximum possible decoding time for @frame that will + * allow it to decode and arrive in time (as determined by QoS events). + * In particular, a negative result means decoding in time is no longer possible + * and should therefore occur as soon/skippy as possible. + * + * Returns: max decoding time. + * + * Since: 0.10.36 + */ +GstClockTimeDiff +gst_video_decoder_get_max_decode_time (GstVideoDecoder * + decoder, GstVideoCodecFrame * frame) +{ + GstClockTimeDiff deadline; + GstClockTime earliest_time; + + GST_OBJECT_LOCK (decoder); + earliest_time = decoder->priv->earliest_time; + if (GST_CLOCK_TIME_IS_VALID (earliest_time)) + deadline = GST_CLOCK_DIFF (earliest_time, frame->deadline); + else + deadline = G_MAXINT64; + + GST_LOG_OBJECT (decoder, "earliest %" GST_TIME_FORMAT + ", frame deadline %" GST_TIME_FORMAT ", deadline %" GST_TIME_FORMAT, + GST_TIME_ARGS (earliest_time), GST_TIME_ARGS (frame->deadline), + GST_TIME_ARGS (deadline)); + + GST_OBJECT_UNLOCK (decoder); + + return deadline; +} + +GstFlowReturn +_gst_video_decoder_error (GstVideoDecoder * dec, gint weight, + GQuark domain, gint code, gchar * txt, gchar * dbg, const gchar * file, + const gchar * function, gint line) +{ + if (txt) + GST_WARNING_OBJECT (dec, "error: %s", txt); + if (dbg) + GST_WARNING_OBJECT (dec, "error: %s", dbg); + dec->priv->error_count += weight; + dec->priv->discont = TRUE; + if (dec->priv->max_errors < dec->priv->error_count) { + gst_element_message_full (GST_ELEMENT (dec), GST_MESSAGE_ERROR, + domain, code, txt, dbg, file, function, line); + return GST_FLOW_ERROR; + } else { + return GST_FLOW_OK; + } +} + +/** + * gst_video_decoder_set_max_errors: + * @dec: a #GstVideoDecoder + * @num: max tolerated errors + * + * Sets numbers of tolerated decoder errors, where a tolerated one is then only + * warned about, but more than tolerated will lead to fatal error. Default + * is set to GST_VIDEO_DECODER_MAX_ERRORS. + * + * Since: 0.10.36 + */ +void +gst_video_decoder_set_max_errors (GstVideoDecoder * dec, gint num) +{ + g_return_if_fail (GST_IS_VIDEO_DECODER (dec)); + + dec->priv->max_errors = num; +} + +/** + * gst_video_decoder_get_max_errors: + * @dec: a #GstVideoDecoder + * + * Returns: currently configured decoder tolerated error count. + * + * Since: 0.10.36 + */ +gint +gst_video_decoder_get_max_errors (GstVideoDecoder * dec) +{ + g_return_val_if_fail (GST_IS_VIDEO_DECODER (dec), 0); + + return dec->priv->max_errors; +} + +/** + * gst_video_decoder_set_packetized: + * @decoder: a #GstVideoDecoder + * @packetized: whether the input data should be considered as packetized. + * + * Allows baseclass to consider input data as packetized or not. If the + * input is packetized, then the @parse method will not be called. + * + * Since: 0.10.36 + */ +void +gst_video_decoder_set_packetized (GstVideoDecoder * decoder, + gboolean packetized) +{ + decoder->priv->packetized = packetized; +} + +/** + * gst_video_decoder_get_packetized: + * @decoder: a #GstVideoDecoder + * + * Queries whether input data is considered packetized or not by the + * base class. + * + * Returns: TRUE if input data is considered packetized. + * + * Since: 0.10.36 + */ +gboolean +gst_video_decoder_get_packetized (GstVideoDecoder * decoder) +{ + return decoder->priv->packetized; +} + +/** + * gst_video_decoder_set_estimate_rate: + * @dec: a #GstVideoDecoder + * @enabled: whether to enable byte to time conversion + * + * Allows baseclass to perform byte to time estimated conversion. + * + * Since: 0.10.36 + */ +void +gst_video_decoder_set_estimate_rate (GstVideoDecoder * dec, gboolean enabled) +{ + g_return_if_fail (GST_IS_VIDEO_DECODER (dec)); + + dec->priv->do_estimate_rate = enabled; +} + +/** + * gst_video_decoder_get_estimate_rate: + * @dec: a #GstVideoDecoder + * + * Returns: currently configured byte to time conversion setting + * + * Since: 0.10.36 + */ +gboolean +gst_video_decoder_get_estimate_rate (GstVideoDecoder * dec) +{ + g_return_val_if_fail (GST_IS_VIDEO_DECODER (dec), 0); + + return dec->priv->do_estimate_rate; +} + +/** + * gst_video_decoder_set_latency: + * @decoder: a #GstVideoDecoder + * @min_latency: minimum latency + * @max_latency: maximum latency + * + * Informs baseclass of encoding latency. + * + * Since: 0.10.36 + */ +void +gst_video_decoder_set_latency (GstVideoDecoder * decoder, + GstClockTime min_latency, GstClockTime max_latency) +{ + g_return_if_fail (GST_CLOCK_TIME_IS_VALID (min_latency)); + g_return_if_fail (max_latency >= min_latency); + + GST_OBJECT_LOCK (decoder); + decoder->priv->min_latency = min_latency; + decoder->priv->max_latency = max_latency; + GST_OBJECT_UNLOCK (decoder); + + gst_element_post_message (GST_ELEMENT_CAST (decoder), + gst_message_new_latency (GST_OBJECT_CAST (decoder))); +} + +/** + * gst_video_decoder_get_latency: + * @decoder: a #GstVideoDecoder + * @min_latency: (out) (allow-none): the configured minimum latency + * @max_latency: (out) (allow-none): the configured maximum latency + * + * Returns the configured encoding latency. + * + * Since: 0.10.36 + */ +void +gst_video_decoder_get_latency (GstVideoDecoder * decoder, + GstClockTime * min_latency, GstClockTime * max_latency) +{ + GST_OBJECT_LOCK (decoder); + if (min_latency) + *min_latency = decoder->priv->min_latency; + if (max_latency) + *max_latency = decoder->priv->max_latency; + GST_OBJECT_UNLOCK (decoder); +} diff --git a/gst-libs/gst/video/gstvideodecoder.h b/gst-libs/gst/video/gstvideodecoder.h new file mode 100644 index 0000000000..535cf858cf --- /dev/null +++ b/gst-libs/gst/video/gstvideodecoder.h @@ -0,0 +1,369 @@ +/* GStreamer + * Copyright (C) 2008 David Schleef + * Copyright (C) 2011 Mark Nauwelaerts . + * Copyright (C) 2011 Nokia Corporation. All rights reserved. + * Contact: Stefan Kost + * Copyright (C) 2012 Collabora Ltd. + * Author : Edward Hervey + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _GST_VIDEO_DECODER_H_ +#define _GST_VIDEO_DECODER_H_ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_VIDEO_DECODER \ + (gst_video_decoder_get_type()) +#define GST_VIDEO_DECODER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VIDEO_DECODER,GstVideoDecoder)) +#define GST_VIDEO_DECODER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VIDEO_DECODER,GstVideoDecoderClass)) +#define GST_VIDEO_DECODER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_VIDEO_DECODER,GstVideoDecoderClass)) +#define GST_IS_VIDEO_DECODER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIDEO_DECODER)) +#define GST_IS_VIDEO_DECODER_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIDEO_DECODER)) + +/** + * GST_VIDEO_DECODER_SINK_NAME: + * + * The name of the templates for the sink pad. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_SINK_NAME "sink" +/** + * GST_VIDEO_DECODER_SRC_NAME: + * + * The name of the templates for the source pad. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_SRC_NAME "src" + +/** + * GST_VIDEO_DECODER_SRC_PAD: + * @obj: a #GstVideoDecoder + * + * Gives the pointer to the source #GstPad object of the element. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_SRC_PAD(obj) (((GstVideoDecoder *) (obj))->srcpad) + +/** + * GST_VIDEO_DECODER_SINK_PAD: + * @obj: a #GstVideoDecoder + * + * Gives the pointer to the sink #GstPad object of the element. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_SINK_PAD(obj) (((GstVideoDecoder *) (obj))->sinkpad) +/** + * GST_VIDEO_DECODER_FLOW_NEED_DATA: + * + * Returned while parsing to indicate more data is needed. + * + * Since: 0.10.36 + **/ +#define GST_VIDEO_DECODER_FLOW_NEED_DATA GST_FLOW_CUSTOM_SUCCESS + +/** + * GST_VIDEO_DECODER_FLOW_DROPPED: + * + * Returned when the event/buffer should be dropped. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_FLOW_DROPPED GST_FLOW_CUSTOM_SUCCESS_1 + +/** + * GST_VIDEO_DECODER_INPUT_SEGMENT: + * @obj: base decoder instance + * + * Gives the segment of the element. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_INPUT_SEGMENT(obj) (GST_VIDEO_DECODER_CAST (obj)->input_segment) + +/** + * GST_VIDEO_DECODER_OUTPUT_SEGMENT: + * @obj: base decoder instance + * + * Gives the segment of the element. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_OUTPUT_SEGMENT(obj) (GST_VIDEO_DECODER_CAST (obj)->output_segment) + +/** + * GST_VIDEO_DECODER_STREAM_LOCK: + * @decoder: video decoder instance + * + * Obtain a lock to protect the decoder function from concurrent access. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_STREAM_LOCK(decoder) g_static_rec_mutex_lock (&GST_VIDEO_DECODER (decoder)->stream_lock) + +/** + * GST_VIDEO_DECODER_STREAM_UNLOCK: + * @decoder: video decoder instance + * + * Release the lock that protects the decoder function from concurrent access. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_STREAM_UNLOCK(decoder) g_static_rec_mutex_unlock (&GST_VIDEO_DECODER (decoder)->stream_lock) + +typedef struct _GstVideoDecoder GstVideoDecoder; +typedef struct _GstVideoDecoderClass GstVideoDecoderClass; +typedef struct _GstVideoDecoderPrivate GstVideoDecoderPrivate; + + +/* do not use this one, use macro below */ +GstFlowReturn _gst_video_decoder_error (GstVideoDecoder *dec, gint weight, + GQuark domain, gint code, + gchar *txt, gchar *debug, + const gchar *file, const gchar *function, + gint line); + +/** + * GST_VIDEO_DECODER_ERROR: + * @el: the base video decoder element that generates the error + * @weight: element defined weight of the error, added to error count + * @domain: like CORE, LIBRARY, RESOURCE or STREAM (see #gstreamer-GstGError) + * @code: error code defined for that domain (see #gstreamer-GstGError) + * @text: the message to display (format string and args enclosed in + * parentheses) + * @debug: debugging information for the message (format string and args + * enclosed in parentheses) + * @ret: variable to receive return value + * + * Utility function that video decoder elements can use in case they encountered + * a data processing error that may be fatal for the current "data unit" but + * need not prevent subsequent decoding. Such errors are counted and if there + * are too many, as configured in the context's max_errors, the pipeline will + * post an error message and the application will be requested to stop further + * media processing. Otherwise, it is considered a "glitch" and only a warning + * is logged. In either case, @ret is set to the proper value to + * return to upstream/caller (indicating either GST_FLOW_ERROR or GST_FLOW_OK). + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_ERROR(el, w, domain, code, text, debug, ret) \ +G_STMT_START { \ + gchar *__txt = _gst_element_error_printf text; \ + gchar *__dbg = _gst_element_error_printf debug; \ + GstVideoDecoder *dec = GST_VIDEO_DECODER (el); \ + ret = _gst_video_decoder_error (dec, w, GST_ ## domain ## _ERROR, \ + GST_ ## domain ## _ERROR_ ## code, __txt, __dbg, __FILE__, \ + GST_FUNCTION, __LINE__); \ +} G_STMT_END + +/** + * GST_VIDEO_DECODER_MAX_ERRORS: + * + * Default maximum number of errors tolerated before signaling error. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_DECODER_MAX_ERRORS 10 + + +/** + * GstVideoDecoder: + * + * The opaque #GstVideoDecoder data structure. + * + * Since: 0.10.36 + */ +struct _GstVideoDecoder +{ + /*< private >*/ + GstElement element; + + /*< protected >*/ + GstPad *sinkpad; + GstPad *srcpad; + + /* protects all data processing, i.e. is locked + * in the chain function, finish_frame and when + * processing serialized events */ + GStaticRecMutex stream_lock; + + /* MT-protected (with STREAM_LOCK) */ + GstSegment input_segment; + GstSegment output_segment; + + GstVideoDecoderPrivate *priv; + + /* FIXME before moving to base */ + void *padding[GST_PADDING_LARGE]; +}; + +/** + * GstVideoDecoderClass: + * @open: Optional. + * Called when the element changes to GST_STATE_READY. + * Allows opening external resources. + * @close: Optional. + * Called when the element changes to GST_STATE_NULL. + * Allows closing external resources. + * @start: Optional. + * Called when the element starts processing. + * Allows opening external resources. + * @stop: Optional. + * Called when the element stops processing. + * Allows closing external resources. + * @set_format: Notifies subclass of incoming data format (caps). + * @parse: Required for non-packetized input. + * Allows chopping incoming data into manageable units (frames) + * for subsequent decoding. + * @reset: Optional. + * Allows subclass (decoder) to perform post-seek semantics reset. + * @handle_frame: Provides input data frame to subclass. + * @finish: Optional. + * Called to request subclass to dispatch any pending remaining + * data (e.g. at EOS). + * @sink_event: Optional. + * Event handler on the sink pad. This function should return + * TRUE if the event was handled and should be discarded + * (i.e. not unref'ed). + * @src_event: Optional. + * Event handler on the source pad. This function should return + * TRUE if the event was handled and should be discarded + * (i.e. not unref'ed). + * + * Subclasses can override any of the available virtual methods or not, as + * needed. At minimum @handle_frame needs to be overridden, and @set_format + * and likely as well. If non-packetized input is supported or expected, + * @parse needs to be overridden as well. + * + * Since: 0.10.36 + */ +struct _GstVideoDecoderClass +{ + /*< private >*/ + GstElementClass element_class; + + /*< public >*/ + gboolean (*open) (GstVideoDecoder *decoder); + + gboolean (*close) (GstVideoDecoder *decoder); + + gboolean (*start) (GstVideoDecoder *decoder); + + gboolean (*stop) (GstVideoDecoder *decoder); + + GstFlowReturn (*parse) (GstVideoDecoder *decoder, + GstVideoCodecFrame *frame, + GstAdapter *adapter, + gboolean at_eos); + + gboolean (*set_format) (GstVideoDecoder *decoder, + GstVideoCodecState * state); + + gboolean (*reset) (GstVideoDecoder *decoder, + gboolean hard); + + GstFlowReturn (*finish) (GstVideoDecoder *decoder); + + GstFlowReturn (*handle_frame) (GstVideoDecoder *decoder, + GstVideoCodecFrame *frame); + + gboolean (*sink_event) (GstVideoDecoder *decoder, + GstEvent *event); + + gboolean (*src_event) (GstVideoDecoder *decoder, + GstEvent *event); + + + /*< private >*/ + /* FIXME before moving to base */ + void *padding[GST_PADDING_LARGE]; +}; + +GType gst_video_decoder_get_type (void); + +/* Context parameters */ +void gst_video_decoder_set_packetized (GstVideoDecoder * decoder, + gboolean packetized); + +gboolean gst_video_decoder_get_packetized (GstVideoDecoder * decoder); + +void gst_video_decoder_set_estimate_rate (GstVideoDecoder * dec, + gboolean enabled); + +gint gst_video_decoder_get_estimate_rate (GstVideoDecoder * dec); + +void gst_video_decoder_set_max_errors (GstVideoDecoder * dec, + gint num); + +gint gst_video_decoder_get_max_errors (GstVideoDecoder * dec); + +void gst_video_decoder_set_latency (GstVideoDecoder *decoder, + GstClockTime min_latency, + GstClockTime max_latency); +void gst_video_decoder_get_latency (GstVideoDecoder *decoder, + GstClockTime *min_latency, + GstClockTime *max_latency); + + +/* Object methods */ + +GstVideoCodecFrame *gst_video_decoder_get_frame (GstVideoDecoder *decoder, + int frame_number); + +GstVideoCodecFrame *gst_video_decoder_get_oldest_frame (GstVideoDecoder *decoder); + +/* Parsing related methods */ +void gst_video_decoder_add_to_frame (GstVideoDecoder *decoder, + int n_bytes); +GstFlowReturn gst_video_decoder_have_frame (GstVideoDecoder *decoder); + +GstBuffer *gst_video_decoder_alloc_output_buffer (GstVideoDecoder * decoder); + +GstFlowReturn gst_video_decoder_alloc_output_frame (GstVideoDecoder *decoder, + GstVideoCodecFrame *frame); + +GstVideoCodecState *gst_video_decoder_set_output_state (GstVideoDecoder *decoder, + GstVideoFormat fmt, guint width, guint height, + GstVideoCodecState *reference); + +GstVideoCodecState *gst_video_decoder_get_output_state (GstVideoDecoder *decoder); + +GstClockTimeDiff gst_video_decoder_get_max_decode_time (GstVideoDecoder *decoder, + GstVideoCodecFrame *frame); + +GstFlowReturn gst_video_decoder_finish_frame (GstVideoDecoder *decoder, + GstVideoCodecFrame *frame); + +GstFlowReturn gst_video_decoder_drop_frame (GstVideoDecoder *dec, + GstVideoCodecFrame *frame); + +G_END_DECLS + +#endif + diff --git a/gst-libs/gst/video/gstvideoencoder.c b/gst-libs/gst/video/gstvideoencoder.c new file mode 100644 index 0000000000..175adcd683 --- /dev/null +++ b/gst-libs/gst/video/gstvideoencoder.c @@ -0,0 +1,1652 @@ +/* GStreamer + * Copyright (C) 2008 David Schleef + * Copyright (C) 2011 Mark Nauwelaerts . + * Copyright (C) 2011 Nokia Corporation. All rights reserved. + * Contact: Stefan Kost + * Copyright (C) 2012 Collabora Ltd. + * Author : Edward Hervey + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gstvideoencoder + * @short_description: Base class for video encoders + * @see_also: + * + * This base class is for video encoders turning raw video into + * encoded video data. + * + * GstVideoEncoder and subclass should cooperate as follows. + * + * + * Configuration + * + * Initially, GstVideoEncoder calls @start when the encoder element + * is activated, which allows subclass to perform any global setup. + * + * + * GstVideoEncoder calls @set_format to inform subclass of the format + * of input video data that it is about to receive. Subclass should + * setup for encoding and configure base class as appropriate + * (e.g. latency). While unlikely, it might be called more than once, + * if changing input parameters require reconfiguration. Baseclass + * will ensure that processing of current configuration is finished. + * + * + * GstVideoEncoder calls @stop at end of all processing. + * + * + * + * + * + * Data processing + * + * Base class collects input data and metadata into a frame and hands + * this to subclass' @handle_frame. + * + * + * If codec processing results in encoded data, subclass should call + * @gst_video_encoder_finish_frame to have encoded data pushed + * downstream. + * + * + * If implemented, baseclass calls subclass @pre_push just prior to + * pushing to allow subclasses to modify some metadata on the buffer. + * If it returns GST_FLOW_OK, the buffer is pushed downstream. + * + * + * GstVideoEncoderClass will handle both srcpad and sinkpad events. + * Sink events will be passed to subclass if @event callback has been + * provided. + * + * + * + * + * Shutdown phase + * + * GstVideoEncoder class calls @stop to inform the subclass that data + * parsing will be stopped. + * + * + * + * + * + * Subclass is responsible for providing pad template caps for + * source and sink pads. The pads need to be named "sink" and "src". It should + * also be able to provide fixed src pad caps in @getcaps by the time it calls + * @gst_video_encoder_finish_frame. + * + * Things that subclass need to take care of: + * + * Provide pad templates + * + * Provide source pad caps before pushing the first buffer + * + * + * Accept data in @handle_frame and provide encoded results to + * @gst_video_encoder_finish_frame. + * + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* TODO + * + * * Change _set_output_format() to steal the reference of the provided caps + * * Calculate actual latency based on input/output timestamp/frame_number + * and if it exceeds the recorded one, save it and emit a GST_MESSAGE_LATENCY + */ + +/* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex + * with newer GLib versions (>= 2.31.0) */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "gstvideoencoder.h" +#include "gstvideoutils.h" + +#include + +GST_DEBUG_CATEGORY (videoencoder_debug); +#define GST_CAT_DEFAULT videoencoder_debug + +#define GST_VIDEO_ENCODER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_VIDEO_ENCODER, \ + GstVideoEncoderPrivate)) + +struct _GstVideoEncoderPrivate +{ + guint64 presentation_frame_number; + int distance_from_sync; + + /* FIXME : (and introduce a context ?) */ + gboolean drained; + gboolean at_eos; + + gint64 min_latency; + gint64 max_latency; + + GList *current_frame_events; + + GList *headers; + gboolean new_headers; /* Whether new headers were just set */ + + GList *force_key_unit; /* List of pending forced keyunits */ + + guint64 system_frame_number; + + GList *frames; /* Protected with OBJECT_LOCK */ + GstVideoCodecState *input_state; + GstVideoCodecState *output_state; + gboolean output_state_changed; + + gint64 bytes; + gint64 time; +}; + +typedef struct _ForcedKeyUnitEvent ForcedKeyUnitEvent; +struct _ForcedKeyUnitEvent +{ + GstClockTime running_time; + gboolean pending; /* TRUE if this was requested already */ + gboolean all_headers; + guint count; +}; + +static void +forced_key_unit_event_free (ForcedKeyUnitEvent * evt) +{ + g_slice_free (ForcedKeyUnitEvent, evt); +} + +static ForcedKeyUnitEvent * +forced_key_unit_event_new (GstClockTime running_time, gboolean all_headers, + guint count) +{ + ForcedKeyUnitEvent *evt = g_slice_new0 (ForcedKeyUnitEvent); + + evt->running_time = running_time; + evt->all_headers = all_headers; + evt->count = count; + + return evt; +} + +static void gst_video_encoder_finalize (GObject * object); + +static gboolean gst_video_encoder_sink_setcaps (GstPad * pad, GstCaps * caps); +static GstCaps *gst_video_encoder_sink_getcaps (GstPad * pad); +static gboolean gst_video_encoder_src_event (GstPad * pad, GstEvent * event); +static gboolean gst_video_encoder_sink_event (GstPad * pad, GstEvent * event); +static GstFlowReturn gst_video_encoder_chain (GstPad * pad, GstBuffer * buf); +static GstStateChangeReturn gst_video_encoder_change_state (GstElement * + element, GstStateChange transition); +static const GstQueryType *gst_video_encoder_get_query_types (GstPad * pad); +static gboolean gst_video_encoder_src_query (GstPad * pad, GstQuery * query); +static GstVideoCodecFrame *gst_video_encoder_new_frame (GstVideoEncoder * + encoder, GstBuffer * buf, GstClockTime timestamp, GstClockTime duration); + +static void +_do_init (GType object_type) +{ + const GInterfaceInfo preset_interface_info = { + NULL, /* interface_init */ + NULL, /* interface_finalize */ + NULL /* interface_data */ + }; + + g_type_add_interface_static (object_type, GST_TYPE_PRESET, + &preset_interface_info); +} + +GST_BOILERPLATE_FULL (GstVideoEncoder, gst_video_encoder, + GstElement, GST_TYPE_ELEMENT, _do_init); + +static void +gst_video_encoder_base_init (gpointer g_class) +{ + GST_DEBUG_CATEGORY_INIT (videoencoder_debug, "videoencoder", 0, + "Base Video Encoder"); +} + +static void +gst_video_encoder_class_init (GstVideoEncoderClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GstVideoEncoderPrivate)); + + gobject_class->finalize = gst_video_encoder_finalize; + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_video_encoder_change_state); +} + +static void +gst_video_encoder_reset (GstVideoEncoder * encoder) +{ + GstVideoEncoderPrivate *priv = encoder->priv; + GList *g; + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + + priv->presentation_frame_number = 0; + priv->distance_from_sync = 0; + + g_list_foreach (priv->force_key_unit, (GFunc) forced_key_unit_event_free, + NULL); + g_list_free (priv->force_key_unit); + priv->force_key_unit = NULL; + + priv->drained = TRUE; + priv->min_latency = 0; + priv->max_latency = 0; + + g_list_foreach (priv->headers, (GFunc) gst_event_unref, NULL); + g_list_free (priv->headers); + priv->headers = NULL; + priv->new_headers = FALSE; + + g_list_foreach (priv->current_frame_events, (GFunc) gst_event_unref, NULL); + g_list_free (priv->current_frame_events); + priv->current_frame_events = NULL; + + for (g = priv->frames; g; g = g->next) { + gst_video_codec_frame_unref ((GstVideoCodecFrame *) g->data); + } + g_list_free (priv->frames); + priv->frames = NULL; + + priv->bytes = 0; + priv->time = 0; + + if (priv->input_state) + gst_video_codec_state_unref (priv->input_state); + priv->input_state = NULL; + if (priv->output_state) + gst_video_codec_state_unref (priv->output_state); + priv->output_state = NULL; + + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); +} + +static void +gst_video_encoder_init (GstVideoEncoder * encoder, GstVideoEncoderClass * klass) +{ + GstVideoEncoderPrivate *priv; + GstPadTemplate *pad_template; + GstPad *pad; + + GST_DEBUG_OBJECT (encoder, "gst_video_encoder_init"); + + priv = encoder->priv = GST_VIDEO_ENCODER_GET_PRIVATE (encoder); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "sink"); + g_return_if_fail (pad_template != NULL); + + encoder->sinkpad = pad = gst_pad_new_from_template (pad_template, "sink"); + + gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_video_encoder_chain)); + gst_pad_set_event_function (pad, + GST_DEBUG_FUNCPTR (gst_video_encoder_sink_event)); + gst_pad_set_setcaps_function (pad, + GST_DEBUG_FUNCPTR (gst_video_encoder_sink_setcaps)); + gst_pad_set_getcaps_function (pad, + GST_DEBUG_FUNCPTR (gst_video_encoder_sink_getcaps)); + gst_element_add_pad (GST_ELEMENT (encoder), encoder->sinkpad); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "src"); + g_return_if_fail (pad_template != NULL); + + encoder->srcpad = pad = gst_pad_new_from_template (pad_template, "src"); + + gst_pad_set_query_type_function (pad, + GST_DEBUG_FUNCPTR (gst_video_encoder_get_query_types)); + gst_pad_set_query_function (pad, + GST_DEBUG_FUNCPTR (gst_video_encoder_src_query)); + gst_pad_set_event_function (pad, + GST_DEBUG_FUNCPTR (gst_video_encoder_src_event)); + gst_element_add_pad (GST_ELEMENT (encoder), encoder->srcpad); + + gst_segment_init (&encoder->input_segment, GST_FORMAT_TIME); + gst_segment_init (&encoder->output_segment, GST_FORMAT_TIME); + + g_static_rec_mutex_init (&encoder->stream_lock); + + priv->at_eos = FALSE; + priv->headers = NULL; + priv->new_headers = FALSE; + + gst_video_encoder_reset (encoder); +} + +static gboolean +gst_video_encoded_video_convert (gint64 bytes, gint64 time, + GstFormat src_format, gint64 src_value, GstFormat * dest_format, + gint64 * dest_value) +{ + gboolean res = FALSE; + + g_return_val_if_fail (dest_format != NULL, FALSE); + g_return_val_if_fail (dest_value != NULL, FALSE); + + if (G_UNLIKELY (src_format == *dest_format || src_value == 0 || + src_value == -1)) { + if (dest_value) + *dest_value = src_value; + return TRUE; + } + + if (bytes <= 0 || time <= 0) { + GST_DEBUG ("not enough metadata yet to convert"); + goto exit; + } + + switch (src_format) { + case GST_FORMAT_BYTES: + switch (*dest_format) { + case GST_FORMAT_TIME: + *dest_value = gst_util_uint64_scale (src_value, time, bytes); + res = TRUE; + break; + default: + res = FALSE; + } + break; + case GST_FORMAT_TIME: + switch (*dest_format) { + case GST_FORMAT_BYTES: + *dest_value = gst_util_uint64_scale (src_value, bytes, time); + res = TRUE; + break; + default: + res = FALSE; + } + break; + default: + GST_DEBUG ("unhandled conversion from %d to %d", src_format, + *dest_format); + res = FALSE; + } + +exit: + return res; +} + +/** + * gst_video_encoder_set_headers: + * @encoder: a #GstVideoEncoder + * @headers: (transfer full) (element-type GstBuffer): a list of #GstBuffer containing the codec header + * + * Set the codec headers to be sent downstream whenever requested. + * + * Since: 0.10.36 + */ +void +gst_video_encoder_set_headers (GstVideoEncoder * video_encoder, GList * headers) +{ + GST_VIDEO_ENCODER_STREAM_LOCK (video_encoder); + + GST_DEBUG_OBJECT (video_encoder, "new headers %p", headers); + if (video_encoder->priv->headers) { + g_list_foreach (video_encoder->priv->headers, (GFunc) gst_buffer_unref, + NULL); + g_list_free (video_encoder->priv->headers); + } + video_encoder->priv->headers = headers; + video_encoder->priv->new_headers = TRUE; + + GST_VIDEO_ENCODER_STREAM_UNLOCK (video_encoder); +} + +static gboolean +gst_video_encoder_drain (GstVideoEncoder * enc) +{ + GstVideoEncoderPrivate *priv; + GstVideoEncoderClass *enc_class; + gboolean ret = TRUE; + + enc_class = GST_VIDEO_ENCODER_GET_CLASS (enc); + priv = enc->priv; + + GST_DEBUG_OBJECT (enc, "draining"); + + if (priv->drained) { + GST_DEBUG_OBJECT (enc, "already drained"); + return TRUE; + } + + if (enc_class->reset) { + GST_DEBUG_OBJECT (enc, "requesting subclass to finish"); + ret = enc_class->reset (enc, TRUE); + } + /* everything should be away now */ + if (priv->frames) { + /* not fatal/impossible though if subclass/enc eats stuff */ + g_list_foreach (priv->frames, (GFunc) gst_video_codec_frame_unref, NULL); + g_list_free (priv->frames); + priv->frames = NULL; + } + + return ret; +} + +static GstVideoCodecState * +_new_output_state (GstCaps * caps, GstVideoCodecState * reference) +{ + GstVideoCodecState *state; + + state = g_slice_new0 (GstVideoCodecState); + state->ref_count = 1; + gst_video_info_init (&state->info); + gst_video_info_set_format (&state->info, GST_VIDEO_FORMAT_ENCODED, 0, 0); + + state->caps = caps; + + if (reference) { + GstVideoInfo *tgt, *ref; + + tgt = &state->info; + ref = &reference->info; + + /* Copy over extra fields from reference state */ + tgt->interlace_mode = ref->interlace_mode; + tgt->flags = ref->flags; + tgt->width = ref->width; + tgt->height = ref->height; + tgt->chroma_site = ref->chroma_site; + tgt->colorimetry = ref->colorimetry; + tgt->par_n = ref->par_n; + tgt->par_d = ref->par_d; + tgt->fps_n = ref->fps_n; + tgt->fps_d = ref->fps_d; + } + + return state; +} + +static GstVideoCodecState * +_new_input_state (GstCaps * caps) +{ + GstVideoCodecState *state; + + state = g_slice_new0 (GstVideoCodecState); + state->ref_count = 1; + gst_video_info_init (&state->info); + if (G_UNLIKELY (!gst_video_info_from_caps (&state->info, caps))) + goto parse_fail; + state->caps = gst_caps_ref (caps); + + return state; + +parse_fail: + { + g_slice_free (GstVideoCodecState, state); + return NULL; + } +} + +static gboolean +gst_video_encoder_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstVideoEncoder *encoder; + GstVideoEncoderClass *encoder_class; + GstVideoCodecState *state; + gboolean ret; + gboolean samecaps = FALSE; + + encoder = GST_VIDEO_ENCODER (gst_pad_get_parent (pad)); + encoder_class = GST_VIDEO_ENCODER_GET_CLASS (encoder); + + /* subclass should do something here ... */ + g_return_val_if_fail (encoder_class->set_format != NULL, FALSE); + + GST_DEBUG_OBJECT (encoder, "setcaps %" GST_PTR_FORMAT, caps); + + state = _new_input_state (caps); + if (G_UNLIKELY (!state)) + goto parse_fail; + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + + if (encoder->priv->input_state) + samecaps = + gst_video_info_is_equal (&state->info, + &encoder->priv->input_state->info); + + if (!samecaps) { + /* arrange draining pending frames */ + gst_video_encoder_drain (encoder); + + /* and subclass should be ready to configure format at any time around */ + ret = encoder_class->set_format (encoder, state); + if (ret) { + if (encoder->priv->input_state) + gst_video_codec_state_unref (encoder->priv->input_state); + encoder->priv->input_state = state; + } else + gst_video_codec_state_unref (state); + } else { + /* no need to stir things up */ + GST_DEBUG_OBJECT (encoder, + "new video format identical to configured format"); + gst_video_codec_state_unref (state); + ret = TRUE; + } + + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + + if (!ret) + GST_WARNING_OBJECT (encoder, "rejected caps %" GST_PTR_FORMAT, caps); + + gst_object_unref (encoder); + + return ret; + +parse_fail: + { + GST_WARNING_OBJECT (encoder, "Failed to parse caps"); + gst_object_unref (encoder); + return FALSE; + } +} + +/** + * gst_video_encoder_proxy_getcaps: + * @enc: a #GstVideoEncoder + * @caps: initial caps + * + * Returns caps that express @caps (or sink template caps if @caps == NULL) + * restricted to resolution/format/... combinations supported by downstream + * elements (e.g. muxers). + * + * Returns: a #GstCaps owned by caller + * + * Since: 0.10.36 + */ +GstCaps * +gst_video_encoder_proxy_getcaps (GstVideoEncoder * encoder, GstCaps * caps) +{ + const GstCaps *templ_caps; + GstCaps *allowed; + GstCaps *fcaps, *filter_caps; + gint i, j; + + /* Allow downstream to specify width/height/framerate/PAR constraints + * and forward them upstream for video converters to handle + */ + templ_caps = caps ? caps : gst_pad_get_pad_template_caps (encoder->sinkpad); + allowed = gst_pad_get_allowed_caps (encoder->srcpad); + + if (!allowed || gst_caps_is_empty (allowed) || gst_caps_is_any (allowed)) { + fcaps = gst_caps_copy (templ_caps); + goto done; + } + + GST_LOG_OBJECT (encoder, "template caps %" GST_PTR_FORMAT, templ_caps); + GST_LOG_OBJECT (encoder, "allowed caps %" GST_PTR_FORMAT, allowed); + + filter_caps = gst_caps_new_empty (); + + for (i = 0; i < gst_caps_get_size (templ_caps); i++) { + GQuark q_name = + gst_structure_get_name_id (gst_caps_get_structure (templ_caps, i)); + + for (j = 0; j < gst_caps_get_size (allowed); j++) { + const GstStructure *allowed_s = gst_caps_get_structure (allowed, j); + const GValue *val; + GstStructure *s; + + s = gst_structure_id_empty_new (q_name); + if ((val = gst_structure_get_value (allowed_s, "width"))) + gst_structure_set_value (s, "width", val); + if ((val = gst_structure_get_value (allowed_s, "height"))) + gst_structure_set_value (s, "height", val); + if ((val = gst_structure_get_value (allowed_s, "framerate"))) + gst_structure_set_value (s, "framerate", val); + if ((val = gst_structure_get_value (allowed_s, "pixel-aspect-ratio"))) + gst_structure_set_value (s, "pixel-aspect-ratio", val); + + gst_caps_merge_structure (filter_caps, s); + } + } + + fcaps = gst_caps_intersect (filter_caps, templ_caps); + gst_caps_unref (filter_caps); + +done: + gst_caps_replace (&allowed, NULL); + + GST_LOG_OBJECT (encoder, "proxy caps %" GST_PTR_FORMAT, fcaps); + + return fcaps; +} + +static GstCaps * +gst_video_encoder_sink_getcaps (GstPad * pad) +{ + GstVideoEncoder *encoder; + GstVideoEncoderClass *klass; + GstCaps *caps; + + encoder = GST_VIDEO_ENCODER (gst_pad_get_parent (pad)); + klass = GST_VIDEO_ENCODER_GET_CLASS (encoder); + + if (klass->getcaps) + caps = klass->getcaps (encoder); + else + caps = gst_video_encoder_proxy_getcaps (encoder, NULL); + gst_object_unref (encoder); + + GST_LOG_OBJECT (encoder, "Returning caps %" GST_PTR_FORMAT, caps); + + return caps; +} + +static void +gst_video_encoder_finalize (GObject * object) +{ + GstVideoEncoder *encoder; + + GST_DEBUG_OBJECT (object, "finalize"); + + encoder = GST_VIDEO_ENCODER (object); + if (encoder->priv->headers) { + g_list_foreach (encoder->priv->headers, (GFunc) gst_buffer_unref, NULL); + g_list_free (encoder->priv->headers); + } + g_static_rec_mutex_free (&encoder->stream_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_video_encoder_push_event (GstVideoEncoder * encoder, GstEvent * event) +{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + double rate; + double applied_rate; + GstFormat format; + gint64 start; + gint64 stop; + gint64 position; + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &position); + + GST_DEBUG_OBJECT (encoder, "newseg rate %g, applied rate %g, " + "format %d, start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, rate, applied_rate, format, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), + GST_TIME_ARGS (position)); + + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (encoder, "received non TIME newsegment"); + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + break; + } + + gst_segment_set_newsegment_full (&encoder->output_segment, update, rate, + applied_rate, format, start, stop, position); + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + break; + } + default: + break; + } + + return gst_pad_push_event (encoder->srcpad, event); +} + +static gboolean +gst_video_encoder_sink_eventfunc (GstVideoEncoder * encoder, GstEvent * event) +{ + GstVideoEncoderClass *encoder_class; + gboolean ret = FALSE; + + encoder_class = GST_VIDEO_ENCODER_GET_CLASS (encoder); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + { + GstFlowReturn flow_ret; + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + encoder->priv->at_eos = TRUE; + + if (encoder_class->finish) { + flow_ret = encoder_class->finish (encoder); + } else { + flow_ret = GST_FLOW_OK; + } + + ret = (flow_ret == GST_VIDEO_ENCODER_FLOW_DROPPED); + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + break; + } + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + double rate; + double applied_rate; + GstFormat format; + gint64 start; + gint64 stop; + gint64 position; + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &position); + + GST_DEBUG_OBJECT (encoder, "newseg rate %g, applied rate %g, " + "format %d, start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, rate, applied_rate, format, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), + GST_TIME_ARGS (position)); + + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (encoder, "received non TIME newsegment"); + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + break; + } + + encoder->priv->at_eos = FALSE; + + gst_segment_set_newsegment_full (&encoder->input_segment, update, rate, + applied_rate, format, start, stop, position); + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + break; + } + case GST_EVENT_CUSTOM_DOWNSTREAM: + { + if (gst_video_event_is_force_key_unit (event)) { + GstClockTime running_time; + gboolean all_headers; + guint count; + + if (gst_video_event_parse_downstream_force_key_unit (event, + NULL, NULL, &running_time, &all_headers, &count)) { + ForcedKeyUnitEvent *fevt; + + GST_OBJECT_LOCK (encoder); + fevt = forced_key_unit_event_new (running_time, all_headers, count); + encoder->priv->force_key_unit = + g_list_append (encoder->priv->force_key_unit, fevt); + GST_OBJECT_UNLOCK (encoder); + + GST_DEBUG_OBJECT (encoder, + "force-key-unit event: running-time %" GST_TIME_FORMAT + ", all_headers %d, count %u", + GST_TIME_ARGS (running_time), all_headers, count); + } + gst_event_unref (event); + ret = TRUE; + } + break; + } + default: + break; + } + + return ret; +} + +static gboolean +gst_video_encoder_sink_event (GstPad * pad, GstEvent * event) +{ + GstVideoEncoder *enc; + GstVideoEncoderClass *klass; + gboolean handled = FALSE; + gboolean ret = TRUE; + + enc = GST_VIDEO_ENCODER (gst_pad_get_parent (pad)); + klass = GST_VIDEO_ENCODER_GET_CLASS (enc); + + GST_DEBUG_OBJECT (enc, "received event %d, %s", GST_EVENT_TYPE (event), + GST_EVENT_TYPE_NAME (event)); + + if (klass->sink_event) + handled = klass->sink_event (enc, event); + + if (!handled) + handled = gst_video_encoder_sink_eventfunc (enc, event); + + if (!handled) { + /* Forward non-serialized events and EOS/FLUSH_STOP immediately. + * For EOS this is required because no buffer or serialized event + * will come after EOS and nothing could trigger another + * _finish_frame() call. * + * If the subclass handles sending of EOS manually it can return + * _DROPPED from ::finish() and all other subclasses should have + * decoded/flushed all remaining data before this + * + * For FLUSH_STOP this is required because it is expected + * to be forwarded immediately and no buffers are queued anyway. + */ + if (!GST_EVENT_IS_SERIALIZED (event) + || GST_EVENT_TYPE (event) == GST_EVENT_EOS + || GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) { + ret = gst_video_encoder_push_event (enc, event); + } else { + GST_VIDEO_ENCODER_STREAM_LOCK (enc); + enc->priv->current_frame_events = + g_list_prepend (enc->priv->current_frame_events, event); + GST_VIDEO_ENCODER_STREAM_UNLOCK (enc); + } + } + + GST_DEBUG_OBJECT (enc, "event handled"); + + gst_object_unref (enc); + return ret; +} + +static gboolean +gst_video_encoder_src_eventfunc (GstVideoEncoder * encoder, GstEvent * event) +{ + gboolean handled = FALSE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_UPSTREAM: + { + if (gst_video_event_is_force_key_unit (event)) { + GstClockTime running_time; + gboolean all_headers; + guint count; + + if (gst_video_event_parse_upstream_force_key_unit (event, + &running_time, &all_headers, &count)) { + ForcedKeyUnitEvent *fevt; + + GST_OBJECT_LOCK (encoder); + fevt = forced_key_unit_event_new (running_time, all_headers, count); + encoder->priv->force_key_unit = + g_list_append (encoder->priv->force_key_unit, fevt); + GST_OBJECT_UNLOCK (encoder); + + GST_DEBUG_OBJECT (encoder, + "force-key-unit event: running-time %" GST_TIME_FORMAT + ", all_headers %d, count %u", + GST_TIME_ARGS (running_time), all_headers, count); + } + gst_event_unref (event); + handled = TRUE; + } + break; + } + default: + break; + } + + return handled; +} + +static gboolean +gst_video_encoder_src_event (GstPad * pad, GstEvent * event) +{ + GstVideoEncoder *encoder; + GstVideoEncoderClass *klass; + gboolean ret = FALSE; + gboolean handled = FALSE; + + encoder = GST_VIDEO_ENCODER (gst_pad_get_parent (pad)); + klass = GST_VIDEO_ENCODER_GET_CLASS (encoder); + + GST_LOG_OBJECT (encoder, "handling event: %" GST_PTR_FORMAT, event); + + if (klass->src_event) + handled = klass->src_event (encoder, event); + + if (!handled) + handled = gst_video_encoder_src_eventfunc (encoder, event); + + if (!handled) + ret = gst_pad_event_default (pad, event); + + gst_object_unref (encoder); + + return ret; +} + +static const GstQueryType * +gst_video_encoder_get_query_types (GstPad * pad) +{ + static const GstQueryType query_types[] = { + GST_QUERY_CONVERT, + GST_QUERY_LATENCY, + 0 + }; + + return query_types; +} + +static gboolean +gst_video_encoder_src_query (GstPad * pad, GstQuery * query) +{ + GstVideoEncoderPrivate *priv; + GstVideoEncoder *enc; + gboolean res; + GstPad *peerpad; + + enc = GST_VIDEO_ENCODER (gst_pad_get_parent (pad)); + priv = enc->priv; + peerpad = gst_pad_get_peer (enc->sinkpad); + + GST_LOG_OBJECT (enc, "handling query: %" GST_PTR_FORMAT, query); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONVERT: + { + GstFormat src_fmt, dest_fmt; + gint64 src_val, dest_val; + + gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); + res = + gst_video_encoded_video_convert (priv->bytes, priv->time, src_fmt, + src_val, &dest_fmt, &dest_val); + if (!res) + goto error; + gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); + break; + } + case GST_QUERY_LATENCY: + { + gboolean live; + GstClockTime min_latency, max_latency; + + res = gst_pad_query (peerpad, query); + if (res) { + gst_query_parse_latency (query, &live, &min_latency, &max_latency); + GST_DEBUG_OBJECT (enc, "Peer latency: live %d, min %" + GST_TIME_FORMAT " max %" GST_TIME_FORMAT, live, + GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); + + GST_OBJECT_LOCK (enc); + min_latency += priv->min_latency; + if (max_latency != GST_CLOCK_TIME_NONE) { + max_latency += priv->max_latency; + } + GST_OBJECT_UNLOCK (enc); + + gst_query_set_latency (query, live, min_latency, max_latency); + } + } + break; + default: + res = gst_pad_query_default (pad, query); + } + gst_object_unref (peerpad); + gst_object_unref (enc); + return res; + +error: + GST_DEBUG_OBJECT (enc, "query failed"); + gst_object_unref (peerpad); + gst_object_unref (enc); + return res; +} + +static GstVideoCodecFrame * +gst_video_encoder_new_frame (GstVideoEncoder * encoder, GstBuffer * buf, + GstClockTime timestamp, GstClockTime duration) +{ + GstVideoEncoderPrivate *priv = encoder->priv; + GstVideoCodecFrame *frame; + + frame = g_slice_new0 (GstVideoCodecFrame); + + frame->ref_count = 1; + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + frame->system_frame_number = priv->system_frame_number; + priv->system_frame_number++; + + frame->presentation_frame_number = priv->presentation_frame_number; + priv->presentation_frame_number++; + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + + frame->events = priv->current_frame_events; + priv->current_frame_events = NULL; + frame->input_buffer = buf; + frame->pts = timestamp; + frame->duration = duration; + + return frame; +} + + +static GstFlowReturn +gst_video_encoder_chain (GstPad * pad, GstBuffer * buf) +{ + GstVideoEncoder *encoder; + GstVideoEncoderPrivate *priv; + GstVideoEncoderClass *klass; + GstVideoCodecFrame *frame; + GstFlowReturn ret = GST_FLOW_OK; + gint64 start, stop = GST_CLOCK_TIME_NONE, cstart, cstop; + + encoder = GST_VIDEO_ENCODER (gst_pad_get_parent (pad)); + priv = encoder->priv; + klass = GST_VIDEO_ENCODER_GET_CLASS (encoder); + + g_return_val_if_fail (klass->handle_frame != NULL, GST_FLOW_ERROR); + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + + /* .... ?? */ + if (!GST_PAD_CAPS (pad)) { + ret = GST_FLOW_NOT_NEGOTIATED; + goto done; + } + + start = GST_BUFFER_TIMESTAMP (buf); + if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buf))) + stop = start + GST_BUFFER_DURATION (buf); + + GST_LOG_OBJECT (encoder, + "received buffer of size %d with ts %" GST_TIME_FORMAT + ", duration %" GST_TIME_FORMAT, GST_BUFFER_SIZE (buf), + GST_TIME_ARGS (start), GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); + + if (priv->at_eos) { + ret = GST_FLOW_UNEXPECTED; + goto done; + } + + /* Drop buffers outside of segment */ + if (!gst_segment_clip (&encoder->output_segment, + GST_FORMAT_TIME, start, stop, &cstart, &cstop)) { + GST_DEBUG_OBJECT (encoder, "clipping to segment dropped frame"); + gst_buffer_unref (buf); + goto done; + } + + frame = gst_video_encoder_new_frame (encoder, buf, cstart, cstop - cstart); + + GST_OBJECT_LOCK (encoder); + if (priv->force_key_unit) { + ForcedKeyUnitEvent *fevt = NULL; + GstClockTime running_time; + GList *l; + + running_time = + gst_segment_to_running_time (&encoder->output_segment, GST_FORMAT_TIME, + GST_BUFFER_TIMESTAMP (buf)); + + for (l = priv->force_key_unit; l; l = l->next) { + ForcedKeyUnitEvent *tmp = l->data; + + /* Skip pending keyunits */ + if (tmp->pending) + continue; + + /* Simple case, keyunit ASAP */ + if (tmp->running_time == GST_CLOCK_TIME_NONE) { + fevt = tmp; + break; + } + + /* Event for before this frame */ + if (tmp->running_time <= running_time) { + fevt = tmp; + break; + } + } + + if (fevt) { + GST_DEBUG_OBJECT (encoder, + "Forcing a key unit at running time %" GST_TIME_FORMAT, + GST_TIME_ARGS (running_time)); + GST_VIDEO_CODEC_FRAME_SET_FORCE_KEYFRAME (frame); + if (fevt->all_headers) + GST_VIDEO_CODEC_FRAME_SET_FORCE_KEYFRAME_HEADERS (frame); + fevt->pending = TRUE; + } + } + GST_OBJECT_UNLOCK (encoder); + + priv->frames = g_list_append (priv->frames, frame); + + /* new data, more finish needed */ + priv->drained = FALSE; + + GST_LOG_OBJECT (encoder, "passing frame pfn %d to subclass", + frame->presentation_frame_number); + + ret = klass->handle_frame (encoder, frame); + +done: + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + + gst_object_unref (encoder); + + return ret; +} + +static GstStateChangeReturn +gst_video_encoder_change_state (GstElement * element, GstStateChange transition) +{ + GstVideoEncoder *encoder; + GstVideoEncoderClass *encoder_class; + GstStateChangeReturn ret; + + encoder = GST_VIDEO_ENCODER (element); + encoder_class = GST_VIDEO_ENCODER_GET_CLASS (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + /* open device/library if needed */ + if (encoder_class->open && !encoder_class->open (encoder)) + goto open_failed; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + /* Initialize device/library if needed */ + if (encoder_class->start && !encoder_class->start (encoder)) + goto start_failed; + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_video_encoder_reset (encoder); + if (encoder_class->stop && !encoder_class->stop (encoder)) + goto stop_failed; + break; + case GST_STATE_CHANGE_READY_TO_NULL: + /* close device/library if needed */ + if (encoder_class->close && !encoder_class->close (encoder)) + goto close_failed; + break; + default: + break; + } + + return ret; + + /* Errors */ + +open_failed: + { + GST_ELEMENT_ERROR (encoder, LIBRARY, INIT, (NULL), + ("Failed to open encoder")); + return GST_STATE_CHANGE_FAILURE; + } + +start_failed: + { + GST_ELEMENT_ERROR (encoder, LIBRARY, INIT, (NULL), + ("Failed to start encoder")); + return GST_STATE_CHANGE_FAILURE; + } + +stop_failed: + { + GST_ELEMENT_ERROR (encoder, LIBRARY, INIT, (NULL), + ("Failed to stop encoder")); + return GST_STATE_CHANGE_FAILURE; + } + +close_failed: + { + GST_ELEMENT_ERROR (encoder, LIBRARY, INIT, (NULL), + ("Failed to close encoder")); + return GST_STATE_CHANGE_FAILURE; + } +} + +static gboolean +gst_video_encoder_set_src_caps (GstVideoEncoder * encoder) +{ + gboolean ret; + GstVideoCodecState *state = encoder->priv->output_state; + GstVideoInfo *info = &state->info; + + g_return_val_if_fail (state->caps != NULL, FALSE); + + if (encoder->priv->output_state_changed) { + state->caps = gst_caps_make_writable (state->caps); + + /* Fill caps */ + gst_caps_set_simple (state->caps, "width", G_TYPE_INT, info->width, + "height", G_TYPE_INT, info->height, + "pixel-aspect-ratio", GST_TYPE_FRACTION, + info->par_n, info->par_d, NULL); + if (info->flags & GST_VIDEO_FLAG_VARIABLE_FPS && info->fps_n != 0) { + /* variable fps with a max-framerate */ + gst_caps_set_simple (state->caps, "framerate", GST_TYPE_FRACTION, 0, 1, + "max-framerate", GST_TYPE_FRACTION, info->fps_n, info->fps_d, NULL); + } else { + /* no variable fps or no max-framerate */ + gst_caps_set_simple (state->caps, "framerate", GST_TYPE_FRACTION, + info->fps_n, info->fps_d, NULL); + } + if (state->codec_data) + gst_caps_set_simple (state->caps, "codec_data", GST_TYPE_BUFFER, + state->codec_data, NULL); + encoder->priv->output_state_changed = FALSE; + } + + ret = gst_pad_set_caps (encoder->srcpad, state->caps); + + return ret; +} + +/** + * gst_video_encoder_finish_frame: + * @encoder: a #GstVideoEncoder + * @frame: (transfer full): an encoded #GstVideoCodecFrame + * + * @frame must have a valid encoded data buffer, whose metadata fields + * are then appropriately set according to frame data or no buffer at + * all if the frame should be dropped. + * It is subsequently pushed downstream or provided to @pre_push. + * In any case, the frame is considered finished and released. + * + * Returns: a #GstFlowReturn resulting from sending data downstream + * + * Since: 0.10.36 + */ +GstFlowReturn +gst_video_encoder_finish_frame (GstVideoEncoder * encoder, + GstVideoCodecFrame * frame) +{ + GstVideoEncoderPrivate *priv = encoder->priv; + GstFlowReturn ret = GST_FLOW_OK; + GstVideoEncoderClass *encoder_class; + GList *l; + gboolean send_headers = FALSE; + gboolean discont = (frame->presentation_frame_number == 0); + + encoder_class = GST_VIDEO_ENCODER_GET_CLASS (encoder); + + GST_LOG_OBJECT (encoder, + "finish frame fpn %d", frame->presentation_frame_number); + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + + if (G_UNLIKELY (priv->output_state_changed)) + gst_video_encoder_set_src_caps (encoder); + + if (G_UNLIKELY (priv->output_state == NULL)) + goto no_output_state; + + /* Push all pending events that arrived before this frame */ + for (l = priv->frames; l; l = l->next) { + GstVideoCodecFrame *tmp = l->data; + + if (tmp->events) { + GList *k; + + for (k = g_list_last (tmp->events); k; k = k->prev) + gst_video_encoder_push_event (encoder, k->data); + g_list_free (tmp->events); + tmp->events = NULL; + } + + if (tmp == frame) + break; + } + + /* no buffer data means this frame is skipped/dropped */ + if (!frame->output_buffer) { + GST_DEBUG_OBJECT (encoder, "skipping frame %" GST_TIME_FORMAT, + GST_TIME_ARGS (frame->pts)); + goto done; + } + + if (GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame) && priv->force_key_unit) { + GstClockTime stream_time, running_time; + GstEvent *ev; + ForcedKeyUnitEvent *fevt = NULL; + GList *l; + + running_time = + gst_segment_to_running_time (&encoder->output_segment, GST_FORMAT_TIME, + frame->pts); + + GST_OBJECT_LOCK (encoder); + for (l = priv->force_key_unit; l; l = l->next) { + ForcedKeyUnitEvent *tmp = l->data; + + /* Skip non-pending keyunits */ + if (!tmp->pending) + continue; + + /* Simple case, keyunit ASAP */ + if (tmp->running_time == GST_CLOCK_TIME_NONE) { + fevt = tmp; + break; + } + + /* Event for before this frame */ + if (tmp->running_time <= running_time) { + fevt = tmp; + break; + } + } + + if (fevt) { + priv->force_key_unit = g_list_remove (priv->force_key_unit, fevt); + } + GST_OBJECT_UNLOCK (encoder); + + if (fevt) { + stream_time = + gst_segment_to_stream_time (&encoder->output_segment, GST_FORMAT_TIME, + frame->pts); + + ev = gst_video_event_new_downstream_force_key_unit + (frame->pts, stream_time, running_time, + fevt->all_headers, fevt->count); + + gst_video_encoder_push_event (encoder, ev); + + if (fevt->all_headers) + send_headers = TRUE; + + GST_DEBUG_OBJECT (encoder, + "Forced key unit: running-time %" GST_TIME_FORMAT + ", all_headers %d, count %u", + GST_TIME_ARGS (running_time), fevt->all_headers, fevt->count); + forced_key_unit_event_free (fevt); + } + } + + if (GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame)) { + priv->distance_from_sync = 0; + GST_BUFFER_FLAG_UNSET (frame->output_buffer, GST_BUFFER_FLAG_DELTA_UNIT); + /* For keyframes, DTS = PTS */ + frame->dts = frame->pts; + } else { + GST_BUFFER_FLAG_SET (frame->output_buffer, GST_BUFFER_FLAG_DELTA_UNIT); + } + + frame->distance_from_sync = priv->distance_from_sync; + priv->distance_from_sync++; + + GST_BUFFER_TIMESTAMP (frame->output_buffer) = frame->pts; + GST_BUFFER_DURATION (frame->output_buffer) = frame->duration; + + /* update rate estimate */ + priv->bytes += GST_BUFFER_SIZE (frame->output_buffer); + if (GST_CLOCK_TIME_IS_VALID (frame->duration)) { + priv->time += frame->duration; + } else { + /* better none than nothing valid */ + priv->time = GST_CLOCK_TIME_NONE; + } + + if (G_UNLIKELY (send_headers || priv->new_headers)) { + GList *tmp, *copy = NULL; + + GST_DEBUG_OBJECT (encoder, "Sending headers"); + + /* First make all buffers metadata-writable */ + for (tmp = priv->headers; tmp; tmp = tmp->next) { + GstBuffer *tmpbuf = GST_BUFFER (tmp->data); + + copy = g_list_append (copy, gst_buffer_make_metadata_writable (tmpbuf)); + } + g_list_free (priv->headers); + priv->headers = copy; + + for (tmp = priv->headers; tmp; tmp = tmp->next) { + GstBuffer *tmpbuf = GST_BUFFER (tmp->data); + + gst_buffer_set_caps (tmpbuf, GST_PAD_CAPS (encoder->srcpad)); + gst_buffer_ref (tmpbuf); + priv->bytes += GST_BUFFER_SIZE (tmpbuf); + if (G_UNLIKELY (discont)) { + GST_LOG_OBJECT (encoder, "marking discont"); + GST_BUFFER_FLAG_SET (tmpbuf, GST_BUFFER_FLAG_DISCONT); + discont = FALSE; + } + + gst_pad_push (encoder->srcpad, tmpbuf); + } + priv->new_headers = FALSE; + } + + if (G_UNLIKELY (discont)) { + GST_LOG_OBJECT (encoder, "marking discont"); + GST_BUFFER_FLAG_SET (frame->output_buffer, GST_BUFFER_FLAG_DISCONT); + } + + gst_buffer_set_caps (GST_BUFFER (frame->output_buffer), + GST_PAD_CAPS (encoder->srcpad)); + + if (encoder_class->pre_push) + ret = encoder_class->pre_push (encoder, frame); + + if (ret == GST_FLOW_OK) + ret = gst_pad_push (encoder->srcpad, frame->output_buffer); + + frame->output_buffer = NULL; + +done: + /* handed out */ + priv->frames = g_list_remove (priv->frames, frame); + + gst_video_codec_frame_unref (frame); + + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + + return ret; + + /* ERRORS */ +no_output_state: + { + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + GST_ERROR_OBJECT (encoder, "Output state was not configured"); + return GST_FLOW_ERROR; + } +} + +/** + * gst_video_encoder_get_output_state: + * @encoder: a #GstVideoEncoder + * + * Get the current #GstVideoCodecState + * + * Returns: (transfer full): #GstVideoCodecState describing format of video data. + * + * Since: 0.10.36 + */ +GstVideoCodecState * +gst_video_encoder_get_output_state (GstVideoEncoder * encoder) +{ + GstVideoCodecState *state; + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + state = gst_video_codec_state_ref (encoder->priv->output_state); + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + + return state; +} + +/** + * gst_video_encoder_set_output_state: + * @encoder: a #GstVideoEncoder + * @caps: (transfer full): the #GstCaps to use for the output + * @reference: (allow-none) (transfer none): An optional reference @GstVideoCodecState + * + * Creates a new #GstVideoCodecState with the specified caps as the output state + * for the encoder. + * Any previously set output state on @decoder will be replaced by the newly + * created one. + * + * The specified @caps should not contain any resolution, pixel-aspect-ratio, + * framerate, codec-data, .... Those should be specified instead in the returned + * #GstVideoCodecState. + * + * If the subclass wishes to copy over existing fields (like pixel aspect ratio, + * or framerate) from an existing #GstVideoCodecState, it can be provided as a + * @reference. + * + * If the subclass wishes to override some fields from the output state (like + * pixel-aspect-ratio or framerate) it can do so on the returned #GstVideoCodecState. + * + * The new output state will only take effect (set on pads and buffers) starting + * from the next call to #gst_video_encoder_finish_frame(). + * + * Returns: (transfer full): the newly configured output state. + * + * Since: 0.10.36 + */ +GstVideoCodecState * +gst_video_encoder_set_output_state (GstVideoEncoder * encoder, GstCaps * caps, + GstVideoCodecState * reference) +{ + GstVideoEncoderPrivate *priv = encoder->priv; + GstVideoCodecState *state; + + g_return_val_if_fail (caps != NULL, NULL); + + state = _new_output_state (caps, reference); + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + if (priv->output_state) + gst_video_codec_state_unref (priv->output_state); + priv->output_state = gst_video_codec_state_ref (state); + + priv->output_state_changed = TRUE; + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + + return state; +} + +/** + * gst_video_encoder_set_latency: + * @encoder: a #GstVideoEncoder + * @min_latency: minimum latency + * @max_latency: maximum latency + * + * Informs baseclass of encoding latency. + * + * Since: 0.10.36 + */ +void +gst_video_encoder_set_latency (GstVideoEncoder * encoder, + GstClockTime min_latency, GstClockTime max_latency) +{ + g_return_if_fail (GST_CLOCK_TIME_IS_VALID (min_latency)); + g_return_if_fail (max_latency >= min_latency); + + GST_OBJECT_LOCK (encoder); + encoder->priv->min_latency = min_latency; + encoder->priv->max_latency = max_latency; + GST_OBJECT_UNLOCK (encoder); + + gst_element_post_message (GST_ELEMENT_CAST (encoder), + gst_message_new_latency (GST_OBJECT_CAST (encoder))); +} + +/** + * gst_video_encoder_get_latency: + * @encoder: a #GstVideoEncoder + * @min_latency: (out) (allow-none): the configured minimum latency + * @max_latency: (out) (allow-none): the configured maximum latency + * + * Returns the configured encoding latency. + * + * Since: 0.10.36 + */ +void +gst_video_encoder_get_latency (GstVideoEncoder * encoder, + GstClockTime * min_latency, GstClockTime * max_latency) +{ + GST_OBJECT_LOCK (encoder); + if (min_latency) + *min_latency = encoder->priv->min_latency; + if (max_latency) + *max_latency = encoder->priv->max_latency; + GST_OBJECT_UNLOCK (encoder); +} + +/** + * gst_video_encoder_get_oldest_frame: + * @encoder: a #GstVideoEncoder + * + * Get the oldest unfinished pending #GstVideoCodecFrame + * + * Returns: oldest unfinished pending #GstVideoCodecFrame + * + * Since: 0.10.36 + */ +GstVideoCodecFrame * +gst_video_encoder_get_oldest_frame (GstVideoEncoder * encoder) +{ + GList *g; + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + g = encoder->priv->frames; + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + + if (g == NULL) + return NULL; + return (GstVideoCodecFrame *) (g->data); +} + +/** + * gst_video_encoder_get_frame: + * @encoder: a #GstVideoEnccoder + * @frame_number: system_frame_number of a frame + * + * Get a pending unfinished #GstVideoCodecFrame + * + * Returns: (transfer none): pending unfinished #GstVideoCodecFrame identified by @frame_number. + * + * Since: 0.10.36 + */ +GstVideoCodecFrame * +gst_video_encoder_get_frame (GstVideoEncoder * encoder, int frame_number) +{ + GList *g; + GstVideoCodecFrame *frame = NULL; + + GST_DEBUG_OBJECT (encoder, "frame_number : %d", frame_number); + + GST_VIDEO_ENCODER_STREAM_LOCK (encoder); + for (g = encoder->priv->frames; g; g = g->next) { + GstVideoCodecFrame *tmp = g->data; + + if (tmp->system_frame_number == frame_number) { + frame = tmp; + break; + } + } + GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); + + return frame; +} diff --git a/gst-libs/gst/video/gstvideoencoder.h b/gst-libs/gst/video/gstvideoencoder.h new file mode 100644 index 0000000000..2df301738f --- /dev/null +++ b/gst-libs/gst/video/gstvideoencoder.h @@ -0,0 +1,306 @@ +/* GStreamer + * Copyright (C) 2008 David Schleef + * Copyright (C) 2011 Mark Nauwelaerts . + * Copyright (C) 2011 Nokia Corporation. All rights reserved. + * Contact: Stefan Kost + * Copyright (C) 2012 Collabora Ltd. + * Author : Edward Hervey + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _GST_VIDEO_ENCODER_H_ +#define _GST_VIDEO_ENCODER_H_ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_VIDEO_ENCODER \ + (gst_video_encoder_get_type()) +#define GST_VIDEO_ENCODER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VIDEO_ENCODER,GstVideoEncoder)) +#define GST_VIDEO_ENCODER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VIDEO_ENCODER,GstVideoEncoderClass)) +#define GST_VIDEO_ENCODER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_VIDEO_ENCODER,GstVideoEncoderClass)) +#define GST_IS_VIDEO_ENCODER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIDEO_ENCODER)) +#define GST_IS_VIDEO_ENCODER_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIDEO_ENCODER)) +#define GST_VIDEO_ENCODER_CAST(enc) ((GstVideoEncoder*)enc) + +/** + * GST_VIDEO_ENCODER_SINK_NAME: + * + * The name of the templates for the sink pad. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_SINK_NAME "sink" +/** + * GST_VIDEO_ENCODER_SRC_NAME: + * + * The name of the templates for the source pad. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_SRC_NAME "src" + +/** + * GST_VIDEO_ENCODER_FLOW_DROPPED: + * + * Returned when the event/buffer should be dropped. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_FLOW_DROPPED GST_FLOW_CUSTOM_SUCCESS_1 + +/** + * GST_VIDEO_ENCODER_SRC_PAD: + * @obj: a #GstVideoEncoder + * + * Gives the pointer to the source #GstPad object of the element. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_SRC_PAD(obj) (((GstVideoEncoder *) (obj))->srcpad) + +/** + * GST_VIDEO_ENCODER_SINK_PAD: + * @obj: a #GstVideoEncoder + * + * Gives the pointer to the sink #GstPad object of the element. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_SINK_PAD(obj) (((GstVideoEncoder *) (obj))->sinkpad) + +/** + * GST_VIDEO_ENCODER_FLOW_NEED_DATA: + * + * Returned while parsing to indicate more data is needed. + * + * Since: 0.10.36 + **/ +#define GST_VIDEO_ENCODER_FLOW_NEED_DATA GST_FLOW_CUSTOM_SUCCESS + +/** + * GST_VIDEO_ENCODER_FLOW_DROPPED: + * + * Returned when the event/buffer should be dropped. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_FLOW_DROPPED GST_FLOW_CUSTOM_SUCCESS_1 + +/** + * GST_VIDEO_ENCODER_INPUT_SEGMENT: + * @obj: base parse instance + * + * Gives the segment of the element. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_INPUT_SEGMENT(obj) (GST_VIDEO_ENCODER_CAST (obj)->input_segment) + +/** + * GST_VIDEO_ENCODER_OUTPUT_SEGMENT: + * @obj: base parse instance + * + * Gives the segment of the element. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_OUTPUT_SEGMENT(obj) (GST_VIDEO_ENCODER_CAST (obj)->output_segment) + +/** + * GST_VIDEO_ENCODER_STREAM_LOCK: + * @encoder: video encoder instance + * + * Obtain a lock to protect the encoder function from concurrent access. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_STREAM_LOCK(encoder) g_static_rec_mutex_lock (&GST_VIDEO_ENCODER (encoder)->stream_lock) + +/** + * GST_VIDEO_ENCODER_STREAM_UNLOCK: + * @encoder: video encoder instance + * + * Release the lock that protects the encoder function from concurrent access. + * + * Since: 0.10.36 + */ +#define GST_VIDEO_ENCODER_STREAM_UNLOCK(encoder) g_static_rec_mutex_unlock (&GST_VIDEO_ENCODER (encoder)->stream_lock) + +typedef struct _GstVideoEncoder GstVideoEncoder; +typedef struct _GstVideoEncoderPrivate GstVideoEncoderPrivate; +typedef struct _GstVideoEncoderClass GstVideoEncoderClass; + +/** + * GstVideoEncoder: + * + * The opaque #GstVideoEncoder data structure. + * + * Since: 0.10.36 + */ +struct _GstVideoEncoder +{ + /*< private >*/ + GstElement element; + + /*< protected >*/ + GstPad *sinkpad; + GstPad *srcpad; + + /* protects all data processing, i.e. is locked + * in the chain function, finish_frame and when + * processing serialized events */ + GStaticRecMutex stream_lock; + + /* MT-protected (with STREAM_LOCK) */ + GstSegment input_segment; + GstSegment output_segment; + + GstVideoEncoderPrivate *priv; + /* FIXME before moving to base */ + void *padding[GST_PADDING_LARGE]; +}; + +/** + * GstVideoEncoderClass: + * @open: Optional. + * Called when the element changes to GST_STATE_READY. + * Allows opening external resources. Since: 0.10.37. + * @close: Optional. + * Called when the element changes to GST_STATE_NULL. + * Allows closing external resources. Since: 0.10.37. + * @start: Optional. + * Called when the element starts processing. + * Allows opening external resources. + * @stop: Optional. + * Called when the element stops processing. + * Allows closing external resources. + * @set_format: Optional. + * Notifies subclass of incoming data format. + * GstVideoCodecState fields have already been + * set according to provided caps. + * @handle_frame: Provides input frame to subclass. + * @reset: Optional. + * Allows subclass (encoder) to perform post-seek semantics reset. + * @finish: Optional. + * Called to request subclass to dispatch any pending remaining + * data (e.g. at EOS). + * @pre_push: Optional. + * Allows subclass to push frame downstream in whatever + * shape or form it deems appropriate. If not provided, + * provided encoded frame data is simply pushed downstream. + * @getcaps: Optional. + * Allows for a custom sink getcaps implementation (e.g. + * for multichannel input specification). If not implemented, + * default returns gst_video_encoder_proxy_getcaps + * applied to sink template caps. + * @sink_event: Optional. + * Event handler on the sink pad. This function should return + * TRUE if the event was handled and should be discarded + * (i.e. not unref'ed). + * @src_event: Optional. + * Event handler on the source pad. This function should return + * TRUE if the event was handled and should be discarded + * (i.e. not unref'ed). + * + * Subclasses can override any of the available virtual methods or not, as + * needed. At minimum @handle_frame needs to be overridden, and @set_format + * and @get_caps are likely needed as well. + * + * Since: 0.10.36 + */ +struct _GstVideoEncoderClass +{ + /*< private >*/ + GstElementClass element_class; + + /*< public >*/ + /* virtual methods for subclasses */ + gboolean (*open) (GstVideoEncoder *encoder); + + gboolean (*close) (GstVideoEncoder *encoder); + + gboolean (*start) (GstVideoEncoder *encoder); + + gboolean (*stop) (GstVideoEncoder *encoder); + + gboolean (*set_format) (GstVideoEncoder *encoder, + GstVideoCodecState *state); + + GstFlowReturn (*handle_frame) (GstVideoEncoder *encoder, + GstVideoCodecFrame *frame); + + gboolean (*reset) (GstVideoEncoder *encoder, + gboolean hard); + + GstFlowReturn (*finish) (GstVideoEncoder *encoder); + + GstFlowReturn (*pre_push) (GstVideoEncoder *encoder, + GstVideoCodecFrame *frame); + + GstCaps * (*getcaps) (GstVideoEncoder *enc); + + gboolean (*sink_event) (GstVideoEncoder *encoder, + GstEvent *event); + + gboolean (*src_event) (GstVideoEncoder *encoder, + GstEvent *event); + + /*< private >*/ + /* FIXME before moving to base */ + gpointer _gst_reserved[GST_PADDING_LARGE]; +}; + +GType gst_video_encoder_get_type (void); + +GstVideoCodecState* gst_video_encoder_get_output_state (GstVideoEncoder *encoder); + +GstVideoCodecState* gst_video_encoder_set_output_state (GstVideoEncoder * encoder, + GstCaps * caps, + GstVideoCodecState * reference); + +GstVideoCodecFrame* gst_video_encoder_get_frame (GstVideoEncoder *encoder, + int frame_number); +GstVideoCodecFrame* gst_video_encoder_get_oldest_frame (GstVideoEncoder *encoder); + +GstFlowReturn gst_video_encoder_finish_frame (GstVideoEncoder *encoder, + GstVideoCodecFrame *frame); + +GstCaps * gst_video_encoder_proxy_getcaps (GstVideoEncoder * enc, + GstCaps * caps); +void gst_video_encoder_set_discont (GstVideoEncoder *encoder); +gboolean gst_video_encoder_get_discont (GstVideoEncoder *encoder); + +void gst_video_encoder_set_latency (GstVideoEncoder *encoder, + GstClockTime min_latency, + GstClockTime max_latency); +void gst_video_encoder_get_latency (GstVideoEncoder *encoder, + GstClockTime *min_latency, + GstClockTime *max_latency); + +void gst_video_encoder_set_headers (GstVideoEncoder *encoder, + GList *headers); +G_END_DECLS + +#endif + diff --git a/gst-libs/gst/video/gstvideoutils.c b/gst-libs/gst/video/gstvideoutils.c new file mode 100644 index 0000000000..acd15dd51a --- /dev/null +++ b/gst-libs/gst/video/gstvideoutils.c @@ -0,0 +1,189 @@ +/* GStreamer + * Copyright (C) 2008 David Schleef + * Copyright (C) 2012 Collabora Ltd. + * Author : Edward Hervey + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstvideoutils.h" + +#include + +GType +gst_video_codec_frame_get_type (void) +{ + static volatile gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + + _type = g_boxed_type_register_static ("GstVideoCodecFrame", + (GBoxedCopyFunc) gst_video_codec_frame_ref, + (GBoxedFreeFunc) gst_video_codec_frame_unref); + g_once_init_leave (&type, _type); + } + return (GType) type; +} + + + +static void +_gst_video_codec_frame_free (GstVideoCodecFrame * frame) +{ + g_return_if_fail (frame != NULL); + + if (frame->input_buffer) { + gst_buffer_unref (frame->input_buffer); + } + + if (frame->output_buffer) { + gst_buffer_unref (frame->output_buffer); + } + + g_list_foreach (frame->events, (GFunc) gst_event_unref, NULL); + g_list_free (frame->events); + + if (frame->coder_hook_destroy_notify && frame->coder_hook) + frame->coder_hook_destroy_notify (frame->coder_hook); + + g_slice_free (GstVideoCodecFrame, frame); +} + +/** + * gst_video_codec_frame_set_hook: + * @frame: a #GstVideoCodecFrame + * @hook: private data + * @notify: (closure hook): a #GDestroyNotify + * + * Sets the #GDestroyNotify that will be called (along with the @hook) when + * the frame is freed. + * + * If a @hook was previously set, then the previous set @notify will be called + * before the @hook is replaced. + */ +void +gst_video_codec_frame_set_hook (GstVideoCodecFrame * frame, void *hook, + GDestroyNotify notify) +{ + if (frame->coder_hook_destroy_notify && frame->coder_hook) + frame->coder_hook_destroy_notify (frame->coder_hook); + + frame->coder_hook = hook; + frame->coder_hook_destroy_notify = notify; +} + +/** + * gst_video_codec_frame_ref: + * @frame: a #GstVideoCodecFrame + * + * Increases the refcount of the given frame by one. + * + * Returns: @buf + */ +GstVideoCodecFrame * +gst_video_codec_frame_ref (GstVideoCodecFrame * frame) +{ + g_return_val_if_fail (frame != NULL, NULL); + + g_atomic_int_inc (&frame->ref_count); + + return frame; +} + +/** + * gst_video_codec_frame_unref: + * @frame: a #GstVideoCodecFrame + * + * Decreases the refcount of the frame. If the refcount reaches 0, the frame + * will be freed. + */ +void +gst_video_codec_frame_unref (GstVideoCodecFrame * frame) +{ + g_return_if_fail (frame != NULL); + g_return_if_fail (frame->ref_count > 0); + + if (g_atomic_int_dec_and_test (&frame->ref_count)) { + _gst_video_codec_frame_free (frame); + } +} + + +/** + * gst_video_codec_state_ref: + * @state: a #GstVideoCodecState + * + * Increases the refcount of the given state by one. + * + * Returns: @buf + */ +GstVideoCodecState * +gst_video_codec_state_ref (GstVideoCodecState * state) +{ + g_return_val_if_fail (state != NULL, NULL); + + g_atomic_int_inc (&state->ref_count); + + return state; +} + +static void +_gst_video_codec_state_free (GstVideoCodecState * state) +{ + if (state->caps) + gst_caps_unref (state->caps); + if (state->codec_data) + gst_buffer_unref (state->codec_data); +} + +/** + * gst_video_codec_state_unref: + * @state: a #GstVideoCodecState + * + * Decreases the refcount of the state. If the refcount reaches 0, the state + * will be freed. + */ +void +gst_video_codec_state_unref (GstVideoCodecState * state) +{ + g_return_if_fail (state != NULL); + g_return_if_fail (state->ref_count > 0); + + if (g_atomic_int_dec_and_test (&state->ref_count)) { + _gst_video_codec_state_free (state); + } +} + +GType +gst_video_codec_state_get_type (void) +{ + static volatile gsize type = 0; + + if (g_once_init_enter (&type)) { + GType _type; + + _type = g_boxed_type_register_static ("GstVideoCodecState", + (GBoxedCopyFunc) gst_video_codec_state_ref, + (GBoxedFreeFunc) gst_video_codec_state_unref); + g_once_init_leave (&type, _type); + } + return (GType) type; +} diff --git a/gst-libs/gst/video/gstvideoutils.h b/gst-libs/gst/video/gstvideoutils.h new file mode 100644 index 0000000000..d68fa1090d --- /dev/null +++ b/gst-libs/gst/video/gstvideoutils.h @@ -0,0 +1,277 @@ +/* GStreamer + * Copyright (C) 2008 David Schleef + * Copyright (C) 2012 Collabora Ltd. + * Author : Edward Hervey + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _GST_VIDEO_UTILS_H_ +#define _GST_VIDEO_UTILS_H_ + +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_VIDEO_CODEC_STATE \ + (gst_video_codec_state_get_type()) + +#define GST_TYPE_VIDEO_CODEC_FRAME \ + (gst_video_codec_frame_get_type()) + +typedef struct _GstVideoCodecState GstVideoCodecState; +typedef struct _GstVideoCodecFrame GstVideoCodecFrame; + +/** + * GstVideoCodecState: + * @info: The #GstVideoInfo describing the stream + * @caps: The #GstCaps + * @codec_data: (optional) a #GstBuffer corresponding to the + * 'codec_data' field of a stream. + * + * Structure representing the state of an incoming or outgoing video + * stream for encoders and decoders. + * + * Decoders and encoders will receive such a state through their + * respective @set_format vmethods. + * + * Decoders and encoders can set the downstream state, by using the + * @gst_video_decoder_set_output_state() or + * @gst_video_encoder_set_output_state() methods. + */ +struct _GstVideoCodecState +{ + /*< private >*/ + gint ref_count; + + /*< public >*/ + GstVideoInfo info; + + GstCaps *caps; + + GstBuffer *codec_data; + + /*< private >*/ + void *padding[GST_PADDING_LARGE]; +}; + +/** + * GstVideoCodecFrameFlags: + * @GST_VIDEO_CODEC_FRAME_FLAG_DECODE_ONLY: is the frame only meant to be decoded + * @GST_VIDEO_CODEC_FRAME_FLAG_SYNC_POINT: is the frame a synchronization point (keyframe) + * @GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME: should the output frame be made a keyframe + * @GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME_HEADERS: should the encoder output stream headers + * @GST_VIDEO_CODEC_FRAME_FLAG_TFF: top-field first + * @GST_VIDEO_CODEC_FRAME_FLAG_RFF: the field is repeated + * @GST_VIDEO_CODEC_FRAME_FLAG_ONEFIELD: only one field is present + * + * Flags for #GstVideoCodecFrame + */ +typedef enum +{ + GST_VIDEO_CODEC_FRAME_FLAG_DECODE_ONLY = (1<<0), + GST_VIDEO_CODEC_FRAME_FLAG_SYNC_POINT = (1<<1), + GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME = (1<<2), + GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME_HEADERS = (1<<3), + GST_VIDEO_CODEC_FRAME_FLAG_TFF = (1<<4), + GST_VIDEO_CODEC_FRAME_FLAG_RFF = (1<<5), + GST_VIDEO_CODEC_FRAME_FLAG_ONEFIELD = (1<<6) +} GstVideoCodecFrameFlags; + +/** + * GST_VIDEO_CODEC_FRAME_FLAGS: + * @frame: a #GstVideoCodecFrame + * + * The entire set of flags for the @frame + */ +#define GST_VIDEO_CODEC_FRAME_FLAGS(frame) ((frame)->flags) + +/** + * GST_VIDEO_CODEC_FRAME_FLAG_IS_SET: + * @frame: a #GstVideoCodecFrame + * @flag: a flag to check for + * + * Checks whether the given @flag is set + */ +#define GST_VIDEO_CODEC_FRAME_FLAG_IS_SET(frame,flag) !!(GST_VIDEO_CODEC_FRAME_FLAGS(frame) & (flag)) + +/** + * GST_VIDEO_CODEC_FRAME_FLAG_SET: + * @frame: a #GstVideoCodecFrame + * @flag: Flag to set, can be any number of bits in guint32. + * + * This macro sets the given bits + */ +#define GST_VIDEO_CODEC_FRAME_FLAG_SET(frame,flag) (GST_VIDEO_CODEC_FRAME_FLAGS(frame) |= (flag)) + +/** + * GST_VIDEO_CODEC_FRAME_FLAG_UNSET: + * @frame: a #GstVideoCodecFrame + * @flag: Flag to unset + * + * This macro usets the given bits. + */ +#define GST_VIDEO_CODEC_FRAME_FLAG_UNSET(frame,flag) (GST_VIDEO_CODEC_FRAME_FLAGS(frame) &= ~(flag)) + +/** + * GST_VIDEO_CODEC_FRAME_IS_DECODE_ONLY: + * @frame: a #GstVideoCodecFrame + * + * Tests if the buffer should only be decoded but not sent downstream. + */ +#define GST_VIDEO_CODEC_FRAME_IS_DECODE_ONLY(frame) (GST_VIDEO_CODEC_FRAME_FLAG_IS_SET(frame, GST_VIDEO_CODEC_FRAME_FLAG_DECODE_ONLY)) + +/** + * GST_VIDEO_CODEC_FRAME_SET_DECODE_ONLY: + * @frame: a #GstVideoCodecFrame + * + * Sets the buffer to not be sent downstream. + * + * Decoder implementation can use this if they have frames that + * are not meant to be displayed. + * + * Encoder implementation can safely ignore this field. + */ +#define GST_VIDEO_CODEC_FRAME_SET_DECODE_ONLY(frame) (GST_VIDEO_CODEC_FRAME_FLAG_SET(frame, GST_VIDEO_CODEC_FRAME_FLAG_DECODE_ONLY)) + +/** + * GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT: + * @frame: a #GstVideoCodecFrame + * + * Tests if the frame is a synchronization point (like a keyframe). + * + * Decoder implementations can use this to detect keyframes. + */ +#define GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT(frame) (GST_VIDEO_CODEC_FRAME_FLAG_IS_SET(frame, GST_VIDEO_CODEC_FRAME_FLAG_SYNC_POINT)) + +/** + * GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT: + * @frame: a #GstVideoCodecFrame + * + * Sets the frame to be a synchronization point (like a keyframe). + * + * Encoder implementations should set this accordingly. + * + * Decoder implementing parsing features should set this when they + * detect such a synchronization point. + */ +#define GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT(frame) (GST_VIDEO_CODEC_FRAME_FLAG_SET(frame, GST_VIDEO_CODEC_FRAME_FLAG_SYNC_POINT)) +#define GST_VIDEO_CODEC_FRAME_UNSET_SYNC_POINT(frame) (GST_VIDEO_CODEC_FRAME_FLAG_UNSET(frame, GST_VIDEO_CODEC_FRAME_FLAG_SYNC_POINT)) + + +/** + * GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME: + * @frame: a #GstVideoCodecFrame + * + * Tests if the frame must be encoded as a keyframe. Applies only to + * frames provided to encoders. Decoders can safely ignore this field. + */ +#define GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME(frame) (GST_VIDEO_CODEC_FRAME_FLAG_IS_SET(frame, GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME)) +#define GST_VIDEO_CODEC_FRAME_SET_FORCE_KEYFRAME(frame) (GST_VIDEO_CODEC_FRAME_FLAG_SET(frame, GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME)) +#define GST_VIDEO_CODEC_FRAME_UNSET_FORCE_KEYFRAME(frame) (GST_VIDEO_CODEC_FRAME_FLAG_UNSET(frame, GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME)) + +/** + * GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME_HEADERS: + * @frame: a #GstVideoCodecFrame + * + * Tests if encoder should output stream headers before outputting the + * resulting encoded buffer for the given frame. + * + * Applies only to frames provided to encoders. Decoders can safely + * ignore this field. + */ +#define GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME_HEADERS(frame) (GST_VIDEO_CODEC_FRAME_FLAG_IS_SET(frame, GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME_HEADERS)) +#define GST_VIDEO_CODEC_FRAME_SET_FORCE_KEYFRAME_HEADERS(frame) (GST_VIDEO_CODEC_FRAME_FLAG_SET(frame, GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME_HEADERS)) +#define GST_VIDEO_CODEC_FRAME_UNSET_FORCE_KEYFRAME_HEADERS(frame) (GST_VIDEO_CODEC_FRAME_FLAG_UNSET(frame, GST_VIDEO_CODEC_FRAME_FLAG_FORCE_KEYFRAME_HEADERS)) + +/** + * GstVideoCodecFrame: + * @pts: Presentation timestamp + * @dts: Decoding timestamp + * @duration: Duration of the frame + * @system_frame_number: Unique identifier for the frame. Use this if you need + * to get hold of the frame later (like when data is being decoded). + * Typical usage in decoders is to set this on the opaque value provided + * to the library and get back the frame using gst_video_decoder_get_frame() + * @distance_from_sync: Distance in frames from the last synchronization point. + * @input_buffer: the input #GstBuffer that created this frame. + * @output_buffer: the output #GstBuffer. Implementations should set this either + * directly, or by using the @gst_video_decoder_alloc_output_frame() or + * @gst_video_decoder_alloc_output_buffer() methods. + * @field_index: + * @n_fields: number of fields in the frame (default 2). Decoders can change + * this if the frame contains a different number of fields. + * @deadline: Running time when the frame will be used. + * @events: Events that will be pushed downstream before this frame is pushed. + * + * A #GstVideoCodecFrame represents a video frame both in raw and + * encoded form. + */ +struct _GstVideoCodecFrame +{ + /*< private >*/ + gint ref_count; + + guint32 flags; + + /*< public >*/ + gint system_frame_number; /* ED */ + gint decode_frame_number; /* ED */ + gint presentation_frame_number; /* ED */ + + GstClockTime dts; /* ED */ + GstClockTime pts; /* ED */ + GstClockTime duration; /* ED */ + + int distance_from_sync; /* ED */ + + GstBuffer *input_buffer; /* ED */ + GstBuffer *output_buffer; /* ED */ + + GstClockTime deadline; /* D */ + + /* Events that should be pushed downstream *before* + * the next output_buffer */ + GList *events; /* ED */ + + /*< private >*/ + + void *coder_hook; + GDestroyNotify coder_hook_destroy_notify; + + void *padding[GST_PADDING_LARGE]; +}; + +/* GstVideoCodecState */ +GType gst_video_codec_state_get_type (void); + +GstVideoCodecState *gst_video_codec_state_ref (GstVideoCodecState * state); + +void gst_video_codec_state_unref (GstVideoCodecState * state); + + +/* GstVideoCodecFrame */ +GType gst_video_codec_frame_get_type (void); + +GstVideoCodecFrame *gst_video_codec_frame_ref (GstVideoCodecFrame * frame); +void gst_video_codec_frame_unref (GstVideoCodecFrame * frame); +void gst_video_codec_frame_set_hook (GstVideoCodecFrame *frame, + void *hook, + GDestroyNotify notify); + +G_END_DECLS + +#endif diff --git a/win32/common/libgstvideo.def b/win32/common/libgstvideo.def index 879b942cef..213118daf0 100644 --- a/win32/common/libgstvideo.def +++ b/win32/common/libgstvideo.def @@ -1,9 +1,48 @@ EXPORTS + _gst_video_decoder_error gst_video_buffer_get_overlay_composition gst_video_buffer_set_overlay_composition gst_video_calculate_display_ratio + gst_video_codec_frame_get_type + gst_video_codec_frame_ref + gst_video_codec_frame_set_hook + gst_video_codec_frame_unref + gst_video_codec_state_get_type + gst_video_codec_state_ref + gst_video_codec_state_unref gst_video_convert_frame gst_video_convert_frame_async + gst_video_decoder_add_to_frame + gst_video_decoder_alloc_output_buffer + gst_video_decoder_alloc_output_frame + gst_video_decoder_drop_frame + gst_video_decoder_finish_frame + gst_video_decoder_get_estimate_rate + gst_video_decoder_get_frame + gst_video_decoder_get_latency + gst_video_decoder_get_max_decode_time + gst_video_decoder_get_max_errors + gst_video_decoder_get_oldest_frame + gst_video_decoder_get_output_state + gst_video_decoder_get_packetized + gst_video_decoder_get_type + gst_video_decoder_have_frame + gst_video_decoder_set_estimate_rate + gst_video_decoder_set_latency + gst_video_decoder_set_max_errors + gst_video_decoder_set_output_state + gst_video_decoder_set_packetized + gst_video_decoder_set_src_caps + gst_video_encoder_finish_frame + gst_video_encoder_get_frame + gst_video_encoder_get_latency + gst_video_encoder_get_oldest_frame + gst_video_encoder_get_output_state + gst_video_encoder_get_type + gst_video_encoder_proxy_getcaps + gst_video_encoder_set_headers + gst_video_encoder_set_latency + gst_video_encoder_set_output_state gst_video_event_is_force_key_unit gst_video_event_new_downstream_force_key_unit gst_video_event_new_still_frame From 7bfd1bc3d7d0507e707af7a37762ee59954cde72 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Wed, 7 Mar 2012 12:22:14 +0100 Subject: [PATCH 10/14] theora: Port to base video classes FIXME : Don't forget to backport changes that happened to theoraenc since April 2011 theoraenc: Don't create keyframe on time gap There is no rational to do so, and also gst_video_encoder_set_discont() is gone from base class. --- ext/theora/gsttheoradec.c | 1229 +++++++------------------------------ ext/theora/gsttheoradec.h | 44 +- ext/theora/gsttheoraenc.c | 1184 +++++++++-------------------------- ext/theora/gsttheoraenc.h | 32 +- 4 files changed, 525 insertions(+), 1964 deletions(-) diff --git a/ext/theora/gsttheoradec.c b/ext/theora/gsttheoradec.c index 5271109886..5d398b415c 100644 --- a/ext/theora/gsttheoradec.c +++ b/ext/theora/gsttheoradec.c @@ -1,5 +1,8 @@ /* GStreamer * Copyright (C) 2004 Benjamin Otte + * Copyright (c) 2012 Collabora Ltd. + * Author : Edward Hervey + * Author : Mark Nauwelaerts * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -81,31 +84,26 @@ GST_STATIC_PAD_TEMPLATE ("sink", GST_STATIC_CAPS ("video/x-theora") ); -GST_BOILERPLATE (GstTheoraDec, gst_theora_dec, GstElement, GST_TYPE_ELEMENT); +GST_BOILERPLATE (GstTheoraDec, gst_theora_dec, GstVideoDecoder, + GST_TYPE_VIDEO_DECODER); static void theora_dec_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void theora_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); -static gboolean theora_dec_sink_event (GstPad * pad, GstEvent * event); -static gboolean theora_dec_setcaps (GstPad * pad, GstCaps * caps); -static GstFlowReturn theora_dec_chain (GstPad * pad, GstBuffer * buffer); -static GstStateChangeReturn theora_dec_change_state (GstElement * element, - GstStateChange transition); -static gboolean theora_dec_src_event (GstPad * pad, GstEvent * event); -static gboolean theora_dec_src_query (GstPad * pad, GstQuery * query); -static gboolean theora_dec_src_convert (GstPad * pad, - GstFormat src_format, gint64 src_value, - GstFormat * dest_format, gint64 * dest_value); +static gboolean theora_dec_start (GstVideoDecoder * decoder); +static gboolean theora_dec_stop (GstVideoDecoder * decoder); +static gboolean theora_dec_set_format (GstVideoDecoder * decoder, + GstVideoCodecState * state); +static gboolean theora_dec_reset (GstVideoDecoder * decoder, gboolean hard); +static GstFlowReturn theora_dec_parse (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame, GstAdapter * adapter, gboolean at_eos); +static GstFlowReturn theora_dec_handle_frame (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame); -#if 0 -static const GstFormat *theora_get_formats (GstPad * pad); -#endif -#if 0 -static const GstEventMask *theora_get_event_masks (GstPad * pad); -#endif -static const GstQueryType *theora_get_query_types (GstPad * pad); +static GstFlowReturn theora_dec_decode_buffer (GstTheoraDec * dec, + GstBuffer * buf, GstVideoCodecFrame * frame); static void @@ -113,10 +111,10 @@ gst_theora_dec_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); - gst_element_class_add_static_pad_template (element_class, - &theora_dec_src_factory); - gst_element_class_add_static_pad_template (element_class, - &theora_dec_sink_factory); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&theora_dec_src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&theora_dec_sink_factory)); gst_element_class_set_details_simple (element_class, "Theora video decoder", "Codec/Decoder/Video", "decode raw theora streams to raw YUV video", @@ -134,7 +132,7 @@ static void gst_theora_dec_class_init (GstTheoraDecClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GstVideoDecoderClass *video_decoder_class = GST_VIDEO_DECODER_CLASS (klass); gobject_class->set_property = theora_dec_set_property; gobject_class->get_property = theora_dec_get_property; @@ -188,7 +186,13 @@ gst_theora_dec_class_init (GstTheoraDecClass * klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } - gstelement_class->change_state = theora_dec_change_state; + video_decoder_class->start = GST_DEBUG_FUNCPTR (theora_dec_start); + video_decoder_class->stop = GST_DEBUG_FUNCPTR (theora_dec_stop); + video_decoder_class->reset = GST_DEBUG_FUNCPTR (theora_dec_reset); + video_decoder_class->set_format = GST_DEBUG_FUNCPTR (theora_dec_set_format); + video_decoder_class->parse = GST_DEBUG_FUNCPTR (theora_dec_parse); + video_decoder_class->handle_frame = + GST_DEBUG_FUNCPTR (theora_dec_handle_frame); GST_DEBUG_CATEGORY_INIT (theoradec_debug, "theoradec", 0, "Theora decoder"); } @@ -196,567 +200,142 @@ gst_theora_dec_class_init (GstTheoraDecClass * klass) static void gst_theora_dec_init (GstTheoraDec * dec, GstTheoraDecClass * g_class) { - dec->sinkpad = - gst_pad_new_from_static_template (&theora_dec_sink_factory, "sink"); - gst_pad_set_event_function (dec->sinkpad, theora_dec_sink_event); - gst_pad_set_setcaps_function (dec->sinkpad, theora_dec_setcaps); - gst_pad_set_chain_function (dec->sinkpad, theora_dec_chain); - gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad); - - dec->srcpad = - gst_pad_new_from_static_template (&theora_dec_src_factory, "src"); - gst_pad_set_event_function (dec->srcpad, theora_dec_src_event); - gst_pad_set_query_type_function (dec->srcpad, theora_get_query_types); - gst_pad_set_query_function (dec->srcpad, theora_dec_src_query); - gst_pad_use_fixed_caps (dec->srcpad); - - gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad); - dec->crop = THEORA_DEF_CROP; dec->telemetry_mv = THEORA_DEF_TELEMETRY_MV; dec->telemetry_mbmode = THEORA_DEF_TELEMETRY_MBMODE; dec->telemetry_qi = THEORA_DEF_TELEMETRY_QI; dec->telemetry_bits = THEORA_DEF_TELEMETRY_BITS; - dec->gather = NULL; - dec->decode = NULL; - dec->queued = NULL; - dec->pendingevents = NULL; + + /* input is packetized, + * but is not marked that way so data gets parsed and keyframes marked */ } static void gst_theora_dec_reset (GstTheoraDec * dec) { dec->need_keyframe = TRUE; - dec->last_timestamp = -1; - dec->discont = TRUE; - dec->frame_nr = -1; - dec->seqnum = gst_util_seqnum_next (); - dec->dropped = 0; - dec->processed = 0; - gst_segment_init (&dec->segment, GST_FORMAT_TIME); +} - GST_OBJECT_LOCK (dec); - dec->proportion = 1.0; - dec->earliest_time = -1; - GST_OBJECT_UNLOCK (dec); +static gboolean +theora_dec_start (GstVideoDecoder * decoder) +{ + GstTheoraDec *dec = GST_THEORA_DEC (decoder); - g_list_foreach (dec->queued, (GFunc) gst_mini_object_unref, NULL); - g_list_free (dec->queued); - dec->queued = NULL; - g_list_foreach (dec->gather, (GFunc) gst_mini_object_unref, NULL); - g_list_free (dec->gather); - dec->gather = NULL; - g_list_foreach (dec->decode, (GFunc) gst_mini_object_unref, NULL); - g_list_free (dec->decode); - dec->decode = NULL; - g_list_foreach (dec->pendingevents, (GFunc) gst_mini_object_unref, NULL); - g_list_free (dec->pendingevents); - dec->pendingevents = NULL; + GST_DEBUG_OBJECT (dec, "start"); + th_info_clear (&dec->info); + th_comment_clear (&dec->comment); + GST_DEBUG_OBJECT (dec, "Setting have_header to FALSE"); + dec->have_header = FALSE; + gst_theora_dec_reset (dec); + return TRUE; +} + +static gboolean +theora_dec_stop (GstVideoDecoder * decoder) +{ + GstTheoraDec *dec = GST_THEORA_DEC (decoder); + + GST_DEBUG_OBJECT (dec, "stop"); + th_info_clear (&dec->info); + th_comment_clear (&dec->comment); + th_setup_free (dec->setup); + dec->setup = NULL; + th_decode_free (dec->decoder); + dec->decoder = NULL; + gst_theora_dec_reset (dec); if (dec->tags) { gst_tag_list_free (dec->tags); dec->tags = NULL; } + + return TRUE; } -#if 0 -static const GstFormat * -theora_get_formats (GstPad * pad) +/* FIXME : Do we want to handle hard resets differently ? */ +static gboolean +theora_dec_reset (GstVideoDecoder * bdec, gboolean hard) { - static GstFormat src_formats[] = { - GST_FORMAT_DEFAULT, /* frames in this case */ - GST_FORMAT_TIME, - GST_FORMAT_BYTES, - 0 - }; - static GstFormat sink_formats[] = { - GST_FORMAT_DEFAULT, - GST_FORMAT_TIME, - 0 - }; - - return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats); + gst_theora_dec_reset (GST_THEORA_DEC (bdec)); + return TRUE; } -#endif -#if 0 -static const GstEventMask * -theora_get_event_masks (GstPad * pad) +static GstFlowReturn +theora_dec_parse (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame, GstAdapter * adapter, gboolean at_eos) { - static const GstEventMask theora_src_event_masks[] = { - {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH}, - {0,} - }; + gint av; + const guint8 *data; - return theora_src_event_masks; -} -#endif + av = gst_adapter_available (adapter); -static const GstQueryType * -theora_get_query_types (GstPad * pad) -{ - static const GstQueryType theora_src_query_types[] = { - GST_QUERY_POSITION, - GST_QUERY_DURATION, - GST_QUERY_CONVERT, - 0 - }; + data = gst_adapter_peek (adapter, 1); + /* check for keyframe; must not be header packet */ + if (!(data[0] & 0x80) && (data[0] & 0x40) == 0) + GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame); - return theora_src_query_types; + /* and pass along all */ + gst_video_decoder_add_to_frame (decoder, av); + return gst_video_decoder_have_frame (decoder); } static gboolean -theora_dec_src_convert (GstPad * pad, - GstFormat src_format, gint64 src_value, - GstFormat * dest_format, gint64 * dest_value) -{ - gboolean res = TRUE; - GstTheoraDec *dec; - guint64 scale = 1; - - if (src_format == *dest_format) { - *dest_value = src_value; - return TRUE; - } - - dec = GST_THEORA_DEC (gst_pad_get_parent (pad)); - - /* we need the info part before we can done something */ - if (!dec->have_header) - goto no_header; - - switch (src_format) { - case GST_FORMAT_BYTES: - switch (*dest_format) { - case GST_FORMAT_DEFAULT: - *dest_value = gst_util_uint64_scale_int (src_value, 8, - dec->info.pic_height * dec->info.pic_width * dec->output_bpp); - break; - case GST_FORMAT_TIME: - /* seems like a rather silly conversion, implement me if you like */ - default: - res = FALSE; - } - break; - case GST_FORMAT_TIME: - switch (*dest_format) { - case GST_FORMAT_BYTES: - scale = - dec->output_bpp * (dec->info.pic_width * dec->info.pic_height) / - 8; - case GST_FORMAT_DEFAULT: - *dest_value = scale * gst_util_uint64_scale (src_value, - dec->info.fps_numerator, dec->info.fps_denominator * GST_SECOND); - break; - default: - res = FALSE; - } - break; - case GST_FORMAT_DEFAULT: - switch (*dest_format) { - case GST_FORMAT_TIME: - *dest_value = gst_util_uint64_scale (src_value, - GST_SECOND * dec->info.fps_denominator, dec->info.fps_numerator); - break; - case GST_FORMAT_BYTES: - *dest_value = gst_util_uint64_scale_int (src_value, - dec->output_bpp * dec->info.pic_width * dec->info.pic_height, 8); - break; - default: - res = FALSE; - } - break; - default: - res = FALSE; - } -done: - gst_object_unref (dec); - return res; - - /* ERRORS */ -no_header: - { - GST_DEBUG_OBJECT (dec, "no header yet, cannot convert"); - res = FALSE; - goto done; - } -} - -#if 0 -static gboolean -theora_dec_sink_convert (GstPad * pad, - GstFormat src_format, gint64 src_value, - GstFormat * dest_format, gint64 * dest_value) -{ - gboolean res = TRUE; - GstTheoraDec *dec; - - if (src_format == *dest_format) { - *dest_value = src_value; - return TRUE; - } - - dec = GST_THEORA_DEC (gst_pad_get_parent (pad)); - - /* we need the info part before we can done something */ - if (!dec->have_header) - goto no_header; - - switch (src_format) { - case GST_FORMAT_DEFAULT: - switch (*dest_format) { - case GST_FORMAT_TIME: - *dest_value = _theora_granule_start_time (dec, src_value); - break; - default: - res = FALSE; - } - break; - case GST_FORMAT_TIME: - switch (*dest_format) { - case GST_FORMAT_DEFAULT: - { - guint rest; - - /* framecount */ - *dest_value = gst_util_uint64_scale (src_value, - dec->info.fps_numerator, GST_SECOND * dec->info.fps_denominator); - - /* funny way of calculating granulepos in theora */ - rest = *dest_value / dec->info.keyframe_granule_shift; - *dest_value -= rest; - *dest_value <<= dec->granule_shift; - *dest_value += rest; - break; - } - default: - res = FALSE; - break; - } - break; - default: - res = FALSE; - } -done: - gst_object_unref (dec); - return res; - - /* ERRORS */ -no_header: - { - GST_DEBUG_OBJECT (dec, "no header yet, cannot convert"); - res = FALSE; - goto done; - } -} -#endif - -static gboolean -theora_dec_src_query (GstPad * pad, GstQuery * query) +theora_dec_set_format (GstVideoDecoder * bdec, GstVideoCodecState * state) { GstTheoraDec *dec; - gboolean res = FALSE; + dec = GST_THEORA_DEC (bdec); - dec = GST_THEORA_DEC (gst_pad_get_parent (pad)); + /* Keep a copy of the input state */ + if (dec->input_state) + gst_video_codec_state_unref (dec->input_state); + dec->input_state = gst_video_codec_state_ref (state); - switch (GST_QUERY_TYPE (query)) { - case GST_QUERY_POSITION: - { - gint64 value; - GstFormat format; - gint64 time; + /* FIXME : Interesting, we always accept any kind of caps ? */ + if (state->codec_data) { + GstBuffer *buffer; + guint8 *data; + guint size; + guint offset; - /* parse format */ - gst_query_parse_position (query, &format, NULL); + buffer = state->codec_data; - time = dec->last_timestamp; - time = gst_segment_to_stream_time (&dec->segment, GST_FORMAT_TIME, time); + offset = 0; + size = GST_BUFFER_SIZE (buffer); + data = GST_BUFFER_DATA (buffer); - GST_LOG_OBJECT (dec, - "query %p: our time: %" GST_TIME_FORMAT, query, GST_TIME_ARGS (time)); + while (size > 2) { + guint psize; + GstBuffer *buf; - if (!(res = - theora_dec_src_convert (pad, GST_FORMAT_TIME, time, &format, - &value))) - goto error; + psize = (data[0] << 8) | data[1]; + /* skip header */ + data += 2; + size -= 2; + offset += 2; - gst_query_set_position (query, format, value); + /* make sure we don't read too much */ + psize = MIN (psize, size); - GST_LOG_OBJECT (dec, - "query %p: we return %" G_GINT64_FORMAT " (format %u)", query, value, - format); - break; - } - case GST_QUERY_DURATION: - { - /* forward to peer for total */ - res = gst_pad_peer_query (dec->sinkpad, query); - if (!res) - goto error; + buf = gst_buffer_create_sub (buffer, offset, psize); - break; - } - case GST_QUERY_CONVERT: - { - GstFormat src_fmt, dest_fmt; - gint64 src_val, dest_val; + /* first buffer is a discont buffer */ + if (offset == 2) + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); - gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); - if (!(res = - theora_dec_src_convert (pad, src_fmt, src_val, &dest_fmt, - &dest_val))) - goto error; + /* now feed it to the decoder we can ignore the error */ + theora_dec_decode_buffer (dec, buf, NULL); + gst_buffer_unref (buf); - gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); - break; - } - default: - res = gst_pad_query_default (pad, query); - break; - } -done: - gst_object_unref (dec); - - return res; - - /* ERRORS */ -error: - { - GST_DEBUG_OBJECT (dec, "query failed"); - goto done; - } -} - -static gboolean -theora_dec_src_event (GstPad * pad, GstEvent * event) -{ - gboolean res = TRUE; - GstTheoraDec *dec; - - dec = GST_THEORA_DEC (gst_pad_get_parent (pad)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_SEEK: - { - GstFormat format, tformat; - gdouble rate; - GstEvent *real_seek; - GstSeekFlags flags; - GstSeekType cur_type, stop_type; - gint64 cur, stop; - gint64 tcur, tstop; - guint32 seqnum; - - gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, - &stop_type, &stop); - seqnum = gst_event_get_seqnum (event); - gst_event_unref (event); - - /* we have to ask our peer to seek to time here as we know - * nothing about how to generate a granulepos from the src - * formats or anything. - * - * First bring the requested format to time - */ - tformat = GST_FORMAT_TIME; - if (!(res = theora_dec_src_convert (pad, format, cur, &tformat, &tcur))) - goto convert_error; - if (!(res = theora_dec_src_convert (pad, format, stop, &tformat, &tstop))) - goto convert_error; - - /* then seek with time on the peer */ - real_seek = gst_event_new_seek (rate, GST_FORMAT_TIME, - flags, cur_type, tcur, stop_type, tstop); - gst_event_set_seqnum (real_seek, seqnum); - - res = gst_pad_push_event (dec->sinkpad, real_seek); - break; - } - case GST_EVENT_QOS: - { - gdouble proportion; - GstClockTimeDiff diff; - GstClockTime timestamp; - - gst_event_parse_qos (event, &proportion, &diff, ×tamp); - - /* we cannot randomly skip frame decoding since we don't have - * B frames. we can however use the timestamp and diff to not - * push late frames. This would at least save us the time to - * crop/memcpy the data. */ - GST_OBJECT_LOCK (dec); - dec->proportion = proportion; - dec->earliest_time = timestamp + diff; - GST_OBJECT_UNLOCK (dec); - - GST_DEBUG_OBJECT (dec, "got QoS %" GST_TIME_FORMAT ", %" G_GINT64_FORMAT, - GST_TIME_ARGS (timestamp), diff); - - res = gst_pad_push_event (dec->sinkpad, event); - break; - } - default: - res = gst_pad_push_event (dec->sinkpad, event); - break; - } -done: - gst_object_unref (dec); - - return res; - - /* ERRORS */ -convert_error: - { - GST_DEBUG_OBJECT (dec, "could not convert format"); - goto done; - } -} - -static gboolean -theora_dec_sink_event (GstPad * pad, GstEvent * event) -{ - gboolean ret = FALSE; - GstTheoraDec *dec; - - dec = GST_THEORA_DEC (gst_pad_get_parent (pad)); - - GST_LOG_OBJECT (dec, "handling event"); - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_FLUSH_START: - ret = gst_pad_push_event (dec->srcpad, event); - break; - case GST_EVENT_FLUSH_STOP: - gst_theora_dec_reset (dec); - ret = gst_pad_push_event (dec->srcpad, event); - break; - case GST_EVENT_EOS: - ret = gst_pad_push_event (dec->srcpad, event); - break; - case GST_EVENT_NEWSEGMENT: - { - gboolean update; - GstFormat format; - gdouble rate, arate; - gint64 start, stop, time; - - gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, - &start, &stop, &time); - - /* we need TIME format */ - if (format != GST_FORMAT_TIME) - goto newseg_wrong_format; - - GST_DEBUG_OBJECT (dec, - "newsegment: update %d, rate %g, arate %g, start %" GST_TIME_FORMAT - ", stop %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, - update, rate, arate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop), - GST_TIME_ARGS (time)); - - /* now configure the values */ - gst_segment_set_newsegment_full (&dec->segment, update, - rate, arate, format, start, stop, time); - dec->seqnum = gst_event_get_seqnum (event); - - /* We don't forward this unless/until the decoder is initialised */ - if (dec->have_header) { - ret = gst_pad_push_event (dec->srcpad, event); - } else { - dec->pendingevents = g_list_append (dec->pendingevents, event); - ret = TRUE; - } - break; - } - case GST_EVENT_TAG: - { - if (dec->have_header) - /* and forward */ - ret = gst_pad_push_event (dec->srcpad, event); - else { - /* store it to send once we're initialized */ - dec->pendingevents = g_list_append (dec->pendingevents, event); - ret = TRUE; - } - break; - } - default: - ret = gst_pad_push_event (dec->srcpad, event); - break; - } -done: - gst_object_unref (dec); - - return ret; - - /* ERRORS */ -newseg_wrong_format: - { - GST_DEBUG_OBJECT (dec, "received non TIME newsegment"); - gst_event_unref (event); - goto done; - } -} - -static gboolean -theora_dec_setcaps (GstPad * pad, GstCaps * caps) -{ - GstTheoraDec *dec; - GstStructure *s; - const GValue *codec_data; - - dec = GST_THEORA_DEC (gst_pad_get_parent (pad)); - - s = gst_caps_get_structure (caps, 0); - - /* parse the par, this overrides the encoded par */ - dec->have_par = gst_structure_get_fraction (s, "pixel-aspect-ratio", - &dec->par_num, &dec->par_den); - - if ((codec_data = gst_structure_get_value (s, "codec_data"))) { - if (G_VALUE_TYPE (codec_data) == GST_TYPE_BUFFER) { - GstBuffer *buffer; - guint8 *data; - guint size; - guint offset; - - buffer = gst_value_get_buffer (codec_data); - - offset = 0; - size = GST_BUFFER_SIZE (buffer); - data = GST_BUFFER_DATA (buffer); - - while (size > 2) { - guint psize; - GstBuffer *buf; - - psize = (data[0] << 8) | data[1]; - /* skip header */ - data += 2; - size -= 2; - offset += 2; - - /* make sure we don't read too much */ - psize = MIN (psize, size); - - buf = gst_buffer_create_sub (buffer, offset, psize); - - /* first buffer is a discont buffer */ - if (offset == 2) - GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); - - /* now feed it to the decoder we can ignore the error */ - theora_dec_chain (pad, buf); - - /* skip the data */ - size -= psize; - data += psize; - offset += psize; - } + /* skip the data */ + size -= psize; + data += psize; + offset += psize; } } - gst_object_unref (dec); + GST_DEBUG_OBJECT (dec, "Done"); return TRUE; } @@ -799,6 +378,8 @@ theora_handle_comment_packet (GstTheoraDec * dec, ogg_packet * packet) GST_TAG_NOMINAL_BITRATE, dec->info.target_bitrate, NULL); } + if (dec->tags) + gst_tag_list_free (dec->tags); dec->tags = list; return GST_FLOW_OK; @@ -807,11 +388,11 @@ theora_handle_comment_packet (GstTheoraDec * dec, ogg_packet * packet) static GstFlowReturn theora_handle_type_packet (GstTheoraDec * dec, ogg_packet * packet) { - GstCaps *caps; gint par_num, par_den; GstFlowReturn ret = GST_FLOW_OK; - GList *walk; - guint32 fourcc; + GstVideoCodecState *state; + GstVideoFormat fmt; + GstVideoInfo *info = &dec->input_state->info; GST_DEBUG_OBJECT (dec, "fps %d/%d, PAR %d/%d", dec->info.fps_numerator, dec->info.fps_denominator, @@ -821,19 +402,15 @@ theora_handle_type_packet (GstTheoraDec * dec, ogg_packet * packet) * the info.aspect_* values reflect PAR; * 0:x and x:0 are allowed and can be interpreted as 1:1. */ - if (dec->have_par) { - /* we had a par on the sink caps, override the encoded par */ - GST_DEBUG_OBJECT (dec, "overriding with input PAR"); - par_num = dec->par_num; - par_den = dec->par_den; - } else { - /* take encoded par */ + par_num = GST_VIDEO_INFO_PAR_N (info); + par_den = GST_VIDEO_INFO_PAR_D (info); + + /* If we have a default PAR, see if the decoder specified a different one */ + if (par_num == 1 && par_den == 1 && + (dec->info.aspect_numerator != 0 && dec->info.aspect_denominator != 0)) { par_num = dec->info.aspect_numerator; par_den = dec->info.aspect_denominator; } - if (par_num == 0 || par_den == 0) { - par_num = par_den = 1; - } /* theora has: * * width/height : dimension of the encoded frame @@ -846,23 +423,23 @@ theora_handle_type_packet (GstTheoraDec * dec, ogg_packet * packet) dec->info.pic_width, dec->info.pic_height, dec->info.pic_x, dec->info.pic_y); - if (dec->info.pixel_fmt == TH_PF_420) { - dec->output_bpp = 12; /* Average bits per pixel. */ - fourcc = GST_MAKE_FOURCC ('I', '4', '2', '0'); - } else if (dec->info.pixel_fmt == TH_PF_422) { - dec->output_bpp = 16; - fourcc = GST_MAKE_FOURCC ('Y', '4', '2', 'B'); - } else if (dec->info.pixel_fmt == TH_PF_444) { - dec->output_bpp = 24; - fourcc = GST_MAKE_FOURCC ('Y', '4', '4', '4'); - } else { - GST_ERROR_OBJECT (dec, "Invalid pixel format %d", dec->info.pixel_fmt); - return GST_FLOW_ERROR; + switch (dec->info.pixel_fmt) { + case TH_PF_420: + fmt = GST_VIDEO_FORMAT_I420; + break; + case TH_PF_422: + fmt = GST_VIDEO_FORMAT_Y42B; + break; + case TH_PF_444: + fmt = GST_VIDEO_FORMAT_Y444; + break; + default: + goto unsupported_format; } if (dec->crop) { - dec->width = dec->info.pic_width; - dec->height = dec->info.pic_height; + GST_VIDEO_INFO_WIDTH (info) = dec->info.pic_width; + GST_VIDEO_INFO_HEIGHT (info) = dec->info.pic_height; dec->offset_x = dec->info.pic_x; dec->offset_y = dec->info.pic_y; /* Ensure correct offsets in chroma for formats that need it @@ -870,22 +447,22 @@ theora_handle_type_packet (GstTheoraDec * dec, ogg_packet * packet) * so no need to handle them ourselves. */ if (dec->offset_x & 1 && dec->info.pixel_fmt != TH_PF_444) { dec->offset_x--; - dec->width++; + GST_VIDEO_INFO_WIDTH (info)++; } if (dec->offset_y & 1 && dec->info.pixel_fmt == TH_PF_420) { dec->offset_y--; - dec->height++; + GST_VIDEO_INFO_HEIGHT (info)++; } } else { /* no cropping, use the encoded dimensions */ - dec->width = dec->info.frame_width; - dec->height = dec->info.frame_height; + GST_VIDEO_INFO_WIDTH (info) = dec->info.frame_width; + GST_VIDEO_INFO_HEIGHT (info) = dec->info.frame_height; dec->offset_x = 0; dec->offset_y = 0; } GST_DEBUG_OBJECT (dec, "after fixup frame dimension %dx%d, offset %d:%d", - dec->width, dec->height, dec->offset_x, dec->offset_y); + info->width, info->height, dec->offset_x, dec->offset_y); /* done */ dec->decoder = th_decode_alloc (&dec->info, dec->setup); @@ -907,33 +484,39 @@ theora_handle_type_packet (GstTheoraDec * dec, ogg_packet * packet) GST_WARNING_OBJECT (dec, "Could not enable BITS mode visualisation"); } - caps = gst_caps_new_simple ("video/x-raw-yuv", - "format", GST_TYPE_FOURCC, fourcc, - "framerate", GST_TYPE_FRACTION, - dec->info.fps_numerator, dec->info.fps_denominator, - "pixel-aspect-ratio", GST_TYPE_FRACTION, par_num, par_den, - "width", G_TYPE_INT, dec->width, "height", G_TYPE_INT, dec->height, - "color-matrix", G_TYPE_STRING, "sdtv", - "chroma-site", G_TYPE_STRING, "jpeg", NULL); - gst_pad_set_caps (dec->srcpad, caps); - gst_caps_unref (caps); + /* Create the output state */ + dec->output_state = state = + gst_video_decoder_set_output_state (GST_VIDEO_DECODER (dec), fmt, + info->width, info->height, dec->input_state); + + /* FIXME : Do we still need to set fps/par now that we pass the reference input stream ? */ + state->info.fps_n = dec->info.fps_numerator; + state->info.fps_d = dec->info.fps_denominator; + state->info.par_n = par_num; + state->info.par_d = par_den; + + state->info.chroma_site = GST_VIDEO_CHROMA_SITE_JPEG; + /* FIXME : Need to specify SDTV color-matrix ... once it's handled + * with the backported GstVideoInfo */ dec->have_header = TRUE; - if (dec->pendingevents) { - for (walk = dec->pendingevents; walk; walk = g_list_next (walk)) - gst_pad_push_event (dec->srcpad, GST_EVENT_CAST (walk->data)); - g_list_free (dec->pendingevents); - dec->pendingevents = NULL; - } - + /* FIXME : Put this on the next outgoing frame */ + /* FIXME : */ if (dec->tags) { - gst_element_found_tags_for_pad (GST_ELEMENT_CAST (dec), dec->srcpad, - dec->tags); + gst_element_found_tags_for_pad (GST_ELEMENT_CAST (dec), + GST_VIDEO_DECODER_SRC_PAD (dec), dec->tags); dec->tags = NULL; } return ret; + + /* ERRORS */ +unsupported_format: + { + GST_ERROR_OBJECT (dec, "Invalid pixel format %d", dec->info.pixel_fmt); + return GST_FLOW_ERROR; + } } static GstFlowReturn @@ -974,125 +557,45 @@ header_read_error: } } -/* returns TRUE if buffer is within segment, else FALSE. - * if Buffer is on segment border, it's timestamp and duration will be clipped */ -static gboolean -clip_buffer (GstTheoraDec * dec, GstBuffer * buf) -{ - gboolean res = TRUE; - GstClockTime in_ts, in_dur, stop; - gint64 cstart, cstop; - - in_ts = GST_BUFFER_TIMESTAMP (buf); - in_dur = GST_BUFFER_DURATION (buf); - - GST_LOG_OBJECT (dec, - "timestamp:%" GST_TIME_FORMAT " , duration:%" GST_TIME_FORMAT, - GST_TIME_ARGS (in_ts), GST_TIME_ARGS (in_dur)); - - /* can't clip without TIME segment */ - if (dec->segment.format != GST_FORMAT_TIME) - goto beach; - - /* we need a start time */ - if (!GST_CLOCK_TIME_IS_VALID (in_ts)) - goto beach; - - /* generate valid stop, if duration unknown, we have unknown stop */ - stop = - GST_CLOCK_TIME_IS_VALID (in_dur) ? (in_ts + in_dur) : GST_CLOCK_TIME_NONE; - - /* now clip */ - if (!(res = gst_segment_clip (&dec->segment, GST_FORMAT_TIME, - in_ts, stop, &cstart, &cstop))) - goto beach; - - /* update timestamp and possibly duration if the clipped stop time is - * valid */ - GST_BUFFER_TIMESTAMP (buf) = cstart; - if (GST_CLOCK_TIME_IS_VALID (cstop)) - GST_BUFFER_DURATION (buf) = cstop - cstart; - -beach: - GST_LOG_OBJECT (dec, "%sdropping", (res ? "not " : "")); - return res; -} - -static GstFlowReturn -theora_dec_push_forward (GstTheoraDec * dec, GstBuffer * buf) -{ - GstFlowReturn result = GST_FLOW_OK; - - if (clip_buffer (dec, buf)) { - if (dec->discont) { - GST_LOG_OBJECT (dec, "setting DISCONT"); - GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); - dec->discont = FALSE; - } - result = gst_pad_push (dec->srcpad, buf); - } else { - gst_buffer_unref (buf); - } - - return result; -} - -static GstFlowReturn -theora_dec_push_reverse (GstTheoraDec * dec, GstBuffer * buf) -{ - GstFlowReturn result = GST_FLOW_OK; - - dec->queued = g_list_prepend (dec->queued, buf); - - return result; -} - /* Allocate buffer and copy image data into Y444 format */ static GstFlowReturn -theora_handle_image (GstTheoraDec * dec, th_ycbcr_buffer buf, GstBuffer ** out) +theora_handle_image (GstTheoraDec * dec, th_ycbcr_buffer buf, + GstVideoCodecFrame * frame) { + GstVideoDecoder *decoder = GST_VIDEO_DECODER (dec); + GstVideoInfo *info; gint width, height, stride; GstFlowReturn result; int i, plane; - GstVideoFormat format; guint8 *dest, *src; + GstBuffer *out; - switch (dec->info.pixel_fmt) { - case TH_PF_444: - format = GST_VIDEO_FORMAT_Y444; - break; - case TH_PF_420: - format = GST_VIDEO_FORMAT_I420; - break; - case TH_PF_422: - format = GST_VIDEO_FORMAT_Y42B; - break; - default: - g_assert_not_reached (); - } + result = gst_video_decoder_alloc_output_frame (decoder, frame); - result = - gst_pad_alloc_buffer_and_set_caps (dec->srcpad, GST_BUFFER_OFFSET_NONE, - gst_video_format_get_size (format, dec->width, dec->height), - GST_PAD_CAPS (dec->srcpad), out); if (G_UNLIKELY (result != GST_FLOW_OK)) { GST_DEBUG_OBJECT (dec, "could not get buffer, reason: %s", gst_flow_get_name (result)); return result; } - for (plane = 0; plane < 3; plane++) { - width = gst_video_format_get_component_width (format, plane, dec->width); - height = gst_video_format_get_component_height (format, plane, dec->height); - stride = gst_video_format_get_row_stride (format, plane, dec->width); + out = frame->output_buffer; + info = &dec->output_state->info; - dest = - GST_BUFFER_DATA (*out) + gst_video_format_get_component_offset (format, - plane, dec->width, dec->height); + /* FIXME : Use GstVideoInfo */ + for (plane = 0; plane < 3; plane++) { + width = GST_VIDEO_INFO_COMP_WIDTH (info, plane); + height = GST_VIDEO_INFO_COMP_HEIGHT (info, plane); + stride = GST_VIDEO_INFO_COMP_STRIDE (info, plane); + + dest = GST_BUFFER_DATA (out) + GST_VIDEO_INFO_COMP_OFFSET (info, plane); src = buf[plane].data; - src += ((height == dec->height) ? dec->offset_y : dec->offset_y / 2) + src += + ((height == + GST_VIDEO_INFO_HEIGHT (info)) ? dec->offset_y : dec->offset_y / 2) * buf[plane].stride; - src += (width == dec->width) ? dec->offset_x : dec->offset_x / 2; + src += + (width == + GST_VIDEO_INFO_WIDTH (info)) ? dec->offset_x : dec->offset_x / 2; for (i = 0; i < height; i++) { memcpy (dest, src, width); @@ -1107,11 +610,10 @@ theora_handle_image (GstTheoraDec * dec, th_ycbcr_buffer buf, GstBuffer ** out) static GstFlowReturn theora_handle_data_packet (GstTheoraDec * dec, ogg_packet * packet, - GstClockTime outtime, GstClockTime outdur) + GstVideoCodecFrame * frame) { /* normal data packet */ th_ycbcr_buffer buf; - GstBuffer *out; gboolean keyframe; GstFlowReturn result; ogg_int64_t gp; @@ -1119,17 +621,6 @@ theora_handle_data_packet (GstTheoraDec * dec, ogg_packet * packet, if (G_UNLIKELY (!dec->have_header)) goto not_initialized; - /* get timestamp and durations */ - if (outtime == -1) - outtime = dec->last_timestamp; - if (outdur == -1) - outdur = gst_util_uint64_scale_int (GST_SECOND, dec->info.fps_denominator, - dec->info.fps_numerator); - - /* calculate expected next timestamp */ - if (outtime != -1 && outdur != -1) - dec->last_timestamp = outtime + outdur; - /* the second most significant bit of the first data byte is cleared * for keyframes. We can only check it if it's not a zero-length packet. */ keyframe = packet->bytes && ((packet->packet[0] & 0x40) == 0); @@ -1146,50 +637,10 @@ theora_handle_data_packet (GstTheoraDec * dec, ogg_packet * packet, if (G_UNLIKELY (th_decode_packetin (dec->decoder, packet, &gp) < 0)) goto decode_error; - if (outtime != -1) { - gboolean need_skip; - GstClockTime running_time; - GstClockTime earliest_time; - gdouble proportion; - - /* qos needs to be done on running time */ - running_time = gst_segment_to_running_time (&dec->segment, GST_FORMAT_TIME, - outtime); - - GST_OBJECT_LOCK (dec); - proportion = dec->proportion; - earliest_time = dec->earliest_time; - /* check for QoS, don't perform the last steps of getting and - * pushing the buffers that are known to be late. */ - need_skip = earliest_time != -1 && running_time <= earliest_time; - GST_OBJECT_UNLOCK (dec); - - if (need_skip) { - GstMessage *qos_msg; - guint64 stream_time; - gint64 jitter; - - GST_DEBUG_OBJECT (dec, "skipping decoding: qostime %" - GST_TIME_FORMAT " <= %" GST_TIME_FORMAT, - GST_TIME_ARGS (running_time), GST_TIME_ARGS (earliest_time)); - - dec->dropped++; - - stream_time = - gst_segment_to_stream_time (&dec->segment, GST_FORMAT_TIME, outtime); - jitter = GST_CLOCK_DIFF (running_time, earliest_time); - - qos_msg = - gst_message_new_qos (GST_OBJECT_CAST (dec), FALSE, running_time, - stream_time, outtime, outdur); - gst_message_set_qos_values (qos_msg, jitter, proportion, 1000000); - gst_message_set_qos_stats (qos_msg, GST_FORMAT_BUFFERS, - dec->processed, dec->dropped); - gst_element_post_message (GST_ELEMENT_CAST (dec), qos_msg); - - goto dropping_qos; - } - } + if (frame && + (gst_video_decoder_get_max_decode_time (GST_VIDEO_DECODER (dec), + frame) < 0)) + goto dropping_qos; /* this does postprocessing and set up the decoded frame * pointers in our yuv variable */ @@ -1200,25 +651,10 @@ theora_handle_data_packet (GstTheoraDec * dec, ogg_packet * packet, || (buf[0].height != dec->info.frame_height))) goto wrong_dimensions; - result = theora_handle_image (dec, buf, &out); + result = theora_handle_image (dec, buf, frame); if (result != GST_FLOW_OK) return result; - GST_BUFFER_OFFSET (out) = dec->frame_nr; - if (dec->frame_nr != -1) - dec->frame_nr++; - GST_BUFFER_OFFSET_END (out) = dec->frame_nr; - - GST_BUFFER_TIMESTAMP (out) = outtime; - GST_BUFFER_DURATION (out) = outdur; - - dec->processed++; - - if (dec->segment.rate >= 0.0) - result = theora_dec_push_forward (dec, out); - else - result = theora_dec_push_reverse (dec, out); - return result; /* ERRORS */ @@ -1231,16 +667,12 @@ not_initialized: dropping: { GST_WARNING_OBJECT (dec, "dropping frame because we need a keyframe"); - dec->discont = TRUE; - return GST_FLOW_OK; + return GST_VIDEO_DECODER_FLOW_NEED_DATA; } dropping_qos: { - if (dec->frame_nr != -1) - dec->frame_nr++; - dec->discont = TRUE; GST_WARNING_OBJECT (dec, "dropping frame because of QoS"); - return GST_FLOW_OK; + return GST_VIDEO_DECODER_FLOW_NEED_DATA; } decode_error: { @@ -1263,11 +695,11 @@ wrong_dimensions: } static GstFlowReturn -theora_dec_decode_buffer (GstTheoraDec * dec, GstBuffer * buf) +theora_dec_decode_buffer (GstTheoraDec * dec, GstBuffer * buf, + GstVideoCodecFrame * frame) { ogg_packet packet; GstFlowReturn result = GST_FLOW_OK; - GstClockTime timestamp, duration; /* make ogg_packet out of the buffer */ packet.packet = GST_BUFFER_DATA (buf); @@ -1280,13 +712,7 @@ theora_dec_decode_buffer (GstTheoraDec * dec, GstBuffer * buf) GST_LOG_OBJECT (dec, "decode buffer of size %ld", packet.bytes); - /* save last seem timestamp for interpolating the next timestamps using the - * framerate when we need to */ - timestamp = GST_BUFFER_TIMESTAMP (buf); - duration = GST_BUFFER_DURATION (buf); - - GST_DEBUG_OBJECT (dec, "header=%02x, outtime=%" GST_TIME_FORMAT, - packet.bytes ? packet.packet[0] : -1, GST_TIME_ARGS (timestamp)); + GST_DEBUG_OBJECT (dec, "header=%02x", packet.bytes ? packet.packet[0] : -1); /* switch depending on packet type. A zero byte packet is always a data * packet; we don't dereference it in that case. */ @@ -1296,254 +722,34 @@ theora_dec_decode_buffer (GstTheoraDec * dec, GstBuffer * buf) goto done; } result = theora_handle_header_packet (dec, &packet); + /* header packets are not meant to be displayed */ + /* FIXME : This is a temporary hack. The proper fix would be to + * not call _finish_frame() for these types of packets */ + GST_VIDEO_CODEC_FRAME_FLAG_SET (frame, + GST_VIDEO_CODEC_FRAME_FLAG_DECODE_ONLY); } else { - result = theora_handle_data_packet (dec, &packet, timestamp, duration); + result = theora_handle_data_packet (dec, &packet, frame); } done: return result; } -/* For reverse playback we use a technique that can be used for - * any keyframe based video codec. - * - * Input: - * Buffer decoding order: 7 8 9 4 5 6 1 2 3 EOS - * Keyframe flag: K K - * Discont flag: D D D - * - * - Each Discont marks a discont in the decoding order. - * - The keyframes mark where we can start decoding. - * - * First we prepend incomming buffers to the gather queue, whenever we receive - * a discont, we flush out the gather queue. - * - * The above data will be accumulated in the gather queue like this: - * - * gather queue: 9 8 7 - * D - * - * Whe buffer 4 is received (with a DISCONT), we flush the gather queue like - * this: - * - * while (gather) - * take head of queue and prepend to decode queue. - * if we copied a keyframe, decode the decode queue. - * - * After we flushed the gather queue, we add 4 to the (now empty) gather queue. - * We get the following situation: - * - * gather queue: 4 - * decode queue: 7 8 9 - * - * After we received 5 (Keyframe) and 6: - * - * gather queue: 6 5 4 - * decode queue: 7 8 9 - * - * When we receive 1 (DISCONT) which triggers a flush of the gather queue: - * - * Copy head of the gather queue (6) to decode queue: - * - * gather queue: 5 4 - * decode queue: 6 7 8 9 - * - * Copy head of the gather queue (5) to decode queue. This is a keyframe so we - * can start decoding. - * - * gather queue: 4 - * decode queue: 5 6 7 8 9 - * - * Decode frames in decode queue, store raw decoded data in output queue, we - * can take the head of the decode queue and prepend the decoded result in the - * output queue: - * - * gather queue: 4 - * decode queue: - * output queue: 9 8 7 6 5 - * - * Now output all the frames in the output queue, picking a frame from the - * head of the queue. - * - * Copy head of the gather queue (4) to decode queue, we flushed the gather - * queue and can now store input buffer in the gather queue: - * - * gather queue: 1 - * decode queue: 4 - * - * When we receive EOS, the queue looks like: - * - * gather queue: 3 2 1 - * decode queue: 4 - * - * Fill decode queue, first keyframe we copy is 2: - * - * gather queue: 1 - * decode queue: 2 3 4 - * - * Decoded output: - * - * gather queue: 1 - * decode queue: - * output queue: 4 3 2 - * - * Leftover buffer 1 cannot be decoded and must be discarded. - */ static GstFlowReturn -theora_dec_flush_decode (GstTheoraDec * dec) -{ - GstFlowReturn res = GST_FLOW_OK; - - while (dec->decode) { - GstBuffer *buf = GST_BUFFER_CAST (dec->decode->data); - - GST_DEBUG_OBJECT (dec, "decoding buffer %p, ts %" GST_TIME_FORMAT, - buf, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); - - /* decode buffer, prepend to output queue */ - res = theora_dec_decode_buffer (dec, buf); - - /* don't need it anymore now */ - gst_buffer_unref (buf); - - dec->decode = g_list_delete_link (dec->decode, dec->decode); - } - while (dec->queued) { - GstBuffer *buf = GST_BUFFER_CAST (dec->queued->data); - - /* iterate output queue an push downstream */ - res = gst_pad_push (dec->srcpad, buf); - - dec->queued = g_list_delete_link (dec->queued, dec->queued); - } - - return res; -} - -static GstFlowReturn -theora_dec_chain_reverse (GstTheoraDec * dec, gboolean discont, GstBuffer * buf) -{ - GstFlowReturn res = GST_FLOW_OK; - - /* if we have a discont, move buffers to the decode list */ - if (G_UNLIKELY (discont)) { - GST_DEBUG_OBJECT (dec, "received discont,gathering buffers"); - while (dec->gather) { - GstBuffer *gbuf; - guint8 *data; - - gbuf = GST_BUFFER_CAST (dec->gather->data); - /* remove from the gather list */ - dec->gather = g_list_delete_link (dec->gather, dec->gather); - /* copy to decode queue */ - dec->decode = g_list_prepend (dec->decode, gbuf); - - /* if we copied a keyframe, flush and decode the decode queue */ - data = GST_BUFFER_DATA (gbuf); - if (data && (data[0] & 0x40) == 0) { - GST_DEBUG_OBJECT (dec, "copied keyframe"); - res = theora_dec_flush_decode (dec); - } - } - } - - /* add buffer to gather queue */ - GST_DEBUG_OBJECT (dec, "gathering buffer %p, size %u", buf, - GST_BUFFER_SIZE (buf)); - dec->gather = g_list_prepend (dec->gather, buf); - - return res; -} - -static GstFlowReturn -theora_dec_chain_forward (GstTheoraDec * dec, gboolean discont, - GstBuffer * buffer) -{ - GstFlowReturn result; - - result = theora_dec_decode_buffer (dec, buffer); - - gst_buffer_unref (buffer); - - return result; -} - -static GstFlowReturn -theora_dec_chain (GstPad * pad, GstBuffer * buf) +theora_dec_handle_frame (GstVideoDecoder * bdec, GstVideoCodecFrame * frame) { GstTheoraDec *dec; GstFlowReturn res; - gboolean discont; - dec = GST_THEORA_DEC (gst_pad_get_parent (pad)); + dec = GST_THEORA_DEC (bdec); - /* peel of DISCONT flag */ - discont = GST_BUFFER_IS_DISCONT (buf); - - /* resync on DISCONT */ - if (G_UNLIKELY (discont)) { - GST_DEBUG_OBJECT (dec, "received DISCONT buffer"); - dec->need_keyframe = TRUE; - dec->last_timestamp = -1; - dec->discont = TRUE; - } - - if (dec->segment.rate > 0.0) - res = theora_dec_chain_forward (dec, discont, buf); - else - res = theora_dec_chain_reverse (dec, discont, buf); - - gst_object_unref (dec); + res = theora_dec_decode_buffer (dec, frame->input_buffer, frame); + if (res == GST_FLOW_OK) + res = gst_video_decoder_finish_frame (bdec, frame); return res; } -static GstStateChangeReturn -theora_dec_change_state (GstElement * element, GstStateChange transition) -{ - GstTheoraDec *dec = GST_THEORA_DEC (element); - GstStateChangeReturn ret; - - switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - th_info_clear (&dec->info); - th_comment_clear (&dec->comment); - GST_DEBUG_OBJECT (dec, "Setting have_header to FALSE in READY->PAUSED"); - dec->have_header = FALSE; - dec->have_par = FALSE; - gst_theora_dec_reset (dec); - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - break; - default: - break; - } - - ret = parent_class->change_state (element, transition); - - switch (transition) { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - th_info_clear (&dec->info); - th_comment_clear (&dec->comment); - th_setup_free (dec->setup); - dec->setup = NULL; - th_decode_free (dec->decoder); - dec->decoder = NULL; - gst_theora_dec_reset (dec); - break; - case GST_STATE_CHANGE_READY_TO_NULL: - break; - default: - break; - } - - return ret; -} - static void theora_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) @@ -1599,3 +805,10 @@ theora_dec_get_property (GObject * object, guint prop_id, break; } } + +gboolean +gst_theora_dec_register (GstPlugin * plugin) +{ + return gst_element_register (plugin, "theoradec", + GST_RANK_PRIMARY, GST_TYPE_THEORA_DEC); +} diff --git a/ext/theora/gsttheoradec.h b/ext/theora/gsttheoradec.h index 34578ced2b..c66eed80b6 100644 --- a/ext/theora/gsttheoradec.h +++ b/ext/theora/gsttheoradec.h @@ -1,5 +1,8 @@ /* GStreamer * Copyright (C) 2004 Benjamin Otte + * Copyright (c) 2012 Collabora Ltd. + * Author : Edward Hervey + * Author : Mark Nauwelaerts * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -25,6 +28,7 @@ #endif #include +#include #include #include @@ -51,11 +55,7 @@ typedef struct _GstTheoraDecClass GstTheoraDecClass; */ struct _GstTheoraDec { - GstElement element; - - /* Pads */ - GstPad *sinkpad; - GstPad *srcpad; + GstVideoDecoder element; /* theora decoder state */ th_dec_ctx *decoder; @@ -66,14 +66,12 @@ struct _GstTheoraDec gboolean have_header; - GstClockTime last_timestamp; - guint64 frame_nr; gboolean need_keyframe; - gint width, height; + GstVideoCodecState *input_state; + GstVideoCodecState *output_state; gint offset_x, offset_y; - gint output_bpp; - /* telemetry debugging options */ + /* telemetry debuging options */ gint telemetry_mv; gint telemetry_mbmode; gint telemetry_qi; @@ -81,39 +79,17 @@ struct _GstTheoraDec gboolean crop; - /* list of buffers that need timestamps */ - GList *queued; - /* list of raw output buffers */ - GList *output; - /* gather/decode queues for reverse playback */ - GList *gather; - GList *decode; - GList *pendingevents; - GstTagList *tags; - /* segment info */ /* with STREAM_LOCK */ - GstSegment segment; - gboolean discont; - guint32 seqnum; - - /* QoS stuff */ /* with LOCK*/ - gdouble proportion; - GstClockTime earliest_time; - guint64 processed; - guint64 dropped; - - gboolean have_par; - gint par_num; - gint par_den; }; struct _GstTheoraDecClass { - GstElementClass parent_class; + GstVideoDecoderClass parent_class; }; GType gst_theora_dec_get_type (void); +gboolean gst_theora_dec_register (GstPlugin * plugin); G_END_DECLS diff --git a/ext/theora/gsttheoraenc.c b/ext/theora/gsttheoraenc.c index bff2384804..75f0bbac7c 100644 --- a/ext/theora/gsttheoraenc.c +++ b/ext/theora/gsttheoraenc.c @@ -1,5 +1,8 @@ /* GStreamer * Copyright (C) 2004 Wim Taymans + * Copyright (c) 2012 Collabora Ltd. + * Author : Edward Hervey + * Author : Mark Nauwelaerts * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -130,7 +133,6 @@ _ilog (unsigned int v) #define THEORA_DEF_RATE_BUFFER 0 #define THEORA_DEF_MULTIPASS_CACHE_FILE NULL #define THEORA_DEF_MULTIPASS_MODE MULTIPASS_MODE_SINGLE_PASS -#define THEORA_DEF_DUP_ON_GAP FALSE enum { PROP_0, @@ -153,8 +155,7 @@ enum PROP_CAP_UNDERFLOW, PROP_RATE_BUFFER, PROP_MULTIPASS_CACHE_FILE, - PROP_MULTIPASS_MODE, - PROP_DUP_ON_GAP + PROP_MULTIPASS_MODE /* FILL ME */ }; @@ -177,57 +178,6 @@ granulepos_to_timestamp (GstTheoraEnc * theoraenc, ogg_int64_t granulepos) theoraenc->info.fps_numerator); } -/* Generate a dummy encoder context for use in th_encode_ctl queries - Release with th_encode_free() - This and the next routine from theora/examples/libtheora_info.c */ -static th_enc_ctx * -dummy_encode_ctx (void) -{ - th_enc_ctx *ctx; - th_info info; - - /* set the minimal video parameters */ - th_info_init (&info); - info.frame_width = 320; - info.frame_height = 240; - info.fps_numerator = 1; - info.fps_denominator = 1; - - /* allocate and initialize a context object */ - ctx = th_encode_alloc (&info); - if (!ctx) - GST_WARNING ("Failed to allocate dummy encoder context."); - - /* clear the info struct */ - th_info_clear (&info); - - return ctx; -} - -/* Query the current and maximum values for the 'speed level' setting. - This can be used to ask the encoder to trade off encoding quality - vs. performance cost, for example to adapt to realtime constraints. */ -static int -check_speed_level (th_enc_ctx * ctx, int *current, int *max) -{ - int ret; - - /* query the current speed level */ - ret = th_encode_ctl (ctx, TH_ENCCTL_GET_SPLEVEL, current, sizeof (int)); - if (ret) { - GST_WARNING ("Error %d getting current speed level.", ret); - return ret; - } - /* query the maximum speed level, which varies by encoder version */ - ret = th_encode_ctl (ctx, TH_ENCCTL_GET_SPLEVEL_MAX, max, sizeof (int)); - if (ret) { - GST_WARNING ("Error %d getting maximum speed level.", ret); - return ret; - } - - return 0; -} - static GstStaticPadTemplate theora_enc_sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, @@ -245,31 +195,20 @@ GST_STATIC_PAD_TEMPLATE ("src", GST_STATIC_CAPS ("video/x-theora") ); -static GstCaps *theora_enc_src_caps; +GST_BOILERPLATE (GstTheoraEnc, gst_theora_enc, GstVideoEncoder, + GST_TYPE_VIDEO_ENCODER); -static void -_do_init (GType object_type) -{ - const GInterfaceInfo preset_interface_info = { - NULL, /* interface_init */ - NULL, /* interface_finalize */ - NULL /* interface_data */ - }; +static gboolean theora_enc_start (GstVideoEncoder * enc); +static gboolean theora_enc_stop (GstVideoEncoder * enc); +static gboolean theora_enc_set_format (GstVideoEncoder * enc, + GstVideoCodecState * state); +static GstFlowReturn theora_enc_handle_frame (GstVideoEncoder * enc, + GstVideoCodecFrame * frame); +static GstFlowReturn theora_enc_pre_push (GstVideoEncoder * benc, + GstVideoCodecFrame * frame); +static GstFlowReturn theora_enc_finish (GstVideoEncoder * enc); - g_type_add_interface_static (object_type, GST_TYPE_PRESET, - &preset_interface_info); -} - -GST_BOILERPLATE_FULL (GstTheoraEnc, gst_theora_enc, GstElement, - GST_TYPE_ELEMENT, _do_init); - -static gboolean theora_enc_sink_event (GstPad * pad, GstEvent * event); -static gboolean theora_enc_src_event (GstPad * pad, GstEvent * event); -static GstFlowReturn theora_enc_chain (GstPad * pad, GstBuffer * buffer); -static GstStateChangeReturn theora_enc_change_state (GstElement * element, - GstStateChange transition); static GstCaps *theora_enc_sink_getcaps (GstPad * pad); -static gboolean theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps); static void theora_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void theora_enc_set_property (GObject * object, guint prop_id, @@ -279,22 +218,15 @@ static void theora_enc_finalize (GObject * object); static gboolean theora_enc_write_multipass_cache (GstTheoraEnc * enc, gboolean begin, gboolean eos); -static char *theora_enc_get_supported_formats (void); - -static void theora_timefifo_free (GstTheoraEnc * enc); -static GstFlowReturn -theora_enc_encode_and_push (GstTheoraEnc * enc, ogg_packet op, - GstBuffer * buffer); - static void gst_theora_enc_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); - gst_element_class_add_static_pad_template (element_class, - &theora_enc_src_factory); - gst_element_class_add_static_pad_template (element_class, - &theora_enc_sink_factory); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&theora_enc_src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&theora_enc_sink_factory)); gst_element_class_set_details_simple (element_class, "Theora video encoder", "Codec/Encoder/Video", "encode raw YUV video to a theora stream", @@ -305,28 +237,22 @@ static void gst_theora_enc_class_init (GstTheoraEncClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; - GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); - char *caps_string; - - /* query runtime encoder properties */ - th_enc_ctx *th_ctx; - int default_speed_level = THEORA_DEF_SPEEDLEVEL; - int max_speed_level = default_speed_level; - - GST_DEBUG_CATEGORY_INIT (theoraenc_debug, "theoraenc", 0, "Theora encoder"); - - th_ctx = dummy_encode_ctx (); - if (th_ctx) { - if (check_speed_level (th_ctx, &default_speed_level, &max_speed_level)) - GST_WARNING - ("Failed to determine settings for the speed-level property."); - th_encode_free (th_ctx); - } + GstVideoEncoderClass *gstvideo_encoder_class = + GST_VIDEO_ENCODER_CLASS (klass); gobject_class->set_property = theora_enc_set_property; gobject_class->get_property = theora_enc_get_property; gobject_class->finalize = theora_enc_finalize; + gstvideo_encoder_class->start = GST_DEBUG_FUNCPTR (theora_enc_start); + gstvideo_encoder_class->stop = GST_DEBUG_FUNCPTR (theora_enc_stop); + gstvideo_encoder_class->set_format = + GST_DEBUG_FUNCPTR (theora_enc_set_format); + gstvideo_encoder_class->handle_frame = + GST_DEBUG_FUNCPTR (theora_enc_handle_frame); + gstvideo_encoder_class->pre_push = GST_DEBUG_FUNCPTR (theora_enc_pre_push); + gstvideo_encoder_class->finish = GST_DEBUG_FUNCPTR (theora_enc_finish); + g_object_class_install_property (gobject_class, PROP_CENTER, g_param_spec_boolean ("center", "Center", "ignored and kept for API compat only", TRUE, @@ -382,38 +308,40 @@ gst_theora_enc_class_init (GstTheoraEncClass * klass) (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_SPEEDLEVEL, g_param_spec_int ("speed-level", "Speed level", - "Controls the amount of analysis performed when encoding." - " Higher values trade compression quality for speed." - " This property requires libtheora version >= 1.0" - ", and the maximum value may vary based on encoder version.", - 0, max_speed_level, default_speed_level, - (GParamFlags) G_PARAM_READWRITE | G_PARAM_CONSTRUCT | - G_PARAM_STATIC_STRINGS)); + "Controls the amount of motion vector searching done while " + "encoding. This property requires libtheora version >= 1.0", + 0, 3, THEORA_DEF_SPEEDLEVEL, + (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_VP3_COMPATIBLE, g_param_spec_boolean ("vp3-compatible", "VP3 Compatible", - "Disables non-VP3 compatible features", + "Disables non-VP3 compatible features." + " This property requires libtheora version >= 1.1", THEORA_DEF_VP3_COMPATIBLE, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_DROP_FRAMES, - g_param_spec_boolean ("drop-frames", "Drop Frames", - "Allow or disallow frame dropping", + g_param_spec_boolean ("drop-frames", "VP3 Compatible", + "Allow or disallow frame dropping." + " This property requires libtheora version >= 1.1", THEORA_DEF_DROP_FRAMES, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CAP_OVERFLOW, - g_param_spec_boolean ("cap-overflow", "Cap Overflow", - "Enable capping of bit reservoir overflows", + g_param_spec_boolean ("cap-overflow", "VP3 Compatible", + "Enable capping of bit reservoir overflows." + " This property requires libtheora version >= 1.1", THEORA_DEF_CAP_OVERFLOW, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CAP_UNDERFLOW, - g_param_spec_boolean ("cap-underflow", "Cap Underflow", - "Enable capping of bit reservoir underflows", + g_param_spec_boolean ("cap-underflow", "VP3 Compatible", + "Enable capping of bit reservoir underflows." + " This property requires libtheora version >= 1.1", THEORA_DEF_CAP_UNDERFLOW, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RATE_BUFFER, g_param_spec_int ("rate-buffer", "Rate Control Buffer", "Sets the size of the rate control buffer, in units of frames. " "The default value of 0 instructs the encoder to automatically " - "select an appropriate value", + "select an appropriate value." + " This property requires libtheora version >= 1.1", 0, 1000, THEORA_DEF_RATE_BUFFER, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_MULTIPASS_CACHE_FILE, @@ -425,46 +353,15 @@ gst_theora_enc_class_init (GstTheoraEncClass * klass) "Single pass or first/second pass", GST_TYPE_MULTIPASS_MODE, THEORA_DEF_MULTIPASS_MODE, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_class, PROP_DUP_ON_GAP, - g_param_spec_boolean ("dup-on-gap", "Create DUP frame on GAP flag", - "Allow codec to handle frames with GAP flag as duplicates " - "of previous frame. " - "This is good to work with variable frame rate stabilized " - "by videorate element. It will add variable latency with maximal " - "size of keyframe distance, this way it is a bad idea " - "to use with live streams.", - THEORA_DEF_DUP_ON_GAP, - (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - caps_string = g_strdup_printf ("video/x-raw-yuv, " - "format = (fourcc) { %s }, " - "framerate = (fraction) [1/MAX, MAX], " - "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]", - theora_enc_get_supported_formats ()); - theora_enc_src_caps = gst_caps_from_string (caps_string); - g_free (caps_string); - - gstelement_class->change_state = theora_enc_change_state; + GST_DEBUG_CATEGORY_INIT (theoraenc_debug, "theoraenc", 0, "Theora encoder"); } static void gst_theora_enc_init (GstTheoraEnc * enc, GstTheoraEncClass * g_class) { - enc->sinkpad = - gst_pad_new_from_static_template (&theora_enc_sink_factory, "sink"); - gst_pad_set_chain_function (enc->sinkpad, theora_enc_chain); - gst_pad_set_event_function (enc->sinkpad, theora_enc_sink_event); - gst_pad_set_getcaps_function (enc->sinkpad, theora_enc_sink_getcaps); - gst_pad_set_setcaps_function (enc->sinkpad, theora_enc_sink_setcaps); - gst_element_add_pad (GST_ELEMENT (enc), enc->sinkpad); - - enc->srcpad = - gst_pad_new_from_static_template (&theora_enc_src_factory, "src"); - gst_pad_set_event_function (enc->srcpad, theora_enc_src_event); - gst_pad_use_fixed_caps (enc->srcpad); - gst_element_add_pad (GST_ELEMENT (enc), enc->srcpad); - - gst_segment_init (&enc->segment, GST_FORMAT_UNDEFINED); + gst_pad_set_getcaps_function (GST_VIDEO_ENCODER_SINK_PAD (enc), + GST_DEBUG_FUNCPTR (theora_enc_sink_getcaps)); enc->video_bitrate = THEORA_DEF_BITRATE; enc->video_quality = THEORA_DEF_QUALITY; @@ -472,15 +369,12 @@ gst_theora_enc_init (GstTheoraEnc * enc, GstTheoraEncClass * g_class) enc->keyframe_freq = THEORA_DEF_KEYFRAME_FREQ; enc->keyframe_force = THEORA_DEF_KEYFRAME_FREQ_FORCE; - enc->expected_ts = GST_CLOCK_TIME_NONE; - - /* enc->speed_level is set to the libtheora default by the constructor */ + enc->speed_level = THEORA_DEF_SPEEDLEVEL; enc->vp3_compatible = THEORA_DEF_VP3_COMPATIBLE; enc->drop_frames = THEORA_DEF_DROP_FRAMES; enc->cap_overflow = THEORA_DEF_CAP_OVERFLOW; enc->cap_underflow = THEORA_DEF_CAP_UNDERFLOW; enc->rate_buffer = THEORA_DEF_RATE_BUFFER; - enc->dup_on_gap = THEORA_DEF_DUP_ON_GAP; enc->multipass_mode = THEORA_DEF_MULTIPASS_MODE; enc->multipass_cache_file = THEORA_DEF_MULTIPASS_CACHE_FILE; @@ -515,6 +409,9 @@ theora_enc_finalize (GObject * object) theora_enc_clear_multipass_cache (enc); + if (enc->input_state) + gst_video_codec_state_unref (enc->input_state); + G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -526,13 +423,7 @@ theora_enc_reset (GstTheoraEnc * enc) GST_OBJECT_LOCK (enc); enc->info.target_bitrate = enc->video_bitrate; - if (enc->quality_changed) { - enc->info.quality = enc->video_quality; - } else { - if (enc->video_bitrate == 0) { - enc->info.quality = enc->video_quality; - } - } + enc->info.quality = enc->video_quality; enc->bitrate_changed = FALSE; enc->quality_changed = FALSE; GST_OBJECT_UNLOCK (enc); @@ -575,19 +466,63 @@ theora_enc_reset (GstTheoraEnc * enc) theora_enc_write_multipass_cache (enc, TRUE, FALSE); } -static void -theora_enc_clear (GstTheoraEnc * enc) +static gboolean +theora_enc_start (GstVideoEncoder * benc) { + GstTheoraEnc *enc; + + GST_DEBUG_OBJECT (benc, "start: init theora"); + enc = GST_THEORA_ENC (benc); + + th_info_init (&enc->info); + th_comment_init (&enc->comment); enc->packetno = 0; - enc->bytes_out = 0; - enc->granulepos_offset = 0; - enc->timestamp_offset = 0; - theora_timefifo_free (enc); + if (enc->multipass_mode >= MULTIPASS_MODE_FIRST_PASS) { + GError *err = NULL; - enc->next_ts = GST_CLOCK_TIME_NONE; - enc->next_discont = FALSE; - enc->expected_ts = GST_CLOCK_TIME_NONE; + if (!enc->multipass_cache_file) { + GST_ELEMENT_ERROR (enc, LIBRARY, SETTINGS, (NULL), (NULL)); + return FALSE; + } + enc->multipass_cache_fd = + g_io_channel_new_file (enc->multipass_cache_file, + (enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS ? "w" : "r"), &err); + + if (enc->multipass_mode == MULTIPASS_MODE_SECOND_PASS) + enc->multipass_cache_adapter = gst_adapter_new (); + + if (!enc->multipass_cache_fd) { + GST_ELEMENT_ERROR (enc, RESOURCE, OPEN_READ, (NULL), + ("Failed to open multipass cache file: %s", err->message)); + g_error_free (err); + return FALSE; + } + + g_io_channel_set_encoding (enc->multipass_cache_fd, NULL, NULL); + } + + return TRUE; +} + +static gboolean +theora_enc_stop (GstVideoEncoder * benc) +{ + GstTheoraEnc *enc; + + GST_DEBUG_OBJECT (benc, "stop: clearing theora state"); + enc = GST_THEORA_ENC (benc); + + if (enc->encoder) { + th_encode_free (enc->encoder); + enc->encoder = NULL; + } + th_comment_clear (&enc->comment); + th_info_clear (&enc->info); + + enc->initialised = FALSE; + + return TRUE; } static char * @@ -595,10 +530,10 @@ theora_enc_get_supported_formats (void) { th_enc_ctx *encoder; th_info info; - static const struct + struct { th_pixel_fmt pixelformat; - const char fourcc[5]; + const char *fourcc; } formats[] = { { TH_PF_420, "I420"}, { @@ -638,68 +573,36 @@ theora_enc_get_supported_formats (void) static GstCaps * theora_enc_sink_getcaps (GstPad * pad) { - GstTheoraEnc *encoder; - GstPad *peer; GstCaps *caps; + char *supported_formats, *caps_string; - /* If we already have caps return them */ - if (GST_PAD_CAPS (pad)) - return gst_caps_ref (GST_PAD_CAPS (pad)); - - encoder = GST_THEORA_ENC (gst_pad_get_parent (pad)); - if (!encoder) + supported_formats = theora_enc_get_supported_formats (); + if (!supported_formats) { + GST_WARNING ("no supported formats found. Encoder disabled?"); return gst_caps_new_empty (); - - peer = gst_pad_get_peer (encoder->srcpad); - if (peer) { - const GstCaps *templ_caps; - GstCaps *peer_caps, *tmp_caps; - GstStructure *s; - guint i, n; - - peer_caps = gst_pad_get_caps (peer); - - /* Translate peercaps to YUV */ - peer_caps = gst_caps_make_writable (peer_caps); - n = gst_caps_get_size (peer_caps); - for (i = 0; i < n; i++) { - s = gst_caps_get_structure (peer_caps, i); - - gst_structure_set_name (s, "video/x-raw-yuv"); - gst_structure_remove_field (s, "streamheader"); - } - - templ_caps = gst_pad_get_pad_template_caps (pad); - - tmp_caps = gst_caps_intersect (peer_caps, templ_caps); - caps = gst_caps_intersect (tmp_caps, theora_enc_src_caps); - gst_caps_unref (tmp_caps); - gst_caps_unref (peer_caps); - gst_object_unref (peer); - peer = NULL; - } else { - caps = gst_caps_ref (theora_enc_src_caps); } - gst_object_unref (encoder); + caps_string = g_strdup_printf ("video/x-raw-yuv, " + "format = (fourcc) { %s }, " + "framerate = (fraction) [1/MAX, MAX], " + "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]", + supported_formats); + caps = gst_caps_from_string (caps_string); + g_free (caps_string); + g_free (supported_formats); + GST_DEBUG ("Supported caps: %" GST_PTR_FORMAT, caps); return caps; } static gboolean -theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps) +theora_enc_set_format (GstVideoEncoder * benc, GstVideoCodecState * state) { - GstStructure *structure = gst_caps_get_structure (caps, 0); - GstTheoraEnc *enc = GST_THEORA_ENC (gst_pad_get_parent (pad)); - guint32 fourcc; - const GValue *par; - gint fps_n, fps_d; + GstTheoraEnc *enc = GST_THEORA_ENC (benc); + GstVideoInfo *info = &state->info; - gst_structure_get_fourcc (structure, "format", &fourcc); - gst_structure_get_int (structure, "width", &enc->width); - gst_structure_get_int (structure, "height", &enc->height); - gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d); - par = gst_structure_get_value (structure, "pixel-aspect-ratio"); + enc->width = GST_VIDEO_INFO_WIDTH (info); + enc->height = GST_VIDEO_INFO_HEIGHT (info); th_info_clear (&enc->info); th_info_init (&enc->info); @@ -709,38 +612,32 @@ theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps) enc->info.frame_height = GST_ROUND_UP_16 (enc->height); enc->info.pic_width = enc->width; enc->info.pic_height = enc->height; - switch (fourcc) { - case GST_MAKE_FOURCC ('I', '4', '2', '0'): + switch (GST_VIDEO_INFO_FORMAT (info)) { + case GST_VIDEO_FORMAT_I420: enc->info.pixel_fmt = TH_PF_420; break; - case GST_MAKE_FOURCC ('Y', '4', '2', 'B'): + case GST_VIDEO_FORMAT_Y42B: enc->info.pixel_fmt = TH_PF_422; break; - case GST_MAKE_FOURCC ('Y', '4', '4', '4'): + case GST_VIDEO_FORMAT_Y444: enc->info.pixel_fmt = TH_PF_444; break; default: g_assert_not_reached (); } - enc->info.fps_numerator = enc->fps_n = fps_n; - enc->info.fps_denominator = enc->fps_d = fps_d; - if (par) { - enc->info.aspect_numerator = gst_value_get_fraction_numerator (par); - enc->par_n = gst_value_get_fraction_numerator (par); - enc->info.aspect_denominator = gst_value_get_fraction_denominator (par); - enc->par_d = gst_value_get_fraction_denominator (par); - } else { - /* setting them to 0 indicates that the decoder can chose a good aspect - * ratio, defaulting to 1/1 */ - enc->info.aspect_numerator = 0; - enc->par_n = 1; - enc->info.aspect_denominator = 0; - enc->par_d = 1; - } + enc->info.fps_numerator = enc->fps_n = GST_VIDEO_INFO_FPS_N (info); + enc->info.fps_denominator = enc->fps_d = GST_VIDEO_INFO_FPS_D (info); + enc->info.aspect_numerator = GST_VIDEO_INFO_PAR_N (info); + enc->info.aspect_denominator = GST_VIDEO_INFO_PAR_D (info); enc->info.colorspace = TH_CS_UNSPECIFIED; + /* Save input state */ + if (enc->input_state) + gst_video_codec_state_unref (enc->input_state); + enc->input_state = gst_video_codec_state_ref (state); + /* as done in theora */ enc->info.keyframe_granule_shift = _ilog (enc->keyframe_force - 1); GST_DEBUG_OBJECT (enc, @@ -750,31 +647,40 @@ theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps) theora_enc_reset (enc); enc->initialised = TRUE; - gst_object_unref (enc); - return TRUE; } -static guint64 -granulepos_add (guint64 granulepos, guint64 addend, gint shift) +static GstFlowReturn +theora_enc_pre_push (GstVideoEncoder * benc, GstVideoCodecFrame * frame) { - guint64 iframe, pframe; + GstTheoraEnc *enc = GST_THEORA_ENC (benc); + guint64 pfn; - iframe = granulepos >> shift; - pframe = granulepos - (iframe << shift); - iframe += addend; + /* see ext/ogg/README; OFFSET_END takes "our" granulepos, OFFSET its + * time representation */ + /* granulepos from sync frame */ + pfn = frame->presentation_frame_number - frame->distance_from_sync; + /* correct to correspond to linear running time */ + pfn -= enc->pfn_offset; + pfn += enc->granulepos_offset + 1; + /* granulepos */ + GST_BUFFER_OFFSET_END (frame->output_buffer) = + (pfn << enc->info.keyframe_granule_shift) + frame->distance_from_sync; + GST_BUFFER_OFFSET (frame->output_buffer) = granulepos_to_timestamp (enc, + GST_BUFFER_OFFSET_END (frame->output_buffer)); - return (iframe << shift) + pframe; + return GST_FLOW_OK; } -/* prepare a buffer for transmission by passing data through libtheora */ static GstFlowReturn -theora_buffer_from_packet (GstTheoraEnc * enc, ogg_packet * packet, - GstClockTime timestamp, GstClockTime running_time, - GstClockTime duration, GstBuffer ** buffer) +theora_push_packet (GstTheoraEnc * enc, ogg_packet * packet) { + GstVideoEncoder *benc; GstBuffer *buf; - GstFlowReturn ret = GST_FLOW_OK; + GstFlowReturn ret; + GstVideoCodecFrame *frame; + + benc = GST_VIDEO_ENCODER (enc); buf = gst_buffer_new_and_alloc (packet->bytes); if (!buf) { @@ -784,73 +690,33 @@ theora_buffer_from_packet (GstTheoraEnc * enc, ogg_packet * packet, } memcpy (GST_BUFFER_DATA (buf), packet->packet, packet->bytes); - gst_buffer_set_caps (buf, GST_PAD_CAPS (enc->srcpad)); - /* see ext/ogg/README; OFFSET_END takes "our" granulepos, OFFSET its - * time representation */ - GST_BUFFER_OFFSET_END (buf) = - granulepos_add (packet->granulepos, enc->granulepos_offset, - enc->info.keyframe_granule_shift); - GST_BUFFER_OFFSET (buf) = granulepos_to_timestamp (enc, - GST_BUFFER_OFFSET_END (buf)); - GST_BUFFER_TIMESTAMP (buf) = timestamp; - GST_BUFFER_DURATION (buf) = duration; + frame = gst_video_encoder_get_oldest_frame (benc); + frame->output_buffer = buf; - if (enc->next_discont) { - GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); - enc->next_discont = FALSE; - } - - /* th_packet_iskeyframe returns positive for keyframes */ - if (th_packet_iskeyframe (packet) > 0) { - GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + /* the second most significant bit of the first data byte is cleared + * for keyframes */ + if (packet->bytes > 0 && (packet->packet[0] & 0x40) == 0) { + GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame); } else { - GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + GST_VIDEO_CODEC_FRAME_UNSET_SYNC_POINT (frame); } enc->packetno++; + ret = gst_video_encoder_finish_frame (benc, frame); + done: - *buffer = buf; - return ret; -} - -/* push out the buffer and do internal bookkeeping */ -static GstFlowReturn -theora_push_buffer (GstTheoraEnc * enc, GstBuffer * buffer) -{ - GstFlowReturn ret; - - enc->bytes_out += GST_BUFFER_SIZE (buffer); - - ret = gst_pad_push (enc->srcpad, buffer); - - return ret; -} - -static GstFlowReturn -theora_push_packet (GstTheoraEnc * enc, ogg_packet * packet, - GstClockTime timestamp, GstClockTime running_time, GstClockTime duration) -{ - GstBuffer *buf; - GstFlowReturn ret; - - ret = - theora_buffer_from_packet (enc, packet, timestamp, running_time, duration, - &buf); - if (ret == GST_FLOW_OK) - ret = theora_push_buffer (enc, buf); - return ret; } static GstCaps * -theora_set_header_on_caps (GstCaps * caps, GSList * buffers) +theora_set_header_on_caps (GstCaps * caps, GList * buffers) { GstStructure *structure; GValue array = { 0 }; GValue value = { 0 }; GstBuffer *buffer; - GSList *walk; + GList *walk; caps = gst_caps_make_writable (caps); structure = gst_caps_get_structure (caps, 0); @@ -883,158 +749,6 @@ theora_set_header_on_caps (GstCaps * caps, GSList * buffers) return caps; } -static void -theora_enc_force_keyframe (GstTheoraEnc * enc) -{ - GstClockTime next_ts; - - /* make sure timestamps increment after resetting the decoder */ - next_ts = enc->next_ts + enc->timestamp_offset; - - theora_enc_reset (enc); - enc->granulepos_offset = - gst_util_uint64_scale (next_ts, enc->fps_n, GST_SECOND * enc->fps_d); - enc->timestamp_offset = next_ts; - enc->next_ts = 0; -} - -static gboolean -theora_enc_sink_event (GstPad * pad, GstEvent * event) -{ - GstTheoraEnc *enc; - ogg_packet op; - gboolean res; - - enc = GST_THEORA_ENC (GST_PAD_PARENT (pad)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_NEWSEGMENT: - { - gboolean update; - gdouble rate, applied_rate; - GstFormat format; - gint64 start, stop, time; - - gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, - &format, &start, &stop, &time); - - gst_segment_set_newsegment_full (&enc->segment, update, rate, - applied_rate, format, start, stop, time); - - res = gst_pad_push_event (enc->srcpad, event); - break; - } - case GST_EVENT_EOS: - if (enc->initialised) { - /* clear all standing buffers */ - if (enc->dup_on_gap) - theora_enc_encode_and_push (enc, op, NULL); - /* push last packet with eos flag, should not be called */ - while (th_encode_packetout (enc->encoder, 1, &op)) { - GstClockTime next_time = - th_granule_time (enc->encoder, op.granulepos) * GST_SECOND; - - theora_push_packet (enc, &op, GST_CLOCK_TIME_NONE, enc->next_ts, - next_time - enc->next_ts); - enc->next_ts = next_time; - } - } - if (enc->initialised && enc->multipass_cache_fd - && enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS) - theora_enc_write_multipass_cache (enc, TRUE, TRUE); - - theora_enc_clear_multipass_cache (enc); - - res = gst_pad_push_event (enc->srcpad, event); - break; - case GST_EVENT_FLUSH_STOP: - gst_segment_init (&enc->segment, GST_FORMAT_UNDEFINED); - res = gst_pad_push_event (enc->srcpad, event); - theora_timefifo_free (enc); - break; - case GST_EVENT_CUSTOM_DOWNSTREAM: - { - const GstStructure *s; - - s = gst_event_get_structure (event); - - if (gst_structure_has_name (s, "GstForceKeyUnit")) - theora_enc_force_keyframe (enc); - res = gst_pad_push_event (enc->srcpad, event); - break; - } - default: - res = gst_pad_push_event (enc->srcpad, event); - break; - } - return res; -} - -static gboolean -theora_enc_src_event (GstPad * pad, GstEvent * event) -{ - GstTheoraEnc *enc; - gboolean res = TRUE; - - enc = GST_THEORA_ENC (GST_PAD_PARENT (pad)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_CUSTOM_UPSTREAM: - { - const GstStructure *s; - - s = gst_event_get_structure (event); - - if (gst_structure_has_name (s, "GstForceKeyUnit")) { - GST_OBJECT_LOCK (enc); - enc->force_keyframe = TRUE; - GST_OBJECT_UNLOCK (enc); - /* consume the event */ - res = TRUE; - gst_event_unref (event); - } else { - res = gst_pad_push_event (enc->sinkpad, event); - } - break; - } - default: - res = gst_pad_push_event (enc->sinkpad, event); - break; - } - - return res; -} - -static gboolean -theora_enc_is_discontinuous (GstTheoraEnc * enc, GstClockTime timestamp, - GstClockTime duration) -{ - GstClockTimeDiff max_diff; - gboolean ret = FALSE; - - /* Allow 3/4 a frame off */ - max_diff = (enc->info.fps_denominator * GST_SECOND * 3) / - (enc->info.fps_numerator * 4); - - if (timestamp != GST_CLOCK_TIME_NONE - && enc->expected_ts != GST_CLOCK_TIME_NONE) { - if ((GstClockTimeDiff) (timestamp - enc->expected_ts) > max_diff) { - GST_DEBUG_OBJECT (enc, "Incoming TS %" GST_TIME_FORMAT - " exceeds expected value %" GST_TIME_FORMAT - " by too much, marking discontinuity", - GST_TIME_ARGS (timestamp), GST_TIME_ARGS (enc->expected_ts)); - ret = TRUE; - } - } - - if (GST_CLOCK_TIME_IS_VALID (duration)) - enc->expected_ts = timestamp + duration; - else - enc->expected_ts = GST_CLOCK_TIME_NONE; - - return ret; -} - static void theora_enc_init_buffer (th_ycbcr_buffer buf, th_info * info, guint8 * data) { @@ -1081,8 +795,7 @@ theora_enc_read_multipass_cache (GstTheoraEnc * enc) { GstBuffer *cache_buf; const guint8 *cache_data; - gsize bytes_read = 0; - gint bytes_consumed = 0; + gsize bytes_read = 0, bytes_consumed = 0; GIOStatus stat = G_IO_STATUS_NORMAL; gboolean done = FALSE; @@ -1151,7 +864,7 @@ theora_enc_write_multipass_cache (GstTheoraEnc * enc, gboolean begin, } - if (stat == G_IO_STATUS_ERROR || bytes_read < 0) { + if (stat == G_IO_STATUS_ERROR || bytes_read < 0 || bytes_written < 0) { if (begin) { if (eos) GST_ELEMENT_WARNING (enc, RESOURCE, WRITE, (NULL), @@ -1173,305 +886,57 @@ theora_enc_write_multipass_cache (GstTheoraEnc * enc, gboolean begin, return TRUE; } -/** - * g_slice_free can't be used with g_queue_foreach. - * so we create new function with predefined GstClockTime size. - */ static void -theora_free_gstclocktime (gpointer mem) +theora_enc_reset_ts (GstTheoraEnc * enc, GstClockTime running_time, gint pfn) { - g_slice_free (GstClockTime, mem); + enc->granulepos_offset = + gst_util_uint64_scale (running_time, enc->fps_n, GST_SECOND * enc->fps_d); + enc->timestamp_offset = running_time; + enc->pfn_offset = pfn; } -static void -theora_timefifo_in (GstTheoraEnc * enc, const GstClockTime * timestamp) +static GstBuffer * +theora_enc_buffer_from_header_packet (GstTheoraEnc * enc, ogg_packet * packet) { - GstClockTime *ptr; + GstBuffer *outbuf; - if (!enc->t_queue) - enc->t_queue = g_queue_new (); + outbuf = gst_buffer_new_and_alloc (packet->bytes); + memcpy (GST_BUFFER_DATA (outbuf), packet->packet, packet->bytes); + GST_BUFFER_OFFSET (outbuf) = 0; + GST_BUFFER_OFFSET_END (outbuf) = 0; + GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE; - g_assert (enc->t_queue != NULL); - - ptr = g_slice_new (GstClockTime); - *ptr = *timestamp; - - g_queue_push_head (enc->t_queue, ptr); -} - -static GstClockTime -theora_timefifo_out (GstTheoraEnc * enc) -{ - GstClockTime ret, *ptr; - - g_assert (enc->t_queue != NULL); - - ptr = g_queue_pop_tail (enc->t_queue); - g_assert (ptr != NULL); - - ret = *ptr; - theora_free_gstclocktime (ptr); - - return ret; -} - -/** - * theora_timefifo_truncate - truncate the timestamp queue. - * After frame encoding we should have only one buffer for next time. - * The count of timestamps should be the same. If it is less, - * some thing really bad has happened. If it is bigger, encoder - * decided to return less then we ordered. - * TODO: for now we will just drop this timestamps. The better solution - * probably will be to recovery frames by recovery timestamps with - * last buffer. - */ -static void -theora_timefifo_truncate (GstTheoraEnc * enc) -{ - if (enc->dup_on_gap) { - guint length; - g_assert (enc->t_queue != NULL); - length = g_queue_get_length (enc->t_queue); - - if (length > 1) { - /* it is also not good if we have more then 1. */ - GST_DEBUG_OBJECT (enc, "Dropping %u time stamps", length - 1); - while (g_queue_get_length (enc->t_queue) > 1) { - theora_timefifo_out (enc); - } - } - } -} - -static void -theora_timefifo_free (GstTheoraEnc * enc) -{ - if (enc->t_queue) { - if (g_queue_get_length (enc->t_queue)) - g_queue_foreach (enc->t_queue, (GFunc) theora_free_gstclocktime, NULL); - g_queue_free (enc->t_queue); - enc->t_queue = NULL; - } - /* prevbuf makes no sense without timestamps, - * so clear it too. */ - if (enc->prevbuf) { - gst_buffer_unref (enc->prevbuf); - enc->prevbuf = NULL; - } - -} - -static void -theora_update_prevbuf (GstTheoraEnc * enc, GstBuffer * buffer) -{ - if (enc->prevbuf) { - gst_buffer_unref (enc->prevbuf); - enc->prevbuf = NULL; - } - enc->prevbuf = gst_buffer_ref (buffer); -} - -/** - * theora_enc_encode_and_push - encode buffer or queued previous buffer - * buffer - buffer to encode. If set to NULL it should encode only - * queued buffers and produce dups if needed. - */ - -static GstFlowReturn -theora_enc_encode_and_push (GstTheoraEnc * enc, ogg_packet op, - GstBuffer * buffer) -{ - GstFlowReturn ret; - th_ycbcr_buffer ycbcr; - gint res; - - if (enc->dup_on_gap) { - guint t_queue_length; - - if (enc->t_queue) - t_queue_length = g_queue_get_length (enc->t_queue); - else - t_queue_length = 0; - - if (buffer) { - GstClockTime timestamp = GST_BUFFER_TIMESTAMP (buffer); - - /* videorate can easy create 200 dup frames in one shot. - * In this case th_encode_ctl will just return TH_EINVAL - * and we will generate only one frame as result. - * To make us more bullet proof, make sure we have no - * more dup frames than keyframe interval. - */ - if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_GAP) && - enc->keyframe_force > t_queue_length) { - GST_DEBUG_OBJECT (enc, "Got GAP frame, queue as duplicate."); - - theora_timefifo_in (enc, ×tamp); - gst_buffer_unref (buffer); - return GST_FLOW_OK; - } else { - theora_timefifo_in (enc, ×tamp); - /* We should have one frame delay to create correct frame order. - * First time we got buffer, prevbuf should be empty. Nothing else - * should be done here. - */ - if (!enc->prevbuf) { - theora_update_prevbuf (enc, buffer); - gst_buffer_unref (buffer); - return GST_FLOW_OK; - } else { - theora_update_prevbuf (enc, buffer); - /* after theora_update_prevbuf t_queue_length was changed */ - t_queue_length++; - - if (t_queue_length > 2) { - /* now in t_queue_length should be two real buffers: current and - * previous. All others are timestamps of duplicate frames. */ - t_queue_length -= 2; - res = th_encode_ctl (enc->encoder, TH_ENCCTL_SET_DUP_COUNT, - &t_queue_length, sizeof (t_queue_length)); - if (res < 0) - GST_WARNING_OBJECT (enc, "Failed marking dups for last frame"); - } - } - } - } else { - /* if there is no buffer, then probably we got EOS or discontinuous. - * We need to encode every thing what was left in the queue - */ - GST_DEBUG_OBJECT (enc, "Encode collected buffers."); - if (t_queue_length > 1) { - t_queue_length--; - res = th_encode_ctl (enc->encoder, TH_ENCCTL_SET_DUP_COUNT, - &t_queue_length, sizeof (t_queue_length)); - if (res < 0) - GST_WARNING_OBJECT (enc, "Failed marking dups for last frame."); - } else { - GST_DEBUG_OBJECT (enc, "Prevbuffer is empty. Nothing to encode."); - return GST_FLOW_OK; - } - } - theora_enc_init_buffer (ycbcr, &enc->info, GST_BUFFER_DATA (enc->prevbuf)); - } else - theora_enc_init_buffer (ycbcr, &enc->info, GST_BUFFER_DATA (buffer)); - - /* check for buffer, it can be optional */ - if (enc->current_discont && buffer) { - GstClockTime timestamp = GST_BUFFER_TIMESTAMP (buffer); - GstClockTime running_time = - gst_segment_to_running_time (&enc->segment, GST_FORMAT_TIME, timestamp); - theora_enc_reset (enc); - enc->granulepos_offset = - gst_util_uint64_scale (running_time, enc->fps_n, - GST_SECOND * enc->fps_d); - enc->timestamp_offset = running_time; - enc->next_ts = 0; - enc->next_discont = TRUE; - } - - if (enc->multipass_cache_fd - && enc->multipass_mode == MULTIPASS_MODE_SECOND_PASS) { - if (!theora_enc_read_multipass_cache (enc)) { - ret = GST_FLOW_ERROR; - goto multipass_read_failed; - } - } -#ifdef TH_ENCCTL_SET_DUPLICATE_FLAG - if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_GAP)) { - th_encode_ctl (enc->encoder, TH_ENCCTL_SET_DUPLICATE_FLAG, NULL, 0); - } -#endif - - res = th_encode_ycbcr_in (enc->encoder, ycbcr); - /* none of the failure cases can happen here */ - g_assert (res == 0); - - if (enc->multipass_cache_fd - && enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS) { - if (!theora_enc_write_multipass_cache (enc, FALSE, FALSE)) { - ret = GST_FLOW_ERROR; - goto multipass_write_failed; - } - } - - ret = GST_FLOW_OK; - while (th_encode_packetout (enc->encoder, 0, &op)) { - GstClockTime next_time, duration; - GstClockTime timestamp = 0; - GST_DEBUG_OBJECT (enc, "encoded. granule:%" G_GINT64_FORMAT ", packet:%p, " - "bytes:%ld", (gint64) op.granulepos, op.packet, op.bytes); - - next_time = th_granule_time (enc->encoder, op.granulepos) * GST_SECOND; - duration = next_time - enc->next_ts; - - if (enc->dup_on_gap && !enc->current_discont) - timestamp = theora_timefifo_out (enc); - else - timestamp = GST_BUFFER_TIMESTAMP (buffer); - - ret = theora_push_packet (enc, &op, timestamp, enc->next_ts, duration); - - enc->next_ts = next_time; - if (ret != GST_FLOW_OK) { - theora_timefifo_truncate (enc); - goto data_push; - } - } - - theora_timefifo_truncate (enc); - if (buffer) - gst_buffer_unref (buffer); - enc->current_discont = FALSE; - - return ret; - - /* ERRORS */ -multipass_read_failed: - { - gst_buffer_unref (buffer); - return ret; - } -multipass_write_failed: - { - gst_buffer_unref (buffer); - return ret; - } -data_push: - { - gst_buffer_unref (buffer); - return ret; - } + GST_DEBUG ("created header packet buffer, %d bytes", + GST_BUFFER_SIZE (outbuf)); + return outbuf; } static GstFlowReturn -theora_enc_chain (GstPad * pad, GstBuffer * buffer) +theora_enc_handle_frame (GstVideoEncoder * benc, GstVideoCodecFrame * frame) { GstTheoraEnc *enc; ogg_packet op; - GstClockTime timestamp, duration, running_time; + GstClockTime timestamp, running_time; GstFlowReturn ret; gboolean force_keyframe; - enc = GST_THEORA_ENC (GST_PAD_PARENT (pad)); + enc = GST_THEORA_ENC (benc); /* we keep track of two timelines. - * - The timestamps from the incoming buffers, which we copy to the outgoing + * - The timestamps from the incomming buffers, which we copy to the outgoing * encoded buffers as-is. We need to do this as we simply forward the * newsegment events. * - The running_time of the buffers, which we use to construct the granulepos * in the packets. */ - timestamp = GST_BUFFER_TIMESTAMP (buffer); - duration = GST_BUFFER_DURATION (buffer); + timestamp = frame->pts; + /* incoming buffers are clipped, so this should be positive */ running_time = - gst_segment_to_running_time (&enc->segment, GST_FORMAT_TIME, timestamp); - if ((gint64) running_time < 0) { - GST_DEBUG_OBJECT (enc, "Dropping buffer, timestamp: %" GST_TIME_FORMAT, - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); - gst_buffer_unref (buffer); - return GST_FLOW_OK; - } + gst_segment_to_running_time (&GST_VIDEO_ENCODER_INPUT_SEGMENT (enc), + GST_FORMAT_TIME, timestamp); + g_return_val_if_fail (running_time >= 0 || timestamp < 0, GST_FLOW_ERROR); GST_OBJECT_LOCK (enc); if (enc->bitrate_changed) { @@ -1491,40 +956,16 @@ theora_enc_chain (GstPad * pad, GstBuffer * buffer) } /* see if we need to schedule a keyframe */ - force_keyframe = enc->force_keyframe; - enc->force_keyframe = FALSE; + force_keyframe = GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame); GST_OBJECT_UNLOCK (enc); - if (force_keyframe) { - GstClockTime stream_time; - GstStructure *s; - - stream_time = gst_segment_to_stream_time (&enc->segment, - GST_FORMAT_TIME, timestamp); - - s = gst_structure_new ("GstForceKeyUnit", - "timestamp", G_TYPE_UINT64, timestamp, - "stream-time", G_TYPE_UINT64, stream_time, - "running-time", G_TYPE_UINT64, running_time, NULL); - - theora_enc_force_keyframe (enc); - - gst_pad_push_event (enc->srcpad, - gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s)); - } - - /* make sure we copy the discont flag to the next outgoing buffer when it's - * set on the incoming buffer */ - if (GST_BUFFER_IS_DISCONT (buffer)) { - enc->next_discont = TRUE; - } - if (enc->packetno == 0) { /* no packets written yet, setup headers */ GstCaps *caps; GstBuffer *buf; - GSList *buffers = NULL; + GList *buffers = NULL; int result; + GstVideoCodecState *state; enc->granulepos_offset = 0; enc->timestamp_offset = 0; @@ -1543,168 +984,115 @@ theora_enc_chain (GstPad * pad, GstBuffer * buffer) while ((result = th_encode_flushheader (enc->encoder, &enc->comment, &op)) > 0) { - ret = - theora_buffer_from_packet (enc, &op, GST_CLOCK_TIME_NONE, - GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, &buf); - if (ret != GST_FLOW_OK) { - goto header_buffer_alloc; - } - buffers = g_slist_prepend (buffers, buf); + buf = theora_enc_buffer_from_header_packet (enc, &op); + buffers = g_list_prepend (buffers, buf); } if (result < 0) { - g_slist_foreach (buffers, (GFunc) gst_buffer_unref, NULL); - g_slist_free (buffers); + g_list_foreach (buffers, (GFunc) gst_buffer_unref, NULL); + g_list_free (buffers); goto encoder_disabled; } - buffers = g_slist_reverse (buffers); + buffers = g_list_reverse (buffers); /* mark buffers and put on caps */ - caps = gst_caps_new_simple ("video/x-theora", - "width", G_TYPE_INT, enc->width, - "height", G_TYPE_INT, enc->height, - "framerate", GST_TYPE_FRACTION, enc->fps_n, enc->fps_d, - "pixel-aspect-ratio", GST_TYPE_FRACTION, enc->par_n, enc->par_d, NULL); + caps = gst_caps_new_simple ("video/x-theora", NULL); caps = theora_set_header_on_caps (caps, buffers); - GST_DEBUG ("here are the caps: %" GST_PTR_FORMAT, caps); - gst_pad_set_caps (enc->srcpad, caps); + state = gst_video_encoder_set_output_state (benc, caps, enc->input_state); - g_slist_foreach (buffers, (GFunc) gst_buffer_set_caps, caps); + GST_DEBUG ("here are the caps: %" GST_PTR_FORMAT, state->caps); - gst_caps_unref (caps); + gst_video_codec_state_unref (state); - /* push out the header buffers */ - while (buffers) { - buf = buffers->data; - buffers = g_slist_delete_link (buffers, buffers); - if ((ret = theora_push_buffer (enc, buf)) != GST_FLOW_OK) { - g_slist_foreach (buffers, (GFunc) gst_buffer_unref, NULL); - g_slist_free (buffers); - goto header_push; + gst_video_encoder_set_headers (benc, buffers); + + theora_enc_reset_ts (enc, running_time, frame->presentation_frame_number); + } + + { + th_ycbcr_buffer ycbcr; + gint res; + + theora_enc_init_buffer (ycbcr, &enc->info, + GST_BUFFER_DATA (frame->input_buffer)); + + if (force_keyframe) { + theora_enc_reset (enc); + theora_enc_reset_ts (enc, running_time, frame->presentation_frame_number); + } + + if (enc->multipass_cache_fd + && enc->multipass_mode == MULTIPASS_MODE_SECOND_PASS) { + if (!theora_enc_read_multipass_cache (enc)) { + ret = GST_FLOW_ERROR; + goto multipass_read_failed; } } - enc->granulepos_offset = - gst_util_uint64_scale (running_time, enc->fps_n, - GST_SECOND * enc->fps_d); - enc->timestamp_offset = running_time; - enc->next_ts = 0; + res = th_encode_ycbcr_in (enc->encoder, ycbcr); + /* none of the failure cases can happen here */ + g_assert (res == 0); + + if (enc->multipass_cache_fd + && enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS) { + if (!theora_enc_write_multipass_cache (enc, FALSE, FALSE)) { + ret = GST_FLOW_ERROR; + goto multipass_write_failed; + } + } + + ret = GST_FLOW_OK; + while (th_encode_packetout (enc->encoder, 0, &op)) { + ret = theora_push_packet (enc, &op); + if (ret != GST_FLOW_OK) + goto data_push; + } } - enc->current_discont = theora_enc_is_discontinuous (enc, - running_time, duration); - - /* empty queue if discontinuous */ - if (enc->current_discont && enc->dup_on_gap) - theora_enc_encode_and_push (enc, op, NULL); - - ret = theora_enc_encode_and_push (enc, op, buffer); - return ret; /* ERRORS */ -header_buffer_alloc: +multipass_read_failed: { - gst_buffer_unref (buffer); return ret; } -header_push: +multipass_write_failed: + { + return ret; + } +data_push: { - gst_buffer_unref (buffer); return ret; } encoder_disabled: { GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), ("libtheora has been compiled with the encoder disabled")); - gst_buffer_unref (buffer); return GST_FLOW_ERROR; } } -static GstStateChangeReturn -theora_enc_change_state (GstElement * element, GstStateChange transition) +static gboolean +theora_enc_finish (GstVideoEncoder * benc) { GstTheoraEnc *enc; - GstStateChangeReturn ret; - th_enc_ctx *th_ctx; + ogg_packet op; - enc = GST_THEORA_ENC (element); + enc = GST_THEORA_ENC (benc); - switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - th_ctx = dummy_encode_ctx (); - if (!th_ctx) { - GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), - ("libtheora has been compiled with the encoder disabled")); - return GST_STATE_CHANGE_FAILURE; - } - th_encode_free (th_ctx); - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - GST_DEBUG_OBJECT (enc, "READY->PAUSED Initing theora state"); - th_info_init (&enc->info); - th_comment_init (&enc->comment); - enc->packetno = 0; - enc->force_keyframe = FALSE; - - if (enc->multipass_mode >= MULTIPASS_MODE_FIRST_PASS) { - GError *err = NULL; - - if (!enc->multipass_cache_file) { - ret = GST_STATE_CHANGE_FAILURE; - GST_ELEMENT_ERROR (enc, LIBRARY, SETTINGS, (NULL), (NULL)); - return ret; - } - enc->multipass_cache_fd = - g_io_channel_new_file (enc->multipass_cache_file, - (enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS ? "w" : "r"), - &err); - - if (enc->multipass_mode == MULTIPASS_MODE_SECOND_PASS) - enc->multipass_cache_adapter = gst_adapter_new (); - - if (!enc->multipass_cache_fd) { - ret = GST_STATE_CHANGE_FAILURE; - GST_ELEMENT_ERROR (enc, RESOURCE, OPEN_READ, (NULL), - ("Failed to open multipass cache file: %s", err->message)); - g_error_free (err); - return ret; - } - - g_io_channel_set_encoding (enc->multipass_cache_fd, NULL, NULL); - } - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - break; - default: - break; + if (enc->initialised) { + /* push last packet with eos flag, should not be called */ + while (th_encode_packetout (enc->encoder, 1, &op)) { + theora_push_packet (enc, &op); + } } + if (enc->initialised && enc->multipass_cache_fd + && enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS) + theora_enc_write_multipass_cache (enc, TRUE, TRUE); - ret = parent_class->change_state (element, transition); + theora_enc_clear_multipass_cache (enc); - switch (transition) { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - GST_DEBUG_OBJECT (enc, "PAUSED->READY Clearing theora state"); - if (enc->encoder) { - th_encode_free (enc->encoder); - enc->encoder = NULL; - } - th_comment_clear (&enc->comment); - th_info_clear (&enc->info); - - theora_enc_clear (enc); - enc->initialised = FALSE; - break; - case GST_STATE_CHANGE_READY_TO_NULL: - break; - default: - break; - } - - return ret; + return TRUE; } static void @@ -1722,18 +1110,17 @@ theora_enc_set_property (GObject * object, guint prop_id, case PROP_NOISE_SENSITIVITY: case PROP_SHARPNESS: /* kept for API compat, but ignored */ - GST_WARNING_OBJECT (object, "Obsolete property '%s' ignored", - pspec->name); break; case PROP_BITRATE: GST_OBJECT_LOCK (enc); enc->video_bitrate = g_value_get_int (value) * 1000; + enc->video_quality = 0; enc->bitrate_changed = TRUE; GST_OBJECT_UNLOCK (enc); break; case PROP_QUALITY: GST_OBJECT_LOCK (enc); - if (GST_STATE (enc) >= GST_STATE_PAUSED && enc->video_bitrate > 0) { + if (GST_STATE (enc) >= GST_STATE_PAUSED && enc->video_quality == 0) { GST_WARNING_OBJECT (object, "Can't change from bitrate to quality mode" " while playing"); } else { @@ -1754,10 +1141,6 @@ theora_enc_set_property (GObject * object, guint prop_id, break; case PROP_SPEEDLEVEL: enc->speed_level = g_value_get_int (value); - if (enc->encoder) { - th_encode_ctl (enc->encoder, TH_ENCCTL_SET_SPLEVEL, &enc->speed_level, - sizeof (enc->speed_level)); - } break; case PROP_VP3_COMPATIBLE: enc->vp3_compatible = g_value_get_boolean (value); @@ -1780,9 +1163,6 @@ theora_enc_set_property (GObject * object, guint prop_id, case PROP_MULTIPASS_MODE: enc->multipass_mode = g_value_get_enum (value); break; - case PROP_DUP_ON_GAP: - enc->dup_on_gap = g_value_get_boolean (value); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1860,11 +1240,15 @@ theora_enc_get_property (GObject * object, guint prop_id, case PROP_MULTIPASS_MODE: g_value_set_enum (value, enc->multipass_mode); break; - case PROP_DUP_ON_GAP: - g_value_set_boolean (value, enc->dup_on_gap); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } + +gboolean +gst_theora_enc_register (GstPlugin * plugin) +{ + return gst_element_register (plugin, "theoraenc", + GST_RANK_PRIMARY, GST_TYPE_THEORA_ENC); +} diff --git a/ext/theora/gsttheoraenc.h b/ext/theora/gsttheoraenc.h index fdb43ffe7d..92d943d18f 100644 --- a/ext/theora/gsttheoraenc.h +++ b/ext/theora/gsttheoraenc.h @@ -1,5 +1,8 @@ /* GStreamer * Copyright (C) 2004 Wim Taymans + * Copyright (c) 2012 Collabora Ltd. + * Author : Edward Hervey + * Author : Mark Nauwelaerts * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -22,6 +25,7 @@ #include #include +#include #include G_BEGIN_DECLS @@ -77,12 +81,7 @@ typedef enum */ struct _GstTheoraEnc { - GstElement element; - - GstPad *sinkpad; - GstPad *srcpad; - - GstSegment segment; + GstVideoEncoder element; ogg_stream_state to; @@ -99,21 +98,16 @@ struct _GstTheoraEnc gint keyframe_freq; gint keyframe_force; - gint info_width, info_height; + GstVideoCodecState *input_state; + gint width, height; gint fps_n, fps_d; - gint par_n, par_d; - GstClockTime next_ts; - - GstClockTime expected_ts; - gboolean next_discont; - - gboolean force_keyframe; guint packetno; guint64 bytes_out; guint64 granulepos_offset; guint64 timestamp_offset; + guint64 pfn_offset; gint speed_level; gboolean vp3_compatible; @@ -122,13 +116,6 @@ struct _GstTheoraEnc gboolean cap_underflow; int rate_buffer; - /* variables for dup-on-gap */ - gboolean dup_on_gap; - gboolean current_discont; - GstBuffer *prevbuf; - GQueue *t_queue; - /* end dup-on-gap */ - GstTheoraEncMultipassMode multipass_mode; GIOChannel *multipass_cache_fd; GstAdapter *multipass_cache_adapter; @@ -137,10 +124,11 @@ struct _GstTheoraEnc struct _GstTheoraEncClass { - GstElementClass parent_class; + GstVideoEncoderClass parent_class; }; GType gst_theora_enc_get_type (void); +gboolean gst_theora_enc_register (GstPlugin * plugin); G_END_DECLS From a744c9898572ca150a418504033875245cc719d0 Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Tue, 24 Apr 2012 15:07:31 +0200 Subject: [PATCH 11/14] videodecoder: Detect buffers inputted with DTS Some container formats (like AVI) set DTS on the buffers instead of PTS. We detect this by: * detecting if input timestamps are non-increasing * detecting if the order the frames come out is the same as the order they were inputted (meaning the implementation is reordering frames). If the decoder reorders frames, but input buffer timestamps were not reordered, that means the buffers has DTS and not PTS as their timestamp. If this is the case, we use set the PTS of the outgoing frames in the same order as they were given to the decoder. This fixes the issue for any decoder using this base class (yay). --- gst-libs/gst/video/gstvideodecoder.c | 95 +++++++++++++++++++++------- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/gst-libs/gst/video/gstvideodecoder.c b/gst-libs/gst/video/gstvideodecoder.c index 4b71cb1b04..a9de9f1f23 100644 --- a/gst-libs/gst/video/gstvideodecoder.c +++ b/gst-libs/gst/video/gstvideodecoder.c @@ -135,7 +135,6 @@ * decoded). * * Add a flag/boolean for decoders that require keyframes, so the base * class can automatically discard non-keyframes before one has arrived - * * Detect reordered frame/timestamps and fix the pts/dts * * Support for GstIndex (or shall we not care ?) * * Calculate actual latency based on input/output timestamp/frame_number * and if it exceeds the recorded one, save it and emit a GST_MESSAGE_LATENCY @@ -159,6 +158,9 @@ GST_DEBUG_CATEGORY (videodecoder_debug); (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_VIDEO_DECODER, \ GstVideoDecoderPrivate)) +/* FIXME : I really hope we never see streams that go over this */ +#define MAX_DTS_PTS_REORDER_DEPTH 36 + struct _GstVideoDecoderPrivate { /* FIXME introduce a context ? */ @@ -196,8 +198,18 @@ struct _GstVideoDecoderPrivate /* combine to yield (presentation) ts */ GstClockTime timestamp_offset; - /* last outgoing ts */ - GstClockTime last_timestamp; + /* last incoming and outgoing ts */ + GstClockTime last_timestamp_in; + GstClockTime last_timestamp_out; + + /* last outgoing system frame number (used to detect reordering) */ + guint last_out_frame_number; + + /* TRUE if input timestamp is not monotonically increasing */ + gboolean reordered_input; + + /* TRUE if frames come out in a different order than they were inputted */ + gboolean reordered_output; /* reverse playback */ /* collect input */ @@ -242,6 +254,11 @@ struct _GstVideoDecoderPrivate gint64 min_latency; gint64 max_latency; + + /* Handle incoming buffers with DTS instead of PTS as timestamps */ + GstClockTime incoming_timestamps[MAX_DTS_PTS_REORDER_DEPTH]; + guint reorder_idx_in; + guint reorder_idx_out; }; static void gst_video_decoder_finalize (GObject * object); @@ -1068,7 +1085,7 @@ gst_video_decoder_src_query (GstPad * pad, GstQuery * query) } /* we start from the last seen time */ - time = dec->priv->last_timestamp; + time = dec->priv->last_timestamp_out; /* correct for the segment values */ time = gst_segment_to_stream_time (&dec->output_segment, GST_FORMAT_TIME, time); @@ -1316,7 +1333,11 @@ gst_video_decoder_reset (GstVideoDecoder * decoder, gboolean full) priv->discont = TRUE; priv->timestamp_offset = GST_CLOCK_TIME_NONE; - priv->last_timestamp = GST_CLOCK_TIME_NONE; + priv->last_timestamp_in = GST_CLOCK_TIME_NONE; + priv->last_timestamp_out = GST_CLOCK_TIME_NONE; + priv->last_out_frame_number = 0; + priv->reordered_output = FALSE; + priv->reordered_input = FALSE; priv->input_offset = 0; priv->frame_offset = 0; @@ -1348,6 +1369,8 @@ gst_video_decoder_reset (GstVideoDecoder * decoder, gboolean full) priv->earliest_time = GST_CLOCK_TIME_NONE; priv->proportion = 0.5; + priv->reorder_idx_out = priv->reorder_idx_in = 0; + GST_VIDEO_DECODER_STREAM_UNLOCK (decoder); } @@ -1743,6 +1766,7 @@ gst_video_decoder_prepare_finish_frame (GstVideoDecoder * { GstVideoDecoderPrivate *priv = decoder->priv; GList *l, *events = NULL; + GstClockTime reorder_pts; #ifndef GST_DISABLE_GST_DEBUG GST_LOG_OBJECT (decoder, "n %d in %d out %d", @@ -1751,9 +1775,22 @@ gst_video_decoder_prepare_finish_frame (GstVideoDecoder * gst_adapter_available (priv->output_adapter)); #endif + reorder_pts = priv->incoming_timestamps[priv->reorder_idx_out]; + priv->reorder_idx_out = + (priv->reorder_idx_out + 1) % MAX_DTS_PTS_REORDER_DEPTH; + + if (!priv->reordered_output && frame->system_frame_number && + frame->system_frame_number != (priv->last_out_frame_number + 1)) { + GST_DEBUG_OBJECT (decoder, "Detected reordered output"); + priv->reordered_output = TRUE; + } + GST_LOG_OBJECT (decoder, - "finish frame sync=%d pts=%" GST_TIME_FORMAT, - GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame), GST_TIME_ARGS (frame->pts)); + "finish frame (#%d) sync:%d pts:%" GST_TIME_FORMAT " dts:%" + GST_TIME_FORMAT " reorder_pts:%" GST_TIME_FORMAT, + frame->system_frame_number, GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame), + GST_TIME_ARGS (frame->pts), GST_TIME_ARGS (frame->dts), + GST_TIME_ARGS (reorder_pts)); /* Push all pending events that arrived before this frame */ for (l = priv->frames; l; l = l->next) { @@ -1776,14 +1813,9 @@ gst_video_decoder_prepare_finish_frame (GstVideoDecoder * /* Check if the data should not be displayed. For example altref/invisible * frame in vp8. In this case we should not update the timestamps. */ - if (GST_VIDEO_CODEC_FRAME_IS_DECODE_ONLY (frame)) + if (GST_VIDEO_CODEC_FRAME_IS_DECODE_ONLY (frame) || !frame->output_buffer) return; - /* If the frame is meant to be outputted but we don't have an output buffer - * we have a problem :) */ - if (G_UNLIKELY (frame->output_buffer == NULL)) - goto no_output_buffer; - if (GST_CLOCK_TIME_IS_VALID (frame->pts)) { if (frame->pts != priv->timestamp_offset) { GST_DEBUG_OBJECT (decoder, @@ -1807,32 +1839,38 @@ gst_video_decoder_prepare_finish_frame (GstVideoDecoder * } } } + if (frame->pts == GST_CLOCK_TIME_NONE) { frame->pts = gst_video_decoder_get_timestamp (decoder, frame->decode_frame_number); frame->duration = GST_CLOCK_TIME_NONE; } + if (frame->duration == GST_CLOCK_TIME_NONE) { frame->duration = gst_video_decoder_get_frame_duration (decoder, frame); } - if (GST_CLOCK_TIME_IS_VALID (priv->last_timestamp)) { - if (frame->pts < priv->last_timestamp) { + /* Fix buffers that came in with DTS and were reordered */ + if (!priv->reordered_input && priv->reordered_output) { + GST_DEBUG_OBJECT (decoder, + "Correcting PTS, input buffers had DTS on their timestamps"); + frame->pts = reorder_pts; + } + + if (GST_CLOCK_TIME_IS_VALID (priv->last_timestamp_out)) { + if (frame->pts < priv->last_timestamp_out) { GST_WARNING_OBJECT (decoder, "decreasing timestamp (%" GST_TIME_FORMAT " < %" GST_TIME_FORMAT ")", - GST_TIME_ARGS (frame->pts), GST_TIME_ARGS (priv->last_timestamp)); + GST_TIME_ARGS (frame->pts), GST_TIME_ARGS (priv->last_timestamp_out)); + frame->pts = reorder_pts; } } - priv->last_timestamp = frame->pts; + + priv->last_timestamp_out = frame->pts; + priv->last_out_frame_number = frame->system_frame_number; return; - - /* ERRORS */ -no_output_buffer: - { - GST_ERROR_OBJECT (decoder, "No buffer to output !"); - } } static void @@ -2211,6 +2249,17 @@ gst_video_decoder_have_frame_2 (GstVideoDecoder * decoder) gst_segment_to_running_time (&decoder->input_segment, GST_FORMAT_TIME, frame->pts); + /* Store pts */ + if (GST_CLOCK_TIME_IS_VALID (frame->pts) + && GST_CLOCK_TIME_IS_VALID (priv->last_timestamp_in) + && frame->pts < priv->last_timestamp_in) { + GST_DEBUG_OBJECT (decoder, "Incoming timestamps are out of order"); + priv->reordered_input = TRUE; + } + priv->last_timestamp_in = frame->pts; + priv->incoming_timestamps[priv->reorder_idx_in] = frame->pts; + priv->reorder_idx_in = (priv->reorder_idx_in + 1) % MAX_DTS_PTS_REORDER_DEPTH; + /* do something with frame */ ret = decoder_class->handle_frame (decoder, frame); if (ret != GST_FLOW_OK) From 4f1e7eec40cfed167f65eeb8b1739fbf4ef4aa10 Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Tue, 24 Apr 2012 15:27:25 +0200 Subject: [PATCH 12/14] video: Fix gst_video_info_to_caps And use the 0.10 caps style --- gst-libs/gst/video/video.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/gst-libs/gst/video/video.c b/gst-libs/gst/video/video.c index 9231d01035..08df825c29 100644 --- a/gst-libs/gst/video/video.c +++ b/gst-libs/gst/video/video.c @@ -1578,32 +1578,18 @@ GstCaps * gst_video_info_to_caps (GstVideoInfo * info) { GstCaps *caps; - const gchar *capsname = NULL; g_return_val_if_fail (info != NULL, NULL); g_return_val_if_fail (info->finfo != NULL, NULL); g_return_val_if_fail (info->finfo->format != GST_VIDEO_FORMAT_UNKNOWN, NULL); - if (GST_VIDEO_INFO_IS_YUV (info)) - capsname = "video/x-raw-yuv"; - else if (GST_VIDEO_INFO_IS_RGB (info)) - capsname = "video/x-raw-rgb"; - else if (GST_VIDEO_INFO_IS_GRAY (info)) - capsname = "video/x-raw-gray"; + caps = gst_video_format_new_caps_raw (GST_VIDEO_INFO_FORMAT (info)); - caps = gst_caps_new_simple (capsname, + gst_caps_set_simple (caps, "width", G_TYPE_INT, info->width, "height", G_TYPE_INT, info->height, "pixel-aspect-ratio", GST_TYPE_FRACTION, info->par_n, info->par_d, NULL); - if (GST_VIDEO_INFO_IS_YUV (info)) - gst_caps_set_simple (caps, "format", GST_TYPE_FOURCC, - gst_video_format_to_fourcc (info->finfo->format), NULL); - else if (GST_VIDEO_INFO_IS_RGB (info) || GST_VIDEO_INFO_IS_GRAY (info)) - gst_caps_set_simple (caps, "depth", G_TYPE_INT, - info->finfo->bits * GST_VIDEO_INFO_N_COMPONENTS (info), NULL); - - gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN, GST_VIDEO_INFO_IS_INTERLACED (info), NULL); From 416029a006ec822f63a6206591f3392983836c08 Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Tue, 24 Apr 2012 16:32:39 +0200 Subject: [PATCH 13/14] tests: Remove theoraenc discont test It should be fixed differently, see https://bugzilla.gnome.org/show_bug.cgi?id=663262 --- tests/check/pipelines/theoraenc.c | 114 ------------------------------ 1 file changed, 114 deletions(-) diff --git a/tests/check/pipelines/theoraenc.c b/tests/check/pipelines/theoraenc.c index 3b101d0bf0..d230707ce8 100644 --- a/tests/check/pipelines/theoraenc.c +++ b/tests/check/pipelines/theoraenc.c @@ -308,119 +308,6 @@ GST_START_TEST (test_continuity) GST_END_TEST; -static gboolean -drop_second_data_buffer (GstPad * droppad, GstBuffer * buffer, gpointer unused) -{ - return !(GST_BUFFER_OFFSET (buffer) == 1); -} - -GST_START_TEST (test_discontinuity) -{ - GstElement *bin; - GstPad *pad, *droppad; - gchar *pipe_str; - GstBuffer *buffer; - GError *error = NULL; - guint drop_id; - - pipe_str = g_strdup_printf ("videotestsrc num-buffers=10" - " ! video/x-raw-yuv,format=(fourcc)I420,framerate=10/1" - " ! theoraenc ! fakesink name=fs0"); - - bin = gst_parse_launch (pipe_str, &error); - fail_unless (bin != NULL, "Error parsing pipeline: %s", - error ? error->message : "(invalid error)"); - g_free (pipe_str); - - /* the plan: same as test_continuity, but dropping a buffer and seeing if - theoraenc correctly notes the discontinuity */ - - /* get the pad to use to drop buffers */ - { - GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "theoraenc0"); - - fail_unless (sink != NULL, "Could not get theoraenc out of bin"); - droppad = gst_element_get_static_pad (sink, "sink"); - fail_unless (droppad != NULL, "Could not get pad out of theoraenc"); - gst_object_unref (sink); - } - - /* get the pad */ - { - GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0"); - - fail_unless (sink != NULL, "Could not get fakesink out of bin"); - pad = gst_element_get_static_pad (sink, "sink"); - fail_unless (pad != NULL, "Could not get pad out of fakesink"); - gst_object_unref (sink); - } - - drop_id = gst_pad_add_buffer_probe (droppad, - G_CALLBACK (drop_second_data_buffer), NULL); - gst_buffer_straw_start_pipeline (bin, pad); - - /* header packets should have timestamp == NONE, granulepos 0 */ - buffer = gst_buffer_straw_get_buffer (bin, pad); - check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE); - check_buffer_duration (buffer, GST_CLOCK_TIME_NONE); - check_buffer_granulepos (buffer, 0); - check_buffer_is_header (buffer, TRUE); - gst_buffer_unref (buffer); - - buffer = gst_buffer_straw_get_buffer (bin, pad); - check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE); - check_buffer_duration (buffer, GST_CLOCK_TIME_NONE); - check_buffer_granulepos (buffer, 0); - check_buffer_is_header (buffer, TRUE); - gst_buffer_unref (buffer); - - buffer = gst_buffer_straw_get_buffer (bin, pad); - check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE); - check_buffer_duration (buffer, GST_CLOCK_TIME_NONE); - check_buffer_granulepos (buffer, 0); - check_buffer_is_header (buffer, TRUE); - gst_buffer_unref (buffer); - - { - buffer = gst_buffer_straw_get_buffer (bin, pad); - check_buffer_timestamp (buffer, 0); - /* plain division because I know the answer is exact */ - check_buffer_duration (buffer, GST_SECOND / 10); - check_buffer_granulepos (buffer, 1 << GRANULEPOS_SHIFT); - check_buffer_is_header (buffer, FALSE); - fail_if (GST_BUFFER_IS_DISCONT (buffer), "expected continuous buffer yo"); - gst_buffer_unref (buffer); - - /* check discontinuity with the next buffer */ - buffer = gst_buffer_straw_get_buffer (bin, pad); - check_buffer_duration (buffer, GST_SECOND / 10); - /* After a discont, we'll always get a keyframe, so this one should be - * 3< Date: Tue, 24 Apr 2012 16:38:31 +0200 Subject: [PATCH 14/14] win32: Update .defs file --- win32/common/libgstvideo.def | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/win32/common/libgstvideo.def b/win32/common/libgstvideo.def index 213118daf0..8d7a63032a 100644 --- a/win32/common/libgstvideo.def +++ b/win32/common/libgstvideo.def @@ -3,6 +3,7 @@ EXPORTS gst_video_buffer_get_overlay_composition gst_video_buffer_set_overlay_composition gst_video_calculate_display_ratio + gst_video_chroma_site_get_type gst_video_codec_frame_get_type gst_video_codec_frame_ref gst_video_codec_frame_set_hook @@ -10,6 +11,11 @@ EXPORTS gst_video_codec_state_get_type gst_video_codec_state_ref gst_video_codec_state_unref + gst_video_color_matrix_get_type + gst_video_color_primaries_get_type + gst_video_color_range_get_type + gst_video_colorimetry_from_string + gst_video_colorimetry_matches gst_video_convert_frame gst_video_convert_frame_async gst_video_decoder_add_to_frame @@ -51,12 +57,16 @@ EXPORTS gst_video_event_parse_still_frame gst_video_event_parse_upstream_force_key_unit gst_video_filter_get_type + gst_video_flags_get_type gst_video_format_convert + gst_video_format_flags_get_type gst_video_format_from_fourcc + gst_video_format_from_masks gst_video_format_get_component_depth gst_video_format_get_component_height gst_video_format_get_component_offset gst_video_format_get_component_width + gst_video_format_get_info gst_video_format_get_pixel_stride gst_video_format_get_row_stride gst_video_format_get_size @@ -71,9 +81,16 @@ EXPORTS gst_video_format_parse_caps gst_video_format_parse_caps_interlaced gst_video_format_to_fourcc + gst_video_format_to_string gst_video_frame_rate gst_video_get_size gst_video_get_size_from_caps + gst_video_info_from_caps + gst_video_info_init + gst_video_info_is_equal + gst_video_info_set_format + gst_video_info_to_caps + gst_video_interlace_mode_get_type gst_video_overlay_composition_add_rectangle gst_video_overlay_composition_blend gst_video_overlay_composition_copy @@ -85,12 +102,14 @@ EXPORTS gst_video_overlay_composition_new gst_video_overlay_rectangle_copy gst_video_overlay_rectangle_get_flags + gst_video_overlay_rectangle_get_global_alpha gst_video_overlay_rectangle_get_pixels_argb gst_video_overlay_rectangle_get_pixels_unscaled_argb gst_video_overlay_rectangle_get_render_rectangle gst_video_overlay_rectangle_get_seqnum gst_video_overlay_rectangle_get_type gst_video_overlay_rectangle_new_argb + gst_video_overlay_rectangle_set_global_alpha gst_video_overlay_rectangle_set_render_rectangle gst_video_parse_caps_chroma_site gst_video_parse_caps_color_matrix @@ -99,3 +118,4 @@ EXPORTS gst_video_parse_caps_pixel_aspect_ratio gst_video_sink_center_rect gst_video_sink_get_type + gst_video_transfer_function_get_type