| Index: content/renderer/media/track_audio_renderer.cc
|
| diff --git a/content/renderer/media/webrtc_local_audio_renderer.cc b/content/renderer/media/track_audio_renderer.cc
|
| similarity index 39%
|
| rename from content/renderer/media/webrtc_local_audio_renderer.cc
|
| rename to content/renderer/media/track_audio_renderer.cc
|
| index f39a017b94db441164f527fba3b8790aca8d46b0..10826eb7375a881c394c20a373c232bad2283082 100644
|
| --- a/content/renderer/media/webrtc_local_audio_renderer.cc
|
| +++ b/content/renderer/media/track_audio_renderer.cc
|
| @@ -2,9 +2,7 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| -#include "content/renderer/media/webrtc_local_audio_renderer.h"
|
| -
|
| -#include <utility>
|
| +#include "content/renderer/media/track_audio_renderer.h"
|
|
|
| #include "base/location.h"
|
| #include "base/logging.h"
|
| @@ -13,10 +11,7 @@
|
| #include "base/thread_task_runner_handle.h"
|
| #include "base/trace_event/trace_event.h"
|
| #include "content/renderer/media/audio_device_factory.h"
|
| -#include "content/renderer/media/media_stream_dispatcher.h"
|
| -#include "content/renderer/media/webrtc_audio_capturer.h"
|
| -#include "content/renderer/media/webrtc_audio_renderer.h"
|
| -#include "content/renderer/render_frame_impl.h"
|
| +#include "content/renderer/media/media_stream_audio_track.h"
|
| #include "media/audio/audio_output_device.h"
|
| #include "media/base/audio_bus.h"
|
| #include "media/base/audio_shifter.h"
|
| @@ -31,119 +26,145 @@ enum LocalRendererSinkStates {
|
| kSinkStatesMax // Must always be last!
|
| };
|
|
|
| +// Translates |num_samples_rendered| into a TimeDelta duration and adds it to
|
| +// |prior_elapsed_render_time|.
|
| +base::TimeDelta ComputeTotalElapsedRenderTime(
|
| + base::TimeDelta prior_elapsed_render_time,
|
| + int64_t num_samples_rendered,
|
| + int sample_rate) {
|
| + return prior_elapsed_render_time + base::TimeDelta::FromMicroseconds(
|
| + num_samples_rendered * base::Time::kMicrosecondsPerSecond / sample_rate);
|
| +}
|
| +
|
| } // namespace
|
|
|
| // media::AudioRendererSink::RenderCallback implementation
|
| -int WebRtcLocalAudioRenderer::Render(media::AudioBus* audio_bus,
|
| - uint32_t audio_delay_milliseconds,
|
| - uint32_t frames_skipped) {
|
| - TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::Render");
|
| +int TrackAudioRenderer::Render(media::AudioBus* audio_bus,
|
| + uint32_t audio_delay_milliseconds,
|
| + uint32_t frames_skipped) {
|
| + TRACE_EVENT0("audio", "TrackAudioRenderer::Render");
|
| base::AutoLock auto_lock(thread_lock_);
|
|
|
| - if (!playing_ || !volume_ || !audio_shifter_) {
|
| + if (!audio_shifter_) {
|
| audio_bus->Zero();
|
| return 0;
|
| }
|
|
|
| - audio_shifter_->Pull(
|
| - audio_bus,
|
| - base::TimeTicks::Now() -
|
| - base::TimeDelta::FromMilliseconds(audio_delay_milliseconds));
|
| -
|
| + // TODO(miu): Plumbing is needed to determine the actual playout timestamp
|
| + // of the audio, instead of just snapshotting TimeTicks::Now(), for proper
|
| + // audio/video sync. http://crbug.com/335335
|
| + const base::TimeTicks playout_time =
|
| + base::TimeTicks::Now() +
|
| + base::TimeDelta::FromMilliseconds(audio_delay_milliseconds);
|
| + DVLOG(2) << "Pulling audio out of shifter to be played "
|
| + << audio_delay_milliseconds << " ms from now.";
|
| + audio_shifter_->Pull(audio_bus, playout_time);
|
| + num_samples_rendered_ += audio_bus->frames();
|
| return audio_bus->frames();
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::OnRenderError() {
|
| +void TrackAudioRenderer::OnRenderError() {
|
| NOTIMPLEMENTED();
|
| }
|
|
|
| // content::MediaStreamAudioSink implementation
|
| -void WebRtcLocalAudioRenderer::OnData(const media::AudioBus& audio_bus,
|
| - base::TimeTicks estimated_capture_time) {
|
| - DCHECK(capture_thread_checker_.CalledOnValidThread());
|
| - DCHECK(!estimated_capture_time.is_null());
|
| +void TrackAudioRenderer::OnData(const media::AudioBus& audio_bus,
|
| + base::TimeTicks reference_time) {
|
| + DCHECK(audio_thread_checker_.CalledOnValidThread());
|
| + DCHECK(!reference_time.is_null());
|
|
|
| - TRACE_EVENT0("audio", "WebRtcLocalAudioRenderer::CaptureData");
|
| + TRACE_EVENT0("audio", "TrackAudioRenderer::CaptureData");
|
|
|
| base::AutoLock auto_lock(thread_lock_);
|
| - if (!playing_ || !volume_ || !audio_shifter_)
|
| + if (!audio_shifter_)
|
| return;
|
|
|
| scoped_ptr<media::AudioBus> audio_data(
|
| media::AudioBus::Create(audio_bus.channels(), audio_bus.frames()));
|
| audio_bus.CopyTo(audio_data.get());
|
| - audio_shifter_->Push(std::move(audio_data), estimated_capture_time);
|
| - const base::TimeTicks now = base::TimeTicks::Now();
|
| - total_render_time_ += now - last_render_time_;
|
| - last_render_time_ = now;
|
| + // Note: For remote audio sources, |reference_time| is the local playout time,
|
| + // the ideal point-in-time at which the first audio sample should be played
|
| + // out in the future. For local sources, |reference_time| is the
|
| + // point-in-time at which the first audio sample was captured in the past. In
|
| + // either case, AudioShifter will auto-detect and do the right thing when
|
| + // audio is pulled from it.
|
| + audio_shifter_->Push(std::move(audio_data), reference_time);
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::OnSetFormat(
|
| - const media::AudioParameters& params) {
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::OnSetFormat()";
|
| +void TrackAudioRenderer::OnSetFormat(const media::AudioParameters& params) {
|
| + DVLOG(1) << "TrackAudioRenderer::OnSetFormat()";
|
| // If the source is restarted, we might have changed to another capture
|
| // thread.
|
| - capture_thread_checker_.DetachFromThread();
|
| - DCHECK(capture_thread_checker_.CalledOnValidThread());
|
| + audio_thread_checker_.DetachFromThread();
|
| + DCHECK(audio_thread_checker_.CalledOnValidThread());
|
| +
|
| + // If the parameters changed, the audio in the AudioShifter is invalid and
|
| + // should be dropped.
|
| + {
|
| + base::AutoLock auto_lock(thread_lock_);
|
| + if (audio_shifter_ &&
|
| + (audio_shifter_->sample_rate() != params.sample_rate() ||
|
| + audio_shifter_->channels() != params.channels())) {
|
| + HaltAudioFlowWhileLockHeld();
|
| + }
|
| + }
|
|
|
| // Post a task on the main render thread to reconfigure the |sink_| with the
|
| // new format.
|
| task_runner_->PostTask(
|
| FROM_HERE,
|
| - base::Bind(&WebRtcLocalAudioRenderer::ReconfigureSink, this, params));
|
| + base::Bind(&TrackAudioRenderer::ReconfigureSink, this, params));
|
| }
|
|
|
| -// WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer implementation.
|
| -WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer(
|
| +TrackAudioRenderer::TrackAudioRenderer(
|
| const blink::WebMediaStreamTrack& audio_track,
|
| - int source_render_frame_id,
|
| + int playout_render_frame_id,
|
| int session_id,
|
| const std::string& device_id,
|
| const url::Origin& security_origin)
|
| : audio_track_(audio_track),
|
| - source_render_frame_id_(source_render_frame_id),
|
| + playout_render_frame_id_(playout_render_frame_id),
|
| session_id_(session_id),
|
| task_runner_(base::ThreadTaskRunnerHandle::Get()),
|
| + num_samples_rendered_(0),
|
| playing_(false),
|
| output_device_id_(device_id),
|
| security_origin_(security_origin),
|
| volume_(0.0),
|
| sink_started_(false) {
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::WebRtcLocalAudioRenderer()";
|
| + DCHECK(MediaStreamAudioTrack::GetTrack(audio_track_));
|
| + DVLOG(1) << "TrackAudioRenderer::TrackAudioRenderer()";
|
| }
|
|
|
| -WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer() {
|
| +TrackAudioRenderer::~TrackAudioRenderer() {
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
| DCHECK(!sink_.get());
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::~WebRtcLocalAudioRenderer()";
|
| + DVLOG(1) << "TrackAudioRenderer::~TrackAudioRenderer()";
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::Start() {
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::Start()";
|
| +void TrackAudioRenderer::Start() {
|
| + DVLOG(1) << "TrackAudioRenderer::Start()";
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(playing_, false);
|
|
|
| // We get audio data from |audio_track_|...
|
| MediaStreamAudioSink::AddToAudioTrack(this, audio_track_);
|
| // ...and |sink_| will get audio data from us.
|
| DCHECK(!sink_.get());
|
| sink_ =
|
| - AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_,
|
| + AudioDeviceFactory::NewOutputDevice(playout_render_frame_id_, session_id_,
|
| output_device_id_, security_origin_);
|
|
|
| base::AutoLock auto_lock(thread_lock_);
|
| - last_render_time_ = base::TimeTicks::Now();
|
| - playing_ = false;
|
| + prior_elapsed_render_time_ = base::TimeDelta();
|
| + num_samples_rendered_ = 0;
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::Stop() {
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::Stop()";
|
| +void TrackAudioRenderer::Stop() {
|
| + DVLOG(1) << "TrackAudioRenderer::Stop()";
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
|
|
| - {
|
| - base::AutoLock auto_lock(thread_lock_);
|
| - playing_ = false;
|
| - audio_shifter_.reset();
|
| - }
|
| + Pause();
|
|
|
| // Stop the output audio stream, i.e, stop asking for data to render.
|
| // It is safer to call Stop() on the |sink_| to clean up the resources even
|
| @@ -153,7 +174,7 @@ void WebRtcLocalAudioRenderer::Stop() {
|
| sink_ = NULL;
|
| }
|
|
|
| - if (!sink_started_) {
|
| + if (!sink_started_ && IsLocalRenderer()) {
|
| UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates",
|
| kSinkNeverStarted, kSinkStatesMax);
|
| }
|
| @@ -163,83 +184,77 @@ void WebRtcLocalAudioRenderer::Stop() {
|
| MediaStreamAudioSink::RemoveFromAudioTrack(this, audio_track_);
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::Play() {
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::Play()";
|
| +void TrackAudioRenderer::Play() {
|
| + DVLOG(1) << "TrackAudioRenderer::Play()";
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
|
|
| if (!sink_.get())
|
| return;
|
|
|
| - {
|
| - base::AutoLock auto_lock(thread_lock_);
|
| - // Resumes rendering by ensuring that WebRtcLocalAudioRenderer::Render()
|
| - // now reads data from the local FIFO.
|
| - playing_ = true;
|
| - last_render_time_ = base::TimeTicks::Now();
|
| - }
|
| + playing_ = true;
|
|
|
| - // Note: If volume_ is currently muted, the |sink_| will not be started yet.
|
| MaybeStartSink();
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::Pause() {
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::Pause()";
|
| +void TrackAudioRenderer::Pause() {
|
| + DVLOG(1) << "TrackAudioRenderer::Pause()";
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
|
|
| if (!sink_.get())
|
| return;
|
|
|
| - base::AutoLock auto_lock(thread_lock_);
|
| - // Temporarily suspends rendering audio.
|
| - // WebRtcLocalAudioRenderer::Render() will return early during this state
|
| - // and only zeros will be provided to the active sink.
|
| playing_ = false;
|
| +
|
| + base::AutoLock auto_lock(thread_lock_);
|
| + HaltAudioFlowWhileLockHeld();
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::SetVolume(float volume) {
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::SetVolume(" << volume << ")";
|
| +void TrackAudioRenderer::SetVolume(float volume) {
|
| + DVLOG(1) << "TrackAudioRenderer::SetVolume(" << volume << ")";
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
|
|
| - {
|
| - base::AutoLock auto_lock(thread_lock_);
|
| - // Cache the volume.
|
| - volume_ = volume;
|
| - }
|
| -
|
| - // Lazily start the |sink_| when the local renderer is unmuted during
|
| - // playing.
|
| - MaybeStartSink();
|
| -
|
| + // Cache the volume. Whenever |sink_| is re-created, call SetVolume() with
|
| + // this cached volume.
|
| + volume_ = volume;
|
| if (sink_.get())
|
| sink_->SetVolume(volume);
|
| }
|
|
|
| -media::OutputDevice* WebRtcLocalAudioRenderer::GetOutputDevice() {
|
| +media::OutputDevice* TrackAudioRenderer::GetOutputDevice() {
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
| return this;
|
| }
|
|
|
| -base::TimeDelta WebRtcLocalAudioRenderer::GetCurrentRenderTime() const {
|
| +base::TimeDelta TrackAudioRenderer::GetCurrentRenderTime() const {
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
| base::AutoLock auto_lock(thread_lock_);
|
| - if (!sink_.get())
|
| - return base::TimeDelta();
|
| - return total_render_time();
|
| + if (source_params_.IsValid()) {
|
| + return ComputeTotalElapsedRenderTime(prior_elapsed_render_time_,
|
| + num_samples_rendered_,
|
| + source_params_.sample_rate());
|
| + }
|
| + return prior_elapsed_render_time_;
|
| }
|
|
|
| -bool WebRtcLocalAudioRenderer::IsLocalRenderer() const {
|
| - return true;
|
| +bool TrackAudioRenderer::IsLocalRenderer() const {
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + return MediaStreamAudioTrack::GetTrack(audio_track_)->is_local_track();
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::SwitchOutputDevice(
|
| +void TrackAudioRenderer::SwitchOutputDevice(
|
| const std::string& device_id,
|
| const url::Origin& security_origin,
|
| const media::SwitchOutputDeviceCB& callback) {
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::SwitchOutputDevice()";
|
| + DVLOG(1) << "TrackAudioRenderer::SwitchOutputDevice()";
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
|
|
| + {
|
| + base::AutoLock auto_lock(thread_lock_);
|
| + HaltAudioFlowWhileLockHeld();
|
| + }
|
| +
|
| scoped_refptr<media::AudioOutputDevice> new_sink =
|
| - AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_,
|
| + AudioDeviceFactory::NewOutputDevice(playout_render_frame_id_, session_id_,
|
| device_id, security_origin);
|
| if (new_sink->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) {
|
| callback.Run(new_sink->GetDeviceStatus());
|
| @@ -255,26 +270,28 @@ void WebRtcLocalAudioRenderer::SwitchOutputDevice(
|
|
|
| sink_started_ = false;
|
| sink_ = new_sink;
|
| - int frames_per_buffer = sink_->GetOutputParameters().frames_per_buffer();
|
| - sink_params_ = source_params_;
|
| - sink_params_.set_frames_per_buffer(WebRtcAudioRenderer::GetOptimalBufferSize(
|
| - source_params_.sample_rate(), frames_per_buffer));
|
| -
|
| if (was_sink_started)
|
| MaybeStartSink();
|
|
|
| callback.Run(media::OUTPUT_DEVICE_STATUS_OK);
|
| }
|
|
|
| -media::AudioParameters WebRtcLocalAudioRenderer::GetOutputParameters() {
|
| +media::AudioParameters TrackAudioRenderer::GetOutputParameters() {
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
| - if (!sink_.get())
|
| + if (!sink_ || !source_params_.IsValid())
|
| return media::AudioParameters();
|
|
|
| - return sink_->GetOutputParameters();
|
| + // Output parameters consist of the same channel layout and sample rate as the
|
| + // source, but having the buffer duration preferred by the hardware.
|
| + const media::AudioParameters& preferred_params = sink_->GetOutputParameters();
|
| + return media::AudioParameters(
|
| + preferred_params.format(), source_params_.channel_layout(),
|
| + source_params_.sample_rate(), source_params_.bits_per_sample(),
|
| + preferred_params.frames_per_buffer() * source_params_.sample_rate() /
|
| + preferred_params.sample_rate());
|
| }
|
|
|
| -media::OutputDeviceStatus WebRtcLocalAudioRenderer::GetDeviceStatus() {
|
| +media::OutputDeviceStatus TrackAudioRenderer::GetDeviceStatus() {
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
| if (!sink_.get())
|
| return media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL;
|
| @@ -282,75 +299,94 @@ media::OutputDeviceStatus WebRtcLocalAudioRenderer::GetDeviceStatus() {
|
| return sink_->GetDeviceStatus();
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::MaybeStartSink() {
|
| +void TrackAudioRenderer::MaybeStartSink() {
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink()";
|
| + DVLOG(1) << "TrackAudioRenderer::MaybeStartSink()";
|
|
|
| - if (!sink_.get() || !source_params_.IsValid())
|
| + if (!sink_.get() || !source_params_.IsValid() || !playing_)
|
| return;
|
|
|
| - {
|
| - // Clear up the old data in the FIFO.
|
| - base::AutoLock auto_lock(thread_lock_);
|
| - audio_shifter_->Flush();
|
| - }
|
| + // Re-create the AudioShifter to drop old audio data and reset to a starting
|
| + // state. MaybeStartSink() is always called in a situation where either the
|
| + // source or sink has changed somehow and so all of AudioShifter's internal
|
| + // time-sync state is invalid.
|
| + CreateAudioShifter();
|
|
|
| - if (!sink_params_.IsValid() || !playing_ || !volume_ || sink_started_ ||
|
| - sink_->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK)
|
| + if (sink_started_ ||
|
| + sink_->GetDeviceStatus() != media::OUTPUT_DEVICE_STATUS_OK) {
|
| return;
|
| + }
|
|
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::MaybeStartSink() -- Starting sink_.";
|
| - sink_->Initialize(sink_params_, this);
|
| + DVLOG(1) << ("TrackAudioRenderer::MaybeStartSink() -- Starting sink. "
|
| + "source_params_={")
|
| + << source_params_.AsHumanReadableString() << "}, sink parameters={"
|
| + << GetOutputParameters().AsHumanReadableString() << '}';
|
| + sink_->Initialize(GetOutputParameters(), this);
|
| sink_->Start();
|
| + sink_->SetVolume(volume_);
|
| sink_started_ = true;
|
| - UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates",
|
| - kSinkStarted, kSinkStatesMax);
|
| + if (IsLocalRenderer()) {
|
| + UMA_HISTOGRAM_ENUMERATION("Media.LocalRendererSinkStates", kSinkStarted,
|
| + kSinkStatesMax);
|
| + }
|
| }
|
|
|
| -void WebRtcLocalAudioRenderer::ReconfigureSink(
|
| - const media::AudioParameters& params) {
|
| +void TrackAudioRenderer::ReconfigureSink(const media::AudioParameters& params) {
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
|
|
| - DVLOG(1) << "WebRtcLocalAudioRenderer::ReconfigureSink()";
|
| + DVLOG(1) << "TrackAudioRenderer::ReconfigureSink()";
|
|
|
| if (source_params_.Equals(params))
|
| return;
|
| -
|
| - // Reset the |source_params_|, |sink_params_| and |loopback_fifo_| to match
|
| - // the new format.
|
| -
|
| source_params_ = params;
|
| - {
|
| - // Note: The max buffer is fairly large, but will rarely be used.
|
| - // Cast needs the buffer to hold at least one second of audio.
|
| - // The clock accuracy is set to 20ms because clock accuracy is
|
| - // ~15ms on windows.
|
| - media::AudioShifter* const new_shifter = new media::AudioShifter(
|
| - base::TimeDelta::FromSeconds(2),
|
| - base::TimeDelta::FromMilliseconds(20),
|
| - base::TimeDelta::FromSeconds(20),
|
| - source_params_.sample_rate(),
|
| - params.channels());
|
| -
|
| - base::AutoLock auto_lock(thread_lock_);
|
| - audio_shifter_.reset(new_shifter);
|
| - }
|
|
|
| if (!sink_.get())
|
| - return; // WebRtcLocalAudioRenderer has not yet been started.
|
| + return; // TrackAudioRenderer has not yet been started.
|
|
|
| // Stop |sink_| and re-create a new one to be initialized with different audio
|
| // parameters. Then, invoke MaybeStartSink() to restart everything again.
|
| sink_->Stop();
|
| sink_started_ = false;
|
| sink_ =
|
| - AudioDeviceFactory::NewOutputDevice(source_render_frame_id_, session_id_,
|
| + AudioDeviceFactory::NewOutputDevice(playout_render_frame_id_, session_id_,
|
| output_device_id_, security_origin_);
|
| - int frames_per_buffer = sink_->GetOutputParameters().frames_per_buffer();
|
| - sink_params_ = source_params_;
|
| - sink_params_.set_frames_per_buffer(WebRtcAudioRenderer::GetOptimalBufferSize(
|
| - source_params_.sample_rate(), frames_per_buffer));
|
| MaybeStartSink();
|
| }
|
|
|
| +void TrackAudioRenderer::CreateAudioShifter() {
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +
|
| + // Note 1: The max buffer is fairly large to cover the case where
|
| + // remotely-sourced audio is delivered well ahead of its scheduled playout
|
| + // time (e.g., content streaming with a very large end-to-end
|
| + // latency). However, there is no penalty for making it large in the
|
| + // low-latency use cases since AudioShifter will discard data as soon as it is
|
| + // no longer needed.
|
| + //
|
| + // Note 2: The clock accuracy is set to 20ms because clock accuracy is
|
| + // ~15ms on Windows machines without a working high-resolution clock. See
|
| + // comments in base/time/time.h for details.
|
| + media::AudioShifter* const new_shifter = new media::AudioShifter(
|
| + base::TimeDelta::FromSeconds(5), base::TimeDelta::FromMilliseconds(20),
|
| + base::TimeDelta::FromSeconds(20), source_params_.sample_rate(),
|
| + source_params_.channels());
|
| +
|
| + base::AutoLock auto_lock(thread_lock_);
|
| + audio_shifter_.reset(new_shifter);
|
| +}
|
| +
|
| +void TrackAudioRenderer::HaltAudioFlowWhileLockHeld() {
|
| + thread_lock_.AssertAcquired();
|
| +
|
| + audio_shifter_.reset();
|
| +
|
| + if (source_params_.IsValid()) {
|
| + prior_elapsed_render_time_ =
|
| + ComputeTotalElapsedRenderTime(prior_elapsed_render_time_,
|
| + num_samples_rendered_,
|
| + source_params_.sample_rate());
|
| + num_samples_rendered_ = 0;
|
| + }
|
| +}
|
| +
|
| } // namespace content
|
|
|