gstreamer/ext/resindvd/gstpesfilter.c
Jan Schmidt 9d31536adf resindvd: Merge changes across from master mpegdemux
Merge various changes and fixes from the master mpegdemux
Performance improvement from the way streams are organised,
return flow combining, language tag event generation,
adjustments and fixes in debug output, and things like that.
2014-01-16 20:16:47 +11:00

674 lines
18 KiB
C

/*
* This library is licensed under 2 different licenses and you
* can choose to use it under the terms of either one of them. The
* two licenses are the MPL 1.1 and the LGPL.
*
* MPL:
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* LGPL:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* The Original Code is Fluendo MPEG Demuxer plugin.
*
* The Initial Developer of the Original Code is Fluendo, S.L.
* Portions created by Fluendo, S.L. are Copyright (C) 2005
* Fluendo, S.L. All Rights Reserved.
*
* Contributor(s): Wim Taymans <wim@fluendo.com>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstmpegdefs.h"
#include "gstpesfilter.h"
GST_DEBUG_CATEGORY (mpegpspesfilter_debug);
#define GST_CAT_DEFAULT (mpegpspesfilter_debug)
static GstFlowReturn gst_pes_filter_data_push (GstPESFilter * filter,
gboolean first, GstBuffer * buffer);
#define ADAPTER_OFFSET_FLUSH(_bytes_) if (filter->adapter_offset) *filter->adapter_offset = *filter->adapter_offset + (_bytes_)
/* May pass null for adapter to have the filter create one */
void
gst_pes_filter_init (GstPESFilter * filter, GstAdapter * adapter,
guint64 * adapter_offset)
{
g_return_if_fail (filter != NULL);
if (adapter != NULL)
g_object_ref (adapter);
else
adapter = gst_adapter_new ();
filter->adapter = adapter;
filter->adapter_offset = adapter_offset;
filter->state = STATE_HEADER_PARSE;
filter->gather_pes = FALSE;
filter->allow_unbounded = FALSE;
}
void
gst_pes_filter_uninit (GstPESFilter * filter)
{
g_return_if_fail (filter != NULL);
if (filter->adapter)
g_object_unref (filter->adapter);
filter->adapter = NULL;
filter->adapter_offset = NULL;
}
void
gst_pes_filter_set_callbacks (GstPESFilter * filter,
GstPESFilterData data_cb, GstPESFilterResync resync_cb, gpointer user_data)
{
g_return_if_fail (filter != NULL);
filter->data_cb = data_cb;
filter->resync_cb = resync_cb;
filter->user_data = user_data;
}
static gboolean
gst_pes_filter_is_sync (guint32 sync)
{
return ((sync & 0xfffffffc) == 0x000001bc) ||
((sync & 0xfffffffd) == 0x000001bd) ||
((sync & 0xffffffe0) == 0x000001c0) ||
((sync & 0xfffffff0) == 0x000001f0) ||
((sync & 0xfffffff0) == 0x000001e0);
}
static GstFlowReturn
gst_pes_filter_parse (GstPESFilter * filter)
{
GstFlowReturn ret;
guint32 start_code;
gboolean STD_buffer_bound_scale G_GNUC_UNUSED;
guint16 STD_buffer_size_bound;
const guint8 *data;
gint avail, datalen;
gboolean have_size = FALSE;
avail = gst_adapter_available (filter->adapter);
if (avail < 6)
goto need_more_data;
data = gst_adapter_map (filter->adapter, 6);
/* read start code and length */
/* get start code */
start_code = GST_READ_UINT32_BE (data);
if (!gst_pes_filter_is_sync (start_code))
goto lost_sync;
filter->start_code = start_code;
filter->id = data[3];
/* skip start code */
data += 4;
/* start parsing length */
filter->length = GST_READ_UINT16_BE (data);
GST_DEBUG ("id 0x%02x length %d, avail %d start code 0x%02x", filter->id,
filter->length, avail, filter->start_code);
/* A data length of 0 indicates an unbounded packet in transport
* streams, but actually a 0 sized packet in program streams or
* for anything except video packets */
/* FIXME: Remove this hack that is checking start_code. Instead, we need
* a callback that a start_code has been collected, giving the caller a chance
* to set the allow_unbounded flag if they want */
if (filter->length == 0 &&
((filter->start_code & 0xFFFFFFF0) == PACKET_VIDEO_START_CODE ||
filter->start_code == ID_EXTENDED_STREAM_ID ||
filter->allow_unbounded)) {
GST_DEBUG ("id 0x%02x, unbounded length", filter->id);
filter->unbounded_packet = TRUE;
} else {
filter->unbounded_packet = FALSE;
if (filter->gather_pes && avail < filter->length + 6) {
GST_DEBUG ("id 0x%02x, bounded length %d, only have %d",
filter->id, filter->length + 6, avail);
goto need_more_data;
}
/* if we need more data from now on, we lost sync */
avail = MIN (avail, filter->length + 6);
}
if (avail < 6)
goto need_more_data;
gst_adapter_unmap (filter->adapter);
/* read more data, either the whole packet if there is a length
* or whatever we have available if this in an unbounded packet. */
data = gst_adapter_map (filter->adapter, avail);
/* This will make us flag LOST_SYNC if we run out of data from here onward */
have_size = TRUE;
/* skip start code and length */
data += 6;
datalen = avail - 6;
GST_DEBUG ("datalen %d", datalen);
switch (filter->start_code) {
case ID_PS_PROGRAM_STREAM_MAP:
case ID_PRIVATE_STREAM_2:
case ID_ECM_STREAM:
case ID_EMM_STREAM:
case ID_PROGRAM_STREAM_DIRECTORY:
case ID_DSMCC_STREAM:
case ID_ITU_TREC_H222_TYPE_E_STREAM:
/* Push directly out */
goto push_out;
case ID_PADDING_STREAM:
GST_DEBUG ("skipping padding stream");
goto skip;
default:
break;
}
if (datalen == 0)
goto need_more_data;
filter->pts = filter->dts = -1;
/* stuffing bits, first two bits are '10' for mpeg2 pes so this code is
* not triggered. */
while (TRUE) {
if (*data != 0xff)
break;
data++;
datalen--;
GST_DEBUG ("got stuffing bit");
if (datalen < 1)
goto need_more_data;
}
/* STD buffer size, never for mpeg2 */
if ((*data & 0xc0) == 0x40) {
GST_DEBUG ("have STD");
if (datalen < 3)
goto need_more_data;
STD_buffer_bound_scale = *data & 0x20;
STD_buffer_size_bound = ((guint16) (*data++ & 0x1F)) << 8;
STD_buffer_size_bound |= *data++;
datalen -= 2;
}
/* PTS but no DTS, never for mpeg2 */
if ((*data & 0xf0) == 0x20) {
GST_DEBUG ("PTS without DTS");
if (datalen < 5)
goto need_more_data;
READ_TS (data, filter->pts, lost_sync);
GST_DEBUG ("PTS found %" G_GUINT64_FORMAT, filter->pts);
datalen -= 5;
}
/* PTS and DTS, never for mpeg2 */
else if ((*data & 0xf0) == 0x30) {
GST_DEBUG ("PTS and DTS");
if (datalen < 10)
goto need_more_data;
READ_TS (data, filter->pts, lost_sync);
READ_TS (data, filter->dts, lost_sync);
GST_DEBUG ("PTS found %" G_GUINT64_FORMAT, filter->pts);
GST_DEBUG ("DTS found %" G_GUINT64_FORMAT, filter->dts);
datalen -= 10;
} else if ((*data & 0xc0) == 0x80) {
/* mpeg2 case */
guchar flags;
guint8 header_data_length = 0;
GST_DEBUG ("MPEG2 PES packet");
if (datalen < 3)
goto need_more_data;
/* 2: '10'
* 2: PES_scrambling_control
* 1: PES_priority
* 1: data_alignment_indicator
* 1: copyright
* 1: original_or_copy
*/
flags = *data++;
GST_DEBUG ("flags: 0x%02x", flags);
if ((flags & 0xc0) != 0x80)
goto lost_sync;
/* check PES scrambling control */
if ((flags & 0x30) != 0)
GST_DEBUG ("PES scrambling control: %x", (flags >> 4) & 0x3);
/* 2: PTS_DTS_flags
* 1: ESCR_flag
* 1: ES_rate_flag
* 1: DSM_trick_mode_flag
* 1: additional_copy_info_flag
* 1: PES_CRC_flag
* 1: PES_extension_flag
*/
flags = *data++;
/* 8: PES_header_data_length */
header_data_length = *data++;
datalen -= 3;
GST_DEBUG ("header_data_length: %d, flags 0x%02x",
header_data_length, flags);
if (header_data_length > datalen)
goto need_more_data;
/* only DTS: this is invalid */
if ((flags & 0xc0) == 0x40)
goto lost_sync;
/* check for PTS */
if ((flags & 0x80)) {
if (datalen < 5)
goto need_more_data;
READ_TS (data, filter->pts, lost_sync);
GST_DEBUG ("PTS found %" G_GUINT64_FORMAT, filter->pts);
header_data_length -= 5;
datalen -= 5;
}
/* check for DTS */
if ((flags & 0x40)) {
READ_TS (data, filter->dts, lost_sync);
if (datalen < 5)
goto need_more_data;
GST_DEBUG ("DTS found %" G_GUINT64_FORMAT, filter->dts);
header_data_length -= 5;
datalen -= 5;
}
/* ESCR_flag */
if ((flags & 0x20)) {
GST_DEBUG ("%x ESCR found", filter->id);
if (datalen < 6)
goto need_more_data;
data += 6;
header_data_length -= 6;
datalen -= 6;
}
/* ES_rate_flag */
if ((flags & 0x10)) {
guint32 es_rate;
if (datalen < 3)
goto need_more_data;
es_rate = ((guint32) (*data++ & 0x07)) << 14;
es_rate |= ((guint32) (*data++)) << 7;
es_rate |= ((guint32) (*data++ & 0xFE)) >> 1;
GST_DEBUG ("%x ES Rate found %u", filter->id, es_rate);
header_data_length -= 3;
datalen -= 3;
}
/* DSM_trick_mode_flag */
if ((flags & 0x08)) {
guint8 trick_mode_flags;
if (datalen < 1)
goto need_more_data;
/* 3: trick_mode_control */
trick_mode_flags = *data++;
GST_DEBUG ("%x DSM trick mode found, flags 0x%02x", filter->id,
trick_mode_flags);
/* fast_forward */
if ((trick_mode_flags & 0xe0) == 0x00) {
}
/* slow motion */
else if ((trick_mode_flags & 0xe0) == 0x20) {
}
/* freeze frame */
else if ((trick_mode_flags & 0xe0) == 0x40) {
}
/* fast reverse */
else if ((trick_mode_flags & 0xe0) == 0x60) {
}
/* slow reverse */
else if ((trick_mode_flags & 0xe0) == 0x80) {
}
/* reserved */
else {
}
header_data_length -= 1;
datalen -= 1;
}
/* additional_copy_info_flag */
if ((flags & 0x04)) {
GST_DEBUG ("%x additional copy info, flags 0x%02x", filter->id, *data);
}
/* PES_CRC_flag */
if ((flags & 0x02)) {
GST_DEBUG ("%x PES_CRC", filter->id);
}
/* PES_extension_flag */
if ((flags & 0x01)) {
flags = *data++;
header_data_length -= 1;
datalen -= 1;
GST_DEBUG ("%x PES_extension, flags 0x%02x", filter->id, flags);
/* PES_private_data_flag */
if ((flags & 0x80)) {
GST_DEBUG ("%x PES_private_data_flag", filter->id);
data += 16;
header_data_length -= 16;
datalen -= 16;
}
/* pack_header_field_flag */
if ((flags & 0x40)) {
guint8 pack_field_length = *data;
GST_DEBUG ("%x pack_header_field_flag, pack_field_length %d",
filter->id, pack_field_length);
data += pack_field_length + 1;
header_data_length -= pack_field_length + 1;
datalen -= pack_field_length + 1;
}
/* program_packet_sequence_counter_flag */
if ((flags & 0x20)) {
GST_DEBUG ("%x program_packet_sequence_counter_flag", filter->id);
data += 2;
header_data_length -= 2;
datalen -= 2;
}
/* P-STD_buffer_flag */
if ((flags & 0x10)) {
GST_DEBUG ("%x P-STD_buffer_flag", filter->id);
data += 2;
header_data_length -= 2;
datalen -= 2;
}
/* PES_extension_flag_2 */
if ((flags & 0x01)) {
guint8 PES_extension_field_length = *data++;
GST_DEBUG ("%x PES_extension_flag_2, len %d",
filter->id, PES_extension_field_length & 0x7f);
if (PES_extension_field_length == 0x81) {
GST_DEBUG ("%x substream id 0x%02x", filter->id, *data);
}
data += PES_extension_field_length & 0x7f;
header_data_length -= (PES_extension_field_length & 0x7f) + 1;
datalen -= (PES_extension_field_length & 0x7f) + 1;
}
}
/* calculate the amount of real data in this PES packet */
data += header_data_length;
datalen -= header_data_length;
} else if (*data == 0x0f) {
/* Not sure what this clause is for */
data++;
datalen--;
} else {
/* Data byte wasn't recognised as a flags byte */
GST_DEBUG ("Unrecognised flags byte 0x%02x\n", *data);
goto lost_sync;
}
push_out:
{
GstBuffer *out;
#ifndef GST_DISABLE_GST_DEBUG
guint16 consumed;
consumed = avail - 6 - datalen;
#endif
if (filter->unbounded_packet == FALSE) {
filter->length -= avail - 6;
GST_DEBUG ("pushing %d, need %d more, consumed %d",
datalen, filter->length, consumed);
} else {
GST_DEBUG ("pushing %d, unbounded packet, consumed %d",
datalen, consumed);
}
if (datalen > 0) {
out = gst_buffer_new_allocate (NULL, datalen, NULL);
gst_buffer_fill (out, 0, data, datalen);
ret = gst_pes_filter_data_push (filter, TRUE, out);
filter->first = FALSE;
} else {
GST_LOG ("first being set to TRUE");
filter->first = TRUE;
ret = GST_FLOW_OK;
}
if (filter->length > 0 || filter->unbounded_packet)
filter->state = STATE_DATA_PUSH;
}
gst_adapter_unmap (filter->adapter);
gst_adapter_flush (filter->adapter, avail);
ADAPTER_OFFSET_FLUSH (avail);
return ret;
need_more_data:
{
if (filter->unbounded_packet == FALSE) {
if (have_size == TRUE) {
GST_DEBUG ("bounded need more data %" G_GSIZE_FORMAT " , lost sync",
gst_adapter_available (filter->adapter));
ret = GST_FLOW_LOST_SYNC;
} else {
GST_DEBUG ("bounded need more data %" G_GSIZE_FORMAT
", breaking for more", gst_adapter_available (filter->adapter));
ret = GST_FLOW_NEED_MORE_DATA;
}
} else {
GST_DEBUG ("unbounded need more data %" G_GSIZE_FORMAT,
gst_adapter_available (filter->adapter));
ret = GST_FLOW_NEED_MORE_DATA;
}
gst_adapter_unmap (filter->adapter);
return ret;
}
skip:
{
gst_adapter_unmap (filter->adapter);
GST_DEBUG ("skipping 0x%02x", filter->id);
gst_adapter_flush (filter->adapter, avail);
ADAPTER_OFFSET_FLUSH (avail);
filter->length -= avail - 6;
if (filter->length > 0 || filter->unbounded_packet)
filter->state = STATE_DATA_SKIP;
return GST_FLOW_OK;
}
lost_sync:
{
gst_adapter_unmap (filter->adapter);
GST_DEBUG ("lost sync");
gst_adapter_flush (filter->adapter, 4);
ADAPTER_OFFSET_FLUSH (4);
return GST_FLOW_LOST_SYNC;
}
}
static GstFlowReturn
gst_pes_filter_data_push (GstPESFilter * filter, gboolean first,
GstBuffer * buffer)
{
GstFlowReturn ret;
GST_LOG ("pushing, first: %d", first);
if (filter->data_cb) {
ret = filter->data_cb (filter, first, buffer, filter->user_data);
} else {
gst_buffer_unref (buffer);
ret = GST_FLOW_OK;
}
return ret;
}
GstFlowReturn
gst_pes_filter_push (GstPESFilter * filter, GstBuffer * buffer)
{
GstFlowReturn ret;
g_return_val_if_fail (filter != NULL, GST_FLOW_ERROR);
g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR);
switch (filter->state) {
case STATE_HEADER_PARSE:
gst_adapter_push (filter->adapter, buffer);
ret = gst_pes_filter_parse (filter);
break;
case STATE_DATA_PUSH:
ret = gst_pes_filter_data_push (filter, filter->first, buffer);
filter->first = FALSE;
break;
case STATE_DATA_SKIP:
gst_buffer_unref (buffer);
ret = GST_FLOW_OK;
break;
default:
goto wrong_state;
}
return ret;
/* ERROR */
wrong_state:
{
GST_DEBUG ("wrong internal state %d", filter->state);
return GST_FLOW_ERROR;
}
}
GstFlowReturn
gst_pes_filter_process (GstPESFilter * filter)
{
GstFlowReturn ret;
gboolean skip = FALSE;
g_return_val_if_fail (filter != NULL, GST_FLOW_ERROR);
switch (filter->state) {
case STATE_HEADER_PARSE:
ret = gst_pes_filter_parse (filter);
break;
case STATE_DATA_SKIP:
skip = TRUE;
/* fallthrough */
case STATE_DATA_PUSH:
if (filter->length > 0 || filter->unbounded_packet) {
gint avail;
avail = gst_adapter_available (filter->adapter);
if (filter->unbounded_packet == FALSE)
avail = MIN (avail, filter->length);
if (skip) {
gst_adapter_flush (filter->adapter, avail);
ADAPTER_OFFSET_FLUSH (avail);
ret = GST_FLOW_OK;
} else {
GstBuffer *out;
out = gst_adapter_take_buffer (filter->adapter, avail);
ret = gst_pes_filter_data_push (filter, filter->first, out);
filter->first = FALSE;
}
if (filter->unbounded_packet == FALSE) {
filter->length -= avail;
if (filter->length == 0)
filter->state = STATE_HEADER_PARSE;
}
} else {
filter->state = STATE_HEADER_PARSE;
ret = GST_FLOW_OK;
}
break;
default:
goto wrong_state;
}
return ret;
/* ERROR */
wrong_state:
{
GST_DEBUG ("wrong internal state %d", filter->state);
return GST_FLOW_ERROR;
}
}
void
gst_pes_filter_flush (GstPESFilter * filter)
{
g_return_if_fail (filter != NULL);
if (filter->adapter) {
gst_adapter_clear (filter->adapter);
if (filter->adapter_offset)
*filter->adapter_offset = G_MAXUINT64;
}
filter->state = STATE_HEADER_PARSE;
}
GstFlowReturn
gst_pes_filter_drain (GstPESFilter * filter)
{
g_return_val_if_fail (filter != NULL, GST_FLOW_ERROR);
gst_pes_filter_flush (filter);
return GST_FLOW_OK;
}