mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
support live stream transcoding by ffmpeg.
This commit is contained in:
parent
ee18b9f537
commit
525af4599f
6 changed files with 145 additions and 26 deletions
9
trunk/auto/depends.sh
Normal file → Executable file
9
trunk/auto/depends.sh
Normal file → Executable file
|
@ -109,3 +109,12 @@ if [ $SRS_FFMPEG = YES ]; then
|
||||||
else
|
else
|
||||||
echo "#undef SRS_FFMPEG" >> $SRS_AUTO_HEADERS_H
|
echo "#undef SRS_FFMPEG" >> $SRS_AUTO_HEADERS_H
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#####################################################################################
|
||||||
|
# build research code
|
||||||
|
#####################################################################################
|
||||||
|
(cd research/hls && make)
|
||||||
|
ret=$?; if [[ $ret -ne 0 ]]; then echo "build research/hls failed, ret=$ret"; exit $ret; fi
|
||||||
|
|
||||||
|
(cd research/ffempty && make)
|
||||||
|
ret=$?; if [[ $ret -ne 0 ]]; then echo "build research/ffempty failed, ret=$ret"; exit $ret; fi
|
||||||
|
|
|
@ -18,7 +18,8 @@ vhost __defaultVhost__ {
|
||||||
#forward 127.0.0.1:1936;
|
#forward 127.0.0.1:1936;
|
||||||
transcode {
|
transcode {
|
||||||
enabled on;
|
enabled on;
|
||||||
ffmpeg /home/winlin/srs/objs/ffmpeg/bin/ffmpeg;
|
ffmpeg ./objs/ffmpeg/bin/ffmpeg;
|
||||||
|
#ffmpeg ./research/ffempty/ffempty;
|
||||||
engine fd{
|
engine fd{
|
||||||
enabled on;
|
enabled on;
|
||||||
vcodec libx264;
|
vcodec libx264;
|
||||||
|
@ -29,12 +30,14 @@ vhost __defaultVhost__ {
|
||||||
vthreads 2;
|
vthreads 2;
|
||||||
vprofile baseline;
|
vprofile baseline;
|
||||||
vpreset superfast;
|
vpreset superfast;
|
||||||
vparams {}
|
vparams {
|
||||||
|
}
|
||||||
acodec libaacplus;
|
acodec libaacplus;
|
||||||
abitrate 30;
|
abitrate 30;
|
||||||
asample_rate 22050;
|
asample_rate 44100;
|
||||||
achannels 2;
|
achannels 2;
|
||||||
aparams {}
|
aparams {
|
||||||
|
}
|
||||||
output rtmp://[vhost]:[port]/[app]/[stream]_fast;
|
output rtmp://[vhost]:[port]/[app]/[stream]_fast;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,7 +154,34 @@ vhost all.transcode.vhost.com {
|
||||||
}
|
}
|
||||||
acodec libaacplus;
|
acodec libaacplus;
|
||||||
abitrate 30;
|
abitrate 30;
|
||||||
asample_rate 22050;
|
asample_rate 44100;
|
||||||
|
achannels 2;
|
||||||
|
aparams {
|
||||||
|
}
|
||||||
|
output rtmp://[vhost]:[port]/[app]/[stream]_fast;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# transcode all stream using the empty ffmpeg demo, donothing.
|
||||||
|
vhost ffempty.transcode.vhost.com {
|
||||||
|
transcode {
|
||||||
|
enabled on;
|
||||||
|
ffmpeg ./research/ffempty/ffempty;
|
||||||
|
engine fd{
|
||||||
|
enabled on;
|
||||||
|
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;
|
achannels 2;
|
||||||
aparams {
|
aparams {
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,12 @@ int main(int argc, char** argv)
|
||||||
fprintf(stderr, "argv[%d]=%s\n", i, argv[i]);
|
fprintf(stderr, "argv[%d]=%s\n", i, argv[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "summary:\n");
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
fprintf(stderr, "%s ", argv[i]);
|
||||||
|
}
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ int SrsFFMPEG::initialize(std::string vhost, std::string port, std::string app,
|
||||||
vheight -= vheight % 2;
|
vheight -= vheight % 2;
|
||||||
|
|
||||||
// input stream, from local.
|
// input stream, from local.
|
||||||
|
// ie. rtmp://127.0.0.1:1935/live/livestream
|
||||||
input = "rtmp://127.0.0.1:";
|
input = "rtmp://127.0.0.1:";
|
||||||
input += port;
|
input += port;
|
||||||
input += "/";
|
input += "/";
|
||||||
|
@ -88,6 +89,8 @@ int SrsFFMPEG::initialize(std::string vhost, std::string port, std::string app,
|
||||||
input += "/";
|
input += "/";
|
||||||
input += stream;
|
input += stream;
|
||||||
|
|
||||||
|
// output stream, to other/self server
|
||||||
|
// ie. rtmp://127.0.0.1:1935/live/livestream_sd
|
||||||
if (vhost == RTMP_VHOST_DEFAULT) {
|
if (vhost == RTMP_VHOST_DEFAULT) {
|
||||||
output = srs_replace(output, "[vhost]", "127.0.0.1");
|
output = srs_replace(output, "[vhost]", "127.0.0.1");
|
||||||
} else {
|
} else {
|
||||||
|
@ -97,6 +100,22 @@ int SrsFFMPEG::initialize(std::string vhost, std::string port, std::string app,
|
||||||
output = srs_replace(output, "[app]", app);
|
output = srs_replace(output, "[app]", app);
|
||||||
output = srs_replace(output, "[stream]", stream);
|
output = srs_replace(output, "[stream]", stream);
|
||||||
|
|
||||||
|
// important: loop check, donot transcode again.
|
||||||
|
// we think the following is loop circle:
|
||||||
|
// input: rtmp://127.0.0.1:1935/live/livestream_sd
|
||||||
|
// output: rtmp://127.0.0.1:1935/live/livestream_sd_sd
|
||||||
|
std::string tail = ""; // tail="_sd"
|
||||||
|
if (output.length() > input.length()) {
|
||||||
|
tail = output.substr(input.length());
|
||||||
|
}
|
||||||
|
// if input also endwiths the tail, loop detected.
|
||||||
|
if (!tail.empty() && input.rfind(tail) == input.length() - tail.length()) {
|
||||||
|
ret = ERROR_ENCODER_LOOP;
|
||||||
|
srs_info("detect a loop cycle, input=%s, output=%s, ignore it. ret=%d",
|
||||||
|
input.c_str(), output.c_str(), ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
if (vcodec != SRS_ENCODER_VCODEC) {
|
if (vcodec != SRS_ENCODER_VCODEC) {
|
||||||
ret = ERROR_ENCODER_VCODEC;
|
ret = ERROR_ENCODER_VCODEC;
|
||||||
srs_error("invalid vcodec, must be %s, actual %s, ret=%d",
|
srs_error("invalid vcodec, must be %s, actual %s, ret=%d",
|
||||||
|
@ -184,9 +203,32 @@ int SrsFFMPEG::start()
|
||||||
snprintf(vsize, sizeof(vsize), "%dx%d", vwidth, vheight);
|
snprintf(vsize, sizeof(vsize), "%dx%d", vwidth, vheight);
|
||||||
char vaspect[22];
|
char vaspect[22];
|
||||||
snprintf(vaspect, sizeof(vaspect), "%d:%d", vwidth, vheight);
|
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);
|
||||||
|
|
||||||
|
// video params
|
||||||
|
std::string s_vpreset = vpreset;
|
||||||
|
if (!vparams.empty()) {
|
||||||
|
s_vpreset += " ";
|
||||||
|
s_vpreset += vparams;
|
||||||
|
}
|
||||||
|
// audio params
|
||||||
|
std::string s_aparams = s_achannels;
|
||||||
|
if (!aparams.empty()) {
|
||||||
|
s_aparams += " ";
|
||||||
|
s_aparams += aparams;
|
||||||
|
}
|
||||||
|
|
||||||
// we use vfork, for we use fored process
|
|
||||||
// to start ffmpeg, that is, exec after vfork.
|
|
||||||
if ((pid = fork()) < 0) {
|
if ((pid = fork()) < 0) {
|
||||||
ret = ERROR_ENCODER_FORK;
|
ret = ERROR_ENCODER_FORK;
|
||||||
srs_error("vfork process failed. ret=%d", ret);
|
srs_error("vfork process failed. ret=%d", ret);
|
||||||
|
@ -195,25 +237,24 @@ int SrsFFMPEG::start()
|
||||||
|
|
||||||
// child process: ffmpeg encoder engine.
|
// child process: ffmpeg encoder engine.
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
// must exec immediately, or may introduce bug.
|
|
||||||
ret = execl(ffmpeg.c_str(),
|
ret = execl(ffmpeg.c_str(),
|
||||||
|
ffmpeg.c_str(),
|
||||||
|
"-f", "flv",
|
||||||
"-i", input.c_str(),
|
"-i", input.c_str(),
|
||||||
// video specified.
|
// video specified.
|
||||||
"-vcodec", vcodec.c_str(),
|
"-vcodec", vcodec.c_str(),
|
||||||
"-b:v", vbitrate * 1000,
|
"-b:v", s_vbitrate,
|
||||||
"-r", vfps,
|
"-r", s_vfps,
|
||||||
"-size", vsize,
|
"-s", vsize,
|
||||||
"-aspect", vaspect, // TODO: add aspect if needed.
|
"-aspect", vaspect, // TODO: add aspect if needed.
|
||||||
"-threads", vthreads,
|
"-threads", s_vthreads,
|
||||||
"-profile", vprofile.c_str(),
|
"-profile:v", vprofile.c_str(),
|
||||||
"-preset", vpreset.c_str(),
|
"-preset", s_vpreset.c_str(),
|
||||||
vparams.c_str(),
|
|
||||||
// audio specified.
|
// audio specified.
|
||||||
"-acodec", acodec.c_str(),
|
"-acodec", acodec.c_str(),
|
||||||
"-b:a", abitrate * 1000,
|
"-b:a", s_abitrate,
|
||||||
"-ar", asample_rate,
|
"-ar", s_asample_rate,
|
||||||
"-ac", achannels,
|
"-ac", s_aparams.c_str(),
|
||||||
aparams.c_str(),
|
|
||||||
"-f", "flv",
|
"-f", "flv",
|
||||||
"-y", output.c_str(),
|
"-y", output.c_str(),
|
||||||
NULL
|
NULL
|
||||||
|
@ -253,15 +294,10 @@ SrsEncoder::~SrsEncoder()
|
||||||
on_unpublish();
|
on_unpublish();
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsEncoder::on_publish(std::string _vhost, std::string _port, std::string _app, std::string _stream)
|
int SrsEncoder::parse_scope_engines()
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
vhost = _vhost;
|
|
||||||
port = _port;
|
|
||||||
app = _app;
|
|
||||||
stream = _stream;
|
|
||||||
|
|
||||||
// parse all transcode engines.
|
// parse all transcode engines.
|
||||||
SrsConfDirective* conf = NULL;
|
SrsConfDirective* conf = NULL;
|
||||||
|
|
||||||
|
@ -294,6 +330,30 @@ int SrsEncoder::on_publish(std::string _vhost, std::string _port, std::string _a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsEncoder::on_publish(std::string _vhost, std::string _port, std::string _app, std::string _stream)
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
|
vhost = _vhost;
|
||||||
|
port = _port;
|
||||||
|
app = _app;
|
||||||
|
stream = _stream;
|
||||||
|
|
||||||
|
ret = parse_scope_engines();
|
||||||
|
|
||||||
|
// ignore the loop encoder
|
||||||
|
if (ret = ERROR_ENCODER_LOOP) {
|
||||||
|
ret = ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return for error or no engine.
|
||||||
|
if (ret != ERROR_SUCCESS || ffmpegs.empty()) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// start thread to run all encoding engines.
|
// start thread to run all encoding engines.
|
||||||
srs_assert(!tid);
|
srs_assert(!tid);
|
||||||
if((tid = st_thread_create(encoder_thread, this, 1, 0)) == NULL) {
|
if((tid = st_thread_create(encoder_thread, this, 1, 0)) == NULL) {
|
||||||
|
@ -314,6 +374,11 @@ void SrsEncoder::on_unpublish()
|
||||||
tid = NULL;
|
tid = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clear_engines();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SrsEncoder::clear_engines()
|
||||||
|
{
|
||||||
std::vector<SrsFFMPEG*>::iterator it;
|
std::vector<SrsFFMPEG*>::iterator it;
|
||||||
for (it = ffmpegs.begin(); it != ffmpegs.end(); ++it) {
|
for (it = ffmpegs.begin(); it != ffmpegs.end(); ++it) {
|
||||||
SrsFFMPEG* ffmpeg = *it;
|
SrsFFMPEG* ffmpeg = *it;
|
||||||
|
@ -371,6 +436,12 @@ int SrsEncoder::parse_transcode(SrsConfDirective* conf)
|
||||||
if ((ret = ffmpeg->initialize(vhost, port, app, stream, engine)) != ERROR_SUCCESS) {
|
if ((ret = ffmpeg->initialize(vhost, port, app, stream, engine)) != ERROR_SUCCESS) {
|
||||||
srs_freep(ffmpeg);
|
srs_freep(ffmpeg);
|
||||||
|
|
||||||
|
// if got a loop, donot transcode the whole stream.
|
||||||
|
if (ret == ERROR_ENCODER_LOOP) {
|
||||||
|
clear_engines();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
srs_error("invalid transcode engine: %s %s",
|
srs_error("invalid transcode engine: %s %s",
|
||||||
conf->arg0().c_str(), engine->arg0().c_str());
|
conf->arg0().c_str(), engine->arg0().c_str());
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -95,6 +95,8 @@ public:
|
||||||
virtual int on_publish(std::string vhost, std::string port, std::string app, std::string stream);
|
virtual int on_publish(std::string vhost, std::string port, std::string app, std::string stream);
|
||||||
virtual void on_unpublish();
|
virtual void on_unpublish();
|
||||||
private:
|
private:
|
||||||
|
virtual int parse_scope_engines();
|
||||||
|
virtual void clear_engines();
|
||||||
virtual SrsFFMPEG* at(int index);
|
virtual SrsFFMPEG* at(int index);
|
||||||
virtual int parse_transcode(SrsConfDirective* conf);
|
virtual int parse_transcode(SrsConfDirective* conf);
|
||||||
virtual int cycle();
|
virtual int cycle();
|
||||||
|
|
|
@ -136,5 +136,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
#define ERROR_ENCODER_VFPS 711
|
#define ERROR_ENCODER_VFPS 711
|
||||||
#define ERROR_ENCODER_VBITRATE 712
|
#define ERROR_ENCODER_VBITRATE 712
|
||||||
#define ERROR_ENCODER_FORK 713
|
#define ERROR_ENCODER_FORK 713
|
||||||
|
#define ERROR_ENCODER_LOOP 714
|
||||||
|
|
||||||
#endif
|
#endif
|
Loading…
Add table
Add a link
Reference in a new issue