srs/trunk/src/utest/srs_utest_manual_rtc.cpp
Haibo Chen(陈海博) ef048b0d65
RTC: Fix DVR missing first 4-6 seconds by initializing rate from SDP (#4541)
for issue #4418, #4151, #4076 .DVR Missing First Few Seconds of
Audio/Video

### Root Cause
When recording WebRTC streams to FLV files using DVR, the first 4-6
seconds of audio/video are missing. This occurs because:

1. **Packets are discarded before A/V sync is available**: The
RTC-to-RTMP conversion pipeline actively discards all RTP packets when
avsync_time <= 0.
2. **Original algorithm requires 2 RTCP SR packets**: The previous
implementation needed to receive two RTCP Sender Report (SR) packets
before it could calculate the rate for audio/video synchronization
timestamp conversion.
3. **Delay causes packet loss**: Since RTCP SR packets typically arrive
every 2-3 seconds, waiting for 2 SRs means 4-6 seconds of packets are
discarded before A/V sync becomes available.
4. **Audio SR arrives slower than video SR**: As reported in the issue,
video RTCP SR packets arrive much faster than audio SR packets. This
asymmetry causes audio packets to be discarded for a longer period,
resulting in the audio loss observed in DVR recordings.

### Solution
1. **Initialize rate from SDP**: Use the sample rate from SDP (Session
Description Protocol) to calculate the initial rate immediately when the
track is created.
Audio (Opus): 48000 Hz → rate = 48 (RTP units per millisecond)
Video (H.264/H.265): 90000 Hz → rate = 90 (RTP units per millisecond)
2. **Enable immediate A/V sync:** With the SDP rate available,
cal_avsync_time() can calculate valid timestamps from the very first RTP
packet, eliminating packet loss.
3. **Smooth transition to precise rate**: After receiving the 2nd RTCP
SR, update to the precisely calculated rate based on actual RTP/NTP
timestamp mapping.

## Configuration

Added new configuration option `init_rate_from_sdp` in the RTC vhost
section:

```nginx
vhost rtc.vhost.srs.com {
    rtc {
        # Whether initialize RTP rate from SDP sample rate for immediate A/V sync.
        # When enabled, the RTP rate (units per millisecond) is initialized from the SDP
        # sample rate (e.g., 90 for video 90kHz, 48 for audio 48kHz) before receiving
        # 2 RTCP SR packets. This allows immediate audio/video synchronization.
        # The rate will be updated to a more precise value after receiving the 2nd SR.
        # Overwrite by env SRS_VHOST_RTC_INIT_RATE_FROM_SDP for all vhosts.
        # Default: off
        init_rate_from_sdp off;
    }
}
```

**⚠️ Important Note**: This config defaults to **off** because:
-  When **enabled**: Fixes the audio loss problem (no missing first 4-6
seconds)
-  When **enabled**: VLC on macOS cannot play the video properly
-  Other platforms work fine (Windows, Linux)
-  FFplay works fine on all platforms

Users experiencing audio loss in DVR recordings can enable this option
if they don't need VLC macOS compatibility. We're investigating the VLC
macOS issue to make this feature safe to enable by default in the
future.

---------

Co-authored-by: winlin <winlinvip@gmail.com>
Co-authored-by: OSSRS-AI <winlinam@gmail.com>
2025-10-28 09:33:40 -04:00

2703 lines
81 KiB
C++

//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#include <srs_utest_manual_rtc.hpp>
#include <srs_app_rtc_conn.hpp>
#include <srs_app_rtc_source.hpp>
#include <srs_app_utility.hpp>
#include <srs_core_autofree.hpp>
#include <srs_kernel_codec.hpp>
#include <srs_kernel_error.hpp>
#include <srs_kernel_rtc_queue.hpp>
#include <srs_kernel_rtc_rtp.hpp>
#include <srs_protocol_conn.hpp>
#include <srs_utest_ai11.hpp>
#include <srs_utest_manual_protocol3.hpp>
#include <srs_utest_manual_service.hpp>
#include <vector>
using namespace std;
VOID TEST(KernelRTCTest, RtpSTAPPayloadException)
{
srs_error_t err = srs_success;
unsigned char rtp_pkt[328] = {
0x90,
0xe0,
0x65,
0x8d,
0x37,
0xbc,
0x20,
0xb7,
0xd8,
0xf7,
0xae,
0x77,
0xbe,
0xde,
0x00,
0x03,
0x51,
0x06,
0x4f,
0xd5,
0x2f,
0x0e,
0xe1,
0x90,
0x75,
0xc3,
0x00,
0x00,
0xd8,
0x01,
0x00,
0x03,
0xef,
0x93,
0xc7,
0x6a,
0x23,
0x45,
0xdc,
0xb0,
0xce,
0x2b,
0x51,
0x1a,
0x8a,
0xd1,
0x35,
0xab,
0x11,
0xa7,
0x15,
0xc4,
0xd6,
0xe4,
0x5d,
0x12,
0x6c,
0x04,
0x86,
0x25,
0xd3,
0x88,
0x76,
0xa2,
0xb8,
0x58,
0x47,
0x0d,
0x0a,
0xd6,
0x2b,
0x85,
0x04,
0x6a,
0x09,
0x2a,
0x4a,
0xce,
0x22,
0xa2,
0x05,
0x78,
0x8e,
0x71,
0x5c,
0x22,
0x23,
0x58,
0x9e,
0x16,
0x15,
0xe1,
0x5f,
0xff,
0xfd,
0x32,
0x0a,
0xe2,
0xb8,
0xea,
0xd6,
0xba,
0xd5,
0x7e,
0x5a,
0xd6,
0x61,
0x1c,
0x82,
0x38,
0xce,
0x4a,
0xd7,
0xe2,
0xea,
0xaa,
0xab,
0xa8,
0x83,
0xf6,
0x7f,
0x10,
0xf1,
0x7c,
0x55,
0x4d,
0xeb,
0xaa,
0xf8,
0xfd,
0x35,
0xaa,
0xeb,
0x59,
0x8e,
0xf8,
0x8f,
0x12,
0xb9,
0xdd,
0x39,
0xfa,
0x3f,
0x62,
0x9e,
0x23,
0x96,
0xab,
0x5e,
0xc4,
0xce,
0x97,
0x55,
0x43,
0x65,
0x29,
0xde,
0x8f,
0xe2,
0xb9,
0x0f,
0xb8,
0xd0,
0xee,
0x00,
0x31,
0x35,
0xdb,
0x5a,
0xff,
0xff,
0xf8,
0x10,
0xa9,
0x3c,
0xf7,
0x90,
0x8c,
0xf7,
0x3f,
0x5f,
0xd7,
0x15,
0xac,
0xee,
0xa8,
0xfe,
0x23,
0x84,
0x8b,
0xe6,
0x97,
0x2a,
0x61,
0x38,
0xba,
0xd3,
0xee,
0x7b,
0x49,
0xfa,
0x81,
0xcb,
0x3f,
0x72,
0xd5,
0x56,
0x8f,
0xe7,
0x7b,
0x1d,
0xda,
0x85,
0x71,
0xbc,
0x45,
0x75,
0x5d,
0x55,
0x47,
0xc5,
0xf5,
0x36,
0xe4,
0xa9,
0x17,
0x4a,
0x84,
0xf9,
0xdd,
0xd0,
0xa5,
0xb1,
0xcf,
0x69,
0xcf,
0xcd,
0x1d,
0xac,
0xe4,
0xc6,
0x3d,
0xd0,
0x95,
0xa3,
0xbd,
0x0a,
0xd4,
0xa2,
0xb9,
0x05,
0x78,
0xae,
0x5a,
0x92,
0xb5,
0x90,
0x4b,
0xa6,
0x85,
0x3c,
0x27,
0xb3,
0x4d,
0xd2,
0x5c,
0xfa,
0x61,
0x01,
0x4a,
0xa6,
0xd9,
0x26,
0xf3,
0x78,
0x44,
0x57,
0x2e,
0x79,
0xc5,
0x71,
0x42,
0xb5,
0x34,
0x87,
0x94,
0x57,
0x8a,
0xe1,
0x09,
0xb3,
0x8a,
0xe7,
0x0b,
0x7f,
0xfc,
0xff,
0xec,
0x28,
0xe3,
0x4c,
0xff,
0xff,
0xa6,
0x6a,
0xca,
0x2b,
0x84,
0xab,
0x0a,
0xd7,
0xf1,
0xf5,
0x9a,
0x47,
0x08,
0x54,
0xd5,
0xac,
0x9a,
0xf5,
0x09,
0x5a,
0x29,
0x35,
0x52,
0x79,
0xe0,
};
int nb_buf = sizeof(rtp_pkt);
SrsBuffer buf((char *)rtp_pkt, nb_buf);
SrsRtpHeader header;
EXPECT_TRUE((err = header.decode(&buf)) == srs_success);
// We must skip the padding bytes before parsing payload.
uint8_t padding = header.get_padding();
EXPECT_TRUE(buf.require(padding));
buf.set_size(buf.size() - padding);
SrsAvcNaluType nalu_type = SrsAvcNaluTypeReserved;
// Try to parse the NALU type for video decoder.
if (!buf.empty()) {
nalu_type = SrsAvcNaluTypeParse(buf.head()[0]);
}
EXPECT_TRUE(nalu_type == kStapA);
ISrsRtpPayloader *payload = new SrsRtpSTAPPayload();
HELPER_ASSERT_FAILED(payload->decode(&buf));
srs_freep(payload);
}
class MockResource : public ISrsDisposingHandler, public ISrsResource
{
public:
SrsResourceManager *manager_;
MockResource(SrsResourceManager *manager)
{
manager_ = manager;
if (manager_) {
manager_->subscribe(this);
}
}
virtual ~MockResource()
{
if (manager_) {
manager_->unsubscribe(this);
}
}
virtual const SrsContextId &get_id()
{
return _srs_context->get_id();
}
virtual std::string desc()
{
return "";
}
};
class MockResourceHookOwner : public MockResource
{
public:
ISrsResource *owner_;
MockResourceHookOwner(SrsResourceManager *manager) : MockResource(manager)
{
owner_ = NULL;
}
virtual ~MockResourceHookOwner()
{
}
virtual void on_before_dispose(ISrsResource *c)
{
if (c == owner_) { // Remove self if its owner is disposing.
manager_->remove(this);
}
}
virtual void on_disposing(ISrsResource *c)
{
}
};
class MockResourceSelf : public MockResource
{
public:
bool remove_in_before_dispose;
bool remove_in_disposing;
MockResourceSelf(SrsResourceManager *manager) : MockResource(manager)
{
remove_in_before_dispose = remove_in_disposing = false;
}
virtual ~MockResourceSelf()
{
}
virtual void on_before_dispose(ISrsResource *c)
{
if (remove_in_before_dispose) {
manager_->remove(this);
}
}
virtual void on_disposing(ISrsResource *c)
{
if (remove_in_disposing) {
manager_->remove(this);
}
}
};
class MockResourceUnsubscribe : public MockResource
{
public:
int nn_before_dispose;
int nn_disposing;
bool unsubscribe_in_before_dispose;
bool unsubscribe_in_disposing;
MockResourceUnsubscribe *result;
MockResourceUnsubscribe(SrsResourceManager *manager) : MockResource(manager)
{
unsubscribe_in_before_dispose = unsubscribe_in_disposing = false;
nn_before_dispose = nn_disposing = 0;
result = NULL;
}
virtual ~MockResourceUnsubscribe()
{
if (result) { // Copy result before disposing it.
*result = *this;
}
}
virtual void on_before_dispose(ISrsResource *c)
{
nn_before_dispose++;
if (unsubscribe_in_before_dispose) {
manager_->unsubscribe(this);
}
}
virtual void on_disposing(ISrsResource *c)
{
nn_disposing++;
if (unsubscribe_in_disposing) {
manager_->unsubscribe(this);
}
}
};
VOID TEST(KernelRTCTest, ConnectionManagerTest)
{
srs_error_t err = srs_success;
// When notifying, the handlers changed, disposing event may lost.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
MockResourceUnsubscribe *conn0 = new MockResourceUnsubscribe(&manager);
conn0->unsubscribe_in_disposing = true;
manager.add(conn0);
MockResourceUnsubscribe *conn1 = new MockResourceUnsubscribe(&manager);
manager.add(conn1);
MockResourceUnsubscribe *conn2 = new MockResourceUnsubscribe(&manager);
manager.add(conn2);
// When removing conn0, it will unsubscribe and change the handlers,
// which should not cause the conn1 lost event.
manager.remove(conn0);
srs_usleep(0);
ASSERT_EQ(2, (int)manager.size());
EXPECT_EQ(1, conn1->nn_before_dispose);
EXPECT_EQ(1, conn1->nn_disposing); // Should get event.
EXPECT_EQ(1, conn2->nn_before_dispose);
EXPECT_EQ(1, conn2->nn_disposing);
}
// When notifying, the handlers changed, before-dispose event may lost.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
MockResourceUnsubscribe *conn0 = new MockResourceUnsubscribe(&manager);
conn0->unsubscribe_in_before_dispose = true;
manager.add(conn0);
MockResourceUnsubscribe *conn1 = new MockResourceUnsubscribe(&manager);
manager.add(conn1);
MockResourceUnsubscribe *conn2 = new MockResourceUnsubscribe(&manager);
manager.add(conn2);
// When removing conn0, it will unsubscribe and change the handlers,
// which should not cause the conn1 lost event.
manager.remove(conn0);
srs_usleep(0);
ASSERT_EQ(2, (int)manager.size());
EXPECT_EQ(1, conn1->nn_before_dispose); // Should get event.
EXPECT_EQ(1, conn1->nn_disposing);
EXPECT_EQ(1, conn2->nn_before_dispose);
EXPECT_EQ(1, conn2->nn_disposing);
}
// Subscribe or unsubscribe for multiple times.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
MockResourceUnsubscribe *resource = new MockResourceUnsubscribe(&manager);
resource->unsubscribe_in_before_dispose = true;
manager.add(resource);
MockResourceUnsubscribe result(NULL); // No manager for result.
resource->result = &result;
manager.remove(resource);
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
EXPECT_EQ(1, result.nn_before_dispose);
EXPECT_EQ(0, result.nn_disposing); // No disposing event, because we unsubscribe in before-dispose.
}
// Count the event for disposing.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
MockResourceUnsubscribe *resource = new MockResourceUnsubscribe(&manager);
manager.add(resource);
MockResourceUnsubscribe result(NULL); // No manager for result.
resource->result = &result;
manager.remove(resource);
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
EXPECT_EQ(1, result.nn_before_dispose);
EXPECT_EQ(1, result.nn_disposing);
}
// When hooks disposing, remove itself again.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
MockResourceSelf *resource = new MockResourceSelf(&manager);
resource->remove_in_disposing = true;
manager.add(resource);
EXPECT_EQ(1, (int)manager.size());
manager.remove(resource);
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
}
// When hooks before-dispose, remove itself again.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
MockResourceSelf *resource = new MockResourceSelf(&manager);
resource->remove_in_before_dispose = true;
manager.add(resource);
EXPECT_EQ(1, (int)manager.size());
manager.remove(resource);
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
}
// Cover all normal scenarios.
if (true) {
SrsResourceManager manager("mgr", true);
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
// Resource without id or name.
manager.add_with_id("100", new MockSrsConnection());
manager.add_with_id("101", new MockSrsConnection());
manager.add_with_name("srs", new MockSrsConnection());
manager.add_with_name("av", new MockSrsConnection());
ASSERT_EQ(4, (int)manager.size());
manager.remove(manager.at(3));
manager.remove(manager.at(2));
manager.remove(manager.at(1));
manager.remove(manager.at(0));
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
}
// Callback: Remove worker when its master is disposing.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
MockResourceHookOwner *master = new MockResourceHookOwner(&manager);
manager.add(master);
EXPECT_EQ(1, (int)manager.size());
MockResourceHookOwner *worker = new MockResourceHookOwner(&manager);
worker->owner_ = master; // When disposing master, worker will hook the event and remove itself.
manager.add(worker);
EXPECT_EQ(2, (int)manager.size());
manager.remove(master);
srs_usleep(0); // Trigger the disposing.
// Both master and worker should be disposed.
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
}
// Normal scenario, free object by manager.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
MockSrsConnection *conn = new MockSrsConnection();
manager.add(conn);
EXPECT_EQ(1, (int)manager.size());
EXPECT_FALSE(manager.empty());
manager.remove(conn);
srs_usleep(0); // Switch context for manager to dispose connections.
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
}
// Resource with id or name.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
// Resource without id or name.
MockSrsConnection *conn = new MockSrsConnection();
manager.add(conn);
ASSERT_EQ(1, (int)manager.size());
EXPECT_TRUE(manager.at(0));
EXPECT_TRUE(!manager.at(1));
EXPECT_TRUE(!manager.find_by_id("100"));
EXPECT_TRUE(!manager.find_by_name("srs"));
manager.remove(conn);
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
// Resource with id.
if (true) {
MockSrsConnection *id = new MockSrsConnection();
manager.add_with_id("100", id);
EXPECT_EQ(1, (int)manager.size());
EXPECT_TRUE(manager.find_by_id("100"));
EXPECT_TRUE(!manager.find_by_id("101"));
EXPECT_TRUE(!manager.find_by_name("100"));
manager.remove(id);
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
}
// Resource with name.
if (true) {
MockSrsConnection *name = new MockSrsConnection();
manager.add_with_name("srs", name);
EXPECT_EQ(1, (int)manager.size());
EXPECT_TRUE(manager.find_by_name("srs"));
EXPECT_TRUE(!manager.find_by_name("srs0"));
EXPECT_TRUE(!manager.find_by_id("srs"));
manager.remove(name);
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
}
// Resource with id and name.
if (true) {
MockSrsConnection *id_name = new MockSrsConnection();
manager.add_with_id("100", id_name);
manager.add_with_id("200", id_name);
manager.add_with_name("srs", id_name);
manager.add_with_name("av", id_name);
EXPECT_EQ(1, (int)manager.size());
EXPECT_TRUE(manager.find_by_name("srs"));
EXPECT_TRUE(manager.find_by_name("av"));
EXPECT_TRUE(manager.find_by_id("100"));
EXPECT_TRUE(manager.find_by_id("200"));
EXPECT_TRUE(!manager.find_by_name("srs0"));
EXPECT_TRUE(!manager.find_by_id("101"));
manager.remove(id_name);
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
}
// Resource with same id or name.
if (true) {
MockSrsConnection *conn0 = new MockSrsConnection();
MockSrsConnection *conn1 = new MockSrsConnection();
manager.add_with_id("100", conn0);
manager.add_with_id("100", conn1);
EXPECT_TRUE(conn0 != manager.find_by_id("100"));
EXPECT_TRUE(conn1 == manager.find_by_id("100"));
manager.remove(conn0);
srs_usleep(0);
ASSERT_EQ(1, (int)manager.size());
manager.remove(conn1);
srs_usleep(0);
ASSERT_EQ(0, (int)manager.size());
}
}
// Coroutine switch context, signal is lost.
if (true) {
SrsResourceManager manager("mgr");
HELPER_EXPECT_SUCCESS(manager.start());
EXPECT_EQ(0, (int)manager.size());
EXPECT_TRUE(manager.empty());
if (true) { // First connection, which will switch context when deleting.
MockSrsConnection *conn = new MockSrsConnection();
conn->do_switch = true;
manager.add(conn);
EXPECT_EQ(1, (int)manager.size());
EXPECT_EQ(0, (int)manager.zombies_.size());
manager.remove(conn); // Remove conn to zombies.
EXPECT_EQ(1, (int)manager.size());
EXPECT_EQ(1, (int)manager.zombies_.size());
srs_usleep(0); // Switch to manager coroutine to try to free zombies.
EXPECT_EQ(0, (int)manager.size());
EXPECT_EQ(0, (int)manager.zombies_.size());
}
if (true) { // Now the previous conn switch back to here, and lost the signal.
MockSrsConnection *conn = new MockSrsConnection();
manager.add(conn);
EXPECT_EQ(1, (int)manager.size());
EXPECT_EQ(0, (int)manager.zombies_.size());
manager.remove(conn); // Remove conn to zombies, signal is lost.
EXPECT_EQ(1, (int)manager.size());
EXPECT_EQ(1, (int)manager.zombies_.size());
srs_usleep(0); // Switch to manager, but no signal is triggered before, so conn will be freed by loop.
EXPECT_EQ(0, (int)manager.size());
EXPECT_EQ(0, (int)manager.zombies_.size());
}
}
}
VOID TEST(KernelRTCTest, StringDumpHexTest)
{
// Typical normal case.
if (false) {
char data[8];
data[0] = (char)0x3c;
data[sizeof(data) - 2] = (char)0x67;
data[sizeof(data) - 1] = (char)0xc3;
string r = srs_strings_dumps_hex(data, sizeof(data), INT_MAX, 0, 0, 0);
EXPECT_EQ(16, (int)r.length());
EXPECT_EQ('3', r.at(0));
EXPECT_EQ('c', r.at(1));
EXPECT_EQ('c', r.at(r.length() - 2));
EXPECT_EQ('3', r.at(r.length() - 1));
EXPECT_EQ('6', r.at(r.length() - 4));
EXPECT_EQ('7', r.at(r.length() - 3));
}
// Fill all buffer.
if (false) {
char data[8 * 1024];
data[0] = (char)0x3c;
data[sizeof(data) - 2] = (char)0x67;
data[sizeof(data) - 1] = (char)0xc3;
string r = srs_strings_dumps_hex(data, sizeof(data), INT_MAX, 0, 0, 0);
EXPECT_EQ(16 * 1024, (int)r.length());
EXPECT_EQ('3', r.at(0));
EXPECT_EQ('c', r.at(1));
EXPECT_EQ('c', r.at(r.length() - 2));
EXPECT_EQ('3', r.at(r.length() - 1));
EXPECT_EQ('6', r.at(r.length() - 4));
EXPECT_EQ('7', r.at(r.length() - 3));
}
// Overflow 1 byte.
if (true) {
char data[8 * 1024 + 1];
data[0] = (char)0x3c;
data[sizeof(data) - 2] = (char)0x67;
data[sizeof(data) - 1] = (char)0xc3;
string r = srs_strings_dumps_hex(data, sizeof(data), INT_MAX, 0, 0, 0);
EXPECT_EQ(16 * 1024, (int)r.length());
EXPECT_EQ('3', r.at(0));
EXPECT_EQ('c', r.at(1));
EXPECT_EQ('6', r.at(r.length() - 2));
EXPECT_EQ('7', r.at(r.length() - 1));
}
// Fill all buffer, with seperator.
if (true) {
char data[5461];
data[0] = (char)0x3c;
data[sizeof(data) - 2] = (char)0x67;
data[sizeof(data) - 1] = (char)0xc3;
string r = srs_strings_dumps_hex(data, sizeof(data), INT_MAX, ',', 0, 0);
EXPECT_EQ(16383 - 1, (int)r.length());
EXPECT_EQ('3', r.at(0));
EXPECT_EQ('c', r.at(1));
EXPECT_EQ('c', r.at(r.length() - 2));
EXPECT_EQ('3', r.at(r.length() - 1));
EXPECT_EQ('6', r.at(r.length() - 5));
EXPECT_EQ('7', r.at(r.length() - 4));
}
// Overflow 1 byte, with seperator.
if (true) {
char data[5461 + 1];
data[0] = (char)0x3c;
data[sizeof(data) - 2] = (char)0x67;
data[sizeof(data) - 1] = (char)0xc3;
string r = srs_strings_dumps_hex(data, sizeof(data), INT_MAX, ',', 0, 0);
EXPECT_EQ(16383 - 1, (int)r.length());
EXPECT_EQ('3', r.at(0));
EXPECT_EQ('c', r.at(1));
EXPECT_EQ('6', r.at(r.length() - 2));
EXPECT_EQ('7', r.at(r.length() - 1));
}
// Overflow 1 byte, with seperator and newline.
if (true) {
char data[5461 + 1];
data[0] = (char)0x3c;
data[sizeof(data) - 2] = (char)0x67;
data[sizeof(data) - 1] = (char)0xc3;
string r = srs_strings_dumps_hex(data, sizeof(data), INT_MAX, ',', 5461, '\n');
EXPECT_EQ(16383 - 1, (int)r.length());
EXPECT_EQ('3', r.at(0));
EXPECT_EQ('c', r.at(1));
EXPECT_EQ('6', r.at(r.length() - 2));
EXPECT_EQ('7', r.at(r.length() - 1));
}
}
VOID TEST(KernelRTCTest, SequenceCompare)
{
if (true) {
EXPECT_EQ(0, srs_rtp_seq_distance(0, 0));
EXPECT_EQ(0, srs_rtp_seq_distance(1, 1));
EXPECT_EQ(0, srs_rtp_seq_distance(3, 3));
EXPECT_EQ(1, srs_rtp_seq_distance(0, 1));
EXPECT_EQ(-1, srs_rtp_seq_distance(1, 0));
EXPECT_EQ(1, srs_rtp_seq_distance(65535, 0));
}
if (true) {
EXPECT_FALSE(srs_rtp_seq_distance(1, 1) > 0);
EXPECT_TRUE(srs_rtp_seq_distance(65534, 65535) > 0);
EXPECT_TRUE(srs_rtp_seq_distance(0, 1) > 0);
EXPECT_TRUE(srs_rtp_seq_distance(255, 256) > 0);
EXPECT_TRUE(srs_rtp_seq_distance(65535, 0) > 0);
EXPECT_TRUE(srs_rtp_seq_distance(65280, 0) > 0);
EXPECT_TRUE(srs_rtp_seq_distance(65535, 255) > 0);
EXPECT_TRUE(srs_rtp_seq_distance(65280, 255) > 0);
EXPECT_FALSE(srs_rtp_seq_distance(0, 65535) > 0);
EXPECT_FALSE(srs_rtp_seq_distance(0, 65280) > 0);
EXPECT_FALSE(srs_rtp_seq_distance(255, 65535) > 0);
EXPECT_FALSE(srs_rtp_seq_distance(255, 65280) > 0);
// Note that srs_rtp_seq_distance(0, 32768)>0 is TRUE by https://mp.weixin.qq.com/s/JZTInmlB9FUWXBQw_7NYqg
// but for WebRTC jitter buffer it's FALSE and we follow it.
EXPECT_FALSE(srs_rtp_seq_distance(0, 32768) > 0);
// It's FALSE definitely.
EXPECT_FALSE(srs_rtp_seq_distance(32768, 0) > 0);
}
if (true) {
EXPECT_FALSE(srs_seq_is_newer(1, 1));
EXPECT_TRUE(srs_seq_is_newer(65535, 65534));
EXPECT_TRUE(srs_seq_is_newer(1, 0));
EXPECT_TRUE(srs_seq_is_newer(256, 255));
EXPECT_TRUE(srs_seq_is_newer(0, 65535));
EXPECT_TRUE(srs_seq_is_newer(0, 65280));
EXPECT_TRUE(srs_seq_is_newer(255, 65535));
EXPECT_TRUE(srs_seq_is_newer(255, 65280));
EXPECT_FALSE(srs_seq_is_newer(65535, 0));
EXPECT_FALSE(srs_seq_is_newer(65280, 0));
EXPECT_FALSE(srs_seq_is_newer(65535, 255));
EXPECT_FALSE(srs_seq_is_newer(65280, 255));
EXPECT_FALSE(srs_seq_is_newer(32768, 0));
EXPECT_FALSE(srs_seq_is_newer(0, 32768));
}
if (true) {
EXPECT_FALSE(srs_seq_distance(1, 1) > 0);
EXPECT_TRUE(srs_seq_distance(65535, 65534) > 0);
EXPECT_TRUE(srs_seq_distance(1, 0) > 0);
EXPECT_TRUE(srs_seq_distance(256, 255) > 0);
EXPECT_TRUE(srs_seq_distance(0, 65535) > 0);
EXPECT_TRUE(srs_seq_distance(0, 65280) > 0);
EXPECT_TRUE(srs_seq_distance(255, 65535) > 0);
EXPECT_TRUE(srs_seq_distance(255, 65280) > 0);
EXPECT_FALSE(srs_seq_distance(65535, 0) > 0);
EXPECT_FALSE(srs_seq_distance(65280, 0) > 0);
EXPECT_FALSE(srs_seq_distance(65535, 255) > 0);
EXPECT_FALSE(srs_seq_distance(65280, 255) > 0);
EXPECT_FALSE(srs_seq_distance(32768, 0) > 0);
EXPECT_FALSE(srs_seq_distance(0, 32768) > 0);
}
if (true) {
EXPECT_FALSE(srs_seq_is_rollback(1, 1));
EXPECT_FALSE(srs_seq_is_rollback(65535, 65534));
EXPECT_FALSE(srs_seq_is_rollback(1, 0));
EXPECT_FALSE(srs_seq_is_rollback(256, 255));
EXPECT_TRUE(srs_seq_is_rollback(0, 65535));
EXPECT_TRUE(srs_seq_is_rollback(0, 65280));
EXPECT_TRUE(srs_seq_is_rollback(255, 65535));
EXPECT_TRUE(srs_seq_is_rollback(255, 65280));
EXPECT_FALSE(srs_seq_is_rollback(65535, 0));
EXPECT_FALSE(srs_seq_is_rollback(65280, 0));
EXPECT_FALSE(srs_seq_is_rollback(65535, 255));
EXPECT_FALSE(srs_seq_is_rollback(65280, 255));
EXPECT_FALSE(srs_seq_is_rollback(32768, 0));
EXPECT_FALSE(srs_seq_is_rollback(0, 32768));
}
}
VOID TEST(KernelRTCTest, DecodeHeaderWithPadding)
{
srs_error_t err = srs_success;
// RTP packet cipher with padding.
uint8_t data[] = {
0xb0,
0x66,
0x0a,
0x97,
0x7e,
0x32,
0x10,
0xee,
0x7d,
0xe6,
0xd0,
0xe6,
0xbe,
0xde,
0x00,
0x01,
0x31,
0x00,
0x16,
0x00,
0x25,
0xcd,
0xef,
0xce,
0xd7,
0x24,
0x57,
0xd9,
0x3c,
0xfd,
0x0f,
0x77,
0xea,
0x89,
0x61,
0xcb,
0x67,
0xa1,
0x65,
0x4a,
0x7d,
0x1f,
0x10,
0x4e,
0xed,
0x5e,
0x74,
0xe8,
0x7e,
0xce,
0x4d,
0xcf,
0xd5,
0x58,
0xd1,
0x2c,
0x30,
0xf1,
0x26,
0x62,
0xd3,
0x0c,
0x6a,
0x48,
0x29,
0x83,
0xd2,
0x3d,
0x30,
0xa1,
0x7c,
0x6f,
0xa1,
0x5c,
0x9f,
0x08,
0x43,
0x50,
0x34,
0x2b,
0x3c,
0xa1,
0xf0,
0xb0,
0xe2,
0x0e,
0xc8,
0xf9,
0x79,
0x06,
0x51,
0xfe,
0xbb,
0x13,
0x54,
0x3e,
0xb4,
0x37,
0x91,
0x96,
0x94,
0xb7,
0x61,
0x2e,
0x97,
0x09,
0xb8,
0x27,
0x10,
0x6a,
0x2e,
0xe0,
0x62,
0xe4,
0x37,
0x41,
0xab,
0x4f,
0xbf,
0x06,
0x0a,
0x89,
0x80,
0x18,
0x0d,
0x6e,
0x0a,
0xd1,
0x9f,
0xf1,
0xdd,
0x12,
0xbd,
0x1a,
0x70,
0x72,
0x33,
0xcc,
0xaa,
0x82,
0xdf,
0x92,
0x90,
0x45,
0xda,
0x3e,
0x88,
0x1c,
0x63,
0x83,
0xbc,
0xc8,
0xff,
0xfd,
0x64,
0xe3,
0xd4,
0x68,
0xe6,
0xc8,
0xdc,
0x81,
0x72,
0x5f,
0x38,
0x5b,
0xab,
0x63,
0x7b,
0x96,
0x03,
0x03,
0x54,
0xc5,
0xe6,
0x35,
0xf6,
0x86,
0xcc,
0xac,
0x74,
0xb0,
0xf4,
0x07,
0x9e,
0x19,
0x30,
0x4f,
0x90,
0xd6,
0xdb,
0x8b,
0x0d,
0xcb,
0x76,
0x71,
0x55,
0xc7,
0x4a,
0x6e,
0x1b,
0xb4,
0x42,
0xf4,
0xae,
0x81,
0x17,
0x08,
0xb7,
0x50,
0x61,
0x5a,
0x42,
0xde,
0x1f,
0xf3,
0xfd,
0xe2,
0x30,
0xff,
0xb7,
0x07,
0xdd,
0x4b,
0xb1,
0x00,
0xd9,
0x6c,
0x43,
0xa0,
0x9a,
0xfa,
0xbb,
0xec,
0xdf,
0x51,
0xce,
0x33,
0x79,
0x4b,
0xa7,
0x02,
0xf3,
0x96,
0x62,
0x42,
0x25,
0x28,
0x85,
0xa7,
0xe7,
0xd1,
0xd3,
0xf3,
};
// If not plaintext, the padding in body is invalid,
// so it will fail if decoding the header with padding.
if (true) {
SrsBuffer b((char *)data, sizeof(data));
SrsRtpHeader h;
HELPER_EXPECT_FAILED(h.decode(&b));
}
// Should ok if ignore padding.
if (true) {
SrsBuffer b((char *)data, sizeof(data));
SrsRtpHeader h;
h.ignore_padding(true);
HELPER_EXPECT_SUCCESS(h.decode(&b));
}
}
VOID TEST(KernelRTCTest, DumpsHexToString)
{
if (true) {
EXPECT_STREQ("", srs_strings_dumps_hex(NULL, 0).c_str());
}
if (true) {
uint8_t data[] = {0, 0, 0, 0};
EXPECT_STREQ("00 00 00 00", srs_strings_dumps_hex((const char *)data, sizeof(data)).c_str());
}
if (true) {
uint8_t data[] = {0, 1, 2, 3};
EXPECT_STREQ("00 01 02 03", srs_strings_dumps_hex((const char *)data, sizeof(data)).c_str());
}
if (true) {
uint8_t data[] = {0xa, 3, 0xf, 3};
EXPECT_STREQ("0a 03 0f 03", srs_strings_dumps_hex((const char *)data, sizeof(data)).c_str());
}
if (true) {
uint8_t data[] = {0xa, 3, 0xf, 3};
EXPECT_STREQ("0a,03,0f,03", srs_strings_dumps_hex((const char *)data, sizeof(data), INT_MAX, ',', 0, 0).c_str());
EXPECT_STREQ("0a030f03", srs_strings_dumps_hex((const char *)data, sizeof(data), INT_MAX, '\0', 0, 0).c_str());
EXPECT_STREQ("0a,03,\n0f,03", srs_strings_dumps_hex((const char *)data, sizeof(data), INT_MAX, ',', 2, '\n').c_str());
EXPECT_STREQ("0a,03,0f,03", srs_strings_dumps_hex((const char *)data, sizeof(data), INT_MAX, ',', 2, '\0').c_str());
}
if (true) {
uint8_t data[] = {0xa, 3, 0xf};
EXPECT_STREQ("0a 03", srs_strings_dumps_hex((const char *)data, sizeof(data), 2).c_str());
}
}
VOID TEST(KernelRTCTest, NACKFetchRTPPacket)
{
// Create mock interfaces for SrsRtcPlayStream constructor
MockRtcAsyncTaskExecutor mock_executor;
MockExpire mock_expire;
MockRtcPacketSender mock_sender;
SrsUniquePtr<SrsRtcPlayStream> play(new SrsRtcPlayStream(&mock_executor, &mock_expire, &mock_sender, SrsContextId()));
SrsRtcTrackDescription ds;
SrsRtcVideoSendTrack *track = new SrsRtcVideoSendTrack(&mock_sender, &ds);
SrsUniquePtr<SrsRtcVideoSendTrack> track_uptr(track);
// The RTP queue will free the packet.
if (true) {
SrsRtpPacket *pkt = new SrsRtpPacket();
pkt->header_.set_sequence(100);
track->rtp_queue_->set(pkt->header_.get_sequence(), pkt);
}
// If sequence not match, packet not found.
if (true) {
SrsRtpPacket *pkt = track->fetch_rtp_packet(10);
EXPECT_TRUE(pkt == NULL);
}
// The sequence matched, we got the packet.
if (true) {
SrsRtpPacket *pkt = track->fetch_rtp_packet(100);
EXPECT_TRUE(pkt != NULL);
}
// NACK special case.
if (true) {
// The sequence is the "same", 1100%1000 is 100,
// so we can also get it from the RTP queue.
SrsRtpPacket *pkt = track->rtp_queue_->at(1100);
EXPECT_TRUE(pkt != NULL);
// But the track requires exactly match, so it returns NULL.
pkt = track->fetch_rtp_packet(1100);
EXPECT_TRUE(pkt == NULL);
}
}
VOID TEST(KernelRTCTest, NACKEncode)
{
uint32_t ssrc = 123;
char buf_before[kRtcpPacketSize];
SrsBuffer stream_before(buf_before, sizeof(buf_before));
SrsRtcpNack rtcp_nack_encode(ssrc);
for (uint16_t i = 16; i < 50; ++i) {
rtcp_nack_encode.add_lost_sn(i);
}
srs_error_t err_before = rtcp_nack_encode.encode(&stream_before);
EXPECT_TRUE(err_before == 0);
char buf_after[kRtcpPacketSize];
memcpy(buf_after, buf_before, kRtcpPacketSize);
SrsBuffer stream_after(buf_after, sizeof(buf_after));
SrsRtcpNack rtcp_nack_decode(ssrc);
srs_error_t err_after = rtcp_nack_decode.decode(&stream_after);
EXPECT_TRUE(err_after == 0);
vector<uint16_t> before = rtcp_nack_encode.get_lost_sns();
vector<uint16_t> after = rtcp_nack_decode.get_lost_sns();
EXPECT_TRUE(before.size() == after.size());
for (int i = 0; i < (int)before.size() && i < (int)after.size(); ++i) {
EXPECT_TRUE(before.at(i) == after.at(i));
}
}
extern bool srs_is_stun(const uint8_t *data, size_t size);
extern bool srs_is_dtls(const uint8_t *data, size_t len);
extern bool srs_is_rtp_or_rtcp(const uint8_t *data, size_t len);
extern bool srs_is_rtcp(const uint8_t *data, size_t len);
#define mock_arr_push(arr, elem) arr.push_back(vector<uint8_t>(elem, elem + sizeof(elem)))
VOID TEST(KernelRTCTest, TestPacketType)
{
// DTLS packet.
vector<vector<uint8_t> > dtlss;
if (true) {
uint8_t data[13] = {20};
mock_arr_push(dtlss, data);
} // change_cipher_spec(20)
if (true) {
uint8_t data[13] = {21};
mock_arr_push(dtlss, data);
} // alert(21)
if (true) {
uint8_t data[13] = {22};
mock_arr_push(dtlss, data);
} // handshake(22)
if (true) {
uint8_t data[13] = {23};
mock_arr_push(dtlss, data);
} // application_data(23)
for (int i = 0; i < (int)dtlss.size(); i++) {
vector<uint8_t> elem = dtlss.at(i);
EXPECT_TRUE(srs_is_dtls(&elem[0], (size_t)elem.size()));
}
for (int i = 0; i < (int)dtlss.size(); i++) {
vector<uint8_t> elem = dtlss.at(i);
EXPECT_FALSE(srs_is_dtls(&elem[0], 1));
// All DTLS should not be other packets.
EXPECT_FALSE(srs_is_stun(&elem[0], (size_t)elem.size()));
EXPECT_TRUE(srs_is_dtls(&elem[0], (size_t)elem.size()));
EXPECT_FALSE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
}
// STUN packet.
vector<vector<uint8_t> > stuns;
if (true) {
uint8_t data[1] = {0};
mock_arr_push(stuns, data);
} // binding request.
if (true) {
uint8_t data[1] = {1};
mock_arr_push(stuns, data);
} // binding success response.
for (int i = 0; i < (int)stuns.size(); i++) {
vector<uint8_t> elem = stuns.at(i);
EXPECT_TRUE(srs_is_stun(&elem[0], (size_t)elem.size()));
}
for (int i = 0; i < (int)stuns.size(); i++) {
vector<uint8_t> elem = stuns.at(i);
EXPECT_FALSE(srs_is_stun(&elem[0], 0));
// All STUN should not be other packets.
EXPECT_TRUE(srs_is_stun(&elem[0], (size_t)elem.size()));
EXPECT_FALSE(srs_is_dtls(&elem[0], (size_t)elem.size()));
EXPECT_FALSE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
}
// RTCP packet.
vector<vector<uint8_t> > rtcps;
if (true) {
uint8_t data[12] = {0x80, 192};
mock_arr_push(rtcps, data);
}
if (true) {
uint8_t data[12] = {0x80, 200};
mock_arr_push(rtcps, data);
} // SR
if (true) {
uint8_t data[12] = {0x80, 201};
mock_arr_push(rtcps, data);
} // RR
if (true) {
uint8_t data[12] = {0x80, 202};
mock_arr_push(rtcps, data);
} // SDES
if (true) {
uint8_t data[12] = {0x80, 203};
mock_arr_push(rtcps, data);
} // BYE
if (true) {
uint8_t data[12] = {0x80, 204};
mock_arr_push(rtcps, data);
} // APP
if (true) {
uint8_t data[12] = {0x80, 223};
mock_arr_push(rtcps, data);
}
for (int i = 0; i < (int)rtcps.size(); i++) {
vector<uint8_t> elem = rtcps.at(i);
EXPECT_TRUE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
}
for (int i = 0; i < (int)rtcps.size(); i++) {
vector<uint8_t> elem = rtcps.at(i);
EXPECT_FALSE(srs_is_rtcp(&elem[0], 2));
// All RTCP should not be other packets.
EXPECT_FALSE(srs_is_stun(&elem[0], (size_t)elem.size()));
EXPECT_FALSE(srs_is_dtls(&elem[0], (size_t)elem.size()));
EXPECT_TRUE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
EXPECT_TRUE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
}
// RTP packet.
vector<vector<uint8_t> > rtps;
if (true) {
uint8_t data[12] = {0x80, 96};
mock_arr_push(rtps, data);
}
if (true) {
uint8_t data[12] = {0x80, 127};
mock_arr_push(rtps, data);
}
if (true) {
uint8_t data[12] = {0x80, 224};
mock_arr_push(rtps, data);
}
if (true) {
uint8_t data[12] = {0x80, 255};
mock_arr_push(rtps, data);
}
for (int i = 0; i < (int)rtps.size(); i++) {
vector<uint8_t> elem = rtps.at(i);
EXPECT_TRUE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
}
for (int i = 0; i < (int)rtps.size(); i++) {
vector<uint8_t> elem = rtps.at(i);
EXPECT_FALSE(srs_is_rtp_or_rtcp(&elem[0], 2));
// All RTP should not be other packets.
EXPECT_FALSE(srs_is_stun(&elem[0], (size_t)elem.size()));
EXPECT_FALSE(srs_is_dtls(&elem[0], (size_t)elem.size()));
EXPECT_TRUE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
}
}
VOID TEST(KernelRTCTest, DefaultTrackStatus)
{
// By default, track is disabled.
if (true) {
SrsRtcTrackDescription td;
// The track must default to disable, that is, the active is false.
EXPECT_FALSE(td.is_active_);
}
// Enable it by player.
if (true) {
// Create mock interfaces for SrsRtcPlayStream constructor
MockRtcAsyncTaskExecutor mock_executor;
MockExpire mock_expire;
MockRtcPacketSender mock_sender;
SrsUniquePtr<SrsRtcPlayStream> play(new SrsRtcPlayStream(&mock_executor, &mock_expire, &mock_sender, SrsContextId()));
SrsRtcAudioSendTrack *audio;
SrsRtcVideoSendTrack *video;
if (true) {
SrsRtcTrackDescription ds;
ds.type_ = "audio";
ds.id_ = "NSNWOn19NDn12o8nNeji2";
ds.ssrc_ = 100;
play->audio_tracks_[ds.ssrc_] = audio = new SrsRtcAudioSendTrack(&mock_sender, &ds);
}
if (true) {
SrsRtcTrackDescription ds;
ds.type_ = "video";
ds.id_ = "VMo22nfLDn122nfnDNL2";
ds.ssrc_ = 200;
play->video_tracks_[ds.ssrc_] = video = new SrsRtcVideoSendTrack(&mock_sender, &ds);
}
EXPECT_FALSE(audio->get_track_status());
EXPECT_FALSE(video->get_track_status());
play->set_all_tracks_status(true);
EXPECT_TRUE(audio->get_track_status());
EXPECT_TRUE(video->get_track_status());
}
// Enable it by publisher.
if (true) {
MockRtcAsyncTaskExecutor mock_exec;
MockExpire mock_expire;
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcPublishStream> publish(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, SrsContextId()));
SrsRtcAudioRecvTrack *audio;
SrsRtcVideoRecvTrack *video;
if (true) {
SrsRtcTrackDescription ds;
ds.type_ = "audio";
ds.id_ = "NSNWOn19NDn12o8nNeji2";
ds.ssrc_ = 100;
audio = new SrsRtcAudioRecvTrack(&mock_receiver, &ds, false);
publish->audio_tracks_.push_back(audio);
}
if (true) {
SrsRtcTrackDescription ds;
ds.type_ = "video";
ds.id_ = "VMo22nfLDn122nfnDNL2";
ds.ssrc_ = 200;
video = new SrsRtcVideoRecvTrack(&mock_receiver, &ds, false);
publish->video_tracks_.push_back(video);
}
EXPECT_FALSE(audio->get_track_status());
EXPECT_FALSE(video->get_track_status());
publish->set_all_tracks_status(true);
EXPECT_TRUE(audio->get_track_status());
EXPECT_TRUE(video->get_track_status());
}
}
VOID TEST(KernelRTCTest, Ntp)
{
if (true) {
// Test small systime, from 0-10000ms.
for (int i = 0; i < 10000; ++i) {
srs_utime_t now_ms = i;
// Cover systime to ntp
SrsNtp ntp = SrsNtp::from_time_ms(now_ms);
ASSERT_EQ((srs_utime_t)ntp.system_ms_, now_ms);
// Cover ntp to systime
SrsNtp ntp1 = SrsNtp::to_time_ms(ntp.ntp_);
ASSERT_EQ((srs_utime_t)ntp1.system_ms_, now_ms);
}
}
if (true) {
// Test current systime to ntp.
srs_utime_t now_ms = srs_time_now_cached() / 1000;
SrsNtp ntp = SrsNtp::from_time_ms(now_ms);
ASSERT_EQ((srs_utime_t)ntp.system_ms_, now_ms);
SrsNtp ntp1 = SrsNtp::to_time_ms(ntp.ntp_);
ASSERT_EQ((srs_utime_t)ntp1.system_ms_, now_ms);
}
}
VOID TEST(KernelRTCTest, SyncTimestampBySenderReportNormal)
{
MockRtcAsyncTaskExecutor mock_exec;
MockExpire mock_expire;
MockRtcPacketReceiver mock_receiver;
SrsRtcPublishStream publish(&mock_exec, &mock_expire, &mock_receiver, SrsContextId());
SrsRtcTrackDescription video_ds;
video_ds.type_ = "video";
video_ds.id_ = "VMo22nfLDn122nfnDNL2";
video_ds.ssrc_ = 200;
SrsRtcVideoRecvTrack *video = new SrsRtcVideoRecvTrack(&mock_receiver, &video_ds, false);
publish.video_tracks_.push_back(video);
publish.set_all_tracks_status(true);
SrsSharedPtr<SrsRtcSource> rtc_source(new SrsRtcSource());
srand(time(NULL));
if (true) {
SrsRtpPacket *video_rtp_pkt = new SrsRtpPacket();
SrsUniquePtr<SrsRtpPacket> video_rtp_pkt_uptr(video_rtp_pkt);
uint32_t video_absolute_ts = srs_time_now_cached();
uint32_t video_rtp_ts = random();
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
// No received any sender report, can not calculate absolute time, expect equal to -1.
EXPECT_EQ(video_rtp_pkt->get_avsync_time(), -1);
SrsNtp ntp = SrsNtp::from_time_ms(video_absolute_ts);
SrsRtcpSR *video_sr = new SrsRtcpSR();
SrsUniquePtr<SrsRtcpSR> video_sr_uptr(video_sr);
video_sr->set_ssrc(200);
video_sr->set_ntp(ntp.ntp_);
video_sr->set_rtp_ts(video_rtp_ts);
publish.on_rtcp_sr(video_sr);
// Video timebase 90000, fps=25
video_rtp_ts += 3600;
video_absolute_ts += 40;
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
// Received one sender report, can not calculate absolute time, expect equal to -1.
EXPECT_EQ(video_rtp_pkt->get_avsync_time(), -1);
ntp = SrsNtp::from_time_ms(video_absolute_ts);
video_sr->set_ntp(ntp.ntp_);
video_sr->set_rtp_ts(video_rtp_ts);
publish.on_rtcp_sr(video_sr);
for (int i = 0; i <= 1000; ++i) {
// Video timebase 90000, fps=25
video_rtp_ts += 3600;
video_absolute_ts += 40;
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
EXPECT_NEAR(video_rtp_pkt->get_avsync_time(), video_absolute_ts, 1);
}
}
}
VOID TEST(KernelRTCTest, SyncTimestampBySenderReportOutOfOrder)
{
MockRtcAsyncTaskExecutor mock_exec;
MockExpire mock_expire;
MockRtcPacketReceiver mock_receiver;
SrsRtcPublishStream publish(&mock_exec, &mock_expire, &mock_receiver, SrsContextId());
SrsRtcTrackDescription video_ds;
video_ds.type_ = "video";
video_ds.id_ = "VMo22nfLDn122nfnDNL2";
video_ds.ssrc_ = 200;
SrsRtcVideoRecvTrack *video = new SrsRtcVideoRecvTrack(&mock_receiver, &video_ds, false);
publish.video_tracks_.push_back(video);
publish.set_all_tracks_status(true);
SrsSharedPtr<SrsRtcSource> rtc_source(new SrsRtcSource());
srand(time(NULL));
if (true) {
SrsRtpPacket *video_rtp_pkt = new SrsRtpPacket();
SrsUniquePtr<SrsRtpPacket> video_rtp_pkt_uptr(video_rtp_pkt);
uint32_t video_absolute_ts = srs_time_now_cached();
uint32_t video_rtp_ts = random();
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
// No received any sender report, can not calculate absolute time, expect equal to -1.
EXPECT_EQ(video_rtp_pkt->get_avsync_time(), -1);
SrsNtp ntp = SrsNtp::from_time_ms(video_absolute_ts);
SrsRtcpSR *video_sr1 = new SrsRtcpSR();
SrsUniquePtr<SrsRtcpSR> video_sr1_uptr(video_sr1);
video_sr1->set_ssrc(200);
video_sr1->set_ntp(ntp.ntp_);
video_sr1->set_rtp_ts(video_rtp_ts);
// Video timebase 90000, fps=25
video_rtp_ts += 3600;
video_absolute_ts += 40;
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
// No received any sender report, can not calculate absolute time, expect equal to -1.
EXPECT_EQ(video_rtp_pkt->get_avsync_time(), -1);
ntp = SrsNtp::from_time_ms(video_absolute_ts);
SrsRtcpSR *video_sr2 = new SrsRtcpSR();
SrsUniquePtr<SrsRtcpSR> video_sr2_uptr(video_sr2);
video_sr2->set_ssrc(200);
video_sr2->set_ntp(ntp.ntp_);
video_sr2->set_rtp_ts(video_rtp_ts);
// Sender report out of order, sr2 arrived befreo sr1.
publish.on_rtcp_sr(video_sr2);
publish.on_rtcp_sr(video_sr1);
for (int i = 0; i <= 1000; ++i) {
// Video timebase 90000, fps=25
video_rtp_ts += 3600;
video_absolute_ts += 40;
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
EXPECT_NEAR(video_rtp_pkt->get_avsync_time(), video_absolute_ts, 1);
}
}
}
VOID TEST(KernelRTCTest, SyncTimestampBySenderReportConsecutive)
{
MockRtcAsyncTaskExecutor mock_exec;
MockExpire mock_expire;
MockRtcPacketReceiver mock_receiver;
SrsRtcPublishStream publish(&mock_exec, &mock_expire, &mock_receiver, SrsContextId());
SrsRtcTrackDescription video_ds;
video_ds.type_ = "video";
video_ds.id_ = "VMo22nfLDn122nfnDNL2";
video_ds.ssrc_ = 200;
SrsRtcVideoRecvTrack *video = new SrsRtcVideoRecvTrack(&mock_receiver, &video_ds, false);
publish.video_tracks_.push_back(video);
publish.set_all_tracks_status(true);
SrsSharedPtr<SrsRtcSource> rtc_source(new SrsRtcSource());
srand(time(NULL));
if (true) {
SrsRtpPacket *video_rtp_pkt = new SrsRtpPacket();
SrsUniquePtr<SrsRtpPacket> video_rtp_pkt_uptr(video_rtp_pkt);
uint32_t video_absolute_ts = srs_time_now_cached();
uint32_t video_rtp_ts = random();
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
// No received any sender report, can not calculate absolute time, expect equal to -1.
EXPECT_EQ(video_rtp_pkt->get_avsync_time(), -1);
SrsNtp ntp = SrsNtp::from_time_ms(video_absolute_ts);
SrsRtcpSR *video_sr = new SrsRtcpSR();
SrsUniquePtr<SrsRtcpSR> video_sr_uptr(video_sr);
video_sr->set_ssrc(200);
video_sr->set_ntp(ntp.ntp_);
video_sr->set_rtp_ts(video_rtp_ts);
publish.on_rtcp_sr(video_sr);
// Video timebase 90000, fps=25
video_rtp_ts += 3600;
video_absolute_ts += 40;
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
// Received one sender report, can not calculate absolute time, expect equal to -1.
EXPECT_EQ(video_rtp_pkt->get_avsync_time(), -1);
ntp = SrsNtp::from_time_ms(video_absolute_ts);
video_sr->set_ntp(ntp.ntp_);
video_sr->set_rtp_ts(video_rtp_ts);
publish.on_rtcp_sr(video_sr);
for (int i = 0; i <= 1000; ++i) {
// Video timebase 90000, fps=25
video_rtp_ts += 3600;
video_absolute_ts += 40;
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
EXPECT_NEAR(video_rtp_pkt->get_avsync_time(), video_absolute_ts, 1);
// Send sender report every 4 seconds.
if (i % 100 == 99) {
ntp = SrsNtp::from_time_ms(video_absolute_ts);
video_sr->set_ntp(ntp.ntp_);
video_sr->set_rtp_ts(video_rtp_ts);
publish.on_rtcp_sr(video_sr);
}
}
}
}
VOID TEST(KernelRTCTest, SrsRtcpNack)
{
uint32_t sender_ssrc = 0x0A;
uint32_t media_ssrc = 0x0B;
SrsRtcpNack nack_encoder(sender_ssrc);
nack_encoder.set_media_ssrc(media_ssrc);
for (uint16_t seq = 15; seq < 45; seq++) {
nack_encoder.add_lost_sn(seq);
}
EXPECT_FALSE(nack_encoder.empty());
char buf[kRtcpPacketSize];
SrsBuffer stream(buf, sizeof(buf));
srs_error_t err = srs_success;
err = nack_encoder.encode(&stream);
EXPECT_EQ(srs_error_code(err), srs_success);
SrsRtcpNack nack_decoder;
stream.skip(-stream.pos());
err = nack_decoder.decode(&stream);
EXPECT_EQ(srs_error_code(err), srs_success);
vector<uint16_t> actual_lost_sn = nack_encoder.get_lost_sns();
vector<uint16_t> req_lost_sns = nack_decoder.get_lost_sns();
EXPECT_EQ(actual_lost_sn.size(), req_lost_sns.size());
}
VOID TEST(KernelRTCTest, SyncTimestampBySenderReportDuplicated)
{
MockRtcAsyncTaskExecutor mock_exec;
MockExpire mock_expire;
MockRtcPacketReceiver mock_receiver;
SrsRtcPublishStream publish(&mock_exec, &mock_expire, &mock_receiver, SrsContextId());
SrsRtcTrackDescription video_ds;
video_ds.type_ = "video";
video_ds.id_ = "VMo22nfLDn122nfnDNL2";
video_ds.ssrc_ = 200;
SrsRtcVideoRecvTrack *video = new SrsRtcVideoRecvTrack(&mock_receiver, &video_ds, false);
publish.video_tracks_.push_back(video);
publish.set_all_tracks_status(true);
SrsSharedPtr<SrsRtcSource> rtc_source(new SrsRtcSource());
srand(time(NULL));
if (true) {
SrsRtpPacket *video_rtp_pkt = new SrsRtpPacket();
SrsUniquePtr<SrsRtpPacket> video_rtp_pkt_uptr(video_rtp_pkt);
uint32_t video_absolute_ts = srs_time_now_cached();
uint32_t video_rtp_ts = random();
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
// No received any sender report, can not calculate absolute time, expect equal to -1.
EXPECT_EQ(video_rtp_pkt->get_avsync_time(), -1);
SrsNtp ntp = SrsNtp::from_time_ms(video_absolute_ts);
SrsRtcpSR *video_sr = new SrsRtcpSR();
SrsUniquePtr<SrsRtcpSR> video_sr_uptr(video_sr);
video_sr->set_ssrc(200);
video_sr->set_ntp(ntp.ntp_);
video_sr->set_rtp_ts(video_rtp_ts);
publish.on_rtcp_sr(video_sr);
// Video timebase 90000, fps=25
video_rtp_ts += 3600;
video_absolute_ts += 40;
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
// Received one sender report, can not calculate absolute time, expect equal to -1.
EXPECT_EQ(video_rtp_pkt->get_avsync_time(), -1);
ntp = SrsNtp::from_time_ms(video_absolute_ts);
video_sr->set_ntp(ntp.ntp_);
video_sr->set_rtp_ts(video_rtp_ts);
publish.on_rtcp_sr(video_sr);
for (int i = 0; i <= 1000; ++i) {
// Video timebase 90000, fps=25
video_rtp_ts += 3600;
video_absolute_ts += 40;
video_rtp_pkt->header_.set_timestamp(video_rtp_ts);
video->on_rtp(rtc_source, video_rtp_pkt);
EXPECT_NEAR(video_rtp_pkt->get_avsync_time(), video_absolute_ts, 1);
// Duplicate 3 sender report packets.
if (i % 3 == 0) {
ntp = SrsNtp::from_time_ms(video_absolute_ts);
video_sr->set_ntp(ntp.ntp_);
video_sr->set_rtp_ts(video_rtp_ts);
}
publish.on_rtcp_sr(video_sr);
}
}
}
VOID TEST(KernelRTCTest, JitterTimestamp)
{
SrsRtcTsJitter jitter(1000);
// Starts from the base.
EXPECT_EQ((uint32_t)1000, jitter.correct(0));
// Start from here.
EXPECT_EQ((uint32_t)1010, jitter.correct(10));
EXPECT_EQ((uint32_t)1010, jitter.correct(10));
EXPECT_EQ((uint32_t)1020, jitter.correct(20));
// Reset the base for jitter detected.
EXPECT_EQ((uint32_t)1020, jitter.correct(20 + 90 * 3 * 1000 + 1));
EXPECT_EQ((uint32_t)1019, jitter.correct(20 + 90 * 3 * 1000));
EXPECT_EQ((uint32_t)1021, jitter.correct(20 + 90 * 3 * 1000 + 2));
EXPECT_EQ((uint32_t)1019, jitter.correct(20 + 90 * 3 * 1000));
EXPECT_EQ((uint32_t)1020, jitter.correct(20 + 90 * 3 * 1000 + 1));
// Rollback the timestamp.
EXPECT_EQ((uint32_t)1020, jitter.correct(20));
EXPECT_EQ((uint32_t)1021, jitter.correct(20 + 1));
EXPECT_EQ((uint32_t)1021, jitter.correct(21));
// Reset for jitter again.
EXPECT_EQ((uint32_t)1021, jitter.correct(21 + 90 * 3 * 1000 + 1));
EXPECT_EQ((uint32_t)1021, jitter.correct(21));
// No jitter at edge.
EXPECT_EQ((uint32_t)(1021 + 90 * 3 * 1000), jitter.correct(21 + 90 * 3 * 1000));
EXPECT_EQ((uint32_t)(1021 + 90 * 3 * 1000 + 1), jitter.correct(21 + 90 * 3 * 1000 + 1));
EXPECT_EQ((uint32_t)(1021 + 1), jitter.correct(21 + 1));
// Also safety to decrease the value.
EXPECT_EQ((uint32_t)1021, jitter.correct(21));
EXPECT_EQ((uint32_t)1010, jitter.correct(10));
// Try to reset to 0 base.
EXPECT_EQ((uint32_t)1010, jitter.correct(10 + 90 * 3 * 1000 + 1010));
EXPECT_EQ((uint32_t)0, jitter.correct(10 + 90 * 3 * 1000));
EXPECT_EQ((uint32_t)0, jitter.correct(0));
// Also safety to start from zero.
EXPECT_EQ((uint32_t)10, jitter.correct(10));
EXPECT_EQ((uint32_t)11, jitter.correct(11));
}
VOID TEST(KernelRTCTest, JitterSequence)
{
SrsRtcSeqJitter jitter(100);
// Starts from the base.
EXPECT_EQ((uint32_t)100, jitter.correct(0));
// Normal without jitter.
EXPECT_EQ((uint32_t)101, jitter.correct(1));
EXPECT_EQ((uint32_t)102, jitter.correct(2));
EXPECT_EQ((uint32_t)101, jitter.correct(1));
EXPECT_EQ((uint32_t)103, jitter.correct(3));
EXPECT_EQ((uint32_t)110, jitter.correct(10));
// Reset the base for jitter detected.
EXPECT_EQ((uint32_t)110, jitter.correct(10 + 128 + 1));
EXPECT_EQ((uint32_t)109, jitter.correct(10 + 128));
EXPECT_EQ((uint32_t)110, jitter.correct(10 + 128 + 1));
// Rollback the timestamp.
EXPECT_EQ((uint32_t)110, jitter.correct(10));
EXPECT_EQ((uint32_t)111, jitter.correct(10 + 1));
EXPECT_EQ((uint32_t)111, jitter.correct(11));
// Reset for jitter again.
EXPECT_EQ((uint32_t)111, jitter.correct(11 + 128 + 1));
EXPECT_EQ((uint32_t)111, jitter.correct(11));
// No jitter at edge.
EXPECT_EQ((uint32_t)(111 + 128), jitter.correct(11 + 128));
EXPECT_EQ((uint32_t)(111 + 128 + 1), jitter.correct(11 + 128 + 1));
EXPECT_EQ((uint32_t)(111 + 1), jitter.correct(11 + 1));
// Also safety to decrease the value.
EXPECT_EQ((uint32_t)111, jitter.correct(11));
EXPECT_EQ((uint32_t)110, jitter.correct(10));
// Try to reset to 0 base.
EXPECT_EQ((uint32_t)110, jitter.correct(10 + 128 + 110));
EXPECT_EQ((uint32_t)0, jitter.correct(10 + 128));
EXPECT_EQ((uint32_t)0, jitter.correct(0));
// Also safety to start from zero.
EXPECT_EQ((uint32_t)10, jitter.correct(10));
EXPECT_EQ((uint32_t)11, jitter.correct(11));
}
VOID TEST(KernelRTCTest, H265SDPParsing)
{
srs_error_t err;
// Test srs_parse_h265_fmtp with valid parameters
if (true) {
H265SpecificParam h265_param;
string fmtp = "level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST";
HELPER_EXPECT_SUCCESS(srs_parse_h265_fmtp(fmtp, h265_param));
EXPECT_STREQ("180", h265_param.level_id_.c_str());
EXPECT_STREQ("1", h265_param.profile_id_.c_str());
EXPECT_STREQ("0", h265_param.tier_flag_.c_str());
EXPECT_STREQ("SRST", h265_param.tx_mode_.c_str());
}
// Test srs_parse_h265_fmtp with different parameter order
if (true) {
H265SpecificParam h265_param;
string fmtp = "profile-id=2;tier-flag=1;level-id=93;tx-mode=MCTS";
HELPER_EXPECT_SUCCESS(srs_parse_h265_fmtp(fmtp, h265_param));
EXPECT_STREQ("93", h265_param.level_id_.c_str());
EXPECT_STREQ("2", h265_param.profile_id_.c_str());
EXPECT_STREQ("1", h265_param.tier_flag_.c_str());
EXPECT_STREQ("MCTS", h265_param.tx_mode_.c_str());
}
// Test srs_parse_h265_fmtp with missing level-id (should fail)
if (true) {
H265SpecificParam h265_param;
string fmtp = "profile-id=1;tier-flag=0;tx-mode=SRST";
HELPER_EXPECT_FAILED(srs_parse_h265_fmtp(fmtp, h265_param));
}
// Test srs_parse_h265_fmtp with missing profile-id (should fail)
if (true) {
H265SpecificParam h265_param;
string fmtp = "level-id=180;tier-flag=0;tx-mode=SRST";
HELPER_EXPECT_FAILED(srs_parse_h265_fmtp(fmtp, h265_param));
}
// Test srs_parse_h265_fmtp with missing tier-flag (should fail)
if (true) {
H265SpecificParam h265_param;
string fmtp = "level-id=180;profile-id=1;tx-mode=SRST";
HELPER_EXPECT_FAILED(srs_parse_h265_fmtp(fmtp, h265_param));
}
// Test srs_parse_h265_fmtp with missing tx-mode (should fail)
if (true) {
H265SpecificParam h265_param;
string fmtp = "level-id=180;profile-id=1;tier-flag=0";
HELPER_EXPECT_FAILED(srs_parse_h265_fmtp(fmtp, h265_param));
}
// Test srs_parse_h265_fmtp with empty string (should fail)
if (true) {
H265SpecificParam h265_param;
string fmtp = "";
HELPER_EXPECT_FAILED(srs_parse_h265_fmtp(fmtp, h265_param));
}
// Test srs_parse_h265_fmtp with extra unknown parameters (should succeed)
if (true) {
H265SpecificParam h265_param;
string fmtp = "level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST;unknown-param=value";
HELPER_EXPECT_SUCCESS(srs_parse_h265_fmtp(fmtp, h265_param));
EXPECT_STREQ("180", h265_param.level_id_.c_str());
EXPECT_STREQ("1", h265_param.profile_id_.c_str());
EXPECT_STREQ("0", h265_param.tier_flag_.c_str());
EXPECT_STREQ("SRST", h265_param.tx_mode_.c_str());
}
}
// Forward declarations for H.265 SDP functions (defined in srs_app_rtc_conn.cpp)
extern bool srs_sdp_has_h265_profile(const SrsMediaPayloadType &payload_type, const string &profile);
extern bool srs_sdp_has_h265_profile(const SrsSdp &sdp, const string &profile);
VOID TEST(KernelRTCTest, H265SDPProfileChecking)
{
// Test srs_sdp_has_h265_profile with payload type
if (true) {
SrsMediaPayloadType payload_type(96);
payload_type.format_specific_param_ = "level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST";
// Should find Main Profile (profile-id=1)
EXPECT_TRUE(srs_sdp_has_h265_profile(payload_type, "1"));
// Should not find other profiles
EXPECT_FALSE(srs_sdp_has_h265_profile(payload_type, "2"));
EXPECT_FALSE(srs_sdp_has_h265_profile(payload_type, "3"));
}
// Test srs_sdp_has_h265_profile with different profile
if (true) {
SrsMediaPayloadType payload_type(97);
payload_type.format_specific_param_ = "level-id=93;profile-id=2;tier-flag=1;tx-mode=MCTS";
// Should find Main 10 Profile (profile-id=2)
EXPECT_TRUE(srs_sdp_has_h265_profile(payload_type, "2"));
// Should not find Main Profile
EXPECT_FALSE(srs_sdp_has_h265_profile(payload_type, "1"));
}
// Test srs_sdp_has_h265_profile with empty format_specific_param_
if (true) {
SrsMediaPayloadType payload_type(98);
payload_type.format_specific_param_ = "";
// Should not find any profile
EXPECT_FALSE(srs_sdp_has_h265_profile(payload_type, "1"));
EXPECT_FALSE(srs_sdp_has_h265_profile(payload_type, "2"));
}
// Test srs_sdp_has_h265_profile with invalid format_specific_param_
if (true) {
SrsMediaPayloadType payload_type(99);
payload_type.format_specific_param_ = "invalid-format";
// Should not find any profile (parsing should fail gracefully)
EXPECT_FALSE(srs_sdp_has_h265_profile(payload_type, "1"));
}
// Test srs_sdp_has_h265_profile with SDP containing H.265
if (true) {
SrsSdp sdp;
SrsMediaDesc video_desc("video");
SrsMediaPayloadType h265_payload(96);
h265_payload.encoding_name_ = "H265";
h265_payload.format_specific_param_ = "level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST";
video_desc.payload_types_.push_back(h265_payload);
sdp.media_descs_.push_back(video_desc);
// Should find Main Profile in SDP
EXPECT_TRUE(srs_sdp_has_h265_profile(sdp, "1"));
EXPECT_FALSE(srs_sdp_has_h265_profile(sdp, "2"));
}
// Test srs_sdp_has_h265_profile with SDP containing no H.265
if (true) {
SrsSdp sdp;
SrsMediaDesc video_desc("video");
SrsMediaPayloadType h264_payload(96);
h264_payload.encoding_name_ = "H264";
h264_payload.format_specific_param_ = "profile-level-id=42e01f";
video_desc.payload_types_.push_back(h264_payload);
sdp.media_descs_.push_back(video_desc);
// Should not find any H.265 profile
EXPECT_FALSE(srs_sdp_has_h265_profile(sdp, "1"));
}
// Test srs_sdp_has_h265_profile with SDP containing audio only
if (true) {
SrsSdp sdp;
SrsMediaDesc audio_desc("audio");
SrsMediaPayloadType opus_payload(111);
opus_payload.encoding_name_ = "opus";
audio_desc.payload_types_.push_back(opus_payload);
sdp.media_descs_.push_back(audio_desc);
// Should not find any H.265 profile in audio-only SDP
EXPECT_FALSE(srs_sdp_has_h265_profile(sdp, "1"));
}
}
VOID TEST(KernelRTCTest, H265VideoPayload)
{
srs_error_t err;
// Test SrsVideoPayload::set_h265_param_desc with valid parameters
if (true) {
SrsVideoPayload payload;
string fmtp = "level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST";
HELPER_EXPECT_SUCCESS(payload.set_h265_param_desc(fmtp));
EXPECT_STREQ("180", payload.h265_param_.level_id_.c_str());
EXPECT_STREQ("1", payload.h265_param_.profile_id_.c_str());
EXPECT_STREQ("0", payload.h265_param_.tier_flag_.c_str());
EXPECT_STREQ("SRST", payload.h265_param_.tx_mode_.c_str());
}
// Test SrsVideoPayload::set_h265_param_desc with different order
if (true) {
SrsVideoPayload payload;
string fmtp = "tx-mode=MCTS;tier-flag=1;profile-id=2;level-id=93";
HELPER_EXPECT_SUCCESS(payload.set_h265_param_desc(fmtp));
EXPECT_STREQ("93", payload.h265_param_.level_id_.c_str());
EXPECT_STREQ("2", payload.h265_param_.profile_id_.c_str());
EXPECT_STREQ("1", payload.h265_param_.tier_flag_.c_str());
EXPECT_STREQ("MCTS", payload.h265_param_.tx_mode_.c_str());
}
// Test SrsVideoPayload::set_h265_param_desc with invalid parameter format
if (true) {
SrsVideoPayload payload;
string fmtp = "level-id=180;invalid-format;profile-id=1";
HELPER_EXPECT_FAILED(payload.set_h265_param_desc(fmtp));
}
// Test SrsVideoPayload::set_h265_param_desc with unknown parameter
if (true) {
SrsVideoPayload payload;
string fmtp = "level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST;unknown-param=value";
HELPER_EXPECT_FAILED(payload.set_h265_param_desc(fmtp));
}
// Test SrsVideoPayload::generate_media_payload_type_h265
if (true) {
SrsVideoPayload payload;
payload.pt_ = 96;
payload.name_ = "H265";
payload.sample_ = 90000;
payload.h265_param_.level_id_ = "180";
payload.h265_param_.profile_id_ = "1";
payload.h265_param_.tier_flag_ = "0";
payload.h265_param_.tx_mode_ = "SRST";
SrsMediaPayloadType media_type = payload.generate_media_payload_type_h265();
EXPECT_EQ(96, media_type.payload_type_);
EXPECT_STREQ("H265", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::generate_media_payload_type_h265 with partial parameters
if (true) {
SrsVideoPayload payload;
payload.pt_ = 97;
payload.name_ = "H265";
payload.sample_ = 90000;
payload.h265_param_.level_id_ = "93";
payload.h265_param_.profile_id_ = "2";
// tier_flag and tx_mode are empty
SrsMediaPayloadType media_type = payload.generate_media_payload_type_h265();
EXPECT_EQ(97, media_type.payload_type_);
EXPECT_STREQ("H265", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("level-id=93;profile-id=2", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::copy includes H.265 parameters
if (true) {
SrsVideoPayload original;
original.pt_ = 96;
original.name_ = "H265";
original.sample_ = 90000;
original.h265_param_.level_id_ = "180";
original.h265_param_.profile_id_ = "1";
original.h265_param_.tier_flag_ = "0";
original.h265_param_.tx_mode_ = "SRST";
SrsVideoPayload *copied = original.copy();
SrsUniquePtr<SrsVideoPayload> copied_uptr(copied);
EXPECT_EQ(original.pt_, copied->pt_);
EXPECT_STREQ(original.name_.c_str(), copied->name_.c_str());
EXPECT_EQ(original.sample_, copied->sample_);
EXPECT_STREQ(original.h265_param_.level_id_.c_str(), copied->h265_param_.level_id_.c_str());
EXPECT_STREQ(original.h265_param_.profile_id_.c_str(), copied->h265_param_.profile_id_.c_str());
EXPECT_STREQ(original.h265_param_.tier_flag_.c_str(), copied->h265_param_.tier_flag_.c_str());
EXPECT_STREQ(original.h265_param_.tx_mode_.c_str(), copied->h265_param_.tx_mode_.c_str());
}
}
VOID TEST(KernelRTCTest, H265RtpSTAPPayload)
{
srs_error_t err;
// Test SrsRtpSTAPPayloadHevc encoding and decoding
if (true) {
SrsRtpSTAPPayloadHevc stap;
// Create sample VPS NALU
SrsNaluSample *vps = new SrsNaluSample();
uint8_t vps_data[] = {0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3d, 0x95, 0x98, 0x09};
vps->bytes_ = (char *)vps_data;
vps->size_ = sizeof(vps_data);
stap.nalus_.push_back(vps);
// Create sample SPS NALU
SrsNaluSample *sps = new SrsNaluSample();
uint8_t sps_data[] = {0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3d, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0xc0, 0x5a, 0x70, 0x80, 0x80, 0x80, 0x82};
sps->bytes_ = (char *)sps_data;
sps->size_ = sizeof(sps_data);
stap.nalus_.push_back(sps);
// Create sample PPS NALU
SrsNaluSample *pps = new SrsNaluSample();
uint8_t pps_data[] = {0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40};
pps->bytes_ = (char *)pps_data;
pps->size_ = sizeof(pps_data);
stap.nalus_.push_back(pps);
// Test encoding
char buf[1500];
SrsBuffer encode_buf(buf, sizeof(buf));
HELPER_EXPECT_SUCCESS(stap.encode(&encode_buf));
// Verify encoded size
uint64_t expected_size = 2 + 2 + sizeof(vps_data) + 2 + sizeof(sps_data) + 2 + sizeof(pps_data);
EXPECT_EQ(expected_size, stap.nb_bytes());
EXPECT_EQ((int)expected_size, encode_buf.pos());
// Test decoding
SrsRtpSTAPPayloadHevc decode_stap;
SrsBuffer decode_buf(buf, encode_buf.pos()); // Create new buffer with encoded data
HELPER_EXPECT_SUCCESS(decode_stap.decode(&decode_buf));
// Verify decoded NALUs
EXPECT_EQ(3, (int)decode_stap.nalus_.size());
// Check VPS
SrsNaluSample *decoded_vps = decode_stap.get_vps();
EXPECT_TRUE(decoded_vps != NULL);
EXPECT_EQ(sizeof(vps_data), (size_t)decoded_vps->size_);
// Check SPS
SrsNaluSample *decoded_sps = decode_stap.get_sps();
EXPECT_TRUE(decoded_sps != NULL);
EXPECT_EQ(sizeof(sps_data), (size_t)decoded_sps->size_);
// Check PPS
SrsNaluSample *decoded_pps = decode_stap.get_pps();
EXPECT_TRUE(decoded_pps != NULL);
EXPECT_EQ(sizeof(pps_data), (size_t)decoded_pps->size_);
// Test copy functionality
ISrsRtpPayloader *copied = stap.copy();
SrsUniquePtr<ISrsRtpPayloader> copied_uptr(copied);
SrsRtpSTAPPayloadHevc *copied_stap = dynamic_cast<SrsRtpSTAPPayloadHevc *>(copied);
EXPECT_TRUE(copied_stap != NULL);
EXPECT_EQ(3, (int)copied_stap->nalus_.size());
}
// Test SrsRtpSTAPPayloadHevc with empty NALUs
if (true) {
SrsRtpSTAPPayloadHevc stap;
// Test encoding with no NALUs
char buf[100];
SrsBuffer encode_buf(buf, sizeof(buf));
HELPER_EXPECT_SUCCESS(stap.encode(&encode_buf));
EXPECT_EQ(2, encode_buf.pos()); // Only STAP header
// Test get functions with no NALUs
EXPECT_TRUE(stap.get_vps() == NULL);
EXPECT_TRUE(stap.get_sps() == NULL);
EXPECT_TRUE(stap.get_pps() == NULL);
}
// Test SrsRtpSTAPPayloadHevc decoding with forbidden_zero_bit set (should fail)
if (true) {
SrsRtpSTAPPayloadHevc stap;
char buf[] = {static_cast<char>(0x80), 0x01}; // forbidden_zero_bit = 1
SrsBuffer decode_buf(buf, sizeof(buf));
HELPER_EXPECT_FAILED(stap.decode(&decode_buf));
}
}
VOID TEST(KernelRTCTest, H265RtpFUAPayload)
{
srs_error_t err;
// Test SrsRtpFUAPayloadHevc encoding and decoding
if (true) {
SrsRtpFUAPayloadHevc fua;
fua.start_ = true;
fua.end_ = false;
fua.nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR;
// Create sample payload data
SrsNaluSample *sample = new SrsNaluSample();
uint8_t payload_data[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
sample->bytes_ = (char *)payload_data;
sample->size_ = sizeof(payload_data);
fua.nalus_.push_back(sample);
// Test encoding
char buf[100];
SrsBuffer encode_buf(buf, sizeof(buf));
HELPER_EXPECT_SUCCESS(fua.encode(&encode_buf));
// Verify encoded size (PayloadHdr(2) + FU header(1) + payload)
uint64_t expected_size = 3 + sizeof(payload_data);
EXPECT_EQ(expected_size, fua.nb_bytes());
EXPECT_EQ((int)expected_size, encode_buf.pos());
// Test decoding
SrsRtpFUAPayloadHevc decode_fua;
SrsBuffer decode_buf(buf, encode_buf.pos()); // Create new buffer with encoded data
HELPER_EXPECT_SUCCESS(decode_fua.decode(&decode_buf));
// Verify decoded values
EXPECT_TRUE(decode_fua.start_);
EXPECT_FALSE(decode_fua.end_);
EXPECT_EQ(SrsHevcNaluType_CODED_SLICE_IDR, decode_fua.nalu_type_);
EXPECT_EQ(1, (int)decode_fua.nalus_.size());
EXPECT_EQ(sizeof(payload_data), (size_t)decode_fua.nalus_[0]->size_);
// Test copy functionality
ISrsRtpPayloader *copied = fua.copy();
SrsUniquePtr<ISrsRtpPayloader> copied_uptr(copied);
SrsRtpFUAPayloadHevc *copied_fua = dynamic_cast<SrsRtpFUAPayloadHevc *>(copied);
EXPECT_TRUE(copied_fua != NULL);
EXPECT_TRUE(copied_fua->start_);
EXPECT_FALSE(copied_fua->end_);
EXPECT_EQ(SrsHevcNaluType_CODED_SLICE_IDR, copied_fua->nalu_type_);
EXPECT_EQ(1, (int)copied_fua->nalus_.size());
}
// Test SrsRtpFUAPayloadHevc2 encoding and decoding
if (true) {
SrsRtpFUAPayloadHevc2 fua2;
fua2.start_ = false;
fua2.end_ = true;
fua2.nalu_type_ = SrsHevcNaluType_CODED_SLICE_TRAIL_R;
uint8_t payload_data[] = {0xAA, 0xBB, 0xCC, 0xDD};
fua2.payload_ = (char *)payload_data;
fua2.size_ = sizeof(payload_data);
// Test encoding
char buf[100];
SrsBuffer encode_buf(buf, sizeof(buf));
HELPER_EXPECT_SUCCESS(fua2.encode(&encode_buf));
// Verify encoded size (PayloadHdr(2) + FU header(1) + payload)
uint64_t expected_size = 3 + sizeof(payload_data);
EXPECT_EQ(expected_size, fua2.nb_bytes());
EXPECT_EQ((int)expected_size, encode_buf.pos());
// Test decoding
SrsRtpFUAPayloadHevc2 decode_fua2;
SrsBuffer decode_buf2(buf, encode_buf.pos()); // Create new buffer with encoded data
HELPER_EXPECT_SUCCESS(decode_fua2.decode(&decode_buf2));
// Verify decoded values
EXPECT_FALSE(decode_fua2.start_);
EXPECT_TRUE(decode_fua2.end_);
EXPECT_EQ(SrsHevcNaluType_CODED_SLICE_TRAIL_R, decode_fua2.nalu_type_);
EXPECT_EQ(sizeof(payload_data), (size_t)decode_fua2.size_);
// Test copy functionality
ISrsRtpPayloader *copied = fua2.copy();
SrsUniquePtr<ISrsRtpPayloader> copied_uptr(copied);
SrsRtpFUAPayloadHevc2 *copied_fua2 = dynamic_cast<SrsRtpFUAPayloadHevc2 *>(copied);
EXPECT_TRUE(copied_fua2 != NULL);
EXPECT_FALSE(copied_fua2->start_);
EXPECT_TRUE(copied_fua2->end_);
EXPECT_EQ(SrsHevcNaluType_CODED_SLICE_TRAIL_R, copied_fua2->nalu_type_);
EXPECT_EQ(sizeof(payload_data), (size_t)copied_fua2->size_);
}
}
VOID TEST(KernelRTCTest, H265RtpPacketKeyframe)
{
// Test RTP packet keyframe detection for HEVC
if (true) {
SrsRtpPacket pkt;
pkt.frame_type_ = SrsFrameTypeVideo;
// Test VPS NALU (should be keyframe)
pkt.nalu_type_ = SrsHevcNaluType_VPS;
EXPECT_TRUE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
// Test SPS NALU (should be keyframe)
pkt.nalu_type_ = SrsHevcNaluType_SPS;
EXPECT_TRUE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
// Test PPS NALU (should be keyframe)
pkt.nalu_type_ = SrsHevcNaluType_PPS;
EXPECT_TRUE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
// Test IDR NALU (should be keyframe)
pkt.nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR;
EXPECT_TRUE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
// Test CRA NALU (should be keyframe)
pkt.nalu_type_ = SrsHevcNaluType_CODED_SLICE_CRA;
EXPECT_TRUE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
// Test BLA NALU (should be keyframe)
pkt.nalu_type_ = SrsHevcNaluType_CODED_SLICE_BLA;
EXPECT_TRUE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
// Test regular P-frame NALU (should not be keyframe)
pkt.nalu_type_ = SrsHevcNaluType_CODED_SLICE_TRAIL_R;
EXPECT_FALSE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
// Test regular B-frame NALU (should not be keyframe)
pkt.nalu_type_ = SrsHevcNaluType_CODED_SLICE_TSA_N;
EXPECT_FALSE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
}
// Test HEVC STAP payload keyframe detection
if (true) {
SrsRtpPacket pkt;
pkt.frame_type_ = SrsFrameTypeVideo;
pkt.nalu_type_ = kStapHevc;
SrsRtpSTAPPayloadHevc *stap_payload = new SrsRtpSTAPPayloadHevc();
pkt.set_payload(stap_payload, SrsRtpPacketPayloadTypeSTAPHevc);
// Create VPS NALU
SrsNaluSample *vps = new SrsNaluSample();
uint8_t vps_data[] = {0x40, 0x01}; // VPS NALU header
vps->bytes_ = (char *)vps_data;
vps->size_ = sizeof(vps_data);
stap_payload->nalus_.push_back(vps);
// Should be keyframe because it contains VPS
EXPECT_TRUE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
}
// Test HEVC FU-A payload keyframe detection
if (true) {
SrsRtpPacket pkt;
pkt.frame_type_ = SrsFrameTypeVideo;
pkt.nalu_type_ = kFuHevc;
SrsRtpFUAPayloadHevc2 *fua_payload = new SrsRtpFUAPayloadHevc2();
pkt.set_payload(fua_payload, SrsRtpPacketPayloadTypeFUAHevc2);
// Test IDR slice in FU-A (should be keyframe)
fua_payload->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR;
EXPECT_TRUE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
// Test regular slice in FU-A (should not be keyframe)
fua_payload->nalu_type_ = SrsHevcNaluType_CODED_SLICE_TRAIL_R;
EXPECT_FALSE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
}
// Test audio packet (should not be keyframe regardless of NALU type)
if (true) {
SrsRtpPacket pkt;
pkt.frame_type_ = SrsFrameTypeAudio;
pkt.nalu_type_ = SrsHevcNaluType_VPS;
EXPECT_FALSE(pkt.is_keyframe(SrsVideoCodecIdHEVC));
}
}
// Note: Stream bridge codec switching tests are complex and require full RTC infrastructure
// These would be better tested in integration tests rather than unit tests
VOID TEST(KernelRTCTest, H265RtpRawNALUsSkipBytes)
{
srs_error_t err;
// Test SrsRtpRawNALUs::skip_bytes for HEVC (2 bytes header)
if (true) {
SrsRtpRawNALUs raw_nalus;
// Create sample HEVC NALU
SrsNaluSample *sample = new SrsNaluSample();
uint8_t nalu_data[] = {0x26, 0x01, 0x12, 0x34, 0x56, 0x78}; // IDR slice
sample->bytes_ = (char *)nalu_data;
sample->size_ = sizeof(nalu_data);
raw_nalus.push_back(sample);
// Skip HEVC header (2 bytes)
uint8_t header = raw_nalus.skip_bytes(SrsHevcNaluHeaderSize);
EXPECT_EQ(0x26, header); // Should return first byte
// Verify remaining data
std::vector<SrsNaluSample *> samples;
HELPER_EXPECT_SUCCESS(raw_nalus.read_samples(samples, 4));
EXPECT_EQ(1, (int)samples.size());
EXPECT_EQ(4, samples[0]->size_);
EXPECT_EQ(0x12, (uint8_t)samples[0]->bytes_[0]);
EXPECT_EQ(0x34, (uint8_t)samples[0]->bytes_[1]);
EXPECT_EQ(0x56, (uint8_t)samples[0]->bytes_[2]);
EXPECT_EQ(0x78, (uint8_t)samples[0]->bytes_[3]);
// Clean up
for (size_t i = 0; i < samples.size(); i++) {
srs_freep(samples[i]);
}
}
// Test SrsRtpRawNALUs::skip_bytes for H.264 (1 byte header)
if (true) {
SrsRtpRawNALUs raw_nalus;
// Create sample H.264 NALU
SrsNaluSample *sample = new SrsNaluSample();
uint8_t nalu_data[] = {0x65, 0x12, 0x34, 0x56}; // IDR slice
sample->bytes_ = (char *)nalu_data;
sample->size_ = sizeof(nalu_data);
raw_nalus.push_back(sample);
// Skip H.264 header (1 byte)
uint8_t header = raw_nalus.skip_bytes(SrsAvcNaluHeaderSize);
EXPECT_EQ(0x65, header); // Should return first byte
// Verify remaining data
std::vector<SrsNaluSample *> samples;
HELPER_EXPECT_SUCCESS(raw_nalus.read_samples(samples, 3));
EXPECT_EQ(1, (int)samples.size());
EXPECT_EQ(3, samples[0]->size_);
EXPECT_EQ(0x12, (uint8_t)samples[0]->bytes_[0]);
EXPECT_EQ(0x34, (uint8_t)samples[0]->bytes_[1]);
EXPECT_EQ(0x56, (uint8_t)samples[0]->bytes_[2]);
// Clean up
for (size_t i = 0; i < samples.size(); i++) {
srs_freep(samples[i]);
}
}
}