blob: de9e2d67fcaa9337b2c3be65a38df1d1c8a93511 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
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
40namespace cricket {
41
42// Lookup table from the cricket format enum in soundsysteminterface.h to
43// ALSA's enums.
44static 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.
50static 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.
58static const int kMinimumLatencyUsecs = 20 * 1000;
59
60// The latency we'll use for kNoLatencyRequirements (chosen arbitrarily).
61static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2;
62
63// We translate newlines in ALSA device descriptions to hyphens.
64static const char kAlsaDescriptionSearch[] = "\n";
65static const char kAlsaDescriptionReplace[] = " - ";
66
67class 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.
87class 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.
247class 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.
353class 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
456AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {}
457
458AlsaSoundSystem::~AlsaSoundSystem() {
459 // Not really necessary, because Terminate() doesn't really do anything.
460 Terminate();
461}
462
463bool 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
480void 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
491bool AlsaSoundSystem::EnumeratePlaybackDevices(
492 SoundDeviceLocatorList *devices) {
493 return EnumerateDevices(devices, false);
494}
495
496bool AlsaSoundSystem::EnumerateCaptureDevices(
497 SoundDeviceLocatorList *devices) {
498 return EnumerateDevices(devices, true);
499}
500
501bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) {
502 return GetDefaultDevice(device);
503}
504
505bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) {
506 return GetDefaultDevice(device);
507}
508
509SoundOutputStreamInterface *AlsaSoundSystem::OpenPlaybackDevice(
510 const SoundDeviceLocator *device,
511 const OpenParams &params) {
512 return OpenDevice<SoundOutputStreamInterface>(
513 device,
514 params,
515 SND_PCM_STREAM_PLAYBACK,
516 &AlsaSoundSystem::StartOutputStream);
517}
518
519SoundInputStreamInterface *AlsaSoundSystem::OpenCaptureDevice(
520 const SoundDeviceLocator *device,
521 const OpenParams &params) {
522 return OpenDevice<SoundInputStreamInterface>(
523 device,
524 params,
525 SND_PCM_STREAM_CAPTURE,
526 &AlsaSoundSystem::StartInputStream);
527}
528
529const char *AlsaSoundSystem::GetName() const {
530 return "ALSA";
531}
532
533bool 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
618bool AlsaSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) {
619 if (!IsInitialized()) {
620 return false;
621 }
622 *device = new AlsaDeviceLocator("Default device", "default");
623 return true;
624}
625
626inline size_t AlsaSoundSystem::FrameSize(const OpenParams &params) {
627 ASSERT(static_cast<int>(params.format) <
628 ARRAY_SIZE(kCricketFormatToSampleSizeTable));
629 return kCricketFormatToSampleSizeTable[params.format] * params.channels;
630}
631
632template <typename StreamInterface>
633StreamInterface *AlsaSoundSystem::OpenDevice(
634 const SoundDeviceLocator *device,
635 const OpenParams &params,
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
727SoundOutputStreamInterface *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
738SoundInputStreamInterface *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
757inline const char *AlsaSoundSystem::GetError(int err) {
758 return symbol_table_.snd_strerror()(err);
759}
760
761} // namespace cricket