Chromium Code Reviews| 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() + |
| 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 DVLOG(1) << "TrackAudioRenderer::TrackAudioRenderer()"; |
| 113 } | 136 } |
| 114 | 137 |
| 115 WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() { | 138 TrackAudioRenderer::~TrackAudioRenderer() { |
| 116 DCHECK(task_runner_->BelongsToCurrentThread()); | 139 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 117 DCHECK(!sink_.get()); | 140 DCHECK(!sink_.get()); |
| 118 DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()"; | 141 DVLOG(1) << "TrackAudioRenderer::~TrackAudioRenderer()"; |
| 119 } | 142 } |
| 120 | 143 |
| 121 void WebRtcLocalAudioRenderer::Start() { | 144 void TrackAudioRenderer::Start() { |
| 122 DVLOG(1) << "WebRtcLocalAudioRenderer::Start()"; | 145 DVLOG(1) << "TrackAudioRenderer::Start()"; |
| 123 DCHECK(task_runner_->BelongsToCurrentThread()); | 146 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 124 | 147 |
| 125 // We get audio data from |audio_track_|... | 148 // We get audio data from |audio_track_|... |
| 126 MediaStreamAudioSink::AddToAudioTrack(this, audio_track_); | 149 MediaStreamAudioSink::AddToAudioTrack(this, audio_track_); |
| 127 // ...and |sink_| will get audio data from us. | 150 // ...and |sink_| will get audio data from us. |
| 128 DCHECK(!sink_.get()); | 151 DCHECK(!sink_.get()); |
| 129 sink_ = | 152 sink_ = |
| 130 AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_, | 153 AudioDeviceFactory::NewOutputDevice(playout_render_frame_id_, session_id_, |
| 131 output_device_id_, security_origin_); | 154 output_device_id_, security_origin_); |
| 155 sink_->SetVolume(volume_); | |
| 156 | |
| 157 playing_ = false; | |
|
tommi (sloooow) - chröme
2016/02/01 20:32:24
shouldn't this be assumed to be false already? i.e
miu
2016/02/03 03:48:45
Good point. Done.
| |
| 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 |
| 168 playing_ = false; | |
|
tommi (sloooow) - chröme
2016/02/01 20:32:24
Call Pause()?
miu
2016/02/03 03:48:45
Done.
| |
| 169 | |
| 142 { | 170 { |
| 143 base::AutoLock auto_lock(thread_lock_); | 171 base::AutoLock auto_lock(thread_lock_); |
| 144 playing_ = false; | 172 HaltAudioFlowWhileLockHeld(); |
| 145 audio_shifter_.reset(); | |
| 146 } | 173 } |
| 147 | 174 |
| 148 // Stop the output audio stream, i.e, stop asking for data to render. | 175 // 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 | 176 // It is safer to call Stop() on the |sink_| to clean up the resources even |
| 150 // when the |sink_| is never started. | 177 // when the |sink_| is never started. |
| 151 if (sink_.get()) { | 178 if (sink_.get()) { |
| 152 sink_->Stop(); | 179 sink_->Stop(); |
| 153 sink_ = NULL; | 180 sink_ = NULL; |
| 154 } | 181 } |
| 155 | 182 |
| 156 if (!sink_started_) { | 183 if (!sink_started_ && IsLocalRenderer()) { |
| 157 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", | 184 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", |
| 158 kSinkNeverStarted, kSinkStatesMax); | 185 kSinkNeverStarted, kSinkStatesMax); |
| 159 } | 186 } |
| 160 sink_started_ = false; | 187 sink_started_ = false; |
| 161 | 188 |
| 162 // Ensure that the capturer stops feeding us with captured audio. | 189 // Ensure that the capturer stops feeding us with captured audio. |
| 163 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_); | 190 MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_); |
| 164 } | 191 } |
| 165 | 192 |
| 166 void WebRtcLocalAudioRenderer::Play() { | 193 void TrackAudioRenderer::Play() { |
| 167 DVLOG(1) << "WebRtcLocalAudioRenderer::Play()"; | 194 DVLOG(1) << "TrackAudioRenderer::Play()"; |
| 168 DCHECK(task_runner_->BelongsToCurrentThread()); | 195 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 169 | 196 |
| 170 if (!sink_.get()) | 197 if (!sink_.get()) |
| 171 return; | 198 return; |
| 172 | 199 |
| 173 { | 200 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 | 201 |
| 181 // Note: If volume_ is currently muted, the |sink_| will not be started yet. | |
| 182 MaybeStartSink(); | 202 MaybeStartSink(); |
| 183 } | 203 } |
| 184 | 204 |
| 185 void WebRtcLocalAudioRenderer::Pause() { | 205 void TrackAudioRenderer::Pause() { |
| 186 DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()"; | 206 DVLOG(1) << "TrackAudioRenderer::Pause()"; |
| 187 DCHECK(task_runner_->BelongsToCurrentThread()); | 207 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 188 | 208 |
| 189 if (!sink_.get()) | 209 if (!sink_.get()) |
| 190 return; | 210 return; |
| 191 | 211 |
| 212 playing_ = false; | |
| 213 | |
| 192 base::AutoLock auto_lock(thread_lock_); | 214 base::AutoLock auto_lock(thread_lock_); |
| 193 // Temporarily suspends rendering audio. | 215 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 } | 216 } |
| 198 | 217 |
| 199 void WebRtcLocalAudioRenderer::SetVolume(float volume) { | 218 void TrackAudioRenderer::SetVolume(float volume) { |
| 200 DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume << ")"; | 219 DVLOG(1) << "TrackAudioRenderer::SetVolume(" << volume << ")"; |
| 220 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 221 | |
| 222 // Cache the volume. | |
|
tommi (sloooow) - chröme
2016/02/01 20:32:24
Maybe document why we do this instead?
miu
2016/02/03 03:48:45
Done.
| |
| 223 volume_ = volume; | |
| 224 if (sink_.get()) | |
| 225 sink_->SetVolume(volume); | |
| 226 } | |
| 227 | |
| 228 media::OutputDevice* TrackAudioRenderer::GetOutputDevice() { | |
| 229 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 230 return this; | |
| 231 } | |
| 232 | |
| 233 base::TimeDelta TrackAudioRenderer::GetCurrentRenderTime() const { | |
| 234 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 235 base::AutoLock auto_lock(thread_lock_); | |
| 236 if (source_params_.IsValid()) { | |
| 237 return ComputeTotalElapsedRenderTime(prior_elapsed_render_time_, | |
| 238 num_samples_rendered_, | |
| 239 source_params_.sample_rate()); | |
| 240 } | |
| 241 return prior_elapsed_render_time_; | |
| 242 } | |
| 243 | |
| 244 bool TrackAudioRenderer::IsLocalRenderer() const { | |
| 245 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 246 MediaStreamAudioTrack* const track = | |
| 247 MediaStreamAudioTrack::GetTrack(audio_track_); | |
| 248 CHECK(track); | |
|
tommi (sloooow) - chröme
2016/02/01 20:32:24
nit: not needed due to the next line
miu
2016/02/03 03:48:45
Cleaned this up: I moved the null-check to the con
| |
| 249 return track->is_local_track(); | |
| 250 } | |
| 251 | |
| 252 void TrackAudioRenderer::SwitchOutputDevice( | |
| 253 const std::string& device_id, | |
| 254 const url::Origin& security_origin, | |
| 255 const media::SwitchOutputDeviceCB& callback) { | |
| 256 DVLOG(1) << "TrackAudioRenderer::SwitchOutputDevice()"; | |
| 201 DCHECK(task_runner_->BelongsToCurrentThread()); | 257 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 202 | 258 |
| 203 { | 259 { |
| 204 base::AutoLock auto_lock(thread_lock_); | 260 base::AutoLock auto_lock(thread_lock_); |
| 205 // Cache the volume. | 261 HaltAudioFlowWhileLockHeld(); |
| 206 volume_ = volume; | |
| 207 } | 262 } |
| 208 | 263 |
| 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 = | 264 scoped_refptr<media::AudioOutputDevice> new_sink = |
| 242 AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_, | 265 AudioDeviceFactory::NewOutputDevice(playout_render_frame_id_, session_id_, |
| 243 device_id, security_origin); | 266 device_id, security_origin); |
| 244 if (new_sink->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) { | 267 if (new_sink->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) { |
| 245 callback.Run(new_sink->GetDeviceStatus()); | 268 callback.Run(new_sink->GetDeviceStatus()); |
| 246 return; | 269 return; |
| 247 } | 270 } |
| 248 | 271 |
| 249 output_device_id_ = device_id; | 272 output_device_id_ = device_id; |
| 250 security_origin_ = security_origin; | 273 security_origin_ = security_origin; |
| 251 bool was_sink_started = sink_started_; | 274 bool was_sink_started = sink_started_; |
| 252 | 275 |
| 253 if (sink_.get()) | 276 if (sink_.get()) |
| 254 sink_->Stop(); | 277 sink_->Stop(); |
| 255 | 278 |
| 256 sink_started_ = false; | 279 sink_started_ = false; |
| 257 sink_ = new_sink; | 280 sink_ = new_sink; |
| 258 int frames_per_buffer = sink_->GetOutputParameters().frames_per_buffer(); | 281 sink_->SetVolume(volume_); |
| 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) | 282 if (was_sink_started) |
| 264 MaybeStartSink(); | 283 MaybeStartSink(); |
| 265 | 284 |
| 266 callback.Run(media::OUTPUT_DEVICE_STATUS_OK); | 285 callback.Run(media::OUTPUT_DEVICE_STATUS_OK); |
| 267 } | 286 } |
| 268 | 287 |
| 269 media::AudioParameters WebRtcLocalAudioRenderer::GetOutputParameters() { | 288 media::AudioParameters TrackAudioRenderer::GetOutputParameters() { |
| 270 DCHECK(task_runner_->BelongsToCurrentThread()); | 289 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 271 if (!sink_.get()) | 290 if (!sink_ || !source_params_.IsValid()) |
| 272 return media::AudioParameters(); | 291 return media::AudioParameters(); |
| 273 | 292 |
| 274 return sink_->GetOutputParameters(); | 293 // Output parameters consist of the same channel layout and sample rate as the |
| 294 // source, but having the buffer duration preferred by the hardware. | |
| 295 const media::AudioParameters& preferred_params = sink_->GetOutputParameters(); | |
| 296 return media::AudioParameters( | |
| 297 preferred_params.format(), source_params_.channel_layout(), | |
| 298 source_params_.sample_rate(), source_params_.bits_per_sample(), | |
| 299 preferred_params.frames_per_buffer() * source_params_.sample_rate() / | |
| 300 preferred_params.sample_rate()); | |
| 275 } | 301 } |
| 276 | 302 |
| 277 media::OutputDeviceStatus WebRtcLocalAudioRenderer::GetDeviceStatus() { | 303 media::OutputDeviceStatus TrackAudioRenderer::GetDeviceStatus() { |
| 278 DCHECK(task_runner_->BelongsToCurrentThread()); | 304 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 279 if (!sink_.get()) | 305 if (!sink_.get()) |
| 280 return media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL; | 306 return media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL; |
| 281 | 307 |
| 282 return sink_->GetDeviceStatus(); | 308 return sink_->GetDeviceStatus(); |
| 283 } | 309 } |
| 284 | 310 |
| 285 void WebRtcLocalAudioRenderer::MaybeStartSink() { | 311 void TrackAudioRenderer::MaybeStartSink() { |
| 286 DCHECK(task_runner_->BelongsToCurrentThread()); | 312 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 287 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink()"; | 313 DVLOG(1) << "TrackAudioRenderer::MaybeStartSink()"; |
| 288 | 314 |
| 289 if (!sink_.get() || !source_params_.IsValid()) | 315 if (!sink_.get() || !source_params_.IsValid() || !playing_) |
| 290 return; | 316 return; |
| 291 | 317 |
| 292 { | 318 // Re-create the AudioShifter to drop old audio data and reset to a starting |
| 293 // Clear up the old data in the FIFO. | 319 // state. MaybeStartSink() is always called in a situation where either the |
| 294 base::AutoLock auto_lock(thread_lock_); | 320 // source or sink has changed somehow and so all of AudioShifter's internal |
| 295 audio_shifter_->Flush(); | 321 // time-sync state is invalid. |
| 296 } | 322 CreateAudioShifter(); |
| 297 | 323 |
| 298 if (!sink_params_.IsValid() || !playing_ || !volume_ || sink_started_ || | 324 if (sink_started_ || |
| 299 sink_->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) | 325 sink_->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) |
|
tommi (sloooow) - chröme
2016/02/01 20:32:24
nit: add {}?
miu
2016/02/03 03:48:45
Done.
| |
| 300 return; | 326 return; |
| 301 | 327 |
| 302 DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink() -- Starting sink_."; | 328 DVLOG(1) << ("TrackAudioRenderer::MaybeStartSink() -- Starting sink. " |
| 303 sink_->Initialize(sink_params_, this); | 329 "source_params_={") |
| 330 << source_params_.AsHumanReadableString() << "}, sink parameters={" | |
| 331 << GetOutputParameters().AsHumanReadableString() << '}'; | |
| 332 sink_->Initialize(GetOutputParameters(), this); | |
| 304 sink_->Start(); | 333 sink_->Start(); |
| 305 sink_started_ = true; | 334 sink_started_ = true; |
| 306 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", | 335 if (IsLocalRenderer()) { |
| 307 kSinkStarted, kSinkStatesMax); | 336 UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", kSinkStarted, |
| 337 kSinkStatesMax); | |
| 338 } | |
| 308 } | 339 } |
| 309 | 340 |
| 310 void WebRtcLocalAudioRenderer::ReconfigureSink( | 341 void TrackAudioRenderer::ReconfigureSink(const media::AudioParameters& params) { |
| 311 const media::AudioParameters& params) { | |
| 312 DCHECK(task_runner_->BelongsToCurrentThread()); | 342 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 313 | 343 |
| 314 DVLOG(1) << "WebRtcLocalAudioRenderer::ReconfigureSink()"; | 344 DVLOG(1) << "TrackAudioRenderer::ReconfigureSink()"; |
| 315 | 345 |
| 316 if (source_params_.Equals(params)) | 346 if (source_params_.Equals(params)) |
| 317 return; | 347 return; |
| 318 | |
| 319 // Reset the |source_params_|, |sink_params_| and |loopback_fifo_| to match | |
| 320 // the new format. | |
| 321 | |
| 322 source_params_ = params; | 348 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 | 349 |
| 339 if (!sink_.get()) | 350 if (!sink_.get()) |
| 340 return; // WebRtcLocalAudioRenderer has not yet been started. | 351 return; // TrackAudioRenderer has not yet been started. |
| 341 | 352 |
| 342 // Stop |sink_| and re-create a new one to be initialized with different audio | 353 // Stop |sink_| and re-create a new one to be initialized with different audio |
| 343 // parameters. Then, invoke MaybeStartSink() to restart everything again. | 354 // parameters. Then, invoke MaybeStartSink() to restart everything again. |
| 344 sink_->Stop(); | 355 sink_->Stop(); |
| 345 sink_started_ = false; | 356 sink_started_ = false; |
| 346 sink_ = | 357 sink_ = |
| 347 AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_, | 358 AudioDeviceFactory::NewOutputDevice(playout_render_frame_id_, session_id_, |
| 348 output_device_id_, security_origin_); | 359 output_device_id_, security_origin_); |
| 349 int frames_per_buffer = sink_->GetOutputParameters().frames_per_buffer(); | 360 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(); | 361 MaybeStartSink(); |
| 354 } | 362 } |
| 355 | 363 |
| 364 void TrackAudioRenderer::CreateAudioShifter() { | |
| 365 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 366 | |
| 367 // Note 1: The max buffer is fairly large to cover the case where | |
| 368 // remotely-sourced audio is delivered well ahead of its scheduled playout | |
| 369 // time (e.g., content streaming with a very large end-to-end | |
| 370 // latency). However, there is no penalty for making it large in the | |
| 371 // low-latency use cases since AudioShifter will discard data as soon as it is | |
| 372 // no longer needed. | |
| 373 // | |
| 374 // Note 2: The clock accuracy is set to 20ms because clock accuracy is | |
| 375 // ~15ms on Windows machines without a working high-resolution clock. See | |
| 376 // comments in base/time/time.h for details. | |
| 377 media::AudioShifter* const new_shifter = new media::AudioShifter( | |
|
tommi (sloooow) - chröme
2016/02/01 20:32:24
thanks for keeping the scope of the lock down :)
miu
2016/02/03 03:48:45
Acknowledged.
| |
| 378 base::TimeDelta::FromSeconds(5), base::TimeDelta::FromMilliseconds(20), | |
| 379 base::TimeDelta::FromSeconds(20), source_params_.sample_rate(), | |
| 380 source_params_.channels()); | |
| 381 | |
| 382 base::AutoLock auto_lock(thread_lock_); | |
| 383 audio_shifter_.reset(new_shifter); | |
| 384 } | |
| 385 | |
| 386 void TrackAudioRenderer::HaltAudioFlowWhileLockHeld() { | |
| 387 thread_lock_.AssertAcquired(); | |
| 388 | |
| 389 audio_shifter_.reset(); | |
| 390 | |
| 391 if (source_params_.IsValid()) { | |
| 392 prior_elapsed_render_time_ = | |
| 393 ComputeTotalElapsedRenderTime(prior_elapsed_render_time_, | |
| 394 num_samples_rendered_, | |
| 395 source_params_.sample_rate()); | |
| 396 num_samples_rendered_ = 0; | |
| 397 } | |
| 398 } | |
| 399 | |
| 356 } // namespace content | 400 } // namespace content |
| OLD | NEW |