chromium/chromecast/media/cma/backend/desktop/media_sink_desktop.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromecast/media/cma/backend/desktop/media_sink_desktop.h"

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/task/single_thread_task_runner.h"
#include "chromecast/public/media/cast_decoder_buffer.h"
#include "media/base/timestamp_constants.h"

namespace chromecast {
namespace media {

MediaSinkDesktop::MediaSinkDesktop(
    MediaPipelineBackend::Decoder::Delegate* delegate,
    base::TimeDelta start_pts)
    : delegate_(delegate),
      time_interpolator_(&tick_clock_),
      playback_rate_(1.0f),
      last_frame_pts_(start_pts),
      received_eos_(false) {
  DCHECK(delegate_);
  time_interpolator_.SetPlaybackRate(playback_rate_);
  time_interpolator_.SetBounds(start_pts, start_pts, tick_clock_.NowTicks());
  time_interpolator_.StartInterpolating();
}

MediaSinkDesktop::~MediaSinkDesktop() {}

void MediaSinkDesktop::SetPlaybackRate(float rate) {
  DCHECK_GE(rate, 0.0f);
  playback_rate_ = rate;
  time_interpolator_.SetPlaybackRate(playback_rate_);

  // Changing the playback rate affects the delay after which EOS callback
  // should run. Reschedule the task according to the new delay.
  if (received_eos_) {
    eos_task_.Cancel();
    ScheduleEndOfStreamTask();
  }
}

base::TimeDelta MediaSinkDesktop::GetCurrentPts() {
  return time_interpolator_.GetInterpolatedTime();
}

MediaPipelineBackend::BufferStatus MediaSinkDesktop::PushBuffer(
    CastDecoderBuffer* buffer) {
  if (buffer->end_of_stream()) {
    received_eos_ = true;
    ScheduleEndOfStreamTask();
    return MediaPipelineBackend::kBufferSuccess;
  }

  // This is wrong on several levels.
  // 1. The correct PTS should be buffer->timestamp() + buffer->duration().
  //    But CastDecoderBuffer does not expose duration unlike
  //    ::media::DecoderBuffer.
  // 2. The PTS reported by GetCurrentPts should not move backwards.
  //    It should be clamped in the range [start_pts, last_frame_pts_].
  //    But doing so makes several AudioVideoPipelineDeviceTest cases fail.
  //    Those tests are wrong should be fixed.
  // TODO(alokp): Fix these issues when the next version of CMA backend is
  // scheduled to roll out. crbug.com/678394
  auto timestamp = base::Microseconds(buffer->timestamp());
  if (timestamp != ::media::kNoTimestamp) {
    last_frame_pts_ = timestamp;
    time_interpolator_.SetUpperBound(last_frame_pts_);
  }
  return MediaPipelineBackend::kBufferSuccess;
}

void MediaSinkDesktop::ScheduleEndOfStreamTask() {
  DCHECK(received_eos_);
  DCHECK(eos_task_.IsCancelled());

  // Do not schedule if playback is paused.
  if (playback_rate_ == 0.0f)
    return;

  eos_task_.Reset(
      base::BindOnce(&MediaPipelineBackend::Decoder::Delegate::OnEndOfStream,
                     base::Unretained(delegate_)));
  base::TimeDelta delay = (last_frame_pts_ - GetCurrentPts()) / playback_rate_;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, eos_task_.callback(), delay);
}

}  // namespace media
}  // namespace chromecast