[DEV] set member in private for internal backend interface
This commit is contained in:
parent
4febe7b119
commit
5c9361c199
@ -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() {
|
||||
|
@ -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.
|
||||
|
@ -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) << ".");
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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(¤tWritePointer, &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(¤tReadPointer, &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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user