summaryrefslogtreecommitdiffstats
path: root/player/audio.c
diff options
context:
space:
mode:
Diffstat (limited to 'player/audio.c')
-rw-r--r--player/audio.c310
1 files changed, 224 insertions, 86 deletions
diff --git a/player/audio.c b/player/audio.c
index 2a4c90dcc6..a26a7d1ff5 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -33,10 +33,10 @@
#include "common/common.h"
#include "osdep/timer.h"
-#include "audio/audio.h"
#include "audio/audio_buffer.h"
+#include "audio/aconverter.h"
+#include "audio/format.h"
#include "audio/decode/dec_audio.h"
-#include "audio/filter/af.h"
#include "audio/out/ao.h"
#include "demux/demux.h"
#include "video/decode/dec_video.h"
@@ -54,6 +54,11 @@ enum {
AD_STARVE = -6,
};
+#if HAVE_LIBAF
+
+#include "audio/audio.h"
+#include "audio/filter/af.h"
+
// Use pitch correction only for speed adjustments by the user, not minor sync
// correction ones.
static int get_speed_method(struct MPContext *mpctx)
@@ -180,7 +185,6 @@ void audio_update_volume(struct MPContext *mpctx)
if (opts->softvol_mute == 1)
gain = 0.0;
-#if HAVE_GPL
if (!af_control_any_rev(ao_c->af, AF_CONTROL_SET_VOLUME, &gain)) {
if (gain == 1.0)
return;
@@ -190,7 +194,6 @@ void audio_update_volume(struct MPContext *mpctx)
&& af_control_any_rev(ao_c->af, AF_CONTROL_SET_VOLUME, &gain)))
MP_ERR(mpctx, "No volume control available.\n");
}
-#endif
}
/* NOTE: Currently the balance code is seriously buggy: it always changes
@@ -238,7 +241,6 @@ static int recreate_audio_filters(struct MPContext *mpctx)
{
assert(mpctx->ao_chain);
-#if HAVE_GPL
struct af_stream *afs = mpctx->ao_chain->af;
if (afs->initialized < 1 && af_init(afs) < 0)
goto fail;
@@ -246,7 +248,6 @@ static int recreate_audio_filters(struct MPContext *mpctx)
recreate_speed_filters(mpctx);
if (afs->initialized < 1 && af_init(afs) < 0)
goto fail;
-#endif
if (mpctx->opts->softvol == SOFTVOL_NO)
MP_ERR(mpctx, "--softvol=no is not supported anymore.\n");
@@ -284,26 +285,40 @@ int reinit_audio_filters(struct MPContext *mpctx)
return 1;
}
+#else /* HAVE_LIBAV */
+
+void audio_update_volume(struct MPContext *mpctx) {}
+void audio_update_balance(struct MPContext *mpctx) {}
+int reinit_audio_filters(struct MPContext *mpctx) { return 0; }
+
+#endif /* else HAVE_LIBAF */
+
// Call this if opts->playback_speed or mpctx->speed_factor_* change.
void update_playback_speed(struct MPContext *mpctx)
{
mpctx->audio_speed = mpctx->opts->playback_speed * mpctx->speed_factor_a;
mpctx->video_speed = mpctx->opts->playback_speed * mpctx->speed_factor_v;
+#if HAVE_LIBAF
if (!mpctx->ao_chain || mpctx->ao_chain->af->initialized < 1)
return;
if (!update_speed_filters(mpctx))
recreate_audio_filters(mpctx);
+#endif
}
static void ao_chain_reset_state(struct ao_chain *ao_c)
{
ao_c->pts = MP_NOPTS_VALUE;
ao_c->pts_reset = false;
- talloc_free(ao_c->input_frame);
- ao_c->input_frame = NULL;
+ TA_FREEP(&ao_c->input_frame);
+ TA_FREEP(&ao_c->output_frame);
+#if HAVE_LIBAF
af_seek_reset(ao_c->af);
+#endif
+ if (ao_c->conv)
+ mp_aconverter_flush(ao_c->conv);
mp_audio_buffer_clear(ao_c->ao_buffer);
if (ao_c->audio_src)
@@ -350,9 +365,13 @@ static void ao_chain_uninit(struct ao_chain *ao_c)
if (ao_c->filter_src)
lavfi_set_connected(ao_c->filter_src, false);
+#if HAVE_LIBAF
af_destroy(ao_c->af);
+#endif
+ talloc_free(ao_c->conv);
talloc_free(ao_c->input_frame);
talloc_free(ao_c->input_format);
+ talloc_free(ao_c->filter_input_format);
talloc_free(ao_c->ao_buffer);
talloc_free(ao_c);
}
@@ -369,16 +388,17 @@ void uninit_audio_chain(struct MPContext *mpctx)
}
}
-static void get_ao_format(struct ao *ao, struct mp_audio *aformat)
+static char *audio_config_to_str_buf(char *buf, size_t buf_sz, int rate,
+ int format, struct mp_chmap channels)
{
- int samplerate;
- int format;
- struct mp_chmap channels;
- ao_get_format(ao, &samplerate, &format, &channels);
- *aformat = (struct mp_audio){0};
- mp_audio_set_format(aformat, format);
- mp_audio_set_channels(aformat, &channels);
- aformat->rate = samplerate;
+ char ch[128];
+ mp_chmap_to_str_buf(ch, sizeof(ch), &channels);
+ char *hr_ch = mp_chmap_to_str_hr(&channels);
+ if (strcmp(hr_ch, ch) != 0)
+ mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
+ snprintf(buf, buf_sz, "%dHz %s %dch %s", rate,
+ ch, channels.num, af_fmt_to_str(format));
+ return buf;
}
static void reinit_audio_filters_and_output(struct MPContext *mpctx)
@@ -387,7 +407,6 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
struct ao_chain *ao_c = mpctx->ao_chain;
assert(ao_c);
struct track *track = ao_c->track;
- struct af_stream *afs = ao_c->af;
if (!mp_aframe_config_is_valid(ao_c->input_format)) {
// We don't know the audio format yet - so configure it later as we're
@@ -403,24 +422,33 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
uninit_audio_out(mpctx);
}
+ TA_FREEP(&ao_c->output_frame);
+
+ int out_rate = 0;
+ int out_format = 0;
+ struct mp_chmap out_channels = {0};
+ if (mpctx->ao) {
+ ao_get_format(mpctx->ao, &out_rate, &out_format, &out_channels);
+ } else if (af_fmt_is_pcm(mp_aframe_get_format(ao_c->input_format))) {
+ out_rate = opts->force_srate;
+ out_format = opts->audio_output_format;
+ if (opts->audio_output_channels.num_chmaps == 1)
+ out_channels = opts->audio_output_channels.chmaps[0];
+ }
+
+#if HAVE_LIBAF
+ struct af_stream *afs = ao_c->af;
+
struct mp_audio in_format;
mp_audio_config_from_aframe(&in_format, ao_c->input_format);
if (mpctx->ao && mp_audio_config_equals(&in_format, &afs->input))
return;
afs->output = (struct mp_audio){0};
- if (mpctx->ao) {
- get_ao_format(mpctx->ao, &afs->output);
- } else if (af_fmt_is_pcm(mp_aframe_get_format(ao_c->input_format))) {
- afs->output.rate = opts->force_srate;
- mp_audio_set_format(&afs->output, opts->audio_output_format);
- if (opts->audio_output_channels.num_chmaps == 1) {
- mp_audio_set_channels(&afs->output,
- &opts->audio_output_channels.chmaps[0]);
- }
- }
+ afs->output.rate = out_rate;
+ mp_audio_set_format(&afs->output, out_format);
+ mp_audio_set_channels(&afs->output, &out_channels);
-#if HAVE_GPL
// filter input format: same as codec's output format:
afs->input = in_format;
@@ -432,9 +460,27 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
goto init_error;
}
+ out_rate = afs->output.rate;
+ out_format = afs->output.format;
+ out_channels = afs->output.channels;
+#else
+ if (mpctx->ao && ao_c->filter_input_format &&
+ mp_aframe_config_equals(ao_c->filter_input_format, ao_c->input_format))
+ return;
+
+ TA_FREEP(&ao_c->filter_input_format);
+
+ if (!out_rate)
+ out_rate = mp_aframe_get_rate(ao_c->input_format);
+ if (!out_format)
+ out_format = mp_aframe_get_format(ao_c->input_format);
+ if (!out_channels.num)
+ mp_aframe_get_chmap(ao_c->input_format, &out_channels);
+#endif
+
if (!mpctx->ao) {
int ao_flags = 0;
- bool spdif_fallback = af_fmt_is_spdif(afs->output.format) &&
+ bool spdif_fallback = af_fmt_is_spdif(out_format) &&
ao_c->spdif_passthrough;
if (opts->ao_null_fallback && !spdif_fallback)
@@ -446,30 +492,32 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
if (opts->audio_exclusive)
ao_flags |= AO_INIT_EXCLUSIVE;
- if (af_fmt_is_pcm(afs->output.format)) {
+ if (af_fmt_is_pcm(out_format)) {
if (!opts->audio_output_channels.set ||
opts->audio_output_channels.auto_safe)
ao_flags |= AO_INIT_SAFE_MULTICHANNEL_ONLY;
- mp_chmap_sel_list(&afs->output.channels,
+ mp_chmap_sel_list(&out_channels,
opts->audio_output_channels.chmaps,
opts->audio_output_channels.num_chmaps);
}
- mp_audio_set_channels(&afs->output, &afs->output.channels);
-
mpctx->ao = ao_init_best(mpctx->global, ao_flags, mp_wakeup_core_cb,
- mpctx, mpctx->encode_lavc_ctx, afs->output.rate,
- afs->output.format, afs->output.channels);
+ mpctx, mpctx->encode_lavc_ctx, out_rate,
+ out_format, out_channels);
ao_c->ao = mpctx->ao;
- struct mp_audio fmt = {0};
+ int ao_rate = 0;
+ int ao_format = 0;
+ struct mp_chmap ao_channels = {0};
if (mpctx->ao)
- get_ao_format(mpctx->ao, &fmt);
+ ao_get_format(mpctx->ao, &ao_rate, &ao_format, &ao_channels);
// Verify passthrough format was not changed.
- if (mpctx->ao && af_fmt_is_spdif(afs->output.format)) {
- if (!mp_audio_config_equals(&afs->output, &fmt)) {
+ if (mpctx->ao && af_fmt_is_spdif(out_format)) {
+ if (out_rate != ao_rate || out_format != ao_format ||
+ !mp_chmap_equals(&out_channels, &ao_channels))
+ {
MP_ERR(mpctx, "Passthrough format unsupported.\n");
ao_uninit(mpctx->ao);
mpctx->ao = NULL;
@@ -497,17 +545,36 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
goto init_error;
}
- mp_audio_buffer_reinit_fmt(ao_c->ao_buffer, fmt.format, &fmt.channels,
- fmt.rate);
- afs->output = fmt;
+ mp_audio_buffer_reinit_fmt(ao_c->ao_buffer, ao_format, &ao_channels,
+ ao_rate);
+
+#if HAVE_LIBAF
+ afs->output = (struct mp_audio){0};
+ afs->output.rate = ao_rate;
+ mp_audio_set_format(&afs->output, ao_format);
+ mp_audio_set_channels(&afs->output, &ao_channels);
if (!mp_audio_config_equals(&afs->output, &afs->filter_output))
afs->initialized = 0;
+#else
+ int in_rate = mp_aframe_get_rate(ao_c->input_format);
+ int in_format = mp_aframe_get_format(ao_c->input_format);
+ struct mp_chmap in_chmap = {0};
+ mp_aframe_get_chmap(ao_c->input_format, &in_chmap);
+ if (!mp_aconverter_reconfig(ao_c->conv, in_rate, in_format, in_chmap,
+ ao_rate, ao_format, ao_channels))
+ {
+ MP_ERR(mpctx, "Cannot convert audio data for output.\n");
+ goto init_error;
+ }
+ ao_c->filter_input_format = mp_aframe_new_ref(ao_c->input_format);
+#endif
- mpctx->ao_decoder_fmt = mp_aframe_create();
- mp_aframe_config_copy(mpctx->ao_decoder_fmt, ao_c->input_format);
+ mpctx->ao_decoder_fmt = mp_aframe_new_ref(ao_c->input_format);
+ char tmp[80];
MP_INFO(mpctx, "AO: [%s] %s\n", ao_get_name(mpctx->ao),
- mp_audio_config_to_str(&fmt));
+ audio_config_to_str_buf(tmp, sizeof(tmp), ao_rate, ao_format,
+ ao_channels));
MP_VERBOSE(mpctx, "AO: Description: %s\n", ao_get_description(mpctx->ao));
update_window_title(mpctx, true);
@@ -515,6 +582,7 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
opts->audio_wait_open > 0 ? mp_time_sec() + opts->audio_wait_open : 0;
}
+#if HAVE_LIBAF
if (recreate_audio_filters(mpctx) < 0)
goto init_error;
#endif
@@ -584,9 +652,13 @@ 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->log = mpctx->log;
+#if HAVE_LIBAF
ao_c->af = af_new(mpctx->global);
if (track && track->stream)
ao_c->af->replaygain_data = track->stream->codec->replaygain_data;
+#else
+ ao_c->conv = mp_aconverter_create(mpctx->global, mpctx->log, NULL);
+#endif
ao_c->spdif_passthrough = true;
ao_c->pts = MP_NOPTS_VALUE;
ao_c->ao_buffer = mp_audio_buffer_create(NULL);
@@ -628,16 +700,26 @@ double written_audio_pts(struct MPContext *mpctx)
if (!ao_c)
return MP_NOPTS_VALUE;
- if (ao_c->af->initialized < 1)
- return MP_NOPTS_VALUE;
-
// first calculate the end pts of audio that has been output by decoder
double a_pts = ao_c->pts;
if (a_pts == MP_NOPTS_VALUE)
return MP_NOPTS_VALUE;
// Data buffered in audio filters, measured in seconds of "missing" output
- double buffered_output = af_calc_delay(ao_c->af);
+ double buffered_output = 0;
+
+#if HAVE_LIBAF
+ if (ao_c->af->initialized < 1)
+ return MP_NOPTS_VALUE;
+
+ buffered_output += af_calc_delay(ao_c->af);
+#endif
+
+ if (ao_c->conv)
+ buffered_output += mp_aconverter_get_latency(ao_c->conv);
+
+ if (ao_c->output_frame)
+ buffered_output += mp_aframe_duration(ao_c->output_frame);
// Data that was ready for ao but was buffered because ao didn't fully
// accept everything to internal buffers yet
@@ -720,9 +802,12 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
if (mpctx->audio_status != STATUS_SYNCING)
return true;
- struct mp_audio out_format = {0};
- get_ao_format(mpctx->ao, &out_format);
- double play_samplerate = out_format.rate / mpctx->audio_speed;
+ 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;
@@ -784,25 +869,27 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
}
mpctx->audio_allow_second_chance_seek = false;
- int align = af_format_sample_alignment(out_format.format);
+ int align = af_format_sample_alignment(ao_format);
*skip = (int)(-ptsdiff * play_samplerate) / align * align;
return true;
}
-static bool copy_output(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
+static bool copy_output(struct MPContext *mpctx, struct ao_chain *ao_c,
int minsamples, double endpts, bool eof, bool *seteof)
{
- struct af_stream *afs = mpctx->ao_chain->af;
+ struct mp_audio_buffer *outbuf = ao_c->ao_buffer;
- while (mp_audio_buffer_samples(outbuf) < minsamples) {
- if (af_output_frame(afs, eof) < 0)
- return true; // error, stop doing stuff
+ int ao_rate;
+ int ao_format;
+ struct mp_chmap ao_channels;
+ ao_get_format(ao_c->ao, &ao_rate, &ao_format, &ao_channels);
+ while (mp_audio_buffer_samples(outbuf) < minsamples) {
int cursamples = mp_audio_buffer_samples(outbuf);
int maxsamples = INT_MAX;
if (endpts != MP_NOPTS_VALUE) {
- double rate = afs->output.rate / mpctx->audio_speed;
+ double rate = ao_rate / mpctx->audio_speed;
double curpts = written_audio_pts(mpctx);
if (curpts != MP_NOPTS_VALUE) {
double remaining =
@@ -811,24 +898,43 @@ static bool copy_output(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
}
}
- struct mp_audio *mpa = af_read_output_frame(afs);
- if (!mpa)
+ if (!ao_c->output_frame || !mp_aframe_get_size(ao_c->output_frame)) {
+ TA_FREEP(&ao_c->output_frame);
+#if HAVE_LIBAF
+ struct af_stream *afs = mpctx->ao_chain->af;
+ if (af_output_frame(afs, eof) < 0)
+ return true; // error, stop doing stuff
+ struct mp_audio *mpa = af_read_output_frame(afs);
+ ao_c->output_frame = mp_audio_to_aframe(mpa);
+ talloc_free(mpa);
+#else
+ if (eof)
+ mp_aconverter_write_input(ao_c->conv, NULL);
+ mp_aconverter_set_speed(ao_c->conv, mpctx->audio_speed);
+ bool got_eof;
+ ao_c->output_frame = mp_aconverter_read_output(ao_c->conv, &got_eof);
+#endif
+ }
+
+ if (!ao_c->output_frame)
return false; // out of data
- if (cursamples + mpa->samples > maxsamples) {
+ if (cursamples + mp_aframe_get_size(ao_c->output_frame) > maxsamples) {
if (cursamples < maxsamples) {
- struct mp_audio pre = *mpa;
- mp_audio_buffer_append(outbuf, mpa->planes,
+ 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);
- mp_audio_skip_samples(mpa, pre.samples);
}
- af_unread_output_frame(afs, mpa);
*seteof = true;
return true;
}
- mp_audio_buffer_append(outbuf, mpa->planes, mpa->samples);
- talloc_free(mpa);
+ 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;
}
@@ -869,9 +975,14 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
int minsamples)
{
struct ao_chain *ao_c = mpctx->ao_chain;
+#if HAVE_LIBAF
struct af_stream *afs = ao_c->af;
if (afs->initialized < 1)
return AD_ERR;
+#else
+ if (!ao_c->filter_input_format)
+ return AD_ERR;
+#endif
MP_STATS(ao_c, "start audio");
@@ -882,7 +993,7 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
while (1) {
res = 0;
- if (copy_output(mpctx, outbuf, minsamples, endpts, false, &eof))
+ if (copy_output(mpctx, ao_c, minsamples, endpts, false, &eof))
break;
res = decode_new_frame(ao_c);
@@ -892,41 +1003,58 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
break;
if (res < 0) {
// drain filters first (especially for true EOF case)
- copy_output(mpctx, outbuf, minsamples, endpts, true, &eof);
+ copy_output(mpctx, ao_c, minsamples, endpts, true, &eof);
break;
}
// On format change, make sure to drain the filter chain.
+#if HAVE_LIBAF
struct mp_audio in_format;
mp_audio_config_from_aframe(&in_format, ao_c->input_format);
if (!mp_audio_config_equals(&afs->input, &in_format)) {
- copy_output(mpctx, outbuf, minsamples, endpts, true, &eof);
+ copy_output(mpctx, ao_c, minsamples, endpts, true, &eof);
+ res = AD_NEW_FMT;
+ break;
+ }
+#else
+ if (!mp_aframe_config_equals(ao_c->filter_input_format,
+ ao_c->input_format))
+ {
+ copy_output(mpctx, ao_c, minsamples, endpts, true, &eof);
res = AD_NEW_FMT;
break;
}
+#endif
- struct mp_audio *mpa = mp_audio_from_aframe(ao_c->input_frame);
- talloc_free(ao_c->input_frame);
- ao_c->input_frame = NULL;
- if (!mpa)
- abort();
- if (mpa->pts == MP_NOPTS_VALUE) {
+ double pts = mp_aframe_get_pts(ao_c->input_frame);
+ if (pts == MP_NOPTS_VALUE) {
ao_c->pts = MP_NOPTS_VALUE;
} else {
// Attempt to detect jumps in PTS. Even for the lowest sample rates
// and with worst container rounded timestamp, this should be a
// margin more than enough.
- double desync = mpa->pts - ao_c->pts;
+ double desync = pts - ao_c->pts;
if (ao_c->pts != MP_NOPTS_VALUE && fabs(desync) > 0.1) {
MP_WARN(ao_c, "Invalid audio PTS: %f -> %f\n",
- ao_c->pts, mpa->pts);
+ ao_c->pts, pts);
if (desync >= 5)
ao_c->pts_reset = true;
}
- ao_c->pts = mpa->pts + mpa->samples / (double)mpa->rate;
+ ao_c->pts = mp_aframe_end_pts(ao_c->input_frame);
}
+
+#if HAVE_LIBAF
+ struct mp_audio *mpa = mp_audio_from_aframe(ao_c->input_frame);
+ talloc_free(ao_c->input_frame);
+ ao_c->input_frame = NULL;
+ if (!mpa)
+ abort();
if (af_filter_frame(afs, mpa) < 0)
return AD_ERR;
+#else
+ if (mp_aconverter_write_input(ao_c->conv, ao_c->input_frame))
+ ao_c->input_frame = NULL;
+#endif
}
if (res == 0 && mp_audio_buffer_samples(outbuf) < minsamples && eof)
@@ -957,7 +1085,10 @@ void reload_audio_output(struct MPContext *mpctx)
ao_c->spdif_passthrough = true;
ao_c->spdif_failed = false;
d_audio->try_spdif = true;
+#if HAVE_LIBAF
ao_c->af->initialized = 0;
+#endif
+ TA_FREEP(&ao_c->filter_input_format);
if (!audio_init_best_codec(d_audio)) {
MP_ERR(mpctx, "Error reinitializing audio.\n");
error_on_track(mpctx, ao_c->track);
@@ -982,7 +1113,12 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (!ao_c)
return;
- if (ao_c->af->initialized < 1 || !mpctx->ao) {
+ bool is_initialized = !!ao_c->filter_input_format;
+#if HAVE_LIBAF
+ is_initialized = ao_c->af->initialized == 1;
+#endif
+
+ if (!is_initialized || !mpctx->ao) {
// Probe the initial audio format. Returns AD_OK (and does nothing) if
// the format is already known.
int r = AD_NO_PROGRESS;
@@ -1012,10 +1148,12 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
return;
}
- struct mp_audio out_format = {0};
- get_ao_format(mpctx->ao, &out_format);
- double play_samplerate = out_format.rate / mpctx->audio_speed;
- int align = af_format_sample_alignment(out_format.format);
+ 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;
+ int align = af_format_sample_alignment(ao_format);
// If audio is infinitely fast, somehow try keeping approximate A/V sync.
if (mpctx->audio_status == STATUS_PLAYING && ao_untimed(mpctx->ao) &&