/* * pesparse.c : MPEG PES parsing utility * Copyright (C) 2011 Edward Hervey * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "pesparse.h" GST_DEBUG_CATEGORY_STATIC (pes_parser_debug); #define GST_CAT_DEFAULT pes_parser_debug /** * mpegts_parse_pes_header: * @data: data to parse (starting from, and including, the sync code) * @length: size of @data in bytes * @res: PESHeader to fill (only valid with #PES_PARSING_OK. * * Parses the mpeg-ts PES header located in @data into the @res. * * Returns: #PES_PARSING_OK if the header was fully parsed and valid, * #PES_PARSING_BAD if the header is invalid, or #PES_PARSING_NEED_MORE if more data * is needed to properly parse the header. */ PESParsingResult mpegts_parse_pes_header (const guint8 * data, gsize length, PESHeader * res) { PESParsingResult ret = PES_PARSING_NEED_MORE; gsize origlength = length; const guint8 *origdata = data; guint32 val32; guint8 val8, flags; g_return_val_if_fail (res != NULL, PES_PARSING_BAD); /* The smallest valid PES header is 6 bytes (prefix + stream_id + length) */ if (G_UNLIKELY (length < 6)) goto need_more_data; val32 = GST_READ_UINT32_BE (data); data += 4; length -= 4; if (G_UNLIKELY ((val32 & 0xffffff00) != 0x00000100)) goto bad_start_code; /* Clear the header */ memset (res, 0, sizeof (PESHeader)); res->PTS = -1; res->DTS = -1; res->ESCR = -1; res->stream_id = val32 & 0x000000ff; res->packet_length = GST_READ_UINT16_BE (data); if (res->packet_length) res->packet_length += 6; data += 2; length -= 2; GST_LOG ("stream_id : 0x%08x , packet_length : %d", res->stream_id, res->packet_length); /* Jump if we don't need to parse anything more */ if (G_UNLIKELY (res->stream_id == 0xbc || res->stream_id == 0xbe || res->stream_id == 0xbf || (res->stream_id >= 0xf0 && res->stream_id <= 0xf2) || res->stream_id == 0xff || res->stream_id == 0xf8)) goto done_parsing; if (G_UNLIKELY (length < 3)) goto need_more_data; /* '10' 2 * PES_scrambling_control 2 * PES_priority 1 * data_alignment_indicator 1 * copyright 1 * original_or_copy 1 */ val8 = *data++; if (G_UNLIKELY ((val8 & 0xc0) != 0x80)) goto bad_marker_1; res->scrambling_control = (val8 >> 4) & 0x3; res->flags = val8 & 0xf; GST_LOG ("scrambling_control 0x%0x", res->scrambling_control); GST_LOG ("flags_1: %s%s%s%s%s", val8 & 0x08 ? "priority " : "", val8 & 0x04 ? "data_alignment " : "", val8 & 0x02 ? "copyright " : "", val8 & 0x01 ? "original_or_copy " : "", val8 & 0x0f ? "" : ""); /* PTS_DTS_flags 2 * ESCR_flag 1 * ES_rate_flag 1 * DSM_trick_mode_flag 1 * additional_copy_info_flag 1 * PES_CRC_flag 1 * PES_extension_flag 1*/ flags = *data++; GST_LOG ("flags_2: %s%s%s%s%s%s%s%s%s", flags & 0x80 ? "PTS " : "", flags & 0x40 ? "DTS " : "", flags & 0x20 ? "ESCR" : "", flags & 0x10 ? "ES_rate " : "", flags & 0x08 ? "DSM_trick_mode " : "", flags & 0x04 ? "additional_copy_info " : "", flags & 0x02 ? "CRC " : "", flags & 0x01 ? "extension " : "", flags ? "" : ""); /* PES_header_data_length 8 */ res->header_size = *data++; length -= 3; if (G_UNLIKELY (length < res->header_size)) goto need_more_data; res->header_size += 9; /* We add 9 since that's the offset * of the field in the header*/ GST_DEBUG ("header_size : %d", res->header_size); /* PTS/DTS */ /* PTS_DTS_flags == 0x01 is invalid */ if (G_UNLIKELY ((flags >> 6) == 0x01)) { GST_WARNING ("Invalid PTS_DTS_flag (0x01 is forbidden)"); } if ((flags & 0x80) == 0x80) { /* PTS */ if (G_UNLIKELY (length < 5)) goto need_more_data; READ_TS (data, res->PTS, bad_PTS_value); length -= 5; GST_LOG ("PTS %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT, res->PTS, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (res->PTS))); } if ((flags & 0x40) == 0x40) { /* DTS */ if (G_UNLIKELY (length < 5)) goto need_more_data; READ_TS (data, res->DTS, bad_DTS_value); length -= 5; GST_LOG ("DTS %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT, res->DTS, GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (res->DTS))); } if (flags & 0x20) { /* ESCR */ if (G_UNLIKELY (length < 5)) goto need_more_data; READ_TS (data, res->ESCR, bad_ESCR_value); length -= 5; GST_LOG ("ESCR %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT, res->ESCR, GST_TIME_ARGS (PCRTIME_TO_GSTTIME (res->ESCR))); } if (flags & 0x10) { /* ES_rate */ if (G_UNLIKELY (length < 3)) goto need_more_data; val32 = GST_READ_UINT32_BE (data); data += 3; length -= 3; if (G_UNLIKELY ((val32 & 0x80000100) != 0x80000100)) goto bad_ES_rate; res->ES_rate = ((val32 >> 9) & 0x003fffff) * 50; GST_LOG ("ES_rate : %d", res->ES_rate); } if (flags & 0x08) { /* DSM trick mode */ if (G_UNLIKELY (length < 1)) goto need_more_data; val8 = *data++; length -= 1; res->trick_mode = val8 >> 5; GST_LOG ("trick_mode 0x%x", res->trick_mode); switch (res->trick_mode) { case PES_TRICK_MODE_FAST_FORWARD: case PES_TRICK_MODE_FAST_REVERSE: res->intra_slice_refresh = (val8 >> 2) & 0x1; res->frequency_truncation = val8 & 0x3; /* passthrough */ case PES_TRICK_MODE_FREEZE_FRAME: res->field_id = (val8 >> 3) & 0x3; break; case PES_TRICK_MODE_SLOW_MOTION: case PES_TRICK_MODE_SLOW_REVERSE: res->rep_cntrl = val8 & 0x1f; break; default: break; } } if (flags & 0x04) { /* additional copy info */ if (G_UNLIKELY (length < 1)) goto need_more_data; val8 = *data++; length -= 1; if (G_UNLIKELY (!(val8 & 0x80))) goto bad_original_copy_info_marker; res->additional_copy_info = val8 & 0x7f; GST_LOG ("additional_copy_info : 0x%x", res->additional_copy_info); } if (flags & 0x02) { /* CRC */ if (G_UNLIKELY (length < 2)) goto need_more_data; res->previous_PES_packet_CRC = GST_READ_UINT16_BE (data); GST_LOG ("previous_PES_packet_CRC : 0x%x", res->previous_PES_packet_CRC); data += 2; length -= 2; } /* jump if we don't have a PES extension */ if (!(flags & 0x01)) goto stuffing_byte; if (G_UNLIKELY (length < 1)) goto need_more_data; /* PES extension */ flags = *data++; length -= 1; GST_DEBUG ("PES_extension_flag: %s%s%s%s%s%s", flags & 0x80 ? "PES_private_data " : "", flags & 0x40 ? "pack_header_field " : "", flags & 0x20 ? "program_packet_sequence_counter " : "", flags & 0x10 ? "P-STD_buffer " : "", flags & 0x01 ? "PES_extension_flag_2" : "", flags & 0xf1 ? "" : ""); if (flags & 0x80) { /* PES_private data */ if (G_UNLIKELY (length < 16)) goto need_more_data; res->private_data = data; GST_MEMDUMP ("private_data", data, 16); data += 16; length -= 16; } if (flags & 0x40) { /* pack_header_field */ if (G_UNLIKELY (length < 1)) goto need_more_data; val8 = *data++; length -= 1; if (G_UNLIKELY (length < val8)) goto need_more_data; res->pack_header_size = val8; res->pack_header = data; GST_MEMDUMP ("Pack header data", res->pack_header, res->pack_header_size); data += val8; length -= val8; } if (flags & 0x20) { /* sequence counter */ if (G_UNLIKELY (length < 2)) goto need_more_data; val8 = *data++; if (G_UNLIKELY ((val8 & 0x80) != 0x80)) goto bad_sequence_marker1; res->program_packet_sequence_counter = val8 & 0x7f; GST_LOG ("program_packet_sequence_counter %d", res->program_packet_sequence_counter); val8 = *data++; if (G_UNLIKELY ((val8 & 0x80) != 0x80)) goto bad_sequence_marker2; res->MPEG1_MPEG2_identifier = (val8 >> 6) & 0x1; res->original_stuff_length = val8 & 0x3f; GST_LOG ("MPEG1_MPEG2_identifier : %d , original_stuff_length : %d", res->MPEG1_MPEG2_identifier, res->original_stuff_length); length -= 2; } if (flags & 0x10) { /* P-STD */ if (G_UNLIKELY (length < 2)) goto need_more_data; val8 = *data; if (G_UNLIKELY ((val8 & 0xc0) != 0x40)) goto bad_P_STD_marker; res->P_STD_buffer_size = (GST_READ_UINT16_BE (data) & 0x1fff) << (val8 & 0x20) ? 10 : 7; GST_LOG ("P_STD_buffer_size : %d", res->P_STD_buffer_size); data += 2; length -= 2; } /* jump if we don't have a PES 2nd extension */ if (!flags & 0x01) goto stuffing_byte; /* Extension flag 2 */ if (G_UNLIKELY (length < 1)) goto need_more_data; val8 = *data++; length -= 1; if (!(val8 & 0x80)) goto bad_extension_marker_2; res->extension_field_length = val8 & 0x7f; /* Skip empty extensions */ if (G_UNLIKELY (res->extension_field_length == 0)) goto stuffing_byte; if (G_UNLIKELY (length < res->extension_field_length)) goto need_more_data; flags = *data++; res->extension_field_length -= 1; if (!(flags & 0x80)) { /* Only valid if stream_id_extension_flag == 0x0 */ res->stream_id_extension = flags; GST_LOG ("stream_id_extension : 0x%02x", res->stream_id_extension); } else if (!(flags & 0x01)) { /* Skip broken streams (that use stream_id_extension with highest bit set * for example ...) */ if (G_UNLIKELY (res->extension_field_length < 5)) goto stuffing_byte; GST_LOG ("TREF field present"); data += 5; res->extension_field_length -= 5; } /* Extension field data */ if (res->extension_field_length) { res->stream_id_extension_data = data; GST_MEMDUMP ("stream_id_extension_data", res->stream_id_extension_data, res->extension_field_length); } stuffing_byte: /* Go to the expected data start position */ data = origdata + res->header_size; length = origlength - res->header_size; done_parsing: GST_DEBUG ("origlength:%" G_GSIZE_FORMAT ", length:%" G_GSIZE_FORMAT, origlength, length); res->header_size = origlength - length; ret = PES_PARSING_OK; return ret; /* Errors */ need_more_data: GST_DEBUG ("Not enough data to parse PES header"); return ret; bad_start_code: GST_WARNING ("Wrong packet start code 0x%x != 0x000001xx", val32); return PES_PARSING_BAD; bad_marker_1: GST_WARNING ("Wrong '0x10' marker before PES_scrambling_control (0x%02x)", val8); return PES_PARSING_BAD; bad_PTS_value: GST_WARNING ("bad PTS value"); return PES_PARSING_BAD; bad_DTS_value: GST_WARNING ("bad DTS value"); return PES_PARSING_BAD; bad_ESCR_value: GST_WARNING ("bad ESCR value"); return PES_PARSING_BAD; bad_ES_rate: GST_WARNING ("Invalid ES_rate markers 0x%0x", val32); return PES_PARSING_BAD; bad_original_copy_info_marker: GST_WARNING ("Invalid original_copy_info marker bit: 0x%0x", val8); return PES_PARSING_BAD; bad_sequence_marker1: GST_WARNING ("Invalid program_packet_sequence_counter marker 0x%0x", val8); return PES_PARSING_BAD; bad_sequence_marker2: GST_WARNING ("Invalid program_packet_sequence_counter marker 0x%0x", val8); return PES_PARSING_BAD; bad_P_STD_marker: GST_WARNING ("Invalid P-STD_buffer marker 0x%0x", val8); return PES_PARSING_BAD; bad_extension_marker_2: GST_WARNING ("Invalid extension_field_2 marker 0x%0x", val8); return PES_PARSING_BAD; } void init_pes_parser (void) { GST_DEBUG_CATEGORY_INIT (pes_parser_debug, "pesparser", 0, "MPEG PES parser"); }