diff options
Diffstat (limited to 'src/demuxers/demux_ts.c')
-rw-r--r-- | src/demuxers/demux_ts.c | 299 |
1 files changed, 294 insertions, 5 deletions
diff --git a/src/demuxers/demux_ts.c b/src/demuxers/demux_ts.c index 7464cc5bd..c6962d0bd 100644 --- a/src/demuxers/demux_ts.c +++ b/src/demuxers/demux_ts.c @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * - * $Id: demux_ts.c,v 1.66 2002/11/20 11:57:41 mroi Exp $ + * $Id: demux_ts.c,v 1.67 2002/11/28 10:21:07 petli Exp $ * * Demultiplexer for MPEG2 Transport Streams. * @@ -35,6 +35,9 @@ * Date Author * ---- ------ * + * 25-Nov-2002 Peter Liljenberg + * - Added DVBSUB support + * * 07-Nov-2992 Howdy Pierce * - various bugfixes * @@ -75,6 +78,52 @@ * TODO: do without memcpys, preview buffers */ + +/** HOW TO IMPLEMENT A DVBSUB DECODER. + * + * The DVBSUB protocol is specified in ETSI EN 300 743. It can be + * downloaded for free (registration required, though) from + * www.etsi.org. + * + * The spu_decoder should handle the packet type BUF_SPU_DVB. + * + * BUF_SPU_DVBSUB packets without the flag BUF_FLAG_SPECIAL contain + * the payload of the PES packets carrying DVBSUB data. Since the + * payload can be broken up over several buf_element_t and the DVBSUB + * is PES oriented, the CCCC field (low 16 bits) is used to convey the + * packet boundaries to the decoder: + * + * + For the first buffer of a packet, buf->content points to the + * first byte of the PES payload. CCCC is set to the length of the + * payload. The decoder can use this value to determine when a + * complete PES packet has been collected. + * + * + For the following buffers of the PES packet, CCCC is 0. + * + * The decoder can either use this information to reconstruct the PES + * payload, or ignore it and implement a parser that handles the + * irregularites at the start and end of PES packets. + * + * In any case buf->pts is always set to the PTS of the PES packet. + * + * + * BUF_SPU_DVB with BUF_FLAG_SPECIAL set contains no payload, and is + * used to pass control information to the decoder. + * + * If decoder_info[1] == BUF_SPECIAL_SPU_DVB_DESCRIPTOR then + * decoder_info[2] either points to a spu_dvb_descriptor_t or is 0. + * + * If it is 0, the user has disabled the subtitling, or has selected a + * channel that is not present in the stream. The decoder should + * remove any visible subtitling. + * + * If it is a pointer, the decoder should reset itself and start + * extracting the subtitle service identified by comp_page_id and + * aux_page_id in the spu_dvb_descriptor_t, (the composition and + * auxilliary page ids, respectively). + **/ + + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -149,6 +198,15 @@ typedef struct { } demux_ts_media; +/* DVBSUB */ +#define MAX_NO_SPU_LANGS 16 +typedef struct { + spu_dvb_descriptor_t desc; + int pid; + int media_index; +} demux_ts_spu_lang; + + typedef struct { /* * The first field must be the "base class" for the plugin! @@ -189,7 +247,8 @@ typedef struct { unsigned int audioPid; unsigned int videoMedia; unsigned int audioMedia; - + char audioLang[4]; + int send_end_buffers; int ignore_scr_discont; int send_newpts; @@ -202,6 +261,12 @@ typedef struct { uint32_t rstat[NPKT_PER_READ + 1]; #endif + /* DVBSUB */ + unsigned int spu_pid; + unsigned int spu_media; + demux_ts_spu_lang spu_langs[MAX_NO_SPU_LANGS]; + int no_spu_langs; + int current_spu_channel; } demux_ts_t; typedef struct { @@ -239,9 +304,11 @@ static uint32_t demux_ts_compute_crc32(demux_ts_t*this, uint8_t *data, static void check_newpts( demux_ts_t*this, int64_t pts ) { +#ifdef TS_LOG printf ("demux_ts: check_newpts %lld, send_newpts %d, buf_flag_seek %d\n", pts, this->send_newpts, this->buf_flag_seek); - +#endif + if (this->send_newpts && pts) { if (this->buf_flag_seek) { @@ -257,6 +324,61 @@ static void check_newpts( demux_ts_t*this, int64_t pts ) { /* + * demux_ts_update_spu_channel + * + * Send a BUF_SPU_DVB with BUF_SPECIAL_SPU_DVB_DESCRIPTOR to tell + * the decoder to reset itself on the new channel. + */ +static void demux_ts_update_spu_channel(demux_ts_t *this) +{ + xine_event_t ui_event; + buf_element_t *buf; + + this->current_spu_channel = this->stream->spu_channel; + + buf = this->video_fifo->buffer_pool_alloc(this->video_fifo); + + buf->type = BUF_SPU_DVB; + buf->content = buf->mem; + buf->decoder_flags = BUF_FLAG_SPECIAL; + buf->decoder_info[1] = BUF_SPECIAL_SPU_DVB_DESCRIPTOR; + buf->size = 0; + + if (this->current_spu_channel >= 0 + && this->current_spu_channel < this->no_spu_langs) + { + demux_ts_spu_lang *lang = &this->spu_langs[this->current_spu_channel]; + + buf->decoder_info[2] = (uint32_t) &(lang->desc); + + this->spu_pid = lang->pid; + this->spu_media = lang->media_index; + +#ifdef TS_LOG + printf("demux_ts: DVBSUB: selecting lang: %s page %ld %ld\n", + lang->desc.lang, lang->desc.comp_page_id, lang->desc.aux_page_id); +#endif + } + else + { + buf->decoder_info[2] = 0; + + this->spu_pid = INVALID_PID; + +#ifdef TS_LOG + printf("demux_ts: DVBSUB: deselecting lang\n"); +#endif + } + + this->video_fifo->put(this->video_fifo, buf); + + /* Inform UI of SPU channel changes */ + ui_event.type = XINE_EVENT_UI_CHANNELS_CHANGED; + ui_event.data_length = 0; + xine_event_send(this->stream, &ui_event); +} + +/* * demux_ts_parse_pat * * Parse a program association table (PAT). @@ -385,6 +507,7 @@ static void demux_ts_parse_pat (demux_ts_t*this, unsigned char *original_pkt, this->pmt_pid[program_count] = pmt_pid; this->audioPid = INVALID_PID; this->videoPid = INVALID_PID; + this->spu_pid = INVALID_PID; } this->pmt_pid[program_count] = pmt_pid; @@ -496,6 +619,16 @@ static int demux_ts_parse_pes_header (demux_ts_media *m, m->type = BUF_AUDIO_A52; return 1; + } else if (m->descriptor_tag == 0x06 + && p[0] == 0x20 && p[1] == 0x00) { + /* DVBSUB */ + long payload_len = ((buf[4] << 8) | buf[5]) - header_len - 3; + + m->content = p; + m->size = packet_len; + m->type = BUF_SPU_DVB + payload_len; + + return 1; } else if ((p[0] & 0xE0) == 0x20) { spu_id = (p[0] & 0x1f); @@ -636,6 +769,13 @@ static void demux_ts_buffer_pes(demux_ts_t*this, unsigned char *ts, printf ("demux_ts: produced buffer, pts=%lld\n", m->pts); #endif + /* DVBSUB: reset PES packet length field in buffer type, to + * indicate that the next buffer is in the middle of a PES + * packet. + **/ + if ((m->type & 0xffff0000) == BUF_SPU_DVB) { + m->type = BUF_SPU_DVB; + } } memcpy(m->buf->mem + m->buffered_bytes, ts, len); m->buffered_bytes += len; @@ -664,6 +804,28 @@ static void demux_ts_pes_new(demux_ts_t*this, m->buffered_bytes = 0; } + +/* Find the first ISO 639 language descriptor (tag 10) and + * store the 3-char code in dest, nullterminated. If no + * code is found, zero out dest. + **/ +static void demux_ts_get_lang_desc(demux_ts_t *this, char *dest, + const unsigned char *data, int length) +{ + while (length >= 2 && length >= 2 + data[1]) + { + if (data[0] == 10 && data[1] >= 4) + { + memcpy(dest, data + 2, 3); + dest[3] = 0; + printf("demux_ts: found ISO 639 lang: %s\n", dest); + return; + } + } + printf("demux_ts: found no ISO 639 lang\n"); + memset(dest, 0, 4); +} + /* * NAME demux_ts_parse_pmt * @@ -846,6 +1008,7 @@ static void demux_ts_parse_pmt (demux_ts_t *this, * Extract the elementary streams. */ mediaIndex = 0; + this->no_spu_langs = 0; while (section_length > 0) { unsigned int streamInfoLength; @@ -884,6 +1047,8 @@ static void demux_ts_parse_pmt (demux_ts_t *this, demux_ts_pes_new(this, mediaIndex, pid, this->audio_fifo,stream[0]); this->audioPid = pid; this->audioMedia = mediaIndex; + demux_ts_get_lang_desc(this, this->audioLang, + stream + 5, streamInfoLength); } break; case ISO_13818_PRIVATE: @@ -904,8 +1069,47 @@ static void demux_ts_parse_pmt (demux_ts_t *this, demux_ts_pes_new(this, mediaIndex, pid, this->audio_fifo,stream[0]); this->audioPid = pid; this->audioMedia = mediaIndex; + demux_ts_get_lang_desc(this, this->audioLang, + stream + 5, streamInfoLength); break; } + + /* DVBSUB */ + else if (stream[i] == 0x59) + { + int pos; + + for (pos = i + 2; + pos + 8 <= i + 2 + stream[i + 1] + && this->no_spu_langs < MAX_NO_SPU_LANGS; + pos += 8) + { + int no = this->no_spu_langs; + demux_ts_spu_lang *lang = &this->spu_langs[no]; + + this->no_spu_langs++; + + memcpy(lang->desc.lang, &stream[pos], 3); + lang->desc.lang[3] = 0; + lang->desc.comp_page_id = + (stream[pos + 4] << 8) | stream[pos + 5]; + lang->desc.aux_page_id = + (stream[pos + 6] << 8) | stream[pos + 7]; + lang->pid = pid; + lang->media_index = mediaIndex; + + demux_ts_pes_new(this, mediaIndex, + pid, this->video_fifo, + stream[0]); + +#ifdef TS_LOG + printf("demux_ts: DVBSUB: pid %u: %s page %ld %ld\n", + pid, lang->desc.lang, + lang->desc.comp_page_id, + lang->desc.aux_page_id); +#endif + } + } } break; case PRIVATE_A52: @@ -920,6 +1124,8 @@ static void demux_ts_parse_pmt (demux_ts_t *this, demux_ts_pes_new(this, mediaIndex, pid, this->audio_fifo, stream[0]); this->audioPid = pid; this->audioMedia = mediaIndex; + demux_ts_get_lang_desc(this, this->audioLang, + stream + 5, streamInfoLength); } break; default: @@ -953,6 +1159,9 @@ static void demux_ts_parse_pmt (demux_ts_t *this, #endif this->pcrPid = pid; } + + /* DVBSUB: update spu decoder */ + demux_ts_update_spu_channel(this); } static int sync_correct(demux_ts_t*this, uint8_t *buf, int32_t npkt_read) { @@ -1309,7 +1518,17 @@ static void demux_ts_parse_packet (demux_ts_t*this) { #endif return; } - if ((this->audioPid == INVALID_PID) && (this->videoPid == INVALID_PID)) { + /* DVBSUB */ + else if (pid == this->spu_pid) { +#ifdef TS_LOG + printf ("demux_ts: SPU pid: %.4x\n", pid); +#endif + demux_ts_buffer_pes (this, originalPkt+data_offset, this->spu_media, + payload_unit_start_indicator, continuity_counter, + data_len); + return; + } + if ((this->audioPid == INVALID_PID) && (this->videoPid == INVALID_PID)) { program_count = 0; while ((this->program_number[program_count] != INVALID_PROGRAM) ) { if (pid == this->pmt_pid[program_count]) { @@ -1340,6 +1559,13 @@ static int demux_ts_send_chunk (demux_plugin_t *this_gen) { demux_ts_parse_packet(this); + /* DVBSUB: check if channel has changed. Dunno if I should, or + * even could, lock the xine object. */ + if (this->stream->spu_channel != this->current_spu_channel) + { + demux_ts_update_spu_channel(this); + } + return this->status; } @@ -1395,6 +1621,11 @@ static void demux_ts_send_headers (demux_plugin_t *this_gen) { this->last_PCR = 0; this->scrambled_npids = 0; + /* DVBSUB */ + this->spu_pid = INVALID_PID; + this->no_spu_langs = 0; + this->current_spu_channel = this->stream->spu_channel; + /* FIXME ? */ this->stream->stream_info[XINE_STREAM_INFO_HAS_VIDEO] = 1; this->stream->stream_info[XINE_STREAM_INFO_HAS_AUDIO] = 1; @@ -1470,6 +1701,57 @@ static int demux_ts_get_video_frame (demux_plugin_t *this_gen, } +static uint32_t demux_ts_get_capabilities(demux_plugin_t *this_gen) +{ + return DEMUX_CAP_AUDIOLANG | DEMUX_CAP_SPULANG; +} + +static int demux_ts_get_optional_data(demux_plugin_t *this_gen, + void *data, int data_type) +{ + demux_ts_t *this = (demux_ts_t *) this_gen; + char *str = data; + + /* be a bit paranoid */ + if (this == NULL || this->stream == NULL) + return DEMUX_OPTIONAL_UNSUPPORTED; + + switch (data_type) + { + case DEMUX_OPTIONAL_DATA_AUDIOLANG: + if (this->audioLang[0]) + { + strcpy(str, this->audioLang); + } + else + { + sprintf(str, "%3i", xine_get_audio_channel(this->stream)); + } + return DEMUX_OPTIONAL_SUCCESS; + + case DEMUX_OPTIONAL_DATA_SPULANG: + if (this->current_spu_channel >= 0 + && this->current_spu_channel < this->no_spu_langs) + { + memcpy(str, this->spu_langs[this->current_spu_channel].desc.lang, 3); + str[4] = 0; + } + else if (this->current_spu_channel == -1) + { + strcpy(str, "none"); + } + else + { + sprintf(str, "%3i", this->current_spu_channel); + } + return DEMUX_OPTIONAL_SUCCESS; + + default: + return DEMUX_OPTIONAL_UNSUPPORTED; + } +} + + static demux_plugin_t *open_plugin (demux_class_t *class_gen, xine_stream_t *stream, input_plugin_t *input) { @@ -1583,6 +1865,8 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, this->demux_plugin.get_stream_length = demux_ts_get_stream_length; this->demux_plugin.get_video_frame = demux_ts_get_video_frame; this->demux_plugin.got_video_frame_cb= NULL; + this->demux_plugin.get_capabilities = demux_ts_get_capabilities; + this->demux_plugin.get_optional_data = demux_ts_get_optional_data; this->demux_plugin.demux_class = class_gen; /* @@ -1617,6 +1901,11 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, } #endif + /* DVBSUB */ + this->spu_pid = INVALID_PID; + this->no_spu_langs = 0; + this->current_spu_channel = this->stream->spu_channel; + return &this->demux_plugin; } @@ -1672,6 +1961,6 @@ static void *init_class (xine_t *xine, void *data) { plugin_info_t xine_plugin_info[] = { /* type, API, "name", version, special_info, init_function */ - { PLUGIN_DEMUX, 17, "mpeg-ts", XINE_VERSION_CODE, NULL, init_class }, + { PLUGIN_DEMUX, 18, "mpeg-ts", XINE_VERSION_CODE, NULL, init_class }, { PLUGIN_NONE, 0, "", 0, NULL, NULL } }; |