diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/libwebrtc/test/scenario/video_stream.cc | |
parent | Initial commit. (diff) | |
download | thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/test/scenario/video_stream.cc')
-rw-r--r-- | third_party/libwebrtc/test/scenario/video_stream.cc | 636 |
1 files changed, 636 insertions, 0 deletions
diff --git a/third_party/libwebrtc/test/scenario/video_stream.cc b/third_party/libwebrtc/test/scenario/video_stream.cc new file mode 100644 index 0000000000..8d627d8893 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/video_stream.cc @@ -0,0 +1,636 @@ +/* + * Copyright 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 "test/scenario/video_stream.h" + +#include <algorithm> +#include <memory> +#include <utility> + +#include "absl/strings/match.h" +#include "api/test/create_frame_generator.h" +#include "api/test/frame_generator_interface.h" +#include "api/test/video/function_video_encoder_factory.h" +#include "api/video/builtin_video_bitrate_allocator_factory.h" +#include "media/base/media_constants.h" +#include "media/engine/internal_decoder_factory.h" +#include "media/engine/internal_encoder_factory.h" +#include "media/engine/webrtc_video_engine.h" +#include "modules/video_coding/svc/scalability_mode_util.h" +#include "test/call_test.h" +#include "test/fake_encoder.h" +#include "test/scenario/hardware_codecs.h" +#include "test/testsupport/file_utils.h" +#include "video/config/encoder_stream_factory.h" + +namespace webrtc { +namespace test { +namespace { +enum : int { // The first valid value is 1. + kTransportSequenceNumberExtensionId = 1, + kAbsSendTimeExtensionId, + kVideoContentTypeExtensionId, + kVideoRotationRtpExtensionId, +}; + +constexpr int kDefaultMaxQp = cricket::WebRtcVideoChannel::kDefaultQpMax; +uint8_t CodecTypeToPayloadType(VideoCodecType codec_type) { + switch (codec_type) { + case VideoCodecType::kVideoCodecGeneric: + return CallTest::kFakeVideoSendPayloadType; + case VideoCodecType::kVideoCodecVP8: + return CallTest::kPayloadTypeVP8; + case VideoCodecType::kVideoCodecVP9: + return CallTest::kPayloadTypeVP9; + case VideoCodecType::kVideoCodecH264: + return CallTest::kPayloadTypeH264; + default: + RTC_DCHECK_NOTREACHED(); + } + return {}; +} +std::string CodecTypeToCodecName(VideoCodecType codec_type) { + switch (codec_type) { + case VideoCodecType::kVideoCodecGeneric: + return ""; + case VideoCodecType::kVideoCodecVP8: + return cricket::kVp8CodecName; + case VideoCodecType::kVideoCodecVP9: + return cricket::kVp9CodecName; + case VideoCodecType::kVideoCodecH264: + return cricket::kH264CodecName; + default: + RTC_DCHECK_NOTREACHED(); + } + return {}; +} +VideoEncoderConfig::ContentType ConvertContentType( + VideoStreamConfig::Encoder::ContentType content_type) { + switch (content_type) { + case VideoStreamConfig::Encoder::ContentType::kVideo: + return VideoEncoderConfig::ContentType::kRealtimeVideo; + case VideoStreamConfig::Encoder::ContentType::kScreen: + return VideoEncoderConfig::ContentType::kScreen; + } +} + +std::string TransformFilePath(std::string path) { + static const std::string resource_prefix = "res://"; + int ext_pos = path.rfind('.'); + if (ext_pos < 0) { + return test::ResourcePath(path, "yuv"); + } else if (absl::StartsWith(path, resource_prefix)) { + std::string name = path.substr(resource_prefix.length(), ext_pos); + std::string ext = path.substr(ext_pos, path.size()); + return test::ResourcePath(name, ext); + } + return path; +} + +VideoSendStream::Config CreateVideoSendStreamConfig( + VideoStreamConfig config, + std::vector<uint32_t> ssrcs, + std::vector<uint32_t> rtx_ssrcs, + Transport* send_transport) { + VideoSendStream::Config send_config(send_transport); + send_config.rtp.payload_name = CodecTypeToPayloadString(config.encoder.codec); + send_config.rtp.payload_type = CodecTypeToPayloadType(config.encoder.codec); + send_config.rtp.nack.rtp_history_ms = + config.stream.nack_history_time.ms<int>(); + + send_config.rtp.ssrcs = ssrcs; + send_config.rtp.extensions = GetVideoRtpExtensions(config); + + if (config.stream.use_rtx) { + send_config.rtp.rtx.payload_type = CallTest::kSendRtxPayloadType; + send_config.rtp.rtx.ssrcs = rtx_ssrcs; + } + if (config.stream.use_flexfec) { + send_config.rtp.flexfec.payload_type = CallTest::kFlexfecPayloadType; + send_config.rtp.flexfec.ssrc = CallTest::kFlexfecSendSsrc; + send_config.rtp.flexfec.protected_media_ssrcs = ssrcs; + } + if (config.stream.use_ulpfec) { + send_config.rtp.ulpfec.red_payload_type = CallTest::kRedPayloadType; + send_config.rtp.ulpfec.ulpfec_payload_type = CallTest::kUlpfecPayloadType; + send_config.rtp.ulpfec.red_rtx_payload_type = CallTest::kRtxRedPayloadType; + } + return send_config; +} +rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> +CreateVp9SpecificSettings(VideoStreamConfig video_config) { + constexpr auto kScreen = VideoStreamConfig::Encoder::ContentType::kScreen; + VideoStreamConfig::Encoder conf = video_config.encoder; + VideoCodecVP9 vp9 = VideoEncoder::GetDefaultVp9Settings(); + // TODO(bugs.webrtc.org/11607): Support separate scalability mode per + // simulcast stream. + ScalabilityMode scalability_mode = conf.simulcast_streams[0]; + vp9.keyFrameInterval = conf.key_frame_interval.value_or(0); + vp9.numberOfTemporalLayers = + ScalabilityModeToNumTemporalLayers(scalability_mode); + vp9.numberOfSpatialLayers = + ScalabilityModeToNumSpatialLayers(scalability_mode); + vp9.interLayerPred = ScalabilityModeToInterLayerPredMode(scalability_mode); + + if (conf.content_type == kScreen && + (video_config.source.framerate > 5 || vp9.numberOfSpatialLayers >= 3)) { + vp9.flexibleMode = true; + } + + if (conf.content_type == kScreen || vp9.numberOfTemporalLayers > 1 || + vp9.numberOfSpatialLayers > 1) { + vp9.automaticResizeOn = false; + vp9.denoisingOn = false; + } else { + vp9.automaticResizeOn = conf.single.automatic_scaling; + vp9.denoisingOn = conf.single.denoising; + } + return rtc::make_ref_counted<VideoEncoderConfig::Vp9EncoderSpecificSettings>( + vp9); +} + +rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> +CreateVp8SpecificSettings(VideoStreamConfig config) { + VideoCodecVP8 vp8_settings = VideoEncoder::GetDefaultVp8Settings(); + vp8_settings.keyFrameInterval = config.encoder.key_frame_interval.value_or(0); + // TODO(bugs.webrtc.org/11607): Support separate scalability mode per + // simulcast stream. + ScalabilityMode scalability_mode = config.encoder.simulcast_streams[0]; + vp8_settings.numberOfTemporalLayers = + ScalabilityModeToNumTemporalLayers(scalability_mode); + if (vp8_settings.numberOfTemporalLayers > 1 || + config.encoder.simulcast_streams.size() > 1) { + vp8_settings.automaticResizeOn = false; + vp8_settings.denoisingOn = false; + } else { + vp8_settings.automaticResizeOn = config.encoder.single.automatic_scaling; + vp8_settings.denoisingOn = config.encoder.single.denoising; + } + return rtc::make_ref_counted<VideoEncoderConfig::Vp8EncoderSpecificSettings>( + vp8_settings); +} + +rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> +CreateH264SpecificSettings(VideoStreamConfig config) { + RTC_DCHECK_EQ(config.encoder.simulcast_streams.size(), 1); + RTC_DCHECK(config.encoder.simulcast_streams[0] == ScalabilityMode::kL1T1); + // TODO(bugs.webrtc.org/6883): Set a key frame interval as a setting that + // isn't codec specific. + RTC_CHECK_EQ(0, config.encoder.key_frame_interval.value_or(0)); + return nullptr; +} + +rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> +CreateEncoderSpecificSettings(VideoStreamConfig config) { + using Codec = VideoStreamConfig::Encoder::Codec; + switch (config.encoder.codec) { + case Codec::kVideoCodecH264: + return CreateH264SpecificSettings(config); + case Codec::kVideoCodecVP8: + return CreateVp8SpecificSettings(config); + case Codec::kVideoCodecVP9: + return CreateVp9SpecificSettings(config); + case Codec::kVideoCodecGeneric: + case Codec::kVideoCodecAV1: + return nullptr; + case Codec::kVideoCodecMultiplex: + RTC_DCHECK_NOTREACHED(); + return nullptr; + } +} + +VideoEncoderConfig CreateVideoEncoderConfig(VideoStreamConfig config) { + webrtc::VideoEncoder::EncoderInfo encoder_info; + VideoEncoderConfig encoder_config; + encoder_config.codec_type = config.encoder.codec; + encoder_config.content_type = ConvertContentType(config.encoder.content_type); + encoder_config.video_format = + SdpVideoFormat(CodecTypeToPayloadString(config.encoder.codec), {}); + + encoder_config.number_of_streams = config.encoder.simulcast_streams.size(); + encoder_config.simulcast_layers = + std::vector<VideoStream>(encoder_config.number_of_streams); + encoder_config.min_transmit_bitrate_bps = config.stream.pad_to_rate.bps(); + + std::string cricket_codec = CodecTypeToCodecName(config.encoder.codec); + if (!cricket_codec.empty()) { + bool screenshare = config.encoder.content_type == + VideoStreamConfig::Encoder::ContentType::kScreen; + encoder_config.video_stream_factory = + rtc::make_ref_counted<cricket::EncoderStreamFactory>( + cricket_codec, kDefaultMaxQp, screenshare, screenshare, + encoder_info); + } else { + encoder_config.video_stream_factory = + rtc::make_ref_counted<DefaultVideoStreamFactory>(); + } + + // TODO(srte): Base this on encoder capabilities. + encoder_config.max_bitrate_bps = + config.encoder.max_data_rate.value_or(DataRate::KilobitsPerSec(10000)) + .bps(); + + encoder_config.frame_drop_enabled = config.encoder.frame_dropping; + encoder_config.encoder_specific_settings = + CreateEncoderSpecificSettings(config); + + for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { + auto& layer = encoder_config.simulcast_layers[i]; + if (config.encoder.max_framerate) { + layer.max_framerate = *config.encoder.max_framerate; + layer.min_bitrate_bps = config.encoder.min_data_rate->bps_or(-1); + } + layer.scalability_mode = config.encoder.simulcast_streams[i]; + } + + return encoder_config; +} + +std::unique_ptr<FrameGeneratorInterface> CreateImageSlideGenerator( + Clock* clock, + VideoStreamConfig::Source::Slides slides, + int framerate) { + std::vector<std::string> paths = slides.images.paths; + for (std::string& path : paths) + path = TransformFilePath(path); + if (slides.images.crop.width || slides.images.crop.height) { + TimeDelta pause_duration = + slides.change_interval - slides.images.crop.scroll_duration; + RTC_CHECK_GE(pause_duration, TimeDelta::Zero()); + int crop_width = slides.images.crop.width.value_or(slides.images.width); + int crop_height = slides.images.crop.height.value_or(slides.images.height); + RTC_CHECK_LE(crop_width, slides.images.width); + RTC_CHECK_LE(crop_height, slides.images.height); + return CreateScrollingInputFromYuvFilesFrameGenerator( + clock, paths, slides.images.width, slides.images.height, crop_width, + crop_height, slides.images.crop.scroll_duration.ms(), + pause_duration.ms()); + } else { + return CreateFromYuvFileFrameGenerator( + paths, slides.images.width, slides.images.height, + slides.change_interval.seconds<double>() * framerate); + } +} + +std::unique_ptr<FrameGeneratorInterface> CreateFrameGenerator( + Clock* clock, + VideoStreamConfig::Source source) { + using Capture = VideoStreamConfig::Source::Capture; + switch (source.capture) { + case Capture::kGenerator: + return CreateSquareFrameGenerator( + source.generator.width, source.generator.height, + source.generator.pixel_format, /*num_squares*/ absl::nullopt); + case Capture::kVideoFile: + RTC_CHECK(source.video_file.width && source.video_file.height); + return CreateFromYuvFileFrameGenerator( + {TransformFilePath(source.video_file.name)}, source.video_file.width, + source.video_file.height, /*frame_repeat_count*/ 1); + case Capture::kGenerateSlides: + return CreateSlideFrameGenerator( + source.slides.generator.width, source.slides.generator.height, + source.slides.change_interval.seconds<double>() * source.framerate); + case Capture::kImageSlides: + return CreateImageSlideGenerator(clock, source.slides, source.framerate); + } +} + +VideoReceiveStreamInterface::Config CreateVideoReceiveStreamConfig( + VideoStreamConfig config, + Transport* feedback_transport, + VideoDecoderFactory* decoder_factory, + VideoReceiveStreamInterface::Decoder decoder, + rtc::VideoSinkInterface<VideoFrame>* renderer, + uint32_t local_ssrc, + uint32_t ssrc, + uint32_t rtx_ssrc) { + VideoReceiveStreamInterface::Config recv(feedback_transport); + recv.rtp.local_ssrc = local_ssrc; + recv.rtp.extensions = GetVideoRtpExtensions(config); + + RTC_DCHECK(!config.stream.use_rtx || + config.stream.nack_history_time > TimeDelta::Zero()); + recv.rtp.nack.rtp_history_ms = config.stream.nack_history_time.ms(); + recv.rtp.protected_by_flexfec = config.stream.use_flexfec; + recv.rtp.remote_ssrc = ssrc; + recv.decoder_factory = decoder_factory; + recv.decoders.push_back(decoder); + recv.renderer = renderer; + if (config.stream.use_rtx) { + recv.rtp.rtx_ssrc = rtx_ssrc; + recv.rtp.rtx_associated_payload_types[CallTest::kSendRtxPayloadType] = + CodecTypeToPayloadType(config.encoder.codec); + } + if (config.stream.use_ulpfec) { + recv.rtp.red_payload_type = CallTest::kRedPayloadType; + recv.rtp.ulpfec_payload_type = CallTest::kUlpfecPayloadType; + recv.rtp.rtx_associated_payload_types[CallTest::kRtxRedPayloadType] = + CallTest::kRedPayloadType; + } + recv.sync_group = config.render.sync_group; + return recv; +} +} // namespace + +std::vector<RtpExtension> GetVideoRtpExtensions( + const VideoStreamConfig config) { + std::vector<RtpExtension> res = { + RtpExtension(RtpExtension::kVideoContentTypeUri, + kVideoContentTypeExtensionId), + RtpExtension(RtpExtension::kVideoRotationUri, + kVideoRotationRtpExtensionId)}; + if (config.stream.packet_feedback) { + res.push_back(RtpExtension(RtpExtension::kTransportSequenceNumberUri, + kTransportSequenceNumberExtensionId)); + } + if (config.stream.abs_send_time) { + res.push_back( + RtpExtension(RtpExtension::kAbsSendTimeUri, kAbsSendTimeExtensionId)); + } + return res; +} + +SendVideoStream::SendVideoStream(CallClient* sender, + VideoStreamConfig config, + Transport* send_transport, + VideoFrameMatcher* matcher) + : sender_(sender), config_(config) { + video_capturer_ = std::make_unique<FrameGeneratorCapturer>( + sender_->clock_, CreateFrameGenerator(sender_->clock_, config.source), + config.source.framerate, + *sender->time_controller_->GetTaskQueueFactory()); + video_capturer_->Init(); + + using Encoder = VideoStreamConfig::Encoder; + using Codec = VideoStreamConfig::Encoder::Codec; + switch (config.encoder.implementation) { + case Encoder::Implementation::kFake: + encoder_factory_ = + std::make_unique<FunctionVideoEncoderFactory>([this]() { + MutexLock lock(&mutex_); + std::unique_ptr<FakeEncoder> encoder; + if (config_.encoder.codec == Codec::kVideoCodecVP8) { + encoder = std::make_unique<test::FakeVp8Encoder>(sender_->clock_); + } else if (config_.encoder.codec == Codec::kVideoCodecGeneric) { + encoder = std::make_unique<test::FakeEncoder>(sender_->clock_); + } else { + RTC_DCHECK_NOTREACHED(); + } + fake_encoders_.push_back(encoder.get()); + if (config_.encoder.fake.max_rate.IsFinite()) + encoder->SetMaxBitrate(config_.encoder.fake.max_rate.kbps()); + return encoder; + }); + break; + case VideoStreamConfig::Encoder::Implementation::kSoftware: + encoder_factory_.reset(new InternalEncoderFactory()); + break; + case VideoStreamConfig::Encoder::Implementation::kHardware: + encoder_factory_ = CreateHardwareEncoderFactory(); + break; + } + RTC_CHECK(encoder_factory_); + + bitrate_allocator_factory_ = CreateBuiltinVideoBitrateAllocatorFactory(); + RTC_CHECK(bitrate_allocator_factory_); + + VideoEncoderConfig encoder_config = CreateVideoEncoderConfig(config); + for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { + ssrcs_.push_back(sender->GetNextVideoSsrc()); + rtx_ssrcs_.push_back(sender->GetNextRtxSsrc()); + } + VideoSendStream::Config send_config = + CreateVideoSendStreamConfig(config, ssrcs_, rtx_ssrcs_, send_transport); + send_config.encoder_settings.encoder_factory = encoder_factory_.get(); + send_config.encoder_settings.bitrate_allocator_factory = + bitrate_allocator_factory_.get(); + send_config.suspend_below_min_bitrate = + config.encoder.suspend_below_min_bitrate; + + sender_->SendTask([&] { + if (config.stream.fec_controller_factory) { + send_stream_ = sender_->call_->CreateVideoSendStream( + std::move(send_config), std::move(encoder_config), + config.stream.fec_controller_factory->CreateFecController()); + } else { + send_stream_ = sender_->call_->CreateVideoSendStream( + std::move(send_config), std::move(encoder_config)); + } + + if (matcher->Active()) { + frame_tap_ = std::make_unique<ForwardingCapturedFrameTap>( + sender_->clock_, matcher, video_capturer_.get()); + send_stream_->SetSource(frame_tap_.get(), + config.encoder.degradation_preference); + } else { + send_stream_->SetSource(video_capturer_.get(), + config.encoder.degradation_preference); + } + }); +} + +SendVideoStream::~SendVideoStream() { + sender_->SendTask( + [this] { sender_->call_->DestroyVideoSendStream(send_stream_); }); +} + +void SendVideoStream::Start() { + sender_->SendTask([this] { + send_stream_->Start(); + sender_->call_->SignalChannelNetworkState(MediaType::VIDEO, kNetworkUp); + }); +} + +void SendVideoStream::Stop() { + sender_->SendTask([this] { send_stream_->Stop(); }); +} + +void SendVideoStream::UpdateConfig( + std::function<void(VideoStreamConfig*)> modifier) { + sender_->SendTask([&] { + MutexLock lock(&mutex_); + VideoStreamConfig prior_config = config_; + modifier(&config_); + if (prior_config.encoder.fake.max_rate != config_.encoder.fake.max_rate) { + for (auto* encoder : fake_encoders_) { + encoder->SetMaxBitrate(config_.encoder.fake.max_rate.kbps()); + } + } + // TODO(srte): Add more conditions that should cause reconfiguration. + if (prior_config.encoder.max_framerate != config_.encoder.max_framerate || + prior_config.encoder.max_data_rate != config_.encoder.max_data_rate) { + VideoEncoderConfig encoder_config = CreateVideoEncoderConfig(config_); + send_stream_->ReconfigureVideoEncoder(std::move(encoder_config)); + } + if (prior_config.source.framerate != config_.source.framerate) { + SetCaptureFramerate(config_.source.framerate); + } + }); +} + +void SendVideoStream::UpdateActiveLayers(std::vector<bool> active_layers) { + sender_->task_queue_.PostTask([=] { + MutexLock lock(&mutex_); + if (config_.encoder.codec == + VideoStreamConfig::Encoder::Codec::kVideoCodecVP8) { + send_stream_->StartPerRtpStream(active_layers); + } + VideoEncoderConfig encoder_config = CreateVideoEncoderConfig(config_); + RTC_CHECK_EQ(encoder_config.simulcast_layers.size(), active_layers.size()); + for (size_t i = 0; i < encoder_config.simulcast_layers.size(); ++i) + encoder_config.simulcast_layers[i].active = active_layers[i]; + send_stream_->ReconfigureVideoEncoder(std::move(encoder_config)); + }); +} + +bool SendVideoStream::UsingSsrc(uint32_t ssrc) const { + for (uint32_t owned : ssrcs_) { + if (owned == ssrc) + return true; + } + return false; +} + +bool SendVideoStream::UsingRtxSsrc(uint32_t ssrc) const { + for (uint32_t owned : rtx_ssrcs_) { + if (owned == ssrc) + return true; + } + return false; +} + +void SendVideoStream::SetCaptureFramerate(int framerate) { + sender_->SendTask([&] { video_capturer_->ChangeFramerate(framerate); }); +} + +VideoSendStream::Stats SendVideoStream::GetStats() const { + return send_stream_->GetStats(); +} + +ColumnPrinter SendVideoStream::StatsPrinter() { + return ColumnPrinter::Lambda( + "video_target_rate video_sent_rate width height", + [this](rtc::SimpleStringBuilder& sb) { + VideoSendStream::Stats video_stats = send_stream_->GetStats(); + int width = 0; + int height = 0; + for (const auto& stream_stat : video_stats.substreams) { + width = std::max(width, stream_stat.second.width); + height = std::max(height, stream_stat.second.height); + } + sb.AppendFormat("%.0lf %.0lf %i %i", + video_stats.target_media_bitrate_bps / 8.0, + video_stats.media_bitrate_bps / 8.0, width, height); + }, + 64); +} + +ReceiveVideoStream::ReceiveVideoStream(CallClient* receiver, + VideoStreamConfig config, + SendVideoStream* send_stream, + size_t chosen_stream, + Transport* feedback_transport, + VideoFrameMatcher* matcher) + : receiver_(receiver), config_(config) { + if (config.encoder.codec == + VideoStreamConfig::Encoder::Codec::kVideoCodecGeneric || + config.encoder.implementation == VideoStreamConfig::Encoder::kFake) { + decoder_factory_ = std::make_unique<FunctionVideoDecoderFactory>( + []() { return std::make_unique<FakeDecoder>(); }); + } else { + decoder_factory_ = std::make_unique<InternalDecoderFactory>(); + } + + VideoReceiveStreamInterface::Decoder decoder = + CreateMatchingDecoder(CodecTypeToPayloadType(config.encoder.codec), + CodecTypeToPayloadString(config.encoder.codec)); + size_t num_streams = config.encoder.simulcast_streams.size(); + for (size_t i = 0; i < num_streams; ++i) { + rtc::VideoSinkInterface<VideoFrame>* renderer = &fake_renderer_; + if (matcher->Active()) { + render_taps_.emplace_back( + std::make_unique<DecodedFrameTap>(receiver_->clock_, matcher, i)); + renderer = render_taps_.back().get(); + } + auto recv_config = CreateVideoReceiveStreamConfig( + config, feedback_transport, decoder_factory_.get(), decoder, renderer, + receiver_->GetNextVideoLocalSsrc(), send_stream->ssrcs_[i], + send_stream->rtx_ssrcs_[i]); + if (config.stream.use_flexfec) { + RTC_DCHECK(num_streams == 1); + FlexfecReceiveStream::Config flexfec(feedback_transport); + flexfec.payload_type = CallTest::kFlexfecPayloadType; + flexfec.rtp.remote_ssrc = CallTest::kFlexfecSendSsrc; + flexfec.protected_media_ssrcs = send_stream->rtx_ssrcs_; + flexfec.rtp.local_ssrc = recv_config.rtp.local_ssrc; + receiver_->ssrc_media_types_[flexfec.rtp.remote_ssrc] = MediaType::VIDEO; + + receiver_->SendTask([this, &flexfec] { + flecfec_stream_ = receiver_->call_->CreateFlexfecReceiveStream(flexfec); + }); + } + receiver_->ssrc_media_types_[recv_config.rtp.remote_ssrc] = + MediaType::VIDEO; + if (config.stream.use_rtx) + receiver_->ssrc_media_types_[recv_config.rtp.rtx_ssrc] = MediaType::VIDEO; + receiver_->SendTask([this, &recv_config] { + receive_streams_.push_back( + receiver_->call_->CreateVideoReceiveStream(std::move(recv_config))); + }); + } +} + +ReceiveVideoStream::~ReceiveVideoStream() { + receiver_->SendTask([this] { + for (auto* recv_stream : receive_streams_) + receiver_->call_->DestroyVideoReceiveStream(recv_stream); + if (flecfec_stream_) + receiver_->call_->DestroyFlexfecReceiveStream(flecfec_stream_); + }); +} + +void ReceiveVideoStream::Start() { + receiver_->SendTask([this] { + for (auto* recv_stream : receive_streams_) + recv_stream->Start(); + receiver_->call_->SignalChannelNetworkState(MediaType::VIDEO, kNetworkUp); + }); +} + +void ReceiveVideoStream::Stop() { + receiver_->SendTask([this] { + for (auto* recv_stream : receive_streams_) + recv_stream->Stop(); + }); +} + +VideoReceiveStreamInterface::Stats ReceiveVideoStream::GetStats() const { + if (receive_streams_.empty()) + return VideoReceiveStreamInterface::Stats(); + // TODO(srte): Handle multiple receive streams. + return receive_streams_.back()->GetStats(); +} + +VideoStreamPair::~VideoStreamPair() = default; + +VideoStreamPair::VideoStreamPair(CallClient* sender, + CallClient* receiver, + VideoStreamConfig config) + : config_(config), + matcher_(config.hooks.frame_pair_handlers), + send_stream_(sender, config, sender->transport_.get(), &matcher_), + receive_stream_(receiver, + config, + &send_stream_, + /*chosen_stream=*/0, + receiver->transport_.get(), + &matcher_) {} + +} // namespace test +} // namespace webrtc |