// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_STARBOARD_MEDIA_MEDIA_STARBOARD_DECODER_H_
#define CHROMECAST_STARBOARD_MEDIA_MEDIA_STARBOARD_DECODER_H_
#include <optional>
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "chromecast/public/media/cast_decrypt_config.h"
#include "chromecast/public/media/media_pipeline_backend.h"
#include "chromecast/starboard/media/media/drm_util.h"
#include "chromecast/starboard/media/media/starboard_api_wrapper.h"
namespace chromecast {
namespace media {
// A base class for StarboardAudioDecoder and StarboardVideoDecoder, containing
// logic common to all decoders. It manages interactions with Starboard and the
// lifetime of buffers.
//
// This is an abstract class; child classes can implement InitializeInternal to
// perform any necessary initialization logic.
//
// All functions, including the constructor and destructor, must be called on
// the same sequence (the media thread).
class StarboardDecoder {
public:
// Disallow copy and assign.
StarboardDecoder(const StarboardDecoder&) = delete;
StarboardDecoder& operator=(const StarboardDecoder&) = delete;
// Called when a buffer can be deallocated.
void Deallocate(const uint8_t* buffer);
// Initializes the decoder by providing the opaque SbPlayer that will be used
// to decode/render buffers. Before this call, no buffers will be pushed to
// Starboard. Calling Pushbuffer will just queue the buffer.
// `sb_player` must not be null.
void Initialize(void* sb_player);
// Clears any pending buffer, and sets the decoder into an un-initialized
// state.
void Stop();
// Returns true if the decoder is initialized, false otherwise. After Stop(),
// the decoder is no longer initialized.
bool IsInitialized();
// Called when a buffer has been processed by Starboard.
void OnBufferWritten();
// Notifies the delegate that the end of stream buffer has been rendered. This
// should be called once the SbPlayer has sent the
// kStarboardPlayerStateEndOfStream/kSbPlayerStateEndOfStream signal.
void OnSbPlayerEndOfStream();
// Called when the SbPlayer has encountered a decoder error. Notifies the
// delegate that an error has occurred.
void OnStarboardDecodeError();
protected:
// Calls to Starboard are made via `starboard`, to allow mocking in tests.
// `media_type` specifies the type of decoder that this represents (audio or
// video).
StarboardDecoder(StarboardApiWrapper* starboard,
StarboardMediaType media_type);
virtual ~StarboardDecoder();
// Sets the delegate for this decoder. This delegate is informed when buffers
// have been pushed.
void SetDecoderDelegate(MediaPipelineBackend::Decoder::Delegate* delegate);
// Pushes a buffer to starboard. Takes ownership of `buffer_data` to manage
// its lifetime: it will not be freed until Deallocate is called (or this
// object is destroyed).
//
// This function will populate sample_info.drm_info, sample_info.buffer, and
// sample_info.buffer_size before sending the sample to Starboard. The values
// will be populated based on the values of `drm_info` and `buffer_data`.
//
// `buffer_data` must not be empty (use PushEndOfStream to signal the end of a
// stream).
MediaPipelineBackend::BufferStatus PushBufferInternal(
StarboardSampleInfo sample_info,
DrmInfoWrapper drm_info,
std::unique_ptr<uint8_t[]> buffer_data,
size_t buffer_data_size);
// Sends an "end of stream" signal to starboard.
MediaPipelineBackend::BufferStatus PushEndOfStream();
// Returns the current SbPlayer. May return null if the current SbPlayer has
// not been set, or if the decoder has been stopped.
void* GetPlayer();
// Returns a StarboardApiWrapper, which can be used to interact with Starboard
// directly.
StarboardApiWrapper& GetStarboardApi() const;
// Returns the current delegate, or null if none is currently set.
MediaPipelineBackend::Decoder::Delegate* GetDelegate() const;
private:
// Performs any initialization logic for the child class. This will run after
// Initialize runs, and is called each time Initialize is called.
virtual void InitializeInternal() = 0;
// Runs pending_drm_key_ if `token` matches drm_key_token_. Called once a DRM
// key is available.
void RunPendingDrmKeyCallback(int64_t token);
SEQUENCE_CHECKER(sequence_checker_);
StarboardApiWrapper* starboard_ = nullptr;
StarboardMediaType media_type_;
// The opaque SbPlayer.
void* player_ = nullptr;
MediaPipelineBackend::Decoder::Delegate* delegate_ = nullptr;
// If PushBuffer is called before the decoder has received a handle to the
// SbPlayer, the push is delayed and this contains the call to
// PushBufferInternal.
base::OnceCallback<MediaPipelineBackend::BufferStatus()> pending_first_push_;
// This map is expected to be small (likely only one or two elements), hence
// the use of a flat_map.
// Maps from the array address to the unique_ptr that manages the array.
base::flat_map<const uint8_t*, std::unique_ptr<uint8_t[]>> copied_buffers_;
// A callback to be run once the necessary DRM key is available. The callback
// will push a pending buffer.
base::OnceCallback<MediaPipelineBackend::BufferStatus()> pending_drm_key_;
// If we are waiting for a DRM key to be available, this will be set. On
// destruction, we can use this token to unregister the callback with
// StarboardDrmKeyTracker. This is not necessary for correct functionality,
// but helps clean out StarboardDrmKeyTracker's list of callbacks.
std::optional<int64_t> drm_key_token_;
// This should be destructed first.
base::WeakPtrFactory<StarboardDecoder> weak_factory_{this};
};
} // namespace media
} // namespace chromecast
#endif // CHROMECAST_STARBOARD_MEDIA_MEDIA_STARBOARD_DECODER_H_