static void ALSARecoverAfterOverrun(snd_pcm_t* pcm) { Logger::LogWarning("[ALSARecoverAfterOverrun] " + QObject::tr("Warning: An overrun has occurred, some samples were lost.", "Don't translate 'overrun'")); if(snd_pcm_prepare(pcm) < 0) { Logger::LogError("[ALSARecoverAfterOverrun] " + QObject::tr("Error: Can't recover device after overrun!", "Don't translate 'overrun'")); throw ALSAException(); } if(snd_pcm_start(pcm) < 0) { Logger::LogError("[ALSARecoverAfterOverrun] " + QObject::tr("Error: Can't start PCM device after overrun!", "Don't translate 'overrun'")); throw ALSAException(); } }
void ALSAInput::Init() { snd_pcm_hw_params_t *alsa_hw_params = NULL; try { // allocate parameter structure if(snd_pcm_hw_params_malloc(&alsa_hw_params) < 0) { throw std::bad_alloc(); } // open PCM device if(snd_pcm_open(&m_alsa_pcm, m_device_name.toAscii().constData(), SND_PCM_STREAM_CAPTURE, 0) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't open PCM device!")); throw ALSAException(); } if(snd_pcm_hw_params_any(m_alsa_pcm, alsa_hw_params) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't get PCM hardware parameters!")); throw ALSAException(); } // set access type if(snd_pcm_hw_params_set_access(m_alsa_pcm, alsa_hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't set access type!")); throw ALSAException(); } // set sample format if(snd_pcm_hw_params_set_format(m_alsa_pcm, alsa_hw_params, SND_PCM_FORMAT_S16_LE) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't set sample format!")); throw ALSAException(); } // set sample rate unsigned int rate = m_sample_rate; if(snd_pcm_hw_params_set_rate_near(m_alsa_pcm, alsa_hw_params, &rate, NULL) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't set sample rate!")); throw ALSAException(); } if(rate != m_sample_rate) { Logger::LogWarning("[ALSAInput::Init] " + QObject::tr("Warning: Sample rate %1 is not supported, using %2 instead. " "This could be a problem if the difference is large.") .arg(m_sample_rate).arg(rate)); //TODO// enable once resampling is ready //m_sample_rate = rate; } // set channel count if(snd_pcm_hw_params_set_channels(m_alsa_pcm, alsa_hw_params, m_channels) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't set channel count!")); throw ALSAException(); } // set period count unsigned int periods = m_alsa_periods; if(snd_pcm_hw_params_set_periods_near(m_alsa_pcm, alsa_hw_params, &periods, NULL) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't set period count!")); throw ALSAException(); } if(periods != m_alsa_periods) { Logger::LogWarning("[ALSAInput::Init] " + QObject::tr("Warning: Period count %1 is not supported, using %2 instead. " "This is not a problem.") .arg(m_alsa_periods).arg(periods)); m_alsa_periods = periods; } // set period size snd_pcm_uframes_t period_size = m_alsa_period_size; if(snd_pcm_hw_params_set_period_size_near(m_alsa_pcm, alsa_hw_params, &period_size, NULL) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't set period size!")); throw ALSAException(); } if(period_size != m_alsa_period_size) { Logger::LogWarning("[ALSAInput::Init] " + QObject::tr("Warning: Period size %1 is not supported, using %2 instead. " "This is not a problem.") .arg(m_alsa_period_size).arg(period_size)); m_alsa_period_size = period_size; } // apply parameters if(snd_pcm_hw_params(m_alsa_pcm, alsa_hw_params) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't apply PCM hardware parameters!")); throw ALSAException(); } // free parameter structure snd_pcm_hw_params_free(alsa_hw_params); alsa_hw_params = NULL; } catch(...) { if(alsa_hw_params != NULL) { snd_pcm_hw_params_free(alsa_hw_params); alsa_hw_params = NULL; } throw; } // start PCM device if(snd_pcm_start(m_alsa_pcm) < 0) { Logger::LogError("[ALSAInput::Init] " + QObject::tr("Error: Can't start PCM device!")); throw ALSAException(); } // start input thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&ALSAInput::InputThread, this); }
void ALSAInput::InputThread() { try { Logger::LogInfo("[ALSAInput::InputThread] " + QObject::tr("Input thread started.")); std::vector<uint8_t> buffer(m_alsa_period_size * m_channels * 2); bool has_first_samples = false; int64_t first_timestamp = 0; // value won't be used, but GCC gives a warning otherwise while(!m_should_stop) { // wait until samples are available // This is not actually required since snd_pcm_readi is blocking, but unlike snd_pcm_read, // this function has a timeout value. This means the input thread won't hang if the device turns out to be dead. int res = snd_pcm_wait(m_alsa_pcm, 1000); if(res == 0) { continue; } if(res < 0) { if(res == -EPIPE) { ALSARecoverAfterOverrun(m_alsa_pcm); PushAudioHole(); } else { Logger::LogError("[ALSAInput::InputThread] " + QObject::tr("Error: Can't check whether samples are available!")); throw ALSAException(); } continue; } // read the samples snd_pcm_sframes_t samples_read = snd_pcm_readi(m_alsa_pcm, buffer.data(), m_alsa_period_size); if(samples_read < 0) { if(samples_read == -EPIPE) { ALSARecoverAfterOverrun(m_alsa_pcm); PushAudioHole(); } else { Logger::LogError("[ALSAInput::InputThread] " + QObject::tr("Error: Can't read samples!")); throw ALSAException(); } continue; } if(samples_read <= 0) continue; int64_t timestamp = hrt_time_micro(); // skip the first samples if(has_first_samples) { if(timestamp > first_timestamp + START_DELAY) { // push the samples int64_t time = timestamp - (int64_t) samples_read * (int64_t) 1000000 / (int64_t) m_sample_rate; PushAudioSamples(m_sample_rate, m_channels, samples_read, buffer.data(), AV_SAMPLE_FMT_S16, time); } } else { has_first_samples = true; first_timestamp = timestamp; } } Logger::LogInfo("[ALSAInput::InputThread] " + QObject::tr("Input thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[ALSAInput::InputThread] " + QObject::tr("Exception '%1' in input thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[ALSAInput::InputThread] " + QObject::tr("Unknown exception in input thread.")); } }
std::vector<ALSAInput::Source> ALSAInput::GetSourceList() { std::vector<Source> list; /* This code is based on the ALSA device detection code used in PortAudio. I just ported it to C++ and improved it a bit. All credit goes to the PortAudio devs, they saved me a lot of time :). */ // these ALSA plugins are blacklisted because they are always defined but rarely useful // 'pulse' is also blacklisted because the native PulseAudio backend is more reliable std::vector<std::string> plugin_blacklist = { "cards", "default", "sysdefault", "hw", "plughw", "plug", "dmix", "dsnoop", "shm", "tee", "file", "null", "front", "rear", "center_lfe", "side", "surround40", "surround41", "surround50", "surround51", "surround71", "iec958", "spdif", "hdmi", "modem", "phoneline", "pulse", }; std::sort(plugin_blacklist.begin(), plugin_blacklist.end()); // the 'default' PCM must be first, so add it explicitly list.push_back(Source("default", "Default source")); Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Generating source list ...")); snd_ctl_card_info_t *alsa_card_info = NULL; snd_pcm_info_t *alsa_pcm_info = NULL; snd_ctl_t *alsa_ctl = NULL; try { // allocate card and PCM info structure if(snd_ctl_card_info_malloc(&alsa_card_info) < 0) { throw std::bad_alloc(); } if(snd_pcm_info_malloc(&alsa_pcm_info) < 0) { throw std::bad_alloc(); } // update the ALSA configuration snd_config_update_free_global(); if(snd_config_update() < 0) { Logger::LogError("[ALSAInput::GetSourceList] " + Logger::tr("Error: Could not update ALSA configuration!")); throw ALSAException(); } // find all PCM plugins (by parsing the config file) snd_config_t *alsa_config_pcms = NULL; if(snd_config_search(snd_config, "pcm", &alsa_config_pcms) == 0) { snd_config_iterator_t i, next; snd_config_for_each(i, next, alsa_config_pcms) { snd_config_t *alsa_config_pcm = snd_config_iterator_entry(i); // get the name const char *id = NULL; if(snd_config_get_id(alsa_config_pcm, &id) < 0 || id == NULL) continue; std::string plugin_name = id; // ignore the plugin if it is blacklisted if(std::binary_search(plugin_blacklist.begin(), plugin_blacklist.end(), plugin_name)) continue; // try to get the description std::string plugin_description; snd_config_t *alsa_config_description = NULL; if(snd_config_search(alsa_config_pcm, "hint.description", &alsa_config_description) == 0) { const char *str = NULL; if(snd_config_get_string(alsa_config_description, &str) >= 0 && str != NULL) { plugin_description = str; } } // if there is no description, ignore it, because it's probably not meant to be used if(plugin_description.empty()) continue; // if there is no description, use the type instead /*if(plugin_description.empty()) { snd_config_t *alsa_config_type = NULL; if(snd_config_search(alsa_config_pcm, "type", &alsa_config_type) >= 0) { const char *str = NULL; if(snd_config_get_string(alsa_config_type, &str) >= 0 && str != NULL) { plugin_description = std::string(str) + " plugin"; } } }*/ // add to list Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Found plugin: [%1] %2").arg(QString::fromStdString(plugin_name)).arg(QString::fromStdString(plugin_description))); list.push_back(Source(plugin_name, plugin_description)); } }