// Copyright 2021 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. #ifndef CAST_STANDALONE_SENDER_STREAMING_AV1_ENCODER_H_ #define CAST_STANDALONE_SENDER_STREAMING_AV1_ENCODER_H_ #include #include #include #include // NOLINT #include #include #include #include #include #include #include "absl/base/thread_annotations.h" #include "cast/standalone_sender/streaming_video_encoder.h" #include "cast/streaming/constants.h" #include "cast/streaming/frame_id.h" #include "cast/streaming/rtp_time.h" #include "platform/api/task_runner.h" #include "platform/api/time.h" namespace openscreen { class TaskRunner; namespace cast { class Sender; // Uses libaom to encode AV1 video and streams it to a Sender. Includes // extensive logic for fine-tuning the encoder parameters in real-time, to // provide the best quality results given external, uncontrollable factors: // CPU/network availability, and the complexity of the video frame content. // // Internally, a separate encode thread is created and used to prevent blocking // the main thread while frames are being encoded. All public API methods are // assumed to be called on the same sequence/thread as the main TaskRunner // (injected via the constructor). // // Usage: // // 1. EncodeAndSend() is used to queue-up video frames for encoding and sending, // which will be done on a best-effort basis. // // 2. The client is expected to call SetTargetBitrate() frequently based on its // own bandwidth estimates and congestion control logic. In addition, a client // may provide a callback for each frame's encode statistics, which can be used // to further optimize the user experience. For example, the stats can be used // as a signal to reduce the data volume (i.e., resolution and/or frame rate) // coming from the video capture source. class StreamingAv1Encoder : public StreamingVideoEncoder { public: StreamingAv1Encoder(const Parameters& params, TaskRunner* task_runner, Sender* sender); ~StreamingAv1Encoder(); int GetTargetBitrate() const override; void SetTargetBitrate(int new_bitrate) override; void EncodeAndSend(const VideoFrame& frame, Clock::time_point reference_time, std::function stats_callback) override; private: // Syntactic convenience to wrap the aom_image_t alloc/free API in a smart // pointer. struct Av1ImageDeleter { void operator()(aom_image_t* ptr) const { aom_img_free(ptr); } }; using Av1ImageUniquePtr = std::unique_ptr; // Represents the state of one frame encode. This is created in // EncodeAndSend(), and passed to the encode thread via the |encode_queue_|. struct WorkUnit { Av1ImageUniquePtr image; Clock::duration duration; Clock::time_point reference_time; RtpTimeTicks rtp_timestamp; std::function stats_callback; }; // Same as WorkUnit, but with additional fields to carry the encode results. struct WorkUnitWithResults : public WorkUnit { std::vector payload; bool is_key_frame = false; Stats stats; }; bool is_encoder_initialized() const { return config_.g_threads != 0; } // Destroys the AV1 encoder context if it has been initialized. void DestroyEncoder(); // The procedure for the |encode_thread_| that loops, processing work units // from the |encode_queue_| by calling Encode() until it's time to end the // thread. void ProcessWorkUnitsUntilTimeToQuit(); // If the |encoder_| is live, attempt reconfiguration to allow it to encode // frames at a new frame size or target bitrate. If reconfiguration is not // possible, destroy the existing instance and re-create a new |encoder_| // instance. void PrepareEncoder(int width, int height, int target_bitrate); // Wraps the complex libaom aom_codec_encode() call using inputs from // |work_unit| and populating results there. void EncodeFrame(bool force_key_frame, WorkUnitWithResults& work_unit); // Computes and populates |work_unit.stats| after the last call to // EncodeFrame(). void ComputeFrameEncodeStats(Clock::duration encode_wall_time, int target_bitrate, WorkUnitWithResults& work_unit); // Assembles and enqueues an EncodedFrame with the Sender on the main thread. void SendEncodedFrame(WorkUnitWithResults results); // Allocates a aom_image_t and copies the content from |frame| to it. static Av1ImageUniquePtr CloneAsAv1Image(const VideoFrame& frame); // The reference time of the first frame passed to EncodeAndSend(). Clock::time_point start_time_ = Clock::time_point::min(); // The RTP timestamp of the last frame that was pushed into the // |encode_queue_| by EncodeAndSend(). This is used to check whether // timestamps are monotonically increasing. RtpTimeTicks last_enqueued_rtp_timestamp_; // Guards a few members shared by both the main and encode threads. std::mutex mutex_; // Used by the encode thread to sleep until more work is available. std::condition_variable cv_ ABSL_GUARDED_BY(mutex_); // These encode parameters not passed in the WorkUnit struct because it is // desirable for them to be applied as soon as possible, with the very next // WorkUnit popped from the |encode_queue_| on the encode thread, and not to // wait until some later WorkUnit is processed. bool needs_key_frame_ ABSL_GUARDED_BY(mutex_) = true; int target_bitrate_ ABSL_GUARDED_BY(mutex_) = 2 << 20; // Default: 2 Mbps. // The queue of frame encodes. The size of this queue is implicitly bounded by // EncodeAndSend(), where it checks for the total in-flight media duration and // maybe drops a frame. std::queue encode_queue_ ABSL_GUARDED_BY(mutex_); // Current AV1 encoder configuration. Most of the fields are unchanging, and // are populated in the ctor; but thereafter, only the encode thread accesses // this struct. // // The speed setting is controlled via a separate libaom API (see members // below). aom_codec_enc_cfg_t config_{}; // libaom AV1 encoder instance. Only the encode thread accesses this. aom_codec_ctx_t encoder_; }; } // namespace cast } // namespace openscreen #endif // CAST_STANDALONE_SENDER_STREAMING_AV1_ENCODER_H_