blob: c2be190db746681362881e9b3c0f2943418059aa [file] [log] [blame]
henrike@webrtc.org91bac042014-08-26 22:04:04 +00001/*
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
23namespace rtc {
24
25// Lookup table from the rtc format enum in soundsysteminterface.h to
26// ALSA's enums.
27static 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.
33static 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.
41static const int kMinimumLatencyUsecs = 20 * 1000;
42
43// The latency we'll use for kNoLatencyRequirements (chosen arbitrarily).
44static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2;
45
46// We translate newlines in ALSA device descriptions to hyphens.
47static const char kAlsaDescriptionSearch[] = "\n";
48static const char kAlsaDescriptionReplace[] = " - ";
49
50class 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.
70class 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.
230class 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.
336class 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
439AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {}
440
441AlsaSoundSystem::~AlsaSoundSystem() {
442 // Not really necessary, because Terminate() doesn't really do anything.
443 Terminate();
444}
445
446bool 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
463void 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
474bool AlsaSoundSystem::EnumeratePlaybackDevices(
475 SoundDeviceLocatorList *devices) {
476 return EnumerateDevices(devices, false);
477}
478
479bool AlsaSoundSystem::EnumerateCaptureDevices(
480 SoundDeviceLocatorList *devices) {
481 return EnumerateDevices(devices, true);
482}
483
484bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) {
485 return GetDefaultDevice(device);
486}
487
488bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) {
489 return GetDefaultDevice(device);
490}
491
492SoundOutputStreamInterface *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
502SoundInputStreamInterface *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
512const char *AlsaSoundSystem::GetName() const {
513 return "ALSA";
514}
515
516bool 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
601bool AlsaSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) {
602 if (!IsInitialized()) {
603 return false;
604 }
605 *device = new AlsaDeviceLocator("Default device", "default");
606 return true;
607}
608
609inline 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
615template <typename StreamInterface>
616StreamInterface *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
710SoundOutputStreamInterface *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
721SoundInputStreamInterface *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
740inline const char *AlsaSoundSystem::GetError(int err) {
741 return symbol_table_.snd_strerror()(err);
742}
743
744} // namespace rtc