diff options
Diffstat (limited to 'webrtc/modules/audio_device/win/audio_device_wave_win.cc')
-rw-r--r-- | webrtc/modules/audio_device/win/audio_device_wave_win.cc | 3746 |
1 files changed, 3746 insertions, 0 deletions
diff --git a/webrtc/modules/audio_device/win/audio_device_wave_win.cc b/webrtc/modules/audio_device/win/audio_device_wave_win.cc new file mode 100644 index 0000000000..96bee7425a --- /dev/null +++ b/webrtc/modules/audio_device/win/audio_device_wave_win.cc @@ -0,0 +1,3746 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/audio_device/audio_device_config.h" +#include "webrtc/modules/audio_device/win/audio_device_wave_win.h" + +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/system_wrappers/include/tick_util.h" +#include "webrtc/system_wrappers/include/trace.h" + +#include <windows.h> +#include <objbase.h> // CoTaskMemAlloc, CoTaskMemFree +#include <strsafe.h> // StringCchCopy(), StringCchCat(), StringCchPrintf() +#include <assert.h> + +// Avoids the need of Windows 7 SDK +#ifndef WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE +#define WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE 0x0010 +#endif + +// Supported in Windows Vista and Windows 7. +// http://msdn.microsoft.com/en-us/library/dd370819(v=VS.85).aspx +// Taken from Mmddk.h. +#define DRV_RESERVED 0x0800 +#define DRV_QUERYFUNCTIONINSTANCEID (DRV_RESERVED + 17) +#define DRV_QUERYFUNCTIONINSTANCEIDSIZE (DRV_RESERVED + 18) + +#define POW2(A) (2 << ((A) - 1)) + +namespace webrtc { + +// ============================================================================ +// Construction & Destruction +// ============================================================================ + +// ---------------------------------------------------------------------------- +// AudioDeviceWindowsWave - ctor +// ---------------------------------------------------------------------------- + +AudioDeviceWindowsWave::AudioDeviceWindowsWave(const int32_t id) : + _ptrAudioBuffer(NULL), + _critSect(*CriticalSectionWrapper::CreateCriticalSection()), + _timeEvent(*EventTimerWrapper::Create()), + _recStartEvent(*EventWrapper::Create()), + _playStartEvent(*EventWrapper::Create()), + _hGetCaptureVolumeThread(NULL), + _hShutdownGetVolumeEvent(NULL), + _hSetCaptureVolumeThread(NULL), + _hShutdownSetVolumeEvent(NULL), + _hSetCaptureVolumeEvent(NULL), + _critSectCb(*CriticalSectionWrapper::CreateCriticalSection()), + _id(id), + _mixerManager(id), + _usingInputDeviceIndex(false), + _usingOutputDeviceIndex(false), + _inputDevice(AudioDeviceModule::kDefaultDevice), + _outputDevice(AudioDeviceModule::kDefaultDevice), + _inputDeviceIndex(0), + _outputDeviceIndex(0), + _inputDeviceIsSpecified(false), + _outputDeviceIsSpecified(false), + _initialized(false), + _recIsInitialized(false), + _playIsInitialized(false), + _recording(false), + _playing(false), + _startRec(false), + _stopRec(false), + _startPlay(false), + _stopPlay(false), + _AGC(false), + _hWaveIn(NULL), + _hWaveOut(NULL), + _recChannels(N_REC_CHANNELS), + _playChannels(N_PLAY_CHANNELS), + _recBufCount(0), + _recPutBackDelay(0), + _recDelayCount(0), + _playBufCount(0), + _prevPlayTime(0), + _prevRecTime(0), + _prevTimerCheckTime(0), + _timesdwBytes(0), + _timerFaults(0), + _timerRestartAttempts(0), + _no_of_msecleft_warnings(0), + _MAX_minBuffer(65), + _useHeader(0), + _dTcheckPlayBufDelay(10), + _playBufDelay(80), + _playBufDelayFixed(80), + _minPlayBufDelay(20), + _avgCPULoad(0), + _sndCardPlayDelay(0), + _sndCardRecDelay(0), + _plSampOld(0), + _rcSampOld(0), + _playBufType(AudioDeviceModule::kAdaptiveBufferSize), + _recordedBytes(0), + _playWarning(0), + _playError(0), + _recWarning(0), + _recError(0), + _newMicLevel(0), + _minMicVolume(0), + _maxMicVolume(0) +{ + WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, id, "%s created", __FUNCTION__); + + // Initialize value, set to 0 if it fails + if (!QueryPerformanceFrequency(&_perfFreq)) + { + _perfFreq.QuadPart = 0; + } + + _hShutdownGetVolumeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + _hShutdownSetVolumeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + _hSetCaptureVolumeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); +} + +// ---------------------------------------------------------------------------- +// AudioDeviceWindowsWave - dtor +// ---------------------------------------------------------------------------- + +AudioDeviceWindowsWave::~AudioDeviceWindowsWave() +{ + WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s destroyed", __FUNCTION__); + + Terminate(); + + delete &_recStartEvent; + delete &_playStartEvent; + delete &_timeEvent; + delete &_critSect; + delete &_critSectCb; + + if (NULL != _hShutdownGetVolumeEvent) + { + CloseHandle(_hShutdownGetVolumeEvent); + _hShutdownGetVolumeEvent = NULL; + } + + if (NULL != _hShutdownSetVolumeEvent) + { + CloseHandle(_hShutdownSetVolumeEvent); + _hShutdownSetVolumeEvent = NULL; + } + + if (NULL != _hSetCaptureVolumeEvent) + { + CloseHandle(_hSetCaptureVolumeEvent); + _hSetCaptureVolumeEvent = NULL; + } +} + +// ============================================================================ +// API +// ============================================================================ + +// ---------------------------------------------------------------------------- +// AttachAudioBuffer +// ---------------------------------------------------------------------------- + +void AudioDeviceWindowsWave::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) +{ + + CriticalSectionScoped lock(&_critSect); + + _ptrAudioBuffer = audioBuffer; + + // inform the AudioBuffer about default settings for this implementation + _ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC); + _ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC); + _ptrAudioBuffer->SetRecordingChannels(N_REC_CHANNELS); + _ptrAudioBuffer->SetPlayoutChannels(N_PLAY_CHANNELS); +} + +// ---------------------------------------------------------------------------- +// ActiveAudioLayer +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::ActiveAudioLayer(AudioDeviceModule::AudioLayer& audioLayer) const +{ + audioLayer = AudioDeviceModule::kWindowsWaveAudio; + return 0; +} + +// ---------------------------------------------------------------------------- +// Init +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::Init() +{ + + CriticalSectionScoped lock(&_critSect); + + if (_initialized) + { + return 0; + } + + const uint32_t nowTime(TickTime::MillisecondTimestamp()); + + _recordedBytes = 0; + _prevRecByteCheckTime = nowTime; + _prevRecTime = nowTime; + _prevPlayTime = nowTime; + _prevTimerCheckTime = nowTime; + + _playWarning = 0; + _playError = 0; + _recWarning = 0; + _recError = 0; + + _mixerManager.EnumerateAll(); + + if (_ptrThread) + { + // thread is already created and active + return 0; + } + + const char* threadName = "webrtc_audio_module_thread"; + _ptrThread = ThreadWrapper::CreateThread(ThreadFunc, this, threadName); + if (!_ptrThread->Start()) + { + WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, + "failed to start the audio thread"); + _ptrThread.reset(); + return -1; + } + _ptrThread->SetPriority(kRealtimePriority); + + const bool periodic(true); + if (!_timeEvent.StartTimer(periodic, TIMER_PERIOD_MS)) + { + WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, + "failed to start the timer event"); + _ptrThread->Stop(); + _ptrThread.reset(); + return -1; + } + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, + "periodic timer (dT=%d) is now active", TIMER_PERIOD_MS); + + _hGetCaptureVolumeThread = CreateThread(NULL, + 0, + GetCaptureVolumeThread, + this, + 0, + NULL); + if (_hGetCaptureVolumeThread == NULL) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, + " failed to create the volume getter thread"); + return -1; + } + + SetThreadPriority(_hGetCaptureVolumeThread, THREAD_PRIORITY_NORMAL); + + _hSetCaptureVolumeThread = CreateThread(NULL, + 0, + SetCaptureVolumeThread, + this, + 0, + NULL); + if (_hSetCaptureVolumeThread == NULL) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, + " failed to create the volume setter thread"); + return -1; + } + + SetThreadPriority(_hSetCaptureVolumeThread, THREAD_PRIORITY_NORMAL); + + _initialized = true; + + return 0; +} + +// ---------------------------------------------------------------------------- +// Terminate +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::Terminate() +{ + + if (!_initialized) + { + return 0; + } + + _critSect.Enter(); + + _mixerManager.Close(); + + if (_ptrThread) + { + ThreadWrapper* tmpThread = _ptrThread.release(); + _critSect.Leave(); + + _timeEvent.Set(); + + tmpThread->Stop(); + delete tmpThread; + } + else + { + _critSect.Leave(); + } + + _critSect.Enter(); + SetEvent(_hShutdownGetVolumeEvent); + _critSect.Leave(); + int32_t ret = WaitForSingleObject(_hGetCaptureVolumeThread, 2000); + if (ret != WAIT_OBJECT_0) + { + // the thread did not stop as it should + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, + " failed to close down volume getter thread"); + CloseHandle(_hGetCaptureVolumeThread); + _hGetCaptureVolumeThread = NULL; + return -1; + } + _critSect.Enter(); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, + " volume getter thread is now closed"); + + SetEvent(_hShutdownSetVolumeEvent); + _critSect.Leave(); + ret = WaitForSingleObject(_hSetCaptureVolumeThread, 2000); + if (ret != WAIT_OBJECT_0) + { + // the thread did not stop as it should + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, + " failed to close down volume setter thread"); + CloseHandle(_hSetCaptureVolumeThread); + _hSetCaptureVolumeThread = NULL; + return -1; + } + _critSect.Enter(); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, + " volume setter thread is now closed"); + + CloseHandle(_hGetCaptureVolumeThread); + _hGetCaptureVolumeThread = NULL; + + CloseHandle(_hSetCaptureVolumeThread); + _hSetCaptureVolumeThread = NULL; + + _critSect.Leave(); + + _timeEvent.StopTimer(); + + _initialized = false; + _outputDeviceIsSpecified = false; + _inputDeviceIsSpecified = false; + + return 0; +} + + +DWORD WINAPI AudioDeviceWindowsWave::GetCaptureVolumeThread(LPVOID context) +{ + return(((AudioDeviceWindowsWave*)context)->DoGetCaptureVolumeThread()); +} + +DWORD WINAPI AudioDeviceWindowsWave::SetCaptureVolumeThread(LPVOID context) +{ + return(((AudioDeviceWindowsWave*)context)->DoSetCaptureVolumeThread()); +} + +DWORD AudioDeviceWindowsWave::DoGetCaptureVolumeThread() +{ + HANDLE waitObject = _hShutdownGetVolumeEvent; + + while (1) + { + DWORD waitResult = WaitForSingleObject(waitObject, + GET_MIC_VOLUME_INTERVAL_MS); + switch (waitResult) + { + case WAIT_OBJECT_0: // _hShutdownGetVolumeEvent + return 0; + case WAIT_TIMEOUT: // timeout notification + break; + default: // unexpected error + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, + " unknown wait termination on get volume thread"); + return 1; + } + + if (AGC()) + { + uint32_t currentMicLevel = 0; + if (MicrophoneVolume(currentMicLevel) == 0) + { + // This doesn't set the system volume, just stores it. + _critSect.Enter(); + if (_ptrAudioBuffer) + { + _ptrAudioBuffer->SetCurrentMicLevel(currentMicLevel); + } + _critSect.Leave(); + } + } + } +} + +DWORD AudioDeviceWindowsWave::DoSetCaptureVolumeThread() +{ + HANDLE waitArray[2] = {_hShutdownSetVolumeEvent, _hSetCaptureVolumeEvent}; + + while (1) + { + DWORD waitResult = WaitForMultipleObjects(2, waitArray, FALSE, INFINITE); + switch (waitResult) + { + case WAIT_OBJECT_0: // _hShutdownSetVolumeEvent + return 0; + case WAIT_OBJECT_0 + 1: // _hSetCaptureVolumeEvent + break; + default: // unexpected error + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, + " unknown wait termination on set volume thread"); + return 1; + } + + _critSect.Enter(); + uint32_t newMicLevel = _newMicLevel; + _critSect.Leave(); + + if (SetMicrophoneVolume(newMicLevel) == -1) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, + " the required modification of the microphone volume failed"); + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// Initialized +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::Initialized() const +{ + return (_initialized); +} + +// ---------------------------------------------------------------------------- +// InitSpeaker +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::InitSpeaker() +{ + + CriticalSectionScoped lock(&_critSect); + + if (_playing) + { + return -1; + } + + if (_mixerManager.EnumerateSpeakers() == -1) + { + // failed to locate any valid/controllable speaker + return -1; + } + + if (IsUsingOutputDeviceIndex()) + { + if (_mixerManager.OpenSpeaker(OutputDeviceIndex()) == -1) + { + return -1; + } + } + else + { + if (_mixerManager.OpenSpeaker(OutputDevice()) == -1) + { + return -1; + } + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// InitMicrophone +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::InitMicrophone() +{ + + CriticalSectionScoped lock(&_critSect); + + if (_recording) + { + return -1; + } + + if (_mixerManager.EnumerateMicrophones() == -1) + { + // failed to locate any valid/controllable microphone + return -1; + } + + if (IsUsingInputDeviceIndex()) + { + if (_mixerManager.OpenMicrophone(InputDeviceIndex()) == -1) + { + return -1; + } + } + else + { + if (_mixerManager.OpenMicrophone(InputDevice()) == -1) + { + return -1; + } + } + + uint32_t maxVol = 0; + if (_mixerManager.MaxMicrophoneVolume(maxVol) == -1) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, + " unable to retrieve max microphone volume"); + } + _maxMicVolume = maxVol; + + uint32_t minVol = 0; + if (_mixerManager.MinMicrophoneVolume(minVol) == -1) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, + " unable to retrieve min microphone volume"); + } + _minMicVolume = minVol; + + return 0; +} + +// ---------------------------------------------------------------------------- +// SpeakerIsInitialized +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::SpeakerIsInitialized() const +{ + return (_mixerManager.SpeakerIsInitialized()); +} + +// ---------------------------------------------------------------------------- +// MicrophoneIsInitialized +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::MicrophoneIsInitialized() const +{ + return (_mixerManager.MicrophoneIsInitialized()); +} + +// ---------------------------------------------------------------------------- +// SpeakerVolumeIsAvailable +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SpeakerVolumeIsAvailable(bool& available) +{ + + bool isAvailable(false); + + // Enumerate all avaliable speakers and make an attempt to open up the + // output mixer corresponding to the currently selected output device. + // + if (InitSpeaker() == -1) + { + // failed to find a valid speaker + available = false; + return 0; + } + + // Check if the selected speaker has a volume control + // + _mixerManager.SpeakerVolumeIsAvailable(isAvailable); + available = isAvailable; + + // Close the initialized output mixer + // + _mixerManager.CloseSpeaker(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetSpeakerVolume +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetSpeakerVolume(uint32_t volume) +{ + + return (_mixerManager.SetSpeakerVolume(volume)); +} + +// ---------------------------------------------------------------------------- +// SpeakerVolume +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SpeakerVolume(uint32_t& volume) const +{ + + uint32_t level(0); + + if (_mixerManager.SpeakerVolume(level) == -1) + { + return -1; + } + + volume = level; + return 0; +} + +// ---------------------------------------------------------------------------- +// SetWaveOutVolume +// +// The low-order word contains the left-channel volume setting, and the +// high-order word contains the right-channel setting. +// A value of 0xFFFF represents full volume, and a value of 0x0000 is silence. +// +// If a device does not support both left and right volume control, +// the low-order word of dwVolume specifies the volume level, +// and the high-order word is ignored. +// +// Most devices do not support the full 16 bits of volume-level control +// and will not use the least-significant bits of the requested volume setting. +// For example, if a device supports 4 bits of volume control, the values +// 0x4000, 0x4FFF, and 0x43BE will all be truncated to 0x4000. +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetWaveOutVolume(uint16_t volumeLeft, uint16_t volumeRight) +{ + + MMRESULT res(0); + WAVEOUTCAPS caps; + + CriticalSectionScoped lock(&_critSect); + + if (_hWaveOut == NULL) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "no open playout device exists => using default"); + } + + // To determine whether the device supports volume control on both + // the left and right channels, use the WAVECAPS_LRVOLUME flag. + // + res = waveOutGetDevCaps((UINT_PTR)_hWaveOut, &caps, sizeof(WAVEOUTCAPS)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutGetDevCaps() failed (err=%d)", res); + TraceWaveOutError(res); + } + if (!(caps.dwSupport & WAVECAPS_VOLUME)) + { + // this device does not support volume control using the waveOutSetVolume API + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "device does not support volume control using the Wave API"); + return -1; + } + if (!(caps.dwSupport & WAVECAPS_LRVOLUME)) + { + // high-order word (right channel) is ignored + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "device does not support volume control on both channels"); + } + + DWORD dwVolume(0x00000000); + dwVolume = (DWORD)(((volumeRight & 0xFFFF) << 16) | (volumeLeft & 0xFFFF)); + + res = waveOutSetVolume(_hWaveOut, dwVolume); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "waveOutSetVolume() failed (err=%d)", res); + TraceWaveOutError(res); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// WaveOutVolume +// +// The low-order word of this location contains the left-channel volume setting, +// and the high-order word contains the right-channel setting. +// A value of 0xFFFF (65535) represents full volume, and a value of 0x0000 +// is silence. +// +// If a device does not support both left and right volume control, +// the low-order word of the specified location contains the mono volume level. +// +// The full 16-bit setting(s) set with the waveOutSetVolume function is returned, +// regardless of whether the device supports the full 16 bits of volume-level +// control. +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::WaveOutVolume(uint16_t& volumeLeft, uint16_t& volumeRight) const +{ + + MMRESULT res(0); + WAVEOUTCAPS caps; + + CriticalSectionScoped lock(&_critSect); + + if (_hWaveOut == NULL) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "no open playout device exists => using default"); + } + + // To determine whether the device supports volume control on both + // the left and right channels, use the WAVECAPS_LRVOLUME flag. + // + res = waveOutGetDevCaps((UINT_PTR)_hWaveOut, &caps, sizeof(WAVEOUTCAPS)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutGetDevCaps() failed (err=%d)", res); + TraceWaveOutError(res); + } + if (!(caps.dwSupport & WAVECAPS_VOLUME)) + { + // this device does not support volume control using the waveOutSetVolume API + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "device does not support volume control using the Wave API"); + return -1; + } + if (!(caps.dwSupport & WAVECAPS_LRVOLUME)) + { + // high-order word (right channel) is ignored + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "device does not support volume control on both channels"); + } + + DWORD dwVolume(0x00000000); + + res = waveOutGetVolume(_hWaveOut, &dwVolume); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "waveOutGetVolume() failed (err=%d)", res); + TraceWaveOutError(res); + return -1; + } + + WORD wVolumeLeft = LOWORD(dwVolume); + WORD wVolumeRight = HIWORD(dwVolume); + + volumeLeft = static_cast<uint16_t> (wVolumeLeft); + volumeRight = static_cast<uint16_t> (wVolumeRight); + + return 0; +} + +// ---------------------------------------------------------------------------- +// MaxSpeakerVolume +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MaxSpeakerVolume(uint32_t& maxVolume) const +{ + + uint32_t maxVol(0); + + if (_mixerManager.MaxSpeakerVolume(maxVol) == -1) + { + return -1; + } + + maxVolume = maxVol; + return 0; +} + +// ---------------------------------------------------------------------------- +// MinSpeakerVolume +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MinSpeakerVolume(uint32_t& minVolume) const +{ + + uint32_t minVol(0); + + if (_mixerManager.MinSpeakerVolume(minVol) == -1) + { + return -1; + } + + minVolume = minVol; + return 0; +} + +// ---------------------------------------------------------------------------- +// SpeakerVolumeStepSize +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SpeakerVolumeStepSize(uint16_t& stepSize) const +{ + + uint16_t delta(0); + + if (_mixerManager.SpeakerVolumeStepSize(delta) == -1) + { + return -1; + } + + stepSize = delta; + return 0; +} + +// ---------------------------------------------------------------------------- +// SpeakerMuteIsAvailable +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SpeakerMuteIsAvailable(bool& available) +{ + + bool isAvailable(false); + + // Enumerate all avaliable speakers and make an attempt to open up the + // output mixer corresponding to the currently selected output device. + // + if (InitSpeaker() == -1) + { + // If we end up here it means that the selected speaker has no volume + // control, hence it is safe to state that there is no mute control + // already at this stage. + available = false; + return 0; + } + + // Check if the selected speaker has a mute control + // + _mixerManager.SpeakerMuteIsAvailable(isAvailable); + available = isAvailable; + + // Close the initialized output mixer + // + _mixerManager.CloseSpeaker(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetSpeakerMute +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetSpeakerMute(bool enable) +{ + return (_mixerManager.SetSpeakerMute(enable)); +} + +// ---------------------------------------------------------------------------- +// SpeakerMute +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SpeakerMute(bool& enabled) const +{ + + bool muted(0); + + if (_mixerManager.SpeakerMute(muted) == -1) + { + return -1; + } + + enabled = muted; + return 0; +} + +// ---------------------------------------------------------------------------- +// MicrophoneMuteIsAvailable +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MicrophoneMuteIsAvailable(bool& available) +{ + + bool isAvailable(false); + + // Enumerate all avaliable microphones and make an attempt to open up the + // input mixer corresponding to the currently selected input device. + // + if (InitMicrophone() == -1) + { + // If we end up here it means that the selected microphone has no volume + // control, hence it is safe to state that there is no boost control + // already at this stage. + available = false; + return 0; + } + + // Check if the selected microphone has a mute control + // + _mixerManager.MicrophoneMuteIsAvailable(isAvailable); + available = isAvailable; + + // Close the initialized input mixer + // + _mixerManager.CloseMicrophone(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetMicrophoneMute +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetMicrophoneMute(bool enable) +{ + return (_mixerManager.SetMicrophoneMute(enable)); +} + +// ---------------------------------------------------------------------------- +// MicrophoneMute +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MicrophoneMute(bool& enabled) const +{ + + bool muted(0); + + if (_mixerManager.MicrophoneMute(muted) == -1) + { + return -1; + } + + enabled = muted; + return 0; +} + +// ---------------------------------------------------------------------------- +// MicrophoneBoostIsAvailable +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MicrophoneBoostIsAvailable(bool& available) +{ + + bool isAvailable(false); + + // Enumerate all avaliable microphones and make an attempt to open up the + // input mixer corresponding to the currently selected input device. + // + if (InitMicrophone() == -1) + { + // If we end up here it means that the selected microphone has no volume + // control, hence it is safe to state that there is no boost control + // already at this stage. + available = false; + return 0; + } + + // Check if the selected microphone has a boost control + // + _mixerManager.MicrophoneBoostIsAvailable(isAvailable); + available = isAvailable; + + // Close the initialized input mixer + // + _mixerManager.CloseMicrophone(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetMicrophoneBoost +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetMicrophoneBoost(bool enable) +{ + + return (_mixerManager.SetMicrophoneBoost(enable)); +} + +// ---------------------------------------------------------------------------- +// MicrophoneBoost +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MicrophoneBoost(bool& enabled) const +{ + + bool onOff(0); + + if (_mixerManager.MicrophoneBoost(onOff) == -1) + { + return -1; + } + + enabled = onOff; + return 0; +} + +// ---------------------------------------------------------------------------- +// StereoRecordingIsAvailable +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::StereoRecordingIsAvailable(bool& available) +{ + available = true; + return 0; +} + +// ---------------------------------------------------------------------------- +// SetStereoRecording +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetStereoRecording(bool enable) +{ + + if (enable) + _recChannels = 2; + else + _recChannels = 1; + + return 0; +} + +// ---------------------------------------------------------------------------- +// StereoRecording +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::StereoRecording(bool& enabled) const +{ + + if (_recChannels == 2) + enabled = true; + else + enabled = false; + + return 0; +} + +// ---------------------------------------------------------------------------- +// StereoPlayoutIsAvailable +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::StereoPlayoutIsAvailable(bool& available) +{ + available = true; + return 0; +} + +// ---------------------------------------------------------------------------- +// SetStereoPlayout +// +// Specifies the number of output channels. +// +// NOTE - the setting will only have an effect after InitPlayout has +// been called. +// +// 16-bit mono: +// +// Each sample is 2 bytes. Sample 1 is followed by samples 2, 3, 4, and so on. +// For each sample, the first byte is the low-order byte of channel 0 and the +// second byte is the high-order byte of channel 0. +// +// 16-bit stereo: +// +// Each sample is 4 bytes. Sample 1 is followed by samples 2, 3, 4, and so on. +// For each sample, the first byte is the low-order byte of channel 0 (left channel); +// the second byte is the high-order byte of channel 0; the third byte is the +// low-order byte of channel 1 (right channel); and the fourth byte is the +// high-order byte of channel 1. +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetStereoPlayout(bool enable) +{ + + if (enable) + _playChannels = 2; + else + _playChannels = 1; + + return 0; +} + +// ---------------------------------------------------------------------------- +// StereoPlayout +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::StereoPlayout(bool& enabled) const +{ + + if (_playChannels == 2) + enabled = true; + else + enabled = false; + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetAGC +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetAGC(bool enable) +{ + + _AGC = enable; + + return 0; +} + +// ---------------------------------------------------------------------------- +// AGC +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::AGC() const +{ + return _AGC; +} + +// ---------------------------------------------------------------------------- +// MicrophoneVolumeIsAvailable +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MicrophoneVolumeIsAvailable(bool& available) +{ + + bool isAvailable(false); + + // Enumerate all avaliable microphones and make an attempt to open up the + // input mixer corresponding to the currently selected output device. + // + if (InitMicrophone() == -1) + { + // Failed to find valid microphone + available = false; + return 0; + } + + // Check if the selected microphone has a volume control + // + _mixerManager.MicrophoneVolumeIsAvailable(isAvailable); + available = isAvailable; + + // Close the initialized input mixer + // + _mixerManager.CloseMicrophone(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetMicrophoneVolume +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetMicrophoneVolume(uint32_t volume) +{ + return (_mixerManager.SetMicrophoneVolume(volume)); +} + +// ---------------------------------------------------------------------------- +// MicrophoneVolume +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MicrophoneVolume(uint32_t& volume) const +{ + uint32_t level(0); + + if (_mixerManager.MicrophoneVolume(level) == -1) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "failed to retrive current microphone level"); + return -1; + } + + volume = level; + return 0; +} + +// ---------------------------------------------------------------------------- +// MaxMicrophoneVolume +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MaxMicrophoneVolume(uint32_t& maxVolume) const +{ + // _maxMicVolume can be zero in AudioMixerManager::MaxMicrophoneVolume(): + // (1) API GetLineControl() returns failure at querying the max Mic level. + // (2) API GetLineControl() returns maxVolume as zero in rare cases. + // Both cases show we don't have access to the mixer controls. + // We return -1 here to indicate that. + if (_maxMicVolume == 0) + { + return -1; + } + + maxVolume = _maxMicVolume;; + return 0; +} + +// ---------------------------------------------------------------------------- +// MinMicrophoneVolume +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MinMicrophoneVolume(uint32_t& minVolume) const +{ + minVolume = _minMicVolume; + return 0; +} + +// ---------------------------------------------------------------------------- +// MicrophoneVolumeStepSize +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MicrophoneVolumeStepSize(uint16_t& stepSize) const +{ + + uint16_t delta(0); + + if (_mixerManager.MicrophoneVolumeStepSize(delta) == -1) + { + return -1; + } + + stepSize = delta; + return 0; +} + +// ---------------------------------------------------------------------------- +// PlayoutDevices +// ---------------------------------------------------------------------------- + +int16_t AudioDeviceWindowsWave::PlayoutDevices() +{ + + return (waveOutGetNumDevs()); +} + +// ---------------------------------------------------------------------------- +// SetPlayoutDevice I (II) +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetPlayoutDevice(uint16_t index) +{ + + if (_playIsInitialized) + { + return -1; + } + + UINT nDevices = waveOutGetNumDevs(); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "number of availiable waveform-audio output devices is %u", nDevices); + + if (index < 0 || index > (nDevices-1)) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "device index is out of range [0,%u]", (nDevices-1)); + return -1; + } + + _usingOutputDeviceIndex = true; + _outputDeviceIndex = index; + _outputDeviceIsSpecified = true; + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetPlayoutDevice II (II) +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetPlayoutDevice(AudioDeviceModule::WindowsDeviceType device) +{ + if (_playIsInitialized) + { + return -1; + } + + if (device == AudioDeviceModule::kDefaultDevice) + { + } + else if (device == AudioDeviceModule::kDefaultCommunicationDevice) + { + } + + _usingOutputDeviceIndex = false; + _outputDevice = device; + _outputDeviceIsSpecified = true; + + return 0; +} + +// ---------------------------------------------------------------------------- +// PlayoutDeviceName +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::PlayoutDeviceName( + uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) +{ + + uint16_t nDevices(PlayoutDevices()); + + // Special fix for the case when the user asks for the name of the default device. + // + if (index == (uint16_t)(-1)) + { + index = 0; + } + + if ((index > (nDevices-1)) || (name == NULL)) + { + return -1; + } + + memset(name, 0, kAdmMaxDeviceNameSize); + + if (guid != NULL) + { + memset(guid, 0, kAdmMaxGuidSize); + } + + WAVEOUTCAPSW caps; // szPname member (product name (NULL terminated) is a WCHAR + MMRESULT res; + + res = waveOutGetDevCapsW(index, &caps, sizeof(WAVEOUTCAPSW)); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutGetDevCapsW() failed (err=%d)", res); + return -1; + } + if (WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, name, kAdmMaxDeviceNameSize, NULL, NULL) == 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WideCharToMultiByte(CP_UTF8) failed with error code %d - 1", GetLastError()); + } + + if (guid == NULL) + { + return 0; + } + + // It is possible to get the unique endpoint ID string using the Wave API. + // However, it is only supported on Windows Vista and Windows 7. + + size_t cbEndpointId(0); + + // Get the size (including the terminating null) of the endpoint ID string of the waveOut device. + // Windows Vista supports the DRV_QUERYFUNCTIONINSTANCEIDSIZE and DRV_QUERYFUNCTIONINSTANCEID messages. + res = waveOutMessage((HWAVEOUT)IntToPtr(index), + DRV_QUERYFUNCTIONINSTANCEIDSIZE, + (DWORD_PTR)&cbEndpointId, NULL); + if (res != MMSYSERR_NOERROR) + { + // DRV_QUERYFUNCTIONINSTANCEIDSIZE is not supported <=> earlier version of Windows than Vista + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "waveOutMessage(DRV_QUERYFUNCTIONINSTANCEIDSIZE) failed (err=%d)", res); + TraceWaveOutError(res); + // Best we can do is to copy the friendly name and use it as guid + if (WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, guid, kAdmMaxGuidSize, NULL, NULL) == 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WideCharToMultiByte(CP_UTF8) failed with error code %d - 2", GetLastError()); + } + return 0; + } + + // waveOutMessage(DRV_QUERYFUNCTIONINSTANCEIDSIZE) worked => we are on a Vista or Windows 7 device + + WCHAR *pstrEndpointId = NULL; + pstrEndpointId = (WCHAR*)CoTaskMemAlloc(cbEndpointId); + + // Get the endpoint ID string for this waveOut device. + res = waveOutMessage((HWAVEOUT)IntToPtr(index), + DRV_QUERYFUNCTIONINSTANCEID, + (DWORD_PTR)pstrEndpointId, + cbEndpointId); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "waveOutMessage(DRV_QUERYFUNCTIONINSTANCEID) failed (err=%d)", res); + TraceWaveOutError(res); + // Best we can do is to copy the friendly name and use it as guid + if (WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, guid, kAdmMaxGuidSize, NULL, NULL) == 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WideCharToMultiByte(CP_UTF8) failed with error code %d - 3", GetLastError()); + } + CoTaskMemFree(pstrEndpointId); + return 0; + } + + if (WideCharToMultiByte(CP_UTF8, 0, pstrEndpointId, -1, guid, kAdmMaxGuidSize, NULL, NULL) == 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WideCharToMultiByte(CP_UTF8) failed with error code %d - 4", GetLastError()); + } + CoTaskMemFree(pstrEndpointId); + + return 0; +} + +// ---------------------------------------------------------------------------- +// RecordingDeviceName +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::RecordingDeviceName( + uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) +{ + + uint16_t nDevices(RecordingDevices()); + + // Special fix for the case when the user asks for the name of the default device. + // + if (index == (uint16_t)(-1)) + { + index = 0; + } + + if ((index > (nDevices-1)) || (name == NULL)) + { + return -1; + } + + memset(name, 0, kAdmMaxDeviceNameSize); + + if (guid != NULL) + { + memset(guid, 0, kAdmMaxGuidSize); + } + + WAVEINCAPSW caps; // szPname member (product name (NULL terminated) is a WCHAR + MMRESULT res; + + res = waveInGetDevCapsW(index, &caps, sizeof(WAVEINCAPSW)); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInGetDevCapsW() failed (err=%d)", res); + return -1; + } + if (WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, name, kAdmMaxDeviceNameSize, NULL, NULL) == 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WideCharToMultiByte(CP_UTF8) failed with error code %d - 1", GetLastError()); + } + + if (guid == NULL) + { + return 0; + } + + // It is possible to get the unique endpoint ID string using the Wave API. + // However, it is only supported on Windows Vista and Windows 7. + + size_t cbEndpointId(0); + + // Get the size (including the terminating null) of the endpoint ID string of the waveOut device. + // Windows Vista supports the DRV_QUERYFUNCTIONINSTANCEIDSIZE and DRV_QUERYFUNCTIONINSTANCEID messages. + res = waveInMessage((HWAVEIN)IntToPtr(index), + DRV_QUERYFUNCTIONINSTANCEIDSIZE, + (DWORD_PTR)&cbEndpointId, NULL); + if (res != MMSYSERR_NOERROR) + { + // DRV_QUERYFUNCTIONINSTANCEIDSIZE is not supported <=> earlier version of Windows than Vista + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "waveInMessage(DRV_QUERYFUNCTIONINSTANCEIDSIZE) failed (err=%d)", res); + TraceWaveInError(res); + // Best we can do is to copy the friendly name and use it as guid + if (WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, guid, kAdmMaxGuidSize, NULL, NULL) == 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WideCharToMultiByte(CP_UTF8) failed with error code %d - 2", GetLastError()); + } + return 0; + } + + // waveOutMessage(DRV_QUERYFUNCTIONINSTANCEIDSIZE) worked => we are on a Vista or Windows 7 device + + WCHAR *pstrEndpointId = NULL; + pstrEndpointId = (WCHAR*)CoTaskMemAlloc(cbEndpointId); + + // Get the endpoint ID string for this waveOut device. + res = waveInMessage((HWAVEIN)IntToPtr(index), + DRV_QUERYFUNCTIONINSTANCEID, + (DWORD_PTR)pstrEndpointId, + cbEndpointId); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "waveInMessage(DRV_QUERYFUNCTIONINSTANCEID) failed (err=%d)", res); + TraceWaveInError(res); + // Best we can do is to copy the friendly name and use it as guid + if (WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, guid, kAdmMaxGuidSize, NULL, NULL) == 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WideCharToMultiByte(CP_UTF8) failed with error code %d - 3", GetLastError()); + } + CoTaskMemFree(pstrEndpointId); + return 0; + } + + if (WideCharToMultiByte(CP_UTF8, 0, pstrEndpointId, -1, guid, kAdmMaxGuidSize, NULL, NULL) == 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WideCharToMultiByte(CP_UTF8) failed with error code %d - 4", GetLastError()); + } + CoTaskMemFree(pstrEndpointId); + + return 0; +} + +// ---------------------------------------------------------------------------- +// RecordingDevices +// ---------------------------------------------------------------------------- + +int16_t AudioDeviceWindowsWave::RecordingDevices() +{ + + return (waveInGetNumDevs()); +} + +// ---------------------------------------------------------------------------- +// SetRecordingDevice I (II) +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetRecordingDevice(uint16_t index) +{ + + if (_recIsInitialized) + { + return -1; + } + + UINT nDevices = waveInGetNumDevs(); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "number of availiable waveform-audio input devices is %u", nDevices); + + if (index < 0 || index > (nDevices-1)) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "device index is out of range [0,%u]", (nDevices-1)); + return -1; + } + + _usingInputDeviceIndex = true; + _inputDeviceIndex = index; + _inputDeviceIsSpecified = true; + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetRecordingDevice II (II) +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetRecordingDevice(AudioDeviceModule::WindowsDeviceType device) +{ + if (device == AudioDeviceModule::kDefaultDevice) + { + } + else if (device == AudioDeviceModule::kDefaultCommunicationDevice) + { + } + + if (_recIsInitialized) + { + return -1; + } + + _usingInputDeviceIndex = false; + _inputDevice = device; + _inputDeviceIsSpecified = true; + + return 0; +} + +// ---------------------------------------------------------------------------- +// PlayoutIsAvailable +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::PlayoutIsAvailable(bool& available) +{ + + available = false; + + // Try to initialize the playout side + int32_t res = InitPlayout(); + + // Cancel effect of initialization + StopPlayout(); + + if (res != -1) + { + available = true; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// RecordingIsAvailable +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::RecordingIsAvailable(bool& available) +{ + + available = false; + + // Try to initialize the recording side + int32_t res = InitRecording(); + + // Cancel effect of initialization + StopRecording(); + + if (res != -1) + { + available = true; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// InitPlayout +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::InitPlayout() +{ + + CriticalSectionScoped lock(&_critSect); + + if (_playing) + { + return -1; + } + + if (!_outputDeviceIsSpecified) + { + return -1; + } + + if (_playIsInitialized) + { + return 0; + } + + // Initialize the speaker (devices might have been added or removed) + if (InitSpeaker() == -1) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "InitSpeaker() failed"); + } + + // Enumerate all availiable output devices + EnumeratePlayoutDevices(); + + // Start by closing any existing wave-output devices + // + MMRESULT res(MMSYSERR_ERROR); + + if (_hWaveOut != NULL) + { + res = waveOutClose(_hWaveOut); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutClose() failed (err=%d)", res); + TraceWaveOutError(res); + } + } + + // Set the output wave format + // + WAVEFORMATEX waveFormat; + + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nChannels = _playChannels; // mono <=> 1, stereo <=> 2 + waveFormat.nSamplesPerSec = N_PLAY_SAMPLES_PER_SEC; + waveFormat.wBitsPerSample = 16; + waveFormat.nBlockAlign = waveFormat.nChannels * (waveFormat.wBitsPerSample/8); + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + // Open the given waveform-audio output device for playout + // + HWAVEOUT hWaveOut(NULL); + + if (IsUsingOutputDeviceIndex()) + { + // verify settings first + res = waveOutOpen(NULL, _outputDeviceIndex, &waveFormat, 0, 0, CALLBACK_NULL | WAVE_FORMAT_QUERY); + if (MMSYSERR_NOERROR == res) + { + // open the given waveform-audio output device for recording + res = waveOutOpen(&hWaveOut, _outputDeviceIndex, &waveFormat, 0, 0, CALLBACK_NULL); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "opening output device corresponding to device ID %u", _outputDeviceIndex); + } + } + else + { + if (_outputDevice == AudioDeviceModule::kDefaultCommunicationDevice) + { + // check if it is possible to open the default communication device (supported on Windows 7) + res = waveOutOpen(NULL, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE | WAVE_FORMAT_QUERY); + if (MMSYSERR_NOERROR == res) + { + // if so, open the default communication device for real + res = waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "opening default communication device"); + } + else + { + // use default device since default communication device was not avaliable + res = waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "unable to open default communication device => using default instead"); + } + } + else if (_outputDevice == AudioDeviceModule::kDefaultDevice) + { + // open default device since it has been requested + res = waveOutOpen(NULL, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL | WAVE_FORMAT_QUERY); + if (MMSYSERR_NOERROR == res) + { + res = waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "opening default output device"); + } + } + } + + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "waveOutOpen() failed (err=%d)", res); + TraceWaveOutError(res); + return -1; + } + + // Log information about the aquired output device + // + WAVEOUTCAPS caps; + + res = waveOutGetDevCaps((UINT_PTR)hWaveOut, &caps, sizeof(WAVEOUTCAPS)); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutGetDevCaps() failed (err=%d)", res); + TraceWaveOutError(res); + } + + UINT deviceID(0); + res = waveOutGetID(hWaveOut, &deviceID); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutGetID() failed (err=%d)", res); + TraceWaveOutError(res); + } + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "utilized device ID : %u", deviceID); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "product name : %s", caps.szPname); + + // Store valid handle for the open waveform-audio output device + _hWaveOut = hWaveOut; + + // Store the input wave header as well + _waveFormatOut = waveFormat; + + // Prepare wave-out headers + // + const uint8_t bytesPerSample = 2*_playChannels; + + for (int n = 0; n < N_BUFFERS_OUT; n++) + { + // set up the output wave header + _waveHeaderOut[n].lpData = reinterpret_cast<LPSTR>(&_playBuffer[n]); + _waveHeaderOut[n].dwBufferLength = bytesPerSample*PLAY_BUF_SIZE_IN_SAMPLES; + _waveHeaderOut[n].dwFlags = 0; + _waveHeaderOut[n].dwLoops = 0; + + memset(_playBuffer[n], 0, bytesPerSample*PLAY_BUF_SIZE_IN_SAMPLES); + + // The waveOutPrepareHeader function prepares a waveform-audio data block for playback. + // The lpData, dwBufferLength, and dwFlags members of the WAVEHDR structure must be set + // before calling this function. + // + res = waveOutPrepareHeader(_hWaveOut, &_waveHeaderOut[n], sizeof(WAVEHDR)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutPrepareHeader(%d) failed (err=%d)", n, res); + TraceWaveOutError(res); + } + + // perform extra check to ensure that the header is prepared + if (_waveHeaderOut[n].dwFlags != WHDR_PREPARED) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutPrepareHeader(%d) failed (dwFlags != WHDR_PREPARED)", n); + } + } + + // Mark playout side as initialized + _playIsInitialized = true; + + _dTcheckPlayBufDelay = 10; // check playback buffer delay every 10 ms + _playBufCount = 0; // index of active output wave header (<=> output buffer index) + _playBufDelay = 80; // buffer delay/size is initialized to 80 ms and slowly decreased until er < 25 + _minPlayBufDelay = 25; // minimum playout buffer delay + _MAX_minBuffer = 65; // adaptive minimum playout buffer delay cannot be larger than this value + _intro = 1; // Used to make sure that adaption starts after (2000-1700)/100 seconds + _waitCounter = 1700; // Counter for start of adaption of playback buffer + _erZeroCounter = 0; // Log how many times er = 0 in consequtive calls to RecTimeProc + _useHeader = 0; // Counts number of "useHeader" detections. Stops at 2. + + _writtenSamples = 0; + _writtenSamplesOld = 0; + _playedSamplesOld = 0; + _sndCardPlayDelay = 0; + _sndCardRecDelay = 0; + + WEBRTC_TRACE(kTraceInfo, kTraceUtility, _id,"initial playout status: _playBufDelay=%d, _minPlayBufDelay=%d", + _playBufDelay, _minPlayBufDelay); + + return 0; +} + +// ---------------------------------------------------------------------------- +// InitRecording +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::InitRecording() +{ + + CriticalSectionScoped lock(&_critSect); + + if (_recording) + { + return -1; + } + + if (!_inputDeviceIsSpecified) + { + return -1; + } + + if (_recIsInitialized) + { + return 0; + } + + _avgCPULoad = 0; + _playAcc = 0; + + // Initialize the microphone (devices might have been added or removed) + if (InitMicrophone() == -1) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "InitMicrophone() failed"); + } + + // Enumerate all availiable input devices + EnumerateRecordingDevices(); + + // Start by closing any existing wave-input devices + // + MMRESULT res(MMSYSERR_ERROR); + + if (_hWaveIn != NULL) + { + res = waveInClose(_hWaveIn); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInClose() failed (err=%d)", res); + TraceWaveInError(res); + } + } + + // Set the input wave format + // + WAVEFORMATEX waveFormat; + + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nChannels = _recChannels; // mono <=> 1, stereo <=> 2 + waveFormat.nSamplesPerSec = N_REC_SAMPLES_PER_SEC; + waveFormat.wBitsPerSample = 16; + waveFormat.nBlockAlign = waveFormat.nChannels * (waveFormat.wBitsPerSample/8); + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + // Open the given waveform-audio input device for recording + // + HWAVEIN hWaveIn(NULL); + + if (IsUsingInputDeviceIndex()) + { + // verify settings first + res = waveInOpen(NULL, _inputDeviceIndex, &waveFormat, 0, 0, CALLBACK_NULL | WAVE_FORMAT_QUERY); + if (MMSYSERR_NOERROR == res) + { + // open the given waveform-audio input device for recording + res = waveInOpen(&hWaveIn, _inputDeviceIndex, &waveFormat, 0, 0, CALLBACK_NULL); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "opening input device corresponding to device ID %u", _inputDeviceIndex); + } + } + else + { + if (_inputDevice == AudioDeviceModule::kDefaultCommunicationDevice) + { + // check if it is possible to open the default communication device (supported on Windows 7) + res = waveInOpen(NULL, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE | WAVE_FORMAT_QUERY); + if (MMSYSERR_NOERROR == res) + { + // if so, open the default communication device for real + res = waveInOpen(&hWaveIn, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "opening default communication device"); + } + else + { + // use default device since default communication device was not avaliable + res = waveInOpen(&hWaveIn, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "unable to open default communication device => using default instead"); + } + } + else if (_inputDevice == AudioDeviceModule::kDefaultDevice) + { + // open default device since it has been requested + res = waveInOpen(NULL, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL | WAVE_FORMAT_QUERY); + if (MMSYSERR_NOERROR == res) + { + res = waveInOpen(&hWaveIn, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "opening default input device"); + } + } + } + + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "waveInOpen() failed (err=%d)", res); + TraceWaveInError(res); + return -1; + } + + // Log information about the aquired input device + // + WAVEINCAPS caps; + + res = waveInGetDevCaps((UINT_PTR)hWaveIn, &caps, sizeof(WAVEINCAPS)); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInGetDevCaps() failed (err=%d)", res); + TraceWaveInError(res); + } + + UINT deviceID(0); + res = waveInGetID(hWaveIn, &deviceID); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInGetID() failed (err=%d)", res); + TraceWaveInError(res); + } + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "utilized device ID : %u", deviceID); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "product name : %s", caps.szPname); + + // Store valid handle for the open waveform-audio input device + _hWaveIn = hWaveIn; + + // Store the input wave header as well + _waveFormatIn = waveFormat; + + // Mark recording side as initialized + _recIsInitialized = true; + + _recBufCount = 0; // index of active input wave header (<=> input buffer index) + _recDelayCount = 0; // ensures that input buffers are returned with certain delay + + return 0; +} + +// ---------------------------------------------------------------------------- +// StartRecording +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::StartRecording() +{ + + if (!_recIsInitialized) + { + return -1; + } + + if (_recording) + { + return 0; + } + + // set state to ensure that the recording starts from the audio thread + _startRec = true; + + // the audio thread will signal when recording has stopped + if (kEventTimeout == _recStartEvent.Wait(10000)) + { + _startRec = false; + StopRecording(); + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "failed to activate recording"); + return -1; + } + + if (_recording) + { + // the recording state is set by the audio thread after recording has started + } + else + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "failed to activate recording"); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// StopRecording +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::StopRecording() +{ + + CriticalSectionScoped lock(&_critSect); + + if (!_recIsInitialized) + { + return 0; + } + + if (_hWaveIn == NULL) + { + return -1; + } + + bool wasRecording = _recording; + _recIsInitialized = false; + _recording = false; + + MMRESULT res; + + // Stop waveform-adio input. If there are any buffers in the queue, the + // current buffer will be marked as done (the dwBytesRecorded member in + // the header will contain the length of data), but any empty buffers in + // the queue will remain there. + // + res = waveInStop(_hWaveIn); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInStop() failed (err=%d)", res); + TraceWaveInError(res); + } + + // Stop input on the given waveform-audio input device and resets the current + // position to zero. All pending buffers are marked as done and returned to + // the application. + // + res = waveInReset(_hWaveIn); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInReset() failed (err=%d)", res); + TraceWaveInError(res); + } + + // Clean up the preparation performed by the waveInPrepareHeader function. + // Only unprepare header if recording was ever started (and headers are prepared). + // + if (wasRecording) + { + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "waveInUnprepareHeader() will be performed"); + for (int n = 0; n < N_BUFFERS_IN; n++) + { + res = waveInUnprepareHeader(_hWaveIn, &_waveHeaderIn[n], sizeof(WAVEHDR)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInUnprepareHeader() failed (err=%d)", res); + TraceWaveInError(res); + } + } + } + + // Close the given waveform-audio input device. + // + res = waveInClose(_hWaveIn); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInClose() failed (err=%d)", res); + TraceWaveInError(res); + } + + // Set the wave input handle to NULL + // + _hWaveIn = NULL; + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "_hWaveIn is now set to NULL"); + + return 0; +} + +// ---------------------------------------------------------------------------- +// RecordingIsInitialized +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::RecordingIsInitialized() const +{ + return (_recIsInitialized); +} + +// ---------------------------------------------------------------------------- +// Recording +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::Recording() const +{ + return (_recording); +} + +// ---------------------------------------------------------------------------- +// PlayoutIsInitialized +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::PlayoutIsInitialized() const +{ + return (_playIsInitialized); +} + +// ---------------------------------------------------------------------------- +// StartPlayout +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::StartPlayout() +{ + + if (!_playIsInitialized) + { + return -1; + } + + if (_playing) + { + return 0; + } + + // set state to ensure that playout starts from the audio thread + _startPlay = true; + + // the audio thread will signal when recording has started + if (kEventTimeout == _playStartEvent.Wait(10000)) + { + _startPlay = false; + StopPlayout(); + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "failed to activate playout"); + return -1; + } + + if (_playing) + { + // the playing state is set by the audio thread after playout has started + } + else + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "failed to activate playing"); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// StopPlayout +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::StopPlayout() +{ + + CriticalSectionScoped lock(&_critSect); + + if (!_playIsInitialized) + { + return 0; + } + + if (_hWaveOut == NULL) + { + return -1; + } + + _playIsInitialized = false; + _playing = false; + _sndCardPlayDelay = 0; + _sndCardRecDelay = 0; + + MMRESULT res; + + // The waveOutReset function stops playback on the given waveform-audio + // output device and resets the current position to zero. All pending + // playback buffers are marked as done (WHDR_DONE) and returned to the application. + // After this function returns, the application can send new playback buffers + // to the device by calling waveOutWrite, or close the device by calling waveOutClose. + // + res = waveOutReset(_hWaveOut); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutReset() failed (err=%d)", res); + TraceWaveOutError(res); + } + + // The waveOutUnprepareHeader function cleans up the preparation performed + // by the waveOutPrepareHeader function. This function must be called after + // the device driver is finished with a data block. + // You must call this function before freeing the buffer. + // + for (int n = 0; n < N_BUFFERS_OUT; n++) + { + res = waveOutUnprepareHeader(_hWaveOut, &_waveHeaderOut[n], sizeof(WAVEHDR)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutUnprepareHeader() failed (err=%d)", res); + TraceWaveOutError(res); + } + } + + // The waveOutClose function closes the given waveform-audio output device. + // The close operation fails if the device is still playing a waveform-audio + // buffer that was previously sent by calling waveOutWrite. Before calling + // waveOutClose, the application must wait for all buffers to finish playing + // or call the waveOutReset function to terminate playback. + // + res = waveOutClose(_hWaveOut); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutClose() failed (err=%d)", res); + TraceWaveOutError(res); + } + + _hWaveOut = NULL; + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "_hWaveOut is now set to NULL"); + + return 0; +} + +// ---------------------------------------------------------------------------- +// PlayoutDelay +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::PlayoutDelay(uint16_t& delayMS) const +{ + CriticalSectionScoped lock(&_critSect); + delayMS = (uint16_t)_sndCardPlayDelay; + return 0; +} + +// ---------------------------------------------------------------------------- +// RecordingDelay +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::RecordingDelay(uint16_t& delayMS) const +{ + CriticalSectionScoped lock(&_critSect); + delayMS = (uint16_t)_sndCardRecDelay; + return 0; +} + +// ---------------------------------------------------------------------------- +// Playing +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::Playing() const +{ + return (_playing); +} +// ---------------------------------------------------------------------------- +// SetPlayoutBuffer +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::SetPlayoutBuffer(const AudioDeviceModule::BufferType type, uint16_t sizeMS) +{ + CriticalSectionScoped lock(&_critSect); + _playBufType = type; + if (type == AudioDeviceModule::kFixedBufferSize) + { + _playBufDelayFixed = sizeMS; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// PlayoutBuffer +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::PlayoutBuffer(AudioDeviceModule::BufferType& type, uint16_t& sizeMS) const +{ + CriticalSectionScoped lock(&_critSect); + type = _playBufType; + if (type == AudioDeviceModule::kFixedBufferSize) + { + sizeMS = _playBufDelayFixed; + } + else + { + sizeMS = _playBufDelay; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// CPULoad +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::CPULoad(uint16_t& load) const +{ + + load = static_cast<uint16_t>(100*_avgCPULoad); + + return 0; +} + +// ---------------------------------------------------------------------------- +// PlayoutWarning +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::PlayoutWarning() const +{ + return ( _playWarning > 0); +} + +// ---------------------------------------------------------------------------- +// PlayoutError +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::PlayoutError() const +{ + return ( _playError > 0); +} + +// ---------------------------------------------------------------------------- +// RecordingWarning +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::RecordingWarning() const +{ + return ( _recWarning > 0); +} + +// ---------------------------------------------------------------------------- +// RecordingError +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::RecordingError() const +{ + return ( _recError > 0); +} + +// ---------------------------------------------------------------------------- +// ClearPlayoutWarning +// ---------------------------------------------------------------------------- + +void AudioDeviceWindowsWave::ClearPlayoutWarning() +{ + _playWarning = 0; +} + +// ---------------------------------------------------------------------------- +// ClearPlayoutError +// ---------------------------------------------------------------------------- + +void AudioDeviceWindowsWave::ClearPlayoutError() +{ + _playError = 0; +} + +// ---------------------------------------------------------------------------- +// ClearRecordingWarning +// ---------------------------------------------------------------------------- + +void AudioDeviceWindowsWave::ClearRecordingWarning() +{ + _recWarning = 0; +} + +// ---------------------------------------------------------------------------- +// ClearRecordingError +// ---------------------------------------------------------------------------- + +void AudioDeviceWindowsWave::ClearRecordingError() +{ + _recError = 0; +} + +// ============================================================================ +// Private Methods +// ============================================================================ + +// ---------------------------------------------------------------------------- +// InputSanityCheckAfterUnlockedPeriod +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::InputSanityCheckAfterUnlockedPeriod() const +{ + if (_hWaveIn == NULL) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "input state has been modified during unlocked period"); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// OutputSanityCheckAfterUnlockedPeriod +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::OutputSanityCheckAfterUnlockedPeriod() const +{ + if (_hWaveOut == NULL) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "output state has been modified during unlocked period"); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// EnumeratePlayoutDevices +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::EnumeratePlayoutDevices() +{ + + uint16_t nDevices(PlayoutDevices()); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "==============================================================="); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "#output devices: %u", nDevices); + + WAVEOUTCAPS caps; + MMRESULT res; + + for (UINT deviceID = 0; deviceID < nDevices; deviceID++) + { + res = waveOutGetDevCaps(deviceID, &caps, sizeof(WAVEOUTCAPS)); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutGetDevCaps() failed (err=%d)", res); + } + + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "==============================================================="); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "Device ID %u:", deviceID); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "manufacturer ID : %u", caps.wMid); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "product ID : %u",caps.wPid); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "version of driver : %u.%u", HIBYTE(caps.vDriverVersion), LOBYTE(caps.vDriverVersion)); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "product name : %s", caps.szPname); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "dwFormats : 0x%x", caps.dwFormats); + if (caps.dwFormats & WAVE_FORMAT_48S16) + { + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " 48kHz,stereo,16bit : SUPPORTED"); + } + else + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 48kHz,stereo,16bit : *NOT* SUPPORTED"); + } + if (caps.dwFormats & WAVE_FORMAT_48M16) + { + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " 48kHz,mono,16bit : SUPPORTED"); + } + else + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 48kHz,mono,16bit : *NOT* SUPPORTED"); + } + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "wChannels : %u", caps.wChannels); + TraceSupportFlags(caps.dwSupport); + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// EnumerateRecordingDevices +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::EnumerateRecordingDevices() +{ + + uint16_t nDevices(RecordingDevices()); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "==============================================================="); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "#input devices: %u", nDevices); + + WAVEINCAPS caps; + MMRESULT res; + + for (UINT deviceID = 0; deviceID < nDevices; deviceID++) + { + res = waveInGetDevCaps(deviceID, &caps, sizeof(WAVEINCAPS)); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInGetDevCaps() failed (err=%d)", res); + } + + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "==============================================================="); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "Device ID %u:", deviceID); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "manufacturer ID : %u", caps.wMid); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "product ID : %u",caps.wPid); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "version of driver : %u.%u", HIBYTE(caps.vDriverVersion), LOBYTE(caps.vDriverVersion)); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "product name : %s", caps.szPname); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "dwFormats : 0x%x", caps.dwFormats); + if (caps.dwFormats & WAVE_FORMAT_48S16) + { + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " 48kHz,stereo,16bit : SUPPORTED"); + } + else + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 48kHz,stereo,16bit : *NOT* SUPPORTED"); + } + if (caps.dwFormats & WAVE_FORMAT_48M16) + { + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " 48kHz,mono,16bit : SUPPORTED"); + } + else + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 48kHz,mono,16bit : *NOT* SUPPORTED"); + } + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "wChannels : %u", caps.wChannels); + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// TraceSupportFlags +// ---------------------------------------------------------------------------- + +void AudioDeviceWindowsWave::TraceSupportFlags(DWORD dwSupport) const +{ + TCHAR buf[256]; + + StringCchPrintf(buf, 128, TEXT("support flags : 0x%x "), dwSupport); + + if (dwSupport & WAVECAPS_PITCH) + { + // supports pitch control + StringCchCat(buf, 256, TEXT("(PITCH)")); + } + if (dwSupport & WAVECAPS_PLAYBACKRATE) + { + // supports playback rate control + StringCchCat(buf, 256, TEXT("(PLAYBACKRATE)")); + } + if (dwSupport & WAVECAPS_VOLUME) + { + // supports volume control + StringCchCat(buf, 256, TEXT("(VOLUME)")); + } + if (dwSupport & WAVECAPS_LRVOLUME) + { + // supports separate left and right volume control + StringCchCat(buf, 256, TEXT("(LRVOLUME)")); + } + if (dwSupport & WAVECAPS_SYNC) + { + // the driver is synchronous and will block while playing a buffer + StringCchCat(buf, 256, TEXT("(SYNC)")); + } + if (dwSupport & WAVECAPS_SAMPLEACCURATE) + { + // returns sample-accurate position information + StringCchCat(buf, 256, TEXT("(SAMPLEACCURATE)")); + } + + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%S", buf); +} + +// ---------------------------------------------------------------------------- +// TraceWaveInError +// ---------------------------------------------------------------------------- + +void AudioDeviceWindowsWave::TraceWaveInError(MMRESULT error) const +{ + TCHAR buf[MAXERRORLENGTH]; + TCHAR msg[MAXERRORLENGTH]; + + StringCchPrintf(buf, MAXERRORLENGTH, TEXT("Error details: ")); + waveInGetErrorText(error, msg, MAXERRORLENGTH); + StringCchCat(buf, MAXERRORLENGTH, msg); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%S", buf); +} + +// ---------------------------------------------------------------------------- +// TraceWaveOutError +// ---------------------------------------------------------------------------- + +void AudioDeviceWindowsWave::TraceWaveOutError(MMRESULT error) const +{ + TCHAR buf[MAXERRORLENGTH]; + TCHAR msg[MAXERRORLENGTH]; + + StringCchPrintf(buf, MAXERRORLENGTH, TEXT("Error details: ")); + waveOutGetErrorText(error, msg, MAXERRORLENGTH); + StringCchCat(buf, MAXERRORLENGTH, msg); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%S", buf); +} + +// ---------------------------------------------------------------------------- +// PrepareStartPlayout +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::PrepareStartPlayout() +{ + + CriticalSectionScoped lock(&_critSect); + + if (_hWaveOut == NULL) + { + return -1; + } + + // A total of 30ms of data is immediately placed in the SC buffer + // + int8_t zeroVec[4*PLAY_BUF_SIZE_IN_SAMPLES]; // max allocation + memset(zeroVec, 0, 4*PLAY_BUF_SIZE_IN_SAMPLES); + + { + Write(zeroVec, PLAY_BUF_SIZE_IN_SAMPLES); + Write(zeroVec, PLAY_BUF_SIZE_IN_SAMPLES); + Write(zeroVec, PLAY_BUF_SIZE_IN_SAMPLES); + } + + _playAcc = 0; + _playWarning = 0; + _playError = 0; + _dc_diff_mean = 0; + _dc_y_prev = 0; + _dc_penalty_counter = 20; + _dc_prevtime = 0; + _dc_prevplay = 0; + + return 0; +} + +// ---------------------------------------------------------------------------- +// PrepareStartRecording +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::PrepareStartRecording() +{ + + CriticalSectionScoped lock(&_critSect); + + if (_hWaveIn == NULL) + { + return -1; + } + + _playAcc = 0; + _recordedBytes = 0; + _recPutBackDelay = REC_PUT_BACK_DELAY; + + MMRESULT res; + MMTIME mmtime; + mmtime.wType = TIME_SAMPLES; + + res = waveInGetPosition(_hWaveIn, &mmtime, sizeof(mmtime)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInGetPosition(TIME_SAMPLES) failed (err=%d)", res); + TraceWaveInError(res); + } + + _read_samples = mmtime.u.sample; + _read_samples_old = _read_samples; + _rec_samples_old = mmtime.u.sample; + _wrapCounter = 0; + + for (int n = 0; n < N_BUFFERS_IN; n++) + { + const uint8_t nBytesPerSample = 2*_recChannels; + + // set up the input wave header + _waveHeaderIn[n].lpData = reinterpret_cast<LPSTR>(&_recBuffer[n]); + _waveHeaderIn[n].dwBufferLength = nBytesPerSample * REC_BUF_SIZE_IN_SAMPLES; + _waveHeaderIn[n].dwFlags = 0; + _waveHeaderIn[n].dwBytesRecorded = 0; + _waveHeaderIn[n].dwUser = 0; + + memset(_recBuffer[n], 0, nBytesPerSample * REC_BUF_SIZE_IN_SAMPLES); + + // prepare a buffer for waveform-audio input + res = waveInPrepareHeader(_hWaveIn, &_waveHeaderIn[n], sizeof(WAVEHDR)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInPrepareHeader(%d) failed (err=%d)", n, res); + TraceWaveInError(res); + } + + // send an input buffer to the given waveform-audio input device + res = waveInAddBuffer(_hWaveIn, &_waveHeaderIn[n], sizeof(WAVEHDR)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInAddBuffer(%d) failed (err=%d)", n, res); + TraceWaveInError(res); + } + } + + // start input on the given waveform-audio input device + res = waveInStart(_hWaveIn); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInStart() failed (err=%d)", res); + TraceWaveInError(res); + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// GetPlayoutBufferDelay +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::GetPlayoutBufferDelay(uint32_t& writtenSamples, uint32_t& playedSamples) +{ + int i; + int ms_Header; + long playedDifference; + int msecInPlayoutBuffer(0); // #milliseconds of audio in the playout buffer + + const uint16_t nSamplesPerMs = (uint16_t)(N_PLAY_SAMPLES_PER_SEC/1000); // default is 48000/1000 = 48 + + MMRESULT res; + MMTIME mmtime; + + if (!_playing) + { + playedSamples = 0; + return (0); + } + + // Retrieve the current playback position. + // + mmtime.wType = TIME_SAMPLES; // number of waveform-audio samples + res = waveOutGetPosition(_hWaveOut, &mmtime, sizeof(mmtime)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveOutGetPosition() failed (err=%d)", res); + TraceWaveOutError(res); + } + + writtenSamples = _writtenSamples; // #samples written to the playout buffer + playedSamples = mmtime.u.sample; // current playout position in the playout buffer + + // derive remaining amount (in ms) of data in the playout buffer + msecInPlayoutBuffer = ((writtenSamples - playedSamples)/nSamplesPerMs); + + playedDifference = (long) (_playedSamplesOld - playedSamples); + + if (playedDifference > 64000) + { + // If the sound cards number-of-played-out-samples variable wraps around before + // written_sampels wraps around this needs to be adjusted. This can happen on + // sound cards that uses less than 32 bits to keep track of number of played out + // sampels. To avoid being fooled by sound cards that sometimes produces false + // output we compare old value minus the new value with a large value. This is + // neccessary because some SC:s produce an output like 153, 198, 175, 230 which + // would trigger the wrap-around function if we didn't compare with a large value. + // The value 64000 is chosen because 2^16=65536 so we allow wrap around at 16 bits. + + i = 31; + while((_playedSamplesOld <= (unsigned long)POW2(i)) && (i > 14)) { + i--; + } + + if((i < 31) && (i > 14)) { + // Avoid adjusting when there is 32-bit wrap-around since that is + // something neccessary. + // + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "msecleft() => wrap around occured: %d bits used by sound card)", (i+1)); + + _writtenSamples = _writtenSamples - POW2(i + 1); + writtenSamples = _writtenSamples; + msecInPlayoutBuffer = ((writtenSamples - playedSamples)/nSamplesPerMs); + } + } + else if ((_writtenSamplesOld > POW2(31)) && (writtenSamples < 96000)) + { + // Wrap around as expected after having used all 32 bits. (But we still + // test if the wrap around happened earlier which it should not) + + i = 31; + while (_writtenSamplesOld <= (unsigned long)POW2(i)) { + i--; + } + + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, " msecleft() (wrap around occured after having used all 32 bits)"); + + _writtenSamplesOld = writtenSamples; + _playedSamplesOld = playedSamples; + msecInPlayoutBuffer = (int)((writtenSamples + POW2(i + 1) - playedSamples)/nSamplesPerMs); + + } + else if ((writtenSamples < 96000) && (playedSamples > POW2(31))) + { + // Wrap around has, as expected, happened for written_sampels before + // playedSampels so we have to adjust for this until also playedSampels + // has had wrap around. + + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, " msecleft() (wrap around occured: correction of output is done)"); + + _writtenSamplesOld = writtenSamples; + _playedSamplesOld = playedSamples; + msecInPlayoutBuffer = (int)((writtenSamples + POW2(32) - playedSamples)/nSamplesPerMs); + } + + _writtenSamplesOld = writtenSamples; + _playedSamplesOld = playedSamples; + + + // We use the following formaula to track that playout works as it should + // y=playedSamples/48 - timeGetTime(); + // y represent the clock drift between system clock and sound card clock - should be fairly stable + // When the exponential mean value of diff(y) goes away from zero something is wrong + // The exponential formula will accept 1% clock drift but not more + // The driver error means that we will play to little audio and have a high negative clock drift + // We kick in our alternative method when the clock drift reaches 20% + + int diff,y; + int unsigned time =0; + + // If we have other problems that causes playout glitches + // we don't want to switch playout method. + // Check if playout buffer is extremely low, or if we haven't been able to + // exectue our code in more than 40 ms + + time = timeGetTime(); + + if ((msecInPlayoutBuffer < 20) || (time - _dc_prevtime > 40)) + { + _dc_penalty_counter = 100; + } + + if ((playedSamples != 0)) + { + y = playedSamples/48 - time; + if ((_dc_y_prev != 0) && (_dc_penalty_counter == 0)) + { + diff = y - _dc_y_prev; + _dc_diff_mean = (990*_dc_diff_mean)/1000 + 10*diff; + } + _dc_y_prev = y; + } + + if (_dc_penalty_counter) + { + _dc_penalty_counter--; + } + + if (_dc_diff_mean < -200) + { + // Always reset the filter + _dc_diff_mean = 0; + + // Problem is detected. Switch delay method and set min buffer to 80. + // Reset the filter and keep monitoring the filter output. + // If issue is detected a second time, increase min buffer to 100. + // If that does not help, we must modify this scheme further. + + _useHeader++; + if (_useHeader == 1) + { + _minPlayBufDelay = 80; + _playWarning = 1; // only warn first time + WEBRTC_TRACE(kTraceInfo, kTraceUtility, -1, "Modification #1: _useHeader = %d, _minPlayBufDelay = %d", _useHeader, _minPlayBufDelay); + } + else if (_useHeader == 2) + { + _minPlayBufDelay = 100; // add some more safety + WEBRTC_TRACE(kTraceInfo, kTraceUtility, -1, "Modification #2: _useHeader = %d, _minPlayBufDelay = %d", _useHeader, _minPlayBufDelay); + } + else + { + // This state should not be entered... (HA) + WEBRTC_TRACE (kTraceWarning, kTraceUtility, -1, "further actions are required!"); + } + if (_playWarning == 1) + { + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, "pending playout warning exists"); + } + _playWarning = 1; // triggers callback from module process thread + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, "kPlayoutWarning message posted: switching to alternative playout delay method"); + } + _dc_prevtime = time; + _dc_prevplay = playedSamples; + + // Try a very rough method of looking at how many buffers are still playing + ms_Header = 0; + for (i = 0; i < N_BUFFERS_OUT; i++) { + if ((_waveHeaderOut[i].dwFlags & WHDR_INQUEUE)!=0) { + ms_Header += 10; + } + } + + if ((ms_Header-50) > msecInPlayoutBuffer) { + // Test for cases when GetPosition appears to be screwed up (currently just log....) + TCHAR infoStr[300]; + if (_no_of_msecleft_warnings%20==0) + { + StringCchPrintf(infoStr, 300, TEXT("writtenSamples=%i, playedSamples=%i, msecInPlayoutBuffer=%i, ms_Header=%i"), writtenSamples, playedSamples, msecInPlayoutBuffer, ms_Header); + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, "%S", infoStr); + } + _no_of_msecleft_warnings++; + } + + // If this is true we have had a problem with the playout + if (_useHeader > 0) + { + return (ms_Header); + } + + + if (ms_Header < msecInPlayoutBuffer) + { + if (_no_of_msecleft_warnings % 100 == 0) + { + TCHAR str[300]; + StringCchPrintf(str, 300, TEXT("_no_of_msecleft_warnings=%i, msecInPlayoutBuffer=%i ms_Header=%i (minBuffer=%i buffersize=%i writtenSamples=%i playedSamples=%i)"), + _no_of_msecleft_warnings, msecInPlayoutBuffer, ms_Header, _minPlayBufDelay, _playBufDelay, writtenSamples, playedSamples); + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, "%S", str); + } + _no_of_msecleft_warnings++; + ms_Header -= 6; // Round off as we only have 10ms resolution + Header info is usually slightly delayed compared to GetPosition + + if (ms_Header < 0) + ms_Header = 0; + + return (ms_Header); + } + else + { + return (msecInPlayoutBuffer); + } +} + +// ---------------------------------------------------------------------------- +// GetRecordingBufferDelay +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::GetRecordingBufferDelay(uint32_t& readSamples, uint32_t& recSamples) +{ + long recDifference; + MMTIME mmtime; + MMRESULT mmr; + + const uint16_t nSamplesPerMs = (uint16_t)(N_REC_SAMPLES_PER_SEC/1000); // default is 48000/1000 = 48 + + // Retrieve the current input position of the given waveform-audio input device + // + mmtime.wType = TIME_SAMPLES; + mmr = waveInGetPosition(_hWaveIn, &mmtime, sizeof(mmtime)); + if (MMSYSERR_NOERROR != mmr) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInGetPosition() failed (err=%d)", mmr); + TraceWaveInError(mmr); + } + + readSamples = _read_samples; // updated for each full fram in RecProc() + recSamples = mmtime.u.sample; // remaining time in input queue (recorded but not read yet) + + recDifference = (long) (_rec_samples_old - recSamples); + + if( recDifference > 64000) { + WEBRTC_TRACE (kTraceDebug, kTraceUtility, -1,"WRAP 1 (recDifference =%d)", recDifference); + // If the sound cards number-of-recorded-samples variable wraps around before + // read_sampels wraps around this needs to be adjusted. This can happen on + // sound cards that uses less than 32 bits to keep track of number of played out + // sampels. To avoid being fooled by sound cards that sometimes produces false + // output we compare old value minus the new value with a large value. This is + // neccessary because some SC:s produce an output like 153, 198, 175, 230 which + // would trigger the wrap-around function if we didn't compare with a large value. + // The value 64000 is chosen because 2^16=65536 so we allow wrap around at 16 bits. + // + int i = 31; + while((_rec_samples_old <= (unsigned long)POW2(i)) && (i > 14)) + i--; + + if((i < 31) && (i > 14)) { + // Avoid adjusting when there is 32-bit wrap-around since that is + // somethying neccessary. + // + _read_samples = _read_samples - POW2(i + 1); + readSamples = _read_samples; + _wrapCounter++; + } else { + WEBRTC_TRACE (kTraceWarning, kTraceUtility, -1,"AEC (_rec_samples_old %d recSamples %d)",_rec_samples_old, recSamples); + } + } + + if((_wrapCounter>200)){ + // Do nothing, handled later + } + else if((_rec_samples_old > POW2(31)) && (recSamples < 96000)) { + WEBRTC_TRACE (kTraceDebug, kTraceUtility, -1,"WRAP 2 (_rec_samples_old %d recSamples %d)",_rec_samples_old, recSamples); + // Wrap around as expected after having used all 32 bits. + _read_samples_old = readSamples; + _rec_samples_old = recSamples; + _wrapCounter++; + return (int)((recSamples + POW2(32) - readSamples)/nSamplesPerMs); + + + } else if((recSamples < 96000) && (readSamples > POW2(31))) { + WEBRTC_TRACE (kTraceDebug, kTraceUtility, -1,"WRAP 3 (readSamples %d recSamples %d)",readSamples, recSamples); + // Wrap around has, as expected, happened for rec_sampels before + // readSampels so we have to adjust for this until also readSampels + // has had wrap around. + _read_samples_old = readSamples; + _rec_samples_old = recSamples; + _wrapCounter++; + return (int)((recSamples + POW2(32) - readSamples)/nSamplesPerMs); + } + + _read_samples_old = _read_samples; + _rec_samples_old = recSamples; + int res=(((int)_rec_samples_old - (int)_read_samples_old)/nSamplesPerMs); + + if((res > 2000)||(res < 0)||(_wrapCounter>200)){ + // Reset everything + WEBRTC_TRACE (kTraceWarning, kTraceUtility, -1,"msec_read error (res %d wrapCounter %d)",res, _wrapCounter); + MMTIME mmtime; + mmtime.wType = TIME_SAMPLES; + + mmr=waveInGetPosition(_hWaveIn, &mmtime, sizeof(mmtime)); + if (mmr != MMSYSERR_NOERROR) { + WEBRTC_TRACE (kTraceWarning, kTraceUtility, -1, "waveInGetPosition failed (mmr=%d)", mmr); + } + _read_samples=mmtime.u.sample; + _read_samples_old=_read_samples; + _rec_samples_old=mmtime.u.sample; + + // Guess a decent value + res = 20; + } + + _wrapCounter = 0; + return res; +} + +// ============================================================================ +// Thread Methods +// ============================================================================ + +// ---------------------------------------------------------------------------- +// ThreadFunc +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::ThreadFunc(void* pThis) +{ + return (static_cast<AudioDeviceWindowsWave*>(pThis)->ThreadProcess()); +} + +// ---------------------------------------------------------------------------- +// ThreadProcess +// ---------------------------------------------------------------------------- + +bool AudioDeviceWindowsWave::ThreadProcess() +{ + uint32_t time(0); + uint32_t playDiff(0); + uint32_t recDiff(0); + + LONGLONG playTime(0); + LONGLONG recTime(0); + + switch (_timeEvent.Wait(1000)) + { + case kEventSignaled: + break; + case kEventError: + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "EventWrapper::Wait() failed => restarting timer"); + _timeEvent.StopTimer(); + _timeEvent.StartTimer(true, TIMER_PERIOD_MS); + return true; + case kEventTimeout: + return true; + } + + time = TickTime::MillisecondTimestamp(); + + if (_startPlay) + { + if (PrepareStartPlayout() == 0) + { + _prevTimerCheckTime = time; + _prevPlayTime = time; + _startPlay = false; + _playing = true; + _playStartEvent.Set(); + } + } + + if (_startRec) + { + if (PrepareStartRecording() == 0) + { + _prevTimerCheckTime = time; + _prevRecTime = time; + _prevRecByteCheckTime = time; + _startRec = false; + _recording = true; + _recStartEvent.Set(); + } + } + + if (_playing) + { + playDiff = time - _prevPlayTime; + } + + if (_recording) + { + recDiff = time - _prevRecTime; + } + + if (_playing || _recording) + { + RestartTimerIfNeeded(time); + } + + if (_playing && + (playDiff > (uint32_t)(_dTcheckPlayBufDelay - 1)) || + (playDiff < 0)) + { + Lock(); + if (_playing) + { + if (PlayProc(playTime) == -1) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "PlayProc() failed"); + } + _prevPlayTime = time; + if (playTime != 0) + _playAcc += playTime; + } + UnLock(); + } + + if (_playing && (playDiff > 12)) + { + // It has been a long time since we were able to play out, try to + // compensate by calling PlayProc again. + // + Lock(); + if (_playing) + { + if (PlayProc(playTime)) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "PlayProc() failed"); + } + _prevPlayTime = time; + if (playTime != 0) + _playAcc += playTime; + } + UnLock(); + } + + if (_recording && + (recDiff > REC_CHECK_TIME_PERIOD_MS) || + (recDiff < 0)) + { + Lock(); + if (_recording) + { + int32_t nRecordedBytes(0); + uint16_t maxIter(10); + + // Deliver all availiable recorded buffers and update the CPU load measurement. + // We use a while loop here to compensate for the fact that the multi-media timer + // can sometimed enter a "bad state" after hibernation where the resolution is + // reduced from ~1ms to ~10-15 ms. + // + while ((nRecordedBytes = RecProc(recTime)) > 0) + { + maxIter--; + _recordedBytes += nRecordedBytes; + if (recTime && _perfFreq.QuadPart) + { + // Measure the average CPU load: + // This is a simplified expression where an exponential filter is used: + // _avgCPULoad = 0.99 * _avgCPULoad + 0.01 * newCPU, + // newCPU = (recTime+playAcc)/f is time in seconds + // newCPU / 0.01 is the fraction of a 10 ms period + // The two 0.01 cancels each other. + // NOTE - assumes 10ms audio buffers. + // + _avgCPULoad = (float)(_avgCPULoad*.99 + (recTime+_playAcc)/(double)(_perfFreq.QuadPart)); + _playAcc = 0; + } + if (maxIter == 0) + { + // If we get this message ofte, our compensation scheme is not sufficient. + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "failed to compensate for reduced MM-timer resolution"); + } + } + + if (nRecordedBytes == -1) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "RecProc() failed"); + } + + _prevRecTime = time; + + // Monitor the recording process and generate error/warning callbacks if needed + MonitorRecording(time); + } + UnLock(); + } + + if (!_recording) + { + _prevRecByteCheckTime = time; + _avgCPULoad = 0; + } + + return true; +} + +// ---------------------------------------------------------------------------- +// RecProc +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::RecProc(LONGLONG& consumedTime) +{ + MMRESULT res; + uint32_t bufCount(0); + uint32_t nBytesRecorded(0); + + consumedTime = 0; + + // count modulo N_BUFFERS_IN (0,1,2,...,(N_BUFFERS_IN-1),0,1,2,..) + if (_recBufCount == N_BUFFERS_IN) + { + _recBufCount = 0; + } + + bufCount = _recBufCount; + + // take mono/stereo mode into account when deriving size of a full buffer + const uint16_t bytesPerSample = 2*_recChannels; + const uint32_t fullBufferSizeInBytes = bytesPerSample * REC_BUF_SIZE_IN_SAMPLES; + + // read number of recorded bytes for the given input-buffer + nBytesRecorded = _waveHeaderIn[bufCount].dwBytesRecorded; + + if (nBytesRecorded == fullBufferSizeInBytes || + (nBytesRecorded > 0)) + { + int32_t msecOnPlaySide; + int32_t msecOnRecordSide; + uint32_t writtenSamples; + uint32_t playedSamples; + uint32_t readSamples, recSamples; + bool send = true; + + uint32_t nSamplesRecorded = (nBytesRecorded/bytesPerSample); // divide by 2 or 4 depending on mono or stereo + + if (nBytesRecorded == fullBufferSizeInBytes) + { + _timesdwBytes = 0; + } + else + { + // Test if it is stuck on this buffer + _timesdwBytes++; + if (_timesdwBytes < 5) + { + // keep trying + return (0); + } + else + { + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id,"nBytesRecorded=%d => don't use", nBytesRecorded); + _timesdwBytes = 0; + send = false; + } + } + + // store the recorded buffer (no action will be taken if the #recorded samples is not a full buffer) + _ptrAudioBuffer->SetRecordedBuffer(_waveHeaderIn[bufCount].lpData, nSamplesRecorded); + + // update #samples read + _read_samples += nSamplesRecorded; + + // Check how large the playout and recording buffers are on the sound card. + // This info is needed by the AEC. + // + msecOnPlaySide = GetPlayoutBufferDelay(writtenSamples, playedSamples); + msecOnRecordSide = GetRecordingBufferDelay(readSamples, recSamples); + + // If we use the alternative playout delay method, skip the clock drift compensation + // since it will be an unreliable estimate and might degrade AEC performance. + int32_t drift = (_useHeader > 0) ? 0 : GetClockDrift(playedSamples, recSamples); + + _ptrAudioBuffer->SetVQEData(msecOnPlaySide, msecOnRecordSide, drift); + + _ptrAudioBuffer->SetTypingStatus(KeyPressed()); + + // Store the play and rec delay values for video synchronization + _sndCardPlayDelay = msecOnPlaySide; + _sndCardRecDelay = msecOnRecordSide; + + LARGE_INTEGER t1={0},t2={0}; + + if (send) + { + QueryPerformanceCounter(&t1); + + // deliver recorded samples at specified sample rate, mic level etc. to the observer using callback + UnLock(); + _ptrAudioBuffer->DeliverRecordedData(); + Lock(); + + QueryPerformanceCounter(&t2); + + if (InputSanityCheckAfterUnlockedPeriod() == -1) + { + // assert(false); + return -1; + } + } + + if (_AGC) + { + uint32_t newMicLevel = _ptrAudioBuffer->NewMicLevel(); + if (newMicLevel != 0) + { + // The VQE will only deliver non-zero microphone levels when a change is needed. + WEBRTC_TRACE(kTraceStream, kTraceUtility, _id,"AGC change of volume: => new=%u", newMicLevel); + + // We store this outside of the audio buffer to avoid + // having it overwritten by the getter thread. + _newMicLevel = newMicLevel; + SetEvent(_hSetCaptureVolumeEvent); + } + } + + // return utilized buffer to queue after specified delay (default is 4) + if (_recDelayCount > (_recPutBackDelay-1)) + { + // deley buffer counter to compensate for "put-back-delay" + bufCount = (bufCount + N_BUFFERS_IN - _recPutBackDelay) % N_BUFFERS_IN; + + // reset counter so we can make new detection + _waveHeaderIn[bufCount].dwBytesRecorded = 0; + + // return the utilized wave-header after certain delay (given by _recPutBackDelay) + res = waveInUnprepareHeader(_hWaveIn, &(_waveHeaderIn[bufCount]), sizeof(WAVEHDR)); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "waveInUnprepareHeader(%d) failed (err=%d)", bufCount, res); + TraceWaveInError(res); + } + + // ensure that the utilized header can be used again + res = waveInPrepareHeader(_hWaveIn, &(_waveHeaderIn[bufCount]), sizeof(WAVEHDR)); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "waveInPrepareHeader(%d) failed (err=%d)", bufCount, res); + TraceWaveInError(res); + return -1; + } + + // add the utilized buffer to the queue again + res = waveInAddBuffer(_hWaveIn, &(_waveHeaderIn[bufCount]), sizeof(WAVEHDR)); + if (res != MMSYSERR_NOERROR) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "waveInAddBuffer(%d) failed (err=%d)", bufCount, res); + TraceWaveInError(res); + if (_recPutBackDelay < 50) + { + _recPutBackDelay++; + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "_recPutBackDelay increased to %d", _recPutBackDelay); + } + else + { + if (_recError == 1) + { + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, "pending recording error exists"); + } + _recError = 1; // triggers callback from module process thread + WEBRTC_TRACE(kTraceError, kTraceUtility, _id, "kRecordingError message posted: _recPutBackDelay=%u", _recPutBackDelay); + } + } + } // if (_recDelayCount > (_recPutBackDelay-1)) + + if (_recDelayCount < (_recPutBackDelay+1)) + { + _recDelayCount++; + } + + // increase main buffer count since one complete buffer has now been delivered + _recBufCount++; + + if (send) { + // Calculate processing time + consumedTime = (int)(t2.QuadPart-t1.QuadPart); + // handle wraps, time should not be higher than a second + if ((consumedTime > _perfFreq.QuadPart) || (consumedTime < 0)) + consumedTime = 0; + } + + } // if ((nBytesRecorded == fullBufferSizeInBytes)) + + return nBytesRecorded; +} + +// ---------------------------------------------------------------------------- +// PlayProc +// ---------------------------------------------------------------------------- + +int AudioDeviceWindowsWave::PlayProc(LONGLONG& consumedTime) +{ + int32_t remTimeMS(0); + int8_t playBuffer[4*PLAY_BUF_SIZE_IN_SAMPLES]; + uint32_t writtenSamples(0); + uint32_t playedSamples(0); + + LARGE_INTEGER t1; + LARGE_INTEGER t2; + + consumedTime = 0; + _waitCounter++; + + // Get number of ms of sound that remains in the sound card buffer for playback. + // + remTimeMS = GetPlayoutBufferDelay(writtenSamples, playedSamples); + + // The threshold can be adaptive or fixed. The adaptive scheme is updated + // also for fixed mode but the updated threshold is not utilized. + // + const uint16_t thresholdMS = + (_playBufType == AudioDeviceModule::kAdaptiveBufferSize) ? _playBufDelay : _playBufDelayFixed; + + if (remTimeMS < thresholdMS + 9) + { + _dTcheckPlayBufDelay = 5; + + if (remTimeMS == 0) + { + WEBRTC_TRACE(kTraceInfo, kTraceUtility, _id, "playout buffer is empty => we must adapt..."); + if (_waitCounter > 30) + { + _erZeroCounter++; + if (_erZeroCounter == 2) + { + _playBufDelay += 15; + _minPlayBufDelay += 20; + _waitCounter = 50; + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "New playout states (er=0,erZero=2): minPlayBufDelay=%u, playBufDelay=%u", _minPlayBufDelay, _playBufDelay); + } + else if (_erZeroCounter == 3) + { + _erZeroCounter = 0; + _playBufDelay += 30; + _minPlayBufDelay += 25; + _waitCounter = 0; + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "New playout states (er=0, erZero=3): minPlayBufDelay=%u, playBufDelay=%u", _minPlayBufDelay, _playBufDelay); + } + else + { + _minPlayBufDelay += 10; + _playBufDelay += 15; + _waitCounter = 50; + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "New playout states (er=0, erZero=1): minPlayBufDelay=%u, playBufDelay=%u", _minPlayBufDelay, _playBufDelay); + } + } + } + else if (remTimeMS < _minPlayBufDelay) + { + // If there is less than 25 ms of audio in the play out buffer + // increase the buffersize limit value. _waitCounter prevents + // _playBufDelay to be increased every time this function is called. + + if (_waitCounter > 30) + { + _playBufDelay += 10; + if (_intro == 0) + _waitCounter = 0; + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "Playout threshold is increased: playBufDelay=%u", _playBufDelay); + } + } + else if (remTimeMS < thresholdMS - 9) + { + _erZeroCounter = 0; + } + else + { + _erZeroCounter = 0; + _dTcheckPlayBufDelay = 10; + } + + QueryPerformanceCounter(&t1); // measure time: START + + // Ask for new PCM data to be played out using the AudioDeviceBuffer. + // Ensure that this callback is executed without taking the audio-thread lock. + // + UnLock(); + uint32_t nSamples = _ptrAudioBuffer->RequestPlayoutData(PLAY_BUF_SIZE_IN_SAMPLES); + Lock(); + + if (OutputSanityCheckAfterUnlockedPeriod() == -1) + { + // assert(false); + return -1; + } + + nSamples = _ptrAudioBuffer->GetPlayoutData(playBuffer); + if (nSamples != PLAY_BUF_SIZE_IN_SAMPLES) + { + WEBRTC_TRACE(kTraceError, kTraceUtility, _id, "invalid number of output samples(%d)", nSamples); + } + + QueryPerformanceCounter(&t2); // measure time: STOP + consumedTime = (int)(t2.QuadPart - t1.QuadPart); + + Write(playBuffer, PLAY_BUF_SIZE_IN_SAMPLES); + + } // if (er < thresholdMS + 9) + else if (thresholdMS + 9 < remTimeMS ) + { + _erZeroCounter = 0; + _dTcheckPlayBufDelay = 2; // check buffer more often + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "Need to check playout buffer more often (dT=%u, remTimeMS=%u)", _dTcheckPlayBufDelay, remTimeMS); + } + + // If the buffersize has been stable for 20 seconds try to decrease the buffer size + if (_waitCounter > 2000) + { + _intro = 0; + _playBufDelay--; + _waitCounter = 1990; + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "Playout threshold is decreased: playBufDelay=%u", _playBufDelay); + } + + // Limit the minimum sound card (playback) delay to adaptive minimum delay + if (_playBufDelay < _minPlayBufDelay) + { + _playBufDelay = _minPlayBufDelay; + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "Playout threshold is limited to %u", _minPlayBufDelay); + } + + // Limit the maximum sound card (playback) delay to 150 ms + if (_playBufDelay > 150) + { + _playBufDelay = 150; + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "Playout threshold is limited to %d", _playBufDelay); + } + + // Upper limit of the minimum sound card (playback) delay to 65 ms. + // Deactivated during "useHeader mode" (_useHeader > 0). + if (_minPlayBufDelay > _MAX_minBuffer && + (_useHeader == 0)) + { + _minPlayBufDelay = _MAX_minBuffer; + WEBRTC_TRACE(kTraceDebug, kTraceUtility, _id, "Minimum playout threshold is limited to %d", _MAX_minBuffer); + } + + return (0); +} + +// ---------------------------------------------------------------------------- +// Write +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::Write(int8_t* data, uint16_t nSamples) +{ + if (_hWaveOut == NULL) + { + return -1; + } + + if (_playIsInitialized) + { + MMRESULT res; + + const uint16_t bufCount(_playBufCount); + + // Place data in the memory associated with _waveHeaderOut[bufCount] + // + const int16_t nBytes = (2*_playChannels)*nSamples; + memcpy(&_playBuffer[bufCount][0], &data[0], nBytes); + + // Send a data block to the given waveform-audio output device. + // + // When the buffer is finished, the WHDR_DONE bit is set in the dwFlags + // member of the WAVEHDR structure. The buffer must be prepared with the + // waveOutPrepareHeader function before it is passed to waveOutWrite. + // Unless the device is paused by calling the waveOutPause function, + // playback begins when the first data block is sent to the device. + // + res = waveOutWrite(_hWaveOut, &_waveHeaderOut[bufCount], sizeof(_waveHeaderOut[bufCount])); + if (MMSYSERR_NOERROR != res) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "waveOutWrite(%d) failed (err=%d)", bufCount, res); + TraceWaveOutError(res); + + _writeErrors++; + if (_writeErrors > 10) + { + if (_playError == 1) + { + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, "pending playout error exists"); + } + _playError = 1; // triggers callback from module process thread + WEBRTC_TRACE(kTraceError, kTraceUtility, _id, "kPlayoutError message posted: _writeErrors=%u", _writeErrors); + } + + return -1; + } + + _playBufCount = (_playBufCount+1) % N_BUFFERS_OUT; // increase buffer counter modulo size of total buffer + _writtenSamples += nSamples; // each sample is 2 or 4 bytes + _writeErrors = 0; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// GetClockDrift +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::GetClockDrift(const uint32_t plSamp, const uint32_t rcSamp) +{ + int drift = 0; + unsigned int plSampDiff = 0, rcSampDiff = 0; + + if (plSamp >= _plSampOld) + { + plSampDiff = plSamp - _plSampOld; + } + else + { + // Wrap + int i = 31; + while(_plSampOld <= (unsigned int)POW2(i)) + { + i--; + } + + // Add the amount remaining prior to wrapping + plSampDiff = plSamp + POW2(i + 1) - _plSampOld; + } + + if (rcSamp >= _rcSampOld) + { + rcSampDiff = rcSamp - _rcSampOld; + } + else + { // Wrap + int i = 31; + while(_rcSampOld <= (unsigned int)POW2(i)) + { + i--; + } + + rcSampDiff = rcSamp + POW2(i + 1) - _rcSampOld; + } + + drift = plSampDiff - rcSampDiff; + + _plSampOld = plSamp; + _rcSampOld = rcSamp; + + return drift; +} + +// ---------------------------------------------------------------------------- +// MonitorRecording +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::MonitorRecording(const uint32_t time) +{ + const uint16_t bytesPerSample = 2*_recChannels; + const uint32_t nRecordedSamples = _recordedBytes/bytesPerSample; + + if (nRecordedSamples > 5*N_REC_SAMPLES_PER_SEC) + { + // 5 seconds of audio has been recorded... + if ((time - _prevRecByteCheckTime) > 5700) + { + // ...and it was more than 5.7 seconds since we last did this check <=> + // we have not been able to record 5 seconds of audio in 5.7 seconds, + // hence a problem should be reported. + // This problem can be related to USB overload. + // + if (_recWarning == 1) + { + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, "pending recording warning exists"); + } + _recWarning = 1; // triggers callback from module process thread + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, "kRecordingWarning message posted: time-_prevRecByteCheckTime=%d", time - _prevRecByteCheckTime); + } + + _recordedBytes = 0; // restart "check again when 5 seconds are recorded" + _prevRecByteCheckTime = time; // reset timer to measure time for recording of 5 seconds + } + + if ((time - _prevRecByteCheckTime) > 8000) + { + // It has been more than 8 seconds since we able to confirm that 5 seconds of + // audio was recorded, hence we have not been able to record 5 seconds in + // 8 seconds => the complete recording process is most likely dead. + // + if (_recError == 1) + { + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, "pending recording error exists"); + } + _recError = 1; // triggers callback from module process thread + WEBRTC_TRACE(kTraceError, kTraceUtility, _id, "kRecordingError message posted: time-_prevRecByteCheckTime=%d", time - _prevRecByteCheckTime); + + _prevRecByteCheckTime = time; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// MonitorRecording +// +// Restart timer if needed (they seem to be messed up after a hibernate). +// ---------------------------------------------------------------------------- + +int32_t AudioDeviceWindowsWave::RestartTimerIfNeeded(const uint32_t time) +{ + const uint32_t diffMS = time - _prevTimerCheckTime; + _prevTimerCheckTime = time; + + if (diffMS > 7) + { + // one timer-issue detected... + _timerFaults++; + if (_timerFaults > 5 && _timerRestartAttempts < 2) + { + // Reinitialize timer event if event fails to execute at least every 5ms. + // On some machines it helps and the timer starts working as it should again; + // however, not all machines (we have seen issues on e.g. IBM T60). + // Therefore, the scheme below ensures that we do max 2 attempts to restart the timer. + // For the cases where restart does not do the trick, we compensate for the reduced + // resolution on both the recording and playout sides. + WEBRTC_TRACE(kTraceWarning, kTraceUtility, _id, " timer issue detected => timer is restarted"); + _timeEvent.StopTimer(); + _timeEvent.StartTimer(true, TIMER_PERIOD_MS); + // make sure timer gets time to start up and we don't kill/start timer serveral times over and over again + _timerFaults = -20; + _timerRestartAttempts++; + } + } + else + { + // restart timer-check scheme since we are OK + _timerFaults = 0; + _timerRestartAttempts = 0; + } + + return 0; +} + + +bool AudioDeviceWindowsWave::KeyPressed() const{ + + int key_down = 0; + for (int key = VK_SPACE; key < VK_NUMLOCK; key++) { + short res = GetAsyncKeyState(key); + key_down |= res & 0x1; // Get the LSB + } + return (key_down > 0); +} +} // namespace webrtc |