blob: 6863d9b905284d830df687d4bf43ee895baa9c02 [file] [log] [blame]
/*
* Copyright (c) 2024 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 "modules/video_coding/svc/simulcast_to_svc_converter.h"
#include "modules/video_coding/svc/create_scalability_structure.h"
#include "modules/video_coding/svc/scalability_mode_util.h"
#include "modules/video_coding/utility/simulcast_utility.h"
#include "rtc_base/checks.h"
namespace webrtc {
SimulcastToSvcConverter::SimulcastToSvcConverter(const VideoCodec& codec) {
config_ = codec;
int num_temporal_layers =
config_.simulcastStream[0].GetNumberOfTemporalLayers();
int num_spatial_layers = config_.numberOfSimulcastStreams;
ScalabilityMode scalability_mode;
switch (num_temporal_layers) {
case 1:
scalability_mode = ScalabilityMode::kL1T1;
break;
case 2:
scalability_mode = ScalabilityMode::kL1T2;
break;
case 3:
scalability_mode = ScalabilityMode::kL1T3;
break;
default:
RTC_DCHECK_NOTREACHED();
}
for (int i = 0; i < num_spatial_layers; ++i) {
config_.spatialLayers[i] = config_.simulcastStream[i];
}
config_.simulcastStream[0] =
config_.simulcastStream[config_.numberOfSimulcastStreams - 1];
config_.VP9()->numberOfSpatialLayers = config_.numberOfSimulcastStreams;
config_.VP9()->numberOfTemporalLayers =
config_.spatialLayers[0].numberOfTemporalLayers;
config_.VP9()->interLayerPred = InterLayerPredMode::kOff;
config_.numberOfSimulcastStreams = 1;
config_.UnsetScalabilityMode();
for (int i = 0; i < num_spatial_layers; ++i) {
layers_.emplace_back(scalability_mode, num_temporal_layers);
}
}
VideoCodec SimulcastToSvcConverter::GetConfig() const {
return config_;
}
void SimulcastToSvcConverter::EncodeStarted(bool force_keyframe) {
// Check if at least one layer was encoded successfully.
bool some_layers_has_completed = false;
for (size_t i = 0; i < layers_.size(); ++i) {
some_layers_has_completed |= !layers_[i].awaiting_frame;
}
for (size_t i = 0; i < layers_.size(); ++i) {
if (layers_[i].awaiting_frame && some_layers_has_completed) {
// Simulcast SVC controller updates pattern on all layers, even
// if some layers has dropped the frame.
// Simulate that behavior for all controllers, not updated
// while rewriting frame descriptors.
layers_[i].video_controller->OnEncodeDone(layers_[i].layer_config);
}
layers_[i].awaiting_frame = true;
auto configs = layers_[i].video_controller->NextFrameConfig(force_keyframe);
RTC_CHECK_EQ(configs.size(), 1u);
layers_[i].layer_config = configs[0];
}
}
bool SimulcastToSvcConverter::ConvertFrame(EncodedImage& encoded_image,
CodecSpecificInfo& codec_specific) {
int sid = encoded_image.SpatialIndex().value_or(0);
encoded_image.SetSimulcastIndex(sid);
encoded_image.SetSpatialIndex(std::nullopt);
codec_specific.end_of_picture = true;
if (codec_specific.scalability_mode) {
int num_temporal_layers =
ScalabilityModeToNumTemporalLayers(*codec_specific.scalability_mode);
RTC_DCHECK_LE(num_temporal_layers, 3);
if (num_temporal_layers == 1) {
codec_specific.scalability_mode = ScalabilityMode::kL1T1;
} else if (num_temporal_layers == 2) {
codec_specific.scalability_mode = ScalabilityMode::kL1T2;
} else if (num_temporal_layers == 3) {
codec_specific.scalability_mode = ScalabilityMode::kL1T3;
}
}
CodecSpecificInfoVP9& vp9_info = codec_specific.codecSpecific.VP9;
vp9_info.num_spatial_layers = 1;
vp9_info.first_active_layer = 0;
vp9_info.first_frame_in_picture = true;
if (vp9_info.ss_data_available) {
vp9_info.width[0] = vp9_info.width[sid];
vp9_info.height[0] = vp9_info.height[sid];
}
auto& video_controller = *layers_[sid].video_controller;
if (codec_specific.generic_frame_info) {
layers_[sid].awaiting_frame = false;
uint8_t tid = encoded_image.TemporalIndex().value_or(0);
auto& frame_config = layers_[sid].layer_config;
RTC_DCHECK_EQ(frame_config.TemporalId(), tid == kNoTemporalIdx ? 0 : tid);
if (frame_config.TemporalId() != (tid == kNoTemporalIdx ? 0 : tid)) {
return false;
}
codec_specific.generic_frame_info =
video_controller.OnEncodeDone(frame_config);
}
if (codec_specific.template_structure) {
auto resolution = codec_specific.template_structure->resolutions[sid];
codec_specific.template_structure = video_controller.DependencyStructure();
codec_specific.template_structure->resolutions.resize(1);
codec_specific.template_structure->resolutions[0] = resolution;
}
return true;
}
SimulcastToSvcConverter::LayerState::LayerState(
ScalabilityMode scalability_mode,
int num_temporal_layers)
: video_controller(CreateScalabilityStructure(scalability_mode)),
awaiting_frame(false) {
VideoBitrateAllocation dummy_bitrates;
for (int i = 0; i < num_temporal_layers; ++i) {
dummy_bitrates.SetBitrate(0, i, 10000);
}
video_controller->OnRatesUpdated(dummy_bitrates);
}
// static
bool SimulcastToSvcConverter::IsConfigSupported(const VideoCodec& codec) {
if (codec.numberOfSimulcastStreams <= 1 ||
!SimulcastUtility::ValidSimulcastParameters(
codec, codec.numberOfSimulcastStreams)) {
return false;
}
// Ensure there's 4:2:1 scaling.
for (int i = 1; i < codec.numberOfSimulcastStreams; ++i) {
if (codec.simulcastStream[i].active &&
codec.simulcastStream[i - 1].active &&
(codec.simulcastStream[i].width !=
codec.simulcastStream[i - 1].width * 2 ||
codec.simulcastStream[i].height !=
codec.simulcastStream[i - 1].height * 2)) {
return false;
}
}
int first_active_layer = -1;
int last_active_layer = -1;
int num_active_layers = 0;
for (int i = 0; i < codec.numberOfSimulcastStreams; ++i) {
if (codec.simulcastStream[i].active) {
if (first_active_layer < 0)
first_active_layer = i;
last_active_layer = i;
++num_active_layers;
}
}
// Active layers must form a continuous segment. Can't have holes, because
// most SVC encoders can't process that.
return num_active_layers == last_active_layer - first_active_layer + 1;
}
} // namespace webrtc