srs/trunk/src/utest/srs_utest_rtc_recv_track.cpp
Winlin 04b88e889f AI: Improve coverage of app by utest (#4494)
Co-authored-by: OSSRS-AI <winlinam@gmail.com>
2025-09-17 21:51:07 -04:00

509 lines
16 KiB
C++

//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#include "srs_utest_rtc_recv_track.hpp"
#include <srs_app_rtc_conn.hpp>
#include <srs_app_rtc_dtls.hpp>
#include <srs_app_rtc_source.hpp>
#include <srs_kernel_error.hpp>
using namespace std;
class SrsRtcRecvTrack;
class MockSrsRtcRecvTrack : public SrsRtcRecvTrack
{
private:
static SrsRtcTrackDescription *create_default_track_desc()
{
SrsRtcTrackDescription *desc = new SrsRtcTrackDescription();
// Initialize with minimal required values
desc->type_ = "audio";
desc->id_ = "test_track";
desc->ssrc_ = 12345;
desc->is_active_ = true;
return desc;
}
public:
void receiver_insert(uint16_t first, uint16_t last)
{
nack_receiver_->insert(first, last);
}
void receiver_remove(uint16_t seq)
{
nack_receiver_->remove(seq);
}
SrsRtpNackInfo *receiver_find(uint16_t seq)
{
return nack_receiver_->find(seq);
}
void set_nack_no_copy_for_test(bool v)
{
set_nack_no_copy(v);
}
SrsRtpPacket *get_packet_from_queue(uint16_t seq)
{
return rtp_queue_->at(seq);
}
bool is_queue_empty()
{
return rtp_queue_->empty();
}
int get_queue_size()
{
return rtp_queue_->size();
}
// Implement pure virtual methods from SrsRtcRecvTrack
virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource> &source, SrsRtpPacket *pkt)
{
// Mock implementation - just return success
return srs_success;
}
virtual srs_error_t check_send_nacks()
{
// Mock implementation - just return success
return srs_success;
}
MockSrsRtcRecvTrack()
: SrsRtcRecvTrack(nullptr, create_default_track_desc(), true) {} // true for is_audio
};
VOID TEST(RtcRecvTrackTest, OnNackBasicTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Create a test RTP packet
SrsRtpPacket pkt;
pkt.header_.set_sequence(100);
SrsRtpPacket *ppkt = &pkt;
// Test case 1: NACK info does not exist (new packet)
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// Test case 2: NACK info exists (recovered packet)
recv_track.receiver_insert(101, 102);
SrsRtpPacket pkt2;
pkt2.header_.set_sequence(101);
ppkt = &pkt2;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
}
VOID TEST(RtcRecvTrackTest, OnNackRecoveredPacketTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Insert a sequence into NACK receiver (simulating lost packet)
recv_track.receiver_insert(200, 201);
// Verify the packet is in NACK receiver
EXPECT_TRUE(recv_track.receiver_find(200) != NULL);
// Create recovered packet
SrsRtpPacket pkt;
pkt.header_.set_sequence(200);
SrsRtpPacket *ppkt = &pkt;
// Process the recovered packet
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// Verify the packet was removed from NACK receiver
EXPECT_TRUE(recv_track.receiver_find(200) == NULL);
// Verify the packet was added to RTP queue
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(200);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == 200);
}
VOID TEST(RtcRecvTrackTest, OnNackSequentialPacketsTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Process sequential packets
for (uint16_t seq = 300; seq < 305; seq++) {
SrsRtpPacket pkt;
pkt.header_.set_sequence(seq);
SrsRtpPacket *ppkt = &pkt;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// Verify packet is in queue
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(seq);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == seq);
}
}
VOID TEST(RtcRecvTrackTest, OnNackOutOfOrderPacketsTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Process packets out of order: 400, 402, 401
uint16_t sequences[] = {400, 402, 401};
for (int i = 0; i < 3; i++) {
SrsRtpPacket pkt;
pkt.header_.set_sequence(sequences[i]);
SrsRtpPacket *ppkt = &pkt;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// Verify packet is in queue
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(sequences[i]);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == sequences[i]);
}
}
VOID TEST(RtcRecvTrackTest, OnNackNoCopyModeTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Enable no-copy mode
recv_track.set_nack_no_copy_for_test(true);
// Create a test RTP packet
SrsRtpPacket *pkt = new SrsRtpPacket();
pkt->header_.set_sequence(500);
SrsRtpPacket *ppkt = pkt;
// Process packet in no-copy mode
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// In no-copy mode, ppkt should be set to NULL
EXPECT_TRUE(ppkt == NULL);
// Verify packet is in queue (original packet, not a copy)
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(500);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == 500);
EXPECT_TRUE(queued_pkt == pkt); // Should be the same object
}
VOID TEST(RtcRecvTrackTest, OnNackCopyModeTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Ensure copy mode is enabled (default)
recv_track.set_nack_no_copy_for_test(false);
// Create a test RTP packet
SrsRtpPacket pkt;
pkt.header_.set_sequence(600);
SrsRtpPacket *ppkt = &pkt;
// Process packet in copy mode
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// In copy mode, ppkt should remain unchanged
EXPECT_TRUE(ppkt == &pkt);
// Verify packet is in queue (should be a copy)
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(600);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == 600);
EXPECT_TRUE(queued_pkt != &pkt); // Should be a different object (copy)
}
VOID TEST(RtcRecvTrackTest, OnNackSequenceWrapAroundTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Test sequence number wrap-around (65535 -> 0)
uint16_t sequences[] = {65534, 65535, 0, 1, 2};
for (int i = 0; i < 5; i++) {
SrsRtpPacket pkt;
pkt.header_.set_sequence(sequences[i]);
SrsRtpPacket *ppkt = &pkt;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// Verify packet is in queue
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(sequences[i]);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == sequences[i]);
}
}
VOID TEST(RtcRecvTrackTest, OnNackMixedRecoveredAndNewPacketsTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Insert some sequences into NACK receiver (simulating lost packets)
recv_track.receiver_insert(700, 702); // 700, 701 are "lost"
// Verify packets are in NACK receiver
EXPECT_TRUE(recv_track.receiver_find(700) != NULL);
EXPECT_TRUE(recv_track.receiver_find(701) != NULL);
// Process mixed sequence: new packet (699), recovered packet (700), new packet (702), recovered packet (701)
uint16_t sequences[] = {699, 700, 702, 701};
bool should_be_in_nack[] = {false, true, false, true};
for (int i = 0; i < 4; i++) {
SrsRtpPacket pkt;
pkt.header_.set_sequence(sequences[i]);
SrsRtpPacket *ppkt = &pkt;
// Check if packet is in NACK receiver before processing
bool was_in_nack = (recv_track.receiver_find(sequences[i]) != NULL);
EXPECT_TRUE(was_in_nack == should_be_in_nack[i]);
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// Verify packet was removed from NACK receiver if it was there
if (was_in_nack) {
EXPECT_TRUE(recv_track.receiver_find(sequences[i]) == NULL);
}
// Verify packet is in queue
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(sequences[i]);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == sequences[i]);
}
}
VOID TEST(RtcRecvTrackTest, OnNackDuplicatePacketTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Process the same packet twice
uint16_t seq = 800;
// First time processing
SrsRtpPacket pkt1;
pkt1.header_.set_sequence(seq);
SrsRtpPacket *ppkt1 = &pkt1;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt1));
// Verify packet is in queue
SrsRtpPacket *queued_pkt1 = recv_track.get_packet_from_queue(seq);
EXPECT_TRUE(queued_pkt1 != NULL);
EXPECT_TRUE(queued_pkt1->header_.get_sequence() == seq);
// Second time processing (duplicate)
SrsRtpPacket pkt2;
pkt2.header_.set_sequence(seq);
SrsRtpPacket *ppkt2 = &pkt2;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt2));
// Verify the packet in queue is replaced (should be the new one)
SrsRtpPacket *queued_pkt2 = recv_track.get_packet_from_queue(seq);
EXPECT_TRUE(queued_pkt2 != NULL);
EXPECT_TRUE(queued_pkt2->header_.get_sequence() == seq);
// In copy mode, it should be a different object
EXPECT_TRUE(queued_pkt2 != queued_pkt1);
}
VOID TEST(RtcRecvTrackTest, OnNackLargeGapTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Process packets with large gap to test NACK range handling
uint16_t seq1 = 900;
uint16_t seq2 = 950; // Gap of 50 packets
// Process first packet
SrsRtpPacket pkt1;
pkt1.header_.set_sequence(seq1);
SrsRtpPacket *ppkt1 = &pkt1;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt1));
// Process second packet with large gap
SrsRtpPacket pkt2;
pkt2.header_.set_sequence(seq2);
SrsRtpPacket *ppkt2 = &pkt2;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt2));
// Verify both packets are in queue
SrsRtpPacket *queued_pkt1 = recv_track.get_packet_from_queue(seq1);
EXPECT_TRUE(queued_pkt1 != NULL);
EXPECT_TRUE(queued_pkt1->header_.get_sequence() == seq1);
SrsRtpPacket *queued_pkt2 = recv_track.get_packet_from_queue(seq2);
EXPECT_TRUE(queued_pkt2 != NULL);
EXPECT_TRUE(queued_pkt2->header_.get_sequence() == seq2);
}
VOID TEST(RtcRecvTrackTest, OnNackRecoveredPacketRemovedFromNackTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Insert multiple sequences into NACK receiver
recv_track.receiver_insert(1000, 1005); // 1000, 1001, 1002, 1003, 1004
// Verify all packets are in NACK receiver
for (uint16_t seq = 1000; seq < 1005; seq++) {
EXPECT_TRUE(recv_track.receiver_find(seq) != NULL);
}
// Recover some packets (1001, 1003)
uint16_t recovered_seqs[] = {1001, 1003};
for (int i = 0; i < 2; i++) {
SrsRtpPacket pkt;
pkt.header_.set_sequence(recovered_seqs[i]);
SrsRtpPacket *ppkt = &pkt;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// Verify recovered packet is removed from NACK receiver
EXPECT_TRUE(recv_track.receiver_find(recovered_seqs[i]) == NULL);
// Verify packet is in queue
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(recovered_seqs[i]);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == recovered_seqs[i]);
}
// Verify non-recovered packets are still in NACK receiver
uint16_t non_recovered_seqs[] = {1000, 1002, 1004};
for (int i = 0; i < 3; i++) {
EXPECT_TRUE(recv_track.receiver_find(non_recovered_seqs[i]) != NULL);
}
}
VOID TEST(RtcRecvTrackTest, OnNackBugFixVerificationTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// This test specifically verifies the bug fix from PR #4467
// The bug was that recovered packets were not being added to the RTP queue
// Step 1: Simulate lost packet by adding to NACK receiver
uint16_t lost_seq = 1100;
recv_track.receiver_insert(lost_seq, lost_seq + 1);
// Verify packet is in NACK receiver (lost)
EXPECT_TRUE(recv_track.receiver_find(lost_seq) != NULL);
// Step 2: Simulate packet recovery (retransmission arrives)
SrsRtpPacket recovered_pkt;
recovered_pkt.header_.set_sequence(lost_seq);
SrsRtpPacket *ppkt = &recovered_pkt;
// Step 3: Process the recovered packet
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// Step 4: Verify the fix - packet should be removed from NACK receiver
EXPECT_TRUE(recv_track.receiver_find(lost_seq) == NULL);
// Step 5: Verify the fix - packet should be added to RTP queue (this was the bug)
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(lost_seq);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == lost_seq);
// This test ensures that recovered packets are properly processed and not discarded
}
VOID TEST(RtcRecvTrackTest, OnNackControlFlowTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Test the control flow differences between recovered and new packets
// Scenario 1: New packet (not in NACK receiver)
uint16_t new_seq = 1200;
SrsRtpPacket new_pkt;
new_pkt.header_.set_sequence(new_seq);
SrsRtpPacket *new_ppkt = &new_pkt;
// Should not be in NACK receiver initially
EXPECT_TRUE(recv_track.receiver_find(new_seq) == NULL);
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&new_ppkt));
// New packet should be added to queue
SrsRtpPacket *new_queued = recv_track.get_packet_from_queue(new_seq);
EXPECT_TRUE(new_queued != NULL);
EXPECT_TRUE(new_queued->header_.get_sequence() == new_seq);
// Scenario 2: Recovered packet (in NACK receiver)
uint16_t recovered_seq = 1201;
recv_track.receiver_insert(recovered_seq, recovered_seq + 1);
// Should be in NACK receiver initially
EXPECT_TRUE(recv_track.receiver_find(recovered_seq) != NULL);
SrsRtpPacket recovered_pkt;
recovered_pkt.header_.set_sequence(recovered_seq);
SrsRtpPacket *recovered_ppkt = &recovered_pkt;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&recovered_ppkt));
// Recovered packet should be removed from NACK receiver
EXPECT_TRUE(recv_track.receiver_find(recovered_seq) == NULL);
// Recovered packet should also be added to queue (the fix)
SrsRtpPacket *recovered_queued = recv_track.get_packet_from_queue(recovered_seq);
EXPECT_TRUE(recovered_queued != NULL);
EXPECT_TRUE(recovered_queued->header_.get_sequence() == recovered_seq);
}
VOID TEST(RtcRecvTrackTest, OnNackStressTest)
{
srs_error_t err;
MockSrsRtcRecvTrack recv_track;
// Stress test with many packets, mixed recovered and new
const int num_packets = 100;
const uint16_t base_seq = 2000;
// Insert every other packet into NACK receiver (simulate 50% loss)
for (int i = 0; i < num_packets; i += 2) {
uint16_t seq = base_seq + i;
recv_track.receiver_insert(seq, seq + 1);
}
// Process all packets
for (int i = 0; i < num_packets; i++) {
uint16_t seq = base_seq + i;
bool should_be_recovered = (i % 2 == 0);
// Verify initial state
bool is_in_nack = (recv_track.receiver_find(seq) != NULL);
EXPECT_TRUE(is_in_nack == should_be_recovered);
SrsRtpPacket pkt;
pkt.header_.set_sequence(seq);
SrsRtpPacket *ppkt = &pkt;
HELPER_EXPECT_SUCCESS(recv_track.on_nack(&ppkt));
// Verify packet was removed from NACK receiver if it was there
EXPECT_TRUE(recv_track.receiver_find(seq) == NULL);
// Verify packet is in queue (the key fix)
SrsRtpPacket *queued_pkt = recv_track.get_packet_from_queue(seq);
EXPECT_TRUE(queued_pkt != NULL);
EXPECT_TRUE(queued_pkt->header_.get_sequence() == seq);
}
}