audio-orchestra/airtaudio/api/Pulse.cpp

443 lines
14 KiB
C++

/**
* @author Gary P. SCAVONE
*
* @copyright 2001-2013 Gary P. Scavone, all right reserved
*
* @license like MIT (see license file)
*/
#if defined(__LINUX_PULSE__)
#include <unistd.h>
#include <limits.h>
#include <airtaudio/Interface.h>
#include <airtaudio/debug.h>
// Code written by Peter Meerwald, pmeerw@pmeerw.net
// and Tristan Matthews.
#include <pulse/error.h>
#include <pulse/simple.h>
#include <cstdio>
airtaudio::Api* airtaudio::api::Pulse::Create(void) {
return new airtaudio::api::Pulse();
}
static const uint32_t SUPPORTED_SAMPLERATES[] = {
8000,
16000,
22050,
32000,
44100,
48000,
96000,
0
};
struct rtaudio_pa_format_mapping_t {
airtaudio::format airtaudio_format;
pa_sample_format_t pa_format;
};
static const rtaudio_pa_format_mapping_t supported_sampleformats[] = {
{airtaudio::SINT16, PA_SAMPLE_S16LE},
{airtaudio::SINT32, PA_SAMPLE_S32LE},
{airtaudio::FLOAT32, PA_SAMPLE_FLOAT32LE},
{0, PA_SAMPLE_INVALID}};
struct PulseAudioHandle {
pa_simple *s_play;
pa_simple *s_rec;
std::thread* thread;
std::condition_variable runnable_cv;
bool runnable;
PulseAudioHandle(void) :
s_play(0),
s_rec(0),
runnable(false) {
}
};
airtaudio::api::Pulse::~Pulse(void) {
if (m_stream.state != STREAM_CLOSED) {
closeStream();
}
}
uint32_t airtaudio::api::Pulse::getDeviceCount(void) {
return 1;
}
airtaudio::DeviceInfo airtaudio::api::Pulse::getDeviceInfo(uint32_t _device) {
airtaudio::DeviceInfo info;
info.probed = true;
info.name = "PulseAudio";
info.outputChannels = 2;
info.inputChannels = 2;
info.duplexChannels = 2;
info.isDefaultOutput = true;
info.isDefaultInput = true;
for (const uint32_t *sr = SUPPORTED_SAMPLERATES; *sr; ++sr) {
info.sampleRates.push_back(*sr);
}
info.nativeFormats = SINT16 | SINT32 | FLOAT32;
return info;
}
static void pulseaudio_callback(void* _user) {
airtaudio::CallbackInfo *cbi = static_cast<airtaudio::CallbackInfo *>(_user);
airtaudio::api::Pulse *context = static_cast<airtaudio::api::Pulse*>(cbi->object);
volatile bool *isRunning = &cbi->isRunning;
while (*isRunning) {
context->callbackEvent();
}
}
enum airtaudio::errorType airtaudio::api::Pulse::closeStream(void) {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
m_stream.callbackInfo.isRunning = false;
if (pah) {
m_stream.mutex.lock();
if (m_stream.state == STREAM_STOPPED) {
pah->runnable = true;
pah->runnable_cv.notify_one();;
}
m_stream.mutex.unlock();
pah->thread->join();
if (pah->s_play) {
pa_simple_flush(pah->s_play, NULL);
pa_simple_free(pah->s_play);
}
if (pah->s_rec) {
pa_simple_free(pah->s_rec);
}
delete pah;
m_stream.apiHandle = NULL;
}
if (m_stream.userBuffer[0] != NULL) {
free(m_stream.userBuffer[0]);
m_stream.userBuffer[0] = NULL;
}
if (m_stream.userBuffer[1] != NULL) {
free(m_stream.userBuffer[1]);
m_stream.userBuffer[1] = NULL;
}
m_stream.state = STREAM_CLOSED;
m_stream.mode = UNINITIALIZED;
return airtaudio::errorNone;
}
void airtaudio::api::Pulse::callbackEvent(void) {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
if (m_stream.state == STREAM_STOPPED) {
std::unique_lock<std::mutex> lck(m_stream.mutex);
while (!pah->runnable) {
pah->runnable_cv.wait(lck);
}
if (m_stream.state != STREAM_RUNNING) {
m_stream.mutex.unlock();
return;
}
}
if (m_stream.state == STREAM_CLOSED) {
ATA_ERROR("airtaudio::api::Pulse::callbackEvent(): the stream is closed ... this shouldn't happen!");
return;
}
airtaudio::AirTAudioCallback callback = (airtaudio::AirTAudioCallback) m_stream.callbackInfo.callback;
double streamTime = getStreamTime();
airtaudio::streamStatus status = 0;
int32_t doStopStream = callback(m_stream.userBuffer[OUTPUT],
m_stream.userBuffer[INPUT],
m_stream.bufferSize,
streamTime,
status,
m_stream.callbackInfo.userData);
if (doStopStream == 2) {
abortStream();
return;
}
m_stream.mutex.lock();
void *pulse_in = m_stream.doConvertBuffer[INPUT] ? m_stream.deviceBuffer : m_stream.userBuffer[INPUT];
void *pulse_out = m_stream.doConvertBuffer[OUTPUT] ? m_stream.deviceBuffer : m_stream.userBuffer[OUTPUT];
if (m_stream.state != STREAM_RUNNING) {
goto unlock;
}
int32_t pa_error;
size_t bytes;
if ( m_stream.mode == OUTPUT
|| m_stream.mode == DUPLEX) {
if (m_stream.doConvertBuffer[OUTPUT]) {
convertBuffer(m_stream.deviceBuffer,
m_stream.userBuffer[OUTPUT],
m_stream.convertInfo[OUTPUT]);
bytes = m_stream.nDeviceChannels[OUTPUT] * m_stream.bufferSize * formatBytes(m_stream.deviceFormat[OUTPUT]);
} else {
bytes = m_stream.nUserChannels[OUTPUT] * m_stream.bufferSize * formatBytes(m_stream.userFormat);
}
if (pa_simple_write(pah->s_play, pulse_out, bytes, &pa_error) < 0) {
ATA_ERROR("airtaudio::api::Pulse::callbackEvent: audio write error, " << pa_strerror(pa_error) << ".");
return;
}
}
if (m_stream.mode == INPUT || m_stream.mode == DUPLEX) {
if (m_stream.doConvertBuffer[INPUT]) {
bytes = m_stream.nDeviceChannels[INPUT] * m_stream.bufferSize * formatBytes(m_stream.deviceFormat[INPUT]);
} else {
bytes = m_stream.nUserChannels[INPUT] * m_stream.bufferSize * formatBytes(m_stream.userFormat);
}
if (pa_simple_read(pah->s_rec, pulse_in, bytes, &pa_error) < 0) {
ATA_ERROR("airtaudio::api::Pulse::callbackEvent: audio read error, " << pa_strerror(pa_error) << ".");
return;
}
if (m_stream.doConvertBuffer[INPUT]) {
convertBuffer(m_stream.userBuffer[INPUT],
m_stream.deviceBuffer,
m_stream.convertInfo[INPUT]);
}
}
unlock:
m_stream.mutex.unlock();
airtaudio::Api::tickStreamTime();
if (doStopStream == 1) {
stopStream();
return;
}
return;
}
enum airtaudio::errorType airtaudio::api::Pulse::startStream(void) {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
if (m_stream.state == STREAM_CLOSED) {
ATA_ERROR("airtaudio::api::Pulse::startStream(): the stream is not open!");
return airtaudio::errorInvalidUse;
}
if (m_stream.state == STREAM_RUNNING) {
ATA_ERROR("airtaudio::api::Pulse::startStream(): the stream is already running!");
return airtaudio::errorWarning;
}
m_stream.mutex.lock();
m_stream.state = STREAM_RUNNING;
pah->runnable = true;
pah->runnable_cv.notify_one();
m_stream.mutex.unlock();
return airtaudio::errorNone;
}
enum airtaudio::errorType airtaudio::api::Pulse::stopStream(void) {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
if (m_stream.state == STREAM_CLOSED) {
ATA_ERROR("airtaudio::api::Pulse::stopStream(): the stream is not open!");
return airtaudio::errorInvalidUse;
}
if (m_stream.state == STREAM_STOPPED) {
ATA_ERROR("airtaudio::api::Pulse::stopStream(): the stream is already stopped!");
return airtaudio::errorWarning;
}
m_stream.state = STREAM_STOPPED;
m_stream.mutex.lock();
if (pah && pah->s_play) {
int32_t pa_error;
if (pa_simple_drain(pah->s_play, &pa_error) < 0) {
ATA_ERROR("airtaudio::api::Pulse::stopStream: error draining output device, " << pa_strerror(pa_error) << ".");
m_stream.mutex.unlock();
return airtaudio::errorSystemError;
}
}
m_stream.state = STREAM_STOPPED;
m_stream.mutex.unlock();
return airtaudio::errorNone;
}
enum airtaudio::errorType airtaudio::api::Pulse::abortStream(void) {
PulseAudioHandle *pah = static_cast<PulseAudioHandle*>(m_stream.apiHandle);
if (m_stream.state == STREAM_CLOSED) {
ATA_ERROR("airtaudio::api::Pulse::abortStream(): the stream is not open!");
return airtaudio::errorInvalidUse;
}
if (m_stream.state == STREAM_STOPPED) {
ATA_ERROR("airtaudio::api::Pulse::abortStream(): the stream is already stopped!");
return airtaudio::errorWarning;
}
m_stream.state = STREAM_STOPPED;
m_stream.mutex.lock();
if (pah && pah->s_play) {
int32_t pa_error;
if (pa_simple_flush(pah->s_play, &pa_error) < 0) {
ATA_ERROR("airtaudio::api::Pulse::abortStream: error flushing output device, " << pa_strerror(pa_error) << ".");
m_stream.mutex.unlock();
return airtaudio::errorSystemError;
}
}
m_stream.state = STREAM_STOPPED;
m_stream.mutex.unlock();
return airtaudio::errorNone;
}
bool airtaudio::api::Pulse::probeDeviceOpen(uint32_t _device,
airtaudio::api::StreamMode _mode,
uint32_t _channels,
uint32_t _firstChannel,
uint32_t _sampleRate,
airtaudio::format _format,
uint32_t *_bufferSize,
airtaudio::StreamOptions *_options) {
PulseAudioHandle *pah = 0;
uint64_t bufferBytes = 0;
pa_sample_spec ss;
if (_device != 0) {
return false;
}
if (_mode != INPUT && _mode != OUTPUT) {
return false;
}
if (_channels != 1 && _channels != 2) {
ATA_ERROR("airtaudio::api::Pulse::probeDeviceOpen: unsupported number of channels.");
return false;
}
ss.channels = _channels;
if (_firstChannel != 0) {
return false;
}
bool sr_found = false;
for (const uint32_t *sr = SUPPORTED_SAMPLERATES; *sr; ++sr) {
if (_sampleRate == *sr) {
sr_found = true;
m_stream.sampleRate = _sampleRate;
ss.rate = _sampleRate;
break;
}
}
if (!sr_found) {
ATA_ERROR("airtaudio::api::Pulse::probeDeviceOpen: unsupported sample rate.");
return false;
}
bool sf_found = 0;
for (const rtaudio_pa_format_mapping_t *sf = supported_sampleformats;
sf->airtaudio_format && sf->pa_format != PA_SAMPLE_INVALID;
++sf) {
if (_format == sf->airtaudio_format) {
sf_found = true;
m_stream.userFormat = sf->airtaudio_format;
ss.format = sf->pa_format;
break;
}
}
if (!sf_found) {
ATA_ERROR("airtaudio::api::Pulse::probeDeviceOpen: unsupported sample format.");
return false;
}
// Set interleaving parameters.
if (_options && _options->flags & NONINTERLEAVED) {
m_stream.userInterleaved = false;
} else {
m_stream.userInterleaved = true;
}
m_stream.deviceInterleaved[_mode] = true;
m_stream.nBuffers = 1;
m_stream.doByteSwap[_mode] = false;
m_stream.doConvertBuffer[_mode] = _channels > 1 && !m_stream.userInterleaved;
m_stream.deviceFormat[_mode] = m_stream.userFormat;
m_stream.nUserChannels[_mode] = _channels;
m_stream.nDeviceChannels[_mode] = _channels + _firstChannel;
m_stream.channelOffset[_mode] = 0;
// Allocate necessary internal buffers.
bufferBytes = m_stream.nUserChannels[_mode] * *_bufferSize * formatBytes(m_stream.userFormat);
m_stream.userBuffer[_mode] = (char *) calloc(bufferBytes, 1);
if (m_stream.userBuffer[_mode] == NULL) {
ATA_ERROR("airtaudio::api::Pulse::probeDeviceOpen: error allocating user buffer memory.");
goto error;
}
m_stream.bufferSize = *_bufferSize;
if (m_stream.doConvertBuffer[_mode]) {
bool makeBuffer = true;
bufferBytes = m_stream.nDeviceChannels[_mode] * formatBytes(m_stream.deviceFormat[_mode]);
if (_mode == INPUT) {
if (m_stream.mode == OUTPUT && m_stream.deviceBuffer) {
uint64_t bytesOut = m_stream.nDeviceChannels[0] * formatBytes(m_stream.deviceFormat[0]);
if (bufferBytes <= bytesOut) makeBuffer = false;
}
}
if (makeBuffer) {
bufferBytes *= *_bufferSize;
if (m_stream.deviceBuffer) free(m_stream.deviceBuffer);
m_stream.deviceBuffer = (char *) calloc(bufferBytes, 1);
if (m_stream.deviceBuffer == NULL) {
ATA_ERROR("airtaudio::api::Pulse::probeDeviceOpen: error allocating device buffer memory.");
goto error;
}
}
}
m_stream.device[_mode] = _device;
// Setup the buffer conversion information structure.
if (m_stream.doConvertBuffer[_mode]) {
setConvertInfo(_mode, _firstChannel);
}
if (!m_stream.apiHandle) {
PulseAudioHandle *pah = new PulseAudioHandle;
if (!pah) {
ATA_ERROR("airtaudio::api::Pulse::probeDeviceOpen: error allocating memory for handle.");
goto error;
}
m_stream.apiHandle = pah;
}
pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
int32_t error;
switch (_mode) {
case INPUT:
pah->s_rec = pa_simple_new(NULL, "RtAudio", PA_STREAM_RECORD, NULL, "Record", &ss, NULL, NULL, &error);
if (!pah->s_rec) {
ATA_ERROR("airtaudio::api::Pulse::probeDeviceOpen: error connecting input to PulseAudio server.");
goto error;
}
break;
case OUTPUT:
pah->s_play = pa_simple_new(NULL, "RtAudio", PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error);
if (!pah->s_play) {
ATA_ERROR("airtaudio::api::Pulse::probeDeviceOpen: error connecting output to PulseAudio server.");
goto error;
}
break;
default:
goto error;
}
if (m_stream.mode == UNINITIALIZED) {
m_stream.mode = _mode;
} else if (m_stream.mode == _mode) {
goto error;
}else {
m_stream.mode = DUPLEX;
}
if (!m_stream.callbackInfo.isRunning) {
m_stream.callbackInfo.object = this;
m_stream.callbackInfo.isRunning = true;
pah->thread = new std::thread(pulseaudio_callback, (void *)&m_stream.callbackInfo);
if (pah->thread == NULL) {
ATA_ERROR("airtaudio::api::Pulse::probeDeviceOpen: error creating thread.");
goto error;
}
}
m_stream.state = STREAM_STOPPED;
return true;
error:
if (pah && m_stream.callbackInfo.isRunning) {
delete pah;
m_stream.apiHandle = 0;
}
for (int32_t i=0; i<2; i++) {
if (m_stream.userBuffer[i]) {
free(m_stream.userBuffer[i]);
m_stream.userBuffer[i] = 0;
}
}
if (m_stream.deviceBuffer) {
free(m_stream.deviceBuffer);
m_stream.deviceBuffer = 0;
}
return false;
}
#endif