audio: don't let ao_lavc access frontend internals, change gapless audio

ao_lavc.c accesses ao->buffer, which I consider internal. The access was
done in ao_lavc.c/uninit(), which tried to get the left-over audio in
order to write the last (possibly partial) audio frame. The play()
function didn't accept partial frames, because the AOPLAY_FINAL_CHUNK
flag was not correctly set, and handling it otherwise would require an
internal FIFO.

Fix this by making sure that with gapless audio (used with encoding),
the AOPLAY_FINAL_CHUNK is set only once, instead when each file ends.
Basically, move the hack in ao_lavc's uninit to uninit_player.

One thing can not be entirely correctly handled: if gapless audio is
active, we don't know really whether the AO is closed because the file
ended playing (i.e. we want to send the buffered remainder of the audio
to the AO), or whether the user is quitting the player. (The stop_play
flag is overwritten, fixing that is perhaps not worth it.) Handle this
by adding additional code to drain the AO and the buffers when playback
is quit (see play_current_file() change).

Test case: mpv avdevice://lavfi:sine=441 avdevice://lavfi:sine=441 -length 0.2267  -gapless-audio
This commit is contained in:
wm4 2013-11-08 20:00:58 +01:00
parent 052a7d54ab
commit 8125252399
6 changed files with 77 additions and 46 deletions

View File

@ -198,11 +198,7 @@ autoprobe:
void ao_uninit(struct ao *ao, bool cut_audio)
{
assert(ao->buffer.len >= ao->buffer_playable_size);
ao->buffer.len = ao->buffer_playable_size;
ao->driver->uninit(ao, cut_audio);
if (!cut_audio && ao->buffer.len)
mp_msg(MSGT_AO, MSGL_WARN, "Audio output truncated at end.\n");
talloc_free(ao);
}
@ -234,8 +230,6 @@ int ao_get_space(struct ao *ao)
void ao_reset(struct ao *ao)
{
ao->buffer.len = 0;
ao->buffer_playable_size = 0;
if (ao->driver->reset)
ao->driver->reset(ao);
}

View File

@ -73,7 +73,8 @@ struct ao {
int bps; // bytes per second
double pts; // some mplayer.c state (why is this here?)
struct bstr buffer;
int buffer_playable_size;
int buffer_playable_size; // part of the part of the buffer the AO hasn't
// accepted yet with play()
bool probing; // if true, don't fail loudly on init
bool untimed;
bool no_persistent_volume; // the AO does the equivalent of af_volume

View File

@ -299,7 +299,6 @@ static int encode(struct ao *ao, double apts, void *data);
static int play(struct ao *ao, void *data, int len, int flags);
static void uninit(struct ao *ao, bool cut_audio)
{
struct priv *ac = ao->priv;
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
if (!encode_lavc_start(ectx)) {
@ -307,33 +306,6 @@ static void uninit(struct ao *ao, bool cut_audio)
return;
}
if (ac->buffer) {
if (ao->buffer.len > 0) {
// TRICK: append aframesize-1 samples to the end, then play() will
// encode all it can
size_t extralen =
(ac->aframesize - 1) * ao->channels.num * ac->sample_size;
void *paddingbuf = talloc_size(ao, ao->buffer.len + extralen);
memcpy(paddingbuf, ao->buffer.start, ao->buffer.len);
fill_with_padding((char *) paddingbuf + ao->buffer.len,
extralen / ac->sample_size,
ac->sample_size, ac->sample_padding);
int written = play(ao, paddingbuf, ao->buffer.len + extralen, 0);
if (written < ao->buffer.len) {
MP_ERR(ao, "did not write enough data at the end\n");
}
talloc_free(paddingbuf);
ao->buffer.len = 0;
}
double outpts = ac->expected_next_pts;
if (!ectx->options->rawts && ectx->options->copyts)
outpts += ectx->discontinuity_pts_offset;
outpts += encode_lavc_getoffset(ectx, ac->stream);
while (encode(ao, outpts, NULL) > 0) ;
}
ao->priv = NULL;
}
@ -484,6 +456,7 @@ static int play(struct ao *ao, void *data, int len, int flags)
double nextpts;
double pts = ao->pts;
double outpts;
int bytelen = len;
len /= ac->sample_size * ao->channels.num;
@ -491,6 +464,36 @@ static int play(struct ao *ao, void *data, int len, int flags)
MP_WARN(ao, "not ready yet for encoding audio\n");
return 0;
}
if (flags & AOPLAY_FINAL_CHUNK) {
int written = 0;
if (len > 0) {
size_t extralen =
(ac->aframesize - 1) * ao->channels.num * ac->sample_size;
paddingbuf = talloc_size(NULL, bytelen + extralen);
memcpy(paddingbuf, data, bytelen);
fill_with_padding((char *) paddingbuf + bytelen,
extralen / ac->sample_size,
ac->sample_size, ac->sample_padding);
// No danger of recursion, because AOPLAY_FINAL_CHUNK not set
written = play(ao, paddingbuf, bytelen + extralen, 0);
if (written < bytelen) {
MP_ERR(ao, "did not write enough data at the end\n");
}
talloc_free(paddingbuf);
paddingbuf = NULL;
}
outpts = ac->expected_next_pts;
if (!ectx->options->rawts && ectx->options->copyts)
outpts += ectx->discontinuity_pts_offset;
outpts += encode_lavc_getoffset(ectx, ac->stream);
while (encode(ao, outpts, NULL) > 0) ;
return FFMIN(written, bytelen);
}
if (pts == MP_NOPTS_VALUE) {
MP_WARN(ao, "frame without pts, please report; synthesizing pts instead\n");
// synthesize pts from previous expected next pts

View File

@ -225,10 +225,13 @@ static int write_to_ao(struct MPContext *mpctx, void *data, int len, int flags,
return 0;
struct ao *ao = mpctx->ao;
double bps = ao->bps / mpctx->opts->playback_speed;
int unitsize = ao->channels.num * af_fmt2bits(ao->format) / 8;
ao->pts = pts;
int played = ao_play(mpctx->ao, data, len, flags);
assert(played <= len);
assert(played % unitsize == 0);
if (played > 0) {
mpctx->shown_aframes += played / (af_fmt2bits(ao->format) / 8);
mpctx->shown_aframes += played / unitsize;
mpctx->delay += played / bps;
// Keep correct pts for remaining data - could be used to flush
// remaining buffer when closing ao.
@ -332,6 +335,7 @@ int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
int playsize;
int playflags = 0;
bool audio_eof = false;
bool signal_eof = false;
bool partial_fill = false;
sh_audio_t * const sh_audio = mpctx->sh_audio;
bool modifiable_audio_format = !(ao->format & AF_FORMAT_SPECIAL_MASK);
@ -377,7 +381,6 @@ int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
* ao->bps / opts->playback_speed;
if (playsize > bytes) {
playsize = MPMAX(bytes, 0);
playflags |= AOPLAY_FINAL_CHUNK;
audio_eof = true;
partial_fill = true;
}
@ -387,18 +390,24 @@ int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
if (playsize > ao->buffer.len) {
partial_fill = true;
playsize = ao->buffer.len;
if (audio_eof)
playflags |= AOPLAY_FINAL_CHUNK;
}
playsize -= playsize % unitsize;
if (!playsize)
return partial_fill && audio_eof ? -2 : -partial_fill;
// play audio:
if (audio_eof && partial_fill) {
if (opts->gapless_audio) {
// With gapless audio, delay this to ao_uninit. There must be only
// 1 final chunk, and that is handled when calling ao_uninit().
signal_eof = true;
} else {
playflags |= AOPLAY_FINAL_CHUNK;
}
}
assert(ao->buffer_playable_size <= ao->buffer.len);
int played = write_to_ao(mpctx, ao->buffer.start, playsize, playflags,
written_audio_pts(mpctx));
assert(played % unitsize == 0);
ao->buffer_playable_size = playsize - played;
if (played > 0) {
@ -407,8 +416,8 @@ int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
} else if (!mpctx->paused && audio_eof && ao_get_delay(ao) < .04) {
// Sanity check to avoid hanging in case current ao doesn't output
// partial chunks and doesn't check for AOPLAY_FINAL_CHUNK
return -2;
signal_eof = true;
}
return -partial_fill;
return signal_eof ? -2 : -partial_fill;
}

View File

@ -62,6 +62,8 @@
void uninit_player(struct MPContext *mpctx, unsigned int mask)
{
struct MPOpts *opts = mpctx->opts;
mask &= mpctx->initialized_flags;
MP_DBG(mpctx, "\n*** uninit(0x%X)\n", mask);
@ -154,9 +156,22 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask)
}
if (mask & INITIALIZED_AO) {
struct ao *ao = mpctx->ao;
mpctx->initialized_flags &= ~INITIALIZED_AO;
if (mpctx->ao)
ao_uninit(mpctx->ao, mpctx->stop_play != AT_END_OF_FILE);
if (ao) {
bool drain = false;
// Note: with gapless_audio, stop_play is not correctly set
if (opts->gapless_audio || mpctx->stop_play == AT_END_OF_FILE) {
drain = true;
int len = ao->buffer_playable_size;
assert(len <= ao->buffer.len);
int played = ao_play(ao, ao->buffer.start, len,
AOPLAY_FINAL_CHUNK);
if (played < len)
MP_WARN(ao, "Audio output truncated at end.\n");
}
ao_uninit(ao, drain);
}
mpctx->ao = NULL;
}
@ -1277,6 +1292,13 @@ terminate_playback: // don't jump here after ao/vo/getch initialization!
if (mpctx->stop_play == KEEP_PLAYING)
mpctx->stop_play = AT_END_OF_FILE;
// Drop audio left in the buffers
if (mpctx->stop_play != AT_END_OF_FILE && mpctx->ao) {
mpctx->ao->buffer_playable_size = 0;
mpctx->ao->buffer.len = 0;
ao_reset(mpctx->ao);
}
if (opts->position_save_on_quit && mpctx->stop_play == PT_QUIT)
mp_write_watch_later_conf(mpctx);

View File

@ -254,10 +254,12 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
if (demuxer_amount == -1) {
assert(!need_reset);
mpctx->stop_play = AT_END_OF_FILE;
// Clear audio from current position
if (mpctx->sh_audio && !timeline_fallthrough) {
// Seek outside of the file -> clear audio from current position
ao_reset(mpctx->ao);
mpctx->sh_audio->a_buffer_len = 0;
mpctx->ao->buffer.len = 0;
mpctx->ao->buffer_playable_size = 0;
}
return -1;
}