X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=libs%2Fbackends%2Fportaudio%2Fportaudio_io.cc;h=9ec8f6d6fb9d0e292d0603955af5398e5ba24e6f;hb=58c1ff368c0326902a12a179c750e340d5c89e33;hp=b7ea02ff22dab96c4d68d3369cb7e69f3129f8bb;hpb=b12f865a4ac161c2d9e08379a83842342975090c;p=ardour.git diff --git a/libs/backends/portaudio/portaudio_io.cc b/libs/backends/portaudio/portaudio_io.cc index b7ea02ff22..9ec8f6d6fb 100644 --- a/libs/backends/portaudio/portaudio_io.cc +++ b/libs/backends/portaudio/portaudio_io.cc @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Robin Gareus + * Copyright (C) 2015 Tim Mayberry * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,17 +22,27 @@ #include #include #include + #include "portaudio_io.h" +#ifdef WITH_ASIO +#include "pa_asio.h" +#endif + +#include "pbd/compose.h" + +#include "ardour/audio_backend.h" + +#include "debug.h" + #define INTERLEAVED_INPUT #define INTERLEAVED_OUTPUT +using namespace PBD; using namespace ARDOUR; PortAudioIO::PortAudioIO () - : _state (-1) - , _initialized (false) - , _capture_channels (0) + : _capture_channels (0) , _playback_channels (0) , _stream (0) , _input_buffer (0) @@ -39,39 +50,81 @@ PortAudioIO::PortAudioIO () , _cur_sample_rate (0) , _cur_input_latency (0) , _cur_output_latency (0) + , _host_api_index(-1) { } PortAudioIO::~PortAudioIO () { - if (_state == 0) { - pcm_stop(); - } - if (_initialized) { - Pa_Terminate(); - } + close_stream(); + pa_deinitialize (); clear_device_lists (); free (_input_buffer); _input_buffer = NULL; free (_output_buffer); _output_buffer = NULL; } +std::string +PortAudioIO::control_app_name (int device_id) const +{ +#ifdef WITH_ASIO + if (get_current_host_api_type() == paASIO) { + // is this used for anything, or just acts as a boolean? + return "PortaudioASIO"; + } +#endif + + return std::string(); +} + +void +PortAudioIO::launch_control_app (int device_id) +{ +#ifdef WITH_ASIO + PaError err = PaAsio_ShowControlPanel (device_id, NULL); + + if (err != paNoError) { + // error << ? + DEBUG_AUDIO (string_compose ( + "Unable to show control panel for device with index %1\n", device_id)); + } +#endif +} + +void +PortAudioIO::get_default_sample_rates (std::vector& rates) +{ + rates.push_back(8000.0); + rates.push_back(22050.0); + rates.push_back(24000.0); + rates.push_back(44100.0); + rates.push_back(48000.0); + rates.push_back(88200.0); + rates.push_back(96000.0); + rates.push_back(176400.0); + rates.push_back(192000.0); +} int PortAudioIO::available_sample_rates(int device_id, std::vector& sampleRates) { - static const float ardourRates[] = { 8000.0, 22050.0, 24000.0, 44100.0, 48000.0, 88200.0, 96000.0, 176400.0, 192000.0}; + if (!pa_initialize()) return -1; - if (!initialize_pa()) 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 == -1) { + if (device_id == DeviceDefault) { device_id = get_default_input_device (); } -#ifndef NDEBUG - printf("PortAudio: Querying Samplerates for device %d\n", device_id); -#endif + + DEBUG_AUDIO ( + string_compose ("Querying Samplerates for device %1\n", device_id)); sampleRates.clear(); const PaDeviceInfo* nfo = Pa_GetDeviceInfo(device_id); @@ -92,33 +145,175 @@ PortAudioIO::available_sample_rates(int device_id, std::vector& sampleRat outputParam.suggestedLatency = 0; outputParam.hostApiSpecificStreamInfo = 0; - for (uint32_t i = 0; i < sizeof(ardourRates)/sizeof(float); ++i) { - if (paFormatIsSupported == Pa_IsFormatSupported( - nfo->maxInputChannels > 0 ? &inputParam : NULL, - nfo->maxOutputChannels > 0 ? &outputParam : NULL, - ardourRates[i])) { - sampleRates.push_back (ardourRates[i]); + std::vector rates; + get_default_sample_rates(rates); + + for (std::vector::const_iterator i = rates.begin(); i != rates.end(); + ++i) { + if (paFormatIsSupported == + Pa_IsFormatSupported(nfo->maxInputChannels > 0 ? &inputParam : NULL, + nfo->maxOutputChannels > 0 ? &outputParam : NULL, + *i)) { + sampleRates.push_back(*i); } } } if (sampleRates.empty()) { // fill in something.. - sampleRates.push_back (44100.0); - sampleRates.push_back (48000.0); + get_default_sample_rates(sampleRates); } return 0; } +#ifdef WITH_ASIO +bool +PortAudioIO::get_asio_buffer_properties (int device_id, + long& min_size_frames, + long& max_size_frames, + long& preferred_size_frames, + long& granularity) +{ + // we shouldn't really need all these checks but it shouldn't hurt + const PaDeviceInfo* device_info = Pa_GetDeviceInfo(device_id); + + if (!device_info) { + DEBUG_AUDIO (string_compose ( + "Unable to get device info from device index %1\n", device_id)); + return false; + } + + if (get_current_host_api_type() != paASIO) { + DEBUG_AUDIO (string_compose ( + "ERROR device_id %1 is not an ASIO device\n", device_id)); + return false; + } + + PaError err = PaAsio_GetAvailableBufferSizes (device_id, + &min_size_frames, + &max_size_frames, + &preferred_size_frames, + &granularity); + + if (err != paNoError) { + DEBUG_AUDIO (string_compose ( + "Unable to determine available buffer sizes for device %1\n", device_id)); + return false; + } + return true; +} + +static +bool +is_power_of_two (uint32_t v) +{ + return ((v != 0) && !(v & (v - 1))); +} + +bool +PortAudioIO::get_asio_buffer_sizes(int device_id, + std::vector& buffer_sizes, + bool preferred_only) +{ + long min_size_frames = 0; + long max_size_frames = 0; + long preferred_size_frames = 0; + long granularity = 0; + + if (!get_asio_buffer_properties (device_id, + min_size_frames, + max_size_frames, + preferred_size_frames, + granularity)) { + DEBUG_AUDIO (string_compose ( + "Unable to get device buffer properties from device index %1\n", device_id)); + return false; + } + + DEBUG_AUDIO (string_compose ("ASIO buffer properties for device %1, " + "min_size_frames: %2, max_size_frames: %3, " + "preferred_size_frames: %4, granularity: %5\n", + device_id, + min_size_frames, + max_size_frames, + preferred_size_frames, + granularity)); + + 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; + + // 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); + } + return true; +} +#endif + +void +PortAudioIO::get_default_buffer_sizes(std::vector& buffer_sizes) +{ + buffer_sizes.push_back(64); + buffer_sizes.push_back(128); + buffer_sizes.push_back(256); + buffer_sizes.push_back(512); + buffer_sizes.push_back(1024); + buffer_sizes.push_back(2048); + buffer_sizes.push_back(4096); +} + int -PortAudioIO::available_buffer_sizes(int device_id, std::vector& bufferSizes) +PortAudioIO::available_buffer_sizes(int device_id, std::vector& buffer_sizes) { - // TODO - static const uint32_t ardourSizes[] = { 64, 128, 256, 512, 1024, 2048, 4096 }; - for(uint32_t i = 0; i < sizeof(ardourSizes)/sizeof(uint32_t); ++i) { - bufferSizes.push_back (ardourSizes[i]); +#ifdef WITH_ASIO + if (get_current_host_api_type() == paASIO) { + if (get_asio_buffer_sizes (device_id, buffer_sizes, false)) { + return 0; + } } +#endif + + get_default_buffer_sizes (buffer_sizes); + return 0; } @@ -142,26 +337,44 @@ PortAudioIO::output_device_list(std::map &devices) const } } +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; + 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& api_list) { - if (!initialize_pa()) return; + if (!pa_initialize()) return; PaHostApiIndex count = Pa_GetHostApiCount(); @@ -175,36 +388,70 @@ PortAudioIO::host_api_list (std::vector& api_list) } } -void + +PaHostApiTypeId +PortAudioIO::get_current_host_api_type () const +{ + const PaHostApiInfo* info = Pa_GetHostApiInfo (_host_api_index); + + if (info == NULL) { + DEBUG_AUDIO(string_compose( + "Unable to determine Host API type from index %1\n", _host_api_index)); + return (PaHostApiTypeId)0; + } + + return info->type; +} + +std::string +PortAudioIO::get_host_api_name_from_index (PaHostApiIndex index) +{ + std::vector api_list; + host_api_list(api_list); + return api_list[index]; +} + +bool PortAudioIO::set_host_api (const std::string& host_api_name) { - _host_api_index = get_host_api_index_from_name (host_api_name); + PaHostApiIndex new_index = get_host_api_index_from_name (host_api_name); - if (_host_api_index < 0) { - fprintf(stderr, "Error setting host API\n"); + if (new_index < 0) { + DEBUG_AUDIO ("Portaudio: Error setting host API\n"); + return false; } + _host_api_index = new_index; + _host_api_name = host_api_name; + return true; } 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(); - if (count < 0) return -1; + if (count < 0) { + DEBUG_AUDIO ("Host API count < 0\n"); + return -1; + } for (int i = 0; i < count; ++i) { const PaHostApiInfo* info = Pa_GetHostApiInfo (i); - if (info->name != NULL) { // possible? - if (name == info->name) return i; + if (info != NULL && info->name != NULL) { // possible? + if (name == info->name) { + return i; + } } } + DEBUG_AUDIO (string_compose ("Unable to get host API from name: %1\n", name)); + return -1; } 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; @@ -212,7 +459,7 @@ PortAudioIO::get_default_input_device () } 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; @@ -233,6 +480,15 @@ PortAudioIO::clear_device_lists () _output_devices.clear(); } +void +PortAudioIO::add_none_devices () +{ + _input_devices.insert(std::pair( + DeviceNone, new paDevice(AudioBackend::get_standard_device_name(AudioBackend::DeviceNone), 0, 0))); + _output_devices.insert(std::pair( + DeviceNone, new paDevice(AudioBackend::get_standard_device_name(AudioBackend::DeviceNone), 0, 0))); +} + void PortAudioIO::add_default_devices () { @@ -242,13 +498,13 @@ PortAudioIO::add_default_devices () const PaDeviceInfo* nfo_i = Pa_GetDeviceInfo(get_default_input_device()); const PaDeviceInfo* nfo_o = Pa_GetDeviceInfo(get_default_output_device()); if (nfo_i && nfo_o) { - _input_devices.insert (std::pair (-1, - new paDevice("Default", + _input_devices.insert (std::pair (DeviceDefault, + new paDevice(AudioBackend::get_standard_device_name(AudioBackend::DeviceDefault), nfo_i->maxInputChannels, nfo_o->maxOutputChannels ))); - _output_devices.insert (std::pair (-1, - new paDevice("Default", + _output_devices.insert (std::pair (DeviceDefault, + new paDevice(AudioBackend::get_standard_device_name(AudioBackend::DeviceDefault), nfo_i->maxInputChannels, nfo_o->maxOutputChannels ))); @@ -262,26 +518,28 @@ PortAudioIO::add_devices () if (info == NULL) return; int n_devices = Pa_GetDeviceCount(); -#ifndef NDEBUG - printf("PortAudio %d devices found:\n", n_devices); -#endif + + DEBUG_AUDIO (string_compose ("PortAudio found %1 devices\n", n_devices)); for (int i = 0 ; i < n_devices; ++i) { const PaDeviceInfo* nfo = Pa_GetDeviceInfo(i); if (!nfo) continue; if (nfo->hostApi != _host_api_index) continue; -#ifndef NDEBUG - printf(" (%d) '%s' '%s' in: %d (lat: %.1f .. %.1f) out: %d (lat: %.1f .. %.1f) sr:%.2f\n", - i, info->name, nfo->name, - nfo->maxInputChannels, - nfo->defaultLowInputLatency * 1e3, - nfo->defaultHighInputLatency * 1e3, - nfo->maxOutputChannels, - nfo->defaultLowOutputLatency * 1e3, - nfo->defaultHighOutputLatency * 1e3, - nfo->defaultSampleRate); -#endif + + DEBUG_AUDIO (string_compose (" (%1) '%2' '%3' in: %4 (lat: %5 .. %6) out: %7 " + "(lat: %8 .. %9) sr:%10\n", + i, + info->name, + nfo->name, + nfo->maxInputChannels, + nfo->defaultLowInputLatency * 1e3, + nfo->defaultHighInputLatency * 1e3, + nfo->maxOutputChannels, + nfo->defaultLowOutputLatency * 1e3, + nfo->defaultHighOutputLatency * 1e3, + nfo->defaultSampleRate)); + if ( nfo->maxInputChannels == 0 && nfo->maxOutputChannels == 0) { continue; } @@ -303,223 +561,309 @@ PortAudioIO::add_devices () } } -void -PortAudioIO::discover() +bool +PortAudioIO::update_devices() { - 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_default_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; - - // TODO error reporting sans fprintf() + 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; - const PaDeviceInfo *nfo_out; - const PaStreamInfo *nfo_s; - - if (!initialize_pa()) { - fprintf(stderr, "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 == -1) { + if (device_input == DeviceDefault) { device_input = get_default_input_device (); } - if (device_output == -1) { - device_output = get_default_output_device (); + + if (device_input == DeviceNone) { + return false; } - _capture_channels = 0; - _playback_channels = 0; - _cur_sample_rate = 0; - _cur_input_latency = 0; - _cur_output_latency = 0; + nfo_in = Pa_GetDeviceInfo(device_input); -#ifndef NDEBUG - printf("PortAudio Device IDs: i:%d o:%d\n", device_input, device_output); + if (nfo_in == NULL) { + DEBUG_AUDIO ("PortAudio Cannot Query Input Device Info\n"); + return false; + } + + 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; - nfo_in = Pa_GetDeviceInfo(device_input); - nfo_out = Pa_GetDeviceInfo(device_output); + return true; +} + +bool +PortAudioIO::get_output_stream_params(int device_output, + PaStreamParameters& outputParam) const +{ + const PaDeviceInfo *nfo_out = NULL; - if (!nfo_in && ! nfo_out) { - fprintf(stderr, "PortAudio Cannot Query Device Info\n"); - goto error; + if (device_output == DeviceDefault) { + device_output = get_default_output_device (); } - if (nfo_in) { - _capture_channels = nfo_in->maxInputChannels; + if (device_output == DeviceNone) { + return false; } - if (nfo_out) { - _playback_channels = nfo_out->maxOutputChannels; + + nfo_out = Pa_GetDeviceInfo(device_output); + + if (nfo_out == NULL) { + DEBUG_AUDIO ("PortAudio Cannot Query Output Device Info\n"); + return false; } - if(_capture_channels == 0 && _playback_channels == 0) { - fprintf(stderr, "PortAudio no Input and no output channels.\n"); - goto error; + 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; } + reset_stream_dependents (); -#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) { - printf("Adjusted capture channes to power of two (portaudio rb bug)\n"); - _capture_channels = lower_power_of_two (_capture_channels); + 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 ((_playback_channels & (_playback_channels - 1)) != 0) { - printf("Adjusted capture channes to power of two (portaudio rb bug)\n"); - _playback_channels = lower_power_of_two (_playback_channels); + if (get_input_stream_params(device_input, inputParam)) { + _capture_channels = inputParam.channelCount; } -#endif - -#ifndef NDEBUG - printf("PortAudio Channels: in:%d out:%d\n", - _capture_channels, _playback_channels); -#endif + if (get_output_stream_params(device_output, outputParam)) { + _playback_channels = outputParam.channelCount; + } + + if (_capture_channels == 0 && _playback_channels == 0) { + DEBUG_AUDIO("PortAudio no input or output channels.\n"); + return paBadIODeviceCombination; + } + + 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(string_compose("PortAudio failed to open stream %1\n", + Pa_GetErrorText(err))); + 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, _playback_channels > 0 ? &outputParam: NULL, sample_rate, samples_per_period, - paClipOff | paDitherOff, + paDitherOff, NULL, NULL); if (err != paNoError) { - fprintf(stderr, "PortAudio failed to start stream.\n"); - goto error; - } - - nfo_s = Pa_GetStreamInfo (_stream); - if (!nfo_s) { - fprintf(stderr, "PortAudio failed to query stream information.\n"); - pcm_stop(); - goto error; + DEBUG_AUDIO(string_compose("PortAudio failed to open stream %1\n", + Pa_GetErrorText(err))); + return (PaErrorCode)err; } - _cur_sample_rate = nfo_s->sampleRate; - _cur_input_latency = nfo_s->inputLatency * _cur_sample_rate; - _cur_output_latency = nfo_s->outputLatency * _cur_sample_rate; - -#ifndef NDEBUG - printf("PA Sample Rate %.1f SPS\n", _cur_sample_rate); - printf("PA Input Latency %.1fms %d spl\n", 1e3 * nfo_s->inputLatency, _cur_input_latency); - printf("PA Output Latency %.1fms %d spl\n", 1e3 * nfo_s->outputLatency, _cur_output_latency); -#endif - - _state = 0; - - if (_capture_channels > 0) { - _input_buffer = (float*) malloc (samples_per_period * _capture_channels * sizeof(float)); - if (!_input_buffer) { - fprintf(stderr, "PortAudio failed to allocate input buffer.\n"); - pcm_stop(); - goto error; - } + if (!set_sample_rate_and_latency_from_stream()) { + DEBUG_AUDIO ("PortAudio failed to query stream information.\n"); + close_stream(); + return paInternalError; } - if (_playback_channels > 0) { - _output_buffer = (float*) calloc (samples_per_period * _playback_channels, sizeof(float)); - if (!_output_buffer) { - fprintf(stderr, "PortAudio failed to allocate output buffer.\n"); - pcm_stop(); - goto error; - } + if (!allocate_buffers_for_blocking_api(samples_per_period)) { + close_stream(); + return paInternalError; } - - 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 @@ -554,6 +898,47 @@ PortAudioIO::next_cycle (uint32_t n_samples) return xrun ? 1 : 0; } +std::string +PortAudioIO::get_input_channel_name (int device_id, uint32_t channel) const +{ +#ifdef WITH_ASIO + const char* channel_name; + + // This will return an error for non-ASIO devices so no need to check if + // the device_id corresponds to an ASIO device. + PaError err = PaAsio_GetInputChannelName (device_id, channel, &channel_name); + + if (err == paNoError) { + DEBUG_AUDIO ( + string_compose ("Input channel name for device %1, channel %2 is %3\n", + device_id, + channel, + channel_name)); + return channel_name; + } +#endif + return std::string(); +} + +std::string +PortAudioIO::get_output_channel_name (int device_id, uint32_t channel) const +{ +#ifdef WITH_ASIO + const char* channel_name; + + PaError err = PaAsio_GetOutputChannelName (device_id, channel, &channel_name); + + if (err == paNoError) { + DEBUG_AUDIO ( + string_compose ("Output channel name for device %1, channel %2 is %3\n", + device_id, + channel, + channel_name)); + return channel_name; + } +#endif + return std::string(); +} #ifdef INTERLEAVED_INPUT