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