diff --git a/gst/videoparsers/Makefile.am b/gst/videoparsers/Makefile.am index 072e001524..0ff15e7d42 100644 --- a/gst/videoparsers/Makefile.am +++ b/gst/videoparsers/Makefile.am @@ -1,9 +1,12 @@ plugin_LTLIBRARIES = libgsth263parse.la -libgsth263parse_la_SOURCES = plugin.c h263parse.c gsth263parse.c gstbaseparse.c +libgsth263parse_la_SOURCES = plugin.c \ + h263parse.c gsth263parse.c gsth264parse.c h264parse.c gstbaseparse.c libgsth263parse_la_CFLAGS = $(GST_CFLAGS) libgsth263parse_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) libgsth263parse_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgsth263parse_la_LIBTOOLFLAGS = --tag=disable-static -noinst_HEADERS = gsth263parse.h h263parse.h gstbaseparse.h +noinst_HEADERS = gsth263parse.h h263parse.h \ + gsth264parse.h gsth264parse.h \ + gstbaseparse.h diff --git a/gst/videoparsers/gsth264parse.c b/gst/videoparsers/gsth264parse.c new file mode 100644 index 0000000000..799303ea35 --- /dev/null +++ b/gst/videoparsers/gsth264parse.c @@ -0,0 +1,1150 @@ +/* GStreamer H.264 Parser + * Copyright (C) <2010> Mark Nauwelaerts + * Copyright (C) <2010> Collabora Multimedia + * Copyright (C) <2010> Nokia Corporation + * + * 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 +#include +#include "gsth264parse.h" + +#include + +GST_DEBUG_CATEGORY (h264_parse_debug); +#define GST_CAT_DEFAULT h264_parse_debug + +#define DEFAULT_SPLIT_PACKETIZED FALSE +#define DEFAULT_CONFIG_INTERVAL (0) + +enum +{ + PROP_0, + PROP_SPLIT_PACKETIZED, + PROP_CONFIG_INTERVAL, + PROP_LAST +}; + +enum +{ + GST_H264_PARSE_FORMAT_NONE, + GST_H264_PARSE_FORMAT_AVC, + GST_H264_PARSE_FORMAT_BYTE +}; + +enum +{ + GST_H264_PARSE_ALIGN_NONE = 0, + GST_H264_PARSE_ALIGN_NAL, + GST_H264_PARSE_ALIGN_AU +}; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-h264, parsed = (boolean) false")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-h264, parsed = (boolean) true")); + +GST_BOILERPLATE (GstH264Parse, gst_h264_parse, GstElement, GST_TYPE_BASE_PARSE); + +static void gst_h264_parse_finalize (GObject * object); + +static gboolean gst_h264_parse_start (GstBaseParse * parse); +static gboolean gst_h264_parse_stop (GstBaseParse * parse); +static gboolean gst_h264_parse_check_valid_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, guint * framesize, gint * skipsize); +static GstFlowReturn gst_h264_parse_parse_frame (GstBaseParse * parse, + GstBaseParseFrame * frame); +static GstFlowReturn gst_h264_parse_pre_push_frame (GstBaseParse * parse, + GstBaseParseFrame * frame); + +static void gst_h264_parse_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_h264_parse_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean gst_h264_parse_set_caps (GstBaseParse * parse, GstCaps * caps); +static GstFlowReturn gst_h264_parse_chain (GstPad * pad, GstBuffer * buffer); + +static void +gst_h264_parse_base_init (gpointer g_class) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&srctemplate)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sinktemplate)); + + gst_element_class_set_details_simple (gstelement_class, "H.264 parser", + "Codec/Parser/Video", + "Parses H.264 streams", + "Mark Nauwelaerts "); + + GST_DEBUG_CATEGORY_INIT (h264_parse_debug, "h264parse", 0, "h264 parser"); +} + +static void +gst_h264_parse_class_init (GstH264ParseClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass); + + gobject_class->finalize = gst_h264_parse_finalize; + gobject_class->set_property = gst_h264_parse_set_property; + gobject_class->get_property = gst_h264_parse_get_property; + + g_object_class_install_property (gobject_class, PROP_SPLIT_PACKETIZED, + g_param_spec_boolean ("split-packetized", "Split packetized", + "Split NAL units of packetized streams", DEFAULT_SPLIT_PACKETIZED, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_CONFIG_INTERVAL, + g_param_spec_uint ("config-interval", + "SPS PPS Send Interval", + "Send SPS and PPS Insertion Interval in seconds (sprop parameter sets " + "will be multiplexed in the data stream when detected.) (0 = disabled)", + 0, 3600, DEFAULT_CONFIG_INTERVAL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + /* Override BaseParse vfuncs */ + parse_class->start = GST_DEBUG_FUNCPTR (gst_h264_parse_start); + parse_class->stop = GST_DEBUG_FUNCPTR (gst_h264_parse_stop); + parse_class->check_valid_frame = + GST_DEBUG_FUNCPTR (gst_h264_parse_check_valid_frame); + parse_class->parse_frame = GST_DEBUG_FUNCPTR (gst_h264_parse_parse_frame); + parse_class->pre_push_frame = + GST_DEBUG_FUNCPTR (gst_h264_parse_pre_push_frame); + parse_class->set_sink_caps = GST_DEBUG_FUNCPTR (gst_h264_parse_set_caps); +} + +static void +gst_h264_parse_init (GstH264Parse * h264parse, GstH264ParseClass * g_class) +{ + h264parse->frame_out = gst_adapter_new (); + + /* retrieve and intercept baseparse. + * Quite HACKish, but fairly OK since it is needed to perform avc packet + * splitting, which is the penultimate de-parsing */ + h264parse->parse_chain = + GST_PAD_CHAINFUNC (GST_BASE_PARSE_SINK_PAD (h264parse)); + gst_pad_set_chain_function (GST_BASE_PARSE_SINK_PAD (h264parse), + gst_h264_parse_chain); +} + + +static void +gst_h264_parse_finalize (GObject * object) +{ + GstH264Parse *h264parse = GST_H264_PARSE (object); + + g_object_unref (h264parse->frame_out); +} + +static void +gst_h264_parse_reset_frame (GstH264Parse * h264parse) +{ + /* done parsing; reset state */ + h264parse->last_nal_pos = 0; + h264parse->next_sc_pos = 0; + h264parse->picture_start = FALSE; + h264parse->update_caps = FALSE; + h264parse->idr_pos = -1; + h264parse->keyframe = FALSE; + h264parse->frame_start = FALSE; +} + +static void +gst_h264_parse_reset (GstH264Parse * h264parse) +{ + h264parse->width = 0; + h264parse->height = 0; + h264parse->fps_num = 0; + h264parse->fps_den = 0; + gst_buffer_replace (&h264parse->codec_data, NULL); + h264parse->nal_length_size = 4; + h264parse->packetized = FALSE; + + h264parse->align = GST_H264_PARSE_ALIGN_NONE; + h264parse->format = GST_H264_PARSE_FORMAT_NONE; + + h264parse->last_report = GST_CLOCK_TIME_NONE; + h264parse->push_codec = FALSE; + + gst_h264_parse_reset_frame (h264parse); +} + +static gboolean +gst_h264_parse_start (GstBaseParse * parse) +{ + GstH264Parse *h264parse = GST_H264_PARSE (parse); + + GST_DEBUG ("Start"); + gst_h264_parse_reset (h264parse); + + gst_h264_params_create (&h264parse->params, GST_ELEMENT (h264parse)); + + gst_base_parse_set_min_frame_size (parse, 512); + + return TRUE; +} + +static gboolean +gst_h264_parse_stop (GstBaseParse * parse) +{ + GstH264Parse *h264parse = GST_H264_PARSE (parse); + + GST_DEBUG ("Stop"); + gst_h264_parse_reset (h264parse); + + gst_h264_params_free (h264parse->params); + h264parse->params = NULL; + + return TRUE; +} + +static const gchar * +gst_h264_parse_get_string (GstH264Parse * parse, gboolean format, gint code) +{ + if (format) { + switch (code) { + case GST_H264_PARSE_FORMAT_AVC: + return "avc"; + case GST_H264_PARSE_FORMAT_BYTE: + return "byte-stream"; + default: + return "none"; + } + } else { + switch (code) { + case GST_H264_PARSE_ALIGN_NAL: + return "nal"; + case GST_H264_PARSE_ALIGN_AU: + return "au"; + default: + return "none"; + } + } +} + +/* check downstream caps to configure format and alignment */ +static void +gst_h264_parse_negotiate (GstH264Parse * h264parse) +{ + GstCaps *caps; + guint format = GST_H264_PARSE_FORMAT_NONE; + guint align = GST_H264_PARSE_ALIGN_NONE; + + caps = gst_pad_get_allowed_caps (GST_BASE_PARSE_SRC_PAD (h264parse)); + GST_DEBUG_OBJECT (h264parse, "allowed caps: %" GST_PTR_FORMAT, caps); + + if (caps && gst_caps_get_size (caps) > 0) { + GstStructure *s = gst_caps_get_structure (caps, 0); + const gchar *str = NULL; + + if ((str = gst_structure_get_string (s, "stream-format"))) { + if (strcmp (str, "avc") == 0) { + format = GST_H264_PARSE_FORMAT_AVC; + } else if (strcmp (str, "byte-stream") == 0) { + format = GST_H264_PARSE_FORMAT_BYTE; + } else { + GST_DEBUG_OBJECT (h264parse, "unknown stream-format: %s", str); + } + } + + if ((str = gst_structure_get_string (s, "alignment"))) { + if (strcmp (str, "au") == 0) { + align = GST_H264_PARSE_ALIGN_AU; + } else if (strcmp (str, "nal") == 0) { + align = GST_H264_PARSE_ALIGN_NAL; + } else { + GST_DEBUG_OBJECT (h264parse, "unknown alignment: %s", str); + } + } + } + + if (caps) + gst_caps_unref (caps); + + /* default */ + if (!format) + format = GST_H264_PARSE_FORMAT_BYTE; + if (!align) + align = GST_H264_PARSE_ALIGN_AU; + + GST_DEBUG_OBJECT (h264parse, "selected format %s, alignment %s", + gst_h264_parse_get_string (h264parse, TRUE, format), + gst_h264_parse_get_string (h264parse, FALSE, align)); + + h264parse->format = format; + h264parse->align = align; +} + +static GstBuffer * +gst_h264_parse_wrap_nal (GstH264Parse * h264parse, guint format, guint8 * data, + guint size) +{ + GstBuffer *buf; + const guint nl = h264parse->nal_length_size; + + buf = gst_buffer_new_and_alloc (size + nl + 4); + if (format == GST_H264_PARSE_FORMAT_AVC) { + GST_WRITE_UINT32_BE (GST_BUFFER_DATA (buf), size << (32 - 8 * nl)); + } else { + g_assert (nl == 4); + GST_WRITE_UINT32_BE (GST_BUFFER_DATA (buf), 1); + } + + GST_BUFFER_SIZE (buf) = size + nl; + memcpy (GST_BUFFER_DATA (buf) + nl, data, size); + + return buf; +} + +/* SPS/PPS/IDR considered key, all others DELTA; + * so downstream waiting for keyframe can pick up at SPS/PPS/IDR */ +#define NAL_TYPE_IS_KEY(nt) (((nt) == 5) || ((nt) == 7) || ((nt) == 8)) + +/* caller guarantees 2 bytes of nal payload */ +static void +gst_h264_parse_process_nal (GstH264Parse * h264parse, guint8 * data, + gint sc_pos, gint nal_pos, guint nal_size) +{ + guint nal_type; + + g_return_if_fail (nal_size >= 2); + g_return_if_fail (nal_pos - sc_pos > 0 && nal_pos - sc_pos <= 4); + + /* lower layer collects params */ + gst_h264_params_parse_nal (h264parse->params, data + nal_pos, nal_size); + + /* we have a peek as well */ + nal_type = data[nal_pos] & 0x1f; + h264parse->keyframe |= NAL_TYPE_IS_KEY (nal_type); + + switch (nal_type) { + case NAL_SPS: + case NAL_PPS: + /* parameters might have changed, force caps check */ + GST_DEBUG_OBJECT (h264parse, "triggering src caps check"); + h264parse->update_caps = TRUE; + /* found in stream, no need to forcibly push at start */ + h264parse->push_codec = FALSE; + break; + case NAL_SLICE: + case NAL_SLICE_DPA: + case NAL_SLICE_DPB: + case NAL_SLICE_DPC: + /* real frame data */ + h264parse->frame_start |= (h264parse->params->first_mb_in_slice == 0); + /* if we need to sneak codec NALs into the stream, + * this is a good place, so fake it as IDR + * (which should be at start anyway) */ + if (G_LIKELY (!h264parse->push_codec)) + break; + /* fall-through */ + case NAL_SLICE_IDR: + /* real frame data */ + h264parse->frame_start |= (h264parse->params->first_mb_in_slice == 0); + /* mark where config needs to go if interval expired */ + /* mind replacement buffer if applicable */ + if (h264parse->format == GST_H264_PARSE_FORMAT_AVC) + h264parse->idr_pos = gst_adapter_available (h264parse->frame_out); + else + h264parse->idr_pos = sc_pos; + GST_DEBUG_OBJECT (h264parse, "marking IDR in frame at offset %d", + h264parse->idr_pos); + break; + } + + /* if AVC output needed, collect properly prefixed nal in adapter, + * and use that to replace outgoing buffer data later on */ + if (h264parse->format == GST_H264_PARSE_FORMAT_AVC) { + GstBuffer *buf; + + GST_LOG_OBJECT (h264parse, "collecting NAL in AVC frame"); + buf = gst_h264_parse_wrap_nal (h264parse, h264parse->format, + data + nal_pos, nal_size); + gst_adapter_push (h264parse->frame_out, buf); + } +} + +/* caller guarantees at least 2 bytes of nal payload for each nal + * returns TRUE if next_nal indicates that nal terminates an AU */ +static inline gboolean +gst_h264_parse_collect_nal (GstH264Parse * h264parse, guint8 * nal, + guint8 * next_nal) +{ + gint nal_type; + gboolean complete; + + if (h264parse->align == GST_H264_PARSE_ALIGN_NAL) + return TRUE; + + /* determine if AU complete */ + nal_type = nal[0] & 0x1f; + GST_LOG_OBJECT (h264parse, "nal type: %d", nal_type); + /* coded slice NAL starts a picture, + * i.e. other types become aggregated in front of it */ + h264parse->picture_start |= (nal_type == 1 || nal_type == 2 || nal_type == 5); + + /* consider a coded slices (IDR or not) to start a picture, + * (so ending the previous one) if first_mb_in_slice == 0 + * (non-0 is part of previous one) */ + /* NOTE this is not entirely according to Access Unit specs in 7.4.1.2.4, + * but in practice it works in sane cases, needs not much parsing, + * and also works with broken frame_num in NAL + * (where spec-wise would fail) */ + nal_type = next_nal[0] & 0x1f; + GST_LOG_OBJECT (h264parse, "next nal type: %d", nal_type); + complete = h264parse->picture_start && (nal_type >= 6 && nal_type <= 9); + complete |= h264parse->picture_start && + (nal_type == 1 || nal_type == 2 || nal_type == 5) && + /* first_mb_in_slice == 0 considered start of frame */ + (next_nal[1] & 0x80); + + GST_LOG_OBJECT (h264parse, "au complete: %d", complete); + + return complete; +} + +/* finds next startcode == 00 00 01, along with a subsequent byte */ +static guint +gst_h264_parse_find_sc (GstBuffer * buffer, guint skip) +{ + GstByteReader br; + guint sc_pos = -1; + + gst_byte_reader_init_from_buffer (&br, buffer); + + /* NALU not empty, so we can at least expect 1 (even 2) bytes following sc */ + sc_pos = gst_byte_reader_masked_scan_uint32 (&br, 0xffffff00, 0x00000100, + skip, gst_byte_reader_get_remaining (&br) - skip); + + return sc_pos; +} + +static gboolean +gst_h264_parse_check_valid_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, guint * framesize, gint * skipsize) +{ + GstH264Parse *h264parse = GST_H264_PARSE (parse); + GstBuffer *buffer = frame->buffer; + gint sc_pos, nal_pos, next_sc_pos, next_nal_pos; + guint8 *data; + guint size; + gboolean drain; + + /* expect at least 3 bytes startcode == sc, and 2 bytes NALU payload */ + if (G_UNLIKELY (GST_BUFFER_SIZE (buffer) < 5)) + return FALSE; + + /* need to configure aggregation */ + if (G_UNLIKELY (h264parse->format == GST_H264_PARSE_FORMAT_NONE)) + gst_h264_parse_negotiate (h264parse); + + data = GST_BUFFER_DATA (buffer); + size = GST_BUFFER_SIZE (buffer); + + GST_LOG_OBJECT (h264parse, "last_nal_pos: %d, last_scan_pos %d", + h264parse->last_nal_pos, h264parse->next_sc_pos); + + nal_pos = h264parse->last_nal_pos; + next_sc_pos = h264parse->next_sc_pos; + + if (!next_sc_pos) { + sc_pos = gst_h264_parse_find_sc (buffer, 0); + + if (sc_pos == -1) { + /* SC not found, need more data */ + sc_pos = GST_BUFFER_SIZE (buffer) - 3; + goto more; + } + + nal_pos = sc_pos + 3; + next_sc_pos = nal_pos; + /* sc might have 2 or 3 0-bytes */ + if (sc_pos > 0 && data[sc_pos - 1] == 00) + sc_pos--; + GST_LOG_OBJECT (h264parse, "found sc at offset %d", sc_pos); + } else { + /* previous checks already arrange sc at start */ + sc_pos = 0; + } + + drain = GST_BASE_PARSE_FRAME_DRAIN (frame); + while (TRUE) { + gint prev_sc_pos; + + next_sc_pos = gst_h264_parse_find_sc (buffer, next_sc_pos); + if (next_sc_pos == -1) { + GST_LOG_OBJECT (h264parse, "no next sc"); + if (drain) { + /* FLUSH/EOS, it's okay if we can't find the next frame */ + next_sc_pos = size; + next_nal_pos = size; + } else { + next_sc_pos = size - 3; + goto more; + } + } else { + next_nal_pos = next_sc_pos + 3; + if (data[next_sc_pos - 1] == 00) + next_sc_pos--; + GST_LOG_OBJECT (h264parse, "found next sc at offset %d", next_sc_pos); + /* need at least 1 more byte of next NAL */ + if (!drain && (next_nal_pos == size - 1)) + goto more; + } + + /* determine nal's sc position */ + prev_sc_pos = nal_pos - 3; + g_assert (prev_sc_pos >= 0); + if (prev_sc_pos > 0 && data[prev_sc_pos - 1] == 0) + prev_sc_pos--; + + /* already consume and gather info from NAL */ + gst_h264_parse_process_nal (h264parse, data, prev_sc_pos, nal_pos, + next_sc_pos - nal_pos); + if (next_nal_pos >= size - 1 || + gst_h264_parse_collect_nal (h264parse, data + nal_pos, + data + next_nal_pos)) + break; + + /* move along */ + next_sc_pos = nal_pos = next_nal_pos; + } + + *skipsize = sc_pos; + *framesize = next_sc_pos - sc_pos; + + return TRUE; + +more: + /* Ask for 1024 bytes more - this is an arbitrary choice */ + gst_base_parse_set_min_frame_size (parse, GST_BUFFER_SIZE (buffer) + 1024); + + /* skip up to initial startcode */ + *skipsize = sc_pos; + /* resume scanning here next time */ + h264parse->last_nal_pos = nal_pos; + h264parse->next_sc_pos = next_sc_pos; + + return FALSE; +} + +/* byte together avc codec data based on collected pps and sps so far */ +static GstBuffer * +gst_h264_parse_make_codec_data (GstH264Parse * h264parse) +{ + GstBuffer *buf, *nal; + gint i, sps_size = 0, pps_size = 0, num_sps = 0, num_pps = 0; + guint8 profile_idc = 0, profile_comp = 0, level_idc = 0; + gboolean found = FALSE; + guint8 *data; + + /* only nal payload in stored nals */ + + for (i = 0; i < MAX_SPS_COUNT; i++) { + if ((nal = h264parse->params->sps_nals[i])) { + num_sps++; + /* size bytes also count */ + sps_size += GST_BUFFER_SIZE (nal) + 2; + if (GST_BUFFER_SIZE (nal) >= 4) { + found = TRUE; + profile_idc = (GST_BUFFER_DATA (nal))[1]; + profile_comp = (GST_BUFFER_DATA (nal))[2]; + level_idc = (GST_BUFFER_DATA (nal))[3]; + } + } + } + for (i = 0; i < MAX_PPS_COUNT; i++) { + if ((nal = h264parse->params->pps_nals[i])) { + num_pps++; + /* size bytes also count */ + pps_size += GST_BUFFER_SIZE (nal) + 2; + } + } + + GST_DEBUG_OBJECT (h264parse, + "constructing codec_data: num_sps=%d, num_pps=%d", num_sps, num_pps); + + if (!found || !num_pps) + return NULL; + + buf = gst_buffer_new_and_alloc (5 + 1 + sps_size + 1 + pps_size); + data = GST_BUFFER_DATA (buf); + + data[0] = 1; /* AVC Decoder Configuration Record ver. 1 */ + data[1] = profile_idc; /* profile_idc */ + data[2] = profile_comp; /* profile_compability */ + data[3] = level_idc; /* level_idc */ + data[4] = 0xfc | (4 - 1); /* nal_length_size_minus1 */ + data[5] = 0xe0 | num_sps; /* number of SPSs */ + + data += 6; + for (i = 0; i < MAX_SPS_COUNT; i++) { + if ((nal = h264parse->params->sps_nals[i])) { + GST_WRITE_UINT16_BE (data, GST_BUFFER_SIZE (nal)); + memcpy (data + 2, GST_BUFFER_DATA (nal), GST_BUFFER_SIZE (nal)); + data += 2 + GST_BUFFER_SIZE (nal); + } + } + + data[0] = num_pps; + data++; + for (i = 0; i < MAX_PPS_COUNT; i++) { + if ((nal = h264parse->params->pps_nals[i])) { + GST_WRITE_UINT16_BE (data, GST_BUFFER_SIZE (nal)); + memcpy (data + 2, GST_BUFFER_DATA (nal), GST_BUFFER_SIZE (nal)); + data += 2 + GST_BUFFER_SIZE (nal); + } + } + + return buf; +} + +static void +gst_h264_parse_update_src_caps (GstH264Parse * h264parse) +{ + GstH264ParamsSPS *sps; + GstCaps *caps = NULL, *sink_caps; + gboolean modified = FALSE; + GstBuffer *buf = NULL; + + if (G_UNLIKELY (!GST_PAD_CAPS (GST_BASE_PARSE_SRC_PAD (h264parse)))) + modified = TRUE; + else if (G_UNLIKELY (!h264parse->update_caps)) + return; + + /* carry over input caps as much as possible; override with our own stuff */ + sink_caps = GST_PAD_CAPS (GST_BASE_PARSE_SINK_PAD (h264parse)); + if (sink_caps) + gst_caps_ref (sink_caps); + else + sink_caps = gst_caps_new_simple ("video/x-h264", NULL); + + sps = h264parse->params->sps; + GST_DEBUG_OBJECT (h264parse, "sps: %p", sps); + + /* only codec-data for nice-and-clean au aligned packetized avc format */ + if (h264parse->format == GST_H264_PARSE_FORMAT_AVC && + h264parse->align == GST_H264_PARSE_ALIGN_AU) { + buf = gst_h264_parse_make_codec_data (h264parse); + if (buf && h264parse->codec_data) { + if (GST_BUFFER_SIZE (buf) != GST_BUFFER_SIZE (h264parse->codec_data) || + memcmp (GST_BUFFER_DATA (buf), + GST_BUFFER_DATA (h264parse->codec_data), GST_BUFFER_SIZE (buf))) + modified = TRUE; + } else { + if (h264parse->codec_data) + buf = gst_buffer_ref (h264parse->codec_data); + modified = TRUE; + } + } + + if (G_UNLIKELY (!sps)) { + caps = gst_caps_copy (sink_caps); + } else if (G_UNLIKELY (h264parse->width != sps->width || + h264parse->height != sps->height || h264parse->fps_num != sps->fps_num + || h264parse->fps_den != sps->fps_den || modified)) { + caps = gst_caps_copy (sink_caps); + /* sps should give this */ + gst_caps_set_simple (caps, "width", G_TYPE_INT, sps->width, + "height", G_TYPE_INT, sps->height, NULL); + h264parse->height = sps->height; + h264parse->width = sps->width; + /* but not necessarily or reliably this */ + if ((!h264parse->fps_num || !h264parse->fps_den) && + sps->fps_num > 0 && sps->fps_den > 0) { + gst_caps_set_simple (caps, "framerate", + GST_TYPE_FRACTION, sps->fps_num, sps->fps_den, NULL); + h264parse->fps_num = sps->fps_num; + h264parse->fps_den = sps->fps_den; + gst_base_parse_set_frame_props (GST_BASE_PARSE (h264parse), + h264parse->fps_num, h264parse->fps_den, 0, 0); + } + } + + if (caps) { + gst_caps_set_simple (caps, "stream-format", G_TYPE_STRING, + gst_h264_parse_get_string (h264parse, TRUE, h264parse->format), + "alignment", G_TYPE_STRING, + gst_h264_parse_get_string (h264parse, FALSE, h264parse->align), NULL); + if (buf) { + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_replace (&h264parse->codec_data, buf); + gst_buffer_unref (buf); + buf = NULL; + } else { + GstStructure *s; + /* remove any left-over codec-data hanging around */ + s = gst_caps_get_structure (caps, 0); + gst_structure_remove_field (s, "codec_data"); + } + gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (h264parse), caps); + gst_caps_unref (caps); + } + + gst_caps_unref (sink_caps); + if (buf) + gst_buffer_unref (buf); +} + +static GstFlowReturn +gst_h264_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame) +{ + GstH264Parse *h264parse; + GstBuffer *buffer; + guint av; + + h264parse = GST_H264_PARSE (parse); + buffer = frame->buffer; + + gst_h264_parse_update_src_caps (h264parse); + + gst_h264_params_get_timestamp (h264parse->params, + &GST_BUFFER_TIMESTAMP (buffer), &GST_BUFFER_DURATION (buffer), + h264parse->frame_start); + + if (h264parse->keyframe) + GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + else + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + + /* replace with transformed AVC output if applicable */ + av = gst_adapter_available (h264parse->frame_out); + if (av) { + GstBuffer *buf; + + buf = gst_adapter_take_buffer (h264parse->frame_out, av); + gst_buffer_copy_metadata (buf, buffer, GST_BUFFER_COPY_ALL); + gst_buffer_replace (&frame->buffer, buf); + } + + return GST_FLOW_OK; +} + +/* sends a codec NAL downstream, decorating and transforming as needed. + * No ownership is taken of @nal */ +static GstFlowReturn +gst_h264_parse_push_codec_buffer (GstH264Parse * h264parse, GstBuffer * nal, + GstClockTime ts) +{ + nal = gst_h264_parse_wrap_nal (h264parse, h264parse->format, + GST_BUFFER_DATA (nal), GST_BUFFER_SIZE (nal)); + + GST_BUFFER_TIMESTAMP (nal) = ts; + GST_BUFFER_DURATION (nal) = 0; + + gst_buffer_set_caps (nal, GST_PAD_CAPS (GST_BASE_PARSE_SRC_PAD (h264parse))); + + return gst_pad_push (GST_BASE_PARSE_SRC_PAD (h264parse), nal); +} + +static GstFlowReturn +gst_h264_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame) +{ + GstH264Parse *h264parse; + GstBuffer *buffer; + + h264parse = GST_H264_PARSE (parse); + buffer = frame->buffer; + + /* periodic SPS/PPS sending */ + if (h264parse->interval > 0 || h264parse->push_codec) { + GstClockTime timestamp = GST_BUFFER_TIMESTAMP (buffer); + guint64 diff; + + /* init */ + if (!GST_CLOCK_TIME_IS_VALID (h264parse->last_report)) { + h264parse->last_report = timestamp; + } + + if (h264parse->idr_pos >= 0) { + GST_LOG_OBJECT (h264parse, "IDR nal at offset %d", h264parse->idr_pos); + + if (timestamp > h264parse->last_report) + diff = timestamp - h264parse->last_report; + else + diff = 0; + + GST_LOG_OBJECT (h264parse, + "now %" GST_TIME_FORMAT ", last SPS/PPS %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp), GST_TIME_ARGS (h264parse->last_report)); + + GST_DEBUG_OBJECT (h264parse, + "interval since last SPS/PPS %" GST_TIME_FORMAT, + GST_TIME_ARGS (diff)); + + if (GST_TIME_AS_SECONDS (diff) >= h264parse->interval || + h264parse->push_codec) { + GstBuffer *codec_nal; + gint i; + GstClockTime new_ts; + + /* avoid overwriting a perfectly fine timestamp */ + new_ts = GST_CLOCK_TIME_IS_VALID (timestamp) ? timestamp : + h264parse->last_report; + + if (h264parse->align == GST_H264_PARSE_ALIGN_NAL) { + /* send separate config NAL buffers */ + GST_DEBUG_OBJECT (h264parse, "- sending SPS/PPS"); + for (i = 0; i < MAX_SPS_COUNT; i++) { + if ((codec_nal = h264parse->params->sps_nals[i])) { + GST_DEBUG_OBJECT (h264parse, "sending SPS nal"); + gst_h264_parse_push_codec_buffer (h264parse, codec_nal, + timestamp); + h264parse->last_report = new_ts; + } + } + for (i = 0; i < MAX_PPS_COUNT; i++) { + if ((codec_nal = h264parse->params->pps_nals[i])) { + GST_DEBUG_OBJECT (h264parse, "sending PPS nal"); + gst_h264_parse_push_codec_buffer (h264parse, codec_nal, + timestamp); + h264parse->last_report = new_ts; + } + } + } else { + /* insert config NALs into AU */ + GstByteWriter bw; + GstBuffer *new_buf; + const gboolean bs = h264parse->format == GST_H264_PARSE_FORMAT_BYTE; + + gst_byte_writer_init_with_size (&bw, GST_BUFFER_SIZE (buffer), FALSE); + gst_byte_writer_put_data (&bw, GST_BUFFER_DATA (buffer), + h264parse->idr_pos); + GST_DEBUG_OBJECT (h264parse, "- inserting SPS/PPS"); + for (i = 0; i < MAX_SPS_COUNT; i++) { + if ((codec_nal = h264parse->params->sps_nals[i])) { + GST_DEBUG_OBJECT (h264parse, "inserting SPS nal"); + gst_byte_writer_put_uint32_be (&bw, + bs ? 1 : GST_BUFFER_SIZE (codec_nal)); + gst_byte_writer_put_data (&bw, GST_BUFFER_DATA (codec_nal), + GST_BUFFER_SIZE (codec_nal)); + h264parse->last_report = new_ts; + } + } + for (i = 0; i < MAX_PPS_COUNT; i++) { + if ((codec_nal = h264parse->params->pps_nals[i])) { + GST_DEBUG_OBJECT (h264parse, "inserting PPS nal"); + gst_byte_writer_put_uint32_be (&bw, + bs ? 1 : GST_BUFFER_SIZE (codec_nal)); + gst_byte_writer_put_data (&bw, GST_BUFFER_DATA (codec_nal), + GST_BUFFER_SIZE (codec_nal)); + h264parse->last_report = new_ts; + } + } + gst_byte_writer_put_data (&bw, + GST_BUFFER_DATA (buffer) + h264parse->idr_pos, + GST_BUFFER_SIZE (buffer) - h264parse->idr_pos); + /* collect result and push */ + new_buf = gst_byte_writer_reset_and_get_buffer (&bw); + gst_buffer_copy_metadata (new_buf, buffer, GST_BUFFER_COPY_ALL); + gst_buffer_replace (&frame->buffer, new_buf); + } + } + /* we pushed whatever we had */ + h264parse->push_codec = FALSE; + } + } + + gst_h264_parse_reset_frame (h264parse); + + return GST_FLOW_OK; +} + +static gboolean +gst_h264_parse_set_caps (GstBaseParse * parse, GstCaps * caps) +{ + GstH264Parse *h264parse; + GstStructure *str; + const GValue *value; + GstBuffer *buffer = NULL; + guint size; + + h264parse = GST_H264_PARSE (parse); + + /* reset */ + h264parse->push_codec = FALSE; + + str = gst_caps_get_structure (caps, 0); + + /* accept upstream info if provided */ + gst_structure_get_int (str, "width", &h264parse->width); + gst_structure_get_int (str, "height", &h264parse->height); + gst_structure_get_fraction (str, "framerate", &h264parse->fps_num, + &h264parse->fps_den); + + /* packetized video has a codec_data */ + if ((value = gst_structure_get_value (str, "codec_data"))) { + guint8 *data; + guint num_sps, num_pps, profile, len; + gint i; + + GST_DEBUG_OBJECT (h264parse, "have packetized h264"); + /* make note for optional split processing */ + h264parse->packetized = TRUE; + + buffer = gst_value_get_buffer (value); + if (!buffer) + goto wrong_type; + data = GST_BUFFER_DATA (buffer); + size = GST_BUFFER_SIZE (buffer); + + /* parse the avcC data */ + if (size < 7) + goto avcc_too_small; + /* parse the version, this must be 1 */ + if (data[0] != 1) + goto wrong_version; + + /* AVCProfileIndication */ + /* profile_compat */ + /* AVCLevelIndication */ + profile = (data[1] << 16) | (data[2] << 8) | data[3]; + GST_DEBUG_OBJECT (h264parse, "profile %06x", profile); + + /* 6 bits reserved | 2 bits lengthSizeMinusOne */ + /* this is the number of bytes in front of the NAL units to mark their + * length */ + h264parse->nal_length_size = (data[4] & 0x03) + 1; + GST_DEBUG_OBJECT (h264parse, "nal length %u", h264parse->nal_length_size); + + num_sps = data[5] & 0x1f; + data += 6; + size -= 6; + for (i = 0; i < num_sps; i++) { + len = GST_READ_UINT16_BE (data); + if (size < len + 2 || len < 2) + goto avcc_too_small; + /* digest for later reference */ + gst_h264_parse_process_nal (h264parse, data, 0, 2, len); + data += len + 2; + size -= len + 2; + } + num_pps = data[0]; + data++; + size++; + for (i = 0; i < num_pps; i++) { + len = GST_READ_UINT16_BE (data); + if (size < len + 2 || len < 2) + goto avcc_too_small; + /* digest for later reference */ + gst_h264_parse_process_nal (h264parse, data, 0, 2, len); + data += len + 2; + size -= len + 2; + } + } else { + GST_DEBUG_OBJECT (h264parse, "have bytestream h264"); + /* nothing to pre-process */ + h264parse->packetized = FALSE; + /* we have 4 sync bytes */ + h264parse->nal_length_size = 4; + } + + if (h264parse->packetized) { + if (h264parse->split_packetized) { + GST_DEBUG_OBJECT (h264parse, + "converting AVC to nal bytestream prior to parsing"); + /* negotiate behaviour with upstream */ + gst_h264_parse_negotiate (h264parse); + if (h264parse->format == GST_H264_PARSE_FORMAT_BYTE) { + /* arrange to insert codec-data in-stream if needed */ + h264parse->push_codec = h264parse->packetized; + } + } else { + GST_DEBUG_OBJECT (h264parse, "passing on packetized AVC"); + /* no choice to negotiate */ + h264parse->format = GST_H264_PARSE_FORMAT_AVC; + h264parse->align = GST_H264_PARSE_ALIGN_AU; + /* fallback codec-data */ + h264parse->codec_data = gst_buffer_ref (buffer); + /* pass through unharmed, though _chain will parse a bit */ + gst_base_parse_set_format (parse, + GST_BASE_PARSE_FORMAT_PASSTHROUGH, TRUE); + /* we did parse codec-data and might supplement src caps */ + gst_h264_parse_update_src_caps (h264parse); + } + } + + /* src caps are only arranged for later on */ + return TRUE; + + /* ERRORS */ +avcc_too_small: + { + GST_DEBUG_OBJECT (h264parse, "avcC size %u < 7", size); + goto refuse_caps; + } +wrong_version: + { + GST_DEBUG_OBJECT (h264parse, "wrong avcC version"); + goto refuse_caps; + } +wrong_type: + { + GST_DEBUG_OBJECT (h264parse, "wrong codec-data type"); + goto refuse_caps; + } +refuse_caps: + { + GST_WARNING_OBJECT (h264parse, "refused caps %" GST_PTR_FORMAT, caps); + return FALSE; + } +} + +static GstFlowReturn +gst_h264_parse_chain (GstPad * pad, GstBuffer * buffer) +{ + GstH264Parse *h264parse = GST_H264_PARSE (GST_PAD_PARENT (pad)); + + if (h264parse->packetized && buffer) { + GstByteReader br; + GstBuffer *sub; + GstFlowReturn ret = GST_FLOW_OK; + guint32 len; + const guint nl = h264parse->nal_length_size; + + GST_LOG_OBJECT (h264parse, "processing packet buffer of size %d", + GST_BUFFER_SIZE (buffer)); + gst_byte_reader_init_from_buffer (&br, buffer); + while (ret == GST_FLOW_OK && gst_byte_reader_get_remaining (&br)) { + GST_DEBUG_OBJECT (h264parse, "AVC nal offset %d", + gst_byte_reader_get_pos (&br)); + if (gst_byte_reader_get_remaining (&br) < nl) + goto parse_failed; + switch (nl) { + case 4: + len = gst_byte_reader_get_uint32_be_unchecked (&br); + break; + case 3: + len = gst_byte_reader_get_uint24_be_unchecked (&br); + break; + case 2: + len = gst_byte_reader_get_uint16_be_unchecked (&br); + break; + case 1: + len = gst_byte_reader_get_uint8_unchecked (&br); + break; + default: + goto not_negotiated; + break; + } + GST_DEBUG_OBJECT (h264parse, "AVC nal size %d", len); + if (gst_byte_reader_get_remaining (&br) < len) + goto parse_failed; + if (h264parse->split_packetized) { + /* convert to NAL aligned byte stream input */ + sub = gst_h264_parse_wrap_nal (h264parse, GST_H264_PARSE_FORMAT_BYTE, + (guint8 *) gst_byte_reader_get_data_unchecked (&br, len), len); + /* at least this should make sense */ + GST_BUFFER_TIMESTAMP (sub) = GST_BUFFER_TIMESTAMP (buffer); + GST_LOG_OBJECT (h264parse, "pushing NAL of size %d", len); + ret = h264parse->parse_chain (pad, sub); + } else { + /* pass-through: no looking for frames (and nal processing), + * so need to parse to collect data here */ + /* NOTE: so if it is really configured to do so, + * pre_push can/will still insert codec-data at intervals, + * which is not really pure pass-through, but anyway ... */ + gst_h264_parse_process_nal (h264parse, + GST_BUFFER_DATA (buffer), gst_byte_reader_get_pos (&br) - nl, + gst_byte_reader_get_pos (&br), len); + gst_byte_reader_skip_unchecked (&br, len); + } + } + if (h264parse->split_packetized) + return ret; + } + +exit: + /* nal processing in pass-through might have collected stuff; + * ensure nothing happens with this later on */ + gst_adapter_clear (h264parse->frame_out); + + return h264parse->parse_chain (pad, buffer); + + /* ERRORS */ +not_negotiated: + { + GST_DEBUG_OBJECT (h264parse, "insufficient data to split input"); + return GST_FLOW_NOT_NEGOTIATED; + } +parse_failed: + { + if (h264parse->split_packetized) { + GST_ELEMENT_ERROR (h264parse, STREAM, FAILED, (NULL), + ("invalid AVC input data")); + return GST_FLOW_ERROR; + } else { + /* do not meddle to much in this case */ + GST_DEBUG_OBJECT (h264parse, "parsing packet failed"); + goto exit; + } + } +} + +static void +gst_h264_parse_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstH264Parse *parse; + + parse = GST_H264_PARSE (object); + + switch (prop_id) { + case PROP_SPLIT_PACKETIZED: + parse->split_packetized = g_value_get_boolean (value); + break; + case PROP_CONFIG_INTERVAL: + parse->interval = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_h264_parse_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstH264Parse *parse; + + parse = GST_H264_PARSE (object); + + switch (prop_id) { + case PROP_SPLIT_PACKETIZED: + g_value_set_boolean (value, parse->split_packetized); + break; + case PROP_CONFIG_INTERVAL: + g_value_set_uint (value, parse->interval); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} diff --git a/gst/videoparsers/gsth264parse.h b/gst/videoparsers/gsth264parse.h new file mode 100644 index 0000000000..50f4cf2364 --- /dev/null +++ b/gst/videoparsers/gsth264parse.h @@ -0,0 +1,93 @@ +/* GStreamer H.264 Parser + * Copyright (C) <2010> Mark Nauwelaerts + * Copyright (C) <2010> Collabora Multimedia + * Copyright (C) <2010> Nokia Corporation + * + * 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_H264_PARSE_H__ +#define __GST_H264_PARSE_H__ + +#include +#include "gstbaseparse.h" + +#include "h264parse.h" + +G_BEGIN_DECLS + +typedef struct _H264Params H264Params; + +#define GST_TYPE_H264_PARSE \ + (gst_h264_parse_get_type()) +#define GST_H264_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_H264_PARSE,GstH264Parse)) +#define GST_H264_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_H264_PARSE,GstH264ParseClass)) +#define GST_IS_H264_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_H264_PARSE)) +#define GST_IS_H264_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_H264_PARSE)) + +GType gst_h264_parse_get_type (void); + +typedef struct _GstH264Parse GstH264Parse; +typedef struct _GstH264ParseClass GstH264ParseClass; + +struct _GstH264Parse +{ + GstBaseParse baseparse; + + GstPadChainFunction parse_chain; + + /* stream */ + gint width, height; + gint fps_num, fps_den; + GstBuffer *codec_data; + guint nal_length_size; + gboolean packetized; + + /* state */ + GstH264Params *params; + guint align; + guint format; + + GstClockTime last_report; + gboolean push_codec; + + /* frame parsing */ + guint last_nal_pos; + guint next_sc_pos; + gint idr_pos; + gboolean update_caps; + GstAdapter *frame_out; + gboolean keyframe; + gboolean frame_start; + /* AU state */ + gboolean picture_start; + + /* props */ + gboolean split_packetized; + guint interval; +}; + +struct _GstH264ParseClass +{ + GstBaseParseClass parent_class; +}; + +G_END_DECLS +#endif diff --git a/gst/videoparsers/h264parse.c b/gst/videoparsers/h264parse.c new file mode 100644 index 0000000000..f0ec757f87 --- /dev/null +++ b/gst/videoparsers/h264parse.c @@ -0,0 +1,1042 @@ +/* GStreamer H.264 Parser + * Copyright (C) <2010> Mark Nauwelaerts + * Copyright (C) <2010> Collabora Multimedia + * Copyright (C) <2010> Nokia Corporation + * + * Some bits C-c,C-v'ed and s/4/3 from h264parse: + * (C) 2005 Michal Benes + * (C) 2008 Wim Taymans + * + * 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 "h264parse.h" + +#include + +GST_DEBUG_CATEGORY_EXTERN (h264_parse_debug); +#define GST_CAT_DEFAULT h264_parse_debug + +/* simple bitstream parser, automatically skips over + * emulation_prevention_three_bytes. */ +typedef struct +{ + const guint8 *orig_data; + const guint8 *data; + const guint8 *end; + /* bitpos in the cache of next bit */ + gint head; + /* cached bytes */ + guint64 cache; +} GstNalBs; + +static void +gst_nal_bs_init (GstNalBs * bs, const guint8 * data, guint size) +{ + bs->orig_data = data; + bs->data = data; + bs->end = data + size; + bs->head = 0; + /* fill with something other than 0 to detect emulation prevention bytes */ + bs->cache = 0xffffffff; +} + +static inline void +gst_nal_bs_get_data (GstNalBs * bs, const guint8 ** data, guint * size) +{ + *data = bs->orig_data; + *size = bs->end - bs->orig_data; +} + +static guint32 +gst_nal_bs_read (GstNalBs * bs, guint n) +{ + guint32 res = 0; + gint shift; + + if (n == 0) + return res; + + /* fill up the cache if we need to */ + while (bs->head < n) { + guint8 byte; + gboolean check_three_byte; + + check_three_byte = TRUE; + next_byte: + if (bs->data >= bs->end) { + /* we're at the end, can't produce more than head number of bits */ + n = bs->head; + break; + } + /* get the byte, this can be an emulation_prevention_three_byte that we need + * to ignore. */ + byte = *bs->data++; + if (check_three_byte && byte == 0x03 && ((bs->cache & 0xffff) == 0)) { + /* next byte goes unconditionally to the cache, even if it's 0x03 */ + check_three_byte = FALSE; + goto next_byte; + } + /* shift bytes in cache, moving the head bits of the cache left */ + bs->cache = (bs->cache << 8) | byte; + bs->head += 8; + } + + /* bring the required bits down and truncate */ + if ((shift = bs->head - n) > 0) + res = bs->cache >> shift; + else + res = bs->cache; + + /* mask out required bits */ + if (n < 32) + res &= (1 << n) - 1; + + bs->head = shift; + + return res; +} + +static gboolean +gst_nal_bs_eos (GstNalBs * bs) +{ + return (bs->data >= bs->end) && (bs->head == 0); +} + +/* read unsigned Exp-Golomb code */ +static gint +gst_nal_bs_read_ue (GstNalBs * bs) +{ + gint i = 0; + + while (gst_nal_bs_read (bs, 1) == 0 && !gst_nal_bs_eos (bs) && i < 32) + i++; + + return ((1 << i) - 1 + gst_nal_bs_read (bs, i)); +} + +/* read signed Exp-Golomb code */ +static gint +gst_nal_bs_read_se (GstNalBs * bs) +{ + gint i = 0; + + i = gst_nal_bs_read_ue (bs); + /* (-1)^(i+1) Ceil (i / 2) */ + i = (i + 1) / 2 * (i & 1 ? 1 : -1); + + return i; +} + +/* end parser helper */ + +static void +gst_h264_params_store_nal (GstH264Params * params, GstBuffer ** store, gint id, + GstNalBs * bs) +{ + const guint8 *data; + GstBuffer *buf; + guint size; + + g_return_if_fail (MAX_SPS_COUNT == MAX_PPS_COUNT); + + if (id >= MAX_SPS_COUNT) { + GST_DEBUG_OBJECT (params->el, + "unable to store nal, id out-of-range %d", id); + return; + } + + gst_nal_bs_get_data (bs, &data, &size); + buf = gst_buffer_new_and_alloc (size); + memcpy (GST_BUFFER_DATA (buf), data, size); + gst_buffer_replace (store + id, buf); +} + +static GstH264ParamsSPS * +gst_h264_params_get_sps (GstH264Params * params, guint8 sps_id, gboolean set) +{ + GstH264ParamsSPS *sps; + + g_return_val_if_fail (params != NULL, NULL); + + if (G_UNLIKELY (sps_id >= MAX_SPS_COUNT)) { + GST_WARNING_OBJECT (params->el, + "requested sps_id=%04x out of range", sps_id); + return NULL; + } + + sps = ¶ms->sps_buffers[sps_id]; + if (set) { + if (sps->valid) { + params->sps = sps; + } else { + GST_WARNING_OBJECT (params->el, "invalid sps not selected"); + params->sps = NULL; + sps = NULL; + } + } + + return sps; +} + +static GstH264ParamsPPS * +gst_h264_params_get_pps (GstH264Params * params, guint8 pps_id, gboolean set) +{ + GstH264ParamsPPS *pps; + + g_return_val_if_fail (params != NULL, NULL); + + if (G_UNLIKELY (pps_id >= MAX_PPS_COUNT)) { + GST_WARNING_OBJECT (params->el, + "requested pps_id=%04x out of range", pps_id); + return NULL; + } + + pps = ¶ms->pps_buffers[pps_id]; + if (set) { + if (pps->valid) { + params->pps = pps; + } else { + GST_WARNING_OBJECT (params->el, "invalid pps not selected"); + params->pps = NULL; + pps = NULL; + } + } + + return pps; +} + +static gboolean +gst_h264_params_decode_sps_vui_hrd (GstH264Params * params, + GstH264ParamsSPS * sps, GstNalBs * bs) +{ + gint sched_sel_idx; + + sps->cpb_cnt_minus1 = gst_nal_bs_read_ue (bs); + if (sps->cpb_cnt_minus1 > 31U) { + GST_WARNING_OBJECT (params->el, "cpb_cnt_minus1 = %d out of range", + sps->cpb_cnt_minus1); + return FALSE; + } + + /* bit_rate_scale */ + gst_nal_bs_read (bs, 4); + /* cpb_size_scale */ + gst_nal_bs_read (bs, 4); + + for (sched_sel_idx = 0; sched_sel_idx <= sps->cpb_cnt_minus1; sched_sel_idx++) { + /* bit_rate_value_minus1 */ + gst_nal_bs_read_ue (bs); + /* cpb_size_value_minus1 */ + gst_nal_bs_read_ue (bs); + /* cbr_flag */ + gst_nal_bs_read (bs, 1); + } + + sps->initial_cpb_removal_delay_length_minus1 = gst_nal_bs_read (bs, 5); + sps->cpb_removal_delay_length_minus1 = gst_nal_bs_read (bs, 5); + sps->dpb_output_delay_length_minus1 = gst_nal_bs_read (bs, 5); + sps->time_offset_length_minus1 = gst_nal_bs_read (bs, 5); + + return TRUE; +} + +static gboolean +gst_h264_params_decode_sps_vui (GstH264Params * params, GstH264ParamsSPS * sps, + GstNalBs * bs) +{ + if (G_UNLIKELY (!sps)) + return FALSE; + + /* aspect_ratio_info_present_flag */ + if (gst_nal_bs_read (bs, 1)) { + /* aspect_ratio_idc */ + if (gst_nal_bs_read (bs, 8) == 255) { + /* Extended_SAR */ + /* sar_width */ + gst_nal_bs_read (bs, 16); + /* sar_height */ + gst_nal_bs_read (bs, 16); + } + } + + /* overscan_info_present_flag */ + if (gst_nal_bs_read (bs, 1)) { + /* overscan_appropriate_flag */ + gst_nal_bs_read (bs, 1); + } + + /* video_signal_type_present_flag */ + if (gst_nal_bs_read (bs, 1)) { + /* video_format */ + gst_nal_bs_read (bs, 3); + /* video_full_range_flag */ + gst_nal_bs_read (bs, 1); + + /* colour_description_present_flag */ + if (gst_nal_bs_read (bs, 1)) { + /* colour_primaries */ + gst_nal_bs_read (bs, 8); + /* transfer_characteristics */ + gst_nal_bs_read (bs, 8); + /* matrix_coefficients */ + gst_nal_bs_read (bs, 8); + } + } + + /* chroma_loc_info_present_flag */ + if (gst_nal_bs_read (bs, 1)) { + /* chroma_sample_loc_type_top_field */ + gst_nal_bs_read_ue (bs); + /* chroma_sample_loc_type_bottom_field */ + gst_nal_bs_read_ue (bs); + } + + sps->timing_info_present_flag = gst_nal_bs_read (bs, 1); + if (sps->timing_info_present_flag) { + guint32 num_units_in_tick = gst_nal_bs_read (bs, 32); + guint32 time_scale = gst_nal_bs_read (bs, 32); + + /* If any of these parameters = 0, discard all timing_info */ + if (time_scale == 0) { + GST_WARNING_OBJECT (params->el, + "time_scale = 0 detected in stream (incompliant to H.264 E.2.1)." + " Discarding related info."); + } else if (num_units_in_tick == 0) { + GST_WARNING_OBJECT (params->el, + "num_units_in_tick = 0 detected in stream (incompliant to H.264 E.2.1)." + " Discarding related info."); + } else { + sps->num_units_in_tick = num_units_in_tick; + sps->time_scale = time_scale; + sps->fixed_frame_rate_flag = gst_nal_bs_read (bs, 1); + GST_LOG_OBJECT (params->el, "timing info: dur=%d/%d fixed=%d", + num_units_in_tick, time_scale, sps->fixed_frame_rate_flag); + } + } + + sps->nal_hrd_parameters_present_flag = gst_nal_bs_read (bs, 1); + if (sps->nal_hrd_parameters_present_flag) { + gst_h264_params_decode_sps_vui_hrd (params, sps, bs); + } + sps->vcl_hrd_parameters_present_flag = gst_nal_bs_read (bs, 1); + if (sps->vcl_hrd_parameters_present_flag) { + gst_h264_params_decode_sps_vui_hrd (params, sps, bs); + } + if (sps->nal_hrd_parameters_present_flag + || sps->vcl_hrd_parameters_present_flag) { + gst_nal_bs_read (bs, 1); /* low_delay_hrd_flag */ + } + + sps->pic_struct_present_flag = gst_nal_bs_read (bs, 1); + + /* derive framerate */ + /* FIXME verify / also handle other cases */ + if (sps->fixed_frame_rate_flag && sps->frame_mbs_only_flag && + !sps->pic_struct_present_flag) { + sps->fps_num = sps->time_scale; + sps->fps_den = sps->num_units_in_tick; + /* picture is a frame = 2 fields */ + sps->fps_den *= 2; + GST_LOG_OBJECT (params->el, "framerate %d/%d", sps->fps_num, sps->fps_den); + } + + return TRUE; +} + +static gboolean +gst_h264_params_decode_sps (GstH264Params * params, GstNalBs * bs) +{ + guint8 profile_idc, level_idc; + guint8 sps_id; + GstH264ParamsSPS *sps = NULL; + guint subwc[] = { 1, 2, 2, 1 }; + guint subhc[] = { 1, 2, 1, 1 }; + guint chroma; + guint fc_top, fc_bottom, fc_left, fc_right; + gint width, height; + + profile_idc = gst_nal_bs_read (bs, 8); + /* constraint_set0_flag */ + gst_nal_bs_read (bs, 1); + /* constraint_set1_flag */ + gst_nal_bs_read (bs, 1); + /* constraint_set1_flag */ + gst_nal_bs_read (bs, 1); + /* constraint_set1_flag */ + gst_nal_bs_read (bs, 1); + /* reserved */ + gst_nal_bs_read (bs, 4); + level_idc = gst_nal_bs_read (bs, 8); + + sps_id = gst_nal_bs_read_ue (bs); + sps = gst_h264_params_get_sps (params, sps_id, FALSE); + if (G_UNLIKELY (sps == NULL)) + return FALSE; + + gst_h264_params_store_nal (params, params->sps_nals, sps_id, bs); + + /* could be redefined mid stream, arrange for clear state */ + memset (sps, 0, sizeof (*sps)); + + GST_LOG_OBJECT (params->el, "sps id %d", sps_id); + sps->valid = TRUE; + /* validate and force activate this one if it is the first SPS we see */ + if (params->sps == NULL) + params->sps = sps; + + sps->profile_idc = profile_idc; + sps->level_idc = level_idc; + + if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 + || profile_idc == 244 || profile_idc == 44 || + profile_idc == 83 || profile_idc == 86) { + gint scp_flag = 0; + + /* chroma_format_idc */ + if ((chroma = gst_nal_bs_read_ue (bs)) == 3) { + /* separate_colour_plane_flag */ + sps->scp_flag = gst_nal_bs_read (bs, 1); + } + /* bit_depth_luma_minus8 */ + gst_nal_bs_read_ue (bs); + /* bit_depth_chroma_minus8 */ + gst_nal_bs_read_ue (bs); + /* qpprime_y_zero_transform_bypass_flag */ + gst_nal_bs_read (bs, 1); + /* seq_scaling_matrix_present_flag */ + if (gst_nal_bs_read (bs, 1)) { + gint i, j, m, d; + + m = (chroma != 3) ? 8 : 12; + for (i = 0; i < m; i++) { + /* seq_scaling_list_present_flag[i] */ + d = gst_nal_bs_read (bs, 1); + if (d) { + gint lastScale = 8, nextScale = 8, deltaScale; + + j = (i < 6) ? 16 : 64; + for (; j > 0; j--) { + if (nextScale != 0) { + deltaScale = gst_nal_bs_read_se (bs); + nextScale = (lastScale + deltaScale + 256) % 256; + } + if (nextScale != 0) + lastScale = nextScale; + } + } + } + } + if (scp_flag) + chroma = 0; + } else { + /* inferred value */ + chroma = 1; + } + + /* between 0 and 12 */ + sps->log2_max_frame_num_minus4 = gst_nal_bs_read_ue (bs); + if (sps->log2_max_frame_num_minus4 > 12) { + GST_WARNING_OBJECT (params->el, + "log2_max_frame_num_minus4 = %d out of range" " [0,12]", + sps->log2_max_frame_num_minus4); + return FALSE; + } + + sps->pic_order_cnt_type = gst_nal_bs_read_ue (bs); + if (sps->pic_order_cnt_type == 0) { + sps->log2_max_pic_order_cnt_lsb_minus4 = gst_nal_bs_read_ue (bs); + } else if (sps->pic_order_cnt_type == 1) { + gint d; + + /* delta_pic_order_always_zero_flag */ + gst_nal_bs_read (bs, 1); + /* offset_for_non_ref_pic */ + gst_nal_bs_read_ue (bs); + /* offset_for_top_to_bottom_field */ + gst_nal_bs_read_ue (bs); + /* num_ref_frames_in_pic_order_cnt_cycle */ + d = gst_nal_bs_read_ue (bs); + for (; d > 0; d--) { + /* offset_for_ref_frame[i] */ + gst_nal_bs_read_ue (bs); + } + } + + /* max_num_ref_frames */ + gst_nal_bs_read_ue (bs); + /* gaps_in_frame_num_value_allowed_flag */ + gst_nal_bs_read (bs, 1); + /* pic_width_in_mbs_minus1 */ + width = gst_nal_bs_read_ue (bs); + /* pic_height_in_map_units_minus1 */ + height = gst_nal_bs_read_ue (bs); + + sps->frame_mbs_only_flag = gst_nal_bs_read (bs, 1); + if (!sps->frame_mbs_only_flag) { + /* mb_adaptive_frame_field_flag */ + gst_nal_bs_read (bs, 1); + } + + width++; + width *= 16; + height++; + height *= 16 * (2 - sps->frame_mbs_only_flag); + + /* direct_8x8_inference_flag */ + gst_nal_bs_read (bs, 1); + /* frame_cropping_flag */ + if (gst_nal_bs_read (bs, 1)) { + /* frame_crop_left_offset */ + fc_left = gst_nal_bs_read_ue (bs); + /* frame_crop_right_offset */ + fc_right = gst_nal_bs_read_ue (bs); + /* frame_crop_top_offset */ + fc_top = gst_nal_bs_read_ue (bs); + /* frame_crop_bottom_offset */ + fc_bottom = gst_nal_bs_read_ue (bs); + } else { + fc_left = fc_right = fc_top = fc_bottom = 0; + } + + GST_LOG_OBJECT (params->el, "decoding SPS: profile_idc = %d, " + "level_idc = %d, sps_id = %d, pic_order_cnt_type = %d, " + "frame_mbs_only_flag = %d", + sps->profile_idc, sps->level_idc, sps_id, sps->pic_order_cnt_type, + sps->frame_mbs_only_flag); + + /* calculate width and height */ + GST_LOG_OBJECT (params->el, "initial width=%d, height=%d", width, height); + GST_LOG_OBJECT (params->el, "crop (%d,%d)(%d,%d)", + fc_left, fc_top, fc_right, fc_bottom); + if (chroma > 3) { + GST_LOG_OBJECT (params->el, "chroma=%d in SPS is out of range", chroma); + return FALSE; + } + width -= (fc_left + fc_right) * subwc[chroma]; + height -= + (fc_top + fc_bottom) * subhc[chroma] * (2 - sps->frame_mbs_only_flag); + if (width < 0 || height < 0) { + GST_WARNING_OBJECT (params->el, "invalid width/height in SPS"); + return FALSE; + } + GST_LOG_OBJECT (params->el, "final width=%u, height=%u", width, height); + sps->width = width; + sps->height = height; + + sps->vui_parameters_present_flag = gst_nal_bs_read (bs, 1); + if (sps->vui_parameters_present_flag) { + /* discard parsing problem */ + gst_h264_params_decode_sps_vui (params, sps, bs); + } + + return TRUE; +} + +static gboolean +gst_h264_params_decode_pps (GstH264Params * params, GstNalBs * bs) +{ + guint8 pps_id; + GstH264ParamsPPS *pps = NULL; + + pps_id = gst_nal_bs_read_ue (bs); + + pps = gst_h264_params_get_pps (params, pps_id, FALSE); + if (G_UNLIKELY (pps == NULL)) + return FALSE; + + /* validate and set */ + pps->valid = TRUE; + params->pps = pps; + + gst_h264_params_store_nal (params, params->pps_nals, pps_id, bs); + + pps->sps_id = gst_nal_bs_read_ue (bs); + GST_LOG_OBJECT (params->el, "pps %d referencing sps %d", pps_id, pps->sps_id); + + /* activate referenced sps */ + if (!gst_h264_params_get_sps (params, pps->sps_id, TRUE)) + return FALSE; + + /* not parsing the rest for the time being */ + return TRUE; +} + +static gboolean +gst_h264_params_decode_sei_buffering_period (GstH264Params * params, + GstNalBs * bs) +{ +#ifdef EXTRA_PARSE + guint8 sps_id; + gint sched_sel_idx; + GstH264ParamsSPS *sps; + + sps_id = gst_nal_bs_read_ue (bs); + sps = gst_h264_params_get_sps (params, sps_id, TRUE); + if (G_UNLIKELY (sps == NULL)) + return FALSE; + + if (sps->nal_hrd_parameters_present_flag) { + for (sched_sel_idx = 0; sched_sel_idx <= sps->cpb_cnt_minus1; + sched_sel_idx++) { + params->initial_cpb_removal_delay[sched_sel_idx] + = gst_nal_bs_read (bs, + sps->initial_cpb_removal_delay_length_minus1 + 1); + /* initial_cpb_removal_delay_offset */ + gst_nal_bs_read (bs, sps->initial_cpb_removal_delay_length_minus1 + 1); + } + } + + if (sps->vcl_hrd_parameters_present_flag) { + for (sched_sel_idx = 0; sched_sel_idx <= sps->cpb_cnt_minus1; + sched_sel_idx++) { + params->initial_cpb_removal_delay[sched_sel_idx] + = gst_nal_bs_read (bs, + sps->initial_cpb_removal_delay_length_minus1 + 1); + /* initial_cpb_removal_delay_offset */ + gst_nal_bs_read (bs, sps->initial_cpb_removal_delay_length_minus1 + 1); + } + } +#endif + + if (params->ts_trn_nb == GST_CLOCK_TIME_NONE || + params->dts == GST_CLOCK_TIME_NONE) + params->ts_trn_nb = 0; + else + params->ts_trn_nb = params->dts; + + GST_LOG_OBJECT (params->el, + "new buffering period; ts_trn_nb updated: %" GST_TIME_FORMAT, + GST_TIME_ARGS (params->ts_trn_nb)); + + return 0; +} + +static gboolean +gst_h264_params_decode_sei_picture_timing (GstH264Params * params, + GstNalBs * bs) +{ + GstH264ParamsSPS *sps = params->sps; + + if (sps == NULL) { + GST_WARNING_OBJECT (params->el, + "SPS == NULL; delayed decoding of picture timing info not implemented "); + return FALSE; + } + + if (sps->nal_hrd_parameters_present_flag + || sps->vcl_hrd_parameters_present_flag) { + params->sei_cpb_removal_delay = + gst_nal_bs_read (bs, sps->cpb_removal_delay_length_minus1 + 1); + /* sei_dpb_output_delay */ + gst_nal_bs_read (bs, sps->dpb_output_delay_length_minus1 + 1); + } + + if (sps->pic_struct_present_flag) { +#ifdef EXTRA_PARSE + /* pic_struct to NumClockTS lookup table */ + static const guint8 sei_num_clock_ts_table[9] = + { 1, 1, 1, 2, 2, 3, 3, 2, 3 }; + guint i, num_clock_ts; + guint sei_ct_type = 0; +#endif + + params->sei_pic_struct = gst_nal_bs_read (bs, 4); + GST_LOG_OBJECT (params, "pic_struct:%d", params->sei_pic_struct); + if (params->sei_pic_struct > SEI_PIC_STRUCT_FRAME_TRIPLING) + return FALSE; + +#ifdef EXTRA_PARSE + num_clock_ts = sei_num_clock_ts_table[params->sei_pic_struct]; + + for (i = 0; i < num_clock_ts; i++) { + /* clock_timestamp_flag */ + if (gst_nal_bs_read (bs, 1)) { + guint full_timestamp_flag; + + sei_ct_type |= 1 << gst_nal_bs_read (bs, 2); + /* nuit_field_based_flag */ + gst_nal_bs_read (bs, 1); + /* counting_type */ + gst_nal_bs_read (bs, 5); + full_timestamp_flag = gst_nal_bs_read (bs, 1); + /* discontinuity_flag */ + gst_nal_bs_read (bs, 1); + /* cnt_dropped_flag */ + gst_nal_bs_read (bs, 1); + /* n_frames */ + gst_nal_bs_read (bs, 8); + if (full_timestamp_flag) { + /* seconds_value 0..59 */ + gst_nal_bs_read (bs, 6); + /* minutes_value 0..59 */ + gst_nal_bs_read (bs, 6); + /* hours_value 0..23 */ + gst_nal_bs_read (bs, 5); + } else { + /* seconds_flag */ + if (gst_nal_bs_read (bs, 1)) { + /* seconds_value range 0..59 */ + gst_nal_bs_read (bs, 6); + /* minutes_flag */ + if (gst_nal_bs_read (bs, 1)) { + /* minutes_value 0..59 */ + gst_nal_bs_read (bs, 6); + /* hours_flag */ + if (gst_nal_bs_read (bs, 1)) + /* hours_value 0..23 */ + gst_nal_bs_read (bs, 5); + } + } + } + if (sps->time_offset_length_minus1 >= 0) { + /* time_offset */ + gst_nal_bs_read (bs, sps->time_offset_length_minus1 + 1); + } + } + } + + GST_LOG_OBJECT (params, "ct_type:%X", sei_ct_type); +#endif + } + + return TRUE; +} + +static gboolean +gst_h264_params_decode_sei (GstH264Params * params, GstNalBs * bs) +{ + guint8 tmp; + GstH264ParamsSEIPayloadType payloadType = 0; + gint8 payloadSize = 0; + + do { + tmp = gst_nal_bs_read (bs, 8); + payloadType += tmp; + } while (tmp == 255); + do { + tmp = gst_nal_bs_read (bs, 8); + payloadSize += tmp; + } while (tmp == 255); + + GST_LOG_OBJECT (params->el, + "SEI message received: payloadType = %d, payloadSize = %d bytes", + payloadType, payloadSize); + + switch (payloadType) { + case SEI_BUF_PERIOD: + if (!gst_h264_params_decode_sei_buffering_period (params, bs)) + return FALSE; + break; + case SEI_PIC_TIMING: + /* TODO: According to H264 D2.2 Note1, it might be the case that the + * picture timing SEI message is encountered before the corresponding SPS + * is specified. Need to hold down the message and decode it later. */ + if (!gst_h264_params_decode_sei_picture_timing (params, bs)) + return FALSE; + break; + default: + GST_LOG_OBJECT (params->el, + "SEI message of payloadType = %d is received but not parsed", + payloadType); + break; + } + + return TRUE; +} + +static gboolean +gst_h264_params_decode_slice_header (GstH264Params * params, GstNalBs * bs) +{ + GstH264ParamsSPS *sps; + GstH264ParamsPPS *pps; + guint8 pps_id; + + params->first_mb_in_slice = gst_nal_bs_read_ue (bs); + params->slice_type = gst_nal_bs_read_ue (bs); + + pps_id = gst_nal_bs_read_ue (bs); + GST_LOG_OBJECT (params->el, "slice header references pps id %d", pps_id); + pps = gst_h264_params_get_pps (params, pps_id, TRUE); + if (G_UNLIKELY (pps == NULL)) + return FALSE; + sps = gst_h264_params_get_sps (params, pps->sps_id, TRUE); + if (G_UNLIKELY (sps == NULL)) + return FALSE; + + if (sps->scp_flag) { + /* colour_plane_id */ + gst_nal_bs_read (bs, 2); + } + + /* frame num */ + gst_nal_bs_read (bs, sps->log2_max_pic_order_cnt_lsb_minus4 + 4); + + if (!sps->frame_mbs_only_flag) { + params->field_pic_flag = gst_nal_bs_read (bs, 1); + if (params->field_pic_flag) + params->bottom_field_flag = gst_nal_bs_read (bs, 1); + } + + /* not parsing the rest for the time being */ + return TRUE; +} + +/* only payload in @data */ +gboolean +gst_h264_params_parse_nal (GstH264Params * params, guint8 * data, gint size) +{ + GstH264ParamsNalUnitType nal_type; + GstNalBs bs; + gint nal_ref_idc; + gboolean res = TRUE; + + g_return_val_if_fail (params != NULL, FALSE); + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (size != 0, FALSE); + + nal_type = (data[0] & 0x1f); + nal_ref_idc = (data[0] & 0x60) >> 5; + + GST_LOG_OBJECT (params->el, "NAL type: %d, ref_idc: %d", nal_type, + nal_ref_idc); + + gst_nal_bs_init (&bs, data + 1, size - 1); + /* optimality HACK */ + bs.orig_data = data; + + /* first parse some things needed to get to the frame type */ + switch (nal_type) { + case NAL_SLICE: + case NAL_SLICE_DPA: + case NAL_SLICE_DPB: + case NAL_SLICE_DPC: + case NAL_SLICE_IDR: + { + gint first_mb_in_slice, slice_type; + + gst_h264_params_decode_slice_header (params, &bs); + first_mb_in_slice = params->first_mb_in_slice; + slice_type = params->slice_type; + + GST_LOG_OBJECT (params->el, "first MB: %d, slice type: %d", + first_mb_in_slice, slice_type); + + switch (slice_type) { + case 0: + case 5: + case 3: + case 8: /* SP */ + /* P frames */ + GST_LOG_OBJECT (params->el, "we have a P slice"); + break; + case 1: + case 6: + /* B frames */ + GST_LOG_OBJECT (params->el, "we have a B slice"); + break; + case 2: + case 7: + case 4: + case 9: + /* I frames */ + GST_LOG_OBJECT (params->el, "we have an I slice"); + break; + } + break; + } + case NAL_SEI: + GST_LOG_OBJECT (params->el, "SEI NAL"); + res = gst_h264_params_decode_sei (params, &bs); + break; + case NAL_SPS: + GST_LOG_OBJECT (params->el, "SPS NAL"); + res = gst_h264_params_decode_sps (params, &bs); + break; + case NAL_PPS: + GST_LOG_OBJECT (params->el, "PPS NAL"); + res = gst_h264_params_decode_pps (params, &bs); + break; + case NAL_AU_DELIMITER: + GST_LOG_OBJECT (params->el, "AU delimiter NAL"); + break; + default: + GST_LOG_OBJECT (params->el, "unparsed NAL"); + break; + } + + return res; +} + +void +gst_h264_params_get_timestamp (GstH264Params * params, + GstClockTime * out_ts, GstClockTime * out_dur, gboolean frame) +{ + GstH264ParamsSPS *sps = params->sps; + GstClockTime upstream; + gint duration = 1; + + g_return_if_fail (out_dur != NULL); + g_return_if_fail (out_ts != NULL); + + upstream = *out_ts; + + if (!frame) { + GST_LOG_OBJECT (params->el, "no frame data -> 0 duration"); + *out_dur = 0; + goto exit; + } else { + *out_ts = upstream; + } + + if (!sps) { + GST_DEBUG_OBJECT (params->el, "referred SPS invalid"); + goto exit; + } else if (!sps->timing_info_present_flag) { + GST_DEBUG_OBJECT (params->el, + "unable to compute timestamp: timing info not present"); + goto exit; + } else if (sps->time_scale == 0) { + GST_DEBUG_OBJECT (params->el, + "unable to compute timestamp: time_scale = 0 " + "(this is forbidden in spec; bitstream probably contains error)"); + goto exit; + } + + if (sps->pic_struct_present_flag && params->sei_pic_struct != (guint8) - 1) { + /* Note that when h264parse->sei_pic_struct == -1 (unspecified), there + * are ways to infer its value. This is related to computing the + * TopFieldOrderCnt and BottomFieldOrderCnt, which looks + * complicated and thus not implemented for the time being. Yet + * the value we have here is correct for many applications + */ + switch (params->sei_pic_struct) { + case SEI_PIC_STRUCT_TOP_FIELD: + case SEI_PIC_STRUCT_BOTTOM_FIELD: + duration = 1; + break; + case SEI_PIC_STRUCT_FRAME: + case SEI_PIC_STRUCT_TOP_BOTTOM: + case SEI_PIC_STRUCT_BOTTOM_TOP: + duration = 2; + break; + case SEI_PIC_STRUCT_TOP_BOTTOM_TOP: + case SEI_PIC_STRUCT_BOTTOM_TOP_BOTTOM: + duration = 3; + break; + case SEI_PIC_STRUCT_FRAME_DOUBLING: + duration = 4; + break; + case SEI_PIC_STRUCT_FRAME_TRIPLING: + duration = 6; + break; + default: + GST_DEBUG_OBJECT (params, + "h264parse->sei_pic_struct of unknown value %d. Not parsed", + params->sei_pic_struct); + break; + } + } else { + duration = params->field_pic_flag ? 1 : 2; + } + + GST_LOG_OBJECT (params->el, "frame tick duration %d", duration); + + /* + * h264parse.264 C.1.2 Timing of coded picture removal (equivalent to DTS): + * Tr,n(0) = initial_cpb_removal_delay[ SchedSelIdx ] / 90000 + * Tr,n(n) = Tr,n(nb) + Tc * cpb_removal_delay(n) + * where + * Tc = num_units_in_tick / time_scale + */ + + if (params->ts_trn_nb != GST_CLOCK_TIME_NONE) { + GST_LOG_OBJECT (params->el, "buffering based ts"); + /* buffering period is present */ + if (upstream != GST_CLOCK_TIME_NONE) { + /* If upstream timestamp is valid, we respect it and adjust current + * reference point */ + params->ts_trn_nb = upstream - + (GstClockTime) gst_util_uint64_scale_int + (params->sei_cpb_removal_delay * GST_SECOND, + sps->num_units_in_tick, sps->time_scale); + } else { + /* If no upstream timestamp is given, we write in new timestamp */ + upstream = params->dts = params->ts_trn_nb + + (GstClockTime) gst_util_uint64_scale_int + (params->sei_cpb_removal_delay * GST_SECOND, + sps->num_units_in_tick, sps->time_scale); + } + } else { + GstClockTime dur; + + GST_LOG_OBJECT (params->el, "duration based ts"); + /* naive method: no removal delay specified + * track upstream timestamp and provide best guess frame duration */ + dur = gst_util_uint64_scale_int (duration * GST_SECOND, + sps->num_units_in_tick, sps->time_scale); + /* sanity check */ + if (dur < GST_MSECOND) { + GST_DEBUG_OBJECT (params->el, "discarding dur %" GST_TIME_FORMAT, + GST_TIME_ARGS (dur)); + } else { + *out_dur = dur; + } + } + +exit: + if (GST_CLOCK_TIME_IS_VALID (upstream)) + *out_ts = params->dts = upstream; + + if (GST_CLOCK_TIME_IS_VALID (*out_dur) && + GST_CLOCK_TIME_IS_VALID (params->dts)) + params->dts += *out_dur; +} + +void +gst_h264_params_create (GstH264Params ** _params, GstElement * element) +{ + GstH264Params *params; + + g_return_if_fail (_params != NULL); + + params = g_new0 (GstH264Params, 1); + params->el = element; + + params->dts = GST_CLOCK_TIME_NONE; + params->ts_trn_nb = GST_CLOCK_TIME_NONE; + + *_params = params; +} + +void +gst_h264_params_free (GstH264Params * params) +{ + gint i; + + g_return_if_fail (params != NULL); + + for (i = 0; i < MAX_SPS_COUNT; i++) + gst_buffer_replace (¶ms->sps_nals[i], NULL); + for (i = 0; i < MAX_PPS_COUNT; i++) + gst_buffer_replace (¶ms->sps_nals[i], NULL); + + g_free (params); +} diff --git a/gst/videoparsers/h264parse.h b/gst/videoparsers/h264parse.h new file mode 100644 index 0000000000..141564bcef --- /dev/null +++ b/gst/videoparsers/h264parse.h @@ -0,0 +1,186 @@ +/* GStreamer H.264 Parser + * Copyright (C) <2010> Mark Nauwelaerts + * Copyright (C) <2010> Collabora Multimedia + * Copyright (C) <2010> Nokia Corporation + * + * Some bits C-c,C-v'ed and s/4/3 from h264parse: + * (C) 2005 Michal Benes + * (C) 2008 Wim Taymans + * + * 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_H264_PARAMS_H__ +#define __GST_H264_PARAMS_H__ + +#include + +G_BEGIN_DECLS + +typedef enum +{ + NAL_UNKNOWN = 0, + NAL_SLICE = 1, + NAL_SLICE_DPA = 2, + NAL_SLICE_DPB = 3, + NAL_SLICE_DPC = 4, + NAL_SLICE_IDR = 5, + NAL_SEI = 6, + NAL_SPS = 7, + NAL_PPS = 8, + NAL_AU_DELIMITER = 9, + NAL_SEQ_END = 10, + NAL_STREAM_END = 11, + NAL_FILTER_DATA = 12 +} GstH264ParamsNalUnitType; + +/* SEI type */ +typedef enum +{ + SEI_BUF_PERIOD = 0, + SEI_PIC_TIMING = 1 + /* and more... */ +} GstH264ParamsSEIPayloadType; + +/* SEI pic_struct type */ +typedef enum +{ + SEI_PIC_STRUCT_FRAME = 0, + SEI_PIC_STRUCT_TOP_FIELD = 1, + SEI_PIC_STRUCT_BOTTOM_FIELD = 2, + SEI_PIC_STRUCT_TOP_BOTTOM = 3, + SEI_PIC_STRUCT_BOTTOM_TOP = 4, + SEI_PIC_STRUCT_TOP_BOTTOM_TOP = 5, + SEI_PIC_STRUCT_BOTTOM_TOP_BOTTOM = 6, + SEI_PIC_STRUCT_FRAME_DOUBLING = 7, + SEI_PIC_STRUCT_FRAME_TRIPLING = 8 +} GstH264ParamsSEIPicStructType; + +typedef struct _GstH264Params GstH264Params; +typedef struct _GstH264ParamsSPS GstH264ParamsSPS; +typedef struct _GstH264ParamsPPS GstH264ParamsPPS; + +#define MAX_SPS_COUNT 32 +#define MAX_PPS_COUNT 32 + +/* SPS: sequential parameter sets */ +struct _GstH264ParamsSPS +{ + gboolean valid; + + /* raw values */ + guint8 profile_idc; + guint8 level_idc; + + guint8 sps_id; + + guint8 pic_order_cnt_type; + + guint8 log2_max_frame_num_minus4; + gboolean frame_mbs_only_flag; + guint8 log2_max_pic_order_cnt_lsb_minus4; + + gboolean frame_cropping_flag; + gboolean scp_flag; + + /* VUI parameters */ + gboolean vui_parameters_present_flag; + + gboolean timing_info_present_flag; + guint32 num_units_in_tick; + guint32 time_scale; + gboolean fixed_frame_rate_flag; + + gboolean nal_hrd_parameters_present_flag; + gboolean vcl_hrd_parameters_present_flag; + + /* hrd parameters */ + guint8 cpb_cnt_minus1; + gint initial_cpb_removal_delay_length_minus1; + gint cpb_removal_delay_length_minus1; + gint dpb_output_delay_length_minus1; + gboolean time_offset_length_minus1; + + gboolean pic_struct_present_flag; + + /* ... and probably more ... */ + + /* derived values */ + gint width, height; + gint fps_num, fps_den; +}; + +/* PPS: pic parameter sets */ +struct _GstH264ParamsPPS +{ + gboolean valid; + + /* raw values */ + guint8 pps_id; + guint8 sps_id; +}; + +struct _GstH264Params +{ + /* debug purposes */ + GstElement *el; + + /* SPS: sequential parameter set */ + GstH264ParamsSPS sps_buffers[MAX_SPS_COUNT]; + /* current SPS; most recent one in stream or referenced by PPS */ + GstH264ParamsSPS *sps; + /* PPS: sequential parameter set */ + GstH264ParamsPPS pps_buffers[MAX_PPS_COUNT]; + /* current PPS; most recent one in stream */ + GstH264ParamsPPS *pps; + + /* extracted from slice header or otherwise relevant nal */ + guint8 first_mb_in_slice; + guint8 slice_type; + gboolean field_pic_flag; + gboolean bottom_field_flag; + + /* SEI: supplemental enhancement messages */ +#ifdef EXTRA_PARSE + /* buffering period */ + guint32 initial_cpb_removal_delay[32]; +#endif + /* picture timing */ + guint32 sei_cpb_removal_delay; + guint8 sei_pic_struct; + /* And more... */ + + /* cached timestamps */ + /* (trying to) track upstream dts and interpolate */ + GstClockTime dts; + /* dts at start of last buffering period */ + GstClockTime ts_trn_nb; + + /* collected SPS and PPS NALUs */ + GstBuffer *sps_nals[MAX_SPS_COUNT]; + GstBuffer *pps_nals[MAX_PPS_COUNT]; +}; + +gboolean gst_h264_params_parse_nal (GstH264Params * params, guint8 * nal, gint size); +void gst_h264_params_get_timestamp (GstH264Params * params, + GstClockTime * out_ts, GstClockTime * out_dur, + gboolean frame); +void gst_h264_params_create (GstH264Params ** _params, GstElement * element); +void gst_h264_params_free (GstH264Params * params); + + +G_END_DECLS +#endif diff --git a/gst/videoparsers/plugin.c b/gst/videoparsers/plugin.c index b6d29c459b..463e417cb6 100644 --- a/gst/videoparsers/plugin.c +++ b/gst/videoparsers/plugin.c @@ -22,6 +22,7 @@ #endif #include "gsth263parse.h" +#include "gsth264parse.h" static gboolean plugin_init (GstPlugin * plugin) @@ -30,6 +31,8 @@ plugin_init (GstPlugin * plugin) ret = gst_element_register (plugin, "h263parse", GST_RANK_NONE, GST_TYPE_H263_PARSE); + ret = gst_element_register (plugin, "h264parse", + GST_RANK_NONE, GST_TYPE_H264_PARSE); return ret; }