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.
This commit is contained in:
John Lorentzson 2025-04-24 21:00:44 +02:00
parent a56c9c57a2
commit 6c04e37864

View file

@ -17,33 +17,46 @@
#include <sys/audioio.h> #include <sys/audioio.h>
#include <pthread.h> #include <pthread.h>
static const int samplesPerGo = 1600; static const int bufferSize = 300;
static const int parkSignal = SIGUSR1; static const int samplingRate = 8000;
static pthread_t mainThread;
static int buffersWritten = 0;
static int sndfp; static int sndfp;
static FILE* sndfile; static FILE* sndfile;
static void (*parent_callback)(long *stream, size_t frames_total); static void (*parent_callback)(long *stream, size_t frames_total);
static bool audioDone = false;
static inline void setUpSoundFrame(long *mix) { static inline void setUpSoundFrame(long *mix) {
memset(mix, 0, samplesPerGo * 2 * sizeof(long)); memset(mix, 0, bufferSize * 2 * sizeof(long));
parent_callback(mix, samplesPerGo); 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; audio_info_t audioInfo;
ioctl(sndfp, AUDIO_GETINFO, &audioInfo); ioctl(sndfp, AUDIO_GETINFO, &audioInfo);
int playedBefore = audioInfo.play.samples;
int playedNow = audioInfo.play.samples; // audioInfo.play.eof is the number of "end-of-file records" the sound chip
while(playedNow - playedBefore < samplesPerGo) { // has encountered. A "end-of-file record" is created by performing a
Backend_Delay(10); // write() of zero length. We end each audio buffer with one such write.
ioctl(sndfp, AUDIO_GETINFO, &audioInfo); int previousEOF = audioInfo.play.eof;
playedNow = audioInfo.play.samples;
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) { 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]; short sample = mix[i];
if(mix[i] > 0x7FFF) { if(mix[i] > 0x7FFF) {
sample = 0x7FFF; sample = 0x7FFF;
@ -54,69 +67,64 @@ static inline void feedOutSound(long* mix) {
fputc((sample >> 8) & 0xFF, sndfile); fputc((sample >> 8) & 0xFF, sndfile);
fputc(sample & 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*) { static void* soundThread(void*) {
printf("Entered sound thread.\n"); long mix[bufferSize * 2];
long mix[samplesPerGo * 2]; Backend_PrintInfo("Entered sound thread.\n");
setUpSoundFrame(mix);
feedOutSound(mix);
setUpSoundFrame(mix);
feedOutSound(mix);
while(1) { while(1) {
setUpSoundFrame(mix);
// Waiting for more to be needed // 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)) { unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total)) {
parent_callback = callback; parent_callback = callback;
if(sndfp == -1) { sndfile = fopen("/dev/audio", "wb");
if(sndfile == NULL) {
Backend_PrintError("Failed to open audio device.\n"); Backend_PrintError("Failed to open audio device.\n");
return 0; return 0;
} }
sndfile = fopen("/dev/audio", "wb");
sndfp = fileno(sndfile); sndfp = fileno(sndfile);
audio_info_t audioInfo; audio_info_t audioInfo;
ioctl(sndfp, AUDIO_GETINFO, &audioInfo); ioctl(sndfp, AUDIO_GETINFO, &audioInfo);
audio_prinfo_t info = audioInfo.play; audio_prinfo_t info = audioInfo.play;
info.sample_rate = 8000; info.sample_rate = samplingRate;
info.channels = 2; info.channels = 2;
info.precision = 8; info.precision = 8;
info.encoding = AUDIO_ENCODING_LINEAR; info.encoding = AUDIO_ENCODING_LINEAR;
info.port = AUDIO_SPEAKER | AUDIO_HEADPHONE; info.port = AUDIO_HEADPHONE;
info.gain = 128; info.gain = 128;
audioInfo.play = info; audioInfo.play = info;
ioctl(sndfp, AUDIO_SETINFO, &audioInfo); 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_t thread;
pthread_attr_t thread_attr; pthread_attr_t thread_attr;
pthread_attr_init(&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) { void SoftwareMixerBackend_Deinit(void) {