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/renderer/media/webrtc_local_audio_renderer.h" | 5 #include "content/renderer/media/track_audio_renderer.h" |
6 | |
7 #include <utility> | |
8 | 6 |
9 #include "base/location.h" | 7 #include "base/location.h" |
10 #include "base/logging.h" | 8 #include "base/logging.h" |
11 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
12 #include "base/synchronization/lock.h" | 10 #include "base/synchronization/lock.h" |
13 #include "base/thread_task_runner_handle.h" | 11 #include "base/thread_task_runner_handle.h" |
14 #include "base/trace_event/trace_event.h" | 12 #include "base/trace_event/trace_event.h" |
15 #include "content/renderer/media/audio_device_factory.h" | 13 #include "content/renderer/media/audio_device_factory.h" |
16 #include "content/renderer/media/media_stream_dispatcher.h" | 14 #include "content/renderer/media/media_stream_audio_track.h" |
17 #include "content/renderer/media/webrtc_audio_capturer.h" | |
18 #include "content/renderer/media/webrtc_audio_renderer.h" | |
19 #include "content/renderer/render_frame_impl.h" | |
20 #include "media/audio/audio_output_device.h" | 15 #include "media/audio/audio_output_device.h" |
21 #include "media/base/audio_bus.h" | 16 #include "media/base/audio_bus.h" |
22 #include "media/base/audio_shifter.h" | 17 #include "media/base/audio_shifter.h" |
23 | 18 |
24 namespace content { | 19 namespace content { |
25 | 20 |
26 namespace { | 21 namespace { |
27 | 22 |
28 enum LocalRendererSinkStates { | 23 enum LocalRendererSinkStates { |
29 kSinkStarted = 0, | 24 kSinkStarted = 0, |
30 kSinkNeverStarted, | 25 kSinkNeverStarted, |
31 kSinkStatesMax // Must always be last! | 26 kSinkStatesMax // Must always be last! |
32 }; | 27 }; |
33 | 28 |
29 // Translates |num_samples_rendered| into a TimeDelta duration and adds it to | |
30 // |prior_elapsed_render_time|. | |
31 base::TimeDelta ComputeTotalElapsedRenderTime( | |
32 base::TimeDelta prior_elapsed_render_time, | |
33 int64_t num_samples_rendered, | |
34 int sample_rate) { | |
35 return prior_elapsed_render_time + base::TimeDelta::FromMicroseconds( | |
36 num_samples_rendered * base::Time::kMicrosecondsPerSecond / sample_rate); | |
37 } | |
38 | |
34 } // namespace | 39 } // namespace |
35 | 40 |
36 // media::AudioRendererSink::RenderCallback implementation | 41 // media::AudioRendererSink::RenderCallback implementation |
37 int WebRtcLocalAudioRenderer::Render(media::AudioBus* audio_bus, | 42 int TrackAudioRenderer::Render(media::AudioBus* audio_bus, |
38 uint32_t audio_delay_milliseconds, | 43 uint32_t audio_delay_milliseconds, |
39 uint32_t frames_skipped) { | 44 uint32_t frames_skipped) { |
40 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::Render"); | 45 TRACE_EVENT0("audio", "TrackAudioRenderer::Render"); |
41 base::AutoLock auto_lock(thread_lock_); | 46 base::AutoLock auto_lock(thread_lock_); |
42 | 47 |
43 if (!playing_ || !volume_ || !audio_shifter_) { | 48 if (!audio_shifter_) { |
44 audio_bus->Zero(); | 49 audio_bus->Zero(); |
45 return 0; | 50 return 0; |
46 } | 51 } |
47 | 52 |
48 audio_shifter_->Pull( | 53 // TODO(miu): Plumbing is needed to determine the actual playout timestamp |
49 audio_bus, | 54 // of the audio, instead of just snapshotting TimeTicks::Now(), for proper |
50 base::TimeTicks::Now() - | 55 // audio/video sync. http://crbug.com/335335 |
51 base::TimeDelta::FromMilliseconds(audio_delay_milliseconds)); | 56 const base::TimeTicks playout_time = |
52 | 57 base::TimeTicks::Now() + |
hubbe
2016/02/06 21:42:19
There is a change her from now - audio_delay to no
miu
2016/02/10 04:25:48
hubbe: As we discussed in-person, I "presented" th
| |
58 base::TimeDelta::FromMilliseconds(audio_delay_milliseconds); | |
59 DVLOG(2) << "Pulling audio out of shifter to be played " | |
60 << audio_delay_milliseconds << " ms from now."; | |
61 audio_shifter_->Pull(audio_bus, playout_time); | |
62 num_samples_rendered_ += audio_bus->frames(); | |
53 return audio_bus->frames(); | 63 return audio_bus->frames(); |
54 } | 64 } |
55 | 65 |
56 void WebRtcLocalAudioRenderer::OnRenderError() { | 66 void TrackAudioRenderer::OnRenderError() { |
57 NOTIMPLEMENTED(); | 67 NOTIMPLEMENTED(); |
58 } | 68 } |
59 | 69 |
60 // content::MediaStreamAudioSink implementation | 70 // content::MediaStreamAudioSink implementation |
61 void WebRtcLocalAudioRenderer::OnData(const media::AudioBus& audio_bus, | 71 void TrackAudioRenderer::OnData(const media::AudioBus& audio_bus, |
62 base::TimeTicks estimated_capture_time) { | 72 base::TimeTicks reference_time) { |
63 DCHECK(capture_thread_checker_.CalledOnValidThread()); | 73 DCHECK(audio_thread_checker_.CalledOnValidThread()); |
64 DCHECK(!estimated_capture_time.is_null()); | 74 DCHECK(!reference_time.is_null()); |
65 | 75 |
66 TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::CaptureData"); | 76 TRACE_EVENT0("audio", "TrackAudioRenderer::CaptureData"); |
67 | 77 |
68 base::AutoLock auto_lock(thread_lock_); | 78 base::AutoLock auto_lock(thread_lock_); |
69 if (!playing_ || !volume_ || !audio_shifter_) | 79 if (!audio_shifter_) |
70 return; | 80 return; |
71 | 81 |
72 scoped_ptr<media::AudioBus> audio_data( | 82 scoped_ptr<media::AudioBus> audio_data( |
73 media::AudioBus::Create(audio_bus.channels(), audio_bus.frames())); | 83 media::AudioBus::Create(audio_bus.channels(), audio_bus.frames())); |
74 audio_bus.CopyTo(audio_data.get()); | 84 audio_bus.CopyTo(audio_data.get()); |
75 audio_shifter_->Push(std::move(audio_data), estimated_capture_time); | 85 // Note: For remote audio sources, |reference_time| is the local playout time, |
76 const base::TimeTicks now = base::TimeTicks::Now(); | 86 // the ideal point-in-time at which the first audio sample should be played |
77 total_render_time_ += now - last_render_time_; | 87 // out in the future. For local sources, |reference_time| is the |
78 last_render_time_ = now; | 88 // point-in-time at which the first audio sample was captured in the past. In |
89 // either case, AudioShifter will auto-detect and do the right thing when | |
90 // audio is pulled from it. | |
91 audio_shifter_->Push(std::move(audio_data), reference_time); | |
79 } | 92 } |
80 | 93 |
81 void WebRtcLocalAudioRenderer::OnSetFormat( | 94 void TrackAudioRenderer::OnSetFormat(const media::AudioParameters& params) { |
82 const media::AudioParameters& params) { | 95 DVLOG(1) << "TrackAudioRenderer::OnSetFormat()"; |
83 DVLOG(1) << "WebRtcLocalAudioRenderer::OnSetFormat()"; | |
84 // If the source is restarted, we might have changed to another capture | 96 // If the source is restarted, we might have changed to another capture |
85 // thread. | 97 // thread. |
86 capture_thread_checker_.DetachFromThread(); | 98 audio_thread_checker_.DetachFromThread(); |
87 DCHECK(capture_thread_checker_.CalledOnValidThread()); | 99 DCHECK(audio_thread_checker_.CalledOnValidThread()); |
100 | |
101 // If the parameters changed, the audio in the AudioShifter is invalid and | |
102 // should be dropped. | |
103 { | |
104 base::AutoLock auto_lock(thread_lock_); | |
105 if (audio_shifter_ && | |
106 (audio_shifter_->sample_rate() != params.sample_rate() || | |
107 audio_shifter_->channels() != params.channels())) { | |
108 HaltAudioFlowWhileLockHeld(); | |
109 } | |
110 } | |
88 | 111 |
89 // Post a task on the main render thread to reconfigure the |sink_| with the | 112 // Post a task on the main render thread to reconfigure the |sink_| with the |
90 // new format. | 113 // new format. |
91 task_runner_->PostTask( | 114 task_runner_->PostTask( |
92 FROM_HERE, | 115 FROM_HERE, |
93 base::Bind(&WebRtcLocalAudioRenderer::ReconfigureSink, this, params)); | 116 base::Bind(&TrackAudioRenderer::ReconfigureSink, this, params)); |
94 } | 117 } |
95 | 118 |
96 // WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer implementation. | 119 TrackAudioRenderer::TrackAudioRenderer( |
97 WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer( | |
98 const blink::WebMediaStreamTrack& audio_track, | 120 const blink::WebMediaStreamTrack& audio_track, |
99 int source_render_frame_id, | 121 int playout_render_frame_id, |
100 int session_id, | 122 int session_id, |
101 const std::string& device_id, | 123 const std::string& device_id, |
102 const url::Origin& security_origin) | 124 const url::Origin& security_origin) |
103 : audio_track_(audio_track), | 125 : audio_track_(audio_track), |
104 source_render_frame_id_(source_render_frame_id), | 126 playout_render_frame_id_(playout_render_frame_id), |
105 session_id_(session_id), | 127 session_id_(session_id), |
106 task_runner_(base::ThreadTaskRunnerHandle::Get()), | 128 task_runner_(base::ThreadTaskRunnerHandle::Get()), |
129 num_samples_rendered_(0), | |
107 playing_(false), | 130 playing_(false), |
108 output_device_id_(device_id), | 131 output_device_id_(device_id), |
109 security_origin_(security_origin), | 132 security_origin_(security_origin), |
110 volume_(0.0), | 133 volume_(0.0), |
111 sink_started_(false) { | 134 sink_started_(false) { |
112 DVLOG(1) << "WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer()"; | 135 DCHECK(MediaStreamAudioTrack::GetTrack(audio_track_)); |
136 DVLOG(1) << "TrackAudioRenderer::TrackAudioRenderer()"; | |
113 } | 137 } |
114 | 138 |
115 WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() { | 139 TrackAudioRenderer::~TrackAudioRenderer() { |
116 DCHECK(task_runner_->BelongsToCurrentThread()); | 140 DCHECK(task_runner_->BelongsToCurrentThread()); |
117 DCHECK(!sink_.get()); | 141 DCHECK(!sink_.get()); |
118 DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()"; | 142 DVLOG(1) << "TrackAudioRenderer::~TrackAudioRenderer()"; |
119 } | 143 } |
120 | 144 |
121 void WebRtcLocalAudioRenderer::Start() { | 145 void TrackAudioRenderer::Start() { |
122 DVLOG(1) << "WebRtcLocalAudioRenderer::Start()"; | 146 DVLOG(1) << "TrackAudioRenderer::Start()"; |
123 DCHECK(task_runner_->BelongsToCurrentThread()); | 147 DCHECK(task_runner_->BelongsToCurrentThread()); |
148 DCHECK_EQ(playing_, false); | |
124 | 149 |
125 // We get audio data from |audio_track_|... | 150 // We get audio data from |audio_track_|... |
126 MediaStreamAudioSink::AddToAudioTrack(this, audio_track_); | 151 MediaStreamAudioSink::AddToAudioTrack(this, audio_track_); |
127 // ...and |sink_| will get audio data from us. | 152 // ...and |sink_| will get audio data from us. |
128 DCHECK(!sink_.get()); | 153 DCHECK(!sink_.get()); |
129 sink_ = | 154 sink_ = |
130 AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_, | 155 AudioDeviceFactory::NewOutputDevice(playout_render_frame_id_, session_id_, |
131 output_device_id_, security_origin_); | 156 output_device_id_, security_origin_); |
157 sink_->SetVolume(volume_); | |
132 | 158 |
133 base::AutoLock auto_lock(thread_lock_); | 159 base::AutoLock auto_lock(thread_lock_); |
134 last_render_time_ = base::TimeTicks::Now(); | 160 prior_elapsed_render_time_ = base::TimeDelta(); |
135 playing_ = false; | 161 num_samples_rendered_ = 0; |
136 } | 162 } |
137 | 163 |
138 void WebRtcLocalAudioRenderer::Stop() { | 164 void TrackAudioRenderer::Stop() { |
139 DVLOG(1) << "WebRtcLocalAudioRenderer::Stop()"; | 165 DVLOG(1) << "TrackAudioRenderer::Stop()"; |
140 DCHECK(task_runner_->BelongsToCurrentThread()); | 166 DCHECK(task_runner_->BelongsToCurrentThread()); |
141 | 167 |
142 { | 168 Pause(); |
143 base::AutoLock auto_lock(thread_lock_); | |
144 playing_ = false; | |
145 audio_shifter_.reset(); | |
146 } | |
147 | 169 |
148 // Stop the output audio stream, i.e, stop asking for data to render. | 170 // Stop the output audio stream, i.e, stop asking for data to render. |
149 // It is safer to call Stop() on the |sink_| to clean up the resources even | 171 // It is safer to call Stop() on the |sink_| to clean up the resources even |
150 // when the |sink_| is never started. | 172 // when the |sink_| is never started. |
151 if (sink_.get()) { | 173 if (sink_.get()) { |
152 sink_->Stop(); | 174 sink_->Stop(); |
153 sink_ = NULL; | 175 sink_ = NULL; |
154 } | 176 } |
155 | 177 |
156 if (!sink_started_) { | 178 if (!sink_started_ && IsLocalRenderer()) { |
157 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", | 179 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", |
158 kSinkNeverStarted, kSinkStatesMax); | 180 kSinkNeverStarted, kSinkStatesMax); |
159 } | 181 } |
160 sink_started_ = false; | 182 sink_started_ = false; |
161 | 183 |
162 // Ensure that the capturer stops feeding us with captured audio. | 184 // Ensure that the capturer stops feeding us with captured audio. |
163 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_); | 185 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_); |
164 } | 186 } |
165 | 187 |
166 void WebRtcLocalAudioRenderer::Play() { | 188 void TrackAudioRenderer::Play() { |
167 DVLOG(1) << "WebRtcLocalAudioRenderer::Play()"; | 189 DVLOG(1) << "TrackAudioRenderer::Play()"; |
168 DCHECK(task_runner_->BelongsToCurrentThread()); | 190 DCHECK(task_runner_->BelongsToCurrentThread()); |
169 | 191 |
170 if (!sink_.get()) | 192 if (!sink_.get()) |
171 return; | 193 return; |
172 | 194 |
173 { | 195 playing_ = true; |
174 base::AutoLock auto_lock(thread_lock_); | |
175 // Resumes rendering by ensuring that WebRtcLocalAudioRenderer::Render() | |
176 // now reads data from the local FIFO. | |
177 playing_ = true; | |
178 last_render_time_ = base::TimeTicks::Now(); | |
179 } | |
180 | 196 |
181 // Note: If volume_ is currently muted, the |sink_| will not be started yet. | |
182 MaybeStartSink(); | 197 MaybeStartSink(); |
183 } | 198 } |
184 | 199 |
185 void WebRtcLocalAudioRenderer::Pause() { | 200 void TrackAudioRenderer::Pause() { |
186 DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()"; | 201 DVLOG(1) << "TrackAudioRenderer::Pause()"; |
187 DCHECK(task_runner_->BelongsToCurrentThread()); | 202 DCHECK(task_runner_->BelongsToCurrentThread()); |
188 | 203 |
189 if (!sink_.get()) | 204 if (!sink_.get()) |
190 return; | 205 return; |
191 | 206 |
207 playing_ = false; | |
208 | |
192 base::AutoLock auto_lock(thread_lock_); | 209 base::AutoLock auto_lock(thread_lock_); |
193 // Temporarily suspends rendering audio. | 210 HaltAudioFlowWhileLockHeld(); |
194 // WebRtcLocalAudioRenderer::Render() will return early during this state | |
195 // and only zeros will be provided to the active sink. | |
196 playing_ = false; | |
197 } | 211 } |
198 | 212 |
199 void WebRtcLocalAudioRenderer::SetVolume(float volume) { | 213 void TrackAudioRenderer::SetVolume(float volume) { |
200 DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume << ")"; | 214 DVLOG(1) << "TrackAudioRenderer::SetVolume(" << volume << ")"; |
215 DCHECK(task_runner_->BelongsToCurrentThread()); | |
216 | |
217 // Cache the volume. Whenever |sink_| is re-created, call SetVolume() with | |
218 // this cached volume. | |
219 volume_ = volume; | |
220 if (sink_.get()) | |
221 sink_->SetVolume(volume); | |
222 } | |
223 | |
224 media::OutputDevice* TrackAudioRenderer::GetOutputDevice() { | |
225 DCHECK(task_runner_->BelongsToCurrentThread()); | |
226 return this; | |
227 } | |
228 | |
229 base::TimeDelta TrackAudioRenderer::GetCurrentRenderTime() const { | |
230 DCHECK(task_runner_->BelongsToCurrentThread()); | |
231 base::AutoLock auto_lock(thread_lock_); | |
232 if (source_params_.IsValid()) { | |
233 return ComputeTotalElapsedRenderTime(prior_elapsed_render_time_, | |
234 num_samples_rendered_, | |
235 source_params_.sample_rate()); | |
236 } | |
237 return prior_elapsed_render_time_; | |
238 } | |
239 | |
240 bool TrackAudioRenderer::IsLocalRenderer() const { | |
241 DCHECK(task_runner_->BelongsToCurrentThread()); | |
242 return MediaStreamAudioTrack::GetTrack(audio_track_)->is_local_track(); | |
243 } | |
244 | |
245 void TrackAudioRenderer::SwitchOutputDevice( | |
246 const std::string& device_id, | |
247 const url::Origin& security_origin, | |
248 const media::SwitchOutputDeviceCB& callback) { | |
249 DVLOG(1) << "TrackAudioRenderer::SwitchOutputDevice()"; | |
201 DCHECK(task_runner_->BelongsToCurrentThread()); | 250 DCHECK(task_runner_->BelongsToCurrentThread()); |
202 | 251 |
203 { | 252 { |
204 base::AutoLock auto_lock(thread_lock_); | 253 base::AutoLock auto_lock(thread_lock_); |
205 // Cache the volume. | 254 HaltAudioFlowWhileLockHeld(); |
206 volume_ = volume; | |
207 } | 255 } |
208 | 256 |
209 // Lazily start the |sink_| when the local renderer is unmuted during | |
210 // playing. | |
211 MaybeStartSink(); | |
212 | |
213 if (sink_.get()) | |
214 sink_->SetVolume(volume); | |
215 } | |
216 | |
217 media::OutputDevice* WebRtcLocalAudioRenderer::GetOutputDevice() { | |
218 DCHECK(task_runner_->BelongsToCurrentThread()); | |
219 return this; | |
220 } | |
221 | |
222 base::TimeDelta WebRtcLocalAudioRenderer::GetCurrentRenderTime() const { | |
223 DCHECK(task_runner_->BelongsToCurrentThread()); | |
224 base::AutoLock auto_lock(thread_lock_); | |
225 if (!sink_.get()) | |
226 return base::TimeDelta(); | |
227 return total_render_time(); | |
228 } | |
229 | |
230 bool WebRtcLocalAudioRenderer::IsLocalRenderer() const { | |
231 return true; | |
232 } | |
233 | |
234 void WebRtcLocalAudioRenderer::SwitchOutputDevice( | |
235 const std::string& device_id, | |
236 const url::Origin& security_origin, | |
237 const media::SwitchOutputDeviceCB& callback) { | |
238 DVLOG(1) << "WebRtcLocalAudioRenderer::SwitchOutputDevice()"; | |
239 DCHECK(task_runner_->BelongsToCurrentThread()); | |
240 | |
241 scoped_refptr<media::AudioOutputDevice> new_sink = | 257 scoped_refptr<media::AudioOutputDevice> new_sink = |
242 AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_, | 258 AudioDeviceFactory::NewOutputDevice(playout_render_frame_id_, session_id_, |
243 device_id, security_origin); | 259 device_id, security_origin); |
244 if (new_sink->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) { | 260 if (new_sink->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) { |
245 callback.Run(new_sink->GetDeviceStatus()); | 261 callback.Run(new_sink->GetDeviceStatus()); |
246 return; | 262 return; |
247 } | 263 } |
248 | 264 |
249 output_device_id_ = device_id; | 265 output_device_id_ = device_id; |
250 security_origin_ = security_origin; | 266 security_origin_ = security_origin; |
251 bool was_sink_started = sink_started_; | 267 bool was_sink_started = sink_started_; |
252 | 268 |
253 if (sink_.get()) | 269 if (sink_.get()) |
254 sink_->Stop(); | 270 sink_->Stop(); |
255 | 271 |
256 sink_started_ = false; | 272 sink_started_ = false; |
257 sink_ = new_sink; | 273 sink_ = new_sink; |
258 int frames_per_buffer = sink_->GetOutputParameters().frames_per_buffer(); | 274 sink_->SetVolume(volume_); |
o1ka
2016/02/08 16:58:58
Here and a couple more other places. As far as I u
miu
2016/02/10 04:25:48
Ah! So it is. I moved the SetVolume() call to oc
o1ka
2016/02/10 10:24:12
Acknowledged.
| |
259 sink_params_ = source_params_; | |
260 sink_params_.set_frames_per_buffer(WebRtcAudioRenderer::GetOptimalBufferSize( | |
261 source_params_.sample_rate(), frames_per_buffer)); | |
262 | |
263 if (was_sink_started) | 275 if (was_sink_started) |
264 MaybeStartSink(); | 276 MaybeStartSink(); |
265 | 277 |
266 callback.Run(media::OUTPUT_DEVICE_STATUS_OK); | 278 callback.Run(media::OUTPUT_DEVICE_STATUS_OK); |
267 } | 279 } |
268 | 280 |
269 media::AudioParameters WebRtcLocalAudioRenderer::GetOutputParameters() { | 281 media::AudioParameters TrackAudioRenderer::GetOutputParameters() { |
270 DCHECK(task_runner_->BelongsToCurrentThread()); | 282 DCHECK(task_runner_->BelongsToCurrentThread()); |
271 if (!sink_.get()) | 283 if (!sink_ || !source_params_.IsValid()) |
272 return media::AudioParameters(); | 284 return media::AudioParameters(); |
273 | 285 |
274 return sink_->GetOutputParameters(); | 286 // Output parameters consist of the same channel layout and sample rate as the |
287 // source, but having the buffer duration preferred by the hardware. | |
288 const media::AudioParameters& preferred_params = sink_->GetOutputParameters(); | |
289 return media::AudioParameters( | |
290 preferred_params.format(), source_params_.channel_layout(), | |
291 source_params_.sample_rate(), source_params_.bits_per_sample(), | |
292 preferred_params.frames_per_buffer() * source_params_.sample_rate() / | |
o1ka
2016/02/08 16:58:58
I may be missing something: why buffer size is not
miu
2016/02/10 04:25:48
The call to GetOptimalBufferSize() looked wrong to
o1ka
2016/02/10 10:24:12
Sounds reasonable :) Thanks for clarification!
| |
293 preferred_params.sample_rate()); | |
275 } | 294 } |
276 | 295 |
277 media::OutputDeviceStatus WebRtcLocalAudioRenderer::GetDeviceStatus() { | 296 media::OutputDeviceStatus TrackAudioRenderer::GetDeviceStatus() { |
278 DCHECK(task_runner_->BelongsToCurrentThread()); | 297 DCHECK(task_runner_->BelongsToCurrentThread()); |
279 if (!sink_.get()) | 298 if (!sink_.get()) |
280 return media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL; | 299 return media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL; |
281 | 300 |
282 return sink_->GetDeviceStatus(); | 301 return sink_->GetDeviceStatus(); |
283 } | 302 } |
284 | 303 |
285 void WebRtcLocalAudioRenderer::MaybeStartSink() { | 304 void TrackAudioRenderer::MaybeStartSink() { |
286 DCHECK(task_runner_->BelongsToCurrentThread()); | 305 DCHECK(task_runner_->BelongsToCurrentThread()); |
287 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink()"; | 306 DVLOG(1) << "TrackAudioRenderer::MaybeStartSink()"; |
288 | 307 |
289 if (!sink_.get() || !source_params_.IsValid()) | 308 if (!sink_.get() || !source_params_.IsValid() || !playing_) |
290 return; | 309 return; |
291 | 310 |
292 { | 311 // Re-create the AudioShifter to drop old audio data and reset to a starting |
293 // Clear up the old data in the FIFO. | 312 // state. MaybeStartSink() is always called in a situation where either the |
294 base::AutoLock auto_lock(thread_lock_); | 313 // source or sink has changed somehow and so all of AudioShifter's internal |
295 audio_shifter_->Flush(); | 314 // time-sync state is invalid. |
315 CreateAudioShifter(); | |
316 | |
317 if (sink_started_ || | |
318 sink_->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) { | |
319 return; | |
296 } | 320 } |
297 | 321 |
298 if (!sink_params_.IsValid() || !playing_ || !volume_ || sink_started_ || | 322 DVLOG(1) << ("TrackAudioRenderer::MaybeStartSink() -- Starting sink. " |
299 sink_->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) | 323 "source_params_={") |
300 return; | 324 << source_params_.AsHumanReadableString() << "}, sink parameters={" |
301 | 325 << GetOutputParameters().AsHumanReadableString() << '}'; |
302 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink() -- Starting sink_."; | 326 sink_->Initialize(GetOutputParameters(), this); |
303 sink_->Initialize(sink_params_, this); | |
304 sink_->Start(); | 327 sink_->Start(); |
305 sink_started_ = true; | 328 sink_started_ = true; |
306 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", | 329 if (IsLocalRenderer()) { |
307 kSinkStarted, kSinkStatesMax); | 330 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", kSinkStarted, |
331 kSinkStatesMax); | |
332 } | |
308 } | 333 } |
309 | 334 |
310 void WebRtcLocalAudioRenderer::ReconfigureSink( | 335 void TrackAudioRenderer::ReconfigureSink(const media::AudioParameters& params) { |
311 const media::AudioParameters& params) { | |
312 DCHECK(task_runner_->BelongsToCurrentThread()); | 336 DCHECK(task_runner_->BelongsToCurrentThread()); |
313 | 337 |
314 DVLOG(1) << "WebRtcLocalAudioRenderer::ReconfigureSink()"; | 338 DVLOG(1) << "TrackAudioRenderer::ReconfigureSink()"; |
315 | 339 |
316 if (source_params_.Equals(params)) | 340 if (source_params_.Equals(params)) |
317 return; | 341 return; |
318 | |
319 // Reset the |source_params_|, |sink_params_| and |loopback_fifo_| to match | |
320 // the new format. | |
321 | |
322 source_params_ = params; | 342 source_params_ = params; |
323 { | |
324 // Note: The max buffer is fairly large, but will rarely be used. | |
325 // Cast needs the buffer to hold at least one second of audio. | |
326 // The clock accuracy is set to 20ms because clock accuracy is | |
327 // ~15ms on windows. | |
328 media::AudioShifter* const new_shifter = new media::AudioShifter( | |
329 base::TimeDelta::FromSeconds(2), | |
330 base::TimeDelta::FromMilliseconds(20), | |
331 base::TimeDelta::FromSeconds(20), | |
332 source_params_.sample_rate(), | |
333 params.channels()); | |
334 | |
335 base::AutoLock auto_lock(thread_lock_); | |
336 audio_shifter_.reset(new_shifter); | |
337 } | |
338 | 343 |
339 if (!sink_.get()) | 344 if (!sink_.get()) |
340 return; // WebRtcLocalAudioRenderer has not yet been started. | 345 return; // TrackAudioRenderer has not yet been started. |
341 | 346 |
342 // Stop |sink_| and re-create a new one to be initialized with different audio | 347 // Stop |sink_| and re-create a new one to be initialized with different audio |
343 // parameters. Then, invoke MaybeStartSink() to restart everything again. | 348 // parameters. Then, invoke MaybeStartSink() to restart everything again. |
344 sink_->Stop(); | 349 sink_->Stop(); |
345 sink_started_ = false; | 350 sink_started_ = false; |
346 sink_ = | 351 sink_ = |
347 AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_, | 352 AudioDeviceFactory::NewOutputDevice(playout_render_frame_id_, session_id_, |
348 output_device_id_, security_origin_); | 353 output_device_id_, security_origin_); |
349 int frames_per_buffer = sink_->GetOutputParameters().frames_per_buffer(); | 354 sink_->SetVolume(volume_); |
350 sink_params_ = source_params_; | |
351 sink_params_.set_frames_per_buffer(WebRtcAudioRenderer::GetOptimalBufferSize( | |
352 source_params_.sample_rate(), frames_per_buffer)); | |
353 MaybeStartSink(); | 355 MaybeStartSink(); |
354 } | 356 } |
355 | 357 |
358 void TrackAudioRenderer::CreateAudioShifter() { | |
359 DCHECK(task_runner_->BelongsToCurrentThread()); | |
360 | |
361 // Note 1: The max buffer is fairly large to cover the case where | |
362 // remotely-sourced audio is delivered well ahead of its scheduled playout | |
363 // time (e.g., content streaming with a very large end-to-end | |
364 // latency). However, there is no penalty for making it large in the | |
365 // low-latency use cases since AudioShifter will discard data as soon as it is | |
366 // no longer needed. | |
367 // | |
368 // Note 2: The clock accuracy is set to 20ms because clock accuracy is | |
369 // ~15ms on Windows machines without a working high-resolution clock. See | |
370 // comments in base/time/time.h for details. | |
371 media::AudioShifter* const new_shifter = new media::AudioShifter( | |
372 base::TimeDelta::FromSeconds(5), base::TimeDelta::FromMilliseconds(20), | |
373 base::TimeDelta::FromSeconds(20), source_params_.sample_rate(), | |
374 source_params_.channels()); | |
375 | |
376 base::AutoLock auto_lock(thread_lock_); | |
377 audio_shifter_.reset(new_shifter); | |
378 } | |
379 | |
380 void TrackAudioRenderer::HaltAudioFlowWhileLockHeld() { | |
381 thread_lock_.AssertAcquired(); | |
382 | |
383 audio_shifter_.reset(); | |
384 | |
385 if (source_params_.IsValid()) { | |
386 prior_elapsed_render_time_ = | |
387 ComputeTotalElapsedRenderTime(prior_elapsed_render_time_, | |
388 num_samples_rendered_, | |
389 source_params_.sample_rate()); | |
390 num_samples_rendered_ = 0; | |
391 } | |
392 } | |
393 | |
356 } // namespace content | 394 } // namespace content |
OLD | NEW |