blob: 4e268e8ade3598476a21877d98a916cb93230485 [file] [log] [blame]
// 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 <stdint.h>
#include <functional>
#include <map>
#include <string>
#include "cast/standalone_receiver/decoder.h"
#include "cast/standalone_receiver/sdl_glue.h"
#include "cast/streaming/message_fields.h"
#include "cast/streaming/receiver.h"
#include "platform/api/task_runner.h"
#include "platform/api/time.h"
#include "platform/base/error.h"
namespace openscreen {
namespace cast {
// Common base class that consumes frames from a Receiver, decodes them, and
// plays them out via the appropriate SDL subsystem. Subclasses implement the
// specifics, based on the type of media (audio or video).
class SDLPlayerBase : public Receiver::Consumer, public Decoder::Client {
~SDLPlayerBase() override;
// Returns OK unless a fatal error has occurred.
const Error& error_status() const { return error_status_; }
// Current player state, which is used to determine what to render/present,
// and how frequently.
enum PlayerState {
kWaitingForFirstFrame, // Render silent "blue splash" screen at idle FPS.
kScheduledToPresent, // Present new content at an exact time point.
kPresented, // Continue presenting same content at idle FPS.
kError, // Render silent "red splash" screen at idle FPS.
// A decoded frame and its target presentation time.
struct PresentableFrame {
Clock::time_point presentation_time;
AVFrameUniquePtr decoded_frame;
PresentableFrame(PresentableFrame&& other) noexcept;
PresentableFrame& operator=(PresentableFrame&& other) noexcept;
// |error_callback| is run only if a fatal error occurs, at which point the
// player has halted and set |error_status()|. |media_type| should be "audio"
// or "video" (only used when logging).
SDLPlayerBase(ClockNowFunctionPtr now_function,
TaskRunner* task_runner,
Receiver* receiver,
const std::string& codec_name,
std::function<void()> error_callback,
const char* media_type);
PlayerState state() const { return state_; }
// Called back from either |decoder_| or a player subclass to handle a fatal
// error event.
void OnFatalError(std::string message) final;
// Renders the |frame| and returns its [possibly adjusted] presentation time.
virtual ErrorOr<Clock::time_point> RenderNextFrame(
const PresentableFrame& frame) = 0;
// Called to render when the player has no new content, and returns true if a
// Present() is necessary. |frame| may be null, if it is not available. This
// method can be called before the first frame, after any frame, or after a
// fatal error has occurred.
virtual bool RenderWhileIdle(const PresentableFrame* frame) = 0;
// Presents the rendering from the last call to RenderNextFrame() or
// RenderWhileIdle().
virtual void Present() = 0;
struct PendingFrame : public PresentableFrame {
Clock::time_point start_time;
PendingFrame(PendingFrame&& other) noexcept;
PendingFrame& operator=(PendingFrame&& other) noexcept;
// Receiver::Consumer implementation.
void OnFramesReady(int next_frame_buffer_size) final;
// Determine the presentation time of the frame. Ideally, this will occur
// based on the time progression of the media, given by the RTP timestamps.
// However, if this falls too far out-of-sync with the system reference clock,
// re-synchronize, possibly causing user-visible "jank."
Clock::time_point ResyncAndDeterminePresentationTime(
const EncodedFrame& frame);
// AVCodecDecoder::Client implementation. These are called-back from
// |decoder_| to provide results.
void OnFrameDecoded(FrameId frame_id, const AVFrame& frame) final;
void OnDecodeError(FrameId frame_id, std::string message) final;
// Calls RenderNextFrame() on the next available decoded frame, and schedules
// its presentation. If no decoded frame is available, RenderWhileIdle() is
// called instead.
void RenderAndSchedulePresentation();
// Schedules an explicit check to see if more frames are ready for
// consumption. Normally, the Receiver will notify this Consumer when more
// frames are ready. However, there are cases where prior notifications were
// ignored because there were too many frames in the player's pipeline. Thus,
// whenever frames are removed from the pipeline, this method should be
// called.
void ResumeDecoding();
// Called whenever a frame has been decoded, presentation of a prior frame has
// completed, and/or the player has encountered a state change that might
// require rendering/presenting a different output.
void ResumeRendering();
const ClockNowFunctionPtr now_;
Receiver* const receiver_;
std::function<void()> error_callback_; // Run once by OnFatalError().
const char* const media_type_; // For logging only.
// Set to the error code that placed the player in a fatal error state.
Error error_status_;
// Current player state, which is used to determine what to render/present,
// and how frequently.
PlayerState state_ = kWaitingForFirstFrame;
// Queue of frames currently being decoded and decoded frames awaiting
// rendering.
std::map<FrameId, PendingFrame> frames_to_render_;
// Buffer for holding EncodedFrame::data.
Decoder::Buffer buffer_;
// Associates a RTP timestamp with a local clock time point. This is updated
// whenever the media (RTP) timestamps drift too much away from the rate at
// which the local clock ticks. This is important for A/V synchronization.
RtpTimeTicks last_sync_rtp_timestamp_{};
Clock::time_point last_sync_reference_time_{};
Decoder decoder_;
// The decoded frame to be rendered/presented.
PendingFrame current_frame_;
// A cumulative moving average of recent single-frame processing times
// (consume + decode + render). This is passed to the Cast Receiver so that it
// can determine when to drop late frames.
Clock::duration recent_processing_time_{};
// Alarms that execute the various stages of the player pipeline at certain
// times.
Alarm decode_alarm_;
Alarm render_alarm_;
Alarm presentation_alarm_;
// Maximum number of frames in the decode/render pipeline. This limit is about
// making sure the player uses resources efficiently: It is better for frames
// to remain in the Receiver's queue until this player is ready to process
// them.
static constexpr int kMaxFramesInPipeline = 8;
} // namespace cast
} // namespace openscreen