OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/renderer_host/media/audio_input_device_manager.h" | 5 #include "content/browser/renderer_host/media/audio_input_device_manager.h" |
6 | 6 |
7 #include <memory> | 7 #include <memory> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
11 #include "base/memory/ptr_util.h" | |
11 #include "base/metrics/histogram_macros.h" | 12 #include "base/metrics/histogram_macros.h" |
12 #include "build/build_config.h" | 13 #include "build/build_config.h" |
13 #include "content/public/browser/browser_thread.h" | 14 #include "content/public/browser/browser_thread.h" |
14 #include "content/public/common/media_stream_request.h" | 15 #include "content/public/common/media_stream_request.h" |
15 #include "media/audio/audio_input_ipc.h" | 16 #include "media/audio/audio_input_ipc.h" |
16 #include "media/audio/audio_manager_base.h" | 17 #include "media/audio/audio_system.h" |
17 #include "media/base/audio_parameters.h" | 18 #include "media/base/audio_parameters.h" |
19 #include "media/base/bind_to_current_loop.h" | |
18 #include "media/base/channel_layout.h" | 20 #include "media/base/channel_layout.h" |
19 #include "media/base/media_switches.h" | 21 #include "media/base/media_switches.h" |
20 | 22 |
21 #if defined(OS_CHROMEOS) | 23 #if defined(OS_CHROMEOS) |
22 #include "chromeos/audio/cras_audio_handler.h" | 24 #include "chromeos/audio/cras_audio_handler.h" |
23 #endif | 25 #endif |
24 | 26 |
25 namespace content { | 27 namespace content { |
26 | 28 |
27 const int AudioInputDeviceManager::kFakeOpenSessionId = 1; | 29 const int AudioInputDeviceManager::kFakeOpenSessionId = 1; |
28 | 30 |
29 namespace { | 31 namespace { |
30 // Starting id for the first capture session. | 32 // Starting id for the first capture session. |
31 const int kFirstSessionId = AudioInputDeviceManager::kFakeOpenSessionId + 1; | 33 const int kFirstSessionId = AudioInputDeviceManager::kFakeOpenSessionId + 1; |
34 | |
35 // The object of this class lives on AudioSystem thread and at each given | |
36 // moment is owned by a callback it posted to that thread. | |
37 class DeviceOpener { | |
38 public: | |
39 using OnOpenedCallback = base::Callback<void(const StreamDeviceInfo&)>; | |
40 | |
41 ~DeviceOpener(); | |
42 | |
43 // Wwill reply with |on_opened_cb| on the thread Open() is called on. | |
Max Morin
2017/03/22 17:46:24
Typo Wwill
| |
44 static void Open(media::AudioSystem* audio_system, | |
45 const MediaStreamDevice& device, | |
46 int session_id, | |
47 OnOpenedCallback on_opened_cb); | |
48 | |
49 protected: | |
50 DeviceOpener(media::AudioSystem* audio_system, | |
51 const MediaStreamDevice& device, | |
52 int session_id, | |
53 OnOpenedCallback on_opened_cb); | |
54 | |
55 private: | |
56 void DoOpen(std::unique_ptr<DeviceOpener> owned_this); | |
57 void OpenWithMatchedOutputId(std::unique_ptr<DeviceOpener> owned_this, | |
58 const std::string& matched_output_id); | |
59 void OpenWithInputParams(std::unique_ptr<DeviceOpener> owned_this, | |
60 const media::AudioParameters& params); | |
61 void OpenWithOutputParams(std::unique_ptr<DeviceOpener> owned_this, | |
62 const media::AudioParameters& params); | |
63 void ReplyOpened(); | |
64 | |
65 base::WeakPtr<media::AudioSystem> audio_system_; | |
66 base::TimeTicks start_time_; | |
67 OnOpenedCallback on_opened_cb_; | |
68 StreamDeviceInfo info_; | |
69 | |
70 DISALLOW_COPY_AND_ASSIGN(DeviceOpener); | |
71 }; | |
72 | |
73 DeviceOpener::DeviceOpener(media::AudioSystem* audio_system, | |
74 const MediaStreamDevice& device, | |
75 int session_id, | |
76 OnOpenedCallback on_opened_cb) | |
77 : audio_system_(audio_system->GetWeakPtr()), | |
78 on_opened_cb_(std::move(on_opened_cb)), | |
79 info_(device.type, device.name, device.id, 0, 0, 0) { | |
80 info_.session_id = session_id; | |
32 } | 81 } |
33 | 82 |
83 DeviceOpener::~DeviceOpener() { | |
84 if (!audio_system_) | |
85 return; | |
86 DCHECK(audio_system_->GetTaskRunner()->BelongsToCurrentThread()); | |
87 | |
88 // Do not ReplyOpened() here, since destructor may be called on message | |
89 // loop destruction before callback runs. | |
90 | |
91 DCHECK(start_time_ == base::TimeTicks() || | |
92 (base::TimeTicks::Now() - start_time_).InSeconds() < 30) | |
93 << "DeviceOpener hang"; | |
94 } | |
95 | |
96 // static | |
97 void DeviceOpener::Open(media::AudioSystem* audio_system, | |
98 const MediaStreamDevice& device, | |
99 int session_id, | |
100 OnOpenedCallback on_opened_cb) { | |
101 DCHECK(audio_system); | |
102 | |
103 std::unique_ptr<DeviceOpener> opener( | |
104 new DeviceOpener(audio_system, device, session_id, | |
105 media::BindToCurrentLoop(on_opened_cb))); | |
106 | |
107 audio_system->GetTaskRunner()->PostTask( | |
108 FROM_HERE, | |
109 base::Bind(&DeviceOpener::DoOpen, base::Unretained(opener.get()), | |
110 base::Passed(&opener))); | |
111 } | |
112 | |
113 void DeviceOpener::DoOpen(std::unique_ptr<DeviceOpener> owned_this) { | |
114 if (!audio_system_) | |
115 return; | |
116 DCHECK(audio_system_->GetTaskRunner()->BelongsToCurrentThread()); | |
117 | |
118 start_time_ = base::TimeTicks::Now(); | |
119 | |
120 audio_system_->GetAssociatedOutputDeviceID( | |
121 info_.device.id, | |
122 base::Bind(&DeviceOpener::OpenWithMatchedOutputId, base::Unretained(this), | |
123 base::Passed(&owned_this))); | |
124 } | |
125 | |
126 void DeviceOpener::OpenWithMatchedOutputId( | |
127 std::unique_ptr<DeviceOpener> owned_this, | |
128 const std::string& matched_output_id) { | |
129 if (!audio_system_) | |
130 return; | |
131 DCHECK(audio_system_->GetTaskRunner()->BelongsToCurrentThread()); | |
132 | |
133 info_.device.matched_output_device_id = matched_output_id; | |
134 | |
135 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( | |
136 switches::kUseFakeDeviceForMediaStream)) { | |
137 audio_system_->GetInputStreamParameters( | |
138 info_.device.id, | |
139 base::Bind(&DeviceOpener::OpenWithInputParams, base::Unretained(this), | |
140 base::Passed(&owned_this))); | |
141 return; | |
142 } | |
143 | |
144 // Don't need to query the hardware information if using fake device. | |
145 info_.device.input.sample_rate = 44100; | |
146 info_.device.input.channel_layout = media::CHANNEL_LAYOUT_STEREO; | |
147 if (!info_.device.matched_output_device_id.empty()) { | |
148 info_.device.matched_output.sample_rate = 44100; | |
149 info_.device.matched_output.channel_layout = media::CHANNEL_LAYOUT_STEREO; | |
150 } | |
151 ReplyOpened(); | |
152 } | |
153 | |
154 void DeviceOpener::OpenWithInputParams(std::unique_ptr<DeviceOpener> owned_this, | |
155 const media::AudioParameters& params) { | |
156 if (!audio_system_) | |
157 return; | |
158 DCHECK(audio_system_->GetTaskRunner()->BelongsToCurrentThread()); | |
159 | |
160 // TODO(tommi): As is, we hit this code path when device.type is | |
161 // MEDIA_TAB_AUDIO_CAPTURE and the device id is not a device that | |
162 // the AudioManager can know about. This currently does not fail because | |
163 // the implementation of GetInputStreamParameters returns valid parameters | |
164 // by default for invalid devices. That behavior is problematic because it | |
165 // causes other parts of the code to attempt to open truly invalid or | |
166 // missing devices and falling back on alternate devices (and likely fail | |
167 // twice in a row). Tab audio capture should not pass through here and | |
168 // GetInputStreamParameters should return invalid parameters for invalid | |
169 // devices. | |
170 | |
171 // Get the preferred sample rate and channel configuration for the | |
172 // audio device. | |
173 info_.device.input.sample_rate = params.sample_rate(); | |
174 info_.device.input.channel_layout = params.channel_layout(); | |
175 info_.device.input.frames_per_buffer = params.frames_per_buffer(); | |
176 info_.device.input.effects = params.effects(); | |
177 info_.device.input.mic_positions = params.mic_positions(); | |
178 | |
179 if (info_.device.matched_output_device_id.empty()) { | |
180 ReplyOpened(); | |
181 return; | |
182 } | |
183 | |
184 audio_system_->GetOutputStreamParameters( | |
185 info_.device.matched_output_device_id, | |
186 base::Bind(&DeviceOpener::OpenWithOutputParams, base::Unretained(this), | |
187 base::Passed(&owned_this))); | |
188 } | |
189 | |
190 void DeviceOpener::OpenWithOutputParams( | |
191 std::unique_ptr<DeviceOpener> owned_this, | |
192 const media::AudioParameters& params) { | |
193 if (!audio_system_) | |
194 return; | |
195 DCHECK(audio_system_->GetTaskRunner()->BelongsToCurrentThread()); | |
196 | |
197 info_.device.matched_output.sample_rate = params.sample_rate(); | |
198 info_.device.matched_output.channel_layout = params.channel_layout(); | |
199 info_.device.matched_output.frames_per_buffer = params.frames_per_buffer(); | |
200 ReplyOpened(); | |
201 } | |
202 | |
203 void DeviceOpener::ReplyOpened() { | |
204 if (!audio_system_) | |
205 return; | |
206 DCHECK(audio_system_->GetTaskRunner()->BelongsToCurrentThread()); | |
207 | |
208 DCHECK(start_time_ > base::TimeTicks()); | |
209 UMA_HISTOGRAM_TIMES("Media.AudioInputDeviceManager.OpenOnDeviceThreadTime", | |
o1ka
2017/03/22 17:09:33
TODO: Make sure UMA meaning is unchanged. Add UMA
| |
210 base::TimeTicks::Now() - start_time_); | |
211 std::move(on_opened_cb_).Run(info_); | |
212 start_time_ = base::TimeTicks(); | |
213 } | |
214 | |
215 } // namespace | |
216 | |
34 AudioInputDeviceManager::AudioInputDeviceManager( | 217 AudioInputDeviceManager::AudioInputDeviceManager( |
35 media::AudioManager* audio_manager) | 218 media::AudioSystem* audio_system) |
36 : next_capture_session_id_(kFirstSessionId), | 219 : next_capture_session_id_(kFirstSessionId), |
37 #if defined(OS_CHROMEOS) | 220 #if defined(OS_CHROMEOS) |
38 keyboard_mic_streams_count_(0), | 221 keyboard_mic_streams_count_(0), |
39 #endif | 222 #endif |
40 audio_manager_(audio_manager), | 223 audio_system_(audio_system) { |
41 device_task_runner_(audio_manager_->GetTaskRunner()) { | |
42 } | 224 } |
43 | 225 |
44 AudioInputDeviceManager::~AudioInputDeviceManager() { | 226 AudioInputDeviceManager::~AudioInputDeviceManager() {} |
45 } | |
46 | 227 |
47 const StreamDeviceInfo* AudioInputDeviceManager::GetOpenedDeviceInfoById( | 228 const StreamDeviceInfo* AudioInputDeviceManager::GetOpenedDeviceInfoById( |
48 int session_id) { | 229 int session_id) { |
49 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 230 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
50 StreamDeviceList::iterator device = GetDevice(session_id); | 231 StreamDeviceList::iterator device = GetDevice(session_id); |
51 if (device == devices_.end()) | 232 if (device == devices_.end()) |
52 return nullptr; | 233 return nullptr; |
53 | 234 |
54 return &(*device); | 235 return &(*device); |
55 } | 236 } |
56 | 237 |
57 void AudioInputDeviceManager::RegisterListener( | 238 void AudioInputDeviceManager::RegisterListener( |
58 MediaStreamProviderListener* listener) { | 239 MediaStreamProviderListener* listener) { |
59 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 240 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
60 DCHECK(listener); | 241 DCHECK(listener); |
61 DCHECK(device_task_runner_); | |
62 listeners_.AddObserver(listener); | 242 listeners_.AddObserver(listener); |
63 } | 243 } |
64 | 244 |
65 void AudioInputDeviceManager::UnregisterListener( | 245 void AudioInputDeviceManager::UnregisterListener( |
66 MediaStreamProviderListener* listener) { | 246 MediaStreamProviderListener* listener) { |
67 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 247 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
68 DCHECK(listener); | 248 DCHECK(listener); |
69 listeners_.RemoveObserver(listener); | 249 listeners_.RemoveObserver(listener); |
70 } | 250 } |
71 | 251 |
72 int AudioInputDeviceManager::Open(const MediaStreamDevice& device) { | 252 int AudioInputDeviceManager::Open(const MediaStreamDevice& device) { |
73 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 253 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
74 // Generate a new id for this device. | 254 // Generate a new id for this device. |
75 int session_id = next_capture_session_id_++; | 255 int session_id = next_capture_session_id_++; |
76 device_task_runner_->PostTask( | 256 DeviceOpener::Open( |
77 FROM_HERE, | 257 audio_system_, device, session_id, |
78 base::Bind(&AudioInputDeviceManager::OpenOnDeviceThread, | 258 base::Bind(&AudioInputDeviceManager::OpenedOnIOThread, this, session_id)); |
79 this, session_id, device)); | |
80 | 259 |
81 return session_id; | 260 return session_id; |
82 } | 261 } |
83 | 262 |
84 void AudioInputDeviceManager::Close(int session_id) { | 263 void AudioInputDeviceManager::Close(int session_id) { |
85 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 264 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
86 StreamDeviceList::iterator device = GetDevice(session_id); | 265 StreamDeviceList::iterator device = GetDevice(session_id); |
87 if (device == devices_.end()) | 266 if (device == devices_.end()) |
88 return; | 267 return; |
89 const MediaStreamType stream_type = device->device.type; | 268 const MediaStreamType stream_type = device->device.type; |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
128 BrowserThread::UI, | 307 BrowserThread::UI, |
129 FROM_HERE, | 308 FROM_HERE, |
130 base::Bind( | 309 base::Bind( |
131 &AudioInputDeviceManager::SetKeyboardMicStreamActiveOnUIThread, | 310 &AudioInputDeviceManager::SetKeyboardMicStreamActiveOnUIThread, |
132 this, | 311 this, |
133 false)); | 312 false)); |
134 } | 313 } |
135 } | 314 } |
136 #endif | 315 #endif |
137 | 316 |
138 void AudioInputDeviceManager::OpenOnDeviceThread( | |
139 int session_id, | |
140 const MediaStreamDevice& device) { | |
141 SCOPED_UMA_HISTOGRAM_TIMER( | |
142 "Media.AudioInputDeviceManager.OpenOnDeviceThreadTime"); | |
143 DCHECK(IsOnDeviceThread()); | |
144 | |
145 StreamDeviceInfo out(device.type, device.name, device.id, 0, 0, 0); | |
146 out.session_id = session_id; | |
147 | |
148 MediaStreamDevice::AudioDeviceParameters& input_params = out.device.input; | |
149 | |
150 // Add preferred output device information if a matching output device | |
151 // exists. | |
152 out.device.matched_output_device_id = | |
153 audio_manager_->GetAssociatedOutputDeviceID(device.id); | |
154 | |
155 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | |
156 switches::kUseFakeDeviceForMediaStream)) { | |
157 // Don't need to query the hardware information if using fake device. | |
158 input_params.sample_rate = 44100; | |
159 input_params.channel_layout = media::CHANNEL_LAYOUT_STEREO; | |
160 if (!out.device.matched_output_device_id.empty()) { | |
161 out.device.matched_output.sample_rate = 44100; | |
162 out.device.matched_output.channel_layout = media::CHANNEL_LAYOUT_STEREO; | |
163 } | |
164 } else { | |
165 // TODO(tommi): As is, we hit this code path when device.type is | |
166 // MEDIA_TAB_AUDIO_CAPTURE and the device id is not a device that | |
167 // the AudioManager can know about. This currently does not fail because | |
168 // the implementation of GetInputStreamParameters returns valid parameters | |
169 // by default for invalid devices. That behavior is problematic because it | |
170 // causes other parts of the code to attempt to open truly invalid or | |
171 // missing devices and falling back on alternate devices (and likely fail | |
172 // twice in a row). Tab audio capture should not pass through here and | |
173 // GetInputStreamParameters should return invalid parameters for invalid | |
174 // devices. | |
175 | |
176 // Get the preferred sample rate and channel configuration for the | |
177 // audio device. | |
178 media::AudioParameters params = | |
179 audio_manager_->GetInputStreamParameters(device.id); | |
180 input_params.sample_rate = params.sample_rate(); | |
181 input_params.channel_layout = params.channel_layout(); | |
182 input_params.frames_per_buffer = params.frames_per_buffer(); | |
183 input_params.effects = params.effects(); | |
184 input_params.mic_positions = params.mic_positions(); | |
185 if (!out.device.matched_output_device_id.empty()) { | |
186 params = audio_manager_->GetOutputStreamParameters( | |
187 out.device.matched_output_device_id); | |
188 MediaStreamDevice::AudioDeviceParameters& matched_output_params = | |
189 out.device.matched_output; | |
190 matched_output_params.sample_rate = params.sample_rate(); | |
191 matched_output_params.channel_layout = params.channel_layout(); | |
192 matched_output_params.frames_per_buffer = params.frames_per_buffer(); | |
193 } | |
194 } | |
195 | |
196 // Return the |session_id| through the listener by posting a task on | |
197 // IO thread since MediaStreamManager handles the callback asynchronously. | |
198 BrowserThread::PostTask(BrowserThread::IO, | |
199 FROM_HERE, | |
200 base::Bind(&AudioInputDeviceManager::OpenedOnIOThread, | |
201 this, session_id, out)); | |
202 } | |
203 | |
204 void AudioInputDeviceManager::OpenedOnIOThread(int session_id, | 317 void AudioInputDeviceManager::OpenedOnIOThread(int session_id, |
205 const StreamDeviceInfo& info) { | 318 const StreamDeviceInfo& info) { |
206 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 319 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
207 DCHECK_EQ(session_id, info.session_id); | 320 DCHECK_EQ(session_id, info.session_id); |
208 DCHECK(GetDevice(session_id) == devices_.end()); | 321 DCHECK(GetDevice(session_id) == devices_.end()); |
209 | 322 |
210 devices_.push_back(info); | 323 devices_.push_back(info); |
211 | 324 |
212 for (auto& listener : listeners_) | 325 for (auto& listener : listeners_) |
213 listener.Opened(info.device.type, session_id); | 326 listener.Opened(info.device.type, session_id); |
214 } | 327 } |
215 | 328 |
216 void AudioInputDeviceManager::ClosedOnIOThread(MediaStreamType stream_type, | 329 void AudioInputDeviceManager::ClosedOnIOThread(MediaStreamType stream_type, |
217 int session_id) { | 330 int session_id) { |
218 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 331 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
219 for (auto& listener : listeners_) | 332 for (auto& listener : listeners_) |
220 listener.Closed(stream_type, session_id); | 333 listener.Closed(stream_type, session_id); |
221 } | 334 } |
222 | 335 |
223 bool AudioInputDeviceManager::IsOnDeviceThread() const { | |
224 return device_task_runner_->BelongsToCurrentThread(); | |
225 } | |
226 | |
227 AudioInputDeviceManager::StreamDeviceList::iterator | 336 AudioInputDeviceManager::StreamDeviceList::iterator |
228 AudioInputDeviceManager::GetDevice(int session_id) { | 337 AudioInputDeviceManager::GetDevice(int session_id) { |
229 for (StreamDeviceList::iterator i(devices_.begin()); i != devices_.end(); | 338 for (StreamDeviceList::iterator i(devices_.begin()); i != devices_.end(); |
230 ++i) { | 339 ++i) { |
231 if (i->session_id == session_id) | 340 if (i->session_id == session_id) |
232 return i; | 341 return i; |
233 } | 342 } |
234 | 343 |
235 return devices_.end(); | 344 return devices_.end(); |
236 } | 345 } |
237 | 346 |
238 #if defined(OS_CHROMEOS) | 347 #if defined(OS_CHROMEOS) |
239 void AudioInputDeviceManager::SetKeyboardMicStreamActiveOnUIThread( | 348 void AudioInputDeviceManager::SetKeyboardMicStreamActiveOnUIThread( |
240 bool active) { | 349 bool active) { |
241 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 350 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
242 chromeos::CrasAudioHandler::Get()->SetKeyboardMicActive(active); | 351 chromeos::CrasAudioHandler::Get()->SetKeyboardMicActive(active); |
243 } | 352 } |
244 #endif | 353 #endif |
245 | 354 |
246 | |
247 } // namespace content | 355 } // namespace content |
OLD | NEW |