Home | History | Annotate | Download | only in sound
      1 /*
      2  *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 
     11 #include "webrtc/sound/alsasoundsystem.h"
     12 
     13 #include <algorithm>
     14 #include <string>
     15 
     16 #include "webrtc/base/arraysize.h"
     17 #include "webrtc/base/common.h"
     18 #include "webrtc/base/logging.h"
     19 #include "webrtc/base/scoped_ptr.h"
     20 #include "webrtc/base/stringutils.h"
     21 #include "webrtc/base/timeutils.h"
     22 #include "webrtc/base/worker.h"
     23 #include "webrtc/sound/sounddevicelocator.h"
     24 #include "webrtc/sound/soundinputstreaminterface.h"
     25 #include "webrtc/sound/soundoutputstreaminterface.h"
     26 
     27 namespace rtc {
     28 
     29 // Lookup table from the rtc format enum in soundsysteminterface.h to
     30 // ALSA's enums.
     31 static const snd_pcm_format_t kCricketFormatToAlsaFormatTable[] = {
     32   // The order here must match the order in soundsysteminterface.h
     33   SND_PCM_FORMAT_S16_LE,
     34 };
     35 
     36 // Lookup table for the size of a single sample of a given format.
     37 static const size_t kCricketFormatToSampleSizeTable[] = {
     38   // The order here must match the order in soundsysteminterface.h
     39   sizeof(int16_t),  // 2
     40 };
     41 
     42 // Minimum latency we allow, in microseconds. This is more or less arbitrary,
     43 // but it has to be at least large enough to be able to buffer data during a
     44 // missed context switch, and the typical Linux scheduling quantum is 10ms.
     45 static const int kMinimumLatencyUsecs = 20 * 1000;
     46 
     47 // The latency we'll use for kNoLatencyRequirements (chosen arbitrarily).
     48 static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2;
     49 
     50 // We translate newlines in ALSA device descriptions to hyphens.
     51 static const char kAlsaDescriptionSearch[] = "\n";
     52 static const char kAlsaDescriptionReplace[] = " - ";
     53 
     54 class AlsaDeviceLocator : public SoundDeviceLocator {
     55  public:
     56   AlsaDeviceLocator(const std::string &name,
     57                     const std::string &device_name)
     58       : SoundDeviceLocator(name, device_name) {
     59     // The ALSA descriptions have newlines in them, which won't show up in
     60     // a drop-down box. Replace them with hyphens.
     61     rtc::replace_substrs(kAlsaDescriptionSearch,
     62                                sizeof(kAlsaDescriptionSearch) - 1,
     63                                kAlsaDescriptionReplace,
     64                                sizeof(kAlsaDescriptionReplace) - 1,
     65                                &name_);
     66   }
     67 
     68   SoundDeviceLocator *Copy() const override {
     69     return new AlsaDeviceLocator(*this);
     70   }
     71 };
     72 
     73 // Functionality that is common to both AlsaInputStream and AlsaOutputStream.
     74 class AlsaStream {
     75  public:
     76   AlsaStream(AlsaSoundSystem *alsa,
     77              snd_pcm_t *handle,
     78              size_t frame_size,
     79              int wait_timeout_ms,
     80              int flags,
     81              int freq)
     82       : alsa_(alsa),
     83         handle_(handle),
     84         frame_size_(frame_size),
     85         wait_timeout_ms_(wait_timeout_ms),
     86         flags_(flags),
     87         freq_(freq) {
     88   }
     89 
     90   ~AlsaStream() {
     91     Close();
     92   }
     93 
     94   // Waits for the stream to be ready to accept/return more data, and returns
     95   // how much can be written/read, or 0 if we need to Wait() again.
     96   snd_pcm_uframes_t Wait() {
     97     snd_pcm_sframes_t frames;
     98     // Ideally we would not use snd_pcm_wait() and instead hook snd_pcm_poll_*
     99     // into PhysicalSocketServer, but PhysicalSocketServer is nasty enough
    100     // already and the current clients of SoundSystemInterface do not run
    101     // anything else on their worker threads, so snd_pcm_wait() is good enough.
    102     frames = symbol_table()->snd_pcm_avail_update()(handle_);
    103     if (frames < 0) {
    104       LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames);
    105       Recover(frames);
    106       return 0;
    107     } else if (frames > 0) {
    108       // Already ready, so no need to wait.
    109       return frames;
    110     }
    111     // Else no space/data available, so must wait.
    112     int ready = symbol_table()->snd_pcm_wait()(handle_, wait_timeout_ms_);
    113     if (ready < 0) {
    114       LOG(LS_ERROR) << "snd_pcm_wait(): " << GetError(ready);
    115       Recover(ready);
    116       return 0;
    117     } else if (ready == 0) {
    118       // Timeout, so nothing can be written/read right now.
    119       // We set the timeout to twice the requested latency, so continuous
    120       // timeouts are indicative of a problem, so log as a warning.
    121       LOG(LS_WARNING) << "Timeout while waiting on stream";
    122       return 0;
    123     }
    124     // Else ready > 0 (i.e., 1), so it's ready. Get count.
    125     frames = symbol_table()->snd_pcm_avail_update()(handle_);
    126     if (frames < 0) {
    127       LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames);
    128       Recover(frames);
    129       return 0;
    130     } else if (frames == 0) {
    131       // wait() said we were ready, so this ought to have been positive. Has
    132       // been observed to happen in practice though.
    133       LOG(LS_WARNING) << "Spurious wake-up";
    134     }
    135     return frames;
    136   }
    137 
    138   int CurrentDelayUsecs() {
    139     if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) {
    140       return 0;
    141     }
    142 
    143     snd_pcm_sframes_t delay;
    144     int err = symbol_table()->snd_pcm_delay()(handle_, &delay);
    145     if (err != 0) {
    146       LOG(LS_ERROR) << "snd_pcm_delay(): " << GetError(err);
    147       Recover(err);
    148       // We'd rather continue playout/capture with an incorrect delay than stop
    149       // it altogether, so return a valid value.
    150       return 0;
    151     }
    152     // The delay is in frames. Convert to microseconds.
    153     return delay * rtc::kNumMicrosecsPerSec / freq_;
    154   }
    155 
    156   // Used to recover from certain recoverable errors, principally buffer overrun
    157   // or underrun (identified as EPIPE). Without calling this the stream stays
    158   // in the error state forever.
    159   bool Recover(int error) {
    160     int err;
    161     err = symbol_table()->snd_pcm_recover()(
    162         handle_,
    163         error,
    164         // Silent; i.e., no logging on stderr.
    165         1);
    166     if (err != 0) {
    167       // Docs say snd_pcm_recover returns the original error if it is not one
    168       // of the recoverable ones, so this log message will probably contain the
    169       // same error twice.
    170       LOG(LS_ERROR) << "Unable to recover from \"" << GetError(error) << "\": "
    171                     << GetError(err);
    172       return false;
    173     }
    174     if (error == -EPIPE &&  // Buffer underrun/overrun.
    175         symbol_table()->snd_pcm_stream()(handle_) == SND_PCM_STREAM_CAPTURE) {
    176       // For capture streams we also have to repeat the explicit start() to get
    177       // data flowing again.
    178       err = symbol_table()->snd_pcm_start()(handle_);
    179       if (err != 0) {
    180         LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err);
    181         return false;
    182       }
    183     }
    184     return true;
    185   }
    186 
    187   bool Close() {
    188     if (handle_) {
    189       int err;
    190       err = symbol_table()->snd_pcm_drop()(handle_);
    191       if (err != 0) {
    192         LOG(LS_ERROR) << "snd_pcm_drop(): " << GetError(err);
    193         // Continue anyways.
    194       }
    195       err = symbol_table()->snd_pcm_close()(handle_);
    196       if (err != 0) {
    197         LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err);
    198         // Continue anyways.
    199       }
    200       handle_ = NULL;
    201     }
    202     return true;
    203   }
    204 
    205   AlsaSymbolTable *symbol_table() {
    206     return &alsa_->symbol_table_;
    207   }
    208 
    209   snd_pcm_t *handle() {
    210     return handle_;
    211   }
    212 
    213   const char *GetError(int err) {
    214     return alsa_->GetError(err);
    215   }
    216 
    217   size_t frame_size() {
    218     return frame_size_;
    219   }
    220 
    221  private:
    222   AlsaSoundSystem *alsa_;
    223   snd_pcm_t *handle_;
    224   size_t frame_size_;
    225   int wait_timeout_ms_;
    226   int flags_;
    227   int freq_;
    228 
    229   RTC_DISALLOW_COPY_AND_ASSIGN(AlsaStream);
    230 };
    231 
    232 // Implementation of an input stream. See soundinputstreaminterface.h regarding
    233 // thread-safety.
    234 class AlsaInputStream :
    235     public SoundInputStreamInterface,
    236     private rtc::Worker {
    237  public:
    238   AlsaInputStream(AlsaSoundSystem *alsa,
    239                   snd_pcm_t *handle,
    240                   size_t frame_size,
    241                   int wait_timeout_ms,
    242                   int flags,
    243                   int freq)
    244       : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq),
    245         buffer_size_(0) {
    246   }
    247 
    248   ~AlsaInputStream() override {
    249     bool success = StopReading();
    250     // We need that to live.
    251     VERIFY(success);
    252   }
    253 
    254   bool StartReading() override {
    255     return StartWork();
    256   }
    257 
    258   bool StopReading() override {
    259     return StopWork();
    260   }
    261 
    262   bool GetVolume(int *volume) override {
    263     // TODO(henrika): Implement this.
    264     return false;
    265   }
    266 
    267   bool SetVolume(int volume) override {
    268     // TODO(henrika): Implement this.
    269     return false;
    270   }
    271 
    272   bool Close() override {
    273     return StopReading() && stream_.Close();
    274   }
    275 
    276   int LatencyUsecs() override {
    277     return stream_.CurrentDelayUsecs();
    278   }
    279 
    280  private:
    281   // Inherited from Worker.
    282   void OnStart() override {
    283     HaveWork();
    284   }
    285 
    286   // Inherited from Worker.
    287   void OnHaveWork() override {
    288     // Block waiting for data.
    289     snd_pcm_uframes_t avail = stream_.Wait();
    290     if (avail > 0) {
    291       // Data is available.
    292       size_t size = avail * stream_.frame_size();
    293       if (size > buffer_size_) {
    294         // Must increase buffer size.
    295         buffer_.reset(new char[size]);
    296         buffer_size_ = size;
    297       }
    298       // Read all the data.
    299       snd_pcm_sframes_t read = stream_.symbol_table()->snd_pcm_readi()(
    300           stream_.handle(),
    301           buffer_.get(),
    302           avail);
    303       if (read < 0) {
    304         LOG(LS_ERROR) << "snd_pcm_readi(): " << GetError(read);
    305         stream_.Recover(read);
    306       } else if (read == 0) {
    307         // Docs say this shouldn't happen.
    308         ASSERT(false);
    309         LOG(LS_ERROR) << "No data?";
    310       } else {
    311         // Got data. Pass it off to the app.
    312         SignalSamplesRead(buffer_.get(),
    313                           read * stream_.frame_size(),
    314                           this);
    315       }
    316     }
    317     // Check for more data with no delay, after any pending messages are
    318     // dispatched.
    319     HaveWork();
    320   }
    321 
    322   // Inherited from Worker.
    323   void OnStop() override {
    324     // Nothing to do.
    325   }
    326 
    327   const char *GetError(int err) {
    328     return stream_.GetError(err);
    329   }
    330 
    331   AlsaStream stream_;
    332   rtc::scoped_ptr<char[]> buffer_;
    333   size_t buffer_size_;
    334 
    335   RTC_DISALLOW_COPY_AND_ASSIGN(AlsaInputStream);
    336 };
    337 
    338 // Implementation of an output stream. See soundoutputstreaminterface.h
    339 // regarding thread-safety.
    340 class AlsaOutputStream : public SoundOutputStreamInterface,
    341                          private rtc::Worker {
    342  public:
    343   AlsaOutputStream(AlsaSoundSystem *alsa,
    344                    snd_pcm_t *handle,
    345                    size_t frame_size,
    346                    int wait_timeout_ms,
    347                    int flags,
    348                    int freq)
    349       : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq) {
    350   }
    351 
    352   ~AlsaOutputStream() override {
    353     bool success = DisableBufferMonitoring();
    354     // We need that to live.
    355     VERIFY(success);
    356   }
    357 
    358   bool EnableBufferMonitoring() override {
    359     return StartWork();
    360   }
    361 
    362   bool DisableBufferMonitoring() override {
    363     return StopWork();
    364   }
    365 
    366   bool WriteSamples(const void *sample_data, size_t size) override {
    367     if (size % stream_.frame_size() != 0) {
    368       // No client of SoundSystemInterface does this, so let's not support it.
    369       // (If we wanted to support it, we'd basically just buffer the fractional
    370       // frame until we get more data.)
    371       ASSERT(false);
    372       LOG(LS_ERROR) << "Writes with fractional frames are not supported";
    373       return false;
    374     }
    375     snd_pcm_uframes_t frames = size / stream_.frame_size();
    376     snd_pcm_sframes_t written = stream_.symbol_table()->snd_pcm_writei()(
    377         stream_.handle(),
    378         sample_data,
    379         frames);
    380     if (written < 0) {
    381       LOG(LS_ERROR) << "snd_pcm_writei(): " << GetError(written);
    382       stream_.Recover(written);
    383       return false;
    384     } else if (static_cast<snd_pcm_uframes_t>(written) < frames) {
    385       // Shouldn't happen. Drop the rest of the data.
    386       LOG(LS_ERROR) << "Stream wrote only " << written << " of " << frames
    387                     << " frames!";
    388       return false;
    389     }
    390     return true;
    391   }
    392 
    393   bool GetVolume(int *volume) override {
    394     // TODO(henrika): Implement this.
    395     return false;
    396   }
    397 
    398   bool SetVolume(int volume) override {
    399     // TODO(henrika): Implement this.
    400     return false;
    401   }
    402 
    403   bool Close() override {
    404     return DisableBufferMonitoring() && stream_.Close();
    405   }
    406 
    407   int LatencyUsecs() override {
    408     return stream_.CurrentDelayUsecs();
    409   }
    410 
    411  private:
    412   // Inherited from Worker.
    413   void OnStart() override {
    414     HaveWork();
    415   }
    416 
    417   // Inherited from Worker.
    418   void OnHaveWork() override {
    419     snd_pcm_uframes_t avail = stream_.Wait();
    420     if (avail > 0) {
    421       size_t space = avail * stream_.frame_size();
    422       SignalBufferSpace(space, this);
    423     }
    424     HaveWork();
    425   }
    426 
    427   // Inherited from Worker.
    428   void OnStop() override {
    429     // Nothing to do.
    430   }
    431 
    432   const char *GetError(int err) {
    433     return stream_.GetError(err);
    434   }
    435 
    436   AlsaStream stream_;
    437 
    438   RTC_DISALLOW_COPY_AND_ASSIGN(AlsaOutputStream);
    439 };
    440 
    441 AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {}
    442 
    443 AlsaSoundSystem::~AlsaSoundSystem() {
    444   // Not really necessary, because Terminate() doesn't really do anything.
    445   Terminate();
    446 }
    447 
    448 bool AlsaSoundSystem::Init() {
    449   if (IsInitialized()) {
    450     return true;
    451   }
    452 
    453   // Load libasound.
    454   if (!symbol_table_.Load()) {
    455     // Very odd for a Linux machine to not have a working libasound ...
    456     LOG(LS_ERROR) << "Failed to load symbol table";
    457     return false;
    458   }
    459 
    460   initialized_ = true;
    461 
    462   return true;
    463 }
    464 
    465 void AlsaSoundSystem::Terminate() {
    466   if (!IsInitialized()) {
    467     return;
    468   }
    469 
    470   initialized_ = false;
    471 
    472   // We do not unload the symbol table because we may need it again soon if
    473   // Init() is called again.
    474 }
    475 
    476 bool AlsaSoundSystem::EnumeratePlaybackDevices(
    477     SoundDeviceLocatorList *devices) {
    478   return EnumerateDevices(devices, false);
    479 }
    480 
    481 bool AlsaSoundSystem::EnumerateCaptureDevices(
    482     SoundDeviceLocatorList *devices) {
    483   return EnumerateDevices(devices, true);
    484 }
    485 
    486 bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) {
    487   return GetDefaultDevice(device);
    488 }
    489 
    490 bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) {
    491   return GetDefaultDevice(device);
    492 }
    493 
    494 SoundOutputStreamInterface *AlsaSoundSystem::OpenPlaybackDevice(
    495     const SoundDeviceLocator *device,
    496     const OpenParams &params) {
    497   return OpenDevice<SoundOutputStreamInterface>(
    498       device,
    499       params,
    500       SND_PCM_STREAM_PLAYBACK,
    501       &AlsaSoundSystem::StartOutputStream);
    502 }
    503 
    504 SoundInputStreamInterface *AlsaSoundSystem::OpenCaptureDevice(
    505     const SoundDeviceLocator *device,
    506     const OpenParams &params) {
    507   return OpenDevice<SoundInputStreamInterface>(
    508       device,
    509       params,
    510       SND_PCM_STREAM_CAPTURE,
    511       &AlsaSoundSystem::StartInputStream);
    512 }
    513 
    514 const char *AlsaSoundSystem::GetName() const {
    515   return "ALSA";
    516 }
    517 
    518 bool AlsaSoundSystem::EnumerateDevices(
    519     SoundDeviceLocatorList *devices,
    520     bool capture_not_playback) {
    521   ClearSoundDeviceLocatorList(devices);
    522 
    523   if (!IsInitialized()) {
    524     return false;
    525   }
    526 
    527   const char *type = capture_not_playback ? "Input" : "Output";
    528   // dmix and dsnoop are only for playback and capture, respectively, but ALSA
    529   // stupidly includes them in both lists.
    530   const char *ignore_prefix = capture_not_playback ? "dmix:" : "dsnoop:";
    531   // (ALSA lists many more "devices" of questionable interest, but we show them
    532   // just in case the weird devices may actually be desirable for some
    533   // users/systems.)
    534   const char *ignore_default = "default";
    535   const char *ignore_null = "null";
    536   const char *ignore_pulse = "pulse";
    537   // The 'pulse' entry has a habit of mysteriously disappearing when you query
    538   // a second time. Remove it from our list. (GIPS lib did the same thing.)
    539   int err;
    540 
    541   void **hints;
    542   err = symbol_table_.snd_device_name_hint()(-1,     // All cards
    543                                              "pcm",  // Only PCM devices
    544                                              &hints);
    545   if (err != 0) {
    546     LOG(LS_ERROR) << "snd_device_name_hint(): " << GetError(err);
    547     return false;
    548   }
    549 
    550   for (void **list = hints; *list != NULL; ++list) {
    551     char *actual_type = symbol_table_.snd_device_name_get_hint()(*list, "IOID");
    552     if (actual_type) {  // NULL means it's both.
    553       bool wrong_type = (strcmp(actual_type, type) != 0);
    554       free(actual_type);
    555       if (wrong_type) {
    556         // Wrong type of device (i.e., input vs. output).
    557         continue;
    558       }
    559     }
    560 
    561     char *name = symbol_table_.snd_device_name_get_hint()(*list, "NAME");
    562     if (!name) {
    563       LOG(LS_ERROR) << "Device has no name???";
    564       // Skip it.
    565       continue;
    566     }
    567 
    568     // Now check if we actually want to show this device.
    569     if (strcmp(name, ignore_default) != 0 &&
    570         strcmp(name, ignore_null) != 0 &&
    571         strcmp(name, ignore_pulse) != 0 &&
    572         !rtc::starts_with(name, ignore_prefix)) {
    573       // Yes, we do.
    574       char *desc = symbol_table_.snd_device_name_get_hint()(*list, "DESC");
    575       if (!desc) {
    576         // Virtual devices don't necessarily have descriptions. Use their names
    577         // instead (not pretty!).
    578         desc = name;
    579       }
    580 
    581       AlsaDeviceLocator *device = new AlsaDeviceLocator(desc, name);
    582 
    583       devices->push_back(device);
    584 
    585       if (desc != name) {
    586         free(desc);
    587       }
    588     }
    589 
    590     free(name);
    591   }
    592 
    593   err = symbol_table_.snd_device_name_free_hint()(hints);
    594   if (err != 0) {
    595     LOG(LS_ERROR) << "snd_device_name_free_hint(): " << GetError(err);
    596     // Continue and return true anyways, since we did get the whole list.
    597   }
    598 
    599   return true;
    600 }
    601 
    602 bool AlsaSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) {
    603   if (!IsInitialized()) {
    604     return false;
    605   }
    606   *device = new AlsaDeviceLocator("Default device", "default");
    607   return true;
    608 }
    609 
    610 inline size_t AlsaSoundSystem::FrameSize(const OpenParams &params) {
    611   return kCricketFormatToSampleSizeTable[params.format] * params.channels;
    612 }
    613 
    614 template <typename StreamInterface>
    615 StreamInterface *AlsaSoundSystem::OpenDevice(
    616     const SoundDeviceLocator *device,
    617     const OpenParams &params,
    618     snd_pcm_stream_t type,
    619     StreamInterface *(AlsaSoundSystem::*start_fn)(
    620         snd_pcm_t *handle,
    621         size_t frame_size,
    622         int wait_timeout_ms,
    623         int flags,
    624         int freq)) {
    625   if (!IsInitialized()) {
    626     return NULL;
    627   }
    628 
    629   StreamInterface *stream;
    630   int err;
    631 
    632   const char *dev = static_cast<const AlsaDeviceLocator *>(device)->
    633       device_name().c_str();
    634 
    635   snd_pcm_t *handle = NULL;
    636   err = symbol_table_.snd_pcm_open()(
    637       &handle,
    638       dev,
    639       type,
    640       // No flags.
    641       0);
    642   if (err != 0) {
    643     LOG(LS_ERROR) << "snd_pcm_open(" << dev << "): " << GetError(err);
    644     return NULL;
    645   }
    646   LOG(LS_VERBOSE) << "Opening " << dev;
    647   ASSERT(handle);  // If open succeeded, handle ought to be valid
    648 
    649   // Compute requested latency in microseconds.
    650   int latency;
    651   if (params.latency == kNoLatencyRequirements) {
    652     latency = kDefaultLatencyUsecs;
    653   } else {
    654     // kLowLatency is 0, so we treat it the same as a request for zero latency.
    655     // Compute what the user asked for.
    656     latency = rtc::kNumMicrosecsPerSec *
    657         params.latency /
    658         params.freq /
    659         FrameSize(params);
    660     // And this is what we'll actually use.
    661     latency = std::max(latency, kMinimumLatencyUsecs);
    662   }
    663 
    664   ASSERT(params.format < arraysize(kCricketFormatToAlsaFormatTable));
    665 
    666   err = symbol_table_.snd_pcm_set_params()(
    667       handle,
    668       kCricketFormatToAlsaFormatTable[params.format],
    669       // SoundSystemInterface only supports interleaved audio.
    670       SND_PCM_ACCESS_RW_INTERLEAVED,
    671       params.channels,
    672       params.freq,
    673       1,  // Allow ALSA to resample.
    674       latency);
    675   if (err != 0) {
    676     LOG(LS_ERROR) << "snd_pcm_set_params(): " << GetError(err);
    677     goto fail;
    678   }
    679 
    680   err = symbol_table_.snd_pcm_prepare()(handle);
    681   if (err != 0) {
    682     LOG(LS_ERROR) << "snd_pcm_prepare(): " << GetError(err);
    683     goto fail;
    684   }
    685 
    686   stream = (this->*start_fn)(
    687       handle,
    688       FrameSize(params),
    689       // We set the wait time to twice the requested latency, so that wait
    690       // timeouts should be rare.
    691       2 * latency / rtc::kNumMicrosecsPerMillisec,
    692       params.flags,
    693       params.freq);
    694   if (stream) {
    695     return stream;
    696   }
    697   // Else fall through.
    698 
    699  fail:
    700   err = symbol_table_.snd_pcm_close()(handle);
    701   if (err != 0) {
    702     LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err);
    703   }
    704   return NULL;
    705 }
    706 
    707 SoundOutputStreamInterface *AlsaSoundSystem::StartOutputStream(
    708     snd_pcm_t *handle,
    709     size_t frame_size,
    710     int wait_timeout_ms,
    711     int flags,
    712     int freq) {
    713   // Nothing to do here but instantiate the stream.
    714   return new AlsaOutputStream(
    715       this, handle, frame_size, wait_timeout_ms, flags, freq);
    716 }
    717 
    718 SoundInputStreamInterface *AlsaSoundSystem::StartInputStream(
    719     snd_pcm_t *handle,
    720     size_t frame_size,
    721     int wait_timeout_ms,
    722     int flags,
    723     int freq) {
    724   // Output streams start automatically once enough data has been written, but
    725   // input streams must be started manually or else snd_pcm_wait() will never
    726   // return true.
    727   int err;
    728   err = symbol_table_.snd_pcm_start()(handle);
    729   if (err != 0) {
    730     LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err);
    731     return NULL;
    732   }
    733   return new AlsaInputStream(
    734       this, handle, frame_size, wait_timeout_ms, flags, freq);
    735 }
    736 
    737 inline const char *AlsaSoundSystem::GetError(int err) {
    738   return symbol_table_.snd_strerror()(err);
    739 }
    740 
    741 }  // namespace rtc
    742