summaryrefslogtreecommitdiffstats
path: root/media/audio/audio_output_controller.cc
diff options
context:
space:
mode:
Diffstat (limited to 'media/audio/audio_output_controller.cc')
-rw-r--r--media/audio/audio_output_controller.cc355
1 files changed, 355 insertions, 0 deletions
diff --git a/media/audio/audio_output_controller.cc b/media/audio/audio_output_controller.cc
new file mode 100644
index 0000000..62cf148
--- /dev/null
+++ b/media/audio/audio_output_controller.cc
@@ -0,0 +1,355 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/audio/audio_output_controller.h"
+
+// The following parameters limit the request buffer and packet size from the
+// renderer to avoid renderer from requesting too much memory.
+static const uint32 kMegabytes = 1024 * 1024;
+static const uint32 kMaxHardwareBufferSize = 2 * kMegabytes;
+static const int kMaxChannels = 32;
+static const int kMaxBitsPerSample = 64;
+static const int kMaxSampleRate = 192000;
+
+// Return true if the parameters for creating an audio stream is valid.
+// Return false otherwise.
+static bool CheckParameters(int channels, int sample_rate,
+ int bits_per_sample, uint32 hardware_buffer_size) {
+ if (channels <= 0 || channels > kMaxChannels)
+ return false;
+ if (sample_rate <= 0 || sample_rate > kMaxSampleRate)
+ return false;
+ if (bits_per_sample <= 0 || bits_per_sample > kMaxBitsPerSample)
+ return false;
+ if (hardware_buffer_size <= 0 ||
+ hardware_buffer_size > kMaxHardwareBufferSize) {
+ return false;
+ }
+ return true;
+}
+
+namespace media {
+
+AudioOutputController::AudioOutputController(EventHandler* handler,
+ uint32 capacity,
+ SyncReader* sync_reader)
+ : handler_(handler),
+ volume_(0),
+ state_(kEmpty),
+ hardware_pending_bytes_(0),
+ buffer_capacity_(capacity),
+ sync_reader_(sync_reader),
+ thread_("AudioOutputControllerThread") {
+}
+
+AudioOutputController::~AudioOutputController() {
+ DCHECK(kClosed == state_);
+}
+
+// static
+scoped_refptr<AudioOutputController> AudioOutputController::Create(
+ EventHandler* event_handler,
+ AudioManager::Format format,
+ int channels,
+ int sample_rate,
+ int bits_per_sample,
+ uint32 hardware_buffer_size,
+ uint32 buffer_capacity) {
+
+ if (!CheckParameters(channels, sample_rate, bits_per_sample,
+ hardware_buffer_size))
+ return NULL;
+
+ // Starts the audio controller thread.
+ scoped_refptr<AudioOutputController> controller = new AudioOutputController(
+ event_handler, buffer_capacity, NULL);
+
+ // Start the audio controller thread and post a task to create the
+ // audio stream.
+ controller->thread_.Start();
+ controller->thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(controller.get(), &AudioOutputController::DoCreate,
+ format, channels, sample_rate, bits_per_sample,
+ hardware_buffer_size));
+ return controller;
+}
+
+// static
+scoped_refptr<AudioOutputController> AudioOutputController::CreateLowLatency(
+ EventHandler* event_handler,
+ AudioManager::Format format,
+ int channels,
+ int sample_rate,
+ int bits_per_sample,
+ uint32 hardware_buffer_size,
+ SyncReader* sync_reader) {
+
+ DCHECK(sync_reader);
+
+ if (!CheckParameters(channels, sample_rate, bits_per_sample,
+ hardware_buffer_size))
+ return NULL;
+
+ // Starts the audio controller thread.
+ scoped_refptr<AudioOutputController> controller = new AudioOutputController(
+ event_handler, 0, sync_reader);
+
+ // Start the audio controller thread and post a task to create the
+ // audio stream.
+ controller->thread_.Start();
+ controller->thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(controller.get(), &AudioOutputController::DoCreate,
+ format, channels, sample_rate, bits_per_sample,
+ hardware_buffer_size));
+ return controller;
+}
+
+void AudioOutputController::Play() {
+ DCHECK(thread_.IsRunning());
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioOutputController::DoPlay));
+}
+
+void AudioOutputController::Pause() {
+ DCHECK(thread_.IsRunning());
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioOutputController::DoPause));
+}
+
+void AudioOutputController::Flush() {
+ DCHECK(thread_.IsRunning());
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioOutputController::DoFlush));
+}
+
+void AudioOutputController::Close() {
+ if (!thread_.IsRunning()) {
+ // If the thread is not running make sure we are stopped.
+ DCHECK_EQ(kClosed, state_);
+ return;
+ }
+
+ // Wait for all tasks to complete on the audio thread.
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioOutputController::DoClose));
+ thread_.Stop();
+}
+
+void AudioOutputController::SetVolume(double volume) {
+ DCHECK(thread_.IsRunning());
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioOutputController::DoSetVolume, volume));
+}
+
+void AudioOutputController::EnqueueData(const uint8* data, uint32 size) {
+ // Write data to the push source and ask for more data if needed.
+ AutoLock auto_lock(lock_);
+ push_source_.Write(data, size);
+ SubmitOnMoreData_Locked();
+}
+
+void AudioOutputController::DoCreate(AudioManager::Format format, int channels,
+ int sample_rate, int bits_per_sample,
+ uint32 hardware_buffer_size) {
+ DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+ DCHECK_EQ(kEmpty, state_);
+
+ // Create the stream in the first place.
+ stream_ = AudioManager::GetAudioManager()->MakeAudioStream(
+ format, channels, sample_rate, bits_per_sample);
+
+ if (!stream_) {
+ // TODO(hclam): Define error types.
+ handler_->OnError(this, 0);
+ return;
+ }
+
+ if (stream_ && !stream_->Open(hardware_buffer_size)) {
+ stream_->Close();
+ stream_ = NULL;
+
+ // TODO(hclam): Define error types.
+ handler_->OnError(this, 0);
+ return;
+ }
+ // We have successfully opened the stream. Set the initial volume.
+ stream_->SetVolume(volume_);
+
+ // Finally set the state to kCreated.
+ state_ = kCreated;
+
+ // And then report we have been created.
+ handler_->OnCreated(this);
+
+ // If in normal latency mode then start buffering.
+ if (!LowLatencyMode()) {
+ AutoLock auto_lock(lock_);
+ SubmitOnMoreData_Locked();
+ }
+}
+
+void AudioOutputController::DoPlay() {
+ DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+
+ // We can start from created or paused state.
+ if (state_ != kCreated && state_ != kPaused)
+ return;
+
+ State old_state;
+ // Update the |state_| to kPlaying.
+ {
+ AutoLock auto_lock(lock_);
+ old_state = state_;
+ state_ = kPlaying;
+ }
+
+ // We start the AudioOutputStream lazily.
+ if (old_state == kCreated) {
+ stream_->Start(this);
+ }
+
+ // Tell the event handler that we are now playing.
+ handler_->OnPlaying(this);
+}
+
+void AudioOutputController::DoPause() {
+ DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+
+ // We can pause from started state.
+ if (state_ != kPlaying)
+ return;
+
+ // Sets the |state_| to kPaused so we don't draw more audio data.
+ // TODO(hclam): Actually pause the audio device.
+ {
+ AutoLock auto_lock(lock_);
+ state_ = kPaused;
+ }
+
+ handler_->OnPaused(this);
+}
+
+void AudioOutputController::DoFlush() {
+ DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+
+ if (state_ != kPaused)
+ return;
+
+ // TODO(hclam): Actually flush the audio device.
+
+ // If we are in the regular latency mode then flush the push source.
+ if (!sync_reader_) {
+ AutoLock auto_lock(lock_);
+ push_source_.ClearAll();
+ }
+}
+
+void AudioOutputController::DoClose() {
+ DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+ DCHECK_NE(kClosed, state_);
+
+ // |stream_| can be null if creating the device failed in DoCreate().
+ if (stream_) {
+ stream_->Stop();
+ stream_->Close();
+ // After stream is closed it is destroyed, so don't keep a reference to it.
+ stream_ = NULL;
+ }
+
+ // Update the current state. Since the stream is closed at this point
+ // there's no other threads reading |state_| so we don't need to lock.
+ state_ = kClosed;
+}
+
+void AudioOutputController::DoSetVolume(double volume) {
+ DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+
+ // Saves the volume to a member first. We may not be able to set the volume
+ // right away but when the stream is created we'll set the volume.
+ volume_ = volume;
+
+ if (state_ != kPlaying && state_ != kPaused && state_ != kCreated)
+ return;
+
+ stream_->SetVolume(volume_);
+}
+
+void AudioOutputController::DoReportError(int code) {
+ DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+ handler_->OnError(this, code);
+}
+
+uint32 AudioOutputController::OnMoreData(AudioOutputStream* stream,
+ void* dest,
+ uint32 max_size,
+ uint32 pending_bytes) {
+ // If regular latency mode is used.
+ if (!sync_reader_) {
+ AutoLock auto_lock(lock_);
+
+ // Record the callback time.
+ last_callback_time_ = base::Time::Now();
+
+ if (state_ != kPlaying) {
+ // Don't read anything. Save the number of bytes in the hardware buffer.
+ hardware_pending_bytes_ = pending_bytes;
+ return 0;
+ }
+
+ // Push source doesn't need to know the stream and number of pending bytes.
+ // So just pass in NULL and 0.
+ uint32 size = push_source_.OnMoreData(NULL, dest, max_size, 0);
+ hardware_pending_bytes_ = pending_bytes + size;
+ SubmitOnMoreData_Locked();
+ return size;
+ }
+
+ // Low latency mode.
+ uint32 size = sync_reader_->Read(dest, max_size);
+ sync_reader_->UpdatePendingBytes(pending_bytes + size);
+ return size;
+}
+
+void AudioOutputController::OnClose(AudioOutputStream* stream) {
+ // Push source doesn't need to know the stream so just pass in NULL.
+ if (LowLatencyMode()) {
+ sync_reader_->Close();
+ } else {
+ AutoLock auto_lock(lock_);
+ push_source_.OnClose(NULL);
+ }
+}
+
+void AudioOutputController::OnError(AudioOutputStream* stream, int code) {
+ // Handle error on the audio controller thread.
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioOutputController::DoReportError, code));
+}
+
+void AudioOutputController::SubmitOnMoreData_Locked() {
+ lock_.AssertAcquired();
+
+ if (push_source_.UnProcessedBytes() > buffer_capacity_)
+ return;
+
+ base::Time timestamp = last_callback_time_;
+ uint32 pending_bytes = hardware_pending_bytes_ +
+ push_source_.UnProcessedBytes();
+
+ // If we need more data then call the event handler to ask for more data.
+ // It is okay that we don't lock in this block because the parameters are
+ // correct and in the worst case we are just asking more data than needed.
+ AutoUnlock auto_unlock(lock_);
+ handler_->OnMoreData(this, timestamp, pending_bytes);
+}
+
+} // namespace media