mirror of https://github.com/mpv-player/mpv
audio: switch output to mp_audio_buffer
Replace the code that used a single buffer with mp_audio_buffer. This also enables non-interleaved output operation, although it's still disabled, and no AO supports it yet.
This commit is contained in:
parent
d1ee9ea261
commit
347a86198b
|
@ -38,6 +38,7 @@
|
|||
#include "dec_audio.h"
|
||||
#include "ad.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
|
||||
#include "audio/filter/af.h"
|
||||
|
||||
|
@ -230,18 +231,9 @@ int init_audio_filters(sh_audio_t *sh_audio, int in_samplerate,
|
|||
return 1;
|
||||
}
|
||||
|
||||
static void set_min_out_buffer_size(struct bstr *outbuf, int len)
|
||||
{
|
||||
size_t oldlen = talloc_get_size(outbuf->start);
|
||||
if (oldlen < len) {
|
||||
assert(outbuf->start); // talloc context should be already set
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "Increasing filtered audio buffer size "
|
||||
"from %zd to %d\n", oldlen, len);
|
||||
outbuf->start = talloc_realloc_size(NULL, outbuf->start, len);
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_n_bytes(sh_audio_t *sh, struct bstr *outbuf, int len)
|
||||
// Filter len bytes of input, put result into outbuf.
|
||||
static int filter_n_bytes(sh_audio_t *sh, struct mp_audio_buffer *outbuf,
|
||||
int len)
|
||||
{
|
||||
assert(len - 1 + sh->audio_out_minsize <= sh->a_buffer_size);
|
||||
|
||||
|
@ -280,10 +272,7 @@ static int filter_n_bytes(sh_audio_t *sh, struct bstr *outbuf, int len)
|
|||
struct mp_audio *filter_output = af_play(sh->afilter, &filter_input);
|
||||
if (!filter_output)
|
||||
return -1;
|
||||
int outlen = filter_output->samples * filter_output->sstride;
|
||||
set_min_out_buffer_size(outbuf, outbuf->len + outlen);
|
||||
memcpy(outbuf->start + outbuf->len, filter_output->planes[0], outlen);
|
||||
outbuf->len += outlen;
|
||||
mp_audio_buffer_append(outbuf, filter_output);
|
||||
|
||||
// remove processed data from decoder buffer:
|
||||
sh->a_buffer_len -= len;
|
||||
|
@ -292,20 +281,20 @@ static int filter_n_bytes(sh_audio_t *sh, struct bstr *outbuf, int len)
|
|||
return error;
|
||||
}
|
||||
|
||||
/* Try to get at least minlen decoded+filtered bytes in outbuf
|
||||
/* Try to get at least minsamples decoded+filtered samples in outbuf
|
||||
* (total length including possible existing data).
|
||||
* Return 0 on success, -1 on error/EOF (not distinguished).
|
||||
* In the former case outbuf->len is always >= minlen on return.
|
||||
* In case of EOF/error it might or might not be.
|
||||
* Outbuf.start must be talloc-allocated, and will be reallocated
|
||||
* if needed to fit all filter output. */
|
||||
int decode_audio(sh_audio_t *sh_audio, struct bstr *outbuf, int minlen)
|
||||
* In the former case outbuf has at least minsamples buffered on return.
|
||||
* In case of EOF/error it might or might not be. */
|
||||
int decode_audio(sh_audio_t *sh_audio, struct mp_audio_buffer *outbuf,
|
||||
int minsamples)
|
||||
{
|
||||
// Indicates that a filter seems to be buffering large amounts of data
|
||||
int huge_filter_buffer = 0;
|
||||
int sstride =
|
||||
af_fmt2bits(sh_audio->sample_format) / 8 * sh_audio->channels.num;
|
||||
// Decoded audio must be cut at boundaries of this many bytes
|
||||
int bps = af_fmt2bits(sh_audio->sample_format) / 8;
|
||||
int unitsize = sh_audio->channels.num * bps * 16;
|
||||
int unitsize = sstride * 16;
|
||||
|
||||
/* Filter output size will be about filter_multiplier times input size.
|
||||
* If some filter buffers audio in big blocks this might only hold
|
||||
|
@ -322,9 +311,13 @@ int decode_audio(sh_audio_t *sh_audio, struct bstr *outbuf, int minlen)
|
|||
return -1;
|
||||
max_decode_len -= max_decode_len % unitsize;
|
||||
|
||||
while (minlen >= 0 && outbuf->len < minlen) {
|
||||
while (minsamples >= 0 && mp_audio_buffer_samples(outbuf) < minsamples) {
|
||||
struct af_stream *afs = sh_audio->afilter;
|
||||
int out_sstride = afs->output.sstride;
|
||||
int declen = (minsamples - mp_audio_buffer_samples(outbuf))
|
||||
* out_sstride / filter_multiplier;
|
||||
// + some extra for possible filter buffering
|
||||
int declen = (minlen - outbuf->len) / filter_multiplier + (unitsize << 5);
|
||||
declen += unitsize << 5;
|
||||
if (huge_filter_buffer)
|
||||
/* Some filter must be doing significant buffering if the estimated
|
||||
* input length didn't produce enough output from filters.
|
||||
|
@ -349,15 +342,6 @@ int decode_audio(sh_audio_t *sh_audio, struct bstr *outbuf, int minlen)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void decode_audio_prepend_bytes(struct bstr *outbuf, int count, int byte)
|
||||
{
|
||||
set_min_out_buffer_size(outbuf, outbuf->len + count);
|
||||
memmove(outbuf->start + count, outbuf->start, outbuf->len);
|
||||
memset(outbuf->start, byte, count);
|
||||
outbuf->len += count;
|
||||
}
|
||||
|
||||
|
||||
void resync_audio_stream(sh_audio_t *sh_audio)
|
||||
{
|
||||
sh_audio->pts = MP_NOPTS_VALUE;
|
||||
|
|
|
@ -22,15 +22,14 @@
|
|||
#include "audio/chmap.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
struct bstr;
|
||||
struct mp_audio_buffer;
|
||||
struct mp_decoder_list;
|
||||
|
||||
struct mp_decoder_list *mp_audio_decoder_list(void);
|
||||
int init_best_audio_codec(sh_audio_t *sh_audio, char *audio_decoders);
|
||||
int decode_audio(sh_audio_t *sh_audio, struct bstr *outbuf, int minlen);
|
||||
void decode_audio_prepend_bytes(struct bstr *outbuf, int count, int byte);
|
||||
int decode_audio(sh_audio_t *sh_audio, struct mp_audio_buffer *outbuf,
|
||||
int minsamples);
|
||||
void resync_audio_stream(sh_audio_t *sh_audio);
|
||||
void skip_audio_frame(sh_audio_t *sh_audio);
|
||||
void uninit_audio(sh_audio_t *sh_audio);
|
||||
|
||||
int init_audio_filters(sh_audio_t *sh_audio, int in_samplerate,
|
||||
|
|
|
@ -209,10 +209,9 @@ void ao_uninit(struct ao *ao, bool cut_audio)
|
|||
talloc_free(ao);
|
||||
}
|
||||
|
||||
int ao_play(struct ao *ao, void *data, int len, int flags)
|
||||
int ao_play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
int r = ao->driver->play(ao, &data, len / ao->sstride, flags);
|
||||
return r < 0 ? r : r * ao->sstride;
|
||||
return ao->driver->play(ao, data, samples, flags);
|
||||
}
|
||||
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
|
@ -233,7 +232,7 @@ double ao_get_delay(struct ao *ao)
|
|||
|
||||
int ao_get_space(struct ao *ao)
|
||||
{
|
||||
return ao->driver->get_space(ao) * ao->sstride;
|
||||
return ao->driver->get_space(ao);
|
||||
}
|
||||
|
||||
void ao_reset(struct ao *ao)
|
||||
|
@ -260,7 +259,10 @@ int ao_play_silence(struct ao *ao, int samples)
|
|||
return 0;
|
||||
char *p = talloc_size(NULL, samples * ao->sstride);
|
||||
af_fill_silence(p, samples * ao->sstride, ao->format);
|
||||
int r = ao_play(ao, p, samples * ao->sstride, 0);
|
||||
void *tmp[MP_NUM_CHANNELS];
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
tmp[n] = p;
|
||||
int r = ao_play(ao, tmp, samples, 0);
|
||||
talloc_free(p);
|
||||
return r;
|
||||
}
|
||||
|
|
|
@ -76,8 +76,8 @@ struct ao {
|
|||
int sstride; // size of a sample on each plane
|
||||
// (format_size*num_channels/num_planes)
|
||||
double pts; // some mplayer.c state (why is this here?)
|
||||
struct bstr buffer;
|
||||
int buffer_playable_size; // part of the part of the buffer the AO hasn't
|
||||
struct mp_audio_buffer *buffer; // queued audio; passed to play() later
|
||||
int buffer_playable_samples;// 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;
|
||||
|
@ -97,7 +97,7 @@ struct ao *ao_init_best(struct mpv_global *global,
|
|||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels);
|
||||
void ao_uninit(struct ao *ao, bool cut_audio);
|
||||
int ao_play(struct ao *ao, void *data, int len, int flags);
|
||||
int ao_play(struct ao *ao, void **data, int samples, int flags);
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
|
||||
double ao_get_delay(struct ao *ao);
|
||||
int ao_get_space(struct ao *ao);
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
#include "mpvcore/mp_common.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/decode/dec_audio.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/out/ao.h"
|
||||
|
@ -147,7 +149,11 @@ void reinit_audio_chain(struct MPContext *mpctx)
|
|||
MP_ERR(mpctx, "Could not open/initialize audio device -> no sound.\n");
|
||||
goto init_error;
|
||||
}
|
||||
ao->buffer.start = talloc_new(ao);
|
||||
|
||||
ao->buffer = mp_audio_buffer_create(ao);
|
||||
mp_audio_buffer_reinit_fmt(ao->buffer, ao->format, &ao->channels,
|
||||
ao->samplerate);
|
||||
|
||||
char *s = mp_audio_fmt_to_str(ao->samplerate, &ao->channels, ao->format);
|
||||
MP_INFO(mpctx, "AO: [%s] %s\n", ao->driver->name, s);
|
||||
talloc_free(s);
|
||||
|
@ -196,16 +202,16 @@ double written_audio_pts(struct MPContext *mpctx)
|
|||
// Decoded but not filtered
|
||||
a_pts -= sh_audio->a_buffer_len / bps;
|
||||
|
||||
// Data buffered in audio filters, measured in bytes of "missing" output
|
||||
double buffered_output = af_calc_delay(sh_audio->afilter);
|
||||
// Data buffered in audio filters, measured in seconds of "missing" output
|
||||
double buffered_output = af_calc_delay(sh_audio->afilter) / mpctx->ao->bps;
|
||||
|
||||
// Data that was ready for ao but was buffered because ao didn't fully
|
||||
// accept everything to internal buffers yet
|
||||
buffered_output += mpctx->ao->buffer.len;
|
||||
buffered_output += mp_audio_buffer_seconds(mpctx->ao->buffer);
|
||||
|
||||
// Filters divide audio length by playback_speed, so multiply by it
|
||||
// to get the length in original units without speedup or slowdown
|
||||
a_pts -= buffered_output * mpctx->opts->playback_speed / mpctx->ao->bps;
|
||||
a_pts -= buffered_output * mpctx->opts->playback_speed;
|
||||
|
||||
return a_pts + mpctx->video_offset;
|
||||
}
|
||||
|
@ -219,29 +225,42 @@ double playing_audio_pts(struct MPContext *mpctx)
|
|||
return pts - mpctx->opts->playback_speed * ao_get_delay(mpctx->ao);
|
||||
}
|
||||
|
||||
static int write_to_ao(struct MPContext *mpctx, void *data, int len, int flags,
|
||||
static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags,
|
||||
double pts)
|
||||
{
|
||||
if (mpctx->paused)
|
||||
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);
|
||||
double real_samplerate = ao->samplerate / mpctx->opts->playback_speed;
|
||||
int played = ao_play(mpctx->ao, data->planes, data->samples, flags);
|
||||
assert(played <= data->samples);
|
||||
if (played > 0) {
|
||||
mpctx->shown_aframes += played / unitsize;
|
||||
mpctx->delay += played / bps;
|
||||
mpctx->shown_aframes += played;
|
||||
mpctx->delay += played / real_samplerate;
|
||||
// Keep correct pts for remaining data - could be used to flush
|
||||
// remaining buffer when closing ao.
|
||||
ao->pts += played / bps;
|
||||
ao->pts += played / real_samplerate;
|
||||
return played;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_silence_to_ao(struct MPContext *mpctx, int samples, int flags,
|
||||
double pts)
|
||||
{
|
||||
struct mp_audio tmp = {0};
|
||||
mp_audio_buffer_get_format(mpctx->ao->buffer, &tmp);
|
||||
tmp.samples = samples;
|
||||
char *p = talloc_size(NULL, tmp.samples * tmp.sstride);
|
||||
for (int n = 0; n < tmp.num_planes; n++)
|
||||
tmp.planes[n] = p;
|
||||
mp_audio_fill_silence(&tmp, 0, tmp.samples);
|
||||
int r = write_to_ao(mpctx, &tmp, 0, pts);
|
||||
talloc_free(p);
|
||||
return r;
|
||||
}
|
||||
|
||||
#define ASYNC_PLAY_DONE -3
|
||||
static int audio_start_sync(struct MPContext *mpctx, int playsize)
|
||||
{
|
||||
|
@ -251,14 +270,14 @@ static int audio_start_sync(struct MPContext *mpctx, int playsize)
|
|||
int res;
|
||||
|
||||
// Timing info may not be set without
|
||||
res = decode_audio(sh_audio, &ao->buffer, 1);
|
||||
res = decode_audio(sh_audio, ao->buffer, 1);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
int bytes;
|
||||
int samples;
|
||||
bool did_retry = false;
|
||||
double written_pts;
|
||||
double bps = ao->bps / opts->playback_speed;
|
||||
double real_samplerate = ao->samplerate / opts->playback_speed;
|
||||
bool hrseek = mpctx->hrseek_active; // audio only hrseek
|
||||
mpctx->hrseek_active = false;
|
||||
while (1) {
|
||||
|
@ -269,64 +288,56 @@ static int audio_start_sync(struct MPContext *mpctx, int playsize)
|
|||
else
|
||||
ptsdiff = written_pts - mpctx->sh_video->pts - mpctx->delay
|
||||
- mpctx->audio_delay;
|
||||
bytes = ptsdiff * bps;
|
||||
bytes -= bytes % (ao->channels.num * af_fmt2bits(ao->format) / 8);
|
||||
samples = ptsdiff * real_samplerate;
|
||||
|
||||
// ogg demuxers give packets without timing
|
||||
if (written_pts <= 1 && sh_audio->pts == MP_NOPTS_VALUE) {
|
||||
if (!did_retry) {
|
||||
// Try to read more data to see packets that have pts
|
||||
res = decode_audio(sh_audio, &ao->buffer, ao->bps);
|
||||
res = decode_audio(sh_audio, ao->buffer, ao->samplerate);
|
||||
if (res < 0)
|
||||
return res;
|
||||
did_retry = true;
|
||||
continue;
|
||||
}
|
||||
bytes = 0;
|
||||
samples = 0;
|
||||
}
|
||||
|
||||
if (fabs(ptsdiff) > 300 || isnan(ptsdiff)) // pts reset or just broken?
|
||||
bytes = 0;
|
||||
samples = 0;
|
||||
|
||||
if (bytes > 0)
|
||||
if (samples > 0)
|
||||
break;
|
||||
|
||||
mpctx->syncing_audio = false;
|
||||
int a = MPMIN(-bytes, MPMAX(playsize, 20000));
|
||||
res = decode_audio(sh_audio, &ao->buffer, a);
|
||||
bytes += ao->buffer.len;
|
||||
if (bytes >= 0) {
|
||||
memmove(ao->buffer.start,
|
||||
ao->buffer.start + ao->buffer.len - bytes, bytes);
|
||||
ao->buffer.len = bytes;
|
||||
int skip_samples = -samples;
|
||||
int a = MPMIN(skip_samples, MPMAX(playsize, 2500));
|
||||
res = decode_audio(sh_audio, ao->buffer, a);
|
||||
if (skip_samples <= mp_audio_buffer_samples(ao->buffer)) {
|
||||
mp_audio_buffer_skip(ao->buffer, skip_samples);
|
||||
if (res < 0)
|
||||
return res;
|
||||
return decode_audio(sh_audio, &ao->buffer, playsize);
|
||||
return decode_audio(sh_audio, ao->buffer, playsize);
|
||||
}
|
||||
ao->buffer.len = 0;
|
||||
mp_audio_buffer_clear(ao->buffer);
|
||||
if (res < 0)
|
||||
return res;
|
||||
}
|
||||
if (hrseek)
|
||||
// Don't add silence in audio-only case even if position is too late
|
||||
return 0;
|
||||
int fillbyte = 0;
|
||||
if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_US)
|
||||
fillbyte = 0x80;
|
||||
if (bytes >= playsize) {
|
||||
if (samples >= playsize) {
|
||||
/* This case could fall back to the one below with
|
||||
* bytes = playsize, but then silence would keep accumulating
|
||||
* in a_out_buffer if the AO accepts less data than it asks for
|
||||
* samples = playsize, but then silence would keep accumulating
|
||||
* in ao->buffer if the AO accepts less data than it asks for
|
||||
* in playsize. */
|
||||
char *p = malloc(playsize);
|
||||
memset(p, fillbyte, playsize);
|
||||
write_to_ao(mpctx, p, playsize, 0, written_pts - bytes / bps);
|
||||
free(p);
|
||||
write_silence_to_ao(mpctx, playsize, 0,
|
||||
written_pts - samples / real_samplerate);
|
||||
return ASYNC_PLAY_DONE;
|
||||
}
|
||||
mpctx->syncing_audio = false;
|
||||
decode_audio_prepend_bytes(&ao->buffer, bytes, fillbyte);
|
||||
return decode_audio(sh_audio, &ao->buffer, playsize);
|
||||
mp_audio_buffer_prepend_silence(ao->buffer, samples);
|
||||
return decode_audio(sh_audio, ao->buffer, playsize);
|
||||
}
|
||||
|
||||
int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
|
@ -340,7 +351,6 @@ int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
|||
bool partial_fill = false;
|
||||
sh_audio_t * const sh_audio = mpctx->sh_audio;
|
||||
bool modifiable_audio_format = !(ao->format & AF_FORMAT_SPECIAL_MASK);
|
||||
int unitsize = ao->channels.num * af_fmt2bits(ao->format) / 8;
|
||||
|
||||
if (mpctx->paused)
|
||||
playsize = 1; // just initialize things (audio pts at least)
|
||||
|
@ -359,7 +369,7 @@ int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
|||
if (mpctx->syncing_audio || mpctx->hrseek_active)
|
||||
res = audio_start_sync(mpctx, playsize);
|
||||
else
|
||||
res = decode_audio(sh_audio, &ao->buffer, playsize);
|
||||
res = decode_audio(sh_audio, ao->buffer, playsize);
|
||||
|
||||
if (res < 0) { // EOF, error or format change
|
||||
if (res == -2) {
|
||||
|
@ -378,21 +388,19 @@ int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
|||
}
|
||||
|
||||
if (endpts != MP_NOPTS_VALUE && modifiable_audio_format) {
|
||||
double bytes = (endpts - written_audio_pts(mpctx) + mpctx->audio_delay)
|
||||
* ao->bps / opts->playback_speed;
|
||||
if (playsize > bytes) {
|
||||
playsize = MPMAX(bytes, 0);
|
||||
double samples = (endpts - written_audio_pts(mpctx) + mpctx->audio_delay)
|
||||
* ao->samplerate / opts->playback_speed;
|
||||
if (playsize > samples) {
|
||||
playsize = MPMAX(samples, 0);
|
||||
audio_eof = true;
|
||||
partial_fill = true;
|
||||
}
|
||||
}
|
||||
|
||||
assert(ao->buffer.len % unitsize == 0);
|
||||
if (playsize > ao->buffer.len) {
|
||||
if (playsize > mp_audio_buffer_samples(ao->buffer)) {
|
||||
playsize = mp_audio_buffer_samples(ao->buffer);
|
||||
partial_fill = true;
|
||||
playsize = ao->buffer.len;
|
||||
}
|
||||
playsize -= playsize % unitsize;
|
||||
if (!playsize)
|
||||
return partial_fill && audio_eof ? -2 : -partial_fill;
|
||||
|
||||
|
@ -406,14 +414,16 @@ int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
|||
}
|
||||
}
|
||||
|
||||
assert(ao->buffer_playable_size <= ao->buffer.len);
|
||||
int played = write_to_ao(mpctx, ao->buffer.start, playsize, playflags,
|
||||
written_audio_pts(mpctx));
|
||||
ao->buffer_playable_size = playsize - played;
|
||||
assert(ao->buffer_playable_samples <= mp_audio_buffer_samples(ao->buffer));
|
||||
|
||||
struct mp_audio data;
|
||||
mp_audio_buffer_peek(ao->buffer, &data);
|
||||
data.samples = MPMIN(data.samples, playsize);
|
||||
int played = write_to_ao(mpctx, &data, playflags, written_audio_pts(mpctx));
|
||||
ao->buffer_playable_samples = playsize - played;
|
||||
|
||||
if (played > 0) {
|
||||
ao->buffer.len -= played;
|
||||
memmove(ao->buffer.start, ao->buffer.start + played, ao->buffer.len);
|
||||
mp_audio_buffer_skip(ao->buffer, played);
|
||||
} 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
|
||||
|
@ -428,8 +438,8 @@ void clear_audio_output_buffers(struct MPContext *mpctx)
|
|||
{
|
||||
if (mpctx->ao) {
|
||||
ao_reset(mpctx->ao);
|
||||
mpctx->ao->buffer.len = 0;
|
||||
mpctx->ao->buffer_playable_size = 0;
|
||||
mp_audio_buffer_clear(mpctx->ao->buffer);
|
||||
mpctx->ao->buffer_playable_samples = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
#include "mpvcore/input/input.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/decode/dec_audio.h"
|
||||
#include "audio/out/ao.h"
|
||||
#include "demux/demux.h"
|
||||
|
@ -163,11 +165,13 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask)
|
|||
// 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,
|
||||
struct mp_audio data;
|
||||
mp_audio_buffer_peek(ao->buffer, &data);
|
||||
int samples = ao->buffer_playable_samples;
|
||||
assert(samples <= data.samples);
|
||||
int played = ao_play(ao, data.planes, samples,
|
||||
AOPLAY_FINAL_CHUNK);
|
||||
if (played < len)
|
||||
if (played < samples)
|
||||
MP_WARN(ao, "Audio output truncated at end.\n");
|
||||
}
|
||||
ao_uninit(ao, drain);
|
||||
|
|
Loading…
Reference in New Issue