-
#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)
}
}
-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];
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;
}
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;
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)
#include "db.h"
#include "flags.h"
+#include "mixer.h"
#include "timebase.h"
using namespace bmusb;
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();
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);
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);
}
}
+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;
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;
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 };
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;
}
// 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);
}
}
class AudioMixer {
public:
AudioMixer(unsigned num_cards);
- ~AudioMixer();
void reset_resampler(DeviceSpec device_spec);
void reset_meters();
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);
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.