diff options
Diffstat (limited to 'gst/mpegtsdemux')
-rw-r--r-- | gst/mpegtsdemux/Makefile.am | 27 | ||||
-rw-r--r-- | gst/mpegtsdemux/TODO | 117 | ||||
-rw-r--r-- | gst/mpegtsdemux/gstmpegdefs.h | 217 | ||||
-rw-r--r-- | gst/mpegtsdemux/gstmpegdesc.c | 205 | ||||
-rw-r--r-- | gst/mpegtsdemux/gstmpegdesc.h | 338 | ||||
-rw-r--r-- | gst/mpegtsdemux/gsttsdemux.c | 47 | ||||
-rw-r--r-- | gst/mpegtsdemux/mpegtsbase.c | 1258 | ||||
-rw-r--r-- | gst/mpegtsdemux/mpegtsbase.h | 169 | ||||
-rw-r--r-- | gst/mpegtsdemux/mpegtspacketizer.c | 2714 | ||||
-rw-r--r-- | gst/mpegtsdemux/mpegtspacketizer.h | 167 | ||||
-rw-r--r-- | gst/mpegtsdemux/mpegtsparse.c | 718 | ||||
-rw-r--r-- | gst/mpegtsdemux/mpegtsparse.h | 71 | ||||
-rw-r--r-- | gst/mpegtsdemux/tsdemux.c | 1491 | ||||
-rw-r--r-- | gst/mpegtsdemux/tsdemux.h | 78 |
14 files changed, 7617 insertions, 0 deletions
diff --git a/gst/mpegtsdemux/Makefile.am b/gst/mpegtsdemux/Makefile.am new file mode 100644 index 000000000..d3d2c3ed5 --- /dev/null +++ b/gst/mpegtsdemux/Makefile.am @@ -0,0 +1,27 @@ +plugin_LTLIBRARIES = libgstmpegtsdemux.la + +libgstmpegtsdemux_la_SOURCES = \ + gsttsdemux.c \ + gstmpegdesc.c \ + mpegtsbase.c \ + mpegtspacketizer.c \ + mpegtsparse.c \ + tsdemux.c + +libgstmpegtsdemux_la_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) $(GST_CFLAGS) +libgstmpegtsdemux_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgsttag-$(GST_MAJORMINOR) \ + $(GST_BASE_LIBS) $(GST_LIBS) +libgstmpegtsdemux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstmpegtsdemux_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = \ + gstmpegdefs.h \ + gstmpegdesc.h \ + mpegtsbase.h \ + mpegtspacketizer.h \ + mpegtsparse.h \ + tsdemux.h + diff --git a/gst/mpegtsdemux/TODO b/gst/mpegtsdemux/TODO new file mode 100644 index 000000000..b8f636687 --- /dev/null +++ b/gst/mpegtsdemux/TODO @@ -0,0 +1,117 @@ +mpegtsparse rebasing +-------------------- + +Rationale : +----------- + + mpegtsparse code is more sane to handle and work with. + + We need a modular demuxer + + We need to avoid duplicating code regarding mpeg-ts in a gazillion + elements and allow easy creatiof new elements. + + +Battleplan : +------------ +* Figure out code from mpegtsparse which would be also needed for a +mpeg-ts demuxer (ex: packet/psi/pcr parsing). +* Extract common code into a base mpegtsbase class. +* Refactor mpegtsparse to subclass that base class. +* Create a minimalistic demuxer that creates pads (based on PSI info) +and outputs ES packets (let's say mpeg audio and video to start with) + +Potential subclasses : +---------------------- +* MpegTSParse : Program splitter. Given an incoming multi-program + mpeg-ts stream, it can provide request pads for each program. Each + of those pads will contain the ts packets specific to that program. + +* TSDemux : Program demuxer. Given an incoming single or multi-program + mpeg-ts stream, it will reconstruct the original Program Streams of + the selected program and output them on dynamically created pads. + +* HDVSplitter : Given an incoming HDV mpeg-ts stream, it will locate + the beginning of new scenes and output a mpeg-ts stream with the + PAT/PMT/AUX packets properly ordered and marked with DISCONT, so + that the following pipeline will automatically cut up a tape dump + into individual scenes: + filesrc ! hdvsplit ! multifilesink next-file=discont + +Code/Design common to a program-spliter and a demuxer : +------------------------------------------------------- +* Parsing TS packets +* Establishing PAT/PMT mapping +* Handling the notions of Programs/Streams +* Seeking ? + + One proposal... would be to have the base class automatically create + all the structures (and relationships) for the following objects: + + * Programs (from PAT/PMT, dunno if it could come from something + else) + * Program id + * Streams contained in that program (with links to them) + * Which stream contains the PCR + * Metadata ? + * Streams (ideally... in a table for fast access) + * We want to be able to have stream-type specific information + easily accessible also (like mpeg video specific data) + * Maybe some other info ??? + + The subclasses would then be able to make their own decision based + on those objects. + Maybe we could have some virtual methods that will be called when a + new program is detected, a new stream is added, etc... + + It is the subclass who decides what's to do with a given packet once + it's been parsed. + tsparse : forward it as-is to the pad corresponding to the program + tsdemux : forward it to the proper PS parser + hdvsplit : ? + + +Ideas to be taken into account for a proper demuxer : +----------------------------------------------------- +* Push-based (with inacurrate seeking) +* Pull-based (with fast *AND* accurate seeking) +* Modular system to add stream-type specific helper parsing + * Doesn't have to be fully fledged, just enough to help any kind of + seeking and scanning code. +* ... + +Problems to figure out : +------------------------ +* clock + Needed for proper dvb playback. mpegtsdemux currently does internal + clock estimation... to provide a clock with PCR estimations. + A proper way to solve that would be to timestamp the buffers at the + source element using the system clock, and then adjusting the PCR + against those values. (i.e. doing the opposite of what's done in + mpegtsdemux, but it will be more accurate since the timestamping is + done at the source). + + +Bugs that need fixing : +----------------------- +* Perfomance : Creation/Destruction of buffers is slow + * => This is due to g_type_instance_create using a dogslow rwlock + which take up to 50% of gst_adapter_take_buffer() + => Bugzilla #585375 (performance and contention problems) + +Code structure: + + MpegTSBase + +--- MpegTSParse + +--- TSDemux + + +Known limitations and problems : +-------------------------------- +* mpegtspacketizer + * Assumes 188 bytes packets. It should support all modes. + * offset/timestamp of incoming buffers need to be carried on to the + sub-buffers in order for several demuxer features to work correctly. +* mpegtsparser + * SERIOUS room for improvement performance-wise (see callgrind) + diff --git a/gst/mpegtsdemux/gstmpegdefs.h b/gst/mpegtsdemux/gstmpegdefs.h new file mode 100644 index 000000000..268234f25 --- /dev/null +++ b/gst/mpegtsdemux/gstmpegdefs.h @@ -0,0 +1,217 @@ +/* + * + * 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 <wim@fluendo.com> + */ + +#ifndef __GST_MPEG_DEFS_H__ +#define __GST_MPEG_DEFS_H__ + +/* + * 1011 1100 program_stream_map + * 1011 1101 private_stream_1 + * 1011 1110 padding_stream + * 1011 1111 private_stream_2 + * 110x xxxx ISO/IEC 13818-3 or ISO/IEC 11172-3 audio stream number x xxxx + * 1110 xxxx ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 video stream number xxxx + * 1111 0000 ECM_stream + * 1111 0001 EMM_stream + * 1111 0010 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A or ISO/IEC 13818-6_DSMCC_stream + * 1111 0011 ISO/IEC_13522_stream + * 1111 0100 ITU-T Rec. H.222.1 type A + * 1111 0101 ITU-T Rec. H.222.1 type B + * 1111 0110 ITU-T Rec. H.222.1 type C + * 1111 0111 ITU-T Rec. H.222.1 type D + * 1111 1000 ITU-T Rec. H.222.1 type E + * 1111 1001 ancillary_stream + * 1111 1010 E 1111 1110 reserved data stream + * 1111 1111 program_stream_directory + */ + +#define ID_PS_END_CODE 0x000001B9 +#define ID_PS_PACK_START_CODE 0x000001BA +#define ID_PS_SYSTEM_HEADER_START_CODE 0x000001BB +#define ID_PS_PROGRAM_STREAM_MAP 0x000001BC +#define ID_PRIVATE_STREAM_1 0x000001BD +#define ID_PADDING_STREAM 0x000001BE +#define ID_PRIVATE_STREAM_2 0x000001BF +#define ID_ISO_IEC_MPEG12_AUDIO_STREAM_0 0x000001C0 +#define ID_ISO_IEC_MPEG12_AUDIO_STREAM_32 0x000001DF +#define ID_ISO_IEC_MPEG12_VIDEO_STREAM_0 0x000001E0 +#define ID_ISO_IEC_MPEG12_VIDEO_STREAM_16 0x000001EF +#define ID_ECM_STREAM 0x000001F0 +#define ID_EMM_STREAM 0x000001F1 +#define ID_DSMCC_STREAM 0x000001F2 +#define ID_ISO_IEC_13522_STREAM 0x000001F3 +#define ID_ITU_TREC_H222_TYPE_A_STREAM 0x000001F4 +#define ID_ITU_TREC_H222_TYPE_B_STREAM 0x000001F5 +#define ID_ITU_TREC_H222_TYPE_C_STREAM 0x000001F6 +#define ID_ITU_TREC_H222_TYPE_D_STREAM 0x000001F7 +#define ID_ITU_TREC_H222_TYPE_E_STREAM 0x000001F8 +#define ID_ANCILLARY_STREAM 0x000001F9 +#define ID_RESERVED_STREAM_1 0x000001FA +#define ID_RESERVED_STREAM_2 0x000001FB +#define ID_EXTENDED_METADATA 0x000001FC +#define ID_EXTENDED_STREAM_ID 0x000001FD +#define ID_RESERVED_STREAM_3 0x000001FE +#define ID_PROGRAM_STREAM_DIRECTORY 0x000001FF + +#define PACKET_VIDEO_START_CODE 0x000001E0 +#define PACKET_AUDIO_START_CODE 0x000001C0 +#define PICTURE_START_CODE 0x00000100 +#define USER_DATA_START_CODE 0x000001B2 +#define SEQUENCE_HEADER_CODE 0x000001B3 +#define SEQUENCE_ERROR_CODE 0x000001B4 +#define EXTENSION_START_CODE 0x000001B5 +#define SEQUENCE_END_CODE 0x000001B7 +#define GROUP_START_CODE 0x000001B8 + +#define AC3_SYNC_WORD 0x0b770000 + +#define MPEG_TS_SYNC_BYTE 0x00000047 + +#define PID_PROGRAM_ASSOCIATION_TABLE 0x0000 +#define PID_CONDITIONAL_ACCESS_TABLE 0x0001 +#define PID_RESERVED_FIRST 0x0002 +#define PID_RESERVED_LAST 0x0010 +#define PID_NULL_PACKET 0x1FFF + +#define PID_TYPE_UNKNOWN 0 +#define PID_TYPE_RESERVED 1 +#define PID_TYPE_PROGRAM_ASSOCIATION 2 +#define PID_TYPE_CONDITIONAL_ACCESS 3 +#define PID_TYPE_PROGRAM_MAP 4 +#define PID_TYPE_ELEMENTARY 5 +#define PID_TYPE_NULL_PACKET 6 +#define PID_TYPE_PRIVATE_SECTION 7 + +/* Stream type assignments + * + * 0x00 ITU-T | ISO/IEC Reserved + * 0x01 ISO/IEC 11172 Video + * 0x02 ITU-T Rec. H.262 | ISO/IEC 13818-2 Video or + * ISO/IEC 11172-2 constrained parameter video + * stream + * 0x03 ISO/IEC 11172 Audio + * 0x04 ISO/IEC 13818-3 Audio + * 0x05 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 + * private_sections + * 0x06 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 PES + * packets containing private data + * 0x07 ISO/IEC 13522 MHEG + * 0x08 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A + * DSM CC + * 0x09 ITU-T Rec. H.222.1 + * 0x0A ISO/IEC 13818-6 type A + * 0x0B ISO/IEC 13818-6 type B + * 0x0C ISO/IEC 13818-6 type C + * 0x0D ISO/IEC 13818-6 type D + * 0x0E ISO/IEC 13818-1 auxiliary + * 0x0F-0x7F ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Reserved + * 0x80-0xFF User Private + */ +#define ST_RESERVED 0x00 +#define ST_VIDEO_MPEG1 0x01 +#define ST_VIDEO_MPEG2 0x02 +#define ST_AUDIO_MPEG1 0x03 +#define ST_AUDIO_MPEG2 0x04 +#define ST_PRIVATE_SECTIONS 0x05 +#define ST_PRIVATE_DATA 0x06 +#define ST_MHEG 0x07 +#define ST_DSMCC 0x08 +#define ST_H222_1 0x09 + +/* later extensions */ +#define ST_AUDIO_AAC 0x0f +#define ST_VIDEO_MPEG4 0x10 +#define ST_VIDEO_H264 0x1b + +/* Un-official Dirac extension */ +#define ST_VIDEO_DIRAC 0xd1 + +/* private stream types */ +#define ST_PS_AUDIO_AC3 0x81 +#define ST_PS_AUDIO_DTS 0x8a +#define ST_PS_AUDIO_LPCM 0x8b +#define ST_PS_DVD_SUBPICTURE 0xff +/* Blu-ray related */ +#define ST_BD_AUDIO_LPCM 0x80 +#define ST_BD_AUDIO_AC3 0x81 +#define ST_BD_AUDIO_DTS 0x82 +#define ST_BD_AUDIO_AC3_TRUE_HD 0x83 +#define ST_BD_AUDIO_AC3_PLUS 0x84 +#define ST_BD_AUDIO_DTS_HD 0x85 +#define ST_BD_AUDIO_DTS_HD_MASTER_AUDIO 0x86 +#define ST_BD_AUDIO_EAC3 0x87 +#define ST_BD_PGS_SUBPICTURE 0x90 +#define ST_BD_IGS 0x91 +#define ST_BD_SUBTITLE 0x92 +#define ST_BD_SECONDARY_AC3_PLUS 0xa1 +#define ST_BD_SECONDARY_DTS_HD 0xa2 + +/* defined for VC1 extension in RP227 */ +#define ST_PRIVATE_EA 0xea + +/* HDV AUX stream mapping + * 0xA0 ISO/IEC 61834-11 + * 0xA1 ISO/IEC 61834-11 + */ +#define ST_HDV_AUX_A 0xa0 +#define ST_HDV_AUX_V 0xa1 + +/* Un-official time-code stream */ +#define ST_PS_TIMECODE 0xd2 + +/* Internal stream types >= 0x100 */ +#define ST_GST_AUDIO_RAWA52 0x181 + /* Used when we don't yet know which stream type it will be in a PS stream */ +#define ST_GST_VIDEO_MPEG1_OR_2 0x102 + +#define CLOCK_BASE 9LL +#define CLOCK_FREQ (CLOCK_BASE * 10000) + +#define PCRTIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \ + GST_MSECOND/10, 300 * CLOCK_BASE)) +#define MPEGTIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \ + GST_MSECOND/10, CLOCK_BASE)) +#define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \ + CLOCK_BASE, GST_MSECOND/10)) + +#define MPEG_MUX_RATE_MULT 50 + +/* sync:4 == 00xx ! pts:3 ! 1 ! pts:15 ! 1 | pts:15 ! 1 */ +#define READ_TS(data, target, lost_sync_label) \ + if ((*data & 0x01) != 0x01) goto lost_sync_label; \ + target = ((guint64) (*data++ & 0x0E)) << 29; \ + target |= ((guint64) (*data++ )) << 22; \ + if ((*data & 0x01) != 0x01) goto lost_sync_label; \ + target |= ((guint64) (*data++ & 0xFE)) << 14; \ + target |= ((guint64) (*data++ )) << 7; \ + if ((*data & 0x01) != 0x01) goto lost_sync_label; \ + target |= ((guint64) (*data++ & 0xFE)) >> 1; + +/* some extra GstFlowReturn values used internally */ +#define GST_FLOW_NEED_MORE_DATA GST_FLOW_CUSTOM_SUCCESS +#define GST_FLOW_LOST_SYNC GST_FLOW_CUSTOM_SUCCESS_1 + +#endif /* __GST_MPEG_DEFS_H__ */ diff --git a/gst/mpegtsdemux/gstmpegdesc.c b/gst/mpegtsdemux/gstmpegdesc.c new file mode 100644 index 000000000..4abf5eb36 --- /dev/null +++ b/gst/mpegtsdemux/gstmpegdesc.c @@ -0,0 +1,205 @@ +/* + * + * 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 <wim@fluendo.com> + * + */ + +#include <string.h> + +#include <gst/gst.h> + +#include "gstmpegdesc.h" + +GST_DEBUG_CATEGORY (gstmpegtsdesc_debug); +#define GST_CAT_DEFAULT (gstmpegtsdesc_debug) + +void +gst_mpeg_descriptor_free (GstMPEGDescriptor * desc) +{ + g_return_if_fail (desc != NULL); + + g_free (desc); +} + +static guint +gst_mpeg_descriptor_parse_1 (guint8 * data, guint size) +{ + guint8 tag; + guint8 length; + + /* need at least 2 bytes for tag and length */ + if (size < 2) + return 0; + + tag = *data++; + length = *data++; + size -= 2; + + GST_DEBUG ("tag: 0x%02x, length: %d", tag, length); + + if (length > size) + return 0; + + GST_MEMDUMP ("tag contents:", data, length); + + return length + 2; +} + +GstMPEGDescriptor * +gst_mpeg_descriptor_parse (guint8 * data, guint size) +{ + guint8 *current; + guint consumed, total, n_desc; + GstMPEGDescriptor *result; + + g_return_val_if_fail (data != NULL, NULL); + + current = data; + total = 0; + n_desc = 0; + + do { + consumed = gst_mpeg_descriptor_parse_1 (current, size); + + if (consumed > 0) { + current += consumed; + total += consumed; + size -= consumed; + n_desc++; + } + } + while (consumed > 0); + + GST_DEBUG ("parsed %d descriptors", n_desc); + + if (total == 0) + return NULL; + + result = g_malloc (sizeof (GstMPEGDescriptor) + total); + result->n_desc = n_desc; + result->data_length = total; + result->data = ((guint8 *) result) + sizeof (GstMPEGDescriptor); + + memcpy (result->data, data, total); + + return result; +} + +guint +gst_mpeg_descriptor_n_desc (GstMPEGDescriptor * desc) +{ + g_return_val_if_fail (desc != NULL, 0); + + return desc->n_desc; +} + +guint8 * +gst_mpeg_descriptor_find (GstMPEGDescriptor * desc, gint tag) +{ + guint8 length; + guint8 *current; + guint size; + + g_return_val_if_fail (desc != NULL, NULL); + + current = desc->data; + length = desc->data_length; + + while (length > 0) { + if (DESC_TAG (current) == tag) + return current; + + size = DESC_LENGTH (current) + 2; + + current += size; + length -= size; + } + return NULL; +} + +/* array needs freeing afterwards */ +GArray * +gst_mpeg_descriptor_find_all (GstMPEGDescriptor * desc, gint tag) +{ + GArray *all; + + guint8 length; + guint8 *current; + guint size; + + g_return_val_if_fail (desc != NULL, NULL); + all = g_array_new (TRUE, TRUE, sizeof (guint8 *)); + + current = desc->data; + length = desc->data_length; + + while (length > 0) { + if (DESC_TAG (current) == tag) + g_array_append_val (all, current); + size = DESC_LENGTH (current) + 2; + + current += size; + length -= size; + } + + GST_DEBUG ("found tag 0x%02x %d times", tag, all->len); + + return all; +} + +guint8 * +gst_mpeg_descriptor_nth (GstMPEGDescriptor * desc, guint i) +{ + guint8 length; + guint8 *current; + guint size; + + g_return_val_if_fail (desc != NULL, NULL); + + if (i > desc->n_desc) + return NULL; + + current = desc->data; + length = desc->data_length; + + while (length > 0) { + if (i == 0) + return current; + + size = DESC_LENGTH (current) + 2; + + current += size; + length -= size; + i--; + + } + return NULL; +} + +void +gst_mpegtsdesc_init_debug (void) +{ + GST_DEBUG_CATEGORY_INIT (gstmpegtsdesc_debug, "mpegtsdesc", 0, + "MPEG transport stream parser (descriptor)"); +} diff --git a/gst/mpegtsdemux/gstmpegdesc.h b/gst/mpegtsdemux/gstmpegdesc.h new file mode 100644 index 000000000..06aa17b95 --- /dev/null +++ b/gst/mpegtsdemux/gstmpegdesc.h @@ -0,0 +1,338 @@ +/* + * + * 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 <wim@fluendo.com> + * + */ + +#ifndef __GST_MPEG_DESC_H__ +#define __GST_MPEG_DESC_H__ + +#include <glib.h> +/* + * descriptor_tag TS PS Identification + * 0 n/a n/a Reserved + * 1 n/a n/a Reserved + * 2 X X video_stream_descriptor + * 3 X X audio_stream_descriptor + * 4 X X hierarchy_descriptor + * 5 X X registration_descriptor + * 6 X X data_stream_alignment_descriptor + * 7 X X target_background_grid_descriptor + * 8 X X video_window_descriptor + * 9 X X CA_descriptor + * 10 X X ISO_639_language_descriptor + * 11 X X system_clock_descriptor + * 12 X X multiplex_buffer_utilization_descriptor + * 13 X X copyright_descriptor + * 14 X maximum bitrate descriptor + * 15 X X private data indicator descriptor + * 16 X X smoothing buffer descriptor + * 17 X STD_descriptor + * 18 X X IBP descriptor + * 19-63 n/a n/a ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Reserved + * 64-255 n/a n/a User Private + */ +#define DESC_VIDEO_STREAM 2 +#define DESC_AUDIO_STREAM 3 +#define DESC_HIERARCHY 4 +#define DESC_REGISTRATION 5 +#define DESC_DATA_STREAM_ALIGNMENT 6 +#define DESC_TARGET_BACKGROUND_GRID 7 +#define DESC_VIDEO_WINDOW 8 +#define DESC_CA 9 +#define DESC_ISO_639_LANGUAGE 10 +#define DESC_SYSTEM_CLOCK 11 +#define DESC_MULTIPLEX_BUFFER_UTILISATION 12 +#define DESC_COPYRIGHT 13 +#define DESC_MAXIMUM_BITRATE 14 +#define DESC_PRIVATE_DATA_INDICATOR 15 +#define DESC_SMOOTHING_BUFFER 16 +#define DESC_STD 17 +#define DESC_IBP 18 + +#define DESC_DIRAC_TC_PRIVATE 0xAC + +/* DVB tags */ +#define DESC_DVB_CAROUSEL_IDENTIFIER 0x13 +#define DESC_DVB_NETWORK_NAME 0x40 +#define DESC_DVB_SERVICE_LIST 0x41 +#define DESC_DVB_STUFFING 0x42 +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM 0x43 +#define DESC_DVB_CABLE_DELIVERY_SYSTEM 0x44 +#define DESC_DVB_VBI_DATA 0x45 +#define DESC_DVB_VBI_TELETEXT 0x46 +#define DESC_DVB_BOUQUET_NAME 0x47 +#define DESC_DVB_SERVICE 0x48 +#define DESC_DVB_COUNTRY_AVAILABILITY 0x49 +#define DESC_DVB_LINKAGE 0x4A +#define DESC_DVB_NVOD_REFERENCE 0x4B +#define DESC_DVB_TIME_SHIFTED_SERVICE 0x4C +#define DESC_DVB_SHORT_EVENT 0x4D +#define DESC_DVB_EXTENDED_EVENT 0x4E +#define DESC_DVB_TIME_SHIFTED_EVENT 0x4F +#define DESC_DVB_COMPONENT 0x50 +#define DESC_DVB_MOSAIC 0x51 +#define DESC_DVB_STREAM_IDENTIFIER 0x52 +#define DESC_DVB_CA_IDENTIFIER 0x53 +#define DESC_DVB_CONTENT 0x54 +#define DESC_DVB_PARENTAL_RATING 0x55 +#define DESC_DVB_TELETEXT 0x56 +#define DESC_DVB_TELEPHONE 0x57 +#define DESC_DVB_LOCAL_TIME_OFFSET 0x58 +#define DESC_DVB_SUBTITLING 0x59 +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM 0x5A +#define DESC_DVB_MULTILINGUAL_NETWORK_NAME 0x5B +#define DESC_DVB_MULTILINGUAL_BOUQUET_NAME 0x5C +#define DESC_DVB_MULTILINGUAL_SERVICE_NAME 0x5D +#define DESC_DVB_MULTILINGUAL_COMPONENT 0x5E +#define DESC_DVB_PRIVATE_DATA 0x5F +#define DESC_DVB_SERVICE_MOVE 0x60 +#define DESC_DVB_SHORT_SMOOTHING_BUFFER 0x61 +#define DESC_DVB_FREQUENCY_LIST 0x62 +#define DESC_DVB_PARTIAL_TRANSPORT_STREAM 0x63 +#define DESC_DVB_DATA_BROADCAST 0x64 +#define DESC_DVB_SCRAMBLING 0x65 +#define DESC_DVB_DATA_BROADCAST_ID 0x66 +#define DESC_DVB_TRANSPORT_STREAM 0x67 +#define DESC_DVB_DSNG 0x68 +#define DESC_DVB_PDC 0x69 +#define DESC_DVB_AC3 0x6A +#define DESC_DVB_ANCILLARY_DATA 0x6B +#define DESC_DVB_CELL_LIST 0x6C +#define DESC_DVB_CELL_FREQUENCY_LINK 0x6D +#define DESC_DVB_ANNOUNCEMENT_SUPPORT 0x6E +#define DESC_DVB_APPLICATION_SIGNALLING 0x6F +#define DESC_DVB_ADAPTATION_FIELD_DATA 0x70 +#define DESC_DVB_SERVICE_IDENTIFIER 0x71 +#define DESC_DVB_SERVICE_AVAILABILITY 0x72 +#define DESC_DVB_DEFAULT_AUTHORITY 0x73 +#define DESC_DVB_RELATED_CONTENT 0x74 +#define DESC_DVB_TVA_ID 0x75 +#define DESC_DVB_CONTENT_IDENTIFIER 0x76 +#define DESC_DVB_TIMESLICE_FEC_IDENTIFIER 0x77 +#define DESC_DVB_ECM_REPETITION_RATE 0x78 +#define DESC_DVB_S2_SATELLITE_DELIVERY_SYSTEM 0x79 +#define DESC_DVB_ENHANCED_AC3 0x7A +#define DESC_DVB_DTS 0x7B +#define DESC_DVB_AAC 0x7C +/* 0x7D and 0x7E are reserved for future use */ +#define DESC_DVB_EXTENSION 0x7F +/* 0x80 - 0xFE are user defined */ +#define DESC_DTG_LOGICAL_CHANNEL 0x83 /* from DTG D-Book */ +/* 0xFF is forbidden */ + +/* common for all descriptors */ +#define DESC_TAG(desc) (desc[0]) +#define DESC_LENGTH(desc) (desc[1]) + +/* video_stream_descriptor */ +#define DESC_VIDEO_STREAM_multiple_framerate_flag(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_VIDEO_STREAM_frame_rate_code(desc) (((desc)[2] & 0x38) >> 3) +#define DESC_VIDEO_STREAM_MPEG_1_only_flag(desc) (((desc)[2] & 0x04) == 0x04) +#define DESC_VIDEO_STREAM_constrained_parameter_flag(desc) (((desc)[2] & 0x02) == 0x02) +#define DESC_VIDEO_STREAM_still_picture_flag(desc) (((desc)[2] & 0x01) == 0x01) +/* if (MPEG_1_only_flag == 1) */ +#define DESC_VIDEO_STREAM_profile_and_level_indication(desc) ((desc)[3]) +#define DESC_VIDEO_STREAM_chroma_format(desc) (((desc)[4] & 0xc0) >> 6) +#define DESC_VIDEO_STREAM_frame_rate_extension_flag(desc) (((desc)[4] & 0x20) == 0x20) + +/* audio_stream_descriptor */ +#define DESC_AUDIO_STREAM_free_format_flag(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_AUDIO_STREAM_ID(desc) (((desc)[2] & 0x40) == 0x40) +#define DESC_AUDIO_STREAM_layer(desc) (((desc)[2] & 0x30) >> 4) +#define DESC_AUDIO_STREAM_variable_rate_audio_indicator(desc) (((desc)[2] & 0x08) == 0x08) + +/* hierarchy_descriptor */ +#define DESC_HIERARCHY_hierarchy_type(desc) (((desc)[2] & 0x0f)) +#define DESC_HIERARCHY_hierarchy_layer_index(desc) (((desc)[3] & 0x3f)) +#define DESC_HIERARCHY_hierarchy_embedded_layer_index(desc) (((desc)[4] & 0x3f)) +#define DESC_HIERARCHY_hierarchy_channel(desc) (((desc)[5] & 0x3f)) + +/* registration_descriptor */ +#define DESC_REGISTRATION_format_identifier(desc) (GST_READ_UINT32_BE ((desc)+2)) +#define DESC_REGISTRATION_additional_ident_info_len(desc) ((desc)[1] - 4) +#define DESC_REGISTRATION_additional_ident_info(desc) (&(desc)[6]) + +/* data_stream_alignment_descriptor */ +#define DESC_DATA_STREAM_ALIGNMENT_alignment_type(desc) ((desc)[2]) + +/* target_background_grid_descriptor */ +#define DESC_TARGET_BACKGROUND_GRID_horizontal_size(desc) (GST_READ_UINT16_BE ((desc)+2) >> 2) +#define DESC_TARGET_BACKGROUND_GRID_vertical_size(desc) ((GST_READ_UINT32_BE ((desc)+2) & 0x0003fff0) >> 4) +#define DESC_TARGET_BACKGROUND_GRID_aspect_ratio_information(desc) ((desc)[5] & 0x0f) + +/* video_window_descriptor */ +#define DESC_VIDEO_WINDOW_horizontal_offset(desc) (GST_READ_UINT16_BE ((desc)+2) >> 2) +#define DESC_VIDEO_WINDOW_vertical_offset(desc) ((GST_READ_UINT32_BE ((desc)+2) & 0x0003fff0) >> 4) +#define DESC_VIDEO_WINDOW_window_priority(desc) ((desc)[5] & 0x0f) + +/* CA_descriptor */ +#define DESC_CA_system_ID(desc) (GST_READ_UINT16_BE ((desc)+2)) +#define DESC_CA_PID(desc) (GST_READ_UINT16_BE ((desc)+2) & 0x1fff) + +/* ISO_639_language_descriptor */ +#define DESC_ISO_639_LANGUAGE_codes_n(desc) ((desc[1]) >> 2) +#define DESC_ISO_639_LANGUAGE_language_code_nth(desc,i) (&(desc[2 + (4*i)])) +#define DESC_ISO_639_LANGUAGE_audio_type_nth(desc,i) ((desc)[5 + (4*i)]) + +/* system_clock_descriptor */ +#define DESC_SYSTEM_CLOCK_external_clock_reference_indicator(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_SYSTEM_CLOCK_clock_accuracy_integer(desc) ((desc)[2] & 0x3f) +#define DESC_SYSTEM_CLOCK_clock_accuracy_exponent(desc) (((desc)[3] & 0xe0) >> 5) + +/* multiplex_buffer_utilization_descriptor */ +#define DESC_MULTIPLEX_BUFFER_UTILISATION_bound_valid_flag(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_MULTIPLEX_BUFFER_UTILISATION_LTW_offset_lower_bound(desc) (GST_READ_UINT16_BE ((desc)+2) & 0x7fff) +#define DESC_MULTIPLEX_BUFFER_UTILISATION_LTW_offset_upper_bound(desc) (GST_READ_UINT16_BE ((desc)+4) & 0x7fff) + +/* copyright_descriptor */ +#define DESC_COPYRIGHT_copyright_identifier(desc) (GST_READ_UINT32_BE ((desc)+2)) +#define DESC_COPYRIGHT_additional_copyright_info_len(desc) ((desc)[1] - 4) +#define DESC_COPYRIGHT_additional_copyright_info(desc) (&(desc)[6]) + +/* maximum_bitrate_descriptor */ +#define DESC_MAXIMUM_BITRAT_maximum_bitrate(desc) (((((guint32)desc[2]) & 0x3f) << 16) | \ + GST_READ_UINT16_BE ((desc)+3)) + +/* private_data_indicator_descriptor */ +#define DESC_PRIVATE_DATA_INDICATOR_indicator(desc) (GST_READ_UINT32_BE(&desc[2])) + +/* smoothing_buffer_descriptor */ +#define DESC_SMOOTHING_BUFFER_sb_leak_rate(desc) (((((guint32)desc[2]) & 0x3f) << 16) | \ + GST_READ_UINT16_BE ((desc)+3)) +#define DESC_SMOOTHING_BUFFER_sb_size(desc) (((((guint32)desc[5]) & 0x3f) << 16) | \ + GST_READ_UINT16_BE ((desc)+6)) +/* STD_descriptor */ +#define DESC_STD_leak_valid_flag(desc) (((desc)[2] & 0x01) == 0x01) + +/* ibp_descriptor */ +#define DESC_IBP_closed_gop_flag(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_IBP_identical_gop_flag(desc) (((desc)[2] & 0x40) == 0x40) +#define DESC_IBP_max_gop_length(desc) (GST_READ_UINT16_BE ((desc)+6) & 0x3fff) + +/* time_code descriptor */ +#define DESC_TIMECODE_video_pid(desc) (GST_READ_UINT16_BE ((desc) + 2) & 0x1fff) + +/* Stream identifier descriptor */ +#define DESC_DVB_STREAM_IDENTIFIER_component_tag(desc) (desc[2]) + +/* DVB Network Name descriptor */ +#define DESC_DVB_NETWORK_NAME_length(desc) (GST_READ_UINT8((desc)+1)) +#define DESC_DVB_NETWORK_NAME_text(desc) (desc+2) + +/* DVB Service Descriptor */ +#define DESC_DVB_SERVICE_type(desc) (desc[2]) +#define DESC_DVB_SERVICE_provider_name_length(desc) (desc[3]) +#define DESC_DVB_SERVICE_provider_name_text(desc) (desc+4) +#define DESC_DVB_SERVICE_name_length(desc) (desc[4 + DESC_DVB_SERVICE_provider_name_length(desc)]) +#define DESC_DVB_SERVICE_name_text(desc) (desc + 5 + DESC_DVB_SERVICE_provider_name_length(desc)) + +/* DVB Component Descriptor */ +#define DESC_DVB_COMPONENT_stream_content(desc) (desc[2] & 0x0F) +#define DESC_DVB_COMPONENT_type(desc) (desc[3]) +#define DESC_DVB_COMPONENT_tag(desc) (desc[4]) +#define DESC_DVB_COMPONENT_language(desc) (desc + 5) + +/* DVB Bouquet Name Descriptor */ +#define DESC_DVB_BOUQUET_NAME_text(desc) (desc + 2) + +/* DVB Short Event Descriptor */ +#define DESC_DVB_SHORT_EVENT_name_text(desc) (desc + 6) +#define DESC_DVB_SHORT_EVENT_name_length(desc) (desc[5]) +#define DESC_DVB_SHORT_EVENT_description_text(desc) (desc + 6 + DESC_DVB_SHORT_EVENT_name_length(desc) + 1) +#define DESC_DVB_SHORT_EVENT_description_length(desc) (desc[6 + DESC_DVB_SHORT_EVENT_name_length(desc)]) + +/* DVB Extended Event Descriptor */ +#define DESC_DVB_EXTENDED_EVENT_descriptor_number(desc) ((desc[2] & 0xF0) >> 4) +#define DESC_DVB_EXTENDED_EVENT_last_descriptor_number(desc) (desc[2] & 0x0F) +#define DESC_DVB_EXTENDED_EVENT_iso639_language_code(desc) (desc + 3) +#define DESC_DVB_EXTENDED_EVENT_items_length(desc) (desc[6]) +#define DESC_DVB_EXTENDED_EVENT_items(desc) (desc + 7) +#define DESC_DVB_EXTENDED_EVENT_text_length(desc) (desc[7 + DESC_DVB_EXTENDED_EVENT_items_length(desc)]) +#define DESC_DVB_EXTENDED_EVENT_text(desc) (desc + 7 + DESC_DVB_EXTENDED_EVENT_items_length(desc) + 1) + +/* DVB Satellite Delivery System Descriptor */ +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_frequency(desc) (desc + 2) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_orbital_position(desc) (desc + 6) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_west_east_flag(desc) ((desc[8] & 0x80) == 0x80) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_polarization(desc) ((desc[8] >> 5) & 0x3) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_modulation(desc) (desc[8] & 0x1F) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_symbol_rate(desc) (desc + 9) +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM_fec_inner(desc) (desc[12] & 0x0F) + +/* DVB Terrestrial Delivery System Descriptor */ +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_frequency(desc) (GST_READ_UINT32_BE((desc) + 2)) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_bandwidth(desc) ((desc[6] >> 5) & 0x7) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_constellation(desc) ((desc[7] >> 6) & 0x3) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_hierarchy(desc) ((desc[7] >> 3) & 0x7) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_code_rate_hp(desc) (desc[7] & 0x7) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_code_rate_lp(desc) ((desc[8] >> 5) & 0x7) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_guard_interval(desc) ((desc[8] >> 3) & 0x3) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_transmission_mode(desc) ((desc[8] >> 1) & 0x3) +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_other_frequency(desc) ((desc[8] & 0x01) == 0x01) + +/* DVB Cable Delivery System Descriptor */ +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_frequency(desc) (desc + 2) +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_fec_outer(desc) (desc[7] & 0x0F) +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_modulation(desc) (desc[8]) +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_symbol_rate(desc) (desc + 9) +#define DESC_DVB_CABLE_DELIVERY_SYSTEM_fec_inner(desc) (desc[12] & 0x0F) + +/* DVB Data Broadcast Descriptor */ +#define DESC_DVB_DATA_BROADCAST_data_broadcast_id(desc) (GST_READ_UINT16_BE((desc) + 2)) +#define DESC_DVB_DATA_BROADCAST_component_tag(desc) (desc[4]) +#define DESC_DVB_DATA_BROADCAST_selector_length(desc) (desc[5]) +#define DESC_DVB_DATA_BROADCAST_selector(desc) (desc + 6) +#define DESC_DVB_DATA_BROADCAST_iso639_language_code(desc) (desc + 6 + DESC_DVB_DATA_BROADCAST_selector_length(desc)) +#define DESC_DVB_DATA_BROADCAST_text_length(desc) (desc + 9 + DESC_DVB_DATA_BROADCAST_selector_length(desc)) +#define DESC_DVB_DATA_BROADCAST_text(desc) (desc + 10 + DESC_DVB_DATA_BROADCAST_selector_length(desc)) + +/* DVB Data Broadcast Id Descriptor */ +#define DESC_DVB_DATA_BROADCAST_ID_data_broadcast_id(desc) (GST_READ_UINT16_BE((desc) + 2)) +#define DESC_DVB_DATA_BROADCAST_ID_id_selector_byte(desc) (desc + 4) + +/* DVB Carousel Identifier Descriptor */ +#define DESC_DVB_CAROUSEL_IDENTIFIER_carousel_id(desc) (GST_READ_UINT32_BE((desc) + 2)) + +/* registration_descriptor format IDs */ +#define DRF_ID_HDMV 0x48444d56 +#define DRF_ID_VC1 0x56432D31 /* defined in RP227 */ + +typedef struct { + guint n_desc; + guint8 data_length; + guint8 *data; +} GstMPEGDescriptor; + +void gst_mpegtsdesc_init_debug (void); +GstMPEGDescriptor* gst_mpeg_descriptor_parse (guint8 *data, guint size); +void gst_mpeg_descriptor_free (GstMPEGDescriptor *desc); + +guint gst_mpeg_descriptor_n_desc (GstMPEGDescriptor *desc); +guint8* gst_mpeg_descriptor_find (GstMPEGDescriptor *desc, gint tag); +GArray* gst_mpeg_descriptor_find_all (GstMPEGDescriptor * desc, gint tag); + +guint8* gst_mpeg_descriptor_nth (GstMPEGDescriptor *desc, guint i); + +#endif /* __GST_MPEG_DESC_H__ */ diff --git a/gst/mpegtsdemux/gsttsdemux.c b/gst/mpegtsdemux/gsttsdemux.c new file mode 100644 index 000000000..fc9aa8ddd --- /dev/null +++ b/gst/mpegtsdemux/gsttsdemux.c @@ -0,0 +1,47 @@ +/* + * + * 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 "mpegtsbase.h" +#include "mpegtspacketizer.h" +#include "mpegtsparse.h" +#include "tsdemux.h" + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_mpegtsbase_plugin_init (plugin)) + return FALSE; + if (!gst_mpegtsparse_plugin_init (plugin)) + return FALSE; + if (!gst_ts_demux_plugin_init (plugin)) + return FALSE; + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "mpegtsdemux", + "MPEG TS demuxer", + plugin_init, VERSION, + GST_LICENSE_UNKNOWN, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst/mpegtsdemux/mpegtsbase.c b/gst/mpegtsdemux/mpegtsbase.c new file mode 100644 index 000000000..8eb89bff2 --- /dev/null +++ b/gst/mpegtsdemux/mpegtsbase.c @@ -0,0 +1,1258 @@ +/* + * mpegtsbase.c - + * Copyright (C) 2007 Alessandro Decina + * 2010 Edward Hervey + * + * Authors: + * Alessandro Decina <alessandro@nnva.org> + * Zaheer Abbas Merali <zaheerabbas at merali dot org> + * Edward Hervey <edward.hervey@collabora.co.uk> + * + * 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 <stdlib.h> +#include <string.h> + +#include <gst/gst-i18n-plugin.h> +#include "mpegtsbase.h" +#include "gstmpegdesc.h" + +/* latency in mseconds */ +#define TS_LATENCY 700 + +#define TABLE_ID_UNSET 0xFF +#define RUNNING_STATUS_RUNNING 4 + +GST_DEBUG_CATEGORY_STATIC (mpegts_base_debug); +#define GST_CAT_DEFAULT mpegts_base_debug + +static GQuark QUARK_PROGRAMS; +static GQuark QUARK_PROGRAM_NUMBER; +static GQuark QUARK_PID; +static GQuark QUARK_PCR_PID; +static GQuark QUARK_STREAMS; +static GQuark QUARK_STREAM_TYPE; + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ") + ); + +enum +{ + ARG_0, + /* FILL ME */ +}; + +static void mpegts_base_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void mpegts_base_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void mpegts_base_dispose (GObject * object); +static void mpegts_base_finalize (GObject * object); +static void mpegts_base_free_program (MpegTSBaseProgram * program); +static void mpegts_base_free_stream (MpegTSBaseStream * ptream); +static gboolean mpegts_base_sink_activate (GstPad * pad); +static gboolean mpegts_base_sink_activate_pull (GstPad * pad, gboolean active); +static gboolean mpegts_base_sink_activate_push (GstPad * pad, gboolean active); +static GstFlowReturn mpegts_base_chain (GstPad * pad, GstBuffer * buf); +static gboolean mpegts_base_sink_event (GstPad * pad, GstEvent * event); +static GstStateChangeReturn mpegts_base_change_state (GstElement * element, + GstStateChange transition); +static void _extra_init (GType type); +static void mpegts_base_get_tags_from_sdt (MpegTSBase * base, + GstStructure * sdt_info); +static void mpegts_base_get_tags_from_eit (MpegTSBase * base, + GstStructure * eit_info); + +GST_BOILERPLATE_FULL (MpegTSBase, mpegts_base, GstElement, GST_TYPE_ELEMENT, + _extra_init); + + +static const guint32 crc_tab[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/* relicenced to LGPL from fluendo ts demuxer */ +static guint32 +mpegts_base_calc_crc32 (guint8 * data, guint datalen) +{ + gint i; + guint32 crc = 0xffffffff; + + for (i = 0; i < datalen; i++) { + crc = (crc << 8) ^ crc_tab[((crc >> 24) ^ *data++) & 0xff]; + } + return crc; +} + +static void +_extra_init (GType type) +{ + QUARK_PROGRAMS = g_quark_from_string ("programs"); + QUARK_PROGRAM_NUMBER = g_quark_from_string ("program-number"); + QUARK_PID = g_quark_from_string ("pid"); + QUARK_PCR_PID = g_quark_from_string ("pcr-pid"); + QUARK_STREAMS = g_quark_from_string ("streams"); + QUARK_STREAM_TYPE = g_quark_from_string ("stream-type"); +} + +static void +mpegts_base_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); +} + +static void +mpegts_base_class_init (MpegTSBaseClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + + element_class = GST_ELEMENT_CLASS (klass); + element_class->change_state = mpegts_base_change_state; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->set_property = mpegts_base_set_property; + gobject_class->get_property = mpegts_base_get_property; + gobject_class->dispose = mpegts_base_dispose; + gobject_class->finalize = mpegts_base_finalize; + +} + +static void +mpegts_base_reset (MpegTSBase * base) +{ + mpegts_packetizer_clear (base->packetizer); + memset (base->is_pes, 0, 8192); + memset (base->known_psi, 0, 8192); + + /* PAT */ + base->known_psi[0] = TRUE; + + /* FIXME : Commenting the Following lines is to be in sync with the following + * commit + * + * 61a885613316ce7657c36a6cd215b43f9dc67b79 + * mpegtsparse: don't free PAT structure which may still be needed later + */ + + /* if (base->pat != NULL) */ + /* gst_structure_free (base->pat); */ + /* base->pat = NULL; */ + /* pmt pids will be added and removed dynamically */ + +} + +static void +mpegts_base_init (MpegTSBase * base, MpegTSBaseClass * klass) +{ + base->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink"); + gst_pad_set_activate_function (base->sinkpad, mpegts_base_sink_activate); + gst_pad_set_activatepull_function (base->sinkpad, + mpegts_base_sink_activate_pull); + gst_pad_set_activatepush_function (base->sinkpad, + mpegts_base_sink_activate_push); + gst_pad_set_chain_function (base->sinkpad, mpegts_base_chain); + gst_pad_set_event_function (base->sinkpad, mpegts_base_sink_event); + gst_element_add_pad (GST_ELEMENT (base), base->sinkpad); + + base->disposed = FALSE; + base->packetizer = mpegts_packetizer_new (); + base->programs = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) mpegts_base_free_program); + + base->is_pes = g_new0 (gboolean, 8192); + base->known_psi = g_new0 (gboolean, 8192); + mpegts_base_reset (base); + base->program_size = sizeof (MpegTSBaseProgram); + base->stream_size = sizeof (MpegTSBaseStream); + + base->mode = BASE_MODE_STREAMING; + base->first_pat_offset = -1; +} + +static void +mpegts_base_dispose (GObject * object) +{ + MpegTSBase *base = GST_MPEGTS_BASE (object); + + if (!base->disposed) { + g_object_unref (base->packetizer); + base->disposed = TRUE; + g_free (base->known_psi); + g_free (base->is_pes); + } + + if (G_OBJECT_CLASS (parent_class)->dispose) + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +mpegts_base_finalize (GObject * object) +{ + MpegTSBase *base = GST_MPEGTS_BASE (object); + + if (base->pat) { + gst_structure_free (base->pat); + base->pat = NULL; + } + g_hash_table_destroy (base->programs); + + if (G_OBJECT_CLASS (parent_class)->finalize) + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +mpegts_base_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + /* MpegTSBase *base = GST_MPEGTS_BASE (object); */ + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +mpegts_base_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + /* MpegTSBase *base = GST_MPEGTS_BASE (object); */ + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +/* returns NULL if no matching descriptor found * + * otherwise returns a descriptor that needs to * + * be freed */ +guint8 * +mpegts_get_descriptor_from_stream (MpegTSBaseStream * stream, guint8 tag) +{ + GValueArray *descriptors = NULL; + GstStructure *stream_info = stream->stream_info; + guint8 *retval = NULL; + int i; + + gst_structure_get (stream_info, "descriptors", G_TYPE_VALUE_ARRAY, + &descriptors, NULL); + if (descriptors) { + for (i = 0; i < descriptors->n_values; i++) { + GValue *value = g_value_array_get_nth (descriptors, i); + guint8 *desc = g_value_dup_boxed (value); + if (DESC_TAG (desc) == tag) { + retval = desc; + break; + } + } + g_value_array_free (descriptors); + } + return retval; +} + +/* returns NULL if no matching descriptor found * + * otherwise returns a descriptor that needs to * + * be freed */ +guint8 * +mpegts_get_descriptor_from_program (MpegTSBaseProgram * program, guint8 tag) +{ + GValueArray *descriptors = NULL; + GstStructure *program_info; + guint8 *retval = NULL; + int i; + + if (G_UNLIKELY (program == NULL)) + return NULL; + program_info = program->pmt_info; + gst_structure_get (program_info, "descriptors", G_TYPE_VALUE_ARRAY, + &descriptors, NULL); + if (descriptors) { + for (i = 0; i < descriptors->n_values; i++) { + GValue *value = g_value_array_get_nth (descriptors, i); + GString *desc = g_value_dup_boxed (value); + if (DESC_TAG (desc->str) == tag) { + retval = (guint8 *) desc->str; + g_string_free (desc, FALSE); + break; + } else + g_string_free (desc, FALSE); + } + g_value_array_free (descriptors); + } + return retval; +} + +MpegTSBaseProgram * +mpegts_base_add_program (MpegTSBase * base, + gint program_number, guint16 pmt_pid) +{ + MpegTSBaseProgram *program; + + program = g_malloc0 (base->program_size); + program->program_number = program_number; + program->pmt_pid = pmt_pid; + program->pcr_pid = G_MAXUINT16; + program->streams = g_new0 (MpegTSBaseStream *, 0x2000); + program->patcount = 0; + + g_hash_table_insert (base->programs, + GINT_TO_POINTER (program_number), program); + + return program; +} + +MpegTSBaseProgram * +mpegts_base_get_program (MpegTSBase * base, gint program_number) +{ + MpegTSBaseProgram *program; + + program = (MpegTSBaseProgram *) g_hash_table_lookup (base->programs, + GINT_TO_POINTER ((gint) program_number)); + + return program; +} + +#if 0 +static GstPad * +mpegts_base_activate_program (MpegTSBase * base, MpegTSBaseProgram * program) +{ + MpegTSBasePad *tspad; + gchar *pad_name; + + pad_name = g_strdup_printf ("program_%d", program->program_number); + + tspad = mpegts_base_create_tspad (base, pad_name); + tspad->program_number = program->program_number; + tspad->program = program; + program->tspad = tspad; + g_free (pad_name); + gst_pad_set_active (tspad->pad, TRUE); + program->active = TRUE; + + return tspad->pad; +} + +static GstPad * +mpegts_base_deactivate_program (MpegTSBase * base, MpegTSBaseProgram * program) +{ + MpegTSBasePad *tspad; + + tspad = program->tspad; + gst_pad_set_active (tspad->pad, FALSE); + program->active = FALSE; + + /* tspad will be destroyed in GstElementClass::pad_removed */ + + return tspad->pad; +} +#endif + + +static void +mpegts_base_free_program (MpegTSBaseProgram * program) +{ + guint i; + + if (program->pmt_info) + gst_structure_free (program->pmt_info); + + for (i = 0; i < 0x2000; i++) + if (program->streams[i]) + mpegts_base_free_stream (program->streams[i]); + + if (program->tags) + gst_tag_list_free (program->tags); + + g_free (program); +} + +void +mpegts_base_remove_program (MpegTSBase * base, gint program_number) +{ + MpegTSBaseProgram *program; + MpegTSBaseClass *klass = GST_MPEGTS_BASE_GET_CLASS (base); + + if (klass->program_stopped) { + program = + (MpegTSBaseProgram *) g_hash_table_lookup (base->programs, + GINT_TO_POINTER (program_number)); + klass->program_stopped (base, program); + } + g_hash_table_remove (base->programs, GINT_TO_POINTER (program_number)); + +} + +static MpegTSBaseStream * +mpegts_base_program_add_stream (MpegTSBase * base, + MpegTSBaseProgram * program, guint16 pid, guint8 stream_type, + GstStructure * stream_info) +{ + MpegTSBaseClass *klass = GST_MPEGTS_BASE_GET_CLASS (base); + MpegTSBaseStream *stream; + + GST_DEBUG ("pid:0x%04x, stream_type:0x%03x, stream_info:%" GST_PTR_FORMAT, + pid, stream_type, stream_info); + + stream = g_malloc0 (base->stream_size); + stream->pid = pid; + stream->stream_type = stream_type; + stream->stream_info = stream_info; + + program->streams[pid] = stream; + + if (klass->stream_added) + klass->stream_added (base, stream, program); + + return stream; +} + +static void +mpegts_base_free_stream (MpegTSBaseStream * stream) +{ + g_free (stream); +} + +void +mpegts_base_program_remove_stream (MpegTSBase * base, + MpegTSBaseProgram * program, guint16 pid) +{ + MpegTSBaseClass *klass = GST_MPEGTS_BASE_GET_CLASS (base); + + /* If subclass needs it, inform it of the stream we are about to remove */ + if (klass->stream_removed) + klass->stream_removed (base, program->streams[pid]); + + mpegts_base_free_stream (program->streams[pid]); + program->streams[pid] = NULL; +} + +static void +mpegts_base_deactivate_pmt (MpegTSBase * base, MpegTSBaseProgram * program) +{ + gint i; + guint pid; + guint stream_type; + GstStructure *stream; + const GValue *streams; + const GValue *value; + MpegTSBaseClass *klass = GST_MPEGTS_BASE_GET_CLASS (base); + + if (program->pmt_info) { + /* Inform subclasses we're deactivating this program */ + if (klass->program_stopped) + klass->program_stopped (base, program); + + streams = gst_structure_id_get_value (program->pmt_info, QUARK_STREAMS); + + for (i = 0; i < gst_value_list_get_size (streams); ++i) { + value = gst_value_list_get_value (streams, i); + stream = g_value_get_boxed (value); + gst_structure_id_get (stream, QUARK_PID, G_TYPE_UINT, &pid, + QUARK_STREAM_TYPE, G_TYPE_UINT, &stream_type, NULL); + mpegts_base_program_remove_stream (base, program, (guint16) pid); + base->is_pes[pid] = FALSE; + } + /* remove pcr stream */ + mpegts_base_program_remove_stream (base, program, program->pcr_pid); + base->is_pes[program->pcr_pid] = FALSE; + } +} + + +gboolean +mpegts_base_is_psi (MpegTSBase * base, MpegTSPacketizerPacket * packet) +{ + gboolean retval = FALSE; + guint8 table_id; + int i; + static const guint8 si_tables[] = + { 0x00, 0x01, 0x02, 0x03, 0x40, 0x41, 0x42, 0x46, 0x4A, + 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, + 0x72, 0x73, 0x7E, 0x7F, TABLE_ID_UNSET + }; + + if (base->known_psi[packet->pid]) + retval = TRUE; + + /* check is it is a pes pid */ + if (base->is_pes[packet->pid]) + return FALSE; + + if (!retval) { + if (packet->payload_unit_start_indicator) { + table_id = *(packet->data); + i = 0; + while (si_tables[i] != TABLE_ID_UNSET) { + if (G_UNLIKELY (si_tables[i] == table_id)) { + GST_DEBUG_OBJECT (base, "Packet has table id 0x%x", table_id); + retval = TRUE; + break; + } + i++; + } + } else { + MpegTSPacketizerStream *stream = (MpegTSPacketizerStream *) + base->packetizer->streams[packet->pid]; + + if (stream) { + i = 0; + GST_DEBUG_OBJECT (base, "section table id: 0x%x", + stream->section_table_id); + while (si_tables[i] != TABLE_ID_UNSET) { + if (G_UNLIKELY (si_tables[i] == stream->section_table_id)) { + retval = TRUE; + break; + } + i++; + } + } + } + } + + GST_LOG_OBJECT (base, "Packet of pid 0x%x is psi: %d", packet->pid, retval); + return retval; +} + +static void +mpegts_base_apply_pat (MpegTSBase * base, GstStructure * pat_info) +{ + const GValue *value; + GstStructure *old_pat; + GstStructure *program_info; + guint program_number; + guint pid; + MpegTSBaseProgram *program; + gint i; + const GValue *programs; + MpegTSBaseClass *klass = GST_MPEGTS_BASE_GET_CLASS (base); + + old_pat = base->pat; + base->pat = gst_structure_copy (pat_info); + + GST_INFO_OBJECT (base, "PAT %" GST_PTR_FORMAT, pat_info); + + gst_element_post_message (GST_ELEMENT_CAST (base), + gst_message_new_element (GST_OBJECT (base), + gst_structure_copy (pat_info))); + + programs = gst_structure_id_get_value (pat_info, QUARK_PROGRAMS); + /* activate the new table */ + for (i = 0; i < gst_value_list_get_size (programs); ++i) { + value = gst_value_list_get_value (programs, i); + + program_info = g_value_get_boxed (value); + gst_structure_id_get (program_info, QUARK_PROGRAM_NUMBER, G_TYPE_UINT, + &program_number, QUARK_PID, G_TYPE_UINT, &pid, NULL); + + program = mpegts_base_get_program (base, program_number); + if (program) { + if (program->pmt_pid != pid) { + if (program->pmt_pid != G_MAXUINT16) { + /* pmt pid changed */ + /* FIXME: when this happens it may still be pmt pid of another + * program, so setting to False may make it go through expensive + * path in is_psi unnecessarily */ + base->known_psi[program->pmt_pid] = FALSE; + } + + program->pmt_pid = pid; + base->known_psi[pid] = TRUE; + } + } else { + base->known_psi[pid] = TRUE; + program = mpegts_base_add_program (base, program_number, pid); + } + program->patcount += 1; + } + + if (old_pat) { + /* deactivate the old table */ + + programs = gst_structure_id_get_value (old_pat, QUARK_PROGRAMS); + for (i = 0; i < gst_value_list_get_size (programs); ++i) { + value = gst_value_list_get_value (programs, i); + + program_info = g_value_get_boxed (value); + gst_structure_id_get (program_info, + QUARK_PROGRAM_NUMBER, G_TYPE_UINT, &program_number, + QUARK_PID, G_TYPE_UINT, &pid, NULL); + + program = mpegts_base_get_program (base, program_number); + if (program == NULL) { + GST_DEBUG_OBJECT (base, "broken PAT, duplicated entry for program %d", + program_number); + continue; + } + + if (--program->patcount > 0) + /* the program has been referenced by the new pat, keep it */ + continue; + + GST_INFO_OBJECT (base, "PAT removing program %" GST_PTR_FORMAT, + program_info); + + if (klass->program_stopped) { + klass->program_stopped (base, program); + } + mpegts_base_deactivate_pmt (base, program); + mpegts_base_remove_program (base, program_number); + /* FIXME: when this happens it may still be pmt pid of another + * program, so setting to False may make it go through expensive + * path in is_psi unnecessarily */ + base->known_psi[pid] = TRUE; + mpegts_packetizer_remove_stream (base->packetizer, pid); + } + + gst_structure_free (old_pat); + } +#if 0 + mpegts_base_sync_program_pads (base); +#endif +} + +static void +mpegts_base_apply_pmt (MpegTSBase * base, + guint16 pmt_pid, GstStructure * pmt_info) +{ + MpegTSBaseProgram *program; + guint program_number; + guint pcr_pid; + guint pid; + guint stream_type; + GstStructure *stream; + gint i; + const GValue *new_streams; + const GValue *value; + MpegTSBaseClass *klass = GST_MPEGTS_BASE_GET_CLASS (base); + + if (G_UNLIKELY (base->first_pat_offset == -1)) { + GST_WARNING ("Got pmt without pat first. Returning"); + return; + } + + gst_structure_id_get (pmt_info, + QUARK_PROGRAM_NUMBER, G_TYPE_UINT, &program_number, + QUARK_PCR_PID, G_TYPE_UINT, &pcr_pid, NULL); + new_streams = gst_structure_id_get_value (pmt_info, QUARK_STREAMS); + + program = mpegts_base_get_program (base, program_number); + if (program) { + /* deactivate old pmt */ ; + mpegts_base_deactivate_pmt (base, program); + if (program->pmt_info) + gst_structure_free (program->pmt_info); + program->pmt_info = NULL; + } else { + /* no PAT?? */ + base->known_psi[pmt_pid] = TRUE; + program = mpegts_base_add_program (base, program_number, pid); + } + + /* activate new pmt */ + program->pmt_info = gst_structure_copy (pmt_info); + program->pmt_pid = pmt_pid; + program->pcr_pid = pcr_pid; + mpegts_base_program_add_stream (base, program, (guint16) pcr_pid, -1, NULL); + base->is_pes[pcr_pid] = TRUE; + + for (i = 0; i < gst_value_list_get_size (new_streams); ++i) { + value = gst_value_list_get_value (new_streams, i); + stream = g_value_get_boxed (value); + + gst_structure_id_get (stream, QUARK_PID, G_TYPE_UINT, &pid, + QUARK_STREAM_TYPE, G_TYPE_UINT, &stream_type, NULL); + mpegts_base_program_add_stream (base, program, + (guint16) pid, (guint8) stream_type, stream); + base->is_pes[pid] = TRUE; + + } + + if (klass->program_started != NULL) { + klass->program_started (base, program); + } + + GST_DEBUG_OBJECT (base, "new pmt %" GST_PTR_FORMAT, pmt_info); + + gst_element_post_message (GST_ELEMENT_CAST (base), + gst_message_new_element (GST_OBJECT (base), + gst_structure_copy (pmt_info))); +} + +static void +mpegts_base_apply_nit (MpegTSBase * base, + guint16 pmt_pid, GstStructure * nit_info) +{ + GST_DEBUG_OBJECT (base, "NIT %" GST_PTR_FORMAT, nit_info); + + gst_element_post_message (GST_ELEMENT_CAST (base), + gst_message_new_element (GST_OBJECT (base), + gst_structure_copy (nit_info))); +} + +static void +mpegts_base_apply_sdt (MpegTSBase * base, + guint16 pmt_pid, GstStructure * sdt_info) +{ + GST_DEBUG_OBJECT (base, "SDT %" GST_PTR_FORMAT, sdt_info); + + mpegts_base_get_tags_from_sdt (base, sdt_info); + + gst_element_post_message (GST_ELEMENT_CAST (base), + gst_message_new_element (GST_OBJECT (base), + gst_structure_copy (sdt_info))); +} + +static void +mpegts_base_apply_eit (MpegTSBase * base, + guint16 pmt_pid, GstStructure * eit_info) +{ + GST_DEBUG_OBJECT (base, "EIT %" GST_PTR_FORMAT, eit_info); + + mpegts_base_get_tags_from_eit (base, eit_info); + + gst_element_post_message (GST_ELEMENT_CAST (base), + gst_message_new_element (GST_OBJECT (base), + gst_structure_copy (eit_info))); +} + +static void +mpegts_base_apply_tdt (MpegTSBase * base, + guint16 tdt_pid, GstStructure * tdt_info) +{ + gst_element_post_message (GST_ELEMENT_CAST (base), + gst_message_new_element (GST_OBJECT (base), + gst_structure_copy (tdt_info))); + + GST_MPEGTS_BASE_GET_CLASS (base)->push_event (base, + gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, + gst_structure_copy (tdt_info))); +} + + +gboolean +mpegts_base_handle_psi (MpegTSBase * base, MpegTSPacketizerSection * section) +{ + gboolean res = TRUE; + GstStructure *structure = NULL; + + /* table ids 0x70 - 0x73 do not have a crc */ + if (G_LIKELY (section->table_id < 0x70 || section->table_id > 0x73)) { + if (G_UNLIKELY (mpegts_base_calc_crc32 (GST_BUFFER_DATA (section->buffer), + GST_BUFFER_SIZE (section->buffer)) != 0)) { + GST_WARNING_OBJECT (base, "bad crc in psi pid 0x%x", section->pid); + return FALSE; + } + } + + switch (section->table_id) { + case 0x00: + /* PAT */ + structure = mpegts_packetizer_parse_pat (base->packetizer, section); + if (G_LIKELY (structure)) { + mpegts_base_apply_pat (base, structure); + if (base->first_pat_offset == -1) { + + base->first_pat_offset = GST_BUFFER_OFFSET (section->buffer); + GST_DEBUG ("First PAT offset: %" G_GUINT64_FORMAT, + base->first_pat_offset); + } + + } else + res = FALSE; + + break; + case 0x02: + structure = mpegts_packetizer_parse_pmt (base->packetizer, section); + if (G_LIKELY (structure)) + mpegts_base_apply_pmt (base, section->pid, structure); + else + res = FALSE; + + break; + case 0x40: + /* NIT, actual network */ + case 0x41: + /* NIT, other network */ + structure = mpegts_packetizer_parse_nit (base->packetizer, section); + if (G_LIKELY (structure)) + mpegts_base_apply_nit (base, section->pid, structure); + else + res = FALSE; + + break; + case 0x42: + case 0x46: + structure = mpegts_packetizer_parse_sdt (base->packetizer, section); + if (G_LIKELY (structure)) + mpegts_base_apply_sdt (base, section->pid, structure); + else + res = FALSE; + break; + case 0x4E: + case 0x4F: + /* EIT, present/following */ + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + /* EIT, schedule */ + structure = mpegts_packetizer_parse_eit (base->packetizer, section); + if (G_LIKELY (structure)) + mpegts_base_apply_eit (base, section->pid, structure); + else + res = FALSE; + break; + case 0x70: + /* TDT (Time and Date table) */ + structure = mpegts_packetizer_parse_tdt (base->packetizer, section); + if (G_LIKELY (structure)) + mpegts_base_apply_tdt (base, section->pid, structure); + else + res = FALSE; + break; + default: + break; + } + + if (structure) + gst_structure_free (structure); + + return res; +} + +static void +mpegts_base_get_tags_from_sdt (MpegTSBase * base, GstStructure * sdt_info) +{ + const GValue *services; + guint i; + + services = gst_structure_get_value (sdt_info, "services"); + + for (i = 0; i < gst_value_list_get_size (services); i++) { + const GstStructure *service; + const gchar *sid_str; + gchar *tmp; + gint program_number; + MpegTSBaseProgram *program; + + service = gst_value_get_structure (gst_value_list_get_value (services, i)); + + /* get program_number from structure name + * which looks like service-%d */ + sid_str = gst_structure_get_name (service); + tmp = g_strstr_len (sid_str, -1, "-"); + program_number = atoi (++tmp); + + program = mpegts_base_get_program (base, program_number); + if (program && !program->tags) { + program->tags = gst_tag_list_new_full (GST_TAG_ARTIST, + gst_structure_get_string (service, "name"), NULL); + } + } +} + +static void +mpegts_base_get_tags_from_eit (MpegTSBase * base, GstStructure * eit_info) +{ + const GValue *events; + guint i; + guint program_number; + MpegTSBaseProgram *program; + gboolean present_following; + + gst_structure_get_uint (eit_info, "service-id", &program_number); + program = mpegts_base_get_program (base, program_number); + + gst_structure_get_boolean (eit_info, "present-following", &present_following); + + if (program && present_following) { + events = gst_structure_get_value (eit_info, "events"); + + for (i = 0; i < gst_value_list_get_size (events); i++) { + const GstStructure *event; + const gchar *title; + guint status; + guint event_id; + guint duration; + + event = gst_value_get_structure (gst_value_list_get_value (events, i)); + + title = gst_structure_get_string (event, "name"); + gst_structure_get_uint (event, "event-id", &event_id); + gst_structure_get_uint (event, "running-status", &status); + + if (title && event_id != program->event_id + && status == RUNNING_STATUS_RUNNING) { + gst_structure_get_uint (event, "duration", &duration); + + program->event_id = event_id; + program->tags = gst_tag_list_new_full (GST_TAG_TITLE, + title, GST_TAG_DURATION, duration * GST_SECOND, NULL); + } + } + } +} + + +static gboolean +mpegts_base_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean res; + MpegTSBase *base = GST_MPEGTS_BASE (gst_object_get_parent (GST_OBJECT (pad))); + + GST_WARNING_OBJECT (base, "Got event %s", + gst_event_type_get_name (GST_EVENT_TYPE (event))); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + /* FIXME : STORE NEW SEGMENT ! */ + gst_event_unref (event); + res = FALSE; + break; + case GST_EVENT_FLUSH_STOP: + mpegts_packetizer_clear (base->packetizer); + /* Passthrough */ + default: + res = GST_MPEGTS_BASE_GET_CLASS (base)->push_event (base, event); + gst_event_unref (event); + } + + gst_object_unref (base); + return res; +} + +static inline GstFlowReturn +mpegts_base_push (MpegTSBase * base, MpegTSPacketizerPacket * packet, + MpegTSPacketizerSection * section) +{ + MpegTSBaseClass *klass = GST_MPEGTS_BASE_GET_CLASS (base); + + /* Call implementation */ + if (G_UNLIKELY (klass->push == NULL)) { + GST_ERROR_OBJECT (base, "Class doesn't have a 'push' implementation !"); + return GST_FLOW_ERROR; + } + + return klass->push (base, packet, section); +} + +static GstFlowReturn +mpegts_base_chain (GstPad * pad, GstBuffer * buf) +{ + GstFlowReturn res = GST_FLOW_OK; + MpegTSBase *base; + gboolean based; + MpegTSPacketizerPacketReturn pret; + MpegTSPacketizer2 *packetizer; + MpegTSPacketizerPacket packet; + MpegTSBaseClass *klass; + + base = GST_MPEGTS_BASE (gst_object_get_parent (GST_OBJECT (pad))); + klass = GST_MPEGTS_BASE_GET_CLASS (base); + packetizer = base->packetizer; + + mpegts_packetizer_push (base->packetizer, buf); + while (((pret = + mpegts_packetizer_next_packet (base->packetizer, + &packet)) != PACKET_NEED_MORE) && res == GST_FLOW_OK) { + if (G_UNLIKELY (pret == PACKET_BAD)) + /* bad header, skip the packet */ + goto next; + + /* base PSI data */ + if (packet.payload != NULL && mpegts_base_is_psi (base, &packet)) { + MpegTSPacketizerSection section; + based = mpegts_packetizer_push_section (packetizer, &packet, §ion); + if (G_UNLIKELY (!based)) + /* bad section data */ + goto next; + + if (G_LIKELY (section.complete)) { + /* section complete */ + based = mpegts_base_handle_psi (base, §ion); + gst_buffer_unref (section.buffer); + + if (G_UNLIKELY (!based)) + /* bad PSI table */ + goto next; + } + /* we need to push section packet downstream */ + res = mpegts_base_push (base, &packet, §ion); + + } else { + /* push the packet downstream */ + res = mpegts_base_push (base, &packet, NULL); + } + + next: + mpegts_packetizer_clear_packet (base->packetizer, &packet); + } + + gst_object_unref (base); + return res; +} + +static GstFlowReturn +mpegts_base_scan (MpegTSBase * base) +{ + GstFlowReturn ret; + GstBuffer *buf; + guint i; + MpegTSBaseClass *klass = GST_MPEGTS_BASE_GET_CLASS (base); + + GST_DEBUG ("Scanning for initial sync point"); + + /* Find initial sync point */ + for (i = 0; i < 10; i++) { + GST_DEBUG ("Grabbing %d => %d", + i * 50 * MPEGTS_MAX_PACKETSIZE, 50 * MPEGTS_MAX_PACKETSIZE); + ret = gst_pad_pull_range (base->sinkpad, i * 50 * MPEGTS_MAX_PACKETSIZE, + 50 * MPEGTS_MAX_PACKETSIZE, &buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto beach; + + /* Push to packetizer */ + mpegts_packetizer_push (base->packetizer, buf); + + if (mpegts_packetizer_has_packets (base->packetizer)) { + /* Mark the initial sync point and remember the packetsize */ + base->initial_sync_point = base->seek_offset = base->packetizer->offset; + GST_DEBUG ("Sync point is now %" G_GUINT64_FORMAT, base->seek_offset); + base->packetsize = base->packetizer->packet_size; + + /* If the subclass can seek for timestamps, do that */ + if (klass->find_timestamps) { + guint64 offset; + mpegts_packetizer_clear (base->packetizer); + + ret = klass->find_timestamps (base, 0, &offset); + + base->initial_sync_point = base->seek_offset = + base->packetizer->offset = base->first_pat_offset; + GST_DEBUG ("Sync point is now %" G_GUINT64_FORMAT, base->seek_offset); + } + goto beach; + } + } + + GST_WARNING ("Didn't find initial sync point"); + ret = GST_FLOW_ERROR; + +beach: + mpegts_packetizer_clear (base->packetizer); + return ret; + +} + + +static void +mpegts_base_loop (MpegTSBase * base) +{ + GstFlowReturn ret = GST_FLOW_ERROR; + switch (base->mode) { + case BASE_MODE_SCANNING: + /* Find first sync point */ + ret = mpegts_base_scan (base); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto error; + base->mode = BASE_MODE_STREAMING; + GST_DEBUG ("Changing to Streaming"); + break; + case BASE_MODE_SEEKING: + /* FIXME : yes, we should do something here */ + base->mode = BASE_MODE_STREAMING; + break; + case BASE_MODE_STREAMING: + { + GstBuffer *buf; + + GST_DEBUG ("Pulling data from %" G_GUINT64_FORMAT, base->seek_offset); + + ret = gst_pad_pull_range (base->sinkpad, base->seek_offset, + 100 * base->packetsize, &buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto error; + base->seek_offset += GST_BUFFER_SIZE (buf); + ret = mpegts_base_chain (base->sinkpad, buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto error; + } + break; + } + + return; + +error: + { + const gchar *reason = gst_flow_get_name (ret); + GST_DEBUG_OBJECT (base, "Pausing task, reason %s", reason); + if (ret == GST_FLOW_UNEXPECTED) + GST_MPEGTS_BASE_GET_CLASS (base)->push_event (base, gst_event_new_eos ()); + else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_UNEXPECTED) { + GST_ELEMENT_ERROR (base, STREAM, FAILED, + (_("Internal data stream error.")), + ("stream stopped, reason %s", reason)); + GST_MPEGTS_BASE_GET_CLASS (base)->push_event (base, gst_event_new_eos ()); + } + gst_pad_pause_task (base->sinkpad); + } +} + +static gboolean +mpegts_base_sink_activate (GstPad * pad) +{ + if (gst_pad_check_pull_range (pad)) { + GST_DEBUG_OBJECT (pad, "activating pull"); + return gst_pad_activate_pull (pad, TRUE); + } else { + GST_DEBUG_OBJECT (pad, "activating push"); + return gst_pad_activate_push (pad, TRUE); + } +} + +static gboolean +mpegts_base_sink_activate_pull (GstPad * pad, gboolean active) +{ + MpegTSBase *base = GST_MPEGTS_BASE (GST_OBJECT_PARENT (pad)); + if (active) { + base->mode = BASE_MODE_SCANNING; + return gst_pad_start_task (pad, (GstTaskFunction) mpegts_base_loop, base); + } else + return gst_pad_stop_task (pad); +} + +static gboolean +mpegts_base_sink_activate_push (GstPad * pad, gboolean active) +{ + return TRUE; +} + + +static GstStateChangeReturn +mpegts_base_change_state (GstElement * element, GstStateChange transition) +{ + MpegTSBase *base; + GstStateChangeReturn ret; + + base = GST_MPEGTS_BASE (element); + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + mpegts_base_reset (base); + break; + default: + break; + } + + return ret; +} + +gboolean +gst_mpegtsbase_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (mpegts_base_debug, "mpegtsbase", 0, + "MPEG transport stream base class"); + + gst_mpegtsdesc_init_debug (); + + return TRUE; +} diff --git a/gst/mpegtsdemux/mpegtsbase.h b/gst/mpegtsdemux/mpegtsbase.h new file mode 100644 index 000000000..168f2ff1c --- /dev/null +++ b/gst/mpegtsdemux/mpegtsbase.h @@ -0,0 +1,169 @@ +/* + * mpegtsbase.h - GStreamer MPEG transport stream base class + * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk> + * 2007 Alessandro Decina + * + * Authors: + * Alessandro Decina <alessandro@nnva.org> + * Edward Hervey <edward.hervey@collabora.co.uk> + * + * 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 GST_MPEG_TS_BASE_H +#define GST_MPEG_TS_BASE_H + +#include <gst/gst.h> +#include "mpegtspacketizer.h" + +G_BEGIN_DECLS + +#define GST_TYPE_MPEGTS_BASE \ + (mpegts_base_get_type()) +#define GST_MPEGTS_BASE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MPEGTS_BASE,MpegTSBase)) +#define GST_MPEGTS_BASE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MPEGTS_BASE,MpegTSBaseClass)) +#define GST_IS_MPEGTS_BASE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MPEGTS_BASE)) +#define GST_IS_MPEGTS_BASE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MPEGTS_BASE)) +#define GST_MPEGTS_BASE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_MPEGTS_BASE, MpegTSBaseClass)) + +typedef struct _MpegTSBase MpegTSBase; +typedef struct _MpegTSBaseClass MpegTSBaseClass; +typedef struct _MpegTSBaseStream MpegTSBaseStream; +typedef struct _MpegTSBaseProgram MpegTSBaseProgram; + +struct _MpegTSBaseStream +{ + guint16 pid; + guint8 stream_type; + GstStructure* stream_info; +}; + +struct _MpegTSBaseProgram +{ + gint program_number; + guint16 pmt_pid; + guint16 pcr_pid; + GstStructure *pmt_info; + MpegTSBaseStream **streams; + gint patcount; + + /* Pending Tags for the program */ + GstTagList *tags; + guint event_id; +}; + +typedef enum { + BASE_MODE_SCANNING, + BASE_MODE_SEEKING, + BASE_MODE_STREAMING +} MpegTSBaseMode; + +struct _MpegTSBase { + GstElement element; + + GstPad *sinkpad; + + /* pull-based behaviour */ + MpegTSBaseMode mode; + + /* location of first sync point */ + guint64 initial_sync_point; + + /* Current pull offset (also set by seek handler) */ + guint64 seek_offset; + + /* Cached packetsize */ + guint16 packetsize; + + /* the following vars must be protected with the OBJECT_LOCK as they can be + * accessed from the application thread and the streaming thread */ + GHashTable *programs; + + GstStructure *pat; + MpegTSPacketizer2 *packetizer; + + /* arrays that say whether a pid is a known psi pid or a pes pid + * FIXME: Make these bit arrays so we can make them 8 times smaller */ + gboolean *known_psi; + gboolean *is_pes; + + gboolean disposed; + + /* size of the MpegTSBaseProgram structure, can be overridden + * by subclasses if they have their own MpegTSBaseProgram subclasses. */ + gsize program_size; + + /* size of the MpegTSBaseStream structure, can be overridden + * by subclasses if they have their own MpegTSBaseStream subclasses */ + gsize stream_size; + + /*Offset from the origin to the first PAT (pullmode) */ + guint64 first_pat_offset; +}; + +struct _MpegTSBaseClass { + GstElementClass parent_class; + + /* Virtual methods */ + GstFlowReturn (*push) (MpegTSBase *base, MpegTSPacketizerPacket *packet, MpegTSPacketizerSection * section); + gboolean (*push_event) (MpegTSBase *base, GstEvent * event); + /* program_started gets called when program's pmt arrives for first time */ + void (*program_started) (MpegTSBase *base, MpegTSBaseProgram *program); + /* program_stopped gets called when pat no longer has program's pmt */ + void (*program_stopped) (MpegTSBase *base, MpegTSBaseProgram *program); + + /* stream_added is called whenever a new stream has been identified */ + void (*stream_added) (MpegTSBase *base, MpegTSBaseStream *stream, MpegTSBaseProgram *program); + /* stream_removed is called whenever a stream is no longer referenced */ + void (*stream_removed) (MpegTSBase *base, MpegTSBaseStream *stream); + + /* find_timestamps is called to find PCR */ + GstFlowReturn (*find_timestamps) (MpegTSBase * base, guint64 initoff, guint64 *offset); + + /* signals */ + void (*pat_info) (GstStructure *pat); + void (*pmt_info) (GstStructure *pmt); + void (*nit_info) (GstStructure *nit); + void (*sdt_info) (GstStructure *sdt); + void (*eit_info) (GstStructure *eit); +}; + +GType mpegts_base_get_type(void); + +MpegTSBaseProgram *mpegts_base_get_program (MpegTSBase * base, gint program_number); +MpegTSBaseProgram *mpegts_base_add_program (MpegTSBase * base, gint program_number, guint16 pmt_pid); + +guint8 *mpegts_get_descriptor_from_stream (MpegTSBaseStream * stream, guint8 tag); +guint8 *mpegts_get_descriptor_from_program (MpegTSBaseProgram * program, guint8 tag); + + +gboolean gst_mpegtsbase_plugin_init (GstPlugin * plugin); + +gboolean mpegts_base_is_psi (MpegTSBase * base, MpegTSPacketizerPacket * packet); +gboolean mpegts_base_handle_psi (MpegTSBase * base, MpegTSPacketizerSection * section); + +void mpegts_base_program_remove_stream (MpegTSBase * base, MpegTSBaseProgram * program, guint16 pid); + +void mpegts_base_remove_program(MpegTSBase *base, gint program_number); +G_END_DECLS + +#endif /* GST_MPEG_TS_BASE_H */ diff --git a/gst/mpegtsdemux/mpegtspacketizer.c b/gst/mpegtsdemux/mpegtspacketizer.c new file mode 100644 index 000000000..f27918a0e --- /dev/null +++ b/gst/mpegtsdemux/mpegtspacketizer.c @@ -0,0 +1,2714 @@ +/* + * mpegtspacketizer.c - + * Copyright (C) 2007, 2008 Alessandro Decina, Zaheer Merali + * + * Authors: + * Zaheer Merali <zaheerabbas at merali dot org> + * Alessandro Decina <alessandro@nnva.org> + * + * 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. + */ + +#include <string.h> + +#include "mpegtspacketizer.h" +#include "gstmpegdesc.h" + +GST_DEBUG_CATEGORY_STATIC (mpegts_packetizer_debug); +#define GST_CAT_DEFAULT mpegts_packetizer_debug + +static GQuark QUARK_PAT; +static GQuark QUARK_TRANSPORT_STREAM_ID; +static GQuark QUARK_PROGRAM_NUMBER; +static GQuark QUARK_PID; +static GQuark QUARK_PROGRAMS; + +static GQuark QUARK_PMT; +static GQuark QUARK_PCR_PID; +static GQuark QUARK_VERSION_NUMBER; +static GQuark QUARK_DESCRIPTORS; +static GQuark QUARK_STREAM_TYPE; +static GQuark QUARK_STREAMS; + +static GQuark QUARK_NIT; +static GQuark QUARK_NETWORK_ID; +static GQuark QUARK_CURRENT_NEXT_INDICATOR; +static GQuark QUARK_ACTUAL_NETWORK; +static GQuark QUARK_NETWORK_NAME; +static GQuark QUARK_ORIGINAL_NETWORK_ID; +static GQuark QUARK_TRANSPORTS; + +static GQuark QUARK_SDT; +static GQuark QUARK_ACTUAL_TRANSPORT_STREAM; +static GQuark QUARK_SERVICES; + +static GQuark QUARK_EIT; +static GQuark QUARK_SERVICE_ID; +static GQuark QUARK_PRESENT_FOLLOWING; +static GQuark QUARK_SEGMENT_LAST_SECTION_NUMBER; +static GQuark QUARK_LAST_TABLE_ID; +static GQuark QUARK_EVENTS; + +static void _init_local (void); +G_DEFINE_TYPE_EXTENDED (MpegTSPacketizer2, mpegts_packetizer, G_TYPE_OBJECT, 0, + _init_local ()); + +static void mpegts_packetizer_dispose (GObject * object); +static void mpegts_packetizer_finalize (GObject * object); +static gchar *convert_to_utf8 (const gchar * text, gint length, guint start, + const gchar * encoding, gboolean is_multibyte, GError ** error); +static gchar *get_encoding (const gchar * text, guint * start_text, + gboolean * is_multibyte); +static gchar *get_encoding_and_convert (const gchar * text, guint length); + +#define CONTINUITY_UNSET 255 +#define MAX_CONTINUITY 15 +#define VERSION_NUMBER_UNSET 255 +#define TABLE_ID_UNSET 0xFF + +static gint +mpegts_packetizer_stream_subtable_compare (gconstpointer a, gconstpointer b) +{ + MpegTSPacketizerStreamSubtable *asub, *bsub; + + asub = (MpegTSPacketizerStreamSubtable *) a; + bsub = (MpegTSPacketizerStreamSubtable *) b; + + if (asub->table_id == bsub->table_id && + asub->subtable_extension == bsub->subtable_extension) + return 0; + return -1; +} + +static MpegTSPacketizerStreamSubtable * +mpegts_packetizer_stream_subtable_new (guint8 table_id, + guint16 subtable_extension) +{ + MpegTSPacketizerStreamSubtable *subtable; + + subtable = g_new0 (MpegTSPacketizerStreamSubtable, 1); + subtable->version_number = VERSION_NUMBER_UNSET; + subtable->table_id = table_id; + subtable->subtable_extension = subtable_extension; + subtable->crc = 0; + return subtable; +} + +static MpegTSPacketizerStream * +mpegts_packetizer_stream_new (void) +{ + MpegTSPacketizerStream *stream; + + stream = (MpegTSPacketizerStream *) g_new0 (MpegTSPacketizerStream, 1); + stream->section_adapter = gst_adapter_new (); + stream->continuity_counter = CONTINUITY_UNSET; + stream->subtables = NULL; + stream->section_table_id = TABLE_ID_UNSET; + return stream; +} + +static void +mpegts_packetizer_stream_free (MpegTSPacketizerStream * stream) +{ + gst_adapter_clear (stream->section_adapter); + g_object_unref (stream->section_adapter); + g_slist_foreach (stream->subtables, (GFunc) g_free, NULL); + g_slist_free (stream->subtables); + g_free (stream); +} + +static void +mpegts_packetizer_clear_section (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerStream * stream) +{ + gst_adapter_clear (stream->section_adapter); + stream->continuity_counter = CONTINUITY_UNSET; + stream->section_length = 0; + stream->section_table_id = TABLE_ID_UNSET; +} + +static void +mpegts_packetizer_class_init (MpegTSPacketizer2Class * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = mpegts_packetizer_dispose; + gobject_class->finalize = mpegts_packetizer_finalize; +} + +static void +mpegts_packetizer_init (MpegTSPacketizer2 * packetizer) +{ + packetizer->adapter = gst_adapter_new (); + packetizer->offset = 0; + packetizer->empty = TRUE; + packetizer->streams = g_new0 (MpegTSPacketizerStream *, 8192); + packetizer->know_packet_size = FALSE; +} + +static void +mpegts_packetizer_dispose (GObject * object) +{ + MpegTSPacketizer2 *packetizer = GST_MPEGTS_PACKETIZER (object); + + if (!packetizer->disposed) { + if (packetizer->know_packet_size && packetizer->caps != NULL) { + gst_caps_unref (packetizer->caps); + packetizer->caps = NULL; + packetizer->know_packet_size = FALSE; + } + if (packetizer->streams) { + int i; + for (i = 0; i < 8192; i++) { + if (packetizer->streams[i]) + mpegts_packetizer_stream_free (packetizer->streams[i]); + } + g_free (packetizer->streams); + } + + gst_adapter_clear (packetizer->adapter); + g_object_unref (packetizer->adapter); + packetizer->disposed = TRUE; + packetizer->offset = 0; + packetizer->empty = TRUE; + } + + if (G_OBJECT_CLASS (mpegts_packetizer_parent_class)->dispose) + G_OBJECT_CLASS (mpegts_packetizer_parent_class)->dispose (object); +} + +static void +mpegts_packetizer_finalize (GObject * object) +{ + if (G_OBJECT_CLASS (mpegts_packetizer_parent_class)->finalize) + G_OBJECT_CLASS (mpegts_packetizer_parent_class)->finalize (object); +} + +static gboolean +mpegts_packetizer_parse_adaptation_field_control (MpegTSPacketizer2 * + packetizer, MpegTSPacketizerPacket * packet) +{ + guint8 length, afcflags; + guint8 *data; + + length = *packet->data++; + + if (packet->adaptation_field_control == 0x02) { + /* no payload, adaptation field of 183 bytes */ + if (length != 183) { + GST_DEBUG ("PID %d afc == 0x%x and length %d != 183", + packet->pid, packet->adaptation_field_control, length); + } + } else if (length > 182) { + GST_DEBUG ("PID %d afc == 0x%01x and length %d > 182", + packet->pid, packet->adaptation_field_control, length); + } + + if (packet->data + length > packet->data_end) { + GST_DEBUG ("PID %d afc length %d overflows the buffer current %d max %d", + packet->pid, length, (gint) (packet->data - packet->data_start), + (gint) (packet->data_end - packet->data_start)); + return FALSE; + } + + data = packet->data; + packet->data += length; + + afcflags = packet->afc_flags = *data++; + + /* PCR */ + if (afcflags & MPEGTS_AFC_PCR_FLAG) { + guint32 pcr1; + guint16 pcr2; + guint64 pcr, pcr_ext; + + pcr1 = GST_READ_UINT32_BE (data); + pcr2 = GST_READ_UINT16_BE (data + 4); + pcr = ((guint64) pcr1) << 1; + pcr |= (pcr2 & 0x8000) >> 15; + pcr_ext = (pcr2 & 0x01ff); + packet->pcr = pcr * 300 + pcr_ext % 300;; + *data += 6; + } + + /* OPCR */ + if (afcflags & MPEGTS_AFC_OPCR_FLAG) { + guint32 pcr1; + guint16 pcr2; + guint64 pcr, pcr_ext; + + pcr1 = GST_READ_UINT32_BE (data); + pcr2 = GST_READ_UINT16_BE (data + 4); + pcr = ((guint64) pcr1) << 1; + pcr |= (pcr2 & 0x8000) >> 15; + pcr_ext = (pcr2 & 0x01ff); + packet->opcr = pcr * 300 + pcr_ext % 300;; + *data += 6; + } + + return TRUE; +} + +static gboolean +mpegts_packetizer_parse_packet (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerPacket * packet) +{ + guint8 *data; + + data = packet->data_start; + data++; + + packet->payload_unit_start_indicator = (*data >> 6) & 0x01; + packet->pid = GST_READ_UINT16_BE (data) & 0x1FFF; + data += 2; + + packet->adaptation_field_control = (*data >> 4) & 0x03; + packet->continuity_counter = *data & 0x0F; + data += 1; + + packet->data = data; + + if (packet->adaptation_field_control & 0x02) + if (!mpegts_packetizer_parse_adaptation_field_control (packetizer, packet)) + return FALSE; + + if (packet->adaptation_field_control & 0x01) + packet->payload = packet->data; + else + packet->payload = NULL; + + return TRUE; +} + +static gboolean +mpegts_packetizer_parse_section_header (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerStream * stream, MpegTSPacketizerSection * section) +{ + guint8 tmp; + guint8 *data, *crc_data; + MpegTSPacketizerStreamSubtable *subtable; + GSList *subtable_list = NULL; + + section->complete = TRUE; + /* get the section buffer, pass the ownership to the caller */ + section->buffer = gst_adapter_take_buffer (stream->section_adapter, + 3 + stream->section_length); + data = GST_BUFFER_DATA (section->buffer); + GST_BUFFER_OFFSET (section->buffer) = stream->offset; + + section->table_id = *data++; + /* if table_id is 0 (pat) then ignore the subtable extension */ + if ((data[0] & 0x80) == 0 || section->table_id == 0) + section->subtable_extension = 0; + else + section->subtable_extension = GST_READ_UINT16_BE (data + 2); + + subtable = mpegts_packetizer_stream_subtable_new (section->table_id, + section->subtable_extension); + + subtable_list = g_slist_find_custom (stream->subtables, subtable, + mpegts_packetizer_stream_subtable_compare); + if (subtable_list) { + g_free (subtable); + subtable = (MpegTSPacketizerStreamSubtable *) (subtable_list->data); + } else { + stream->subtables = g_slist_prepend (stream->subtables, subtable); + } + + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + /* skip to the version byte */ + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + if (!section->current_next_indicator) + goto not_applicable; + + /* CRC is at the end of the section */ + crc_data = + GST_BUFFER_DATA (section->buffer) + GST_BUFFER_SIZE (section->buffer) - 4; + section->crc = GST_READ_UINT32_BE (crc_data); + + if (section->version_number == subtable->version_number && + section->crc == subtable->crc) + goto not_applicable; + + subtable->version_number = section->version_number; + subtable->crc = section->crc; + stream->section_table_id = section->table_id; + + return TRUE; + +not_applicable: + GST_LOG + ("not applicable pid %d table_id %d subtable_extension %d, current_next %d version %d, crc 0x%x", + section->pid, section->table_id, section->subtable_extension, + section->current_next_indicator, section->version_number, section->crc); + section->complete = FALSE; + gst_buffer_unref (section->buffer); + return TRUE; +} + +static gboolean +mpegts_packetizer_parse_descriptors (MpegTSPacketizer2 * packetizer, + guint8 ** buffer, guint8 * buffer_end, GValueArray * descriptors) +{ + guint8 tag, length; + guint8 *data; + GValue value = { 0 }; + GString *desc; + + data = *buffer; + + while (data < buffer_end) { + tag = *data++; + length = *data++; + + if (data + length > buffer_end) { + GST_WARNING ("invalid descriptor length %d now at %d max %d", length, + (gint) (data - *buffer), (gint) (buffer_end - *buffer)); + goto error; + } + + /* include tag and length */ + desc = g_string_new_len ((gchar *) data - 2, length + 2); + data += length; + /* G_TYPE_GSTING is a GBoxed type and is used so properly marshalled from python */ + g_value_init (&value, G_TYPE_GSTRING); + g_value_take_boxed (&value, desc); + g_value_array_append (descriptors, &value); + g_value_unset (&value); + } + + if (data != buffer_end) { + GST_WARNING ("descriptors size %d expected %d", (gint) (data - *buffer), + (gint) (buffer_end - *buffer)); + goto error; + } + + *buffer = data; + + return TRUE; +error: + return FALSE; +} + +GstStructure * +mpegts_packetizer_parse_pat (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *pat_info = NULL; + guint8 *data, *end; + guint transport_stream_id; + guint8 tmp; + guint program_number; + guint pmt_pid; + GValue entries = { 0 }; + GValue value = { 0 }; + GstStructure *entry = NULL; + gchar *struct_name; + + data = GST_BUFFER_DATA (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + transport_stream_id = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + pat_info = gst_structure_id_new (QUARK_PAT, + QUARK_TRANSPORT_STREAM_ID, G_TYPE_UINT, transport_stream_id, NULL); + g_value_init (&entries, GST_TYPE_LIST); + /* stop at the CRC */ + end = GST_BUFFER_DATA (section->buffer) + GST_BUFFER_SIZE (section->buffer); + while (data < end - 4) { + program_number = GST_READ_UINT16_BE (data); + data += 2; + + pmt_pid = GST_READ_UINT16_BE (data) & 0x1FFF; + data += 2; + + struct_name = g_strdup_printf ("program-%d", program_number); + entry = gst_structure_new (struct_name, NULL); + g_free (struct_name); + gst_structure_id_set (entry, QUARK_PROGRAM_NUMBER, G_TYPE_UINT, + program_number, QUARK_PID, G_TYPE_UINT, pmt_pid, NULL); + + g_value_init (&value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&value, entry); + gst_value_list_append_value (&entries, &value); + g_value_unset (&value); + } + + gst_structure_id_set_value (pat_info, QUARK_PROGRAMS, &entries); + g_value_unset (&entries); + + if (data != end - 4) { + /* FIXME: check the CRC before parsing the packet */ + GST_ERROR ("at the end of PAT data != end - 4"); + gst_structure_free (pat_info); + + return NULL; + } + + return pat_info; +} + +GstStructure * +mpegts_packetizer_parse_pmt (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *pmt = NULL; + guint8 *data, *end; + guint16 program_number; + guint8 tmp; + guint pcr_pid; + guint program_info_length; + guint8 stream_type; + guint16 pid; + guint stream_info_length; + GValueArray *descriptors; + GValue stream_value = { 0 }; + GValue programs = { 0 }; + GstStructure *stream_info = NULL; + gchar *struct_name; + + /* fixed header + CRC == 16 */ + if (GST_BUFFER_SIZE (section->buffer) < 16) { + GST_WARNING ("PID %d invalid PMT size %d", + section->pid, section->section_length); + goto error; + } + + data = GST_BUFFER_DATA (section->buffer); + end = data + GST_BUFFER_SIZE (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + program_number = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + pcr_pid = GST_READ_UINT16_BE (data) & 0x1FFF; + data += 2; + + program_info_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + pmt = gst_structure_id_new (QUARK_PMT, + QUARK_PROGRAM_NUMBER, G_TYPE_UINT, program_number, + QUARK_PCR_PID, G_TYPE_UINT, pcr_pid, + QUARK_VERSION_NUMBER, G_TYPE_UINT, section->version_number, NULL); + + if (program_info_length) { + /* check that the buffer is large enough to contain at least + * program_info_length bytes + CRC */ + if (data + program_info_length + 4 > end) { + GST_WARNING ("PID %d invalid program info length %d left %d", + section->pid, program_info_length, (gint) (end - data)); + goto error; + } + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + program_info_length, descriptors)) { + g_value_array_free (descriptors); + goto error; + } + + gst_structure_id_set (pmt, QUARK_DESCRIPTORS, G_TYPE_VALUE_ARRAY, + descriptors, NULL); + g_value_array_free (descriptors); + } + + g_value_init (&programs, GST_TYPE_LIST); + /* parse entries, cycle until there's space for another entry (at least 5 + * bytes) plus the CRC */ + while (data <= end - 4 - 5) { + stream_type = *data++; + + pid = GST_READ_UINT16_BE (data) & 0x1FFF; + data += 2; + + stream_info_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + if (data + stream_info_length + 4 > end) { + GST_WARNING ("PID %d invalid stream info length %d left %d", section->pid, + stream_info_length, (gint) (end - data)); + g_value_unset (&programs); + goto error; + } + + struct_name = g_strdup_printf ("pid-%d", pid); + stream_info = gst_structure_new (struct_name, NULL); + g_free (struct_name); + gst_structure_id_set (stream_info, + QUARK_PID, G_TYPE_UINT, pid, QUARK_STREAM_TYPE, G_TYPE_UINT, + stream_type, NULL); + + if (stream_info_length) { + /* check for AC3 descriptor */ + GstMPEGDescriptor *desc = + gst_mpeg_descriptor_parse (data, stream_info_length); + if (desc != NULL) { + /* DVB AC3 */ + guint8 *desc_data; + if (gst_mpeg_descriptor_find (desc, DESC_DVB_AC3)) { + gst_structure_set (stream_info, "has-ac3", G_TYPE_BOOLEAN, TRUE, + NULL); + } + + /* DATA BROADCAST ID */ + desc_data = gst_mpeg_descriptor_find (desc, DESC_DVB_DATA_BROADCAST_ID); + if (desc_data) { + guint16 data_broadcast_id; + data_broadcast_id = + DESC_DVB_DATA_BROADCAST_ID_data_broadcast_id (desc_data); + gst_structure_set (stream_info, "data-broadcast-id", G_TYPE_UINT, + data_broadcast_id, NULL); + } + + /* DATA BROADCAST */ + desc_data = gst_mpeg_descriptor_find (desc, DESC_DVB_DATA_BROADCAST); + if (desc_data) { + GstStructure *databroadcast_info; + guint16 data_broadcast_id; + guint8 component_tag; + data_broadcast_id = + DESC_DVB_DATA_BROADCAST_data_broadcast_id (desc_data); + component_tag = DESC_DVB_DATA_BROADCAST_component_tag (desc_data); + databroadcast_info = gst_structure_new ("data-broadcast", "id", + G_TYPE_UINT, data_broadcast_id, "component-tag", component_tag, + NULL); + gst_structure_set (stream_info, "data-broadcast", GST_TYPE_STRUCTURE, + databroadcast_info, NULL); + } + + /* DVB CAROUSEL IDENTIFIER */ + desc_data = + gst_mpeg_descriptor_find (desc, DESC_DVB_CAROUSEL_IDENTIFIER); + if (desc_data) { + guint32 carousel_id; + carousel_id = DESC_DVB_CAROUSEL_IDENTIFIER_carousel_id (desc_data); + gst_structure_set (stream_info, "carousel-id", G_TYPE_UINT, + carousel_id, NULL); + } + + /* DVB STREAM IDENTIFIER */ + desc_data = gst_mpeg_descriptor_find (desc, DESC_DVB_STREAM_IDENTIFIER); + if (desc_data) { + guint8 component_tag; + component_tag = DESC_DVB_STREAM_IDENTIFIER_component_tag (desc_data); + gst_structure_set (stream_info, "component-tag", G_TYPE_UINT, + component_tag, NULL); + } + + /* ISO 639 LANGUAGE */ + desc_data = gst_mpeg_descriptor_find (desc, DESC_ISO_639_LANGUAGE); + if (desc_data && DESC_ISO_639_LANGUAGE_codes_n (desc_data)) { + gchar *lang_code; + gchar *language_n = (gchar *) + DESC_ISO_639_LANGUAGE_language_code_nth (desc_data, 0); + lang_code = g_strndup (language_n, 3); + gst_structure_set (stream_info, "lang-code", G_TYPE_STRING, + lang_code, NULL); + g_free (lang_code); + } + + gst_mpeg_descriptor_free (desc); + } + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + stream_info_length, descriptors)) { + g_value_unset (&programs); + gst_structure_free (stream_info); + g_value_array_free (descriptors); + goto error; + } + + gst_structure_id_set (stream_info, + QUARK_DESCRIPTORS, G_TYPE_VALUE_ARRAY, descriptors, NULL); + g_value_array_free (descriptors); + + } + + g_value_init (&stream_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&stream_value, stream_info); + gst_value_list_append_value (&programs, &stream_value); + g_value_unset (&stream_value); + } + + gst_structure_id_set_value (pmt, QUARK_STREAMS, &programs); + g_value_unset (&programs); + + g_assert (data == end - 4); + + return pmt; + +error: + if (pmt) + gst_structure_free (pmt); + + return NULL; +} + +GstStructure * +mpegts_packetizer_parse_nit (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *nit = NULL, *transport = NULL, *delivery_structure = NULL; + guint8 *data, *end, *entry_begin; + guint16 network_id, transport_stream_id, original_network_id; + guint tmp; + guint16 descriptors_loop_length, transport_stream_loop_length; + GValue transports = { 0 }; + GValue transport_value = { 0 }; + GValueArray *descriptors = NULL; + + GST_DEBUG ("NIT"); + /* fixed header + CRC == 16 */ + if (GST_BUFFER_SIZE (section->buffer) < 23) { + GST_WARNING ("PID %d invalid NIT size %d", + section->pid, section->section_length); + goto error; + } + + data = GST_BUFFER_DATA (section->buffer); + end = data + GST_BUFFER_SIZE (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + if (data + section->section_length != end) { + GST_WARNING ("PID %d invalid NIT section length %d expected %d", + section->pid, section->section_length, (gint) (end - data)); + goto error; + } + + network_id = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + nit = gst_structure_id_new (QUARK_NIT, + QUARK_NETWORK_ID, G_TYPE_UINT, network_id, + QUARK_VERSION_NUMBER, G_TYPE_UINT, section->version_number, + QUARK_CURRENT_NEXT_INDICATOR, G_TYPE_UINT, + section->current_next_indicator, QUARK_ACTUAL_NETWORK, G_TYPE_BOOLEAN, + section->table_id == 0x40, NULL); + + /* see if the buffer is large enough */ + if (descriptors_loop_length) { + guint8 *networkname_descriptor; + GstMPEGDescriptor *mpegdescriptor; + + if (data + descriptors_loop_length > end - 4) { + GST_WARNING ("PID %d invalid NIT descriptors loop length %d", + section->pid, descriptors_loop_length); + gst_structure_free (nit); + goto error; + } + mpegdescriptor = gst_mpeg_descriptor_parse (data, descriptors_loop_length); + networkname_descriptor = + gst_mpeg_descriptor_find (mpegdescriptor, DESC_DVB_NETWORK_NAME); + if (networkname_descriptor != NULL) { + gchar *networkname_tmp; + + /* No need to bounds check this value as it comes from the descriptor length itself */ + guint8 networkname_length = + DESC_DVB_NETWORK_NAME_length (networkname_descriptor); + gchar *networkname = + (gchar *) DESC_DVB_NETWORK_NAME_text (networkname_descriptor); + + networkname_tmp = + get_encoding_and_convert (networkname, networkname_length); + gst_structure_id_set (nit, QUARK_NETWORK_NAME, G_TYPE_STRING, + networkname_tmp, NULL); + g_free (networkname_tmp); + } + gst_mpeg_descriptor_free (mpegdescriptor); + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + descriptors_loop_length, descriptors)) { + gst_structure_free (nit); + g_value_array_free (descriptors); + goto error; + } + + gst_structure_id_set (nit, QUARK_DESCRIPTORS, G_TYPE_VALUE_ARRAY, + descriptors, NULL); + g_value_array_free (descriptors); + } + + transport_stream_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + g_value_init (&transports, GST_TYPE_LIST); + /* read up to the CRC */ + while (transport_stream_loop_length - 4 > 0) { + gchar *transport_name; + + entry_begin = data; + + if (transport_stream_loop_length < 10) { + /* each entry must be at least 6 bytes (+ 4bytes CRC) */ + GST_WARNING ("PID %d invalid NIT entry size %d", + section->pid, transport_stream_loop_length); + goto error; + } + + transport_stream_id = GST_READ_UINT16_BE (data); + data += 2; + + original_network_id = GST_READ_UINT16_BE (data); + data += 2; + + descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + transport_name = g_strdup_printf ("transport-%d", transport_stream_id); + transport = gst_structure_new (transport_name, NULL); + g_free (transport_name); + gst_structure_id_set (transport, + QUARK_TRANSPORT_STREAM_ID, G_TYPE_UINT, transport_stream_id, + QUARK_ORIGINAL_NETWORK_ID, G_TYPE_UINT, original_network_id, NULL); + + if (descriptors_loop_length) { + GstMPEGDescriptor *mpegdescriptor; + guint8 *delivery; + + if (data + descriptors_loop_length > end - 4) { + GST_WARNING ("PID %d invalid NIT entry %d descriptors loop length %d", + section->pid, transport_stream_id, descriptors_loop_length); + gst_structure_free (transport); + goto error; + } + mpegdescriptor = + gst_mpeg_descriptor_parse (data, descriptors_loop_length); + + if ((delivery = + gst_mpeg_descriptor_find (mpegdescriptor, + DESC_DVB_SATELLITE_DELIVERY_SYSTEM))) { + + guint8 *frequency_bcd = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_frequency (delivery); + guint32 frequency = + 10 * ((frequency_bcd[3] & 0x0F) + + 10 * ((frequency_bcd[3] & 0xF0) >> 4) + + 100 * (frequency_bcd[2] & 0x0F) + + 1000 * ((frequency_bcd[2] & 0xF0) >> 4) + + 10000 * (frequency_bcd[1] & 0x0F) + + 100000 * ((frequency_bcd[1] & 0xF0) >> 4) + + 1000000 * (frequency_bcd[0] & 0x0F) + + 10000000 * ((frequency_bcd[0] & 0xF0) >> 4)); + guint8 *orbital_bcd = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_orbital_position (delivery); + gfloat orbital = + (orbital_bcd[1] & 0x0F) / 10. + ((orbital_bcd[1] & 0xF0) >> 4) + + 10 * (orbital_bcd[0] & 0x0F) + 100 * ((orbital_bcd[0] & 0xF0) >> 4); + gboolean east = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_west_east_flag (delivery); + guint8 polarization = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_polarization (delivery); + const gchar *polarization_str; + guint8 modulation = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_modulation (delivery); + const gchar *modulation_str; + guint8 *symbol_rate_bcd = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_symbol_rate (delivery); + guint32 symbol_rate = + (symbol_rate_bcd[2] & 0x0F) + + 10 * ((symbol_rate_bcd[2] & 0xF0) >> 4) + + 100 * (symbol_rate_bcd[1] & 0x0F) + + 1000 * ((symbol_rate_bcd[1] & 0xF0) >> 4) + + 10000 * (symbol_rate_bcd[0] & 0x0F) + + 100000 * ((symbol_rate_bcd[0] & 0xF0) >> 4); + guint8 fec_inner = + DESC_DVB_SATELLITE_DELIVERY_SYSTEM_fec_inner (delivery); + const gchar *fec_inner_str; + + switch (polarization) { + case 0: + polarization_str = "horizontal"; + break; + case 1: + polarization_str = "vertical"; + break; + case 2: + polarization_str = "left"; + break; + case 3: + polarization_str = "right"; + break; + default: + polarization_str = ""; + } + switch (fec_inner) { + case 0: + fec_inner_str = "undefined"; + break; + case 1: + fec_inner_str = "1/2"; + break; + case 2: + fec_inner_str = "2/3"; + break; + case 3: + fec_inner_str = "3/4"; + break; + case 4: + fec_inner_str = "5/6"; + break; + case 5: + fec_inner_str = "7/8"; + break; + case 6: + fec_inner_str = "8/9"; + break; + case 0xF: + fec_inner_str = "none"; + break; + default: + fec_inner_str = "reserved"; + } + switch (modulation) { + case 0x00: + modulation_str = "undefined"; + break; + case 0x01: + modulation_str = "QAM16"; + break; + case 0x02: + modulation_str = "QAM32"; + break; + case 0x03: + modulation_str = "QAM64"; + break; + case 0x04: + modulation_str = "QAM128"; + break; + case 0x05: + modulation_str = "QAM256"; + break; + default: + modulation_str = "reserved"; + } + delivery_structure = gst_structure_new ("satellite", + "orbital", G_TYPE_FLOAT, orbital, + "east-or-west", G_TYPE_STRING, east ? "east" : "west", + "modulation", G_TYPE_STRING, modulation_str, + "frequency", G_TYPE_UINT, frequency, + "polarization", G_TYPE_STRING, polarization_str, + "symbol-rate", G_TYPE_UINT, symbol_rate, + "inner-fec", G_TYPE_STRING, fec_inner_str, NULL); + gst_structure_set (transport, "delivery", GST_TYPE_STRUCTURE, + delivery_structure, NULL); + } else if ((delivery = + gst_mpeg_descriptor_find (mpegdescriptor, + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM))) { + + guint32 frequency = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_frequency (delivery) * 10; + guint8 bandwidth = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_bandwidth (delivery); + guint8 constellation = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_constellation (delivery); + guint8 hierarchy = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_hierarchy (delivery); + guint8 code_rate_hp = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_code_rate_hp (delivery); + guint8 code_rate_lp = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_code_rate_lp (delivery); + guint8 guard_interval = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_guard_interval (delivery); + guint8 transmission_mode = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_transmission_mode (delivery); + gboolean other_frequency = + DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM_other_frequency (delivery); + const gchar *constellation_str, *code_rate_hp_str, *code_rate_lp_str, + *transmission_mode_str; + /* do the stuff */ + /* bandwidth is 8 if 0, 7 if 1, 6 if 2, reserved otherwise */ + if (bandwidth <= 2) + bandwidth = 8 - bandwidth; + else + bandwidth = 0; + switch (constellation) { + case 0: + constellation_str = "QPSK"; + break; + case 1: + constellation_str = "QAM16"; + break; + case 2: + constellation_str = "QAM64"; + break; + default: + constellation_str = "reserved"; + } + /* hierarchy is 4 if 3, 2 if 2, 1 if 1, 0 if 0, reserved if > 3 */ + if (hierarchy <= 3) { + if (hierarchy == 3) + hierarchy = 4; + } else { + hierarchy = 0; + } + + switch (code_rate_hp) { + case 0: + code_rate_hp_str = "1/2"; + break; + case 1: + code_rate_hp_str = "2/3"; + break; + case 2: + code_rate_hp_str = "3/4"; + break; + case 3: + code_rate_hp_str = "5/6"; + break; + case 4: + code_rate_hp_str = "7/8"; + break; + default: + code_rate_hp_str = "reserved"; + } + + switch (code_rate_lp) { + case 0: + code_rate_lp_str = "1/2"; + break; + case 1: + code_rate_lp_str = "2/3"; + break; + case 2: + code_rate_lp_str = "3/4"; + break; + case 3: + code_rate_lp_str = "5/6"; + break; + case 4: + code_rate_lp_str = "7/8"; + break; + default: + code_rate_lp_str = "reserved"; + } + /* guard is 32 if 0, 16 if 1, 8 if 2, 4 if 3 */ + switch (guard_interval) { + case 0: + guard_interval = 32; + break; + case 1: + guard_interval = 16; + break; + case 2: + guard_interval = 8; + break; + case 3: + guard_interval = 4; + break; + default: /* make it default to 32 */ + guard_interval = 32; + } + switch (transmission_mode) { + case 0: + transmission_mode_str = "2k"; + break; + case 1: + transmission_mode_str = "8k"; + break; + default: + transmission_mode_str = "reserved"; + } + delivery_structure = gst_structure_new ("terrestrial", + "frequency", G_TYPE_UINT, frequency, + "bandwidth", G_TYPE_UINT, bandwidth, + "constellation", G_TYPE_STRING, constellation_str, + "hierarchy", G_TYPE_UINT, hierarchy, + "code-rate-hp", G_TYPE_STRING, code_rate_hp_str, + "code-rate-lp", G_TYPE_STRING, code_rate_lp_str, + "guard-interval", G_TYPE_UINT, guard_interval, + "transmission-mode", G_TYPE_STRING, transmission_mode_str, + "other-frequency", G_TYPE_BOOLEAN, other_frequency, NULL); + gst_structure_set (transport, "delivery", GST_TYPE_STRUCTURE, + delivery_structure, NULL); + } else if ((delivery = + gst_mpeg_descriptor_find (mpegdescriptor, + DESC_DVB_CABLE_DELIVERY_SYSTEM))) { + + guint8 *frequency_bcd = + DESC_DVB_CABLE_DELIVERY_SYSTEM_frequency (delivery); + /* see en 300 468 section 6.2.13.1 least significant bcd digit + * is measured in 100Hz units so multiplier needs to be 100 to get + * into Hz */ + guint32 frequency = 100 * + ((frequency_bcd[3] & 0x0F) + + 10 * ((frequency_bcd[3] & 0xF0) >> 4) + + 100 * (frequency_bcd[2] & 0x0F) + + 1000 * ((frequency_bcd[2] & 0xF0) >> 4) + + 10000 * (frequency_bcd[1] & 0x0F) + + 100000 * ((frequency_bcd[1] & 0xF0) >> 4) + + 1000000 * (frequency_bcd[0] & 0x0F) + + 10000000 * ((frequency_bcd[0] & 0xF0) >> 4)); + guint8 modulation = + DESC_DVB_CABLE_DELIVERY_SYSTEM_modulation (delivery); + const gchar *modulation_str; + guint8 *symbol_rate_bcd = + DESC_DVB_CABLE_DELIVERY_SYSTEM_symbol_rate (delivery); + guint32 symbol_rate = + (symbol_rate_bcd[2] & 0x0F) + + 10 * ((symbol_rate_bcd[2] & 0xF0) >> 4) + + 100 * (symbol_rate_bcd[1] & 0x0F) + + 1000 * ((symbol_rate_bcd[1] & 0xF0) >> 4) + + 10000 * (symbol_rate_bcd[0] & 0x0F) + + 100000 * ((symbol_rate_bcd[0] & 0xF0) >> 4); + guint8 fec_inner = DESC_DVB_CABLE_DELIVERY_SYSTEM_fec_inner (delivery); + const gchar *fec_inner_str; + + switch (fec_inner) { + case 0: + fec_inner_str = "undefined"; + break; + case 1: + fec_inner_str = "1/2"; + break; + case 2: + fec_inner_str = "2/3"; + break; + case 3: + fec_inner_str = "3/4"; + break; + case 4: + fec_inner_str = "5/6"; + break; + case 5: + fec_inner_str = "7/8"; + break; + case 6: + fec_inner_str = "8/9"; + break; + case 0xF: + fec_inner_str = "none"; + break; + default: + fec_inner_str = "reserved"; + } + switch (modulation) { + case 0x00: + modulation_str = "undefined"; + break; + case 0x01: + modulation_str = "QAM16"; + break; + case 0x02: + modulation_str = "QAM32"; + break; + case 0x03: + modulation_str = "QAM64"; + break; + case 0x04: + modulation_str = "QAM128"; + break; + case 0x05: + modulation_str = "QAM256"; + break; + default: + modulation_str = "reserved"; + } + delivery_structure = gst_structure_new ("cable", + "modulation", G_TYPE_STRING, modulation_str, + "frequency", G_TYPE_UINT, frequency, + "symbol-rate", G_TYPE_UINT, symbol_rate, + "inner-fec", G_TYPE_STRING, fec_inner_str, NULL); + gst_structure_set (transport, "delivery", GST_TYPE_STRUCTURE, + delivery_structure, NULL); + } + /* free the temporary delivery structure */ + if (delivery_structure != NULL) { + gst_structure_free (delivery_structure); + delivery_structure = NULL; + } + if ((delivery = + gst_mpeg_descriptor_find (mpegdescriptor, + DESC_DTG_LOGICAL_CHANNEL))) { + guint8 *current_pos = delivery + 2; + GValue channel_numbers = { 0 }; + + g_value_init (&channel_numbers, GST_TYPE_LIST); + while (current_pos < delivery + DESC_LENGTH (delivery)) { + GstStructure *channel; + GValue channel_value = { 0 }; + guint16 service_id = GST_READ_UINT16_BE (current_pos); + guint16 logical_channel_number; + + current_pos += 2; + logical_channel_number = GST_READ_UINT16_BE (current_pos) & 0x03ff; + channel = + gst_structure_new ("channels", "service-id", G_TYPE_UINT, + service_id, "logical-channel-number", G_TYPE_UINT, + logical_channel_number, NULL); + g_value_init (&channel_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&channel_value, channel); + gst_value_list_append_value (&channel_numbers, &channel_value); + g_value_unset (&channel_value); + current_pos += 2; + } + gst_structure_set_value (transport, "channels", &channel_numbers); + g_value_unset (&channel_numbers); + } + if ((delivery = + gst_mpeg_descriptor_find (mpegdescriptor, + DESC_DVB_FREQUENCY_LIST))) { + guint8 *current_pos = delivery + 2; + GValue frequencies = { 0 }; + guint8 type; + + type = *current_pos & 0x03; + current_pos++; + + if (type) { + const gchar *fieldname = NULL; + g_value_init (&frequencies, GST_TYPE_LIST); + + while (current_pos < delivery + DESC_LENGTH (delivery) - 3) { + guint32 freq = 0; + guint8 *frequency_bcd = current_pos; + GValue frequency = { 0 }; + + switch (type) { + case 0x01: + /* satellite */ + freq = + 10 * ((frequency_bcd[3] & 0x0F) + + 10 * ((frequency_bcd[3] & 0xF0) >> 4) + + 100 * (frequency_bcd[2] & 0x0F) + + 1000 * ((frequency_bcd[2] & 0xF0) >> 4) + + 10000 * (frequency_bcd[1] & 0x0F) + + 100000 * ((frequency_bcd[1] & 0xF0) >> 4) + + 1000000 * (frequency_bcd[0] & 0x0F) + + 10000000 * ((frequency_bcd[0] & 0xF0) >> 4)); + break; + case 0x02: + /* cable */ + freq = 100 * + ((frequency_bcd[3] & 0x0F) + + 10 * ((frequency_bcd[3] & 0xF0) >> 4) + + 100 * (frequency_bcd[2] & 0x0F) + + 1000 * ((frequency_bcd[2] & 0xF0) >> 4) + + 10000 * (frequency_bcd[1] & 0x0F) + + 100000 * ((frequency_bcd[1] & 0xF0) >> 4) + + 1000000 * (frequency_bcd[0] & 0x0F) + + 10000000 * ((frequency_bcd[0] & 0xF0) >> 4)); + break; + case 0x03: + /* terrestrial */ + freq = GST_READ_UINT32_BE (current_pos) * 10; + break; + } + g_value_init (&frequency, G_TYPE_UINT); + g_value_set_uint (&frequency, freq); + gst_value_list_append_value (&frequencies, &frequency); + g_value_unset (&frequency); + current_pos += 4; + } + + switch (type) { + case 0x01: + fieldname = "frequency-list-satellite"; + break; + case 0x02: + fieldname = "frequency-list-cable"; + break; + case 0x03: + fieldname = "frequency-list-terrestrial"; + break; + } + + gst_structure_set_value (transport, fieldname, &frequencies); + g_value_unset (&frequencies); + } + } + gst_mpeg_descriptor_free (mpegdescriptor); + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + descriptors_loop_length, descriptors)) { + gst_structure_free (transport); + g_value_array_free (descriptors); + goto error; + } + + gst_structure_id_set (transport, QUARK_DESCRIPTORS, G_TYPE_VALUE_ARRAY, + descriptors, NULL); + g_value_array_free (descriptors); + } + + g_value_init (&transport_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&transport_value, transport); + gst_value_list_append_value (&transports, &transport_value); + g_value_unset (&transport_value); + + transport_stream_loop_length -= data - entry_begin; + } + + if (data != end - 4) { + GST_WARNING ("PID %d invalid NIT parsed %d length %d", + section->pid, (gint) (data - GST_BUFFER_DATA (section->buffer)), + GST_BUFFER_SIZE (section->buffer)); + goto error; + } + + gst_structure_id_set_value (nit, QUARK_TRANSPORTS, &transports); + g_value_unset (&transports); + + GST_DEBUG ("NIT %" GST_PTR_FORMAT, nit); + + return nit; + +error: + if (nit) + gst_structure_free (nit); + + if (GST_VALUE_HOLDS_LIST (&transports)) + g_value_unset (&transports); + + return NULL; +} + +GstStructure * +mpegts_packetizer_parse_sdt (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *sdt = NULL, *service = NULL; + guint8 *data, *end, *entry_begin; + guint16 transport_stream_id, original_network_id, service_id; + guint tmp; + guint sdt_info_length; + gboolean EIT_schedule, EIT_present_following; + guint8 running_status; + gboolean scrambled; + guint descriptors_loop_length; + GValue services = { 0 }; + GValueArray *descriptors = NULL; + GValue service_value = { 0 }; + + GST_DEBUG ("SDT"); + /* fixed header + CRC == 16 */ + if (GST_BUFFER_SIZE (section->buffer) < 14) { + GST_WARNING ("PID %d invalid SDT size %d", + section->pid, section->section_length); + goto error; + } + + data = GST_BUFFER_DATA (section->buffer); + end = data + GST_BUFFER_SIZE (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + if (data + section->section_length != end) { + GST_WARNING ("PID %d invalid SDT section length %d expected %d", + section->pid, section->section_length, (gint) (end - data)); + goto error; + } + + transport_stream_id = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + original_network_id = GST_READ_UINT16_BE (data); + data += 2; + + /* skip reserved byte */ + data += 1; + + sdt = gst_structure_id_new (QUARK_SDT, + QUARK_TRANSPORT_STREAM_ID, G_TYPE_UINT, transport_stream_id, + QUARK_VERSION_NUMBER, G_TYPE_UINT, section->version_number, + QUARK_CURRENT_NEXT_INDICATOR, G_TYPE_UINT, + section->current_next_indicator, QUARK_ORIGINAL_NETWORK_ID, G_TYPE_UINT, + original_network_id, QUARK_ACTUAL_TRANSPORT_STREAM, G_TYPE_BOOLEAN, + section->table_id == 0x42, NULL); + + sdt_info_length = section->section_length - 8; + g_value_init (&services, GST_TYPE_LIST); + /* read up to the CRC */ + while (sdt_info_length - 4 > 0) { + gchar *service_name; + + entry_begin = data; + + if (sdt_info_length < 9) { + /* each entry must be at least 5 bytes (+4 bytes for the CRC) */ + GST_WARNING ("PID %d invalid SDT entry size %d", + section->pid, sdt_info_length); + goto error; + } + + service_id = GST_READ_UINT16_BE (data); + data += 2; + + EIT_schedule = ((*data & 0x02) == 2); + EIT_present_following = (*data & 0x01) == 1; + + data += 1; + tmp = GST_READ_UINT16_BE (data); + + running_status = (*data >> 5) & 0x07; + scrambled = (*data >> 4) & 0x01; + descriptors_loop_length = tmp & 0x0FFF; + data += 2; + + /* TODO send tag event down relevant pad for channel name and provider */ + service_name = g_strdup_printf ("service-%d", service_id); + service = gst_structure_new (service_name, NULL); + g_free (service_name); + + if (descriptors_loop_length) { + guint8 *service_descriptor; + GstMPEGDescriptor *mpegdescriptor; + + if (data + descriptors_loop_length > end - 4) { + GST_WARNING ("PID %d invalid SDT entry %d descriptors loop length %d", + section->pid, service_id, descriptors_loop_length); + gst_structure_free (service); + goto error; + } + mpegdescriptor = + gst_mpeg_descriptor_parse (data, descriptors_loop_length); + service_descriptor = + gst_mpeg_descriptor_find (mpegdescriptor, DESC_DVB_SERVICE); + if (service_descriptor != NULL) { + gchar *servicename_tmp, *serviceprovider_name_tmp; + guint8 serviceprovider_name_length = + DESC_DVB_SERVICE_provider_name_length (service_descriptor); + gchar *serviceprovider_name = + (gchar *) DESC_DVB_SERVICE_provider_name_text (service_descriptor); + guint8 servicename_length = + DESC_DVB_SERVICE_name_length (service_descriptor); + gchar *servicename = + (gchar *) DESC_DVB_SERVICE_name_text (service_descriptor); + if (servicename_length + serviceprovider_name_length + 2 <= + DESC_LENGTH (service_descriptor)) { + const gchar *running_status_tmp; + switch (running_status) { + case 0: + running_status_tmp = "undefined"; + break; + case 1: + running_status_tmp = "not running"; + break; + case 2: + running_status_tmp = "starts in a few seconds"; + break; + case 3: + running_status_tmp = "pausing"; + break; + case 4: + running_status_tmp = "running"; + break; + default: + running_status_tmp = "reserved"; + } + servicename_tmp = + get_encoding_and_convert (servicename, servicename_length); + serviceprovider_name_tmp = + get_encoding_and_convert (serviceprovider_name, + serviceprovider_name_length); + + gst_structure_set (service, + "name", G_TYPE_STRING, servicename_tmp, + "provider-name", G_TYPE_STRING, serviceprovider_name_tmp, + "scrambled", G_TYPE_BOOLEAN, scrambled, + "running-status", G_TYPE_STRING, running_status_tmp, NULL); + + g_free (servicename_tmp); + g_free (serviceprovider_name_tmp); + } + } + gst_mpeg_descriptor_free (mpegdescriptor); + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + descriptors_loop_length, descriptors)) { + gst_structure_free (service); + g_value_array_free (descriptors); + goto error; + } + + gst_structure_id_set (service, QUARK_DESCRIPTORS, G_TYPE_VALUE_ARRAY, + descriptors, NULL); + + g_value_array_free (descriptors); + } + + g_value_init (&service_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&service_value, service); + gst_value_list_append_value (&services, &service_value); + g_value_unset (&service_value); + + sdt_info_length -= data - entry_begin; + } + + if (data != end - 4) { + GST_WARNING ("PID %d invalid SDT parsed %d length %d", + section->pid, (gint) (data - GST_BUFFER_DATA (section->buffer)), + GST_BUFFER_SIZE (section->buffer)); + goto error; + } + + gst_structure_id_set_value (sdt, QUARK_SERVICES, &services); + g_value_unset (&services); + + return sdt; + +error: + if (sdt) + gst_structure_free (sdt); + + if (GST_VALUE_HOLDS_LIST (&services)) + g_value_unset (&services); + + return NULL; +} + +GstStructure * +mpegts_packetizer_parse_eit (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *eit = NULL, *event = NULL; + guint service_id, last_table_id, segment_last_section_number; + guint transport_stream_id, original_network_id; + gboolean free_ca_mode; + guint event_id, running_status; + guint64 start_and_duration; + guint16 mjd; + guint year, month, day, hour, minute, second; + guint duration; + guint8 *data, *end, *duration_ptr, *utc_ptr; + guint16 descriptors_loop_length; + GValue events = { 0 }; + GValue event_value = { 0 }; + GValueArray *descriptors = NULL; + gchar *event_name; + guint tmp; + + /* fixed header + CRC == 16 */ + if (GST_BUFFER_SIZE (section->buffer) < 18) { + GST_WARNING ("PID %d invalid EIT size %d", + section->pid, section->section_length); + goto error; + } + + data = GST_BUFFER_DATA (section->buffer); + end = data + GST_BUFFER_SIZE (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + if (data + section->section_length != end) { + GST_WARNING ("PID %d invalid EIT section length %d expected %d", + section->pid, section->section_length, (gint) (end - data)); + goto error; + } + + service_id = GST_READ_UINT16_BE (data); + data += 2; + + tmp = *data++; + section->version_number = (tmp >> 1) & 0x1F; + section->current_next_indicator = tmp & 0x01; + + /* skip section_number and last_section_number */ + data += 2; + + transport_stream_id = GST_READ_UINT16_BE (data); + data += 2; + original_network_id = GST_READ_UINT16_BE (data); + data += 2; + segment_last_section_number = *data; + data += 1; + last_table_id = *data; + data += 1; + + eit = gst_structure_id_new (QUARK_EIT, + QUARK_VERSION_NUMBER, G_TYPE_UINT, section->version_number, + QUARK_CURRENT_NEXT_INDICATOR, G_TYPE_UINT, + section->current_next_indicator, QUARK_SERVICE_ID, G_TYPE_UINT, + service_id, QUARK_ACTUAL_TRANSPORT_STREAM, G_TYPE_BOOLEAN, + (section->table_id == 0x4E || (section->table_id >= 0x50 + && section->table_id <= 0x5F)), QUARK_PRESENT_FOLLOWING, + G_TYPE_BOOLEAN, (section->table_id == 0x4E + || section->table_id == 0x4F), QUARK_TRANSPORT_STREAM_ID, G_TYPE_UINT, + transport_stream_id, QUARK_ORIGINAL_NETWORK_ID, G_TYPE_UINT, + original_network_id, QUARK_SEGMENT_LAST_SECTION_NUMBER, G_TYPE_UINT, + segment_last_section_number, QUARK_LAST_TABLE_ID, G_TYPE_UINT, + last_table_id, NULL); + + g_value_init (&events, GST_TYPE_LIST); + while (data < end - 4) { + /* 12 is the minimum entry size + CRC */ + if (end - data < 12 + 4) { + GST_WARNING ("PID %d invalid EIT entry length %d", + section->pid, (gint) (end - 4 - data)); + gst_structure_free (eit); + goto error; + } + + event_id = GST_READ_UINT16_BE (data); + data += 2; + start_and_duration = GST_READ_UINT64_BE (data); + duration_ptr = data + 5; + utc_ptr = data + 2; + mjd = GST_READ_UINT16_BE (data); + if (mjd == G_MAXUINT16) { + year = 1900; + month = day = hour = minute = second = 0; + } else { + /* See EN 300 468 Annex C */ + year = (guint32) (((mjd - 15078.2) / 365.25)); + month = (guint8) ((mjd - 14956.1 - (guint) (year * 365.25)) / 30.6001); + day = mjd - 14956 - (guint) (year * 365.25) - (guint) (month * 30.6001); + if (month == 14 || month == 15) { + year++; + month = month - 1 - 12; + } else { + month--; + } + year += 1900; + hour = ((utc_ptr[0] & 0xF0) >> 4) * 10 + (utc_ptr[0] & 0x0F); + minute = ((utc_ptr[1] & 0xF0) >> 4) * 10 + (utc_ptr[1] & 0x0F); + second = ((utc_ptr[2] & 0xF0) >> 4) * 10 + (utc_ptr[2] & 0x0F); + } + + duration = (((duration_ptr[0] & 0xF0) >> 4) * 10 + + (duration_ptr[0] & 0x0F)) * 60 * 60 + + (((duration_ptr[1] & 0xF0) >> 4) * 10 + + (duration_ptr[1] & 0x0F)) * 60 + + ((duration_ptr[2] & 0xF0) >> 4) * 10 + (duration_ptr[2] & 0x0F); + + data += 8; + running_status = *data >> 5; + free_ca_mode = (*data >> 4) & 0x01; + descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + /* TODO: send tag event down relevant pad saying what is currently playing */ + event_name = g_strdup_printf ("event-%d", event_id); + event = gst_structure_new (event_name, + "event-id", G_TYPE_UINT, event_id, + "year", G_TYPE_UINT, year, + "month", G_TYPE_UINT, month, + "day", G_TYPE_UINT, day, + "hour", G_TYPE_UINT, hour, + "minute", G_TYPE_UINT, minute, + "second", G_TYPE_UINT, second, + "duration", G_TYPE_UINT, duration, + "running-status", G_TYPE_UINT, running_status, + "free-ca-mode", G_TYPE_BOOLEAN, free_ca_mode, NULL); + g_free (event_name); + + if (descriptors_loop_length) { + guint8 *event_descriptor; + GArray *component_descriptors; + GArray *extended_event_descriptors; + GstMPEGDescriptor *mpegdescriptor; + + if (data + descriptors_loop_length > end - 4) { + GST_WARNING ("PID %d invalid EIT descriptors loop length %d", + section->pid, descriptors_loop_length); + gst_structure_free (event); + goto error; + } + mpegdescriptor = + gst_mpeg_descriptor_parse (data, descriptors_loop_length); + event_descriptor = + gst_mpeg_descriptor_find (mpegdescriptor, DESC_DVB_SHORT_EVENT); + if (event_descriptor != NULL) { + gchar *eventname_tmp, *eventdescription_tmp; + guint8 eventname_length = + DESC_DVB_SHORT_EVENT_name_length (event_descriptor); + gchar *eventname = + (gchar *) DESC_DVB_SHORT_EVENT_name_text (event_descriptor); + guint8 eventdescription_length = + DESC_DVB_SHORT_EVENT_description_length (event_descriptor); + gchar *eventdescription = + (gchar *) DESC_DVB_SHORT_EVENT_description_text (event_descriptor); + if (eventname_length + eventdescription_length + 2 <= + DESC_LENGTH (event_descriptor)) { + + eventname_tmp = + get_encoding_and_convert (eventname, eventname_length), + eventdescription_tmp = + get_encoding_and_convert (eventdescription, + eventdescription_length); + + gst_structure_set (event, "name", G_TYPE_STRING, eventname_tmp, NULL); + gst_structure_set (event, "description", G_TYPE_STRING, + eventdescription_tmp, NULL); + g_free (eventname_tmp); + g_free (eventdescription_tmp); + } + } + extended_event_descriptors = gst_mpeg_descriptor_find_all (mpegdescriptor, + DESC_DVB_EXTENDED_EVENT); + if (extended_event_descriptors) { + int i; + guint8 *extended_descriptor; + /*GValue extended_items = { 0 }; */ + gchar *extended_text = NULL; + /*g_value_init (&extended_items, GST_TYPE_LIST); */ + for (i = 0; i < extended_event_descriptors->len; i++) { + extended_descriptor = g_array_index (extended_event_descriptors, + guint8 *, i); + if (DESC_DVB_EXTENDED_EVENT_descriptor_number (extended_descriptor) == + i) { + if (extended_text) { + gchar *tmp; + gchar *old_extended_text = extended_text; + tmp = get_encoding_and_convert ((gchar *) + DESC_DVB_EXTENDED_EVENT_text (extended_descriptor), + DESC_DVB_EXTENDED_EVENT_text_length (extended_descriptor)); + extended_text = g_strdup_printf ("%s%s", extended_text, tmp); + g_free (old_extended_text); + g_free (tmp); + } else { + extended_text = get_encoding_and_convert ((gchar *) + DESC_DVB_EXTENDED_EVENT_text (extended_descriptor), + DESC_DVB_EXTENDED_EVENT_text_length (extended_descriptor)); + } + } + } + if (extended_text) { + gst_structure_set (event, "extended-text", G_TYPE_STRING, + extended_text, NULL); + g_free (extended_text); + } + g_array_free (extended_event_descriptors, TRUE); + } + + component_descriptors = gst_mpeg_descriptor_find_all (mpegdescriptor, + DESC_DVB_COMPONENT); + if (component_descriptors) { + int i; + guint8 *comp_descriptor; + GValue components = { 0 }; + g_value_init (&components, GST_TYPE_LIST); + /* FIXME: do the component descriptor parsing less verbosely + * and better...a task for 0.10.6 */ + for (i = 0; i < component_descriptors->len; i++) { + GstStructure *component = NULL; + GValue component_value = { 0 }; + gint widescreen = 0; /* 0 for 4:3, 1 for 16:9, 2 for > 16:9 */ + gint freq = 25; /* 25 or 30 measured in Hertz */ + gboolean highdef = FALSE; + gboolean panvectors = FALSE; + const gchar *comptype = ""; + + comp_descriptor = g_array_index (component_descriptors, guint8 *, i); + switch (DESC_DVB_COMPONENT_stream_content (comp_descriptor)) { + case 0x01: + /* video */ + switch (DESC_DVB_COMPONENT_type (comp_descriptor)) { + case 0x01: + widescreen = 0; + freq = 25; + break; + case 0x02: + widescreen = 1; + panvectors = TRUE; + freq = 25; + break; + case 0x03: + widescreen = 1; + panvectors = FALSE; + freq = 25; + break; + case 0x04: + widescreen = 2; + freq = 25; + break; + case 0x05: + widescreen = 0; + freq = 30; + break; + case 0x06: + widescreen = 1; + panvectors = TRUE; + freq = 30; + break; + case 0x07: + widescreen = 1; + panvectors = FALSE; + freq = 30; + break; + case 0x08: + widescreen = 2; + freq = 30; + break; + case 0x09: + widescreen = 0; + highdef = TRUE; + freq = 25; + break; + case 0x0A: + widescreen = 1; + highdef = TRUE; + panvectors = TRUE; + freq = 25; + break; + case 0x0B: + widescreen = 1; + highdef = TRUE; + panvectors = FALSE; + freq = 25; + break; + case 0x0C: + widescreen = 2; + highdef = TRUE; + freq = 25; + break; + case 0x0D: + widescreen = 0; + highdef = TRUE; + freq = 30; + break; + case 0x0E: + widescreen = 1; + highdef = TRUE; + panvectors = TRUE; + freq = 30; + break; + case 0x0F: + widescreen = 1; + highdef = TRUE; + panvectors = FALSE; + freq = 30; + break; + case 0x10: + widescreen = 2; + highdef = TRUE; + freq = 30; + break; + } + component = gst_structure_new ("video", "high-definition", + G_TYPE_BOOLEAN, TRUE, "frequency", G_TYPE_INT, freq, + "tag", G_TYPE_INT, DESC_DVB_COMPONENT_tag (comp_descriptor), + NULL); + if (widescreen == 0) { + gst_structure_set (component, "aspect-ratio", + G_TYPE_STRING, "4:3", NULL); + } else if (widescreen == 2) { + gst_structure_set (component, "aspect-ratio", G_TYPE_STRING, + "> 16:9", NULL); + } else { + gst_structure_set (component, "aspect-ratio", G_TYPE_STRING, + "16:9", "pan-vectors", G_TYPE_BOOLEAN, panvectors, NULL); + } + break; + case 0x02: /* audio */ + comptype = "undefined"; + switch (DESC_DVB_COMPONENT_type (comp_descriptor)) { + case 0x01: + comptype = "single channel mono"; + break; + case 0x02: + comptype = "dual channel mono"; + break; + case 0x03: + comptype = "stereo"; + break; + case 0x04: + comptype = "multi-channel multi-lingual"; + break; + case 0x05: + comptype = "surround"; + break; + case 0x40: + comptype = "audio description for the visually impaired"; + break; + case 0x41: + comptype = "audio for the hard of hearing"; + break; + } + component = gst_structure_new ("audio", "type", G_TYPE_STRING, + comptype, "tag", G_TYPE_INT, + DESC_DVB_COMPONENT_tag (comp_descriptor), NULL); + break; + case 0x03: /* subtitles/teletext/vbi */ + comptype = "reserved"; + switch (DESC_DVB_COMPONENT_type (comp_descriptor)) { + case 0x01: + comptype = "EBU Teletext subtitles"; + break; + case 0x02: + comptype = "associated EBU Teletext"; + break; + case 0x03: + comptype = "VBI data"; + break; + case 0x10: + comptype = "Normal DVB subtitles"; + break; + case 0x11: + comptype = "Normal DVB subtitles for 4:3"; + break; + case 0x12: + comptype = "Normal DVB subtitles for 16:9"; + break; + case 0x13: + comptype = "Normal DVB subtitles for 2.21:1"; + break; + case 0x20: + comptype = "Hard of hearing DVB subtitles"; + break; + case 0x21: + comptype = "Hard of hearing DVB subtitles for 4:3"; + break; + case 0x22: + comptype = "Hard of hearing DVB subtitles for 16:9"; + break; + case 0x23: + comptype = "Hard of hearing DVB subtitles for 2.21:1"; + break; + } + component = gst_structure_new ("teletext", "type", G_TYPE_STRING, + comptype, "tag", G_TYPE_INT, + DESC_DVB_COMPONENT_tag (comp_descriptor), NULL); + break; + } + if (component) { + g_value_init (&component_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&component_value, component); + gst_value_list_append_value (&components, &component_value); + g_value_unset (&component_value); + component = NULL; + } + } + gst_structure_set_value (event, "components", &components); + g_value_unset (&components); + g_array_free (component_descriptors, TRUE); + } + gst_mpeg_descriptor_free (mpegdescriptor); + + descriptors = g_value_array_new (0); + if (!mpegts_packetizer_parse_descriptors (packetizer, + &data, data + descriptors_loop_length, descriptors)) { + gst_structure_free (event); + g_value_array_free (descriptors); + goto error; + } + gst_structure_id_set (event, QUARK_DESCRIPTORS, G_TYPE_VALUE_ARRAY, + descriptors, NULL); + g_value_array_free (descriptors); + } + + g_value_init (&event_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&event_value, event); + gst_value_list_append_value (&events, &event_value); + g_value_unset (&event_value); + } + + if (data != end - 4) { + GST_WARNING ("PID %d invalid EIT parsed %d length %d", + section->pid, (gint) (data - GST_BUFFER_DATA (section->buffer)), + GST_BUFFER_SIZE (section->buffer)); + goto error; + } + + gst_structure_id_set_value (eit, QUARK_EVENTS, &events); + g_value_unset (&events); + + GST_DEBUG ("EIT %" GST_PTR_FORMAT, eit); + + return eit; + +error: + if (eit) + gst_structure_free (eit); + + if (GST_VALUE_HOLDS_LIST (&events)) + g_value_unset (&events); + + return NULL; +} + +GstStructure * +mpegts_packetizer_parse_tdt (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerSection * section) +{ + GstStructure *tdt = NULL; + guint16 mjd; + guint year, month, day, hour, minute, second; + guint8 *data, *end, *utc_ptr; + + GST_DEBUG ("TDT"); + /* length always 8 */ + if (G_UNLIKELY (GST_BUFFER_SIZE (section->buffer) != 8)) { + GST_WARNING ("PID %d invalid TDT size %d", + section->pid, section->section_length); + goto error; + } + + data = GST_BUFFER_DATA (section->buffer); + end = data + GST_BUFFER_SIZE (section->buffer); + + section->table_id = *data++; + section->section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + data += 2; + + if (data + section->section_length != end) { + GST_WARNING ("PID %d invalid TDT section length %d expected %d", + section->pid, section->section_length, (gint) (end - data)); + goto error; + } + + mjd = GST_READ_UINT16_BE (data); + data += 2; + utc_ptr = data; + if (mjd == G_MAXUINT16) { + year = 1900; + month = day = hour = minute = second = 0; + } else { + /* See EN 300 468 Annex C */ + year = (guint32) (((mjd - 15078.2) / 365.25)); + month = (guint8) ((mjd - 14956.1 - (guint) (year * 365.25)) / 30.6001); + day = mjd - 14956 - (guint) (year * 365.25) - (guint) (month * 30.6001); + if (month == 14 || month == 15) { + year++; + month = month - 1 - 12; + } else { + month--; + } + year += 1900; + hour = ((utc_ptr[0] & 0xF0) >> 4) * 10 + (utc_ptr[0] & 0x0F); + minute = ((utc_ptr[1] & 0xF0) >> 4) * 10 + (utc_ptr[1] & 0x0F); + second = ((utc_ptr[2] & 0xF0) >> 4) * 10 + (utc_ptr[2] & 0x0F); + } + tdt = gst_structure_new ("tdt", + "year", G_TYPE_UINT, year, + "month", G_TYPE_UINT, month, + "day", G_TYPE_UINT, day, + "hour", G_TYPE_UINT, hour, + "minute", G_TYPE_UINT, minute, "second", G_TYPE_UINT, second, NULL); + + return tdt; + +error: + if (tdt) + gst_structure_free (tdt); + + return NULL; +} + +void +mpegts_packetizer_clear (MpegTSPacketizer2 * packetizer) +{ + if (packetizer->know_packet_size) { + packetizer->know_packet_size = FALSE; + packetizer->packet_size = 0; + if (packetizer->caps != NULL) { + gst_caps_unref (packetizer->caps); + packetizer->caps = NULL; + } + } + if (packetizer->streams) { + int i; + for (i = 0; i < 8192; i++) { + if (packetizer->streams[i]) { + mpegts_packetizer_stream_free (packetizer->streams[i]); + } + } + memset (packetizer->streams, 0, 8192 * sizeof (MpegTSPacketizerStream *)); + } + + gst_adapter_clear (packetizer->adapter); + packetizer->offset = 0; + packetizer->empty = TRUE; +} + +void +mpegts_packetizer_remove_stream (MpegTSPacketizer2 * packetizer, gint16 pid) +{ + MpegTSPacketizerStream *stream = packetizer->streams[pid]; + if (stream) { + GST_INFO ("Removing stream for PID %d", pid); + mpegts_packetizer_stream_free (stream); + packetizer->streams[pid] = NULL; + } +} + +MpegTSPacketizer2 * +mpegts_packetizer_new (void) +{ + MpegTSPacketizer2 *packetizer; + + packetizer = + GST_MPEGTS_PACKETIZER (g_object_new (GST_TYPE_MPEGTS_PACKETIZER, NULL)); + + return packetizer; +} + +void +mpegts_packetizer_push (MpegTSPacketizer2 * packetizer, GstBuffer * buffer) +{ + if (G_UNLIKELY (packetizer->empty)) { + packetizer->empty = FALSE; + packetizer->offset = GST_BUFFER_OFFSET (buffer); + } + + GST_DEBUG ("Pushing %u byte from offset %" G_GUINT64_FORMAT, + GST_BUFFER_SIZE (buffer), GST_BUFFER_OFFSET (buffer)); + gst_adapter_push (packetizer->adapter, buffer); +} + +static gboolean +mpegts_try_discover_packet_size (MpegTSPacketizer2 * packetizer) +{ + guint8 *dest; + int i, pos = -1, j; + static const guint psizes[] = { + MPEGTS_NORMAL_PACKETSIZE, + MPEGTS_M2TS_PACKETSIZE, + MPEGTS_DVB_ASI_PACKETSIZE, + MPEGTS_ATSC_PACKETSIZE + }; + + + dest = g_malloc (MPEGTS_MAX_PACKETSIZE * 4); + /* wait for 3 sync bytes */ + while (packetizer->adapter->size >= MPEGTS_MAX_PACKETSIZE * 4) { + + /* check for sync bytes */ + gst_adapter_copy (packetizer->adapter, dest, 0, MPEGTS_MAX_PACKETSIZE * 4); + /* find first sync byte */ + pos = -1; + for (i = 0; i < MPEGTS_MAX_PACKETSIZE; i++) { + if (dest[i] == 0x47) { + for (j = 0; j < 4; j++) { + guint packetsize = psizes[j]; + /* check each of the packet size possibilities in turn */ + if (dest[i] == 0x47 && dest[i + packetsize] == 0x47 && + dest[i + packetsize * 2] == 0x47 && + dest[i + packetsize * 3] == 0x47) { + packetizer->know_packet_size = TRUE; + packetizer->packet_size = packetsize; + packetizer->caps = gst_caps_new_simple ("video/mpegts", + "systemstream", G_TYPE_BOOLEAN, TRUE, + "packetsize", G_TYPE_INT, packetsize, NULL); + if (packetsize == MPEGTS_M2TS_PACKETSIZE) + pos = i - 4; + else + pos = i; + break; + } + } + break; + } + } + + if (packetizer->know_packet_size) + break; + + /* Skip MPEGTS_MAX_PACKETSIZE */ + gst_adapter_flush (packetizer->adapter, MPEGTS_MAX_PACKETSIZE); + packetizer->offset += MPEGTS_MAX_PACKETSIZE; + } + + g_free (dest); + + if (packetizer->know_packet_size) { + GST_DEBUG ("have packetsize detected: %d of %u bytes", + packetizer->know_packet_size, packetizer->packet_size); + /* flush to sync byte */ + if (pos > 0) { + GST_DEBUG ("Flushing out %d bytes", pos); + gst_adapter_flush (packetizer->adapter, pos); + packetizer->offset += pos; + } else if (!packetizer->know_packet_size) { + /* drop invalid data and move to the next possible packets */ + gst_adapter_flush (packetizer->adapter, MPEGTS_MAX_PACKETSIZE); + } + } + + return packetizer->know_packet_size; +} + +gboolean +mpegts_packetizer_has_packets (MpegTSPacketizer2 * packetizer) +{ + if (G_UNLIKELY (packetizer->know_packet_size == FALSE)) { + if (!mpegts_try_discover_packet_size (packetizer)) + return FALSE; + } + return packetizer->adapter->size >= packetizer->packet_size; +} + +MpegTSPacketizerPacketReturn +mpegts_packetizer_next_packet (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerPacket * packet) +{ + guint avail; + + packet->buffer = NULL; + + if (G_UNLIKELY (!packetizer->know_packet_size)) { + if (!mpegts_try_discover_packet_size (packetizer)) + return PACKET_NEED_MORE; + } + + while ((avail = packetizer->adapter->size) >= packetizer->packet_size) { + packet->buffer = gst_adapter_take_buffer (packetizer->adapter, + packetizer->packet_size); + /* M2TS packets don't start with the sync byte, all other variants do */ + if (packetizer->packet_size == MPEGTS_M2TS_PACKETSIZE) { + packet->data_start = GST_BUFFER_DATA (packet->buffer) + 4; + } else { + packet->data_start = GST_BUFFER_DATA (packet->buffer); + } + /* ALL mpeg-ts variants contain 188 bytes of data. Those with bigger packet + * sizes contain either extra data (timesync, FEC, ..) either before or after + * the data */ + packet->data_end = packet->data_start + 188; + GST_BUFFER_OFFSET (packet->buffer) = packet->offset = packetizer->offset; + GST_DEBUG ("offset %" G_GUINT64_FORMAT, packet->offset); + packetizer->offset += packetizer->packet_size; + GST_MEMDUMP ("buffer", GST_BUFFER_DATA (packet->buffer), 16); + GST_MEMDUMP ("data_start", packet->data_start, 16); + + /* Check sync byte */ + if (G_UNLIKELY (packet->data_start[0] != 0x47)) { + guint i; + GstBuffer *tmpbuf; + + GST_LOG ("Lost sync %d", packetizer->packet_size); + /* Find the 0x47 in the buffer */ + for (i = 0; i < packetizer->packet_size; i++) + if (GST_BUFFER_DATA (packet->buffer)[i] == 0x47) + break; + if (G_UNLIKELY (i == packetizer->packet_size)) { + GST_ERROR ("REALLY lost the sync"); + gst_buffer_unref (packet->buffer); + goto done; + } + /* Pop out the remaining data... */ + GST_BUFFER_DATA (packet->buffer) += i; + GST_BUFFER_SIZE (packet->buffer) -= i; + GST_BUFFER_OFFSET (packet->buffer) += i; + tmpbuf = + gst_adapter_take_buffer (packetizer->adapter, + packetizer->adapter->size); + /* ... and push everything back in */ + gst_adapter_push (packetizer->adapter, packet->buffer); + gst_adapter_push (packetizer->adapter, tmpbuf); + continue; + } + + return mpegts_packetizer_parse_packet (packetizer, packet); + } + +done: + return PACKET_NEED_MORE; +} + +void +mpegts_packetizer_clear_packet (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerPacket * packet) +{ + memset (packet, 0, sizeof (MpegTSPacketizerPacket)); +} + +gboolean +mpegts_packetizer_push_section (MpegTSPacketizer2 * packetizer, + MpegTSPacketizerPacket * packet, MpegTSPacketizerSection * section) +{ + gboolean res = FALSE; + MpegTSPacketizerStream *stream; + guint8 pointer, table_id; + guint16 subtable_extension; + guint section_length; + GstBuffer *sub_buf; + guint8 *data; + + data = packet->data; + section->pid = packet->pid; + + if (packet->payload_unit_start_indicator == 1) { + pointer = *data++; + if (data + pointer > packet->data_end) { + GST_WARNING ("PID %d PSI section pointer points past the end " + "of the buffer", packet->pid); + goto out; + } + + data += pointer; + } + /* TDT and TOT sections (see ETSI EN 300 468 5.2.5) + * these sections do not extend to several packets so we don't need to use the + * sections filter. */ + if (packet->pid == 0x14) { + table_id = data[0]; + section->section_length = GST_READ_UINT24_BE (data) & 0x000FFF; + section->buffer = gst_buffer_create_sub (packet->buffer, + data - GST_BUFFER_DATA (packet->buffer), section->section_length + 3); + section->table_id = table_id; + section->complete = TRUE; + res = TRUE; + GST_DEBUG ("TDT section pid:%d table_id:%d section_length: %d\n", + packet->pid, table_id, section->section_length); + goto out; + } + + /* create a sub buffer from the start of the section (table_id and + * section_length included) to the end */ + sub_buf = gst_buffer_create_sub (packet->buffer, + data - GST_BUFFER_DATA (packet->buffer), packet->data_end - data); + + + stream = packetizer->streams[packet->pid]; + if (stream == NULL) { + stream = mpegts_packetizer_stream_new (); + packetizer->streams[packet->pid] = stream; + } + + if (packet->payload_unit_start_indicator) { + table_id = *data++; + /* subtable_extension should be read from 4th and 5th bytes only if + * section_syntax_indicator is 1 */ + if ((data[0] & 0x80) == 0) + subtable_extension = 0; + else + subtable_extension = GST_READ_UINT16_BE (data + 2); + GST_DEBUG ("pid: %d table_id %d sub_table_extension %d", + packet->pid, table_id, subtable_extension); + + section_length = GST_READ_UINT16_BE (data) & 0x0FFF; + + if (stream->continuity_counter != CONTINUITY_UNSET) { + GST_DEBUG + ("PID %d table_id %d sub_table_extension %d payload_unit_start_indicator set but section " + "not complete (last_continuity: %d continuity: %d sec len %d buffer %d avail %d", + packet->pid, table_id, subtable_extension, stream->continuity_counter, + packet->continuity_counter, section_length, GST_BUFFER_SIZE (sub_buf), + stream->section_adapter->size); + mpegts_packetizer_clear_section (packetizer, stream); + } else { + GST_DEBUG + ("pusi set and new stream section is %d long and data we have is: %d", + section_length, (gint) (packet->data_end - packet->data)); + } + stream->continuity_counter = packet->continuity_counter; + stream->section_length = section_length; + stream->section_table_id = table_id; + stream->offset = packet->offset; + gst_adapter_push (stream->section_adapter, sub_buf); + + res = TRUE; + } else if (stream->continuity_counter != CONTINUITY_UNSET && + (packet->continuity_counter == stream->continuity_counter + 1 || + (stream->continuity_counter == MAX_CONTINUITY && + packet->continuity_counter == 0))) { + stream->continuity_counter = packet->continuity_counter; + gst_adapter_push (stream->section_adapter, sub_buf); + + res = TRUE; + } else { + if (stream->continuity_counter == CONTINUITY_UNSET) + GST_DEBUG ("PID %d waiting for pusi", packet->pid); + else + GST_DEBUG ("PID %d section discontinuity " + "(last_continuity: %d continuity: %d", packet->pid, + stream->continuity_counter, packet->continuity_counter); + mpegts_packetizer_clear_section (packetizer, stream); + gst_buffer_unref (sub_buf); + } + + if (res) { + /* we pushed some data in the section adapter, see if the section is + * complete now */ + + /* >= as sections can be padded and padding is not included in + * section_length */ + if (stream->section_adapter->size >= stream->section_length + 3) { + res = mpegts_packetizer_parse_section_header (packetizer, + stream, section); + + /* flush stuffing bytes */ + mpegts_packetizer_clear_section (packetizer, stream); + } else { + GST_DEBUG ("section not complete"); + /* section not complete yet */ + section->complete = FALSE; + } + } else { + GST_WARNING ("section not complete"); + section->complete = FALSE; + } + +out: + packet->data = data; + GST_DEBUG ("result: %d complete: %d", res, section->complete); + return res; +} + +static void +_init_local (void) +{ + GST_DEBUG_CATEGORY_INIT (mpegts_packetizer_debug, "mpegtspacketizer", 0, + "MPEG transport stream parser"); + + QUARK_PAT = g_quark_from_string ("pat"); + QUARK_TRANSPORT_STREAM_ID = g_quark_from_string ("transport-stream-id"); + QUARK_PROGRAM_NUMBER = g_quark_from_string ("program-number"); + QUARK_PID = g_quark_from_string ("pid"); + QUARK_PROGRAMS = g_quark_from_string ("programs"); + + QUARK_PMT = g_quark_from_string ("pmt"); + QUARK_PCR_PID = g_quark_from_string ("pcr-pid"); + QUARK_VERSION_NUMBER = g_quark_from_string ("version-number"); + QUARK_DESCRIPTORS = g_quark_from_string ("descriptors"); + QUARK_STREAM_TYPE = g_quark_from_string ("stream-type"); + QUARK_STREAMS = g_quark_from_string ("streams"); + + QUARK_NIT = g_quark_from_string ("nit"); + QUARK_NETWORK_ID = g_quark_from_string ("network-id"); + QUARK_CURRENT_NEXT_INDICATOR = g_quark_from_string ("current-next-indicator"); + QUARK_ACTUAL_NETWORK = g_quark_from_string ("actual-network"); + QUARK_NETWORK_NAME = g_quark_from_string ("network-name"); + QUARK_ORIGINAL_NETWORK_ID = g_quark_from_string ("original-network-id"); + QUARK_TRANSPORTS = g_quark_from_string ("transports"); + + QUARK_SDT = g_quark_from_string ("sdt"); + QUARK_ACTUAL_TRANSPORT_STREAM = + g_quark_from_string ("actual-transport-stream"); + QUARK_SERVICES = g_quark_from_string ("services"); + + QUARK_EIT = g_quark_from_string ("eit"); + QUARK_SERVICE_ID = g_quark_from_string ("service-id"); + QUARK_PRESENT_FOLLOWING = g_quark_from_string ("present-following"); + QUARK_SEGMENT_LAST_SECTION_NUMBER = + g_quark_from_string ("segment-last-section-number"); + QUARK_LAST_TABLE_ID = g_quark_from_string ("last-table-id"); + QUARK_EVENTS = g_quark_from_string ("events"); +} + +/** + * @text: The text you want to get the encoding from + * @start_text: Location where the beginning of the actual text is stored + * @is_multibyte: Location where information whether it's a multibyte encoding + * or not is stored + * @returns: Name of encoding or NULL of encoding could not be detected. + * + * The returned string should be freed with g_free () when no longer needed. + */ +static gchar * +get_encoding (const gchar * text, guint * start_text, gboolean * is_multibyte) +{ + gchar *encoding; + guint8 firstbyte; + + g_return_val_if_fail (text != NULL, NULL); + + firstbyte = (guint8) text[0]; + + if (firstbyte == 0x01) { + encoding = g_strdup ("iso8859-5"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte == 0x02) { + encoding = g_strdup ("iso8859-6"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte == 0x03) { + encoding = g_strdup ("iso8859-7"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte == 0x04) { + encoding = g_strdup ("iso8859-8"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte == 0x05) { + encoding = g_strdup ("iso8859-9"); + *start_text = 1; + *is_multibyte = FALSE; + } else if (firstbyte >= 0x20) { + encoding = g_strdup ("iso6937"); + *start_text = 0; + *is_multibyte = FALSE; + } else if (firstbyte == 0x10) { + guint16 table; + gchar table_str[6]; + + text++; + table = GST_READ_UINT16_BE (text); + g_snprintf (table_str, 6, "%d", table); + + encoding = g_strconcat ("iso8859-", table_str, NULL); + *start_text = 3; + *is_multibyte = FALSE; + } else if (firstbyte == 0x11) { + encoding = g_strdup ("ISO-10646/UCS2"); + *start_text = 1; + *is_multibyte = TRUE; + } else if (firstbyte == 0x12) { + // That's korean encoding. + // The spec says it's encoded in KSC 5601, but iconv only knows KSC 5636. + // Couldn't find any information about either of them. + encoding = NULL; + *start_text = 1; + *is_multibyte = TRUE; + } else { + // reserved + encoding = NULL; + *start_text = 0; + *is_multibyte = FALSE; + } + + GST_DEBUG + ("Found encoding %s, first byte is 0x%02x, start_text: %u, is_multibyte: %d", + encoding, firstbyte, *start_text, *is_multibyte); + + return encoding; +} + +/** + * @text: The text to convert. It may include pango markup (<b> and </b>) + * @length: The length of the string -1 if it's nul-terminated + * @start: Where to start converting in the text + * @encoding: The encoding of text + * @is_multibyte: Whether the encoding is a multibyte encoding + * @error: The location to store the error, or NULL to ignore errors + * @returns: UTF-8 encoded string + * + * Convert text to UTF-8. + */ +static gchar * +convert_to_utf8 (const gchar * text, gint length, guint start, + const gchar * encoding, gboolean is_multibyte, GError ** error) +{ + gchar *new_text; + GByteArray *sb; + gint i; + + g_return_val_if_fail (text != NULL, NULL); + g_return_val_if_fail (encoding != NULL, NULL); + + text += start; + + sb = g_byte_array_sized_new (length * 1.1); + + if (is_multibyte) { + if (length == -1) { + while (*text != '\0') { + guint16 code = GST_READ_UINT16_BE (text); + + switch (code) { + case 0xE086: /* emphasis on */ + case 0xE087: /* emphasis off */ + /* skip it */ + break; + case 0xE08A:{ + guint8 nl[] = { 0x0A, 0x00 }; // new line + g_byte_array_append (sb, nl, 2); + break; + } + default: + g_byte_array_append (sb, (guint8 *) text, 2); + break; + } + + text += 2; + } + } else { + for (i = 0; i < length; i += 2) { + guint16 code = GST_READ_UINT16_BE (text); + + switch (code) { + case 0xE086: /* emphasis on */ + case 0xE087: /* emphasis off */ + /* skip it */ + break; + case 0xE08A:{ + guint8 nl[] = { 0x0A, 0x00 }; // new line + g_byte_array_append (sb, nl, 2); + break; + } + default: + g_byte_array_append (sb, (guint8 *) text, 2); + break; + } + + text += 2; + } + } + } else { + if (length == -1) { + while (*text != '\0') { + guint8 code = (guint8) (*text); + + switch (code) { + case 0x86: /* emphasis on */ + case 0x87: /* emphasis off */ + /* skip it */ + break; + case 0x8A: + g_byte_array_append (sb, (guint8 *) "\n", 1); + break; + default: + g_byte_array_append (sb, &code, 1); + break; + } + + text++; + } + } else { + for (i = 0; i < length; i++) { + guint8 code = (guint8) (*text); + + switch (code) { + case 0x86: /* emphasis on */ + case 0x87: /* emphasis off */ + /* skip it */ + break; + case 0x8A: + g_byte_array_append (sb, (guint8 *) "\n", 1); + break; + default: + g_byte_array_append (sb, &code, 1); + break; + } + + text++; + } + } + } + + if (sb->len > 0) { + new_text = + g_convert ((gchar *) sb->data, sb->len, "utf-8", encoding, NULL, NULL, + error); + } else { + new_text = g_strdup (""); + } + + g_byte_array_free (sb, TRUE); + + return new_text; +} + +static gchar * +get_encoding_and_convert (const gchar * text, guint length) +{ + GError *error = NULL; + gchar *converted_str; + gchar *encoding; + guint start_text = 0; + gboolean is_multibyte; + + g_return_val_if_fail (text != NULL, NULL); + + if (length == 0) + return g_strdup (""); + + encoding = get_encoding (text, &start_text, &is_multibyte); + + if (encoding == NULL) { + GST_WARNING ("Could not detect encoding"); + converted_str = g_strndup (text, length); + } else { + converted_str = convert_to_utf8 (text, length - start_text, start_text, + encoding, is_multibyte, &error); + if (error != NULL) { + GST_WARNING ("Could not convert string, encoding is %s: %s", + encoding, error->message); + g_error_free (error); + error = NULL; + + /* The first part of ISO 6937 is identical to ISO 8859-9, but + * they differ in the second part. Some channels don't + * provide the first byte that indicates ISO 8859-9 encoding. + * If decoding from ISO 6937 failed, we try ISO 8859-9 here. + */ + if (strcmp (encoding, "iso6937") == 0) { + GST_INFO ("Trying encoding ISO 8859-9"); + converted_str = convert_to_utf8 (text, length, 0, + "iso8859-9", FALSE, &error); + if (error != NULL) { + GST_WARNING + ("Could not convert string while assuming encoding ISO 8859-9: %s", + error->message); + g_error_free (error); + goto failed; + } + } else { + goto failed; + } + } + + g_free (encoding); + } + + return converted_str; + +failed: + { + g_free (encoding); + text += start_text; + return g_strndup (text, length - start_text); + } +} diff --git a/gst/mpegtsdemux/mpegtspacketizer.h b/gst/mpegtsdemux/mpegtspacketizer.h new file mode 100644 index 000000000..e0be1e09f --- /dev/null +++ b/gst/mpegtsdemux/mpegtspacketizer.h @@ -0,0 +1,167 @@ +/* + * mpegtspacketizer.h - + * Copyright (C) 2007 Alessandro Decina + * + * Authors: + * Alessandro Decina <alessandro@nnva.org> + * + * 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 GST_MPEGTS_PACKETIZER_H +#define GST_MPEGTS_PACKETIZER_H + +#include <gst/gst.h> +#include <gst/base/gstadapter.h> +#include <glib.h> + +#define MPEGTS_NORMAL_PACKETSIZE 188 +#define MPEGTS_M2TS_PACKETSIZE 192 +#define MPEGTS_DVB_ASI_PACKETSIZE 204 +#define MPEGTS_ATSC_PACKETSIZE 208 + +#define MPEGTS_MIN_PACKETSIZE MPEGTS_NORMAL_PACKETSIZE +#define MPEGTS_MAX_PACKETSIZE MPEGTS_ATSC_PACKETSIZE + +#define MPEGTS_AFC_PCR_FLAG 0x10 +#define MPEGTS_AFC_OPCR_FLAG 0x08 + +G_BEGIN_DECLS + +#define GST_TYPE_MPEGTS_PACKETIZER \ + (mpegts_packetizer_get_type()) +#define GST_MPEGTS_PACKETIZER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MPEGTS_PACKETIZER,MpegTSPacketizer2)) +#define GST_MPEGTS_PACKETIZER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MPEGTS_PACKETIZER,MpegTSPacketizer2Class)) +#define GST_IS_MPEGTS_PACKETIZER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MPEGTS_PACKETIZER)) +#define GST_IS_MPEGTS_PACKETIZER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MPEGTS_PACKETIZER)) + +typedef struct _MpegTSPacketizer2 MpegTSPacketizer2; +typedef struct _MpegTSPacketizer2Class MpegTSPacketizer2Class; + +typedef struct +{ + guint continuity_counter; + GstAdapter *section_adapter; + guint8 section_table_id; + guint section_length; + GSList *subtables; + guint64 offset; +} MpegTSPacketizerStream; + +struct _MpegTSPacketizer2 { + GObject object; + + GstAdapter *adapter; + /* streams hashed by pid */ + MpegTSPacketizerStream **streams; + gboolean disposed; + gboolean know_packet_size; + guint16 packet_size; + GstCaps *caps; + + /* current offset of the tip of the adapter */ + guint64 offset; + gboolean empty; +}; + +struct _MpegTSPacketizer2Class { + GObjectClass object_class; +}; + +typedef struct +{ + GstBuffer *buffer; + gint16 pid; + guint8 payload_unit_start_indicator; + guint8 adaptation_field_control; + guint8 continuity_counter; + guint8 *payload; + + guint8 *data_start; + guint8 *data_end; + guint8 *data; + + guint8 afc_flags; + guint64 pcr; + guint64 opcr; + guint64 offset; +} MpegTSPacketizerPacket; + +typedef struct +{ + gboolean complete; + GstBuffer *buffer; + gint16 pid; + guint8 table_id; + guint16 subtable_extension; + guint section_length; + guint8 version_number; + guint8 current_next_indicator; + guint32 crc; +} MpegTSPacketizerSection; + +typedef struct +{ + guint8 table_id; + /* the spec says sub_table_extension is the fourth and fifth byte of a + * section when the section_syntax_indicator is set to a value of "1". If + * section_syntax_indicator is 0, sub_table_extension will be set to 0 */ + guint16 subtable_extension; + guint8 version_number; + guint32 crc; +} MpegTSPacketizerStreamSubtable; + +typedef enum { + PACKET_BAD = FALSE, + PACKET_OK = TRUE, + PACKET_NEED_MORE +} MpegTSPacketizerPacketReturn; + +GType mpegts_packetizer_get_type(void); + +MpegTSPacketizer2 *mpegts_packetizer_new (void); +void mpegts_packetizer_clear (MpegTSPacketizer2 *packetizer); +void mpegts_packetizer_push (MpegTSPacketizer2 *packetizer, GstBuffer *buffer); +gboolean mpegts_packetizer_has_packets (MpegTSPacketizer2 *packetizer); +MpegTSPacketizerPacketReturn mpegts_packetizer_next_packet (MpegTSPacketizer2 *packetizer, + MpegTSPacketizerPacket *packet); +void mpegts_packetizer_clear_packet (MpegTSPacketizer2 *packetizer, + MpegTSPacketizerPacket *packet); +void mpegts_packetizer_remove_stream(MpegTSPacketizer2 *packetizer, + gint16 pid); + +gboolean mpegts_packetizer_push_section (MpegTSPacketizer2 *packetzer, + MpegTSPacketizerPacket *packet, MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_pat (MpegTSPacketizer2 *packetizer, + MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_pmt (MpegTSPacketizer2 *packetizer, + MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_nit (MpegTSPacketizer2 *packetizer, + MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_sdt (MpegTSPacketizer2 *packetizer, + MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_eit (MpegTSPacketizer2 *packetizer, + MpegTSPacketizerSection *section); +GstStructure *mpegts_packetizer_parse_tdt (MpegTSPacketizer2 *packetizer, + MpegTSPacketizerSection *section); + +G_END_DECLS + +#endif /* GST_MPEGTS_PACKETIZER_H */ diff --git a/gst/mpegtsdemux/mpegtsparse.c b/gst/mpegtsdemux/mpegtsparse.c new file mode 100644 index 000000000..ede74ae82 --- /dev/null +++ b/gst/mpegtsdemux/mpegtsparse.c @@ -0,0 +1,718 @@ +/* + * mpegtsparse.c - + * Copyright (C) 2007 Alessandro Decina + * + * Authors: + * Alessandro Decina <alessandro@nnva.org> + * Zaheer Abbas Merali <zaheerabbas at merali dot org> + * + * 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 <stdlib.h> + +#include "mpegtsbase.h" +#include "mpegtsparse.h" +#include "gstmpegdesc.h" + +/* latency in mseconds */ +#define TS_LATENCY 700 + +#define TABLE_ID_UNSET 0xFF +#define RUNNING_STATUS_RUNNING 4 + +GST_DEBUG_CATEGORY_STATIC (mpegts_parse_debug); +#define GST_CAT_DEFAULT mpegts_parse_debug + +typedef struct _MpegTSParsePad MpegTSParsePad; + +typedef struct +{ + MpegTSBaseProgram program; + gint selected; + gboolean active; + MpegTSParsePad *tspad; +} MpegTSParseProgram; + +struct _MpegTSParsePad +{ + GstPad *pad; + + /* the program number that the peer wants on this pad */ + gint program_number; + MpegTSParseProgram *program; + + /* set to FALSE before a push and TRUE after */ + gboolean pushed; + + /* the return of the latest push */ + GstFlowReturn flow_return; + + GstTagList *tags; + guint event_id; +}; + +static GstStaticPadTemplate src_template = +GST_STATIC_PAD_TEMPLATE ("src%d", GST_PAD_SRC, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ") + ); + +static GstStaticPadTemplate program_template = +GST_STATIC_PAD_TEMPLATE ("program_%d", GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ") + ); + +enum +{ + ARG_0, + PROP_PROGRAM_NUMBERS, + /* FILL ME */ +}; + +static void +mpegts_parse_program_started (MpegTSBase * base, MpegTSBaseProgram * program); +static void +mpegts_parse_program_stopped (MpegTSBase * base, MpegTSBaseProgram * program); + +static GstFlowReturn +mpegts_parse_push (MpegTSBase * base, MpegTSPacketizerPacket * packet, + MpegTSPacketizerSection * section); +static void mpegts_parse_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void mpegts_parse_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void mpegts_parse_finalize (GObject * object); + +static MpegTSParsePad *mpegts_parse_create_tspad (MpegTSParse2 * parse, + const gchar * name); +static void mpegts_parse_destroy_tspad (MpegTSParse2 * parse, + MpegTSParsePad * tspad); +static GstPad *mpegts_parse_activate_program (MpegTSParse2 * parse, + MpegTSParseProgram * program); +static void mpegts_parse_reset_selected_programs (MpegTSParse2 * parse, + gchar * programs); + +static void mpegts_parse_pad_removed (GstElement * element, GstPad * pad); +static GstPad *mpegts_parse_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name); +static void mpegts_parse_release_pad (GstElement * element, GstPad * pad); +static gboolean mpegts_parse_src_pad_query (GstPad * pad, GstQuery * query); +static gboolean push_event (MpegTSBase * base, GstEvent * event); + +GST_BOILERPLATE (MpegTSParse2, mpegts_parse, MpegTSBase, GST_TYPE_MPEGTS_BASE); + +static void +mpegts_parse_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&program_template)); + + gst_element_class_set_details_simple (element_class, + "MPEG transport stream parser", "Codec/Parser", + "Parses MPEG2 transport streams", + "Alessandro Decina <alessandro@nnva.org>, " + "Zaheer Abbas Merali <zaheerabbas at merali dot org>"); +} + +static void +mpegts_parse_class_init (MpegTSParse2Class * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + MpegTSBaseClass *ts_class; + + element_class = GST_ELEMENT_CLASS (klass); + element_class->pad_removed = mpegts_parse_pad_removed; + element_class->request_new_pad = mpegts_parse_request_new_pad; + element_class->release_pad = mpegts_parse_release_pad; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->set_property = mpegts_parse_set_property; + gobject_class->get_property = mpegts_parse_get_property; + gobject_class->finalize = mpegts_parse_finalize; + + g_object_class_install_property (gobject_class, PROP_PROGRAM_NUMBERS, + g_param_spec_string ("program-numbers", + "Program Numbers", + "Colon separated list of programs", "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + ts_class = GST_MPEGTS_BASE_CLASS (klass); + ts_class->push = GST_DEBUG_FUNCPTR (mpegts_parse_push); + ts_class->push_event = GST_DEBUG_FUNCPTR (push_event); + ts_class->program_started = GST_DEBUG_FUNCPTR (mpegts_parse_program_started); + ts_class->program_stopped = GST_DEBUG_FUNCPTR (mpegts_parse_program_stopped); +} + +static void +mpegts_parse_init (MpegTSParse2 * parse, MpegTSParse2Class * klass) +{ + parse->need_sync_program_pads = FALSE; + parse->program_numbers = g_strdup (""); + parse->pads_to_add = NULL; + parse->pads_to_remove = NULL; + GST_MPEGTS_BASE (parse)->program_size = sizeof (MpegTSParseProgram); +} + +static void +mpegts_parse_finalize (GObject * object) +{ + MpegTSParse2 *parse = GST_MPEGTS_PARSE (object); + + g_free (parse->program_numbers); + + if (G_OBJECT_CLASS (parent_class)->finalize) + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +mpegts_parse_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + MpegTSParse2 *parse = GST_MPEGTS_PARSE (object); + + switch (prop_id) { + case PROP_PROGRAM_NUMBERS: + mpegts_parse_reset_selected_programs (parse, g_value_dup_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +mpegts_parse_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + MpegTSParse2 *parse = GST_MPEGTS_PARSE (object); + + switch (prop_id) { + case PROP_PROGRAM_NUMBERS: + g_value_set_string (value, parse->program_numbers); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static GstPad * +mpegts_parse_activate_program (MpegTSParse2 * parse, + MpegTSParseProgram * program) +{ + MpegTSParsePad *tspad; + gchar *pad_name; + + pad_name = + g_strdup_printf ("program_%d", + ((MpegTSBaseProgram *) program)->program_number); + + tspad = mpegts_parse_create_tspad (parse, pad_name); + tspad->program_number = ((MpegTSBaseProgram *) program)->program_number; + tspad->program = program; + program->tspad = tspad; + g_free (pad_name); + gst_pad_set_active (tspad->pad, TRUE); + program->active = TRUE; + + return tspad->pad; +} + +static gboolean +push_event (MpegTSBase * base, GstEvent * event) +{ + MpegTSParse2 *parse = (MpegTSParse2 *) base; + GList *tmp; + + for (tmp = GST_ELEMENT_CAST (parse)->srcpads; tmp; tmp = tmp->next) { + GstPad *pad = (GstPad *) tmp->data; + if (pad) { + gst_event_ref (event); + gst_pad_push_event (pad, event); + } + } + return TRUE; +} + +static GstPad * +mpegts_parse_deactivate_program (MpegTSParse2 * parse, + MpegTSParseProgram * program) +{ + MpegTSParsePad *tspad; + + tspad = program->tspad; + gst_pad_set_active (tspad->pad, FALSE); + program->active = FALSE; + + /* tspad will be destroyed in GstElementClass::pad_removed */ + + return tspad->pad; +} + +static void +mpegts_parse_sync_program_pads (MpegTSParse2 * parse) +{ + GList *walk; + + GST_INFO_OBJECT (parse, "begin sync pads"); + for (walk = parse->pads_to_remove; walk; walk = walk->next) + gst_element_remove_pad (GST_ELEMENT (parse), GST_PAD (walk->data)); + + for (walk = parse->pads_to_add; walk; walk = walk->next) + gst_element_add_pad (GST_ELEMENT (parse), GST_PAD (walk->data)); + + if (parse->pads_to_add) + g_list_free (parse->pads_to_add); + + if (parse->pads_to_remove) + g_list_free (parse->pads_to_remove); + + GST_OBJECT_LOCK (parse); + parse->pads_to_remove = NULL; + parse->pads_to_add = NULL; + parse->need_sync_program_pads = FALSE; + GST_OBJECT_UNLOCK (parse); + + GST_INFO_OBJECT (parse, "end sync pads"); +} + +static void +foreach_program_activate_or_deactivate (gpointer key, gpointer value, + gpointer data) +{ + MpegTSParse2 *parse = GST_MPEGTS_PARSE (data); + MpegTSParseProgram *program = (MpegTSParseProgram *) value; + + /* at this point selected programs have program->selected == 2, + * unselected programs thay may have to be deactivated have selected == 1 and + * unselected inactive programs have selected == 0 */ + + switch (--program->selected) { + case 1: + /* selected */ + if (!program->active + && ((MpegTSBaseProgram *) program)->pmt_pid != G_MAXUINT16) + parse->pads_to_add = + g_list_append (parse->pads_to_add, + mpegts_parse_activate_program (parse, program)); + else { + program->selected = 2; + } + break; + case 0: + /* unselected */ + if (program->active) + parse->pads_to_remove = g_list_append (parse->pads_to_remove, + mpegts_parse_deactivate_program (parse, program)); + break; + case -1: + /* was already unselected */ + program->selected = 0; + break; + default: + g_return_if_reached (); + } +} + +static void +mpegts_parse_reset_selected_programs (MpegTSParse2 * parse, + gchar * program_numbers) +{ + GST_OBJECT_LOCK (parse); + if (parse->program_numbers) + g_free (parse->program_numbers); + + parse->program_numbers = program_numbers; + + if (*parse->program_numbers != '\0') { + gint program_number; + MpegTSParseProgram *program; + gchar **progs, **walk; + + progs = g_strsplit (parse->program_numbers, ":", 0); + + walk = progs; + while (*walk != NULL) { + program_number = strtol (*walk, NULL, 0); + program = + (MpegTSParseProgram *) mpegts_base_get_program ((MpegTSBase *) parse, + program_number); + if (program == NULL) + /* create the program, it will get activated once we get a PMT for it */ + program = (MpegTSParseProgram *) mpegts_base_add_program ((MpegTSBase *) + parse, program_number, G_MAXUINT16); + program->selected = 2; + ++walk; + } + g_strfreev (progs); + } + + g_hash_table_foreach (((MpegTSBase *) parse)->programs, + foreach_program_activate_or_deactivate, parse); + + if (parse->pads_to_remove || parse->pads_to_add) + parse->need_sync_program_pads = TRUE; + GST_OBJECT_UNLOCK (parse); +} + + +static MpegTSParsePad * +mpegts_parse_create_tspad (MpegTSParse2 * parse, const gchar * pad_name) +{ + GstPad *pad; + MpegTSParsePad *tspad; + + pad = gst_pad_new_from_static_template (&program_template, pad_name); + gst_pad_set_query_function (pad, + GST_DEBUG_FUNCPTR (mpegts_parse_src_pad_query)); + + /* create our wrapper */ + tspad = g_new0 (MpegTSParsePad, 1); + tspad->pad = pad; + tspad->program_number = -1; + tspad->program = NULL; + tspad->pushed = FALSE; + tspad->flow_return = GST_FLOW_NOT_LINKED; + gst_pad_set_element_private (pad, tspad); + + return tspad; +} + +static void +mpegts_parse_destroy_tspad (MpegTSParse2 * parse, MpegTSParsePad * tspad) +{ + if (tspad->tags) { + gst_tag_list_free (tspad->tags); + } + + /* free the wrapper */ + g_free (tspad); +} + +static void +mpegts_parse_pad_removed (GstElement * element, GstPad * pad) +{ + MpegTSParsePad *tspad; + MpegTSParse2 *parse = GST_MPEGTS_PARSE (element); + + if (gst_pad_get_direction (pad) == GST_PAD_SINK) + return; + + tspad = (MpegTSParsePad *) gst_pad_get_element_private (pad); + mpegts_parse_destroy_tspad (parse, tspad); + + if (GST_ELEMENT_CLASS (parent_class)->pad_removed) + GST_ELEMENT_CLASS (parent_class)->pad_removed (element, pad); +} + +static GstPad * +mpegts_parse_request_new_pad (GstElement * element, GstPadTemplate * template, + const gchar * unused) +{ + MpegTSParse2 *parse; + gchar *name; + GstPad *pad; + + g_return_val_if_fail (template != NULL, NULL); + g_return_val_if_fail (GST_IS_MPEGTS_PARSE (element), NULL); + + parse = GST_MPEGTS_PARSE (element); + + GST_OBJECT_LOCK (element); + name = g_strdup_printf ("src%d", parse->req_pads++); + GST_OBJECT_UNLOCK (element); + + pad = mpegts_parse_create_tspad (parse, name)->pad; + gst_pad_set_active (pad, TRUE); + gst_element_add_pad (element, pad); + g_free (name); + + return pad; +} + +static void +mpegts_parse_release_pad (GstElement * element, GstPad * pad) +{ + g_return_if_fail (GST_IS_MPEGTS_PARSE (element)); + + gst_pad_set_active (pad, FALSE); + /* we do the cleanup in GstElement::pad-removed */ + gst_element_remove_pad (element, pad); +} + +static GstFlowReturn +mpegts_parse_tspad_push_section (MpegTSParse2 * parse, MpegTSParsePad * tspad, + MpegTSPacketizerSection * section, GstBuffer * buffer) +{ + GstFlowReturn ret = GST_FLOW_NOT_LINKED; + gboolean to_push = TRUE; + + if (tspad->program_number != -1) { + if (tspad->program) { + /* we push all sections to all pads except PMTs which we + * only push to pads meant to receive that program number */ + if (section->table_id == 0x02) { + /* PMT */ + if (section->subtable_extension != tspad->program_number) + to_push = FALSE; + } + } else { + /* there's a program filter on the pad but the PMT for the program has not + * been parsed yet, ignore the pad until we get a PMT */ + to_push = FALSE; + ret = GST_FLOW_OK; + } + } + GST_DEBUG_OBJECT (parse, + "pushing section: %d program number: %d table_id: %d", to_push, + tspad->program_number, section->table_id); + if (to_push) { + ret = gst_pad_push (tspad->pad, buffer); + } else { + gst_buffer_unref (buffer); + if (gst_pad_is_linked (tspad->pad)) + ret = GST_FLOW_OK; + } + + return ret; +} + +static GstFlowReturn +mpegts_parse_tspad_push (MpegTSParse2 * parse, MpegTSParsePad * tspad, + guint16 pid, GstBuffer * buffer) +{ + GstFlowReturn ret = GST_FLOW_NOT_LINKED; + MpegTSBaseStream **pad_pids = NULL; + + if (tspad->program_number != -1) { + if (tspad->program) { + MpegTSBaseProgram *bp = (MpegTSBaseProgram *) tspad->program; + pad_pids = bp->streams; + if (bp->tags) { + gst_element_found_tags_for_pad (GST_ELEMENT_CAST (parse), tspad->pad, + bp->tags); + bp->tags = NULL; + } + } else { + /* there's a program filter on the pad but the PMT for the program has not + * been parsed yet, ignore the pad until we get a PMT */ + gst_buffer_unref (buffer); + ret = GST_FLOW_OK; + goto out; + } + } + + if (pad_pids == NULL || pad_pids[pid]) { + /* push if there's no filter or if the pid is in the filter */ + ret = gst_pad_push (tspad->pad, buffer); + } else { + gst_buffer_unref (buffer); + if (gst_pad_is_linked (tspad->pad)) + ret = GST_FLOW_OK; + } + +out: + return ret; +} + +static void +pad_clear_for_push (GstPad * pad, MpegTSParse2 * parse) +{ + MpegTSParsePad *tspad = (MpegTSParsePad *) gst_pad_get_element_private (pad); + + tspad->flow_return = GST_FLOW_NOT_LINKED; + tspad->pushed = FALSE; +} + +static GstFlowReturn +mpegts_parse_push (MpegTSBase * base, MpegTSPacketizerPacket * packet, + MpegTSPacketizerSection * section) +{ + MpegTSParse2 *parse = (MpegTSParse2 *) base; + guint32 pads_cookie; + gboolean done = FALSE; + GstPad *pad = NULL; + MpegTSParsePad *tspad; + guint16 pid; + GstBuffer *buffer; + GstFlowReturn ret; + GList *srcpads; + + if (G_UNLIKELY (parse->need_sync_program_pads)) + mpegts_parse_sync_program_pads (parse); + + pid = packet->pid; + buffer = gst_buffer_make_metadata_writable (packet->buffer); + /* we have the same caps on all the src pads */ + gst_buffer_set_caps (buffer, base->packetizer->caps); + + GST_OBJECT_LOCK (parse); + /* clear tspad->pushed on pads */ + g_list_foreach (GST_ELEMENT_CAST (parse)->srcpads, + (GFunc) pad_clear_for_push, parse); + if (GST_ELEMENT_CAST (parse)->srcpads) + ret = GST_FLOW_NOT_LINKED; + else + ret = GST_FLOW_OK; + + /* Get cookie and source pads list */ + pads_cookie = GST_ELEMENT_CAST (parse)->pads_cookie; + srcpads = GST_ELEMENT_CAST (parse)->srcpads; + if (G_LIKELY (srcpads)) { + pad = GST_PAD_CAST (srcpads->data); + g_object_ref (pad); + } + GST_OBJECT_UNLOCK (parse); + + while (pad && !done) { + tspad = gst_pad_get_element_private (pad); + + if (G_LIKELY (!tspad->pushed)) { + /* ref the buffer as gst_pad_push takes a ref but we want to reuse the + * same buffer for next pushes */ + gst_buffer_ref (buffer); + if (section) { + tspad->flow_return = + mpegts_parse_tspad_push_section (parse, tspad, section, buffer); + } else { + tspad->flow_return = + mpegts_parse_tspad_push (parse, tspad, pid, buffer); + } + tspad->pushed = TRUE; + + if (G_UNLIKELY (tspad->flow_return != GST_FLOW_OK + && tspad->flow_return != GST_FLOW_NOT_LINKED)) { + /* return the error upstream */ + ret = tspad->flow_return; + done = TRUE; + } + + } + + if (ret == GST_FLOW_NOT_LINKED) + ret = tspad->flow_return; + + g_object_unref (pad); + + if (G_UNLIKELY (!done)) { + GST_OBJECT_LOCK (parse); + if (G_UNLIKELY (pads_cookie != GST_ELEMENT_CAST (parse)->pads_cookie)) { + /* resync */ + GST_DEBUG ("resync"); + pads_cookie = GST_ELEMENT_CAST (parse)->pads_cookie; + srcpads = GST_ELEMENT_CAST (parse)->srcpads; + } else { + GST_DEBUG ("getting next pad"); + /* Get next pad */ + srcpads = g_list_next (srcpads); + } + + if (srcpads) { + pad = GST_PAD_CAST (srcpads->data); + g_object_ref (pad); + } else + done = TRUE; + GST_OBJECT_UNLOCK (parse); + } + } + + gst_buffer_unref (buffer); + packet->buffer = NULL; + + return ret; +} + +static void +mpegts_parse_program_started (MpegTSBase * base, MpegTSBaseProgram * program) +{ + MpegTSParse2 *parse = GST_MPEGTS_PARSE (base); + MpegTSParseProgram *parseprogram = (MpegTSParseProgram *) program; + if (parseprogram->selected == 2) { + parse->pads_to_add = + g_list_append (parse->pads_to_add, + mpegts_parse_activate_program (parse, parseprogram)); + parseprogram->selected = 1; + parse->need_sync_program_pads = TRUE; + } + +} + +static void +mpegts_parse_program_stopped (MpegTSBase * base, MpegTSBaseProgram * program) +{ + MpegTSParse2 *parse = GST_MPEGTS_PARSE (base); + MpegTSParseProgram *parseprogram = (MpegTSParseProgram *) program; + + if (parseprogram->active) { + parse->pads_to_remove = + g_list_append (parse->pads_to_remove, + mpegts_parse_deactivate_program (parse, parseprogram)); + parse->need_sync_program_pads = TRUE; + } +} + +static gboolean +mpegts_parse_src_pad_query (GstPad * pad, GstQuery * query) +{ + MpegTSParse2 *parse = GST_MPEGTS_PARSE (gst_pad_get_parent (pad)); + gboolean res; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + { + if ((res = gst_pad_peer_query (((MpegTSBase *) parse)->sinkpad, query))) { + gboolean is_live; + GstClockTime min_latency, max_latency; + + gst_query_parse_latency (query, &is_live, &min_latency, &max_latency); + if (is_live) { + min_latency += TS_LATENCY * GST_MSECOND; + if (max_latency != GST_CLOCK_TIME_NONE) + max_latency += TS_LATENCY * GST_MSECOND; + } + + gst_query_set_latency (query, is_live, min_latency, max_latency); + } + + break; + } + default: + res = gst_pad_query_default (pad, query); + } + gst_object_unref (parse); + return res; +} + +gboolean +gst_mpegtsparse_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (mpegts_parse_debug, "tsparse", 0, + "MPEG transport stream parser"); + + gst_mpegtsdesc_init_debug (); + + return gst_element_register (plugin, "tsparse", + GST_RANK_NONE, GST_TYPE_MPEGTS_PARSE); +} diff --git a/gst/mpegtsdemux/mpegtsparse.h b/gst/mpegtsdemux/mpegtsparse.h new file mode 100644 index 000000000..ef722dd5e --- /dev/null +++ b/gst/mpegtsdemux/mpegtsparse.h @@ -0,0 +1,71 @@ +/* + * mpegts_parse.h - GStreamer MPEG transport stream parser + * Copyright (C) 2007 Alessandro Decina + * + * Authors: + * Alessandro Decina <alessandro@nnva.org> + * + * 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 GST_MPEG_TS_PARSE_H +#define GST_MPEG_TS_PARSE_H + +#include <gst/gst.h> +#include "mpegtsbase.h" +#include "mpegtspacketizer.h" + +G_BEGIN_DECLS + +#define GST_TYPE_MPEGTS_PARSE \ + (mpegts_parse_get_type()) +#define GST_MPEGTS_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MPEGTS_PARSE,MpegTSParse2)) +#define GST_MPEGTS_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MPEGTS_PARSE,MpegTSParse2Class)) +#define GST_IS_MPEGTS_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MPEGTS_PARSE)) +#define GST_IS_MPEGTS_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MPEGTS_PARSE)) + +typedef struct _MpegTSParse2 MpegTSParse2; +typedef struct _MpegTSParse2Class MpegTSParse2Class; + +struct _MpegTSParse2 { + MpegTSBase parent; + + /* the following vars must be protected with the OBJECT_LOCK as they can be + * accessed from the application thread and the streaming thread */ + gchar *program_numbers; + GList *pads_to_add; + GList *pads_to_remove; + guint req_pads; + + gboolean need_sync_program_pads; +}; + +struct _MpegTSParse2Class { + MpegTSBaseClass parent_class; +}; + +GType mpegts_parse_get_type(void); + +gboolean gst_mpegtsparse_plugin_init (GstPlugin * plugin); + +G_END_DECLS + +#endif /* GST_MPEG_TS_PARSE_H */ diff --git a/gst/mpegtsdemux/tsdemux.c b/gst/mpegtsdemux/tsdemux.c new file mode 100644 index 000000000..97dd3802b --- /dev/null +++ b/gst/mpegtsdemux/tsdemux.c @@ -0,0 +1,1491 @@ +/* + * tsdemux.c + * Copyright (C) 2009 Zaheer Abbas Merali + * 2010 Edward Hervey + * + * Authors: + * Zaheer Abbas Merali <zaheerabbas at merali dot org> + * Edward Hervey <edward.hervey@collabora.co.uk> + * + * 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 <stdlib.h> +#include <string.h> + +#include "mpegtsbase.h" +#include "tsdemux.h" +#include "gstmpegdesc.h" +#include "gstmpegdefs.h" +#include "mpegtspacketizer.h" + +/* latency in mseconds */ +#define TS_LATENCY 700 + +#define TABLE_ID_UNSET 0xFF + +/* Size of the pendingbuffers array. */ +#define TS_MAX_PENDING_BUFFERS 256 + +GST_DEBUG_CATEGORY_STATIC (ts_demux_debug); +#define GST_CAT_DEFAULT ts_demux_debug + +static GQuark QUARK_TSDEMUX; +static GQuark QUARK_PID; +static GQuark QUARK_PCR; +static GQuark QUARK_OPCR; +static GQuark QUARK_PTS; +static GQuark QUARK_DTS; +static GQuark QUARK_OFFSET; + + + +typedef enum +{ + PENDING_PACKET_EMPTY = 0, /* No pending packet/buffer + * Push incoming buffers to the array */ + PENDING_PACKET_HEADER, /* PES header needs to be parsed + * Push incoming buffers to the array */ + PENDING_PACKET_BUFFER, /* Currently filling up output buffer + * Push incoming buffers to the bufferlist */ + PENDING_PACKET_DISCONT /* Discontinuity in incoming packets + * Drop all incoming buffers */ +} PendingPacketState; + +typedef struct _TSDemuxStream TSDemuxStream; + +struct _TSDemuxStream +{ + MpegTSBaseStream stream; + + GstPad *pad; + + /* set to FALSE before a push and TRUE after */ + gboolean pushed; + + /* the return of the latest push */ + GstFlowReturn flow_return; + + /* Output data */ + PendingPacketState state; + /* Pending buffers array. */ + /* These buffers are stored in this array until the PES header (if needed) + * is succesfully parsed. */ + GstBuffer *pendingbuffers[TS_MAX_PENDING_BUFFERS]; + guint8 nbpending; + + /* Current data to be pushed out */ + GstBufferList *current; + GstBufferListIterator *currentit; + GList *currentlist; + + GstClockTime pts; +}; + +#define VIDEO_CAPS \ + GST_STATIC_CAPS (\ + "video/mpeg, " \ + "mpegversion = (int) { 1, 2, 4 }, " \ + "systemstream = (boolean) FALSE; " \ + "video/x-h264;" \ + "video/x-dirac;" \ + "video/x-wmv," \ + "wmvversion = (int) 3, " \ + "format = (fourcc) WVC1" \ + ) + +#define AUDIO_CAPS \ + GST_STATIC_CAPS ( \ + "audio/mpeg, " \ + "mpegversion = (int) { 1, 4 };" \ + "audio/x-lpcm, " \ + "width = (int) { 16, 20, 24 }, " \ + "rate = (int) { 48000, 96000 }, " \ + "channels = (int) [ 1, 8 ], " \ + "dynamic_range = (int) [ 0, 255 ], " \ + "emphasis = (boolean) { FALSE, TRUE }, " \ + "mute = (boolean) { FALSE, TRUE }; " \ + "audio/x-ac3; audio/x-eac3;" \ + "audio/x-dts;" \ + "audio/x-private-ts-lpcm" \ + ) + +/* Can also use the subpicture pads for text subtitles? */ +#define SUBPICTURE_CAPS \ + GST_STATIC_CAPS ("subpicture/x-pgs; video/x-dvd-subpicture") + +static GstStaticPadTemplate video_template = +GST_STATIC_PAD_TEMPLATE ("video_%04x", GST_PAD_SRC, + GST_PAD_SOMETIMES, + VIDEO_CAPS); + +static GstStaticPadTemplate audio_template = +GST_STATIC_PAD_TEMPLATE ("audio_%04x", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + AUDIO_CAPS); + +static GstStaticPadTemplate subpicture_template = +GST_STATIC_PAD_TEMPLATE ("subpicture_%04x", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + SUBPICTURE_CAPS); + +static GstStaticPadTemplate private_template = +GST_STATIC_PAD_TEMPLATE ("private_%04x", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +enum +{ + ARG_0, + PROP_PROGRAM_NUMBER, + PROP_EMIT_STATS, + /* FILL ME */ +}; + +/* Pad functions */ +static const GstQueryType *gst_ts_demux_srcpad_query_types (GstPad * pad); +static gboolean gst_ts_demux_srcpad_query (GstPad * pad, GstQuery * query); + + +/* mpegtsbase methods */ +static void +gst_ts_demux_program_started (MpegTSBase * base, MpegTSBaseProgram * program); +static void +gst_ts_demux_program_stopped (MpegTSBase * base, MpegTSBaseProgram * program); +static GstFlowReturn +gst_ts_demux_push (MpegTSBase * base, MpegTSPacketizerPacket * packet, + MpegTSPacketizerSection * section); +static void +gst_ts_demux_stream_added (MpegTSBase * base, MpegTSBaseStream * stream, + MpegTSBaseProgram * program); +static void +gst_ts_demux_stream_removed (MpegTSBase * base, MpegTSBaseStream * stream); +static GstFlowReturn +find_timestamps (MpegTSBase * base, guint64 initoff, guint64 * offset); +static void gst_ts_demux_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_ts_demux_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_ts_demux_finalize (GObject * object); +static GstFlowReturn +process_pcr (MpegTSBase * base, guint64 initoff, GstClockTime * pcr, + guint numpcr, gboolean isinitial); +static gboolean push_event (MpegTSBase * base, GstEvent * event); +static void _extra_init (GType type); + +GST_BOILERPLATE_FULL (GstTSDemux, gst_ts_demux, MpegTSBase, + GST_TYPE_MPEGTS_BASE, _extra_init); + +static void +_extra_init (GType type) +{ + QUARK_TSDEMUX = g_quark_from_string ("tsdemux"); + QUARK_PID = g_quark_from_string ("pid"); + QUARK_PCR = g_quark_from_string ("pcr"); + QUARK_OPCR = g_quark_from_string ("opcr"); + QUARK_PTS = g_quark_from_string ("pts"); + QUARK_DTS = g_quark_from_string ("dts"); + QUARK_OFFSET = g_quark_from_string ("offset"); +} + +static void +gst_ts_demux_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&video_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&audio_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&subpicture_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&private_template)); + + gst_element_class_set_details_simple (element_class, + "MPEG transport stream demuxer", + "Codec/Demuxer", + "Demuxes MPEG2 transport streams", + "Zaheer Abbas Merali <zaheerabbas at merali dot org>;" + " Edward Hervey <edward.hervey@collabora.co.uk>"); +} + +static void +gst_ts_demux_class_init (GstTSDemuxClass * klass) +{ + GObjectClass *gobject_class; + MpegTSBaseClass *ts_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->set_property = gst_ts_demux_set_property; + gobject_class->get_property = gst_ts_demux_get_property; + gobject_class->finalize = gst_ts_demux_finalize; + + g_object_class_install_property (gobject_class, PROP_PROGRAM_NUMBER, + g_param_spec_int ("program-number", "Program number", + "Program Number to demux for (-1 to ignore)", -1, G_MAXINT, + -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_EMIT_STATS, + g_param_spec_boolean ("emit-stats", "Emit statistics", + "Emit messages for every pcr/opcr/pts/dts", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + + ts_class = GST_MPEGTS_BASE_CLASS (klass); + ts_class->push = GST_DEBUG_FUNCPTR (gst_ts_demux_push); + ts_class->push_event = GST_DEBUG_FUNCPTR (push_event); + ts_class->program_started = GST_DEBUG_FUNCPTR (gst_ts_demux_program_started); + ts_class->program_stopped = GST_DEBUG_FUNCPTR (gst_ts_demux_program_stopped); + ts_class->stream_added = gst_ts_demux_stream_added; + ts_class->stream_removed = gst_ts_demux_stream_removed; + ts_class->find_timestamps = GST_DEBUG_FUNCPTR (find_timestamps); +} + +static void +gst_ts_demux_init (GstTSDemux * demux, GstTSDemuxClass * klass) +{ + demux->need_newsegment = TRUE; + demux->program_number = -1; + demux->duration = GST_CLOCK_TIME_NONE; + GST_MPEGTS_BASE (demux)->stream_size = sizeof (TSDemuxStream); +} + +static void +gst_ts_demux_finalize (GObject * object) +{ + if (G_OBJECT_CLASS (parent_class)->finalize) + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + + +static void +gst_ts_demux_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstTSDemux *demux = GST_TS_DEMUX (object); + + switch (prop_id) { + case PROP_PROGRAM_NUMBER: + /* FIXME: do something if program is switched as opposed to set at + * beginning */ + demux->program_number = g_value_get_int (value); + break; + case PROP_EMIT_STATS: + demux->emit_statistics = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_ts_demux_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstTSDemux *demux = GST_TS_DEMUX (object); + + switch (prop_id) { + case PROP_PROGRAM_NUMBER: + g_value_set_int (value, demux->program_number); + break; + case PROP_EMIT_STATS: + g_value_set_boolean (value, demux->emit_statistics); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static const GstQueryType * +gst_ts_demux_srcpad_query_types (GstPad * pad) +{ + static const GstQueryType query_types[] = { + GST_QUERY_DURATION, + 0 + }; + + return query_types; +} + +static gboolean +gst_ts_demux_srcpad_query (GstPad * pad, GstQuery * query) +{ + gboolean res = TRUE; + GstTSDemux *demux; + + demux = GST_TS_DEMUX (gst_pad_get_parent (pad)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration (query, &format, NULL); + /* can only get position in time */ + if (format != GST_FORMAT_TIME) + goto wrong_format; + + gst_query_set_duration (query, GST_FORMAT_TIME, demux->duration); + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + +done: + gst_object_unref (demux); + return res; + +wrong_format: + { + GST_DEBUG_OBJECT (demux, "only query duration on TIME is supported"); + res = FALSE; + goto done; + } +} + + +static gboolean +push_event (MpegTSBase * base, GstEvent * event) +{ + GstTSDemux *demux = (GstTSDemux *) base; + guint i; + + if (G_UNLIKELY (demux->program == NULL)) + return FALSE; + + for (i = 0; i < 0x2000; i++) { + if (demux->program->streams[i]) { + if (((TSDemuxStream *) demux->program->streams[i])->pad) { + gst_event_ref (event); + gst_pad_push_event (((TSDemuxStream *) demux->program->streams[i])->pad, + event); + } + } + } + + return TRUE; +} + +static GstFlowReturn +tsdemux_combine_flows (GstTSDemux * demux, TSDemuxStream * stream, + GstFlowReturn ret) +{ + guint i; + + /* Store the value */ + stream->flow_return = ret; + + /* any other error that is not-linked can be returned right away */ + if (ret != GST_FLOW_NOT_LINKED) + goto done; + + /* Only return NOT_LINKED if all other pads returned NOT_LINKED */ + for (i = 0; i < 0x2000; i++) { + if (demux->program->streams[i]) { + stream = (TSDemuxStream *) demux->program->streams[i]; + if (stream->pad) { + ret = stream->flow_return; + /* some other return value (must be SUCCESS but we can return + * other values as well) */ + if (ret != GST_FLOW_NOT_LINKED) + goto done; + } + } + /* if we get here, all other pads were unlinked and we return + * NOT_LINKED then */ + } + +done: + return ret; +} + +static GstPad * +create_pad_for_stream (GstTSDemux * demux, MpegTSBaseStream * bstream, + MpegTSBaseProgram * program) +{ + TSDemuxStream *stream = (TSDemuxStream *) bstream; + gchar *name = NULL; + GstCaps *caps = NULL; + GstPadTemplate *template = NULL; + guint8 *desc = NULL; + GstPad *pad = NULL; + + + GST_LOG ("Attempting to create pad for stream 0x%04x with stream_type %d", + bstream->pid, bstream->stream_type); + + switch (bstream->stream_type) { + case ST_VIDEO_MPEG1: + case ST_VIDEO_MPEG2: + GST_LOG ("mpeg video"); + template = gst_static_pad_template_get (&video_template); + name = g_strdup_printf ("video_%04x", bstream->pid); + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, + bstream->stream_type == ST_VIDEO_MPEG1 ? 1 : 2, "systemstream", + G_TYPE_BOOLEAN, FALSE, NULL); + + break; + case ST_AUDIO_MPEG1: + case ST_AUDIO_MPEG2: + GST_LOG ("mpeg audio"); + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = + gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 1, + NULL); + break; + case ST_PRIVATE_DATA: + GST_LOG ("private data"); + desc = + mpegts_get_descriptor_from_stream ((MpegTSBaseStream *) stream, + DESC_DVB_AC3); + if (desc) { + GST_LOG ("ac3 audio"); + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/x-ac3", NULL); + g_free (desc); + break; + } + desc = + mpegts_get_descriptor_from_stream ((MpegTSBaseStream *) stream, + DESC_DVB_ENHANCED_AC3); + if (desc) { + GST_LOG ("ac3 audio"); + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/x-eac3", NULL); + g_free (desc); + break; + } + desc = + mpegts_get_descriptor_from_stream ((MpegTSBaseStream *) stream, + DESC_DVB_TELETEXT); + if (desc) { + GST_LOG ("teletext"); + template = gst_static_pad_template_get (&private_template); + name = g_strdup_printf ("private_%04x", bstream->pid); + caps = gst_caps_new_simple ("private/teletext", NULL); + g_free (desc); + break; + } + desc = + mpegts_get_descriptor_from_stream ((MpegTSBaseStream *) stream, + DESC_DVB_SUBTITLING); + if (desc) { + GST_LOG ("subtitling"); + template = gst_static_pad_template_get (&private_template); + name = g_strdup_printf ("private_%04x", bstream->pid); + caps = gst_caps_new_simple ("subpicture/x-dvb", NULL); + g_free (desc); + } + /* hack for itv hd (sid 10510, video pid 3401 */ + if (program->program_number == 10510 && bstream->pid == 3401) { + template = gst_static_pad_template_get (&video_template); + name = g_strdup_printf ("video_%04x", bstream->pid); + caps = gst_caps_new_simple ("video/x-h264", NULL); + } + break; + case ST_HDV_AUX_V: + /* We don't expose those streams since they're only helper streams */ + /* template = gst_static_pad_template_get (&private_template); */ + /* name = g_strdup_printf ("private_%04x", bstream->pid); */ + /* caps = gst_caps_new_simple ("hdv/aux-v", NULL); */ + break; + case ST_HDV_AUX_A: + /* We don't expose those streams since they're only helper streams */ + /* template = gst_static_pad_template_get (&private_template); */ + /* name = g_strdup_printf ("private_%04x", bstream->pid); */ + /* caps = gst_caps_new_simple ("hdv/aux-a", NULL); */ + break; + case ST_PRIVATE_SECTIONS: + case ST_MHEG: + case ST_DSMCC: + break; + case ST_AUDIO_AAC: + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 4, NULL); + break; + case ST_VIDEO_MPEG4: + template = gst_static_pad_template_get (&video_template); + name = g_strdup_printf ("video_%04x", bstream->pid); + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, 4, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case ST_VIDEO_H264: + template = gst_static_pad_template_get (&video_template); + name = g_strdup_printf ("video_%04x", bstream->pid); + caps = gst_caps_new_simple ("video/x-h264", NULL); + break; + case ST_VIDEO_DIRAC: + desc = + mpegts_get_descriptor_from_stream ((MpegTSBaseStream *) stream, + DESC_REGISTRATION); + if (desc) { + if (DESC_LENGTH (desc) >= 4) { + if (DESC_REGISTRATION_format_identifier (desc) == 0x64726163) { + GST_LOG ("dirac"); + /* dirac in hex */ + template = gst_static_pad_template_get (&video_template); + name = g_strdup_printf ("video_%04x", bstream->pid); + caps = gst_caps_new_simple ("video/x-dirac", NULL); + } + } + g_free (desc); + } + break; + case ST_PRIVATE_EA: /* Try to detect a VC1 stream */ + { + desc = + mpegts_get_descriptor_from_stream ((MpegTSBaseStream *) stream, + DESC_REGISTRATION); + if (desc) { + if (DESC_LENGTH (desc) >= 4) { + if (DESC_REGISTRATION_format_identifier (desc) == DRF_ID_VC1) { + GST_WARNING ("0xea private stream type found but no descriptor " + "for VC1. Assuming plain VC1."); + template = gst_static_pad_template_get (&video_template); + name = g_strdup_printf ("video_%04x", bstream->pid); + caps = gst_caps_new_simple ("video/x-wmv", + "wmvversion", G_TYPE_INT, 3, + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('W', 'V', 'C', '1'), + NULL); + } + } + g_free (desc); + } + break; + } + case ST_BD_AUDIO_AC3: + { + /* REGISTRATION DRF_ID_HDMV */ + desc = mpegts_get_descriptor_from_program (program, DESC_REGISTRATION); + if (desc) { + if (DESC_REGISTRATION_format_identifier (desc) == DRF_ID_HDMV) { + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/x-eac3", NULL); + } + g_free (desc); + } + if (template) + break; + + /* DVB_ENHANCED_AC3 */ + desc = + mpegts_get_descriptor_from_stream ((MpegTSBaseStream *) stream, + DESC_DVB_ENHANCED_AC3); + if (desc) { + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/x-eac3", NULL); + g_free (desc); + break; + } + + /* DVB_AC3 */ + desc = + mpegts_get_descriptor_from_stream ((MpegTSBaseStream *) stream, + DESC_DVB_AC3); + if (!desc) + GST_WARNING ("AC3 stream type found but no corresponding " + "descriptor to differentiate between AC3 and EAC3. " + "Assuming plain AC3."); + else + g_free (desc); + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/x-ac3", NULL); + break; + } + case ST_BD_AUDIO_EAC3: + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/x-eac3", NULL); + break; + case ST_PS_AUDIO_DTS: + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/x-dts", NULL); + break; + case ST_PS_AUDIO_LPCM: + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/x-lpcm", NULL); + break; + case ST_BD_AUDIO_LPCM: + template = gst_static_pad_template_get (&audio_template); + name = g_strdup_printf ("audio_%04x", bstream->pid); + caps = gst_caps_new_simple ("audio/x-private-ts-lpcm", NULL); + break; + case ST_PS_DVD_SUBPICTURE: + template = gst_static_pad_template_get (&subpicture_template); + name = g_strdup_printf ("subpicture_%04x", bstream->pid); + caps = gst_caps_new_simple ("video/x-dvd-subpicture", NULL); + break; + case ST_BD_PGS_SUBPICTURE: + template = gst_static_pad_template_get (&subpicture_template); + name = g_strdup_printf ("subpicture_%04x", bstream->pid); + caps = gst_caps_new_simple ("subpicture/x-pgs", NULL); + break; + } + if (template && name && caps) { + GST_LOG ("stream:%p creating pad with name %s and caps %s", stream, name, + gst_caps_to_string (caps)); + pad = gst_pad_new_from_template (template, name); + gst_pad_use_fixed_caps (pad); + gst_pad_set_caps (pad, caps); + gst_pad_set_query_type_function (pad, gst_ts_demux_srcpad_query_types); + gst_pad_set_query_function (pad, gst_ts_demux_srcpad_query); + gst_caps_unref (caps); + } + + g_free (name); + + return pad; +} + +static void +gst_ts_demux_stream_added (MpegTSBase * base, MpegTSBaseStream * bstream, + MpegTSBaseProgram * program) +{ + GstTSDemux *tsdemux = (GstTSDemux *) base; + TSDemuxStream *stream = (TSDemuxStream *) bstream; + + if (!stream->pad) { + /* Create the pad */ + if (bstream->stream_type != 0xff) + stream->pad = create_pad_for_stream (tsdemux, bstream, program); + stream->pts = GST_CLOCK_TIME_NONE; + } + stream->flow_return = GST_FLOW_OK; +} + +static void +gst_ts_demux_stream_removed (MpegTSBase * base, MpegTSBaseStream * bstream) +{ + TSDemuxStream *stream = (TSDemuxStream *) bstream; + if (stream) { + if (stream->pad) { + /* Unref the pad, clear it */ + gst_object_unref (stream->pad); + stream->pad = NULL; + } + stream->flow_return = GST_FLOW_NOT_LINKED; + } +} + +static void +activate_pad_for_stream (GstTSDemux * tsdemux, TSDemuxStream * stream) +{ + if (stream->pad) { + GST_DEBUG_OBJECT (tsdemux, "Activating pad %s:%s for stream %p", + GST_DEBUG_PAD_NAME (stream->pad), stream); + gst_pad_set_active (stream->pad, TRUE); + gst_element_add_pad ((GstElement *) tsdemux, stream->pad); + GST_DEBUG_OBJECT (stream->pad, "done adding pad"); + } else + GST_WARNING_OBJECT (tsdemux, "stream %p has no pad", stream); +} + +static void +gst_ts_demux_program_started (MpegTSBase * base, MpegTSBaseProgram * program) +{ + GstTSDemux *demux = GST_TS_DEMUX (base); + + if (demux->program_number == -1 || + demux->program_number == program->program_number) { + guint i; + + GST_LOG ("program %d started", program->program_number); + demux->program_number = program->program_number; + demux->program = program; + + /* Activate all stream pads, the pads will already have been created */ + + /* FIXME : Actually, we don't want to activate *ALL* streams ! + * For example, we don't want to expose HDV AUX private streams, we will just + * be using them directly for seeking and metadata. */ + if (base->mode != BASE_MODE_SCANNING) + for (i = 0; i < 0x2000; i++) + if (program->streams[i]) + activate_pad_for_stream (demux, + (TSDemuxStream *) program->streams[i]); + + /* Inform scanner we have got our program */ + demux->current_program_number = program->program_number; + } +} + +static void +gst_ts_demux_program_stopped (MpegTSBase * base, MpegTSBaseProgram * program) +{ + guint i; + GstTSDemux *demux = GST_TS_DEMUX (base); + TSDemuxStream *localstream = NULL; + + GST_LOG ("program %d stopped", program->program_number); + + if (program != demux->program) + return; + + for (i = 0; i < 0x2000; i++) { + if (demux->program->streams[i]) { + localstream = (TSDemuxStream *) program->streams[i]; + if (localstream->pad) { + GST_DEBUG ("HAVE PAD %s:%s", GST_DEBUG_PAD_NAME (localstream->pad)); + if (gst_pad_is_active (localstream->pad)) + gst_element_remove_pad (GST_ELEMENT_CAST (demux), localstream->pad); + else + gst_object_unref (localstream->pad); + localstream->pad = NULL; + } + } + } + demux->program = NULL; + demux->program_number = -1; +} + +static gboolean +process_section (MpegTSBase * base) +{ + GstTSDemux *demux = GST_TS_DEMUX (base); + gboolean based; + gboolean done = FALSE; + MpegTSPacketizerPacket packet; + MpegTSPacketizerPacketReturn pret; + + while ((!done) + && ((pret = + mpegts_packetizer_next_packet (base->packetizer, + &packet)) != PACKET_NEED_MORE)) { + if (G_UNLIKELY (pret == PACKET_BAD)) + /* bad header, skip the packet */ + goto next; + + /* base PSI data */ + if (packet.payload != NULL && mpegts_base_is_psi (base, &packet)) { + MpegTSPacketizerSection section; + + based = + mpegts_packetizer_push_section (base->packetizer, &packet, §ion); + if (G_UNLIKELY (!based)) + /* bad section data */ + goto next; + + if (G_LIKELY (section.complete)) { + /* section complete */ + GST_DEBUG ("Section Complete"); + based = mpegts_base_handle_psi (base, §ion); + gst_buffer_unref (section.buffer); + if (G_UNLIKELY (!based)) + /* bad PSI table */ + goto next; + + } + + if (demux->program != NULL) { + GST_DEBUG ("Got Program"); + done = TRUE; + } + } + next: + mpegts_packetizer_clear_packet (base->packetizer, &packet); + } + return done; +} + + +static GstFlowReturn +find_timestamps (MpegTSBase * base, guint64 initoff, guint64 * offset) +{ + + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *buf; + gboolean done = FALSE; + GstFormat format = GST_FORMAT_BYTES; + gint64 total_bytes; + guint64 scan_offset; + guint i = 0; + GstClockTime initial, final; + GstTSDemux *demux = GST_TS_DEMUX (base); + + GST_DEBUG ("Scanning for timestamps"); + + /* Flush what remained from before */ + mpegts_packetizer_clear (base->packetizer); + + /* Start scanning from know PAT offset */ + while (!done) { + ret = + gst_pad_pull_range (base->sinkpad, i * 50 * MPEGTS_MAX_PACKETSIZE, + 50 * MPEGTS_MAX_PACKETSIZE, &buf); + if (ret != GST_FLOW_OK) + goto beach; + mpegts_packetizer_push (base->packetizer, buf); + done = process_section (base); + i++; + } + mpegts_packetizer_clear (base->packetizer); + done = FALSE; + i = 1; + + + *offset = base->seek_offset; + + /* Search for the first PCRs */ + ret = process_pcr (base, base->first_pat_offset, &initial, 10, TRUE); + mpegts_packetizer_clear (base->packetizer); + /* Remove current program so we ensure looking for a PAT when scanning the + * for the final PCR */ + mpegts_base_remove_program (base, demux->current_program_number); + + if (ret != GST_FLOW_OK) { + GST_WARNING ("Problem getting initial PCRs"); + goto beach; + } + + /* Find end position */ + if (G_UNLIKELY (!gst_pad_query_peer_duration (base->sinkpad, &format, + &total_bytes) || format != GST_FORMAT_BYTES)) { + GST_WARNING_OBJECT (base, "Couldn't get upstream size in bytes"); + ret = GST_FLOW_ERROR; + mpegts_packetizer_clear (base->packetizer); + return ret; + } + GST_DEBUG ("Upstream is %" G_GINT64_FORMAT " bytes", total_bytes); + + scan_offset = total_bytes - 4000 * MPEGTS_MAX_PACKETSIZE; + + GST_DEBUG ("Scanning for last sync point between:%" G_GINT64_FORMAT + " and the end:%" G_GINT64_FORMAT, scan_offset, total_bytes); + while ((!done) && (scan_offset < total_bytes)) { + ret = + gst_pad_pull_range (base->sinkpad, + scan_offset, 50 * MPEGTS_MAX_PACKETSIZE, &buf); + if (ret != GST_FLOW_OK) + goto beach; + + mpegts_packetizer_push (base->packetizer, buf); + done = process_section (base); + scan_offset += 50 * MPEGTS_MAX_PACKETSIZE; + } + + mpegts_packetizer_clear (base->packetizer); + + GST_DEBUG ("Searching PCR"); + ret = + process_pcr (base, total_bytes - 4000 * MPEGTS_MAX_PACKETSIZE, &final, 10, + FALSE); + + if (ret != GST_FLOW_OK) { + GST_DEBUG ("Problem getting last PCRs"); + goto beach; + } + + demux->duration = final - initial; + + GST_DEBUG ("Done, duration:%" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->duration)); + +beach: + + mpegts_packetizer_clear (base->packetizer); + /* Remove current program */ + mpegts_base_remove_program (base, demux->current_program_number); + + return ret; +} + +static GstFlowReturn +process_pcr (MpegTSBase * base, guint64 initoff, GstClockTime * pcr, + guint numpcr, gboolean isinitial) +{ + GstTSDemux *demux = GST_TS_DEMUX (base); + GstFlowReturn ret = GST_FLOW_OK; + MpegTSBaseProgram *program; + GstBuffer *buf; + guint nbpcr, i = 0; + guint32 pcrmask, pcrpattern; + guint64 pcrs[50]; + guint64 pcroffs[50]; + GstByteReader br; + + GST_DEBUG ("initoff:%" G_GUINT64_FORMAT ", numpcr:%d, isinitial:%d", + initoff, numpcr, isinitial); + + /* Get the program */ + program = demux->program; + if (G_UNLIKELY (program == NULL)) + return GST_FLOW_ERROR; + + /* First find the first X PCR */ + nbpcr = 0; + /* Mask/pattern is PID:PCR_PID, AFC&0x02 */ + /* sync_byte (0x47) : 8bits => 0xff + * transport_error_indicator : 1bit ACTIVATE + * payload_unit_start_indicator : 1bit IGNORE + * transport_priority : 1bit IGNORE + * PID : 13bit => 0x9f 0xff + * transport_scrambling_control : 2bit + * adaptation_field_control : 2bit + * continuity_counter : 4bit => 0x30 + */ + pcrmask = 0xff9fff20; + pcrpattern = 0x47000020 | ((program->pcr_pid & 0x1fff) << 8); + + for (i = 0; (i < 20) && (nbpcr < numpcr); i++) { + guint offset, size; + + ret = + gst_pad_pull_range (base->sinkpad, + initoff + i * 500 * base->packetsize, 500 * base->packetsize, &buf); + + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto beach; + + gst_byte_reader_init_from_buffer (&br, buf); + + offset = 0; + size = GST_BUFFER_SIZE (buf); + + /* FIXME : We should jump to next packet instead of scanning everything */ + while ((size >= br.size) && (nbpcr < numpcr) + && (offset = + gst_byte_reader_masked_scan_uint32 (&br, pcrmask, pcrpattern, + offset, size)) != -1) { + /* Potential PCR */ +/* GST_DEBUG ("offset %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buf) + offset); + GST_MEMDUMP ("something", GST_BUFFER_DATA (buf) + offset, 16);*/ + if ((*(br.data + offset + 5)) & 0x10) { + guint16 pcr2; + guint64 pcr, pcr_ext; + + pcr = ((guint64) GST_READ_UINT32_BE (br.data + offset + 6)) << 1; + pcr2 = GST_READ_UINT16_BE (br.data + offset + 10); + pcr |= (pcr2 & 0x8000) >> 15; + pcr_ext = (pcr2 & 0x01ff); + pcr = pcr * 300 + pcr_ext % 300; + + GST_DEBUG ("Found PCR %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT + " at offset %" G_GUINT64_FORMAT, pcr, + GST_TIME_ARGS (PCRTIME_TO_GSTTIME (pcr)), + GST_BUFFER_OFFSET (buf) + offset); + pcrs[nbpcr] = pcr; + pcroffs[nbpcr] = GST_BUFFER_OFFSET (buf) + offset; + /* Safeguard against bogus PCR (by detecting if it's the same as the + * previous one or wheter the difference with the previous one is + * greater than 10mins */ + if (nbpcr > 1) { + if (pcrs[nbpcr] == pcrs[nbpcr - 1]) { + GST_WARNING ("Found same PCR at different offset"); + } else if ((pcrs[nbpcr] - pcrs[nbpcr - 1]) > + (guint64) 10 * 60 * 27000000) { + GST_WARNING ("PCR differs with previous PCR by more than 10 mins"); + } else + nbpcr += 1; + } else + nbpcr += 1; + } + /* Move offset forward by 1 */ + size -= offset + 1; + offset += 1; + + } + } + +beach: + GST_DEBUG ("Found %d PCR", nbpcr); + if (nbpcr) { + if (isinitial) + *pcr = PCRTIME_TO_GSTTIME (pcrs[0]); + else + *pcr = PCRTIME_TO_GSTTIME (pcrs[nbpcr - 1]); + GST_DEBUG ("pcrdiff:%" GST_TIME_FORMAT " offsetdiff %" G_GUINT64_FORMAT, + GST_TIME_ARGS (PCRTIME_TO_GSTTIME (pcrs[nbpcr - 1] - pcrs[0])), + pcroffs[nbpcr - 1] - pcroffs[0]); + GST_DEBUG ("Estimated bitrate %" G_GUINT64_FORMAT, + gst_util_uint64_scale (GST_SECOND, pcroffs[nbpcr - 1] - pcroffs[0], + PCRTIME_TO_GSTTIME (pcrs[nbpcr - 1] - pcrs[0]))); + GST_DEBUG ("Average PCR interval %" G_GUINT64_FORMAT, + (pcroffs[nbpcr - 1] - pcroffs[0]) / nbpcr); + } + /* Swallow any errors if it happened during the end scanning */ + if (!isinitial) + ret = GST_FLOW_OK; + return ret; +} + + + + +static inline void +gst_ts_demux_record_pcr (GstTSDemux * demux, TSDemuxStream * stream, + guint64 pcr, guint64 offset) +{ + MpegTSBaseStream *bs = (MpegTSBaseStream *) stream; + + GST_LOG ("pid 0x%04x pcr:%" GST_TIME_FORMAT " at offset %" + G_GUINT64_FORMAT, bs->pid, + GST_TIME_ARGS (PCRTIME_TO_GSTTIME (pcr)), offset); + + if (G_UNLIKELY (demux->emit_statistics)) { + GstStructure *st; + st = gst_structure_id_empty_new (QUARK_TSDEMUX); + gst_structure_id_set (st, + QUARK_PID, G_TYPE_UINT, bs->pid, + QUARK_OFFSET, G_TYPE_UINT64, offset, QUARK_PCR, G_TYPE_UINT64, pcr, + NULL); + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_element (GST_OBJECT (demux), st)); + } +} + +static inline void +gst_ts_demux_record_opcr (GstTSDemux * demux, TSDemuxStream * stream, + guint64 opcr, guint64 offset) +{ + MpegTSBaseStream *bs = (MpegTSBaseStream *) stream; + + GST_LOG ("pid 0x%04x opcr:%" GST_TIME_FORMAT " at offset %" + G_GUINT64_FORMAT, bs->pid, + GST_TIME_ARGS (PCRTIME_TO_GSTTIME (opcr)), offset); + + if (G_UNLIKELY (demux->emit_statistics)) { + GstStructure *st; + st = gst_structure_id_empty_new (QUARK_TSDEMUX); + gst_structure_id_set (st, + QUARK_PID, G_TYPE_UINT, bs->pid, + QUARK_OFFSET, G_TYPE_UINT64, offset, + QUARK_OPCR, G_TYPE_UINT64, opcr, NULL); + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_element (GST_OBJECT (demux), st)); + } +} + +static inline void +gst_ts_demux_record_pts (GstTSDemux * demux, TSDemuxStream * stream, + guint64 pts, guint64 offset) +{ + MpegTSBaseStream *bs = (MpegTSBaseStream *) stream; + + GST_LOG ("pid 0x%04x pts:%" GST_TIME_FORMAT " at offset %" + G_GUINT64_FORMAT, bs->pid, + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (pts)), offset); + + if (G_UNLIKELY (demux->emit_statistics)) { + GstStructure *st; + st = gst_structure_id_empty_new (QUARK_TSDEMUX); + gst_structure_id_set (st, + QUARK_PID, G_TYPE_UINT, bs->pid, + QUARK_OFFSET, G_TYPE_UINT64, offset, QUARK_PTS, G_TYPE_UINT64, pts, + NULL); + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_element (GST_OBJECT (demux), st)); + } +} + +static inline void +gst_ts_demux_record_dts (GstTSDemux * demux, TSDemuxStream * stream, + guint64 dts, guint64 offset) +{ + MpegTSBaseStream *bs = (MpegTSBaseStream *) stream; + + GST_LOG ("pid 0x%04x dts:%" GST_TIME_FORMAT " at offset %" + G_GUINT64_FORMAT, bs->pid, + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (dts)), offset); + + if (G_UNLIKELY (demux->emit_statistics)) { + GstStructure *st; + st = gst_structure_id_empty_new (QUARK_TSDEMUX); + gst_structure_id_set (st, + QUARK_PID, G_TYPE_UINT, bs->pid, + QUARK_OFFSET, G_TYPE_UINT64, offset, QUARK_DTS, G_TYPE_UINT64, dts, + NULL); + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_element (GST_OBJECT (demux), st)); + } +} + +static GstFlowReturn +gst_ts_demux_parse_pes_header (GstTSDemux * demux, TSDemuxStream * stream) +{ + GstFlowReturn res = GST_FLOW_OK; + guint8 *data; + guint32 length; + guint32 psc_stid; + guint8 stid; + guint16 pesplength; + guint8 PES_header_data_length = 0; + + data = GST_BUFFER_DATA (stream->pendingbuffers[0]); + length = GST_BUFFER_SIZE (stream->pendingbuffers[0]); + + GST_MEMDUMP ("Header buffer", data, MIN (length, 32)); + + /* packet_start_code_prefix 24 + * stream_id 8*/ + psc_stid = GST_READ_UINT32_BE (data); + data += 4; + length -= 4; + if (G_UNLIKELY ((psc_stid & 0xffffff00) != 0x00000100)) { + GST_WARNING ("WRONG PACKET START CODE !"); + goto discont; + } + stid = psc_stid & 0x000000ff; + GST_LOG ("stream_id:0x%02x", stid); + + /* PES_packet_length 16 */ + /* FIXME : store the expected pes length somewhere ? */ + pesplength = GST_READ_UINT16_BE (data); + data += 2; + length -= 2; + GST_LOG ("PES_packet_length:%d", pesplength); + + /* FIXME : Only parse header on streams which require it (see table 2-21) */ + if (stid != 0xbf) { + guint8 p1, p2; + guint64 pts, dts; + p1 = *data++; + p2 = *data++; + PES_header_data_length = *data++ + 3; + length -= 3; + + GST_LOG ("0x%02x 0x%02x 0x%02x", p1, p2, PES_header_data_length); + GST_LOG ("PES header data length:%d", PES_header_data_length); + + /* '10' 2 + * PES_scrambling_control 2 + * PES_priority 1 + * data_alignment_indicator 1 + * copyright 1 + * original_or_copy 1 */ + if (G_UNLIKELY ((p1 & 0xc0) != 0x80)) { + GST_WARNING ("p1 >> 6 != 0x2"); + goto discont; + } + + /* 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*/ + + /* PES_header_data_length 8 */ + if (G_UNLIKELY (length < PES_header_data_length)) { + GST_WARNING ("length < PES_header_data_length"); + goto discont; + } + + /* PTS 32 */ + if ((p2 & 0x80)) { /* PTS */ + READ_TS (data, pts, discont); + gst_ts_demux_record_pts (demux, stream, pts, + GST_BUFFER_OFFSET (stream->pendingbuffers[0])); + length -= 4; + GST_BUFFER_TIMESTAMP (stream->pendingbuffers[0]) = + MPEGTIME_TO_GSTTIME (pts); + + if (!GST_CLOCK_TIME_IS_VALID (stream->pts)) { + stream->pts = GST_BUFFER_TIMESTAMP (stream->pendingbuffers[0]); + } + + } + /* DTS 32 */ + if ((p2 & 0x40)) { /* DTS */ + READ_TS (data, dts, discont); + gst_ts_demux_record_dts (demux, stream, dts, + GST_BUFFER_OFFSET (stream->pendingbuffers[0])); + length -= 4; + } + /* ESCR 48 */ + if ((p2 & 0x20)) { + GST_LOG ("ESCR present"); + data += 6; + length -= 6; + } + /* ES_rate 24 */ + if ((p2 & 0x10)) { + GST_LOG ("ES_rate present"); + data += 3; + length -= 3; + } + /* DSM_trick_mode 8 */ + if ((p2 & 0x08)) { + GST_LOG ("DSM_trick_mode present"); + data += 1; + length -= 1; + } + } + + /* Remove PES headers */ + GST_BUFFER_DATA (stream->pendingbuffers[0]) += 6 + PES_header_data_length; + GST_BUFFER_SIZE (stream->pendingbuffers[0]) -= 6 + PES_header_data_length; + + /* FIXME : responsible for switching to PENDING_PACKET_BUFFER and + * creating the bufferlist */ + if (1) { + /* Append to the buffer list */ + if (G_UNLIKELY (stream->current == NULL)) { + guint8 i; + + /* Create a new bufferlist */ + stream->current = gst_buffer_list_new (); + stream->currentit = gst_buffer_list_iterate (stream->current); + stream->currentlist = NULL; + gst_buffer_list_iterator_add_group (stream->currentit); + + /* Push pending buffers into the list */ + for (i = stream->nbpending; i; i--) + stream->currentlist = + g_list_prepend (stream->currentlist, stream->pendingbuffers[i - 1]); + memset (stream->pendingbuffers, 0, TS_MAX_PENDING_BUFFERS); + stream->nbpending = 0; + } + stream->state = PENDING_PACKET_BUFFER; + } + + return res; + +discont: + stream->state = PENDING_PACKET_DISCONT; + return res; +} + + /* ONLY CALL THIS: + * * WITH packet->payload != NULL + * * WITH pending/current flushed out if beginning of new PES packet + */ +static inline void +gst_ts_demux_queue_data (GstTSDemux * demux, TSDemuxStream * stream, + MpegTSPacketizerPacket * packet) +{ + GstBuffer *buf; + + GST_DEBUG ("state:%d", stream->state); + + buf = packet->buffer; + /* HACK : Instead of creating a new buffer, we just modify the data/size + * of the buffer to point to the payload */ + GST_BUFFER_DATA (buf) = packet->payload; + GST_BUFFER_SIZE (buf) = packet->data_end - packet->payload; + + if (stream->state == PENDING_PACKET_EMPTY) { + if (G_UNLIKELY (!packet->payload_unit_start_indicator)) { + stream->state = PENDING_PACKET_DISCONT; + GST_WARNING ("Didn't get the first packet of this PES"); + } else { + GST_LOG ("EMPTY=>HEADER"); + stream->state = PENDING_PACKET_HEADER; + if (stream->pad) { + GST_DEBUG ("Setting pad caps on buffer %p", buf); + gst_buffer_set_caps (buf, GST_PAD_CAPS (stream->pad)); + } + } + } + + if (stream->state == PENDING_PACKET_HEADER) { + GST_LOG ("HEADER: appending data to array"); + /* Append to the array */ + stream->pendingbuffers[stream->nbpending++] = buf; + + /* parse the header */ + gst_ts_demux_parse_pes_header (demux, stream); + } else if (stream->state == PENDING_PACKET_BUFFER) { + GST_LOG ("BUFFER: appending data to bufferlist"); + stream->currentlist = g_list_prepend (stream->currentlist, buf); + } + + + return; +} + +static GstFlowReturn +gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream) +{ + GstFlowReturn res = GST_FLOW_OK; + MpegTSBaseStream *bs = (MpegTSBaseStream *) stream; + + + guint i; + GstClockTime tinypts = GST_CLOCK_TIME_NONE; + GstClockTime stop = GST_CLOCK_TIME_NONE; + GstEvent *newsegmentevent; + + GST_DEBUG ("stream:%p, pid:0x%04x stream_type:%d state:%d pad:%s:%s", + stream, bs->pid, bs->stream_type, stream->state, + GST_DEBUG_PAD_NAME (stream->pad)); + + if (G_UNLIKELY (stream->current == NULL)) { + GST_LOG ("stream->current == NULL"); + goto beach; + } + + if (G_UNLIKELY (stream->state == PENDING_PACKET_EMPTY)) { + GST_LOG ("EMPTY: returning"); + goto beach; + } + + /* We have a confirmed buffer, let's push it out */ + if (stream->state == PENDING_PACKET_BUFFER) { + GST_LOG ("BUFFER: pushing out pending data"); + stream->currentlist = g_list_reverse (stream->currentlist); + gst_buffer_list_iterator_add_list (stream->currentit, stream->currentlist); + gst_buffer_list_iterator_free (stream->currentit); + + + if (stream->pad) { + + if (demux->need_newsegment) { + + for (i = 0; i < 0x2000; i++) { + + if (demux->program->streams[i]) { + if ((!GST_CLOCK_TIME_IS_VALID (tinypts)) + || (((TSDemuxStream *) demux->program->streams[i])->pts < + tinypts)) + tinypts = ((TSDemuxStream *) demux->program->streams[i])->pts; + } + + + } + + if (GST_CLOCK_TIME_IS_VALID (demux->duration)) + stop = tinypts + demux->duration; + + GST_DEBUG ("Sending newsegment event"); + newsegmentevent = + gst_event_new_new_segment (0, 1.0, GST_FORMAT_TIME, tinypts, stop, + 0); + + push_event ((MpegTSBase *) demux, newsegmentevent); + + demux->need_newsegment = FALSE; + } + + GST_DEBUG_OBJECT (stream->pad, "Pushing buffer list "); + + res = gst_pad_push_list (stream->pad, stream->current); + GST_DEBUG_OBJECT (stream->pad, "Returned %s", gst_flow_get_name (res)); + /* FIXME : combine flow returns */ + res = tsdemux_combine_flows (demux, stream, res); + GST_DEBUG_OBJECT (stream->pad, "combined %s", gst_flow_get_name (res)); + } + } + +beach: + /* Reset everything */ + GST_LOG ("Resetting to EMPTY"); + stream->state = PENDING_PACKET_EMPTY; + + /* for (i = 0; i < stream->nbpending; i++) */ + /* gst_buffer_unref (stream->pendingbuffers[i]); */ + memset (stream->pendingbuffers, 0, TS_MAX_PENDING_BUFFERS); + stream->nbpending = 0; + + stream->current = NULL; + + + + return res; +} + +static GstFlowReturn +gst_ts_demux_handle_packet (GstTSDemux * demux, TSDemuxStream * stream, + MpegTSPacketizerPacket * packet, MpegTSPacketizerSection * section) +{ + GstFlowReturn res = GST_FLOW_OK; + + GST_DEBUG ("buffer:%p, data:%p", GST_BUFFER_DATA (packet->buffer), + packet->data); + GST_LOG ("pid 0x%04x pusi:%d, afc:%d, cont:%d, payload:%p", + packet->pid, + packet->payload_unit_start_indicator, + packet->adaptation_field_control, + packet->continuity_counter, packet->payload); + + if (section) { + GST_DEBUG ("section complete:%d, buffer size %d", + section->complete, GST_BUFFER_SIZE (section->buffer)); + } + + if (G_UNLIKELY (packet->payload_unit_start_indicator)) + /* Flush previous data */ + res = gst_ts_demux_push_pending_data (demux, stream); + + if (packet->adaptation_field_control & 0x2) { + if (packet->afc_flags & MPEGTS_AFC_PCR_FLAG) + gst_ts_demux_record_pcr (demux, stream, packet->pcr, + GST_BUFFER_OFFSET (packet->buffer)); + if (packet->afc_flags & MPEGTS_AFC_OPCR_FLAG) + gst_ts_demux_record_opcr (demux, stream, packet->opcr, + GST_BUFFER_OFFSET (packet->buffer)); + } + + if (packet->payload) + gst_ts_demux_queue_data (demux, stream, packet); + + return res; +} + +static GstFlowReturn +gst_ts_demux_push (MpegTSBase * base, MpegTSPacketizerPacket * packet, + MpegTSPacketizerSection * section) +{ + GstTSDemux *demux = GST_TS_DEMUX_CAST (base); + TSDemuxStream *stream = NULL; + GstFlowReturn res = GST_FLOW_OK; + + if (G_LIKELY (demux->program)) { + stream = (TSDemuxStream *) demux->program->streams[packet->pid]; + + if (stream) { + res = gst_ts_demux_handle_packet (demux, stream, packet, section); + } else if (packet->buffer) + gst_buffer_unref (packet->buffer); + } else { + if (packet->buffer) + gst_buffer_unref (packet->buffer); + } + return res; +} + +gboolean +gst_ts_demux_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (ts_demux_debug, "tsdemux", 0, + "MPEG transport stream demuxer"); + + return gst_element_register (plugin, "tsdemux", + GST_RANK_SECONDARY, GST_TYPE_TS_DEMUX); +} diff --git a/gst/mpegtsdemux/tsdemux.h b/gst/mpegtsdemux/tsdemux.h new file mode 100644 index 000000000..bfee2df42 --- /dev/null +++ b/gst/mpegtsdemux/tsdemux.h @@ -0,0 +1,78 @@ +/* + * tsdemux - GStreamer MPEG transport stream demuxer + * Copyright (C) 2009 Zaheer Abbas Merali + * 2010 Edward Hervey + * + * Authors: + * Zaheer Abbas Merali <zaheerabbas at merali dot org> + * Edward Hervey <edward.hervey@collabora.co.uk> + * + * 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 GST_TS_DEMUX_H +#define GST_TS_DEMUX_H + +#include <gst/gst.h> +#include <gst/base/gstbytereader.h> +#include "mpegtsbase.h" +#include "mpegtspacketizer.h" + +G_BEGIN_DECLS +#define GST_TYPE_TS_DEMUX \ + (gst_ts_demux_get_type()) +#define GST_TS_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TS_DEMUX,GstTSDemux)) +#define GST_TS_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TS_DEMUX,GstTSDemuxClass)) +#define GST_IS_TS_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TS_DEMUX)) +#define GST_IS_TS_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TS_DEMUX)) +#define GST_TS_DEMUX_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_TS_DEMUX, GstTSDemuxClass)) +#define GST_TS_DEMUX_CAST(obj) ((GstTSDemux*) obj) +typedef struct _GstTSDemux GstTSDemux; +typedef struct _GstTSDemuxClass GstTSDemuxClass; + +struct _GstTSDemux +{ + MpegTSBase parent; + + /* the following vars must be protected with the OBJECT_LOCK as they can be + * accessed from the application thread and the streaming thread */ + guint program_number; /* Required program number (ignore:-1) */ + gboolean emit_statistics; + + /*< private >*/ + MpegTSBaseProgram *program; /* Current program */ + guint current_program_number; + gboolean need_newsegment; + GstClockTime duration; /* Total duration */ +}; + +struct _GstTSDemuxClass +{ + MpegTSBaseClass parent_class; +}; + +GType gst_ts_demux_get_type (void); + +gboolean gst_ts_demux_plugin_init (GstPlugin * plugin); + +G_END_DECLS +#endif /* GST_TS_DEMUX_H */ |