Index: media/base/pipeline_impl.cc |
diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc |
index 38a835b17af3248a968cb8650ca0b88ea908389c..bb23fd26e1e515ffa5e3dc06116138d8258eac83 100644 |
--- a/media/base/pipeline_impl.cc |
+++ b/media/base/pipeline_impl.cc |
@@ -29,9 +29,21 @@ |
#include "media/base/timestamp_constants.h" |
#include "media/base/video_decoder_config.h" |
+namespace { |
static const double kDefaultPlaybackRate = 0.0; |
static const float kDefaultVolume = 1.0f; |
+base::TimeDelta CurrentMediaTime(base::TimeDelta min_time, |
+ base::TimeDelta max_time, |
+ base::TimeTicks ref_time, |
+ base::TimeTicks curr_time, |
+ double playback_rate) { |
+ DCHECK(!ref_time.is_null()); |
+ return std::min(min_time + (curr_time - ref_time) * playback_rate, max_time); |
+} |
+ |
+} // namespace |
+ |
namespace media { |
class PipelineImpl::RendererWrapper : public DemuxerHost, |
@@ -51,7 +63,9 @@ class PipelineImpl::RendererWrapper : public DemuxerHost, |
void Resume(std::unique_ptr<Renderer> renderer, base::TimeDelta time); |
void SetPlaybackRate(double playback_rate); |
void SetVolume(float volume); |
- base::TimeDelta GetMediaTime() const; |
+ void GetMediaTimeExtents(base::TimeDelta* time_min, |
+ base::TimeDelta* time_max, |
+ base::TimeTicks* time_reference) const; |
Ranges<base::TimeDelta> GetBufferedTimeRanges() const; |
bool DidLoadingProgress(); |
PipelineStatistics GetStatistics() const; |
@@ -78,10 +92,6 @@ class PipelineImpl::RendererWrapper : public DemuxerHost, |
// frequently than needed. Posting all those notifications to the main thread |
// causes performance issues: crbug.com/619975. |
struct SharedState { |
- // TODO(scherkus): Enforce that Renderer is only called on a single thread, |
- // even for accessing media time http://crbug.com/370634 |
- std::unique_ptr<Renderer> renderer; |
- |
// True when OnBufferedTimeRangesChanged() has been called more recently |
// than DidLoadingProgress(). |
bool did_loading_progress = false; |
@@ -92,9 +102,10 @@ class PipelineImpl::RendererWrapper : public DemuxerHost, |
// Accumulated statistics reported by the renderer. |
PipelineStatistics statistics; |
- // The media timestamp to return while the pipeline is suspended. |
- // Otherwise set to kNoTimestamp. |
- base::TimeDelta suspend_timestamp = kNoTimestamp; |
+ // Media time extents reported by the renderer. |
+ base::TimeDelta media_time_min; |
+ base::TimeDelta media_time_max; |
+ base::TimeTicks media_time_reference; |
}; |
// DemuxerHost implementaion. |
@@ -113,6 +124,9 @@ class PipelineImpl::RendererWrapper : public DemuxerHost, |
void OnWaitingForDecryptionKey() final; |
void OnVideoNaturalSizeChange(const gfx::Size& size) final; |
void OnVideoOpacityChange(bool opaque) final; |
+ void OnTimeUpdate(base::TimeDelta curr_time, |
+ base::TimeDelta max_time, |
+ base::TimeTicks capture_time) final; |
// TextRenderer tasks and notifications. |
void OnTextRendererEnded(); |
@@ -133,7 +147,6 @@ class PipelineImpl::RendererWrapper : public DemuxerHost, |
void CompleteSuspend(PipelineStatus status); |
void InitializeDemuxer(const PipelineStatusCB& done_cb); |
void InitializeRenderer(const PipelineStatusCB& done_cb); |
- void DestroyRenderer(); |
void ReportMetadata(); |
const scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_; |
@@ -142,6 +155,7 @@ class PipelineImpl::RendererWrapper : public DemuxerHost, |
base::WeakPtr<PipelineImpl> weak_pipeline_; |
Demuxer* demuxer_; |
+ std::unique_ptr<Renderer> renderer_; |
std::unique_ptr<TextRenderer> text_renderer_; |
double playback_rate_; |
float volume_; |
@@ -216,16 +230,13 @@ void PipelineImpl::RendererWrapper::Start( |
SetState(kStarting); |
DCHECK(!demuxer_); |
- DCHECK(!shared_state_.renderer); |
+ DCHECK(!renderer_); |
DCHECK(!text_renderer_); |
DCHECK(!renderer_ended_); |
DCHECK(!text_renderer_ended_); |
DCHECK(!weak_pipeline_); |
demuxer_ = demuxer; |
- { |
- base::AutoLock auto_lock(shared_state_lock_); |
- shared_state_.renderer = std::move(renderer); |
- } |
+ renderer_ = std::move(renderer); |
text_renderer_ = std::move(text_renderer); |
if (text_renderer_) { |
text_renderer_->Initialize( |
@@ -270,9 +281,8 @@ void PipelineImpl::RendererWrapper::Stop(const base::Closure& stop_cb) { |
// tasks. |
pending_callbacks_.reset(); |
- DestroyRenderer(); |
+ renderer_.reset(); |
text_renderer_.reset(); |
- |
if (demuxer_) { |
demuxer_->Stop(); |
demuxer_ = NULL; |
@@ -300,8 +310,6 @@ void PipelineImpl::RendererWrapper::Seek(base::TimeDelta time) { |
return; |
} |
- base::TimeDelta seek_timestamp = std::max(time, demuxer_->GetStartTime()); |
- |
SetState(kSeeking); |
renderer_ended_ = false; |
text_renderer_ended_ = false; |
@@ -317,9 +325,9 @@ void PipelineImpl::RendererWrapper::Seek(base::TimeDelta time) { |
} |
// Flush. |
- DCHECK(shared_state_.renderer); |
- bound_fns.Push(base::Bind(&Renderer::Flush, |
- base::Unretained(shared_state_.renderer.get()))); |
+ DCHECK(renderer_); |
+ bound_fns.Push( |
+ base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); |
if (text_renderer_) { |
bound_fns.Push(base::Bind(&TextRenderer::Flush, |
@@ -327,13 +335,14 @@ void PipelineImpl::RendererWrapper::Seek(base::TimeDelta time) { |
} |
// Seek demuxer. |
+ base::TimeDelta seek_time = std::max(time, demuxer_->GetStartTime()); |
bound_fns.Push( |
- base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp)); |
+ base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), seek_time)); |
// Run tasks. |
pending_callbacks_ = SerialRunner::Run( |
bound_fns, |
- base::Bind(&RendererWrapper::CompleteSeek, weak_this_, seek_timestamp)); |
+ base::Bind(&RendererWrapper::CompleteSeek, weak_this_, seek_time)); |
} |
void PipelineImpl::RendererWrapper::Suspend() { |
@@ -346,18 +355,20 @@ void PipelineImpl::RendererWrapper::Suspend() { |
OnPipelineError(PIPELINE_ERROR_INVALID_STATE); |
return; |
} |
- DCHECK(shared_state_.renderer); |
+ DCHECK(renderer_); |
DCHECK(!pending_callbacks_.get()); |
SetState(kSuspending); |
// Freeze playback and record the media time before flushing. (Flushing clears |
// the value.) |
- shared_state_.renderer->SetPlaybackRate(0.0); |
+ renderer_->SetPlaybackRate(0.0); |
+ base::TimeDelta media_time = renderer_->GetMediaTime(); |
{ |
base::AutoLock auto_lock(shared_state_lock_); |
- shared_state_.suspend_timestamp = shared_state_.renderer->GetMediaTime(); |
- DCHECK(shared_state_.suspend_timestamp != kNoTimestamp); |
+ shared_state_.media_time_min = media_time; |
+ shared_state_.media_time_max = media_time; |
+ shared_state_.media_time_reference = base::TimeTicks(); |
} |
// Queue the asynchronous actions required to stop playback. |
@@ -368,8 +379,7 @@ void PipelineImpl::RendererWrapper::Suspend() { |
base::Unretained(text_renderer_.get()))); |
} |
- fns.Push(base::Bind(&Renderer::Flush, |
- base::Unretained(shared_state_.renderer.get()))); |
+ fns.Push(base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); |
if (text_renderer_) { |
fns.Push(base::Bind(&TextRenderer::Flush, |
@@ -391,16 +401,12 @@ void PipelineImpl::RendererWrapper::Resume(std::unique_ptr<Renderer> renderer, |
OnPipelineError(PIPELINE_ERROR_INVALID_STATE); |
return; |
} |
- DCHECK(!shared_state_.renderer); |
+ DCHECK(!renderer_); |
DCHECK(!pending_callbacks_.get()); |
SetState(kResuming); |
- { |
- base::AutoLock auto_lock(shared_state_lock_); |
- shared_state_.renderer = std::move(renderer); |
- } |
- |
+ renderer_ = std::move(renderer); |
renderer_ended_ = false; |
text_renderer_ended_ = false; |
base::TimeDelta start_timestamp = |
@@ -422,9 +428,18 @@ void PipelineImpl::RendererWrapper::Resume(std::unique_ptr<Renderer> renderer, |
void PipelineImpl::RendererWrapper::SetPlaybackRate(double playback_rate) { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
+ if (!shared_state_.media_time_reference.is_null()) { |
+ base::AutoLock auto_lock(shared_state_lock_); |
+ base::TimeTicks now_time = base::TimeTicks::Now(); |
+ shared_state_.media_time_min = CurrentMediaTime( |
+ shared_state_.media_time_min, shared_state_.media_time_max, |
+ shared_state_.media_time_reference, now_time, playback_rate_); |
+ shared_state_.media_time_reference = now_time; |
+ } |
+ |
playback_rate_ = playback_rate; |
if (state_ == kPlaying) |
- shared_state_.renderer->SetPlaybackRate(playback_rate_); |
+ renderer_->SetPlaybackRate(playback_rate_); |
} |
void PipelineImpl::RendererWrapper::SetVolume(float volume) { |
@@ -432,17 +447,18 @@ void PipelineImpl::RendererWrapper::SetVolume(float volume) { |
volume_ = volume; |
if (state_ == kPlaying) |
- shared_state_.renderer->SetVolume(volume_); |
+ renderer_->SetVolume(volume_); |
} |
-base::TimeDelta PipelineImpl::RendererWrapper::GetMediaTime() const { |
- DCHECK(main_task_runner_->BelongsToCurrentThread()); |
- |
+void PipelineImpl::RendererWrapper::GetMediaTimeExtents( |
+ base::TimeDelta* time_min, |
+ base::TimeDelta* time_max, |
+ base::TimeTicks* time_reference) const { |
base::AutoLock auto_lock(shared_state_lock_); |
- if (shared_state_.suspend_timestamp != kNoTimestamp) |
- return shared_state_.suspend_timestamp; |
- return shared_state_.renderer ? shared_state_.renderer->GetMediaTime() |
- : base::TimeDelta(); |
+ |
+ *time_min = shared_state_.media_time_min; |
+ *time_max = shared_state_.media_time_max; |
+ *time_reference = shared_state_.media_time_reference; |
} |
Ranges<base::TimeDelta> PipelineImpl::RendererWrapper::GetBufferedTimeRanges() |
@@ -474,15 +490,15 @@ void PipelineImpl::RendererWrapper::SetCdm( |
const CdmAttachedCB& cdm_attached_cb) { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
- if (!shared_state_.renderer) { |
+ if (!renderer_) { |
cdm_context_ = cdm_context; |
cdm_attached_cb.Run(true); |
return; |
} |
- shared_state_.renderer->SetCdm( |
- cdm_context, base::Bind(&RendererWrapper::OnCdmAttached, weak_this_, |
- cdm_attached_cb, cdm_context)); |
+ renderer_->SetCdm(cdm_context, |
+ base::Bind(&RendererWrapper::OnCdmAttached, weak_this_, |
+ cdm_attached_cb, cdm_context)); |
} |
void PipelineImpl::RendererWrapper::OnBufferedTimeRangesChanged( |
@@ -582,11 +598,10 @@ void PipelineImpl::RendererWrapper::OnEnabledAudioTracksChanged( |
} |
DCHECK(demuxer_); |
- DCHECK(shared_state_.renderer); |
+ DCHECK(renderer_); |
- base::TimeDelta currTime = (state_ == kPlaying) |
- ? shared_state_.renderer->GetMediaTime() |
- : demuxer_->GetStartTime(); |
+ base::TimeDelta currTime = (state_ == kPlaying) ? renderer_->GetMediaTime() |
+ : demuxer_->GetStartTime(); |
demuxer_->OnEnabledAudioTracksChanged(enabledTrackIds, currTime); |
} |
@@ -601,11 +616,10 @@ void PipelineImpl::RendererWrapper::OnSelectedVideoTrackChanged( |
} |
DCHECK(demuxer_); |
- DCHECK(shared_state_.renderer); |
+ DCHECK(renderer_); |
- base::TimeDelta currTime = (state_ == kPlaying) |
- ? shared_state_.renderer->GetMediaTime() |
- : demuxer_->GetStartTime(); |
+ base::TimeDelta currTime = (state_ == kPlaying) ? renderer_->GetMediaTime() |
+ : demuxer_->GetStartTime(); |
demuxer_->OnSelectedVideoTrackChanged(selectedTrackId, currTime); |
} |
@@ -658,6 +672,19 @@ void PipelineImpl::RendererWrapper::OnVideoOpacityChange(bool opaque) { |
base::Bind(&PipelineImpl::OnVideoOpacityChange, weak_pipeline_, opaque)); |
} |
+void PipelineImpl::RendererWrapper::OnTimeUpdate(base::TimeDelta curr_time, |
+ base::TimeDelta max_time, |
+ base::TimeTicks capture_time) { |
+ DVLOG(4) << __func__ << "(" << curr_time.InMicroseconds() << ", " |
+ << max_time.InMicroseconds() << ", " << capture_time << ")"; |
+ DCHECK(media_task_runner_->BelongsToCurrentThread()); |
+ |
+ base::AutoLock auto_lock(shared_state_lock_); |
+ shared_state_.media_time_min = curr_time; |
+ shared_state_.media_time_max = max_time; |
+ shared_state_.media_time_reference = capture_time; |
+} |
+ |
void PipelineImpl::RendererWrapper::OnTextRendererEnded() { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED)); |
@@ -727,7 +754,7 @@ void PipelineImpl::RendererWrapper::OnCdmAttached( |
void PipelineImpl::RendererWrapper::CheckPlaybackEnded() { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
- if (shared_state_.renderer && !renderer_ended_) |
+ if (renderer_ && !renderer_ended_) |
return; |
if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_) |
@@ -760,18 +787,20 @@ void PipelineImpl::RendererWrapper::CompleteSeek(base::TimeDelta seek_time, |
return; |
} |
- shared_state_.renderer->StartPlayingFrom( |
- std::max(seek_time, demuxer_->GetStartTime())); |
- { |
- base::AutoLock auto_lock(shared_state_lock_); |
- shared_state_.suspend_timestamp = kNoTimestamp; |
- } |
+ seek_time = std::max(seek_time, demuxer_->GetStartTime()); |
+ renderer_->StartPlayingFrom(seek_time); |
+ renderer_->SetPlaybackRate(playback_rate_); |
+ renderer_->SetVolume(volume_); |
if (text_renderer_) |
text_renderer_->StartPlaying(); |
- shared_state_.renderer->SetPlaybackRate(playback_rate_); |
- shared_state_.renderer->SetVolume(volume_); |
+ { |
+ base::AutoLock auto_lock(shared_state_lock_); |
+ shared_state_.media_time_min = seek_time; |
+ shared_state_.media_time_max = seek_time; |
+ shared_state_.media_time_reference = base::TimeTicks(); |
+ } |
SetState(kPlaying); |
main_task_runner_->PostTask( |
@@ -791,7 +820,7 @@ void PipelineImpl::RendererWrapper::CompleteSuspend(PipelineStatus status) { |
LOG_IF(WARNING, status != PIPELINE_OK) |
<< "Encountered pipeline error while suspending: " << status; |
- DestroyRenderer(); |
+ renderer_.reset(); |
{ |
base::AutoLock auto_lock(shared_state_lock_); |
shared_state_.statistics.audio_memory_usage = 0; |
@@ -821,22 +850,9 @@ void PipelineImpl::RendererWrapper::InitializeRenderer( |
} |
if (cdm_context_) |
- shared_state_.renderer->SetCdm(cdm_context_, |
- base::Bind(&IgnoreCdmAttached)); |
+ renderer_->SetCdm(cdm_context_, base::Bind(&IgnoreCdmAttached)); |
- shared_state_.renderer->Initialize(demuxer_, this, done_cb); |
-} |
- |
-void PipelineImpl::RendererWrapper::DestroyRenderer() { |
- DCHECK(media_task_runner_->BelongsToCurrentThread()); |
- |
- // Destroy the renderer outside the lock scope to avoid holding the lock |
- // while renderer is being destroyed (in case Renderer destructor is costly). |
- std::unique_ptr<Renderer> renderer; |
- { |
- base::AutoLock auto_lock(shared_state_lock_); |
- renderer.swap(shared_state_.renderer); |
- } |
+ renderer_->Initialize(demuxer_, this, done_cb); |
} |
void PipelineImpl::RendererWrapper::ReportMetadata() { |
@@ -979,6 +995,8 @@ void PipelineImpl::Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb) { |
DCHECK(seek_cb_.is_null()); |
seek_cb_ = seek_cb; |
+ media_time_last_ = base::TimeDelta(); |
+ |
media_task_runner_->PostTask( |
FROM_HERE, base::Bind(&RendererWrapper::Seek, |
base::Unretained(renderer_wrapper_.get()), time)); |
@@ -1008,6 +1026,7 @@ void PipelineImpl::Resume(std::unique_ptr<Renderer> renderer, |
DCHECK(IsRunning()); |
DCHECK(seek_cb_.is_null()); |
seek_cb_ = seek_cb; |
+ media_time_last_ = base::TimeDelta(); |
media_task_runner_->PostTask( |
FROM_HERE, base::Bind(&RendererWrapper::Resume, |
@@ -1060,7 +1079,34 @@ void PipelineImpl::SetVolume(float volume) { |
base::TimeDelta PipelineImpl::GetMediaTime() const { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- return renderer_wrapper_->GetMediaTime(); |
+ |
+ base::TimeDelta time_min, time_max; |
+ base::TimeTicks time_ref; |
+ renderer_wrapper_->GetMediaTimeExtents(&time_min, &time_max, &time_ref); |
+ |
+ // Return the current time based on the known extents of media data plus an |
+ // estimate based on the last time those values were calculated. |
+ base::TimeDelta media_time = |
+ time_ref.is_null() |
+ ? time_min |
+ : CurrentMediaTime(time_min, time_max, time_ref, |
+ base::TimeTicks::Now(), playback_rate_); |
+ |
+ // Clamp current media time to the last reported value, this prevents higher |
+ // level clients from seeing time go backwards based on inaccurate or spurious |
+ // delay values reported to the AudioClock. |
+ // |
+ // It is expected that such events are transient and will be recovered as |
+ // rendering continues over time. |
+ if (media_time < media_time_last_) { |
+ DVLOG(2) << __func__ << ": " << media_time_last_ |
+ << " (clamped), actual: " << media_time; |
+ return media_time_last_; |
+ } |
+ |
+ DVLOG(2) << __func__ << ": " << media_time; |
+ media_time_last_ = media_time; |
+ return media_time; |
} |
Ranges<base::TimeDelta> PipelineImpl::GetBufferedTimeRanges() const { |