diff --git a/README.md b/README.md
index 8daf0cf9d..c82901ab8 100755
--- a/README.md
+++ b/README.md
@@ -69,15 +69,16 @@ Supported operating systems and hardware:
14. support forward publish stream to build active-standby cluster.
15. support broadcast by forward the stream to other servers(origin/edge).
16. support live stream transcoding by ffmpeg.
-17. [plan] support full http callback api.
-18. [plan] support network based cli and json result.
-19. [plan] support bandwidth test api and flash client.
-20. [plan] support adobe flash refer/token/swf verification.
-21. [plan] support adobe amf3 codec.
-22. [plan] support dvr(record live to vod file)
-23. [plan] support FMS edge protocol
-24. [plan] support encryption: RTMPE/RTMPS, HLS DRM
-25. [plan] support RTMPT, http to tranverse firewalls
+17. support live stream transcoding by ffmpeg.
+18. support ffmpeg filters(logo/overlay/crop), x264 params.
+19. [plan] support network based cli and json result.
+20. [plan] support bandwidth test api and flash client.
+21. [plan] support adobe flash refer/token/swf verification.
+22. [plan] support adobe amf3 codec.
+23. [plan] support dvr(record live to vod file)
+24. [plan] support FMS edge protocol
+25. [plan] support encryption: RTMPE/RTMPS, HLS DRM
+26. [plan] support RTMPT, http to tranverse firewalls
### Performance
1. 300 connections, 150Mbps, 500kbps, CPU 18.8%, 5956KB.
diff --git a/trunk/auto/build_ffmpeg.sh b/trunk/auto/build_ffmpeg.sh
index 1c1350393..d2ad359d0 100755
--- a/trunk/auto/build_ffmpeg.sh
+++ b/trunk/auto/build_ffmpeg.sh
@@ -84,6 +84,7 @@ else
--extra-ldflags='-L${ffmpeg_exported_release_dir}/lib -lm -ldl' \
--disable-ffplay --disable-ffprobe --disable-ffserver --disable-doc \
--enable-postproc --enable-bzlib --enable-zlib --enable-parsers \
+ --enable-libfreetype \
--enable-libx264 --enable-libmp3lame --enable-libaacplus \
--enable-pthreads --extra-libs=-lpthread --enable-encoders --enable-decoders --enable-avfilter --enable-muxers --enable-demuxers &&
make && make install
diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf
index 464341e88..3212bc449 100755
--- a/trunk/conf/srs.conf
+++ b/trunk/conf/srs.conf
@@ -15,13 +15,19 @@ vhost __defaultVhost__ {
hls_path ./objs/nginx/html;
hls_fragment 5;
hls_window 30;
- #forward 127.0.0.1:1936;
+ forward 127.0.0.1:1936;
transcode {
enabled on;
ffmpeg ./objs/ffmpeg/bin/ffmpeg;
#ffmpeg ./research/ffempty/ffempty;
engine fast{
enabled on;
+ vfilter {
+ vf 'drawtext=text=SRS';
+ #vf 'crop=in_w-20:in_h-160:10:80';
+ #i ./doc/ffmpeg-logo.png;
+ #filter_complex 'overlay=10:10';
+ }
vcodec libx264;
vbitrate 300;
vfps 20;
@@ -37,11 +43,15 @@ vhost __defaultVhost__ {
asample_rate 44100;
achannels 2;
aparams {
+ profile:a aac_low;
}
output rtmp://[vhost]:[port]/[app]/[stream]_fast;
}
engine sd{
- enabled on;
+ enabled off;
+ vfilter {
+ vf 'split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2';
+ }
vcodec libx264;
vbitrate 500;
vfps 20;
@@ -79,6 +89,14 @@ vhost all.transcode.vhost.com {
# whether the engine is enabled
# default: off.
enabled on;
+ # ffmpeg filters, follows the main input.
+ vfilter {
+ # the logo input file.
+ i ./doc/ffmpeg-logo.png;
+ # the ffmpeg complex filter.
+ # for filters, @see: http://ffmpeg.org/ffmpeg-filters.html
+ filter_complex 'overlay=10:10';
+ }
# video encoder name
vcodec libx264;
# video bitrate, in kbps
@@ -100,6 +118,13 @@ vhost all.transcode.vhost.com {
vpreset medium;
# other x264 or ffmpeg video params
vparams {
+ # ffmpeg options, @see: http://ffmpeg.org/ffmpeg.html
+ t 100;
+ # 264 params, @see: http://ffmpeg.org/ffmpeg-codecs.html#libx264
+ coder 1;
+ b_strategy 2;
+ bf 3;
+ refs 10;
}
# audio encoder name
acodec libaacplus;
@@ -182,12 +207,102 @@ vhost all.transcode.vhost.com {
}
}
}
+# the mirror filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#Filtering-Introduction
+vhost mirror.transcode.vhost.com {
+ transcode {
+ enabled on;
+ ffmpeg ./objs/ffmpeg/bin/ffmpeg;
+ engine mirror{
+ enabled on;
+ vfilter {
+ vf 'split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2';
+ }
+ vcodec libx264;
+ vbitrate 300;
+ vfps 20;
+ vwidth 480;
+ vheight 320;
+ vthreads 2;
+ vprofile baseline;
+ vpreset superfast;
+ vparams {
+ }
+ acodec libaacplus;
+ abitrate 30;
+ asample_rate 44100;
+ achannels 2;
+ aparams {
+ }
+ output rtmp://[vhost]:[port]/[app]/[stream]_mirror;
+ }
+ }
+}
+# the logo filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#crop
+vhost crop.transcode.vhost.com {
+ transcode {
+ enabled on;
+ ffmpeg ./objs/ffmpeg/bin/ffmpeg;
+ engine crop{
+ enabled on;
+ vfilter {
+ vf 'crop=in_w-20:in_h-160:10:80';
+ }
+ vcodec libx264;
+ vbitrate 300;
+ vfps 20;
+ vwidth 480;
+ vheight 320;
+ vthreads 2;
+ vprofile baseline;
+ vpreset superfast;
+ vparams {
+ }
+ acodec libaacplus;
+ abitrate 30;
+ asample_rate 44100;
+ achannels 2;
+ aparams {
+ }
+ output rtmp://[vhost]:[port]/[app]/[stream]_crop;
+ }
+ }
+}
+# the crop filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#crop
+vhost logo.transcode.vhost.com {
+ transcode {
+ enabled on;
+ ffmpeg ./objs/ffmpeg/bin/ffmpeg;
+ engine logo{
+ enabled on;
+ vfilter {
+ vf 'crop=200:100:10:10';
+ }
+ vcodec libx264;
+ vbitrate 300;
+ vfps 20;
+ vwidth 480;
+ vheight 320;
+ vthreads 2;
+ vprofile baseline;
+ vpreset superfast;
+ vparams {
+ }
+ acodec libaacplus;
+ abitrate 30;
+ asample_rate 44100;
+ achannels 2;
+ aparams {
+ }
+ output rtmp://[vhost]:[port]/[app]/[stream]_logo;
+ }
+ }
+}
# transcode all stream using the empty ffmpeg demo, donothing.
vhost ffempty.transcode.vhost.com {
transcode {
enabled on;
ffmpeg ./research/ffempty/ffempty;
- engine fd{
+ engine empty{
enabled on;
vcodec libx264;
vbitrate 300;
@@ -205,7 +320,7 @@ vhost ffempty.transcode.vhost.com {
achannels 2;
aparams {
}
- output rtmp://[vhost]:[port]/[app]/[stream]_fast;
+ output rtmp://[vhost]:[port]/[app]/[stream]_empty;
}
}
}
diff --git a/trunk/doc/ffmpeg-logo.png b/trunk/doc/ffmpeg-logo.png
new file mode 100644
index 000000000..d93339690
Binary files /dev/null and b/trunk/doc/ffmpeg-logo.png differ
diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp
index a517449c0..60c64c572 100644
--- a/trunk/src/core/srs_core_config.cpp
+++ b/trunk/src/core/srs_core_config.cpp
@@ -768,30 +768,48 @@ std::string SrsConfig::get_engine_vpreset(SrsConfDirective* engine)
return conf->arg0();
}
-std::string SrsConfig::get_engine_vparams(SrsConfDirective* engine)
+void SrsConfig::get_engine_vparams(SrsConfDirective* engine, std::vector& vparams)
{
if (!engine) {
- return "";
+ return;
}
SrsConfDirective* conf = engine->get("vparams");
if (!conf) {
- return "";
+ return;
}
- std::string avparams;
for (int i = 0; i < (int)conf->directives.size(); i++) {
SrsConfDirective* p = conf->directives[i];
if (!p) {
continue;
}
- avparams += p->name;
- avparams += " ";
- avparams += p->arg0();
+ vparams.push_back("-" + p->name);
+ vparams.push_back(p->arg0());
+ }
+}
+
+void SrsConfig::get_engine_vfilter(SrsConfDirective* engine, std::vector& vfilter)
+{
+ if (!engine) {
+ return;
}
- return avparams;
+ SrsConfDirective* conf = engine->get("vfilter");
+ if (!conf) {
+ return;
+ }
+
+ for (int i = 0; i < (int)conf->directives.size(); i++) {
+ SrsConfDirective* p = conf->directives[i];
+ if (!p) {
+ continue;
+ }
+
+ vfilter.push_back("-" + p->name);
+ vfilter.push_back(p->arg0());
+ }
}
std::string SrsConfig::get_engine_acodec(SrsConfDirective* engine)
@@ -850,30 +868,26 @@ int SrsConfig::get_engine_achannels(SrsConfDirective* engine)
return ::atoi(conf->arg0().c_str());
}
-std::string SrsConfig::get_engine_aparams(SrsConfDirective* engine)
+void SrsConfig::get_engine_aparams(SrsConfDirective* engine, std::vector& aparams)
{
if (!engine) {
- return "";
+ return;
}
SrsConfDirective* conf = engine->get("aparams");
if (!conf) {
- return "";
+ return;
}
- std::string avparams;
for (int i = 0; i < (int)conf->directives.size(); i++) {
SrsConfDirective* p = conf->directives[i];
if (!p) {
continue;
}
- avparams += p->name;
- avparams += " ";
- avparams += p->arg0();
+ aparams.push_back("-" + p->name);
+ aparams.push_back(p->arg0());
}
-
- return avparams;
}
std::string SrsConfig::get_engine_output(SrsConfDirective* engine)
diff --git a/trunk/src/core/srs_core_config.hpp b/trunk/src/core/srs_core_config.hpp
index 7935262b0..a43c79899 100644
--- a/trunk/src/core/srs_core_config.hpp
+++ b/trunk/src/core/srs_core_config.hpp
@@ -128,12 +128,13 @@ public:
virtual int get_engine_vthreads(SrsConfDirective* engine);
virtual std::string get_engine_vprofile(SrsConfDirective* engine);
virtual std::string get_engine_vpreset(SrsConfDirective* engine);
- virtual std::string get_engine_vparams(SrsConfDirective* engine);
+ virtual void get_engine_vparams(SrsConfDirective* engine, std::vector& vparams);
+ virtual void get_engine_vfilter(SrsConfDirective* engine, std::vector& vfilter);
virtual std::string get_engine_acodec(SrsConfDirective* engine);
virtual int get_engine_abitrate(SrsConfDirective* engine);
virtual int get_engine_asample_rate(SrsConfDirective* engine);
virtual int get_engine_achannels(SrsConfDirective* engine);
- virtual std::string get_engine_aparams(SrsConfDirective* engine);
+ virtual void get_engine_aparams(SrsConfDirective* engine, std::vector& aparams);
virtual std::string get_engine_output(SrsConfDirective* engine);
virtual SrsConfDirective* get_gop_cache(std::string vhost);
virtual SrsConfDirective* get_forward(std::string vhost);
diff --git a/trunk/src/core/srs_core_encoder.cpp b/trunk/src/core/srs_core_encoder.cpp
index 1ee75172d..10f4acf31 100644
--- a/trunk/src/core/srs_core_encoder.cpp
+++ b/trunk/src/core/srs_core_encoder.cpp
@@ -60,6 +60,7 @@ int SrsFFMPEG::initialize(std::string vhost, std::string port, std::string app,
{
int ret = ERROR_SUCCESS;
+ config->get_engine_vfilter(engine, vfilter);
vcodec = config->get_engine_vcodec(engine);
vbitrate = config->get_engine_vbitrate(engine);
vfps = config->get_engine_vfps(engine);
@@ -68,12 +69,12 @@ int SrsFFMPEG::initialize(std::string vhost, std::string port, std::string app,
vthreads = config->get_engine_vthreads(engine);
vprofile = config->get_engine_vprofile(engine);
vpreset = config->get_engine_vpreset(engine);
- vparams = config->get_engine_vparams(engine);
+ config->get_engine_vparams(engine, vparams);
acodec = config->get_engine_acodec(engine);
abitrate = config->get_engine_abitrate(engine);
asample_rate = config->get_engine_asample_rate(engine);
achannels = config->get_engine_achannels(engine);
- aparams = config->get_engine_aparams(engine);
+ config->get_engine_aparams(engine, aparams);
output = config->get_engine_output(engine);
// ensure the size is even.
@@ -198,37 +199,109 @@ int SrsFFMPEG::start()
return ret;
}
- // prepare execl params
- char vsize[22];
- snprintf(vsize, sizeof(vsize), "%dx%d", vwidth, vheight);
- char vaspect[22];
- snprintf(vaspect, sizeof(vaspect), "%d:%d", vwidth, vheight);
- char s_vbitrate[10];
- snprintf(s_vbitrate, sizeof(s_vbitrate), "%d", vbitrate * 1000);
- char s_vfps[10];
- snprintf(s_vfps, sizeof(s_vfps), "%.2f", vfps);
- char s_vthreads[10];
- snprintf(s_vthreads, sizeof(s_vthreads), "%d", vthreads);
- char s_abitrate[10];
- snprintf(s_abitrate, sizeof(s_abitrate), "%d", abitrate * 1000);
- char s_asample_rate[10];
- snprintf(s_asample_rate, sizeof(s_asample_rate), "%d", asample_rate);
- char s_achannels[10];
- snprintf(s_achannels, sizeof(s_achannels), "%d", achannels);
+ // prepare exec params
+ char tmp[256];
+ std::vector params;
- // TODO: execl donot support the params.
- // video params
- std::string s_vpreset = vpreset;
+ // argv[0], set to ffmpeg bin.
+ // The execv() and execvp() functions ....
+ // The first argument, by convention, should point to
+ // the filename associated with the file being executed.
+ params.push_back(ffmpeg);
+
+ // input.
+ params.push_back("-f");
+ params.push_back("flv");
+
+ params.push_back("-i");
+ params.push_back(input);
+
+ // build the filter
+ if (!vfilter.empty()) {
+ std::vector::iterator it;
+ for (it = vfilter.begin(); it != vfilter.end(); ++it) {
+ std::string p = *it;
+ if (!p.empty()) {
+ params.push_back(p);
+ }
+ }
+ }
+
+ // video specified.
+ params.push_back("-vcodec");
+ params.push_back(vcodec);
+
+ params.push_back("-b:v");
+ snprintf(tmp, sizeof(tmp), "%d", vbitrate * 1000);
+ params.push_back(tmp);
+
+ params.push_back("-r");
+ snprintf(tmp, sizeof(tmp), "%.2f", vfps);
+ params.push_back(tmp);
+
+ params.push_back("-s");
+ snprintf(tmp, sizeof(tmp), "%dx%d", vwidth, vheight);
+ params.push_back(tmp);
+
+ // TODO: add aspect if needed.
+ params.push_back("-aspect");
+ snprintf(tmp, sizeof(tmp), "%d:%d", vwidth, vheight);
+ params.push_back(tmp);
+
+ params.push_back("-threads");
+ snprintf(tmp, sizeof(tmp), "%d", vthreads);
+ params.push_back(tmp);
+
+ params.push_back("-profile:v");
+ params.push_back(vprofile);
+
+ params.push_back("-preset");
+ params.push_back(vpreset);
+
+ // vparams
if (!vparams.empty()) {
- s_vpreset += " ";
- s_vpreset += vparams;
+ std::vector::iterator it;
+ for (it = vparams.begin(); it != vparams.end(); ++it) {
+ std::string p = *it;
+ if (!p.empty()) {
+ params.push_back(p);
+ }
+ }
}
- // audio params
- std::string s_aparams = s_achannels;
+
+ // audio specified.
+ params.push_back("-acodec");
+ params.push_back(acodec);
+
+ params.push_back("-b:a");
+ snprintf(tmp, sizeof(tmp), "%d", abitrate * 1000);
+ params.push_back(tmp);
+
+ params.push_back("-ar");
+ snprintf(tmp, sizeof(tmp), "%d", asample_rate);
+ params.push_back(tmp);
+
+ params.push_back("-ac");
+ snprintf(tmp, sizeof(tmp), "%d", achannels);
+ params.push_back(tmp);
+
+ // aparams
if (!aparams.empty()) {
- s_aparams += " ";
- s_aparams += aparams;
+ std::vector::iterator it;
+ for (it = aparams.begin(); it != aparams.end(); ++it) {
+ std::string p = *it;
+ if (!p.empty()) {
+ params.push_back(p);
+ }
+ }
}
+
+ // output
+ params.push_back("-f");
+ params.push_back("flv");
+
+ params.push_back("-y");
+ params.push_back(output);
// TODO: fork or vfork?
if ((pid = fork()) < 0) {
@@ -239,28 +312,17 @@ int SrsFFMPEG::start()
// child process: ffmpeg encoder engine.
if (pid == 0) {
- // TODO: execl or execlp
- ret = execl(ffmpeg.c_str(),
- "-f", "flv",
- "-i", input.c_str(),
- // video specified.
- "-vcodec", vcodec.c_str(),
- "-b:v", s_vbitrate,
- "-r", s_vfps,
- "-s", vsize,
- "-aspect", vaspect, // TODO: add aspect if needed.
- "-threads", s_vthreads,
- "-profile:v", vprofile.c_str(),
- "-preset", s_vpreset.c_str(),
- // audio specified.
- "-acodec", acodec.c_str(),
- "-b:a", s_abitrate,
- "-ar", s_asample_rate,
- "-ac", s_aparams.c_str(),
- "-f", "flv",
- "-y", output.c_str(),
- NULL
- );
+ // memory leak in child process, it's ok.
+ char** charpv_params = new char*[params.size() + 1];
+ for (int i = 0; i < (int)params.size(); i++) {
+ std::string p = params[i];
+ charpv_params[i] = (char*)p.c_str();
+ }
+ // EOF: NULL
+ charpv_params[params.size()] = NULL;
+
+ // TODO: execv or execvp
+ ret = execv(ffmpeg.c_str(), charpv_params);
if (ret < 0) {
fprintf(stderr, "fork ffmpeg failed, errno=%d(%s)",
errno, strerror(errno));
@@ -347,7 +409,7 @@ int SrsEncoder::on_publish(std::string _vhost, std::string _port, std::string _a
ret = parse_scope_engines();
// ignore the loop encoder
- if (ret = ERROR_ENCODER_LOOP) {
+ if (ret == ERROR_ENCODER_LOOP) {
ret = ERROR_SUCCESS;
}
diff --git a/trunk/src/core/srs_core_encoder.hpp b/trunk/src/core/srs_core_encoder.hpp
index f13e8dfa9..fe3bb362c 100644
--- a/trunk/src/core/srs_core_encoder.hpp
+++ b/trunk/src/core/srs_core_encoder.hpp
@@ -47,6 +47,7 @@ private:
pid_t pid;
private:
std::string ffmpeg;
+ std::vector vfilter;
std::string vcodec;
int vbitrate;
double vfps;
@@ -55,12 +56,12 @@ private:
int vthreads;
std::string vprofile;
std::string vpreset;
- std::string vparams;
+ std::vector vparams;
std::string acodec;
int abitrate;
int asample_rate;
int achannels;
- std::string aparams;
+ std::vector aparams;
std::string output;
std::string input;
public: