diff --git a/trunk/src/app/srs_app_factory.cpp b/trunk/src/app/srs_app_factory.cpp index ea04c14a0..0c6325def 100644 --- a/trunk/src/app/srs_app_factory.cpp +++ b/trunk/src/app/srs_app_factory.cpp @@ -23,9 +23,9 @@ #ifdef SRS_RTSP #include #endif +#include #include #include -#include #include #include #include diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp index a583e477d..8cf1d3db1 100644 --- a/trunk/src/app/srs_app_rtc_source.cpp +++ b/trunk/src/app/srs_app_rtc_source.cpp @@ -35,10 +35,10 @@ #ifdef SRS_FFMPEG_FIT #include #endif +#include #include #include #include -#include // The NACK sent by us(SFU). SrsPps *_srs_pps_snack = NULL; diff --git a/trunk/src/app/srs_app_rtmp_conn.cpp b/trunk/src/app/srs_app_rtmp_conn.cpp index b5c9f7c36..3370fa55d 100644 --- a/trunk/src/app/srs_app_rtmp_conn.cpp +++ b/trunk/src/app/srs_app_rtmp_conn.cpp @@ -230,6 +230,7 @@ SrsRtmpConn::SrsRtmpConn(ISrsRtmpTransport *transport, string cip, int cport) publish_1stpkt_timeout_ = 0; publish_normal_timeout_ = 0; + app_factory_ = _srs_app_factory; config_ = _srs_config; manager_ = _srs_conn_manager; stream_publish_tokens_ = _srs_stream_publish_tokens; @@ -270,6 +271,7 @@ SrsRtmpConn::~SrsRtmpConn() srs_freep(refer_); srs_freep(security_); + app_factory_ = NULL; config_ = NULL; manager_ = NULL; stream_publish_tokens_ = NULL; @@ -1065,7 +1067,7 @@ srs_error_t SrsRtmpConn::acquire_publish(SrsSharedPtr source) // Bridge to RTC streaming. // TODO: FIXME: Need to convert RTMP to SRT. - SrsRtmpBridge *bridge = new SrsRtmpBridge(_srs_app_factory); + SrsRtmpBridge *bridge = new SrsRtmpBridge(app_factory_); #if defined(SRS_FFMPEG_FIT) bool rtmp_to_rtc = config_->get_rtc_from_rtmp(req->vhost_); diff --git a/trunk/src/app/srs_app_rtmp_conn.hpp b/trunk/src/app/srs_app_rtmp_conn.hpp index cb8f46c91..133e6df88 100644 --- a/trunk/src/app/srs_app_rtmp_conn.hpp +++ b/trunk/src/app/srs_app_rtmp_conn.hpp @@ -47,6 +47,7 @@ class ISrsStreamPublishTokenManager; class ISrsLiveSourceManager; class ISrsStatistic; class ISrsHttpHooks; +class ISrsAppFactory; class ISrsRtcSourceManager; class ISrsSrtSourceManager; class ISrsRtspSourceManager; @@ -174,6 +175,7 @@ class SrsRtmpConn : public ISrsConnection, // It's a resource. // clang-format off SRS_DECLARE_PRIVATE: // clang-format on + ISrsAppFactory *app_factory_; ISrsResourceManager *manager_; ISrsAppConfig *config_; ISrsStreamPublishTokenManager *stream_publish_tokens_; diff --git a/trunk/src/utest/srs_utest_manual_mock.cpp b/trunk/src/utest/srs_utest_manual_mock.cpp index 6e22c9192..471fd97a1 100644 --- a/trunk/src/utest/srs_utest_manual_mock.cpp +++ b/trunk/src/utest/srs_utest_manual_mock.cpp @@ -303,6 +303,21 @@ ISrsRequest *MockRtcAsyncCallRequest::as_http() return this; } +MockRtcSource::MockRtcSource() +{ + on_rtp_count_ = 0; +} + +MockRtcSource::~MockRtcSource() +{ +} + +srs_error_t MockRtcSource::on_rtp(SrsRtpPacket *pkt) +{ + on_rtp_count_++; + return SrsRtcSource::on_rtp(pkt); +} + // MockRtcSourceManager implementation MockRtcSourceManager::MockRtcSourceManager() { @@ -310,7 +325,7 @@ MockRtcSourceManager::MockRtcSourceManager() fetch_or_create_error_ = srs_success; initialize_count_ = 0; fetch_or_create_count_ = 0; - mock_source_ = SrsSharedPtr(new SrsRtcSource()); + mock_source_ = SrsSharedPtr(new MockRtcSource()); } MockRtcSourceManager::~MockRtcSourceManager() @@ -2726,7 +2741,7 @@ void MockAudioTranscoder::aac_codec_header(uint8_t **data, int *len) if (size <= 0) { return; } - + uint8_t *copy = new uint8_t[size]; memcpy(copy, aac_header_.data(), size); *data = copy; diff --git a/trunk/src/utest/srs_utest_manual_mock.hpp b/trunk/src/utest/srs_utest_manual_mock.hpp index b02536f5d..f09a73a55 100644 --- a/trunk/src/utest/srs_utest_manual_mock.hpp +++ b/trunk/src/utest/srs_utest_manual_mock.hpp @@ -157,6 +157,20 @@ public: virtual ISrsRequest *as_http(); }; +// Mock RTC source for testing +class MockRtcSource : public SrsRtcSource +{ +public: + int on_rtp_count_; + +public: + MockRtcSource(); + virtual ~MockRtcSource(); + +public: + virtual srs_error_t on_rtp(SrsRtpPacket *pkt); +}; + // Mock RTC source manager for testing class MockRtcSourceManager : public ISrsRtcSourceManager { diff --git a/trunk/src/utest/srs_utest_workflow_rtmp2rtc.cpp b/trunk/src/utest/srs_utest_workflow_rtmp2rtc.cpp index f982bac79..0176fcdd0 100644 --- a/trunk/src/utest/srs_utest_workflow_rtmp2rtc.cpp +++ b/trunk/src/utest/srs_utest_workflow_rtmp2rtc.cpp @@ -23,6 +23,8 @@ #include +#include +#include #include #include #include @@ -38,6 +40,35 @@ #include #include +// Mock app factory for RTMP to RTC workflow testing +class MockAppFactoryForRtmp2Rtc : public SrsAppFactory +{ +public: + MockAudioTranscoder *last_created_audio_transcoder_; + +public: + MockAppFactoryForRtmp2Rtc() + { + last_created_audio_transcoder_ = NULL; + } + + virtual ~MockAppFactoryForRtmp2Rtc() + { + // Don't delete last_created_audio_transcoder_ here, + // it is managed by RTP builder + } + +#ifdef SRS_FFMPEG_FIT + virtual ISrsAudioTranscoder *create_audio_transcoder() + { + // Create a new mock audio transcoder each time + MockAudioTranscoder *transcoder = new MockAudioTranscoder(); + last_created_audio_transcoder_ = transcoder; + return transcoder; + } +#endif +}; + // This test is used to verify the basic workflow of the RTMP connection. // It's finished with the help of AI, but each step is manually designed // and verified. So this is not dominated by AI, but by humanbeing. @@ -46,6 +77,7 @@ VOID TEST(BasicWorkflowRtmp2RtcTest, ManuallyVerifyTypicalScenario) srs_error_t err; // Mock all interface dependencies + SrsUniquePtr mock_factory(new MockAppFactoryForRtmp2Rtc()); SrsUniquePtr mock_config(new MockAppConfig()); SrsUniquePtr mock_manager(new MockConnectionManager()); SrsUniquePtr mock_sources(new MockLiveSourceManager()); @@ -77,6 +109,7 @@ VOID TEST(BasicWorkflowRtmp2RtcTest, ManuallyVerifyTypicalScenario) ISrsRtmpTransport *transport = new MockRtmpTransport(); SrsUniquePtr conn(new SrsRtmpConn(transport, "192.168.1.100", 1935)); + conn->app_factory_ = mock_factory.get(); conn->config_ = mock_config.get(); conn->manager_ = mock_manager.get(); conn->live_sources_ = mock_sources.get(); @@ -124,42 +157,83 @@ VOID TEST(BasicWorkflowRtmp2RtcTest, ManuallyVerifyTypicalScenario) EXPECT_TRUE(bridge != NULL); builder = bridge->rtp_builder_; EXPECT_TRUE(builder != NULL); + +#ifdef SRS_FFMPEG_FIT + // Verify that the mock factory created the audio transcoder + EXPECT_TRUE(mock_factory->last_created_audio_transcoder_ != NULL); +#endif } - // Create an RTMP audio message to feed consumer. + // Send AAC sequence header first (required before raw data). + MockRtcSource *mock_rtc_source = dynamic_cast(mock_rtc_sources->mock_source_.get()); if (true) { - // Create a real AAC audio message with proper format. + // Create AAC sequence header message. // AAC audio format in RTMP/FLV: // Byte 0: (SoundFormat << 4) | (SoundRate << 2) | (SoundSize << 1) | SoundType // SoundFormat=10 (AAC), SoundRate=3 (44kHz), SoundSize=1 (16-bit), SoundType=1 (stereo) // = 0xAF // Byte 1: AACPacketType (0=sequence header, 1=raw data) - // Remaining bytes: AAC data - int payload_size = 10; + // Remaining bytes: AudioSpecificConfig (ASC) data + int payload_size = 4; SrsRtmpCommonMessage *msg = new SrsRtmpCommonMessage(); msg->header_.initialize_audio(payload_size, 0, 1); msg->create_payload(payload_size); - // Fill in AAC audio data + // Fill in AAC sequence header SrsBuffer stream(msg->payload(), payload_size); // Audio format byte: AAC(10), 44kHz(3), 16-bit(1), stereo(1) = 0xAF stream.write_1bytes(0xAF); - // AAC packet type: 1 = AAC raw data - stream.write_1bytes(0x01); - // AAC raw data (8 bytes of dummy audio data) - for (int i = 0; i < 8; i++) { - stream.write_1bytes(0x00); - } + // AAC packet type: 0 = AAC sequence header + stream.write_1bytes(0x00); + // AudioSpecificConfig (ASC) data: 0x12 0x10 + // This represents AAC-LC profile, 44.1kHz sample rate, 2 channels + stream.write_1bytes(0x12); + stream.write_1bytes(0x10); - // Feed audio to rtmp server. + // Feed sequence header to rtmp server. mock_rtmp_server->recv_msgs_.push_back(msg); mock_rtmp_server->cond_->signal(); // Wait for consumer to process the message. srs_usleep(1 * SRS_UTIME_MILLISECONDS); - // Verify that the message is sent to the client. + // Verify that the sequence header is sent to the RTC source. EXPECT_EQ(1, mock_source->on_audio_count_); + EXPECT_EQ(0, mock_rtc_source->on_rtp_count_); + } + + // Send AAC raw data frame. + if (true) { + // Create AAC raw data message. + // Byte 0: Audio format byte (0xAF) + // Byte 1: AACPacketType (1=raw data) + // Remaining bytes: AAC raw frame data + int payload_size = 10; + SrsRtmpCommonMessage *msg = new SrsRtmpCommonMessage(); + msg->header_.initialize_audio(payload_size, 23, 1); // 23ms timestamp for second frame + msg->create_payload(payload_size); + + // Fill in AAC raw data + SrsBuffer stream(msg->payload(), payload_size); + // Audio format byte: AAC(10), 44kHz(3), 16-bit(1), stereo(1) = 0xAF + stream.write_1bytes(0xAF); + // AAC packet type: 1 = AAC raw data + stream.write_1bytes(0x01); + // AAC raw frame data (8 bytes of dummy audio data) + for (int i = 0; i < 8; i++) { + stream.write_1bytes(0x00); + } + + // Feed raw data to rtmp server. + mock_rtmp_server->recv_msgs_.push_back(msg); + mock_rtmp_server->cond_->signal(); + + // Wait for consumer to process the message. + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + // Verify that the raw data is sent to the client. + EXPECT_EQ(2, mock_source->on_audio_count_); + EXPECT_EQ(1, mock_rtc_source->on_rtp_count_); } // Simulate client quit event, the receive thread will get this error.