2013-07-10 02:45:36 +02:00
|
|
|
/*
|
|
|
|
* 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"
|
|
|
|
|
2014-08-13 19:26:08 +02:00
|
|
|
#include "talk/sound/sounddevicelocator.h"
|
|
|
|
#include "talk/sound/soundinputstreaminterface.h"
|
|
|
|
#include "talk/sound/soundoutputstreaminterface.h"
|
2014-07-29 19:36:52 +02:00
|
|
|
#include "webrtc/base/common.h"
|
|
|
|
#include "webrtc/base/logging.h"
|
|
|
|
#include "webrtc/base/scoped_ptr.h"
|
|
|
|
#include "webrtc/base/stringutils.h"
|
|
|
|
#include "webrtc/base/timeutils.h"
|
|
|
|
#include "webrtc/base/worker.h"
|
2013-07-10 02:45:36 +02:00
|
|
|
|
|
|
|
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.
|
2014-07-29 19:36:52 +02:00
|
|
|
rtc::replace_substrs(kAlsaDescriptionSearch,
|
2013-07-10 02:45:36 +02:00
|
|
|
sizeof(kAlsaDescriptionSearch) - 1,
|
|
|
|
kAlsaDescriptionReplace,
|
|
|
|
sizeof(kAlsaDescriptionReplace) - 1,
|
|
|
|
&name_);
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual SoundDeviceLocator *Copy() const {
|
|
|
|
return new AlsaDeviceLocator(*this);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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 = symbol_table()->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 = symbol_table()->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 = symbol_table()->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 = symbol_table()->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.
|
2014-07-29 19:36:52 +02:00
|
|
|
return delay * rtc::kNumMicrosecsPerSec / freq_;
|
2013-07-10 02:45:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 = symbol_table()->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.
|
|
|
|
symbol_table()->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 = symbol_table()->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 = symbol_table()->snd_pcm_drop()(handle_);
|
|
|
|
if (err != 0) {
|
|
|
|
LOG(LS_ERROR) << "snd_pcm_drop(): " << GetError(err);
|
|
|
|
// Continue anyways.
|
|
|
|
}
|
|
|
|
err = symbol_table()->snd_pcm_close()(handle_);
|
|
|
|
if (err != 0) {
|
|
|
|
LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err);
|
|
|
|
// Continue anyways.
|
|
|
|
}
|
|
|
|
handle_ = NULL;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
AlsaSymbolTable *symbol_table() {
|
|
|
|
return &alsa_->symbol_table_;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Implementation of an input stream. See soundinputstreaminterface.h regarding
|
|
|
|
// thread-safety.
|
|
|
|
class AlsaInputStream :
|
|
|
|
public SoundInputStreamInterface,
|
2014-07-29 19:36:52 +02:00
|
|
|
private rtc::Worker {
|
2013-07-10 02:45:36 +02:00
|
|
|
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 = stream_.symbol_table()->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_;
|
2014-07-29 19:36:52 +02:00
|
|
|
rtc::scoped_ptr<char[]> buffer_;
|
2013-07-10 02:45:36 +02:00
|
|
|
size_t buffer_size_;
|
|
|
|
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(AlsaInputStream);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Implementation of an output stream. See soundoutputstreaminterface.h
|
|
|
|
// regarding thread-safety.
|
|
|
|
class AlsaOutputStream :
|
|
|
|
public SoundOutputStreamInterface,
|
2014-07-29 19:36:52 +02:00
|
|
|
private rtc::Worker {
|
2013-07-10 02:45:36 +02:00
|
|
|
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 = stream_.symbol_table()->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);
|
|
|
|
};
|
|
|
|
|
|
|
|
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 = symbol_table_.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 = symbol_table_.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 = symbol_table_.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 &&
|
2014-07-29 19:36:52 +02:00
|
|
|
!rtc::starts_with(name, ignore_prefix)) {
|
2013-07-10 02:45:36 +02:00
|
|
|
|
|
|
|
// Yes, we do.
|
|
|
|
char *desc = symbol_table_.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 = symbol_table_.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(static_cast<int>(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 = symbol_table_.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.
|
2014-07-29 19:36:52 +02:00
|
|
|
latency = rtc::kNumMicrosecsPerSec *
|
2013-07-10 02:45:36 +02:00
|
|
|
params.latency /
|
|
|
|
params.freq /
|
|
|
|
FrameSize(params);
|
|
|
|
// And this is what we'll actually use.
|
2014-07-29 19:36:52 +02:00
|
|
|
latency = rtc::_max(latency, kMinimumLatencyUsecs);
|
2013-07-10 02:45:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT(static_cast<int>(params.format) <
|
|
|
|
ARRAY_SIZE(kCricketFormatToAlsaFormatTable));
|
|
|
|
|
|
|
|
err = symbol_table_.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 = symbol_table_.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.
|
2014-07-29 19:36:52 +02:00
|
|
|
2 * latency / rtc::kNumMicrosecsPerMillisec,
|
2013-07-10 02:45:36 +02:00
|
|
|
params.flags,
|
|
|
|
params.freq);
|
|
|
|
if (stream) {
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
// Else fall through.
|
|
|
|
|
|
|
|
fail:
|
|
|
|
err = symbol_table_.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 = symbol_table_.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 symbol_table_.snd_strerror()(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace cricket
|