1 /*
2 * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h"
12
13 #include <cmath>
14 #include <memory>
15 #include <utility>
16
17 #include "absl/strings/string_view.h"
18 #include "api/video/video_codec_type.h"
19 #include "api/video_codecs/video_encoder.h"
20 #include "modules/video_coding/include/video_error_codes.h"
21 #include "modules/video_coding/svc/scalability_mode_util.h"
22 #include "rtc_base/logging.h"
23
24 namespace webrtc {
25 namespace webrtc_pc_e2e {
26 namespace {
27
28 using EmulatedSFUConfigMap =
29 ::webrtc::webrtc_pc_e2e::QualityAnalyzingVideoEncoder::EmulatedSFUConfigMap;
30
31 constexpr size_t kMaxFrameInPipelineCount = 1000;
32 constexpr double kNoMultiplier = 1.0;
33 constexpr double kEps = 1e-6;
34
GetMinMaxBitratesBps(const VideoCodec & codec,size_t spatial_idx)35 std::pair<uint32_t, uint32_t> GetMinMaxBitratesBps(const VideoCodec& codec,
36 size_t spatial_idx) {
37 uint32_t min_bitrate = codec.minBitrate;
38 uint32_t max_bitrate = codec.maxBitrate;
39 if (spatial_idx < codec.numberOfSimulcastStreams &&
40 codec.codecType != VideoCodecType::kVideoCodecVP9) {
41 min_bitrate =
42 std::max(min_bitrate, codec.simulcastStream[spatial_idx].minBitrate);
43 max_bitrate =
44 std::min(max_bitrate, codec.simulcastStream[spatial_idx].maxBitrate);
45 }
46 if (codec.codecType == VideoCodecType::kVideoCodecVP9 &&
47 spatial_idx < codec.VP9().numberOfSpatialLayers) {
48 min_bitrate =
49 std::max(min_bitrate, codec.spatialLayers[spatial_idx].minBitrate);
50 max_bitrate =
51 std::min(max_bitrate, codec.spatialLayers[spatial_idx].maxBitrate);
52 }
53 RTC_DCHECK_GT(max_bitrate, min_bitrate);
54 return {min_bitrate * 1000, max_bitrate * 1000};
55 }
56
57 } // namespace
58
QualityAnalyzingVideoEncoder(absl::string_view peer_name,std::unique_ptr<VideoEncoder> delegate,double bitrate_multiplier,EmulatedSFUConfigMap stream_to_sfu_config,EncodedImageDataInjector * injector,VideoQualityAnalyzerInterface * analyzer)59 QualityAnalyzingVideoEncoder::QualityAnalyzingVideoEncoder(
60 absl::string_view peer_name,
61 std::unique_ptr<VideoEncoder> delegate,
62 double bitrate_multiplier,
63 EmulatedSFUConfigMap stream_to_sfu_config,
64 EncodedImageDataInjector* injector,
65 VideoQualityAnalyzerInterface* analyzer)
66 : peer_name_(peer_name),
67 delegate_(std::move(delegate)),
68 bitrate_multiplier_(bitrate_multiplier),
69 stream_to_sfu_config_(std::move(stream_to_sfu_config)),
70 injector_(injector),
71 analyzer_(analyzer),
72 mode_(SimulcastMode::kNormal),
73 delegate_callback_(nullptr) {}
74 QualityAnalyzingVideoEncoder::~QualityAnalyzingVideoEncoder() = default;
75
SetFecControllerOverride(FecControllerOverride * fec_controller_override)76 void QualityAnalyzingVideoEncoder::SetFecControllerOverride(
77 FecControllerOverride* fec_controller_override) {
78 // Ignored.
79 }
80
InitEncode(const VideoCodec * codec_settings,const Settings & settings)81 int32_t QualityAnalyzingVideoEncoder::InitEncode(
82 const VideoCodec* codec_settings,
83 const Settings& settings) {
84 MutexLock lock(&mutex_);
85 codec_settings_ = *codec_settings;
86 mode_ = SimulcastMode::kNormal;
87 absl::optional<InterLayerPredMode> inter_layer_pred_mode;
88 if (codec_settings->GetScalabilityMode().has_value()) {
89 inter_layer_pred_mode = ScalabilityModeToInterLayerPredMode(
90 *codec_settings->GetScalabilityMode());
91 } else if (codec_settings->codecType == kVideoCodecVP9) {
92 if (codec_settings->VP9().numberOfSpatialLayers > 1) {
93 inter_layer_pred_mode = codec_settings->VP9().interLayerPred;
94 }
95 }
96 if (inter_layer_pred_mode.has_value()) {
97 switch (*inter_layer_pred_mode) {
98 case InterLayerPredMode::kOn:
99 mode_ = SimulcastMode::kSVC;
100 break;
101 case InterLayerPredMode::kOnKeyPic:
102 mode_ = SimulcastMode::kKSVC;
103 break;
104 case InterLayerPredMode::kOff:
105 mode_ = SimulcastMode::kSimulcast;
106 break;
107 default:
108 RTC_DCHECK_NOTREACHED()
109 << "Unknown InterLayerPredMode value " << *inter_layer_pred_mode;
110 break;
111 }
112 }
113 if (codec_settings->numberOfSimulcastStreams > 1) {
114 mode_ = SimulcastMode::kSimulcast;
115 }
116 return delegate_->InitEncode(codec_settings, settings);
117 }
118
RegisterEncodeCompleteCallback(EncodedImageCallback * callback)119 int32_t QualityAnalyzingVideoEncoder::RegisterEncodeCompleteCallback(
120 EncodedImageCallback* callback) {
121 // We need to get a lock here because delegate_callback can be hypothetically
122 // accessed from different thread (encoder one) concurrently.
123 MutexLock lock(&mutex_);
124 delegate_callback_ = callback;
125 return delegate_->RegisterEncodeCompleteCallback(this);
126 }
127
Release()128 int32_t QualityAnalyzingVideoEncoder::Release() {
129 // Release encoder first. During release process it can still encode some
130 // frames, so we don't take a lock to prevent deadlock.
131 int32_t result = delegate_->Release();
132
133 MutexLock lock(&mutex_);
134 delegate_callback_ = nullptr;
135 return result;
136 }
137
Encode(const VideoFrame & frame,const std::vector<VideoFrameType> * frame_types)138 int32_t QualityAnalyzingVideoEncoder::Encode(
139 const VideoFrame& frame,
140 const std::vector<VideoFrameType>* frame_types) {
141 {
142 MutexLock lock(&mutex_);
143 // Store id to be able to retrieve it in analyzing callback.
144 timestamp_to_frame_id_list_.push_back({frame.timestamp(), frame.id()});
145 // If this list is growing, it means that we are not receiving new encoded
146 // images from encoder. So it should be a bug in setup on in the encoder.
147 RTC_DCHECK_LT(timestamp_to_frame_id_list_.size(), kMaxFrameInPipelineCount);
148 }
149 analyzer_->OnFramePreEncode(peer_name_, frame);
150 int32_t result = delegate_->Encode(frame, frame_types);
151 if (result != WEBRTC_VIDEO_CODEC_OK) {
152 // If origin encoder failed, then cleanup data for this frame.
153 {
154 MutexLock lock(&mutex_);
155 // The timestamp-frame_id pair can be not the last one, so we need to
156 // find it first and then remove. We will search from the end, because
157 // usually it will be the last or close to the last one.
158 auto it = timestamp_to_frame_id_list_.end();
159 while (it != timestamp_to_frame_id_list_.begin()) {
160 --it;
161 if (it->first == frame.timestamp()) {
162 timestamp_to_frame_id_list_.erase(it);
163 break;
164 }
165 }
166 }
167 analyzer_->OnEncoderError(peer_name_, frame, result);
168 }
169 return result;
170 }
171
SetRates(const VideoEncoder::RateControlParameters & parameters)172 void QualityAnalyzingVideoEncoder::SetRates(
173 const VideoEncoder::RateControlParameters& parameters) {
174 RTC_DCHECK_GT(bitrate_multiplier_, 0.0);
175 if (fabs(bitrate_multiplier_ - kNoMultiplier) < kEps) {
176 {
177 MutexLock lock(&mutex_);
178 bitrate_allocation_ = parameters.bitrate;
179 }
180 return delegate_->SetRates(parameters);
181 }
182
183 RateControlParameters adjusted_params = parameters;
184 {
185 MutexLock lock(&mutex_);
186 // Simulating encoder overshooting target bitrate, by configuring actual
187 // encoder too high. Take care not to adjust past limits of config,
188 // otherwise encoders may crash on DCHECK.
189 VideoBitrateAllocation multiplied_allocation;
190 for (size_t si = 0; si < kMaxSpatialLayers; ++si) {
191 const uint32_t spatial_layer_bitrate_bps =
192 parameters.bitrate.GetSpatialLayerSum(si);
193 if (spatial_layer_bitrate_bps == 0) {
194 continue;
195 }
196
197 uint32_t min_bitrate_bps;
198 uint32_t max_bitrate_bps;
199 std::tie(min_bitrate_bps, max_bitrate_bps) =
200 GetMinMaxBitratesBps(codec_settings_, si);
201 double bitrate_multiplier = bitrate_multiplier_;
202 const uint32_t corrected_bitrate = rtc::checked_cast<uint32_t>(
203 bitrate_multiplier * spatial_layer_bitrate_bps);
204 if (corrected_bitrate < min_bitrate_bps) {
205 bitrate_multiplier = min_bitrate_bps / spatial_layer_bitrate_bps;
206 } else if (corrected_bitrate > max_bitrate_bps) {
207 bitrate_multiplier = max_bitrate_bps / spatial_layer_bitrate_bps;
208 }
209
210 for (size_t ti = 0; ti < kMaxTemporalStreams; ++ti) {
211 if (parameters.bitrate.HasBitrate(si, ti)) {
212 multiplied_allocation.SetBitrate(
213 si, ti,
214 rtc::checked_cast<uint32_t>(
215 bitrate_multiplier * parameters.bitrate.GetBitrate(si, ti)));
216 }
217 }
218 }
219
220 adjusted_params.bitrate = multiplied_allocation;
221 bitrate_allocation_ = adjusted_params.bitrate;
222 }
223 return delegate_->SetRates(adjusted_params);
224 }
225
GetEncoderInfo() const226 VideoEncoder::EncoderInfo QualityAnalyzingVideoEncoder::GetEncoderInfo() const {
227 return delegate_->GetEncoderInfo();
228 }
229
230 // It is assumed, that encoded callback will be always invoked with encoded
231 // images that correspond to the frames in the same sequence, that frames
232 // arrived. In other words, assume we have frames F1, F2 and F3 and they have
233 // corresponding encoded images I1, I2 and I3. In such case if we will call
234 // encode first with F1, then with F2 and then with F3, then encoder callback
235 // will be called first with all spatial layers for F1 (I1), then F2 (I2) and
236 // then F3 (I3).
237 //
238 // Basing on it we will use a list of timestamp-frame_id pairs like this:
239 // 1. If current encoded image timestamp is equals to timestamp in the front
240 // pair - pick frame id from that pair
241 // 2. If current encoded image timestamp isn't equals to timestamp in the front
242 // pair - remove the front pair and got to the step 1.
OnEncodedImage(const EncodedImage & encoded_image,const CodecSpecificInfo * codec_specific_info)243 EncodedImageCallback::Result QualityAnalyzingVideoEncoder::OnEncodedImage(
244 const EncodedImage& encoded_image,
245 const CodecSpecificInfo* codec_specific_info) {
246 uint16_t frame_id;
247 bool discard = false;
248 uint32_t target_encode_bitrate = 0;
249 std::string codec_name;
250 {
251 MutexLock lock(&mutex_);
252 std::pair<uint32_t, uint16_t> timestamp_frame_id;
253 while (!timestamp_to_frame_id_list_.empty()) {
254 timestamp_frame_id = timestamp_to_frame_id_list_.front();
255 if (timestamp_frame_id.first == encoded_image.Timestamp()) {
256 break;
257 }
258 timestamp_to_frame_id_list_.pop_front();
259 }
260
261 // After the loop the first element should point to current `encoded_image`
262 // frame id. We don't remove it from the list, because there may be
263 // multiple spatial layers for this frame, so encoder can produce more
264 // encoded images with this timestamp. The first element will be removed
265 // when the next frame would be encoded and EncodedImageCallback would be
266 // called with the next timestamp.
267
268 if (timestamp_to_frame_id_list_.empty()) {
269 // Ensure, that we have info about this frame. It can happen that for some
270 // reasons encoder response, that he failed to decode, when we were
271 // posting frame to it, but then call the callback for this frame.
272 RTC_LOG(LS_ERROR) << "QualityAnalyzingVideoEncoder::OnEncodedImage: No "
273 "frame id for encoded_image.Timestamp()="
274 << encoded_image.Timestamp();
275 return EncodedImageCallback::Result(
276 EncodedImageCallback::Result::Error::OK);
277 }
278 frame_id = timestamp_frame_id.second;
279
280 discard = ShouldDiscard(frame_id, encoded_image);
281 if (!discard) {
282 target_encode_bitrate = bitrate_allocation_.GetSpatialLayerSum(
283 encoded_image.SpatialIndex().value_or(0));
284 }
285 codec_name =
286 std::string(CodecTypeToPayloadString(codec_settings_.codecType)) + "_" +
287 delegate_->GetEncoderInfo().implementation_name;
288 }
289
290 VideoQualityAnalyzerInterface::EncoderStats stats;
291 stats.encoder_name = codec_name;
292 stats.target_encode_bitrate = target_encode_bitrate;
293 stats.qp = encoded_image.qp_;
294 analyzer_->OnFrameEncoded(peer_name_, frame_id, encoded_image, stats,
295 discard);
296
297 // Image data injector injects frame id and discard flag into provided
298 // EncodedImage and returns the image with a) modified original buffer (in
299 // such case the current owner of the buffer will be responsible for deleting
300 // it) or b) a new buffer (in such case injector will be responsible for
301 // deleting it).
302 const EncodedImage& image =
303 injector_->InjectData(frame_id, discard, encoded_image);
304 {
305 MutexLock lock(&mutex_);
306 RTC_DCHECK(delegate_callback_);
307 return delegate_callback_->OnEncodedImage(image, codec_specific_info);
308 }
309 }
310
OnDroppedFrame(EncodedImageCallback::DropReason reason)311 void QualityAnalyzingVideoEncoder::OnDroppedFrame(
312 EncodedImageCallback::DropReason reason) {
313 MutexLock lock(&mutex_);
314 analyzer_->OnFrameDropped(peer_name_, reason);
315 RTC_DCHECK(delegate_callback_);
316 delegate_callback_->OnDroppedFrame(reason);
317 }
318
ShouldDiscard(uint16_t frame_id,const EncodedImage & encoded_image)319 bool QualityAnalyzingVideoEncoder::ShouldDiscard(
320 uint16_t frame_id,
321 const EncodedImage& encoded_image) {
322 std::string stream_label = analyzer_->GetStreamLabel(frame_id);
323 EmulatedSFUConfigMap::mapped_type emulated_sfu_config =
324 stream_to_sfu_config_[stream_label];
325
326 if (!emulated_sfu_config)
327 return false;
328
329 int cur_spatial_index = encoded_image.SpatialIndex().value_or(0);
330 int cur_temporal_index = encoded_image.TemporalIndex().value_or(0);
331
332 if (emulated_sfu_config->target_temporal_index &&
333 cur_temporal_index > *emulated_sfu_config->target_temporal_index)
334 return true;
335
336 if (emulated_sfu_config->target_layer_index) {
337 switch (mode_) {
338 case SimulcastMode::kSimulcast:
339 // In simulcast mode only encoded images with required spatial index are
340 // interested, so all others have to be discarded.
341 return cur_spatial_index != *emulated_sfu_config->target_layer_index;
342 case SimulcastMode::kSVC:
343 // In SVC mode encoded images with spatial indexes that are equal or
344 // less than required one are interesting, so all above have to be
345 // discarded.
346 return cur_spatial_index > *emulated_sfu_config->target_layer_index;
347 case SimulcastMode::kKSVC:
348 // In KSVC mode for key frame encoded images with spatial indexes that
349 // are equal or less than required one are interesting, so all above
350 // have to be discarded. For other frames only required spatial index
351 // is interesting, so all others except the ones depending on the
352 // keyframes can be discarded. There's no good test for that, so we keep
353 // all of temporal layer 0 for now.
354 if (encoded_image._frameType == VideoFrameType::kVideoFrameKey ||
355 cur_temporal_index == 0)
356 return cur_spatial_index > *emulated_sfu_config->target_layer_index;
357 return cur_spatial_index != *emulated_sfu_config->target_layer_index;
358 case SimulcastMode::kNormal:
359 RTC_DCHECK_NOTREACHED() << "Analyzing encoder is in kNormal mode, but "
360 "target_layer_index is set";
361 }
362 }
363 return false;
364 }
365
QualityAnalyzingVideoEncoderFactory(absl::string_view peer_name,std::unique_ptr<VideoEncoderFactory> delegate,double bitrate_multiplier,EmulatedSFUConfigMap stream_to_sfu_config,EncodedImageDataInjector * injector,VideoQualityAnalyzerInterface * analyzer)366 QualityAnalyzingVideoEncoderFactory::QualityAnalyzingVideoEncoderFactory(
367 absl::string_view peer_name,
368 std::unique_ptr<VideoEncoderFactory> delegate,
369 double bitrate_multiplier,
370 EmulatedSFUConfigMap stream_to_sfu_config,
371 EncodedImageDataInjector* injector,
372 VideoQualityAnalyzerInterface* analyzer)
373 : peer_name_(peer_name),
374 delegate_(std::move(delegate)),
375 bitrate_multiplier_(bitrate_multiplier),
376 stream_to_sfu_config_(std::move(stream_to_sfu_config)),
377 injector_(injector),
378 analyzer_(analyzer) {}
379 QualityAnalyzingVideoEncoderFactory::~QualityAnalyzingVideoEncoderFactory() =
380 default;
381
382 std::vector<SdpVideoFormat>
GetSupportedFormats() const383 QualityAnalyzingVideoEncoderFactory::GetSupportedFormats() const {
384 return delegate_->GetSupportedFormats();
385 }
386
387 std::unique_ptr<VideoEncoder>
CreateVideoEncoder(const SdpVideoFormat & format)388 QualityAnalyzingVideoEncoderFactory::CreateVideoEncoder(
389 const SdpVideoFormat& format) {
390 return std::make_unique<QualityAnalyzingVideoEncoder>(
391 peer_name_, delegate_->CreateVideoEncoder(format), bitrate_multiplier_,
392 stream_to_sfu_config_, injector_, analyzer_);
393 }
394
395 } // namespace webrtc_pc_e2e
396 } // namespace webrtc
397