#include "alsa_input.h"
#include "audio_mixer.h"
#include "defs.h"
+#include "state.pb.h"
#include <sys/inotify.h>
+#include <algorithm>
#include <functional>
#include <unordered_map>
ALSAInput::~ALSAInput()
{
- WARN_ON_ERROR("snd_pcm_close()", snd_pcm_close(pcm_handle));
+ if (pcm_handle) {
+ WARN_ON_ERROR("snd_pcm_close()", snd_pcm_close(pcm_handle));
+ }
}
void ALSAInput::start_capture_thread()
void ALSAInput::capture_thread_func()
{
+ parent_pool->set_card_state(internal_dev_index, ALSAPool::Device::State::STARTING);
+
// If the device hasn't been opened already, we need to do so
// before we can capture.
while (!should_quit && pcm_handle == nullptr) {
if (should_quit) {
// Don't call free_card(); that would be a deadlock.
+ WARN_ON_ERROR("snd_pcm_close()", snd_pcm_close(pcm_handle));
+ pcm_handle = nullptr;
return;
}
switch (do_capture()) {
case CaptureEndReason::REQUESTED_QUIT:
// Don't call free_card(); that would be a deadlock.
+ WARN_ON_ERROR("snd_pcm_close()", snd_pcm_close(pcm_handle));
+ pcm_handle = nullptr;
return;
case CaptureEndReason::DEVICE_GONE:
parent_pool->free_card(internal_dev_index);
+ WARN_ON_ERROR("snd_pcm_close()", snd_pcm_close(pcm_handle));
+ pcm_handle = nullptr;
return;
case CaptureEndReason::OTHER_ERROR:
parent_pool->set_card_state(internal_dev_index, ALSAPool::Device::State::STARTING);
snd_ctl_card_info_alloca(&card_info);
snd_ctl_card_info(ctl, card_info);
- lock_guard<mutex> lock(mu);
- unsigned internal_dev_index = find_free_device_index();
- devices[internal_dev_index].address = address;
- devices[internal_dev_index].name = snd_ctl_card_info_get_name(card_info);
- devices[internal_dev_index].info = snd_pcm_info_get_name(pcm_info);
- devices[internal_dev_index].num_channels = num_channels;
- devices[internal_dev_index].state = Device::State::READY;
+ string name = snd_ctl_card_info_get_name(card_info);
+ string info = snd_pcm_info_get_name(pcm_info);
+
+ unsigned internal_dev_index;
+ string display_name;
+ {
+ lock_guard<mutex> lock(mu);
+ internal_dev_index = find_free_device_index(name, info, num_channels, address);
+ devices[internal_dev_index].address = address;
+ devices[internal_dev_index].name = name;
+ devices[internal_dev_index].info = info;
+ devices[internal_dev_index].num_channels = num_channels;
+ // Note: Purposefully does not overwrite held.
+
+ display_name = devices[internal_dev_index].display_name();
+ }
fprintf(stderr, "%s: Probed successfully.\n", address);
+ reset_device(internal_dev_index); // Restarts it if it is held (ie., we just replaced a dead card).
+
+ DeviceSpec spec{InputSourceType::ALSA_INPUT, internal_dev_index};
+ global_audio_mixer->set_display_name(spec, display_name);
+ global_audio_mixer->trigger_state_changed_callback();
+
return ALSAPool::ProbeResult::SUCCESS;
}
+void ALSAPool::unplug_device(unsigned card_index, unsigned dev_index)
+{
+ char address[256];
+ snprintf(address, sizeof(address), "hw:%d,%d", card_index, dev_index);
+ for (unsigned i = 0; i < devices.size(); ++i) {
+ if (devices[i].state != Device::State::EMPTY &&
+ devices[i].state != Device::State::DEAD &&
+ devices[i].address == address) {
+ free_card(i);
+ }
+ }
+}
+
void ALSAPool::init()
{
thread(&ALSAPool::inotify_thread_func, this).detach();
if (sscanf(event->name, "pcmC%uD%u%c", &card, &device, &type) == 3 && type == 'c') {
if (event->mask & (IN_MOVED_FROM | IN_DELETE)) {
printf("Deleted capture device: Card %u, device %u\n", card, device);
- // TODO: Unplug.
+ unplug_device(card, device);
}
if (event->mask & (IN_MOVED_TO | IN_CREATE)) {
printf("Adding capture device: Card %u, device %u\n", card, device);
void ALSAPool::set_card_state(unsigned index, ALSAPool::Device::State state)
{
- lock_guard<mutex> lock(mu);
- devices[index].state = state;
+ {
+ lock_guard<mutex> lock(mu);
+ devices[index].state = state;
+ }
+
+ DeviceSpec spec{InputSourceType::ALSA_INPUT, index};
+ bool silence = (state != ALSAPool::Device::State::RUNNING);
+ while (!global_audio_mixer->silence_card(spec, silence))
+ ;
+ global_audio_mixer->trigger_state_changed_callback();
}
-unsigned ALSAPool::find_free_device_index()
+unsigned ALSAPool::find_free_device_index(const string &name, const string &info, unsigned num_channels, const string &address)
{
+ // First try to find an exact match on a dead card.
+ for (unsigned i = 0; i < devices.size(); ++i) {
+ if (devices[i].state == Device::State::DEAD &&
+ devices[i].address == address &&
+ devices[i].name == name &&
+ devices[i].info == info &&
+ devices[i].num_channels == num_channels) {
+ devices[i].state = Device::State::READY;
+ return i;
+ }
+ }
+
+ // Then try to find a match on everything but the address
+ // (probably that devices were plugged back in a different order).
+ // If we have two cards that are equal, this might get them mixed up,
+ // but we don't have anything better.
+ for (unsigned i = 0; i < devices.size(); ++i) {
+ if (devices[i].state == Device::State::DEAD &&
+ devices[i].name == name &&
+ devices[i].info == info &&
+ devices[i].num_channels == num_channels) {
+ devices[i].state = Device::State::READY;
+ return i;
+ }
+ }
+
+ // OK, so we didn't find a match; see if there are any empty slots.
for (unsigned i = 0; i < devices.size(); ++i) {
if (devices[i].state == Device::State::EMPTY) {
devices[i].state = Device::State::READY;
+ devices[i].held = false;
return i;
}
}
+
+ // Failing that, we just insert the new device at the end.
Device new_dev;
new_dev.state = Device::State::READY;
+ new_dev.held = false;
devices.push_back(new_dev);
inputs.emplace_back(nullptr);
return devices.size() - 1;
}
-void ALSAPool::free_card(unsigned index)
+unsigned ALSAPool::create_dead_card(const string &name, const string &info, unsigned num_channels)
{
lock_guard<mutex> lock(mu);
- if (devices[index].held) {
- devices[index].state = Device::State::DEAD;
- } else {
- devices[index].state = Device::State::EMPTY;
- inputs[index].reset();
+
+ // See if there are any empty slots. If not, insert one at the end.
+ vector<Device>::iterator free_device =
+ find_if(devices.begin(), devices.end(),
+ [](const Device &device) { return device.state == Device::State::EMPTY; });
+ if (free_device == devices.end()) {
+ devices.push_back(Device());
+ inputs.emplace_back(nullptr);
+ free_device = devices.end() - 1;
}
- while (!devices.empty() && devices.back().state == Device::State::EMPTY) {
- devices.pop_back();
- inputs.pop_back();
+
+ free_device->state = Device::State::DEAD;
+ free_device->name = name;
+ free_device->info = info;
+ free_device->num_channels = num_channels;
+ free_device->held = true;
+
+ return distance(devices.begin(), free_device);
+}
+
+void ALSAPool::serialize_device(unsigned index, DeviceSpecProto *serialized)
+{
+ lock_guard<mutex> lock(mu);
+ assert(index < devices.size());
+ assert(devices[index].held);
+ serialized->set_type(DeviceSpecProto::ALSA_INPUT);
+ serialized->set_index(index);
+ serialized->set_display_name(devices[index].display_name());
+ serialized->set_alsa_name(devices[index].name);
+ serialized->set_alsa_info(devices[index].info);
+ serialized->set_num_channels(devices[index].num_channels);
+ serialized->set_address(devices[index].address);
+}
+
+void ALSAPool::free_card(unsigned index)
+{
+ DeviceSpec spec{InputSourceType::ALSA_INPUT, index};
+ while (!global_audio_mixer->silence_card(spec, true))
+ ;
+
+ {
+ lock_guard<mutex> lock(mu);
+ if (devices[index].held) {
+ devices[index].state = Device::State::DEAD;
+ } else {
+ devices[index].state = Device::State::EMPTY;
+ inputs[index].reset();
+ }
+ while (!devices.empty() && devices.back().state == Device::State::EMPTY) {
+ devices.pop_back();
+ inputs.pop_back();
+ }
}
+
+ global_audio_mixer->trigger_state_changed_callback();
}