| /* |
| * 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/base/common.h" |
| #include "talk/base/fileutils.h" // for GetApplicationName() |
| #include "talk/base/logging.h" |
| #include "talk/base/worker.h" |
| #include "talk/base/timeutils.h" |
| #include "talk/sound/sounddevicelocator.h" |
| #include "talk/sound/soundinputstreaminterface.h" |
| #include "talk/sound/soundoutputstreaminterface.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 talk_base::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 talk_base::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 / |
| talk_base::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? |
| talk_base::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 = talk_base::_max( |
| latency, |
| static_cast<int>( |
| bytes_per_sec * kPlaybackLatencyMinimumMsecs / |
| talk_base::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 / |
| talk_base::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 / |
| talk_base::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 |