summaryrefslogtreecommitdiff
path: root/chromium/media/renderers/video_renderer_impl.cc
blob: 10568c1df88c0cfc5bc1eb3e1e0041ce7e71e124 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/renderers/video_renderer_impl.h"

#include <algorithm>
#include <utility>

#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/feature_list.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/time/default_tick_clock.h"
#include "base/trace_event/trace_event.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/pipeline_status.h"
#include "media/base/renderer_client.h"
#include "media/base/video_frame.h"

namespace media {

namespace {

// Maximum number of frames we will buffer, regardless of their "effectiveness".
// See HaveReachedBufferingCap(). The value was historically described in terms
// of |min_buffered_frames_| as follows:
// = 3 * high_water_mark(min_buffered_frames_),
// = 3 * (2 * limits::kMaxVideoFrames)
// = 3 * 2 * 4
// Today, |min_buffered_frames_| can go down (as low as 1) and up in response to
// SetLatencyHint(), so we needed to peg this with a constant.
constexpr int kAbsoluteMaxFrames = 24;

bool ShouldUseLowDelayMode(DemuxerStream* stream) {
  return base::FeatureList::IsEnabled(kLowDelayVideoRenderingOnLiveStream) &&
         stream->liveness() == DemuxerStream::LIVENESS_LIVE;
}

}  // namespace

VideoRendererImpl::VideoRendererImpl(
    const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
    VideoRendererSink* sink,
    const CreateVideoDecodersCB& create_video_decoders_cb,
    bool drop_frames,
    MediaLog* media_log,
    std::unique_ptr<GpuMemoryBufferVideoFramePool> gmb_pool)
    : task_runner_(media_task_runner),
      sink_(sink),
      sink_started_(false),
      client_(nullptr),
      gpu_memory_buffer_pool_(std::move(gmb_pool)),
      media_log_(media_log),
      low_delay_(false),
      received_end_of_stream_(false),
      rendered_end_of_stream_(false),
      state_(kUninitialized),
      create_video_decoders_cb_(create_video_decoders_cb),
      pending_read_(false),
      drop_frames_(drop_frames),
      buffering_state_(BUFFERING_HAVE_NOTHING),
      tick_clock_(base::DefaultTickClock::GetInstance()),
      was_background_rendering_(false),
      time_progressing_(false),
      have_renderered_frames_(false),
      last_frame_opaque_(false),
      painted_first_frame_(false),
      min_buffered_frames_(initial_buffering_size_.value()),
      max_buffered_frames_(initial_buffering_size_.value()) {
  DCHECK(create_video_decoders_cb_);
}

VideoRendererImpl::~VideoRendererImpl() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  if (init_cb_)
    FinishInitialization(PIPELINE_ERROR_ABORT);

  if (flush_cb_)
    FinishFlush();

  if (sink_started_)
    StopSink();
}

void VideoRendererImpl::Flush(base::OnceClosure callback) {
  DVLOG(1) << __func__;
  DCHECK(task_runner_->BelongsToCurrentThread());

  if (sink_started_)
    StopSink();

  base::AutoLock auto_lock(lock_);

  DCHECK_EQ(state_, kPlaying);
  flush_cb_ = std::move(callback);
  state_ = kFlushing;

  if (buffering_state_ != BUFFERING_HAVE_NOTHING) {
    buffering_state_ = BUFFERING_HAVE_NOTHING;
    task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&VideoRendererImpl::OnBufferingStateChange,
                       weak_factory_.GetWeakPtr(), buffering_state_));
  }
  received_end_of_stream_ = false;
  rendered_end_of_stream_ = false;

  // Reset |video_decoder_stream_| and drop any pending read callbacks from it.
  pending_read_ = false;
  if (gpu_memory_buffer_pool_)
    gpu_memory_buffer_pool_->Abort();
  cancel_on_flush_weak_factory_.InvalidateWeakPtrs();
  video_decoder_stream_->Reset(
      base::BindOnce(&VideoRendererImpl::OnVideoDecoderStreamResetDone,
                     weak_factory_.GetWeakPtr()));

  // To avoid unnecessary work by VDAs, only delete queued frames after
  // resetting |video_decoder_stream_|. If this is done in the opposite order
  // VDAs will get a bunch of ReusePictureBuffer() calls before the Reset(),
  // which they may use to output more frames that won't be used.
  algorithm_->Reset();
  painted_first_frame_ = false;

  // Reset preroll capacity so seek time is not penalized. |latency_hint_|
  // and |low_delay_| mode disable automatic preroll adjustments.
  if (!latency_hint_.has_value() && !low_delay_) {
    min_buffered_frames_ = max_buffered_frames_ =
        initial_buffering_size_.value();
  }
}

void VideoRendererImpl::StartPlayingFrom(base::TimeDelta timestamp) {
  DVLOG(1) << __func__ << "(" << timestamp.InMicroseconds() << ")";
  DCHECK(task_runner_->BelongsToCurrentThread());
  base::AutoLock auto_lock(lock_);
  DCHECK_EQ(state_, kFlushed);
  DCHECK(!pending_read_);
  DCHECK_EQ(buffering_state_, BUFFERING_HAVE_NOTHING);

  state_ = kPlaying;
  start_timestamp_ = timestamp;
  painted_first_frame_ = false;
  last_render_time_ = last_frame_ready_time_ = base::TimeTicks();
  video_decoder_stream_->SkipPrepareUntil(start_timestamp_);
  AttemptRead_Locked();
}

void VideoRendererImpl::Initialize(
    DemuxerStream* stream,
    CdmContext* cdm_context,
    RendererClient* client,
    const TimeSource::WallClockTimeCB& wall_clock_time_cb,
    PipelineStatusCallback init_cb) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT_ASYNC_BEGIN0("media", "VideoRendererImpl::Initialize", this);

  base::AutoLock auto_lock(lock_);
  DCHECK(stream);
  DCHECK_EQ(stream->type(), DemuxerStream::VIDEO);
  DCHECK(init_cb);
  DCHECK(wall_clock_time_cb);
  DCHECK(kUninitialized == state_ || kFlushed == state_);
  DCHECK(!was_background_rendering_);
  DCHECK(!time_progressing_);

  demuxer_stream_ = stream;

  video_decoder_stream_.reset(new VideoDecoderStream(
      std::make_unique<VideoDecoderStream::StreamTraits>(media_log_),
      task_runner_, create_video_decoders_cb_, media_log_));
  video_decoder_stream_->set_config_change_observer(base::BindRepeating(
      &VideoRendererImpl::OnConfigChange, weak_factory_.GetWeakPtr()));
  if (gpu_memory_buffer_pool_) {
    video_decoder_stream_->SetPrepareCB(base::BindRepeating(
        &GpuMemoryBufferVideoFramePool::MaybeCreateHardwareFrame,
        // Safe since VideoDecoderStream won't issue calls after destruction.
        base::Unretained(gpu_memory_buffer_pool_.get())));
  }

  low_delay_ = ShouldUseLowDelayMode(demuxer_stream_);
  UMA_HISTOGRAM_BOOLEAN("Media.VideoRenderer.LowDelay", low_delay_);
  if (low_delay_) {
    MEDIA_LOG(DEBUG, media_log_) << "Video rendering in low delay mode.";

    // "Low delay mode" means only one frame must be buffered to transition to
    // BUFFERING_HAVE_ENOUGH.
    min_buffered_frames_ = 1;
  }

  // Always post |init_cb_| because |this| could be destroyed if initialization
  // failed.
  init_cb_ = BindToCurrentLoop(std::move(init_cb));

  client_ = client;
  wall_clock_time_cb_ = wall_clock_time_cb;
  state_ = kInitializing;

  current_decoder_config_ = demuxer_stream_->video_decoder_config();
  DCHECK(current_decoder_config_.IsValidConfig());

  video_decoder_stream_->Initialize(
      demuxer_stream_,
      base::BindOnce(&VideoRendererImpl::OnVideoDecoderStreamInitialized,
                     weak_factory_.GetWeakPtr()),
      cdm_context,
      base::BindRepeating(&VideoRendererImpl::OnStatisticsUpdate,
                          weak_factory_.GetWeakPtr()),
      base::BindRepeating(&VideoRendererImpl::OnWaiting,
                          weak_factory_.GetWeakPtr()));
}

scoped_refptr<VideoFrame> VideoRendererImpl::Render(
    base::TimeTicks deadline_min,
    base::TimeTicks deadline_max,
    bool background_rendering) {
  TRACE_EVENT_BEGIN1("media", "VideoRendererImpl::Render", "id",
                     media_log_->id());
  base::AutoLock auto_lock(lock_);
  DCHECK_EQ(state_, kPlaying);
  last_render_time_ = tick_clock_->NowTicks();

  size_t frames_dropped = 0;
  scoped_refptr<VideoFrame> result =
      algorithm_->Render(deadline_min, deadline_max, &frames_dropped);

  // Due to how the |algorithm_| holds frames, this should never be null if
  // we've had a proper startup sequence.
  DCHECK(result);

  // Declare HAVE_NOTHING if we reach a state where we can't progress playback
  // any further.  We don't want to do this if we've already done so, reached
  // end of stream, or have frames available.  We also don't want to do this in
  // background rendering mode, as the frames aren't visible anyways.
  MaybeFireEndedCallback_Locked(true);
  if (buffering_state_ == BUFFERING_HAVE_ENOUGH && !received_end_of_stream_ &&
      !algorithm_->effective_frames_queued() && !background_rendering &&
      !was_background_rendering_) {
    // Do not set |buffering_state_| here as the lock in FrameReady() may be
    // held already and it fire the state changes in the wrong order.
    DVLOG(3) << __func__ << " posted TransitionToHaveNothing.";
    task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&VideoRendererImpl::TransitionToHaveNothing,
                                  weak_factory_.GetWeakPtr()));
  }

  // We don't count dropped frames in the background to avoid skewing the count
  // and impacting JavaScript visible metrics used by web developers.
  //
  // Just after resuming from background rendering, we also don't count the
  // dropped frames since they are likely just dropped due to being too old.
  if (!background_rendering && !was_background_rendering_)
    stats_.video_frames_dropped += frames_dropped;
  was_background_rendering_ = background_rendering;

  // Always post this task, it will acquire new frames if necessary and since it
  // happens on another thread, even if we don't have room in the queue now, by
  // the time it runs (may be delayed up to 50ms for complex decodes!) we might.
  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&VideoRendererImpl::AttemptReadAndCheckForMetadataChanges,
                     weak_factory_.GetWeakPtr(), result->format(),
                     result->natural_size()));

  TRACE_EVENT_END1("media", "VideoRendererImpl::Render", "frame",
                   result->AsHumanReadableString());
  return result;
}

void VideoRendererImpl::OnFrameDropped() {
  base::AutoLock auto_lock(lock_);
  algorithm_->OnLastFrameDropped();
}

base::TimeDelta VideoRendererImpl::GetPreferredRenderInterval() {
  base::AutoLock auto_lock(lock_);
  return algorithm_->average_frame_duration();
}

void VideoRendererImpl::OnVideoDecoderStreamInitialized(bool success) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  base::AutoLock auto_lock(lock_);
  DCHECK_EQ(state_, kInitializing);

  if (!success) {
    state_ = kUninitialized;
    FinishInitialization(DECODER_ERROR_NOT_SUPPORTED);
    return;
  }

  // We're all good! Consider ourselves flushed because we have not read any
  // frames yet.
  state_ = kFlushed;

  algorithm_.reset(new VideoRendererAlgorithm(wall_clock_time_cb_, media_log_));
  if (!drop_frames_)
    algorithm_->disable_frame_dropping();

  FinishInitialization(PIPELINE_OK);
}

void VideoRendererImpl::FinishInitialization(PipelineStatus status) {
  DCHECK(init_cb_);
  TRACE_EVENT_ASYNC_END1("media", "VideoRendererImpl::Initialize", this,
                         "status", PipelineStatusToString(status));
  std::move(init_cb_).Run(status);
}

void VideoRendererImpl::FinishFlush() {
  DCHECK(flush_cb_);
  TRACE_EVENT_ASYNC_END0("media", "VideoRendererImpl::Flush", this);
  std::move(flush_cb_).Run();
}

void VideoRendererImpl::OnPlaybackError(PipelineStatus error) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  client_->OnError(error);
}

void VideoRendererImpl::OnPlaybackEnded() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  {
    // Send one last stats update so things like memory usage are correct.
    base::AutoLock auto_lock(lock_);
    UpdateStats_Locked(true);
  }

  client_->OnEnded();
}

void VideoRendererImpl::OnStatisticsUpdate(const PipelineStatistics& stats) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  client_->OnStatisticsUpdate(stats);
}

void VideoRendererImpl::OnBufferingStateChange(BufferingState buffering_state) {
  DCHECK(task_runner_->BelongsToCurrentThread());

  // "Underflow" is only possible when playing. This avoids noise like blaming
  // the decoder for an "underflow" that is really just a seek.
  BufferingStateChangeReason reason = BUFFERING_CHANGE_REASON_UNKNOWN;
  if (state_ == kPlaying && buffering_state == BUFFERING_HAVE_NOTHING) {
    reason = video_decoder_stream_->is_demuxer_read_pending()
                 ? DEMUXER_UNDERFLOW
                 : DECODER_UNDERFLOW;
  }

  media_log_->AddEvent<MediaLogEvent::kBufferingStateChanged>(
      SerializableBufferingState<SerializableBufferingStateType::kVideo>{
          buffering_state, reason});

  client_->OnBufferingStateChange(buffering_state, reason);
}

void VideoRendererImpl::OnWaiting(WaitingReason reason) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  client_->OnWaiting(reason);
}

void VideoRendererImpl::OnConfigChange(const VideoDecoderConfig& config) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  DCHECK(config.IsValidConfig());

  // RendererClient only cares to know about config changes that differ from
  // previous configs.
  if (!current_decoder_config_.Matches(config)) {
    current_decoder_config_ = config;
    client_->OnVideoConfigChange(config);
  }
}

void VideoRendererImpl::SetTickClockForTesting(
    const base::TickClock* tick_clock) {
  tick_clock_ = tick_clock;
}

void VideoRendererImpl::OnTimeProgressing() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  // WARNING: Do not attempt to use |lock_| here as StartSink() may cause a
  // reentrant call.

  time_progressing_ = true;

  if (sink_started_)
    return;

  // If only an EOS frame came in after a seek, the renderer may not have
  // received the ended event yet though we've posted it.
  if (rendered_end_of_stream_)
    return;

  // If we have no frames queued, there is a pending buffering state change in
  // flight and we should ignore the start attempt.
  if (!algorithm_->frames_queued()) {
    DCHECK_EQ(buffering_state_, BUFFERING_HAVE_NOTHING);
    return;
  }

  StartSink();
}

void VideoRendererImpl::OnTimeStopped() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  // WARNING: Do not attempt to use |lock_| here as StopSink() may cause a
  // reentrant call.

  time_progressing_ = false;

  if (!sink_started_)
    return;

  StopSink();

  // Make sure we expire everything we can if we can't read any more currently,
  // otherwise playback may hang indefinitely.  Note: There are no effective
  // frames queued at this point, otherwise FrameReady() would have canceled
  // the underflow state before reaching this point.
  if (buffering_state_ == BUFFERING_HAVE_NOTHING) {
    base::AutoLock al(lock_);
    RemoveFramesForUnderflowOrBackgroundRendering();

    // If we've underflowed, increase the number of frames required to reach
    // BUFFERING_HAVE_ENOUGH upon resume; this will help prevent us from
    // repeatedly underflowing. Providing a |latency_hint_| or enabling
    // |low_delay_| mode disables automatic increases. In these cases the site
    // is expressing a desire to manually control/minimize the buffering
    // threshold for HAVE_ENOUGH.
    const size_t kMaxUnderflowGrowth = 2 * initial_buffering_size_.value();
    if (!latency_hint_.has_value() && !low_delay_) {
      DCHECK_EQ(min_buffered_frames_, max_buffered_frames_);

      if (min_buffered_frames_ < kMaxUnderflowGrowth) {
        min_buffered_frames_++;
        DVLOG(2) << __func__ << " Underflow! Increased min_buffered_frames_: "
                 << min_buffered_frames_;
      }
    }

    // Increase |max_buffered_frames_| irrespective of |latency_hint_| and
    // |low_delay_| mode. Unlike |min_buffered_frames_|, this does not affect
    // the buffering threshold for HAVE_ENOUGH. When max > min, the renderer can
    // buffer frames _beyond_ the HAVE_ENOUGH threshold (assuming decoder is
    // fast enough), which still helps reduce the likelihood of repeat
    // underflow.
    if (max_buffered_frames_ < kMaxUnderflowGrowth) {
      max_buffered_frames_++;
      DVLOG(2) << __func__ << " Underflow! Increased max_buffered_frames_: "
               << max_buffered_frames_;
    }
  }
}

void VideoRendererImpl::SetLatencyHint(
    base::Optional<base::TimeDelta> latency_hint) {
  base::AutoLock auto_lock(lock_);

  latency_hint_ = latency_hint;

  // Permanently disable implicit |low_delay_| mode. Apps using latencyHint
  // are taking manual control of how buffering works. Unsetting the hint
  // will make rendering behave as if |low_delay_| were never set.
  low_delay_ = false;

  if (!latency_hint_.has_value()) {
    // Restore default values.
    // NOTE |initial_buffering_size_| the default max, not the max overall.
    min_buffered_frames_ = max_buffered_frames_ =
        initial_buffering_size_.value();
    MEDIA_LOG(DEBUG, media_log_)
        << "Video latency hint cleared. Default buffer size ("
        << min_buffered_frames_ << " frames) restored";
  } else if (latency_hint_->is_zero()) {
    // Zero is a special case implying the bare minimum buffering (1 frame).
    // We apply the hint here outside of UpdateLatencyHintBufferingCaps_Locked()
    // to avoid needless churn since the "bare minimum" buffering doesn't
    // fluctuate with changes to FPS.
    min_buffered_frames_ = 1;
    max_buffered_frames_ = initial_buffering_size_.value();
    MEDIA_LOG(DEBUG, media_log_)
        << "Video latency hint set:" << *latency_hint << ". "
        << "Effective buffering latency: 1 frame";
  } else {
    // Non-zero latency hints are set here. Update buffering caps immediately if
    // we already have an algorithm_. Otherwise, the update will be applied as
    // frames arrive and duration becomes known. The caps will be recalculated
    // for each frame in case |average_frame_druation| changes.
    // |is_latency_hint_media_logged_| ensures that we only MEDIA_LOG on the
    // first application of this hint.
    is_latency_hint_media_logged_ = false;
    if (algorithm_) {
      UpdateLatencyHintBufferingCaps_Locked(
          algorithm_->average_frame_duration());
    }
  }
}

void VideoRendererImpl::UpdateLatencyHintBufferingCaps_Locked(
    base::TimeDelta average_frame_duration) {
  lock_.AssertAcquired();

  // NOTE: this method may be called for every frame. Only perform trivial
  // tasks.

  // This method should only be called for non-zero latency hints. Zero is hard
  // coded to 1 frame inside SetLatencyHint().
  DCHECK(latency_hint_.has_value() && !latency_hint_->is_zero());

  // For hints > 0, we need |average_frame_duration| to determine how many
  // frames would yield the specified target latency. This method will be called
  // again as |average_frame_duration| changes.
  if (average_frame_duration.is_zero())
    return;

  int latency_hint_frames =
      base::ClampRound(*latency_hint_ / average_frame_duration);

  std::string clamp_string;
  if (latency_hint_frames > kAbsoluteMaxFrames) {
    min_buffered_frames_ = kAbsoluteMaxFrames;
    clamp_string = " (clamped to max)";
  } else if (latency_hint_frames < 1) {
    min_buffered_frames_ = 1;
    clamp_string = " (clamped to min)";
  } else {
    min_buffered_frames_ = latency_hint_frames;
  }

  // Use initial capacity limit if possible. Increase if needed.
  max_buffered_frames_ =
      std::max(min_buffered_frames_, initial_buffering_size_.value());

  if (!is_latency_hint_media_logged_) {
    is_latency_hint_media_logged_ = true;
    MEDIA_LOG(DEBUG, media_log_)
        << "Video latency hint set:" << *latency_hint_ << ". "
        << "Effective buffering latency:"
        << (min_buffered_frames_ * average_frame_duration) << clamp_string;
  }
}

void VideoRendererImpl::FrameReady(VideoDecoderStream::ReadResult result) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  base::AutoLock auto_lock(lock_);
  DCHECK_EQ(state_, kPlaying);
  CHECK(pending_read_);
  pending_read_ = false;

  // Can happen when demuxers are preparing for a new Seek().
  switch (result.code()) {
    case StatusCode::kOk:
      break;
    case StatusCode::kAborted:
      // TODO(liberato): This used to check specifically for the value
      // DEMUXER_READ_ABORTED, which was more specific than |kAborted|.
      // However, since it's a dcheck, this seems okay.
      return;
    default:
      DCHECK(result.has_error());
      // Anything other than `kOk` or `kAborted` is treated as an error.
      task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&VideoRendererImpl::OnPlaybackError,
                         weak_factory_.GetWeakPtr(), PIPELINE_ERROR_DECODE));
      return;
  }

  DCHECK(result.has_value());
  scoped_refptr<VideoFrame> frame = std::move(result).value();
  DCHECK(frame);

  last_frame_ready_time_ = tick_clock_->NowTicks();
  last_decoder_stream_avg_duration_ = video_decoder_stream_->AverageDuration();

  const bool is_eos = frame->metadata().end_of_stream;
  const bool is_before_start_time = !is_eos && IsBeforeStartTime(*frame);
  const bool cant_read = !video_decoder_stream_->CanReadWithoutStalling();

  if (is_eos) {
    DCHECK(!received_end_of_stream_);
    received_end_of_stream_ = true;
    fps_estimator_.Reset();
    ReportFrameRateIfNeeded_Locked();
  } else if ((min_buffered_frames_ == 1 || cant_read) && is_before_start_time) {
    // Don't accumulate frames that are earlier than the start time if we
    // won't have a chance for a better frame, otherwise we could declare
    // HAVE_ENOUGH_DATA and start playback prematurely.
    fps_estimator_.Reset();
    ReportFrameRateIfNeeded_Locked();
    AttemptRead_Locked();
    return;
  } else {
    // If the sink hasn't been started, we still have time to release less
    // than ideal frames prior to startup.  We don't use IsBeforeStartTime()
    // here since it's based on a duration estimate and we can be exact here.
    if (!sink_started_ && frame->timestamp() <= start_timestamp_) {
      algorithm_->Reset();
      fps_estimator_.Reset();
      ReportFrameRateIfNeeded_Locked();
    }

    // Provide frame duration information so that even if we only have one frame
    // in the queue we can properly estimate duration. This allows the call to
    // RemoveFramesForUnderflowOrBackgroundRendering() below to actually expire
    // this frame if it's too far behind the current media time. Without this,
    // we may resume too soon after a track change in the low delay case.
    if (!frame->metadata().frame_duration.has_value())
      frame->metadata().frame_duration = last_decoder_stream_avg_duration_;

    AddReadyFrame_Locked(std::move(frame));
  }

  // Attempt to purge bad frames in case of underflow or backgrounding.
  RemoveFramesForUnderflowOrBackgroundRendering();

  // Paint the first frame if possible and necessary. Paint ahead of
  // HAVE_ENOUGH_DATA to ensure the user sees the frame as early as possible.
  // Paint before calling algorithm_->average_frame_duration(), as the call to
  // Render() will trigger internal duration updates.
  //
  // We want to paint the first frame under two conditions: Either (1) we have
  // enough frames to know it's definitely the first frame or (2) there may be
  // no more frames coming (sometimes unless we paint one of them).
  //
  // We have to check both effective_frames_queued() and |is_before_start_time|
  // since prior to the clock starting effective_frames_queued() is a guess.
  //
  // NOTE: Do this before using algorithm_->average_frame_duration(). This
  // initial render will update the duration to be non-zero when provided by
  // frame metadata.
  if (!sink_started_ && !painted_first_frame_ && algorithm_->frames_queued() &&
      (received_end_of_stream_ || cant_read ||
       (algorithm_->effective_frames_queued() && !is_before_start_time))) {
    scoped_refptr<VideoFrame> first_frame =
        algorithm_->Render(base::TimeTicks(), base::TimeTicks(), nullptr);
    CheckForMetadataChanges(first_frame->format(), first_frame->natural_size());
    sink_->PaintSingleFrame(first_frame);
    painted_first_frame_ = true;
  }

  // Update average frame duration.
  base::TimeDelta frame_duration = algorithm_->average_frame_duration();
  if (frame_duration != kNoTimestamp &&
      frame_duration != base::TimeDelta::FromSeconds(0)) {
    fps_estimator_.AddSample(frame_duration);
  } else {
    fps_estimator_.Reset();
  }
  ReportFrameRateIfNeeded_Locked();

  // Update any statistics since the last call.
  UpdateStats_Locked();

  // Update hint-driven buffering caps to use the latest average frame duration.
  // NOTE: Do this before updating the buffering state below, as it may affect
  // the outcome of HaveEnoughData_Locked().
  // TODO(chcunningham): Duration from |algorithm_| is affected by playback
  // rate. Consider using wall clock frame duration instead.
  if (latency_hint_.has_value() && !latency_hint_->is_zero())
    UpdateLatencyHintBufferingCaps_Locked(frame_duration);

  // Signal buffering state if we've met our conditions.
  if (buffering_state_ == BUFFERING_HAVE_NOTHING && HaveEnoughData_Locked())
    TransitionToHaveEnough_Locked();

  // We may have removed all frames above and have reached end of stream. This
  // must happen after the buffering state change has been signaled.
  MaybeFireEndedCallback_Locked(time_progressing_);

  // Always request more decoded video if we have capacity.
  AttemptRead_Locked();
}

bool VideoRendererImpl::HaveEnoughData_Locked() const {
  DCHECK_EQ(state_, kPlaying);
  lock_.AssertAcquired();

  if (received_end_of_stream_)
    return true;

  if (HaveReachedBufferingCap(min_buffered_frames_))
    return true;

  // If we've decoded any frames since the last render, signal have enough to
  // avoid underflowing when video is not visible unless we run out of frames.
  if (was_background_rendering_ && last_frame_ready_time_ >= last_render_time_)
    return true;

  if (min_buffered_frames_ > 1 &&
      video_decoder_stream_->CanReadWithoutStalling()) {
    return false;
  }

  // Note: We still require an effective frame in the stalling case since this
  // method is also used to inform TransitionToHaveNothing_Locked() and thus
  // would never pause and rebuffer if we always return true here.
  return algorithm_->effective_frames_queued() > 0u;
}

void VideoRendererImpl::TransitionToHaveEnough_Locked() {
  DVLOG(3) << __func__;
  DCHECK(task_runner_->BelongsToCurrentThread());
  DCHECK_EQ(buffering_state_, BUFFERING_HAVE_NOTHING);
  lock_.AssertAcquired();

  buffering_state_ = BUFFERING_HAVE_ENOUGH;
  task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&VideoRendererImpl::OnBufferingStateChange,
                                weak_factory_.GetWeakPtr(), buffering_state_));
}

void VideoRendererImpl::TransitionToHaveNothing() {
  DVLOG(3) << __func__;
  DCHECK(task_runner_->BelongsToCurrentThread());

  base::AutoLock auto_lock(lock_);
  TransitionToHaveNothing_Locked();
}

void VideoRendererImpl::TransitionToHaveNothing_Locked() {
  DVLOG(3) << __func__;
  DCHECK(task_runner_->BelongsToCurrentThread());
  lock_.AssertAcquired();

  if (buffering_state_ != BUFFERING_HAVE_ENOUGH || HaveEnoughData_Locked())
    return;

  buffering_state_ = BUFFERING_HAVE_NOTHING;
  task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&VideoRendererImpl::OnBufferingStateChange,
                                weak_factory_.GetWeakPtr(), buffering_state_));
}

void VideoRendererImpl::AddReadyFrame_Locked(scoped_refptr<VideoFrame> frame) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  lock_.AssertAcquired();
  DCHECK(!frame->metadata().end_of_stream);

  ++stats_.video_frames_decoded;

  if (frame->metadata().power_efficient)
    ++stats_.video_frames_decoded_power_efficient;

  algorithm_->EnqueueFrame(std::move(frame));
}

void VideoRendererImpl::AttemptRead_Locked() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  lock_.AssertAcquired();

  if (pending_read_ || received_end_of_stream_)
    return;

  if (HaveReachedBufferingCap(max_buffered_frames_))
    return;

  switch (state_) {
    case kPlaying:
      pending_read_ = true;
      video_decoder_stream_->Read(
          base::BindOnce(&VideoRendererImpl::FrameReady,
                         cancel_on_flush_weak_factory_.GetWeakPtr()));
      return;
    case kUninitialized:
    case kInitializing:
    case kFlushing:
    case kFlushed:
      return;
  }
}

void VideoRendererImpl::OnVideoDecoderStreamResetDone() {
  // We don't need to acquire the |lock_| here, because we can only get here
  // when Flush is in progress, so rendering and video sink must be stopped.
  DCHECK(task_runner_->BelongsToCurrentThread());
  DCHECK(!sink_started_);
  DCHECK_EQ(kFlushing, state_);
  DCHECK(!received_end_of_stream_);
  DCHECK(!rendered_end_of_stream_);
  DCHECK_EQ(buffering_state_, BUFFERING_HAVE_NOTHING);

  state_ = kFlushed;
  FinishFlush();
}

void VideoRendererImpl::UpdateStats_Locked(bool force_update) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  lock_.AssertAcquired();

  // No need to check for `stats_.video_frames_decoded_power_efficient` because
  // if it is greater than 0, `stats_.video_frames_decoded` will too.
  if (!force_update && !stats_.video_frames_decoded &&
      !stats_.video_frames_dropped) {
    return;
  }

  if (stats_.video_frames_dropped) {
    TRACE_EVENT_INSTANT2("media", "VideoFramesDropped",
                         TRACE_EVENT_SCOPE_THREAD, "count",
                         stats_.video_frames_dropped, "id", media_log_->id());
  }

  const size_t memory_usage = algorithm_->GetMemoryUsage();
  stats_.video_memory_usage = memory_usage - stats_.video_memory_usage;
  stats_.video_frame_duration_average = algorithm_->average_frame_duration();
  OnStatisticsUpdate(stats_);

  stats_.video_frames_decoded = 0;
  stats_.video_frames_dropped = 0;
  stats_.video_frames_decoded_power_efficient = 0;
  stats_.video_memory_usage = memory_usage;
}

void VideoRendererImpl::ReportFrameRateIfNeeded_Locked() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  lock_.AssertAcquired();

  base::Optional<int> current_fps = fps_estimator_.ComputeFPS();
  if (last_reported_fps_ && current_fps &&
      *last_reported_fps_ == *current_fps) {
    // Reported an FPS before, and it hasn't changed.
    return;
  } else if (!last_reported_fps_ && !current_fps) {
    // Did not report an FPS before, and we still don't have one
    return;
  }

  // FPS changed, possibly to unknown.
  last_reported_fps_ = current_fps;
  client_->OnVideoFrameRateChange(current_fps);
}

bool VideoRendererImpl::HaveReachedBufferingCap(size_t buffering_cap) const {
  DCHECK(task_runner_->BelongsToCurrentThread());

  // When the display rate is less than the frame rate, the effective frames
  // queued may be much smaller than the actual number of frames queued.  Here
  // we ensure that frames_queued() doesn't get excessive.
  return algorithm_->effective_frames_queued() >= buffering_cap ||
         algorithm_->frames_queued() >= kAbsoluteMaxFrames;
}

void VideoRendererImpl::StartSink() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  DCHECK_GT(algorithm_->frames_queued(), 0u);
  sink_started_ = true;
  was_background_rendering_ = false;
  sink_->Start(this);
}

void VideoRendererImpl::StopSink() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  sink_->Stop();
  algorithm_->set_time_stopped();
  sink_started_ = false;
  was_background_rendering_ = false;
}

void VideoRendererImpl::MaybeFireEndedCallback_Locked(bool time_progressing) {
  lock_.AssertAcquired();

  // If there's only one frame in the video or Render() was never called, the
  // algorithm will have one frame linger indefinitely.  So in cases where the
  // frame duration is unknown and we've received EOS, fire it once we get down
  // to a single frame.

  // Don't fire ended if we haven't received EOS or have already done so.
  if (!received_end_of_stream_ || rendered_end_of_stream_)
    return;

  const bool have_frames_after_start_time =
      algorithm_->frames_queued() &&
      !IsBeforeStartTime(algorithm_->last_frame());

  // Don't fire ended if time isn't moving and we have frames.
  if (!time_progressing && have_frames_after_start_time)
    return;

  // Fire ended if we have no more effective frames, only ever had one frame, or
  // we only have 1 effective frame and there's less than one render interval
  // left before the ended event should execute.
  base::TimeDelta ended_event_delay;
  bool should_render_end_of_stream = false;
  if (!algorithm_->effective_frames_queued()) {
    should_render_end_of_stream = true;
  } else if (algorithm_->frames_queued() == 1u &&
             algorithm_->average_frame_duration().is_zero()) {
    should_render_end_of_stream = true;
  } else if (algorithm_->frames_queued() == 1u &&
             algorithm_->render_interval().is_zero()) {
    should_render_end_of_stream = true;
  } else if (algorithm_->frames_queued() == 1u &&
             algorithm_->effective_frames_queued() == 1) {
    const auto end_delay =
        std::max(base::TimeDelta(),
                 algorithm_->last_frame_end_time() - tick_clock_->NowTicks());
    if (end_delay < algorithm_->render_interval()) {
      should_render_end_of_stream = true;
      ended_event_delay = end_delay;
    }
  }

  if (!should_render_end_of_stream)
    return;

  rendered_end_of_stream_ = true;
  task_runner_->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&VideoRendererImpl::OnPlaybackEnded,
                     cancel_on_flush_weak_factory_.GetWeakPtr()),
      ended_event_delay);
}

base::TimeTicks VideoRendererImpl::ConvertMediaTimestamp(
    base::TimeDelta media_time) {
  std::vector<base::TimeDelta> media_times(1, media_time);
  std::vector<base::TimeTicks> wall_clock_times;
  if (!wall_clock_time_cb_.Run(media_times, &wall_clock_times))
    return base::TimeTicks();
  return wall_clock_times[0];
}

base::TimeTicks VideoRendererImpl::GetCurrentMediaTimeAsWallClockTime() {
  std::vector<base::TimeTicks> current_time;
  wall_clock_time_cb_.Run(std::vector<base::TimeDelta>(), &current_time);
  return current_time[0];
}

bool VideoRendererImpl::IsBeforeStartTime(const VideoFrame& frame) {
  // Prefer the actual frame duration over the average if available.
  return frame.timestamp() + frame.metadata().frame_duration.value_or(
                                 last_decoder_stream_avg_duration_) <
         start_timestamp_;
}

void VideoRendererImpl::RemoveFramesForUnderflowOrBackgroundRendering() {
  // Nothing to do if frame dropping is disabled for testing or we have nothing.
  if (!drop_frames_ || !algorithm_->frames_queued())
    return;

  // If we're paused for prerolling (current time is 0), don't expire any
  // frames. It's possible that during preroll |have_nothing| is false while
  // |was_background_rendering_| is true. We differentiate this from actual
  // background rendering by checking if current time is 0.
  const base::TimeTicks current_time = GetCurrentMediaTimeAsWallClockTime();
  if (current_time.is_null())
    return;

  // Background rendering updates may not be ticking fast enough to remove
  // expired frames, so provide a boost here by ensuring we don't exit the
  // decoding cycle too early. Dropped frames are not counted in this case.
  if (was_background_rendering_) {
    algorithm_->RemoveExpiredFrames(tick_clock_->NowTicks());
    return;
  }

  // If we've paused for underflow, and still have no effective frames, clear
  // the entire queue.  Note: this may cause slight inaccuracies in the number
  // of dropped frames since the frame may have been rendered before.
  if (!sink_started_ && !algorithm_->effective_frames_queued()) {
    stats_.video_frames_dropped += algorithm_->frames_queued();
    algorithm_->Reset(
        VideoRendererAlgorithm::ResetFlag::kPreserveNextFrameEstimates);
    painted_first_frame_ = false;

    // It's possible in the background rendering case for us to expire enough
    // frames that we need to transition from HAVE_ENOUGH => HAVE_NOTHING. Just
    // calling this function will check if we need to transition or not.
    if (buffering_state_ == BUFFERING_HAVE_ENOUGH)
      TransitionToHaveNothing_Locked();
    return;
  }

  // Use the current media wall clock time plus the frame duration since
  // RemoveExpiredFrames() is expecting the end point of an interval (it will
  // subtract from the given value). It's important to always call this so
  // that frame statistics are updated correctly.
  if (buffering_state_ == BUFFERING_HAVE_NOTHING) {
    stats_.video_frames_dropped += algorithm_->RemoveExpiredFrames(
        current_time + algorithm_->average_frame_duration());
    return;
  }

  // If we reach this point, the normal rendering process will take care of
  // removing any expired frames.
}

void VideoRendererImpl::CheckForMetadataChanges(VideoPixelFormat pixel_format,
                                                const gfx::Size& natural_size) {
  DCHECK(task_runner_->BelongsToCurrentThread());

  // Notify client of size and opacity changes if this is the first frame
  // or if those have changed from the last frame.
  if (!have_renderered_frames_ || last_frame_natural_size_ != natural_size) {
    last_frame_natural_size_ = natural_size;
    client_->OnVideoNaturalSizeChange(last_frame_natural_size_);
  }

  const bool is_opaque = IsOpaque(pixel_format);
  if (!have_renderered_frames_ || last_frame_opaque_ != is_opaque) {
    last_frame_opaque_ = is_opaque;
    client_->OnVideoOpacityChange(last_frame_opaque_);
  }

  have_renderered_frames_ = true;
}

void VideoRendererImpl::AttemptReadAndCheckForMetadataChanges(
    VideoPixelFormat pixel_format,
    const gfx::Size& natural_size) {
  base::AutoLock auto_lock(lock_);
  CheckForMetadataChanges(pixel_format, natural_size);
  AttemptRead_Locked();
}

}  // namespace media