/* * Copyright 2017 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. */ #include "sdk/android/src/jni/video_encoder_wrapper.h" #include #include "common_video/h264/h264_common.h" #include "modules/video_coding/include/video_codec_interface.h" #include "modules/video_coding/include/video_error_codes.h" #include "modules/video_coding/utility/vp8_header_parser.h" #include "modules/video_coding/utility/vp9_uncompressed_header_parser.h" #include "rtc_base/logging.h" #include "rtc_base/task_utils/to_queued_task.h" #include "rtc_base/time_utils.h" #include "sdk/android/generated_video_jni/VideoEncoderWrapper_jni.h" #include "sdk/android/generated_video_jni/VideoEncoder_jni.h" #include "sdk/android/native_api/jni/class_loader.h" #include "sdk/android/native_api/jni/java_types.h" #include "sdk/android/src/jni/encoded_image.h" #include "sdk/android/src/jni/video_codec_status.h" namespace webrtc { namespace jni { VideoEncoderWrapper::VideoEncoderWrapper(JNIEnv* jni, const JavaRef& j_encoder) : encoder_(jni, j_encoder), int_array_class_(GetClass(jni, "[I")) { initialized_ = false; num_resets_ = 0; // Get bitrate thresholds in the constructor. This is a static property of the // encoder and is expected to be available before it is initialized. encoder_info_.resolution_bitrate_thresholds = GetResolutionBitrateThresholds(jni); } VideoEncoderWrapper::~VideoEncoderWrapper() = default; int VideoEncoderWrapper::InitEncode(const VideoCodec* codec_settings, const Settings& settings) { JNIEnv* jni = AttachCurrentThreadIfNeeded(); codec_settings_ = *codec_settings; capabilities_ = settings.capabilities; number_of_cores_ = settings.number_of_cores; num_resets_ = 0; { rtc::CritScope lock(&encoder_queue_crit_); encoder_queue_ = TaskQueueBase::Current(); } return InitEncodeInternal(jni); } int32_t VideoEncoderWrapper::InitEncodeInternal(JNIEnv* jni) { bool automatic_resize_on; switch (codec_settings_.codecType) { case kVideoCodecVP8: automatic_resize_on = codec_settings_.VP8()->automaticResizeOn; break; case kVideoCodecVP9: automatic_resize_on = codec_settings_.VP9()->automaticResizeOn; gof_.SetGofInfoVP9(TemporalStructureMode::kTemporalStructureMode1); gof_idx_ = 0; break; default: automatic_resize_on = true; } RTC_DCHECK(capabilities_); ScopedJavaLocalRef capabilities = Java_Capabilities_Constructor(jni, capabilities_->loss_notification); ScopedJavaLocalRef settings = Java_Settings_Constructor( jni, number_of_cores_, codec_settings_.width, codec_settings_.height, static_cast(codec_settings_.startBitrate), static_cast(codec_settings_.maxFramerate), static_cast(codec_settings_.numberOfSimulcastStreams), automatic_resize_on, capabilities); ScopedJavaLocalRef callback = Java_VideoEncoderWrapper_createEncoderCallback(jni, jlongFromPointer(this)); int32_t status = JavaToNativeVideoCodecStatus( jni, Java_VideoEncoder_initEncode(jni, encoder_, settings, callback)); RTC_LOG(LS_INFO) << "initEncode: " << status; encoder_info_.supports_native_handle = true; encoder_info_.implementation_name = GetImplementationName(jni); encoder_info_.scaling_settings = GetScalingSettingsInternal(jni); encoder_info_.is_hardware_accelerated = IsHardwareVideoEncoder(jni, encoder_); encoder_info_.has_internal_source = false; if (status == WEBRTC_VIDEO_CODEC_OK) { initialized_ = true; } return status; } int32_t VideoEncoderWrapper::RegisterEncodeCompleteCallback( EncodedImageCallback* callback) { callback_ = callback; return WEBRTC_VIDEO_CODEC_OK; } int32_t VideoEncoderWrapper::Release() { JNIEnv* jni = AttachCurrentThreadIfNeeded(); int32_t status = JavaToNativeVideoCodecStatus( jni, Java_VideoEncoder_release(jni, encoder_)); RTC_LOG(LS_INFO) << "release: " << status; frame_extra_infos_.clear(); initialized_ = false; { rtc::CritScope lock(&encoder_queue_crit_); encoder_queue_ = nullptr; } return status; } int32_t VideoEncoderWrapper::Encode( const VideoFrame& frame, const std::vector* frame_types) { if (!initialized_) { // Most likely initializing the codec failed. return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; } JNIEnv* jni = AttachCurrentThreadIfNeeded(); // Construct encode info. ScopedJavaLocalRef j_frame_types = NativeToJavaFrameTypeArray(jni, *frame_types); ScopedJavaLocalRef encode_info = Java_EncodeInfo_Constructor(jni, j_frame_types); FrameExtraInfo info; info.capture_time_ns = frame.timestamp_us() * rtc::kNumNanosecsPerMicrosec; info.timestamp_rtp = frame.timestamp(); frame_extra_infos_.push_back(info); ScopedJavaLocalRef j_frame = NativeToJavaVideoFrame(jni, frame); ScopedJavaLocalRef ret = Java_VideoEncoder_encode(jni, encoder_, j_frame, encode_info); ReleaseJavaVideoFrame(jni, j_frame); return HandleReturnCode(jni, ret, "encode"); } void VideoEncoderWrapper::SetRates(const RateControlParameters& parameters) { JNIEnv* jni = AttachCurrentThreadIfNeeded(); ScopedJavaLocalRef j_bitrate_allocation = ToJavaBitrateAllocation(jni, parameters.bitrate); ScopedJavaLocalRef ret = Java_VideoEncoder_setRateAllocation( jni, encoder_, j_bitrate_allocation, (jint)(parameters.framerate_fps + 0.5)); HandleReturnCode(jni, ret, "setRateAllocation"); } VideoEncoder::EncoderInfo VideoEncoderWrapper::GetEncoderInfo() const { return encoder_info_; } VideoEncoderWrapper::ScalingSettings VideoEncoderWrapper::GetScalingSettingsInternal(JNIEnv* jni) const { ScopedJavaLocalRef j_scaling_settings = Java_VideoEncoder_getScalingSettings(jni, encoder_); bool isOn = Java_VideoEncoderWrapper_getScalingSettingsOn(jni, j_scaling_settings); if (!isOn) return ScalingSettings::kOff; absl::optional low = JavaToNativeOptionalInt( jni, Java_VideoEncoderWrapper_getScalingSettingsLow(jni, j_scaling_settings)); absl::optional high = JavaToNativeOptionalInt( jni, Java_VideoEncoderWrapper_getScalingSettingsHigh(jni, j_scaling_settings)); if (low && high) return ScalingSettings(*low, *high); switch (codec_settings_.codecType) { case kVideoCodecVP8: { // Same as in vp8_impl.cc. static const int kLowVp8QpThreshold = 29; static const int kHighVp8QpThreshold = 95; return ScalingSettings(low.value_or(kLowVp8QpThreshold), high.value_or(kHighVp8QpThreshold)); } case kVideoCodecVP9: { // QP is obtained from VP9-bitstream, so the QP corresponds to the // bitstream range of [0, 255] and not the user-level range of [0,63]. static const int kLowVp9QpThreshold = 96; static const int kHighVp9QpThreshold = 185; return VideoEncoder::ScalingSettings(kLowVp9QpThreshold, kHighVp9QpThreshold); } case kVideoCodecH264: { // Same as in h264_encoder_impl.cc. static const int kLowH264QpThreshold = 24; static const int kHighH264QpThreshold = 37; return ScalingSettings(low.value_or(kLowH264QpThreshold), high.value_or(kHighH264QpThreshold)); } default: return ScalingSettings::kOff; } } std::vector VideoEncoderWrapper::GetResolutionBitrateThresholds(JNIEnv* jni) const { std::vector resolution_bitrate_thresholds; ScopedJavaLocalRef j_thresholds_array = Java_VideoEncoder_getResolutionBitrateThresholds(jni, encoder_); const jsize num_thresholds = jni->GetArrayLength(j_thresholds_array.obj()); for (int i = 0; i < num_thresholds; ++i) { ScopedJavaLocalRef j_thresholds = ScopedJavaLocalRef( jni, jni->GetObjectArrayElement(j_thresholds_array.obj(), i)); jint frame_size_pixels = Java_ResolutionBitrateThresholds_getFrameSizePixels(jni, j_thresholds); jint min_start_bitrate_bps = Java_ResolutionBitrateThresholds_getMinStartBitrateBps(jni, j_thresholds); jint min_bitrate_bps = Java_ResolutionBitrateThresholds_getMinBitrateBps(jni, j_thresholds); jint max_bitrate_bps = Java_ResolutionBitrateThresholds_getMaxBitrateBps(jni, j_thresholds); resolution_bitrate_thresholds.push_back( VideoEncoder::ResolutionBitrateThresholds( frame_size_pixels, min_start_bitrate_bps, min_bitrate_bps, max_bitrate_bps)); } return resolution_bitrate_thresholds; } void VideoEncoderWrapper::OnEncodedFrame( JNIEnv* jni, const JavaRef& j_caller, const JavaRef& j_encoded_image) { EncodedImage frame = JavaToNativeEncodedImage(jni, j_encoded_image); int64_t capture_time_ns = GetJavaEncodedImageCaptureTimeNs(jni, j_encoded_image); { rtc::CritScope lock(&encoder_queue_crit_); if (encoder_queue_ != nullptr) { encoder_queue_->PostTask(ToQueuedTask([this, frame, capture_time_ns]() { // Encoded frames are delivered in the order received, but some of them // may be dropped, so remove records of frames older than the current // one. // // NOTE: if the current frame is associated with Encoder A, in the time // since this frame was received, Encoder A could have been // Release()'ed, Encoder B InitEncode()'ed (due to reuse of Encoder A), // and frames received by Encoder B. Thus there may be frame_extra_infos // entries that don't belong to us, and we need to be careful not to // remove them. Removing only those entries older than the current frame // provides this guarantee. while (!frame_extra_infos_.empty() && frame_extra_infos_.front().capture_time_ns < capture_time_ns) { frame_extra_infos_.pop_front(); } if (frame_extra_infos_.empty() || frame_extra_infos_.front().capture_time_ns != capture_time_ns) { RTC_LOG(LS_WARNING) << "Java encoder produced an unexpected frame with timestamp: " << capture_time_ns; return; } FrameExtraInfo frame_extra_info = std::move(frame_extra_infos_.front()); frame_extra_infos_.pop_front(); // This is a bit subtle. The |frame| variable from the lambda capture is // const. Which implies that (i) we need to make a copy to be able to // write to the metadata, and (ii) we should avoid using the .data() // method (including implicit conversion to ArrayView) on the non-const // copy, since that would trigget a copy operation on the underlying // CopyOnWriteBuffer. EncodedImage frame_copy = frame; frame_copy.SetTimestamp(frame_extra_info.timestamp_rtp); frame_copy.capture_time_ms_ = capture_time_ns / rtc::kNumNanosecsPerMillisec; RTPFragmentationHeader header = ParseFragmentationHeader(frame); if (frame_copy.qp_ < 0) frame_copy.qp_ = ParseQp(frame); CodecSpecificInfo info(ParseCodecSpecificInfo(frame)); callback_->OnEncodedImage(frame_copy, &info, &header); })); } } } int32_t VideoEncoderWrapper::HandleReturnCode(JNIEnv* jni, const JavaRef& j_value, const char* method_name) { int32_t value = JavaToNativeVideoCodecStatus(jni, j_value); if (value >= 0) { // OK or NO_OUTPUT return value; } RTC_LOG(LS_WARNING) << method_name << ": " << value; if (value == WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE || value == WEBRTC_VIDEO_CODEC_UNINITIALIZED) { // Critical error. RTC_LOG(LS_WARNING) << "Java encoder requested software fallback."; return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; } // Try resetting the codec. if (Release() == WEBRTC_VIDEO_CODEC_OK && InitEncodeInternal(jni) == WEBRTC_VIDEO_CODEC_OK) { RTC_LOG(LS_WARNING) << "Reset Java encoder."; return WEBRTC_VIDEO_CODEC_ERROR; } RTC_LOG(LS_WARNING) << "Unable to reset Java encoder."; return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; } RTPFragmentationHeader VideoEncoderWrapper::ParseFragmentationHeader( rtc::ArrayView buffer) { RTPFragmentationHeader header; if (codec_settings_.codecType == kVideoCodecH264) { h264_bitstream_parser_.ParseBitstream(buffer.data(), buffer.size()); // For H.264 search for start codes. const std::vector nalu_idxs = H264::FindNaluIndices(buffer.data(), buffer.size()); if (nalu_idxs.empty()) { RTC_LOG(LS_ERROR) << "Start code is not found!"; RTC_LOG(LS_ERROR) << "Data:" << buffer[0] << " " << buffer[1] << " " << buffer[2] << " " << buffer[3] << " " << buffer[4] << " " << buffer[5]; } 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; } } else { // Generate a header describing a single fragment. header.VerifyAndAllocateFragmentationHeader(1); header.fragmentationOffset[0] = 0; header.fragmentationLength[0] = buffer.size(); } return header; } int VideoEncoderWrapper::ParseQp(rtc::ArrayView buffer) { int qp; bool success; switch (codec_settings_.codecType) { case kVideoCodecVP8: success = vp8::GetQp(buffer.data(), buffer.size(), &qp); break; case kVideoCodecVP9: success = vp9::GetQp(buffer.data(), buffer.size(), &qp); break; case kVideoCodecH264: success = h264_bitstream_parser_.GetLastSliceQp(&qp); break; default: // Default is to not provide QP. success = false; break; } return success ? qp : -1; // -1 means unknown QP. } CodecSpecificInfo VideoEncoderWrapper::ParseCodecSpecificInfo( const EncodedImage& frame) { const bool key_frame = frame._frameType == VideoFrameType::kVideoFrameKey; CodecSpecificInfo info; info.codecType = codec_settings_.codecType; switch (codec_settings_.codecType) { case kVideoCodecVP8: info.codecSpecific.VP8.nonReference = false; info.codecSpecific.VP8.temporalIdx = kNoTemporalIdx; info.codecSpecific.VP8.layerSync = false; info.codecSpecific.VP8.keyIdx = kNoKeyIdx; break; case kVideoCodecVP9: if (key_frame) { gof_idx_ = 0; } 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.temporal_idx = kNoTemporalIdx; info.codecSpecific.VP9.temporal_up_switch = true; info.codecSpecific.VP9.inter_layer_predicted = false; info.codecSpecific.VP9.gof_idx = static_cast(gof_idx_++ % gof_.num_frames_in_gof); info.codecSpecific.VP9.num_spatial_layers = 1; info.codecSpecific.VP9.first_frame_in_picture = true; info.codecSpecific.VP9.end_of_picture = true; 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] = frame._encodedWidth; info.codecSpecific.VP9.height[0] = frame._encodedHeight; info.codecSpecific.VP9.gof.CopyGofInfoVP9(gof_); } break; default: break; } return info; } ScopedJavaLocalRef VideoEncoderWrapper::ToJavaBitrateAllocation( JNIEnv* jni, const VideoBitrateAllocation& allocation) { ScopedJavaLocalRef j_allocation_array( jni, jni->NewObjectArray(kMaxSpatialLayers, int_array_class_.obj(), nullptr /* initial */)); for (int spatial_i = 0; spatial_i < kMaxSpatialLayers; ++spatial_i) { std::array spatial_layer; for (int temporal_i = 0; temporal_i < kMaxTemporalStreams; ++temporal_i) { spatial_layer[temporal_i] = allocation.GetBitrate(spatial_i, temporal_i); } ScopedJavaLocalRef j_array_spatial_layer = NativeToJavaIntArray(jni, spatial_layer); jni->SetObjectArrayElement(j_allocation_array.obj(), spatial_i, j_array_spatial_layer.obj()); } return Java_BitrateAllocation_Constructor(jni, j_allocation_array); } std::string VideoEncoderWrapper::GetImplementationName(JNIEnv* jni) const { return JavaToStdString( jni, Java_VideoEncoder_getImplementationName(jni, encoder_)); } std::unique_ptr JavaToNativeVideoEncoder( JNIEnv* jni, const JavaRef& j_encoder) { const jlong native_encoder = Java_VideoEncoder_createNativeVideoEncoder(jni, j_encoder); VideoEncoder* encoder; if (native_encoder == 0) { encoder = new VideoEncoderWrapper(jni, j_encoder); } else { encoder = reinterpret_cast(native_encoder); } return std::unique_ptr(encoder); } bool IsHardwareVideoEncoder(JNIEnv* jni, const JavaRef& j_encoder) { return Java_VideoEncoder_isHardwareEncoder(jni, j_encoder); } } // namespace jni } // namespace webrtc