| OLD | NEW |
| (Empty) |
| 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 | |
| OLD | NEW |