https://www.educba.com/websocket-vs-socket-io/

https://support.theoplayer.com/hc/en-us/articles/206932789-Cross-Origin-Resource-Sharing

socket 通信
https://yogurt.iteye.com/blog/1968272
https://sites.google.com/site/ucanlab/Home/chimain/opensource/scocket-programming-with-dev-c
https://studygolang.com/articles/4664

轉換流解決方案
https://stackoverflow.com/questions/19658216/how-can-we-transcode-live-rtmp-stream-to-live-hls-stream-using-ffmpeg

https://www.youtube.com/watch?v=W9QYOwSmc5c

https://412887952-qq-com.iteye.com/blog/1620334

            Console.WriteLine("Starting:Creating Socket object");
            Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listener.Bind(new IPEndPoint(IPAddress.Any, 2112));
            listener.Listen(10);

            while (true)
            {
                Console.WriteLine("Waiting for connection on port 2112");
                Socket socket = listener.Accept();
                string receivedValue = string.Empty;

                while (true)
                {
                    byte[] receivedBytes = new byte[1024];
                    int numBytes = socket.Receive(receivedBytes);
                    Console.WriteLine("Receiving.");
                    receivedValue += Encoding.ASCII.GetString(receivedBytes, 0, numBytes);
                    if (receivedValue.IndexOf("[FINAL]") > -1)
                    {
                        break;
                    }
                }

                Console.WriteLine("Received value:{0}", receivedValue);
                string replyValue = "Message successfully received.";
                byte[] replyMessage = Encoding.ASCII.GetBytes(replyValue);
                socket.Send(replyMessage);
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
            }

```C:\Users\Administrator>ffmpeg -re -i rtmp://localhost/live/myCamera -s 640x480 -
vcodec copy  -tune  zerolatency  -filter_complex aresample=44100  -bufsize 1000
-c:a aac -b:a:0 128k -f flv rtmp://localhost/livepkgr/livestream?adbe-live-event
=liveevent

https://dotblogs.com.tw/atowngit/2009/12/19/12547

https://www.one-tab.com/page/yWlxqQR9RIuT2oumjQXJtg

https://www.one-tab.com/page/vsoW7_TMQUyTyZ7TtcUrSg
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Process process;
        public Form1()
        {
            InitializeComponent();
          
        }

        private void button1_Click(object sender, EventArgs e)
        {
          
            ProcessStartInfo pinfo = new ProcessStartInfo("c:\\ffmpeg\\bin\\ffmpeg.exe");
            pinfo.Arguments = "-re -i rtmp://localhost/live/mycamera -s 640x480 -vcodec copy  -tune  zerolatency  -filter_complex aresample=44100  -bufsize 1000 -c:a aac -b:a:0 128k -f flv rtmp://localhost/livepkgr/livestream?adbe-live-event=liveevent";
            process= Process.Start("calc.exe");
            process.EnableRaisingEvents = true;
            process.SynchronizingObject = this;
            process.Exited += new EventHandler(process_Exited);
            MessageBox.Show("Main Thread ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
            //pinfo.UseShellExecute = false; //必须为false
            //pinfo.RedirectStandardOutput = true;
            //pinfo.CreateNoWindow = true;
            //using (Process p = new Process())
            //{

            //    p.EnableRaisingEvents = true;
            //    p.StartInfo = pinfo;
            //    p.Exited += new EventHandler(P_Exited);
            //    p.Start();
            //    //p.BeginOutputReadLine();

            //}

        }
        void process_Exited(object sender, EventArgs e)
        {
            while (!process.HasExited)
            {
                Thread.Sleep(1);
                Application.DoEvents();
            }
            MessageBox.Show("Process Thread ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }

        private void button2_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Process Thread ID:" + process.Responding.ToString());
        }
    }
}

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

​​​​    private void button1_Click(object sender, EventArgs e)
​​​​    {

​​​​        Process process = new Process();
​​​​        //Environment.GetEnvironmentVariable("WinDir") + "\\Notepad.exe";
​​​​      //  process.StartInfo.FileName = "C:\\ffmpeg\\bin\\ffmpeg.exe -re -i rtmp://localhost/live/myCamera -s 640x480 -vcodec copy  -tune  zerolatency - filter_complex aresample = 44100 - bufsize 1000  - c:a aac -b:a: 0 128k - f flv rtmp://localhost/livepkgr/livestream?adbe-live-event= liveevent";
​​​​        ProcessStartInfo pinfo = new ProcessStartInfo("ffmpeg");
​​​​        pinfo.Arguments = "-re -i rtmp://localhost/live/myCamera -s 640x480 -vcodec copy  -tune  zerolatency  -filter_complex aresample=44100  -bufsize 1000 -c:a aac -b:a:0 128k -f flv rtmp://localhost/livepkgr/livestream?adbe-live-event=liveevent";
​​​​        process.StartInfo = pinfo;
​​​​        process.Start();
​​​​        process.Exited += new EventHandler(process_Exited);
​​​​        process.SynchronizingObject = this;
​​​​        process.EnableRaisingEvents = true;
​​​​    }
​​​​    void process_Exited(object sender, EventArgs e)
​​​​    {
​​​​        
​​​​        MessageBox.Show("Notepad.exe 已被關閉!!!");
​​​​    }
​​​​}

}


好參數
​			try{
​				cam = Camera.getCamera();
​				cam.setMode(1027, 768,30);  
​				
​				/**
​				 * public function setKeyFrameInterval(keyFrameInterval:int):void
​				 * The number of video frames transmitted in full (called keyframes) instead of being interpolated by the video compression algorithm.
​				 * The default value is 15, which means that every 15th frame is a keyframe. A value of 1 means that every frame is a keyframe. 
​				 * The allowed values are 1 through 300. 
​				 */
​				cam.setKeyFrameInterval(15);
​				
​				/**
​				 * public function setQuality(bandwidth:int, quality:int):void  
​				 * bandwidth:int — Specifies the maximum amount of bandwidth that the current outgoing video feed can use, in bytes per second (bps).   
​				 *    To specify that the video can use as much bandwidth as needed to maintain the value of quality, pass 0 for bandwidth.   
​				 *    The default value is 16384.  
​				 * quality:int — An integer that specifies the required level of picture quality, as determined by the amount of compression   
​				 *     being applied to each video frame. Acceptable values range from 1 (lowest quality, maximum compression) to 100   
​				 *    (highest quality, no compression). To specify that picture quality can vary as needed to avoid exceeding bandwidth,   
​				 *    pass 0 for quality.  
​				 */
​				cam.setQuality(0,100); 
​				
​				/**
​				 * public function setProfileLevel(profile:String, level:String):void
​				 * Set profile and level for video encoding. 
​				 * Possible values for profile are H264Profile.BASELINE and H264Profile.MAIN. Default value is H264Profile.BASELINE.
​				 * Other values are ignored and results in an error.
​				 * Supported levels are 1, 1b, 1.1, 1.2, 1.3, 2, 2.1, 2.2, 3, 3.1, 3.2, 4, 4.1, 4.2, 5, and 5.1.
​				 * Level may be increased if required by resolution and frame rate.
​				 */
​				//var h264setting:H264VideoStreamSettings = new H264VideoStreamSettings();  
​				//h264setting.setProfileLevel(H264Profile.BASELINE, H264Level.LEVEL_3_1);
​				
​				
​				//Mic
​				
​				mic = Microphone.getMicrophone();
​				
​				/*
​				* The encoded speech quality when using the Speex codec. Possible values are from 0 to 10. The default value is 6. 
​				* Higher numbers represent higher quality but require more bandwidth, as shown in the following table. 
​				* The bit rate values that are listed represent net bit rates and do not include packetization overhead. 
​				* ------------------------------------------
​				* Quality value | Required bit rate (kbps)
​				*-------------------------------------------
​				*      0        |       3.95 
​				*      1        |       5.75 
​				*      2        |       7.75 
​				*      3        |       9.80 
​				*      4        |       12.8 
​				*      5        |       16.8 
​				*      6        |       20.6 
​				*      7        |       23.8 
​				*      8        |       27.8 
​				*      9        |       34.2 
​				*      10       |       42.2 
​				*-------------------------------------------
​				*/
​				mic.encodeQuality = 9;  
​				
​				/* The rate at which the microphone is capturing sound, in kHz. Acceptable values are 5, 8, 11, 22, and 44. The default value is 8 kHz   
​				* if your sound capture device supports this value. Otherwise, the default value is the next available capture level above 8 kHz that   
​				* your sound capture device supports, usually 11 kHz.  
​				*
​				*/
​				mic.rate = 44;  
​				
​				
​				ns = new NetStream(nc);
​				var h264Settings:H264VideoStreamSettings=new H264VideoStreamSettings();
​				h264Settings.setProfileLevel(H264Profile.MAIN, H264Level.LEVEL_5_1);
​				h264Settings.setMode(1027, 768, 30);
​				h264Settings.setQuality(0, 100);
​				//H.264 Setting
​				ns.videoStreamSettings =h264Settings; 
​				ns.attachCamera(cam);
​				ns.attachAudio(mic);
​				ns.publish(publish_name, play_type);

    pinfo.Arguments = "-re -i rtmp://localhost/live/myCamera -r 60 -vcodec copy  -tune  zerolatency  -filter_complex aresample=44100" +
                "  -bufsize 2000 -c:a aac -b:a:0 128k -f flv rtmp://localhost/livepkgr/livestream?adbe-live-event=liveevent";
                
                
         c# thread 呼叫      
​​​​  using System;

using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1
{
class Program
{
static String nProcessID;
static int count = 0;
public static void process_Exited(object sender, EventArgs e)
{
Console.WriteLine(nProcessID);
//Console.WriteLine("exit");
}

​​​​    public static void call_process_ffmpeg(object name)
​​​​    {
​​​​        string thread_name = (string)name; //unboxing
​​​​        Process process = new Process();
​​​​        //Environment.GetEnvironmentVariable("WinDir") + "\\Notepad.exe";
​​​​        //  process.StartInfo.FileName = "C:\\ffmpeg\\bin\\ffmpeg.exe -re -i rtmp://localhost/live/myCamera -s 640x480 -vcodec copy  -tune  zerolatency - filter_complex aresample = 44100 - bufsize 1000  - c:a aac -b:a: 0 128k - f flv rtmp://localhost/livepkgr/livestream?adbe-live-event= liveevent";
​​​​        ProcessStartInfo pinfo = new ProcessStartInfo("ffmpeg");
​​​​        pinfo.Arguments = "-re -i rtmp://localhost/live/myCamera -r 30 -vcodec copy  -tune  zerolatency  -filter_complex aresample=44100  -bufsize 1000 -c:a aac -b:a:0 128k -f flv rtmp://localhost/livepkgr/livestream?adbe-live-event=liveevent";
​​​​        process.StartInfo = pinfo;

​​​​        process.Start();

​​​​        process.Exited += new EventHandler(process_Exited);

​​​​        process.EnableRaisingEvents = true;
​​​​        // process.WaitForInputIdle();

​​​​        nProcessID = process.SessionId.ToString();

​​​​        process.WaitForExit();
​​​​        Console.WriteLine(thread_name +"exit");
​​​​    }
​​​​    static void Main(string[] args)
​​​​    {


​​​​        Thread test = new Thread( new ParameterizedThreadStart(call_process_ffmpeg));
​​​​        
​​​​        test.Start("test1");
​​​​        Thread test2 = new Thread(new ParameterizedThreadStart(call_process_ffmpeg));

​​​​        test2.Start("test2");
​​​​        Console.ReadLine();


​​​​    }

​​​​}

}



using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1
{
class Program
{
static String nProcessID;
static int count = 0;
public static void process_Exited(object sender, EventArgs e)
{
Console.WriteLine(nProcessID);
//Console.WriteLine("exit");
}

​​​​    public static void call_process_ffmpeg(object name)
​​​​    {
​​​​        string thread_name = (string)name; //unboxing
​​​​        Process process = new Process();
​​​​        //Environment.GetEnvironmentVariable("WinDir") + "\\Notepad.exe";
​​​​        //  process.StartInfo.FileName = "C:\\ffmpeg\\bin\\ffmpeg.exe -re -i rtmp://localhost/live/myCamera -s 640x480 -vcodec copy  -tune  zerolatency - filter_complex aresample = 44100 - bufsize 1000  - c:a aac -b:a: 0 128k - f flv rtmp://localhost/livepkgr/livestream?adbe-live-event= liveevent";
​​​​        ProcessStartInfo pinfo = new ProcessStartInfo("ffmpeg");
​​​​        pinfo.Arguments = "-re -i rtmp://localhost/live/myCamera -r 30 -vcodec copy  -tune  zerolatency  -filter_complex aresample=44100  -bufsize 1000 -c:a aac -b:a:0 128k -f flv rtmp://localhost/livepkgr/livestream?adbe-live-event=liveevent";
​​​​        process.StartInfo = pinfo;

​​​​        process.Start();

​​​​        process.Exited += new EventHandler(process_Exited);

​​​​        process.EnableRaisingEvents = true;
​​​​        // process.WaitForInputIdle();

​​​​        nProcessID = process.SessionId.ToString();

​​​​        process.WaitForExit();
​​​​        Console.WriteLine(thread_name +"exit");
​​​​    }
​​​​    static void Main(string[] args)
​​​​    {
​​​​        List<Thread> threads = new List<Thread>();

​​​​        for (int i = 0; i < 10; i++)
​​​​        {
​​​​            Thread tmp = new Thread(new ParameterizedThreadStart(call_process_ffmpeg));

​​​​            threads.Add(tmp);

​​​​        }
​​​​        int count = 0;
​​​​        foreach (Thread tmp in threads)
​​​​        {
​​​​            tmp.Start(count.ToString());
​​​​            count++;
​​​​        }

​​​​        //for (int i = 0; i < threads.)
​​​​        //Thread test = new Thread( new ParameterizedThreadStart(call_process_ffmpeg));
​​​​        
​​​​        //test.Start("test1");
​​​​        //Thread test2 = new Thread(new ParameterizedThreadStart(call_process_ffmpeg));

​​​​        //test2.Start("test2");
​​​​        //Console.ReadLine();


​​​​    }

​​​​}

}

https://github.com/gwuhaolin/blog/issues/3

https://blog.csdn.net/the_victory/article/details/79666702

https://blog.csdn.net/mengzhengjie/article/details/71280007

https://d887419.wordpress.com/2017/04/23/nvidia-%E9%A1%AF%E5%8D%A1%E7%9A%84-cuvid-decoder-ffmpeg-decoder-name/

https://ithelp.ithome.com.tw/articles/10192437

\
https://13521308103.iteye.com/blog/1997136

http://yindingtsai.blogspot.com/2010/01/runtimeexec.html

https://ithelp.ithome.com.tw/articles/10203335


var express = require('express');
var app = express();
//var http = require('http').Server(app);


//var ffmpeg = require('fluent-ffmpeg');
//var stream = require('stream');
var spawn = require('child_process').spawn;
var fs = require('fs');

app.use(express.static('static'));
const server = require('https').createServer({
 key: fs.readFileSync('abels-key.pem'),
  cert: fs.readFileSync('abels-cert.pem')
 
},app);
//testing

var io = require('socket.io')(server);
spawn('ffmpeg',['-h']).on('error',function(m){
	console.error("FFMpeg not found in system cli; please install ffmpeg properly or make a softlink to ./!");
	process.exit(-1);
});


io.on('connection', function(socket){
	socket.emit('message','Hello from mediarecorder-to-rtmp server!');
	socket.emit('message','Please set rtmp destination before start streaming.');
	
	var ffmpeg_process, feedStream=false;
	socket.on('config_rtmpDestination',function(m){
		if(typeof m != 'string'){
			socket.emit('fatal','rtmp destination setup error.');
			return;
		}
		var regexValidator=/^rtmp:\/\/[^\s]*$/;//TODO: should read config
		if(!regexValidator.test(m)){
			socket.emit('fatal','rtmp address rejected.');
			return;
		}
		socket._rtmpDestination=m;
		socket.emit('message','rtmp destination set to:'+m);
	}); 
	//socket._vcodec='libvpx';//from firefox default encoder
	socket.on('config_vcodec',function(m){
		if(typeof m != 'string'){
			socket.emit('fatal','input codec setup error.');
			return;
		}
		if(!/^[0-9a-z]{2,}$/.test(m)){
			socket.emit('fatal','input codec contains illegal character?.');
			return;
		}//for safety
		socket._vcodec=m;
	}); 	


	socket.on('start',function(m){
		if(ffmpeg_process || feedStream){
			
			socket.emit('fatal','stream already started.');
			return;
		}
		if(!socket._rtmpDestination){
			socket.emit('fatal','no destination given.');
			return;
		}
		
		var ops=[
			'-i','-',
			'-r','60',
			'-vcodec', 'libx264',
			'-preset','ultrafast',
			'-b:v', '400k',
			'-s', '480x360',
			'-tune' ,'zerolatency',
			//'-preset', 'ultrafast',
			//'-an', //TODO: give up audio for now...
			//'-async', '1', 
			'-filter_complex', 'aresample=44100', //necessary for trunked streaming?
			'-strict', 'experimental',
			//'-strict', 'experimental', '-c:a', 'aac', '-b:a', '128k',
			//'-bufsize', '1000',
			"-fflags",'nobuffer',
			'-analyzeduration','0',
			'-c:a', 'aac' ,
			'-b:a:0', '64k',
			'-benchmark',
			'-f', 'flv', socket._rtmpDestination
		];
		
		console.log(socket._rtmpDestination);
		ffmpeg_process=spawn('ffmpeg', ops);
		feedStream=function(data){
			
			ffmpeg_process.stdin.write(data);
			//write exception cannot be caught here.	
		}

		ffmpeg_process.stderr.on('data',function(d){
			socket.emit('ffmpeg_stderr',''+d);
		});
		ffmpeg_process.on('error',function(e){
			console.log('child process error'+e);
			socket.emit('fatal','ffmpeg error!'+e);
			feedStream=false;
			socket.disconnect();
		});
		ffmpeg_process.on('exit',function(e){
			console.log('child process exit'+e);
			socket.emit('fatal','ffmpeg exit!'+e);
			socket.disconnect();
		});
	});

	socket.on('binarystream',function(m){
		if(!feedStream){
			socket.emit('fatal','rtmp not set yet.');
			return;
		}
		feedStream(m);
	});
	socket.on('disconnect', function () {
		feedStream=false;
		if(ffmpeg_process)
		try{
			ffmpeg_process.stdin.end();
			ffmpeg_process.kill('SIGINT');
		}catch(e){console.warn('killing ffmoeg process attempt failed...');}
	});
	socket.on('error',function(e){
		
		console.log('socket.io error:'+e);
	});
});

io.on('error',function(e){
	console.log('socket.io error:'+e);
});

//http.listen(8888, function(){
 // console.log('http and websocket listening on *:8888');
//});

server.listen(443, function(){
  console.log('https and websocket listening on *:443');
});

process.on('uncaughtException', function(err) {
    // handle the error safely
    console.log(err)
    // Note: after client disconnect, the subprocess will cause an Error EPIPE, which can only be caught this way.
})


		var ops=[
			'-i','-',
			'-r','60',
			'-vcodec', 'libx264',
			'-preset','ultrafast',
			'-b:v', '400k',
			'-s', '480x360',
			'-tune' ,'zerolatency',
			//'-preset', 'ultrafast',
			//'-an', //TODO: give up audio for now...
			//'-async', '1', 
			'-filter_complex', 'aresample=44100', //necessary for trunked streaming?
			'-strict', 'experimental',
			//'-strict', 'experimental', '-c:a', 'aac', '-b:a', '128k',
			//'-bufsize', '1000',
			"-fflags",'nobuffer',
			'-analyzeduration','0',
			'-c:a', 'aac' ,
			'-b:a:0', '64k',
			'-benchmark',
			'-f', 'flv', socket._rtmpDestination
		];




# iiiiiiiiii
<!doctype>
<html>
<head>
	<title>MediaRecorder to RTMP Demo</title>
	<script src="/socket.io/socket.io.js"></script>
</head>
<body style="max-width:800px;height:100%;margin:auto;">
<h1>
MediaRecorder to RTMP Demo
</h1>
<label for="option_width" >Size:</label>
<input type="text" id="option_width" value="480"/> &times;
<input type="text" id="option_height" value="360"/>
<br>
<label for="option_url" >RTMP Destination:</label>
<input type="text" id="option_url" value="rtmp://localhost/live"/>
<br>
<button id="button_start">Start streaming</button>
<hr/>
<div>
	<p id="output_message"></p>
	<video id="output_video" autoplay=true></video>
</div>
<hr/>
<textarea readonly="true" id="output_console" cols=91 rows=5>
</textarea>

	<script>
function fail(str){alert(str+"\nPlease download the latest version of Firefox!");location.replace('http://mozilla.org/firefox');}
var output_console=document.getElementById('output_console'),
	output_message=document.getElementById('output_message'),
	output_video=document.getElementById('output_video'),
	option_url=document.getElementById('option_url'),
	option_width=document.getElementById('option_width'),
	option_height=document.getElementById('option_height'),
	button_start=document.getElementById('button_start'),
	height=option_height.value,
	width=option_width.value,
	url=option_url.value='rtmp://'+location.host.split(':')[0]+':1935/live/test';

option_height.onchange=option_height.onkeyup=function(){height=1*this.value;}
option_width.onchange=option_width.onkeyup=function(){width=1*this.value;}
option_url.onchange=option_url.onkeyup=function(){url=this.value;}
button_start.onclick=requestMedia;

function video_show(stream){
	if ("srcObject" in output_video) {
		output_video.srcObject = stream;
	} else {
		output_video.src = window.URL.createObjectURL(stream);
	}
  output_video.addEventListener( "loadedmetadata", function (e) {
	output_message.innerHTML="Local video source size:"+output_video.videoWidth+"x"+output_video.videoHeight;
}, false );
}
function show_output(str){
	output_console.value+="\n"+str;
	output_console.scrollTop = output_console.scrollHeight;
}


navigator.getUserMedia = (navigator.getUserMedia ||
                          navigator.mozGetUserMedia ||
                          navigator.msGetUserMedia ||
                          navigator.webkitGetUserMedia);
if(!navigator.getUserMedia){fail('No getUserMedia() available.');}
if(!MediaRecorder){fail('No MediaRecorder available.');}
var mediaRecorder;
var socket = io.connect("https://localhost", {secure: true});
//({
  // option 1
 // ca: fs.readFileSync("https://localhost:443",'./abels-cert.pem'),
  // option 2. WARNING: it leaves you vulnerable to MITM attacks!
   // requestCert: false,
  //  rejectUnauthorized: false
//});
//var socket = io.
socket.on('message',function(m){
	console.log('recv server message',m);
	show_output('SERVER:'+m);
});
socket.on('fatal',function(m){
	show_output('ERROR: unexpected:'+m);
	alert('Error:'+m);
	mediaRecorder.stop();
	//should reload?
});
socket.on('ffmpeg_stderr',function(m){
	show_output('FFMPEG:'+m);
});
socket.on('disconnect', function () {
	show_output('ERROR: server disconnected!');
	mediaRecorder.stop();
});

function requestMedia(){
	var constraints = { audio: true,
		video:{
	        width: { min: width, ideal: width, max: width },
	        height: { min: height, ideal: height, max: height },
	    }
	};
	navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
		video_show(stream);//only show locally, not remotely
	
		socket.emit('config_rtmpDestination',url);
		socket.emit('start','start');
		mediaRecorder = new MediaRecorder(stream);
		mediaRecorder.start(0);
	
		mediaRecorder.onstop = function(e) {
			stream.stop();
				
		}

		mediaRecorder.ondataavailable = function(e) {
			
		  socket.emit("binarystream",e.data);
		  //chunks.push(e.data);
		}
	}).catch(function(err) {
		console.log('The following error occured: ' + err);
		show_output('Local getUserMedia ERROR:'+err);
	});
}
	</script>
</body>
</html>


https://www.jianshu.com/p/92e32d6e7190

https://www.one-tab.com/page/SL0YBc_7QW-ENq1NMjW5Yw

# ffmpeg gpu 加速
https://blog.csdn.net/qq_39575835/article/details/83826073

https://juejin.im/post/5b0f69e46fb9a009f41479b4#heading-0


https://www.bilibili.com/read/cv1570233/

http://nodejs.cn/api/child_process.html


https://segmentfault.com/a/1190000007735211

http://nodejs.cn/api/cluster.html

https://larrylu.blog/nodejs-pm2-cluster-455ffbd7671

https://ithelp.ithome.com.tw/articles/10202999
Select a repo