mirror of
https://github.com/mpv-player/mpv
synced 2024-12-28 10:02:17 +00:00
b9d351f02a
See manpage additions. This is a huge hack. You can bet there are shit tons of bugs. It's literally forcing square pegs into round holes. Hopefully, the manpage wall of text makes it clear enough that the whole shit can easily crash and burn. (Although it shouldn't literally crash. That would be a bug. It possibly _could_ start a fire by entering some sort of endless loop, not a literal one, just something where it tries to do work without making progress.) (Some obvious bugs I simply ignored for this initial version, but there's a number of potential bugs I can't even imagine. Normal playback should remain completely unaffected, though.) How this works is also described in the manpage. Basically, we demux in reverse, then we decode in reverse, then we render in reverse. The decoding part is the simplest: just reorder the decoder output. This weirdly integrates with the timeline/ordered chapter code, which also has special requirements on feeding the packets to the decoder in a non-straightforward way (it doesn't conflict, although a bugmessmass breaks correct slicing of segments, so EDL/ordered chapter playback is broken in backward direction). Backward demuxing is pretty involved. In theory, it could be much easier: simply iterating the usual demuxer output backward. But this just doesn't fit into our code, so there's a cthulhu nightmare of shit. To be specific, each stream (audio, video) is reversed separately. At least this means we can do backward playback within cached content (for example, you could play backwards in a live stream; on that note, it disables prefetching, which would lead to losing new live video, but this could be avoided). The fuckmess also meant that I didn't bother trying to support subtitles. Subtitles are a problem because they're "sparse" streams. They need to be "passively" demuxed: you don't try to read a subtitle packet, you demux audio and video, and then look whether there was a subtitle packet. This means to get subtitles for a time range, you need to know that you demuxed video and audio over this range, which becomes pretty messy when you demux audio and video backwards separately. Backward display is the most weird (and potentially buggy) part. To avoid that we need to touch a LOT of timing code, we negate all timestamps. The basic idea is that due to the navigation, all comparisons and subtractions of timestamps keep working, and you don't need to touch every single of them to "reverse" them. E.g.: bool before = pts_a < pts_b; would need to be: bool before = forward ? pts_a < pts_b : pts_a > pts_b; or: bool before = pts_a * dir < pts_b * dir; or if you, as it's implemented now, just do this after decoding: pts_a *= dir; pts_b *= dir; and then in the normal timing/renderer code: bool before = pts_a < pts_b; Consequently, we don't need many changes in the latter code. But some assumptions inhererently true for forward playback may have been broken anyway. What is mainly needed is fixing places where values are passed between positive and negative "domains". For example, seeking and timestamp user display always uses positive timestamps. The main mess is that it's not obvious which domain a given variable should or does use. Well, in my tests with a single file, it suddenly started to work when I did this. I'm honestly surprised that it did, and that I didn't have to change a single line in the timing code past decoder (just something minor to make external/cached text subtitles display). I committed it immediately while avoiding thinking about it. But there really likely are subtle problems of all sorts. As far as I'm aware, gstreamer also supports backward playback. When I looked at this years ago, I couldn't find a way to actually try this, and I didn't revisit it now. Back then I also read talk slides from the person who implemented it, and I'm not sure if and which ideas I might have taken from it. It's possible that the timestamp reversal is inspired by it, but I didn't check. (I think it claimed that it could avoid large changes by changing a sign?) VapourSynth has some sort of reverse function, which provides a backward view on a video. The function itself is trivial to implement, as VapourSynth aims to provide random access to video by frame numbers (so you just request decreasing frame numbers). From what I remember, it wasn't exactly fluid, but it worked. It's implemented by creating an index, and seeking to the target on demand, and a bunch of caching. mpv could use it, but it would either require using VapourSynth as demuxer and decoder for everything, or replacing the current file every time something is supposed to be played backwards. FFmpeg's libavfilter has reversal filters for audio and video. These require buffering the entire media data of the file, and don't really fit into mpv's architecture. It could be used by playing a libavfilter graph that also demuxes, but that's like VapourSynth but worse.
313 lines
11 KiB
C
313 lines
11 KiB
C
/*
|
|
* 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/>.
|
|
*/
|
|
|
|
#ifndef MPLAYER_DEMUXER_H
|
|
#define MPLAYER_DEMUXER_H
|
|
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "misc/bstr.h"
|
|
#include "common/common.h"
|
|
#include "common/tags.h"
|
|
#include "packet.h"
|
|
#include "stheader.h"
|
|
|
|
#define MAX_SEEK_RANGES 10
|
|
|
|
struct demux_seek_range {
|
|
double start, end;
|
|
};
|
|
|
|
struct demux_reader_state {
|
|
bool eof, underrun, idle;
|
|
bool bof_cached, eof_cached;
|
|
double ts_duration;
|
|
double ts_reader; // approx. timerstamp of decoder position
|
|
double ts_end; // approx. timestamp of end of buffered range
|
|
int64_t total_bytes;
|
|
int64_t fw_bytes;
|
|
double seeking; // current low level seek target, or NOPTS
|
|
int low_level_seeks; // number of started low level seeks
|
|
double ts_last; // approx. timestamp of demuxer position
|
|
uint64_t bytes_per_second; // low level statistics
|
|
// Positions that can be seeked to without incurring the latency of a low
|
|
// level seek.
|
|
int num_seek_ranges;
|
|
struct demux_seek_range seek_ranges[MAX_SEEK_RANGES];
|
|
};
|
|
|
|
#define SEEK_FACTOR (1 << 1) // argument is in range [0,1]
|
|
#define SEEK_FORWARD (1 << 2) // prefer later time if not exact
|
|
// (if unset, prefer earlier time)
|
|
#define SEEK_CACHED (1 << 3) // allow packet cache seeks only
|
|
#define SEEK_SATAN (1 << 4) // enable backward demuxing
|
|
#define SEEK_HR (1 << 5) // hr-seek (this is a weak hint only)
|
|
|
|
// Strictness of the demuxer open format check.
|
|
// demux.c will try by default: NORMAL, UNSAFE (in this order)
|
|
// Using "-demuxer format" will try REQUEST
|
|
// Using "-demuxer +format" will try FORCE
|
|
// REQUEST can be used as special value for raw demuxers which have no file
|
|
// header check; then they should fail if check!=FORCE && check!=REQUEST.
|
|
//
|
|
// In general, the list is sorted from weakest check to normal check.
|
|
// You can use relation operators to compare the check level.
|
|
enum demux_check {
|
|
DEMUX_CHECK_FORCE, // force format if possible
|
|
DEMUX_CHECK_UNSAFE, // risky/fuzzy detection
|
|
DEMUX_CHECK_REQUEST,// requested by user or stream implementation
|
|
DEMUX_CHECK_NORMAL, // normal, safe detection
|
|
};
|
|
|
|
enum demux_event {
|
|
DEMUX_EVENT_INIT = 1 << 0, // complete (re-)initialization
|
|
DEMUX_EVENT_STREAMS = 1 << 1, // a stream was added
|
|
DEMUX_EVENT_METADATA = 1 << 2, // metadata or stream_metadata changed
|
|
DEMUX_EVENT_DURATION = 1 << 3, // duration updated
|
|
DEMUX_EVENT_ALL = 0xFFFF,
|
|
};
|
|
|
|
struct demuxer;
|
|
struct timeline;
|
|
|
|
/**
|
|
* Demuxer description structure
|
|
*/
|
|
typedef struct demuxer_desc {
|
|
const char *name; // Demuxer name, used with -demuxer switch
|
|
const char *desc; // Displayed to user
|
|
|
|
// Return 0 on success, otherwise -1
|
|
int (*open)(struct demuxer *demuxer, enum demux_check check);
|
|
// The following functions are all optional
|
|
// Try to read a packet. Return false on EOF. If true is returned, the
|
|
// demuxer may set *pkt to a new packet (the reference goes to the caller).
|
|
// If *pkt is NULL (the value when this function is called), the call
|
|
// will be repeated.
|
|
bool (*read_packet)(struct demuxer *demuxer, struct demux_packet **pkt);
|
|
void (*close)(struct demuxer *demuxer);
|
|
void (*seek)(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
|
void (*switched_tracks)(struct demuxer *demuxer);
|
|
// See timeline.c
|
|
void (*load_timeline)(struct timeline *tl);
|
|
} demuxer_desc_t;
|
|
|
|
typedef struct demux_chapter
|
|
{
|
|
int original_index;
|
|
double pts;
|
|
struct mp_tags *metadata;
|
|
uint64_t demuxer_id; // for mapping to internal demuxer data structures
|
|
} demux_chapter_t;
|
|
|
|
struct demux_edition {
|
|
uint64_t demuxer_id;
|
|
bool default_edition;
|
|
struct mp_tags *metadata;
|
|
};
|
|
|
|
struct matroska_segment_uid {
|
|
unsigned char segment[16];
|
|
uint64_t edition;
|
|
};
|
|
|
|
struct matroska_data {
|
|
struct matroska_segment_uid uid;
|
|
// Ordered chapter information if any
|
|
struct matroska_chapter {
|
|
uint64_t start;
|
|
uint64_t end;
|
|
bool has_segment_uid;
|
|
struct matroska_segment_uid uid;
|
|
char *name;
|
|
} *ordered_chapters;
|
|
int num_ordered_chapters;
|
|
};
|
|
|
|
struct replaygain_data {
|
|
float track_gain;
|
|
float track_peak;
|
|
float album_gain;
|
|
float album_peak;
|
|
};
|
|
|
|
typedef struct demux_attachment
|
|
{
|
|
char *name;
|
|
char *type;
|
|
void *data;
|
|
unsigned int data_size;
|
|
} demux_attachment_t;
|
|
|
|
struct demuxer_params {
|
|
char *force_format;
|
|
int matroska_num_wanted_uids;
|
|
struct matroska_segment_uid *matroska_wanted_uids;
|
|
int matroska_wanted_segment;
|
|
bool *matroska_was_valid;
|
|
struct timeline *timeline;
|
|
bool disable_timeline;
|
|
bstr init_fragment;
|
|
bool skip_lavf_probing;
|
|
bool stream_record; // if true, enable stream recording if option is set
|
|
int stream_flags;
|
|
// result
|
|
bool demuxer_failed;
|
|
};
|
|
|
|
typedef struct demuxer {
|
|
const demuxer_desc_t *desc; ///< Demuxer description structure
|
|
const char *filetype; // format name when not identified by demuxer (libavformat)
|
|
int64_t filepos; // input stream current pos.
|
|
int64_t filesize;
|
|
char *filename; // same as stream->url
|
|
bool seekable;
|
|
bool partially_seekable; // true if _maybe_ seekable; implies seekable=true
|
|
double start_time;
|
|
double duration; // -1 if unknown
|
|
// File format allows PTS resets (even if the current file is without)
|
|
bool ts_resets_possible;
|
|
// The file data was fully read, and there is no need to keep the stream
|
|
// open, keep the cache active, or to run the demuxer thread. Generating
|
|
// packets is not slow either (unlike e.g. libavdevice pseudo-demuxers).
|
|
// Typical examples: text subtitles, playlists
|
|
bool fully_read;
|
|
bool is_network; // opened directly from a network stream
|
|
bool access_references; // allow opening other files/URLs
|
|
|
|
// Bitmask of DEMUX_EVENT_*
|
|
int events;
|
|
|
|
struct demux_edition *editions;
|
|
int num_editions;
|
|
int edition;
|
|
|
|
struct demux_chapter *chapters;
|
|
int num_chapters;
|
|
|
|
struct demux_attachment *attachments;
|
|
int num_attachments;
|
|
|
|
struct matroska_data matroska_data;
|
|
|
|
// If the file is a playlist file
|
|
struct playlist *playlist;
|
|
|
|
struct mp_tags *metadata;
|
|
|
|
void *priv; // demuxer-specific internal data
|
|
struct mpv_global *global;
|
|
struct mp_log *log, *glog;
|
|
struct demuxer_params *params;
|
|
|
|
// internal to demux.c
|
|
struct demux_internal *in;
|
|
|
|
// Triggered when ending demuxing forcefully. Usually bound to the stream too.
|
|
struct mp_cancel *cancel;
|
|
|
|
// Since the demuxer can run in its own thread, and the stream is not
|
|
// thread-safe, only the demuxer is allowed to access the stream directly.
|
|
// Also note that the stream can get replaced if fully_read is set.
|
|
struct stream *stream;
|
|
} demuxer_t;
|
|
|
|
void demux_free(struct demuxer *demuxer);
|
|
void demux_cancel_and_free(struct demuxer *demuxer);
|
|
|
|
struct demux_free_async_state;
|
|
struct demux_free_async_state *demux_free_async(struct demuxer *demuxer);
|
|
void demux_free_async_force(struct demux_free_async_state *state);
|
|
bool demux_free_async_finish(struct demux_free_async_state *state);
|
|
|
|
void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp);
|
|
|
|
int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt);
|
|
bool demux_stream_is_selected(struct sh_stream *stream);
|
|
void demux_set_stream_wakeup_cb(struct sh_stream *sh,
|
|
void (*cb)(void *ctx), void *ctx);
|
|
struct demux_packet *demux_read_any_packet(struct demuxer *demuxer);
|
|
|
|
struct sh_stream *demux_get_stream(struct demuxer *demuxer, int index);
|
|
int demux_get_num_stream(struct demuxer *demuxer);
|
|
|
|
struct sh_stream *demux_alloc_sh_stream(enum stream_type type);
|
|
void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh);
|
|
|
|
struct mp_cancel;
|
|
struct demuxer *demux_open_url(const char *url,
|
|
struct demuxer_params *params,
|
|
struct mp_cancel *cancel,
|
|
struct mpv_global *global);
|
|
|
|
void demux_start_thread(struct demuxer *demuxer);
|
|
void demux_stop_thread(struct demuxer *demuxer);
|
|
void demux_set_wakeup_cb(struct demuxer *demuxer, void (*cb)(void *ctx), void *ctx);
|
|
void demux_start_prefetch(struct demuxer *demuxer);
|
|
|
|
bool demux_cancel_test(struct demuxer *demuxer);
|
|
|
|
void demux_flush(struct demuxer *demuxer);
|
|
int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
|
void demux_set_ts_offset(struct demuxer *demuxer, double offset);
|
|
|
|
void demux_get_bitrate_stats(struct demuxer *demuxer, double *rates);
|
|
void demux_get_reader_state(struct demuxer *demuxer, struct demux_reader_state *r);
|
|
|
|
void demux_block_reading(struct demuxer *demuxer, bool block);
|
|
|
|
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
|
|
double ref_pts, bool selected);
|
|
|
|
void demuxer_help(struct mp_log *log);
|
|
|
|
int demuxer_add_attachment(struct demuxer *demuxer, char *name,
|
|
char *type, void *data, size_t data_size);
|
|
int demuxer_add_chapter(demuxer_t *demuxer, char *name,
|
|
double pts, uint64_t demuxer_id);
|
|
void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
|
|
struct mp_tags *tags);
|
|
void demux_close_stream(struct demuxer *demuxer);
|
|
|
|
void demux_metadata_changed(demuxer_t *demuxer);
|
|
void demux_update(demuxer_t *demuxer);
|
|
|
|
void demux_disable_cache(demuxer_t *demuxer);
|
|
bool demux_is_network_cached(demuxer_t *demuxer);
|
|
|
|
void demux_report_unbuffered_read_bytes(struct demuxer *demuxer, int64_t new);
|
|
int64_t demux_get_bytes_read_hack(struct demuxer *demuxer);
|
|
|
|
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
|
|
enum stream_type t, int id);
|
|
|
|
struct demux_chapter *demux_copy_chapter_data(struct demux_chapter *c, int num);
|
|
|
|
bool demux_matroska_uid_cmp(struct matroska_segment_uid *a,
|
|
struct matroska_segment_uid *b);
|
|
|
|
const char *stream_type_name(enum stream_type type);
|
|
|
|
void mp_packet_tags_unref(struct mp_packet_tags *tags);
|
|
void mp_packet_tags_setref(struct mp_packet_tags **dst, struct mp_packet_tags *src);
|
|
|
|
#endif /* MPLAYER_DEMUXER_H */
|