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_ALSA__)
|
|
|
|
|
|
|
|
#include <alsa/asoundlib.h>
|
|
|
|
#include <unistd.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 <limits.h>
|
|
|
|
|
2015-01-27 23:06:19 +01:00
|
|
|
#undef __class__
|
|
|
|
#define __class__ "api::Alsa"
|
|
|
|
|
2014-05-15 21:37:39 +02:00
|
|
|
airtaudio::Api* airtaudio::api::Alsa::Create() {
|
2014-03-11 22:37:22 +01:00
|
|
|
return new airtaudio::api::Alsa();
|
|
|
|
}
|
|
|
|
|
2015-02-10 21:01:53 +01:00
|
|
|
namespace airtaudio {
|
|
|
|
namespace api {
|
|
|
|
class AlsaPrivate {
|
|
|
|
public:
|
|
|
|
snd_pcm_t *handles[2];
|
|
|
|
bool synchronized;
|
|
|
|
bool xrun[2];
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::condition_variable runnable_cv;
|
2015-02-10 21:01:53 +01:00
|
|
|
bool runnable;
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::thread* thread;
|
2015-02-17 21:08:15 +01:00
|
|
|
bool threadRunning;
|
2015-02-26 21:44:16 +01:00
|
|
|
bool isMonotonic; //!< the timestamp of the flow came from the harware.
|
2015-02-10 21:01:53 +01:00
|
|
|
AlsaPrivate() :
|
|
|
|
synchronized(false),
|
2015-02-17 21:08:15 +01:00
|
|
|
runnable(false),
|
2015-02-24 22:20:11 +01:00
|
|
|
thread(nullptr),
|
2015-02-26 21:57:13 +01:00
|
|
|
threadRunning(false),
|
|
|
|
isMonotonic(false) {
|
2015-02-10 21:01:53 +01:00
|
|
|
handles[0] = nullptr;
|
|
|
|
handles[1] = nullptr;
|
|
|
|
xrun[0] = false;
|
|
|
|
xrun[1] = false;
|
2015-02-17 21:08:15 +01:00
|
|
|
// TODO : Wait thread ...
|
2015-02-10 21:01:53 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
2014-03-11 21:46:00 +01:00
|
|
|
};
|
|
|
|
|
2015-02-10 21:01:53 +01:00
|
|
|
airtaudio::api::Alsa::Alsa() :
|
|
|
|
m_private(new airtaudio::api::AlsaPrivate()) {
|
2014-03-11 21:46:00 +01:00
|
|
|
// Nothing to do here.
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:37:39 +02:00
|
|
|
airtaudio::api::Alsa::~Alsa() {
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state != airtaudio::state_closed) {
|
2014-03-11 21:46:00 +01:00
|
|
|
closeStream();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:37:39 +02:00
|
|
|
uint32_t airtaudio::api::Alsa::getDeviceCount() {
|
2014-03-11 21:46:00 +01:00
|
|
|
unsigned nDevices = 0;
|
|
|
|
int32_t result, subdevice, card;
|
|
|
|
char name[64];
|
|
|
|
snd_ctl_t *handle;
|
|
|
|
// Count cards and devices
|
|
|
|
card = -1;
|
|
|
|
snd_card_next(&card);
|
|
|
|
while (card >= 0) {
|
|
|
|
sprintf(name, "hw:%d", card);
|
|
|
|
result = snd_ctl_open(&handle, name, 0);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("control open, card = " << card << ", " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return error airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
goto nextcard;
|
|
|
|
}
|
|
|
|
subdevice = -1;
|
|
|
|
while(1) {
|
|
|
|
result = snd_ctl_pcm_next_device(handle, &subdevice);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("control next device, card = " << card << ", " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return error airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (subdevice < 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nDevices++;
|
|
|
|
}
|
|
|
|
nextcard:
|
|
|
|
snd_ctl_close(handle);
|
|
|
|
snd_card_next(&card);
|
|
|
|
}
|
|
|
|
return nDevices;
|
|
|
|
}
|
|
|
|
|
2015-02-26 21:57:13 +01:00
|
|
|
bool airtaudio::api::Alsa::getDeviceInfo(const std::string& _deviceName, airtaudio::DeviceInfo& _info) {
|
|
|
|
int32_t result;
|
|
|
|
int32_t openMode = SND_PCM_ASYNC;
|
|
|
|
snd_pcm_stream_t stream;
|
|
|
|
snd_pcm_info_t *pcminfo;
|
|
|
|
snd_pcm_info_alloca(&pcminfo);
|
|
|
|
snd_pcm_t *phandle;
|
|
|
|
snd_pcm_hw_params_t *params;
|
|
|
|
snd_pcm_hw_params_alloca(¶ms);
|
|
|
|
// First try for playback unless default _device (which has subdev -1)
|
|
|
|
stream = SND_PCM_STREAM_PLAYBACK;
|
|
|
|
snd_pcm_info_set_stream(pcminfo, stream);
|
|
|
|
/*
|
|
|
|
if (subdevice != -1) {
|
|
|
|
snd_pcm_info_set_device(pcminfo, subdevice);
|
|
|
|
snd_pcm_info_set_subdevice(pcminfo, 0);
|
|
|
|
result = snd_ctl_pcm_info(chandle, pcminfo);
|
|
|
|
if (result < 0) {
|
|
|
|
// Device probably doesn't support playback.
|
|
|
|
goto captureProbe;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
result = snd_pcm_open(&phandle, _deviceName.c_str(), stream, openMode | SND_PCM_NONBLOCK);
|
|
|
|
if (result < 0) {
|
|
|
|
ATA_ERROR("snd_pcm_open error for device (" << _deviceName << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
goto captureProbe;
|
|
|
|
}
|
|
|
|
// The device is open ... fill the parameter structure.
|
|
|
|
result = snd_pcm_hw_params_any(phandle, params);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
ATA_ERROR("snd_pcm_hw_params error for device (" << _deviceName << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
goto captureProbe;
|
|
|
|
}
|
|
|
|
// Get output channel information.
|
|
|
|
uint32_t value;
|
|
|
|
result = snd_pcm_hw_params_get_channels_max(params, &value);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
ATA_ERROR("error getting device (" << _deviceName << ") output channels, " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
goto captureProbe;
|
|
|
|
}
|
|
|
|
_info.outputChannels = value;
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
|
|
|
|
captureProbe:
|
|
|
|
stream = SND_PCM_STREAM_CAPTURE;
|
|
|
|
snd_pcm_info_set_stream(pcminfo, stream);
|
|
|
|
// Now try for capture unless default device (with subdev = -1)
|
|
|
|
/*
|
|
|
|
if (subdevice != -1) {
|
|
|
|
result = snd_ctl_pcm_info(chandle, pcminfo);
|
|
|
|
snd_ctl_close(chandle);
|
|
|
|
if (result < 0) {
|
|
|
|
// Device probably doesn't support capture.
|
|
|
|
if (_info.outputChannels == 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
goto probeParameters;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
result = snd_pcm_open(&phandle, _deviceName.c_str(), stream, openMode | SND_PCM_NONBLOCK);
|
|
|
|
if (result < 0) {
|
|
|
|
ATA_ERROR("snd_pcm_open error for device (" << _deviceName << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
if (_info.outputChannels == 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
goto probeParameters;
|
|
|
|
}
|
|
|
|
// The device is open ... fill the parameter structure.
|
|
|
|
result = snd_pcm_hw_params_any(phandle, params);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
ATA_ERROR("snd_pcm_hw_params error for device (" << _deviceName << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
if (_info.outputChannels == 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
goto probeParameters;
|
|
|
|
}
|
|
|
|
result = snd_pcm_hw_params_get_channels_max(params, &value);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
ATA_ERROR("error getting device (" << _deviceName << ") input channels, " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
if (_info.outputChannels == 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
goto probeParameters;
|
|
|
|
}
|
|
|
|
_info.inputChannels = value;
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
// If device opens for both playback and capture, we determine the channels.
|
|
|
|
if ( _info.outputChannels > 0
|
|
|
|
&& _info.inputChannels > 0) {
|
|
|
|
_info.duplexChannels = (_info.outputChannels > _info.inputChannels) ? _info.inputChannels : _info.outputChannels;
|
|
|
|
}
|
|
|
|
// ALSA doesn't provide default devices so we'll use the first available one.
|
|
|
|
/*
|
|
|
|
if ( _name == 0
|
|
|
|
&& _info.outputChannels > 0) {
|
|
|
|
info.isDefaultOutput = true;
|
|
|
|
}
|
|
|
|
if ( _device == 0
|
|
|
|
&& _info.inputChannels > 0) {
|
|
|
|
info.isDefaultInput = true;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
probeParameters:
|
|
|
|
// At this point, we just need to figure out the supported data
|
|
|
|
// formats and sample rates. We'll proceed by opening the device in
|
|
|
|
// the direction with the maximum number of channels, or playback if
|
|
|
|
// they are equal. This might limit our sample rate options, but so
|
|
|
|
// be it.
|
|
|
|
if (_info.outputChannels >= _info.inputChannels) {
|
|
|
|
stream = SND_PCM_STREAM_PLAYBACK;
|
|
|
|
} else {
|
|
|
|
stream = SND_PCM_STREAM_CAPTURE;
|
|
|
|
}
|
|
|
|
snd_pcm_info_set_stream(pcminfo, stream);
|
|
|
|
result = snd_pcm_open(&phandle, _deviceName.c_str(), stream, openMode | SND_PCM_NONBLOCK);
|
|
|
|
if (result < 0) {
|
|
|
|
ATA_ERROR("snd_pcm_open error for device (" << _deviceName << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// The device is open ... fill the parameter structure.
|
|
|
|
result = snd_pcm_hw_params_any(phandle, params);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
ATA_ERROR("snd_pcm_hw_params error for device (" << _deviceName << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Test our discrete set of sample rate values.
|
|
|
|
_info.sampleRates.clear();
|
|
|
|
for (std::vector<uint32_t>::const_iterator it(airtaudio::genericSampleRate().begin());
|
|
|
|
it != airtaudio::genericSampleRate().end();
|
|
|
|
++it ) {
|
|
|
|
if (snd_pcm_hw_params_test_rate(phandle, params, *it, 0) == 0) {
|
|
|
|
_info.sampleRates.push_back(*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_info.sampleRates.size() == 0) {
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
ATA_ERROR("no supported sample rates found for device (" << _deviceName << ").");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Probe the supported data formats ... we don't care about endian-ness just yet
|
|
|
|
snd_pcm_format_t format;
|
|
|
|
_info.nativeFormats.clear();
|
|
|
|
format = SND_PCM_FORMAT_S8;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
|
|
|
_info.nativeFormats.push_back(audio::format_int8);
|
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_S16;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
|
|
|
_info.nativeFormats.push_back(audio::format_int16);
|
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_S24;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
|
|
|
_info.nativeFormats.push_back(audio::format_int24);
|
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_S32;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
|
|
|
_info.nativeFormats.push_back(audio::format_int32);
|
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_FLOAT;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
|
|
|
_info.nativeFormats.push_back(audio::format_float);
|
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_FLOAT64;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
|
|
|
_info.nativeFormats.push_back(audio::format_double);
|
|
|
|
}
|
|
|
|
// Check that we have at least one supported format
|
|
|
|
if (_info.nativeFormats.size() == 0) {
|
|
|
|
ATA_ERROR("pcm device (" << _deviceName << ") data format not supported by RtAudio.");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Get the device name
|
|
|
|
/*
|
|
|
|
char *cardname;
|
|
|
|
result = snd_card_get_name(card, &cardname);
|
|
|
|
if (result >= 0) {
|
|
|
|
sprintf(name, "hw:%s,%d", cardname, subdevice);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
_info.name = _deviceName;
|
|
|
|
// That's all ... close the device and return
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
_info.probed = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-03-11 21:46:00 +01:00
|
|
|
airtaudio::DeviceInfo airtaudio::api::Alsa::getDeviceInfo(uint32_t _device) {
|
|
|
|
airtaudio::DeviceInfo info;
|
2015-02-26 21:57:13 +01:00
|
|
|
ATA_WARNING("plop");
|
|
|
|
getDeviceInfo("hw:0,0,0", info);
|
|
|
|
info.display();
|
|
|
|
getDeviceInfo("hw:0,0,1", info);
|
|
|
|
info.display();
|
2014-03-11 21:46:00 +01:00
|
|
|
info.probed = false;
|
|
|
|
unsigned nDevices = 0;
|
|
|
|
int32_t result, subdevice, card;
|
|
|
|
char name[64];
|
|
|
|
snd_ctl_t *chandle;
|
|
|
|
// Count cards and devices
|
|
|
|
card = -1;
|
|
|
|
snd_card_next(&card);
|
|
|
|
while (card >= 0) {
|
|
|
|
sprintf(name, "hw:%d", card);
|
|
|
|
result = snd_ctl_open(&chandle, name, SND_CTL_NONBLOCK);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_WARNING("control open, card = " << card << ", " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto nextcard;
|
|
|
|
}
|
|
|
|
subdevice = -1;
|
|
|
|
while(1) {
|
|
|
|
result = snd_ctl_pcm_next_device(chandle, &subdevice);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_WARNING("control next device, card = " << card << ", " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (subdevice < 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (nDevices == _device) {
|
|
|
|
sprintf(name, "hw:%d,%d", card, subdevice);
|
|
|
|
goto foundDevice;
|
|
|
|
}
|
|
|
|
nDevices++;
|
|
|
|
}
|
|
|
|
nextcard:
|
|
|
|
snd_ctl_close(chandle);
|
|
|
|
snd_card_next(&card);
|
|
|
|
}
|
|
|
|
if (nDevices == 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("no devices found!");
|
|
|
|
// TODO : airtaudio::error_invalidUse;
|
2014-03-11 21:46:00 +01:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
if (_device >= nDevices) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("device ID is invalid!");
|
|
|
|
// TODO : airtaudio::error_invalidUse;
|
2014-03-11 21:46:00 +01:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
|
|
|
foundDevice:
|
|
|
|
// If a stream is already open, we cannot probe the stream devices.
|
|
|
|
// Thus, use the saved results.
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( m_state != airtaudio::state_closed
|
|
|
|
&& ( m_device[0] == _device
|
|
|
|
|| m_device[1] == _device)) {
|
2014-03-11 21:46:00 +01:00
|
|
|
snd_ctl_close(chandle);
|
|
|
|
if (_device >= m_devices.size()) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("device ID was not present before stream was opened.");
|
|
|
|
// TODO : return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
return m_devices[ _device ];
|
|
|
|
}
|
2015-02-26 21:57:13 +01:00
|
|
|
bool ret = airtaudio::api::Alsa::getDeviceInfo(name, info);
|
|
|
|
|
2014-03-11 21:46:00 +01:00
|
|
|
int32_t openMode = SND_PCM_ASYNC;
|
|
|
|
snd_pcm_stream_t stream;
|
|
|
|
snd_pcm_info_t *pcminfo;
|
|
|
|
snd_pcm_info_alloca(&pcminfo);
|
|
|
|
snd_pcm_t *phandle;
|
|
|
|
snd_pcm_hw_params_t *params;
|
|
|
|
snd_pcm_hw_params_alloca(¶ms);
|
|
|
|
// First try for playback unless default _device (which has subdev -1)
|
|
|
|
stream = SND_PCM_STREAM_PLAYBACK;
|
|
|
|
snd_pcm_info_set_stream(pcminfo, stream);
|
|
|
|
if (subdevice != -1) {
|
|
|
|
snd_pcm_info_set_device(pcminfo, subdevice);
|
|
|
|
snd_pcm_info_set_subdevice(pcminfo, 0);
|
|
|
|
result = snd_ctl_pcm_info(chandle, pcminfo);
|
|
|
|
if (result < 0) {
|
|
|
|
// Device probably doesn't support playback.
|
|
|
|
goto captureProbe;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result = snd_pcm_open(&phandle, name, stream, openMode | SND_PCM_NONBLOCK);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("snd_pcm_open error for device (" << name << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
goto captureProbe;
|
|
|
|
}
|
|
|
|
// The device is open ... fill the parameter structure.
|
|
|
|
result = snd_pcm_hw_params_any(phandle, params);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("snd_pcm_hw_params error for device (" << name << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
goto captureProbe;
|
|
|
|
}
|
|
|
|
// Get output channel information.
|
|
|
|
uint32_t value;
|
|
|
|
result = snd_pcm_hw_params_get_channels_max(params, &value);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error getting device (" << name << ") output channels, " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
goto captureProbe;
|
|
|
|
}
|
|
|
|
info.outputChannels = value;
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
|
|
|
|
captureProbe:
|
|
|
|
stream = SND_PCM_STREAM_CAPTURE;
|
|
|
|
snd_pcm_info_set_stream(pcminfo, stream);
|
|
|
|
// Now try for capture unless default device (with subdev = -1)
|
|
|
|
if (subdevice != -1) {
|
|
|
|
result = snd_ctl_pcm_info(chandle, pcminfo);
|
|
|
|
snd_ctl_close(chandle);
|
|
|
|
if (result < 0) {
|
|
|
|
// Device probably doesn't support capture.
|
|
|
|
if (info.outputChannels == 0) {
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
goto probeParameters;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result = snd_pcm_open(&phandle, name, stream, openMode | SND_PCM_NONBLOCK);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("snd_pcm_open error for device (" << name << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
if (info.outputChannels == 0) {
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
goto probeParameters;
|
|
|
|
}
|
|
|
|
// The device is open ... fill the parameter structure.
|
|
|
|
result = snd_pcm_hw_params_any(phandle, params);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("snd_pcm_hw_params error for device (" << name << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
if (info.outputChannels == 0) {
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
goto probeParameters;
|
|
|
|
}
|
|
|
|
result = snd_pcm_hw_params_get_channels_max(params, &value);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error getting device (" << name << ") input channels, " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
if (info.outputChannels == 0) {
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
goto probeParameters;
|
|
|
|
}
|
|
|
|
info.inputChannels = value;
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
// If device opens for both playback and capture, we determine the channels.
|
|
|
|
if (info.outputChannels > 0 && info.inputChannels > 0) {
|
|
|
|
info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;
|
|
|
|
}
|
|
|
|
// ALSA doesn't provide default devices so we'll use the first available one.
|
|
|
|
if (_device == 0 && info.outputChannels > 0) {
|
|
|
|
info.isDefaultOutput = true;
|
|
|
|
}
|
|
|
|
if (_device == 0 && info.inputChannels > 0) {
|
|
|
|
info.isDefaultInput = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
probeParameters:
|
|
|
|
// At this point, we just need to figure out the supported data
|
|
|
|
// formats and sample rates. We'll proceed by opening the device in
|
|
|
|
// the direction with the maximum number of channels, or playback if
|
|
|
|
// they are equal. This might limit our sample rate options, but so
|
|
|
|
// be it.
|
|
|
|
if (info.outputChannels >= info.inputChannels) {
|
|
|
|
stream = SND_PCM_STREAM_PLAYBACK;
|
|
|
|
} else {
|
|
|
|
stream = SND_PCM_STREAM_CAPTURE;
|
|
|
|
}
|
|
|
|
snd_pcm_info_set_stream(pcminfo, stream);
|
|
|
|
result = snd_pcm_open(&phandle, name, stream, openMode | SND_PCM_NONBLOCK);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("snd_pcm_open error for device (" << name << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
// The device is open ... fill the parameter structure.
|
|
|
|
result = snd_pcm_hw_params_any(phandle, params);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("snd_pcm_hw_params error for device (" << name << "), " << snd_strerror(result) << ".");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
// Test our discrete set of sample rate values.
|
|
|
|
info.sampleRates.clear();
|
2015-02-25 22:05:00 +01:00
|
|
|
#if __CPP_VERSION__ >= 2011
|
2015-02-24 22:20:11 +01:00
|
|
|
for (auto &it : airtaudio::genericSampleRate()) {
|
|
|
|
if (snd_pcm_hw_params_test_rate(phandle, params, it, 0) == 0) {
|
|
|
|
info.sampleRates.push_back(it);
|
|
|
|
}
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-24 22:20:11 +01:00
|
|
|
#else
|
|
|
|
for (std::vector<uint32_t>::const_iterator it(airtaudio::genericSampleRate().begin());
|
|
|
|
it != airtaudio::genericSampleRate().end();
|
|
|
|
++it ) {
|
|
|
|
if (snd_pcm_hw_params_test_rate(phandle, params, *it, 0) == 0) {
|
|
|
|
info.sampleRates.push_back(*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2014-03-11 21:46:00 +01:00
|
|
|
if (info.sampleRates.size() == 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("no supported sample rates found for device (" << name << ").");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
// Probe the supported data formats ... we don't care about endian-ness just yet
|
|
|
|
snd_pcm_format_t format;
|
2015-02-05 23:31:22 +01:00
|
|
|
info.nativeFormats.clear();
|
2014-03-11 21:46:00 +01:00
|
|
|
format = SND_PCM_FORMAT_S8;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
info.nativeFormats.push_back(audio::format_int8);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_S16;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
info.nativeFormats.push_back(audio::format_int16);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_S24;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
info.nativeFormats.push_back(audio::format_int24);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_S32;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
info.nativeFormats.push_back(audio::format_int32);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_FLOAT;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
info.nativeFormats.push_back(audio::format_float);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
format = SND_PCM_FORMAT_FLOAT64;
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, params, format) == 0) {
|
2015-02-05 23:31:22 +01:00
|
|
|
info.nativeFormats.push_back(audio::format_double);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Check that we have at least one supported format
|
2015-02-05 23:31:22 +01:00
|
|
|
if (info.nativeFormats.size() == 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("pcm device (" << name << ") data format not supported by RtAudio.");
|
|
|
|
// TODO : Return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
// Get the device name
|
|
|
|
char *cardname;
|
|
|
|
result = snd_card_get_name(card, &cardname);
|
|
|
|
if (result >= 0) {
|
|
|
|
sprintf(name, "hw:%s,%d", cardname, subdevice);
|
|
|
|
}
|
|
|
|
info.name = name;
|
|
|
|
// That's all ... close the device and return
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
info.probed = true;
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:37:39 +02:00
|
|
|
void airtaudio::api::Alsa::saveDeviceInfo() {
|
2014-03-11 21:46:00 +01:00
|
|
|
m_devices.clear();
|
|
|
|
uint32_t nDevices = getDeviceCount();
|
|
|
|
m_devices.resize(nDevices);
|
|
|
|
for (uint32_t iii=0; iii<nDevices; ++iii) {
|
|
|
|
m_devices[iii] = getDeviceInfo(iii);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool airtaudio::api::Alsa::probeDeviceOpen(uint32_t _device,
|
2015-02-06 23:54:08 +01:00
|
|
|
airtaudio::mode _mode,
|
2014-03-11 22:37:22 +01:00
|
|
|
uint32_t _channels,
|
|
|
|
uint32_t _firstChannel,
|
|
|
|
uint32_t _sampleRate,
|
2015-02-05 23:31:22 +01:00
|
|
|
audio::format _format,
|
2014-03-11 22:37:22 +01:00
|
|
|
uint32_t *_bufferSize,
|
|
|
|
airtaudio::StreamOptions *_options) {
|
2014-03-11 21:46:00 +01:00
|
|
|
// I'm not using the "plug" interface ... too much inconsistent behavior.
|
|
|
|
unsigned nDevices = 0;
|
|
|
|
int32_t result, subdevice, card;
|
|
|
|
char name[64];
|
|
|
|
snd_ctl_t *chandle;
|
2015-02-06 23:54:08 +01:00
|
|
|
// Count cards and devices
|
|
|
|
card = -1;
|
|
|
|
// NOTE : Find the device name : [BEGIN]
|
|
|
|
snd_card_next(&card);
|
|
|
|
while (card >= 0) {
|
|
|
|
sprintf(name, "hw:%d", card);
|
|
|
|
result = snd_ctl_open(&chandle, name, SND_CTL_NONBLOCK);
|
|
|
|
if (result < 0) {
|
|
|
|
ATA_ERROR("control open, card = " << card << ", " << snd_strerror(result) << ".");
|
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-06 23:54:08 +01:00
|
|
|
subdevice = -1;
|
|
|
|
while(1) {
|
|
|
|
result = snd_ctl_pcm_next_device(chandle, &subdevice);
|
|
|
|
if (result < 0) break;
|
|
|
|
if (subdevice < 0) break;
|
2014-03-11 21:46:00 +01:00
|
|
|
if (nDevices == _device) {
|
2015-02-06 23:54:08 +01:00
|
|
|
sprintf(name, "hw:%d,%d", card, subdevice);
|
|
|
|
snd_ctl_close(chandle);
|
2014-03-11 21:46:00 +01:00
|
|
|
goto foundDevice;
|
|
|
|
}
|
|
|
|
nDevices++;
|
|
|
|
}
|
2015-02-06 23:54:08 +01:00
|
|
|
snd_ctl_close(chandle);
|
|
|
|
snd_card_next(&card);
|
|
|
|
}
|
|
|
|
result = snd_ctl_open(&chandle, "default", SND_CTL_NONBLOCK);
|
|
|
|
if (result == 0) {
|
|
|
|
if (nDevices == _device) {
|
|
|
|
strcpy(name, "default");
|
|
|
|
goto foundDevice;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-06 23:54:08 +01:00
|
|
|
nDevices++;
|
|
|
|
}
|
|
|
|
if (nDevices == 0) {
|
|
|
|
// This should not happen because a check is made before this function is called.
|
|
|
|
ATA_ERROR("no devices found!");
|
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-06 23:54:08 +01:00
|
|
|
if (_device >= nDevices) {
|
|
|
|
// This should not happen because a check is made before this function is called.
|
|
|
|
ATA_ERROR("device ID is invalid!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// NOTE : Find the device name : [ END ]
|
2014-03-11 21:46:00 +01:00
|
|
|
|
|
|
|
foundDevice:
|
|
|
|
// The getDeviceInfo() function will not work for a device that is
|
|
|
|
// already open. Thus, we'll probe the system before opening a
|
|
|
|
// stream and save the results for use by getDeviceInfo().
|
2015-02-06 23:54:08 +01:00
|
|
|
if ( _mode == airtaudio::mode_output
|
|
|
|
|| ( _mode == airtaudio::mode_input
|
2015-02-09 21:44:32 +01:00
|
|
|
&& m_mode != airtaudio::mode_output)) {
|
2014-03-11 21:46:00 +01:00
|
|
|
// only do once
|
|
|
|
this->saveDeviceInfo();
|
|
|
|
}
|
|
|
|
snd_pcm_stream_t stream;
|
2015-02-06 23:54:08 +01:00
|
|
|
if (_mode == airtaudio::mode_output) {
|
2014-03-11 21:46:00 +01:00
|
|
|
stream = SND_PCM_STREAM_PLAYBACK;
|
|
|
|
} else {
|
|
|
|
stream = SND_PCM_STREAM_CAPTURE;
|
|
|
|
}
|
|
|
|
snd_pcm_t *phandle;
|
|
|
|
int32_t openMode = SND_PCM_ASYNC;
|
|
|
|
result = snd_pcm_open(&phandle, name, stream, openMode);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
if (_mode == airtaudio::mode_output) {
|
|
|
|
ATA_ERROR("pcm device (" << name << ") won't open for output.");
|
2014-03-11 21:46:00 +01:00
|
|
|
} else {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("pcm device (" << name << ") won't open for input.");
|
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
|
|
|
}
|
|
|
|
// Fill the parameter structure.
|
|
|
|
snd_pcm_hw_params_t *hw_params;
|
|
|
|
snd_pcm_hw_params_alloca(&hw_params);
|
|
|
|
result = snd_pcm_hw_params_any(phandle, hw_params);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error getting pcm device (" << name << ") parameters, " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-06 23:54:08 +01:00
|
|
|
// Open stream all time in interleave mode (by default): (open in non interleave if we have no choice
|
|
|
|
result = snd_pcm_hw_params_set_access(phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
|
|
if (result < 0) {
|
2014-03-11 21:46:00 +01:00
|
|
|
result = snd_pcm_hw_params_set_access(phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED);
|
2015-02-09 21:44:32 +01:00
|
|
|
m_deviceInterleaved[modeToIdTable(_mode)] = false;
|
2014-03-11 21:46:00 +01:00
|
|
|
} else {
|
2015-02-09 21:44:32 +01:00
|
|
|
m_deviceInterleaved[modeToIdTable(_mode)] = true;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error setting pcm device (" << name << ") access, " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Determine how to set the device format.
|
2015-02-09 21:44:32 +01:00
|
|
|
m_userFormat = _format;
|
2014-03-11 21:46:00 +01:00
|
|
|
snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN;
|
2015-02-05 23:31:22 +01:00
|
|
|
if (_format == audio::format_int8) {
|
2014-03-11 21:46:00 +01:00
|
|
|
deviceFormat = SND_PCM_FORMAT_S8;
|
2015-02-05 23:31:22 +01:00
|
|
|
} else if (_format == audio::format_int16) {
|
2014-03-11 21:46:00 +01:00
|
|
|
deviceFormat = SND_PCM_FORMAT_S16;
|
2015-02-05 23:31:22 +01:00
|
|
|
} else if (_format == audio::format_int24) {
|
2014-03-11 21:46:00 +01:00
|
|
|
deviceFormat = SND_PCM_FORMAT_S24;
|
2015-02-05 23:31:22 +01:00
|
|
|
} else if (_format == audio::format_int32) {
|
2014-03-11 21:46:00 +01:00
|
|
|
deviceFormat = SND_PCM_FORMAT_S32;
|
2015-02-05 23:31:22 +01:00
|
|
|
} else if (_format == audio::format_float) {
|
2014-03-11 21:46:00 +01:00
|
|
|
deviceFormat = SND_PCM_FORMAT_FLOAT;
|
2015-02-05 23:31:22 +01:00
|
|
|
} else if (_format == audio::format_double) {
|
2014-03-11 21:46:00 +01:00
|
|
|
deviceFormat = SND_PCM_FORMAT_FLOAT64;
|
|
|
|
}
|
|
|
|
if (snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) {
|
2015-02-09 21:44:32 +01:00
|
|
|
m_deviceFormat[modeToIdTable(_mode)] = _format;
|
2015-02-05 23:31:22 +01:00
|
|
|
} else {
|
|
|
|
// If we get here, no supported format was found.
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
ATA_ERROR("pcm device " << _device << " data format not supported: " << _format);
|
|
|
|
// TODO : display list of all supported format ..
|
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
result = snd_pcm_hw_params_set_format(phandle, hw_params, deviceFormat);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error setting pcm device (" << name << ") data format, " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Determine whether byte-swaping is necessary.
|
2015-02-09 21:44:32 +01:00
|
|
|
m_doByteSwap[modeToIdTable(_mode)] = false;
|
2014-03-11 21:46:00 +01:00
|
|
|
if (deviceFormat != SND_PCM_FORMAT_S8) {
|
|
|
|
result = snd_pcm_format_cpu_endian(deviceFormat);
|
|
|
|
if (result == 0) {
|
2015-02-09 21:44:32 +01:00
|
|
|
m_doByteSwap[modeToIdTable(_mode)] = true;
|
2014-03-11 21:46:00 +01:00
|
|
|
} else if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error getting pcm device (" << name << ") endian-ness, " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Set the sample rate.
|
|
|
|
result = snd_pcm_hw_params_set_rate_near(phandle, hw_params, (uint32_t*) &_sampleRate, 0);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error setting sample rate on device (" << name << "), " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Determine the number of channels for this device. We support a possible
|
|
|
|
// minimum device channel number > than the value requested by the user.
|
2015-02-09 21:44:32 +01:00
|
|
|
m_nUserChannels[modeToIdTable(_mode)] = _channels;
|
2014-03-11 21:46:00 +01:00
|
|
|
uint32_t value;
|
|
|
|
result = snd_pcm_hw_params_get_channels_max(hw_params, &value);
|
|
|
|
uint32_t deviceChannels = value;
|
|
|
|
if ( result < 0
|
|
|
|
|| deviceChannels < _channels + _firstChannel) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("requested channel parameters not supported by device (" << name << "), " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
result = snd_pcm_hw_params_get_channels_min(hw_params, &value);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error getting minimum channels for device (" << name << "), " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
deviceChannels = value;
|
|
|
|
if (deviceChannels < _channels + _firstChannel) {
|
|
|
|
deviceChannels = _channels + _firstChannel;
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_nDeviceChannels[modeToIdTable(_mode)] = deviceChannels;
|
2014-03-11 21:46:00 +01:00
|
|
|
// Set the device channels.
|
|
|
|
result = snd_pcm_hw_params_set_channels(phandle, hw_params, deviceChannels);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error setting channels for device (" << name << "), " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Set the buffer (or period) size.
|
|
|
|
int32_t dir = 0;
|
|
|
|
snd_pcm_uframes_t periodSize = *_bufferSize;
|
|
|
|
result = snd_pcm_hw_params_set_period_size_near(phandle, hw_params, &periodSize, &dir);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error setting period size for device (" << name << "), " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
*_bufferSize = periodSize;
|
|
|
|
// Set the buffer number, which in ALSA is referred to as the "period".
|
|
|
|
uint32_t periods = 0;
|
2015-02-06 23:54:08 +01:00
|
|
|
if ( _options != nullptr
|
|
|
|
&& _options->flags.m_minimizeLatency == true) {
|
|
|
|
periods = 2;
|
|
|
|
}
|
|
|
|
/* TODO : Chouse the number of low level buffer ...
|
|
|
|
if ( _options != nullptr
|
|
|
|
&& _options->numberOfBuffers > 0) {
|
|
|
|
periods = _options->numberOfBuffers;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
if (periods < 2) {
|
|
|
|
periods = 4; // a fairly safe default value
|
|
|
|
}
|
2014-03-11 21:46:00 +01:00
|
|
|
result = snd_pcm_hw_params_set_periods_near(phandle, hw_params, &periods, &dir);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error setting periods for device (" << name << "), " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// If attempting to setup a duplex stream, the bufferSize parameter
|
|
|
|
// MUST be the same in both directions!
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( m_mode == airtaudio::mode_output
|
2015-02-06 23:54:08 +01:00
|
|
|
&& _mode == airtaudio::mode_input
|
2015-02-09 21:44:32 +01:00
|
|
|
&& *_bufferSize != m_bufferSize) {
|
2014-03-11 21:46:00 +01:00
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("system error setting buffer size for duplex stream on device (" << name << ").");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_bufferSize = *_bufferSize;
|
2015-02-26 21:44:16 +01:00
|
|
|
// check if the hardware provide hardware clock :
|
|
|
|
if (snd_pcm_hw_params_is_monotonic(hw_params) == 0) {
|
|
|
|
m_private->isMonotonic = false;
|
|
|
|
ATA_WARNING("ALSA Audio timestamp is NOT monotonic (Generate with the start timestamp)");
|
|
|
|
} else {
|
|
|
|
m_private->isMonotonic = true;
|
|
|
|
ATA_DEBUG("ALSA Audio timestamp is monotonic (came from harware)");
|
|
|
|
}
|
|
|
|
|
2014-03-11 21:46:00 +01:00
|
|
|
// Install the hardware configuration
|
|
|
|
result = snd_pcm_hw_params(phandle, hw_params);
|
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error installing hardware configuration on device (" << name << "), " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Set the software configuration to fill buffers with zeros and prevent device stopping on xruns.
|
2015-02-16 23:01:26 +01:00
|
|
|
snd_pcm_sw_params_t *swParams = nullptr;
|
|
|
|
snd_pcm_sw_params_alloca(&swParams);
|
|
|
|
snd_pcm_sw_params_current(phandle, swParams);
|
|
|
|
snd_pcm_sw_params_set_start_threshold(phandle, swParams, *_bufferSize);
|
|
|
|
snd_pcm_sw_params_set_stop_threshold(phandle, swParams, ULONG_MAX);
|
|
|
|
snd_pcm_sw_params_set_silence_threshold(phandle, swParams, 0);
|
2014-03-11 21:46:00 +01:00
|
|
|
// The following two settings were suggested by Theo Veenker
|
2015-02-16 23:01:26 +01:00
|
|
|
//snd_pcm_sw_params_set_avail_min(phandle, swParams, *_bufferSize);
|
|
|
|
//snd_pcm_sw_params_set_xfer_align(phandle, swParams, 1);
|
2014-03-11 21:46:00 +01:00
|
|
|
// here are two options for a fix
|
2015-02-16 23:01:26 +01:00
|
|
|
//snd_pcm_sw_params_set_silence_size(phandle, swParams, ULONG_MAX);
|
|
|
|
snd_pcm_sw_params_set_tstamp_mode(phandle, swParams, SND_PCM_TSTAMP_ENABLE);
|
2014-03-11 21:46:00 +01:00
|
|
|
snd_pcm_uframes_t val;
|
2015-02-16 23:01:26 +01:00
|
|
|
snd_pcm_sw_params_get_boundary(swParams, &val);
|
|
|
|
snd_pcm_sw_params_set_silence_size(phandle, swParams, val);
|
|
|
|
result = snd_pcm_sw_params(phandle, swParams);
|
2014-03-11 21:46:00 +01:00
|
|
|
if (result < 0) {
|
|
|
|
snd_pcm_close(phandle);
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error installing software configuration on device (" << name << "), " << snd_strerror(result) << ".");
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Set flags for buffer conversion
|
2015-02-09 21:44:32 +01:00
|
|
|
m_doConvertBuffer[modeToIdTable(_mode)] = false;
|
|
|
|
if (m_userFormat != m_deviceFormat[modeToIdTable(_mode)]) {
|
|
|
|
m_doConvertBuffer[modeToIdTable(_mode)] = true;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_nUserChannels[modeToIdTable(_mode)] < m_nDeviceChannels[modeToIdTable(_mode)]) {
|
|
|
|
m_doConvertBuffer[modeToIdTable(_mode)] = true;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( m_deviceInterleaved[modeToIdTable(_mode)] == false
|
|
|
|
&& m_nUserChannels[modeToIdTable(_mode)] > 1) {
|
|
|
|
m_doConvertBuffer[modeToIdTable(_mode)] = true;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->handles[modeToIdTable(_mode)] = phandle;
|
2014-03-11 21:46:00 +01:00
|
|
|
phandle = 0;
|
|
|
|
// Allocate necessary internal buffers.
|
|
|
|
uint64_t bufferBytes;
|
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-06 23:54:08 +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
|
|
|
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) {
|
|
|
|
bufferBytes *= *_bufferSize;
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_deviceBuffer) {
|
|
|
|
free(m_deviceBuffer);
|
|
|
|
m_deviceBuffer = nullptr;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_deviceBuffer = (char *) calloc(bufferBytes, 1);
|
|
|
|
if (m_deviceBuffer == nullptr) {
|
2015-02-06 23:54:08 +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_sampleRate = _sampleRate;
|
|
|
|
m_nBuffers = periods;
|
|
|
|
m_device[modeToIdTable(_mode)] = _device;
|
|
|
|
m_state = airtaudio::state_stopped;
|
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-11 21:46:00 +01:00
|
|
|
setConvertInfo(_mode, _firstChannel);
|
|
|
|
}
|
|
|
|
// Setup thread if necessary.
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( m_mode == airtaudio::mode_output
|
2015-02-06 23:54:08 +01:00
|
|
|
&& _mode == airtaudio::mode_input) {
|
2014-03-11 21:46:00 +01:00
|
|
|
// We had already set up an output stream.
|
2015-02-09 21:44:32 +01:00
|
|
|
m_mode = airtaudio::mode_duplex;
|
2014-03-11 21:46:00 +01:00
|
|
|
// Link the streams if possible.
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->synchronized = false;
|
|
|
|
if (snd_pcm_link(m_private->handles[0], m_private->handles[1]) == 0) {
|
|
|
|
m_private->synchronized = true;
|
2014-03-11 21:46:00 +01:00
|
|
|
} else {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("unable to synchronize input and output devices.");
|
|
|
|
// TODO : airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
} else {
|
2015-02-09 21:44:32 +01:00
|
|
|
m_mode = _mode;
|
2014-03-11 21:46:00 +01:00
|
|
|
// Setup callback thread.
|
2015-02-17 21:08:15 +01:00
|
|
|
m_private->threadRunning = true;
|
2015-02-24 22:20:11 +01:00
|
|
|
m_private->thread = new std11::thread(&airtaudio::api::Alsa::alsaCallbackEvent, this);
|
2015-02-17 21:08:15 +01:00
|
|
|
if (m_private->thread == nullptr) {
|
|
|
|
m_private->threadRunning = false;
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("creating callback thread!");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
2014-03-13 21:16:30 +01:00
|
|
|
return true;
|
2014-03-11 21:46:00 +01:00
|
|
|
error:
|
2015-02-10 21:01:53 +01:00
|
|
|
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;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
if (phandle) {
|
|
|
|
snd_pcm_close(phandle);
|
|
|
|
}
|
|
|
|
for (int32_t iii=0; iii<2; ++iii) {
|
2015-02-09 21:44:32 +01:00
|
|
|
m_userBuffer[iii].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
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_state = airtaudio::state_closed;
|
2014-03-13 21:16:30 +01:00
|
|
|
return false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
2015-02-06 23:54:08 +01:00
|
|
|
enum airtaudio::error airtaudio::api::Alsa::closeStream() {
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state == airtaudio::state_closed) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("no open stream to close!");
|
|
|
|
return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-17 21:08:15 +01:00
|
|
|
m_private->threadRunning = false;
|
2015-02-09 21:44:32 +01:00
|
|
|
m_mutex.lock();
|
|
|
|
if (m_state == airtaudio::state_stopped) {
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->runnable = true;
|
|
|
|
m_private->runnable_cv.notify_one();
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_mutex.unlock();
|
2015-02-17 21:08:15 +01:00
|
|
|
if (m_private->thread != nullptr) {
|
|
|
|
m_private->thread->join();
|
2015-02-24 22:20:11 +01:00
|
|
|
m_private->thread = nullptr;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state == airtaudio::state_running) {
|
|
|
|
m_state = airtaudio::state_stopped;
|
|
|
|
if ( m_mode == airtaudio::mode_output
|
|
|
|
|| m_mode == airtaudio::mode_duplex) {
|
2015-02-10 21:01:53 +01:00
|
|
|
snd_pcm_drop(m_private->handles[0]);
|
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) {
|
2015-02-10 21:01:53 +01:00
|
|
|
snd_pcm_drop(m_private->handles[1]);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
}
|
2015-02-10 21:01:53 +01:00
|
|
|
// 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;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
for (int32_t iii=0; iii<2; ++iii) {
|
2015-02-09 21:44:32 +01:00
|
|
|
m_userBuffer[iii].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
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_mode = airtaudio::mode_unknow;
|
|
|
|
m_state = airtaudio::state_closed;
|
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::Alsa::startStream() {
|
2015-02-10 22:38:30 +01:00
|
|
|
// TODO : Check return ...
|
2015-02-19 22:00:21 +01:00
|
|
|
//airtaudio::Api::startStream();
|
2014-03-11 21:46:00 +01:00
|
|
|
// This method calls snd_pcm_prepare if the device isn't already in that state.
|
2015-02-06 23:54:08 +01:00
|
|
|
if (verifyStream() != airtaudio::error_none) {
|
|
|
|
return airtaudio::error_fail;
|
2014-03-12 23:55:49 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state == airtaudio::state_running) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("the stream is already running!");
|
|
|
|
return airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::unique_lock<std11::mutex> lck(m_mutex);
|
2014-03-11 21:46:00 +01:00
|
|
|
int32_t result = 0;
|
|
|
|
snd_pcm_state_t state;
|
2015-02-10 21:01:53 +01:00
|
|
|
snd_pcm_t **handle = (snd_pcm_t **) m_private->handles;
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( m_mode == airtaudio::mode_output
|
|
|
|
|| m_mode == airtaudio::mode_duplex) {
|
2015-02-06 23:54:08 +01:00
|
|
|
if (handle[0] == nullptr) {
|
|
|
|
ATA_ERROR("send nullptr to alsa ...");
|
|
|
|
if (handle[1] != nullptr) {
|
|
|
|
ATA_ERROR("note : 1 is not null");
|
|
|
|
}
|
|
|
|
}
|
2014-03-11 21:46:00 +01:00
|
|
|
state = snd_pcm_state(handle[0]);
|
|
|
|
if (state != SND_PCM_STATE_PREPARED) {
|
|
|
|
result = snd_pcm_prepare(handle[0]);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error preparing output pcm device, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( ( m_mode == airtaudio::mode_input
|
|
|
|
|| m_mode == airtaudio::mode_duplex)
|
2015-02-10 21:01:53 +01:00
|
|
|
&& !m_private->synchronized) {
|
2015-02-06 23:54:08 +01:00
|
|
|
if (handle[1] == nullptr) {
|
|
|
|
ATA_ERROR("send nullptr to alsa ...");
|
|
|
|
if (handle[0] != nullptr) {
|
|
|
|
ATA_ERROR("note : 0 is not null");
|
|
|
|
}
|
|
|
|
}
|
2014-03-11 21:46:00 +01:00
|
|
|
state = snd_pcm_state(handle[1]);
|
|
|
|
if (state != SND_PCM_STATE_PREPARED) {
|
|
|
|
result = snd_pcm_prepare(handle[1]);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error preparing input pcm device, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
m_state = airtaudio::state_running;
|
2014-03-11 21:46:00 +01:00
|
|
|
unlock:
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->runnable = true;
|
|
|
|
m_private->runnable_cv.notify_one();
|
2014-03-11 21:46:00 +01:00
|
|
|
if (result >= 0) {
|
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
|
|
|
return airtaudio::error_systemError;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
2015-02-06 23:54:08 +01:00
|
|
|
enum airtaudio::error airtaudio::api::Alsa::stopStream() {
|
|
|
|
if (verifyStream() != airtaudio::error_none) {
|
|
|
|
return airtaudio::error_fail;
|
2014-03-12 23:55:49 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state == airtaudio::state_stopped) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("the stream is already stopped!");
|
|
|
|
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;
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::unique_lock<std11::mutex> lck(m_mutex);
|
2014-03-11 21:46:00 +01:00
|
|
|
int32_t result = 0;
|
2015-02-10 21:01:53 +01:00
|
|
|
snd_pcm_t **handle = (snd_pcm_t **) m_private->handles;
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( m_mode == airtaudio::mode_output
|
|
|
|
|| m_mode == airtaudio::mode_duplex) {
|
2015-02-10 21:01:53 +01:00
|
|
|
if (m_private->synchronized) {
|
2014-03-11 21:46:00 +01:00
|
|
|
result = snd_pcm_drop(handle[0]);
|
|
|
|
} else {
|
|
|
|
result = snd_pcm_drain(handle[0]);
|
|
|
|
}
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error draining output pcm device, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( ( m_mode == airtaudio::mode_input
|
|
|
|
|| m_mode == airtaudio::mode_duplex)
|
2015-02-10 21:01:53 +01:00
|
|
|
&& !m_private->synchronized) {
|
2014-03-11 21:46:00 +01:00
|
|
|
result = snd_pcm_drop(handle[1]);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error stopping input pcm device, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unlock:
|
|
|
|
if (result >= 0) {
|
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
|
|
|
return airtaudio::error_systemError;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
2015-02-06 23:54:08 +01:00
|
|
|
enum airtaudio::error airtaudio::api::Alsa::abortStream() {
|
|
|
|
if (verifyStream() != airtaudio::error_none) {
|
|
|
|
return airtaudio::error_fail;
|
2014-03-12 23:55:49 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_state == airtaudio::state_stopped) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("the stream is already stopped!");
|
|
|
|
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;
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::unique_lock<std11::mutex> lck(m_mutex);
|
2014-03-11 21:46:00 +01:00
|
|
|
int32_t result = 0;
|
2015-02-10 21:01:53 +01:00
|
|
|
snd_pcm_t **handle = (snd_pcm_t **) m_private->handles;
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( m_mode == airtaudio::mode_output
|
|
|
|
|| m_mode == airtaudio::mode_duplex) {
|
2014-03-11 21:46:00 +01:00
|
|
|
result = snd_pcm_drop(handle[0]);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error aborting output pcm device, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( ( m_mode == airtaudio::mode_input
|
|
|
|
|| m_mode == airtaudio::mode_duplex)
|
2015-02-10 21:01:53 +01:00
|
|
|
&& !m_private->synchronized) {
|
2014-03-11 21:46:00 +01:00
|
|
|
result = snd_pcm_drop(handle[1]);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error aborting input pcm device, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unlock:
|
|
|
|
if (result >= 0) {
|
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
|
|
|
return airtaudio::error_systemError;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
2015-02-09 21:44:32 +01:00
|
|
|
|
|
|
|
void airtaudio::api::Alsa::alsaCallbackEvent(void *_userData) {
|
|
|
|
airtaudio::api::Alsa* myClass = reinterpret_cast<airtaudio::api::Alsa*>(_userData);
|
|
|
|
myClass->callbackEvent();
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:37:39 +02:00
|
|
|
void airtaudio::api::Alsa::callbackEvent() {
|
2015-02-17 21:08:15 +01:00
|
|
|
etk::log::setThreadName("Alsa IO-" + m_name);
|
|
|
|
while (m_private->threadRunning == true) {
|
2015-02-09 21:44:32 +01:00
|
|
|
callbackEventOneCycle();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-26 21:44:16 +01:00
|
|
|
std11::chrono::system_clock::time_point airtaudio::api::Alsa::getStreamTime() {
|
|
|
|
if (m_private->isMonotonic == true) {
|
|
|
|
snd_pcm_status_t *status = nullptr;
|
|
|
|
snd_pcm_status_alloca(&status);
|
|
|
|
// get harware timestamp all the time:
|
2015-02-17 21:08:15 +01:00
|
|
|
if (m_private->handles[0] != nullptr) {
|
2015-02-26 21:44:16 +01:00
|
|
|
snd_pcm_status(m_private->handles[0], status);
|
2015-02-17 21:08:15 +01:00
|
|
|
} else if (m_private->handles[1] != nullptr) {
|
2015-02-26 21:44:16 +01:00
|
|
|
snd_pcm_status(m_private->handles[1], status);
|
|
|
|
} else {
|
|
|
|
ATA_WARNING(" get time of the signal error ...");
|
|
|
|
return m_startTime + m_duration;
|
2015-02-17 21:08:15 +01:00
|
|
|
}
|
2015-02-26 21:44:16 +01:00
|
|
|
// get start time:
|
|
|
|
//snd_pcm_status_get_trigger_tstamp(status, ×tamp);
|
|
|
|
//m_startTime = std11::chrono::system_clock::from_time_t(timestamp.tv_sec) + std11::chrono::microseconds(timestamp.tv_usec);
|
|
|
|
#if 0
|
|
|
|
snd_timestamp_t timestamp;
|
|
|
|
snd_pcm_status_get_tstamp(status, ×tamp);
|
|
|
|
m_startTime = std11::chrono::system_clock::from_time_t(timestamp.tv_sec) + std11::chrono::microseconds(timestamp.tv_usec);
|
|
|
|
#else
|
|
|
|
snd_htimestamp_t timestamp;
|
|
|
|
snd_pcm_status_get_htstamp(status, ×tamp);
|
|
|
|
m_startTime = std11::chrono::system_clock::from_time_t(timestamp.tv_sec) + std11::chrono::nanoseconds(timestamp.tv_nsec);
|
|
|
|
#endif
|
|
|
|
ATA_VERBOSE("snd_pcm_status_get_htstamp : " << m_startTime);
|
|
|
|
snd_pcm_sframes_t delay = snd_pcm_status_get_delay(status);
|
|
|
|
std11::chrono::nanoseconds timeDelay(delay*1000000000LL/int64_t(m_sampleRate));
|
|
|
|
ATA_VERBOSE("delay : " << timeDelay.count() << " ns");
|
|
|
|
//return m_startTime + m_duration;
|
2015-02-19 22:00:21 +01:00
|
|
|
if (m_private->handles[0] != nullptr) {
|
2015-02-26 21:44:16 +01:00
|
|
|
// output
|
|
|
|
m_startTime += timeDelay;
|
|
|
|
} else {
|
|
|
|
// input
|
|
|
|
m_startTime -= timeDelay;
|
2015-02-19 22:00:21 +01:00
|
|
|
}
|
2015-02-26 21:44:16 +01:00
|
|
|
return m_startTime;
|
|
|
|
} else {
|
|
|
|
if (m_startTime == std11::chrono::system_clock::time_point()) {
|
|
|
|
m_startTime = std11::chrono::system_clock::now();
|
|
|
|
std11::chrono::nanoseconds timeDelay(m_bufferSize*1000000000LL/int64_t(m_sampleRate));
|
|
|
|
if (m_private->handles[0] != nullptr) {
|
|
|
|
// output
|
|
|
|
m_startTime += timeDelay;
|
|
|
|
} else {
|
|
|
|
// input
|
|
|
|
m_startTime -= timeDelay;
|
|
|
|
}
|
|
|
|
m_duration = std11::chrono::microseconds(0);
|
2015-02-19 22:49:33 +01:00
|
|
|
}
|
2015-02-19 22:00:21 +01:00
|
|
|
return m_startTime + m_duration;
|
|
|
|
}
|
2015-02-26 21:44:16 +01:00
|
|
|
return m_startTime + m_duration;
|
2015-02-19 22:00:21 +01:00
|
|
|
}
|
2015-02-16 23:01:26 +01:00
|
|
|
|
2015-02-09 21:44:32 +01:00
|
|
|
void airtaudio::api::Alsa::callbackEventOneCycle() {
|
|
|
|
if (m_state == airtaudio::state_stopped) {
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::unique_lock<std11::mutex> lck(m_mutex);
|
2014-03-11 21:46:00 +01:00
|
|
|
// TODO : Set this back ....
|
|
|
|
/*
|
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) {
|
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-06 23:54:08 +01:00
|
|
|
ATA_CRITICAL("the stream is closed ... this shouldn't happen!");
|
|
|
|
return; // TODO : notify appl: airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
int32_t doStopStream = 0;
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::chrono::system_clock::time_point streamTime;
|
2015-02-17 21:08:15 +01:00
|
|
|
std::vector<enum airtaudio::status> status;
|
2015-02-16 23:01:26 +01:00
|
|
|
if ( m_mode != airtaudio::mode_input
|
|
|
|
&& m_private->xrun[0] == true) {
|
2015-02-17 21:08:15 +01:00
|
|
|
status.push_back(airtaudio::status_underflow);
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->xrun[0] = false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-16 23:01:26 +01:00
|
|
|
if ( m_mode != airtaudio::mode_output
|
|
|
|
&& m_private->xrun[1] == true) {
|
2015-02-17 21:08:15 +01:00
|
|
|
status.push_back(airtaudio::status_overflow);
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->xrun[1] = false;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
int32_t result;
|
|
|
|
char *buffer;
|
|
|
|
int32_t channels;
|
|
|
|
snd_pcm_t **handle;
|
|
|
|
snd_pcm_sframes_t frames;
|
2015-02-05 23:31:22 +01:00
|
|
|
audio::format format;
|
2015-02-10 21:01:53 +01:00
|
|
|
handle = (snd_pcm_t **) m_private->handles;
|
2015-02-19 22:49:33 +01:00
|
|
|
|
|
|
|
if (m_state == airtaudio::state_stopped) {
|
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( m_mode == airtaudio::mode_input
|
|
|
|
|| m_mode == airtaudio::mode_duplex) {
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::unique_lock<std11::mutex> lck(m_mutex);
|
2014-03-11 21:46:00 +01:00
|
|
|
// Setup parameters.
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_doConvertBuffer[1]) {
|
|
|
|
buffer = m_deviceBuffer;
|
|
|
|
channels = m_nDeviceChannels[1];
|
|
|
|
format = m_deviceFormat[1];
|
2014-03-11 21:46:00 +01:00
|
|
|
} else {
|
2015-02-09 21:44:32 +01:00
|
|
|
buffer = &m_userBuffer[1][0];
|
|
|
|
channels = m_nUserChannels[1];
|
|
|
|
format = m_userFormat;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Read samples from device in interleaved/non-interleaved format.
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_deviceInterleaved[1]) {
|
|
|
|
result = snd_pcm_readi(handle[1], buffer, m_bufferSize);
|
2014-03-11 21:46:00 +01:00
|
|
|
} else {
|
|
|
|
void *bufs[channels];
|
2015-02-09 21:44:32 +01:00
|
|
|
size_t offset = m_bufferSize * audio::getFormatBytes(format);
|
2014-03-11 21:46:00 +01:00
|
|
|
for (int32_t i=0; i<channels; i++)
|
|
|
|
bufs[i] = (void *) (buffer + (i * offset));
|
2015-02-09 21:44:32 +01:00
|
|
|
result = snd_pcm_readn(handle[1], bufs, m_bufferSize);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-19 22:49:33 +01:00
|
|
|
// get timestamp : (to init here ...
|
|
|
|
streamTime = getStreamTime();
|
2015-02-09 21:44:32 +01:00
|
|
|
if (result < (int) m_bufferSize) {
|
2014-03-11 21:46:00 +01:00
|
|
|
// Either an error or overrun occured.
|
|
|
|
if (result == -EPIPE) {
|
|
|
|
snd_pcm_state_t state = snd_pcm_state(handle[1]);
|
|
|
|
if (state == SND_PCM_STATE_XRUN) {
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->xrun[1] = true;
|
2014-03-11 21:46:00 +01:00
|
|
|
result = snd_pcm_prepare(handle[1]);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error preparing device after overrun, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
} else {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error, current state is " << snd_pcm_state_name(state) << ", " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
} else {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("audio read error, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-06 23:54:08 +01:00
|
|
|
// TODO : Notify application ... airtaudio::error_warning;
|
2015-02-19 22:49:33 +01:00
|
|
|
goto noInput;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Do byte swapping if necessary.
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_doByteSwap[1]) {
|
|
|
|
byteSwapBuffer(buffer, m_bufferSize * channels, format);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Do buffer conversion if necessary.
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_doConvertBuffer[1]) {
|
|
|
|
convertBuffer(&m_userBuffer[1][0], m_deviceBuffer, m_convertInfo[1]);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Check stream latency
|
|
|
|
result = snd_pcm_delay(handle[1], &frames);
|
2014-03-12 23:55:49 +01:00
|
|
|
if (result == 0 && frames > 0) {
|
2015-02-19 22:00:21 +01:00
|
|
|
ATA_WARNING("Delay in the Input " << frames << " chunk");
|
2015-02-09 21:44:32 +01:00
|
|
|
m_latency[1] = frames;
|
2014-03-12 23:55:49 +01:00
|
|
|
}
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
2015-02-19 22:49:33 +01:00
|
|
|
noInput:
|
|
|
|
streamTime = getStreamTime();
|
|
|
|
doStopStream = m_callback(&m_userBuffer[1][0],
|
2015-02-24 22:20:11 +01:00
|
|
|
streamTime,// - std11::chrono::nanoseconds(m_latency[1]*1000000000LL/int64_t(m_sampleRate)),
|
2015-02-19 22:49:33 +01:00
|
|
|
&m_userBuffer[0][0],
|
2015-02-24 22:20:11 +01:00
|
|
|
streamTime,// + std11::chrono::nanoseconds(m_latency[0]*1000000000LL/int64_t(m_sampleRate)),
|
2015-02-19 22:49:33 +01:00
|
|
|
m_bufferSize,
|
|
|
|
status);
|
|
|
|
if (doStopStream == 2) {
|
|
|
|
abortStream();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-02-09 21:44:32 +01:00
|
|
|
if ( m_mode == airtaudio::mode_output
|
|
|
|
|| m_mode == airtaudio::mode_duplex) {
|
2015-02-24 22:20:11 +01:00
|
|
|
std11::unique_lock<std11::mutex> lck(m_mutex);
|
2014-03-11 21:46:00 +01:00
|
|
|
// Setup parameters and do buffer conversion if necessary.
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_doConvertBuffer[0]) {
|
|
|
|
buffer = m_deviceBuffer;
|
|
|
|
convertBuffer(buffer, &m_userBuffer[0][0], m_convertInfo[0]);
|
|
|
|
channels = m_nDeviceChannels[0];
|
|
|
|
format = m_deviceFormat[0];
|
2014-03-11 21:46:00 +01:00
|
|
|
} else {
|
2015-02-09 21:44:32 +01:00
|
|
|
buffer = &m_userBuffer[0][0];
|
|
|
|
channels = m_nUserChannels[0];
|
|
|
|
format = m_userFormat;
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Do byte swapping if necessary.
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_doByteSwap[0]) {
|
|
|
|
byteSwapBuffer(buffer, m_bufferSize * channels, format);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
// Write samples to device in interleaved/non-interleaved format.
|
2015-02-09 21:44:32 +01:00
|
|
|
if (m_deviceInterleaved[0]) {
|
|
|
|
result = snd_pcm_writei(handle[0], buffer, m_bufferSize);
|
2014-03-11 21:46:00 +01:00
|
|
|
} else {
|
|
|
|
void *bufs[channels];
|
2015-02-09 21:44:32 +01:00
|
|
|
size_t offset = m_bufferSize * audio::getFormatBytes(format);
|
2014-03-11 21:46:00 +01:00
|
|
|
for (int32_t i=0; i<channels; i++) {
|
|
|
|
bufs[i] = (void *) (buffer + (i * offset));
|
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
result = snd_pcm_writen(handle[0], bufs, m_bufferSize);
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-09 21:44:32 +01:00
|
|
|
if (result < (int) m_bufferSize) {
|
2014-03-11 21:46:00 +01:00
|
|
|
// Either an error or underrun occured.
|
|
|
|
if (result == -EPIPE) {
|
|
|
|
snd_pcm_state_t state = snd_pcm_state(handle[0]);
|
|
|
|
if (state == SND_PCM_STATE_XRUN) {
|
2015-02-10 21:01:53 +01:00
|
|
|
m_private->xrun[0] = true;
|
2014-03-11 21:46:00 +01:00
|
|
|
result = snd_pcm_prepare(handle[0]);
|
|
|
|
if (result < 0) {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error preparing device after underrun, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
} else {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("error, current state is " << snd_pcm_state_name(state) << ", " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
} else {
|
2015-02-06 23:54:08 +01:00
|
|
|
ATA_ERROR("audio write error, " << snd_strerror(result) << ".");
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
2015-02-06 23:54:08 +01:00
|
|
|
// TODO : Notuify application airtaudio::error_warning;
|
2014-03-11 21:46:00 +01:00
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
// Check stream latency
|
|
|
|
result = snd_pcm_delay(handle[0], &frames);
|
2014-03-12 23:55:49 +01:00
|
|
|
if (result == 0 && frames > 0) {
|
2015-02-19 22:00:21 +01:00
|
|
|
ATA_WARNING("Delay in the Output " << frames << " chunk");
|
2015-02-09 21:44:32 +01:00
|
|
|
m_latency[0] = frames;
|
2014-03-12 23:55:49 +01:00
|
|
|
}
|
2014-03-11 21:46:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
unlock:
|
|
|
|
airtaudio::Api::tickStreamTime();
|
|
|
|
if (doStopStream == 1) {
|
|
|
|
this->stopStream();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif
|