summaryrefslogtreecommitdiffstats
path: root/player
diff options
context:
space:
mode:
Diffstat (limited to 'player')
-rw-r--r--player/audio.c765
-rw-r--r--player/client.c433
-rw-r--r--player/command.c1654
-rw-r--r--player/command.h8
-rw-r--r--player/configfiles.c266
-rw-r--r--player/core.h102
-rw-r--r--player/external_files.c98
-rw-r--r--player/external_files.h4
-rw-r--r--player/javascript.c167
-rw-r--r--player/javascript/defaults.js120
-rw-r--r--player/javascript/meson.build6
-rw-r--r--player/loadfile.c460
-rw-r--r--player/lua.c197
-rw-r--r--player/lua/auto_profiles.lua67
-rw-r--r--player/lua/console.lua1043
-rw-r--r--player/lua/defaults.lua86
-rw-r--r--player/lua/input.lua69
-rw-r--r--player/lua/meson.build11
-rw-r--r--player/lua/options.lua17
-rw-r--r--player/lua/osc.lua533
-rw-r--r--player/lua/stats.lua865
-rw-r--r--player/lua/ytdl_hook.lua456
-rw-r--r--player/main.c77
-rw-r--r--player/meson.build11
-rw-r--r--player/misc.c25
-rw-r--r--player/osd.c17
-rw-r--r--player/playloop.c174
-rw-r--r--player/screenshot.c132
-rw-r--r--player/scripting.c112
-rw-r--r--player/sub.c76
-rw-r--r--player/video.c174
31 files changed, 5637 insertions, 2588 deletions
diff --git a/player/audio.c b/player/audio.c
index e541506778..da91dd4340 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -22,7 +22,6 @@
#include <math.h>
#include <assert.h>
-#include "config.h"
#include "mpv_talloc.h"
#include "common/msg.h"
@@ -31,11 +30,12 @@
#include "common/common.h"
#include "osdep/timer.h"
-#include "audio/audio_buffer.h"
#include "audio/format.h"
#include "audio/out/ao.h"
#include "demux/demux.h"
+#include "filters/f_async_queue.h"
#include "filters/f_decoder_wrapper.h"
+#include "filters/filter_internal.h"
#include "core.h"
#include "command.h"
@@ -46,6 +46,8 @@ enum {
AD_WAIT = -4,
};
+static void ao_process(struct mp_filter *f);
+
static void update_speed_filters(struct MPContext *mpctx)
{
struct ao_chain *ao_c = mpctx->ao_chain;
@@ -61,9 +63,17 @@ static void update_speed_filters(struct MPContext *mpctx)
speed = 1.0;
}
- if (mpctx->display_sync_active && mpctx->opts->video_sync == VS_DISP_ADROP) {
- drop *= speed * resample;
- resample = speed = 1.0;
+ if (mpctx->display_sync_active) {
+ switch (mpctx->video_out->opts->video_sync) {
+ case VS_DISP_ADROP:
+ drop *= speed * resample;
+ resample = speed = 1.0;
+ break;
+ case VS_DISP_TEMPO:
+ speed = mpctx->audio_speed;
+ resample = 1.0;
+ break;
+ }
}
mp_output_chain_set_audio_speed(ao_c->filter, speed, resample, drop);
@@ -165,6 +175,7 @@ void audio_update_volume(struct MPContext *mpctx)
float gain = MPMAX(opts->softvol_volume / 100.0, 0);
gain = pow(gain, 3);
gain *= compute_replaygain(mpctx);
+ gain *= db_gain(opts->softvol_gain);
if (opts->softvol_mute == 1)
gain = 0.0;
@@ -180,14 +191,28 @@ void update_playback_speed(struct MPContext *mpctx)
update_speed_filters(mpctx);
}
+static bool has_video_track(struct MPContext *mpctx)
+{
+ if (mpctx->vo_chain && mpctx->vo_chain->is_coverart)
+ return false;
+
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (track->type == STREAM_VIDEO && !track->attached_picture && !track->image)
+ return true;
+ }
+
+ return false;
+}
+
static void ao_chain_reset_state(struct ao_chain *ao_c)
{
ao_c->last_out_pts = MP_NOPTS_VALUE;
- TA_FREEP(&ao_c->output_frame);
ao_c->out_eof = false;
+ ao_c->start_pts_known = false;
+ ao_c->start_pts = MP_NOPTS_VALUE;
+ ao_c->untimed_throttle = false;
ao_c->underrun = false;
-
- mp_audio_buffer_clear(ao_c->ao_buffer);
}
void reset_audio_state(struct MPContext *mpctx)
@@ -200,15 +225,25 @@ void reset_audio_state(struct MPContext *mpctx)
}
mpctx->audio_status = mpctx->ao_chain ? STATUS_SYNCING : STATUS_EOF;
mpctx->delay = 0;
- mpctx->audio_stat_start = 0;
+ mpctx->logged_async_diff = -1;
}
void uninit_audio_out(struct MPContext *mpctx)
{
+ struct ao_chain *ao_c = mpctx->ao_chain;
+ if (ao_c) {
+ ao_c->ao_queue = NULL;
+ TA_FREEP(&ao_c->queue_filter);
+ ao_c->ao = NULL;
+ }
if (mpctx->ao) {
// Note: with gapless_audio, stop_play is not correctly set
- if (mpctx->opts->gapless_audio || mpctx->stop_play == AT_END_OF_FILE)
+ if ((mpctx->opts->gapless_audio || mpctx->stop_play == AT_END_OF_FILE) &&
+ ao_is_playing(mpctx->ao) && !get_internal_paused(mpctx))
+ {
+ MP_VERBOSE(mpctx, "draining left over audio\n");
ao_drain(mpctx->ao);
+ }
ao_uninit(mpctx->ao);
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
@@ -233,8 +268,7 @@ static void ao_chain_uninit(struct ao_chain *ao_c)
mp_pin_disconnect(ao_c->filter_src);
talloc_free(ao_c->filter->f);
- talloc_free(ao_c->output_frame);
- talloc_free(ao_c->ao_buffer);
+ talloc_free(ao_c->ao_filter);
talloc_free(ao_c);
}
@@ -268,8 +302,7 @@ static bool keep_weak_gapless_format(struct mp_aframe *old, struct mp_aframe* ne
{
bool res = false;
struct mp_aframe *new_mod = mp_aframe_new_ref(new);
- if (!new_mod)
- abort();
+ MP_HANDLE_OOM(new_mod);
// If the sample formats are compatible (== libswresample generally can
// convert them), keep the AO. On other changes, recreate it.
@@ -290,22 +323,38 @@ done:
return res;
}
-static void reinit_audio_filters_and_output(struct MPContext *mpctx)
+static void ao_chain_set_ao(struct ao_chain *ao_c, struct ao *ao)
+{
+ if (ao_c->ao != ao) {
+ assert(!ao_c->ao);
+ ao_c->ao = ao;
+ ao_c->ao_queue = ao_get_queue(ao_c->ao);
+ ao_c->queue_filter = mp_async_queue_create_filter(ao_c->ao_filter,
+ MP_PIN_IN, ao_c->ao_queue);
+ mp_async_queue_set_notifier(ao_c->queue_filter, ao_c->ao_filter);
+ // Make sure filtering never stops with frames stuck in access filter.
+ mp_filter_set_high_priority(ao_c->queue_filter, true);
+ audio_update_volume(ao_c->mpctx);
+ }
+
+ if (ao_c->filter->ao_needs_update)
+ mp_output_chain_set_ao(ao_c->filter, ao_c->ao);
+
+ mp_filter_wakeup(ao_c->ao_filter);
+}
+
+static int reinit_audio_filters_and_output(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct ao_chain *ao_c = mpctx->ao_chain;
assert(ao_c);
struct track *track = ao_c->track;
- if (!ao_c->filter->ao_needs_update)
- return;
-
- TA_FREEP(&ao_c->output_frame); // stale?
+ assert(ao_c->filter->ao_needs_update);
// The "ideal" filter output format
struct mp_aframe *out_fmt = mp_aframe_new_ref(ao_c->filter->output_aformat);
- if (!out_fmt)
- abort();
+ MP_HANDLE_OOM(out_fmt);
if (!mp_aframe_config_is_valid(out_fmt)) {
talloc_free(out_fmt);
@@ -328,9 +377,28 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
keep_weak_gapless_format(mpctx->ao_filter_fmt, out_fmt)) ||
(mpctx->ao && opts->gapless_audio > 0))
{
- mp_output_chain_set_ao(ao_c->filter, mpctx->ao);
+ ao_chain_set_ao(ao_c, mpctx->ao);
talloc_free(out_fmt);
- return;
+ return 0;
+ }
+
+ // Wait until all played.
+ if (mpctx->ao && ao_is_playing(mpctx->ao)) {
+ talloc_free(out_fmt);
+ return 0;
+ }
+ // Format change during syncing. Force playback start early, then wait.
+ if (ao_c->ao_queue && mp_async_queue_get_frames(ao_c->ao_queue) &&
+ mpctx->audio_status == STATUS_SYNCING)
+ {
+ mpctx->audio_status = STATUS_READY;
+ mp_wakeup_core(mpctx);
+ talloc_free(out_fmt);
+ return 0;
+ }
+ if (mpctx->audio_status == STATUS_READY) {
+ talloc_free(out_fmt);
+ return 0;
}
uninit_audio_out(mpctx);
@@ -363,12 +431,14 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
opts->audio_output_channels.num_chmaps);
}
+ if (!has_video_track(mpctx))
+ ao_flags |= AO_INIT_MEDIA_ROLE_MUSIC;
+
mpctx->ao_filter_fmt = out_fmt;
mpctx->ao = ao_init_best(mpctx->global, ao_flags, mp_wakeup_core_cb,
mpctx, mpctx->encode_lavc_ctx, out_rate,
out_format, out_channels);
- ao_c->ao = mpctx->ao;
int ao_rate = 0;
int ao_format = 0;
@@ -384,7 +454,6 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
MP_ERR(mpctx, "Passthrough format unsupported.\n");
ao_uninit(mpctx->ao);
mpctx->ao = NULL;
- ao_c->ao = NULL;
}
}
@@ -400,7 +469,7 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
reset_audio_state(mpctx);
mp_output_chain_reset_harder(ao_c->filter);
mp_wakeup_core(mpctx); // reinit with new format next time
- return;
+ return 0;
}
MP_ERR(mpctx, "Could not open/initialize audio device -> no sound.\n");
@@ -408,9 +477,6 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
goto init_error;
}
- mp_audio_buffer_reinit_fmt(ao_c->ao_buffer, ao_format, &ao_channels,
- ao_rate);
-
char tmp[192];
MP_INFO(mpctx, "AO: [%s] %s\n", ao_get_name(mpctx->ao),
audio_config_to_str_buf(tmp, sizeof(tmp), ao_rate, ao_format,
@@ -421,18 +487,27 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
ao_c->ao_resume_time =
opts->audio_wait_open > 0 ? mp_time_sec() + opts->audio_wait_open : 0;
- mp_output_chain_set_ao(ao_c->filter, mpctx->ao);
+ bool eof = mpctx->audio_status == STATUS_EOF;
+ ao_set_paused(mpctx->ao, get_internal_paused(mpctx), eof);
+
+ ao_chain_set_ao(ao_c, mpctx->ao);
audio_update_volume(mpctx);
+ // Almost nonsensical hack to deal with certain format change scenarios.
+ if (mpctx->audio_status == STATUS_PLAYING)
+ ao_start(mpctx->ao);
+
+ mp_wakeup_core(mpctx);
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
- return;
+ return 0;
init_error:
uninit_audio_chain(mpctx);
uninit_audio_out(mpctx);
error_on_track(mpctx, track);
+ return -1;
}
int init_audio_decoder(struct MPContext *mpctx, struct track *track)
@@ -466,13 +541,19 @@ void reinit_audio_chain(struct MPContext *mpctx)
struct track *track = NULL;
track = mpctx->current_track[0][STREAM_AUDIO];
if (!track || !track->stream) {
- uninit_audio_out(mpctx);
+ if (!mpctx->encode_lavc_ctx)
+ uninit_audio_out(mpctx);
error_on_track(mpctx, track);
return;
}
reinit_audio_chain_src(mpctx, track);
}
+static const struct mp_filter_info ao_filter = {
+ .name = "ao",
+ .process = ao_process,
+};
+
// (track=NULL creates a blank chain, used for lavfi-complex)
void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track)
{
@@ -482,15 +563,22 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track)
struct ao_chain *ao_c = talloc_zero(NULL, struct ao_chain);
mpctx->ao_chain = ao_c;
+ ao_c->mpctx = mpctx;
ao_c->log = mpctx->log;
ao_c->filter =
mp_output_chain_create(mpctx->filter_root, MP_OUTPUT_CHAIN_AUDIO);
ao_c->spdif_passthrough = true;
ao_c->last_out_pts = MP_NOPTS_VALUE;
- ao_c->ao_buffer = mp_audio_buffer_create(NULL);
- ao_c->ao = mpctx->ao;
ao_c->delay = mpctx->opts->audio_delay;
+ ao_c->ao_filter = mp_filter_create(mpctx->filter_root, &ao_filter);
+ if (!ao_c->filter || !ao_c->ao_filter)
+ goto init_error;
+ ao_c->ao_filter->priv = ao_c;
+
+ mp_filter_add_pin(ao_c->ao_filter, MP_PIN_IN, "in");
+ mp_pin_connect(ao_c->ao_filter->pins[0], ao_c->filter->f->pins[1]);
+
if (track) {
ao_c->track = track;
track->ao_c = ao_c;
@@ -505,15 +593,8 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track)
if (recreate_audio_filters(mpctx) < 0)
goto init_error;
- if (mpctx->ao) {
- int rate;
- int format;
- struct mp_chmap channels;
- ao_get_format(mpctx->ao, &rate, &format, &channels);
- mp_audio_buffer_reinit_fmt(ao_c->ao_buffer, format, &channels, rate);
-
+ if (mpctx->ao)
audio_update_volume(mpctx);
- }
mp_wakeup_core(mpctx);
return;
@@ -524,25 +605,11 @@ init_error:
error_on_track(mpctx, track);
}
-// Return pts value corresponding to the end point of audio written to the
-// ao so far.
+// Return pts value corresponding to the start point of audio written to the
+// ao queue so far.
double written_audio_pts(struct MPContext *mpctx)
{
- struct ao_chain *ao_c = mpctx->ao_chain;
- if (!ao_c)
- return MP_NOPTS_VALUE;
-
- // end pts of audio that has been output by filters
- double a_pts = ao_c->last_out_pts;
- if (a_pts == MP_NOPTS_VALUE)
- return MP_NOPTS_VALUE;
-
- // Data that was ready for ao but was buffered because ao didn't fully
- // accept everything to internal buffers yet. This also does not correctly
- // track playback speed, so we use the current speed.
- a_pts -= mp_audio_buffer_seconds(ao_c->ao_buffer) * mpctx->audio_speed;
-
- return a_pts;
+ return mpctx->ao_chain ? mpctx->ao_chain->last_out_pts : MP_NOPTS_VALUE;
}
// Return pts value corresponding to currently playing audio.
@@ -551,210 +618,140 @@ double playing_audio_pts(struct MPContext *mpctx)
double pts = written_audio_pts(mpctx);
if (pts == MP_NOPTS_VALUE || !mpctx->ao)
return pts;
- return pts - mpctx->audio_speed * ao_get_delay(mpctx->ao);
+ return pts - ao_get_delay(mpctx->ao);
}
-static int write_to_ao(struct MPContext *mpctx, uint8_t **planes, int samples,
- int flags)
+// This garbage is needed for untimed AOs. These consume audio infinitely fast,
+// so try keeping approximate A/V sync by blocking audio transfer as needed.
+static void update_throttle(struct MPContext *mpctx)
{
- if (mpctx->paused)
- return 0;
- struct ao *ao = mpctx->ao;
- int samplerate;
- int format;
- struct mp_chmap channels;
- ao_get_format(ao, &samplerate, &format, &channels);
- encode_lavc_set_audio_pts(mpctx->encode_lavc_ctx, playing_audio_pts(mpctx));
- if (samples == 0)
- return 0;
- double real_samplerate = samplerate / mpctx->audio_speed;
- int played = ao_play(mpctx->ao, (void **)planes, samples, flags);
- assert(played <= samples);
- if (played > 0) {
- mpctx->shown_aframes += played;
- mpctx->delay += played / real_samplerate;
- mpctx->written_audio += played / (double)samplerate;
- return played;
+ struct ao_chain *ao_c = mpctx->ao_chain;
+ bool new_throttle = mpctx->audio_status == STATUS_PLAYING &&
+ mpctx->delay > 0 && ao_c && ao_c->ao &&
+ ao_untimed(ao_c->ao) &&
+ mpctx->video_status != STATUS_EOF;
+ if (ao_c && new_throttle != ao_c->untimed_throttle) {
+ ao_c->untimed_throttle = new_throttle;
+ mp_wakeup_core(mpctx);
+ mp_filter_wakeup(ao_c->ao_filter);
}
- return 0;
}
-static void dump_audio_stats(struct MPContext *mpctx)
+static void ao_process(struct mp_filter *f)
{
- if (!mp_msg_test(mpctx->log, MSGL_STATS))
- return;
- if (mpctx->audio_status != STATUS_PLAYING || !mpctx->ao || mpctx->paused) {
- mpctx->audio_stat_start = 0;
+ struct ao_chain *ao_c = f->priv;
+ struct MPContext *mpctx = ao_c->mpctx;
+
+ if (!ao_c->queue_filter) {
+ // This will eventually lead to the creation of the AO + queue, due
+ // to how f_output_chain and AO management works.
+ mp_pin_out_request_data(f->ppins[0]);
+ // Check for EOF with no data case, which is a mess because everything
+ // hates us.
+ struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
+ if (frame.type == MP_FRAME_EOF) {
+ MP_VERBOSE(mpctx, "got EOF with no data before it\n");
+ ao_c->out_eof = true;
+ mpctx->audio_status = STATUS_DRAINING;
+ mp_wakeup_core(mpctx);
+ } else if (frame.type) {
+ mp_pin_out_unread(f->ppins[0], frame);
+ }
return;
}
- double delay = ao_get_delay(mpctx->ao);
- if (!mpctx->audio_stat_start) {
- mpctx->audio_stat_start = mp_time_us();
- mpctx->written_audio = delay;
- }
- double current_audio = mpctx->written_audio - delay;
- double current_time = (mp_time_us() - mpctx->audio_stat_start) / 1e6;
- MP_STATS(mpctx, "value %f ao-dev", current_audio - current_time);
-}
-
-// Return the number of samples that must be skipped or prepended to reach the
-// target audio pts after a seek (for A/V sync or hr-seek).
-// Return value (*skip):
-// >0: skip this many samples
-// =0: don't do anything
-// <0: prepend this many samples of silence
-// Returns false if PTS is not known yet.
-static bool get_sync_samples(struct MPContext *mpctx, int *skip)
-{
- struct MPOpts *opts = mpctx->opts;
- *skip = 0;
-
- if (mpctx->audio_status != STATUS_SYNCING)
- return true;
-
- int ao_rate;
- int ao_format;
- struct mp_chmap ao_channels;
- ao_get_format(mpctx->ao, &ao_rate, &ao_format, &ao_channels);
-
- double play_samplerate = ao_rate / mpctx->audio_speed;
-
- if (!opts->initial_audio_sync) {
- mpctx->audio_status = STATUS_FILLING;
- return true;
- }
-
- double written_pts = written_audio_pts(mpctx);
- if (written_pts == MP_NOPTS_VALUE &&
- !mp_audio_buffer_samples(mpctx->ao_chain->ao_buffer))
- return false; // no audio read yet
-
- bool sync_to_video = mpctx->vo_chain && mpctx->video_status != STATUS_EOF &&
- !mpctx->vo_chain->is_sparse;
-
- double sync_pts = MP_NOPTS_VALUE;
- if (sync_to_video) {
- if (mpctx->video_status < STATUS_READY)
- return false; // wait until we know a video PTS
- if (mpctx->video_pts != MP_NOPTS_VALUE)
- sync_pts = mpctx->video_pts - opts->audio_delay;
- } else if (mpctx->hrseek_active) {
- sync_pts = mpctx->hrseek_pts;
- } else {
- // If audio-only is enabled mid-stream during playback, sync accordingly.
- sync_pts = mpctx->playback_pts;
- }
- if (sync_pts == MP_NOPTS_VALUE) {
- mpctx->audio_status = STATUS_FILLING;
- return true; // syncing disabled
- }
-
- double ptsdiff = written_pts - sync_pts;
-
- // Missing timestamp, or PTS reset, or just broken.
- if (written_pts == MP_NOPTS_VALUE) {
- MP_WARN(mpctx, "Failed audio resync.\n");
- mpctx->audio_status = STATUS_FILLING;
- return true;
- }
- ptsdiff = MPCLAMP(ptsdiff, -3600, 3600);
-
- MP_VERBOSE(mpctx, "audio sync: sync_to_video=%d, offset=%f\n",
- sync_to_video, ptsdiff);
+ // Due to mp_async_queue_set_notifier() this function is called when the
+ // queue becomes full. This affects state changes in the normal playloop,
+ // so wake it up. But avoid redundant wakeups during normal playback.
+ if (mpctx->audio_status != STATUS_PLAYING &&
+ mp_async_queue_is_full(ao_c->ao_queue))
+ mp_wakeup_core(mpctx);
- int align = af_format_sample_alignment(ao_format);
- *skip = (int)(-ptsdiff * play_samplerate) / align * align;
- return true;
-}
+ if (mpctx->audio_status == STATUS_SYNCING && !ao_c->start_pts_known)
+ return;
+ if (ao_c->untimed_throttle)
+ return;
-static bool copy_output(struct MPContext *mpctx, struct ao_chain *ao_c,
- int minsamples, double endpts, bool *seteof)
-{
- struct mp_audio_buffer *outbuf = ao_c->ao_buffer;
+ if (!mp_pin_can_transfer_data(ao_c->queue_filter->pins[0], f->ppins[0]))
+ return;
- int ao_rate;
- int ao_format;
- struct mp_chmap ao_channels;
- ao_get_format(ao_c->ao, &ao_rate, &ao_format, &ao_channels);
+ struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
+ if (frame.type == MP_FRAME_AUDIO) {
+ struct mp_aframe *af = frame.data;
- while (mp_audio_buffer_samples(outbuf) < minsamples) {
- int cursamples = mp_audio_buffer_samples(outbuf);
- int maxsamples = INT_MAX;
+ double endpts = get_play_end_pts(mpctx);
if (endpts != MP_NOPTS_VALUE) {
- double rate = ao_rate / mpctx->audio_speed;
- double curpts = written_audio_pts(mpctx);
- if (curpts != MP_NOPTS_VALUE) {
- double remaining =
- (endpts - curpts - mpctx->opts->audio_delay) * rate;
- maxsamples = MPCLAMP(remaining, 0, INT_MAX);
+ endpts *= mpctx->play_dir;
+ // Avoid decoding and discarding the entire rest of the file.
+ if (mp_aframe_get_pts(af) >= endpts) {
+ mp_pin_out_unread(f->ppins[0], frame);
+ if (!ao_c->out_eof) {
+ ao_c->out_eof = true;
+ mp_pin_in_write(ao_c->queue_filter->pins[0], MP_EOF_FRAME);
+ }
+ return;
}
}
-
- if (!ao_c->output_frame || !mp_aframe_get_size(ao_c->output_frame)) {
- TA_FREEP(&ao_c->output_frame);
-
- struct mp_frame frame = mp_pin_out_read(ao_c->filter->f->pins[1]);
- if (frame.type == MP_FRAME_AUDIO) {
- ao_c->output_frame = frame.data;
- ao_c->out_eof = false;
- ao_c->last_out_pts = mp_aframe_end_pts(ao_c->output_frame);
- } else if (frame.type == MP_FRAME_EOF) {
- ao_c->out_eof = true;
- } else if (frame.type) {
- MP_ERR(mpctx, "unknown frame type\n");
- mp_frame_unref(&frame);
- }
+ double startpts = mpctx->audio_status == STATUS_SYNCING ?
+ ao_c->start_pts : MP_NOPTS_VALUE;
+ mp_aframe_clip_timestamps(af, startpts, endpts);
+
+ int samples = mp_aframe_get_size(af);
+ if (!samples) {
+ mp_filter_internal_mark_progress(f);
+ mp_frame_unref(&frame);
+ return;
}
- // out of data
- if (!ao_c->output_frame) {
- if (ao_c->out_eof) {
- *seteof = true;
- return true;
- }
- return false;
- }
+ ao_c->out_eof = false;
- if (cursamples + mp_aframe_get_size(ao_c->output_frame) > maxsamples) {
- if (cursamples < maxsamples) {
- uint8_t **data = mp_aframe_get_data_ro(ao_c->output_frame);
- mp_audio_buffer_append(outbuf, (void **)data,
- maxsamples - cursamples);
- mp_aframe_skip_samples(ao_c->output_frame,
- maxsamples - cursamples);
- }
- *seteof = true;
- return true;
+ if (mpctx->audio_status == STATUS_DRAINING ||
+ mpctx->audio_status == STATUS_EOF)
+ {
+ // If a new frame comes decoder/filter EOF, we should preferably
+ // call get_sync_pts() again, which (at least in obscure situations)
+ // may require us to wait a while until the sync PTS is known. Our
+ // code sucks and can't deal with that, so jump through a hoop to
+ // get things done in the correct order.
+ mp_pin_out_unread(f->ppins[0], frame);
+ ao_c->start_pts_known = false;
+ mpctx->audio_status = STATUS_SYNCING;
+ mp_wakeup_core(mpctx);
+ MP_VERBOSE(mpctx, "new audio frame after EOF\n");
+ return;
}
- uint8_t **data = mp_aframe_get_data_ro(ao_c->output_frame);
- mp_audio_buffer_append(outbuf, (void **)data,
- mp_aframe_get_size(ao_c->output_frame));
- TA_FREEP(&ao_c->output_frame);
- }
- return true;
-}
+ mpctx->shown_aframes += samples;
+ double real_samplerate = mp_aframe_get_rate(af) / mpctx->audio_speed;
+ if (mpctx->video_status != STATUS_EOF)
+ mpctx->delay += samples / real_samplerate;
+ ao_c->last_out_pts = mp_aframe_end_pts(af);
+ update_throttle(mpctx);
+
+ // Gapless case: the AO is still playing from previous file. It makes
+ // no sense to wait, and in fact the "full queue" event we're waiting
+ // for may never happen, so start immediately.
+ // If the new audio starts "later" (big video sync offset), transfer
+ // of data is stopped somewhere else.
+ if (mpctx->audio_status == STATUS_SYNCING && ao_is_playing(ao_c->ao)) {
+ mpctx->audio_status = STATUS_READY;
+ mp_wakeup_core(mpctx);
+ MP_VERBOSE(mpctx, "previous audio still playing; continuing\n");
+ }
-/* Try to get at least minsamples decoded+filtered samples in outbuf
- * (total length including possible existing data).
- * Return 0 on success, or negative AD_* error code.
- * In the former case outbuf has at least minsamples buffered on return.
- * In case of EOF/error it might or might not be. */
-static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
- int minsamples)
-{
- struct ao_chain *ao_c = mpctx->ao_chain;
+ mp_pin_in_write(ao_c->queue_filter->pins[0], frame);
+ } else if (frame.type == MP_FRAME_EOF) {
+ MP_VERBOSE(mpctx, "audio filter EOF\n");
- double endpts = get_play_end_pts(mpctx);
- if (endpts != MP_NOPTS_VALUE)
- endpts *= mpctx->play_dir;
+ ao_c->out_eof = true;
+ mp_wakeup_core(mpctx);
- bool eof = false;
- if (!copy_output(mpctx, ao_c, minsamples, endpts, &eof))
- return AD_WAIT;
- return eof ? AD_EOF : AD_OK;
+ mp_pin_in_write(ao_c->queue_filter->pins[0], frame);
+ mp_filter_internal_mark_progress(f);
+ } else {
+ mp_frame_unref(&frame);
+ }
}
void reload_audio_output(struct MPContext *mpctx)
@@ -792,12 +789,78 @@ void reload_audio_output(struct MPContext *mpctx)
mp_wakeup_core(mpctx);
}
-void fill_audio_out_buffers(struct MPContext *mpctx)
+// Returns audio start pts for seeking or video sync.
+// Returns false if PTS is not known yet.
+static bool get_sync_pts(struct MPContext *mpctx, double *pts)
{
struct MPOpts *opts = mpctx->opts;
- bool was_eof = mpctx->audio_status == STATUS_EOF;
- dump_audio_stats(mpctx);
+ *pts = MP_NOPTS_VALUE;
+
+ if (!opts->initial_audio_sync)
+ return true;
+
+ bool sync_to_video = mpctx->vo_chain && mpctx->video_status != STATUS_EOF &&
+ !mpctx->vo_chain->is_sparse;
+
+ if (sync_to_video) {
+ if (mpctx->video_status < STATUS_READY)
+ return false; // wait until we know a video PTS
+ if (mpctx->video_pts != MP_NOPTS_VALUE)
+ *pts = mpctx->video_pts - opts->audio_delay;
+ } else if (mpctx->hrseek_active) {
+ *pts = mpctx->hrseek_pts;
+ } else {
+ // If audio-only is enabled mid-stream during playback, sync accordingly.
+ *pts = mpctx->playback_pts;
+ }
+
+ return true;
+}
+
+// Look whether audio can be started yet - if audio has to start some time
+// after video.
+// Caller needs to ensure mpctx->restart_complete is OK
+void audio_start_ao(struct MPContext *mpctx)
+{
+ struct ao_chain *ao_c = mpctx->ao_chain;
+ if (!ao_c || !ao_c->ao || mpctx->audio_status != STATUS_READY)
+ return;
+ double pts = MP_NOPTS_VALUE;
+ if (!get_sync_pts(mpctx, &pts))
+ return;
+ double apts = written_audio_pts(mpctx);
+ apts -= apts != MP_NOPTS_VALUE ? mpctx->audio_speed * ao_get_delay(mpctx->ao) : 0;
+ if (pts != MP_NOPTS_VALUE && apts != MP_NOPTS_VALUE && pts < apts &&
+ mpctx->video_status != STATUS_EOF)
+ {
+ double diff = (apts - pts) / mpctx->opts->playback_speed;
+ if (!get_internal_paused(mpctx))
+ mp_set_timeout(mpctx, diff);
+ if (mpctx->logged_async_diff != diff) {
+ MP_VERBOSE(mpctx, "delaying audio start %f vs. %f, diff=%f\n",
+ apts, pts, diff);
+ mpctx->logged_async_diff = diff;
+ }
+ return;
+ }
+
+ MP_VERBOSE(mpctx, "starting audio playback\n");
+ ao_c->audio_started = true;
+ ao_start(ao_c->ao);
+ mpctx->audio_status = STATUS_PLAYING;
+ if (ao_c->out_eof) {
+ mpctx->audio_status = STATUS_DRAINING;
+ MP_VERBOSE(mpctx, "audio draining\n");
+ }
+ ao_c->underrun = false;
+ mpctx->logged_async_diff = -1;
+ mp_wakeup_core(mpctx);
+}
+
+void fill_audio_out_buffers(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
if (mpctx->ao && ao_query_and_reset_events(mpctx->ao, AO_EVENT_RELOAD))
reload_audio_output(mpctx);
@@ -806,6 +869,8 @@ void fill_audio_out_buffers(struct MPContext *mpctx)