| /* |
| * libjingle |
| * Copyright 2004--2010, Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "talk/sound/alsasoundsystem.h" |
| |
| #include "talk/base/common.h" |
| #include "talk/base/logging.h" |
| #include "talk/base/scoped_ptr.h" |
| #include "talk/base/stringutils.h" |
| #include "talk/base/timeutils.h" |
| #include "talk/base/worker.h" |
| #include "talk/sound/sounddevicelocator.h" |
| #include "talk/sound/soundinputstreaminterface.h" |
| #include "talk/sound/soundoutputstreaminterface.h" |
| |
| namespace cricket { |
| |
| // Lookup table from the cricket format enum in soundsysteminterface.h to |
| // ALSA's enums. |
| static const snd_pcm_format_t kCricketFormatToAlsaFormatTable[] = { |
| // The order here must match the order in soundsysteminterface.h |
| SND_PCM_FORMAT_S16_LE, |
| }; |
| |
| // Lookup table for the size of a single sample of a given format. |
| static const size_t kCricketFormatToSampleSizeTable[] = { |
| // The order here must match the order in soundsysteminterface.h |
| sizeof(int16_t), // 2 |
| }; |
| |
| // Minimum latency we allow, in microseconds. This is more or less arbitrary, |
| // but it has to be at least large enough to be able to buffer data during a |
| // missed context switch, and the typical Linux scheduling quantum is 10ms. |
| static const int kMinimumLatencyUsecs = 20 * 1000; |
| |
| // The latency we'll use for kNoLatencyRequirements (chosen arbitrarily). |
| static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2; |
| |
| // We translate newlines in ALSA device descriptions to hyphens. |
| static const char kAlsaDescriptionSearch[] = "\n"; |
| static const char kAlsaDescriptionReplace[] = " - "; |
| |
| class AlsaDeviceLocator : public SoundDeviceLocator { |
| public: |
| AlsaDeviceLocator(const std::string &name, |
| const std::string &device_name) |
| : SoundDeviceLocator(name, device_name) { |
| // The ALSA descriptions have newlines in them, which won't show up in |
| // a drop-down box. Replace them with hyphens. |
| talk_base::replace_substrs(kAlsaDescriptionSearch, |
| sizeof(kAlsaDescriptionSearch) - 1, |
| kAlsaDescriptionReplace, |
| sizeof(kAlsaDescriptionReplace) - 1, |
| &name_); |
| } |
| |
| virtual SoundDeviceLocator *Copy() const { |
| return new AlsaDeviceLocator(*this); |
| } |
| }; |
| |
| // Accesses ALSA functions through our late-binding symbol table instead of |
| // directly. This way we don't have to link to libasound, which means our binary |
| // will load faster and we can run on strange systems that may not have |
| // libasound |
| #define LATE(sym) \ |
| LATESYM_GET(AlsaSymbolTable, &alsa_->symbol_table_, sym) |
| |
| // Functionality that is common to both AlsaInputStream and AlsaOutputStream. |
| class AlsaStream { |
| public: |
| AlsaStream(AlsaSoundSystem *alsa, |
| snd_pcm_t *handle, |
| size_t frame_size, |
| int wait_timeout_ms, |
| int flags, |
| int freq) |
| : alsa_(alsa), |
| handle_(handle), |
| frame_size_(frame_size), |
| wait_timeout_ms_(wait_timeout_ms), |
| flags_(flags), |
| freq_(freq) { |
| } |
| |
| ~AlsaStream() { |
| Close(); |
| } |
| |
| // Waits for the stream to be ready to accept/return more data, and returns |
| // how much can be written/read, or 0 if we need to Wait() again. |
| snd_pcm_uframes_t Wait() { |
| snd_pcm_sframes_t frames; |
| // Ideally we would not use snd_pcm_wait() and instead hook snd_pcm_poll_* |
| // into PhysicalSocketServer, but PhysicalSocketServer is nasty enough |
| // already and the current clients of SoundSystemInterface do not run |
| // anything else on their worker threads, so snd_pcm_wait() is good enough. |
| frames = LATE(snd_pcm_avail_update)(handle_); |
| if (frames < 0) { |
| LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames); |
| Recover(frames); |
| return 0; |
| } else if (frames > 0) { |
| // Already ready, so no need to wait. |
| return frames; |
| } |
| // Else no space/data available, so must wait. |
| int ready = LATE(snd_pcm_wait)(handle_, wait_timeout_ms_); |
| if (ready < 0) { |
| LOG(LS_ERROR) << "snd_pcm_wait(): " << GetError(ready); |
| Recover(ready); |
| return 0; |
| } else if (ready == 0) { |
| // Timeout, so nothing can be written/read right now. |
| // We set the timeout to twice the requested latency, so continuous |
| // timeouts are indicative of a problem, so log as a warning. |
| LOG(LS_WARNING) << "Timeout while waiting on stream"; |
| return 0; |
| } |
| // Else ready > 0 (i.e., 1), so it's ready. Get count. |
| frames = LATE(snd_pcm_avail_update)(handle_); |
| if (frames < 0) { |
| LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames); |
| Recover(frames); |
| return 0; |
| } else if (frames == 0) { |
| // wait() said we were ready, so this ought to have been positive. Has |
| // been observed to happen in practice though. |
| LOG(LS_WARNING) << "Spurious wake-up"; |
| } |
| return frames; |
| } |
| |
| int CurrentDelayUsecs() { |
| if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) { |
| return 0; |
| } |
| |
| snd_pcm_sframes_t delay; |
| int err = LATE(snd_pcm_delay)(handle_, &delay); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_pcm_delay(): " << GetError(err); |
| Recover(err); |
| // We'd rather continue playout/capture with an incorrect delay than stop |
| // it altogether, so return a valid value. |
| return 0; |
| } |
| // The delay is in frames. Convert to microseconds. |
| return delay * talk_base::kNumMicrosecsPerSec / freq_; |
| } |
| |
| // Used to recover from certain recoverable errors, principally buffer overrun |
| // or underrun (identified as EPIPE). Without calling this the stream stays |
| // in the error state forever. |
| bool Recover(int error) { |
| int err; |
| err = LATE(snd_pcm_recover)(handle_, |
| error, |
| // Silent; i.e., no logging on stderr. |
| 1); |
| if (err != 0) { |
| // Docs say snd_pcm_recover returns the original error if it is not one |
| // of the recoverable ones, so this log message will probably contain the |
| // same error twice. |
| LOG(LS_ERROR) << "Unable to recover from \"" << GetError(error) << "\": " |
| << GetError(err); |
| return false; |
| } |
| if (error == -EPIPE && // Buffer underrun/overrun. |
| LATE(snd_pcm_stream)(handle_) == SND_PCM_STREAM_CAPTURE) { |
| // For capture streams we also have to repeat the explicit start() to get |
| // data flowing again. |
| err = LATE(snd_pcm_start)(handle_); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool Close() { |
| if (handle_) { |
| int err; |
| err = LATE(snd_pcm_drop)(handle_); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_pcm_drop(): " << GetError(err); |
| // Continue anyways. |
| } |
| err = LATE(snd_pcm_close)(handle_); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err); |
| // Continue anyways. |
| } |
| handle_ = NULL; |
| } |
| return true; |
| } |
| |
| AlsaSoundSystem *alsa() { |
| return alsa_; |
| } |
| |
| snd_pcm_t *handle() { |
| return handle_; |
| } |
| |
| const char *GetError(int err) { |
| return alsa_->GetError(err); |
| } |
| |
| size_t frame_size() { |
| return frame_size_; |
| } |
| |
| private: |
| AlsaSoundSystem *alsa_; |
| snd_pcm_t *handle_; |
| size_t frame_size_; |
| int wait_timeout_ms_; |
| int flags_; |
| int freq_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AlsaStream); |
| }; |
| |
| // Redefine for the next two classes. |
| #undef LATE |
| #define LATE(sym) \ |
| LATESYM_GET(AlsaSymbolTable, &stream_.alsa()->symbol_table_, sym) |
| |
| // Implementation of an input stream. See soundinputstreaminterface.h regarding |
| // thread-safety. |
| class AlsaInputStream : |
| public SoundInputStreamInterface, |
| private talk_base::Worker { |
| public: |
| AlsaInputStream(AlsaSoundSystem *alsa, |
| snd_pcm_t *handle, |
| size_t frame_size, |
| int wait_timeout_ms, |
| int flags, |
| int freq) |
| : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq), |
| buffer_size_(0) { |
| } |
| |
| virtual ~AlsaInputStream() { |
| bool success = StopReading(); |
| // We need that to live. |
| VERIFY(success); |
| } |
| |
| virtual bool StartReading() { |
| return StartWork(); |
| } |
| |
| virtual bool StopReading() { |
| return StopWork(); |
| } |
| |
| virtual bool GetVolume(int *volume) { |
| // TODO: Implement this. |
| return false; |
| } |
| |
| virtual bool SetVolume(int volume) { |
| // TODO: Implement this. |
| return false; |
| } |
| |
| virtual bool Close() { |
| return StopReading() && stream_.Close(); |
| } |
| |
| virtual int LatencyUsecs() { |
| return stream_.CurrentDelayUsecs(); |
| } |
| |
| private: |
| // Inherited from Worker. |
| virtual void OnStart() { |
| HaveWork(); |
| } |
| |
| // Inherited from Worker. |
| virtual void OnHaveWork() { |
| // Block waiting for data. |
| snd_pcm_uframes_t avail = stream_.Wait(); |
| if (avail > 0) { |
| // Data is available. |
| size_t size = avail * stream_.frame_size(); |
| if (size > buffer_size_) { |
| // Must increase buffer size. |
| buffer_.reset(new char[size]); |
| buffer_size_ = size; |
| } |
| // Read all the data. |
| snd_pcm_sframes_t read = LATE(snd_pcm_readi)(stream_.handle(), |
| buffer_.get(), |
| avail); |
| if (read < 0) { |
| LOG(LS_ERROR) << "snd_pcm_readi(): " << GetError(read); |
| stream_.Recover(read); |
| } else if (read == 0) { |
| // Docs say this shouldn't happen. |
| ASSERT(false); |
| LOG(LS_ERROR) << "No data?"; |
| } else { |
| // Got data. Pass it off to the app. |
| SignalSamplesRead(buffer_.get(), |
| read * stream_.frame_size(), |
| this); |
| } |
| } |
| // Check for more data with no delay, after any pending messages are |
| // dispatched. |
| HaveWork(); |
| } |
| |
| // Inherited from Worker. |
| virtual void OnStop() { |
| // Nothing to do. |
| } |
| |
| const char *GetError(int err) { |
| return stream_.GetError(err); |
| } |
| |
| AlsaStream stream_; |
| talk_base::scoped_array<char> buffer_; |
| size_t buffer_size_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AlsaInputStream); |
| }; |
| |
| // Implementation of an output stream. See soundoutputstreaminterface.h |
| // regarding thread-safety. |
| class AlsaOutputStream : |
| public SoundOutputStreamInterface, |
| private talk_base::Worker { |
| public: |
| AlsaOutputStream(AlsaSoundSystem *alsa, |
| snd_pcm_t *handle, |
| size_t frame_size, |
| int wait_timeout_ms, |
| int flags, |
| int freq) |
| : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq) { |
| } |
| |
| virtual ~AlsaOutputStream() { |
| bool success = DisableBufferMonitoring(); |
| // We need that to live. |
| VERIFY(success); |
| } |
| |
| virtual bool EnableBufferMonitoring() { |
| return StartWork(); |
| } |
| |
| virtual bool DisableBufferMonitoring() { |
| return StopWork(); |
| } |
| |
| virtual bool WriteSamples(const void *sample_data, |
| size_t size) { |
| if (size % stream_.frame_size() != 0) { |
| // No client of SoundSystemInterface does this, so let's not support it. |
| // (If we wanted to support it, we'd basically just buffer the fractional |
| // frame until we get more data.) |
| ASSERT(false); |
| LOG(LS_ERROR) << "Writes with fractional frames are not supported"; |
| return false; |
| } |
| snd_pcm_uframes_t frames = size / stream_.frame_size(); |
| snd_pcm_sframes_t written = LATE(snd_pcm_writei)(stream_.handle(), |
| sample_data, |
| frames); |
| if (written < 0) { |
| LOG(LS_ERROR) << "snd_pcm_writei(): " << GetError(written); |
| stream_.Recover(written); |
| return false; |
| } else if (static_cast<snd_pcm_uframes_t>(written) < frames) { |
| // Shouldn't happen. Drop the rest of the data. |
| LOG(LS_ERROR) << "Stream wrote only " << written << " of " << frames |
| << " frames!"; |
| return false; |
| } |
| return true; |
| } |
| |
| virtual bool GetVolume(int *volume) { |
| // TODO: Implement this. |
| return false; |
| } |
| |
| virtual bool SetVolume(int volume) { |
| // TODO: Implement this. |
| return false; |
| } |
| |
| virtual bool Close() { |
| return DisableBufferMonitoring() && stream_.Close(); |
| } |
| |
| virtual int LatencyUsecs() { |
| return stream_.CurrentDelayUsecs(); |
| } |
| |
| private: |
| // Inherited from Worker. |
| virtual void OnStart() { |
| HaveWork(); |
| } |
| |
| // Inherited from Worker. |
| virtual void OnHaveWork() { |
| snd_pcm_uframes_t avail = stream_.Wait(); |
| if (avail > 0) { |
| size_t space = avail * stream_.frame_size(); |
| SignalBufferSpace(space, this); |
| } |
| HaveWork(); |
| } |
| |
| // Inherited from Worker. |
| virtual void OnStop() { |
| // Nothing to do. |
| } |
| |
| const char *GetError(int err) { |
| return stream_.GetError(err); |
| } |
| |
| AlsaStream stream_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AlsaOutputStream); |
| }; |
| |
| // Redefine for the main class. |
| #undef LATE |
| #define LATE(sym) LATESYM_GET(AlsaSymbolTable, &symbol_table_, sym) |
| |
| AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {} |
| |
| AlsaSoundSystem::~AlsaSoundSystem() { |
| // Not really necessary, because Terminate() doesn't really do anything. |
| Terminate(); |
| } |
| |
| bool AlsaSoundSystem::Init() { |
| if (IsInitialized()) { |
| return true; |
| } |
| |
| // Load libasound. |
| if (!symbol_table_.Load()) { |
| // Very odd for a Linux machine to not have a working libasound ... |
| LOG(LS_ERROR) << "Failed to load symbol table"; |
| return false; |
| } |
| |
| initialized_ = true; |
| |
| return true; |
| } |
| |
| void AlsaSoundSystem::Terminate() { |
| if (!IsInitialized()) { |
| return; |
| } |
| |
| initialized_ = false; |
| |
| // We do not unload the symbol table because we may need it again soon if |
| // Init() is called again. |
| } |
| |
| bool AlsaSoundSystem::EnumeratePlaybackDevices( |
| SoundDeviceLocatorList *devices) { |
| return EnumerateDevices(devices, false); |
| } |
| |
| bool AlsaSoundSystem::EnumerateCaptureDevices( |
| SoundDeviceLocatorList *devices) { |
| return EnumerateDevices(devices, true); |
| } |
| |
| bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) { |
| return GetDefaultDevice(device); |
| } |
| |
| bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) { |
| return GetDefaultDevice(device); |
| } |
| |
| SoundOutputStreamInterface *AlsaSoundSystem::OpenPlaybackDevice( |
| const SoundDeviceLocator *device, |
| const OpenParams ¶ms) { |
| return OpenDevice<SoundOutputStreamInterface>( |
| device, |
| params, |
| SND_PCM_STREAM_PLAYBACK, |
| &AlsaSoundSystem::StartOutputStream); |
| } |
| |
| SoundInputStreamInterface *AlsaSoundSystem::OpenCaptureDevice( |
| const SoundDeviceLocator *device, |
| const OpenParams ¶ms) { |
| return OpenDevice<SoundInputStreamInterface>( |
| device, |
| params, |
| SND_PCM_STREAM_CAPTURE, |
| &AlsaSoundSystem::StartInputStream); |
| } |
| |
| const char *AlsaSoundSystem::GetName() const { |
| return "ALSA"; |
| } |
| |
| bool AlsaSoundSystem::EnumerateDevices( |
| SoundDeviceLocatorList *devices, |
| bool capture_not_playback) { |
| ClearSoundDeviceLocatorList(devices); |
| |
| if (!IsInitialized()) { |
| return false; |
| } |
| |
| const char *type = capture_not_playback ? "Input" : "Output"; |
| // dmix and dsnoop are only for playback and capture, respectively, but ALSA |
| // stupidly includes them in both lists. |
| const char *ignore_prefix = capture_not_playback ? "dmix:" : "dsnoop:"; |
| // (ALSA lists many more "devices" of questionable interest, but we show them |
| // just in case the weird devices may actually be desirable for some |
| // users/systems.) |
| const char *ignore_default = "default"; |
| const char *ignore_null = "null"; |
| const char *ignore_pulse = "pulse"; |
| // The 'pulse' entry has a habit of mysteriously disappearing when you query |
| // a second time. Remove it from our list. (GIPS lib did the same thing.) |
| int err; |
| |
| void **hints; |
| err = LATE(snd_device_name_hint)(-1, // All cards |
| "pcm", // Only PCM devices |
| &hints); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_device_name_hint(): " << GetError(err); |
| return false; |
| } |
| |
| for (void **list = hints; *list != NULL; ++list) { |
| char *actual_type = LATE(snd_device_name_get_hint)(*list, "IOID"); |
| if (actual_type) { // NULL means it's both. |
| bool wrong_type = (strcmp(actual_type, type) != 0); |
| free(actual_type); |
| if (wrong_type) { |
| // Wrong type of device (i.e., input vs. output). |
| continue; |
| } |
| } |
| |
| char *name = LATE(snd_device_name_get_hint)(*list, "NAME"); |
| if (!name) { |
| LOG(LS_ERROR) << "Device has no name???"; |
| // Skip it. |
| continue; |
| } |
| |
| // Now check if we actually want to show this device. |
| if (strcmp(name, ignore_default) != 0 && |
| strcmp(name, ignore_null) != 0 && |
| strcmp(name, ignore_pulse) != 0 && |
| !talk_base::starts_with(name, ignore_prefix)) { |
| |
| // Yes, we do. |
| char *desc = LATE(snd_device_name_get_hint)(*list, "DESC"); |
| if (!desc) { |
| // Virtual devices don't necessarily have descriptions. Use their names |
| // instead (not pretty!). |
| desc = name; |
| } |
| |
| AlsaDeviceLocator *device = new AlsaDeviceLocator(desc, name); |
| |
| devices->push_back(device); |
| |
| if (desc != name) { |
| free(desc); |
| } |
| } |
| |
| free(name); |
| } |
| |
| err = LATE(snd_device_name_free_hint)(hints); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_device_name_free_hint(): " << GetError(err); |
| // Continue and return true anyways, since we did get the whole list. |
| } |
| |
| return true; |
| } |
| |
| bool AlsaSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) { |
| if (!IsInitialized()) { |
| return false; |
| } |
| *device = new AlsaDeviceLocator("Default device", "default"); |
| return true; |
| } |
| |
| inline size_t AlsaSoundSystem::FrameSize(const OpenParams ¶ms) { |
| ASSERT(params.format < ARRAY_SIZE(kCricketFormatToSampleSizeTable)); |
| return kCricketFormatToSampleSizeTable[params.format] * params.channels; |
| } |
| |
| template <typename StreamInterface> |
| StreamInterface *AlsaSoundSystem::OpenDevice( |
| const SoundDeviceLocator *device, |
| const OpenParams ¶ms, |
| snd_pcm_stream_t type, |
| StreamInterface *(AlsaSoundSystem::*start_fn)( |
| snd_pcm_t *handle, |
| size_t frame_size, |
| int wait_timeout_ms, |
| int flags, |
| int freq)) { |
| |
| if (!IsInitialized()) { |
| return NULL; |
| } |
| |
| StreamInterface *stream; |
| int err; |
| |
| const char *dev = static_cast<const AlsaDeviceLocator *>(device)-> |
| device_name().c_str(); |
| |
| snd_pcm_t *handle = NULL; |
| err = LATE(snd_pcm_open)( |
| &handle, |
| dev, |
| type, |
| // No flags. |
| 0); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_pcm_open(" << dev << "): " << GetError(err); |
| return NULL; |
| } |
| LOG(LS_VERBOSE) << "Opening " << dev; |
| ASSERT(handle); // If open succeeded, handle ought to be valid |
| |
| // Compute requested latency in microseconds. |
| int latency; |
| if (params.latency == kNoLatencyRequirements) { |
| latency = kDefaultLatencyUsecs; |
| } else { |
| // kLowLatency is 0, so we treat it the same as a request for zero latency. |
| // Compute what the user asked for. |
| latency = talk_base::kNumMicrosecsPerSec * |
| params.latency / |
| params.freq / |
| FrameSize(params); |
| // And this is what we'll actually use. |
| latency = talk_base::_max(latency, kMinimumLatencyUsecs); |
| } |
| |
| ASSERT(params.format < ARRAY_SIZE(kCricketFormatToAlsaFormatTable)); |
| |
| err = LATE(snd_pcm_set_params)(handle, |
| kCricketFormatToAlsaFormatTable[params.format], |
| // SoundSystemInterface only supports |
| // interleaved audio. |
| SND_PCM_ACCESS_RW_INTERLEAVED, |
| params.channels, |
| params.freq, |
| 1, // Allow ALSA to resample. |
| latency); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_pcm_set_params(): " << GetError(err); |
| goto fail; |
| } |
| |
| err = LATE(snd_pcm_prepare)(handle); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_pcm_prepare(): " << GetError(err); |
| goto fail; |
| } |
| |
| stream = (this->*start_fn)( |
| handle, |
| FrameSize(params), |
| // We set the wait time to twice the requested latency, so that wait |
| // timeouts should be rare. |
| 2 * latency / talk_base::kNumMicrosecsPerMillisec, |
| params.flags, |
| params.freq); |
| if (stream) { |
| return stream; |
| } |
| // Else fall through. |
| |
| fail: |
| err = LATE(snd_pcm_close)(handle); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err); |
| } |
| return NULL; |
| } |
| |
| SoundOutputStreamInterface *AlsaSoundSystem::StartOutputStream( |
| snd_pcm_t *handle, |
| size_t frame_size, |
| int wait_timeout_ms, |
| int flags, |
| int freq) { |
| // Nothing to do here but instantiate the stream. |
| return new AlsaOutputStream( |
| this, handle, frame_size, wait_timeout_ms, flags, freq); |
| } |
| |
| SoundInputStreamInterface *AlsaSoundSystem::StartInputStream( |
| snd_pcm_t *handle, |
| size_t frame_size, |
| int wait_timeout_ms, |
| int flags, |
| int freq) { |
| // Output streams start automatically once enough data has been written, but |
| // input streams must be started manually or else snd_pcm_wait() will never |
| // return true. |
| int err; |
| err = LATE(snd_pcm_start)(handle); |
| if (err != 0) { |
| LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err); |
| return NULL; |
| } |
| return new AlsaInputStream( |
| this, handle, frame_size, wait_timeout_ms, flags, freq); |
| } |
| |
| inline const char *AlsaSoundSystem::GetError(int err) { |
| return LATE(snd_strerror)(err); |
| } |
| |
| } // namespace cricket |