/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include "driver.h" #include "general.h" #include #include "../boolean.h" #include #include typedef struct { pa_threaded_mainloop *mainloop; pa_context *context; pa_stream *stream; size_t buffer_size; bool nonblock; bool success; bool is_paused; } pa_t; static void pulse_free(void *data) { pa_t *pa = (pa_t*)data; if (pa) { if (pa->mainloop) pa_threaded_mainloop_stop(pa->mainloop); if (pa->stream) { pa_stream_disconnect(pa->stream); pa_stream_unref(pa->stream); } if (pa->context) { pa_context_disconnect(pa->context); pa_context_unref(pa->context); } if (pa->mainloop) pa_threaded_mainloop_free(pa->mainloop); free(pa); } } static void stream_success_cb(pa_stream *s, int success, void *data) { (void)s; pa_t *pa = (pa_t*)data; pa->success = success; pa_threaded_mainloop_signal(pa->mainloop, 0); } static void context_state_cb(pa_context *c, void *data) { pa_t *pa = (pa_t*)data; switch (pa_context_get_state(c)) { case PA_CONTEXT_READY: case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: pa_threaded_mainloop_signal(pa->mainloop, 0); break; default: break; } } static void stream_state_cb(pa_stream *s, void *data) { pa_t *pa = (pa_t*)data; switch (pa_stream_get_state(s)) { case PA_STREAM_READY: case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: pa_threaded_mainloop_signal(pa->mainloop, 0); break; default: break; } } static void stream_request_cb(pa_stream *s, size_t length, void *data) { (void)length; (void)s; pa_t *pa = (pa_t*)data; pa_threaded_mainloop_signal(pa->mainloop, 0); } static void stream_latency_update_cb(pa_stream *s, void *data) { (void)s; pa_t *pa = (pa_t*)data; pa_threaded_mainloop_signal(pa->mainloop, 0); } static void underrun_update_cb(pa_stream *s, void *data) { (void)s; pa_t *pa = (pa_t*)data; RARCH_LOG("[PulseAudio]: Underrun (Buffer: %u, Writable size: %u).\n", (unsigned)pa->buffer_size, (unsigned)pa_stream_writable_size(pa->stream)); } static void buffer_attr_cb(pa_stream *s, void *data) { pa_t *pa = (pa_t*)data; const pa_buffer_attr *server_attr = pa_stream_get_buffer_attr(s); if (server_attr) pa->buffer_size = server_attr->tlength; RARCH_LOG("[PulseAudio]: Got new buffer size %u.\n", (unsigned)pa->buffer_size); } static void *pulse_init(const char *device, unsigned rate, unsigned latency) { const pa_buffer_attr *server_attr = NULL; pa_sample_spec spec; memset(&spec, 0, sizeof(spec)); pa_buffer_attr buffer_attr = {0}; pa_t *pa = (pa_t*)calloc(1, sizeof(*pa)); if (!pa) goto error; pa->mainloop = pa_threaded_mainloop_new(); if (!pa->mainloop) goto error; pa->context = pa_context_new(pa_threaded_mainloop_get_api(pa->mainloop), "RetroArch"); if (!pa->context) goto error; pa_context_set_state_callback(pa->context, context_state_cb, pa); if (pa_context_connect(pa->context, device, PA_CONTEXT_NOFLAGS, NULL) < 0) goto error; pa_threaded_mainloop_lock(pa->mainloop); if (pa_threaded_mainloop_start(pa->mainloop) < 0) goto error; pa_threaded_mainloop_wait(pa->mainloop); if (pa_context_get_state(pa->context) != PA_CONTEXT_READY) goto unlock_error; spec.format = is_little_endian() ? PA_SAMPLE_FLOAT32LE : PA_SAMPLE_FLOAT32BE; spec.channels = 2; spec.rate = rate; pa->stream = pa_stream_new(pa->context, "audio", &spec, NULL); if (!pa->stream) goto unlock_error; pa_stream_set_state_callback(pa->stream, stream_state_cb, pa); pa_stream_set_write_callback(pa->stream, stream_request_cb, pa); pa_stream_set_latency_update_callback(pa->stream, stream_latency_update_cb, pa); pa_stream_set_underflow_callback(pa->stream, underrun_update_cb, pa); pa_stream_set_buffer_attr_callback(pa->stream, buffer_attr_cb, pa); buffer_attr.maxlength = -1; buffer_attr.tlength = pa_usec_to_bytes(latency * PA_USEC_PER_MSEC, &spec); buffer_attr.prebuf = -1; buffer_attr.minreq = -1; buffer_attr.fragsize = -1; if (pa_stream_connect_playback(pa->stream, NULL, &buffer_attr, PA_STREAM_ADJUST_LATENCY, NULL, NULL) < 0) goto error; pa_threaded_mainloop_wait(pa->mainloop); if (pa_stream_get_state(pa->stream) != PA_STREAM_READY) goto unlock_error; server_attr = pa_stream_get_buffer_attr(pa->stream); if (server_attr) { pa->buffer_size = server_attr->tlength; RARCH_LOG("[PulseAudio]: Requested %u bytes buffer, got %u.\n", (unsigned)buffer_attr.tlength, (unsigned)pa->buffer_size); } else pa->buffer_size = buffer_attr.tlength; pa_threaded_mainloop_unlock(pa->mainloop); return pa; unlock_error: pa_threaded_mainloop_unlock(pa->mainloop); error: pulse_free(pa); return NULL; } static ssize_t pulse_write(void *data, const void *buf_, size_t size) { pa_t *pa = (pa_t*)data; const uint8_t *buf = (const uint8_t*)buf_; size_t written = 0; pa_threaded_mainloop_lock(pa->mainloop); while (size) { size_t writable = pa_stream_writable_size(pa->stream); writable = min(size, writable); if (writable) { pa_stream_write(pa->stream, buf, writable, NULL, 0, PA_SEEK_RELATIVE); buf += writable; size -= writable; written += writable; } else if (!pa->nonblock) pa_threaded_mainloop_wait(pa->mainloop); else break; } pa_threaded_mainloop_unlock(pa->mainloop); return written; } static bool pulse_stop(void *data) { RARCH_LOG("[PulseAudio]: Pausing.\n"); pa_t *pa = (pa_t*)data; pa->success = true; // In case of spurious wakeup. Not critical. pa_threaded_mainloop_lock(pa->mainloop); pa_stream_cork(pa->stream, true, stream_success_cb, pa); pa_threaded_mainloop_wait(pa->mainloop); bool ret = pa->success; pa_threaded_mainloop_unlock(pa->mainloop); pa->is_paused = true; return ret; } static bool pulse_alive(void *data) { pa_t *pa = (pa_t*)data; if (pa) return !pa->is_paused; return false; } static bool pulse_start(void *data) { RARCH_LOG("[PulseAudio]: Unpausing.\n"); pa_t *pa = (pa_t*)data; pa->success = true; // In case of spurious wakeup. Not critical. pa_threaded_mainloop_lock(pa->mainloop); pa_stream_cork(pa->stream, false, stream_success_cb, pa); pa_threaded_mainloop_wait(pa->mainloop); bool ret = pa->success; pa_threaded_mainloop_unlock(pa->mainloop); pa->is_paused = false; return ret; } static void pulse_set_nonblock_state(void *data, bool state) { pa_t *pa = (pa_t*)data; pa->nonblock = state; } static bool pulse_use_float(void *data) { (void)data; return true; } static size_t pulse_write_avail(void *data) { pa_t *pa = (pa_t*)data; pa_threaded_mainloop_lock(pa->mainloop); size_t length = pa_stream_writable_size(pa->stream); g_extern.audio_data.driver_buffer_size = pa->buffer_size; // Can change spuriously. pa_threaded_mainloop_unlock(pa->mainloop); return length; } static size_t pulse_buffer_size(void *data) { pa_t *pa = (pa_t*)data; return pa->buffer_size; } audio_driver_t audio_pulse = { pulse_init, pulse_write, pulse_stop, pulse_start, pulse_alive, pulse_set_nonblock_state, pulse_free, pulse_use_float, "pulse", pulse_write_avail, pulse_buffer_size, };