blob: 3882c9001f56c6d3f17c9ba58568d0d9b66bc1e2 [file] [log] [blame]
/*
* Copyright 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
// NOTICE: androidmediaencoder_jni.h must be included before
// androidmediacodeccommon.h to avoid build errors.
#include "sdk/android/src/jni/androidmediaencoder_jni.h"
#include <algorithm>
#include <list>
#include <memory>
#include <string>
#include <utility>
#include "third_party/libyuv/include/libyuv/convert.h"
#include "third_party/libyuv/include/libyuv/convert_from.h"
#include "third_party/libyuv/include/libyuv/video_common.h"
#include "api/video_codecs/video_encoder.h"
#include "common_types.h" // NOLINT(build/include)
#include "common_video/h264/h264_bitstream_parser.h"
#include "common_video/h264/h264_common.h"
#include "common_video/h264/profile_level_id.h"
#include "media/engine/internalencoderfactory.h"
#include "modules/video_coding/include/video_codec_interface.h"
#include "modules/video_coding/utility/quality_scaler.h"
#include "modules/video_coding/utility/vp8_header_parser.h"
#include "modules/video_coding/utility/vp9_uncompressed_header_parser.h"
#include "rtc_base/bind.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/random.h"
#include "rtc_base/sequenced_task_checker.h"
#include "rtc_base/task_queue.h"
#include "rtc_base/thread.h"
#include "rtc_base/timeutils.h"
#include "rtc_base/weak_ptr.h"
#include "sdk/android/src/jni/androidmediacodeccommon.h"
#include "sdk/android/src/jni/classreferenceholder.h"
#include "sdk/android/src/jni/jni_helpers.h"
#include "sdk/android/src/jni/native_handle_impl.h"
#include "system_wrappers/include/field_trial.h"
using rtc::Bind;
using rtc::Thread;
using rtc::ThreadManager;
namespace webrtc {
namespace jni {
// Maximum supported HW video encoder fps.
#define MAX_VIDEO_FPS 30
// Maximum allowed fps value in SetRates() call.
#define MAX_ALLOWED_VIDEO_FPS 60
// Maximum allowed frames in encoder input queue.
#define MAX_ENCODER_Q_SIZE 2
// Maximum amount of dropped frames caused by full encoder queue - exceeding
// this threshold means that encoder probably got stuck and need to be reset.
#define ENCODER_STALL_FRAMEDROP_THRESHOLD 60
// Logging macros.
#define TAG_ENCODER "MediaCodecVideoEncoder"
#ifdef TRACK_BUFFER_TIMING
#define ALOGV(...)
__android_log_print(ANDROID_LOG_VERBOSE, TAG_ENCODER, __VA_ARGS__)
#else
#define ALOGV(...)
#endif
#define ALOGD LOG_TAG(rtc::LS_INFO, TAG_ENCODER)
#define ALOGW LOG_TAG(rtc::LS_WARNING, TAG_ENCODER)
#define ALOGE LOG_TAG(rtc::LS_ERROR, TAG_ENCODER)
namespace {
// Maximum time limit between incoming frames before requesting a key frame.
const size_t kFrameDiffThresholdMs = 350;
const int kMinKeyFrameInterval = 6;
const char kH264HighProfileFieldTrial[] = "WebRTC-H264HighProfile";
const char kCustomQPThresholdsFieldTrial[] = "WebRTC-CustomQPThresholds";
} // namespace
// MediaCodecVideoEncoder is a VideoEncoder implementation that uses
// Android's MediaCodec SDK API behind the scenes to implement (hopefully)
// HW-backed video encode. This C++ class is implemented as a very thin shim,
// delegating all of the interesting work to org.webrtc.MediaCodecVideoEncoder.
// MediaCodecVideoEncoder must be operated on a single task queue, currently
// this is the encoder queue from ViE encoder.
class MediaCodecVideoEncoder : public VideoEncoder {
public:
virtual ~MediaCodecVideoEncoder();
MediaCodecVideoEncoder(JNIEnv* jni,
const cricket::VideoCodec& codec,
jobject egl_context);
// VideoEncoder implementation.
int32_t InitEncode(const VideoCodec* codec_settings,
int32_t /* number_of_cores */,
size_t /* max_payload_size */) override;
int32_t Encode(const VideoFrame& input_image,
const CodecSpecificInfo* /* codec_specific_info */,
const std::vector<FrameType>* frame_types) override;
int32_t RegisterEncodeCompleteCallback(
EncodedImageCallback* callback) override;
int32_t Release() override;
int32_t SetChannelParameters(uint32_t /* packet_loss */,
int64_t /* rtt */) override;
int32_t SetRateAllocation(const BitrateAllocation& rate_allocation,
uint32_t frame_rate) override;
bool SupportsNativeHandle() const override { return egl_context_ != nullptr; }
const char* ImplementationName() const override;
// Fills the input buffer with data from the buffers passed as parameters.
bool FillInputBuffer(JNIEnv* jni,
int input_buffer_index,
uint8_t const* buffer_y,
int stride_y,
uint8_t const* buffer_u,
int stride_u,
uint8_t const* buffer_v,
int stride_v);
private:
class EncodeTask : public rtc::QueuedTask {
public:
explicit EncodeTask(rtc::WeakPtr<MediaCodecVideoEncoder> encoder);
bool Run() override;
private:
rtc::WeakPtr<MediaCodecVideoEncoder> encoder_;
};
// ResetCodec() calls Release() and InitEncodeInternal() in an attempt to
// restore the codec to an operable state. Necessary after all manner of
// OMX-layer errors. Returns true if the codec was reset successfully.
bool ResetCodec();
// Fallback to a software encoder if one is supported else try to reset the
// encoder. Called with |reset_if_fallback_unavailable| equal to false from
// init/release encoder so that we don't go into infinite recursion.
// Returns true if the codec was reset successfully.
bool ProcessHWError(bool reset_if_fallback_unavailable);
// Calls ProcessHWError(true). Returns WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE if
// sw_fallback_required_ was set or WEBRTC_VIDEO_CODEC_ERROR otherwise.
int32_t ProcessHWErrorOnEncode();
// If width==0 then this is assumed to be a re-initialization and the
// previously-current values are reused instead of the passed parameters
// (makes it easier to reason about thread-safety).
int32_t InitEncodeInternal(int width,
int height,
int kbps,
int fps,
bool use_surface);
// Reconfigure to match |frame| in width, height. Also reconfigures the
// encoder if |frame| is a texture/byte buffer and the encoder is initialized
// for byte buffer/texture. Returns false if reconfiguring fails.
bool MaybeReconfigureEncoder(JNIEnv* jni, const VideoFrame& frame);
// Returns true if the frame is a texture frame and we should use surface
// based encoding.
bool IsTextureFrame(JNIEnv* jni, const VideoFrame& frame);
bool EncodeByteBuffer(JNIEnv* jni,
bool key_frame,
const VideoFrame& frame,
int input_buffer_index);
bool EncodeTexture(JNIEnv* jni, bool key_frame, const VideoFrame& frame);
// Encodes a new style org.webrtc.VideoFrame. Might be a I420 or a texture
// frame.
bool EncodeJavaFrame(JNIEnv* jni,
bool key_frame,
jobject frame,
int input_buffer_index);
// Helper accessors for MediaCodecVideoEncoder$OutputBufferInfo members.
int GetOutputBufferInfoIndex(JNIEnv* jni, jobject j_output_buffer_info);
jobject GetOutputBufferInfoBuffer(JNIEnv* jni, jobject j_output_buffer_info);
bool GetOutputBufferInfoIsKeyFrame(JNIEnv* jni, jobject j_output_buffer_info);
jlong GetOutputBufferInfoPresentationTimestampUs(
JNIEnv* jni, jobject j_output_buffer_info);
// Deliver any outputs pending in the MediaCodec to our |callback_| and return
// true on success.
bool DeliverPendingOutputs(JNIEnv* jni);
VideoEncoder::ScalingSettings GetScalingSettings() const override;
// Displays encoder statistics.
void LogStatistics(bool force_log);
VideoCodecType GetCodecType() const;
#if RTC_DCHECK_IS_ON
// Mutex for protecting inited_. It is only used for correctness checking on
// debug build. It is used for checking that encoder has been released in the
// destructor. Because this might happen on a different thread, we need a
// mutex.
rtc::CriticalSection inited_crit_;
#endif
// Type of video codec.
const cricket::VideoCodec codec_;
EncodedImageCallback* callback_;
// State that is constant for the lifetime of this object once the ctor
// returns.
rtc::SequencedTaskChecker encoder_queue_checker_;
ScopedGlobalRef<jclass> j_media_codec_video_encoder_class_;
ScopedGlobalRef<jobject> j_media_codec_video_encoder_;
jmethodID j_init_encode_method_;
jmethodID j_get_input_buffers_method_;
jmethodID j_dequeue_input_buffer_method_;
jmethodID j_encode_buffer_method_;
jmethodID j_encode_texture_method_;
jmethodID j_encode_frame_method_;
jmethodID j_release_method_;
jmethodID j_set_rates_method_;
jmethodID j_dequeue_output_buffer_method_;
jmethodID j_release_output_buffer_method_;
jfieldID j_color_format_field_;
jfieldID j_info_index_field_;
jfieldID j_info_buffer_field_;
jfieldID j_info_is_key_frame_field_;
jfieldID j_info_presentation_timestamp_us_field_;
const JavaVideoFrameFactory video_frame_factory_;
ScopedGlobalRef<jclass> j_video_frame_texture_buffer_class_;
// State that is valid only between InitEncode() and the next Release().
int width_; // Frame width in pixels.
int height_; // Frame height in pixels.
bool inited_;
bool use_surface_;
enum libyuv::FourCC encoder_fourcc_; // Encoder color space format.
int last_set_bitrate_kbps_; // Last-requested bitrate in kbps.
int last_set_fps_; // Last-requested frame rate.
int64_t current_timestamp_us_; // Current frame timestamps in us.
int frames_received_; // Number of frames received by encoder.
int frames_encoded_; // Number of frames encoded by encoder.
int frames_dropped_media_encoder_; // Number of frames dropped by encoder.
// Number of dropped frames caused by full queue.
int consecutive_full_queue_frame_drops_;
int64_t stat_start_time_ms_; // Start time for statistics.
int current_frames_; // Number of frames in the current statistics interval.
int current_bytes_; // Encoded bytes in the current statistics interval.
int current_acc_qp_; // Accumulated QP in the current statistics interval.
int current_encoding_time_ms_; // Overall encoding time in the current second
int64_t last_input_timestamp_ms_; // Timestamp of last received yuv frame.
int64_t last_output_timestamp_ms_; // Timestamp of last encoded frame.
// Holds the task while the polling loop is paused.
std::unique_ptr<rtc::QueuedTask> encode_task_;
struct InputFrameInfo {
InputFrameInfo(int64_t encode_start_time,
int32_t frame_timestamp,
int64_t frame_render_time_ms,
VideoRotation rotation)
: encode_start_time(encode_start_time),
frame_timestamp(frame_timestamp),
frame_render_time_ms(frame_render_time_ms),
rotation(rotation) {}
// Time when video frame is sent to encoder input.
const int64_t encode_start_time;
// Input frame information.
const int32_t frame_timestamp;
const int64_t frame_render_time_ms;
const VideoRotation rotation;
};
std::list<InputFrameInfo> input_frame_infos_;
int32_t output_timestamp_; // Last output frame timestamp from
// |input_frame_infos_|.
int64_t output_render_time_ms_; // Last output frame render time from
// |input_frame_infos_|.
VideoRotation output_rotation_; // Last output frame rotation from
// |input_frame_infos_|.
// Frame size in bytes fed to MediaCodec.
int yuv_size_;
// True only when between a callback_->OnEncodedImage() call return a positive
// value and the next Encode() call being ignored.
bool drop_next_input_frame_;
bool scale_;
H264::Profile profile_;
// Global references; must be deleted in Release().
std::vector<jobject> input_buffers_;
H264BitstreamParser h264_bitstream_parser_;
// VP9 variables to populate codec specific structure.
GofInfoVP9 gof_; // Contains each frame's temporal information for
// non-flexible VP9 mode.
size_t gof_idx_;
// EGL context - owned by factory, should not be allocated/destroyed
// by MediaCodecVideoEncoder.
jobject egl_context_;
// Temporary fix for VP8.
// Sends a key frame if frames are largely spaced apart (possibly
// corresponding to a large image change).
int64_t last_frame_received_ms_;
int frames_received_since_last_key_;
VideoCodecMode codec_mode_;
// RTP state.
uint16_t picture_id_;
uint8_t tl0_pic_idx_;
bool sw_fallback_required_;
// All other member variables should be before WeakPtrFactory. Valid only from
// InitEncode to Release.
std::unique_ptr<rtc::WeakPtrFactory<MediaCodecVideoEncoder>> weak_factory_;
};
MediaCodecVideoEncoder::~MediaCodecVideoEncoder() {
#if RTC_DCHECK_IS_ON
rtc::CritScope lock(&inited_crit_);
RTC_DCHECK(!inited_);
#endif
}
MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni,
const cricket::VideoCodec& codec,
jobject egl_context)
: codec_(codec),
callback_(NULL),
j_media_codec_video_encoder_class_(
jni,
FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")),
j_media_codec_video_encoder_(
jni,
jni->NewObject(*j_media_codec_video_encoder_class_,
GetMethodID(jni,
*j_media_codec_video_encoder_class_,
"<init>",
"()V"))),
video_frame_factory_(jni),
j_video_frame_texture_buffer_class_(
jni,
FindClass(jni, "org/webrtc/VideoFrame$TextureBuffer")),
inited_(false),
use_surface_(false),
egl_context_(egl_context),
sw_fallback_required_(false) {
encoder_queue_checker_.Detach();
jclass j_output_buffer_info_class =
FindClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo");
j_init_encode_method_ =
GetMethodID(jni, *j_media_codec_video_encoder_class_, "initEncode",
"(Lorg/webrtc/MediaCodecVideoEncoder$VideoCodecType;"
"IIIIILorg/webrtc/EglBase14$Context;)Z");
j_get_input_buffers_method_ = GetMethodID(
jni,
*j_media_codec_video_encoder_class_,
"getInputBuffers",
"()[Ljava/nio/ByteBuffer;");
j_dequeue_input_buffer_method_ = GetMethodID(
jni, *j_media_codec_video_encoder_class_, "dequeueInputBuffer", "()I");
j_encode_buffer_method_ = GetMethodID(
jni, *j_media_codec_video_encoder_class_, "encodeBuffer", "(ZIIJ)Z");
j_encode_texture_method_ = GetMethodID(
jni, *j_media_codec_video_encoder_class_, "encodeTexture",
"(ZI[FJ)Z");
j_encode_frame_method_ =
GetMethodID(jni, *j_media_codec_video_encoder_class_, "encodeFrame",
"(JZLorg/webrtc/VideoFrame;I)Z");
j_release_method_ =
GetMethodID(jni, *j_media_codec_video_encoder_class_, "release", "()V");
j_set_rates_method_ = GetMethodID(
jni, *j_media_codec_video_encoder_class_, "setRates", "(II)Z");
j_dequeue_output_buffer_method_ = GetMethodID(
jni,
*j_media_codec_video_encoder_class_,
"dequeueOutputBuffer",
"()Lorg/webrtc/MediaCodecVideoEncoder$OutputBufferInfo;");
j_release_output_buffer_method_ = GetMethodID(
jni, *j_media_codec_video_encoder_class_, "releaseOutputBuffer", "(I)Z");
j_color_format_field_ =
GetFieldID(jni, *j_media_codec_video_encoder_class_, "colorFormat", "I");
j_info_index_field_ =
GetFieldID(jni, j_output_buffer_info_class, "index", "I");
j_info_buffer_field_ = GetFieldID(
jni, j_output_buffer_info_class, "buffer", "Ljava/nio/ByteBuffer;");
j_info_is_key_frame_field_ =
GetFieldID(jni, j_output_buffer_info_class, "isKeyFrame", "Z");
j_info_presentation_timestamp_us_field_ = GetFieldID(
jni, j_output_buffer_info_class, "presentationTimestampUs", "J");
if (CheckException(jni)) {
ALOGW << "MediaCodecVideoEncoder ctor failed.";
ProcessHWError(true /* reset_if_fallback_unavailable */);
}
Random random(rtc::TimeMicros());
picture_id_ = random.Rand<uint16_t>() & 0x7FFF;
tl0_pic_idx_ = random.Rand<uint8_t>();
}
int32_t MediaCodecVideoEncoder::InitEncode(const VideoCodec* codec_settings,
int32_t /* number_of_cores */,
size_t /* max_payload_size */) {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
if (codec_settings == NULL) {
ALOGE << "NULL VideoCodec instance";
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
}
// Factory should guard against other codecs being used with us.
const VideoCodecType codec_type = GetCodecType();
RTC_CHECK(codec_settings->codecType == codec_type)
<< "Unsupported codec " << codec_settings->codecType << " for "
<< codec_type;
if (sw_fallback_required_) {
return WEBRTC_VIDEO_CODEC_OK;
}
codec_mode_ = codec_settings->mode;
int init_width = codec_settings->width;
int init_height = codec_settings->height;
// Scaling is optionally enabled for VP8 and VP9.
// TODO(pbos): Extract automaticResizeOn out of VP8 settings.
scale_ = false;
if (codec_type == kVideoCodecVP8) {
scale_ = codec_settings->VP8().automaticResizeOn;
} else if (codec_type == kVideoCodecVP9) {
scale_ = codec_settings->VP9().automaticResizeOn;
} else {
scale_ = true;
}
ALOGD << "InitEncode request: " << init_width << " x " << init_height;
ALOGD << "Encoder automatic resize " << (scale_ ? "enabled" : "disabled");
// Check allowed H.264 profile
profile_ = H264::Profile::kProfileBaseline;
if (codec_type == kVideoCodecH264) {
const rtc::Optional<H264::ProfileLevelId> profile_level_id =
H264::ParseSdpProfileLevelId(codec_.params);
RTC_DCHECK(profile_level_id);
profile_ = profile_level_id->profile;
ALOGD << "H.264 profile: " << profile_;
}
return InitEncodeInternal(
init_width, init_height, codec_settings->startBitrate,
codec_settings->maxFramerate, codec_settings->expect_encode_from_texture);
}
int32_t MediaCodecVideoEncoder::SetChannelParameters(uint32_t /* packet_loss */,
int64_t /* rtt */) {
return WEBRTC_VIDEO_CODEC_OK;
}
bool MediaCodecVideoEncoder::ResetCodec() {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
ALOGE << "Reset";
if (Release() != WEBRTC_VIDEO_CODEC_OK) {
ALOGE << "Releasing codec failed during reset.";
return false;
}
if (InitEncodeInternal(width_, height_, 0, 0, false) !=
WEBRTC_VIDEO_CODEC_OK) {
ALOGE << "Initializing encoder failed during reset.";
return false;
}
return true;
}
MediaCodecVideoEncoder::EncodeTask::EncodeTask(
rtc::WeakPtr<MediaCodecVideoEncoder> encoder)
: encoder_(encoder) {}
bool MediaCodecVideoEncoder::EncodeTask::Run() {
if (!encoder_) {
// Encoder was destroyed.
return true;
}
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_->encoder_queue_checker_);
JNIEnv* jni = AttachCurrentThreadIfNeeded();
ScopedLocalRefFrame local_ref_frame(jni);
if (!encoder_->inited_) {
encoder_->encode_task_ = std::unique_ptr<rtc::QueuedTask>(this);
return false;
}
// It would be nice to recover from a failure here if one happened, but it's
// unclear how to signal such a failure to the app, so instead we stay silent
// about it and let the next app-called API method reveal the borkedness.
encoder_->DeliverPendingOutputs(jni);
if (!encoder_) {
// Encoder can be destroyed in DeliverPendingOutputs.
return true;
}
// Call log statistics here so it's called even if no frames are being
// delivered.
encoder_->LogStatistics(false);
// If there aren't more frames to deliver, we can start polling at lower rate.
if (encoder_->input_frame_infos_.empty()) {
rtc::TaskQueue::Current()->PostDelayedTask(
std::unique_ptr<rtc::QueuedTask>(this), kMediaCodecPollNoFramesMs);
} else {
rtc::TaskQueue::Current()->PostDelayedTask(
std::unique_ptr<rtc::QueuedTask>(this), kMediaCodecPollMs);
}
return false;
}
bool MediaCodecVideoEncoder::ProcessHWError(
bool reset_if_fallback_unavailable) {
ALOGE << "ProcessHWError";
if (FindMatchingCodec(cricket::InternalEncoderFactory().supported_codecs(),
codec_)) {
ALOGE << "Fallback to SW encoder.";
sw_fallback_required_ = true;
return false;
} else if (reset_if_fallback_unavailable) {
ALOGE << "Reset encoder.";
return ResetCodec();
}
return false;
}
int32_t MediaCodecVideoEncoder::ProcessHWErrorOnEncode() {
ProcessHWError(true /* reset_if_fallback_unavailable */);
return sw_fallback_required_ ? WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE
: WEBRTC_VIDEO_CODEC_ERROR;
}
VideoCodecType MediaCodecVideoEncoder::GetCodecType() const {
return PayloadStringToCodecType(codec_.name);
}
int32_t MediaCodecVideoEncoder::InitEncodeInternal(int width,
int height,
int kbps,
int fps,
bool use_surface) {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
if (sw_fallback_required_) {
return WEBRTC_VIDEO_CODEC_OK;
}
RTC_CHECK(!use_surface || egl_context_ != nullptr) << "EGL context not set.";
JNIEnv* jni = AttachCurrentThreadIfNeeded();
ScopedLocalRefFrame local_ref_frame(jni);
const VideoCodecType codec_type = GetCodecType();
ALOGD << "InitEncodeInternal Type: " << static_cast<int>(codec_type) << ", "
<< width << " x " << height << ". Bitrate: " << kbps
<< " kbps. Fps: " << fps << ". Profile: " << profile_ << ".";
if (kbps == 0) {
kbps = last_set_bitrate_kbps_;
}
if (fps == 0) {
fps = MAX_VIDEO_FPS;
}
width_ = width;
height_ = height;
last_set_bitrate_kbps_ = kbps;
last_set_fps_ = (fps < MAX_VIDEO_FPS) ? fps : MAX_VIDEO_FPS;
yuv_size_ = width_ * height_ * 3 / 2;
frames_received_ = 0;
frames_encoded_ = 0;
frames_dropped_media_encoder_ = 0;
consecutive_full_queue_frame_drops_ = 0;
current_timestamp_us_ = 0;
stat_start_time_ms_ = rtc::TimeMillis();
current_frames_ = 0;
current_bytes_ = 0;
current_acc_qp_ = 0;
current_encoding_time_ms_ = 0;
last_input_timestamp_ms_ = -1;
last_output_timestamp_ms_ = -1;
output_timestamp_ = 0;
output_render_time_ms_ = 0;
input_frame_infos_.clear();
drop_next_input_frame_ = false;
use_surface_ = use_surface;
gof_.SetGofInfoVP9(TemporalStructureMode::kTemporalStructureMode1);
gof_idx_ = 0;
last_frame_received_ms_ = -1;
frames_received_since_last_key_ = kMinKeyFrameInterval;
// We enforce no extra stride/padding in the format creation step.
jobject j_video_codec_enum = JavaEnumFromIndexAndClassName(
jni, "MediaCodecVideoEncoder$VideoCodecType", codec_type);
const bool encode_status = jni->CallBooleanMethod(
*j_media_codec_video_encoder_, j_init_encode_method_, j_video_codec_enum,
profile_, width, height, kbps, fps,
(use_surface ? egl_context_ : nullptr));
if (!encode_status) {
ALOGE << "Failed to configure encoder.";
ProcessHWError(false /* reset_if_fallback_unavailable */);
return WEBRTC_VIDEO_CODEC_ERROR;
}
if (CheckException(jni)) {
ALOGE << "Exception in init encode.";
ProcessHWError(false /* reset_if_fallback_unavailable */);
return WEBRTC_VIDEO_CODEC_ERROR;
}
if (!use_surface) {
jobjectArray input_buffers = reinterpret_cast<jobjectArray>(
jni->CallObjectMethod(*j_media_codec_video_encoder_,
j_get_input_buffers_method_));
if (CheckException(jni)) {
ALOGE << "Exception in get input buffers.";
ProcessHWError(false /* reset_if_fallback_unavailable */);
return WEBRTC_VIDEO_CODEC_ERROR;
}
if (IsNull(jni, input_buffers)) {
ProcessHWError(false /* reset_if_fallback_unavailable */);
return WEBRTC_VIDEO_CODEC_ERROR;
}
switch (GetIntField(jni, *j_media_codec_video_encoder_,
j_color_format_field_)) {
case COLOR_FormatYUV420Planar:
encoder_fourcc_ = libyuv::FOURCC_YU12;
break;
case COLOR_FormatYUV420SemiPlanar:
case COLOR_QCOM_FormatYUV420SemiPlanar:
case COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m:
encoder_fourcc_ = libyuv::FOURCC_NV12;
break;
default:
LOG(LS_ERROR) << "Wrong color format.";
ProcessHWError(false /* reset_if_fallback_unavailable */);
return WEBRTC_VIDEO_CODEC_ERROR;
}
size_t num_input_buffers = jni->GetArrayLength(input_buffers);
RTC_CHECK(input_buffers_.empty())
<< "Unexpected double InitEncode without Release";
input_buffers_.resize(num_input_buffers);
for (size_t i = 0; i < num_input_buffers; ++i) {
input_buffers_[i] =
jni->NewGlobalRef(jni->GetObjectArrayElement(input_buffers, i));
int64_t yuv_buffer_capacity =
jni->GetDirectBufferCapacity(input_buffers_[i]);
if (CheckException(jni)) {
ALOGE << "Exception in get direct buffer capacity.";
ProcessHWError(false /* reset_if_fallback_unavailable */);
return WEBRTC_VIDEO_CODEC_ERROR;
}
RTC_CHECK(yuv_buffer_capacity >= yuv_size_) << "Insufficient capacity";
}
}
{
#if RTC_DCHECK_IS_ON
rtc::CritScope lock(&inited_crit_);
#endif
inited_ = true;
}
weak_factory_.reset(new rtc::WeakPtrFactory<MediaCodecVideoEncoder>(this));
encode_task_.reset(new EncodeTask(weak_factory_->GetWeakPtr()));
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t MediaCodecVideoEncoder::Encode(
const VideoFrame& frame,
const CodecSpecificInfo* /* codec_specific_info */,
const std::vector<FrameType>* frame_types) {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
if (sw_fallback_required_)
return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
JNIEnv* jni = AttachCurrentThreadIfNeeded();
ScopedLocalRefFrame local_ref_frame(jni);
const int64_t frame_input_time_ms = rtc::TimeMillis();
if (!inited_) {
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
}
bool send_key_frame = false;
if (codec_mode_ == kRealtimeVideo) {
++frames_received_since_last_key_;
int64_t now_ms = rtc::TimeMillis();
if (last_frame_received_ms_ != -1 &&
(now_ms - last_frame_received_ms_) > kFrameDiffThresholdMs) {
// Add limit to prevent triggering a key for every frame for very low
// framerates (e.g. if frame diff > kFrameDiffThresholdMs).
if (frames_received_since_last_key_ > kMinKeyFrameInterval) {
ALOGD << "Send key, frame diff: " << (now_ms - last_frame_received_ms_);
send_key_frame = true;
}
frames_received_since_last_key_ = 0;
}
last_frame_received_ms_ = now_ms;
}
frames_received_++;
if (!DeliverPendingOutputs(jni)) {
if (!ProcessHWError(true /* reset_if_fallback_unavailable */)) {
return sw_fallback_required_ ? WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE
: WEBRTC_VIDEO_CODEC_ERROR;
}
}
if (frames_encoded_ < kMaxEncodedLogFrames) {
ALOGD << "Encoder frame in # " << (frames_received_ - 1)
<< ". TS: " << static_cast<int>(current_timestamp_us_ / 1000)
<< ". Q: " << input_frame_infos_.size() << ". Fps: " << last_set_fps_
<< ". Kbps: " << last_set_bitrate_kbps_;
}
if (drop_next_input_frame_) {
ALOGW << "Encoder drop frame - failed callback.";
drop_next_input_frame_ = false;
current_timestamp_us_ += rtc::kNumMicrosecsPerSec / last_set_fps_;
frames_dropped_media_encoder_++;
return WEBRTC_VIDEO_CODEC_OK;
}
RTC_CHECK(frame_types->size() == 1) << "Unexpected stream count";
// Check if we accumulated too many frames in encoder input buffers and drop
// frame if so.
if (input_frame_infos_.size() > MAX_ENCODER_Q_SIZE) {
ALOGD << "Already " << input_frame_infos_.size()
<< " frames in the queue, dropping"
<< ". TS: " << static_cast<int>(current_timestamp_us_ / 1000)
<< ". Fps: " << last_set_fps_
<< ". Consecutive drops: " << consecutive_full_queue_frame_drops_;
current_timestamp_us_ += rtc::kNumMicrosecsPerSec / last_set_fps_;
consecutive_full_queue_frame_drops_++;
if (consecutive_full_queue_frame_drops_ >=
ENCODER_STALL_FRAMEDROP_THRESHOLD) {
ALOGE << "Encoder got stuck.";
return ProcessHWErrorOnEncode();
}
frames_dropped_media_encoder_++;
return WEBRTC_VIDEO_CODEC_OK;
}
consecutive_full_queue_frame_drops_ = 0;
rtc::scoped_refptr<VideoFrameBuffer> input_buffer(frame.video_frame_buffer());
VideoFrame input_frame(input_buffer, frame.timestamp(),
frame.render_time_ms(), frame.rotation());
if (!MaybeReconfigureEncoder(jni, input_frame)) {
ALOGE << "Failed to reconfigure encoder.";
return WEBRTC_VIDEO_CODEC_ERROR;
}
const bool key_frame =
frame_types->front() != kVideoFrameDelta || send_key_frame;
bool encode_status = true;
int j_input_buffer_index = -1;
if (!use_surface_) {
j_input_buffer_index = jni->CallIntMethod(*j_media_codec_video_encoder_,
j_dequeue_input_buffer_method_);
if (CheckException(jni)) {
ALOGE << "Exception in dequeu input buffer.";
return ProcessHWErrorOnEncode();
}
if (j_input_buffer_index == -1) {
// Video codec falls behind - no input buffer available.
ALOGW << "Encoder drop frame - no input buffers available";
if (frames_received_ > 1) {
current_timestamp_us_ += rtc::kNumMicrosecsPerSec / last_set_fps_;
frames_dropped_media_encoder_++;
} else {
// Input buffers are not ready after codec initialization, HW is still
// allocating thme - this is expected and should not result in drop
// frame report.
frames_received_ = 0;
}
return WEBRTC_VIDEO_CODEC_OK; // TODO(fischman): see webrtc bug 2887.
} else if (j_input_buffer_index == -2) {
return ProcessHWErrorOnEncode();
}
}
if (input_frame.video_frame_buffer()->type() !=
VideoFrameBuffer::Type::kNative) {
encode_status =
EncodeByteBuffer(jni, key_frame, input_frame, j_input_buffer_index);
} else {
AndroidVideoFrameBuffer* android_buffer =
static_cast<AndroidVideoFrameBuffer*>(
input_frame.video_frame_buffer().get());
switch (android_buffer->android_type()) {
case AndroidVideoFrameBuffer::AndroidType::kTextureBuffer:
encode_status = EncodeTexture(jni, key_frame, input_frame);
break;
case AndroidVideoFrameBuffer::AndroidType::kJavaBuffer:
encode_status = EncodeJavaFrame(
jni, key_frame, video_frame_factory_.ToJavaFrame(jni, input_frame),
j_input_buffer_index);
break;
default:
RTC_NOTREACHED();
return WEBRTC_VIDEO_CODEC_ERROR;
}
}
if (!encode_status) {
ALOGE << "Failed encode frame with timestamp: " << input_frame.timestamp();
return ProcessHWErrorOnEncode();
}
// Save input image timestamps for later output.
input_frame_infos_.emplace_back(frame_input_time_ms, input_frame.timestamp(),
input_frame.render_time_ms(),
input_frame.rotation());
last_input_timestamp_ms_ =
current_timestamp_us_ / rtc::kNumMicrosecsPerMillisec;
current_timestamp_us_ += rtc::kNumMicrosecsPerSec / last_set_fps_;
// Start the polling loop if it is not started.
if (encode_task_) {
rtc::TaskQueue::Current()->PostDelayedTask(std::move(encode_task_),
kMediaCodecPollMs);
}
if (!DeliverPendingOutputs(jni)) {
return ProcessHWErrorOnEncode();
}
return WEBRTC_VIDEO_CODEC_OK;
}
bool MediaCodecVideoEncoder::MaybeReconfigureEncoder(JNIEnv* jni,
const VideoFrame& frame) {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
bool is_texture = IsTextureFrame(jni, frame);
const bool reconfigure_due_to_format = is_texture != use_surface_;
const bool reconfigure_due_to_size =
frame.width() != width_ || frame.height() != height_;
if (reconfigure_due_to_format) {
ALOGD << "Reconfigure encoder due to format change. "
<< (use_surface_ ?
"Reconfiguring to encode from byte buffer." :
"Reconfiguring to encode from texture.");
LogStatistics(true);
}
if (reconfigure_due_to_size) {
ALOGW << "Reconfigure encoder due to frame resolution change from "
<< width_ << " x " << height_ << " to " << frame.width() << " x "
<< frame.height();
LogStatistics(true);
width_ = frame.width();
height_ = frame.height();
}
if (!reconfigure_due_to_format && !reconfigure_due_to_size)
return true;
Release();
return InitEncodeInternal(width_, height_, 0, 0, is_texture) ==
WEBRTC_VIDEO_CODEC_OK;
}
bool MediaCodecVideoEncoder::IsTextureFrame(JNIEnv* jni,
const VideoFrame& frame) {
if (frame.video_frame_buffer()->type() != VideoFrameBuffer::Type::kNative) {
return false;
}
AndroidVideoFrameBuffer* android_buffer =
static_cast<AndroidVideoFrameBuffer*>(frame.video_frame_buffer().get());
switch (android_buffer->android_type()) {
case AndroidVideoFrameBuffer::AndroidType::kTextureBuffer:
return true;
case AndroidVideoFrameBuffer::AndroidType::kJavaBuffer:
return jni->IsInstanceOf(static_cast<AndroidVideoBuffer*>(android_buffer)
->video_frame_buffer(),
*j_video_frame_texture_buffer_class_);
default:
RTC_NOTREACHED();
return false;
}
}
bool MediaCodecVideoEncoder::EncodeByteBuffer(JNIEnv* jni,
bool key_frame,
const VideoFrame& frame,
int input_buffer_index) {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
RTC_CHECK(!use_surface_);
rtc::scoped_refptr<I420BufferInterface> i420_buffer =
frame.video_frame_buffer()->ToI420();
if (!FillInputBuffer(jni, input_buffer_index, i420_buffer->DataY(),
i420_buffer->StrideY(), i420_buffer->DataU(),
i420_buffer->StrideU(), i420_buffer->DataV(),
i420_buffer->StrideV())) {
return false;
}
bool encode_status = jni->CallBooleanMethod(
*j_media_codec_video_encoder_, j_encode_buffer_method_, key_frame,
input_buffer_index, yuv_size_, current_timestamp_us_);
if (CheckException(jni)) {
ALOGE << "Exception in encode buffer.";
ProcessHWError(true /* reset_if_fallback_unavailable */);
return false;
}
return encode_status;
}
bool MediaCodecVideoEncoder::FillInputBuffer(JNIEnv* jni,
int input_buffer_index,
uint8_t const* buffer_y,
int stride_y,
uint8_t const* buffer_u,
int stride_u,
uint8_t const* buffer_v,
int stride_v) {
jobject j_input_buffer = input_buffers_[input_buffer_index];
uint8_t* yuv_buffer =
reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress(j_input_buffer));
if (CheckException(jni)) {
ALOGE << "Exception in get direct buffer address.";
ProcessHWError(true /* reset_if_fallback_unavailable */);
return false;
}
RTC_CHECK(yuv_buffer) << "Indirect buffer??";
RTC_CHECK(!libyuv::ConvertFromI420(buffer_y, stride_y, buffer_u, stride_u,
buffer_v, stride_v, yuv_buffer, width_,
width_, height_, encoder_fourcc_))
<< "ConvertFromI420 failed";
return true;
}
bool MediaCodecVideoEncoder::EncodeTexture(JNIEnv* jni,
bool key_frame,
const VideoFrame& frame) {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
RTC_CHECK(use_surface_);
NativeHandleImpl handle =
static_cast<AndroidTextureBuffer*>(frame.video_frame_buffer().get())
->native_handle_impl();
jfloatArray sampling_matrix = handle.sampling_matrix.ToJava(jni);
bool encode_status = jni->CallBooleanMethod(
*j_media_codec_video_encoder_, j_encode_texture_method_, key_frame,
handle.oes_texture_id, sampling_matrix, current_timestamp_us_);
if (CheckException(jni)) {
ALOGE << "Exception in encode texture.";
ProcessHWError(true /* reset_if_fallback_unavailable */);
return false;
}
return encode_status;
}
bool MediaCodecVideoEncoder::EncodeJavaFrame(JNIEnv* jni,
bool key_frame,
jobject frame,
int input_buffer_index) {
bool encode_status = jni->CallBooleanMethod(
*j_media_codec_video_encoder_, j_encode_frame_method_,
jlongFromPointer(this), key_frame, frame, input_buffer_index);
if (CheckException(jni)) {
ALOGE << "Exception in encode frame.";
ProcessHWError(true /* reset_if_fallback_unavailable */);
return false;
}
return encode_status;
}
int32_t MediaCodecVideoEncoder::RegisterEncodeCompleteCallback(
EncodedImageCallback* callback) {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
JNIEnv* jni = AttachCurrentThreadIfNeeded();
ScopedLocalRefFrame local_ref_frame(jni);
callback_ = callback;
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t MediaCodecVideoEncoder::Release() {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
if (!inited_) {
return WEBRTC_VIDEO_CODEC_OK;
}
JNIEnv* jni = AttachCurrentThreadIfNeeded();
ALOGD << "EncoderRelease: Frames received: " << frames_received_
<< ". Encoded: " << frames_encoded_
<< ". Dropped: " << frames_dropped_media_encoder_;
encode_task_.reset(nullptr);
weak_factory_.reset(nullptr);
ScopedLocalRefFrame local_ref_frame(jni);
for (size_t i = 0; i < input_buffers_.size(); ++i)
jni->DeleteGlobalRef(input_buffers_[i]);
input_buffers_.clear();
jni->CallVoidMethod(*j_media_codec_video_encoder_, j_release_method_);
if (CheckException(jni)) {
ALOGE << "Exception in release.";
ProcessHWError(false /* reset_if_fallback_unavailable */);
return WEBRTC_VIDEO_CODEC_ERROR;
}
{
#if RTC_DCHECK_IS_ON
rtc::CritScope lock(&inited_crit_);
#endif
inited_ = false;
}
use_surface_ = false;
ALOGD << "EncoderRelease done.";
// It's legal to move the encoder to another queue now.
encoder_queue_checker_.Detach();
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t MediaCodecVideoEncoder::SetRateAllocation(
const BitrateAllocation& rate_allocation,
uint32_t frame_rate) {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
const uint32_t new_bit_rate = rate_allocation.get_sum_kbps();
if (sw_fallback_required_)
return WEBRTC_VIDEO_CODEC_OK;
frame_rate =
(frame_rate < MAX_ALLOWED_VIDEO_FPS) ? frame_rate : MAX_ALLOWED_VIDEO_FPS;
if (last_set_bitrate_kbps_ == new_bit_rate && last_set_fps_ == frame_rate) {
return WEBRTC_VIDEO_CODEC_OK;
}
JNIEnv* jni = AttachCurrentThreadIfNeeded();
ScopedLocalRefFrame local_ref_frame(jni);
if (new_bit_rate > 0) {
last_set_bitrate_kbps_ = new_bit_rate;
}
if (frame_rate > 0) {
last_set_fps_ = frame_rate;
}
bool ret =
jni->CallBooleanMethod(*j_media_codec_video_encoder_, j_set_rates_method_,
last_set_bitrate_kbps_, last_set_fps_);
if (CheckException(jni) || !ret) {
ProcessHWError(true /* reset_if_fallback_unavailable */);
return sw_fallback_required_ ? WEBRTC_VIDEO_CODEC_OK
: WEBRTC_VIDEO_CODEC_ERROR;
}
return WEBRTC_VIDEO_CODEC_OK;
}
int MediaCodecVideoEncoder::GetOutputBufferInfoIndex(
JNIEnv* jni,
jobject j_output_buffer_info) {
return GetIntField(jni, j_output_buffer_info, j_info_index_field_);
}
jobject MediaCodecVideoEncoder::GetOutputBufferInfoBuffer(
JNIEnv* jni,
jobject j_output_buffer_info) {
return GetObjectField(jni, j_output_buffer_info, j_info_buffer_field_);
}
bool MediaCodecVideoEncoder::GetOutputBufferInfoIsKeyFrame(
JNIEnv* jni,
jobject j_output_buffer_info) {
return GetBooleanField(jni, j_output_buffer_info, j_info_is_key_frame_field_);
}
jlong MediaCodecVideoEncoder::GetOutputBufferInfoPresentationTimestampUs(
JNIEnv* jni,
jobject j_output_buffer_info) {
return GetLongField(
jni, j_output_buffer_info, j_info_presentation_timestamp_us_field_);
}
bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) {
RTC_DCHECK_CALLED_SEQUENTIALLY(&encoder_queue_checker_);
while (true) {
jobject j_output_buffer_info = jni->CallObjectMethod(
*j_media_codec_video_encoder_, j_dequeue_output_buffer_method_);
if (CheckException(jni)) {
ALOGE << "Exception in set dequeue output buffer.";
ProcessHWError(true /* reset_if_fallback_unavailable */);
return WEBRTC_VIDEO_CODEC_ERROR;
}
if (IsNull(jni, j_output_buffer_info)) {
break;
}
int output_buffer_index =
GetOutputBufferInfoIndex(jni, j_output_buffer_info);
if (output_buffer_index == -1) {
ProcessHWError(true /* reset_if_fallback_unavailable */);
return false;
}
// Get key and config frame flags.
jobject j_output_buffer =
GetOutputBufferInfoBuffer(jni, j_output_buffer_info);
bool key_frame = GetOutputBufferInfoIsKeyFrame(jni, j_output_buffer_info);
// Get frame timestamps from a queue - for non config frames only.
int64_t encoding_start_time_ms = 0;
int64_t frame_encoding_time_ms = 0;
last_output_timestamp_ms_ =
GetOutputBufferInfoPresentationTimestampUs(jni, j_output_buffer_info) /
rtc::kNumMicrosecsPerMillisec;
if (!input_frame_infos_.empty()) {
const InputFrameInfo& frame_info = input_frame_infos_.front();
output_timestamp_ = frame_info.frame_timestamp;
output_render_time_ms_ = frame_info.frame_render_time_ms;
output_rotation_ = frame_info.rotation;
encoding_start_time_ms = frame_info.encode_start_time;
input_frame_infos_.pop_front();
}
// Extract payload.
size_t payload_size = jni->GetDirectBufferCapacity(j_output_buffer);
uint8_t* payload = reinterpret_cast<uint8_t*>(
jni->GetDirectBufferAddress(j_output_buffer));
if (CheckException(jni)) {
ALOGE << "Exception in get direct buffer address.";
ProcessHWError(true /* reset_if_fallback_unavailable */);
return WEBRTC_VIDEO_CODEC_ERROR;
}
// Callback - return encoded frame.
const VideoCodecType codec_type = GetCodecType();
EncodedImageCallback::Result callback_result(
EncodedImageCallback::Result::OK);
if (callback_) {
std::unique_ptr<EncodedImage> image(
new EncodedImage(payload, payload_size, payload_size));
image->_encodedWidth = width_;
image->_encodedHeight = height_;
image->_timeStamp = output_timestamp_;
image->capture_time_ms_ = output_render_time_ms_;
image->rotation_ = output_rotation_;
image->content_type_ = (codec_mode_ == VideoCodecMode::kScreensharing)
? VideoContentType::SCREENSHARE
: VideoContentType::UNSPECIFIED;
image->timing_.flags = TimingFrameFlags::kInvalid;
image->_frameType = (key_frame ? kVideoFrameKey : kVideoFrameDelta);
image->_completeFrame = true;
CodecSpecificInfo info;
memset(&info, 0, sizeof(info));
info.codecType = codec_type;
if (codec_type == kVideoCodecVP8) {
info.codecSpecific.VP8.pictureId = picture_id_;
info.codecSpecific.VP8.nonReference = false;
info.codecSpecific.VP8.simulcastIdx = 0;
info.codecSpecific.VP8.temporalIdx = kNoTemporalIdx;
info.codecSpecific.VP8.layerSync = false;
info.codecSpecific.VP8.tl0PicIdx = kNoTl0PicIdx;
info.codecSpecific.VP8.keyIdx = kNoKeyIdx;
} else if (codec_type == kVideoCodecVP9) {
if (key_frame) {
gof_idx_ = 0;
}
info.codecSpecific.VP9.picture_id = picture_id_;
info.codecSpecific.VP9.inter_pic_predicted = key_frame ? false : true;
info.codecSpecific.VP9.flexible_mode = false;
info.codecSpecific.VP9.ss_data_available = key_frame ? true : false;
info.codecSpecific.VP9.tl0_pic_idx = tl0_pic_idx_++;
info.codecSpecific.VP9.temporal_idx = kNoTemporalIdx;
info.codecSpecific.VP9.spatial_idx = kNoSpatialIdx;
info.codecSpecific.VP9.temporal_up_switch = true;
info.codecSpecific.VP9.inter_layer_predicted = false;
info.codecSpecific.VP9.gof_idx =
static_cast<uint8_t>(gof_idx_++ % gof_.num_frames_in_gof);
info.codecSpecific.VP9.num_spatial_layers = 1;
info.codecSpecific.VP9.spatial_layer_resolution_present = false;
if (info.codecSpecific.VP9.ss_data_available) {
info.codecSpecific.VP9.spatial_layer_resolution_present = true;
info.codecSpecific.VP9.width[0] = width_;
info.codecSpecific.VP9.height[0] = height_;
info.codecSpecific.VP9.gof.CopyGofInfoVP9(gof_);
}
}
picture_id_ = (picture_id_ + 1) & 0x7FFF;
// Generate a header describing a single fragment.
RTPFragmentationHeader header;
memset(&header, 0, sizeof(header));
if (codec_type == kVideoCodecVP8 || codec_type == kVideoCodecVP9) {
header.VerifyAndAllocateFragmentationHeader(1);
header.fragmentationOffset[0] = 0;
header.fragmentationLength[0] = image->_length;
header.fragmentationPlType[0] = 0;
header.fragmentationTimeDiff[0] = 0;
if (codec_type == kVideoCodecVP8) {
int qp;
if (vp8::GetQp(payload, payload_size, &qp)) {
current_acc_qp_ += qp;
image->qp_ = qp;
}
} else if (codec_type == kVideoCodecVP9) {
int qp;
if (vp9::GetQp(payload, payload_size, &qp)) {
current_acc_qp_ += qp;
image->qp_ = qp;
}
}
} else if (codec_type == kVideoCodecH264) {
h264_bitstream_parser_.ParseBitstream(payload, payload_size);
int qp;
if (h264_bitstream_parser_.GetLastSliceQp(&qp)) {
current_acc_qp_ += qp;
image->qp_ = qp;
}
// For H.264 search for start codes.
const std::vector<H264::NaluIndex> nalu_idxs =
H264::FindNaluIndices(payload, payload_size);
if (nalu_idxs.empty()) {
ALOGE << "Start code is not found!";
ALOGE << "Data:" << image->_buffer[0] << " " << image->_buffer[1]
<< " " << image->_buffer[2] << " " << image->_buffer[3]
<< " " << image->_buffer[4] << " " << image->_buffer[5];
ProcessHWError(true /* reset_if_fallback_unavailable */);
return false;
}
header.VerifyAndAllocateFragmentationHeader(nalu_idxs.size());
for (size_t i = 0; i < nalu_idxs.size(); i++) {
header.fragmentationOffset[i] = nalu_idxs[i].payload_start_offset;
header.fragmentationLength[i] = nalu_idxs[i].payload_size;
header.fragmentationPlType[i] = 0;
header.fragmentationTimeDiff[i] = 0;
}
}
callback_result = callback_->OnEncodedImage(*image, &info, &header);
}
// Return output buffer back to the encoder.
bool success = jni->CallBooleanMethod(*j_media_codec_video_encoder_,
j_release_output_buffer_method_,
output_buffer_index);
if (CheckException(jni) || !success) {
ProcessHWError(true /* reset_if_fallback_unavailable */);
return false;
}
// Print per frame statistics.
if (encoding_start_time_ms > 0) {
frame_encoding_time_ms = rtc::TimeMillis() - encoding_start_time_ms;
}
if (frames_encoded_ < kMaxEncodedLogFrames) {
int current_latency = static_cast<int>(last_input_timestamp_ms_ -
last_output_timestamp_ms_);
ALOGD << "Encoder frame out # " << frames_encoded_
<< ". Key: " << key_frame << ". Size: " << payload_size
<< ". TS: " << static_cast<int>(last_output_timestamp_ms_)
<< ". Latency: " << current_latency
<< ". EncTime: " << frame_encoding_time_ms;
}
// Calculate and print encoding statistics - every 3 seconds.
frames_encoded_++;
current_frames_++;
current_bytes_ += payload_size;
current_encoding_time_ms_ += frame_encoding_time_ms;
LogStatistics(false);
// Errors in callback_result are currently ignored.
if (callback_result.drop_next_frame)
drop_next_input_frame_ = true;
}
return true;
}
void MediaCodecVideoEncoder::LogStatistics(bool force_log) {
int statistic_time_ms = rtc::TimeMillis() - stat_start_time_ms_;
if ((statistic_time_ms >= kMediaCodecStatisticsIntervalMs || force_log)
&& statistic_time_ms > 0) {
// Prevent division by zero.
int current_frames_divider = current_frames_ != 0 ? current_frames_ : 1;
int current_bitrate = current_bytes_ * 8 / statistic_time_ms;
int current_fps =
(current_frames_ * 1000 + statistic_time_ms / 2) / statistic_time_ms;
ALOGD << "Encoded frames: " << frames_encoded_ <<
". Bitrate: " << current_bitrate <<
", target: " << last_set_bitrate_kbps_ << " kbps" <<
", fps: " << current_fps <<
", encTime: " << (current_encoding_time_ms_ / current_frames_divider) <<
". QP: " << (current_acc_qp_ / current_frames_divider) <<
" for last " << statistic_time_ms << " ms.";
stat_start_time_ms_ = rtc::TimeMillis();
current_frames_ = 0;
current_bytes_ = 0;
current_acc_qp_ = 0;
current_encoding_time_ms_ = 0;
}
}
VideoEncoder::ScalingSettings MediaCodecVideoEncoder::GetScalingSettings()
const {
if (field_trial::IsEnabled(kCustomQPThresholdsFieldTrial)) {
const VideoCodecType codec_type = GetCodecType();
std::string experiment_string =
field_trial::FindFullName(kCustomQPThresholdsFieldTrial);
ALOGD << "QP custom thresholds: " << experiment_string << " for codec "
<< codec_type;
int low_vp8_qp_threshold;
int high_vp8_qp_threshold;
int low_h264_qp_threshold;
int high_h264_qp_threshold;
int parsed_values = sscanf(experiment_string.c_str(), "Enabled-%u,%u,%u,%u",
&low_vp8_qp_threshold, &high_vp8_qp_threshold,
&low_h264_qp_threshold, &high_h264_qp_threshold);
if (parsed_values == 4) {
RTC_CHECK_GT(high_vp8_qp_threshold, low_vp8_qp_threshold);
RTC_CHECK_GT(low_vp8_qp_threshold, 0);
RTC_CHECK_GT(high_h264_qp_threshold, low_h264_qp_threshold);
RTC_CHECK_GT(low_h264_qp_threshold, 0);
if (codec_type == kVideoCodecVP8) {
return VideoEncoder::ScalingSettings(scale_, low_vp8_qp_threshold,
high_vp8_qp_threshold);
} else if (codec_type == kVideoCodecH264) {
return VideoEncoder::ScalingSettings(scale_, low_h264_qp_threshold,
high_h264_qp_threshold);
}
}
}
return VideoEncoder::ScalingSettings(scale_);
}
const char* MediaCodecVideoEncoder::ImplementationName() const {
return "MediaCodec";
}
MediaCodecVideoEncoderFactory::MediaCodecVideoEncoderFactory()
: egl_context_(nullptr) {
JNIEnv* jni = AttachCurrentThreadIfNeeded();
ScopedLocalRefFrame local_ref_frame(jni);
jclass j_encoder_class = FindClass(jni, "org/webrtc/MediaCodecVideoEncoder");
jclass j_decoder_class = FindClass(jni, "org/webrtc/MediaCodecVideoDecoder");
supported_codecs_.clear();
bool is_vp8_hw_supported = jni->CallStaticBooleanMethod(
j_encoder_class,
GetStaticMethodID(jni, j_encoder_class, "isVp8HwSupported", "()Z"));
CHECK_EXCEPTION(jni);
if (is_vp8_hw_supported) {
ALOGD << "VP8 HW Encoder supported.";
supported_codecs_.push_back(cricket::VideoCodec(cricket::kVp8CodecName));
}
bool is_vp9_hw_supported = jni->CallStaticBooleanMethod(
j_encoder_class,
GetStaticMethodID(jni, j_encoder_class, "isVp9HwSupported", "()Z"));
CHECK_EXCEPTION(jni);
if (is_vp9_hw_supported) {
ALOGD << "VP9 HW Encoder supported.";
supported_codecs_.push_back(cricket::VideoCodec(cricket::kVp9CodecName));
}
supported_codecs_with_h264_hp_ = supported_codecs_;
// Check if high profile is supported by decoder. If yes, encoder can always
// fall back to baseline profile as a subset as high profile.
bool is_h264_high_profile_hw_supported = jni->CallStaticBooleanMethod(
j_decoder_class,
GetStaticMethodID(jni, j_decoder_class, "isH264HighProfileHwSupported",
"()Z"));
CHECK_EXCEPTION(jni);
if (is_h264_high_profile_hw_supported) {
ALOGD << "H.264 High Profile HW Encoder supported.";
// TODO(magjed): Enumerate actual level instead of using hardcoded level
// 3.1. Level 3.1 is 1280x720@30fps which is enough for now.
cricket::VideoCodec constrained_high(cricket::kH264CodecName);
const H264::ProfileLevelId constrained_high_profile(
H264::kProfileConstrainedHigh, H264::kLevel3_1);
constrained_high.SetParam(
cricket::kH264FmtpProfileLevelId,
*H264::ProfileLevelIdToString(constrained_high_profile));
constrained_high.SetParam(cricket::kH264FmtpLevelAsymmetryAllowed, "1");
constrained_high.SetParam(cricket::kH264FmtpPacketizationMode, "1");
supported_codecs_with_h264_hp_.push_back(constrained_high);
}
bool is_h264_hw_supported = jni->CallStaticBooleanMethod(
j_encoder_class,
GetStaticMethodID(jni, j_encoder_class, "isH264HwSupported", "()Z"));
CHECK_EXCEPTION(jni);
if (is_h264_hw_supported) {
ALOGD << "H.264 HW Encoder supported.";
// TODO(magjed): Push Constrained High profile as well when negotiation is
// ready, http://crbug/webrtc/6337. We can negotiate Constrained High
// profile as long as we have decode support for it and still send Baseline
// since Baseline is a subset of the High profile.
cricket::VideoCodec constrained_baseline(cricket::kH264CodecName);
const H264::ProfileLevelId constrained_baseline_profile(
H264::kProfileConstrainedBaseline, H264::kLevel3_1);
constrained_baseline.SetParam(
cricket::kH264FmtpProfileLevelId,
*H264::ProfileLevelIdToString(constrained_baseline_profile));
constrained_baseline.SetParam(cricket::kH264FmtpLevelAsymmetryAllowed, "1");
constrained_baseline.SetParam(cricket::kH264FmtpPacketizationMode, "1");
supported_codecs_.push_back(constrained_baseline);
supported_codecs_with_h264_hp_.push_back(constrained_baseline);
}
}
MediaCodecVideoEncoderFactory::~MediaCodecVideoEncoderFactory() {
ALOGD << "MediaCodecVideoEncoderFactory dtor";
if (egl_context_) {
JNIEnv* jni = AttachCurrentThreadIfNeeded();
jni->DeleteGlobalRef(egl_context_);
}
}
void MediaCodecVideoEncoderFactory::SetEGLContext(
JNIEnv* jni, jobject egl_context) {
ALOGD << "MediaCodecVideoEncoderFactory::SetEGLContext";
if (egl_context_) {
jni->DeleteGlobalRef(egl_context_);
egl_context_ = nullptr;
}
egl_context_ = jni->NewGlobalRef(egl_context);
if (CheckException(jni)) {
ALOGE << "error calling NewGlobalRef for EGL Context.";
}
}
VideoEncoder* MediaCodecVideoEncoderFactory::CreateVideoEncoder(
const cricket::VideoCodec& codec) {
if (supported_codecs().empty()) {
ALOGW << "No HW video encoder for codec " << codec.name;
return nullptr;
}
if (FindMatchingCodec(supported_codecs(), codec)) {
ALOGD << "Create HW video encoder for " << codec.name;
JNIEnv* jni = AttachCurrentThreadIfNeeded();
ScopedLocalRefFrame local_ref_frame(jni);
return new MediaCodecVideoEncoder(jni, codec, egl_context_);
}
ALOGW << "Can not find HW video encoder for type " << codec.name;
return nullptr;
}
const std::vector<cricket::VideoCodec>&
MediaCodecVideoEncoderFactory::supported_codecs() const {
if (field_trial::IsEnabled(kH264HighProfileFieldTrial)) {
return supported_codecs_with_h264_hp_;
} else {
return supported_codecs_;
}
}
void MediaCodecVideoEncoderFactory::DestroyVideoEncoder(VideoEncoder* encoder) {
ALOGD << "Destroy video encoder.";
delete encoder;
}
JNI_FUNCTION_DECLARATION(void,
MediaCodecVideoEncoder_nativeFillBuffer,
JNIEnv* jni,
jclass,
jlong native_encoder,
jint input_buffer,
jobject j_buffer_y,
jint stride_y,
jobject j_buffer_u,
jint stride_u,
jobject j_buffer_v,
jint stride_v) {
uint8_t* buffer_y =
static_cast<uint8_t*>(jni->GetDirectBufferAddress(j_buffer_y));
uint8_t* buffer_u =
static_cast<uint8_t*>(jni->GetDirectBufferAddress(j_buffer_u));
uint8_t* buffer_v =
static_cast<uint8_t*>(jni->GetDirectBufferAddress(j_buffer_v));
RTC_DCHECK(buffer_y) << "GetDirectBufferAddress returned null. Ensure that "
"getDataY returns a direct ByteBuffer.";
RTC_DCHECK(buffer_u) << "GetDirectBufferAddress returned null. Ensure that "
"getDataU returns a direct ByteBuffer.";
RTC_DCHECK(buffer_v) << "GetDirectBufferAddress returned null. Ensure that "
"getDataV returns a direct ByteBuffer.";
reinterpret_cast<MediaCodecVideoEncoder*>(native_encoder)
->FillInputBuffer(jni, input_buffer, buffer_y, stride_y, buffer_u,
stride_u, buffer_v, stride_v);
}
} // namespace jni
} // namespace webrtc