Allow encoding the pulled stream to h264

This commit is contained in:
Ingo Oppermann
2019-04-28 12:50:09 +02:00
parent cc791c157c
commit 8e24b8f2bb
12 changed files with 622 additions and 211 deletions

View File

@@ -7,6 +7,7 @@ MAINTAINER datarhei <info@datarhei.org>
ARG NASM_VERSION=2.14.02 ARG NASM_VERSION=2.14.02
ARG LAME_VERSION=3.100 ARG LAME_VERSION=3.100
ARG X264_VERSION=20190409-2245-stable ARG X264_VERSION=20190409-2245-stable
ARG X265_VERSION=3.0
ARG FFMPEG_VERSION=4.1.3 ARG FFMPEG_VERSION=4.1.3
ARG NGINX_VERSION=1.14.2 ARG NGINX_VERSION=1.14.2
ARG NGINXRTMP_VERSION=1.2.1 ARG NGINXRTMP_VERSION=1.2.1
@@ -25,7 +26,8 @@ RUN apt-get update && \
libssl-dev \ libssl-dev \
zlib1g-dev \ zlib1g-dev \
libasound2-dev \ libasound2-dev \
build-essential build-essential \
cmake
# nasm # nasm
RUN mkdir -p /dist && cd /dist && \ RUN mkdir -p /dist && cd /dist && \
@@ -45,6 +47,15 @@ RUN mkdir -p /dist && cd /dist && \
make -j$(nproc) && \ make -j$(nproc) && \
make install make install
# x265
RUN mkdir -p /dist && cd /dist && \
curl -OL "http://ftp.videolan.org/pub/videolan/x265/x265_${X265_VERSION}.tar.gz" && \
tar -xvz -f x265_${X265_VERSION}.tar.gz && \
cd x265_${X265_VERSION}/build && \
cmake ../source && \
make -j$(nproc) && \
make install
# libmp3lame # libmp3lame
RUN mkdir -p /dist && cd /dist && \ RUN mkdir -p /dist && cd /dist && \
curl -OL "https://kent.dl.sourceforge.net/project/lame/lame/${LAME_VERSION}/lame-${LAME_VERSION}.tar.gz" && \ curl -OL "https://kent.dl.sourceforge.net/project/lame/lame/${LAME_VERSION}/lame-${LAME_VERSION}.tar.gz" && \
@@ -54,11 +65,14 @@ RUN mkdir -p /dist && cd /dist && \
make -j$(nproc) && \ make -j$(nproc) && \
make install make install
# ffmpeg # ffmpeg && patch
COPY ./contrib/ffmpeg /dist/restreamer/contrib/ffmpeg
RUN mkdir -p /dist && cd /dist && \ RUN mkdir -p /dist && cd /dist && \
curl -OL "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \ curl -OL "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
tar -xvz -f ffmpeg-${FFMPEG_VERSION}.tar.gz && \ tar -xvz -f ffmpeg-${FFMPEG_VERSION}.tar.gz && \
cd ffmpeg-${FFMPEG_VERSION} && \ cd ffmpeg-${FFMPEG_VERSION} && \
patch -p1 < /dist/restreamer/contrib/ffmpeg/bitrate.patch && \
./configure \ ./configure \
--bindir="${SRC}/bin" \ --bindir="${SRC}/bin" \
--extra-cflags="-I${SRC}/include" \ --extra-cflags="-I${SRC}/include" \
@@ -69,6 +83,7 @@ RUN mkdir -p /dist && cd /dist && \
--enable-version3 \ --enable-version3 \
--enable-libmp3lame \ --enable-libmp3lame \
--enable-libx264 \ --enable-libx264 \
--enable-libx265 \
--enable-openssl \ --enable-openssl \
--enable-postproc \ --enable-postproc \
--enable-small \ --enable-small \
@@ -98,17 +113,6 @@ RUN mkdir -p /dist && cd /dist && \
cp -R bin /usr/local && \ cp -R bin /usr/local && \
cp -R lib /usr/local cp -R lib /usr/local
RUN rm -r /dist && \
apt-get remove -y \
pkg-config \
curl \
libpcre3-dev \
libtool \
libssl-dev \
zlib1g-dev \
build-essential && \
apt autoremove -y
FROM $IMAGE FROM $IMAGE
COPY --from=builder /usr/local/bin /usr/local/bin COPY --from=builder /usr/local/bin /usr/local/bin

View File

@@ -6,6 +6,7 @@ MAINTAINER datarhei <info@datarhei.org>
ARG LAME_VERSION=3.100 ARG LAME_VERSION=3.100
ARG X264_VERSION=20190409-2245-stable ARG X264_VERSION=20190409-2245-stable
ARG X265_VERSION=3.0
ARG FFMPEG_VERSION=4.1.3 ARG FFMPEG_VERSION=4.1.3
ARG NGINX_VERSION=1.14.2 ARG NGINX_VERSION=1.14.2
ARG NGINXRTMP_VERSION=1.2.1 ARG NGINXRTMP_VERSION=1.2.1
@@ -44,11 +45,14 @@ RUN mkdir -p /dist && cd /dist && \
make -j$(nproc) && \ make -j$(nproc) && \
make install make install
# ffmpeg # ffmpeg && patch
COPY ./contrib/ffmpeg /dist/restreamer/contrib/ffmpeg
RUN mkdir -p /dist && cd /dist && \ RUN mkdir -p /dist && cd /dist && \
curl -OL "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \ curl -OL "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
tar -xvz -f ffmpeg-${FFMPEG_VERSION}.tar.gz && \ tar -xvz -f ffmpeg-${FFMPEG_VERSION}.tar.gz && \
cd ffmpeg-${FFMPEG_VERSION} && \ cd ffmpeg-${FFMPEG_VERSION} && \
patch -p1 < /dist/restreamer/contrib/ffmpeg/bitrate.patch && \
./configure \ ./configure \
--bindir="${SRC}/bin" \ --bindir="${SRC}/bin" \
--extra-cflags="-I${SRC}/include" \ --extra-cflags="-I${SRC}/include" \

View File

@@ -6,6 +6,7 @@ MAINTAINER datarhei <info@datarhei.org>
ARG LAME_VERSION=3.100 ARG LAME_VERSION=3.100
ARG X264_VERSION=20190409-2245-stable ARG X264_VERSION=20190409-2245-stable
ARG X265_VERSION=3.0
ARG FFMPEG_VERSION=4.1.3 ARG FFMPEG_VERSION=4.1.3
ARG NGINX_VERSION=1.14.2 ARG NGINX_VERSION=1.14.2
ARG NGINXRTMP_VERSION=1.2.1 ARG NGINXRTMP_VERSION=1.2.1
@@ -44,11 +45,14 @@ RUN mkdir -p /dist && cd /dist && \
make -j$(nproc) && \ make -j$(nproc) && \
make install make install
# ffmpeg # ffmpeg && patch
COPY ./contrib/ffmpeg /dist/restreamer/contrib/ffmpeg
RUN mkdir -p /dist && cd /dist && \ RUN mkdir -p /dist && cd /dist && \
curl -OL "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \ curl -OL "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
tar -xvz -f ffmpeg-${FFMPEG_VERSION}.tar.gz && \ tar -xvz -f ffmpeg-${FFMPEG_VERSION}.tar.gz && \
cd ffmpeg-${FFMPEG_VERSION} && \ cd ffmpeg-${FFMPEG_VERSION} && \
patch -p1 < /dist/restreamer/contrib/ffmpeg/bitrate.patch && \
./configure \ ./configure \
--bindir="${SRC}/bin" \ --bindir="${SRC}/bin" \
--extra-cflags="-I${SRC}/include" \ --extra-cflags="-I${SRC}/include" \

View File

@@ -6,6 +6,7 @@ MAINTAINER datarhei <info@datarhei.org>
ARG LAME_VERSION=3.100 ARG LAME_VERSION=3.100
ARG X264_VERSION=20190409-2245-stable ARG X264_VERSION=20190409-2245-stable
ARG X265_VERSION=3.0
ARG FFMPEG_VERSION=4.1.3 ARG FFMPEG_VERSION=4.1.3
ARG NGINX_VERSION=1.14.2 ARG NGINX_VERSION=1.14.2
ARG NGINXRTMP_VERSION=1.2.1 ARG NGINXRTMP_VERSION=1.2.1
@@ -44,11 +45,14 @@ RUN mkdir -p /dist && cd /dist && \
make -j$(nproc) && \ make -j$(nproc) && \
make install make install
# ffmpeg # ffmpeg && patch
COPY ./contrib/ffmpeg /dist/restreamer/contrib/ffmpeg
RUN mkdir -p /dist && cd /dist && \ RUN mkdir -p /dist && cd /dist && \
curl -OL "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \ curl -OL "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
tar -xvz -f ffmpeg-${FFMPEG_VERSION}.tar.gz && \ tar -xvz -f ffmpeg-${FFMPEG_VERSION}.tar.gz && \
cd ffmpeg-${FFMPEG_VERSION} && \ cd ffmpeg-${FFMPEG_VERSION} && \
patch -p1 < /dist/restreamer/contrib/ffmpeg/bitrate.patch && \
./configure \ ./configure \
--bindir="${SRC}/bin" \ --bindir="${SRC}/bin" \
--extra-cflags="-I${SRC}/include" \ --extra-cflags="-I${SRC}/include" \

View File

@@ -1,81 +1,168 @@
{ {
"$schema": "http://json-schema.org/draft-04/schema#", "definitions": {},
"id": "http://jsonschema.net", "$schema": "http://json-schema.org/draft-07/schema#",
"type": "object", "$id": "http://jsonschema.net/",
"properties": { "type": "object",
"addresses": { "required": [
"id": "http://jsonschema.net/addresses", "addresses",
"type": "object", "options",
"properties": { "states",
"srcAddress": { "userActions"
"id": "http://jsonschema.net/addresses/srcAddress", ],
"type": "string" "properties": {
}, "addresses": {
"optionalOutputAddress": { "type": "object",
"id": "http://jsonschema.net/addresses/optionalOutputAddress", "required": [
"type": "string" "srcAddress",
} "optionalOutputAddress"
}, ],
"required": [ "properties": {
"srcAddress", "srcAddress": {
"optionalOutputAddress" "type": "string",
] "default": "",
"pattern": "^(.*)$"
}, },
"options": { "optionalOutputAddress": {
"id": "http://jsonschema.net/options", "type": "string",
"type": "object", "default": "",
"properties": { "pattern": "^(.*)$"
"rtspTcp": {
"id": "http://jsonschema.net/options/rtspTcp",
"type": "boolean"
}
}
},
"states": {
"id": "http://jsonschema.net/states",
"type": "object",
"properties": {
"repeatToLocalNginx": {
"id": "http://jsonschema.net/states/repeatToLocalNginx",
"type": "object",
"properties": {
"type": {
"id": "http://jsonschema.net/states/repeatToLocalNginx/type",
"type": "string"
}
}
},
"repeatToOptionalOutput": {
"id": "http://jsonschema.net/states/repeatToOptionalOutput",
"type": "object",
"properties": {
"type": {
"id": "http://jsonschema.net/states/repeatToOptionalOutput/type",
"type": "string"
}
}
}
}
},
"userActions": {
"id": "http://jsonschema.net/userActions",
"type": "object",
"properties": {
"repeatToLocalNginx": {
"id": "http://jsonschema.net/userActions/repeatToLocalNginx",
"type": "string"
},
"repeatToOptionalOutput": {
"id": "http://jsonschema.net/userActions/repeatToOptionalOutput",
"type": "string"
}
}
} }
}
}, },
"required": [ "options": {
"addresses", "type": "object",
"options", "required": [
"states", "rtspTcp"
"userActions" ],
] "properties": {
"rtspTcp": {
"type": "boolean",
"default": false
},
"video": {
"type": "object",
"default": null,
"required": [
"codec",
"preset",
"bitrate",
"fps"
],
"properties": {
"codec": {
"type": "string",
"default": "copy",
"pattern": "^(.*)$"
},
"preset": {
"type": "string",
"default": "ultrafast",
"pattern": "^(.*)$"
},
"bitrate": {
"type": "string",
"default": "4096",
"pattern": "^(.*)$"
},
"fps": {
"type": "string",
"default": "25",
"pattern": "^(.*)$"
}
}
},
"audio": {
"type": "object",
"required": [
"codec",
"preset",
"bitrate",
"channels",
"sampling"
],
"properties": {
"codec": {
"type": "string",
"default": "copy",
"pattern": "^(.*)$"
},
"preset": {
"type": "string",
"default": "silence",
"pattern": "^(.*)$"
},
"bitrate": {
"type": "string",
"default": "64",
"pattern": "^(.*)$"
},
"channels": {
"type": "string",
"default": "mono",
"pattern": "^(.*)$"
},
"sampling": {
"type": "string",
"default": "44100",
"pattern": "^(.*)$"
}
}
}
}
},
"states": {
"type": "object",
"required": [
"repeatToLocalNginx",
"repeatToOptionalOutput"
],
"properties": {
"repeatToLocalNginx": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"default": "",
"pattern": "^(.*)$"
}
}
},
"repeatToOptionalOutput": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"default": "",
"pattern": "^(.*)$"
}
}
}
}
},
"userActions": {
"type": "object",
"required": [
"repeatToLocalNginx",
"repeatToOptionalOutput"
],
"properties": {
"repeatToLocalNginx": {
"type": "string",
"default": "",
"pattern": "^(.*)$"
},
"repeatToOptionalOutput": {
"type": "string",
"default": "",
"pattern": "^(.*)$"
}
}
}
}
} }

View File

@@ -8,72 +8,90 @@
}, },
"ffmpeg": { "ffmpeg": {
"options": { "options": {
"native_h264_native_audio": { "audio_codec_copy": {
"outputOptions": [ "outputOptions": [
"-codec copy", "-codec:a copy"
"-map 0:v",
"-map 0:a"
] ]
}, },
"native_h264_no_audio": { "audio_codec_copy_aac": {
"outputOptions": [ "outputOptions": [
"-vcodec copy", "-codec:a copy",
"-an",
"-map 0:v"
]
},
"native_h264_native_aac": {
"outputOptions": [
"-codec copy",
"-map 0:v",
"-map 0:a",
"-bsf:a aac_adtstoasc" "-bsf:a aac_adtstoasc"
] ]
}, },
"native_h264_silence_aac": { "audio_codec_none": {
"input": "anullsrc=r=44100:cl=mono", "outputOptions": [
"-an"
]
},
"audio_codec_aac": {
"outputOptions": [
"-codec:a aac",
"-bsf:a aac_adtstoasc"
]
},
"audio_codec_mp3": {
"outputOptions": [
"-codec:a libmp3lame"
]
},
"audio_preset_copy": {
"outputOptions": [
"-map 0:a"
]
},
"audio_preset_encode": {
"outputOptions": [
"-b:a {bitrate}k",
"-map 0:a"
]
},
"audio_preset_silence": {
"input": "anullsrc=r={sampling}:cl={channels}",
"inputOptions": [ "inputOptions": [
"-f lavfi" "-f lavfi",
"-thread_queue_size 512"
], ],
"outputOptions": [ "outputOptions": [
"-vcodec copy", "-b:a {bitrate}k",
"-acodec aac",
"-b:a 0k",
"-map 0:v",
"-map 1:a", "-map 1:a",
"-shortest" "-shortest"
] ]
}, },
"native_h264_transcode_aac": { "video_codec_copy": {
"outputOptions": [ "outputOptions": [
"-vcodec copy", "-codec:v copy",
"-acodec aac",
"-b:a 64k",
"-map 0:v", "-map 0:v",
"-map 0:a" "-vsync 0",
"-copyts",
"-start_at_zero"
] ]
}, },
"native_h264_silence_mp3": { "video_codec_h264": {
"input": "anullsrc=r=44100:cl=mono",
"inputOptions": [
"-f lavfi"
],
"outputOptions": [ "outputOptions": [
"-vcodec copy", "-codec:v libx264",
"-acodec libmp3lame", "-preset:v {preset}",
"-b:a 0k", "-b:v {bitrate}k",
"-map 0:v", "-maxrate {bitrate}k",
"-map 1:a", "-bufsize {bitrate}k",
"-shortest" "-r {fps}",
"-g {gop}",
"-map 0:v"
] ]
}, },
"native_h264_transcode_mp3": { "video_codec_hevc": {
"outputOptions": [ "outputOptions": [
"-vcodec copy", "-codec:v libx265",
"-acodec libmp3lame", "-preset:v {preset}",
"-b:a 64k", "-b:v {bitrate}k",
"-maxrate {bitrate}k",
"-bufsize {bitrate}k",
"-r {fps}",
"-x265-params keyint={gop}",
"-map 0:v", "-map 0:v",
"-map 0:a" "-hls_segment_type fmp4",
"-hls_fmp4_init_filename live.stream_init.mp4",
"-hls_segment_filename /tmp/hls/live.stream_%09d.m4s"
] ]
}, },
"global": { "global": {
@@ -86,11 +104,25 @@
"video": { "video": {
"outputOptions": [ "outputOptions": [
"-map_metadata -1", "-map_metadata -1",
"-metadata application=datarhei/Restreamer", "-metadata application=datarhei/Restreamer"
"-metadata server=NGINX-RTMP", ]
},
"rtmp": {
"outputOptions": [
"-f flv" "-f flv"
] ]
}, },
"hls": {
"inputOptions": [
"-fflags nobuffer+genpts"
],
"outputOptions": [
"-f hls",
"-hls_time 2",
"-hls_list_size 10",
"-hls_flags delete_segments+temp_file+append_list"
]
},
"rtsp-tcp": { "rtsp-tcp": {
"inputOptions": [ "inputOptions": [
"-rtsp_transport tcp" "-rtsp_transport tcp"

View File

@@ -0,0 +1,38 @@
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index f502961..d2b4bf9 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -1651,8 +1651,7 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
{
AVBPrint buf, buf_script;
OutputStream *ost;
- AVFormatContext *oc;
- int64_t total_size;
+ int64_t total_size = 0;
AVCodecContext *enc;
int frame_number, vid, i;
double bitrate;
@@ -1680,13 +1679,6 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
t = (cur_time-timer_start) / 1000000.0;
-
- oc = output_files[0]->ctx;
-
- total_size = avio_size(oc->pb);
- if (total_size <= 0) // FIXME improve avio_size() so it works with non seekable output too
- total_size = avio_tell(oc->pb);
-
vid = 0;
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC);
@@ -1761,6 +1753,9 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
ost->st->time_base, AV_TIME_BASE_Q));
if (is_last_report)
nb_frames_drop += ost->last_dropped;
+
+ total_size += ost->data_size;
+ total_size += ost->enc_ctx->extradata_size;
}
secs = FFABS(pts) / AV_TIME_BASE;

View File

@@ -31,13 +31,13 @@ class Restreamer {
let nginx = config.nginx.streaming; let nginx = config.nginx.streaming;
let token = process.env.RS_TOKEN || config.auth.token; let token = process.env.RS_TOKEN || config.auth.token;
let hlspath = 'rtmp://' + nginx.ip + ':' + nginx.rtmp_port + nginx.rtmp_hls_path + 'live.stream'; let path = 'rtmp://' + nginx.ip + ':' + nginx.rtmp_port + nginx.rtmp_hls_path + 'live.stream';
if(token != '') { if(token != '') {
hlspath += '?token=' + token; path += '?token=' + token;
} }
return hlspath; return path;
} }
/** /**
@@ -79,8 +79,8 @@ class Restreamer {
command.output(Restreamer.getSnapshotPath()); command.output(Restreamer.getSnapshotPath());
Restreamer.addStreamOptions(command, 'global'); Restreamer.addStreamOptions(command, 'global', null);
Restreamer.addStreamOptions(command, 'snapshot'); Restreamer.addStreamOptions(command, 'snapshot', null);
command.on('start', (commandLine) => { command.on('start', (commandLine) => {
logger.debug('Spawned: ' + commandLine, 'snapshot'); logger.debug('Spawned: ' + commandLine, 'snapshot');
@@ -99,7 +99,7 @@ class Restreamer {
command.exec(); command.exec();
} }
static addStreamOptions(command, name) { static addStreamOptions(command, name, replace) {
if(!(name in config.ffmpeg.options)) { if(!(name in config.ffmpeg.options)) {
logger.debug('Unknown option: ' + name); logger.debug('Unknown option: ' + name);
return; return;
@@ -109,16 +109,49 @@ class Restreamer {
let options = config.ffmpeg.options[name]; let options = config.ffmpeg.options[name];
let replacer = function(options, replace) {
if(replace == null) {
return options;
}
let replaced_options = [];
if(typeof options != 'string') {
for(let i = 0; i < options.length; i++) {
let option = options[i];
for(let r in replace) {
if(option.indexOf('{'+r+'}') != -1) {
logger.debug('Replacing {'+r+'} with "'+replace[r]+'" in: '+option);
option = option.replace('{'+r+'}', replace[r]);
}
}
replaced_options.push(option);
}
}
else {
let option = options;
for(let r in replace) {
if(options.indexOf('{'+r+'}') != -1) {
logger.debug('Replacing {'+r+'} with "'+replace[r]+'" in: '+option);
option = option.replace('{'+r+'}', replace[r]);
}
}
replaced_options = option;
}
return replaced_options;
}
if('input' in options) { if('input' in options) {
command.input(options.input); command.input(replacer(options.input, replace));
} }
if('inputOptions' in options) { if('inputOptions' in options) {
command.inputOptions(options.inputOptions); command.inputOptions(replacer(options.inputOptions, replace));
} }
if('outputOptions' in options) { if('outputOptions' in options) {
command.outputOptions(options.outputOptions); command.outputOptions(replacer(options.outputOptions, replace));
} }
} }
@@ -287,71 +320,81 @@ class Restreamer {
return deferred.reject("no video stream detected"); return deferred.reject("no video stream detected");
} }
if(video.codec_name != 'h264') { let options = {
return deferred.reject("video stream must be h264, found " + video.codec_name); audio: [],
} video: []
};
let option = "native_h264_no_audio";
if(streamType == 'repeatToLocalNginx') { if(streamType == 'repeatToLocalNginx') {
if(video.codec_name != 'h264' && Restreamer.data.options.video.codec == 'copy') {
return deferred.reject("video stream must be h264, found " + video.codec_name);
}
if(Restreamer.data.options.video.codec == 'h264') {
options.video.push('video_codec_h264');
}
else {
if(video.codec_name != 'h264') {
return deferred.reject("video stream must be h264, found " + video.codec_name);
}
options.video.push('video_codec_copy');
}
if(audio !== null) { if(audio !== null) {
switch(audio.codec_name) { // consider all allowed audio codecs for FLV if(Restreamer.data.options.audio.codec == 'none') {
case 'mp3': options.audio.push('audio_codec_none');
case 'pcm_alaw': }
case 'pcm_mulaw': else if(Restreamer.data.options.audio.codec == 'aac') {
option = "native_h264_native_audio"; break; options.audio.push('audio_codec_aac');
case 'aac': options.audio.push('audio_preset_'+Restreamer.data.options.audio.preset);
option = "native_h264_native_aac"; break; }
default: else if(Restreamer.data.options.audio.codec == 'mp3') {
option = "native_h264_transcode_aac"; break; options.audio.push('audio_codec_mp3');
options.audio.push('audio_preset_'+Restreamer.data.options.audio.preset);
}
else {
switch(audio.codec_name) { // consider all allowed audio codecs for FLV
case 'mp3':
case 'pcm_alaw':
case 'pcm_mulaw':
options.audio.push('audio_codec_copy');
break;
case 'aac':
options.audio.push('audio_codec_copy_aac');
break;
default:
return deferred.reject("can't copy audio stream, found unsupported codec " + audio.codec_name);
}
options.audio.push('audio_preset_copy');
} }
} }
else { else {
option = "native_h264_silence_aac"; if(Restreamer.data.options.audio.codec == 'aac') {
} options.audio.push('audio_codec_aac');
options.audio.push('audio_preset_silence');
if(process.env.RS_AUDIO == "none") { }
option = "native_h264_no_audio"; else if(Restreamer.data.options.audio.codec == 'mp3') {
} options.audio.push('audio_codec_mp3');
else if(process.env.RS_AUDIO == "silence") { options.audio.push('audio_preset_silence');
option = "native_h264_silence_aac";
}
else if(process.env.RS_AUDIO == "aac") {
if(audio !== null) {
if(audio.codec_name != 'aac') {
option = "native_h264_transcode_aac";
}
else {
option = "native_h264_native_aac";
}
} }
else { else {
option = "native_h264_silence_aac"; options.audio.push('audio_codec_none');
}
}
else if(process.env.RS_AUDIO == "mp3") {
if(audio !== null) {
if(audio.codec_name != 'mp3') {
option = "native_h264_transcode_mp3";
}
else {
option = "native_h264_native_audio";
}
}
else {
option = "native_h264_silence_mp3";
} }
} }
} }
else { else {
options.video.push('video_codec_copy');
if(audio !== null) { if(audio !== null) {
option = "native_h264_native_audio"; options.audio.push('audio_codec_copy');
}
else {
options.audio.push('audio_codec_none');
} }
} }
logger.debug('Selected ffmpeg option: ' + option, streamType); return deferred.resolve(options);
return deferred.resolve(option);
}); });
return deferred.promise; return deferred.promise;
@@ -499,14 +542,13 @@ class Restreamer {
stdoutLines: 1 stdoutLines: 1
}); });
Restreamer.addStreamOptions(command, 'global'); Restreamer.addStreamOptions(command, 'global', null);
Restreamer.addStreamOptions(command, 'video'); Restreamer.addStreamOptions(command, 'video', null);
Restreamer.addStreamOptions(command, 'rtmp', null);
// GUI option // GUI option
if(streamType == 'repeatToLocalNginx') { if(Restreamer.data.options.rtspTcp && streamUrl.indexOf('rtsp') == 0) {
if(Restreamer.data.options.rtspTcp && Restreamer.data.addresses.srcAddress.indexOf('rtsp') == 0) { Restreamer.addStreamOptions(command, 'rtsp-tcp', null);
Restreamer.addStreamOptions(command, 'rtsp-tcp');
}
} }
// add outputs to the ffmpeg stream // add outputs to the ffmpeg stream
@@ -518,8 +560,9 @@ class Restreamer {
stdoutLines: 1 stdoutLines: 1
}); });
Restreamer.addStreamOptions(command, 'global'); Restreamer.addStreamOptions(command, 'global', null);
Restreamer.addStreamOptions(command, 'video'); Restreamer.addStreamOptions(command, 'video', null);
Restreamer.addStreamOptions(command, 'rtmp', null);
// add outputs to the ffmpeg stream // add outputs to the ffmpeg stream
command.output(streamUrl); command.output(streamUrl);
@@ -550,8 +593,27 @@ class Restreamer {
let nFrames = -1; let nFrames = -1;
// after adding outputs, define events on the new FFmpeg stream // after adding outputs, define events on the new FFmpeg stream
probePromise.then((option) => { probePromise.then((options) => {
Restreamer.addStreamOptions(command, option); let replace_video = {
preset: Restreamer.data.options.video.preset,
bitrate: Restreamer.data.options.video.bitrate,
fps: Restreamer.data.options.video.fps,
gop: (parseInt(Restreamer.data.options.video.fps) * 2) + ''
}
for(let o in options.video) {
Restreamer.addStreamOptions(command, options.video[o], replace_video);
}
let replace_audio = {
bitrate: Restreamer.data.options.audio.bitrate,
channels: Restreamer.data.options.audio.channels,
sampling: Restreamer.data.options.audio.sampling
}
for(let o in options.audio) {
Restreamer.addStreamOptions(command, options.audio[o], replace_audio);
}
command command
.on('start', (commandLine) => { .on('start', (commandLine) => {
@@ -631,7 +693,7 @@ class Restreamer {
command.exec(); command.exec();
}).catch((error) => { }).catch((error) => {
logger.debug('Failed to probe stream: ' + error.toString(), streamType); logger.debug('Failed to spawn ffmpeg: ' + error.toString(), streamType);
if(Restreamer.data.userActions[streamType] == 'stop') { if(Restreamer.data.userActions[streamType] == 'stop') {
Restreamer.updateState(streamType, 'disconnected'); Restreamer.updateState(streamType, 'disconnected');
@@ -794,7 +856,20 @@ Restreamer.data = {
} }
}, },
'options': { 'options': {
'rtspTcp': false 'rtspTcp': false,
'video': {
'codec': 'copy',
'preset': 'ultrafast',
'bitrate': '4096',
'fps': '25'
},
'audio': {
'codec': 'copy',
'preset': 'silence',
'bitrate': '64',
'channels': 'mono',
'sampling': '44100'
}
}, },
'states': { 'states': {
'repeatToLocalNginx': { 'repeatToLocalNginx': {

View File

@@ -46,6 +46,32 @@ class RestreamerData {
throw new Error(JSON.stringify(validateResult.errors)); throw new Error(JSON.stringify(validateResult.errors));
} else { } else {
logger.debug('"v1.db" is valid'); logger.debug('"v1.db" is valid');
// Fill up optional fields if not present
if(!('video' in dbdata.options)) {
dbdata.options.video = {
'codec': 'copy',
'preset': 'ultrafast',
'bitrate': '4096',
'fps': '25'
}
}
if(!('audio' in dbdata.options)) {
dbdata.options.audio = {
'codec': 'copy',
'preset': 'silence',
'bitrate': '64',
'channels': 'mono',
'sampling': '44100'
}
}
if (!fs.existsSync(dbPath)) {
fs.mkdirSync(dbPath);
}
fs.writeFileSync(path.join(dbPath, dbFile), JSON.stringify(dbdata));
deferred.resolve(); deferred.resolve();
} }
}) })
@@ -56,7 +82,20 @@ class RestreamerData {
'optionalOutputAddress': '' 'optionalOutputAddress': ''
}, },
'options': { 'options': {
'rtspTcp': true 'rtspTcp': true,
'video': {
'codec': 'copy',
'preset': 'ultrafast',
'bitrate': '4096',
'fps': '25'
},
'audio': {
'codec': 'copy',
'preset': 'silence',
'bitrate': '64',
'channels': 'mono',
'sampling': '44100'
}
}, },
'states': { 'states': {
'repeatToLocalNginx': { 'repeatToLocalNginx': {

View File

@@ -44,7 +44,19 @@ window.angular.module('Main').controller('mainController',
$scope.reStreamerData = { $scope.reStreamerData = {
'options': { 'options': {
'rtspTcp': false 'rtspTcp': false,
'video': {
'codec': 'copy',
'preset': 'ultrafast',
'bitrate': 4096
},
'audio': {
'codec': 'copy',
'preset': 'silence',
'bitrate': 64,
'channels': 'mono',
'sampling': 41000
}
}, },
'states': { 'states': {
'repeatToLocalNginx': { 'repeatToLocalNginx': {

View File

@@ -64,6 +64,7 @@ window.angular.module('StreamingInterface').controller('streamingStatusControlle
* @returns {number} current bit rate * @returns {number} current bit rate
*/ */
$scope.kbps = () => { $scope.kbps = () => {
return $scope.data.progresses ? $scope.data.progresses[$scope.name].currentKbps : 0; var bitrate = $scope.data.progresses ? $scope.data.progresses[$scope.name].currentKbps : 0;
return bitrate.toFixed(1);
}; };
}]); }]);

View File

@@ -17,6 +17,116 @@
<streaming-status name="repeatToLocalNginx" data="reStreamerData"></streaming-status> <streaming-status name="repeatToLocalNginx" data="reStreamerData"></streaming-status>
<div class="form-inline form-group"> <div class="form-inline form-group">
<div style="margin-bottom:1em" ng-if="showStartButton('repeatToLocalNginx')">
<h4>Stream options:</h4>
<table style="width:100%">
<thread>
<tr>
<th></th>
<th class="text-center" style="width:150px"><strong>Video</strong></th>
<th class="text-center" style="width:150px"><strong>Audio</strong></th>
</tr>
</thread>
<tbody>
<tr style="height:24px">
<td>Codec</td>
<td class="text-right">
<select class="input" ng-model="reStreamerData.options.video.codec">
<option value="copy" selected>copy</option>
<option value="h264">H264 (x264)</option>
</select>
</td>
<td class="text-right">
<select class="input" ng-model="reStreamerData.options.audio.codec">
<option value="copy" selected>copy</option>
<option value="none">none</option>
<option value="aac">AAC</option>
<option value="mp3">MP3</option>
</select>
</td>
</tr><tr style="height:24px" ng-if="reStreamerData.options.video.codec != 'copy' || (reStreamerData.options.audio.codec != 'copy' && reStreamerData.options.audio.codec != 'none')">
<td>Preset</td>
<td class="text-right">
<select class="input" ng-if="reStreamerData.options.video.codec != 'copy'" ng-model="reStreamerData.options.video.preset">
<option value="ultrafast" selected>ultrafast</option>
<option value="superfast">superfast</option>
<option value="veryfast">veryfast</option>
<option value="faster">faster</option>
<option value="fast">fast</option>
<option value="medium">medium</option>
<option value="slow">slow</option>
<option value="slower">slower</option>
<option value="veryslow">veryslow</option>
<option value="placebo">placebo</option>
</select>
</td>
<td class="text-right">
<select class="input" ng-if="reStreamerData.options.audio.codec != 'copy' && reStreamerData.options.audio.codec != 'none'" ng-model="reStreamerData.options.audio.preset">
<option value="encode" selected>encode</option>
<option value="silence">silence</option>
</select>
</td>
</tr><tr style="height:24px" ng-if="reStreamerData.options.video.codec != 'copy' || (reStreamerData.options.audio.codec != 'copy' && reStreamerData.options.audio.codec != 'none')">
<td>Bitrate</td>
<td class="text-right">
<select class="input" ng-if="reStreamerData.options.video.codec != 'copy'" ng-model="reStreamerData.options.video.bitrate">
<option value="32768">32 Mbit/s</option>
<option value="24576">24 Mbit/s</option>
<option value="20480">20 Mbit/s</option>
<option value="16384">16 Mbit/s</option>
<option value="12288">12 Mbit/s</option>
<option value="8192">8 Mbit/s</option>
<option value="4096" selected>4 Mbit/s</option>
<option value="2048">2 Mbit/s</option>
<option value="1024">1 Mbit/s</option>
<option value="512">512 Kbit/s</option>
<option value="256">256 Kbit/s</option>
</select>
</td>
<td class="text-right">
<select class="input" ng-if="reStreamerData.options.audio.codec != 'copy' && reStreamerData.options.audio.codec != 'none'" ng-model="reStreamerData.options.audio.bitrate">
<option value="256">256 Kbit/s</option>
<option value="128">128 Kbit/s</option>
<option value="64" selected>64 Kbit/s</option>
<option value="32">32 Kbit/s</option>
<option value="16">16 Kbit/s</option>
<option value="8">8 Kbit/s</option>
</select>
</td>
</tr><tr style="height:24px" ng-if="reStreamerData.options.video.codec != 'copy'">
<td>FPS</td>
<td class="text-right">
<select class="input" ng-if="reStreamerData.options.video.codec != 'copy'" ng-model="reStreamerData.options.video.fps">
<option value="60">60</option>
<option value="30">30</option>
<option value="25" selected>25</option>
</select>
</td>
<td></td>
</tr><tr style="height:24px" ng-if="reStreamerData.options.audio.codec != 'copy' && reStreamerData.options.audio.codec != 'none'">
<td>Channels</td>
<td></td>
<td class="text-right">
<select class="input" ng-if="reStreamerData.options.audio.codec != 'copy' && reStreamerData.options.audio.codec != 'none'" ng-model="reStreamerData.options.audio.channels">
<option value="mono" selected>mono</option>
<option value="stereo">stereo</option>
</select>
</td>
</tr><tr style="height:24px" ng-if="reStreamerData.options.audio.codec != 'copy' && reStreamerData.options.audio.codec != 'none'">
<td>Sampling</td>
<td></td>
<td class="text-right">
<select class="input" ng-if="reStreamerData.options.audio.codec != 'copy' && reStreamerData.options.audio.codec != 'none'" ng-model="reStreamerData.options.audio.sampling">
<option value="44100" selected>44100 Hz</option>
<option value="22050">22050 Hz</option>
<option value="11025">11025 Hz</option>
<option value="8000">8000 Hz</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
<div ng-if="reStreamerData.addresses.srcAddress.indexOf('rtsp') === 0 && showStartButton('repeatToLocalNginx')" <div ng-if="reStreamerData.addresses.srcAddress.indexOf('rtsp') === 0 && showStartButton('repeatToLocalNginx')"
class="checkbox pull-left"> class="checkbox pull-left">
<label> <label>
@@ -44,6 +154,7 @@
{{'button_stop' | translate}} {{'button_stop' | translate}}
</button> </button>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<!-- todo: this hr to css border-bottom or something--> <!-- todo: this hr to css border-bottom or something-->