RTC: Extract the sample rate from the SDP to facilitate swift a/v synchronization.
This commit is contained in:
parent
8f1578e0e3
commit
5d928619f6
2
trunk/configure
vendored
2
trunk/configure
vendored
|
|
@ -386,7 +386,7 @@ if [[ $SRS_UTEST == YES ]]; then
|
|||
MODULE_FILES+=("srs_utest_ai01" "srs_utest_ai02" "srs_utest_ai03" "srs_utest_ai04" "srs_utest_ai05"
|
||||
"srs_utest_ai06" "srs_utest_ai07" "srs_utest_ai08" "srs_utest_ai09" "srs_utest_ai10" "srs_utest_ai11"
|
||||
"srs_utest_ai12" "srs_utest_ai13" "srs_utest_ai14" "srs_utest_ai15" "srs_utest_ai16" "srs_utest_ai17"
|
||||
"srs_utest_ai18" "srs_utest_ai19" "srs_utest_ai20" "srs_utest_workflow_forward")
|
||||
"srs_utest_ai18" "srs_utest_ai19" "srs_utest_ai20" "srs_utest_ai24" "srs_utest_workflow_forward")
|
||||
if [[ $SRS_GB28181 == YES ]]; then
|
||||
MODULE_FILES+=("srs_utest_manual_gb28181" "srs_utest_ai23")
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1820,8 +1820,9 @@ srs_error_t SrsRtcFrameBuilder::on_rtp(SrsRtpPacket *pkt)
|
|||
return err;
|
||||
}
|
||||
|
||||
// Have no received any sender report, can't calculate avsync_time,
|
||||
// discard it to avoid timestamp problem in live source
|
||||
// Check if avsync_time is valid (> 0).
|
||||
// NOTE: This check should NEVER fail unless SDP has no sample rate, in which case packets are discarded
|
||||
// to avoid timestamp problems in live source.
|
||||
const SrsRtpHeader &h = pkt->header_;
|
||||
if (pkt->get_avsync_time() <= 0) {
|
||||
if (sync_state_ < 0) {
|
||||
|
|
@ -3105,7 +3106,17 @@ SrsRtcRecvTrack::SrsRtcRecvTrack(ISrsRtcPacketReceiver *receiver, SrsRtcTrackDes
|
|||
|
||||
last_sender_report_rtp_time_ = 0;
|
||||
last_sender_report_rtp_time1_ = 0;
|
||||
|
||||
// Initialize rate from SDP sample rate
|
||||
// rate_ is RTP units per millisecond (e.g., 90 for video 90kHz, 48 for audio 48kHz)
|
||||
// This allows immediate A/V sync before receiving 2 RTCP SR packets
|
||||
// Will be updated to precise rate after receiving 2nd SR
|
||||
rate_ = 0.0;
|
||||
if (track_desc_->media_) {
|
||||
rate_ = static_cast<double>(track_desc_->media_->sample_) / 1000.0;
|
||||
srs_trace("RTC: Init %s track, ssrc=%u, rate from SDP=%.0f (RTP units per ms, will be updated after 2nd SR)",
|
||||
track_desc_->type_.c_str(), track_desc_->ssrc_, rate_);
|
||||
}
|
||||
|
||||
last_sender_report_sys_time_ = 0;
|
||||
}
|
||||
|
|
@ -3163,8 +3174,14 @@ void SrsRtcRecvTrack::update_send_report_time(const SrsNtp &ntp, uint32_t rtp_ti
|
|||
double rtp_time_elpased = static_cast<double>(last_sender_report_rtp_time_) - static_cast<double>(last_sender_report_rtp_time1_);
|
||||
double rate = round(rtp_time_elpased / sys_time_elapsed);
|
||||
|
||||
// TODO: FIXME: use the sample rate from sdp.
|
||||
if (rate > 0) {
|
||||
if (rate_ != rate) {
|
||||
srs_warn("RTC: SR update %s, ssrc=%u, ntp_ms=%u->%u (delta=%.0fms), rtp_time=%u->%u (delta=%.0f), rate %.0f->%.0f",
|
||||
track_desc_->type_.c_str(), track_desc_->ssrc_,
|
||||
last_sender_report_ntp1_.system_ms_, last_sender_report_ntp_.system_ms_, sys_time_elapsed,
|
||||
(uint32_t)last_sender_report_rtp_time1_, (uint32_t)last_sender_report_rtp_time_, rtp_time_elpased,
|
||||
rate_, rate);
|
||||
}
|
||||
rate_ = rate;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
302
trunk/src/utest/srs_utest_ai24.cpp
Normal file
302
trunk/src/utest/srs_utest_ai24.cpp
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
//
|
||||
// Copyright (c) 2013-2025 The SRS Authors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
#include <srs_utest_ai_rtc_avsync.hpp>
|
||||
|
||||
#include <srs_app_rtc_source.hpp>
|
||||
#include <srs_kernel_error.hpp>
|
||||
#include <srs_kernel_rtc_rtcp.hpp>
|
||||
#include <srs_protocol_sdp.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Mock class to access protected members of SrsRtcRecvTrack
|
||||
class MockSrsRtcRecvTrackForAVSync : public SrsRtcRecvTrack
|
||||
{
|
||||
SRS_DECLARE_PRIVATE:
|
||||
static SrsRtcTrackDescription* create_track_desc(const string& type, uint32_t ssrc, int sample_rate)
|
||||
{
|
||||
SrsRtcTrackDescription* desc = new SrsRtcTrackDescription();
|
||||
desc->type_ = type;
|
||||
desc->id_ = "test_track";
|
||||
desc->ssrc_ = ssrc;
|
||||
desc->is_active_ = true;
|
||||
|
||||
// Create media description with sample rate
|
||||
desc->media_ = new SrsAudioPayload();
|
||||
desc->media_->sample_ = sample_rate;
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
public:
|
||||
MockSrsRtcRecvTrackForAVSync(const string& type, uint32_t ssrc, int sample_rate, bool is_audio)
|
||||
: SrsRtcRecvTrack(NULL, create_track_desc(type, ssrc, sample_rate), is_audio)
|
||||
{
|
||||
}
|
||||
|
||||
// Expose protected methods for testing
|
||||
double get_rate() const { return rate_; }
|
||||
|
||||
void set_rate(double rate) { rate_ = rate; }
|
||||
|
||||
int64_t test_cal_avsync_time(uint32_t rtp_time) {
|
||||
return cal_avsync_time(rtp_time);
|
||||
}
|
||||
|
||||
void test_update_send_report_time(const SrsNtp& ntp, uint32_t rtp_time) {
|
||||
update_send_report_time(ntp, rtp_time);
|
||||
}
|
||||
|
||||
// Implement pure virtual methods
|
||||
virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource>& source, SrsRtpPacket* pkt) {
|
||||
return srs_success;
|
||||
}
|
||||
|
||||
virtual srs_error_t check_send_nacks() {
|
||||
return srs_success;
|
||||
}
|
||||
};
|
||||
|
||||
// Test: Rate initialization from SDP for audio track (48kHz)
|
||||
VOID TEST(RtcAVSyncTest, AudioRateInitFromSDP)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("audio", 12345, 48000, true);
|
||||
|
||||
// Rate should be initialized to 48 (48000 Hz / 1000 = 48 RTP units per ms)
|
||||
EXPECT_DOUBLE_EQ(48.0, track.get_rate());
|
||||
}
|
||||
|
||||
// Test: Rate initialization from SDP for video track (90kHz)
|
||||
VOID TEST(RtcAVSyncTest, VideoRateInitFromSDP)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("video", 67890, 90000, false);
|
||||
|
||||
// Rate should be initialized to 90 (90000 Hz / 1000 = 90 RTP units per ms)
|
||||
EXPECT_DOUBLE_EQ(90.0, track.get_rate());
|
||||
}
|
||||
|
||||
// Test: cal_avsync_time with SDP rate (before receiving SR)
|
||||
VOID TEST(RtcAVSyncTest, CalAVSyncTimeWithSDPRate)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("audio", 12345, 48000, true);
|
||||
|
||||
// Simulate first SR received
|
||||
SrsNtp ntp;
|
||||
ntp.system_ms_ = 1000; // 1000 ms
|
||||
uint32_t rtp_time = 48000; // 48000 RTP units
|
||||
track.test_update_send_report_time(ntp, rtp_time);
|
||||
|
||||
// Calculate avsync time for a later RTP packet
|
||||
// RTP time: 48000 + 4800 = 52800 (100ms later at 48kHz)
|
||||
// Expected avsync_time: 1000 + (52800 - 48000) / 48 = 1000 + 100 = 1100 ms
|
||||
int64_t avsync_time = track.test_cal_avsync_time(52800);
|
||||
EXPECT_EQ(1100, avsync_time);
|
||||
}
|
||||
|
||||
// Test: cal_avsync_time returns -1 when rate is 0
|
||||
VOID TEST(RtcAVSyncTest, CalAVSyncTimeWithZeroRate)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("audio", 12345, 48000, true);
|
||||
|
||||
// Manually set rate to 0
|
||||
track.set_rate(0.0);
|
||||
|
||||
// Should return -1 when rate is too small
|
||||
int64_t avsync_time = track.test_cal_avsync_time(1000);
|
||||
EXPECT_EQ(-1, avsync_time);
|
||||
}
|
||||
|
||||
// Test: Rate update after receiving 2nd SR (audio)
|
||||
VOID TEST(RtcAVSyncTest, AudioRateUpdateAfter2ndSR)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("audio", 12345, 48000, true);
|
||||
|
||||
// Initial rate from SDP
|
||||
EXPECT_DOUBLE_EQ(48.0, track.get_rate());
|
||||
|
||||
// First SR
|
||||
SrsNtp ntp1;
|
||||
ntp1.system_ms_ = 1000;
|
||||
uint32_t rtp_time1 = 48000;
|
||||
track.test_update_send_report_time(ntp1, rtp_time1);
|
||||
|
||||
// Rate should still be 48 (from SDP)
|
||||
EXPECT_DOUBLE_EQ(48.0, track.get_rate());
|
||||
|
||||
// Second SR (20ms later, RTP increased by 960)
|
||||
SrsNtp ntp2;
|
||||
ntp2.system_ms_ = 1020; // 20ms later
|
||||
uint32_t rtp_time2 = 48960; // 960 RTP units later (48 * 20)
|
||||
track.test_update_send_report_time(ntp2, rtp_time2);
|
||||
|
||||
// Rate should be updated to calculated value: 960 / 20 = 48
|
||||
EXPECT_DOUBLE_EQ(48.0, track.get_rate());
|
||||
}
|
||||
|
||||
// Test: Rate update after receiving 2nd SR (video)
|
||||
VOID TEST(RtcAVSyncTest, VideoRateUpdateAfter2ndSR)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("video", 67890, 90000, false);
|
||||
|
||||
// Initial rate from SDP
|
||||
EXPECT_DOUBLE_EQ(90.0, track.get_rate());
|
||||
|
||||
// First SR
|
||||
SrsNtp ntp1;
|
||||
ntp1.system_ms_ = 2000;
|
||||
uint32_t rtp_time1 = 180000;
|
||||
track.test_update_send_report_time(ntp1, rtp_time1);
|
||||
|
||||
// Rate should still be 90 (from SDP)
|
||||
EXPECT_DOUBLE_EQ(90.0, track.get_rate());
|
||||
|
||||
// Second SR (100ms later, RTP increased by 9000)
|
||||
SrsNtp ntp2;
|
||||
ntp2.system_ms_ = 2100; // 100ms later
|
||||
uint32_t rtp_time2 = 189000; // 9000 RTP units later (90 * 100)
|
||||
track.test_update_send_report_time(ntp2, rtp_time2);
|
||||
|
||||
// Rate should be updated to calculated value: 9000 / 100 = 90
|
||||
EXPECT_DOUBLE_EQ(90.0, track.get_rate());
|
||||
}
|
||||
|
||||
// Test: Rate calculation with clock drift (slightly off from SDP)
|
||||
VOID TEST(RtcAVSyncTest, RateUpdateWithClockDrift)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("video", 67890, 90000, false);
|
||||
|
||||
// Initial rate from SDP
|
||||
EXPECT_DOUBLE_EQ(90.0, track.get_rate());
|
||||
|
||||
// First SR
|
||||
SrsNtp ntp1;
|
||||
ntp1.system_ms_ = 1000;
|
||||
uint32_t rtp_time1 = 90000;
|
||||
track.test_update_send_report_time(ntp1, rtp_time1);
|
||||
|
||||
// Second SR with slight clock drift
|
||||
// Expected: 100ms -> 9000 RTP units
|
||||
// Actual: 100ms -> 9010 RTP units (slight drift)
|
||||
SrsNtp ntp2;
|
||||
ntp2.system_ms_ = 1100;
|
||||
uint32_t rtp_time2 = 99010; // Slightly more than expected
|
||||
track.test_update_send_report_time(ntp2, rtp_time2);
|
||||
|
||||
// Rate should be updated to: round(9010 / 100) = 90
|
||||
EXPECT_DOUBLE_EQ(90.0, track.get_rate());
|
||||
}
|
||||
|
||||
// Test: Rate calculation with larger time interval
|
||||
VOID TEST(RtcAVSyncTest, RateUpdateWithLargeInterval)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("audio", 12345, 48000, true);
|
||||
|
||||
// First SR
|
||||
SrsNtp ntp1;
|
||||
ntp1.system_ms_ = 5000;
|
||||
uint32_t rtp_time1 = 240000;
|
||||
track.test_update_send_report_time(ntp1, rtp_time1);
|
||||
|
||||
// Second SR (1000ms later)
|
||||
SrsNtp ntp2;
|
||||
ntp2.system_ms_ = 6000;
|
||||
uint32_t rtp_time2 = 288000; // 48000 RTP units later (48 * 1000)
|
||||
track.test_update_send_report_time(ntp2, rtp_time2);
|
||||
|
||||
// Rate should be: 48000 / 1000 = 48
|
||||
EXPECT_DOUBLE_EQ(48.0, track.get_rate());
|
||||
}
|
||||
|
||||
// Test: cal_avsync_time with precise rate after 2nd SR
|
||||
VOID TEST(RtcAVSyncTest, CalAVSyncTimeAfter2ndSR)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("video", 67890, 90000, false);
|
||||
|
||||
// First SR
|
||||
SrsNtp ntp1;
|
||||
ntp1.system_ms_ = 1000;
|
||||
uint32_t rtp_time1 = 90000;
|
||||
track.test_update_send_report_time(ntp1, rtp_time1);
|
||||
|
||||
// Second SR
|
||||
SrsNtp ntp2;
|
||||
ntp2.system_ms_ = 1100;
|
||||
uint32_t rtp_time2 = 99000;
|
||||
track.test_update_send_report_time(ntp2, rtp_time2);
|
||||
|
||||
// Now calculate avsync time for a packet
|
||||
// RTP time: 99000 + 4500 = 103500 (50ms later at 90kHz)
|
||||
// Expected: 1100 + (103500 - 99000) / 90 = 1100 + 50 = 1150 ms
|
||||
int64_t avsync_time = track.test_cal_avsync_time(103500);
|
||||
EXPECT_EQ(1150, avsync_time);
|
||||
}
|
||||
|
||||
// Test: Immediate A/V sync availability (issue #4418 fix)
|
||||
VOID TEST(RtcAVSyncTest, ImmediateAVSyncAvailability)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("audio", 12345, 48000, true);
|
||||
|
||||
// Before any SR, rate should be available from SDP
|
||||
EXPECT_DOUBLE_EQ(48.0, track.get_rate());
|
||||
|
||||
// First SR received
|
||||
SrsNtp ntp1;
|
||||
ntp1.system_ms_ = 1000;
|
||||
uint32_t rtp_time1 = 48000;
|
||||
track.test_update_send_report_time(ntp1, rtp_time1);
|
||||
|
||||
// Should be able to calculate avsync_time immediately (not -1)
|
||||
int64_t avsync_time = track.test_cal_avsync_time(48480); // 10ms later
|
||||
EXPECT_GT(avsync_time, 0); // Should be > 0, not -1
|
||||
EXPECT_EQ(1010, avsync_time); // Should be 1000 + 10 = 1010
|
||||
}
|
||||
|
||||
// Test: RTP timestamp wraparound handling
|
||||
VOID TEST(RtcAVSyncTest, RTPTimestampWraparound)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("audio", 12345, 48000, true);
|
||||
|
||||
// First SR near wraparound
|
||||
SrsNtp ntp1;
|
||||
ntp1.system_ms_ = 1000;
|
||||
uint32_t rtp_time1 = 0xFFFFF000; // Near max uint32_t
|
||||
track.test_update_send_report_time(ntp1, rtp_time1);
|
||||
|
||||
// Second SR after wraparound
|
||||
SrsNtp ntp2;
|
||||
ntp2.system_ms_ = 1020; // 20ms later
|
||||
uint32_t rtp_time2 = 0x000003C0; // Wrapped around, 960 units after wraparound
|
||||
track.test_update_send_report_time(ntp2, rtp_time2);
|
||||
|
||||
// Note: Current implementation may not handle wraparound correctly
|
||||
// This test documents the current behavior
|
||||
// Rate calculation: (0x000003C0 - 0xFFFFF000) will underflow
|
||||
// This is a known limitation that may need fixing in the future
|
||||
}
|
||||
|
||||
// Test: Zero time elapsed between SRs (edge case)
|
||||
VOID TEST(RtcAVSyncTest, ZeroTimeElapsedBetweenSRs)
|
||||
{
|
||||
MockSrsRtcRecvTrackForAVSync track("audio", 12345, 48000, true);
|
||||
|
||||
// First SR
|
||||
SrsNtp ntp1;
|
||||
ntp1.system_ms_ = 1000;
|
||||
uint32_t rtp_time1 = 48000;
|
||||
track.test_update_send_report_time(ntp1, rtp_time1);
|
||||
|
||||
double rate_before = track.get_rate();
|
||||
|
||||
// Second SR with same timestamp (0ms elapsed)
|
||||
SrsNtp ntp2;
|
||||
ntp2.system_ms_ = 1000; // Same time
|
||||
uint32_t rtp_time2 = 48000; // Same RTP time
|
||||
track.test_update_send_report_time(ntp2, rtp_time2);
|
||||
|
||||
// Rate should remain unchanged (SDP rate)
|
||||
EXPECT_DOUBLE_EQ(rate_before, track.get_rate());
|
||||
}
|
||||
|
||||
12
trunk/src/utest/srs_utest_ai24.hpp
Normal file
12
trunk/src/utest/srs_utest_ai24.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// Copyright (c) 2013-2025 The SRS Authors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
#ifndef SRS_UTEST_AI_RTC_AVSYNC_HPP
|
||||
#define SRS_UTEST_AI_RTC_AVSYNC_HPP
|
||||
|
||||
#include <srs_utest.hpp>
|
||||
|
||||
#endif
|
||||
|
||||
Loading…
Reference in New Issue
Block a user