blob: e5f55575eca90d8121f8dccfba409ee20c3314ff [file] [log] [blame]
/*
* Copyright (c) 2019 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/frame_encode_metadata_writer.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "common_video/h264/sps_vui_rewriter.h"
#include "modules/include/module_common_types_public.h"
#include "modules/video_coding/include/video_coding_defines.h"
#include "rtc_base/logging.h"
#include "rtc_base/ref_counted_object.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
namespace {
const int kMessagesThrottlingThreshold = 2;
const int kThrottleRatio = 100000;
class EncodedImageBufferWrapper : public EncodedImageBufferInterface {
public:
explicit EncodedImageBufferWrapper(rtc::Buffer&& buffer)
: buffer_(std::move(buffer)) {}
const uint8_t* data() const override { return buffer_.data(); }
uint8_t* data() override { return buffer_.data(); }
size_t size() const override { return buffer_.size(); }
private:
rtc::Buffer buffer_;
};
} // namespace
FrameEncodeMetadataWriter::TimingFramesLayerInfo::TimingFramesLayerInfo() =
default;
FrameEncodeMetadataWriter::TimingFramesLayerInfo::~TimingFramesLayerInfo() =
default;
FrameEncodeMetadataWriter::FrameEncodeMetadataWriter(
EncodedImageCallback* frame_drop_callback)
: frame_drop_callback_(frame_drop_callback),
internal_source_(false),
framerate_fps_(0),
last_timing_frame_time_ms_(-1),
reordered_frames_logged_messages_(0),
stalled_encoder_logged_messages_(0) {
codec_settings_.timing_frame_thresholds = {-1, 0};
}
FrameEncodeMetadataWriter::~FrameEncodeMetadataWriter() {}
void FrameEncodeMetadataWriter::OnEncoderInit(const VideoCodec& codec,
bool internal_source) {
rtc::CritScope cs(&lock_);
codec_settings_ = codec;
internal_source_ = internal_source;
}
void FrameEncodeMetadataWriter::OnSetRates(
const VideoBitrateAllocation& bitrate_allocation,
uint32_t framerate_fps) {
rtc::CritScope cs(&lock_);
framerate_fps_ = framerate_fps;
const size_t num_spatial_layers = NumSpatialLayers();
if (timing_frames_info_.size() < num_spatial_layers) {
timing_frames_info_.resize(num_spatial_layers);
}
for (size_t i = 0; i < num_spatial_layers; ++i) {
timing_frames_info_[i].target_bitrate_bytes_per_sec =
bitrate_allocation.GetSpatialLayerSum(i) / 8;
}
}
void FrameEncodeMetadataWriter::OnEncodeStarted(const VideoFrame& frame) {
rtc::CritScope cs(&lock_);
if (internal_source_) {
return;
}
const size_t num_spatial_layers = NumSpatialLayers();
timing_frames_info_.resize(num_spatial_layers);
FrameMetadata metadata;
metadata.rtp_timestamp = frame.timestamp();
metadata.encode_start_time_ms = rtc::TimeMillis();
metadata.ntp_time_ms = frame.ntp_time_ms();
metadata.timestamp_us = frame.timestamp_us();
metadata.rotation = frame.rotation();
metadata.color_space = frame.color_space();
metadata.packet_infos = frame.packet_infos();
for (size_t si = 0; si < num_spatial_layers; ++si) {
RTC_DCHECK(timing_frames_info_[si].frames.empty() ||
rtc::TimeDiff(
frame.render_time_ms(),
timing_frames_info_[si].frames.back().timestamp_us / 1000) >=
0);
// If stream is disabled due to low bandwidth OnEncodeStarted still will be
// called and have to be ignored.
if (timing_frames_info_[si].target_bitrate_bytes_per_sec == 0)
continue;
if (timing_frames_info_[si].frames.size() == kMaxEncodeStartTimeListSize) {
++stalled_encoder_logged_messages_;
if (stalled_encoder_logged_messages_ <= kMessagesThrottlingThreshold ||
stalled_encoder_logged_messages_ % kThrottleRatio == 0) {
RTC_LOG(LS_WARNING) << "Too many frames in the encode_start_list."
" Did encoder stall?";
if (stalled_encoder_logged_messages_ == kMessagesThrottlingThreshold) {
RTC_LOG(LS_WARNING)
<< "Too many log messages. Further stalled encoder"
"warnings will be throttled.";
}
}
frame_drop_callback_->OnDroppedFrame(
EncodedImageCallback::DropReason::kDroppedByEncoder);
timing_frames_info_[si].frames.pop_front();
}
timing_frames_info_[si].frames.emplace_back(metadata);
}
}
void FrameEncodeMetadataWriter::FillTimingInfo(size_t simulcast_svc_idx,
EncodedImage* encoded_image) {
rtc::CritScope cs(&lock_);
absl::optional<size_t> outlier_frame_size;
absl::optional<int64_t> encode_start_ms;
uint8_t timing_flags = VideoSendTiming::kNotTriggered;
int64_t encode_done_ms = rtc::TimeMillis();
// Encoders with internal sources do not call OnEncodeStarted
// |timing_frames_info_| may be not filled here.
if (!internal_source_) {
encode_start_ms =
ExtractEncodeStartTimeAndFillMetadata(simulcast_svc_idx, encoded_image);
}
if (timing_frames_info_.size() > simulcast_svc_idx) {
size_t target_bitrate =
timing_frames_info_[simulcast_svc_idx].target_bitrate_bytes_per_sec;
if (framerate_fps_ > 0 && target_bitrate > 0) {
// framerate and target bitrate were reported by encoder.
size_t average_frame_size = target_bitrate / framerate_fps_;
outlier_frame_size.emplace(
average_frame_size *
codec_settings_.timing_frame_thresholds.outlier_ratio_percent / 100);
}
}
// Outliers trigger timing frames, but do not affect scheduled timing
// frames.
if (outlier_frame_size && encoded_image->size() >= *outlier_frame_size) {
timing_flags |= VideoSendTiming::kTriggeredBySize;
}
// Check if it's time to send a timing frame.
int64_t timing_frame_delay_ms =
encoded_image->capture_time_ms_ - last_timing_frame_time_ms_;
// Trigger threshold if it's a first frame, too long passed since the last
// timing frame, or we already sent timing frame on a different simulcast
// stream with the same capture time.
if (last_timing_frame_time_ms_ == -1 ||
timing_frame_delay_ms >=
codec_settings_.timing_frame_thresholds.delay_ms ||
timing_frame_delay_ms == 0) {
timing_flags |= VideoSendTiming::kTriggeredByTimer;
last_timing_frame_time_ms_ = encoded_image->capture_time_ms_;
}
// Workaround for chromoting encoder: it passes encode start and finished
// timestamps in |timing_| field, but they (together with capture timestamp)
// are not in the WebRTC clock.
if (internal_source_ && encoded_image->timing_.encode_finish_ms > 0 &&
encoded_image->timing_.encode_start_ms > 0) {
int64_t clock_offset_ms =
encode_done_ms - encoded_image->timing_.encode_finish_ms;
// Translate capture timestamp to local WebRTC clock.
encoded_image->capture_time_ms_ += clock_offset_ms;
encoded_image->SetTimestamp(
static_cast<uint32_t>(encoded_image->capture_time_ms_ * 90));
encode_start_ms.emplace(encoded_image->timing_.encode_start_ms +
clock_offset_ms);
}
// If encode start is not available that means that encoder uses internal
// source. In that case capture timestamp may be from a different clock with a
// drift relative to rtc::TimeMillis(). We can't use it for Timing frames,
// because to being sent in the network capture time required to be less than
// all the other timestamps.
if (encode_start_ms) {
encoded_image->SetEncodeTime(*encode_start_ms, encode_done_ms);
encoded_image->timing_.flags = timing_flags;
} else {
encoded_image->timing_.flags = VideoSendTiming::kInvalid;
}
}
std::unique_ptr<RTPFragmentationHeader>
FrameEncodeMetadataWriter::UpdateBitstream(
const CodecSpecificInfo* codec_specific_info,
const RTPFragmentationHeader* fragmentation,
EncodedImage* encoded_image) {
if (!codec_specific_info ||
codec_specific_info->codecType != kVideoCodecH264 || !fragmentation ||
encoded_image->_frameType != VideoFrameType::kVideoFrameKey) {
return nullptr;
}
rtc::Buffer modified_buffer;
std::unique_ptr<RTPFragmentationHeader> modified_fragmentation =
std::make_unique<RTPFragmentationHeader>();
modified_fragmentation->CopyFrom(*fragmentation);
// Make sure that the data is not copied if owned by EncodedImage.
const EncodedImage& buffer = *encoded_image;
SpsVuiRewriter::ParseOutgoingBitstreamAndRewriteSps(
buffer, fragmentation->fragmentationVectorSize,
fragmentation->fragmentationOffset, fragmentation->fragmentationLength,
encoded_image->ColorSpace(), &modified_buffer,
modified_fragmentation->fragmentationOffset,
modified_fragmentation->fragmentationLength);
encoded_image->SetEncodedData(
new rtc::RefCountedObject<EncodedImageBufferWrapper>(
std::move(modified_buffer)));
return modified_fragmentation;
}
void FrameEncodeMetadataWriter::Reset() {
rtc::CritScope cs(&lock_);
for (auto& info : timing_frames_info_) {
info.frames.clear();
}
last_timing_frame_time_ms_ = -1;
reordered_frames_logged_messages_ = 0;
stalled_encoder_logged_messages_ = 0;
}
absl::optional<int64_t>
FrameEncodeMetadataWriter::ExtractEncodeStartTimeAndFillMetadata(
size_t simulcast_svc_idx,
EncodedImage* encoded_image) {
absl::optional<int64_t> result;
size_t num_simulcast_svc_streams = timing_frames_info_.size();
if (simulcast_svc_idx < num_simulcast_svc_streams) {
auto metadata_list = &timing_frames_info_[simulcast_svc_idx].frames;
// Skip frames for which there was OnEncodeStarted but no OnEncodedImage
// call. These are dropped by encoder internally.
// Because some hardware encoders don't preserve capture timestamp we
// use RTP timestamps here.
while (!metadata_list->empty() &&
IsNewerTimestamp(encoded_image->Timestamp(),
metadata_list->front().rtp_timestamp)) {
frame_drop_callback_->OnDroppedFrame(
EncodedImageCallback::DropReason::kDroppedByEncoder);
metadata_list->pop_front();
}
encoded_image->content_type_ =
(codec_settings_.mode == VideoCodecMode::kScreensharing)
? VideoContentType::SCREENSHARE
: VideoContentType::UNSPECIFIED;
if (!metadata_list->empty() &&
metadata_list->front().rtp_timestamp == encoded_image->Timestamp()) {
result.emplace(metadata_list->front().encode_start_time_ms);
encoded_image->capture_time_ms_ =
metadata_list->front().timestamp_us / 1000;
encoded_image->ntp_time_ms_ = metadata_list->front().ntp_time_ms;
encoded_image->rotation_ = metadata_list->front().rotation;
encoded_image->SetColorSpace(metadata_list->front().color_space);
encoded_image->SetPacketInfos(metadata_list->front().packet_infos);
metadata_list->pop_front();
} else {
++reordered_frames_logged_messages_;
if (reordered_frames_logged_messages_ <= kMessagesThrottlingThreshold ||
reordered_frames_logged_messages_ % kThrottleRatio == 0) {
RTC_LOG(LS_WARNING) << "Frame with no encode started time recordings. "
"Encoder may be reordering frames "
"or not preserving RTP timestamps.";
if (reordered_frames_logged_messages_ == kMessagesThrottlingThreshold) {
RTC_LOG(LS_WARNING) << "Too many log messages. Further frames "
"reordering warnings will be throttled.";
}
}
}
}
return result;
}
size_t FrameEncodeMetadataWriter::NumSpatialLayers() const {
size_t num_spatial_layers = codec_settings_.numberOfSimulcastStreams;
if (codec_settings_.codecType == kVideoCodecVP9) {
num_spatial_layers = std::max(
num_spatial_layers,
static_cast<size_t>(codec_settings_.VP9().numberOfSpatialLayers));
}
return std::max(num_spatial_layers, size_t{1});
}
} // namespace webrtc