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/audio_input_device.cc | |
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/audio_input_device.cc')
-rw-r--r-- | media/audio/audio_input_device.cc | 350 |
1 files changed, 350 insertions, 0 deletions
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 |