diff options
Diffstat (limited to 'sub/dec_sub.c')
-rw-r--r-- | sub/dec_sub.c | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/sub/dec_sub.c b/sub/dec_sub.c new file mode 100644 index 0000000..18d826e --- /dev/null +++ b/sub/dec_sub.c @@ -0,0 +1,498 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <math.h> +#include <assert.h> + +#include "demux/demux.h" +#include "sd.h" +#include "dec_sub.h" +#include "options/m_config.h" +#include "options/options.h" +#include "common/global.h" +#include "common/msg.h" +#include "common/recorder.h" +#include "misc/dispatch.h" +#include "osdep/threads.h" + +extern const struct sd_functions sd_ass; +extern const struct sd_functions sd_lavc; + +static const struct sd_functions *const sd_list[] = { + &sd_lavc, + &sd_ass, + NULL +}; + +struct dec_sub { + mp_mutex lock; + + struct mp_log *log; + struct mpv_global *global; + struct mp_subtitle_opts *opts; + struct m_config_cache *opts_cache; + + struct mp_recorder_sink *recorder_sink; + + struct attachment_list *attachments; + + struct sh_stream *sh; + int play_dir; + int order; + double last_pkt_pts; + bool preload_attempted; + double video_fps; + double sub_speed; + + struct mp_codec_params *codec; + double start, end; + + double last_vo_pts; + struct sd *sd; + + struct demux_packet *new_segment; + struct demux_packet *cached_pkts[2]; +}; + +static void update_subtitle_speed(struct dec_sub *sub) +{ + struct mp_subtitle_opts *opts = sub->opts; + sub->sub_speed = 1.0; + + if (sub->video_fps > 0 && sub->codec->frame_based > 0) { + MP_VERBOSE(sub, "Frame based format, dummy FPS: %f, video FPS: %f\n", + sub->codec->frame_based, sub->video_fps); + sub->sub_speed *= sub->codec->frame_based / sub->video_fps; + } + + if (opts->sub_fps && sub->video_fps) + sub->sub_speed *= opts->sub_fps / sub->video_fps; + + sub->sub_speed *= opts->sub_speed; +} + +// Return the subtitle PTS used for a given video PTS. +static double pts_to_subtitle(struct dec_sub *sub, double pts) +{ + struct mp_subtitle_opts *opts = sub->opts; + + if (pts != MP_NOPTS_VALUE) + pts = (pts * sub->play_dir - opts->sub_delay) / sub->sub_speed; + + return pts; +} + +static double pts_from_subtitle(struct dec_sub *sub, double pts) +{ + struct mp_subtitle_opts *opts = sub->opts; + + if (pts != MP_NOPTS_VALUE) + pts = (pts * sub->sub_speed + opts->sub_delay) * sub->play_dir; + + return pts; +} + +static void wakeup_demux(void *ctx) +{ + struct mp_dispatch_queue *q = ctx; + mp_dispatch_interrupt(q); +} + +void sub_destroy(struct dec_sub *sub) +{ + if (!sub) + return; + demux_set_stream_wakeup_cb(sub->sh, NULL, NULL); + if (sub->sd) { + sub_reset(sub); + sub->sd->driver->uninit(sub->sd); + } + talloc_free(sub->sd); + mp_mutex_destroy(&sub->lock); + talloc_free(sub); +} + +static struct sd *init_decoder(struct dec_sub *sub) +{ + for (int n = 0; sd_list[n]; n++) { + const struct sd_functions *driver = sd_list[n]; + struct sd *sd = talloc(NULL, struct sd); + *sd = (struct sd){ + .global = sub->global, + .log = mp_log_new(sd, sub->log, driver->name), + .opts = sub->opts, + .driver = driver, + .attachments = sub->attachments, + .codec = sub->codec, + .preload_ok = true, + }; + + if (sd->driver->init(sd) >= 0) + return sd; + + talloc_free(sd); + } + + MP_ERR(sub, "Could not find subtitle decoder for format '%s'.\n", + sub->codec->codec); + return NULL; +} + +// Thread-safety of the returned object: all functions are thread-safe, +// except sub_get_bitmaps() and sub_get_text(). Decoder backends (sd_*) +// do not need to acquire locks. +// Ownership of attachments goes to the callee, and is released with +// talloc_free() (even on failure). +struct dec_sub *sub_create(struct mpv_global *global, struct track *track, + struct attachment_list *attachments, int order) +{ + assert(track->stream && track->stream->type == STREAM_SUB); + + struct dec_sub *sub = talloc(NULL, struct dec_sub); + *sub = (struct dec_sub){ + .log = mp_log_new(sub, global->log, "sub"), + .global = global, + .opts_cache = m_config_cache_alloc(sub, global, &mp_subtitle_sub_opts), + .sh = track->stream, + .codec = track->stream->codec, + .attachments = talloc_steal(sub, attachments), + .play_dir = 1, + .order = order, + .last_pkt_pts = MP_NOPTS_VALUE, + .last_vo_pts = MP_NOPTS_VALUE, + .start = MP_NOPTS_VALUE, + .end = MP_NOPTS_VALUE, + }; + sub->opts = sub->opts_cache->opts; + mp_mutex_init_type(&sub->lock, MP_MUTEX_RECURSIVE); + + sub->sd = init_decoder(sub); + if (sub->sd) { + update_subtitle_speed(sub); + return sub; + } + + sub_destroy(sub); + return NULL; +} + +// Called locked. +static void update_segment(struct dec_sub *sub) +{ + if (sub->new_segment && sub->last_vo_pts != MP_NOPTS_VALUE && + sub->last_vo_pts >= sub->new_segment->start) + { + MP_VERBOSE(sub, "Switch segment: %f at %f\n", sub->new_segment->start, + sub->last_vo_pts); + + sub->codec = sub->new_segment->codec; + sub->start = sub->new_segment->start; + sub->end = sub->new_segment->end; + struct sd *new = init_decoder(sub); + if (new) { + sub->sd->driver->uninit(sub->sd); + talloc_free(sub->sd); + sub->sd = new; + update_subtitle_speed(sub); + sub_control(sub, SD_CTRL_SET_TOP, &sub->order); + } else { + // We'll just keep the current decoder, and feed it possibly + // invalid data (not our fault if it crashes or something). + MP_ERR(sub, "Can't change to new codec.\n"); + } + sub->sd->driver->decode(sub->sd, sub->new_segment); + talloc_free(sub->new_segment); + sub->new_segment = NULL; + } +} + +bool sub_can_preload(struct dec_sub *sub) +{ + bool r; + mp_mutex_lock(&sub->lock); + r = sub->sd->driver->accept_packets_in_advance && !sub->preload_attempted; + mp_mutex_unlock(&sub->lock); + return r; +} + +void sub_preload(struct dec_sub *sub) +{ + mp_mutex_lock(&sub->lock); + + struct mp_dispatch_queue *demux_waiter = mp_dispatch_create(NULL); + demux_set_stream_wakeup_cb(sub->sh, wakeup_demux, demux_waiter); + + sub->preload_attempted = true; + + for (;;) { + struct demux_packet *pkt = NULL; + int r = demux_read_packet_async(sub->sh, &pkt); + if (r == 0) { + mp_dispatch_queue_process(demux_waiter, INFINITY); + continue; + } + if (!pkt) + break; + sub->sd->driver->decode(sub->sd, pkt); + talloc_free(pkt); + } + + demux_set_stream_wakeup_cb(sub->sh, NULL, NULL); + talloc_free(demux_waiter); + + mp_mutex_unlock(&sub->lock); +} + +static bool is_new_segment(struct dec_sub *sub, struct demux_packet *p) +{ + return p->segmented && + (p->start != sub->start || p->end != sub->end || p->codec != sub->codec); +} + +// Read packets from the demuxer stream passed to sub_create(). Return true if +// enough packets were read, false if the player should wait until the demuxer +// signals new packets available (and then should retry). +bool sub_read_packets(struct dec_sub *sub, double video_pts, bool force) +{ + bool r = true; + mp_mutex_lock(&sub->lock); + video_pts = pts_to_subtitle(sub, video_pts); + while (1) { + bool read_more = true; + if (sub->sd->driver->accepts_packet) + read_more = sub->sd->driver->accepts_packet(sub->sd, video_pts); + + if (!read_more) + break; + + if (sub->new_segment && sub->new_segment->start < video_pts) { + sub->last_vo_pts = video_pts; + update_segment(sub); + } + + if (sub->new_segment) + break; + + // (Use this mechanism only if sub_delay matters to avoid corner cases.) + double min_pts = sub->opts->sub_delay < 0 || force ? video_pts : MP_NOPTS_VALUE; + + struct demux_packet *pkt; + int st = demux_read_packet_async_until(sub->sh, min_pts, &pkt); + // Note: "wait" (st==0) happens with non-interleaved streams only, and + // then we should stop the playloop until a new enough packet has been + // seen (or the subtitle decoder's queue is full). This usually does not + // happen for interleaved subtitle streams, which never return "wait" + // when reading, unless min_pts is set. + if (st <= 0) { + r = st < 0 || (sub->last_pkt_pts != MP_NOPTS_VALUE && + sub->last_pkt_pts > video_pts); + break; + } + + if (sub->recorder_sink) + mp_recorder_feed_packet(sub->recorder_sink, pkt); + + + // Update cached packets + if (sub->cached_pkts[0]) { + if (sub->cached_pkts[1]) + talloc_free(sub->cached_pkts[1]); + sub->cached_pkts[1] = sub->cached_pkts[0]; + } + sub->cached_pkts[0] = pkt; + + sub->last_pkt_pts = pkt->pts; + + if (is_new_segment(sub, pkt)) { + sub->new_segment = demux_copy_packet(pkt); + // Note that this can be delayed to a much later point in time. + update_segment(sub); + break; + } + + if (!(sub->preload_attempted && sub->sd->preload_ok)) + sub->sd->driver->decode(sub->sd, pkt); + } + mp_mutex_unlock(&sub->lock); + return r; +} + +// Redecode both cached packets if needed. +// Used with UPDATE_SUB_HARD and UPDATE_SUB_FILT. +void sub_redecode_cached_packets(struct dec_sub *sub) +{ + mp_mutex_lock(&sub->lock); + if (sub->cached_pkts[0]) + sub->sd->driver->decode(sub->sd, sub->cached_pkts[0]); + if (sub->cached_pkts[1]) + sub->sd->driver->decode(sub->sd, sub->cached_pkts[1]); + mp_mutex_unlock(&sub->lock); +} + +// Unref sub_bitmaps.rc to free the result. May return NULL. +struct sub_bitmaps *sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, + int format, double pts) +{ + mp_mutex_lock(&sub->lock); + + pts = pts_to_subtitle(sub, pts); + + sub->last_vo_pts = pts; + update_segment(sub); + + struct sub_bitmaps *res = NULL; + + if (!(sub->end != MP_NOPTS_VALUE && pts >= sub->end) && + sub->sd->driver->get_bitmaps) + res = sub->sd->driver->get_bitmaps(sub->sd, dim, format, pts); + + mp_mutex_unlock(&sub->lock); + return res; +} + +// The returned string is talloc'ed. +char *sub_get_text(struct dec_sub *sub, double pts, enum sd_text_type type) +{ + mp_mutex_lock(&sub->lock); + char *text = NULL; + + pts = pts_to_subtitle(sub, pts); + + sub->last_vo_pts = pts; + update_segment(sub); + + if (sub->sd->driver->get_text) + text = sub->sd->driver->get_text(sub->sd, pts, type); + mp_mutex_unlock(&sub->lock); + return text; +} + +char *sub_ass_get_extradata(struct dec_sub *sub) +{ + if (strcmp(sub->sd->codec->codec, "ass") != 0) + return NULL; + char *extradata = sub->sd->codec->extradata; + int extradata_size = sub->sd->codec->extradata_size; + return talloc_strndup(NULL, extradata, extradata_size); +} + +struct sd_times sub_get_times(struct dec_sub *sub, double pts) +{ + mp_mutex_lock(&sub->lock); + struct sd_times res = { .start = MP_NOPTS_VALUE, .end = MP_NOPTS_VALUE }; + + pts = pts_to_subtitle(sub, pts); + + sub->last_vo_pts = pts; + update_segment(sub); + + if (sub->sd->driver->get_times) + res = sub->sd->driver->get_times(sub->sd, pts); + + mp_mutex_unlock(&sub->lock); + return res; +} + +void sub_reset(struct dec_sub *sub) +{ + mp_mutex_lock(&sub->lock); + if (sub->sd->driver->reset) + sub->sd->driver->reset(sub->sd); + sub->last_pkt_pts = MP_NOPTS_VALUE; + sub->last_vo_pts = MP_NOPTS_VALUE; + TA_FREEP(&sub->cached_pkts[0]); + TA_FREEP(&sub->cached_pkts[1]); + TA_FREEP(&sub->new_segment); + mp_mutex_unlock(&sub->lock); +} + +void sub_select(struct dec_sub *sub, bool selected) +{ + mp_mutex_lock(&sub->lock); + if (sub->sd->driver->select) + sub->sd->driver->select(sub->sd, selected); + mp_mutex_unlock(&sub->lock); +} + +int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg) +{ + int r = CONTROL_UNKNOWN; + mp_mutex_lock(&sub->lock); + bool propagate = false; + switch (cmd) { + case SD_CTRL_SET_VIDEO_DEF_FPS: + sub->video_fps = *(double *)arg; + update_subtitle_speed(sub); + break; + case SD_CTRL_SUB_STEP: { + double *a = arg; + double arg2[2] = {a[0], a[1]}; + arg2[0] = pts_to_subtitle(sub, arg2[0]); + if (sub->sd->driver->control) + r = sub->sd->driver->control(sub->sd, cmd, arg2); + if (r == CONTROL_OK) + a[0] = pts_from_subtitle(sub, arg2[0]); + break; + } + case SD_CTRL_UPDATE_OPTS: { + int flags = (uintptr_t)arg; + if (m_config_cache_update(sub->opts_cache)) + update_subtitle_speed(sub); + propagate = true; + if (flags & UPDATE_SUB_HARD) { + // forget about the previous preload because + // UPDATE_SUB_HARD will cause a sub reinit + // that clears all preloaded sub packets + sub->preload_attempted = false; + } + break; + } + default: + propagate = true; + } + if (propagate && sub->sd->driver->control) + r = sub->sd->driver->control(sub->sd, cmd, arg); + mp_mutex_unlock(&sub->lock); + return r; +} + +void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink) +{ + mp_mutex_lock(&sub->lock); + sub->recorder_sink = sink; + mp_mutex_unlock(&sub->lock); +} + +void sub_set_play_dir(struct dec_sub *sub, int dir) +{ + mp_mutex_lock(&sub->lock); + sub->play_dir = dir; + mp_mutex_unlock(&sub->lock); +} + +bool sub_is_primary_visible(struct dec_sub *sub) +{ + return !!sub->opts->sub_visibility; +} + +bool sub_is_secondary_visible(struct dec_sub *sub) +{ + return !!sub->opts->sec_sub_visibility; +} |