You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
8.2 KiB
400 lines
8.2 KiB
//========= Copyright © 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; |
|
} |
|
} |
|
|
|
|