source-engine/engine/audio/voice_record_dsound.cpp

401 lines
8.2 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// This module implements the voice record and compression functions
#include "audio_pch.h"
#if !defined( _X360 )
#include "dsound.h"
#endif
#include <assert.h>
#include "voice.h"
#include "tier0/vcrmode.h"
#include "ivoicerecord.h"
#if defined( _X360 )
#include "xbox/xbox_win32stubs.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// ------------------------------------------------------------------------------
// Globals.
// ------------------------------------------------------------------------------
typedef HRESULT (WINAPI *DirectSoundCaptureCreateFn)(const GUID FAR *lpGUID, LPDIRECTSOUNDCAPTURE *pCapture, LPUNKNOWN pUnkOuter);
// ------------------------------------------------------------------------------
// Static helpers
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// VoiceRecord_DSound
// ------------------------------------------------------------------------------
class VoiceRecord_DSound : public IVoiceRecord
{
protected:
virtual ~VoiceRecord_DSound();
// IVoiceRecord.
public:
VoiceRecord_DSound();
virtual void Release();
virtual bool RecordStart();
virtual void RecordStop();
// Initialize. The format of the data we expect from the provider is
// 8-bit signed mono at the specified sample rate.
virtual bool Init(int sampleRate);
virtual void Idle();
// Get the most recent N samples.
virtual int GetRecordedData(short *pOut, int nSamplesWanted);
private:
void Term(); // Delete members.
void Clear(); // Clear members.
void UpdateWrapping();
inline DWORD NumCaptureBufferBytes() {return m_nCaptureBufferBytes;}
private:
HINSTANCE m_hInstDS;
LPDIRECTSOUNDCAPTURE m_pCapture;
LPDIRECTSOUNDCAPTUREBUFFER m_pCaptureBuffer;
// How many bytes our capture buffer has.
DWORD m_nCaptureBufferBytes;
// We need to know when the capture buffer loops, so we install an event and
// update this in the event.
DWORD m_WrapOffset;
HANDLE m_hWrapEvent;
// This is our (unwrapped) position that tells how much data we've given to the app.
DWORD m_LastReadPos;
};
VoiceRecord_DSound::VoiceRecord_DSound()
{
Clear();
}
VoiceRecord_DSound::~VoiceRecord_DSound()
{
Term();
}
void VoiceRecord_DSound::Release()
{
delete this;
}
bool VoiceRecord_DSound::RecordStart()
{
//When we start recording we want to make sure we don't provide any audio
//that occurred before now. So set m_LastReadPos to the current
//read position of the audio device
if (m_pCaptureBuffer == NULL)
{
return false;
}
Idle();
DWORD dwStatus;
HRESULT hr = m_pCaptureBuffer->GetStatus(&dwStatus);
if (FAILED(hr) || !(dwStatus & DSCBSTATUS_CAPTURING))
return false;
DWORD dwReadPos;
hr = m_pCaptureBuffer->GetCurrentPosition(NULL, &dwReadPos);
if (!FAILED(hr))
{
m_LastReadPos = dwReadPos + m_WrapOffset;
}
return true;
}
void VoiceRecord_DSound::RecordStop()
{
}
static bool IsRunningWindows7()
{
if ( IsPC() )
{
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
if ( GetVersionEx ((OSVERSIONINFO *)&osvi) )
{
if ( osvi.dwMajorVersion > 6 || (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion >= 1) )
return true;
}
}
return false;
}
bool VoiceRecord_DSound::Init(int sampleRate)
{
HRESULT hr;
DSCBUFFERDESC dscDesc;
DirectSoundCaptureCreateFn createFn;
Term();
WAVEFORMATEX recordFormat =
{
WAVE_FORMAT_PCM, // wFormatTag
1, // nChannels
(uint32)sampleRate, // nSamplesPerSec
(uint32)sampleRate*2, // nAvgBytesPerSec
2, // nBlockAlign
16, // wBitsPerSample
sizeof(WAVEFORMATEX) // cbSize
};
// Load the DSound DLL.
m_hInstDS = LoadLibrary("dsound.dll");
if(!m_hInstDS)
goto HandleError;
createFn = (DirectSoundCaptureCreateFn)GetProcAddress(m_hInstDS, "DirectSoundCaptureCreate");
if(!createFn)
goto HandleError;
const GUID FAR *pGuid = &DSDEVID_DefaultVoiceCapture;
if ( IsRunningWindows7() )
{
pGuid = NULL;
}
hr = createFn(pGuid, &m_pCapture, NULL);
if(FAILED(hr))
goto HandleError;
// Create the capture buffer.
memset(&dscDesc, 0, sizeof(dscDesc));
dscDesc.dwSize = sizeof(dscDesc);
dscDesc.dwFlags = 0;
dscDesc.dwBufferBytes = recordFormat.nAvgBytesPerSec;
dscDesc.lpwfxFormat = &recordFormat;
hr = m_pCapture->CreateCaptureBuffer(&dscDesc, &m_pCaptureBuffer, NULL);
if(FAILED(hr))
goto HandleError;
// Figure out how many bytes we got in our capture buffer.
DSCBCAPS caps;
memset(&caps, 0, sizeof(caps));
caps.dwSize = sizeof(caps);
hr = m_pCaptureBuffer->GetCaps(&caps);
if(FAILED(hr))
goto HandleError;
m_nCaptureBufferBytes = caps.dwBufferBytes;
// Set it up so we get notification when the buffer wraps.
m_hWrapEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if(!m_hWrapEvent)
goto HandleError;
DSBPOSITIONNOTIFY dsbNotify;
dsbNotify.dwOffset = dscDesc.dwBufferBytes - 1;
dsbNotify.hEventNotify = m_hWrapEvent;
// Get the IDirectSoundNotify interface.
LPDIRECTSOUNDNOTIFY pNotify;
hr = m_pCaptureBuffer->QueryInterface(IID_IDirectSoundNotify, (void**)&pNotify);
if(FAILED(hr))
goto HandleError;
hr = pNotify->SetNotificationPositions(1, &dsbNotify);
pNotify->Release();
if(FAILED(hr))
goto HandleError;
// Start capturing.
hr = m_pCaptureBuffer->Start(DSCBSTART_LOOPING);
if(FAILED(hr))
return false;
return true;
HandleError:;
Term();
return false;
}
void VoiceRecord_DSound::Term()
{
if(m_pCaptureBuffer)
m_pCaptureBuffer->Release();
if(m_pCapture)
m_pCapture->Release();
if(m_hWrapEvent)
DeleteObject(m_hWrapEvent);
if(m_hInstDS)
{
FreeLibrary(m_hInstDS);
m_hInstDS = NULL;
}
Clear();
}
void VoiceRecord_DSound::Clear()
{
m_pCapture = NULL;
m_pCaptureBuffer = NULL;
m_WrapOffset = 0;
m_LastReadPos = 0;
m_hWrapEvent = NULL;
m_hInstDS = NULL;
}
void VoiceRecord_DSound::Idle()
{
UpdateWrapping();
}
int VoiceRecord_DSound::GetRecordedData( short *pOut, int nSamples )
{
if(!m_pCaptureBuffer)
{
assert(false);
return 0;
}
DWORD dwStatus;
HRESULT hr = m_pCaptureBuffer->GetStatus(&dwStatus);
if(FAILED(hr) || !(dwStatus & DSCBSTATUS_CAPTURING))
return 0;
Idle(); // Update wrapping..
DWORD nBytesWanted = (DWORD)( nSamples << 1 );
DWORD dwReadPos;
hr = m_pCaptureBuffer->GetCurrentPosition( NULL, &dwReadPos);
if(FAILED(hr))
return 0;
dwReadPos += m_WrapOffset;
// Read the range (dwReadPos-nSamplesWanted, dwReadPos), but don't re-read data we've already read.
DWORD readStart = Max( dwReadPos - nBytesWanted, (DWORD)0u );
if ( readStart < m_LastReadPos )
{
readStart = m_LastReadPos;
}
// Lock the buffer.
LPVOID pData[2];
DWORD dataLen[2];
hr = m_pCaptureBuffer->Lock(
readStart % NumCaptureBufferBytes(), // Offset.
dwReadPos - readStart, // Number of bytes to lock.
&pData[0], // Buffer 1.
&dataLen[0], // Buffer 1 length.
&pData[1], // Buffer 2.
&dataLen[1], // Buffer 2 length.
0 // Flags.
);
if(FAILED(hr))
return 0;
// Hopefully we didn't get too much data back!
if((dataLen[0]+dataLen[1]) > nBytesWanted )
{
assert(false);
m_pCaptureBuffer->Unlock(pData[0], dataLen[0], pData[1], dataLen[1]);
return 0;
}
// Copy the data to the output.
memcpy(pOut, pData[0], dataLen[0]);
memcpy(&pOut[dataLen[0]/2], pData[1], dataLen[1]);
m_pCaptureBuffer->Unlock(pData[0], dataLen[0], pData[1], dataLen[1]);
// Last Read Position
m_LastReadPos = dwReadPos;
// Return sample count (not bytes)
return (dataLen[0] + dataLen[1]) >> 1;
}
void VoiceRecord_DSound::UpdateWrapping()
{
if(!m_pCaptureBuffer)
return;
// Has the buffer wrapped?
if ( VCRHook_WaitForSingleObject(m_hWrapEvent, 0) == WAIT_OBJECT_0 )
{
m_WrapOffset += m_nCaptureBufferBytes;
}
}
IVoiceRecord* CreateVoiceRecord_DSound(int sampleRate)
{
VoiceRecord_DSound *pRecord = new VoiceRecord_DSound;
if(pRecord && pRecord->Init(sampleRate))
{
return pRecord;
}
else
{
if(pRecord)
pRecord->Release();
return NULL;
}
}