[DEV] set member in private for internal backend interface

This commit is contained in:
Edouard DUPIN 2015-02-10 21:01:53 +01:00
parent 4febe7b119
commit 5c9361c199
18 changed files with 593 additions and 716 deletions

View File

@ -49,7 +49,6 @@ airtaudio::Api::Api() :
m_device[1] = 11111;
m_state = airtaudio::state_closed;
m_mode = airtaudio::mode_unknow;
m_apiHandle = 0;
}
airtaudio::Api::~Api() {

View File

@ -38,13 +38,13 @@ namespace airtaudio {
virtual airtaudio::DeviceInfo getDeviceInfo(uint32_t _device) = 0;
virtual uint32_t getDefaultInputDevice();
virtual uint32_t getDefaultOutputDevice();
enum airtaudio::error openStream(airtaudio::StreamParameters *_outputParameters,
airtaudio::StreamParameters *_inputParameters,
enum airtaudio::error openStream(airtaudio::StreamParameters* _outputParameters,
airtaudio::StreamParameters* _inputParameters,
audio::format _format,
uint32_t _sampleRate,
uint32_t *_bufferFrames,
uint32_t* _bufferFrames,
airtaudio::AirTAudioCallback _callback,
airtaudio::StreamOptions *_options);
airtaudio::StreamOptions* _options);
virtual enum airtaudio::error closeStream();
virtual enum airtaudio::error startStream() = 0;
virtual enum airtaudio::error stopStream() = 0;
@ -62,8 +62,6 @@ namespace airtaudio {
protected:
mutable std::mutex m_mutex;
uint32_t m_device[2]; // Playback and record, respectively.
// TODO : Remove this use derivative property of the c++ class ...
void *m_apiHandle; // void pointer for API specific stream handle information
enum airtaudio::mode m_mode; // airtaudio::mode_output, airtaudio::mode_input, or airtaudio::mode_duplex.
enum airtaudio::state m_state; // STOPPED, RUNNING, or CLOSED
std::vector<char> m_userBuffer[2]; // Playback and record, respectively.

View File

@ -21,29 +21,30 @@ airtaudio::Api* airtaudio::api::Alsa::Create() {
return new airtaudio::api::Alsa();
}
// A structure to hold various information related to the ALSA API
// implementation.
struct AlsaHandle {
snd_pcm_t *handles[2];
bool synchronized;
bool xrun[2];
std::condition_variable runnable_cv;
bool runnable;
AlsaHandle() :
synchronized(false),
runnable(false) {
handles[0] = nullptr;
handles[1] = nullptr;
xrun[0] = false;
xrun[1] = false;
}
namespace airtaudio {
namespace api {
class AlsaPrivate {
public:
snd_pcm_t *handles[2];
bool synchronized;
bool xrun[2];
std::condition_variable runnable_cv;
bool runnable;
AlsaPrivate() :
synchronized(false),
runnable(false) {
handles[0] = nullptr;
handles[1] = nullptr;
xrun[0] = false;
xrun[1] = false;
}
};
};
};
airtaudio::api::Alsa::Alsa() {
airtaudio::api::Alsa::Alsa() :
m_private(new airtaudio::api::AlsaPrivate()) {
// Nothing to do here.
}
@ -631,19 +632,7 @@ foundDevice:
&& m_nUserChannels[modeToIdTable(_mode)] > 1) {
m_doConvertBuffer[modeToIdTable(_mode)] = true;
}
// Allocate the ApiHandle if necessary and then save.
AlsaHandle *apiInfo = nullptr;
if (m_apiHandle == nullptr) {
apiInfo = (AlsaHandle *) new AlsaHandle;
if (apiInfo == nullptr) {
ATA_ERROR("error allocating AlsaHandle memory.");
goto error;
}
m_apiHandle = (void *) apiInfo;
} else {
apiInfo = (AlsaHandle *) m_apiHandle;
}
apiInfo->handles[modeToIdTable(_mode)] = phandle;
m_private->handles[modeToIdTable(_mode)] = phandle;
phandle = 0;
// Allocate necessary internal buffers.
uint64_t bufferBytes;
@ -692,9 +681,9 @@ foundDevice:
// We had already set up an output stream.
m_mode = airtaudio::mode_duplex;
// Link the streams if possible.
apiInfo->synchronized = false;
if (snd_pcm_link(apiInfo->handles[0], apiInfo->handles[1]) == 0) {
apiInfo->synchronized = true;
m_private->synchronized = false;
if (snd_pcm_link(m_private->handles[0], m_private->handles[1]) == 0) {
m_private->synchronized = true;
} else {
ATA_ERROR("unable to synchronize input and output devices.");
// TODO : airtaudio::error_warning;
@ -712,16 +701,13 @@ foundDevice:
}
return true;
error:
if (apiInfo != nullptr) {
if (apiInfo->handles[0]) {
snd_pcm_close(apiInfo->handles[0]);
}
if (apiInfo->handles[1]) {
snd_pcm_close(apiInfo->handles[1]);
}
delete apiInfo;
apiInfo = nullptr;
m_apiHandle = 0;
if (m_private->handles[0]) {
snd_pcm_close(m_private->handles[0]);
m_private->handles[0] = nullptr;
}
if (m_private->handles[1]) {
snd_pcm_close(m_private->handles[1]);
m_private->handles[1] = nullptr;
}
if (phandle) {
snd_pcm_close(phandle);
@ -742,12 +728,11 @@ enum airtaudio::error airtaudio::api::Alsa::closeStream() {
ATA_ERROR("no open stream to close!");
return airtaudio::error_warning;
}
AlsaHandle *apiInfo = (AlsaHandle *) m_apiHandle;
m_callbackInfo.isRunning = false;
m_mutex.lock();
if (m_state == airtaudio::state_stopped) {
apiInfo->runnable = true;
apiInfo->runnable_cv.notify_one();
m_private->runnable = true;
m_private->runnable_cv.notify_one();
}
m_mutex.unlock();
if (m_callbackInfo.thread != nullptr) {
@ -757,23 +742,21 @@ enum airtaudio::error airtaudio::api::Alsa::closeStream() {
m_state = airtaudio::state_stopped;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
snd_pcm_drop(apiInfo->handles[0]);
snd_pcm_drop(m_private->handles[0]);
}
if ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex) {
snd_pcm_drop(apiInfo->handles[1]);
snd_pcm_drop(m_private->handles[1]);
}
}
if (apiInfo != nullptr) {
if (apiInfo->handles[0]) {
snd_pcm_close(apiInfo->handles[0]);
}
if (apiInfo->handles[1]) {
snd_pcm_close(apiInfo->handles[1]);
}
delete apiInfo;
apiInfo = nullptr;
m_apiHandle = 0;
// close all stream :
if (m_private->handles[0]) {
snd_pcm_close(m_private->handles[0]);
m_private->handles[0] = nullptr;
}
if (m_private->handles[1]) {
snd_pcm_close(m_private->handles[1]);
m_private->handles[1] = nullptr;
}
for (int32_t iii=0; iii<2; ++iii) {
m_userBuffer[iii].clear();
@ -799,8 +782,7 @@ enum airtaudio::error airtaudio::api::Alsa::startStream() {
std::unique_lock<std::mutex> lck(m_mutex);
int32_t result = 0;
snd_pcm_state_t state;
AlsaHandle *apiInfo = (AlsaHandle *) m_apiHandle;
snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;
snd_pcm_t **handle = (snd_pcm_t **) m_private->handles;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (handle[0] == nullptr) {
@ -820,7 +802,7 @@ enum airtaudio::error airtaudio::api::Alsa::startStream() {
}
if ( ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex)
&& !apiInfo->synchronized) {
&& !m_private->synchronized) {
if (handle[1] == nullptr) {
ATA_ERROR("send nullptr to alsa ...");
if (handle[0] != nullptr) {
@ -838,8 +820,8 @@ enum airtaudio::error airtaudio::api::Alsa::startStream() {
}
m_state = airtaudio::state_running;
unlock:
apiInfo->runnable = true;
apiInfo->runnable_cv.notify_one();
m_private->runnable = true;
m_private->runnable_cv.notify_one();
if (result >= 0) {
return airtaudio::error_none;
}
@ -857,11 +839,10 @@ enum airtaudio::error airtaudio::api::Alsa::stopStream() {
m_state = airtaudio::state_stopped;
std::unique_lock<std::mutex> lck(m_mutex);
int32_t result = 0;
AlsaHandle *apiInfo = (AlsaHandle *) m_apiHandle;
snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;
snd_pcm_t **handle = (snd_pcm_t **) m_private->handles;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (apiInfo->synchronized) {
if (m_private->synchronized) {
result = snd_pcm_drop(handle[0]);
} else {
result = snd_pcm_drain(handle[0]);
@ -873,7 +854,7 @@ enum airtaudio::error airtaudio::api::Alsa::stopStream() {
}
if ( ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex)
&& !apiInfo->synchronized) {
&& !m_private->synchronized) {
result = snd_pcm_drop(handle[1]);
if (result < 0) {
ATA_ERROR("error stopping input pcm device, " << snd_strerror(result) << ".");
@ -898,8 +879,7 @@ enum airtaudio::error airtaudio::api::Alsa::abortStream() {
m_state = airtaudio::state_stopped;
std::unique_lock<std::mutex> lck(m_mutex);
int32_t result = 0;
AlsaHandle *apiInfo = (AlsaHandle *) m_apiHandle;
snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;
snd_pcm_t **handle = (snd_pcm_t **) m_private->handles;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
result = snd_pcm_drop(handle[0]);
@ -910,7 +890,7 @@ enum airtaudio::error airtaudio::api::Alsa::abortStream() {
}
if ( ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex)
&& !apiInfo->synchronized) {
&& !m_private->synchronized) {
result = snd_pcm_drop(handle[1]);
if (result < 0) {
ATA_ERROR("error aborting input pcm device, " << snd_strerror(result) << ".");
@ -937,13 +917,12 @@ void airtaudio::api::Alsa::callbackEvent() {
}
void airtaudio::api::Alsa::callbackEventOneCycle() {
AlsaHandle *apiInfo = (AlsaHandle *) m_apiHandle;
if (m_state == airtaudio::state_stopped) {
std::unique_lock<std::mutex> lck(m_mutex);
// TODO : Set this back ....
/*
while (!apiInfo->runnable) {
apiInfo->runnable_cv.wait(lck);
while (!m_private->runnable) {
m_private->runnable_cv.wait(lck);
}
*/
if (m_state != airtaudio::state_running) {
@ -957,13 +936,13 @@ void airtaudio::api::Alsa::callbackEventOneCycle() {
int32_t doStopStream = 0;
double streamTime = getStreamTime();
enum airtaudio::status status = airtaudio::status_ok;
if (m_mode != airtaudio::mode_input && apiInfo->xrun[0] == true) {
if (m_mode != airtaudio::mode_input && m_private->xrun[0] == true) {
status = airtaudio::status_underflow;
apiInfo->xrun[0] = false;
m_private->xrun[0] = false;
}
if (m_mode != airtaudio::mode_output && apiInfo->xrun[1] == true) {
if (m_mode != airtaudio::mode_output && m_private->xrun[1] == true) {
status = airtaudio::status_overflow;
apiInfo->xrun[1] = false;
m_private->xrun[1] = false;
}
doStopStream = m_callbackInfo.callback(&m_userBuffer[0][0],
&m_userBuffer[1][0],
@ -985,7 +964,7 @@ void airtaudio::api::Alsa::callbackEventOneCycle() {
snd_pcm_t **handle;
snd_pcm_sframes_t frames;
audio::format format;
handle = (snd_pcm_t **) apiInfo->handles;
handle = (snd_pcm_t **) m_private->handles;
if ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex) {
// Setup parameters.
@ -1013,7 +992,7 @@ void airtaudio::api::Alsa::callbackEventOneCycle() {
if (result == -EPIPE) {
snd_pcm_state_t state = snd_pcm_state(handle[1]);
if (state == SND_PCM_STATE_XRUN) {
apiInfo->xrun[1] = true;
m_private->xrun[1] = true;
result = snd_pcm_prepare(handle[1]);
if (result < 0) {
ATA_ERROR("error preparing device after overrun, " << snd_strerror(result) << ".");
@ -1076,7 +1055,7 @@ tryOutput:
if (result == -EPIPE) {
snd_pcm_state_t state = snd_pcm_state(handle[0]);
if (state == SND_PCM_STATE_XRUN) {
apiInfo->xrun[0] = true;
m_private->xrun[0] = true;
result = snd_pcm_prepare(handle[0]);
if (result < 0) {
ATA_ERROR("error preparing device after underrun, " << snd_strerror(result) << ".");

View File

@ -10,6 +10,7 @@
namespace airtaudio {
namespace api {
class AlsaPrivate;
class Alsa: public airtaudio::Api {
public:
static airtaudio::Api* Create();
@ -34,6 +35,7 @@ namespace airtaudio {
private:
static void alsaCallbackEvent(void* _userData);
private:
std::unique_ptr<AlsaPrivate> m_private;
std::vector<airtaudio::DeviceInfo> m_devices;
void saveDeviceInfo();
bool probeDeviceOpen(uint32_t _device,

View File

@ -47,25 +47,31 @@ static ASIODriverInfo driverInfo;
static CallbackInfo *asioCallbackInfo;
static bool asioXRun;
struct AsioHandle {
int32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
ASIOBufferInfo *bufferInfos;
HANDLE condition;
AsioHandle() :
drainCounter(0),
internalDrain(false),
bufferInfos(0) {
namespace airtaudio {
namespace api {
class AsioPrivate {
public:
int32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
ASIOBufferInfo *bufferInfos;
HANDLE condition;
AsioPrivate() :
drainCounter(0),
internalDrain(false),
bufferInfos(0) {
}
};
}
};
}
// Function declarations (definitions at end of section)
static const char* getAsioErrorString(ASIOError _result);
static void sampleRateChanged(ASIOSampleRate _sRate);
static long asioMessages(long _selector, long _value, void* _message, double* _opt);
airtaudio::api::Asio::Asio() {
airtaudio::api::Asio::Asio() :
m_private(new airtaudio::api::AsioPrivate()) {
// ASIO cannot run on a multi-threaded appartment. You can call
// CoInitialize beforehand, but it must be for appartment threading
// (in which case, CoInitilialize will return S_FALSE here).
@ -403,23 +409,12 @@ bool airtaudio::api::Asio::probeDeviceOpen(uint32_t _device,
m_nBuffers = 2;
// ASIO always uses non-interleaved buffers.
m_deviceInterleaved[modeToIdTable(_mode)] = false;
// Allocate, if necessary, our AsioHandle structure for the stream.
AsioHandle *handle = (AsioHandle *) m_apiHandle;
if (handle == nullptr) {
handle = new AsioHandle;
if (handle == nullptr) {
drivers.removeCurrentDriver();
ATA_ERROR("error allocating AsioHandle memory.");
return false;
}
handle->bufferInfos = 0;
// Create a manual-reset event.
handle->condition = CreateEvent(nullptr, // no security
TRUE, // manual-reset
FALSE, // non-signaled initially
nullptr); // unnamed
m_apiHandle = (void *) handle;
}
m_private->bufferInfos = 0;
// Create a manual-reset event.
m_private->condition = CreateEvent(nullptr, // no security
TRUE, // manual-reset
FALSE, // non-signaled initially
nullptr); // unnamed
// Create the ASIO internal buffers. Since RtAudio sets up input
// and output separately, we'll have to dispose of previously
// created output buffers for a duplex stream.
@ -427,21 +422,21 @@ bool airtaudio::api::Asio::probeDeviceOpen(uint32_t _device,
if ( _mode == airtaudio::mode_input
&& m_mode == airtaudio::mode_output) {
ASIODisposeBuffers();
if (handle->bufferInfos == nullptr) {
free(handle->bufferInfos);
handle->bufferInfos = nullptr;
if (m_private->bufferInfos == nullptr) {
free(m_private->bufferInfos);
m_private->bufferInfos = nullptr;
}
}
// Allocate, initialize, and save the bufferInfos in our stream callbackInfo structure.
bool buffersAllocated = false;
uint32_t i, nChannels = m_nDeviceChannels[0] + m_nDeviceChannels[1];
handle->bufferInfos = (ASIOBufferInfo *) malloc(nChannels * sizeof(ASIOBufferInfo));
if (handle->bufferInfos == nullptr) {
m_private->bufferInfos = (ASIOBufferInfo *) malloc(nChannels * sizeof(ASIOBufferInfo));
if (m_private->bufferInfos == nullptr) {
ATA_ERROR("error allocating bufferInfo memory for driver (" << driverName << ").");
goto error;
}
ASIOBufferInfo *infos;
infos = handle->bufferInfos;
infos = m_private->bufferInfos;
for (i=0; i<m_nDeviceChannels[0]; i++, infos++) {
infos->isInput = ASIOFalse;
infos->channelNum = i + m_channelOffset[0];
@ -457,7 +452,7 @@ bool airtaudio::api::Asio::probeDeviceOpen(uint32_t _device,
asioCallbacks.sampleRateDidChange = &sampleRateChanged;
asioCallbacks.asioMessage = &asioMessages;
asioCallbacks.bufferSwitchTimeInfo = nullptr;
result = ASIOCreateBuffers(handle->bufferInfos, nChannels, m_bufferSize, &asioCallbacks);
result = ASIOCreateBuffers(m_private->bufferInfos, nChannels, m_bufferSize, &asioCallbacks);
if (result != ASE_OK) {
ATA_ERROR("driver (" << driverName << ") error (" << getAsioErrorString(result) << ") creating buffers.");
goto error;
@ -536,15 +531,10 @@ error:
ASIODisposeBuffers();
}
drivers.removeCurrentDriver();
if (handle) {
CloseHandle(handle->condition);
if (handle->bufferInfos) {
free(handle->bufferInfos);
handle->bufferInfos = nullptr;
}
delete handle;
handle = nullptr;
m_apiHandle = 0;
CloseHandle(m_private->condition);
if (m_private->bufferInfos != nullptr) {
free(m_private->bufferInfos);
m_private->bufferInfos = nullptr;
}
for (int32_t i=0; i<2; i++) {
if (m_userBuffer[i]) {
@ -570,14 +560,9 @@ enum airtaudio::error airtaudio::api::Asio::closeStream() {
}
ASIODisposeBuffers();
drivers.removeCurrentDriver();
AsioHandle *handle = (AsioHandle *) m_apiHandle;
if (handle) {
CloseHandle(handle->condition);
if (handle->bufferInfos) {
free(handle->bufferInfos);
}
delete handle;
m_apiHandle = 0;
CloseHandle(m_private->condition);
if (m_private->bufferInfos) {
free(m_private->bufferInfos);
}
for (int32_t i=0; i<2; i++) {
if (m_userBuffer[i]) {
@ -604,15 +589,14 @@ enum airtaudio::error airtaudio::api::Asio::startStream() {
ATA_ERROR("the stream is already running!");
return airtaudio::error_warning;
}
AsioHandle *handle = (AsioHandle *) m_apiHandle;
ASIOError result = ASIOStart();
if (result != ASE_OK) {
ATA_ERROR("error (" << getAsioErrorString(result) << ") starting device.");
goto unlock;
}
handle->drainCounter = 0;
handle->internalDrain = false;
ResetEvent(handle->condition);
m_private->drainCounter = 0;
m_private->internalDrain = false;
ResetEvent(m_private->condition);
m_state = airtaudio::state_running;
asioXRun = false;
unlock:
@ -631,11 +615,10 @@ enum airtaudio::error airtaudio::api::Asio::stopStream() {
ATA_ERROR("the stream is already stopped!");
return airtaudio::error_warning;
}
AsioHandle *handle = (AsioHandle *) m_apiHandle;
if (m_mode == airtaudio::mode_output || m_mode == airtaudio::mode_duplex) {
if (handle->drainCounter == 0) {
handle->drainCounter = 2;
WaitForSingleObject(handle->condition, INFINITE); // block until signaled
if (m_private->drainCounter == 0) {
m_private->drainCounter = 2;
WaitForSingleObject(m_private->condition, INFINITE); // block until signaled
}
}
m_state = airtaudio::state_stopped;
@ -663,7 +646,6 @@ enum airtaudio::error airtaudio::api::Asio::abortStream() {
// noted where the device buffers need to be zeroed to avoid
// continuing sound, even when the device buffers are completely
// disposed. So now, calling abort is the same as calling stop.
// AsioHandle *handle = (AsioHandle *) m_apiHandle;
// handle->drainCounter = 2;
return stopStream();
}
@ -691,22 +673,25 @@ bool airtaudio::api::Asio::callbackEvent(long bufferIndex) {
return false;
}
CallbackInfo *info = (CallbackInfo *) &m_callbackInfo;
AsioHandle *handle = (AsioHandle *) m_apiHandle;
// Check if we were draining the stream and signal if finished.
if (handle->drainCounter > 3) {
if (m_private->drainCounter > 3) {
m_state = airtaudio::state_stopping;
if (handle->internalDrain == false) {
SetEvent(handle->condition);
if (m_private->internalDrain == false) {
SetEvent(m_private->condition);
} else { // spawn a thread to stop the stream
unsigned threadId;
m_callbackInfo.thread = _beginthreadex(nullptr, 0, &asioStopStream,
&m_callbackInfo, 0, &threadId);
m_callbackInfo.thread = _beginthreadex(nullptr,
0,
&asioStopStream,
&m_callbackInfo,
0,
&threadId);
}
return true;
}
// Invoke user callback to get fresh output data UNLESS we are
// draining stream.
if (handle->drainCounter == 0) {
if (m_private->drainCounter == 0) {
double streamTime = getStreamTime();
rtaudio::streamStatus status = 0;
if (m_mode != airtaudio::mode_input && asioXRun == true) {
@ -724,7 +709,7 @@ bool airtaudio::api::Asio::callbackEvent(long bufferIndex) {
status);
if (cbReturnValue == 2) {
m_state = airtaudio::state_stopping;
handle->drainCounter = 2;
m_private->drainCounter = 2;
unsigned threadId;
m_callbackInfo.thread = _beginthreadex(nullptr,
0,
@ -734,8 +719,8 @@ bool airtaudio::api::Asio::callbackEvent(long bufferIndex) {
&threadId);
return true;
} else if (cbReturnValue == 1) {
handle->drainCounter = 1;
handle->internalDrain = true;
m_private->drainCounter = 1;
m_private->internalDrain = true;
}
}
uint32_t nChannels, bufferBytes, i, j;
@ -743,10 +728,10 @@ bool airtaudio::api::Asio::callbackEvent(long bufferIndex) {
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
bufferBytes = m_bufferSize * audio::getFormatBytes(m_deviceFormat[0]);
if (handle->drainCounter > 1) { // write zeros to the output stream
if (m_private->drainCounter > 1) { // write zeros to the output stream
for (i=0, j=0; i<nChannels; i++) {
if (handle->bufferInfos[i].isInput != ASIOTrue) {
memset(handle->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes);
if (m_private->bufferInfos[i].isInput != ASIOTrue) {
memset(m_private->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes);
}
}
} else if (m_doConvertBuffer[0]) {
@ -757,8 +742,8 @@ bool airtaudio::api::Asio::callbackEvent(long bufferIndex) {
m_deviceFormat[0]);
}
for (i=0, j=0; i<nChannels; i++) {
if (handle->bufferInfos[i].isInput != ASIOTrue) {
memcpy(handle->bufferInfos[i].buffers[bufferIndex],
if (m_private->bufferInfos[i].isInput != ASIOTrue) {
memcpy(m_private->bufferInfos[i].buffers[bufferIndex],
&m_deviceBuffer[j++*bufferBytes],
bufferBytes);
}
@ -770,15 +755,15 @@ bool airtaudio::api::Asio::callbackEvent(long bufferIndex) {
m_userFormat);
}
for (i=0, j=0; i<nChannels; i++) {
if (handle->bufferInfos[i].isInput != ASIOTrue) {
memcpy(handle->bufferInfos[i].buffers[bufferIndex],
if (m_private->bufferInfos[i].isInput != ASIOTrue) {
memcpy(m_private->bufferInfos[i].buffers[bufferIndex],
&m_userBuffer[0][bufferBytes*j++],
bufferBytes);
}
}
}
if (handle->drainCounter) {
handle->drainCounter++;
if (m_private->drainCounter) {
m_private->drainCounter++;
goto unlock;
}
}
@ -788,9 +773,9 @@ bool airtaudio::api::Asio::callbackEvent(long bufferIndex) {
if (m_doConvertBuffer[1]) {
// Always interleave ASIO input data.
for (i=0, j=0; i<nChannels; i++) {
if (handle->bufferInfos[i].isInput == ASIOTrue) {
if (m_private->bufferInfos[i].isInput == ASIOTrue) {
memcpy(&m_deviceBuffer[j++*bufferBytes],
handle->bufferInfos[i].buffers[bufferIndex],
m_private->bufferInfos[i].buffers[bufferIndex],
bufferBytes);
}
}
@ -804,9 +789,9 @@ bool airtaudio::api::Asio::callbackEvent(long bufferIndex) {
m_convertInfo[1]);
} else {
for (i=0, j=0; i<nChannels; i++) {
if (handle->bufferInfos[i].isInput == ASIOTrue) {
if (m_private->bufferInfos[i].isInput == ASIOTrue) {
memcpy(&m_userBuffer[1][bufferBytes*j++],
handle->bufferInfos[i].buffers[bufferIndex],
m_private->bufferInfos[i].buffers[bufferIndex],
bufferBytes);
}
}

View File

@ -10,6 +10,7 @@
namespace airtaudio {
namespace api {
class AsioPrivate:
class Asio: public airtaudio::Api {
public:
static airtaudio::Api* Create();
@ -32,6 +33,7 @@ namespace airtaudio {
// will most likely produce highly undesireable results!
bool callbackEvent(long _bufferIndex);
private:
std::unique_ptr<AsioPrivate> m_private;
std::vector<airtaudio::DeviceInfo> m_devices;
void saveDeviceInfo();
bool m_coInitialized;

View File

@ -24,49 +24,38 @@ airtaudio::Api* airtaudio::api::Core::Create() {
#undef __class__
#define __class__ "api::Core"
// The OS X CoreAudio API is designed to use a separate callback
// procedure for each of its audio devices. A single RtAudio duplex
// stream using two different devices is supported here, though it
// cannot be guaranteed to always behave correctly because we cannot
// synchronize these two callbacks.
//
// A property listener is installed for over/underrun information.
// However, no functionality is currently provided to allow property
// listeners to trigger user handlers because it is unclear what could
// be done if a critical stream parameter (buffer size, sample rate,
// device disconnect) notification arrived. The listeners entail
// quite a bit of extra code and most likely, a user program wouldn't
// be prepared for the result anyway. However, we do provide a flag
// to the client callback function to inform of an over/underrun.
// A structure to hold various information related to the CoreAudio API
// implementation.
struct CoreHandle {
AudioDeviceID id[2]; // device ids
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
AudioDeviceIOProcID procId[2];
#endif
uint32_t iStream[2]; // device stream index (or first if using multiple)
uint32_t nStreams[2]; // number of streams to use
bool xrun[2];
char *deviceBuffer;
std::condition_variable condition;
int32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
CoreHandle() :
deviceBuffer(0),
drainCounter(0),
internalDrain(false) {
nStreams[0] = 1;
nStreams[1] = 1;
id[0] = 0;
id[1] = 0;
xrun[0] = false;
xrun[1] = false;
namespace airtaudio {
namespace api {
class CorePrivate {
public:
AudioDeviceID id[2]; // device ids
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
AudioDeviceIOProcID procId[2];
#endif
uint32_t iStream[2]; // device stream index (or first if using multiple)
uint32_t nStreams[2]; // number of streams to use
bool xrun[2];
char *deviceBuffer;
std::condition_variable condition;
int32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
CorePrivate() :
deviceBuffer(0),
drainCounter(0),
internalDrain(false) {
nStreams[0] = 1;
nStreams[1] = 1;
id[0] = 0;
id[1] = 0;
xrun[0] = false;
xrun[1] = false;
}
};
}
};
}
airtaudio::api::Core::Core() {
airtaudio::api::Core::Core() :
m_private(new airtaudio::api::CorePrivate()) {
#if defined(AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER)
// This is a largely undocumented but absolutely necessary
// requirement starting with OS-X 10.6. If not called, queries and
@ -402,14 +391,14 @@ OSStatus airtaudio::api::Core::callbackEvent(AudioDeviceID _inDevice,
static OSStatus xrunListener(AudioObjectID _inDevice,
uint32_t _nAddresses,
const AudioObjectPropertyAddress _properties[],
void* _handlePointer) {
CoreHandle* handle = (CoreHandle*)_handlePointer;
void* _userData) {
airtaudio::api::Core* myClass = reinterpret_cast<airtaudio::api::Core*>(_userData);
for (uint32_t i=0; i<_nAddresses; i++) {
if (_properties[i].mSelector == kAudioDeviceProcessorOverload) {
if (_properties[i].mScope == kAudioDevicePropertyScopeInput) {
handle->xrun[1] = true;
myClass->m_private->xrun[1] = true;
} else {
handle->xrun[0] = true;
myClass->m_private->xrun[0] = true;
}
}
}
@ -803,21 +792,9 @@ bool airtaudio::api::Core::probeDeviceOpen(uint32_t _device,
} else if (monoMode) {
m_doConvertBuffer[modeToIdTable(_mode)] = true;
}
// Allocate our CoreHandle structure for the stream.
CoreHandle *handle = 0;
if (m_apiHandle == 0) {
handle = new CoreHandle;
if (handle == nullptr) {
ATA_ERROR("error allocating CoreHandle memory.");
return false;
}
m_apiHandle = (void *) handle;
} else {
handle = (CoreHandle *) m_apiHandle;
}
handle->iStream[modeToIdTable(_mode)] = firstStream;
handle->nStreams[modeToIdTable(_mode)] = streamCount;
handle->id[modeToIdTable(_mode)] = id;
m_private->iStream[modeToIdTable(_mode)] = firstStream;
m_private->nStreams[modeToIdTable(_mode)] = streamCount;
m_private->id[modeToIdTable(_mode)] = id;
// Allocate necessary internal buffers.
uint64_t bufferBytes;
bufferBytes = m_nUserChannels[modeToIdTable(_mode)] * *_bufferSize * audio::getFormatBytes(m_userFormat);
@ -832,7 +809,7 @@ bool airtaudio::api::Core::probeDeviceOpen(uint32_t _device,
// "device buffers". However, we can't do this if using multiple
// streams.
if ( m_doConvertBuffer[modeToIdTable(_mode)]
&& handle->nStreams[modeToIdTable(_mode)] > 1) {
&& m_private->nStreams[modeToIdTable(_mode)] > 1) {
bool makeBuffer = true;
bufferBytes = m_nDeviceChannels[modeToIdTable(_mode)] * audio::getFormatBytes(m_deviceFormat[modeToIdTable(_mode)]);
if (_mode == airtaudio::mode_input) {
@ -876,7 +853,7 @@ bool airtaudio::api::Core::probeDeviceOpen(uint32_t _device,
m_mode = airtaudio::mode_duplex;
} else {
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
result = AudioDeviceCreateIOProcID(id, &airtaudio::api::Core::callbackEvent, (void *) &m_callbackInfo, &handle->procId[modeToIdTable(_mode)]);
result = AudioDeviceCreateIOProcID(id, &airtaudio::api::Core::callbackEvent, (void *) &m_callbackInfo, &m_private->procId[modeToIdTable(_mode)]);
#else
// deprecated in favor of AudioDeviceCreateIOProcID()
result = AudioDeviceAddIOProc(id, &airtaudio::api::Core::callbackEvent, (void *) &m_callbackInfo);
@ -894,13 +871,9 @@ bool airtaudio::api::Core::probeDeviceOpen(uint32_t _device,
}
// Setup the device property listener for over/underload.
property.mSelector = kAudioDeviceProcessorOverload;
result = AudioObjectAddPropertyListener(id, &property, xrunListener, (void *) handle);
result = AudioObjectAddPropertyListener(id, &property, xrunListener, this);
return true;
error:
if (handle) {
delete handle;
m_apiHandle = 0;
}
for (int32_t i=0; i<2; i++) {
if (m_userBuffer[i]) {
free(m_userBuffer[i]);
@ -920,30 +893,29 @@ enum airtaudio::error airtaudio::api::Core::closeStream() {
ATA_ERROR("no open stream to close!");
return airtaudio::error_warning;
}
CoreHandle *handle = (CoreHandle *) m_apiHandle;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (m_state == airtaudio::state_running) {
AudioDeviceStop(handle->id[0], &airtaudio::api::Core::callbackEvent);
AudioDeviceStop(m_private->id[0], &airtaudio::api::Core::callbackEvent);
}
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
AudioDeviceDestroyIOProcID(handle->id[0], handle->procId[0]);
AudioDeviceDestroyIOProcID(m_private->id[0], m_private->procId[0]);
#else
// deprecated in favor of AudioDeviceDestroyIOProcID()
AudioDeviceRemoveIOProc(handle->id[0], &airtaudio::api::Core::callbackEvent);
AudioDeviceRemoveIOProc(m_private->id[0], &airtaudio::api::Core::callbackEvent);
#endif
}
if ( m_mode == airtaudio::mode_input
|| ( m_mode == airtaudio::mode_duplex
&& m_device[0] != m_device[1])) {
if (m_state == airtaudio::state_running) {
AudioDeviceStop(handle->id[1], &airtaudio::api::Core::callbackEvent);
AudioDeviceStop(m_private->id[1], &airtaudio::api::Core::callbackEvent);
}
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
AudioDeviceDestroyIOProcID(handle->id[1], handle->procId[1]);
AudioDeviceDestroyIOProcID(m_private->id[1], m_private->procId[1]);
#else
// deprecated in favor of AudioDeviceDestroyIOProcID()
AudioDeviceRemoveIOProc(handle->id[1], &airtaudio::api::Core::callbackEvent);
AudioDeviceRemoveIOProc(m_private->id[1], &airtaudio::api::Core::callbackEvent);
#endif
}
for (int32_t i=0; i<2; i++) {
@ -956,8 +928,6 @@ enum airtaudio::error airtaudio::api::Core::closeStream() {
free(m_deviceBuffer);
m_deviceBuffer = nullptr;
}
delete handle;
m_apiHandle = 0;
m_mode = airtaudio::mode_unknow;
m_state = airtaudio::state_closed;
return airtaudio::error_none;
@ -972,10 +942,9 @@ enum airtaudio::error airtaudio::api::Core::startStream() {
return airtaudio::error_warning;
}
OSStatus result = noErr;
CoreHandle *handle = (CoreHandle *) m_apiHandle;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
result = AudioDeviceStart(handle->id[0], &airtaudio::api::Core::callbackEvent);
result = AudioDeviceStart(m_private->id[0], &airtaudio::api::Core::callbackEvent);
if (result != noErr) {
ATA_ERROR("system error (" << getErrorCode(result) << ") starting callback procedure on device (" << m_device[0] << ").");
goto unlock;
@ -984,14 +953,14 @@ enum airtaudio::error airtaudio::api::Core::startStream() {
if ( m_mode == airtaudio::mode_input
|| ( m_mode == airtaudio::mode_duplex
&& m_device[0] != m_device[1])) {
result = AudioDeviceStart(handle->id[1], &airtaudio::api::Core::callbackEvent);
result = AudioDeviceStart(m_private->id[1], &airtaudio::api::Core::callbackEvent);
if (result != noErr) {
ATA_ERROR("system error starting input callback procedure on device (" << m_device[1] << ").");
goto unlock;
}
}
handle->drainCounter = 0;
handle->internalDrain = false;
m_private->drainCounter = 0;
m_private->internalDrain = false;
m_state = airtaudio::state_running;
unlock:
if (result == noErr) {
@ -1009,15 +978,14 @@ enum airtaudio::error airtaudio::api::Core::stopStream() {
return airtaudio::error_warning;
}
OSStatus result = noErr;
CoreHandle *handle = (CoreHandle *) m_apiHandle;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (handle->drainCounter == 0) {
if (m_private->drainCounter == 0) {
std::unique_lock<std::mutex> lck(m_mutex);
handle->drainCounter = 2;
handle->condition.wait(lck);
m_private->drainCounter = 2;
m_private->condition.wait(lck);
}
result = AudioDeviceStop(handle->id[0], &airtaudio::api::Core::callbackEvent);
result = AudioDeviceStop(m_private->id[0], &airtaudio::api::Core::callbackEvent);
if (result != noErr) {
ATA_ERROR("system error (" << getErrorCode(result) << ") stopping callback procedure on device (" << m_device[0] << ").");
goto unlock;
@ -1026,7 +994,7 @@ enum airtaudio::error airtaudio::api::Core::stopStream() {
if ( m_mode == airtaudio::mode_input
|| ( m_mode == airtaudio::mode_duplex
&& m_device[0] != m_device[1])) {
result = AudioDeviceStop(handle->id[1], &airtaudio::api::Core::callbackEvent);
result = AudioDeviceStop(m_private->id[1], &airtaudio::api::Core::callbackEvent);
if (result != noErr) {
ATA_ERROR("system error (" << getErrorCode(result) << ") stopping input callback procedure on device (" << m_device[1] << ").");
goto unlock;
@ -1048,8 +1016,7 @@ enum airtaudio::error airtaudio::api::Core::abortStream() {
ATA_ERROR("the stream is already stopped!");
return airtaudio::error_warning;
}
CoreHandle* handle = (CoreHandle*)m_apiHandle;
handle->drainCounter = 2;
m_private->drainCounter = 2;
return stopStream();
}
@ -1075,34 +1042,33 @@ bool airtaudio::api::Core::callbackEvent(AudioDeviceID _deviceId,
return false;
}
CallbackInfo *info = (CallbackInfo *) &m_callbackInfo;
CoreHandle *handle = (CoreHandle *) m_apiHandle;
// Check if we were draining the stream and signal is finished.
if (handle->drainCounter > 3) {
if (m_private->drainCounter > 3) {
m_state = airtaudio::state_stopping;
if (handle->internalDrain == true) {
if (m_private->internalDrain == true) {
new std::thread(&airtaudio::api::Core::coreStopStream, this);
} else {
// external call to stopStream()
handle->condition.notify_one();
m_private->condition.notify_one();
}
return true;
}
AudioDeviceID outputDevice = handle->id[0];
AudioDeviceID outputDevice = m_private->id[0];
// Invoke user callback to get fresh output data UNLESS we are
// draining stream or duplex mode AND the input/output devices are
// different AND this function is called for the input device.
if (handle->drainCounter == 0 && (m_mode != airtaudio::mode_duplex || _deviceId == outputDevice)) {
if (m_private->drainCounter == 0 && (m_mode != airtaudio::mode_duplex || _deviceId == outputDevice)) {
double streamTime = getStreamTime();
enum airtaudio::status status = airtaudio::status_ok;
if ( m_mode != airtaudio::mode_input
&& handle->xrun[0] == true) {
&& m_private->xrun[0] == true) {
status |= airtaudio::status_underflow;
handle->xrun[0] = false;
m_private->xrun[0] = false;
}
if ( m_mode != airtaudio::mode_output
&& handle->xrun[1] == true) {
&& m_private->xrun[1] == true) {
status |= airtaudio::mode_input_OVERFLOW;
handle->xrun[1] = false;
m_private->xrun[1] = false;
}
int32_t cbReturnValue = info->callback(m_userBuffer[0],
m_userBuffer[1],
@ -1111,42 +1077,42 @@ bool airtaudio::api::Core::callbackEvent(AudioDeviceID _deviceId,
status);
if (cbReturnValue == 2) {
m_state = airtaudio::state_stopping;
handle->drainCounter = 2;
m_private->drainCounter = 2;
abortStream();
return true;
} else if (cbReturnValue == 1) {
handle->drainCounter = 1;
handle->internalDrain = true;
m_private->drainCounter = 1;
m_private->internalDrain = true;
}
}
if ( m_mode == airtaudio::mode_output
|| ( m_mode == airtaudio::mode_duplex
&& _deviceId == outputDevice)) {
if (handle->drainCounter > 1) {
if (m_private->drainCounter > 1) {
// write zeros to the output stream
if (handle->nStreams[0] == 1) {
memset(_outBufferList->mBuffers[handle->iStream[0]].mData,
if (m_private->nStreams[0] == 1) {
memset(_outBufferList->mBuffers[m_private->iStream[0]].mData,
0,
_outBufferList->mBuffers[handle->iStream[0]].mDataByteSize);
_outBufferList->mBuffers[m_private->iStream[0]].mDataByteSize);
} else {
// fill multiple streams with zeros
for (uint32_t i=0; i<handle->nStreams[0]; i++) {
memset(_outBufferList->mBuffers[handle->iStream[0]+i].mData,
for (uint32_t i=0; i<m_private->nStreams[0]; i++) {
memset(_outBufferList->mBuffers[m_private->iStream[0]+i].mData,
0,
_outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize);
_outBufferList->mBuffers[m_private->iStream[0]+i].mDataByteSize);
}
}
} else if (handle->nStreams[0] == 1) {
} else if (m_private->nStreams[0] == 1) {
if (m_doConvertBuffer[0]) {
// convert directly to CoreAudio stream buffer
convertBuffer((char*)_outBufferList->mBuffers[handle->iStream[0]].mData,
convertBuffer((char*)_outBufferList->mBuffers[m_private->iStream[0]].mData,
m_userBuffer[0],
m_convertInfo[0]);
} else {
// copy from user buffer
memcpy(_outBufferList->mBuffers[handle->iStream[0]].mData,
memcpy(_outBufferList->mBuffers[m_private->iStream[0]].mData,
m_userBuffer[0],
_outBufferList->mBuffers[handle->iStream[0]].mDataByteSize);
_outBufferList->mBuffers[m_private->iStream[0]].mDataByteSize);
}
} else {
// fill multiple streams
@ -1156,9 +1122,9 @@ bool airtaudio::api::Core::callbackEvent(AudioDeviceID _deviceId,
inBuffer = (float *) m_deviceBuffer;
}
if (m_deviceInterleaved[0] == false) { // mono mode
uint32_t bufferBytes = _outBufferList->mBuffers[handle->iStream[0]].mDataByteSize;
uint32_t bufferBytes = _outBufferList->mBuffers[m_private->iStream[0]].mDataByteSize;
for (uint32_t i=0; i<m_nUserChannels[0]; i++) {
memcpy(_outBufferList->mBuffers[handle->iStream[0]+i].mData,
memcpy(_outBufferList->mBuffers[m_private->iStream[0]+i].mData,
(void *)&inBuffer[i*m_bufferSize],
bufferBytes);
}
@ -1178,10 +1144,10 @@ bool airtaudio::api::Core::callbackEvent(AudioDeviceID _deviceId,
inOffset = m_bufferSize;
}
channelsLeft = inChannels;
for (uint32_t i=0; i<handle->nStreams[0]; i++) {
for (uint32_t i=0; i<m_private->nStreams[0]; i++) {
in = inBuffer;
out = (float *) _outBufferList->mBuffers[handle->iStream[0]+i].mData;
streamChannels = _outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels;
out = (float *) _outBufferList->mBuffers[m_private->iStream[0]+i].mData;
streamChannels = _outBufferList->mBuffers[m_private->iStream[0]+i].mNumberChannels;
outJump = 0;
// Account for possible channel offset in first stream
if (i == 0 && m_channelOffset[0] > 0) {
@ -1213,26 +1179,26 @@ bool airtaudio::api::Core::callbackEvent(AudioDeviceID _deviceId,
}
}
}
if (handle->drainCounter) {
handle->drainCounter++;
if (m_private->drainCounter) {
m_private->drainCounter++;
goto unlock;
}
}
AudioDeviceID inputDevice;
inputDevice = handle->id[1];
inputDevice = m_private->id[1];
if ( m_mode == airtaudio::mode_input
|| ( m_mode == airtaudio::mode_duplex
&& _deviceId == inputDevice)) {
if (handle->nStreams[1] == 1) {
if (m_private->nStreams[1] == 1) {
if (m_doConvertBuffer[1]) {
// convert directly from CoreAudio stream buffer
convertBuffer(m_userBuffer[1],
(char *) _inBufferList->mBuffers[handle->iStream[1]].mData,
(char *) _inBufferList->mBuffers[m_private->iStream[1]].mData,
m_convertInfo[1]);
} else { // copy to user buffer
memcpy(m_userBuffer[1],
_inBufferList->mBuffers[handle->iStream[1]].mData,
_inBufferList->mBuffers[handle->iStream[1]].mDataByteSize);
_inBufferList->mBuffers[m_private->iStream[1]].mData,
_inBufferList->mBuffers[m_private->iStream[1]].mDataByteSize);
}
} else { // read from multiple streams
float *outBuffer = (float *) m_userBuffer[1];
@ -1241,10 +1207,10 @@ bool airtaudio::api::Core::callbackEvent(AudioDeviceID _deviceId,
}
if (m_deviceInterleaved[1] == false) {
// mono mode
uint32_t bufferBytes = _inBufferList->mBuffers[handle->iStream[1]].mDataByteSize;
uint32_t bufferBytes = _inBufferList->mBuffers[m_private->iStream[1]].mDataByteSize;
for (uint32_t i=0; i<m_nUserChannels[1]; i++) {
memcpy((void *)&outBuffer[i*m_bufferSize],
_inBufferList->mBuffers[handle->iStream[1]+i].mData,
_inBufferList->mBuffers[m_private->iStream[1]+i].mData,
bufferBytes);
}
} else {
@ -1263,10 +1229,10 @@ bool airtaudio::api::Core::callbackEvent(AudioDeviceID _deviceId,
outOffset = m_bufferSize;
}
channelsLeft = outChannels;
for (uint32_t i=0; i<handle->nStreams[1]; i++) {
for (uint32_t i=0; i<m_private->nStreams[1]; i++) {
out = outBuffer;
in = (float *) _inBufferList->mBuffers[handle->iStream[1]+i].mData;
streamChannels = _inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels;
in = (float *) _inBufferList->mBuffers[m_private->iStream[1]+i].mData;
streamChannels = _inBufferList->mBuffers[m_private->iStream[1]+i].mNumberChannels;
inJump = 0;
// Account for possible channel offset in first stream
if (i == 0 && m_channelOffset[1] > 0) {

View File

@ -12,6 +12,7 @@
namespace airtaudio {
namespace api {
class CorePrivate;
class Core: public airtaudio::Api {
public:
static airtaudio::Api* Create();
@ -46,6 +47,7 @@ namespace airtaudio {
void* _infoPointer);
void coreStopStream(void *_userData);
private:
std::unique_ptr<CorePrivate> m_private;
bool probeDeviceOpen(uint32_t _device,
airtaudio::mode _mode,
uint32_t _channels,

View File

@ -46,7 +46,7 @@ namespace airtaudio {
void callBackEvent(void* _data,
int32_t _frameRate);
private:
CoreIosPrivate* m_private;
std::unique_ptr<CoreIosPrivate> m_private;
static OSStatus playbackCallback(void *_userData,
AudioUnitRenderActionFlags* _ioActionFlags,
const AudioTimeStamp* _inTimeStamp,

View File

@ -38,7 +38,7 @@ namespace airtaudio {
airtaudio::api::CoreIos::CoreIos(void) :
m_private(new airtaudio::api::CoreIosPrivate) {
m_private(new airtaudio::api::CoreIosPrivate()) {
ATA_INFO("new CoreIos");
int32_t deviceCount = 2;
ATA_ERROR("Get count devices : " << 2);

View File

@ -56,32 +56,36 @@ static inline DWORD dsPointerBetween(DWORD _pointer, DWORD _laterPointer, DWORD
return _pointer >= _earlierPointer && _pointer < _laterPointer;
}
// A structure to hold various information related to the DirectSound
// API implementation.
struct DsHandle {
uint32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
void *id[2];
void *buffer[2];
bool xrun[2];
UINT bufferPointer[2];
DWORD dsBufferSize[2];
DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by.
HANDLE condition;
DsHandle() :
drainCounter(0),
internalDrain(false) {
id[0] = 0;
id[1] = 0;
buffer[0] = 0;
buffer[1] = 0;
xrun[0] = false;
xrun[1] = false;
bufferPointer[0] = 0;
bufferPointer[1] = 0;
namespace airtaudio {
namespace api {
class DsPrivate {
public:
uint32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
void *id[2];
void *buffer[2];
bool xrun[2];
UINT bufferPointer[2];
DWORD dsBufferSize[2];
DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by.
HANDLE condition;
DsPrivate() :
drainCounter(0),
internalDrain(false) {
id[0] = 0;
id[1] = 0;
buffer[0] = 0;
buffer[1] = 0;
xrun[0] = false;
xrun[1] = false;
bufferPointer[0] = 0;
bufferPointer[1] = 0;
}
};
}
};
}
// Declarations for utility functions, callbacks, and structures
// specific to the DirectSound implementation.
@ -111,7 +115,8 @@ struct DsProbeData {
std::vector<struct DsDevice>* dsDevices;
};
airtaudio::api::Ds::Ds() {
airtaudio::api::Ds::Ds() :
m_private(new airtaudio::api::DsPrivate()) {
// Dsound will run both-threaded. If CoInitialize fails, then just
// accept whatever the mainline chose for a threading model.
m_coInitialized = false;
@ -755,26 +760,15 @@ bool airtaudio::api::Ds::probeDeviceOpen(uint32_t _device,
}
}
}
// Allocate our DsHandle structures for the stream.
if (m_apiHandle == 0) {
handle = new DsHandle;
if (handle == nullptr) {
ATA_ERROR("error allocating AsioHandle memory.");
goto error;
}
// Create a manual-reset event.
handle->condition = CreateEvent(nullptr, // no security
TRUE, // manual-reset
FALSE, // non-signaled initially
nullptr); // unnamed
m_apiHandle = (void *) handle;
} else {
handle = (DsHandle *) m_apiHandle;
}
handle->id[modeToIdTable(_mode)] = ohandle;
handle->buffer[modeToIdTable(_mode)] = bhandle;
handle->dsBufferSize[modeToIdTable(_mode)] = dsBufferSize;
handle->dsPointerLeadTime[modeToIdTable(_mode)] = dsPointerLeadTime;
// Create a manual-reset event.
m_private->condition = CreateEvent(nullptr, // no security
TRUE, // manual-reset
FALSE, // non-signaled initially
nullptr); // unnamed
m_private->id[modeToIdTable(_mode)] = ohandle;
m_private->buffer[modeToIdTable(_mode)] = bhandle;
m_private->dsBufferSize[modeToIdTable(_mode)] = dsBufferSize;
m_private->dsPointerLeadTime[modeToIdTable(_mode)] = dsPointerLeadTime;
m_device[modeToIdTable(_mode)] = _device;
m_state = airtaudio::state_stopped;
if ( m_mode == airtaudio::mode_output
@ -810,27 +804,23 @@ bool airtaudio::api::Ds::probeDeviceOpen(uint32_t _device,
}
return true;
error:
if (handle) {
if (handle->buffer[0]) {
// the object pointer can be nullptr and valid
LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0];
LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];
if (buffer) {
buffer->Release();
}
object->Release();
if (m_private->buffer[0]) {
// the object pointer can be nullptr and valid
LPDIRECTSOUND object = (LPDIRECTSOUND) m_private->id[0];
LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0];
if (buffer) {
buffer->Release();
}
if (handle->buffer[1]) {
LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1];
LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];
if (buffer != nullptr) {
buffer->Release();
}
}
CloseHandle(handle->condition);
delete handle;
m_apiHandle = 0;
object->Release();
}
if (m_private->buffer[1]) {
LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) m_private->id[1];
LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1];
if (buffer != nullptr) {
buffer->Release();
}
}
CloseHandle(m_private->condition);
for (int32_t i=0; i<2; i++) {
if (m_userBuffer[i]) {
free(m_userBuffer[i]);
@ -854,30 +844,25 @@ enum airtaudio::error airtaudio::api::Ds::closeStream() {
m_callbackInfo.isRunning = false;
WaitForSingleObject((HANDLE) m_callbackInfo.thread, INFINITE);
CloseHandle((HANDLE) m_callbackInfo.thread);
DsHandle *handle = (DsHandle *) m_apiHandle;
if (handle) {
if (handle->buffer[0]) { // the object pointer can be nullptr and valid
LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0];
LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];
if (buffer) {
buffer->Stop();
buffer->Release();
}
object->Release();
if (m_private->buffer[0]) { // the object pointer can be nullptr and valid
LPDIRECTSOUND object = (LPDIRECTSOUND) m_private->id[0];
LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0];
if (buffer) {
buffer->Stop();
buffer->Release();
}
if (handle->buffer[1]) {
LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1];
LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];
if (buffer) {
buffer->Stop();
buffer->Release();
}
object->Release();
}
CloseHandle(handle->condition);
delete handle;
m_apiHandle = 0;
object->Release();
}
if (m_private->buffer[1]) {
LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) m_private->id[1];
LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1];
if (buffer) {
buffer->Stop();
buffer->Release();
}
object->Release();
}
CloseHandle(m_private->condition);
for (int32_t i=0; i<2; i++) {
if (m_userBuffer[i]) {
free(m_userBuffer[i]);
@ -900,7 +885,6 @@ enum airtaudio::error airtaudio::api::Ds::startStream() {
ATA_ERROR("the stream is already running!");
return airtaudio::error_warning;
}
DsHandle *handle = (DsHandle *) m_apiHandle;
// Increase scheduler frequency on lesser windows (a side-effect of
// increasing timer accuracy). On greater windows (Win2K or later),
// this is already in effect.
@ -914,7 +898,7 @@ enum airtaudio::error airtaudio::api::Ds::startStream() {
HRESULT result = 0;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];
LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0];
result = buffer->Play(0, 0, DSBPLAY_LOOPING);
if (FAILED(result)) {
ATA_ERROR("error (" << getErrorString(result) << ") starting output buffer!");
@ -923,16 +907,16 @@ enum airtaudio::error airtaudio::api::Ds::startStream() {
}
if ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex) {
LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];
LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1];
result = buffer->Start(DSCBSTART_LOOPING);
if (FAILED(result)) {
ATA_ERROR("error (" << getErrorString(result) << ") starting input buffer!");
goto unlock;
}
}
handle->drainCounter = 0;
handle->internalDrain = false;
ResetEvent(handle->condition);
m_private->drainCounter = 0;
m_private->internalDrain = false;
ResetEvent(m_private->condition);
m_state = airtaudio::state_running;
unlock:
if (FAILED(result)) {
@ -952,16 +936,15 @@ enum airtaudio::error airtaudio::api::Ds::stopStream() {
HRESULT result = 0;
LPVOID audioPtr;
DWORD dataLen;
DsHandle *handle = (DsHandle *) m_apiHandle;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (handle->drainCounter == 0) {
handle->drainCounter = 2;
WaitForSingleObject(handle->condition, INFINITE); // block until signaled
if (m_private->drainCounter == 0) {
m_private->drainCounter = 2;
WaitForSingleObject(m_private->condition, INFINITE); // block until signaled
}
m_state = airtaudio::state_stopped;
// Stop the buffer and clear memory
LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];
LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0];
result = buffer->Stop();
if (FAILED(result)) {
ATA_ERROR("error (" << getErrorString(result) << ") stopping output buffer!");
@ -969,7 +952,7 @@ enum airtaudio::error airtaudio::api::Ds::stopStream() {
}
// Lock the buffer and clear it so that if we start to play again,
// we won't have old data playing.
result = buffer->Lock(0, handle->dsBufferSize[0], &audioPtr, &dataLen, nullptr, nullptr, 0);
result = buffer->Lock(0, m_private->dsBufferSize[0], &audioPtr, &dataLen, nullptr, nullptr, 0);
if (FAILED(result)) {
ATA_ERROR("error (" << getErrorString(result) << ") locking output buffer!");
goto unlock;
@ -983,11 +966,11 @@ enum airtaudio::error airtaudio::api::Ds::stopStream() {
goto unlock;
}
// If we start playing again, we must begin at beginning of buffer.
handle->bufferPointer[0] = 0;
m_private->bufferPointer[0] = 0;
}
if ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex) {
LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];
LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1];
audioPtr = nullptr;
dataLen = 0;
m_state = airtaudio::state_stopped;
@ -998,7 +981,7 @@ enum airtaudio::error airtaudio::api::Ds::stopStream() {
}
// Lock the buffer and clear it so that if we start to play again,
// we won't have old data playing.
result = buffer->Lock(0, handle->dsBufferSize[1], &audioPtr, &dataLen, nullptr, nullptr, 0);
result = buffer->Lock(0, m_private->dsBufferSize[1], &audioPtr, &dataLen, nullptr, nullptr, 0);
if (FAILED(result)) {
ATA_ERROR("error (" << getErrorString(result) << ") locking input buffer!");
goto unlock;
@ -1012,7 +995,7 @@ enum airtaudio::error airtaudio::api::Ds::stopStream() {
goto unlock;
}
// If we start recording again, we must begin at beginning of buffer.
handle->bufferPointer[1] = 0;
m_private->bufferPointer[1] = 0;
}
unlock:
timeEndPeriod(1); // revert to normal scheduler frequency on lesser windows.
@ -1030,8 +1013,7 @@ enum airtaudio::error airtaudio::api::Ds::abortStream() {
ATA_ERROR("the stream is already stopped!");
return airtaudio::error_warning;
}
DsHandle *handle = (DsHandle *) m_apiHandle;
handle->drainCounter = 2;
m_private->drainCounter = 2;
return stopStream();
}
@ -1045,12 +1027,11 @@ void airtaudio::api::Ds::callbackEvent() {
return;
}
CallbackInfo *info = (CallbackInfo *) &m_callbackInfo;
DsHandle *handle = (DsHandle *) m_apiHandle;
// Check if we were draining the stream and signal is finished.
if (handle->drainCounter > m_nBuffers + 2) {
if (m_private->drainCounter > m_nBuffers + 2) {
m_state = airtaudio::state_stopping;
if (handle->internalDrain == false) {
SetEvent(handle->condition);
if (m_private->internalDrain == false) {
SetEvent(m_private->condition);
} else {
stopStream();
}
@ -1058,18 +1039,18 @@ void airtaudio::api::Ds::callbackEvent() {
}
// Invoke user callback to get fresh output data UNLESS we are
// draining stream.
if (handle->drainCounter == 0) {
if (m_private->drainCounter == 0) {
double streamTime = getStreamTime();
rtaudio::streamStatus status = 0;
if ( m_mode != airtaudio::mode_input
&& handle->xrun[0] == true) {
&& m_private->xrun[0] == true) {
status |= RTAUDIO_airtaudio::status_underflow;
handle->xrun[0] = false;
m_private->xrun[0] = false;
}
if ( m_mode != airtaudio::mode_output
&& handle->xrun[1] == true) {
&& m_private->xrun[1] == true) {
status |= RTAUDIO_airtaudio::mode_input_OVERFLOW;
handle->xrun[1] = false;
m_private->xrun[1] = false;
}
int32_t cbReturnValue = info->callback(m_userBuffer[0],
m_userBuffer[1],
@ -1078,12 +1059,12 @@ void airtaudio::api::Ds::callbackEvent() {
status);
if (cbReturnValue == 2) {
m_state = airtaudio::state_stopping;
handle->drainCounter = 2;
m_private->drainCounter = 2;
abortStream();
return;
} else if (cbReturnValue == 1) {
handle->drainCounter = 1;
handle->internalDrain = true;
m_private->drainCounter = 1;
m_private->internalDrain = true;
}
}
HRESULT result;
@ -1098,7 +1079,7 @@ void airtaudio::api::Ds::callbackEvent() {
long bufferBytes;
if (m_buffersRolling == false) {
if (m_mode == airtaudio::mode_duplex) {
//assert(handle->dsBufferSize[0] == handle->dsBufferSize[1]);
//assert(m_private->dsBufferSize[0] == m_private->dsBufferSize[1]);
// It takes a while for the devices to get rolling. As a result,
// there's no guarantee that the capture and write device pointers
// will move in lockstep. Wait here for both devices to start
@ -1111,8 +1092,8 @@ void airtaudio::api::Ds::callbackEvent() {
// Realtime priority, maybe; but I'm not sure what priority the
// DirectSound service threads run at. We *should* be roughly
// within a ms or so of correct.
LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];
LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];
LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0];
LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1];
DWORD startSafeWritePointer, startSafeReadPointer;
result = dsWriteBuffer->GetCurrentPosition(nullptr, &startSafeWritePointer);
if (FAILED(result)) {
@ -1141,31 +1122,31 @@ void airtaudio::api::Ds::callbackEvent() {
}
Sleep(1);
}
//assert(handle->dsBufferSize[0] == handle->dsBufferSize[1]);
handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0];
if (handle->bufferPointer[0] >= handle->dsBufferSize[0]) {
handle->bufferPointer[0] -= handle->dsBufferSize[0];
//assert(m_private->dsBufferSize[0] == m_private->dsBufferSize[1]);
m_private->bufferPointer[0] = safeWritePointer + m_private->dsPointerLeadTime[0];
if (m_private->bufferPointer[0] >= m_private->dsBufferSize[0]) {
m_private->bufferPointer[0] -= m_private->dsBufferSize[0];
}
handle->bufferPointer[1] = safeReadPointer;
m_private->bufferPointer[1] = safeReadPointer;
} else if (m_mode == airtaudio::mode_output) {
// Set the proper nextWritePosition after initial startup.
LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];
LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0];
result = dsWriteBuffer->GetCurrentPosition(&currentWritePointer, &safeWritePointer);
if (FAILED(result)) {
ATA_ERROR("error (" << getErrorString(result) << ") getting current write position!");
return;
}
handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0];
if (handle->bufferPointer[0] >= handle->dsBufferSize[0]) {
handle->bufferPointer[0] -= handle->dsBufferSize[0];
m_private->bufferPointer[0] = safeWritePointer + m_private->dsPointerLeadTime[0];
if (m_private->bufferPointer[0] >= m_private->dsBufferSize[0]) {
m_private->bufferPointer[0] -= m_private->dsBufferSize[0];
}
}
m_buffersRolling = true;
}
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];
if (handle->drainCounter > 1) { // write zeros to the output stream
LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) m_private->buffer[0];
if (m_private->drainCounter > 1) { // write zeros to the output stream
bufferBytes = m_bufferSize * m_nUserChannels[0];
bufferBytes *= audio::getFormatBytes(m_userFormat);
memset(m_userBuffer[0], 0, bufferBytes);
@ -1190,8 +1171,8 @@ void airtaudio::api::Ds::callbackEvent() {
buffer[i] = (unsigned char) (buffer[i] + 128);
}
}
DWORD dsBufferSize = handle->dsBufferSize[0];
nextWritePointer = handle->bufferPointer[0];
DWORD dsBufferSize = m_private->dsBufferSize[0];
nextWritePointer = m_private->bufferPointer[0];
DWORD endWrite, leadPointer;
while (true) {
// Find out where the read and "safe write" pointers are.
@ -1203,7 +1184,7 @@ void airtaudio::api::Ds::callbackEvent() {
// We will copy our output buffer into the region between
// safeWritePointer and leadPointer. If leadPointer is not
// beyond the next endWrite position, wait until it is.
leadPointer = safeWritePointer + handle->dsPointerLeadTime[0];
leadPointer = safeWritePointer + m_private->dsPointerLeadTime[0];
//std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl;
if (leadPointer > dsBufferSize) {
leadPointer -= dsBufferSize;
@ -1229,12 +1210,12 @@ void airtaudio::api::Ds::callbackEvent() {
if ( dsPointerBetween(nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize)
|| dsPointerBetween(endWrite, safeWritePointer, currentWritePointer, dsBufferSize)) {
// We've strayed into the forbidden zone ... resync the read pointer.
handle->xrun[0] = true;
nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes;
m_private->xrun[0] = true;
nextWritePointer = safeWritePointer + m_private->dsPointerLeadTime[0] - bufferBytes;
if (nextWritePointer >= dsBufferSize) {
nextWritePointer -= dsBufferSize;
}
handle->bufferPointer[0] = nextWritePointer;
m_private->bufferPointer[0] = nextWritePointer;
endWrite = nextWritePointer + bufferBytes;
}
// Lock free space in the buffer
@ -1261,9 +1242,9 @@ void airtaudio::api::Ds::callbackEvent() {
return;
}
nextWritePointer = (nextWritePointer + bufferSize1 + bufferSize2) % dsBufferSize;
handle->bufferPointer[0] = nextWritePointer;
if (handle->drainCounter) {
handle->drainCounter++;
m_private->bufferPointer[0] = nextWritePointer;
if (m_private->drainCounter) {
m_private->drainCounter++;
goto unlock;
}
}
@ -1279,9 +1260,9 @@ void airtaudio::api::Ds::callbackEvent() {
bufferBytes = m_bufferSize * m_nUserChannels[1];
bufferBytes *= audio::getFormatBytes(m_userFormat);
}
LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1];
long nextReadPointer = handle->bufferPointer[1];
DWORD dsBufferSize = handle->dsBufferSize[1];
LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) m_private->buffer[1];
long nextReadPointer = m_private->bufferPointer[1];
DWORD dsBufferSize = m_private->dsBufferSize[1];
// Find out where the write and "safe read" pointers are.
result = dsBuffer->GetCurrentPosition(&currentReadPointer, &safeReadPointer);
if (FAILED(result)) {
@ -1310,7 +1291,7 @@ void airtaudio::api::Ds::callbackEvent() {
if (m_duplexPrerollBytes <= 0) {
// Pre-roll time over. Be more agressive.
int32_t adjustment = endRead-safeReadPointer;
handle->xrun[1] = true;
m_private->xrun[1] = true;
// Two cases:
// - large adjustments: we've probably run out of CPU cycles, so just resync exactly,
// and perform fine adjustments later.
@ -1386,7 +1367,7 @@ void airtaudio::api::Ds::callbackEvent() {
ATA_ERROR("error (" << getErrorString(result) << ") unlocking capture buffer!");
return;
}
handle->bufferPointer[1] = nextReadPointer;
m_private->bufferPointer[1] = nextReadPointer;
// No byte swapping necessary in DirectSound implementation.
// If necessary, convert 8-bit data from unsigned to signed.
if (m_deviceFormat[1] == RTAUDIO_SINT8) {

View File

@ -10,6 +10,7 @@
namespace airtaudio {
namespace api {
class DsPrivate;
class Ds: public airtaudio::Api {
public:
static airtaudio::Api* Create();
@ -34,6 +35,7 @@ namespace airtaudio {
// will most likely produce highly undesireable results!
void callbackEvent();
private:
std::unique_ptr<DsPrivate> m_private;
bool m_coInitialized;
bool m_buffersRolling;
long m_duplexPrerollBytes;

View File

@ -56,29 +56,34 @@ airtaudio::Api* airtaudio::api::Jack::Create() {
#include <unistd.h>
#include <cstdio>
// A structure to hold various information related to the Jack API
// implementation.
struct JackHandle {
jack_client_t *client;
jack_port_t **ports[2];
std::string deviceName[2];
bool xrun[2];
std::condition_variable condition;
int32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
JackHandle() :
client(0),
drainCounter(0),
internalDrain(false) {
ports[0] = 0;
ports[1] = 0;
xrun[0] = false;
xrun[1] = false;
namespace airtaudio {
namespace api {
class JackPrivate {
public:
jack_client_t *client;
jack_port_t **ports[2];
std::string deviceName[2];
bool xrun[2];
std::condition_variable condition;
int32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
JackPrivate() :
client(0),
drainCounter(0),
internalDrain(false) {
ports[0] = 0;
ports[1] = 0;
xrun[0] = false;
xrun[1] = false;
}
};
}
};
}
airtaudio::api::Jack::Jack() {
airtaudio::api::Jack::Jack() :
m_private(new airtaudio::api::JackPrivate()) {
// Nothing to do here.
}
@ -246,12 +251,11 @@ void airtaudio::api::Jack::jackShutdown(void* _userData) {
int32_t airtaudio::api::Jack::jackXrun(void* _userData) {
airtaudio::api::Jack* myClass = reinterpret_cast<airtaudio::api::Jack*>(_userData);
JackHandle* handle = (JackHandle*)myClass->m_apiHandle;
if (handle->ports[0]) {
handle->xrun[0] = true;
if (myClass->m_private->ports[0]) {
myClass->m_private->xrun[0] = true;
}
if (handle->ports[1]) {
handle->xrun[1] = true;
if (myClass->m_private->ports[1]) {
myClass->m_private->xrun[1] = true;
}
return 0;
}
@ -264,7 +268,6 @@ bool airtaudio::api::Jack::probeDeviceOpen(uint32_t _device,
audio::format _format,
uint32_t* _bufferSize,
airtaudio::StreamOptions* _options) {
JackHandle *handle = (JackHandle *) m_apiHandle;
// Look for jack server and try to become a client (only do once per stream).
jack_client_t *client = 0;
if ( _mode == airtaudio::mode_output
@ -283,7 +286,7 @@ bool airtaudio::api::Jack::probeDeviceOpen(uint32_t _device,
}
} else {
// The handle must have been created on an earlier pass.
client = handle->client;
client = m_private->client;
}
const char **ports;
std::string port, previousPort, deviceName;
@ -376,16 +379,8 @@ bool airtaudio::api::Jack::probeDeviceOpen(uint32_t _device,
m_doConvertBuffer[modeToIdTable(_mode)] = true;
}
// Allocate our JackHandle structure for the stream.
if (handle == 0) {
handle = new JackHandle;
if (handle == nullptr) {
ATA_ERROR("error allocating JackHandle memory.");
goto error;
}
m_apiHandle = (void *) handle;
handle->client = client;
}
handle->deviceName[modeToIdTable(_mode)] = deviceName;
m_private->client = client;
m_private->deviceName[modeToIdTable(_mode)] = deviceName;
// Allocate necessary internal buffers.
uint64_t bufferBytes;
bufferBytes = m_nUserChannels[modeToIdTable(_mode)] * *_bufferSize * audio::getFormatBytes(m_deviceFormat[modeToIdTable(_mode)]);
@ -419,8 +414,8 @@ bool airtaudio::api::Jack::probeDeviceOpen(uint32_t _device,
}
}
// Allocate memory for the Jack ports (channels) identifiers.
handle->ports[modeToIdTable(_mode)] = (jack_port_t **) malloc (sizeof (jack_port_t *) * _channels);
if (handle->ports[modeToIdTable(_mode)] == nullptr) {
m_private->ports[modeToIdTable(_mode)] = (jack_port_t **) malloc (sizeof (jack_port_t *) * _channels);
if (m_private->ports[modeToIdTable(_mode)] == nullptr) {
ATA_ERROR("error allocating port memory.");
goto error;
}
@ -433,29 +428,29 @@ bool airtaudio::api::Jack::probeDeviceOpen(uint32_t _device,
m_mode = airtaudio::mode_duplex;
} else {
m_mode = _mode;
jack_set_process_callback(handle->client, &airtaudio::api::Jack::jackCallbackHandler, this);
jack_set_xrun_callback(handle->client, &airtaudio::api::Jack::jackXrun, this);
jack_on_shutdown(handle->client, &airtaudio::api::Jack::jackShutdown, this);
jack_set_process_callback(m_private->client, &airtaudio::api::Jack::jackCallbackHandler, this);
jack_set_xrun_callback(m_private->client, &airtaudio::api::Jack::jackXrun, this);
jack_on_shutdown(m_private->client, &airtaudio::api::Jack::jackShutdown, this);
}
// Register our ports.
char label[64];
if (_mode == airtaudio::mode_output) {
for (uint32_t i=0; i<m_nUserChannels[0]; i++) {
snprintf(label, 64, "outport %d", i);
handle->ports[0][i] = jack_port_register(handle->client,
(const char *)label,
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput,
0);
m_private->ports[0][i] = jack_port_register(m_private->client,
(const char *)label,
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput,
0);
}
} else {
for (uint32_t i=0; i<m_nUserChannels[1]; i++) {
snprintf(label, 64, "inport %d", i);
handle->ports[1][i] = jack_port_register(handle->client,
(const char *)label,
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput,
0);
m_private->ports[1][i] = jack_port_register(m_private->client,
(const char *)label,
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput,
0);
}
}
// Setup the buffer conversion information structure. We don't use
@ -466,16 +461,14 @@ bool airtaudio::api::Jack::probeDeviceOpen(uint32_t _device,
}
return true;
error:
if (handle) {
jack_client_close(handle->client);
if (handle->ports[0]) {
free(handle->ports[0]);
}
if (handle->ports[1]) {
free(handle->ports[1]);
}
delete handle;
m_apiHandle = nullptr;
jack_client_close(m_private->client);
if (m_private->ports[0] != nullptr) {
free(m_private->ports[0]);
m_private->ports[0] = nullptr;
}
if (m_private->ports[1] != nullptr) {
free(m_private->ports[1]);
m_private->ports[1] = nullptr;
}
for (int32_t iii=0; iii<2; ++iii) {
m_userBuffer[iii].clear();
@ -492,22 +485,19 @@ enum airtaudio::error airtaudio::api::Jack::closeStream() {
ATA_ERROR("no open stream to close!");
return airtaudio::error_warning;
}
JackHandle *handle = (JackHandle *) m_apiHandle;
if (handle != nullptr) {
if (m_private != nullptr) {
if (m_state == airtaudio::state_running) {
jack_deactivate(handle->client);
jack_deactivate(m_private->client);
}
jack_client_close(handle->client);
jack_client_close(m_private->client);
}
if (handle != nullptr) {
if (handle->ports[0]) {
free(handle->ports[0]);
}
if (handle->ports[1]) {
free(handle->ports[1]);
}
delete handle;
m_apiHandle = nullptr;
if (m_private->ports[0] != nullptr) {
free(m_private->ports[0]);
m_private->ports[0] = nullptr;
}
if (m_private->ports[1] != nullptr) {
free(m_private->ports[1]);
m_private->ports[1] = nullptr;
}
for (int32_t i=0; i<2; i++) {
m_userBuffer[i].clear();
@ -529,8 +519,7 @@ enum airtaudio::error airtaudio::api::Jack::startStream() {
ATA_ERROR("the stream is already running!");
return airtaudio::error_warning;
}
JackHandle *handle = (JackHandle *) m_apiHandle;
int32_t result = jack_activate(handle->client);
int32_t result = jack_activate(m_private->client);
if (result) {
ATA_ERROR("unable to activate JACK client!");
goto unlock;
@ -540,7 +529,7 @@ enum airtaudio::error airtaudio::api::Jack::startStream() {
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
result = 1;
ports = jack_get_ports(handle->client, handle->deviceName[0].c_str(), nullptr, JackPortIsInput);
ports = jack_get_ports(m_private->client, m_private->deviceName[0].c_str(), nullptr, JackPortIsInput);
if (ports == nullptr) {
ATA_ERROR("error determining available JACK input ports!");
goto unlock;
@ -551,7 +540,7 @@ enum airtaudio::error airtaudio::api::Jack::startStream() {
for (uint32_t i=0; i<m_nUserChannels[0]; i++) {
result = 1;
if (ports[ m_channelOffset[0] + i ])
result = jack_connect(handle->client, jack_port_name(handle->ports[0][i]), ports[ m_channelOffset[0] + i ]);
result = jack_connect(m_private->client, jack_port_name(m_private->ports[0][i]), ports[ m_channelOffset[0] + i ]);
if (result) {
free(ports);
ATA_ERROR("error connecting output ports!");
@ -563,7 +552,7 @@ enum airtaudio::error airtaudio::api::Jack::startStream() {
if ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex) {
result = 1;
ports = jack_get_ports(handle->client, handle->deviceName[1].c_str(), nullptr, JackPortIsOutput);
ports = jack_get_ports(m_private->client, m_private->deviceName[1].c_str(), nullptr, JackPortIsOutput);
if (ports == nullptr) {
ATA_ERROR("error determining available JACK output ports!");
goto unlock;
@ -572,7 +561,7 @@ enum airtaudio::error airtaudio::api::Jack::startStream() {
for (uint32_t i=0; i<m_nUserChannels[1]; i++) {
result = 1;
if (ports[ m_channelOffset[1] + i ]) {
result = jack_connect(handle->client, ports[ m_channelOffset[1] + i ], jack_port_name(handle->ports[1][i]));
result = jack_connect(m_private->client, ports[ m_channelOffset[1] + i ], jack_port_name(m_private->ports[1][i]));
}
if (result) {
free(ports);
@ -582,8 +571,8 @@ enum airtaudio::error airtaudio::api::Jack::startStream() {
}
free(ports);
}
handle->drainCounter = 0;
handle->internalDrain = false;
m_private->drainCounter = 0;
m_private->internalDrain = false;
m_state = airtaudio::state_running;
unlock:
if (result == 0) {
@ -600,16 +589,15 @@ enum airtaudio::error airtaudio::api::Jack::stopStream() {
ATA_ERROR("the stream is already stopped!");
return airtaudio::error_warning;
}
JackHandle *handle = (JackHandle *) m_apiHandle;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (handle->drainCounter == 0) {
handle->drainCounter = 2;
if (m_private->drainCounter == 0) {
m_private->drainCounter = 2;
std::unique_lock<std::mutex> lck(m_mutex);
handle->condition.wait(lck);
m_private->condition.wait(lck);
}
}
jack_deactivate(handle->client);
jack_deactivate(m_private->client);
m_state = airtaudio::state_stopped;
return airtaudio::error_none;
}
@ -622,8 +610,7 @@ enum airtaudio::error airtaudio::api::Jack::abortStream() {
ATA_ERROR("the stream is already stopped!");
return airtaudio::error_warning;
}
JackHandle *handle = (JackHandle *) m_apiHandle;
handle->drainCounter = 2;
m_private->drainCounter = 2;
return stopStream();
}
@ -650,28 +637,27 @@ bool airtaudio::api::Jack::callbackEvent(uint64_t _nframes) {
ATA_ERROR("the JACK buffer size has changed ... cannot process!");
return false;
}
JackHandle *handle = (JackHandle *) m_apiHandle;
// Check if we were draining the stream and signal is finished.
if (handle->drainCounter > 3) {
if (m_private->drainCounter > 3) {
m_state = airtaudio::state_stopping;
if (handle->internalDrain == true) {
if (m_private->internalDrain == true) {
new std::thread(jackStopStream, this);
} else {
handle->condition.notify_one();
m_private->condition.notify_one();
}
return true;
}
// Invoke user callback first, to get fresh output data.
if (handle->drainCounter == 0) {
if (m_private->drainCounter == 0) {
double streamTime = getStreamTime();
enum airtaudio::status status = airtaudio::status_ok;
if (m_mode != airtaudio::mode_input && handle->xrun[0] == true) {
if (m_mode != airtaudio::mode_input && m_private->xrun[0] == true) {
status = airtaudio::status_underflow;
handle->xrun[0] = false;
m_private->xrun[0] = false;
}
if (m_mode != airtaudio::mode_output && handle->xrun[1] == true) {
if (m_mode != airtaudio::mode_output && m_private->xrun[1] == true) {
status = airtaudio::status_overflow;
handle->xrun[1] = false;
m_private->xrun[1] = false;
}
int32_t cbReturnValue = m_callbackInfo.callback(&m_userBuffer[0][0],
&m_userBuffer[1][0],
@ -680,38 +666,38 @@ bool airtaudio::api::Jack::callbackEvent(uint64_t _nframes) {
status);
if (cbReturnValue == 2) {
m_state = airtaudio::state_stopping;
handle->drainCounter = 2;
m_private->drainCounter = 2;
new std::thread(jackStopStream, this);
return true;
}
else if (cbReturnValue == 1) {
handle->drainCounter = 1;
handle->internalDrain = true;
m_private->drainCounter = 1;
m_private->internalDrain = true;
}
}
jack_default_audio_sample_t *jackbuffer;
uint64_t bufferBytes = _nframes * sizeof(jack_default_audio_sample_t);
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (handle->drainCounter > 1) { // write zeros to the output stream
if (m_private->drainCounter > 1) { // write zeros to the output stream
for (uint32_t i=0; i<m_nDeviceChannels[0]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[0][i], (jack_nframes_t) _nframes);
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[0][i], (jack_nframes_t) _nframes);
memset(jackbuffer, 0, bufferBytes);
}
} else if (m_doConvertBuffer[0]) {
convertBuffer(m_deviceBuffer, &m_userBuffer[0][0], m_convertInfo[0]);
for (uint32_t i=0; i<m_nDeviceChannels[0]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[0][i], (jack_nframes_t) _nframes);
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[0][i], (jack_nframes_t) _nframes);
memcpy(jackbuffer, &m_deviceBuffer[i*bufferBytes], bufferBytes);
}
} else { // no buffer conversion
for (uint32_t i=0; i<m_nUserChannels[0]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[0][i], (jack_nframes_t) _nframes);
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[0][i], (jack_nframes_t) _nframes);
memcpy(jackbuffer, &m_userBuffer[0][i*bufferBytes], bufferBytes);
}
}
if (handle->drainCounter) {
handle->drainCounter++;
if (m_private->drainCounter) {
m_private->drainCounter++;
goto unlock;
}
}
@ -719,14 +705,14 @@ bool airtaudio::api::Jack::callbackEvent(uint64_t _nframes) {
|| m_mode == airtaudio::mode_duplex) {
if (m_doConvertBuffer[1]) {
for (uint32_t i=0; i<m_nDeviceChannels[1]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[1][i], (jack_nframes_t) _nframes);
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[1][i], (jack_nframes_t) _nframes);
memcpy(&m_deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes);
}
convertBuffer(&m_userBuffer[1][0], m_deviceBuffer, m_convertInfo[1]);
} else {
// no buffer conversion
for (uint32_t i=0; i<m_nUserChannels[1]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[1][i], (jack_nframes_t) _nframes);
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[1][i], (jack_nframes_t) _nframes);
memcpy(&m_userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes);
}
}

View File

@ -11,6 +11,7 @@
#include <jack/jack.h>
namespace airtaudio {
namespace api {
class JackPrivate;
class Jack: public airtaudio::Api {
public:
static airtaudio::Api* Create();
@ -38,6 +39,7 @@ namespace airtaudio {
static void jackShutdown(void* _userData);
static int32_t jackCallbackHandler(jack_nframes_t _nframes, void* _userData);
private:
std::unique_ptr<JackPrivate> m_private;
bool probeDeviceOpen(uint32_t _device,
airtaudio::mode _mode,
uint32_t _channels,

View File

@ -25,25 +25,30 @@ airtaudio::Api* airtaudio::api::Oss::Create() {
return new airtaudio::api::Oss();
}
static void *ossCallbackHandler(void* _ptr);
static void *ossCallbackHandler(void* _userData);
// A structure to hold various information related to the OSS API
// implementation.
struct OssHandle {
int32_t id[2]; // device ids
bool xrun[2];
bool triggered;
std::condition_variable runnable;
OssHandle():
triggered(false) {
id[0] = 0;
id[1] = 0;
xrun[0] = false;
xrun[1] = false;
namespace airtaudio {
namespace api {
class OssPrivate {
public:
int32_t id[2]; // device ids
bool xrun[2];
bool triggered;
std::condition_variable runnable;
OssPrivate():
triggered(false) {
id[0] = 0;
id[1] = 0;
xrun[0] = false;
xrun[1] = false;
}
};
}
};
}
airtaudio::api::Oss::Oss() {
airtaudio::api::Oss::Oss() :
m_private(new airtaudio::api::OssPrivate()) {
// Nothing to do here.
}
@ -226,15 +231,14 @@ bool airtaudio::api::Oss::probeDeviceOpen(uint32_t _device,
return false;
}
int32_t flags = 0;
OssHandle *handle = (OssHandle *) m_apiHandle;
if (_mode == airtaudio::mode_output) {
flags |= O_WRONLY;
} else { // _mode == airtaudio::mode_input
if ( m_mode == airtaudio::mode_output
&& m_device[0] == _device) {
// We just set the same device for playback ... close and reopen for duplex (OSS only).
close(handle->id[0]);
handle->id[0] = 0;
close(m_private->id[0]);
m_private->id[0] = 0;
if (!(ainfo.caps & PCM_CAP_airtaudio::mode_duplex)) {
ATA_ERROR("device (" << ainfo.name << ") does not support duplex mode.");
return false;
@ -450,18 +454,7 @@ bool airtaudio::api::Oss::probeDeviceOpen(uint32_t _device,
&& m_nUserChannels[modeToIdTable(_mode)] > 1) {
m_doConvertBuffer[modeToIdTable(_mode)] = true;
}
// Allocate the stream handles if necessary and then save.
if (m_apiHandle == 0) {
handle = new OssHandle;
if handle == nullptr) {
ATA_ERROR("error allocating OssHandle memory.");
goto error;
}
m_apiHandle = (void *) handle;
} else {
handle = (OssHandle *) m_apiHandle;
}
handle->id[modeToIdTable(_mode)] = fd;
m_private->id[modeToIdTable(_mode)] = fd;
// Allocate necessary internal buffers.
uint64_t bufferBytes;
bufferBytes = m_nUserChannels[modeToIdTable(_mode)] * *_bufferSize * audio::getFormatBytes(m_userFormat);
@ -505,14 +498,13 @@ bool airtaudio::api::Oss::probeDeviceOpen(uint32_t _device,
// We had already set up an output stream.
m_mode = airtaudio::mode_duplex;
if (m_device[0] == _device) {
handle->id[0] = fd;
m_private->id[0] = fd;
}
} else {
m_mode = _mode;
// Setup callback thread.
m_callbackInfo.object = (void *) this;
m_callbackInfo.isRunning = true;
m_callbackInfo.thread = new std::thread(ossCallbackHandler, &m_callbackInfo);
m_callbackInfo.thread = new std::thread(ossCallbackHandler, this);
if (m_callbackInfo.thread == nullptr) {
m_callbackInfo.isRunning = false;
ATA_ERROR("creating callback thread!");
@ -521,15 +513,13 @@ bool airtaudio::api::Oss::probeDeviceOpen(uint32_t _device,
}
return true;
error:
if (handle) {
if (handle->id[0]) {
close(handle->id[0]);
}
if (handle->id[1]) {
close(handle->id[1]);
}
delete handle;
m_apiHandle = 0;
if (m_private->id[0] != nullptr) {
close(m_private->id[0]);
m_private->id[0] = nullptr;
}
if (m_private->id[1] != nullptr) {
close(m_private->id[1]);
m_private->id[1] = nullptr;
}
for (int32_t i=0; i<2; i++) {
if (m_userBuffer[i]) {
@ -549,31 +539,28 @@ enum airtaudio::error airtaudio::api::Oss::closeStream() {
ATA_ERROR("no open stream to close!");
return airtaudio::error_warning;
}
OssHandle *handle = (OssHandle *) m_apiHandle;
m_callbackInfo.isRunning = false;
m_mutex.lock();
if (m_state == airtaudio::state_stopped) {
handle->runnable.notify_one();
m_private->runnable.notify_one();
}
m_mutex.unlock();
m_callbackInfo.thread->join();
if (m_state == airtaudio::state_running) {
if (m_mode == airtaudio::mode_output || m_mode == airtaudio::mode_duplex) {
ioctl(handle->id[0], SNDCTL_DSP_HALT, 0);
ioctl(m_private->id[0], SNDCTL_DSP_HALT, 0);
} else {
ioctl(handle->id[1], SNDCTL_DSP_HALT, 0);
ioctl(m_private->id[1], SNDCTL_DSP_HALT, 0);
}
m_state = airtaudio::state_stopped;
}
if (handle) {
if (handle->id[0]) {
close(handle->id[0]);
}
if (handle->id[1]) {
close(handle->id[1]);
}
delete handle;
m_apiHandle = 0;
if (m_private->id[0] != nullptr) {
close(m_private->id[0]);
m_private->id[0] = nullptr;
}
if (m_private->id[1] != nullptr) {
close(m_private->id[1]);
m_private->id[1] = nullptr;
}
for (int32_t i=0; i<2; i++) {
if (m_userBuffer[i]) {
@ -603,8 +590,7 @@ enum airtaudio::error airtaudio::api::Oss::startStream() {
// No need to do anything else here ... OSS automatically starts
// when fed samples.
m_mutex.unlock();
OssHandle *handle = (OssHandle *) m_apiHandle;
handle->runnable.notify_one();
m_private->runnable.notify_one();
}
enum airtaudio::error airtaudio::api::Oss::stopStream() {
@ -622,7 +608,6 @@ enum airtaudio::error airtaudio::api::Oss::stopStream() {
return;
}
int32_t result = 0;
OssHandle *handle = (OssHandle *) m_apiHandle;
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
// Flush the output with zeros a few times.
@ -640,23 +625,23 @@ enum airtaudio::error airtaudio::api::Oss::stopStream() {
}
memset(buffer, 0, samples * audio::getFormatBytes(format));
for (uint32_t i=0; i<m_nBuffers+1; i++) {
result = write(handle->id[0], buffer, samples * audio::getFormatBytes(format));
result = write(m_private->id[0], buffer, samples * audio::getFormatBytes(format));
if (result == -1) {
ATA_ERROR("audio write error.");
return airtaudio::error_warning;
}
}
result = ioctl(handle->id[0], SNDCTL_DSP_HALT, 0);
result = ioctl(m_private->id[0], SNDCTL_DSP_HALT, 0);
if (result == -1) {
ATA_ERROR("system error stopping callback procedure on device (" << m_device[0] << ").");
goto unlock;
}
handle->triggered = false;
m_private->triggered = false;
}
if ( m_mode == airtaudio::mode_input
|| ( m_mode == airtaudio::mode_duplex
&& handle->id[0] != handle->id[1])) {
result = ioctl(handle->id[1], SNDCTL_DSP_HALT, 0);
&& m_private->id[0] != m_private->id[1])) {
result = ioctl(m_private->id[1], SNDCTL_DSP_HALT, 0);
if (result == -1) {
ATA_ERROR("system error stopping input callback procedure on device (" << m_device[0] << ").");
goto unlock;
@ -686,17 +671,16 @@ enum airtaudio::error airtaudio::api::Oss::abortStream() {
return;
}
int32_t result = 0;
OssHandle *handle = (OssHandle *) m_apiHandle;
if (m_mode == airtaudio::mode_output || m_mode == airtaudio::mode_duplex) {
result = ioctl(handle->id[0], SNDCTL_DSP_HALT, 0);
result = ioctl(m_private->id[0], SNDCTL_DSP_HALT, 0);
if (result == -1) {
ATA_ERROR("system error stopping callback procedure on device (" << m_device[0] << ").");
goto unlock;
}
handle->triggered = false;
m_private->triggered = false;
}
if (m_mode == airtaudio::mode_input || (m_mode == airtaudio::mode_duplex && handle->id[0] != handle->id[1])) {
result = ioctl(handle->id[1], SNDCTL_DSP_HALT, 0);
if (m_mode == airtaudio::mode_input || (m_mode == airtaudio::mode_duplex && m_private->id[0] != m_private->id[1])) {
result = ioctl(m_private->id[1], SNDCTL_DSP_HALT, 0);
if (result == -1) {
ATA_ERROR("system error stopping input callback procedure on device (" << m_device[0] << ").");
goto unlock;
@ -712,10 +696,9 @@ unlock:
}
void airtaudio::api::Oss::callbackEvent() {
OssHandle *handle = (OssHandle *) m_apiHandle;
if (m_state == airtaudio::state_stopped) {
std::unique_lock<std::mutex> lck(m_mutex);
handle->runnable.wait(lck);
m_private->runnable.wait(lck);
if (m_state != airtaudio::state_running) {
return;
}
@ -729,14 +712,14 @@ void airtaudio::api::Oss::callbackEvent() {
double streamTime = getStreamTime();
rtaudio::streamStatus status = 0;
if ( m_mode != airtaudio::mode_input
&& handle->xrun[0] == true) {
&& m_private->xrun[0] == true) {
status |= RTAUDIO_airtaudio::status_underflow;
handle->xrun[0] = false;
m_private->xrun[0] = false;
}
if ( m_mode != airtaudio::mode_output
&& handle->xrun[1] == true) {
&& m_private->xrun[1] == true) {
status |= RTAUDIO_airtaudio::mode_input_OVERFLOW;
handle->xrun[1] = false;
m_private->xrun[1] = false;
}
doStopStream = m_callbackInfo.callback(m_userBuffer[0],
m_userBuffer[1],
@ -774,21 +757,21 @@ void airtaudio::api::Oss::callbackEvent() {
byteSwapBuffer(buffer, samples, format);
}
if ( m_mode == airtaudio::mode_duplex
&& handle->triggered == false) {
&& m_private->triggered == false) {
int32_t trig = 0;
ioctl(handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig);
result = write(handle->id[0], buffer, samples * audio::getFormatBytes(format));
ioctl(m_private->id[0], SNDCTL_DSP_SETTRIGGER, &trig);
result = write(m_private->id[0], buffer, samples * audio::getFormatBytes(format));
trig = PCM_ENABLE_airtaudio::mode_input|PCM_ENABLE_airtaudio::mode_output;
ioctl(handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig);
handle->triggered = true;
ioctl(m_private->id[0], SNDCTL_DSP_SETTRIGGER, &trig);
m_private->triggered = true;
} else {
// Write samples to device.
result = write(handle->id[0], buffer, samples * audio::getFormatBytes(format));
result = write(m_private->id[0], buffer, samples * audio::getFormatBytes(format));
}
if (result == -1) {
// We'll assume this is an underrun, though there isn't a
// specific means for determining that.
handle->xrun[0] = true;
m_private->xrun[0] = true;
ATA_ERROR("audio write error.");
//error(airtaudio::error_warning);
// Continue on to input section.
@ -807,11 +790,11 @@ void airtaudio::api::Oss::callbackEvent() {
format = m_userFormat;
}
// Read samples from device.
result = read(handle->id[1], buffer, samples * audio::getFormatBytes(format));
result = read(m_private->id[1], buffer, samples * audio::getFormatBytes(format));
if (result == -1) {
// We'll assume this is an overrun, though there isn't a
// specific means for determining that.
handle->xrun[1] = true;
m_private->xrun[1] = true;
ATA_ERROR("audio read error.");
goto unlock;
}
@ -832,12 +815,10 @@ unlock:
}
}
static void ossCallbackHandler(void* _ptr) {
CallbackInfo* info = (CallbackInfo*)_ptr;
RtApiOss* object = (RtApiOss*)info->object;
bool *isRunning = &info->isRunning;
while (*isRunning == true) {
object->callbackEvent();
static void ossCallbackHandler(void* _userData) {
airtaudio::api::Alsa* myClass = reinterpret_cast<airtaudio::api::Oss*>(_userData);
while (myClass->m_callbackInfo->isRunning == true) {
myClass->callbackEvent();
}
}

View File

@ -10,6 +10,7 @@
namespace airtaudio {
namespace api {
class OssPrivate;
class Oss: public airtaudio::Api {
public:
static airtaudio::Api* Create();
@ -31,6 +32,7 @@ namespace airtaudio {
// will most likely produce highly undesireable results!
void callbackEvent();
private:
std::unique_ptr<OssPrivate> m_private;
bool probeDeviceOpen(uint32_t _device,
airtaudio::mode _mode,
uint32_t _channels,

View File

@ -49,19 +49,29 @@ static const rtaudio_pa_format_mapping_t supported_sampleformats[] = {
{audio::format_float, PA_SAMPLE_FLOAT32LE},
{audio::format_unknow, PA_SAMPLE_INVALID}};
struct PulseAudioHandle {
pa_simple *s_play;
pa_simple *s_rec;
std::thread* thread;
std::condition_variable runnable_cv;
bool runnable;
PulseAudioHandle() :
s_play(0),
s_rec(0),
runnable(false) {
namespace airtaudio {
namespace api {
class PulsePrivate {
public:
pa_simple *s_play;
pa_simple *s_rec;
std::thread* thread;
std::condition_variable runnable_cv;
bool runnable;
PulsePrivate() :
s_play(0),
s_rec(0),
runnable(false) {
}
};
}
};
}
airtaudio::api::Pulse::Pulse() :
m_private(new airtaudio::api::PulsePrivate()) {
}
airtaudio::api::Pulse::~Pulse() {
if (m_state != airtaudio::state_closed) {
@ -103,25 +113,20 @@ void airtaudio::api::Pulse::callbackEvent() {
}
enum airtaudio::error airtaudio::api::Pulse::closeStream() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_apiHandle);
m_callbackInfo.isRunning = false;
if (pah) {
m_mutex.lock();
if (m_state == airtaudio::state_stopped) {
pah->runnable = true;
pah->runnable_cv.notify_one();;
}
m_mutex.unlock();
pah->thread->join();
if (pah->s_play) {
pa_simple_flush(pah->s_play, nullptr);
pa_simple_free(pah->s_play);
}
if (pah->s_rec) {
pa_simple_free(pah->s_rec);
}
delete pah;
m_apiHandle = nullptr;
m_mutex.lock();
if (m_state == airtaudio::state_stopped) {
m_private->runnable = true;
m_private->runnable_cv.notify_one();;
}
m_mutex.unlock();
m_private->thread->join();
if (m_private->s_play) {
pa_simple_flush(m_private->s_play, nullptr);
pa_simple_free(m_private->s_play);
}
if (m_private->s_rec) {
pa_simple_free(m_private->s_rec);
}
m_userBuffer[0].clear();
m_userBuffer[1].clear();
@ -131,11 +136,10 @@ enum airtaudio::error airtaudio::api::Pulse::closeStream() {
}
void airtaudio::api::Pulse::callbackEventOneCycle() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_apiHandle);
if (m_state == airtaudio::state_stopped) {
std::unique_lock<std::mutex> lck(m_mutex);
while (!pah->runnable) {
pah->runnable_cv.wait(lck);
while (!m_private->runnable) {
m_private->runnable_cv.wait(lck);
}
if (m_state != airtaudio::state_running) {
m_mutex.unlock();
@ -175,7 +179,7 @@ void airtaudio::api::Pulse::callbackEventOneCycle() {
} else {
bytes = m_nUserChannels[airtaudio::modeToIdTable(airtaudio::mode_output)] * m_bufferSize * audio::getFormatBytes(m_userFormat);
}
if (pa_simple_write(pah->s_play, pulse_out, bytes, &pa_error) < 0) {
if (pa_simple_write(m_private->s_play, pulse_out, bytes, &pa_error) < 0) {
ATA_ERROR("audio write error, " << pa_strerror(pa_error) << ".");
return;
}
@ -186,7 +190,7 @@ void airtaudio::api::Pulse::callbackEventOneCycle() {
} else {
bytes = m_nUserChannels[airtaudio::modeToIdTable(airtaudio::mode_input)] * m_bufferSize * audio::getFormatBytes(m_userFormat);
}
if (pa_simple_read(pah->s_rec, pulse_in, bytes, &pa_error) < 0) {
if (pa_simple_read(m_private->s_rec, pulse_in, bytes, &pa_error) < 0) {
ATA_ERROR("audio read error, " << pa_strerror(pa_error) << ".");
return;
}
@ -207,7 +211,6 @@ unlock:
}
enum airtaudio::error airtaudio::api::Pulse::startStream() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_apiHandle);
if (m_state == airtaudio::state_closed) {
ATA_ERROR("the stream is not open!");
return airtaudio::error_invalidUse;
@ -218,14 +221,13 @@ enum airtaudio::error airtaudio::api::Pulse::startStream() {
}
m_mutex.lock();
m_state = airtaudio::state_running;
pah->runnable = true;
pah->runnable_cv.notify_one();
m_private->runnable = true;
m_private->runnable_cv.notify_one();
m_mutex.unlock();
return airtaudio::error_none;
}
enum airtaudio::error airtaudio::api::Pulse::stopStream() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_apiHandle);
if (m_state == airtaudio::state_closed) {
ATA_ERROR("the stream is not open!");
return airtaudio::error_invalidUse;
@ -236,9 +238,9 @@ enum airtaudio::error airtaudio::api::Pulse::stopStream() {
}
m_state = airtaudio::state_stopped;
m_mutex.lock();
if (pah && pah->s_play) {
if (m_private->s_play) {
int32_t pa_error;
if (pa_simple_drain(pah->s_play, &pa_error) < 0) {
if (pa_simple_drain(m_private->s_play, &pa_error) < 0) {
ATA_ERROR("error draining output device, " << pa_strerror(pa_error) << ".");
m_mutex.unlock();
return airtaudio::error_systemError;
@ -250,7 +252,6 @@ enum airtaudio::error airtaudio::api::Pulse::stopStream() {
}
enum airtaudio::error airtaudio::api::Pulse::abortStream() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle*>(m_apiHandle);
if (m_state == airtaudio::state_closed) {
ATA_ERROR("the stream is not open!");
return airtaudio::error_invalidUse;
@ -261,9 +262,9 @@ enum airtaudio::error airtaudio::api::Pulse::abortStream() {
}
m_state = airtaudio::state_stopped;
m_mutex.lock();
if (pah && pah->s_play) {
if (m_private && m_private->s_play) {
int32_t pa_error;
if (pa_simple_flush(pah->s_play, &pa_error) < 0) {
if (pa_simple_flush(m_private->s_play, &pa_error) < 0) {
ATA_ERROR("error flushing output device, " << pa_strerror(pa_error) << ".");
m_mutex.unlock();
return airtaudio::error_systemError;
@ -282,7 +283,6 @@ bool airtaudio::api::Pulse::probeDeviceOpen(uint32_t _device,
audio::format _format,
uint32_t *_bufferSize,
airtaudio::StreamOptions *_options) {
PulseAudioHandle *pah = 0;
uint64_t bufferBytes = 0;
pa_sample_spec ss;
if (_device != 0) {
@ -367,27 +367,18 @@ bool airtaudio::api::Pulse::probeDeviceOpen(uint32_t _device,
if (m_doConvertBuffer[modeToIdTable(_mode)]) {
setConvertInfo(_mode, _firstChannel);
}
if (!m_apiHandle) {
PulseAudioHandle *pah = new PulseAudioHandle;
if (!pah) {
ATA_ERROR("error allocating memory for handle.");
goto error;
}
m_apiHandle = pah;
}
pah = static_cast<PulseAudioHandle *>(m_apiHandle);
int32_t error;
switch (_mode) {
case airtaudio::mode_input:
pah->s_rec = pa_simple_new(nullptr, "airtAudio", PA_STREAM_RECORD, nullptr, "Record", &ss, nullptr, nullptr, &error);
if (!pah->s_rec) {
m_private->s_rec = pa_simple_new(nullptr, "airtAudio", PA_STREAM_RECORD, nullptr, "Record", &ss, nullptr, nullptr, &error);
if (!m_private->s_rec) {
ATA_ERROR("error connecting input to PulseAudio server.");
goto error;
}
break;
case airtaudio::mode_output:
pah->s_play = pa_simple_new(nullptr, "airtAudio", PA_STREAM_PLAYBACK, nullptr, "Playback", &ss, nullptr, nullptr, &error);
if (!pah->s_play) {
m_private->s_play = pa_simple_new(nullptr, "airtAudio", PA_STREAM_PLAYBACK, nullptr, "Playback", &ss, nullptr, nullptr, &error);
if (!m_private->s_play) {
ATA_ERROR("error connecting output to PulseAudio server.");
goto error;
}
@ -404,8 +395,8 @@ bool airtaudio::api::Pulse::probeDeviceOpen(uint32_t _device,
}
if (!m_callbackInfo.isRunning) {
m_callbackInfo.isRunning = true;
pah->thread = new std::thread(pulseaudio_callback, this);
if (pah->thread == nullptr) {
m_private->thread = new std::thread(pulseaudio_callback, this);
if (m_private->thread == nullptr) {
ATA_ERROR("error creating thread.");
goto error;
}
@ -413,10 +404,6 @@ bool airtaudio::api::Pulse::probeDeviceOpen(uint32_t _device,
m_state = airtaudio::state_stopped;
return true;
error:
if (pah && m_callbackInfo.isRunning) {
delete pah;
m_apiHandle = 0;
}
for (int32_t i=0; i<2; i++) {
m_userBuffer[i].clear();
}

View File

@ -10,10 +10,12 @@
namespace airtaudio {
namespace api {
class PulsePrivate;
class Pulse: public airtaudio::Api {
public:
static airtaudio::Api* Create();
public:
Pulse();
virtual ~Pulse();
enum airtaudio::type getCurrentApi() {
return airtaudio::type_pulse;
@ -31,6 +33,7 @@ namespace airtaudio {
void callbackEventOneCycle();
void callbackEvent();
private:
std::unique_ptr<PulsePrivate> m_private;
std::vector<airtaudio::DeviceInfo> m_devices;
void saveDeviceInfo();
bool probeDeviceOpen(uint32_t _device,