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