| /* |
| * pesparse.c : MPEG PES parsing utility |
| * Copyright (C) 2011 Edward Hervey <bilboed@gmail.com> |
| * |
| * 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 <stdlib.h> |
| #include <string.h> |
| |
| #include <glib.h> |
| |
| #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_assert (res != NULL); |
| |
| /* 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 ? "" : "<none>"); |
| |
| /* 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 ? "" : "<none>"); |
| |
| /* 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 ? "" : "<none>"); |
| |
| 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 |
| * '01' : 2 bits |
| * P-STD_buffer_scale : 1 bit |
| * P-STD_buffer_size : 13 bits |
| * */ |
| if (G_UNLIKELY (length < 2)) |
| goto need_more_data; |
| val8 = *data; |
| if (G_UNLIKELY ((val8 & 0xc0) != 0x40)) |
| goto bad_P_STD_marker; |
| /* If P-STD_buffer_scale is 0 |
| * multiply by 128 (i.e. << 7), |
| * else |
| * multiply by 1024 (i.e. << 10) |
| */ |
| 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"); |
| } |