blob: e3b65075bab8ac2d620e7f253c47f46a918c1ae2 [file] [log] [blame]
/*
* 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;
}