From 6f9a6cc2badd5ca24586088a7c9b1d45a6ff2f34 Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Sun, 17 Jul 2011 14:17:10 +0200 Subject: [PATCH] mpegtsdemux: Add standalone PES parser --- gst/mpegtsdemux/Makefile.am | 6 +- gst/mpegtsdemux/pesparse.c | 423 ++++++++++++++++++++++++++++++++++++ gst/mpegtsdemux/pesparse.h | 112 ++++++++++ 3 files changed, 539 insertions(+), 2 deletions(-) create mode 100644 gst/mpegtsdemux/pesparse.c create mode 100644 gst/mpegtsdemux/pesparse.h diff --git a/gst/mpegtsdemux/Makefile.am b/gst/mpegtsdemux/Makefile.am index 7d3e663256..55b57d9600 100644 --- a/gst/mpegtsdemux/Makefile.am +++ b/gst/mpegtsdemux/Makefile.am @@ -7,7 +7,8 @@ libgstmpegtsdemux_la_SOURCES = \ mpegtspacketizer.c \ mpegtsparse.c \ payload_parsers.c \ - tsdemux.c + tsdemux.c \ + pesparse.c libgstmpegtsdemux_la_CFLAGS = \ $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \ @@ -25,7 +26,8 @@ noinst_HEADERS = \ mpegtspacketizer.h \ mpegtsparse.h \ payload_parsers.h \ - tsdemux.h + tsdemux.h \ + pesparse.h Android.mk: Makefile.am $(BUILT_SOURCES) androgenizer \ diff --git a/gst/mpegtsdemux/pesparse.c b/gst/mpegtsdemux/pesparse.c new file mode 100644 index 0000000000..902dead994 --- /dev/null +++ b/gst/mpegtsdemux/pesparse.c @@ -0,0 +1,423 @@ +/* + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, 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. + * @offset: Offset in @data to the data to parse. If #PES_PARSING_OK, offset to + * first byte of data after the header. + * + * 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, + gint * offset) +{ + PESParsingResult ret = PES_PARSING_NEED_MORE; + gsize origlength = length; + guint32 val32; + guint8 val8, flags; + + g_return_val_if_fail (res != NULL, PES_PARSING_BAD); + g_return_val_if_fail (offset != NULL, PES_PARSING_BAD); + g_return_val_if_fail (*offset < length, PES_PARSING_BAD); + + data += *offset; + length -= *offset; + + /* 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); + 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); + + /* 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_DEBUG ("PES_flag 0x%02x", flags); + + /* PES_header_data_length 8 */ + val8 = *data++; + length -= 3; + if (G_UNLIKELY (length < val8)) + goto need_more_data; + + /* PTS/DTS */ + + /* PTS_DTS_flags == 0x01 is invalid */ + if (G_UNLIKELY ((flags >> 6) == 0x01)) + goto bad_PTS_DTS_flags; + + 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 >> 1; + 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 0x%02x", flags); + + 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++; + /* GRMBL, this is most often wrong */ + if (G_UNLIKELY ((val8 & 0x80) != 0x80)) + goto bad_sequence_marker1; + res->program_packet_sequence_counter = val8 * 0x70; + GST_LOG ("program_packet_sequence_counter %d", + res->program_packet_sequence_counter); + + val8 = *data++; + /* GRMBL, this is most often wrong */ + 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; + } + + if (flags & 0x01) { + /* 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; + if (G_UNLIKELY (length < res->extension_field_length + 1)) + goto need_more_data; + + GST_LOG ("extension_field_length : %d", res->extension_field_length); + + if (res->extension_field_length) { + flags = *data++; + /* Only valid if stream_id_extension_flag == 0x0 */ + if (!(flags & 0x80)) { + res->stream_id_extension = flags & 0x7f; + GST_LOG ("stream_id_extension : 0x%02x", res->stream_id_extension); + res->stream_id_extension_data = data; + GST_MEMDUMP ("stream_id_extension_data", + res->stream_id_extension_data, res->extension_field_length); + } else + GST_WARNING ("What are we meant to do ??"); + data += res->extension_field_length; + } + length -= res->extension_field_length + 1; + } + +stuffing_byte: + /* There can be no more than 32 stuff bytes */ + while (length && *data == 0xff) { + data++; + length--; + } + +done_parsing: + GST_DEBUG ("origlength:%d, length:%d", origlength, length); + + /* Update the length based on parsed size */ + if (res->packet_length) + res->packet_length += 6; + res->header_size = origlength - length; + *offset += res->header_size; + 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_DTS_flags: + GST_WARNING ("Invalid '0x01' PTS_DTS_flags"); + 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"); +} diff --git a/gst/mpegtsdemux/pesparse.h b/gst/mpegtsdemux/pesparse.h new file mode 100644 index 0000000000..0d5214671a --- /dev/null +++ b/gst/mpegtsdemux/pesparse.h @@ -0,0 +1,112 @@ +/* + * pesparse.h : 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __PES_PARSE_H__ +#define __PES_PARSE_H__ + +#include +#include "gstmpegdefs.h" + +G_BEGIN_DECLS + +typedef enum { + PES_FLAG_PRIORITY = 1 << 3, /* PES_priority (present: high-priority) */ + PES_FLAG_DATA_ALIGNMENT = 1 << 2, /* data_alignment_indicator */ + PES_FLAG_COPYRIGHT = 1 << 1, /* copyright */ + PES_FLAG_ORIGINAL_OR_COPY = 1 << 0 /* original_or_copy */ +} PESHeaderFlags; + +typedef enum { + PES_TRICK_MODE_FAST_FORWARD = 0x000, + PES_TRICK_MODE_SLOW_MOTION = 0x001, + PES_TRICK_MODE_FREEZE_FRAME = 0x010, + PES_TRICK_MODE_FAST_REVERSE = 0x011, + PES_TRICK_MODE_SLOW_REVERSE = 0x100, + /* ... */ + PES_TRICK_MODE_INVALID = 0xfff /* Not present or invalid */ +} PESTrickModeControl; + +typedef enum { + PES_FIELD_ID_TOP_ONLY = 0x00, /* Display from top field only */ + PES_FIELD_ID_BOTTOM_ONLY = 0x01, /* Display from bottom field only */ + PES_FIELD_ID_COMPLETE_FRAME = 0x10, /* Display complete frame */ + PES_FIELD_ID_INVALID = 0x11 /* Reserved/Invalid */ +} PESFieldID; + +typedef enum { + PES_PARSING_OK = 0, /* Header fully parsed and valid */ + PES_PARSING_BAD = 1, /* Header invalid (CRC error for ex) */ + PES_PARSING_NEED_MORE = 2 /* Not enough data to parse header */ +} PESParsingResult; + +typedef struct { + guint8 stream_id; /* See ID_* in gstmpegdefs.h */ + guint16 packet_length; /* The size of the remaining data + * (if 0 => unbounded packet) */ + guint16 header_size; /* The complete size of the PES header */ + + /* All remaining entries in this structure are optional */ + guint8 scrambling_control; /* 0x00 : Not scrambled/unspecified, + * The following are according to ETSI TS 101 154 + * 0x01 : reserved for future DVB use + * 0x10 : PES packet scrambled with Even key + * 0x11 : PES packet scrambled with Odd key + */ + PESHeaderFlags flags; + + guint64 PTS; /* PTS (-1 if not present or invalid) */ + guint64 DTS; /* DTS (-1 if not present or invalid) */ + guint64 ESCR; /* ESCR (-1 if not present or invalid) */ + + guint32 ES_rate; /* in bytes/seconds (0 if not present or invalid) */ + PESTrickModeControl trick_mode; + + /* Only valid for _FAST_FORWARD, _FAST_REVERSE and _FREEZE_FRAME */ + PESFieldID field_id; + /* Only valid for _FAST_FORWARD and _FAST_REVERSE */ + gboolean intra_slice_refresh; + guint8 frequency_truncation; + /* Only valid for _SLOW_FORWARD and _SLOW_REVERSE */ + guint8 rep_cntrl; + + guint8 additional_copy_info; /* Private data */ + guint16 previous_PES_packet_CRC; + + /* Extension fields */ + const guint8* private_data; /* PES_private_data, 16 bytes long */ + guint8 pack_header_size; /* Size of pack_header in bytes */ + const guint8* pack_header; + gint8 program_packet_sequence_counter; /* -1 if not present or invalid */ + gboolean MPEG1_MPEG2_identifier; + guint8 original_stuff_length; + + guint32 P_STD_buffer_size; /* P-STD buffer size in bytes (0 if invalid + * or not present */ + + gsize extension_field_length; + guint8 stream_id_extension; /* Only valid if stream_id == ID_EXTENDED_STREAM_ID */ + const guint8* stream_id_extension_data; +} PESHeader; + +PESParsingResult mpegts_parse_pes_header (const guint8* data, gsize size, + PESHeader *res, gint *offset); +void init_pes_parser (void); +G_END_DECLS +#endif /* __PES_PARSE_H__ */