| /* |
| * 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; |
| } |