总结
最近在写直播项目 目前比较重要的点就是推拉流 自己也去了解了一下
ffmpeg
FFmpeg 是一个开源项目,它提供了一个跨平台的命令行工具,以及一系列用于处理音频和视频数据的库。FFmpeg 能够执行多种任务,包括解封装、转封装、视频和音频的编码和解码、流处理等。它广泛应用于多媒体处理领域,被许多项目和网站推荐使用。
FFmpeg 的主要特点包括:
多平台支持:FFmpeg 可以在多种操作系统上运行,包括 Windows、Mac OS X、Linux 等。
功能丰富:它支持大量的音视频格式,能够进行编码、解码、转码、复用、解复用、滤波、转换、抓取等操作。
命令行工具:FFmpeg 提供了简洁的命令行接口,使得用户能够方便地进行批处理操作。
下载ffmpeg
官网 http://ffmpeg.org/
配置环境变量 添加到系统环境变量中
输入这个ffmpeg -version 出现这个 说明成功
node端
const http = require('http');
const express = require('express');
const socketio = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(express.static('public'))
var spawn = require('child_process').spawn;
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) {
socket._rtmpDestination = m;
});
socket.on('config_vcodec', function (m) {
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 framerate = socket.handshake.query.framespersecond;
var audioBitrate = parseInt(socket.handshake.query.audioBitrate);
var audioEncoding = "64k";
console.log(audioEncoding, audioBitrate);
console.log('framerate on node side', framerate);
var ops = [
'-i', '-',
'-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency',
'-c:a', 'aac', '-ar', '44100', '-b:a', '64k',
'-y',
'-use_wallclock_as_timestamps', '1',
'-async', '1',
'-filter_complex', 'aresample=44100',
'-strict', 'experimental',
'-bufsize', '1000',
'-f', 'flv', socket._rtmpDestination
];
ffmpeg_process = spawn('ffmpeg', ops);
ffmpeg_process.stderr.on('data', function (data) {
socket.emit('ffmpeg_stderr', '' + data);
});
ffmpeg_process.on('error', function (err) {
socket.emit('fatal', 'ffmpeg error!' + err);
feedStream = false;
console.log('意外出错',err);
socket.disconnect();
});
ffmpeg_process.on('exit', function (err) {
socket.emit('fatal', 'ffmpeg exit!' + err);
socket.disconnect();
console.log('exit',err);
});
socket.on('binarystream', function (data) {
console.log(data,'48888');
ffmpeg_process.stdin.write(data);
});
});
socket.on('disconnect', function () {
feedStream = false;
ffmpeg_process.stdin.end();
ffmpeg_process.kill('SIGINT');
});
});
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`Socket.IO server is running on port ${PORT}`);
});
解释一下代码
开启一个子线程用于运行ffmpeg
var spawn = require('child_process').spawn;
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);
});
用sockect进行连接 当监听到connection事件之后所做的事情
io.on('connection', function (socket) {
....
}
socket监听start消息
io.on('start', function (socket) {
....
}
设置对ffmpeg进行操作的命令 用于推流
var ops = [
'-i', '-',
'-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency',
'-c:a', 'aac', '-ar', '44100', '-b:a', '64k',
'-y',
'-use_wallclock_as_timestamps', '1',
'-async', '1',
'-filter_complex', 'aresample=44100',
'-strict', 'experimental',
'-bufsize', '1000',
'-f', 'flv', socket._rtmpDestination
];
ffmpeg_process = spawn('ffmpeg', ops);
这个data就是接收到的数据流
ffmpeg_process.stderr.on('data', function (data) {
socket.emit('ffmpeg_stderr', '' + data);
});
前端部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./index.css">
</head>
<script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.7.2/socket.io.js"></script>
<body>
<header class="header">
<h1>
Start a Live Stream!
</h1>
</header>
<br><br>
<label for="option_width" class="form">Size:</label>
<input class="form" type="text" id="option_width" value="1280" /> ×
<input class="form" type="text" id="option_height" value="720" />
<br><br>
<label class="form" for="option_framerate">Frame Rate:</label>
<input class="form" type="text" id="option_framerate" value="15" />
<br><br>
<label class="form" for="option_framerate">Audio bitrate:</label>
<select class="form" id="option_bitrate">
<option value="22050">22050</option>
<option value="44100">44100</option>
<option value="11025">11025</option>
</select>
<br><br>
<input class="form" type="hidden" id="socket.io_address" value="/" />
<label class="form" for="option_url">RTMP:</label>
<!-- RMTP YOUTUBE URL/KEY-->
<input class="form" type="text" id="option_url" style="width:33%"
value="rtmp://a.rtmp.youtube.com/live2/cqg8-76xs-hztf-hfx7-9q5y" />
<br>
<!-- RMTP YOUTUBE URL/KEY-->
<input class="form" type="checkbox" style="display:none" id="checkbox_Reconection" checked="true">
<label class="form" style="display:none">Reconnection </label>
<br>
<div class="form">
Connect the server, then start streaming.
</div>
<br />
<button class="form" id="button_server">Connect Server</button>
<button class="form" id="button_start">Start Streaming</button>
<button class="form" id="button_stop">Stop Streaming</button>
<br>
<p id="output_message"></p>
<textarea readonly="true" id="output_console" rows=15>Server Logs</textarea>
<br>
<div class="video">
<video class="output_video" id="output_video" autoplay=true></video>
</div>
<br>
<script src="./index.js"></script>
</body>
</html>
js部分
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'),
socketio_address = document.getElementById('socket.io_address'),
option_width = document.getElementById('option_width'),
option_height = document.getElementById('option_height'),
option_framerate = document.getElementById('option_framerate'),
option_bitrate = document.getElementById('option_bitrate'),
button_start = document.getElementById('button_start'),
height = parseInt(option_height.value),
width = parseInt(option_width.value),
framerate = parseInt(option_framerate.value),
audiobitrate = parseInt(option_bitrate.value),
url = option_url.value;
option_height.onchange = option_height.onkeyup = function () { height = 1 * this.value; }
option_width.onchange = option_width.onkeyup = function () { width = 1 * this.value; console.log("width" + width); }
option_framerate.onchange = option_framerate.onkeyup = function () { framerate = 1 * this.value; console.log("framerate" + framerate); }
option_bitrate.onchange = option_bitrate.onkeyup = function () { audiobitrate = 1 * this.value; console.log("bitrate" + audiobitrate); }
option_url.onchange = option_url.onkeyup = function () { url = this.value; }
button_start.onclick = requestMedia;
button_server.onclick = connect_server;
var flagConnect = true
var mediaRecorder;
var socket;
var state = "stop";
console.log("state initiated = " + state);
var t;
button_start.disabled = true;
button_stop.disabled = true;
function connect_server() {
navigator.getUserMedia = (navigator.mediaDevices.getUserMedia ||
navigator.mediaDevices.mozGetUserMedia ||
navigator.mediaDevices.msGetUserMedia ||
navigator.mediaDevices.webkitGetUserMedia);
if (!navigator.getUserMedia) { fail('No getUserMedia() available.'); }
if (!MediaRecorder) { fail('No MediaRecorder available.'); }
var socketOptions = { secure: true, reconnection: true, reconnectionDelay: 1000, timeout: 15000, pingTimeout: 15000, pingInterval: 45000, query: { framespersecond: framerate, audioBitrate: audiobitrate } };
socket = io(socketOptions);
socket.on('connect_timeout', (timeout) => {
console.log("连接超时" + timeout);
output_message.innerHTML = "Connection timed out";
});
socket.on('error', (error) => {
console.log("state on connection error= " + error);
output_message.innerHTML = "Connection error";
});
socket.on('connect_error', function () {
console.log("连接错误" + state);
});
socket.on('message', function (m) {
console.log("state on message= " + state + m);
show_output('SERVER:' + m);
});
socket.on('fatal', function (m) {
show_output('Fatal ERROR: unexpected:' + m);
console.log("fatal socket error!!", m);
console.log("state on fatal error= " + state);
console.log('media recorder restarted');
if (flagConnect) {
output_message.innerHTML = "server is reload!";
console.log("重置连接");
}
//should reload?
});
socket.on('flagConnect', function (m) {
//this is the ffmpeg output for each frame
show_output('FFMPEG:' + m);
console.log(m, '我是每一帧');
});
socket.on('disconnect', function (reason) {
console.log("state disconec= " + state);
show_output('ERROR: server disconnected!');
console.log('失去连接' + reason);
connect_server();
if (flagConnect) {
output_message.innerHTML = "重置";
console.log("重置");
}
});
state = "ready";
console.log("state = " + state);
button_start.disabled = false;
button_stop.disabled = false;
button_server.disabled = true;
output_message.innerHTML = "Connecting local server...";
}
async function requestMedia() {
var constraints = {
audio: {
sampleRate: audiobitrate,
echoCancellation: true
},
video: {
width: { min: 100, ideal: width, max: 1920 },
height: { min: 100, ideal: height, max: 1080 },
frameRate: { ideal: framerate }
}
}
const stream = await navigator.mediaDevices.getUserMedia(constraints)
video_show(stream)
socket.emit('config_rtmpDestination', url);
socket.emit('start', 'start');
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.start(250)
button_stop.disabled = false;
button_start.disabled = true;
button_server.disabled = true;
var livestream = document.getElementsByClassName("Livestream");
livestream.innerHtml = "test";
mediaRecorder.onstop = function (e) {
console.log("stopped!");
console.log(e);
}
mediaRecorder.onpause = function (e) {
console.log("media recorder paused!!");
console.log(e);
}
mediaRecorder.onerror = function (event) {
let error = event.error;
console.log("error", error.name);
};
mediaRecorder.ondataavailable = function (e) {
socket.emit("binarystream", e.data);
state = "start";
}
}
function video_show(stream) {
console.log(stream, '111111');
if ("srcObject" in output_video) {
output_video.muted = true;
output_video.srcObject = stream;
} else {
output_video.src = window.URL.createObjectURL(stream);
}
}
function show_output(str) {
output_console.value += "\n" + str;
output_console.scrollTop = output_console.scrollHeight;
};
以上代码分为 这三个方法
其中 以connect-server
先使用浏览器自带的 navigator.getUserMedia获取设备 并且将配置的属性传入socket配置中 这个方法就是对socket连接进行处理 包括处理服务器端连接过程通信的过程
requestMedia() 获取媒体数据流 并且通过 socket.emit(“binarystream”, e.data);传递给后台
const stream = await navigator.mediaDevices.getUserMedia(constraints)
实例
下周
目前弄了一个分片上传 下周弄完之后可以分享分享