X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=alsa_input.cpp;h=4e507f39cb374de0360d1beb6eeb5a06345b98e9;hb=b590a9a091974607517a9f872e28cbfa65014e2a;hp=87adb4ab2427c705a1a1682b9fad24ba7e309abf;hpb=1d75037f1e47e1e86adc444417922b402ba8272a;p=nageru 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; }