#include "remoting/codec/webrtc_video_encoder_gpu.h"
#include <algorithm>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/numerics/checked_math.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "gpu/config/gpu_driver_bug_workarounds.h"
#include "gpu/config/gpu_preferences.h"
#include "media/base/bitstream_buffer.h"
#include "media/base/media_log.h"
#include "media/base/video_frame.h"
#include "media/gpu/gpu_video_encode_accelerator_factory.h"
#include "media/video/video_encode_accelerator.h"
#include "remoting/base/constants.h"
#include "remoting/codec/encoder_bitrate_filter.h"
#include "third_party/libyuv/include/libyuv/convert_from_argb.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_com_initializer.h"
#endif
namespace {
using media::VideoCodecProfile;
using media::VideoFrame;
using media::VideoPixelFormat;
const int kWebrtcVideoEncoderGpuOutputBufferCount = 1;
constexpr VideoCodecProfile kH264Profile = VideoCodecProfile::H264PROFILE_MAIN;
constexpr int kH264MinimumTargetBitrateKbpsPerMegapixel = 1800;
gpu::GpuDriverBugWorkarounds CreateGpuWorkarounds() {
gpu::GpuDriverBugWorkarounds gpu_workarounds;
return gpu_workarounds;
}
gpu::GPUInfo::GPUDevice CreateGpuDevice() {
gpu::GPUInfo::GPUDevice device;
return device;
}
struct OutputBuffer {
base::UnsafeSharedMemoryRegion region;
base::WritableSharedMemoryMapping mapping;
bool IsValid();
};
bool OutputBuffer::IsValid() {
return region.IsValid() && mapping.IsValid();
}
}
namespace remoting {
class WebrtcVideoEncoderGpu::Core
: public WebrtcVideoEncoder,
public media::VideoEncodeAccelerator::Client {
public:
explicit Core(media::VideoCodecProfile codec_profile);
Core(const Core&) = delete;
Core& operator=(const Core&) = delete;
~Core() override;
void Encode(std::unique_ptr<webrtc::DesktopFrame> frame,
const FrameParams& params,
WebrtcVideoEncoder::EncodeCallback done) override;
void RequireBitstreamBuffers(unsigned int input_count,
const gfx::Size& input_coded_size,
size_t output_buffer_size) override;
void BitstreamBufferReady(
int32_t bitstream_buffer_id,
const media::BitstreamBufferMetadata& metadata) override;
void NotifyErrorStatus(const media::EncoderStatus& status) override;
private:
enum State { UNINITIALIZED, INITIALIZING, INITIALIZED, INITIALIZATION_ERROR };
void BeginInitialization();
void UseOutputBitstreamBufferId(int32_t bitstream_buffer_id);
void RunAnyPendingEncode();
#if BUILDFLAG(IS_WIN)
std::unique_ptr<base::win::ScopedCOMInitializer> scoped_com_initializer_;
#endif
State state_ = UNINITIALIZED;
base::OnceClosure pending_encode_;
std::unique_ptr<media::VideoEncodeAccelerator> video_encode_accelerator_;
base::TimeDelta previous_timestamp_;
media::VideoCodecProfile codec_profile_;
std::vector<std::unique_ptr<OutputBuffer>> output_buffers_;
gfx::Size input_coded_size_;
gfx::Size input_visible_size_;
size_t output_buffer_size_;
base::flat_map<base::TimeDelta, WebrtcVideoEncoder::EncodeCallback>
callbacks_;
EncoderBitrateFilter bitrate_filter_{
kH264MinimumTargetBitrateKbpsPerMegapixel};
THREAD_CHECKER(thread_checker_);
};
WebrtcVideoEncoderGpu::WebrtcVideoEncoderGpu(VideoCodecProfile codec_profile)
: core_(std::make_unique<WebrtcVideoEncoderGpu::Core>(codec_profile)),
hw_encode_task_runner_(base::ThreadPool::CreateSingleThreadTaskRunner(
{base::MayBlock(), base::WithBaseSyncPrimitives(),
base::TaskPriority::HIGHEST},
base::SingleThreadTaskRunnerThreadMode::DEDICATED)) {}
WebrtcVideoEncoderGpu::~WebrtcVideoEncoderGpu() {
hw_encode_task_runner_->DeleteSoon(FROM_HERE, core_.release());
}
void WebrtcVideoEncoderGpu::Encode(std::unique_ptr<webrtc::DesktopFrame> frame,
const FrameParams& params,
WebrtcVideoEncoder::EncodeCallback done) {
DCHECK(core_);
DCHECK(frame);
DCHECK(done);
DCHECK_GT(params.duration, base::Milliseconds(0));
hw_encode_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WebrtcVideoEncoderGpu::Core::Encode,
base::Unretained(core_.get()), std::move(frame), params,
base::BindPostTaskToCurrentDefault(std::move(done))));
}
WebrtcVideoEncoderGpu::Core::Core(media::VideoCodecProfile codec_profile)
: codec_profile_(codec_profile) {
DETACH_FROM_THREAD(thread_checker_);
}
WebrtcVideoEncoderGpu::Core::~Core() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}
void WebrtcVideoEncoderGpu::Core::Encode(
std::unique_ptr<webrtc::DesktopFrame> frame,
const FrameParams& params,
WebrtcVideoEncoder::EncodeCallback done) {
TRACE_EVENT0("media", "WebrtcVideoEncoderGpu::Core::Encode");
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
bitrate_filter_.SetFrameSize(frame->size().width(), frame->size().height());
if (state_ == INITIALIZATION_ERROR) {
DLOG(ERROR) << "Encoder failed to initialize; dropping encode request";
std::move(done).Run(EncodeResult::FRAME_SIZE_EXCEEDS_CAPABILITY, nullptr);
return;
}
if (state_ == UNINITIALIZED ||
input_visible_size_.width() != frame->size().width() ||
input_visible_size_.height() != frame->size().height()) {
input_visible_size_ =
gfx::Size(frame->size().width(), frame->size().height());
pending_encode_ = base::BindOnce(&WebrtcVideoEncoderGpu::Core::Encode,
base::Unretained(this), std::move(frame),
params, std::move(done));
BeginInitialization();
return;
}
DCHECK_EQ(state_, INITIALIZED);
scoped_refptr<VideoFrame> video_frame = VideoFrame::CreateFrame(
VideoPixelFormat::PIXEL_FORMAT_NV12, input_coded_size_,
gfx::Rect(input_visible_size_), input_visible_size_, base::TimeDelta());
base::TimeDelta new_timestamp = previous_timestamp_ + params.duration;
video_frame->set_timestamp(new_timestamp);
previous_timestamp_ = new_timestamp;
libyuv::ARGBToNV12(frame->data(), frame->stride(),
video_frame->writable_data(VideoFrame::Plane::kY),
video_frame->stride(VideoFrame::Plane::kY),
video_frame->writable_data(VideoFrame::Plane::kUV),
video_frame->stride(VideoFrame::Plane::kUV),
video_frame->visible_rect().width(),
video_frame->visible_rect().height());
callbacks_[video_frame->timestamp()] = std::move(done);
if (params.bitrate_kbps > 0 && params.fps > 0) {
bitrate_filter_.SetBandwidthEstimateKbps(params.bitrate_kbps);
base::CheckedNumeric<uint32_t> checked_bitrate = base::CheckMul<uint32_t>(
std::max(bitrate_filter_.GetTargetBitrateKbps(), 0), 1000);
uint32_t bitrate_bps =
checked_bitrate.ValueOrDefault(std::numeric_limits<uint32_t>::max());
video_encode_accelerator_->RequestEncodingParametersChange(
media::Bitrate::ConstantBitrate(bitrate_bps), params.fps, std::nullopt);
}
video_encode_accelerator_->Encode(video_frame, params.key_frame);
}
void WebrtcVideoEncoderGpu::Core::RequireBitstreamBuffers(
unsigned int input_count,
const gfx::Size& input_coded_size,
size_t output_buffer_size) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(state_ == INITIALIZING);
input_coded_size_ = input_coded_size;
output_buffer_size_ = output_buffer_size;
output_buffers_.clear();
for (unsigned int i = 0; i < kWebrtcVideoEncoderGpuOutputBufferCount; i++) {
auto output_buffer = std::make_unique<OutputBuffer>();
output_buffer->region =
base::UnsafeSharedMemoryRegion::Create(output_buffer_size_);
output_buffer->mapping = output_buffer->region.Map();
CHECK(output_buffer->IsValid());
output_buffers_.push_back(std::move(output_buffer));
}
for (size_t i = 0; i < output_buffers_.size(); i++) {
UseOutputBitstreamBufferId(i);
}
state_ = INITIALIZED;
RunAnyPendingEncode();
}
void WebrtcVideoEncoderGpu::Core::BitstreamBufferReady(
int32_t bitstream_buffer_id,
const media::BitstreamBufferMetadata& metadata) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto encoded_frame = std::make_unique<EncodedFrame>();
OutputBuffer* output_buffer = output_buffers_[bitstream_buffer_id].get();
CHECK(output_buffer->IsValid());
base::span<uint8_t> data_span =
output_buffer->mapping.GetMemoryAsSpan<uint8_t>(
metadata.payload_size_bytes);
encoded_frame->data =
webrtc::EncodedImageBuffer::Create(data_span.data(), data_span.size());
encoded_frame->key_frame = metadata.key_frame;
encoded_frame->dimensions = {input_coded_size_.width(),
input_coded_size_.height()};
encoded_frame->quantizer = 0;
encoded_frame->codec = webrtc::kVideoCodecH264;
encoded_frame->profile = static_cast<int>(codec_profile_);
UseOutputBitstreamBufferId(bitstream_buffer_id);
auto callback_it = callbacks_.find(metadata.timestamp);
CHECK(callback_it != callbacks_.end())
<< "Callback not found for timestamp " << metadata.timestamp;
std::move(std::get<1>(*callback_it))
.Run(EncodeResult::SUCCEEDED, std::move(encoded_frame));
callbacks_.erase(metadata.timestamp);
}
void WebrtcVideoEncoderGpu::Core::NotifyErrorStatus(
const media::EncoderStatus& status) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
LOG(ERROR) << "NotifyErrorStatus() is called, code="
<< static_cast<int32_t>(status.code())
<< ", message=" << status.message();
}
void WebrtcVideoEncoderGpu::Core::BeginInitialization() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#if BUILDFLAG(IS_WIN)
if (!scoped_com_initializer_) {
scoped_com_initializer_ =
std::make_unique<base::win::ScopedCOMInitializer>();
}
#endif
VideoPixelFormat input_format = VideoPixelFormat::PIXEL_FORMAT_NV12;
media::Bitrate initial_bitrate = media::Bitrate::ConstantBitrate(
static_cast<uint32_t>(kTargetFrameRate * 1024 * 1024 * 8));
const media::VideoEncodeAccelerator::Config config(
input_format, input_visible_size_, codec_profile_, initial_bitrate,
kTargetFrameRate,
media::VideoEncodeAccelerator::Config::StorageType::kShmem,
media::VideoEncodeAccelerator::Config::ContentType::kDisplay);
auto accelerator_or_error =
media::GpuVideoEncodeAcceleratorFactory::CreateVEA(
config, this, gpu::GpuPreferences(), CreateGpuWorkarounds(),
CreateGpuDevice());
video_encode_accelerator_ = accelerator_or_error.has_value()
? std::move(accelerator_or_error).value()
: nullptr;
if (!video_encode_accelerator_) {
LOG(ERROR) << "Could not create VideoEncodeAccelerator";
state_ = INITIALIZATION_ERROR;
RunAnyPendingEncode();
return;
}
state_ = INITIALIZING;
}
void WebrtcVideoEncoderGpu::Core::UseOutputBitstreamBufferId(
int32_t bitstream_buffer_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
video_encode_accelerator_->UseOutputBitstreamBuffer(media::BitstreamBuffer(
bitstream_buffer_id,
output_buffers_[bitstream_buffer_id]->region.Duplicate(),
output_buffers_[bitstream_buffer_id]->region.GetSize()));
}
void WebrtcVideoEncoderGpu::Core::RunAnyPendingEncode() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (pending_encode_) {
std::move(pending_encode_).Run();
}
}
std::unique_ptr<WebrtcVideoEncoder> WebrtcVideoEncoderGpu::CreateForH264() {
LOG(WARNING) << "H264 video encoder is created.";
return base::WrapUnique(new WebrtcVideoEncoderGpu(kH264Profile));
}
bool WebrtcVideoEncoderGpu::IsSupportedByH264(const Profile& profile) {
#if BUILDFLAG(IS_WIN)
base::win::ScopedCOMInitializer scoped_com_initializer;
#endif
media::VideoEncodeAccelerator::SupportedProfiles profiles =
media::GpuVideoEncodeAcceleratorFactory::GetSupportedProfiles(
gpu::GpuPreferences(), CreateGpuWorkarounds(), CreateGpuDevice());
for (const auto& supported_profile : profiles) {
if (supported_profile.profile != kH264Profile) {
continue;
}
double supported_framerate = supported_profile.max_framerate_numerator;
supported_framerate /= supported_profile.max_framerate_denominator;
if (profile.frame_rate > supported_framerate) {
continue;
}
if (profile.resolution.GetArea() >
supported_profile.max_resolution.GetArea()) {
continue;
}
return true;
}
return false;
}
}