diff options
Diffstat (limited to 'libs/backends/wavesaudio/wavesapi/devicemanager/WCMRPortAudioDeviceManager.cpp')
-rw-r--r-- | libs/backends/wavesaudio/wavesapi/devicemanager/WCMRPortAudioDeviceManager.cpp | 1697 |
1 files changed, 1697 insertions, 0 deletions
diff --git a/libs/backends/wavesaudio/wavesapi/devicemanager/WCMRPortAudioDeviceManager.cpp b/libs/backends/wavesaudio/wavesapi/devicemanager/WCMRPortAudioDeviceManager.cpp new file mode 100644 index 0000000000..2f6cd710ff --- /dev/null +++ b/libs/backends/wavesaudio/wavesapi/devicemanager/WCMRPortAudioDeviceManager.cpp @@ -0,0 +1,1697 @@ +//---------------------------------------------------------------------------------- +// +// 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" + +///< 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++) + { + std::stringstream chNameStream; + //A better implementation would be to retrieve the names from ASIO or CoreAudio interfaces + chNameStream << "Input " << (channel+1); + m_InputChannels.push_back (chNameStream.str()); + } + + + //Update output channels + m_OutputChannels.clear(); + for (int channel = 0; channel < maxOutputChannels; channel++) + { + std::stringstream chNameStream; + //A better implementation would be to retrieve the names from ASIO or CoreAudio interfaces + 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()) + { + //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; + paErr = Pa_OpenStream(&m_PortAudioStream, + pInS, + pOutS, + m_CurrentSamplingRate, + m_CurrentBufferSize, + paDitherOff, + WCMRPortAudioDevice::TheCallback, + this); + + 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 stream " << paErr << std::endl; + DEBUG_MSG( "Failed to open pa stream 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(); + + // Notify the Application about reset + m_pMyManager->NotifyClient (WCMRAudioDeviceManagerClient::RequestReset); + + // 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; + SetEvent(m_hResetFromDevRequestedEvent); + 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::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; +} |