diff options
author | wm4 <wm4@nowhere> | 2017-02-07 17:05:17 +0100 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2017-02-07 17:05:17 +0100 |
commit | 96a45a16af5594900ca94e7d4abb18d1e6d5ed4a (patch) | |
tree | d08473b07adfa7963ce6735c72af1e5d220930a9 | |
parent | 061b752217d15d41496ca6e4777835fcd945e237 (diff) | |
download | mpv-96a45a16af5594900ca94e7d4abb18d1e6d5ed4a.tar.bz2 mpv-96a45a16af5594900ca94e7d4abb18d1e6d5ed4a.tar.xz |
player: add experimental stream recording feature
This is basically a WIP, but it can't remain in a branch forever. A
warning is print when using it as it's still a bit "shaky".
-rw-r--r-- | DOCS/interface-changes.rst | 1 | ||||
-rw-r--r-- | DOCS/man/options.rst | 23 | ||||
-rw-r--r-- | audio/decode/dec_audio.c | 4 | ||||
-rw-r--r-- | audio/decode/dec_audio.h | 2 | ||||
-rw-r--r-- | common/recorder.c | 384 | ||||
-rw-r--r-- | common/recorder.h | 21 | ||||
-rw-r--r-- | options/options.c | 2 | ||||
-rw-r--r-- | options/options.h | 1 | ||||
-rw-r--r-- | player/command.c | 22 | ||||
-rw-r--r-- | player/core.h | 8 | ||||
-rw-r--r-- | player/loadfile.c | 93 | ||||
-rw-r--r-- | player/playloop.c | 3 | ||||
-rw-r--r-- | sub/dec_sub.c | 14 | ||||
-rw-r--r-- | sub/dec_sub.h | 2 | ||||
-rw-r--r-- | video/decode/dec_video.c | 19 | ||||
-rw-r--r-- | video/decode/dec_video.h | 2 | ||||
-rw-r--r-- | wscript_build.py | 1 |
17 files changed, 597 insertions, 5 deletions
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index c04b62a422..a032e7c721 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -32,6 +32,7 @@ Interface changes - "vo-drop-frame-count" to "frame-drop-count" The old names still work, but are deprecated. - remove the --stream-capture option and property. No replacement. + (--stream-record might serve as alternative) - add --sub-justify - add --sub-ass-justify - internally there's a different way to enable the demuxer cache now diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 13fd4351ca..afdf5b2dd1 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -4863,6 +4863,29 @@ Miscellaneous This does not affect playlist expansion, redirection, or other loading of referenced files like with ordered chapters. +``--record-stream=<file>`` + Record the current stream to the given target file. The target file will + always be overwritten without asking. + + This remuxes the source stream without reencoding, which makes this a + highly fragile and experimental feature. It's entirely possible that this + writes files which are broken, not standards compliant, not playable with + all players (including mpv), or incomplete. + + The target file format is determined by the file extension of the target + filename. It is recommended to use the same target container as the source + container if possible, and preferring Matroska as fallback. + + Seeking during stream recording, or enabling/disabling stream recording + during playback, can cut off data, or produce "holes" in the output file. + These are technical restrictions. In particular, video data or subtitles + which were read ahead can produce such holes, which might cause playback + problems with various players (including mpv). + + The behavior of this option might changed in the future, such as changing + it to a template (similar to ``--screenshot-template``), being renamed, + removed, or anything else, until it is declared semi-stable. + ``--lavfi-complex=<string>`` Set a "complex" libavfilter filter, which means a single filter graph can take input from multiple source audio and video tracks. The graph can result diff --git a/audio/decode/dec_audio.c b/audio/decode/dec_audio.c index 56f0fe59ad..5a2735ef20 100644 --- a/audio/decode/dec_audio.c +++ b/audio/decode/dec_audio.c @@ -27,6 +27,7 @@ #include "common/codecs.h" #include "common/msg.h" +#include "common/recorder.h" #include "misc/bstr.h" #include "stream/stream.h" @@ -218,6 +219,9 @@ void audio_work(struct dec_audio *da) } if (da->ad_driver->send_packet(da, da->packet)) { + if (da->recorder_sink) + mp_recorder_feed_packet(da->recorder_sink, da->packet); + talloc_free(da->packet); da->packet = NULL; } diff --git a/audio/decode/dec_audio.h b/audio/decode/dec_audio.h index ebe7c8ae5b..02447d6742 100644 --- a/audio/decode/dec_audio.h +++ b/audio/decode/dec_audio.h @@ -37,6 +37,8 @@ struct dec_audio { bool try_spdif; + struct mp_recorder_sink *recorder_sink; + // For free use by the ad_driver void *priv; diff --git a/common/recorder.c b/common/recorder.c new file mode 100644 index 0000000000..d8f937b902 --- /dev/null +++ b/common/recorder.c @@ -0,0 +1,384 @@ +/* + * 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 <math.h> + +#include <libavformat/avformat.h> + +#include "common/av_common.h" +#include "common/common.h" +#include "common/global.h" +#include "common/msg.h" +#include "demux/packet.h" +#include "demux/stheader.h" + +#include "recorder.h" + +#define PTS_ADD(a, b) ((a) == MP_NOPTS_VALUE ? (a) : ((a) + (b))) + +// Maximum number of packets we buffer at most to attempt to resync streams. +// Essentially, this should be higher than the highest supported keyframe +// interval. +#define QUEUE_MAX_PACKETS 256 +// Number of packets we should buffer at least to determine timestamps (due to +// codec delay and frame reordering, and potentially lack of DTS). +// Keyframe flags can trigger this earlier. +#define QUEUE_MIN_PACKETS 16 + +struct mp_recorder { + struct mpv_global *global; + struct mp_log *log; + + struct mp_recorder_sink **streams; + int num_streams; + + bool opened; // mux context is valid + bool muxing; // we're currently recording (instead of preparing) + bool muxing_from_start; // no discontinuity at start + bool dts_warning; + + // The start timestamp of the currently recorded segment (the timestamp of + // the first packet of the incoming packet stream). + double base_ts; + // The output packet timestamp corresponding to base_ts. It's the timestamp + // of the first packet of the current segment written to the output. + double rebase_ts; + + AVFormatContext *mux; +}; + +struct mp_recorder_sink { + struct mp_recorder *owner; + struct sh_stream *sh; + AVStream *av_stream; + double max_out_pts; + bool discont; + bool proper_eof; + struct demux_packet **packets; + int num_packets; +}; + +static int add_stream(struct mp_recorder *priv, struct sh_stream *sh) +{ + enum AVMediaType av_type = mp_to_av_stream_type(sh->type); + if (av_type == AVMEDIA_TYPE_UNKNOWN) + return -1; + + struct mp_recorder_sink *rst = talloc(priv, struct mp_recorder_sink); + *rst = (struct mp_recorder_sink) { + .owner = priv, + .sh = sh, + .av_stream = avformat_new_stream(priv->mux, NULL), + .max_out_pts = MP_NOPTS_VALUE, + }; + + if (!rst->av_stream) + return -1; + + AVCodecParameters *avp = mp_codec_params_to_av(sh->codec); + if (!avp) + return -1; + +#if LIBAVCODEC_VERSION_MICRO >= 100 + // We don't know the delay, so make something up. If the format requires + // DTS, the result will probably be broken. FFmpeg provides nothing better + // yet (unless you demux with libavformat, which contains tons of hacks + // that try to determine a PTS). + if (!sh->codec->lav_codecpar) + avp->video_delay = 16; +#endif + + if (avp->codec_id == AV_CODEC_ID_NONE) + return -1; + + if (avcodec_parameters_copy(rst->av_stream->codecpar, avp) < 0) + return -1; + + rst->av_stream->time_base = mp_get_codec_timebase(sh->codec); + + MP_TARRAY_APPEND(priv, priv->streams, priv->num_streams, rst); + return 0; +} + +struct mp_recorder *mp_recorder_create(struct mpv_global *global, + const char *target_file, + struct sh_stream **streams, + int num_streams) +{ + struct mp_recorder *priv = talloc_zero(NULL, struct mp_recorder); + + priv->global = global; + priv->log = mp_log_new(priv, global->log, "recorder"); + + if (!num_streams) { + MP_ERR(priv, "No streams.\n"); + goto error; + } + + priv->mux = avformat_alloc_context(); + if (!priv->mux) + goto error; + + priv->mux->oformat = av_guess_format(NULL, target_file, NULL); + if (!priv->mux->oformat) { + MP_ERR(priv, "Output format not found.\n"); + goto error; + } + + if (avio_open2(&priv->mux->pb, target_file, AVIO_FLAG_WRITE, NULL, NULL) < 0) { + MP_ERR(priv, "Failed opening output file.\n"); + goto error; + } + + for (int n = 0; n < num_streams; n++) { + if (add_stream(priv, streams[n]) < 0) { + MP_ERR(priv, "Can't mux one of the input streams.\n"); + goto error; + } + } + + // Not sure how to write this in a "standard" way. It appears only mkv + // and mp4 support this directly. + char version[200]; + snprintf(version, sizeof(version), "%s experimental stream recording " + "feature (can generate broken files - please report bugs)", + mpv_version); + av_dict_set(&priv->mux->metadata, "encoding_tool", version, 0); + + if (avformat_write_header(priv->mux, NULL) < 0) { + MP_ERR(priv, "Write header failed.\n"); + goto error; + } + + priv->opened = true; + priv->muxing_from_start = true; + + priv->base_ts = MP_NOPTS_VALUE; + priv->rebase_ts = 0; + + MP_WARN(priv, "This is an experimental feature. Output files might be " + "broken or not play correctly with various players " + "(including mpv itself).\n"); + + return priv; + +error: + mp_recorder_destroy(priv); + return NULL; +} + +static void flush_packets(struct mp_recorder *priv) +{ + for (int n = 0; n < priv->num_streams; n++) { + struct mp_recorder_sink *rst = priv->streams[n]; + for (int i = 0; i < rst->num_packets; i++) + talloc_free(rst->packets[i]); + rst->num_packets = 0; + } +} + +static void mux_packet(struct mp_recorder_sink *rst, + struct demux_packet *pkt) +{ + struct mp_recorder *priv = rst->owner; + struct demux_packet mpkt = *pkt; + + double diff = priv->rebase_ts - priv->base_ts; + mpkt.pts = PTS_ADD(mpkt.pts, diff); + mpkt.dts = PTS_ADD(mpkt.dts, diff); + + rst->max_out_pts = MPMAX(rst->max_out_pts, pkt->pts); + + AVPacket avpkt; + mp_set_av_packet(&avpkt, &mpkt, &rst->av_stream->time_base); + + avpkt.stream_index = rst->av_stream->index; + + if (avpkt.duration < 0 && rst->sh->type != STREAM_SUB) + avpkt.duration = 0; + + AVPacket *new_packet = av_packet_clone(&avpkt); + if (!new_packet) { + MP_ERR(priv, "Failed to allocate packet.\n"); + return; + } + + if (av_interleaved_write_frame(priv->mux, new_packet) < 0) + MP_ERR(priv, "Failed writing packet.\n"); +} + +// Write all packets that currently can be written. +static void mux_packets(struct mp_recorder_sink *rst, bool force) +{ + struct mp_recorder *priv = rst->owner; + if (!priv->muxing || !rst->num_packets) + return; + + int safe_count = 0; + for (int n = 0; n < rst->num_packets; n++) { + if (rst->packets[n]->keyframe) + safe_count = n; + } + if (force) + safe_count = rst->num_packets; + + for (int n = 0; n < safe_count; n++) { + mux_packet(rst, rst->packets[n]); + talloc_free(rst->packets[n]); + } + + // Remove packets[0..safe_count] + memmove(&rst->packets[0], &rst->packets[safe_count], + (rst->num_packets - safe_count) * sizeof(rst->packets[0])); + rst->num_packets -= safe_count; +} + +// If there was a discontinuity, check whether we can resume muxing (and from +// where). +static void check_restart(struct mp_recorder *priv) +{ + if (priv->muxing) + return; + + double min_ts = INFINITY; + double rebase_ts = 0; + for (int n = 0; n < priv->num_streams; n++) { + struct mp_recorder_sink *rst = priv->streams[n]; + int min_packets = rst->sh->type == STREAM_VIDEO ? QUEUE_MIN_PACKETS : 1; + + rebase_ts = MPMAX(rebase_ts, rst->max_out_pts); + + if (rst->num_packets < min_packets) { + if (!rst->proper_eof && rst->sh->type != STREAM_SUB) + return; + continue; + } + + for (int i = 0; i < min_packets; i++) + min_ts = MPMIN(min_ts, rst->packets[i]->pts); + } + + // Subtitle only stream (wait longer) or stream without any PTS (fuck it). + if (!isfinite(min_ts)) + return; + + priv->rebase_ts = rebase_ts; + priv->base_ts = min_ts; + + for (int n = 0; n < priv->num_streams; n++) { + struct mp_recorder_sink *rst = priv->streams[n]; + rst->max_out_pts = min_ts; + } + + priv->muxing = true; + + if (!priv->muxing_from_start) + MP_WARN(priv, "Discontinuity at timestamp %f.\n", priv->rebase_ts); +} + +void mp_recorder_destroy(struct mp_recorder *priv) +{ + if (priv->opened) { + for (int n = 0; n < priv->num_streams; n++) { + struct mp_recorder_sink *rst = priv->streams[n]; + if (!rst->proper_eof) + continue; + mux_packets(rst, true); + } + + if (av_write_trailer(priv->mux) < 0) + MP_ERR(priv, "Writing trailer failed.\n"); + } + + if (priv->mux) { + if (avio_closep(&priv->mux->pb) < 0) + MP_ERR(priv, "Closing file failed\n"); + + avformat_free_context(priv->mux); + } + + flush_packets(priv); + talloc_free(priv); +} + +// This is called on a seek, or when recording was started mid-stream. +void mp_recorder_mark_discontinuity(struct mp_recorder *priv) +{ + flush_packets(priv); + + for (int n = 0; n < priv->num_streams; n++) { + struct mp_recorder_sink *rst = priv->streams[n]; + rst->discont = true; + rst->proper_eof = false; + } + + priv->muxing = false; + priv->muxing_from_start = false; +} + +// Get a stream for writing. The pointer is valid until mp_recorder is +// destroyed. The stream is the index referencing the stream passed to +// mp_recorder_create(). +struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream) +{ + assert(stream >= 0 && stream < r->num_streams); + return r->streams[stream]; +} + +// Pass a packet to the given stream. The function does not own the packet, but +// can create a new reference to it if it needs to retain it. Can be NULL to +// signal proper end of stream. +void mp_recorder_feed_packet(struct mp_recorder_sink *rst, + struct demux_packet *pkt) +{ + struct mp_recorder *priv = rst->owner; + + if (!pkt) { + rst->proper_eof = true; + check_restart(priv); + mux_packets(rst, false); + return; + } + + if (pkt->dts == MP_NOPTS_VALUE && !priv->dts_warning) { + // No, FFmpeg has no actually usable helpers to generate correct DTS. + // No, FFmpeg doesn't tell us which formats need DTS at all. + // No, we can not shut up the FFmpeg warning, which will follow. + MP_WARN(priv, "Source stream misses DTS on at least some packets!\n" + "If the target file format requires DTS, the written\n" + "file will be invalid.\n"); + priv->dts_warning = true; + } + + if (rst->discont && !pkt->keyframe) + return; + rst->discont = false; + + if (rst->num_packets >= QUEUE_MAX_PACKETS) { + MP_ERR(priv, "Stream %d has too many queued packets; dropping.\n", + rst->av_stream->index); + return; + } + + pkt = demux_copy_packet(pkt); + if (!pkt) + return; + MP_TARRAY_APPEND(rst, rst->packets, rst->num_packets, pkt); + + check_restart(priv); + mux_packets(rst, false); +} diff --git a/common/recorder.h b/common/recorder.h new file mode 100644 index 0000000000..a6c8635c01 --- /dev/null +++ b/common/recorder.h @@ -0,0 +1,21 @@ +#ifndef MP_RECORDER_H_ +#define MP_RECORDER_H_ + +struct mp_recorder; +struct mpv_global; +struct demux_packet; +struct sh_stream; +struct mp_recorder_sink; + +struct mp_recorder *mp_recorder_create(struct mpv_global *global, + const char *target_file, + struct sh_stream **streams, + int num_streams); +void mp_recorder_destroy(struct mp_recorder *r); +void mp_recorder_mark_discontinuity(struct mp_recorder *r); + +struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream); +void mp_recorder_feed_packet(struct mp_recorder_sink *s, + struct demux_packet *pkt); + +#endif diff --git a/options/options.c b/options/options.c index 5166ef6940..b711156c18 100644 --- a/options/options.c +++ b/options/options.c @@ -681,6 +681,8 @@ const m_option_t mp_opts[] = { OPT_STRING("screenshot-template", screenshot_template, 0), OPT_STRING("screenshot-directory", screenshot_directory, 0), + OPT_STRING("record-file", record_file, M_OPT_FILE), + OPT_SUBSTRUCT("", input_opts, input_config, 0), OPT_PRINT("list-protocols", stream_print_proto_list), diff --git a/options/options.h b/options/options.h index 82a8f92d48..f465c0f862 100644 --- a/options/options.h +++ b/options/options.h @@ -138,6 +138,7 @@ typedef struct MPOpts { int untimed; char *stream_dump; + char *record_file; int stop_playback_on_init_failure; int loop_times; int loop_file; diff --git a/player/command.c b/player/command.c index 6d335e535a..cde67ebe51 100644 --- a/player/command.c +++ b/player/command.c @@ -3549,6 +3549,26 @@ static int mp_property_cwd(void *ctx, struct m_property *prop, return M_PROPERTY_NOT_IMPLEMENTED; } +static int mp_property_record_file(void *ctx, struct m_property *prop, + int action, void *arg) +{ + struct MPContext *mpctx = ctx; + struct MPOpts *opts = mpctx->opts; + if (action == M_PROPERTY_SET) { + char *new = *(char **)arg; + if (!bstr_equals(bstr0(new), bstr0(opts->record_file))) { + talloc_free(opts->record_file); + opts->record_file = talloc_strdup(NULL, new); + open_recorder(mpctx, false); + // open_recorder() unsets it on failure. + if (new && !opts->record_file) + return M_PROPERTY_ERROR; + } + return M_PROPERTY_OK; + } + return mp_property_generic_option(mpctx, prop, action, arg); +} + static int mp_property_protocols(void *ctx, struct m_property *prop, int action, void *arg) { @@ -4020,6 +4040,8 @@ static const struct m_property mp_properties_base[] = { {"working-directory", mp_property_cwd}, + {"record-file", mp_property_record_file}, + {"protocol-list", mp_property_protocols}, {"decoder-list", mp_property_decoders}, {"encoder-list", mp_property_encoders}, diff --git a/player/core.h b/player/core.h index 4767830bc9..79120decd3 100644 --- a/player/core.h +++ b/player/core.h @@ -153,6 +153,9 @@ struct track { struct vo_chain *vo_c; struct ao_chain *ao_c; struct lavfi_pad *sink; + + // For stream recording (remuxing mode). + struct mp_recorder_sink *remux_sink; }; // Summarizes video filtering and output. @@ -421,6 +424,8 @@ typedef struct MPContext { // playback rate. Used to avoid showing it multiple times. bool drop_message_shown; + struct mp_recorder *recorder; + char *cached_watch_later_configdir; struct screenshot_ctx *screenshot_ctx; @@ -506,6 +511,9 @@ void autoload_external_files(struct MPContext *mpctx); struct track *select_default_track(struct MPContext *mpctx, int order, enum stream_type type); void prefetch_next(struct MPContext *mpctx); +void close_recorder(struct MPContext *mpctx); +void close_recorder_and_error(struct MPContext *mpctx); +void open_recorder(struct MPContext *mpctx, bool on_init); // main.c int mp_initialize(struct MPContext *mpctx, char **argv); diff --git a/player/loadfile.c b/player/loadfile.c index 524ff8f1b2..dba02ee828 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -40,6 +40,7 @@ #include "options/m_property.h" #include "common/common.h" #include "common/encode.h" +#include "common/recorder.h" #include "input/input.h" #include "audio/audio.h" @@ -469,6 +470,8 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type uninit_sub(mpctx, current); if (current) { + if (current->remux_sink) + close_recorder_and_error(mpctx); current->selected = false; reselect_demux_stream(mpctx, current); } @@ -1256,6 +1259,8 @@ reopen_file: if (mpctx->opts->pause) pause_player(mpctx); + open_recorder(mpctx, true); + playback_start = mp_time_sec(); mpctx->error_playing = 0; while (!mpctx->stop_play) @@ -1278,6 +1283,8 @@ terminate_playback: mp_abort_playback_async(mpctx); + close_recorder(mpctx); + // time to uninit all, except global stuff: uninit_complex_filters(mpctx); uninit_audio_chain(mpctx); @@ -1449,3 +1456,89 @@ void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e) mpctx->stop_play = PT_CURRENT_ENTRY; mp_wakeup_core(mpctx); } + +static void set_track_recorder_sink(struct track *track, + struct mp_recorder_sink *sink) +{ + if (track->d_sub) + sub_set_recorder_sink(track->d_sub, sink); + if (track->d_video) + track->d_video->recorder_sink = sink; + if (track->d_audio) + track->d_audio->recorder_sink = sink; + track->remux_sink = sink; +} + +void close_recorder(struct MPContext *mpctx) +{ + if (!mpctx->recorder) + return; + + for (int n = 0; n < mpctx->num_tracks; n++) + set_track_recorder_sink(mpctx->tracks[n], NULL); + + mp_recorder_destroy(mpctx->recorder); + mpctx->recorder = NULL; +} + +// Like close_recorder(), but also unset the option. Intended for use on errors. +void close_recorder_and_error(struct MPContext *mpctx) +{ + close_recorder(mpctx); + talloc_free(mpctx->opts->record_file); + mpctx->opts->record_file = NULL; + mp_notify_property(mpctx, "record-file"); + MP_ERR(mpctx, "Disabling stream recording.\n"); +} + +void open_recorder(struct MPContext *mpctx, bool on_init) +{ + if (!mpctx->playback_initialized) + return; + + close_recorder(mpctx); + + char *target = mpctx->opts->record_file; + if (!target || !target[0]) + return; + + struct sh_stream **streams = NULL; + int num_streams = 0; + + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + if (track->stream && track->selected && + (track->d_sub || track->d_video || track->d_audio)) + { + MP_TARRAY_APPEND(NULL, streams, num_streams, track->stream); + } + } + + mpctx->recorder = mp_recorder_create(mpctx->global, mpctx->opts->record_file, + streams, num_streams); + + if (!mpctx->recorder) { + talloc_free(streams); + close_recorder_and_error(mpctx); + return; + } + + if (!on_init) + mp_recorder_mark_discontinuity(mpctx->recorder); + + int n_stream = 0; + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + if (n_stream >= num_streams) + break; + // (We expect track->stream not to be reused on other tracks.) + if (track->stream == streams[n_stream]) { + set_track_recorder_sink(track, + mp_recorder_get_sink(mpctx->recorder, n_stream)); + n_stream++; + } + } + + talloc_free(streams); +} + diff --git a/player/playloop.c b/player/playloop.c index 232a75f814..e73ad61788 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -28,6 +28,7 @@ #include "options/options.h" #include "common/common.h" #include "common/encode.h" +#include "common/recorder.h" #include "options/m_config.h" #include "options/m_property.h" #include "common/playlist.h" @@ -330,6 +331,8 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) clear_audio_output_buffers(mpctx); reset_playback_state(mpctx); + if (mpctx->recorder) + mp_recorder_mark_discontinuity(mpctx->recorder); /* Use the target time as "current position" for further relative * seeks etc until a new video frame has been decoded */ diff --git a/sub/dec_sub.c b/sub/dec_sub.c index b9f04b3123..743a06ed14 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -29,6 +29,7 @@ #include "options/options.h" #include "common/global.h" #include "common/msg.h" +#include "common/recorder.h" #include "osdep/threads.h" extern const struct sd_functions sd_ass; @@ -49,6 +50,8 @@ struct dec_sub { struct mpv_global *global; struct MPOpts *opts; + struct mp_recorder_sink *recorder_sink; + struct attachment_list *attachments; struct sh_stream *sh; @@ -240,6 +243,9 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts) break; } + if (sub->recorder_sink) + mp_recorder_feed_packet(sub->recorder_sink, pkt); + sub->last_pkt_pts = pkt->pts; if (is_new_segment(sub, pkt)) { @@ -323,3 +329,11 @@ int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg) pthread_mutex_unlock(&sub->lock); return r; } + +void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink) +{ + pthread_mutex_lock(&sub->lock); + sub->recorder_sink = sink; + pthread_mutex_unlock(&sub->lock); +} + diff --git a/sub/dec_sub.h b/sub/dec_sub.h index bdf6e35492..26781fd99f 100644 --- a/sub/dec_sub.h +++ b/sub/dec_sub.h @@ -9,6 +9,7 @@ struct sh_stream; struct mpv_global; struct demux_packet; +struct mp_recorder_sink; struct dec_sub; struct sd; @@ -40,6 +41,7 @@ void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, int format, char *sub_get_text(struct dec_sub *sub, double pts); void sub_reset(struct dec_sub *sub); void sub_select(struct dec_sub *sub, bool selected); +void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink); int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg); diff --git a/video/decode/dec_video.c b/video/decode/dec_video.c index 273b80c3c9..23aba81709 100644 --- a/video/decode/dec_video.c +++ b/video/decode/dec_video.c @@ -33,6 +33,7 @@ #include "demux/packet.h" #include "common/codecs.h" +#include "common/recorder.h" #include "video/out/vo.h" #include "video/csputils.h" @@ -259,6 +260,12 @@ static bool send_packet(struct dec_video *d_video, struct demux_packet *packet) if (pkt_pts == MP_NOPTS_VALUE) d_video->has_broken_packet_pts = 1; + bool dts_replaced = false; + if (packet && packet->dts == MP_NOPTS_VALUE && !d_video->codec->avi_dts) { + packet->dts = packet->pts; + dts_replaced = true; + } + double pkt_pdts = pkt_pts == MP_NOPTS_VALUE ? pkt_dts : pkt_pts; if (pkt_pdts != MP_NOPTS_VALUE && d_video->first_packet_pdts == MP_NOPTS_VALUE) d_video->first_packet_pdts = pkt_pdts; @@ -269,6 +276,10 @@ static bool send_packet(struct dec_video *d_video, struct demux_packet *packet) MP_STATS(d_video, "end decode video"); + // Stream recording can't deal with almost surely wrong fake DTS. + if (dts_replaced) + packet->dts = MP_NOPTS_VALUE; + return res; } @@ -396,11 +407,6 @@ void video_work(struct dec_video *d_video) return; } - if (d_video->packet) { - if (d_video->packet->dts == MP_NOPTS_VALUE && !d_video->codec->avi_dts) - d_video->packet->dts = d_video->packet->pts; - } - if (d_video->packet && d_video->packet->new_segment) { assert(!d_video->new_segment); d_video->new_segment = d_video->packet; @@ -423,6 +429,9 @@ void video_work(struct dec_video *d_video) d_video->vd_driver->control(d_video, VDCTRL_SET_FRAMEDROP, &framedrop_type); if (send_packet(d_video, d_video->packet)) { + if (d_video->recorder_sink) + mp_recorder_feed_packet(d_video->recorder_sink, d_video->packet); + talloc_free(d_video->packet); d_video->packet = NULL; }< |