Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */
#include <stddef.h>#include <pthread.h>#include <inttypes.h>#include <unistd.h>#include <errno.h>#include <assert.h>
#include "osdep/io.h"
#include "ao.h"#include "internal.h"#include "audio/format.h"
#include "common/msg.h"#include "common/common.h"
#include "input/input.h"
#include "osdep/threads.h"#include "osdep/timer.h"#include "osdep/atomic.h"
#include "audio/audio_buffer.h"
struct ao_push_state { pthread_t thread; pthread_mutex_t lock; pthread_cond_t wakeup;
// --- protected by lock
struct mp_audio_buffer *buffer;
uint8_t *silence[MP_NUM_CHANNELS]; int silence_samples;
bool terminate; bool wait_on_ao; bool still_playing; bool need_wakeup; bool paused; bool initial_unblocked;
// Whether the current buffer contains the complete audio. bool final_chunk; double expected_end_time;
int wakeup_pipe[2];};
// lock must be heldstatic void wakeup_playthread(struct ao *ao){ struct ao_push_state *p = ao->api_priv; if (ao->driver->wakeup) ao->driver->wakeup(ao); p->need_wakeup = true; pthread_cond_signal(&p->wakeup);}
static int control(struct ao *ao, enum aocontrol cmd, void *arg){ int r = CONTROL_UNKNOWN; if (ao->driver->control) { struct ao_push_state *p = ao->api_priv; pthread_mutex_lock(&p->lock); r = ao->driver->control(ao, cmd, arg); pthread_mutex_unlock(&p->lock); } return r;}
static double unlocked_get_delay(struct ao *ao){ struct ao_push_state *p = ao->api_priv; double driver_delay = 0; if (ao->driver->get_delay) driver_delay = ao->driver->get_delay(ao); return driver_delay + mp_audio_buffer_seconds(p->buffer);}
static double get_delay(struct ao *ao){ struct ao_push_state *p = ao->api_priv; pthread_mutex_lock(&p->lock); double delay = unlocked_get_delay(ao); pthread_mutex_unlock(&p->lock); return delay;}
static void reset(struct ao *ao){ struct ao_push_state *p = ao->api_priv; pthread_mutex_lock(&p->lock); if (ao->driver->reset) ao->driver->reset(ao); mp_audio_buffer_clear(p->buffer); p->paused = false; if (p->still_playing) wakeup_playthread(ao); p->still_playing = false; pthread_mutex_unlock(&p->lock);}
static void audio_pause(struct ao *ao){ struct ao_push_state *p = ao->api_priv; pthread_mutex_lock(&p->lock); if (ao->driver->pause) ao->driver->pause(ao); p->paused = true; wakeup_playthread(ao); pthread_mutex_unlock(&p->lock);}
static void resume(struct ao *ao){ struct ao_push_state *p = ao->api_priv; pthread_mutex_lock(&p->lock); if (ao->driver->resume) ao->driver->resume(ao); p->paused = false; p->expected_end_time = 0; wakeup_playthread(ao); pthread_mutex_unlock(&p->lock);}
static void drain(struct ao *ao){ struct ao_push_state *p = ao->api_priv; double maxbuffer = ao->buffer / (double)ao->samplerate + 1;
MP_VERBOSE(ao, "draining...\n");
pthread_mutex_lock(&p->lock); if (p->paused) goto done;
p->final_chunk = true; wakeup_playthread(ao);
// Wait until everything is done. Since the audio API (especially ALSA) // can't be trusted to do this right, and we're hard-blocking here, apply // an upper bound timeout. struct timespec until = mp_rel_time_to_timespec(maxbuffer); while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0) { if (pthread_cond_timedwait(&p->wakeup, &p->lock, &until)) { MP_WARN(ao, "Draining is taking too long, aborting.\n"); goto done; } }
if (ao->driver->drain) { ao->driver->drain(ao); } else { double time = unlocked_get_delay(ao); mp_sleep_us(MPMIN(time, maxbuffer) * 1e6); }
done: pthread_mutex_unlock(&p->lock);
reset(ao);}
static int unlocked_get_space(struct ao *ao){ struct ao_push_state *p = ao->api_priv; int space = mp_audio_buffer_get_write_available(p->buffer); if (ao->driver->get_space) { int align = af_format_sample_alignment(ao->format); // The following code attempts to keep the total buffered audio to // ao->buffer in order to improve latency. int device_space = ao->driver->get_space(ao); int device_buffered = ao->device_buffer - device_space; int soft_buffered = mp_audio_buffer_samples(p->buffer); // 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;}
static int get_space(struct ao *ao){ struct ao_push_state *p = ao->api_priv; pthread_mutex_lock(&p->lock); int space = unlocked_get_space(ao); pthread_mutex_unlock(&p->lock); return space;}
static bool get_eof(struct ao *ao){ struct ao_push_state *p = ao->api_priv; pthread_mutex_lock(&p->lock); bool eof = !p->still_playing; pthread_mutex_unlock(&p->lock); return eof;}
static int play(struct ao *ao, void **data, int samples, int flags){ struct ao_push_state *p = ao->api_priv;
pthread_mutex_lock(&p->lock);
int write_samples = mp_audio_buffer_get_write_available(p->buffer); write_samples = MPMIN(write_samples, samples);
MP_TRACE(ao, "samples=%d flags=%d r=%d\n", samples, flags, write_samples);
if (write_samples < samples) flags = flags & ~AOPLAY_FINAL_CHUNK; bool is_final = flags & AOPLAY_FINAL_CHUNK;
mp_audio_buffer_append(p->buffer, data, samples);
bool got_data = write_samples > 0 || p->paused || p->final_chunk != is_final;
p->final_chunk = is_final; p->paused = false; if (got_data) { p->still_playing = true; p->expected_end_time = 0;
// If we don't have new data, the decoder thread basically promises it // will send new data as soon as it's available. wakeup_playthread(ao); } pthread_mutex_unlock(&p->lock); return write_samples;}
static bool realloc_silence(struct ao *ao, int samples){ struct ao_push_state *p = ao->api_priv;
if (samples <= 0 || !af_fmt_is_pcm(ao->format)) return false;
if (samples > p->silence_samples) { talloc_free(p->silence[0]);
int bytes = af_fmt_to_bytes(ao->format) * samples * ao->channels.num; p->silence[0] = talloc_size(p, bytes); for (int n = 1; n < MP_NUM_CHANNELS; n++) p->silence[n] = p->silence[0]; p->silence_samples = samples;
af_fill_silence(p->silence[0], bytes, ao->format); }
return true;}
// called lockedstatic void ao_play_data(struct ao *ao){ struct ao_push_state *p = ao->api_priv; 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"); uint8_t **planes; int samples; if (play_silence) { planes = p->silence; samples = realloc_silence(ao, space) ? space : 0; } else { mp_audio_buffer_peek(p->buffer, &planes, &samples); } int max = samples; if (samples > space) samples = space; int flags = 0; if (p->final_chunk && samples == max) { flags |= AOPLAY_FINAL_CHUNK; } else { samples = samples / ao->period_size * ao->period_size; } MP_STATS(ao, "start ao fill"); ao_post_process_data(ao, (void **)planes, samples); int r = 0; if (samples) r = ao->driver->play(ao, (void **)planes, samples, flags); MP_STATS(ao, "end ao fill"); if (r > samples) { MP_ERR(ao, "Audio device returned nonsense value.\n"); r = samples; } else if (r < 0) { MP_ERR(ao, "Error writing audio to device.\n"); } else if (r != samples) { MP_ERR(ao, "Audio device returned broken buffer state (sent %d samples, " "got %d samples, %d period%s)!\n", 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. bool stuck_eof = r <= 0 && space >= max && samples > 0; if ((flags & AOPLAY_FINAL_CHUNK) && stuck_eof) { MP_ERR(ao, "Audio output driver seems to ignore AOPLAY_FINAL_CHUNK.\n"); r = max; } if (!play_silence) mp_audio_buffer_skip(p->buffer, r); if (r > 0) p->expected_end_time = 0; // Nothing written, but more input data than space - this must mean the // AO's get_space() doesn't do period alignment correctly. bool stuck = r == 0 && max >= space && space > 0; if (stuck) MP_ERR(ao, "Audio output is reporting incorrect buffer status.\n"); // 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->wait_on_ao = space == 0 || r > 0 || stuck; p->still_playing |= r > 0 && !play_silence; // If we just filled the AO completely (r == space), don't refill for a // while. Prevents wakeup feedback with byte-granular AOs. int needed = unlocked_get_space(ao); bool more = needed >= (r == space ? ao->device_buffer / 4 : 1) && !stuck && !(flags & AOPLAY_FINAL_CHUNK); if (more) ao->wakeup_cb(ao->wakeup_ctx); // request more data MP_TRACE(ao, "in=%d flags=%d space=%d r=%d wa/pl=%d/%d needed=%d more=%d\n", max, flags, space, r, p->wait_on_ao, p->still_playing, needed, more);}
static void *playthread(void *arg){ struct ao *ao = arg; struct ao_push_state *p = ao->api_priv; mpthread_set_name("ao"); pthread_mutex_lock(&p->lock); while (!p->terminate) { bool blocked = ao->driver->initially_blocked && !p->initial_unblocked; bool playing = (!p->paused || ao->stream_silence) && !blocked; if (playing) ao_play_data(ao);
if (!p->need_wakeup) { MP_STATS(ao, "start audio wait"); if (!p->wait_on_ao || !playing) { // Avoid busy waiting, because the audio API will still report // that it needs new data, even if we're not ready yet, or if // get_space() decides that the amount of audio buffered in the // device is enough, and p->buffer can be empty. // The most important part is that the decoder is woken up, so // that the decoder will wake up us in turn. MP_TRACE(ao, "buffer inactive.\n");
bool was_playing = p->still_playing; double timeout = -1; if (p->still_playing && !p->paused && p->final_chunk && !mp_audio_buffer_samples(p->buffer)) { double now = mp_time_sec(); if (!p->expected_end_time) p->expected_end_time = now + unlocked_get_delay(ao); if (p->expected_end_time < now) { p->still_playing = false; } else { timeout = p->expected_end_time - now; } }
if (was_playing && !p->still_playing) ao->wakeup_cb(ao->wakeup_ctx); pthread_cond_signal(&p->wakeup); // for draining
if (p->still_playing && timeout > 0) { struct timespec ts = mp_rel_time_to_timespec(timeout); pthread_cond_timedwait(&p->wakeup, &p->lock, &ts); } else { pthread_cond_wait(&p->wakeup, &p->lock); } } else { // Wait until the device wants us to write more data to it. if (!ao->driver->wait || ao->driver->wait(ao, &p->lock) < 0) { // Fallback to guessing. double timeout = 0; if (ao->driver->get_delay) timeout = ao->driver->get_delay(ao); timeout *= 0.25; // wake up if 25% played if (!p->need_wakeup) { struct timespec ts = mp_rel_time_to_timespec(timeout); pthread_cond_timedwait(&p->wakeup, &p->lock, &ts); } } } MP_STATS(ao, "end audio wait"); } p->need_wakeup = false; } pthread_mutex_unlock(&p->lock); return NULL;}
static void destroy_no_thread(struct ao *ao){ struct ao_push_state *p = ao->api_priv;
ao->driver->uninit(ao);
for (int n = 0; n < 2; n++) { int h = p->wakeup_pipe[n]; if (h >= 0) close(h); }
pthread_cond_destroy(&p->wakeup); pthread_mutex_destroy(&p->lock);}
static void uninit(struct ao *ao){ struct ao_push_state *p = ao->api_priv;
pthread_mutex_lock(&p->lock); p->terminate = true; wakeup_playthread(ao); pthread_mutex_unlock(&p->lock);
pthread_join(p->thread, NULL);
destroy_no_thread(ao);}
static int init(struct ao *ao){ struct ao_push_state *p = ao->api_priv;
pthread_mutex_init(&p->lock, NULL); pthread_cond_init(&p->wakeup, NULL); mp_make_wakeup_pipe(p->wakeup_pipe);
if (ao->device_buffer <= 0) { MP_FATAL(ao, "Couldn't probe device buffer size.\n"); goto err; }
p->buffer = mp_audio_buffer_create(ao); mp_audio_buffer_reinit_fmt(p->buffer, ao->format, &ao->channels, ao->samplerate); mp_audio_buffer_preallocate_min(p->buffer, ao->buffer); if (pthread_create(&p->thread, NULL, playthread, ao)) goto err; return 0;err: destroy_no_thread(ao); return -1;}
const struct ao_driver ao_api_push = { .init = init, .control = control, .uninit = uninit, .reset = reset, .get_space = get_space, .play = play, .get_delay = get_delay, .pause = audio_pause, .resume = resume, .drain = drain, .get_eof = get_eof, .priv_size = sizeof(struct ao_push_state),};
// Must be called locked.int ao_play_silence(struct ao *ao, int samples){ assert(ao->api == &ao_api_push);
struct ao_push_state *p = ao->api_priv;
if (!realloc_silence(ao, samples) || !ao->driver->play) return 0;
return ao->driver->play(ao, (void **)p->silence, samples, 0);}
void ao_unblock(struct ao *ao){ if (ao->api == &ao_api_push) { struct ao_push_state *p = ao->api_priv; pthread_mutex_lock(&p->lock); p->need_wakeup = true; p->initial_unblocked = true; wakeup_playthread(ao); pthread_cond_signal(&p->wakeup); pthread_mutex_unlock(&p->lock); }}
#ifndef __MINGW32__
#include <poll.h>
#define MAX_POLL_FDS 20
// Call poll() for the given fds. This will extend the given fds with the// wakeup pipe, so ao_wakeup_poll() will basically interrupt this function.// Unlocks the lock temporarily.// Returns <0 on error, 0 on success, 1 if the caller should return immediately.int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds, pthread_mutex_t *lock){ struct ao_push_state *p = ao->api_priv; assert(ao->api == &ao_api_push); assert(&p->lock == lock);
if (num_fds >= MAX_POLL_FDS || p->wakeup_pipe[0] < 0) return -1;
struct pollfd p_fds[MAX_POLL_FDS]; memcpy(p_fds, fds, num_fds * sizeof(p_fds[0])); p_fds[num_fds] = (struct pollfd){ .fd = p->wakeup_pipe[0], .events = POLLIN, };
pthread_mutex_unlock(&p->lock); int r = poll(p_fds, num_fds + 1, -1); r = r < 0 ? -errno : 0; pthread_mutex_lock(&p->lock);
memcpy(fds, p_fds, num_fds * sizeof(fds[0])); bool wakeup = false; if (p_fds[num_fds].revents & POLLIN) { wakeup = true; // might "drown" some wakeups, but that's ok for our use-case mp_flush_wakeup_pipe(p->wakeup_pipe[0]); } return (r >= 0 || r == -EINTR) ? wakeup : -1;}
void ao_wakeup_poll(struct ao *ao){ assert(ao->api == &ao_api_push); struct ao_push_state *p = ao->api_priv;
(void)write(p->wakeup_pipe[1], &(char){0}, 1);}
#endif