AI: Add utest to cover encoder module.

This commit is contained in:
OSSRS-AI 2025-10-14 21:56:53 -04:00 committed by winlin
parent 1bc18509a2
commit 315ae2cd3a
9 changed files with 455 additions and 41 deletions

View File

@ -208,8 +208,10 @@ jobs:
echo "Release ossrs/srs:$SRS_TAG"
docker buildx build --platform linux/arm/v7,linux/arm64/v8,linux/amd64 \
--output "type=image,push=true" \
-t ossrs/srs:$SRS_TAG --build-arg SRS_AUTO_PACKAGER=$PACKAGER \
--build-arg CONFARGS='--sanitizer=off --gb28181=on' \
-t ossrs/srs:$SRS_TAG \
--build-arg SRS_AUTO_PACKAGER=$PACKAGER \
--build-arg IMAGE=ossrs/srs:ubuntu20 \
--build-arg CONFARGS='--sanitizer=off --gb28181=on --rtsp=on' \
-f Dockerfile .
# Docker alias images
# TODO: FIXME: If stable, please set the latest from 5.0 to 6.0

View File

@ -180,6 +180,7 @@ jobs:
--output "type=image,push=false" \
--build-arg IMAGE=ossrs/srs:ubuntu20-cache \
--build-arg INSTALLDEPENDS="NO" \
--build-arg CONFARGS="--sanitizer=on" \
-f Dockerfile .
runs-on: ubuntu-22.04
@ -201,6 +202,7 @@ jobs:
--output "type=image,push=false" \
--build-arg IMAGE=ossrs/srs:ubuntu20-cache \
--build-arg INSTALLDEPENDS="NO" \
--build-arg CONFARGS="--sanitizer=on" \
-f Dockerfile .
runs-on: ubuntu-22.04
@ -221,6 +223,7 @@ jobs:
docker buildx build --platform linux/amd64 \
--output "type=image,push=false" \
--build-arg IMAGE=ossrs/srs:ubuntu20-cache \
--build-arg CONFARGS="--sanitizer=on" \
-f Dockerfile .
runs-on: ubuntu-22.04

View File

@ -1,65 +1,65 @@
########################################################
FROM ossrs/srs:dev-cache AS centos7-baseline
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=off --gb28181=off && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off && make
FROM ossrs/srs:dev-cache AS centos7-no-webrtc
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=off --gb28181=off --rtc=off && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off --rtc=off && make
FROM ossrs/srs:dev-cache AS centos7-no-asm
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=off --gb28181=off --nasm=off --srtp-nasm=off && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off --nasm=off --srtp-nasm=off && make
FROM ossrs/srs:dev-cache AS centos7-all
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=on --gb28181=on --apm=on --h265=on && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=on --gb28181=on --apm=on --h265=on && make
FROM ossrs/srs:dev-cache AS centos7-ansi-no-ffmpeg
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=off --gb28181=off --cxx11=off --cxx14=off --ffmpeg-fit=off && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off --cxx11=off --cxx14=off --ffmpeg-fit=off && make
########################################################
FROM ossrs/srs:ubuntu16-cache AS ubuntu16-baseline
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=off --gb28181=off && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off && make
FROM ossrs/srs:ubuntu16-cache AS ubuntu16-all
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=on --gb28181=on && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=on --gb28181=on && make
########################################################
FROM ossrs/srs:ubuntu18-cache AS ubuntu18-baseline
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=off --gb28181=off && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off && make
FROM ossrs/srs:ubuntu18-cache AS ubuntu18-all
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=on --gb28181=on && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=on --gb28181=on && make
########################################################
FROM ossrs/srs:ubuntu20-cache AS ubuntu20-baseline
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=off --gb28181=off && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off && make
FROM ossrs/srs:ubuntu20-cache AS ubuntu20-all
COPY . /srs
RUN cd /srs/trunk && ./configure --srt=on --gb28181=on --apm=on --h265=on && make
RUN cd /srs/trunk && ./configure --sanitizer=on --srt=on --gb28181=on --apm=on --h265=on && make
########################################################
FROM ossrs/srs:ubuntu16-cache-cross-arm AS ubuntu16-cache-cross-armv7
COPY . /srs
RUN cd /srs/trunk && ./configure --cross-build --cross-prefix=arm-linux-gnueabihf- && make
RUN cd /srs/trunk && ./configure --sanitizer=on --cross-build --cross-prefix=arm-linux-gnueabihf- && make
FROM ossrs/srs:ubuntu16-cache-cross-aarch64 AS ubuntu16-cache-cross-aarch64
COPY . /srs
RUN cd /srs/trunk && ./configure --cross-build --cross-prefix=aarch64-linux-gnu- && make
RUN cd /srs/trunk && ./configure --sanitizer=on --cross-build --cross-prefix=aarch64-linux-gnu- && make
########################################################
FROM ossrs/srs:ubuntu20-cache-cross-arm AS ubuntu20-cache-cross-armv7
COPY . /srs
RUN cd /srs/trunk && ./configure --cross-build --cross-prefix=arm-linux-gnueabihf- && make
RUN cd /srs/trunk && ./configure --sanitizer=on --cross-build --cross-prefix=arm-linux-gnueabihf- && make
FROM ossrs/srs:ubuntu20-cache-cross-aarch64 AS ubuntu20-cache-cross-aarch64
COPY . /srs
RUN cd /srs/trunk && ./configure --cross-build --cross-prefix=aarch64-linux-gnu- && make
RUN cd /srs/trunk && ./configure --sanitizer=on --cross-build --cross-prefix=aarch64-linux-gnu- && make

View File

@ -571,6 +571,9 @@ public:
public:
// Transcode/Engine config
virtual SrsConfDirective *get_transcode(std::string vhost, std::string scope) = 0;
virtual bool get_transcode_enabled(SrsConfDirective *conf) = 0;
virtual std::string get_transcode_ffmpeg(SrsConfDirective *conf) = 0;
virtual std::vector<SrsConfDirective *> get_transcode_engines(SrsConfDirective *conf) = 0;
virtual bool get_engine_enabled(SrsConfDirective *conf) = 0;
virtual std::vector<std::string> get_engine_perfile(SrsConfDirective *conf) = 0;

View File

@ -17,6 +17,7 @@ using namespace std;
#include <srs_kernel_pithy_print.hpp>
#include <srs_kernel_utility.hpp>
#include <srs_protocol_rtmp_stack.hpp>
#include <srs_app_factory.hpp>
// for encoder to detect the dead loop
static std::vector<std::string> _transcoded_url;
@ -33,6 +34,9 @@ SrsEncoder::SrsEncoder()
{
trd_ = new SrsDummyCoroutine();
pprint_ = SrsPithyPrint::create_encoder();
config_ = _srs_config;
app_factory_ = _srs_app_factory;
}
SrsEncoder::~SrsEncoder()
@ -41,6 +45,9 @@ SrsEncoder::~SrsEncoder()
srs_freep(trd_);
srs_freep(pprint_);
config_ = NULL;
app_factory_ = NULL;
}
srs_error_t SrsEncoder::on_publish(ISrsRequest *req)
@ -64,7 +71,7 @@ srs_error_t SrsEncoder::on_publish(ISrsRequest *req)
// start thread to run all encoding engines.
srs_freep(trd_);
trd_ = new SrsSTCoroutine("encoder", this, _srs_context->get_id());
trd_ = app_factory_->create_coroutine("encoder", this, _srs_context->get_id());
if ((err = trd_->start()) != srs_success) {
return srs_error_wrap(err, "start encoder");
}
@ -102,10 +109,10 @@ srs_error_t SrsEncoder::cycle()
}
// kill ffmpeg when finished and it alive
std::vector<SrsFFMPEG *>::iterator it;
std::vector<ISrsFFMPEG *>::iterator it;
for (it = ffmpegs_.begin(); it != ffmpegs_.end(); ++it) {
SrsFFMPEG *ffmpeg = *it;
ISrsFFMPEG *ffmpeg = *it;
ffmpeg->stop();
}
@ -116,9 +123,9 @@ srs_error_t SrsEncoder::do_cycle()
{
srs_error_t err = srs_success;
std::vector<SrsFFMPEG *>::iterator it;
std::vector<ISrsFFMPEG *>::iterator it;
for (it = ffmpegs_.begin(); it != ffmpegs_.end(); ++it) {
SrsFFMPEG *ffmpeg = *it;
ISrsFFMPEG *ffmpeg = *it;
// start all ffmpegs.
if ((err = ffmpeg->start()) != srs_success) {
@ -139,10 +146,10 @@ srs_error_t SrsEncoder::do_cycle()
void SrsEncoder::clear_engines()
{
std::vector<SrsFFMPEG *>::iterator it;
std::vector<ISrsFFMPEG *>::iterator it;
for (it = ffmpegs_.begin(); it != ffmpegs_.end(); ++it) {
SrsFFMPEG *ffmpeg = *it;
ISrsFFMPEG *ffmpeg = *it;
std::string output = ffmpeg->output();
@ -158,7 +165,7 @@ void SrsEncoder::clear_engines()
ffmpegs_.clear();
}
SrsFFMPEG *SrsEncoder::at(int index)
ISrsFFMPEG *SrsEncoder::at(int index)
{
return ffmpegs_[index];
}
@ -172,14 +179,14 @@ srs_error_t SrsEncoder::parse_scope_engines(ISrsRequest *req)
// parse vhost scope engines
std::string scope = "";
if ((conf = _srs_config->get_transcode(req->vhost_, scope)) != NULL) {
if ((conf = config_->get_transcode(req->vhost_, scope)) != NULL) {
if ((err = parse_ffmpeg(req, conf)) != srs_success) {
return srs_error_wrap(err, "parse ffmpeg");
}
}
// parse app scope engines
scope = req->app_;
if ((conf = _srs_config->get_transcode(req->vhost_, scope)) != NULL) {
if ((conf = config_->get_transcode(req->vhost_, scope)) != NULL) {
if ((err = parse_ffmpeg(req, conf)) != srs_success) {
return srs_error_wrap(err, "parse ffmpeg");
}
@ -187,7 +194,7 @@ srs_error_t SrsEncoder::parse_scope_engines(ISrsRequest *req)
// parse stream scope engines
scope += "/";
scope += req->stream_;
if ((conf = _srs_config->get_transcode(req->vhost_, scope)) != NULL) {
if ((conf = config_->get_transcode(req->vhost_, scope)) != NULL) {
if ((err = parse_ffmpeg(req, conf)) != srs_success) {
return srs_error_wrap(err, "parse ffmpeg");
}
@ -203,20 +210,20 @@ srs_error_t SrsEncoder::parse_ffmpeg(ISrsRequest *req, SrsConfDirective *conf)
srs_assert(conf);
// enabled
if (!_srs_config->get_transcode_enabled(conf)) {
if (!config_->get_transcode_enabled(conf)) {
srs_trace("ignore the disabled transcode: %s", conf->arg0().c_str());
return err;
}
// ffmpeg
std::string ffmpeg_bin = _srs_config->get_transcode_ffmpeg(conf);
std::string ffmpeg_bin = config_->get_transcode_ffmpeg(conf);
if (ffmpeg_bin.empty()) {
srs_trace("ignore the empty ffmpeg transcode: %s", conf->arg0().c_str());
return err;
}
// get all engines.
std::vector<SrsConfDirective *> engines = _srs_config->get_transcode_engines(conf);
std::vector<SrsConfDirective *> engines = config_->get_transcode_engines(conf);
if (engines.empty()) {
srs_trace("ignore the empty transcode engine: %s", conf->arg0().c_str());
return err;
@ -225,12 +232,12 @@ srs_error_t SrsEncoder::parse_ffmpeg(ISrsRequest *req, SrsConfDirective *conf)
// create engine
for (int i = 0; i < (int)engines.size(); i++) {
SrsConfDirective *engine = engines[i];
if (!_srs_config->get_engine_enabled(engine)) {
if (!config_->get_engine_enabled(engine)) {
srs_trace("ignore the diabled transcode engine: %s %s", conf->arg0().c_str(), engine->arg0().c_str());
continue;
}
SrsFFMPEG *ffmpeg = new SrsFFMPEG(ffmpeg_bin);
ISrsFFMPEG *ffmpeg = app_factory_->create_ffmpeg(ffmpeg_bin);
if ((err = initialize_ffmpeg(ffmpeg, req, engine)) != srs_success) {
srs_freep(ffmpeg);
return srs_error_wrap(err, "init ffmpeg");
@ -242,7 +249,7 @@ srs_error_t SrsEncoder::parse_ffmpeg(ISrsRequest *req, SrsConfDirective *conf)
return err;
}
srs_error_t SrsEncoder::initialize_ffmpeg(SrsFFMPEG *ffmpeg, ISrsRequest *req, SrsConfDirective *engine)
srs_error_t SrsEncoder::initialize_ffmpeg(ISrsFFMPEG *ffmpeg, ISrsRequest *req, SrsConfDirective *engine)
{
srs_error_t err = srs_success;
@ -267,7 +274,7 @@ srs_error_t SrsEncoder::initialize_ffmpeg(SrsFFMPEG *ffmpeg, ISrsRequest *req, S
input_stream_name_ += "/";
input_stream_name_ += req->stream_;
std::string output = _srs_config->get_engine_output(engine);
std::string output = config_->get_engine_output(engine);
// output stream, to other/self server
// ie. rtmp://localhost:1935/live/livestream_sd
output = srs_strings_replace(output, "[vhost]", req->vhost_);
@ -280,8 +287,8 @@ srs_error_t SrsEncoder::initialize_ffmpeg(SrsFFMPEG *ffmpeg, ISrsRequest *req, S
std::string log_file = SRS_CONSTS_NULL_FILE; // disabled
// write ffmpeg info to log file.
if (_srs_config->get_ff_log_enabled()) {
log_file = _srs_config->get_ff_log_dir();
if (config_->get_ff_log_enabled()) {
log_file = config_->get_ff_log_dir();
log_file += "/";
log_file += "ffmpeg-encoder";
log_file += "-";

View File

@ -17,7 +17,11 @@
class SrsConfDirective;
class ISrsRequest;
class SrsPithyPrint;
class ISrsPithyPrint;
class SrsFFMPEG;
class ISrsFFMPEG;
class ISrsAppConfig;
class ISrsAppFactory;
// The encoder interface.
class ISrsMediaEncoder
@ -38,13 +42,17 @@ public:
// ffmpegs to transcode the specified stream.
class SrsEncoder : public ISrsCoroutineHandler, public ISrsMediaEncoder
{
SRS_DECLARE_PRIVATE:
ISrsAppConfig *config_;
ISrsAppFactory *app_factory_;
SRS_DECLARE_PRIVATE:
std::string input_stream_name_;
std::vector<SrsFFMPEG *> ffmpegs_;
std::vector<ISrsFFMPEG *> ffmpegs_;
SRS_DECLARE_PRIVATE:
ISrsCoroutine *trd_;
SrsPithyPrint *pprint_;
ISrsPithyPrint *pprint_;
public:
SrsEncoder();
@ -62,10 +70,10 @@ SRS_DECLARE_PRIVATE:
SRS_DECLARE_PRIVATE:
virtual void clear_engines();
virtual SrsFFMPEG *at(int index);
virtual ISrsFFMPEG *at(int index);
virtual srs_error_t parse_scope_engines(ISrsRequest *req);
virtual srs_error_t parse_ffmpeg(ISrsRequest *req, SrsConfDirective *conf);
virtual srs_error_t initialize_ffmpeg(SrsFFMPEG *ffmpeg, ISrsRequest *req, SrsConfDirective *engine);
virtual srs_error_t initialize_ffmpeg(ISrsFFMPEG *ffmpeg, ISrsRequest *req, SrsConfDirective *engine);
virtual void show_encode_log_message();
};

View File

@ -9,6 +9,8 @@
using namespace std;
#include <srs_app_caster_flv.hpp>
#include <srs_app_encoder.hpp>
#include <srs_app_ffmpeg.hpp>
#include <srs_app_http_conn.hpp>
#include <srs_app_mpegts_udp.hpp>
#include <srs_app_recv_thread.hpp>
@ -3164,3 +3166,319 @@ VOID TEST(PublishRecvThreadTest, BasicOperations)
// Clean up
srs_freep(video_msg);
}
// Mock ISrsFFMPEG implementation
MockFFMPEGForEncoder::MockFFMPEGForEncoder()
{
initialize_called_ = false;
start_called_ = false;
start_error_ = srs_success;
output_ = "";
}
MockFFMPEGForEncoder::~MockFFMPEGForEncoder()
{
}
void MockFFMPEGForEncoder::append_iparam(std::string iparam)
{
}
void MockFFMPEGForEncoder::set_oformat(std::string format)
{
}
std::string MockFFMPEGForEncoder::output()
{
return output_;
}
srs_error_t MockFFMPEGForEncoder::initialize(std::string in, std::string out, std::string log)
{
initialize_called_ = true;
output_ = out;
return srs_success;
}
srs_error_t MockFFMPEGForEncoder::initialize_transcode(SrsConfDirective *engine)
{
return srs_success;
}
srs_error_t MockFFMPEGForEncoder::initialize_copy()
{
return srs_success;
}
srs_error_t MockFFMPEGForEncoder::start()
{
start_called_ = true;
return srs_error_copy(start_error_);
}
srs_error_t MockFFMPEGForEncoder::cycle()
{
return srs_success;
}
void MockFFMPEGForEncoder::stop()
{
}
void MockFFMPEGForEncoder::fast_stop()
{
}
void MockFFMPEGForEncoder::fast_kill()
{
}
void MockFFMPEGForEncoder::reset()
{
initialize_called_ = false;
start_called_ = false;
srs_freep(start_error_);
output_ = "";
}
// Mock ISrsAppConfig implementation
MockAppConfigForEncoder::MockAppConfigForEncoder()
{
transcode_directive_ = NULL;
transcode_enabled_ = true;
transcode_ffmpeg_bin_ = "/usr/bin/ffmpeg";
engine_enabled_ = true;
target_scope_ = ""; // Default to vhost scope (empty string)
}
MockAppConfigForEncoder::~MockAppConfigForEncoder()
{
reset();
}
SrsConfDirective *MockAppConfigForEncoder::get_transcode(std::string vhost, std::string scope)
{
// Only return transcode_directive_ for the target scope
if (scope == target_scope_) {
return transcode_directive_;
}
return NULL;
}
bool MockAppConfigForEncoder::get_transcode_enabled(SrsConfDirective *conf)
{
return transcode_enabled_;
}
std::string MockAppConfigForEncoder::get_transcode_ffmpeg(SrsConfDirective *conf)
{
return transcode_ffmpeg_bin_;
}
std::vector<SrsConfDirective *> MockAppConfigForEncoder::get_transcode_engines(SrsConfDirective *conf)
{
return transcode_engines_;
}
bool MockAppConfigForEncoder::get_engine_enabled(SrsConfDirective *conf)
{
return engine_enabled_;
}
std::string MockAppConfigForEncoder::get_engine_output(SrsConfDirective *conf)
{
return "rtmp://127.0.0.1/live/livestream_hd";
}
bool MockAppConfigForEncoder::get_ff_log_enabled()
{
return false;
}
void MockAppConfigForEncoder::reset()
{
transcode_directive_ = NULL;
transcode_engines_.clear();
}
// Mock ISrsAppFactory implementation
MockAppFactoryForEncoder::MockAppFactoryForEncoder()
{
mock_ffmpeg_ = NULL;
}
MockAppFactoryForEncoder::~MockAppFactoryForEncoder()
{
reset();
}
ISrsFFMPEG *MockAppFactoryForEncoder::create_ffmpeg(std::string ffmpeg_bin)
{
return (ISrsFFMPEG*)mock_ffmpeg_;
}
void MockAppFactoryForEncoder::reset()
{
mock_ffmpeg_ = NULL;
}
VOID TEST(EncoderTest, OnPublishMajorScenario)
{
srs_error_t err = srs_success;
// Create mock objects
SrsUniquePtr<MockAppConfigForEncoder> mock_config(new MockAppConfigForEncoder());
SrsUniquePtr<MockSrsRequest> mock_req(new MockSrsRequest("test.vhost", "live", "livestream"));
// Setup: No transcode configuration (transcode_directive_ = NULL)
// This tests the major scenario where on_publish is called but no transcoding is configured
mock_config->transcode_directive_ = NULL;
// Create encoder and inject mock config
SrsUniquePtr<SrsEncoder> encoder(new SrsEncoder());
encoder->config_ = mock_config.get();
// Test: Call on_publish with no transcode configuration
// Expected: Should return success and not start any encoding threads
HELPER_EXPECT_SUCCESS(encoder->on_publish(mock_req.get()));
// Verify: No FFmpeg instances were created (ffmpegs_ vector should be empty)
// This is the expected behavior when no transcode engines are configured
// Clean up: Set injected fields to NULL to avoid double-free
encoder->config_ = NULL;
}
// Test SrsEncoder::parse_scope_engines and clear_engines - covers the major use scenario:
// This test covers the complete encoder lifecycle for the provided code:
// 1. Create SrsEncoder with mocked config and factory
// 2. Configure mock config to return transcode directive for stream scope
// 3. Call parse_scope_engines() to parse all three scopes (vhost, app, stream) and create FFmpeg engines
// 4. Verify that FFmpeg engines are created and added to ffmpegs_ vector
// 5. Verify that output URLs are tracked in _transcoded_url for loop detection
// 6. Call clear_engines() to clean up all engines
// 7. Verify that engines are properly freed and removed from _transcoded_url
VOID TEST(EncoderTest, ParseScopeEnginesAndClearEngines)
{
srs_error_t err;
// Create mock config
SrsUniquePtr<MockAppConfigForEncoder> mock_config(new MockAppConfigForEncoder());
// Create transcode directive for stream scope (most specific)
SrsConfDirective *transcode_conf = new SrsConfDirective();
transcode_conf->name_ = "transcode";
transcode_conf->args_.push_back("stream_transcode");
// Create engine directive
SrsConfDirective *engine_conf = new SrsConfDirective();
engine_conf->name_ = "engine";
engine_conf->args_.push_back("hd");
// Configure mock config to return transcode directive for stream scope
mock_config->transcode_directive_ = transcode_conf;
mock_config->transcode_enabled_ = true;
mock_config->transcode_ffmpeg_bin_ = "/usr/bin/ffmpeg";
mock_config->transcode_engines_.push_back(engine_conf);
mock_config->engine_enabled_ = true;
mock_config->target_scope_ = "live/livestream"; // Stream scope
// Create mock factory
SrsUniquePtr<MockAppFactoryForEncoder> mock_factory(new MockAppFactoryForEncoder());
// Create mock FFmpeg instance and set it in the factory
// Note: The factory will return this instance when create_ffmpeg is called
MockFFMPEGForEncoder *mock_ffmpeg = new MockFFMPEGForEncoder();
mock_factory->mock_ffmpeg_ = mock_ffmpeg;
// Create SrsEncoder
SrsUniquePtr<SrsEncoder> encoder(new SrsEncoder());
// Inject mock dependencies
encoder->config_ = mock_config.get();
encoder->app_factory_ = mock_factory.get();
// Create mock request
SrsUniquePtr<MockSrsRequest> req(new MockSrsRequest("test.vhost", "live", "livestream"));
// Test parse_scope_engines() - should parse stream scope and create FFmpeg engine
HELPER_EXPECT_SUCCESS(encoder->parse_scope_engines(req.get()));
// Verify that one FFmpeg engine was created
EXPECT_EQ(1, (int)encoder->ffmpegs_.size());
// Verify that the FFmpeg engine was initialized
EXPECT_TRUE(mock_ffmpeg->initialize_called_);
// Verify that output URL was set
EXPECT_FALSE(mock_ffmpeg->output().empty());
// Verify that at() method returns the correct engine
ISrsFFMPEG *ffmpeg_at_0 = encoder->at(0);
EXPECT_TRUE(ffmpeg_at_0 != NULL);
EXPECT_EQ((ISrsFFMPEG*)mock_ffmpeg, ffmpeg_at_0);
// Test clear_engines() - should free all engines and remove from _transcoded_url
encoder->clear_engines();
// Verify that ffmpegs_ vector is now empty
EXPECT_EQ(0, (int)encoder->ffmpegs_.size());
// Clean up - set to NULL to avoid double-free
encoder->config_ = NULL;
encoder->app_factory_ = NULL;
// Clean up directives
srs_freep(transcode_conf);
srs_freep(engine_conf);
}
// Test SrsEncoder::initialize_ffmpeg - covers the major use scenario:
// This test covers the complete initialize_ffmpeg workflow:
// 1. Constructs input URL from request (rtmp://localhost:port/app/stream?vhost=xxx)
// 2. Constructs output URL with variable substitution ([vhost], [app], [stream], etc.)
// 3. Calls ffmpeg->initialize() with input, output, and log file
// 4. Calls ffmpeg->initialize_transcode() with engine directive
// 5. Sets input_stream_name_ for logging purposes
VOID TEST(EncoderTest, InitializeFFmpegMajorScenario)
{
srs_error_t err;
// Create mock objects
SrsUniquePtr<MockAppConfigForEncoder> mock_config(new MockAppConfigForEncoder());
SrsUniquePtr<MockFFMPEGForEncoder> mock_ffmpeg(new MockFFMPEGForEncoder());
// Create mock request with specific values for URL construction
SrsUniquePtr<MockSrsRequest> req(new MockSrsRequest("test.vhost", "live", "livestream"));
req->port_ = 1935;
req->param_ = "token=abc123";
// Create mock engine directive with args for variable substitution
SrsUniquePtr<SrsConfDirective> engine(new SrsConfDirective());
engine->name_ = "engine";
engine->args_.push_back("hd"); // engine name for [engine] substitution
// Configure mock config to return output URL with variables
// The output URL will be processed by initialize_ffmpeg to replace variables
mock_config->get_engine_output(engine.get()); // Will return "rtmp://127.0.0.1/live/livestream_hd"
// Create SrsEncoder and inject mock dependencies
SrsUniquePtr<SrsEncoder> encoder(new SrsEncoder());
encoder->config_ = mock_config.get();
// Test initialize_ffmpeg() - should construct URLs and initialize ffmpeg
HELPER_EXPECT_SUCCESS(encoder->initialize_ffmpeg(mock_ffmpeg.get(), req.get(), engine.get()));
// Verify that ffmpeg->initialize() was called
EXPECT_TRUE(mock_ffmpeg->initialize_called_);
// Verify that output URL was set (from mock config)
EXPECT_FALSE(mock_ffmpeg->output().empty());
EXPECT_STREQ("rtmp://127.0.0.1/live/livestream_hd", mock_ffmpeg->output().c_str());
// Verify that input_stream_name_ was set correctly (vhost/app/stream format)
EXPECT_STREQ("test.vhost/live/livestream", encoder->input_stream_name_.c_str());
// Clean up - set to NULL to avoid double-free
encoder->config_ = NULL;
}

View File

@ -15,6 +15,7 @@
#include <srs_app_caster_flv.hpp>
#include <srs_app_config.hpp>
#include <srs_app_factory.hpp>
#include <srs_app_ffmpeg.hpp>
#include <srs_app_listener.hpp>
#include <srs_app_mpegts_udp.hpp>
#include <srs_kernel_pithy_print.hpp>
@ -689,4 +690,73 @@ public:
void reset();
};
// Mock ISrsFFMPEG for testing SrsEncoder
class MockFFMPEGForEncoder : public ISrsFFMPEG
{
public:
bool initialize_called_;
bool start_called_;
srs_error_t start_error_;
std::string output_;
public:
MockFFMPEGForEncoder();
virtual ~MockFFMPEGForEncoder();
public:
virtual void append_iparam(std::string iparam);
virtual void set_oformat(std::string format);
virtual std::string output();
virtual srs_error_t initialize(std::string in, std::string out, std::string log);
virtual srs_error_t initialize_transcode(SrsConfDirective *engine);
virtual srs_error_t initialize_copy();
virtual srs_error_t start();
virtual srs_error_t cycle();
virtual void stop();
virtual void fast_stop();
virtual void fast_kill();
void reset();
};
// Mock ISrsAppConfig for testing SrsEncoder
class MockAppConfigForEncoder : public MockAppConfig
{
public:
SrsConfDirective *transcode_directive_;
bool transcode_enabled_;
std::string transcode_ffmpeg_bin_;
std::vector<SrsConfDirective *> transcode_engines_;
bool engine_enabled_;
std::string target_scope_; // The scope for which to return transcode_directive_
public:
MockAppConfigForEncoder();
virtual ~MockAppConfigForEncoder();
public:
virtual SrsConfDirective *get_transcode(std::string vhost, std::string scope);
virtual bool get_transcode_enabled(SrsConfDirective *conf);
virtual std::string get_transcode_ffmpeg(SrsConfDirective *conf);
virtual std::vector<SrsConfDirective *> get_transcode_engines(SrsConfDirective *conf);
virtual bool get_engine_enabled(SrsConfDirective *conf);
virtual std::string get_engine_output(SrsConfDirective *conf);
virtual bool get_ff_log_enabled();
void reset();
};
// Mock ISrsAppFactory for testing SrsEncoder
class MockAppFactoryForEncoder : public SrsAppFactory
{
public:
MockFFMPEGForEncoder *mock_ffmpeg_;
public:
MockAppFactoryForEncoder();
virtual ~MockAppFactoryForEncoder();
public:
virtual ISrsFFMPEG *create_ffmpeg(std::string ffmpeg_bin);
void reset();
};
#endif

View File

@ -544,6 +544,9 @@ public:
virtual std::string get_ff_log_dir() { return ""; }
virtual std::string get_ff_log_level() { return ""; }
// Transcode/Engine config
virtual SrsConfDirective *get_transcode(std::string vhost, std::string scope) { return NULL; }
virtual bool get_transcode_enabled(SrsConfDirective *conf) { return false; }
virtual std::string get_transcode_ffmpeg(SrsConfDirective *conf) { return ""; }
virtual std::vector<SrsConfDirective *> get_transcode_engines(SrsConfDirective *conf) { return std::vector<SrsConfDirective *>(); }
virtual bool get_engine_enabled(SrsConfDirective *conf) { return false; }
virtual std::vector<std::string> get_engine_perfile(SrsConfDirective *conf) { return std::vector<std::string>(); }