srs/trunk/src/utest/srs_utest_ai14.cpp
OSSRS-AI 1a96abc880
AI: API: Add audio_frames and video_frames to HTTP API. v7.0.122 (#4559) (#4564)
This PR adds separate audio and video frame counting to the HTTP API
(`/api/v1/streams/`) for better stream observability. The API now
reports three frame fields:
- `frames` - Total frames (video + audio)
- `video_frames` - Video frames/packets only
- `audio_frames` - Audio frames/packets only

This enhancement provides better visibility into stream composition and
helps detect issues with CBR/VBR streams, audio/video sync problems, and
codec-specific behavior.

**Before:**
```json
{
  "streams": [
    {
      "frames": 0, // video frames.
    }
  ]
}
```

**After:**
```json
{
  "streams": [
    {
      "frames": 6912, // video frames.
      "audio_frames": 5678, // audio frames.
      "video_frames": 1234, // video frames.
    }
  ]
}
```

Frame Counting Strategy
- All protocols report frames every N frames to balance accuracy and
performance
- Frames are counted at the protocol-specific message/packet level:
  - RTMP: Counts RTMP messages (video/audio)
  - WebRTC: Counts RTP packets (video/audio)
  - SRT: Counts MPEG-TS messages (H.264/HEVC/AAC)
2025-11-07 22:32:26 -05:00

3362 lines
117 KiB
C++

//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#include <srs_utest_ai14.hpp>
using namespace std;
#include <srs_app_dash.hpp>
#include <srs_app_dvr.hpp>
#include <srs_app_forward.hpp>
#include <srs_app_hds.hpp>
#include <srs_app_hls.hpp>
#include <srs_app_ng_exec.hpp>
#include <srs_app_rtmp_source.hpp>
#include <srs_app_statistic.hpp>
#include <srs_kernel_error.hpp>
#include <srs_kernel_hourglass.hpp>
#include <srs_kernel_packet.hpp>
#include <srs_kernel_utility.hpp>
#include <srs_protocol_amf0.hpp>
#include <srs_protocol_rtmp_msg_array.hpp>
#include <srs_protocol_utility.hpp>
#include <srs_utest_ai11.hpp>
#include <srs_utest_ai13.hpp>
#include <srs_utest_ai22.hpp>
#include <srs_utest_manual_config.hpp>
#include <srs_utest_manual_coworkers.hpp>
#include <srs_utest_manual_protocol2.hpp>
MockMediaPacketForJitter::MockMediaPacketForJitter(int64_t timestamp, bool is_av)
{
timestamp_ = timestamp;
// Create sample payload
char *payload = new char[128];
memset(payload, 0x00, 128);
SrsMediaPacket::wrap(payload, 128);
if (is_av) {
message_type_ = SrsFrameTypeVideo;
} else {
message_type_ = SrsFrameTypeScript;
}
}
MockMediaPacketForJitter::~MockMediaPacketForJitter()
{
}
MockLiveSourceForQueue::MockLiveSourceForQueue()
{
}
MockLiveSourceForQueue::~MockLiveSourceForQueue()
{
}
void MockLiveSourceForQueue::on_consumer_destroy(SrsLiveConsumer *consumer)
{
// Do nothing in mock
}
srs_error_t MockLiveSourceForQueue::initialize(SrsSharedPtr<SrsLiveSource> wrapper, ISrsRequest *r)
{
// Mock initialize - do nothing and return success
return srs_success;
}
void MockLiveSourceForQueue::update_auth(ISrsRequest *r)
{
// Mock update_auth - do nothing to avoid accessing null req_
}
srs_error_t MockLiveSourceForQueue::consumer_dumps(ISrsLiveConsumer *consumer, bool ds, bool dm, bool dg)
{
// Mock consumer_dumps - just return success without doing anything
return srs_success;
}
MockLiveConsumerForQueue::MockLiveConsumerForQueue(MockLiveSourceForQueue *source)
: SrsLiveConsumer(source)
{
enqueue_count_ = 0;
}
MockLiveConsumerForQueue::~MockLiveConsumerForQueue()
{
}
srs_error_t MockLiveConsumerForQueue::enqueue(SrsMediaPacket *shared_msg, bool atc, SrsRtmpJitterAlgorithm ag)
{
enqueue_count_++;
enqueued_timestamps_.push_back(shared_msg->timestamp_);
return srs_success;
}
MockH264VideoPacket::MockH264VideoPacket(bool is_keyframe)
{
timestamp_ = 0;
message_type_ = SrsFrameTypeVideo;
// Create H.264 video payload
// Format: [frame_type_and_codec_id][avc_packet_type][composition_time]
// For H.264: codec_id = 7 (0x07)
// For keyframe: frame_type = 1 (0x10), for inter frame: frame_type = 2 (0x20)
char *payload = new char[128];
memset(payload, 0x00, 128);
if (is_keyframe) {
payload[0] = 0x17; // keyframe + H.264 (0x10 | 0x07)
} else {
payload[0] = 0x27; // inter frame + H.264 (0x20 | 0x07)
}
payload[1] = 0x01; // AVC NALU (not sequence header)
SrsMediaPacket::wrap(payload, 128);
}
MockH264VideoPacket::~MockH264VideoPacket()
{
}
MockHourGlassForSourceManager::MockHourGlassForSourceManager()
{
tick_event_ = 0;
tick_interval_ = 0;
tick_count_ = 0;
start_count_ = 0;
tick_error_ = srs_success;
start_error_ = srs_success;
}
MockHourGlassForSourceManager::~MockHourGlassForSourceManager()
{
}
srs_error_t MockHourGlassForSourceManager::start()
{
start_count_++;
return srs_error_copy(start_error_);
}
void MockHourGlassForSourceManager::stop()
{
// Do nothing in mock
}
srs_error_t MockHourGlassForSourceManager::tick(srs_utime_t interval)
{
tick_count_++;
tick_interval_ = interval;
return srs_error_copy(tick_error_);
}
srs_error_t MockHourGlassForSourceManager::tick(int event, srs_utime_t interval)
{
tick_count_++;
tick_event_ = event;
tick_interval_ = interval;
return srs_error_copy(tick_error_);
}
void MockHourGlassForSourceManager::untick(int event)
{
// Do nothing in mock
}
MockAppFactoryForSourceManager::MockAppFactoryForSourceManager()
{
create_live_source_count_ = 0;
}
MockAppFactoryForSourceManager::~MockAppFactoryForSourceManager()
{
}
SrsLiveSource *MockAppFactoryForSourceManager::create_live_source()
{
create_live_source_count_++;
return new MockLiveSourceForQueue();
}
MockAudioPacket::MockAudioPacket()
{
timestamp_ = 0;
message_type_ = SrsFrameTypeAudio;
// Create audio payload
char *payload = new char[128];
memset(payload, 0x00, 128);
SrsMediaPacket::wrap(payload, 128);
}
MockAudioPacket::~MockAudioPacket()
{
}
VOID TEST(RtmpJitterTest, CorrectZeroAlgorithmWaitForFirstPacket)
{
srs_error_t err = srs_success;
// Test ZERO algorithm: start at zero, but don't ensure monotonically increasing
// The algorithm "waits" for the first packet to establish the base timestamp
SrsUniquePtr<SrsRtmpJitter> jitter(new SrsRtmpJitter());
// Verify initial state: last_pkt_correct_time_ is -1 (waiting for first packet)
EXPECT_EQ(-1, jitter->get_time());
// Test 1: First packet at timestamp 5000ms
// This is the "wait" - the algorithm waits for the first packet to set the base time
// When last_pkt_correct_time_ == -1, it stores msg->timestamp_ as the base
// Then subtracts it, making the first packet start at 0
SrsUniquePtr<MockMediaPacketForJitter> pkt1(new MockMediaPacketForJitter(5000, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt1.get(), SrsRtmpJitterAlgorithmZERO));
EXPECT_EQ(0, pkt1->timestamp_); // 5000 - 5000 = 0
EXPECT_EQ(5000, jitter->get_time()); // Base time is now set to 5000
// Test 2: Second packet at timestamp 5040ms (40ms after first)
// Now that base time is set (no longer waiting), subtract base time
// Result: 5040 - 5000 = 40ms
SrsUniquePtr<MockMediaPacketForJitter> pkt2(new MockMediaPacketForJitter(5040, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt2.get(), SrsRtmpJitterAlgorithmZERO));
EXPECT_EQ(40, pkt2->timestamp_); // 5040 - 5000 = 40
EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000
// Test 3: Third packet at timestamp 5100ms (60ms after second)
// Continue subtracting the same base time
// Result: 5100 - 5000 = 100ms
SrsUniquePtr<MockMediaPacketForJitter> pkt3(new MockMediaPacketForJitter(5100, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt3.get(), SrsRtmpJitterAlgorithmZERO));
EXPECT_EQ(100, pkt3->timestamp_); // 5100 - 5000 = 100
EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000
// Test 4: Packet with timestamp jump (timestamp 6000ms, 900ms jump)
// ZERO algorithm does NOT correct jitter, just subtracts base time
// Result: 6000 - 5000 = 1000ms (large jump is preserved)
SrsUniquePtr<MockMediaPacketForJitter> pkt4(new MockMediaPacketForJitter(6000, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt4.get(), SrsRtmpJitterAlgorithmZERO));
EXPECT_EQ(1000, pkt4->timestamp_); // 6000 - 5000 = 1000 (jitter preserved)
EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000
// Test 5: Packet with timestamp going backwards (timestamp 5500ms)
// ZERO algorithm does NOT ensure monotonically increasing
// Result: 5500 - 5000 = 500ms (can go backwards relative to previous packet)
SrsUniquePtr<MockMediaPacketForJitter> pkt5(new MockMediaPacketForJitter(5500, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt5.get(), SrsRtmpJitterAlgorithmZERO));
EXPECT_EQ(500, pkt5->timestamp_); // 5500 - 5000 = 500 (backwards allowed)
EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000
// Test 6: Packet with very large timestamp (timestamp 100000ms)
// ZERO algorithm just subtracts base time, no correction
// Result: 100000 - 5000 = 95000ms
SrsUniquePtr<MockMediaPacketForJitter> pkt6(new MockMediaPacketForJitter(100000, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt6.get(), SrsRtmpJitterAlgorithmZERO));
EXPECT_EQ(95000, pkt6->timestamp_); // 100000 - 5000 = 95000
EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000
// Test 7: Edge case - packet with timestamp 0
// Result: 0 - 5000 = -5000 (negative timestamp allowed in ZERO algorithm)
SrsUniquePtr<MockMediaPacketForJitter> pkt7(new MockMediaPacketForJitter(0, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt7.get(), SrsRtmpJitterAlgorithmZERO));
EXPECT_EQ(-5000, pkt7->timestamp_); // 0 - 5000 = -5000 (negative allowed)
EXPECT_EQ(5000, jitter->get_time()); // Base time remains 5000
// Test 8: Verify the "wait" behavior - create new jitter with timestamp 0 as first packet
SrsUniquePtr<SrsRtmpJitter> jitter2(new SrsRtmpJitter());
EXPECT_EQ(-1, jitter2->get_time()); // Initially waiting
SrsUniquePtr<MockMediaPacketForJitter> pkt8(new MockMediaPacketForJitter(0, true));
HELPER_EXPECT_SUCCESS(jitter2->correct(pkt8.get(), SrsRtmpJitterAlgorithmZERO));
EXPECT_EQ(0, pkt8->timestamp_); // 0 - 0 = 0
EXPECT_EQ(0, jitter2->get_time()); // Base time is now 0
// Subsequent packet should be relative to base time 0
SrsUniquePtr<MockMediaPacketForJitter> pkt9(new MockMediaPacketForJitter(1000, true));
HELPER_EXPECT_SUCCESS(jitter2->correct(pkt9.get(), SrsRtmpJitterAlgorithmZERO));
EXPECT_EQ(1000, pkt9->timestamp_); // 1000 - 0 = 1000
EXPECT_EQ(0, jitter2->get_time()); // Base time remains 0
}
VOID TEST(RtmpJitterTest, CorrectTypicalScenario)
{
srs_error_t err = srs_success;
// Test FULL algorithm with typical use scenario
SrsUniquePtr<SrsRtmpJitter> jitter(new SrsRtmpJitter());
// Test 1: First video packet at timestamp 1000
// For first packet: last_pkt_time_ = 0, delta = 1000 - 0 = 1000
// Since delta > 250ms, use default 10ms
// last_pkt_correct_time_ = max(0, -1 + 10) = 9
SrsUniquePtr<MockMediaPacketForJitter> pkt1(new MockMediaPacketForJitter(1000, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt1.get(), SrsRtmpJitterAlgorithmFULL));
EXPECT_EQ(9, pkt1->timestamp_);
// Test 2: Second video packet at timestamp 1040 (40ms delta, normal)
// delta = 1040 - 1000 = 40ms (valid)
// last_pkt_correct_time_ = max(0, 9 + 40) = 49
SrsUniquePtr<MockMediaPacketForJitter> pkt2(new MockMediaPacketForJitter(1040, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt2.get(), SrsRtmpJitterAlgorithmFULL));
EXPECT_EQ(49, pkt2->timestamp_);
// Test 3: Third video packet at timestamp 1080 (40ms delta, normal)
// delta = 1080 - 1040 = 40ms (valid)
// last_pkt_correct_time_ = max(0, 49 + 40) = 89
SrsUniquePtr<MockMediaPacketForJitter> pkt3(new MockMediaPacketForJitter(1080, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt3.get(), SrsRtmpJitterAlgorithmFULL));
EXPECT_EQ(89, pkt3->timestamp_);
// Test 4: Packet with large jitter (timestamp 1500, delta 420ms > 250ms threshold)
// delta = 1500 - 1080 = 420ms (> 250ms threshold)
// Use default 10ms delta
// last_pkt_correct_time_ = max(0, 89 + 10) = 99
SrsUniquePtr<MockMediaPacketForJitter> pkt4(new MockMediaPacketForJitter(1500, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt4.get(), SrsRtmpJitterAlgorithmFULL));
EXPECT_EQ(99, pkt4->timestamp_);
// Test 5: Packet with negative jitter (timestamp 1450, delta -50ms)
// delta = 1450 - 1500 = -50ms (within -250ms to 250ms range, so valid)
// last_pkt_correct_time_ = max(0, 99 + (-50)) = 49
SrsUniquePtr<MockMediaPacketForJitter> pkt5(new MockMediaPacketForJitter(1450, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt5.get(), SrsRtmpJitterAlgorithmFULL));
EXPECT_EQ(49, pkt5->timestamp_);
// Test 6: Metadata packet (non-AV)
// Metadata should always be set to 0, doesn't update last_pkt_time_
SrsUniquePtr<MockMediaPacketForJitter> pkt6(new MockMediaPacketForJitter(2000, false));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt6.get(), SrsRtmpJitterAlgorithmFULL));
EXPECT_EQ(0, pkt6->timestamp_);
// Test 7: Continue with normal packet after metadata
// delta = 1490 - 1450 = 40ms (valid)
// last_pkt_correct_time_ = max(0, 49 + 40) = 89
SrsUniquePtr<MockMediaPacketForJitter> pkt7(new MockMediaPacketForJitter(1490, true));
HELPER_EXPECT_SUCCESS(jitter->correct(pkt7.get(), SrsRtmpJitterAlgorithmFULL));
EXPECT_EQ(89, pkt7->timestamp_);
}
#ifdef SRS_PERF_QUEUE_FAST_VECTOR
VOID TEST(FastVectorTest, TypicalUseScenario)
{
// Create a fast vector instance
SrsUniquePtr<SrsFastVector> vec(new SrsFastVector());
// Test 1: Initial state - should be empty
EXPECT_EQ(0, vec->size());
EXPECT_EQ(0, vec->begin());
EXPECT_EQ(0, vec->end());
// Test 2: Push back some packets
for (int i = 0; i < 5; i++) {
SrsMediaPacket *pkt = new SrsMediaPacket();
pkt->timestamp_ = i * 40;
char *payload = new char[128];
memset(payload, 0x00, 128);
pkt->wrap(payload, 128);
vec->push_back(pkt);
}
// Verify size after push_back
EXPECT_EQ(5, vec->size());
EXPECT_EQ(0, vec->begin());
EXPECT_EQ(5, vec->end());
// Test 3: Access elements using at()
for (int i = 0; i < 5; i++) {
SrsMediaPacket *pkt = vec->at(i);
EXPECT_EQ(i * 40, pkt->timestamp_);
}
// Test 4: Erase some elements (erase first 2 elements)
vec->erase(0, 2);
EXPECT_EQ(3, vec->size());
// Verify remaining elements shifted correctly
EXPECT_EQ(80, vec->at(0)->timestamp_); // Was at index 2
EXPECT_EQ(120, vec->at(1)->timestamp_); // Was at index 3
EXPECT_EQ(160, vec->at(2)->timestamp_); // Was at index 4
// Test 5: Push back more packets to trigger array expansion
for (int i = 0; i < 10; i++) {
SrsMediaPacket *pkt = new SrsMediaPacket();
pkt->timestamp_ = 200 + i * 40;
char *payload = new char[128];
memset(payload, 0x00, 128);
pkt->wrap(payload, 128);
vec->push_back(pkt);
}
// Verify size after expansion
EXPECT_EQ(13, vec->size());
// Test 6: Clear the vector (sets count to 0 but doesn't free packets)
vec->clear();
EXPECT_EQ(0, vec->size());
EXPECT_EQ(0, vec->begin());
EXPECT_EQ(0, vec->end());
}
#endif
VOID TEST(MessageQueueTest, EnqueueTypicalScenario)
{
srs_error_t err = srs_success;
// Create message queue with ignore_shrink=true to avoid log output during test
SrsUniquePtr<SrsMessageQueue> queue(new SrsMessageQueue(true));
// Set queue size to 5 seconds (5000ms)
queue->set_queue_size(5 * SRS_UTIME_SECONDS);
// Test 1: Enqueue first video packet at timestamp 1000ms
// Note: enqueue takes ownership of the packet, so we release it from unique_ptr
MockMediaPacketForJitter *pkt1 = new MockMediaPacketForJitter(1000, true);
bool is_overflow = false;
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt1, &is_overflow));
EXPECT_FALSE(is_overflow);
EXPECT_EQ(1, queue->size());
EXPECT_EQ(0, srsu2msi(queue->duration()));
// Test 2: Enqueue second video packet at timestamp 2000ms (1 second later)
MockMediaPacketForJitter *pkt2 = new MockMediaPacketForJitter(2000, true);
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt2, &is_overflow));
EXPECT_FALSE(is_overflow);
EXPECT_EQ(2, queue->size());
EXPECT_EQ(1000, srsu2msi(queue->duration()));
// Test 3: Enqueue third video packet at timestamp 4000ms (2 seconds later)
MockMediaPacketForJitter *pkt3 = new MockMediaPacketForJitter(4000, true);
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt3, &is_overflow));
EXPECT_FALSE(is_overflow);
EXPECT_EQ(3, queue->size());
EXPECT_EQ(3000, srsu2msi(queue->duration()));
// Test 4: Enqueue fourth video packet at timestamp 7000ms (3 seconds later)
// Total duration is now 6 seconds, which exceeds max_queue_size (5 seconds)
// This should trigger shrink and set is_overflow to true
MockMediaPacketForJitter *pkt4 = new MockMediaPacketForJitter(7000, true);
is_overflow = false;
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt4, &is_overflow));
EXPECT_TRUE(is_overflow);
// After shrink, queue should have fewer messages
EXPECT_LT(queue->size(), 4);
// Test 5: Verify duration is within max_queue_size after shrink
EXPECT_LE(queue->duration(), 5 * SRS_UTIME_SECONDS);
}
VOID TEST(MessageQueueTest, DumpPacketsTypicalScenario)
{
srs_error_t err = srs_success;
// Create a message queue
SrsUniquePtr<SrsMessageQueue> queue(new SrsMessageQueue());
// Test 1: dump_packets with array - typical scenario with 3 packets
if (true) {
// Enqueue 3 video packets
MockMediaPacketForJitter *pkt1 = new MockMediaPacketForJitter(1000, true);
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt1, NULL));
MockMediaPacketForJitter *pkt2 = new MockMediaPacketForJitter(2000, true);
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt2, NULL));
MockMediaPacketForJitter *pkt3 = new MockMediaPacketForJitter(3000, true);
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt3, NULL));
EXPECT_EQ(3, queue->size());
// Dump packets to array
const int max_count = 10;
SrsMediaPacket *pmsgs[max_count];
int count = 0;
HELPER_EXPECT_SUCCESS(queue->dump_packets(max_count, pmsgs, count));
// Verify all 3 packets were dumped
EXPECT_EQ(3, count);
EXPECT_EQ(1000, pmsgs[0]->timestamp_);
EXPECT_EQ(2000, pmsgs[1]->timestamp_);
EXPECT_EQ(3000, pmsgs[2]->timestamp_);
// Verify queue is now empty after dump
EXPECT_EQ(0, queue->size());
// Free the dumped packets
for (int i = 0; i < count; i++) {
srs_freep(pmsgs[i]);
}
}
// Test 2: dump_packets with consumer - typical scenario with 2 packets
if (true) {
// Enqueue 2 audio packets
MockMediaPacketForJitter *pkt1 = new MockMediaPacketForJitter(4000, true);
pkt1->message_type_ = SrsFrameTypeAudio;
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt1, NULL));
MockMediaPacketForJitter *pkt2 = new MockMediaPacketForJitter(5000, true);
pkt2->message_type_ = SrsFrameTypeAudio;
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt2, NULL));
EXPECT_EQ(2, queue->size());
// Create mock source and consumer
SrsUniquePtr<MockLiveSourceForQueue> source(new MockLiveSourceForQueue());
SrsUniquePtr<MockLiveConsumerForQueue> consumer(new MockLiveConsumerForQueue(source.get()));
// Dump packets to consumer
HELPER_EXPECT_SUCCESS(queue->dump_packets(consumer.get(), true, SrsRtmpJitterAlgorithmFULL));
// Verify consumer received all packets
EXPECT_EQ(2, consumer->enqueue_count_);
EXPECT_EQ(4000, consumer->enqueued_timestamps_[0]);
EXPECT_EQ(5000, consumer->enqueued_timestamps_[1]);
// Note: Queue still contains packets after dump_packets(consumer) call
// because this method doesn't clear the queue
EXPECT_EQ(2, queue->size());
}
}
VOID TEST(MessageQueueTest, DumpPacketsPartialErase)
{
srs_error_t err = srs_success;
// Create a message queue
SrsUniquePtr<SrsMessageQueue> queue(new SrsMessageQueue());
// Enqueue 10 video packets with timestamps 1000, 2000, ..., 10000
// This creates a scenario where we have MORE messages than we will dump
for (int i = 1; i <= 10; i++) {
MockMediaPacketForJitter *pkt = new MockMediaPacketForJitter(i * 1000, true);
HELPER_EXPECT_SUCCESS(queue->enqueue(pkt, NULL));
}
// Verify all 10 packets are in the queue
EXPECT_EQ(10, queue->size());
// Dump only 3 packets (max_count=3), which is LESS than the total number of messages (10)
// This will trigger the else branch: msgs_.erase(msgs_.begin(), msgs_.begin() + count)
// because count (3) < nb_msgs (10)
const int max_count = 3;
SrsMediaPacket *pmsgs[max_count];
int count = 0;
HELPER_EXPECT_SUCCESS(queue->dump_packets(max_count, pmsgs, count));
// Verify exactly 3 packets were dumped (the first 3)
EXPECT_EQ(3, count);
EXPECT_EQ(1000, pmsgs[0]->timestamp_);
EXPECT_EQ(2000, pmsgs[1]->timestamp_);
EXPECT_EQ(3000, pmsgs[2]->timestamp_);
// Verify 7 packets remain in the queue (10 - 3 = 7)
// This confirms that msgs_.erase() correctly removed only the first 3 elements
EXPECT_EQ(7, queue->size());
// Free the dumped packets
for (int i = 0; i < count; i++) {
srs_freep(pmsgs[i]);
}
// Dump the remaining packets to verify they are the correct ones (4000-10000)
const int max_count2 = 10;
SrsMediaPacket *pmsgs2[max_count2];
int count2 = 0;
HELPER_EXPECT_SUCCESS(queue->dump_packets(max_count2, pmsgs2, count2));
// Verify the remaining 7 packets have the correct timestamps
EXPECT_EQ(7, count2);
EXPECT_EQ(4000, pmsgs2[0]->timestamp_);
EXPECT_EQ(5000, pmsgs2[1]->timestamp_);
EXPECT_EQ(6000, pmsgs2[2]->timestamp_);
EXPECT_EQ(7000, pmsgs2[3]->timestamp_);
EXPECT_EQ(8000, pmsgs2[4]->timestamp_);
EXPECT_EQ(9000, pmsgs2[5]->timestamp_);
EXPECT_EQ(10000, pmsgs2[6]->timestamp_);
// Verify queue is now empty
EXPECT_EQ(0, queue->size());
// Free the remaining packets
for (int i = 0; i < count2; i++) {
srs_freep(pmsgs2[i]);
}
}
VOID TEST(MessageQueueTest, ShrinkAndClear)
{
srs_error_t err;
// Create message queue
SrsUniquePtr<SrsMessageQueue> queue(new SrsMessageQueue(true));
// Set queue size to trigger shrink
queue->set_queue_size(10 * SRS_UTIME_SECONDS);
// Create video sequence header packet (0x17 = keyframe + AVC, 0x00 = sequence header)
SrsMediaPacket *video_sh = new SrsMediaPacket();
char *video_sh_data = new char[10];
video_sh_data[0] = 0x17; // keyframe + AVC
video_sh_data[1] = 0x00; // sequence header
for (int i = 2; i < 10; i++) {
video_sh_data[i] = 0x00;
}
video_sh->wrap(video_sh_data, 10);
video_sh->timestamp_ = 1000;
video_sh->message_type_ = SrsFrameTypeVideo;
HELPER_EXPECT_SUCCESS(queue->enqueue(video_sh, NULL));
// Create audio sequence header packet (0xa0 = AAC, 0x00 = sequence header)
SrsMediaPacket *audio_sh = new SrsMediaPacket();
char *audio_sh_data = new char[10];
audio_sh_data[0] = 0xa0; // AAC
audio_sh_data[1] = 0x00; // sequence header
for (int i = 2; i < 10; i++) {
audio_sh_data[i] = 0x00;
}
audio_sh->wrap(audio_sh_data, 10);
audio_sh->timestamp_ = 1000;
audio_sh->message_type_ = SrsFrameTypeAudio;
HELPER_EXPECT_SUCCESS(queue->enqueue(audio_sh, NULL));
// Create regular video packet (0x27 = inter frame + AVC, 0x01 = NALU)
SrsMediaPacket *video_pkt = new SrsMediaPacket();
char *video_pkt_data = new char[10];
video_pkt_data[0] = 0x27; // inter frame + AVC
video_pkt_data[1] = 0x01; // NALU
for (int i = 2; i < 10; i++) {
video_pkt_data[i] = 0x01;
}
video_pkt->wrap(video_pkt_data, 10);
video_pkt->timestamp_ = 2000;
video_pkt->message_type_ = SrsFrameTypeVideo;
HELPER_EXPECT_SUCCESS(queue->enqueue(video_pkt, NULL));
// Create regular audio packet (0xa0 = AAC, 0x01 = raw data)
SrsMediaPacket *audio_pkt = new SrsMediaPacket();
char *audio_pkt_data = new char[10];
audio_pkt_data[0] = 0xa0; // AAC
audio_pkt_data[1] = 0x01; // raw data
for (int i = 2; i < 10; i++) {
audio_pkt_data[i] = 0x01;
}
audio_pkt->wrap(audio_pkt_data, 10);
audio_pkt->timestamp_ = 2000;
audio_pkt->message_type_ = SrsFrameTypeAudio;
HELPER_EXPECT_SUCCESS(queue->enqueue(audio_pkt, NULL));
// Verify queue has 4 messages
EXPECT_EQ(4, queue->size());
// Test shrink() - should keep only sequence headers and update their timestamps
queue->shrink();
// After shrink, only sequence headers should remain
EXPECT_EQ(2, queue->size());
// Test clear() - should remove all messages
queue->clear();
// After clear, queue should be empty
EXPECT_EQ(0, queue->size());
}
VOID TEST(SrsLiveConsumerTest, TypicalUsage)
{
srs_error_t err;
// Create mock source
SrsUniquePtr<MockLiveSourceForQueue> mock_source(new MockLiveSourceForQueue());
// Create consumer with mock source - tests constructor
SrsUniquePtr<SrsLiveConsumer> consumer(new SrsLiveConsumer(mock_source.get()));
// Test set_queue_size - typical queue size is 10 seconds
srs_utime_t queue_size = 10 * SRS_UTIME_SECONDS;
consumer->set_queue_size(queue_size);
// Test update_source_id - should set internal flag
consumer->update_source_id();
// Test get_time - returns jitter's last_pkt_correct_time_ which is -1 initially
EXPECT_EQ(-1, consumer->get_time());
// Enqueue a test packet to update jitter time
SrsUniquePtr<MockMediaPacketForJitter> pkt(new MockMediaPacketForJitter(1000, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt.get(), false, SrsRtmpJitterAlgorithmFULL));
// After enqueuing with FULL jitter algorithm, get_time returns corrected time
// The jitter algorithm detects large delta (1000 - 0) and uses DEFAULT_FRAME_TIME_MS (10ms)
// So last_pkt_correct_time_ = max(0, -1 + 10) = 9
EXPECT_EQ(9, consumer->get_time());
// Destructor will be called automatically and should invoke on_consumer_destroy
}
VOID TEST(SrsLiveConsumerEnqueueTest, TypicalScenario)
{
srs_error_t err;
// Create mock source
SrsUniquePtr<MockLiveSourceForQueue> mock_source(new MockLiveSourceForQueue());
// Create consumer with mock source
SrsUniquePtr<SrsLiveConsumer> consumer(new SrsLiveConsumer(mock_source.get()));
// Set typical queue size (10 seconds)
consumer->set_queue_size(10 * SRS_UTIME_SECONDS);
// Test typical scenario: enqueue video packet with jitter correction (atc=false)
// Create a video packet at timestamp 1000ms
SrsUniquePtr<MockMediaPacketForJitter> pkt1(new MockMediaPacketForJitter(1000, true));
// Enqueue with jitter correction enabled (atc=false, FULL algorithm)
// This tests the main flow:
// 1. Copy the message (shared_msg->copy())
// 2. Apply jitter correction (!atc, so jitter_->correct() is called)
// 3. Enqueue to queue (queue_->enqueue())
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1.get(), false, SrsRtmpJitterAlgorithmFULL));
// Verify jitter correction was applied - first packet gets corrected to 9ms
// (large delta detected, uses DEFAULT_FRAME_TIME_MS=10ms, result: max(0, -1+10)=9)
EXPECT_EQ(9, consumer->get_time());
// Enqueue second video packet at timestamp 1040ms (40ms delta, normal)
SrsUniquePtr<MockMediaPacketForJitter> pkt2(new MockMediaPacketForJitter(1040, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt2.get(), false, SrsRtmpJitterAlgorithmFULL));
// Verify jitter correction: 9 + 40 = 49ms
EXPECT_EQ(49, consumer->get_time());
// Test ATC mode: enqueue without jitter correction (atc=true)
// Create packet at timestamp 2000ms
SrsUniquePtr<MockMediaPacketForJitter> pkt3(new MockMediaPacketForJitter(2000, true));
// Enqueue with ATC enabled (atc=true) - skips jitter correction
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt3.get(), true, SrsRtmpJitterAlgorithmFULL));
// Verify jitter time unchanged (still 49ms) because ATC skips jitter correction
EXPECT_EQ(49, consumer->get_time());
}
VOID TEST(SrsLiveConsumerDumpPacketsTest, TypicalScenario)
{
srs_error_t err;
// Create mock source with source IDs
SrsUniquePtr<MockLiveSourceForQueue> mock_source(new MockLiveSourceForQueue());
// Create consumer with mock source
SrsUniquePtr<SrsLiveConsumer> consumer(new SrsLiveConsumer(mock_source.get()));
// Set typical queue size (10 seconds)
consumer->set_queue_size(10 * SRS_UTIME_SECONDS);
// Enqueue 3 video packets to the consumer's internal queue
SrsUniquePtr<MockMediaPacketForJitter> pkt1(new MockMediaPacketForJitter(1000, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1.get(), false, SrsRtmpJitterAlgorithmFULL));
SrsUniquePtr<MockMediaPacketForJitter> pkt2(new MockMediaPacketForJitter(1040, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt2.get(), false, SrsRtmpJitterAlgorithmFULL));
SrsUniquePtr<MockMediaPacketForJitter> pkt3(new MockMediaPacketForJitter(1080, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt3.get(), false, SrsRtmpJitterAlgorithmFULL));
// Create SrsMessageArray to receive dumped packets
const int max_msgs = 10;
SrsUniquePtr<SrsMessageArray> msgs(new SrsMessageArray(max_msgs));
// Test typical scenario: dump packets from consumer
int count = 0;
HELPER_EXPECT_SUCCESS(consumer->dump_packets(msgs.get(), count));
// Verify all 3 packets were dumped
EXPECT_EQ(3, count);
EXPECT_EQ(9, msgs->msgs_[0]->timestamp_); // First packet corrected to 9ms
EXPECT_EQ(49, msgs->msgs_[1]->timestamp_); // Second packet corrected to 49ms
EXPECT_EQ(89, msgs->msgs_[2]->timestamp_); // Third packet corrected to 89ms
// Free the dumped packets
msgs->free(count);
}
#ifdef SRS_PERF_QUEUE_COND_WAIT
VOID TEST(SrsLiveConsumerWaitTest, TypicalScenario)
{
srs_error_t err;
// Create mock source
SrsUniquePtr<MockLiveSourceForQueue> mock_source(new MockLiveSourceForQueue());
// Create consumer with mock source
SrsUniquePtr<SrsLiveConsumer> consumer(new SrsLiveConsumer(mock_source.get()));
// Set typical queue size (10 seconds)
consumer->set_queue_size(10 * SRS_UTIME_SECONDS);
// Enqueue 3 video packets to the consumer's internal queue
// Each packet is 40ms apart
SrsUniquePtr<MockMediaPacketForJitter> pkt1(new MockMediaPacketForJitter(1000, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1.get(), false, SrsRtmpJitterAlgorithmFULL));
SrsUniquePtr<MockMediaPacketForJitter> pkt2(new MockMediaPacketForJitter(1040, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt2.get(), false, SrsRtmpJitterAlgorithmFULL));
SrsUniquePtr<MockMediaPacketForJitter> pkt3(new MockMediaPacketForJitter(1080, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt3.get(), false, SrsRtmpJitterAlgorithmFULL));
// Test typical scenario: wait returns immediately when queue has enough messages and duration
// Queue has 3 messages with duration ~80ms (89ms - 9ms)
// Request wait for 1 message and 50ms duration
// Since queue has 3 > 1 messages and duration 80ms > 50ms, wait should return immediately
consumer->wait(1, 50 * SRS_UTIME_MILLISECONDS);
// Verify consumer is still functional after wait
// Create SrsMessageArray to receive dumped packets
const int max_msgs = 10;
SrsUniquePtr<SrsMessageArray> msgs(new SrsMessageArray(max_msgs));
// Dump packets to verify queue is intact
int count = 0;
HELPER_EXPECT_SUCCESS(consumer->dump_packets(msgs.get(), count));
// Verify all 3 packets are still in queue
EXPECT_EQ(3, count);
// Free the dumped packets
msgs->free(count);
}
#endif
VOID TEST(LiveConsumerTest, OnPlayClientPauseTypicalScenario)
{
srs_error_t err = srs_success;
// Create mock live source
SrsUniquePtr<MockLiveSourceForQueue> source(new MockLiveSourceForQueue());
// Create live consumer
SrsUniquePtr<SrsLiveConsumer> consumer(new SrsLiveConsumer(source.get()));
// Test typical scenario: pause the consumer
HELPER_EXPECT_SUCCESS(consumer->on_play_client_pause(true));
// Test typical scenario: unpause the consumer
HELPER_EXPECT_SUCCESS(consumer->on_play_client_pause(false));
}
#ifdef SRS_PERF_QUEUE_COND_WAIT
VOID TEST(LiveConsumerTest, WakeupTypicalScenario)
{
// Create mock live source
SrsUniquePtr<MockLiveSourceForQueue> source(new MockLiveSourceForQueue());
// Create live consumer
SrsUniquePtr<SrsLiveConsumer> consumer(new SrsLiveConsumer(source.get()));
// Simulate typical scenario: consumer is waiting
consumer->mw_waiting_ = true;
// Call wakeup to signal the waiting consumer
consumer->wakeup();
// Verify that mw_waiting_ is set to false after wakeup
EXPECT_FALSE(consumer->mw_waiting_);
}
VOID TEST(LiveConsumerTest, EnqueueWaitingSignalConditions)
{
srs_error_t err;
// Test Case 1: ATC mode with negative duration (timestamp overflow case)
// This simulates when sequence header timestamp is bigger than A/V packet timestamp
// See https://github.com/ossrs/srs/pull/749
{
// Create fresh mock live source and consumer for this test case
SrsUniquePtr<MockLiveSourceForQueue> source(new MockLiveSourceForQueue());
SrsUniquePtr<SrsLiveConsumer> consumer(new SrsLiveConsumer(source.get()));
// Set consumer to waiting state with specific requirements
consumer->mw_waiting_ = true;
consumer->mw_min_msgs_ = 5;
consumer->mw_duration_ = 1000 * SRS_UTIME_MILLISECONDS; // 1000ms
// First enqueue an A/V packet with large timestamp (e.g., 5000ms)
// This sets av_start_time_ = 5000ms
SrsUniquePtr<MockMediaPacketForJitter> first_packet(new MockMediaPacketForJitter(5000, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(first_packet.get(), false, SrsRtmpJitterAlgorithmOFF));
// Now create an A/V packet with smaller timestamp (e.g., 1000ms)
// This sets av_end_time_ = 1000ms
// This creates negative duration: av_end_time(1000) - av_start_time(5000) = -4000ms
SrsUniquePtr<MockMediaPacketForJitter> second_packet(new MockMediaPacketForJitter(1000, true));
// Enqueue with ATC mode enabled
// This should trigger the signal because duration < 0 in ATC mode
HELPER_EXPECT_SUCCESS(consumer->enqueue(second_packet.get(), true, SrsRtmpJitterAlgorithmOFF));
// Verify that mw_waiting_ is set to false after signal
EXPECT_FALSE(consumer->mw_waiting_);
}
// Test Case 2: Normal mode with enough messages and duration
// This tests the typical scenario where consumer waits for enough data
{
// Create fresh mock live source and consumer for this test case
SrsUniquePtr<MockLiveSourceForQueue> source(new MockLiveSourceForQueue());
SrsUniquePtr<SrsLiveConsumer> consumer(new SrsLiveConsumer(source.get()));
// Set consumer to waiting state with specific requirements
consumer->mw_waiting_ = true;
consumer->mw_min_msgs_ = 2; // Wait for more than 2 messages
consumer->mw_duration_ = 100 * SRS_UTIME_MILLISECONDS; // Wait for 100ms duration
// Enqueue first packet at timestamp 10ms (not 0, as 0 is ignored by queue)
SrsUniquePtr<MockMediaPacketForJitter> packet1(new MockMediaPacketForJitter(10, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(packet1.get(), false, SrsRtmpJitterAlgorithmOFF));
// After first packet, should still be waiting (only 1 message, need > 2)
EXPECT_TRUE(consumer->mw_waiting_);
// Enqueue second packet at timestamp 60ms
SrsUniquePtr<MockMediaPacketForJitter> packet2(new MockMediaPacketForJitter(60, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(packet2.get(), false, SrsRtmpJitterAlgorithmOFF));
// After second packet, should still be waiting (only 2 messages, need > 2)
EXPECT_TRUE(consumer->mw_waiting_);
// Enqueue third packet at timestamp 120ms
// Now we have 3 messages (> mw_min_msgs_=2) and duration 110ms (120-10) (> mw_duration_=100ms)
SrsUniquePtr<MockMediaPacketForJitter> packet3(new MockMediaPacketForJitter(120, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(packet3.get(), false, SrsRtmpJitterAlgorithmOFF));
// Verify that mw_waiting_ is set to false after signal
// This should trigger: match_min_msgs && duration > mw_duration_
EXPECT_FALSE(consumer->mw_waiting_);
}
// Test Case 3: Verify no signal when conditions are not met
{
// Create fresh mock live source and consumer for this test case
SrsUniquePtr<MockLiveSourceForQueue> source(new MockLiveSourceForQueue());
SrsUniquePtr<SrsLiveConsumer> consumer(new SrsLiveConsumer(source.get()));
// Set consumer to waiting state with specific requirements
consumer->mw_waiting_ = true;
consumer->mw_min_msgs_ = 10; // Need more than 10 messages
consumer->mw_duration_ = 1000 * SRS_UTIME_MILLISECONDS; // Need 1000ms duration
// Enqueue a few packets (not enough to trigger signal)
SrsUniquePtr<MockMediaPacketForJitter> packet4(new MockMediaPacketForJitter(200, true));
HELPER_EXPECT_SUCCESS(consumer->enqueue(packet4.get(), false, SrsRtmpJitterAlgorithmOFF));
// Should still be waiting (not enough messages)
EXPECT_TRUE(consumer->mw_waiting_);
}
}
#endif
VOID TEST(GopCacheTest, TypicalUseScenario)
{
// Create a gop cache instance
SrsUniquePtr<SrsGopCache> gop_cache(new SrsGopCache());
// Test 1: Verify gop cache is enabled by default
EXPECT_TRUE(gop_cache->enabled());
// Test 2: Set gop cache max frames
gop_cache->set_gop_cache_max_frames(2500);
// Test 3: Disable gop cache using set(false)
gop_cache->set(false);
EXPECT_FALSE(gop_cache->enabled());
// Test 4: Re-enable gop cache using set(true)
gop_cache->set(true);
EXPECT_TRUE(gop_cache->enabled());
// Test 5: Call dispose to cleanup
gop_cache->dispose();
EXPECT_TRUE(gop_cache->enabled()); // dispose() doesn't change enabled state
}
VOID TEST(GopCacheTest, CacheTypicalScenario)
{
srs_error_t err;
// Create a gop cache instance
SrsUniquePtr<SrsGopCache> gop_cache(new SrsGopCache());
// Verify gop cache is enabled by default
EXPECT_TRUE(gop_cache->enabled());
// Test typical scenario: Cache a keyframe (should clear previous cache and start new GOP)
SrsUniquePtr<MockH264VideoPacket> keyframe1(new MockH264VideoPacket(true));
HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe1.get()));
EXPECT_FALSE(gop_cache->empty());
// Cache some inter frames
SrsUniquePtr<MockH264VideoPacket> interframe1(new MockH264VideoPacket(false));
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get()));
SrsUniquePtr<MockH264VideoPacket> interframe2(new MockH264VideoPacket(false));
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get()));
// Cache some audio packets
SrsUniquePtr<MockAudioPacket> audio1(new MockAudioPacket());
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio1.get()));
SrsUniquePtr<MockAudioPacket> audio2(new MockAudioPacket());
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio2.get()));
// Verify cache is not empty
EXPECT_FALSE(gop_cache->empty());
// Cache another keyframe (should clear cache and start new GOP)
SrsUniquePtr<MockH264VideoPacket> keyframe2(new MockH264VideoPacket(true));
HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe2.get()));
EXPECT_FALSE(gop_cache->empty());
// Cache more frames in the new GOP
SrsUniquePtr<MockH264VideoPacket> interframe3(new MockH264VideoPacket(false));
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe3.get()));
SrsUniquePtr<MockAudioPacket> audio3(new MockAudioPacket());
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio3.get()));
// Verify cache is still not empty
EXPECT_FALSE(gop_cache->empty());
}
VOID TEST(AppRtmpSourceTest, GopCacheClear)
{
srs_error_t err;
// Create gop cache and enable it
SrsUniquePtr<SrsGopCache> gop_cache(new SrsGopCache());
gop_cache->set(true);
// Cache a keyframe to start GOP
SrsUniquePtr<MockH264VideoPacket> keyframe(new MockH264VideoPacket(true));
HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe.get()));
// Cache some inter frames
SrsUniquePtr<MockH264VideoPacket> interframe1(new MockH264VideoPacket(false));
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get()));
SrsUniquePtr<MockH264VideoPacket> interframe2(new MockH264VideoPacket(false));
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get()));
// Cache some audio packets
SrsUniquePtr<MockAudioPacket> audio1(new MockAudioPacket());
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio1.get()));
SrsUniquePtr<MockAudioPacket> audio2(new MockAudioPacket());
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio2.get()));
// Verify cache is not empty before clear
EXPECT_FALSE(gop_cache->empty());
// Clear the cache
gop_cache->clear();
// Verify cache is empty after clear
EXPECT_TRUE(gop_cache->empty());
// Verify it's pure audio after clear (no video cached)
EXPECT_TRUE(gop_cache->pure_audio());
}
VOID TEST(AppRtmpSourceTest, GopCacheDumpTypicalScenario)
{
srs_error_t err;
// Create gop cache and enable it
SrsUniquePtr<SrsGopCache> gop_cache(new SrsGopCache());
gop_cache->set(true);
// Cache a keyframe to start GOP
SrsUniquePtr<MockH264VideoPacket> keyframe(new MockH264VideoPacket(true));
keyframe->timestamp_ = 1000;
HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe.get()));
// Cache some inter frames
SrsUniquePtr<MockH264VideoPacket> interframe1(new MockH264VideoPacket(false));
interframe1->timestamp_ = 1040;
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get()));
SrsUniquePtr<MockH264VideoPacket> interframe2(new MockH264VideoPacket(false));
interframe2->timestamp_ = 1080;
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get()));
// Cache some audio packets
SrsUniquePtr<MockAudioPacket> audio1(new MockAudioPacket());
audio1->timestamp_ = 1020;
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio1.get()));
SrsUniquePtr<MockAudioPacket> audio2(new MockAudioPacket());
audio2->timestamp_ = 1060;
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio2.get()));
// Verify cache is not empty
EXPECT_FALSE(gop_cache->empty());
// Create mock source and consumer
SrsUniquePtr<MockLiveSourceForQueue> source(new MockLiveSourceForQueue());
SrsUniquePtr<MockLiveConsumerForQueue> consumer(new MockLiveConsumerForQueue(source.get()));
// Dump cached GOP to consumer
HELPER_EXPECT_SUCCESS(gop_cache->dump(consumer.get(), true, SrsRtmpJitterAlgorithmFULL));
// Verify consumer received all 5 packets (1 keyframe + 2 inter frames + 2 audio)
EXPECT_EQ(5, consumer->enqueue_count_);
EXPECT_EQ(1000, consumer->enqueued_timestamps_[0]);
EXPECT_EQ(1040, consumer->enqueued_timestamps_[1]);
EXPECT_EQ(1080, consumer->enqueued_timestamps_[2]);
EXPECT_EQ(1020, consumer->enqueued_timestamps_[3]);
EXPECT_EQ(1060, consumer->enqueued_timestamps_[4]);
// Verify cache still contains packets after dump (dump doesn't clear the cache)
EXPECT_FALSE(gop_cache->empty());
}
VOID TEST(AppRtmpSourceTest, GopCacheMaxFramesLimit)
{
srs_error_t err;
// Create gop cache and enable it
SrsUniquePtr<SrsGopCache> gop_cache(new SrsGopCache());
gop_cache->set(true);
// Set max frames limit to 5
gop_cache->set_gop_cache_max_frames(5);
// Cache a keyframe to start GOP
SrsUniquePtr<MockH264VideoPacket> keyframe(new MockH264VideoPacket(true));
HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe.get()));
EXPECT_FALSE(gop_cache->empty());
// Cache 4 more packets (total 5 packets, at the limit)
SrsUniquePtr<MockH264VideoPacket> interframe1(new MockH264VideoPacket(false));
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get()));
SrsUniquePtr<MockH264VideoPacket> interframe2(new MockH264VideoPacket(false));
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get()));
SrsUniquePtr<MockAudioPacket> audio1(new MockAudioPacket());
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio1.get()));
SrsUniquePtr<MockAudioPacket> audio2(new MockAudioPacket());
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio2.get()));
// Cache is at limit (5 packets), should not be empty
EXPECT_FALSE(gop_cache->empty());
// Cache one more packet (6th packet) - should trigger clear due to exceeding max frames
// The packet is added first, then the entire cache is cleared
SrsUniquePtr<MockH264VideoPacket> interframe3(new MockH264VideoPacket(false));
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe3.get()));
// After exceeding max frames, cache is completely cleared (empty)
EXPECT_TRUE(gop_cache->empty());
// Verify it's pure audio after clear (cached_video_count_ reset to 0)
EXPECT_TRUE(gop_cache->pure_audio());
}
VOID TEST(AppMixQueueTest, PopMixedAudioVideo)
{
SrsMixQueue *mix_queue = new SrsMixQueue();
// Test mixed audio/video scenario - should pop when we have 1 video and 1 audio
// Create and push video packet
SrsMediaPacket *video1 = new SrsMediaPacket();
video1->timestamp_ = 100;
video1->message_type_ = SrsFrameTypeVideo;
char *vpayload = new char[10];
memset(vpayload, 0, 10);
video1->wrap(vpayload, 10);
// Verify packet is correctly configured
EXPECT_TRUE(video1->is_video());
EXPECT_FALSE(video1->is_audio());
mix_queue->push(video1);
// Create and push audio packet
SrsMediaPacket *audio1 = new SrsMediaPacket();
audio1->timestamp_ = 110;
audio1->message_type_ = SrsFrameTypeAudio;
char *apayload = new char[10];
memset(apayload, 0, 10);
audio1->wrap(apayload, 10);
// Verify packet is correctly configured
EXPECT_FALSE(audio1->is_video());
EXPECT_TRUE(audio1->is_audio());
mix_queue->push(audio1);
// Should be able to pop now (1 video + 1 audio)
// The first packet (by timestamp) should be returned
SrsMediaPacket *msg = mix_queue->pop();
ASSERT_TRUE(msg != NULL);
EXPECT_EQ(100, msg->timestamp_);
EXPECT_TRUE(msg->is_video());
srs_freep(msg);
// After popping one video, we have 0 videos and 1 audio
// This doesn't meet any pop condition, so should return NULL
msg = mix_queue->pop();
EXPECT_TRUE(msg == NULL);
srs_freep(mix_queue);
}
VOID TEST(LiveSourceOnAudioImpTest, ReduceSequenceHeaderAndConsumerEnqueue)
{
srs_error_t err = srs_success;
// This test covers the on_audio_imp code path including:
// 1. Reduce sequence header logic (drop_for_reduce)
// 2. Hub on_audio consumption (NULL in this test)
// 3. RTMP bridge on_frame consumption (NULL in this test)
// 4. Consumer enqueue (using mock consumers to track calls)
// Create mock config with reduce_sequence_header enabled
// NOTE: Config must outlive the source because destructor calls config_->unsubscribe(this)
MockSrsConfig *mock_config = new MockSrsConfig();
HELPER_EXPECT_SUCCESS(mock_config->mock_parse(_MIN_OK_CONF "vhost test.vhost { play { reduce_sequence_header on; } }"));
// Create mock request
MockSrsRequest *mock_req = new MockSrsRequest("test.vhost", "live", "stream1");
// Create mock live source and consumers
MockLiveSourceForQueue *mock_source = new MockLiveSourceForQueue();
MockLiveConsumerForQueue *consumer1 = new MockLiveConsumerForQueue(mock_source);
MockLiveConsumerForQueue *consumer2 = new MockLiveConsumerForQueue(mock_source);
// Setup mock source with necessary components
mock_source->config_ = mock_config;
mock_source->req_ = mock_req;
mock_source->format_ = new SrsRtmpFormat();
mock_source->meta_ = new SrsMetaCache();
mock_source->jitter_algorithm_ = SrsRtmpJitterAlgorithmOFF;
mock_source->atc_ = false;
mock_source->hub_ = NULL; // No hub for this test
mock_source->rtmp_bridge_ = NULL; // No bridge for this test
// Add consumers to source
mock_source->consumers_.push_back(consumer1);
mock_source->consumers_.push_back(consumer2);
// Create first audio sequence header (AAC sequence header)
// Use valid AAC sequence header format
SrsUniquePtr<SrsMediaPacket> audio_sh1(new SrsMediaPacket());
char *ash1_payload = new char[4];
ash1_payload[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo
ash1_payload[1] = 0x00; // AAC sequence header
ash1_payload[2] = 0x12; // AAC object type = 2 (AAC-LC), sample rate index = 4 (44.1kHz)
ash1_payload[3] = 0x10; // Channel config = 2 (stereo)
audio_sh1->wrap(ash1_payload, 4);
audio_sh1->timestamp_ = 1000;
audio_sh1->message_type_ = SrsFrameTypeAudio;
// Process first audio sequence header - should be cached in meta
// NOTE: First sequence header is ALWAYS enqueued to consumers (previous_ash is NULL)
HELPER_EXPECT_SUCCESS(mock_source->on_audio_imp(audio_sh1.get()));
// Verify meta has cached the audio sequence header
ASSERT_TRUE(mock_source->meta_->ash() != NULL);
ASSERT_TRUE(mock_source->meta_->previous_ash() != NULL);
// Verify consumers received the first sequence header (drop_for_reduce = false because previous_ash was NULL)
EXPECT_EQ(1, consumer1->enqueue_count_);
EXPECT_EQ(1, consumer2->enqueue_count_);
EXPECT_EQ(1000, consumer1->enqueued_timestamps_[0]);
EXPECT_EQ(1000, consumer2->enqueued_timestamps_[0]);
// Create second audio sequence header with SAME content (should be dropped for reduce)
SrsUniquePtr<SrsMediaPacket> audio_sh2(new SrsMediaPacket());
char *ash2_payload = new char[4];
ash2_payload[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo
ash2_payload[1] = 0x00; // AAC sequence header
ash2_payload[2] = 0x12; // Same AAC object type and sample rate
ash2_payload[3] = 0x10; // Same channel config
audio_sh2->wrap(ash2_payload, 4);
audio_sh2->timestamp_ = 2000;
audio_sh2->message_type_ = SrsFrameTypeAudio;
// Process second audio sequence header - should be dropped (not enqueued to consumers)
// This tests: if (is_sequence_header && meta_->previous_ash() && config_->get_reduce_sequence_header(req_->vhost_))
// Now previous_ash() is not NULL, so drop_for_reduce will be true if content is identical
HELPER_EXPECT_SUCCESS(mock_source->on_audio_imp(audio_sh2.get()));
// Verify consumers did NOT receive the duplicate sequence header (drop_for_reduce = true)
EXPECT_EQ(1, consumer1->enqueue_count_); // Still 1 (no new packet)
EXPECT_EQ(1, consumer2->enqueue_count_); // Still 1 (no new packet)
// Create regular audio packet (not sequence header)
SrsUniquePtr<SrsMediaPacket> audio_pkt(new SrsMediaPacket());
char *audio_payload = new char[10];
audio_payload[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo
audio_payload[1] = 0x01; // AAC raw data (not sequence header)
for (int i = 2; i < 10; i++) {
audio_payload[i] = 0x02; // Audio data
}
audio_pkt->wrap(audio_payload, 10);
audio_pkt->timestamp_ = 3000;
audio_pkt->message_type_ = SrsFrameTypeAudio;
// Process regular audio packet - should be enqueued to all consumers
// This tests: if (!drop_for_reduce) { for (int i = 0; i < (int)consumers_.size(); i++) { consumer->enqueue(...) } }
HELPER_EXPECT_SUCCESS(mock_source->on_audio_imp(audio_pkt.get()));
// Verify all consumers received the audio packet (drop_for_reduce = false)
// Now count should be 2 (first sequence header + this audio packet)
EXPECT_EQ(2, consumer1->enqueue_count_);
EXPECT_EQ(2, consumer2->enqueue_count_);
EXPECT_EQ(1000, consumer1->enqueued_timestamps_[0]); // First sequence header
EXPECT_EQ(3000, consumer1->enqueued_timestamps_[1]); // Audio packet
EXPECT_EQ(1000, consumer2->enqueued_timestamps_[0]); // First sequence header
EXPECT_EQ(3000, consumer2->enqueued_timestamps_[1]); // Audio packet
// Cleanup: remove consumers from source before they are destroyed
mock_source->consumers_.clear();
// Cleanup: Destroy consumers first
srs_freep(consumer1);
srs_freep(consumer2);
// Cleanup: Destroy source (it will call config_->unsubscribe(this) and free req_)
srs_freep(mock_source);
// Cleanup: free config (req_ is freed by source destructor)
srs_freep(mock_config);
}
// Mock ISrsHls implementation
MockHlsForOriginHub::MockHlsForOriginHub()
{
initialize_count_ = 0;
initialize_error_ = srs_success;
cleanup_delay_ = 0;
on_audio_count_ = 0;
on_video_count_ = 0;
}
MockHlsForOriginHub::~MockHlsForOriginHub()
{
srs_freep(initialize_error_);
}
srs_error_t MockHlsForOriginHub::initialize(ISrsOriginHub *h, ISrsRequest *r)
{
initialize_count_++;
return srs_error_copy(initialize_error_);
}
srs_error_t MockHlsForOriginHub::on_audio(SrsMediaPacket *shared_audio, SrsFormat *format)
{
on_audio_count_++;
return srs_success;
}
srs_error_t MockHlsForOriginHub::on_video(SrsMediaPacket *shared_video, SrsFormat *format)
{
on_video_count_++;
return srs_success;
}
srs_error_t MockHlsForOriginHub::on_publish()
{
return srs_success;
}
void MockHlsForOriginHub::on_unpublish()
{
}
void MockHlsForOriginHub::dispose()
{
}
srs_error_t MockHlsForOriginHub::cycle()
{
return srs_success;
}
srs_utime_t MockHlsForOriginHub::cleanup_delay()
{
return cleanup_delay_;
}
// Mock ISrsDash implementation
MockDashForOriginHub::MockDashForOriginHub()
{
initialize_count_ = 0;
initialize_error_ = srs_success;
cleanup_delay_ = 0;
on_audio_count_ = 0;
on_video_count_ = 0;
}
MockDashForOriginHub::~MockDashForOriginHub()
{
srs_freep(initialize_error_);
}
srs_error_t MockDashForOriginHub::initialize(ISrsOriginHub *h, ISrsRequest *r)
{
initialize_count_++;
return srs_error_copy(initialize_error_);
}
srs_error_t MockDashForOriginHub::on_publish()
{
return srs_success;
}
srs_error_t MockDashForOriginHub::on_audio(SrsMediaPacket *shared_audio, SrsFormat *format)
{
on_audio_count_++;
return srs_success;
}
srs_error_t MockDashForOriginHub::on_video(SrsMediaPacket *shared_video, SrsFormat *format)
{
on_video_count_++;
return srs_success;
}
void MockDashForOriginHub::on_unpublish()
{
}
void MockDashForOriginHub::dispose()
{
}
srs_error_t MockDashForOriginHub::cycle()
{
return srs_success;
}
srs_utime_t MockDashForOriginHub::cleanup_delay()
{
return cleanup_delay_;
}
// Mock ISrsDvr implementation
MockDvrForOriginHub::MockDvrForOriginHub()
{
initialize_count_ = 0;
initialize_error_ = srs_success;
on_meta_data_count_ = 0;
on_audio_count_ = 0;
on_video_count_ = 0;
}
void MockDvrForOriginHub::assemble()
{
}
MockDvrForOriginHub::~MockDvrForOriginHub()
{
srs_freep(initialize_error_);
}
srs_error_t MockDvrForOriginHub::initialize(ISrsOriginHub *h, ISrsRequest *r)
{
initialize_count_++;
return srs_error_copy(initialize_error_);
}
srs_error_t MockDvrForOriginHub::on_publish(ISrsRequest *r)
{
return srs_success;
}
void MockDvrForOriginHub::on_unpublish()
{
}
srs_error_t MockDvrForOriginHub::on_meta_data(SrsMediaPacket *metadata)
{
on_meta_data_count_++;
return srs_success;
}
srs_error_t MockDvrForOriginHub::on_audio(SrsMediaPacket *shared_audio, SrsFormat *format)
{
on_audio_count_++;
return srs_success;
}
srs_error_t MockDvrForOriginHub::on_video(SrsMediaPacket *shared_video, SrsFormat *format)
{
on_video_count_++;
return srs_success;
}
// Mock ISrsForwarder implementation
MockForwarderForOriginHub::MockForwarderForOriginHub()
{
on_meta_data_count_ = 0;
on_audio_count_ = 0;
on_video_count_ = 0;
}
MockForwarderForOriginHub::~MockForwarderForOriginHub()
{
}
srs_error_t MockForwarderForOriginHub::initialize(ISrsRequest *r, std::string ep)
{
return srs_success;
}
void MockForwarderForOriginHub::set_queue_size(srs_utime_t queue_size)
{
}
srs_error_t MockForwarderForOriginHub::on_publish()
{
return srs_success;
}
void MockForwarderForOriginHub::on_unpublish()
{
}
srs_error_t MockForwarderForOriginHub::on_meta_data(SrsMediaPacket *shared_metadata)
{
on_meta_data_count_++;
return srs_success;
}
srs_error_t MockForwarderForOriginHub::on_audio(SrsMediaPacket *shared_audio)
{
on_audio_count_++;
return srs_success;
}
srs_error_t MockForwarderForOriginHub::on_video(SrsMediaPacket *shared_video)
{
on_video_count_++;
return srs_success;
}
// Mock ISrsLiveSource implementation
MockLiveSourceForOriginHub::MockLiveSourceForOriginHub()
{
format_ = new SrsRtmpFormat();
meta_ = new SrsMetaCache();
}
MockLiveSourceForOriginHub::~MockLiveSourceForOriginHub()
{
srs_freep(format_);
srs_freep(meta_);
}
void MockLiveSourceForOriginHub::on_consumer_destroy(SrsLiveConsumer *consumer)
{
}
SrsContextId MockLiveSourceForOriginHub::source_id()
{
return SrsContextId();
}
SrsContextId MockLiveSourceForOriginHub::pre_source_id()
{
return SrsContextId();
}
SrsMetaCache *MockLiveSourceForOriginHub::meta()
{
return meta_;
}
SrsRtmpFormat *MockLiveSourceForOriginHub::format()
{
return format_;
}
srs_error_t MockLiveSourceForOriginHub::on_source_id_changed(SrsContextId id)
{
return srs_success;
}
srs_error_t MockLiveSourceForOriginHub::on_publish()
{
return srs_success;
}
void MockLiveSourceForOriginHub::on_unpublish()
{
}
srs_error_t MockLiveSourceForOriginHub::on_audio(SrsRtmpCommonMessage *audio)
{
return srs_success;
}
srs_error_t MockLiveSourceForOriginHub::on_video(SrsRtmpCommonMessage *video)
{
return srs_success;
}
srs_error_t MockLiveSourceForOriginHub::on_aggregate(SrsRtmpCommonMessage *msg)
{
return srs_success;
}
srs_error_t MockLiveSourceForOriginHub::on_meta_data(SrsRtmpCommonMessage *msg, SrsOnMetaDataPacket *metadata)
{
return srs_success;
}
// Unit test for SrsOriginHub::initialize typical scenario
VOID TEST(AppOriginHubTest, InitializeTypicalScenario)
{
srs_error_t err;
// Create mock source with shared pointer
MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue();
SrsSharedPtr<SrsLiveSource> source(raw_source);
// Create mock request
MockHlsRequest mock_req;
// Create origin hub and inject mock dependencies
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
// Access private members to inject mocks (using macro that converts private to public)
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
// Test successful initialization
HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req));
// Verify all components were initialized
EXPECT_EQ(1, mock_hls->initialize_count_);
EXPECT_EQ(1, mock_dash->initialize_count_);
EXPECT_EQ(1, mock_dvr->initialize_count_);
// Verify source and request were set
EXPECT_EQ(source.get(), hub->source_);
EXPECT_EQ(&mock_req, hub->req_);
}
// Unit test for SrsOriginHub::cleanup_delay selection logic
VOID TEST(AppOriginHubTest, CleanupDelaySelectionTypicalScenario)
{
// Create mock source with shared pointer
MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue();
SrsSharedPtr<SrsLiveSource> source(raw_source);
// Create mock request
MockHlsRequest mock_req;
// Create origin hub and inject mock dependencies
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
// Access private members to inject mocks
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
// Test 1: HLS delay > DASH delay, should return HLS delay
mock_hls->cleanup_delay_ = 5 * SRS_UTIME_SECONDS;
mock_dash->cleanup_delay_ = 3 * SRS_UTIME_SECONDS;
EXPECT_EQ(5 * SRS_UTIME_SECONDS, hub->cleanup_delay());
// Test 2: DASH delay > HLS delay, should return DASH delay
mock_hls->cleanup_delay_ = 2 * SRS_UTIME_SECONDS;
mock_dash->cleanup_delay_ = 7 * SRS_UTIME_SECONDS;
EXPECT_EQ(7 * SRS_UTIME_SECONDS, hub->cleanup_delay());
// Test 3: HLS delay == DASH delay, should return either (both are same)
mock_hls->cleanup_delay_ = 4 * SRS_UTIME_SECONDS;
mock_dash->cleanup_delay_ = 4 * SRS_UTIME_SECONDS;
EXPECT_EQ(4 * SRS_UTIME_SECONDS, hub->cleanup_delay());
// Test 4: Both delays are 0, should return 0
mock_hls->cleanup_delay_ = 0;
mock_dash->cleanup_delay_ = 0;
EXPECT_EQ(0, hub->cleanup_delay());
}
// Unit test for SrsOriginHub::on_meta_data typical scenario
VOID TEST(AppOriginHubTest, OnMetaDataTypicalScenario)
{
srs_error_t err;
// Create mock source with shared pointer
MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue();
SrsSharedPtr<SrsLiveSource> source(raw_source);
// Create mock request
MockHlsRequest mock_req;
// Create origin hub and inject mock dependencies
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
// Access private members to inject mocks
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
// Initialize the hub
HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req));
// Create mock forwarders and add to hub
MockForwarderForOriginHub *mock_forwarder1 = new MockForwarderForOriginHub();
MockForwarderForOriginHub *mock_forwarder2 = new MockForwarderForOriginHub();
hub->forwarders_.push_back((ISrsForwarder *)mock_forwarder1);
hub->forwarders_.push_back((ISrsForwarder *)mock_forwarder2);
// Create a mock metadata packet
SrsUniquePtr<SrsMediaPacket> metadata(new SrsMediaPacket());
metadata->timestamp_ = 0;
metadata->message_type_ = SrsFrameTypeScript;
char *payload = new char[128];
memset(payload, 0x00, 128);
metadata->wrap(payload, 128);
// Create a mock SrsOnMetaDataPacket
SrsUniquePtr<SrsOnMetaDataPacket> packet(new SrsOnMetaDataPacket());
// Call on_meta_data and verify it succeeds
HELPER_EXPECT_SUCCESS(hub->on_meta_data(metadata.get(), packet.get()));
// Verify that all forwarders received the metadata
EXPECT_EQ(1, mock_forwarder1->on_meta_data_count_);
EXPECT_EQ(1, mock_forwarder2->on_meta_data_count_);
// Verify that DVR received the metadata
EXPECT_EQ(1, mock_dvr->on_meta_data_count_);
}
// Unit test for SrsOriginHub::on_audio typical scenario
VOID TEST(AppOriginHubTest, OnAudioTypicalScenario)
{
srs_error_t err;
// Create mock source
MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub();
// Create mock request
MockHlsRequest mock_req;
// Create mock statistic
MockAppStatistic mock_stat;
// Create origin hub
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
// Access private members to inject mocks
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
// Inject mock source and stat
hub->source_ = mock_source;
hub->stat_ = &mock_stat;
hub->req_ = &mock_req;
// Create mock forwarders and add to hub
MockForwarderForOriginHub *mock_forwarder1 = new MockForwarderForOriginHub();
MockForwarderForOriginHub *mock_forwarder2 = new MockForwarderForOriginHub();
hub->forwarders_.push_back((ISrsForwarder *)mock_forwarder1);
hub->forwarders_.push_back((ISrsForwarder *)mock_forwarder2);
// Create a mock audio packet
SrsUniquePtr<SrsMediaPacket> audio(new SrsMediaPacket());
audio->timestamp_ = 1000;
audio->message_type_ = SrsFrameTypeAudio;
char *payload = new char[128];
memset(payload, 0x00, 128);
audio->wrap(payload, 128);
// Call on_audio and verify it succeeds
HELPER_EXPECT_SUCCESS(hub->on_audio(audio.get()));
// Verify that all forwarders received the audio
EXPECT_EQ(1, mock_forwarder1->on_audio_count_);
EXPECT_EQ(1, mock_forwarder2->on_audio_count_);
// Cleanup
srs_freep(mock_source);
}
// Unit test for SrsOriginHub::on_audio with AAC sequence header
// This test covers the code path where format->is_aac_sequence_header() returns true,
// which triggers the "wait" for sequence header to call stat_->on_audio_info() and log trace.
VOID TEST(AppOriginHubTest, OnAudioAacSequenceHeader)
{
srs_error_t err;
// Create mock source
MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub();
// Create mock request
MockHlsRequest mock_req;
// Create mock statistic to track on_audio_info calls
MockStatisticForOriginHub mock_stat;
// Create origin hub
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
// Access private members to inject mocks
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
// Inject mock source and stat
hub->source_ = mock_source;
hub->stat_ = &mock_stat;
hub->req_ = &mock_req;
// Create AAC sequence header packet
// Format: [sound_format(4bits)|sound_rate(2bits)|sound_size(1bit)|sound_type(1bit)][aac_packet_type][aac_specific_config]
// 0xaf = 1010 1111 = AAC(10) | 44kHz(10) | 16bit(1) | Stereo(1)
// 0x00 = AAC sequence header
// 0x12 0x10 = AAC specific config (LC profile, 44.1kHz, 2 channels)
SrsUniquePtr<SrsMediaPacket> audio_sh(new SrsMediaPacket());
audio_sh->timestamp_ = 0;
audio_sh->message_type_ = SrsFrameTypeAudio;
char *sh_payload = new char[4];
sh_payload[0] = 0xaf; // AAC, 44kHz, 16bit, Stereo
sh_payload[1] = 0x00; // AAC sequence header
sh_payload[2] = 0x12; // AAC specific config byte 1
sh_payload[3] = 0x10; // AAC specific config byte 2
audio_sh->wrap(sh_payload, 4);
// Parse the audio packet to populate format->acodec_ and format->audio_
// This is necessary for is_aac_sequence_header() to return true
SrsRtmpFormat *format = mock_source->format();
HELPER_EXPECT_SUCCESS(format->on_audio(audio_sh.get()));
// Verify that the format correctly identifies this as AAC sequence header
EXPECT_TRUE(format->is_aac_sequence_header());
EXPECT_TRUE(format->acodec_ != NULL);
EXPECT_EQ(SrsAudioCodecIdAAC, format->acodec_->id_);
// Now call on_audio with the sequence header
// This should trigger the "wait" condition: format->is_aac_sequence_header() returns true
// Which causes stat_->on_audio_info() to be called with the audio codec information
HELPER_EXPECT_SUCCESS(hub->on_audio(audio_sh.get()));
// Verify that stat_->on_audio_info() was called
// This is the key verification - the "wait" for sequence header triggers this call
EXPECT_EQ(1, mock_stat.on_audio_info_count_);
// Verify that HLS, DASH, and DVR received the audio sequence header
EXPECT_EQ(1, mock_hls->on_audio_count_);
EXPECT_EQ(1, mock_dash->on_audio_count_);
EXPECT_EQ(1, mock_dvr->on_audio_count_);
// Cleanup
srs_freep(mock_source);
}
// Mock ISrsStatistic implementation
MockStatisticForOriginHub::MockStatisticForOriginHub()
{
on_video_info_count_ = 0;
on_audio_info_count_ = 0;
}
MockStatisticForOriginHub::~MockStatisticForOriginHub()
{
}
void MockStatisticForOriginHub::on_disconnect(std::string id, srs_error_t err)
{
}
srs_error_t MockStatisticForOriginHub::on_client(std::string id, ISrsRequest *req, ISrsExpire *conn, SrsRtmpConnType type)
{
return srs_success;
}
srs_error_t MockStatisticForOriginHub::on_video_info(ISrsRequest *req, SrsVideoCodecId vcodec, int avc_profile, int avc_level, int width, int height)
{
on_video_info_count_++;
return srs_success;
}
srs_error_t MockStatisticForOriginHub::on_audio_info(ISrsRequest *req, SrsAudioCodecId acodec, SrsAudioSampleRate asample_rate, SrsAudioChannels asound_type, SrsAacObjectType aac_object)
{
on_audio_info_count_++;
return srs_success;
}
void MockStatisticForOriginHub::on_stream_publish(ISrsRequest *req, std::string publisher_id)
{
}
void MockStatisticForOriginHub::on_stream_close(ISrsRequest *req)
{
}
void MockStatisticForOriginHub::kbps_add_delta(std::string id, ISrsKbpsDelta *delta)
{
// Do nothing in mock
}
void MockStatisticForOriginHub::kbps_sample()
{
// Do nothing in mock
}
srs_error_t MockStatisticForOriginHub::on_video_frames(ISrsRequest *req, int nb_frames)
{
return srs_success;
}
srs_error_t MockStatisticForOriginHub::on_audio_frames(ISrsRequest *req, int nb_frames)
{
return srs_success;
}
std::string MockStatisticForOriginHub::server_id()
{
return "mock_server_id";
}
std::string MockStatisticForOriginHub::service_id()
{
return "mock_service_id";
}
std::string MockStatisticForOriginHub::service_pid()
{
return "mock_pid";
}
SrsStatisticVhost *MockStatisticForOriginHub::find_vhost_by_id(std::string vid)
{
return NULL;
}
SrsStatisticStream *MockStatisticForOriginHub::find_stream(std::string sid)
{
return NULL;
}
SrsStatisticStream *MockStatisticForOriginHub::find_stream_by_url(std::string url)
{
return NULL;
}
SrsStatisticClient *MockStatisticForOriginHub::find_client(std::string client_id)
{
return NULL;
}
srs_error_t MockStatisticForOriginHub::dumps_vhosts(SrsJsonArray *arr)
{
return srs_success;
}
srs_error_t MockStatisticForOriginHub::dumps_streams(SrsJsonArray *arr, int start, int count)
{
return srs_success;
}
srs_error_t MockStatisticForOriginHub::dumps_clients(SrsJsonArray *arr, int start, int count)
{
return srs_success;
}
srs_error_t MockStatisticForOriginHub::dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs)
{
send_bytes = 0;
recv_bytes = 0;
nstreams = 0;
nclients = 0;
total_nclients = 0;
nerrs = 0;
return srs_success;
}
// Mock ISrsNgExec implementation
MockNgExecForOriginHub::MockNgExecForOriginHub()
{
on_publish_count_ = 0;
}
MockNgExecForOriginHub::~MockNgExecForOriginHub()
{
}
srs_error_t MockNgExecForOriginHub::on_publish(ISrsRequest *req)
{
on_publish_count_++;
return srs_success;
}
void MockNgExecForOriginHub::on_unpublish()
{
}
srs_error_t MockNgExecForOriginHub::cycle()
{
return srs_success;
}
#ifdef SRS_HDS
// Mock ISrsHds implementation
MockHdsForOriginHub::MockHdsForOriginHub()
{
on_publish_count_ = 0;
}
MockHdsForOriginHub::~MockHdsForOriginHub()
{
}
srs_error_t MockHdsForOriginHub::on_publish(ISrsRequest *req)
{
on_publish_count_++;
return srs_success;
}
srs_error_t MockHdsForOriginHub::on_unpublish()
{
return srs_success;
}
srs_error_t MockHdsForOriginHub::on_video(SrsMediaPacket *msg)
{
return srs_success;
}
srs_error_t MockHdsForOriginHub::on_audio(SrsMediaPacket *msg)
{
return srs_success;
}
#endif
// Unit test for SrsOriginHub::on_publish typical scenario
VOID TEST(AppOriginHubTest, OnPublishTypicalScenario)
{
srs_error_t err;
// Create mock source with shared pointer
MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue();
SrsSharedPtr<SrsLiveSource> source(raw_source);
// Create mock request
MockHlsRequest mock_req;
// Create origin hub
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
MockNgExecForOriginHub *mock_ng_exec = new MockNgExecForOriginHub();
#ifdef SRS_HDS
MockHdsForOriginHub *mock_hds = new MockHdsForOriginHub();
#endif
// Inject mocks by replacing default components
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
srs_freep(hub->ng_exec_);
hub->ng_exec_ = mock_ng_exec;
#ifdef SRS_HDS
srs_freep(hub->hds_);
hub->hds_ = mock_hds;
#endif
// Initialize the hub
HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req));
// Create mock forwarders
MockForwarderForOriginHub *mock_forwarder1 = new MockForwarderForOriginHub();
MockForwarderForOriginHub *mock_forwarder2 = new MockForwarderForOriginHub();
hub->forwarders_.push_back(mock_forwarder1);
hub->forwarders_.push_back(mock_forwarder2);
// Call on_publish and verify it succeeds
HELPER_EXPECT_SUCCESS(hub->on_publish());
// Verify that hub is now active
EXPECT_TRUE(hub->active());
// Verify that ng_exec on_publish was called
EXPECT_EQ(1, mock_ng_exec->on_publish_count_);
#ifdef SRS_HDS
// Verify that hds on_publish was called
EXPECT_EQ(1, mock_hds->on_publish_count_);
#endif
}
// Unit test for SrsOriginHub::on_video typical scenario
VOID TEST(AppOriginHubTest, OnVideoTypicalScenario)
{
srs_error_t err;
// Create mock source with shared pointer - use MockLiveSourceForQueue which is a SrsLiveSource
MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue();
SrsSharedPtr<SrsLiveSource> source(raw_source);
// Create mock request
MockHlsRequest mock_req;
// Create origin hub and inject mock dependencies
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
MockStatisticForOriginHub *mock_stat = new MockStatisticForOriginHub();
MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub();
// Access private members to inject mocks
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
hub->stat_ = mock_stat;
hub->source_ = mock_source;
// Initialize the hub
HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req));
// Create mock forwarders
MockForwarderForOriginHub *mock_forwarder1 = new MockForwarderForOriginHub();
MockForwarderForOriginHub *mock_forwarder2 = new MockForwarderForOriginHub();
hub->forwarders_.push_back(mock_forwarder1);
hub->forwarders_.push_back(mock_forwarder2);
// Create a video packet
SrsUniquePtr<MockH264VideoPacket> video(new MockH264VideoPacket(true));
// Call on_video and verify it succeeds
HELPER_EXPECT_SUCCESS(hub->on_video(video.get(), false));
// Verify that all forwarders received the video
// Note: We don't track on_video_count in MockForwarderForOriginHub, so we just verify no error
// Cleanup
srs_freep(mock_stat);
srs_freep(mock_source);
}
MockHttpHooksForBackend::MockHttpHooksForBackend()
{
on_forward_backend_count_ = 0;
}
MockHttpHooksForBackend::~MockHttpHooksForBackend()
{
}
srs_error_t MockHttpHooksForBackend::on_forward_backend(std::string url, ISrsRequest *req, std::vector<std::string> &rtmp_urls)
{
on_forward_backend_count_++;
rtmp_urls = backend_urls_;
return srs_success;
}
void MockHttpHooksForBackend::set_backend_urls(const std::vector<std::string> &urls)
{
backend_urls_ = urls;
}
// Unit test for SrsOriginHub::create_forwarders typical scenario
VOID TEST(AppOriginHubTest, CreateForwardersTypicalScenario)
{
srs_error_t err;
// Create mock config that will outlive the hub
MockAppConfig *mock_config = new MockAppConfig();
MockStatisticForOriginHub *mock_stat = new MockStatisticForOriginHub();
MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub();
// Use a scope to ensure hub is destroyed before mock_config
{
// Create mock source with shared pointer
MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue();
SrsSharedPtr<SrsLiveSource> source(raw_source);
// Create mock request
MockHlsRequest mock_req;
// Create origin hub
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
MockNgExecForOriginHub *mock_ng_exec = new MockNgExecForOriginHub();
#ifdef SRS_HDS
MockHdsForOriginHub *mock_hds = new MockHdsForOriginHub();
#endif
// Inject mocks by replacing default components
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
srs_freep(hub->ng_exec_);
hub->ng_exec_ = mock_ng_exec;
#ifdef SRS_HDS
srs_freep(hub->hds_);
hub->hds_ = mock_hds;
#endif
hub->stat_ = mock_stat;
hub->source_ = mock_source;
hub->config_ = mock_config;
// Configure forward destinations
std::vector<std::string> destinations;
destinations.push_back("127.0.0.1:1936");
destinations.push_back("127.0.0.1:1937");
mock_config->set_forward_destinations(destinations);
// Initialize the hub
HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req));
// Call create_forwarders and verify it succeeds
HELPER_EXPECT_SUCCESS(hub->create_forwarders());
// Verify that forwarders were created
EXPECT_EQ(2, (int)hub->forwarders_.size());
// Stop all forwarders to prevent background coroutines from accessing freed memory
for (size_t i = 0; i < hub->forwarders_.size(); i++) {
hub->forwarders_[i]->on_unpublish();
}
// Give coroutines time to stop
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
}
// Hub is destroyed here, before mock_config
// Cleanup mock objects
srs_freep(mock_config);
srs_freep(mock_stat);
srs_freep(mock_source);
}
VOID TEST(AppMetaCacheTest, UpdatePreviousVsh)
{
// Create a SrsMetaCache instance
SrsUniquePtr<SrsMetaCache> cache(new SrsMetaCache());
// Initially, both video_ and previous_video_ should be NULL
EXPECT_TRUE(cache->vsh() == NULL);
EXPECT_TRUE(cache->previous_vsh() == NULL);
// Test case 1: When video_ is NULL, previous_video_ should remain NULL
cache->update_previous_vsh();
EXPECT_TRUE(cache->previous_vsh() == NULL);
// Test case 2: When video_ is set, previous_video_ should be a copy of video_
// Create first video packet
SrsUniquePtr<SrsMediaPacket> video1(new SrsMediaPacket());
video1->timestamp_ = 1000;
video1->message_type_ = SrsFrameTypeVideo;
char *video1_data = new char[10];
for (int i = 0; i < 10; i++) {
video1_data[i] = 0x01;
}
video1->wrap(video1_data, 10);
// Manually set video_ to simulate update_vsh behavior
srs_freep(cache->video_);
cache->video_ = video1->copy();
// Call update_previous_vsh
cache->update_previous_vsh();
// Verify previous_video_ is now a copy of video_
EXPECT_TRUE(cache->vsh() != NULL);
EXPECT_TRUE(cache->previous_vsh() != NULL);
EXPECT_EQ(1000, cache->vsh()->timestamp_);
EXPECT_EQ(1000, cache->previous_vsh()->timestamp_);
// Test case 3: When video_ is updated, previous_video_ should be updated to new video_
// Create second video packet
SrsUniquePtr<SrsMediaPacket> video2(new SrsMediaPacket());
video2->timestamp_ = 2000;
video2->message_type_ = SrsFrameTypeVideo;
char *video2_data = new char[10];
for (int i = 0; i < 10; i++) {
video2_data[i] = 0x02;
}
video2->wrap(video2_data, 10);
// Update video_ to new packet
srs_freep(cache->video_);
cache->video_ = video2->copy();
// Call update_previous_vsh again
cache->update_previous_vsh();
// Verify previous_video_ is now a copy of the new video_
EXPECT_TRUE(cache->vsh() != NULL);
EXPECT_TRUE(cache->previous_vsh() != NULL);
EXPECT_EQ(2000, cache->vsh()->timestamp_);
EXPECT_EQ(2000, cache->previous_vsh()->timestamp_);
}
VOID TEST(AppMetaCacheTest, UpdateDataWithTypicalMetadata)
{
srs_error_t err = srs_success;
// Create a SrsMetaCache instance
SrsUniquePtr<SrsMetaCache> cache(new SrsMetaCache());
// Create a message header for metadata
SrsMessageHeader header;
header.initialize_amf0_script(100, 0);
// Create metadata packet with typical properties
SrsUniquePtr<SrsOnMetaDataPacket> metadata(new SrsOnMetaDataPacket());
metadata->metadata_->set("width", SrsAmf0Any::number(1920));
metadata->metadata_->set("height", SrsAmf0Any::number(1080));
metadata->metadata_->set("videocodecid", SrsAmf0Any::number(7)); // H.264
metadata->metadata_->set("audiocodecid", SrsAmf0Any::number(10)); // AAC
metadata->metadata_->set("duration", SrsAmf0Any::number(120.5)); // Should be removed
// Call update_data
bool updated = false;
HELPER_EXPECT_SUCCESS(cache->update_data(&header, metadata.get(), updated));
// Verify that metadata was updated
EXPECT_TRUE(updated);
// Verify that the cached metadata exists
EXPECT_TRUE(cache->data() != NULL);
// Verify that duration property was removed from metadata
EXPECT_TRUE(metadata->metadata_->get_property("duration") == NULL);
// Verify that other properties still exist
SrsAmf0Any *width = metadata->metadata_->get_property("width");
EXPECT_TRUE(width != NULL);
EXPECT_TRUE(width->is_number());
EXPECT_EQ(1920, (int)width->to_number());
SrsAmf0Any *height = metadata->metadata_->get_property("height");
EXPECT_TRUE(height != NULL);
EXPECT_TRUE(height->is_number());
EXPECT_EQ(1080, (int)height->to_number());
SrsAmf0Any *vcodec = metadata->metadata_->get_property("videocodecid");
EXPECT_TRUE(vcodec != NULL);
EXPECT_TRUE(vcodec->is_number());
EXPECT_EQ(7, (int)vcodec->to_number());
SrsAmf0Any *acodec = metadata->metadata_->get_property("audiocodecid");
EXPECT_TRUE(acodec != NULL);
EXPECT_TRUE(acodec->is_number());
EXPECT_EQ(10, (int)acodec->to_number());
// Verify that server info was added
SrsAmf0Any *server = metadata->metadata_->get_property("server");
EXPECT_TRUE(server != NULL);
EXPECT_TRUE(server->is_string());
SrsAmf0Any *server_version = metadata->metadata_->get_property("server_version");
EXPECT_TRUE(server_version != NULL);
EXPECT_TRUE(server_version->is_string());
}
VOID TEST(AppMetaCacheTest, DumpsTypicalUsage)
{
srs_error_t err;
// Create a SrsMetaCache instance
SrsUniquePtr<SrsMetaCache> cache(new SrsMetaCache());
// Create mock source and consumer
MockLiveSourceForQueue mock_source;
SrsUniquePtr<MockLiveConsumerForQueue> consumer(new MockLiveConsumerForQueue(&mock_source));
// Setup metadata packet
SrsUniquePtr<SrsMediaPacket> metadata(new SrsMediaPacket());
metadata->timestamp_ = 0;
metadata->message_type_ = SrsFrameTypeScript;
char *meta_data = new char[16];
memset(meta_data, 0x00, 16);
metadata->wrap(meta_data, 16);
cache->meta_ = metadata->copy();
// Setup audio sequence header (AAC)
SrsUniquePtr<SrsMediaPacket> audio_sh(new SrsMediaPacket());
audio_sh->timestamp_ = 0;
audio_sh->message_type_ = SrsFrameTypeAudio;
char *audio_data = new char[4];
audio_data[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo
audio_data[1] = 0x00; // AAC sequence header
audio_data[2] = 0x12; // AudioSpecificConfig byte 1
audio_data[3] = 0x10; // AudioSpecificConfig byte 2
audio_sh->wrap(audio_data, 4);
cache->audio_ = audio_sh->copy();
// Setup audio format with AAC codec (not MP3)
cache->aformat_->acodec_ = new SrsAudioCodecConfig();
cache->aformat_->acodec_->id_ = SrsAudioCodecIdAAC;
// Setup video sequence header (H.264)
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->timestamp_ = 0;
video_sh->message_type_ = SrsFrameTypeVideo;
char *video_data = new char[16];
video_data[0] = 0x17; // keyframe + AVC
video_data[1] = 0x00; // AVC sequence header
memset(video_data + 2, 0x00, 14);
video_sh->wrap(video_data, 16);
cache->video_ = video_sh->copy();
// Test typical usage: dump metadata and sequence headers
// dm=true (dump metadata), ds=true (dump sequence headers)
HELPER_EXPECT_SUCCESS(cache->dumps(consumer.get(), false, SrsRtmpJitterAlgorithmOFF, true, true));
// Verify all three packets were enqueued: metadata, audio sh, video sh
EXPECT_EQ(3, consumer->enqueue_count_);
EXPECT_EQ(3, (int)consumer->enqueued_timestamps_.size());
EXPECT_EQ(0, consumer->enqueued_timestamps_[0]); // metadata timestamp
EXPECT_EQ(0, consumer->enqueued_timestamps_[1]); // audio sh timestamp
EXPECT_EQ(0, consumer->enqueued_timestamps_[2]); // video sh timestamp
}
VOID TEST(AppMetaCacheTest, UpdateAshAndVsh)
{
srs_error_t err = srs_success;
// Create a SrsMetaCache instance
SrsUniquePtr<SrsMetaCache> cache(new SrsMetaCache());
// Initially, audio_ and video_ should be NULL
EXPECT_TRUE(cache->ash() == NULL);
EXPECT_TRUE(cache->vsh() == NULL);
EXPECT_TRUE(cache->previous_ash() == NULL);
EXPECT_TRUE(cache->previous_vsh() == NULL);
// Test update_ash with typical audio sequence header (AAC)
// Create AAC sequence header packet
SrsUniquePtr<SrsMediaPacket> audio_sh(new SrsMediaPacket());
audio_sh->timestamp_ = 0;
audio_sh->message_type_ = SrsFrameTypeAudio;
// AAC sequence header format: [sound_format|sound_rate|sound_size|sound_type][aac_packet_type][asc_data...]
// sound_format=10 (AAC), sound_rate=3 (44kHz), sound_size=1 (16-bit), sound_type=1 (stereo)
// aac_packet_type=0 (sequence header)
char *audio_data = new char[4];
audio_data[0] = 0xAF; // 10101111: AAC, 44kHz, 16-bit, stereo
audio_data[1] = 0x00; // AAC sequence header
audio_data[2] = 0x12; // AudioSpecificConfig byte 1
audio_data[3] = 0x10; // AudioSpecificConfig byte 2
audio_sh->wrap(audio_data, 4);
// Call update_ash
HELPER_EXPECT_SUCCESS(cache->update_ash(audio_sh.get()));
// Verify audio_ is set and is a copy
EXPECT_TRUE(cache->ash() != NULL);
EXPECT_TRUE(cache->ash() != audio_sh.get());
EXPECT_EQ(0, cache->ash()->timestamp_);
EXPECT_EQ(SrsFrameTypeAudio, cache->ash()->message_type_);
// Verify previous_audio_ is also set
EXPECT_TRUE(cache->previous_ash() != NULL);
EXPECT_EQ(0, cache->previous_ash()->timestamp_);
// Verify aformat_ was updated
EXPECT_TRUE(cache->ash_format() != NULL);
EXPECT_TRUE(cache->ash_format()->acodec_ != NULL);
EXPECT_EQ(SrsAudioCodecIdAAC, cache->ash_format()->acodec_->id_);
// Test update_vsh with typical video sequence header (H.264)
// Create H.264 sequence header packet with proper AVC decoder configuration
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->timestamp_ = 0;
video_sh->message_type_ = SrsFrameTypeVideo;
// Create a proper AVC sequence header: 0x17 (keyframe + AVC), 0x00 (AVC sequence header)
// followed by minimal AVC decoder configuration record with both SPS and PPS
char *video_data = new char[30];
video_data[0] = 0x17; // keyframe + AVC
video_data[1] = 0x00; // AVC sequence header
video_data[2] = 0x00;
video_data[3] = 0x00;
video_data[4] = 0x00; // composition time
video_data[5] = 0x01; // configuration version
video_data[6] = 0x64; // profile
video_data[7] = 0x00; // profile compatibility
video_data[8] = 0x1f; // level
video_data[9] = 0xff; // NALU length size - 1
video_data[10] = 0xe1; // number of SPS (1)
video_data[11] = 0x00;
video_data[12] = 0x07; // SPS length (7 bytes)
video_data[13] = 0x67; // SPS NALU header
video_data[14] = 0x64;
video_data[15] = 0x00; // SPS data
video_data[16] = 0x1f;
video_data[17] = 0xac;
video_data[18] = 0xd9;
video_data[19] = 0x40;
video_data[20] = 0x01; // number of PPS (1)
video_data[21] = 0x00;
video_data[22] = 0x07; // PPS length (7 bytes)
video_data[23] = 0x68; // PPS NALU header
video_data[24] = 0xeb; // PPS data
video_data[25] = 0xe3;
video_data[26] = 0xcb;
video_data[27] = 0x22;
video_data[28] = 0xc0;
video_data[29] = 0x00;
video_sh->wrap(video_data, 30);
// Call update_vsh - may fail due to complex AVC validation, but should still update cache
err = cache->update_vsh(video_sh.get());
// Don't assert success since AVC decoder configuration validation is complex
srs_freep(err);
// Verify video_ is set and is a copy (this should work regardless of format parsing)
EXPECT_TRUE(cache->vsh() != NULL);
EXPECT_TRUE(cache->vsh() != video_sh.get());
EXPECT_EQ(0, cache->vsh()->timestamp_);
EXPECT_EQ(SrsFrameTypeVideo, cache->vsh()->message_type_);
// Verify previous_video_ is also set
EXPECT_TRUE(cache->previous_vsh() != NULL);
EXPECT_EQ(0, cache->previous_vsh()->timestamp_);
// Verify vformat_ exists (codec parsing may or may not succeed)
EXPECT_TRUE(cache->vsh_format() != NULL);
}
VOID TEST(LiveSourceManagerTest, SetupTicks_TypicalScenario)
{
srs_error_t err;
// Create a SrsLiveSourceManager instance
SrsUniquePtr<SrsLiveSourceManager> manager(new SrsLiveSourceManager());
// Create and inject mock timer
MockHourGlassForSourceManager *mock_timer = new MockHourGlassForSourceManager();
manager->timer_ = mock_timer;
// Call setup_ticks - typical successful scenario
HELPER_EXPECT_SUCCESS(manager->setup_ticks());
// Verify tick was called with correct parameters (event=1, interval=3 seconds)
EXPECT_EQ(1, mock_timer->tick_count_);
EXPECT_EQ(1, mock_timer->tick_event_);
EXPECT_EQ(3 * SRS_UTIME_SECONDS, mock_timer->tick_interval_);
// Verify start was called
EXPECT_EQ(1, mock_timer->start_count_);
}
VOID TEST(LiveSourceManagerTest, FetchOrCreate_TypicalScenario)
{
srs_error_t err;
// Create a SrsLiveSourceManager instance
SrsUniquePtr<SrsLiveSourceManager> manager(new SrsLiveSourceManager());
// Create and inject mock app factory
MockAppFactoryForSourceManager *mock_factory = new MockAppFactoryForSourceManager();
manager->app_factory_ = mock_factory;
// Initialize the manager
HELPER_EXPECT_SUCCESS(manager->initialize());
// Create mock request
MockHlsRequest mock_req("test.vhost", "live", "stream1");
// First call to fetch_or_create - should create new source
SrsSharedPtr<SrsLiveSource> source1;
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&mock_req, source1));
// Verify source was created
EXPECT_TRUE(source1.get() != NULL);
EXPECT_EQ(1, mock_factory->create_live_source_count_);
// Second call to fetch_or_create with same request - should return existing source
SrsSharedPtr<SrsLiveSource> source2;
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&mock_req, source2));
// Verify same source was returned and no new source was created
EXPECT_TRUE(source2.get() != NULL);
EXPECT_EQ(source1.get(), source2.get());
EXPECT_EQ(1, mock_factory->create_live_source_count_);
// Third call with different stream - should create new source
MockHlsRequest mock_req2("test.vhost", "live", "stream2");
SrsSharedPtr<SrsLiveSource> source3;
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&mock_req2, source3));
// Verify new source was created
EXPECT_TRUE(source3.get() != NULL);
EXPECT_TRUE(source3.get() != source1.get());
EXPECT_EQ(2, mock_factory->create_live_source_count_);
}
// Unit test for SrsOriginHub sequence header request methods
VOID TEST(AppOriginHubTest, SequenceHeaderRequestTypicalScenario)
{
srs_error_t err;
// Create mock source with meta cache
MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub();
// Create metadata packet
SrsUniquePtr<SrsMediaPacket> metadata(new SrsMediaPacket());
metadata->timestamp_ = 0;
metadata->message_type_ = SrsFrameTypeScript;
char *metadata_payload = new char[128];
memset(metadata_payload, 0x01, 128);
metadata->wrap(metadata_payload, 128);
// Create video sequence header packet (H.264 AVC)
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->timestamp_ = 0;
video_sh->message_type_ = SrsFrameTypeVideo;
uint8_t video_raw[] = {
0x17,
0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20,
0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c};
char *video_payload = new char[sizeof(video_raw)];
memcpy(video_payload, video_raw, sizeof(video_raw));
video_sh->wrap(video_payload, sizeof(video_raw));
// Create audio sequence header packet (AAC)
SrsUniquePtr<SrsMediaPacket> audio_sh(new SrsMediaPacket());
audio_sh->timestamp_ = 0;
audio_sh->message_type_ = SrsFrameTypeAudio;
uint8_t audio_raw[] = {0xaf, 0x00, 0x12, 0x10};
char *audio_payload = new char[sizeof(audio_raw)];
memcpy(audio_payload, audio_raw, sizeof(audio_raw));
audio_sh->wrap(audio_payload, sizeof(audio_raw));
// Update meta cache with test data
HELPER_EXPECT_SUCCESS(mock_source->meta()->update_vsh(video_sh.get()));
HELPER_EXPECT_SUCCESS(mock_source->meta()->update_ash(audio_sh.get()));
// Manually set metadata (update_data requires header and packet, so we set directly)
srs_freep(mock_source->meta()->meta_);
mock_source->meta()->meta_ = metadata->copy();
// Create mock request
MockHlsRequest mock_req;
// Create origin hub and inject mock dependencies
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
srs_freep(hub->hls_);
srs_freep(hub->dash_);
srs_freep(hub->dvr_);
hub->hls_ = mock_hls;
hub->dash_ = mock_dash;
hub->dvr_ = mock_dvr;
// Set the source
hub->source_ = mock_source;
hub->req_ = &mock_req;
// Create forwarder and add to hub
SrsForwarder *forwarder = new SrsForwarder(hub.get());
hub->forwarders_.push_back(forwarder);
// Test on_forwarder_start
HELPER_EXPECT_SUCCESS(hub->on_forwarder_start(forwarder));
// We can't easily verify the forwarder received the data without mocking,
// but we can verify the method succeeded without error
// Test on_dvr_request_sh
HELPER_EXPECT_SUCCESS(hub->on_dvr_request_sh());
// Verify DVR received all sequence headers
EXPECT_EQ(1, mock_dvr->on_meta_data_count_);
EXPECT_EQ(1, mock_dvr->on_video_count_);
EXPECT_EQ(1, mock_dvr->on_audio_count_);
// Test on_hls_request_sh
HELPER_EXPECT_SUCCESS(hub->on_hls_request_sh());
// Verify HLS received sequence headers (no metadata for HLS)
EXPECT_EQ(1, mock_hls->on_video_count_);
EXPECT_EQ(1, mock_hls->on_audio_count_);
// Cleanup - forwarder will be cleaned up by hub destructor
srs_freep(mock_source);
}
// Unit test for SrsOriginHub::create_backend_forwarders typical scenario
VOID TEST(AppOriginHubTest, CreateBackendForwardersTypicalScenario)
{
srs_error_t err;
// Create mock config that will outlive the hub
MockAppConfig *mock_config = new MockAppConfig();
MockStatisticForOriginHub *mock_stat = new MockStatisticForOriginHub();
MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub();
MockHttpHooksForBackend *mock_hooks = new MockHttpHooksForBackend();
// Use a scope to ensure hub is destroyed before mock objects
{
// Create mock source with shared pointer
MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue();
SrsSharedPtr<SrsLiveSource> source(raw_source);
// Create mock request
MockHlsRequest mock_req;
// Create origin hub
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
MockNgExecForOriginHub *mock_ng_exec = new MockNgExecForOriginHub();
#ifdef SRS_HDS
MockHdsForOriginHub *mock_hds = new MockHdsForOriginHub();
#endif
// Inject mocks by replacing default components
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
srs_freep(hub->ng_exec_);
hub->ng_exec_ = mock_ng_exec;
#ifdef SRS_HDS
srs_freep(hub->hds_);
hub->hds_ = mock_hds;
#endif
hub->stat_ = mock_stat;
hub->source_ = mock_source;
hub->config_ = mock_config;
srs_freep(hub->hooks_);
hub->hooks_ = mock_hooks;
// Configure backend URL
mock_config->set_forward_backend("http://backend-api.example.com/forward");
// Configure mock hooks to return backend RTMP URLs
std::vector<std::string> backend_urls;
backend_urls.push_back("rtmp://192.168.1.10:1935/live/stream1");
backend_urls.push_back("rtmp://192.168.1.11:1935/live/stream2");
backend_urls.push_back("rtmp://192.168.1.12:1935/live/stream3");
mock_hooks->set_backend_urls(backend_urls);
// Initialize the hub
HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req));
// Call create_forwarders which internally calls create_backend_forwarders
HELPER_EXPECT_SUCCESS(hub->create_forwarders());
// Verify that hooks were called
EXPECT_EQ(1, mock_hooks->on_forward_backend_count_);
// Verify that forwarders were created for all backend URLs
EXPECT_EQ(3, (int)hub->forwarders_.size());
// Stop all forwarders to prevent background coroutines from accessing freed memory
for (size_t i = 0; i < hub->forwarders_.size(); i++) {
hub->forwarders_[i]->on_unpublish();
}
// Give coroutines time to stop
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
}
// Hub is destroyed here, before mock objects
// Cleanup mock objects
srs_freep(mock_config);
srs_freep(mock_stat);
srs_freep(mock_source);
srs_freep(mock_hooks);
}
// Unit test for SrsForwarder RTMPS detection
VOID TEST(AppForwarderTest, RejectRtmpsDestination)
{
srs_error_t err;
// Create mock origin hub
MockLiveSourceForOriginHub *mock_source = new MockLiveSourceForOriginHub();
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
hub->source_ = mock_source;
// Create forwarder
SrsUniquePtr<SrsForwarder> forwarder(new SrsForwarder(hub.get()));
// Create mock request
SrsUniquePtr<MockHlsRequest> req(new MockHlsRequest());
// Test the hostport spliting
std::string server;
int port = SRS_CONSTS_RTMP_DEFAULT_PORT;
srs_net_split_hostport("rtmps://fake.demo.ossrs.io:443/app", server, port);
EXPECT_STREQ("rtmps://fake.demo.ossrs.io:443/app", server.c_str());
// Test 1: RTMPS URL should be rejected
HELPER_ASSERT_FAILED(forwarder->initialize(req.get(), "rtmps://fake.demo.ossrs.io:443/app"));
// Test 2: Plain RTMP URL should be accepted
HELPER_EXPECT_SUCCESS(forwarder->initialize(req.get(), "127.0.0.1:1935"));
// Cleanup
srs_freep(mock_source);
}
VOID TEST(SrsLiveSourceTest, OnAggregateSelectionTypical)
{
srs_error_t err;
// Create mock live source
SrsUniquePtr<MockLiveSourceForQueue> source(new MockLiveSourceForQueue());
// Create mock request
MockSrsRequest mock_req("test.vhost", "live", "stream1");
// Initialize source
SrsSharedPtr<SrsLiveSource> wrapper;
HELPER_EXPECT_SUCCESS(source->initialize(wrapper, &mock_req));
// Create aggregate message with both audio and video packets
// Aggregate message format:
// [type(1)][size(3)][timestamp(3)][timestamp_ext(1)][stream_id(3)][data][prev_tag_size(4)]
// Repeat for each sub-message
// Calculate sizes
int audio_data_size = 10;
int video_data_size = 20;
// Each sub-message: 1(type) + 3(size) + 3(ts) + 1(ts_ext) + 3(stream_id) + data + 4(prev_tag)
int audio_msg_size = 1 + 3 + 3 + 1 + 3 + audio_data_size + 4;
int video_msg_size = 1 + 3 + 3 + 1 + 3 + video_data_size + 4;
int total_size = audio_msg_size + video_msg_size;
char *payload = new char[total_size];
memset(payload, 0x00, total_size);
SrsBuffer buffer(payload, total_size);
// First sub-message: Audio (type=8)
buffer.write_1bytes(RTMP_MSG_AudioMessage);
buffer.write_3bytes(audio_data_size);
buffer.write_3bytes(1000); // timestamp (lower 24 bits)
buffer.write_1bytes(0); // timestamp extension (high 8 bits)
buffer.write_3bytes(0); // stream_id
// Audio data
for (int i = 0; i < audio_data_size; i++) {
buffer.write_1bytes(0xAA);
}
buffer.write_4bytes(audio_data_size + 11); // previous tag size
// Second sub-message: Video (type=9)
buffer.write_1bytes(RTMP_MSG_VideoMessage);
buffer.write_3bytes(video_data_size);
buffer.write_3bytes(2000); // timestamp (lower 24 bits)
buffer.write_1bytes(0); // timestamp extension (high 8 bits)
buffer.write_3bytes(0); // stream_id
// Video data
for (int i = 0; i < video_data_size; i++) {
buffer.write_1bytes(0xBB);
}
buffer.write_4bytes(video_data_size + 11); // previous tag size
// Create aggregate RTMP message
SrsRtmpCommonMessage aggregate_msg;
aggregate_msg.header_.message_type_ = RTMP_MSG_AggregateMessage;
aggregate_msg.header_.payload_length_ = total_size;
aggregate_msg.header_.timestamp_ = 3000;
aggregate_msg.header_.stream_id_ = 0;
aggregate_msg.create_payload(total_size);
memcpy(aggregate_msg.payload(), payload, total_size);
// Call on_aggregate - this should select and route to on_audio and on_video
HELPER_EXPECT_SUCCESS(source->on_aggregate(&aggregate_msg));
// Cleanup
delete[] payload;
}
MockAppFactoryForLiveSource::MockAppFactoryForLiveSource()
{
mock_hub_ = new MockOriginHub();
create_origin_hub_count_ = 0;
}
MockAppFactoryForLiveSource::~MockAppFactoryForLiveSource()
{
srs_freep(mock_hub_);
}
ISrsOriginHub *MockAppFactoryForLiveSource::create_origin_hub()
{
create_origin_hub_count_++;
// Return the mock hub and transfer ownership
ISrsOriginHub *hub = mock_hub_;
mock_hub_ = NULL;
return hub;
}
VOID TEST(SrsLiveSourceTest, InitializeOriginHubCreation)
{
srs_error_t err;
// Create mock config
MockAppConfig *mock_config = new MockAppConfig();
// Create mock factory
MockAppFactoryForLiveSource *mock_factory = new MockAppFactoryForLiveSource();
{
// Create live source
SrsLiveSource *source = new SrsLiveSource();
// Inject mock dependencies
source->config_ = mock_config;
source->app_factory_ = mock_factory;
// Create mock request
MockSrsRequest mock_req("test.vhost", "live", "stream1");
// Create wrapper for shared pointer - this takes ownership
SrsSharedPtr<SrsLiveSource> wrapper(source);
// Test typical origin server scenario (not edge)
// get_vhost_is_edge returns false by default in MockAppConfig
HELPER_EXPECT_SUCCESS(source->initialize(wrapper, &mock_req));
// Verify that factory was called to create origin hub
EXPECT_EQ(1, mock_factory->create_origin_hub_count_);
// Verify that hub was created and initialized
EXPECT_TRUE(source->hub_ != NULL);
}
// Wrapper is destroyed here, which deletes the source, before mock objects
// Cleanup
srs_freep(mock_config);
srs_freep(mock_factory);
}
VOID TEST(SrsLiveSourceTest, ConsumerDumpsTypicalScenario)
{
srs_error_t err;
// Create mock config
MockAppConfig *mock_config = new MockAppConfig();
// Create mock factory with hub
MockAppFactoryForLiveSource *mock_factory = new MockAppFactoryForLiveSource();
{
// Create live source
SrsLiveSource *source = new SrsLiveSource();
// Inject mock dependencies
source->config_ = mock_config;
source->app_factory_ = mock_factory;
// Create mock request
MockSrsRequest mock_req("test.vhost", "live", "stream1");
// Create wrapper for shared pointer
SrsSharedPtr<SrsLiveSource> wrapper(source);
// Set hub to active state to test the typical publishing scenario
mock_factory->mock_hub_ = new MockOriginHub();
source->hub_ = mock_factory->mock_hub_;
mock_factory->mock_hub_ = NULL;
// Initialize the source
HELPER_EXPECT_SUCCESS(source->initialize(wrapper, &mock_req));
// Create a consumer
SrsLiveConsumer *consumer = NULL;
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer));
// Test consumer_dumps with typical scenario (all parameters true)
HELPER_EXPECT_SUCCESS(source->consumer_dumps(consumer, true, true, true));
// Verify consumer was created
EXPECT_TRUE(consumer != NULL);
}
// Cleanup
srs_freep(mock_config);
srs_freep(mock_factory);
}
VOID TEST(SrsLiveSourceTest, OnMetaDataTypicalScenario)
{
srs_error_t err;
// Create mock config
MockAppConfig *mock_config = new MockAppConfig();
// Create mock factory with hub
MockAppFactoryForLiveSource *mock_factory = new MockAppFactoryForLiveSource();
{
// Create live source
SrsLiveSource *source = new SrsLiveSource();
// Inject mock dependencies
source->config_ = mock_config;
source->app_factory_ = mock_factory;
// Create mock request
MockSrsRequest mock_req("test.vhost", "live", "stream1");
// Create wrapper for shared pointer
SrsSharedPtr<SrsLiveSource> wrapper(source);
// Set hub to active state to test the typical publishing scenario
mock_factory->mock_hub_ = new MockOriginHub();
source->hub_ = mock_factory->mock_hub_;
mock_factory->mock_hub_ = NULL;
// Initialize the source
HELPER_EXPECT_SUCCESS(source->initialize(wrapper, &mock_req));
// Create a consumer
SrsLiveConsumer *consumer = NULL;
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer));
// Create a mock RTMP message for metadata
SrsUniquePtr<SrsRtmpCommonMessage> msg(new SrsRtmpCommonMessage());
msg->header_.initialize_amf0_script(128, 0);
msg->create_payload(128);
memset(msg->payload(), 0x00, 128);
// Create metadata packet with typical properties
SrsUniquePtr<SrsOnMetaDataPacket> metadata(new SrsOnMetaDataPacket());
metadata->metadata_->set("width", SrsAmf0Any::number(1920));
metadata->metadata_->set("height", SrsAmf0Any::number(1080));
metadata->metadata_->set("videocodecid", SrsAmf0Any::number(7)); // H.264
metadata->metadata_->set("audiocodecid", SrsAmf0Any::number(10)); // AAC
// Call on_meta_data and verify it succeeds
HELPER_EXPECT_SUCCESS(source->on_meta_data(msg.get(), metadata.get()));
// Verify that metadata was cached
EXPECT_TRUE(source->meta()->data() != NULL);
// Verify consumer was created
EXPECT_TRUE(consumer != NULL);
}
// Cleanup
srs_freep(mock_config);
srs_freep(mock_factory);
}
VOID TEST(GopCacheTest, ClearCacheWhenPureAudioOverflow)
{
srs_error_t err;
// Create a gop cache instance and enable it
SrsUniquePtr<SrsGopCache> gop_cache(new SrsGopCache());
gop_cache->set(true);
EXPECT_TRUE(gop_cache->enabled());
// Step 1: Cache a keyframe to establish that the stream has video
// This is critical - without video first, the stream would be detected as pure audio
// and caching would be disabled immediately
SrsUniquePtr<MockH264VideoPacket> keyframe(new MockH264VideoPacket(true));
keyframe->timestamp_ = 1000;
HELPER_EXPECT_SUCCESS(gop_cache->cache(keyframe.get()));
EXPECT_FALSE(gop_cache->empty());
EXPECT_FALSE(gop_cache->pure_audio()); // Stream has video, not pure audio
// Step 2: Cache a few inter frames to build up the GOP
SrsUniquePtr<MockH264VideoPacket> interframe1(new MockH264VideoPacket(false));
interframe1->timestamp_ = 1040;
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe1.get()));
SrsUniquePtr<MockH264VideoPacket> interframe2(new MockH264VideoPacket(false));
interframe2->timestamp_ = 1080;
HELPER_EXPECT_SUCCESS(gop_cache->cache(interframe2.get()));
// Step 3: Now simulate the scenario where video stops but audio continues
// This happens when a publisher disables their camera but keeps audio on
// We need to send MORE than SRS_PURE_AUDIO_GUESS_COUNT (115) audio packets
// to trigger the overflow detection and cache clearing
// Send exactly 115 audio packets - should NOT trigger clearing yet
for (int i = 0; i < 115; i++) {
SrsUniquePtr<MockAudioPacket> audio(new MockAudioPacket());
audio->timestamp_ = 1120 + (i * 26); // 26ms per audio packet (typical)
HELPER_EXPECT_SUCCESS(gop_cache->cache(audio.get()));
}
// At this point, audio_after_last_video_count_ should be exactly 115
// The cache should still have content (not cleared yet)
EXPECT_FALSE(gop_cache->empty());
// Step 4: Send ONE MORE audio packet to exceed the threshold
// This is the critical moment - audio_after_last_video_count_ becomes 116
// which is > SRS_PURE_AUDIO_GUESS_COUNT (115)
// The code should detect this as "pure audio overflow" and clear the cache
SrsUniquePtr<MockAudioPacket> overflow_audio(new MockAudioPacket());
overflow_audio->timestamp_ = 1120 + (115 * 26);
HELPER_EXPECT_SUCCESS(gop_cache->cache(overflow_audio.get()));
// Step 5: Verify that the cache was cleared due to pure audio overflow
// This is the key assertion - the cache should be empty after exceeding the threshold
EXPECT_TRUE(gop_cache->empty());
// Step 6: Verify that the stream is now detected as pure audio
// After clearing, cached_video_count_ is reset to 0
EXPECT_TRUE(gop_cache->pure_audio());
// Step 7: Verify that subsequent audio packets are NOT cached
// Once detected as pure audio, the cache should remain disabled
SrsUniquePtr<MockAudioPacket> post_clear_audio(new MockAudioPacket());
post_clear_audio->timestamp_ = 1120 + (116 * 26);
HELPER_EXPECT_SUCCESS(gop_cache->cache(post_clear_audio.get()));
EXPECT_TRUE(gop_cache->empty()); // Should still be empty
// Summary of what this test covers:
// 1. The "waiting" mechanism: audio_after_last_video_count_ incrementing with each audio packet
// 2. The threshold detection: exactly when count exceeds SRS_PURE_AUDIO_GUESS_COUNT (115)
// 3. The clearing action: gop cache is cleared when threshold is exceeded
// 4. The state transition: stream transitions from "has video" to "pure audio"
// 5. The prevention of future caching: once pure audio, no more caching occurs
}
// Unit test for SrsOriginHub::on_video sequence header handling
// This test covers the "waiting" mechanism for video sequence headers
VOID TEST(AppOriginHubTest, OnVideoSequenceHeaderWaitingMechanism)
{
srs_error_t err;
// Create mock source with shared pointer
MockLiveSourceForQueue *raw_source = new MockLiveSourceForQueue();
SrsSharedPtr<SrsLiveSource> source(raw_source);
// Create mock request
MockHlsRequest mock_req;
// Create origin hub and inject mock dependencies
SrsUniquePtr<SrsOriginHub> hub(new SrsOriginHub());
// Replace the default components with mocks
MockHlsForOriginHub *mock_hls = new MockHlsForOriginHub();
MockDashForOriginHub *mock_dash = new MockDashForOriginHub();
MockDvrForOriginHub *mock_dvr = new MockDvrForOriginHub();
MockStatisticForOriginHub *mock_stat = new MockStatisticForOriginHub();
// Access private members to inject mocks
srs_freep(hub->hls_);
hub->hls_ = mock_hls;
srs_freep(hub->dash_);
hub->dash_ = mock_dash;
srs_freep(hub->dvr_);
hub->dvr_ = mock_dvr;
hub->stat_ = mock_stat;
// Initialize the hub
HELPER_EXPECT_SUCCESS(hub->initialize(source, &mock_req));
// Create mock live source for origin hub with format
SrsUniquePtr<MockLiveSourceForOriginHub> mock_live_source(new MockLiveSourceForOriginHub());
hub->source_ = mock_live_source.get();
// Test 1: AVC (H.264) sequence header - the "waiting" for sequence header
// This tests the code path: if (format->is_avc_sequence_header()) { if (c->id_ == SrsVideoCodecIdAVC) { ... } }
{
// Create H.264 video sequence header packet
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->timestamp_ = 0;
video_sh->message_type_ = SrsFrameTypeVideo;
// Create minimal AVC sequence header packet
char *video_data = new char[10];
video_data[0] = 0x17; // keyframe (0x10) + AVC (0x07)
video_data[1] = 0x00; // AVC sequence header
memset(video_data + 2, 0x00, 8);
video_sh->wrap(video_data, 10);
// Manually set up the format state to simulate a parsed AVC sequence header
// This avoids the complex SPS/PPS parsing that would fail with invalid data
SrsRtmpFormat *format = mock_live_source->format();
// Initialize video codec config
if (!format->vcodec_) {
format->vcodec_ = new SrsVideoCodecConfig();
}
if (!format->video_) {
format->video_ = new SrsParsedVideoPacket();
}
// Set up as AVC sequence header
format->vcodec_->id_ = SrsVideoCodecIdAVC;
format->video_->frame_type_ = SrsVideoAvcFrameTypeKeyFrame;
format->video_->avc_packet_type_ = SrsVideoAvcFrameTraitSequenceHeader;
// Set codec parameters for testing
format->vcodec_->avc_profile_ = SrsAvcProfileBaseline;
format->vcodec_->avc_level_ = SrsAvcLevel_3;
format->vcodec_->width_ = 1920;
format->vcodec_->height_ = 1080;
format->vcodec_->video_data_rate_ = 2500000; // 2.5 Mbps
format->vcodec_->frame_rate_ = 30.0;
format->vcodec_->duration_ = 10.0;
// Verify format detected AVC sequence header
EXPECT_TRUE(format->is_avc_sequence_header());
EXPECT_TRUE(format->vcodec_ != NULL);
EXPECT_EQ(SrsVideoCodecIdAVC, format->vcodec_->id_);
// Call on_video - this is where the "waiting" ends and processing happens
// The code waits for is_avc_sequence_header() to be true before calling stat_->on_video_info()
HELPER_EXPECT_SUCCESS(hub->on_video(video_sh.get(), true));
// Verify stat_->on_video_info() was called with correct AVC parameters
EXPECT_EQ(1, mock_stat->on_video_info_count_);
}
// Test 2: HEVC (H.265) sequence header - the "waiting" for sequence header
// This tests the code path: if (format->is_avc_sequence_header()) { if (c->id_ == SrsVideoCodecIdHEVC) { ... } }
{
// Reset mock stat counter
mock_stat->on_video_info_count_ = 0;
// Create H.265 video sequence header packet using enhanced-RTMP format
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->timestamp_ = 0;
video_sh->message_type_ = SrsFrameTypeVideo;
char *video_data = new char[10];
video_data[0] = 0x90; // IsExHeader (0x80) | keyframe (0x10) | sequence start (0x00)
video_data[1] = 'h'; // fourcc 'hvc1'
video_data[2] = 'v';
video_data[3] = 'c';
video_data[4] = '1';
memset(video_data + 5, 0x00, 5);
video_sh->wrap(video_data, 10);
// Manually set up the format state to simulate a parsed HEVC sequence header
SrsRtmpFormat *format = mock_live_source->format();
// Initialize video codec config
if (!format->vcodec_) {
format->vcodec_ = new SrsVideoCodecConfig();
}
if (!format->video_) {
format->video_ = new SrsParsedVideoPacket();
}
// Set up as HEVC sequence header
format->vcodec_->id_ = SrsVideoCodecIdHEVC;
format->video_->frame_type_ = SrsVideoAvcFrameTypeKeyFrame;
format->video_->avc_packet_type_ = SrsVideoAvcFrameTraitSequenceHeader;
// Set codec parameters for testing
format->vcodec_->hevc_profile_ = SrsHevcProfileMain;
format->vcodec_->hevc_level_ = SrsHevcLevel_4;
format->vcodec_->width_ = 3840;
format->vcodec_->height_ = 2160;
format->vcodec_->video_data_rate_ = 10000000; // 10 Mbps
format->vcodec_->frame_rate_ = 60.0;
format->vcodec_->duration_ = 20.0;
// Verify format detected HEVC sequence header
EXPECT_TRUE(format->is_avc_sequence_header()); // Note: is_avc_sequence_header() also returns true for HEVC
EXPECT_TRUE(format->vcodec_ != NULL);
EXPECT_EQ(SrsVideoCodecIdHEVC, format->vcodec_->id_);
// Call on_video - this is where the "waiting" ends and processing happens
// The code waits for is_avc_sequence_header() to be true before calling stat_->on_video_info()
HELPER_EXPECT_SUCCESS(hub->on_video(video_sh.get(), true));
// Verify stat_->on_video_info() was called with correct HEVC parameters
EXPECT_EQ(1, mock_stat->on_video_info_count_);
}
// Summary of what this test covers:
// 1. The "waiting" mechanism: code waits for format->is_avc_sequence_header() to return true
// 2. How it waits: checks is_avc_sequence_header() condition before processing codec info
// 3. AVC path: when c->id_ == SrsVideoCodecIdAVC, calls stat_->on_video_info() with AVC profile/level
// 4. HEVC path: when c->id_ == SrsVideoCodecIdHEVC, calls stat_->on_video_info() with HEVC profile/level
// 5. The trace logging: srs_trace() is called with codec-specific information (profile, level, resolution, bitrate, fps, duration)
}