summaryrefslogtreecommitdiffstats
path: root/media/audio
diff options
context:
space:
mode:
authortommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-27 18:30:13 +0000
committertommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-27 18:30:13 +0000
commit9840d2cf96a2d9873365f4a89b3fd3542730a1d4 (patch)
tree53393b971f49e8a40233c120945723cece9450c4 /media/audio
parent0392ff47c388699e16f9b7260f72fe491432db90 (diff)
downloadchromium_src-9840d2cf96a2d9873365f4a89b3fd3542730a1d4.zip
chromium_src-9840d2cf96a2d9873365f4a89b3fd3542730a1d4.tar.gz
chromium_src-9840d2cf96a2d9873365f4a89b3fd3542730a1d4.tar.bz2
Move AudioDevice and AudioInputDevice to media.
This CL does the following: * Move AudioDevice, AudioInputDevice out of content, into media/audio. * ...and a couple of dependent classes: AudioDeviceThread and ScopedLoopObserver. * ...and the unit test. * Renamed AudioDevice -> AudioOutputDevice * Moved the classes into the media namespace. * Updated the unit test code as necessary. Aside from the unit test*, there are minimal code changes. Only what was required to make things build and work as before - mostly just adding or removing "media::". * The unit test changes were to add expectations for AddDelegate/RemoveDelegate since previously a mock class was inheriting from AudioMessageFilter and not the IPC interface. Review URL: https://chromiumcodereview.appspot.com/10834033 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148777 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r--media/audio/audio_device_thread.cc208
-rw-r--r--media/audio/audio_device_thread.h111
-rw-r--r--media/audio/audio_input_device.cc350
-rw-r--r--media/audio/audio_input_device.h211
-rw-r--r--media/audio/audio_output_device.cc280
-rw-r--r--media/audio/audio_output_device.h161
-rw-r--r--media/audio/audio_output_device_unittest.cc257
-rw-r--r--media/audio/audio_output_ipc.h8
-rw-r--r--media/audio/mac/audio_manager_mac.cc4
-rw-r--r--media/audio/scoped_loop_observer.cc47
-rw-r--r--media/audio/scoped_loop_observer.h50
11 files changed, 1682 insertions, 5 deletions
diff --git a/media/audio/audio_device_thread.cc b/media/audio/audio_device_thread.cc
new file mode 100644
index 0000000..92831ab
--- /dev/null
+++ b/media/audio/audio_device_thread.cc
@@ -0,0 +1,208 @@
+// Copyright (c) 2012 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_device_thread.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "media/audio/audio_util.h"
+
+using base::PlatformThread;
+
+namespace media {
+
+// The actual worker thread implementation. It's very bare bones and much
+// simpler than SimpleThread (no synchronization in Start, etc) and supports
+// joining the thread handle asynchronously via a provided message loop even
+// after the Thread object itself has been deleted.
+class AudioDeviceThread::Thread
+ : public PlatformThread::Delegate,
+ public base::RefCountedThreadSafe<AudioDeviceThread::Thread> {
+ public:
+ Thread(AudioDeviceThread::Callback* callback,
+ base::SyncSocket::Handle socket,
+ const char* thread_name);
+
+ void Start();
+
+ // Stops the thread. If |loop_for_join| is non-NULL, the function posts
+ // a task to join (close) the thread handle later instead of waiting for
+ // the thread. If loop_for_join is NULL, then the function waits
+ // synchronously for the thread to terminate.
+ void Stop(MessageLoop* loop_for_join);
+
+ private:
+ friend class base::RefCountedThreadSafe<AudioDeviceThread::Thread>;
+ virtual ~Thread();
+
+ // Overrides from PlatformThread::Delegate.
+ virtual void ThreadMain() OVERRIDE;
+
+ // Runs the loop that reads from the socket.
+ void Run();
+
+ private:
+ base::PlatformThreadHandle thread_;
+ AudioDeviceThread::Callback* callback_;
+ base::CancelableSyncSocket socket_;
+ base::Lock callback_lock_;
+ const char* thread_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(Thread);
+};
+
+// AudioDeviceThread implementation
+
+AudioDeviceThread::AudioDeviceThread() {
+}
+
+AudioDeviceThread::~AudioDeviceThread() {
+ DCHECK(!thread_);
+}
+
+void AudioDeviceThread::Start(AudioDeviceThread::Callback* callback,
+ base::SyncSocket::Handle socket,
+ const char* thread_name) {
+ base::AutoLock auto_lock(thread_lock_);
+ CHECK(thread_ == NULL);
+ thread_ = new AudioDeviceThread::Thread(callback, socket, thread_name);
+ thread_->Start();
+}
+
+void AudioDeviceThread::Stop(MessageLoop* loop_for_join) {
+ base::AutoLock auto_lock(thread_lock_);
+ if (thread_) {
+ thread_->Stop(loop_for_join);
+ thread_ = NULL;
+ }
+}
+
+bool AudioDeviceThread::IsStopped() {
+ base::AutoLock auto_lock(thread_lock_);
+ return thread_ == NULL;
+}
+
+// AudioDeviceThread::Thread implementation
+AudioDeviceThread::Thread::Thread(AudioDeviceThread::Callback* callback,
+ base::SyncSocket::Handle socket,
+ const char* thread_name)
+ : thread_(base::kNullThreadHandle),
+ callback_(callback),
+ socket_(socket),
+ thread_name_(thread_name) {
+}
+
+AudioDeviceThread::Thread::~Thread() {
+ DCHECK_EQ(thread_, base::kNullThreadHandle) << "Stop wasn't called";
+}
+
+void AudioDeviceThread::Thread::Start() {
+ base::AutoLock auto_lock(callback_lock_);
+ DCHECK_EQ(thread_, base::kNullThreadHandle);
+ // This reference will be released when the thread exists.
+ AddRef();
+
+ PlatformThread::CreateWithPriority(0, this, &thread_,
+ base::kThreadPriority_RealtimeAudio);
+ CHECK(thread_ != base::kNullThreadHandle);
+}
+
+void AudioDeviceThread::Thread::Stop(MessageLoop* loop_for_join) {
+ socket_.Shutdown();
+
+ base::PlatformThreadHandle thread = base::kNullThreadHandle;
+
+ { // NOLINT
+ base::AutoLock auto_lock(callback_lock_);
+ callback_ = NULL;
+ std::swap(thread, thread_);
+ }
+
+ if (thread != base::kNullThreadHandle) {
+ if (loop_for_join) {
+ loop_for_join->PostTask(FROM_HERE,
+ base::Bind(&base::PlatformThread::Join, thread));
+ } else {
+ base::PlatformThread::Join(thread);
+ }
+ }
+}
+
+void AudioDeviceThread::Thread::ThreadMain() {
+ PlatformThread::SetName(thread_name_);
+
+ // Singleton access is safe from this thread as long as callback is non-NULL.
+ // The callback is the only point where the thread calls out to 'unknown' code
+ // that might touch singletons and the lifetime of the callback is controlled
+ // by another thread on which singleton access is OK as well.
+ base::ThreadRestrictions::SetSingletonAllowed(true);
+
+ { // NOLINT
+ base::AutoLock auto_lock(callback_lock_);
+ if (callback_)
+ callback_->InitializeOnAudioThread();
+ }
+
+ Run();
+
+ // Release the reference for the thread. Note that after this, the Thread
+ // instance will most likely be deleted.
+ Release();
+}
+
+void AudioDeviceThread::Thread::Run() {
+ while (true) {
+ int pending_data = 0;
+ size_t bytes_read = socket_.Receive(&pending_data, sizeof(pending_data));
+ if (bytes_read != sizeof(pending_data)) {
+ DCHECK_EQ(bytes_read, 0U);
+ break;
+ }
+
+ base::AutoLock auto_lock(callback_lock_);
+ if (callback_)
+ callback_->Process(pending_data);
+ }
+}
+
+// AudioDeviceThread::Callback implementation
+
+AudioDeviceThread::Callback::Callback(
+ const AudioParameters& audio_parameters,
+ base::SharedMemoryHandle memory, int memory_length)
+ : audio_parameters_(audio_parameters),
+ samples_per_ms_(audio_parameters.sample_rate() / 1000),
+ bytes_per_ms_(audio_parameters.channels() *
+ (audio_parameters_.bits_per_sample() / 8) *
+ samples_per_ms_),
+ shared_memory_(memory, false),
+ memory_length_(memory_length) {
+ CHECK_NE(bytes_per_ms_, 0); // Catch division by zero early.
+ CHECK_NE(samples_per_ms_, 0);
+}
+
+AudioDeviceThread::Callback::~Callback() {
+ for (size_t i = 0; i < audio_data_.size(); ++i)
+ delete [] audio_data_[i];
+}
+
+void AudioDeviceThread::Callback::InitializeOnAudioThread() {
+ DCHECK(audio_data_.empty());
+
+ MapSharedMemory();
+ DCHECK(shared_memory_.memory() != NULL);
+
+ audio_data_.reserve(audio_parameters_.channels());
+ for (int i = 0; i < audio_parameters_.channels(); ++i) {
+ float* channel_data = new float[audio_parameters_.frames_per_buffer()];
+ audio_data_.push_back(channel_data);
+ }
+}
+
+} // namespace media.
diff --git a/media/audio/audio_device_thread.h b/media/audio/audio_device_thread.h
new file mode 100644
index 0000000..a50339a
--- /dev/null
+++ b/media/audio/audio_device_thread.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 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_DEVICE_THREAD_H_
+#define MEDIA_AUDIO_AUDIO_DEVICE_THREAD_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/shared_memory.h"
+#include "base/sync_socket.h"
+#include "base/synchronization/lock.h"
+#include "media/base/media_export.h"
+#include "media/audio/audio_parameters.h"
+
+class MessageLoop;
+
+namespace media {
+
+// Data transfer between browser and render process uses a combination
+// of sync sockets and shared memory. To read from the socket and render
+// data, we use a worker thread, a.k.a. the AudioDeviceThread, which reads
+// data from the browser via the socket and fills the shared memory from the
+// audio thread via the AudioDeviceThread::Callback interface/class.
+// For more details see the documentation in audio_device.h.
+//
+// TODO(tommi): Multiple audio input/output device instances should be able to
+// share the same thread instead of spinning one per instance.
+class MEDIA_EXPORT AudioDeviceThread {
+ public:
+ // This is the callback interface/base class that Audio[Output|Input]Device
+ // implements to render input/output data. The callbacks run on the
+ // thread owned by AudioDeviceThread.
+ class Callback {
+ public:
+ Callback(const AudioParameters& audio_parameters,
+ base::SharedMemoryHandle memory,
+ int memory_length);
+ virtual ~Callback();
+
+ // One time initialization for the callback object on the audio thread.
+ void InitializeOnAudioThread();
+
+ // Derived implementations must call shared_memory_.Map appropriately
+ // before Process can be called.
+ virtual void MapSharedMemory() = 0;
+
+ // Called whenever we receive notifications about pending data.
+ virtual void Process(int pending_data) = 0;
+
+ protected:
+ // Protected so that derived classes can access directly.
+ // The variables are 'const' since values are calculated/set in the
+ // constructor and must never change.
+ const AudioParameters audio_parameters_;
+ const int samples_per_ms_;
+ const int bytes_per_ms_;
+
+ // Audio buffers that are allocated in InitializeOnAudioThread() based on
+ // info from audio_parameters_.
+ std::vector<float*> audio_data_;
+ base::SharedMemory shared_memory_;
+ const int memory_length_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Callback);
+ };
+
+ AudioDeviceThread();
+ ~AudioDeviceThread();
+
+ // Starts the audio thread. The thread must not already be running.
+ void Start(AudioDeviceThread::Callback* callback,
+ base::SyncSocket::Handle socket,
+ const char* thread_name);
+
+ // This tells the audio thread to stop and clean up the data.
+ // The method can stop the thread synchronously or asynchronously.
+ // In the latter case, the thread will still be running after Stop()
+ // returns, but the callback pointer is cleared so no further callbacks will
+ // be made (IOW after Stop() returns, it is safe to delete the callback).
+ // The |loop_for_join| parameter is required for asynchronous operation
+ // in order to join the worker thread and close the thread handle later via a
+ // posted task.
+ // If set to NULL, function will wait for the thread to exit before returning.
+ void Stop(MessageLoop* loop_for_join);
+
+ // Returns true if the thread is stopped or stopping.
+ bool IsStopped();
+
+ private:
+ // Our own private SimpleThread override. We implement this in a
+ // private class so that we get the following benefits:
+ // 1) AudioDeviceThread doesn't expose SimpleThread methods.
+ // I.e. the caller can't call Start()/Stop() - which would be bad.
+ // 2) We override ThreadMain to add additional on-thread initialization
+ // while still synchronized with SimpleThread::Start() to provide
+ // reliable initialization.
+ class Thread;
+
+ base::Lock thread_lock_;
+ scoped_refptr<AudioDeviceThread::Thread> thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDeviceThread);
+};
+
+} // namespace media.
+
+#endif // MEDIA_AUDIO_AUDIO_DEVICE_THREAD_H_
diff --git a/media/audio/audio_input_device.cc b/media/audio/audio_input_device.cc
new file mode 100644
index 0000000..381af19
--- /dev/null
+++ b/media/audio/audio_input_device.cc
@@ -0,0 +1,350 @@
+// Copyright (c) 2012 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_input_device.h"
+
+#include "base/bind.h"
+#include "base/message_loop.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/audio_util.h"
+
+namespace media {
+
+AudioInputDevice::CaptureCallback::~CaptureCallback() {}
+AudioInputDevice::CaptureEventHandler::~CaptureEventHandler() {}
+
+// Takes care of invoking the capture callback on the audio thread.
+// An instance of this class is created for each capture stream in
+// OnLowLatencyCreated().
+class AudioInputDevice::AudioThreadCallback
+ : public AudioDeviceThread::Callback {
+ public:
+ AudioThreadCallback(const AudioParameters& audio_parameters,
+ base::SharedMemoryHandle memory,
+ int memory_length,
+ CaptureCallback* capture_callback);
+ virtual ~AudioThreadCallback();
+
+ virtual void MapSharedMemory() OVERRIDE;
+
+ // Called whenever we receive notifications about pending data.
+ virtual void Process(int pending_data) OVERRIDE;
+
+ private:
+ CaptureCallback* capture_callback_;
+ DISALLOW_COPY_AND_ASSIGN(AudioThreadCallback);
+};
+
+AudioInputDevice::AudioInputDevice(
+ AudioInputIPC* ipc,
+ const scoped_refptr<base::MessageLoopProxy>& io_loop)
+ : ScopedLoopObserver(io_loop),
+ callback_(NULL),
+ event_handler_(NULL),
+ ipc_(ipc),
+ stream_id_(0),
+ session_id_(0),
+ pending_device_ready_(false),
+ agc_is_enabled_(false) {
+ CHECK(ipc_);
+}
+
+void AudioInputDevice::Initialize(const AudioParameters& params,
+ CaptureCallback* callback,
+ CaptureEventHandler* event_handler) {
+ DCHECK(!callback_);
+ DCHECK(!event_handler_);
+ audio_parameters_ = params;
+ callback_ = callback;
+ event_handler_ = event_handler;
+}
+
+void AudioInputDevice::SetDevice(int session_id) {
+ DVLOG(1) << "SetDevice (session_id=" << session_id << ")";
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioInputDevice::SetSessionIdOnIOThread, this, session_id));
+}
+
+void AudioInputDevice::Start() {
+ DVLOG(1) << "Start()";
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioInputDevice::InitializeOnIOThread, this));
+}
+
+void AudioInputDevice::Stop() {
+ DVLOG(1) << "Stop()";
+
+ {
+ base::AutoLock auto_lock(audio_thread_lock_);
+ audio_thread_.Stop(MessageLoop::current());
+ }
+
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioInputDevice::ShutDownOnIOThread, this));
+}
+
+void AudioInputDevice::SetVolume(double volume) {
+ if (volume < 0 || volume > 1.0) {
+ DLOG(ERROR) << "Invalid volume value specified";
+ return;
+ }
+
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioInputDevice::SetVolumeOnIOThread, this, volume));
+}
+
+void AudioInputDevice::SetAutomaticGainControl(bool enabled) {
+ DVLOG(1) << "SetAutomaticGainControl(enabled=" << enabled << ")";
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioInputDevice::SetAutomaticGainControlOnIOThread,
+ this, enabled));
+}
+
+void AudioInputDevice::OnStreamCreated(
+ base::SharedMemoryHandle handle,
+ base::SyncSocket::Handle socket_handle,
+ int length) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+#if defined(OS_WIN)
+ DCHECK(handle);
+ DCHECK(socket_handle);
+#else
+ DCHECK_GE(handle.fd, 0);
+ DCHECK_GE(socket_handle, 0);
+#endif
+ DCHECK(length);
+ DVLOG(1) << "OnStreamCreated (stream_id=" << stream_id_ << ")";
+
+ // We should only get this callback if stream_id_ is valid. If it is not,
+ // the IPC layer should have closed the shared memory and socket handles
+ // for us and not invoked the callback. The basic assertion is that when
+ // stream_id_ is 0 the AudioInputDevice instance is not registered as a
+ // delegate and hence it should not receive callbacks.
+ DCHECK(stream_id_);
+
+ base::AutoLock auto_lock(audio_thread_lock_);
+
+ DCHECK(audio_thread_.IsStopped());
+ audio_callback_.reset(
+ new AudioInputDevice::AudioThreadCallback(audio_parameters_, handle,
+ length, callback_));
+ audio_thread_.Start(audio_callback_.get(), socket_handle, "AudioInputDevice");
+
+ MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(&AudioInputDevice::StartOnIOThread, this));
+}
+
+void AudioInputDevice::OnVolume(double volume) {
+ NOTIMPLEMENTED();
+}
+
+void AudioInputDevice::OnStateChanged(
+ AudioInputIPCDelegate::State state) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+
+ // Do nothing if the stream has been closed.
+ if (!stream_id_)
+ return;
+
+ switch (state) {
+ case AudioInputIPCDelegate::kStopped:
+ // TODO(xians): Should we just call ShutDownOnIOThread here instead?
+ ipc_->RemoveDelegate(stream_id_);
+
+ audio_thread_.Stop(MessageLoop::current());
+ audio_callback_.reset();
+
+ if (event_handler_)
+ event_handler_->OnDeviceStopped();
+
+ stream_id_ = 0;
+ pending_device_ready_ = false;
+ break;
+ case AudioInputIPCDelegate::kRecording:
+ NOTIMPLEMENTED();
+ break;
+ case AudioInputIPCDelegate::kError:
+ DLOG(WARNING) << "AudioInputDevice::OnStateChanged(kError)";
+ // Don't dereference the callback object if the audio thread
+ // is stopped or stopping. That could mean that the callback
+ // object has been deleted.
+ // TODO(tommi): Add an explicit contract for clearing the callback
+ // object. Possibly require calling Initialize again or provide
+ // a callback object via Start() and clear it in Stop().
+ if (!audio_thread_.IsStopped())
+ callback_->OnCaptureError();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void AudioInputDevice::OnDeviceReady(const std::string& device_id) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ DVLOG(1) << "OnDeviceReady (device_id=" << device_id << ")";
+
+ // Takes care of the case when Stop() is called before OnDeviceReady().
+ if (!pending_device_ready_)
+ return;
+
+ // If AudioInputDeviceManager returns an empty string, it means no device
+ // is ready for start.
+ if (device_id.empty()) {
+ ipc_->RemoveDelegate(stream_id_);
+ stream_id_ = 0;
+ } else {
+ ipc_->CreateStream(stream_id_, audio_parameters_, device_id,
+ agc_is_enabled_);
+ }
+
+ pending_device_ready_ = false;
+ // Notify the client that the device has been started.
+ if (event_handler_)
+ event_handler_->OnDeviceStarted(device_id);
+}
+
+void AudioInputDevice::OnIPCClosed() {
+ ipc_ = NULL;
+}
+
+AudioInputDevice::~AudioInputDevice() {
+ // TODO(henrika): The current design requires that the user calls
+ // Stop before deleting this class.
+ CHECK_EQ(0, stream_id_);
+}
+
+void AudioInputDevice::InitializeOnIOThread() {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ // Make sure we don't call Start() more than once.
+ DCHECK_EQ(0, stream_id_);
+ if (stream_id_)
+ return;
+
+ stream_id_ = ipc_->AddDelegate(this);
+ // If |session_id_| is not specified, it will directly create the stream;
+ // otherwise it will send a AudioInputHostMsg_StartDevice msg to the browser
+ // and create the stream when getting a OnDeviceReady() callback.
+ if (!session_id_) {
+ ipc_->CreateStream(stream_id_, audio_parameters_,
+ AudioManagerBase::kDefaultDeviceId, agc_is_enabled_);
+ } else {
+ ipc_->StartDevice(stream_id_, session_id_);
+ pending_device_ready_ = true;
+ }
+}
+
+void AudioInputDevice::SetSessionIdOnIOThread(int session_id) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ session_id_ = session_id;
+}
+
+void AudioInputDevice::StartOnIOThread() {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ if (stream_id_)
+ ipc_->RecordStream(stream_id_);
+}
+
+void AudioInputDevice::ShutDownOnIOThread() {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ // NOTE: |completion| may be NULL.
+ // Make sure we don't call shutdown more than once.
+ if (stream_id_) {
+ ipc_->CloseStream(stream_id_);
+ ipc_->RemoveDelegate(stream_id_);
+
+ stream_id_ = 0;
+ session_id_ = 0;
+ pending_device_ready_ = false;
+ agc_is_enabled_ = false;
+ }
+
+ // We can run into an issue where ShutDownOnIOThread is called right after
+ // OnStreamCreated is called in cases where Start/Stop are called before we
+ // get the OnStreamCreated callback. To handle that corner case, we call
+ // Stop(). In most cases, the thread will already be stopped.
+ // Another situation is when the IO thread goes away before Stop() is called
+ // in which case, we cannot use the message loop to close the thread handle
+ // and can't not rely on the main thread existing either.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ audio_thread_.Stop(NULL);
+ audio_callback_.reset();
+}
+
+void AudioInputDevice::SetVolumeOnIOThread(double volume) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ if (stream_id_)
+ ipc_->SetVolume(stream_id_, volume);
+}
+
+void AudioInputDevice::SetAutomaticGainControlOnIOThread(bool enabled) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ DCHECK_EQ(0, stream_id_) <<
+ "The AGC state can not be modified while capturing is active.";
+ if (stream_id_)
+ return;
+
+ // We simply store the new AGC setting here. This value will be used when
+ // a new stream is initialized and by GetAutomaticGainControl().
+ agc_is_enabled_ = enabled;
+}
+
+void AudioInputDevice::WillDestroyCurrentMessageLoop() {
+ LOG(ERROR) << "IO loop going away before the input device has been stopped";
+ ShutDownOnIOThread();
+}
+
+// AudioInputDevice::AudioThreadCallback
+AudioInputDevice::AudioThreadCallback::AudioThreadCallback(
+ const AudioParameters& audio_parameters,
+ base::SharedMemoryHandle memory,
+ int memory_length,
+ CaptureCallback* capture_callback)
+ : AudioDeviceThread::Callback(audio_parameters, memory, memory_length),
+ capture_callback_(capture_callback) {
+}
+
+AudioInputDevice::AudioThreadCallback::~AudioThreadCallback() {
+}
+
+void AudioInputDevice::AudioThreadCallback::MapSharedMemory() {
+ shared_memory_.Map(memory_length_);
+}
+
+void AudioInputDevice::AudioThreadCallback::Process(int pending_data) {
+ // The shared memory represents parameters, size of the data buffer and the
+ // actual data buffer containing audio data. Map the memory into this
+ // structure and parse out parameters and the data area.
+ AudioInputBuffer* buffer =
+ reinterpret_cast<AudioInputBuffer*>(shared_memory_.memory());
+ DCHECK_EQ(buffer->params.size,
+ memory_length_ - sizeof(AudioInputBufferParameters));
+ double volume = buffer->params.volume;
+
+ int audio_delay_milliseconds = pending_data / bytes_per_ms_;
+ int16* memory = reinterpret_cast<int16*>(&buffer->audio[0]);
+ const size_t number_of_frames = audio_parameters_.frames_per_buffer();
+ const int bytes_per_sample = sizeof(memory[0]);
+
+ // Deinterleave each channel and convert to 32-bit floating-point
+ // with nominal range -1.0 -> +1.0.
+ for (int channel_index = 0; channel_index < audio_parameters_.channels();
+ ++channel_index) {
+ DeinterleaveAudioChannel(memory,
+ audio_data_[channel_index],
+ audio_parameters_.channels(),
+ channel_index,
+ bytes_per_sample,
+ number_of_frames);
+ }
+
+ // Deliver captured data to the client in floating point format
+ // and update the audio-delay measurement.
+ capture_callback_->Capture(audio_data_, number_of_frames,
+ audio_delay_milliseconds, volume);
+}
+
+} // namespace media
diff --git a/media/audio/audio_input_device.h b/media/audio/audio_input_device.h
new file mode 100644
index 0000000..42d6869
--- /dev/null
+++ b/media/audio/audio_input_device.h
@@ -0,0 +1,211 @@
+// Copyright (c) 2012 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.
+
+// Low-latency audio capturing class utilizing audio input stream provided
+// by a server (browser) process by use of an IPC interface.
+//
+// Relationship of classes:
+//
+// AudioInputController AudioInputDevice
+// ^ ^
+// | |
+// v IPC v
+// AudioInputRendererHost <---------> AudioInputIPCDelegate
+// ^ (impl in AudioInputMessageFilter)
+// |
+// v
+// AudioInputDeviceManager
+//
+// Transportation of audio samples from the browser to the render process
+// is done by using shared memory in combination with a SyncSocket.
+// The AudioInputDevice user registers an AudioInputDevice::CaptureCallback by
+// calling Initialize(). The callback will be called with recorded audio from
+// the underlying audio layers.
+// The session ID is used by the AudioInputRendererHost to start the device
+// referenced by this ID.
+//
+// State sequences:
+//
+// Sequence where session_id has not been set using SetDevice():
+// ('<-' signifies callbacks, -> signifies calls made by AudioInputDevice)
+// Start -> InitializeOnIOThread -> CreateStream ->
+// <- OnStreamCreated <-
+// -> StartOnIOThread -> PlayStream ->
+//
+// Sequence where session_id has been set using SetDevice():
+// Start -> InitializeOnIOThread -> StartDevice ->
+// <- OnDeviceReady <-
+// -> CreateStream ->
+// <- OnStreamCreated <-
+// -> StartOnIOThread -> PlayStream ->
+//
+// AudioInputDevice::Capture => low latency audio transport on audio thread =>
+// |
+// Stop --> ShutDownOnIOThread ------> CloseStream -> Close
+//
+// This class depends on two threads to function:
+//
+// 1. An IO thread.
+// This thread is used to asynchronously process Start/Stop etc operations
+// that are available via the public interface. The public methods are
+// asynchronous and simply post a task to the IO thread to actually perform
+// the work.
+// 2. Audio transport thread.
+// Responsible for calling the CaptureCallback and feed audio samples from
+// the server side audio layer using a socket and shared memory.
+//
+// Implementation notes:
+// - The user must call Stop() before deleting the class instance.
+
+#ifndef MEDIA_AUDIO_AUDIO_INPUT_DEVICE_H_
+#define MEDIA_AUDIO_AUDIO_INPUT_DEVICE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/shared_memory.h"
+#include "media/audio/audio_device_thread.h"
+#include "media/audio/audio_input_ipc.h"
+#include "media/audio/audio_parameters.h"
+#include "media/audio/scoped_loop_observer.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// TODO(henrika): This class is based on the AudioOutputDevice class and it has
+// many components in common. Investigate potential for re-factoring.
+// TODO(henrika): Add support for event handling (e.g. OnStateChanged,
+// OnCaptureStopped etc.) and ensure that we can deliver these notifications
+// to any clients using this class.
+class MEDIA_EXPORT AudioInputDevice
+ : NON_EXPORTED_BASE(public AudioInputIPCDelegate),
+ NON_EXPORTED_BASE(public ScopedLoopObserver),
+ public base::RefCountedThreadSafe<AudioInputDevice> {
+ public:
+ class MEDIA_EXPORT CaptureCallback {
+ public:
+ virtual void Capture(const std::vector<float*>& audio_data,
+ int number_of_frames,
+ int audio_delay_milliseconds,
+ double volume) = 0;
+ virtual void OnCaptureError() = 0;
+ protected:
+ virtual ~CaptureCallback();
+ };
+
+ class MEDIA_EXPORT CaptureEventHandler {
+ public:
+ // Notification to the client that the device with the specific |device_id|
+ // has been started.
+ // This callback is triggered as a result of StartDevice().
+ virtual void OnDeviceStarted(const std::string& device_id) = 0;
+
+ // Notification to the client that the device has been stopped.
+ virtual void OnDeviceStopped() = 0;
+
+ protected:
+ virtual ~CaptureEventHandler();
+ };
+
+ AudioInputDevice(AudioInputIPC* ipc,
+ const scoped_refptr<base::MessageLoopProxy>& io_loop);
+
+ // Initializes the AudioInputDevice. This method must be called before
+ // any other methods can be used.
+ void Initialize(const AudioParameters& params,
+ CaptureCallback* callback,
+ CaptureEventHandler* event_handler);
+
+ // Specify the |session_id| to query which device to use.
+ // Start() will use the second sequence if this method is called before.
+ void SetDevice(int session_id);
+
+ // Starts audio capturing.
+ // TODO(henrika): add support for notification when recording has started.
+ void Start();
+
+ // Stops audio capturing.
+ // TODO(henrika): add support for notification when recording has stopped.
+ void Stop();
+
+ // Sets the capture volume scaling, with range [0.0, 1.0] inclusive.
+ // Returns |true| on success.
+ void SetVolume(double volume);
+
+ // Sets the Automatic Gain Control state to on or off.
+ // This method must be called before Start(). It will not have any effect
+ // if it is called while capturing has already started.
+ void SetAutomaticGainControl(bool enabled);
+
+ protected:
+ // Methods called on IO thread ----------------------------------------------
+ // AudioInputIPCDelegate implementation.
+ virtual void OnStreamCreated(base::SharedMemoryHandle handle,
+ base::SyncSocket::Handle socket_handle,
+ int length) OVERRIDE;
+ virtual void OnVolume(double volume) OVERRIDE;
+ virtual void OnStateChanged(
+ AudioInputIPCDelegate::State state) OVERRIDE;
+ virtual void OnDeviceReady(const std::string& device_id) OVERRIDE;
+ virtual void OnIPCClosed() OVERRIDE;
+
+ friend class base::RefCountedThreadSafe<AudioInputDevice>;
+ virtual ~AudioInputDevice();
+
+ private:
+ // Methods called on IO thread ----------------------------------------------
+ // The following methods are tasks posted on the IO thread that needs to
+ // be executed on that thread. They interact with AudioInputMessageFilter and
+ // sends IPC messages on that thread.
+ void InitializeOnIOThread();
+ void SetSessionIdOnIOThread(int session_id);
+ void StartOnIOThread();
+ void ShutDownOnIOThread();
+ void SetVolumeOnIOThread(double volume);
+ void SetAutomaticGainControlOnIOThread(bool enabled);
+
+ // MessageLoop::DestructionObserver implementation for the IO loop.
+ // If the IO loop dies before we do, we shut down the audio thread from here.
+ virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
+
+ AudioParameters audio_parameters_;
+
+ CaptureCallback* callback_;
+ CaptureEventHandler* event_handler_;
+
+ AudioInputIPC* ipc_;
+
+ // Our stream ID on the message filter. Only modified on the IO thread.
+ int stream_id_;
+
+ // The media session ID used to identify which input device to be started.
+ // Only modified on the IO thread.
+ int session_id_;
+
+ // State variable used to indicate it is waiting for a OnDeviceReady()
+ // callback. Only modified on the IO thread.
+ bool pending_device_ready_;
+
+ // Stores the Automatic Gain Control state. Default is false.
+ // Only modified on the IO thread.
+ bool agc_is_enabled_;
+
+ // Our audio thread callback class. See source file for details.
+ class AudioThreadCallback;
+
+ // In order to avoid a race between OnStreamCreated and Stop(), we use this
+ // guard to control stopping and starting the audio thread.
+ base::Lock audio_thread_lock_;
+ AudioDeviceThread audio_thread_;
+ scoped_ptr<AudioInputDevice::AudioThreadCallback> audio_callback_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AudioInputDevice);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_INPUT_DEVICE_H_
diff --git a/media/audio/audio_output_device.cc b/media/audio/audio_output_device.cc
new file mode 100644
index 0000000..d432103
--- /dev/null
+++ b/media/audio/audio_output_device.cc
@@ -0,0 +1,280 @@
+// Copyright (c) 2012 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_device.h"
+
+#include "base/debug/trace_event.h"
+#include "base/message_loop.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time.h"
+#include "media/audio/audio_output_controller.h"
+#include "media/audio/audio_util.h"
+
+namespace media {
+
+// Takes care of invoking the render callback on the audio thread.
+// An instance of this class is created for each capture stream in
+// OnStreamCreated().
+class AudioOutputDevice::AudioThreadCallback
+ : public AudioDeviceThread::Callback {
+ public:
+ AudioThreadCallback(const AudioParameters& audio_parameters,
+ base::SharedMemoryHandle memory,
+ int memory_length,
+ AudioRendererSink::RenderCallback* render_callback);
+ virtual ~AudioThreadCallback();
+
+ virtual void MapSharedMemory() OVERRIDE;
+
+ // Called whenever we receive notifications about pending data.
+ virtual void Process(int pending_data) OVERRIDE;
+
+ private:
+ AudioRendererSink::RenderCallback* render_callback_;
+ DISALLOW_COPY_AND_ASSIGN(AudioThreadCallback);
+};
+
+AudioOutputDevice::AudioOutputDevice(
+ AudioOutputIPC* ipc,
+ const scoped_refptr<base::MessageLoopProxy>& io_loop)
+ : ScopedLoopObserver(io_loop),
+ callback_(NULL),
+ ipc_(ipc),
+ stream_id_(0),
+ play_on_start_(true),
+ is_started_(false) {
+ CHECK(ipc_);
+}
+
+void AudioOutputDevice::Initialize(const AudioParameters& params,
+ RenderCallback* callback) {
+ CHECK_EQ(0, stream_id_) <<
+ "AudioOutputDevice::Initialize() must be called before Start()";
+
+ CHECK(!callback_); // Calling Initialize() twice?
+
+ audio_parameters_ = params;
+ callback_ = callback;
+}
+
+AudioOutputDevice::~AudioOutputDevice() {
+ // The current design requires that the user calls Stop() before deleting
+ // this class.
+ CHECK_EQ(0, stream_id_);
+}
+
+void AudioOutputDevice::Start() {
+ DCHECK(callback_) << "Initialize hasn't been called";
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioOutputDevice::CreateStreamOnIOThread, this,
+ audio_parameters_));
+}
+
+void AudioOutputDevice::Stop() {
+ {
+ base::AutoLock auto_lock(audio_thread_lock_);
+ audio_thread_.Stop(MessageLoop::current());
+ }
+
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioOutputDevice::ShutDownOnIOThread, this));
+}
+
+void AudioOutputDevice::Play() {
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioOutputDevice::PlayOnIOThread, this));
+}
+
+void AudioOutputDevice::Pause(bool flush) {
+ message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioOutputDevice::PauseOnIOThread, this, flush));
+}
+
+bool AudioOutputDevice::SetVolume(double volume) {
+ if (volume < 0 || volume > 1.0)
+ return false;
+
+ if (!message_loop()->PostTask(FROM_HERE,
+ base::Bind(&AudioOutputDevice::SetVolumeOnIOThread, this, volume))) {
+ return false;
+ }
+
+ return true;
+}
+
+void AudioOutputDevice::CreateStreamOnIOThread(const AudioParameters& params) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ // Make sure we don't create the stream more than once.
+ DCHECK_EQ(0, stream_id_);
+ if (stream_id_)
+ return;
+
+ stream_id_ = ipc_->AddDelegate(this);
+ ipc_->CreateStream(stream_id_, params);
+}
+
+void AudioOutputDevice::PlayOnIOThread() {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ if (stream_id_ && is_started_)
+ ipc_->PlayStream(stream_id_);
+ else
+ play_on_start_ = true;
+}
+
+void AudioOutputDevice::PauseOnIOThread(bool flush) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ if (stream_id_ && is_started_) {
+ ipc_->PauseStream(stream_id_);
+ if (flush)
+ ipc_->FlushStream(stream_id_);
+ } else {
+ // Note that |flush| isn't relevant here since this is the case where
+ // the stream is first starting.
+ play_on_start_ = false;
+ }
+}
+
+void AudioOutputDevice::ShutDownOnIOThread() {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+
+ // Make sure we don't call shutdown more than once.
+ if (stream_id_) {
+ is_started_ = false;
+
+ ipc_->CloseStream(stream_id_);
+ ipc_->RemoveDelegate(stream_id_);
+ stream_id_ = 0;
+ }
+
+ // We can run into an issue where ShutDownOnIOThread is called right after
+ // OnStreamCreated is called in cases where Start/Stop are called before we
+ // get the OnStreamCreated callback. To handle that corner case, we call
+ // Stop(). In most cases, the thread will already be stopped.
+ // Another situation is when the IO thread goes away before Stop() is called
+ // in which case, we cannot use the message loop to close the thread handle
+ // and can't not rely on the main thread existing either.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ audio_thread_.Stop(NULL);
+ audio_callback_.reset();
+}
+
+void AudioOutputDevice::SetVolumeOnIOThread(double volume) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ if (stream_id_)
+ ipc_->SetVolume(stream_id_, volume);
+}
+
+void AudioOutputDevice::OnStateChanged(AudioOutputIPCDelegate::State state) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+
+ // Do nothing if the stream has been closed.
+ if (!stream_id_)
+ return;
+
+ if (state == AudioOutputIPCDelegate::kError) {
+ DLOG(WARNING) << "AudioOutputDevice::OnStateChanged(kError)";
+ // Don't dereference the callback object if the audio thread
+ // is stopped or stopping. That could mean that the callback
+ // object has been deleted.
+ // TODO(tommi): Add an explicit contract for clearing the callback
+ // object. Possibly require calling Initialize again or provide
+ // a callback object via Start() and clear it in Stop().
+ if (!audio_thread_.IsStopped())
+ callback_->OnRenderError();
+ }
+}
+
+void AudioOutputDevice::OnStreamCreated(
+ base::SharedMemoryHandle handle,
+ base::SyncSocket::Handle socket_handle,
+ int length) {
+ DCHECK(message_loop()->BelongsToCurrentThread());
+ DCHECK_GE(length, audio_parameters_.GetBytesPerBuffer());
+#if defined(OS_WIN)
+ DCHECK(handle);
+ DCHECK(socket_handle);
+#else
+ DCHECK_GE(handle.fd, 0);
+ DCHECK_GE(socket_handle, 0);
+#endif
+
+ // We should only get this callback if stream_id_ is valid. If it is not,
+ // the IPC layer should have closed the shared memory and socket handles
+ // for us and not invoked the callback. The basic assertion is that when
+ // stream_id_ is 0 the AudioOutputDevice instance is not registered as a
+ // delegate and hence it should not receive callbacks.
+ DCHECK(stream_id_);
+
+ base::AutoLock auto_lock(audio_thread_lock_);
+
+ DCHECK(audio_thread_.IsStopped());
+ audio_callback_.reset(new AudioOutputDevice::AudioThreadCallback(
+ audio_parameters_, handle, length, callback_));
+ audio_thread_.Start(audio_callback_.get(), socket_handle,
+ "AudioOutputDevice");
+
+ // We handle the case where Play() and/or Pause() may have been called
+ // multiple times before OnStreamCreated() gets called.
+ is_started_ = true;
+ if (play_on_start_)
+ PlayOnIOThread();
+}
+
+void AudioOutputDevice::OnIPCClosed() {
+ ipc_ = NULL;
+}
+
+void AudioOutputDevice::WillDestroyCurrentMessageLoop() {
+ LOG(ERROR) << "IO loop going away before the audio device has been stopped";
+ ShutDownOnIOThread();
+}
+
+// AudioOutputDevice::AudioThreadCallback
+
+AudioOutputDevice::AudioThreadCallback::AudioThreadCallback(
+ const AudioParameters& audio_parameters,
+ base::SharedMemoryHandle memory,
+ int memory_length,
+ AudioRendererSink::RenderCallback* render_callback)
+ : AudioDeviceThread::Callback(audio_parameters, memory, memory_length),
+ render_callback_(render_callback) {
+}
+
+AudioOutputDevice::AudioThreadCallback::~AudioThreadCallback() {
+}
+
+void AudioOutputDevice::AudioThreadCallback::MapSharedMemory() {
+ shared_memory_.Map(TotalSharedMemorySizeInBytes(memory_length_));
+}
+
+// Called whenever we receive notifications about pending data.
+void AudioOutputDevice::AudioThreadCallback::Process(int pending_data) {
+ if (pending_data == AudioOutputController::kPauseMark) {
+ memset(shared_memory_.memory(), 0, memory_length_);
+ SetActualDataSizeInBytes(&shared_memory_, memory_length_, 0);
+ return;
+ }
+
+ // Convert the number of pending bytes in the render buffer
+ // into milliseconds.
+ int audio_delay_milliseconds = pending_data / bytes_per_ms_;
+
+ TRACE_EVENT0("audio", "AudioOutputDevice::FireRenderCallback");
+
+ // Update the audio-delay measurement then ask client to render audio.
+ size_t num_frames = render_callback_->Render(audio_data_,
+ audio_parameters_.frames_per_buffer(), audio_delay_milliseconds);
+
+ // Interleave, scale, and clip to int.
+ // TODO(crogers/vrk): Figure out a way to avoid the float -> int -> float
+ // conversions that happen in the <audio> and WebRTC scenarios.
+ InterleaveFloatToInt(audio_data_, shared_memory_.memory(),
+ num_frames, audio_parameters_.bits_per_sample() / 8);
+
+ // Let the host know we are done.
+ SetActualDataSizeInBytes(&shared_memory_, memory_length_,
+ num_frames * audio_parameters_.GetBytesPerFrame());
+}
+
+} // namespace media.
diff --git a/media/audio/audio_output_device.h b/media/audio/audio_output_device.h
new file mode 100644
index 0000000..0e70ceb
--- /dev/null
+++ b/media/audio/audio_output_device.h
@@ -0,0 +1,161 @@
+// Copyright (c) 2012 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.
+//
+// Audio rendering unit utilizing audio output stream provided by browser
+// process through IPC.
+//
+// Relationship of classes.
+//
+// AudioOutputController AudioOutputDevice
+// ^ ^
+// | |
+// v IPC v
+// AudioRendererHost <---------> AudioOutputIPC (AudioMessageFilter)
+//
+// Transportation of audio samples from the render to the browser process
+// is done by using shared memory in combination with a sync socket pair
+// to generate a low latency transport. The AudioOutputDevice user registers an
+// AudioOutputDevice::RenderCallback at construction and will be polled by the
+// AudioOutputDevice for audio to be played out by the underlying audio layers.
+//
+// State sequences.
+//
+// Task [IO thread] IPC [IO thread]
+//
+// Start -> CreateStreamOnIOThread -----> CreateStream ------>
+// <- OnStreamCreated <- AudioMsg_NotifyStreamCreated <-
+// ---> PlayOnIOThread -----------> PlayStream -------->
+//
+// Optionally Play() / Pause() sequences may occur:
+// Play -> PlayOnIOThread --------------> PlayStream --------->
+// Pause -> PauseOnIOThread ------------> PauseStream -------->
+// (note that Play() / Pause() sequences before OnStreamCreated are
+// deferred until OnStreamCreated, with the last valid state being used)
+//
+// AudioOutputDevice::Render => audio transport on audio thread =>
+// |
+// Stop --> ShutDownOnIOThread --------> CloseStream -> Close
+//
+// This class utilizes several threads during its lifetime, namely:
+// 1. Creating thread.
+// Must be the main render thread.
+// 2. Control thread (may be the main render thread or another thread).
+// The methods: Start(), Stop(), Play(), Pause(), SetVolume()
+// must be called on the same thread.
+// 3. IO thread (internal implementation detail - not exposed to public API)
+// The thread within which this class receives all the IPC messages and
+// IPC communications can only happen in this thread.
+// 4. Audio transport thread (See AudioDeviceThread).
+// Responsible for calling the AudioThreadCallback implementation that in
+// turn calls AudioRendererSink::RenderCallback which feeds audio samples to
+// the audio layer in the browser process using sync sockets and shared
+// memory.
+//
+// Implementation notes:
+// - The user must call Stop() before deleting the class instance.
+
+#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_H_
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/shared_memory.h"
+#include "media/base/media_export.h"
+#include "media/audio/audio_device_thread.h"
+#include "media/audio/audio_output_ipc.h"
+#include "media/audio/audio_parameters.h"
+#include "media/audio/scoped_loop_observer.h"
+#include "media/base/audio_renderer_sink.h"
+
+namespace media {
+
+class MEDIA_EXPORT AudioOutputDevice
+ : NON_EXPORTED_BASE(public AudioRendererSink),
+ public AudioOutputIPCDelegate,
+ NON_EXPORTED_BASE(public ScopedLoopObserver) {
+ public:
+ // AudioRendererSink implementation.
+ virtual void Initialize(const AudioParameters& params,
+ RenderCallback* callback) OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void Play() OVERRIDE;
+ virtual void Pause(bool flush) OVERRIDE;
+ virtual bool SetVolume(double volume) OVERRIDE;
+
+ // Methods called on IO thread ----------------------------------------------
+ // AudioOutputIPCDelegate methods.
+ virtual void OnStateChanged(AudioOutputIPCDelegate::State state) OVERRIDE;
+ virtual void OnStreamCreated(base::SharedMemoryHandle handle,
+ base::SyncSocket::Handle socket_handle,
+ int length) OVERRIDE;
+ virtual void OnIPCClosed() OVERRIDE;
+
+ // Creates an uninitialized AudioOutputDevice. Clients must call Initialize()
+ // before using.
+ // TODO(tommi): When all dependencies on |content| have been removed
+ // from AudioOutputDevice, move this class over to media/audio.
+ AudioOutputDevice(AudioOutputIPC* ipc,
+ const scoped_refptr<base::MessageLoopProxy>& io_loop);
+
+ protected:
+ // Magic required by ref_counted.h to avoid any code deleting the object
+ // accidentally while there are references to it.
+ friend class base::RefCountedThreadSafe<AudioOutputDevice>;
+ virtual ~AudioOutputDevice();
+
+ private:
+ // Methods called on IO thread ----------------------------------------------
+ // The following methods are tasks posted on the IO thread that needs to
+ // be executed on that thread. They interact with AudioMessageFilter and
+ // sends IPC messages on that thread.
+ void CreateStreamOnIOThread(const AudioParameters& params);
+ void PlayOnIOThread();
+ void PauseOnIOThread(bool flush);
+ void ShutDownOnIOThread();
+ void SetVolumeOnIOThread(double volume);
+
+ // MessageLoop::DestructionObserver implementation for the IO loop.
+ // If the IO loop dies before we do, we shut down the audio thread from here.
+ virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
+
+ AudioParameters audio_parameters_;
+
+ RenderCallback* callback_;
+
+ // A pointer to the IPC layer that takes care of sending requests over to
+ // the AudioRendererHost.
+ AudioOutputIPC* ipc_;
+
+ // Our stream ID on the message filter. Only accessed on the IO thread.
+ // Must only be modified on the IO thread.
+ int stream_id_;
+
+ // State of Play() / Pause() calls before OnStreamCreated() is called.
+ bool play_on_start_;
+
+ // Set to |true| when OnStreamCreated() is called.
+ // Set to |false| when ShutDownOnIOThread() is called.
+ // This is for use with play_on_start_ to track Play() / Pause() state.
+ // Must only be touched from the IO thread.
+ bool is_started_;
+
+ // Our audio thread callback class. See source file for details.
+ class AudioThreadCallback;
+
+ // In order to avoid a race between OnStreamCreated and Stop(), we use this
+ // guard to control stopping and starting the audio thread.
+ base::Lock audio_thread_lock_;
+ AudioDeviceThread audio_thread_;
+ scoped_ptr<AudioOutputDevice::AudioThreadCallback> audio_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioOutputDevice);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_H_
+
diff --git a/media/audio/audio_output_device_unittest.cc b/media/audio/audio_output_device_unittest.cc
new file mode 100644
index 0000000..8133f34
--- /dev/null
+++ b/media/audio/audio_output_device_unittest.cc
@@ -0,0 +1,257 @@
+// Copyright (c) 2012 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 <vector>
+
+#include "base/at_exit.h"
+#include "base/message_loop.h"
+#include "base/process_util.h"
+#include "base/shared_memory.h"
+#include "base/sync_socket.h"
+#include "base/test/test_timeouts.h"
+#include "media/audio/audio_output_device.h"
+#include "media/audio/audio_util.h"
+#include "media/audio/sample_rates.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gmock_mutant.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::CancelableSyncSocket;
+using base::SharedMemory;
+using base::SyncSocket;
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+using testing::WithArgs;
+
+namespace media {
+
+namespace {
+
+class MockRenderCallback : public AudioRendererSink::RenderCallback {
+ public:
+ MockRenderCallback() {}
+ virtual ~MockRenderCallback() {}
+
+ MOCK_METHOD3(Render, int(const std::vector<float*>& audio_data,
+ int number_of_frames,
+ int audio_delay_milliseconds));
+ MOCK_METHOD0(OnRenderError, void());
+};
+
+class MockAudioOutputIPC : public AudioOutputIPC {
+ public:
+ MockAudioOutputIPC() {}
+ virtual ~MockAudioOutputIPC() {}
+
+ MOCK_METHOD1(AddDelegate, int(AudioOutputIPCDelegate* delegate));
+ MOCK_METHOD1(RemoveDelegate, void(int stream_id));
+
+ MOCK_METHOD2(CreateStream,
+ void(int stream_id, const AudioParameters& params));
+ MOCK_METHOD1(PlayStream, void(int stream_id));
+ MOCK_METHOD1(CloseStream, void(int stream_id));
+ MOCK_METHOD2(SetVolume, void(int stream_id, double volume));
+ MOCK_METHOD1(PauseStream, void(int stream_id));
+ MOCK_METHOD1(FlushStream, void(int stream_id));
+};
+
+// Creates a copy of a SyncSocket handle that we can give to AudioOutputDevice.
+// On Windows this means duplicating the pipe handle so that AudioOutputDevice
+// can call CloseHandle() (since ownership has been transferred), but on other
+// platforms, we just copy the same socket handle since AudioOutputDevice on
+// those platforms won't actually own the socket (FileDescriptor.auto_close is
+// false).
+bool DuplicateSocketHandle(SyncSocket::Handle socket_handle,
+ SyncSocket::Handle* copy) {
+#if defined(OS_WIN)
+ HANDLE process = GetCurrentProcess();
+ ::DuplicateHandle(process, socket_handle, process, copy,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ return *copy != NULL;
+#else
+ *copy = socket_handle;
+ return *copy != -1;
+#endif
+}
+
+ACTION_P2(SendPendingBytes, socket, pending_bytes) {
+ socket->Send(&pending_bytes, sizeof(pending_bytes));
+}
+
+// Used to terminate a loop from a different thread than the loop belongs to.
+// |loop| should be a MessageLoopProxy.
+ACTION_P(QuitLoop, loop) {
+ loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+}
+
+// Zeros out |number_of_frames| in all channel buffers pointed to by
+// the |audio_data| vector.
+void ZeroAudioData(int number_of_frames,
+ const std::vector<float*>& audio_data) {
+ std::vector<float*>::const_iterator it = audio_data.begin();
+ for (; it != audio_data.end(); ++it) {
+ float* channel = *it;
+ for (int j = 0; j < number_of_frames; ++j) {
+ channel[j] = 0.0f;
+ }
+ }
+}
+
+} // namespace.
+
+class AudioOutputDeviceTest : public testing::Test {
+ public:
+ AudioOutputDeviceTest()
+ : default_audio_parameters_(AudioParameters::AUDIO_PCM_LINEAR,
+ CHANNEL_LAYOUT_STEREO,
+ 48000, 16, 1024),
+ stream_id_(-1) {
+ }
+
+ ~AudioOutputDeviceTest() {}
+
+ AudioOutputDevice* CreateAudioDevice() {
+ return new AudioOutputDevice(
+ &audio_output_ipc_, io_loop_.message_loop_proxy());
+ }
+
+ void set_stream_id(int stream_id) { stream_id_ = stream_id; }
+
+ protected:
+ // Used to clean up TLS pointers that the test(s) will initialize.
+ // Must remain the first member of this class.
+ base::ShadowingAtExitManager at_exit_manager_;
+ MessageLoopForIO io_loop_;
+ const AudioParameters default_audio_parameters_;
+ MockRenderCallback callback_;
+ MockAudioOutputIPC audio_output_ipc_;
+ int stream_id_;
+};
+
+// The simplest test for AudioOutputDevice. Used to test construction of
+// AudioOutputDevice and that the runtime environment is set up correctly.
+TEST_F(AudioOutputDeviceTest, Initialize) {
+ scoped_refptr<AudioOutputDevice> audio_device(CreateAudioDevice());
+ audio_device->Initialize(default_audio_parameters_, &callback_);
+ io_loop_.RunAllPending();
+}
+
+// Calls Start() followed by an immediate Stop() and check for the basic message
+// filter messages being sent in that case.
+TEST_F(AudioOutputDeviceTest, StartStop) {
+ scoped_refptr<AudioOutputDevice> audio_device(CreateAudioDevice());
+ audio_device->Initialize(default_audio_parameters_, &callback_);
+
+ EXPECT_CALL(audio_output_ipc_, AddDelegate(audio_device.get()))
+ .WillOnce(Return(1));
+ EXPECT_CALL(audio_output_ipc_, RemoveDelegate(1)).WillOnce(Return());
+
+ audio_device->Start();
+ audio_device->Stop();
+
+ EXPECT_CALL(audio_output_ipc_, CreateStream(_, _));
+ EXPECT_CALL(audio_output_ipc_, CloseStream(_));
+
+ io_loop_.RunAllPending();
+}
+
+// Starts an audio stream, creates a shared memory section + SyncSocket pair
+// that AudioOutputDevice must use for audio data. It then sends a request for
+// a single audio packet and quits when the packet has been sent.
+TEST_F(AudioOutputDeviceTest, CreateStream) {
+ scoped_refptr<AudioOutputDevice> audio_device(CreateAudioDevice());
+ audio_device->Initialize(default_audio_parameters_, &callback_);
+
+ EXPECT_CALL(audio_output_ipc_, AddDelegate(audio_device.get()))
+ .WillOnce(Return(1));
+ EXPECT_CALL(audio_output_ipc_, RemoveDelegate(1)).WillOnce(Return());
+
+ audio_device->Start();
+
+ EXPECT_CALL(audio_output_ipc_, CreateStream(_, _))
+ .WillOnce(WithArgs<0>(
+ Invoke(this, &AudioOutputDeviceTest::set_stream_id)));
+
+
+ EXPECT_EQ(stream_id_, -1);
+ io_loop_.RunAllPending();
+
+ // OnCreateStream() must have been called and we should have a valid
+ // stream id.
+ ASSERT_NE(stream_id_, -1);
+
+ // This is where it gets a bit hacky. The shared memory contract between
+ // AudioOutputDevice and its browser side counter part includes a bit more
+ // than just the audio data, so we must call TotalSharedMemorySizeInBytes()
+ // to get the actual size needed to fit the audio data plus the extra data.
+ int memory_size = TotalSharedMemorySizeInBytes(
+ default_audio_parameters_.GetBytesPerBuffer());
+ SharedMemory shared_memory;
+ ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(memory_size));
+ memset(shared_memory.memory(), 0xff, memory_size);
+
+ CancelableSyncSocket browser_socket, renderer_socket;
+ ASSERT_TRUE(CancelableSyncSocket::CreatePair(&browser_socket,
+ &renderer_socket));
+
+ // Create duplicates of the handles we pass to AudioOutputDevice since
+ // ownership will be transferred and AudioOutputDevice is responsible for
+ // freeing.
+ SyncSocket::Handle audio_device_socket = SyncSocket::kInvalidHandle;
+ ASSERT_TRUE(DuplicateSocketHandle(renderer_socket.handle(),
+ &audio_device_socket));
+ base::SharedMemoryHandle duplicated_memory_handle;
+ ASSERT_TRUE(shared_memory.ShareToProcess(base::GetCurrentProcessHandle(),
+ &duplicated_memory_handle));
+
+ // We should get a 'play' notification when we call OnStreamCreated().
+ // Respond by asking for some audio data. This should ask our callback
+ // to provide some audio data that AudioOutputDevice then writes into the
+ // shared memory section.
+ EXPECT_CALL(audio_output_ipc_, PlayStream(stream_id_))
+ .WillOnce(SendPendingBytes(&browser_socket, memory_size));
+
+ // We expect calls to our audio renderer callback, which returns the number
+ // of frames written to the memory section.
+ // Here's the second place where it gets hacky: There's no way for us to
+ // know (without using a sleep loop!) when the AudioOutputDevice has finished
+ // writing the interleaved audio data into the shared memory section.
+ // So, for the sake of this test, we consider the call to Render a sign
+ // of success and quit the loop.
+
+ // A note on the call to ZeroAudioData():
+ // Valgrind caught a bug in AudioOutputDevice::AudioThreadCallback::Process()
+ // whereby we always interleaved all the frames in the buffer regardless
+ // of how many were actually rendered. So to keep the benefits of that
+ // test, we explicitly pass 0 in here as the number of frames to
+ // ZeroAudioData(). Other tests might want to pass the requested number
+ // by using WithArgs<1, 0>(Invoke(&ZeroAudioData)) and set the return
+ // value accordingly.
+ const int kNumberOfFramesToProcess = 0;
+
+ EXPECT_CALL(callback_, Render(_, _, _))
+ .WillOnce(DoAll(
+ WithArgs<0>(Invoke(
+ testing::CreateFunctor(&ZeroAudioData,
+ kNumberOfFramesToProcess))),
+ QuitLoop(io_loop_.message_loop_proxy()),
+ Return(kNumberOfFramesToProcess)));
+
+ audio_device->OnStreamCreated(duplicated_memory_handle, audio_device_socket,
+ memory_size);
+
+ io_loop_.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
+ TestTimeouts::action_timeout());
+ io_loop_.Run();
+
+ // Close the stream sequence.
+ EXPECT_CALL(audio_output_ipc_, CloseStream(stream_id_));
+
+ audio_device->Stop();
+ io_loop_.RunAllPending();
+}
+
+} // namespace media.
diff --git a/media/audio/audio_output_ipc.h b/media/audio/audio_output_ipc.h
index 6983761..7f9df3b 100644
--- a/media/audio/audio_output_ipc.h
+++ b/media/audio/audio_output_ipc.h
@@ -14,7 +14,7 @@ namespace media {
// Contains IPC notifications for the state of the server side
// (AudioOutputController) audio state changes and when an AudioOutputController
-// has been created. Implemented by AudioDevice.
+// has been created. Implemented by AudioOutputDevice.
class MEDIA_EXPORT AudioOutputIPCDelegate {
public:
// Current status of the audio output stream in the browser process. Browser
@@ -51,9 +51,9 @@ class MEDIA_EXPORT AudioOutputIPCDelegate {
virtual ~AudioOutputIPCDelegate();
};
-// Provides IPC functionality for an AudioDevice. The implementation should
-// asynchronously deliver the messages to an AudioOutputController object (or
-// create one in the case of CreateStream()), that may live in a separate
+// Provides IPC functionality for an AudioOutputDevice. The implementation
+// should asynchronously deliver the messages to an AudioOutputController object
+// (or create one in the case of CreateStream()), that may live in a separate
// process.
class MEDIA_EXPORT AudioOutputIPC {
public:
diff --git a/media/audio/mac/audio_manager_mac.cc b/media/audio/mac/audio_manager_mac.cc
index 2bd8501..863a976 100644
--- a/media/audio/mac/audio_manager_mac.cc
+++ b/media/audio/mac/audio_manager_mac.cc
@@ -4,6 +4,8 @@
#include <CoreAudio/AudioHardware.h>
+#include <string>
+
#include "base/mac/mac_logging.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
@@ -282,7 +284,7 @@ AudioInputStream* AudioManagerMac::MakeLinearInputStream(
AudioInputStream* AudioManagerMac::MakeLowLatencyInputStream(
const AudioParameters& params, const std::string& device_id) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
- // Gets the AudioDeviceID that refers to the AudioDevice with the device
+ // Gets the AudioDeviceID that refers to the AudioOutputDevice with the device
// unique id. This AudioDeviceID is used to set the device for Audio Unit.
AudioDeviceID audio_device_id = GetAudioDeviceIdByUId(true, device_id);
AudioInputStream* stream = NULL;
diff --git a/media/audio/scoped_loop_observer.cc b/media/audio/scoped_loop_observer.cc
new file mode 100644
index 0000000..1332b07
--- /dev/null
+++ b/media/audio/scoped_loop_observer.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 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/scoped_loop_observer.h"
+
+#include "base/bind.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace media {
+
+ScopedLoopObserver::ScopedLoopObserver(
+ const scoped_refptr<base::MessageLoopProxy>& loop)
+ : loop_(loop) {
+ ObserveLoopDestruction(true, NULL);
+}
+
+ScopedLoopObserver::~ScopedLoopObserver() {
+ ObserveLoopDestruction(false, NULL);
+}
+
+void ScopedLoopObserver::ObserveLoopDestruction(bool enable,
+ base::WaitableEvent* done) {
+ // Note: |done| may be NULL.
+ if (loop_->BelongsToCurrentThread()) {
+ MessageLoop* loop = MessageLoop::current();
+ if (enable) {
+ loop->AddDestructionObserver(this);
+ } else {
+ loop->RemoveDestructionObserver(this);
+ }
+ } else {
+ base::WaitableEvent event(false, false);
+ if (loop_->PostTask(FROM_HERE,
+ base::Bind(&ScopedLoopObserver::ObserveLoopDestruction,
+ base::Unretained(this), enable, &event))) {
+ event.Wait();
+ } else {
+ // The message loop's thread has already terminated, so no need to wait.
+ }
+ }
+
+ if (done)
+ done->Signal();
+}
+
+} // namespace media.
diff --git a/media/audio/scoped_loop_observer.h b/media/audio/scoped_loop_observer.h
new file mode 100644
index 0000000..659c68b
--- /dev/null
+++ b/media/audio/scoped_loop_observer.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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_SCOPED_LOOP_OBSERVER_H_
+#define MEDIA_AUDIO_SCOPED_LOOP_OBSERVER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+
+namespace base {
+class WaitableEvent;
+}
+
+namespace media {
+
+// A common base class for AudioOutputDevice and AudioInputDevice that manages
+// a message loop pointer and monitors it for destruction. If the object goes
+// out of scope before the message loop, the object will automatically remove
+// itself from the message loop's list of destruction observers.
+// NOTE: The class that inherits from this class must implement the
+// WillDestroyCurrentMessageLoop virtual method from DestructionObserver.
+class ScopedLoopObserver
+ : public MessageLoop::DestructionObserver {
+ public:
+ explicit ScopedLoopObserver(
+ const scoped_refptr<base::MessageLoopProxy>& message_loop);
+
+ protected:
+ virtual ~ScopedLoopObserver();
+
+ // Accessor to the loop that's used by the derived class.
+ const scoped_refptr<base::MessageLoopProxy>& message_loop() { return loop_; }
+
+ private:
+ // Call to add or remove ourselves from the list of destruction observers for
+ // the message loop.
+ void ObserveLoopDestruction(bool enable, base::WaitableEvent* done);
+
+ // A pointer to the message loop's proxy. In case the loop gets destroyed
+ // before this object goes out of scope, PostTask etc will fail but not crash.
+ scoped_refptr<base::MessageLoopProxy> loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedLoopObserver);
+};
+
+} // namespace media.
+
+#endif // MEDIA_AUDIO_SCOPED_LOOP_OBSERVER_H_