ardour/libs/backends/wavesaudio/wavesapi/devicemanager/WCMRPortAudioDeviceManager.cpp
Greg Zharun b5c08b6984 [Summary] Improved Port audio Waves API on Windows
[Details] Fixed issue when device sample rate changed by device but not updated in client (tracks engine state controller)
Added PA stream opening retry attempts, because PA needs some time to apply changes and may not successfully open stream at once
2014-08-13 19:39:00 +03:00

1771 lines
54 KiB
C++

//----------------------------------------------------------------------------------
//
// Copyright (c) 2008 Waves Audio Ltd. All rights reserved.
//
//! \file WCMRPortAudioDeviceManager.cpp
//!
//! WCMRPortAudioDeviceManager and related class declarations
//!
//---------------------------------------------------------------------------------*/
#include "WCMRPortAudioDeviceManager.h"
#include "MiscUtils/safe_delete.h"
#include "UMicroseconds.h"
#include <sstream>
#include <algorithm>
using namespace wvNS;
#include "IncludeWindows.h"
#include <MMSystem.h>
#include "pa_asio.h"
#include "asio.h"
#define PROPERTY_CHANGE_SLEEP_TIME_MILLISECONDS 200
#define PROPERTY_CHANGE_TIMEOUT_SECONDS 2
///< Supported Sample rates
static const double gAllSampleRates[] =
{
44100.0, 48000.0, 88200.0, 96000.0, -1 /* negative terminated list */
};
///< Default Supported Buffer Sizes.
static const int gAllBufferSizes[] =
{
32, 64, 96, 128, 192, 256, 512, 1024, 2048
};
///< The default SR.
static const int DEFAULT_SR = 44100;
///< The default buffer size.
static const int DEFAULT_BUFFERSIZE = 128;
static const int NONE_DEVICE_ID = -1;
///< Number of stalls to wait before notifying user...
static const int NUM_STALLS_FOR_NOTIFICATION = 100; // 100 corresponds to 100 x 42 ms idle timer - about 4 seconds.
static const int CHANGE_CHECK_COUNTER_PERIOD = 100; // 120 corresponds to 120 x 42 ms idle timer - about 4 seconds.
#define HUNDRED_NANO_TO_MILLI_CONSTANT 10000
#define CONSUMPTION_CALCULATION_INTERVAL 500 // Milli Seconds
// This wrapper is used to adapt device DoIdle method as entry point for MS thread
DWORD WINAPI WCMRPortAudioDevice::__DoIdle__(LPVOID lpThreadParameter)
{
WCMRPortAudioDevice* pDevice = (WCMRPortAudioDevice*)lpThreadParameter;
pDevice->DoIdle();
return 0;
}
//**********************************************************************************************
// WCMRPortAudioDevice::WCMRPortAudioDevice
//
//! Constructor for the audio device. Opens the PA device
//! and gets information about the device.
//! Starts the thread which will process requests to this device
//! such as determining supported sampling rates, buffer sizes, and channel counts.
//!
//! \param *pManager : The audio device manager that's managing this device.
//! \param deviceID : The port audio device ID.
//! \param useMultithreading : Whether to use multi-threading for audio processing. Default is true.
//!
//! \return Nothing.
//!
//**********************************************************************************************
WCMRPortAudioDevice::WCMRPortAudioDevice (WCMRPortAudioDeviceManager *pManager, unsigned int deviceID, bool useMultithreading, bool bNoCopy) :
WCMRNativeAudioDevice (pManager, useMultithreading, bNoCopy)
, m_SampleCounter(0)
, m_BufferSizeChangeRequested (0)
, m_BufferSizeChangeReported (0)
, m_ResetRequested (0)
, m_ResetReported (0)
, m_ResyncRequested (0)
, m_ResyncReported (0)
, m_DropsDetected(0)
, m_DropsReported(0)
, m_IgnoreThisDrop(true)
, m_hDeviceProcessingThread(NULL)
, m_DeviceProcessingThreadID(0)
, m_hUpdateDeviceInfoRequestedEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hUpdateDeviceInfoDone(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hActivateRequestedEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hActivationDone(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hDeActivateRequestedEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hDeActivationDone(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hStartStreamingRequestedEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hStartStreamingDone(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hStopStreamingRequestedEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hStopStreamingDone(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hResetRequestedEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hResetDone(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hResetFromDevRequestedEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hBufferSizeChangedEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hSampleRateChangedEvent(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hExitIdleThread(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_hDeviceInitialized(CreateEvent(NULL, FALSE, FALSE, NULL))
, m_lastErr(eNoErr)
{
AUTO_FUNC_DEBUG;
//Set initial device info...
m_DeviceID = deviceID;
m_PortAudioStream = NULL;
m_CurrentSamplingRate = DEFAULT_SR;
m_CurrentBufferSize = DEFAULT_BUFFERSIZE;
m_StopRequested = true;
m_pInputData = NULL;
//initialize device processing thread
//the divice become alive and now is able to process requests
m_hDeviceProcessingThread = CreateThread( NULL, 0, __DoIdle__, (LPVOID)this, 0, &m_DeviceProcessingThreadID );
if (!m_hDeviceProcessingThread)
{
DEBUG_MSG("API::Device " << m_DeviceName << " cannot create processing thread");
throw eGenericErr;
}
WaitForSingleObject(m_hDeviceInitialized, INFINITE);
if (ConnectionStatus() == DeviceErrors)
{
throw m_lastErr;
}
}
void WCMRPortAudioDevice::initDevice()
{
// Initialize COM for this thread
std::cout << "API::Device " << m_DeviceID << " initializing COM" << std::endl;
if (S_OK == CoInitialize(NULL) )
{
// Initialize PA
Pa_Initialize();
updateDeviceInfo();
//should use a valid current SR...
if (m_SamplingRates.size())
{
//see if the current sr is present in the sr list, if not, use the first one!
std::vector<int>::iterator intIter = find(m_SamplingRates.begin(), m_SamplingRates.end(), m_CurrentSamplingRate);
if (intIter == m_SamplingRates.end())
{
//not found... use the first one
m_CurrentSamplingRate = m_SamplingRates[0];
}
}
else
std::cout << "API::Device " << m_DeviceName << " Device does not support any sample rate of ours" << std::endl;
//should use a valid current buffer size
if (m_BufferSizes.size())
{
//see if the current sr is present in the buffersize list, if not, use the first one!
std::vector<int>::iterator intIter = find(m_BufferSizes.begin(), m_BufferSizes.end(), m_CurrentBufferSize);
if (intIter == m_BufferSizes.end())
{
//not found... use the first one
m_CurrentBufferSize = m_BufferSizes[0];
}
}
//build our input/output level lists
for (unsigned int currentChannel = 0; currentChannel < m_InputChannels.size(); currentChannel++)
{
m_InputLevels.push_back (0.0);
}
//build our input/output level lists
for (unsigned int currentChannel = 0; currentChannel < m_OutputChannels.size(); currentChannel++)
{
m_OutputLevels.push_back (0.0);
}
std::cout << "API::Device " << m_DeviceName << " Device has been initialized" << std::endl;
m_ConnectionStatus = DeviceDisconnected;
m_lastErr = eNoErr;
}
else
{
/*Replace with debug trace*/std::cout << "API::Device " << m_DeviceName << " cannot initialize COM" << std::endl;
DEBUG_MSG("Device " << m_DeviceName << " cannot initialize COM");
m_ConnectionStatus = DeviceErrors;
m_lastErr = eSomeThingNotInitailzed;
SetEvent(m_hExitIdleThread);
}
SetEvent(m_hDeviceInitialized);
}
void WCMRPortAudioDevice::terminateDevice()
{
std::cout << "API::Device " << m_DeviceName << " Terminating DEVICE" << std::endl;
//If device is streaming, need to stop it!
if (Streaming())
{
stopStreaming();
}
//If device is active (meaning stream is open) we need to close it.
if (Active())
{
deactivateDevice();
}
std::cout << "API::Device " << m_DeviceName << " Terminating PA" << std::endl;
//Deinitialize PA
Pa_Terminate();
}
//**********************************************************************************************
// WCMRPortAudioDevice::~WCMRPortAudioDevice
//
//! Destructor for the audio device. The base release all the connections that were created, if
//! they have not been already destroyed! Here we simply stop streaming, and close device
//! handles if necessary.
//!
//! \param none
//!
//! \return Nothing.
//!
//**********************************************************************************************
WCMRPortAudioDevice::~WCMRPortAudioDevice ()
{
AUTO_FUNC_DEBUG;
std::cout << "API::Destroying Device Instance: " << DeviceName() << std::endl;
try
{
//Stop deviceprocessing thread
SignalObjectAndWait(m_hExitIdleThread, m_hDeviceProcessingThread, INFINITE, false);
std::cout << "API::Device " << m_DeviceName << " Processing Thread is stopped" << std::endl;
CloseHandle(m_hDeviceProcessingThread);
//Now it's safe to free all event handlers
CloseHandle(m_hUpdateDeviceInfoRequestedEvent);
CloseHandle(m_hUpdateDeviceInfoDone);
CloseHandle(m_hActivateRequestedEvent);
CloseHandle(m_hActivationDone);
CloseHandle(m_hDeActivateRequestedEvent);
CloseHandle(m_hDeActivationDone);
CloseHandle(m_hStartStreamingRequestedEvent);
CloseHandle(m_hStartStreamingDone);
CloseHandle(m_hStopStreamingRequestedEvent);
CloseHandle(m_hStopStreamingDone);
CloseHandle(m_hResetRequestedEvent);
CloseHandle(m_hResetDone);
CloseHandle(m_hResetFromDevRequestedEvent);
CloseHandle(m_hBufferSizeChangedEvent);
CloseHandle(m_hSampleRateChangedEvent);
CloseHandle(m_hExitIdleThread);
CloseHandle(m_hDeviceInitialized);
}
catch (...)
{
//destructors should absorb exceptions, no harm in logging though!!
DEBUG_MSG ("Exception during destructor");
}
}
WTErr WCMRPortAudioDevice::UpdateDeviceInfo ()
{
std::cout << "API::Device (ID:)" << m_DeviceID << " Updating device info" << std::endl;
SignalObjectAndWait(m_hUpdateDeviceInfoRequestedEvent, m_hUpdateDeviceInfoDone, INFINITE, false);
return eNoErr;
}
//**********************************************************************************************
// WCMRPortAudioDevice::updateDeviceInfo
//
//! Must be called be device processing thread
//! Updates Device Information about channels, sampling rates, buffer sizes.
//!
//! \return Nothing.
//!
//**********************************************************************************************
void WCMRPortAudioDevice::updateDeviceInfo (bool callerIsWaiting/*=false*/)
{
AUTO_FUNC_DEBUG;
//get device info
const PaDeviceInfo *pDeviceInfo = Pa_GetDeviceInfo(m_DeviceID);
//update name.
m_DeviceName = pDeviceInfo->name;
std::cout << "API::Device " << m_DeviceName << " Getting device info " << std::endl;
//following parameters are needed opening test stream and for sample rates validation
PaStreamParameters inputParameters, outputParameters;
PaStreamParameters *pInS = NULL, *pOutS = NULL;
inputParameters.device = m_DeviceID;
inputParameters.channelCount = std::min<int>(2, pDeviceInfo->maxInputChannels);
inputParameters.sampleFormat = paFloat32 | paNonInterleaved;
inputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */
inputParameters.hostApiSpecificStreamInfo = 0;
if (inputParameters.channelCount)
pInS = &inputParameters;
outputParameters.device = m_DeviceID;
outputParameters.channelCount = std::min<int>(2, pDeviceInfo->maxOutputChannels);
outputParameters.sampleFormat = paFloat32;
outputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */
outputParameters.hostApiSpecificStreamInfo = 0;
if (outputParameters.channelCount)
pOutS = &outputParameters;
std::cout << "API::Device" << m_DeviceName << " Updating sample rates " << std::endl;
////////////////////////////////////////////////////////////////////////////////////
//update list of supported SRs...
m_SamplingRates.clear();
// now iterate through our standard SRs and check if they are supported by device
// store them for this device
for(int sr=0; gAllSampleRates[sr] > 0; sr++)
{
PaError err = Pa_IsFormatSupported(pInS, pOutS, gAllSampleRates[sr]);
if( err == paFormatIsSupported)
{
m_SamplingRates.push_back ((int)gAllSampleRates[sr]);
}
}
std::cout << "API::Device" << m_DeviceName << " Updating buffer sizes" << std::endl;
///////////////////////////////////////////////////////////////////////////////////
//update buffer sizes
m_BufferSizes.clear();
bool useDefaultBuffers = true;
PaError paErr = paNoError;
//sometimes devices change buffer size if sample rate changes
//it updates buffer size during stream opening
//we need to find out how device would behave with current sample rate
//try opening test stream to load device driver for current sample rate and buffer size
//(skip this step if the device is Active)
if ( !Active() )
{
if (paNoError != testStateValidness(m_CurrentSamplingRate, m_CurrentBufferSize) )
{
//buffer size did change
Pa_Terminate();
Pa_Initialize();
// test validness with current sample rate and device prefered buffer size
paErr = testStateValidness(m_CurrentSamplingRate, 0);
}
}
if (paErr == paNoError)
{
// In ASIO Windows, the buffer size is set from the sound device manufacturer's control panel
long minSize, maxSize, preferredSize, granularity;
paErr = PaAsio_GetAvailableBufferSizes(m_DeviceID, &minSize, &maxSize, &preferredSize, &granularity);
if (paErr == paNoError)
{
std::cout << "API::Device " << m_DeviceName << " Buffers: " << minSize << " " << maxSize << " " << preferredSize << std::endl;
m_BufferSizes.push_back (preferredSize);
useDefaultBuffers = false;
}
else
{
std::cout << "API::Device" << m_DeviceName << " Preffered buffer size is not supported" << std::endl;
}
}
else
{
std::cout << "API::Device" << m_DeviceName << " Device does not start with sample rate: "<< m_CurrentSamplingRate << " and default buffer size" << std::endl;
}
if (useDefaultBuffers)
{
std::cout << "API::Device" << m_DeviceName << " Using default buffer sizes " <<std::endl;
for(int bsize=0; bsize < (sizeof(gAllBufferSizes)/sizeof(gAllBufferSizes[0])); bsize++)
m_BufferSizes.push_back (gAllBufferSizes[bsize]);
}
/////////////////////////////////////////////////////////////////////////////////////////
//update channels info
{
int maxInputChannels = pDeviceInfo->maxInputChannels;
int maxOutputChannels = pDeviceInfo->maxOutputChannels;
//Update input channels
m_InputChannels.clear();
for (int channel = 0; channel < maxInputChannels; channel++)
{
const char* channelName[32]; // 32 is max leth declared by PortAudio for this operation
std::stringstream chNameStream;
PaError error = PaAsio_GetInputChannelName(m_DeviceID, channel, channelName);
chNameStream << (channel+1) << " - ";
if (error == paNoError)
{
chNameStream << *channelName;
}
else
{
chNameStream << "Input " << (channel+1);
}
m_InputChannels.push_back (chNameStream.str());
}
//Update output channels
m_OutputChannels.clear();
for (int channel = 0; channel < maxOutputChannels; channel++)
{
const char* channelName[32]; // 32 is max leth declared by PortAudio for this operation
std::stringstream chNameStream;
PaError error = PaAsio_GetOutputChannelName(m_DeviceID, channel, channelName);
chNameStream << (channel+1) << " - ";
if (error == paNoError)
{
chNameStream << *channelName;
}
else
{
chNameStream << "Output " << (channel+1);
}
m_OutputChannels.push_back (chNameStream.str());
}
}
std::cout << "API::Device" << m_DeviceName << " Device info update has been finished" << std::endl;
if (callerIsWaiting)
SetEvent(m_hUpdateDeviceInfoDone);
}
PaError WCMRPortAudioDevice::testStateValidness(int sampleRate, int bufferSize)
{
PaError paErr = paNoError;
//get device info
const PaDeviceInfo *pDeviceInfo = Pa_GetDeviceInfo(m_DeviceID);
//following parameters are needed opening test stream and for sample rates validation
PaStreamParameters inputParameters, outputParameters;
PaStreamParameters *pInS = NULL, *pOutS = NULL;
inputParameters.device = m_DeviceID;
inputParameters.channelCount = std::min<int>(2, pDeviceInfo->maxInputChannels);
inputParameters.sampleFormat = paFloat32 | paNonInterleaved;
inputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */
inputParameters.hostApiSpecificStreamInfo = 0;
if (inputParameters.channelCount)
pInS = &inputParameters;
outputParameters.device = m_DeviceID;
outputParameters.channelCount = std::min<int>(2, pDeviceInfo->maxOutputChannels);
outputParameters.sampleFormat = paFloat32;
outputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */
outputParameters.hostApiSpecificStreamInfo = 0;
if (outputParameters.channelCount)
pOutS = &outputParameters;
PaStream *portAudioStream = NULL;
//sometimes devices change buffer size if sample rate changes
//it updates buffer size during stream opening
//we need to find out how device would behave with current sample rate
//try opening test stream to load device driver for current sample rate and buffer size
paErr = Pa_OpenStream (&portAudioStream, pInS, pOutS, m_CurrentSamplingRate, m_CurrentBufferSize, paDitherOff, NULL, NULL);
if (portAudioStream)
{
// close test stream
Pa_CloseStream (portAudioStream);
portAudioStream = NULL;
}
return paErr;
}
//**********************************************************************************************
// WCMRPortAudioDevice::CurrentSamplingRate
//
//! The device's current sampling rate. This may be overridden, if the device needs to
//! query the driver for the current rate.
//!
//! \param none
//!
//! \return The device's current sampling rate. -1 on error.
//!
//**********************************************************************************************
int WCMRPortAudioDevice::CurrentSamplingRate ()
{
AUTO_FUNC_DEBUG;
//ToDo: Perhaps for ASIO devices that are active, we should retrive the SR from the device...
return (m_CurrentSamplingRate);
}
WTErr WCMRPortAudioDevice::SetActive (bool newState)
{
if (newState == true)
{
std::cout << "API::Device " << m_DeviceName << " Activation requested" << std::endl;
SignalObjectAndWait(m_hActivateRequestedEvent, m_hActivationDone, INFINITE, false);
}
else
{
std::cout << "API::Device " << m_DeviceName << " Deactivation requested" << std::endl;
SignalObjectAndWait(m_hDeActivateRequestedEvent, m_hDeActivationDone, INFINITE, false);
}
if (newState == Active() )
return eNoErr;
else
return eGenericErr;
}
WTErr WCMRPortAudioDevice::SetStreaming (bool newState)
{
if (newState == true)
{
std::cout << "API::Device " << m_DeviceName << " Stream start requested" << std::endl;
SignalObjectAndWait(m_hStartStreamingRequestedEvent, m_hStartStreamingDone, INFINITE, false);
}
else
{
std::cout << "API::Device " << m_DeviceName << " Stream stop requested" << std::endl;
SignalObjectAndWait(m_hStopStreamingRequestedEvent, m_hStopStreamingDone, INFINITE, false);
}
if (newState == Streaming() )
return eNoErr;
else
return eGenericErr;
}
WTErr WCMRPortAudioDevice::ResetDevice()
{
std::cout << "API::Device: " << m_DeviceName << " Reseting device" << std::endl;
SignalObjectAndWait(m_hResetRequestedEvent, m_hResetDone, INFINITE, false);
if (ConnectionStatus() == DeviceErrors)
{
return m_lastErr;
}
return eNoErr;
}
//**********************************************************************************************
// WCMRPortAudioDevice::SetCurrentSamplingRate
//
//! Change the sampling rate to be used by the device.
//!
//! \param newRate : The rate to use (samples per sec).
//!
//! \return eNoErr always. The derived classes may return error codes.
//!
//**********************************************************************************************
WTErr WCMRPortAudioDevice::SetCurrentSamplingRate (int newRate)
{
AUTO_FUNC_DEBUG;
std::vector<int>::iterator intIter;
WTErr retVal = eNoErr;
//changes the status.
int oldRate = CurrentSamplingRate();
bool oldActive = Active();
//no change, nothing to do
if (oldRate == newRate)
return (retVal);
//see if this is one of our supported rates...
intIter = find(m_SamplingRates.begin(), m_SamplingRates.end(), newRate);
if (intIter == m_SamplingRates.end())
{
//Can't change, perhaps use an "invalid param" type of error
retVal = eCommandLineParameter;
return (retVal);
}
if (Streaming())
{
//Can't change, perhaps use an "in use" type of error
retVal = eGenericErr;
return (retVal);
}
if (oldActive)
{
//Deactivate it for the change...
SetActive (false);
}
//make the change...
m_CurrentSamplingRate = newRate;
// Before reactivating the device: opening stream we should try getting buffer size update from the device
// because for new sampling rate some devices may change buffer size as well
int oldBufferSize = m_CurrentBufferSize;
retVal = ResetDevice();
//reactivate it.
if (oldActive && retVal == eNoErr)
{
retVal = SetActive (true);
}
if (retVal != eNoErr)
{
//revert changes if the device was not activated
m_CurrentSamplingRate = oldRate;
m_CurrentBufferSize = oldBufferSize;
int bufferSize = m_CurrentBufferSize;
m_pMyManager->NotifyClient (WCMRAudioDeviceManagerClient::BufferSizeChanged, (void *)&bufferSize);
retVal = eCommandLineParameter;
}
return (retVal);
}
//**********************************************************************************************
// WCMRPortAudioDevice::CurrentBufferSize
//
//! The device's current buffer size in use. This may be overridden, if the device needs to
//! query the driver for the current size.
//!
//! \param none
//!
//! \return The device's current buffer size. 0 on error.
//!
//**********************************************************************************************
int WCMRPortAudioDevice::CurrentBufferSize ()
{
return m_CurrentBufferSize;
}
//**********************************************************************************************
// WCMRPortAudioDevice::SetCurrentBufferSize
//
//! Change the buffer size to be used by the device. This will most likely be overridden,
//! the base class simply updates the member variable.
//!
//! \param newSize : The buffer size to use (in sample-frames)
//!
//! \return eNoErr always. The derived classes may return error codes.
//!
//**********************************************************************************************
WTErr WCMRPortAudioDevice::SetCurrentBufferSize (int newSize)
{
AUTO_FUNC_DEBUG;
WTErr retVal = eNoErr;
std::vector<int>::iterator intIter;
//changes the status.
int oldSize = CurrentBufferSize();
bool oldActive = Active();
//same size, nothing to do.
if (oldSize == newSize)
return (retVal);
//see if this is one of our supported rates...
intIter = find(m_BufferSizes.begin(), m_BufferSizes.end(), newSize);
if (intIter == m_BufferSizes.end())
{
//Sample rate proposed by client is not supported any more
if (m_BufferSizes.size() == 1)
{
// we have only one aloved buffer size which is preffered by PA
// this is the only value which could be set
m_CurrentBufferSize = m_BufferSizes[0];
int bufferSize = m_CurrentBufferSize;
// notify client to update sample rate after us
m_pMyManager->NotifyClient (WCMRAudioDeviceManagerClient::BufferSizeChanged, (void *)&bufferSize);
} else {
// more then one buffer size value is available
//Can't change, perhaps use an "invalid param" type of error
retVal = eCommandLineParameter;
return (retVal);
}
}
if (Streaming())
{
//Can't change, perhaps use an "in use" type of error
retVal = eGenericErr;
return (retVal);
}
if (oldActive)
{
//Deactivate it for the change...
SetActive (false);
}
//make the change...
m_CurrentBufferSize = newSize;
//reactivate it.
if (oldActive)
{
retVal = SetActive (true);
}
return (retVal);
}
//**********************************************************************************************
// WCMRPortAudioDevice::ConnectionStatus
//
//! Retrieves the device's current connection status. This will most likely be overridden,
//! in case some driver communication is required to query the status.
//!
//! \param none
//!
//! \return A ConnectionStates value.
//!
//**********************************************************************************************
WCMRPortAudioDevice::ConnectionStates WCMRPortAudioDevice::ConnectionStatus ()
{
AUTO_FUNC_DEBUG;
//ToDo: May want to do something more to extract the actual status!
return (m_ConnectionStatus);
}
//**********************************************************************************************
// WCMRPortAudioDevice::activateDevice
//
//! IS CALLED BY PROCESS THREAD
//! Sets the device into "active" state. Essentially, opens the PA device.
//! If it's an ASIO device it may result in buffer size change in some cases.
//!
//**********************************************************************************************
void WCMRPortAudioDevice::activateDevice (bool callerIsWaiting/*=false*/)
{
AUTO_FUNC_DEBUG;
PaError paErr = paNoError;
// if device is not active activate it
if (!Active() )
{
PaStreamParameters inputParameters, outputParameters;
PaStreamParameters *pInS = NULL, *pOutS = NULL;
const PaDeviceInfo *pDeviceInfo = Pa_GetDeviceInfo(m_DeviceID);
const PaHostApiInfo *pHostApiInfo = Pa_GetHostApiInfo(pDeviceInfo->hostApi);
inputParameters.device = m_DeviceID;
inputParameters.channelCount = (int)m_InputChannels.size();
inputParameters.sampleFormat = paFloat32 | paNonInterleaved;
inputParameters.suggestedLatency = Pa_GetDeviceInfo(m_DeviceID)->defaultLowInputLatency;
inputParameters.hostApiSpecificStreamInfo = 0;
if (inputParameters.channelCount)
pInS = &inputParameters;
outputParameters.device = m_DeviceID;
outputParameters.channelCount = (int)m_OutputChannels.size();
outputParameters.sampleFormat = paFloat32;
outputParameters.suggestedLatency = Pa_GetDeviceInfo(m_DeviceID)->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = 0;
if (outputParameters.channelCount)
pOutS = &outputParameters;
std::cout << "API::Device" << m_DeviceName << " Opening device stream " << std::endl;
std::cout << "Sample rate: " << m_CurrentSamplingRate << " buffer size: " << m_CurrentBufferSize << std::endl;
int tryAgain = ((PROPERTY_CHANGE_TIMEOUT_SECONDS * 1000) / PROPERTY_CHANGE_SLEEP_TIME_MILLISECONDS) ;
while (tryAgain) {
paErr = Pa_OpenStream(&m_PortAudioStream,
pInS,
pOutS,
m_CurrentSamplingRate,
m_CurrentBufferSize,
paDitherOff,
WCMRPortAudioDevice::TheCallback,
this);
if(paErr == paNoError)
{
break;
}
std::cout << "Cannot open streamm sleeping for "<< PROPERTY_CHANGE_SLEEP_TIME_MILLISECONDS << "msec and trying again" << std::endl;
// sleep and try again
wvThread::sleep_milliseconds (PROPERTY_CHANGE_SLEEP_TIME_MILLISECONDS);
--tryAgain;
}
if(paErr == paNoError)
{
m_DropsDetected = 0;
m_DropsReported = 0;
m_IgnoreThisDrop = true;
if (pHostApiInfo->type == paASIO)
{
m_BufferSizeChangeRequested = 0;
m_BufferSizeChangeReported = 0;
m_ResetRequested = 0;
m_ResetReported = 0;
m_ResyncRequested = 0;
m_ResyncReported = 0;
PaAsio_SetMessageHook (StaticASIOMessageHook, this);
}
m_IsActive = true;
m_ConnectionStatus = DeviceAvailable;
m_lastErr = eNoErr;
}
else
{
//failed, do not update device state
std::cout << "Failed to open pa stream " << paErr << std::endl;
DEBUG_MSG( "Failed to open pa stream " << paErr );
m_ConnectionStatus = DeviceErrors;
m_lastErr = eAsioFailed;
}
}
if (callerIsWaiting)
SetEvent(m_hActivationDone);
}
//**********************************************************************************************
// WCMRPortAudioDevice::deactivateDevice
//
//! IS CALLED BY PROCESS THREAD
//! Sets the device into "inactive" state. Essentially, closes the PA device.
//!
//**********************************************************************************************
void WCMRPortAudioDevice::deactivateDevice (bool callerIsWaiting/*=false*/)
{
AUTO_FUNC_DEBUG;
PaError paErr = paNoError;
if (Active() )
{
if (Streaming())
{
stopStreaming ();
}
if (m_PortAudioStream)
{
//close the stream first
std::cout << "API::Device" << m_DeviceName << " Closing device stream" << std::endl;
paErr = Pa_CloseStream (m_PortAudioStream);
if(paErr == paNoError)
{
m_PortAudioStream = NULL;
m_DropsDetected = 0;
m_DropsReported = 0;
m_IgnoreThisDrop = true;
m_BufferSizeChangeRequested = 0;
m_BufferSizeChangeReported = 0;
m_ResetRequested = 0;
m_ResetReported = 0;
m_ResyncRequested = 0;
m_ResyncReported = 0;
PaAsio_SetMessageHook (NULL, NULL);
//finaly set device state to "not active"
m_IsActive = false;
m_ConnectionStatus = DeviceDisconnected;
m_lastErr = eNoErr;
}
else
{
//failed, do not update device state
std::cout << "Failed to close pa stream stream " << paErr << std::endl;
DEBUG_MSG( "Failed to open pa stream stream " << paErr );
m_ConnectionStatus = DeviceErrors;
m_lastErr = eAsioFailed;
}
}
}
if (callerIsWaiting)
SetEvent(m_hDeActivationDone);
}
//**********************************************************************************************
// WCMRPortAudioDevice::startStreaming
//
//! Sets the devices into "streaming" state. Calls PA's Start stream routines.
//! This roughly corresponds to calling Start on the lower level interface.
//!
//**********************************************************************************************
void WCMRPortAudioDevice::startStreaming (bool callerIsWaiting/*=false*/)
{
AUTO_FUNC_DEBUG;
// proceed if the device is not streaming
if (!Streaming () )
{
PaError paErr = paNoError;
m_StopRequested = false;
m_SampleCounter = 0;
std::cout << "API::Device" << m_DeviceName << " Starting device stream" << std::endl;
paErr = Pa_StartStream( m_PortAudioStream );
if(paErr == paNoError)
{
// if the stream was started successfully
m_IsStreaming = true;
}
else
{
std::cout << "Failed to start PA stream: " << paErr << std::endl;
DEBUG_MSG( "Failed to start PA stream: " << paErr );
m_lastErr = eGenericErr;
}
}
if (callerIsWaiting)
SetEvent(m_hStartStreamingDone);
}
//**********************************************************************************************
// WCMRPortAudioDevice::stopStreaming
//
//! Sets the devices into "not streaming" state. Calls PA's Stop stream routines.
//! This roughly corresponds to calling Stop on the lower level interface.
//!
//**********************************************************************************************
void WCMRPortAudioDevice::stopStreaming (bool callerIsWaiting/*=false*/)
{
AUTO_FUNC_DEBUG;
// proceed if the device is streaming
if (Streaming () )
{
PaError paErr = paNoError;
m_StopRequested = true;
std::cout << "API::Device " << m_DeviceName << " Stopping device stream" << std::endl;
paErr = Pa_StopStream( m_PortAudioStream );
if(paErr == paNoError)
{
// if the stream was stopped successfully
m_IsStreaming = false;
m_pInputData = NULL;
}
else
{
std::cout << "Failed to stop PA stream: " << paErr << std::endl;
DEBUG_MSG( "Failed to stop PA stream " << paErr );
m_lastErr = eGenericErr;
}
}
if (callerIsWaiting)
SetEvent(m_hStopStreamingDone);
}
//**********************************************************************************************
// WCMRPortAudioDevice::resetDevice
//
//! Resets the device, updates device info. Importnat: does PA reinitialization calling
//! Pa_terminate/Pa_initialize functions.
//!
//! \param none
//!
//! \return nothing
//!
//**********************************************************************************************
void WCMRPortAudioDevice::resetDevice (bool callerIsWaiting /*=false*/ )
{
std::cout << "API::Device" << m_DeviceName << "Reseting device" << std::endl;
// Keep device sates
bool wasStreaming = Streaming();
bool wasActive = Active();
// Reset the device
stopStreaming();
deactivateDevice();
// Reinitialize PA
Pa_Terminate();
Pa_Initialize();
updateDeviceInfo();
// Cache device buffer size as it might be changed during reset
int oldBufferSize = m_CurrentBufferSize;
// In ASIO Windows, the buffer size is set from the sound device manufacturer's control panel
// Backend should always use preffered buffer size value in this case
long minSize, maxSize, preferredSize, granularity;
PaError paErr = PaAsio_GetAvailableBufferSizes(m_DeviceID, &minSize, &maxSize, &preferredSize, &granularity);
if (paErr == paNoError)
{
m_CurrentBufferSize = preferredSize;
}
else
{
// if we can't get device buffer sizes, use the first one among supported
if (m_BufferSizes.size() != 0)
m_CurrentBufferSize = m_BufferSizes.front();
}
// Notify the Application about device setting changes
if (oldBufferSize != m_CurrentBufferSize)
{
std::cout << "API::Device" << m_DeviceName << " buffer size changed" << std::endl;
int bufferSize = m_CurrentBufferSize;
m_pMyManager->NotifyClient (WCMRAudioDeviceManagerClient::BufferSizeChanged, (void *)&bufferSize);
}
// Activate the device if it was active before
if (wasActive)
activateDevice();
// Resume streaming if the device was streaming before
if(wasStreaming)
{
// Notify the Application to prepare for the stream start
m_pMyManager->NotifyClient (WCMRAudioDeviceManagerClient::DeviceStartsStreaming);
startStreaming();
}
if (callerIsWaiting)
SetEvent(m_hResetDone);
}
#ifdef _WINDOWS
long WCMRPortAudioDevice::StaticASIOMessageHook (void *pRefCon, long selector, long value, void* message, double* opt)
{
if (pRefCon)
{
return ((WCMRPortAudioDevice*)(pRefCon))->ASIOMessageHook (selector, value, message, opt);
}
else
return -1;
}
long WCMRPortAudioDevice::ASIOMessageHook (long selector, long WCUNUSEDPARAM(value), void* WCUNUSEDPARAM(message), double* WCUNUSEDPARAM(opt))
{
switch(selector)
{
case kAsioBufferSizeChange:
m_BufferSizeChangeRequested++;
std::cout << "\t\t\tWCMRPortAudioDevice::ASIOMessageHook -- m_BufferSizeChangeRequested" << std::endl;
SetEvent(m_hBufferSizeChangedEvent);
break;
case kAsioResetRequest:
m_ResetRequested++;
std::cout << "\t\t\tWCMRPortAudioDevice::ASIOMessageHook -- kAsioResetRequest" << std::endl;
m_pMyManager->NotifyClient (WCMRAudioDeviceManagerClient::RequestReset);
break;
case kAsioResyncRequest:
std::cout << "\t\t\tWCMRPortAudioDevice::ASIOMessageHook -- kAsioResyncRequest" << std::endl;
m_ResyncRequested++;
break;
case kAsioLatenciesChanged:
std::cout << "\t\t\tWCMRPortAudioDevice::ASIOMessageHook -- kAsioLatenciesChanged" << std::endl;
SetEvent(m_hBufferSizeChangedEvent);
m_BufferSizeChangeRequested++;
break;
case kAsioOverload:
std::cout << "\t\t\tWCMRPortAudioDevice::ASIOMessageHook -- kAsioOverload" << std::endl;
m_DropsDetected++;
break;
}
return 0;
}
#endif
//**********************************************************************************************
// WCMRPortAudioDevice::DoIdle
//
//! A place for doing idle time processing. The other derived classes will probably do something
//! meaningful.
//!
//! \param none
//!
//! \return eNoErr always.
//!
//**********************************************************************************************
WTErr WCMRPortAudioDevice::DoIdle ()
{
WTErr retVal = eNoErr;
std::cout << "WCMRPortAudioDevice::DoIdle ()" << std::endl;
HANDLE hEvents[] =
{
m_hUpdateDeviceInfoRequestedEvent,
m_hActivateRequestedEvent,
m_hDeActivateRequestedEvent,
m_hStartStreamingRequestedEvent,
m_hStopStreamingRequestedEvent,
m_hBufferSizeChangedEvent,
m_hSampleRateChangedEvent,
m_hResetRequestedEvent,
m_hResetFromDevRequestedEvent,
m_hExitIdleThread
};
const size_t hEventsSize = sizeof(hEvents)/sizeof(hEvents[0]);
initDevice();
for(;;)
{
DWORD result = WaitForMultipleObjects (hEventsSize, hEvents, FALSE, INFINITE);
result = result - WAIT_OBJECT_0;
if ((result < 0) || (result >= hEventsSize)) {
std::cout << "\t\t\t\t\t\t\tWCMRPortAudioDevice::DoIdle () -> (result < 0) || (result >= hEventsSize):" << result << std::endl;
retVal = eGenericErr;
break;
}
if (hEvents[result] == m_hExitIdleThread) {
std::cout << "\t\t\t\t\t\t\tWCMRPortAudioDevice::DoIdle () -> m_hExitIdleThread" << result << std::endl;
retVal = eNoErr;
break;
}
if (hEvents[result] == m_hUpdateDeviceInfoRequestedEvent) {
std::cout << "\t\t\t\t\t\tupdate requested ..." << std::endl;
updateDeviceInfo(true);
}
if (hEvents[result] == m_hActivateRequestedEvent) {
std::cout << "\t\t\t\t\t\tactivation requested ..." << std::endl;
activateDevice(true);
}
if (hEvents[result] == m_hDeActivateRequestedEvent) {
std::cout << "\t\t\t\t\t\tdeactivation requested ..." << std::endl;
deactivateDevice(true);
}
if (hEvents[result] == m_hStartStreamingRequestedEvent) {
std::cout << "\t\t\t\t\t\tStart stream requested ..." << std::endl;
startStreaming(true);
}
if (hEvents[result] == m_hStopStreamingRequestedEvent) {
std::cout << "\t\t\t\t\t\tStop stream requested ..." << std::endl;
stopStreaming(true);
}
if (hEvents[result] == m_hResetRequestedEvent) {
std::cout << "\t\t\t\t\t\treset requested ..." << std::endl;
resetDevice(true);
}
if (hEvents[result] == m_hResetFromDevRequestedEvent) {
std::cout << "\t\t\t\t\t\treset requested from device..." << std::endl;
resetDevice();
}
if (hEvents[result] == m_hBufferSizeChangedEvent) {
std::cout << "\t\t\t\t\t\tbuffer size changed from device..." << std::endl;
m_pMyManager->NotifyClient (WCMRAudioDeviceManagerClient::BufferSizeChanged);
}
if (hEvents[result] == m_hSampleRateChangedEvent) {
std::cout << "\t\t\t\t\t\tsample rate changed from device..." << std::endl;
m_pMyManager->NotifyClient (WCMRAudioDeviceManagerClient::SamplingRateChanged);
}
}
terminateDevice();
return retVal;
}
//**********************************************************************************************
// WCMRPortAudioDevice::SetMonitorChannels
//
//! Used to set the channels to be used for monitoring.
//!
//! \param leftChannel : Left monitor channel index.
//! \param rightChannel : Right monitor channel index.
//!
//! \return eNoErr always, the derived classes may return appropriate errors.
//!
//**********************************************************************************************
WTErr WCMRPortAudioDevice::SetMonitorChannels (int leftChannel, int rightChannel)
{
AUTO_FUNC_DEBUG;
//This will most likely be overridden, the base class simply
//changes the member.
m_LeftMonitorChannel = leftChannel;
m_RightMonitorChannel = rightChannel;
return (eNoErr);
}
//**********************************************************************************************
// WCMRPortAudioDevice::SetMonitorGain
//
//! Used to set monitor gain (or atten).
//!
//! \param newGain : The new gain or atten. value to use. Specified as a linear multiplier (not dB)
//!
//! \return eNoErr always, the derived classes may return appropriate errors.
//!
//**********************************************************************************************
WTErr WCMRPortAudioDevice::SetMonitorGain (float newGain)
{
AUTO_FUNC_DEBUG;
//This will most likely be overridden, the base class simply
//changes the member.
m_MonitorGain = newGain;
return (eNoErr);
}
//**********************************************************************************************
// WCMRPortAudioDevice::ShowConfigPanel
//
//! Used to show device specific config/control panel. Some interfaces may not support it.
//! Some interfaces may require the device to be active before it can display a panel.
//!
//! \param pParam : A device/interface specific parameter, should be the app window handle for ASIO.
//!
//! \return eNoErr always, the derived classes may return errors.
//!
//**********************************************************************************************
WTErr WCMRPortAudioDevice::ShowConfigPanel (void *pParam)
{
AUTO_FUNC_DEBUG;
WTErr retVal = eNoErr;
if (Active())
{
#ifdef _WINDOWS
if(Pa_GetHostApiInfo(Pa_GetDeviceInfo(m_DeviceID)->hostApi)->type == paASIO)
{
// stop and deactivate the device
bool wasStreaming = Streaming();
SetActive(false);
// show control panel for the device
if (PaAsio_ShowControlPanel (m_DeviceID, pParam) != paNoError)
retVal = eGenericErr;
// reset device to pick up changes
ResetDevice();
// restore previous state for the device
SetActive(true);
if (wasStreaming)
SetStreaming(true);
}
#else
pParam = pParam;
#endif //_windows
}
return (retVal);
}
//*****************************************************************************************************
// WCMRPortAudioDevice::TheCallback
//
//! The (static) Port Audio Callback function. This is a static member. It calls on the AudioCallback in the
//! WCMRPortAudioDevice to do the real work.
//!
//! \param pInputBuffer: pointer to input buffer.
//! \param pOutputBuffer: pointer to output buffer.
//! \param framesPerBuffer: number of sample frames per buffer.
//! \param pTimeInfo: time info for PaStream callback.
//! \param statusFlags:
//! \param pUserData: pointer to user data, in our case the WCMRPortAudioDevice object.
//!
//! \return true to stop streaming else returns false.
//******************************************************************************************************
int WCMRPortAudioDevice::TheCallback (const void *pInputBuffer, void *pOutputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* /*pTimeInfo*/, PaStreamCallbackFlags statusFlags, void *pUserData )
{
WCMRPortAudioDevice *pMyDevice = (WCMRPortAudioDevice *)pUserData;
if (pMyDevice)
return pMyDevice->AudioCallback ((float *)pInputBuffer, (float *)pOutputBuffer, framesPerBuffer,
(statusFlags & (paInputOverflow | paOutputUnderflow)) != 0);
else
return (true);
}
//**********************************************************************************************
// WCMRPortAudioDevice::AudoiCallback
//
//! Here's where the actual audio processing happens. We call upon all the active connections'
//! sinks to provide data to us which can be put/mixed in the output buffer! Also, we make the
//! input data available to any sources that may call upon us during this time!
//!
//! \param *pInputBuffer : Points to a buffer with recorded data.
//! \param *pOutputBuffer : Points to a buffer to receive playback data.
//! \param framesPerBuffer : Number of sample frames in input and output buffers. Number of channels,
//! which are interleaved, is fixed at Device Open (Active) time. In this implementation,
//! the number of channels are fixed to use the maximum available.
//! \param dropsDetected : True if dropouts were detected in input or output. Can be used to signal the GUI.
//!
//! \return true
//!
//**********************************************************************************************
int WCMRPortAudioDevice::AudioCallback( const float *pInputBuffer, float *pOutputBuffer, unsigned long framesPerBuffer, bool dropsDetected )
{
UMicroseconds theStartTime;
// detect drops
if (dropsDetected)
{
if (m_IgnoreThisDrop)
m_IgnoreThisDrop = false; //We'll ignore once, just once!
else
m_DropsDetected++;
}
m_pInputData = pInputBuffer;
// VKamyshniy: Is this a right place to call the client???:
struct WCMRAudioDeviceManagerClient::AudioCallbackData audioCallbackData =
{
m_pInputData,
pOutputBuffer,
framesPerBuffer,
m_SampleCounter,
theStartTime.MicroSeconds()*1000
};
m_pMyManager->NotifyClient (WCMRAudioDeviceManagerClient::AudioCallback, (void *)&audioCallbackData );
//Don't try to access after this call returns!
m_pInputData = NULL;
m_SampleCounter += framesPerBuffer;
return m_StopRequested;
}
//**********************************************************************************************
// WCMRPortAudioDeviceManager::WCMRPortAudioDeviceManager
//
//! The constructuor, we initialize PA, and build the device list.
//!
//! \param *pTheClient : The manager's client object (which receives notifications).
//! \param interfaceType : The PortAudio interface type to use for this manager - acts as a filter.
//! \param useMultithreading : Whether to use multi-threading for audio processing. Default is true.
//!
//! \return Nothing.
//!
//**********************************************************************************************
WCMRPortAudioDeviceManager::WCMRPortAudioDeviceManager (WCMRAudioDeviceManagerClient *pTheClient,
eAudioDeviceFilter eCurAudioDeviceFilter, bool useMultithreading, bool bNocopy)
: WCMRAudioDeviceManager (pTheClient, eCurAudioDeviceFilter)
, m_NoneDevice(0)
, m_UseMultithreading(useMultithreading)
, m_bNoCopyAudioBuffer(bNocopy)
{
AUTO_FUNC_DEBUG;
std::cout << "API::PortAudioDeviceManager::PA Device manager constructor" << std::endl;
//Always create the None device first...
m_NoneDevice = new WCMRNativeAudioNoneDevice(this);
WTErr err = generateDeviceListImpl();
if (eNoErr != err)
throw err;
timeBeginPeriod (1);
}
//**********************************************************************************************
// WCMRPortAudioDeviceManager::~WCMRPortAudioDeviceManager
//
//! It clears the device list, releasing each of the device.
//!
//! \param none
//!
//! \return Nothing.
//!
//**********************************************************************************************
WCMRPortAudioDeviceManager::~WCMRPortAudioDeviceManager()
{
AUTO_FUNC_DEBUG;
std::cout << "API::Destroying PortAudioDeviceManager " << std::endl;
try
{
delete m_NoneDevice;
}
catch (...)
{
//destructors should absorb exceptions, no harm in logging though!!
DEBUG_MSG ("Exception during destructor");
}
timeEndPeriod (1);
}
WCMRAudioDevice* WCMRPortAudioDeviceManager::initNewCurrentDeviceImpl(const std::string & deviceName)
{
destroyCurrentDeviceImpl();
std::cout << "API::PortAudioDeviceManager::initNewCurrentDevice " << deviceName << std::endl;
if (deviceName == m_NoneDevice->DeviceName() )
{
m_CurrentDevice = m_NoneDevice;
return m_CurrentDevice;
}
DeviceInfo devInfo;
WTErr err = GetDeviceInfoByName(deviceName, devInfo);
if (eNoErr == err)
{
try
{
std::cout << "API::PortAudioDeviceManager::Creating PA device: " << devInfo.m_DeviceId << ", Device Name: " << devInfo.m_DeviceName << std::endl;
TRACE_MSG ("API::PortAudioDeviceManager::Creating PA device: " << devInfo.m_DeviceId << ", Device Name: " << devInfo.m_DeviceName);
m_CurrentDevice = new WCMRPortAudioDevice (this, devInfo.m_DeviceId, m_UseMultithreading, m_bNoCopyAudioBuffer);
}
catch (...)
{
std::cout << "Unabled to create PA Device: " << devInfo.m_DeviceId << std::endl;
DEBUG_MSG ("Unabled to create PA Device: " << devInfo.m_DeviceId);
}
}
return m_CurrentDevice;
}
void WCMRPortAudioDeviceManager::destroyCurrentDeviceImpl()
{
if (m_CurrentDevice != m_NoneDevice)
delete m_CurrentDevice;
m_CurrentDevice = 0;
}
WTErr WCMRPortAudioDeviceManager::getDeviceAvailableSampleRates(DeviceID deviceId, std::vector<int>& sampleRates)
{
sampleRates.clear();
const PaDeviceInfo *pPaDeviceInfo = Pa_GetDeviceInfo(deviceId);
//now find supported sample rates
//following parameters are needed for sample rates validation
PaStreamParameters inputParameters, outputParameters;
PaStreamParameters *pInS = NULL, *pOutS = NULL;
inputParameters.device = deviceId;
inputParameters.channelCount = std::min<int>(2, pPaDeviceInfo->maxInputChannels);
inputParameters.sampleFormat = paFloat32 | paNonInterleaved;
inputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */
inputParameters.hostApiSpecificStreamInfo = 0;
if (inputParameters.channelCount)
pInS = &inputParameters;
outputParameters.device = deviceId;
outputParameters.channelCount = std::min<int>(2, pPaDeviceInfo->maxOutputChannels);
outputParameters.sampleFormat = paFloat32;
outputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */
outputParameters.hostApiSpecificStreamInfo = 0;
if (outputParameters.channelCount)
pOutS = &outputParameters;
for(int sr=0; gAllSampleRates[sr] > 0; sr++)
{
if( paFormatIsSupported == Pa_IsFormatSupported(pInS, pOutS, gAllSampleRates[sr]) )
{
sampleRates.push_back ((int)gAllSampleRates[sr]);
}
}
}
WTErr WCMRPortAudioDeviceManager::generateDeviceListImpl()
{
std::cout << "API::PortAudioDeviceManager::Generating device list" << std::endl;
WTErr retVal = eNoErr;
//Initialize PortAudio and ASIO first
PaError paErr = Pa_Initialize();
if (paErr != paNoError)
{
//ToDo: throw an exception here!
retVal = eSomeThingNotInitailzed;
return retVal;
}
// lock DeviceInfoVec firts
wvNS::wvThread::ThreadMutex::lock theLock(m_AudioDeviceInfoVecMutex);
if (m_NoneDevice)
{
DeviceInfo *pDevInfo = new DeviceInfo(NONE_DEVICE_ID, m_NoneDevice->DeviceName() );
pDevInfo->m_AvailableSampleRates = m_NoneDevice->SamplingRates();
m_DeviceInfoVec.push_back(pDevInfo);
}
//Get device count...
int numDevices = Pa_GetDeviceCount();
//for each device,
for (int thisDeviceID = 0; thisDeviceID < numDevices; thisDeviceID++)
{
//if it's of the required type...
const PaDeviceInfo *pPaDeviceInfo = Pa_GetDeviceInfo(thisDeviceID);
if (Pa_GetHostApiInfo(pPaDeviceInfo->hostApi)->type == paASIO)
{
//build a device object...
try
{
std::cout << "API::PortAudioDeviceManager::DeviceID: " << thisDeviceID << ", Device Name: " << pPaDeviceInfo->name << std::endl;
TRACE_MSG ("PA DeviceID: " << thisDeviceID << ", Device Name: " << pPaDeviceInfo->name);
DeviceInfo *pDevInfo = new DeviceInfo(thisDeviceID, pPaDeviceInfo->name);
if (pDevInfo)
{
std::vector<int> availableSampleRates;
WTErr wErr = WCMRPortAudioDeviceManager::getDeviceAvailableSampleRates(thisDeviceID, availableSampleRates);
if (wErr != eNoErr)
{
DEBUG_MSG ("Failed to get device available sample rates. Device ID: " << m_DeviceID);
delete pDevInfo;
continue; //proceed to the next device
}
pDevInfo->m_AvailableSampleRates = availableSampleRates;
pDevInfo->m_MaxInputChannels = pPaDeviceInfo->maxInputChannels;
pDevInfo->m_MaxOutputChannels = pPaDeviceInfo->maxOutputChannels;
//Now check if this device is acceptable according to current input/output settings
bool bRejectDevice = false;
switch(m_eAudioDeviceFilter)
{
case eInputOnlyDevices:
if (pDevInfo->m_MaxInputChannels != 0)
{
m_DeviceInfoVec.push_back(pDevInfo);
}
else
{
// Delete unnecesarry device
bRejectDevice = true;
}
break;
case eOutputOnlyDevices:
if (pDevInfo->m_MaxOutputChannels != 0)
{
m_DeviceInfoVec.push_back(pDevInfo);
}
else
{
// Delete unnecesarry device
bRejectDevice = true;
}
break;
case eFullDuplexDevices:
if (pDevInfo->m_MaxInputChannels != 0 && pDevInfo->m_MaxOutputChannels != 0)
{
m_DeviceInfoVec.push_back(pDevInfo);
}
else
{
// Delete unnecesarry device
bRejectDevice = true;
}
break;
case eAllDevices:
default:
m_DeviceInfoVec.push_back(pDevInfo);
break;
}
if(bRejectDevice)
{
TRACE_MSG ("API::PortAudioDeviceManager::Device " << pDevInfo->m_DeviceName << "Rejected. \
In Channels = " << pDevInfo->m_MaxInputChannels << "Out Channels = " <<pDevInfo->m_MaxOutputChannels );
delete pDevInfo;
}
}
}
catch (...)
{
std::cout << "API::PortAudioDeviceManager::Unabled to create PA Device: " << std::endl;
DEBUG_MSG ("Unabled to create PA Device: " << thisDeviceID);
}
}
}
//If no devices were found, that's not a good thing!
if (m_DeviceInfoVec.empty() )
{
std::cout << "API::PortAudioDeviceManager::No matching PortAudio devices were found, total PA devices = " << numDevices << std::endl;
DEBUG_MSG ("No matching PortAudio devices were found, total PA devices = " << numDevices);
}
//we don't need PA initialized right now
Pa_Terminate();
return retVal;
}
WTErr WCMRPortAudioDeviceManager::getDeviceSampleRatesImpl(const std::string & deviceName, std::vector<int>& sampleRates) const
{
sampleRates.clear ();
WTErr retVal = eNoErr;
DeviceInfo devInfo;
retVal = GetDeviceInfoByName(deviceName, devInfo);
if (eNoErr == retVal)
{
sampleRates.assign(devInfo.m_AvailableSampleRates.begin(), devInfo.m_AvailableSampleRates.end() );
}
else
{
std::cout << "API::PortAudioDeviceManager::GetSampleRates: Device not found: "<< deviceName << std::endl;
}
return retVal;
}
WTErr WCMRPortAudioDeviceManager::getDeviceBufferSizesImpl(const std::string & deviceName, std::vector<int>& buffers) const
{
WTErr retVal = eNoErr;
std::cout << "API::PortAudioDeviceManager::GetBufferSizes: getting buffer size for device: "<< deviceName << std::endl;
//first check if the request has been made for None device
if (deviceName == m_NoneDevice->DeviceName() )
{
buffers = m_NoneDevice->BufferSizes();
return retVal;
}
//if we have current device initialized and it's PA device, reset it
//this procedure will reset PA corrently and update info for all PA devices as well
bool paLocalInit = false;
WCMRPortAudioDevice* portaudioDevice = dynamic_cast<WCMRPortAudioDevice*>(m_CurrentDevice);
if (portaudioDevice)
{
portaudioDevice->ResetDevice();
}
else
{
//initialize PA to get buffers for the device
Pa_Initialize();
paLocalInit = true;
}
DeviceInfo devInfo;
retVal = GetDeviceInfoByName(deviceName, devInfo);
if (eNoErr == retVal)
{
//make PA request to get actual device buffer sizes
long minSize, maxSize, preferredSize, granularity;
PaError paErr = PaAsio_GetAvailableBufferSizes(devInfo.m_DeviceId, &minSize, &maxSize, &preferredSize, &granularity);
//for Windows ASIO devices we always use prefferes buffer size ONLY
if (paNoError == paErr )
{
buffers.push_back(preferredSize);
}
else
{
retVal = eAsioFailed;
std::cout << "API::PortAudioDeviceManager::GetBufferSizes: error: " << paErr << " getting buffer size fo device: "<< deviceName << std::endl;
}
}
else
{
std::cout << "API::PortAudioDeviceManager::GetBufferSizes: Device not found: "<< deviceName << std::endl;
}
//deinitialize PA now
if (paLocalInit)
Pa_Terminate();
return retVal;
}