From 38e53859fdf5702bb82d937345c2158c2394a3f1 Mon Sep 17 00:00:00 2001 From: Nicos Gollan Date: Sat, 10 Oct 2009 14:47:57 +0100 Subject: Extended Matroska demuxer This adds the following functionality: * Read segment title and uses that for display in a UI There is an issue when the file does not specify a segment title. It will then fall back to a generic "(No title)", since I could not find a way to retrieve the file name the player shows. * More implementation files Added: - demux_matroska.h - demux_matroska_chapters.h This breaks the OO-ish C visibility a bit, since there need to be public (i.e. non-static) interfaces between the units. * Chapter Handling I did a rough initial implementation of Matroska's "editions" system. The demuxer will parse all editions from the header, and for each edition the top level of chapters. This is not quite the full spec as Matroska intends, but it should work fine as long as there is only a single edition and all editions/chapters only reference only one (the first and only) segment in the stream, and are supposed to apply to all tracks therein. When the stream has chapters, the demuxer will now handle skip events from the player to jump between chapters. --- ChangeLog | 1 + src/demuxers/Makefile.am | 4 +- src/demuxers/demux_matroska-chapters.c | 433 ++++++++++++++++++++++++++++ src/demuxers/demux_matroska.c | 506 ++++++++++++++++++--------------- src/demuxers/demux_matroska.h | 150 ++++++++++ src/demuxers/matroska.h | 53 +++- 6 files changed, 910 insertions(+), 237 deletions(-) create mode 100644 src/demuxers/demux_matroska-chapters.c create mode 100644 src/demuxers/demux_matroska.h diff --git a/ChangeLog b/ChangeLog index 7fa1dd398..0dccfbb78 100644 --- a/ChangeLog +++ b/ChangeLog @@ -20,6 +20,7 @@ xine-lib (1.1.17) 2009-??-?? viewing of Apple film trailers. * Fixed int-to-float conversion in the JACK output plugin. * Work around MOD files with reported length == 0. + * Reworked Matroska demuxer. Now reads files created by mkvmerge 2.7.0. xine-lib (1.1.16.3) 2009-04-03 * Security fixes: diff --git a/src/demuxers/Makefile.am b/src/demuxers/Makefile.am index 3bc1103ec..4c2aac019 100644 --- a/src/demuxers/Makefile.am +++ b/src/demuxers/Makefile.am @@ -130,7 +130,7 @@ xineplug_dmx_image_la_LIBADD = $(XINE_LIB) xineplug_dmx_nsv_la_SOURCES = demux_nsv.c xineplug_dmx_nsv_la_LIBADD = $(XINE_LIB) -xineplug_dmx_matroska_la_SOURCES = demux_matroska.c ebml.c +xineplug_dmx_matroska_la_SOURCES = demux_matroska.c demux_matroska-chapters.c ebml.c xineplug_dmx_matroska_la_LIBADD = $(XINE_LIB) $(ZLIB_LIBS) $(LTLIBINTL) xineplug_dmx_matroska_la_CPPFLAGS = $(ZLIB_CPPFLAGS) xineplug_dmx_matroska_la_CFLAGS = $(AM_CFLAGS) -fno-strict-aliasing @@ -142,4 +142,4 @@ xineplug_dmx_flv_la_SOURCES = demux_flv.c xineplug_dmx_flv_la_LIBADD = $(XINE_LIB) $(LTLIBINTL) xineinclude_HEADERS = demux.h -noinst_HEADERS = asfheader.h qtpalette.h group_games.h group_audio.h id3.h ebml.h matroska.h iff.h flacutils.h real_common.h +noinst_HEADERS = asfheader.h qtpalette.h group_games.h group_audio.h id3.h ebml.h matroska.h demux_matroska.h iff.h flacutils.h real_common.h diff --git a/src/demuxers/demux_matroska-chapters.c b/src/demuxers/demux_matroska-chapters.c new file mode 100644 index 000000000..aad8fe46d --- /dev/null +++ b/src/demuxers/demux_matroska-chapters.c @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2009 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * demultiplexer for matroska streams: chapter handling + * + * TODO: + * - nested chapters + * + * Authors: + * Nicos Gollan + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define LOG_MODULE "demux_matroska_chapters" +#define LOG_VERBOSE +/* +#define LOG +*/ + +#include "xine_internal.h" +#include "xineutils.h" +#include "demux.h" + +#include "ebml.h" +#include "matroska.h" +#include "demux_matroska.h" + +/* TODO: this only handles one single (title, language, country) tuple. + * See the header for information. */ +static int parse_chapter_display(demux_matroska_t *this, matroska_chapter_t *chap, int level) { + ebml_parser_t *ebml = this->ebml; + int next_level = level+1; + char* tmp_name = NULL; + char* tmp_lang = NULL; + char* tmp_country = NULL; + + while (next_level == level+1) { + ebml_elem_t elem; + + if (!ebml_read_elem_head(ebml, &elem)) + return 0; + + switch (elem.id) { + + case MATROSKA_ID_CH_STRING: + tmp_name = ebml_alloc_read_ascii(ebml, &elem); + break; + + case MATROSKA_ID_CH_LANGUAGE: + tmp_lang = ebml_alloc_read_ascii(ebml, &elem); + break; + + case MATROSKA_ID_CH_COUNTRY: + tmp_country = ebml_alloc_read_ascii(ebml, &elem); + break; + + default: + lprintf("Unhandled ID (inside ChapterDisplay): 0x%x\n", elem.id); + if (!ebml_skip(ebml, &elem)) + return 0; + } + + next_level = ebml_get_next_level(ebml, &elem); + } + + if (NULL != chap->title) { + chap->title = tmp_name; + + free(chap->language); + chap->language = tmp_lang; + + free(chap->country); + chap->country = tmp_country; + } else if (tmp_lang != NULL && !strcmp("eng", tmp_lang) && (chap->language == NULL || strcmp("eng", chap->language))) { + free(chap->title); + chap->title = tmp_name; + + free(chap->language); + chap->language = tmp_lang; + + free(chap->country); + chap->country = tmp_country; + } else { + free(tmp_name); + free(tmp_lang); + free(tmp_country); + } + + return 1; +} + +static int parse_chapter_atom(demux_matroska_t *this, matroska_chapter_t *chap, int level) { + ebml_parser_t *ebml = this->ebml; + int next_level = level+1; + uint64_t num; + + chap->time_start = 0; + chap->time_end = 0; + chap->hidden = 0; + chap->enabled = 1; + + while (next_level == level+1) { + ebml_elem_t elem; + + if (!ebml_read_elem_head(ebml, &elem)) { + lprintf("invalid head\n"); + return 0; + } + + switch (elem.id) { + case MATROSKA_ID_CH_UID: + if (!ebml_read_uint(ebml, &elem, &chap->uid)) { + lprintf("invalid UID\n"); + return 0; + } + break; + + case MATROSKA_ID_CH_TIMESTART: + if (!ebml_read_uint(ebml, &elem, &chap->time_start)) { + lprintf("invalid start time\n"); + return 0; + } + /* convert to xine timing: Matroska timestamps are in nanoseconds, + * xine's PTS are in 1/90,000s */ + chap->time_start /= 100000; + chap->time_start *= 9; + break; + + case MATROSKA_ID_CH_TIMEEND: + if (!ebml_read_uint(ebml, &elem, &chap->time_end)) { + lprintf("invalid end time\n"); + return 0; + } + /* convert to xine timing */ + chap->time_end /= 100000; + chap->time_end *= 9; + break; + + case MATROSKA_ID_CH_DISPLAY: + if (!ebml_read_master(ebml, &elem)) + return 0; + + lprintf("ChapterDisplay\n"); + if(!parse_chapter_display(this, chap, level+1)) { + lprintf("invalid display information\n"); + return 0; + } + break; + + case MATROSKA_ID_CH_HIDDEN: + if (!ebml_read_uint(ebml, &elem, &num)) + return 0; + chap->hidden = (int)num; + break; + + case MATROSKA_ID_CH_ENABLED: + if (!ebml_read_uint(ebml, &elem, &num)) + return 0; + chap->enabled = (int)num; + break; + + case MATROSKA_ID_CH_ATOM: /* TODO */ + xprintf(this->stream->xine, XINE_VERBOSITY_NONE, + LOG_MODULE ": Warning: Nested chapters are not supported, playback may suffer!\n"); + if (!ebml_skip(ebml, &elem)) + return 0; + break; + + case MATROSKA_ID_CH_TRACK: /* TODO */ + xprintf(this->stream->xine, XINE_VERBOSITY_NONE, + LOG_MODULE ": Warning: Specific track information in chapters is not supported, playback may suffer!\n"); + if (!ebml_skip(ebml, &elem)) + return 0; + break; + + default: + lprintf("Unhandled ID (inside ChapterAtom): 0x%x\n", elem.id); + if (!ebml_skip(ebml, &elem)) + return 0; + } + + next_level = ebml_get_next_level(ebml, &elem); + } + + /* fallback information */ + /* FIXME: check allocations! */ + if (NULL == chap->title) { + chap->title = malloc(9); + if (chap->title != NULL) + strncpy(chap->title, "No title", 9); + } + + if (NULL == chap->language) { + chap->language = malloc(4); + if (chap->language != NULL) + strncpy(chap->language, "unk", 4); + } + + if (NULL == chap->country) { + chap->country = malloc(3); + if (chap->country != NULL) + strncpy(chap->country, "XX", 3); + } + + lprintf( "Chapter 0x%" PRIx64 ": %" PRIu64 "-%" PRIu64 "(pts), %s (%s). %shidden, %senabled.\n", + chap->uid, chap->time_start, chap->time_end, + chap->title, chap->language, + (chap->hidden ? "" : "not "), + (chap->enabled ? "" : "not ")); + + return 1; +} + +static void free_chapter(demux_matroska_t *this, matroska_chapter_t *chap) { + free(chap->title); + free(chap->language); + free(chap->country); + + free(chap); +} + +static int parse_edition_entry(demux_matroska_t *this, matroska_edition_t *ed) { + ebml_parser_t *ebml = this->ebml; + int next_level = 3; + uint64_t num; + int i; + + ed->hidden = 0; + ed->is_default = 0; + ed->ordered = 0; + + while (next_level == 3) { + ebml_elem_t elem; + + if (!ebml_read_elem_head(ebml, &elem)) + return 0; + + switch (elem.id) { + case MATROSKA_ID_CH_ED_UID: + if (!ebml_read_uint(ebml, &elem, &ed->uid)) + return 0; + break; + + case MATROSKA_ID_CH_ED_HIDDEN: + if (!ebml_read_uint(ebml, &elem, &num)) + return 0; + ed->hidden = (int)num; + break; + + case MATROSKA_ID_CH_ED_DEFAULT: + if (!ebml_read_uint(ebml, &elem, &num)) + return 0; + ed->is_default = (int)num; + break; + + case MATROSKA_ID_CH_ED_ORDERED: + if (!ebml_read_uint(ebml, &elem, &num)) + return 0; + ed->ordered = (int)num; + break; + + case MATROSKA_ID_CH_ATOM: + { + matroska_chapter_t *chapter = calloc(1, sizeof(matroska_chapter_t)); + if (NULL == chapter) + return 0; + + lprintf("ChapterAtom\n"); + if (!ebml_read_master(ebml, &elem)) + return 0; + + if (!parse_chapter_atom(this, chapter, next_level)) + return 0; + + /* resize chapters array if necessary */ + if (ed->num_chapters >= ed->cap_chapters) { + matroska_chapter_t** old_chapters = ed->chapters; + ed->cap_chapters += 10; + ed->chapters = realloc(ed->chapters, ed->cap_chapters * sizeof(matroska_chapter_t*)); + + if (NULL == ed->chapters) { + ed->chapters = old_chapters; + ed->cap_chapters -= 10; + return 0; + } + } + + ed->chapters[ed->num_chapters] = chapter; + ++ed->num_chapters; + + break; + } + + default: + lprintf("Unhandled ID (inside EditionEntry): 0x%x\n", elem.id); + if (!ebml_skip(ebml, &elem)) + return 0; + } + + next_level = ebml_get_next_level(ebml, &elem); + } + + xprintf( this->stream->xine, XINE_VERBOSITY_LOG, + LOG_MODULE ": Edition 0x%" PRIx64 ": %shidden, %sdefault, %sordered. %d chapters:\n", + ed->uid, + (ed->hidden ? "" : "not "), + (ed->is_default ? "" : "not "), + (ed->ordered ? "" : "not "), + ed->num_chapters ); + + for (i=0; inum_chapters; ++i) { + matroska_chapter_t* chap = ed->chapters[i]; + xprintf( this->stream->xine, XINE_VERBOSITY_LOG, + LOG_MODULE ": Chapter %d: %" PRIu64 "-%" PRIu64 "(pts), %s (%s). %shidden, %senabled.\n", + i+1, chap->time_start, chap->time_end, + chap->title, chap->language, + (chap->hidden ? "" : "not "), + (chap->enabled ? "" : "not ")); + } + + return 1; +} + +static void free_edition(demux_matroska_t *this, matroska_edition_t *ed) { + int i; + + for(i=0; inum_chapters; ++i) { + free_chapter(this, ed->chapters[i]); + } + free(ed->chapters); + free(ed); +} + +int matroska_parse_chapters(demux_matroska_t *this) { + ebml_parser_t *ebml = this->ebml; + int next_level = 2; + + while (next_level == 2) { + ebml_elem_t elem; + + if (!ebml_read_elem_head(ebml, &elem)) + return 0; + + switch (elem.id) { + case MATROSKA_ID_CH_EDITIONENTRY: + { + matroska_edition_t *edition = calloc(1, sizeof(matroska_edition_t)); + if (NULL == edition) + return 0; + + lprintf("EditionEntry\n"); + if (!ebml_read_master(ebml, &elem)) + return 0; + + if (!parse_edition_entry(this, edition)) + return 0; + + /* resize editions array if necessary */ + if (this->num_editions >= this->cap_editions) { + matroska_edition_t** old_editions = this->editions; + this->cap_editions += 10; + this->editions = realloc(this->editions, this->cap_editions * sizeof(matroska_edition_t*)); + + if (NULL == this->editions) { + this->editions = old_editions; + this->cap_editions -= 10; + return 0; + } + } + + this->editions[this->num_editions] = edition; + ++this->num_editions; + + break; + } + + default: + lprintf("Unhandled ID: 0x%x\n", elem.id); + if (!ebml_skip(ebml, &elem)) + return 0; + } + + next_level = ebml_get_next_level(ebml, &elem); + } + + return 1; +} + +void matroska_free_editions(demux_matroska_t *this) { + int i; + + for(i=0; inum_editions; ++i) { + free_edition(this, this->editions[i]); + } + free(this->editions); + this->num_editions = 0; + this->cap_editions = 0; +} + +int matroska_get_chapter(demux_matroska_t *this, uint64_t tc, matroska_edition_t** ed) { + uint64_t block_pts = (tc * this->timecode_scale) / 100000 * 9; + int chapter_idx = 0; + + if (this->num_editions < 1) + return -1; + + while (chapter_idx < (*ed)->num_chapters && block_pts > (*ed)->chapters[chapter_idx]->time_start) + ++chapter_idx; + + if (chapter_idx > 0) + --chapter_idx; + + return chapter_idx; +} diff --git a/src/demuxers/demux_matroska.c b/src/demuxers/demux_matroska.c index 5cf783bf5..9d224c62c 100644 --- a/src/demuxers/demux_matroska.c +++ b/src/demuxers/demux_matroska.c @@ -42,6 +42,7 @@ /* #define LOG */ + #include "xine_internal.h" #include "xineutils.h" #include "demux.h" @@ -50,89 +51,7 @@ #include "ebml.h" #include "matroska.h" - -#define NUM_PREVIEW_BUFFERS 10 - -#define MAX_STREAMS 128 -#define MAX_FRAMES 32 - -#define WRAP_THRESHOLD 90000 - -typedef struct { - int track_num; - off_t *pos; - uint64_t *timecode; - int num_entries; - -} matroska_index_t; - -typedef struct { - - demux_plugin_t demux_plugin; - - xine_stream_t *stream; - - input_plugin_t *input; - - int status; - - ebml_parser_t *ebml; - - /* segment element */ - ebml_elem_t segment; - uint64_t timecode_scale; - int duration; /* in millis */ - int preview_sent; - int preview_mode; - - /* meta seek info */ - int has_seekhead; - int seekhead_handled; - - /* seek info */ - matroska_index_t *indexes; - int num_indexes; - int first_cluster_found; - int skip_to_timecode; - int skip_for_track; - - /* tracks */ - int num_tracks; - int num_video_tracks; - int num_audio_tracks; - int num_sub_tracks; - - matroska_track_t *tracks[MAX_STREAMS]; - - /* block */ - uint8_t *block_data; - size_t block_data_size; - - /* current tracks */ - matroska_track_t *video_track; /* to remove */ - matroska_track_t *audio_track; /* to remove */ - matroska_track_t *sub_track; /* to remove */ - - int send_newpts; - int buf_flag_seek; - - /* seekhead parsing */ - int top_level_list_size; - int top_level_list_max_size; - off_t *top_level_list; - -} demux_matroska_t ; - -typedef struct { - - demux_class_t demux_class; - - /* class-wide, global variables here */ - - xine_t *xine; - -} demux_matroska_class_t; - +#include "demux_matroska.h" static void check_newpts (demux_matroska_t *this, int64_t pts, matroska_track_t *track) { @@ -206,10 +125,10 @@ static int parse_info(demux_matroska_t *this) { ebml_parser_t *ebml = this->ebml; int next_level = 2; double duration = 0.0; /* in matroska unit */ - + while (next_level == 2) { ebml_elem_t elem; - + if (!ebml_read_elem_head(ebml, &elem)) return 0; @@ -219,14 +138,22 @@ static int parse_info(demux_matroska_t *this) { if (!ebml_read_uint(ebml, &elem, &this->timecode_scale)) return 0; break; - case MATROSKA_ID_I_DURATION: { - + + case MATROSKA_ID_I_DURATION: lprintf("duration\n"); if (!ebml_read_float(ebml, &elem, &duration)) return 0; - } - break; - + break; + + case MATROSKA_ID_I_TITLE: + lprintf("title\n"); + if (NULL != this->title) + free(this->title); + + this->title = ebml_alloc_read_ascii(ebml, &elem); + _x_meta_info_set_utf8(this->stream, XINE_META_INFO_TITLE, this->title); + break; + default: lprintf("Unhandled ID: 0x%x\n", elem.id); if (!ebml_skip(ebml, &elem)) @@ -240,6 +167,8 @@ static int parse_info(demux_matroska_t *this) { this->duration = (int)(duration * (double)this->timecode_scale / 1000000.0); lprintf("timecode_scale: %" PRId64 "\n", this->timecode_scale); lprintf("duration: %d\n", this->duration); + lprintf("title: %s\n", (NULL != this->title ? this->title : "(none)")); + return 1; } @@ -1151,67 +1080,72 @@ static void handle_vobsub (demux_plugin_t *this_gen, matroska_track_t *track, static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { ebml_parser_t *ebml = this->ebml; int next_level = 3; - + while (next_level == 3) { ebml_elem_t elem; - + if (!ebml_read_elem_head(ebml, &elem)) return 0; switch (elem.id) { - case MATROSKA_ID_TR_NUMBER: { - uint64_t num; - lprintf("TrackNumber\n"); - if (!ebml_read_uint(ebml, &elem, &num)) - return 0; - track->track_num = num; - } - break; - - case MATROSKA_ID_TR_TYPE: { - uint64_t num; - lprintf("TrackType\n"); - if (!ebml_read_uint(ebml, &elem, &num)) - return 0; - track->track_type = num; - } - break; - - case MATROSKA_ID_TR_CODECID: { - char *codec_id = ebml_alloc_read_ascii (ebml, &elem); - lprintf("CodecID\n"); - if (!codec_id) - return 0; - track->codec_id = codec_id; - } - break; - - case MATROSKA_ID_TR_CODECPRIVATE: { - char *codec_private; - if (elem.len >= 0x80000000) - return 0; - codec_private = malloc (elem.len); - if (! codec_private) - return 0; - lprintf("CodecPrivate\n"); - if (!ebml_read_binary(ebml, &elem, codec_private)) { - free(codec_private); - return 0; - } - track->codec_private = codec_private; - track->codec_private_len = elem.len; - } - break; - - case MATROSKA_ID_TR_LANGUAGE: { - char *language = ebml_alloc_read_ascii (ebml, &elem); - lprintf("Language\n"); - if (!language) - return 0; - track->language = language; - } - break; - + case MATROSKA_ID_TR_NUMBER: + { + uint64_t num; + lprintf("TrackNumber\n"); + if (!ebml_read_uint(ebml, &elem, &num)) + return 0; + track->track_num = num; + } + break; + + case MATROSKA_ID_TR_TYPE: + { + uint64_t num; + lprintf("TrackType\n"); + if (!ebml_read_uint(ebml, &elem, &num)) + return 0; + track->track_type = num; + } + break; + + case MATROSKA_ID_TR_CODECID: + { + char *codec_id = ebml_alloc_read_ascii (ebml, &elem); + lprintf("CodecID\n"); + if (!codec_id) + return 0; + track->codec_id = codec_id; + } + break; + + case MATROSKA_ID_TR_CODECPRIVATE: + { + char *codec_private; + if (elem.len >= 0x80000000) + return 0; + codec_private = malloc (elem.len); + if (! codec_private) + return 0; + lprintf("CodecPrivate\n"); + if (!ebml_read_binary(ebml, &elem, codec_private)) { + free(codec_private); + return 0; + } + track->codec_private = codec_private; + track->codec_private_len = elem.len; + } + break; + + case MATROSKA_ID_TR_LANGUAGE: + { + char *language = ebml_alloc_read_ascii (ebml, &elem); + lprintf("Language\n"); + if (!language) + return 0; + track->language = language; + } + break; + case MATROSKA_ID_TV: lprintf("Video\n"); if (track->video_track) @@ -1221,8 +1155,8 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { return 0; if ((elem.len > 0) && !parse_video_track(this, track->video_track)) return 0; - break; - + break; + case MATROSKA_ID_TA: lprintf("Audio\n"); if (track->audio_track) @@ -1232,38 +1166,54 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { return 0; if ((elem.len > 0) && !parse_audio_track(this, track->audio_track)) return 0; - break; - - case MATROSKA_ID_TR_FLAGDEFAULT: { - uint64_t val; - - lprintf("Default\n"); - if (!ebml_read_uint(ebml, &elem, &val)) - return 0; - track->default_flag = (int)val; - } - break; + break; - case MATROSKA_ID_TR_DEFAULTDURATION: { - uint64_t val; + case MATROSKA_ID_TR_FLAGDEFAULT: + { + uint64_t val; - if (!ebml_read_uint(ebml, &elem, &val)) - return 0; - track->default_duration = val; - lprintf("Default Duration: %"PRIu64"\n", track->default_duration); - } - break; + lprintf("Default\n"); + if (!ebml_read_uint(ebml, &elem, &val)) + return 0; + track->default_flag = (int)val; + } + break; - case MATROSKA_ID_CONTENTENCODINGS: { - lprintf("ContentEncodings\n"); - if (!ebml_read_master (ebml, &elem)) - return 0; - if ((elem.len > 0) && !parse_content_encodings(this, track)) - return 0; - } - break; + case MATROSKA_ID_TR_DEFAULTDURATION: + { + uint64_t val; + + if (!ebml_read_uint(ebml, &elem, &val)) + return 0; + track->default_duration = val; + lprintf("Default Duration: %"PRIu64"\n", track->default_duration); + } + break; + + case MATROSKA_ID_CONTENTENCODINGS: + { + lprintf("ContentEncodings\n"); + if (!ebml_read_master (ebml, &elem)) + return 0; + if ((elem.len > 0) && !parse_content_encodings(this, track)) + return 0; + } + break; + + case MATROSKA_ID_TR_UID: + { + uint64_t val; + + if (!ebml_read_uint(ebml, &elem, &val)) { + lprintf("Track UID (invalid)\n"); + return 0; + } + + track->uid = val; + lprintf("Track UID: 0x%" PRIx64 "\n", track->uid); + } + break; - case MATROSKA_ID_TR_UID: case MATROSKA_ID_TR_FLAGENABLED: case MATROSKA_ID_TR_FLAGLACING: case MATROSKA_ID_TR_MINCACHE: @@ -1284,12 +1234,12 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { } next_level = ebml_get_next_level(ebml, &elem); } - + xprintf(this->stream->xine, XINE_VERBOSITY_LOG, - "demux_matroska: Track %d, %s %s\n", - track->track_num, - (track->codec_id ? track->codec_id : ""), - (track->language ? track->language : "")); + "demux_matroska: Track %d, %s %s\n", + track->track_num, + (track->codec_id ? track->codec_id : ""), + (track->language ? track->language : "")); if (track->codec_id) { void (*init_codec)(demux_matroska_t *, matroska_track_t *) = NULL; @@ -1307,14 +1257,14 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_UNCOMPRESSED)) { } else if ((!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG4_SP)) || - (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG4_ASP)) || - (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG4_AP))) { + (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG4_ASP)) || + (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG4_AP))) { xine_bmiheader *bih; - + lprintf("MATROSKA_CODEC_ID_V_MPEG4_*\n"); if (track->codec_private_len > 0x7fffffff - sizeof(xine_bmiheader)) track->codec_private_len = 0x7fffffff - sizeof(xine_bmiheader); - + /* create a bitmap info header struct for MPEG 4 */ bih = calloc(1, sizeof(xine_bmiheader) + track->codec_private_len); bih->biSize = sizeof(xine_bmiheader) + track->codec_private_len; @@ -1322,23 +1272,23 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { bih->biWidth = track->video_track->pixel_width; bih->biHeight = track->video_track->pixel_height; _x_bmiheader_le2me(bih); - + /* add bih extra data */ memcpy(bih + 1, track->codec_private, track->codec_private_len); free(track->codec_private); track->codec_private = (uint8_t *)bih; track->codec_private_len = bih->biSize; track->buf_type = BUF_VIDEO_MPEG4; - + /* init as a vfw decoder */ init_codec = init_codec_video; } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG4_AVC)) { xine_bmiheader *bih; - + lprintf("MATROSKA_CODEC_ID_V_MPEG4_AVC\n"); if (track->codec_private_len > 0x7fffffff - sizeof(xine_bmiheader)) track->codec_private_len = 0x7fffffff - sizeof(xine_bmiheader); - + /* create a bitmap info header struct for h264 */ bih = calloc(1, sizeof(xine_bmiheader) + track->codec_private_len); bih->biSize = sizeof(xine_bmiheader) + track->codec_private_len; @@ -1346,14 +1296,14 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { bih->biWidth = track->video_track->pixel_width; bih->biHeight = track->video_track->pixel_height; _x_bmiheader_le2me(bih); - + /* add bih extra data */ memcpy(bih + 1, track->codec_private, track->codec_private_len); free(track->codec_private); track->codec_private = (uint8_t *)bih; track->codec_private_len = bih->biSize; track->buf_type = BUF_VIDEO_H264; - + /* init as a vfw decoder */ init_codec = init_codec_video; } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MSMPEG4V3)) { @@ -1374,7 +1324,7 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { track->handle_content = handle_realvideo; init_codec = init_codec_real; } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_REAL_RV40)) { - + lprintf("MATROSKA_CODEC_ID_V_REAL_RV40\n"); track->buf_type = BUF_VIDEO_RV40; track->handle_content = handle_realvideo; @@ -1386,8 +1336,8 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { track->buf_type = BUF_VIDEO_THEORA_RAW; init_codec = init_codec_xiph; } else if ((!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_MPEG1_L1)) || - (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_MPEG1_L2)) || - (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_MPEG1_L3))) { + (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_MPEG1_L2)) || + (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_MPEG1_L3))) { lprintf("MATROSKA_CODEC_ID_A_MPEG1\n"); track->buf_type = BUF_AUDIO_MPEG; init_codec = init_codec_audio; @@ -1399,7 +1349,7 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { lprintf("MATROSKA_CODEC_ID_A_AC3\n"); track->buf_type = BUF_AUDIO_A52; init_codec = init_codec_audio; - + } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_DTS)) { lprintf("MATROSKA_CODEC_ID_A_DTS\n"); track->buf_type = BUF_AUDIO_DTS; @@ -1423,7 +1373,7 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { init_codec = init_codec_audio; } } else if (!strncmp(track->codec_id, MATROSKA_CODEC_ID_A_AAC, - sizeof(MATROSKA_CODEC_ID_A_AAC) - 1)) { + sizeof(MATROSKA_CODEC_ID_A_AAC) - 1)) { lprintf("MATROSKA_CODEC_ID_A_AAC\n"); track->buf_type = BUF_AUDIO_AAC; init_codec = init_codec_aac; @@ -1443,17 +1393,17 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { track->buf_type = BUF_AUDIO_ATRK; init_codec = init_codec_real; } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_S_TEXT_UTF8) || - !strcmp(track->codec_id, MATROSKA_CODEC_ID_S_UTF8)) { + !strcmp(track->codec_id, MATROSKA_CODEC_ID_S_UTF8)) { lprintf("MATROSKA_CODEC_ID_S_TEXT_UTF8\n"); track->buf_type = BUF_SPU_OGM; track->handle_content = handle_sub_utf8; } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_S_TEXT_SSA) || - !strcmp(track->codec_id, MATROSKA_CODEC_ID_S_SSA)) { + !strcmp(track->codec_id, MATROSKA_CODEC_ID_S_SSA)) { lprintf("MATROSKA_CODEC_ID_S_TEXT_SSA\n"); track->buf_type = BUF_SPU_OGM; track->handle_content = handle_sub_ssa; } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_S_TEXT_ASS) || - !strcmp(track->codec_id, MATROSKA_CODEC_ID_S_ASS)) { + !strcmp(track->codec_id, MATROSKA_CODEC_ID_S_ASS)) { lprintf("MATROSKA_CODEC_ID_S_TEXT_ASS\n"); track->buf_type = BUF_SPU_OGM; track->handle_content = handle_sub_ssa; @@ -1466,7 +1416,7 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { track->buf_type = BUF_SPU_DVD; track->handle_content = handle_vobsub; init_codec = init_codec_vobsub; - + /* Enable autodetection of the zlib compression, unless it was * explicitely set. Most vobsubs are compressed with zlib but * are not declared as such. @@ -1503,16 +1453,16 @@ static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) { } if (init_codec) { - if (! track->fifo) { - xprintf(this->stream->xine, XINE_VERBOSITY_LOG, - "demux_matroska: Error: fifo not set up for track of type type %" PRIu32 "\n", track->track_type); - return 0; + if (! track->fifo) { + xprintf(this->stream->xine, XINE_VERBOSITY_LOG, + "demux_matroska: Error: fifo not set up for track of type type %" PRIu32 "\n", track->track_type); + return 0; } init_codec(this, track); } } } - + return 1; } @@ -1531,6 +1481,12 @@ static int parse_tracks(demux_matroska_t *this) { case MATROSKA_ID_TR_ENTRY: { matroska_track_t *track; + /* bail out early if no more tracks can be handled! */ + if (this->num_tracks >= MAX_STREAMS) { + lprintf("Too many tracks!\n"); + return 0; + } + /* alloc and initialize a track with 0 */ track = calloc(1, sizeof(matroska_track_t)); track->compress_algo = MATROSKA_COMPRESS_NONE; @@ -1544,29 +1500,7 @@ static int parse_tracks(demux_matroska_t *this) { this->num_tracks++; } break; - - default: - lprintf("Unhandled ID: 0x%x\n", elem.id); - if (!ebml_skip(ebml, &elem)) - return 0; - } - next_level = ebml_get_next_level(ebml, &elem); - } - return 1; -} - -static int parse_chapters(demux_matroska_t *this) { - ebml_parser_t *ebml = this->ebml; - int next_level = 2; - - while (next_level == 2) { - ebml_elem_t elem; - - if (!ebml_read_elem_head(ebml, &elem)) - return 0; - - switch (elem.id) { default: lprintf("Unhandled ID: 0x%x\n", elem.id); if (!ebml_skip(ebml, &elem)) @@ -1577,7 +1511,6 @@ static int parse_chapters(demux_matroska_t *this) { return 1; } - static int parse_cue_trackposition(demux_matroska_t *this, int *track_num, int64_t *pos) { ebml_parser_t *ebml = this->ebml; @@ -2165,6 +2098,48 @@ static int parse_block_group(demux_matroska_t *this, return 1; } +static int demux_matroska_seek (demux_plugin_t*, off_t, int, int); + +static void handle_events(demux_matroska_t *this) { + xine_event_t* event; + + while ((event = xine_event_get(this->event_queue))) { + if (this->num_editions > 0) { + matroska_edition_t* ed = this->editions[0]; + int chapter_idx = matroska_get_chapter(this, this->last_timecode, &ed); + uint64_t next_time; + + if (chapter_idx < 0) { + xine_event_free(event); + continue; + } + + switch(event->type) { + case XINE_EVENT_INPUT_NEXT: + if (chapter_idx < ed->num_chapters-1) { + next_time = ed->chapters[chapter_idx+1]->time_start / 90; + demux_matroska_seek((demux_plugin_t*)this, 0, next_time, 1); + } + break; + + /* TODO: should this try to implement common "start of chapter" + * functionality? */ + case XINE_EVENT_INPUT_PREVIOUS: + if (chapter_idx > 0) { + next_time = ed->chapters[chapter_idx-1]->time_start / 90; + demux_matroska_seek((demux_plugin_t*)this, 0, next_time, 1); + } + break; + + default: + break; + } + } + + xine_event_free(event); + } +} + static int parse_cluster(demux_matroska_t *this) { ebml_parser_t *ebml = this->ebml; int this_level = ebml->level; @@ -2185,6 +2160,8 @@ static int parse_cluster(demux_matroska_t *this) { this->first_cluster_found = 1; } + handle_events(this); + while (next_level == this_level) { ebml_elem_t elem; @@ -2226,6 +2203,49 @@ static int parse_cluster(demux_matroska_t *this) { } next_level = ebml_get_next_level(ebml, &elem); } + + /* at this point, we MUST have a timecode (according to format spec). + * Use that to find the chapter we are in, and adjust the title. + * + * TODO: this only looks at the chapters in the first edition. + */ + + this->last_timecode = timecode; + + if (this->num_editions <= 0) + return 1; + matroska_edition_t *ed = this->editions[0]; + + if (ed->num_chapters <= 0) + return 1; + + /* fix up a makeshift title if none has been set yet (e.g. filename) */ + if (NULL == this->title && NULL != _x_meta_info_get(this->stream, XINE_META_INFO_TITLE)) + this->title = strdup(_x_meta_info_get(this->stream, XINE_META_INFO_TITLE)); + + if (NULL == this->title) + this->title = strdup("(No title)"); + + if (NULL == this->title) { + lprintf("Failed to determine a valid stream title!\n"); + return 1; + } + + int chapter_idx = matroska_get_chapter(this, timecode, &ed); + if (chapter_idx < 0) { + _x_meta_info_set_utf8(this->stream, XINE_META_INFO_TITLE, this->title); + return 1; + } + + xine_ui_data_t uidata = { + .str = {0, }, + .str_len = 0, + }; + + uidata.str_len = snprintf(uidata.str, sizeof(uidata.str), "%s / (%d) %s", + this->title, chapter_idx+1, ed->chapters[chapter_idx]->title); + _x_meta_info_set_utf8(this->stream, XINE_META_INFO_TITLE, uidata.str); + return 1; } @@ -2399,7 +2419,7 @@ static int parse_top_level_head(demux_matroska_t *this, int *next_level) { lprintf("Chapters\n"); if (!ebml_read_master (ebml, &elem)) return 0; - if ((elem.len > 0) && !parse_chapters(this)) + if ((elem.len > 0) && !matroska_parse_chapters(this)) return 0; break; case MATROSKA_ID_CLUSTER: @@ -2763,6 +2783,8 @@ static void demux_matroska_dispose (demux_plugin_t *this_gen) { demux_matroska_t *this = (demux_matroska_t *) this_gen; int i; + free(this->block_data); + /* free tracks */ for (i = 0; i < this->num_tracks; i++) { matroska_track_t *track; @@ -2780,7 +2802,7 @@ static void demux_matroska_dispose (demux_plugin_t *this_gen) { free (track->audio_track); if (track->sub_track) free (track->sub_track); - + free (track); } /* Free the cues. */ @@ -2792,12 +2814,17 @@ static void demux_matroska_dispose (demux_plugin_t *this_gen) { } if (this->indexes) free(this->indexes); - - /* Free the top_level elem list */ + + /* Free the top_level elem list */ if (this->top_level_list) free(this->top_level_list); + free(this->title); + + matroska_free_editions(this); + dispose_ebml_parser(this->ebml); + xine_event_dispose_queue(this->event_queue); free (this); } @@ -2811,7 +2838,13 @@ static int demux_matroska_get_stream_length (demux_plugin_t *this_gen) { static uint32_t demux_matroska_get_capabilities (demux_plugin_t *this_gen) { - return DEMUX_CAP_SPULANG | DEMUX_CAP_AUDIOLANG; + demux_matroska_t* this = (demux_matroska_t*)this_gen; + uint32_t caps = DEMUX_CAP_SPULANG | DEMUX_CAP_AUDIOLANG; + + if(this->num_editions > 0 && this->editions[0]->num_chapters > 0) + caps |= DEMUX_CAP_CHAPTERS; + + return caps; } @@ -2942,11 +2975,18 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, xine_stream_t *str if (strcmp(ebml->doctype, "matroska")) goto error; + this->event_queue = xine_event_new_queue(this->stream); + return &this->demux_plugin; error: dispose_ebml_parser(ebml); - free(this); + + if (NULL != this) { + xine_event_dispose_queue(this->event_queue); + free(this); + } + return NULL; } diff --git a/src/demuxers/demux_matroska.h b/src/demuxers/demux_matroska.h new file mode 100644 index 000000000..a62aba498 --- /dev/null +++ b/src/demuxers/demux_matroska.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2000-2008 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * demultiplexer for matroska streams: shared header + */ + +#ifndef _DEMUX_MATROSKA_H_ +#define _DEMUX_MATROSKA_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "xine_internal.h" +#include "demux.h" +#include "buffer.h" +#include "bswap.h" + +#include "ebml.h" +#include "matroska.h" + +#define NUM_PREVIEW_BUFFERS 10 + +#define MAX_STREAMS 128 +#define MAX_FRAMES 32 + +#define WRAP_THRESHOLD 90000 + +typedef struct { + int track_num; + off_t *pos; + uint64_t *timecode; + int num_entries; + +} matroska_index_t; + +typedef struct { + + demux_plugin_t demux_plugin; + + xine_stream_t *stream; + + input_plugin_t *input; + + int status; + + ebml_parser_t *ebml; + + /* segment element */ + ebml_elem_t segment; + uint64_t timecode_scale; + int duration; /* in millis */ + int preview_sent; + int preview_mode; + char *title; + + /* meta seek info */ + int has_seekhead; + int seekhead_handled; + + /* seek info */ + matroska_index_t *indexes; + int num_indexes; + int first_cluster_found; + int skip_to_timecode; + int skip_for_track; + + /* tracks */ + int num_tracks; + int num_video_tracks; + int num_audio_tracks; + int num_sub_tracks; + + matroska_track_t *tracks[MAX_STREAMS]; + + /* maintain editions, number and capacity */ + int num_editions, cap_editions; + matroska_edition_t **editions; + + /* block */ + uint8_t *block_data; + size_t block_data_size; + + /* current tracks */ + matroska_track_t *video_track; /* to remove */ + matroska_track_t *audio_track; /* to remove */ + matroska_track_t *sub_track; /* to remove */ + uint64_t last_timecode; + + int send_newpts; + int buf_flag_seek; + + /* seekhead parsing */ + int top_level_list_size; + int top_level_list_max_size; + off_t *top_level_list; + + /* event handling (chapter navigation) */ + xine_event_queue_t *event_queue; +} demux_matroska_t ; + +typedef struct { + + demux_class_t demux_class; + + /* class-wide, global variables here */ + + xine_t *xine; + +} demux_matroska_class_t; + +/* "entry points" for chapter handling. + * The parser descends into "Chapters" elements at the _parse_ function, + * and editions care about cleanup internally. */ +int matroska_parse_chapters(demux_matroska_t*); +void matroska_free_editions(demux_matroska_t*); + +/* Search an edition for the chapter matching a given timecode. + * + * Return: chapter index, or -1 if none is found. + * + * TODO: does not handle chapter end times yet. + */ +int matroska_get_chapter(demux_matroska_t*, uint64_t, matroska_edition_t**); + +#endif /* _DEMUX_MATROSKA_H_ */ diff --git a/src/demuxers/matroska.h b/src/demuxers/matroska.h index df6e8ad1b..b32725b3b 100644 --- a/src/demuxers/matroska.h +++ b/src/demuxers/matroska.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2000-2007 the xine project + * Copyright (C) 2000-2009 the xine project * * This file is part of xine, a free video player. * @@ -16,6 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * Matroska EBML stream handling */ #ifndef MATROSKA_H #define MATROSKA_H @@ -169,10 +171,16 @@ /* Chapters */ #define MATROSKA_ID_CHAPTERS 0x1043A770 #define MATROSKA_ID_CH_EDITIONENTRY 0x45B9 +#define MATROSKA_ID_CH_ED_UID 0x45BC +#define MATROSKA_ID_CH_ED_HIDDEN 0x45BD +#define MATROSKA_ID_CH_ED_DEFAULT 0x45DB +#define MATROSKA_ID_CH_ED_ORDERED 0x45DD #define MATROSKA_ID_CH_ATOM 0xB6 #define MATROSKA_ID_CH_UID 0x73C4 #define MATROSKA_ID_CH_TIMESTART 0x91 #define MATROSKA_ID_CH_TIMEEND 0x92 +#define MATROSKA_ID_CH_HIDDEN 0x98 +#define MATROSKA_ID_CH_ENABLED 0x4598 #define MATROSKA_ID_CH_TRACK 0x8F #define MATROSKA_ID_CH_TRACKNUMBER 0x89 #define MATROSKA_ID_CH_DISPLAY 0x80 @@ -183,6 +191,46 @@ /* Tags */ #define MATROSKA_ID_TAGS 0x1254C367 +/* Chapter (used in tracks) */ +typedef struct { + uint64_t uid; + uint64_t time_start; + uint64_t time_end; + /* if not 0, the chapter could e.g. be used for skipping, but not + * be shown in the chapter list */ + int hidden; + /* disabled chapters should be skipped during playback (using this + * would require parsing control blocks) */ + int enabled; + /* Tracks this chapter belongs to. + * Remember that elements can occur in any order, so in theory the + * chapters could become available before the tracks do. + * TODO: currently unused + */ + /* uint64_t* tracks; */ + /* Chapter titles and locale information + * TODO: chapters can have multiple sets of those, i.e. several tuples + * (title, language, country). The current implementation picks from + * those by the following rules: + * 1) remember the first element + * 2) overwrite with an element where language=="eng" + */ + char* title; + char* language; + char* country; +} matroska_chapter_t; + +/* Edition */ +typedef struct { + uint64_t uid; + unsigned int hidden; + unsigned int is_default; + unsigned int ordered; + + int num_chapters, cap_chapters; + matroska_chapter_t** chapters; +} matroska_edition_t; + /* Matroska Track */ typedef struct { int flag_interlaced; @@ -214,7 +262,8 @@ typedef struct { typedef struct matroska_track_s matroska_track_t; struct matroska_track_s { int track_num; - + uint64_t uid; + uint32_t track_type; uint64_t default_duration; char *language; -- cgit v1.2.3 From 017c80580344fa05660dd0046066339d2dede6fd Mon Sep 17 00:00:00 2001 From: Darren Salt Date: Mon, 31 Aug 2009 00:52:28 +0100 Subject: Add BluRay subtitle type. --- src/xine-engine/buffer.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xine-engine/buffer.h b/src/xine-engine/buffer.h index 6928ebd96..e0866f549 100644 --- a/src/xine-engine/buffer.h +++ b/src/xine-engine/buffer.h @@ -271,6 +271,7 @@ extern "C" { #define BUF_SPU_CVD 0x04050000 #define BUF_SPU_OGM 0x04060000 #define BUF_SPU_CMML 0x04070000 +#define BUF_SPU_HDMV 0x04080000 /* demuxer block types: */ -- cgit v1.2.3 From 583ecbfa02687d27b987b55c2cb55529f86b4096 Mon Sep 17 00:00:00 2001 From: Petri Hintukainen Date: Mon, 31 Aug 2009 01:04:08 +0100 Subject: Cosmetics. Splitted detect_ts() from open_plugin(). --- src/demuxers/demux_ts.c | 54 ++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/demuxers/demux_ts.c b/src/demuxers/demux_ts.c index 98de1f9ea..a83ccd8ca 100644 --- a/src/demuxers/demux_ts.c +++ b/src/demuxers/demux_ts.c @@ -1758,7 +1758,7 @@ static void demux_ts_parse_packet (demux_ts_t*this) { /* * Discard packets that are obviously bad. */ - if (sync_byte != 0x47) { + if (sync_byte != SYNC_BYTE) { xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "demux error! invalid ts sync byte %.2x\n", sync_byte); return; @@ -2170,6 +2170,32 @@ static int demux_ts_get_optional_data(demux_plugin_t *this_gen, } } +static int detect_ts(uint8_t *buf, size_t len, int ts_size) +{ + int i, j; + int try_again, ts_detected = 0; + size_t packs = len / ts_size - 2; + + for (i = 0; i < ts_size; i++) { + try_again = 0; + if (buf[i] == SYNC_BYTE) { + for (j = 1; j < packs; j++) { + if (buf[i + j*ts_size] != SYNC_BYTE) { + try_again = 1; + break; + } + } + if (try_again == 0) { +#ifdef TS_LOG + printf ("demux_ts: found 0x47 pattern at offset %d\n", i); +#endif + ts_detected = 1; + } + } + } + + return ts_detected; +} static demux_plugin_t *open_plugin (demux_class_t *class_gen, xine_stream_t *stream, @@ -2182,33 +2208,11 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, case METHOD_BY_CONTENT: { uint8_t buf[2069]; - int i, j; - int try_again, ts_detected; - if (!_x_demux_read_header(input, buf, 2069)) + if (!_x_demux_read_header(input, buf, sizeof(buf))) return NULL; - ts_detected = 0; - - for (i = 0; i < 188; i++) { - try_again = 0; - if (buf[i] == 0x47) { - for (j = 1; j <= 10; j++) { - if (buf[i + j*188] != 0x47) { - try_again = 1; - break; - } - } - if (try_again == 0) { -#ifdef TS_LOG - printf ("demux_ts: found 0x47 pattern at offset %d\n", i); -#endif - ts_detected = 1; - } - } - } - - if (!ts_detected) + if (! detect_ts(buf, sizeof(buf), PKT_SIZE)) return NULL; } break; -- cgit v1.2.3 From e7bd06ed2951ee173a58528a88859c159bc3f5b0 Mon Sep 17 00:00:00 2001 From: Petri Hintukainen Date: Mon, 31 Aug 2009 01:12:24 +0100 Subject: PKT_SIZE --> this->pkt_size (in selected places) --- src/demuxers/demux_ts.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/demuxers/demux_ts.c b/src/demuxers/demux_ts.c index a83ccd8ca..c02aae1e8 100644 --- a/src/demuxers/demux_ts.c +++ b/src/demuxers/demux_ts.c @@ -288,6 +288,8 @@ typedef struct { int status; + int pkt_size; /* TS packet size */ + int blockSize; int rate; int media_num; @@ -1512,10 +1514,10 @@ static int sync_correct(demux_ts_t*this, uint8_t *buf, int32_t npkt_read) { xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "demux_ts: about to resync!\n"); for (p=0; p < npkt_read; p++) { - for(n=0; n < PKT_SIZE; n++) { + for(n=0; n < this->pkt_size; n++) { sync_ok = 1; for (i=0; i < MIN(MIN_SYNCS, npkt_read - p); i++) { - if (buf[n + ((i+p) * PKT_SIZE)] != SYNC_BYTE) { + if (buf[n + ((i+p) * this->pkt_size)] != SYNC_BYTE) { sync_ok = 0; break; } @@ -1527,13 +1529,13 @@ static int sync_correct(demux_ts_t*this, uint8_t *buf, int32_t npkt_read) { if (sync_ok) { /* Found sync, fill in */ - memmove(&buf[0], &buf[n + p * PKT_SIZE], - ((PKT_SIZE * (npkt_read - p)) - n)); + memmove(&buf[0], &buf[n + p * this->pkt_size], + ((this->pkt_size * (npkt_read - p)) - n)); read_length = this->input->read(this->input, - &buf[(PKT_SIZE * (npkt_read - p)) - n], - n + p * PKT_SIZE); + &buf[(this->pkt_size * (npkt_read - p)) - n], + n + p * this->pkt_size); /* FIXME: when read_length is not as required... we now stop demuxing */ - if (read_length != (n + p * PKT_SIZE)) { + if (read_length != (n + p * this->pkt_size)) { xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "demux_ts_tsync_correct: sync found, but read failed\n"); return 0; @@ -1575,15 +1577,15 @@ static unsigned char * demux_synchronise(demux_ts_t* this) { /* NEW: handle read returning less packets than NPKT_PER_READ... */ do { read_length = this->input->read(this->input, this->buf, - PKT_SIZE * NPKT_PER_READ); - if (read_length < 0 || read_length % PKT_SIZE) { + this->pkt_size * NPKT_PER_READ); + if (read_length < 0 || read_length % this->pkt_size) { xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "demux_ts: read returned %d bytes (not a multiple of %d!)\n", - read_length, PKT_SIZE); + read_length, this->pkt_size); this->status = DEMUX_FINISHED; return NULL; } - this->npkt_read = read_length / PKT_SIZE; + this->npkt_read = read_length / this->pkt_size; #ifdef TS_READ_STATS this->rstat[this->npkt_read]++; @@ -1610,7 +1612,7 @@ static unsigned char * demux_synchronise(demux_ts_t* this) { return NULL; } } - return_pointer = &(this->buf)[PKT_SIZE * this->packet_number]; + return_pointer = &(this->buf)[this->pkt_size * this->packet_number]; this->packet_number++; return return_pointer; } @@ -2305,7 +2307,9 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, /* dvb */ this->event_queue = xine_event_new_queue (this->stream); - + + this->pkt_size = PKT_SIZE; + this->numPreview=0; return &this->demux_plugin; -- cgit v1.2.3 From 40a7fb9d58287dc0cb27591836b892f2d10fd778 Mon Sep 17 00:00:00 2001 From: Petri Hintukainen Date: Mon, 31 Aug 2009 01:16:10 +0100 Subject: Support for BluRay/HDMV 192-byte TS packets --- src/demuxers/demux_ts.c | 63 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/src/demuxers/demux_ts.c b/src/demuxers/demux_ts.c index c02aae1e8..aaf19b1d2 100644 --- a/src/demuxers/demux_ts.c +++ b/src/demuxers/demux_ts.c @@ -33,6 +33,11 @@ * Date Author * ---- ------ * + * 8-Apr-2009 Petri Hintukainen + * - support for 192-byte packets (HDMV/BluRay) + * - support for audio inside PES PID 0xfd (HDMV/BluRay) + * - demux HDMV/BluRay bitmap subtitles + * * 28-Nov-2004 Mike Lampard * - Added support for PMT sections larger than 1 ts packet * @@ -172,9 +177,9 @@ #define SYNC_BYTE 0x47 #define MIN_SYNCS 3 -#define NPKT_PER_READ 100 +#define NPKT_PER_READ 96 // 96*188 = 94*192 -#define BUF_SIZE (NPKT_PER_READ * PKT_SIZE) +#define BUF_SIZE (NPKT_PER_READ * (PKT_SIZE + 4)) #define MAX_PES_BUF_SIZE 2048 @@ -227,6 +232,11 @@ #define PTS_AUDIO 0 #define PTS_VIDEO 1 +#undef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#undef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) + /* ** ** DATA STRUCTURES @@ -288,7 +298,9 @@ typedef struct { int status; + int hdmv; /* -1 = unknown, 0 = mpeg-ts, 1 = hdmv/m2ts */ int pkt_size; /* TS packet size */ + int pkt_offset; /* TS packet offset */ int blockSize; int rate; @@ -1517,7 +1529,7 @@ static int sync_correct(demux_ts_t*this, uint8_t *buf, int32_t npkt_read) { for(n=0; n < this->pkt_size; n++) { sync_ok = 1; for (i=0; i < MIN(MIN_SYNCS, npkt_read - p); i++) { - if (buf[n + ((i+p) * this->pkt_size)] != SYNC_BYTE) { + if (buf[this->pkt_offset + n + ((i+p) * this->pkt_size)] != SYNC_BYTE) { sync_ok = 0; break; } @@ -1554,6 +1566,32 @@ static int sync_detect(demux_ts_t*this, uint8_t *buf, int32_t npkt_read) { sync_ok = 1; + if (this->hdmv) { + this->pkt_size = PKT_SIZE + 4; + this->pkt_offset = 4; + for (i=0; i < MIN(MIN_SYNCS, npkt_read - 3); i++) { + if (buf[this->pkt_offset + i * this->pkt_size] != SYNC_BYTE) { + sync_ok = 0; + break; + } + } + if (sync_ok) { + if (this->hdmv < 0) { + /* fix npkt_read (packet size is 192, not 188) */ + this->npkt_read = npkt_read * PKT_SIZE / this->pkt_size; + } + this->hdmv = 1; + return sync_ok; + } + if (this->hdmv > 0) + return sync_correct(this, buf, npkt_read); + + /* plain ts */ + this->hdmv = 0; + this->pkt_size = PKT_SIZE; + this->pkt_offset = 0; + } + for (i=0; i < MIN(MIN_SYNCS, npkt_read); i++) { if (buf[i * PKT_SIZE] != SYNC_BYTE) { sync_ok = 0; @@ -1612,7 +1650,7 @@ static unsigned char * demux_synchronise(demux_ts_t* this) { return NULL; } } - return_pointer = &(this->buf)[this->pkt_size * this->packet_number]; + return_pointer = &(this->buf)[this->pkt_offset + this->pkt_size * this->packet_number]; this->packet_number++; return return_pointer; } @@ -2205,6 +2243,7 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, demux_ts_t *this; int i; + int hdmv = -1; switch (stream->content_detection_method) { @@ -2214,7 +2253,11 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, if (!_x_demux_read_header(input, buf, sizeof(buf))) return NULL; - if (! detect_ts(buf, sizeof(buf), PKT_SIZE)) + if (detect_ts(buf, sizeof(buf), PKT_SIZE)) + hdmv = 0; + else if (detect_ts(buf, sizeof(buf), PKT_SIZE+4)) + hdmv = 1; + else return NULL; } break; @@ -2222,6 +2265,11 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, case METHOD_BY_EXTENSION: { const char *const mrl = input->get_mrl (input); + if (_x_demux_check_extension (mrl, "m2ts mts")) + hdmv = 1; + else + hdmv = 0; + /* check extension */ const char *const extensions = class_gen->get_extensions (class_gen); @@ -2308,7 +2356,10 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, /* dvb */ this->event_queue = xine_event_new_queue (this->stream); - this->pkt_size = PKT_SIZE; + /* HDMV */ + this->hdmv = hdmv; + this->pkt_offset = (hdmv > 0) ? 4 : 0; + this->pkt_size = PKT_SIZE + this->pkt_offset; this->numPreview=0; -- cgit v1.2.3 From d7aedf3ba509ef563f1839cc2c3cdde449a46c7f Mon Sep 17 00:00:00 2001 From: Petri Hintukainen Date: Mon, 31 Aug 2009 01:22:19 +0100 Subject: Fixed audio. HDMV uses PES stream 0xfd instead of 0xbd. --- src/demuxers/demux_ts.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demuxers/demux_ts.c b/src/demuxers/demux_ts.c index aaf19b1d2..0bb70b74b 100644 --- a/src/demuxers/demux_ts.c +++ b/src/demuxers/demux_ts.c @@ -755,7 +755,7 @@ static int demux_ts_parse_pes_header (xine_t *xine, demux_ts_media *m, p += header_len + 9; packet_len -= header_len + 3; - if (stream_id == 0xbd) { + if (stream_id == 0xbd || stream_id == 0xfd /* HDMV */) { int spu_id; -- cgit v1.2.3 From d122ca934a7551a2a98108bb15fe5f2b15524cc2 Mon Sep 17 00:00:00 2001 From: Petri Hintukainen Date: Mon, 31 Aug 2009 01:25:47 +0100 Subject: added spu_type parameter to demux_send_special_spu_buf() --- src/demuxers/demux_ts.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/demuxers/demux_ts.c b/src/demuxers/demux_ts.c index 0bb70b74b..408ae43cf 100644 --- a/src/demuxers/demux_ts.c +++ b/src/demuxers/demux_ts.c @@ -460,12 +460,12 @@ static void check_newpts( demux_ts_t *this, int64_t pts, int video ) } /* Send a BUF_SPU_DVB to let xine know of that channel. */ -static void demux_send_special_spu_buf( demux_ts_t *this, int spu_channel ) +static void demux_send_special_spu_buf( demux_ts_t *this, uint32_t spu_type, int spu_channel ) { buf_element_t *buf; buf = this->video_fifo->buffer_pool_alloc( this->video_fifo ); - buf->type = BUF_SPU_DVB|spu_channel; + buf->type = spu_type|spu_channel; buf->content = buf->mem; buf->size = 0; this->video_fifo->put( this->video_fifo, buf ); @@ -1427,7 +1427,7 @@ printf("Program Number is %i, looking for %i\n",program_number,this->program_num lang->media_index = this->media_num; this->media[this->media_num].type = no; demux_ts_pes_new(this, this->media_num, pid, this->video_fifo, stream[0]); - demux_send_special_spu_buf( this, no ); + demux_send_special_spu_buf( this, BUF_SPU_DVB, no ); #ifdef TS_LOG printf("demux_ts: DVBSUB: pid 0x%.4x: %s page %ld %ld type %2.2x\n", pid, lang->desc.lang, -- cgit v1.2.3 From 70396b8a99ef58182c493dab0348286fdb25e369 Mon Sep 17 00:00:00 2001 From: Petri Hintukainen Date: Mon, 31 Aug 2009 01:33:27 +0100 Subject: Demux HDMV/BluRay bitmap subtitles --- src/demuxers/demux_ts.c | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/demuxers/demux_ts.c b/src/demuxers/demux_ts.c index 408ae43cf..872d4a85f 100644 --- a/src/demuxers/demux_ts.c +++ b/src/demuxers/demux_ts.c @@ -223,8 +223,9 @@ ISO_14496_PART2_VIDEO = 0x10, /* ISO/IEC 14496-2 Visual (MPEG-4) */ ISO_14496_PART3_AUDIO = 0x11, /* ISO/IEC 14496-3 Audio with LATM transport syntax */ ISO_14496_PART10_VIDEO = 0x1b, /* ISO/IEC 14496-10 Video (MPEG-4 part 10/AVC, aka H.264) */ - STREAM_VIDEO_MPEG = 0x80, - STREAM_AUDIO_AC3 = 0x81, + STREAM_VIDEO_MPEG = 0x80, + STREAM_AUDIO_AC3 = 0x81, + STREAM_SPU_BITMAP_HDMV = 0x90, } streamType; #define WRAP_THRESHOLD 270000 @@ -755,6 +756,17 @@ static int demux_ts_parse_pes_header (xine_t *xine, demux_ts_media *m, p += header_len + 9; packet_len -= header_len + 3; + if (m->descriptor_tag == STREAM_SPU_BITMAP_HDMV) { + long payload_len = ((buf[4] << 8) | buf[5]) - header_len - 3; + + m->content = p; + m->size = packet_len; + m->type |= BUF_SPU_HDMV; + m->buf->decoder_info[2] = payload_len; + return 1; + + } else + if (stream_id == 0xbd || stream_id == 0xfd /* HDMV */) { int spu_id; @@ -1440,6 +1452,29 @@ printf("Program Number is %i, looking for %i\n",program_number,this->program_num } break; + case STREAM_SPU_BITMAP_HDMV: + if (this->hdmv > 0) { + if (pid >= 0x1200 && pid < 0x1300) { + /* HDMV Presentation Graphics / SPU */ + demux_ts_spu_lang *lang = &this->spu_langs[this->spu_langs_count]; + + memset(lang->desc.lang, 0, sizeof(lang->desc.lang)); + /*memcpy(lang->desc.lang, &stream[pos], 3);*/ + /*lang->desc.lang[3] = 0;*/ + lang->pid = pid; + lang->media_index = this->media_num; + this->media[this->media_num].type = this->spu_langs_count; + demux_ts_pes_new(this, this->media_num, pid, this->video_fifo, stream[0]); + demux_send_special_spu_buf( this, BUF_SPU_HDMV, this->spu_langs_count ); + this->spu_langs_count++; +#ifdef TS_PMT_LOG + printf("demux_ts: HDMV subtitle stream_type: 0x%.2x pid: 0x%.4x\n", + stream[0], pid); +#endif + break; + } + } + /* fall thru */ default: /* This following section handles all the cases where the audio track info is stored in PMT user info with stream id >= 0x80 -- cgit v1.2.3 From affa7abe6c5509149a8fffff5bcaacaf2be3f33e Mon Sep 17 00:00:00 2001 From: Petri Hintukainen Date: Mon, 31 Aug 2009 01:34:16 +0100 Subject: Fixed selecting HDMV SPU track --- src/demuxers/demux_ts.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/demuxers/demux_ts.c b/src/demuxers/demux_ts.c index 872d4a85f..e52c4c765 100644 --- a/src/demuxers/demux_ts.c +++ b/src/demuxers/demux_ts.c @@ -519,6 +519,10 @@ static void demux_ts_update_spu_channel(demux_ts_t *this) #endif } + if ((this->media[this->spu_media].type & BUF_MAJOR_MASK) == BUF_SPU_HDMV) { + buf->type = BUF_SPU_HDMV; + } + this->video_fifo->put(this->video_fifo, buf); } -- cgit v1.2.3 From 577025283cdbfc7588c093c4530a5394f19d9f5b Mon Sep 17 00:00:00 2001 From: Petri Hintukainen Date: Mon, 31 Aug 2009 02:05:53 +0100 Subject: BluRay subtitles decoder plugin --- src/libspuhdmv/xine_hdmv_decoder.c | 967 +++++++++++++++++++++++++++++++++++++ 1 file changed, 967 insertions(+) create mode 100644 src/libspuhdmv/xine_hdmv_decoder.c diff --git a/src/libspuhdmv/xine_hdmv_decoder.c b/src/libspuhdmv/xine_hdmv_decoder.c new file mode 100644 index 000000000..358cb0b72 --- /dev/null +++ b/src/libspuhdmv/xine_hdmv_decoder.c @@ -0,0 +1,967 @@ +/* + * Copyright (C) 2000-2009 the xine project + * + * Copyright (C) 2009 Petri Hintukainen + * + * This file is part of xine, a unix video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * Decoder for HDMV/BluRay bitmap subtitles + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +# include "xine_internal.h" +# include "buffer.h" +# include "xineutils.h" +# include "video_out.h" +# include "video_overlay.h" +#else +# include +# include +# include +# include +# include +#endif + +#define TRACE(x...) printf(x) +/*#define TRACE(x...) */ +#define ERROR(x...) fprintf(stderr, x) +/*#define ERROR(x...) lprintf(x) */ + +/* + * cached palette (xine-lib format) + */ +typedef struct subtitle_clut_s subtitle_clut_t; +struct subtitle_clut_s { + uint8_t id; + uint32_t color[256]; + uint8_t trans[256]; + subtitle_clut_t *next; +}; + +/* + * cached RLE image (xine-lib format) + */ +typedef struct subtitle_object_s subtitle_object_t; +struct subtitle_object_s { + uint16_t id; + uint16_t xpos, ypos; + uint16_t width, height; + + rle_elem_t *rle; + uint num_rle; + size_t data_size; + + uint8_t *raw_data; /* partial RLE data in HDMV format */ + size_t raw_data_len; + size_t raw_data_size; + + subtitle_object_t *next; +}; + +/* + * Window definition + */ +typedef struct window_def_s window_def_t; +struct window_def_s { + uint8_t id; + uint16_t xpos, ypos; + uint16_t width, height; + + window_def_t *next; +}; + + +/* + * decoded SPU + */ +typedef struct composition_object_s composition_object_t; +struct composition_object_s { + uint8_t window_id_ref; + uint8_t object_id_ref; + + uint16_t xpos, ypos; + + uint8_t forced_flag; + uint8_t cropped_flag; + uint16_t crop_horiz_pos, crop_vert_pos; + uint16_t crop_width, crop_height; + + composition_object_t *next; +}; + +/* + * segment_buffer_t + * + * assemble and decode segments + */ + +typedef struct { + /* current segment */ + int segment_len; /* length of current segment (without 3-byte header) */ + uint8_t segment_type; /* current segment type */ + uint8_t *segment_data; /* pointer to current segment payload */ + uint8_t *segment_end; /* pointer to last byte + 1 of current segment */ + uint8_t error; /* boolean: buffer overflow etc. */ + + /* accumulated data */ + uint8_t *buf; /* */ + size_t len; /* count of unprocessed bytes */ + size_t data_size; /* allocated buffer size */ +} segment_buffer_t; + +/* + * mgmt + */ + +static segment_buffer_t *segbuf_init(void) +{ + segment_buffer_t *buf = calloc(1, sizeof(segment_buffer_t)); + return buf; +} + +static void segbuf_dispose(segment_buffer_t *buf) +{ + if (buf->buf) + free (buf->buf); + free (buf); +} + +static void segbuf_reset(segment_buffer_t *buf) +{ + buf->segment_end = buf->segment_data = buf->buf; + buf->len = 0; + buf->segment_len = -1; + buf->segment_type = 0; + buf->error = 0; +} + +/* + * assemble, parse + */ + +static void segbuf_parse_segment_header(segment_buffer_t *buf) +{ + if (buf->len > 2) { + buf->segment_type = buf->buf[0]; + buf->segment_len = (buf->buf[1] << 8) | buf->buf[2]; + buf->segment_data = buf->buf + 3; + buf->segment_end = buf->segment_data + buf->segment_len; + buf->error = 0; + + if ( buf->segment_type < 0x14 || + ( buf->segment_type > 0x18 && + buf->segment_type != 0x80)) { + ERROR("unknown segment type, resetting\n"); + segbuf_reset(buf); + } + } else { + buf->segment_len = -1; + buf->error = 1; + } +} + +static void segbuf_fill(segment_buffer_t *buf, uint8_t *data, size_t len) +{ + if (buf->len + len > buf->data_size) { + buf->data_size = buf->len + len; + if (buf->buf) + buf->buf = realloc(buf->buf, buf->data_size); + else + buf->buf = malloc(buf->data_size); + } + + memcpy(buf->buf + buf->len, data, len); + buf->len += len; + + segbuf_parse_segment_header(buf); +} + +static int segbuf_segment_complete(segment_buffer_t *buf) +{ + return (buf->segment_len >= 0) && (buf->len >= buf->segment_len + 3); +} + +static void segbuf_skip_segment(segment_buffer_t *buf) +{ + if (segbuf_segment_complete (buf)) { + buf->len -= buf->segment_len + 3; + if (buf->len > 0) + memmove(buf->buf, buf->buf + buf->segment_len + 3, buf->len); + + segbuf_parse_segment_header(buf); + + TRACE(" skip_segment: %d bytes left\n", (uint)buf->len); + } else { + ERROR(" skip_segment: ERROR - %d bytes queued, %d required\n", + (uint)buf->len, buf->segment_len); + segbuf_reset (buf); + } +} + +/* + * access segment data + */ + +static uint8_t segbuf_segment_type(segment_buffer_t *buf) +{ + return buf->segment_type; +} + +static size_t segbuf_data_length(segment_buffer_t *buf) +{ + ssize_t val = buf->segment_end - buf->segment_data; + if (val < 0) val = 0; + return (size_t)val; +} + +static uint8_t segbuf_get_u8(segment_buffer_t *buf) +{ + if (!(buf->error = ++buf->segment_data > buf->segment_end)) + return buf->segment_data[-1]; + ERROR("segbuf_get_u8: read failed (end of segment reached) !"); + return 0; +} + +static uint16_t segbuf_get_u16(segment_buffer_t *buf) +{ + return (segbuf_get_u8(buf) << 8) | segbuf_get_u8(buf); +} + +static uint32_t segbuf_get_u24(segment_buffer_t *buf) +{ + return (segbuf_get_u8(buf) << 16) | (segbuf_get_u8(buf) << 8) | segbuf_get_u8(buf); +} + +static uint8_t *segbuf_get_string(segment_buffer_t *buf, size_t len) +{ + if (len > 0) { + uint8_t *val = buf->segment_data; + buf->segment_data += len; + if (buf->segment_data <= buf->segment_end) + return val; + } + ERROR("segbuf_get_string(%d): read failed (end of segment reached) !", (int)len); + buf->error = 1; + return NULL; +} + +/* + * decode segments + */ + +static subtitle_clut_t *segbuf_decode_palette(segment_buffer_t *buf) +{ + uint8_t palette_id = segbuf_get_u8 (buf); + uint8_t palette_version_number = segbuf_get_u8 (buf); + + size_t len = segbuf_data_length(buf); + size_t entries = len / 5; + int i; + + if (buf->error) + return NULL; + + if (len % 5) { + ERROR(" decode_palette: segment size error (%d ; expected %d for %d entries)\n", + (uint)len, (uint)(5 * entries), (uint)entries); + return NULL; + } + TRACE("decode_palette: %d items (id %d, version %d)\n", + (uint)entries, palette_id, palette_version_number); + + /* convert to xine-lib clut */ + subtitle_clut_t *clut = calloc(1, sizeof(subtitle_clut_t)); + clut->id = palette_id; + + for (i = 0; i < entries; i++) { + uint8_t index = segbuf_get_u8 (buf); + uint8_t Y = segbuf_get_u8 (buf); + uint8_t Cr = segbuf_get_u8 (buf); + uint8_t Cb = segbuf_get_u8 (buf); + uint8_t alpha = segbuf_get_u8 (buf); + clut->color[index] = (Y << 16) | (Cr << 8) | Cb; + clut->trans[index] = alpha >> 4; + } + + return clut; +} + +static int segbuf_decode_rle(segment_buffer_t *buf, subtitle_object_t *obj) +{ + int x = 0, y = 0; + int rle_size = sizeof(rle_elem_t) * obj->width / 16 * obj->height + 1; + rle_elem_t *rlep = malloc(rle_size); + + free (obj->rle); + obj->rle = rlep; + obj->data_size = rle_size; + obj->num_rle = 0; + + /* convert to xine-lib rle format */ + while (y < obj->height && !buf->error) { + + /* decode RLE element */ + uint8_t byte = segbuf_get_u8 (buf); + if (byte != 0) { + rlep->color = byte; + rlep->len = 1; + } else { + byte = segbuf_get_u8 (buf); + if (!(byte & 0x80)) { + rlep->color = 0; + if (!(byte & 0x40)) + rlep->len = byte & 0x3f; + else + rlep->len = ((byte & 0x3f) << 8) | segbuf_get_u8 (buf); + } else { + if (!(byte & 0x40)) + rlep->len = byte & 0x3f; + else + rlep->len = ((byte & 0x3f) << 8) | segbuf_get_u8 (buf); + rlep->color = segbuf_get_u8 (buf); + } + } + + /* move to next element */ + if (rlep->len > 0) { + x += rlep->len; + rlep++; + obj->num_rle ++; + } else { + /* end of line marker (00 00) */ + if (x < obj->width) { + rlep->len = obj->width - x; + rlep->color = 0xff; + rlep++; + obj->num_rle ++; + } + x = 0; + y++; + } + + /* grow allocated RLE data size ? */ + if (obj->data_size <= (obj->num_rle + 1) * sizeof(rle_elem_t)) { + obj->data_size *= 2; + obj->rle = realloc(obj->rle, obj->data_size); + rlep = obj->rle + obj->num_rle; + } + } + + return buf->error; +} + +static subtitle_object_t *segbuf_decode_object(segment_buffer_t *buf) +{ + uint8_t object_id = segbuf_get_u16(buf); + uint8_t version = segbuf_get_u8 (buf); + uint8_t seq_desc = segbuf_get_u8 (buf); + + TRACE(" decode_object: object_id %d, version %d, seq 0x%x\n", + object_id, version, seq_desc); + + //LIST_FIND(); + subtitle_object_t *obj = calloc(1, sizeof(subtitle_object_t)); + obj->id = object_id; + + if (seq_desc & 0x80) { + + uint32_t data_len = segbuf_get_u24(buf); + obj->width = segbuf_get_u16(buf); + obj->height = segbuf_get_u16(buf); + + TRACE(" object length %d bytes, size %dx%d\n", data_len, obj->width, obj->height); + + segbuf_decode_rle (buf, obj); + + if (buf->error) { + free(obj); + return NULL; + } + + } else { + ERROR(" TODO: APPEND RLE, length %d bytes\n", buf->segment_len - 4); + /* TODO */ + free(obj); + return NULL; + } + + return obj; +} + +static window_def_t *segbuf_decode_window_definition(segment_buffer_t *buf) +{ + window_def_t *wnd = calloc(1, sizeof(window_def_t)); + + uint8_t a = segbuf_get_u8 (buf); + wnd->id = segbuf_get_u8 (buf); + wnd->xpos = segbuf_get_u16 (buf); + wnd->ypos = segbuf_get_u16 (buf); + wnd->width = segbuf_get_u16 (buf); + wnd->height = segbuf_get_u16 (buf); + + TRACE(" window: [%02x %d] %d,%d %dx%d\n", a, + wnd->id, wnd->xpos, wnd->ypos, wnd->width, wnd->height); + + if (buf->error) { + free(wnd); + return NULL; + } + + return wnd; +} + +static int segbuf_decode_video_descriptor(segment_buffer_t *buf) +{ + uint16_t width = segbuf_get_u16(buf); + uint16_t height = segbuf_get_u16(buf); + uint8_t frame_rate = segbuf_get_u8 (buf); + + TRACE(" video_descriptor: %dx%d fps %d\n", width, height, frame_rate); + return buf->error; +} + +typedef struct composition_descriptor_s composition_descriptor_t; +struct composition_descriptor_s { + uint16_t number; + uint8_t state; +}; + +static int segbuf_decode_composition_descriptor(segment_buffer_t *buf, composition_descriptor_t *descr) +{ + descr->number = segbuf_get_u16(buf); + descr->state = segbuf_get_u8 (buf); + + TRACE(" composition_descriptor: number %d, state %d\n", descr->number, descr->state); + return buf->error; +} + +static composition_object_t *segbuf_decode_composition_object(segment_buffer_t *buf) +{ + composition_object_t *cobj = calloc(1, sizeof(composition_object_t)); + + cobj->object_id_ref = segbuf_get_u16 (buf); + cobj->window_id_ref = segbuf_get_u8 (buf); + uint8_t tmp = segbuf_get_u8 (buf); + cobj->cropped_flag = !!(tmp & 0x80); + cobj->forced_flag = !!(tmp & 0x40); + cobj->xpos = segbuf_get_u16 (buf); + cobj->ypos = segbuf_get_u16 (buf); + if (cobj->cropped_flag) { + /* x,y where to take the image from */ + cobj->crop_horiz_pos = segbuf_get_u8 (buf); + cobj->crop_vert_pos = segbuf_get_u8 (buf); + /* size of the cropped image */ + cobj->crop_width = segbuf_get_u8 (buf); + cobj->crop_height = segbuf_get_u8 (buf); + } + + if (buf->error) { + free(cobj); + return NULL; + } + + TRACE(" composition_object: id: %d, win: %d, position %d,%d crop %d forced %d\n", + cobj->object_id_ref, cobj->window_id_ref, cobj->xpos, cobj->ypos, + cobj->cropped_flag, cobj->forced_flag); + + return cobj; +} + +static rle_elem_t *copy_crop_rle(subtitle_object_t *obj, composition_object_t *cobj) +{ + /* TODO: exec cropping here (w,h sized image from pos x,y) */ + + rle_elem_t *rle = calloc (obj->num_rle, sizeof(rle_elem_t)); + memcpy (rle, obj->rle, obj->num_rle * sizeof(rle_elem_t)); + return rle; +} + + +/* + * xine plugin + */ + +typedef struct { + spu_decoder_class_t decoder_class; +} spuhdmv_class_t; + +typedef struct spuhdmv_decoder_s { + spu_decoder_t spu_decoder; + + spuhdmv_class_t *class; + xine_stream_t *stream; + + segment_buffer_t *buf; + + subtitle_clut_t *cluts; + subtitle_object_t *objects; + window_def_t *windows; + int overlay_handles[MAX_OBJECTS]; + + int64_t pts; + +} spuhdmv_decoder_t; + +#define LIST_REPLACE_OLD(type, list, obj) \ + do { \ + /* insert to list */ \ + obj->next = list; \ + list = obj; \ +\ + /* remove old */ \ + type *i = list; \ + while (i->next && i->next->id != obj->id) \ + i = i->next; \ + if (i->next) { \ + void *tmp = (void*)i->next; \ + i->next = i->next->next; \ + free(tmp); \ + } \ + } while (0); + +#define LIST_REPLACE(list, obj) \ + do { \ + uint id = obj->id; \ + \ + /* insert to list */ \ + obj->next = list; \ + list = obj; \ + \ + /* remove old */ \ + while (obj->next && obj->next->id != id) \ + obj = obj->next; \ + if (obj->next) { \ + void *tmp = (void*)obj->next; \ + obj->next = obj->next->next; \ + free(tmp); \ + } \ + } while (0); + +#define LIST_DESTROY(list) \ + while (list) { \ + void *tmp = (void*)list; \ + list = list->next; \ + free (tmp); \ + } + +static int decode_palette(spuhdmv_decoder_t *this) +{ + /* decode */ + subtitle_clut_t *clut = segbuf_decode_palette(this->buf); + if (!clut) + return 1; + + LIST_REPLACE (this->cluts, clut); + + return 0; +} + +static int decode_object(spuhdmv_decoder_t *this) +{ + /* decode */ + subtitle_object_t *obj = segbuf_decode_object(this->buf); + if (!obj) + return 1; + + LIST_REPLACE (this->objects, obj); + + return 0; +} + +static int decode_window_definition(spuhdmv_decoder_t *this) +{ + /* decode */ + window_def_t *wnd = segbuf_decode_window_definition (this->buf); + if (!wnd) + return 1; + + LIST_REPLACE (this->windows, wnd); + + return 0; +} + +static int show_overlay(spuhdmv_decoder_t *this, composition_object_t *cobj, uint palette_id_ref, + int overlay_index, int64_t pts) +{ + video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager(this->stream->video_out); + metronom_t *metronom = this->stream->metronom; + video_overlay_event_t event = {0}; + vo_overlay_t overlay = {0}; + + /* find palette */ + subtitle_clut_t *clut = this->cluts; + while (clut && clut->id != palette_id_ref) + clut = clut->next; + if (!clut) { + ERROR(" fill_overlay: clut %d not found !\n", palette_id_ref); + return -1; + } + + /* copy palette to xine overlay */ + overlay.rgb_clut = 0; + memcpy(overlay.color, clut->color, sizeof(uint32_t) * 256); + memcpy(overlay.trans, clut->trans, sizeof(uint8_t) * 256); + + /* find RLE image */ + subtitle_object_t *obj = this->objects; + while (obj && obj->id != cobj->object_id_ref) + obj = obj->next; + if (!obj) { + ERROR(" fill_overlay: object %d not found !\n", cobj->object_id_ref); + return -1; + } + + /* find window */ + window_def_t *wnd = this->windows; + while (wnd && wnd->id != cobj->window_id_ref) + wnd = wnd->next; + if (!wnd) { + ERROR(" fill_overlay: window %d not found !\n", cobj->window_id_ref); + return -1; + } + + /* copy and crop RLE image to xine overlay */ + overlay.width = obj->width; + overlay.height = obj->height; + + overlay.rle = copy_crop_rle (obj, cobj); + overlay.num_rle = obj->num_rle; + overlay.data_size = obj->num_rle * sizeof(rle_elem_t); + + /* */ + + overlay.x = /*wnd->xpos +*/ cobj->xpos; + overlay.y = /*wnd->ypos +*/ cobj->ypos; + + overlay.unscaled = 0; + overlay.hili_top = -1; + overlay.hili_bottom = -1; + overlay.hili_left = -1; + overlay.hili_right = -1; + + TRACE(" -> overlay: %d,%d %dx%d\n", + overlay.x, overlay.y, overlay.width, overlay.height); + + + /* set timings */ + + if (pts > 0) + event.vpts = metronom->got_spu_packet (metronom, pts); + else + event.vpts = 0; + + + /* generate SHOW event */ + + this->stream->video_out->enable_ovl(this->stream->video_out, 1); + + if (this->overlay_handles[overlay_index] < 0) + this->overlay_handles[overlay_index] = ovl_manager->get_handle(ovl_manager, 0); + + event.event_type = OVERLAY_EVENT_SHOW; + event.object.handle = this->overlay_handles[overlay_index]; + event.object.overlay = &overlay; + event.object.object_type = 0; /* subtitle */ + + ovl_manager->add_event (ovl_manager, (void *)&event); + + obj->rle = NULL; + + return 0; +} + +typedef struct presentation_segment_s presentation_segment_t; +struct presentation_segment_s { + composition_descriptor_t comp_descr; + + uint8_t palette_update_flag; + uint8_t palette_id_ref; + uint8_t object_number; + + composition_object_t *comp_objs; + + presentation_segment_t *next; + + int64_t pts; +}; + +static void show_overlays(spuhdmv_decoder_t *this, presentation_segment_t *pseg) +{ + composition_object_t *cobj = pseg->comp_objs; + int i; + + for (i = 0; i < pseg->object_number; i++) { + if (!cobj) { + ERROR("show_overlays: composition object %d missing !\n", i); + } else { + show_overlay(this, cobj, pseg->palette_id_ref, i, pseg->pts); + cobj = cobj->next; + } + } +} + +static void hide_overlays(spuhdmv_decoder_t *this, int64_t pts) +{ + video_overlay_event_t event = {0}; + int i = 0; + + while (this->overlay_handles[i] >= 0) { + TRACE(" -> HIDE %d\n", i); + + video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager(this->stream->video_out); + metronom_t *metronom = this->stream->metronom; + + event.object.handle = this->overlay_handles[i]; + if (this) + event.vpts = metronom->got_spu_packet (metronom, pts); + else + event.vpts = 0; + event.event_type = OVERLAY_EVENT_HIDE; + event.object.overlay = NULL; + ovl_manager->add_event (ovl_manager, (void *)&event); + + //this->overlay_handles[i] = -1; + i++; + } +} + +static int decode_presentation_segment(spuhdmv_decoder_t *this) +{ + presentation_segment_t p = {}; + segment_buffer_t *buf = this->buf; + int index; + + segbuf_decode_video_descriptor (this->buf); + segbuf_decode_composition_descriptor (this->buf, &p.comp_descr); + + p.palette_update_flag = !!((segbuf_get_u8(buf)) & 0x80); + p.palette_id_ref = segbuf_get_u8 (buf); + p.object_number = segbuf_get_u8 (buf); + + TRACE(" presentation_segment: object_number %d, palette %d\n", + p.object_number, p.palette_id_ref); + + p.pts = this->pts; /* !! todo - use it ? */ + + for (index = 0; index < p.object_number; index++) { + composition_object_t *cobj = segbuf_decode_composition_object (this->buf); + cobj->next = p.comp_objs; + p.comp_objs = cobj; + } + + if (!p.comp_descr.state) { + hide_overlays (this, this->pts); + } else { + show_overlays (this, &p); + LIST_DESTROY (p.comp_objs); + } + + return buf->error; +} + +static void decode_segment(spuhdmv_decoder_t *this) +{ + TRACE("*** new segment, pts %010ld: 0x%02x (%8d bytes)", + this->pts, (uint)this->buf->segment_type, (uint)this->buf->segment_len); + + switch (this->buf->segment_type) { + case 0x14: + TRACE(" segment: PALETTE\n"); + decode_palette(this); + break; + case 0x15: + TRACE(" segment: OBJECT\n"); + decode_object(this); + break; + case 0x16: + TRACE(" segment: PRESENTATION SEGMENT\n"); + decode_presentation_segment(this); + break; + case 0x17: + TRACE(" segment: WINDOW DEFINITION\n"); + decode_window_definition(this); + break; + case 0x18: + TRACE(" segment: INTERACTIVE\n"); + break; + case 0x80: + TRACE(" segment: END OF DISPLAY\n"); + { + int64_t pts = xine_get_current_vpts(this->stream) - + this->stream->metronom->get_option(this->stream->metronom, + METRONOM_VPTS_OFFSET); + TRACE(" * current pts = %ld\n", pts); + } + + break; + default: + ERROR(" segment type 0x%x unknown, skipping\n", this->buf->segment_type); + break; + } + if (this->buf->error) { + ERROR("*** DECODE ERROR ***\n"); + } +} + +static void close_osd(spuhdmv_decoder_t *this) +{ + video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager (this->stream->video_out); + + int i = 0; + while (this->overlay_handles[i] >= 0) { + ovl_manager->free_handle(ovl_manager, this->overlay_handles[i]); + this->overlay_handles[i] = -1; + i++; + } +} + +static void spudec_decode_data (spu_decoder_t * this_gen, buf_element_t * buf) +{ + spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen; + + if ((buf->type & 0xffff0000) != BUF_SPU_HDMV) + return; + + if (buf->size < 1) + return; + + if (buf->pts) + this->pts = buf->pts; + +#ifdef DUMP_SPU_DATA + int i; + for(i = 0; i < buf->size; i++) + printf(" %02x", buf->content[i]); + printf("\n"); +#endif + + segbuf_fill(this->buf, buf->content, buf->size); + + while (segbuf_segment_complete(this->buf)) { + decode_segment(this); + segbuf_skip_segment(this->buf); + } +} + +static void spudec_reset (spu_decoder_t * this_gen) +{ + spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen; + + if (this->buf) + segbuf_reset(this->buf); + + LIST_DESTROY (this->cluts); + LIST_DESTROY (this->objects); + LIST_DESTROY (this->windows); + + close_osd(this); +} + +static void spudec_discontinuity (spu_decoder_t *this_gen) +{ + spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen; + + close_osd(this); +} + +static void spudec_dispose (spu_decoder_t *this_gen) +{ + spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen; + + close_osd (this); + segbuf_dispose (this->buf); + + LIST_DESTROY (this->cluts); + LIST_DESTROY (this->objects); + LIST_DESTROY (this->windows); + + free (this); +} + +static spu_decoder_t *open_plugin (spu_decoder_class_t *class_gen, xine_stream_t *stream) +{ + spuhdmv_decoder_t *this; + + this = (spuhdmv_decoder_t *) calloc(1, sizeof (spuhdmv_decoder_t)); + + this->spu_decoder.decode_data = spudec_decode_data; + this->spu_decoder.reset = spudec_reset; + this->spu_decoder.discontinuity = spudec_discontinuity; + this->spu_decoder.dispose = spudec_dispose; + this->spu_decoder.get_interact_info = NULL; + this->spu_decoder.set_button = NULL; + this->stream = stream; + this->class = (spuhdmv_class_t *) class_gen; + + this->buf = segbuf_init(); + + memset(this->overlay_handles, 0xff, sizeof(this->overlay_handles)); /* --> -1 */ + + return &this->spu_decoder; +} + +static char *get_identifier (spu_decoder_class_t *this) +{ + return "spuhdmv"; +} + +static char *get_description (spu_decoder_class_t *this) +{ + return "HDMV/BluRay bitmap SPU decoder plugin"; +} + +static void dispose_class (spu_decoder_class_t *this) +{ + free (this); +} + +static void *init_plugin (xine_t *xine, void *data) +{ + spuhdmv_class_t *this; + + this = calloc(1, sizeof (spuhdmv_class_t)); + + this->decoder_class.open_plugin = open_plugin; + this->decoder_class.get_identifier = get_identifier; + this->decoder_class.get_description = get_description; + this->decoder_class.dispose = dispose_class; + + return this; +} + +/* plugin catalog information */ +static uint32_t supported_types[] = { BUF_SPU_HDMV, 0 }; + +static const decoder_info_t dec_info_data = { + supported_types, /* supported types */ + 5 /* priority */ +}; + +const plugin_info_t xine_plugin_info[] EXPORTED = { + /* type, API, "name", version, special_info, init_function */ + { PLUGIN_SPU_DECODER, 16, "spuhdmv", XINE_VERSION_CODE, &dec_info_data, &init_plugin }, + { PLUGIN_NONE, 0, "", 0, NULL, NULL } +}; -- cgit v1.2.3 From 55e15562a270c865b65866698bcc4a2a90f9d52b Mon Sep 17 00:00:00 2001 From: Darren Salt Date: Mon, 31 Aug 2009 02:06:01 +0100 Subject: Connect up the BluRay subtitles decoder. --HG-- rename : src/libspudvb/Makefile.am => src/libspuhdmv/Makefile.am --- configure.ac | 1 + src/Makefile.am | 1 + src/libspuhdmv/Makefile.am | 9 +++++++++ 3 files changed, 11 insertions(+) create mode 100644 src/libspuhdmv/Makefile.am diff --git a/configure.ac b/configure.ac index 5e87d63e0..c9a8557df 100644 --- a/configure.ac +++ b/configure.ac @@ -2783,6 +2783,7 @@ src/libspudec/Makefile src/libspucc/Makefile src/libspucmml/Makefile src/libspudvb/Makefile +src/libspuhdmv/Makefile src/libsputext/Makefile src/libw32dll/Makefile src/libw32dll/wine/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 249bbc2b6..5d21a97eb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,6 +17,7 @@ SUBDIRS = \ libspucc \ libspucmml \ libspudvb \ + libspuhdmv \ libsputext \ libdts \ libmad \ diff --git a/src/libspuhdmv/Makefile.am b/src/libspuhdmv/Makefile.am new file mode 100644 index 000000000..15a029f8a --- /dev/null +++ b/src/libspuhdmv/Makefile.am @@ -0,0 +1,9 @@ +include $(top_builddir)/misc/Makefile.plugins +include $(top_srcdir)/misc/Makefile.common + +xineplug_LTLIBRARIES = xineplug_decode_spuhdmv.la + +xineplug_decode_spuhdmv_la_SOURCES = xine_hdmv_decoder.c +xineplug_decode_spuhdmv_la_LIBADD = $(XINE_LIB) $(PTHREAD_LIBS) $(LTLIBINTL) +xineplug_decode_spuhdmv_la_CFLAGS = $(VISIBILITY_FLAG) +xineplug_decode_spuhdmv_la_LDFLAGS = $(xineplug_ldflags) -- cgit v1.2.3 From 16a2e010084afeca017005cee5ae6155e69f94c6 Mon Sep 17 00:00:00 2001 From: Darren Salt Date: Sat, 10 Oct 2009 14:48:17 +0100 Subject: Changelog entry for BluRay/HDMV support. --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 0dccfbb78..588583471 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,7 @@ xine-lib (1.1.17) 2009-??-?? * Fixed int-to-float conversion in the JACK output plugin. * Work around MOD files with reported length == 0. * Reworked Matroska demuxer. Now reads files created by mkvmerge 2.7.0. + * Support BluRay/HDMV streams & subtitles. xine-lib (1.1.16.3) 2009-04-03 * Security fixes: -- cgit v1.2.3