• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
AlsaDeviceLocator(const std::string & name,const std::string & device_name)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 
Copy() const68   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:
AlsaStream(AlsaSoundSystem * alsa,snd_pcm_t * handle,size_t frame_size,int wait_timeout_ms,int flags,int freq)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 
~AlsaStream()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.
Wait()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 
CurrentDelayUsecs()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.
Recover(int error)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 
Close()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 
symbol_table()205   AlsaSymbolTable *symbol_table() {
206     return &alsa_->symbol_table_;
207   }
208 
handle()209   snd_pcm_t *handle() {
210     return handle_;
211   }
212 
GetError(int err)213   const char *GetError(int err) {
214     return alsa_->GetError(err);
215   }
216 
frame_size()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:
AlsaInputStream(AlsaSoundSystem * alsa,snd_pcm_t * handle,size_t frame_size,int wait_timeout_ms,int flags,int freq)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 
~AlsaInputStream()248   ~AlsaInputStream() override {
249     bool success = StopReading();
250     // We need that to live.
251     VERIFY(success);
252   }
253 
StartReading()254   bool StartReading() override {
255     return StartWork();
256   }
257 
StopReading()258   bool StopReading() override {
259     return StopWork();
260   }
261 
GetVolume(int * volume)262   bool GetVolume(int *volume) override {
263     // TODO(henrika): Implement this.
264     return false;
265   }
266 
SetVolume(int volume)267   bool SetVolume(int volume) override {
268     // TODO(henrika): Implement this.
269     return false;
270   }
271 
Close()272   bool Close() override {
273     return StopReading() && stream_.Close();
274   }
275 
LatencyUsecs()276   int LatencyUsecs() override {
277     return stream_.CurrentDelayUsecs();
278   }
279 
280  private:
281   // Inherited from Worker.
OnStart()282   void OnStart() override {
283     HaveWork();
284   }
285 
286   // Inherited from Worker.
OnHaveWork()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.
OnStop()323   void OnStop() override {
324     // Nothing to do.
325   }
326 
GetError(int err)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:
AlsaOutputStream(AlsaSoundSystem * alsa,snd_pcm_t * handle,size_t frame_size,int wait_timeout_ms,int flags,int freq)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 
~AlsaOutputStream()352   ~AlsaOutputStream() override {
353     bool success = DisableBufferMonitoring();
354     // We need that to live.
355     VERIFY(success);
356   }
357 
EnableBufferMonitoring()358   bool EnableBufferMonitoring() override {
359     return StartWork();
360   }
361 
DisableBufferMonitoring()362   bool DisableBufferMonitoring() override {
363     return StopWork();
364   }
365 
WriteSamples(const void * sample_data,size_t size)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 
GetVolume(int * volume)393   bool GetVolume(int *volume) override {
394     // TODO(henrika): Implement this.
395     return false;
396   }
397 
SetVolume(int volume)398   bool SetVolume(int volume) override {
399     // TODO(henrika): Implement this.
400     return false;
401   }
402 
Close()403   bool Close() override {
404     return DisableBufferMonitoring() && stream_.Close();
405   }
406 
LatencyUsecs()407   int LatencyUsecs() override {
408     return stream_.CurrentDelayUsecs();
409   }
410 
411  private:
412   // Inherited from Worker.
OnStart()413   void OnStart() override {
414     HaveWork();
415   }
416 
417   // Inherited from Worker.
OnHaveWork()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.
OnStop()428   void OnStop() override {
429     // Nothing to do.
430   }
431 
GetError(int err)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 
AlsaSoundSystem()441 AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {}
442 
~AlsaSoundSystem()443 AlsaSoundSystem::~AlsaSoundSystem() {
444   // Not really necessary, because Terminate() doesn't really do anything.
445   Terminate();
446 }
447 
Init()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 
Terminate()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 
EnumeratePlaybackDevices(SoundDeviceLocatorList * devices)476 bool AlsaSoundSystem::EnumeratePlaybackDevices(
477     SoundDeviceLocatorList *devices) {
478   return EnumerateDevices(devices, false);
479 }
480 
EnumerateCaptureDevices(SoundDeviceLocatorList * devices)481 bool AlsaSoundSystem::EnumerateCaptureDevices(
482     SoundDeviceLocatorList *devices) {
483   return EnumerateDevices(devices, true);
484 }
485 
GetDefaultPlaybackDevice(SoundDeviceLocator ** device)486 bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) {
487   return GetDefaultDevice(device);
488 }
489 
GetDefaultCaptureDevice(SoundDeviceLocator ** device)490 bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) {
491   return GetDefaultDevice(device);
492 }
493 
OpenPlaybackDevice(const SoundDeviceLocator * device,const OpenParams & params)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 
OpenCaptureDevice(const SoundDeviceLocator * device,const OpenParams & params)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 
GetName() const514 const char *AlsaSoundSystem::GetName() const {
515   return "ALSA";
516 }
517 
EnumerateDevices(SoundDeviceLocatorList * devices,bool capture_not_playback)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 
GetDefaultDevice(SoundDeviceLocator ** device)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 
FrameSize(const OpenParams & params)610 inline size_t AlsaSoundSystem::FrameSize(const OpenParams &params) {
611   return kCricketFormatToSampleSizeTable[params.format] * params.channels;
612 }
613 
614 template <typename StreamInterface>
OpenDevice(const SoundDeviceLocator * device,const OpenParams & params,snd_pcm_stream_t type,StreamInterface * (AlsaSoundSystem::* start_fn)(snd_pcm_t * handle,size_t frame_size,int wait_timeout_ms,int flags,int freq))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 
StartOutputStream(snd_pcm_t * handle,size_t frame_size,int wait_timeout_ms,int flags,int freq)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 
StartInputStream(snd_pcm_t * handle,size_t frame_size,int wait_timeout_ms,int flags,int freq)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 
GetError(int err)737 inline const char *AlsaSoundSystem::GetError(int err) {
738   return symbol_table_.snd_strerror()(err);
739 }
740 
741 }  // namespace rtc
742