audio-orchestra/airtaudio/api/Pulse.cpp

422 lines
13 KiB
C++
Raw Normal View History

2015-02-09 21:44:32 +01:00
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
* @fork from RTAudio
*/
#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>
#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 {
2015-02-08 15:09:39 +01:00
enum 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},
2015-02-08 15:09:39 +01:00
{audio::format_unknow, PA_SAMPLE_INVALID}};
namespace airtaudio {
namespace api {
class PulsePrivate {
public:
pa_simple *s_play;
pa_simple *s_rec;
2015-02-24 22:20:11 +01:00
std11::shared_ptr<std11::thread> thread;
bool threadRunning;
2015-02-24 22:20:11 +01:00
std11::condition_variable runnable_cv;
bool runnable;
PulsePrivate() :
s_play(0),
s_rec(0),
threadRunning(false),
runnable(false) {
}
};
2014-03-11 22:37:22 +01:00
}
}
airtaudio::api::Pulse::Pulse() :
m_private(new airtaudio::api::PulsePrivate()) {
}
airtaudio::api::Pulse::~Pulse() {
2015-02-09 21:44:32 +01:00
if (m_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;
}
2015-02-09 21:44:32 +01:00
static void pulseaudio_callback(void* _userData) {
airtaudio::api::Pulse* myClass = reinterpret_cast<airtaudio::api::Pulse*>(_userData);
myClass->callbackEvent();
}
void airtaudio::api::Pulse::callbackEvent() {
etk::log::setThreadName("Pulse IO-" + m_name);
while (m_private->threadRunning == true) {
2015-02-09 21:44:32 +01:00
callbackEventOneCycle();
}
}
2015-02-06 23:54:08 +01:00
enum airtaudio::error airtaudio::api::Pulse::closeStream() {
m_private->threadRunning = false;
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);
}
2015-02-09 21:44:32 +01:00
m_userBuffer[0].clear();
m_userBuffer[1].clear();
m_state = airtaudio::state_closed;
m_mode = airtaudio::mode_unknow;
2015-02-06 23:54:08 +01:00
return airtaudio::error_none;
}
2015-02-09 21:44:32 +01:00
void airtaudio::api::Pulse::callbackEventOneCycle() {
if (m_state == airtaudio::state_stopped) {
2015-02-24 22:20:11 +01:00
std11::unique_lock<std11::mutex> lck(m_mutex);
while (!m_private->runnable) {
m_private->runnable_cv.wait(lck);
}
2015-02-09 21:44:32 +01:00
if (m_state != airtaudio::state_running) {
m_mutex.unlock();
return;
}
}
2015-02-09 21:44:32 +01:00
if (m_state == airtaudio::state_closed) {
ATA_ERROR("the stream is closed ... this shouldn't happen!");
return;
}
2015-02-24 22:20:11 +01:00
std11::chrono::system_clock::time_point streamTime = getStreamTime();
std::vector<enum airtaudio::status> status;
int32_t doStopStream = m_callback(&m_userBuffer[airtaudio::modeToIdTable(airtaudio::mode_input)][0],
streamTime,
&m_userBuffer[airtaudio::modeToIdTable(airtaudio::mode_output)][0],
streamTime,
m_bufferSize,
status);
if (doStopStream == 2) {
abortStream();
return;
}
2015-02-09 21:44:32 +01:00
m_mutex.lock();
void *pulse_in = m_doConvertBuffer[airtaudio::modeToIdTable(airtaudio::mode_input)] ? m_deviceBuffer : &m_userBuffer[airtaudio::modeToIdTable(airtaudio::mode_input)][0];
void *pulse_out = m_doConvertBuffer[airtaudio::modeToIdTable(airtaudio::mode_output)] ? m_deviceBuffer : &m_userBuffer[airtaudio::modeToIdTable(airtaudio::mode_output)][0];
if (m_state != airtaudio::state_running) {
goto unlock;
}
int32_t pa_error;
size_t bytes;
2015-02-09 21:44:32 +01:00
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (m_doConvertBuffer[airtaudio::modeToIdTable(airtaudio::mode_output)]) {
convertBuffer(m_deviceBuffer,
&m_userBuffer[airtaudio::modeToIdTable(airtaudio::mode_output)][0],
m_convertInfo[airtaudio::modeToIdTable(airtaudio::mode_output)]);
bytes = m_nDeviceChannels[airtaudio::modeToIdTable(airtaudio::mode_output)] * m_bufferSize * audio::getFormatBytes(m_deviceFormat[airtaudio::modeToIdTable(airtaudio::mode_output)]);
} else {
2015-02-09 21:44:32 +01:00
bytes = m_nUserChannels[airtaudio::modeToIdTable(airtaudio::mode_output)] * m_bufferSize * audio::getFormatBytes(m_userFormat);
}
if (pa_simple_write(m_private->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-09 21:44:32 +01:00
if (m_mode == airtaudio::mode_input || m_mode == airtaudio::mode_duplex) {
if (m_doConvertBuffer[airtaudio::modeToIdTable(airtaudio::mode_input)]) {
bytes = m_nDeviceChannels[airtaudio::modeToIdTable(airtaudio::mode_input)] * m_bufferSize * audio::getFormatBytes(m_deviceFormat[airtaudio::modeToIdTable(airtaudio::mode_input)]);
} else {
2015-02-09 21:44:32 +01:00
bytes = m_nUserChannels[airtaudio::modeToIdTable(airtaudio::mode_input)] * m_bufferSize * audio::getFormatBytes(m_userFormat);
}
if (pa_simple_read(m_private->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-09 21:44:32 +01:00
if (m_doConvertBuffer[airtaudio::modeToIdTable(airtaudio::mode_input)]) {
convertBuffer(&m_userBuffer[airtaudio::modeToIdTable(airtaudio::mode_input)][0],
m_deviceBuffer,
m_convertInfo[airtaudio::modeToIdTable(airtaudio::mode_input)]);
}
}
unlock:
2015-02-09 21:44:32 +01:00
m_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() {
2015-02-10 22:38:30 +01:00
// TODO : Check return ...
airtaudio::Api::startStream();
2015-02-09 21:44:32 +01:00
if (m_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-09 21:44:32 +01:00
if (m_state == airtaudio::state_running) {
ATA_ERROR("the stream is already running!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_warning;
}
2015-02-09 21:44:32 +01:00
m_mutex.lock();
m_state = airtaudio::state_running;
m_private->runnable = true;
m_private->runnable_cv.notify_one();
2015-02-09 21:44:32 +01:00
m_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() {
2015-02-09 21:44:32 +01:00
if (m_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-09 21:44:32 +01:00
if (m_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-09 21:44:32 +01:00
m_state = airtaudio::state_stopped;
m_mutex.lock();
if (m_private->s_play) {
int32_t pa_error;
if (pa_simple_drain(m_private->s_play, &pa_error) < 0) {
ATA_ERROR("error draining output device, " << pa_strerror(pa_error) << ".");
2015-02-09 21:44:32 +01:00
m_mutex.unlock();
2015-02-06 23:54:08 +01:00
return airtaudio::error_systemError;
}
}
2015-02-09 21:44:32 +01:00
m_state = airtaudio::state_stopped;
m_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() {
2015-02-09 21:44:32 +01:00
if (m_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-09 21:44:32 +01:00
if (m_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-09 21:44:32 +01:00
m_state = airtaudio::state_stopped;
m_mutex.lock();
if (m_private && m_private->s_play) {
int32_t pa_error;
if (pa_simple_flush(m_private->s_play, &pa_error) < 0) {
ATA_ERROR("error flushing output device, " << pa_strerror(pa_error) << ".");
2015-02-09 21:44:32 +01:00
m_mutex.unlock();
2015-02-06 23:54:08 +01:00
return airtaudio::error_systemError;
}
}
2015-02-09 21:44:32 +01:00
m_state = airtaudio::state_stopped;
m_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,
const airtaudio.::StreamOptions& _options) {
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;
2015-02-09 21:44:32 +01:00
m_sampleRate = _sampleRate;
2014-03-12 23:55:49 +01:00
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;
2015-02-09 21:44:32 +01:00
m_userFormat = sf->airtaudio_format;
ss.format = sf->pa_format;
break;
}
}
if (!sf_found) {
ATA_ERROR("unsupported sample format.");
return false;
}
2015-02-09 21:44:32 +01:00
m_deviceInterleaved[modeToIdTable(_mode)] = true;
m_nBuffers = 1;
m_doByteSwap[modeToIdTable(_mode)] = false;
m_doConvertBuffer[modeToIdTable(_mode)] = false;
m_deviceFormat[modeToIdTable(_mode)] = m_userFormat;
m_nUserChannels[modeToIdTable(_mode)] = _channels;
m_nDeviceChannels[modeToIdTable(_mode)] = _channels + _firstChannel;
m_channelOffset[modeToIdTable(_mode)] = 0;
// Allocate necessary internal buffers.
2015-02-09 21:44:32 +01:00
bufferBytes = m_nUserChannels[modeToIdTable(_mode)] * *_bufferSize * audio::getFormatBytes(m_userFormat);
m_userBuffer[modeToIdTable(_mode)].resize(bufferBytes, 0);
if (m_userBuffer[modeToIdTable(_mode)].size() == 0) {
ATA_ERROR("error allocating user buffer memory.");
goto error;
}
2015-02-09 21:44:32 +01:00
m_bufferSize = *_bufferSize;
if (m_doConvertBuffer[modeToIdTable(_mode)]) {
bool makeBuffer = true;
2015-02-09 21:44:32 +01:00
bufferBytes = m_nDeviceChannels[modeToIdTable(_mode)] * audio::getFormatBytes(m_deviceFormat[modeToIdTable(_mode)]);
2015-02-06 23:54:08 +01:00
if (_mode == airtaudio::mode_input) {
2015-02-09 21:44:32 +01:00
if (m_mode == airtaudio::mode_output && m_deviceBuffer) {
uint64_t bytesOut = m_nDeviceChannels[0] * audio::getFormatBytes(m_deviceFormat[0]);
if (bufferBytes <= bytesOut) makeBuffer = false;
}
}
if (makeBuffer) {
2014-03-12 23:55:49 +01:00
bufferBytes *= *_bufferSize;
2015-02-09 21:44:32 +01:00
if (m_deviceBuffer) free(m_deviceBuffer);
m_deviceBuffer = (char *) calloc(bufferBytes, 1);
if (m_deviceBuffer == nullptr) {
ATA_ERROR("error allocating device buffer memory.");
goto error;
}
}
}
2015-02-09 21:44:32 +01:00
m_device[modeToIdTable(_mode)] = _device;
// Setup the buffer conversion information structure.
2015-02-09 21:44:32 +01:00
if (m_doConvertBuffer[modeToIdTable(_mode)]) {
2014-03-12 23:55:49 +01:00
setConvertInfo(_mode, _firstChannel);
}
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:
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;
2015-02-06 23:54:08 +01:00
case airtaudio::mode_output:
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;
}
break;
default:
goto error;
}
2015-02-09 21:44:32 +01:00
if (m_mode == airtaudio::mode_unknow) {
m_mode = _mode;
} else if (m_mode == _mode) {
goto error;
}else {
2015-02-09 21:44:32 +01:00
m_mode = airtaudio::mode_duplex;
}
if (!m_private->threadRunning) {
m_private->threadRunning = true;
2015-02-24 22:20:11 +01:00
std11::shared_ptr<std11::thread> tmpThread(new std11::thread(&pulseaudio_callback, this));
m_private->thread = std::move(tmpThread);
if (m_private->thread == nullptr) {
ATA_ERROR("error creating thread.");
goto error;
}
}
2015-02-09 21:44:32 +01:00
m_state = airtaudio::state_stopped;
return true;
error:
for (int32_t i=0; i<2; i++) {
2015-02-09 21:44:32 +01:00
m_userBuffer[i].clear();
}
2015-02-09 21:44:32 +01:00
if (m_deviceBuffer) {
free(m_deviceBuffer);
m_deviceBuffer = 0;
}
return false;
}
#endif