John Emmas
b9fab9ea28
(whilst technically, it doesn't report any encountered errors, it should nevertheless be returning some kind of error status)
1703 lines
52 KiB
C++
1703 lines
52 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 <iostream>
|
|
#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 PLATFORM_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 PLATFORM_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)
|
|
{
|
|
WTErr retVal = eNoErr;
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|