audio-orchestra/airtaudio/api/Pulse.cpp

440 lines
14 KiB
C++
Raw Normal View History

/**
* @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>
2014-03-12 23:55:49 +01:00
#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>
2015-01-27 23:06:19 +01:00
#undef __class__
#define __class__ "api::Pulse"
airtaudio::Api* airtaudio::api::Pulse::Create() {
2014-03-11 22:37:22 +01:00
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 {
audio::format airtaudio_format;
pa_sample_format_t pa_format;
};
static const rtaudio_pa_format_mapping_t supported_sampleformats[] = {
{audio::format_int16, PA_SAMPLE_S16LE},
{audio::format_int32, PA_SAMPLE_S32LE},
{audio::format_float, 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() :
2014-03-11 22:37:22 +01:00
s_play(0),
s_rec(0),
runnable(false) {
}
};
airtaudio::api::Pulse::~Pulse() {
2015-02-06 23:54:08 +01:00
if (m_stream.state != airtaudio::state_closed) {
closeStream();
2014-03-12 23:55:49 +01:00
}
}
uint32_t airtaudio::api::Pulse::getDeviceCount() {
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.push_back(audio::format_int16);
info.nativeFormats.push_back(audio::format_int32);
info.nativeFormats.push_back(audio::format_float);
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();
}
}
2015-02-06 23:54:08 +01:00
enum airtaudio::error airtaudio::api::Pulse::closeStream() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
m_stream.callbackInfo.isRunning = false;
if (pah) {
m_stream.mutex.lock();
2015-02-06 23:54:08 +01:00
if (m_stream.state == airtaudio::state_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, nullptr);
pa_simple_free(pah->s_play);
}
if (pah->s_rec) {
pa_simple_free(pah->s_rec);
}
delete pah;
m_stream.apiHandle = nullptr;
}
if (m_stream.userBuffer[0] != nullptr) {
free(m_stream.userBuffer[0]);
m_stream.userBuffer[0] = nullptr;
}
if (m_stream.userBuffer[1] != nullptr) {
free(m_stream.userBuffer[1]);
m_stream.userBuffer[1] = nullptr;
}
2015-02-06 23:54:08 +01:00
m_stream.state = airtaudio::state_closed;
m_stream.mode = airtaudio::mode_unknow;
return airtaudio::error_none;
}
void airtaudio::api::Pulse::callbackEvent() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
2015-02-06 23:54:08 +01:00
if (m_stream.state == airtaudio::state_stopped) {
std::unique_lock<std::mutex> lck(m_stream.mutex);
while (!pah->runnable) {
pah->runnable_cv.wait(lck);
}
2015-02-06 23:54:08 +01:00
if (m_stream.state != airtaudio::state_running) {
m_stream.mutex.unlock();
return;
}
}
2015-02-06 23:54:08 +01:00
if (m_stream.state == airtaudio::state_closed) {
ATA_ERROR("the stream is closed ... this shouldn't happen!");
return;
}
double streamTime = getStreamTime();
2015-02-06 23:54:08 +01:00
enum airtaudio::status status = airtaudio::status_ok;
int32_t doStopStream = m_stream.callbackInfo.callback(m_stream.userBuffer[airtaudio::mode_output],
m_stream.userBuffer[airtaudio::mode_input],
m_stream.bufferSize,
streamTime,
status);
if (doStopStream == 2) {
abortStream();
return;
}
m_stream.mutex.lock();
2015-02-06 23:54:08 +01:00
void *pulse_in = m_stream.doConvertBuffer[airtaudio::mode_input] ? m_stream.deviceBuffer : m_stream.userBuffer[airtaudio::mode_input];
void *pulse_out = m_stream.doConvertBuffer[airtaudio::mode_output] ? m_stream.deviceBuffer : m_stream.userBuffer[airtaudio::mode_output];
if (m_stream.state != airtaudio::state_running) {
goto unlock;
}
int32_t pa_error;
size_t bytes;
2015-02-06 23:54:08 +01:00
if ( m_stream.mode == airtaudio::mode_output
|| m_stream.mode == airtaudio::mode_duplex) {
if (m_stream.doConvertBuffer[airtaudio::mode_output]) {
convertBuffer(m_stream.deviceBuffer,
2015-02-06 23:54:08 +01:00
m_stream.userBuffer[airtaudio::mode_output],
m_stream.convertInfo[airtaudio::mode_output]);
bytes = m_stream.nDeviceChannels[airtaudio::mode_output] * m_stream.bufferSize * formatBytes(m_stream.deviceFormat[airtaudio::mode_output]);
} else {
2015-02-06 23:54:08 +01:00
bytes = m_stream.nUserChannels[airtaudio::mode_output] * m_stream.bufferSize * formatBytes(m_stream.userFormat);
}
if (pa_simple_write(pah->s_play, pulse_out, bytes, &pa_error) < 0) {
ATA_ERROR("audio write error, " << pa_strerror(pa_error) << ".");
2014-03-12 23:55:49 +01:00
return;
}
}
2015-02-06 23:54:08 +01:00
if (m_stream.mode == airtaudio::mode_input || m_stream.mode == airtaudio::mode_duplex) {
if (m_stream.doConvertBuffer[airtaudio::mode_input]) {
bytes = m_stream.nDeviceChannels[airtaudio::mode_input] * m_stream.bufferSize * formatBytes(m_stream.deviceFormat[airtaudio::mode_input]);
} else {
2015-02-06 23:54:08 +01:00
bytes = m_stream.nUserChannels[airtaudio::mode_input] * m_stream.bufferSize * formatBytes(m_stream.userFormat);
}
if (pa_simple_read(pah->s_rec, pulse_in, bytes, &pa_error) < 0) {
ATA_ERROR("audio read error, " << pa_strerror(pa_error) << ".");
2014-03-12 23:55:49 +01:00
return;
}
2015-02-06 23:54:08 +01:00
if (m_stream.doConvertBuffer[airtaudio::mode_input]) {
convertBuffer(m_stream.userBuffer[airtaudio::mode_input],
m_stream.deviceBuffer,
2015-02-06 23:54:08 +01:00
m_stream.convertInfo[airtaudio::mode_input]);
}
}
unlock:
m_stream.mutex.unlock();
airtaudio::Api::tickStreamTime();
if (doStopStream == 1) {
stopStream();
2014-03-12 23:55:49 +01:00
return;
}
2014-03-12 23:55:49 +01:00
return;
}
2015-02-06 23:54:08 +01:00
enum airtaudio::error airtaudio::api::Pulse::startStream() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
2015-02-06 23:54:08 +01:00
if (m_stream.state == airtaudio::state_closed) {
ATA_ERROR("the stream is not open!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_invalidUse;
}
2015-02-06 23:54:08 +01:00
if (m_stream.state == airtaudio::state_running) {
ATA_ERROR("the stream is already running!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_warning;
}
m_stream.mutex.lock();
2015-02-06 23:54:08 +01:00
m_stream.state = airtaudio::state_running;
pah->runnable = true;
pah->runnable_cv.notify_one();
m_stream.mutex.unlock();
2015-02-06 23:54:08 +01:00
return airtaudio::error_none;
}
2015-02-06 23:54:08 +01:00
enum airtaudio::error airtaudio::api::Pulse::stopStream() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
2015-02-06 23:54:08 +01:00
if (m_stream.state == airtaudio::state_closed) {
ATA_ERROR("the stream is not open!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_invalidUse;
}
2015-02-06 23:54:08 +01:00
if (m_stream.state == airtaudio::state_stopped) {
ATA_ERROR("the stream is already stopped!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_warning;
}
2015-02-06 23:54:08 +01:00
m_stream.state = airtaudio::state_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("error draining output device, " << pa_strerror(pa_error) << ".");
m_stream.mutex.unlock();
2015-02-06 23:54:08 +01:00
return airtaudio::error_systemError;
}
}
2015-02-06 23:54:08 +01:00
m_stream.state = airtaudio::state_stopped;
m_stream.mutex.unlock();
2015-02-06 23:54:08 +01:00
return airtaudio::error_none;
}
2015-02-06 23:54:08 +01:00
enum airtaudio::error airtaudio::api::Pulse::abortStream() {
PulseAudioHandle *pah = static_cast<PulseAudioHandle*>(m_stream.apiHandle);
2015-02-06 23:54:08 +01:00
if (m_stream.state == airtaudio::state_closed) {
ATA_ERROR("the stream is not open!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_invalidUse;
}
2015-02-06 23:54:08 +01:00
if (m_stream.state == airtaudio::state_stopped) {
ATA_ERROR("the stream is already stopped!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_warning;
}
2015-02-06 23:54:08 +01:00
m_stream.state = airtaudio::state_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("error flushing output device, " << pa_strerror(pa_error) << ".");
m_stream.mutex.unlock();
2015-02-06 23:54:08 +01:00
return airtaudio::error_systemError;
}
}
2015-02-06 23:54:08 +01:00
m_stream.state = airtaudio::state_stopped;
m_stream.mutex.unlock();
2015-02-06 23:54:08 +01:00
return airtaudio::error_none;
}
2014-03-12 23:55:49 +01:00
bool airtaudio::api::Pulse::probeDeviceOpen(uint32_t _device,
2015-02-06 23:54:08 +01:00
airtaudio::mode _mode,
2014-03-12 23:55:49 +01:00
uint32_t _channels,
uint32_t _firstChannel,
uint32_t _sampleRate,
audio::format _format,
2014-03-12 23:55:49 +01:00
uint32_t *_bufferSize,
airtaudio::StreamOptions *_options) {
PulseAudioHandle *pah = 0;
uint64_t bufferBytes = 0;
pa_sample_spec ss;
2014-03-12 23:55:49 +01:00
if (_device != 0) {
return false;
}
2015-02-06 23:54:08 +01:00
if (_mode != airtaudio::mode_input && _mode != airtaudio::mode_output) {
return false;
}
2014-03-12 23:55:49 +01:00
if (_channels != 1 && _channels != 2) {
ATA_ERROR("unsupported number of channels.");
return false;
}
2014-03-12 23:55:49 +01:00
ss.channels = _channels;
if (_firstChannel != 0) {
return false;
}
bool sr_found = false;
for (const uint32_t *sr = SUPPORTED_SAMPLERATES; *sr; ++sr) {
2014-03-12 23:55:49 +01:00
if (_sampleRate == *sr) {
sr_found = true;
2014-03-12 23:55:49 +01:00
m_stream.sampleRate = _sampleRate;
ss.rate = _sampleRate;
break;
}
}
if (!sr_found) {
ATA_ERROR("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) {
2014-03-12 23:55:49 +01:00
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("unsupported sample format.");
return false;
}
2015-02-06 23:54:08 +01:00
m_stream.deviceInterleaved[modeToIdTable(_mode)] = true;
m_stream.nBuffers = 1;
2015-02-06 23:54:08 +01:00
m_stream.doByteSwap[modeToIdTable(_mode)] = false;
m_stream.doConvertBuffer[modeToIdTable(_mode)] = false;
m_stream.deviceFormat[modeToIdTable(_mode)] = m_stream.userFormat;
m_stream.nUserChannels[modeToIdTable(_mode)] = _channels;
m_stream.nDeviceChannels[modeToIdTable(_mode)] = _channels + _firstChannel;
m_stream.channelOffset[modeToIdTable(_mode)] = 0;
// Allocate necessary internal buffers.
2015-02-06 23:54:08 +01:00
bufferBytes = m_stream.nUserChannels[modeToIdTable(_mode)] * *_bufferSize * formatBytes(m_stream.userFormat);
m_stream.userBuffer[modeToIdTable(_mode)] = (char *) calloc(bufferBytes, 1);
if (m_stream.userBuffer[modeToIdTable(_mode)] == nullptr) {
ATA_ERROR("error allocating user buffer memory.");
goto error;
}
2014-03-12 23:55:49 +01:00
m_stream.bufferSize = *_bufferSize;
2015-02-06 23:54:08 +01:00
if (m_stream.doConvertBuffer[modeToIdTable(_mode)]) {
bool makeBuffer = true;
2015-02-06 23:54:08 +01:00
bufferBytes = m_stream.nDeviceChannels[modeToIdTable(_mode)] * formatBytes(m_stream.deviceFormat[modeToIdTable(_mode)]);
if (_mode == airtaudio::mode_input) {
if (m_stream.mode == airtaudio::mode_output && m_stream.deviceBuffer) {
uint64_t bytesOut = m_stream.nDeviceChannels[0] * formatBytes(m_stream.deviceFormat[0]);
if (bufferBytes <= bytesOut) makeBuffer = false;
}
}
if (makeBuffer) {
2014-03-12 23:55:49 +01:00
bufferBytes *= *_bufferSize;
if (m_stream.deviceBuffer) free(m_stream.deviceBuffer);
m_stream.deviceBuffer = (char *) calloc(bufferBytes, 1);
if (m_stream.deviceBuffer == nullptr) {
ATA_ERROR("error allocating device buffer memory.");
goto error;
}
}
}
2015-02-06 23:54:08 +01:00
m_stream.device[modeToIdTable(_mode)] = _device;
// Setup the buffer conversion information structure.
2015-02-06 23:54:08 +01:00
if (m_stream.doConvertBuffer[modeToIdTable(_mode)]) {
2014-03-12 23:55:49 +01:00
setConvertInfo(_mode, _firstChannel);
}
if (!m_stream.apiHandle) {
PulseAudioHandle *pah = new PulseAudioHandle;
if (!pah) {
ATA_ERROR("error allocating memory for handle.");
goto error;
}
m_stream.apiHandle = pah;
}
pah = static_cast<PulseAudioHandle *>(m_stream.apiHandle);
int32_t error;
2014-03-12 23:55:49 +01:00
switch (_mode) {
2015-02-06 23:54:08 +01:00
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) {
ATA_ERROR("error connecting input to PulseAudio server.");
goto error;
}
break;
2015-02-06 23:54:08 +01:00
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) {
ATA_ERROR("error connecting output to PulseAudio server.");
goto error;
}
break;
default:
goto error;
}
2015-02-06 23:54:08 +01:00
if (m_stream.mode == airtaudio::mode_unknow) {
2014-03-12 23:55:49 +01:00
m_stream.mode = _mode;
} else if (m_stream.mode == _mode) {
goto error;
}else {
2015-02-06 23:54:08 +01:00
m_stream.mode = airtaudio::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 == nullptr) {
ATA_ERROR("error creating thread.");
goto error;
}
}
2015-02-06 23:54:08 +01:00
m_stream.state = airtaudio::state_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