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