diff options
author | tommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-27 18:30:13 +0000 |
---|---|---|
committer | tommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-27 18:30:13 +0000 |
commit | 9840d2cf96a2d9873365f4a89b3fd3542730a1d4 (patch) | |
tree | 53393b971f49e8a40233c120945723cece9450c4 /media/audio | |
parent | 0392ff47c388699e16f9b7260f72fe491432db90 (diff) | |
download | chromium_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.cc | 208 | ||||
-rw-r--r-- | media/audio/audio_device_thread.h | 111 | ||||
-rw-r--r-- | media/audio/audio_input_device.cc | 350 | ||||
-rw-r--r-- | media/audio/audio_input_device.h | 211 | ||||
-rw-r--r-- | media/audio/audio_output_device.cc | 280 | ||||
-rw-r--r-- | media/audio/audio_output_device.h | 161 | ||||
-rw-r--r-- | media/audio/audio_output_device_unittest.cc | 257 | ||||
-rw-r--r-- | media/audio/audio_output_ipc.h | 8 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.cc | 4 | ||||
-rw-r--r-- | media/audio/scoped_loop_observer.cc | 47 | ||||
-rw-r--r-- | media/audio/scoped_loop_observer.h | 50 |
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_ |