/* * 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, 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 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstmpegdefs.h" #include "gstpesfilter.h" GST_DEBUG_CATEGORY (gstflupesfilter_debug); #define GST_CAT_DEFAULT (gstflupesfilter_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 & 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; guint16 STD_buffer_size_bound; const guint8 *data; gint avail, datalen; gboolean have_size = FALSE; /* read start code and length */ if (!(data = gst_adapter_peek (filter->adapter, 6))) goto need_more_data; /* 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); /* see how much is available */ avail = gst_adapter_available (filter->adapter); 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 < 7) goto need_more_data; /* read more data, either the whole packet if there is a length * or whatever we have available if this in an unbounded packet. */ if (!(data = gst_adapter_peek (filter->adapter, avail))) goto need_more_data; /* 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; } 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; guint16 consumed; consumed = avail - 6 - datalen; 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 (); GST_BUFFER_DATA (out) = g_memdup (data, datalen); GST_BUFFER_SIZE (out) = datalen; GST_BUFFER_MALLOCDATA (out) = GST_BUFFER_DATA (out); 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_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 %d, lost sync", gst_adapter_available (filter->adapter)); ret = GST_FLOW_LOST_SYNC; } else { GST_DEBUG ("bounded need more data %d, breaking for more", gst_adapter_available (filter->adapter)); ret = GST_FLOW_NEED_MORE_DATA; } } else { GST_DEBUG ("unbounded need more data %d", gst_adapter_available (filter->adapter)); ret = GST_FLOW_NEED_MORE_DATA; } return ret; } skip: { 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_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; }