diff options
Diffstat (limited to 'sound/pulseaudiosoundsystem.cc')
-rw-r--r-- | sound/pulseaudiosoundsystem.cc | 1559 |
1 files changed, 0 insertions, 1559 deletions
diff --git a/sound/pulseaudiosoundsystem.cc b/sound/pulseaudiosoundsystem.cc deleted file mode 100644 index 24eea5c..0000000 --- a/sound/pulseaudiosoundsystem.cc +++ /dev/null @@ -1,1559 +0,0 @@ -/* - * libjingle - * Copyright 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/pulseaudiosoundsystem.h" - -#ifdef HAVE_LIBPULSE - -#include "talk/sound/sounddevicelocator.h" -#include "talk/sound/soundinputstreaminterface.h" -#include "talk/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 cricket { - -// First PulseAudio protocol version that supports PA_STREAM_ADJUST_LATENCY. -static const uint32_t kAdjustLatencyProtocolVersion = 13; - -// Lookup table from the cricket 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_; - - 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_; - - 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_; - - 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 ¶ms) { - return OpenDevice<SoundOutputStreamInterface>( - device, - params, - "Playback", - &PulseAudioSoundSystem::ConnectOutputStream); -} - -SoundInputStreamInterface *PulseAudioSoundSystem::OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) { - 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 ¶ms, - 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 = rtc::_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 cricket - -#endif // HAVE_LIBPULSE |