Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / libjingle / source / talk / sound / alsasoundsystem.cc
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/sound/sounddevicelocator.h"
31 #include "talk/sound/soundinputstreaminterface.h"
32 #include "talk/sound/soundoutputstreaminterface.h"
33 #include "webrtc/base/common.h"
34 #include "webrtc/base/logging.h"
35 #include "webrtc/base/scoped_ptr.h"
36 #include "webrtc/base/stringutils.h"
37 #include "webrtc/base/timeutils.h"
38 #include "webrtc/base/worker.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     rtc::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 * rtc::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 rtc::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   rtc::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 rtc::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         !rtc::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 = rtc::kNumMicrosecsPerSec *
676         params.latency /
677         params.freq /
678         FrameSize(params);
679     // And this is what we'll actually use.
680     latency = rtc::_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 / rtc::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