diff options
author | xians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-20 17:28:44 +0000 |
---|---|---|
committer | xians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-20 17:28:44 +0000 |
commit | 855cb828e518efd40ac97d08107ad58d996698be (patch) | |
tree | 1a28a6429c873ac637362ebcc8000011095c5779 /content | |
parent | a42a7ee454165d73e161448383cdf3dc3b1f164a (diff) | |
download | chromium_src-855cb828e518efd40ac97d08107ad58d996698be.zip chromium_src-855cb828e518efd40ac97d08107ad58d996698be.tar.gz chromium_src-855cb828e518efd40ac97d08107ad58d996698be.tar.bz2 |
Hooking up AudioInputDeviceManager to AudioInputRendererHost and MediaStreamManager.
Review URL: http://codereview.chromium.org/7462012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@101975 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
14 files changed, 461 insertions, 70 deletions
diff --git a/content/browser/renderer_host/media/audio_input_device_manager.cc b/content/browser/renderer_host/media/audio_input_device_manager.cc index 8fafb01..e1970c1 100644 --- a/content/browser/renderer_host/media/audio_input_device_manager.cc +++ b/content/browser/renderer_host/media/audio_input_device_manager.cc @@ -11,8 +11,8 @@ namespace media_stream { -const int AudioInputDeviceManager::kFakeOpenSessionId = 0; -const int AudioInputDeviceManager::kInvalidSessionId = -1; +const int AudioInputDeviceManager::kFakeOpenSessionId = 1; +const int AudioInputDeviceManager::kInvalidSessionId = 0; const int AudioInputDeviceManager::kInvalidDevice = -1; const int AudioInputDeviceManager::kDefaultDeviceIndex = 0; @@ -111,14 +111,14 @@ void AudioInputDeviceManager::Start( // And we do not store the info for the kFakeOpenSessionId but return // the callback immediately. if (session_id == kFakeOpenSessionId) { - event_handler->OnStartDevice(session_id, kDefaultDeviceIndex); + event_handler->OnDeviceStarted(session_id, kDefaultDeviceIndex); return; } // If session has been started, post a callback with an error. if (event_handlers_.find(session_id) != event_handlers_.end()) { // Session has been started, post a callback with error. - event_handler->OnStartDevice(session_id, kInvalidDevice); + event_handler->OnDeviceStarted(session_id, kInvalidDevice); return; } @@ -265,7 +265,7 @@ void AudioInputDeviceManager::ClosedOnIOThread(int session_id) { EventHandlerMap::iterator it = event_handlers_.find(session_id); if (it != event_handlers_.end()) { // The device hasn't been stopped, send stop signal. - it->second->OnStopDevice(session_id); + it->second->OnDeviceStopped(session_id); event_handlers_.erase(session_id); } listener_->Closed(kAudioCapture, session_id); @@ -286,8 +286,8 @@ void AudioInputDeviceManager::StartedOnIOThread(int session_id, int index) { if (it == event_handlers_.end()) return; - // Post a callback through the event handler to start the device. - it->second->OnStartDevice(session_id, index); + // Post a callback through the event handler to create an audio stream. + it->second->OnDeviceStarted(session_id, index); } void AudioInputDeviceManager::StoppedOnIOThread(int session_id) { diff --git a/content/browser/renderer_host/media/audio_input_device_manager_event_handler.h b/content/browser/renderer_host/media/audio_input_device_manager_event_handler.h index 75cb984..3790e4e 100644 --- a/content/browser/renderer_host/media/audio_input_device_manager_event_handler.h +++ b/content/browser/renderer_host/media/audio_input_device_manager_event_handler.h @@ -12,12 +12,14 @@ namespace media_stream { class AudioInputDeviceManagerEventHandler { public: - // Used to start the device referenced by session id and index. - virtual void OnStartDevice(int session_id, int index) = 0; - - // Used to stop the device referenced by session id. This method is used - // only when users call Close() without calling Stop() on a started device. - virtual void OnStopDevice(int session_id) = 0; + // Called by AudioInputDeviceManager to create an audio stream using the + // device index when the device has been started. + virtual void OnDeviceStarted(int session_id, int index) = 0; + + // Called by AudioInputDeviceManager to stop the audio stream when a device + // has been stopped. This method is used only when users call Close() without + // calling Stop() on a started device. + virtual void OnDeviceStopped(int session_id) = 0; virtual ~AudioInputDeviceManagerEventHandler() {} }; diff --git a/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc b/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc index de27d5d..f7cc6ff 100644 --- a/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc +++ b/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc @@ -52,8 +52,8 @@ class MockAudioInputDeviceManagerEventHandler MockAudioInputDeviceManagerEventHandler() {} virtual ~MockAudioInputDeviceManagerEventHandler() {} - MOCK_METHOD2(OnStartDevice, void(int, int)); - MOCK_METHOD1(OnStopDevice, void(int)); + MOCK_METHOD2(OnDeviceStarted, void(int, int)); + MOCK_METHOD1(OnDeviceStopped, void(int)); private: DISALLOW_COPY_AND_ASSIGN(MockAudioInputDeviceManagerEventHandler); @@ -294,7 +294,7 @@ TEST_F(AudioInputDeviceManagerTest, StartAndStopDevice) { session_id[index])) .Times(1); EXPECT_CALL(*audio_input_event_handler, - OnStartDevice(session_id[index], index)) + OnDeviceStarted(session_id[index], index)) .Times(1); EXPECT_CALL(*audio_input_listener_, Closed(kAudioCapture, session_id[index])) @@ -332,12 +332,12 @@ TEST_F(AudioInputDeviceManagerTest, CloseWithoutStopDevice) { session_id[index])) .Times(1); EXPECT_CALL(*audio_input_event_handler, - OnStartDevice(session_id[index], index)) + OnDeviceStarted(session_id[index], index)) .Times(1); // Event Handler should get a stop device notification as no stop is called // before closing the device. EXPECT_CALL(*audio_input_event_handler, - OnStopDevice(session_id[index])) + OnDeviceStopped(session_id[index])) .Times(1); EXPECT_CALL(*audio_input_listener_, Closed(kAudioCapture, session_id[index])) @@ -381,10 +381,10 @@ TEST_F(AudioInputDeviceManagerTest, StartDeviceTwice) { EXPECT_CALL(*audio_input_listener_, Opened(kAudioCapture, second_session_id)) .Times(1); EXPECT_CALL(*first_audio_input_event_handler, - OnStartDevice(first_session_id, 0)) + OnDeviceStarted(first_session_id, 0)) .Times(1); EXPECT_CALL(*second_audio_input_event_handler, - OnStartDevice(second_session_id, 0)) + OnDeviceStarted(second_session_id, 0)) .Times(1); EXPECT_CALL(*audio_input_listener_, Closed(kAudioCapture, first_session_id)) .Times(1); diff --git a/content/browser/renderer_host/media/audio_input_renderer_host.cc b/content/browser/renderer_host/media/audio_input_renderer_host.cc index f8b92db..84532ba 100644 --- a/content/browser/renderer_host/media/audio_input_renderer_host.cc +++ b/content/browser/renderer_host/media/audio_input_renderer_host.cc @@ -8,7 +8,9 @@ #include "base/process.h" #include "base/shared_memory.h" #include "content/browser/renderer_host/media/audio_common.h" +#include "content/browser/renderer_host/media/audio_input_device_manager.h" #include "content/browser/renderer_host/media/audio_input_sync_writer.h" +#include "content/browser/renderer_host/media/media_stream_manager.h" #include "content/common/media/audio_messages.h" #include "ipc/ipc_logging.h" @@ -167,6 +169,7 @@ bool AudioInputRendererHost::OnMessageReceived(const IPC::Message& message, bool* message_was_ok) { bool handled = true; IPC_BEGIN_MESSAGE_MAP_EX(AudioInputRendererHost, message, *message_was_ok) + IPC_MESSAGE_HANDLER(AudioInputHostMsg_StartDevice, OnStartDevice) IPC_MESSAGE_HANDLER(AudioInputHostMsg_CreateStream, OnCreateStream) IPC_MESSAGE_HANDLER(AudioInputHostMsg_RecordStream, OnRecordStream) IPC_MESSAGE_HANDLER(AudioInputHostMsg_CloseStream, OnCloseStream) @@ -177,6 +180,25 @@ bool AudioInputRendererHost::OnMessageReceived(const IPC::Message& message, return handled; } +void AudioInputRendererHost::OnStartDevice(int stream_id, int session_id) { + VLOG(1) << "AudioInputRendererHost::OnStartDevice(stream_id=" + << stream_id << ", session_id = " << session_id << ")"; + + // Get access to the AudioInputDeviceManager to start the device. + // TODO(mflodman): Get AudioInputDeviceManager from MediaStreamManager. + media_stream::AudioInputDeviceManager* audio_input_man = NULL; + if (!audio_input_man) { + SendErrorMessage(stream_id); + return; + } + + // Add the session entry to the map. + session_entries_[session_id] = stream_id; + + // Start the device with the session_id. If the device is started + // successfully, OnDeviceStarted() callback will be triggered. + audio_input_man->Start(session_id, this); +} void AudioInputRendererHost::OnCreateStream( int stream_id, const AudioParameters& params, bool low_latency) { @@ -257,6 +279,11 @@ void AudioInputRendererHost::OnCloseStream(int stream_id) { if (entry) CloseAndDeleteStream(entry); + + int session_id = LookupSessionById(stream_id); + + if (session_id) + StopAndDeleteDevice(session_id); } void AudioInputRendererHost::OnSetVolume(int stream_id, double volume) { @@ -300,6 +327,54 @@ void AudioInputRendererHost::DeleteEntries() { } } +void AudioInputRendererHost::OnDeviceStarted(int session_id, int index) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + SessionEntryMap::iterator it = session_entries_.find(session_id); + if (it == session_entries_.end()) { + DLOG(WARNING) << "AudioInputRendererHost::OnDeviceStarted()" + " session does not exist."; + return; + } + + // Notify the renderer that the device has been started. + Send(new AudioInputMsg_NotifyDeviceStarted(it->second, index)); +} + +void AudioInputRendererHost::OnDeviceStopped(int session_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + SessionEntryMap::iterator it = session_entries_.find(session_id); + // Return if the stream has been closed. + if (it == session_entries_.end()) + return; + + int stream_id = it->second; + AudioEntry* entry = LookupById(stream_id); + + if (entry) { + // Device has been stopped, close the input stream. + CloseAndDeleteStream(entry); + // Notify the renderer that the state of the input stream has changed. + Send(new AudioInputMsg_NotifyStreamStateChanged(stream_id, + kAudioStreamPaused)); + } + + // Delete the session entry. + session_entries_.erase(it); +} + +void AudioInputRendererHost::StopAndDeleteDevice(int session_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // TODO(mflodman): Get AudioInputDeviceManager from MediaStreamManager. + media_stream::AudioInputDeviceManager* audio_input_man = NULL; + if (audio_input_man) + audio_input_man->Stop(session_id); + + // Delete the session entry. + session_entries_.erase(session_id); +} + void AudioInputRendererHost::CloseAndDeleteStream(AudioEntry* entry) { if (!entry->pending_close) { entry->pending_close = true; @@ -360,3 +435,15 @@ AudioInputRendererHost::AudioEntry* AudioInputRendererHost::LookupByController( } return NULL; } + +int AudioInputRendererHost::LookupSessionById(int stream_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + for (SessionEntryMap::iterator it = session_entries_.begin(); + it != session_entries_.end(); ++it) { + if (stream_id == it->second) { + return it->first; + } + } + return 0; +} diff --git a/content/browser/renderer_host/media/audio_input_renderer_host.h b/content/browser/renderer_host/media/audio_input_renderer_host.h index 5fde275..94ef85e 100644 --- a/content/browser/renderer_host/media/audio_input_renderer_host.h +++ b/content/browser/renderer_host/media/audio_input_renderer_host.h @@ -5,6 +5,42 @@ // AudioInputRendererHost serves audio related requests from audio capturer // which lives inside the render process and provide access to audio hardware. // +// OnCreateStream() request is only available in the low latency mode. It will +// creates a shared memory, a SyncWriter and a AudioInputController for the +// input stream. + +// OnCloseStream() will close the input stream. +// +// Create stream sequence: +// +// OnCreateStream -> AudioInputController::CreateLowLatency() -> +// DoCompleteCreation -> AudioInputMsg_NotifyLowLatencyStreamCreated +// +// Close stream sequence: +// OnCloseStream -> AudioInputController::Close +// +// For the OnStartDevice() request, AudioInputRendererHost starts the device +// referenced by the session id, and a OnDeviceStarted() callback with the +// index of the opened device will be received later. Then it will send a IPC +// message to notify the renderer that the device is ready, so that renderer +// can continue with the OnCreateStream() request. +// +// OnDeviceStopped() is called when the user closes the device through +// AudioInputDeviceManager without calling Stop() before. What +// AudioInputRenderHost::OnDeviceStopped() does is to send a IPC mesaage to +// notify the renderer in order to stop the stream. +// +// Start device sequence: +// +// OnStartDevice -> AudioInputDeviceManager::Start -> +// AudioInputDeviceManagerEventHandler::OnDeviceStarted -> +// AudioInputMsg_NotifyDeviceStarted +// +// Shutdown device sequence: +// +// OnDeviceStopped -> CloseAndDeleteStream +// AudioInputMsg_NotifyStreamStateChanged +// // This class is owned by BrowserRenderProcessHost and instantiated on UI // thread. All other operations and method calls happen on IO thread, so we // need to be extra careful about the lifetime of this object. AudioManager is a @@ -28,6 +64,7 @@ #include "base/shared_memory.h" #include "content/browser/browser_message_filter.h" #include "content/browser/browser_thread.h" +#include "content/browser/renderer_host/media/audio_input_device_manager_event_handler.h" #include "media/audio/audio_input_controller.h" #include "media/audio/audio_io.h" #include "media/audio/simple_sources.h" @@ -37,7 +74,8 @@ struct AudioParameters; class AudioInputRendererHost : public BrowserMessageFilter, - public media::AudioInputController::EventHandler { + public media::AudioInputController::EventHandler, + public media_stream::AudioInputDeviceManagerEventHandler { public: struct AudioEntry { AudioEntry(); @@ -60,8 +98,6 @@ class AudioInputRendererHost bool pending_close; }; - typedef std::map<int, AudioEntry*> AudioEntryMap; - // Called from UI thread from the owner of this object. AudioInputRendererHost(); @@ -80,6 +116,10 @@ class AudioInputRendererHost const uint8* data, uint32 size); + // media_stream::AudioInputDeviceManagerEventHandler implementation. + virtual void OnDeviceStarted(int session_id, int index); + virtual void OnDeviceStopped(int session_id); + private: // TODO(henrika): extend test suite (compare AudioRenderHost) friend class BrowserThread; @@ -89,6 +129,10 @@ class AudioInputRendererHost // Methods called on IO thread ---------------------------------------------- + // Start the audio input device with the session id. If the device + // starts successfully, it will trigger OnDeviceStarted() callback. + void OnStartDevice(int stream_id, int session_id); + // Audio related IPC message handlers. // Creates an audio input stream with the specified format. If this call is // successful this object would keep an internal entry of the stream for the @@ -139,6 +183,9 @@ class AudioInputRendererHost // Delete audio entry and close the related audio input stream. void DeleteEntryOnError(AudioEntry* entry); + // Stop the device and delete its audio session entry. + void StopAndDeleteDevice(int stream_id); + // A helper method to look up a AudioEntry identified by |stream_id|. // Returns NULL if not found. AudioEntry* LookupById(int stream_id); @@ -148,9 +195,18 @@ class AudioInputRendererHost // event is received. AudioEntry* LookupByController(media::AudioInputController* controller); + // A helper method to look up a session identified by |stream_id|. + // Returns 0 if not found. + int LookupSessionById(int stream_id); + // A map of stream IDs to audio sources. + typedef std::map<int, AudioEntry*> AudioEntryMap; AudioEntryMap audio_entries_; + // A map of session IDs to audio session sources. + typedef std::map<int, int> SessionEntryMap; + SessionEntryMap session_entries_; + DISALLOW_COPY_AND_ASSIGN(AudioInputRendererHost); }; diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc index 786cb26..3348a1b9 100644 --- a/content/browser/renderer_host/media/media_stream_manager.cc +++ b/content/browser/renderer_host/media/media_stream_manager.cc @@ -17,7 +17,7 @@ namespace media_stream { -// TODO(mflodman) Find out who should own MediaStreamManager. +// TODO(mflodman): Find out who should own MediaStreamManager. base::LazyInstance<MediaStreamManager> g_media_stream_manager( base::LINKER_INITIALIZED); @@ -63,6 +63,12 @@ VideoCaptureManager* MediaStreamManager::video_capture_manager() { return video_capture_manager_; } +AudioInputDeviceManager* MediaStreamManager::audio_input_device_manager() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + // TODO(mflodman): Add when audio input manager is available. + return NULL; +} + void MediaStreamManager::GenerateStream(MediaStreamRequester* requester, int render_process_id, int render_view_id, @@ -71,7 +77,7 @@ void MediaStreamManager::GenerateStream(MediaStreamRequester* requester, std::string* label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - // TODO(mflodman) Remove next line when audio is supported. + // TODO(mflodman): Remove next line when audio is supported. (const_cast<StreamOptions&>(options)).audio = false; // Create a new request based on options. @@ -111,7 +117,7 @@ void MediaStreamManager::CancelRequests(MediaStreamRequester* requester) { request->audio_devices.begin(); it != request->audio_devices.end(); ++it) { if (it->in_use == true) { - // TODO(mflodman) Add when audio input device manager is available. + // TODO(mflodman): Add when audio input device manager is available. } } } @@ -139,7 +145,7 @@ void MediaStreamManager::StopGeneratedStream(const std::string& label) { for (StreamDeviceInfoArray::iterator audio_it = it->second.audio_devices.begin(); audio_it != it->second.audio_devices.end(); ++audio_it) { - // TODO(mflodman) Add code when audio input manager exists. + // TODO(mflodman): Add code when audio input manager exists. NOTREACHED(); } for (StreamDeviceInfoArray::iterator video_it = @@ -356,7 +362,7 @@ void MediaStreamManager::UseFakeDevice() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); video_capture_manager_->UseFakeDevice(); device_settings_->UseFakeUI(); - // TODO(mflodman) Add audio manager when available. + // TODO(mflodman): Add audio manager when available. } bool MediaStreamManager::RequestDone(const DeviceRequest& request) const { @@ -389,7 +395,7 @@ MediaStreamProvider* MediaStreamManager::GetDeviceManager( if (stream_type == kVideoCapture) { return video_capture_manager_; } else if (stream_type == kAudioCapture) { - // TODO(mflodman) Add support when audio input manager is available. + // TODO(mflodman): Add support when audio input manager is available. NOTREACHED(); return NULL; } @@ -404,7 +410,7 @@ MediaStreamManager::MediaStreamManager() device_settings_(NULL) { device_settings_ = new MediaStreamDeviceSettings(this); video_capture_manager_->Register(this); - // TODO(mflodman) Add when audio input manager is available. + // TODO(mflodman): Add when audio input manager is available. } MediaStreamManager::DeviceRequest::DeviceRequest() diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h index f5792ce..27fea0c 100644 --- a/content/browser/renderer_host/media/media_stream_manager.h +++ b/content/browser/renderer_host/media/media_stream_manager.h @@ -30,6 +30,7 @@ namespace media_stream { +class AudioInputDeviceManager; class MediaStreamDeviceSettings; class MediaStreamRequester; class VideoCaptureManager; @@ -49,6 +50,9 @@ class MediaStreamManager // Used to access VideoCaptuerManager. VideoCaptureManager* video_capture_manager(); + // Used to access AudioInputDeviceManager. + AudioInputDeviceManager* audio_input_device_manager(); + // GenerateStream opens new media devices according to |components|. The // request is identified using |label|, which is pointing to an already // created std::string. @@ -113,7 +117,6 @@ class MediaStreamManager MediaStreamManager(); VideoCaptureManager* video_capture_manager_; - // TODO(mflodman) Add AudioInputManager. // Keeps track of device types currently being enumerated to not enumerate // when not necessary. diff --git a/content/common/media/audio_messages.h b/content/common/media/audio_messages.h index 7a7fde4..3499258 100644 --- a/content/common/media/audio_messages.h +++ b/content/common/media/audio_messages.h @@ -88,6 +88,11 @@ IPC_MESSAGE_CONTROL2(AudioMsg_NotifyStreamStateChanged, int /* stream id */, AudioStreamState /* new state */) +// Notification message sent from browser to renderer for state update. +IPC_MESSAGE_CONTROL2(AudioInputMsg_NotifyStreamStateChanged, + int /* stream id */, + AudioStreamState /* new state */) + IPC_MESSAGE_CONTROL2(AudioMsg_NotifyStreamVolume, int /* stream id */, double /* volume */) @@ -96,6 +101,10 @@ IPC_MESSAGE_CONTROL2(AudioInputMsg_NotifyStreamVolume, int /* stream id */, double /* volume */) +IPC_MESSAGE_CONTROL2(AudioInputMsg_NotifyDeviceStarted, + int /* stream id */, + int /* device_index */) + // Messages sent from the renderer to the browser. // Request that got sent to browser for creating an audio output stream @@ -159,3 +168,9 @@ IPC_MESSAGE_CONTROL2(AudioHostMsg_SetVolume, IPC_MESSAGE_CONTROL2(AudioInputHostMsg_SetVolume, int /* stream_id */, double /* volume */) + +// Start the device referenced by the session_id for the input stream specified +// by stream_id. +IPC_MESSAGE_CONTROL2(AudioInputHostMsg_StartDevice, + int /* stream_id */, + int /* session_id */) diff --git a/content/renderer/media/audio_input_device.cc b/content/renderer/media/audio_input_device.cc index 8b87a3e..7e5f532 100644 --- a/content/renderer/media/audio_input_device.cc +++ b/content/renderer/media/audio_input_device.cc @@ -15,17 +15,22 @@ AudioInputDevice::AudioInputDevice(size_t buffer_size, int channels, double sample_rate, - CaptureCallback* callback) - : buffer_size_(buffer_size), - channels_(channels), - bits_per_sample_(16), - sample_rate_(sample_rate), - callback_(callback), + CaptureCallback* callback, + CaptureEventHandler* event_handler) + : callback_(callback), + event_handler_(event_handler), audio_delay_milliseconds_(0), volume_(1.0), - stream_id_(0) { + stream_id_(0), + session_id_(0), + pending_device_ready_(false) { filter_ = RenderThread::current()->audio_input_message_filter(); audio_data_.reserve(channels); + audio_parameters_.format = AudioParameters::AUDIO_PCM_LINEAR; + audio_parameters_.channels = channels; + audio_parameters_.sample_rate = static_cast<int>(sample_rate); + audio_parameters_.bits_per_sample = 16; + audio_parameters_.samples_per_packet = buffer_size; for (int i = 0; i < channels; ++i) { float* channel_data = new float[buffer_size]; audio_data_.push_back(channel_data); @@ -36,23 +41,23 @@ AudioInputDevice::~AudioInputDevice() { // TODO(henrika): The current design requires that the user calls // Stop before deleting this class. CHECK_EQ(0, stream_id_); - for (int i = 0; i < channels_; ++i) + for (int i = 0; i < audio_parameters_.channels; ++i) delete [] audio_data_[i]; } void AudioInputDevice::Start() { VLOG(1) << "Start()"; - AudioParameters params; - // TODO(henrika): add support for low-latency mode? - params.format = AudioParameters::AUDIO_PCM_LINEAR; - params.channels = channels_; - params.sample_rate = static_cast<int>(sample_rate_); - params.bits_per_sample = bits_per_sample_; - params.samples_per_packet = buffer_size_; + ChildProcess::current()->io_message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &AudioInputDevice::InitializeOnIOThread)); +} +void AudioInputDevice::SetDevice(int session_id) { + VLOG(1) << "SetDevice (session_id=" << session_id << ")"; ChildProcess::current()->io_message_loop()->PostTask( FROM_HERE, - NewRunnableMethod(this, &AudioInputDevice::InitializeOnIOThread, params)); + NewRunnableMethod(this, &AudioInputDevice::SetSessionIdOnIOThread, + session_id)); } bool AudioInputDevice::Stop() { @@ -99,7 +104,7 @@ bool AudioInputDevice::GetVolume(double* volume) { return false; } -void AudioInputDevice::InitializeOnIOThread(const AudioParameters& params) { +void AudioInputDevice::InitializeOnIOThread() { DCHECK(MessageLoop::current() == ChildProcess::current()->io_message_loop()); // Make sure we don't call Start() more than once. DCHECK_EQ(0, stream_id_); @@ -107,7 +112,21 @@ void AudioInputDevice::InitializeOnIOThread(const AudioParameters& params) { return; stream_id_ = filter_->AddDelegate(this); - Send(new AudioInputHostMsg_CreateStream(stream_id_, params, true)); + // 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_) { + Send(new AudioInputHostMsg_CreateStream(stream_id_, audio_parameters_, + true)); + } else { + Send(new AudioInputHostMsg_StartDevice(stream_id_, session_id_)); + pending_device_ready_ = true; + } +} + +void AudioInputDevice::SetSessionIdOnIOThread(int session_id) { + DCHECK(MessageLoop::current() == ChildProcess::current()->io_message_loop()); + session_id_ = session_id; } void AudioInputDevice::StartOnIOThread() { @@ -126,7 +145,10 @@ void AudioInputDevice::ShutDownOnIOThread(base::WaitableEvent* completion) { filter_->RemoveDelegate(stream_id_); Send(new AudioInputHostMsg_CloseStream(stream_id_)); + stream_id_ = 0; + session_id_ = 0; + pending_device_ready_ = false; completion->Signal(); } @@ -178,6 +200,65 @@ void AudioInputDevice::OnVolume(double volume) { NOTIMPLEMENTED(); } +void AudioInputDevice::OnStateChanged(AudioStreamState state) { + DCHECK(MessageLoop::current() == ChildProcess::current()->io_message_loop()); + switch (state) { + case kAudioStreamPaused: + // Do nothing if the stream has been closed. + if (!stream_id_) + return; + + filter_->RemoveDelegate(stream_id_); + + // Joining the audio thread will be quite soon, since the stream has + // been closed before. + if (audio_thread_.get()) { + socket_->Close(); + audio_thread_->Join(); + audio_thread_.reset(NULL); + } + + if (event_handler_) + event_handler_->OnDeviceStopped(); + + stream_id_ = 0; + pending_device_ready_ = false; + break; + case kAudioStreamPlaying: + NOTIMPLEMENTED(); + break; + case kAudioStreamError: + DLOG(WARNING) << "AudioInputDevice::OnStateChanged(kError)"; + break; + default: + NOTREACHED(); + break; + } +} + +void AudioInputDevice::OnDeviceReady(int index) { + DCHECK(MessageLoop::current() == ChildProcess::current()->io_message_loop()); + VLOG(1) << "OnDeviceReady (index=" << index << ")"; + + // Takes care of the case when Stop() is called before OnDeviceReady(). + if (!pending_device_ready_) + return; + + // -1 means no device has been started. + if (index == -1) { + filter_->RemoveDelegate(stream_id_); + stream_id_ = 0; + } else { + Send(new AudioInputHostMsg_CreateStream( + stream_id_, audio_parameters_, true)); + } + + pending_device_ready_ = false; + // Notify the client that the device has been started. + if (event_handler_) + event_handler_->OnDeviceStarted(index); +} + void AudioInputDevice::Send(IPC::Message* message) { filter_->Send(message); } @@ -188,8 +269,10 @@ void AudioInputDevice::Run() { audio_thread_->SetThreadPriority(base::kThreadPriority_RealtimeAudio); int pending_data; - const int samples_per_ms = static_cast<int>(sample_rate_) / 1000; - const int bytes_per_ms = channels_ * (bits_per_sample_ / 8) * samples_per_ms; + const int samples_per_ms = + static_cast<int>(audio_parameters_.sample_rate) / 1000; + const int bytes_per_ms = audio_parameters_.channels * + (audio_parameters_.bits_per_sample / 8) * samples_per_ms; while (sizeof(pending_data) == socket_->Receive(&pending_data, sizeof(pending_data)) && @@ -209,7 +292,7 @@ void AudioInputDevice::FireCaptureCallback() { if (!callback_) return; - const size_t number_of_frames = buffer_size_; + const size_t number_of_frames = audio_parameters_.samples_per_packet; // Read 16-bit samples from shared memory (browser writes to it). int16* input_audio = static_cast<int16*>(shared_memory_data()); @@ -217,10 +300,11 @@ void AudioInputDevice::FireCaptureCallback() { // Deinterleave each channel and convert to 32-bit floating-point // with nominal range -1.0 -> +1.0. - for (int channel_index = 0; channel_index < channels_; ++channel_index) { + for (int channel_index = 0; channel_index < audio_parameters_.channels; + ++channel_index) { media::DeinterleaveAudioChannel(input_audio, audio_data_[channel_index], - channels_, + audio_parameters_.channels, channel_index, bytes_per_sample, number_of_frames); diff --git a/content/renderer/media/audio_input_device.h b/content/renderer/media/audio_input_device.h index 2418a44..8b65613 100644 --- a/content/renderer/media/audio_input_device.h +++ b/content/renderer/media/audio_input_device.h @@ -12,21 +12,35 @@ // | | // v IPC v // AudioInputRendererHost <---------> AudioInputMessageFilter +// ^ +// | +// v +// AudioInputDeviceManager // // Transportation of audio samples from the browser to the render process // is done by using shared memory in combination with a sync socket pair // to generate a low latency transport. The AudioInputDevice user registers // an AudioInputDevice::CaptureCallback at construction and will be called // by the AudioInputDevice 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: // // Task [IO thread] IPC [IO thread] // +// Sequence where session_id has not been set using SetDevice(): // Start -> InitializeOnIOThread -----> AudioInputHostMsg_CreateStream -------> // <- OnLowLatencyCreated <- AudioInputMsg_NotifyLowLatencyStreamCreated <- // ---> StartOnIOThread ---------> AudioInputHostMsg_PlayStream --------> // +// Sequence where session_id has been set using SetDevice(): +// Start -> InitializeOnIOThread --> AudioInputHostMsg_StartDevice ---> +// <---- OnStarted <-------------- AudioInputMsg_NotifyDeviceStarted <---- +// -> OnDeviceReady ------------> AudioInputHostMsg_CreateStream -------> +// <- OnLowLatencyCreated <- AudioInputMsg_NotifyLowLatencyStreamCreated <- +// ---> StartOnIOThread ---------> AudioInputHostMsg_PlayStream --------> +// // AudioInputDevice::Capture => low latency audio transport on audio thread => // | // Stop --> ShutDownOnIOThread ------> AudioInputHostMsg_CloseStream -> Close @@ -48,6 +62,7 @@ // // - Start() is asynchronous/non-blocking. // - Stop() is synchronous/blocking. +// - SetDevice() is asynchronous/non-blocking. // - The user must call Stop() before deleting the class instance. #ifndef CONTENT_RENDERER_MEDIA_AUDIO_INPUT_DEVICE_H_ @@ -61,8 +76,7 @@ #include "base/shared_memory.h" #include "base/threading/simple_thread.h" #include "content/renderer/media/audio_input_message_filter.h" - -struct AudioParameters; +#include "media/audio/audio_parameters.h" // TODO(henrika): This class is based on the AudioDevice class and it has // many components in common. Investigate potential for re-factoring. @@ -83,13 +97,32 @@ class AudioInputDevice virtual ~CaptureCallback() {} }; + class CaptureEventHandler { + public: + // Notification to the client that the device with the specific index has + // been started. This callback is triggered as a result of StartDevice(). + virtual void OnDeviceStarted(int device_index) = 0; + + // Notification to the client that the device has been stopped. + virtual void OnDeviceStopped() = 0; + + protected: + virtual ~CaptureEventHandler() {} + }; + // Methods called on main render thread ------------------------------------- AudioInputDevice(size_t buffer_size, int channels, double sample_rate, - CaptureCallback* callback); + CaptureCallback* callback, + CaptureEventHandler* event_handler); virtual ~AudioInputDevice(); + // Specify the |session_id| to query which device to use. This method is + // asynchronous/non-blocking. + // Start() will use the second sequence if this method is called before. + void SetDevice(int session_id); + // Starts audio capturing. This method is asynchronous/non-blocking. // TODO(henrika): add support for notification when recording has started. void Start(); @@ -107,8 +140,8 @@ class AudioInputDevice // Returns |true| on success. bool GetVolume(double* volume); - double sample_rate() const { return sample_rate_; } - size_t buffer_size() const { return buffer_size_; } + double sample_rate() const { return audio_parameters_.sample_rate; } + size_t buffer_size() const { return audio_parameters_.samples_per_packet; } // Methods called on IO thread ---------------------------------------------- // AudioInputMessageFilter::Delegate impl., called by AudioInputMessageFilter @@ -116,13 +149,16 @@ class AudioInputDevice base::SyncSocket::Handle socket_handle, uint32 length); virtual void OnVolume(double volume); + virtual void OnStateChanged(AudioStreamState state); + virtual void OnDeviceReady(int index); 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(const AudioParameters& params); + void InitializeOnIOThread(); + void SetSessionIdOnIOThread(int session_id); void StartOnIOThread(); void ShutDownOnIOThread(base::WaitableEvent* completion); void SetVolumeOnIOThread(double volume); @@ -137,12 +173,10 @@ class AudioInputDevice virtual void Run(); // Format - size_t buffer_size_; // in sample-frames - int channels_; - int bits_per_sample_; - double sample_rate_; + AudioParameters audio_parameters_; CaptureCallback* callback_; + CaptureEventHandler* event_handler_; // The client callback receives captured audio here. std::vector<float*> audio_data_; @@ -169,6 +203,14 @@ class AudioInputDevice // Our stream ID on the message filter. Only modified on the IO thread. int32 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_; + scoped_ptr<base::SharedMemory> shared_memory_; scoped_ptr<base::SyncSocket> socket_; diff --git a/content/renderer/media/audio_input_message_filter.cc b/content/renderer/media/audio_input_message_filter.cc index 27706e2..4f8c8ba 100644 --- a/content/renderer/media/audio_input_message_filter.cc +++ b/content/renderer/media/audio_input_message_filter.cc @@ -43,6 +43,10 @@ bool AudioInputMessageFilter::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(AudioInputMsg_NotifyLowLatencyStreamCreated, OnLowLatencyStreamCreated) IPC_MESSAGE_HANDLER(AudioInputMsg_NotifyStreamVolume, OnStreamVolume) + IPC_MESSAGE_HANDLER(AudioInputMsg_NotifyStreamStateChanged, + OnStreamStateChanged) + IPC_MESSAGE_HANDLER(AudioInputMsg_NotifyDeviceStarted, + OnDeviceStarted) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; @@ -96,6 +100,27 @@ void AudioInputMessageFilter::OnStreamVolume(int stream_id, double volume) { delegate->OnVolume(volume); } +void AudioInputMessageFilter::OnStreamStateChanged( + int stream_id, AudioStreamState state) { + Delegate* delegate = delegates_.Lookup(stream_id); + if (!delegate) { + DLOG(WARNING) << "Got audio stream event for a non-existent or removed" + " audio renderer."; + return; + } + delegate->OnStateChanged(state); +} + +void AudioInputMessageFilter::OnDeviceStarted(int stream_id, int index) { + Delegate* delegate = delegates_.Lookup(stream_id); + if (!delegate) { + DLOG(WARNING) << "Got audio stream event for a non-existent or removed" + " audio renderer."; + return; + } + delegate->OnDeviceReady(index); +} + int32 AudioInputMessageFilter::AddDelegate(Delegate* delegate) { return delegates_.Add(delegate); } diff --git a/content/renderer/media/audio_input_message_filter.h b/content/renderer/media/audio_input_message_filter.h index 1ea0283..74ba3c0 100644 --- a/content/renderer/media/audio_input_message_filter.h +++ b/content/renderer/media/audio_input_message_filter.h @@ -16,6 +16,7 @@ #include "base/id_map.h" #include "base/shared_memory.h" #include "base/sync_socket.h" +#include "content/common/media/audio_stream_state.h" #include "ipc/ipc_channel_proxy.h" #include "media/audio/audio_buffers_state.h" @@ -35,6 +36,13 @@ class AudioInputMessageFilter : public IPC::ChannelProxy::MessageFilter { // browser process. virtual void OnVolume(double volume) = 0; + // Called when state of an input stream has changed in the browser process. + virtual void OnStateChanged(AudioStreamState state) = 0; + + // Called when the device referenced by the index has been started in + // the browswer process. + virtual void OnDeviceReady(int index) = 0; + protected: virtual ~Delegate() {} }; @@ -71,6 +79,13 @@ class AudioInputMessageFilter : public IPC::ChannelProxy::MessageFilter { // Notification of volume property of an audio input stream. void OnStreamVolume(int stream_id, double volume); + // Received when internal state of browser process' audio input stream has + // changed. + void OnStreamStateChanged(int stream_id, AudioStreamState state); + + // Notification of the opened device of an audio session. + void OnDeviceStarted(int stream_id, int index); + // A map of stream ids to delegates. IDMap<Delegate> delegates_; diff --git a/content/renderer/media/webrtc_audio_device_impl.cc b/content/renderer/media/webrtc_audio_device_impl.cc index 702d428..397aa4f 100644 --- a/content/renderer/media/webrtc_audio_device_impl.cc +++ b/content/renderer/media/webrtc_audio_device_impl.cc @@ -25,6 +25,7 @@ WebRtcAudioDeviceImpl::WebRtcAudioDeviceImpl() output_delay_ms_(0), last_error_(AudioDeviceModule::kAdmErrNone), last_process_time_(base::TimeTicks::Now()), + session_id_(0), initialized_(false), playing_(false), recording_(false) { @@ -159,6 +160,25 @@ void WebRtcAudioDeviceImpl::Capture( } } +void WebRtcAudioDeviceImpl::OnDeviceStarted(int device_index) { + VLOG(1) << "OnDeviceStarted (device_index=" << device_index << ")"; + // -1 is an invalid device index. Do nothing if a valid device has + // been started. Otherwise update the |recording_| state to false. + if (device_index != -1) + return; + + base::AutoLock auto_lock(lock_); + if (recording_) + recording_ = false; +} + +void WebRtcAudioDeviceImpl::OnDeviceStopped() { + VLOG(1) << "OnDeviceStopped"; + base::AutoLock auto_lock(lock_); + if (recording_) + recording_ = false; +} + int32_t WebRtcAudioDeviceImpl::Version(char* version, uint32_t& remaining_buffer_in_bytes, uint32_t& position) const { @@ -352,7 +372,7 @@ int32_t WebRtcAudioDeviceImpl::Init() { // Create and configure the audio capturing client. audio_input_device_ = new AudioInputDevice( - input_buffer_size, input_channels, output_sample_rate, this); + input_buffer_size, input_channels, output_sample_rate, this, this); #if defined(OS_MACOSX) // We create the input device for Mac as well but the performance // will be very bad. @@ -549,12 +569,22 @@ int32_t WebRtcAudioDeviceImpl::StartRecording() { LOG(ERROR) << "Audio transport is missing"; return -1; } + + if (session_id_ <= 0) { + LOG(WARNING) << session_id_ << " is an invalid session id."; + return -1; + } + + base::AutoLock auto_lock(lock_); if (recording_) { // webrtc::VoiceEngine assumes that it is OK to call Start() twice and // that the call is ignored the second time. LOG(WARNING) << "Recording is already active"; return 0; } + + // Specify the session_id which is mapped to a certain device. + audio_input_device_->SetDevice(session_id_); audio_input_device_->Start(); recording_ = true; return 0; @@ -563,6 +593,8 @@ int32_t WebRtcAudioDeviceImpl::StartRecording() { int32_t WebRtcAudioDeviceImpl::StopRecording() { VLOG(1) << "StopRecording()"; DCHECK(audio_input_device_); + + base::AutoLock auto_lock(lock_); if (!recording_) { // webrtc::VoiceEngine assumes that it is OK to call Stop() just in case. LOG(WARNING) << "Recording was already stopped"; @@ -892,3 +924,7 @@ int32_t WebRtcAudioDeviceImpl::GetLoudspeakerStatus(bool* enabled) const { NOTIMPLEMENTED(); return -1; } + +void WebRtcAudioDeviceImpl::SetSessionId(int session_id) { + session_id_ = session_id; +} diff --git a/content/renderer/media/webrtc_audio_device_impl.h b/content/renderer/media/webrtc_audio_device_impl.h index 508fa5b..968afb4 100644 --- a/content/renderer/media/webrtc_audio_device_impl.h +++ b/content/renderer/media/webrtc_audio_device_impl.h @@ -20,10 +20,14 @@ // A WebRtcAudioDeviceImpl instance implements the abstract interface // webrtc::AudioDeviceModule which makes it possible for a user (e.g. webrtc:: // VoiceEngine) to register this class as an external AudioDeviceModule (ADM). -// The user can then call WebRtcAudioDeviceImpl::StartPlayout() and -// WebRtcAudioDeviceImpl::StartRecording() from the render process -// to initiate and start audio rendering and capturing in the browser process. -// IPC is utilized to set up the media streams. +// Then WebRtcAudioDeviceImpl::SetSessionId() needs to be called to set the +// session id that tells which device to use. The user can either get the +// session id from the MediaStream or use a value of 1 (AudioInputDeviceManager +// ::kFakeOpenSessionId), the later will open the default device without going +// through the MediaStream. The user can then call WebRtcAudioDeviceImpl:: +// StartPlayout() and WebRtcAudioDeviceImpl::StartRecording() from the render +// process to initiate and start audio rendering and capturing in the browser +// process. IPC is utilized to set up the media streams. // // Usage example: // @@ -32,6 +36,7 @@ // { // scoped_refptr<WebRtcAudioDeviceImpl> external_adm; // external_adm = new WebRtcAudioDeviceImpl(); +// external_adm->SetSessionId(1); // VoiceEngine* voe = VoiceEngine::Create(); // VoEBase* base = VoEBase::GetInterface(voe); // base->Init(external_adm); @@ -88,7 +93,8 @@ class WebRtcAudioDeviceImpl : public webrtc::AudioDeviceModule, public AudioDevice::RenderCallback, - public AudioInputDevice::CaptureCallback { + public AudioInputDevice::CaptureCallback, + public AudioInputDevice::CaptureEventHandler { public: // Methods called on main render thread. WebRtcAudioDeviceImpl(); @@ -112,6 +118,10 @@ class WebRtcAudioDeviceImpl size_t number_of_frames, size_t audio_delay_milliseconds) OVERRIDE; + // AudioInputDevice::CaptureEventHandler implementation. + virtual void OnDeviceStarted(int device_index); + virtual void OnDeviceStopped(); + // webrtc::Module implementation. virtual int32_t Version(char* version, uint32_t& remaining_buffer_in_bytes, @@ -238,6 +248,9 @@ class WebRtcAudioDeviceImpl virtual int32_t SetLoudspeakerStatus(bool enable) OVERRIDE; virtual int32_t GetLoudspeakerStatus(bool* enabled) const OVERRIDE; + // Sets the session id. + void SetSessionId(int session_id); + // Accessors. size_t input_buffer_size() const { return input_buffer_size_; } size_t output_buffer_size() const { return output_buffer_size_; } @@ -293,6 +306,13 @@ class WebRtcAudioDeviceImpl base::TimeTicks last_process_time_; + // Id of the media session to be started, it tells which device to be used + // on the input/capture side. + int session_id_; + + // Protect |recording_|. + base::Lock lock_; + int bytes_per_sample_; bool initialized_; |