aboutsummaryrefslogtreecommitdiff
path: root/webrtc/sound/pulseaudiosoundsystem.cc
diff options
context:
space:
mode:
authorChih-hung Hsieh <chh@google.com>2015-12-01 17:07:48 +0000
committerandroid-build-merger <android-build-merger@google.com>2015-12-01 17:07:48 +0000
commita4acd9d6bc9b3b033d7d274316e75ee067df8d20 (patch)
tree672a185b294789cf991f385c3e395dd63bea9063 /webrtc/sound/pulseaudiosoundsystem.cc
parent3681b90ba4fe7a27232dd3e27897d5d7ed9d651c (diff)
parentfe8b4a657979b49e1701bd92f6d5814a99e0b2be (diff)
downloadwebrtc-a4acd9d6bc9b3b033d7d274316e75ee067df8d20.tar.gz
Merge changes I7bbf776e,I1b827825
am: fe8b4a6579 * commit 'fe8b4a657979b49e1701bd92f6d5814a99e0b2be': (7237 commits) WIP: Changes after merge commit 'cb3f9bd' Make the nonlinear beamformer steerable Utilize bitrate above codec max to protect video. Enable VP9 internal resize by default. Filter overlapping RTP header extensions. Make VCMEncodedFrameCallback const. MediaCodecVideoEncoder: Add number of quality resolution downscales to Encoded callback. Remove redudant encoder rate calls. Create isolate files for nonparallel tests. Register header extensions in RtpRtcpObserver to avoid log spam. Make an enum class out of NetEqDecoder, and hide the neteq_decoders_ table ACM: Move NACK functionality inside NetEq Fix chromium-style warnings in webrtc/sound/. Create a 'webrtc_nonparallel_tests' target. Update scalability structure data according to updates in the RTP payload profile. audio_coding: rename interface -> include Rewrote perform_action_on_all_files to be parallell. Update reference indices according to updates in the RTP payload profile. Disable P2PTransport...TestFailoverControlledSide on Memcheck pass clangcl compile options to ignore warnings in gflags.cc ...
Diffstat (limited to 'webrtc/sound/pulseaudiosoundsystem.cc')
-rw-r--r--webrtc/sound/pulseaudiosoundsystem.cc1541
1 files changed, 1541 insertions, 0 deletions
diff --git a/webrtc/sound/pulseaudiosoundsystem.cc b/webrtc/sound/pulseaudiosoundsystem.cc
new file mode 100644
index 0000000000..b44a1dfad7
--- /dev/null
+++ b/webrtc/sound/pulseaudiosoundsystem.cc
@@ -0,0 +1,1541 @@
+/*
+ * Copyright 2010 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/sound/pulseaudiosoundsystem.h"
+
+#ifdef HAVE_LIBPULSE
+
+#include <algorithm>
+#include "webrtc/sound/sounddevicelocator.h"
+#include "webrtc/sound/soundinputstreaminterface.h"
+#include "webrtc/sound/soundoutputstreaminterface.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/fileutils.h" // for GetApplicationName()
+#include "webrtc/base/logging.h"
+#include "webrtc/base/timeutils.h"
+#include "webrtc/base/worker.h"
+
+namespace rtc {
+
+// First PulseAudio protocol version that supports PA_STREAM_ADJUST_LATENCY.
+static const uint32_t kAdjustLatencyProtocolVersion = 13;
+
+// Lookup table from the rtc format enum in soundsysteminterface.h to
+// Pulse's enums.
+static const pa_sample_format_t kCricketFormatToPulseFormatTable[] = {
+ // The order here must match the order in soundsysteminterface.h
+ PA_SAMPLE_S16LE,
+};
+
+// Some timing constants for optimal operation. See
+// https://tango.0pointer.de/pipermail/pulseaudio-discuss/2008-January/001170.html
+// for a good explanation of some of the factors that go into this.
+
+// Playback.
+
+// For playback, there is a round-trip delay to fill the server-side playback
+// buffer, so setting too low of a latency is a buffer underflow risk. We will
+// automatically increase the latency if a buffer underflow does occur, but we
+// also enforce a sane minimum at start-up time. Anything lower would be
+// virtually guaranteed to underflow at least once, so there's no point in
+// allowing lower latencies.
+static const int kPlaybackLatencyMinimumMsecs = 20;
+// Every time a playback stream underflows, we will reconfigure it with target
+// latency that is greater by this amount.
+static const int kPlaybackLatencyIncrementMsecs = 20;
+// We also need to configure a suitable request size. Too small and we'd burn
+// CPU from the overhead of transfering small amounts of data at once. Too large
+// and the amount of data remaining in the buffer right before refilling it
+// would be a buffer underflow risk. We set it to half of the buffer size.
+static const int kPlaybackRequestFactor = 2;
+
+// Capture.
+
+// For capture, low latency is not a buffer overflow risk, but it makes us burn
+// CPU from the overhead of transfering small amounts of data at once, so we set
+// a recommended value that we use for the kLowLatency constant (but if the user
+// explicitly requests something lower then we will honour it).
+// 1ms takes about 6-7% CPU. 5ms takes about 5%. 10ms takes about 4.x%.
+static const int kLowCaptureLatencyMsecs = 10;
+// There is a round-trip delay to ack the data to the server, so the
+// server-side buffer needs extra space to prevent buffer overflow. 20ms is
+// sufficient, but there is no penalty to making it bigger, so we make it huge.
+// (750ms is libpulse's default value for the _total_ buffer size in the
+// kNoLatencyRequirements case.)
+static const int kCaptureBufferExtraMsecs = 750;
+
+static void FillPlaybackBufferAttr(int latency,
+ pa_buffer_attr *attr) {
+ attr->maxlength = latency;
+ attr->tlength = latency;
+ attr->minreq = latency / kPlaybackRequestFactor;
+ attr->prebuf = attr->tlength - attr->minreq;
+ LOG(LS_VERBOSE) << "Configuring latency = " << attr->tlength << ", minreq = "
+ << attr->minreq << ", minfill = " << attr->prebuf;
+}
+
+static pa_volume_t CricketVolumeToPulseVolume(int volume) {
+ // PA's volume space goes from 0% at PA_VOLUME_MUTED (value 0) to 100% at
+ // PA_VOLUME_NORM (value 0x10000). It can also go beyond 100% up to
+ // PA_VOLUME_MAX (value UINT32_MAX-1), but using that is probably unwise.
+ // We just linearly map the 0-255 scale of SoundSystemInterface onto
+ // PA_VOLUME_MUTED-PA_VOLUME_NORM. If the programmer exceeds kMaxVolume then
+ // they can access the over-100% features of PA.
+ return PA_VOLUME_MUTED + (PA_VOLUME_NORM - PA_VOLUME_MUTED) *
+ volume / SoundSystemInterface::kMaxVolume;
+}
+
+static int PulseVolumeToCricketVolume(pa_volume_t pa_volume) {
+ return SoundSystemInterface::kMinVolume +
+ (SoundSystemInterface::kMaxVolume - SoundSystemInterface::kMinVolume) *
+ pa_volume / PA_VOLUME_NORM;
+}
+
+static pa_volume_t MaxChannelVolume(pa_cvolume *channel_volumes) {
+ pa_volume_t pa_volume = PA_VOLUME_MUTED; // Minimum possible value.
+ for (int i = 0; i < channel_volumes->channels; ++i) {
+ if (pa_volume < channel_volumes->values[i]) {
+ pa_volume = channel_volumes->values[i];
+ }
+ }
+ return pa_volume;
+}
+
+class PulseAudioDeviceLocator : public SoundDeviceLocator {
+ public:
+ PulseAudioDeviceLocator(const std::string &name,
+ const std::string &device_name)
+ : SoundDeviceLocator(name, device_name) {
+ }
+
+ virtual SoundDeviceLocator *Copy() const {
+ return new PulseAudioDeviceLocator(*this);
+ }
+};
+
+// Functionality that is common to both PulseAudioInputStream and
+// PulseAudioOutputStream.
+class PulseAudioStream {
+ public:
+ PulseAudioStream(PulseAudioSoundSystem *pulse, pa_stream *stream, int flags)
+ : pulse_(pulse), stream_(stream), flags_(flags) {
+ }
+
+ ~PulseAudioStream() {
+ // Close() should have been called during the containing class's destructor.
+ ASSERT(stream_ == NULL);
+ }
+
+ // Must be called with the lock held.
+ bool Close() {
+ if (!IsClosed()) {
+ // Unset this here so that we don't get a TERMINATED callback.
+ symbol_table()->pa_stream_set_state_callback()(stream_, NULL, NULL);
+ if (symbol_table()->pa_stream_disconnect()(stream_) != 0) {
+ LOG(LS_ERROR) << "Can't disconnect stream";
+ // Continue and return true anyways.
+ }
+ symbol_table()->pa_stream_unref()(stream_);
+ stream_ = NULL;
+ }
+ return true;
+ }
+
+ // Must be called with the lock held.
+ int LatencyUsecs() {
+ if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) {
+ return 0;
+ }
+
+ pa_usec_t latency;
+ int negative;
+ Lock();
+ int re = symbol_table()->pa_stream_get_latency()(stream_, &latency,
+ &negative);
+ Unlock();
+ if (re != 0) {
+ LOG(LS_ERROR) << "Can't query latency";
+ // We'd rather continue playout/capture with an incorrect delay than stop
+ // it altogether, so return a valid value.
+ return 0;
+ }
+ if (negative) {
+ // The delay can be negative for monitoring streams if the captured
+ // samples haven't been played yet. In such a case, "latency" contains the
+ // magnitude, so we must negate it to get the real value.
+ return -latency;
+ } else {
+ return latency;
+ }
+ }
+
+ PulseAudioSoundSystem *pulse() {
+ return pulse_;
+ }
+
+ PulseAudioSymbolTable *symbol_table() {
+ return &pulse()->symbol_table_;
+ }
+
+ pa_stream *stream() {
+ ASSERT(stream_ != NULL);
+ return stream_;
+ }
+
+ bool IsClosed() {
+ return stream_ == NULL;
+ }
+
+ void Lock() {
+ pulse()->Lock();
+ }
+
+ void Unlock() {
+ pulse()->Unlock();
+ }
+
+ private:
+ PulseAudioSoundSystem *pulse_;
+ pa_stream *stream_;
+ int flags_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(PulseAudioStream);
+};
+
+// Implementation of an input stream. See soundinputstreaminterface.h regarding
+// thread-safety.
+class PulseAudioInputStream :
+ public SoundInputStreamInterface,
+ private rtc::Worker {
+
+ struct GetVolumeCallbackData {
+ PulseAudioInputStream *instance;
+ pa_cvolume *channel_volumes;
+ };
+
+ struct GetSourceChannelCountCallbackData {
+ PulseAudioInputStream *instance;
+ uint8_t *channels;
+ };
+
+ public:
+ PulseAudioInputStream(PulseAudioSoundSystem *pulse,
+ pa_stream *stream,
+ int flags)
+ : stream_(pulse, stream, flags),
+ temp_sample_data_(NULL),
+ temp_sample_data_size_(0) {
+ // This callback seems to never be issued, but let's set it anyways.
+ symbol_table()->pa_stream_set_overflow_callback()(stream, &OverflowCallback,
+ NULL);
+ }
+
+ virtual ~PulseAudioInputStream() {
+ bool success = Close();
+ // We need that to live.
+ VERIFY(success);
+ }
+
+ virtual bool StartReading() {
+ return StartWork();
+ }
+
+ virtual bool StopReading() {
+ return StopWork();
+ }
+
+ virtual bool GetVolume(int *volume) {
+ bool ret = false;
+
+ Lock();
+
+ // Unlike output streams, input streams have no concept of a stream volume,
+ // only a device volume. So we have to retrieve the volume of the device
+ // itself.
+
+ pa_cvolume channel_volumes;
+
+ GetVolumeCallbackData data;
+ data.instance = this;
+ data.channel_volumes = &channel_volumes;
+
+ pa_operation *op = symbol_table()->pa_context_get_source_info_by_index()(
+ stream_.pulse()->context_,
+ symbol_table()->pa_stream_get_device_index()(stream_.stream()),
+ &GetVolumeCallbackThunk,
+ &data);
+ if (!stream_.pulse()->FinishOperation(op)) {
+ goto done;
+ }
+
+ if (data.channel_volumes) {
+ // This pointer was never unset by the callback, so we must have received
+ // an empty list of infos. This probably never happens, but we code for it
+ // anyway.
+ LOG(LS_ERROR) << "Did not receive GetVolumeCallback";
+ goto done;
+ }
+
+ // We now have the volume for each channel. Each channel could have a
+ // different volume if, e.g., the user went and changed the volumes in the
+ // PA UI. To get a single volume for SoundSystemInterface we just take the
+ // maximum. Ideally we'd do so with pa_cvolume_max, but it doesn't exist in
+ // Hardy, so we do it manually.
+ pa_volume_t pa_volume;
+ pa_volume = MaxChannelVolume(&channel_volumes);
+ // Now map onto the SoundSystemInterface range.
+ *volume = PulseVolumeToCricketVolume(pa_volume);
+
+ ret = true;
+ done:
+ Unlock();
+ return ret;
+ }
+
+ virtual bool SetVolume(int volume) {
+ bool ret = false;
+ pa_volume_t pa_volume = CricketVolumeToPulseVolume(volume);
+
+ Lock();
+
+ // Unlike output streams, input streams have no concept of a stream volume,
+ // only a device volume. So we have to change the volume of the device
+ // itself.
+
+ // The device may have a different number of channels than the stream and
+ // their mapping may be different, so we don't want to use the channel count
+ // from our sample spec. We could use PA_CHANNELS_MAX to cover our bases,
+ // and the server allows that even if the device's channel count is lower,
+ // but some buggy PA clients don't like that (the pavucontrol on Hardy dies
+ // in an assert if the channel count is different). So instead we look up
+ // the actual number of channels that the device has.
+
+ uint8_t channels;
+
+ GetSourceChannelCountCallbackData data;
+ data.instance = this;
+ data.channels = &channels;
+
+ uint32_t device_index = symbol_table()->pa_stream_get_device_index()(
+ stream_.stream());
+
+ pa_operation *op = symbol_table()->pa_context_get_source_info_by_index()(
+ stream_.pulse()->context_,
+ device_index,
+ &GetSourceChannelCountCallbackThunk,
+ &data);
+ if (!stream_.pulse()->FinishOperation(op)) {
+ goto done;
+ }
+
+ if (data.channels) {
+ // This pointer was never unset by the callback, so we must have received
+ // an empty list of infos. This probably never happens, but we code for it
+ // anyway.
+ LOG(LS_ERROR) << "Did not receive GetSourceChannelCountCallback";
+ goto done;
+ }
+
+ pa_cvolume channel_volumes;
+ symbol_table()->pa_cvolume_set()(&channel_volumes, channels, pa_volume);
+
+ op = symbol_table()->pa_context_set_source_volume_by_index()(
+ stream_.pulse()->context_,
+ device_index,
+ &channel_volumes,
+ // This callback merely logs errors.
+ &SetVolumeCallback,
+ NULL);
+ if (!op) {
+ LOG(LS_ERROR) << "pa_context_set_source_volume_by_index()";
+ goto done;
+ }
+ // Don't need to wait for this to complete.
+ symbol_table()->pa_operation_unref()(op);
+
+ ret = true;
+ done:
+ Unlock();
+ return ret;
+ }
+
+ virtual bool Close() {
+ if (!StopReading()) {
+ return false;
+ }
+ bool ret = true;
+ if (!stream_.IsClosed()) {
+ Lock();
+ ret = stream_.Close();
+ Unlock();
+ }
+ return ret;
+ }
+
+ virtual int LatencyUsecs() {
+ return stream_.LatencyUsecs();
+ }
+
+ private:
+ void Lock() {
+ stream_.Lock();
+ }
+
+ void Unlock() {
+ stream_.Unlock();
+ }
+
+ PulseAudioSymbolTable *symbol_table() {
+ return stream_.symbol_table();
+ }
+
+ void EnableReadCallback() {
+ symbol_table()->pa_stream_set_read_callback()(
+ stream_.stream(),
+ &ReadCallbackThunk,
+ this);
+ }
+
+ void DisableReadCallback() {
+ symbol_table()->pa_stream_set_read_callback()(
+ stream_.stream(),
+ NULL,
+ NULL);
+ }
+
+ static void ReadCallbackThunk(pa_stream *unused1,
+ size_t unused2,
+ void *userdata) {
+ PulseAudioInputStream *instance =
+ static_cast<PulseAudioInputStream *>(userdata);
+ instance->OnReadCallback();
+ }
+
+ void OnReadCallback() {
+ // We get the data pointer and size now in order to save one Lock/Unlock
+ // on OnMessage.
+ if (symbol_table()->pa_stream_peek()(stream_.stream(),
+ &temp_sample_data_,
+ &temp_sample_data_size_) != 0) {
+ LOG(LS_ERROR) << "Can't read data!";
+ return;
+ }
+ // Since we consume the data asynchronously on a different thread, we have
+ // to temporarily disable the read callback or else Pulse will call it
+ // continuously until we consume the data. We re-enable it below.
+ DisableReadCallback();
+ HaveWork();
+ }
+
+ // Inherited from Worker.
+ virtual void OnStart() {
+ Lock();
+ EnableReadCallback();
+ Unlock();
+ }
+
+ // Inherited from Worker.
+ virtual void OnHaveWork() {
+ ASSERT(temp_sample_data_ && temp_sample_data_size_);
+ SignalSamplesRead(temp_sample_data_,
+ temp_sample_data_size_,
+ this);
+ temp_sample_data_ = NULL;
+ temp_sample_data_size_ = 0;
+
+ Lock();
+ for (;;) {
+ // Ack the last thing we read.
+ if (symbol_table()->pa_stream_drop()(stream_.stream()) != 0) {
+ LOG(LS_ERROR) << "Can't ack read data";
+ }
+
+ if (symbol_table()->pa_stream_readable_size()(stream_.stream()) <= 0) {
+ // Then that was all the data.
+ break;
+ }
+
+ // Else more data.
+ const void *sample_data;
+ size_t sample_data_size;
+ if (symbol_table()->pa_stream_peek()(stream_.stream(),
+ &sample_data,
+ &sample_data_size) != 0) {
+ LOG(LS_ERROR) << "Can't read data!";
+ break;
+ }
+
+ // Drop lock for sigslot dispatch, which could take a while.
+ Unlock();
+ SignalSamplesRead(sample_data, sample_data_size, this);
+ Lock();
+
+ // Return to top of loop for the ack and the check for more data.
+ }
+ EnableReadCallback();
+ Unlock();
+ }
+
+ // Inherited from Worker.
+ virtual void OnStop() {
+ Lock();
+ DisableReadCallback();
+ Unlock();
+ }
+
+ static void OverflowCallback(pa_stream *stream,
+ void *userdata) {
+ LOG(LS_WARNING) << "Buffer overflow on capture stream " << stream;
+ }
+
+ static void GetVolumeCallbackThunk(pa_context *unused,
+ const pa_source_info *info,
+ int eol,
+ void *userdata) {
+ GetVolumeCallbackData *data =
+ static_cast<GetVolumeCallbackData *>(userdata);
+ data->instance->OnGetVolumeCallback(info, eol, &data->channel_volumes);
+ }
+
+ void OnGetVolumeCallback(const pa_source_info *info,
+ int eol,
+ pa_cvolume **channel_volumes) {
+ if (eol) {
+ // List is over. Wake GetVolume().
+ stream_.pulse()->Signal();
+ return;
+ }
+
+ if (*channel_volumes) {
+ **channel_volumes = info->volume;
+ // Unset the pointer so that we know that we have have already copied the
+ // volume.
+ *channel_volumes = NULL;
+ } else {
+ // We have received an additional callback after the first one, which
+ // doesn't make sense for a single source. This probably never happens,
+ // but we code for it anyway.
+ LOG(LS_WARNING) << "Ignoring extra GetVolumeCallback";
+ }
+ }
+
+ static void GetSourceChannelCountCallbackThunk(pa_context *unused,
+ const pa_source_info *info,
+ int eol,
+ void *userdata) {
+ GetSourceChannelCountCallbackData *data =
+ static_cast<GetSourceChannelCountCallbackData *>(userdata);
+ data->instance->OnGetSourceChannelCountCallback(info, eol, &data->channels);
+ }
+
+ void OnGetSourceChannelCountCallback(const pa_source_info *info,
+ int eol,
+ uint8_t **channels) {
+ if (eol) {
+ // List is over. Wake SetVolume().
+ stream_.pulse()->Signal();
+ return;
+ }
+
+ if (*channels) {
+ **channels = info->channel_map.channels;
+ // Unset the pointer so that we know that we have have already copied the
+ // channel count.
+ *channels = NULL;
+ } else {
+ // We have received an additional callback after the first one, which
+ // doesn't make sense for a single source. This probably never happens,
+ // but we code for it anyway.
+ LOG(LS_WARNING) << "Ignoring extra GetSourceChannelCountCallback";
+ }
+ }
+
+ static void SetVolumeCallback(pa_context *unused1,
+ int success,
+ void *unused2) {
+ if (!success) {
+ LOG(LS_ERROR) << "Failed to change capture volume";
+ }
+ }
+
+ PulseAudioStream stream_;
+ // Temporary storage for passing data between threads.
+ const void *temp_sample_data_;
+ size_t temp_sample_data_size_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(PulseAudioInputStream);
+};
+
+// Implementation of an output stream. See soundoutputstreaminterface.h
+// regarding thread-safety.
+class PulseAudioOutputStream :
+ public SoundOutputStreamInterface,
+ private rtc::Worker {
+
+ struct GetVolumeCallbackData {
+ PulseAudioOutputStream *instance;
+ pa_cvolume *channel_volumes;
+ };
+
+ public:
+ PulseAudioOutputStream(PulseAudioSoundSystem *pulse,
+ pa_stream *stream,
+ int flags,
+ int latency)
+ : stream_(pulse, stream, flags),
+ configured_latency_(latency),
+ temp_buffer_space_(0) {
+ symbol_table()->pa_stream_set_underflow_callback()(stream,
+ &UnderflowCallbackThunk,
+ this);
+ }
+
+ virtual ~PulseAudioOutputStream() {
+ bool success = Close();
+ // 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) {
+ bool ret = true;
+ Lock();
+ if (symbol_table()->pa_stream_write()(stream_.stream(),
+ sample_data,
+ size,
+ NULL,
+ 0,
+ PA_SEEK_RELATIVE) != 0) {
+ LOG(LS_ERROR) << "Unable to write";
+ ret = false;
+ }
+ Unlock();
+ return ret;
+ }
+
+ virtual bool GetVolume(int *volume) {
+ bool ret = false;
+
+ Lock();
+
+ pa_cvolume channel_volumes;
+
+ GetVolumeCallbackData data;
+ data.instance = this;
+ data.channel_volumes = &channel_volumes;
+
+ pa_operation *op = symbol_table()->pa_context_get_sink_input_info()(
+ stream_.pulse()->context_,
+ symbol_table()->pa_stream_get_index()(stream_.stream()),
+ &GetVolumeCallbackThunk,
+ &data);
+ if (!stream_.pulse()->FinishOperation(op)) {
+ goto done;
+ }
+
+ if (data.channel_volumes) {
+ // This pointer was never unset by the callback, so we must have received
+ // an empty list of infos. This probably never happens, but we code for it
+ // anyway.
+ LOG(LS_ERROR) << "Did not receive GetVolumeCallback";
+ goto done;
+ }
+
+ // We now have the volume for each channel. Each channel could have a
+ // different volume if, e.g., the user went and changed the volumes in the
+ // PA UI. To get a single volume for SoundSystemInterface we just take the
+ // maximum. Ideally we'd do so with pa_cvolume_max, but it doesn't exist in
+ // Hardy, so we do it manually.
+ pa_volume_t pa_volume;
+ pa_volume = MaxChannelVolume(&channel_volumes);
+ // Now map onto the SoundSystemInterface range.
+ *volume = PulseVolumeToCricketVolume(pa_volume);
+
+ ret = true;
+ done:
+ Unlock();
+ return ret;
+ }
+
+ virtual bool SetVolume(int volume) {
+ bool ret = false;
+ pa_volume_t pa_volume = CricketVolumeToPulseVolume(volume);
+
+ Lock();
+
+ const pa_sample_spec *spec = symbol_table()->pa_stream_get_sample_spec()(
+ stream_.stream());
+ if (!spec) {
+ LOG(LS_ERROR) << "pa_stream_get_sample_spec()";
+ goto done;
+ }
+
+ pa_cvolume channel_volumes;
+ symbol_table()->pa_cvolume_set()(&channel_volumes, spec->channels,
+ pa_volume);
+
+ pa_operation *op;
+ op = symbol_table()->pa_context_set_sink_input_volume()(
+ stream_.pulse()->context_,
+ symbol_table()->pa_stream_get_index()(stream_.stream()),
+ &channel_volumes,
+ // This callback merely logs errors.
+ &SetVolumeCallback,
+ NULL);
+ if (!op) {
+ LOG(LS_ERROR) << "pa_context_set_sink_input_volume()";
+ goto done;
+ }
+ // Don't need to wait for this to complete.
+ symbol_table()->pa_operation_unref()(op);
+
+ ret = true;
+ done:
+ Unlock();
+ return ret;
+ }
+
+ virtual bool Close() {
+ if (!DisableBufferMonitoring()) {
+ return false;
+ }
+ bool ret = true;
+ if (!stream_.IsClosed()) {
+ Lock();
+ symbol_table()->pa_stream_set_underflow_callback()(stream_.stream(),
+ NULL,
+ NULL);
+ ret = stream_.Close();
+ Unlock();
+ }
+ return ret;
+ }
+
+ virtual int LatencyUsecs() {
+ return stream_.LatencyUsecs();
+ }
+
+#if 0
+ // TODO: Versions 0.9.16 and later of Pulse have a new API for
+ // zero-copy writes, but Hardy is not new enough to have that so we can't
+ // rely on it. Perhaps auto-detect if it's present or not and use it if we
+ // can?
+
+ virtual bool GetWriteBuffer(void **buffer, size_t *size) {
+ bool ret = true;
+ Lock();
+ if (symbol_table()->pa_stream_begin_write()(stream_.stream(), buffer, size)
+ != 0) {
+ LOG(LS_ERROR) << "Can't get write buffer";
+ ret = false;
+ }
+ Unlock();
+ return ret;
+ }
+
+ // Releases the caller's hold on the write buffer. "written" must be the
+ // amount of data that was written.
+ virtual bool ReleaseWriteBuffer(void *buffer, size_t written) {
+ bool ret = true;
+ Lock();
+ if (written == 0) {
+ if (symbol_table()->pa_stream_cancel_write()(stream_.stream()) != 0) {
+ LOG(LS_ERROR) << "Can't cancel write";
+ ret = false;
+ }
+ } else {
+ if (symbol_table()->pa_stream_write()(stream_.stream(),
+ buffer,
+ written,
+ NULL,
+ 0,
+ PA_SEEK_RELATIVE) != 0) {
+ LOG(LS_ERROR) << "Unable to write";
+ ret = false;
+ }
+ }
+ Unlock();
+ return ret;
+ }
+#endif
+
+ private:
+ void Lock() {
+ stream_.Lock();
+ }
+
+ void Unlock() {
+ stream_.Unlock();
+ }
+
+ PulseAudioSymbolTable *symbol_table() {
+ return stream_.symbol_table();
+ }
+
+ void EnableWriteCallback() {
+ pa_stream_state_t state = symbol_table()->pa_stream_get_state()(
+ stream_.stream());
+ if (state == PA_STREAM_READY) {
+ // May already have available space. Must check.
+ temp_buffer_space_ = symbol_table()->pa_stream_writable_size()(
+ stream_.stream());
+ if (temp_buffer_space_ > 0) {
+ // Yup, there is already space available, so if we register a write
+ // callback then it will not receive any event. So dispatch one ourself
+ // instead.
+ HaveWork();
+ return;
+ }
+ }
+ symbol_table()->pa_stream_set_write_callback()(
+ stream_.stream(),
+ &WriteCallbackThunk,
+ this);
+ }
+
+ void DisableWriteCallback() {
+ symbol_table()->pa_stream_set_write_callback()(
+ stream_.stream(),
+ NULL,
+ NULL);
+ }
+
+ static void WriteCallbackThunk(pa_stream *unused,
+ size_t buffer_space,
+ void *userdata) {
+ PulseAudioOutputStream *instance =
+ static_cast<PulseAudioOutputStream *>(userdata);
+ instance->OnWriteCallback(buffer_space);
+ }
+
+ void OnWriteCallback(size_t buffer_space) {
+ temp_buffer_space_ = buffer_space;
+ // Since we write the data asynchronously on a different thread, we have
+ // to temporarily disable the write callback or else Pulse will call it
+ // continuously until we write the data. We re-enable it below.
+ DisableWriteCallback();
+ HaveWork();
+ }
+
+ // Inherited from Worker.
+ virtual void OnStart() {
+ Lock();
+ EnableWriteCallback();
+ Unlock();
+ }
+
+ // Inherited from Worker.
+ virtual void OnHaveWork() {
+ ASSERT(temp_buffer_space_ > 0);
+
+ SignalBufferSpace(temp_buffer_space_, this);
+
+ temp_buffer_space_ = 0;
+ Lock();
+ EnableWriteCallback();
+ Unlock();
+ }
+
+ // Inherited from Worker.
+ virtual void OnStop() {
+ Lock();
+ DisableWriteCallback();
+ Unlock();
+ }
+
+ static void UnderflowCallbackThunk(pa_stream *unused,
+ void *userdata) {
+ PulseAudioOutputStream *instance =
+ static_cast<PulseAudioOutputStream *>(userdata);
+ instance->OnUnderflowCallback();
+ }
+
+ void OnUnderflowCallback() {
+ LOG(LS_WARNING) << "Buffer underflow on playback stream "
+ << stream_.stream();
+
+ if (configured_latency_ == SoundSystemInterface::kNoLatencyRequirements) {
+ // We didn't configure a pa_buffer_attr before, so switching to one now
+ // would be questionable.
+ return;
+ }
+
+ // Otherwise reconfigure the stream with a higher target latency.
+
+ const pa_sample_spec *spec = symbol_table()->pa_stream_get_sample_spec()(
+ stream_.stream());
+ if (!spec) {
+ LOG(LS_ERROR) << "pa_stream_get_sample_spec()";
+ return;
+ }
+
+ size_t bytes_per_sec = symbol_table()->pa_bytes_per_second()(spec);
+
+ int new_latency = configured_latency_ +
+ bytes_per_sec * kPlaybackLatencyIncrementMsecs /
+ rtc::kNumMicrosecsPerSec;
+
+ pa_buffer_attr new_attr = {0};
+ FillPlaybackBufferAttr(new_latency, &new_attr);
+
+ pa_operation *op = symbol_table()->pa_stream_set_buffer_attr()(
+ stream_.stream(),
+ &new_attr,
+ // No callback.
+ NULL,
+ NULL);
+ if (!op) {
+ LOG(LS_ERROR) << "pa_stream_set_buffer_attr()";
+ return;
+ }
+ // Don't need to wait for this to complete.
+ symbol_table()->pa_operation_unref()(op);
+
+ // Save the new latency in case we underflow again.
+ configured_latency_ = new_latency;
+ }
+
+ static void GetVolumeCallbackThunk(pa_context *unused,
+ const pa_sink_input_info *info,
+ int eol,
+ void *userdata) {
+ GetVolumeCallbackData *data =
+ static_cast<GetVolumeCallbackData *>(userdata);
+ data->instance->OnGetVolumeCallback(info, eol, &data->channel_volumes);
+ }
+
+ void OnGetVolumeCallback(const pa_sink_input_info *info,
+ int eol,
+ pa_cvolume **channel_volumes) {
+ if (eol) {
+ // List is over. Wake GetVolume().
+ stream_.pulse()->Signal();
+ return;
+ }
+
+ if (*channel_volumes) {
+ **channel_volumes = info->volume;
+ // Unset the pointer so that we know that we have have already copied the
+ // volume.
+ *channel_volumes = NULL;
+ } else {
+ // We have received an additional callback after the first one, which
+ // doesn't make sense for a single sink input. This probably never
+ // happens, but we code for it anyway.
+ LOG(LS_WARNING) << "Ignoring extra GetVolumeCallback";
+ }
+ }
+
+ static void SetVolumeCallback(pa_context *unused1,
+ int success,
+ void *unused2) {
+ if (!success) {
+ LOG(LS_ERROR) << "Failed to change playback volume";
+ }
+ }
+
+ PulseAudioStream stream_;
+ int configured_latency_;
+ // Temporary storage for passing data between threads.
+ size_t temp_buffer_space_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(PulseAudioOutputStream);
+};
+
+PulseAudioSoundSystem::PulseAudioSoundSystem()
+ : mainloop_(NULL), context_(NULL) {
+}
+
+PulseAudioSoundSystem::~PulseAudioSoundSystem() {
+ Terminate();
+}
+
+bool PulseAudioSoundSystem::Init() {
+ if (IsInitialized()) {
+ return true;
+ }
+
+ // Load libpulse.
+ if (!symbol_table_.Load()) {
+ // Most likely the Pulse library and sound server are not installed on
+ // this system.
+ LOG(LS_WARNING) << "Failed to load symbol table";
+ return false;
+ }
+
+ // Now create and start the Pulse event thread.
+ mainloop_ = symbol_table_.pa_threaded_mainloop_new()();
+ if (!mainloop_) {
+ LOG(LS_ERROR) << "Can't create mainloop";
+ goto fail0;
+ }
+
+ if (symbol_table_.pa_threaded_mainloop_start()(mainloop_) != 0) {
+ LOG(LS_ERROR) << "Can't start mainloop";
+ goto fail1;
+ }
+
+ Lock();
+ context_ = CreateNewConnection();
+ Unlock();
+
+ if (!context_) {
+ goto fail2;
+ }
+
+ // Otherwise we're now ready!
+ return true;
+
+ fail2:
+ symbol_table_.pa_threaded_mainloop_stop()(mainloop_);
+ fail1:
+ symbol_table_.pa_threaded_mainloop_free()(mainloop_);
+ mainloop_ = NULL;
+ fail0:
+ return false;
+}
+
+void PulseAudioSoundSystem::Terminate() {
+ if (!IsInitialized()) {
+ return;
+ }
+
+ Lock();
+ symbol_table_.pa_context_disconnect()(context_);
+ symbol_table_.pa_context_unref()(context_);
+ Unlock();
+ context_ = NULL;
+ symbol_table_.pa_threaded_mainloop_stop()(mainloop_);
+ symbol_table_.pa_threaded_mainloop_free()(mainloop_);
+ mainloop_ = NULL;
+
+ // We do not unload the symbol table because we may need it again soon if
+ // Init() is called again.
+}
+
+bool PulseAudioSoundSystem::EnumeratePlaybackDevices(
+ SoundDeviceLocatorList *devices) {
+ return EnumerateDevices<pa_sink_info>(
+ devices,
+ symbol_table_.pa_context_get_sink_info_list(),
+ &EnumeratePlaybackDevicesCallbackThunk);
+}
+
+bool PulseAudioSoundSystem::EnumerateCaptureDevices(
+ SoundDeviceLocatorList *devices) {
+ return EnumerateDevices<pa_source_info>(
+ devices,
+ symbol_table_.pa_context_get_source_info_list(),
+ &EnumerateCaptureDevicesCallbackThunk);
+}
+
+bool PulseAudioSoundSystem::GetDefaultPlaybackDevice(
+ SoundDeviceLocator **device) {
+ return GetDefaultDevice<&pa_server_info::default_sink_name>(device);
+}
+
+bool PulseAudioSoundSystem::GetDefaultCaptureDevice(
+ SoundDeviceLocator **device) {
+ return GetDefaultDevice<&pa_server_info::default_source_name>(device);
+}
+
+SoundOutputStreamInterface *PulseAudioSoundSystem::OpenPlaybackDevice(
+ const SoundDeviceLocator *device,
+ const OpenParams &params) {
+ return OpenDevice<SoundOutputStreamInterface>(
+ device,
+ params,
+ "Playback",
+ &PulseAudioSoundSystem::ConnectOutputStream);
+}
+
+SoundInputStreamInterface *PulseAudioSoundSystem::OpenCaptureDevice(
+ const SoundDeviceLocator *device,
+ const OpenParams &params) {
+ return OpenDevice<SoundInputStreamInterface>(
+ device,
+ params,
+ "Capture",
+ &PulseAudioSoundSystem::ConnectInputStream);
+}
+
+const char *PulseAudioSoundSystem::GetName() const {
+ return "PulseAudio";
+}
+
+inline bool PulseAudioSoundSystem::IsInitialized() {
+ return mainloop_ != NULL;
+}
+
+struct ConnectToPulseCallbackData {
+ PulseAudioSoundSystem *instance;
+ bool connect_done;
+};
+
+void PulseAudioSoundSystem::ConnectToPulseCallbackThunk(
+ pa_context *context, void *userdata) {
+ ConnectToPulseCallbackData *data =
+ static_cast<ConnectToPulseCallbackData *>(userdata);
+ data->instance->OnConnectToPulseCallback(context, &data->connect_done);
+}
+
+void PulseAudioSoundSystem::OnConnectToPulseCallback(
+ pa_context *context, bool *connect_done) {
+ pa_context_state_t state = symbol_table_.pa_context_get_state()(context);
+ if (state == PA_CONTEXT_READY ||
+ state == PA_CONTEXT_FAILED ||
+ state == PA_CONTEXT_TERMINATED) {
+ // Connection process has reached a terminal state. Wake ConnectToPulse().
+ *connect_done = true;
+ Signal();
+ }
+}
+
+// Must be called with the lock held.
+bool PulseAudioSoundSystem::ConnectToPulse(pa_context *context) {
+ bool ret = true;
+ ConnectToPulseCallbackData data;
+ // Have to put this up here to satisfy the compiler.
+ pa_context_state_t state;
+
+ data.instance = this;
+ data.connect_done = false;
+
+ symbol_table_.pa_context_set_state_callback()(context,
+ &ConnectToPulseCallbackThunk,
+ &data);
+
+ // Connect to PulseAudio sound server.
+ if (symbol_table_.pa_context_connect()(
+ context,
+ NULL, // Default server
+ PA_CONTEXT_NOAUTOSPAWN,
+ NULL) != 0) { // No special fork handling needed
+ LOG(LS_ERROR) << "Can't start connection to PulseAudio sound server";
+ ret = false;
+ goto done;
+ }
+
+ // Wait for the connection state machine to reach a terminal state.
+ do {
+ Wait();
+ } while (!data.connect_done);
+
+ // Now check to see what final state we reached.
+ state = symbol_table_.pa_context_get_state()(context);
+
+ if (state != PA_CONTEXT_READY) {
+ if (state == PA_CONTEXT_FAILED) {
+ LOG(LS_ERROR) << "Failed to connect to PulseAudio sound server";
+ } else if (state == PA_CONTEXT_TERMINATED) {
+ LOG(LS_ERROR) << "PulseAudio connection terminated early";
+ } else {
+ // Shouldn't happen, because we only signal on one of those three states.
+ LOG(LS_ERROR) << "Unknown problem connecting to PulseAudio";
+ }
+ ret = false;
+ }
+
+ done:
+ // We unset our callback for safety just in case the state might somehow
+ // change later, because the pointer to "data" will be invalid after return
+ // from this function.
+ symbol_table_.pa_context_set_state_callback()(context, NULL, NULL);
+ return ret;
+}
+
+// Must be called with the lock held.
+pa_context *PulseAudioSoundSystem::CreateNewConnection() {
+ // Create connection context.
+ std::string app_name;
+ // TODO: Pulse etiquette says this name should be localized. Do
+ // we care?
+ rtc::Filesystem::GetApplicationName(&app_name);
+ pa_context *context = symbol_table_.pa_context_new()(
+ symbol_table_.pa_threaded_mainloop_get_api()(mainloop_),
+ app_name.c_str());
+ if (!context) {
+ LOG(LS_ERROR) << "Can't create context";
+ goto fail0;
+ }
+
+ // Now connect.
+ if (!ConnectToPulse(context)) {
+ goto fail1;
+ }
+
+ // Otherwise the connection succeeded and is ready.
+ return context;
+
+ fail1:
+ symbol_table_.pa_context_unref()(context);
+ fail0:
+ return NULL;
+}
+
+struct EnumerateDevicesCallbackData {
+ PulseAudioSoundSystem *instance;
+ SoundSystemInterface::SoundDeviceLocatorList *devices;
+};
+
+void PulseAudioSoundSystem::EnumeratePlaybackDevicesCallbackThunk(
+ pa_context *unused,
+ const pa_sink_info *info,
+ int eol,
+ void *userdata) {
+ EnumerateDevicesCallbackData *data =
+ static_cast<EnumerateDevicesCallbackData *>(userdata);
+ data->instance->OnEnumeratePlaybackDevicesCallback(data->devices, info, eol);
+}
+
+void PulseAudioSoundSystem::EnumerateCaptureDevicesCallbackThunk(
+ pa_context *unused,
+ const pa_source_info *info,
+ int eol,
+ void *userdata) {
+ EnumerateDevicesCallbackData *data =
+ static_cast<EnumerateDevicesCallbackData *>(userdata);
+ data->instance->OnEnumerateCaptureDevicesCallback(data->devices, info, eol);
+}
+
+void PulseAudioSoundSystem::OnEnumeratePlaybackDevicesCallback(
+ SoundDeviceLocatorList *devices,
+ const pa_sink_info *info,
+ int eol) {
+ if (eol) {
+ // List is over. Wake EnumerateDevices().
+ Signal();
+ return;
+ }
+
+ // Else this is the next device.
+ devices->push_back(
+ new PulseAudioDeviceLocator(info->description, info->name));
+}
+
+void PulseAudioSoundSystem::OnEnumerateCaptureDevicesCallback(
+ SoundDeviceLocatorList *devices,
+ const pa_source_info *info,
+ int eol) {
+ if (eol) {
+ // List is over. Wake EnumerateDevices().
+ Signal();
+ return;
+ }
+
+ if (info->monitor_of_sink != PA_INVALID_INDEX) {
+ // We don't want to list monitor sources, since they are almost certainly
+ // not what the user wants for voice conferencing.
+ return;
+ }
+
+ // Else this is the next device.
+ devices->push_back(
+ new PulseAudioDeviceLocator(info->description, info->name));
+}
+
+template <typename InfoStruct>
+bool PulseAudioSoundSystem::EnumerateDevices(
+ SoundDeviceLocatorList *devices,
+ pa_operation *(*enumerate_fn)(
+ pa_context *c,
+ void (*callback_fn)(
+ pa_context *c,
+ const InfoStruct *i,
+ int eol,
+ void *userdata),
+ void *userdata),
+ void (*callback_fn)(
+ pa_context *c,
+ const InfoStruct *i,
+ int eol,
+ void *userdata)) {
+ ClearSoundDeviceLocatorList(devices);
+ if (!IsInitialized()) {
+ return false;
+ }
+
+ EnumerateDevicesCallbackData data;
+ data.instance = this;
+ data.devices = devices;
+
+ Lock();
+ pa_operation *op = (*enumerate_fn)(
+ context_,
+ callback_fn,
+ &data);
+ bool ret = FinishOperation(op);
+ Unlock();
+ return ret;
+}
+
+struct GetDefaultDeviceCallbackData {
+ PulseAudioSoundSystem *instance;
+ SoundDeviceLocator **device;
+};
+
+template <const char *(pa_server_info::*field)>
+void PulseAudioSoundSystem::GetDefaultDeviceCallbackThunk(
+ pa_context *unused,
+ const pa_server_info *info,
+ void *userdata) {
+ GetDefaultDeviceCallbackData *data =
+ static_cast<GetDefaultDeviceCallbackData *>(userdata);
+ data->instance->OnGetDefaultDeviceCallback<field>(info, data->device);
+}
+
+template <const char *(pa_server_info::*field)>
+void PulseAudioSoundSystem::OnGetDefaultDeviceCallback(
+ const pa_server_info *info,
+ SoundDeviceLocator **device) {
+ if (info) {
+ const char *dev = info->*field;
+ if (dev) {
+ *device = new PulseAudioDeviceLocator("Default device", dev);
+ }
+ }
+ Signal();
+}
+
+template <const char *(pa_server_info::*field)>
+bool PulseAudioSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) {
+ if (!IsInitialized()) {
+ return false;
+ }
+ bool ret;
+ *device = NULL;
+ GetDefaultDeviceCallbackData data;
+ data.instance = this;
+ data.device = device;
+ Lock();
+ pa_operation *op = symbol_table_.pa_context_get_server_info()(
+ context_,
+ &GetDefaultDeviceCallbackThunk<field>,
+ &data);
+ ret = FinishOperation(op);
+ Unlock();
+ return ret && (*device != NULL);
+}
+
+void PulseAudioSoundSystem::StreamStateChangedCallbackThunk(
+ pa_stream *stream,
+ void *userdata) {
+ PulseAudioSoundSystem *instance =
+ static_cast<PulseAudioSoundSystem *>(userdata);
+ instance->OnStreamStateChangedCallback(stream);
+}
+
+void PulseAudioSoundSystem::OnStreamStateChangedCallback(pa_stream *stream) {
+ pa_stream_state_t state = symbol_table_.pa_stream_get_state()(stream);
+ if (state == PA_STREAM_READY) {
+ LOG(LS_INFO) << "Pulse stream " << stream << " ready";
+ } else if (state == PA_STREAM_FAILED ||
+ state == PA_STREAM_TERMINATED ||
+ state == PA_STREAM_UNCONNECTED) {
+ LOG(LS_ERROR) << "Pulse stream " << stream << " failed to connect: "
+ << LastError();
+ }
+}
+
+template <typename StreamInterface>
+StreamInterface *PulseAudioSoundSystem::OpenDevice(
+ const SoundDeviceLocator *device,
+ const OpenParams &params,
+ const char *stream_name,
+ StreamInterface *(PulseAudioSoundSystem::*connect_fn)(
+ pa_stream *stream,
+ const char *dev,
+ int flags,
+ pa_stream_flags_t pa_flags,
+ int latency,
+ const pa_sample_spec &spec)) {
+ if (!IsInitialized()) {
+ return NULL;
+ }
+
+ const char *dev = static_cast<const PulseAudioDeviceLocator *>(device)->
+ device_name().c_str();
+
+ StreamInterface *stream_interface = NULL;
+
+ ASSERT(params.format < ARRAY_SIZE(kCricketFormatToPulseFormatTable));
+
+ pa_sample_spec spec;
+ spec.format = kCricketFormatToPulseFormatTable[params.format];
+ spec.rate = params.freq;
+ spec.channels = params.channels;
+
+ int pa_flags = 0;
+ if (params.flags & FLAG_REPORT_LATENCY) {
+ pa_flags |= PA_STREAM_INTERPOLATE_TIMING |
+ PA_STREAM_AUTO_TIMING_UPDATE;
+ }
+
+ if (params.latency != kNoLatencyRequirements) {
+ // If configuring a specific latency then we want to specify
+ // PA_STREAM_ADJUST_LATENCY to make the server adjust parameters
+ // automatically to reach that target latency. However, that flag doesn't
+ // exist in Ubuntu 8.04 and many people still use that, so we have to check
+ // the protocol version of libpulse.
+ if (symbol_table_.pa_context_get_protocol_version()(context_) >=
+ kAdjustLatencyProtocolVersion) {
+ pa_flags |= PA_STREAM_ADJUST_LATENCY;
+ }
+ }
+
+ Lock();
+
+ pa_stream *stream = symbol_table_.pa_stream_new()(context_, stream_name,
+ &spec, NULL);
+ if (!stream) {
+ LOG(LS_ERROR) << "Can't create pa_stream";
+ goto done;
+ }
+
+ // Set a state callback to log errors.
+ symbol_table_.pa_stream_set_state_callback()(stream,
+ &StreamStateChangedCallbackThunk,
+ this);
+
+ stream_interface = (this->*connect_fn)(
+ stream,
+ dev,
+ params.flags,
+ static_cast<pa_stream_flags_t>(pa_flags),
+ params.latency,
+ spec);
+ if (!stream_interface) {
+ LOG(LS_ERROR) << "Can't connect stream to " << dev;
+ symbol_table_.pa_stream_unref()(stream);
+ }
+
+ done:
+ Unlock();
+ return stream_interface;
+}
+
+// Must be called with the lock held.
+SoundOutputStreamInterface *PulseAudioSoundSystem::ConnectOutputStream(
+ pa_stream *stream,
+ const char *dev,
+ int flags,
+ pa_stream_flags_t pa_flags,
+ int latency,
+ const pa_sample_spec &spec) {
+ pa_buffer_attr attr = {0};
+ pa_buffer_attr *pattr = NULL;
+ if (latency != kNoLatencyRequirements) {
+ // kLowLatency is 0, so we treat it the same as a request for zero latency.
+ ssize_t bytes_per_sec = symbol_table_.pa_bytes_per_second()(&spec);
+ latency = std::max(
+ latency, static_cast<int>(bytes_per_sec * kPlaybackLatencyMinimumMsecs /
+ rtc::kNumMicrosecsPerSec));
+ FillPlaybackBufferAttr(latency, &attr);
+ pattr = &attr;
+ }
+ if (symbol_table_.pa_stream_connect_playback()(
+ stream,
+ dev,
+ pattr,
+ pa_flags,
+ // Let server choose volume
+ NULL,
+ // Not synchronized to any other playout
+ NULL) != 0) {
+ return NULL;
+ }
+ return new PulseAudioOutputStream(this, stream, flags, latency);
+}
+
+// Must be called with the lock held.
+SoundInputStreamInterface *PulseAudioSoundSystem::ConnectInputStream(
+ pa_stream *stream,
+ const char *dev,
+ int flags,
+ pa_stream_flags_t pa_flags,
+ int latency,
+ const pa_sample_spec &spec) {
+ pa_buffer_attr attr = {0};
+ pa_buffer_attr *pattr = NULL;
+ if (latency != kNoLatencyRequirements) {
+ size_t bytes_per_sec = symbol_table_.pa_bytes_per_second()(&spec);
+ if (latency == kLowLatency) {
+ latency = bytes_per_sec * kLowCaptureLatencyMsecs /
+ rtc::kNumMicrosecsPerSec;
+ }
+ // Note: fragsize specifies a maximum transfer size, not a minimum, so it is
+ // not possible to force a high latency setting, only a low one.
+ attr.fragsize = latency;
+ attr.maxlength = latency + bytes_per_sec * kCaptureBufferExtraMsecs /
+ rtc::kNumMicrosecsPerSec;
+ LOG(LS_VERBOSE) << "Configuring latency = " << attr.fragsize
+ << ", maxlength = " << attr.maxlength;
+ pattr = &attr;
+ }
+ if (symbol_table_.pa_stream_connect_record()(stream,
+ dev,
+ pattr,
+ pa_flags) != 0) {
+ return NULL;
+ }
+ return new PulseAudioInputStream(this, stream, flags);
+}
+
+// Must be called with the lock held.
+bool PulseAudioSoundSystem::FinishOperation(pa_operation *op) {
+ if (!op) {
+ LOG(LS_ERROR) << "Failed to start operation";
+ return false;
+ }
+
+ do {
+ Wait();
+ } while (symbol_table_.pa_operation_get_state()(op) == PA_OPERATION_RUNNING);
+
+ symbol_table_.pa_operation_unref()(op);
+
+ return true;
+}
+
+inline void PulseAudioSoundSystem::Lock() {
+ symbol_table_.pa_threaded_mainloop_lock()(mainloop_);
+}
+
+inline void PulseAudioSoundSystem::Unlock() {
+ symbol_table_.pa_threaded_mainloop_unlock()(mainloop_);
+}
+
+// Must be called with the lock held.
+inline void PulseAudioSoundSystem::Wait() {
+ symbol_table_.pa_threaded_mainloop_wait()(mainloop_);
+}
+
+// Must be called with the lock held.
+inline void PulseAudioSoundSystem::Signal() {
+ symbol_table_.pa_threaded_mainloop_signal()(mainloop_, 0);
+}
+
+// Must be called with the lock held.
+const char *PulseAudioSoundSystem::LastError() {
+ return symbol_table_.pa_strerror()(symbol_table_.pa_context_errno()(
+ context_));
+}
+
+} // namespace rtc
+
+#endif // HAVE_LIBPULSE