]> git.sesse.net Git - nageru/commitdiff
Refactor ALSA code in preparation for hotplug.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Thu, 8 Sep 2016 18:09:04 +0000 (20:09 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 19 Oct 2016 22:55:44 +0000 (00:55 +0200)
In particular, all the inputs are not owned by a class ALSAPool,
which also takes over enumeration. There's a new state machine
(only in preparation, not actually used yet), and some policy
has been defined for what to do when cards go in and out.

alsa_input.cpp
alsa_input.h
audio_mixer.cpp
audio_mixer.h

index 87adb4ab2427c705a1a1682b9fad24ba7e309abf..4e507f39cb374de0360d1beb6eeb5a06345b98e9 100644 (file)
@@ -1,7 +1,49 @@
-
 #include "alsa_input.h"
+#include "audio_mixer.h"
+#include "defs.h"
+
+#include <sys/inotify.h>
+
+#include <functional>
+#include <unordered_map>
 
 using namespace std;
+using namespace std::placeholders;
+
+namespace {
+
+bool set_base_params(const char *device_name, snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hw_params, unsigned *sample_rate)
+{
+       int err;
+       err = snd_pcm_hw_params_any(pcm_handle, hw_params);
+       if (err < 0) {
+               fprintf(stderr, "[%s] snd_pcm_hw_params_any(): %s\n", device_name, snd_strerror(err));
+               return false;
+       }
+       err = snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+       if (err < 0) {
+               fprintf(stderr, "[%s] snd_pcm_hw_params_set_access(): %s\n", device_name, snd_strerror(err));
+               return false;
+       }
+       snd_pcm_format_mask_t *format_mask;
+       snd_pcm_format_mask_alloca(&format_mask);
+       snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S16_LE);
+       snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S24_LE);
+       snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S32_LE);
+       err = snd_pcm_hw_params_set_format_mask(pcm_handle, hw_params, format_mask);
+       if (err < 0) {
+               fprintf(stderr, "[%s] snd_pcm_hw_params_set_format_mask(): %s\n", device_name, snd_strerror(err));
+               return false;
+       }
+       err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, sample_rate, 0);
+       if (err < 0) {
+               fprintf(stderr, "[%s] snd_pcm_hw_params_set_rate_near(): %s\n", device_name, snd_strerror(err));
+               return false;
+       }
+       return true;
+}
+
+}  // namespace
 
 ALSAInput::ALSAInput(const char *device, unsigned sample_rate, unsigned num_channels, audio_callback_t audio_callback)
        : device(device), sample_rate(sample_rate), num_channels(num_channels), audio_callback(audio_callback)
@@ -140,41 +182,42 @@ void ALSAInput::die_on_error(const char *func_name, int err)
        }
 }
 
-bool ALSAInput::set_base_params(const char *device_name, snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hw_params, unsigned *sample_rate)
+ALSAPool::~ALSAPool()
 {
-       int err;
-       err = snd_pcm_hw_params_any(pcm_handle, hw_params);
-       if (err < 0) {
-               fprintf(stderr, "[%s] snd_pcm_hw_params_any(): %s\n", device_name, snd_strerror(err));
-               return false;
-       }
-       err = snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
-       if (err < 0) {
-               fprintf(stderr, "[%s] snd_pcm_hw_params_set_access(): %s\n", device_name, snd_strerror(err));
-               return false;
-       }
-       snd_pcm_format_mask_t *format_mask;
-       snd_pcm_format_mask_alloca(&format_mask);
-       snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S16_LE);
-       snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S24_LE);
-       snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S32_LE);
-       err = snd_pcm_hw_params_set_format_mask(pcm_handle, hw_params, format_mask);
-       if (err < 0) {
-               fprintf(stderr, "[%s] snd_pcm_hw_params_set_format_mask(): %s\n", device_name, snd_strerror(err));
-               return false;
+       for (Device &device : devices) {
+               if (device.input != nullptr) {
+                       device.input->stop_capture_thread();
+                       delete device.input;
+               }
        }
-       err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, sample_rate, 0);
-       if (err < 0) {
-               fprintf(stderr, "[%s] snd_pcm_hw_params_set_rate_near(): %s\n", device_name, snd_strerror(err));
-               return false;
+}
+
+std::vector<ALSAPool::Device> ALSAPool::get_devices()
+{
+       lock_guard<mutex> lock(mu);
+       for (Device &device : devices) {
+               device.held = true;
        }
-       return true;
+       return devices;
 }
 
-vector<ALSAInput::Device> ALSAInput::enumerate_devices()
+void ALSAPool::hold_device(unsigned index)
 {
-       vector<Device> ret;
+       lock_guard<mutex> lock(mu);
+       assert(index < devices.size());
+       devices[index].held = true;
+}
 
+void ALSAPool::release_device(unsigned index)
+{
+       lock_guard<mutex> lock(mu);
+       if (index < devices.size()) {
+               devices[index].held = false;
+       }
+}
+
+void ALSAPool::enumerate_devices()
+{
        // Enumerate all cards.
        for (int card_index = -1; snd_card_next(&card_index) == 0 && card_index >= 0; ) {
                char address[256];
@@ -186,79 +229,127 @@ vector<ALSAInput::Device> ALSAInput::enumerate_devices()
                        printf("%s: %s\n", address, snd_strerror(err));
                        continue;
                }
-               snd_ctl_card_info_t *card_info;
-               snd_ctl_card_info_alloca(&card_info);
-               snd_ctl_card_info(ctl, card_info);
-
-               string card_name = snd_ctl_card_info_get_name(card_info);
+               unique_ptr<snd_ctl_t, decltype(snd_ctl_close)*> ctl_closer(ctl, snd_ctl_close);
 
                // Enumerate all devices on this card.
                for (int dev_index = -1; snd_ctl_pcm_next_device(ctl, &dev_index) == 0 && dev_index >= 0; ) {
-                       snd_pcm_info_t *pcm_info;
-                       snd_pcm_info_alloca(&pcm_info);
-                       snd_pcm_info_set_device(pcm_info, dev_index);
-                       snd_pcm_info_set_subdevice(pcm_info, 0);
-                       snd_pcm_info_set_stream(pcm_info, SND_PCM_STREAM_CAPTURE);
-                       if (snd_ctl_pcm_info(ctl, pcm_info) < 0) {
-                               // Not available for capture.
-                               continue;
-                       }
-
-                       snprintf(address, sizeof(address), "hw:%d,%d", card_index, dev_index);
-
-                       unsigned num_channels = 0;
-
-                       // Find all channel maps for this device, and pick out the one
-                       // with the most channels.
-                       snd_pcm_chmap_query_t **cmaps = snd_pcm_query_chmaps_from_hw(card_index, dev_index, 0, SND_PCM_STREAM_CAPTURE);
-                       if (cmaps != nullptr) {
-                               for (snd_pcm_chmap_query_t **ptr = cmaps; *ptr; ++ptr) {
-                                       num_channels = max(num_channels, (*ptr)->map.channels);
-                               }
-                               snd_pcm_free_chmaps(cmaps);
-                       }
-                       if (num_channels == 0) {
-                               // Device had no channel maps. We need to open it to query.
-                               snd_pcm_t *pcm_handle;
-                               int err = snd_pcm_open(&pcm_handle, address, SND_PCM_STREAM_CAPTURE, 0);
-                               if (err < 0) {
-                                       // TODO: When we go to hotplug support, we should support some
-                                       // retry here, as the device could legitimately be busy.
-                                       printf("%s: %s\n", address, snd_strerror(err));
-                                       continue;
-                               }
-                               snd_pcm_hw_params_t *hw_params;
-                               snd_pcm_hw_params_alloca(&hw_params);
-                               unsigned sample_rate;
-                               if (!set_base_params(address, pcm_handle, hw_params, &sample_rate)) {
-                                       snd_pcm_close(pcm_handle);
-                                       continue;
-                               }
-                               err = snd_pcm_hw_params_get_channels_max(hw_params, &num_channels);
-                               if (err < 0) {
-                                       fprintf(stderr, "[%s] snd_pcm_hw_params_get_channels_max(): %s\n",
-                                               address, snd_strerror(err));
-                                       snd_pcm_close(pcm_handle);
-                                       continue;
-                               }
-                               snd_pcm_close(pcm_handle);
-                       }
-
-                       if (num_channels == 0) {
-                               printf("%s: No channel maps with channels\n", address);
-                               continue;
-                       }
-
-                       Device dev;
-                       dev.address = address;
-                       dev.name = card_name;
-                       dev.info = snd_pcm_info_get_name(pcm_info);
-                       dev.num_channels = num_channels;
-
-                       ret.push_back(dev);
+                       add_device(card_index, dev_index);
                }
-               snd_ctl_close(ctl);
        }
+}
+
+bool ALSAPool::add_device(unsigned card_index, unsigned dev_index)
+{
+       char address[256];
+       snprintf(address, sizeof(address), "hw:%d", card_index);
+       snd_ctl_t *ctl;
+       int err = snd_ctl_open(&ctl, address, 0);
+       if (err < 0) {
+               printf("%s: %s\n", address, snd_strerror(err));
+               return false;
+       }
+       unique_ptr<snd_ctl_t, decltype(snd_ctl_close)*> ctl_closer(ctl, snd_ctl_close);
+
+       snd_pcm_info_t *pcm_info;
+       snd_pcm_info_alloca(&pcm_info);
+       snd_pcm_info_set_device(pcm_info, dev_index);
+       snd_pcm_info_set_subdevice(pcm_info, 0);
+       snd_pcm_info_set_stream(pcm_info, SND_PCM_STREAM_CAPTURE);
+       if (snd_ctl_pcm_info(ctl, pcm_info) < 0) {
+               // Not available for capture.
+               return false;
+       }
+
+       snprintf(address, sizeof(address), "hw:%d,%d", card_index, dev_index);
+
+       unsigned num_channels = 0;
 
-       return ret;
+       // Find all channel maps for this device, and pick out the one
+       // with the most channels.
+       snd_pcm_chmap_query_t **cmaps = snd_pcm_query_chmaps_from_hw(card_index, dev_index, 0, SND_PCM_STREAM_CAPTURE);
+       if (cmaps != nullptr) {
+               for (snd_pcm_chmap_query_t **ptr = cmaps; *ptr; ++ptr) {
+                       num_channels = max(num_channels, (*ptr)->map.channels);
+               }
+               snd_pcm_free_chmaps(cmaps);
+       }
+       if (num_channels == 0) {
+               // Device had no channel maps. We need to open it to query.
+               // TODO: Do this asynchronously.
+               snd_pcm_t *pcm_handle;
+               int err = snd_pcm_open(&pcm_handle, address, SND_PCM_STREAM_CAPTURE, 0);
+               if (err < 0) {
+                       // TODO: When we go to hotplug support, we should support some
+                       // retry here, as the device could legitimately be busy.
+                       printf("%s: %s\n", address, snd_strerror(err));
+                       return false;
+               }
+               snd_pcm_hw_params_t *hw_params;
+               snd_pcm_hw_params_alloca(&hw_params);
+               unsigned sample_rate;
+               if (!set_base_params(address, pcm_handle, hw_params, &sample_rate)) {
+                       snd_pcm_close(pcm_handle);
+                       return false;
+               }
+               err = snd_pcm_hw_params_get_channels_max(hw_params, &num_channels);
+               if (err < 0) {
+                       fprintf(stderr, "[%s] snd_pcm_hw_params_get_channels_max(): %s\n",
+                               address, snd_strerror(err));
+                       snd_pcm_close(pcm_handle);
+                       return false;
+               }
+               snd_pcm_close(pcm_handle);
+       }
+
+       if (num_channels == 0) {
+               printf("%s: No channel maps with channels\n", address);
+               return true;
+       }
+
+       snd_ctl_card_info_t *card_info;
+       snd_ctl_card_info_alloca(&card_info);
+       snd_ctl_card_info(ctl, card_info);
+
+       Device dev;
+       dev.address = address;
+       dev.name = snd_ctl_card_info_get_name(card_info);
+       dev.info = snd_pcm_info_get_name(pcm_info);
+       dev.num_channels = num_channels;
+       dev.state = Device::State::RUNNING;
+
+       lock_guard<mutex> lock(mu);
+       devices.push_back(std::move(dev));
+       return true;
+}
+
+void ALSAPool::init()
+{
+       enumerate_devices();
+}
+
+void ALSAPool::reset_device(unsigned index)
+{
+       lock_guard<mutex> lock(mu);
+       Device *device = &devices[index];
+       if (inputs[index] != nullptr) {
+               inputs[index]->stop_capture_thread();
+       }
+       if (!device->held) {
+               inputs[index].reset();
+       } else {
+               // TODO: Put on a background thread instead of locking?
+               inputs[index].reset(new ALSAInput(device->address.c_str(), OUTPUT_FREQUENCY, device->num_channels, bind(&AudioMixer::add_audio, global_audio_mixer, DeviceSpec{InputSourceType::ALSA_INPUT, index}, _1, _2, _3, _4)));
+               inputs[index]->start_capture_thread();
+       }
+       device->input = inputs[index].get();
+}
+
+unsigned ALSAPool::get_capture_frequency(unsigned index)
+{
+       lock_guard<mutex> lock(mu);
+       assert(devices[index].held);
+       if (devices[index].input)
+               return devices[index].input->get_sample_rate();
+       else
+               return OUTPUT_FREQUENCY;
 }
index 84e46b0887707dd6b273419d29cf2fada00c43d7..d3768c5d109ed3921452641b2d75df09ba7633c4 100644 (file)
@@ -34,19 +34,10 @@ public:
        void start_capture_thread();
        void stop_capture_thread();
 
-       // TODO: Worry about hotplug.
-       struct Device {
-               std::string address;  // E.g. “hw:0,0”.
-               std::string name, info;
-               unsigned num_channels;
-       };
-       static std::vector<Device> enumerate_devices();
-
 private:
        void capture_thread_func();
        int64_t frames_to_pts(uint64_t n) const;
        void die_on_error(const char *func_name, int err);
-       static bool set_base_params(const char *device, snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hw_params, unsigned *sample_rate);
 
        std::string device;
        unsigned sample_rate, num_channels, num_periods;
@@ -61,4 +52,88 @@ private:
        std::unique_ptr<uint8_t[]> buffer;
 };
 
+// The class dealing with the collective of all ALSA cards in the system.
+// In particular, it deals with enumeration of cards, and hotplug of new ones.
+class ALSAPool {
+public:
+       ~ALSAPool();
+
+       struct Device {
+               enum class State {
+                       // There is no card here. (There probably used to be one,
+                       // but it got removed.) We don't insert a card before
+                       // we've actually probed it, ie., we know whether it
+                       // can be captured from at all, and what its name is.
+                       EMPTY,
+
+                       // This card is ready for capture, as far as we know.
+                       // (It could still be used by someone else; we don't know
+                       // until we try to open it.)
+                       READY,
+
+                       // We are trying to start capture from this card, but we are not
+                       // streaming yet. Note that this could in theory go on forever,
+                       // if the card is in use by some other process; in the UI,
+                       // we will show this state as “(busy)”.
+                       STARTING,
+
+                       // The card is capturing and sending data. If there's a fatal error,
+                       // it could go back to STARTING, or it could go to DEAD
+                       // (depending on the error).
+                       RUNNING,
+
+                       // The card is gone (e.g., unplugged). However, since there's
+                       // still a bus using it, we can't just remove the entry.
+                       // If the card comes back (ie., a new card is plugged in,
+                       // and we believe it has the same configuration), it could be
+                       // installed in place of this card, and then presumably be put
+                       // back into STARTING or RUNNING.
+                       DEAD
+               } state;
+
+               std::string address;  // E.g. “hw:0,0”.
+               std::string name, info;
+               unsigned num_channels;
+               ALSAInput *input = nullptr;  // nullptr iff EMPTY or DEAD.
+
+               // Whether the AudioMixer is interested in this card or not.
+               // “Interested” could mean either of two things: Either it is part of
+               // a bus mapping, or it is in the process of enumerating devices
+               // (to show to the user). A card that is _not_ held can disappear
+               // at any given time as a result of an error or hotplug event;
+               // a card that is held will go to the DEAD state instead.
+               bool held = false;
+       };
+
+       void init();
+
+       // Get the list of all current devices. Note that this will implicitly mark
+       // all of the returned devices as held, since the input mapping UI needs
+       // some kind of stability when the user is to choose. Thus, when you are done
+       // with the list and have set a new mapping, you must go through all the devices
+       // you don't want and release them using release_device().
+       std::vector<Device> get_devices();
+
+       void hold_device(unsigned index);
+       void release_device(unsigned index);  // Note: index is allowed to go out of bounds.
+
+       // If device is held, start or restart capture. If device is not held,
+       // stop capture if it isn't already.
+       void reset_device(unsigned index);
+
+       // Note: The card must be held. Returns OUTPUT_FREQUENCY if the card is in EMPTY or DEAD.
+       unsigned get_capture_frequency(unsigned index);
+
+       // TODO: Add accessors and/or callbacks about changed state, so that
+       // the UI actually stands a chance in using that information.
+
+private:
+       mutable std::mutex mu;
+       std::vector<Device> devices;  // Under mu.
+       std::vector<std::unique_ptr<ALSAInput>> inputs;  // Under mu, corresponds 1:1 to devices.
+
+       void enumerate_devices();
+       bool add_device(unsigned card_index, unsigned dev_index);
+};
+
 #endif  // !defined(_ALSA_INPUT_H)
index e28acfeb00154d2926e1f00c9fa26382c511aae8..8485d0a6343fec00a97fe2417dd46d7aac0bb248 100644 (file)
@@ -12,6 +12,7 @@
 
 #include "db.h"
 #include "flags.h"
+#include "mixer.h"
 #include "timebase.h"
 
 using namespace bmusb;
@@ -190,8 +191,7 @@ AudioMixer::AudioMixer(unsigned num_cards)
        new_input_mapping.buses.push_back(input);
        set_input_mapping(new_input_mapping);
 
-       // Look for ALSA cards.
-       available_alsa_cards = ALSAInput::enumerate_devices();
+       alsa_pool.init();
 
        r128.init(2, OUTPUT_FREQUENCY);
        r128.integr_start();
@@ -201,17 +201,6 @@ AudioMixer::AudioMixer(unsigned num_cards)
        peak_resampler.setup(OUTPUT_FREQUENCY, OUTPUT_FREQUENCY * 4, /*num_channels=*/2, /*hlen=*/16, /*frel=*/1.0);
 }
 
-AudioMixer::~AudioMixer()
-{
-       for (unsigned card_index = 0; card_index < available_alsa_cards.size(); ++card_index) {
-               const AudioDevice &device = alsa_inputs[card_index];
-               if (device.alsa_device != nullptr) {
-                       device.alsa_device->stop_capture_thread();
-               }
-       }
-}
-
-
 void AudioMixer::reset_resampler(DeviceSpec device_spec)
 {
        lock_guard<timed_mutex> lock(audio_mutex);
@@ -232,25 +221,6 @@ void AudioMixer::reset_resampler_mutex_held(DeviceSpec device_spec)
        device->next_local_pts = 0;
 }
 
-void AudioMixer::reset_alsa_mutex_held(DeviceSpec device_spec)
-{
-       assert(device_spec.type == InputSourceType::ALSA_INPUT);
-       unsigned card_index = device_spec.index;
-       AudioDevice *device = find_audio_device(device_spec);
-
-       if (device->alsa_device != nullptr) {
-               device->alsa_device->stop_capture_thread();
-       }
-       if (device->interesting_channels.empty()) {
-               device->alsa_device.reset();
-       } else {
-               const ALSAInput::Device &alsa_dev = available_alsa_cards[card_index];
-               device->alsa_device.reset(new ALSAInput(alsa_dev.address.c_str(), OUTPUT_FREQUENCY, alsa_dev.num_channels, bind(&AudioMixer::add_audio, this, device_spec, _1, _2, _3, _4)));
-               device->capture_frequency = device->alsa_device->get_sample_rate();
-               device->alsa_device->start_capture_thread();
-       }
-}
-
 bool AudioMixer::add_audio(DeviceSpec device_spec, const uint8_t *data, unsigned num_samples, AudioFormat audio_format, int64_t frame_length)
 {
        AudioDevice *device = find_audio_device(device_spec);
@@ -384,6 +354,24 @@ void AudioMixer::fill_audio_bus(const map<DeviceSpec, vector<float>> &samples_ca
        }
 }
 
+vector<DeviceSpec> AudioMixer::get_active_devices() const
+{
+       vector<DeviceSpec> ret;
+       for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
+               const DeviceSpec device_spec{InputSourceType::CAPTURE_CARD, card_index};
+               if (!find_audio_device(device_spec)->interesting_channels.empty()) {
+                       ret.push_back(device_spec);
+               }
+       }
+       for (unsigned card_index = 0; card_index < MAX_ALSA_CARDS; ++card_index) {
+               const DeviceSpec device_spec{InputSourceType::ALSA_INPUT, card_index};
+               if (!find_audio_device(device_spec)->interesting_channels.empty()) {
+                       ret.push_back(device_spec);
+               }
+       }
+       return ret;
+}
+
 vector<float> AudioMixer::get_output(double pts, unsigned num_samples, ResamplingQueue::RateAdjustmentPolicy rate_adjustment_policy)
 {
        map<DeviceSpec, vector<float>> samples_card;
@@ -392,20 +380,14 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
        lock_guard<timed_mutex> lock(audio_mutex);
 
        // Pick out all the interesting channels from all the cards.
-       // TODO: If the card has been hotswapped, the number of channels
-       // might have changed; if so, we need to do some sort of remapping
-       // to silence.
-       for (const auto &spec_and_info : get_devices_mutex_held()) {
-               const DeviceSpec &device_spec = spec_and_info.first;
+       for (const DeviceSpec &device_spec : get_active_devices()) {
                AudioDevice *device = find_audio_device(device_spec);
-               if (!device->interesting_channels.empty()) {
-                       samples_card[device_spec].resize(num_samples * device->interesting_channels.size());
-                       device->resampling_queue->get_output_samples(
-                               pts,
-                               &samples_card[device_spec][0],
-                               num_samples,
-                               rate_adjustment_policy);
-               }
+               samples_card[device_spec].resize(num_samples * device->interesting_channels.size());
+               device->resampling_queue->get_output_samples(
+                       pts,
+                       &samples_card[device_spec][0],
+                       num_samples,
+                       rate_adjustment_policy);
        }
 
        vector<float> samples_out, left, right;
@@ -730,14 +712,10 @@ void AudioMixer::send_audio_level_callback()
                correlation.get_correlation());
 }
 
-map<DeviceSpec, DeviceInfo> AudioMixer::get_devices() const
+map<DeviceSpec, DeviceInfo> AudioMixer::get_devices()
 {
        lock_guard<timed_mutex> lock(audio_mutex);
-       return get_devices_mutex_held();
-}
 
-map<DeviceSpec, DeviceInfo> AudioMixer::get_devices_mutex_held() const
-{
        map<DeviceSpec, DeviceInfo> devices;
        for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
                const DeviceSpec spec{ InputSourceType::CAPTURE_CARD, card_index };
@@ -747,9 +725,10 @@ map<DeviceSpec, DeviceInfo> AudioMixer::get_devices_mutex_held() const
                info.num_channels = 8;  // FIXME: This is wrong for fake cards.
                devices.insert(make_pair(spec, info));
        }
-       for (unsigned card_index = 0; card_index < available_alsa_cards.size(); ++card_index) {
+       vector<ALSAPool::Device> available_alsa_devices = alsa_pool.get_devices();
+       for (unsigned card_index = 0; card_index < available_alsa_devices.size(); ++card_index) {
                const DeviceSpec spec{ InputSourceType::ALSA_INPUT, card_index };
-               const ALSAInput::Device &device = available_alsa_cards[card_index];
+               const ALSAPool::Device &device = available_alsa_devices[card_index];
                DeviceInfo info;
                info.name = device.name + " (" + device.info + ")";
                info.num_channels = device.num_channels;
@@ -783,14 +762,25 @@ void AudioMixer::set_input_mapping(const InputMapping &new_input_mapping)
        }
 
        // Reset resamplers for all cards that don't have the exact same state as before.
-       for (const auto &spec_and_info : get_devices_mutex_held()) {
-               const DeviceSpec &device_spec = spec_and_info.first;
+       for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
+               const DeviceSpec device_spec{InputSourceType::CAPTURE_CARD, card_index};
                AudioDevice *device = find_audio_device(device_spec);
                if (device->interesting_channels != interesting_channels[device_spec]) {
                        device->interesting_channels = interesting_channels[device_spec];
-                       if (device_spec.type == InputSourceType::ALSA_INPUT) {
-                               reset_alsa_mutex_held(device_spec);
-                       }
+                       reset_resampler_mutex_held(device_spec);
+               }
+       }
+       for (unsigned card_index = 0; card_index < MAX_ALSA_CARDS; ++card_index) {
+               const DeviceSpec device_spec{InputSourceType::ALSA_INPUT, card_index};
+               AudioDevice *device = find_audio_device(device_spec);
+               if (interesting_channels[device_spec].empty()) {
+                       alsa_pool.release_device(card_index);
+               } else {
+                       alsa_pool.hold_device(card_index);
+               }
+               if (device->interesting_channels != interesting_channels[device_spec]) {
+                       device->interesting_channels = interesting_channels[device_spec];
+                       alsa_pool.reset_device(device_spec.index);
                        reset_resampler_mutex_held(device_spec);
                }
        }
index 8bb344f2b2a46b98b196a45ea35d72e8ee5fcd39..e5922674f1245382b2a0dd872ca6291b437d6def 100644 (file)
@@ -82,7 +82,6 @@ struct InputMapping {
 class AudioMixer {
 public:
        AudioMixer(unsigned num_cards);
-       ~AudioMixer();
        void reset_resampler(DeviceSpec device_spec);
        void reset_meters();
 
@@ -97,7 +96,12 @@ public:
        std::vector<float> get_output(double pts, unsigned num_samples, ResamplingQueue::RateAdjustmentPolicy rate_adjustment_policy);
 
        void set_fader_volume(unsigned bus_index, float level_db) { fader_volume_db[bus_index] = level_db; }
-       std::map<DeviceSpec, DeviceInfo> get_devices() const;
+
+       // Note: This operation holds all ALSA devices (see ALSAPool::get_devices()).
+       // You will need to call set_input_mapping() to get the hold state correctly,
+       // or every card will be held forever.
+       std::map<DeviceSpec, DeviceInfo> get_devices();
+
        void set_name(DeviceSpec device_spec, const std::string &name);
 
        void set_input_mapping(const InputMapping &input_mapping);
@@ -253,31 +257,32 @@ private:
                unsigned capture_frequency = OUTPUT_FREQUENCY;
                // Which channels we consider interesting (ie., are part of some input_mapping).
                std::set<unsigned> interesting_channels;
-               // Only used for ALSA cards, obviously.
-               std::unique_ptr<ALSAInput> alsa_device;
        };
+
+       const AudioDevice *find_audio_device(DeviceSpec device_spec) const
+       {
+               return const_cast<AudioMixer *>(this)->find_audio_device(device_spec);
+       }
+
        AudioDevice *find_audio_device(DeviceSpec device_spec);
 
        void find_sample_src_from_device(const std::map<DeviceSpec, std::vector<float>> &samples_card, DeviceSpec device_spec, int source_channel, const float **srcptr, unsigned *stride);
        void fill_audio_bus(const std::map<DeviceSpec, std::vector<float>> &samples_card, const InputMapping::Bus &bus, unsigned num_samples, float *output);
        void reset_resampler_mutex_held(DeviceSpec device_spec);
-       void reset_alsa_mutex_held(DeviceSpec device_spec);
-       std::map<DeviceSpec, DeviceInfo> get_devices_mutex_held() const;
        void apply_eq(unsigned bus_index, std::vector<float> *samples_bus);
        void update_meters(const std::vector<float> &samples);
        void add_bus_to_master(unsigned bus_index, const std::vector<float> &samples_bus, std::vector<float> *samples_out);
        void measure_bus_levels(unsigned bus_index, const std::vector<float> &left, const std::vector<float> &right);
        void send_audio_level_callback();
+       std::vector<DeviceSpec> get_active_devices() const;
 
        unsigned num_cards;
 
        mutable std::timed_mutex audio_mutex;
 
+       ALSAPool alsa_pool;
        AudioDevice video_cards[MAX_VIDEO_CARDS];  // Under audio_mutex.
-
-       // TODO: Figure out a better way to unify these two, as they are sharing indexing.
        AudioDevice alsa_inputs[MAX_ALSA_CARDS];  // Under audio_mutex.
-       std::vector<ALSAInput::Device> available_alsa_cards;
 
        std::atomic<float> locut_cutoff_hz{120};
        StereoFilter locut[MAX_BUSES];  // Default cutoff 120 Hz, 24 dB/oct.