Multithreaded Audio (#554)

* created the audio thread

* Update playback.c

* moved call to audio_game_loop_tick() to the audio thread

* added semaphores

* Update pc_main.c

* audio thread now runs at 60hz

also now using the built-in sys_sleep() upon sleeping. also implemented mutex locks.

* audio thread doesn't run when loading a level

uses code lifted straight from the original decomp since that deals with multithreading

* mutex, semaphore, and time functions moved to platform.c

* fixed race condition (i hope). also created thread.h and .c for global access

* refactored so that pc_main has the functions for waiting for a semaphore and whatnot

* Update pc_main.c
This commit is contained in:
Miguel 2024-05-11 05:48:23 -07:00 committed by GitHub
parent f2cfd74721
commit 39cfae663f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 189 additions and 18 deletions

View File

@ -13,6 +13,7 @@
#include "seq_ids.h"
#include "dialog_ids.h"
#include "level_table.h"
#include "pc/pc_main.h"
#ifdef VERSION_EU
#define EU_FLOAT(x) x ## f
@ -774,6 +775,7 @@ void create_next_audio_buffer(s16 *samples, u32 num_samples) {
update_game_sound();
sGameLoopTicked = 0;
}
s32 writtenCmds;
synthesis_execute(gAudioCmdBuffers[0], &writtenCmds, samples, num_samples);
gAudioRandom = ((gAudioRandom + gAudioFrameCount) * gAudioFrameCount);
@ -2348,6 +2350,10 @@ void sound_reset(u8 presetId) {
sUnused8033323C = 0;
}
#endif
// Wait for audio thread to finish rendering
pc_wait_for_audio();
pc_request_gameloop_wait();
sGameLoopTicked = 0;
disable_all_sequence_players();
sound_init();

View File

@ -8,6 +8,7 @@
#include "pc/platform.h"
#include "pc/fs/fs.h"
#include "pc/thread.h"
#define ALIGN16(val) (((val) + 0xF) & ~0xF)

View File

@ -4,6 +4,7 @@
#include "data.h"
#include "seqplayer.h"
#include "synthesis.h"
#include "pc/thread.h"
#ifdef VERSION_EU
@ -54,6 +55,13 @@ void create_next_audio_buffer(s16 *samples, u32 num_samples) {
if (osRecvMesg(OSMesgQueues[1], &msg, OS_MESG_NOBLOCK) != -1) {
func_802ad7ec((u32) msg);
}
// If the game thread is resetting the sound, don't process any audio commands
pcthread_mutex_lock(&pcthread_game_mutex); bool reseting_sound = pcthread_game_reset_sound; pcthread_mutex_unlock(&pcthread_game_mutex);
if (reseting_sound) {
printf("Audio thread: Dropped 1 frame\n");
return;
}
synthesis_execute(gAudioCmdBuffers[0], &writtenCmds, samples, num_samples);
gAudioRandom = ((gAudioRandom + gAudioFrameCount) * gAudioFrameCount);
gAudioRandom = gAudioRandom + writtenCmds / 8;

View File

@ -1,5 +1,8 @@
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h> //Header file for sleep(). man 3 sleep for details.
#include <time.h>
#include <errno.h>
#ifdef TARGET_WEB
#include <emscripten.h>
@ -25,6 +28,7 @@
#include "audio/audio_null.h"
#include "pc_main.h"
#include "thread.h"
#include "cliopts.h"
#include "configfile.h"
#include "controller/controller_api.h"
@ -56,10 +60,24 @@ static struct AudioAPI *audio_api;
static struct GfxWindowManagerAPI *wm_api;
static struct GfxRenderingAPI *rendering_api;
pthread_t pcthread_audio_id;
pthread_mutex_t pcthread_audio_mutex = PTHREAD_MUTEX_INITIALIZER;
bool pcthread_audio_init = false;
bool pcthread_audio_rendering = false;
sem_t pcthread_audio_sema;
pthread_mutex_t pcthread_game_mutex = PTHREAD_MUTEX_INITIALIZER;
bool pcthread_game_loop_iterating = false;
bool pcthread_game_reset_sound = false;
bool pcthread_wait_for_gameloop = false;
sem_t pcthread_game_sema;
extern void gfx_run(Gfx *commands);
extern void thread5_game_loop(void *arg);
extern void audio_game_loop_tick(void);
extern void create_next_audio_buffer(s16 *samples, u32 num_samples);
void game_loop_one_iteration(void);
void* audio_thread();
void dispatch_audio_sptask(struct SPTask *spTask) {
}
@ -67,6 +85,28 @@ void dispatch_audio_sptask(struct SPTask *spTask) {
void set_vblank_handler(s32 index, struct VblankHandler *handler, OSMesgQueue *queue, OSMesg *msg) {
}
// Send a request for non-game threads to wait for the game thread to finish
void pc_request_gameloop_wait(void) {
pcthread_mutex_lock(&pcthread_game_mutex); pcthread_wait_for_gameloop = true; pcthread_mutex_unlock(&pcthread_game_mutex);
}
// Wait for the audio thread to finish rendering audio
void pc_wait_for_audio(void) {
pcthread_semaphore_wait(&pcthread_audio_sema);
}
// Check if the audio thread is currently rendering audio
bool pc_check_audio_rendering(void) {
pcthread_mutex_lock(&pcthread_audio_mutex); bool rendering = pcthread_audio_rendering; pcthread_mutex_unlock(&pcthread_audio_mutex);
return rendering;
}
// Check if the game thread should finish before continuing
bool pc_check_gameloop_wait(void) {
pcthread_mutex_lock(&pcthread_game_mutex); bool waiting = pcthread_wait_for_gameloop; pcthread_mutex_unlock(&pcthread_game_mutex);
return waiting;
}
static bool inited = false;
#include "game/display.h" // for gGlobalTimer
@ -86,33 +126,84 @@ void send_display_list(struct SPTask *spTask) {
void produce_one_frame(void) {
gfx_start_frame();
const f32 master_mod = (f32)configMasterVolume / 127.0f;
set_sequence_player_volume(SEQ_PLAYER_LEVEL, (f32)configMusicVolume / 127.0f * master_mod);
set_sequence_player_volume(SEQ_PLAYER_SFX, (f32)configSfxVolume / 127.0f * master_mod);
set_sequence_player_volume(SEQ_PLAYER_ENV, (f32)configEnvVolume / 127.0f * master_mod);
game_loop_one_iteration();
thread6_rumble_loop(NULL);
int samples_left = audio_api->buffered();
u32 num_audio_samples = samples_left < audio_api->get_desired_buffered() ? SAMPLES_HIGH : SAMPLES_LOW;
//printf("Audio samples: %d %u\n", samples_left, num_audio_samples);
s16 audio_buffer[SAMPLES_HIGH * 2 * 2];
for (int i = 0; i < 2; i++) {
/*if (audio_cnt-- == 0) {
audio_cnt = 2;
}
u32 num_audio_samples = audio_cnt < 2 ? 528 : 544;*/
create_next_audio_buffer(audio_buffer + i * (num_audio_samples * 2), num_audio_samples);
// Post the game thread semaphore if the game thread requested it
pcthread_mutex_lock(&pcthread_game_mutex);
if (pcthread_wait_for_gameloop) {
pcthread_semaphore_post(&pcthread_game_sema);
pcthread_wait_for_gameloop = false;
}
//printf("Audio samples before submitting: %d\n", audio_api->buffered());
pcthread_mutex_unlock(&pcthread_game_mutex);
audio_api->play((u8 *)audio_buffer, 2 * num_audio_samples * 4);
thread6_rumble_loop(NULL);
gfx_end_frame();
}
// Seperate the audio thread from the main thread so that your ears won't bleed at a low framerate
// RACE CONDITION:
void* audio_thread() {
// Keep track of the time in microseconds
const f64 frametime_micro = 16666.666; // 16.666666 ms = 60Hz; run this thread 60 times a second like the original game
f64 start_time;
f64 end_time;
bool doloop = true;
pcthread_semaphore_wait(&pcthread_game_sema);
while(doloop) {
start_time = sys_profile_time();
const f32 master_mod = (f32)configMasterVolume / 127.0f;
set_sequence_player_volume(SEQ_PLAYER_LEVEL, (f32)configMusicVolume / 127.0f * master_mod);
set_sequence_player_volume(SEQ_PLAYER_SFX, (f32)configSfxVolume / 127.0f * master_mod);
set_sequence_player_volume(SEQ_PLAYER_ENV, (f32)configEnvVolume / 127.0f * master_mod);
int samples_left = audio_api->buffered();
u32 num_audio_samples = samples_left < audio_api->get_desired_buffered() ? SAMPLES_HIGH : SAMPLES_LOW;
// printf("Audio samples: %d %u\n", samples_left, num_audio_samples);
s16 audio_buffer[SAMPLES_HIGH * 2];
/*if (audio_cnt-- == 0) {
audio_cnt = 2;
}
u32 num_audio_samples = audio_cnt < 2 ? 528 : 544;*/
if (!pc_check_gameloop_wait()) {
pcthread_mutex_lock(&pcthread_audio_mutex); pcthread_audio_rendering = true; pcthread_mutex_unlock(&pcthread_audio_mutex);
create_next_audio_buffer(audio_buffer, num_audio_samples);
pcthread_semaphore_post(&pcthread_audio_sema);
pcthread_mutex_lock(&pcthread_audio_mutex); pcthread_audio_rendering = false; pcthread_mutex_unlock(&pcthread_audio_mutex);
} /* else {
printf("Audio thread: dropped frame\n");
} */
// printf("Audio samples before submitting: %d\n", audio_api->buffered());
audio_api->play((u8 *)audio_buffer, num_audio_samples * 4);
end_time = sys_profile_time();
// Sleep for the remaining time
f64 nap_time = frametime_micro - (end_time - start_time);
// printf("Audio thread nap time: %f\n", nap_time);
if (nap_time > 0.0) sys_sleep(nap_time);
// Check if the game thread is still running
pcthread_mutex_lock(&pcthread_audio_mutex); doloop = pcthread_audio_init; pcthread_mutex_unlock(&pcthread_audio_mutex);
}
return NULL;
}
void audio_thread_init() {
pcthread_audio_init = true;
pcthread_semaphore_init(&pcthread_audio_sema, 0, 1);
pthread_create(&pcthread_audio_id, NULL, audio_thread, NULL);
}
void audio_shutdown(void) {
// Tell the audio thread to stop
pcthread_mutex_lock(&pcthread_audio_mutex); pcthread_audio_init = false; pcthread_mutex_unlock(&pcthread_audio_mutex);
pcthread_semaphore_wait(&pcthread_audio_sema); // Wait for the audio thread to finish rendering audio, then destroy it all
pthread_join(pcthread_audio_id, NULL);
pcthread_semaphore_destroy(&pcthread_audio_sema);
if (audio_api) {
if (audio_api->shutdown) audio_api->shutdown();
audio_api = NULL;
@ -127,6 +218,7 @@ void game_deinit(void) {
controller_shutdown();
audio_shutdown();
gfx_shutdown();
pcthread_semaphore_destroy(&pcthread_game_sema);
inited = false;
}
@ -257,6 +349,10 @@ void main_func(void) {
emscripten_set_main_loop(em_main_loop, 0, 0);
request_anim_frame(on_anim_frame);
#else
// initialize multithreading
pcthread_semaphore_init(&pcthread_game_sema, 0, 0);
audio_thread_init();
while (true) {
wm_api->main_loop(produce_one_frame);
#ifdef DISCORDRPC

View File

@ -5,9 +5,16 @@
extern "C" {
#endif
#include <stdbool.h>
void game_deinit(void);
void game_exit(void);
void pc_request_gameloop_wait(void);
void pc_wait_for_audio(void);
bool pc_check_audio_rendering(void);
bool pc_check_gameloop_wait(void);
#ifdef __cplusplus
}
#endif

View File

@ -73,6 +73,14 @@ void sys_sleep(const uint64_t us) {
usleep(us);
}
// A high-resolution profiling timer. Returns the current time in microseconds.
double sys_profile_time(void) {
// TODO: Platform specific stuff
struct timespec tv;
clock_gettime(CLOCK_MONOTONIC, &tv);
return (double)(tv.tv_nsec) / 1000.0 + (double)(tv.tv_sec) * 1000000.0;
}
/* this calls a platform-specific impl function after forming the error message */
static void sys_fatal_impl(const char *msg) __attribute__ ((noreturn));

View File

@ -5,6 +5,9 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>
/* Platform-specific functions and whatnot */
@ -18,6 +21,7 @@ char *sys_strdup(const char *src);
char *sys_strlwr(char *src);
int sys_strcasecmp(const char *s1, const char *s2);
void sys_sleep(const uint64_t us);
double sys_profile_time(void);
// path stuff
const char *sys_user_path(void);

26
src/pc/thread.c Normal file
View File

@ -0,0 +1,26 @@
#include "thread.h"
// TODO: Cross-platform stuff
void pcthread_mutex_lock(pthread_mutex_t *mutex) {
pthread_mutex_lock(mutex);
}
void pcthread_mutex_unlock(pthread_mutex_t *mutex) {
pthread_mutex_unlock(mutex);
}
void pcthread_semaphore_init(sem_t *sem, int pshared, unsigned int value) {
sem_init(sem, pshared, value);
}
void pcthread_semaphore_wait(sem_t *sem) {
sem_wait(sem);
}
void pcthread_semaphore_post(sem_t *sem) {
sem_post(sem);
}
void pcthread_semaphore_destroy(sem_t *sem) {
sem_destroy(sem);
}

15
src/pc/thread.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef _PC_THREAD_H
#define _PC_THREAD_H
#include <pthread.h>
#include <semaphore.h>
#include <stdbool.h>
void pcthread_mutex_lock(pthread_mutex_t *mutex);
void pcthread_mutex_unlock(pthread_mutex_t *mutex);
void pcthread_semaphore_init(sem_t *sem, int pshared, unsigned int value);
void pcthread_semaphore_wait(sem_t *sem);
void pcthread_semaphore_post(sem_t *sem);
void pcthread_semaphore_destroy(sem_t *sem);
#endif // _PC_THREAD_H