/* * Original work: Copyright (c) 2014, Oculus VR, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * RakNet License.txt file in the licenses directory of this source tree. An additional grant * of patent rights can be found in the RakNet Patents.txt file in the same directory. * * * Modified work: Copyright (c) 2016-2020, SLikeSoft UG (haftungsbeschränkt) * * This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style * license found in the license.txt file in the root directory of this source tree. */ #include "DSoundVoiceAdapter.h" #include "slikenet/assert.h" #include "slikenet/peerinterface.h" #include /// To test sending to myself //#define _TEST_LOOPBACK using namespace SLNet; DSoundVoiceAdapter DSoundVoiceAdapter::instance; DSoundVoiceAdapter::DSoundVoiceAdapter() { m_rakVoice = 0; ds = 0; dsC = 0; dsbIncoming = 0; dsbOutgoing = 0; m_mute = false; memset(incomingBufferNotifications,0,sizeof(incomingBufferNotifications)); memset(outgoingBufferNotifications,0,sizeof(outgoingBufferNotifications)); } DSoundVoiceAdapter* DSoundVoiceAdapter::Instance() { return &instance; } bool DSoundVoiceAdapter::SetupAdapter(RakVoice *rakVoice, HWND hwnd, DWORD dwCoopLevel) { // Check if it was already initialized RakAssert(ds == 0); if (ds != 0) return false; HRESULT hr; if (FAILED(hr = DirectSoundCreate8(nullptr, &ds, nullptr))) { DXTRACE_ERR_MSGBOX(_T("DirectSoundCreate8, when initiliazing DirectSound"), hr); return false; } if (FAILED(hr = ds->SetCooperativeLevel(hwnd, dwCoopLevel))) { DXTRACE_ERR_MSGBOX(_T("IDirectSound8::SetCooperativeLevel"), hr); Release(); return false; } // Check if the rest of the initialization fails, and if so, release any allocated resources if (SetupAdapter(rakVoice)) { return true; } else { Release(); return false; } } bool DSoundVoiceAdapter::SetupAdapter(RakVoice *rakVoice, IDirectSound8 *pDS) { // Check if it was already initialized RakAssert(ds == 0); if (ds != 0) return false; // User provided his own device object, so use it and add another reference pDS->AddRef(); ds = pDS; // Check if the rest of the initialization fails, and if so, release any allocated resources if (SetupAdapter(rakVoice)) { return true; } else { Release(); return false; } } bool DSoundVoiceAdapter::SetupAdapter(RakVoice *rakVoice) { RakAssert(rakVoice); // Make sure rakVoice was initialized RakAssert((rakVoice->IsInitialized())&&(rakVoice->GetRakPeerInterface()!= nullptr)); m_rakVoice = rakVoice; if (!SetupIncomingBuffer()) return false; return SetupOutgoingBuffer(); } bool DSoundVoiceAdapter::SetupIncomingBuffer() { // // // Create the buffer for incoming sound // // WAVEFORMATEX wfx; DSBUFFERDESC dsbdesc; LPDIRECTSOUNDBUFFER pDsb = nullptr; HRESULT hr; // Set up WAV format structure. memset(&wfx, 0, sizeof(WAVEFORMATEX)); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = 1; wfx.nSamplesPerSec = m_rakVoice->GetSampleRate(); wfx.nBlockAlign = 2; wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; wfx.wBitsPerSample = 16; // Set up DSBUFFERDESC structure. memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); dsbdesc.dwSize = sizeof(DSBUFFERDESC); dsbdesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_LOCSOFTWARE; // Create in software, because DX documentation says its the best option when using notifications dsbdesc.dwBufferBytes = m_rakVoice->GetBufferSizeBytes()*FRAMES_IN_SOUND; dsbdesc.lpwfxFormat = &wfx; // Create buffer. if (FAILED(hr = ds->CreateSoundBuffer(&dsbdesc, &pDsb, nullptr))) { DXTRACE_ERR_MSGBOX(_T("IDirectSound8::CreateSoundBuffer, when creating buffer for incoming sound )"), hr); return false; } hr = pDsb->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*) &dsbIncoming); pDsb->Release(); if (FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("IDirectSoundBuffer::QueryInterface, when getting IDirectSoundBuffer8 interface for incoming sound"), hr); return false; } // // Setup the notification events // for (int i=0; iGetBufferSizeBytes(); #if defined(WINDOWS_PHONE_8) if ((incomingBufferNotifications[i].hEventNotify = CreateEventEx(0, 0, CREATE_EVENT_MANUAL_RESET, 0))== nullptr) #else if ((incomingBufferNotifications[i].hEventNotify = CreateEvent(nullptr, TRUE, FALSE, nullptr))== nullptr) #endif { DXTRACE_ERR_MSGBOX(_T("CreateEvent"), GetLastError()); return false; } } IDirectSoundNotify8 *dsbNotify=0; if (FAILED(hr=dsbIncoming->QueryInterface(IID_IDirectSoundNotify8, (LPVOID*) &dsbNotify))) { DXTRACE_ERR_MSGBOX(_T("IDirectSoundBuffer8::QueryInterface, when getting IDirectSoundNotify8 interface for incoming sound"), hr); return false; } hr = dsbNotify->SetNotificationPositions(FRAMES_IN_SOUND, incomingBufferNotifications); dsbNotify->Release(); if (FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("IDirectSoundNotify8::SetNotificationPositions, when setting notifications for incoming sound"), hr); return false; } if (FAILED(hr = dsbIncoming->Play(0,0,DSBPLAY_LOOPING))) { DXTRACE_ERR_MSGBOX(_T("IDirectSoundBuffer8::Play, when starting incoming sound buffer"), hr); return false; } return true; } bool DSoundVoiceAdapter::SetupOutgoingBuffer() { HRESULT hr; // // // Create the buffer for outgoing sound // // if (FAILED(hr=DirectSoundCaptureCreate8(nullptr, &dsC, nullptr))) { DXTRACE_ERR_MSGBOX(_T("DirectSoundCaptureCreate8"), hr); return false; } // Set up WAV format structure. WAVEFORMATEX wfx; memset(&wfx, 0, sizeof(WAVEFORMATEX)); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = 1; wfx.nSamplesPerSec = m_rakVoice->GetSampleRate(); wfx.nBlockAlign = 2; wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; wfx.wBitsPerSample = 16; // Set up DSCBUFFERDESC structure. DSCBUFFERDESC dscbd; memset(&dscbd, 0, sizeof(DSCBUFFERDESC)); dscbd.dwSize = sizeof(DSCBUFFERDESC); dscbd.dwFlags = 0; dscbd.dwBufferBytes = m_rakVoice->GetBufferSizeBytes()*FRAMES_IN_SOUND; dscbd.dwReserved = 0; dscbd.lpwfxFormat = &wfx; dscbd.dwFXCount = 0; dscbd.lpDSCFXDesc = nullptr; // Create capture buffer. LPDIRECTSOUNDCAPTUREBUFFER pDscb = nullptr; if (FAILED(hr = dsC->CreateCaptureBuffer(&dscbd, &pDscb, nullptr))) { DXTRACE_ERR_MSGBOX(_T("IDirectSoundCapture8::CreateCaptureBuffer, when creating buffer for outgoing sound )"), hr); return false; } hr = pDscb->QueryInterface(IID_IDirectSoundCaptureBuffer8, (LPVOID*) &dsbOutgoing); pDscb->Release(); if (FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("IDirectSoundBuffer::QueryInterface, when getting IDirectSoundCaptureBuffer8 interface for outgoing sound"), hr); return false; } // // Setup the notification events // for (int i=0; iGetBufferSizeBytes(); if ((outgoingBufferNotifications[i].hEventNotify = CreateEventEx(0, 0, CREATE_EVENT_MANUAL_RESET, 0))== nullptr) { DXTRACE_ERR_MSGBOX(_T("CreateEvent"), GetLastError()); return false; } } IDirectSoundNotify8 *dsbNotify=0; if (FAILED(hr=dsbOutgoing->QueryInterface(IID_IDirectSoundNotify8, (LPVOID*) &dsbNotify))) { DXTRACE_ERR_MSGBOX(_T("IDirectSoundCaptureBuffer8::QueryInterface, when getting IDirectSoundNotify8 interface for outgoing sound"), hr); return false; } hr = dsbNotify->SetNotificationPositions(FRAMES_IN_SOUND, outgoingBufferNotifications); dsbNotify->Release(); if (FAILED(hr)) { DXTRACE_ERR_MSGBOX(_T("IDirectSoundNotify8::SetNotificationPositions, when setting notifications for outgoing sound"), hr); return false; } if (FAILED(hr = dsbOutgoing->Start(DSCBSTART_LOOPING))) { DXTRACE_ERR_MSGBOX(_T("IDirectSoundCaptureBuffer8::Start, when starting outgoing sound buffer"), hr); return false; } return true; } void DSoundVoiceAdapter::Release() { // Release DirectSound buffer used for incoming voice if (dsbIncoming) { dsbIncoming->Stop(); dsbIncoming->Release(); dsbIncoming = 0; } // Release DirectSound buffer used for outgoing voice if (dsbOutgoing) { dsbOutgoing->Stop(); dsbOutgoing->Release(); dsbOutgoing = 0; } // Release DirectSound device object if (ds) { ds->Release(); ds = 0; } if (dsC) { dsC->Release(); dsC = 0; } // Release the notification events for (int i=0; iLock(dwOffset, m_rakVoice->GetBufferSizeBytes(), &audioPtr, &audioPtrbytes, nullptr, nullptr, 0); // #med - should change GetBufferSizeBytes()-interface to unsigned int return RakAssert(audioPtrbytes==static_cast(m_rakVoice->GetBufferSizeBytes())); if (SUCCEEDED(hr)) { m_rakVoice->ReceiveFrame(audioPtr); dsbIncoming->Unlock(audioPtr, audioPtrbytes, nullptr, 0); } ResetEvent(incomingBufferNotifications[i].hEventNotify); } // // Update outgoing sound // if (WaitForSingleObject(outgoingBufferNotifications[i].hEventNotify, 0)==WAIT_OBJECT_0) { /* If we're set to mute, we don't send anything, and just reset the event */ if (!m_mute) { // The lock offset is the buffer right before the one the event refers to DWORD dwOffset = (i==0) ? outgoingBufferNotifications[FRAMES_IN_SOUND-1].dwOffset : outgoingBufferNotifications[i-1].dwOffset; hr = dsbOutgoing->Lock(dwOffset, m_rakVoice->GetBufferSizeBytes(), &audioPtr, &audioPtrbytes, nullptr, nullptr, 0); // #med - should change GetBufferSizeBytes()-interface to unsigned int return RakAssert(audioPtrbytes == static_cast(m_rakVoice->GetBufferSizeBytes())); if (SUCCEEDED(hr)) { BroadcastFrame(audioPtr); dsbOutgoing->Unlock(audioPtr, audioPtrbytes, nullptr, 0); } } ResetEvent(outgoingBufferNotifications[i].hEventNotify); } } } void DSoundVoiceAdapter::BroadcastFrame(void *ptr) { #ifndef _TEST_LOOPBACK unsigned i; unsigned int numPeers = m_rakVoice->GetRakPeerInterface()->GetMaximumNumberOfPeers(); for (i=0; i < numPeers; i++) { m_rakVoice->SendFrame(m_rakVoice->GetRakPeerInterface()->GetGUIDFromIndex(i), ptr); } #else m_rakVoice->SendFrame(SLNet::UNASSIGNED_SYSTEM_ADDRESS, ptr); #endif } void DSoundVoiceAdapter::SetMute(bool mute) { m_mute = mute; }