You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
9.1 KiB
257 lines
9.1 KiB
// Copyright 2019 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 "cast/standalone_receiver/sdl_player_base.h"
|
|
|
|
#include <chrono>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include "absl/types/span.h"
|
|
#include "cast/standalone_receiver/avcodec_glue.h"
|
|
#include "cast/streaming/constants.h"
|
|
#include "cast/streaming/encoded_frame.h"
|
|
#include "util/big_endian.h"
|
|
#include "util/chrono_helpers.h"
|
|
#include "util/osp_logging.h"
|
|
#include "util/trace_logging.h"
|
|
|
|
namespace openscreen {
|
|
namespace cast {
|
|
|
|
SDLPlayerBase::SDLPlayerBase(ClockNowFunctionPtr now_function,
|
|
TaskRunner* task_runner,
|
|
Receiver* receiver,
|
|
const std::string& codec_name,
|
|
std::function<void()> error_callback,
|
|
const char* media_type)
|
|
: now_(now_function),
|
|
receiver_(receiver),
|
|
error_callback_(std::move(error_callback)),
|
|
media_type_(media_type),
|
|
decoder_(codec_name),
|
|
decode_alarm_(now_, task_runner),
|
|
render_alarm_(now_, task_runner),
|
|
presentation_alarm_(now_, task_runner) {
|
|
OSP_DCHECK(receiver_);
|
|
OSP_DCHECK(media_type_);
|
|
|
|
decoder_.set_client(this);
|
|
receiver_->SetConsumer(this);
|
|
ResumeRendering();
|
|
}
|
|
|
|
SDLPlayerBase::~SDLPlayerBase() {
|
|
receiver_->SetConsumer(nullptr);
|
|
decoder_.set_client(nullptr);
|
|
}
|
|
|
|
void SDLPlayerBase::OnFatalError(std::string message) {
|
|
state_ = kError;
|
|
error_status_ = Error(Error::Code::kUnknownError, std::move(message));
|
|
|
|
// Halt decoding and clear the rendering queue.
|
|
receiver_->SetConsumer(nullptr);
|
|
decoder_.set_client(nullptr);
|
|
decode_alarm_.Cancel();
|
|
frames_to_render_.clear();
|
|
|
|
// Resume rendering, to emit an error indication (e.g., "red splash" screen).
|
|
ResumeRendering();
|
|
|
|
if (error_callback_) {
|
|
const auto callback = std::move(error_callback_);
|
|
callback();
|
|
}
|
|
}
|
|
|
|
Clock::time_point SDLPlayerBase::ResyncAndDeterminePresentationTime(
|
|
const EncodedFrame& frame) {
|
|
constexpr auto kMaxPlayoutDrift = milliseconds(100);
|
|
const auto media_time_since_last_sync =
|
|
(frame.rtp_timestamp - last_sync_rtp_timestamp_)
|
|
.ToDuration<Clock::duration>(receiver_->rtp_timebase());
|
|
Clock::time_point presentation_time =
|
|
last_sync_reference_time_ + media_time_since_last_sync;
|
|
const auto drift = to_milliseconds(frame.reference_time - presentation_time);
|
|
if (drift > kMaxPlayoutDrift || drift < -kMaxPlayoutDrift) {
|
|
// Only log if not the very first frame.
|
|
OSP_LOG_IF(INFO, frame.frame_id != FrameId::first())
|
|
<< "Playout drift (" << drift.count() << " ms) exceeded threshold ("
|
|
<< kMaxPlayoutDrift.count() << " ms) for " << media_type_
|
|
<< ". Re-synchronizing...";
|
|
// This is the "big-stick" way to re-synchronize. If the amount of drift
|
|
// is small, a production-worthy player should "nudge" things gradually
|
|
// back into sync over several frames.
|
|
last_sync_rtp_timestamp_ = frame.rtp_timestamp;
|
|
last_sync_reference_time_ = frame.reference_time;
|
|
presentation_time = frame.reference_time;
|
|
}
|
|
return presentation_time;
|
|
}
|
|
|
|
void SDLPlayerBase::OnFramesReady(int buffer_size) {
|
|
TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
|
|
// Do not consume anything if there are too many frames in the pipeline
|
|
// already.
|
|
if (static_cast<int>(frames_to_render_.size()) > kMaxFramesInPipeline) {
|
|
return;
|
|
}
|
|
|
|
// Consume the next frame.
|
|
const Clock::time_point start_time = now_();
|
|
buffer_.Resize(buffer_size);
|
|
EncodedFrame frame = receiver_->ConsumeNextFrame(buffer_.GetSpan());
|
|
|
|
// Create the tracking state for the frame in the player pipeline.
|
|
OSP_DCHECK_EQ(frames_to_render_.count(frame.frame_id), 0);
|
|
PendingFrame& pending_frame = frames_to_render_[frame.frame_id];
|
|
pending_frame.start_time = start_time;
|
|
|
|
pending_frame.presentation_time = ResyncAndDeterminePresentationTime(frame);
|
|
|
|
// Start decoding the frame. This call may synchronously call back into the
|
|
// AVCodecDecoder::Client methods in this class.
|
|
decoder_.Decode(frame.frame_id, buffer_);
|
|
}
|
|
|
|
void SDLPlayerBase::OnFrameDecoded(FrameId frame_id, const AVFrame& frame) {
|
|
TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
|
|
const auto it = frames_to_render_.find(frame_id);
|
|
if (it == frames_to_render_.end()) {
|
|
return;
|
|
}
|
|
OSP_DCHECK(!it->second.decoded_frame);
|
|
// av_clone_frame() does a shallow copy here, incrementing a ref-count on the
|
|
// memory backing the frame.
|
|
it->second.decoded_frame = AVFrameUniquePtr(av_frame_clone(&frame));
|
|
ResumeRendering();
|
|
}
|
|
|
|
void SDLPlayerBase::OnDecodeError(FrameId frame_id, std::string message) {
|
|
const auto it = frames_to_render_.find(frame_id);
|
|
if (it != frames_to_render_.end()) {
|
|
frames_to_render_.erase(it);
|
|
}
|
|
OSP_LOG_WARN << "Requesting " << media_type_
|
|
<< " key frame because of error decoding" << frame_id << ": "
|
|
<< message;
|
|
receiver_->RequestKeyFrame();
|
|
ResumeDecoding();
|
|
}
|
|
|
|
void SDLPlayerBase::RenderAndSchedulePresentation() {
|
|
TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
|
|
// If something has already been scheduled to present at an exact time point,
|
|
// don't render anything new yet.
|
|
if (state_ == kScheduledToPresent) {
|
|
return;
|
|
}
|
|
|
|
// If no frames are available, just re-render the currently-presented frame
|
|
// (or the error screen).
|
|
auto it =
|
|
(state_ == kError) ? frames_to_render_.end() : frames_to_render_.begin();
|
|
if (it == frames_to_render_.end() || !it->second.decoded_frame) {
|
|
if (RenderWhileIdle(state_ == kPresented ? ¤t_frame_ : nullptr)) {
|
|
// Schedule presentation to happen after a rather lengthy interval, to
|
|
// minimize redraw/etc. resource usage while doing "idle mode" play-out.
|
|
// The interval here, is "lengthy" from the program's perspective, but
|
|
// reasonably "snappy" from the user's perspective.
|
|
constexpr auto kIdlePresentInterval = milliseconds(250);
|
|
presentation_alarm_.ScheduleFromNow(
|
|
[this] {
|
|
Present();
|
|
ResumeRendering();
|
|
},
|
|
kIdlePresentInterval);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Skip late frames, to render the first not-late frame. If all decoded frames
|
|
// are late, skip-forward to the least-late frame.
|
|
const Clock::time_point now = now_();
|
|
while (it->second.presentation_time < now) {
|
|
const auto next_it = std::next(it);
|
|
if (next_it == frames_to_render_.end() || !next_it->second.decoded_frame) {
|
|
break;
|
|
}
|
|
frames_to_render_.erase(it); // Drop the late frame.
|
|
it = next_it;
|
|
}
|
|
|
|
// Remove the frame from the queue, making it the |current_frame_|. Then,
|
|
// render it and, if successful, schedule its presentation.
|
|
current_frame_ = std::move(it->second);
|
|
frames_to_render_.erase(it);
|
|
const ErrorOr<Clock::time_point> presentation_time =
|
|
RenderNextFrame(current_frame_);
|
|
if (!presentation_time) {
|
|
OnFatalError(presentation_time.error().message());
|
|
return;
|
|
}
|
|
state_ = kScheduledToPresent;
|
|
presentation_alarm_.Schedule(
|
|
[this] {
|
|
Present();
|
|
if (state_ == kScheduledToPresent) {
|
|
state_ = kPresented;
|
|
}
|
|
ResumeRendering();
|
|
},
|
|
presentation_time.value());
|
|
|
|
// Resume consuming/decoding frames, since some of the prior OnFramesReady()
|
|
// calls may have been ignored to leave things in the Receiver's queue.
|
|
ResumeDecoding();
|
|
|
|
// Compute how long it took to decode/render this frame, and notify the
|
|
// Receiver of the recent-average per-frame processing time. This is used by
|
|
// the Receiver to determine when to drop late frames.
|
|
const Clock::duration measured_processing_time =
|
|
now_() - current_frame_.start_time;
|
|
constexpr int kCumulativeAveragePoints = 8;
|
|
recent_processing_time_ =
|
|
((kCumulativeAveragePoints - 1) * recent_processing_time_ +
|
|
1 * measured_processing_time) /
|
|
kCumulativeAveragePoints;
|
|
receiver_->SetPlayerProcessingTime(recent_processing_time_);
|
|
}
|
|
|
|
void SDLPlayerBase::ResumeDecoding() {
|
|
decode_alarm_.Schedule(
|
|
[this] {
|
|
const int buffer_size = receiver_->AdvanceToNextFrame();
|
|
if (buffer_size != Receiver::kNoFramesReady) {
|
|
OnFramesReady(buffer_size);
|
|
}
|
|
},
|
|
Alarm::kImmediately);
|
|
}
|
|
|
|
void SDLPlayerBase::ResumeRendering() {
|
|
render_alarm_.Schedule([this] { RenderAndSchedulePresentation(); },
|
|
Alarm::kImmediately);
|
|
}
|
|
|
|
// static
|
|
constexpr int SDLPlayerBase::kMaxFramesInPipeline;
|
|
|
|
SDLPlayerBase::PresentableFrame::PresentableFrame() = default;
|
|
SDLPlayerBase::PresentableFrame::~PresentableFrame() = default;
|
|
SDLPlayerBase::PresentableFrame::PresentableFrame(PresentableFrame&&) noexcept =
|
|
default;
|
|
SDLPlayerBase::PresentableFrame& SDLPlayerBase::PresentableFrame::operator=(
|
|
PresentableFrame&&) noexcept = default;
|
|
|
|
SDLPlayerBase::PendingFrame::PendingFrame() = default;
|
|
SDLPlayerBase::PendingFrame::~PendingFrame() = default;
|
|
SDLPlayerBase::PendingFrame::PendingFrame(PendingFrame&&) noexcept = default;
|
|
SDLPlayerBase::PendingFrame& SDLPlayerBase::PendingFrame::operator=(
|
|
PendingFrame&&) noexcept = default;
|
|
|
|
} // namespace cast
|
|
} // namespace openscreen
|