您现在的位置是:首页 >技术教程 >ffmpeg转码数据流通过websocket传给浏览器播放网站首页技术教程
ffmpeg转码数据流通过websocket传给浏览器播放
简介ffmpeg转码数据流通过websocket传给浏览器播放
背景
浏览器支持的视频格式有限。而ffmpeg有很强的格式转换功能。那我们能不能通过ffmpeg把不支持的视频转成浏览器可以支持的视频呢?
方案
要实现以上方案要解决几个问题:
- 如何实时获取ffmpeg的转换后的数据
- 如何将数据实时传给浏览器
- 浏览器收到数据后如何播放
如何实时获取ffmpeg的转换后的数据
正常我们通过 ffmpeg 是可以将一个视频文件转换成另一种视频文件。
如下是将一个其他编码的mp4文件编码格式进行转换
ffmpeg -i 1.mp4 -c:v libx264 -f mp4 mp4-264.mp4
但我们怎么通过程序调用ffmpeg来获取转换后的数据呢。
ffmpeg 提供了一个pipe的功能可以将数据传给标准输入输出
nodejs 可以通过spawn来调用ffmpeg获取输出流
let ffmpeg = spawn('ffmpeg', //ffmpeg 自己从官网上载,改成自己路径,或配置成全局变量
[
'-i', 'html/1.mp4',
'-c:v', 'libx264', // 如果原视频已经是264格式可以不转
'-movflags', 'frag_keyframe+empty_moov+default_base_moof', // 转成fragment mp4
'-f', 'mp4',
'pipe:1' // 输出流
]);
ffmpeg.stdout.on('data', chunk=>{
console.log("data")
})
如何将数据实时传给浏览器
现在转换后的视频数据拿到了,怎么将数据传给浏览器呢。
浏览器实时数据的传输一般想到的是用websocket.
我这边想到的是用两条websocket. 一条传输控制命令,一条传输视频数据。
websocket 支持二进制数据传输和文本数据传输。这里控制命令用文本类型。视频数据用的是二进制数据类型。
ffmpeg.stdout.on('data', chunk=>{
console.log("data")
//转码后的数据用二制制传输
ws.send(chunk, {binary: true, mask: false});
})
浏览器收到数据后如何播放
视频流的播放这里用到了MediaSourceExtension(MSE). https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
需要注意的是,MSE只支持 fragmented mp4 用ffmpeg指定转framented mp4参数
-movflags frag_keyframe+empty_moov+default_base_moof // 转成fragment mp4
let video = document.querySelector('video');
let mediasource = new MediaSource();
video.src = URL.createObjectURL(mediasource);
可将websocket接收到数据传给Mediasource
ws.onmessage = (event) => {
sourceBuffer.appendBuffer(event.data);
};
由于播放速度和转码的数据不一致,所以要根据播放进度来控制进度。这里用到刚提到的控制websocket, 通过监听播放进度和缓存的时间来判断是否要从后端拉取数据.
function getData() {
controlWs.send("get")
}
// updateend会在往MediaSource放数据后调用, 可以读取到缓存时间
sourceBuffer.addEventListener('updateend', () => {
let buffered = sourceBuffer.buffered
for (let i = 0; i < buffered.length; i++) {
bufferTime = buffered.end(i); // 缓存了多少时间的数据
}
/**
*readyState
* 0 = HAVE_NOTHING - 没有关于音频/视频是否就绪的信息
1 = HAVE_METADATA - 关于音频/视频就绪的元数据
2 = HAVE_CURRENT_DATA - 关于当前播放位置的数据是可用的,但没有足够的数据来播放下一帧/毫秒
3 = HAVE_FUTURE_DATA - 当前及至少下一帧的数据是可用的
4 = HAVE_ENOUGH_DATA - 可用数据足以开始播放
* */
if (video.readyState !== 4) {// 数据不够拉数据
getData();
}
});
video.addEventListener('timeupdate', (e)=>{
console.log("timeupdate: " , bufferTime , video.currentTime, video.readyState);
// 缓存10秒
if (bufferTime - video.currentTime < 10) {
getData()
}
})
完整代码
https://gitee.com/morris-mao/websocket-video
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。