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.
This commit is contained in:
wm4 2012-08-19 18:01:30 +02:00
parent 13482fb397
commit 0f155921b0
12 changed files with 696 additions and 1019 deletions

650
command.c
View File

@ -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,51 +2070,45 @@ 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;
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;
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)
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");
res = talloc_asprintf_append(res, "\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;

View File

@ -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) } },

View File

@ -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,

View File

@ -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;

View File

@ -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)
void demuxer_switch_track(struct demuxer *demuxer, enum stream_type type,
struct sh_stream *stream)
{
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;
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)
char *demuxer_stream_lang(demuxer_t *d, struct sh_stream *sh)
{
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)
{
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;
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);
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;
}
}
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;
}
if (stream_control(d->stream, STREAM_CTRL_GET_LANG, &req) == STREAM_OK)
return req.name;
return NULL;
}

View File

@ -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);

View File

@ -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; \

View File

@ -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);

755
mplayer.c

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,11 @@
#include "spudec.h"
#include "av_sub.h"
bool is_av_sub(int type)
{
return type == 'b' || type == 'p' || type == 'x';
}
void reset_avsub(struct sh_sub *sh)
{
if (sh->context) {

View File

@ -26,5 +26,6 @@ struct sh_sub;
void reset_avsub(struct sh_sub *sh);
int decode_avsub(struct sh_sub *sh, uint8_t *data, int size,
double pts, double endpts);
bool is_av_sub(int type);
#endif /* MPLAYER_AV_SUB_H */

View File

@ -289,7 +289,8 @@ void osd_draw_text_ext(struct osd_state *osd, int dxs, int dys,
if(obj->flags&OSDFLAG_VISIBLE){
switch(obj->type){
case OSDTYPE_SPU:
vo_draw_spudec_sub(obj, draw_alpha, ctx); // FIXME
if (vo_spudec)
vo_draw_spudec_sub(obj, draw_alpha, ctx); // FIXME
break;
case OSDTYPE_OSD:
case OSDTYPE_SUBTITLE: