#include "media/cast/encoding/media_video_encoder_wrapper.h"
#include <memory>
#include <utility>
#include "base/check.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 "base/types/cxx23_to_underlying.h"
#include "media/base/async_destroy_video_encoder.h"
#include "media/base/encoder_status.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/fake_software_video_encoder.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
namespace media::cast {
namespace {
std::unique_ptr<media::VideoEncoder> CreateHardwareEncoder(
media::GpuVideoAcceleratorFactories& gpu_factories,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
return std::make_unique<
media::AsyncDestroyVideoEncoder<media::VideoEncodeAcceleratorAdapter>>(
std::make_unique<media::VideoEncodeAcceleratorAdapter>(
&gpu_factories, std::make_unique<media::NullMediaLog>(),
std::move(task_runner)));
}
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
default:
NOTREACHED() << "Unhandled codec. value=" << base::to_underlying(codec);
}
}
VideoCodecProfile ToProfile(VideoCodec codec) {
switch (codec) {
case VideoCodec::kH264:
return H264PROFILE_MAIN;
case VideoCodec::kHEVC:
return HEVCPROFILE_MAIN;
case VideoCodec::kVP8:
return VP8PROFILE_ANY;
case VideoCodec::kVP9:
return VP9PROFILE_PROFILE0;
case VideoCodec::kAV1:
return AV1PROFILE_PROFILE_MAIN;
default:
NOTREACHED() << "Unhandled codec. value=" << base::to_underlying(codec);
}
}
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));
encoder.DisablePostedCallbacks();
}
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));
}
void CallChangeOptions(media::VideoEncoder& encoder,
media::VideoEncoder::Options options,
media::VideoEncoder::OutputCB output_cb,
media::VideoEncoder::EncoderStatusCB done_cb,
EncoderStatus flush_result) {
if (!flush_result.is_ok()) {
std::move(done_cb).Run(flush_result);
return;
}
encoder.ChangeOptions(std::move(options), std::move(output_cb),
std::move(done_cb));
}
void CallFlush(media::VideoEncoder& encoder,
media::VideoEncoder::EncoderStatusCB done_cb) {
encoder.Flush(std::move(done_cb));
}
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;
default:
return STATUS_CODEC_RUNTIME_ERROR;
}
}
}
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()),
encoder_(nullptr,
base::OnTaskRunnerDeleter(cast_environment_->GetTaskRunner(
CastEnvironment::ThreadId::kVideo))) {
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));
if (codec_ == media::VideoCodec::kH264) {
options_.avc.produce_annexb = true;
}
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());
if (num_pending_updates_ > 0) {
return false;
}
const gfx::Size frame_size = video_frame->visible_rect().size();
if (frame_size != options_.frame_size) {
options_.frame_size = frame_size;
ConstructEncoder();
}
CHECK(encoder_);
recent_metadata_.emplace(CachedMetadata{
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), std::move(frame_encoded_callback)});
if (last_frame_timestamp_) {
CHECK_GT(video_frame->timestamp(), last_frame_timestamp_.value());
}
last_frame_timestamp_ = video_frame->timestamp();
CallEncoderOnCorrectThread(base::BindOnce(
&CallEncodeVideoFrame, std::ref(*encoder_), std::move(video_frame),
encode_options_,
CreateCallback(&MediaVideoEncoderWrapper::OnFrameEncodeDone,
reference_time)));
encode_options_.key_frame = false;
return true;
}
void MediaVideoEncoderWrapper::SetBitRate(int new_bit_rate) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
options_.bitrate =
Bitrate::ConstantBitrate(base::checked_cast<uint32_t>(new_bit_rate));
if (encoder_) {
UpdateEncoderOptions();
}
}
void MediaVideoEncoderWrapper::GenerateKeyFrame() {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
encode_options_.key_frame = true;
}
void MediaVideoEncoderWrapper::OnEncodedFrame(
VideoEncoderOutput output,
std::optional<media::VideoEncoder::CodecDescription> description) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
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 =
processing_time / metadata.frame_duration;
encoded_frame->lossiness = 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));
}
void MediaVideoEncoderWrapper::OnEncoderStatus(EncoderStatus error) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
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));
}
status_change_cb_.Run(ToOperationalStatus(error));
}
}
void MediaVideoEncoderWrapper::OnEncoderInfo(
const VideoEncoderInfo& encoder_info) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
}
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,
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),
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));
if (is_hardware_encoder_) {
CHECK(gpu_factories_);
SetEncoder(CreateHardwareEncoder(
*gpu_factories_,
cast_environment_->GetTaskRunner(CastEnvironment::ThreadId::kMain)));
} else if (encoder_is_overridden_for_testing_) {
} else {
SetEncoder(CreateSoftwareEncoder(codec_));
}
CHECK(encoder_);
const VideoCodecProfile profile = ToProfile(codec_);
metrics_provider_->Initialize(profile, options_.frame_size,
is_hardware_encoder_);
CallEncoderOnCorrectThread(base::BindOnce(
&CallInitializeEncoder, std::ref(*encoder_), profile, options_,
CreateCallback(&MediaVideoEncoderWrapper::OnEncoderInfo),
CreateCallback(&MediaVideoEncoderWrapper::OnEncodedFrame),
CreateCallback(&MediaVideoEncoderWrapper::OnEncoderStatus)));
}
void MediaVideoEncoderWrapper::SetEncoder(
std::unique_ptr<media::VideoEncoder> encoder) {
encoder_.reset(encoder.release());
}
base::TimeDelta MediaVideoEncoderWrapper::GetFrameDuration(
const VideoFrame& frame) {
if (frame.metadata().frame_duration.has_value()) {
return frame.metadata().frame_duration.value();
}
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);
}
void MediaVideoEncoderWrapper::UpdateEncoderOptions() {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
num_pending_updates_++;
auto flush_done_callback = base::BindOnce(
&CallChangeOptions,
std::ref(*encoder_), options_,
CreateCallback(&MediaVideoEncoderWrapper::OnEncodedFrame),
CreateCallback(&MediaVideoEncoderWrapper::OnOptionsUpdated));
CallEncoderOnCorrectThread(base::BindOnce(&CallFlush, std::ref(*encoder_),
std::move(flush_done_callback)));
}
void MediaVideoEncoderWrapper::CallEncoderOnCorrectThread(
base::OnceClosure closure) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (is_hardware_encoder_) {
std::move(closure).Run();
} else {
cast_environment_->PostTask(CastEnvironment::ThreadId::kVideo, FROM_HERE,
std::move(closure));
}
}
void MediaVideoEncoderWrapper::OnFrameEncodeDone(base::TimeTicks reference_time,
EncoderStatus status) {
if (status.is_ok()) {
return;
}
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);
}
void MediaVideoEncoderWrapper::OnOptionsUpdated(EncoderStatus status) {
CHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
--num_pending_updates_;
OnEncoderStatus(status);
}
}