diff --git a/audio/audio_driver.c b/audio/audio_driver.c index b7eb32aa69..161b5989b4 100644 --- a/audio/audio_driver.c +++ b/audio/audio_driver.c @@ -125,6 +125,9 @@ static const audio_driver_t *audio_drivers[] = { #endif #ifdef SWITCH &audio_switch, +#ifdef HAVE_LIBNX + &audio_switch_thread, +#endif #endif &audio_null, NULL, diff --git a/audio/audio_driver.h b/audio/audio_driver.h index a2cdad4f41..1d67fa0768 100644 --- a/audio/audio_driver.h +++ b/audio/audio_driver.h @@ -338,6 +338,7 @@ extern audio_driver_t audio_psp; extern audio_driver_t audio_ctr_csnd; extern audio_driver_t audio_ctr_dsp; extern audio_driver_t audio_switch; +extern audio_driver_t audio_switch_thread; extern audio_driver_t audio_rwebaudio; extern audio_driver_t audio_null; diff --git a/audio/drivers/switch_audio.c b/audio/drivers/switch_audio.c index 3a06cc7398..322750bba0 100644 --- a/audio/drivers/switch_audio.c +++ b/audio/drivers/switch_audio.c @@ -30,14 +30,15 @@ static const size_t sample_buffer_size = ((max_num_samples * num_channels * size typedef struct { - audio_output_t output; - handle_t event; - audio_output_buffer_t buffers[3]; - audio_output_buffer_t *current_buffer; bool blocking; bool is_paused; uint64_t last_append; unsigned latency; + audio_output_buffer_t buffers[3]; + audio_output_buffer_t *current_buffer; + + audio_output_t output; + handle_t event; } switch_audio_t; static ssize_t switch_audio_write(void *data, const void *buf, size_t size) diff --git a/audio/drivers/switch_nx_audio.c b/audio/drivers/switch_nx_audio.c new file mode 100644 index 0000000000..9a31eb9211 --- /dev/null +++ b/audio/drivers/switch_nx_audio.c @@ -0,0 +1,284 @@ +/* RetroArch - A frontend for libretro. + * + * 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 +#include +#include +#include + +#include + +#include "../audio_driver.h" +#include "../../verbosity.h" + +#include "../../tasks/tasks_internal.h" + +typedef struct +{ + bool blocking; + bool is_paused; + uint64_t last_append; + unsigned latency; + AudioOutBuffer buffers[5]; + AudioOutBuffer *current_buffer; +} switch_audio_t; + +static bool switch_tasks_finder(retro_task_t *task, void *userdata) +{ + return task; +} +task_finder_data_t switch_tasks_finder_data = {switch_tasks_finder, NULL}; + +#define SAMPLERATE 48000 +#define CHANNELCOUNT 2 +#define FRAMERATE (1000 / 30) +#define SAMPLECOUNT SAMPLERATE / FRAMERATE +#define BYTESPERSAMPLE sizeof(uint16_t) + +static uint32_t switch_audio_data_size(void) +{ + return (SAMPLECOUNT * CHANNELCOUNT * BYTESPERSAMPLE); +} + +static size_t switch_audio_buffer_size(void *data) +{ + (void)data; + return (switch_audio_data_size() + 0xfff) & ~0xfff; +} + +static ssize_t switch_audio_write(void *data, const void *buf, size_t size) +{ + size_t to_write = size; + switch_audio_t *swa = (switch_audio_t *)data; + + if (!swa) + { + return -1; + } + + if (!swa->current_buffer) + { + uint32_t num; + if (R_FAILED(audoutGetReleasedAudioOutBuffer(&swa->current_buffer, &num))) + return -1; + + if (num < 1) + { + swa->current_buffer = NULL; + + if (swa->blocking) + { + /* No buffer, blocking... */ + + while (swa->current_buffer == NULL) + { + num = 0; + if (R_FAILED(audoutWaitPlayFinish(&swa->current_buffer, &num, U64_MAX))) + { +#if 0 + if (task_queue_find(&switch_tasks_finder_data)) + task_queue_check(); +#endif + } + } + } + else + return 0; + } + + swa->current_buffer->data_size = 0; + } + + if (to_write > switch_audio_buffer_size(NULL) - swa->current_buffer->data_size) + to_write = switch_audio_buffer_size(NULL) - swa->current_buffer->data_size; + + memcpy(((uint8_t *)swa->current_buffer->buffer) + swa->current_buffer->data_size, buf, to_write); + swa->current_buffer->data_size += to_write; + swa->current_buffer->buffer_size = switch_audio_buffer_size(NULL); + + if (swa->current_buffer->data_size > (48000 * swa->latency) / 1000) + { + Result r = audoutAppendAudioOutBuffer(swa->current_buffer); + if (R_FAILED(r)) + return -1; + swa->current_buffer = NULL; + } + + swa->last_append = svcGetSystemTick(); + + return to_write; +} + +static bool switch_audio_stop(void *data) +{ + return true; + + switch_audio_t *swa = (switch_audio_t *)data; + if (!swa) + return false; + + if (!swa->is_paused) + { + Result rc = audoutStopAudioOut(); + if (R_FAILED(rc)) + return false; + } + + swa->is_paused = true; + return true; +} + +static bool switch_audio_start(void *data, bool is_shutdown) +{ + return true; + + switch_audio_t *swa = (switch_audio_t *)data; + if (!swa) + return false; + + if (swa->is_paused) + { + Result rc = audoutStartAudioOut(); + if (R_FAILED(rc)) + return false; + } + + swa->is_paused = false; + return true; +} + +static bool switch_audio_alive(void *data) +{ + switch_audio_t *swa = (switch_audio_t *)data; + if (!swa) + return false; + return !swa->is_paused; +} + +static void switch_audio_free(void *data) +{ + switch_audio_t *swa = (switch_audio_t *)data; + + if (swa) + { + unsigned i; + if (!swa->is_paused) + audoutStopAudioOut(); + + audoutExit(); + + for (i = 0; i < 5; i++) + free(swa->buffers[i].buffer); + + free(swa); + } +} + +static bool switch_audio_use_float(void *data) +{ + (void)data; + return false; /* force INT16 */ +} + +static size_t switch_audio_write_avail(void *data) +{ + switch_audio_t *swa = (switch_audio_t *)data; + + if (!swa || !swa->current_buffer) + return 0; + + return swa->current_buffer->buffer_size; +} + +static void switch_audio_set_nonblock_state(void *data, bool state) +{ + switch_audio_t *swa = (switch_audio_t *)data; + + if (swa) + swa->blocking = !state; +} + +static void *switch_audio_init(const char *device, + unsigned rate, unsigned latency, + unsigned block_frames, + unsigned *new_rate) +{ + unsigned i; + switch_audio_t *swa = (switch_audio_t *)calloc(1, sizeof(*swa)); + if (!swa) + return NULL; + + /* Init Audio Output */ + Result rc = audoutInitialize(); + if (R_FAILED(rc)) + { + goto cleanExit; + } + + rc = audoutStartAudioOut(); + if (R_FAILED(rc)) + goto cleanExit; + + /* Create Buffers */ + for (i = 0; i < 5; i++) + { + swa->buffers[i].next = NULL; /* Unused */ + swa->buffers[i].buffer = memalign(0x1000, switch_audio_buffer_size(NULL)); + swa->buffers[i].buffer_size = switch_audio_buffer_size(NULL); + swa->buffers[i].data_size = switch_audio_data_size(); + swa->buffers[i].data_offset = 0; + + memset(swa->buffers[i].buffer, 0, switch_audio_buffer_size(NULL)); + + audoutAppendAudioOutBuffer(&swa->buffers[i]); + } + + /* Set audio rate */ + *new_rate = audoutGetSampleRate(); + + swa->is_paused = false; + swa->current_buffer = NULL; + swa->latency = latency; + swa->last_append = svcGetSystemTick(); + swa->blocking = block_frames; + + RARCH_LOG("[Audio]: Audio initialized\n"); + + return swa; + +cleanExit:; + + if (swa) + free(swa); + + RARCH_LOG("[Audio]: Something failed in Audio Init!\n"); + + return NULL; +} + +audio_driver_t audio_switch = { + switch_audio_init, + switch_audio_write, + switch_audio_stop, + switch_audio_start, + switch_audio_alive, + switch_audio_set_nonblock_state, + switch_audio_free, + switch_audio_use_float, + "switch", + NULL, /* device_list_new */ + NULL, /* device_list_free */ + switch_audio_write_avail, + switch_audio_buffer_size, /* buffer_size */ +}; diff --git a/audio/drivers/switch_nx_thread_audio.c b/audio/drivers/switch_nx_thread_audio.c new file mode 100644 index 0000000000..2b72b318de --- /dev/null +++ b/audio/drivers/switch_nx_thread_audio.c @@ -0,0 +1,355 @@ +/* RetroArch - A frontend for libretro. + * + * 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 +#include +#include +#include +#include + +#include + +#include +#include "../audio_driver.h" +#include "../../verbosity.h" + +#include "../../tasks/tasks_internal.h" + +#define THREAD_STACK_SIZE (1024 * 8) + +#define AUDIO_THREAD_CPU 2 + +#define CHANNELCOUNT 2 +#define BYTESPERSAMPLE sizeof(uint16_t) +#define SAMPLE_SIZE (CHANNELCOUNT * BYTESPERSAMPLE) + +#define AUDIO_BUFFER_COUNT 2 + +static inline void lockMutex(Mutex* mtx) +{ + mutexLock(mtx); +} + +typedef struct +{ + fifo_buffer_t* fifo; + Mutex fifoLock; + CondVar cond; + Mutex condLock; + + size_t fifoSize; + + volatile bool running; + bool nonblocking; + bool is_paused; + + AudioOutBuffer buffer[AUDIO_BUFFER_COUNT]; + Thread thread; + + unsigned latency; + uint32_t sampleRate; +} switch_thread_audio_t; + +static void mainLoop(void* data) +{ + switch_thread_audio_t* swa = (switch_thread_audio_t*)data; + + if (!swa) + return; + + RARCH_LOG("[Audio]: start mainLoop cpu %u tid %u\n", svcGetCurrentProcessorNumber(), swa->thread.handle); + + for (int i = 0; i < AUDIO_BUFFER_COUNT; i++) + { + swa->buffer[i].next = NULL; /* Unused */ + swa->buffer[i].buffer_size = swa->fifoSize; + swa->buffer[i].buffer = memalign(0x1000, swa->buffer[i].buffer_size); + swa->buffer[i].data_size = swa->buffer[i].buffer_size; + swa->buffer[i].data_offset = 0; + + memset(swa->buffer[i].buffer, 0, swa->buffer[i].buffer_size); + audoutAppendAudioOutBuffer(&swa->buffer[i]); + } + + AudioOutBuffer* released_out_buffer = NULL; + u32 released_out_count; + Result rc; + + while (swa->running) + { + if (!released_out_buffer) + { + rc = audoutWaitPlayFinish(&released_out_buffer, &released_out_count, U64_MAX); + if (R_FAILED(rc)) + { + swa->running = false; + RARCH_LOG("[Audio]: audoutGetReleasedAudioOutBuffer failed: %d\n", (int)rc); + break; + } + released_out_buffer->data_size = 0; + } + + size_t bufAvail = released_out_buffer->buffer_size - released_out_buffer->data_size; + + lockMutex(&swa->fifoLock); + + size_t avail = fifo_read_avail(swa->fifo); + size_t to_write = MIN(avail, bufAvail); + if (to_write > 0) + fifo_read(swa->fifo, ((u8*)released_out_buffer->buffer) + released_out_buffer->data_size, to_write); + + mutexUnlock(&swa->fifoLock); + condvarWakeAll(&swa->cond); + + released_out_buffer->data_size += to_write; + if (released_out_buffer->data_size >= released_out_buffer->buffer_size / 2) + { + rc = audoutAppendAudioOutBuffer(released_out_buffer); + if (R_FAILED(rc)) + { + RARCH_LOG("[Audio]: audoutAppendAudioOutBuffer failed: %d\n", (int)rc); + } + released_out_buffer = NULL; + } + else + svcSleepThread(16000000); /* 16ms */ + } +} + +static void *switch_thread_audio_init(const char *device, unsigned rate, unsigned latency, unsigned block_frames, unsigned *new_rate) +{ + (void)device; + + switch_thread_audio_t *swa = (switch_thread_audio_t *)calloc(1, sizeof(switch_thread_audio_t)); + + if (!swa) + return NULL; + + swa->running = true; + swa->nonblocking = true; + swa->is_paused = true; + swa->latency = MAX(latency, 8); + + Result rc = audoutInitialize(); + if (R_FAILED(rc)) + { + RARCH_LOG("[Audio]: audio init failed %d\n", (int)rc); + return NULL; + } + + rc = audoutStartAudioOut(); + if (R_FAILED(rc)) + { + RARCH_LOG("[Audio]: audio start init failed: %d\n", (int)rc); + return NULL; + } + + swa->sampleRate = audoutGetSampleRate(); + *new_rate = swa->sampleRate; + + mutexInit(&swa->fifoLock); + swa->fifoSize = (swa->sampleRate * SAMPLE_SIZE * swa->latency) / 1000; + swa->fifo = fifo_new(swa->fifoSize); + + condvarInit(&swa->cond, &swa->condLock); + + RARCH_LOG("[Audio]: switch_thread_audio_init device %s requested rate %hu rate %hu latency %hu block_frames %hu fifoSize %lu\n", + device, rate, swa->sampleRate, swa->latency, block_frames, swa->fifoSize); + + u32 prio; + svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); + rc = threadCreate(&swa->thread, &mainLoop, (void*)swa, THREAD_STACK_SIZE, prio + 1, AUDIO_THREAD_CPU); + + if (R_FAILED(rc)) + { + RARCH_LOG("[Audio]: thread creation failed create %u\n", swa->thread.handle); + swa->running = false; + return NULL; + } + + if (R_FAILED(threadStart(&swa->thread))) + { + RARCH_LOG("[Audio]: thread creation failed start %u\n", swa->thread.handle); + threadClose(&swa->thread); + swa->running = false; + return NULL; + } + + return swa; +} + +static bool switch_thread_audio_start(void *data, bool is_shutdown) +{ + /* RARCH_LOG("[Audio]: switch_thread_audio_start\n"); */ + switch_thread_audio_t *swa = (switch_thread_audio_t *)data; + + if (!swa) + return false; + + swa->is_paused = false; + return true; +} + +static bool switch_thread_audio_stop(void *data) +{ + switch_thread_audio_t* swa = (switch_thread_audio_t*)data; + + if (!swa) + return false; + + swa->is_paused = true; + return true; +} + +static void switch_thread_audio_free(void *data) +{ + switch_thread_audio_t *swa = (switch_thread_audio_t *)data; + + if (!swa) + return; + + if (swa->running) + { + swa->running = false; + threadWaitForExit(&swa->thread); + threadClose(&swa->thread); + } + + audoutStopAudioOut(); + audoutExit(); + + if (swa->fifo) + { + fifo_free(swa->fifo); + swa->fifo = NULL; + } + + for (int i = 0; i < AUDIO_BUFFER_COUNT; i++) + free(swa->buffer[i].buffer); + + free(swa); + swa = NULL; +} + +static ssize_t switch_thread_audio_write(void *data, const void *buf, size_t size) +{ + switch_thread_audio_t *swa = (switch_thread_audio_t *)data; + + if (!swa || !swa->running) + return 0; + + size_t avail; + size_t written; + + if (swa->nonblocking) + { + lockMutex(&swa->fifoLock); + avail = fifo_write_avail(swa->fifo); + written = MIN(avail, size); + if (written > 0) + { + fifo_write(swa->fifo, buf, written); + } + mutexUnlock(&swa->fifoLock); + } + else + { + written = 0; + while (written < size && swa->running) + { + lockMutex(&swa->fifoLock); + avail = fifo_write_avail(swa->fifo); + if (avail == 0) + { + mutexUnlock(&swa->fifoLock); + lockMutex(&swa->condLock); + if (swa->running) + condvarWait(&swa->cond); + mutexUnlock(&swa->condLock); + } + else + { + size_t write_amt = MIN(size - written, avail); + fifo_write(swa->fifo, (const char*)buf + written, write_amt); + mutexUnlock(&swa->fifoLock); + written += write_amt; + } + } + } + + return written; +} + +static bool switch_thread_audio_alive(void *data) +{ + switch_thread_audio_t *swa = (switch_thread_audio_t *)data; + + if (!swa) + return false; + + return !swa->is_paused; +} + +static void switch_thread_audio_set_nonblock_state(void *data, bool state) +{ + switch_thread_audio_t *swa = (switch_thread_audio_t *)data; + + if (swa) + swa->nonblocking = state; +} + +static bool switch_thread_audio_use_float(void *data) +{ + (void)data; + return false; +} + +static size_t switch_thread_audio_write_avail(void *data) +{ + switch_thread_audio_t* swa = (switch_thread_audio_t*)data; + + lockMutex(&swa->fifoLock); + size_t val = fifo_write_avail(swa->fifo); + mutexUnlock(&swa->fifoLock); + + return val; +} + +size_t switch_thread_audio_buffer_size(void *data) +{ + switch_thread_audio_t *swa = (switch_thread_audio_t *)data; + + if (!swa) + return 0; + + return swa->fifoSize; +} + +audio_driver_t audio_switch_thread = { + switch_thread_audio_init, + switch_thread_audio_write, + switch_thread_audio_stop, + switch_thread_audio_start, + switch_thread_audio_alive, + switch_thread_audio_set_nonblock_state, + switch_thread_audio_free, + switch_thread_audio_use_float, + "switch_thread", + NULL, /* device_list_new */ + NULL, /* device_list_free */ + switch_thread_audio_write_avail, + switch_thread_audio_buffer_size +}; + +/* vim: set ts=6 sw=6 sts=6: */