#include "media/cast/encoding/external_video_encoder.h"
#include <array>
#include <cmath>
#include <list>
#include <sstream>
#include <utility>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "media/base/bitrate.h"
#include "media/base/bitstream_buffer.h"
#include "media/base/media_switches.h"
#include "media/base/media_util.h"
#include "media/base/video_codecs.h"
#include "media/base/video_encoder_metrics_provider.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "media/base/video_util.h"
#include "media/cast/cast_config.h"
#include "media/cast/common/encoded_frame.h"
#include "media/cast/common/openscreen_conversion_helpers.h"
#include "media/cast/common/rtp_time.h"
#include "media/cast/common/sender_encoded_frame.h"
#include "media/cast/encoding/encoding_util.h"
#include "media/cast/encoding/vpx_quantizer_parser.h"
#include "media/cast/logging/logging_defines.h"
#include "media/parsers/h264_parser.h"
namespace media::cast {
namespace {
constexpr int kFrameSamplingPercentage = 10;
constexpr int kMaxH264Quantizer = 51;
constexpr size_t kOutputBufferCount = 3;
constexpr size_t kExtraInputBufferCount = 2;
constexpr int kBacklogRedlineThreshold = 4;
constexpr int kQuantizationHistogramSize = 511;
bool IsVpxProfile(VideoCodecProfile codec_profile) {
const VideoCodec codec = VideoCodecProfileToVideoCodec(codec_profile);
return codec == VideoCodec::kVP8 || codec == VideoCodec::kVP9;
}
}
struct InProgressExternalVideoFrameEncode {
const scoped_refptr<VideoFrame> video_frame;
const base::TimeTicks reference_time;
VideoEncoder::FrameEncodedCallback frame_encoded_callback;
const int target_bit_rate;
const base::TimeTicks start_time;
InProgressExternalVideoFrameEncode(
scoped_refptr<VideoFrame> v_frame,
base::TimeTicks r_time,
VideoEncoder::FrameEncodedCallback callback,
int bit_rate)
: video_frame(std::move(v_frame)),
reference_time(r_time),
frame_encoded_callback(std::move(callback)),
target_bit_rate(bit_rate),
start_time(base::TimeTicks::Now()) {}
};
class ExternalVideoEncoder::VEAClientImpl final
: public VideoEncodeAccelerator::Client,
public base::RefCountedThreadSafe<VEAClientImpl> {
public:
REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
using EncoderStatusChangeCallback =
base::RepeatingCallback<void(media::EncoderStatus, OperationalStatus)>;
VEAClientImpl(
const scoped_refptr<CastEnvironment>& cast_environment,
const scoped_refptr<base::SingleThreadTaskRunner>& encoder_task_runner,
std::unique_ptr<media::VideoEncodeAccelerator> vea,
double max_frame_rate,
EncoderStatusChangeCallback status_change_cb)
: cast_environment_(cast_environment),
task_runner_(encoder_task_runner),
max_frame_rate_(max_frame_rate),
status_change_cb_(std::move(status_change_cb)),
video_encode_accelerator_(std::move(vea)),
encoder_active_(false),
next_frame_id_(FrameId::first()),
key_frame_encountered_(false),
codec_profile_(media::VIDEO_CODEC_PROFILE_UNKNOWN),
key_frame_quantizer_parsable_(false),
requested_bit_rate_(-1),
allocate_input_buffer_in_progress_(false) {}
VEAClientImpl(const VEAClientImpl&) = delete;
VEAClientImpl& operator=(const VEAClientImpl&) = delete;
base::SingleThreadTaskRunner* task_runner() const {
return task_runner_.get();
}
void Initialize(const gfx::Size& frame_size,
VideoCodecProfile codec_profile,
int start_bit_rate,
FrameId first_frame_id) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
requested_bit_rate_ = start_bit_rate;
const media::Bitrate bitrate = media::Bitrate::ConstantBitrate(
base::saturated_cast<uint32_t>(start_bit_rate));
media::VideoEncodeAccelerator::Config config(
media::PIXEL_FORMAT_I420, frame_size, codec_profile, bitrate,
static_cast<uint32_t>(max_frame_rate_ + 0.5),
media::VideoEncodeAccelerator::Config::StorageType::kShmem,
media::VideoEncodeAccelerator::Config::ContentType::kDisplay);
config.drop_frame_thresh_percentage = GetEncoderDropFrameThreshold();
encoder_active_ =
video_encode_accelerator_
->Initialize(config, this, std::make_unique<media::NullMediaLog>())
.is_ok();
next_frame_id_ = first_frame_id;
codec_profile_ = codec_profile;
UMA_HISTOGRAM_BOOLEAN("Cast.Sender.VideoEncodeAcceleratorInitializeSuccess",
encoder_active_);
cast_environment_->PostTask(
CastEnvironment::ThreadId::kMain, FROM_HERE,
base::BindOnce(
status_change_cb_,
encoder_active_ ? media::EncoderStatus::Codes::kOk
: media::EncoderStatus::Codes::kEncoderFailedEncode,
encoder_active_ ? STATUS_INITIALIZED : STATUS_CODEC_INIT_FAILED));
}
void SetBitRate(int bit_rate) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
requested_bit_rate_ = bit_rate;
if (encoder_active_) {
video_encode_accelerator_->RequestEncodingParametersChange(
Bitrate::ConstantBitrate(base::saturated_cast<uint32_t>(bit_rate)),
static_cast<uint32_t>(max_frame_rate_ + 0.5), std::nullopt);
}
}
void ReturnInputBufferToPool(int index) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK_GE(index, 0);
DCHECK_LT(index, static_cast<int>(input_buffers_.size()));
free_input_buffer_index_.push_back(index);
}
void EncodeVideoFrame(
scoped_refptr<media::VideoFrame> video_frame,
base::TimeTicks reference_time,
bool key_frame_requested,
VideoEncoder::FrameEncodedCallback frame_encoded_callback) {
TRACE_EVENT0("media", "ExternalVideoEncoder::EncodeVideoFrame");
DCHECK(task_runner_->RunsTasksInCurrentSequence());
in_progress_frame_encodes_.push_back(InProgressExternalVideoFrameEncode(
video_frame, reference_time, std::move(frame_encoded_callback),
requested_bit_rate_));
if (!encoder_active_) {
AbortLatestEncodeAttemptDueToErrors();
return;
}
if (free_input_buffer_index_.empty()) {
if (!allocate_input_buffer_in_progress_ &&
input_buffers_.size() < max_allowed_input_buffers_) {
allocate_input_buffer_in_progress_ = true;
const size_t buffer_size = media::VideoFrame::AllocationSize(
media::PIXEL_FORMAT_I420, frame_coded_size_);
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VEAClientImpl::AllocateInputBuffer, this,
buffer_size));
}
AbortLatestEncodeAttemptDueToErrors();
return;
}
scoped_refptr<media::VideoFrame> frame = video_frame;
if (video_frame->coded_size() != frame_coded_size_ ||
video_frame->storage_type() !=
media::VideoFrame::StorageType::STORAGE_SHMEM) {
TRACE_EVENT1("media", "VideoFrame copy", "coded size",
video_frame->coded_size().ToString());
const int index = free_input_buffer_index_.back();
auto& mapped_region = input_buffers_[index];
DCHECK(mapped_region.IsValid());
CHECK_GE(frame_coded_size_.height(),
video_frame->visible_rect().height());
CHECK_GE(frame_coded_size_.width(), video_frame->visible_rect().width());
frame = VideoFrame::WrapExternalData(
video_frame->format(), frame_coded_size_,
gfx::Rect(video_frame->visible_rect().size()),
video_frame->visible_rect().size(),
mapped_region.mapping.GetMemoryAsSpan<uint8_t>(),
video_frame->timestamp());
if (!frame || !media::I420CopyWithPadding(*video_frame, frame.get())) {
LOG(DFATAL) << "Error: ExternalVideoEncoder: copy failed.";
AbortLatestEncodeAttemptDueToErrors();
return;
}
frame->BackWithSharedMemory(&mapped_region.region);
frame->AddDestructionObserver(
base::BindPostTaskToCurrentDefault(base::BindOnce(
&ExternalVideoEncoder::VEAClientImpl::ReturnInputBufferToPool,
this, index)));
free_input_buffer_index_.pop_back();
}
video_encode_accelerator_->Encode(std::move(frame), key_frame_requested);
}
protected:
void NotifyErrorStatus(const media::EncoderStatus& status) final {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
CHECK(!status.is_ok());
LOG(ERROR) << "NotifyErrorStatus() is called, code="
<< static_cast<int32_t>(status.code())
<< ", message=" << status.message();
encoder_active_ = false;
cast_environment_->PostTask(
CastEnvironment::ThreadId::kMain, FROM_HERE,
base::BindOnce(status_change_cb_, status, STATUS_CODEC_RUNTIME_ERROR));
while (!in_progress_frame_encodes_.empty()) {
AbortLatestEncodeAttemptDueToErrors();
}
}
void AllocateInputBuffer(size_t size) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
auto mapped_region = base::ReadOnlySharedMemoryRegion::Create(size);
if (mapped_region.IsValid()) {
input_buffers_.push_back(std::move(mapped_region));
free_input_buffer_index_.push_back(input_buffers_.size() - 1);
}
allocate_input_buffer_in_progress_ = false;
}
void AllocateOutputBuffers(size_t size) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
for (size_t i = 0; i < kOutputBufferCount; ++i) {
auto memory = base::UnsafeSharedMemoryRegion::Create(size);
base::WritableSharedMemoryMapping mapping = memory.Map();
DCHECK(mapping.IsValid());
output_buffers_.push_back(
std::make_pair(std::move(memory), std::move(mapping)));
video_encode_accelerator_->UseOutputBitstreamBuffer(
media::BitstreamBuffer(static_cast<int32_t>(i),
output_buffers_[i].first.Duplicate(),
output_buffers_[i].first.GetSize()));
}
}
void RequireBitstreamBuffers(unsigned int input_count,
const gfx::Size& input_coded_size,
size_t output_buffer_size) final {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
frame_coded_size_ = input_coded_size;
max_allowed_input_buffers_ = input_count + kExtraInputBufferCount;
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VEAClientImpl::AllocateOutputBuffers, this,
output_buffer_size));
}
void BitstreamBufferReady(int32_t bitstream_buffer_id,
const BitstreamBufferMetadata& metadata) final {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (bitstream_buffer_id < 0 ||
bitstream_buffer_id >= static_cast<int32_t>(output_buffers_.size())) {
NotifyErrorStatus({media::EncoderStatus::Codes::kInvalidOutputBuffer,
"invalid bitstream_buffer_id=" +
base::NumberToString(bitstream_buffer_id)});
return;
}
if (metadata.dropped_frame()) {
CHECK(key_frame_encountered_);
InProgressExternalVideoFrameEncode& request =
in_progress_frame_encodes_.front();
cast_environment_->PostTask(
CastEnvironment::ThreadId::kMain, FROM_HERE,
base::BindOnce(std::move(request.frame_encoded_callback), nullptr));
in_progress_frame_encodes_.pop_front();
if (encoder_active_) {
video_encode_accelerator_->UseOutputBitstreamBuffer(
media::BitstreamBuffer(
bitstream_buffer_id,
output_buffers_[bitstream_buffer_id].first.Duplicate(),
output_buffers_[bitstream_buffer_id].first.GetSize()));
}
return;
}
CHECK_NE(metadata.payload_size_bytes, 0u);
const char* output_buffer_memory =
output_buffers_[bitstream_buffer_id]
.second.GetMemoryAsSpan<char>(metadata.payload_size_bytes)
.data();
if (metadata.payload_size_bytes >
output_buffers_[bitstream_buffer_id].second.size()) {
NotifyErrorStatus(
{media::EncoderStatus::Codes::kInvalidOutputBuffer,
"invalid payload_size=" +
base::NumberToString(metadata.payload_size_bytes)});
return;
}
if (metadata.key_frame) {
key_frame_encountered_ = true;
}
if (!key_frame_encountered_) {
stream_header_.write(output_buffer_memory, metadata.payload_size_bytes);
} else if (!in_progress_frame_encodes_.empty()) {
InProgressExternalVideoFrameEncode& request =
in_progress_frame_encodes_.front();
auto encoded_frame = std::make_unique<SenderEncodedFrame>();
encoded_frame->is_key_frame = metadata.key_frame;
encoded_frame->frame_id = next_frame_id_++;
if (metadata.key_frame) {
encoded_frame->referenced_frame_id = encoded_frame->frame_id;
} else {
encoded_frame->referenced_frame_id = encoded_frame->frame_id - 1;
}
encoded_frame->rtp_timestamp =
ToRtpTimeTicks(request.video_frame->timestamp(), kVideoFrequency);
encoded_frame->reference_time = request.reference_time;
encoded_frame->capture_begin_time =
request.video_frame->metadata().capture_begin_time;
encoded_frame->capture_end_time =
request.video_frame->metadata().capture_end_time;
std::string data = stream_header_.str();
std::ostringstream().swap(stream_header_);
data.append(output_buffer_memory, metadata.payload_size_bytes);
encoded_frame->data =
base::HeapArray<uint8_t>::CopiedFrom(UNSAFE_TODO(base::span(
reinterpret_cast<const uint8_t*>(data.c_str()), data.size())));
base::TimeDelta frame_duration =
request.video_frame->metadata().frame_duration.value_or(
base::TimeDelta());
if (frame_duration.is_positive()) {
encoded_frame->encoder_utilization =
static_cast<double>(in_progress_frame_encodes_.size()) /
kBacklogRedlineThreshold;
const double actual_bitrate =
encoded_frame->data.size() * 8.0 / frame_duration.InSecondsF();
DCHECK_GT(request.target_bit_rate, 0);
const double bitrate_utilization =
actual_bitrate / request.target_bit_rate;
std::optional<double> quantizer;
if (metadata.key_frame || key_frame_quantizer_parsable_) {
if (IsVpxProfile(codec_profile_)) {
quantizer = ParseVpxHeaderQuantizer(encoded_frame->data);
} else if (codec_profile_ == media::H264PROFILE_MAIN) {
quantizer = GetH264FrameQuantizer(encoded_frame->data);
} else {
NOTIMPLEMENTED();
}
if (!quantizer.has_value()) {
DVLOG(2) << "Unable to parse quantizer from encoded "
<< (metadata.key_frame ? "key" : "delta")
<< " frame, id=" << encoded_frame->frame_id;
if (metadata.key_frame) {
key_frame_quantizer_parsable_ = false;
quantizer = quantizer_estimator_.EstimateForKeyFrame(
*request.video_frame);
}
} else {
if (metadata.key_frame) {
key_frame_quantizer_parsable_ = true;
}
}
} else {
quantizer =
quantizer_estimator_.EstimateForDeltaFrame(*request.video_frame);
}
if (quantizer.has_value()) {
const double max_quantizer =
IsVpxProfile(codec_profile_)
? static_cast<int>(QuantizerEstimator::MAX_VPX_QUANTIZER)
: static_cast<int>(kMaxH264Quantizer);
encoded_frame->lossiness =
bitrate_utilization * (quantizer.value() / max_quantizer);
}
} else {
quantizer_estimator_.Reset();
}
encoded_frame->encode_completion_time = cast_environment_->NowTicks();
cast_environment_->PostTask(
CastEnvironment::ThreadId::kMain, FROM_HERE,
base::BindOnce(std::move(request.frame_encoded_callback),
std::move(encoded_frame)));
in_progress_frame_encodes_.pop_front();
} else {
VLOG(1) << "BitstreamBufferReady(): no encoded frame data available";
}
if (encoder_active_) {
video_encode_accelerator_->UseOutputBitstreamBuffer(
media::BitstreamBuffer(
bitstream_buffer_id,
output_buffers_[bitstream_buffer_id].first.Duplicate(),
output_buffers_[bitstream_buffer_id].first.GetSize()));
}
}
private:
friend class base::RefCountedThreadSafe<VEAClientImpl>;
~VEAClientImpl() final {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
while (!in_progress_frame_encodes_.empty()) {
AbortLatestEncodeAttemptDueToErrors();
}
if (video_encode_accelerator_) {
video_encode_accelerator_.release()->Destroy();
}
}
void AbortLatestEncodeAttemptDueToErrors() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
std::unique_ptr<SenderEncodedFrame> no_result(nullptr);
cast_environment_->PostTask(
CastEnvironment::ThreadId::kMain, FROM_HERE,
base::BindOnce(
std::move(in_progress_frame_encodes_.back().frame_encoded_callback),
std::move(no_result)));
in_progress_frame_encodes_.pop_back();
}
std::optional<double> GetH264FrameQuantizer(
base::span<uint8_t> encoded_data) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (encoded_data.empty()) {
return std::nullopt;
}
h264_parser_.SetStream(encoded_data);
double total_quantizer = 0;
int num_slices = 0;
while (true) {
H264NALU nalu;
H264Parser::Result res = h264_parser_.AdvanceToNextNALU(&nalu);
if (res == H264Parser::kEOStream) {
break;
}
if (res != H264Parser::kOk) {
return std::nullopt;
}
switch (nalu.nal_unit_type) {
case H264NALU::kIDRSlice:
case H264NALU::kNonIDRSlice: {
H264SliceHeader slice_header;
if (h264_parser_.ParseSliceHeader(nalu, &slice_header) !=
H264Parser::kOk) {
return std::nullopt;
}
const H264PPS* pps =
h264_parser_.GetPPS(slice_header.pic_parameter_set_id);
if (!pps) {
return std::nullopt;
}
++num_slices;
int slice_quantizer =
26 +
((slice_header.IsSPSlice() || slice_header.IsSISlice())
? pps->pic_init_qs_minus26 + slice_header.slice_qs_delta
: pps->pic_init_qp_minus26 + slice_header.slice_qp_delta);
DCHECK_GE(slice_quantizer, 0);
DCHECK_LE(slice_quantizer, kMaxH264Quantizer);
total_quantizer += slice_quantizer;
break;
}
case H264NALU::kSPS: {
int id;
if (h264_parser_.ParseSPS(&id) != H264Parser::kOk) {
return std::nullopt;
}
break;
}
case H264NALU::kPPS: {
int id;
if (h264_parser_.ParsePPS(&id) != H264Parser::kOk) {
return std::nullopt;
}
break;
}
default:
break;
}
}
if (num_slices == 0) {
return std::nullopt;
}
return total_quantizer / num_slices;
}
const scoped_refptr<CastEnvironment> cast_environment_;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
const double max_frame_rate_;
const EncoderStatusChangeCallback status_change_cb_;
std::unique_ptr<media::VideoEncodeAccelerator> video_encode_accelerator_;
bool encoder_active_;
FrameId next_frame_id_;
bool key_frame_encountered_;
std::ostringstream stream_header_;
VideoCodecProfile codec_profile_;
bool key_frame_quantizer_parsable_;
H264Parser h264_parser_;
std::vector<std::pair<base::UnsafeSharedMemoryRegion,
base::WritableSharedMemoryMapping>>
output_buffers_;
std::vector<base::MappedReadOnlyRegion> input_buffers_;
std::vector<int> free_input_buffer_index_;
std::list<InProgressExternalVideoFrameEncode> in_progress_frame_encodes_;
int requested_bit_rate_;
QuantizerEstimator quantizer_estimator_;
gfx::Size frame_coded_size_;
size_t max_allowed_input_buffers_;
bool allocate_input_buffer_in_progress_;
};
ExternalVideoEncoder::ExternalVideoEncoder(
const scoped_refptr<CastEnvironment>& cast_environment,
const FrameSenderConfig& video_config,
VideoEncoderMetricsProvider& metrics_provider,
const gfx::Size& frame_size,
FrameId first_frame_id,
StatusChangeCallback status_change_cb,
const CreateVideoEncodeAcceleratorCallback& create_vea_cb)
: cast_environment_(cast_environment),
metrics_provider_(metrics_provider),
frame_size_(frame_size),
bit_rate_(video_config.start_bitrate) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
DCHECK_GT(video_config.max_frame_rate, 0);
DCHECK(!frame_size_.IsEmpty());
DCHECK(status_change_cb);
DCHECK(create_vea_cb);
DCHECK_GT(bit_rate_, 0);
create_vea_cb.Run(
base::BindOnce(&ExternalVideoEncoder::OnCreateVideoEncodeAccelerator,
weak_factory_.GetWeakPtr(), video_config, first_frame_id,
std::move(status_change_cb)));
}
ExternalVideoEncoder::~ExternalVideoEncoder() {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
DestroyClientSoon();
}
void ExternalVideoEncoder::DestroyClientSoon() {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (client_) {
client_->task_runner()->PostTask(
FROM_HERE, base::DoNothingWithBoundArgs(std::move(client_)));
}
}
void ExternalVideoEncoder::SetErrorToMetricsProvider(
const media::EncoderStatus& encoder_status) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
metrics_provider_->SetError(encoder_status);
}
bool ExternalVideoEncoder::EncodeVideoFrame(
scoped_refptr<media::VideoFrame> video_frame,
base::TimeTicks reference_time,
FrameEncodedCallback frame_encoded_callback) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
CHECK(!frame_encoded_callback.is_null());
if (!client_ || video_frame->visible_rect().size() != frame_size_) {
return false;
}
client_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VEAClientImpl::EncodeVideoFrame, client_,
std::move(video_frame), reference_time,
key_frame_requested_, std::move(frame_encoded_callback)));
key_frame_requested_ = false;
return true;
}
void ExternalVideoEncoder::SetBitRate(int new_bit_rate) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
DCHECK_GT(new_bit_rate, 0);
bit_rate_ = new_bit_rate;
if (!client_) {
return;
}
client_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VEAClientImpl::SetBitRate, client_, bit_rate_));
}
void ExternalVideoEncoder::GenerateKeyFrame() {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
key_frame_requested_ = true;
}
void ExternalVideoEncoder::OnCreateVideoEncodeAccelerator(
const FrameSenderConfig& video_config,
FrameId first_frame_id,
const StatusChangeCallback& status_change_cb,
scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner,
std::unique_ptr<media::VideoEncodeAccelerator> vea) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::ThreadId::kMain));
if (!encoder_task_runner || !vea) {
cast_environment_->PostTask(
CastEnvironment::ThreadId::kMain, FROM_HERE,
base::BindOnce(status_change_cb, STATUS_CODEC_INIT_FAILED));
return;
}
VideoCodecProfile codec_profile;
switch (video_config.video_codec()) {
case VideoCodec::kVP8:
codec_profile = media::VP8PROFILE_ANY;
break;
case VideoCodec::kVP9:
codec_profile = media::VP9PROFILE_PROFILE0;
break;
case VideoCodec::kH264:
codec_profile = media::H264PROFILE_MAIN;
break;
case VideoCodec::kUnknown:
NOTREACHED() << "Fake software video encoder cannot be external";
default:
cast_environment_->PostTask(
CastEnvironment::ThreadId::kMain, FROM_HERE,
base::BindOnce(status_change_cb, STATUS_UNSUPPORTED_CODEC));
return;
}
VEAClientImpl::EncoderStatusChangeCallback wrapped_status_change_cb =
base::BindRepeating(
[](base::WeakPtr<ExternalVideoEncoder> self,
const StatusChangeCallback& status_change_cb,
media::EncoderStatus encoder_status, OperationalStatus status) {
if (self.get()) {
if (!encoder_status.is_ok()) {
self->SetErrorToMetricsProvider(encoder_status);
}
switch (status) {
case STATUS_UNINITIALIZED:
case STATUS_INITIALIZED:
case STATUS_CODEC_REINIT_PENDING:
break;
case STATUS_INVALID_CONFIGURATION:
case STATUS_UNSUPPORTED_CODEC:
case STATUS_CODEC_INIT_FAILED:
case STATUS_CODEC_RUNTIME_ERROR:
self->DestroyClientSoon();
break;
}
}
status_change_cb.Run(status);
},
weak_factory_.GetWeakPtr(), status_change_cb);
DCHECK(!client_);
client_ = base::MakeRefCounted<VEAClientImpl>(
cast_environment_, encoder_task_runner, std::move(vea),
video_config.max_frame_rate, std::move(wrapped_status_change_cb));
metrics_provider_->Initialize(codec_profile, frame_size_,
true);
client_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VEAClientImpl::Initialize, client_, frame_size_,
codec_profile, bit_rate_, first_frame_id));
}
SizeAdaptableExternalVideoEncoder::SizeAdaptableExternalVideoEncoder(
const scoped_refptr<CastEnvironment>& cast_environment,
const FrameSenderConfig& video_config,
std::unique_ptr<VideoEncoderMetricsProvider> metrics_provider,
StatusChangeCallback status_change_cb,
const CreateVideoEncodeAcceleratorCallback& create_vea_cb)
: SizeAdaptableVideoEncoderBase(cast_environment,
video_config,
std::move(metrics_provider),
std::move(status_change_cb)),
create_vea_cb_(create_vea_cb) {}
SizeAdaptableExternalVideoEncoder::~SizeAdaptableExternalVideoEncoder() =
default;
std::unique_ptr<VideoEncoder>
SizeAdaptableExternalVideoEncoder::CreateEncoder() {
return std::make_unique<ExternalVideoEncoder>(
cast_environment(), video_config(), metrics_provider(), frame_size(),
next_frame_id(), CreateEncoderStatusChangeCallback(), create_vea_cb_);
}
QuantizerEstimator::QuantizerEstimator() = default;
QuantizerEstimator::~QuantizerEstimator() = default;
void QuantizerEstimator::Reset() {
last_frame_pixel_buffer_.reset();
}
std::optional<double> QuantizerEstimator::EstimateForKeyFrame(
const VideoFrame& frame) {
if (!CanExamineFrame(frame)) {
return std::nullopt;
}
const gfx::Size size = frame.visible_rect().size();
const int rows_in_subset =
std::max(1, size.height() * kFrameSamplingPercentage / 100);
if (last_frame_size_ != size || !last_frame_pixel_buffer_) {
last_frame_pixel_buffer_ =
std::make_unique<uint8_t[]>(size.width() * rows_in_subset);
last_frame_size_ = size;
}
std::array<int, kQuantizationHistogramSize> histogram{};
const int row_skip = size.height() / rows_in_subset;
int y = 0;
for (int i = 0; i < rows_in_subset; ++i, y += row_skip) {
const uint8_t* const row_begin =
UNSAFE_TODO(frame.visible_data(VideoFrame::Plane::kY) +
y * frame.stride(VideoFrame::Plane::kY));
const uint8_t* const row_end = UNSAFE_TODO(row_begin + size.width());
int left_hand_pixel_value = static_cast<int>(*row_begin);
for (const uint8_t* p = UNSAFE_TODO(row_begin + 1); p < row_end;
UNSAFE_TODO(++p)) {
const int right_hand_pixel_value = static_cast<int>(*p);
const int difference = right_hand_pixel_value - left_hand_pixel_value;
const int histogram_index = difference + 255;
++histogram[histogram_index];
left_hand_pixel_value = right_hand_pixel_value;
}
UNSAFE_TODO(memcpy(last_frame_pixel_buffer_.get() + i * size.width(),
row_begin, size.width()));
}
const int num_samples = (size.width() - 1) * rows_in_subset;
return ToQuantizerEstimate(
ComputeEntropyFromHistogram(histogram, histogram.size(), num_samples));
}
std::optional<double> QuantizerEstimator::EstimateForDeltaFrame(
const VideoFrame& frame) {
if (!CanExamineFrame(frame)) {
return std::nullopt;
}
const gfx::Size& size = frame.visible_rect().size();
if (last_frame_size_ != size || !last_frame_pixel_buffer_) {
return EstimateForKeyFrame(frame);
}
const int rows_in_subset =
std::max(1, size.height() * (kFrameSamplingPercentage / 100));
std::array<int, kQuantizationHistogramSize> histogram{};
const int row_skip = size.height() / rows_in_subset;
int y = 0;
for (int i = 0; i < rows_in_subset; ++i, y += row_skip) {
const uint8_t* const row_begin =
UNSAFE_TODO(frame.visible_data(VideoFrame::Plane::kY) +
y * frame.stride(VideoFrame::Plane::kY));
const uint8_t* const row_end = UNSAFE_TODO(row_begin + size.width());
uint8_t* const last_frame_row_begin =
UNSAFE_TODO(last_frame_pixel_buffer_.get() + i * size.width());
for (const uint8_t *p = row_begin, *q = last_frame_row_begin; p < row_end;
UNSAFE_TODO(++p), UNSAFE_TODO(++q)) {
const int difference = static_cast<int>(*p) - static_cast<int>(*q);
const int histogram_index = difference + 255;
++histogram[histogram_index];
}
UNSAFE_TODO(memcpy(last_frame_row_begin, row_begin, size.width()));
}
const int num_samples = size.width() * rows_in_subset;
return ToQuantizerEstimate(
ComputeEntropyFromHistogram(histogram, histogram.size(), num_samples));
}
bool QuantizerEstimator::CanExamineFrame(const VideoFrame& frame) {
DCHECK_EQ(8, VideoFrame::PlaneHorizontalBitsPerPixel(frame.format(),
VideoFrame::Plane::kY));
return media::IsYuvPlanar(frame.format()) && !frame.visible_rect().IsEmpty();
}
double QuantizerEstimator::ComputeEntropyFromHistogram(
base::span<const int> histogram,
size_t spanification_suspected_redundant_histogram_size,
int num_samples) {
CHECK(spanification_suspected_redundant_histogram_size == histogram.size(),
base::NotFatalUntil::M143);
DCHECK_LT(0, num_samples);
double entropy = 0.0;
for (size_t i = 0; i < spanification_suspected_redundant_histogram_size;
++i) {
const double probability = static_cast<double>(histogram[i]) / num_samples;
if (probability > 0.0) {
entropy = entropy - probability * std::log2(probability);
}
}
return entropy;
}
double QuantizerEstimator::ToQuantizerEstimate(double shannon_entropy) {
DCHECK_GE(shannon_entropy, 0.0);
constexpr double kEntropyAtMaxQuantizer = 7.5;
constexpr double kSlope =
(MAX_VPX_QUANTIZER - MIN_VPX_QUANTIZER) / kEntropyAtMaxQuantizer;
const double quantizer = std::min<double>(
MAX_VPX_QUANTIZER, MIN_VPX_QUANTIZER + kSlope * shannon_entropy);
return quantizer;
}
}