437 lines
16 KiB
C++
437 lines
16 KiB
C++
/*
|
|
* Copyright (c) 2013 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 <memory>
|
|
|
|
#include "api/test/simulated_network.h"
|
|
#include "api/test/video/function_video_encoder_factory.h"
|
|
#include "call/fake_network_pipe.h"
|
|
#include "call/simulated_network.h"
|
|
#include "media/engine/internal_encoder_factory.h"
|
|
#include "media/engine/simulcast_encoder_adapter.h"
|
|
#include "modules/rtp_rtcp/source/create_video_rtp_depacketizer.h"
|
|
#include "modules/rtp_rtcp/source/rtp_packet.h"
|
|
#include "modules/video_coding/codecs/vp8/include/vp8.h"
|
|
#include "modules/video_coding/codecs/vp9/include/vp9.h"
|
|
#include "rtc_base/numerics/safe_conversions.h"
|
|
#include "rtc_base/numerics/sequence_number_util.h"
|
|
#include "rtc_base/synchronization/mutex.h"
|
|
#include "rtc_base/task_queue_for_test.h"
|
|
#include "test/call_test.h"
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
const int kFrameMaxWidth = 1280;
|
|
const int kFrameMaxHeight = 720;
|
|
const int kFrameRate = 30;
|
|
const int kMaxSecondsLost = 5;
|
|
const int kMaxFramesLost = kFrameRate * kMaxSecondsLost;
|
|
const int kMinPacketsToObserve = 10;
|
|
const int kEncoderBitrateBps = 300000;
|
|
const uint32_t kPictureIdWraparound = (1 << 15);
|
|
const size_t kNumTemporalLayers[] = {1, 2, 3};
|
|
|
|
} // namespace
|
|
|
|
class PictureIdObserver : public test::RtpRtcpObserver {
|
|
public:
|
|
explicit PictureIdObserver(VideoCodecType codec_type)
|
|
: test::RtpRtcpObserver(test::CallTest::kDefaultTimeout),
|
|
depacketizer_(CreateVideoRtpDepacketizer(codec_type)),
|
|
max_expected_picture_id_gap_(0),
|
|
max_expected_tl0_idx_gap_(0),
|
|
num_ssrcs_to_observe_(1) {}
|
|
|
|
void SetExpectedSsrcs(size_t num_expected_ssrcs) {
|
|
MutexLock lock(&mutex_);
|
|
num_ssrcs_to_observe_ = num_expected_ssrcs;
|
|
}
|
|
|
|
void ResetObservedSsrcs() {
|
|
MutexLock lock(&mutex_);
|
|
// Do not clear the timestamp and picture_id, to ensure that we check
|
|
// consistency between reinits and recreations.
|
|
num_packets_sent_.clear();
|
|
observed_ssrcs_.clear();
|
|
}
|
|
|
|
void SetMaxExpectedPictureIdGap(int max_expected_picture_id_gap) {
|
|
MutexLock lock(&mutex_);
|
|
max_expected_picture_id_gap_ = max_expected_picture_id_gap;
|
|
// Expect smaller gap for `tl0_pic_idx` (running index for temporal_idx 0).
|
|
max_expected_tl0_idx_gap_ = max_expected_picture_id_gap_ / 2;
|
|
}
|
|
|
|
private:
|
|
struct ParsedPacket {
|
|
uint32_t timestamp;
|
|
uint32_t ssrc;
|
|
int16_t picture_id;
|
|
int16_t tl0_pic_idx;
|
|
uint8_t temporal_idx;
|
|
VideoFrameType frame_type;
|
|
};
|
|
|
|
bool ParsePayload(const uint8_t* packet,
|
|
size_t length,
|
|
ParsedPacket* parsed) const {
|
|
RtpPacket rtp_packet;
|
|
EXPECT_TRUE(rtp_packet.Parse(packet, length));
|
|
EXPECT_TRUE(rtp_packet.Ssrc() == test::CallTest::kVideoSendSsrcs[0] ||
|
|
rtp_packet.Ssrc() == test::CallTest::kVideoSendSsrcs[1] ||
|
|
rtp_packet.Ssrc() == test::CallTest::kVideoSendSsrcs[2])
|
|
<< "Unknown SSRC sent.";
|
|
|
|
if (rtp_packet.payload_size() == 0) {
|
|
return false; // Padding packet.
|
|
}
|
|
|
|
parsed->timestamp = rtp_packet.Timestamp();
|
|
parsed->ssrc = rtp_packet.Ssrc();
|
|
|
|
absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed_payload =
|
|
depacketizer_->Parse(rtp_packet.PayloadBuffer());
|
|
EXPECT_TRUE(parsed_payload);
|
|
|
|
if (const auto* vp8_header = absl::get_if<RTPVideoHeaderVP8>(
|
|
&parsed_payload->video_header.video_type_header)) {
|
|
parsed->picture_id = vp8_header->pictureId;
|
|
parsed->tl0_pic_idx = vp8_header->tl0PicIdx;
|
|
parsed->temporal_idx = vp8_header->temporalIdx;
|
|
} else if (const auto* vp9_header = absl::get_if<RTPVideoHeaderVP9>(
|
|
&parsed_payload->video_header.video_type_header)) {
|
|
parsed->picture_id = vp9_header->picture_id;
|
|
parsed->tl0_pic_idx = vp9_header->tl0_pic_idx;
|
|
parsed->temporal_idx = vp9_header->temporal_idx;
|
|
} else {
|
|
RTC_DCHECK_NOTREACHED();
|
|
}
|
|
|
|
parsed->frame_type = parsed_payload->video_header.frame_type;
|
|
return true;
|
|
}
|
|
|
|
// Verify continuity and monotonicity of picture_id sequence.
|
|
void VerifyPictureId(const ParsedPacket& current,
|
|
const ParsedPacket& last) const
|
|
RTC_EXCLUSIVE_LOCKS_REQUIRED(&mutex_) {
|
|
if (current.timestamp == last.timestamp) {
|
|
EXPECT_EQ(last.picture_id, current.picture_id);
|
|
return; // Same frame.
|
|
}
|
|
|
|
// Packet belongs to a new frame.
|
|
// Picture id should be increasing.
|
|
EXPECT_TRUE((AheadOf<uint16_t, kPictureIdWraparound>(current.picture_id,
|
|
last.picture_id)));
|
|
|
|
// Expect continuously increasing picture id.
|
|
int diff = ForwardDiff<uint16_t, kPictureIdWraparound>(last.picture_id,
|
|
current.picture_id);
|
|
EXPECT_LE(diff - 1, max_expected_picture_id_gap_);
|
|
if (diff > 2) {
|
|
// If the VideoSendStream is destroyed, any frames still in queue is lost.
|
|
// This can result in a two-frame gap, which will result in logs like
|
|
// "packet transmission failed, no matching RTP module found, or
|
|
// transmission error".
|
|
// A larger gap is only possible for first frame after a recreation, i.e.
|
|
// key frames.
|
|
EXPECT_EQ(VideoFrameType::kVideoFrameKey, current.frame_type);
|
|
}
|
|
}
|
|
|
|
void VerifyTl0Idx(const ParsedPacket& current, const ParsedPacket& last) const
|
|
RTC_EXCLUSIVE_LOCKS_REQUIRED(&mutex_) {
|
|
if (current.tl0_pic_idx == kNoTl0PicIdx ||
|
|
current.temporal_idx == kNoTemporalIdx) {
|
|
return; // No temporal layers.
|
|
}
|
|
|
|
if (current.timestamp == last.timestamp || current.temporal_idx != 0) {
|
|
EXPECT_EQ(last.tl0_pic_idx, current.tl0_pic_idx);
|
|
return;
|
|
}
|
|
|
|
// New frame with `temporal_idx` 0.
|
|
// `tl0_pic_idx` should be increasing.
|
|
EXPECT_TRUE(AheadOf<uint8_t>(current.tl0_pic_idx, last.tl0_pic_idx));
|
|
|
|
// Expect continuously increasing idx.
|
|
int diff = ForwardDiff<uint8_t>(last.tl0_pic_idx, current.tl0_pic_idx);
|
|
if (diff > 1) {
|
|
// If the VideoSendStream is destroyed, any frames still in queue is lost.
|
|
// Gaps only possible for first frame after a recreation, i.e. key frames.
|
|
EXPECT_EQ(VideoFrameType::kVideoFrameKey, current.frame_type);
|
|
EXPECT_LE(diff - 1, max_expected_tl0_idx_gap_);
|
|
}
|
|
}
|
|
|
|
Action OnSendRtp(const uint8_t* packet, size_t length) override {
|
|
MutexLock lock(&mutex_);
|
|
|
|
ParsedPacket parsed;
|
|
if (!ParsePayload(packet, length, &parsed))
|
|
return SEND_PACKET;
|
|
|
|
uint32_t ssrc = parsed.ssrc;
|
|
if (last_observed_packet_.find(ssrc) != last_observed_packet_.end()) {
|
|
// Compare to last packet.
|
|
VerifyPictureId(parsed, last_observed_packet_[ssrc]);
|
|
VerifyTl0Idx(parsed, last_observed_packet_[ssrc]);
|
|
}
|
|
|
|
last_observed_packet_[ssrc] = parsed;
|
|
|
|
// Pass the test when enough media packets have been received on all
|
|
// streams.
|
|
if (++num_packets_sent_[ssrc] >= kMinPacketsToObserve &&
|
|
observed_ssrcs_.find(ssrc) == observed_ssrcs_.end()) {
|
|
observed_ssrcs_.insert(ssrc);
|
|
if (observed_ssrcs_.size() == num_ssrcs_to_observe_) {
|
|
observation_complete_.Set();
|
|
}
|
|
}
|
|
return SEND_PACKET;
|
|
}
|
|
|
|
Mutex mutex_;
|
|
const std::unique_ptr<VideoRtpDepacketizer> depacketizer_;
|
|
std::map<uint32_t, ParsedPacket> last_observed_packet_ RTC_GUARDED_BY(mutex_);
|
|
std::map<uint32_t, size_t> num_packets_sent_ RTC_GUARDED_BY(mutex_);
|
|
int max_expected_picture_id_gap_ RTC_GUARDED_BY(mutex_);
|
|
int max_expected_tl0_idx_gap_ RTC_GUARDED_BY(mutex_);
|
|
size_t num_ssrcs_to_observe_ RTC_GUARDED_BY(mutex_);
|
|
std::set<uint32_t> observed_ssrcs_ RTC_GUARDED_BY(mutex_);
|
|
};
|
|
|
|
class PictureIdTest : public test::CallTest,
|
|
public ::testing::WithParamInterface<size_t> {
|
|
public:
|
|
PictureIdTest() : num_temporal_layers_(GetParam()) {}
|
|
|
|
virtual ~PictureIdTest() {
|
|
SendTask(task_queue(), [this]() {
|
|
send_transport_.reset();
|
|
receive_transport_.reset();
|
|
DestroyCalls();
|
|
});
|
|
}
|
|
|
|
void SetupEncoder(VideoEncoderFactory* encoder_factory,
|
|
const std::string& payload_name);
|
|
void SetVideoEncoderConfig(int num_streams);
|
|
void TestPictureIdContinuousAfterReconfigure(
|
|
const std::vector<int>& ssrc_counts);
|
|
void TestPictureIdIncreaseAfterRecreateStreams(
|
|
const std::vector<int>& ssrc_counts);
|
|
|
|
private:
|
|
const size_t num_temporal_layers_;
|
|
std::unique_ptr<PictureIdObserver> observer_;
|
|
};
|
|
|
|
// TODO(bugs.webrtc.org/13725): Enable on android when flakiness fixed.
|
|
#if defined(WEBRTC_ANDROID)
|
|
#define MAYBE_TemporalLayers DISABLED_TemporalLayers
|
|
#else
|
|
#define MAYBE_TemporalLayers TemporalLayers
|
|
#endif
|
|
|
|
INSTANTIATE_TEST_SUITE_P(MAYBE_TemporalLayers,
|
|
PictureIdTest,
|
|
::testing::ValuesIn(kNumTemporalLayers));
|
|
|
|
void PictureIdTest::SetupEncoder(VideoEncoderFactory* encoder_factory,
|
|
const std::string& payload_name) {
|
|
observer_.reset(
|
|
new PictureIdObserver(PayloadStringToCodecType(payload_name)));
|
|
|
|
SendTask(
|
|
task_queue(), [this, encoder_factory, payload_name]() {
|
|
CreateCalls();
|
|
|
|
send_transport_.reset(new test::PacketTransport(
|
|
task_queue(), sender_call_.get(), observer_.get(),
|
|
test::PacketTransport::kSender, payload_type_map_,
|
|
std::make_unique<FakeNetworkPipe>(
|
|
Clock::GetRealTimeClock(),
|
|
std::make_unique<SimulatedNetwork>(
|
|
BuiltInNetworkBehaviorConfig()))));
|
|
|
|
CreateSendConfig(kNumSimulcastStreams, 0, 0, send_transport_.get());
|
|
GetVideoSendConfig()->encoder_settings.encoder_factory =
|
|
encoder_factory;
|
|
GetVideoSendConfig()->rtp.payload_name = payload_name;
|
|
GetVideoEncoderConfig()->codec_type =
|
|
PayloadStringToCodecType(payload_name);
|
|
SetVideoEncoderConfig(/* number_of_streams */ 1);
|
|
});
|
|
}
|
|
|
|
void PictureIdTest::SetVideoEncoderConfig(int num_streams) {
|
|
GetVideoEncoderConfig()->number_of_streams = num_streams;
|
|
GetVideoEncoderConfig()->max_bitrate_bps = kEncoderBitrateBps;
|
|
|
|
// Always divide the same total bitrate across all streams so that sending a
|
|
// single stream avoids lowering the bitrate estimate and requiring a
|
|
// subsequent rampup.
|
|
const int encoder_stream_bps = kEncoderBitrateBps / num_streams;
|
|
double scale_factor = 1.0;
|
|
for (int i = num_streams - 1; i >= 0; --i) {
|
|
VideoStream& stream = GetVideoEncoderConfig()->simulcast_layers[i];
|
|
// Reduce the min bitrate by 10% to account for overhead that might
|
|
// otherwise cause streams to not be enabled.
|
|
stream.min_bitrate_bps = static_cast<int>(encoder_stream_bps * 0.9);
|
|
stream.target_bitrate_bps = encoder_stream_bps;
|
|
stream.max_bitrate_bps = encoder_stream_bps;
|
|
stream.num_temporal_layers = num_temporal_layers_;
|
|
stream.scale_resolution_down_by = scale_factor;
|
|
scale_factor *= 2.0;
|
|
}
|
|
}
|
|
|
|
void PictureIdTest::TestPictureIdContinuousAfterReconfigure(
|
|
const std::vector<int>& ssrc_counts) {
|
|
SendTask(task_queue(), [this]() {
|
|
CreateVideoStreams();
|
|
CreateFrameGeneratorCapturer(kFrameRate, kFrameMaxWidth, kFrameMaxHeight);
|
|
|
|
// Initial test with a single stream.
|
|
Start();
|
|
});
|
|
|
|
EXPECT_TRUE(observer_->Wait()) << "Timed out waiting for packets.";
|
|
|
|
// Reconfigure VideoEncoder and test picture id increase.
|
|
// Expect continuously increasing picture id, equivalent to no gaps.
|
|
observer_->SetMaxExpectedPictureIdGap(0);
|
|
for (int ssrc_count : ssrc_counts) {
|
|
SetVideoEncoderConfig(ssrc_count);
|
|
observer_->SetExpectedSsrcs(ssrc_count);
|
|
observer_->ResetObservedSsrcs();
|
|
// Make sure the picture_id sequence is continuous on reinit and recreate.
|
|
SendTask(task_queue(), [this]() {
|
|
GetVideoSendStream()->ReconfigureVideoEncoder(
|
|
GetVideoEncoderConfig()->Copy());
|
|
});
|
|
EXPECT_TRUE(observer_->Wait()) << "Timed out waiting for packets.";
|
|
}
|
|
|
|
SendTask(task_queue(), [this]() {
|
|
Stop();
|
|
DestroyStreams();
|
|
});
|
|
}
|
|
|
|
void PictureIdTest::TestPictureIdIncreaseAfterRecreateStreams(
|
|
const std::vector<int>& ssrc_counts) {
|
|
SendTask(task_queue(), [this]() {
|
|
CreateVideoStreams();
|
|
CreateFrameGeneratorCapturer(kFrameRate, kFrameMaxWidth, kFrameMaxHeight);
|
|
|
|
// Initial test with a single stream.
|
|
Start();
|
|
});
|
|
|
|
EXPECT_TRUE(observer_->Wait()) << "Timed out waiting for packets.";
|
|
|
|
// Recreate VideoSendStream and test picture id increase.
|
|
// When the VideoSendStream is destroyed, any frames still in queue is lost
|
|
// with it, therefore it is expected that some frames might be lost.
|
|
observer_->SetMaxExpectedPictureIdGap(kMaxFramesLost);
|
|
for (int ssrc_count : ssrc_counts) {
|
|
SendTask(task_queue(), [this, &ssrc_count]() {
|
|
DestroyVideoSendStreams();
|
|
|
|
SetVideoEncoderConfig(ssrc_count);
|
|
observer_->SetExpectedSsrcs(ssrc_count);
|
|
observer_->ResetObservedSsrcs();
|
|
|
|
CreateVideoSendStreams();
|
|
GetVideoSendStream()->Start();
|
|
CreateFrameGeneratorCapturer(kFrameRate, kFrameMaxWidth, kFrameMaxHeight);
|
|
});
|
|
|
|
EXPECT_TRUE(observer_->Wait()) << "Timed out waiting for packets.";
|
|
}
|
|
|
|
SendTask(task_queue(), [this]() {
|
|
Stop();
|
|
DestroyStreams();
|
|
});
|
|
}
|
|
|
|
TEST_P(PictureIdTest, ContinuousAfterReconfigureVp8) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
SetupEncoder(&encoder_factory, "VP8");
|
|
TestPictureIdContinuousAfterReconfigure({1, 3, 3, 1, 1});
|
|
}
|
|
|
|
TEST_P(PictureIdTest, IncreasingAfterRecreateStreamVp8) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
SetupEncoder(&encoder_factory, "VP8");
|
|
TestPictureIdIncreaseAfterRecreateStreams({1, 3, 3, 1, 1});
|
|
}
|
|
|
|
TEST_P(PictureIdTest, ContinuousAfterStreamCountChangeVp8) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP8Encoder::Create(); });
|
|
// Make sure that the picture id is not reset if the stream count goes
|
|
// down and then up.
|
|
SetupEncoder(&encoder_factory, "VP8");
|
|
TestPictureIdContinuousAfterReconfigure({3, 1, 3});
|
|
}
|
|
|
|
TEST_P(PictureIdTest, ContinuousAfterReconfigureSimulcastEncoderAdapter) {
|
|
InternalEncoderFactory internal_encoder_factory;
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[&internal_encoder_factory]() {
|
|
return std::make_unique<SimulcastEncoderAdapter>(
|
|
&internal_encoder_factory, SdpVideoFormat("VP8"));
|
|
});
|
|
SetupEncoder(&encoder_factory, "VP8");
|
|
TestPictureIdContinuousAfterReconfigure({1, 3, 3, 1, 1});
|
|
}
|
|
|
|
TEST_P(PictureIdTest, IncreasingAfterRecreateStreamSimulcastEncoderAdapter) {
|
|
InternalEncoderFactory internal_encoder_factory;
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[&internal_encoder_factory]() {
|
|
return std::make_unique<SimulcastEncoderAdapter>(
|
|
&internal_encoder_factory, SdpVideoFormat("VP8"));
|
|
});
|
|
SetupEncoder(&encoder_factory, "VP8");
|
|
TestPictureIdIncreaseAfterRecreateStreams({1, 3, 3, 1, 1});
|
|
}
|
|
|
|
TEST_P(PictureIdTest, ContinuousAfterStreamCountChangeSimulcastEncoderAdapter) {
|
|
InternalEncoderFactory internal_encoder_factory;
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[&internal_encoder_factory]() {
|
|
return std::make_unique<SimulcastEncoderAdapter>(
|
|
&internal_encoder_factory, SdpVideoFormat("VP8"));
|
|
});
|
|
// Make sure that the picture id is not reset if the stream count goes
|
|
// down and then up.
|
|
SetupEncoder(&encoder_factory, "VP8");
|
|
TestPictureIdContinuousAfterReconfigure({3, 1, 3});
|
|
}
|
|
|
|
TEST_P(PictureIdTest, IncreasingAfterRecreateStreamVp9) {
|
|
test::FunctionVideoEncoderFactory encoder_factory(
|
|
[]() { return VP9Encoder::Create(); });
|
|
SetupEncoder(&encoder_factory, "VP9");
|
|
TestPictureIdIncreaseAfterRecreateStreams({1, 1});
|
|
}
|
|
|
|
} // namespace webrtc
|