2770 lines
94 KiB
C++
2770 lines
94 KiB
C++
//
|
|
// Copyright (c) 2013-2025 The SRS Authors
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
#include <srs_utest_protocol3.hpp>
|
|
|
|
using namespace std;
|
|
|
|
#include <srs_app_listener.hpp>
|
|
#include <srs_app_st.hpp>
|
|
#include <srs_core_autofree.hpp>
|
|
#include <srs_kernel_buffer.hpp>
|
|
#include <srs_kernel_codec.hpp>
|
|
#include <srs_kernel_error.hpp>
|
|
#include <srs_kernel_utility.hpp>
|
|
#include <srs_protocol_amf0.hpp>
|
|
#include <srs_protocol_conn.hpp>
|
|
#include <srs_protocol_format.hpp>
|
|
#include <srs_protocol_http_client.hpp>
|
|
#include <srs_protocol_http_conn.hpp>
|
|
#include <srs_protocol_io.hpp>
|
|
#include <srs_protocol_json.hpp>
|
|
#include <srs_protocol_log.hpp>
|
|
#include <srs_protocol_protobuf.hpp>
|
|
#include <srs_protocol_raw_avc.hpp>
|
|
#include <srs_protocol_rtc_stun.hpp>
|
|
#include <srs_protocol_rtmp_conn.hpp>
|
|
#include <srs_protocol_rtmp_msg_array.hpp>
|
|
#include <srs_protocol_rtmp_stack.hpp>
|
|
#include <srs_protocol_rtp.hpp>
|
|
#include <srs_protocol_sdp.hpp>
|
|
#include <srs_protocol_st.hpp>
|
|
#include <srs_protocol_stream.hpp>
|
|
#include <srs_protocol_utility.hpp>
|
|
|
|
extern bool srs_is_valid_jsonp_callback(std::string callback);
|
|
extern uint32_t srs_crc32_ieee(const void *buf, int size, uint32_t previous);
|
|
|
|
VOID TEST(ProtocolHttpTest, JsonpCallbackName)
|
|
{
|
|
EXPECT_TRUE(srs_is_valid_jsonp_callback(""));
|
|
EXPECT_TRUE(srs_is_valid_jsonp_callback("callback"));
|
|
EXPECT_TRUE(srs_is_valid_jsonp_callback("Callback"));
|
|
EXPECT_TRUE(srs_is_valid_jsonp_callback("Callback1234567890"));
|
|
EXPECT_TRUE(srs_is_valid_jsonp_callback("Callback-1234567890"));
|
|
EXPECT_TRUE(srs_is_valid_jsonp_callback("Callback_1234567890"));
|
|
EXPECT_TRUE(srs_is_valid_jsonp_callback("Callback.1234567890"));
|
|
EXPECT_TRUE(srs_is_valid_jsonp_callback("Callback1234567890-_."));
|
|
EXPECT_FALSE(srs_is_valid_jsonp_callback("callback()//"));
|
|
EXPECT_FALSE(srs_is_valid_jsonp_callback("callback!"));
|
|
EXPECT_FALSE(srs_is_valid_jsonp_callback("callback;"));
|
|
}
|
|
|
|
// Mock class implementations
|
|
MockConnection::MockConnection(std::string ip) : ip_(ip)
|
|
{
|
|
}
|
|
|
|
MockConnection::~MockConnection()
|
|
{
|
|
}
|
|
|
|
std::string MockConnection::remote_ip()
|
|
{
|
|
return ip_;
|
|
}
|
|
|
|
std::string MockConnection::desc()
|
|
{
|
|
return "MockConnection";
|
|
}
|
|
|
|
const SrsContextId &MockConnection::get_id()
|
|
{
|
|
static SrsContextId id = SrsContextId();
|
|
return id;
|
|
}
|
|
|
|
MockExpire::MockExpire() : expired_(false)
|
|
{
|
|
}
|
|
|
|
MockExpire::~MockExpire()
|
|
{
|
|
}
|
|
|
|
void MockExpire::expire()
|
|
{
|
|
expired_ = true;
|
|
}
|
|
|
|
VOID TEST(ProtocolConnTest, ISrsConnectionInterface)
|
|
{
|
|
MockConnection conn("192.168.1.100");
|
|
EXPECT_STREQ("192.168.1.100", conn.remote_ip().c_str());
|
|
EXPECT_STREQ("MockConnection", conn.desc().c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolConnTest, ISrsExpireInterface)
|
|
{
|
|
MockExpire expire;
|
|
EXPECT_FALSE(expire.expired_);
|
|
|
|
expire.expire();
|
|
EXPECT_TRUE(expire.expired_);
|
|
}
|
|
|
|
VOID TEST(ProtocolIOTest, ISrsProtocolReaderInterface)
|
|
{
|
|
// Test that interfaces are properly defined
|
|
// These are abstract classes, so we just verify they exist
|
|
ISrsProtocolReader *reader = NULL;
|
|
ISrsProtocolWriter *writer = NULL;
|
|
ISrsProtocolReadWriter *rw = NULL;
|
|
|
|
// Just verify the pointers can be assigned
|
|
EXPECT_TRUE(reader == NULL);
|
|
EXPECT_TRUE(writer == NULL);
|
|
EXPECT_TRUE(rw == NULL);
|
|
}
|
|
|
|
VOID TEST(ProtocolFormatTest, SrsRtmpFormatBasic)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRtmpFormat format;
|
|
|
|
// Test metadata handling
|
|
SrsOnMetaDataPacket *meta = new SrsOnMetaDataPacket();
|
|
SrsUniquePtr<SrsOnMetaDataPacket> meta_uptr(meta);
|
|
|
|
HELPER_EXPECT_SUCCESS(format.on_metadata(meta));
|
|
}
|
|
|
|
VOID TEST(ProtocolFormatTest, SrsRtmpFormatAudioVideo)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRtmpFormat format;
|
|
|
|
// Test audio packet handling
|
|
if (true) {
|
|
SrsMediaPacket *audio = new SrsMediaPacket();
|
|
SrsUniquePtr<SrsMediaPacket> audio_uptr(audio);
|
|
|
|
char *audio_data = new char[6];
|
|
audio_data[0] = 0xaf;
|
|
audio_data[1] = 0x01;
|
|
audio_data[2] = 0x00;
|
|
audio_data[3] = 0x01;
|
|
audio_data[4] = 0x02;
|
|
audio_data[5] = 0x03;
|
|
audio->wrap(audio_data, 6);
|
|
audio->timestamp_ = 1000;
|
|
audio->message_type_ = SrsFrameTypeAudio;
|
|
|
|
HELPER_EXPECT_SUCCESS(format.on_audio(audio));
|
|
}
|
|
|
|
// Test video packet handling with AVC sequence header
|
|
if (true) {
|
|
SrsMediaPacket *video = new SrsMediaPacket();
|
|
SrsUniquePtr<SrsMediaPacket> video_uptr(video);
|
|
|
|
// Create a proper AVC sequence header: 0x17 (keyframe + AVC), 0x00 (AVC sequence header)
|
|
// followed by minimal AVC decoder configuration record
|
|
char *video_data = new char[20];
|
|
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
|
|
video_data[11] = 0x00;
|
|
video_data[12] = 0x07; // SPS length
|
|
video_data[13] = 0x67;
|
|
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->wrap(video_data, 20);
|
|
video->timestamp_ = 2000;
|
|
video->message_type_ = SrsFrameTypeVideo;
|
|
|
|
// Video processing may fail due to complex AVC validation - just test that method exists
|
|
srs_error_t video_err = format.on_video(video);
|
|
srs_freep(video_err);
|
|
// Don't assert success since AVC decoder configuration validation is complex
|
|
}
|
|
|
|
// Test direct timestamp-based calls - these may fail due to codec validation
|
|
// but we just test that the methods exist and don't crash
|
|
char test_data[] = {0x01, 0x02, 0x03, 0x04};
|
|
srs_error_t audio_err = format.on_audio(3000, test_data, sizeof(test_data));
|
|
srs_error_t video_err = format.on_video(4000, test_data, sizeof(test_data));
|
|
srs_freep(audio_err);
|
|
srs_freep(video_err);
|
|
// Don't assert success since codec validation may fail with test data
|
|
}
|
|
|
|
VOID TEST(ProtocolJsonTest, SrsJsonAnyBasic)
|
|
{
|
|
// Test string creation and conversion
|
|
if (true) {
|
|
SrsJsonAny *str = SrsJsonAny::str("hello world");
|
|
SrsUniquePtr<SrsJsonAny> str_uptr(str);
|
|
|
|
EXPECT_TRUE(str->is_string());
|
|
EXPECT_FALSE(str->is_boolean());
|
|
EXPECT_FALSE(str->is_integer());
|
|
EXPECT_FALSE(str->is_number());
|
|
EXPECT_FALSE(str->is_object());
|
|
EXPECT_FALSE(str->is_array());
|
|
EXPECT_FALSE(str->is_null());
|
|
|
|
EXPECT_STREQ("hello world", str->to_str().c_str());
|
|
}
|
|
|
|
// Test boolean creation and conversion
|
|
if (true) {
|
|
SrsJsonAny *boolean = SrsJsonAny::boolean(true);
|
|
SrsUniquePtr<SrsJsonAny> boolean_uptr(boolean);
|
|
|
|
EXPECT_FALSE(boolean->is_string());
|
|
EXPECT_TRUE(boolean->is_boolean());
|
|
EXPECT_FALSE(boolean->is_integer());
|
|
EXPECT_FALSE(boolean->is_number());
|
|
EXPECT_FALSE(boolean->is_object());
|
|
EXPECT_FALSE(boolean->is_array());
|
|
EXPECT_FALSE(boolean->is_null());
|
|
|
|
EXPECT_TRUE(boolean->to_boolean());
|
|
}
|
|
|
|
// Test integer creation and conversion
|
|
if (true) {
|
|
SrsJsonAny *integer = SrsJsonAny::integer(12345);
|
|
SrsUniquePtr<SrsJsonAny> integer_uptr(integer);
|
|
|
|
EXPECT_FALSE(integer->is_string());
|
|
EXPECT_FALSE(integer->is_boolean());
|
|
EXPECT_TRUE(integer->is_integer());
|
|
EXPECT_FALSE(integer->is_number());
|
|
EXPECT_FALSE(integer->is_object());
|
|
EXPECT_FALSE(integer->is_array());
|
|
EXPECT_FALSE(integer->is_null());
|
|
|
|
EXPECT_EQ(12345, integer->to_integer());
|
|
}
|
|
|
|
// Test number creation and conversion
|
|
if (true) {
|
|
SrsJsonAny *number = SrsJsonAny::number(3.14159);
|
|
SrsUniquePtr<SrsJsonAny> number_uptr(number);
|
|
|
|
EXPECT_FALSE(number->is_string());
|
|
EXPECT_FALSE(number->is_boolean());
|
|
EXPECT_FALSE(number->is_integer());
|
|
EXPECT_TRUE(number->is_number());
|
|
EXPECT_FALSE(number->is_object());
|
|
EXPECT_FALSE(number->is_array());
|
|
EXPECT_FALSE(number->is_null());
|
|
|
|
EXPECT_NEAR(3.14159, number->to_number(), 0.00001);
|
|
}
|
|
|
|
// Test null creation and conversion
|
|
if (true) {
|
|
SrsJsonAny *null_val = SrsJsonAny::null();
|
|
SrsUniquePtr<SrsJsonAny> null_uptr(null_val);
|
|
|
|
EXPECT_FALSE(null_val->is_string());
|
|
EXPECT_FALSE(null_val->is_boolean());
|
|
EXPECT_FALSE(null_val->is_integer());
|
|
EXPECT_FALSE(null_val->is_number());
|
|
EXPECT_FALSE(null_val->is_object());
|
|
EXPECT_FALSE(null_val->is_array());
|
|
EXPECT_TRUE(null_val->is_null());
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolJsonTest, SrsJsonObjectArray)
|
|
{
|
|
// Test object creation
|
|
if (true) {
|
|
SrsJsonObject *obj = SrsJsonAny::object();
|
|
SrsUniquePtr<SrsJsonObject> obj_uptr(obj);
|
|
|
|
EXPECT_FALSE(obj->is_string());
|
|
EXPECT_FALSE(obj->is_boolean());
|
|
EXPECT_FALSE(obj->is_integer());
|
|
EXPECT_FALSE(obj->is_number());
|
|
EXPECT_TRUE(obj->is_object());
|
|
EXPECT_FALSE(obj->is_array());
|
|
EXPECT_FALSE(obj->is_null());
|
|
|
|
SrsJsonObject *converted = obj->to_object();
|
|
EXPECT_TRUE(converted != NULL);
|
|
EXPECT_EQ(obj, converted);
|
|
}
|
|
|
|
// Test array creation
|
|
if (true) {
|
|
SrsJsonArray *arr = SrsJsonAny::array();
|
|
SrsUniquePtr<SrsJsonArray> arr_uptr(arr);
|
|
|
|
EXPECT_FALSE(arr->is_string());
|
|
EXPECT_FALSE(arr->is_boolean());
|
|
EXPECT_FALSE(arr->is_integer());
|
|
EXPECT_FALSE(arr->is_number());
|
|
EXPECT_FALSE(arr->is_object());
|
|
EXPECT_TRUE(arr->is_array());
|
|
EXPECT_FALSE(arr->is_null());
|
|
|
|
SrsJsonArray *converted = arr->to_array();
|
|
EXPECT_TRUE(converted != NULL);
|
|
EXPECT_EQ(arr, converted);
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolJsonTest, SrsJsonLoads)
|
|
{
|
|
// Test loading simple JSON string
|
|
if (true) {
|
|
SrsJsonAny *json = SrsJsonAny::loads("{\"name\":\"test\",\"value\":123}");
|
|
if (json) {
|
|
SrsUniquePtr<SrsJsonAny> json_uptr(json);
|
|
EXPECT_TRUE(json->is_object());
|
|
}
|
|
}
|
|
|
|
// Test loading invalid JSON - should return NULL
|
|
if (true) {
|
|
SrsJsonAny *json = SrsJsonAny::loads("invalid json");
|
|
EXPECT_TRUE(json == NULL);
|
|
}
|
|
|
|
// Test loading empty string - should return NULL
|
|
if (true) {
|
|
SrsJsonAny *json = SrsJsonAny::loads("");
|
|
EXPECT_TRUE(json == NULL);
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolRawAvcTest, SrsRawH264StreamBasic)
|
|
{
|
|
SrsRawH264Stream h264;
|
|
|
|
// Test basic functionality - these methods should exist and not crash
|
|
// We can't easily test the full functionality without complex H.264 data
|
|
|
|
// Test SPS/PPS detection with minimal data - just test that methods don't crash
|
|
char test_frame[] = {0x00, 0x00, 0x00, 0x01, 0x67}; // Minimal SPS-like data
|
|
bool is_sps = h264.is_sps(test_frame, sizeof(test_frame));
|
|
bool is_pps = h264.is_pps(test_frame, sizeof(test_frame));
|
|
(void)is_sps;
|
|
(void)is_pps;
|
|
// Don't assert specific results since frame detection is complex
|
|
|
|
char pps_frame[] = {0x00, 0x00, 0x00, 0x01, 0x68}; // Minimal PPS-like data
|
|
bool is_sps2 = h264.is_sps(pps_frame, sizeof(pps_frame));
|
|
bool is_pps2 = h264.is_pps(pps_frame, sizeof(pps_frame));
|
|
(void)is_sps2;
|
|
(void)is_pps2;
|
|
// Don't assert specific results since frame detection is complex
|
|
}
|
|
|
|
VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamBasic)
|
|
{
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Test basic functionality for HEVC - just test that methods don't crash
|
|
// Test VPS/SPS/PPS detection with minimal data
|
|
char vps_frame[] = {0x00, 0x00, 0x00, 0x01, 0x40}; // Minimal VPS-like data (NALU type 32)
|
|
char sps_frame[] = {0x00, 0x00, 0x00, 0x01, 0x42}; // Minimal SPS-like data (NALU type 33)
|
|
char pps_frame[] = {0x00, 0x00, 0x00, 0x01, 0x44}; // Minimal PPS-like data (NALU type 34)
|
|
|
|
// Test that methods don't crash - don't assert specific results since frame detection is complex
|
|
bool is_vps1 = hevc.is_vps(vps_frame, sizeof(vps_frame));
|
|
bool is_sps1 = hevc.is_sps(vps_frame, sizeof(vps_frame));
|
|
bool is_pps1 = hevc.is_pps(vps_frame, sizeof(vps_frame));
|
|
(void)is_vps1;
|
|
(void)is_sps1;
|
|
(void)is_pps1;
|
|
|
|
bool is_vps2 = hevc.is_vps(sps_frame, sizeof(sps_frame));
|
|
bool is_sps2 = hevc.is_sps(sps_frame, sizeof(sps_frame));
|
|
bool is_pps2 = hevc.is_pps(sps_frame, sizeof(sps_frame));
|
|
(void)is_vps2;
|
|
(void)is_sps2;
|
|
(void)is_pps2;
|
|
|
|
bool is_vps3 = hevc.is_vps(pps_frame, sizeof(pps_frame));
|
|
bool is_sps3 = hevc.is_sps(pps_frame, sizeof(pps_frame));
|
|
bool is_pps3 = hevc.is_pps(pps_frame, sizeof(pps_frame));
|
|
(void)is_vps3;
|
|
(void)is_sps3;
|
|
(void)is_pps3;
|
|
}
|
|
|
|
VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamVpsDemux)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Test VPS demux with valid VPS data
|
|
if (true) {
|
|
// Create VPS NALU data (NALU type 32 = 0x40)
|
|
unsigned char vps_data[] = {0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xac, 0x09};
|
|
std::string vps_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.vps_demux((char *)vps_data, sizeof(vps_data), vps_output));
|
|
|
|
// Should copy the VPS data
|
|
EXPECT_EQ(sizeof(vps_data), vps_output.length());
|
|
EXPECT_EQ(0, memcmp(vps_data, vps_output.data(), sizeof(vps_data)));
|
|
}
|
|
|
|
// Test VPS demux with empty data - should fail
|
|
if (true) {
|
|
std::string vps_output;
|
|
HELPER_EXPECT_FAILED(hevc.vps_demux(NULL, 0, vps_output));
|
|
}
|
|
|
|
// Test VPS demux with minimal data
|
|
if (true) {
|
|
unsigned char minimal_vps[] = {0x40};
|
|
std::string vps_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.vps_demux((char *)minimal_vps, sizeof(minimal_vps), vps_output));
|
|
EXPECT_EQ(sizeof(minimal_vps), vps_output.length());
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamSpsDemux)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Test SPS demux with valid SPS data
|
|
if (true) {
|
|
// Create SPS NALU data (NALU type 33 = 0x42)
|
|
unsigned char sps_data[] = {0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0xc0, 0x5a, 0x70, 0x80, 0x00, 0x01, 0xf4, 0x80, 0x00, 0x5d, 0xc0, 0x47, 0xe1, 0x81, 0x65, 0x80};
|
|
std::string sps_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.sps_demux((char *)sps_data, sizeof(sps_data), sps_output));
|
|
|
|
// Should copy the SPS data
|
|
EXPECT_EQ(sizeof(sps_data), sps_output.length());
|
|
EXPECT_EQ(0, memcmp(sps_data, sps_output.data(), sizeof(sps_data)));
|
|
}
|
|
|
|
// Test SPS demux with insufficient data (less than 4 bytes) - should succeed but return empty
|
|
if (true) {
|
|
unsigned char short_sps[] = {0x42, 0x01, 0x01};
|
|
std::string sps_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.sps_demux((char *)short_sps, sizeof(short_sps), sps_output));
|
|
EXPECT_TRUE(sps_output.empty());
|
|
}
|
|
|
|
// Test SPS demux with exactly 4 bytes
|
|
if (true) {
|
|
unsigned char min_sps[] = {0x42, 0x01, 0x01, 0x01};
|
|
std::string sps_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.sps_demux((char *)min_sps, sizeof(min_sps), sps_output));
|
|
EXPECT_EQ(sizeof(min_sps), sps_output.length());
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamPpsDemux)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Test PPS demux with valid PPS data
|
|
if (true) {
|
|
// Create PPS NALU data (NALU type 34 = 0x44)
|
|
unsigned char pps_data[] = {0x44, 0x01, 0xc1, 0x73, 0xd1, 0x89, 0x00};
|
|
std::string pps_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.pps_demux((char *)pps_data, sizeof(pps_data), pps_output));
|
|
|
|
// Should copy the PPS data
|
|
EXPECT_EQ(sizeof(pps_data), pps_output.length());
|
|
EXPECT_EQ(0, memcmp(pps_data, pps_output.data(), sizeof(pps_data)));
|
|
}
|
|
|
|
// Test PPS demux with empty data - should fail
|
|
if (true) {
|
|
std::string pps_output;
|
|
HELPER_EXPECT_FAILED(hevc.pps_demux(NULL, 0, pps_output));
|
|
}
|
|
|
|
// Test PPS demux with minimal data
|
|
if (true) {
|
|
unsigned char minimal_pps[] = {0x44};
|
|
std::string pps_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.pps_demux((char *)minimal_pps, sizeof(minimal_pps), pps_output));
|
|
EXPECT_EQ(sizeof(minimal_pps), pps_output.length());
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamMuxSequenceHeader)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Test mux_sequence_header with valid HEVC VPS, SPS, and PPS data
|
|
if (true) {
|
|
// Create valid HEVC VPS data that will pass demux validation
|
|
// VPS NALU: forbidden_zero_bit(1) + nal_unit_type(6) + nuh_layer_id(6) + nuh_temporal_id_plus1(3) + RBSP
|
|
// NALU type 32 (VPS) = 0x40 (32 << 1), nuh_layer_id=0, nuh_temporal_id_plus1=1
|
|
unsigned char vps_raw[] = {
|
|
0x40, 0x01, // NALU header: type=32(VPS), layer_id=0, temporal_id=1
|
|
0x0C, 0x01, 0xFF, 0xFF, 0x01, 0x60, 0x00, 0x00, // VPS RBSP data
|
|
0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00,
|
|
0x03, 0x00, 0x5D, 0xAC, 0x09};
|
|
std::string vps_data((char *)vps_raw, sizeof(vps_raw));
|
|
|
|
// Create valid HEVC SPS data that will pass demux validation
|
|
// SPS NALU: NALU type 33 (SPS) = 0x42 (33 << 1)
|
|
unsigned char sps_raw[] = {
|
|
0x42, 0x01, // NALU header: type=33(SPS), layer_id=0, temporal_id=1
|
|
0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, // SPS RBSP data
|
|
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5D,
|
|
0xA0, 0x02, 0x80, 0x80, 0x2D, 0x16, 0x59, 0x59,
|
|
0xA4, 0x93, 0x2B, 0xC0, 0x5A, 0x70, 0x80};
|
|
std::string sps_data((char *)sps_raw, sizeof(sps_raw));
|
|
|
|
// Create valid HEVC PPS data
|
|
// PPS NALU: NALU type 34 (PPS) = 0x44 (34 << 1)
|
|
std::vector<std::string> pps_list;
|
|
unsigned char pps_raw[] = {
|
|
0x44, 0x01, // NALU header: type=34(PPS), layer_id=0, temporal_id=1
|
|
0xC1, 0x73, 0xD1, 0x89, 0x00 // PPS RBSP data
|
|
};
|
|
pps_list.push_back(std::string((char *)pps_raw, sizeof(pps_raw)));
|
|
|
|
std::string hvcC_output;
|
|
|
|
// This should now succeed with valid HEVC data and cover the configuration record generation
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_sequence_header(vps_data, sps_data, pps_list, hvcC_output));
|
|
|
|
// Should produce non-empty HEVCDecoderConfigurationRecord
|
|
EXPECT_FALSE(hvcC_output.empty());
|
|
EXPECT_GT(hvcC_output.length(), 23); // Minimum HEVC configuration record size
|
|
|
|
// Verify HEVCDecoderConfigurationRecord structure
|
|
const char *data = hvcC_output.data();
|
|
EXPECT_EQ(0x01, (unsigned char)data[0]); // configurationVersion = 1
|
|
|
|
// Verify the configuration record contains our VPS, SPS, PPS data
|
|
// The exact structure depends on the implementation, but it should be significantly larger than header
|
|
EXPECT_GT(hvcC_output.length(), vps_data.length() + sps_data.length() + pps_list[0].length());
|
|
}
|
|
|
|
// Test mux_sequence_header with empty VPS - should fail
|
|
if (true) {
|
|
std::string empty_vps;
|
|
std::string sps_data = std::string("\x42\x01\x01\x01\x60", 5);
|
|
std::vector<std::string> pps_list;
|
|
pps_list.push_back(std::string("\x44\x01", 2));
|
|
std::string hvcC_output;
|
|
|
|
srs_error_t mux_err = hevc.mux_sequence_header(empty_vps, sps_data, pps_list, hvcC_output);
|
|
HELPER_EXPECT_FAILED(mux_err); // Should fail due to empty VPS
|
|
}
|
|
|
|
// Test mux_sequence_header with empty PPS list - should fail
|
|
if (true) {
|
|
std::string vps_data = std::string("\x40\x01\x0c", 3);
|
|
std::string sps_data = std::string("\x42\x01\x01\x01\x60", 5);
|
|
std::vector<std::string> empty_pps_list;
|
|
std::string hvcC_output;
|
|
|
|
srs_error_t mux_err = hevc.mux_sequence_header(vps_data, sps_data, empty_pps_list, hvcC_output);
|
|
HELPER_EXPECT_FAILED(mux_err); // Should fail due to empty PPS list
|
|
}
|
|
|
|
// Test mux_sequence_header with multiple PPS
|
|
if (true) {
|
|
std::string vps_data = std::string("\x40\x01", 2);
|
|
std::string sps_data = std::string("\x42\x01\x01\x01", 4);
|
|
|
|
std::vector<std::string> pps_list;
|
|
pps_list.push_back(std::string("\x44\x01", 2));
|
|
pps_list.push_back(std::string("\x44\x02", 2));
|
|
pps_list.push_back(std::string("\x44\x03", 2));
|
|
|
|
std::string hvcC_output;
|
|
|
|
// This may fail due to HEVC validation but shouldn't crash
|
|
srs_error_t mux_err = hevc.mux_sequence_header(vps_data, sps_data, pps_list, hvcC_output);
|
|
srs_freep(mux_err); // Don't assert success since HEVC validation is complex
|
|
|
|
// Test passed if no crash occurred
|
|
EXPECT_TRUE(true);
|
|
}
|
|
|
|
// Test mux_sequence_header with different HEVC profile/level to cover more configuration record generation
|
|
if (true) {
|
|
// Create VPS with different profile space and tier flag
|
|
unsigned char vps_raw2[] = {
|
|
0x40, 0x01, // NALU header: type=32(VPS)
|
|
0x2C, 0x01, 0xFF, 0xFF, 0x02, 0x20, 0x00, 0x00, // VPS RBSP with different profile_space=1, tier_flag=1
|
|
0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00,
|
|
0x03, 0x00, 0x78, 0xAC, 0x09};
|
|
std::string vps_data2((char *)vps_raw2, sizeof(vps_raw2));
|
|
|
|
// Create SPS with different parameters
|
|
unsigned char sps_raw2[] = {
|
|
0x42, 0x01, // NALU header: type=33(SPS)
|
|
0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, // SPS RBSP with different profile_idc
|
|
0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xA0,
|
|
0x03, 0x50, 0x80, 0x32, 0x16, 0x59, 0x59, 0xA4,
|
|
0x93, 0x2B, 0xC0, 0x40, 0x40, 0x40, 0x80};
|
|
std::string sps_data2((char *)sps_raw2, sizeof(sps_raw2));
|
|
|
|
std::vector<std::string> pps_list2;
|
|
unsigned char pps_raw2[] = {
|
|
0x44, 0x01, // NALU header: type=34(PPS)
|
|
0xC2, 0x73, 0xD1, 0x89, 0x10 // PPS RBSP data
|
|
};
|
|
pps_list2.push_back(std::string((char *)pps_raw2, sizeof(pps_raw2)));
|
|
|
|
std::string hvcC_output2;
|
|
|
|
// This should succeed and generate different configuration record
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_sequence_header(vps_data2, sps_data2, pps_list2, hvcC_output2));
|
|
|
|
// Should produce non-empty HEVCDecoderConfigurationRecord
|
|
EXPECT_FALSE(hvcC_output2.empty());
|
|
EXPECT_GT(hvcC_output2.length(), 23); // Minimum HEVC configuration record size
|
|
|
|
// Verify HEVCDecoderConfigurationRecord structure
|
|
const char *data2 = hvcC_output2.data();
|
|
EXPECT_EQ(0x01, (unsigned char)data2[0]); // configurationVersion = 1
|
|
|
|
// The second byte should contain profile_space, tier_flag, and profile_idc
|
|
// This tests the bit manipulation code: temp8bits |= ((hevc_info->general_profile_space_ & 0x03) << 6);
|
|
uint8_t profile_byte = (unsigned char)data2[1];
|
|
uint8_t profile_space = (profile_byte >> 6) & 0x03;
|
|
uint8_t tier_flag = (profile_byte >> 5) & 0x01;
|
|
uint8_t profile_idc = profile_byte & 0x1f;
|
|
|
|
// These values should be extracted from the VPS/SPS parsing
|
|
EXPECT_GE(profile_space, 0);
|
|
EXPECT_LE(profile_space, 3);
|
|
EXPECT_GE(tier_flag, 0);
|
|
EXPECT_LE(tier_flag, 1);
|
|
EXPECT_GE(profile_idc, 0);
|
|
EXPECT_LE(profile_idc, 31);
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamMuxIpbFrame)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Test mux_ipb_frame with valid frame data
|
|
if (true) {
|
|
// Create test HEVC frame data (IDR slice)
|
|
unsigned char frame_data[] = {0x26, 0x01, 0xaf, 0x06, 0xb8, 0x63, 0xef, 0x3a, 0x7f, 0x3c, 0x00, 0x01, 0x00, 0x80};
|
|
std::string ibp_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)frame_data, sizeof(frame_data), ibp_output));
|
|
|
|
// Should produce frame with 4-byte length prefix + frame data
|
|
EXPECT_EQ(4 + sizeof(frame_data), ibp_output.length());
|
|
|
|
// Check length prefix (big-endian)
|
|
const char *data = ibp_output.data();
|
|
uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3];
|
|
EXPECT_EQ(sizeof(frame_data), length);
|
|
|
|
// Check frame data follows length prefix
|
|
EXPECT_EQ(0, memcmp(frame_data, data + 4, sizeof(frame_data)));
|
|
}
|
|
|
|
// Test mux_ipb_frame with empty frame - should still work
|
|
if (true) {
|
|
unsigned char empty_frame[] = {};
|
|
std::string ibp_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)empty_frame, 0, ibp_output));
|
|
|
|
// Should produce only 4-byte length prefix with zero length
|
|
EXPECT_EQ(4, ibp_output.length());
|
|
|
|
const char *data = ibp_output.data();
|
|
uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3];
|
|
EXPECT_EQ(0, length);
|
|
}
|
|
|
|
// Test mux_ipb_frame with large frame
|
|
if (true) {
|
|
unsigned char large_frame[1000];
|
|
for (int i = 0; i < 1000; i++) {
|
|
large_frame[i] = i % 256;
|
|
}
|
|
std::string ibp_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)large_frame, sizeof(large_frame), ibp_output));
|
|
|
|
// Should produce frame with 4-byte length prefix + frame data
|
|
EXPECT_EQ(4 + sizeof(large_frame), ibp_output.length());
|
|
|
|
// Check length prefix
|
|
const char *data = ibp_output.data();
|
|
uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3];
|
|
EXPECT_EQ(sizeof(large_frame), length);
|
|
}
|
|
|
|
// Test mux_ipb_frame with different HEVC NALU types
|
|
if (true) {
|
|
// Test with P-frame (TRAIL_R NALU type 1)
|
|
unsigned char p_frame[] = {0x02, 0x01, 0x50, 0x80, 0x12, 0x34, 0x56, 0x78};
|
|
std::string ibp_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)p_frame, sizeof(p_frame), ibp_output));
|
|
|
|
// Verify output format
|
|
EXPECT_EQ(4 + sizeof(p_frame), ibp_output.length());
|
|
const char *data = ibp_output.data();
|
|
uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3];
|
|
EXPECT_EQ(sizeof(p_frame), length);
|
|
EXPECT_EQ(0, memcmp(p_frame, data + 4, sizeof(p_frame)));
|
|
}
|
|
|
|
// Test mux_ipb_frame with B-frame (TSA_N NALU type 2)
|
|
if (true) {
|
|
unsigned char b_frame[] = {0x04, 0x01, 0x60, 0x90, 0xab, 0xcd, 0xef};
|
|
std::string ibp_output;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)b_frame, sizeof(b_frame), ibp_output));
|
|
|
|
// Verify output format
|
|
EXPECT_EQ(4 + sizeof(b_frame), ibp_output.length());
|
|
const char *data = ibp_output.data();
|
|
uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3];
|
|
EXPECT_EQ(sizeof(b_frame), length);
|
|
EXPECT_EQ(0, memcmp(b_frame, data + 4, sizeof(b_frame)));
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamMuxAvc2flv)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Test mux_avc2flv with sequence header
|
|
if (true) {
|
|
std::string video_data = std::string("\x01\x64\x00\x20\xff\xe1\x00\x19\x67\x64\x00\x20", 12);
|
|
int8_t frame_type = 1; // keyframe
|
|
int8_t avc_packet_type = 0; // sequence header
|
|
uint32_t dts = 1000;
|
|
uint32_t pts = 1000;
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
// Should produce FLV packet with 5-byte header + video data
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
EXPECT_EQ(5 + video_data.length(), nb_flv);
|
|
|
|
if (flv_data) {
|
|
// Check FLV header format
|
|
EXPECT_EQ((frame_type << 4) | 12, (uint8_t)flv_data[0]); // frame_type | SrsVideoCodecIdHEVC(12)
|
|
EXPECT_EQ(avc_packet_type, flv_data[1]); // AVCPacketType
|
|
|
|
// Check composition time (CTS = PTS - DTS = 0)
|
|
uint32_t cts = ((unsigned char)flv_data[2] << 16) | ((unsigned char)flv_data[3] << 8) | (unsigned char)flv_data[4];
|
|
EXPECT_EQ(0, cts);
|
|
|
|
// Check video data follows header
|
|
EXPECT_EQ(0, memcmp(video_data.data(), flv_data + 5, video_data.length()));
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
|
|
// Test mux_avc2flv with NALU frame
|
|
if (true) {
|
|
std::string video_data = std::string("\x00\x00\x00\x0e\x26\x01\xaf\x06\xb8\x63\xef\x3a\x7f\x3c\x00\x01\x00\x80", 18);
|
|
int8_t frame_type = 1; // keyframe
|
|
int8_t avc_packet_type = 1; // NALU
|
|
uint32_t dts = 2000;
|
|
uint32_t pts = 2100; // PTS > DTS
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
EXPECT_EQ(5 + video_data.length(), nb_flv);
|
|
|
|
if (flv_data) {
|
|
// Check composition time (CTS = PTS - DTS = 100)
|
|
uint32_t cts = ((unsigned char)flv_data[2] << 16) | ((unsigned char)flv_data[3] << 8) | (unsigned char)flv_data[4];
|
|
EXPECT_EQ(100, cts);
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
|
|
// Test mux_avc2flv with inter frame
|
|
if (true) {
|
|
std::string video_data = std::string("\x00\x00\x00\x08\x02\x01\xd0\x80\x93\x25\x88\x84", 12);
|
|
int8_t frame_type = 2; // inter frame
|
|
int8_t avc_packet_type = 1; // NALU
|
|
uint32_t dts = 3000;
|
|
uint32_t pts = 3000;
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
if (flv_data) {
|
|
// Check frame type in FLV header
|
|
EXPECT_EQ((frame_type << 4) | 12, (uint8_t)flv_data[0]); // inter frame | HEVC codec
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
|
|
// Test mux_avc2flv with empty video data
|
|
if (true) {
|
|
std::string empty_video_data;
|
|
int8_t frame_type = 1; // keyframe
|
|
int8_t avc_packet_type = 0; // sequence header
|
|
uint32_t dts = 4000;
|
|
uint32_t pts = 4000;
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(empty_video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
// Should produce FLV packet with 5-byte header only
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
EXPECT_EQ(5, nb_flv);
|
|
|
|
if (flv_data) {
|
|
// Check FLV header format
|
|
EXPECT_EQ((frame_type << 4) | 12, (uint8_t)flv_data[0]); // frame_type | SrsVideoCodecIdHEVC(12)
|
|
EXPECT_EQ(avc_packet_type, flv_data[1]); // AVCPacketType
|
|
|
|
// Check composition time (CTS = PTS - DTS = 0)
|
|
uint32_t cts = ((unsigned char)flv_data[2] << 16) | ((unsigned char)flv_data[3] << 8) | (unsigned char)flv_data[4];
|
|
EXPECT_EQ(0, cts);
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
|
|
// Test mux_avc2flv with large composition time offset
|
|
if (true) {
|
|
std::string video_data = std::string("\x00\x00\x00\x04\x26\x01\xaf\x06", 8);
|
|
int8_t frame_type = 1; // keyframe
|
|
int8_t avc_packet_type = 1; // NALU
|
|
uint32_t dts = 5000;
|
|
uint32_t pts = 10000; // Large PTS offset
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
if (flv_data) {
|
|
// Check composition time (CTS = PTS - DTS = 5000)
|
|
uint32_t cts = ((unsigned char)flv_data[2] << 16) | ((unsigned char)flv_data[3] << 8) | (unsigned char)flv_data[4];
|
|
EXPECT_EQ(5000, cts);
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamMuxAvc2flvEnhanced)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Test mux_avc2flv_enhanced with sequence header
|
|
if (true) {
|
|
std::string video_data = std::string("\x01\x64\x00\x20\xff\xe1\x00\x19\x67\x64\x00\x20", 12);
|
|
int8_t frame_type = 1; // keyframe
|
|
int8_t packet_type = 0; // sequence start
|
|
uint32_t dts = 1000;
|
|
uint32_t pts = 1000;
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
// Should produce enhanced FLV packet with 5-byte header + video data
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
EXPECT_EQ(5 + video_data.length(), nb_flv);
|
|
|
|
if (flv_data) {
|
|
// Check enhanced FLV header format
|
|
uint8_t header_byte = flv_data[0];
|
|
EXPECT_TRUE((header_byte & 0x80) != 0); // SRS_FLV_IS_EX_HEADER bit set
|
|
EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // frame type (3 bits, mask out EX_HEADER bit)
|
|
EXPECT_EQ(packet_type, header_byte & 0x0f); // packet type
|
|
|
|
// Check HEVC fourcc 'hvc1'
|
|
EXPECT_EQ('h', flv_data[1]);
|
|
EXPECT_EQ('v', flv_data[2]);
|
|
EXPECT_EQ('c', flv_data[3]);
|
|
EXPECT_EQ('1', flv_data[4]);
|
|
|
|
// Check video data follows header
|
|
EXPECT_EQ(0, memcmp(video_data.data(), flv_data + 5, video_data.length()));
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
|
|
// Test mux_avc2flv_enhanced with coded frames
|
|
if (true) {
|
|
std::string video_data = std::string("\x00\x00\x00\x0e\x26\x01\xaf\x06\xb8\x63\xef\x3a\x7f\x3c\x00\x01\x00\x80", 18);
|
|
int8_t frame_type = 1; // keyframe
|
|
int8_t packet_type = 1; // coded frames
|
|
uint32_t dts = 2000;
|
|
uint32_t pts = 2000;
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
EXPECT_EQ(5 + video_data.length(), nb_flv);
|
|
|
|
if (flv_data) {
|
|
// Check enhanced header with coded frames packet type
|
|
uint8_t header_byte = flv_data[0];
|
|
EXPECT_EQ(packet_type, header_byte & 0x0f); // coded frames packet type
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
|
|
// Test mux_avc2flv_enhanced with inter frame
|
|
if (true) {
|
|
std::string video_data = std::string("\x00\x00\x00\x08\x02\x01\xd0\x80\x93\x25\x88\x84", 12);
|
|
int8_t frame_type = 2; // inter frame
|
|
int8_t packet_type = 1; // coded frames
|
|
uint32_t dts = 3000;
|
|
uint32_t pts = 3000;
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
if (flv_data) {
|
|
// Check inter frame type in enhanced header
|
|
uint8_t header_byte = flv_data[0];
|
|
EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // inter frame type (3 bits, mask out EX_HEADER bit)
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
|
|
// Test mux_avc2flv_enhanced with sequence end packet
|
|
if (true) {
|
|
std::string video_data; // Empty for sequence end
|
|
int8_t frame_type = 1; // keyframe
|
|
int8_t packet_type = 2; // sequence end
|
|
uint32_t dts = 4000;
|
|
uint32_t pts = 4000;
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
EXPECT_EQ(5, nb_flv); // Should produce 5-byte header only
|
|
|
|
if (flv_data) {
|
|
// Check enhanced FLV header format for sequence end
|
|
uint8_t header_byte = flv_data[0];
|
|
EXPECT_TRUE((header_byte & 0x80) != 0); // SRS_FLV_IS_EX_HEADER bit set
|
|
EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // frame type
|
|
EXPECT_EQ(packet_type, header_byte & 0x0f); // sequence end packet type
|
|
|
|
// Check HEVC fourcc 'hvc1'
|
|
EXPECT_EQ('h', flv_data[1]);
|
|
EXPECT_EQ('v', flv_data[2]);
|
|
EXPECT_EQ('c', flv_data[3]);
|
|
EXPECT_EQ('1', flv_data[4]);
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
|
|
// Test mux_avc2flv_enhanced with different frame types
|
|
if (true) {
|
|
std::string video_data = std::string("\x00\x00\x00\x06\x04\x01\x70\x80\x12\x34", 10);
|
|
int8_t frame_type = 3; // disposable inter frame
|
|
int8_t packet_type = 1; // coded frames
|
|
uint32_t dts = 5000;
|
|
uint32_t pts = 5000;
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
if (flv_data) {
|
|
// Check disposable inter frame type in enhanced header
|
|
uint8_t header_byte = flv_data[0];
|
|
EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // disposable inter frame type
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
|
|
// Test mux_avc2flv_enhanced with empty video data
|
|
if (true) {
|
|
std::string empty_video_data;
|
|
int8_t frame_type = 1; // keyframe
|
|
int8_t packet_type = 0; // sequence start
|
|
uint32_t dts = 6000;
|
|
uint32_t pts = 6000;
|
|
char *flv_data = NULL;
|
|
int nb_flv = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(empty_video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv));
|
|
|
|
// Should produce enhanced FLV packet with 5-byte header only
|
|
EXPECT_TRUE(flv_data != NULL);
|
|
EXPECT_EQ(5, nb_flv);
|
|
|
|
if (flv_data) {
|
|
// Check enhanced FLV header format
|
|
uint8_t header_byte = flv_data[0];
|
|
EXPECT_TRUE((header_byte & 0x80) != 0); // SRS_FLV_IS_EX_HEADER bit set
|
|
EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // frame type
|
|
EXPECT_EQ(packet_type, header_byte & 0x0f); // packet type
|
|
|
|
// Check HEVC fourcc 'hvc1'
|
|
EXPECT_EQ('h', flv_data[1]);
|
|
EXPECT_EQ('v', flv_data[2]);
|
|
EXPECT_EQ('c', flv_data[3]);
|
|
EXPECT_EQ('1', flv_data[4]);
|
|
|
|
delete[] flv_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID TEST(ProtocolStreamTest, SrsFastStreamBasic)
|
|
{
|
|
SrsFastStream stream;
|
|
|
|
// Test initial state
|
|
EXPECT_EQ(0, stream.size());
|
|
|
|
// Test bytes() method - should not crash even when empty
|
|
char *bytes = stream.bytes();
|
|
(void)bytes;
|
|
// bytes might be NULL or valid pointer, both are acceptable for empty stream
|
|
|
|
// Test buffer setting
|
|
stream.set_buffer(1024);
|
|
EXPECT_EQ(0, stream.size()); // Size should still be 0 after setting buffer
|
|
}
|
|
|
|
VOID TEST(ProtocolLogTest, SrsThreadContextBasic)
|
|
{
|
|
SrsThreadContext context;
|
|
|
|
// Test ID generation
|
|
SrsContextId id1 = context.generate_id();
|
|
SrsContextId id2 = context.generate_id();
|
|
|
|
// IDs should be different - compare using compare method
|
|
EXPECT_TRUE(id1.compare(id2) != 0);
|
|
|
|
// Test getting current ID
|
|
const SrsContextId ¤t = context.get_id();
|
|
(void)current;
|
|
// Should not crash
|
|
|
|
// Test setting ID
|
|
SrsContextId new_id = context.generate_id();
|
|
const SrsContextId &set_result = context.set_id(new_id);
|
|
EXPECT_EQ(0, new_id.compare(set_result));
|
|
}
|
|
|
|
VOID TEST(ProtocolRtmpConnTest, SrsBasicRtmpClientBasic)
|
|
{
|
|
// Test basic RTMP client construction
|
|
SrsBasicRtmpClient client("rtmp://127.0.0.1:1935/live/test", 3000 * SRS_UTIME_MILLISECONDS, 9000 * SRS_UTIME_MILLISECONDS);
|
|
|
|
// Test stream ID - should be 0 initially
|
|
EXPECT_EQ(0, client.sid());
|
|
|
|
// Test extra args access
|
|
SrsAmf0Object *args = client.extra_args();
|
|
EXPECT_TRUE(args != NULL);
|
|
|
|
// Note: We don't test set_recv_timeout() here because it requires a valid socket connection
|
|
// which we don't have in unit tests. The method exists and will be tested in integration tests.
|
|
}
|
|
|
|
VOID TEST(ProtocolStTest, SrsStSocketBasic)
|
|
{
|
|
// Test basic socket operations - these should exist and not crash
|
|
// We can't easily test full socket functionality without network setup
|
|
|
|
// Test socket creation functions exist
|
|
srs_netfd_t fd = NULL;
|
|
EXPECT_TRUE(fd == NULL);
|
|
|
|
// Test that we can call socket utility functions
|
|
bool is_never_timeout = srs_is_never_timeout(SRS_UTIME_NO_TIMEOUT);
|
|
EXPECT_TRUE(is_never_timeout);
|
|
|
|
bool is_not_never_timeout = srs_is_never_timeout(1000 * SRS_UTIME_MILLISECONDS);
|
|
EXPECT_FALSE(is_not_never_timeout);
|
|
}
|
|
|
|
VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderBasic)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRtpVideoBuilder builder;
|
|
|
|
// Test initialization with basic parameters
|
|
SrsFormat format;
|
|
uint32_t ssrc = 12345;
|
|
uint8_t payload_type = 96;
|
|
|
|
HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type));
|
|
|
|
// Test that builder is properly initialized
|
|
// We can't easily test the full functionality without complex media packets
|
|
// but we can verify the initialization doesn't crash
|
|
EXPECT_TRUE(true); // Basic initialization test passed
|
|
}
|
|
|
|
VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackaging)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRtpVideoBuilder builder;
|
|
SrsFormat format;
|
|
uint32_t ssrc = 54321;
|
|
uint8_t payload_type = 97;
|
|
|
|
HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type));
|
|
|
|
// Test packaging with minimal media packet
|
|
SrsMediaPacket *msg = new SrsMediaPacket();
|
|
SrsUniquePtr<SrsMediaPacket> msg_uptr(msg);
|
|
|
|
// Create minimal video data
|
|
char *video_data = new char[10];
|
|
video_data[0] = 0x17; // keyframe + AVC
|
|
video_data[1] = 0x01; // AVC NALU
|
|
for (int i = 2; i < 10; i++) {
|
|
video_data[i] = i;
|
|
}
|
|
msg->wrap(video_data, 10);
|
|
msg->timestamp_ = 1000;
|
|
msg->message_type_ = SrsFrameTypeVideo;
|
|
|
|
std::vector<SrsRtpPacket *> pkts;
|
|
|
|
// Test packaging - may fail due to complex validation but shouldn't crash
|
|
srs_error_t package_err = builder.package_nalus(msg, std::vector<SrsNaluSample *>(), pkts);
|
|
srs_freep(package_err);
|
|
|
|
// Clean up any created packets
|
|
for (size_t i = 0; i < pkts.size(); i++) {
|
|
delete pkts[i];
|
|
}
|
|
pkts.clear();
|
|
|
|
// Test passed if no crash occurred
|
|
EXPECT_TRUE(true);
|
|
}
|
|
|
|
VOID TEST(ProtocolRtcStunTest, SrsStunPacketBasic)
|
|
{
|
|
SrsStunPacket stun;
|
|
|
|
// Test initial state
|
|
EXPECT_FALSE(stun.is_binding_request());
|
|
EXPECT_FALSE(stun.is_binding_response());
|
|
EXPECT_EQ(0, stun.get_message_type());
|
|
EXPECT_TRUE(stun.get_username().empty());
|
|
EXPECT_TRUE(stun.get_local_ufrag().empty());
|
|
EXPECT_TRUE(stun.get_remote_ufrag().empty());
|
|
EXPECT_TRUE(stun.get_transcation_id().empty());
|
|
EXPECT_EQ(0, stun.get_mapped_address());
|
|
EXPECT_EQ(0, stun.get_mapped_port());
|
|
EXPECT_FALSE(stun.get_ice_controlled());
|
|
EXPECT_FALSE(stun.get_ice_controlling());
|
|
EXPECT_FALSE(stun.get_use_candidate());
|
|
}
|
|
|
|
VOID TEST(ProtocolRtcStunTest, SrsStunPacketSetters)
|
|
{
|
|
SrsStunPacket stun;
|
|
|
|
// Test setters
|
|
stun.set_message_type(BindingRequest);
|
|
EXPECT_TRUE(stun.is_binding_request());
|
|
EXPECT_FALSE(stun.is_binding_response());
|
|
EXPECT_EQ(BindingRequest, stun.get_message_type());
|
|
|
|
stun.set_message_type(BindingResponse);
|
|
EXPECT_FALSE(stun.is_binding_request());
|
|
EXPECT_TRUE(stun.is_binding_response());
|
|
EXPECT_EQ(BindingResponse, stun.get_message_type());
|
|
|
|
stun.set_local_ufrag("local123");
|
|
EXPECT_STREQ("local123", stun.get_local_ufrag().c_str());
|
|
|
|
stun.set_remote_ufrag("remote456");
|
|
EXPECT_STREQ("remote456", stun.get_remote_ufrag().c_str());
|
|
|
|
stun.set_transcation_id("transaction789");
|
|
EXPECT_STREQ("transaction789", stun.get_transcation_id().c_str());
|
|
|
|
stun.set_mapped_address(0x7f000001); // 127.0.0.1
|
|
EXPECT_EQ(0x7f000001, stun.get_mapped_address());
|
|
|
|
stun.set_mapped_port(8080);
|
|
EXPECT_EQ(8080, stun.get_mapped_port());
|
|
}
|
|
|
|
VOID TEST(ProtocolRtcStunTest, SrsStunPacketDecode)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsStunPacket stun;
|
|
|
|
// Test decode with invalid data - should fail
|
|
char invalid_data[] = {0x01, 0x02, 0x03};
|
|
HELPER_EXPECT_FAILED(stun.decode(invalid_data, sizeof(invalid_data)));
|
|
|
|
// Test decode with minimal valid STUN packet structure
|
|
// STUN header: message type (2) + message length (2) + magic cookie (4) + transaction ID (12) = 20 bytes minimum
|
|
char valid_stun[20];
|
|
memset(valid_stun, 0, sizeof(valid_stun));
|
|
|
|
// Set message type to binding request
|
|
valid_stun[0] = 0x00;
|
|
valid_stun[1] = 0x01; // BindingRequest
|
|
|
|
// Set message length to 0 (no attributes)
|
|
valid_stun[2] = 0x00;
|
|
valid_stun[3] = 0x00;
|
|
|
|
// Set magic cookie (0x2112A442 in network byte order)
|
|
valid_stun[4] = 0x21;
|
|
valid_stun[5] = 0x12;
|
|
valid_stun[6] = 0xA4;
|
|
valid_stun[7] = 0x42;
|
|
|
|
// Set transaction ID (12 bytes)
|
|
for (int i = 8; i < 20; i++) {
|
|
valid_stun[i] = i - 8;
|
|
}
|
|
|
|
HELPER_EXPECT_SUCCESS(stun.decode(valid_stun, sizeof(valid_stun)));
|
|
EXPECT_TRUE(stun.is_binding_request());
|
|
EXPECT_EQ(BindingRequest, stun.get_message_type());
|
|
}
|
|
|
|
VOID TEST(ProtocolRtcStunTest, SrsStunPacketEncode)
|
|
{
|
|
SrsStunPacket stun;
|
|
stun.set_message_type(BindingResponse);
|
|
stun.set_transcation_id("123456789012"); // 12 bytes
|
|
stun.set_mapped_address(0x7f000001);
|
|
stun.set_mapped_port(8080);
|
|
|
|
char buffer[1024];
|
|
SrsBuffer stream(buffer, sizeof(buffer));
|
|
|
|
// Test encode - may fail due to complex HMAC validation but shouldn't crash
|
|
srs_error_t encode_err = stun.encode("password", &stream);
|
|
srs_freep(encode_err);
|
|
|
|
// Test passed if no crash occurred
|
|
EXPECT_TRUE(true);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpBasic)
|
|
{
|
|
SrsSdp sdp;
|
|
|
|
// Test basic SDP construction - should not crash
|
|
EXPECT_TRUE(true);
|
|
|
|
// Test encode to empty stream
|
|
std::ostringstream os;
|
|
srs_error_t err = sdp.encode(os);
|
|
HELPER_EXPECT_SUCCESS(err);
|
|
|
|
// Should produce some basic SDP output
|
|
std::string result = os.str();
|
|
EXPECT_FALSE(result.empty());
|
|
|
|
// Should contain basic SDP fields
|
|
EXPECT_TRUE(result.find("v=") != std::string::npos); // version
|
|
EXPECT_TRUE(result.find("o=") != std::string::npos); // origin
|
|
EXPECT_TRUE(result.find("s=") != std::string::npos); // session name
|
|
EXPECT_TRUE(result.find("t=") != std::string::npos); // timing
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpParse)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSdp sdp;
|
|
|
|
// Test parsing minimal valid SDP
|
|
std::string minimal_sdp =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp.parse(minimal_sdp));
|
|
|
|
// Test parsing invalid SDP - may succeed or fail depending on SDP parser implementation
|
|
std::string invalid_sdp = "invalid sdp content";
|
|
srs_error_t invalid_err = sdp.parse(invalid_sdp);
|
|
srs_freep(invalid_err); // Don't assert specific result as parser may be lenient
|
|
|
|
// Test parsing empty SDP - may succeed or fail depending on implementation
|
|
srs_error_t empty_err = sdp.parse("");
|
|
srs_freep(empty_err); // Don't assert specific result as parser may handle empty input
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpMediaDescription)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSdp sdp;
|
|
|
|
// Test parsing SDP with media description
|
|
std::string sdp_with_media =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 9 RTP/AVP 96\r\n"
|
|
"a=rtpmap:96 H264/90000\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp.parse(sdp_with_media));
|
|
|
|
// Test that we can encode it back
|
|
std::ostringstream os;
|
|
HELPER_EXPECT_SUCCESS(sdp.encode(os));
|
|
|
|
std::string result = os.str();
|
|
EXPECT_FALSE(result.empty());
|
|
EXPECT_TRUE(result.find("m=") != std::string::npos); // media line
|
|
}
|
|
|
|
VOID TEST(ProtocolRtmpConnTest, SrsBasicRtmpClientConstruction)
|
|
{
|
|
// Test RTMP client construction with various URLs
|
|
SrsBasicRtmpClient client1("rtmp://127.0.0.1:1935/live/test", 3000 * SRS_UTIME_MILLISECONDS, 9000 * SRS_UTIME_MILLISECONDS);
|
|
|
|
// Test initial state
|
|
EXPECT_EQ(0, client1.sid());
|
|
|
|
// Test extra args access
|
|
SrsAmf0Object *args1 = client1.extra_args();
|
|
EXPECT_TRUE(args1 != NULL);
|
|
|
|
// Test with different URL format
|
|
SrsBasicRtmpClient client2("rtmp://192.168.1.100/app/stream", 5000 * SRS_UTIME_MILLISECONDS, 15000 * SRS_UTIME_MILLISECONDS);
|
|
|
|
EXPECT_EQ(0, client2.sid());
|
|
|
|
SrsAmf0Object *args2 = client2.extra_args();
|
|
EXPECT_TRUE(args2 != NULL);
|
|
|
|
// Args should be different objects
|
|
EXPECT_NE(args1, args2);
|
|
}
|
|
|
|
VOID TEST(ProtocolRtmpConnTest, SrsBasicRtmpClientOperations)
|
|
{
|
|
SrsBasicRtmpClient client("rtmp://127.0.0.1:1935/live/stream", 1000 * SRS_UTIME_MILLISECONDS, 3000 * SRS_UTIME_MILLISECONDS);
|
|
|
|
// Test connection operations - these will fail without a server but shouldn't crash
|
|
srs_error_t connect_err = client.connect();
|
|
srs_freep(connect_err); // Expected to fail without server
|
|
|
|
// Test close - should not crash even if not connected
|
|
client.close();
|
|
|
|
// Test kbps sampling - should not crash
|
|
client.kbps_sample("test", 1000 * SRS_UTIME_MILLISECONDS);
|
|
client.kbps_sample("test2", 2000 * SRS_UTIME_MILLISECONDS, 10);
|
|
|
|
// Note: We don't test publish(), play(), recv_message(), or set_recv_timeout() here
|
|
// because they require valid internal client/transport objects which we don't have
|
|
// without a successful connection. These methods exist and will be tested in
|
|
// integration tests with actual RTMP server connections.
|
|
}
|
|
|
|
VOID TEST(ProtocolRtcStunTest, SrsCrc32IeeeBasic)
|
|
{
|
|
// Test CRC32 IEEE calculation with known values
|
|
const char *test_data = "hello";
|
|
uint32_t crc = srs_crc32_ieee(test_data, strlen(test_data));
|
|
|
|
// CRC32 should be deterministic for same input
|
|
uint32_t crc2 = srs_crc32_ieee(test_data, strlen(test_data));
|
|
EXPECT_EQ(crc, crc2);
|
|
|
|
// Different data should produce different CRC
|
|
const char *test_data2 = "world";
|
|
uint32_t crc3 = srs_crc32_ieee(test_data2, strlen(test_data2));
|
|
EXPECT_NE(crc, crc3);
|
|
|
|
// Test with empty data
|
|
uint32_t crc_empty = srs_crc32_ieee("", 0);
|
|
EXPECT_EQ(0, crc_empty);
|
|
|
|
// Test with previous CRC value
|
|
uint32_t crc_combined = srs_crc32_ieee(test_data2, strlen(test_data2), crc);
|
|
EXPECT_NE(crc, crc_combined);
|
|
EXPECT_NE(crc3, crc_combined);
|
|
}
|
|
|
|
VOID TEST(ProtocolRtcStunTest, SrsStunPacketComplexDecode)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsStunPacket stun;
|
|
|
|
// Test STUN packet with username attribute
|
|
char stun_with_username[32];
|
|
memset(stun_with_username, 0, sizeof(stun_with_username));
|
|
|
|
// STUN header
|
|
stun_with_username[0] = 0x00;
|
|
stun_with_username[1] = 0x01; // BindingRequest
|
|
stun_with_username[2] = 0x00;
|
|
stun_with_username[3] = 0x0C; // message length = 12 (username attribute)
|
|
|
|
// Magic cookie
|
|
stun_with_username[4] = 0x21;
|
|
stun_with_username[5] = 0x12;
|
|
stun_with_username[6] = 0xA4;
|
|
stun_with_username[7] = 0x42;
|
|
|
|
// Transaction ID (12 bytes)
|
|
for (int i = 8; i < 20; i++) {
|
|
stun_with_username[i] = i - 8;
|
|
}
|
|
|
|
// Username attribute: type=0x0006, length=8, value="test:usr"
|
|
stun_with_username[20] = 0x00;
|
|
stun_with_username[21] = 0x06; // Username attribute type
|
|
stun_with_username[22] = 0x00;
|
|
stun_with_username[23] = 0x08; // Length = 8
|
|
stun_with_username[24] = 't';
|
|
stun_with_username[25] = 'e';
|
|
stun_with_username[26] = 's';
|
|
stun_with_username[27] = 't';
|
|
stun_with_username[28] = ':';
|
|
stun_with_username[29] = 'u';
|
|
stun_with_username[30] = 's';
|
|
stun_with_username[31] = 'r';
|
|
|
|
HELPER_EXPECT_SUCCESS(stun.decode(stun_with_username, sizeof(stun_with_username)));
|
|
EXPECT_TRUE(stun.is_binding_request());
|
|
EXPECT_STREQ("test:usr", stun.get_username().c_str());
|
|
EXPECT_STREQ("test", stun.get_local_ufrag().c_str());
|
|
EXPECT_STREQ("usr", stun.get_remote_ufrag().c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackageStapA)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRtpVideoBuilder builder;
|
|
SrsFormat format;
|
|
uint32_t ssrc = 12345;
|
|
uint8_t payload_type = 96;
|
|
|
|
// Initialize format and setup video codec config (required for package_stap_a)
|
|
HELPER_EXPECT_SUCCESS(format.initialize());
|
|
|
|
// Setup video sequence header (H.264 AVC) with SPS/PPS data
|
|
// This is based on MockSrsFormat from srs_utest_fmp4.cpp
|
|
uint8_t video_raw[] = {
|
|
0x17, // keyframe + AVC
|
|
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};
|
|
|
|
// Parse the video sequence header to populate SPS/PPS
|
|
HELPER_EXPECT_SUCCESS(format.on_video(0, (char *)video_raw, sizeof(video_raw)));
|
|
|
|
// Verify that vcodec and SPS/PPS are properly set
|
|
EXPECT_TRUE(format.vcodec_ != NULL);
|
|
EXPECT_EQ(SrsVideoCodecIdAVC, format.vcodec_->id_);
|
|
EXPECT_FALSE(format.vcodec_->sequenceParameterSetNALUnit_.empty());
|
|
EXPECT_FALSE(format.vcodec_->pictureParameterSetNALUnit_.empty());
|
|
|
|
HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type));
|
|
|
|
// Create a media packet for testing
|
|
SrsMediaPacket *msg = new SrsMediaPacket();
|
|
SrsUniquePtr<SrsMediaPacket> msg_uptr(msg);
|
|
|
|
char *video_data = new char[10];
|
|
video_data[0] = 0x17; // keyframe + AVC
|
|
for (int i = 1; i < 10; i++) {
|
|
video_data[i] = i;
|
|
}
|
|
msg->wrap(video_data, 10);
|
|
msg->timestamp_ = 1000;
|
|
msg->message_type_ = SrsFrameTypeVideo;
|
|
|
|
// Create RTP packet for STAP-A
|
|
SrsRtpPacket *pkt = new SrsRtpPacket();
|
|
SrsUniquePtr<SrsRtpPacket> pkt_uptr(pkt);
|
|
|
|
// Test package_stap_a - should now work with proper SPS/PPS setup
|
|
HELPER_EXPECT_SUCCESS(builder.package_stap_a(msg, pkt));
|
|
|
|
// Verify RTP packet was properly configured
|
|
EXPECT_EQ(payload_type, pkt->header_.get_payload_type());
|
|
EXPECT_EQ(ssrc, pkt->header_.get_ssrc());
|
|
EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_);
|
|
EXPECT_EQ(1000 * 90, pkt->header_.get_timestamp()); // timestamp * 90
|
|
EXPECT_FALSE(pkt->header_.get_marker()); // STAP-A should not have marker bit set
|
|
|
|
// Verify STAP-A payload was created
|
|
EXPECT_TRUE(pkt->payload() != NULL);
|
|
|
|
// Verify it's a STAP-A payload by checking if we can cast to SrsRtpSTAPPayload
|
|
SrsRtpSTAPPayload *stap_payload = dynamic_cast<SrsRtpSTAPPayload *>(pkt->payload());
|
|
EXPECT_TRUE(stap_payload != NULL);
|
|
|
|
// Verify STAP-A contains SPS and PPS
|
|
EXPECT_TRUE(stap_payload->get_sps() != NULL);
|
|
EXPECT_TRUE(stap_payload->get_pps() != NULL);
|
|
}
|
|
|
|
VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackageNalusWithFormat)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRtpVideoBuilder builder;
|
|
SrsFormat format;
|
|
uint32_t ssrc = 54321;
|
|
uint8_t payload_type = 97;
|
|
|
|
// Initialize format and setup video codec config (required for package_nalus)
|
|
HELPER_EXPECT_SUCCESS(format.initialize());
|
|
|
|
// Setup video sequence header (H.264 AVC) with SPS/PPS data
|
|
uint8_t video_raw[] = {
|
|
0x17, // keyframe + AVC
|
|
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};
|
|
|
|
// Parse the video sequence header to populate SPS/PPS and vcodec
|
|
HELPER_EXPECT_SUCCESS(format.on_video(0, (char *)video_raw, sizeof(video_raw)));
|
|
|
|
// Verify that vcodec is properly set
|
|
EXPECT_TRUE(format.vcodec_ != NULL);
|
|
EXPECT_EQ(SrsVideoCodecIdAVC, format.vcodec_->id_);
|
|
|
|
HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type));
|
|
|
|
// Create a media packet
|
|
SrsMediaPacket *msg = new SrsMediaPacket();
|
|
SrsUniquePtr<SrsMediaPacket> msg_uptr(msg);
|
|
|
|
char *video_data = new char[20];
|
|
video_data[0] = 0x17; // keyframe + AVC
|
|
video_data[1] = 0x01; // AVC NALU
|
|
for (int i = 2; i < 20; i++) {
|
|
video_data[i] = i;
|
|
}
|
|
msg->wrap(video_data, 20);
|
|
msg->timestamp_ = 2000;
|
|
msg->message_type_ = SrsFrameTypeVideo;
|
|
|
|
// Create NALU samples for testing with proper H.264 NALU headers
|
|
std::vector<SrsNaluSample *> samples;
|
|
|
|
// Create SPS NALU sample
|
|
SrsNaluSample *sample1 = new SrsNaluSample();
|
|
uint8_t sps_data[] = {0x67, 0x64, 0x00, 0x20, 0xac}; // SPS NALU (type 7)
|
|
sample1->bytes_ = (char *)sps_data;
|
|
sample1->size_ = sizeof(sps_data);
|
|
samples.push_back(sample1);
|
|
|
|
// Create PPS NALU sample
|
|
SrsNaluSample *sample2 = new SrsNaluSample();
|
|
uint8_t pps_data[] = {0x68, 0xeb, 0xec, 0xb2}; // PPS NALU (type 8)
|
|
sample2->bytes_ = (char *)pps_data;
|
|
sample2->size_ = sizeof(pps_data);
|
|
samples.push_back(sample2);
|
|
|
|
std::vector<SrsRtpPacket *> pkts;
|
|
|
|
// Test package_nalus with proper format setup - should now work
|
|
HELPER_EXPECT_SUCCESS(builder.package_nalus(msg, samples, pkts));
|
|
|
|
// Should create at least one packet
|
|
EXPECT_GT((int)pkts.size(), 0);
|
|
|
|
// Verify first packet configuration
|
|
if (!pkts.empty()) {
|
|
SrsRtpPacket *pkt = pkts[0];
|
|
EXPECT_EQ(payload_type, pkt->header_.get_payload_type());
|
|
EXPECT_EQ(ssrc, pkt->header_.get_ssrc());
|
|
EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_);
|
|
EXPECT_EQ(2000 * 90, pkt->header_.get_timestamp()); // timestamp * 90
|
|
}
|
|
|
|
// Clean up any created packets
|
|
for (size_t i = 0; i < pkts.size(); i++) {
|
|
delete pkts[i];
|
|
}
|
|
pkts.clear();
|
|
|
|
// Clean up samples
|
|
delete sample1;
|
|
delete sample2;
|
|
}
|
|
|
|
VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackageSingleNalu)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRtpVideoBuilder builder;
|
|
SrsFormat format;
|
|
uint32_t ssrc = 11111;
|
|
uint8_t payload_type = 98;
|
|
|
|
HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type));
|
|
|
|
// Create a media packet
|
|
SrsMediaPacket *msg = new SrsMediaPacket();
|
|
SrsUniquePtr<SrsMediaPacket> msg_uptr(msg);
|
|
|
|
char *video_data = new char[15];
|
|
video_data[0] = 0x17; // keyframe + AVC
|
|
for (int i = 1; i < 15; i++) {
|
|
video_data[i] = i;
|
|
}
|
|
msg->wrap(video_data, 15);
|
|
msg->timestamp_ = 3000;
|
|
msg->message_type_ = SrsFrameTypeVideo;
|
|
|
|
// Create a single NALU sample
|
|
SrsNaluSample *sample = new SrsNaluSample();
|
|
sample->bytes_ = video_data + 2;
|
|
sample->size_ = 10;
|
|
|
|
std::vector<SrsRtpPacket *> pkts;
|
|
|
|
// Test package_single_nalu
|
|
HELPER_EXPECT_SUCCESS(builder.package_single_nalu(msg, sample, pkts));
|
|
|
|
// Should create exactly one packet
|
|
EXPECT_EQ(1, (int)pkts.size());
|
|
|
|
if (!pkts.empty()) {
|
|
SrsRtpPacket *pkt = pkts[0];
|
|
EXPECT_EQ(payload_type, pkt->header_.get_payload_type());
|
|
EXPECT_EQ(ssrc, pkt->header_.get_ssrc());
|
|
EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_);
|
|
EXPECT_EQ(3000 * 90, pkt->header_.get_timestamp()); // timestamp * 90
|
|
}
|
|
|
|
// Clean up packets
|
|
for (size_t i = 0; i < pkts.size(); i++) {
|
|
delete pkts[i];
|
|
}
|
|
pkts.clear();
|
|
|
|
// Clean up sample
|
|
delete sample;
|
|
}
|
|
|
|
VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackageFuA)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsRtpVideoBuilder builder;
|
|
SrsFormat format;
|
|
uint32_t ssrc = 22222;
|
|
uint8_t payload_type = 99;
|
|
|
|
// Initialize format and setup video codec config (required for package_fu_a)
|
|
HELPER_EXPECT_SUCCESS(format.initialize());
|
|
|
|
// Setup video sequence header (H.264 AVC) with SPS/PPS data
|
|
uint8_t video_raw[] = {
|
|
0x17, // keyframe + AVC
|
|
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};
|
|
|
|
// Parse the video sequence header to populate SPS/PPS and vcodec
|
|
HELPER_EXPECT_SUCCESS(format.on_video(0, (char *)video_raw, sizeof(video_raw)));
|
|
|
|
// Verify that vcodec is properly set
|
|
EXPECT_TRUE(format.vcodec_ != NULL);
|
|
EXPECT_EQ(SrsVideoCodecIdAVC, format.vcodec_->id_);
|
|
|
|
HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type));
|
|
|
|
// Create a media packet
|
|
SrsMediaPacket *msg = new SrsMediaPacket();
|
|
SrsUniquePtr<SrsMediaPacket> msg_uptr(msg);
|
|
|
|
char *video_data = new char[100];
|
|
video_data[0] = 0x17; // keyframe + AVC
|
|
video_data[1] = 0x65; // IDR NALU header (type 5)
|
|
for (int i = 2; i < 100; i++) {
|
|
video_data[i] = i;
|
|
}
|
|
msg->wrap(video_data, 100);
|
|
msg->timestamp_ = 4000;
|
|
msg->message_type_ = SrsFrameTypeVideo;
|
|
|
|
// Create a large NALU sample that requires FU-A fragmentation
|
|
SrsNaluSample *sample = new SrsNaluSample();
|
|
sample->bytes_ = video_data + 1; // Start from NALU header (IDR)
|
|
sample->size_ = 80; // Large enough to require fragmentation
|
|
|
|
std::vector<SrsRtpPacket *> pkts;
|
|
int fu_payload_size = 30; // Small payload size to force fragmentation
|
|
|
|
// Test package_fu_a - should now work with proper format setup
|
|
HELPER_EXPECT_SUCCESS(builder.package_fu_a(msg, sample, fu_payload_size, pkts));
|
|
|
|
// Should create multiple packets due to fragmentation
|
|
EXPECT_GT((int)pkts.size(), 1);
|
|
|
|
// Verify first packet configuration
|
|
if (!pkts.empty()) {
|
|
SrsRtpPacket *pkt = pkts[0];
|
|
EXPECT_EQ(payload_type, pkt->header_.get_payload_type());
|
|
EXPECT_EQ(ssrc, pkt->header_.get_ssrc());
|
|
EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_);
|
|
EXPECT_EQ(4000 * 90, pkt->header_.get_timestamp()); // timestamp * 90
|
|
|
|
// Verify it's a FU-A payload
|
|
SrsRtpFUAPayload2 *fua_payload = dynamic_cast<SrsRtpFUAPayload2 *>(pkt->payload());
|
|
EXPECT_TRUE(fua_payload != NULL);
|
|
|
|
if (fua_payload) {
|
|
// First packet should have start bit set
|
|
EXPECT_TRUE(fua_payload->start_);
|
|
EXPECT_FALSE(fua_payload->end_);
|
|
}
|
|
}
|
|
|
|
// Last packet should have end bit set
|
|
if (pkts.size() > 1) {
|
|
SrsRtpPacket *last_pkt = pkts[pkts.size() - 1];
|
|
SrsRtpFUAPayload2 *last_fua = dynamic_cast<SrsRtpFUAPayload2 *>(last_pkt->payload());
|
|
EXPECT_TRUE(last_fua != NULL);
|
|
|
|
if (last_fua) {
|
|
EXPECT_FALSE(last_fua->start_);
|
|
EXPECT_TRUE(last_fua->end_);
|
|
}
|
|
}
|
|
|
|
// Clean up any created packets
|
|
for (size_t i = 0; i < pkts.size(); i++) {
|
|
delete pkts[i];
|
|
}
|
|
pkts.clear();
|
|
|
|
// Clean up sample
|
|
delete sample;
|
|
}
|
|
|
|
VOID TEST(ProtocolConnTest, SrsBufferedReadWriterPeek)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// Create mock IO with test data
|
|
MockBufferIO mock_io;
|
|
mock_io.append("Hello World Test Data");
|
|
|
|
SrsBufferedReadWriter buffered(&mock_io);
|
|
|
|
// Test peek functionality
|
|
char peek_buf[10];
|
|
int peek_size = sizeof(peek_buf);
|
|
|
|
HELPER_EXPECT_SUCCESS(buffered.peek(peek_buf, &peek_size));
|
|
|
|
// Should have peeked some data
|
|
EXPECT_GT(peek_size, 0);
|
|
EXPECT_LE(peek_size, (int)sizeof(peek_buf));
|
|
|
|
// Test peek with smaller buffer
|
|
char small_buf[5];
|
|
int small_size = sizeof(small_buf);
|
|
|
|
HELPER_EXPECT_SUCCESS(buffered.peek(small_buf, &small_size));
|
|
EXPECT_GT(small_size, 0);
|
|
EXPECT_LE(small_size, (int)sizeof(small_buf));
|
|
}
|
|
|
|
VOID TEST(ProtocolConnTest, SrsBufferedReadWriterRead)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// Create mock IO with test data
|
|
MockBufferIO mock_io;
|
|
mock_io.append("Hello World");
|
|
|
|
SrsBufferedReadWriter buffered(&mock_io);
|
|
|
|
// Test read functionality
|
|
char read_buf[20];
|
|
ssize_t nread = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(buffered.read(read_buf, sizeof(read_buf), &nread));
|
|
|
|
// Should have read some data
|
|
EXPECT_GT(nread, 0);
|
|
EXPECT_LE(nread, (ssize_t)sizeof(read_buf));
|
|
|
|
// Test read with smaller buffer
|
|
mock_io.append("More Data");
|
|
char small_buf[5];
|
|
ssize_t small_nread = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(buffered.read(small_buf, sizeof(small_buf), &small_nread));
|
|
EXPECT_GT(small_nread, 0);
|
|
EXPECT_LE(small_nread, (ssize_t)sizeof(small_buf));
|
|
}
|
|
|
|
VOID TEST(ProtocolConnTest, SrsBufferedReadWriterReadFully)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// Create mock IO with sufficient test data
|
|
MockBufferIO mock_io;
|
|
mock_io.append("Hello World Test Data For Read Fully");
|
|
|
|
SrsBufferedReadWriter buffered(&mock_io);
|
|
|
|
// Test read_fully functionality
|
|
char read_buf[10];
|
|
ssize_t nread = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(buffered.read_fully(read_buf, sizeof(read_buf), &nread));
|
|
|
|
// read_fully should read exactly the requested amount or fail
|
|
EXPECT_EQ((ssize_t)sizeof(read_buf), nread);
|
|
|
|
// Test read_fully with smaller amount
|
|
char small_buf[5];
|
|
ssize_t small_nread = 0;
|
|
|
|
HELPER_EXPECT_SUCCESS(buffered.read_fully(small_buf, sizeof(small_buf), &small_nread));
|
|
EXPECT_EQ((ssize_t)sizeof(small_buf), small_nread);
|
|
}
|
|
|
|
VOID TEST(ProtocolConnTest, SrsBufferedReadWriterReloadBuffer)
|
|
{
|
|
// Test that reload_buffer method exists and is accessible through peek
|
|
// Since reload_buffer is private, we test it indirectly through peek
|
|
|
|
MockBufferIO mock_io;
|
|
mock_io.append("Test Data For Buffer Reload");
|
|
|
|
SrsBufferedReadWriter buffered(&mock_io);
|
|
|
|
// First peek should trigger reload_buffer
|
|
char peek_buf[10];
|
|
int peek_size = sizeof(peek_buf);
|
|
|
|
srs_error_t err = buffered.peek(peek_buf, &peek_size);
|
|
HELPER_EXPECT_SUCCESS(err);
|
|
|
|
// Should have loaded data into buffer
|
|
EXPECT_GT(peek_size, 0);
|
|
|
|
// Second peek should use cached data (no reload needed)
|
|
char peek_buf2[5];
|
|
int peek_size2 = sizeof(peek_buf2);
|
|
|
|
HELPER_EXPECT_SUCCESS(buffered.peek(peek_buf2, &peek_size2));
|
|
EXPECT_GT(peek_size2, 0);
|
|
}
|
|
|
|
VOID TEST(ProtocolConnTest, SrsSslConnectionRead)
|
|
{
|
|
// Use available test certificates
|
|
std::string key_file = "conf/server.key";
|
|
std::string crt_file = "conf/server.crt";
|
|
|
|
// Create mock transport for SSL connection testing
|
|
MockBufferIO mock_io;
|
|
mock_io.append("SSL encrypted test data for reading");
|
|
|
|
// Create SSL connection with mock transport
|
|
SrsSslConnection ssl_conn(&mock_io);
|
|
|
|
// Test timeout methods
|
|
ssl_conn.set_recv_timeout(5 * SRS_UTIME_SECONDS);
|
|
srs_utime_t timeout = ssl_conn.get_recv_timeout();
|
|
EXPECT_EQ(5 * SRS_UTIME_SECONDS, timeout);
|
|
|
|
ssl_conn.set_send_timeout(3 * SRS_UTIME_SECONDS);
|
|
srs_utime_t send_timeout = ssl_conn.get_send_timeout();
|
|
EXPECT_EQ(3 * SRS_UTIME_SECONDS, send_timeout);
|
|
|
|
// Test byte counters
|
|
int64_t recv_bytes = ssl_conn.get_recv_bytes();
|
|
int64_t send_bytes = ssl_conn.get_send_bytes();
|
|
EXPECT_GE(recv_bytes, 0);
|
|
EXPECT_GE(send_bytes, 0);
|
|
|
|
// Test handshake method exists (will fail without proper SSL setup but shouldn't crash)
|
|
srs_error_t handshake_err = ssl_conn.handshake(key_file, crt_file);
|
|
srs_freep(handshake_err); // Expected to fail due to mock transport, just testing interface
|
|
|
|
// Test read method exists (will fail without handshake but shouldn't crash)
|
|
char read_buf[100];
|
|
ssize_t nread = 0;
|
|
srs_error_t read_err = ssl_conn.read(read_buf, sizeof(read_buf), &nread);
|
|
srs_freep(read_err); // Expected to fail, just testing interface
|
|
|
|
// Test read_fully method exists
|
|
srs_error_t read_fully_err = ssl_conn.read_fully(read_buf, 10, &nread);
|
|
srs_freep(read_fully_err); // Expected to fail, just testing interface
|
|
|
|
// Test write method exists
|
|
const char *test_data = "Hello SSL World";
|
|
ssize_t nwrite = 0;
|
|
srs_error_t write_err = ssl_conn.write((void *)test_data, strlen(test_data), &nwrite);
|
|
srs_freep(write_err); // Expected to fail, just testing interface
|
|
|
|
// Test writev method exists
|
|
struct iovec iov[1];
|
|
iov[0].iov_base = (void *)test_data;
|
|
iov[0].iov_len = strlen(test_data);
|
|
srs_error_t writev_err = ssl_conn.writev(iov, 1, &nwrite);
|
|
srs_freep(writev_err); // Expected to fail, just testing interface
|
|
|
|
// Test passed - all SSL connection methods exist and can be called safely
|
|
EXPECT_TRUE(true);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsParseH264Fmtp)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// Test valid H264 fmtp parsing
|
|
H264SpecificParam h264_param;
|
|
std::string fmtp = "profile-level-id=42e01e;packetization-mode=1;level-asymmetry-allowed=1";
|
|
|
|
HELPER_EXPECT_SUCCESS(srs_parse_h264_fmtp(fmtp, h264_param));
|
|
|
|
EXPECT_STREQ("42e01e", h264_param.profile_level_id_.c_str());
|
|
EXPECT_STREQ("1", h264_param.packetization_mode_.c_str());
|
|
EXPECT_STREQ("1", h264_param.level_asymmetry_allow_.c_str());
|
|
|
|
// Test partial fmtp parsing (should fail due to missing packetization-mode)
|
|
H264SpecificParam h264_param2;
|
|
std::string fmtp2 = "profile-level-id=640028";
|
|
|
|
srs_error_t partial_err = srs_parse_h264_fmtp(fmtp2, h264_param2);
|
|
srs_freep(partial_err); // Expected to fail due to missing packetization-mode
|
|
|
|
// Test empty fmtp (should fail due to missing required parameters)
|
|
H264SpecificParam h264_param3;
|
|
std::string fmtp3 = "";
|
|
|
|
srs_error_t parse_err = srs_parse_h264_fmtp(fmtp3, h264_param3);
|
|
srs_freep(parse_err); // Expected to fail, just testing interface
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSessionInfoParseAttribute)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSessionInfo session_info;
|
|
|
|
// Test ice-ufrag attribute
|
|
HELPER_EXPECT_SUCCESS(session_info.parse_attribute("ice-ufrag", "test_ufrag"));
|
|
EXPECT_STREQ("test_ufrag", session_info.ice_ufrag_.c_str());
|
|
|
|
// Test ice-pwd attribute
|
|
HELPER_EXPECT_SUCCESS(session_info.parse_attribute("ice-pwd", "test_password"));
|
|
EXPECT_STREQ("test_password", session_info.ice_pwd_.c_str());
|
|
|
|
// Test ice-options attribute
|
|
HELPER_EXPECT_SUCCESS(session_info.parse_attribute("ice-options", "trickle"));
|
|
EXPECT_STREQ("trickle", session_info.ice_options_.c_str());
|
|
|
|
// Test fingerprint attribute
|
|
HELPER_EXPECT_SUCCESS(session_info.parse_attribute("fingerprint", "sha-256 AA:BB:CC:DD:EE:FF"));
|
|
EXPECT_STREQ("sha-256", session_info.fingerprint_algo_.c_str());
|
|
EXPECT_STREQ("AA:BB:CC:DD:EE:FF", session_info.fingerprint_.c_str());
|
|
|
|
// Test setup attribute
|
|
HELPER_EXPECT_SUCCESS(session_info.parse_attribute("setup", "active"));
|
|
EXPECT_STREQ("active", session_info.setup_.c_str());
|
|
|
|
// Test unknown attribute (should not fail)
|
|
HELPER_EXPECT_SUCCESS(session_info.parse_attribute("unknown", "value"));
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSessionInfoOperatorAssign)
|
|
{
|
|
SrsSessionInfo session1;
|
|
session1.ice_ufrag_ = "ufrag1";
|
|
session1.ice_pwd_ = "pwd1";
|
|
session1.ice_options_ = "trickle";
|
|
session1.fingerprint_algo_ = "sha-256";
|
|
session1.fingerprint_ = "AA:BB:CC";
|
|
session1.setup_ = "active";
|
|
|
|
SrsSessionInfo session2;
|
|
session2.ice_ufrag_ = "ufrag2";
|
|
session2.ice_pwd_ = "pwd2";
|
|
|
|
// Test assignment operator
|
|
session2 = session1;
|
|
|
|
EXPECT_STREQ("ufrag1", session2.ice_ufrag_.c_str());
|
|
EXPECT_STREQ("pwd1", session2.ice_pwd_.c_str());
|
|
EXPECT_STREQ("trickle", session2.ice_options_.c_str());
|
|
EXPECT_STREQ("sha-256", session2.fingerprint_algo_.c_str());
|
|
EXPECT_STREQ("AA:BB:CC", session2.fingerprint_.c_str());
|
|
EXPECT_STREQ("active", session2.setup_.c_str());
|
|
|
|
// Test equality operator
|
|
EXPECT_TRUE(session1 == session2);
|
|
|
|
// Test inequality
|
|
session2.ice_ufrag_ = "different";
|
|
EXPECT_FALSE(session1 == session2);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSSRCInfoEncode)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// Test normal SSRC encoding
|
|
SrsSSRCInfo ssrc_info(12345, "test_cname", "stream_id", "track_id");
|
|
|
|
std::ostringstream os;
|
|
HELPER_EXPECT_SUCCESS(ssrc_info.encode(os));
|
|
|
|
std::string result = os.str();
|
|
EXPECT_TRUE(result.find("a=ssrc:12345 cname:test_cname") != std::string::npos);
|
|
EXPECT_TRUE(result.find("a=ssrc:12345 msid:stream_id track_id") != std::string::npos);
|
|
|
|
// Test GB28181 SSRC encoding
|
|
SrsSSRCInfo gb_ssrc_info(67890, "gb_cname", "", "");
|
|
gb_ssrc_info.label_ = "gb28181";
|
|
|
|
std::ostringstream os2;
|
|
HELPER_EXPECT_SUCCESS(gb_ssrc_info.encode(os2));
|
|
|
|
std::string gb_result = os2.str();
|
|
EXPECT_TRUE(gb_result.find("y=gb_cname") != std::string::npos);
|
|
|
|
// Test invalid SSRC (should fail)
|
|
SrsSSRCInfo invalid_ssrc;
|
|
invalid_ssrc.ssrc_ = 0;
|
|
|
|
std::ostringstream os3;
|
|
srs_error_t encode_err = invalid_ssrc.encode(os3);
|
|
HELPER_EXPECT_FAILED(encode_err);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSSRCGroupEncode)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// Test valid SSRC group encoding
|
|
std::vector<uint32_t> ssrcs;
|
|
ssrcs.push_back(12345);
|
|
ssrcs.push_back(67890);
|
|
|
|
SrsSSRCGroup ssrc_group("FID", ssrcs);
|
|
|
|
std::ostringstream os;
|
|
HELPER_EXPECT_SUCCESS(ssrc_group.encode(os));
|
|
|
|
std::string result = os.str();
|
|
EXPECT_TRUE(result.find("a=ssrc-group:FID 12345 67890") != std::string::npos);
|
|
|
|
// Test invalid semantic (should fail)
|
|
SrsSSRCGroup invalid_group;
|
|
invalid_group.semantic_ = "";
|
|
invalid_group.ssrcs_.push_back(12345);
|
|
|
|
std::ostringstream os2;
|
|
srs_error_t encode_err = invalid_group.encode(os2);
|
|
HELPER_EXPECT_FAILED(encode_err);
|
|
|
|
// Test empty SSRCs (should fail)
|
|
SrsSSRCGroup empty_group;
|
|
empty_group.semantic_ = "FID";
|
|
// ssrcs_ is empty
|
|
|
|
std::ostringstream os3;
|
|
srs_error_t encode_err2 = empty_group.encode(os3);
|
|
HELPER_EXPECT_FAILED(encode_err2);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescUpdateMsid)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Add some SSRC info
|
|
SrsSSRCInfo ssrc1(12345, "cname1", "old_stream", "old_track");
|
|
SrsSSRCInfo ssrc2(67890, "cname2", "old_stream", "old_track");
|
|
media_desc.ssrc_infos_.push_back(ssrc1);
|
|
media_desc.ssrc_infos_.push_back(ssrc2);
|
|
|
|
// Update MSID
|
|
HELPER_EXPECT_SUCCESS(media_desc.update_msid("new_stream_id"));
|
|
|
|
// Verify all SSRC infos were updated
|
|
EXPECT_STREQ("new_stream_id", media_desc.ssrc_infos_[0].msid_.c_str());
|
|
EXPECT_STREQ("new_stream_id", media_desc.ssrc_infos_[0].mslabel_.c_str());
|
|
EXPECT_STREQ("new_stream_id", media_desc.ssrc_infos_[1].msid_.c_str());
|
|
EXPECT_STREQ("new_stream_id", media_desc.ssrc_infos_[1].mslabel_.c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttributeDirections)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Add a payload type for testing
|
|
media_desc.payload_types_.push_back(SrsMediaPayloadType(96));
|
|
|
|
// Test rtcp-mux attribute
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp-mux"));
|
|
EXPECT_TRUE(media_desc.rtcp_mux_);
|
|
|
|
// Test rtcp-rsize attribute
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp-rsize"));
|
|
EXPECT_TRUE(media_desc.rtcp_rsize_);
|
|
|
|
// Test sendonly attribute
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=sendonly"));
|
|
EXPECT_TRUE(media_desc.sendonly_);
|
|
|
|
// Test recvonly attribute
|
|
SrsMediaDesc media_desc2("audio");
|
|
HELPER_EXPECT_SUCCESS(media_desc2.parse_line("a=recvonly"));
|
|
EXPECT_TRUE(media_desc2.recvonly_);
|
|
|
|
// Test sendrecv attribute
|
|
SrsMediaDesc media_desc3("video");
|
|
HELPER_EXPECT_SUCCESS(media_desc3.parse_line("a=sendrecv"));
|
|
EXPECT_TRUE(media_desc3.sendrecv_);
|
|
|
|
// Test inactive attribute
|
|
SrsMediaDesc media_desc4("audio");
|
|
HELPER_EXPECT_SUCCESS(media_desc4.parse_line("a=inactive"));
|
|
EXPECT_TRUE(media_desc4.inactive_);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrExtmap)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Test valid extmap parsing
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level"));
|
|
|
|
const std::map<int, std::string> &extmaps = media_desc.get_extmaps();
|
|
EXPECT_EQ(1, (int)extmaps.size());
|
|
EXPECT_STREQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level", extmaps.at(1).c_str());
|
|
|
|
// Test another extmap
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=extmap:2 urn:ietf:params:rtp-hdrext:toffset"));
|
|
EXPECT_EQ(2, (int)extmaps.size());
|
|
EXPECT_STREQ("urn:ietf:params:rtp-hdrext:toffset", extmaps.at(2).c_str());
|
|
|
|
// Test duplicate extmap ID (should fail)
|
|
srs_error_t dup_err = media_desc.parse_line("a=extmap:1 urn:ietf:params:rtp-hdrext:duplicate");
|
|
HELPER_EXPECT_FAILED(dup_err);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrRtcp)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Test rtcp attribute parsing (currently just returns success)
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp:9 IN IP4 224.2.17.12"));
|
|
|
|
// The current implementation is a TODO, so we just verify it doesn't crash
|
|
EXPECT_TRUE(true);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrRtcpFb)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Add payload type for testing
|
|
media_desc.payload_types_.push_back(SrsMediaPayloadType(96));
|
|
|
|
// Test rtcp-fb attribute parsing
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp-fb:96 nack"));
|
|
|
|
SrsMediaPayloadType *payload = media_desc.find_media_with_payload_type(96);
|
|
EXPECT_TRUE(payload != NULL);
|
|
EXPECT_EQ(1, (int)payload->rtcp_fb_.size());
|
|
EXPECT_STREQ("nack", payload->rtcp_fb_[0].c_str());
|
|
|
|
// Test another rtcp-fb
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp-fb:96 nack pli"));
|
|
EXPECT_EQ(2, (int)payload->rtcp_fb_.size());
|
|
EXPECT_STREQ("nack pli", payload->rtcp_fb_[1].c_str());
|
|
|
|
// Test invalid payload type (should fail)
|
|
srs_error_t invalid_err = media_desc.parse_line("a=rtcp-fb:97 nack");
|
|
HELPER_EXPECT_FAILED(invalid_err);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrFmtp)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Add payload type for testing
|
|
media_desc.payload_types_.push_back(SrsMediaPayloadType(96));
|
|
|
|
// Test fmtp attribute parsing
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=fmtp:96 profile-level-id=42e01e"));
|
|
|
|
SrsMediaPayloadType *payload = media_desc.find_media_with_payload_type(96);
|
|
EXPECT_TRUE(payload != NULL);
|
|
EXPECT_STREQ("profile-level-id=42e01e", payload->format_specific_param_.c_str());
|
|
|
|
// Test invalid payload type (should fail)
|
|
srs_error_t invalid_err = media_desc.parse_line("a=fmtp:97 param=value");
|
|
HELPER_EXPECT_FAILED(invalid_err);
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrMid)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Test mid attribute parsing
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=mid:video"));
|
|
EXPECT_STREQ("video", media_desc.mid_.c_str());
|
|
|
|
// Test numeric mid
|
|
SrsMediaDesc media_desc2("audio");
|
|
HELPER_EXPECT_SUCCESS(media_desc2.parse_line("a=mid:0"));
|
|
EXPECT_STREQ("0", media_desc2.mid_.c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrMsid)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Test msid attribute parsing with both stream and track
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=msid:stream_id track_id"));
|
|
EXPECT_STREQ("stream_id", media_desc.msid_.c_str());
|
|
EXPECT_STREQ("track_id", media_desc.msid_tracker_.c_str());
|
|
|
|
// Test msid with only stream ID
|
|
SrsMediaDesc media_desc2("audio");
|
|
HELPER_EXPECT_SUCCESS(media_desc2.parse_line("a=msid:stream_only"));
|
|
EXPECT_STREQ("stream_only", media_desc2.msid_.c_str());
|
|
EXPECT_TRUE(media_desc2.msid_tracker_.empty());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrSsrc)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Test ssrc cname attribute
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:12345 cname:test_cname"));
|
|
|
|
EXPECT_EQ(1, (int)media_desc.ssrc_infos_.size());
|
|
EXPECT_EQ(12345, media_desc.ssrc_infos_[0].ssrc_);
|
|
EXPECT_STREQ("test_cname", media_desc.ssrc_infos_[0].cname_.c_str());
|
|
|
|
// Test ssrc msid attribute
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:12345 msid:stream_id track_id"));
|
|
EXPECT_STREQ("stream_id", media_desc.ssrc_infos_[0].msid_.c_str());
|
|
EXPECT_STREQ("track_id", media_desc.ssrc_infos_[0].msid_tracker_.c_str());
|
|
|
|
// Test ssrc mslabel attribute
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:12345 mslabel:media_stream"));
|
|
EXPECT_STREQ("media_stream", media_desc.ssrc_infos_[0].mslabel_.c_str());
|
|
|
|
// Test ssrc label attribute
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:12345 label:video_track"));
|
|
EXPECT_STREQ("video_track", media_desc.ssrc_infos_[0].label_.c_str());
|
|
|
|
// Test new SSRC
|
|
HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:67890 cname:another_cname"));
|
|
EXPECT_EQ(2, (int)media_desc.ssrc_infos_.size());
|
|
EXPECT_EQ(67890, media_desc.ssrc_infos_[1].ssrc_);
|
|
EXPECT_STREQ("another_cname", media_desc.ssrc_infos_[1].cname_.c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsMediaDescFetchOrCreateSsrcInfo)
|
|
{
|
|
SrsMediaDesc media_desc("video");
|
|
|
|
// Test creating new SSRC info
|
|
SrsSSRCInfo &ssrc_info1 = media_desc.fetch_or_create_ssrc_info(12345);
|
|
EXPECT_EQ(12345, ssrc_info1.ssrc_);
|
|
EXPECT_EQ(1, (int)media_desc.ssrc_infos_.size());
|
|
|
|
// Test fetching existing SSRC info
|
|
SrsSSRCInfo &ssrc_info2 = media_desc.fetch_or_create_ssrc_info(12345);
|
|
EXPECT_EQ(12345, ssrc_info2.ssrc_);
|
|
EXPECT_EQ(1, (int)media_desc.ssrc_infos_.size()); // Should not create new one
|
|
|
|
// Verify they are the same object
|
|
EXPECT_EQ(&ssrc_info1, &ssrc_info2);
|
|
|
|
// Test creating another SSRC info
|
|
SrsSSRCInfo &ssrc_info3 = media_desc.fetch_or_create_ssrc_info(67890);
|
|
EXPECT_EQ(67890, ssrc_info3.ssrc_);
|
|
EXPECT_EQ(2, (int)media_desc.ssrc_infos_.size());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpSetGetIceCredentials)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSdp sdp;
|
|
|
|
// Parse SDP with media descriptions
|
|
std::string sdp_str =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 9 RTP/AVP 96\r\n"
|
|
"m=audio 9 RTP/AVP 97\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str));
|
|
|
|
// Test setting ICE credentials
|
|
sdp.set_ice_ufrag("test_ufrag");
|
|
sdp.set_ice_pwd("test_password");
|
|
|
|
// Test getting ICE credentials
|
|
EXPECT_STREQ("test_ufrag", sdp.get_ice_ufrag().c_str());
|
|
EXPECT_STREQ("test_password", sdp.get_ice_pwd().c_str());
|
|
|
|
// Verify all media descriptions were updated
|
|
std::vector<SrsMediaDesc *> video_descs = sdp.find_media_descs("video");
|
|
std::vector<SrsMediaDesc *> audio_descs = sdp.find_media_descs("audio");
|
|
|
|
EXPECT_STREQ("test_ufrag", video_descs[0]->session_info_.ice_ufrag_.c_str());
|
|
EXPECT_STREQ("test_password", video_descs[0]->session_info_.ice_pwd_.c_str());
|
|
EXPECT_STREQ("test_ufrag", audio_descs[0]->session_info_.ice_ufrag_.c_str());
|
|
EXPECT_STREQ("test_password", audio_descs[0]->session_info_.ice_pwd_.c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpSetGetDtlsRole)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSdp sdp;
|
|
|
|
// Parse SDP with media descriptions
|
|
std::string sdp_str =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 9 RTP/AVP 96\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str));
|
|
|
|
// Test setting DTLS role
|
|
sdp.set_dtls_role("active");
|
|
|
|
// Test getting DTLS role
|
|
EXPECT_STREQ("active", sdp.get_dtls_role().c_str());
|
|
|
|
// Verify media description was updated
|
|
std::vector<SrsMediaDesc *> video_descs = sdp.find_media_descs("video");
|
|
EXPECT_STREQ("active", video_descs[0]->session_info_.setup_.c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpSetFingerprint)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSdp sdp;
|
|
|
|
// Parse SDP with media descriptions
|
|
std::string sdp_str =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 9 RTP/AVP 96\r\n"
|
|
"m=audio 9 RTP/AVP 97\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str));
|
|
|
|
// Test setting fingerprint algorithm and value
|
|
sdp.set_fingerprint_algo("sha-256");
|
|
sdp.set_fingerprint("AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99");
|
|
|
|
// Verify all media descriptions were updated
|
|
std::vector<SrsMediaDesc *> video_descs = sdp.find_media_descs("video");
|
|
std::vector<SrsMediaDesc *> audio_descs = sdp.find_media_descs("audio");
|
|
|
|
EXPECT_STREQ("sha-256", video_descs[0]->session_info_.fingerprint_algo_.c_str());
|
|
EXPECT_STREQ("AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
|
video_descs[0]->session_info_.fingerprint_.c_str());
|
|
EXPECT_STREQ("sha-256", audio_descs[0]->session_info_.fingerprint_algo_.c_str());
|
|
EXPECT_STREQ("AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
|
audio_descs[0]->session_info_.fingerprint_.c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpAddCandidate)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSdp sdp;
|
|
|
|
// Parse SDP with media descriptions
|
|
std::string sdp_str =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 9 RTP/AVP 96\r\n"
|
|
"m=audio 9 RTP/AVP 97\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str));
|
|
|
|
// Test adding UDP candidate
|
|
sdp.add_candidate("udp", "192.168.1.100", 5000, "host");
|
|
|
|
// Test adding TCP candidate
|
|
sdp.add_candidate("tcp", "192.168.1.100", 5001, "host");
|
|
|
|
// Verify candidates were added to all media descriptions
|
|
std::vector<SrsMediaDesc *> video_descs = sdp.find_media_descs("video");
|
|
std::vector<SrsMediaDesc *> audio_descs = sdp.find_media_descs("audio");
|
|
|
|
EXPECT_EQ(2, (int)video_descs[0]->candidates_.size());
|
|
EXPECT_EQ(2, (int)audio_descs[0]->candidates_.size());
|
|
|
|
// Check UDP candidate
|
|
EXPECT_STREQ("udp", video_descs[0]->candidates_[0].protocol_.c_str());
|
|
EXPECT_STREQ("192.168.1.100", video_descs[0]->candidates_[0].ip_.c_str());
|
|
EXPECT_EQ(5000, video_descs[0]->candidates_[0].port_);
|
|
EXPECT_STREQ("host", video_descs[0]->candidates_[0].type_.c_str());
|
|
|
|
// Check TCP candidate
|
|
EXPECT_STREQ("tcp", video_descs[0]->candidates_[1].protocol_.c_str());
|
|
EXPECT_STREQ("192.168.1.100", video_descs[0]->candidates_[1].ip_.c_str());
|
|
EXPECT_EQ(5001, video_descs[0]->candidates_[1].port_);
|
|
EXPECT_STREQ("host", video_descs[0]->candidates_[1].type_.c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpParseAttribute)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSdp sdp;
|
|
|
|
// Test parsing group attribute
|
|
HELPER_EXPECT_SUCCESS(sdp.parse_line("a=group:BUNDLE video audio"));
|
|
|
|
// Test parsing msid-semantic attribute
|
|
HELPER_EXPECT_SUCCESS(sdp.parse_line("a=msid-semantic: WMS stream_id"));
|
|
EXPECT_STREQ("WMS", sdp.msid_semantic_.c_str());
|
|
EXPECT_EQ(1, (int)sdp.msids_.size());
|
|
EXPECT_STREQ("stream_id", sdp.msids_[0].c_str());
|
|
|
|
// Test parsing session-level ICE attributes
|
|
HELPER_EXPECT_SUCCESS(sdp.parse_line("a=ice-ufrag:session_ufrag"));
|
|
HELPER_EXPECT_SUCCESS(sdp.parse_line("a=ice-pwd:session_password"));
|
|
|
|
EXPECT_STREQ("session_ufrag", sdp.session_info_.ice_ufrag_.c_str());
|
|
EXPECT_STREQ("session_password", sdp.session_info_.ice_pwd_.c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpParseGb28181Ssrc)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSdp sdp;
|
|
|
|
// Parse basic SDP first
|
|
std::string sdp_str =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 9 RTP/AVP 96\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str));
|
|
|
|
// Test parsing GB28181 SSRC
|
|
HELPER_EXPECT_SUCCESS(sdp.parse_line("y=0100008888"));
|
|
|
|
// Verify GB28181 SSRC was converted to standard format
|
|
std::vector<SrsMediaDesc *> video_descs = sdp.find_media_descs("video");
|
|
EXPECT_EQ(1, (int)video_descs[0]->ssrc_infos_.size());
|
|
EXPECT_STREQ("0100008888", video_descs[0]->ssrc_infos_[0].cname_.c_str());
|
|
EXPECT_STREQ("gb28181", video_descs[0]->ssrc_infos_[0].label_.c_str());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpIsUnified)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// Test non-unified SDP (2 or fewer media descriptions)
|
|
SrsSdp sdp1;
|
|
std::string sdp_str1 =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 9 RTP/AVP 96\r\n"
|
|
"m=audio 9 RTP/AVP 97\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp1.parse(sdp_str1));
|
|
EXPECT_FALSE(sdp1.is_unified());
|
|
|
|
// Test unified SDP (more than 2 media descriptions)
|
|
SrsSdp sdp2;
|
|
std::string sdp_str2 =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 9 RTP/AVP 96\r\n"
|
|
"m=audio 9 RTP/AVP 97\r\n"
|
|
"m=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp2.parse(sdp_str2));
|
|
EXPECT_TRUE(sdp2.is_unified());
|
|
}
|
|
|
|
VOID TEST(ProtocolSdpTest, SrsSdpUpdateMsid)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
SrsSdp sdp;
|
|
|
|
// Parse SDP with media descriptions and SSRC info
|
|
std::string sdp_str =
|
|
"v=0\r\n"
|
|
"o=- 123456 654321 IN IP4 127.0.0.1\r\n"
|
|
"s=Test Session\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 9 RTP/AVP 96\r\n"
|
|
"a=ssrc:12345 cname:video_cname\r\n"
|
|
"m=audio 9 RTP/AVP 97\r\n"
|
|
"a=ssrc:67890 cname:audio_cname\r\n";
|
|
|
|
HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str));
|
|
|
|
// Update MSID
|
|
HELPER_EXPECT_SUCCESS(sdp.update_msid("new_stream_id"));
|
|
|
|
// Verify global MSID was updated
|
|
EXPECT_EQ(1, (int)sdp.msids_.size());
|
|
EXPECT_STREQ("new_stream_id", sdp.msids_[0].c_str());
|
|
|
|
// Verify all media descriptions were updated
|
|
std::vector<SrsMediaDesc *> video_descs = sdp.find_media_descs("video");
|
|
std::vector<SrsMediaDesc *> audio_descs = sdp.find_media_descs("audio");
|
|
|
|
EXPECT_STREQ("new_stream_id", video_descs[0]->ssrc_infos_[0].msid_.c_str());
|
|
EXPECT_STREQ("new_stream_id", video_descs[0]->ssrc_infos_[0].mslabel_.c_str());
|
|
EXPECT_STREQ("new_stream_id", audio_descs[0]->ssrc_infos_[0].msid_.c_str());
|
|
EXPECT_STREQ("new_stream_id", audio_descs[0]->ssrc_infos_[0].mslabel_.c_str());
|
|
}
|
|
|
|
VOID TEST(RawHEVCStreamTest, VpsDemux)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Test vps_demux method with valid VPS data
|
|
if (true) {
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Create mock VPS NALU data (HEVC VPS type 32)
|
|
char vps_data[] = {
|
|
(char)((32 << 1) | 0), // HEVC VPS NALU type 32, layer_id=0
|
|
(char)1, // layer_id bits + temporal_id=1
|
|
0x01, 0x02, 0x03, 0x04 // Mock VPS payload
|
|
};
|
|
int vps_size = sizeof(vps_data);
|
|
|
|
string vps_output;
|
|
HELPER_ASSERT_SUCCESS(hevc.vps_demux(vps_data, vps_size, vps_output));
|
|
|
|
// Verify VPS output matches input
|
|
EXPECT_EQ(vps_size, (int)vps_output.length());
|
|
EXPECT_EQ(0, memcmp(vps_data, vps_output.data(), vps_size));
|
|
}
|
|
|
|
// Test vps_demux with empty frame (should fail)
|
|
if (true) {
|
|
SrsRawHEVCStream hevc;
|
|
|
|
string vps_output;
|
|
HELPER_EXPECT_FAILED(hevc.vps_demux(NULL, 0, vps_output));
|
|
}
|
|
|
|
// Test vps_demux with negative frame size (should fail)
|
|
if (true) {
|
|
SrsRawHEVCStream hevc;
|
|
|
|
char dummy_data[] = {0x01, 0x02};
|
|
string vps_output;
|
|
HELPER_EXPECT_FAILED(hevc.vps_demux(dummy_data, -1, vps_output));
|
|
}
|
|
}
|
|
|
|
VOID TEST(RawHEVCStreamTest, SpsDemux)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Test sps_demux method with valid SPS data
|
|
if (true) {
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Create mock SPS NALU data (HEVC SPS type 33)
|
|
char sps_data[] = {
|
|
(char)((33 << 1) | 0), // HEVC SPS NALU type 33, layer_id=0
|
|
(char)1, // layer_id bits + temporal_id=1
|
|
0x10, 0x20, 0x30, 0x40, 0x50 // Mock SPS payload (>= 4 bytes total)
|
|
};
|
|
int sps_size = sizeof(sps_data);
|
|
|
|
string sps_output;
|
|
HELPER_ASSERT_SUCCESS(hevc.sps_demux(sps_data, sps_size, sps_output));
|
|
|
|
// Verify SPS output matches input
|
|
EXPECT_EQ(sps_size, (int)sps_output.length());
|
|
EXPECT_EQ(0, memcmp(sps_data, sps_output.data(), sps_size));
|
|
}
|
|
|
|
// Test sps_demux with frame size < 4 (should succeed but return empty)
|
|
if (true) {
|
|
SrsRawHEVCStream hevc;
|
|
|
|
char small_data[] = {0x01, 0x02}; // Only 2 bytes
|
|
string sps_output;
|
|
HELPER_ASSERT_SUCCESS(hevc.sps_demux(small_data, 2, sps_output));
|
|
|
|
// Should return empty string for frames < 4 bytes
|
|
EXPECT_EQ(0, (int)sps_output.length());
|
|
}
|
|
|
|
// Test sps_demux with exactly 4 bytes
|
|
if (true) {
|
|
SrsRawHEVCStream hevc;
|
|
|
|
char sps_data[] = {0x42, 0x01, 0x01, 0x01}; // Exactly 4 bytes
|
|
string sps_output;
|
|
HELPER_ASSERT_SUCCESS(hevc.sps_demux(sps_data, 4, sps_output));
|
|
|
|
// Should return the 4 bytes
|
|
EXPECT_EQ(4, (int)sps_output.length());
|
|
EXPECT_EQ(0, memcmp(sps_data, sps_output.data(), 4));
|
|
}
|
|
}
|
|
|
|
VOID TEST(RawHEVCStreamTest, PpsDemux)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Test pps_demux method with valid PPS data
|
|
if (true) {
|
|
SrsRawHEVCStream hevc;
|
|
|
|
// Create mock PPS NALU data (HEVC PPS type 34)
|
|
unsigned char pps_data[] = {
|
|
(unsigned char)((34 << 1) | 0), // HEVC PPS NALU type 34, layer_id=0
|
|
(unsigned char)1, // layer_id bits + temporal_id=1
|
|
0xA0, 0xB0, 0xC0 // Mock PPS payload
|
|
};
|
|
int pps_size = sizeof(pps_data);
|
|
|
|
string pps_output;
|
|
HELPER_ASSERT_SUCCESS(hevc.pps_demux((char *)pps_data, pps_size, pps_output));
|
|
|
|
// Verify PPS output matches input
|
|
EXPECT_EQ(pps_size, (int)pps_output.length());
|
|
EXPECT_EQ(0, memcmp(pps_data, pps_output.data(), pps_size));
|
|
}
|
|
|
|
// Test pps_demux with empty frame (should fail)
|
|
if (true) {
|
|
SrsRawHEVCStream hevc;
|
|
|
|
string pps_output;
|
|
HELPER_EXPECT_FAILED(hevc.pps_demux(NULL, 0, pps_output));
|
|
}
|
|
|
|
// Test pps_demux with negative frame size (should fail)
|
|
if (true) {
|
|
SrsRawHEVCStream hevc;
|
|
|
|
char dummy_data[] = {0x01, 0x02};
|
|
string pps_output;
|
|
HELPER_EXPECT_FAILED(hevc.pps_demux(dummy_data, -1, pps_output));
|
|
}
|
|
}
|