#include "alsa_pool.h"
-#include <alsa/control.h>
-#include <alsa/error.h>
-#include <alsa/pcm.h>
+#include <alsa/asoundlib.h>
#include <assert.h>
+#include <errno.h>
#include <limits.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdint.h>
#include <stdio.h>
+#include <sys/eventfd.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <algorithm>
+#include <chrono>
+#include <functional>
+#include <iterator>
#include <memory>
+#include <ratio>
#include "alsa_input.h"
#include "audio_mixer.h"
using namespace std;
using namespace std::placeholders;
+ALSAPool::ALSAPool()
+{
+ should_quit_fd = eventfd(/*initval=*/0, /*flags=*/0);
+ assert(should_quit_fd != -1);
+}
+
ALSAPool::~ALSAPool()
{
for (Device &device : devices) {
device.input->stop_capture_thread();
}
}
+ should_quit = true;
+ const uint64_t one = 1;
+ if (write(should_quit_fd, &one, sizeof(one)) != sizeof(one)) {
+ perror("write(should_quit_fd)");
+ exit(1);
+ }
+ inotify_thread.join();
+
+ while (retry_threads_running > 0) {
+ this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
}
std::vector<ALSAPool::Device> ALSAPool::get_devices()
// then start it ourselves.
fprintf(stderr, "Trying %s again in one second...\n", address);
add_device_tries_left[address] = num_retries;
+ ++retry_threads_running;
thread(&ALSAPool::probe_device_retry_thread_func, this, card_index, dev_index).detach();
}
char address[256];
snprintf(address, sizeof(address), "hw:%d,%d", card_index, dev_index);
+ char thread_name[16];
+ snprintf(thread_name, sizeof(thread_name), "Reprobe_hw:%d,%d", card_index, dev_index);
+ pthread_setname_np(pthread_self(), thread_name);
+
for ( ;; ) { // Termination condition within the loop.
sleep(1);
// See if there are any retries left.
lock_guard<mutex> lock(add_device_mutex);
- if (!add_device_tries_left.count(address) ||
+ if (should_quit ||
+ !add_device_tries_left.count(address) ||
add_device_tries_left[address] == 0) {
add_device_tries_left.erase(address);
fprintf(stderr, "Giving up probe of %s.\n", address);
- return;
+ break;
}
// Seemingly there were. Give it a try (we still hold the mutex).
if (result == ProbeResult::SUCCESS) {
add_device_tries_left.erase(address);
fprintf(stderr, "Probe of %s succeeded.\n", address);
- return;
+ break;
} else if (result == ProbeResult::FAILURE || --add_device_tries_left[address] == 0) {
add_device_tries_left.erase(address);
fprintf(stderr, "Giving up probe of %s.\n", address);
- return;
+ break;
}
// Failed again.
fprintf(stderr, "Trying %s again in one second (%d tries left)...\n",
address, add_device_tries_left[address]);
}
+
+ --retry_threads_running;
}
ALSAPool::ProbeResult ALSAPool::probe_device_once(unsigned card_index, unsigned dev_index)
}
unique_ptr<snd_ctl_t, decltype(snd_ctl_close)*> ctl_closer(ctl, snd_ctl_close);
+ snprintf(address, sizeof(address), "hw:%d,%d", card_index, dev_index);
+
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) {
+ err = snd_ctl_pcm_info(ctl, pcm_info);
+ if (err == -ENOENT) {
+ // Not a capture card.
+ return ALSAPool::ProbeResult::FAILURE;
+ }
+ if (err < 0) {
// Not available for capture.
printf("%s: Not available for capture.\n", address);
return ALSAPool::ProbeResult::DEFER;
}
- 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
void ALSAPool::init()
{
- thread(&ALSAPool::inotify_thread_func, this).detach();
+ inotify_thread = thread(&ALSAPool::inotify_thread_func, this);
enumerate_devices();
}
void ALSAPool::inotify_thread_func()
{
+ pthread_setname_np(pthread_self(), "ALSA_Hotplug");
+
int inotify_fd = inotify_init();
if (inotify_fd == -1) {
perror("inotify_init()");
int size = sizeof(inotify_event) + NAME_MAX + 1;
unique_ptr<char[]> buf(new char[size]);
- for ( ;; ) {
- int ret = read(inotify_fd, buf.get(), size);
+ while (!should_quit) {
+ pollfd fds[2];
+ fds[0].fd = inotify_fd;
+ fds[0].events = POLLIN;
+ fds[0].revents = 0;
+ fds[1].fd = should_quit_fd;
+ fds[1].events = POLLIN;
+ fds[1].revents = 0;
+
+ int ret = poll(fds, 2, -1);
+ if (ret == -1) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ perror("poll(inotify_fd)");
+ return;
+ }
+ }
+ if (ret == 0) {
+ continue;
+ }
+
+ if (fds[1].revents) break; // should_quit_fd asserted.
+
+ ret = read(inotify_fd, buf.get(), size);
+ if (ret == -1) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ perror("read(inotify_fd)");
+ close(watch_fd);
+ close(inotify_fd);
+ return;
+ }
+ }
if (ret < int(sizeof(inotify_event))) {
fprintf(stderr, "inotify read unexpectedly returned %d, giving up hotplug of ALSA devices.\n",
int(ret));
}
}
}
+ close(watch_fd);
+ close(inotify_fd);
+ close(should_quit_fd);
}
void ALSAPool::reset_device(unsigned index)
inputs[index].reset();
} else {
// TODO: Put on a background thread instead of locking?
- auto callback = bind(&AudioMixer::add_audio, global_audio_mixer, DeviceSpec{InputSourceType::ALSA_INPUT, index}, _1, _2, _3, _4);
+ auto callback = bind(&AudioMixer::add_audio, global_audio_mixer, DeviceSpec{InputSourceType::ALSA_INPUT, index}, _1, _2, _3, _4, _5);
inputs[index].reset(new ALSAInput(device->address.c_str(), OUTPUT_FREQUENCY, device->num_channels, callback, this, index));
inputs[index]->start_capture_thread();
}