diff --git a/unix/unix.cpp b/unix/unix.cpp index 174550ae..0fe8f013 100644 --- a/unix/unix.cpp +++ b/unix/unix.cpp @@ -12,12 +12,14 @@ #include #include #include +#include #ifdef HAVE_STRINGS_H #include #endif #ifdef USE_THREADS #include #include +#include #endif #include #include @@ -114,10 +116,6 @@ struct SoundStatus { int sound_fd; 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; -#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 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 }; @@ -156,14 +145,8 @@ bool S9xDisplayPollButton (uint32, bool *); bool S9xDisplayPollAxis (uint32, int16 *); bool S9xDisplayPollPointer (uint32, int16 *, int16 *); -static long log2 (long); -static void SoundTrigger (void); -static void InitTimer (void); static void NSRTControllerSetup (void); static int make_snes9x_dirs (void); -#ifndef NOSOUND -static void * S9xProcessSound (void *); -#endif #ifdef JOYSTICK_SUPPORT static void InitJoysticks (void); static bool8 ReadJoysticks (void); @@ -180,6 +163,169 @@ static long log2 (long num) 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(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 m_PlayingBuffer; + std::vector m_Buffering; + int m_WrittenSize; // for dynamic rate control + + static void* AudioOutputThreadEntry(void* arg) + { + S9xAudioOutput* obj = reinterpret_cast(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) { /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */ @@ -1181,49 +1327,17 @@ void S9xSamplesAvailable(void *data) { #ifndef NOSOUND - audio_buf_info info; int samples_to_write; - int bytes_to_write; - int bytes_written; static uint8 *sound_buffer = NULL; static int sound_buffer_size = 0; - - ioctl(so.sound_fd, SNDCTL_DSP_GETOSPACE, &info); - if (Settings.DynamicRateControl) { - S9xUpdateDynamicRate(info.bytes, so.fragment_size * 4); + S9xUpdateDynamicRate(s_AudioOutput->GetFreeBufferSize(), so.fragment_size * 4); } 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) return; @@ -1235,22 +1349,7 @@ void S9xSamplesAvailable(void *data) S9xMixSamples(sound_buffer, samples_to_write); - bytes_written = 0; - 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; - } + s_AudioOutput->Write(sound_buffer, samples_to_write * 2); #endif } @@ -1266,6 +1365,12 @@ bool8 S9xOpenSoundDevice (void) return (FALSE); } + s_AudioOutput = new S9xAudioOutput( + so.sound_fd, + Settings.SoundPlaybackRate, + bool(unixSettings.ThreadSound) + ); + J = log2(unixSettings.SoundFragmentSize) | (4 << 16); if (ioctl(so.sound_fd, SNDCTL_DSP_SETFRAGMENT, &J) == -1) return (FALSE); @@ -1295,10 +1400,6 @@ bool8 S9xOpenSoundDevice (void) return (TRUE); } -#ifndef NOSOUND - - -#endif void S9xExit (void) { @@ -1312,6 +1413,10 @@ void S9xExit (void) S9xNPDisconnect(); #endif +#ifndef NOSOUND + delete s_AudioOutput; +#endif + Memory.SaveSRAM(S9xGetFilename(".srm", SRAM_DIR)); S9xResetSaveTimer(FALSE); S9xSaveCheatFile(S9xGetFilename(".cht", CHEAT_DIR));