Index: webrtc/sound/pulseaudiosoundsystem.cc |
diff --git a/webrtc/sound/pulseaudiosoundsystem.cc b/webrtc/sound/pulseaudiosoundsystem.cc |
deleted file mode 100644 |
index 15da76c5833cc278a6c5fa83e7b637333aafb8ee..0000000000000000000000000000000000000000 |
--- a/webrtc/sound/pulseaudiosoundsystem.cc |
+++ /dev/null |
@@ -1,1542 +0,0 @@ |
-/* |
- * 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 <string> |
- |
-#include "webrtc/base/arraysize.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" |
-#include "webrtc/sound/sounddevicelocator.h" |
-#include "webrtc/sound/soundinputstreaminterface.h" |
-#include "webrtc/sound/soundoutputstreaminterface.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 { |
- 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: |
- struct GetVolumeCallbackData { |
- PulseAudioInputStream* instance; |
- pa_cvolume* channel_volumes; |
- }; |
- |
- struct GetSourceChannelCountCallbackData { |
- PulseAudioInputStream* instance; |
- uint8_t* channels; |
- }; |
- |
- 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 { |
- 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(henrika): 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: |
- struct GetVolumeCallbackData { |
- PulseAudioOutputStream* instance; |
- pa_cvolume* channel_volumes; |
- }; |
- |
- 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 ¶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(henrika): 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 < arraysize(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 |