diff options
Diffstat (limited to 'audio/out/buffer.c')
-rw-r--r-- | audio/out/buffer.c | 686 |
1 files changed, 354 insertions, 332 deletions
diff --git a/audio/out/buffer.c b/audio/out/buffer.c index 2992180854..d64e74fa92 100644 --- a/audio/out/buffer.c +++ b/audio/out/buffer.c @@ -16,10 +16,8 @@ */ #include <stddef.h> -#include <pthread.h> #include <inttypes.h> #include <math.h> -#include <unistd.h> #include <errno.h> #include <assert.h> @@ -31,49 +29,51 @@ #include "common/msg.h" #include "common/common.h" -#include "input/input.h" +#include "filters/f_async_queue.h" +#include "filters/filter_internal.h" -#include "osdep/io.h" #include "osdep/timer.h" #include "osdep/threads.h" -#include "osdep/atomic.h" -#include "misc/ring.h" struct buffer_state { // Buffer and AO - pthread_mutex_t lock; - pthread_cond_t wakeup; + mp_mutex lock; + mp_cond wakeup; - // Playthread sleep - pthread_mutex_t pt_lock; - pthread_cond_t pt_wakeup; + // AO thread sleep + mp_mutex pt_lock; + mp_cond pt_wakeup; // Access from AO driver's thread only. char *convert_buffer; - // --- protected by lock + // Immutable. + struct mp_async_queue *queue; - struct mp_ring *buffers[MP_NUM_CHANNELS]; + // --- protected by lock + struct mp_filter *filter_root; + struct mp_filter *input; // connected to queue + struct mp_aframe *pending; // last, not fully consumed output bool streaming; // AO streaming active bool playing; // logically playing audio from buffer - bool paused; // logically paused; implies playing=true - bool final_chunk; // if buffer contains EOF + bool paused; // logically paused - int64_t end_time_us; // absolute output time of last played sample - int64_t underflow; // number of samples missing since last check + int64_t end_time_ns; // absolute output time of last played sample + int64_t queued_time_ns; // duration of samples that have been queued to + // the device but have not been played. + // This field is only set in ao_set_paused(), + // and is considered as a temporary solution; + // DO NOT USE IT IN OTHER PLACES. bool initial_unblocked; // "Push" AOs only (AOs with driver->write). - bool still_playing; bool hw_paused; // driver->set_pause() was used successfully bool recover_pause; // non-hw_paused: needs to recover delay - bool draining; - bool ao_wait_low_buffer; struct mp_pcm_state prepause_state; - pthread_t thread; // thread shoveling data to AO + mp_thread thread; // thread shoveling data to AO bool thread_valid; // thread is running struct mp_aframe *temp_buf; @@ -82,15 +82,15 @@ struct buffer_state { bool terminate; // exit thread }; -static void *playthread(void *arg); +static MP_THREAD_VOID ao_thread(void *arg); -void ao_wakeup_playthread(struct ao *ao) +void ao_wakeup(struct ao *ao) { struct buffer_state *p = ao->buffer_state; - pthread_mutex_lock(&p->pt_lock); + mp_mutex_lock(&p->pt_lock); p->need_wakeup = true; - pthread_cond_broadcast(&p->pt_wakeup); - pthread_mutex_unlock(&p->pt_lock); + mp_cond_broadcast(&p->pt_wakeup); + mp_mutex_unlock(&p->pt_lock); } // called locked @@ -98,7 +98,7 @@ static void get_dev_state(struct ao *ao, struct mp_pcm_state *state) { struct buffer_state *p = ao->buffer_state; - if (p->paused) { + if (p->paused && p->playing && !ao->stream_silence) { *state = p->prepause_state; return; } @@ -111,84 +111,91 @@ static void get_dev_state(struct ao *ao, struct mp_pcm_state *state) ao->driver->get_state(ao, state); } -static int unlocked_get_space(struct ao *ao) +struct mp_async_queue *ao_get_queue(struct ao *ao) { struct buffer_state *p = ao->buffer_state; - - int space = mp_ring_available(p->buffers[0]) / ao->sstride; - - // The following code attempts to keep the total buffered audio at - // ao->buffer in order to improve latency. - if (ao->driver->write) { - struct mp_pcm_state state; - get_dev_state(ao, &state); - int align = af_format_sample_alignment(ao->format); - int device_space = MPMAX(state.free_samples, 0); - int device_buffered = ao->device_buffer - device_space; - int soft_buffered = mp_ring_size(p->buffers[0]) / ao->sstride - space; - // The extra margin helps avoiding too many wakeups if the AO is fully - // byte based and doesn't do proper chunked processing. - int min_buffer = ao->buffer + 64; - int missing = min_buffer - device_buffered - soft_buffered; - missing = (missing + align - 1) / align * align; - // But always keep the device's buffer filled as much as we can. - int device_missing = device_space - soft_buffered; - missing = MPMAX(missing, device_missing); - space = MPMIN(space, missing); - space = MPMAX(0, space); - } - - return space; + return p->queue; } -int ao_get_space(struct ao *ao) +// Special behavior with data==NULL: caller uses p->pending. +static int read_buffer(struct ao *ao, void **data, int samples, bool *eof, + bool pad_silence) { struct buffer_state *p = ao->buffer_state; - pthread_mutex_lock(&p->lock); - int space = unlocked_get_space(ao); - pthread_mutex_unlock(&p->lock); - return space; -} + int pos = 0; + *eof = false; + + while (p->playing && !p->paused && pos < samples) { + if (!p->pending || !mp_aframe_get_size(p->pending)) { + TA_FREEP(&p->pending); + struct mp_frame frame = mp_pin_out_read(p->input->pins[0]); + if (!frame.type) + break; // we can't/don't want to block + if (frame.type != MP_FRAME_AUDIO) { + if (frame.type == MP_FRAME_EOF) + *eof = true; + mp_frame_unref(&frame); + continue; + } + p->pending = frame.data; + } -int ao_play(struct ao *ao, void **data, int samples, int flags) -{ - struct buffer_state *p = ao->buffer_state; + if (!data) + break; - pthread_mutex_lock(&p->lock); + int copy = mp_aframe_get_size(p->pending); + uint8_t **fdata = mp_aframe_get_data_ro(p->pending); + copy = MPMIN(copy, samples - pos); + for (int n = 0; n < ao->num_planes; n++) { + memcpy((char *)data[n] + pos * ao->sstride, + fdata[n], copy * ao->sstride); + } + mp_aframe_skip_samples(p->pending, copy); + pos += copy; + *eof = false; + } - int write_samples = mp_ring_available(p->buffers[0]) / ao->sstride; - write_samples = MPMIN(write_samples, samples); + if (!data) { + if (!p->pending) + return 0; + void **pd = (void *)mp_aframe_get_data_rw(p->pending); + if (pd) + ao_post_process_data(ao, pd, mp_aframe_get_size(p->pending)); + return 1; + } - int write_bytes = write_samples * ao->sstride; - for (int n = 0; n < ao->num_planes; n++) { - int r = mp_ring_write(p->buffers[n], data[n], write_bytes); - assert(r == write_bytes); + // pad with silence (underflow/paused/eof) + if (pad_silence) { + for (int n = 0; n < ao->num_planes; n++) { + af_fill_silence((char *)data[n] + pos * ao->sstride, + (samples - pos) * ao->sstride, + ao->format); + } } - p->paused = false; - p->final_chunk = write_samples == samples && (flags & PLAYER_FINAL_CHUNK); + ao_post_process_data(ao, data, pos); + return pos; +} - if (p->underflow) - MP_DBG(ao, "Audio underrun by %lld samples.\n", (long long)p->underflow); - p->underflow = 0; +static int ao_read_data_locked(struct ao *ao, void **data, int samples, + int64_t out_time_ns, bool pad_silence) +{ + struct buffer_state *p = ao->buffer_state; + assert(!ao->driver->write); - if (write_samples) { - p->playing = true; - p->still_playing = true; - p->draining = false; + int pos = read_buffer(ao, data, samples, &(bool){0}, pad_silence); - if (!ao->driver->write && !p->streaming) { - p->streaming = true; - ao->driver->start(ao); - } + if (pos > 0) + p->end_time_ns = out_time_ns; + if (pos < samples && p->playing && !p->paused) { + p->playing = false; + ao->wakeup_cb(ao->wakeup_ctx); + // For ao_drain(). + mp_cond_broadcast(&p->wakeup); } - pthread_mutex_unlock(&p->lock); - - if (write_samples) - ao_wakeup_playthread(ao); - return write_samples; + return pos; } // Read the given amount of samples in the user-provided data buffer. Returns @@ -197,64 +204,47 @@ int ao_play(struct ao *ao, void **data, int samples, int flags) // rest of the user-provided buffer with silence. // This basically assumes that the audio device doesn't care about underruns. // If this is called in paused mode, it will always return 0. -// The caller should set out_time_us to the expected delay until the last sample -// reaches the speakers, in microseconds, using mp_time_us() as reference. -int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us) +// The caller should set out_time_ns to the expected delay until the last sample +// reaches the speakers, in nanoseconds, using mp_time_ns() as reference. +int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns) { struct buffer_state *p = ao->buffer_state; - int full_bytes = samples * ao->sstride; - bool need_wakeup = false; - int bytes = 0; - - pthread_mutex_lock(&p->lock); - - if (!p->playing || p->paused) - goto end; - - int buffered_bytes = mp_ring_buffered(p->buffers[0]); - bytes = MPMIN(buffered_bytes, full_bytes); - - if (full_bytes > bytes && !p->final_chunk) { - p->underflow += (full_bytes - bytes) / ao->sstride; - ao_add_events(ao, AO_EVENT_UNDERRUN); - } - if (bytes > 0) - p->end_time_us = out_time_us; + mp_mutex_lock(&p->lock); - for (int n = 0; n < ao->num_planes; n++) - mp_ring_read(p->buffers[n], data[n], bytes); + int pos = ao_read_data_locked(ao, data, samples, out_time_ns, true); - // Half of the buffer played -> request more. - if (!ao->driver->write) - need_wakeup = buffered_bytes - bytes <= mp_ring_size(p->buffers[0]) / 2; + mp_mutex_unlock(&p->lock); -end: + return pos; +} - pthread_mutex_unlock(&p->lock); +// Like ao_read_data() but does not block and also may return partial data. +// Callers have to check the return value. +int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t out_time_ns) +{ + struct buffer_state *p = ao->buffer_state; - if (need_wakeup) - ao->wakeup_cb(ao->wakeup_ctx); + if (mp_mutex_trylock(&p->lock)) + return 0; - // pad with silence (underflow/paused/eof) - for (int n = 0; n < ao->num_planes; n++) - af_fill_silence((char *)data[n] + bytes, full_bytes - bytes, ao->format); + int pos = ao_read_data_locked(ao, data, samples, out_time_ns, false); - ao_post_process_data(ao, data, samples); + mp_mutex_unlock(&p->lock); - return bytes / ao->sstride; + return pos; } // Same as ao_read_data(), but convert data according to *fmt. // fmt->src_fmt and fmt->channels must be the same as the AO parameters. int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt, - void **data, int samples, int64_t out_time_us) + void **data, int samples, int64_t out_time_ns) { struct buffer_state *p = ao->buffer_state; void *ndata[MP_NUM_CHANNELS] = {0}; if (!ao_need_conversion(fmt)) - return ao_read_data(ao, data, samples, out_time_us); + return ao_read_data(ao, data, samples, out_time_ns); assert(ao->format == fmt->src_fmt); assert(ao->channels.num == fmt->channels); @@ -274,7 +264,7 @@ int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt, for (int n = 0; n < planes; n++) ndata[n] = p->convert_buffer + n * src_plane_size; - int res = ao_read_data(ao, ndata, samples, out_time_us); + int res = ao_read_data(ao, ndata, samples, out_time_ns); ao_convert_inplace(fmt, ndata, samples); for (int n = 0; n < planes; n++) @@ -290,54 +280,54 @@ int ao_control(struct ao *ao, enum aocontrol cmd, void *arg) if (ao->driver->control) { // Only need to lock in push mode. if (ao->driver->write) - pthread_mutex_lock(&p->lock); + mp_mutex_lock(&p->lock); r = ao->driver->control(ao, cmd, arg); if (ao->driver->write) - pthread_mutex_unlock(&p->lock); + mp_mutex_unlock(&p->lock); } return r; } -static double unlocked_get_delay(struct ao *ao) +double ao_get_delay(struct ao *ao) { struct buffer_state *p = ao->buffer_state; - double driver_delay = 0; + mp_mutex_lock(&p->lock); + + double driver_delay; if (ao->driver->write) { struct mp_pcm_state state; get_dev_state(ao, &state); driver_delay = state.delay; } else { - int64_t end = p->end_time_us; - int64_t now = mp_time_us(); - driver_delay += MPMAX(0, (end - now) / (1000.0 * 1000.0)); + int64_t end = p->end_time_ns; + int64_t now = mp_time_ns(); + driver_delay = MPMAX(0, MP_TIME_NS_TO_S(end - now)); } - return mp_ring_buffered(p->buffers[0]) / (double)ao->bps + driver_delay; -} - -double ao_get_delay(struct ao *ao) -{ - struct buffer_state *p = ao->buffer_state; + int pending = mp_async_queue_get_samples(p->queue); + if (p->pending) + pending += mp_aframe_get_size(p->pending); - pthread_mutex_lock(&p->lock); - double delay = unlocked_get_delay(ao); - pthread_mutex_unlock(&p->lock); - return delay; + mp_mutex_unlock(&p->lock); + return driver_delay + pending / (double)ao->samplerate; } +// Fully stop playback; clear buffers, including queue. void ao_reset(struct ao *ao) { struct buffer_state *p = ao->buffer_state; bool wakeup = false; bool do_reset = false; - pthread_mutex_lock(&p->lock); + mp_mutex_lock(&p->lock); - for (int n = 0; n < ao->num_planes; n++) - mp_ring_reset(p->buffers[n]); + TA_FREEP(&p->pending); + mp_async_queue_reset(p->queue); + mp_filter_reset(p->filter_root); + mp_async_queue_resume_reading(p->queue); if (!ao->stream_silence && ao->driver->reset) { if (ao->driver->write) { @@ -349,35 +339,62 @@ void ao_reset(struct ao *ao) } p->streaming = false; } - p->paused = false; + wakeup = p->playing; p->playing = false; p->recover_pause = false; p->hw_paused = false; - wakeup = p->still_playing || p->draining; - p->draining = false; - p->still_playing = false; - p->end_time_us = 0; - - atomic_fetch_and(&ao->events_, ~(unsigned int)AO_EVENT_UNDERRUN); + p->end_time_ns = 0; - pthread_mutex_unlock(&p->lock); + mp_mutex_unlock(&p->lock); if (do_reset) ao->driver->reset(ao); if (wakeup) - ao_wakeup_playthread(ao); + ao_wakeup(ao); +} + +// Initiate playback. This moves from the stop/underrun state to actually +// playing (orthogonally taking the paused state into account). Plays all +// data in the queue, and goes into underrun state if no more data available. +// No-op if already running. +void ao_start(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + bool do_start = false; + + mp_mutex_lock(&p->lock); + + p->playing = true; + + if (!ao->driver->write && !p->paused && !p->streaming) { + p->streaming = true; + do_start = true; + } + + mp_mutex_unlock(&p->lock); + + // Pull AOs might call ao_read_data() so do this outside the lock. + if (do_start) + ao->driver->start(ao); + + ao_wakeup(ao); } -void ao_pause(struct ao *ao) +void ao_set_paused(struct ao *ao, bool paused, bool eof) { struct buffer_state *p = ao->buffer_state; bool wakeup = false; - bool do_reset = false; + bool do_change_state = false; - pthread_mutex_lock(&p->lock); + // If we are going to pause on eof and ao is still playing, + // be sure to drain the ao first for gapless. + if (eof && paused && ao_is_playing(ao)) + ao_drain(ao); - if (p->playing && !p->paused) { + mp_mutex_lock(&p->lock); + + if ((p->playing || !ao->driver->write) && !p->paused && paused) { if (p->streaming && !ao->stream_silence) { if (ao->driver->write) { if (!p->recover_pause) @@ -387,72 +404,65 @@ void ao_pause(struct ao *ao) } else { ao->driver->reset(ao); p->streaming = false; + p->recover_pause = !ao->untimed; } - } else if (ao->driver->reset) { + } else if (ao->driver->reset || ao->driver->set_pause) { // See ao_reset() why this is done outside of the lock. - do_reset = true; + do_change_state = true; p->streaming = false; } } - p->paused = true; wakeup = true; - } - - pthread_mutex_unlock(&p->lock); - - if (do_reset) - ao->driver->reset(ao); - - if (wakeup) - ao_wakeup_playthread(ao); -} - -void ao_resume(struct ao *ao) -{ - struct buffer_state *p = ao->buffer_state; - bool wakeup = false; - - pthread_mutex_lock(&p->lock); - - if (p->playing && p->paused) { + } else if (p->playing && p->paused && !paused) { if (ao->driver->write) { - if (p->streaming && p->hw_paused) { + if (p->hw_paused) ao->driver->set_pause(ao, false); - } else { - p->recover_pause = true; - } p->hw_paused = false; } else { if (!p->streaming) - ao->driver->start(ao); + do_change_state = true; p->streaming = true; } - p->paused = false; wakeup = true; } + p->paused = paused; - pthread_mutex_unlock(&p->lock); + mp_mutex_unlock(&p->lock); + + if (do_change_state) { + if (ao->driver->set_pause) { + if (paused) { + ao->driver->set_pause(ao, true); + p->queued_time_ns = p->end_time_ns - mp_time_ns(); + } else { + p->end_time_ns = p->queued_time_ns + mp_time_ns(); + ao->driver->set_pause(ao, false); + } + } else { + if (paused) + ao->driver->reset(ao); + else + ao->driver->start(ao); + } + } if (wakeup) - ao_wakeup_playthread(ao); + ao_wakeup(ao); } -bool ao_eof_reached(struct ao *ao) +// Whether audio is playing. This means that there is still data in the buffers, +// and ao_start() was called. This returns true even if playback was logically +// paused. On false, EOF was reached, or an underrun happened, or ao_reset() +// was called. +bool ao_is_playing(struct ao *ao) { struct buffer_state *p = ao->buffer_state; - pthread_mutex_lock(&p->lock); - bool eof = !p->playing; - if (ao->driver->write) { - eof |= !p->still_playing; - } else { - // For simplicity, ignore the latency. Otherwise, we would have to run - // an extra thread to time it. - eof |= mp_ring_buffered(p->buffers[0]) == 0; - } - pthread_mutex_unlock(&p->lock); + mp_mutex_lock(&p->lock); + bool playing = p->playing; + mp_mutex_unlock(&p->lock); - return eof; + return playing; } // Block until the current audio buffer has played completely. @@ -460,69 +470,71 @@ void ao_drain(struct ao *ao) { struct buffer_state *p = ao->buffer_state; - pthread_mutex_lock(&p->lock); - p->final_chunk = true; - while (!p->paused && p->still_playing && p->streaming) { - if (ao->driver->write) { - if (p->draining) { - // Wait for EOF signal from AO. - pthread_cond_wait(&p->wakeup, &p->lock); - } else { - p->draining = true; - MP_VERBOSE(ao, "waiting for draining...\n"); - pthread_mutex_unlock(&p->lock); - ao_wakeup_playthread(ao); - pthread_mutex_lock(&p->lock); - } - } else { - double left = mp_ring_buffered(p->buffers[0]) / (double)ao->bps * 1e6; - pthread_mutex_unlock(&p->lock); - - if (left > 0) { - // Wait for lower bound. - mp_sleep_us(left); - // And then poll for actual end. No other way. - // Limit to arbitrary ~250ms max. waiting for robustness. - int64_t max = mp_time_us() + 250000; - while (mp_time_us() < max && !ao_eof_reached(ao)) - mp_sleep_us(1); - } else { - p->still_playing = false; - } + mp_mutex_lock(&p->lock); + while (!p->paused && p->playing) { + mp_mutex_unlock(&p->lock); + double delay = ao_get_delay(ao); + mp_mutex_lock(&p->lock); + + // Limit to buffer + arbitrary ~250ms max. waiting for robustness. + delay += mp_async_queue_get_samples(p->queue) / (double)ao->samplerate; + + // Wait for EOF signal from AO. + if (mp_cond_timedwait(&p->wakeup, &p->lock, + MP_TIME_S_TO_NS(MPMAX(delay, 0) + 0.25))) + { + MP_VERBOSE(ao, "drain timeout\n"); + break; + } - pthread_mutex_lock(&p->lock); + if (!p->playing && mp_async_queue_get_samples(p->queue)) { + MP_WARN(ao, "underrun during draining\n"); + mp_mutex_unlock(&p->lock); + ao_start(ao); + mp_mutex_lock(&p->lock); } } - pthread_mutex_unlock(&p->lock); + mp_mutex_unlock(&p->lock); ao_reset(ao); } +static void wakeup_filters(void *ctx) +{ + struct ao *ao = ctx; + ao_wakeup(ao); +} + void ao_uninit(struct ao *ao) { struct buffer_state *p = ao->buffer_state; - if (p->thread_valid) { - pthread_mutex_lock(&p->pt_lock); + if (p && p->thread_valid) { + mp_mutex_lock(&p->pt_lock); p->terminate = true; - pthread_cond_broadcast(&p->pt_wakeup); - pthread_mutex_unlock(&p->pt_lock); + mp_cond_broadcast(&p->pt_wakeup); + mp_mutex_unlock(&p->pt_lock); - pthread_join(p->thread, NULL); + mp_thread_join(p->thread); p->thread_valid = false; } if (ao->driver_initialized) ao->driver->uninit(ao); - talloc_free(p->convert_buffer); - talloc_free(p->temp_buf); + if (p) { + talloc_free(p->filter_root); + talloc_free(p->queue); + talloc_free(p->pending); + talloc_free(p->convert_buffer); + talloc_free(p->temp_buf); - pthread_cond_destroy(&p->wakeup); - pthread_mutex_destroy(&p->lock); + mp_cond_destroy(&p->wakeup); + mp_mutex_destroy(&p->lock); - pthread_cond_destroy(&p->pt_wakeup); - pthread_mutex_destroy(&p->pt_lock); + mp_cond_destroy(&p->pt_wakeup); + mp_mutex_destroy(&p->pt_lock); + } talloc_free(ao); } @@ -542,18 +554,30 @@ bool init_buffer_post(struct ao *ao) assert(ao->driver->get_state); } - for (int n = 0; n < ao->num_planes; n++) - p->buffers[n] = mp_ring_new(ao, ao->buffer * ao->sstride); + mp_mutex_init(&p->lock); + mp_cond_init(&p->wakeup); + + mp_mutex_init(&p->pt_lock); + mp_cond_init(&p->pt_wakeup); - mpthread_mutex_init_recursive(&p->lock); - pthread_cond_init(&p->wakeup, NULL); + p->queue = mp_async_queue_create(); + p->filter_root = mp_filter_create_root(ao->global); + p->input = mp_async_queue_create_filter(p->filter_root, MP_PIN_OUT, p->queue); - pthread_mutex_init(&p->pt_lock, NULL); - pthread_cond_init(&p->pt_wakeup, NULL); + mp_async_queue_resume_reading(p->queue); + + struct mp_async_queue_config cfg = { + .sample_unit = AQUEUE_UNIT_SAMPLES, + .max_samples = ao->buffer, + .max_bytes = INT64_MAX, + }; + mp_async_queue_set_config(p->queue, cfg); if (ao->driver->write) { + mp_filter_graph_set_wakeup_cb(p->filter_root, wakeup_filters, ao); + p->thread_valid = true; - if (pthread_create(&p->thread, NULL, playthread, ao)) { + if (mp_thread_create(&p->thread, ao_thread, ao)) { p->thread_valid = false; return false; } @@ -564,6 +588,11 @@ bool init_buffer_post(struct ao *ao) } } + if (ao->stream_silence) { + MP_WARN(ao, "The --audio-stream-silence option is set. This will break " + "certain player behavior.\n"); + } + return true; } @@ -590,141 +619,134 @@ static bool realloc_buf(struct ao *ao, int samples) } // called locked -static void ao_play_data(struct ao *ao) +static bool ao_play_data(struct ao *ao) { struct buffer_state *p = ao->buffer_state; + + if ((!p->playing || p->paused) && !ao->stream_silence) + return false; + struct mp_pcm_state state; get_dev_state(ao, &state); - if (p->streaming && !state.playing && !ao->untimed) { - if (p->draining) { - MP_VERBOSE(ao, "underrun signaled for audio end\n"); - p->still_playing = false; - pthread_cond_broadcast(&p->wakeup); - } else { - ao_add_events(ao, AO_EVENT_UNDERRUN); + if (p->streaming && !state.playing && !ao->untimed) + goto eof; + + void **planes = NULL; + int space = state.free_samples; + if (!space) + return false; + assert(space >= 0); + + int samples = 0; + bool got_eof = false; + if (ao->driver->write_frames) { + TA_FREEP(&p->pending); + samples = read_buffer(ao, NULL, 1, &got_eof, false); + planes = (void **)&p->pending; + } else { + if (!realloc_buf(ao, space)) { + MP_ERR(ao, "Failed to allocate buffer.\n"); + return false; } + planes = (void **)mp_aframe_get_data_rw(p->temp_buf); + assert(planes); - p->streaming = false; - } + if (p->recover_pause) { + samples = MPCLAMP(p->prepause_state.delay * ao->samplerate, 0, space); + p->recover_pause = false; + mp_aframe_set_silence(p->temp_buf, 0, space); + } - // Round free space to period sizes to reduce number of write() calls. - int space = state.free_samples / ao->period_size * ao->period_size; - bool play_silence = p->paused || (ao->stream_silence && !p->still_playing); - space = MPMAX(space, 0); - if (!realloc_buf(ao, space)) { - MP_ERR(ao, "Failed to allocate buffer.\n"); - return; - } - void **planes = (void **)mp_aframe_get_data_rw(p->temp_buf); - assert(planes); - int samples = mp_ring_buffered(p->buffers[0]) / ao->sstride; - if (samples > space) - samples = space; - if (play_silence) - samples = space; - if (p->recover_pause) { - samples = MPCLAMP(p->prepause_state.delay * ao->samplerate, 0, space); - p->recover_pause = false; - mp_aframe_set_silence(p->temp_buf, 0, space); - } else { - samples = ao_read_data(ao, planes, samples, 0); + if (!samples) { + samples = read_buffer(ao, planes, space, &got_eof, true); + if (p->paused || (ao->stream_silence && !p->playing)) + samples = space; // read_buffer() sets remainder to silent + } } - if (play_silence) - samples = space; // ao_read_data() sets remainder to silent - bool is_eof = p->final_chunk && samples < space; - bool ok = true; - int written = 0; if (samples) { - p->draining |= is_eof; MP_STATS(ao, "start ao fill"); - ok = ao->driver->write(ao, planes, samples); + if (!ao->driver->write(ao, planes, samples)) + MP_ERR(ao, "Error writing audio to device.\n"); MP_STATS(ao, "end ao fill"); - } - if (!ok) - MP_ERR(ao, "Error writing audio to device.\n"); - - if (samples > 0 && ok) { - written = samples; if (!p->streaming) { MP_VERBOSE(ao, "starting AO\n"); ao->driver->start(ao); p->streaming = true; + state.playing = true; } - p->still_playing = !play_silence; } - if (p->draining && p->still_playing && ao->untimed) { - p->still_playing = false; - pthread_cond_broadcast(&p->wakeup); - } + MP_TRACE(ao, "in=%d space=%d(%d) pl=%d, eof=%d\n", + samples, space, state.free_samples, p->playing, got_eof); - // Wait until space becomes available. Also wait if we actually wrote data, - // so the AO wakes us up properly if it needs more data. - p->ao_wait_low_buffer = space == 0 || written > 0 || p->draining; - - // Request more data if we're below some random buffer level. - int needed = unlocked_get_space(ao); - bool more = needed >= ao->device_buffer / 4 && !p->final_chunk; - if (more) - ao->wakeup_cb(ao->wakeup_ctx); // request more data - MP_TRACE(ao, "in=%d eof=%d space=%d r=%d wa/pl/dr=%d/%d/%d needed=%d more=%d\n", - samples, is_eof, space, written, p->ao_wait_low_buffer, - p->still_playing, p->draining, needed, more); + if (got_eof) + goto eof; + + return samples > 0 && (samples < space || ao->untimed); + +eof: + MP_VERBOSE(ao, "audio end or underrun\n"); + // Normal AOs signal EOF on underrun, untimed AOs never signal underruns. + if (ao->untimed || !state.playing || ao->stream_silence) { + p->streaming = state.playing && !ao->untimed; + p->playing = false; + } + ao->wakeup_cb(ao->wakeup_ctx); + // For ao_drain(). + mp_cond_broadcast(&p->wakeup); + return true; } -static void *playthread(void *arg) +static MP_THREAD_VOID ao_thread(void *arg) { struct ao *ao = arg; struct buffer_state *p = ao->buffer_state; - mpthread_set_name("ao"); + mp_thread_set_name("ao"); while (1) { - pthread_mutex_lock(&p->lock); + mp_mutex_lock(&p->lock); - bool blocked = ao->driver->initially_blocked && !p->initial_unblocked; - bool playing = !p->paused && (p->playing || ao->stream_silence); - if (playing && !blocked) - ao_play_data(ao); + bool retry = false; + if (!ao->driver->initially_blocked || p->initial_unblocked) + retry = ao_play_data(ao); // Wait until the device wants us to write more data to it. // Fallback to guessing. - double timeout = INFINITY; - if (p->ao_wait_low_buffer) { + int64_t timeout = INT64_MAX; + if (p->streaming && !retry && (!p->paused || ao->stream_silence)) { // Wake up again if half of the audio buffer has been played. // Since audio could play at a faster or slower pace, wake up twice // as often as ideally needed. - timeout = ao->device_buffer / (double)ao->samplerate * 0.25; - p->ao_wait_low_buffer = false; + timeout = MP_TIME_S_TO_NS(ao->device_buffer / (double)ao->samplerate * 0.25); } - pthread_mutex_unlock(&p->lock); + mp_mutex_unlock(&p->lock); - pthread_mutex_lock(&p->pt_lock); + mp_mutex_lock(&p->pt_lock); if (p->terminate) { - pthread_mutex_unlock(&p->pt_lock); + mp_mutex_unlock(&p->pt_lock); break; } - if (!p->need_wakeup) { + if (!p->need_wakeup && !retry) { MP_STATS(ao, "start audio wait"); - struct timespec ts = mp_rel_time_to_timespec(timeout); - pthread_cond_timedwait(&p->pt_wakeup, &p->pt_lock, &ts); + mp_cond_timedwait(&p->pt_wakeup, &p->pt_lock, timeout); MP_STATS(ao, "end audio wait"); } p->need_wakeup = false; - pthread_mutex_unlock(&p->pt_lock); + mp_mutex_unlock(&p->pt_lock); } - return NULL; + MP_THREAD_RETURN(); } void ao_unblock(struct ao *ao) { if (ao->driver->write) { struct buffer_state *p = ao->buffer_state; - pthread_mutex_lock(&p->lock); + mp_mutex_lock(&p->lock); p->initial_unblocked = true; - pthread_mutex_unlock(&p->lock); - ao_wakeup_playthread(ao); + mp_mutex_unlock(&p->lock); + ao_wakeup(ao); } } |