mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-28 04:31:06 +00:00
d0b942980e
Functionally equivalent to (legacy)h264parse and re-uses the latter's low level NAL parsing, but otherwise based on GstBaseParse, and replacing some property configuration with caps negotiation.
1042 lines
29 KiB
C
1042 lines
29 KiB
C
/* GStreamer H.264 Parser
|
|
* Copyright (C) <2010> Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
|
|
* 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 <michal.benes@itonis.tv>
|
|
* (C) 2008 Wim Taymans <wim.taymans@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "h264parse.h"
|
|
|
|
#include <string.h>
|
|
|
|
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);
|
|
}
|