From b943cec756fefab7f840b39c7b9afb86b7388526 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Fri, 10 Apr 2020 04:39:58 +0200 Subject: ALSA: allow to select different I/O devices This adds a basic support to use multiple sound-cards, currently limited to two devices: In/Out with shared settings. Advanced setups still have to resort to using the ARDOUR_ALSA_EXT environment variable --- libs/backends/alsa/alsa_audiobackend.cc | 73 +++++++++++++++++++++++---------- libs/backends/alsa/alsa_audiobackend.h | 23 +++++++---- 2 files changed, 66 insertions(+), 30 deletions(-) (limited to 'libs/backends') diff --git a/libs/backends/alsa/alsa_audiobackend.cc b/libs/backends/alsa/alsa_audiobackend.cc index 2efe2e8976..d8ceb7f796 100644 --- a/libs/backends/alsa/alsa_audiobackend.cc +++ b/libs/backends/alsa/alsa_audiobackend.cc @@ -782,10 +782,22 @@ AlsaAudioBackend::_start (bool for_latency_measurement) return AudioDeviceInvalidError; } + std::string slave_device; + AudioSlave::DuplexMode slave_duplex = AudioSlave::FullDuplex; + if (_input_audio_device != _output_audio_device) { if (_input_audio_device != get_standard_device_name(DeviceNone) && _output_audio_device != get_standard_device_name(DeviceNone)) { - PBD::error << _("AlsaAudioBackend: Cannot use two different devices."); - return AudioDeviceInvalidError; +#if 0 /* ideally we'd resample output ...*/ + slave_device = _output_audio_device; + _output_audio_device = get_standard_device_name(DeviceNone); + slave_duplex = AudioSlave::HalfDuplexOut; +#else + /*.. but input is usually a cheap USB device, and keeping + * output does auto-connect master-out to the main device. */ + slave_device = _input_audio_device; + _input_audio_device = get_standard_device_name(DeviceNone); + slave_duplex = AudioSlave::HalfDuplexIn; +#endif } if (_input_audio_device != get_standard_device_name(DeviceNone)) { get_alsa_audio_device_names(devices, HalfDuplexIn); @@ -802,15 +814,14 @@ AlsaAudioBackend::_start (bool for_latency_measurement) duplex = 3; } - for (std::map::const_iterator i = devices.begin (); i != devices.end(); ++i) { - if (i->first == audio_device) { - alsa_device = i->second; - break; - } - } - if (alsa_device == "") { + std::map::const_iterator di = devices.find (audio_device); + + if (di == devices.end ()) { PBD::error << _("AlsaAudioBackend: Cannot find configured device. Is it still connected?"); return AudioDeviceNotAvailableError; + } else { + alsa_device = di->second; + assert (!alsa_device.empty()); } _device_reservation.acquire_device(alsa_device.c_str()); @@ -954,26 +965,43 @@ AlsaAudioBackend::_start (bool for_latency_measurement) _midi_device_thread_active = listen_for_midi_device_changes (); + if (!slave_device.empty () && (di = devices.find (slave_device)) != devices.end ()) { + std::string dev = di->second; + if (add_slave (dev.c_str(), _samplerate, _samples_per_period, _periods_per_cycle, slave_duplex)) { + PBD::info << string_compose (_("ALSA slave '%1' added"), dev) << endmsg; + } else { + PBD::error << string_compose (_("ALSA failed to add '%1' as slave"), dev) << endmsg; + } + } + #if 1 // TODO: we need a GUI (and API) for this + /* example: ARDOUR_ALSA_EXT="hw:2@48000/512*3;hw:3@44100" */ if (NULL != getenv ("ARDOUR_ALSA_EXT")) { boost::char_separator sep (";"); - boost::tokenizer > devs (std::string(getenv ("ARDOUR_ALSA_EXT")), sep); + std::string ext (getenv ("ARDOUR_ALSA_EXT")); + boost::tokenizer > devs (ext, sep); BOOST_FOREACH (const std::string& tmp, devs) { std::string dev (tmp); - std::string::size_type n = dev.find ('@'); unsigned int sr = _samplerate; unsigned int spp = _samples_per_period; - unsigned int duplex = 3; // TODO parse 1: play, 2: capt, 3:both + unsigned int ppc = _periods_per_cycle; + AudioSlave::DuplexMode duplex = AudioSlave::FullDuplex; + std::string::size_type n = dev.find ('@'); if (n != std::string::npos) { - std::string opt (dev.substr (n + 1)); + std::string const opt (dev.substr (n + 1)); sr = PBD::atoi (opt); dev = dev.substr (0, n); std::string::size_type n = opt.find ('/'); if (n != std::string::npos) { - spp = PBD::atoi (opt.substr (n + 1)); + std::string const opt2 (opt.substr (n + 1)); + spp = PBD::atoi (opt2); + std::string::size_type n = opt2.find ('*'); + if (n != std::string::npos) { + ppc = PBD::atoi (opt2.substr (n + 1)); + } } } - if (add_slave (dev.c_str(), sr, spp, duplex)) { + if (add_slave (dev.c_str(), sr, spp, ppc, duplex)) { PBD::info << string_compose (_("ALSA slave '%1' added"), dev) << endmsg; } else { PBD::error << string_compose (_("ALSA failed to add '%1' as slave"), dev) << endmsg; @@ -2007,11 +2035,12 @@ bool AlsaAudioBackend::add_slave (const char* device, unsigned int slave_rate, unsigned int slave_spp, - unsigned int duplex) + unsigned int slave_ppc, + AudioSlave::DuplexMode duplex) { AudioSlave* s = new AudioSlave (device, duplex, _samplerate, _samples_per_period, - slave_rate, slave_spp, 2); + slave_rate, slave_spp, slave_ppc); if (s->state ()) { // TODO parse error status @@ -2064,18 +2093,18 @@ errout: AlsaAudioBackend::AudioSlave::AudioSlave ( const char* device, - unsigned int duplex, + DuplexMode duplex, unsigned int master_rate, unsigned int master_samples_per_period, unsigned int slave_rate, unsigned int slave_samples_per_period, - unsigned int periods_per_cycle) + unsigned int slave_periods_per_cycle) : AlsaDeviceReservation (device) , AlsaAudioSlave ( - (duplex & 1) ? device : NULL /* playback */, - (duplex & 2) ? device : NULL /* capture */, + (duplex & HalfDuplexOut) ? device : NULL /* playback */, + (duplex & HalfDuplexIn) ? device : NULL /* capture */, master_rate, master_samples_per_period, - slave_rate, slave_samples_per_period, periods_per_cycle) + slave_rate, slave_samples_per_period, slave_periods_per_cycle) , active (false) , halt (false) , dead (false) diff --git a/libs/backends/alsa/alsa_audiobackend.h b/libs/backends/alsa/alsa_audiobackend.h index 49afd7e34e..02d61c6042 100644 --- a/libs/backends/alsa/alsa_audiobackend.h +++ b/libs/backends/alsa/alsa_audiobackend.h @@ -130,7 +130,7 @@ class AlsaAudioBackend : public AudioBackend, public PortEngineSharedImpl bool is_realtime () const; bool use_separate_input_and_output_devices () const { return true; } - bool match_input_output_devices_or_none () const { return true; } + bool match_input_output_devices_or_none () const { return false; } bool can_set_period_size () const { return true; } std::vector enumerate_devices () const; @@ -399,17 +399,17 @@ class AlsaAudioBackend : public AudioBackend, public PortEngineSharedImpl void update_systemic_audio_latencies (); void update_systemic_midi_latencies (); - /* additional re-sampled I/O */ - bool add_slave (const char* slave_device, - unsigned int slave_rate, - unsigned int slave_spp, - unsigned int duplex = 3); - class AudioSlave : public AlsaDeviceReservation, public AlsaAudioSlave { public: + enum DuplexMode { + HalfDuplexIn = 1, + HalfDuplexOut = 2, + FullDuplex = 3 + }; + AudioSlave ( const char* device, - unsigned int duplex, + DuplexMode duplex, unsigned int master_rate, unsigned int master_samples_per_period, unsigned int slave_rate, @@ -436,6 +436,13 @@ class AlsaAudioBackend : public AudioBackend, public PortEngineSharedImpl void halted (); }; + /* additional re-sampled I/O */ + bool add_slave (const char* slave_device, + unsigned int slave_rate, + unsigned int slave_spp, + unsigned int slave_ppc, + AudioSlave::DuplexMode); + typedef std::vector AudioSlaves; AudioSlaves _slaves; -- cgit v1.2.3