The current Key Frame request system doesn't take into account failed decryptions and this can lead to WebRTC spamming new key frame requests when the issue is actually in the decryptor layer. To prevent this if frame decryption is required for the PeerConnection key frame requests will not be sent at 200ms intervals but will wait until the stream is decryptable before utilizing this logic. Bug: webrtc:10330 Change-Id: I188a21dfd142dec6175d9def95f39a2bc517017c Reviewed-on: https://webrtc-review.googlesource.com/c/123414 Commit-Queue: Benjamin Wright <benwright@webrtc.org> Reviewed-by: Stefan Holmer <stefan@webrtc.org> Reviewed-by: Philip Eliasson <philipel@webrtc.org> Cr-Commit-Position: refs/heads/master@{#26931}
120 lines
4.4 KiB
C++
120 lines
4.4 KiB
C++
/*
|
|
* Copyright (c) 2018 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 "video/buffered_frame_decryptor.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/system/fallthrough.h"
|
|
#include "system_wrappers/include/field_trial.h"
|
|
|
|
namespace webrtc {
|
|
|
|
BufferedFrameDecryptor::BufferedFrameDecryptor(
|
|
OnDecryptedFrameCallback* decrypted_frame_callback,
|
|
OnDecryptionStatusChangeCallback* decryption_status_change_callback,
|
|
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor)
|
|
: generic_descriptor_auth_experiment_(
|
|
field_trial::IsEnabled("WebRTC-GenericDescriptorAuth")),
|
|
frame_decryptor_(std::move(frame_decryptor)),
|
|
decrypted_frame_callback_(decrypted_frame_callback),
|
|
decryption_status_change_callback_(decryption_status_change_callback) {}
|
|
|
|
BufferedFrameDecryptor::~BufferedFrameDecryptor() {}
|
|
|
|
void BufferedFrameDecryptor::ManageEncryptedFrame(
|
|
std::unique_ptr<video_coding::RtpFrameObject> encrypted_frame) {
|
|
switch (DecryptFrame(encrypted_frame.get())) {
|
|
case FrameDecision::kStash:
|
|
if (stashed_frames_.size() >= kMaxStashedFrames) {
|
|
stashed_frames_.pop_front();
|
|
}
|
|
stashed_frames_.push_back(std::move(encrypted_frame));
|
|
break;
|
|
case FrameDecision::kDecrypted:
|
|
RetryStashedFrames();
|
|
decrypted_frame_callback_->OnDecryptedFrame(std::move(encrypted_frame));
|
|
break;
|
|
case FrameDecision::kDrop:
|
|
break;
|
|
}
|
|
}
|
|
|
|
BufferedFrameDecryptor::FrameDecision BufferedFrameDecryptor::DecryptFrame(
|
|
video_coding::RtpFrameObject* frame) {
|
|
// Optionally attempt to decrypt the raw video frame if it was provided.
|
|
if (frame_decryptor_ == nullptr) {
|
|
RTC_LOG(LS_WARNING) << "Frame decryption required but not attached to this "
|
|
"stream. Dropping frame.";
|
|
return FrameDecision::kDrop;
|
|
}
|
|
// When using encryption we expect the frame to have the generic descriptor.
|
|
absl::optional<RtpGenericFrameDescriptor> descriptor =
|
|
frame->GetGenericFrameDescriptor();
|
|
if (!descriptor) {
|
|
RTC_LOG(LS_ERROR) << "No generic frame descriptor found dropping frame.";
|
|
return FrameDecision::kDrop;
|
|
}
|
|
// Retrieve the maximum possible size of the decrypted payload.
|
|
const size_t max_plaintext_byte_size =
|
|
frame_decryptor_->GetMaxPlaintextByteSize(cricket::MEDIA_TYPE_VIDEO,
|
|
frame->size());
|
|
RTC_CHECK_LE(max_plaintext_byte_size, frame->size());
|
|
// Place the decrypted frame inline into the existing frame.
|
|
rtc::ArrayView<uint8_t> inline_decrypted_bitstream(frame->data(),
|
|
max_plaintext_byte_size);
|
|
|
|
// Only enable authenticating the header if the field trial is enabled.
|
|
rtc::ArrayView<const uint8_t> additional_data;
|
|
if (generic_descriptor_auth_experiment_) {
|
|
additional_data = descriptor->GetByteRepresentation();
|
|
}
|
|
|
|
// Attempt to decrypt the video frame.
|
|
size_t bytes_written = 0;
|
|
const int status = frame_decryptor_->Decrypt(
|
|
cricket::MEDIA_TYPE_VIDEO, /*csrcs=*/{}, additional_data, *frame,
|
|
inline_decrypted_bitstream, &bytes_written);
|
|
|
|
// Optionally call the callback if there was a change in status
|
|
if (status != last_status_) {
|
|
last_status_ = status;
|
|
decryption_status_change_callback_->OnDecryptionStatusChange(status);
|
|
}
|
|
|
|
if (status != 0) {
|
|
// Only stash frames if we have never decrypted a frame before.
|
|
return first_frame_decrypted_ ? FrameDecision::kDrop
|
|
: FrameDecision::kStash;
|
|
}
|
|
RTC_CHECK_LE(bytes_written, max_plaintext_byte_size);
|
|
// Update the frame to contain just the written bytes.
|
|
frame->set_size(bytes_written);
|
|
|
|
// Indicate that all future fail to decrypt frames should be dropped.
|
|
if (!first_frame_decrypted_) {
|
|
first_frame_decrypted_ = true;
|
|
}
|
|
|
|
return FrameDecision::kDecrypted;
|
|
}
|
|
|
|
void BufferedFrameDecryptor::RetryStashedFrames() {
|
|
for (auto& frame : stashed_frames_) {
|
|
if (DecryptFrame(frame.get()) == FrameDecision::kDecrypted) {
|
|
decrypted_frame_callback_->OnDecryptedFrame(std::move(frame));
|
|
}
|
|
}
|
|
stashed_frames_.clear();
|
|
}
|
|
|
|
} // namespace webrtc
|