/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ #include #include #include #include #include #include "mpv_talloc.h" #include "common/msg.h" #include "options/options.h" #include "common/common.h" #include "stream/stream.h" #include "sub/dec_sub.h" #include "demux/demux.h" #include "video/mp_image.h" #include "core.h" // 0: primary sub, 1: secondary sub, -1: not selected static int get_order(struct MPContext *mpctx, struct track *track) { for (int n = 0; n < num_ptracks[STREAM_SUB]; n++) { if (mpctx->current_track[n][STREAM_SUB] == track) return n; } return -1; } static void reset_subtitles(struct MPContext *mpctx, struct track *track) { if (track->d_sub) { sub_reset(track->d_sub); sub_set_play_dir(track->d_sub, mpctx->play_dir); } term_osd_set_subs(mpctx, NULL); } // Only matters for subs on an image. void redraw_subs(struct MPContext *mpctx) { for (int n = 0; n < num_ptracks[STREAM_SUB]; n++) { if (mpctx->current_track[n][STREAM_SUB] && mpctx->current_track[n][STREAM_SUB]->d_sub) { mpctx->redraw_subs = true; break; } } } void reset_subtitle_state(struct MPContext *mpctx) { for (int n = 0; n < mpctx->num_tracks; n++) reset_subtitles(mpctx, mpctx->tracks[n]); term_osd_set_subs(mpctx, NULL); } void uninit_sub(struct MPContext *mpctx, struct track *track) { if (track && track->d_sub) { reset_subtitles(mpctx, track); sub_select(track->d_sub, false); int order = get_order(mpctx, track); osd_set_sub(mpctx->osd, order, NULL); sub_destroy(track->d_sub); track->d_sub = NULL; } } void uninit_sub_all(struct MPContext *mpctx) { for (int n = 0; n < mpctx->num_tracks; n++) uninit_sub(mpctx, mpctx->tracks[n]); } static bool update_subtitle(struct MPContext *mpctx, double video_pts, struct track *track) { struct dec_sub *dec_sub = track ? track->d_sub : NULL; if (!dec_sub || video_pts == MP_NOPTS_VALUE) return true; if (mpctx->vo_chain) { struct mp_image_params params = mpctx->vo_chain->filter->input_params; if (params.imgfmt) sub_control(dec_sub, SD_CTRL_SET_VIDEO_PARAMS, ¶ms); } // Checking if packets have special animations is relatively expensive. // This is only needed if we are rendering ASS subtitles with no video // being played. bool still_image = mpctx->video_out && ((mpctx->video_status == STATUS_EOF && mpctx->opts->subs_rend->sub_past_video_end) || !mpctx->current_track[0][STREAM_VIDEO] || mpctx->current_track[0][STREAM_VIDEO]->image); sub_control(dec_sub, SD_CTRL_SET_ANIMATED_CHECK, &still_image); if (track->demuxer->fully_read && sub_can_preload(dec_sub)) { // Assume fully_read implies no interleaved audio/video streams. // (Reading packets will change the demuxer position.) demux_seek(track->demuxer, 0, 0); sub_preload(dec_sub); } bool packets_read = false; bool sub_updated = false; sub_read_packets(dec_sub, video_pts, mpctx->paused, &packets_read, &sub_updated); double osd_pts = osd_get_force_video_pts(mpctx->osd); // Check if we need to update subtitles for these special cases. Always // update on discontinuities like seeking or a new file. if (sub_updated || mpctx->redraw_subs || osd_pts == MP_NOPTS_VALUE) { // Always force a redecode of all packets if we have a refresh. if (mpctx->redraw_subs) sub_redecode_cached_packets(dec_sub); // Handle displaying subtitles on terminal; never done for secondary subs if (mpctx->current_track[0][STREAM_SUB] == track && !mpctx->video_out) { char *text = sub_get_text(dec_sub, video_pts, SD_TEXT_TYPE_PLAIN); term_osd_set_subs(mpctx, text); talloc_free(text); } // Handle displaying subtitles on VO with no video being played. This is // quite different, because normally subtitles are redrawn on new video // frames, using the video frames' timestamps. if (still_image) { if (osd_pts != video_pts) { osd_set_force_video_pts(mpctx->osd, video_pts); osd_query_and_reset_want_redraw(mpctx->osd); vo_redraw(mpctx->video_out); } } } mpctx->redraw_subs = false; return packets_read; } // Return true if the subtitles for the given PTS are ready; false if the player // should wait for new demuxer data, and then should retry. bool update_subtitles(struct MPContext *mpctx, double video_pts) { bool ok = true; for (int n = 0; n < num_ptracks[STREAM_SUB]; n++) ok &= update_subtitle(mpctx, video_pts, mpctx->current_track[n][STREAM_SUB]); return ok; } static struct attachment_list *get_all_attachments(struct MPContext *mpctx) { struct attachment_list *list = talloc_zero(NULL, struct attachment_list); struct demuxer *prev_demuxer = NULL; for (int n = 0; n < mpctx->num_tracks; n++) { struct track *t = mpctx->tracks[n]; if (!t->demuxer || prev_demuxer == t->demuxer) continue; prev_demuxer = t->demuxer; for (int i = 0; i < t->demuxer->num_attachments; i++) { struct demux_attachment *att = &t->demuxer->attachments[i]; struct demux_attachment copy = { .name = talloc_strdup(list, att->name), .type = talloc_strdup(list, att->type), .data = talloc_memdup(list, att->data, att->data_size), .data_size = att->data_size, }; MP_TARRAY_APPEND(list, list->entries, list->num_entries, copy); } } return list; } static bool init_subdec(struct MPContext *mpctx, struct track *track) { assert(!track->d_sub); if (!track->demuxer || !track->stream) return false; track->d_sub = sub_create(mpctx->global, track, get_all_attachments(mpctx), get_order(mpctx, track)); if (!track->d_sub) return false; struct track *vtrack = mpctx->current_track[0][STREAM_VIDEO]; struct mp_codec_params *v_c = vtrack && vtrack->stream ? vtrack->stream->codec : NULL; double fps = v_c ? v_c->fps : 25; sub_control(track->d_sub, SD_CTRL_SET_VIDEO_DEF_FPS, &fps); return true; } void reinit_sub(struct MPContext *mpctx, struct track *track) { if (!track || !track->stream || track->stream->type != STREAM_SUB) return; assert(!track->d_sub); if (!init_subdec(mpctx, track)) { error_on_track(mpctx, track); return; } sub_select(track->d_sub, true); int order = get_order(mpctx, track); osd_set_sub(mpctx->osd, order, track->d_sub); // When paused we have to wait for packets to be available. // Retry on a timeout until we get a packet. If still not successful, // then queue it for later in the playloop (but this will have a delay). if (mpctx->playback_initialized) { track->demuxer_ready = false; int64_t end = mp_time_ns() + MP_TIME_MS_TO_NS(50); while (!track->demuxer_ready && mp_time_ns() < end) track->demuxer_ready = update_subtitles(mpctx, mpctx->playback_pts) || !mpctx->paused; if (!track->demuxer_ready) mp_wakeup_core(mpctx); } } void reinit_sub_all(struct MPContext *mpctx) { for (int n = 0; n < num_ptracks[STREAM_SUB]; n++) reinit_sub(mpctx, mpctx->current_track[n][STREAM_SUB]); }