OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/renderer/media/webrtc/processed_local_audio_source.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "base/metrics/histogram.h" | |
9 #include "base/strings/stringprintf.h" | |
10 #include "content/renderer/media/audio_device_factory.h" | |
11 #include "content/renderer/media/media_stream_audio_processor_options.h" | |
12 #include "content/renderer/media/media_stream_constraints_util.h" | |
13 #include "content/renderer/media/webrtc/peer_connection_dependency_factory.h" | |
14 #include "content/renderer/media/webrtc_audio_device_impl.h" | |
15 #include "content/renderer/media/webrtc_logging.h" | |
16 #include "content/renderer/render_frame_impl.h" | |
17 #include "media/audio/sample_rates.h" | |
18 #include "media/base/channel_layout.h" | |
19 #include "third_party/webrtc/api/mediaconstraintsinterface.h" | |
20 #include "third_party/webrtc/media/base/mediachannel.h" | |
21 | |
22 namespace content { | |
23 | |
24 namespace { | |
25 // Used as an identifier for ProcessedLocalAudioSource::From(). | |
26 void* const kClassIdentifier = const_cast<void**>(&kClassIdentifier); | |
27 } // namespace | |
28 | |
29 ProcessedLocalAudioSource::ProcessedLocalAudioSource( | |
30 int consumer_render_frame_id, | |
31 const StreamDeviceInfo& device_info, | |
32 PeerConnectionDependencyFactory* factory) | |
33 : MediaStreamAudioSource(true /* is_local_source */), | |
34 consumer_render_frame_id_(consumer_render_frame_id), | |
35 pc_factory_(factory), | |
36 volume_(0), | |
37 allow_invalid_render_frame_id_for_testing_(false) { | |
38 DCHECK(pc_factory_); | |
39 DVLOG(1) << "ProcessedLocalAudioSource::ProcessedLocalAudioSource()"; | |
40 MediaStreamSource::SetDeviceInfo(device_info); | |
41 } | |
42 | |
43 ProcessedLocalAudioSource::~ProcessedLocalAudioSource() { | |
44 DVLOG(1) << "ProcessedLocalAudioSource::~ProcessedLocalAudioSource()"; | |
45 EnsureSourceIsStopped(); | |
46 } | |
47 | |
48 // static | |
49 ProcessedLocalAudioSource* ProcessedLocalAudioSource::From( | |
50 MediaStreamAudioSource* source) { | |
51 if (source && source->GetClassIdentifier() == kClassIdentifier) | |
52 return static_cast<ProcessedLocalAudioSource*>(source); | |
53 return nullptr; | |
54 } | |
55 | |
56 void ProcessedLocalAudioSource::SetSourceConstraints( | |
57 const blink::WebMediaConstraints& constraints) { | |
58 DCHECK(thread_checker_.CalledOnValidThread()); | |
59 DCHECK(!constraints.isNull()); | |
60 DCHECK(!source_); | |
61 constraints_ = constraints; | |
62 } | |
63 | |
64 void* ProcessedLocalAudioSource::GetClassIdentifier() const { | |
65 return kClassIdentifier; | |
66 } | |
67 | |
68 bool ProcessedLocalAudioSource::EnsureSourceIsStarted() { | |
69 DCHECK(thread_checker_.CalledOnValidThread()); | |
70 | |
71 if (source_) | |
72 return true; | |
73 | |
74 // Sanity-check that the consuming RenderFrame still exists. This is required | |
75 // to initialize the audio source. | |
76 if (!allow_invalid_render_frame_id_for_testing_ && | |
77 !RenderFrameImpl::FromRoutingID(consumer_render_frame_id_)) { | |
78 WebRtcLogMessage("ProcessedLocalAudioSource::EnsureSourceIsStarted() fails " | |
79 " because the render frame does not exist."); | |
80 return false; | |
81 } | |
82 | |
83 WebRtcLogMessage(base::StringPrintf( | |
84 "ProcessedLocalAudioSource::EnsureSourceIsStarted. render_frame_id=%d" | |
85 ", channel_layout=%d, sample_rate=%d, buffer_size=%d" | |
86 ", session_id=%d, paired_output_sample_rate=%d" | |
87 ", paired_output_frames_per_buffer=%d, effects=%d. ", | |
88 consumer_render_frame_id_, device_info().device.input.channel_layout, | |
89 device_info().device.input.sample_rate, | |
90 device_info().device.input.frames_per_buffer, device_info().session_id, | |
91 device_info().device.matched_output.sample_rate, | |
92 device_info().device.matched_output.frames_per_buffer, | |
93 device_info().device.input.effects)); | |
94 | |
95 // Sanity-check that the constraints, plus the additional input effects are | |
96 // valid when combined. | |
97 const MediaAudioConstraints audio_constraints( | |
98 constraints_, device_info().device.input.effects); | |
99 if (!audio_constraints.IsValid()) { | |
100 WebRtcLogMessage("ProcessedLocalAudioSource::EnsureSourceIsStarted() fails " | |
101 " because MediaAudioConstraints are not valid."); | |
102 return false; | |
103 } | |
104 | |
105 // Build an AudioOptions by applying relevant constraints to it, and then use | |
106 // it to create a webrtc::AudioSourceInterface instance. | |
107 cricket::AudioOptions rtc_options; | |
108 rtc_options.echo_cancellation = ConstraintToOptional( | |
109 constraints_, &blink::WebMediaTrackConstraintSet::echoCancellation); | |
110 rtc_options.delay_agnostic_aec = ConstraintToOptional( | |
111 constraints_, &blink::WebMediaTrackConstraintSet::googDAEchoCancellation); | |
112 rtc_options.auto_gain_control = ConstraintToOptional( | |
113 constraints_, &blink::WebMediaTrackConstraintSet::googAutoGainControl); | |
114 rtc_options.experimental_agc = ConstraintToOptional( | |
115 constraints_, | |
116 &blink::WebMediaTrackConstraintSet::googExperimentalAutoGainControl); | |
117 rtc_options.noise_suppression = ConstraintToOptional( | |
118 constraints_, &blink::WebMediaTrackConstraintSet::googNoiseSuppression); | |
119 rtc_options.experimental_ns = ConstraintToOptional( | |
120 constraints_, | |
121 &blink::WebMediaTrackConstraintSet::googExperimentalNoiseSuppression); | |
122 rtc_options.highpass_filter = ConstraintToOptional( | |
123 constraints_, &blink::WebMediaTrackConstraintSet::googHighpassFilter); | |
124 rtc_options.typing_detection = ConstraintToOptional( | |
125 constraints_, | |
126 &blink::WebMediaTrackConstraintSet::googTypingNoiseDetection); | |
127 rtc_options.stereo_swapping = ConstraintToOptional( | |
128 constraints_, &blink::WebMediaTrackConstraintSet::googAudioMirroring); | |
129 MediaAudioConstraints::ApplyFixedAudioConstraints(&rtc_options); | |
130 if (device_info().device.input.effects & | |
131 media::AudioParameters::ECHO_CANCELLER) { | |
132 // TODO(hta): Figure out if we should be looking at echoCancellation. | |
133 // Previous code had googEchoCancellation only. | |
134 const blink::BooleanConstraint& echoCancellation = | |
135 constraints_.basic().googEchoCancellation; | |
136 if (echoCancellation.hasExact() && !echoCancellation.exact()) { | |
137 StreamDeviceInfo modified_device_info(device_info()); | |
138 modified_device_info.device.input.effects &= | |
139 ~media::AudioParameters::ECHO_CANCELLER; | |
140 SetDeviceInfo(modified_device_info); | |
141 } | |
142 rtc_options.echo_cancellation = rtc::Optional<bool>(false); | |
143 } | |
144 rtc_source_ = pc_factory_->CreateLocalAudioSource(rtc_options); | |
145 if (rtc_source_->state() != webrtc::MediaSourceInterface::kLive) { | |
146 WebRtcLogMessage("ProcessedLocalAudioSource::EnsureSourceIsStarted() fails " | |
147 " because the rtc LocalAudioSource is not live."); | |
148 return false; | |
149 } | |
150 | |
151 // Create the MediaStreamAudioProcessor, bound to the WebRTC audio device | |
152 // module. | |
153 WebRtcAudioDeviceImpl* const rtc_audio_device = | |
154 pc_factory_->GetWebRtcAudioDevice(); | |
155 if (!rtc_audio_device) { | |
156 WebRtcLogMessage("ProcessedLocalAudioSource::EnsureSourceIsStarted() fails " | |
157 " because there is no WebRtcAudioDeviceImpl instance."); | |
158 return false; | |
159 } | |
160 audio_processor_ = new rtc::RefCountedObject<MediaStreamAudioProcessor>( | |
161 constraints_, device_info().device.input, rtc_audio_device); | |
162 | |
163 // If KEYBOARD_MIC effect is set, change the layout to the corresponding | |
164 // layout that includes the keyboard mic. | |
165 media::ChannelLayout channel_layout = static_cast<media::ChannelLayout>( | |
166 device_info().device.input.channel_layout); | |
167 if ((device_info().device.input.effects & | |
168 media::AudioParameters::KEYBOARD_MIC) && | |
169 audio_constraints.GetGoogExperimentalNoiseSuppression()) { | |
170 if (channel_layout == media::CHANNEL_LAYOUT_STEREO) { | |
171 channel_layout = media::CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC; | |
172 DVLOG(1) << "Changed stereo layout to stereo + keyboard mic layout due " | |
173 << "to KEYBOARD_MIC effect."; | |
174 } else { | |
175 DVLOG(1) << "KEYBOARD_MIC effect ignored, not compatible with layout " | |
176 << channel_layout; | |
177 } | |
178 } | |
179 | |
180 DVLOG(1) << "Audio input hardware channel layout: " << channel_layout; | |
181 UMA_HISTOGRAM_ENUMERATION("WebRTC.AudioInputChannelLayout", | |
182 channel_layout, media::CHANNEL_LAYOUT_MAX + 1); | |
183 | |
184 // Verify that the reported input channel configuration is supported. | |
185 if (channel_layout != media::CHANNEL_LAYOUT_MONO && | |
186 channel_layout != media::CHANNEL_LAYOUT_STEREO && | |
187 channel_layout != media::CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC) { | |
188 WebRtcLogMessage(base::StringPrintf( | |
189 "ProcessedLocalAudioSource::EnsureSourceIsStarted() fails " | |
190 " because the input channel layout (%d) is not supported.", | |
191 static_cast<int>(channel_layout))); | |
192 return false; | |
193 } | |
194 | |
195 DVLOG(1) << "Audio input hardware sample rate: " | |
196 << device_info().device.input.sample_rate; | |
197 media::AudioSampleRate asr; | |
198 if (media::ToAudioSampleRate(device_info().device.input.sample_rate, &asr)) { | |
199 UMA_HISTOGRAM_ENUMERATION( | |
200 "WebRTC.AudioInputSampleRate", asr, media::kAudioSampleRateMax + 1); | |
201 } else { | |
202 UMA_HISTOGRAM_COUNTS("WebRTC.AudioInputSampleRateUnexpected", | |
203 device_info().device.input.sample_rate); | |
204 } | |
205 | |
206 // Determine the audio format required of the AudioCapturerSource. Then, pass | |
207 // that to the |audio_processor_| and set the output format of this | |
208 // ProcessedLocalAudioSource to the processor's output format. | |
209 media::AudioParameters params( | |
210 media::AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, | |
211 device_info().device.input.sample_rate, 16, | |
212 GetBufferSize(device_info().device.input.sample_rate)); | |
213 params.set_effects(device_info().device.input.effects); | |
214 DCHECK(params.IsValid()); | |
215 audio_processor_->OnCaptureFormatChanged(params); | |
216 MediaStreamAudioSource::SetFormat(audio_processor_->OutputFormat()); | |
217 | |
218 // Start the source. | |
219 VLOG(1) << "Starting WebRTC audio source for consumption by render frame " | |
220 << consumer_render_frame_id_ << " with input parameters={" | |
221 << params.AsHumanReadableString() << "} and output parameters={" | |
222 << GetAudioParameters().AsHumanReadableString() << '}'; | |
223 source_ = | |
224 AudioDeviceFactory::NewAudioCapturerSource(consumer_render_frame_id_); | |
225 source_->Initialize(params, this, device_info().session_id); | |
226 // We need to set the AGC control before starting the stream. | |
227 source_->SetAutomaticGainControl(true); | |
228 source_->Start(); | |
229 | |
230 // Register this source with the WebRtcAudioDeviceImpl. | |
231 rtc_audio_device->AddAudioCapturer(this); | |
232 | |
233 return true; | |
234 } | |
235 | |
236 void ProcessedLocalAudioSource::EnsureSourceIsStopped() { | |
237 DCHECK(thread_checker_.CalledOnValidThread()); | |
238 | |
239 if (!source_) | |
240 return; | |
241 | |
242 if (WebRtcAudioDeviceImpl* rtc_audio_device = | |
243 pc_factory_->GetWebRtcAudioDevice()) { | |
244 rtc_audio_device->RemoveAudioCapturer(this); | |
245 } | |
246 | |
247 // Note: Stopping the source while holding the |volume_lock_| because the | |
248 // SetVolume() method needs to know whether |source_| is valid. | |
249 { | |
250 base::AutoLock auto_lock(volume_lock_); | |
251 source_->Stop(); | |
252 source_ = nullptr; | |
253 } | |
254 | |
255 // Stop the audio processor to avoid feeding render data into the processor. | |
256 audio_processor_->Stop(); | |
257 | |
258 VLOG(1) << "Stopped WebRTC audio pipeline for consumption by render frame " | |
259 << consumer_render_frame_id_ << '.'; | |
260 } | |
261 | |
262 void ProcessedLocalAudioSource::SetVolume(int volume) { | |
263 DVLOG(1) << "ProcessedLocalAudioSource::SetVolume()"; | |
264 DCHECK_LE(volume, MaxVolume()); | |
265 double normalized_volume = static_cast<double>(volume) / MaxVolume(); | |
266 base::AutoLock auto_lock(volume_lock_); | |
267 if (source_) | |
268 source_->SetVolume(normalized_volume); | |
269 } | |
270 | |
271 int ProcessedLocalAudioSource::Volume() const { | |
272 base::AutoLock auto_lock(volume_lock_); | |
273 return volume_; | |
274 } | |
275 | |
276 int ProcessedLocalAudioSource::MaxVolume() const { | |
277 return WebRtcAudioDeviceImpl::kMaxVolumeLevel; | |
278 } | |
279 | |
280 void ProcessedLocalAudioSource::Capture(const media::AudioBus* audio_bus, | |
281 int audio_delay_milliseconds, | |
282 double volume, | |
283 bool key_pressed) { | |
284 #if defined(OS_WIN) || defined(OS_MACOSX) | |
285 DCHECK_LE(volume, 1.0); | |
286 #elif (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_OPENBSD) | |
287 // We have a special situation on Linux where the microphone volume can be | |
288 // "higher than maximum". The input volume slider in the sound preference | |
289 // allows the user to set a scaling that is higher than 100%. It means that | |
290 // even if the reported maximum levels is N, the actual microphone level can | |
291 // go up to 1.5x*N and that corresponds to a normalized |volume| of 1.5x. | |
292 DCHECK_LE(volume, 1.6); | |
293 #endif | |
294 | |
295 // TODO(miu): Plumbing is needed to determine the actual capture timestamp | |
296 // of the audio, instead of just snapshotting TimeTicks::Now(), for proper | |
297 // audio/video sync. http://crbug.com/335335 | |
298 const base::TimeTicks reference_clock_snapshot = base::TimeTicks::Now(); | |
299 | |
300 // Map internal volume range of [0.0, 1.0] into [0, 255] used by AGC. | |
301 // The volume can be higher than 255 on Linux, and it will be cropped to | |
302 // 255 since AGC does not allow values out of range. | |
303 int current_volume = static_cast<int>((volume * MaxVolume()) + 0.5); | |
304 { | |
305 base::AutoLock auto_lock(volume_lock_); | |
306 volume_ = current_volume; | |
307 } | |
308 current_volume = volume_ > MaxVolume() ? MaxVolume() : volume_; | |
309 | |
310 // Sanity-check the input audio format in debug builds. Then, notify the | |
311 // tracks if the format has changed. | |
312 // | |
313 // Locking is not needed here to read the audio input/output parameters | |
314 // because the audio processor format changes only occur while audio capture | |
315 // is stopped. | |
316 DCHECK(audio_processor_->InputFormat().IsValid()); | |
317 DCHECK_EQ(audio_bus->channels(), audio_processor_->InputFormat().channels()); | |
318 DCHECK_EQ(audio_bus->frames(), | |
319 audio_processor_->InputFormat().frames_per_buffer()); | |
320 | |
321 // Figure out if the pre-processed data has any energy or not. This | |
322 // information will be passed to the level calculator to force it to report | |
323 // energy in case the post-processed data is zeroed by the audio processing. | |
324 const bool force_report_nonzero_energy = !audio_bus->AreFramesZero(); | |
325 | |
326 // Push the data to the processor for processing. | |
327 audio_processor_->PushCaptureData( | |
328 *audio_bus, | |
329 base::TimeDelta::FromMilliseconds(audio_delay_milliseconds)); | |
330 | |
331 // Process and consume the data in the processor until there is not enough | |
332 // data in the processor. | |
333 media::AudioBus* processed_data = nullptr; | |
334 base::TimeDelta processed_data_audio_delay; | |
335 int new_volume = 0; | |
336 while (audio_processor_->ProcessAndConsumeData( | |
337 current_volume, key_pressed, | |
338 &processed_data, &processed_data_audio_delay, &new_volume)) { | |
339 DCHECK(processed_data); | |
340 | |
341 level_calculator_.Calculate(*processed_data, force_report_nonzero_energy); | |
342 | |
343 MediaStreamAudioSource::DeliverDataToTracks( | |
344 *processed_data, reference_clock_snapshot - processed_data_audio_delay); | |
345 | |
346 if (new_volume) { | |
347 SetVolume(new_volume); | |
348 | |
349 // Update the |current_volume| to avoid passing the old volume to AGC. | |
350 current_volume = new_volume; | |
351 } | |
352 } | |
353 } | |
354 | |
355 void ProcessedLocalAudioSource::OnCaptureError(const std::string& message) { | |
356 WebRtcLogMessage("ProcessedLocalAudioSource::OnCaptureError: " + message); | |
357 } | |
358 | |
359 media::AudioParameters ProcessedLocalAudioSource::GetInputFormat() const { | |
360 return audio_processor_ ? audio_processor_->InputFormat() | |
361 : media::AudioParameters(); | |
362 } | |
363 | |
364 int ProcessedLocalAudioSource::GetBufferSize(int sample_rate) const { | |
365 DCHECK(thread_checker_.CalledOnValidThread()); | |
366 #if defined(OS_ANDROID) | |
367 // TODO(henrika): Re-evaluate whether to use same logic as other platforms. | |
368 return (2 * sample_rate / 100); | |
369 #endif | |
370 | |
371 // If audio processing is turned on, require 10ms buffers. | |
372 if (audio_processor_->has_audio_processing()) | |
373 return (sample_rate / 100); | |
374 | |
375 // If audio processing is off and the native hardware buffer size was | |
376 // provided, use it. It can be harmful, in terms of CPU/power consumption, to | |
377 // use smaller buffer sizes than the native size (http://crbug.com/362261). | |
378 if (int hardware_buffer_size = device_info().device.input.frames_per_buffer) | |
379 return hardware_buffer_size; | |
380 | |
381 // If the buffer size is missing from the StreamDeviceInfo, provide 10ms as a | |
382 // fall-back. | |
383 // | |
384 // TODO(miu): Identify where/why the buffer size might be missing, fix the | |
385 // code, and then require it here. | |
386 return (sample_rate / 100); | |
387 } | |
388 | |
389 } // namespace content | |
OLD | NEW |