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
|
2014-03-11 21:46:00 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#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>
|
2014-03-11 21:46:00 +01:00
|
|
|
#include <pulse/error.h>
|
|
|
|
#include <pulse/simple.h>
|
|
|
|
#include <cstdio>
|
|
|
|
|
2015-01-27 23:06:19 +01:00
|
|
|
#undef __class__
|
|
|
|
#define __class__ "api::Pulse"
|
|
|
|
|
2014-05-15 21:37:39 +02:00
|
|
|
airtaudio::Api* airtaudio::api::Pulse::Create() {
|
2014-03-11 22:37:22 +01:00
|
|
|
return new airtaudio::api::Pulse();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-11 21:46:00 +01:00
|
|
|
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;
|
2014-03-11 21:46:00 +01:00
|
|
|
pa_sample_format_t pa_format;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const rtaudio_pa_format_mapping_t supported_sampleformats[] = {
|
2015-02-05 23:31:22 +01:00
|
|
|
{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}};
|
2014-03-11 21:46:00 +01:00
|
|
|
|
2015-02-10 21:01:53 +01:00
|
|
|
|
|
|
|
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;
|
2015-02-17 21:08:15 +01:00
|
|
|
bool threadRunning;
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::condition_variable runnable_cv;
|
2015-02-10 21:01:53 +01:00
|
|
|
bool runnable;
|
|
|
|
PulsePrivate() :
|
|
|
|
s_play(0),
|
|
|
|
s_rec(0),
|
2015-02-17 21:08:15 +01:00
|
|
|
threadRunning(false),
|
2015-02-10 21:01:53 +01:00
|
|
|
runnable(false) {
|
|
|
|
|
|
|
|
}
|
|
|
|
};
|
2014-03-11 22:37:22 +01:00
|
|
|
}
|
2015-02-10 21:01:53 +01:00
|
|
|
}
|
|
|
|
airtaudio::api::Pulse::Pulse() :
|
|
|
|
m_private(new airtaudio::api::PulsePrivate()) {
|
|
|
|
|
|
|
|
}
|
2014-03-11 21:46:00 +01:00
|
|
|
|
2014-05-15 21:37:39 +02:00
|
|
|
airtaudio::api::Pulse::~Pulse() {
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state != airtaudio::state_closed) {
|
2014-03-11 21:46:00 +01:00
|
|
|
closeStream();
|
2014-03-12 23:55:49 +01:00
|
|
|
}
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
2014-05-15 21:37:39 +02:00
|
|
|
uint32_t airtaudio::api::Pulse::getDeviceCount() {
|
2014-03-11 21:46:00 +01:00
|
|
|
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);
|
|
|
|
}
|
2015-02-05 23:31:22 +01:00
|
|
|
info.nativeFormats.push_back(audio::format_int16);
|
|
|
|
info.nativeFormats.push_back(audio::format_int32);
|
|
|
|
info.nativeFormats.push_back(audio::format_float);
|
2014-03-11 21:46:00 +01:00
|
|
|
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() {
|
2015-02-17 21:08:15 +01:00
|
|
|
etk::log::setThreadName("Pulse IO-" + m_name);
|
|
|
|
while (m_private->threadRunning == true) {
|
2015-02-09 21:44:32 +01:00
|
|
|
callbackEventOneCycle();
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-06 23:54:08 +01:00
|
|
|
enum airtaudio::error airtaudio::api::Pulse::closeStream() {
|
2015-02-17 21:08:15 +01:00
|
|
|
m_private->threadRunning = false;
|
2015-02-10 21:01:53 +01:00
|
|
|
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);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
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;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
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);
|
2015-02-10 21:01:53 +01:00
|
|
|
while (!m_private->runnable) {
|
|
|
|
m_private->runnable_cv.wait(lck);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state != airtaudio::state_running) {
|
|
|
|
m_mutex.unlock();
|
2014-03-11 21:46:00 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state == airtaudio::state_closed) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("the stream is closed ... this shouldn't happen!");
|
2014-03-11 21:46:00 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::chrono::system_clock::time_point streamTime = getStreamTime();
|
2015-02-17 21:08:15 +01:00
|
|
|
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);
|
2014-03-11 21:46:00 +01:00
|
|
|
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) {
|
2014-03-11 21:46:00 +01:00
|
|
|
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)]);
|
2014-03-11 21:46:00 +01:00
|
|
|
} else {
|
2015-02-09 21:44:32 +01:00
|
|
|
bytes = m_nUserChannels[airtaudio::modeToIdTable(airtaudio::mode_output)] * m_bufferSize * audio::getFormatBytes(m_userFormat);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-10 21:01:53 +01:00
|
|
|
if (pa_simple_write(m_private->s_play, pulse_out, bytes, &pa_error) < 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("audio write error, " << pa_strerror(pa_error) << ".");
|
2014-03-12 23:55:49 +01:00
|
|
|
return;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
}
|
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)]);
|
2014-03-11 21:46:00 +01:00
|
|
|
} else {
|
2015-02-09 21:44:32 +01:00
|
|
|
bytes = m_nUserChannels[airtaudio::modeToIdTable(airtaudio::mode_input)] * m_bufferSize * audio::getFormatBytes(m_userFormat);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-10 21:01:53 +01:00
|
|
|
if (pa_simple_read(m_private->s_rec, pulse_in, bytes, &pa_error) < 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("audio read error, " << pa_strerror(pa_error) << ".");
|
2014-03-12 23:55:49 +01:00
|
|
|
return;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
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)]);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
unlock:
|
2015-02-09 21:44:32 +01:00
|
|
|
m_mutex.unlock();
|
2014-03-11 21:46:00 +01:00
|
|
|
airtaudio::Api::tickStreamTime();
|
|
|
|
if (doStopStream == 1) {
|
|
|
|
stopStream();
|
2014-03-12 23:55:49 +01:00
|
|
|
return;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2014-03-12 23:55:49 +01:00
|
|
|
return;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
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) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("the stream is not open!");
|
2015-02-06 23:54:08 +01:00
|
|
|
return airtaudio::error_invalidUse;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state == airtaudio::state_running) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("the stream is already running!");
|
2015-02-06 23:54:08 +01:00
|
|
|
return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_mutex.lock();
|
|
|
|
m_state = airtaudio::state_running;
|
2015-02-10 21:01:53 +01:00
|
|
|
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;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
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) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("the stream is not open!");
|
2015-02-06 23:54:08 +01:00
|
|
|
return airtaudio::error_invalidUse;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state == airtaudio::state_stopped) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("the stream is already stopped!");
|
2015-02-06 23:54:08 +01:00
|
|
|
return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_state = airtaudio::state_stopped;
|
|
|
|
m_mutex.lock();
|
2015-02-10 21:01:53 +01:00
|
|
|
if (m_private->s_play) {
|
2014-03-11 21:46:00 +01:00
|
|
|
int32_t pa_error;
|
2015-02-10 21:01:53 +01:00
|
|
|
if (pa_simple_drain(m_private->s_play, &pa_error) < 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
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;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
}
|
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-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
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) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("the stream is not open!");
|
2015-02-06 23:54:08 +01:00
|
|
|
return airtaudio::error_invalidUse;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state == airtaudio::state_stopped) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("the stream is already stopped!");
|
2015-02-06 23:54:08 +01:00
|
|
|
return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_state = airtaudio::state_stopped;
|
|
|
|
m_mutex.lock();
|
2015-02-10 21:01:53 +01:00
|
|
|
if (m_private && m_private->s_play) {
|
2014-03-11 21:46:00 +01:00
|
|
|
int32_t pa_error;
|
2015-02-10 21:01:53 +01:00
|
|
|
if (pa_simple_flush(m_private->s_play, &pa_error) < 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
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;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
}
|
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-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
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,
|
2015-02-05 23:31:22 +01:00
|
|
|
audio::format _format,
|
2014-03-12 23:55:49 +01:00
|
|
|
uint32_t *_bufferSize,
|
2015-02-27 21:07:17 +01:00
|
|
|
const airtaudio.::StreamOptions& _options) {
|
2014-03-11 21:46:00 +01:00
|
|
|
uint64_t bufferBytes = 0;
|
|
|
|
pa_sample_spec ss;
|
2014-03-12 23:55:49 +01:00
|
|
|
if (_device != 0) {
|
2014-03-11 21:46:00 +01:00
|
|
|
return false;
|
|
|
|
}
|
2015-02-06 23:54:08 +01:00
|
|
|
if (_mode != airtaudio::mode_input && _mode != airtaudio::mode_output) {
|
2014-03-11 21:46:00 +01:00
|
|
|
return false;
|
|
|
|
}
|
2014-03-12 23:55:49 +01:00
|
|
|
if (_channels != 1 && _channels != 2) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("unsupported number of channels.");
|
2014-03-11 21:46:00 +01:00
|
|
|
return false;
|
|
|
|
}
|
2014-03-12 23:55:49 +01:00
|
|
|
ss.channels = _channels;
|
|
|
|
if (_firstChannel != 0) {
|
2014-03-11 21:46:00 +01:00
|
|
|
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) {
|
2014-03-11 21:46:00 +01:00
|
|
|
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;
|
2014-03-11 21:46:00 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!sr_found) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("unsupported sample rate.");
|
2014-03-11 21:46:00 +01:00
|
|
|
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) {
|
2014-03-11 21:46:00 +01:00
|
|
|
sf_found = true;
|
2015-02-09 21:44:32 +01:00
|
|
|
m_userFormat = sf->airtaudio_format;
|
2014-03-11 21:46:00 +01:00
|
|
|
ss.format = sf->pa_format;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!sf_found) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("unsupported sample format.");
|
2014-03-11 21:46:00 +01:00
|
|
|
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;
|
2014-03-11 21:46:00 +01:00
|
|
|
// 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) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("error allocating user buffer memory.");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto error;
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_bufferSize = *_bufferSize;
|
|
|
|
if (m_doConvertBuffer[modeToIdTable(_mode)]) {
|
2014-03-11 21:46:00 +01:00
|
|
|
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]);
|
2014-03-11 21:46:00 +01:00
|
|
|
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) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("error allocating device buffer memory.");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_device[modeToIdTable(_mode)] = _device;
|
2014-03-11 21:46:00 +01:00
|
|
|
// 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);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
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:
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->s_rec = pa_simple_new(nullptr, "airtAudio", PA_STREAM_RECORD, nullptr, "Record", &ss, nullptr, nullptr, &error);
|
|
|
|
if (!m_private->s_rec) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("error connecting input to PulseAudio server.");
|
2014-03-13 21:16:30 +01:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
break;
|
2015-02-06 23:54:08 +01:00
|
|
|
case airtaudio::mode_output:
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->s_play = pa_simple_new(nullptr, "airtAudio", PA_STREAM_PLAYBACK, nullptr, "Playback", &ss, nullptr, nullptr, &error);
|
|
|
|
if (!m_private->s_play) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("error connecting output to PulseAudio server.");
|
2014-03-13 21:16:30 +01:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2014-03-11 21:46:00 +01:00
|
|
|
goto error;
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_mode == airtaudio::mode_unknow) {
|
|
|
|
m_mode = _mode;
|
|
|
|
} else if (m_mode == _mode) {
|
2014-03-11 21:46:00 +01:00
|
|
|
goto error;
|
|
|
|
}else {
|
2015-02-09 21:44:32 +01:00
|
|
|
m_mode = airtaudio::mode_duplex;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-17 21:08:15 +01:00
|
|
|
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));
|
2015-02-17 21:08:15 +01:00
|
|
|
m_private->thread = std::move(tmpThread);
|
2015-02-10 21:01:53 +01:00
|
|
|
if (m_private->thread == nullptr) {
|
2015-02-05 23:31:22 +01:00
|
|
|
ATA_ERROR("error creating thread.");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_state = airtaudio::state_stopped;
|
2014-03-11 21:46:00 +01:00
|
|
|
return true;
|
|
|
|
error:
|
|
|
|
for (int32_t i=0; i<2; i++) {
|
2015-02-09 21:44:32 +01:00
|
|
|
m_userBuffer[i].clear();
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_deviceBuffer) {
|
|
|
|
free(m_deviceBuffer);
|
|
|
|
m_deviceBuffer = 0;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|