From 6c04e37864275442512c5e4843fa72be4b192b4c Mon Sep 17 00:00:00 2001 From: John Lorentzson Date: Thu, 24 Apr 2025 21:00:44 +0200 Subject: [PATCH] Working and tested audio backend for Solaris Uses the audio device's "EOF counter" to tell how many audio buffers we've played, then ensures that production is three buffers ahead of playback. This has been tested and works. Either game or audio thread may still freeze, this can likely be fixed by implementing the locks. --- src/Backends/Audio/SoftwareMixer/Solaris.cpp | 96 +++++++++++--------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/src/Backends/Audio/SoftwareMixer/Solaris.cpp b/src/Backends/Audio/SoftwareMixer/Solaris.cpp index 18e90149..5c95f1b0 100644 --- a/src/Backends/Audio/SoftwareMixer/Solaris.cpp +++ b/src/Backends/Audio/SoftwareMixer/Solaris.cpp @@ -17,33 +17,46 @@ #include #include -static const int samplesPerGo = 1600; -static const int parkSignal = SIGUSR1; -static pthread_t mainThread; +static const int bufferSize = 300; +static const int samplingRate = 8000; + +static int buffersWritten = 0; static int sndfp; static FILE* sndfile; static void (*parent_callback)(long *stream, size_t frames_total); -static bool audioDone = false; static inline void setUpSoundFrame(long *mix) { - memset(mix, 0, samplesPerGo * 2 * sizeof(long)); - parent_callback(mix, samplesPerGo); + memset(mix, 0, bufferSize * 2 * sizeof(long)); + parent_callback(mix, bufferSize); } -static inline void soundWait() { +static inline int soundWait() { + // Wait until at least one audio buffer has been played back. + // Return the number of audio buffers we have ever finished playing. + audio_info_t audioInfo; ioctl(sndfp, AUDIO_GETINFO, &audioInfo); - int playedBefore = audioInfo.play.samples; - int playedNow = audioInfo.play.samples; - while(playedNow - playedBefore < samplesPerGo) { - Backend_Delay(10); - ioctl(sndfp, AUDIO_GETINFO, &audioInfo); - playedNow = audioInfo.play.samples; + + // audioInfo.play.eof is the number of "end-of-file records" the sound chip + // has encountered. A "end-of-file record" is created by performing a + // write() of zero length. We end each audio buffer with one such write. + int previousEOF = audioInfo.play.eof; + + while(audioInfo.play.eof - previousEOF < 1) { + if(playedNow >= buffersWritten) { + Backend_PrintError("Audio overflow.\n"); + break; + } + + Backend_Delay(10); // Wait some reasonable time + + ioctl(sndfp, AUDIO_GETINFO, &audioInfo); // Get the new current EOF } + return playedNow; } static inline void feedOutSound(long* mix) { - for(int i = 0; i < samplesPerGo * 2; i++) { + for(int i = 0; i < bufferSize * 2; i++) { short sample = mix[i]; if(mix[i] > 0x7FFF) { sample = 0x7FFF; @@ -54,69 +67,64 @@ static inline void feedOutSound(long* mix) { fputc((sample >> 8) & 0xFF, sndfile); fputc(sample & 0xFF, sndfile); } + + // Write a "end-of-file record" here used as a end-of-buffer marker. The + // sound chip counts how many of these it has encountered and we can use + // that to tell how many sound buffers it's played. + write(sndfp, 0, 0); + fflush(sndfile); + + buffersWritten++; } -static void* soundcheckthread(void*) { - printf("Entered sound thread.\n"); - long mix[samplesPerGo * 2]; - setUpSoundFrame(mix); - feedOutSound(mix); - setUpSoundFrame(mix); - feedOutSound(mix); +static void* soundThread(void*) { + long mix[bufferSize * 2]; + Backend_PrintInfo("Entered sound thread.\n"); + while(1) { - - setUpSoundFrame(mix); - // Waiting for more to be needed - soundWait(); + int eofsPlayed = soundWait(); - feedOutSound(mix); + // Produce audio until we're three buffers ahead of playback + while(buffersWritten - eofsPlayed < 3) { + setUpSoundFrame(mix); + feedOutSound(mix); + } } } -static void threadPark(int signo) { - while(audioDone == false); -} - unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total)) { parent_callback = callback; - if(sndfp == -1) { + sndfile = fopen("/dev/audio", "wb"); + + if(sndfile == NULL) { Backend_PrintError("Failed to open audio device.\n"); return 0; } - sndfile = fopen("/dev/audio", "wb"); sndfp = fileno(sndfile); audio_info_t audioInfo; ioctl(sndfp, AUDIO_GETINFO, &audioInfo); audio_prinfo_t info = audioInfo.play; - info.sample_rate = 8000; + info.sample_rate = samplingRate; info.channels = 2; info.precision = 8; info.encoding = AUDIO_ENCODING_LINEAR; - info.port = AUDIO_SPEAKER | AUDIO_HEADPHONE; + info.port = AUDIO_HEADPHONE; info.gain = 128; audioInfo.play = info; ioctl(sndfp, AUDIO_SETINFO, &audioInfo); - //close(sndfp); - - if(signal(parkSignal, threadPark) == SIG_ERR) { - Backend_PrintError("Failed to register thread park signal handler for audio system.\n"); - return 0; - } - - mainThread = pthread_self(); pthread_t thread; pthread_attr_t thread_attr; pthread_attr_init(&thread_attr); - pthread_create(&thread, &thread_attr, soundcheckthread, (void*)NULL); + pthread_create(&thread, &thread_attr, soundThread, (void*)NULL); - return 8000; // return the sampling rate + return samplingRate; // Return the sampling rate } void SoftwareMixerBackend_Deinit(void) {