| Index: media/audio/pulse/pulse_output.cc
 | 
| ===================================================================
 | 
| --- media/audio/pulse/pulse_output.cc	(revision 110923)
 | 
| +++ media/audio/pulse/pulse_output.cc	(working copy)
 | 
| @@ -16,6 +16,7 @@
 | 
|  #include "media/base/data_buffer.h"
 | 
|  #include "media/base/seekable_buffer.h"
 | 
|  
 | 
| +// TODO(xians): Do we support any sample format rather than PA_SAMPLE_S16LE?
 | 
|  static pa_sample_format_t BitsToPASampleFormat(int bits_per_sample) {
 | 
|    switch (bits_per_sample) {
 | 
|      // Unsupported sample formats shown for reference.  I am assuming we want
 | 
| @@ -41,71 +42,6 @@
 | 
|    }
 | 
|  }
 | 
|  
 | 
| -static pa_channel_position ChromiumToPAChannelPosition(Channels channel) {
 | 
| -  switch (channel) {
 | 
| -    // PulseAudio does not differentiate between left/right and
 | 
| -    // stereo-left/stereo-right, both translate to front-left/front-right.
 | 
| -    case LEFT:
 | 
| -    case STEREO_LEFT:
 | 
| -      return PA_CHANNEL_POSITION_FRONT_LEFT;
 | 
| -    case RIGHT:
 | 
| -    case STEREO_RIGHT:
 | 
| -      return PA_CHANNEL_POSITION_FRONT_RIGHT;
 | 
| -    case CENTER:
 | 
| -      return PA_CHANNEL_POSITION_FRONT_CENTER;
 | 
| -    case LFE:
 | 
| -      return PA_CHANNEL_POSITION_LFE;
 | 
| -    case BACK_LEFT:
 | 
| -      return PA_CHANNEL_POSITION_REAR_LEFT;
 | 
| -    case BACK_RIGHT:
 | 
| -      return PA_CHANNEL_POSITION_REAR_RIGHT;
 | 
| -    case LEFT_OF_CENTER:
 | 
| -      return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
 | 
| -    case RIGHT_OF_CENTER:
 | 
| -      return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
 | 
| -    case BACK_CENTER:
 | 
| -      return PA_CHANNEL_POSITION_REAR_CENTER;
 | 
| -    case SIDE_LEFT:
 | 
| -      return PA_CHANNEL_POSITION_SIDE_LEFT;
 | 
| -    case SIDE_RIGHT:
 | 
| -      return PA_CHANNEL_POSITION_SIDE_RIGHT;
 | 
| -    case CHANNELS_MAX:
 | 
| -      return PA_CHANNEL_POSITION_INVALID;
 | 
| -  }
 | 
| -  NOTREACHED() << "Invalid channel " << channel;
 | 
| -  return PA_CHANNEL_POSITION_INVALID;
 | 
| -}
 | 
| -
 | 
| -static pa_channel_map ChannelLayoutToPAChannelMap(
 | 
| -    ChannelLayout channel_layout) {
 | 
| -  // Initialize channel map.
 | 
| -  pa_channel_map channel_map;
 | 
| -  pa_channel_map_init(&channel_map);
 | 
| -
 | 
| -  channel_map.channels = ChannelLayoutToChannelCount(channel_layout);
 | 
| -
 | 
| -  // All channel maps have the same size array of channel positions.
 | 
| -  for (unsigned int channel = 0; channel != CHANNELS_MAX; ++channel) {
 | 
| -    int channel_position = kChannelOrderings[channel_layout][channel];
 | 
| -    if (channel_position > -1) {
 | 
| -      channel_map.map[channel_position] = ChromiumToPAChannelPosition(
 | 
| -          static_cast<Channels>(channel));
 | 
| -    } else {
 | 
| -      // PulseAudio expects unused channels in channel maps to be filled with
 | 
| -      // PA_CHANNEL_POSITION_MONO.
 | 
| -      channel_map.map[channel_position] = PA_CHANNEL_POSITION_MONO;
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  // Fill in the rest of the unused channels.
 | 
| -  for (unsigned int channel = CHANNELS_MAX; channel != PA_CHANNELS_MAX;
 | 
| -       ++channel) {
 | 
| -    channel_map.map[channel] = PA_CHANNEL_POSITION_MONO;
 | 
| -  }
 | 
| -
 | 
| -  return channel_map;
 | 
| -}
 | 
| -
 | 
|  static size_t MicrosecondsToBytes(
 | 
|      uint32 microseconds, uint32 sample_rate, size_t bytes_per_frame) {
 | 
|    return microseconds * sample_rate * bytes_per_frame /
 | 
| @@ -113,42 +49,55 @@
 | 
|  }
 | 
|  
 | 
|  void PulseAudioOutputStream::ContextStateCallback(pa_context* context,
 | 
| -                                                  void* state_addr) {
 | 
| -  pa_context_state_t* state = static_cast<pa_context_state_t*>(state_addr);
 | 
| -  *state = pa_context_get_state(context);
 | 
| +                                                  void* user_data) {
 | 
| +  PulseAudioOutputStream* audio_stream =
 | 
| +      reinterpret_cast<PulseAudioOutputStream*>(user_data);
 | 
| +  pa_context_state_t state = pa_context_get_state(context);
 | 
| +  switch (state) {
 | 
| +    case PA_CONTEXT_TERMINATED:
 | 
| +      audio_stream->context_state_changed_ = true;
 | 
| +      pa_threaded_mainloop_signal(audio_stream->pa_mainloop_, 0);
 | 
| +      break;
 | 
| +    case PA_CONTEXT_READY:
 | 
| +      audio_stream->context_state_changed_ = true;
 | 
| +      pa_threaded_mainloop_signal(audio_stream->pa_mainloop_, 0);
 | 
| +      break;
 | 
| +    case PA_CONTEXT_UNCONNECTED:
 | 
| +    case PA_CONTEXT_CONNECTING:
 | 
| +    case PA_CONTEXT_AUTHORIZING:
 | 
| +    case PA_CONTEXT_SETTING_NAME:
 | 
| +    case PA_CONTEXT_FAILED:
 | 
| +    default:
 | 
| +      break;
 | 
| +  }
 | 
|  }
 | 
|  
 | 
|  void PulseAudioOutputStream::WriteRequestCallback(
 | 
| -    pa_stream* playback_handle, size_t length, void* stream_addr) {
 | 
| -  PulseAudioOutputStream* stream =
 | 
| -      static_cast<PulseAudioOutputStream*>(stream_addr);
 | 
| +    pa_stream* playback_handle, size_t length, void* user_data) {
 | 
| +  PulseAudioOutputStream* audio_stream =
 | 
| +      reinterpret_cast<PulseAudioOutputStream*>(user_data);
 | 
|  
 | 
| -  DCHECK_EQ(stream->message_loop_, MessageLoop::current());
 | 
| -
 | 
| -  stream->write_callback_handled_ = true;
 | 
| -
 | 
| -  // Fulfill write request.
 | 
| -  stream->FulfillWriteRequest(length);
 | 
| +  audio_stream->FulfillWriteRequest(length);
 | 
|  }
 | 
|  
 | 
|  PulseAudioOutputStream::PulseAudioOutputStream(const AudioParameters& params,
 | 
|                                                 AudioManagerPulse* manager,
 | 
|                                                 MessageLoop* message_loop)
 | 
| -    : channel_layout_(params.channel_layout),
 | 
| -      channel_count_(ChannelLayoutToChannelCount(channel_layout_)),
 | 
| +    : channels_(params.channels),
 | 
|        sample_format_(BitsToPASampleFormat(params.bits_per_sample)),
 | 
|        sample_rate_(params.sample_rate),
 | 
|        bytes_per_frame_(params.channels * params.bits_per_sample / 8),
 | 
| +      packet_size_(params.GetPacketSize()),
 | 
| +      frames_per_packet_(packet_size_ / bytes_per_frame_),
 | 
|        manager_(manager),
 | 
|        pa_context_(NULL),
 | 
|        pa_mainloop_(NULL),
 | 
|        playback_handle_(NULL),
 | 
| -      packet_size_(params.GetPacketSize()),
 | 
| -      frames_per_packet_(packet_size_ / bytes_per_frame_),
 | 
| -      client_buffer_(NULL),
 | 
| +      pa_buffer_size_(0),
 | 
| +      buffer_(NULL),
 | 
|        volume_(1.0f),
 | 
|        stream_stopped_(true),
 | 
| -      write_callback_handled_(false),
 | 
| +      context_state_changed_(false),
 | 
|        message_loop_(message_loop),
 | 
|        ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
 | 
|        source_callback_(NULL) {
 | 
| @@ -156,6 +105,9 @@
 | 
|    DCHECK(manager_);
 | 
|  
 | 
|    // TODO(slock): Sanity check input values.
 | 
| +
 | 
| +  // TODO(xians): Check if PA is available here in runtime, and fall back
 | 
| +  // to ALSA if not available.
 | 
|  }
 | 
|  
 | 
|  PulseAudioOutputStream::~PulseAudioOutputStream() {
 | 
| @@ -168,107 +120,79 @@
 | 
|  
 | 
|  bool PulseAudioOutputStream::Open() {
 | 
|    DCHECK_EQ(message_loop_, MessageLoop::current());
 | 
| +  DCHECK(!pa_mainloop_);
 | 
| +DLOG(WARNING) << "PULSE AUDIO";
 | 
| +  // Create a mainloop API and connection to the default server.
 | 
| +  // The mainloop is the internal asynchronous API event loop.
 | 
| +  pa_mainloop_ = pa_threaded_mainloop_new();
 | 
| +  DCHECK(pa_mainloop_) << "Failed to create PA threaded mainloop";
 | 
| +  if (!pa_mainloop_)
 | 
| +    return false;
 | 
|  
 | 
| -  // TODO(slock): Possibly move most of this to an OpenPlaybackDevice function
 | 
| -  // in a new class 'pulse_util', like alsa_util.
 | 
| +  // Start the threaded mainloop.
 | 
| +  if (pa_threaded_mainloop_start(pa_mainloop_)) {
 | 
| +    DLOG(ERROR) << "Failed to start mainloop.";
 | 
| +    return false;
 | 
| +  }
 | 
|  
 | 
| -  // Create a mainloop API and connect to the default server.
 | 
| -  pa_mainloop_ = pa_mainloop_new();
 | 
| -  pa_mainloop_api* pa_mainloop_api = pa_mainloop_get_api(pa_mainloop_);
 | 
| +  // Lock the event loop object, effectively blocking the event loop thread
 | 
| +  // from processing events. This is necessary.
 | 
| +  pa_threaded_mainloop_lock(pa_mainloop_);
 | 
| +
 | 
| +  // TODO(xians): Share one pa_context_ for streams.
 | 
| +  pa_mainloop_api* pa_mainloop_api =
 | 
| +      pa_threaded_mainloop_get_api(pa_mainloop_);
 | 
|    pa_context_ = pa_context_new(pa_mainloop_api, "Chromium");
 | 
| -  pa_context_state_t pa_context_state = PA_CONTEXT_UNCONNECTED;
 | 
| -  pa_context_connect(pa_context_, NULL, PA_CONTEXT_NOFLAGS, NULL);
 | 
| +  DCHECK(pa_context_) << "Failed to create PA context";
 | 
| +  if (!pa_context_) {
 | 
| +    Reset();
 | 
| +    pa_threaded_mainloop_unlock(pa_mainloop_);
 | 
| +    return false;
 | 
| +  }
 | 
|  
 | 
| -  // Wait until PulseAudio is ready.
 | 
| -  pa_context_set_state_callback(pa_context_, &ContextStateCallback,
 | 
| -                                &pa_context_state);
 | 
| -  while (pa_context_state != PA_CONTEXT_READY) {
 | 
| -    pa_mainloop_iterate(pa_mainloop_, 1, NULL);
 | 
| -    if (pa_context_state == PA_CONTEXT_FAILED ||
 | 
| -        pa_context_state == PA_CONTEXT_TERMINATED) {
 | 
| -      Reset();
 | 
| -      return false;
 | 
| -    }
 | 
| +  context_state_changed_ = false;
 | 
| +  pa_context_set_state_callback(pa_context_, &ContextStateCallback, this);
 | 
| +  if (pa_context_connect(pa_context_, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL)) {
 | 
| +    DLOG(ERROR) << "Failed to connect to the context";
 | 
| +    Reset();
 | 
| +    pa_threaded_mainloop_unlock(pa_mainloop_);
 | 
| +    return false;
 | 
|    }
 | 
|  
 | 
| +  while (!context_state_changed_) {
 | 
| +    pa_threaded_mainloop_wait(pa_mainloop_);
 | 
| +  }
 | 
| +
 | 
| +  if (pa_context_get_state(pa_context_) != PA_CONTEXT_READY) {
 | 
| +    DLOG(ERROR) <<  "Unknown problem connecting to PulseAudio server";
 | 
| +    Reset();
 | 
| +    pa_threaded_mainloop_unlock(pa_mainloop_);
 | 
| +    return false;
 | 
| +  }
 | 
| +
 | 
|    // Set sample specifications.
 | 
|    pa_sample_spec pa_sample_specifications;
 | 
|    pa_sample_specifications.format = sample_format_;
 | 
|    pa_sample_specifications.rate = sample_rate_;
 | 
| -  pa_sample_specifications.channels = channel_count_;
 | 
| +  pa_sample_specifications.channels = channels_;
 | 
|  
 | 
| -  // Get channel mapping and open playback stream.
 | 
| -  pa_channel_map* map = NULL;
 | 
| -  pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
 | 
| -      channel_layout_);
 | 
| -  if (source_channel_map.channels != 0) {
 | 
| -    // The source data uses a supported channel map so we will use it rather
 | 
| -    // than the default channel map (NULL).
 | 
| -    map = &source_channel_map;
 | 
| -  }
 | 
| -  playback_handle_ = pa_stream_new(pa_context_, "Playback",
 | 
| -                                   &pa_sample_specifications, map);
 | 
| -
 | 
| -  // Initialize client buffer.
 | 
| -  uint32 output_packet_size = frames_per_packet_ * bytes_per_frame_;
 | 
| -  client_buffer_.reset(new media::SeekableBuffer(0, output_packet_size));
 | 
| -
 | 
| -  // Set write callback.
 | 
| -  pa_stream_set_write_callback(playback_handle_, &WriteRequestCallback, this);
 | 
| -
 | 
| -  // Set server-side buffer attributes.
 | 
| -  // (uint32_t)-1 is the default and recommended value from PulseAudio's
 | 
| -  // documentation, found at:
 | 
| -  // http://freedesktop.org/software/pulseaudio/doxygen/structpa__buffer__attr.html.
 | 
| -  pa_buffer_attr pa_buffer_attributes;
 | 
| -  pa_buffer_attributes.maxlength = static_cast<uint32_t>(-1);
 | 
| -  pa_buffer_attributes.tlength = output_packet_size;
 | 
| -  pa_buffer_attributes.prebuf = static_cast<uint32_t>(-1);
 | 
| -  pa_buffer_attributes.minreq = static_cast<uint32_t>(-1);
 | 
| -  pa_buffer_attributes.fragsize = static_cast<uint32_t>(-1);
 | 
| -
 | 
| -  // Connect playback stream.
 | 
| -  pa_stream_connect_playback(playback_handle_, NULL,
 | 
| -                             &pa_buffer_attributes,
 | 
| -                             (pa_stream_flags_t)
 | 
| -                             (PA_STREAM_INTERPOLATE_TIMING |
 | 
| -                             PA_STREAM_ADJUST_LATENCY |
 | 
| -                             PA_STREAM_AUTO_TIMING_UPDATE),
 | 
| -                             NULL, NULL);
 | 
| -
 | 
| +  // Create a new play stream
 | 
| +  playback_handle_ = pa_stream_new(pa_context_, "PlayStream",
 | 
| +                                   &pa_sample_specifications, NULL);
 | 
|    if (!playback_handle_) {
 | 
| +    DLOG(ERROR) << "Open: failed to create PA stream";
 | 
|      Reset();
 | 
| +    pa_threaded_mainloop_unlock(pa_mainloop_);
 | 
|      return false;
 | 
|    }
 | 
|  
 | 
| +  pa_stream_set_write_callback(playback_handle_, &WriteRequestCallback, this);
 | 
| +  pa_threaded_mainloop_unlock(pa_mainloop_);
 | 
| +
 | 
| +  buffer_.reset(new media::SeekableBuffer(0, packet_size_));
 | 
|    return true;
 | 
|  }
 | 
|  
 | 
| -void PulseAudioOutputStream::Reset() {
 | 
| -  stream_stopped_ = true;
 | 
| -
 | 
| -  // Close the stream.
 | 
| -  if (playback_handle_) {
 | 
| -    pa_stream_flush(playback_handle_, NULL, NULL);
 | 
| -    pa_stream_disconnect(playback_handle_);
 | 
| -
 | 
| -    // Release PulseAudio structures.
 | 
| -    pa_stream_unref(playback_handle_);
 | 
| -    playback_handle_ = NULL;
 | 
| -  }
 | 
| -  if (pa_context_) {
 | 
| -    pa_context_unref(pa_context_);
 | 
| -    pa_context_ = NULL;
 | 
| -  }
 | 
| -  if (pa_mainloop_) {
 | 
| -    pa_mainloop_free(pa_mainloop_);
 | 
| -    pa_mainloop_ = NULL;
 | 
| -  }
 | 
| -
 | 
| -  // Release internal buffer.
 | 
| -  client_buffer_.reset();
 | 
| -}
 | 
| -
 | 
|  void PulseAudioOutputStream::Close() {
 | 
|    DCHECK_EQ(message_loop_, MessageLoop::current());
 | 
|  
 | 
| @@ -279,146 +203,187 @@
 | 
|    manager_->ReleaseOutputStream(this);
 | 
|  }
 | 
|  
 | 
| -void PulseAudioOutputStream::WaitForWriteRequest() {
 | 
| +void PulseAudioOutputStream::Start(AudioSourceCallback* callback) {
 | 
|    DCHECK_EQ(message_loop_, MessageLoop::current());
 | 
| +  CHECK(callback);
 | 
|  
 | 
| -  if (stream_stopped_)
 | 
| +  if (!stream_stopped_)
 | 
|      return;
 | 
| +  stream_stopped_ = false;
 | 
|  
 | 
| -  // Iterate the PulseAudio mainloop.  If PulseAudio doesn't request a write,
 | 
| -  // post a task to iterate the mainloop again.
 | 
| -  write_callback_handled_ = false;
 | 
| -  pa_mainloop_iterate(pa_mainloop_, 1, NULL);
 | 
| -  if (!write_callback_handled_) {
 | 
| -    message_loop_->PostTask(FROM_HERE, base::Bind(
 | 
| -        &PulseAudioOutputStream::WaitForWriteRequest,
 | 
| -        weak_factory_.GetWeakPtr()));
 | 
| -  }
 | 
| -}
 | 
| +  // First time to start the stream.
 | 
| +  if (!source_callback_) {
 | 
| +    // Set server-side playback buffer metrics. Detailed documentation on what
 | 
| +    // values should be chosen can be found at
 | 
| +    // freedesktop.org/software/pulseaudio/doxygen/structpa__buffer__attr.html.
 | 
| +    pa_buffer_attr pa_buffer_attributes;
 | 
| +    pa_buffer_size_ = packet_size_;
 | 
| +    pa_buffer_attributes.maxlength =  static_cast<uint32_t>(-1);
 | 
| +    pa_buffer_attributes.tlength = pa_buffer_size_;
 | 
| +    pa_buffer_attributes.minreq = pa_buffer_size_ / 2;
 | 
| +    pa_buffer_attributes.prebuf =
 | 
| +        pa_buffer_attributes.tlength  - pa_buffer_attributes.minreq;
 | 
| +    pa_buffer_attributes.fragsize = packet_size_;
 | 
| +    int err = pa_stream_connect_playback(playback_handle_,
 | 
| +                                         NULL,  // Default device.
 | 
| +                                         &pa_buffer_attributes,
 | 
| +                                         static_cast<pa_stream_flags_t>
 | 
| +                                         (PA_STREAM_AUTO_TIMING_UPDATE |
 | 
| +                                          PA_STREAM_INTERPOLATE_TIMING |
 | 
| +                                          PA_STREAM_ADJUST_LATENCY),
 | 
| +                                         NULL,  // Default volume.
 | 
| +                                         NULL  // Standalone stream.
 | 
| +                                         );
 | 
| +    if (err) {
 | 
| +      DLOG(ERROR) << "pa_stream_connect_playback FAILED " << err;
 | 
| +      Reset();
 | 
| +      return;
 | 
| +    }
 | 
| +  } else {  // Resume the playout stream.
 | 
| +    // Flush the stream.
 | 
| +    pa_operation* operation = pa_stream_flush(playback_handle_, NULL, NULL);
 | 
| +    if (!operation) {
 | 
| +      DLOG(ERROR) << "PulseAudioOutputStream: failed to flush the playout "
 | 
| +                  << "stream";
 | 
| +      return;
 | 
| +    }
 | 
| +    // Do not need to wait for the operation.
 | 
| +    pa_operation_unref(operation);
 | 
|  
 | 
| -bool PulseAudioOutputStream::BufferPacketFromSource() {
 | 
| -  uint32 buffer_delay = client_buffer_->forward_bytes();
 | 
| -  pa_usec_t pa_latency_micros;
 | 
| -  int negative;
 | 
| -  pa_stream_get_latency(playback_handle_, &pa_latency_micros, &negative);
 | 
| -  uint32 hardware_delay = MicrosecondsToBytes(pa_latency_micros,
 | 
| -                                              sample_rate_,
 | 
| -                                              bytes_per_frame_);
 | 
| -  // TODO(slock): Deal with negative latency (negative == 1).  This has yet
 | 
| -  // to happen in practice though.
 | 
| -  scoped_refptr<media::DataBuffer> packet =
 | 
| -      new media::DataBuffer(packet_size_);
 | 
| -  size_t packet_size = RunDataCallback(packet->GetWritableData(),
 | 
| -                                       packet->GetBufferSize(),
 | 
| -                                       AudioBuffersState(buffer_delay,
 | 
| -                                                         hardware_delay));
 | 
| +    // Start the stream.
 | 
| +    operation = pa_stream_cork(playback_handle_, 0, NULL, NULL);
 | 
| +    if (!operation) {
 | 
| +      DLOG(ERROR) << "PulseAudioOutputStream: failed to start the playout "
 | 
| +                  << "stream";
 | 
| +      return;
 | 
| +    }
 | 
| +    pa_operation_unref(operation);
 | 
|  
 | 
| -  if (packet_size == 0)
 | 
| -    return false;
 | 
| -
 | 
| -  media::AdjustVolume(packet->GetWritableData(),
 | 
| -                      packet_size,
 | 
| -                      channel_count_,
 | 
| -                      bytes_per_frame_ / channel_count_,
 | 
| -                      volume_);
 | 
| -  packet->SetDataSize(packet_size);
 | 
| -  // Add the packet to the buffer.
 | 
| -  client_buffer_->Append(packet);
 | 
| -  return true;
 | 
| -}
 | 
| -
 | 
| -void PulseAudioOutputStream::FulfillWriteRequest(size_t requested_bytes) {
 | 
| -  // If we have enough data to fulfill the request, we can finish the write.
 | 
| -  if (stream_stopped_)
 | 
| -    return;
 | 
| -
 | 
| -  // Request more data from the source until we can fulfill the request or
 | 
| -  // fail to receive anymore data.
 | 
| -  bool buffering_successful = true;
 | 
| -  while (client_buffer_->forward_bytes() < requested_bytes &&
 | 
| -         buffering_successful) {
 | 
| -    buffering_successful = BufferPacketFromSource();
 | 
| +    operation = pa_stream_trigger(playback_handle_, NULL, NULL);
 | 
| +    if (!operation) {
 | 
| +      DLOG(ERROR) << "PulseAudioOutputStream: failed to trigger the playout "
 | 
| +                  << "callback";
 | 
| +      return;
 | 
| +    }
 | 
| +    pa_operation_unref(operation);
 | 
|    }
 | 
|  
 | 
| -  size_t bytes_written = 0;
 | 
| -  if (client_buffer_->forward_bytes() > 0) {
 | 
| -    // Try to fulfill the request by writing as many of the requested bytes to
 | 
| -    // the stream as we can.
 | 
| -    WriteToStream(requested_bytes, &bytes_written);
 | 
| -  }
 | 
| +  source_callback_ = callback;
 | 
|  
 | 
| -  if (bytes_written < requested_bytes) {
 | 
| -    // We weren't able to buffer enough data to fulfill the request.  Try to
 | 
| -    // fulfill the rest of the request later.
 | 
| -    message_loop_->PostTask(FROM_HERE, base::Bind(
 | 
| -        &PulseAudioOutputStream::FulfillWriteRequest,
 | 
| -        weak_factory_.GetWeakPtr(),
 | 
| -        requested_bytes - bytes_written));
 | 
| -  } else {
 | 
| -    // Continue playback.
 | 
| -    message_loop_->PostTask(FROM_HERE, base::Bind(
 | 
| -        &PulseAudioOutputStream::WaitForWriteRequest,
 | 
| -        weak_factory_.GetWeakPtr()));
 | 
| -  }
 | 
| +  // Before starting, the buffer might have audio from previous user of this
 | 
| +  // device.
 | 
| +  buffer_->Clear();
 | 
|  }
 | 
|  
 | 
| -void PulseAudioOutputStream::WriteToStream(size_t bytes_to_write,
 | 
| -                                           size_t* bytes_written) {
 | 
| -  *bytes_written = 0;
 | 
| -  while (*bytes_written < bytes_to_write) {
 | 
| -    const uint8* chunk;
 | 
| -    size_t chunk_size;
 | 
| +void PulseAudioOutputStream::Stop() {
 | 
| +  DCHECK_EQ(message_loop_, MessageLoop::current());
 | 
| +  // Set the flag to false to stop filling new data to soundcard.
 | 
| +  stream_stopped_ = true;
 | 
|  
 | 
| -    // Stop writing if there is no more data available.
 | 
| -    if (!client_buffer_->GetCurrentChunk(&chunk, &chunk_size))
 | 
| -      break;
 | 
| +  if (!playback_handle_)
 | 
| +    return;
 | 
|  
 | 
| -    // Write data to stream.
 | 
| -    pa_stream_write(playback_handle_, chunk, chunk_size,
 | 
| -                    NULL, 0LL, PA_SEEK_RELATIVE);
 | 
| -    client_buffer_->Seek(chunk_size);
 | 
| -    *bytes_written += chunk_size;
 | 
| +  // Stop the stream.
 | 
| +  pa_operation* operation = pa_stream_cork(playback_handle_, 1, NULL, NULL);
 | 
| +  if (!operation) {
 | 
| +    DLOG(ERROR) << "PulseAudioOutputStream: failed to stop the playout";
 | 
| +    return;
 | 
|    }
 | 
| +  // Do not need to wait for the operation.
 | 
| +  pa_operation_unref(operation);
 | 
|  }
 | 
|  
 | 
| -void PulseAudioOutputStream::Start(AudioSourceCallback* callback) {
 | 
| +void PulseAudioOutputStream::SetVolume(double volume) {
 | 
|    DCHECK_EQ(message_loop_, MessageLoop::current());
 | 
|  
 | 
| -  CHECK(callback);
 | 
| -  source_callback_ = callback;
 | 
| -
 | 
| -  // Clear buffer, it might still have data in it.
 | 
| -  client_buffer_->Clear();
 | 
| -  stream_stopped_ = false;
 | 
| -
 | 
| -  // Start playback.
 | 
| -  message_loop_->PostTask(FROM_HERE, base::Bind(
 | 
| -      &PulseAudioOutputStream::WaitForWriteRequest,
 | 
| -      weak_factory_.GetWeakPtr()));
 | 
| +  volume_ = static_cast<float>(volume);
 | 
|  }
 | 
|  
 | 
| -void PulseAudioOutputStream::Stop() {
 | 
| +void PulseAudioOutputStream::GetVolume(double* volume) {
 | 
|    DCHECK_EQ(message_loop_, MessageLoop::current());
 | 
|  
 | 
| -  stream_stopped_ = true;
 | 
| +  *volume = volume_;
 | 
|  }
 | 
|  
 | 
| -void PulseAudioOutputStream::SetVolume(double volume) {
 | 
| -  DCHECK_EQ(message_loop_, MessageLoop::current());
 | 
| +void PulseAudioOutputStream::FulfillWriteRequest(size_t requested_bytes) {
 | 
| +  // Update the delay.
 | 
| +  pa_usec_t pa_latency_micros;
 | 
| +  int negative;
 | 
| +  pa_stream_get_latency(playback_handle_, &pa_latency_micros, &negative);
 | 
| +  uint32 hardware_delay = MicrosecondsToBytes(pa_latency_micros,
 | 
| +                                              sample_rate_,
 | 
| +                                              bytes_per_frame_);
 | 
| +  uint32 buffer_delay = buffer_->forward_bytes();
 | 
| +  // TODO(slock): Deal with negative latency (negative == 1).  This has yet
 | 
| +  // to happen in practice though.
 | 
|  
 | 
| -  volume_ = static_cast<float>(volume);
 | 
| +  // Request more data from the source until we can fulfill the request or
 | 
| +  // fail to receive anymore data.
 | 
| +  scoped_refptr<media::DataBuffer> packet(new media::DataBuffer(packet_size_));
 | 
| +  size_t filled = 0;
 | 
| +  int bytes_to_fill = requested_bytes;
 | 
| +
 | 
| +  // Request more data only if we need more.
 | 
| +  if (!buffer_->forward_bytes() && bytes_to_fill) {
 | 
| +    if (!stream_stopped_ && source_callback_)
 | 
| +      filled = source_callback_->OnMoreData(
 | 
| +          this,
 | 
| +          packet->GetWritableData(),
 | 
| +          packet->GetBufferSize(),
 | 
| +          AudioBuffersState(buffer_delay, hardware_delay));
 | 
| +    if (filled) {
 | 
| +      packet->SetDataSize(filled);
 | 
| +      buffer_->Append(packet);
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  const uint8* buffer_data;
 | 
| +  size_t buffer_size;
 | 
| +  if (buffer_->GetCurrentChunk(&buffer_data, &buffer_size)) {
 | 
| +    if (buffer_size < static_cast<unsigned int>(bytes_to_fill))
 | 
| +      filled = buffer_size;
 | 
| +    else
 | 
| +      filled = bytes_to_fill;
 | 
| +     // Write data to stream.
 | 
| +    if (pa_stream_write(playback_handle_, buffer_data, filled,
 | 
| +                        NULL, 0, PA_SEEK_RELATIVE)) {
 | 
| +      DLOG(WARNING) << "FulfillWriteRequest: failed to write "
 | 
| +                    << filled << " bytes of data";
 | 
| +    }
 | 
| +     // Seek forward in the buffer after we've written some data to ALSA.
 | 
| +    buffer_->Seek(filled);
 | 
| +    bytes_to_fill -= filled;
 | 
| +  }
 | 
| +
 | 
| +  size_t avialable_space = pa_stream_writable_size(playback_handle_);
 | 
| +  if (avialable_space >= static_cast<size_t>(packet_size_))
 | 
| +    FulfillWriteRequest(avialable_space);
 | 
|  }
 | 
|  
 | 
| -void PulseAudioOutputStream::GetVolume(double* volume) {
 | 
| +void PulseAudioOutputStream::Reset() {
 | 
|    DCHECK_EQ(message_loop_, MessageLoop::current());
 | 
| +  stream_stopped_ = true;
 | 
|  
 | 
| -  *volume = volume_;
 | 
| -}
 | 
| +  pa_threaded_mainloop_lock(pa_mainloop_);
 | 
| +  // Close the stream.
 | 
| +  if (playback_handle_) {
 | 
| +    // Disable all the callbacks before disconnecting.
 | 
| +    pa_stream_set_state_callback(playback_handle_, NULL, NULL);
 | 
|  
 | 
| -uint32 PulseAudioOutputStream::RunDataCallback(
 | 
| -    uint8* dest, uint32 max_size, AudioBuffersState buffers_state) {
 | 
| -  if (source_callback_)
 | 
| -    return source_callback_->OnMoreData(this, dest, max_size, buffers_state);
 | 
| +    pa_stream_flush(playback_handle_, NULL, NULL);
 | 
| +    pa_stream_disconnect(playback_handle_);
 | 
|  
 | 
| -  return 0;
 | 
| +    // Release PulseAudio structures.
 | 
| +    pa_stream_unref(playback_handle_);
 | 
| +    playback_handle_ = NULL;
 | 
| +  }
 | 
| +  if (pa_context_) {
 | 
| +    pa_context_unref(pa_context_);
 | 
| +    pa_context_ = NULL;
 | 
| +  }
 | 
| +  pa_threaded_mainloop_unlock(pa_mainloop_);
 | 
| +  if (pa_mainloop_) {
 | 
| +    pa_threaded_mainloop_free(pa_mainloop_);
 | 
| +    pa_mainloop_ = NULL;
 | 
| +  }
 | 
|  }
 | 
| 
 |