From 0f155921b046c9e6cfed3fe601aa891c2d2a8b16 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 19 Aug 2012 18:01:30 +0200 Subject: core: manage tracks in the frontend Introduce a general track struct for every audio/video/subtitle track known to the frontend. External files (subtitles) are now represented as tracks too. This mainly serves to clean up the subtitle selection code: now every subtitle is simply a track, instead of using a messy numbering that goes by subtitle type (as it was stored in the global_sub_pos field). The mplayer fontend will list external subtitle files as additional tracks. The timeline code now tries to match the exact demuxer IDs of all tracks. This may cause problems when Matroska files with different track numberings are used with EDL timelines. Change demux_lavf not to set demuxer IDs, since most time they are not set. --- command.c | 650 ++++++----------------------------------- input/input.c | 4 - input/input.h | 4 - libmpdemux/demux_lavf.c | 3 - libmpdemux/demuxer.c | 179 ++++-------- libmpdemux/demuxer.h | 28 +- libmpdemux/stheader.h | 7 +- mp_core.h | 76 +++-- mplayer.c | 751 ++++++++++++++++++++++++++++++------------------ sub/av_sub.c | 5 + sub/av_sub.h | 1 + sub/sub.c | 3 +- 12 files changed, 694 insertions(+), 1017 deletions(-) diff --git a/command.c b/command.c index edb0e5a77a..065802b6d0 100644 --- a/command.c +++ b/command.c @@ -105,86 +105,6 @@ static void rescale_input_coordinates(struct MPContext *mpctx, int ix, int iy, vo->dheight, vo_fs); } -static int sub_pos_by_source(MPContext *mpctx, int src) -{ - int i, cnt = 0; - if (src >= SUB_SOURCES || mpctx->sub_counts[src] == 0) - return -1; - for (i = 0; i < src; i++) - cnt += mpctx->sub_counts[i]; - return cnt; -} - -static int sub_source_and_index_by_pos(MPContext *mpctx, int *pos) -{ - int start = 0; - int i; - for (i = 0; i < SUB_SOURCES; i++) { - int cnt = mpctx->sub_counts[i]; - if (*pos >= start && *pos < start + cnt) { - *pos -= start; - return i; - } - start += cnt; - } - *pos = -1; - return -1; -} - -static int sub_source_by_pos(MPContext *mpctx, int pos) -{ - return sub_source_and_index_by_pos(mpctx, &pos); -} - -static int sub_source_pos(MPContext *mpctx) -{ - int pos = mpctx->global_sub_pos; - sub_source_and_index_by_pos(mpctx, &pos); - return pos; -} - -static int sub_source(MPContext *mpctx) -{ - return sub_source_by_pos(mpctx, mpctx->global_sub_pos); -} - -static void update_global_sub_size(MPContext *mpctx) -{ - struct MPOpts *opts = &mpctx->opts; - int i; - int cnt = 0; - - if (!mpctx->demuxer) { - mpctx->global_sub_size = -1; - mpctx->global_sub_pos = -1; - return; - } - - // update number of demuxer sub streams - for (i = 0; i < MAX_S_STREAMS; i++) - if (mpctx->d_sub->demuxer->s_streams[i]) - cnt++; - if (cnt > mpctx->sub_counts[SUB_SOURCE_DEMUX]) - mpctx->sub_counts[SUB_SOURCE_DEMUX] = cnt; - - // update global size - mpctx->global_sub_size = 0; - for (i = 0; i < SUB_SOURCES; i++) - mpctx->global_sub_size += mpctx->sub_counts[i]; - - // update global_sub_pos if we auto-detected a demuxer sub - if (mpctx->global_sub_pos == -1) { - int sub_id = -1; - if (mpctx->demuxer->sub) - sub_id = mpctx->demuxer->sub->id; - if (sub_id < 0) - sub_id = opts->sub_id; - if (sub_id >= 0 && sub_id < mpctx->sub_counts[SUB_SOURCE_DEMUX]) - mpctx->global_sub_pos = sub_pos_by_source(mpctx, SUB_SOURCE_DEMUX) + - sub_id; - } -} - static int mp_property_generic_option(struct m_option *prop, int action, void *arg, MPContext *mpctx) { @@ -893,120 +813,91 @@ static int mp_property_balance(m_option_t *prop, int action, void *arg, return M_PROPERTY_NOT_IMPLEMENTED; } -/// Selected audio id (RW) -static int mp_property_audio(m_option_t *prop, int action, void *arg, - MPContext *mpctx) +static struct track* track_next(struct MPContext *mpctx, enum stream_type type, + int direction, struct track *track) +{ + assert(direction == -1 || direction == +1); + struct track *prev = NULL, *next = NULL; + bool seen = track == NULL; + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *cur = mpctx->tracks[n]; + if (cur->type == type) { + if (cur == track) { + seen = true; + } else { + if (seen && !next) { + next = cur; + } else if (!seen || !track) { + prev = cur; + } + } + } + } + return direction > 0 ? next : prev; +} + +static int property_switch_track(m_option_t *prop, int action, void *arg, + MPContext *mpctx, enum stream_type type) { - int current_id, tmp; if (!mpctx->num_sources) return M_PROPERTY_UNAVAILABLE; - struct sh_audio *sh = mpctx->sh_audio; - current_id = sh ? sh->aid : -2; + struct track *track = mpctx->current_track[type]; switch (action) { case M_PROPERTY_GET: if (!arg) return M_PROPERTY_ERROR; - *(int *) arg = current_id; + *(int *) arg = track ? track->user_tid : -1; return M_PROPERTY_OK; case M_PROPERTY_PRINT: if (!arg) return M_PROPERTY_ERROR; - if (!sh || current_id < 0) + if (!track) *(char **) arg = talloc_strdup(NULL, mp_gtext("disabled")); else { - char *lang = demuxer_stream_lang(sh->ds->demuxer, sh->gsh); + char *lang = track->lang; if (!lang) - lang = talloc_strdup(NULL, mp_gtext("unknown")); + lang = mp_gtext("unknown"); - if (sh->gsh->title) + if (track->title) *(char **)arg = talloc_asprintf(NULL, "(%d) %s (\"%s\")", - current_id, lang, sh->gsh->title); + track->user_tid, lang, track->title); else - *(char **)arg = talloc_asprintf(NULL, "(%d) %s", current_id, - lang); - - talloc_free(lang); + *(char **)arg = talloc_asprintf(NULL, "(%d) %s", + track->user_tid, lang); } return M_PROPERTY_OK; case M_PROPERTY_STEP_UP: - case M_PROPERTY_SET: + case M_PROPERTY_STEP_DOWN: + case M_PROPERTY_SET: { + int i = (arg ? *((int *) arg) : +1) * + (action == M_PROPERTY_STEP_DOWN ? -1 : +1); if (action == M_PROPERTY_SET && arg) - tmp = *((int *) arg); + track = mp_track_by_tid(mpctx, type, i); else - tmp = -1; - int new_id = demuxer_switch_audio(mpctx->d_audio->demuxer, tmp); - if (new_id != current_id) - uninit_player(mpctx, INITIALIZED_AO | INITIALIZED_ACODEC); - if (new_id != current_id && new_id >= 0) { - mpctx->opts.audio_id = new_id; - sh_audio_t *sh2; - sh2 = mpctx->d_audio->demuxer->a_streams[mpctx->d_audio->id]; - sh2->ds = mpctx->d_audio; - mpctx->sh_audio = sh2; - reinit_audio_chain(mpctx); - } - mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_AUDIO_TRACK=%d\n", new_id); + track = track_next(mpctx, type, i > 0 ? +1 : -1, track); + mp_switch_track(mpctx, type, track); return M_PROPERTY_OK; + } default: return M_PROPERTY_NOT_IMPLEMENTED; } +} +/// Selected audio id (RW) +static int mp_property_audio(m_option_t *prop, int action, void *arg, + MPContext *mpctx) +{ + return property_switch_track(prop, action, arg, mpctx, STREAM_AUDIO); } /// Selected video id (RW) static int mp_property_video(m_option_t *prop, int action, void *arg, MPContext *mpctx) { - struct MPOpts *opts = &mpctx->opts; - int current_id, tmp; - if (!mpctx->num_sources) - return M_PROPERTY_UNAVAILABLE; - current_id = mpctx->sh_video ? mpctx->sh_video->vid : -2; - - switch (action) { - case M_PROPERTY_GET: - if (!arg) - return M_PROPERTY_ERROR; - *(int *) arg = current_id; - return M_PROPERTY_OK; - case M_PROPERTY_PRINT: - if (!arg) - return M_PROPERTY_ERROR; - - if (current_id < 0) - *(char **) arg = talloc_strdup(NULL, mp_gtext("disabled")); - else { - *(char **) arg = talloc_asprintf(NULL, "(%d) %s", current_id, - mp_gtext("unknown")); - } - return M_PROPERTY_OK; - - case M_PROPERTY_STEP_UP: - case M_PROPERTY_SET: - if (action == M_PROPERTY_SET && arg) - tmp = *((int *) arg); - else - tmp = -1; - int new_id = demuxer_switch_video(mpctx->d_video->demuxer, tmp); - if (new_id != current_id) - uninit_player(mpctx, INITIALIZED_VCODEC | - (opts->fixed_vo && new_id >= 0 ? 0 : INITIALIZED_VO)); - if (new_id != current_id && new_id >= 0) { - sh_video_t *sh2; - sh2 = mpctx->d_video->demuxer->v_streams[mpctx->d_video->id]; - sh2->ds = mpctx->d_video; - mpctx->sh_video = sh2; - reinit_video_chain(mpctx); - } - mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_VIDEO_TRACK=%d\n", new_id); - return M_PROPERTY_OK; - - default: - return M_PROPERTY_NOT_IMPLEMENTED; - } + return property_switch_track(prop, action, arg, mpctx, STREAM_VIDEO); } static int mp_property_program(m_option_t *prop, int action, void *arg, @@ -1364,7 +1255,7 @@ static int mp_property_gamma(m_option_t *prop, int action, void *arg, } #ifdef CONFIG_TV - if (mpctx->sh_video->ds->demuxer->type == DEMUXER_TYPE_TV) { + if (mpctx->sh_video->gsh->demuxer->type == DEMUXER_TYPE_TV) { int l = strlen(prop->name); char tv_prop[3 + l + 1]; sprintf(tv_prop, "tv_%s", prop->name); @@ -1496,350 +1387,7 @@ static int mp_property_sub_pos(m_option_t *prop, int action, void *arg, static int mp_property_sub(m_option_t *prop, int action, void *arg, MPContext *mpctx) { - struct MPOpts *opts = &mpctx->opts; - demux_stream_t *const d_sub = mpctx->d_sub; - int source = -1, reset_spu av_unused = 0; // used under CONFIG_DVDREAD - int source_pos = -1; - - update_global_sub_size(mpctx); - const int global_sub_size = mpctx->global_sub_size; - - if (global_sub_size <= 0) - return M_PROPERTY_UNAVAILABLE; - - switch (action) { - case M_PROPERTY_GET: - if (!arg) - return M_PROPERTY_ERROR; - *(int *) arg = mpctx->global_sub_pos; - return M_PROPERTY_OK; - case M_PROPERTY_PRINT: - if (!arg) - return M_PROPERTY_ERROR; - char *sub_name = NULL; - if (mpctx->subdata) - sub_name = mpctx->subdata->filename; -#ifdef CONFIG_ASS - if (mpctx->osd->ass_track) - sub_name = mpctx->osd->ass_track->name; -#endif - if (!sub_name && mpctx->subdata) - sub_name = mpctx->subdata->filename; - if (sub_name) { - const char *tmp = mp_basename(sub_name); - - *(char **) arg = talloc_asprintf(NULL, "(%d) %s%s", - mpctx->set_of_sub_pos + 1, - strlen(tmp) < 20 ? "" : "...", - strlen(tmp) < 20 ? tmp : tmp + strlen(tmp) - 19); - return M_PROPERTY_OK; - } - - if (vo_vobsub && vobsub_id >= 0) { - const char *language = mp_gtext("unknown"); - language = vobsub_get_id(vo_vobsub, (unsigned int) vobsub_id); - *(char **) arg = talloc_asprintf(NULL, "(%d) %s", - vobsub_id, language ? language : mp_gtext("unknown")); - return M_PROPERTY_OK; - } - if (opts->sub_id >= 0 && mpctx->d_sub && mpctx->d_sub->sh) { - struct sh_stream *sh = ((struct sh_sub *)mpctx->d_sub->sh)->gsh; - char *lang = demuxer_stream_lang(sh->common_header->ds->demuxer, sh); - if (!lang) - lang = talloc_strdup(NULL, mp_gtext("unknown")); - if (sh->title) - *(char **)arg = talloc_asprintf(NULL, "(%d) %s (\"%s\")", - opts->sub_id, lang, sh->title); - else - *(char **) arg = talloc_asprintf(NULL, "(%d) %s", opts->sub_id, - lang); - talloc_free(lang); - return M_PROPERTY_OK; - } - *(char **) arg = talloc_strdup(NULL, mp_gtext("disabled")); - return M_PROPERTY_OK; - - case M_PROPERTY_SET: - if (!arg) - return M_PROPERTY_ERROR; - if (*(int *) arg < -1) - *(int *) arg = -1; - else if (*(int *) arg >= global_sub_size) - *(int *) arg = global_sub_size - 1; - mpctx->global_sub_pos = *(int *) arg; - break; - case M_PROPERTY_STEP_UP: - mpctx->global_sub_pos += 2; - mpctx->global_sub_pos = - (mpctx->global_sub_pos % (global_sub_size + 1)) - 1; - break; - case M_PROPERTY_STEP_DOWN: - mpctx->global_sub_pos += global_sub_size + 1; - mpctx->global_sub_pos = - (mpctx->global_sub_pos % (global_sub_size + 1)) - 1; - break; - default: - return M_PROPERTY_NOT_IMPLEMENTED; - } - - if (mpctx->global_sub_pos >= 0) { - source = sub_source(mpctx); - source_pos = sub_source_pos(mpctx); - } - - mp_msg(MSGT_CPLAYER, MSGL_DBG3, - "subtitles: %d subs, (v@%d s@%d d@%d), @%d, source @%d\n", - global_sub_size, - mpctx->sub_counts[SUB_SOURCE_VOBSUB], - mpctx->sub_counts[SUB_SOURCE_SUBS], - mpctx->sub_counts[SUB_SOURCE_DEMUX], - mpctx->global_sub_pos, source); - - mpctx->set_of_sub_pos = -1; - mpctx->subdata = NULL; - - vobsub_id = -1; - opts->sub_id = -1; - if (d_sub) { - if (d_sub->id > -2) - reset_spu = 1; - d_sub->id = -2; - } - mpctx->osd->ass_track = NULL; - uninit_player(mpctx, INITIALIZED_SUB); - - if (source == SUB_SOURCE_VOBSUB) - vobsub_id = vobsub_get_id_by_index(vo_vobsub, source_pos); - else if (source == SUB_SOURCE_SUBS) { - mpctx->set_of_sub_pos = source_pos; -#ifdef CONFIG_ASS - if (opts->ass_enabled - && mpctx->set_of_ass_tracks[mpctx->set_of_sub_pos]) { - mpctx->osd->ass_track = - mpctx->set_of_ass_tracks[mpctx->set_of_sub_pos]; - mpctx->osd->ass_track_changed = true; - mpctx->osd->vsfilter_aspect = - mpctx->track_was_native_ass[mpctx->set_of_sub_pos]; - } else -#endif - { - mpctx->subdata = mpctx->set_of_subtitles[mpctx->set_of_sub_pos]; - vo_osd_changed(OSDTYPE_SUBTITLE); - } - } else if (source == SUB_SOURCE_DEMUX) { - opts->sub_id = source_pos; - if (d_sub && opts->sub_id < MAX_S_STREAMS) { - int i = 0; - // default: assume 1:1 mapping of sid and stream id - d_sub->id = opts->sub_id; - d_sub->sh = mpctx->d_sub->demuxer->s_streams[d_sub->id]; - ds_free_packs(d_sub); - for (i = 0; i < MAX_S_STREAMS; i++) { - sh_sub_t *sh = mpctx->d_sub->demuxer->s_streams[i]; - if (sh && sh->sid == opts->sub_id) { - d_sub->id = i; - d_sub->sh = sh; - break; - } - } - if (d_sub->sh && d_sub->id >= 0) { - sh_sub_t *sh = d_sub->sh; - if (sh->type == 'v') - init_vo_spudec(mpctx); - else { - sub_init(sh, mpctx->osd); - mpctx->initialized_flags |= INITIALIZED_SUB; - } - } else { - d_sub->id = -2; - d_sub->sh = NULL; - } - } - } -#ifdef CONFIG_DVDREAD - if (vo_spudec && (mpctx->stream->type == STREAMTYPE_DVD) - && opts->sub_id < 0 && reset_spu) - { - d_sub->id = -2; - d_sub->sh = NULL; - } -#endif - - update_subtitles(mpctx, 0, true); - - return M_PROPERTY_OK; -} - -/// Selected sub source (RW) -static int mp_property_sub_source(m_option_t *prop, int action, void *arg, - MPContext *mpctx) -{ - int source; - update_global_sub_size(mpctx); - if (!mpctx->sh_video || mpctx->global_sub_size <= 0) - return M_PROPERTY_UNAVAILABLE; - - switch (action) { - case M_PROPERTY_GET: - if (!arg) - return M_PROPERTY_ERROR; - *(int *) arg = sub_source(mpctx); - return M_PROPERTY_OK; - case M_PROPERTY_PRINT: - if (!arg) - return M_PROPERTY_ERROR; - char *sourcename; - switch (sub_source(mpctx)) { - case SUB_SOURCE_SUBS: - sourcename = mp_gtext("file"); - break; - case SUB_SOURCE_VOBSUB: - sourcename = mp_gtext("vobsub"); - break; - case SUB_SOURCE_DEMUX: - sourcename = mp_gtext("embedded"); - break; - default: - sourcename = mp_gtext("disabled"); - } - *(char **)arg = talloc_strdup(NULL, sourcename); - return M_PROPERTY_OK; - case M_PROPERTY_SET: - if (!arg) - return M_PROPERTY_ERROR; - M_PROPERTY_CLAMP(prop, *(int *)arg); - if (*(int *) arg < 0) - mpctx->global_sub_pos = -1; - else if (*(int *) arg != sub_source(mpctx)) { - int new_pos = sub_pos_by_source(mpctx, *(int *)arg); - if (new_pos == -1) - return M_PROPERTY_UNAVAILABLE; - mpctx->global_sub_pos = new_pos; - } - break; - case M_PROPERTY_STEP_UP: - case M_PROPERTY_STEP_DOWN: { - int step_all = (arg && *(int *)arg != 0 ? *(int *)arg : 1) - * (action == M_PROPERTY_STEP_UP ? 1 : -1); - int step = (step_all > 0) ? 1 : -1; - int cur_source = sub_source(mpctx); - source = cur_source; - while (step_all) { - source += step; - if (source >= SUB_SOURCES) - source = -1; - else if (source < -1) - source = SUB_SOURCES - 1; - if (source == cur_source || source == -1 || - mpctx->sub_counts[source]) - step_all -= step; - } - if (source == cur_source) - return M_PROPERTY_OK; - if (source == -1) - mpctx->global_sub_pos = -1; - else - mpctx->global_sub_pos = sub_pos_by_source(mpctx, source); - break; - } - default: - return M_PROPERTY_NOT_IMPLEMENTED; - } - --mpctx->global_sub_pos; - return mp_property_sub(prop, M_PROPERTY_STEP_UP, NULL, mpctx); -} - -/// Selected subtitles from specific source (RW) -static int mp_property_sub_by_type(m_option_t *prop, int action, void *arg, - MPContext *mpctx) -{ - int source, is_cur_source, offset, new_pos; - update_global_sub_size(mpctx); - if (!mpctx->sh_video || mpctx->global_sub_size <= 0) - return M_PROPERTY_UNAVAILABLE; - - if (!strcmp(prop->name, "sub_file")) - source = SUB_SOURCE_SUBS; - else if (!strcmp(prop->name, "sub_vob")) - source = SUB_SOURCE_VOBSUB; - else if (!strcmp(prop->name, "sub_demux")) - source = SUB_SOURCE_DEMUX; - else - return M_PROPERTY_ERROR; - - offset = sub_pos_by_source(mpctx, source); - if (offset < 0) - return M_PROPERTY_UNAVAILABLE; - - is_cur_source = sub_source(mpctx) == source; - new_pos = mpctx->global_sub_pos; - switch (action) { - case M_PROPERTY_GET: - if (!arg) - return M_PROPERTY_ERROR; - if (is_cur_source) { - *(int *) arg = sub_source_pos(mpctx); - if (source == SUB_SOURCE_VOBSUB) - *(int *) arg = vobsub_get_id_by_index(vo_vobsub, *(int *) arg); - } else - *(int *) arg = -1; - return M_PROPERTY_OK; - case M_PROPERTY_PRINT: - if (!arg) - return M_PROPERTY_ERROR; - if (is_cur_source) - return mp_property_sub(prop, M_PROPERTY_PRINT, arg, mpctx); - *(char **) arg = talloc_strdup(NULL, mp_gtext("disabled")); - return M_PROPERTY_OK; - case M_PROPERTY_SET: - if (!arg) - return M_PROPERTY_ERROR; - if (*(int *) arg >= 0) { - int index = *(int *)arg; - if (source == SUB_SOURCE_VOBSUB) - index = vobsub_get_index_by_id(vo_vobsub, index); - new_pos = offset + index; - if (index < 0 || index > mpctx->sub_counts[source]) { - new_pos = -1; - *(int *) arg = -1; - } - } else - new_pos = -1; - break; - case M_PROPERTY_STEP_UP: - case M_PROPERTY_STEP_DOWN: { - int step_all = (arg && *(int *)arg != 0 ? *(int *)arg : 1) - * (action == M_PROPERTY_STEP_UP ? 1 : -1); - int step = (step_all > 0) ? 1 : -1; - int max_sub_pos_for_source = -1; - if (!is_cur_source) - new_pos = -1; - while (step_all) { - if (new_pos == -1) { - if (step > 0) - new_pos = offset; - else if (max_sub_pos_for_source == -1) { - // Find max pos for specific source - new_pos = mpctx->global_sub_size - 1; - while (new_pos >= 0 && sub_source(mpctx) != source) - new_pos--; - } else - new_pos = max_sub_pos_for_source; - } else { - new_pos += step; - if (new_pos < offset || - new_pos >= mpctx->global_sub_size || - sub_source(mpctx) != source) - new_pos = -1; - } - step_all -= step; - } - break; - } - default: - return M_PROPERTY_NOT_IMPLEMENTED; - } - return mp_property_sub(prop, M_PROPERTY_SET, &new_pos, mpctx); + return property_switch_track(prop, action, arg, mpctx, STREAM_SUB); } /// Subtitle delay (RW) @@ -1859,8 +1407,7 @@ static int mp_property_sub_alignment(m_option_t *prop, int action, _("top"), _("center"), _("bottom") }; - if (!mpctx->sh_video || mpctx->global_sub_pos < 0 - || sub_source(mpctx) != SUB_SOURCE_SUBS) + if (!mpctx->current_track[STREAM_SUB]) return M_PROPERTY_UNAVAILABLE; switch (action) { @@ -2183,14 +1730,6 @@ static const m_option_t mp_properties[] = { // Subs { "sub", mp_property_sub, CONF_TYPE_INT, M_OPT_MIN, -1, 0, NULL }, - { "sub_source", mp_property_sub_source, CONF_TYPE_INT, - M_OPT_RANGE, -1, SUB_SOURCES - 1, NULL }, - { "sub_vob", mp_property_sub_by_type, CONF_TYPE_INT, - M_OPT_MIN, -1, 0, NULL }, - { "sub_demux", mp_property_sub_by_type, CONF_TYPE_INT, - M_OPT_MIN, -1, 0, NULL }, - { "sub_file", mp_property_sub_by_type, CONF_TYPE_INT, - M_OPT_MIN, -1, 0, NULL }, { "sub_delay", mp_property_sub_delay, CONF_TYPE_FLOAT, 0, 0, 0, NULL }, { "sub_pos", mp_property_sub_pos, CONF_TYPE_INT, @@ -2299,10 +1838,6 @@ static struct property_osd_display { { "vsync", 0, -1, _("VSync: %s") }, // subs { "sub", 0, -1, _("Subtitles: %s") }, - { "sub_source", 0, -1, _("Sub source: %s") }, - { "sub_vob", 0, -1, _("Subtitles: %s") }, - { "sub_demux", 0, -1, _("Subtitles: %s") }, - { "sub_file", 0, -1, _("Subtitles: %s") }, { "sub_pos", 0, -1, _("Sub position: %s/100") }, { "sub_alignment", 0, -1, _("Sub alignment: %s") }, { "sub_delay", 0, OSD_MSG_SUB_DELAY, _("Sub delay: %s") }, @@ -2423,10 +1958,6 @@ static struct { { "vsync", MP_CMD_SWITCH_VSYNC, 1}, // subs { "sub", MP_CMD_SUB_SELECT, 1}, - { "sub_source", MP_CMD_SUB_SOURCE, 1}, - { "sub_vob", MP_CMD_SUB_VOB, 1}, - { "sub_demux", MP_CMD_SUB_DEMUX, 1}, - { "sub_file", MP_CMD_SUB_FILE, 1}, { "sub_pos", MP_CMD_SUB_POS, 0}, { "sub_alignment", MP_CMD_SUB_ALIGNMENT, 1}, { "sub_delay", MP_CMD_SUB_DELAY, 0}, @@ -2539,52 +2070,46 @@ static void show_chapters_on_osd(MPContext *mpctx) talloc_free(res); } +static const char *track_type_name(enum stream_type t) +{ + switch (t) { + case STREAM_VIDEO: return "Video"; + case STREAM_AUDIO: return "Audio"; + case STREAM_SUB: return "Sub"; + } + return NULL; +} + static void show_tracks_on_osd(MPContext *mpctx) { struct MPOpts *opts = &mpctx->opts; - demuxer_t *demuxer = mpctx->master_demuxer; char *res = NULL; - if (!demuxer) - return; - - struct sh_stream *cur_a = mpctx->sh_audio ? mpctx->sh_audio->gsh : NULL; - struct sh_stream *cur_s = NULL; - if (opts->sub_id >= 0 && mpctx->d_sub && mpctx->d_sub->sh) - cur_s = ((struct sh_sub *)mpctx->d_sub->sh)->gsh; - - int v_count = 0; - enum stream_type t = STREAM_AUDIO; - - for (int n = 0; n < demuxer->num_streams; n++) { - struct sh_stream *sh = demuxer->streams[n]; - if (sh->type == STREAM_VIDEO) { - v_count++; - continue; - } - if (t != sh->type) + for (int type = 0; type < STREAM_TYPE_COUNT; type++) { + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + if (track->type != type) + continue; + + bool selected = mpctx->current_track[track->type] == track; + res = talloc_asprintf_append(res, "%s: ", track_type_name(track->type)); + if (selected) + res = talloc_asprintf_append(res, "> "); + res = talloc_asprintf_append(res, "(%d) ", track->user_tid); + if (track->title) + res = talloc_asprintf_append(res, "'%s' ", track->title); + if (track->lang) + res = talloc_asprintf_append(res, "(%s) ", track->lang); + if (track->is_external) + res = talloc_asprintf_append(res, "(external) "); + if (selected) + res = talloc_asprintf_append(res, "<"); res = talloc_asprintf_append(res, "\n"); - bool selected = sh == cur_a || sh == cur_s; - res = talloc_asprintf_append(res, "%s: ", - sh->type == STREAM_AUDIO ? "Audio" : "Sub"); - if (selected) - res = talloc_asprintf_append(res, "> "); - res = talloc_asprintf_append(res, "(%d) ", sh->tid); - if (sh->title) - res = talloc_asprintf_append(res, "'%s' ", sh->title); - char *lang = demuxer_stream_lang(sh->common_header->ds->demuxer, sh); - if (lang) - res = talloc_asprintf_append(res, "(%s) ", lang); - talloc_free(lang); - if (selected) - res = talloc_asprintf_append(res, "<"); + } + res = talloc_asprintf_append(res, "\n"); - t = sh->type; } - if (v_count > 1) - res = talloc_asprintf_append(res, "\n(Warning: more than one video stream.)\n"); - set_osd_msg(mpctx, OSD_MSG_TEXT, 1, opts->osd_duration, "%s", res); talloc_free(res); } @@ -2783,7 +2308,9 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd) case MP_CMD_SUB_STEP: if (sh_video) { int movement = cmd->args[0].v.i; - step_sub(mpctx->subdata, mpctx->video_pts, movement); + struct track *track = mpctx->current_track[STREAM_SUB]; + if (track && track->subdata) + step_sub(track->subdata, mpctx->video_pts, movement); #ifdef CONFIG_ASS if (mpctx->osd->ass_track) sub_delay += @@ -3083,12 +2610,7 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd) case MP_CMD_SUB_LOAD: if (sh_video) { - int n = mpctx->set_of_sub_size; add_subtitles(mpctx, cmd->args[0].v.s, sh_video->fps, 0); - if (n != mpctx->set_of_sub_size) { - mpctx->sub_counts[SUB_SOURCE_SUBS]++; - ++mpctx->global_sub_size; - } } break; diff --git a/input/input.c b/input/input.c index 4ea9e0c531..d1136532ed 100644 --- a/input/input.c +++ b/input/input.c @@ -134,10 +134,6 @@ static const mp_cmd_t mp_cmds[] = { { MP_CMD_SUB_LOAD, "sub_load", { ARG_STRING } }, { MP_CMD_SUB_SELECT, "vobsub_lang", { OARG_INT(-2) } }, // for compatibility { MP_CMD_SUB_SELECT, "sub_select", { OARG_INT(-2) } }, - { MP_CMD_SUB_SOURCE, "sub_source", { OARG_INT(-2) } }, - { MP_CMD_SUB_VOB, "sub_vob", { OARG_INT(-2) } }, - { MP_CMD_SUB_DEMUX, "sub_demux", { OARG_INT(-2) } }, - { MP_CMD_SUB_FILE, "sub_file", { OARG_INT(-2) } }, { MP_CMD_SUB_SCALE, "sub_scale", { ARG_FLOAT, OARG_INT(0) } }, #ifdef CONFIG_ASS { MP_CMD_ASS_USE_MARGINS, "ass_use_margins", { OARG_INT(-1) } }, diff --git a/input/input.h b/input/input.h index e1e333c486..a055b59138 100644 --- a/input/input.h +++ b/input/input.h @@ -118,10 +118,6 @@ enum mp_command_type { MP_CMD_BALANCE, MP_CMD_SUB_SCALE, MP_CMD_TV_START_SCAN, - MP_CMD_SUB_SOURCE, - MP_CMD_SUB_FILE, - MP_CMD_SUB_VOB, - MP_CMD_SUB_DEMUX, MP_CMD_SWITCH_ANGLE, MP_CMD_ASS_USE_MARGINS, MP_CMD_SWITCH_TITLE, diff --git a/libmpdemux/demux_lavf.c b/libmpdemux/demux_lavf.c index baf013a8ef..4dd9938d07 100644 --- a/libmpdemux/demux_lavf.c +++ b/libmpdemux/demux_lavf.c @@ -309,7 +309,6 @@ static void handle_stream(demuxer_t *demuxer, AVFormatContext *avfc, int i) sh_audio = new_sh_audio_aid(demuxer, i, priv->audio_streams); if (!sh_audio) break; - sh_audio->gsh->demuxer_id = i; sh_audio->demuxer_codecname = codec_name; stream_type = "audio"; priv->astreams[priv->audio_streams] = i; @@ -391,7 +390,6 @@ static void handle_stream(demuxer_t *demuxer, AVFormatContext *avfc, int i) sh_video = new_sh_video_vid(demuxer, i, priv->video_streams); if (!sh_video) break; - sh_video->gsh->demuxer_id = i; sh_video->demuxer_codecname = codec_name; stream_type = "video"; priv->vstreams[priv->video_streams] = i; @@ -504,7 +502,6 @@ static void handle_stream(demuxer_t *demuxer, AVFormatContext *avfc, int i) sh_sub = new_sh_sub_sid(demuxer, i, priv->sub_streams); if (!sh_sub) break; - sh_sub->gsh->demuxer_id = i; sh_sub->demuxer_codecname = codec_name; stream_type = "subtitle"; priv->sstreams[priv->sub_streams] = i; diff --git a/libmpdemux/demuxer.c b/libmpdemux/demuxer.c index bc2e132c60..13f5fab268 100644 --- a/libmpdemux/demuxer.c +++ b/libmpdemux/demuxer.c @@ -189,10 +189,12 @@ static void free_demuxer_stream(struct demux_stream *ds) free(ds); } -static struct demux_stream *new_demuxer_stream(struct demuxer *demuxer, int id) +static struct demux_stream *new_demuxer_stream(struct demuxer *demuxer, + enum stream_type type, int id) { demux_stream_t *ds = malloc(sizeof(demux_stream_t)); - *ds = (demux_stream_t){ + *ds = (demux_stream_t) { + .stream_type = type, .id = id, .demuxer = demuxer, .asf_seq = -1, @@ -200,6 +202,19 @@ static struct demux_stream *new_demuxer_stream(struct demuxer *demuxer, int id) return ds; } +struct sh_stream *ds_gsh(struct demux_stream *ds) +{ + // Ideally ds would have a gsh field, but since all the old demuxers set + // ds->sh themselves and we don't want to change them, enjoy this hack. + if (!ds->sh) + return NULL; + switch (ds->stream_type) { + case STREAM_VIDEO: return ((struct sh_video *)ds->sh)->gsh; + case STREAM_AUDIO: return ((struct sh_audio *)ds->sh)->gsh; + case STREAM_SUB: return ((struct sh_sub *)ds->sh)->gsh; + } + assert(false); +} /** * Get demuxer description structure for a given demuxer type @@ -231,9 +246,12 @@ demuxer_t *new_demuxer(struct MPOpts *opts, stream_t *stream, int type, d->seekable = 1; d->synced = 0; d->filepos = -1; - d->audio = new_demuxer_stream(d, a_id); - d->video = new_demuxer_stream(d, v_id); - d->sub = new_demuxer_stream(d, s_id); + d->audio = new_demuxer_stream(d, STREAM_VIDEO, a_id); + d->video = new_demuxer_stream(d, STREAM_AUDIO, v_id); + d->sub = new_demuxer_stream(d, STREAM_SUB, s_id); + d->ds[STREAM_VIDEO] = d->video; + d->ds[STREAM_AUDIO] = d->audio; + d->ds[STREAM_SUB] = d->sub; d->type = type; d->opts = opts; if (type) @@ -269,8 +287,9 @@ static struct sh_stream *new_sh_stream(demuxer_t *demuxer, { struct sh_stream *sh = talloc_struct(demuxer, struct sh_stream, { .type = type, + .demuxer = demuxer, .index = demuxer->num_streams, - .demuxer_id = demuxer->new_stream_id++, // possibly temporary value only + .demuxer_id = tid, // may be overwritten by demuxer .tid = tid, .stream_index = stream_index, .opts = demuxer->opts, @@ -308,7 +327,6 @@ static struct sh_stream *new_sh_stream(demuxer_t *demuxer, } default: assert(false); } - sh->common_header->id = sh->tid; sh->common_header->opts = sh->opts; sh->common_header->gsh = sh; return sh; @@ -1235,37 +1253,42 @@ int demux_control(demuxer_t *demuxer, int cmd, void *arg) return DEMUXER_CTRL_NOTIMPL; } -int demuxer_switch_audio(demuxer_t *demuxer, int index) +struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d, + enum stream_type t, int id) { - int res = demux_control(demuxer, DEMUXER_CTRL_SWITCH_AUDIO, &index); - if (res == DEMUXER_CTRL_NOTIMPL) { - struct sh_audio *sh_audio = demuxer->audio->sh; - return sh_audio ? sh_audio->aid : -2; + for (int n = 0; n < d->num_streams; n++) { + struct sh_stream *s = d->streams[n]; + if (s->type == t && s->demuxer_id == id) + return d->streams[n]; } - if (demuxer->audio->id >= 0) { - struct sh_audio *sh_audio = demuxer->a_streams[demuxer->audio->id]; - demuxer->audio->sh = sh_audio; - index = sh_audio->aid; // internal MPEG demuxers don't set it right - } - else - demuxer->audio->sh = NULL; - return index; + return NULL; } -int demuxer_switch_video(demuxer_t *demuxer, int index) -{ - int res = demux_control(demuxer, DEMUXER_CTRL_SWITCH_VIDEO, &index); - if (res == DEMUXER_CTRL_NOTIMPL) { - struct sh_video *sh_video = demuxer->video->sh; - return sh_video ? sh_video->vid : -2; +void demuxer_switch_track(struct demuxer *demuxer, enum stream_type type, + struct sh_stream *stream) +{ + assert(!stream || stream->type == type); + int index = stream ? stream->tid : -2; + if (type == STREAM_AUDIO) { + demux_control(demuxer, DEMUXER_CTRL_SWITCH_AUDIO, &index); + } else if (type == STREAM_VIDEO) { + demux_control(demuxer, DEMUXER_CTRL_SWITCH_VIDEO, &index); + } else if (type == STREAM_SUB) { + int index2 = stream ? stream->stream_index : -2; + if (demuxer->ds[type]->id != index2) + ds_free_packs(demuxer->ds[type]); + demuxer->ds[type]->id = index2; } - if (demuxer->video->id >= 0) { - struct sh_video *sh_video = demuxer->v_streams[demuxer->video->id]; - demuxer->video->sh = sh_video; - index = sh_video->vid; // internal MPEG demuxers don't set it right - } else - demuxer->video->sh = NULL; - return index; + int new_id = demuxer->ds[type]->id; + void *new = NULL; + if (new_id >= 0) { + switch (type) { + case STREAM_VIDEO: new = demuxer->v_streams[new_id]; break; + case STREAM_AUDIO: new = demuxer->a_streams[new_id]; break; + case STREAM_SUB: new = demuxer->s_streams[new_id]; break; + } + } + demuxer->ds[type]->sh = new; } int demuxer_add_attachment(demuxer_t *demuxer, struct bstr name, @@ -1456,91 +1479,15 @@ int demuxer_set_angle(demuxer_t *demuxer, int angle) return angle; } -static char *demuxer_audio_lang(demuxer_t *d, int id) -{ - struct stream_lang_req req; - sh_audio_t *sh; - if (id < 0 || id >= MAX_A_STREAMS) - return NULL; - sh = d->a_streams[id]; - if (!sh) - return NULL; - if (sh->lang) - return talloc_strdup(NULL, sh->lang); - req.type = stream_ctrl_audio; - req.id = sh->aid; - if (stream_control(d->stream, STREAM_CTRL_GET_LANG, &req) == STREAM_OK) - return req.name; - return NULL; -} - -static char *demuxer_sub_lang(demuxer_t *d, int id) +char *demuxer_stream_lang(demuxer_t *d, struct sh_stream *sh) { - struct stream_lang_req req; - sh_sub_t *sh; - if (id < 0 || id >= MAX_S_STREAMS) - return NULL; - sh = d->s_streams[id]; - if (sh && sh->lang) - return talloc_strdup(NULL, sh->lang); - req.type = stream_ctrl_sub; - // assume 1:1 mapping so we can show the language of - // DVD subs even when we have not yet created the stream. - req.id = sh ? sh->sid : id; + struct stream_lang_req req = { .id = sh->tid }; // assume 1:1 mapping + switch (sh->type) { + case STREAM_AUDIO: req.type = stream_ctrl_audio; break; + case STREAM_SUB: req.type = stream_ctrl_sub; break; + default: return NULL; + } if (stream_control(d->stream, STREAM_CTRL_GET_LANG, &req) == STREAM_OK) return req.name; return NULL; } - -char *demuxer_stream_lang(demuxer_t *d, struct sh_stream *s) -{ - switch (s->type) { - case STREAM_AUDIO: return demuxer_audio_lang(d, s->stream_index); - case STREAM_SUB: return demuxer_sub_lang(d, s->stream_index); - default: return NULL; - } -} - -int demuxer_audio_track_by_lang_and_default(struct demuxer *d, char **langt) -{ - int n = 0; - while (1) { - char *lang = langt ? langt[n++] : NULL; - int id = -1; - for (int i = 0; i < MAX_A_STREAMS; i++) { - struct sh_audio *sh = d->a_streams[i]; - if (sh && (!lang || sh->lang && !strcmp(lang, sh->lang))) { - if (sh->gsh->default_track) - return sh->aid; - if (id < 0) - id = sh->aid; - } - } - if (id >= 0) - return id; - if (!lang) - return -1; - } -} - -int demuxer_sub_track_by_lang_and_default(struct demuxer *d, char **langt) -{ - int n = 0; - while (1) { - char *lang = langt ? langt[n++] : NULL; - int id = -1; - for (int i = 0; i < MAX_S_STREAMS; i++) { - struct sh_sub *sh = d->s_streams[i]; - if (sh && (!lang || sh->lang && !strcmp(lang, sh->lang))) { - if (sh->gsh->default_track) - return sh->sid; - if (id < 0) - id = sh->sid; - } - } - if (!lang) - return -1; - if (id >= 0) - return id; - } -} diff --git a/libmpdemux/demuxer.h b/libmpdemux/demuxer.h index 63910240bc..50ab0525c4 100644 --- a/libmpdemux/demuxer.h +++ b/libmpdemux/demuxer.h @@ -28,6 +28,7 @@ #include "bstr.h" #include "mpcommon.h" #include "demux_packet.h" +#include "stheader.h" struct MPOpts; @@ -103,6 +104,7 @@ enum timestamp_type { #define MP_INPUT_BUFFER_PADDING_SIZE 16 typedef struct demux_stream { + enum stream_type stream_type; int buffer_pos; // current buffer position int buffer_size; // current buffer size unsigned char *buffer; // current buffer, never free() it, always use free_demux_packet(buffer_ref); @@ -233,6 +235,9 @@ typedef struct demuxer { bool accurate_seek; enum timestamp_type timestamp_type; + struct demux_stream *ds[STREAM_TYPE_COUNT]; // video/audio/sub buffers + + // These correspond to ds[], e.g.: audio == ds[STREAM_AUDIO] struct demux_stream *audio; // audio buffer/demuxer struct demux_stream *video; // video buffer/demuxer struct demux_stream *sub; // dvd subtitle buffer/demuxer @@ -261,8 +266,6 @@ typedef struct demuxer { char **info; // metadata struct MPOpts *opts; struct demuxer_params *params; - - int new_stream_id; } demuxer_t; typedef struct { @@ -295,6 +298,8 @@ struct demuxer *new_demuxer(struct MPOpts *opts, struct stream *stream, char *filename); void free_demuxer(struct demuxer *demuxer); +struct sh_stream *ds_gsh(struct demux_stream *ds); + void ds_add_packet(struct demux_stream *ds, struct demux_packet *dp); void ds_read_packet(struct demux_stream *ds, struct stream *stream, int len, double pts, off_t pos, bool keyframe); @@ -369,8 +374,8 @@ char *demux_info_get(struct demuxer *demuxer, const char *opt); int demux_info_print(struct demuxer *demuxer); int demux_control(struct demuxer *demuxer, int cmd, void *arg); -int demuxer_switch_audio(struct demuxer *demuxer, int index); -int demuxer_switch_video(struct demuxer *demuxer, int index); +void demuxer_switch_track(struct demuxer *demuxer, enum stream_type type, + struct sh_stream *stream); int demuxer_type_by_filename(char *filename); @@ -398,19 +403,8 @@ int demuxer_set_angle(struct demuxer *demuxer, int angle); /// Get number of angles. int demuxer_angles_count(struct demuxer *demuxer); -/* Get the index of a track. - * lang is a string list, NULL is same as empty list - * Sort tracks based on the following criteria: - * 1) earlier match in lang list, or last no match - * 2) track is marked default (default wins) - * 3) track number (lower wins) - * For audio, select best track according to these criteria; only return -1 - * if there are no tracks at all. - * For subs, select best track according to the same criteria, but return -1 - * if all tracks are no-lang-match, not-default. - */ -int demuxer_audio_track_by_lang_and_default(struct demuxer *d, char **langt); -int demuxer_sub_track_by_lang_and_default(struct demuxer *d, char **langt); +struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d, + enum stream_type t, int id); char *demuxer_stream_lang(demuxer_t *d, struct sh_stream *s); diff --git a/libmpdemux/stheader.h b/libmpdemux/stheader.h index f36ac8c347..90d06f96d5 100644 --- a/libmpdemux/stheader.h +++ b/libmpdemux/stheader.h @@ -27,22 +27,24 @@ struct MPOpts; struct demuxer; enum stream_type { - STREAM_VIDEO = 1, + STREAM_VIDEO, STREAM_AUDIO, STREAM_SUB, + STREAM_TYPE_COUNT, }; // Stream headers: struct sh_stream { enum stream_type type; + struct demuxer *demuxer; // Index into demuxer->streams. int index; // The (possibly) type specific id, e.g. aid or sid. int tid; // Index into stream array (currently one array per type, e.g. a_streams). int stream_index; - // Demuxer specific ID (-1 if unknown, otherwise >= 0). + // Demuxer specific ID (always set, defaults to tid). int demuxer_id; // Abomination. struct sh_common *common_header; @@ -61,7 +63,6 @@ struct sh_stream { #define SH_COMMON \ - int id; \ struct sh_stream *gsh; \ const char *demuxer_codecname; \ struct MPOpts *opts; \ diff --git a/mp_core.h b/mp_core.h index 6eea692acd..04a3dac735 100644 --- a/mp_core.h +++ b/mp_core.h @@ -25,6 +25,7 @@ #include "mixer.h" #include "sub/subreader.h" #include "sub/find_subfiles.h" +#include "libmpdemux/demuxer.h" // definitions used internally by the core player code @@ -75,6 +76,46 @@ struct chapter { char *name; }; +struct track { + enum stream_type type; + // The type specific ID, also called aid (audio), sid (subs), vid (video). + // For UI purposes only; this ID doesn't have anything to do with any + // IDs coming from demuxers or container files. + int user_tid; + + // Same as stream->demuxer_id. -1 if not set. + int demuxer_id; + + char *title; + bool default_track; + char *lang; + + // If this track is from an external file (e.g. subtitle file). + bool is_external; + + // If the track's stream changes with the timeline (ordered chapters). + bool under_timeline; + + // NULL if not backed by a demuxer (e.g. external subtitles). + // Value can change if under_timeline==true. + struct demuxer *demuxer; + // Invariant: (!demuxer && !stream) || stream->demuxer == demuxer + struct sh_stream *stream; + + // NOTE: demuxer subtitles, i.e. if stream!=NULL, do not use the following + // fields. The data is stored in stream->sub this case. + + // External text subtitle using libass subtitle renderer. + struct ass_track *ass_track; + bool native_ass_track; + + // External text subtitle using non-libass subtitle renderer. + struct sub_data *subdata; + + // External image subtitle (data is in vo_vobsub). 0 if not set. + int vobsub_id_plus_one; +}; + typedef struct MPContext { struct MPOpts opts; struct m_config *mconfig; @@ -83,7 +124,7 @@ typedef struct MPContext { struct osd_state *osd; struct mp_osd_msg *osd_msg_stack; char *terminal_osd_text; - struct sub_data *subdata; // current sub_data style subtitles if any + subtitle subs; // subtitle list used when reading subtitles from demuxer bool add_osd_seek_info; unsigned int osd_visible; @@ -99,6 +140,7 @@ typedef struct MPContext { struct demuxer **sources; int num_sources; + struct timeline_part *timeline; int num_timeline_parts; int timeline_part; @@ -110,11 +152,17 @@ typedef struct MPContext { struct stream *stream; struct demuxer *demuxer; - struct sh_audio *sh_audio; - struct sh_video *sh_video; - struct demux_stream *d_audio; - struct demux_stream *d_video; - struct demux_stream *d_sub; + + struct track **tracks; + int num_tracks; + + // Selected tracks. NULL if no track selected. + struct track *current_track[STREAM_TYPE_COUNT]; + + struct sh_stream *sh[STREAM_TYPE_COUNT]; + struct sh_audio *sh_audio; // same as sh[STREAM_AUDIO]->audio + struct sh_video *sh_video; // same as sh[STREAM_VIDEO]->video + struct sh_sub *sh_sub; // same as sh[STREAM_SUB]->sub // Uses: accessing metadata (consider ordered chapters case, where the main // demuxer defines metadata), or special purpose demuxers like TV. @@ -185,16 +233,6 @@ typedef struct MPContext { float begin_skip; ///< start time of the current skip while on edlout mode - int global_sub_size; // this encompasses all subtitle sources - int global_sub_pos; // this encompasses all subtitle sources - int set_of_sub_pos; - int set_of_sub_size; - int sub_counts[SUB_SOURCES]; - // set_of_ass_tracks[i] contains subtitles from set_of_subtitles[i] - // parsed by libass or NULL if format unsupported - struct ass_track *set_of_ass_tracks[MAX_SUBTITLE_FILES]; - sub_data* set_of_subtitles[MAX_SUBTITLE_FILES]; - bool track_was_native_ass[MAX_SUBTITLE_FILES]; struct ass_library *ass_library; int file_format; @@ -240,8 +278,10 @@ char *chapter_display_name(struct MPContext *mpctx, int chapter); char *chapter_name(struct MPContext *mpctx, int chapter); double chapter_start_time(struct MPContext *mpctx, int chapter); int get_chapter_count(struct MPContext *mpctx); -void update_subtitles(struct MPContext *mpctx, double refpts, bool reset); - +void mp_switch_track(struct MPContext *mpctx, enum stream_type type, + struct track *track); +struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type, + int tid); // timeline/tl_matroska.c void build_ordered_chapter_timeline(struct MPContext *mpctx); diff --git a/mplayer.c b/mplayer.c index 7edbeb533a..e7565adfc5 100644 --- a/mplayer.c +++ b/mplayer.c @@ -251,6 +251,8 @@ int use_filedir_conf; #include "metadata.h" +static void reset_subtitles(struct MPContext *mpctx); + static float get_relative_time(struct MPContext *mpctx) { unsigned int new_time = GetTimer(); @@ -382,34 +384,34 @@ char *get_metadata(struct MPContext *mpctx, metadata_t type) return talloc_strdup(NULL, ""); } -static void print_stream(struct MPContext *mpctx, struct sh_stream *s) +static void print_stream(struct MPContext *mpctx, struct track *t, int id) { + struct sh_stream *s = t->stream; const char *tname = "?"; const char *selopt = "?"; const char *langopt = "?"; - switch (s->type) { + switch (t->type) { case STREAM_VIDEO: - tname = "video"; selopt = "vid"; langopt = "vlang"; + tname = "Video"; selopt = "vid"; langopt = "vlang"; break; case STREAM_AUDIO: - tname = "audio"; selopt = "aid"; langopt = "alang"; + tname = "Audio"; selopt = "aid"; langopt = "alang"; break; case STREAM_SUB: - tname = "subtitle"; selopt = "sid"; langopt = "slang"; + tname = "Subs"; selopt = "sid"; langopt = "slang"; break; } - mp_msg(MSGT_CPLAYER, MSGL_INFO, "[stream] ID %d: %s", s->demuxer_id, tname); - mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%d", selopt, s->tid); - char *lang = demuxer_stream_lang(s->common_header->ds->demuxer, s); - if (lang) - mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%s", langopt, lang); - talloc_free(lang); - if (s->default_track) + mp_msg(MSGT_CPLAYER, MSGL_INFO, "[stream] %-5s %3s", + tname, mpctx->current_track[t->type] == t ? "(+)" : ""); + mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%d", selopt, t->user_tid); + if (t->lang) + mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%s", langopt, t->lang); + if (t->default_track) mp_msg(MSGT_CPLAYER, MSGL_INFO, " (*)"); - if (s->title) - mp_msg(MSGT_CPLAYER, MSGL_INFO, " '%s'", s->title); + if (t->title) + mp_msg(MSGT_CPLAYER, MSGL_INFO, " '%s'", t->title); mp_msg(MSGT_CPLAYER, MSGL_INFO, " ("); - if (s->common_header->format) { + if (s && s->common_header->format) { int format = s->common_header->format; // not sure about endian crap char name[sizeof(format) + 1] = {0}; @@ -424,7 +426,7 @@ static void print_stream(struct MPContext *mpctx, struct sh_stream *s) } else { mp_msg(MSGT_CPLAYER, MSGL_INFO, "%#x", format); } - } else if (s->type == STREAM_SUB) { + } else if (s && t->type == STREAM_SUB) { char t = s->sub->type; const char *name = NULL; switch (t) { @@ -436,9 +438,11 @@ static void print_stream(struct MPContext *mpctx, struct sh_stream *s) name = (char[2]){t, '\0'}; mp_msg(MSGT_CPLAYER, MSGL_INFO, "%s", name); } - if (s->common_header->demuxer_codecname) + if (s && s->common_header->demuxer_codecname) mp_msg(MSGT_CPLAYER, MSGL_INFO, "/%s", s->common_header->demuxer_codecname); mp_msg(MSGT_CPLAYER, MSGL_INFO, ")"); + if (t->is_external) + mp_msg(MSGT_CPLAYER, MSGL_INFO, " (external)"); mp_msg(MSGT_CPLAYER, MSGL_INFO, "\n"); } @@ -466,7 +470,7 @@ static void print_file_properties(struct MPContext *mpctx, const char *filename) "ID_VIDEO_FPS=%5.3f\n", mpctx->sh_video->fps); mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_VIDEO_ASPECT=%1.4f\n", mpctx->sh_video->aspect); - video_start_pts = ds_get_next_pts(mpctx->d_video); + video_start_pts = ds_get_next_pts(mpctx->sh_video->ds); } if (mpctx->sh_audio) { /* Assume FOURCC if all bytes >= 0x20 (' ') */ @@ -482,7 +486,7 @@ static void print_file_properties(struct MPContext *mpctx, const char *filename) "ID_AUDIO_RATE=%d\n", mpctx->sh_audio->samplerate); mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_AUDIO_NCH=%d\n", mpctx->sh_audio->channels); - start_pts = ds_get_next_pts(mpctx->d_audio); + start_pts = ds_get_next_pts(mpctx->sh_audio->ds); } if (video_start_pts != MP_NOPTS_VALUE) { if (start_pts == MP_NOPTS_VALUE || !mpctx->sh_audio || @@ -512,21 +516,73 @@ static void print_file_properties(struct MPContext *mpctx, const char *filename) } } } - if (mpctx->master_demuxer) { - for (int n = 0; n < mpctx->master_demuxer->num_streams; n++) - print_stream(mpctx, mpctx->master_demuxer->streams[n]); + for (int t = 0; t < STREAM_TYPE_COUNT; t++) { + for (int n = 0; n < mpctx->num_tracks; n++) + if (mpctx->tracks[n]->type == t) + print_stream(mpctx, mpctx->tracks[n], n); } } /// step size of mixer changes int volstep = 3; +static void set_demux_field(struct MPContext *mpctx, enum stream_type type, + struct sh_stream *s) +{ + mpctx->sh[type] = s; + // redundant fields for convenience access + switch(type) { + case STREAM_VIDEO: mpctx->sh_video = s ? s->video : NULL; break; + case STREAM_AUDIO: mpctx->sh_audio = s ? s->audio : NULL; break; + case STREAM_SUB: mpctx->sh_sub = s ? s->sub : NULL; break; + } +} + +static void init_demux_stream(struct MPContext *mpctx, enum stream_type type) +{ + struct track *track = mpctx->current_track[type]; + set_demux_field(mpctx, type, track ? track->stream : NULL); + struct sh_stream *stream = mpctx->sh[type]; + if (stream) + demuxer_switch_track(stream->demuxer, type, stream); +} + +static void cleanup_demux_stream(struct MPContext *mpctx, enum stream_type type) +{ + struct sh_stream *stream = mpctx->sh[type]; + if (stream) + demuxer_switch_track(stream->demuxer, type, NULL); + set_demux_field(mpctx, type, NULL); +} + +// Switch the demuxers to current track selection. This is possibly important +// for intialization: if something reads packets from the demuxer (like at least +// reinit_audio_chain does, or when seeking), packets from the other streams +// should be queued instead of discarded. So all streams should be enabled +// before the first initialization function is called. +static void preselect_demux_streams(struct MPContext *mpctx) +{ + // Disable all streams, just to be sure no unwanted streams are selected. + for (int n = 0; n < mpctx->num_sources; n++) { + for (int type = 0; type < STREAM_TYPE_COUNT; type++) + demuxer_switch_track(mpctx->sources[n], type, NULL); + } + + for (int type = 0; type < STREAM_TYPE_COUNT; type++) { + struct track *track = mpctx->current_track[type]; + if (track && track->stream) + demuxer_switch_track(track->stream->demuxer, type, track->stream); + } +} + static void uninit_subs(struct demuxer *demuxer) { for (int i = 0; i < MAX_S_STREAMS; i++) { struct sh_sub *sh = demuxer->s_streams[i]; if (sh && sh->initialized) sub_uninit(sh); + if (sh && is_av_sub(sh->type)) + reset_avsub(sh); } } @@ -540,37 +596,51 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask) mpctx->initialized_flags &= ~INITIALIZED_ACODEC; if (mpctx->sh_audio) uninit_audio(mpctx->sh_audio); - mpctx->sh_audio = NULL; + cleanup_demux_stream(mpctx, STREAM_AUDIO); mpctx->mixer.afilter = NULL; } if (mask & INITIALIZED_SUB) { mpctx->initialized_flags &= ~INITIALIZED_SUB; - if (mpctx->d_sub->sh) - sub_switchoff(mpctx->d_sub->sh, mpctx->osd); + if (mpctx->sh_sub) + sub_switchoff(mpctx->sh_sub, mpctx->osd); + cleanup_demux_stream(mpctx, STREAM_SUB); + reset_subtitles(mpctx); } if (mask & INITIALIZED_VCODEC) { mpctx->initialized_flags &= ~INITIALIZED_VCODEC; if (mpctx->sh_video) uninit_video(mpctx->sh_video); - mpctx->sh_video = NULL; + cleanup_demux_stream(mpctx, STREAM_VIDEO); } if (mask & INITIALIZED_DEMUXER) { mpctx->initialized_flags &= ~INITIALIZED_DEMUXER; + for (int i = 0; i < mpctx->num_tracks; i++) { + struct track *track = mpctx->tracks[i]; + sub_free(track->subdata); +#ifdef CONFIG_ASS + if (track->ass_track) + ass_free_track(track->ass_track); +#endif + talloc_free(track); + } + mpctx->num_tracks = 0; + for (int t = 0; t < STREAM_TYPE_COUNT; t++) + mpctx->current_track[t] = NULL; + assert(!mpctx->sh_video && !mpctx->sh_audio && !mpctx->sh_sub); mpctx->master_demuxer = NULL; - if (mpctx->num_sources) { - mpctx->demuxer = mpctx->sources[0]; - for (int i = 1; i < mpctx->num_sources; i++) { - struct demuxer *demuxer = mpctx->sources[i]; - uninit_subs(demuxer); + for (int i = 0; i < mpctx->num_sources; i++) { + uninit_subs(mpctx->sources[i]); + struct demuxer *demuxer = mpctx->sources[i]; + if (demuxer->stream != mpctx->stream) free_stream(demuxer->stream); - free_demuxer(demuxer); - } + free_demuxer(demuxer); } talloc_free(mpctx->sources); mpctx->sources = NULL; + mpctx->demuxer = NULL; mpctx->num_sources = 0; talloc_free(mpctx->timeline); mpctx->timeline = NULL; @@ -579,12 +649,6 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask) mpctx->chapters = NULL; mpctx->num_chapters = 0; mpctx->video_offset = 0; - if (mpctx->demuxer) { - mpctx->stream = mpctx->demuxer->stream; - uninit_subs(mpctx->demuxer); - free_demuxer(mpctx->demuxer); - } - mpctx->demuxer = NULL; } // kill the cache process: @@ -861,6 +925,56 @@ static bool libmpdemux_was_interrupted(struct MPContext *mpctx) || mpctx->stop_play != AT_END_OF_FILE; } +static int find_new_tid(struct MPContext *mpctx, enum stream_type t) +{ + int new_id = -1; + for (int i = 0; i < mpctx->num_tracks; i++) { + struct track *track = mpctx->tracks[i]; + if (track->type == t) + new_id = FFMAX(new_id, track->user_tid); + } + return new_id + 1; +} + +static struct track *add_stream_track(struct MPContext *mpctx, + struct sh_stream *stream, + bool under_timeline) +{ + for (int i = 0; i < mpctx->num_tracks; i++) { + struct track *track = mpctx->tracks[i]; + if (track->stream == stream) + return track; + } + struct track *track = talloc_ptrtype(NULL, track); + *track = (struct track) { + .type = stream->type, + .user_tid = find_new_tid(mpctx, stream->type), + .demuxer_id = stream->demuxer_id, + .title = stream->title, + .default_track = stream->default_track, + .lang = stream->common_header->lang, + .under_timeline = under_timeline, + .demuxer = stream->demuxer, + .stream = stream, + }; + MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track); + + // Needed for DVD and Blu-Ray. (Note that at least with DVDs and demux_lavf, + // this code is broken: unlike demux_mpg, the demuxer streams are not + // directly mapped to MPEG stream IDs.) + if (!track->lang) + track->lang = talloc_steal(track, demuxer_stream_lang(track->demuxer, + track->stream)); + + return track; +} + +static void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer) +{ + for (int n = 0; n < demuxer->num_streams; n++) + add_stream_track(mpctx, demuxer->streams[n], !!mpctx->timeline); +} + void add_subtitles(struct MPContext *mpctx, char *filename, float fps, int noerr) { @@ -869,7 +983,7 @@ void add_subtitles(struct MPContext *mpctx, char *filename, float fps, struct ass_track *asst = NULL; bool is_native_ass = false; - if (filename == NULL || mpctx->set_of_sub_size >= MAX_SUBTITLE_FILES) + if (filename == NULL) return; #ifdef CONFIG_ASS @@ -895,23 +1009,23 @@ void add_subtitles(struct MPContext *mpctx, char *filename, float fps, return; } - mpctx->set_of_ass_tracks[mpctx->set_of_sub_size] = asst; - mpctx->set_of_subtitles[mpctx->set_of_sub_size] = subd; - mpctx->track_was_native_ass[mpctx->set_of_sub_size] = is_native_ass; - mp_msg(MSGT_IDENTIFY, MSGL_INFO, - "ID_FILE_SUB_ID=%d\n", mpctx->set_of_sub_size); - mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_FILE_SUB_FILENAME=%s\n", filename); - ++mpctx->set_of_sub_size; - mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "SUB: Added subtitle file (%d): %s\n", - mpctx->set_of_sub_size, filename); + struct track *track = talloc_ptrtype(NULL, track); + *track = (struct track) { + .type = STREAM_SUB, + .user_tid = find_new_tid(mpctx, STREAM_SUB), + .demuxer_id = -1, + .is_external = true, + .ass_track = asst, + .native_ass_track = is_native_ass, + .subdata = subd, + }; + MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track); } void init_vo_spudec(struct MPContext *mpctx) { + uninit_player(mpctx, INITIALIZED_SPUDEC); unsigned width, height; - spudec_free(vo_spudec); - mpctx->initialized_flags &= ~INITIALIZED_SPUDEC; - vo_spudec = NULL; // we currently can't work without video stream if (!mpctx->sh_video) @@ -934,8 +1048,8 @@ void init_vo_spudec(struct MPContext *mpctx) } #endif - if (vo_spudec == NULL) { - sh_sub_t *sh = mpctx->d_sub->sh; + if (vo_spudec == NULL && mpctx->sh_sub) { + sh_sub_t *sh = mpctx->sh_sub; vo_spudec = spudec_new_scaled(NULL, width, height, sh->extradata, sh->extradata_len); spudec_set_font_factor(vo_spudec, font_factor); @@ -1444,9 +1558,10 @@ void reinit_audio_chain(struct MPContext *mpctx) { struct MPOpts *opts = &mpctx->opts; struct ao *ao; + init_demux_stream(mpctx, STREAM_AUDIO); if (!mpctx->sh_audio) { uninit_player(mpctx, INITIALIZED_AO); - return; + goto no_audio; } if (!(mpctx->initialized_flags & INITIALIZED_ACODEC)) { if (!init_best_audio_codec(mpctx->sh_audio, audio_codec_list, audio_fm_list)) @@ -1509,8 +1624,10 @@ void reinit_audio_chain(struct MPContext *mpctx) init_error: uninit_player(mpctx, INITIALIZED_ACODEC | INITIALIZED_AO); - mpctx->sh_audio = mpctx->d_audio->sh = NULL; // -> nosound - mpctx->d_audio->id = -2; + cleanup_demux_stream(mpctx, STREAM_AUDIO); +no_audio: + mpctx->current_track[STREAM_AUDIO] = NULL; + mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "Audio: no audio\n"); } @@ -1521,7 +1638,7 @@ static double written_audio_pts(struct MPContext *mpctx) sh_audio_t *sh_audio = mpctx->sh_audio; if (!sh_audio) return MP_NOPTS_VALUE; - demux_stream_t *d_audio = mpctx->d_audio; + demux_stream_t *d_audio = mpctx->sh_audio->ds; // first calculate the end pts of audio that has been output by decoder double a_pts = sh_audio->pts; if (a_pts != MP_NOPTS_VALUE) @@ -1580,54 +1697,58 @@ double playing_audio_pts(struct MPContext *mpctx) return pts - mpctx->opts.playback_speed *ao_get_delay(mpctx->ao); } -static bool is_av_sub(int type) +static void reset_subtitles(struct MPContext *mpctx) { - return type == 'b' || type == 'p' || type == 'x'; + struct sh_sub *sh_sub = mpctx->sh_sub; + int type = sh_sub ? sh_sub->type : '\0'; + + if (sh_sub) + sub_reset(sh_sub, mpctx->osd); + sub_clear_text(&mpctx->subs, MP_NOPTS_VA