1 /* 2 * libjingle 3 * Copyright 2004--2010, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "talk/sound/alsasoundsystem.h" 29 30 #include "talk/base/common.h" 31 #include "talk/base/logging.h" 32 #include "talk/base/scoped_ptr.h" 33 #include "talk/base/stringutils.h" 34 #include "talk/base/timeutils.h" 35 #include "talk/base/worker.h" 36 #include "talk/sound/sounddevicelocator.h" 37 #include "talk/sound/soundinputstreaminterface.h" 38 #include "talk/sound/soundoutputstreaminterface.h" 39 40 namespace cricket { 41 42 // Lookup table from the cricket format enum in soundsysteminterface.h to 43 // ALSA's enums. 44 static const snd_pcm_format_t kCricketFormatToAlsaFormatTable[] = { 45 // The order here must match the order in soundsysteminterface.h 46 SND_PCM_FORMAT_S16_LE, 47 }; 48 49 // Lookup table for the size of a single sample of a given format. 50 static const size_t kCricketFormatToSampleSizeTable[] = { 51 // The order here must match the order in soundsysteminterface.h 52 sizeof(int16_t), // 2 53 }; 54 55 // Minimum latency we allow, in microseconds. This is more or less arbitrary, 56 // but it has to be at least large enough to be able to buffer data during a 57 // missed context switch, and the typical Linux scheduling quantum is 10ms. 58 static const int kMinimumLatencyUsecs = 20 * 1000; 59 60 // The latency we'll use for kNoLatencyRequirements (chosen arbitrarily). 61 static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2; 62 63 // We translate newlines in ALSA device descriptions to hyphens. 64 static const char kAlsaDescriptionSearch[] = "\n"; 65 static const char kAlsaDescriptionReplace[] = " - "; 66 67 class AlsaDeviceLocator : public SoundDeviceLocator { 68 public: 69 AlsaDeviceLocator(const std::string &name, 70 const std::string &device_name) 71 : SoundDeviceLocator(name, device_name) { 72 // The ALSA descriptions have newlines in them, which won't show up in 73 // a drop-down box. Replace them with hyphens. 74 talk_base::replace_substrs(kAlsaDescriptionSearch, 75 sizeof(kAlsaDescriptionSearch) - 1, 76 kAlsaDescriptionReplace, 77 sizeof(kAlsaDescriptionReplace) - 1, 78 &name_); 79 } 80 81 virtual SoundDeviceLocator *Copy() const { 82 return new AlsaDeviceLocator(*this); 83 } 84 }; 85 86 // Functionality that is common to both AlsaInputStream and AlsaOutputStream. 87 class AlsaStream { 88 public: 89 AlsaStream(AlsaSoundSystem *alsa, 90 snd_pcm_t *handle, 91 size_t frame_size, 92 int wait_timeout_ms, 93 int flags, 94 int freq) 95 : alsa_(alsa), 96 handle_(handle), 97 frame_size_(frame_size), 98 wait_timeout_ms_(wait_timeout_ms), 99 flags_(flags), 100 freq_(freq) { 101 } 102 103 ~AlsaStream() { 104 Close(); 105 } 106 107 // Waits for the stream to be ready to accept/return more data, and returns 108 // how much can be written/read, or 0 if we need to Wait() again. 109 snd_pcm_uframes_t Wait() { 110 snd_pcm_sframes_t frames; 111 // Ideally we would not use snd_pcm_wait() and instead hook snd_pcm_poll_* 112 // into PhysicalSocketServer, but PhysicalSocketServer is nasty enough 113 // already and the current clients of SoundSystemInterface do not run 114 // anything else on their worker threads, so snd_pcm_wait() is good enough. 115 frames = symbol_table()->snd_pcm_avail_update()(handle_); 116 if (frames < 0) { 117 LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames); 118 Recover(frames); 119 return 0; 120 } else if (frames > 0) { 121 // Already ready, so no need to wait. 122 return frames; 123 } 124 // Else no space/data available, so must wait. 125 int ready = symbol_table()->snd_pcm_wait()(handle_, wait_timeout_ms_); 126 if (ready < 0) { 127 LOG(LS_ERROR) << "snd_pcm_wait(): " << GetError(ready); 128 Recover(ready); 129 return 0; 130 } else if (ready == 0) { 131 // Timeout, so nothing can be written/read right now. 132 // We set the timeout to twice the requested latency, so continuous 133 // timeouts are indicative of a problem, so log as a warning. 134 LOG(LS_WARNING) << "Timeout while waiting on stream"; 135 return 0; 136 } 137 // Else ready > 0 (i.e., 1), so it's ready. Get count. 138 frames = symbol_table()->snd_pcm_avail_update()(handle_); 139 if (frames < 0) { 140 LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames); 141 Recover(frames); 142 return 0; 143 } else if (frames == 0) { 144 // wait() said we were ready, so this ought to have been positive. Has 145 // been observed to happen in practice though. 146 LOG(LS_WARNING) << "Spurious wake-up"; 147 } 148 return frames; 149 } 150 151 int CurrentDelayUsecs() { 152 if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) { 153 return 0; 154 } 155 156 snd_pcm_sframes_t delay; 157 int err = symbol_table()->snd_pcm_delay()(handle_, &delay); 158 if (err != 0) { 159 LOG(LS_ERROR) << "snd_pcm_delay(): " << GetError(err); 160 Recover(err); 161 // We'd rather continue playout/capture with an incorrect delay than stop 162 // it altogether, so return a valid value. 163 return 0; 164 } 165 // The delay is in frames. Convert to microseconds. 166 return delay * talk_base::kNumMicrosecsPerSec / freq_; 167 } 168 169 // Used to recover from certain recoverable errors, principally buffer overrun 170 // or underrun (identified as EPIPE). Without calling this the stream stays 171 // in the error state forever. 172 bool Recover(int error) { 173 int err; 174 err = symbol_table()->snd_pcm_recover()( 175 handle_, 176 error, 177 // Silent; i.e., no logging on stderr. 178 1); 179 if (err != 0) { 180 // Docs say snd_pcm_recover returns the original error if it is not one 181 // of the recoverable ones, so this log message will probably contain the 182 // same error twice. 183 LOG(LS_ERROR) << "Unable to recover from \"" << GetError(error) << "\": " 184 << GetError(err); 185 return false; 186 } 187 if (error == -EPIPE && // Buffer underrun/overrun. 188 symbol_table()->snd_pcm_stream()(handle_) == SND_PCM_STREAM_CAPTURE) { 189 // For capture streams we also have to repeat the explicit start() to get 190 // data flowing again. 191 err = symbol_table()->snd_pcm_start()(handle_); 192 if (err != 0) { 193 LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err); 194 return false; 195 } 196 } 197 return true; 198 } 199 200 bool Close() { 201 if (handle_) { 202 int err; 203 err = symbol_table()->snd_pcm_drop()(handle_); 204 if (err != 0) { 205 LOG(LS_ERROR) << "snd_pcm_drop(): " << GetError(err); 206 // Continue anyways. 207 } 208 err = symbol_table()->snd_pcm_close()(handle_); 209 if (err != 0) { 210 LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err); 211 // Continue anyways. 212 } 213 handle_ = NULL; 214 } 215 return true; 216 } 217 218 AlsaSymbolTable *symbol_table() { 219 return &alsa_->symbol_table_; 220 } 221 222 snd_pcm_t *handle() { 223 return handle_; 224 } 225 226 const char *GetError(int err) { 227 return alsa_->GetError(err); 228 } 229 230 size_t frame_size() { 231 return frame_size_; 232 } 233 234 private: 235 AlsaSoundSystem *alsa_; 236 snd_pcm_t *handle_; 237 size_t frame_size_; 238 int wait_timeout_ms_; 239 int flags_; 240 int freq_; 241 242 DISALLOW_COPY_AND_ASSIGN(AlsaStream); 243 }; 244 245 // Implementation of an input stream. See soundinputstreaminterface.h regarding 246 // thread-safety. 247 class AlsaInputStream : 248 public SoundInputStreamInterface, 249 private talk_base::Worker { 250 public: 251 AlsaInputStream(AlsaSoundSystem *alsa, 252 snd_pcm_t *handle, 253 size_t frame_size, 254 int wait_timeout_ms, 255 int flags, 256 int freq) 257 : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq), 258 buffer_size_(0) { 259 } 260 261 virtual ~AlsaInputStream() { 262 bool success = StopReading(); 263 // We need that to live. 264 VERIFY(success); 265 } 266 267 virtual bool StartReading() { 268 return StartWork(); 269 } 270 271 virtual bool StopReading() { 272 return StopWork(); 273 } 274 275 virtual bool GetVolume(int *volume) { 276 // TODO: Implement this. 277 return false; 278 } 279 280 virtual bool SetVolume(int volume) { 281 // TODO: Implement this. 282 return false; 283 } 284 285 virtual bool Close() { 286 return StopReading() && stream_.Close(); 287 } 288 289 virtual int LatencyUsecs() { 290 return stream_.CurrentDelayUsecs(); 291 } 292 293 private: 294 // Inherited from Worker. 295 virtual void OnStart() { 296 HaveWork(); 297 } 298 299 // Inherited from Worker. 300 virtual void OnHaveWork() { 301 // Block waiting for data. 302 snd_pcm_uframes_t avail = stream_.Wait(); 303 if (avail > 0) { 304 // Data is available. 305 size_t size = avail * stream_.frame_size(); 306 if (size > buffer_size_) { 307 // Must increase buffer size. 308 buffer_.reset(new char[size]); 309 buffer_size_ = size; 310 } 311 // Read all the data. 312 snd_pcm_sframes_t read = stream_.symbol_table()->snd_pcm_readi()( 313 stream_.handle(), 314 buffer_.get(), 315 avail); 316 if (read < 0) { 317 LOG(LS_ERROR) << "snd_pcm_readi(): " << GetError(read); 318 stream_.Recover(read); 319 } else if (read == 0) { 320 // Docs say this shouldn't happen. 321 ASSERT(false); 322 LOG(LS_ERROR) << "No data?"; 323 } else { 324 // Got data. Pass it off to the app. 325 SignalSamplesRead(buffer_.get(), 326 read * stream_.frame_size(), 327 this); 328 } 329 } 330 // Check for more data with no delay, after any pending messages are 331 // dispatched. 332 HaveWork(); 333 } 334 335 // Inherited from Worker. 336 virtual void OnStop() { 337 // Nothing to do. 338 } 339 340 const char *GetError(int err) { 341 return stream_.GetError(err); 342 } 343 344 AlsaStream stream_; 345 talk_base::scoped_array<char> buffer_; 346 size_t buffer_size_; 347 348 DISALLOW_COPY_AND_ASSIGN(AlsaInputStream); 349 }; 350 351 // Implementation of an output stream. See soundoutputstreaminterface.h 352 // regarding thread-safety. 353 class AlsaOutputStream : 354 public SoundOutputStreamInterface, 355 private talk_base::Worker { 356 public: 357 AlsaOutputStream(AlsaSoundSystem *alsa, 358 snd_pcm_t *handle, 359 size_t frame_size, 360 int wait_timeout_ms, 361 int flags, 362 int freq) 363 : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq) { 364 } 365 366 virtual ~AlsaOutputStream() { 367 bool success = DisableBufferMonitoring(); 368 // We need that to live. 369 VERIFY(success); 370 } 371 372 virtual bool EnableBufferMonitoring() { 373 return StartWork(); 374 } 375 376 virtual bool DisableBufferMonitoring() { 377 return StopWork(); 378 } 379 380 virtual bool WriteSamples(const void *sample_data, 381 size_t size) { 382 if (size % stream_.frame_size() != 0) { 383 // No client of SoundSystemInterface does this, so let's not support it. 384 // (If we wanted to support it, we'd basically just buffer the fractional 385 // frame until we get more data.) 386 ASSERT(false); 387 LOG(LS_ERROR) << "Writes with fractional frames are not supported"; 388 return false; 389 } 390 snd_pcm_uframes_t frames = size / stream_.frame_size(); 391 snd_pcm_sframes_t written = stream_.symbol_table()->snd_pcm_writei()( 392 stream_.handle(), 393 sample_data, 394 frames); 395 if (written < 0) { 396 LOG(LS_ERROR) << "snd_pcm_writei(): " << GetError(written); 397 stream_.Recover(written); 398 return false; 399 } else if (static_cast<snd_pcm_uframes_t>(written) < frames) { 400 // Shouldn't happen. Drop the rest of the data. 401 LOG(LS_ERROR) << "Stream wrote only " << written << " of " << frames 402 << " frames!"; 403 return false; 404 } 405 return true; 406 } 407 408 virtual bool GetVolume(int *volume) { 409 // TODO: Implement this. 410 return false; 411 } 412 413 virtual bool SetVolume(int volume) { 414 // TODO: Implement this. 415 return false; 416 } 417 418 virtual bool Close() { 419 return DisableBufferMonitoring() && stream_.Close(); 420 } 421 422 virtual int LatencyUsecs() { 423 return stream_.CurrentDelayUsecs(); 424 } 425 426 private: 427 // Inherited from Worker. 428 virtual void OnStart() { 429 HaveWork(); 430 } 431 432 // Inherited from Worker. 433 virtual void OnHaveWork() { 434 snd_pcm_uframes_t avail = stream_.Wait(); 435 if (avail > 0) { 436 size_t space = avail * stream_.frame_size(); 437 SignalBufferSpace(space, this); 438 } 439 HaveWork(); 440 } 441 442 // Inherited from Worker. 443 virtual void OnStop() { 444 // Nothing to do. 445 } 446 447 const char *GetError(int err) { 448 return stream_.GetError(err); 449 } 450 451 AlsaStream stream_; 452 453 DISALLOW_COPY_AND_ASSIGN(AlsaOutputStream); 454 }; 455 456 AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {} 457 458 AlsaSoundSystem::~AlsaSoundSystem() { 459 // Not really necessary, because Terminate() doesn't really do anything. 460 Terminate(); 461 } 462 463 bool AlsaSoundSystem::Init() { 464 if (IsInitialized()) { 465 return true; 466 } 467 468 // Load libasound. 469 if (!symbol_table_.Load()) { 470 // Very odd for a Linux machine to not have a working libasound ... 471 LOG(LS_ERROR) << "Failed to load symbol table"; 472 return false; 473 } 474 475 initialized_ = true; 476 477 return true; 478 } 479 480 void AlsaSoundSystem::Terminate() { 481 if (!IsInitialized()) { 482 return; 483 } 484 485 initialized_ = false; 486 487 // We do not unload the symbol table because we may need it again soon if 488 // Init() is called again. 489 } 490 491 bool AlsaSoundSystem::EnumeratePlaybackDevices( 492 SoundDeviceLocatorList *devices) { 493 return EnumerateDevices(devices, false); 494 } 495 496 bool AlsaSoundSystem::EnumerateCaptureDevices( 497 SoundDeviceLocatorList *devices) { 498 return EnumerateDevices(devices, true); 499 } 500 501 bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) { 502 return GetDefaultDevice(device); 503 } 504 505 bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) { 506 return GetDefaultDevice(device); 507 } 508 509 SoundOutputStreamInterface *AlsaSoundSystem::OpenPlaybackDevice( 510 const SoundDeviceLocator *device, 511 const OpenParams ¶ms) { 512 return OpenDevice<SoundOutputStreamInterface>( 513 device, 514 params, 515 SND_PCM_STREAM_PLAYBACK, 516 &AlsaSoundSystem::StartOutputStream); 517 } 518 519 SoundInputStreamInterface *AlsaSoundSystem::OpenCaptureDevice( 520 const SoundDeviceLocator *device, 521 const OpenParams ¶ms) { 522 return OpenDevice<SoundInputStreamInterface>( 523 device, 524 params, 525 SND_PCM_STREAM_CAPTURE, 526 &AlsaSoundSystem::StartInputStream); 527 } 528 529 const char *AlsaSoundSystem::GetName() const { 530 return "ALSA"; 531 } 532 533 bool AlsaSoundSystem::EnumerateDevices( 534 SoundDeviceLocatorList *devices, 535 bool capture_not_playback) { 536 ClearSoundDeviceLocatorList(devices); 537 538 if (!IsInitialized()) { 539 return false; 540 } 541 542 const char *type = capture_not_playback ? "Input" : "Output"; 543 // dmix and dsnoop are only for playback and capture, respectively, but ALSA 544 // stupidly includes them in both lists. 545 const char *ignore_prefix = capture_not_playback ? "dmix:" : "dsnoop:"; 546 // (ALSA lists many more "devices" of questionable interest, but we show them 547 // just in case the weird devices may actually be desirable for some 548 // users/systems.) 549 const char *ignore_default = "default"; 550 const char *ignore_null = "null"; 551 const char *ignore_pulse = "pulse"; 552 // The 'pulse' entry has a habit of mysteriously disappearing when you query 553 // a second time. Remove it from our list. (GIPS lib did the same thing.) 554 int err; 555 556 void **hints; 557 err = symbol_table_.snd_device_name_hint()(-1, // All cards 558 "pcm", // Only PCM devices 559 &hints); 560 if (err != 0) { 561 LOG(LS_ERROR) << "snd_device_name_hint(): " << GetError(err); 562 return false; 563 } 564 565 for (void **list = hints; *list != NULL; ++list) { 566 char *actual_type = symbol_table_.snd_device_name_get_hint()(*list, "IOID"); 567 if (actual_type) { // NULL means it's both. 568 bool wrong_type = (strcmp(actual_type, type) != 0); 569 free(actual_type); 570 if (wrong_type) { 571 // Wrong type of device (i.e., input vs. output). 572 continue; 573 } 574 } 575 576 char *name = symbol_table_.snd_device_name_get_hint()(*list, "NAME"); 577 if (!name) { 578 LOG(LS_ERROR) << "Device has no name???"; 579 // Skip it. 580 continue; 581 } 582 583 // Now check if we actually want to show this device. 584 if (strcmp(name, ignore_default) != 0 && 585 strcmp(name, ignore_null) != 0 && 586 strcmp(name, ignore_pulse) != 0 && 587 !talk_base::starts_with(name, ignore_prefix)) { 588 589 // Yes, we do. 590 char *desc = symbol_table_.snd_device_name_get_hint()(*list, "DESC"); 591 if (!desc) { 592 // Virtual devices don't necessarily have descriptions. Use their names 593 // instead (not pretty!). 594 desc = name; 595 } 596 597 AlsaDeviceLocator *device = new AlsaDeviceLocator(desc, name); 598 599 devices->push_back(device); 600 601 if (desc != name) { 602 free(desc); 603 } 604 } 605 606 free(name); 607 } 608 609 err = symbol_table_.snd_device_name_free_hint()(hints); 610 if (err != 0) { 611 LOG(LS_ERROR) << "snd_device_name_free_hint(): " << GetError(err); 612 // Continue and return true anyways, since we did get the whole list. 613 } 614 615 return true; 616 } 617 618 bool AlsaSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) { 619 if (!IsInitialized()) { 620 return false; 621 } 622 *device = new AlsaDeviceLocator("Default device", "default"); 623 return true; 624 } 625 626 inline size_t AlsaSoundSystem::FrameSize(const OpenParams ¶ms) { 627 ASSERT(static_cast<int>(params.format) < 628 ARRAY_SIZE(kCricketFormatToSampleSizeTable)); 629 return kCricketFormatToSampleSizeTable[params.format] * params.channels; 630 } 631 632 template <typename StreamInterface> 633 StreamInterface *AlsaSoundSystem::OpenDevice( 634 const SoundDeviceLocator *device, 635 const OpenParams ¶ms, 636 snd_pcm_stream_t type, 637 StreamInterface *(AlsaSoundSystem::*start_fn)( 638 snd_pcm_t *handle, 639 size_t frame_size, 640 int wait_timeout_ms, 641 int flags, 642 int freq)) { 643 644 if (!IsInitialized()) { 645 return NULL; 646 } 647 648 StreamInterface *stream; 649 int err; 650 651 const char *dev = static_cast<const AlsaDeviceLocator *>(device)-> 652 device_name().c_str(); 653 654 snd_pcm_t *handle = NULL; 655 err = symbol_table_.snd_pcm_open()( 656 &handle, 657 dev, 658 type, 659 // No flags. 660 0); 661 if (err != 0) { 662 LOG(LS_ERROR) << "snd_pcm_open(" << dev << "): " << GetError(err); 663 return NULL; 664 } 665 LOG(LS_VERBOSE) << "Opening " << dev; 666 ASSERT(handle); // If open succeeded, handle ought to be valid 667 668 // Compute requested latency in microseconds. 669 int latency; 670 if (params.latency == kNoLatencyRequirements) { 671 latency = kDefaultLatencyUsecs; 672 } else { 673 // kLowLatency is 0, so we treat it the same as a request for zero latency. 674 // Compute what the user asked for. 675 latency = talk_base::kNumMicrosecsPerSec * 676 params.latency / 677 params.freq / 678 FrameSize(params); 679 // And this is what we'll actually use. 680 latency = talk_base::_max(latency, kMinimumLatencyUsecs); 681 } 682 683 ASSERT(static_cast<int>(params.format) < 684 ARRAY_SIZE(kCricketFormatToAlsaFormatTable)); 685 686 err = symbol_table_.snd_pcm_set_params()( 687 handle, 688 kCricketFormatToAlsaFormatTable[params.format], 689 // SoundSystemInterface only supports interleaved audio. 690 SND_PCM_ACCESS_RW_INTERLEAVED, 691 params.channels, 692 params.freq, 693 1, // Allow ALSA to resample. 694 latency); 695 if (err != 0) { 696 LOG(LS_ERROR) << "snd_pcm_set_params(): " << GetError(err); 697 goto fail; 698 } 699 700 err = symbol_table_.snd_pcm_prepare()(handle); 701 if (err != 0) { 702 LOG(LS_ERROR) << "snd_pcm_prepare(): " << GetError(err); 703 goto fail; 704 } 705 706 stream = (this->*start_fn)( 707 handle, 708 FrameSize(params), 709 // We set the wait time to twice the requested latency, so that wait 710 // timeouts should be rare. 711 2 * latency / talk_base::kNumMicrosecsPerMillisec, 712 params.flags, 713 params.freq); 714 if (stream) { 715 return stream; 716 } 717 // Else fall through. 718 719 fail: 720 err = symbol_table_.snd_pcm_close()(handle); 721 if (err != 0) { 722 LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err); 723 } 724 return NULL; 725 } 726 727 SoundOutputStreamInterface *AlsaSoundSystem::StartOutputStream( 728 snd_pcm_t *handle, 729 size_t frame_size, 730 int wait_timeout_ms, 731 int flags, 732 int freq) { 733 // Nothing to do here but instantiate the stream. 734 return new AlsaOutputStream( 735 this, handle, frame_size, wait_timeout_ms, flags, freq); 736 } 737 738 SoundInputStreamInterface *AlsaSoundSystem::StartInputStream( 739 snd_pcm_t *handle, 740 size_t frame_size, 741 int wait_timeout_ms, 742 int flags, 743 int freq) { 744 // Output streams start automatically once enough data has been written, but 745 // input streams must be started manually or else snd_pcm_wait() will never 746 // return true. 747 int err; 748 err = symbol_table_.snd_pcm_start()(handle); 749 if (err != 0) { 750 LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err); 751 return NULL; 752 } 753 return new AlsaInputStream( 754 this, handle, frame_size, wait_timeout_ms, flags, freq); 755 } 756 757 inline const char *AlsaSoundSystem::GetError(int err) { 758 return symbol_table_.snd_strerror()(err); 759 } 760 761 } // namespace cricket 762