diff --git a/Makefile.common b/Makefile.common index f3d69a3a71..70d10c7ac9 100644 --- a/Makefile.common +++ b/Makefile.common @@ -954,7 +954,9 @@ endif ifeq ($(TARGET), retroarch_switch) ifeq ($(HAVE_LIBNX), 1) OBJ += menu/drivers_display/menu_display_switch.o \ - gfx/drivers/switch_nx_gfx.o + gfx/drivers/switch_nx_gfx.o \ + audio/drivers/switch_libnx_audren_audio.o \ + audio/drivers/switch_libnx_audren_thread_audio.o ifeq ($(HAVE_OPENGL), 1) OBJ += gfx/drivers_context/switch_ctx.o endif diff --git a/audio/drivers/switch_libnx_audren_audio.c b/audio/drivers/switch_libnx_audren_audio.c new file mode 100644 index 0000000000..4b37e87688 --- /dev/null +++ b/audio/drivers/switch_libnx_audren_audio.c @@ -0,0 +1,361 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2019 - misson20000 + * Copyright (C) 2019 - m4xw + * Copyright (C) 2019 - lifajucejo + * Copyright (C) 2019 - p-sam + * + * 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 "../../retroarch.h" +#include "../../verbosity.h" + +#define BUFFER_COUNT 5 + +static const int sample_rate = 48000; +static const int num_channels = 2; +static const uint8_t sink_channels[] = { 0, 1 }; + +static const AudioRendererConfig audio_renderer_config = +{ + .output_rate = AudioRendererOutputRate_48kHz, + .num_voices = 24, + .num_effects = 0, + .num_sinks = 1, + .num_mix_objs = 1, + .num_mix_buffers = 2, +}; + +typedef struct { + AudioDriver drv; + void* mempool; + AudioDriverWaveBuf wavebufs[BUFFER_COUNT]; + AudioDriverWaveBuf* current_wavebuf; + void* current_pool_ptr; + size_t current_size; + size_t buffer_size; + size_t samples; + Mutex update_lock; + bool nonblocking; +} libnx_audren_t; + +static void *libnx_audren_audio_init(const char *device, unsigned rate, unsigned latency, + unsigned block_frames, + unsigned *new_rate) +{ + unsigned i, j; + libnx_audren_t *aud; + Result rc; + int mpid; + size_t mempool_size; + unsigned real_latency; + + RARCH_LOG("[Audio]: Using libnx_audren driver\n"); + + aud = (libnx_audren_t*)calloc(1, sizeof(libnx_audren_t)); + + if (!aud) + { + RARCH_ERR("[Audio]: struct alloc failed\n"); + goto fail; + } + + real_latency = MAX(5, latency); + RARCH_LOG("[Audio]: real_latency is %u\n", real_latency); + + aud->nonblocking = !block_frames; + aud->buffer_size = (real_latency * sample_rate / 1000); + aud->samples = (aud->buffer_size / num_channels / sizeof(int16_t)); + aud->current_size = 0; + *new_rate = sample_rate; + + mempool_size = (aud->buffer_size * BUFFER_COUNT + (AUDREN_MEMPOOL_ALIGNMENT-1)) &~ (AUDREN_MEMPOOL_ALIGNMENT-1); + aud->mempool = memalign(AUDREN_MEMPOOL_ALIGNMENT, mempool_size); + if (!aud->mempool) + { + RARCH_ERR("[Audio]: mempool alloc failed\n"); + goto fail; + } + + rc = audrenInitialize(&audio_renderer_config); + if(R_FAILED(rc)) + { + RARCH_ERR("[Audio]: audrenInitialize: %x\n", rc); + goto fail; + } + + rc = audrvCreate(&aud->drv, &audio_renderer_config, num_channels); + if(R_FAILED(rc)) + { + RARCH_ERR("[Audio]: audrvCreate: %x\n", rc); + goto fail_init; + } + + for(i = 0; i < BUFFER_COUNT; i++) + { + aud->wavebufs[i].data_raw = aud->mempool; + aud->wavebufs[i].size = mempool_size; + aud->wavebufs[i].start_sample_offset = i * aud->samples; + aud->wavebufs[i].end_sample_offset = aud->wavebufs[i].start_sample_offset + aud->samples; + } + + aud->current_wavebuf = NULL; + + mpid = audrvMemPoolAdd(&aud->drv, aud->mempool, mempool_size); + audrvMemPoolAttach(&aud->drv, mpid); + + audrvDeviceSinkAdd(&aud->drv, AUDREN_DEFAULT_DEVICE_NAME, num_channels, sink_channels); + + rc = audrenStartAudioRenderer(); + if(R_FAILED(rc)) + { + RARCH_ERR("[Audio]: audrenStartAudioRenderer: %x\n", rc); + } + + audrvVoiceInit(&aud->drv, 0, num_channels, PcmFormat_Int16, sample_rate); + audrvVoiceSetDestinationMix(&aud->drv, 0, AUDREN_FINAL_MIX_ID); + for(i = 0; i < num_channels; i++) + { + for(j = 0; j < num_channels; j++) + { + audrvVoiceSetMixFactor(&aud->drv, 0, i == j ? 1.0f : 0.0f, i, j); + } + } + + mutexInit(&aud->update_lock); + *new_rate = sample_rate; + + return aud; + +fail_init: + audrenExit(); + +fail: + if (aud) + { + if (aud->mempool) + { + free(aud->mempool); + } + + free(aud); + } + + return NULL; +} + +static size_t libnx_audren_audio_buffer_size(void *data) +{ + libnx_audren_t *aud = (libnx_audren_t*)data; + + if (!aud) + return 0; + + return aud->buffer_size; +} + +static ssize_t libnx_audren_audio_get_free_wavebuf_idx(libnx_audren_t* aud) +{ + unsigned i; + + for (i = 0; i < BUFFER_COUNT; i++) + { + if (aud->wavebufs[i].state == AudioDriverWaveBufState_Free + || aud->wavebufs[i].state == AudioDriverWaveBufState_Done) + { + return i; + } + } + + return -1; +} + +static size_t libnx_audren_audio_append(libnx_audren_t* aud, const void *buf, size_t size) +{ + ssize_t free_idx = -1; + + if(!aud->current_wavebuf) + { + free_idx = libnx_audren_audio_get_free_wavebuf_idx(aud); + if(free_idx == -1) + return 0; + + aud->current_wavebuf = &aud->wavebufs[free_idx]; + aud->current_pool_ptr = aud->mempool + (free_idx * aud->buffer_size); + aud->current_size = 0; + } + + if(size > aud->buffer_size - aud->current_size) + { + size = aud->buffer_size - aud->current_size; + } + + void *dstbuf = aud->current_pool_ptr + aud->current_size; + memcpy(dstbuf, buf, size); + armDCacheFlush(dstbuf, size); + + aud->current_size += size; + + if(aud->current_size == aud->buffer_size) + { + audrvVoiceAddWaveBuf(&aud->drv, 0, aud->current_wavebuf); + + mutexLock(&aud->update_lock); + audrvUpdate(&aud->drv); + mutexUnlock(&aud->update_lock); + + if (!audrvVoiceIsPlaying(&aud->drv, 0)) + { + audrvVoiceStart(&aud->drv, 0); + } + + aud->current_wavebuf = NULL; + } + + return size; +} + +static ssize_t libnx_audren_audio_write(void *data, const void *buf, size_t size) +{ + libnx_audren_t *aud = (libnx_audren_t*)data; + size_t written = 0; + + if (!aud) + return -1; + + while(written < size) + { + written += libnx_audren_audio_append(aud, buf + written, size - written); + if(written != size) + { + if(aud->nonblocking) + { + break; + } + else + { + mutexLock(&aud->update_lock); + audrvUpdate(&aud->drv); + mutexUnlock(&aud->update_lock); + audrenWaitFrame(); + } + } + } + + return written; +} + +static bool libnx_audren_audio_stop(void *data) +{ + libnx_audren_t *aud = (libnx_audren_t*)data; + + if (!aud) + return false; + + audrvVoiceStop(&aud->drv, 0); + + return true; +} + +static bool libnx_audren_audio_start(void *data, bool is_shutdown) +{ + (void)is_shutdown; + libnx_audren_t *aud = (libnx_audren_t*)data; + + if (!aud) + return false; + + audrvVoiceStart(&aud->drv, 0); + + return true; +} + +static bool libnx_audren_audio_alive(void *data) +{ + libnx_audren_t *aud = (libnx_audren_t*)data; + + if (!aud) + return false; + + return true; +} + +static void libnx_audren_audio_free(void *data) +{ + libnx_audren_t *aud = (libnx_audren_t*)data; + + if (!aud) + return; + + audrvVoiceStop(&aud->drv, 0); + audrvClose(&aud->drv); + audrenExit(); + + if (aud->mempool) + { + free(aud->mempool); + } + + free(aud); +} + +static bool libnx_audren_audio_use_float(void *data) +{ + (void)data; + return false; /* force S16 */ +} + +static size_t libnx_audren_audio_write_avail(void *data) +{ + libnx_audren_t *aud = (libnx_audren_t*)data; + size_t avail; + + if (!aud || !aud->current_wavebuf) + return 0; + + avail = aud->buffer_size - aud->current_size; + + return avail; +} + +static void libnx_audren_audio_set_nonblock_state(void *data, bool state) +{ + libnx_audren_t *aud = (libnx_audren_t*)data; + + if (!aud) + return; + + aud->nonblocking = state; +} + +audio_driver_t audio_switch_libnx_audren = { + libnx_audren_audio_init, + libnx_audren_audio_write, + libnx_audren_audio_stop, + libnx_audren_audio_start, + libnx_audren_audio_alive, + libnx_audren_audio_set_nonblock_state, + libnx_audren_audio_free, + libnx_audren_audio_use_float, + "switch_audren", + NULL, /* device_list_new */ + NULL, /* device_list_free */ + libnx_audren_audio_write_avail, + libnx_audren_audio_buffer_size, +}; diff --git a/audio/drivers/switch_libnx_audren_thread_audio.c b/audio/drivers/switch_libnx_audren_thread_audio.c new file mode 100644 index 0000000000..f5f41fafee --- /dev/null +++ b/audio/drivers/switch_libnx_audren_thread_audio.c @@ -0,0 +1,438 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2019 - p-sam + * + * 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 "../../retroarch.h" +#include "../../verbosity.h" +#include "../../tasks/tasks_internal.h" + +#define BUFFER_COUNT 5 + +static const int sample_rate = 48000; +static const int num_channels = 2; +static const uint8_t sink_channels[] = { 0, 1 }; +static const size_t thread_stack_size = 1024 * 8; +static const int thread_preferred_cpu = 2; + +static const AudioRendererConfig audio_renderer_config = +{ + .output_rate = AudioRendererOutputRate_48kHz, + .num_voices = 24, + .num_effects = 0, + .num_sinks = 1, + .num_mix_objs = 1, + .num_mix_buffers = 2, +}; + +typedef struct { + AudioDriver drv; + void* mempool; + AudioDriverWaveBuf wavebufs[BUFFER_COUNT]; + size_t buffer_size; + size_t samples; + bool nonblocking; + + fifo_buffer_t* fifo; + Mutex fifo_lock; + CondVar fifo_condvar; + Mutex fifo_condlock; + Thread thread; + + volatile bool running; +} libnx_audren_thread_t; + +static void thread_job(void* data) +{ + libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data; + size_t available = 0; + size_t current_size = 0; + size_t written_tmp = 0; + AudioDriverWaveBuf* current_wavebuf = NULL; + void* current_pool_ptr = NULL; + void* dstbuf = NULL; + unsigned i; + + if (!aud) + return; + + while(aud->running) + { + if(!current_wavebuf) + { + for (i = 0; i < BUFFER_COUNT; i++) + { + if (aud->wavebufs[i].state == AudioDriverWaveBufState_Free + || aud->wavebufs[i].state == AudioDriverWaveBufState_Done) + { + current_wavebuf = &aud->wavebufs[i]; + current_pool_ptr = aud->mempool + (i * aud->buffer_size); + current_size = 0; + break; + } + } + } + + if(current_wavebuf) + { + mutexLock(&aud->fifo_lock); + available = fifo_read_avail(aud->fifo); + written_tmp = MIN(available, aud->buffer_size - current_size); + dstbuf = current_pool_ptr + current_size; + if(written_tmp > 0) + { + fifo_read(aud->fifo, dstbuf, written_tmp); + } + mutexUnlock(&aud->fifo_lock); + + if(written_tmp > 0) + { + condvarWakeAll(&aud->fifo_condvar); + + current_size += written_tmp; + armDCacheFlush(dstbuf, written_tmp); + } + + if(current_size == aud->buffer_size) + { + audrvVoiceAddWaveBuf(&aud->drv, 0, current_wavebuf); + + audrvUpdate(&aud->drv); + if (!audrvVoiceIsPlaying(&aud->drv, 0)) + { + audrvVoiceStart(&aud->drv, 0); + } + + current_wavebuf = NULL; + } + + svcSleepThread(1000UL); + } + else + { + audrvUpdate(&aud->drv); + audrenWaitFrame(); + } + } +} + +static void *libnx_audren_thread_audio_init(const char *device, unsigned rate, unsigned latency, + unsigned block_frames, + unsigned *new_rate) +{ + unsigned i, j; + libnx_audren_thread_t *aud; + Result rc; + int mpid; + size_t mempool_size; + unsigned real_latency; + uint32_t thread_priority; + + RARCH_LOG("[Audio]: Using libnx_audren_thread driver\n"); + + aud = (libnx_audren_thread_t*)calloc(1, sizeof(libnx_audren_thread_t)); + + if (!aud) + { + RARCH_ERR("[Audio]: struct alloc failed\n"); + goto fail; + } + + real_latency = MAX(latency, 5); + RARCH_LOG("[Audio]: real_latency is %u\n", real_latency); + + aud->running = true; + aud->nonblocking = !block_frames; + aud->buffer_size = (real_latency * sample_rate / 1000); + aud->samples = (aud->buffer_size / num_channels / sizeof(int16_t)); + + mempool_size = (aud->buffer_size * BUFFER_COUNT + (AUDREN_MEMPOOL_ALIGNMENT-1)) &~ (AUDREN_MEMPOOL_ALIGNMENT-1); + aud->mempool = memalign(AUDREN_MEMPOOL_ALIGNMENT, mempool_size); + if (!aud->mempool) + { + RARCH_ERR("[Audio]: mempool alloc failed\n"); + goto fail; + } + + rc = audrenInitialize(&audio_renderer_config); + if(R_FAILED(rc)) + { + RARCH_ERR("[Audio]: audrenInitialize: %x\n", rc); + goto fail; + } + + rc = audrvCreate(&aud->drv, &audio_renderer_config, num_channels); + if(R_FAILED(rc)) + { + RARCH_ERR("[Audio]: audrvCreate: %x\n", rc); + goto fail_init; + } + + for(i = 0; i < BUFFER_COUNT; i++) + { + aud->wavebufs[i].data_raw = aud->mempool; + aud->wavebufs[i].size = mempool_size; + aud->wavebufs[i].start_sample_offset = i * aud->samples; + aud->wavebufs[i].end_sample_offset = aud->wavebufs[i].start_sample_offset + aud->samples; + } + + mpid = audrvMemPoolAdd(&aud->drv, aud->mempool, mempool_size); + audrvMemPoolAttach(&aud->drv, mpid); + + audrvDeviceSinkAdd(&aud->drv, AUDREN_DEFAULT_DEVICE_NAME, num_channels, sink_channels); + + rc = audrenStartAudioRenderer(); + if(R_FAILED(rc)) + { + RARCH_ERR("[Audio]: audrenStartAudioRenderer: %x\n", rc); + } + + audrvVoiceInit(&aud->drv, 0, num_channels, PcmFormat_Int16, sample_rate); + audrvVoiceSetDestinationMix(&aud->drv, 0, AUDREN_FINAL_MIX_ID); + for(i = 0; i < num_channels; i++) + { + for(j = 0; j < num_channels; j++) + { + audrvVoiceSetMixFactor(&aud->drv, 0, i == j ? 1.0f : 0.0f, i, j); + } + } + + aud->fifo = fifo_new(aud->buffer_size); + if (!aud->fifo) + { + RARCH_ERR("[Audio]: fifo alloc failed\n"); + goto fail_drv; + } + + mutexInit(&aud->fifo_lock); + condvarInit(&aud->fifo_condvar); + mutexInit(&aud->fifo_condlock); + + svcGetThreadPriority(&thread_priority, CUR_THREAD_HANDLE); + rc = threadCreate(&aud->thread, &thread_job, (void*)aud, thread_stack_size, thread_priority - 1, thread_preferred_cpu); + if(R_FAILED(rc)) + { + RARCH_ERR("[Audio]: threadCreate: %x\n", rc); + goto fail_drv; + } + + rc = threadStart(&aud->thread); + if(R_FAILED(rc)) + { + RARCH_ERR("[Audio]: threadStart: %x\n", rc); + threadClose(&aud->thread); + goto fail_drv; + } + + *new_rate = sample_rate; + + return aud; + +fail_drv: + audrvClose(&aud->drv); + +fail_init: + audrenExit(); + +fail: + if (aud) + { + if (aud->mempool) + { + free(aud->mempool); + } + + free(aud); + } + + return NULL; +} + +static size_t libnx_audren_thread_audio_buffer_size(void *data) +{ + libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data; + + if (!aud) + return 0; + + return aud->buffer_size; +} + +static ssize_t libnx_audren_thread_audio_write(void *data, const void *buf, size_t size) +{ + libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data; + size_t available, written, written_tmp; + + if (!aud || !aud->running) + return -1; + + if(aud->nonblocking) + { + mutexLock(&aud->fifo_lock); + available = fifo_write_avail(aud->fifo); + written = MIN(available, size); + if(written > 0) + { + fifo_write(aud->fifo, buf, written); + } + mutexUnlock(&aud->fifo_lock); + } + else + { + written = 0; + while (written < size && aud->running) + { + mutexLock(&aud->fifo_lock); + available = fifo_write_avail(aud->fifo); + if(available) + { + written_tmp = MIN(size - written, available); + fifo_write(aud->fifo, (const char*)buf + written, written_tmp); + mutexUnlock(&aud->fifo_lock); + written += written_tmp; + } + else + { + mutexUnlock(&aud->fifo_lock); + mutexLock(&aud->fifo_condlock); + condvarWait(&aud->fifo_condvar, &aud->fifo_condlock); + mutexUnlock(&aud->fifo_condlock); + } + } + } + + return written; +} + +static bool libnx_audren_thread_audio_stop(void *data) +{ + libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data; + + if (!aud) + return false; + + mutexLock(&aud->fifo_lock); + audrvVoiceStop(&aud->drv, 0); + mutexUnlock(&aud->fifo_lock); + + return true; +} + +static bool libnx_audren_thread_audio_start(void *data, bool is_shutdown) +{ + (void)is_shutdown; + libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data; + + if (!aud) + return false; + + mutexLock(&aud->fifo_lock); + audrvVoiceStart(&aud->drv, 0); + mutexUnlock(&aud->fifo_lock); + + return true; +} + +static bool libnx_audren_thread_audio_alive(void *data) +{ + libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data; + + if (!aud) + return false; + + return true; +} + +static void libnx_audren_thread_audio_free(void *data) +{ + libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data; + + if (!aud) + return; + + aud->running = false; + mutexUnlock(&aud->fifo_lock); + threadWaitForExit(&aud->thread); + threadClose(&aud->thread); + audrvVoiceStop(&aud->drv, 0); + audrvClose(&aud->drv); + audrenExit(); + + if (aud->mempool) + { + free(aud->mempool); + } + + if (aud->fifo) + { + fifo_clear(aud->fifo); + fifo_free(aud->fifo); + } + + free(aud); +} + +static bool libnx_audren_thread_audio_use_float(void *data) +{ + (void)data; + return false; /* force S16 */ +} + +static size_t libnx_audren_thread_audio_write_avail(void *data) +{ + libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data; + size_t available; + + if (!aud) + return 0; + + mutexLock(&aud->fifo_lock); + available = fifo_write_avail(aud->fifo); + mutexUnlock(&aud->fifo_lock); + + return available; +} + +static void libnx_audren_thread_audio_set_nonblock_state(void *data, bool state) +{ + libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data; + + if (!aud) + return; + + aud->nonblocking = state; +} + +audio_driver_t audio_switch_libnx_audren_thread = { + libnx_audren_thread_audio_init, + libnx_audren_thread_audio_write, + libnx_audren_thread_audio_stop, + libnx_audren_thread_audio_start, + libnx_audren_thread_audio_alive, + libnx_audren_thread_audio_set_nonblock_state, + libnx_audren_thread_audio_free, + libnx_audren_thread_audio_use_float, + "switch_audren_thread", + NULL, /* device_list_new */ + NULL, /* device_list_free */ + libnx_audren_thread_audio_write_avail, + libnx_audren_thread_audio_buffer_size, +}; diff --git a/configuration.c b/configuration.c index 5844cf2701..5e953bf650 100644 --- a/configuration.c +++ b/configuration.c @@ -695,7 +695,11 @@ const char *config_get_default_audio(void) case AUDIO_CTR: return "dsp"; case AUDIO_SWITCH: +#if defined(HAVE_LIBNX) + return "switch_thread"; +#else return "switch"; +#endif case AUDIO_RWEBAUDIO: return "rwebaudio"; case AUDIO_JACK: diff --git a/retroarch.c b/retroarch.c index f61527ef55..2c27156ae7 100644 --- a/retroarch.c +++ b/retroarch.c @@ -312,8 +312,12 @@ static const audio_driver_t *audio_drivers[] = { &audio_ctr_dsp, #endif #ifdef SWITCH - &audio_switch_thread, &audio_switch, + &audio_switch_thread, +#ifdef HAVE_LIBNX + &audio_switch_libnx_audren, + &audio_switch_libnx_audren_thread, +#endif #endif &audio_null, NULL, diff --git a/retroarch.h b/retroarch.h index 87a97c44bc..5feefc4088 100644 --- a/retroarch.h +++ b/retroarch.h @@ -611,6 +611,8 @@ 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_switch_libnx_audren; +extern audio_driver_t audio_switch_libnx_audren_thread; extern audio_driver_t audio_rwebaudio; extern audio_driver_t audio_null;