audio output thread

This commit is contained in:
Kenta Yoshimura 2020-08-17 07:06:22 +09:00
parent a3c0c4621d
commit e2c1abb7fc
1 changed files with 177 additions and 72 deletions

View File

@ -12,12 +12,14 @@
#include <signal.h> #include <signal.h>
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <algorithm>
#ifdef HAVE_STRINGS_H #ifdef HAVE_STRINGS_H
#include <strings.h> #include <strings.h>
#endif #endif
#ifdef USE_THREADS #ifdef USE_THREADS
#include <sched.h> #include <sched.h>
#include <pthread.h> #include <pthread.h>
#include <vector>
#endif #endif
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/time.h> #include <sys/time.h>
@ -114,10 +116,6 @@ struct SoundStatus
{ {
int sound_fd; int sound_fd;
uint32 fragment_size; uint32 fragment_size;
uint32 err_counter;
uint32 err_rate;
int32 samples_mixed_so_far;
int32 play_position;
}; };
@ -127,15 +125,6 @@ static SoundStatus so;
static bool8 rewinding; static bool8 rewinding;
#ifndef NOSOUND
static uint8 Buf[SOUND_BUFFER_SIZE];
#endif
#ifdef USE_THREADS
static pthread_t thread;
static pthread_mutex_t mutex;
#endif
#ifdef JOYSTICK_SUPPORT #ifdef JOYSTICK_SUPPORT
static uint8 js_mod[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; static uint8 js_mod[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
static int js_fd[8] = { -1, -1, -1, -1, -1, -1, -1, -1 }; static int js_fd[8] = { -1, -1, -1, -1, -1, -1, -1, -1 };
@ -156,14 +145,8 @@ bool S9xDisplayPollButton (uint32, bool *);
bool S9xDisplayPollAxis (uint32, int16 *); bool S9xDisplayPollAxis (uint32, int16 *);
bool S9xDisplayPollPointer (uint32, int16 *, int16 *); bool S9xDisplayPollPointer (uint32, int16 *, int16 *);
static long log2 (long);
static void SoundTrigger (void);
static void InitTimer (void);
static void NSRTControllerSetup (void); static void NSRTControllerSetup (void);
static int make_snes9x_dirs (void); static int make_snes9x_dirs (void);
#ifndef NOSOUND
static void * S9xProcessSound (void *);
#endif
#ifdef JOYSTICK_SUPPORT #ifdef JOYSTICK_SUPPORT
static void InitJoysticks (void); static void InitJoysticks (void);
static bool8 ReadJoysticks (void); static bool8 ReadJoysticks (void);
@ -180,6 +163,169 @@ static long log2 (long num)
return (n); return (n);
} }
namespace {
#if ! defined(NOSOUND)
class S9xAudioOutput
{
public:
S9xAudioOutput(int fd, uint32 sampleRateHz, bool isThreaded)
{
m_FD = fd;
uint32 bufferSizeMS = unixSettings.SoundBufferSize; // milliseconds
// 4 = sizeof(uint16) * STEREO
m_BufferSize = int(uint64(sampleRateHz) * bufferSizeMS / 1000 * 4);
#if defined(USE_THREADS)
m_WrittenSize = 0;
m_Thread = pthread_t();
m_isExit = false;
if (isThreaded)
{
m_BufferMutex = PTHREAD_MUTEX_INITIALIZER;
m_hasBuffer = PTHREAD_COND_INITIALIZER;
if (pthread_create(&m_Thread, NULL, AudioOutputThreadEntry, this))
{
return;
}
}
#endif
}
~S9xAudioOutput()
{
#if defined(USE_THREADS)
if (m_Thread)
{
pthread_mutex_lock(&m_BufferMutex);
{
m_isExit = true;
pthread_cond_signal(&m_hasBuffer);
}
pthread_mutex_unlock(&m_BufferMutex);
pthread_join(m_Thread, NULL);
pthread_mutex_destroy(&m_BufferMutex);
pthread_cond_destroy(&m_hasBuffer);
}
#endif
}
void Write(void* data, int size)
{
#if defined(USE_THREADS)
if (m_Thread)
{
pthread_mutex_lock(&m_BufferMutex);
{
if (int(m_Buffering.size()) < m_BufferSize)
{
size_t oldSize = m_Buffering.size();
size_t newSize = oldSize + size;
m_WrittenSize = newSize;
m_Buffering.resize(newSize);
memcpy(&m_Buffering[oldSize], data, size);
pthread_cond_signal(&m_hasBuffer);
}
}
pthread_mutex_unlock(&m_BufferMutex);
}
else
#endif
{
WriteImpl(data, size);
}
}
int GetFreeBufferSize()
{
#if defined(USE_THREADS)
if (m_Thread)
{
int writtenSize;
pthread_mutex_lock(&m_BufferMutex);
{
writtenSize = m_WrittenSize;
}
pthread_mutex_unlock(&m_BufferMutex);
return m_BufferSize - writtenSize;
}
else
#endif
{
audio_buf_info info;
ioctl(m_FD, SNDCTL_DSP_GETOSPACE, &info);
int writtenSize = info.fragsize * info.fragstotal - info.bytes;
return std::max(0, m_BufferSize - writtenSize);
}
}
private:
void WriteImpl(const void* data, int size)
{
const char* p = reinterpret_cast<const char*>(data);
while (size > 0)
{
int result = write(m_FD, p, size);
if (result < 0)
{
return;
}
p += result;
size -= result;
}
}
int m_FD;
int m_BufferSize;
#if defined(USE_THREADS)
pthread_t m_Thread;
volatile bool m_isExit;
pthread_mutex_t m_BufferMutex;
pthread_cond_t m_hasBuffer;
std::vector<uint8> m_PlayingBuffer;
std::vector<uint8> m_Buffering;
int m_WrittenSize; // for dynamic rate control
static void* AudioOutputThreadEntry(void* arg)
{
S9xAudioOutput* obj = reinterpret_cast<S9xAudioOutput*>(arg);
obj->AudioOutputThread();
return NULL;
}
void AudioOutputThread()
{
while (true)
{
pthread_mutex_lock(&m_BufferMutex);
{
pthread_cond_wait(&m_hasBuffer, &m_BufferMutex);
if (m_isExit)
{
return;
}
m_PlayingBuffer.swap(m_Buffering);
m_WrittenSize = 0;
}
pthread_mutex_unlock(&m_BufferMutex);
if (! m_PlayingBuffer.empty())
{
WriteImpl(&m_PlayingBuffer[0], m_PlayingBuffer.size());
m_PlayingBuffer.resize(0);
}
}
}
#endif // USE_THREADS
};
S9xAudioOutput* s_AudioOutput = NULL;
#endif // NOSOUND
}
void S9xExtraUsage (void) void S9xExtraUsage (void)
{ {
/* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */ /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
@ -1181,49 +1327,17 @@ void S9xSamplesAvailable(void *data)
{ {
#ifndef NOSOUND #ifndef NOSOUND
audio_buf_info info;
int samples_to_write; int samples_to_write;
int bytes_to_write;
int bytes_written;
static uint8 *sound_buffer = NULL; static uint8 *sound_buffer = NULL;
static int sound_buffer_size = 0; static int sound_buffer_size = 0;
ioctl(so.sound_fd, SNDCTL_DSP_GETOSPACE, &info);
if (Settings.DynamicRateControl) if (Settings.DynamicRateControl)
{ {
S9xUpdateDynamicRate(info.bytes, so.fragment_size * 4); S9xUpdateDynamicRate(s_AudioOutput->GetFreeBufferSize(), so.fragment_size * 4);
} }
samples_to_write = S9xGetSampleCount(); samples_to_write = S9xGetSampleCount();
if (Settings.DynamicRateControl && !Settings.SoundSync)
{
// Using rate control, we should always keep the emulator's sound buffers empty to
// maintain an accurate measurement.
if (samples_to_write > (info.bytes >> 1))
{
S9xClearSamples();
return;
}
}
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
{
while (info.bytes >> 1 < samples_to_write)
{
int usec_to_sleep = ((samples_to_write >> 1) - (info.bytes >> 2)) * 10000 /
(Settings.SoundPlaybackRate / 100);
usleep(usec_to_sleep > 0 ? usec_to_sleep : 0);
ioctl(so.sound_fd, SNDCTL_DSP_GETOSPACE, &info);
}
}
else
{
samples_to_write = MIN(info.bytes >> 1, samples_to_write) & ~1;
}
if (samples_to_write < 0) if (samples_to_write < 0)
return; return;
@ -1235,22 +1349,7 @@ void S9xSamplesAvailable(void *data)
S9xMixSamples(sound_buffer, samples_to_write); S9xMixSamples(sound_buffer, samples_to_write);
bytes_written = 0; s_AudioOutput->Write(sound_buffer, samples_to_write * 2);
bytes_to_write = samples_to_write * 2;
while (bytes_to_write > bytes_written)
{
int result;
result = write(so.sound_fd,
((char *)sound_buffer) + bytes_written,
bytes_to_write - bytes_written);
if (result < 0)
break;
bytes_written += result;
}
#endif #endif
} }
@ -1266,6 +1365,12 @@ bool8 S9xOpenSoundDevice (void)
return (FALSE); return (FALSE);
} }
s_AudioOutput = new S9xAudioOutput(
so.sound_fd,
Settings.SoundPlaybackRate,
bool(unixSettings.ThreadSound)
);
J = log2(unixSettings.SoundFragmentSize) | (4 << 16); J = log2(unixSettings.SoundFragmentSize) | (4 << 16);
if (ioctl(so.sound_fd, SNDCTL_DSP_SETFRAGMENT, &J) == -1) if (ioctl(so.sound_fd, SNDCTL_DSP_SETFRAGMENT, &J) == -1)
return (FALSE); return (FALSE);
@ -1295,10 +1400,6 @@ bool8 S9xOpenSoundDevice (void)
return (TRUE); return (TRUE);
} }
#ifndef NOSOUND
#endif
void S9xExit (void) void S9xExit (void)
{ {
@ -1312,6 +1413,10 @@ void S9xExit (void)
S9xNPDisconnect(); S9xNPDisconnect();
#endif #endif
#ifndef NOSOUND
delete s_AudioOutput;
#endif
Memory.SaveSRAM(S9xGetFilename(".srm", SRAM_DIR)); Memory.SaveSRAM(S9xGetFilename(".srm", SRAM_DIR));
S9xResetSaveTimer(FALSE); S9xResetSaveTimer(FALSE);
S9xSaveCheatFile(S9xGetFilename(".cht", CHEAT_DIR)); S9xSaveCheatFile(S9xGetFilename(".cht", CHEAT_DIR));