blob: ef927ce434d8135feb3e2668f0f506dca125ff0d [file]
// Copyright 2014 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/gpu/vaapi/vaapi_video_encode_accelerator.h"
#include <string.h>
#include <va/va.h>
#include <algorithm>
#include <memory>
#include <type_traits>
#include <utility>
#include <variant>
#include "base/bits.h"
#include "base/containers/contains.h"
#include "base/cxx17_backports.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.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/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "media/base/format_utils.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/video_bitrate_allocation.h"
#include "media/gpu/chromeos/platform_video_frame_utils.h"
#include "media/gpu/gpu_video_encode_accelerator_helpers.h"
#include "media/gpu/h264_dpb.h"
#include "media/gpu/macros.h"
#include "media/gpu/vaapi/av1_vaapi_video_encoder_delegate.h"
#include "media/gpu/vaapi/h264_vaapi_video_encoder_delegate.h"
#include "media/gpu/vaapi/va_surface.h"
#include "media/gpu/vaapi/vaapi_common.h"
#include "media/gpu/vaapi/vaapi_utils.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#include "media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.h"
#include "media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.h"
#include "media/gpu/vp8_reference_frame_vector.h"
#include "media/gpu/vp9_reference_frame_vector.h"
#include "media/gpu/vp9_svc_layers.h"
namespace media {
namespace {
// Minimum number of frames in flight for pipeline depth, adjust to this number
// if encoder requests less.
constexpr size_t kMinNumFramesInFlight = 4;
// VASurfaceIDs internal format.
constexpr unsigned int kVaSurfaceFormat = VA_RT_FORMAT_YUV420;
// Creates one |encode_size| ScopedVASurface using |vaapi_wrapper|.
std::unique_ptr<ScopedVASurface> CreateScopedSurface(
VaapiWrapper& vaapi_wrapper,
const gfx::Size& encode_size,
const std::vector<VaapiWrapper::SurfaceUsageHint>& surface_usage_hints) {
auto surfaces = vaapi_wrapper.CreateScopedVASurfaces(
kVaSurfaceFormat, encode_size, surface_usage_hints, 1u,
/*visible_size=*/absl::nullopt,
/*va_fourcc=*/absl::nullopt);
return surfaces.empty() ? nullptr : std::move(surfaces.front());
}
} // namespace
struct VaapiVideoEncodeAccelerator::InputFrameRef {
InputFrameRef(scoped_refptr<VideoFrame> frame, bool force_keyframe)
: frame(frame), force_keyframe(force_keyframe) {}
// If |frame| is nullptr, the InputFrameRef indicates the Flush request.
const scoped_refptr<VideoFrame> frame;
const bool force_keyframe;
};
// static
base::AtomicRefCount VaapiVideoEncodeAccelerator::num_instances_(0);
VideoEncodeAccelerator::SupportedProfiles
VaapiVideoEncodeAccelerator::GetSupportedProfiles() {
if (IsConfiguredForTesting())
return supported_profiles_for_testing_;
return VaapiWrapper::GetSupportedEncodeProfiles();
}
VaapiVideoEncodeAccelerator::VaapiVideoEncodeAccelerator()
: can_use_encoder_(num_instances_.Increment() < kMaxNumOfInstances),
child_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
// TODO(akahuang): Change to use SequencedTaskRunner to see if the
// performance is affected.
encoder_task_runner_(base::ThreadPool::CreateSingleThreadTaskRunner(
{base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()},
base::SingleThreadTaskRunnerThreadMode::DEDICATED)) {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(child_sequence_checker_);
DETACH_FROM_SEQUENCE(encoder_sequence_checker_);
child_weak_this_ = child_weak_this_factory_.GetWeakPtr();
encoder_weak_this_ = encoder_weak_this_factory_.GetWeakPtr();
// The default value of VideoEncoderInfo of VaapiVideoEncodeAccelerator.
encoder_info_.implementation_name = "VaapiVideoEncodeAccelerator";
DCHECK(!encoder_info_.has_trusted_rate_controller);
DCHECK(encoder_info_.is_hardware_accelerated);
DCHECK(encoder_info_.supports_native_handle);
DCHECK(!encoder_info_.supports_simulcast);
}
VaapiVideoEncodeAccelerator::~VaapiVideoEncodeAccelerator() {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
this);
num_instances_.Decrement();
}
bool VaapiVideoEncodeAccelerator::Initialize(
const Config& config,
Client* client,
std::unique_ptr<MediaLog> media_log) {
DCHECK_CALLED_ON_VALID_SEQUENCE(child_sequence_checker_);
DCHECK_EQ(state_, kUninitialized);
VLOGF(2) << "Initializing VAVEA, " << config.AsHumanReadableString();
if (!can_use_encoder_) {
MEDIA_LOG(ERROR, media_log.get()) << "Too many encoders are allocated";
return false;
}
if (AttemptedInitialization()) {
MEDIA_LOG(ERROR, media_log.get())
<< "Initialize() cannot be called more than once.";
return false;
}
client_ptr_factory_.reset(new base::WeakPtrFactory<Client>(client));
client_ = client_ptr_factory_->GetWeakPtr();
if (config.HasSpatialLayer()) {
#if BUILDFLAG(IS_CHROMEOS)
if (!base::FeatureList::IsEnabled(kVaapiVp9kSVCHWEncoding) &&
!IsConfiguredForTesting()) {
MEDIA_LOG(ERROR, media_log.get())
<< "Spatial layer encoding is not yet enabled by default";
return false;
}
#endif // BUILDFLAG(IS_CHROMEOS)
if (config.inter_layer_pred != Config::InterLayerPredMode::kOnKeyPic) {
MEDIA_LOG(ERROR, media_log.get()) << "Only K-SVC encoding is supported.";
return false;
}
if (config.output_profile != VideoCodecProfile::VP9PROFILE_PROFILE0) {
MEDIA_LOG(ERROR, media_log.get())
<< "Spatial layers are only supported for VP9 encoding";
return false;
}
// TODO(crbug.com/1186051): Remove this restriction.
for (size_t i = 0; i < config.spatial_layers.size(); ++i) {
for (size_t j = i + 1; j < config.spatial_layers.size(); ++j) {
if (config.spatial_layers[i].width == config.spatial_layers[j].width &&
config.spatial_layers[i].height ==
config.spatial_layers[j].height) {
MEDIA_LOG(ERROR, media_log.get())
<< "Doesn't support k-SVC encoding where spatial layers "
"have the same resolution";
return false;
}
}
}
if (!IsConfiguredForTesting()) {
VAProfile va_profile = VAProfileVP9Profile0;
if (VaapiWrapper::GetDefaultVaEntryPoint(
VaapiWrapper::kEncodeConstantQuantizationParameter, va_profile) !=
VAEntrypointEncSliceLP) {
MEDIA_LOG(ERROR, media_log.get())
<< "Currently spatial layer encoding is only supported by "
"VAEntrypointEncSliceLP";
return false;
}
}
}
const VideoCodec codec = VideoCodecProfileToVideoCodec(config.output_profile);
if (codec != VideoCodec::kH264 && codec != VideoCodec::kVP8 &&
codec != VideoCodec::kVP9 && codec != VideoCodec::kAV1) {
MEDIA_LOG(ERROR, media_log.get())
<< "Unsupported profile: " << GetProfileName(config.output_profile);
return false;
}
if (config.bitrate.mode() == Bitrate::Mode::kVariable) {
if (!base::FeatureList::IsEnabled(kChromeOSHWVBREncoding)) {
MEDIA_LOG(ERROR, media_log.get()) << "Variable bitrate is disabled.";
return false;
}
if (codec != VideoCodec::kH264) {
MEDIA_LOG(ERROR, media_log.get())
<< "Variable bitrate is only supported with H264 encoding.";
return false;
}
}
if (config.input_format != PIXEL_FORMAT_I420 &&
config.input_format != PIXEL_FORMAT_NV12) {
MEDIA_LOG(ERROR, media_log.get())
<< "Unsupported input format: " << config.input_format;
return false;
}
if (config.storage_type.value_or(Config::StorageType::kShmem) ==
Config::StorageType::kGpuMemoryBuffer) {
#if !BUILDFLAG(IS_OZONE)
MEDIA_LOG(ERROR, media_log.get())
<< "Native mode is only available on OZONE platform.";
return false;
#else
if (config.input_format != PIXEL_FORMAT_NV12) {
// TODO(crbug.com/894381): Support other formats.
MEDIA_LOG(ERROR, media_log.get())
<< "Unsupported format for native input mode: "
<< VideoPixelFormatToString(config.input_format);
return false;
}
native_input_mode_ = true;
#endif // BUILDFLAG(IS_OZONE)
}
if (config.HasSpatialLayer() && !native_input_mode_) {
MEDIA_LOG(ERROR, media_log.get())
<< "Spatial scalability is only supported for native input now";
return false;
}
const SupportedProfiles& profiles = GetSupportedProfiles();
const auto profile = find_if(profiles.begin(), profiles.end(),
[output_profile = config.output_profile](
const SupportedProfile& profile) {
return profile.profile == output_profile;
});
if (profile == profiles.end()) {
MEDIA_LOG(ERROR, media_log.get()) << "Unsupported output profile "
<< GetProfileName(config.output_profile);
return false;
}
if (config.input_visible_size.width() > profile->max_resolution.width() ||
config.input_visible_size.height() > profile->max_resolution.height()) {
MEDIA_LOG(ERROR, media_log.get())
<< "Input size too big: " << config.input_visible_size.ToString()
<< ", max supported size: " << profile->max_resolution.ToString();
return false;
}
// Finish remaining initialization on the encoder thread.
encoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VaapiVideoEncodeAccelerator::InitializeTask,
encoder_weak_this_, config));
return true;
}
void VaapiVideoEncodeAccelerator::InitializeTask(const Config& config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DCHECK_EQ(state_, kUninitialized);
VLOGF(2);
output_codec_ = VideoCodecProfileToVideoCodec(config.output_profile);
DCHECK_EQ(IsConfiguredForTesting(), !!vaapi_wrapper_);
if (!IsConfiguredForTesting()) {
VaapiWrapper::CodecMode mode;
switch (output_codec_) {
case VideoCodec::kH264:
mode = config.bitrate.mode() == Bitrate::Mode::kConstant
? VaapiWrapper::kEncodeConstantBitrate
: VaapiWrapper::kEncodeVariableBitrate;
break;
case VideoCodec::kVP8:
case VideoCodec::kVP9:
case VideoCodec::kAV1:
mode = VaapiWrapper::kEncodeConstantQuantizationParameter;
break;
default:
NotifyError({EncoderStatus::Codes::kEncoderUnsupportedCodec,
"Unsupported codec: " + GetCodecName(output_codec_)});
return;
}
vaapi_wrapper_ = VaapiWrapper::CreateForVideoCodec(
mode, config.output_profile, EncryptionScheme::kUnencrypted,
base::BindRepeating(&ReportVaapiErrorToUMA,
"Media.VaapiVideoEncodeAccelerator.VAAPIError"));
if (!vaapi_wrapper_) {
NotifyError({EncoderStatus::Codes::kEncoderInitializationError,
"Failed initializing VAAPI for profile " +
GetProfileName(config.output_profile)});
return;
}
}
DCHECK_EQ(IsConfiguredForTesting(), !!encoder_);
// Base::Unretained(this) is safe because |error_cb| is called by
// |encoder_| and |this| outlives |encoder_|.
auto error_cb = base::BindRepeating(
[](VaapiVideoEncodeAccelerator* const vea) {
// TODO(b/276005687): Report encoder status from
// VaapiVIdeoEncoderDelegate.
vea->NotifyError({EncoderStatus::Codes::kEncoderFailedEncode,
"VaapiVideoEncodeAcceleratorDelegate error"});
},
base::Unretained(this));
VaapiVideoEncoderDelegate::Config ave_config{};
switch (output_codec_) {
case VideoCodec::kH264:
if (!IsConfiguredForTesting()) {
encoder_ = std::make_unique<H264VaapiVideoEncoderDelegate>(
vaapi_wrapper_, error_cb);
// HW encoders on Intel GPUs will not put average QP in slice/tile
// header when it is not working at CQP mode. Currently only H264 is
// working at non CQP mode.
if (VaapiWrapper::GetImplementationType() ==
VAImplementation::kIntelI965 ||
VaapiWrapper::GetImplementationType() ==
VAImplementation::kIntelIHD) {
encoder_info_.reports_average_qp = false;
}
}
break;
case VideoCodec::kVP8:
if (!IsConfiguredForTesting()) {
encoder_ = std::make_unique<VP8VaapiVideoEncoderDelegate>(
vaapi_wrapper_, error_cb);
}
break;
case VideoCodec::kVP9:
if (!IsConfiguredForTesting()) {
encoder_ = std::make_unique<VP9VaapiVideoEncoderDelegate>(
vaapi_wrapper_, error_cb);
}
break;
case VideoCodec::kAV1:
if (!IsConfiguredForTesting()) {
encoder_ = std::make_unique<AV1VaapiVideoEncoderDelegate>(
vaapi_wrapper_, error_cb);
}
break;
default:
NOTREACHED() << "Unsupported codec type " << GetCodecName(output_codec_);
return;
}
if (!vaapi_wrapper_->GetVAEncMaxNumOfRefFrames(
config.output_profile, &ave_config.max_num_ref_frames)) {
NotifyError({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Failed getting max number of reference frames supported by "
"the driver"});
return;
}
DCHECK_GT(ave_config.max_num_ref_frames, 0u);
if (!encoder_->Initialize(config, ave_config)) {
NotifyError({EncoderStatus::Codes::kEncoderInitializationError,
"Failed initializing encoder"});
return;
}
output_buffer_byte_size_ = encoder_->GetBitstreamBufferSize();
visible_rect_ = gfx::Rect(config.input_visible_size);
expected_input_coded_size_ = VideoFrame::DetermineAlignedSize(
config.input_format, config.input_visible_size);
DCHECK(
expected_input_coded_size_.width() <= encoder_->GetCodedSize().width() &&
expected_input_coded_size_.height() <= encoder_->GetCodedSize().height());
// The number of required buffers is the number of required reference frames
// + 1 for the current frame to be encoded.
const size_t max_ref_frames = encoder_->GetMaxNumOfRefFrames();
num_frames_in_flight_ = std::max(kMinNumFramesInFlight, max_ref_frames);
DVLOGF(1) << "Frames in flight: " << num_frames_in_flight_;
max_pending_results_size_ =
num_frames_in_flight_ * std::max<size_t>(1, config.spatial_layers.size());
if (!vaapi_wrapper_->CreateContext(encoder_->GetCodedSize())) {
NotifyError({EncoderStatus::Codes::kEncoderInitializationError,
"Failed creating VAContext"});
return;
}
child_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&Client::RequireBitstreamBuffers, client_,
num_frames_in_flight_, expected_input_coded_size_,
output_buffer_byte_size_));
if (config.HasSpatialLayer() || config.HasTemporalLayer()) {
DCHECK(!config.spatial_layers.empty());
for (size_t i = 0; i < config.spatial_layers.size(); ++i) {
encoder_info_.fps_allocation[i] =
GetFpsAllocation(config.spatial_layers[i].num_of_temporal_layers);
}
} else {
constexpr uint8_t kFullFramerate = 255;
encoder_info_.fps_allocation[0] = {kFullFramerate};
}
// Notify VideoEncoderInfo after initialization.
child_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&Client::NotifyEncoderInfoChange, client_, encoder_info_));
SetState(kEncoding);
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "media::VaapiVideoEncodeAccelerator", encoder_task_runner_);
}
void VaapiVideoEncodeAccelerator::RecycleVASurface(
std::vector<std::unique_ptr<ScopedVASurface>>* va_surfaces,
std::unique_ptr<ScopedVASurface> va_surface,
VASurfaceID va_surface_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DCHECK(va_surface);
DCHECK_EQ(va_surface_id, va_surface->id());
DVLOGF(4) << "va_surface_id: " << va_surface_id;
va_surfaces->push_back(std::move(va_surface));
}
void VaapiVideoEncodeAccelerator::TryToReturnBitstreamBuffers() {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
if (state_ != kEncoding)
return;
TRACE_EVENT2("media,gpu", "VAVEA::TryToReturnBitstreamBuffers",
"pending encode results", pending_encode_results_.size(),
"available bitstream buffers",
available_bitstream_buffers_.size());
while (!pending_encode_results_.empty()) {
if (!pending_encode_results_.front()) {
// A null job indicates a flush command.
pending_encode_results_.pop();
DVLOGF(2) << "FlushDone";
DCHECK(flush_callback_);
child_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(flush_callback_), true));
continue;
}
if (available_bitstream_buffers_.empty())
return;
ReturnBitstreamBuffer(*pending_encode_results_.front(),
available_bitstream_buffers_.front());
available_bitstream_buffers_.pop();
pending_encode_results_.pop();
}
}
void VaapiVideoEncodeAccelerator::ReturnBitstreamBuffer(
const EncodeResult& encode_result,
const BitstreamBuffer& buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
const base::UnsafeSharedMemoryRegion& shm_region = buffer.region();
DCHECK(shm_region.IsValid());
base::WritableSharedMemoryMapping shm_mapping = shm_region.Map();
uint8_t* target_data = shm_mapping.GetMemoryAs<uint8_t>();
size_t data_size = 0;
// vaSyncSurface() is not necessary because GetEncodedChunkSize() has been
// called in VaapiVideoEncoderDelegate::Encode().
if (!vaapi_wrapper_->DownloadFromVABuffer(
encode_result.coded_buffer_id(), /*sync_surface_id=*/absl::nullopt,
target_data, shm_mapping.size(), &data_size)) {
NotifyError({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Failed downloading coded buffer"});
return;
}
auto metadata = encode_result.metadata();
DCHECK_NE(metadata.payload_size_bytes, 0u);
DVLOGF(4) << "Returning bitstream buffer "
<< (metadata.key_frame ? "(keyframe)" : "")
<< " id: " << buffer.id() << " size: " << data_size;
child_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Client::BitstreamBufferReady, client_,
buffer.id(), std::move(metadata)));
}
void VaapiVideoEncodeAccelerator::Encode(scoped_refptr<VideoFrame> frame,
bool force_keyframe) {
DVLOGF(4) << "Frame timestamp: " << frame->timestamp().InMilliseconds()
<< " force_keyframe: " << force_keyframe;
DCHECK_CALLED_ON_VALID_SEQUENCE(child_sequence_checker_);
encoder_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VaapiVideoEncodeAccelerator::EncodeTask,
encoder_weak_this_, std::move(frame), force_keyframe));
}
void VaapiVideoEncodeAccelerator::EncodeTask(scoped_refptr<VideoFrame> frame,
bool force_keyframe) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DCHECK_NE(state_, kUninitialized);
if (frame) {
// |frame| can be nullptr to indicate a flush.
const bool is_expected_storage_type =
native_input_mode_
? frame->storage_type() == VideoFrame::STORAGE_GPU_MEMORY_BUFFER
: frame->IsMappable();
if (!is_expected_storage_type) {
NotifyError({EncoderStatus::Codes::kInvalidInputFrame,
"Unexpected storage: " +
VideoFrame::StorageTypeToString(frame->storage_type())});
return;
}
}
input_queue_.emplace(std::move(frame), force_keyframe);
EncodePendingInputs();
}
bool VaapiVideoEncodeAccelerator::CreateSurfacesForGpuMemoryBufferEncoding(
const VideoFrame& frame,
const std::vector<gfx::Size>& spatial_layer_resolutions,
std::vector<scoped_refptr<VASurface>>* input_surfaces,
std::vector<scoped_refptr<VASurface>>* reconstructed_surfaces) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DCHECK(native_input_mode_);
DCHECK_EQ(frame.storage_type(), VideoFrame::STORAGE_GPU_MEMORY_BUFFER);
TRACE_EVENT0("media,gpu", "VAVEA::CreateSurfacesForGpuMemoryBuffer");
if (frame.format() != PIXEL_FORMAT_NV12) {
NotifyError(
{EncoderStatus::Codes::kUnsupportedFrameFormat,
"Expected NV12, got: " + VideoPixelFormatToString(frame.format())});
return false;
}
if (spatial_layer_resolutions.empty())
return false;
// Create reconstructed surfaces.
reconstructed_surfaces->reserve(spatial_layer_resolutions.size());
for (const auto& encode_size : spatial_layer_resolutions) {
reconstructed_surfaces->push_back(CreateEncodeSurface(encode_size));
if (!reconstructed_surfaces->back()) {
return false;
}
}
scoped_refptr<VASurface> source_surface;
{
TRACE_EVENT0("media,gpu", "VAVEA::ImportGpuMemoryBufferToVASurface");
// Create VASurface from GpuMemory-based VideoFrame.
scoped_refptr<gfx::NativePixmap> pixmap = CreateNativePixmapDmaBuf(&frame);
if (!pixmap) {
NotifyError({EncoderStatus::Codes::kSystemAPICallError,
"Failed to create NativePixmap from VideoFrame"});
return false;
}
source_surface =
vaapi_wrapper_->CreateVASurfaceForPixmap(std::move(pixmap));
if (!source_surface) {
NotifyError({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Failed to create VASurface"});
return false;
}
}
// Create input surfaces.
TRACE_EVENT1("media,gpu", "VAVEA::ConstructSurfaces", "layers",
spatial_layer_resolutions.size());
input_surfaces->resize(spatial_layer_resolutions.size());
// Process from uppermost layer, then use immediate upper layer as vpp source
// surface if applicable.
auto source_rect = frame.visible_rect();
for (size_t i = spatial_layer_resolutions.size() - 1; i != std::variant_npos;
--i) {
const gfx::Size& encode_size = spatial_layer_resolutions[i];
const bool engage_vpp = source_rect != gfx::Rect(encode_size);
// Crop and Scale input surface to a surface whose size is |encode_size|.
// The size of a reconstructed surface is also |encode_size|.
if (engage_vpp) {
if (i + 1 < spatial_layer_resolutions.size()) {
source_surface = input_surfaces->at(i + 1);
source_rect = gfx::Rect(source_surface->size());
}
input_surfaces->at(i) =
ExecuteBlitSurface(*source_surface, source_rect, encode_size);
} else {
input_surfaces->at(i) = source_surface;
}
if (!input_surfaces->at(i)) {
return false;
}
}
return true;
}
bool VaapiVideoEncodeAccelerator::CreateSurfacesForShmemEncoding(
const VideoFrame& frame,
scoped_refptr<VASurface>* input_surface,
scoped_refptr<VASurface>* reconstructed_surface) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DCHECK(!native_input_mode_);
DCHECK(frame.IsMappable());
TRACE_EVENT0("media,gpu", "VAVEA::CreateSurfacesForShmem");
if (expected_input_coded_size_ != frame.coded_size()) {
// In non-zero copy mode, the coded size of the incoming frame should be
// the same as the one we requested through
// Client::RequireBitstreamBuffers().
NotifyError(
{EncoderStatus::Codes::kInvalidInputFrame,
"Expected frame coded size: " + expected_input_coded_size_.ToString() +
", but got: " + frame.coded_size().ToString()});
return false;
}
DCHECK(visible_rect_.origin().IsOrigin());
if (visible_rect_ != frame.visible_rect()) {
// In non-zero copy mode, the client is responsible for scaling and
// cropping.
NotifyError(
{EncoderStatus::Codes::kInvalidInputFrame,
"Expected frame visible rectangle: " + visible_rect_.ToString() +
", but got: " + frame.visible_rect().ToString()});
return false;
}
const gfx::Size& encode_size = encoder_->GetCodedSize();
*reconstructed_surface = CreateEncodeSurface(encode_size);
if (!*reconstructed_surface) {
return false;
}
*input_surface =
CreateInputSurface(*vaapi_wrapper_, encode_size,
{VaapiWrapper::SurfaceUsageHint::kVideoEncoder});
if (!*input_surface) {
NotifyError({EncoderStatus::Codes::kEncoderIllegalState,
"Failed to create input surface"});
return false;
}
if (!vaapi_wrapper_->UploadVideoFrameToSurface(frame, (*input_surface)->id(),
(*input_surface)->size())) {
NotifyError({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Failed to upload frame"});
return false;
}
return !!*reconstructed_surface;
}
scoped_refptr<VASurface> VaapiVideoEncodeAccelerator::CreateInputSurface(
VaapiWrapper& vaapi_wrapper,
const gfx::Size& encode_size,
const std::vector<VaapiWrapper::SurfaceUsageHint>& surface_usage_hints) {
if (!base::Contains(input_surfaces_, encode_size)) {
auto surface =
CreateScopedSurface(vaapi_wrapper, encode_size, surface_usage_hints);
if (!surface) {
NotifyError({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Failed to create surface"});
return nullptr;
}
input_surfaces_[encode_size] = std::move(surface);
}
const ScopedVASurface& surface = *input_surfaces_[encode_size];
return base::MakeRefCounted<VASurface>(surface.id(), surface.size(),
surface.format(), base::DoNothing());
}
scoped_refptr<VASurface> VaapiVideoEncodeAccelerator::CreateEncodeSurface(
const gfx::Size& encode_size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
const size_t max_allocated_surfaces = num_frames_in_flight_ + 1;
const bool no_surfaces_available =
!base::Contains(available_encode_surfaces_, encode_size) ||
available_encode_surfaces_[encode_size].empty();
if (no_surfaces_available &&
encode_surfaces_count_[encode_size] >= max_allocated_surfaces) {
DVLOGF(4) << "Not enough surfaces available";
return nullptr;
}
if (no_surfaces_available) {
auto surface =
CreateScopedSurface(*vaapi_wrapper_, encode_size,
{VaapiWrapper::SurfaceUsageHint::kVideoEncoder});
if (!surface) {
NotifyError({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Failed creating surfaces"});
return nullptr;
}
available_encode_surfaces_[encode_size].push_back(std::move(surface));
encode_surfaces_count_[encode_size] += 1;
}
auto& surfaces = available_encode_surfaces_[encode_size];
auto scoped_va_surface = std::move(surfaces.back());
surfaces.pop_back();
const VASurfaceID id = scoped_va_surface->id();
const gfx::Size& size = scoped_va_surface->size();
const unsigned int format = scoped_va_surface->format();
VASurface::ReleaseCB release_cb = base::BindOnce(
&VaapiVideoEncodeAccelerator::RecycleVASurface, encoder_weak_this_,
&surfaces, std::move(scoped_va_surface));
return base::MakeRefCounted<VASurface>(id, size, format,
std::move(release_cb));
}
scoped_refptr<VaapiWrapper>
VaapiVideoEncodeAccelerator::CreateVppVaapiWrapper() {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DCHECK(!vpp_vaapi_wrapper_);
auto vpp_vaapi_wrapper = VaapiWrapper::Create(
VaapiWrapper::kVideoProcess, VAProfileNone,
EncryptionScheme::kUnencrypted,
base::BindRepeating(&ReportVaapiErrorToUMA,
"Media.VaapiVideoEncodeAccelerator.Vpp.VAAPIError"));
if (!vpp_vaapi_wrapper) {
NotifyError({EncoderStatus::Codes::kEncoderUnsupportedConfig,
"Failed to initialize VppVaapiWrapper"});
return nullptr;
}
// VA context for VPP is not associated with a specific resolution.
if (!vpp_vaapi_wrapper->CreateContext(gfx::Size())) {
NotifyError({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Failed creating Context for VPP"});
return nullptr;
}
return vpp_vaapi_wrapper;
}
scoped_refptr<VASurface> VaapiVideoEncodeAccelerator::ExecuteBlitSurface(
const VASurface& source_surface,
const gfx::Rect source_visible_rect,
const gfx::Size& encode_size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
if (!vpp_vaapi_wrapper_) {
vpp_vaapi_wrapper_ = CreateVppVaapiWrapper();
if (!vpp_vaapi_wrapper_) {
LOG(ERROR) << "Failed to create Vpp";
return nullptr;
}
}
auto blit_surface =
CreateInputSurface(*vpp_vaapi_wrapper_, encode_size,
{VaapiWrapper::SurfaceUsageHint::kVideoProcessWrite,
VaapiWrapper::SurfaceUsageHint::kVideoEncoder});
if (!blit_surface)
return nullptr;
DCHECK(vpp_vaapi_wrapper_);
if (!vpp_vaapi_wrapper_->BlitSurface(source_surface, *blit_surface,
source_visible_rect,
gfx::Rect(encode_size))) {
NotifyError({EncoderStatus::Codes::kFormatConversionError,
"Failed BlitSurface on frame size: " +
source_surface.size().ToString() +
" (visible rect: " + source_visible_rect.ToString() +
") -> encode size: " + encode_size.ToString()});
return nullptr;
}
return blit_surface;
}
std::unique_ptr<VaapiVideoEncoderDelegate::EncodeJob>
VaapiVideoEncodeAccelerator::CreateEncodeJob(
bool force_keyframe,
base::TimeDelta frame_timestamp,
const VASurface& input_surface,
scoped_refptr<VASurface> reconstructed_surface) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DCHECK_NE(input_surface.id(), VA_INVALID_ID);
DCHECK(!input_surface.size().IsEmpty());
DCHECK(reconstructed_surface);
std::unique_ptr<ScopedVABuffer> coded_buffer;
{
TRACE_EVENT1("media,gpu", "VAVEA::CreateVABuffer", "buffer size",
output_buffer_byte_size_);
coded_buffer = vaapi_wrapper_->CreateVABuffer(VAEncCodedBufferType,
output_buffer_byte_size_);
if (!coded_buffer) {
NotifyError({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Failed creating coded buffer"});
return nullptr;
}
}
scoped_refptr<CodecPicture> picture;
switch (output_codec_) {
case VideoCodec::kH264:
picture = new VaapiH264Picture(std::move(reconstructed_surface));
break;
case VideoCodec::kVP8:
picture = new VaapiVP8Picture(std::move(reconstructed_surface));
break;
case VideoCodec::kVP9:
picture = new VaapiVP9Picture(std::move(reconstructed_surface));
break;
case VideoCodec::kAV1:
picture = new VaapiAV1Picture(/*display_va_surface=*/nullptr,
std::move(reconstructed_surface));
break;
default:
return nullptr;
}
return std::make_unique<EncodeJob>(force_keyframe, frame_timestamp,
input_surface.id(), std::move(picture),
std::move(coded_buffer));
}
void VaapiVideoEncodeAccelerator::EncodePendingInputs() {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DVLOGF(4);
std::vector<gfx::Size> spatial_layer_resolutions =
encoder_->GetSVCLayerResolutions();
if (spatial_layer_resolutions.empty()) {
VLOGF(1) << " Failed to get SVC layer resolutions";
return;
}
TRACE_EVENT1("media,gpu", "VAVEA::EncodePendingInputs",
"pending input frames", input_queue_.size());
// Encode all the frames in |input_queue_|. So that we avoid a number of
// encoded chunks are stuck at |pending_encode_results_|, we breaks if the
// queue size is more than |max_pending_encode_results_size|. Since the
// pending frames to be encoded are held in |input_queue_|, a client that
// recycles the VideoFrames will not input any more frames until an available
// bitstream buffer is given and a pending frame is released thanks to
// the resumed encode.
while (state_ == kEncoding && !input_queue_.empty() &&
pending_encode_results_.size() < max_pending_results_size_) {
const InputFrameRef& input_frame = input_queue_.front();
if (!input_frame.frame) {
// If this is a flush (null) frame, don't create/submit a new encode
// result for it, but forward absl::nulloptto the
// |pending_encode_results_| queue.
pending_encode_results_.push(absl::nullopt);
input_queue_.pop();
TryToReturnBitstreamBuffers();
continue;
}
TRACE_EVENT0("media,gpu",
"VAVEA::EncodeOneInputFrameAndReturnEncodedChunks");
const size_t num_spatial_layers = spatial_layer_resolutions.size();
std::vector<scoped_refptr<VASurface>> input_surfaces;
std::vector<scoped_refptr<VASurface>> reconstructed_surfaces;
if (native_input_mode_) {
if (!CreateSurfacesForGpuMemoryBufferEncoding(
*input_frame.frame, spatial_layer_resolutions, &input_surfaces,
&reconstructed_surfaces)) {
return;
}
} else {
DCHECK_EQ(num_spatial_layers, 1u);
input_surfaces.resize(1u);
reconstructed_surfaces.resize(1u);
if (!CreateSurfacesForShmemEncoding(*input_frame.frame,
&input_surfaces[0],
&reconstructed_surfaces[0])) {
return;
}
}
// Encoding different spatial layers for |input_frame|.
std::vector<std::unique_ptr<EncodeJob>> jobs;
for (size_t spatial_idx = 0; spatial_idx < num_spatial_layers;
++spatial_idx) {
std::unique_ptr<EncodeJob> job;
TRACE_EVENT0("media,gpu", "VAVEA::FromCreateEncodeJobToReturn");
const bool force_key =
(spatial_idx == 0 ? input_frame.force_keyframe : false);
job = CreateEncodeJob(force_key, input_frame.frame->timestamp(),
*input_surfaces[spatial_idx],
std::move(reconstructed_surfaces[spatial_idx]));
if (!job)
return;
jobs.emplace_back(std::move(job));
}
for (auto& job : jobs) {
TRACE_EVENT0("media,gpu", "VAVEA::Encode");
if (!encoder_->Encode(*job)) {
NotifyError({EncoderStatus::Codes::kEncoderFailedEncode,
"Failed encoding job"});
return;
}
}
for (auto&& job : jobs) {
TRACE_EVENT0("media,gpu", "VAVEA::GetEncodeResult");
absl::optional<EncodeResult> result =
encoder_->GetEncodeResult(std::move(job));
if (!result) {
NotifyError({EncoderStatus::Codes::kEncoderFailedEncode,
"Failed getting encode result"});
return;
}
pending_encode_results_.push(std::move(result));
}
// Invalidates |input_frame| here; it notifies a client |input_frame.frame|
// can be reused for the future encoding.
// If the frame is copied (|native_input_mode_| == false), it is clearly
// safe to release |input_frame|. If the frame is imported
// (|native_input_mode_| == true), the write operation to the frame is
// blocked on DMA_BUF_IOCTL_SYNC because a VA-API driver protects the buffer
// through a DRM driver until encoding is complete, that is, vaMapBuffer()
// on a coded buffer returns.
input_queue_.pop();
TryToReturnBitstreamBuffers();
}
}
void VaapiVideoEncodeAccelerator::UseOutputBitstreamBuffer(
BitstreamBuffer buffer) {
DVLOGF(4) << "id: " << buffer.id();
DCHECK_CALLED_ON_VALID_SEQUENCE(child_sequence_checker_);
if (buffer.size() < output_buffer_byte_size_) {
NotifyError({EncoderStatus::Codes::kInvalidOutputBuffer,
"Provided bitstream buffer too small"});
return;
}
encoder_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VaapiVideoEncodeAccelerator::UseOutputBitstreamBufferTask,
encoder_weak_this_, std::move(buffer)));
}
void VaapiVideoEncodeAccelerator::UseOutputBitstreamBufferTask(
BitstreamBuffer buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DCHECK_NE(state_, kUninitialized);
available_bitstream_buffers_.push(std::move(buffer));
TryToReturnBitstreamBuffers();
// If there is a pending frame, it is pended because of the bitstream buffer
// shortage. Try to encode it.
if (!input_queue_.empty()) {
EncodePendingInputs();
}
}
void VaapiVideoEncodeAccelerator::RequestEncodingParametersChange(
const Bitrate& bitrate,
uint32_t framerate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(child_sequence_checker_);
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, bitrate.target_bps());
encoder_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&VaapiVideoEncodeAccelerator::RequestEncodingParametersChangeTask,
encoder_weak_this_, allocation, framerate));
}
void VaapiVideoEncodeAccelerator::RequestEncodingParametersChange(
const VideoBitrateAllocation& bitrate_allocation,
uint32_t framerate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(child_sequence_checker_);
encoder_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&VaapiVideoEncodeAccelerator::RequestEncodingParametersChangeTask,
encoder_weak_this_, bitrate_allocation, framerate));
}
void VaapiVideoEncodeAccelerator::RequestEncodingParametersChangeTask(
VideoBitrateAllocation bitrate_allocation,
uint32_t framerate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
DCHECK_NE(state_, kUninitialized);
if (!encoder_->UpdateRates(bitrate_allocation, framerate)) {
VLOGF(1) << "Failed to update rates to " << bitrate_allocation.GetSumBps()
<< " " << framerate;
}
}
void VaapiVideoEncodeAccelerator::Flush(FlushCallback flush_callback) {
DVLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(child_sequence_checker_);
encoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VaapiVideoEncodeAccelerator::FlushTask,
encoder_weak_this_, std::move(flush_callback)));
}
void VaapiVideoEncodeAccelerator::FlushTask(FlushCallback flush_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
if (flush_callback_) {
NotifyError({EncoderStatus::Codes::kEncoderIllegalState,
"There is a pending flush"});
child_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(flush_callback), false));
return;
}
flush_callback_ = std::move(flush_callback);
// Insert InputFrameRef whose frame is nullptr to indicate a flush command.
input_queue_.emplace(nullptr, false);
EncodePendingInputs();
}
bool VaapiVideoEncodeAccelerator::IsFlushSupported() {
return true;
}
void VaapiVideoEncodeAccelerator::Destroy() {
DVLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(child_sequence_checker_);
child_weak_this_factory_.InvalidateWeakPtrs();
// We're destroying; cancel all callbacks.
if (client_ptr_factory_)
client_ptr_factory_->InvalidateWeakPtrs();
encoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VaapiVideoEncodeAccelerator::DestroyTask,
encoder_weak_this_));
}
void VaapiVideoEncodeAccelerator::DestroyTask() {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
encoder_weak_this_factory_.InvalidateWeakPtrs();
if (flush_callback_) {
child_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(flush_callback_), false));
}
// Clean up members that are to be accessed on the encoder thread only.
// Call DestroyContext() explicitly to make sure it's destroyed before
// VA surfaces.
if (vaapi_wrapper_)
vaapi_wrapper_->DestroyContext();
if (vpp_vaapi_wrapper_)
vpp_vaapi_wrapper_->DestroyContext();
input_surfaces_.clear();
available_bitstream_buffers_ = {};
input_queue_ = {};
// Note ScopedVABuffer owned by EncodeResults must be destroyed before
// |vaapi_wrapper_| is destroyed to ensure VADisplay is valid on the
// ScopedVABuffer's destruction.
DCHECK(vaapi_wrapper_ || pending_encode_results_.empty());
pending_encode_results_ = {};
encoder_.reset();
// Clear |available_encode_surfaces_| after |encoder_| is destroyed because
// the reconstructed surface in the reference frame pool owned by |encoder_|
// are back to |available_encode_surfaces_|.
available_encode_surfaces_.clear();
delete this;
}
void VaapiVideoEncodeAccelerator::SetState(State state) {
// Only touch state on encoder thread, unless it's not running.
if (!encoder_task_runner_->BelongsToCurrentThread()) {
encoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VaapiVideoEncodeAccelerator::SetState,
encoder_weak_this_, state));
return;
}
VLOGF(2) << "setting state to: " << state;
state_ = state;
}
void VaapiVideoEncodeAccelerator::NotifyError(EncoderStatus status) {
if (!child_task_runner_->RunsTasksInCurrentSequence()) {
child_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VaapiVideoEncodeAccelerator::NotifyError,
child_weak_this_, std::move(status)));
return;
}
SetState(kError);
CHECK(!status.is_ok());
LOG(ERROR) << "Calling NotifyErrorStatus(" << static_cast<int>(status.code())
<< "), message=" << status.message();
if (client_) {
client_->NotifyErrorStatus(status);
client_ptr_factory_->InvalidateWeakPtrs();
}
}
bool VaapiVideoEncodeAccelerator::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
using base::trace_event::MemoryAllocatorDump;
DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
auto dump_name = base::StringPrintf("gpu/vaapi/encoder/0x%" PRIxPTR,
reinterpret_cast<uintptr_t>(this));
MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name);
dump->AddString("encoder native input mode", "",
native_input_mode_ ? "true" : "false");
constexpr double kNumBytesPerPixelYUV420 = 12.0 / 8;
for (const auto& surface : encode_surfaces_count_) {
const gfx::Size& resolution = surface.first;
const size_t count = surface.second;
MemoryAllocatorDump* sub_dump = pmd->CreateAllocatorDump(
dump_name + "/encode surface/" + resolution.ToString());
sub_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
MemoryAllocatorDump::kUnitsObjects,
static_cast<uint64_t>(count));
const uint64_t surfaces_packed_size = static_cast<uint64_t>(
resolution.GetArea() * kNumBytesPerPixelYUV420 * count);
sub_dump->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes, surfaces_packed_size);
}
for (const auto& surface : input_surfaces_) {
const gfx::Size& resolution = surface.first;
MemoryAllocatorDump* sub_dump = pmd->CreateAllocatorDump(
dump_name + "/input surface/" + resolution.ToString());
const uint64_t surfaces_packed_size =
static_cast<uint64_t>(resolution.GetArea() * kNumBytesPerPixelYUV420);
sub_dump->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes, surfaces_packed_size);
}
return true;
}
} // namespace media