
BUG=1662 R=xians@webrtc.org Review URL: https://webrtc-codereview.appspot.com/1785005 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4330 4adac7df-926f-26a2-2b94-8c16560cd09d
3836 lines
123 KiB
C++
3836 lines
123 KiB
C++
/*
|
|
* 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/audio_device_utility.h"
|
|
#include "webrtc/modules/audio_device/win/audio_device_wave_win.h"
|
|
|
|
#include "webrtc/system_wrappers/interface/event_wrapper.h"
|
|
#include "webrtc/system_wrappers/interface/thread_wrapper.h"
|
|
#include "webrtc/system_wrappers/interface/trace.h"
|
|
|
|
#include <windows.h>
|
|
#include <objbase.h> // CoTaskMemAlloc, CoTaskMemFree
|
|
#include <strsafe.h> // StringCchCopy(), StringCchCat(), StringCchPrintf()
|
|
#include <cassert>
|
|
|
|
// 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(*EventWrapper::Create()),
|
|
_recStartEvent(*EventWrapper::Create()),
|
|
_playStartEvent(*EventWrapper::Create()),
|
|
_hGetCaptureVolumeThread(NULL),
|
|
_hShutdownGetVolumeEvent(NULL),
|
|
_hSetCaptureVolumeThread(NULL),
|
|
_hShutdownSetVolumeEvent(NULL),
|
|
_hSetCaptureVolumeEvent(NULL),
|
|
_ptrThread(NULL),
|
|
_threadID(0),
|
|
_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(AudioDeviceUtility::GetTimeInMS());
|
|
|
|
_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,
|
|
kRealtimePriority,
|
|
threadName);
|
|
if (_ptrThread == NULL)
|
|
{
|
|
WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
|
|
"failed to create the audio thread");
|
|
return -1;
|
|
}
|
|
|
|
unsigned int threadID(0);
|
|
if (!_ptrThread->Start(threadID))
|
|
{
|
|
WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
|
|
"failed to start the audio thread");
|
|
delete _ptrThread;
|
|
_ptrThread = NULL;
|
|
return -1;
|
|
}
|
|
_threadID = threadID;
|
|
|
|
const bool periodic(true);
|
|
if (!_timeEvent.StartTimer(periodic, TIMER_PERIOD_MS))
|
|
{
|
|
WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
|
|
"failed to start the timer event");
|
|
if (_ptrThread->Stop())
|
|
{
|
|
delete _ptrThread;
|
|
_ptrThread = NULL;
|
|
}
|
|
else
|
|
{
|
|
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
|
|
"unable to stop the activated thread");
|
|
}
|
|
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;
|
|
_ptrThread = NULL;
|
|
_critSect.Leave();
|
|
|
|
tmpThread->SetNotAlive();
|
|
_timeEvent.Set();
|
|
|
|
if (tmpThread->Stop())
|
|
{
|
|
delete tmpThread;
|
|
}
|
|
else
|
|
{
|
|
_critSect.Leave();
|
|
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
|
|
"failed to close down the audio thread");
|
|
return -1;
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SpeakerIsAvailable
|
|
// ----------------------------------------------------------------------------
|
|
|
|
int32_t AudioDeviceWindowsWave::SpeakerIsAvailable(bool& available)
|
|
{
|
|
|
|
// Enumerate all avaliable speakers and make an attempt to open up the
|
|
// output mixer corresponding to the currently selected output device.
|
|
//
|
|
if (InitSpeaker() == -1)
|
|
{
|
|
available = false;
|
|
return 0;
|
|
}
|
|
|
|
// Given that InitSpeaker was successful, we know that a valid speaker exists
|
|
//
|
|
available = true;
|
|
|
|
// Close the initialized output mixer
|
|
//
|
|
_mixerManager.CloseSpeaker();
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// MicrophoneIsAvailable
|
|
// ----------------------------------------------------------------------------
|
|
|
|
int32_t AudioDeviceWindowsWave::MicrophoneIsAvailable(bool& available)
|
|
{
|
|
|
|
// Enumerate all avaliable microphones and make an attempt to open up the
|
|
// input mixer corresponding to the currently selected output device.
|
|
//
|
|
if (InitMicrophone() == -1)
|
|
{
|
|
available = false;
|
|
return 0;
|
|
}
|
|
|
|
// Given that InitMicrophone was successful, we know that a valid microphone exists
|
|
//
|
|
available = true;
|
|
|
|
// Close the initialized input mixer
|
|
//
|
|
_mixerManager.CloseMicrophone();
|
|
|
|
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);
|
|
// DEBUG_PRINTP("msecInPlayoutBuffer=%u\n", msecInPlayoutBuffer);
|
|
|
|
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 = AudioDeviceUtility::GetTimeInMS();
|
|
|
|
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,t2;
|
|
|
|
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
|