Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / webrtc / sound / alsasoundsystem.cc
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