2 * Copyright 2004 The WebRTC Project Authors. All rights reserved.
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.
11 #include "webrtc/sound/alsasoundsystem.h"
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"
25 // Lookup table from the rtc format enum in soundsysteminterface.h to
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,
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
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;
43 // The latency we'll use for kNoLatencyRequirements (chosen arbitrarily).
44 static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2;
46 // We translate newlines in ALSA device descriptions to hyphens.
47 static const char kAlsaDescriptionSearch[] = "\n";
48 static const char kAlsaDescriptionReplace[] = " - ";
50 class AlsaDeviceLocator : public SoundDeviceLocator {
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,
64 virtual SoundDeviceLocator *Copy() const {
65 return new AlsaDeviceLocator(*this);
69 // Functionality that is common to both AlsaInputStream and AlsaOutputStream.
72 AlsaStream(AlsaSoundSystem *alsa,
80 frame_size_(frame_size),
81 wait_timeout_ms_(wait_timeout_ms),
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_);
100 LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames);
103 } else if (frames > 0) {
104 // Already ready, so no need to wait.
107 // Else no space/data available, so must wait.
108 int ready = symbol_table()->snd_pcm_wait()(handle_, wait_timeout_ms_);
110 LOG(LS_ERROR) << "snd_pcm_wait(): " << GetError(ready);
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";
120 // Else ready > 0 (i.e., 1), so it's ready. Get count.
121 frames = symbol_table()->snd_pcm_avail_update()(handle_);
123 LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames);
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";
134 int CurrentDelayUsecs() {
135 if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) {
139 snd_pcm_sframes_t delay;
140 int err = symbol_table()->snd_pcm_delay()(handle_, &delay);
142 LOG(LS_ERROR) << "snd_pcm_delay(): " << GetError(err);
144 // We'd rather continue playout/capture with an incorrect delay than stop
145 // it altogether, so return a valid value.
148 // The delay is in frames. Convert to microseconds.
149 return delay * rtc::kNumMicrosecsPerSec / freq_;
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) {
157 err = symbol_table()->snd_pcm_recover()(
160 // Silent; i.e., no logging on stderr.
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
166 LOG(LS_ERROR) << "Unable to recover from \"" << GetError(error) << "\": "
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_);
176 LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err);
186 err = symbol_table()->snd_pcm_drop()(handle_);
188 LOG(LS_ERROR) << "snd_pcm_drop(): " << GetError(err);
191 err = symbol_table()->snd_pcm_close()(handle_);
193 LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err);
201 AlsaSymbolTable *symbol_table() {
202 return &alsa_->symbol_table_;
205 snd_pcm_t *handle() {
209 const char *GetError(int err) {
210 return alsa_->GetError(err);
213 size_t frame_size() {
218 AlsaSoundSystem *alsa_;
221 int wait_timeout_ms_;
225 DISALLOW_COPY_AND_ASSIGN(AlsaStream);
228 // Implementation of an input stream. See soundinputstreaminterface.h regarding
230 class AlsaInputStream :
231 public SoundInputStreamInterface,
232 private rtc::Worker {
234 AlsaInputStream(AlsaSoundSystem *alsa,
240 : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq),
244 virtual ~AlsaInputStream() {
245 bool success = StopReading();
246 // We need that to live.
250 virtual bool StartReading() {
254 virtual bool StopReading() {
258 virtual bool GetVolume(int *volume) {
259 // TODO: Implement this.
263 virtual bool SetVolume(int volume) {
264 // TODO: Implement this.
268 virtual bool Close() {
269 return StopReading() && stream_.Close();
272 virtual int LatencyUsecs() {
273 return stream_.CurrentDelayUsecs();
277 // Inherited from Worker.
278 virtual void OnStart() {
282 // Inherited from Worker.
283 virtual void OnHaveWork() {
284 // Block waiting for data.
285 snd_pcm_uframes_t avail = stream_.Wait();
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]);
294 // Read all the data.
295 snd_pcm_sframes_t read = stream_.symbol_table()->snd_pcm_readi()(
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.
305 LOG(LS_ERROR) << "No data?";
307 // Got data. Pass it off to the app.
308 SignalSamplesRead(buffer_.get(),
309 read * stream_.frame_size(),
313 // Check for more data with no delay, after any pending messages are
318 // Inherited from Worker.
319 virtual void OnStop() {
323 const char *GetError(int err) {
324 return stream_.GetError(err);
328 rtc::scoped_ptr<char[]> buffer_;
331 DISALLOW_COPY_AND_ASSIGN(AlsaInputStream);
334 // Implementation of an output stream. See soundoutputstreaminterface.h
335 // regarding thread-safety.
336 class AlsaOutputStream :
337 public SoundOutputStreamInterface,
338 private rtc::Worker {
340 AlsaOutputStream(AlsaSoundSystem *alsa,
346 : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq) {
349 virtual ~AlsaOutputStream() {
350 bool success = DisableBufferMonitoring();
351 // We need that to live.
355 virtual bool EnableBufferMonitoring() {
359 virtual bool DisableBufferMonitoring() {
363 virtual bool WriteSamples(const void *sample_data,
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.)
370 LOG(LS_ERROR) << "Writes with fractional frames are not supported";
373 snd_pcm_uframes_t frames = size / stream_.frame_size();
374 snd_pcm_sframes_t written = stream_.symbol_table()->snd_pcm_writei()(
379 LOG(LS_ERROR) << "snd_pcm_writei(): " << GetError(written);
380 stream_.Recover(written);
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
391 virtual bool GetVolume(int *volume) {
392 // TODO: Implement this.
396 virtual bool SetVolume(int volume) {
397 // TODO: Implement this.
401 virtual bool Close() {
402 return DisableBufferMonitoring() && stream_.Close();
405 virtual int LatencyUsecs() {
406 return stream_.CurrentDelayUsecs();
410 // Inherited from Worker.
411 virtual void OnStart() {
415 // Inherited from Worker.
416 virtual void OnHaveWork() {
417 snd_pcm_uframes_t avail = stream_.Wait();
419 size_t space = avail * stream_.frame_size();
420 SignalBufferSpace(space, this);
425 // Inherited from Worker.
426 virtual void OnStop() {
430 const char *GetError(int err) {
431 return stream_.GetError(err);
436 DISALLOW_COPY_AND_ASSIGN(AlsaOutputStream);
439 AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {}
441 AlsaSoundSystem::~AlsaSoundSystem() {
442 // Not really necessary, because Terminate() doesn't really do anything.
446 bool AlsaSoundSystem::Init() {
447 if (IsInitialized()) {
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";
463 void AlsaSoundSystem::Terminate() {
464 if (!IsInitialized()) {
468 initialized_ = false;
470 // We do not unload the symbol table because we may need it again soon if
471 // Init() is called again.
474 bool AlsaSoundSystem::EnumeratePlaybackDevices(
475 SoundDeviceLocatorList *devices) {
476 return EnumerateDevices(devices, false);
479 bool AlsaSoundSystem::EnumerateCaptureDevices(
480 SoundDeviceLocatorList *devices) {
481 return EnumerateDevices(devices, true);
484 bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) {
485 return GetDefaultDevice(device);
488 bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) {
489 return GetDefaultDevice(device);
492 SoundOutputStreamInterface *AlsaSoundSystem::OpenPlaybackDevice(
493 const SoundDeviceLocator *device,
494 const OpenParams ¶ms) {
495 return OpenDevice<SoundOutputStreamInterface>(
498 SND_PCM_STREAM_PLAYBACK,
499 &AlsaSoundSystem::StartOutputStream);
502 SoundInputStreamInterface *AlsaSoundSystem::OpenCaptureDevice(
503 const SoundDeviceLocator *device,
504 const OpenParams ¶ms) {
505 return OpenDevice<SoundInputStreamInterface>(
508 SND_PCM_STREAM_CAPTURE,
509 &AlsaSoundSystem::StartInputStream);
512 const char *AlsaSoundSystem::GetName() const {
516 bool AlsaSoundSystem::EnumerateDevices(
517 SoundDeviceLocatorList *devices,
518 bool capture_not_playback) {
519 ClearSoundDeviceLocatorList(devices);
521 if (!IsInitialized()) {
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
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.)
540 err = symbol_table_.snd_device_name_hint()(-1, // All cards
541 "pcm", // Only PCM devices
544 LOG(LS_ERROR) << "snd_device_name_hint(): " << GetError(err);
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);
554 // Wrong type of device (i.e., input vs. output).
559 char *name = symbol_table_.snd_device_name_get_hint()(*list, "NAME");
561 LOG(LS_ERROR) << "Device has no name???";
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)) {
573 char *desc = symbol_table_.snd_device_name_get_hint()(*list, "DESC");
575 // Virtual devices don't necessarily have descriptions. Use their names
576 // instead (not pretty!).
580 AlsaDeviceLocator *device = new AlsaDeviceLocator(desc, name);
582 devices->push_back(device);
592 err = symbol_table_.snd_device_name_free_hint()(hints);
594 LOG(LS_ERROR) << "snd_device_name_free_hint(): " << GetError(err);
595 // Continue and return true anyways, since we did get the whole list.
601 bool AlsaSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) {
602 if (!IsInitialized()) {
605 *device = new AlsaDeviceLocator("Default device", "default");
609 inline size_t AlsaSoundSystem::FrameSize(const OpenParams ¶ms) {
610 ASSERT(static_cast<int>(params.format) <
611 ARRAY_SIZE(kCricketFormatToSampleSizeTable));
612 return kCricketFormatToSampleSizeTable[params.format] * params.channels;
615 template <typename StreamInterface>
616 StreamInterface *AlsaSoundSystem::OpenDevice(
617 const SoundDeviceLocator *device,
618 const OpenParams ¶ms,
619 snd_pcm_stream_t type,
620 StreamInterface *(AlsaSoundSystem::*start_fn)(
627 if (!IsInitialized()) {
631 StreamInterface *stream;
634 const char *dev = static_cast<const AlsaDeviceLocator *>(device)->
635 device_name().c_str();
637 snd_pcm_t *handle = NULL;
638 err = symbol_table_.snd_pcm_open()(
645 LOG(LS_ERROR) << "snd_pcm_open(" << dev << "): " << GetError(err);
648 LOG(LS_VERBOSE) << "Opening " << dev;
649 ASSERT(handle); // If open succeeded, handle ought to be valid
651 // Compute requested latency in microseconds.
653 if (params.latency == kNoLatencyRequirements) {
654 latency = kDefaultLatencyUsecs;
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 *
662 // And this is what we'll actually use.
663 latency = rtc::_max(latency, kMinimumLatencyUsecs);
666 ASSERT(static_cast<int>(params.format) <
667 ARRAY_SIZE(kCricketFormatToAlsaFormatTable));
669 err = symbol_table_.snd_pcm_set_params()(
671 kCricketFormatToAlsaFormatTable[params.format],
672 // SoundSystemInterface only supports interleaved audio.
673 SND_PCM_ACCESS_RW_INTERLEAVED,
676 1, // Allow ALSA to resample.
679 LOG(LS_ERROR) << "snd_pcm_set_params(): " << GetError(err);
683 err = symbol_table_.snd_pcm_prepare()(handle);
685 LOG(LS_ERROR) << "snd_pcm_prepare(): " << GetError(err);
689 stream = (this->*start_fn)(
692 // We set the wait time to twice the requested latency, so that wait
693 // timeouts should be rare.
694 2 * latency / rtc::kNumMicrosecsPerMillisec,
700 // Else fall through.
703 err = symbol_table_.snd_pcm_close()(handle);
705 LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err);
710 SoundOutputStreamInterface *AlsaSoundSystem::StartOutputStream(
716 // Nothing to do here but instantiate the stream.
717 return new AlsaOutputStream(
718 this, handle, frame_size, wait_timeout_ms, flags, freq);
721 SoundInputStreamInterface *AlsaSoundSystem::StartInputStream(
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
731 err = symbol_table_.snd_pcm_start()(handle);
733 LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err);
736 return new AlsaInputStream(
737 this, handle, frame_size, wait_timeout_ms, flags, freq);
740 inline const char *AlsaSoundSystem::GetError(int err) {
741 return symbol_table_.snd_strerror()(err);