Skip to content
Permalink
Browse files

Use std::mutex instead of CCriticalSection for voice

  • Loading branch information...
botder committed Oct 24, 2018
1 parent ef2b4d4 commit 39c1ba00ae122f0393cf5f185033b17c10b392da
@@ -45,12 +45,10 @@ int CVoiceRecorder::PACallback(const void* inputBuffer, void* outputBuffer, unsi
{
// This assumes that PACallback will only be called when userData is a valid CVoiceRecorder pointer
CVoiceRecorder* pVoiceRecorder = static_cast<CVoiceRecorder*>(userData);
pVoiceRecorder->m_CS.Lock();

if (pVoiceRecorder->IsEnabled())
pVoiceRecorder->SendFrame(inputBuffer);

pVoiceRecorder->m_CS.Unlock();
return 0;
}

@@ -61,7 +59,7 @@ void CVoiceRecorder::Init(bool bEnabled, unsigned int uiServerSampleRate, unsign
if (!bEnabled) // If we aren't enabled, don't bother continuing
return;

m_CS.Lock();
std::lock_guard<std::mutex> lock(m_Mutex);

// Convert the sample rate we received from the server (0-2) into an actual sample rate
m_SampleRate = convertServerSampleRate(uiServerSampleRate);
@@ -124,49 +122,44 @@ void CVoiceRecorder::Init(bool bEnabled, unsigned int uiServerSampleRate, unsign
speex_encoder_ctl(m_pSpeexEncoderState, SPEEX_GET_BITRATE, &iBitRate);

g_pCore->GetConsole()->Printf("Server Voice Chat Quality [%i]; Sample Rate: [%iHz]; Bitrate [%ibps]", m_ucQuality, iSamplingRate, iBitRate);

m_CS.Unlock();
}

void CVoiceRecorder::DeInit(void)
{
if (m_bEnabled)
{
m_bEnabled = false;
if (!m_bEnabled)
return;

Pa_CloseStream(m_pAudioStream);
Pa_Terminate();
std::lock_guard<std::mutex> lock(m_Mutex);

// Assumes now that PACallback will not be called in this context
m_CS.Lock();
m_CS.Unlock();
// Assumes now that PACallback is not executing in this context
m_bEnabled = false;

m_pAudioStream = NULL;
Pa_CloseStream(m_pAudioStream);
Pa_Terminate();

m_iSpeexOutgoingFrameSampleCount = 0;
m_pAudioStream = NULL;

speex_encoder_destroy(m_pSpeexEncoderState);
m_pSpeexEncoderState = NULL;
m_iSpeexOutgoingFrameSampleCount = 0;

speex_preprocess_state_destroy(m_pSpeexPreprocState);
m_pSpeexPreprocState = NULL;
speex_encoder_destroy(m_pSpeexEncoderState);
m_pSpeexEncoderState = NULL;

free(m_pOutgoingBuffer);
m_pOutgoingBuffer = NULL;
speex_preprocess_state_destroy(m_pSpeexPreprocState);
m_pSpeexPreprocState = NULL;

m_VoiceState = VOICESTATE_AWAITING_INPUT;
m_SampleRate = SAMPLERATE_WIDEBAND;
free(m_pOutgoingBuffer);
m_pOutgoingBuffer = NULL;

m_pAudioStream = NULL;
m_VoiceState = VOICESTATE_AWAITING_INPUT;
m_SampleRate = SAMPLERATE_WIDEBAND;

m_iSpeexOutgoingFrameSampleCount = 0;
m_uiOutgoingReadIndex = 0;
m_uiOutgoingWriteIndex = 0;
m_bIsSendingVoiceData = false;
m_ulTimeOfLastSend = 0;
m_uiBufferSizeBytes = 0;
}
m_pAudioStream = NULL;

m_iSpeexOutgoingFrameSampleCount = 0;
m_uiOutgoingReadIndex = 0;
m_uiOutgoingWriteIndex = 0;
m_bIsSendingVoiceData = false;
m_ulTimeOfLastSend = 0;
m_uiBufferSizeBytes = 0;
}

const SpeexMode* CVoiceRecorder::getSpeexModeFromSampleRate(void)
@@ -202,7 +195,7 @@ void CVoiceRecorder::SetPTTState(bool bState)
if (!m_bEnabled)
return;

m_CS.Lock();
m_Mutex.lock();

if (bState)
{
@@ -211,16 +204,17 @@ void CVoiceRecorder::SetPTTState(bool bState)
// Call event on the local player for starting to talk
if (g_pClientGame->GetLocalPlayer())
{
m_CS.Unlock();
m_Mutex.unlock();
CLuaArguments Arguments;
bool bEventTriggered = g_pClientGame->GetLocalPlayer()->CallEvent("onClientPlayerVoiceStart", Arguments, true);

if (!bEventTriggered)
{
return;
}
m_CS.Lock();
m_VoiceState = VOICESTATE_RECORDING;

m_Mutex.lock();

if (m_VoiceState == VOICESTATE_AWAITING_INPUT)
m_VoiceState = VOICESTATE_RECORDING;
}
}
}
@@ -233,25 +227,25 @@ void CVoiceRecorder::SetPTTState(bool bState)
// Call event on the local player for stopping to talk
if (g_pClientGame->GetLocalPlayer())
{
m_CS.Unlock();
m_Mutex.unlock();
CLuaArguments Arguments;
g_pClientGame->GetLocalPlayer()->CallEvent("onClientPlayerVoiceStop", Arguments, true);
m_CS.Lock();
return;
}
}
}

m_CS.Unlock();
m_Mutex.unlock();
}

bool CVoiceRecorder::GetPTTState()
{
return m_VoiceState == VOICESTATE_RECORDING;
return m_VoiceState != VOICESTATE_AWAITING_INPUT;
}

void CVoiceRecorder::DoPulse(void)
{
m_CS.Lock();
std::lock_guard<std::mutex> lock(m_Mutex);

char* pInputBuffer;
char bufTempOutput[2048];
@@ -331,48 +325,47 @@ void CVoiceRecorder::DoPulse(void)
if (m_VoiceState == VOICESTATE_RECORDING_LAST_PACKET) // End of voice data (for events)
{
m_VoiceState = VOICESTATE_AWAITING_INPUT;

NetBitStreamInterface* pBitStream = g_pNet->AllocateNetBitStream();
if (pBitStream)

if (g_pClientGame->GetPlayerManager()->GetLocalPlayer())
{
CClientPlayer* pLocalPlayer = g_pClientGame->GetPlayerManager()->GetLocalPlayer();
NetBitStreamInterface* pBitStream = g_pNet->AllocateNetBitStream();

if (pLocalPlayer)
if (pBitStream)
{
g_pNet->SendPacket(PACKET_ID_VOICE_END, pBitStream, PACKET_PRIORITY_LOW, PACKET_RELIABILITY_UNRELIABLE_SEQUENCED, PACKET_ORDERING_VOICE);
g_pNet->DeallocateNetBitStream(pBitStream);
}
}
}
m_CS.Unlock();
}

// Called from other thread. Critical section is already locked.
void CVoiceRecorder::SendFrame(const void* inputBuffer)
{
if (m_VoiceState != VOICESTATE_AWAITING_INPUT && m_bEnabled && inputBuffer)
{
unsigned int remainingBufferSize = 0;
unsigned int uiTotalBufferSize = m_uiBufferSizeBytes * FRAME_OUTGOING_BUFFER_COUNT;
std::lock_guard<std::mutex> lock(m_Mutex);

// Calculate how much of our buffer is remaining
if (m_uiOutgoingWriteIndex >= m_uiOutgoingReadIndex)
remainingBufferSize = uiTotalBufferSize - (m_uiOutgoingWriteIndex - m_uiOutgoingReadIndex);
else
remainingBufferSize = m_uiOutgoingReadIndex - m_uiOutgoingWriteIndex;
if (m_VoiceState == VOICESTATE_AWAITING_INPUT || !m_bEnabled || !inputBuffer)
return;

// Copy from our input buffer to our outgoing buffer at write index
memcpy(m_pOutgoingBuffer + m_uiOutgoingWriteIndex, inputBuffer, m_uiBufferSizeBytes);
unsigned int remainingBufferSize = 0;
unsigned int uiTotalBufferSize = m_uiBufferSizeBytes * FRAME_OUTGOING_BUFFER_COUNT;

// Re-align our write index
m_uiOutgoingWriteIndex += m_uiBufferSizeBytes;
// Calculate how much of our buffer is remaining
if (m_uiOutgoingWriteIndex >= m_uiOutgoingReadIndex)
remainingBufferSize = uiTotalBufferSize - (m_uiOutgoingWriteIndex - m_uiOutgoingReadIndex);
else
remainingBufferSize = m_uiOutgoingReadIndex - m_uiOutgoingWriteIndex;

// If we have reached the end of the buffer, go back to the start
if (m_uiOutgoingWriteIndex == uiTotalBufferSize)
m_uiOutgoingWriteIndex = 0;
// Copy from our input buffer to our outgoing buffer at write index
memcpy(m_pOutgoingBuffer + m_uiOutgoingWriteIndex, inputBuffer, m_uiBufferSizeBytes);

// Wrap around the buffer?
if (m_uiBufferSizeBytes >= remainingBufferSize)
m_uiOutgoingReadIndex = (m_uiOutgoingReadIndex + m_iSpeexOutgoingFrameSampleCount * VOICE_SAMPLE_SIZE) % uiTotalBufferSize;
}
// Re-align our write index
m_uiOutgoingWriteIndex += m_uiBufferSizeBytes;

// If we have reached the end of the buffer, go back to the start
if (m_uiOutgoingWriteIndex == uiTotalBufferSize)
m_uiOutgoingWriteIndex = 0;

// Wrap around the buffer?
if (m_uiBufferSizeBytes >= remainingBufferSize)
m_uiOutgoingReadIndex = (m_uiOutgoingReadIndex + m_iSpeexOutgoingFrameSampleCount * VOICE_SAMPLE_SIZE) % uiTotalBufferSize;
}
@@ -18,9 +18,7 @@
#define FRAME_OUTGOING_BUFFER_COUNT 100
#define FRAME_INCOMING_BUFFER_COUNT 100

// Uncomment this to hear yourself speak locally (Voice is still encoded & decoded to simulate network transmission)
#define VOICE_DEBUG_LOCAL_PLAYBACK

#include <mutex>
#include <speex/speex.h>
#include <speex/speex_preprocess.h>
#include <portaudio/portaudio.h>
@@ -96,5 +94,5 @@ class CVoiceRecorder
unsigned char m_ucQuality;

std::list<SString> m_EventQueue;
CCriticalSection m_CS;
std::mutex m_Mutex;
};
@@ -56,22 +56,18 @@ void CALLBACK BASS_VoiceStateChange(HSYNC handle, DWORD channel, DWORD data, voi
if (data == 0)
{
CClientPlayerVoice* pVoice = static_cast<CClientPlayerVoice*>(user);
pVoice->m_CS.Lock();
std::lock_guard<std::mutex> lock(pVoice->m_Mutex);

if (pVoice->m_bVoiceActive)
{
pVoice->m_EventQueue.push_back("onClientPlayerVoiceStop");
pVoice->m_bVoiceActive = false;
}

pVoice->m_CS.Unlock();
}
}

void CClientPlayerVoice::Init(void)
{
m_CS.Lock();

// Grab our sample rate and quality
m_SampleRate = m_pVoiceRecorder->GetSampleRate();
unsigned char ucQuality = m_pVoiceRecorder->GetSampleQuality();
@@ -90,8 +86,6 @@ void CClientPlayerVoice::Init(void)
// Initialize our speex decoder
speex_decoder_ctl(m_pSpeexDecoderState, SPEEX_GET_FRAME_SIZE, &m_iSpeexIncomingFrameSampleCount);
speex_decoder_ctl(m_pSpeexDecoderState, SPEEX_SET_QUALITY, &ucQuality);

m_CS.Unlock();
}

void CClientPlayerVoice::DeInit(void)
@@ -112,11 +106,9 @@ void CClientPlayerVoice::DoPulse(void)
// Dispatch queued events
ServiceEventQueue();

m_CS.Lock();
float fPreviousVolume = 0.0f;
g_pCore->GetCVars()->Get("voicevolume", fPreviousVolume);
fPreviousVolume *= g_pCore->GetCVars()->GetValue<float>("mastervolume", 1.0f);
m_CS.Unlock();

if (fPreviousVolume != m_fVolumeScale && m_pPlayer->IsLocalPlayer() == false)
{
@@ -128,18 +120,24 @@ void CClientPlayerVoice::DoPulse(void)

void CClientPlayerVoice::DecodeAndBuffer(char* pBuffer, unsigned int bytesWritten)
{
m_CS.Lock();
CLuaArguments Arguments;
m_Mutex.lock();

if (!m_bVoiceActive)
{
m_CS.Unlock();
m_Mutex.unlock();

ServiceEventQueue();

CLuaArguments Arguments;
if (!m_pPlayer->CallEvent("onClientPlayerVoiceStart", Arguments, true))
return;

m_CS.Lock();
m_bVoiceActive = true;
}
else
{
m_Mutex.unlock();
}

char pTempBuffer[2048];
SpeexBits speexBits;
@@ -153,26 +151,21 @@ void CClientPlayerVoice::DecodeAndBuffer(char* pBuffer, unsigned int bytesWritte
unsigned int uiSpeexBlockSize = m_iSpeexIncomingFrameSampleCount * VOICE_SAMPLE_SIZE;

BASS_StreamPutData(m_pBassPlaybackStream, (void*)pTempBuffer, uiSpeexBlockSize);

m_CS.Unlock();
}

void CClientPlayerVoice::ServiceEventQueue(void)
{
m_CS.Lock();
while (!m_EventQueue.empty())
std::list<SString> eventQueue;
{
SString strEvent = m_EventQueue.front();
m_EventQueue.pop_front();

m_CS.Unlock();
std::lock_guard<std::mutex> lock(m_Mutex);
std::swap(eventQueue, m_EventQueue);
}

for (const SString& strEvent : eventQueue)
{
CLuaArguments Arguments;
m_pPlayer->CallEvent(strEvent, Arguments, true);

m_CS.Lock();
}
m_CS.Unlock();
}

////////////////////////////////////////////////////////////
@@ -16,6 +16,7 @@
#define VOICE_SAMPLE_SIZE 2
*/

#include <mutex>
#include <speex/speex.h>
#include <CClientPlayer.h>
#include <../deathmatch/CVoiceRecorder.h>
@@ -33,7 +34,7 @@ class CClientPlayerVoice
bool m_bVoiceActive;

std::list<SString> m_EventQueue;
CCriticalSection m_CS;
std::mutex m_Mutex; // Only for m_EventQueue and m_bVoiceActive

void GetTempoValues(float& fSampleRate, float& fTempo, float& fPitch, bool& bReverse)
{

5 comments on commit 39c1ba0

@ccw808

This comment has been minimized.

Copy link
Member

replied Oct 25, 2018

Is there a problem with CCriticalSection?

@botder

This comment has been minimized.

Copy link
Member Author

replied Oct 25, 2018

Is there a problem with CCriticalSection?

It is probably not an issue with CCriticalSection, but I am looking for a way to get a clean crash dump for the voice issue.

@qaisjp

This comment has been minimized.

Copy link
Member

replied Apr 28, 2019

according to this SO post std::mutex prevails

image

@botder

This comment has been minimized.

Copy link
Member Author

replied Apr 28, 2019

Nice gravedigging, but also good to know 👌

@Dutchman101

This comment has been minimized.

Copy link
Contributor

replied Apr 29, 2019

Note: for some reason, beyond intent, this change made the current speex voice integration much stabler. Even though it's broken beyond repair, it reduces the amount of crashes/freezes for the lifecycle that speex has left in MTA..

Please sign in to comment.
You can’t perform that action at this time.