 28e2075280
			
		
	
	28e2075280
	
	
	
		
			
			trunk/talk git-svn-id: http://webrtc.googlecode.com/svn/trunk@4318 4adac7df-926f-26a2-2b94-8c16560cd09d
		
			
				
	
	
		
			1560 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1560 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * 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
 |