| // 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/base/audio_bus.h" |
| |
| namespace media { |
| |
| // 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_; |
| scoped_ptr<AudioBus> audio_bus_; |
| 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); |
| #elif defined(__LB_SHELL__) || defined(COBALT) |
| DCHECK(handle.get()); |
| #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_) { |
| if (ipc_) { |
| 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, 0, memory, memory_length), |
| capture_callback_(capture_callback) { |
| audio_bus_ = AudioBus::Create(audio_parameters_); |
| } |
| |
| 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 int bytes_per_sample = sizeof(memory[0]); |
| |
| // Deinterleave each channel and convert to 32-bit floating-point |
| // with nominal range -1.0 -> +1.0. |
| audio_bus_->FromInterleaved(memory, audio_bus_->frames(), bytes_per_sample); |
| |
| // Deliver captured data to the client in floating point format |
| // and update the audio-delay measurement. |
| capture_callback_->Capture(audio_bus_.get(), |
| audio_delay_milliseconds, volume); |
| } |
| |
| } // namespace media |