// // Copyright (c) 2013-2025 The SRS Authors // // SPDX-License-Identifier: MIT // #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; MockRtc2RtmpFrameTarget::MockRtc2RtmpFrameTarget() { on_frame_count_ = 0; last_frame_ = NULL; frame_error_ = srs_success; } MockRtc2RtmpFrameTarget::~MockRtc2RtmpFrameTarget() { reset(); } srs_error_t MockRtc2RtmpFrameTarget::on_frame(SrsMediaPacket *frame) { on_frame_count_++; // Store a copy of the frame for verification srs_freep(last_frame_); if (frame) { last_frame_ = frame->copy(); } return srs_error_copy(frame_error_); } void MockRtc2RtmpFrameTarget::reset() { on_frame_count_ = 0; srs_freep(last_frame_); srs_freep(frame_error_); } void MockRtc2RtmpFrameTarget::set_frame_error(srs_error_t err) { srs_freep(frame_error_); frame_error_ = srs_error_copy(err); } MockRtc2RtmpRequest::MockRtc2RtmpRequest(std::string vhost, std::string app, std::string stream) { vhost_ = vhost; app_ = app; stream_ = stream; host_ = "127.0.0.1"; } MockRtc2RtmpRequest::~MockRtc2RtmpRequest() { } ISrsRequest *MockRtc2RtmpRequest::copy() { MockRtc2RtmpRequest *cp = new MockRtc2RtmpRequest(); cp->vhost_ = vhost_; cp->app_ = app_; cp->stream_ = stream_; cp->host_ = host_; return cp; } std::string MockRtc2RtmpRequest::get_stream_url() { return "rtmp://" + host_ + "/" + app_ + "/" + stream_ + "?vhost=" + vhost_; } void MockRtc2RtmpRequest::update_auth(ISrsRequest *req) { } void MockRtc2RtmpRequest::strip() { } ISrsRequest *MockRtc2RtmpRequest::as_http() { return copy(); } // Helper function to create a mock NALU sample SrsNaluSample *mock_create_nalu_sample(const uint8_t *data, int size) { SrsNaluSample *sample = new SrsNaluSample(); sample->bytes_ = new char[size]; memcpy(sample->bytes_, data, size); sample->size_ = size; return sample; } // Helper function to create STAP-A RTP packet with SPS and PPS SrsRtpPacket *mock_create_stap_packet_with_sps_pps(uint16_t seq, uint32_t ts) { SrsRtpPacket *pkt = new SrsRtpPacket(); pkt->header_.set_ssrc(12345); pkt->header_.set_sequence(seq); pkt->header_.set_timestamp(ts); pkt->frame_type_ = SrsFrameTypeVideo; pkt->set_avsync_time(1000); pkt->nalu_type_ = kStapA; // Create STAP-A payload with SPS and PPS SrsRtpSTAPPayload *stap = new SrsRtpSTAPPayload(); // Mock SPS data (H.264 SPS NALU) uint8_t sps_data[] = {0x67, 0x42, 0x00, 0x1e, 0x9a, 0x66, 0x02, 0x80}; SrsNaluSample *sps = mock_create_nalu_sample(sps_data, sizeof(sps_data)); stap->nalus_.push_back(sps); // Mock PPS data (H.264 PPS NALU) uint8_t pps_data[] = {0x68, 0xce, 0x3c, 0x80}; SrsNaluSample *pps = mock_create_nalu_sample(pps_data, sizeof(pps_data)); stap->nalus_.push_back(pps); pkt->set_payload(stap, SrsRtpPacketPayloadTypeSTAP); return pkt; } // Helper function to create STAP-A RTP packet with SPS, PPS and small IDR frame SrsRtpPacket *mock_create_stap_packet_with_sps_pps_idr(uint16_t seq, uint32_t ts) { SrsRtpPacket *pkt = new SrsRtpPacket(); pkt->header_.set_ssrc(12345); pkt->header_.set_sequence(seq); pkt->header_.set_timestamp(ts); pkt->frame_type_ = SrsFrameTypeVideo; pkt->set_avsync_time(1000); pkt->nalu_type_ = kStapA; pkt->header_.set_marker(true); // Single packet contains complete frame // Create STAP-A payload with SPS, PPS and small IDR SrsRtpSTAPPayload *stap = new SrsRtpSTAPPayload(); // Mock SPS data (H.264 SPS NALU) uint8_t sps_data[] = {0x67, 0x42, 0x00, 0x1e, 0x9a, 0x66, 0x02, 0x80}; SrsNaluSample *sps = mock_create_nalu_sample(sps_data, sizeof(sps_data)); stap->nalus_.push_back(sps); // Mock PPS data (H.264 PPS NALU) uint8_t pps_data[] = {0x68, 0xce, 0x3c, 0x80}; SrsNaluSample *pps = mock_create_nalu_sample(pps_data, sizeof(pps_data)); stap->nalus_.push_back(pps); // Mock small IDR data (~200 bytes) uint8_t idr_data[200]; idr_data[0] = 0x65; // IDR NALU header for (int i = 1; i < 200; i++) { idr_data[i] = (uint8_t)(0x10 + (i % 128)); } SrsNaluSample *idr = mock_create_nalu_sample(idr_data, sizeof(idr_data)); stap->nalus_.push_back(idr); pkt->set_payload(stap, SrsRtpPacketPayloadTypeSTAP); return pkt; } // Helper function to create IDR frame RTP packet SrsRtpPacket *mock_create_idr_packet(uint16_t seq, uint32_t ts, bool marker) { SrsRtpPacket *pkt = new SrsRtpPacket(); pkt->header_.set_ssrc(12345); pkt->header_.set_sequence(seq); pkt->header_.set_timestamp(ts); pkt->frame_type_ = SrsFrameTypeVideo; pkt->set_avsync_time(1000); pkt->nalu_type_ = SrsAvcNaluTypeIDR; // Set marker bit if last packet for a frame. pkt->header_.set_marker(marker); // Create raw payload with IDR data SrsRtpRawPayload *raw = new SrsRtpRawPayload(); char *payload_data = pkt->wrap(200); // Fill with mock IDR data payload_data[0] = 0x65; // IDR NALU header for (int i = 1; i < 200; i++) { payload_data[i] = (char)(0x10 + (i % 128)); } raw->payload_ = payload_data; raw->nn_payload_ = 200; pkt->set_payload(raw, SrsRtpPacketPayloadTypeRaw); return pkt; } // Helper function to create P frame RTP packet SrsRtpPacket *mock_create_p_frame_packet(uint16_t seq, uint32_t ts, bool marker) { SrsRtpPacket *pkt = new SrsRtpPacket(); pkt->header_.set_ssrc(12345); pkt->header_.set_sequence(seq); pkt->header_.set_timestamp(ts); pkt->frame_type_ = SrsFrameTypeVideo; pkt->set_avsync_time(2000); // Different timestamp for P frame pkt->nalu_type_ = SrsAvcNaluTypeNonIDR; // Set marker bit for single packet frame pkt->header_.set_marker(marker); // Create raw payload with P frame data SrsRtpRawPayload *raw = new SrsRtpRawPayload(); char *payload_data = pkt->wrap(150); // Fill with mock P frame data payload_data[0] = 0x41; // P frame NALU header for (int i = 1; i < 150; i++) { payload_data[i] = (char)(0x20 + (i % 100)); } raw->payload_ = payload_data; raw->nn_payload_ = 150; pkt->set_payload(raw, SrsRtpPacketPayloadTypeRaw); return pkt; } // Helper function to create empty IDR frame RTP packet SrsRtpPacket *mock_create_empty_idr_packet(uint16_t seq, uint32_t ts, bool marker) { SrsRtpPacket *pkt = new SrsRtpPacket(); pkt->header_.set_ssrc(12345); pkt->header_.set_sequence(seq); pkt->header_.set_timestamp(ts); pkt->frame_type_ = SrsFrameTypeVideo; pkt->set_avsync_time(1000); pkt->nalu_type_ = SrsAvcNaluTypeIDR; // Set marker bit if last packet for a frame. pkt->header_.set_marker(marker); // Create raw payload with empty IDR data (only NALU header) SrsRtpRawPayload *raw = new SrsRtpRawPayload(); char *payload_data = pkt->wrap(1); // Only IDR NALU header, no payload data payload_data[0] = 0x65; // IDR NALU header raw->payload_ = payload_data; raw->nn_payload_ = 1; pkt->set_payload(raw, SrsRtpPacketPayloadTypeRaw); return pkt; } // Helper function to create empty IDR frame RTP packet using FU-A format SrsRtpPacket *mock_create_empty_idr_packet_fua(uint16_t seq, uint32_t ts) { SrsRtpPacket *pkt = new SrsRtpPacket(); pkt->header_.set_ssrc(12345); pkt->header_.set_sequence(seq); pkt->header_.set_timestamp(ts); pkt->frame_type_ = SrsFrameTypeVideo; pkt->set_avsync_time(1000); pkt->nalu_type_ = kFuA; // Set marker bit if last packet for a frame. pkt->header_.set_marker(true); // Create FU-A payload with empty IDR data SrsRtpFUAPayload2 *fua = new SrsRtpFUAPayload2(); fua->start_ = true; // This is a complete single fragment fua->end_ = true; // This is a complete single fragment fua->nri_ = SrsAvcNaluTypeIDR; // NRI bits from original IDR NALU header (0x65) fua->nalu_type_ = SrsAvcNaluTypeIDR; // IDR NALU type // Empty payload - no actual IDR data, just the fragmentation headers fua->payload_ = NULL; fua->size_ = 0; pkt->set_payload(fua, SrsRtpPacketPayloadTypeFUA2); return pkt; } // Helper function to create audio RTP packet SrsRtpPacket *mock_create_audio_packet(uint16_t seq, uint32_t ts, uint32_t avsync_time) { SrsRtpPacket *pkt = new SrsRtpPacket(); pkt->header_.set_ssrc(12345); pkt->header_.set_sequence(seq); pkt->header_.set_timestamp(ts); pkt->frame_type_ = SrsFrameTypeAudio; pkt->set_avsync_time(avsync_time); SrsRtpRawPayload *raw = new SrsRtpRawPayload(); char audio_data[64]; memset(audio_data, seq & 0xFF, sizeof(audio_data)); raw->payload_ = pkt->wrap(sizeof(audio_data)); memcpy(raw->payload_, audio_data, sizeof(audio_data)); raw->nn_payload_ = sizeof(audio_data); pkt->set_payload(raw, SrsRtpPacketPayloadTypeRaw); return pkt; } // Test SrsRtcFrameBuilder::packet_video with the simplest video sequence: // 1. STAP-A packet with SPS/PPS // 2. IDR frame (2 RTP packets), with same timestamp to SPS/PPS. // 3. P frame (1 RTP packet) // No packet loss scenario // // This is the most typical packet sequence to Chrome. Although SPS/PPS is in the next // RTP packet to IDR, but they share the same timestamp, because they should be in the // same RTP packet, but just for IDR is huge so Chrome put it in the next packet. // // Altough SPS/PPS and IDR is in two RTP packets, and they share the same timestamp, but // we should only packet the IDR to frame. That is the second frame contains only IDR, // this can be verified by the frame size. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_SameKeyframeTimestampVideoSequence) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send IDR frame as 2 RTP packets // First packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt1(mock_create_idr_packet(101, 90000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // Second packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt2(mock_create_idr_packet(102, 90000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // Should generate second frame for IDR EXPECT_EQ(2, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); // As each IDR is about 200 bytes, SPS/PPS is about 20 bytes, the frame // contains 2xIDR should be around 400 bytes EXPECT_GT(target.last_frame_->payload_->size(), 400); EXPECT_LT(target.last_frame_->payload_->size(), 420); } // 3. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(103, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should generate third frame for P frame EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with STAP-A containing SPS/PPS/IDR: // 1. STAP-A packet with SPS/PPS/IDR (small ~200 bytes IDR frame) // 2. P frame (1 RTP packet) // No packet loss scenario // // This tests the case where SPS/PPS/IDR are all in the same RTP packet, // with a small IDR frame that fits within the STAP-A packet. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_SameStapForIdrVideoSequence) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS/IDR (sequence header + keyframe) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps_idr(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // For single packet IDR frame, we don't know whether it's really a single packet or // reordering packet, for example, it may be one of following cases: // Single packet of a IDR frame: // IDR (marker bit = true) seq=100 // Two packets of a IDR frame, this is the reordering case: // IDR (marker bit = true) seq=100 // IDR (marker bit = false) seq=99 // Should be triggered by next frame. EXPECT_EQ(1, target.on_frame_count_); // 2. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(101, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should generate 2 frames for previous IDR and this P frame EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the simplest video sequence: // 1. STAP-A packet with SPS/PPS // 2. IDR frame (2 RTP packets), with different timestamp to SPS/PPS. // 3. P frame (1 RTP packet) // No packet loss scenario // // If the IDR is a single RTP packet with different timestamp, after SPS/PPS be packeted // as a frame, IDR will also be packeted as another frame. This can be verified by the // frame size. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_DifferentKeyframeTimestampVideoSequence) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send IDR frame as 2 RTP packets // First packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt1(mock_create_idr_packet(101, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // Second packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt2(mock_create_idr_packet(102, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // Should generate second frame for IDR EXPECT_EQ(2, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); // As each IDR is about 200 bytes, SPS/PPS is about 20 bytes, the frame contains // 2xIDR should be around 400 bytes. EXPECT_GT(target.last_frame_->payload_->size(), 400); EXPECT_LT(target.last_frame_->payload_->size(), 420); } // 3. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(103, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should generate third frame for P frame EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with typical video sequence: // 1. STAP-A packet with SPS/PPS // 2. IDR frame (3 RTP packets), with same timestamp to SPS/PPS. // 3. P frame (1 RTP packet) // No packet loss scenario VOID TEST(Rtc2RtmpConvertTest, PacketVideo_TypicalVideoSequence) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send IDR frame as 3 RTP packets // First packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt1(mock_create_idr_packet(101, 90000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // Second packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt2(mock_create_idr_packet(102, 90000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // Second packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt3(mock_create_idr_packet(103, 90000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt3.get())); // Should generate second frame for IDR EXPECT_EQ(2, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 3. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(104, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should generate third frame for P frame EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the simplest video sequence: // 1. STAP-A packet with SPS/PPS // 2. IDR frame (1 RTP packets), with same timestamp to SPS/PPS. // 3. P frame (1 RTP packet) // No packet loss scenario VOID TEST(Rtc2RtmpConvertTest, PacketVideo_SingleVideoSequence) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send IDR frame as 1 RTP packets // First packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt1(mock_create_idr_packet(101, 90000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should generate second frame for IDR EXPECT_EQ(2, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 3. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(102, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should generate third frame for P frame EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the simplest video sequence but large P frame: // 1. STAP-A packet with SPS/PPS // 2. IDR frame (1 RTP packets), with same timestamp to SPS/PPS. // 3. P frame (3 RTP packets) - large P frame requiring multiple packets // No packet loss scenario VOID TEST(Rtc2RtmpConvertTest, PacketVideo_SingleVideoSequence_LargePFrame) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send IDR frame as 1 RTP packets // First packet of IDR frame (marker bit = true) SrsUniquePtr idr_pkt1(mock_create_idr_packet(101, 90000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should generate second frame for IDR EXPECT_EQ(2, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 3. Send P frame as 3 RTP packets (large P frame) // First packet of P frame (marker bit = false) SrsUniquePtr p_pkt1(mock_create_p_frame_packet(102, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(2, target.on_frame_count_); // Second packet of P frame (marker bit = false) SrsUniquePtr p_pkt2(mock_create_p_frame_packet(103, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt2.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(2, target.on_frame_count_); // Third packet of P frame (marker bit = true, completes frame) SrsUniquePtr p_pkt3(mock_create_p_frame_packet(104, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt3.get())); // Should generate third frame for P frame EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the simplest video sequence: // 1. STAP-A packet with SPS/PPS // 2. IDR frame (1 RTP packets) // 3. P frame (1 RTP packet) // No packet loss scenario // // Normally the SPS/PPS should be the same timestamp as the IDR frame, but this is not // required. It's acceptable if the IDR RTP packet has a different timestamp. This is // useful when testing some empty NALU case, without the SPS/PPS, only IDR packet. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_AcceptableVideoSequence) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send IDR frame as 1 RTP packets // First packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt1(mock_create_idr_packet(101, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // For single packet IDR frame, we don't know whether it's really a single packet or // reordering packet, for example, it may be one of following cases: // Single packet of a IDR frame: // IDR (marker bit = true) seq=100 // Two packets of a IDR frame, this is the reordering case: // IDR (marker bit = true) seq=100 // IDR (marker bit = false) seq=99 // Should be triggered by next frame. EXPECT_EQ(1, target.on_frame_count_); // 3. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(102, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should generate 2 frames for previous IDR and this P frame EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with empty IDR packet before normal frames: // 1. STAP-A packet with SPS/PPS // 2. Empty IDR frame (1 RTP packet with only NALU header) in RAW payload. // 3. Normal IDR frame (1 RTP packet) // 4. P frame (1 RTP packet) // No packet loss scenario // // This tests the case where an empty IDR packet is received before the actual // IDR frame, which can happen in some streaming scenarios. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_EmptyIDRBeforeNormalFrames) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send empty IDR frame (only NALU header, no payload data) SrsUniquePtr empty_idr_pkt(mock_create_empty_idr_packet(101, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(empty_idr_pkt.get())); // Should not generate frame yet (ignore empty frame) EXPECT_EQ(1, target.on_frame_count_); // 3. Send normal IDR frame as single RTP packet SrsUniquePtr idr_pkt(mock_create_idr_packet(102, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt.get())); // Should be triggered by next frame. EXPECT_EQ(1, target.on_frame_count_); // 4. Send P frame as single RTP packet SrsUniquePtr p_pkt(mock_create_p_frame_packet(103, 360000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should generate 2 frames for previous IDR and this P frame EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with empty IDR packet before normal frames: // 1. STAP-A packet with SPS/PPS // 2. Empty IDR frame (1 RTP packet with only NALU header) in FU-A payload. // 3. Normal IDR frame (1 RTP packet) // 4. P frame (1 RTP packet) // No packet loss scenario // // This tests the case where an empty IDR packet is received before the actual // IDR frame, which can happen in some streaming scenarios. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_EmptyIDRBeforeNormalFrames_FUA) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send empty IDR frame (only NALU header, no payload data) SrsUniquePtr empty_idr_pkt(mock_create_empty_idr_packet_fua(101, 180000)); HELPER_EXPECT_SUCCESS(builder.packet_video(empty_idr_pkt.get())); // Should not generate frame yet (ignore empty frame) EXPECT_EQ(1, target.on_frame_count_); // 3. Send normal IDR frame as single RTP packet SrsUniquePtr idr_pkt(mock_create_idr_packet(102, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt.get())); // Should be triggered by next frame. EXPECT_EQ(1, target.on_frame_count_); // 4. Send P frame as single RTP packet SrsUniquePtr p_pkt(mock_create_p_frame_packet(103, 360000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should generate 2 frames for previous IDR and this P frame EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the packet-reordering video sequence: // 1. STAP-A packet with SPS/PPS // 2. IDR frame (2 RTP packets) // 3. P frame (2 RTP packets) // No packet loss scenario // // This is the baseline case, for IDR with 2 RTP packets, and P frame with 2 RTP packets. // This is useful to conduct the packet reordering or lost tests. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_BaselinePackets) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send IDR frame as 2 RTP packets // First packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt1(mock_create_idr_packet(101, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // Second packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt2(mock_create_idr_packet(102, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // First packet of I frame. EXPECT_EQ(2, target.on_frame_count_); // 3. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(103, 270000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(2, target.on_frame_count_); // Second packet of P frame. SrsUniquePtr p_pkt2(mock_create_p_frame_packet(104, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt2.get())); // Should generate P frame. EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the packet-reordering video sequence: // 1. IDR frame (2 RTP packets) // 2. P frame (2 RTP packets) // No packet loss scenario // // This is the bad case, no SPS/PPS, only IDR frame and P frame. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_NoSpsPpsPackets) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send IDR frame as 2 RTP packets // First packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt1(mock_create_idr_packet(101, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(0, target.on_frame_count_); // Second packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt2(mock_create_idr_packet(102, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // First packet of I frame. EXPECT_EQ(1, target.on_frame_count_); // 2. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(103, 270000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // Second packet of P frame. SrsUniquePtr p_pkt2(mock_create_p_frame_packet(104, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt2.get())); // Should generate P frame. EXPECT_EQ(2, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the packet-reordering video sequence: // 1. STAP-A packet with SPS/PPS // 2. IDR frame (2 RTP packets) // 3. P frame (2 RTP packets) // No packet loss scenario // // For the 2 RTP packets of IDR frame, they are in reordering, so the second packet // comes before the first packet. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_ReorderingIdrPackets) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send IDR frame as 2 RTP packets // First packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt1(mock_create_idr_packet(102, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // Second packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt2(mock_create_idr_packet(101, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // 3. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(103, 270000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should have triggered the previous IDR frame. EXPECT_EQ(2, target.on_frame_count_); // Second packet of P frame SrsUniquePtr p_pkt2(mock_create_p_frame_packet(104, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt2.get())); // Should generate P frame. EXPECT_EQ(3, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the packet-reordering video sequence: // 1. STAP-A packet with SPS/PPS // 2. P frame (2 RTP packets) // 3. IDR frame (2 RTP packets) // No packet loss scenario // // This case makes the P frame before IDR frame. It should work in normal state. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_PframeBeforeIdrPackets) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(101, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // Second packet of P frame. SrsUniquePtr p_pkt2(mock_create_p_frame_packet(102, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt2.get())); // Should generate P frame. EXPECT_EQ(2, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 3. Send IDR frame as 2 RTP packets // First packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt1(mock_create_idr_packet(103, 270000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(2, target.on_frame_count_); // Second packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt2(mock_create_idr_packet(104, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // Should generate I frame. EXPECT_EQ(3, target.on_frame_count_); // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the packet-reordering video sequence: // 1. STAP-A packet with SPS/PPS // 2. P frame (2 RTP packets), the second packet arrive after IDR frame. // 3. IDR frame (2 RTP packets) // No packet loss scenario // // If P frame is before IDR frame, and the P frame is reordering, we should get both // IDR and P frame. But in fact, the IDR frame will clear the P frame. // RTP(100, SPS/PPS) // got_frame=1 // RTP(101, P frame, marker=0). // got_frame=1 // RTP(103, IDR frame, marker=0) // reordering, got_frame=1 // RTP(102, P frame, marker=1) // reordering, got_frame=1, should be 2. // RTP(104, IDR frame, marker=1) // got_frame=2, should be 3. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_PframeReorderingPackets) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(101, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // 3. Send IDR frame as 2 RTP packets // First packet of IDR frame (marker bit = false) // This is the reordering packet, arrive before the previous P frame. SrsUniquePtr idr_pkt1(mock_create_idr_packet(103, 270000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // ---> Reordering P frame arrived, the second packet of P frame. SrsUniquePtr p_pkt2(mock_create_p_frame_packet(102, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt2.get())); // Should generate P frame, but right now it's cleared by IDR. EXPECT_EQ(1, target.on_frame_count_); // Second packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt2(mock_create_idr_packet(104, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // Should generate I frame. EXPECT_EQ(2, target.on_frame_count_); // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the packet-reordering video sequence: // 1. STAP-A packet with SPS/PPS // 2. P frame (2 RTP packets), the second packet arrive after IDR frame. // 3. IDR frame (2 RTP packets) // 4. P frame (1 RTP packet), used to trigger the IDR frame. // No packet loss scenario // // If P frame is before IDR frame, and the P frame is reordering, we should get both // IDR and P frame. But in fact, the IDR frame will clear the P frame. // RTP(100, SPS/PPS) // got_frame=1 // RTP(101, P frame, marker=0). // got_frame=1 // RTP(104, IDR frame, marker=1) // reordering, got_frame=1. // RTP(102, P frame, marker=1) // reordering, got_frame=1, should be 2. // RTP(103, IDR frame, marker=0) // reordering, got_frame=1, should be 2. // RTP(105, P frame, marker=1) // trigger, got_frame=3, should be 4. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_IdrPframeReorderingPackets) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(101, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // 3. Send IDR frame as 2 RTP packets // First packet of IDR frame (marker bit = true, completes frame) // This is the reordering packet, arrive before the previous P frame. SrsUniquePtr idr_pkt1(mock_create_idr_packet(104, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // ---> Reordering P frame arrived, the second packet of P frame. SrsUniquePtr p_pkt2(mock_create_p_frame_packet(102, 180000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt2.get())); // Should generate P frame, but right now it's cleared by IDR. EXPECT_EQ(1, target.on_frame_count_); // Second packet of IDR frame (marker bit = false) SrsUniquePtr idr_pkt2(mock_create_idr_packet(103, 270000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // Should generate I frame. EXPECT_EQ(1, target.on_frame_count_); // 4. Send P frame as single RTP packet SrsUniquePtr p2_pkt(mock_create_p_frame_packet(105, 360000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(p2_pkt.get())); // Should generate 2 frames for previous IDR and this P frame EXPECT_EQ(3, target.on_frame_count_); // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_video with the packet-reordering video sequence: // 1. STAP-A packet with SPS/PPS // 2. P frame (2 RTP packets), the second packet arrive after IDR frame. // 3. IDR frame (2 RTP packets) // No packet loss scenario // // If P frame is before IDR frame, and the P frame is lost, we should get both // IDR and P frame (NACK). But in fact, the IDR frame will clear the P frame. // RTP(100, SPS/PPS) // got_frame=1 // RTP(101, P frame, marker=0). // got_frame=1 // RTP(102, P frame, marker=1) // <---- LOST // RTP(103, IDR frame, marker=0) // got_frame=1, should trigger NACK. // RTP(104, IDR frame, marker=1) // got_frame=2, should be 3. VOID TEST(Rtc2RtmpConvertTest, PacketVideo_PframeLostPackets) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with AVC codec SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC)); // Reset frame count before test target.reset(); // 1. Send STAP-A packet containing SPS/PPS (sequence header) SrsUniquePtr stap_pkt(mock_create_stap_packet_with_sps_pps(100, 90000)); HELPER_EXPECT_SUCCESS(builder.packet_video(stap_pkt.get())); // Should generate one frame for sequence header EXPECT_EQ(1, target.on_frame_count_); EXPECT_TRUE(target.last_frame_ != NULL); if (target.last_frame_) { EXPECT_EQ(SrsFrameTypeVideo, target.last_frame_->message_type_); } // 2. Send P frame as single RTP packet (marker bit = true) SrsUniquePtr p_pkt(mock_create_p_frame_packet(101, 180000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(p_pkt.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // 3. Send IDR frame as 2 RTP packets // First packet of IDR frame (marker bit = false) // This is the reordering packet, arrive before the previous P frame. SrsUniquePtr idr_pkt1(mock_create_idr_packet(103, 270000, false)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt1.get())); // Should not generate frame yet (waiting for complete frame) EXPECT_EQ(1, target.on_frame_count_); // Second packet of IDR frame (marker bit = true, completes frame) SrsUniquePtr idr_pkt2(mock_create_idr_packet(104, 270000, true)); HELPER_EXPECT_SUCCESS(builder.packet_video(idr_pkt2.get())); // Should generate I frame. EXPECT_EQ(2, target.on_frame_count_); // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_audio with 3 audio RTP packets: // 1. First audio packet // 2. Second audio packet // 3. Third audio packet // No packet loss scenario // // This tests the basic audio packet processing through the audio cache // and transcoding pipeline. All packets should be processed successfully // using a mock audio transcoder. VOID TEST(Rtc2RtmpConvertTest, PacketAudio_ThreeAudioPackets) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with Opus codec (typical for WebRTC) SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdOpus, SrsVideoCodecIdAVC)); // Replace the audio transcoder with our mock to avoid FFmpeg issues MockAudioTranscoder *mock_transcoder = new MockAudioTranscoder(); mock_transcoder->set_output_packets(1); // Each input packet produces 1 output packet // Access private member through friendship (utests have access to private members) builder.audio_transcoder_ = mock_transcoder; // Reset frame count before test target.reset(); // 1. Send first audio packet SrsUniquePtr audio_pkt1(mock_create_audio_packet(100, 48000, 1000)); HELPER_EXPECT_SUCCESS(builder.packet_audio(audio_pkt1.get())); // Should generate at least one frame (sequence header + data frame) EXPECT_EQ(target.on_frame_count_, 2); // 2. Send second audio packet SrsUniquePtr audio_pkt2(mock_create_audio_packet(101, 48960, 1020)); HELPER_EXPECT_SUCCESS(builder.packet_audio(audio_pkt2.get())); // Should generate at least one frame (data frame) EXPECT_EQ(target.on_frame_count_, 3); // 3. Send third audio packet SrsUniquePtr audio_pkt3(mock_create_audio_packet(102, 49920, 1040)); HELPER_EXPECT_SUCCESS(builder.packet_audio(audio_pkt3.get())); // Should generate at least one frame (data frame) EXPECT_EQ(target.on_frame_count_, 4); // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); } // Test SrsRtcFrameBuilder::packet_audio with 3 audio RTP packets: // 1. First audio packet // 2. Third audio packet (reordering) // 3. Second audio packet // No packet loss scenario // // This tests the audio packet reordering through the audio cache. VOID TEST(Rtc2RtmpConvertTest, PacketAudio_ReorderingAudioPackets) { srs_error_t err; MockRtc2RtmpFrameTarget target; SrsRtcFrameBuilder builder(&target); // Initialize the builder with Opus codec (typical for WebRTC) SrsUniquePtr req(new MockRtc2RtmpRequest()); HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdOpus, SrsVideoCodecIdAVC)); // Replace the audio transcoder with our mock to avoid FFmpeg issues MockAudioTranscoder *mock_transcoder = new MockAudioTranscoder(); mock_transcoder->set_output_packets(1); // Each input packet produces 1 output packet // Access private member through friendship (utests have access to private members) builder.audio_transcoder_ = mock_transcoder; // Reset frame count before test target.reset(); // 1. Send first audio packet SrsUniquePtr audio_pkt1(mock_create_audio_packet(100, 48000, 1000)); HELPER_EXPECT_SUCCESS(builder.packet_audio(audio_pkt1.get())); // Should generate at least one frame (sequence header + data frame) EXPECT_EQ(target.on_frame_count_, 2); // 2. Send third audio packet SrsUniquePtr audio_pkt3(mock_create_audio_packet(102, 49920, 1040)); HELPER_EXPECT_SUCCESS(builder.packet_audio(audio_pkt3.get())); // Should generate at least one frame (data frame) EXPECT_EQ(target.on_frame_count_, 2); // 3. Send second audio packet SrsUniquePtr audio_pkt2(mock_create_audio_packet(101, 48960, 1020)); HELPER_EXPECT_SUCCESS(builder.packet_audio(audio_pkt2.get())); // Should generate at least one frame (data frame) EXPECT_EQ(target.on_frame_count_, 4); // Verify no errors occurred during processing EXPECT_TRUE(target.frame_error_ == srs_success); }