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
parent
bdbf3458b6
commit
3503ac1426
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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,
|
||||
};
|
|
@ -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;
|
||||
|
|
@ -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 };
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 };
|
|
@ -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
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue