blob: df0231f8fa20f90a99aea33b1647a9a03c188b43 [file]
// 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.
#include "media/cast/encoding/media_video_encoder_wrapper.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/check.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "media/base/async_destroy_video_encoder.h"
#include "media/base/encoder_status.h"
#include "media/base/media_switches.h"
#include "media/base/media_util.h"
#include "media/base/video_codecs.h"
#include "media/base/video_encoder.h"
#include "media/base/video_encoder_metrics_provider.h"
#include "media/base/video_frame.h"
#include "media/cast/cast_config.h"
#include "media/cast/common/openscreen_conversion_helpers.h"
#include "media/cast/common/sender_encoded_frame.h"
#include "media/cast/constants.h"
#include "media/cast/encoding/encoding_support.h"
#include "media/cast/encoding/video_encoder.h"
#include "media/media_buildflags.h"
#include "media/video/gpu_video_accelerator_factories.h"
#include "media/video/video_encode_accelerator_adapter.h"
#include "media/video/video_encoder_info.h"
#include "media_video_encoder_wrapper.h"
#if BUILDFLAG(ENABLE_LIBVPX)
#include "media/video/vpx_video_encoder.h"
#endif
#if BUILDFLAG(ENABLE_LIBAOM)
#include "media/video/av1_video_encoder.h"
#endif // BUILDFLAG(ENABLE_LIBAOM)
namespace media::cast {
namespace {
std::unique_ptr<media::VideoEncoder> CreateHardwareEncoder(
media::GpuVideoAcceleratorFactories& gpu_factories,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
// TODO(crbug.com/282984511): consider providing a non-null MediaLog.
return std::make_unique<
media::AsyncDestroyVideoEncoder<media::VideoEncodeAcceleratorAdapter>>(
std::make_unique<media::VideoEncodeAcceleratorAdapter>(
&gpu_factories, std::make_unique<media::NullMediaLog>(),
std::move(task_runner)));
}
// TODO(crbug.com/282984511): consider adding support for H264 here, using
// the media::OpenH264VideoEncoder.
std::unique_ptr<media::VideoEncoder> CreateSoftwareEncoder(VideoCodec codec) {
switch (codec) {
#if BUILDFLAG(ENABLE_LIBVPX)
case VideoCodec::kVP8:
case VideoCodec::kVP9:
return std::make_unique<media::VpxVideoEncoder>();
#endif
#if BUILDFLAG(ENABLE_LIBAOM)
case VideoCodec::kAV1:
return std::make_unique<media::Av1VideoEncoder>();
#endif // BUILDFLAG(ENABLE_LIBAOM)
default:
NOTREACHED() << "Unhandled codec. value=" << std::to_underlying(codec);
}
}
// Encoder utilization is calculated differently depending on encoder
// acceleration:
// * Hardware encoders are pipelined, so we use the maximum of the backlog of
// frames waiting to be processed, and the processing time of each frame
// relative to the target frame duration.
// * Software encoders are typically synchronous, so we can directly map the
// processing time of each frame relative to the target frame duration.
double CalculateEncoderUtilization(bool is_hardware_encoder,
size_t backlog_size,
base::TimeDelta processing_time,
base::TimeDelta target_frame_duration) {
double utilization = 0.0;
if (is_hardware_encoder) {
// The "pipeline" is the set of frames that is actively undergoing
// processing. This is similar in size to the backlog but represents a
// separate stage in the encoding process. This value is chosen based on the
// capacity of a typical hardware encoder.
static constexpr int kPipelineSize = 3;
// The "backlog" is the set of frames that has been submitted for encoding
// but not yet taken for processing. Our value is based on the size of the
// pipeline, since the backlog is fully saturated when we have more frames
// than the pipeline can process.
static constexpr int kMaxBacklogSize = kPipelineSize + 1;
// 1. Backlog Model: Is the encoder keeping up with the submission rate?
double backlog_utilization =
static_cast<double>(backlog_size) / kMaxBacklogSize;
// 2. Latency Model: Is the pipeline taking too long glass-to-glass?
double latency_utilization =
processing_time / (target_frame_duration * kPipelineSize);
// Hardware utilization is the worst-case of the two metrics.
utilization = std::max(backlog_utilization, latency_utilization);
} else {
// Software is synchronous/bounded, so latency directly maps to CPU load.
utilization = processing_time / target_frame_duration;
}
// Clamp to a safe range to prevent PID controller explosion on anomalies
// (e.g., system sleep or thread starvation).
return std::clamp(utilization, 0.0, 2.0);
}
// Must be called on the ENCODER thread, which resolves to VIDEO for hardware
// encoding, and MAIN for software encoding.
void CallInitializeEncoder(media::VideoEncoder& encoder,
VideoCodecProfile profile,
const media::VideoEncoder::Options& options,
media::VideoEncoder::EncoderInfoCB info_cb,
media::VideoEncoder::OutputCB output_cb,
media::VideoEncoder::EncoderStatusCB done_cb) {
encoder.Initialize(profile, options, std::move(info_cb), std::move(output_cb),
std::move(done_cb));
// Our callbacks post to the correct thread, instead of needing the encoder to
// manage posting back.
encoder.DisablePostedCallbacks();
}
// Must be called on the ENCODER thread, which resolves to VIDEO for hardware
// encoding, and MAIN for software encoding.
void CallEncodeVideoFrame(
media::VideoEncoder& encoder,
scoped_refptr<media::VideoFrame> video_frame,
const media::VideoEncoder::EncodeOptions& encode_options,
media::VideoEncoder::EncoderStatusCB done_cb) {
encoder.Encode(std::move(video_frame), encode_options, std::move(done_cb));
}
// Must be called on the ENCODER thread, which resolves to VIDEO for hardware
// encoding, and MAIN for software encoding.
//
// NOTE: since this call depends on a successful flush, `flush_result` is
// checked before the encoder is accessed.
void CallChangeOptions(media::VideoEncoder& encoder,
media::VideoEncoder::Options options,
media::VideoEncoder::OutputCB output_cb,
media::VideoEncoder::EncoderStatusCB done_cb) {
encoder.ChangeOptions(std::move(options), std::move(output_cb),
std::move(done_cb));
}
// Must be called on the ENCODER thread, which resolves to VIDEO for hardware
// encoding, and MAIN for software encoding.
void CallFlush(media::VideoEncoder& encoder,
media::VideoEncoder::EncoderStatusCB done_cb) {
encoder.Flush(std::move(done_cb));
}
// TODO(crbug.com/282984511): just use EncoderStatus directly once we remove
// media::cast::VideoEncoder.
OperationalStatus ToOperationalStatus(EncoderStatus status) {
switch (status.code()) {
case EncoderStatus::Codes::kOk:
return STATUS_INITIALIZED;
case EncoderStatus::Codes::kEncoderInitializeNeverCompleted:
case EncoderStatus::Codes::kEncoderInitializeTwice:
case EncoderStatus::Codes::kEncoderInitializationError:
return STATUS_CODEC_INIT_FAILED;
case EncoderStatus::Codes::kEncoderUnsupportedProfile:
case EncoderStatus::Codes::kEncoderUnsupportedCodec:
return STATUS_UNSUPPORTED_CODEC;
case EncoderStatus::Codes::kEncoderUnsupportedConfig:
return STATUS_INVALID_CONFIGURATION;
// Most encoder statuses can just be considered runtime errors.
default:
return STATUS_CODEC_RUNTIME_ERROR;
}
}
} // namespace
MediaVideoEncoderWrapper::MediaVideoEncoderWrapper(
scoped_refptr<CastEnvironment> cast_environment,
const FrameSenderConfig& video_config,
std::unique_ptr<VideoEncoderMetricsProvider> metrics_provider,
StatusChangeCallback status_change_cb,
media::GpuVideoAcceleratorFactories* gpu_factories)
: cast_environment_(std::move(cast_environment)),
metrics_provider_(std::move(metrics_provider)),
status_change_cb_(std::move(status_change_cb)),
gpu_factories_(gpu_factories),
is_hardware_encoder_(video_config.use_hardware_encoder),
codec_(video_config.video_codec()),
target_frame_duration_(base::Seconds(1) / video_config.max_frame_rate),
encoder_(nullptr,
base::OnTaskRunnerDeleter(cast_environment_->GetTaskRunner(
CastEnvironment::ThreadId::kVideo))),
frame_complexity_estimator_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE})) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
CHECK(metrics_provider_);
CHECK(status_change_cb_);
encode_options_.key_frame = true;
options_.bitrate = Bitrate::ConstantBitrate(
base::checked_cast<uint32_t>(video_config.start_bitrate));
// NOTE: the H264 encoder can produce either AVC or annexb formatted frames.
// Annexb is better supported by Cast receivers.
if (codec_ == media::VideoCodec::kH264) {
options_.avc.produce_annexb = true;
}
// NOTE: since we don't actually know the frame size until the first
// frame, the encoder will not get created until the first call to
// `EncodeVideoFrame`. However, it is ready to start receiving frames.
status_change_cb_.Run(STATUS_INITIALIZED);
}
MediaVideoEncoderWrapper::~MediaVideoEncoderWrapper() {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
weak_factory_.InvalidateWeakPtrs();
}
bool MediaVideoEncoderWrapper::EncodeVideoFrame(
scoped_refptr<media::VideoFrame> video_frame,
base::TimeTicks reference_time,
FrameEncodedCallback frame_encoded_callback) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
CHECK(!video_frame->visible_rect().IsEmpty());
// TODO(crbug.com/282984511): consider adding optimization to store frames
// that are queued for encoding and sending them once updating options is
// complete.
if (num_pending_updates_ > 0) {
return false;
}
// Construct and initialize the encoder on the first call to this method.
const gfx::Size frame_size = video_frame->visible_rect().size();
if (frame_size != options_.frame_size) {
if (options_.frame_size.IsEmpty()) {
// First frame, reconstruct immediately to avoid startup latency.
options_.frame_size = frame_size;
ConstructEncoder();
} else if (!target_resize_size_ || frame_size != *target_resize_size_) {
// Debounce subsequent size changes. Only restart timer if the new size
// is different from the current target.
target_resize_size_ = frame_size;
resize_debounce_timer_.Start(
FROM_HERE, base::Milliseconds(250),
base::BindOnce(
&MediaVideoEncoderWrapper::ReconstructEncoderForNewSize,
weak_factory_.GetWeakPtr(), frame_size));
}
} else if (resize_debounce_timer_.IsRunning()) {
// If the frame size returns to the original size before the timer fires,
// cancel the resize.
resize_debounce_timer_.Stop();
target_resize_size_.reset();
}
CHECK(encoder_);
frame_complexity_estimator_
.AsyncCall(encode_options_.key_frame
? &FrameComplexityEstimator::EstimateForKeyFrame
: &FrameComplexityEstimator::EstimateForDeltaFrame)
.WithArgs(std::cref(*video_frame))
.Then(base::BindOnce(
&MediaVideoEncoderWrapper::OnComplexityEstimated,
weak_factory_.GetWeakPtr(), video_frame, encode_options_,
reference_time, std::move(frame_encoded_callback), encoder_version_));
encode_options_.key_frame = false;
return true;
}
void MediaVideoEncoderWrapper::OnComplexityEstimated(
scoped_refptr<media::VideoFrame> video_frame,
media::VideoEncoder::EncodeOptions encode_options,
base::TimeTicks reference_time,
FrameEncodedCallback frame_encoded_callback,
int encoder_version,
std::optional<double> estimated_complexity) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (encoder_version != encoder_version_) {
// The encoder was reconstructed while we were estimating the quantizer.
// Drop this frame as it was meant for the old encoder.
std::move(frame_encoded_callback).Run(nullptr);
return;
}
CHECK(encoder_);
recent_metadata_.emplace(
video_frame->metadata().capture_begin_time,
video_frame->metadata().capture_end_time, base::TimeTicks::Now(),
ToRtpTimeTicks(video_frame->timestamp(), kVideoFrequency), reference_time,
GetFrameDuration(*video_frame), estimated_complexity,
std::move(frame_encoded_callback));
// Now that `GetFrameDuration` has been called, we can update the last frame
// timestamp. It must be monotonically increasing.
if (last_frame_timestamp_) {
CHECK_GT(video_frame->timestamp(), last_frame_timestamp_.value());
}
last_frame_timestamp_ = video_frame->timestamp();
auto encode_task = base::BindOnce(
&CallEncodeVideoFrame, std::ref(*encoder_), std::move(video_frame),
encode_options,
CreateCallback(&MediaVideoEncoderWrapper::OnFrameEncodeDone,
encoder_version, reference_time));
if (num_pending_updates_ > 0) {
pending_encodes_.push_back(std::move(encode_task));
} else {
CallEncoderOnCorrectThread(std::move(encode_task));
}
}
// Inform the encoder about the new target bit rate.
void MediaVideoEncoderWrapper::SetBitRate(uint32_t new_bit_rate) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
options_.bitrate = Bitrate::ConstantBitrate(new_bit_rate);
// If this method is called before the encoder_ is constructed, the bitrate
// will be set as part of construction.
if (encoder_) {
// If we have an encoder, we need to update its options.
UpdateEncoderOptions();
}
}
void MediaVideoEncoderWrapper::ReconstructEncoderForNewSize(
const gfx::Size& new_size) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
target_resize_size_.reset();
if (new_size != options_.frame_size) {
options_.frame_size = new_size;
ConstructEncoder();
}
}
// Inform the encoder to encode the next frame as a key frame.
void MediaVideoEncoderWrapper::GenerateKeyFrame() {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
encode_options_.key_frame = true;
}
void MediaVideoEncoderWrapper::OnEncodedFrame(
int encoder_version,
VideoEncoderOutput output,
std::optional<media::VideoEncoder::CodecDescription> description) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (encoder_version != encoder_version_) {
return;
}
CachedMetadata& metadata = recent_metadata_.front();
auto encoded_frame = std::make_unique<SenderEncodedFrame>();
encoded_frame->is_key_frame = output.key_frame;
encoded_frame->frame_id = next_frame_id_++;
encoded_frame->referenced_frame_id = encoded_frame->is_key_frame
? encoded_frame->frame_id
: encoded_frame->frame_id - 1;
encoded_frame->rtp_timestamp = metadata.rtp_timestamp;
encoded_frame->reference_time = metadata.reference_time;
encoded_frame->encode_completion_time = cast_environment_->NowTicks();
const base::TimeDelta processing_time =
encoded_frame->encode_completion_time - metadata.encode_start_time;
encoded_frame->encoder_utilization =
ComputeAndRecordUtilization(processing_time);
encoded_frame->lossiness =
metadata.estimated_complexity
? ComputeLossiness(output.data.size(), metadata.frame_duration,
*metadata.estimated_complexity)
: 0.0f;
encoded_frame->capture_begin_time = metadata.capture_begin_time;
encoded_frame->capture_end_time = metadata.capture_end_time;
encoded_frame->data = std::move(output.data);
auto frame_encoded_callback = std::move(metadata.frame_encoded_callback);
recent_metadata_.pop();
metrics_provider_->IncrementEncodedFrameCount();
std::move(frame_encoded_callback).Run(std::move(encoded_frame));
}
double MediaVideoEncoderWrapper::ComputeAndRecordUtilization(
base::TimeDelta processing_time) {
const double raw_utilization =
CalculateEncoderUtilization(is_hardware_encoder_, recent_metadata_.size(),
processing_time, target_frame_duration_);
// Apply EMA to smooth out I-frame spikes.
// A weight of 0.1 for the new value is standard for 30-60fps media pipelines.
static constexpr double kEmaWeight = 0.1;
if (!ema_encoder_utilization_) {
ema_encoder_utilization_ = raw_utilization;
} else {
ema_encoder_utilization_ = (raw_utilization * kEmaWeight) +
(*ema_encoder_utilization_ * (1.0 - kEmaWeight));
}
return *ema_encoder_utilization_;
}
// Computes a heuristic representing the visual quality degradation of the
// encoded frame. Upstream rate controllers use this "lossiness" metric to
// decide if they need to drop the framerate to give individual frames a
// larger bit budget.
//
// A higher return value indicates a more heavily compressed (lower quality)
// frame. This is estimated by multiplying the frame's bitrate utilization
// by its estimated spatial/temporal complexity.
double MediaVideoEncoderWrapper::ComputeLossiness(
size_t data_size,
base::TimeDelta frame_duration,
double estimated_complexity) const {
const double duration = frame_duration.InSecondsF();
if (duration <= 0.0) {
return 0.0;
}
// 1. Calculate the effective bitrate of this specific frame.
const double data_size_in_bits = data_size * 8.0;
const double actual_bitrate = data_size_in_bits / duration;
// 2. Calculate bitrate utilization compared to target bitrate.
// This measures how much of our target bandwidth budget this frame consumed.
// If actual_bitrate > target_bitrate, the encoder is overshooting to handle
// difficult content.
const double target_bitrate = options_.bitrate->target_bps();
CHECK_GT(target_bitrate, 0.0);
const double bitrate_utilization = actual_bitrate / target_bitrate;
// 3. Scale utilization by the estimated complexity of the frame.
// The estimated_complexity represents how difficult the frame is to compress
// (e.g., high motion or high detail). A frame that uses 100% of the bitrate
// budget on a blank wall (low complexity) is not very lossy. A frame that
// uses 100% of the budget on a confetti explosion (high complexity) is
// assumed to be highly lossy.
const double raw_lossiness = bitrate_utilization * estimated_complexity;
// 4. Clamp to a safe upper bound to protect the rate controller's internal
// state from timestamp jitter anomalies.
constexpr double kMaxLossiness = 5.0;
return std::clamp(raw_lossiness, 0.0, kMaxLossiness);
}
void MediaVideoEncoderWrapper::OnEncoderStatus(int encoder_version,
EncoderStatus error) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (encoder_version != encoder_version_) {
return;
}
if (!last_recorded_status_ || error != last_recorded_status_.value()) {
last_recorded_status_ = error;
if (!error.is_ok()) {
CHECK(metrics_provider_);
metrics_provider_->SetError(std::move(error));
}
// Running the status change callback may delete `this`, so this should be
// done last.
status_change_cb_.Run(ToOperationalStatus(error));
}
}
void MediaVideoEncoderWrapper::OnEncoderInfo(
int encoder_version,
const VideoEncoderInfo& encoder_info) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (encoder_version != encoder_version_) {
return;
}
// TODO(crbug.com/282984511): support handling `supports_frame_size_change`
// property.
}
void MediaVideoEncoderWrapper::SetEncoderForTesting(
std::unique_ptr<media::VideoEncoder> encoder) {
encoder_is_overridden_for_testing_ = true;
SetEncoder(std::move(encoder));
}
MediaVideoEncoderWrapper::CachedMetadata::CachedMetadata(
std::optional<base::TimeTicks> capture_begin_time,
std::optional<base::TimeTicks> capture_end_time,
base::TimeTicks encode_start_time,
RtpTimeTicks rtp_timestamp,
base::TimeTicks reference_time,
base::TimeDelta frame_duration,
std::optional<double> estimated_complexity,
FrameEncodedCallback frame_encoded_callback)
: capture_begin_time(capture_begin_time),
capture_end_time(capture_end_time),
encode_start_time(encode_start_time),
rtp_timestamp(rtp_timestamp),
reference_time(reference_time),
frame_duration(frame_duration),
estimated_complexity(estimated_complexity),
frame_encoded_callback(std::move(frame_encoded_callback)) {}
MediaVideoEncoderWrapper::CachedMetadata::CachedMetadata() = default;
MediaVideoEncoderWrapper::CachedMetadata::CachedMetadata(
MediaVideoEncoderWrapper::CachedMetadata&& other) = default;
MediaVideoEncoderWrapper::CachedMetadata&
MediaVideoEncoderWrapper::CachedMetadata::operator=(
MediaVideoEncoderWrapper::CachedMetadata&& other) = default;
MediaVideoEncoderWrapper::CachedMetadata::~CachedMetadata() = default;
void MediaVideoEncoderWrapper::ConstructEncoder() {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
encoder_version_++;
// Clear any pending encodes and options updates meant for the old encoder.
pending_encodes_.clear();
num_pending_updates_ = 0;
// Clear metadata and run callbacks with nullptr for any frames that were
// in-flight to the old encoder, as the old encoder will be deleted and will
// not emit them.
while (!recent_metadata_.empty()) {
auto callback = std::move(recent_metadata_.front().frame_encoded_callback);
recent_metadata_.pop();
if (callback) {
std::move(callback).Run(nullptr);
}
}
if (encoder_is_overridden_for_testing_) {
// Don't construct a new encoder if it is overridden for testing.
} else if (is_hardware_encoder_) {
CHECK(gpu_factories_);
SetEncoder(CreateHardwareEncoder(
*gpu_factories_,
cast_environment_->GetTaskRunner(CastEnvironment::ThreadId::kMain)));
} else {
SetEncoder(CreateSoftwareEncoder(codec_));
}
CHECK(encoder_);
const VideoCodecProfile profile = encoding_support::ToProfile(codec_);
metrics_provider_->Initialize(profile, options_.frame_size,
is_hardware_encoder_);
CallEncoderOnCorrectThread(base::BindOnce(
&CallInitializeEncoder, std::ref(*encoder_), profile, options_,
CreateCallback(&MediaVideoEncoderWrapper::OnEncoderInfo,
encoder_version_),
CreateCallback(&MediaVideoEncoderWrapper::OnEncodedFrame,
encoder_version_),
CreateCallback(&MediaVideoEncoderWrapper::OnEncoderStatus,
encoder_version_)));
}
void MediaVideoEncoderWrapper::SetEncoder(
std::unique_ptr<media::VideoEncoder> encoder) {
// This unusual syntax unwraps `encoder`, which has a default deleter, and
// transfers ownership to `encoder_`, which has a base::OnTaskRunnerDeleter.
encoder_.reset(encoder.release());
}
base::TimeDelta MediaVideoEncoderWrapper::GetFrameDuration(
const VideoFrame& frame) {
// Frame has duration in metadata, use it.
if (frame.metadata().frame_duration.has_value()) {
return frame.metadata().frame_duration.value();
}
// No real way to figure out duration, use time passed since the last frame
// as an educated guess, but clamp it within reasonable limits.
constexpr auto min_duration = base::Seconds(1.0 / 60.0);
constexpr auto max_duration = base::Seconds(1.0 / 24.0);
const base::TimeDelta duration =
frame.timestamp() - last_frame_timestamp_.value_or(base::TimeDelta{});
return std::clamp(duration, min_duration, max_duration);
}
// The VideoEncoder API requires you to Flush() and wait before calling
// ChangeOptions(). This results in this annoying set of nested callbacks.
void MediaVideoEncoderWrapper::UpdateEncoderOptions() {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
num_pending_updates_++;
auto flush_done_callback =
CreateCallback(&MediaVideoEncoderWrapper::OnFlushDoneForOptionsUpdate,
encoder_version_, options_);
CallEncoderOnCorrectThread(base::BindOnce(&CallFlush, std::ref(*encoder_),
std::move(flush_done_callback)));
}
void MediaVideoEncoderWrapper::OnFlushDoneForOptionsUpdate(
int encoder_version,
media::VideoEncoder::Options options,
EncoderStatus status) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (encoder_version != encoder_version_) {
return;
}
if (!status.is_ok()) {
OnOptionsUpdated(encoder_version, status);
return;
}
CallEncoderOnCorrectThread(base::BindOnce(
&CallChangeOptions, std::ref(*encoder_), std::move(options),
CreateCallback(&MediaVideoEncoderWrapper::OnEncodedFrame,
encoder_version),
CreateCallback(&MediaVideoEncoderWrapper::OnOptionsUpdated,
encoder_version)));
}
void MediaVideoEncoderWrapper::CallEncoderOnCorrectThread(
base::OnceClosure closure) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
// If hardware, let the encoder do the post to the accelerator / VIDEO thread
// on its own.
if (is_hardware_encoder_) {
std::move(closure).Run();
// If software, manually post it to the VIDEO thread to not block MAIN.
} else {
cast_environment_->PostTask(CastEnvironment::ThreadId::kVideo, FROM_HERE,
std::move(closure));
}
}
void MediaVideoEncoderWrapper::OnFrameEncodeDone(int encoder_version,
base::TimeTicks reference_time,
EncoderStatus status) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (encoder_version != encoder_version_) {
return;
}
// An "OK" status is a no-op: the frame is handled in OnEncodedFrame().
if (status.is_ok()) {
return;
}
// Otherwise notify the VideoSender that we had a failed encode (so that it
// can appropriately update the duration in flight).
CachedMetadata& metadata = recent_metadata_.front();
CHECK(metadata.reference_time == reference_time);
auto callback = std::move(metadata.frame_encoded_callback);
recent_metadata_.pop();
std::move(callback).Run(nullptr);
OnEncoderStatus(encoder_version, status);
}
void MediaVideoEncoderWrapper::OnOptionsUpdated(int encoder_version,
EncoderStatus status) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (encoder_version != encoder_version_) {
return;
}
--num_pending_updates_;
if (num_pending_updates_ == 0) {
for (auto& task : pending_encodes_) {
CallEncoderOnCorrectThread(std::move(task));
}
pending_encodes_.clear();
}
OnEncoderStatus(encoder_version, status);
}
} // namespace media::cast