From b590a9a091974607517a9f872e28cbfa65014e2a Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Thu, 8 Sep 2016 20:09:04 +0200 Subject: [PATCH] Refactor ALSA code in preparation for hotplug. 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 | 287 +++++++++++++++++++++++++++++++----------------- alsa_input.h | 93 ++++++++++++++-- audio_mixer.cpp | 104 ++++++++---------- audio_mixer.h | 23 ++-- 4 files changed, 334 insertions(+), 173 deletions(-) diff --git a/alsa_input.cpp b/alsa_input.cpp index 87adb4a..4e507f3 100644 --- a/alsa_input.cpp +++ b/alsa_input.cpp @@ -1,7 +1,49 @@ - #include "alsa_input.h" +#include "audio_mixer.h" +#include "defs.h" + +#include + +#include +#include 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::get_devices() +{ + lock_guard lock(mu); + for (Device &device : devices) { + device.held = true; } - return true; + return devices; } -vector ALSAInput::enumerate_devices() +void ALSAPool::hold_device(unsigned index) { - vector ret; + lock_guard lock(mu); + assert(index < devices.size()); + devices[index].held = true; +} +void ALSAPool::release_device(unsigned index) +{ + lock_guard 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::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 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 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 lock(mu); + devices.push_back(std::move(dev)); + return true; +} + +void ALSAPool::init() +{ + enumerate_devices(); +} + +void ALSAPool::reset_device(unsigned index) +{ + lock_guard 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 lock(mu); + assert(devices[index].held); + if (devices[index].input) + return devices[index].input->get_sample_rate(); + else + return OUTPUT_FREQUENCY; } diff --git a/alsa_input.h b/alsa_input.h index 84e46b0..d3768c5 100644 --- a/alsa_input.h +++ b/alsa_input.h @@ -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 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 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 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 devices; // Under mu. + std::vector> 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) diff --git a/audio_mixer.cpp b/audio_mixer.cpp index e28acfe..8485d0a 100644 --- a/audio_mixer.cpp +++ b/audio_mixer.cpp @@ -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 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> &samples_ca } } +vector AudioMixer::get_active_devices() const +{ + vector 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 AudioMixer::get_output(double pts, unsigned num_samples, ResamplingQueue::RateAdjustmentPolicy rate_adjustment_policy) { map> samples_card; @@ -392,20 +380,14 @@ vector AudioMixer::get_output(double pts, unsigned num_samples, Resamplin lock_guard 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 samples_out, left, right; @@ -730,14 +712,10 @@ void AudioMixer::send_audio_level_callback() correlation.get_correlation()); } -map AudioMixer::get_devices() const +map AudioMixer::get_devices() { lock_guard lock(audio_mutex); - return get_devices_mutex_held(); -} -map AudioMixer::get_devices_mutex_held() const -{ map 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 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 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); } } diff --git a/audio_mixer.h b/audio_mixer.h index 8bb344f..e592267 100644 --- a/audio_mixer.h +++ b/audio_mixer.h @@ -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 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 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 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 interesting_channels; - // Only used for ALSA cards, obviously. - std::unique_ptr alsa_device; }; + + const AudioDevice *find_audio_device(DeviceSpec device_spec) const + { + return const_cast(this)->find_audio_device(device_spec); + } + AudioDevice *find_audio_device(DeviceSpec device_spec); void find_sample_src_from_device(const std::map> &samples_card, DeviceSpec device_spec, int source_channel, const float **srcptr, unsigned *stride); void fill_audio_bus(const std::map> &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 get_devices_mutex_held() const; void apply_eq(unsigned bus_index, std::vector *samples_bus); void update_meters(const std::vector &samples); void add_bus_to_master(unsigned bus_index, const std::vector &samples_bus, std::vector *samples_out); void measure_bus_levels(unsigned bus_index, const std::vector &left, const std::vector &right); void send_audio_level_callback(); + std::vector 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 available_alsa_cards; std::atomic locut_cutoff_hz{120}; StereoFilter locut[MAX_BUSES]; // Default cutoff 120 Hz, 24 dB/oct. -- 2.39.2