summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authordalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-16 02:13:06 +0000
committerdalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-16 02:13:06 +0000
commit0883c2a9dd2c9e6beb06ab0d7a4eaf39974e964f (patch)
treead8699d2bbaebfbf1b6ef1166e82b9be39467404 /media
parentbf593b20669803e95073fbf57915800476d60da6 (diff)
downloadchromium_src-0883c2a9dd2c9e6beb06ab0d7a4eaf39974e964f.zip
chromium_src-0883c2a9dd2c9e6beb06ab0d7a4eaf39974e964f.tar.gz
chromium_src-0883c2a9dd2c9e6beb06ab0d7a4eaf39974e964f.tar.bz2
Get PulseAudio implementation working.
- Switches from iterating the PA mainloop to a threaded mainloop which PulseAudio runs on its own. - Fixes several bugs and cleans up the code. Performance is much better than ALSA. I'm able to run audio output at 440 frames smoothly with Pulse whereas ALSA I couldn't get it to run below 1k w/o glitching. Sadly while the jitter is much better too, it's still not as smooth as the 8k buffer we were using previously: http://commondatastorage.googleapis.com/dalecurtis-shared/alsa-vs-pulse.png Which means I still need to look into writing some clock smoothing in order for us to turn on renderer side mixing. Unit tests to come later, mostly just getting it working now. BUG=32757 TEST=media_unittests / manual playback tests. Review URL: https://codereview.chromium.org/11098031 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@168116 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/audio/audio_io.h3
-rw-r--r--media/audio/pulse/pulse_output.cc524
-rw-r--r--media/audio/pulse/pulse_output.h102
-rw-r--r--media/base/audio_bus.cc22
-rw-r--r--media/base/audio_bus.h2
-rw-r--r--media/base/audio_bus_unittest.cc22
6 files changed, 344 insertions, 331 deletions
diff --git a/media/audio/audio_io.h b/media/audio/audio_io.h
index bdc9f6f..cfc71bd 100644
--- a/media/audio/audio_io.h
+++ b/media/audio/audio_io.h
@@ -90,7 +90,8 @@ class MEDIA_EXPORT AudioOutputStream {
virtual ~AudioOutputStream() {}
- // Open the stream. false is returned if the stream cannot be opened.
+ // Open the stream. false is returned if the stream cannot be opened. Open()
+ // must always be followed by a call to Close() even if Open() fails.
virtual bool Open() = 0;
// Starts playing audio and generating AudioSourceCallback::OnMoreData().
diff --git a/media/audio/pulse/pulse_output.cc b/media/audio/pulse/pulse_output.cc
index 82f9fb9..0687e6e 100644
--- a/media/audio/pulse/pulse_output.cc
+++ b/media/audio/pulse/pulse_output.cc
@@ -4,41 +4,45 @@
#include "media/audio/pulse/pulse_output.h"
-#include "base/bind.h"
+#include <pulse/pulseaudio.h>
+
#include "base/message_loop.h"
+#include "media/audio/audio_manager_base.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/audio_util.h"
-#if defined(OS_LINUX)
-#include "media/audio/linux/audio_manager_linux.h"
-#elif defined(OS_OPENBSD)
-#include "media/audio/openbsd/audio_manager_openbsd.h"
-#endif
-#include "media/base/data_buffer.h"
-#include "media/base/seekable_buffer.h"
namespace media {
+// A helper class that acquires pa_threaded_mainloop_lock() while in scope.
+class AutoPulseLock {
+ public:
+ explicit AutoPulseLock(pa_threaded_mainloop* pa_mainloop)
+ : pa_mainloop_(pa_mainloop) {
+ pa_threaded_mainloop_lock(pa_mainloop_);
+ }
+
+ ~AutoPulseLock() {
+ pa_threaded_mainloop_unlock(pa_mainloop_);
+ }
+
+ private:
+ pa_threaded_mainloop* pa_mainloop_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutoPulseLock);
+};
+
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
- // signed and little endian because that is what we gave to ALSA.
case 8:
return PA_SAMPLE_U8;
- // Also 8-bits: PA_SAMPLE_ALAW and PA_SAMPLE_ULAW
case 16:
return PA_SAMPLE_S16LE;
- // Also 16-bits: PA_SAMPLE_S16BE (big endian).
case 24:
return PA_SAMPLE_S24LE;
- // Also 24-bits: PA_SAMPLE_S24BE (big endian).
- // Other cases: PA_SAMPLE_24_32LE (in LSB of 32-bit field, little endian),
- // and PA_SAMPLE_24_32BE (in LSB of 32-bit field, big endian),
case 32:
return PA_SAMPLE_S32LE;
- // Also 32-bits: PA_SAMPLE_S32BE (big endian),
- // PA_SAMPLE_FLOAT32LE (floating point little endian),
- // and PA_SAMPLE_FLOAT32BE (floating point big endian).
default:
+ NOTREACHED() << "Invalid bits per sample: " << bits_per_sample;
return PA_SAMPLE_INVALID;
}
}
@@ -71,202 +75,245 @@ static pa_channel_position ChromiumToPAChannelPosition(Channels channel) {
return PA_CHANNEL_POSITION_SIDE_RIGHT;
case CHANNELS_MAX:
return PA_CHANNEL_POSITION_INVALID;
+ default:
+ NOTREACHED() << "Invalid channel: " << channel;
+ 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);
+ for (Channels ch = LEFT; ch < CHANNELS_MAX;
+ ch = static_cast<Channels>(ch + 1)) {
+ int channel_index = ChannelOrder(channel_layout, ch);
+ if (channel_index < 0)
+ continue;
- // 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;
+ channel_map.map[channel_index] = ChromiumToPAChannelPosition(ch);
}
return channel_map;
}
-static size_t MicrosecondsToBytes(
- uint32 microseconds, uint32 sample_rate, size_t bytes_per_frame) {
- return microseconds * sample_rate * bytes_per_frame /
- base::Time::kMicrosecondsPerSecond;
-}
+// static, pa_context_notify_cb
+void PulseAudioOutputStream::ContextNotifyCallback(pa_context* c,
+ void* p_this) {
+ PulseAudioOutputStream* stream = static_cast<PulseAudioOutputStream*>(p_this);
+
+ // Forward unexpected failures to the AudioSourceCallback if available. All
+ // these variables are only modified under pa_threaded_mainloop_lock() so this
+ // should be thread safe.
+ if (c && stream->source_callback_ &&
+ pa_context_get_state(c) == PA_CONTEXT_FAILED) {
+ stream->source_callback_->OnError(stream, pa_context_errno(c));
+ }
-// static
-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);
+ pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
}
-// static
-void PulseAudioOutputStream::WriteRequestCallback(pa_stream* playback_handle,
- size_t length,
- void* stream_addr) {
- PulseAudioOutputStream* stream =
- reinterpret_cast<PulseAudioOutputStream*>(stream_addr);
+// static, pa_stream_notify_cb
+void PulseAudioOutputStream::StreamNotifyCallback(pa_stream* s, void* p_this) {
+ PulseAudioOutputStream* stream = static_cast<PulseAudioOutputStream*>(p_this);
+
+ // Forward unexpected failures to the AudioSourceCallback if available. All
+ // these variables are only modified under pa_threaded_mainloop_lock() so this
+ // should be thread safe.
+ if (s && stream->source_callback_ &&
+ pa_stream_get_state(s) == PA_STREAM_FAILED) {
+ stream->source_callback_->OnError(
+ stream, pa_context_errno(stream->pa_context_));
+ }
- DCHECK(stream->manager_->GetMessageLoop()->BelongsToCurrentThread());
+ pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
+}
- stream->write_callback_handled_ = true;
+// static, pa_stream_success_cb_t
+void PulseAudioOutputStream::StreamSuccessCallback(pa_stream* s, int success,
+ void* p_this) {
+ PulseAudioOutputStream* stream = static_cast<PulseAudioOutputStream*>(p_this);
+ pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
+}
- // Fulfill write request.
- stream->FulfillWriteRequest(length);
+// static, pa_stream_request_cb_t
+void PulseAudioOutputStream::StreamRequestCallback(pa_stream* s, size_t len,
+ void* p_this) {
+ // Fulfill write request; must always result in a pa_stream_write() call.
+ static_cast<PulseAudioOutputStream*>(p_this)->FulfillWriteRequest(len);
}
PulseAudioOutputStream::PulseAudioOutputStream(const AudioParameters& params,
- AudioManagerPulse* manager)
- : channel_layout_(params.channel_layout()),
- channel_count_(ChannelLayoutToChannelCount(channel_layout_)),
- sample_format_(BitsToPASampleFormat(params.bits_per_sample())),
- sample_rate_(params.sample_rate()),
- bytes_per_frame_(params.GetBytesPerFrame()),
+ AudioManagerBase* manager)
+ : params_(params),
manager_(manager),
pa_context_(NULL),
pa_mainloop_(NULL),
- playback_handle_(NULL),
- packet_size_(params.GetBytesPerBuffer()),
- frames_per_packet_(packet_size_ / bytes_per_frame_),
- client_buffer_(NULL),
+ pa_stream_(NULL),
volume_(1.0f),
- stream_stopped_(true),
- write_callback_handled_(false),
- ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
source_callback_(NULL) {
DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
- // TODO(slock): Sanity check input values.
+ CHECK(params_.IsValid());
+ audio_bus_ = AudioBus::Create(params_);
}
PulseAudioOutputStream::~PulseAudioOutputStream() {
- // All internal structures should already have been freed in Close(),
- // which calls AudioManagerPulse::Release which deletes this object.
- DCHECK(!playback_handle_);
+ // All internal structures should already have been freed in Close(), which
+ // calls AudioManagerBase::ReleaseOutputStream() which deletes this object.
+ DCHECK(!pa_stream_);
DCHECK(!pa_context_);
DCHECK(!pa_mainloop_);
}
+// Helper macro for Open() to avoid code spam and string bloat.
+#define RETURN_ON_FAILURE(expression, message) do { \
+ if (!(expression)) { \
+ if (pa_context_) { \
+ DLOG(ERROR) << message << " Error: " \
+ << pa_strerror(pa_context_errno(pa_context_)); \
+ } else { \
+ DLOG(ERROR) << message; \
+ } \
+ return false; \
+ } \
+} while(0)
+
bool PulseAudioOutputStream::Open() {
DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
- // TODO(slock): Possibly move most of this to an OpenPlaybackDevice function
- // in a new class 'pulse_util', like alsa_util.
+ pa_mainloop_ = pa_threaded_mainloop_new();
+ RETURN_ON_FAILURE(pa_mainloop_, "Failed to create PulseAudio main loop.");
- // 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_);
+ 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);
-
- // 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;
- }
+ RETURN_ON_FAILURE(pa_context_, "Failed to create PulseAudio context.");
+
+ // A state callback must be set before calling pa_threaded_mainloop_lock() or
+ // pa_threaded_mainloop_wait() calls may lead to dead lock.
+ pa_context_set_state_callback(pa_context_, &ContextNotifyCallback, this);
+
+ // Lock the main loop while setting up the context. Failure to do so may lead
+ // to crashes as the PulseAudio thread tries to run before things are ready.
+ AutoPulseLock auto_lock(pa_mainloop_);
+
+ RETURN_ON_FAILURE(
+ pa_threaded_mainloop_start(pa_mainloop_) == 0,
+ "Failed to start PulseAudio main loop.");
+ RETURN_ON_FAILURE(
+ pa_context_connect(pa_context_, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) == 0,
+ "Failed to connect PulseAudio context.");
+
+ // Wait until |pa_context_| is ready. pa_threaded_mainloop_wait() must be
+ // called after pa_context_get_state() in case the context is already ready,
+ // otherwise pa_threaded_mainloop_wait() will hang indefinitely.
+ while (true) {
+ pa_context_state_t context_state = pa_context_get_state(pa_context_);
+ RETURN_ON_FAILURE(
+ PA_CONTEXT_IS_GOOD(context_state), "Invalid PulseAudio context state.");
+ if (context_state == PA_CONTEXT_READY)
+ break;
+ pa_threaded_mainloop_wait(pa_mainloop_);
}
// 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.format = BitsToPASampleFormat(
+ params_.bits_per_sample());
+ pa_sample_specifications.rate = params_.sample_rate();
+ pa_sample_specifications.channels = params_.channels();
// Get channel mapping and open playback stream.
pa_channel_map* map = NULL;
pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
- channel_layout_);
+ params_.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);
+ pa_stream_ = pa_stream_new(
+ pa_context_, "Playback", &pa_sample_specifications, map);
+ RETURN_ON_FAILURE(pa_stream_, "Failed to create PulseAudio stream.");
+ pa_stream_set_state_callback(pa_stream_, &StreamNotifyCallback, this);
- // Initialize client buffer.
- uint32 output_packet_size = frames_per_packet_ * bytes_per_frame_;
- client_buffer_.reset(new media::SeekableBuffer(0, output_packet_size));
+ // Even though we start the stream corked below, PulseAudio will issue one
+ // stream request after setup. FulfillWriteRequest() must fulfill the write.
+ pa_stream_set_write_callback(pa_stream_, &StreamRequestCallback, this);
- // 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.
+ // Tell pulse audio we only want callbacks of a certain size.
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.maxlength = params_.GetBytesPerBuffer();
+ pa_buffer_attributes.minreq = params_.GetBytesPerBuffer();
+ pa_buffer_attributes.prebuf = params_.GetBytesPerBuffer();
+ pa_buffer_attributes.tlength = params_.GetBytesPerBuffer();
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);
-
- if (!playback_handle_) {
- Reset();
- return false;
+ // TODO(dalecurtis): Pulse tends to want really large buffer sizes if we are
+ // not using the native sample rate. We should always open the stream with
+ // PA_STREAM_FIX_RATE and ensure this is true.
+ RETURN_ON_FAILURE(
+ pa_stream_connect_playback(
+ pa_stream_, NULL, &pa_buffer_attributes,
+ static_cast<pa_stream_flags_t>(
+ PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE |
+ PA_STREAM_NOT_MONOTONIC | PA_STREAM_START_CORKED),
+ NULL, NULL) == 0,
+ "Failed to connect PulseAudio stream.");
+
+ // Wait for the stream to be ready.
+ while (true) {
+ pa_stream_state_t stream_state = pa_stream_get_state(pa_stream_);
+ RETURN_ON_FAILURE(
+ PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state.");
+ if (stream_state == PA_STREAM_READY)
+ break;
+ pa_threaded_mainloop_wait(pa_mainloop_);
}
return true;
}
+#undef RETURN_ON_FAILURE
+
void PulseAudioOutputStream::Reset() {
- stream_stopped_ = true;
+ if (!pa_mainloop_) {
+ DCHECK(!pa_stream_);
+ DCHECK(!pa_context_);
+ return;
+ }
- // Close the stream.
- if (playback_handle_) {
- pa_stream_flush(playback_handle_, NULL, NULL);
- pa_stream_disconnect(playback_handle_);
+ {
+ AutoPulseLock auto_lock(pa_mainloop_);
+
+ // Close the stream.
+ if (pa_stream_) {
+ // Ensure all samples are played out before shutdown.
+ WaitForPulseOperation(pa_stream_flush(
+ pa_stream_, &StreamSuccessCallback, this));
+
+ // Release PulseAudio structures.
+ pa_stream_disconnect(pa_stream_);
+ pa_stream_set_write_callback(pa_stream_, NULL, NULL);
+ pa_stream_set_state_callback(pa_stream_, NULL, NULL);
+ pa_stream_unref(pa_stream_);
+ pa_stream_ = NULL;
+ }
- // 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;
+ if (pa_context_) {
+ pa_context_disconnect(pa_context_);
+ pa_context_set_state_callback(pa_context_, NULL, NULL);
+ pa_context_unref(pa_context_);
+ pa_context_ = NULL;
+ }
}
- // Release internal buffer.
- client_buffer_.reset();
+ pa_threaded_mainloop_stop(pa_mainloop_);
+ pa_threaded_mainloop_free(pa_mainloop_);
+ pa_mainloop_ = NULL;
}
void PulseAudioOutputStream::Close() {
@@ -279,138 +326,107 @@ void PulseAudioOutputStream::Close() {
manager_->ReleaseOutputStream(this);
}
-void PulseAudioOutputStream::WaitForWriteRequest() {
- DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
+int PulseAudioOutputStream::GetHardwareLatencyInBytes() {
+ int negative = 0;
+ pa_usec_t pa_latency_micros = 0;
+ if (pa_stream_get_latency(pa_stream_, &pa_latency_micros, &negative) != 0)
+ return 0;
- if (stream_stopped_)
- return;
+ if (negative)
+ return 0;
- // 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_) {
- manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &PulseAudioOutputStream::WaitForWriteRequest,
- weak_factory_.GetWeakPtr()));
- }
-}
-
-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_);
- int frames_filled = RunDataCallback(
- audio_bus_.get(), AudioBuffersState(buffer_delay, hardware_delay));
- size_t packet_size = frames_filled * bytes_per_frame_;
-
- DCHECK_LE(packet_size, packet_size_);
- // Note: If this ever changes to output raw float the data must be clipped and
- // sanitized since it may come from an untrusted source such as NaCl.
- audio_bus_->ToInterleaved(
- frames_filled, bytes_per_frame_ / channel_count_,
- packet->GetWritableData());
-
- 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;
+ return (pa_latency_micros * params_.sample_rate() *
+ params_.GetBytesPerFrame()) / base::Time::kMicrosecondsPerSecond;
}
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;
+ CHECK_EQ(requested_bytes, static_cast<size_t>(params_.GetBytesPerBuffer()));
- // Request more data from the source until we can fulfill the request or
- // fail to receive anymore data.
- bool buffering_successful = true;
- size_t forward_bytes = static_cast<size_t>(client_buffer_->forward_bytes());
- while (forward_bytes < requested_bytes && buffering_successful) {
- buffering_successful = BufferPacketFromSource();
+ int frames_filled = 0;
+ if (source_callback_) {
+ frames_filled = source_callback_->OnMoreData(
+ audio_bus_.get(), AudioBuffersState(0, GetHardwareLatencyInBytes()));
}
- 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);
+ // Zero any unfilled data so it plays back as silence.
+ if (frames_filled < audio_bus_->frames()) {
+ audio_bus_->ZeroFramesPartial(
+ frames_filled, audio_bus_->frames() - frames_filled);
}
- 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.
- manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &PulseAudioOutputStream::FulfillWriteRequest,
- weak_factory_.GetWeakPtr(),
- requested_bytes - bytes_written));
- } else {
- // Continue playback.
- manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &PulseAudioOutputStream::WaitForWriteRequest,
- weak_factory_.GetWeakPtr()));
- }
-}
-
-void PulseAudioOutputStream::WriteToStream(size_t bytes_to_write,
- size_t* bytes_written) {
- *bytes_written = 0;
- while (*bytes_written < bytes_to_write) {
- const uint8* chunk;
- int chunk_size;
-
- // Stop writing if there is no more data available.
- if (!client_buffer_->GetCurrentChunk(&chunk, &chunk_size))
- break;
+ // PulseAudio won't always be able to provide a buffer large enough, so we may
+ // need to request multiple buffers and fill them individually.
+ int current_frame = 0;
+ size_t bytes_remaining = requested_bytes;
+ while (bytes_remaining > 0) {
+ void* buffer = NULL;
+ size_t bytes_to_fill = bytes_remaining;
+ CHECK_GE(pa_stream_begin_write(pa_stream_, &buffer, &bytes_to_fill), 0);
+
+ // In case PulseAudio gives us a bigger buffer than we want, cap our size.
+ bytes_to_fill = std::min(
+ std::min(bytes_remaining, bytes_to_fill),
+ static_cast<size_t>(params_.GetBytesPerBuffer()));
+
+ int frames_to_fill = bytes_to_fill / params_.GetBytesPerFrame();;
+
+ // Note: If this ever changes to output raw float the data must be clipped
+ // and sanitized since it may come from an untrusted source such as NaCl.
+ audio_bus_->ToInterleavedPartial(
+ current_frame, frames_to_fill, params_.bits_per_sample() / 8, buffer);
+ media::AdjustVolume(buffer, bytes_to_fill, params_.channels(),
+ params_.bits_per_sample() / 8, volume_);
+
+ if (pa_stream_write(pa_stream_, buffer, bytes_to_fill, NULL, 0LL,
+ PA_SEEK_RELATIVE) < 0) {
+ if (source_callback_) {
+ source_callback_->OnError(this, pa_context_errno(pa_context_));
+ }
+ }
- // 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;
+ bytes_remaining -= bytes_to_fill;
+ current_frame = frames_to_fill;
}
}
void PulseAudioOutputStream::Start(AudioSourceCallback* callback) {
DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
CHECK(callback);
- DLOG_IF(ERROR, !playback_handle_)
- << "Open() has not been called successfully";
- if (!playback_handle_)
+ CHECK(pa_stream_);
+
+ AutoPulseLock auto_lock(pa_mainloop_);
+
+ // Ensure the context and stream are ready.
+ if (pa_context_get_state(pa_context_) != PA_CONTEXT_READY &&
+ pa_stream_get_state(pa_stream_) != PA_STREAM_READY) {
+ callback->OnError(this, pa_context_errno(pa_context_));
return;
+ }
source_callback_ = callback;
- // Clear buffer, it might still have data in it.
- client_buffer_->Clear();
- stream_stopped_ = false;
-
- // Start playback.
- manager_->GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
- &PulseAudioOutputStream::WaitForWriteRequest,
- weak_factory_.GetWeakPtr()));
+ // Uncork (resume) the stream.
+ WaitForPulseOperation(pa_stream_cork(
+ pa_stream_, 0, &StreamSuccessCallback, this));
}
void PulseAudioOutputStream::Stop() {
DCHECK(manager_->GetMessageLoop()->BelongsToCurrentThread());
- stream_stopped_ = true;
+ // Cork (pause) the stream. Waiting for the main loop lock will ensure
+ // outstanding callbacks have completed.
+ AutoPulseLock auto_lock(pa_mainloop_);
+
+ // Flush the stream prior to cork, doing so after will cause hangs. Write
+ // callbacks are suspended while inside pa_threaded_mainloop_lock() so this
+ // is all thread safe.
+ WaitForPulseOperation(pa_stream_flush(
+ pa_stream_, &StreamSuccessCallback, this));
+
+ WaitForPulseOperation(pa_stream_cork(
+ pa_stream_, 1, &StreamSuccessCallback, this));
+
+ source_callback_ = NULL;
}
void PulseAudioOutputStream::SetVolume(double volume) {
@@ -425,12 +441,12 @@ void PulseAudioOutputStream::GetVolume(double* volume) {
*volume = volume_;
}
-int PulseAudioOutputStream::RunDataCallback(
- AudioBus* audio_bus, AudioBuffersState buffers_state) {
- if (source_callback_)
- return source_callback_->OnMoreData(audio_bus, buffers_state);
-
- return 0;
+void PulseAudioOutputStream::WaitForPulseOperation(pa_operation* op) {
+ CHECK(op);
+ while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) {
+ pa_threaded_mainloop_wait(pa_mainloop_);
+ }
+ pa_operation_unref(op);
}
} // namespace media
diff --git a/media/audio/pulse/pulse_output.h b/media/audio/pulse/pulse_output.h
index 1d39af4..cdd7cfd 100644
--- a/media/audio/pulse/pulse_output.h
+++ b/media/audio/pulse/pulse_output.h
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Creates an audio output stream based on the PulseAudio asynchronous API.
+// Creates an audio output stream based on the PulseAudio asynchronous API;
+// specifically using the pa_threaded_mainloop model.
//
// If the stream is successfully opened, Close() must be called before the
// stream is deleted as Close() is responsible for ensuring resource cleanup
@@ -19,32 +20,22 @@
#ifndef MEDIA_AUDIO_PULSE_PULSE_OUTPUT_H_
#define MEDIA_AUDIO_PULSE_PULSE_OUTPUT_H_
-#include <pulse/pulseaudio.h>
-
#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
#include "media/audio/audio_io.h"
-#include "media/base/channel_layout.h"
-
-namespace media {
+#include "media/audio/audio_parameters.h"
-#if defined(OS_LINUX)
-class AudioManagerLinux;
-typedef AudioManagerLinux AudioManagerPulse;
-#elif defined(OS_OPENBSD)
-class AudioManagerOpenBSD;
-typedef AudioManagerOpenBSD AudioManagerPulse;
-#else
-#error Unsupported platform
-#endif
+struct pa_context;
+struct pa_operation;
+struct pa_stream;
+struct pa_threaded_mainloop;
-class AudioParameters;
-class SeekableBuffer;
+namespace media {
+class AudioManagerBase;
class PulseAudioOutputStream : public AudioOutputStream {
public:
PulseAudioOutputStream(const AudioParameters& params,
- AudioManagerPulse* manager);
+ AudioManagerBase* manager);
virtual ~PulseAudioOutputStream();
@@ -57,72 +48,47 @@ class PulseAudioOutputStream : public AudioOutputStream {
virtual void GetVolume(double* volume) OVERRIDE;
private:
- // PulseAudio Callbacks.
- static void ContextStateCallback(pa_context* context, void* state_addr);
- static void WriteRequestCallback(pa_stream* playback_handle, size_t length,
- void* stream_addr);
+ // Called by PulseAudio when |pa_context_| and |pa_stream_| change state. If
+ // an unexpected failure state change happens and |source_callback_| is set
+ // these methods will forward the error via OnError().
+ static void ContextNotifyCallback(pa_context* c, void* p_this);
+ static void StreamNotifyCallback(pa_stream* s, void* p_this);
- // Iterate the PulseAudio mainloop to get write requests.
- void WaitForWriteRequest();
+ // Triggers pa_threaded_mainloop_signal() to avoid deadlocks.
+ static void StreamSuccessCallback(pa_stream* s, int success, void* p_this);
- // Get another packet from the data source and write it to the client buffer.
- bool BufferPacketFromSource();
+ // Called by PulseAudio when it needs more audio data.
+ static void StreamRequestCallback(pa_stream* s, size_t len, void* p_this);
- // Fulfill a write request from the write request callback. If the write
- // can't be finished a first, post a new attempt to the message loop.
+ // Fulfill a write request from the write request callback. Outputs silence
+ // if the request could not be fulfilled.
void FulfillWriteRequest(size_t requested_bytes);
- // Write data from the client buffer to the PulseAudio stream.
- void WriteToStream(size_t bytes_to_write, size_t* bytes_written);
-
- // API for Proxying calls to the AudioSourceCallback provided during Start().
- int RunDataCallback(AudioBus* audio_bus, AudioBuffersState buffers_state);
-
// Close() helper function to free internal structs.
void Reset();
- // Configuration constants from the constructor. Referencable by all threads
- // since they are constants.
- const ChannelLayout channel_layout_;
- const uint32 channel_count_;
- const pa_sample_format_t sample_format_;
- const uint32 sample_rate_;
- const uint32 bytes_per_frame_;
+ // Returns the current hardware latency value in bytes.
+ int GetHardwareLatencyInBytes();
+
+ // Helper method for waiting on Pulse Audio operations to complete.
+ void WaitForPulseOperation(pa_operation* op);
+
+ // AudioParameters from the constructor.
+ const AudioParameters params_;
// Audio manager that created us. Used to report that we've closed.
- AudioManagerPulse* manager_;
+ AudioManagerBase* manager_;
// PulseAudio API structs.
pa_context* pa_context_;
- pa_mainloop* pa_mainloop_;
-
- // Handle to the actual PulseAudio playback stream.
- pa_stream* playback_handle_;
-
- // Device configuration data. Populated after Open() completes.
- uint32 packet_size_;
- uint32 frames_per_packet_;
-
- // Client side audio buffer feeding pulse audio's server side buffer.
- scoped_ptr<media::SeekableBuffer> client_buffer_;
+ pa_threaded_mainloop* pa_mainloop_;
+ pa_stream* pa_stream_;
// Float representation of volume from 0.0 to 1.0.
float volume_;
- // Flag indicating the code should stop reading from the data source or
- // writing to the PulseAudio server. This is set because the device has
- // entered an unrecoverable error state, or the Close() has executed.
- bool stream_stopped_;
-
- // Whether or not PulseAudio has called the WriteCallback for the most recent
- // set of pa_mainloop iterations.
- bool write_callback_handled_;
-
- // Allows us to run tasks on the PulseAudioOutputStream instance which are
- // bound by its lifetime.
- base::WeakPtrFactory<PulseAudioOutputStream> weak_factory_;
-
- // Callback to audio data source.
+ // Callback to audio data source. Must only be modified while holding a lock
+ // on |pa_mainloop_| via pa_threaded_mainloop_lock().
AudioSourceCallback* source_callback_;
// Container for retrieving data from AudioSourceCallback::OnMoreData().
diff --git a/media/base/audio_bus.cc b/media/base/audio_bus.cc
index 677a0c7..21072ab 100644
--- a/media/base/audio_bus.cc
+++ b/media/base/audio_bus.cc
@@ -61,8 +61,8 @@ static void FromInterleavedInternal(const void* src, int start_frame,
// |Format| is the destination type, |Fixed| is a type larger than |Format|
// such that operations can be made without overflowing.
template<class Format, class Fixed>
-static void ToInterleavedInternal(const AudioBus* source, int frames,
- void* dst) {
+static void ToInterleavedInternal(const AudioBus* source, int start_frame,
+ int frames, void* dst) {
Format* dest = static_cast<Format*>(dst);
static const Format kBias = std::numeric_limits<Format>::is_signed ? 0 :
@@ -75,7 +75,8 @@ static void ToInterleavedInternal(const AudioBus* source, int frames,
int channels = source->channels();
for (int ch = 0; ch < channels; ++ch) {
const float* channel_data = source->channel(ch);
- for (int i = 0, offset = ch; i < frames; ++i, offset += channels) {
+ for (int i = start_frame, offset = ch; i < frames;
+ ++i, offset += channels) {
float v = channel_data[i];
Fixed sample = v * (v < 0 ? -kMinValue : kMaxValue);
@@ -268,19 +269,24 @@ void AudioBus::FromInterleaved(const void* source, int frames,
FromInterleavedPartial(source, 0, frames, bytes_per_sample);
}
-// TODO(dalecurtis): See if intrinsic optimizations help any here.
void AudioBus::ToInterleaved(int frames, int bytes_per_sample,
void* dest) const {
- CheckOverflow(0, frames, frames_);
+ ToInterleavedPartial(0, frames, bytes_per_sample, dest);
+}
+
+// TODO(dalecurtis): See if intrinsic optimizations help any here.
+void AudioBus::ToInterleavedPartial(int start_frame, int frames,
+ int bytes_per_sample, void* dest) const {
+ CheckOverflow(start_frame, frames, frames_);
switch (bytes_per_sample) {
case 1:
- ToInterleavedInternal<uint8, int16>(this, frames, dest);
+ ToInterleavedInternal<uint8, int16>(this, start_frame, frames, dest);
break;
case 2:
- ToInterleavedInternal<int16, int32>(this, frames, dest);
+ ToInterleavedInternal<int16, int32>(this, start_frame, frames, dest);
break;
case 4:
- ToInterleavedInternal<int32, int64>(this, frames, dest);
+ ToInterleavedInternal<int32, int64>(this, start_frame, frames, dest);
break;
default:
NOTREACHED() << "Unsupported bytes per sample encountered.";
diff --git a/media/base/audio_bus.h b/media/base/audio_bus.h
index 49bed82..5ea4e08 100644
--- a/media/base/audio_bus.h
+++ b/media/base/audio_bus.h
@@ -62,6 +62,8 @@ class MEDIA_EXPORT AudioBus {
// any unfilled frames when |frames| is less than frames().
void FromInterleaved(const void* source, int frames, int bytes_per_sample);
void ToInterleaved(int frames, int bytes_per_sample, void* dest) const;
+ void ToInterleavedPartial(int start_frame, int frames, int bytes_per_sample,
+ void* dest) const;
// Similar to FromInterleaved() above, but meant for streaming sources. Does
// not zero out remaining frames, the caller is responsible for doing so using
diff --git a/media/base/audio_bus_unittest.cc b/media/base/audio_bus_unittest.cc
index 476c82e..8fad49d 100644
--- a/media/base/audio_bus_unittest.cc
+++ b/media/base/audio_bus_unittest.cc
@@ -334,4 +334,26 @@ TEST_F(AudioBusTest, ToInterleaved) {
}
}
+// Verify ToInterleavedPartial() interleaves audio correctly.
+TEST_F(AudioBusTest, ToInterleavedPartial) {
+ // Only interleave the middle two frames in each channel.
+ static const int kPartialStart = 1;
+ static const int kPartialFrames = 2;
+ ASSERT_LE(kPartialStart + kPartialFrames, kTestVectorFrames);
+
+ scoped_ptr<AudioBus> expected = AudioBus::Create(
+ kTestVectorChannels, kTestVectorFrames);
+ for (int ch = 0; ch < kTestVectorChannels; ++ch) {
+ memcpy(expected->channel(ch), kTestVectorResult[ch],
+ kTestVectorFrames * sizeof(*expected->channel(ch)));
+ }
+
+ int16 test_array[arraysize(kTestVectorInt16)];
+ expected->ToInterleavedPartial(
+ kPartialStart, kPartialFrames, sizeof(*kTestVectorInt16), test_array);
+ ASSERT_EQ(memcmp(
+ test_array, kTestVectorInt16 + kPartialStart * kTestVectorChannels,
+ kPartialFrames * sizeof(*kTestVectorInt16)), 0);
+}
+
} // namespace media