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 |