"Fossies" - the Fresh Open Source Software Archive

Member "ssr-0.4.2/src/AV/Input/ALSAInput.cpp" (18 May 2020, 17372 Bytes) of package /linux/privat/ssr-0.4.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ALSAInput.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.4.1_vs_0.4.2.

    1 /*
    2 Copyright (c) 2012-2020 Maarten Baert <maarten-baert@hotmail.com>
    3 
    4 This file is part of SimpleScreenRecorder.
    5 
    6 SimpleScreenRecorder is free software: you can redistribute it and/or modify
    7 it under the terms of the GNU General Public License as published by
    8 the Free Software Foundation, either version 3 of the License, or
    9 (at your option) any later version.
   10 
   11 SimpleScreenRecorder is distributed in the hope that it will be useful,
   12 but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 GNU General Public License for more details.
   15 
   16 You should have received a copy of the GNU General Public License
   17 along with SimpleScreenRecorder.  If not, see <http://www.gnu.org/licenses/>.
   18 */
   19 
   20 #include "ALSAInput.h"
   21 
   22 #if SSR_USE_ALSA
   23 
   24 #include "Logger.h"
   25 
   26 #include "TempBuffer.h"
   27 
   28 // Artificial delay after the first samples have been received (in microseconds). Any samples received during this time will be dropped.
   29 // This is needed because the first samples sometimes have weird timestamps, especially when PulseAudio is active
   30 // (I've seen one situation where PulseAudio instantly 'captures' 2 seconds of silence when the recording is started).
   31 // It also eliminates the clicking sound when the microphone is started for the first time.
   32 const int64_t ALSAInput::START_DELAY = 100000;
   33 
   34 static void ALSARecoverAfterOverrun(snd_pcm_t* pcm) {
   35     Logger::LogWarning("[ALSARecoverAfterOverrun] " + Logger::tr("Warning: An overrun has occurred, some samples were lost.", "Don't translate 'overrun'"));
   36     if(snd_pcm_prepare(pcm) < 0) {
   37         Logger::LogError("[ALSARecoverAfterOverrun] " + Logger::tr("Error: Can't recover device after overrun!", "Don't translate 'overrun'"));
   38         throw ALSAException();
   39     }
   40     if(snd_pcm_start(pcm) < 0) {
   41         Logger::LogError("[ALSARecoverAfterOverrun] " + Logger::tr("Error: Can't start PCM device after overrun!", "Don't translate 'overrun'"));
   42         throw ALSAException();
   43     }
   44 }
   45 
   46 ALSAInput::ALSAInput(const QString& source_name, unsigned int sample_rate) {
   47 
   48     m_source_name = source_name;
   49     m_sample_rate = sample_rate;
   50     m_sample_format = AV_SAMPLE_FMT_S16; // default, may change later
   51     m_convert_24_to_32 = false;
   52     m_channels = 2; // always 2 channels because the synchronizer and encoder don't support anything else at this point
   53     m_period_size = 1024; // number of samples per period
   54     m_buffer_size = m_period_size * 8; // number of samples in the buffer
   55 
   56     m_alsa_pcm = NULL;
   57 
   58     try {
   59         Init();
   60     } catch(...) {
   61         Free();
   62         throw;
   63     }
   64 
   65 }
   66 
   67 ALSAInput::~ALSAInput() {
   68 
   69     // tell the thread to stop
   70     if(m_thread.joinable()) {
   71         Logger::LogInfo("[ALSAInput::~ALSAInput] " + Logger::tr("Stopping input thread ..."));
   72         m_should_stop = true;
   73         m_thread.join();
   74     }
   75 
   76     // free everything
   77     Free();
   78 
   79 }
   80 
   81 std::vector<ALSAInput::Source> ALSAInput::GetSourceList() {
   82     std::vector<Source> list;
   83 
   84     /*
   85     This code is based on the ALSA device detection code used in PortAudio. I just ported it to C++ and improved it a bit.
   86     All credit goes to the PortAudio devs, they saved me a lot of time :).
   87     */
   88 
   89     // these ALSA plugins are blacklisted because they are always defined but rarely useful
   90     // 'pulse' and 'jack' are also blacklisted because the native backends are more reliable
   91     std::vector<std::string> plugin_blacklist = {
   92         "cards", "default", "sysdefault", "hw", "plughw", "plug", "dmix", "dsnoop", "shm", "tee", "file", "null",
   93         "front", "rear", "center_lfe", "side", "surround40", "surround41", "surround50", "surround51", "surround71",
   94         "iec958", "spdif", "hdmi", "modem", "phoneline", "oss", "pulse", "jack", "speex", "speexrate", "samplerate",
   95         "upmix", "vdownmix", "usbstream",
   96     };
   97     std::sort(plugin_blacklist.begin(), plugin_blacklist.end());
   98 
   99     // the 'default' PCM must be first, so add it explicitly
  100     list.push_back(Source("default", "Default source"));
  101 
  102     Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Generating source list ..."));
  103 
  104     snd_ctl_card_info_t *alsa_card_info = NULL;
  105     snd_pcm_info_t *alsa_pcm_info = NULL;
  106     snd_ctl_t *alsa_ctl = NULL;
  107 
  108     try {
  109 
  110         // allocate card and PCM info structure
  111         if(snd_ctl_card_info_malloc(&alsa_card_info) < 0) {
  112             throw std::bad_alloc();
  113         }
  114         if(snd_pcm_info_malloc(&alsa_pcm_info) < 0) {
  115             throw std::bad_alloc();
  116         }
  117 
  118         // update the ALSA configuration
  119         snd_config_update_free_global();
  120         if(snd_config_update() < 0) {
  121             Logger::LogError("[ALSAInput::GetSourceList] " + Logger::tr("Error: Could not update ALSA configuration!"));
  122             throw ALSAException();
  123         }
  124 
  125         // find all PCM plugins (by parsing the config file)
  126         snd_config_t *alsa_config_pcms = NULL;
  127         if(snd_config_search(snd_config, "pcm", &alsa_config_pcms) == 0) {
  128             snd_config_iterator_t i, next;
  129             snd_config_for_each(i, next, alsa_config_pcms) {
  130                 snd_config_t *alsa_config_pcm = snd_config_iterator_entry(i);
  131 
  132                 // get the name
  133                 const char *id = NULL;
  134                 if(snd_config_get_id(alsa_config_pcm, &id) < 0 || id == NULL)
  135                     continue;
  136                 std::string plugin_name = id;
  137 
  138                 // ignore the plugin if it is blacklisted
  139                 if(std::binary_search(plugin_blacklist.begin(), plugin_blacklist.end(), plugin_name))
  140                     continue;
  141 
  142                 // try to get the description
  143                 std::string plugin_description;
  144                 snd_config_t *alsa_config_description = NULL;
  145                 if(snd_config_search(alsa_config_pcm, "hint.description", &alsa_config_description) == 0) {
  146                     const char *str = NULL;
  147                     if(snd_config_get_string(alsa_config_description, &str) >= 0 && str != NULL) {
  148                         plugin_description = str;
  149                     }
  150                 }
  151 
  152                 // if there is no description, ignore it, because it's probably not meant to be used
  153                 if(plugin_description.empty())
  154                     continue;
  155 
  156                 // if there is no description, use the type instead
  157                 /*if(plugin_description.empty()) {
  158                     snd_config_t *alsa_config_type = NULL;
  159                     if(snd_config_search(alsa_config_pcm, "type", &alsa_config_type) >= 0) {
  160                         const char *str = NULL;
  161                         if(snd_config_get_string(alsa_config_type, &str) >= 0 && str != NULL) {
  162                             plugin_description = std::string(str) + " plugin";
  163                         }
  164                     }
  165                 }*/
  166 
  167                 // add to list
  168                 Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Found plugin: [%1] %2").arg(QString::fromStdString(plugin_name)).arg(QString::fromStdString(plugin_description)));
  169                 list.push_back(Source(plugin_name, plugin_description));
  170 
  171             }
  172         }
  173 
  174         // find all sound cards
  175         int card = -1;
  176         while(snd_card_next(&card) == 0 && card >= 0) {
  177 
  178             // try to open the card
  179             std::string card_name = "hw:" + NumToString(card);
  180             if(snd_ctl_open(&alsa_ctl, card_name.c_str(), 0) < 0) {
  181                 Logger::LogWarning("[ALSAInput::GetSourceList] " + Logger::tr("Warning: Could not open sound card %1.").arg(card));
  182                 continue;
  183             }
  184 
  185             // get card info
  186             if(snd_ctl_card_info(alsa_ctl, alsa_card_info) < 0) {
  187                 Logger::LogWarning("[ALSAInput::GetSourceList] " + Logger::tr("Warning: Could not get info for sound card %1.").arg(card));
  188                 continue;
  189             }
  190             std::string card_description = snd_ctl_card_info_get_name(alsa_card_info);
  191             Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Found card: [%1] %2").arg(QString::fromStdString(card_name)).arg(QString::fromStdString(card_description)));
  192 
  193             // find all devices for this card
  194             int device = -1;
  195             bool should_add_shared = true;
  196             while(snd_ctl_pcm_next_device(alsa_ctl, &device) == 0 && device >= 0) {
  197 
  198                 // get device info
  199                 snd_pcm_info_set_device(alsa_pcm_info, device);
  200                 snd_pcm_info_set_subdevice(alsa_pcm_info, 0);
  201                 snd_pcm_info_set_stream(alsa_pcm_info, SND_PCM_STREAM_CAPTURE);
  202                 if(snd_ctl_pcm_info(alsa_ctl, alsa_pcm_info) < 0)
  203                     continue; // not a capture device
  204 
  205                 // add a shared source if needed
  206                 if(should_add_shared) {
  207                     list.push_back(Source("sysdefault:" + NumToString(card), card_description + " (shared)"));
  208                     should_add_shared = false;
  209                 }
  210 
  211                 // get device description
  212                 std::string device_name = "hw:" + NumToString(card) + "," + NumToString(device);
  213                 std::string device_description = card_description + ": " + snd_pcm_info_get_name(alsa_pcm_info);
  214 
  215                 // add to list
  216                 Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Found device: [%1] %2").arg(QString::fromStdString(device_name)).arg(QString::fromStdString(device_description)));
  217                 list.push_back(Source(device_name, device_description));
  218 
  219             }
  220 
  221             // close the card
  222             snd_ctl_close(alsa_ctl);
  223             alsa_ctl = NULL;
  224 
  225         }
  226 
  227         // free card and PCM info struction
  228         snd_pcm_info_free(alsa_pcm_info);
  229         alsa_pcm_info = NULL;
  230         snd_ctl_card_info_free(alsa_card_info);
  231         alsa_card_info = NULL;
  232 
  233     } catch(...) {
  234         if(alsa_ctl != NULL) {
  235             snd_ctl_close(alsa_ctl);
  236             alsa_ctl = NULL;
  237         }
  238         if(alsa_pcm_info != NULL) {
  239             snd_pcm_info_free(alsa_pcm_info);
  240             alsa_pcm_info = NULL;
  241         }
  242         if(alsa_card_info != NULL) {
  243             snd_ctl_card_info_free(alsa_card_info);
  244             alsa_card_info = NULL;
  245         }
  246         // don't re-throw exception
  247     }
  248 
  249     return list;
  250 }
  251 
  252 void ALSAInput::Init() {
  253 
  254     snd_pcm_hw_params_t *alsa_hw_params = NULL;
  255     snd_pcm_format_mask_t *alsa_format_mask = NULL;
  256 
  257     try {
  258 
  259         // allocate parameter structure
  260         if(snd_pcm_hw_params_malloc(&alsa_hw_params) < 0) {
  261             throw std::bad_alloc();
  262         }
  263 
  264         // allocate format mask structure
  265         if(snd_pcm_format_mask_malloc(&alsa_format_mask) < 0) {
  266             throw std::bad_alloc();
  267         }
  268 
  269         // open PCM device
  270         if(snd_pcm_open(&m_alsa_pcm, m_source_name.toUtf8().constData(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) < 0) {
  271             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't open PCM device!"));
  272             throw ALSAException();
  273         }
  274         if(snd_pcm_hw_params_any(m_alsa_pcm, alsa_hw_params) < 0) {
  275             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't get PCM hardware parameters!"));
  276             throw ALSAException();
  277         }
  278 
  279         // set access type
  280         if(snd_pcm_hw_params_set_access(m_alsa_pcm, alsa_hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
  281             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set access type!"));
  282             throw ALSAException();
  283         }
  284 
  285         // set sample format
  286         snd_pcm_format_mask_none(alsa_format_mask);
  287         snd_pcm_format_mask_set(alsa_format_mask, SND_PCM_FORMAT_S16_LE);
  288         snd_pcm_format_mask_set(alsa_format_mask, SND_PCM_FORMAT_S24_LE);
  289         snd_pcm_format_mask_set(alsa_format_mask, SND_PCM_FORMAT_S32_LE);
  290         snd_pcm_format_mask_set(alsa_format_mask, SND_PCM_FORMAT_FLOAT_LE);
  291         if(snd_pcm_hw_params_set_format_mask(m_alsa_pcm, alsa_hw_params, alsa_format_mask) < 0) {
  292             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set sample format mask!"));
  293             throw ALSAException();
  294         }
  295         snd_pcm_format_t sample_format;
  296         if(snd_pcm_hw_params_set_format_first(m_alsa_pcm, alsa_hw_params, &sample_format) < 0) {
  297             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set sample format!"));
  298             throw ALSAException();
  299         }
  300         const char *format_str = NULL;
  301         switch(sample_format) {
  302             case SND_PCM_FORMAT_S16_LE: {
  303                 m_sample_format = AV_SAMPLE_FMT_S16;
  304                 format_str = "s16";
  305                 break;
  306             }
  307             case SND_PCM_FORMAT_S24_LE: {
  308                 m_sample_format = AV_SAMPLE_FMT_S32;
  309                 m_convert_24_to_32 = true;
  310                 format_str = "s24";
  311                 break;
  312             }
  313             case SND_PCM_FORMAT_S32_LE: {
  314                 m_sample_format = AV_SAMPLE_FMT_S32;
  315                 format_str = "s32";
  316                 break;
  317             }
  318             case SND_PCM_FORMAT_FLOAT_LE: {
  319                 m_sample_format = AV_SAMPLE_FMT_FLT;
  320                 format_str = "f32";
  321                 break;
  322             }
  323             default: assert(false);
  324         }
  325         Logger::LogInfo("[ALSAInput::InputThread] " + Logger::tr("Using sample format %1.").arg(format_str));
  326 
  327         // set sample rate
  328         unsigned int rate = m_sample_rate;
  329         if(snd_pcm_hw_params_set_rate_near(m_alsa_pcm, alsa_hw_params, &rate, NULL) < 0) {
  330             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set sample rate!"));
  331             throw ALSAException();
  332         }
  333         if(rate != m_sample_rate) {
  334             Logger::LogWarning("[ALSAInput::Init] " + Logger::tr("Warning: Sample rate %1 is not supported, using %2 instead. "
  335                                                                  "This is not a problem.")
  336                                .arg(m_sample_rate).arg(rate));
  337             m_sample_rate = rate;
  338         }
  339 
  340         // set channel count
  341         unsigned int channels = m_channels;
  342         if(snd_pcm_hw_params_set_channels_near(m_alsa_pcm, alsa_hw_params, &channels) < 0) {
  343             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set channel count!"));
  344             throw ALSAException();
  345         }
  346         if(channels != m_channels) {
  347             Logger::LogWarning("[ALSAInput::Init] " + Logger::tr("Warning: Channel count %1 is not supported, using %2 instead. "
  348                                                                  "This is not a problem.")
  349                                .arg(m_channels).arg(channels));
  350             m_channels = channels;
  351         }
  352 
  353         // set period size
  354         snd_pcm_uframes_t period_size = m_period_size;
  355         if(snd_pcm_hw_params_set_period_size_near(m_alsa_pcm, alsa_hw_params, &period_size, NULL) < 0) {
  356             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set period size!"));
  357             throw ALSAException();
  358         }
  359         if(period_size != m_period_size) {
  360             Logger::LogWarning("[ALSAInput::Init] " + Logger::tr("Warning: Period size %1 is not supported, using %2 instead. "
  361                                                                  "This is not a problem.")
  362                                .arg(m_period_size).arg(period_size));
  363             m_period_size = period_size;
  364         }
  365 
  366         // set buffer size
  367         snd_pcm_uframes_t buffer_size = m_buffer_size;
  368         if(snd_pcm_hw_params_set_buffer_size_near(m_alsa_pcm, alsa_hw_params, &buffer_size) < 0) {
  369             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set buffer size!"));
  370             throw ALSAException();
  371         }
  372         if(buffer_size != m_buffer_size) {
  373             Logger::LogWarning("[ALSAInput::Init] " + Logger::tr("Warning: Buffer size %1 is not supported, using %2 instead. "
  374                                                                  "This is not a problem.")
  375                                .arg(m_buffer_size).arg(buffer_size));
  376             m_buffer_size = buffer_size;
  377         }
  378 
  379         // apply parameters
  380         if(snd_pcm_hw_params(m_alsa_pcm, alsa_hw_params) < 0) {
  381             Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't apply PCM hardware parameters!"));
  382             throw ALSAException();
  383         }
  384 
  385         // free format mask structure
  386         snd_pcm_format_mask_free(alsa_format_mask);
  387         alsa_format_mask = NULL;
  388 
  389         // free parameter structure
  390         snd_pcm_hw_params_free(alsa_hw_params);
  391         alsa_hw_params = NULL;
  392 
  393     } catch(...) {
  394         if(alsa_format_mask != NULL) {
  395             snd_pcm_format_mask_free(alsa_format_mask);
  396             alsa_format_mask = NULL;
  397         }
  398         if(alsa_hw_params != NULL) {
  399             snd_pcm_hw_params_free(alsa_hw_params);
  400             alsa_hw_params = NULL;
  401         }
  402         throw;
  403     }
  404 
  405     // start PCM device
  406     if(snd_pcm_start(m_alsa_pcm) < 0) {
  407         Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't start PCM device!"));
  408         throw ALSAException();
  409     }
  410 
  411     // start input thread
  412     m_should_stop = false;
  413     m_error_occurred = false;
  414     m_thread = std::thread(&ALSAInput::InputThread, this);
  415 
  416 }
  417 
  418 void ALSAInput::Free() {
  419     if(m_alsa_pcm != NULL) {
  420         snd_pcm_close(m_alsa_pcm);
  421         m_alsa_pcm = NULL;
  422     }
  423 }
  424 
  425 void ALSAInput::InputThread() {
  426     try {
  427 
  428         Logger::LogInfo("[ALSAInput::InputThread] " + Logger::tr("Input thread started."));
  429 
  430         // allocate buffer
  431         TempBuffer<uint8_t> buffer;
  432         switch(m_sample_format) {
  433             case AV_SAMPLE_FMT_S16: buffer.Alloc(m_period_size * m_channels * sizeof(int16_t)); break;
  434             case AV_SAMPLE_FMT_S32: buffer.Alloc(m_period_size * m_channels * sizeof(int32_t)); break;
  435             case AV_SAMPLE_FMT_FLT: buffer.Alloc(m_period_size * m_channels * sizeof(float)); break;
  436             default: assert(false);
  437         }
  438 
  439         bool has_first_samples = false;
  440         int64_t first_timestamp = 0; // value won't be used, but GCC gives a warning otherwise
  441 
  442         while(!m_should_stop) {
  443 
  444             // wait for new samples
  445             int wait = snd_pcm_wait(m_alsa_pcm, 100);
  446             if(wait < 0) {
  447                 if(wait == -EPIPE) {
  448                     ALSARecoverAfterOverrun(m_alsa_pcm);
  449                     PushAudioHole();
  450                     continue;
  451                 } else {
  452                     Logger::LogError("[ALSAInput::InputThread] " + Logger::tr("Error: Can't wait for new samples!"));
  453                     throw ALSAException();
  454                 }
  455             } else if(wait == 0) {
  456                 continue;
  457             }
  458 
  459             int64_t timestamp = hrt_time_micro();
  460 
  461             // read the samples
  462             snd_pcm_sframes_t samples_read = snd_pcm_readi(m_alsa_pcm, buffer.GetData(), m_period_size);
  463             if(samples_read < 0) {
  464                 if(samples_read == -EPIPE) {
  465                     ALSARecoverAfterOverrun(m_alsa_pcm);
  466                     PushAudioHole();
  467                     continue;
  468                 } else {
  469                     Logger::LogError("[ALSAInput::InputThread] " + Logger::tr("Error: Can't read samples!"));
  470                     throw ALSAException();
  471                 }
  472             } else if(samples_read == 0) {
  473                 continue;
  474             }
  475 
  476             // skip the first samples
  477             if(has_first_samples) {
  478                 if(timestamp > first_timestamp + START_DELAY) {
  479 
  480                     // convert if needed
  481                     if(m_convert_24_to_32) {
  482                         int32_t *samples = (int32_t*) buffer.GetData();
  483                         for(unsigned int i = 0; i < (unsigned int) samples_read * m_channels; ++i) {
  484                             samples[i] <<= 8;
  485                         }
  486                     }
  487 
  488                     // push the samples
  489                     int64_t time = timestamp - (int64_t) samples_read * (int64_t) 1000000 / (int64_t) m_sample_rate;
  490                     PushAudioSamples(m_channels, m_sample_rate, m_sample_format, samples_read, buffer.GetData(), time);
  491 
  492                 }
  493             } else {
  494                 has_first_samples = true;
  495                 first_timestamp = timestamp;
  496             }
  497 
  498         }
  499 
  500         Logger::LogInfo("[ALSAInput::InputThread] " + Logger::tr("Input thread stopped."));
  501 
  502     } catch(const std::exception& e) {
  503         m_error_occurred = true;
  504         Logger::LogError("[ALSAInput::InputThread] " + Logger::tr("Exception '%1' in input thread.").arg(e.what()));
  505     } catch(...) {
  506         m_error_occurred = true;
  507         Logger::LogError("[ALSAInput::InputThread] " + Logger::tr("Unknown exception in input thread."));
  508     }
  509 }
  510 
  511 #endif