audio/out: require AO drivers to report period size and correct buffer

Before this change, AOs could have internal alignment, and play() would
not consume the trailing data if the size passed to it is not aligned.
Change this to require AOs to report their alignment (via period_size),
and make sure to always send aligned data.

The buffer reported by get_space() now always has to be correct and
reliable. If play() does not consume all data provided (which is bounded
by get_space()), an error is printed.

This is preparation for potential further AO changes.

I casually checked alsa/lavc/null/pcm, the other AOs might or might not
work.
This commit is contained in:
wm4 2017-06-25 15:57:15 +02:00
parent 72ef74dab5
commit 037c37519b
9 changed files with 39 additions and 4 deletions

View File

@ -194,6 +194,8 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
ao->stream_silence = flags & AO_INIT_STREAM_SILENCE;
ao->period_size = 1;
int r = ao->driver->init(ao);
if (r < 0) {
// Silly exception for coreaudio spdif redirection
@ -209,6 +211,11 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
goto fail;
}
if (ao->period_size < 1) {
MP_ERR(ao, "Invalid period size set.\n");
goto fail;
}
ao->sstride = af_fmt_to_bytes(ao->format);
ao->num_planes = 1;
if (af_fmt_is_planar(ao->format)) {

View File

@ -840,6 +840,7 @@ static int init_device(struct ao *ao, int mode)
MP_VERBOSE(ao, "period size: %d samples\n", (int)p->outburst);
ao->device_buffer = p->buffersize;
ao->period_size = p->outburst;
return 0;

View File

@ -160,6 +160,8 @@ static int init(struct ao *ao)
ao->untimed = true;
ao->period_size = ac->aframesize * ac->framecount;
if (ao->channels.num > AV_NUM_DATA_POINTERS)
goto fail;
@ -203,7 +205,7 @@ static void uninit(struct ao *ao)
ac->shutdown = true;
}
// return: how many bytes can be played without blocking
// return: how many samples can be played without blocking
static int get_space(struct ao *ao)
{
struct priv *ac = ao->priv;

View File

@ -108,6 +108,8 @@ static int init(struct ao *ao)
priv->last_time = mp_time_sec();
ao->period_size = priv->outburst;
return 0;
}

View File

@ -214,6 +214,7 @@ static int init(struct ao *ao)
}
p->chunk_size = CHUNK_SAMPLES * af_fmt_to_bytes(ao->format);
ao->period_size = CHUNK_SAMPLES;
return 0;
err_out:

View File

@ -394,7 +394,8 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
}
}
p->outburst -= p->outburst % (channels.num * af_fmt_to_bytes(format)); // round down
ao->period_size = channels.num * af_fmt_to_bytes(format);
p->outburst -= p->outburst % ao->period_size; // round down
return 0;

View File

@ -195,6 +195,8 @@ static int init(struct ao *ao)
if (!p->pfd)
goto error;
ao->period_size = p->par.round;
return 0;
error:

View File

@ -48,6 +48,14 @@ struct ao {
int init_flags; // AO_INIT_* flags
bool stream_silence; // if audio inactive, just play silence
// Set by the driver on init. This is typically the period size, and the
// smallest unit the driver will accept in one piece (although if
// AOPLAY_FINAL_CHUNK is set, the driver must accept everything).
// This value is in complete samples (i.e. 1 for stereo means 1 sample
// for both channels each).
// Used for push based API only.
int period_size;
// The device as selected by the user, usually using ao_device_desc.name
// from an entry from the list returned by driver->list_devices. If the
// default device should be used, this is set to NULL.

View File

@ -285,6 +285,8 @@ static void ao_play_data(struct ao *ao)
int space = ao->driver->get_space(ao);
bool play_silence = p->paused || (ao->stream_silence && !p->still_playing);
space = MPMAX(space, 0);
if (space % ao->period_size)
MP_ERR(ao, "Audio device reports unaligned available buffer size.\n");
struct mp_audio data;
if (play_silence) {
ao_get_silence(ao, &data, space);
@ -295,16 +297,25 @@ static void ao_play_data(struct ao *ao)
if (data.samples > space)
data.samples = space;
int flags = 0;
if (p->final_chunk && data.samples == max)
if (p->final_chunk && data.samples == max) {
flags |= AOPLAY_FINAL_CHUNK;
} else {
data.samples = data.samples / ao->period_size * ao->period_size;
}
MP_STATS(ao, "start ao fill");
int r = 0;
if (data.samples)
r = ao->driver->play(ao, data.planes, data.samples, flags);
MP_STATS(ao, "end ao fill");
if (r > data.samples) {
MP_WARN(ao, "Audio device returned non-sense value.\n");
MP_ERR(ao, "Audio device returned non-sense value.\n");
r = data.samples;
} else if (r < 0) {
MP_ERR(ao, "Error writing audio to device.\n");
} else if (r != data.samples) {
MP_ERR(ao, "Audio device returned broken buffer state (sent %d samples, "
"got %d samples, %d period%s)!\n", data.samples, r,
ao->period_size, flags & AOPLAY_FINAL_CHUNK ? " final" : "");
}
r = MPMAX(r, 0);
// Probably can't copy the rest of the buffer due to period alignment.