using namespace ARDOUR;
PortAudioIO::PortAudioIO ()
- : _state (-1)
- , _initialized (false)
- , _capture_channels (0)
+ : _capture_channels (0)
, _playback_channels (0)
, _stream (0)
, _input_buffer (0)
PortAudioIO::~PortAudioIO ()
{
- if (_state == 0) {
- pcm_stop();
- }
- if (_initialized) {
- Pa_Terminate();
- }
+ close_stream();
+ pa_deinitialize ();
clear_device_lists ();
free (_input_buffer); _input_buffer = NULL;
int
PortAudioIO::available_sample_rates(int device_id, std::vector<float>& sampleRates)
{
- if (!initialize_pa()) return -1;
+ if (!pa_initialize()) return -1;
+
+#ifdef WITH_ASIO
+ if (get_current_host_api_type() == paASIO) {
+ get_default_sample_rates(sampleRates);
+ return 0;
+ }
+#endif
// TODO use separate int device_input, int device_output ?!
if (device_id == DeviceDefault) {
return true;
}
+static
bool
-PortAudioIO::get_asio_buffer_sizes (int device_id, std::vector<uint32_t>& buffer_sizes)
+is_power_of_two (uint32_t v)
+{
+ return ((v != 0) && !(v & (v - 1)));
+}
+
+bool
+PortAudioIO::get_asio_buffer_sizes(int device_id,
+ std::vector<uint32_t>& buffer_sizes,
+ bool preferred_only)
{
long min_size_frames = 0;
long max_size_frames = 0;
preferred_size_frames,
granularity));
-#ifdef USE_ASIO_MIN_MAX_BUFFER_SIZES
- if (min_size_frames >= max_size_frames) {
- buffer_sizes.push_back (preferred_size_frames);
+ bool driver_returns_one_size = (min_size_frames == max_size_frames) &&
+ (min_size_frames == preferred_size_frames);
+
+ if (preferred_only || driver_returns_one_size) {
+ buffer_sizes.push_back(preferred_size_frames);
return true;
}
long buffer_size = min_size_frames;
- while (buffer_size <= max_size_frames) {
- buffer_sizes.push_back (buffer_size);
- if (granularity <= 0) {
- // buffer sizes are power of 2
- buffer_size = buffer_size * 2;
- } else {
+ // If min size and granularity are power of two then just use values that
+ // are power of 2 even if the granularity allows for more values
+ bool use_power_of_two =
+ is_power_of_two(min_size_frames) && is_power_of_two(granularity);
+
+ if (granularity <= 0 || use_power_of_two) {
+ // driver uses buffer sizes that are power of 2
+ while (buffer_size <= max_size_frames) {
+ buffer_sizes.push_back(buffer_size);
+ buffer_size *= 2;
+ }
+ } else {
+ if (min_size_frames == max_size_frames) {
+ // The devices I have tested either return the same values for
+ // min/max/preferred and changing buffer size is intended to only be
+ // done via the control dialog or they return a range where min != max
+ // but I guess min == max could happen if a driver only supports a single
+ // buffer size
+ buffer_sizes.push_back(min_size_frames);
+ return true;
+ }
+
+ // If min_size_frames is not power of 2 use at most 8 of the possible
+ // buffer sizes spread evenly between min and max
+ long max_values = 8;
+ while (((max_size_frames - min_size_frames) / granularity) > max_values) {
+ granularity *= 2;
+ }
+
+ while (buffer_size < max_size_frames) {
+ buffer_sizes.push_back(buffer_size);
buffer_size += granularity;
}
+ buffer_sizes.push_back(max_size_frames);
}
-#else
- buffer_sizes.push_back (preferred_size_frames);
-#endif
return true;
}
#endif
{
#ifdef WITH_ASIO
if (get_current_host_api_type() == paASIO) {
- if (get_asio_buffer_sizes (device_id, buffer_sizes)) {
+ if (get_asio_buffer_sizes (device_id, buffer_sizes, false)) {
return 0;
}
}
}
}
+bool&
+PortAudioIO::pa_initialized()
+{
+ static bool s_initialized = false;
+ return s_initialized;
+}
+
bool
-PortAudioIO::initialize_pa ()
+PortAudioIO::pa_initialize()
{
- PaError err = paNoError;
+ if (pa_initialized()) return true;
- if (!_initialized) {
- err = Pa_Initialize();
- if (err != paNoError) {
- return false;
- }
- _initialized = true;
- _host_api_index = Pa_GetDefaultHostApi ();
- _host_api_name = get_host_api_name_from_index (_host_api_index);
+ PaError err = Pa_Initialize();
+ if (err != paNoError) {
+ return false;
}
+ pa_initialized() = true;
return true;
}
+bool
+PortAudioIO::pa_deinitialize()
+{
+ if (!pa_initialized()) return true;
+
+ PaError err = Pa_Terminate();
+ if (err != paNoError) {
+ return false;
+ }
+ pa_initialized() = false;
+ return true;
+}
+
void
PortAudioIO::host_api_list (std::vector<std::string>& api_list)
{
- if (!initialize_pa()) return;
+ if (!pa_initialize()) return;
PaHostApiIndex count = Pa_GetHostApiCount();
PaHostApiIndex
PortAudioIO::get_host_api_index_from_name (const std::string& name)
{
- if (!initialize_pa()) return -1;
+ if (!pa_initialize()) return -1;
PaHostApiIndex count = Pa_GetHostApiCount();
}
PaDeviceIndex
-PortAudioIO::get_default_input_device ()
+PortAudioIO::get_default_input_device () const
{
const PaHostApiInfo* info = Pa_GetHostApiInfo (_host_api_index);
if (info == NULL) return -1;
}
PaDeviceIndex
-PortAudioIO::get_default_output_device ()
+PortAudioIO::get_default_output_device () const
{
const PaHostApiInfo* info = Pa_GetHostApiInfo (_host_api_index);
if (info == NULL) return -1;
}
}
-void
-PortAudioIO::discover()
+bool
+PortAudioIO::update_devices()
{
- DEBUG_AUDIO ("PortAudio: discover\n");
- if (!initialize_pa()) return;
+ DEBUG_AUDIO ("Update devices\n");
+ if (_stream != NULL) return false;
+ pa_deinitialize();
+ if (!pa_initialize()) return false;
clear_device_lists ();
- add_none_devices ();
+
+ // ASIO doesn't support separate input/output devices so adding None
+ // doesn't make sense
+ if (get_current_host_api_type() != paASIO) {
+ add_none_devices ();
+ }
add_devices ();
+ return true;
}
void
-PortAudioIO::pcm_stop ()
+PortAudioIO::reset_stream_dependents ()
{
- if (_stream) {
- Pa_CloseStream (_stream);
- }
- _stream = NULL;
-
_capture_channels = 0;
_playback_channels = 0;
_cur_sample_rate = 0;
_cur_input_latency = 0;
_cur_output_latency = 0;
+}
+
+PaErrorCode
+PortAudioIO::close_stream()
+{
+ if (!_stream) return paNoError;
+
+ PaError err = Pa_CloseStream (_stream);
+
+ if (err != paNoError) {
+ return (PaErrorCode)err;
+ }
+ _stream = NULL;
+
+ reset_stream_dependents();
free (_input_buffer); _input_buffer = NULL;
free (_output_buffer); _output_buffer = NULL;
- _state = -1;
+ return paNoError;
}
-int
-PortAudioIO::pcm_start()
+PaErrorCode
+PortAudioIO::start_stream()
{
PaError err = Pa_StartStream (_stream);
if (err != paNoError) {
- _state = -1;
- return -1;
+ DEBUG_AUDIO(string_compose("PortAudio failed to start stream %1\n",
+ Pa_GetErrorText(err)));
+ return (PaErrorCode)err;
}
- return 0;
+ return paNoError;
}
-#ifdef __APPLE__
-static uint32_t lower_power_of_two (uint32_t v) {
- v--;
- v |= v >> 1;
- v |= v >> 2;
- v |= v >> 4;
- v |= v >> 8;
- v |= v >> 16;
- v++;
- return v >> 1;
+bool
+PortAudioIO::set_sample_rate_and_latency_from_stream ()
+{
+ const PaStreamInfo* nfo_s = Pa_GetStreamInfo(_stream);
+
+ if (nfo_s == NULL) {
+ return false;
+ }
+
+ _cur_sample_rate = nfo_s->sampleRate;
+ _cur_input_latency = nfo_s->inputLatency * _cur_sample_rate;
+ _cur_output_latency = nfo_s->outputLatency * _cur_sample_rate;
+
+ DEBUG_AUDIO (string_compose ("PA Sample Rate %1 SPS\n", _cur_sample_rate));
+
+ DEBUG_AUDIO (string_compose ("PA Input Latency %1ms, %2 spl\n",
+ 1e3 * nfo_s->inputLatency,
+ _cur_input_latency));
+
+ DEBUG_AUDIO (string_compose ("PA Output Latency %1ms, %2 spl\n",
+ 1e3 * nfo_s->outputLatency,
+ _cur_output_latency));
+ return true;
}
-#endif
-int
-PortAudioIO::pcm_setup (
- int device_input, int device_output,
- double sample_rate, uint32_t samples_per_period)
+bool
+PortAudioIO::allocate_buffers_for_blocking_api (uint32_t samples_per_period)
{
- _state = -2;
+ if (_capture_channels > 0) {
+ _input_buffer =
+ (float*)malloc(samples_per_period * _capture_channels * sizeof(float));
+ if (!_input_buffer) {
+ DEBUG_AUDIO("PortAudio failed to allocate input buffer.\n");
+ return false;
+ }
+ }
- PaError err = paNoError;
- const PaDeviceInfo *nfo_in = NULL;
- const PaDeviceInfo *nfo_out = NULL;
- const PaStreamInfo *nfo_s = NULL;
-
- if (!initialize_pa()) {
- DEBUG_AUDIO ("PortAudio Initialization Failed\n");
- goto error;
+ if (_playback_channels > 0) {
+ _output_buffer =
+ (float*)calloc(samples_per_period * _playback_channels, sizeof(float));
+ if (!_output_buffer) {
+ DEBUG_AUDIO("PortAudio failed to allocate output buffer.\n");
+ return false;
+ }
}
+ return true;
+}
+
+bool
+PortAudioIO::get_input_stream_params(int device_input,
+ PaStreamParameters& inputParam) const
+{
+ const PaDeviceInfo *nfo_in = NULL;
if (device_input == DeviceDefault) {
device_input = get_default_input_device ();
}
- if (device_output == DeviceDefault) {
- device_output = get_default_output_device ();
- }
- _capture_channels = 0;
- _playback_channels = 0;
- _cur_sample_rate = 0;
- _cur_input_latency = 0;
- _cur_output_latency = 0;
+ if (device_input == DeviceNone) {
+ return false;
+ }
- DEBUG_AUDIO (string_compose (
- "PortAudio Device IDs: i:%1 o:%2\n", device_input, device_output));
+ nfo_in = Pa_GetDeviceInfo(device_input);
- if (device_input == DeviceNone && device_output == DeviceNone) {
- // just send the error msg for now rather than return it
- error << AudioBackend::get_error_string(AudioBackend::DeviceConfigurationNotSupportedError)
- << endmsg;
- return -1;
+ if (nfo_in == NULL) {
+ DEBUG_AUDIO ("PortAudio Cannot Query Input Device Info\n");
+ return false;
}
- if (device_input != DeviceNone) {
- nfo_in = Pa_GetDeviceInfo(device_input);
+ inputParam.device = device_input;
+ inputParam.channelCount = nfo_in->maxInputChannels;
+#ifdef INTERLEAVED_INPUT
+ inputParam.sampleFormat = paFloat32;
+#else
+ inputParam.sampleFormat = paFloat32 | paNonInterleaved;
+#endif
+ inputParam.suggestedLatency = nfo_in->defaultLowInputLatency;
+ inputParam.hostApiSpecificStreamInfo = NULL;
+
+ return true;
+}
+
+bool
+PortAudioIO::get_output_stream_params(int device_output,
+ PaStreamParameters& outputParam) const
+{
+ const PaDeviceInfo *nfo_out = NULL;
+
+ if (device_output == DeviceDefault) {
+ device_output = get_default_output_device ();
}
- if (device_output != DeviceNone) {
- nfo_out = Pa_GetDeviceInfo(device_output);
+ if (device_output == DeviceNone) {
+ return false;
}
- if (!nfo_in && ! nfo_out) {
- DEBUG_AUDIO ("PortAudio Cannot Query Device Info\n");
- goto error;
+ nfo_out = Pa_GetDeviceInfo(device_output);
+
+ if (nfo_out == NULL) {
+ DEBUG_AUDIO ("PortAudio Cannot Query Output Device Info\n");
+ return false;
}
- if (nfo_in) {
- _capture_channels = nfo_in->maxInputChannels;
+ outputParam.device = device_output;
+ outputParam.channelCount = nfo_out->maxOutputChannels;
+#ifdef INTERLEAVED_OUTPUT
+ outputParam.sampleFormat = paFloat32;
+#else
+ outputParam.sampleFormat = paFloat32 | paNonInterleaved;
+#endif
+ outputParam.suggestedLatency = nfo_out->defaultLowOutputLatency;
+ outputParam.hostApiSpecificStreamInfo = NULL;
+
+ return true;
+}
+
+PaErrorCode
+PortAudioIO::pre_stream_open(int device_input,
+ PaStreamParameters& inputParam,
+ int device_output,
+ PaStreamParameters& outputParam)
+{
+ if (!pa_initialize()) {
+ DEBUG_AUDIO ("PortAudio Initialization Failed\n");
+ return paNotInitialized;
}
- if (nfo_out) {
- _playback_channels = nfo_out->maxOutputChannels;
+
+ reset_stream_dependents ();
+
+ DEBUG_AUDIO (string_compose (
+ "PortAudio Device IDs: i:%1 o:%2\n", device_input, device_output));
+
+ if (device_input == DeviceNone && device_output == DeviceNone) {
+ return paBadIODeviceCombination;
}
- if(_capture_channels == 0 && _playback_channels == 0) {
- DEBUG_AUDIO ("PortAudio no input or output channels.\n");
- goto error;
+ if (get_input_stream_params(device_input, inputParam)) {
+ _capture_channels = inputParam.channelCount;
}
-#ifdef __APPLE__
- // pa_mac_core_blocking.c pa_stable_v19_20140130
- // BUG: ringbuffer alloc requires power-of-two chn count.
- if ((_capture_channels & (_capture_channels - 1)) != 0) {
- DEBUG_AUDIO (
- "Adjusted capture channels to power of two (portaudio rb bug)\n");
- _capture_channels = lower_power_of_two (_capture_channels);
+ if (get_output_stream_params(device_output, outputParam)) {
+ _playback_channels = outputParam.channelCount;
}
- if ((_playback_channels & (_playback_channels - 1)) != 0) {
- DEBUG_AUDIO (
- "Adjusted capture channels to power of two (portaudio rb bug)\n");
- _playback_channels = lower_power_of_two (_playback_channels);
+ if (_capture_channels == 0 && _playback_channels == 0) {
+ DEBUG_AUDIO("PortAudio no input or output channels.\n");
+ return paBadIODeviceCombination;
}
-#endif
DEBUG_AUDIO (string_compose ("PortAudio Channels: in:%1 out:%2\n",
_capture_channels,
_playback_channels));
+ return paNoError;
+}
+
+PaErrorCode
+PortAudioIO::open_callback_stream(int device_input,
+ int device_output,
+ double sample_rate,
+ uint32_t samples_per_period,
+ PaStreamCallback* callback,
+ void* data)
+{
PaStreamParameters inputParam;
PaStreamParameters outputParam;
- if (nfo_in) {
- inputParam.device = device_input;
- inputParam.channelCount = _capture_channels;
-#ifdef INTERLEAVED_INPUT
- inputParam.sampleFormat = paFloat32;
-#else
- inputParam.sampleFormat = paFloat32 | paNonInterleaved;
-#endif
- inputParam.suggestedLatency = nfo_in->defaultLowInputLatency;
- inputParam.hostApiSpecificStreamInfo = NULL;
+ PaErrorCode error_code =
+ pre_stream_open(device_input, inputParam, device_output, outputParam);
+
+ if (error_code != paNoError) return error_code;
+
+ PaError err = paNoError;
+
+ DEBUG_AUDIO ("Open Callback Stream\n");
+
+ err = Pa_OpenStream(&_stream,
+ _capture_channels > 0 ? &inputParam : NULL,
+ _playback_channels > 0 ? &outputParam : NULL,
+ sample_rate,
+ samples_per_period,
+ paDitherOff,
+ callback,
+ data);
+
+ if (err != paNoError) {
+ DEBUG_AUDIO ("PortAudio failed to start stream.\n");
+ return paInternalError;
}
- if (nfo_out) {
- outputParam.device = device_output;
- outputParam.channelCount = _playback_channels;
-#ifdef INTERLEAVED_OUTPUT
- outputParam.sampleFormat = paFloat32;
-#else
- outputParam.sampleFormat = paFloat32 | paNonInterleaved;
-#endif
- outputParam.suggestedLatency = nfo_out->defaultLowOutputLatency;
- outputParam.hostApiSpecificStreamInfo = NULL;
+ if (!set_sample_rate_and_latency_from_stream()) {
+ DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
+ close_stream();
+ return paInternalError;
}
- // XXX re-consider using callback API, testing needed.
+ return paNoError;
+}
+
+PaErrorCode
+PortAudioIO::open_blocking_stream(int device_input,
+ int device_output,
+ double sample_rate,
+ uint32_t samples_per_period)
+{
+ PaStreamParameters inputParam;
+ PaStreamParameters outputParam;
+
+ PaErrorCode error_code =
+ pre_stream_open(device_input, inputParam, device_output, outputParam);
+
+ if (error_code != paNoError) return error_code;
+
+ PaError err = paNoError;
+
err = Pa_OpenStream (
&_stream,
_capture_channels > 0 ? &inputParam: NULL,
NULL, NULL);
if (err != paNoError) {
- DEBUG_AUDIO ("PortAudio failed to start stream.\n");
- goto error;
+ DEBUG_AUDIO(string_compose("PortAudio failed to open stream %1\n",
+ Pa_GetErrorText(err)));
+ return (PaErrorCode)err;
}
- nfo_s = Pa_GetStreamInfo (_stream);
- if (!nfo_s) {
+ if (!set_sample_rate_and_latency_from_stream()) {
DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
- pcm_stop();
- goto error;
+ close_stream();
+ return paInternalError;
}
- _cur_sample_rate = nfo_s->sampleRate;
- _cur_input_latency = nfo_s->inputLatency * _cur_sample_rate;
- _cur_output_latency = nfo_s->outputLatency * _cur_sample_rate;
-
- DEBUG_AUDIO (string_compose ("PA Sample Rate %1 SPS\n", _cur_sample_rate));
-
- DEBUG_AUDIO (string_compose ("PA Input Latency %1ms, %2 spl\n",
- 1e3 * nfo_s->inputLatency,
- _cur_input_latency));
-
- DEBUG_AUDIO (string_compose ("PA Output Latency %1ms, %2 spl\n",
- 1e3 * nfo_s->outputLatency,
- _cur_output_latency));
-
- _state = 0;
-
- if (_capture_channels > 0) {
- _input_buffer = (float*) malloc (samples_per_period * _capture_channels * sizeof(float));
- if (!_input_buffer) {
- DEBUG_AUDIO ("PortAudio failed to allocate input buffer.\n");
- pcm_stop();
- goto error;
- }
+ if (!allocate_buffers_for_blocking_api(samples_per_period)) {
+ close_stream();
+ return paInternalError;
}
-
- if (_playback_channels > 0) {
- _output_buffer = (float*) calloc (samples_per_period * _playback_channels, sizeof(float));
- if (!_output_buffer) {
- DEBUG_AUDIO ("PortAudio failed to allocate output buffer.\n");
- pcm_stop();
- goto error;
- }
- }
-
- return 0;
-
-error:
- _capture_channels = 0;
- _playback_channels = 0;
- free (_input_buffer); _input_buffer = NULL;
- free (_output_buffer); _output_buffer = NULL;
- return -1;
+ return paNoError;
}
int