| /* |
| * 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/videodecoderwrapper.h" |
| |
| #include "api/video/video_frame.h" |
| #include "modules/video_coding/include/video_codec_interface.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/timeutils.h" |
| #include "sdk/android/generated_video_jni/jni/VideoDecoderWrapper_jni.h" |
| #include "sdk/android/generated_video_jni/jni/VideoDecoder_jni.h" |
| #include "sdk/android/native_api/jni/java_types.h" |
| #include "sdk/android/src/jni/encodedimage.h" |
| #include "sdk/android/src/jni/videocodecstatus.h" |
| #include "sdk/android/src/jni/videoframe.h" |
| |
| namespace webrtc { |
| namespace jni { |
| |
| namespace { |
| // RTP timestamps are 90 kHz. |
| const int64_t kNumRtpTicksPerMillisec = 90000 / rtc::kNumMillisecsPerSec; |
| |
| template <typename Dst, typename Src> |
| inline absl::optional<Dst> cast_optional(const absl::optional<Src>& value) { |
| return value ? absl::optional<Dst>(rtc::dchecked_cast<Dst, Src>(*value)) |
| : absl::nullopt; |
| } |
| } // namespace |
| |
| VideoDecoderWrapper::VideoDecoderWrapper(JNIEnv* jni, |
| const JavaRef<jobject>& decoder) |
| : decoder_(jni, decoder), |
| implementation_name_(JavaToStdString( |
| jni, |
| Java_VideoDecoder_getImplementationName(jni, decoder))), |
| initialized_(false), |
| qp_parsing_enabled_(true) // QP parsing starts enabled and we disable it |
| // if the decoder provides frames. |
| |
| { |
| decoder_thread_checker_.DetachFromThread(); |
| } |
| |
| VideoDecoderWrapper::~VideoDecoderWrapper() = default; |
| |
| int32_t VideoDecoderWrapper::InitDecode(const VideoCodec* codec_settings, |
| int32_t number_of_cores) { |
| RTC_DCHECK_RUN_ON(&decoder_thread_checker_); |
| JNIEnv* jni = AttachCurrentThreadIfNeeded(); |
| codec_settings_ = *codec_settings; |
| number_of_cores_ = number_of_cores; |
| return InitDecodeInternal(jni); |
| } |
| |
| int32_t VideoDecoderWrapper::InitDecodeInternal(JNIEnv* jni) { |
| ScopedJavaLocalRef<jobject> settings = Java_Settings_Constructor( |
| jni, number_of_cores_, codec_settings_.width, codec_settings_.height); |
| |
| ScopedJavaLocalRef<jobject> callback = |
| Java_VideoDecoderWrapper_createDecoderCallback(jni, |
| jlongFromPointer(this)); |
| |
| int32_t status = JavaToNativeVideoCodecStatus( |
| jni, Java_VideoDecoder_initDecode(jni, decoder_, settings, callback)); |
| RTC_LOG(LS_INFO) << "initDecode: " << status; |
| if (status == WEBRTC_VIDEO_CODEC_OK) { |
| initialized_ = true; |
| } |
| |
| // The decoder was reinitialized so re-enable the QP parsing in case it stops |
| // providing QP values. |
| qp_parsing_enabled_ = true; |
| |
| return status; |
| } |
| |
| int32_t VideoDecoderWrapper::Decode( |
| const EncodedImage& image_param, |
| bool missing_frames, |
| const CodecSpecificInfo* codec_specific_info, |
| int64_t render_time_ms) { |
| RTC_DCHECK_RUN_ON(&decoder_thread_checker_); |
| if (!initialized_) { |
| // Most likely initializing the codec failed. |
| return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; |
| } |
| |
| // Make a mutable copy so we can modify the timestamp. |
| EncodedImage input_image(image_param); |
| // We use RTP timestamp for capture time because capture_time_ms_ is always 0. |
| input_image.capture_time_ms_ = |
| input_image.Timestamp() / kNumRtpTicksPerMillisec; |
| |
| FrameExtraInfo frame_extra_info; |
| frame_extra_info.timestamp_ns = |
| input_image.capture_time_ms_ * rtc::kNumNanosecsPerMillisec; |
| frame_extra_info.timestamp_rtp = input_image.Timestamp(); |
| frame_extra_info.timestamp_ntp = input_image.ntp_time_ms_; |
| frame_extra_info.qp = |
| qp_parsing_enabled_ ? ParseQP(input_image) : absl::nullopt; |
| { |
| rtc::CritScope cs(&frame_extra_infos_lock_); |
| frame_extra_infos_.push_back(frame_extra_info); |
| } |
| |
| JNIEnv* env = AttachCurrentThreadIfNeeded(); |
| ScopedJavaLocalRef<jobject> jinput_image = |
| NativeToJavaEncodedImage(env, input_image); |
| ScopedJavaLocalRef<jobject> decode_info; |
| ScopedJavaLocalRef<jobject> ret = |
| Java_VideoDecoder_decode(env, decoder_, jinput_image, decode_info); |
| return HandleReturnCode(env, ret, "decode"); |
| } |
| |
| int32_t VideoDecoderWrapper::RegisterDecodeCompleteCallback( |
| DecodedImageCallback* callback) { |
| RTC_DCHECK_RUNS_SERIALIZED(&callback_race_checker_); |
| callback_ = callback; |
| return WEBRTC_VIDEO_CODEC_OK; |
| } |
| |
| int32_t VideoDecoderWrapper::Release() { |
| JNIEnv* jni = AttachCurrentThreadIfNeeded(); |
| int32_t status = JavaToNativeVideoCodecStatus( |
| jni, Java_VideoDecoder_release(jni, decoder_)); |
| RTC_LOG(LS_INFO) << "release: " << status; |
| { |
| rtc::CritScope cs(&frame_extra_infos_lock_); |
| frame_extra_infos_.clear(); |
| } |
| initialized_ = false; |
| // It is allowed to reinitialize the codec on a different thread. |
| decoder_thread_checker_.DetachFromThread(); |
| return status; |
| } |
| |
| bool VideoDecoderWrapper::PrefersLateDecoding() const { |
| JNIEnv* jni = AttachCurrentThreadIfNeeded(); |
| return Java_VideoDecoder_getPrefersLateDecoding(jni, decoder_); |
| } |
| |
| const char* VideoDecoderWrapper::ImplementationName() const { |
| return implementation_name_.c_str(); |
| } |
| |
| void VideoDecoderWrapper::OnDecodedFrame( |
| JNIEnv* env, |
| const JavaRef<jobject>& j_caller, |
| const JavaRef<jobject>& j_frame, |
| const JavaRef<jobject>& j_decode_time_ms, |
| const JavaRef<jobject>& j_qp) { |
| RTC_DCHECK_RUNS_SERIALIZED(&callback_race_checker_); |
| const int64_t timestamp_ns = GetJavaVideoFrameTimestampNs(env, j_frame); |
| |
| FrameExtraInfo frame_extra_info; |
| { |
| rtc::CritScope cs(&frame_extra_infos_lock_); |
| |
| do { |
| if (frame_extra_infos_.empty()) { |
| RTC_LOG(LS_WARNING) |
| << "Java decoder produced an unexpected frame: " << timestamp_ns; |
| return; |
| } |
| |
| frame_extra_info = frame_extra_infos_.front(); |
| frame_extra_infos_.pop_front(); |
| // If the decoder might drop frames so iterate through the queue until we |
| // find a matching timestamp. |
| } while (frame_extra_info.timestamp_ns != timestamp_ns); |
| } |
| |
| VideoFrame frame = |
| JavaToNativeFrame(env, j_frame, frame_extra_info.timestamp_rtp); |
| frame.set_ntp_time_ms(frame_extra_info.timestamp_ntp); |
| |
| absl::optional<int32_t> decoding_time_ms = |
| JavaToNativeOptionalInt(env, j_decode_time_ms); |
| |
| absl::optional<uint8_t> decoder_qp = |
| cast_optional<uint8_t, int32_t>(JavaToNativeOptionalInt(env, j_qp)); |
| // If the decoder provides QP values itself, no need to parse the bitstream. |
| // Enable QP parsing if decoder does not provide QP values itself. |
| qp_parsing_enabled_ = !decoder_qp.has_value(); |
| callback_->Decoded(frame, decoding_time_ms, |
| decoder_qp ? decoder_qp : frame_extra_info.qp); |
| } |
| |
| VideoDecoderWrapper::FrameExtraInfo::FrameExtraInfo() = default; |
| VideoDecoderWrapper::FrameExtraInfo::FrameExtraInfo(const FrameExtraInfo&) = |
| default; |
| VideoDecoderWrapper::FrameExtraInfo::~FrameExtraInfo() = default; |
| |
| int32_t VideoDecoderWrapper::HandleReturnCode(JNIEnv* jni, |
| const JavaRef<jobject>& 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 decoder requested software fallback."; |
| return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; |
| } |
| |
| // Try resetting the codec. |
| if (Release() == WEBRTC_VIDEO_CODEC_OK && |
| InitDecodeInternal(jni) == WEBRTC_VIDEO_CODEC_OK) { |
| RTC_LOG(LS_WARNING) << "Reset Java decoder."; |
| return WEBRTC_VIDEO_CODEC_ERROR; |
| } |
| |
| RTC_LOG(LS_WARNING) << "Unable to reset Java decoder."; |
| return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; |
| } |
| |
| absl::optional<uint8_t> VideoDecoderWrapper::ParseQP( |
| const EncodedImage& input_image) { |
| if (input_image.qp_ != -1) { |
| return input_image.qp_; |
| } |
| |
| absl::optional<uint8_t> qp; |
| switch (codec_settings_.codecType) { |
| case kVideoCodecVP8: { |
| int qp_int; |
| if (vp8::GetQp(input_image._buffer, input_image._length, &qp_int)) { |
| qp = qp_int; |
| } |
| break; |
| } |
| case kVideoCodecVP9: { |
| int qp_int; |
| if (vp9::GetQp(input_image._buffer, input_image._length, &qp_int)) { |
| qp = qp_int; |
| } |
| break; |
| } |
| case kVideoCodecH264: { |
| h264_bitstream_parser_.ParseBitstream(input_image._buffer, |
| input_image._length); |
| int qp_int; |
| if (h264_bitstream_parser_.GetLastSliceQp(&qp_int)) { |
| qp = qp_int; |
| } |
| break; |
| } |
| default: |
| break; // Default is to not provide QP. |
| } |
| return qp; |
| } |
| |
| std::unique_ptr<VideoDecoder> JavaToNativeVideoDecoder( |
| JNIEnv* jni, |
| const JavaRef<jobject>& j_decoder) { |
| const jlong native_decoder = |
| Java_VideoDecoder_createNativeVideoDecoder(jni, j_decoder); |
| VideoDecoder* decoder; |
| if (native_decoder == 0) { |
| decoder = new VideoDecoderWrapper(jni, j_decoder); |
| } else { |
| decoder = reinterpret_cast<VideoDecoder*>(native_decoder); |
| } |
| return std::unique_ptr<VideoDecoder>(decoder); |
| } |
| |
| } // namespace jni |
| } // namespace webrtc |