Add RTMP Server with Node-Media-Server

- Instead of including the npmjs module; the rtmp portion has been ripped from the main package.
- to enable the RTMP server with default settings just add `"rtmpServer":true` to your conf.json
- `rtmpServer` object in conf.json correlates to `rtmp` object in NodeMediaServer configuration.
- WARNING : currently no authentication for incoming RTMP streams.
merge-requests/53/head
Moe 2019-02-13 22:29:19 -08:00
parent bdbf3458b6
commit 3503ac1426
12 changed files with 3368 additions and 10 deletions

View File

@ -770,26 +770,39 @@ module.exports = function(s,config,onFinish){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
}
if(!e.details.detector_fps||e.details.detector_fps===''){e.details.detector_fps=2}
if(!e.details.detector_fps||e.details.detector_fps===''){x.detector_fps = 2}else{x.detector_fps = parseInt(e.details.detector_fps)}
if(e.details.detector_scale_x&&e.details.detector_scale_x!==''&&e.details.detector_scale_y&&e.details.detector_scale_y!==''){x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y}else{x.dratio=' -s 320x240'}
if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;}
x.detector_vf = ['fps='+e.details.detector_fps]
x.pipe += ' -r ' + x.detector_fps + x.dratio + x.cust_detect
x.detector_vf = []
if(e.cudaEnabled){
x.detector_vf.push('hwdownload,format=nv12')
}
x.detector_vf = '-vf "'+x.detector_vf.join(',')+'"'
if(e.details.detector_pam==='1'){
if(x.detector_vf.length > 0)x.pipe += ' -vf "'+x.detector_vf.join(',')+'"'
// REMOVE AFTER TESTING >
e.details.detector_h264 = '1'
// REMOVE AFTER TESTING />
var h264Output = ' -q:v 1 -an -c:v libx264 -f hls -tune zerolatency -g 1 -hls_time 2 -hls_list_size 3 -start_number 0 -live_start_index 3 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'detectorStreamX.m3u8"'
if(e.details.detector_pam === '1'){
if(e.cudaEnabled){
x.pipe += ' -vf "hwdownload,format=nv12"'
}
x.pipe+=' -an -c:v pam -pix_fmt gray -f image2pipe -r '+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:3'
x.pipe += ' -an -c:v pam -pix_fmt gray -f image2pipe -r '+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:3'
if(e.details.detector_use_detect_object === '1'){
//for object detection
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
x.pipe += ' -an -f singlejpeg '+x.detector_vf+x.cust_detect+x.dratio+' pipe:4';
if(e.details.detector_h264 === '1'){
x.pipe += h264Output
}else{
x.pipe += ' -an -f singlejpeg pipe:4'
}
}
}else{
x.pipe+=' -an -f image2pipe '+x.detector_vf+x.cust_detect+x.dratio+' pipe:3';
if(e.details.detector_h264 === '1'){
x.pipe += h264Output
}else{
x.pipe += ' -an -f singlejpeg pipe:3'
}
}
}
//Traditional Recording Buffer
@ -884,9 +897,10 @@ module.exports = function(s,config,onFinish){
case'mjpeg':
x.ffmpegCommandString += ' -reconnect 1 -f mjpeg'+x.cust_input+x.hwaccel+' -i "'+e.url+'"';
break;
// case'rtmp':
// x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i -';
// break;
case'rtmp':
if(!e.details.rtmp_key)e.details.rtmp_key = ''
x.ffmpegCommandString += x.cust_input+x.hwaccel+` -i "rtmp://127.0.0.1:1935/${e.ke + '_' + e.mid + '_' + e.details.rtmp_key}"`;
break;
case'h264':case'hls':case'mp4':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i "'+e.url+'"';
break;

23
libs/rtmpserver.js Normal file
View File

@ -0,0 +1,23 @@
module.exports = function(s,config,lang){
if(config.rtmpServer){
var defaultRtmpServerConfig = {
port: 1935,
chunk_size: 60000,
gop_cache: true,
ping: 60,
ping_timeout: 30
}
var runningRtmpServerConfig
if(config.rtmpServer instanceof Object === 'false'){
runningRtmpServerConfig = defaultRtmpServerConfig
}else{
runningRtmpServerConfig = Object.assign(defaultRtmpServerConfig,config.rtmpServer)
}
s.systemLog(`RTMP Server Running on port ${runningRtmpServerConfig.port}...`)
var NodeRtmpServer = require('./rtmpserver/node_rtmp_server')
var nmcs = new NodeRtmpServer({
rtmp: runningRtmpServerConfig
})
nmcs.run()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,501 @@
//
// Created by Mingliang Chen on 17/12/21.
// illuspas[a]gmail.com
// Copyright (c) 2018 Nodemedia. All rights reserved.
//
const Bitop = require('./node_core_bitop');
const AAC_SAMPLE_RATE = [
96000, 88200, 64000, 48000,
44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000,
7350, 0, 0, 0
];
const AAC_CHANNELS = [
0, 1, 2, 3, 4, 5, 6, 8
];
const AUDIO_CODEC_NAME = [
'',
'ADPCM',
'MP3',
'LinearLE',
'Nellymoser16',
'Nellymoser8',
'Nellymoser',
'G711A',
'G711U',
'',
'AAC',
'Speex',
'',
'',
'MP3-8K',
'DeviceSpecific',
'Uncompressed'
];
const AUDIO_SOUND_RATE = [
5512, 11025, 22050, 44100
];
const VIDEO_CODEC_NAME = [
'',
'Jpeg',
'Sorenson-H263',
'ScreenVideo',
'On2-VP6',
'On2-VP6-Alpha',
'ScreenVideo2',
'H264',
'',
'',
'',
'',
'H265'
];
function getObjectType(bitop) {
let audioObjectType = bitop.read(5);
if (audioObjectType === 31) {
audioObjectType = bitop.read(6) + 32;
}
return audioObjectType;
}
function getSampleRate(bitop, info) {
info.sampling_index = bitop.read(4);
return info.sampling_index == 0x0f ? bitop.read(24) : AAC_SAMPLE_RATE[info.sampling_index];
}
function readAACSpecificConfig(aacSequenceHeader) {
let info = {};
let bitop = new Bitop(aacSequenceHeader);
bitop.read(16);
info.object_type = getObjectType(bitop);
info.sample_rate = getSampleRate(bitop, info);
info.chan_config = bitop.read(4);
if (info.chan_config < AAC_CHANNELS.length) {
info.channels = AAC_CHANNELS[info.chan_config];
}
info.sbr = -1;
info.ps = -1;
if (info.object_type == 5 || info.object_type == 29) {
if (info.object_type == 29) {
info.ps = 1;
}
info.ext_object_type = 5;
info.sbr = 1;
info.sample_rate = getSampleRate(bitop, info);
info.object_type = getObjectType(bitop);
}
return info;
}
function getAACProfileName(info) {
switch (info.object_type) {
case 1:
return 'Main';
case 2:
if (info.ps > 0) {
return 'HEv2';
}
if (info.sbr > 0) {
return 'HE';
}
return 'LC';
case 3:
return 'SSR';
case 4:
return 'LTP';
case 5:
return 'SBR';
default:
return '';
}
}
function readH264SpecificConfig(avcSequenceHeader) {
let info = {};
let profile_idc, width, height, crop_left, crop_right,
crop_top, crop_bottom, frame_mbs_only, n, cf_idc,
num_ref_frames;
let bitop = new Bitop(avcSequenceHeader);
bitop.read(48);
info.width = 0;
info.height = 0;
do {
info.profile = bitop.read(8);
info.compat = bitop.read(8);
info.level = bitop.read(8);
info.nalu = (bitop.read(8) & 0x03) + 1;
info.nb_sps = bitop.read(8) & 0x1F;
if (info.nb_sps == 0) {
break;
}
/* nal size */
bitop.read(16);
/* nal type */
if (bitop.read(8) != 0x67) {
break;
}
/* SPS */
profile_idc = bitop.read(8);
/* flags */
bitop.read(8);
/* level idc */
bitop.read(8);
/* SPS id */
bitop.read_golomb();
if (profile_idc == 100 || profile_idc == 110 ||
profile_idc == 122 || profile_idc == 244 || profile_idc == 44 ||
profile_idc == 83 || profile_idc == 86 || profile_idc == 118) {
/* chroma format idc */
cf_idc = bitop.read_golomb();
if (cf_idc == 3) {
/* separate color plane */
bitop.read(1);
}
/* bit depth luma - 8 */
bitop.read_golomb();
/* bit depth chroma - 8 */
bitop.read_golomb();
/* qpprime y zero transform bypass */
bitop.read(1);
/* seq scaling matrix present */
if (bitop.read(1)) {
for (n = 0; n < (cf_idc != 3 ? 8 : 12); n++) {
/* seq scaling list present */
if (bitop.read(1)) {
/* TODO: scaling_list()
if (n < 6) {
} else {
}
*/
}
}
}
}
/* log2 max frame num */
bitop.read_golomb();
/* pic order cnt type */
switch (bitop.read_golomb()) {
case 0:
/* max pic order cnt */
bitop.read_golomb();
break;
case 1:
/* delta pic order alwys zero */
bitop.read(1);
/* offset for non-ref pic */
bitop.read_golomb();
/* offset for top to bottom field */
bitop.read_golomb();
/* num ref frames in pic order */
num_ref_frames = bitop.read_golomb();
for (n = 0; n < num_ref_frames; n++) {
/* offset for ref frame */
bitop.read_golomb();
}
}
/* num ref frames */
info.avc_ref_frames = bitop.read_golomb();
/* gaps in frame num allowed */
bitop.read(1);
/* pic width in mbs - 1 */
width = bitop.read_golomb();
/* pic height in map units - 1 */
height = bitop.read_golomb();
/* frame mbs only flag */
frame_mbs_only = bitop.read(1);
if (!frame_mbs_only) {
/* mbs adaprive frame field */
bitop.read(1);
}
/* direct 8x8 inference flag */
bitop.read(1);
/* frame cropping */
if (bitop.read(1)) {
crop_left = bitop.read_golomb();
crop_right = bitop.read_golomb();
crop_top = bitop.read_golomb();
crop_bottom = bitop.read_golomb();
} else {
crop_left = 0;
crop_right = 0;
crop_top = 0;
crop_bottom = 0;
}
info.level = info.level / 10.0;
info.width = (width + 1) * 16 - (crop_left + crop_right) * 2;
info.height = (2 - frame_mbs_only) * (height + 1) * 16 - (crop_top + crop_bottom) * 2;
} while (0);
return info;
}
function HEVCParsePtl(bitop, hevc, max_sub_layers_minus1) {
let general_ptl = {};
general_ptl.profile_space = bitop.read(2);
general_ptl.tier_flag = bitop.read(1);
general_ptl.profile_idc = bitop.read(5);
general_ptl.profile_compatibility_flags = bitop.read(32);
general_ptl.general_progressive_source_flag = bitop.read(1);
general_ptl.general_interlaced_source_flag = bitop.read(1);
general_ptl.general_non_packed_constraint_flag = bitop.read(1);
general_ptl.general_frame_only_constraint_flag = bitop.read(1);
bitop.read(32);
bitop.read(12);
general_ptl.level_idc = bitop.read(8);
general_ptl.sub_layer_profile_present_flag = [];
general_ptl.sub_layer_level_present_flag = [];
for (let i = 0; i < max_sub_layers_minus1; i++) {
general_ptl.sub_layer_profile_present_flag[i] = bitop.read(1);
general_ptl.sub_layer_level_present_flag[i] = bitop.read(1);
}
if (max_sub_layers_minus1 > 0) {
for (let i = max_sub_layers_minus1; i < 8; i++) {
bitop.read(2)
}
}
general_ptl.sub_layer_profile_space = [];
general_ptl.sub_layer_tier_flag = [];
general_ptl.sub_layer_profile_idc = [];
general_ptl.sub_layer_profile_compatibility_flag = [];
general_ptl.sub_layer_progressive_source_flag = [];
general_ptl.sub_layer_interlaced_source_flag = [];
general_ptl.sub_layer_non_packed_constraint_flag = [];
general_ptl.sub_layer_frame_only_constraint_flag = [];
general_ptl.sub_layer_level_idc = [];
for (let i = 0; i < max_sub_layers_minus1; i++) {
if (general_ptl.sub_layer_profile_present_flag[i]) {
general_ptl.sub_layer_profile_space[i] = bitop.read(2);
general_ptl.sub_layer_tier_flag[i] = bitop.read(1);
general_ptl.sub_layer_profile_idc[i] = bitop.read(5);
general_ptl.sub_layer_profile_compatibility_flag[i] = bitop.read(32);
general_ptl.sub_layer_progressive_source_flag[i] = bitop.read(1);
general_ptl.sub_layer_interlaced_source_flag[i] = bitop.read(1);
general_ptl.sub_layer_non_packed_constraint_flag[i] = bitop.read(1);
general_ptl.sub_layer_frame_only_constraint_flag[i] = bitop.read(1);
bitop.read(32);
bitop.read(12);
}
if (general_ptl.sub_layer_level_present_flag[i]) {
general_ptl.sub_layer_level_idc[i] = bitop.read(8);
}
else {
general_ptl.sub_layer_level_idc[i] = 1;
}
}
return general_ptl;
}
function HEVCParseSPS(SPS, hevc) {
let psps = {};
let NumBytesInNALunit = SPS.length;
let NumBytesInRBSP = 0;
let rbsp_array = [];
let bitop = new Bitop(SPS);
bitop.read(1);//forbidden_zero_bit
bitop.read(6);//nal_unit_type
bitop.read(6);//nuh_reserved_zero_6bits
bitop.read(3);//nuh_temporal_id_plus1
for (let i = 2; i < NumBytesInNALunit; i++) {
if (i + 2 < NumBytesInNALunit && bitop.look(24) == 0x000003) {
rbsp_array.push(bitop.read(8));
rbsp_array.push(bitop.read(8));
i += 2;
let emulation_prevention_three_byte = bitop.read(8); /* equal to 0x03 */
} else {
rbsp_array.push(bitop.read(8));
}
}
let rbsp = Buffer.from(rbsp_array);
let rbspBitop = new Bitop(rbsp);
psps.sps_video_parameter_set_id = rbspBitop.read(4);
psps.sps_max_sub_layers_minus1 = rbspBitop.read(3);
psps.sps_temporal_id_nesting_flag = rbspBitop.read(1);
psps.profile_tier_level = HEVCParsePtl(rbspBitop, hevc, psps.sps_max_sub_layers_minus1);
psps.sps_seq_parameter_set_id = rbspBitop.read_golomb();
psps.chroma_format_idc = rbspBitop.read_golomb();
if (psps.chroma_format_idc == 3) {
psps.separate_colour_plane_flag = rbspBitop.read(1);
} else {
psps.separate_colour_plane_flag = 0;
}
psps.pic_width_in_luma_samples = rbspBitop.read_golomb();
psps.pic_height_in_luma_samples = rbspBitop.read_golomb();
psps.conformance_window_flag = rbspBitop.read(1);
if (psps.conformance_window_flag) {
let vert_mult = 1 + (psps.chroma_format_idc < 2);
let horiz_mult = 1 + (psps.chroma_format_idc < 3);
psps.conf_win_left_offset = rbspBitop.read_golomb() * horiz_mult;
psps.conf_win_right_offset = rbspBitop.read_golomb() * horiz_mult;
psps.conf_win_top_offset = rbspBitop.read_golomb() * vert_mult;
psps.conf_win_bottom_offset = rbspBitop.read_golomb() * vert_mult;
}
// Logger.debug(psps);
return psps;
}
function readHEVCSpecificConfig(hevcSequenceHeader) {
let info = {};
info.width = 0;
info.height = 0;
info.profile = 0;
info.level = 0;
// let bitop = new Bitop(hevcSequenceHeader);
// bitop.read(48);
hevcSequenceHeader = hevcSequenceHeader.slice(5);
do {
let hevc = {};
if (hevcSequenceHeader.length < 23) {
break;
}
hevc.configurationVersion = hevcSequenceHeader[0];
if (hevc.configurationVersion != 1) {
break;
}
hevc.general_profile_space = (hevcSequenceHeader[1] >> 6) & 0x03;
hevc.general_tier_flag = (hevcSequenceHeader[1] >> 5) & 0x01;
hevc.general_profile_idc = hevcSequenceHeader[1] & 0x1F;
hevc.general_profile_compatibility_flags = (hevcSequenceHeader[2] << 24) | (hevcSequenceHeader[3] << 16) | (hevcSequenceHeader[4] << 8) | hevcSequenceHeader[5];
hevc.general_constraint_indicator_flags = ((hevcSequenceHeader[6] << 24) | (hevcSequenceHeader[7] << 16) | (hevcSequenceHeader[8] << 8) | hevcSequenceHeader[9]);
hevc.general_constraint_indicator_flags = (hevc.general_constraint_indicator_flags << 16) | (hevcSequenceHeader[10] << 8) | hevcSequenceHeader[11];
hevc.general_level_idc = hevcSequenceHeader[12];
hevc.min_spatial_segmentation_idc = ((hevcSequenceHeader[13] & 0x0F) << 8) | hevcSequenceHeader[14];
hevc.parallelismType = hevcSequenceHeader[15] & 0x03;
hevc.chromaFormat = hevcSequenceHeader[16] & 0x03;
hevc.bitDepthLumaMinus8 = hevcSequenceHeader[17] & 0x07;
hevc.bitDepthChromaMinus8 = hevcSequenceHeader[18] & 0x07;
hevc.avgFrameRate = (hevcSequenceHeader[19] << 8) | hevcSequenceHeader[20];
hevc.constantFrameRate = (hevcSequenceHeader[21] >> 6) & 0x03;
hevc.numTemporalLayers = (hevcSequenceHeader[21] >> 3) & 0x07;
hevc.temporalIdNested = (hevcSequenceHeader[21] >> 2) & 0x01;
hevc.lengthSizeMinusOne = hevcSequenceHeader[21] & 0x03;
let numOfArrays = hevcSequenceHeader[22];
let p = hevcSequenceHeader.slice(23);
for (let i = 0; i < numOfArrays; i++) {
if (p.length < 3) {
brak;
}
let nalutype = p[0];
let n = (p[1]) << 8 | p[2];
// Logger.debug(nalutype, n);
p = p.slice(3);
for (let j = 0; j < n; j++) {
if (p.length < 2) {
break;
}
k = (p[0] << 8) | p[1];
// Logger.debug('k', k);
if (p.length < 2 + k) {
break;
}
p = p.slice(2);
if (nalutype == 33) {
//SPS
let sps = Buffer.alloc(k);
p.copy(sps, 0, 0, k);
// Logger.debug(sps, sps.length);
hevc.psps = HEVCParseSPS(sps, hevc);
info.profile = hevc.general_profile_idc;
info.level = hevc.general_level_idc / 30.0;
info.width = hevc.psps.pic_width_in_luma_samples - (hevc.psps.conf_win_left_offset + hevc.psps.conf_win_right_offset);
info.height = hevc.psps.pic_height_in_luma_samples - (hevc.psps.conf_win_top_offset + hevc.psps.conf_win_bottom_offset);
}
p = p.slice(k);
}
}
} while (0);
return info;
}
function readAVCSpecificConfig(avcSequenceHeader) {
let codec_id = avcSequenceHeader[0] & 0x0f;
if (codec_id == 7) {
return readH264SpecificConfig(avcSequenceHeader);
} else if (codec_id == 12) {
return readHEVCSpecificConfig(avcSequenceHeader);
}
}
function getAVCProfileName(info) {
switch (info.profile) {
case 1:
return 'Main';
case 2:
return 'Main 10';
case 3:
return 'Main Still Picture';
case 66:
return 'Baseline';
case 77:
return 'Main';
case 100:
return 'High';
default:
return '';
}
}
module.exports = {
AUDIO_SOUND_RATE,
AUDIO_CODEC_NAME,
VIDEO_CODEC_NAME,
readAACSpecificConfig,
getAACProfileName,
readAVCSpecificConfig,
getAVCProfileName,
};

View File

@ -0,0 +1,54 @@
class Bitop {
constructor(buffer) {
this.buffer = buffer;
this.buflen = buffer.length;
this.bufpos = 0;
this.bufoff = 0;
this.iserro = false;
}
read(n) {
let v = 0;
let d = 0;
while (n) {
if (n < 0 || this.bufpos >= this.buflen) {
this.iserro = true;
return 0;
}
this.iserro = false;
d = this.bufoff + n > 8 ? 8 - this.bufoff : n;
v <<= d;
v += (this.buffer[this.bufpos] >> (8 - this.bufoff - d)) & (0xff >> (8 - d))
this.bufoff += d;
n -= d;
if (this.bufoff == 8) {
this.bufpos++;
this.bufoff = 0;
}
}
return v;
}
look(n) {
let p = this.bufpos;
let o = this.bufoff;
let v = this.read(n);
this.bufpos = p;
this.bufoff = o;
return v;
}
read_golomb() {
let n;
for (n = 0; this.read(1) == 0 && !this.iserro; n++);
return (1 << n) + this.read(n) - 1;
}
}
module.exports = Bitop;

View File

@ -0,0 +1,17 @@
//
// Created by Mingliang Chen on 18/3/2.
// illuspas[a]gmail.com
// Copyright (c) 2018 Nodemedia. All rights reserved.
//
const EventEmitter = require('events');
let sessions = new Map();
let publishers = new Map();
let idlePlayers = new Set();
let nodeEvent = new EventEmitter();
let stat = {
inbytes: 0,
outbytes: 0,
accepted: 0
};
module.exports = { sessions, publishers, idlePlayers, nodeEvent, stat };

View File

@ -0,0 +1,53 @@
const chalk = require('chalk');
LOG_TYPES = {
NONE: 0,
ERROR: 1,
NORMAL: 2,
DEBUG: 3,
FFDEBUG: 4
};
let logType = LOG_TYPES.NORMAL;
const setLogType = (type) => {
if (typeof type !== 'number') return;
logType = type;
};
const logTime = () => {
let nowDate = new Date();
return nowDate.toLocaleDateString() + ' ' + nowDate.toLocaleTimeString([], { hour12: false });
};
const log = (...args) => {
if (logType < LOG_TYPES.NORMAL) return;
console.log(logTime(), process.pid, chalk.bold.green('[INFO]'), ...args);
};
const error = (...args) => {
if (logType < LOG_TYPES.ERROR) return;
console.log(logTime(), process.pid, chalk.bold.red('[ERROR]'), ...args);
};
const debug = (...args) => {
if (logType < LOG_TYPES.DEBUG) return;
console.log(logTime(), process.pid, chalk.bold.blue('[DEBUG]'), ...args);
};
const ffdebug = (...args) => {
if (logType < LOG_TYPES.FFDEBUG) return;
console.log(logTime(), process.pid, chalk.bold.blue('[FFDEBUG]'), ...args);
};
module.exports = {
LOG_TYPES,
setLogType,
log, error, debug, ffdebug
}

View File

@ -0,0 +1,95 @@
//
// Created by Mingliang Chen on 17/8/23.
// illuspas[a]gmail.com
// Copyright (c) 2018 Nodemedia. All rights reserved.
//
const Crypto = require('crypto');
const EventEmitter = require('events');
const { spawn } = require('child_process');
const readline = require('readline');
const context = require('./node_core_ctx');
function generateNewSessionID() {
let sessionID = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWKYZ0123456789';
const numPossible = possible.length;
do {
for (let i = 0; i < 8; i++) {
sessionID += possible.charAt((Math.random() * numPossible) | 0);
}
} while (context.sessions.has(sessionID))
return sessionID;
}
function genRandomName() {
let name = '';
const possible = 'abcdefghijklmnopqrstuvwxyz0123456789';
const numPossible = possible.length;
for (let i = 0; i < 4; i++) {
name += possible.charAt((Math.random() * numPossible) | 0);
}
return name;
}
function verifyAuth(signStr, streamId, secretKey) {
if (signStr === undefined) {
return false;
}
let now = Date.now() / 1000 | 0;
let exp = parseInt(signStr.split('-')[0]);
let shv = signStr.split('-')[1];
let str = streamId + '-' + exp + '-' + secretKey;
if (exp < now) {
return false;
}
let md5 = Crypto.createHash('md5');
let ohv = md5.update(str).digest('hex');
return shv === ohv;
}
function getFFmpegVersion(ffpath) {
return new Promise((resolve, reject) => {
let ffmpeg_exec = spawn(ffpath, ['-version']);
let version = '';
ffmpeg_exec.on('error', (e) => {
reject(e);
});
ffmpeg_exec.stdout.on('data', (data) => {
try {
version = data.toString().split(/(?:\r\n|\r|\n)/g)[0].split('\ ')[2];
} catch (e) {
}
});
ffmpeg_exec.on('close', (code) => {
resolve(version);
});
});
}
function getFFmpegUrl() {
let url = '';
switch (process.platform) {
case 'darwin':
url = 'https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-latest-macos64-static.zip';
break;
case 'win32':
url = 'https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.zip | https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-latest-win32-static.zip';
break;
case 'linux':
url = 'https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz | https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-32bit-static.tar.xz';
break;
default:
url = 'http://ffmpeg.org/download.html';
break;
}
return url;
}
module.exports = {
generateNewSessionID,
verifyAuth,
genRandomName,
getFFmpegVersion,
getFFmpegUrl
}

View File

@ -0,0 +1,113 @@
//
// Created by Mingliang Chen on 17/8/1.
// illuspas[a]gmail.com
// Copyright (c) 2018 Nodemedia. All rights reserved.
//
// const Logger = require('./node_core_logger');
const Crypto = require('crypto');
const MESSAGE_FORMAT_0 = 0;
const MESSAGE_FORMAT_1 = 1;
const MESSAGE_FORMAT_2 = 2;
const RTMP_SIG_SIZE = 1536;
const SHA256DL = 32;
const RandomCrud = Buffer.from([
0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,
0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,
0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,
0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae
])
const GenuineFMSConst = 'Genuine Adobe Flash Media Server 001';
const GenuineFMSConstCrud = Buffer.concat([Buffer.from(GenuineFMSConst, 'utf8'), RandomCrud]);
const GenuineFPConst = 'Genuine Adobe Flash Player 001';
const GenuineFPConstCrud = Buffer.concat([Buffer.from(GenuineFPConst, 'utf8'), RandomCrud]);
function calcHmac(data, key) {
let hmac = Crypto.createHmac('sha256', key);
hmac.update(data);
return hmac.digest();
}
function GetClientGenuineConstDigestOffset(buf) {
let offset = buf[0] + buf[1] + buf[2] + buf[3];
offset = (offset % 728) + 12;
return offset;
}
function GetServerGenuineConstDigestOffset(buf) {
let offset = buf[0] + buf[1] + buf[2] + buf[3];
offset = (offset % 728) + 776;
return offset;
}
function detectClientMessageFormat(clientsig) {
let computedSignature, msg, providedSignature, sdl;
sdl = GetServerGenuineConstDigestOffset(clientsig.slice(772, 776));
msg = Buffer.concat([clientsig.slice(0, sdl), clientsig.slice(sdl + SHA256DL)], 1504);
computedSignature = calcHmac(msg, GenuineFPConst);
providedSignature = clientsig.slice(sdl, sdl + SHA256DL);
if (computedSignature.equals(providedSignature)) {
return MESSAGE_FORMAT_2;
}
sdl = GetClientGenuineConstDigestOffset(clientsig.slice(8, 12));
msg = Buffer.concat([clientsig.slice(0, sdl), clientsig.slice(sdl + SHA256DL)], 1504);
computedSignature = calcHmac(msg, GenuineFPConst);
providedSignature = clientsig.slice(sdl, sdl + SHA256DL);
if (computedSignature.equals(providedSignature)) {
return MESSAGE_FORMAT_1;
}
return MESSAGE_FORMAT_0;
}
function generateS1(messageFormat) {
let randomBytes = Crypto.randomBytes(RTMP_SIG_SIZE - 8);
let handshakeBytes = Buffer.concat([Buffer.from([0, 0, 0, 0, 1, 2, 3, 4]), randomBytes], RTMP_SIG_SIZE);
let serverDigestOffset
if (messageFormat === 1) {
serverDigestOffset = GetClientGenuineConstDigestOffset(handshakeBytes.slice(8, 12));
} else {
serverDigestOffset = GetServerGenuineConstDigestOffset(handshakeBytes.slice(772, 776));
}
msg = Buffer.concat([handshakeBytes.slice(0, serverDigestOffset), handshakeBytes.slice(serverDigestOffset + SHA256DL)], RTMP_SIG_SIZE - SHA256DL);
hash = calcHmac(msg, GenuineFMSConst);
hash.copy(handshakeBytes, serverDigestOffset, 0, 32);
return handshakeBytes;
}
function generateS2(messageFormat, clientsig, callback) {
let randomBytes = Crypto.randomBytes(RTMP_SIG_SIZE - 32);
let challengeKeyOffset;
if (messageFormat === 1) {
challengeKeyOffset = GetClientGenuineConstDigestOffset(clientsig.slice(8, 12));
} else {
challengeKeyOffset = GetServerGenuineConstDigestOffset(clientsig.slice(772, 776));
}
let challengeKey = clientsig.slice(challengeKeyOffset, challengeKeyOffset + 32);
let hash = calcHmac(challengeKey, GenuineFMSConstCrud);
let signature = calcHmac(randomBytes, hash);
let s2Bytes = Buffer.concat([randomBytes, signature], RTMP_SIG_SIZE);
return s2Bytes
}
function generateS0S1S2(clientsig) {
let clientType = Buffer.alloc(1, 3);
let messageFormat = detectClientMessageFormat(clientsig);
let allBytes;
if (messageFormat === MESSAGE_FORMAT_0) {
// Logger.debug('[rtmp handshake] using simple handshake.');
allBytes = Buffer.concat([clientType, clientsig, clientsig]);
} else {
// Logger.debug('[rtmp handshake] using complex handshake.');
allBytes = Buffer.concat([clientType, generateS1(messageFormat), generateS2(messageFormat, clientsig)]);
}
return allBytes;
}
module.exports = { generateS0S1S2 };

View File

@ -0,0 +1,50 @@
//
// Created by Mingliang Chen on 17/8/1.
// illuspas[a]gmail.com
// Copyright (c) 2018 Nodemedia. All rights reserved.
//
// const Logger = require('./node_core_logger');
const Net = require('net');
const NodeRtmpSession = require('./node_rtmp_session');
const NodeCoreUtils = require('./node_core_utils');
const context = require('./node_core_ctx');
const RTMP_PORT = 1935;
class NodeRtmpServer {
constructor(config) {
config.rtmp.port = this.port = config.rtmp.port ? config.rtmp.port : RTMP_PORT;
this.tcpServer = Net.createServer((socket) => {
let session = new NodeRtmpSession(config, socket);
session.run();
})
}
run() {
this.tcpServer.listen(this.port, () => {
// Logger.log(`Node Media Rtmp Server started on port: ${this.port}`);
});
this.tcpServer.on('error', (e) => {
// Logger.error(`Node Media Rtmp Server ${e}`);
});
this.tcpServer.on('close', () => {
// Logger.log('Node Media Rtmp Server Close.');
});
}
stop() {
this.tcpServer.close();
context.sessions.forEach((session, id) => {
if (session instanceof NodeRtmpSession) {
session.socket.destroy();
context.sessions.delete(id);
}
});
}
}
module.exports = NodeRtmpServer

File diff suppressed because it is too large Load Diff

View File

@ -87,9 +87,15 @@
<option value="socket"><%-lang['Shinobi Streamer']%></option>
<option value="dashcam"><%-lang['Dashcam (Streamer v2)']%></option>
<option value="local"><%-lang['Local']%></option>
<% if(config.rtmpServer){ %><option value="rtmp"><%-lang['RTMP']%></option><% } %>
</select></div>
</label>
</div>
<div class="form-group h_t_input h_t_rtmp">
<label><div><span><%-lang['Stream Key']%></span></div>
<div><input class="form-control" detail="rtmp_key"></div>
</label>
</div>
<div class="h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg h_t_local">
<div class="form-group h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg">
<label><div><span><%-lang['Automatic']%></span></div>