diff options
37 files changed, 931 insertions, 159 deletions
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index c51b5c5..4194aa8 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc @@ -52,6 +52,7 @@ #include "crypto/nss_util.h" #include "media/audio/audio_manager.h" #include "media/base/media.h" +#include "media/base/user_input_monitor.h" #include "media/midi/midi_manager.h" #include "net/base/network_change_notifier.h" #include "net/socket/client_socket_factory.h" @@ -872,6 +873,14 @@ int BrowserMainLoop::BrowserThreadsStarted() { audio_manager_.get(), media_stream_manager_.get())); } + { + TRACE_EVENT0( + "startup", + "BrowserMainLoop::BrowserThreadsStarted::InitUserInputMonitor"); + user_input_monitor_ = media::UserInputMonitor::Create( + io_thread_->message_loop_proxy(), main_thread_->message_loop_proxy()); + } + // Alert the clipboard class to which threads are allowed to access the // clipboard: std::vector<base::PlatformThreadId> allowed_clipboard_threads; diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h index 7b2879f..83dbafd 100644 --- a/content/browser/browser_main_loop.h +++ b/content/browser/browser_main_loop.h @@ -26,6 +26,7 @@ class TraceMemoryController; namespace media { class AudioManager; class MIDIManager; +class UserInputMonitor; } // namespace media namespace net { @@ -86,6 +87,9 @@ class CONTENT_EXPORT BrowserMainLoop { MediaStreamManager* media_stream_manager() const { return media_stream_manager_.get(); } + media::UserInputMonitor* user_input_monitor() const { + return user_input_monitor_.get(); + } media::MIDIManager* midi_manager() const { return midi_manager_.get(); } base::Thread* indexed_db_thread() const { return indexed_db_thread_.get(); } @@ -122,6 +126,8 @@ class CONTENT_EXPORT BrowserMainLoop { scoped_ptr<base::PowerMonitor> power_monitor_; scoped_ptr<base::HighResolutionTimerManager> hi_res_timer_manager_; scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_; + // user_input_monitor_ has to outlive audio_manager_, so declared first. + scoped_ptr<media::UserInputMonitor> user_input_monitor_; scoped_ptr<media::AudioManager> audio_manager_; scoped_ptr<media::MIDIManager> midi_manager_; scoped_ptr<AudioMirroringManager> audio_mirroring_manager_; 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 4a2f731..1c02109 100644 --- a/content/browser/renderer_host/media/audio_input_renderer_host.cc +++ b/content/browser/renderer_host/media/audio_input_renderer_host.cc @@ -51,11 +51,12 @@ AudioInputRendererHost::AudioEntry::~AudioEntry() {} AudioInputRendererHost::AudioInputRendererHost( media::AudioManager* audio_manager, MediaStreamManager* media_stream_manager, - AudioMirroringManager* audio_mirroring_manager) + AudioMirroringManager* audio_mirroring_manager, + media::UserInputMonitor* user_input_monitor) : audio_manager_(audio_manager), media_stream_manager_(media_stream_manager), - audio_mirroring_manager_(audio_mirroring_manager) { -} + audio_mirroring_manager_(audio_mirroring_manager), + user_input_monitor_(user_input_monitor) {} AudioInputRendererHost::~AudioInputRendererHost() { DCHECK(audio_entries_.empty()); @@ -272,20 +273,23 @@ void AudioInputRendererHost::OnCreateStream( entry->controller = media::AudioInputController::CreateForStream( audio_manager_->GetMessageLoop(), this, - WebContentsAudioInputStream::Create( - device_id, audio_params, audio_manager_->GetWorkerLoop(), - audio_mirroring_manager_), - entry->writer.get()); + WebContentsAudioInputStream::Create(device_id, + audio_params, + audio_manager_->GetWorkerLoop(), + audio_mirroring_manager_), + entry->writer.get(), + user_input_monitor_); } else { // TODO(henrika): replace CreateLowLatency() with Create() as soon // as satish has ensured that Speech Input also uses the default low- // latency path. See crbug.com/112472 for details. - entry->controller = media::AudioInputController::CreateLowLatency( - audio_manager_, - this, - audio_params, - device_id, - entry->writer.get()); + entry->controller = + media::AudioInputController::CreateLowLatency(audio_manager_, + this, + audio_params, + device_id, + entry->writer.get(), + user_input_monitor_); } if (!entry->controller.get()) { 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 d16ebfa..5df1fc6 100644 --- a/content/browser/renderer_host/media/audio_input_renderer_host.h +++ b/content/browser/renderer_host/media/audio_input_renderer_host.h @@ -44,6 +44,7 @@ namespace media { class AudioManager; class AudioParameters; +class UserInputMonitor; } namespace content { @@ -55,10 +56,11 @@ class CONTENT_EXPORT AudioInputRendererHost public media::AudioInputController::EventHandler { public: // Called from UI thread from the owner of this object. - AudioInputRendererHost( - media::AudioManager* audio_manager, - MediaStreamManager* media_stream_manager, - AudioMirroringManager* audio_mirroring_manager); + // |user_input_monitor| is used for typing detection and can be NULL. + AudioInputRendererHost(media::AudioManager* audio_manager, + MediaStreamManager* media_stream_manager, + AudioMirroringManager* audio_mirroring_manager, + media::UserInputMonitor* user_input_monitor); // BrowserMessageFilter implementation. virtual void OnChannelClosing() OVERRIDE; @@ -154,6 +156,9 @@ class CONTENT_EXPORT AudioInputRendererHost // A map of stream IDs to audio sources. AudioEntryMap audio_entries_; + // Raw pointer of the UserInputMonitor. + media::UserInputMonitor* user_input_monitor_; + DISALLOW_COPY_AND_ASSIGN(AudioInputRendererHost); }; diff --git a/content/browser/renderer_host/media/audio_input_sync_writer.cc b/content/browser/renderer_host/media/audio_input_sync_writer.cc index 572abf3..369e0a8 100644 --- a/content/browser/renderer_host/media/audio_input_sync_writer.cc +++ b/content/browser/renderer_host/media/audio_input_sync_writer.cc @@ -29,14 +29,17 @@ void AudioInputSyncWriter::UpdateRecordedBytes(uint32 bytes) { socket_->Send(&bytes, sizeof(bytes)); } -uint32 AudioInputSyncWriter::Write( - const void* data, uint32 size, double volume) { +uint32 AudioInputSyncWriter::Write(const void* data, + uint32 size, + double volume, + bool key_pressed) { uint8* ptr = static_cast<uint8*>(shared_memory_->memory()); ptr += current_segment_id_ * shared_memory_segment_size_; media::AudioInputBuffer* buffer = reinterpret_cast<media::AudioInputBuffer*>(ptr); buffer->params.volume = volume; buffer->params.size = size; + buffer->params.key_pressed = key_pressed; memcpy(buffer->audio, data, size); if (++current_segment_id_ >= shared_memory_segment_count_) diff --git a/content/browser/renderer_host/media/audio_input_sync_writer.h b/content/browser/renderer_host/media/audio_input_sync_writer.h index 4cfe9e3..d16911f 100644 --- a/content/browser/renderer_host/media/audio_input_sync_writer.h +++ b/content/browser/renderer_host/media/audio_input_sync_writer.h @@ -28,7 +28,10 @@ class AudioInputSyncWriter : public media::AudioInputController::SyncWriter { // media::AudioOutputController::SyncWriter implementation. virtual void UpdateRecordedBytes(uint32 bytes) OVERRIDE; - virtual uint32 Write(const void* data, uint32 size, double volume) OVERRIDE; + virtual uint32 Write(const void* data, + uint32 size, + double volume, + bool key_pressed) OVERRIDE; virtual void Close() OVERRIDE; bool Init(); diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index e31aff2..3b22bc8 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc @@ -617,11 +617,14 @@ void RenderProcessHostImpl::CreateMessageFilters() { channel_->AddFilter(new AudioInputRendererHost( audio_manager, media_stream_manager, - BrowserMainLoop::GetInstance()->audio_mirroring_manager())); + BrowserMainLoop::GetInstance()->audio_mirroring_manager(), + BrowserMainLoop::GetInstance()->user_input_monitor())); channel_->AddFilter(new AudioRendererHost( - GetID(), audio_manager, + GetID(), + audio_manager, BrowserMainLoop::GetInstance()->audio_mirroring_manager(), - media_internals, media_stream_manager)); + media_internals, + media_stream_manager)); channel_->AddFilter( new MIDIHost(BrowserMainLoop::GetInstance()->midi_manager())); channel_->AddFilter(new MIDIDispatcherHost(GetID(), browser_context)); diff --git a/content/browser/speech/speech_recognizer_impl.cc b/content/browser/speech/speech_recognizer_impl.cc index 2081b2f..2dae430 100644 --- a/content/browser/speech/speech_recognizer_impl.cc +++ b/content/browser/speech/speech_recognizer_impl.cc @@ -564,7 +564,7 @@ SpeechRecognizerImpl::StartRecording(const FSMEventArgs&) { new OnDataConverter(input_parameters, output_parameters)); audio_controller_ = AudioInputController::Create( - audio_manager, this, input_parameters, device_id_); + audio_manager, this, input_parameters, device_id_, NULL); if (!audio_controller_.get()) { return Abort(SpeechRecognitionError(SPEECH_RECOGNITION_ERROR_AUDIO)); diff --git a/content/renderer/media/webaudio_capturer_source.cc b/content/renderer/media/webaudio_capturer_source.cc index 0ab8dc3..35cd99c 100644 --- a/content/renderer/media/webaudio_capturer_source.cc +++ b/content/renderer/media/webaudio_capturer_source.cc @@ -96,7 +96,7 @@ void WebAudioCapturerSource::consumeAudio( int capture_frames = params_.frames_per_buffer(); while (fifo_->frames() >= capture_frames) { fifo_->Consume(capture_bus_.get(), 0, capture_frames); - callback_->Capture(capture_bus_.get(), 0, 1.0); + callback_->Capture(capture_bus_.get(), 0, 1.0, false); } } diff --git a/content/renderer/media/webrtc_audio_capturer.cc b/content/renderer/media/webrtc_audio_capturer.cc index 238b2a2..26b0fd7 100644 --- a/content/renderer/media/webrtc_audio_capturer.cc +++ b/content/renderer/media/webrtc_audio_capturer.cc @@ -102,14 +102,16 @@ class WebRtcAudioCapturer::TrackOwner int number_of_channels, int number_of_frames, int audio_delay_milliseconds, - int volume) { + int volume, + bool key_pressed) { base::AutoLock lock(lock_); if (delegate_) { delegate_->CaptureData(audio_data, number_of_channels, number_of_frames, audio_delay_milliseconds, - volume); + volume, + key_pressed); } } @@ -424,10 +426,11 @@ void WebRtcAudioCapturer::SetAutomaticGainControl(bool enable) { void WebRtcAudioCapturer::Capture(media::AudioBus* audio_source, int audio_delay_milliseconds, - double volume) { - // This callback is driven by AudioInputDevice::AudioThreadCallback if - // |source_| is AudioInputDevice, otherwise it is driven by client's - // CaptureCallback. + double volume, + bool key_pressed) { +// This callback is driven by AudioInputDevice::AudioThreadCallback if +// |source_| is AudioInputDevice, otherwise it is driven by client's +// CaptureCallback. #if defined(OS_WIN) || defined(OS_MACOSX) DCHECK_LE(volume, 1.0); #elif defined(OS_LINUX) || defined(OS_OPENBSD) @@ -471,8 +474,11 @@ void WebRtcAudioCapturer::Capture(media::AudioBus* audio_source, it != tracks.end(); ++it) { (*it)->CaptureData(buffer_ref_while_calling->buffer(), - audio_source->channels(), audio_source->frames(), - audio_delay_milliseconds, volume_); + audio_source->channels(), + audio_source->frames(), + audio_delay_milliseconds, + volume, + key_pressed); } } diff --git a/content/renderer/media/webrtc_audio_capturer.h b/content/renderer/media/webrtc_audio_capturer.h index e85ea04..caa88d03 100644 --- a/content/renderer/media/webrtc_audio_capturer.h +++ b/content/renderer/media/webrtc_audio_capturer.h @@ -108,7 +108,8 @@ class CONTENT_EXPORT WebRtcAudioCapturer // Called on the AudioInputDevice audio thread. virtual void Capture(media::AudioBus* audio_source, int audio_delay_milliseconds, - double volume) OVERRIDE; + double volume, + bool key_pressed) OVERRIDE; virtual void OnCaptureError() OVERRIDE; // Reconfigures the capturer with a new buffer size and capture parameters. diff --git a/content/renderer/media/webrtc_audio_capturer_sink_owner.cc b/content/renderer/media/webrtc_audio_capturer_sink_owner.cc index cb6a813..59de510 100644 --- a/content/renderer/media/webrtc_audio_capturer_sink_owner.cc +++ b/content/renderer/media/webrtc_audio_capturer_sink_owner.cc @@ -18,13 +18,19 @@ int WebRtcAudioCapturerSinkOwner::CaptureData(const std::vector<int>& channels, int number_of_frames, int audio_delay_milliseconds, int current_volume, - bool need_audio_processing) { + bool need_audio_processing, + bool key_pressed) { base::AutoLock lock(lock_); if (delegate_) { - return delegate_->CaptureData(channels, audio_data, sample_rate, - number_of_channels, number_of_frames, - audio_delay_milliseconds, current_volume, - need_audio_processing); + delegate_->CaptureData(channels, + audio_data, + sample_rate, + number_of_channels, + number_of_frames, + audio_delay_milliseconds, + current_volume, + need_audio_processing, + key_pressed); } return 0; diff --git a/content/renderer/media/webrtc_audio_capturer_sink_owner.h b/content/renderer/media/webrtc_audio_capturer_sink_owner.h index a3641b8..f338209 100644 --- a/content/renderer/media/webrtc_audio_capturer_sink_owner.h +++ b/content/renderer/media/webrtc_audio_capturer_sink_owner.h @@ -31,7 +31,8 @@ class WebRtcAudioCapturerSinkOwner int number_of_frames, int audio_delay_milliseconds, int current_volume, - bool need_audio_processing) OVERRIDE; + bool need_audio_processing, + bool key_pressed) OVERRIDE; virtual void SetCaptureFormat(const media::AudioParameters& params) OVERRIDE; diff --git a/content/renderer/media/webrtc_audio_device_impl.cc b/content/renderer/media/webrtc_audio_device_impl.cc index c40e0a2..93e024b 100644 --- a/content/renderer/media/webrtc_audio_device_impl.cc +++ b/content/renderer/media/webrtc_audio_device_impl.cc @@ -59,7 +59,8 @@ int WebRtcAudioDeviceImpl::CaptureData(const std::vector<int>& channels, int number_of_frames, int audio_delay_milliseconds, int current_volume, - bool need_audio_processing) { + bool need_audio_processing, + bool key_pressed) { int total_delay_ms = 0; { base::AutoLock auto_lock(lock_); @@ -75,11 +76,9 @@ int WebRtcAudioDeviceImpl::CaptureData(const std::vector<int>& channels, // Write audio samples in blocks of 10 milliseconds to the registered // webrtc::AudioTransport sink. Keep writing until our internal byte // buffer is empty. - // TODO(niklase): Wire up the key press detection. const int16* audio_buffer = audio_data; const int samples_per_10_msec = (sample_rate / 100); int accumulated_audio_samples = 0; - bool key_pressed = false; uint32_t new_volume = 0; while (accumulated_audio_samples < number_of_frames) { // Deliver 10ms of recorded 16-bit linear PCM audio. diff --git a/content/renderer/media/webrtc_audio_device_impl.h b/content/renderer/media/webrtc_audio_device_impl.h index ef3cb7c..b644178 100644 --- a/content/renderer/media/webrtc_audio_device_impl.h +++ b/content/renderer/media/webrtc_audio_device_impl.h @@ -223,7 +223,8 @@ class WebRtcAudioCapturerSink { int number_of_frames, int audio_delay_milliseconds, int current_volume, - bool need_audio_processing) = 0; + bool need_audio_processing, + bool key_pressed) = 0; // Set the format for the capture audio parameters. virtual void SetCaptureFormat(const media::AudioParameters& params) = 0; @@ -339,7 +340,8 @@ class CONTENT_EXPORT WebRtcAudioDeviceImpl int number_of_frames, int audio_delay_milliseconds, int current_volume, - bool need_audio_processing) OVERRIDE; + bool need_audio_processing, + bool key_pressed) OVERRIDE; // Called on the main render thread. virtual void SetCaptureFormat(const media::AudioParameters& params) OVERRIDE; diff --git a/content/renderer/media/webrtc_audio_device_unittest.cc b/content/renderer/media/webrtc_audio_device_unittest.cc index 6746ab6..a08ea93 100644 --- a/content/renderer/media/webrtc_audio_device_unittest.cc +++ b/content/renderer/media/webrtc_audio_device_unittest.cc @@ -221,7 +221,8 @@ class MockWebRtcAudioCapturerSink : public WebRtcAudioCapturerSink { int number_of_frames, int audio_delay_milliseconds, int current_volume, - bool need_audio_processing) OVERRIDE { + bool need_audio_processing, + bool key_pressed) OVERRIDE { // Signal that a callback has been received. event_->Signal(); return 0; @@ -381,8 +382,13 @@ int RunWebRtcLoopbackTimeTest(media::AudioManager* manager, capturer_sink->CaptureData( voe_channels, reinterpret_cast<int16*>(capture_data.get() + input_packet_size * j), - params.sample_rate(), params.channels(), params.frames_per_buffer(), - kHardwareLatencyInMs, 1.0, enable_apm); + params.sample_rate(), + params.channels(), + params.frames_per_buffer(), + kHardwareLatencyInMs, + 1.0, + enable_apm, + false); // Receiving data from WebRtc. renderer_source->RenderData( diff --git a/content/renderer/media/webrtc_local_audio_renderer.cc b/content/renderer/media/webrtc_local_audio_renderer.cc index 11c125f..af65d8d 100644 --- a/content/renderer/media/webrtc_local_audio_renderer.cc +++ b/content/renderer/media/webrtc_local_audio_renderer.cc @@ -52,13 +52,14 @@ void WebRtcLocalAudioRenderer::OnRenderError() { // content::WebRtcAudioCapturerSink implementation int WebRtcLocalAudioRenderer::CaptureData(const std::vector<int>& channels, - const int16* audio_data, - int sample_rate, - int number_of_channels, - int number_of_frames, - int audio_delay_milliseconds, - int current_volume, - bool need_audio_processing) { + const int16* audio_data, + int sample_rate, + int number_of_channels, + int number_of_frames, + int audio_delay_milliseconds, + int current_volume, + bool need_audio_processing, + bool key_pressed) { TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::CaptureData"); base::AutoLock auto_lock(thread_lock_); diff --git a/content/renderer/media/webrtc_local_audio_renderer.h b/content/renderer/media/webrtc_local_audio_renderer.h index 2930393..f58aee3 100644 --- a/content/renderer/media/webrtc_local_audio_renderer.h +++ b/content/renderer/media/webrtc_local_audio_renderer.h @@ -78,7 +78,8 @@ class CONTENT_EXPORT WebRtcLocalAudioRenderer int number_of_frames, int audio_delay_milliseconds, int current_volume, - bool need_audio_processing) OVERRIDE; + bool need_audio_processing, + bool key_pressed) OVERRIDE; // Can be called on different user thread. virtual void SetCaptureFormat(const media::AudioParameters& params) OVERRIDE; diff --git a/content/renderer/media/webrtc_local_audio_track.cc b/content/renderer/media/webrtc_local_audio_track.cc index de86a3c..cae6bfe 100644 --- a/content/renderer/media/webrtc_local_audio_track.cc +++ b/content/renderer/media/webrtc_local_audio_track.cc @@ -47,7 +47,8 @@ void WebRtcLocalAudioTrack::CaptureData(const int16* audio_data, int number_of_channels, int number_of_frames, int audio_delay_milliseconds, - int volume) { + int volume, + bool key_pressed) { scoped_refptr<WebRtcAudioCapturer> capturer; std::vector<int> voe_channels; int sample_rate = 0; @@ -68,10 +69,15 @@ void WebRtcLocalAudioTrack::CaptureData(const int16* audio_data, // Feed the data to the sinks. for (SinkList::const_iterator it = sinks.begin(); it != sinks.end(); ++it) { - int new_volume = (*it)->CaptureData(voe_channels, audio_data, sample_rate, - number_of_channels, number_of_frames, - audio_delay_milliseconds, volume, - need_audio_processing_); + int new_volume = (*it)->CaptureData(voe_channels, + audio_data, + sample_rate, + number_of_channels, + number_of_frames, + audio_delay_milliseconds, + volume, + need_audio_processing_, + key_pressed); if (new_volume != 0 && capturer.get()) capturer->SetVolume(new_volume); } diff --git a/content/renderer/media/webrtc_local_audio_track.h b/content/renderer/media/webrtc_local_audio_track.h index 7cb8c85..0f4e712 100644 --- a/content/renderer/media/webrtc_local_audio_track.h +++ b/content/renderer/media/webrtc_local_audio_track.h @@ -61,7 +61,8 @@ class CONTENT_EXPORT WebRtcLocalAudioTrack int number_of_channels, int number_of_frames, int audio_delay_milliseconds, - int volume); + int volume, + bool key_pressed); // Method called by the capturer to set the audio parameters used by source // of the capture data.. diff --git a/content/renderer/media/webrtc_local_audio_track_unittest.cc b/content/renderer/media/webrtc_local_audio_track_unittest.cc index 4720e45..b75ca79 100644 --- a/content/renderer/media/webrtc_local_audio_track_unittest.cc +++ b/content/renderer/media/webrtc_local_audio_track_unittest.cc @@ -50,7 +50,7 @@ class FakeAudioThread : public base::PlatformThread::Delegate { static_cast<media::AudioCapturerSource::CaptureCallback*>( capturer_.get()); audio_bus_->Zero(); - callback->Capture(audio_bus_.get(), 0, 0); + callback->Capture(audio_bus_.get(), 0, 0, false); // Sleep 1ms to yield the resource for the main thread. base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1)); @@ -103,20 +103,27 @@ class MockWebRtcAudioCapturerSink : public WebRtcAudioCapturerSink { int number_of_frames, int audio_delay_milliseconds, int current_volume, - bool need_audio_processing) OVERRIDE { - CaptureData(channels.size(), sample_rate, number_of_channels, - number_of_frames, audio_delay_milliseconds, current_volume, - need_audio_processing); + bool need_audio_processing, + bool key_pressed) OVERRIDE { + CaptureData(channels.size(), + sample_rate, + number_of_channels, + number_of_frames, + audio_delay_milliseconds, + current_volume, + need_audio_processing, + key_pressed); return 0; } - MOCK_METHOD7(CaptureData, void(int number_of_network_channels, - int sample_rate, - int number_of_channels, - int number_of_frames, - int audio_delay_milliseconds, - int current_volume, - bool need_audio_processing)); - + MOCK_METHOD8(CaptureData, + void(int number_of_network_channels, + int sample_rate, + int number_of_channels, + int number_of_frames, + int audio_delay_milliseconds, + int current_volume, + bool need_audio_processing, + bool key_pressed)); MOCK_METHOD1(SetCaptureFormat, void(const media::AudioParameters& params)); }; @@ -173,10 +180,16 @@ TEST_F(WebRtcLocalAudioTrackTest, ConnectAndDisconnectOneSink) { const media::AudioParameters params = capturer_->audio_parameters(); base::WaitableEvent event(false, false); EXPECT_CALL(*sink, SetCaptureFormat(_)).WillOnce(Return()); - EXPECT_CALL(*sink, CaptureData( - kNumberOfNetworkChannels, params.sample_rate(), params.channels(), - params.frames_per_buffer(), 0, 0, false)) - .Times(AtLeast(1)).WillRepeatedly(SignalEvent(&event)); + EXPECT_CALL(*sink, + CaptureData(kNumberOfNetworkChannels, + params.sample_rate(), + params.channels(), + params.frames_per_buffer(), + 0, + 0, + false, + false)).Times(AtLeast(1)) + .WillRepeatedly(SignalEvent(&event)); track->AddSink(sink.get()); EXPECT_TRUE(event.TimedWait(TestTimeouts::tiny_timeout())); @@ -207,18 +220,29 @@ TEST_F(WebRtcLocalAudioTrackTest, DISABLED_DisableEnableAudioTrack) { const media::AudioParameters params = capturer_->audio_parameters(); base::WaitableEvent event(false, false); EXPECT_CALL(*sink, SetCaptureFormat(_)).WillOnce(Return()); - EXPECT_CALL(*sink, CaptureData( - 1, params.sample_rate(), params.channels(), - params.frames_per_buffer(), 0, 0, false)) - .Times(0); + EXPECT_CALL(*sink, + CaptureData(1, + params.sample_rate(), + params.channels(), + params.frames_per_buffer(), + 0, + 0, + false, + false)).Times(0); track->AddSink(sink.get()); EXPECT_FALSE(event.TimedWait(TestTimeouts::tiny_timeout())); event.Reset(); - EXPECT_CALL(*sink, CaptureData( - 1, params.sample_rate(), params.channels(), - params.frames_per_buffer(), 0, 0, false)) - .Times(AtLeast(1)).WillRepeatedly(SignalEvent(&event)); + EXPECT_CALL(*sink, + CaptureData(1, + params.sample_rate(), + params.channels(), + params.frames_per_buffer(), + 0, + 0, + false, + false)).Times(AtLeast(1)) + .WillRepeatedly(SignalEvent(&event)); EXPECT_TRUE(track->set_enabled(true)); EXPECT_TRUE(event.TimedWait(TestTimeouts::tiny_timeout())); track->RemoveSink(sink.get()); @@ -243,10 +267,16 @@ TEST_F(WebRtcLocalAudioTrackTest, MultipleAudioTracks) { const media::AudioParameters params = capturer_->audio_parameters(); base::WaitableEvent event_1(false, false); EXPECT_CALL(*sink_1, SetCaptureFormat(_)).WillOnce(Return()); - EXPECT_CALL(*sink_1, CaptureData( - 1, params.sample_rate(), params.channels(), - params.frames_per_buffer(), 0, 0, false)) - .Times(AtLeast(1)).WillRepeatedly(SignalEvent(&event_1)); + EXPECT_CALL(*sink_1, + CaptureData(1, + params.sample_rate(), + params.channels(), + params.frames_per_buffer(), + 0, + 0, + false, + false)).Times(AtLeast(1)) + .WillRepeatedly(SignalEvent(&event_1)); track_1->AddSink(sink_1.get()); EXPECT_TRUE(event_1.TimedWait(TestTimeouts::tiny_timeout())); @@ -264,14 +294,26 @@ TEST_F(WebRtcLocalAudioTrackTest, MultipleAudioTracks) { scoped_ptr<MockWebRtcAudioCapturerSink> sink_2( new MockWebRtcAudioCapturerSink()); EXPECT_CALL(*sink_2, SetCaptureFormat(_)).WillOnce(Return()); - EXPECT_CALL(*sink_1, CaptureData( - 1, params.sample_rate(), params.channels(), - params.frames_per_buffer(), 0, 0, false)) - .Times(AtLeast(1)).WillRepeatedly(SignalEvent(&event_1)); - EXPECT_CALL(*sink_2, CaptureData( - 1, params.sample_rate(), params.channels(), - params.frames_per_buffer(), 0, 0, false)) - .Times(AtLeast(1)).WillRepeatedly(SignalEvent(&event_2)); + EXPECT_CALL(*sink_1, + CaptureData(1, + params.sample_rate(), + params.channels(), + params.frames_per_buffer(), + 0, + 0, + false, + false)).Times(AtLeast(1)) + .WillRepeatedly(SignalEvent(&event_1)); + EXPECT_CALL(*sink_2, + CaptureData(1, + params.sample_rate(), + params.channels(), + params.frames_per_buffer(), + 0, + 0, + false, + false)).Times(AtLeast(1)) + .WillRepeatedly(SignalEvent(&event_2)); track_2->AddSink(sink_2.get()); EXPECT_TRUE(event_1.TimedWait(TestTimeouts::tiny_timeout())); EXPECT_TRUE(event_2.TimedWait(TestTimeouts::tiny_timeout())); @@ -319,7 +361,7 @@ TEST_F(WebRtcLocalAudioTrackTest, StartAndStopAudioTracks) { scoped_ptr<MockWebRtcAudioCapturerSink> sink( new MockWebRtcAudioCapturerSink()); event.Reset(); - EXPECT_CALL(*sink, CaptureData(_, _, _, _, 0, 0, false)) + EXPECT_CALL(*sink, CaptureData(_, _, _, _, 0, 0, false, false)) .Times(AnyNumber()).WillRepeatedly(Return()); EXPECT_CALL(*sink, SetCaptureFormat(_)).Times(1); track_1->AddSink(sink.get()); @@ -339,7 +381,7 @@ TEST_F(WebRtcLocalAudioTrackTest, StartAndStopAudioTracks) { track_1->Stop(); track_1 = NULL; - EXPECT_CALL(*sink, CaptureData(_, _, _, _, 0, 0, false)) + EXPECT_CALL(*sink, CaptureData(_, _, _, _, 0, 0, false, false)) .Times(AnyNumber()).WillRepeatedly(Return()); EXPECT_CALL(*sink, SetCaptureFormat(_)).Times(1); track_2->AddSink(sink.get()); @@ -396,8 +438,10 @@ TEST_F(WebRtcLocalAudioTrackTest, ConnectTracksToDifferentCapturers) { // Verify the data flow by connecting the |sink_1| to |track_1|. scoped_ptr<MockWebRtcAudioCapturerSink> sink_1( new MockWebRtcAudioCapturerSink()); - EXPECT_CALL(*sink_1.get(), CaptureData(kNumberOfNetworkChannelsForTrack1, - 48000, 2, _, 0, 0, false)) + EXPECT_CALL( + *sink_1.get(), + CaptureData( + kNumberOfNetworkChannelsForTrack1, 48000, 2, _, 0, 0, false, false)) .Times(AnyNumber()).WillRepeatedly(Return()); EXPECT_CALL(*sink_1.get(), SetCaptureFormat(_)).Times(1); track_1->AddSink(sink_1.get()); @@ -433,8 +477,10 @@ TEST_F(WebRtcLocalAudioTrackTest, ConnectTracksToDifferentCapturers) { // Verify the data flow by connecting the |sink_2| to |track_2|. scoped_ptr<MockWebRtcAudioCapturerSink> sink_2( new MockWebRtcAudioCapturerSink()); - EXPECT_CALL(*sink_2, CaptureData(kNumberOfNetworkChannelsForTrack2, - 44100, 1, _, 0, 0, false)) + EXPECT_CALL( + *sink_2, + CaptureData( + kNumberOfNetworkChannelsForTrack2, 44100, 1, _, 0, 0, false, false)) .Times(AnyNumber()).WillRepeatedly(Return()); EXPECT_CALL(*sink_2, SetCaptureFormat(_)).Times(1); track_2->AddSink(sink_2.get()); diff --git a/content/test/webrtc_audio_device_test.cc b/content/test/webrtc_audio_device_test.cc index c258caf..eb2f1c7 100644 --- a/content/test/webrtc_audio_device_test.cc +++ b/content/test/webrtc_audio_device_test.cc @@ -252,9 +252,11 @@ void MAYBE_WebRTCAudioDeviceTest::CreateChannel(const char* name) { media_internals_.get(), media_stream_manager_.get()); audio_render_host_->OnChannelConnected(base::GetCurrentProcId()); - audio_input_renderer_host_ = new AudioInputRendererHost( - audio_manager_.get(), media_stream_manager_.get(), - mirroring_manager_.get()); + audio_input_renderer_host_ = + new AudioInputRendererHost(audio_manager_.get(), + media_stream_manager_.get(), + mirroring_manager_.get(), + NULL); audio_input_renderer_host_->OnChannelConnected(base::GetCurrentProcId()); channel_.reset(new IPC::Channel(name, IPC::Channel::MODE_SERVER, this)); @@ -5,6 +5,7 @@ include_rules = [ "+third_party/libvpx", "+third_party/opus", "+third_party/skia", + "+ui/base", "+ui/gfx", "+ui/gl", ] diff --git a/media/audio/audio_input_controller.cc b/media/audio/audio_input_controller.cc index 31e137e..f7747b9 100644 --- a/media/audio/audio_input_controller.cc +++ b/media/audio/audio_input_controller.cc @@ -36,14 +36,17 @@ namespace media { AudioInputController::Factory* AudioInputController::factory_ = NULL; AudioInputController::AudioInputController(EventHandler* handler, - SyncWriter* sync_writer) + SyncWriter* sync_writer, + UserInputMonitor* user_input_monitor) : creator_loop_(base::MessageLoopProxy::current()), handler_(handler), stream_(NULL), data_is_active_(false), state_(kEmpty), sync_writer_(sync_writer), - max_volume_(0.0) { + max_volume_(0.0), + user_input_monitor_(user_input_monitor), + key_pressed_(false) { DCHECK(creator_loop_.get()); } @@ -56,17 +59,19 @@ scoped_refptr<AudioInputController> AudioInputController::Create( AudioManager* audio_manager, EventHandler* event_handler, const AudioParameters& params, - const std::string& device_id) { + const std::string& device_id, + UserInputMonitor* user_input_monitor) { DCHECK(audio_manager); if (!params.IsValid() || (params.channels() > kMaxInputChannels)) return NULL; - if (factory_) - return factory_->Create(audio_manager, event_handler, params); - - scoped_refptr<AudioInputController> controller(new AudioInputController( - event_handler, NULL)); + if (factory_) { + return factory_->Create( + audio_manager, event_handler, params, user_input_monitor); + } + scoped_refptr<AudioInputController> controller( + new AudioInputController(event_handler, NULL, user_input_monitor)); controller->message_loop_ = audio_manager->GetMessageLoop(); @@ -87,7 +92,8 @@ scoped_refptr<AudioInputController> AudioInputController::CreateLowLatency( EventHandler* event_handler, const AudioParameters& params, const std::string& device_id, - SyncWriter* sync_writer) { + SyncWriter* sync_writer, + UserInputMonitor* user_input_monitor) { DCHECK(audio_manager); DCHECK(sync_writer); @@ -96,8 +102,8 @@ scoped_refptr<AudioInputController> AudioInputController::CreateLowLatency( // Create the AudioInputController object and ensure that it runs on // the audio-manager thread. - scoped_refptr<AudioInputController> controller(new AudioInputController( - event_handler, sync_writer)); + scoped_refptr<AudioInputController> controller( + new AudioInputController(event_handler, sync_writer, user_input_monitor)); controller->message_loop_ = audio_manager->GetMessageLoop(); // Create and open a new audio input stream from the existing @@ -116,14 +122,15 @@ scoped_refptr<AudioInputController> AudioInputController::CreateForStream( const scoped_refptr<base::MessageLoopProxy>& message_loop, EventHandler* event_handler, AudioInputStream* stream, - SyncWriter* sync_writer) { + SyncWriter* sync_writer, + UserInputMonitor* user_input_monitor) { DCHECK(sync_writer); DCHECK(stream); // Create the AudioInputController object and ensure that it runs on // the audio-manager thread. - scoped_refptr<AudioInputController> controller(new AudioInputController( - event_handler, sync_writer)); + scoped_refptr<AudioInputController> controller( + new AudioInputController(event_handler, sync_writer, user_input_monitor)); controller->message_loop_ = message_loop; // TODO(miu): See TODO at top of file. Until that's resolved, we need to @@ -233,6 +240,9 @@ void AudioInputController::DoRecord() { stream_->Start(this); handler_->OnRecording(this); + + if (user_input_monitor_) + user_input_monitor_->AddKeyStrokeListener(this); } void AudioInputController::DoClose() { @@ -251,6 +261,9 @@ void AudioInputController::DoClose() { } state_ = kClosed; + + if (user_input_monitor_) + user_input_monitor_->RemoveKeyStrokeListener(this); } } @@ -320,10 +333,13 @@ void AudioInputController::DoCheckForNoData() { void AudioInputController::OnData(AudioInputStream* stream, const uint8* data, uint32 size, uint32 hardware_delay_bytes, double volume) { + bool key_pressed = false; { base::AutoLock auto_lock(lock_); if (state_ != kRecording) return; + + std::swap(key_pressed, key_pressed_); } // Mark data as active to ensure that the periodic calls to @@ -332,7 +348,7 @@ void AudioInputController::OnData(AudioInputStream* stream, const uint8* data, // Use SyncSocket if we are in a low-latency mode. if (LowLatencyMode()) { - sync_writer_->Write(data, size, volume); + sync_writer_->Write(data, size, volume, key_pressed); sync_writer_->UpdateRecordedBytes(hardware_delay_bytes); return; } @@ -353,8 +369,13 @@ void AudioInputController::OnError(AudioInputStream* stream) { &AudioInputController::DoReportError, this)); } +void AudioInputController::OnKeyStroke() { + base::AutoLock auto_lock(lock_); + key_pressed_ = true; +} + void AudioInputController::DoStopCloseAndClearStream( - base::WaitableEvent *done) { + base::WaitableEvent* done) { DCHECK(message_loop_->BelongsToCurrentThread()); // Allow calling unconditionally and bail if we don't have a stream to close. diff --git a/media/audio/audio_input_controller.h b/media/audio/audio_input_controller.h index 586d477..6be4821 100644 --- a/media/audio/audio_input_controller.h +++ b/media/audio/audio_input_controller.h @@ -16,6 +16,7 @@ #include "base/timer/timer.h" #include "media/audio/audio_io.h" #include "media/audio/audio_manager_base.h" +#include "media/base/user_input_monitor.h" // An AudioInputController controls an AudioInputStream and records data // from this input stream. The two main methods are Record() and Close() and @@ -74,7 +75,8 @@ namespace media { class MEDIA_EXPORT AudioInputController : public base::RefCountedThreadSafe<AudioInputController>, - public AudioInputStream::AudioInputCallback { + public AudioInputStream::AudioInputCallback, + public UserInputMonitor::KeyStrokeListener { public: // An event handler that receives events from the AudioInputController. The // following methods are all called on the audio thread. @@ -102,7 +104,10 @@ class MEDIA_EXPORT AudioInputController // Write certain amount of data from |data|. This method returns // number of written bytes. - virtual uint32 Write(const void* data, uint32 size, double volume) = 0; + virtual uint32 Write(const void* data, + uint32 size, + double volume, + bool key_pressed) = 0; // Close this synchronous writer. virtual void Close() = 0; @@ -110,11 +115,15 @@ class MEDIA_EXPORT AudioInputController // AudioInputController::Create() can use the currently registered Factory // to create the AudioInputController. Factory is intended for testing only. + // |user_input_monitor| is used for typing detection and can be NULL. class Factory { public: - virtual AudioInputController* Create(AudioManager* audio_manager, - EventHandler* event_handler, - AudioParameters params) = 0; + virtual AudioInputController* Create( + AudioManager* audio_manager, + EventHandler* event_handler, + AudioParameters params, + UserInputMonitor* user_input_monitor) = 0; + protected: virtual ~Factory() {} }; @@ -123,11 +132,13 @@ class MEDIA_EXPORT AudioInputController // The audio device will be created on the audio thread, and when that is // done, the event handler will receive an OnCreated() call from that same // thread. |device_id| is the unique ID of the audio device to be opened. + // |user_input_monitor| is used for typing detection and can be NULL. static scoped_refptr<AudioInputController> Create( AudioManager* audio_manager, EventHandler* event_handler, const AudioParameters& params, - const std::string& device_id); + const std::string& device_id, + UserInputMonitor* user_input_monitor); // Sets the factory used by the static method Create(). AudioInputController // does not take ownership of |factory|. A value of NULL results in an @@ -138,25 +149,28 @@ class MEDIA_EXPORT AudioInputController // Factory method for creating an AudioInputController for low-latency mode. // The audio device will be created on the audio thread, and when that is // done, the event handler will receive an OnCreated() call from that same - // thread. + // thread. |user_input_monitor| is used for typing detection and can be NULL. static scoped_refptr<AudioInputController> CreateLowLatency( AudioManager* audio_manager, EventHandler* event_handler, const AudioParameters& params, const std::string& device_id, // External synchronous writer for audio controller. - SyncWriter* sync_writer); + SyncWriter* sync_writer, + UserInputMonitor* user_input_monitor); // Factory method for creating an AudioInputController for low-latency mode, // taking ownership of |stream|. The stream will be opened on the audio // thread, and when that is done, the event handler will receive an - // OnCreated() call from that same thread. + // OnCreated() call from that same thread. |user_input_monitor| is used for + // typing detection and can be NULL. static scoped_refptr<AudioInputController> CreateForStream( const scoped_refptr<base::MessageLoopProxy>& message_loop, EventHandler* event_handler, AudioInputStream* stream, // External synchronous writer for audio controller. - SyncWriter* sync_writer); + SyncWriter* sync_writer, + UserInputMonitor* user_input_monitor); // Starts recording using the created audio input stream. // This method is called on the creator thread. @@ -189,6 +203,9 @@ class MEDIA_EXPORT AudioInputController bool LowLatencyMode() const { return sync_writer_ != NULL; } + // Impl of KeyStrokeListener. + virtual void OnKeyStroke() OVERRIDE; + protected: friend class base::RefCountedThreadSafe<AudioInputController>; @@ -201,7 +218,9 @@ class MEDIA_EXPORT AudioInputController kError }; - AudioInputController(EventHandler* handler, SyncWriter* sync_writer); + AudioInputController(EventHandler* handler, + SyncWriter* sync_writer, + UserInputMonitor* user_input_monitor); virtual ~AudioInputController(); // Methods called on the audio thread (owned by the AudioManager). @@ -266,6 +285,11 @@ class MEDIA_EXPORT AudioInputController double max_volume_; + UserInputMonitor* user_input_monitor_; + + // True if any key has been pressed after the last OnData call. + bool key_pressed_; + DISALLOW_COPY_AND_ASSIGN(AudioInputController); }; diff --git a/media/audio/audio_input_controller_unittest.cc b/media/audio/audio_input_controller_unittest.cc index b96ef3a..6388cbf 100644 --- a/media/audio/audio_input_controller_unittest.cc +++ b/media/audio/audio_input_controller_unittest.cc @@ -83,9 +83,13 @@ TEST_F(AudioInputControllerTest, CreateAndClose) { scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); AudioParameters params(AudioParameters::AUDIO_FAKE, kChannelLayout, kSampleRate, kBitsPerSample, kSamplesPerPacket); + scoped_refptr<AudioInputController> controller = - AudioInputController::Create(audio_manager.get(), &event_handler, params, - AudioManagerBase::kDefaultDeviceId); + AudioInputController::Create(audio_manager.get(), + &event_handler, + params, + AudioManagerBase::kDefaultDeviceId, + NULL); ASSERT_TRUE(controller.get()); // Wait for OnCreated() to fire. @@ -120,8 +124,11 @@ TEST_F(AudioInputControllerTest, RecordAndClose) { // Creating the AudioInputController should render an OnCreated() call. scoped_refptr<AudioInputController> controller = - AudioInputController::Create(audio_manager.get(), &event_handler, params, - AudioManagerBase::kDefaultDeviceId); + AudioInputController::Create(audio_manager.get(), + &event_handler, + params, + AudioManagerBase::kDefaultDeviceId, + NULL); ASSERT_TRUE(controller.get()); // Start recording and trigger one OnRecording() call. @@ -167,8 +174,11 @@ TEST_F(AudioInputControllerTest, RecordAndError) { // Creating the AudioInputController should render an OnCreated() call. scoped_refptr<AudioInputController> controller = - AudioInputController::Create(audio_manager.get(), &event_handler, params, - AudioManagerBase::kDefaultDeviceId); + AudioInputController::Create(audio_manager.get(), + &event_handler, + params, + AudioManagerBase::kDefaultDeviceId, + NULL); ASSERT_TRUE(controller.get()); // Start recording and trigger one OnRecording() call. @@ -196,11 +206,17 @@ TEST_F(AudioInputControllerTest, SamplesPerPacketTooLarge) { .Times(Exactly(0)); scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - AudioParameters params(AudioParameters::AUDIO_FAKE, kChannelLayout, - kSampleRate, kBitsPerSample, kSamplesPerPacket * 1000); + AudioParameters params(AudioParameters::AUDIO_FAKE, + kChannelLayout, + kSampleRate, + kBitsPerSample, + kSamplesPerPacket * 1000); scoped_refptr<AudioInputController> controller = - AudioInputController::Create(audio_manager.get(), &event_handler, params, - AudioManagerBase::kDefaultDeviceId); + AudioInputController::Create(audio_manager.get(), + &event_handler, + params, + AudioManagerBase::kDefaultDeviceId, + NULL); ASSERT_FALSE(controller.get()); } @@ -216,11 +232,17 @@ TEST_F(AudioInputControllerTest, CloseTwice) { .Times(Exactly(1)); scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - AudioParameters params(AudioParameters::AUDIO_FAKE, kChannelLayout, - kSampleRate, kBitsPerSample, kSamplesPerPacket); + AudioParameters params(AudioParameters::AUDIO_FAKE, + kChannelLayout, + kSampleRate, + kBitsPerSample, + kSamplesPerPacket); scoped_refptr<AudioInputController> controller = - AudioInputController::Create(audio_manager.get(), &event_handler, params, - AudioManagerBase::kDefaultDeviceId); + AudioInputController::Create(audio_manager.get(), + &event_handler, + params, + AudioManagerBase::kDefaultDeviceId, + NULL); ASSERT_TRUE(controller.get()); controller->Record(); diff --git a/media/audio/audio_input_device.cc b/media/audio/audio_input_device.cc index 87fd571..5477be6 100644 --- a/media/audio/audio_input_device.cc +++ b/media/audio/audio_input_device.cc @@ -294,6 +294,7 @@ void AudioInputDevice::AudioThreadCallback::Process(int pending_data) { DCHECK_EQ(buffer->params.size, segment_length_ - sizeof(AudioInputBufferParameters)); double volume = buffer->params.volume; + bool key_pressed = buffer->params.key_pressed; int audio_delay_milliseconds = pending_data / bytes_per_ms_; int16* memory = reinterpret_cast<int16*>(&buffer->audio[0]); @@ -308,8 +309,8 @@ void AudioInputDevice::AudioThreadCallback::Process(int pending_data) { // 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); + capture_callback_->Capture( + audio_bus_.get(), audio_delay_milliseconds, volume, key_pressed); } } // namespace media diff --git a/media/audio/audio_parameters.h b/media/audio/audio_parameters.h index 2817cd2..bc629a7 100644 --- a/media/audio/audio_parameters.h +++ b/media/audio/audio_parameters.h @@ -14,6 +14,7 @@ namespace media { struct MEDIA_EXPORT AudioInputBufferParameters { double volume; uint32 size; + bool key_pressed; }; // Use a struct-in-struct approach to ensure that we can calculate the required diff --git a/media/audio/test_audio_input_controller_factory.cc b/media/audio/test_audio_input_controller_factory.cc index 64bfb9f..d146231 100644 --- a/media/audio/test_audio_input_controller_factory.cc +++ b/media/audio/test_audio_input_controller_factory.cc @@ -12,8 +12,9 @@ TestAudioInputController::TestAudioInputController( AudioManager* audio_manager, const AudioParameters& audio_parameters, EventHandler* event_handler, - SyncWriter* sync_writer) - : AudioInputController(event_handler, sync_writer), + SyncWriter* sync_writer, + UserInputMonitor* user_input_monitor) + : AudioInputController(event_handler, sync_writer, user_input_monitor), audio_parameters_(audio_parameters), factory_(factory), event_handler_(event_handler) { @@ -48,10 +49,11 @@ TestAudioInputControllerFactory::~TestAudioInputControllerFactory() { AudioInputController* TestAudioInputControllerFactory::Create( AudioManager* audio_manager, AudioInputController::EventHandler* event_handler, - AudioParameters params) { + AudioParameters params, + UserInputMonitor* user_input_monitor) { DCHECK(!controller_); // Only one test instance managed at a time. - controller_ = new TestAudioInputController(this, audio_manager, params, - event_handler, NULL); + controller_ = new TestAudioInputController( + this, audio_manager, params, event_handler, NULL, user_input_monitor); return controller_; } diff --git a/media/audio/test_audio_input_controller_factory.h b/media/audio/test_audio_input_controller_factory.h index 0a17947..4968c01 100644 --- a/media/audio/test_audio_input_controller_factory.h +++ b/media/audio/test_audio_input_controller_factory.h @@ -10,6 +10,7 @@ namespace media { +class UserInputMonitor; class TestAudioInputControllerFactory; // TestAudioInputController and TestAudioInputControllerFactory are used for @@ -56,7 +57,8 @@ class TestAudioInputController : public AudioInputController { AudioManager* audio_manager, const AudioParameters& audio_parameters, EventHandler* event_handler, - SyncWriter* sync_writer); + SyncWriter* sync_writer, + UserInputMonitor* user_input_monitor); // Returns the event handler installed on the AudioInputController. EventHandler* event_handler() const { return event_handler_; } @@ -94,7 +96,8 @@ class TestAudioInputControllerFactory : public AudioInputController::Factory { virtual AudioInputController* Create( AudioManager* audio_manager, AudioInputController::EventHandler* event_handler, - AudioParameters params) OVERRIDE; + AudioParameters params, + UserInputMonitor* user_input_monitor) OVERRIDE; void SetDelegateForTests(TestAudioInputControllerDelegate* delegate); diff --git a/media/base/audio_capturer_source.h b/media/base/audio_capturer_source.h index deae5e2..b584f8a 100644 --- a/media/base/audio_capturer_source.h +++ b/media/base/audio_capturer_source.h @@ -26,7 +26,8 @@ class AudioCapturerSource // Callback to deliver the captured data from the OS. virtual void Capture(AudioBus* audio_source, int audio_delay_milliseconds, - double volume) = 0; + double volume, + bool key_pressed) = 0; // Signals an error has occurred. virtual void OnCaptureError() = 0; diff --git a/media/base/user_input_monitor.cc b/media/base/user_input_monitor.cc new file mode 100644 index 0000000..18b4c80 --- /dev/null +++ b/media/base/user_input_monitor.cc @@ -0,0 +1,87 @@ +// Copyright 2013 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/base/user_input_monitor.h" + +#include "third_party/skia/include/core/SkPoint.h" + +namespace media { + +#ifdef DISABLE_USER_INPUT_MONITOR +scoped_ptr<UserInputMonitor> UserInputMonitor::Create( + const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner) { + return scoped_ptr<UserInputMonitor>(); +} +#endif // DISABLE_USER_INPUT_MONITOR + +UserInputMonitor::~UserInputMonitor() { + DCHECK(!monitoring_mouse_); + DCHECK(!monitoring_keyboard_); +} + +void UserInputMonitor::AddMouseListener(MouseEventListener* listener) { + base::AutoLock auto_lock(lock_); + mouse_listeners_.AddObserver(listener); + if (!monitoring_mouse_) { + StartMouseMonitoring(); + monitoring_mouse_ = true; + DVLOG(2) << "Started mouse monitoring."; + } +} +void UserInputMonitor::RemoveMouseListener(MouseEventListener* listener) { + base::AutoLock auto_lock(lock_); + mouse_listeners_.RemoveObserver(listener); + if (mouse_listeners_.size() == 0) { + StopMouseMonitoring(); + monitoring_mouse_ = false; + DVLOG(2) << "Stopped mouse monitoring."; + } +} +void UserInputMonitor::AddKeyStrokeListener(KeyStrokeListener* listener) { + base::AutoLock auto_lock(lock_); + key_stroke_listeners_.AddObserver(listener); + if (!monitoring_keyboard_) { + StartKeyboardMonitoring(); + monitoring_keyboard_ = true; + DVLOG(2) << "Started keyboard monitoring."; + } +} +void UserInputMonitor::RemoveKeyStrokeListener(KeyStrokeListener* listener) { + base::AutoLock auto_lock(lock_); + key_stroke_listeners_.RemoveObserver(listener); + if (key_stroke_listeners_.size() == 0) { + StopKeyboardMonitoring(); + monitoring_keyboard_ = false; + DVLOG(2) << "Stopped keyboard monitoring."; + } +} + +UserInputMonitor::UserInputMonitor() + : monitoring_mouse_(false), monitoring_keyboard_(false) {} + +void UserInputMonitor::OnMouseEvent(const SkIPoint& position) { + base::AutoLock auto_lock(lock_); + FOR_EACH_OBSERVER( + MouseEventListener, mouse_listeners_, OnMouseMoved(position)); +} + +void UserInputMonitor::OnKeyboardEvent(ui::EventType event, + ui::KeyboardCode key_code) { + base::AutoLock auto_lock(lock_); + // Updates the pressed keys and maybe notifies the key_stroke_listeners_. + if (event == ui::ET_KEY_PRESSED) { + if (pressed_keys_.find(key_code) != pressed_keys_.end()) + return; + pressed_keys_.insert(key_code); + DVLOG(6) << "Key stroke detected."; + FOR_EACH_OBSERVER(KeyStrokeListener, key_stroke_listeners_, OnKeyStroke()); + } else { + DCHECK_EQ(ui::ET_KEY_RELEASED, event); + DCHECK(pressed_keys_.find(key_code) != pressed_keys_.end()); + pressed_keys_.erase(key_code); + } +} + +} // namespace media diff --git a/media/base/user_input_monitor.h b/media/base/user_input_monitor.h new file mode 100644 index 0000000..9eb82f3 --- /dev/null +++ b/media/base/user_input_monitor.h @@ -0,0 +1,99 @@ +// Copyright 2013 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_BASE_USER_INPUT_MONITOR_H_ +#define MEDIA_BASE_USER_INPUT_MONITOR_H_ + +#include <set> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "base/synchronization/lock.h" +#include "media/base/media_export.h" +#include "ui/base/events/event_constants.h" +#include "ui/base/keycodes/keyboard_codes.h" + +struct SkIPoint; + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace media { + +// Monitors and notifies about mouse movements and keyboard events. +// Thread safe. The thread on which the listenters are called is not guaranteed. +// The callers should not perform expensive/blocking tasks in the callback since +// it might be called on the browser UI/IO threads. +class MEDIA_EXPORT UserInputMonitor { + public: + // The interface to receive mouse movement events. + class MEDIA_EXPORT MouseEventListener { + public: + // |position| is the new mouse position. + virtual void OnMouseMoved(const SkIPoint& position) = 0; + + protected: + virtual ~MouseEventListener() {} + }; + // The interface to receive key stroke events. + class MEDIA_EXPORT KeyStrokeListener { + public: + // Called when any key is pressed. Called only once until the key is + // released, i.e. holding down a key for a long period will generate one + // callback just when the key is pressed down. + virtual void OnKeyStroke() = 0; + + protected: + virtual ~KeyStrokeListener() {} + }; + + virtual ~UserInputMonitor(); + + // Creates a platform-specific instance of UserInputMonitor. + // |io_task_runner| is the task runner for an IO thread. + // |ui_task_runner| is the task runner for a UI thread. + static scoped_ptr<UserInputMonitor> Create( + const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner); + + // The same |listener| should only be added once. + // The clients should make sure to call Remove*Listener before |listener| is + // destroyed. + void AddMouseListener(MouseEventListener* listener); + void RemoveMouseListener(MouseEventListener* listener); + void AddKeyStrokeListener(KeyStrokeListener* listener); + void RemoveKeyStrokeListener(KeyStrokeListener* listener); + + protected: + UserInputMonitor(); + + // Called by the platform-specific sub-classes to propagate the events to the + // listeners. + void OnMouseEvent(const SkIPoint& position); + void OnKeyboardEvent(ui::EventType event, ui::KeyboardCode key_code); + + private: + virtual void StartMouseMonitoring() = 0; + virtual void StopMouseMonitoring() = 0; + virtual void StartKeyboardMonitoring() = 0; + virtual void StopKeyboardMonitoring() = 0; + + base::Lock lock_; + ObserverList<MouseEventListener, true> mouse_listeners_; + ObserverList<KeyStrokeListener, true> key_stroke_listeners_; + bool monitoring_mouse_; + bool monitoring_keyboard_; + // The set of keys currently held down. Used for convering raw keyboard events + // into KeyStrokeListener callbacks. + std::set<ui::KeyboardCode> pressed_keys_; + + DISALLOW_COPY_AND_ASSIGN(UserInputMonitor); +}; + +} // namespace media + +#endif // MEDIA_BASE_USER_INPUT_MONITOR_H_ diff --git a/media/base/user_input_monitor_linux.cc b/media/base/user_input_monitor_linux.cc new file mode 100644 index 0000000..ee1b774 --- /dev/null +++ b/media/base/user_input_monitor_linux.cc @@ -0,0 +1,338 @@ +// Copyright 2013 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/base/user_input_monitor.h" + +#include <sys/select.h> +#include <unistd.h> +#define XK_MISCELLANY +#include <X11/keysymdef.h> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_pump_libevent.h" +#include "base/posix/eintr_wrapper.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/non_thread_safe.h" +#include "third_party/skia/include/core/SkPoint.h" +#include "ui/base/keycodes/keyboard_code_conversion_x.h" + +// These includes need to be later than dictated by the style guide due to +// Xlib header pollution, specifically the min, max, and Status macros. +#include <X11/XKBlib.h> +#include <X11/Xlibint.h> +#include <X11/extensions/record.h> + +namespace media { + +namespace { + +class UserInputMonitorLinux : public base::NonThreadSafe, + public UserInputMonitor { + public: + UserInputMonitorLinux( + const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner); + virtual ~UserInputMonitorLinux(); + + private: + enum EventType { + MOUSE_EVENT, + KEYBOARD_EVENT + }; + + // The actual implementation resides in UserInputMonitorLinux::Core class. + // Must be called on the io_task_runner thread. + class Core : public base::RefCountedThreadSafe<Core>, + public base::MessagePumpLibevent::Watcher { + public: + typedef const base::Callback<void(const SkIPoint&)> MouseCallback; + typedef base::Callback<void(ui::EventType event, ui::KeyboardCode key_code)> + KeyboardCallback; + Core(const MouseCallback& mouse_callback, + const KeyboardCallback& keyboard_callback); + + void StartMonitor(EventType type); + void StopMonitor(EventType type); + + private: + friend class base::RefCountedThreadSafe<Core>; + virtual ~Core(); + + // base::MessagePumpLibevent::Watcher interface. + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + // Processes key and mouse events. + void ProcessXEvent(xEvent* event); + static void ProcessReply(XPointer self, XRecordInterceptData* data); + + // Used to receive base::MessagePumpLibevent::Watcher events. + base::MessagePumpLibevent::FileDescriptorWatcher controller_; + + Display* display_; + Display* x_record_display_; + XRecordRange* x_record_range_[2]; + XRecordContext x_record_context_; + base::Callback<void(const SkIPoint&)> mouse_callback_; + base::Callback<void(ui::EventType event, ui::KeyboardCode key_code)> + keyboard_callback_; + + DISALLOW_COPY_AND_ASSIGN(Core); + }; + + virtual void StartMouseMonitoring() OVERRIDE; + virtual void StopMouseMonitoring() OVERRIDE; + virtual void StartKeyboardMonitoring() OVERRIDE; + virtual void StopKeyboardMonitoring() OVERRIDE; + + void OnMouseEvent(const SkIPoint& position); + void OnKeyboardEvent(ui::EventType event, ui::KeyboardCode key_code); + + // Task runner on which X Window events are received. + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + scoped_refptr<Core> core_; + + DISALLOW_COPY_AND_ASSIGN(UserInputMonitorLinux); +}; + +UserInputMonitorLinux::UserInputMonitorLinux( + const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner) + : io_task_runner_(io_task_runner), + core_(new Core(base::Bind(&UserInputMonitorLinux::OnMouseEvent, + base::Unretained(this)), + base::Bind(&UserInputMonitorLinux::OnKeyboardEvent, + base::Unretained(this)))) {} + +UserInputMonitorLinux::~UserInputMonitorLinux() {} + +void UserInputMonitorLinux::StartMouseMonitoring() { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&Core::StartMonitor, core_.get(), MOUSE_EVENT)); +} + +void UserInputMonitorLinux::StopMouseMonitoring() { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&Core::StopMonitor, core_.get(), MOUSE_EVENT)); +} + +void UserInputMonitorLinux::StartKeyboardMonitoring() { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&Core::StartMonitor, core_.get(), KEYBOARD_EVENT)); +} + +void UserInputMonitorLinux::StopKeyboardMonitoring() { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&Core::StopMonitor, core_.get(), KEYBOARD_EVENT)); +} + +void UserInputMonitorLinux::OnMouseEvent(const SkIPoint& position) { + UserInputMonitor::OnMouseEvent(position); +} + +void UserInputMonitorLinux::OnKeyboardEvent(ui::EventType event, + ui::KeyboardCode key_code) { + UserInputMonitor::OnKeyboardEvent(event, key_code); +} + +UserInputMonitorLinux::Core::Core(const MouseCallback& mouse_callback, + const KeyboardCallback& keyboard_callback) + : display_(NULL), + x_record_display_(NULL), + x_record_context_(0), + mouse_callback_(mouse_callback), + keyboard_callback_(keyboard_callback) { + x_record_range_[0] = NULL; + x_record_range_[1] = NULL; +} + +UserInputMonitorLinux::Core::~Core() { + DCHECK(!display_); + DCHECK(!x_record_display_); + DCHECK(!x_record_range_[0]); + DCHECK(!x_record_range_[1]); + DCHECK(!x_record_context_); +} + +void UserInputMonitorLinux::Core::StartMonitor(EventType type) { + DCHECK(base::MessageLoopForIO::current()); + // TODO(jamiewalch): We should pass the display in. At that point, since + // XRecord needs a private connection to the X Server for its data channel + // and both channels are used from a separate thread, we'll need to duplicate + // them with something like the following: + // XOpenDisplay(DisplayString(display)); + if (!display_) + display_ = XOpenDisplay(NULL); + + if (!x_record_display_) + x_record_display_ = XOpenDisplay(NULL); + + if (!display_ || !x_record_display_) { + LOG(ERROR) << "Couldn't open X display"; + return; + } + + int xr_opcode, xr_event, xr_error; + if (!XQueryExtension(display_, "RECORD", &xr_opcode, &xr_event, &xr_error)) { + LOG(ERROR) << "X Record extension not available."; + return; + } + + if (!x_record_range_[type]) + x_record_range_[type] = XRecordAllocRange(); + + if (!x_record_range_[type]) { + LOG(ERROR) << "XRecordAllocRange failed."; + return; + } + + if (type == MOUSE_EVENT) { + x_record_range_[type]->device_events.first = MotionNotify; + x_record_range_[type]->device_events.last = MotionNotify; + } else { + DCHECK_EQ(KEYBOARD_EVENT, type); + x_record_range_[type]->device_events.first = KeyPress; + x_record_range_[type]->device_events.last = KeyRelease; + } + + if (x_record_context_) { + XRecordDisableContext(display_, x_record_context_); + XFlush(display_); + XRecordFreeContext(x_record_display_, x_record_context_); + x_record_context_ = 0; + } + XRecordRange** record_range_to_use = + (x_record_range_[0] && x_record_range_[1]) ? x_record_range_ + : &x_record_range_[type]; + int number_of_ranges = (x_record_range_[0] && x_record_range_[1]) ? 2 : 1; + + XRecordClientSpec client_spec = XRecordAllClients; + x_record_context_ = XRecordCreateContext(x_record_display_, + 0, + &client_spec, + 1, + record_range_to_use, + number_of_ranges); + if (!x_record_context_) { + LOG(ERROR) << "XRecordCreateContext failed."; + return; + } + + if (!XRecordEnableContextAsync(x_record_display_, + x_record_context_, + &Core::ProcessReply, + reinterpret_cast<XPointer>(this))) { + LOG(ERROR) << "XRecordEnableContextAsync failed."; + return; + } + + if (!x_record_range_[0] || !x_record_range_[1]) { + // Register OnFileCanReadWithoutBlocking() to be called every time there is + // something to read from |x_record_display_|. + base::MessageLoopForIO* message_loop = base::MessageLoopForIO::current(); + int result = + message_loop->WatchFileDescriptor(ConnectionNumber(x_record_display_), + true, + base::MessageLoopForIO::WATCH_READ, + &controller_, + this); + if (!result) { + LOG(ERROR) << "Failed to create X record task."; + return; + } + } + + // Fetch pending events if any. + OnFileCanReadWithoutBlocking(ConnectionNumber(x_record_display_)); +} + +void UserInputMonitorLinux::Core::StopMonitor(EventType type) { + DCHECK(base::MessageLoopForIO::current()); + + if (x_record_range_[type]) { + XFree(x_record_range_[type]); + x_record_range_[type] = NULL; + } + if (x_record_range_[0] || x_record_range_[1]) + return; + + // Context must be disabled via the control channel because we can't send + // any X protocol traffic over the data channel while it's recording. + if (x_record_context_) { + XRecordDisableContext(display_, x_record_context_); + XFlush(display_); + XRecordFreeContext(x_record_display_, x_record_context_); + x_record_context_ = 0; + + controller_.StopWatchingFileDescriptor(); + if (x_record_display_) { + XCloseDisplay(x_record_display_); + x_record_display_ = NULL; + } + if (display_) { + XCloseDisplay(display_); + display_ = NULL; + } + } +} + +void UserInputMonitorLinux::Core::OnFileCanReadWithoutBlocking(int fd) { + DCHECK(base::MessageLoopForIO::current()); + XEvent event; + // Fetch pending events if any. + while (XPending(x_record_display_)) { + XNextEvent(x_record_display_, &event); + } +} + +void UserInputMonitorLinux::Core::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +void UserInputMonitorLinux::Core::ProcessXEvent(xEvent* event) { + if (event->u.u.type == MotionNotify) { + SkIPoint position(SkIPoint::Make(event->u.keyButtonPointer.rootX, + event->u.keyButtonPointer.rootY)); + mouse_callback_.Run(position); + } else { + ui::EventType type; + if (event->u.u.type == KeyPress) { + type = ui::ET_KEY_PRESSED; + } else if (event->u.u.type == KeyRelease) { + type = ui::ET_KEY_RELEASED; + } else { + NOTREACHED(); + } + + KeySym key_sym = XkbKeycodeToKeysym(display_, event->u.u.detail, 0, 0); + ui::KeyboardCode key_code = ui::KeyboardCodeFromXKeysym(key_sym); + keyboard_callback_.Run(type, key_code); + } +} + +// static +void UserInputMonitorLinux::Core::ProcessReply(XPointer self, + XRecordInterceptData* data) { + if (data->category == XRecordFromServer) { + xEvent* event = reinterpret_cast<xEvent*>(data->data); + reinterpret_cast<Core*>(self)->ProcessXEvent(event); + } + XRecordFreeData(data); +} + +} // namespace + +scoped_ptr<UserInputMonitor> UserInputMonitor::Create( + const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner) { + return scoped_ptr<UserInputMonitor>( + new UserInputMonitorLinux(io_task_runner)); +} + +} // namespace media diff --git a/media/base/user_input_monitor_mac.mm b/media/base/user_input_monitor_mac.mm new file mode 100644 index 0000000..4ffad42 --- /dev/null +++ b/media/base/user_input_monitor_mac.mm @@ -0,0 +1,16 @@ +// Copyright 2013 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/base/user_input_monitor.h" + +namespace media { + +// TODO(jiayl): add the implementation. +scoped_ptr<UserInputMonitor> UserInputMonitor::Create( + const scoped_refptr<base::SingleThreadTaskRunner>& input_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner) { + return scoped_ptr<UserInputMonitor>(); +} + +} // namespace media diff --git a/media/base/user_input_monitor_win.cc b/media/base/user_input_monitor_win.cc new file mode 100644 index 0000000..4ffad42 --- /dev/null +++ b/media/base/user_input_monitor_win.cc @@ -0,0 +1,16 @@ +// Copyright 2013 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/base/user_input_monitor.h" + +namespace media { + +// TODO(jiayl): add the implementation. +scoped_ptr<UserInputMonitor> UserInputMonitor::Create( + const scoped_refptr<base::SingleThreadTaskRunner>& input_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner) { + return scoped_ptr<UserInputMonitor>(); +} + +} // namespace media diff --git a/media/media.gyp b/media/media.gyp index 92642a2..800126b 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -304,6 +304,11 @@ 'base/stream_parser_buffer.cc', 'base/stream_parser_buffer.h', 'base/text_track.h', + 'base/user_input_monitor.cc', + 'base/user_input_monitor.h', + 'base/user_input_monitor_linux.cc', + 'base/user_input_monitor_mac.mm', + 'base/user_input_monitor_win.cc', 'base/video_decoder.cc', 'base/video_decoder.h', 'base/video_decoder_config.cc', @@ -541,6 +546,7 @@ ['include', '^base/media\\.cc$'], ['include', '^base/media_stub\\.cc$'], ['include', '^base/media_switches\\.'], + ['include', '^base/user_input_monitor\\.'], ['include', '^base/vector_math\\.'], ], 'link_settings': { @@ -551,6 +557,9 @@ '$(SDKROOT)/System/Library/Frameworks/CoreMIDI.framework', ], }, + 'defines': [ + 'DISABLE_USER_INPUT_MONITOR', + ], }], ['OS=="android"', { 'link_settings': { @@ -578,6 +587,9 @@ ], }], ], + 'defines': [ + 'DISABLE_USER_INPUT_MONITOR', + ], }], # A simple WebM encoder for animated avatars on ChromeOS. ['chromeos==1', { @@ -591,6 +603,15 @@ 'webm/chromeos/webm_encoder.cc', 'webm/chromeos/webm_encoder.h', ], + 'defines': [ + # TODO(jiayl): figure out why MediaStreamInfoBarTest. + # DenyingCameraDoesNotCauseStickyDenyForMics fails on ChromeOS and + # remove this. + 'DISABLE_USER_INPUT_MONITOR', + ], + 'sources!': [ + 'base/user_input_monitor_linux.cc', + ], }], ['use_alsa==1', { 'link_settings': { @@ -626,8 +647,16 @@ '-lXdamage', '-lXext', '-lXfixes', + '-lXtst', ], }, + }, { # else: use_x11==0 + 'sources!': [ + 'base/user_input_monitor_linux.cc', + ], + 'defines': [ + 'DISABLE_USER_INPUT_MONITOR', + ], }], ['use_cras==1', { 'cflags': [ |