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 ¶ms) {
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 ¶ms) {
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 ¶ms) {
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 ¶ms,
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