summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 19:06:49 +0000
committerhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 19:06:49 +0000
commit9c32098368e6df8b1c80c5da20131db25b422d40 (patch)
tree86ddf9f4f2b8d0beea3a46fbf2d779b04afdb3ef
parentffbdb354dc57a938943478fd391a7cc49b4f8649 (diff)
downloadchromium_src-9c32098368e6df8b1c80c5da20131db25b422d40.zip
chromium_src-9c32098368e6df8b1c80c5da20131db25b422d40.tar.gz
chromium_src-9c32098368e6df8b1c80c5da20131db25b422d40.tar.bz2
AudioController to perform audio operations on a separate thread
Added AudioController as a wrapper over AudioOutputStream to operate methods of AudioOutputStream on a separate thread managed by AudioController. This way most of the audio operations will be non-blocking. Next step is to modify AudioRendererHost to use AudioController. TEST=media_unittests --gtest_filter=AudioControllerTest.* BUG=39885 Review URL: http://codereview.chromium.org/2477005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49079 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/renderer_host/audio_renderer_host.cc8
-rw-r--r--media/audio/audio_controller.cc329
-rw-r--r--media/audio/audio_controller.h202
-rw-r--r--media/audio/audio_controller_unittest.cc164
-rw-r--r--media/audio/simple_sources.cc4
-rw-r--r--media/audio/simple_sources.h5
-rw-r--r--media/media.gyp3
7 files changed, 705 insertions, 10 deletions
diff --git a/chrome/browser/renderer_host/audio_renderer_host.cc b/chrome/browser/renderer_host/audio_renderer_host.cc
index 0fbc5e6..4d3226c 100644
--- a/chrome/browser/renderer_host/audio_renderer_host.cc
+++ b/chrome/browser/renderer_host/audio_renderer_host.cc
@@ -280,7 +280,7 @@ void AudioRendererHost::IPCAudioSource::Flush() {
if (state_ != kPaused)
return;
- // The following operation is atomic in PushSource so we don't need to lock.
+ AutoLock auto_lock(lock_);
push_source_.ClearAll();
}
@@ -351,10 +351,12 @@ uint32 AudioRendererHost::IPCAudioSource::OnMoreData(AudioOutputStream* stream,
void AudioRendererHost::IPCAudioSource::OnClose(AudioOutputStream* stream) {
// Push source doesn't need to know the stream so just pass in NULL.
- if (!shared_socket_.get())
+ if (!shared_socket_.get()) {
+ AutoLock auto_lock(lock_);
push_source_.OnClose(NULL);
- else
+ } else {
shared_socket_->Close();
+ }
}
void AudioRendererHost::IPCAudioSource::OnError(AudioOutputStream* stream,
diff --git a/media/audio/audio_controller.cc b/media/audio/audio_controller.cc
new file mode 100644
index 0000000..8e9357b
--- /dev/null
+++ b/media/audio/audio_controller.cc
@@ -0,0 +1,329 @@
+// 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_controller.h"
+
+// This constant governs the hardware audio buffer size, this value should be
+// choosen carefully and is platform specific.
+static const int kSamplesPerHardwarePacket = 8192;
+
+static const uint32 kMegabytes = 1024 * 1024;
+
+// The following parameters limit the request buffer and packet size from the
+// renderer to avoid renderer from requesting too much memory.
+static const uint32 kMaxDecodedPacketSize = 2 * kMegabytes;
+static const uint32 kMaxBufferCapacity = 5 * 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) {
+ 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;
+ return true;
+}
+
+namespace media {
+
+AudioController::AudioController(EventHandler* handler, uint32 capacity,
+ SyncReader* sync_reader)
+ : handler_(handler),
+ state_(kCreated),
+ hardware_pending_bytes_(0),
+ buffer_capacity_(capacity),
+ sync_reader_(sync_reader),
+ thread_("AudioControllerThread") {
+}
+
+AudioController::~AudioController() {
+ DCHECK(kClosed == state_ || kCreated == state_);
+}
+
+// static
+scoped_refptr<AudioController> AudioController::Create(
+ EventHandler* event_handler,
+ AudioManager::Format format,
+ int channels,
+ int sample_rate,
+ int bits_per_sample,
+ uint32 buffer_capacity) {
+
+ if (!CheckParameters(channels, sample_rate, bits_per_sample))
+ return NULL;
+
+ // Starts the audio controller thread.
+ scoped_refptr<AudioController> source = new AudioController(
+ event_handler, buffer_capacity, NULL);
+
+ // Start the audio controller thread and post a task to create the
+ // audio stream.
+ source->thread_.Start();
+ source->thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(source.get(), &AudioController::DoCreate,
+ format, channels, sample_rate, bits_per_sample));
+ return source;
+}
+
+// static
+scoped_refptr<AudioController> AudioController::CreateLowLatency(
+ EventHandler* event_handler,
+ AudioManager::Format format,
+ int channels,
+ int sample_rate,
+ int bits_per_sample,
+ SyncReader* sync_reader) {
+
+ DCHECK(sync_reader);
+
+ if (!CheckParameters(channels, sample_rate, bits_per_sample))
+ return NULL;
+
+ // Starts the audio controller thread.
+ scoped_refptr<AudioController> source = new AudioController(
+ event_handler, 0, sync_reader);
+
+ // Start the audio controller thread and post a task to create the
+ // audio stream.
+ source->thread_.Start();
+ source->thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(source.get(), &AudioController::DoCreate,
+ format, channels, sample_rate, bits_per_sample));
+ return source;
+}
+
+void AudioController::Play() {
+ DCHECK(thread_.IsRunning());
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioController::DoPlay));
+}
+
+void AudioController::Pause() {
+ DCHECK(thread_.IsRunning());
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioController::DoPause));
+}
+
+void AudioController::Flush() {
+ DCHECK(thread_.IsRunning());
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioController::DoFlush));
+}
+
+void AudioController::Close() {
+ DCHECK(thread_.IsRunning());
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioController::DoClose));
+ thread_.Stop();
+}
+
+void AudioController::SetVolume(double volume) {
+ DCHECK(thread_.IsRunning());
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioController::DoSetVolume, volume));
+}
+
+void AudioController::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 AudioController::DoCreate(AudioManager::Format format, int channels,
+ int sample_rate, int bits_per_sample) {
+ // 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;
+ }
+
+ uint32 hardware_packet_size = kSamplesPerHardwarePacket * channels *
+ bits_per_sample / 8;
+ if (stream_ && !stream_->Open(hardware_packet_size)) {
+ stream_->Close();
+ stream_ = NULL;
+
+ // TODO(hclam): Define error types.
+ handler_->OnError(this, 0);
+ return;
+ }
+ handler_->OnCreated(this);
+}
+
+void AudioController::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 AudioController::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 AudioController::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 AudioController::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;
+ }
+
+ // If we are in low latency mode then also close the SyncReader.
+ // TODO(hclam): The shutdown procedure for low latency mode if not complete,
+ // especially when OnModeData() is blocked on SyncReader for read and the
+ // above Stop() would deadlock.
+ if (sync_reader_)
+ sync_reader_->Close();
+
+ // 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 AudioController::DoSetVolume(double volume) {
+ DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+
+ if (state_ == kError || state_ == kEmpty)
+ return;
+
+ stream_->SetVolume(volume);
+}
+
+void AudioController::DoReportError(int code) {
+ DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+ handler_->OnError(this, code);
+}
+
+uint32 AudioController::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 AudioController::OnClose(AudioOutputStream* stream) {
+ // Push source doesn't need to know the stream so just pass in NULL.
+ if (!sync_reader_) {
+ AutoLock auto_lock(lock_);
+ push_source_.OnClose(NULL);
+ }
+}
+
+void AudioController::OnError(AudioOutputStream* stream, int code) {
+ // Handle error on the audio controller thread.
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &AudioController::DoReportError, code));
+}
+
+void AudioController::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
diff --git a/media/audio/audio_controller.h b/media/audio/audio_controller.h
new file mode 100644
index 0000000..dae3d58
--- /dev/null
+++ b/media/audio/audio_controller.h
@@ -0,0 +1,202 @@
+// 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.
+
+#ifndef MEDIA_AUDIO_AUDIO_CONTROLLER_H_
+#define MEDIA_AUDIO_AUDIO_CONTROLLER_H_
+
+#include "base/lock.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/thread.h"
+#include "base/time.h"
+#include "media/audio/audio_output.h"
+#include "media/audio/simple_sources.h"
+
+// An AudioController controls an AudioOutputStream and provides data
+// to this output stream. It has an important function that it executes
+// audio operations like play, pause, stop, etc. on a separate thread,
+// namely the audio controller thread.
+//
+// All the public methods of AudioController are non-blocking except close,
+// the actual operations are performed on the audio controller thread.
+//
+// Here is a state diagram for the AudioController:
+//
+// .----> [ Closed / Error ] <------.
+// | ^ |
+// | | |
+// [ Created ] --> [ Playing ] --> [ Paused ]
+// ^ ^ |
+// | | |
+// *[ Empty ] `-----------------'
+//
+// * Initial state
+//
+// There are two modes of buffering operations supported by this class.
+//
+// Regular latency mode:
+// In this mode we receive signals from AudioController and then we
+// enqueue data into it.
+//
+// Low latency mode:
+// In this mode a DataSource object is given to the AudioController
+// and AudioController reads from it synchronously.
+//
+namespace media {
+
+class AudioController : public base::RefCountedThreadSafe<AudioController>,
+ public AudioOutputStream::AudioSourceCallback {
+ public:
+ // Internal state of the source.
+ enum State {
+ kEmpty,
+ kCreated,
+ kPlaying,
+ kPaused,
+ kClosed,
+ kError,
+ };
+
+ // An event handler that receives events from the AudioController. The
+ // following methods are called on the audio controller thread.
+ class EventHandler {
+ public:
+ virtual ~EventHandler() {}
+ virtual void OnCreated(AudioController* controller) = 0;
+ virtual void OnPlaying(AudioController* controller) = 0;
+ virtual void OnPaused(AudioController* controller) = 0;
+ virtual void OnError(AudioController* controller, int error_code) = 0;
+
+ // Audio controller asks for more data.
+ // |pending_bytes| is the number of bytes still on the controller.
+ // |timestamp| is then time when |pending_bytes| is recorded.
+ virtual void OnMoreData(AudioController* controller,
+ base::Time timestamp,
+ uint32 pending_bytes) = 0;
+ };
+
+ // A synchronous reader interface used by AudioController for synchronous
+ // reading.
+ class SyncReader {
+ public:
+ virtual ~SyncReader() {}
+
+ // Notify the synchronous reader the number of bytes in the AudioController
+ // not yet played. This is used by SyncReader to prepare more data and
+ // perform synchronization.
+ virtual void UpdatePendingBytes(uint32 bytes) = 0;
+
+ // Read certain amount of data into |data|. This method returns if some
+ // data is available.
+ virtual uint32 Read(void* data, uint32 size) = 0;
+
+ // Close this synchronous reader.
+ virtual void Close() = 0;
+ };
+
+ virtual ~AudioController();
+
+ // Factory method for creating an AudioController, returns NULL if failed.
+ // If successful, an audio controller thread is created. The audio device
+ // will be created on the audio controller thread and when that is done
+ // event handler will receive a OnCreated() call.
+ static scoped_refptr<AudioController> Create(
+ EventHandler* event_handler,
+ AudioManager::Format format, // Format of the stream.
+ int channels, // Number of channels.
+ int sample_rate, // Sampling frequency/rate.
+ int bits_per_sample, // Number of bits per sample.
+
+ // Soft limit for buffer capacity in this controller. This parameter
+ // is used only in regular latency mode.
+ uint32 buffer_capacity);
+
+ // Factory method for creating a low latency audio stream.
+ static scoped_refptr<AudioController> CreateLowLatency(
+ EventHandler* event_handler,
+ AudioManager::Format format, // Format of the stream.
+ int channels, // Number of channels.
+ int sample_rate, // Sampling frequency/rate.
+ int bits_per_sample, // Number of bits per sample.
+
+ // External synchronous reader for audio controller.
+ SyncReader* sync_reader);
+
+ // Methods to control playback of the stream.
+
+ // Starts the playback of this audio output stream.
+ void Play();
+
+ // Pause this audio output stream.
+ void Pause();
+
+ // Discard all audio data buffered in this output stream. This method only
+ // has effect when the stream is paused.
+ void Flush();
+
+ // Closes the audio output stream and shutdown the audio controller thread.
+ // This method returns only after all operations are completed. This
+ // controller cannot be used after this method is called.
+ void Close();
+
+ // Sets the volume of the audio output stream.
+ void SetVolume(double volume);
+
+ // Enqueue audio |data| into the controller. This method is used only in
+ // the regular latency mode and it is illegal to call this method when
+ // SyncReader is present.
+ void EnqueueData(const uint8* data, uint32 size);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // AudioSourceCallback methods.
+ virtual uint32 OnMoreData(AudioOutputStream* stream, void* dest,
+ uint32 max_size, uint32 pending_bytes);
+ virtual void OnClose(AudioOutputStream* stream);
+ virtual void OnError(AudioOutputStream* stream, int code);
+
+ private:
+ AudioController(EventHandler* handler,
+ uint32 capacity, SyncReader* sync_reader);
+
+ // The following methods are executed on the audio controller thread.
+ void DoCreate(AudioManager::Format format, int channels,
+ int sample_rate, int bits_per_sample);
+ void DoPlay();
+ void DoPause();
+ void DoFlush();
+ void DoClose();
+ void DoSetVolume(double volume);
+ void DoReportError(int code);
+
+ // Helper method to submit a OnMoreData() call to the event handler.
+ void SubmitOnMoreData_Locked();
+
+ EventHandler* handler_;
+ AudioOutputStream* stream_;
+
+ // |state_| is written on the audio controller thread and is read on the
+ // hardware audio thread. These operations need to be locked. But lock
+ // is not required for reading on the audio controller thread.
+ State state_;
+
+ uint32 hardware_pending_bytes_;
+ base::Time last_callback_time_;
+ Lock lock_;
+
+ // PushSource role is to buffer and it's only used in regular latency mode.
+ PushSource push_source_;
+ uint32 buffer_capacity_;
+
+ // SyncReader is used only in low latency mode for synchronous reading.
+ SyncReader* sync_reader_;
+
+ // The audio controller thread that this object runs on.
+ base::Thread thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioController);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_CONTROLLER_H_
diff --git a/media/audio/audio_controller_unittest.cc b/media/audio/audio_controller_unittest.cc
new file mode 100644
index 0000000..5c5a522
--- /dev/null
+++ b/media/audio/audio_controller_unittest.cc
@@ -0,0 +1,164 @@
+// 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 "base/env_var.h"
+#include "base/basictypes.h"
+#include "base/waitable_event.h"
+#include "media/audio/audio_controller.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::Exactly;
+using ::testing::InvokeWithoutArgs;
+using ::testing::NotNull;
+
+namespace media {
+
+class MockAudioControllerEventHandler : public AudioController::EventHandler {
+ public:
+ MockAudioControllerEventHandler() {}
+
+ MOCK_METHOD1(OnCreated, void(AudioController* controller));
+ MOCK_METHOD1(OnPlaying, void(AudioController* controller));
+ MOCK_METHOD1(OnPaused, void(AudioController* controller));
+ MOCK_METHOD2(OnError, void(AudioController* controller, int error_code));
+ MOCK_METHOD3(OnMoreData,
+ void(AudioController* controller,
+ base::Time timestamp, uint32 pending_bytes));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockAudioControllerEventHandler);
+};
+
+class MockAudioControllerSyncReader : public AudioController::SyncReader {
+ public:
+ MockAudioControllerSyncReader() {}
+
+ MOCK_METHOD1(UpdatePendingBytes, void(uint32 bytes));
+ MOCK_METHOD2(Read, uint32(void* data, uint32 size));
+ MOCK_METHOD0(Close, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockAudioControllerSyncReader);
+};
+
+static bool HasAudioDevices() {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ CHECK(audio_man);
+ return audio_man->HasAudioDevices();
+}
+
+static bool IsRunningHeadless() {
+ scoped_ptr<base::EnvVarGetter> env(base::EnvVarGetter::Create());
+ if (env->HasEnv("CHROME_HEADLESS"))
+ return true;
+ return false;
+}
+
+ACTION_P3(SignalEvent, event, count, limit) {
+ if (++*count >= limit) {
+ event->Signal();
+ }
+}
+
+TEST(AudioControllerTest, PlayAndClose) {
+ if (!HasAudioDevices() || IsRunningHeadless())
+ return;
+
+ MockAudioControllerEventHandler event_handler;
+ base::WaitableEvent event(false, false);
+ int count = 0;
+
+ // If OnCreated is called then signal the event.
+ EXPECT_CALL(event_handler, OnCreated(NotNull()))
+ .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal));
+
+ // OnPlaying() will be called only once.
+ EXPECT_CALL(event_handler, OnPlaying(NotNull()))
+ .Times(Exactly(1));
+
+ // If OnMoreData is called enough then signal the event.
+ EXPECT_CALL(event_handler, OnMoreData(NotNull(), _, 0))
+ .Times(AtLeast(10))
+ .WillRepeatedly(SignalEvent(&event, &count, 10));
+
+ scoped_refptr<AudioController> controller = AudioController::Create(
+ &event_handler, AudioManager::AUDIO_PCM_LINEAR, 1,
+ AudioManager::kAudioCDSampleRate, 16, 4096);
+ ASSERT_TRUE(controller.get());
+
+ // Wait for OnCreated() to be called.
+ event.Wait();
+ event.Reset();
+
+ // Play and then wait for the event to be signaled.
+ controller->Play();
+ event.Wait();
+
+ // Now stop the controller. This should shutdown the internal
+ // thread and we hold the only reference to it.
+ controller->Close();
+
+ // TODO(hclam): Make sure releasing the reference to this
+ // object actually destruct it.
+ controller = NULL;
+}
+
+TEST(AudioControllerTest, PlayPauseClose) {
+ if (!HasAudioDevices() || IsRunningHeadless())
+ return;
+
+ MockAudioControllerEventHandler event_handler;
+ base::WaitableEvent event(false, false);
+ int count = 0;
+
+ // If OnCreated is called then signal the event.
+ EXPECT_CALL(event_handler, OnCreated(NotNull()))
+ .Times(Exactly(1))
+ .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal));
+
+ // OnPlaying() will be called only once.
+ EXPECT_CALL(event_handler, OnPlaying(NotNull()))
+ .Times(Exactly(1));
+
+ // If OnMoreData is called enough then signal the event.
+ EXPECT_CALL(event_handler, OnMoreData(NotNull(), _, 0))
+ .Times(AtLeast(10))
+ .WillRepeatedly(SignalEvent(&event, &count, 10));
+
+ // And then OnPaused() will be called.
+ EXPECT_CALL(event_handler, OnPaused(NotNull()))
+ .Times(Exactly(1))
+ .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal));
+
+ scoped_refptr<AudioController> controller = AudioController::Create(
+ &event_handler, AudioManager::AUDIO_PCM_LINEAR, 1,
+ AudioManager::kAudioCDSampleRate, 16, 4096);
+ ASSERT_TRUE(controller.get());
+
+ // Wait for OnCreated() to be called.
+ event.Wait();
+ event.Reset();
+
+ // Play and then wait for the event to be signaled.
+ controller->Play();
+ event.Wait();
+ event.Reset();
+
+ // And then wait for pause to complete.
+ controller->Pause();
+ event.Wait();
+
+ // Now stop the controller. This should shutdown the internal
+ // thread and we hold the only reference to it.
+ controller->Close();
+
+ // TODO(hclam): Make sure releasing the reference to this
+ // object actually destruct it.
+ controller = NULL;
+}
+
+} // namespace media
diff --git a/media/audio/simple_sources.cc b/media/audio/simple_sources.cc
index 4b2ce29..d1669daa 100644
--- a/media/audio/simple_sources.cc
+++ b/media/audio/simple_sources.cc
@@ -60,7 +60,6 @@ PushSource::~PushSource() { }
uint32 PushSource::OnMoreData(AudioOutputStream* stream, void* dest,
uint32 max_size, uint32 pending_bytes) {
- AutoLock auto_lock(buffer_lock_);
return buffer_.Read(static_cast<uint8*>(dest), max_size);
}
@@ -78,13 +77,11 @@ bool PushSource::Write(const void *data, uint32 len) {
NOTREACHED();
return false;
}
- AutoLock auto_lock(buffer_lock_);
buffer_.Append(static_cast<const uint8*>(data), len);
return true;
}
uint32 PushSource::UnProcessedBytes() {
- AutoLock auto_lock(buffer_lock_);
return buffer_.forward_bytes();
}
@@ -94,6 +91,5 @@ void PushSource::ClearAll() {
}
void PushSource::CleanUp() {
- AutoLock auto_lock(buffer_lock_);
buffer_.Clear();
}
diff --git a/media/audio/simple_sources.h b/media/audio/simple_sources.h
index 0ab457b..5c7765c 100644
--- a/media/audio/simple_sources.h
+++ b/media/audio/simple_sources.h
@@ -56,10 +56,11 @@ class PushAudioOutput {
// A fairly basic class to connect a push model provider PushAudioOutput to
// a pull model provider AudioSourceCallback. Fundamentally it manages a series
// of audio buffers and is unaware of the actual audio format.
+// Note that the PushSource is not thread safe and user need to provide locking.
class PushSource : public AudioOutputStream::AudioSourceCallback,
public PushAudioOutput {
public:
- explicit PushSource();
+ PushSource();
virtual ~PushSource();
// Write one buffer.
@@ -82,8 +83,6 @@ class PushSource : public AudioOutputStream::AudioSourceCallback,
void CleanUp();
media::SeekableBuffer buffer_;
- // Serialize access to |buffer_| using this lock.
- Lock buffer_lock_;
};
#endif // MEDIA_AUDIO_SIMPLE_SOURCES_H_
diff --git a/media/media.gyp b/media/media.gyp
index 6821aac..abd32d9 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -21,6 +21,8 @@
],
'msvs_guid': '6AE76406-B03B-11DD-94B1-80B556D89593',
'sources': [
+ 'audio/audio_controller.cc',
+ 'audio/audio_controller.h',
'audio/audio_output.h',
'audio/audio_util.cc',
'audio/audio_util.h',
@@ -180,6 +182,7 @@
'../third_party/openmax/omx_stub.cc',
],
'sources': [
+ 'audio/audio_controller_unittest.cc',
'audio/audio_util_unittest.cc',
'audio/linux/alsa_output_unittest.cc',
'audio/mac/audio_output_mac_unittest.cc',