//========= 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 #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; } }