#include "media/gpu/android/ndk_video_encode_accelerator.h"
#include <optional>
#include "base/bits.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/metrics/histogram_functions.h"
#include "base/notimplemented.h"
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "media/base/android/media_codec_util.h"
#include "media/base/bitstream_buffer.h"
#include "media/base/encoder_status.h"
#include "media/base/media_serializers_base.h"
#include "media/base/media_switches.h"
#include "media/base/video_frame.h"
#include "media/gpu/android/video_accelerator_util.h"
#include "media/gpu/command_buffer_helper.h"
#include "media/gpu/gpu_video_encode_accelerator_helpers.h"
#include "media/parsers/h264_level_limits.h"
#include "media/parsers/h264_parser.h"
#include "media/parsers/temporal_scalability_id_extractor.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gl/gl_switches.h"
#pragma clang attribute push DEFAULT_REQUIRES_ANDROID_API( \
NDK_MEDIA_CODEC_MIN_API)
namespace media {
using EncoderType = VideoEncodeAccelerator::Config::EncoderType;
namespace {
constexpr uint32_t kDefaultGOPLength = 3000;
std::vector<VideoPixelFormat> GetSupportedSharedImagePixelFormats() {
if (base::FeatureList::IsEnabled(features::kVulkanFromANGLE)) {
return {};
}
return {PIXEL_FORMAT_ABGR, PIXEL_FORMAT_XBGR};
}
constexpr int32_t BUFFER_FLAG_KEY_FRAME = 1;
enum PixelFormat {
COLOR_FORMAT_YUV420_PLANAR = 19,
COLOR_FORMAT_YUV420_SEMIPLANAR = 21,
COLOR_FORMAT_SURFACE = 0x7f000789,
};
struct AMediaFormatDeleter {
inline void operator()(AMediaFormat* ptr) const {
if (ptr) {
AMediaFormat_delete(ptr);
}
}
};
enum class CodecProfileLevel {
AVCProfileBaseline = 0x01,
AVCProfileMain = 0x02,
AVCProfileExtended = 0x04,
AVCProfileHigh = 0x08,
AVCProfileHigh10 = 0x10,
AVCProfileHigh422 = 0x20,
AVCProfileHigh444 = 0x40,
AVCProfileConstrainedBaseline = 0x10000,
AVCProfileConstrainedHigh = 0x80000,
AVCLevel1 = 0x01,
AVCLevel1b = 0x02,
AVCLevel11 = 0x04,
AVCLevel12 = 0x08,
AVCLevel13 = 0x10,
AVCLevel2 = 0x20,
AVCLevel21 = 0x40,
AVCLevel22 = 0x80,
AVCLevel3 = 0x100,
AVCLevel31 = 0x200,
AVCLevel32 = 0x400,
AVCLevel4 = 0x800,
AVCLevel41 = 0x1000,
AVCLevel42 = 0x2000,
AVCLevel5 = 0x4000,
AVCLevel51 = 0x8000,
AVCLevel52 = 0x10000,
AVCLevel6 = 0x20000,
AVCLevel61 = 0x40000,
AVCLevel62 = 0x80000,
VP9Profile0 = 0x01,
VP9Profile1 = 0x02,
VP9Profile2 = 0x04,
VP9Profile3 = 0x08,
VP9Profile2HDR = 0x1000,
VP9Profile3HDR = 0x2000,
VP9Profile2HDR10Plus = 0x4000,
VP9Profile3HDR10Plus = 0x8000,
VP8ProfileMain = 0x01,
AV1ProfileMain8 = 0x1,
AV1ProfileMain10 = 0x2,
AV1ProfileMain10HDR10 = 0x1000,
AV1ProfileMain10HDR10Plus = 0x2000,
HEVCProfileMain = 0x01,
HEVCProfileMain10 = 0x02,
HEVCProfileMainStill = 0x04,
HEVCProfileMain10HDR10 = 0x1000,
HEVCProfileMain10HDR10Plus = 0x2000,
Unknown = 0xFFFFFF,
};
CodecProfileLevel GetAndroidVideoProfile(VideoCodecProfile profile,
bool constrained) {
switch (profile) {
case H264PROFILE_BASELINE:
return constrained ? CodecProfileLevel::AVCProfileConstrainedBaseline
: CodecProfileLevel::AVCProfileBaseline;
case H264PROFILE_MAIN:
return CodecProfileLevel::AVCProfileMain;
case H264PROFILE_EXTENDED:
return CodecProfileLevel::AVCProfileExtended;
case H264PROFILE_HIGH:
return constrained ? CodecProfileLevel::AVCProfileConstrainedHigh
: CodecProfileLevel::AVCProfileHigh;
case H264PROFILE_HIGH10PROFILE:
return CodecProfileLevel::AVCProfileHigh10;
case H264PROFILE_HIGH422PROFILE:
return CodecProfileLevel::AVCProfileHigh422;
case H264PROFILE_HIGH444PREDICTIVEPROFILE:
return CodecProfileLevel::AVCProfileHigh444;
case HEVCPROFILE_MAIN:
return CodecProfileLevel::HEVCProfileMain;
case HEVCPROFILE_MAIN10:
return CodecProfileLevel::HEVCProfileMain10;
case HEVCPROFILE_MAIN_STILL_PICTURE:
return CodecProfileLevel::HEVCProfileMainStill;
case VP8PROFILE_ANY:
return CodecProfileLevel::VP8ProfileMain;
case VP9PROFILE_PROFILE0:
return CodecProfileLevel::VP9Profile0;
case VP9PROFILE_PROFILE1:
return CodecProfileLevel::VP9Profile1;
case VP9PROFILE_PROFILE2:
return CodecProfileLevel::VP9Profile2;
case VP9PROFILE_PROFILE3:
return CodecProfileLevel::VP9Profile3;
case AV1PROFILE_PROFILE_MAIN:
return CodecProfileLevel::AV1ProfileMain8;
default:
return CodecProfileLevel::Unknown;
}
}
std::optional<CodecProfileLevel> GetAndroidAvcLevel(
std::optional<uint8_t> level) {
if (!level.has_value()) {
return {};
}
switch (level.value()) {
case H264SPS::kLevelIDC1p0:
return CodecProfileLevel::AVCLevel1;
case H264SPS::kLevelIDC1B:
return CodecProfileLevel::AVCLevel1b;
case H264SPS::kLevelIDC1p1:
return CodecProfileLevel::AVCLevel11;
case H264SPS::kLevelIDC1p2:
return CodecProfileLevel::AVCLevel12;
case H264SPS::kLevelIDC1p3:
return CodecProfileLevel::AVCLevel13;
case H264SPS::kLevelIDC2p0:
return CodecProfileLevel::AVCLevel2;
case H264SPS::kLevelIDC2p1:
return CodecProfileLevel::AVCLevel21;
case H264SPS::kLevelIDC2p2:
return CodecProfileLevel::AVCLevel22;
case H264SPS::kLevelIDC3p0:
return CodecProfileLevel::AVCLevel3;
case H264SPS::kLevelIDC3p1:
return CodecProfileLevel::AVCLevel31;
case H264SPS::kLevelIDC3p2:
return CodecProfileLevel::AVCLevel32;
case H264SPS::kLevelIDC4p0:
return CodecProfileLevel::AVCLevel4;
case H264SPS::kLevelIDC4p1:
return CodecProfileLevel::AVCLevel41;
case H264SPS::kLevelIDC4p2:
return CodecProfileLevel::AVCLevel42;
case H264SPS::kLevelIDC5p0:
return CodecProfileLevel::AVCLevel5;
case H264SPS::kLevelIDC5p1:
return CodecProfileLevel::AVCLevel51;
case H264SPS::kLevelIDC5p2:
return CodecProfileLevel::AVCLevel52;
case H264SPS::kLevelIDC6p0:
return CodecProfileLevel::AVCLevel6;
case H264SPS::kLevelIDC6p1:
return CodecProfileLevel::AVCLevel61;
case H264SPS::kLevelIDC6p2:
return CodecProfileLevel::AVCLevel62;
default:
return {};
}
}
std::optional<uint8_t> FindSuitableH264Level(
const VideoEncodeAccelerator::Config& config,
int framerate,
const gfx::Size& frame_size,
const Bitrate& bitrate) {
constexpr uint32_t kH264MbSize = 16;
uint32_t mb_width =
base::bits::AlignUp(static_cast<uint32_t>(frame_size.width()),
kH264MbSize) /
kH264MbSize;
uint32_t mb_height =
base::bits::AlignUp(static_cast<uint32_t>(frame_size.height()),
kH264MbSize) /
kH264MbSize;
return FindValidH264Level(config.output_profile, bitrate.target_bps(),
framerate, mb_width * mb_height);
}
bool GetAndroidColorValues(const gfx::ColorSpace& cs,
int* standard,
int* transfer,
int* range) {
switch (cs.GetTransferID()) {
case gfx::ColorSpace::TransferID::LINEAR:
case gfx::ColorSpace::TransferID::LINEAR_HDR:
*transfer = 1;
break;
case gfx::ColorSpace::TransferID::PQ:
*transfer = 6;
break;
case gfx::ColorSpace::TransferID::HLG:
*transfer = 7;
break;
case gfx::ColorSpace::TransferID::BT709:
case gfx::ColorSpace::TransferID::SMPTE170M:
case gfx::ColorSpace::TransferID::BT2020_10:
case gfx::ColorSpace::TransferID::BT2020_12:
case gfx::ColorSpace::TransferID::SRGB:
case gfx::ColorSpace::TransferID::SRGB_HDR:
*transfer = 3;
break;
default:
return false;
}
if (cs.GetPrimaryID() == gfx::ColorSpace::PrimaryID::BT709 &&
cs.GetMatrixID() == gfx::ColorSpace::MatrixID::BT709) {
*standard = 1;
} else if (cs.GetPrimaryID() == gfx::ColorSpace::PrimaryID::BT470BG &&
(cs.GetMatrixID() == gfx::ColorSpace::MatrixID::BT470BG ||
cs.GetMatrixID() == gfx::ColorSpace::MatrixID::SMPTE170M)) {
*standard = 2;
} else if (cs.GetPrimaryID() == gfx::ColorSpace::PrimaryID::SMPTE170M &&
(cs.GetMatrixID() == gfx::ColorSpace::MatrixID::BT470BG ||
cs.GetMatrixID() == gfx::ColorSpace::MatrixID::SMPTE170M)) {
*standard = 4;
} else if (cs.GetPrimaryID() == gfx::ColorSpace::PrimaryID::BT2020 &&
cs.GetMatrixID() == gfx::ColorSpace::MatrixID::BT2020_NCL) {
*standard = 6;
} else {
return false;
}
*range = cs.GetRangeID() == gfx::ColorSpace::RangeID::FULL
? 1
: 2;
return true;
}
bool SetFormatColorSpace(AMediaFormat* format, const gfx::ColorSpace& cs) {
DCHECK(cs.IsValid());
int standard, transfer, range;
if (!GetAndroidColorValues(cs, &standard, &transfer, &range)) {
DLOG(ERROR) << "Failed to convert color space to Android color space: "
<< cs.ToString();
return false;
}
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_STANDARD, standard);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_TRANSFER, transfer);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_RANGE, range);
return true;
}
using MediaFormatPtr = std::unique_ptr<AMediaFormat, AMediaFormatDeleter>;
MediaFormatPtr CreateVideoFormat(const VideoEncodeAccelerator::Config& config,
int framerate,
const gfx::Size& frame_size,
const Bitrate& bitrate,
std::optional<gfx::ColorSpace> cs,
int num_temporal_layers,
PixelFormat format) {
int iframe_interval = config.gop_length.value_or(kDefaultGOPLength);
const auto codec = VideoCodecProfileToVideoCodec(config.output_profile);
const auto mime = MediaCodecUtil::CodecToAndroidMimeType(codec);
MediaFormatPtr result(AMediaFormat_new());
AMediaFormat_setString(result.get(), AMEDIAFORMAT_KEY_MIME, mime.c_str());
if (codec == VideoCodec::kH264) {
std::optional<uint8_t> level = config.h264_output_level;
if (!level.has_value()) {
level = FindSuitableH264Level(config, framerate, frame_size, bitrate);
}
auto android_level = GetAndroidAvcLevel(level);
if (!android_level.has_value()) {
DLOG(ERROR) << "Invalid level, can't create MediaFormat.";
return nullptr;
}
int profile = static_cast<int>(GetAndroidVideoProfile(
config.output_profile, config.is_constrained_h264));
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_PROFILE, profile);
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_LEVEL,
static_cast<int>(android_level.value()));
}
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_WIDTH,
frame_size.width());
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_HEIGHT,
frame_size.height());
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_FRAME_RATE, framerate);
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
iframe_interval);
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_COLOR_FORMAT, format);
if (config.require_low_delay) {
if (config.output_profile != H264PROFILE_BASELINE) {
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_LATENCY, 1);
}
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_PRIORITY, 0);
}
constexpr int32_t BITRATE_MODE_VBR = 1;
constexpr int32_t BITRATE_MODE_CBR = 2;
switch (bitrate.mode()) {
case Bitrate::Mode::kConstant:
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_BITRATE_MODE,
BITRATE_MODE_CBR);
break;
case Bitrate::Mode::kVariable:
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_BITRATE_MODE,
BITRATE_MODE_VBR);
break;
default:
NOTREACHED();
}
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_BIT_RATE,
base::saturated_cast<int32_t>(bitrate.target_bps()));
if (cs && cs->IsValid()) {
SetFormatColorSpace(result.get(), *cs);
}
if (num_temporal_layers > 1) {
constexpr const char* AMEDIAFORMAT_KEY_MAX_B_FRAMES = "max-bframes";
AMediaFormat_setInt32(result.get(), AMEDIAFORMAT_KEY_MAX_B_FRAMES, 0);
auto svc_layer_config =
base::StringPrintf("android.generic.%d", num_temporal_layers);
AMediaFormat_setString(result.get(), AMEDIAFORMAT_KEY_TEMPORAL_LAYERING,
svc_layer_config.c_str());
}
return result;
}
bool IsHardwareCodec(const std::string& codec_name) {
for (const auto& info : GetEncoderInfoCache()) {
if (info.name == codec_name) {
return !info.profile.is_software_codec;
}
}
LOG(ERROR) << "Unknown codec name: " << codec_name;
return false;
}
std::optional<std::string> FindMediaCodecFor(
const VideoEncodeAccelerator::Config& config) {
std::optional<std::string> encoder_name;
for (const auto& info : GetEncoderInfoCache()) {
const auto& profile = info.profile;
if (profile.profile != config.output_profile) {
continue;
}
const auto& input_size = config.input_visible_size;
if (profile.min_resolution.width() > input_size.width()) {
continue;
}
if (profile.min_resolution.height() > input_size.height()) {
continue;
}
if (profile.max_resolution.width() < input_size.width()) {
continue;
}
if (profile.max_resolution.height() < input_size.height()) {
continue;
}
double max_supported_framerate =
static_cast<double>(profile.max_framerate_numerator) /
profile.max_framerate_denominator;
if (config.framerate > max_supported_framerate) {
continue;
}
if (profile.is_software_codec) {
if (config.required_encoder_type == EncoderType::kSoftware) {
return info.name;
}
if (config.required_encoder_type == EncoderType::kNoPreference &&
!encoder_name) {
encoder_name = info.name;
}
} else {
if (config.required_encoder_type == EncoderType::kHardware ||
config.required_encoder_type == EncoderType::kNoPreference) {
return info.name;
}
}
}
return encoder_name;
}
bool ProfileNeedsConfigDataInBitstream(VideoCodecProfile profile) {
switch (VideoCodecProfileToVideoCodec(profile)) {
case VideoCodec::kH264:
case VideoCodec::kHEVC:
return true;
case VideoCodec::kAV1:
case VideoCodec::kVP9:
case VideoCodec::kVP8:
return false;
default:
NOTREACHED()
<< "Configuration for unsupported codecs shouldn't come this far.";
}
}
void WaitForSyncTokenOnGpuThread(
scoped_refptr<CommandBufferHelper> command_buffer_helper,
gpu::SyncToken sync_token,
base::OnceClosure done_cb) {
command_buffer_helper->WaitForSyncToken(sync_token, std::move(done_cb));
}
constexpr std::string_view kEncoderStatusHistogramPrefix =
"Media.VideoEncoder.NDKVEA.EncodeStatus.";
std::string GetEncoderStatusHistogramName(VideoCodecProfile profile) {
return base::StrCat(
{kEncoderStatusHistogramPrefix,
GetCodecNameForUMA(VideoCodecProfileToVideoCodec(profile))});
}
constexpr std::string_view kInitStatusHistogramPrefix =
"Media.VideoEncoder.NDKVEA.InitStatus.";
std::string GetInitStatusHistogramName(VideoCodecProfile profile) {
return base::StrCat(
{kInitStatusHistogramPrefix,
GetCodecNameForUMA(VideoCodecProfileToVideoCodec(profile))});
}
bool ShouldUseSurfaceInput() {
if (__builtin_available(android 35, *)) {
if (base::FeatureList::IsEnabled(media::kSurfaceInputForAndroidVEA)) {
return true;
}
}
return false;
}
}
NdkVideoEncodeAccelerator::PendingEncode::PendingEncode(
scoped_refptr<VideoFrame> frame,
const VideoEncoder::EncodeOptions& options)
: frame(std::move(frame)), options(options) {}
NdkVideoEncodeAccelerator::PendingEncode::~PendingEncode() = default;
NdkVideoEncodeAccelerator::PendingEncode::PendingEncode(PendingEncode&&) =
default;
NdkVideoEncodeAccelerator::PendingEncode&
NdkVideoEncodeAccelerator::PendingEncode::operator=(PendingEncode&&) = default;
NdkVideoEncodeAccelerator::NdkVideoEncodeAccelerator(
scoped_refptr<base::SequencedTaskRunner> runner)
: task_runner_(std::move(runner)),
next_timestamp_(base::TimeTicks::Now().since_origin()),
use_surface_as_input_(ShouldUseSurfaceInput()) {}
NdkVideoEncodeAccelerator::~NdkVideoEncodeAccelerator() {
DCHECK(!media_codec_);
if (!error_occurred_ && have_encoded_frames_) {
base::UmaHistogramEnumeration(
GetEncoderStatusHistogramName(config_.output_profile),
EncoderStatus::Codes::kOk);
}
}
VideoEncodeAccelerator::SupportedProfiles
NdkVideoEncodeAccelerator::GetSupportedProfiles() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SupportedProfiles profiles;
for (auto& info : GetEncoderInfoCache()) {
profiles.push_back(info.profile);
if (use_surface_as_input_) {
auto& profile = profiles.back();
profile.gpu_supported_pixel_formats =
GetSupportedSharedImagePixelFormats();
profile.supports_gpu_shared_images =
!profile.gpu_supported_pixel_formats.empty();
}
}
return profiles;
}
EncoderStatus NdkVideoEncodeAccelerator::Initialize(
const Config& config,
VideoEncodeAccelerator::Client* client,
std::unique_ptr<MediaLog> media_log) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!media_codec_);
DCHECK(client);
client_ptr_factory_ =
std::make_unique<base::WeakPtrFactory<VideoEncodeAccelerator::Client>>(
client);
config_ = config;
effective_bitrate_ = config.bitrate;
log_ = std::move(media_log);
VideoCodec codec = VideoCodecProfileToVideoCodec(config.output_profile);
if (codec != VideoCodec::kH264) {
config_.required_encoder_type = EncoderType::kHardware;
}
effective_framerate_ = config.framerate;
num_temporal_layers_ =
config_.HasTemporalLayer()
? config_.spatial_layers.front().num_of_temporal_layers
: 1;
if (num_temporal_layers_ > 1) {
svc_parser_ = std::make_unique<TemporalScalabilityIdExtractor>(
codec, num_temporal_layers_);
}
const EncoderStatus status = ResetMediaCodec();
base::UmaHistogramEnumeration(
GetInitStatusHistogramName(config_.output_profile), status.code());
if (!status.is_ok()) {
return status;
}
const size_t bitstream_buffer_size = EstimateBitstreamBufferSize(
config_.bitrate, config_.framerate, config.input_visible_size);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VideoEncodeAccelerator::Client::RequireBitstreamBuffers,
client_ptr_factory_->GetWeakPtr(), 1,
config.input_visible_size, bitstream_buffer_size));
NotifyEncoderInfo();
return {EncoderStatus::Codes::kOk};
}
void NdkVideoEncodeAccelerator::NotifyEncoderInfo() {
CHECK(media_codec_);
std::string codec_name = "unknown";
char* name_ptr = nullptr;
media_status_t status = AMediaCodec_getName(media_codec_->codec(), &name_ptr);
if (status == AMEDIA_OK && name_ptr) {
codec_name = std::string(name_ptr);
AMediaCodec_releaseName(media_codec_->codec(), name_ptr);
}
for (const auto& info : GetEncoderInfoCache()) {
if (info.name == codec_name) {
encoder_info_.resolution_rate_limits.emplace_back(
info.profile.max_resolution, 0,
0, 0,
info.profile.max_framerate_numerator,
info.profile.max_framerate_denominator);
}
}
encoder_info_.supports_native_handle = false;
encoder_info_.has_trusted_rate_controller = false;
encoder_info_.is_hardware_accelerated = IsHardwareCodec(codec_name);
encoder_info_.supports_simulcast = false;
encoder_info_.reports_average_qp = true;
if (codec_name == "c2.cr52.avc.encoder") {
encoder_info_.reports_average_qp = false;
}
encoder_info_.supports_frame_size_change = false;
if (use_surface_as_input_) {
encoder_info_.gpu_supported_pixel_formats =
GetSupportedSharedImagePixelFormats();
encoder_info_.supports_gpu_shared_images =
!encoder_info_.gpu_supported_pixel_formats.empty();
} else {
encoder_info_.supports_gpu_shared_images = false;
encoder_info_.gpu_supported_pixel_formats.clear();
}
const char* input_type_str = "buffer";
if (use_surface_as_input_) {
input_type_str = encoder_info_.supports_gpu_shared_images
? "surface_with_shared_images"
: "surface";
}
encoder_info_.implementation_name =
base::StringPrintf("NdkVideoEncodeAccelerator(%s) input: %s",
codec_name.c_str(), input_type_str);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VideoEncodeAccelerator::Client::NotifyEncoderInfoChange,
client_ptr_factory_->GetWeakPtr(), encoder_info_));
}
void NdkVideoEncodeAccelerator::Encode(
scoped_refptr<VideoFrame> frame,
const VideoEncoder::EncodeOptions& options) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(media_codec_);
PendingEncode encode(std::move(frame), options);
if (encode.frame->HasSharedImage()) {
encode.sync_state = SyncState::kNeedsSync;
}
pending_frames_.push_back(std::move(encode));
FeedInput();
}
void NdkVideoEncodeAccelerator::Encode(scoped_refptr<VideoFrame> frame,
bool force_keyframe) {
Encode(std::move(frame), VideoEncoder::EncodeOptions(force_keyframe));
}
void NdkVideoEncodeAccelerator::UseOutputBitstreamBuffer(
BitstreamBuffer buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
available_bitstream_buffers_.push_back(std::move(buffer));
DrainOutput();
}
void NdkVideoEncodeAccelerator::RequestEncodingParametersChange(
const Bitrate& bitrate,
uint32_t framerate,
const std::optional<gfx::Size>& size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (size.has_value()) {
NotifyErrorStatus({EncoderStatus::Codes::kEncoderUnsupportedConfig,
"Update output frame size is not supported"});
return;
}
MediaFormatPtr format(AMediaFormat_new());
if (effective_framerate_ != framerate)
AMediaFormat_setInt32(format.get(), AMEDIAFORMAT_KEY_FRAME_RATE, framerate);
if (effective_bitrate_ != bitrate) {
AMediaFormat_setInt32(format.get(),
"video-bitrate" ,
bitrate.target_bps());
}
media_status_t status =
AMediaCodec_setParameters(media_codec_->codec(), format.get());
if (status != AMEDIA_OK) {
NotifyMediaCodecError(EncoderStatus::Codes::kEncoderUnsupportedConfig,
status, "Failed to change bitrate and framerate");
return;
}
effective_framerate_ = framerate;
effective_bitrate_ = bitrate;
}
void NdkVideoEncodeAccelerator::Destroy() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
client_ptr_factory_.reset();
if (media_codec_) {
media_codec_->Stop();
media_codec_.reset();
}
gl_renderer_.reset();
metrics_helper_.reset();
delete this;
}
bool NdkVideoEncodeAccelerator::IsFlushSupported() {
return false;
}
void NdkVideoEncodeAccelerator::SetCommandBufferHelperCB(
base::RepeatingCallback<scoped_refptr<CommandBufferHelper>()>
get_command_buffer_helper_cb,
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner) {
if (!use_surface_as_input_) {
return;
}
gpu_task_runner_ = std::move(gpu_task_runner);
gpu_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, std::move(get_command_buffer_helper_cb),
base::BindOnce(&NdkVideoEncodeAccelerator::OnCommandBufferHelperAvailable,
weak_ptr_factory_.GetWeakPtr()));
}
void NdkVideoEncodeAccelerator::OnCommandBufferHelperAvailable(
scoped_refptr<CommandBufferHelper> command_buffer_helper) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
command_buffer_helper_ = std::move(command_buffer_helper);
if (!command_buffer_helper_) {
NotifyErrorStatus({EncoderStatus::Codes::kEncoderInitializationError,
"Can't obtain CommandBufferHelper"});
return;
}
gl_renderer_->SetSharedImageManager(
command_buffer_helper_->GetSharedImageManager());
FeedInput();
}
bool NdkVideoEncodeAccelerator::SetInputBufferLayout(
const gfx::Size& configured_size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(media_codec_);
DCHECK(!configured_size.IsEmpty());
MediaFormatPtr input_format(
AMediaCodec_getInputFormat(media_codec_->codec()));
if (!input_format) {
return false;
}
const auto aligned_size = gfx::Size(
base::bits::AlignDownDeprecatedDoNotUse(configured_size.width(), 16),
base::bits::AlignDownDeprecatedDoNotUse(configured_size.height(), 16));
bool require_aligned_resolution = false;
if (!AMediaFormat_getInt32(input_format.get(), AMEDIAFORMAT_KEY_STRIDE,
&input_buffer_stride_)) {
input_buffer_stride_ = aligned_size.width();
require_aligned_resolution = true;
}
if (!AMediaFormat_getInt32(input_format.get(), AMEDIAFORMAT_KEY_SLICE_HEIGHT,
&input_buffer_yplane_height_)) {
input_buffer_yplane_height_ = aligned_size.height();
require_aligned_resolution = true;
}
if (!require_aligned_resolution) {
return true;
}
if (config_.input_visible_size == aligned_size) {
return true;
}
if (aligned_size.IsEmpty()) {
MEDIA_LOG(ERROR, log_) << "MediaCodec on this platform requires 16x16 "
"alignment, which is not possible for: "
<< config_.input_visible_size.ToString();
return false;
}
aligned_size_ = aligned_size;
MEDIA_LOG(INFO, log_)
<< "MediaCodec encoder requires 16x16 aligned resolution. Cropping to "
<< aligned_size_->ToString();
return true;
}
base::TimeDelta NdkVideoEncodeAccelerator::RecordFrameTimestamps(
base::TimeDelta real_timestamp) {
base::TimeDelta step = base::Seconds(1) / effective_framerate_;
auto result = next_timestamp_;
generated_to_real_timestamp_map_[result] = {real_timestamp,
base::TimeTicks::Now()};
next_timestamp_ += step;
return result;
}
std::optional<NdkVideoEncodeAccelerator::FrameTimestampInfo>
NdkVideoEncodeAccelerator::RetrieveFrameTimestamps(
base::TimeDelta monotonic_timestamp) {
auto it = generated_to_real_timestamp_map_.find(monotonic_timestamp);
if (it != generated_to_real_timestamp_map_.end()) {
FrameTimestampInfo result = it->second;
generated_to_real_timestamp_map_.erase(it);
return result;
}
return std::nullopt;
}
void NdkVideoEncodeAccelerator::FeedInput() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(media_codec_);
if (error_occurred_) {
return;
}
if (pending_frames_.empty()) {
return;
}
if (!media_codec_->HasInput() && !use_surface_as_input_) {
return;
}
if (pending_color_space_) {
return;
}
auto& next_encode = pending_frames_.front();
auto& frame = next_encode.frame;
bool key_frame = next_encode.options.key_frame;
switch (next_encode.sync_state) {
case SyncState::kReadyForEncoding:
break;
case SyncState::kNeedsSync: {
if (!command_buffer_helper_) {
return;
}
next_encode.sync_state = SyncState::kSyncInProgress;
auto sync_done_callback = base::BindPostTaskToCurrentDefault(
base::BindOnce(&NdkVideoEncodeAccelerator::OnSyncDone,
weak_ptr_factory_.GetWeakPtr(), frame->unique_id()));
gpu_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WaitForSyncTokenOnGpuThread, command_buffer_helper_,
frame->acquire_sync_token(),
std::move(sync_done_callback)));
return;
}
case SyncState::kSyncInProgress:
return;
}
const auto frame_cs = frame->ColorSpace();
if (!encoder_color_space_ || *encoder_color_space_ != frame_cs) {
if (!have_encoded_frames_) {
encoder_color_space_ = frame_cs;
SetEncoderColorSpace();
} else {
pending_color_space_ = frame_cs;
media_status_t status = SendEndOfStream();
if (status != AMEDIA_OK) {
NotifyMediaCodecError(EncoderStatus::Codes::kEncoderHardwareDriverError,
status, "Failed to queueInputBuffer");
}
return;
}
}
if (key_frame) {
MediaFormatPtr format(AMediaFormat_new());
AMediaFormat_setInt32(
format.get(), "request-sync" , 0);
media_status_t status =
AMediaCodec_setParameters(media_codec_->codec(), format.get());
if (status != AMEDIA_OK) {
NotifyMediaCodecError(EncoderStatus::Codes::kEncoderFailedEncode, status,
"Failed to request a keyframe");
return;
}
}
auto timestamp = RecordFrameTimestamps(frame->timestamp());
if (use_surface_as_input_) {
FeedGLSurface(std::move(frame), timestamp);
} else {
FeedInputBuffer(std::move(frame), timestamp);
}
have_encoded_frames_ = true;
pending_frames_.pop_front();
}
void NdkVideoEncodeAccelerator::OnSyncDone(VideoFrame::ID frame_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (pending_frames_.empty() ||
pending_frames_.front().frame->unique_id() != frame_id) {
return;
}
DCHECK_EQ(pending_frames_.front().sync_state, SyncState::kSyncInProgress);
pending_frames_.front().sync_state = SyncState::kReadyForEncoding;
FeedInput();
}
void NdkVideoEncodeAccelerator::FeedInputBuffer(scoped_refptr<VideoFrame> frame,
base::TimeDelta timestamp) {
const size_t buffer_idx = media_codec_->TakeInput();
auto mc_input_buffer = media_codec_->GetInputBuffer(buffer_idx);
if (mc_input_buffer.empty()) {
NotifyErrorStatus({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Can't obtain input buffer from media codec"});
return;
}
const auto visible_size =
aligned_size_.value_or(frame->visible_rect().size());
const int dst_stride_y = input_buffer_stride_;
const int dst_stride_uv = input_buffer_stride_;
const gfx::Size uv_plane_size = VideoFrame::PlaneSizeInSamples(
PIXEL_FORMAT_NV12, VideoFrame::Plane::kUV, visible_size);
const size_t y_plane_len = input_buffer_yplane_height_ * input_buffer_stride_;
const size_t uv_plane_len =
(uv_plane_size.height() - 1) * dst_stride_uv +
uv_plane_size.width() * 2;
const size_t queued_size = y_plane_len + uv_plane_len;
if (queued_size > mc_input_buffer.size()) {
NotifyErrorStatus(
{EncoderStatus::Codes::kInvalidInputFrame,
base::StringPrintf("Frame doesn't fit into the input "
"buffer. queued_size: %zu capacity: "
"%zu",
queued_size, mc_input_buffer.size())});
return;
}
auto dst_y = mc_input_buffer.first(y_plane_len);
auto dst_uv = mc_input_buffer.subspan(y_plane_len, uv_plane_len);
bool converted = false;
if (frame->format() == PIXEL_FORMAT_I420) {
converted =
!libyuv::I420ToNV12(frame->visible_data(VideoFrame::Plane::kY),
frame->stride(VideoFrame::Plane::kY),
frame->visible_data(VideoFrame::Plane::kU),
frame->stride(VideoFrame::Plane::kU),
frame->visible_data(VideoFrame::Plane::kV),
frame->stride(VideoFrame::Plane::kV), dst_y.data(),
dst_stride_y, dst_uv.data(), dst_stride_uv,
visible_size.width(), visible_size.height());
} else if (frame->format() == PIXEL_FORMAT_NV12) {
converted =
!libyuv::NV12Copy(frame->visible_data(VideoFrame::Plane::kY),
frame->stride(VideoFrame::Plane::kY),
frame->visible_data(VideoFrame::Plane::kUV),
frame->stride(VideoFrame::Plane::kUV), dst_y.data(),
dst_stride_y, dst_uv.data(), dst_stride_uv,
visible_size.width(), visible_size.height());
} else {
NotifyErrorStatus({EncoderStatus::Codes::kUnsupportedFrameFormat,
"Unexpected frame format: " +
VideoPixelFormatToString(frame->format())});
return;
}
if (!converted) {
NotifyErrorStatus({EncoderStatus::Codes::kFormatConversionError,
"Failed to copy pixels to input buffer"});
return;
}
uint64_t flags = 0;
media_status_t status = AMediaCodec_queueInputBuffer(
media_codec_->codec(), buffer_idx, 0, queued_size,
timestamp.InMicroseconds(), flags);
if (status != AMEDIA_OK) {
NotifyMediaCodecError(EncoderStatus::Codes::kEncoderHardwareDriverError,
status, "Failed to queueInputBuffer");
return;
}
}
media_status_t NdkVideoEncodeAccelerator::SendEndOfStream() {
if (use_surface_as_input_) {
return AMediaCodec_signalEndOfInputStream(media_codec_->codec());
}
size_t buffer_idx = media_codec_->TakeInput();
return AMediaCodec_queueInputBuffer(
media_codec_->codec(), buffer_idx, 0, 0,
0,
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
}
void NdkVideoEncodeAccelerator::FeedGLSurface(scoped_refptr<VideoFrame> frame,
base::TimeDelta timestamp) {
DCHECK(use_surface_as_input_);
if (!gl_renderer_) {
NotifyErrorStatus({EncoderStatus::Codes::kEncoderHardwareDriverError,
"GL renderer is not initialized"});
return;
}
auto render_status =
gl_renderer_->RenderVideoFrame(frame, timestamp + base::TimeTicks());
if (!render_status.is_ok()) {
NotifyErrorStatus(std::move(render_status));
MEDIA_LOG(ERROR, log_) << "Most recent frame: "
<< frame->AsHumanReadableString();
return;
}
}
void NdkVideoEncodeAccelerator::NotifyMediaCodecError(
EncoderStatus encoder_status,
media_status_t media_codec_status,
std::string message) {
NotifyErrorStatus({encoder_status.code(),
base::StringPrintf("%s MediaCodec error code: %d",
message.c_str(), media_codec_status)});
}
void NdkVideoEncodeAccelerator::NotifyErrorStatus(EncoderStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!status.is_ok());
MEDIA_LOG(ERROR, log_) << EncoderStatusCodeToString(status.code()) << " "
<< status.message();
if (!error_occurred_) {
base::UmaHistogramEnumeration(
GetEncoderStatusHistogramName(config_.output_profile), status.code());
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VideoEncodeAccelerator::Client::NotifyErrorStatus,
client_ptr_factory_->GetWeakPtr(), status));
error_occurred_ = true;
}
}
void NdkVideoEncodeAccelerator::OnInputAvailable() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
FeedInput();
}
void NdkVideoEncodeAccelerator::OnOutputAvailable() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DrainOutput();
FeedInput();
}
void NdkVideoEncodeAccelerator::OnError(media_status_t error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NotifyMediaCodecError(EncoderStatus::Codes::kEncoderFailedEncode, error,
"Async media codec error");
}
bool NdkVideoEncodeAccelerator::DrainConfig() {
if (!media_codec_->HasOutput()) {
return false;
}
NdkMediaCodecWrapper::OutputInfo output_buffer = media_codec_->PeekOutput();
AMediaCodecBufferInfo& mc_buffer_info = output_buffer.info;
if ((mc_buffer_info.flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0)
return false;
std::ignore = media_codec_->TakeOutput();
auto out_buffer_data = media_codec_->GetOutputBuffer(output_buffer);
if (out_buffer_data.empty()) {
NotifyErrorStatus({EncoderStatus::Codes::kEncoderFailedEncode,
"Can't obtain output buffer from media codec"});
return false;
}
if (ProfileNeedsConfigDataInBitstream(config_.output_profile)) {
config_data_.assign(out_buffer_data.begin(), out_buffer_data.end());
}
AMediaCodec_releaseOutputBuffer(media_codec_->codec(),
output_buffer.buffer_index, false);
return true;
}
void NdkVideoEncodeAccelerator::DrainOutput() {
if (error_occurred_)
return;
if (DrainConfig())
return;
if (!media_codec_->HasOutput() || available_bitstream_buffers_.empty()) {
return;
}
NdkMediaCodecWrapper::OutputInfo output_buffer = media_codec_->TakeOutput();
AMediaCodecBufferInfo& mc_buffer_info = output_buffer.info;
const size_t mc_buffer_size = static_cast<size_t>(mc_buffer_info.size);
if ((mc_buffer_info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
if (pending_color_space_) {
DCHECK_EQ(mc_buffer_size, 0u);
encoder_color_space_ = pending_color_space_;
pending_color_space_.reset();
if (!ResetMediaCodec().is_ok()) {
NotifyErrorStatus(
{EncoderStatus::Codes::kEncoderFailedEncode,
"Failed to recreate media codec for color space change."});
}
}
return;
}
const bool key_frame = (mc_buffer_info.flags & BUFFER_FLAG_KEY_FRAME) != 0;
BitstreamBuffer bitstream_buffer =
std::move(available_bitstream_buffers_.back());
available_bitstream_buffers_.pop_back();
const size_t config_size = key_frame ? config_data_.size() : 0u;
if (config_size + mc_buffer_size > bitstream_buffer.size()) {
NotifyErrorStatus(
{EncoderStatus::Codes::kEncoderFailedEncode,
base::StringPrintf("Encoded output is too large. mc output size: %zu"
" bitstream buffer size: %zu"
" config size: %zu",
mc_buffer_size, bitstream_buffer.size(),
config_size)});
return;
}
auto out_buffer_data = media_codec_->GetOutputBuffer(output_buffer);
if (out_buffer_data.empty()) {
NotifyErrorStatus({EncoderStatus::Codes::kEncoderFailedEncode,
"Can't obtain output buffer from media codec"});
return;
}
base::ScopedClosureRunner release_buffer(base::BindOnce(
[](NdkMediaCodecWrapper* media_codec, int index) {
AMediaCodec_releaseOutputBuffer(media_codec->codec(), index, false);
},
base::Unretained(media_codec_.get()), output_buffer.buffer_index));
base::UnsafeSharedMemoryRegion region = bitstream_buffer.TakeRegion();
auto mapping =
region.MapAt(bitstream_buffer.offset(), bitstream_buffer.size());
if (!mapping.IsValid()) {
NotifyErrorStatus(
{EncoderStatus::Codes::kSystemAPICallError, "Failed to map SHM"});
return;
}
auto output_dst = mapping.GetMemoryAsSpan<uint8_t>();
if (config_size > 0) {
output_dst.copy_prefix_from(config_data_);
output_dst = output_dst.subspan(config_size);
}
output_dst.copy_prefix_from(out_buffer_data);
auto timestamp_info = RetrieveFrameTimestamps(
base::Microseconds(mc_buffer_info.presentationTimeUs));
if (!timestamp_info.has_value()) {
MEDIA_LOG(ERROR, log_) << "Failed to find timestamp for encoded frame. ts:"
<< mc_buffer_info.presentationTimeUs;
NOTREACHED(base::NotFatalUntil::M150)
<< "Failed to find timestamp for encoded frame. ts:"
<< mc_buffer_info.presentationTimeUs;
timestamp_info = FrameTimestampInfo();
}
auto metadata = BitstreamBufferMetadata(
mc_buffer_size + config_size, key_frame, timestamp_info->real_timestamp);
if (aligned_size_) {
metadata.encoded_size = aligned_size_;
}
if (encoder_color_space_) {
metadata.encoded_color_space = *encoder_color_space_;
}
if (num_temporal_layers_ > 1) {
DCHECK(svc_parser_);
if (key_frame) {
input_since_keyframe_count_ = 0;
}
TemporalScalabilityIdExtractor::BitstreamMetadata bits_md;
if (!svc_parser_->ParseChunk(output_dst.first(mc_buffer_size),
input_since_keyframe_count_, bits_md)) {
NotifyErrorStatus({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Parse bitstream failed"});
return;
}
switch (VideoCodecProfileToVideoCodec(config_.output_profile)) {
case VideoCodec::kH264:
metadata.h264.emplace().temporal_idx = bits_md.temporal_id;
break;
default:
NOTIMPLEMENTED() << "SVC is only supported for H.264.";
break;
}
++input_since_keyframe_count_;
}
auto encoding_latency =
base::TimeTicks::Now() - timestamp_info->encode_start_time;
metrics_helper_->EncodeOneFrame(key_frame, encoding_latency);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VideoEncodeAccelerator::Client::BitstreamBufferReady,
client_ptr_factory_->GetWeakPtr(), bitstream_buffer.id(),
metadata));
}
EncoderStatus NdkVideoEncodeAccelerator::ResetMediaCodec() {
DCHECK(!pending_color_space_);
have_encoded_frames_ = false;
if (media_codec_) {
media_codec_->Stop();
media_codec_.reset();
}
gl_renderer_.reset();
auto name = FindMediaCodecFor(config_);
if (!name) {
MEDIA_LOG(ERROR, log_) << "No suitable MedicCodec found for: "
<< config_.AsHumanReadableString();
return {EncoderStatus::Codes::kEncoderUnsupportedCodec};
}
auto configured_size = aligned_size_.value_or(config_.input_visible_size);
PixelFormat pixel_format = use_surface_as_input_
? COLOR_FORMAT_SURFACE
: COLOR_FORMAT_YUV420_SEMIPLANAR;
auto media_format = CreateVideoFormat(
config_, effective_framerate_, configured_size, effective_bitrate_,
encoder_color_space_, num_temporal_layers_, pixel_format);
if (!media_format) {
MEDIA_LOG(ERROR, log_) << "Fail to create media format for: "
<< config_.AsHumanReadableString();
return {EncoderStatus::Codes::kEncoderUnsupportedConfig};
}
do {
media_codec_ =
NdkMediaCodecWrapper::CreateByCodecName(*name, this, task_runner_);
if (!media_codec_) {
MEDIA_LOG(ERROR, log_)
<< "Can't create media codec (" << name.value()
<< ") for config: " << config_.AsHumanReadableString();
return {EncoderStatus::Codes::kEncoderInitializationError};
}
media_status_t status = AMediaCodec_configure(
media_codec_->codec(), media_format.get(), nullptr, nullptr,
AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
if (status != AMEDIA_OK) {
MEDIA_LOG(ERROR, log_) << "Can't configure media codec. Error " << status;
return {EncoderStatus::Codes::kEncoderInitializationError};
}
if (use_surface_as_input_) {
ANativeWindow* surface;
status = AMediaCodec_createInputSurface(media_codec_->codec(), &surface);
if (status != AMEDIA_OK) {
MEDIA_LOG(ERROR, log_)
<< "Can't create input surface. Error " << status;
return {EncoderStatus::Codes::kEncoderInitializationError};
}
input_surface_ = gl::ScopedANativeWindow::Adopt(surface);
gl_renderer_ = std::make_unique<VideoFrameGLSurfaceRenderer>(
std::move(input_surface_));
if (command_buffer_helper_) {
gl_renderer_->SetSharedImageManager(
command_buffer_helper_->GetSharedImageManager());
}
auto gl_renderer_status = gl_renderer_->Initialize();
if (!gl_renderer_status.is_ok()) {
MEDIA_LOG(ERROR, log_) << "Failed to initialize GL renderer: "
<< gl_renderer_status.message();
return gl_renderer_status;
}
break;
}
if (!SetInputBufferLayout(configured_size)) {
MEDIA_LOG(ERROR, log_) << "Can't get input buffer layout from MediaCodec";
return {EncoderStatus::Codes::kEncoderInitializationError};
}
if (aligned_size_.value_or(configured_size) != configured_size) {
encoder_info_.requested_resolution_alignment = 16;
encoder_info_.apply_alignment_to_all_simulcast_layers = true;
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&VideoEncodeAccelerator::Client::NotifyEncoderInfoChange,
client_ptr_factory_->GetWeakPtr(), encoder_info_));
media_codec_->Stop();
media_codec_.reset();
AMediaFormat_setInt32(media_format.get(), AMEDIAFORMAT_KEY_WIDTH,
aligned_size_->width());
AMediaFormat_setInt32(media_format.get(), AMEDIAFORMAT_KEY_HEIGHT,
aligned_size_->height());
configured_size = *aligned_size_;
}
} while (!media_codec_);
media_status_t status = media_codec_->Start();
if (status == AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE) {
MEDIA_LOG(ERROR, log_) << "No more encoders available. Error " << status;
return {EncoderStatus::Codes::kOutOfPlatformEncoders};
}
if (status != AMEDIA_OK) {
MEDIA_LOG(ERROR, log_) << "Can't start media codec. Error " << status;
return {EncoderStatus::Codes::kEncoderInitializationError};
}
metrics_helper_ = std::make_unique<VEAEncodingLatencyMetricsHelper>(
"Media.VideoEncoder.NDKVEA.EncodingLatency.",
VideoCodecProfileToVideoCodec(config_.output_profile));
MEDIA_LOG(INFO, log_) << "Created MediaCodec (" << name.value()
<< ") for config: " << config_.AsHumanReadableString();
return {EncoderStatus::Codes::kOk};
}
void NdkVideoEncodeAccelerator::SetEncoderColorSpace() {
DCHECK(!have_encoded_frames_);
DCHECK(encoder_color_space_);
if (!encoder_color_space_->IsValid()) {
return;
}
MediaFormatPtr format(AMediaFormat_new());
if (!SetFormatColorSpace(format.get(), *encoder_color_space_)) {
return;
}
auto status = AMediaCodec_setParameters(media_codec_->codec(), format.get());
if (status != AMEDIA_OK) {
DLOG(ERROR) << "Failed to set color space parameters: " << status;
return;
}
DVLOG(1) << "Set color space to: " << encoder_color_space_->ToString();
}
}
#pragma clang attribute pop