From c87a7df1e13a1205dddb57824188a6c8496e9601 Mon Sep 17 00:00:00 2001 From: realaboo Date: Sat, 19 Nov 2022 15:52:28 +0800 Subject: [PATCH] Add support to customize ffmpeg output options in relay and fission tasks. --- README.md | 41 +++++++++++++++++++++++++++++++++++- README_CN.md | 39 ++++++++++++++++++++++++++++++++++ src/api/controllers/relay.js | 9 ++++++-- src/node_fission_server.js | 2 +- src/node_fission_session.js | 24 +++++++++++++++------ src/node_relay_server.js | 6 ++++-- src/node_relay_session.js | 3 ++- 7 files changed, 110 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index aa9ca709..7b0219c2 100644 --- a/README.md +++ b/README.md @@ -602,7 +602,7 @@ nms.run(); ``` # Rtsp/Rtmp Relay -NodeMediaServer implement RTSP and RTMP relay with ffmpeg. +NodeMediaServer implements RTSP and RTMP relay with ffmpeg. ## Static pull The static pull mode is executed at service startup and reconnect after failure. @@ -668,6 +668,22 @@ relay: { } ``` +## Transcoding Options +FFmpeg output options could be added for the pull/push modes above. Default behavior is `-c copy`. +``` +relay: { + ffmpeg: '/usr/local/bin/ffmpeg', + tasks: [ + { + app: 'live', + mode: 'push', + options: ['-vf', 'scale=1920:-1', '-c:v', 'libx264', '-b:v', '2m', '-c:a', 'copy'], + edge: 'rtmp://192.168.0.10', + } + ] +} +``` + # Fission Real-time transcoding multi-resolution output ![fission](https://raw.githubusercontent.com/illuspas/resources/master/img/admin_panel_fission.png) @@ -725,6 +741,29 @@ fission: { } ``` +## Custom Transcoding Options +Custom FFmpeg output options could be specified directly. In this case, a suffix should also be provided for the transcoded stream. +``` +fission: { + ffmpeg: '/usr/local/bin/ffmpeg', + tasks: [ + { + rule: 'live/*', + model: [ + { + options: ['-c:v', 'hevc_nvenc', '-b:v', '10m', '-vf', 'scale=3840:-1', '-c:a', 'copy'], + suffix: 'uhd5' + }, + { + options: ['-c:v', 'libx264', '-b:v', '4m', '-vf', 'scale=1920:-1', '-c:a', 'copy'], + suffix: 'hd4' + } + ] + } + ] +} +``` + # Publisher and Player App/SDK ## Android Livestream App diff --git a/README_CN.md b/README_CN.md index 1ded4fc4..53549ea4 100644 --- a/README_CN.md +++ b/README_CN.md @@ -562,6 +562,22 @@ relay: { } ``` +## 转码选项 +可以为上面的推流和拉流模式配置FFmpeg输出选项。默认值是 `-c copy`. +``` +relay: { + ffmpeg: '/usr/local/bin/ffmpeg', + tasks: [ + { + app: 'live', + mode: 'push', + options: ['-vf', 'scale=1920:-1', '-c:v', 'libx264', '-b:v', '2m', '-c:a', 'copy'], + edge: 'rtmp://192.168.0.10', + } + ] +} +``` + # 实时多分辨率转码 ![fission](https://raw.githubusercontent.com/illuspas/resources/master/img/admin_panel_fission.png) ``` @@ -618,6 +634,29 @@ fission: { } ``` +## 自定义FFmpeg选项 +可以直接配置FFmpeg输出选项,同时需要为转码后的流设置后缀。 +``` +fission: { + ffmpeg: '/usr/local/bin/ffmpeg', + tasks: [ + { + rule: 'live/*', + model: [ + { + options: ['-c:v', 'hevc_nvenc', '-b:v', '10m', '-vf', 'scale=3840:-1', '-c:a', 'copy'], + suffix: 'uhd5' + }, + { + options: ['-c:v', 'libx264', '-b:v', '4m', '-vf', 'scale=1920:-1', '-c:a', 'copy'], + suffix: 'hd4' + } + ] + } + ] +} +``` + # 推流与播放 App/SDK ## Android Livestream App diff --git a/src/api/controllers/relay.js b/src/api/controllers/relay.js index 53c3fac6..1843b013 100644 --- a/src/api/controllers/relay.js +++ b/src/api/controllers/relay.js @@ -31,6 +31,7 @@ function getStreams(req, res, next) { stats[app][name]['relays'].push({ app: app, name: name, + options: session.conf.options, path: session.conf.inPath, url: session.conf.ouPath, mode: session.conf.mode, @@ -56,6 +57,7 @@ function getStreamByID(req, res, next) { const relays = relaySession.map((item) => ({ app: item.conf.app, name: item.conf.name, + options: item.conf.options, path: item.conf.inPath, url: item.conf.ouPath, mode: item.conf.mode, @@ -81,6 +83,7 @@ function getStreamByName(req, res, next) { const relays = relaySession.map((item) => ({ app: item.conf.app, name: item.conf.name, + options: item.conf.options, url: item.conf.ouPath, mode: item.conf.mode, ts: item.ts, @@ -118,9 +121,10 @@ async function pullStream(req, res, next) { let url = req.body.url; let app = req.body.app; let name = req.body.name; + let options = req.body.options ? req.body.options : null; let rtsp_transport = req.body.rtsp_transport ? req.body.rtsp_transport : null; if (url && app && name) { - process.nextTick(() => this.nodeEvent.emit('relayPull', url, app, name, rtsp_transport)); + process.nextTick(() => this.nodeEvent.emit('relayPull', url, app, name, options, rtsp_transport)); let ret = await once(this.nodeEvent, 'relayPullDone'); res.send(ret[0]); @@ -139,8 +143,9 @@ async function pushStream(req, res, next) { let url = req.body.url; let app = req.body.app; let name = req.body.name; + let options = req.body.options ? req.body.options : null; if (url && app && name) { - process.nextTick(() => this.nodeEvent.emit('relayPush', url, app, name)); + process.nextTick(() => this.nodeEvent.emit('relayPush', url, app, name, options)); let ret = await once(this.nodeEvent, 'relayPushDone'); res.send(ret[0]); } else { diff --git a/src/node_fission_server.js b/src/node_fission_server.js index 3810fd56..50f5ca65 100644 --- a/src/node_fission_server.js +++ b/src/node_fission_server.js @@ -65,7 +65,7 @@ class NodeFissionServer { conf.streamApp = app; conf.streamName = name; conf.args = args; - let session = new NodeFissionSession(conf); + let session = new NodeFissionSession(id, conf); this.fissionSessions.set(id, session); session.on('end', () => { this.fissionSessions.delete(id); diff --git a/src/node_fission_session.js b/src/node_fission_session.js index 62840de5..ababee63 100644 --- a/src/node_fission_session.js +++ b/src/node_fission_session.js @@ -9,8 +9,9 @@ const EventEmitter = require('events'); const { spawn } = require('child_process'); class NodeFissionSession extends EventEmitter { - constructor(conf) { + constructor(id, conf) { super(); + this.id = id; this.conf = conf; } @@ -18,15 +19,24 @@ class NodeFissionSession extends EventEmitter { let inPath = 'rtmp://127.0.0.1:' + this.conf.rtmpPort + this.conf.streamPath; let argv = ['-i', inPath]; for (let m of this.conf.model) { - let x264 = ['-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency', '-maxrate', m.vb, '-bufsize', m.vb, '-g', parseInt(m.vf) * 2, '-r', m.vf, '-s', m.vs]; - let aac = ['-c:a', 'aac', '-b:a', m.ab]; - let outPath = ['-f', 'flv', 'rtmp://127.0.0.1:' + this.conf.rtmpPort + '/' + this.conf.streamApp + '/' + this.conf.streamName + '_' + m.vs.split('x')[1]]; - argv.splice(argv.length, 0, ...x264); - argv.splice(argv.length, 0, ...aac); - argv.splice(argv.length, 0, ...outPath); + if (m.options) { + argv.splice(argv.length, 0, ...m.options); + let outPath = ['-f', 'flv', 'rtmp://127.0.0.1:' + this.conf.rtmpPort + '/' + this.conf.streamApp + '/' + this.conf.streamName + '_' + m.suffix]; + argv.splice(argv.length, 0, ...outPath); + } else { + let x264 = ['-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency', '-maxrate', m.vb, '-bufsize', m.vb, '-g', parseInt(m.vf) * 2, '-r', m.vf, '-s', m.vs]; + let aac = ['-c:a', 'aac', '-b:a', m.ab]; + let outPath = ['-f', 'flv', 'rtmp://127.0.0.1:' + this.conf.rtmpPort + '/' + this.conf.streamApp + '/' + this.conf.streamName + '_' + m.vs.split('x')[1]]; + argv.splice(argv.length, 0, ...x264); + argv.splice(argv.length, 0, ...aac); + argv.splice(argv.length, 0, ...outPath); + } } argv = argv.filter((n) => { return n; }); + + Logger.log('[fission task] id=' + this.id, 'cmd=ffmpeg', argv.join(' ')); + this.ffmpeg_exec = spawn(this.conf.ffmpeg, argv); this.ffmpeg_exec.on('error', (e) => { Logger.ffdebug(e); diff --git a/src/node_relay_server.js b/src/node_relay_server.js index 8206e0bc..08b190df 100644 --- a/src/node_relay_server.js +++ b/src/node_relay_server.js @@ -98,10 +98,11 @@ class NodeRelayServer { } //从远端拉推到本地 - onRelayPull(url, app, name, rtsp_transport) { + onRelayPull(url, app, name, options, rtsp_transport) { let conf = {}; conf.app = app; conf.name = name; + conf.options = options; conf.mode = 'pull'; conf.ffmpeg = this.config.relay.ffmpeg; conf.inPath = url; @@ -124,10 +125,11 @@ class NodeRelayServer { } //从本地拉推到远端 - onRelayPush(url, app, name) { + onRelayPush(url, app, name, options) { let conf = {}; conf.app = app; conf.name = name; + conf.options = options; conf.mode = 'push'; conf.ffmpeg = this.config.relay.ffmpeg; conf.inPath = `rtmp://127.0.0.1:${this.config.rtmp.port}/${app}/${name}`; diff --git a/src/node_relay_session.js b/src/node_relay_session.js index f3b3bf95..712948f9 100644 --- a/src/node_relay_session.js +++ b/src/node_relay_session.js @@ -22,7 +22,8 @@ class NodeRelaySession extends EventEmitter { run() { let format = this.conf.ouPath.startsWith('rtsp://') ? 'rtsp' : 'flv'; - let argv = ['-re', '-i', this.conf.inPath, '-c', 'copy', '-f', format, this.conf.ouPath]; + let options = this.conf.options ? this.conf.options: ['-c', 'copy']; + let argv = ['-re', '-i', this.conf.inPath, ...options, '-f', format, this.conf.ouPath]; if (this.conf.inPath[0] === '/' || this.conf.inPath[1] === ':') { argv.unshift('-1'); argv.unshift('-stream_loop');