这几年直播太火了,H5端实现方案又是哪样,本文将与大家一起聊聊H5端视频直播中的基本流程和主要的技术点,包括但不限于前端技术。
一、大致实现流程
获取音视频流
采用getUserMedia
进行音视频录制,它是 MediaStream API 的一部分,可用来请求用户的摄像头和麦克风权限,并获取到相应的媒体流。
代码示例
1 2 3 4
| navigator.mediaDevices.getUserMedia({ video: true, audio: true }) .then(function(stream) { })
|
建立 WebRTC 连接
音视频流获取到了,但需要实时呈现给用户端,这时需要使用 RTCPeerConnection 建立与服务器端的实时连接。
代码示例
1 2 3 4 5 6 7 8 9
| var configuration = { iceServers: [{ urls: "stun:stun.example.org" }] };
var peerConnection = new RTCPeerConnection(configuration);
stream.getTracks().forEach(track => { peerConnection.addTrack(track, stream); });
|
将流传输到服务器
传输之前,我们得先准备一个信令(信令是指用于建立和维护对等连接所需的控制信息。信令服务器负责协调和传递这些信令消息,以便让通信双方能够建立对等连接)服务器。假设我们的信令服务器已通过websocket搭建完毕
启动连接
1 2
| var signalingServer = new WebSocket('ws://example.com/signaling');
|
信令交换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| signalingServer.onopen = function() { peerConnection.createOffer() .then(function(offer) { return peerConnection.setLocalDescription(offer); }) .then(function() { signalingServer.send(JSON.stringify({ type: 'offer', data: peerConnection.localDescription })); }) .catch(function(err) { }); };
signalingServer.onmessage = function(event) { var message = JSON.parse(event.data); if (message.type === 'answer') { peerConnection.setRemoteDescription(new RTCSessionDescription(message.data)) .catch(function(err) { }); } };
|
服务器端接收并转码
服务器端接收到视频流后,可以使用流媒体处理工具,如 FFmpeg,将视频流转码为适合网络传输的格式(如 HLS 或 RTMP)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const WebSocket = require('ws'); const { spawn } = require('child_process'); const http = require('http'); const fs = require('fs');
const wss = new WebSocket.Server({ port: 8080 });
const ffmpeg = spawn('ffmpeg', [ '-i', 'pipe:0', '-c:v', 'libx264', '-hls_time', '10', '-hls_list_size', '6', '-hls_flags', 'delete_segments', '-f', 'hls', 'output.m3u8' ]);
wss.on('connection', ws => { ws.on('message', data => { ffmpeg.stdin.write(data); }); });
|
至此,在当前工作目录下会产生一个m3u8文件(一个包含 HLS 播放列表的文件)。它本身不是一个真实的视频文件,而是一个指向视频分段的索引文件,包含了一系列的视频片段的 URL,客户端会根据这些 URL 下载视频分段并按照顺序播放,从而实现流媒体的播放。
分发流
接着奏乐接着舞,此时需要将视频流分发给其他客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const WebSocket = require('ws'); const { spawn } = require('child_process'); const http = require('http'); const fs = require('fs');
const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/html' }); const videoFile = fs.createReadStream('output.m3u8'); videoFile.pipe(res); });
server.listen(8000, () => { });
|
客户端播放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="video" controls autoplay></video>
document.addEventListener("DOMContentLoaded", function() { if (Hls.isSupported()) { var video = document.getElementById('video'); var hls = new Hls(); hls.loadSource('path/to/your/hls/video.m3u8'); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function() { video.play(); }); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = 'path/to/your/hls/video.m3u8'; video.addEventListener('loadedmetadata', function() { video.play(); }); } });
|
二、视频直播格式
- .flv:Flash Video格式,常用于使用RTMP协议进行直播。
- .m3u8:HLS(HTTP Live Streaming)播放列表文件,用于指导播放器获取HLS格式的直播流。
- .ts:MPEG Transport Stream格式,用于存储HLS直播流中的视频分段。
- .rtmp:RTMP协议的直播流地址,通常不是作为文件后缀存在,而是作为流媒体地址使用。
三、HLS延时问题
我们了解到了 HLS 协议将直播流切分成一段一段的小视频片段进行下载和播放时,我们可以做出以下推论:假设播放列表中包含 5 个 TS 文件,每个 TS 文件都包含 5 秒的视频内容,那么整体的延迟将达到 25 秒。这是因为当你观看这些视频时,主播已经录制并上传了视频内容,因此会产生这样的延迟。
当然,我们可以通过减少播放列表的长度和单个 TS 文件的大小来降低延迟。极端情况下,我们可以将播放列表的长度缩减到 1,并将 TS 文件的时长缩短至 1 秒。但是这样做会增加请求次数,加大服务器压力,同时在网络速度较慢时可能会导致更多的缓冲现象。
四、如何应对延时
先对比一下流媒体技术H5技术方案分析对比
协议 |
flv+mse |
flv+webAssembly+ffmpeg+webgl |
hls |
webRTC |
传输层 |
http-flv/websocket-flv |
http-flv/websocket-flv |
http |
UDP协议 |
视频格式 |
fly |
fly |
ts文件 |
fMp4 |
延时 |
低 |
低 |
很高 |
很低 |
H5兼容性 |
PC H5/安卓H5 |
PC H5/安卓H5/iOS H5 |
PC H5/安卓H5/iOS H5 |
PC H5 |
服务器编程难易 |
简单 |
简单 |
中等 |
中等 |
cpu性能 |
好 |
良好(比较适合i0S H5) |
好 |
好 |
这时我们发现,hls原来不是必选的方案,但该方案的确很大众,所以先带大家踩一下坑,再出发吧!
安卓H5实现
采用 flv 格式 + MediaSource
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const flvUrl = 'http://example.com/video.flv';
const videoElement = document.getElementById('videoPlayer');
const mediaSource = new MediaSource();
videoElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', function () { const sourceBuffer = mediaSource.addSourceBuffer('video/flv'); fetch(flvUrl) .then(response => response.arrayBuffer()) .then(data => { sourceBuffer.appendBuffer(data); }) .catch(error => { console.error('Failed to fetch FLV video:', error); }); });
|
IOS H5实现
尴尬的是,MediaSource在ios下支持并不乐观,否则一套代码已经结束了,继续把
最终采用 flv + webAssembly + ffmpeg + webgl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
const ffmpegModule = require('./ffmpeg.wasm');
async function decodeFLV() { const response = await fetch('example.flv'); const buffer = await response.arrayBuffer();
const result = ffmpegModule.ccall('decodeFLV', );
const videoFrames = result.videoFrames;
const canvas = document.createElement('canvas'); const context = canvas.getContext('webgl');
const videoPlayer = document.getElementById('videoPlayer'); videoPlayer.srcObject = canvas.captureStream();
videoPlayer.play(); }
decodeFLV();
|
五、HLS 与 FLV 对比
FLV 格式相对于 HLS(HTTP Live Streaming)格式具有较小的延迟,主要是因为它的传输和处理方式不同,以及其设计目标的不同所致。
传输方式:FLV 格式通常是通过基于 TCP 的 RTMP(Real-Time Messaging Protocol)传输的,而 RTMP 是一种实时性较高的流媒体传输协议,它可以更快地传输数据,并且对网络环境的变化更敏感。相比之下,HLS 使用的是基于 HTTP 的传输方式,其实时性较差,需要较长的缓冲时间。
分片大小:FLV 格式通常使用较小的分片(chunk)大小来传输数据,这可以减少传输延迟,并且更容易实现实时性要求较高的场景。相比之下,HLS 的分片通常较大,因为它需要在每个分片中包含一定量的视频数据,这会增加传输延迟。
播放器缓冲策略:FLV 播放器通常会采用较短的缓冲时间,以确保尽快地播放视频内容。相比之下,HLS 播放器通常会采用较长的缓冲时间,以减少网络波动和丢包带来的影响,这会增加播放延迟。
综上所述,FLV 格式相对于 HLS 格式具有较小的延迟,主要是因为它采用了实时性较高的传输方式,使用了较小的分片大小,并且播放器通常采用较短的缓冲时间。这使得 FLV 格式更适合实时性要求较高的场景,例如直播等。
六、知识拓展
1、什么是webAssembly
- WebAssembly(简称为Wasm)是一种低级的、面向栈式虚拟机的编程语言,旨在提供一种通用的、可移植的、高效的二进制代码格式,以便在 Web 浏览器中运行。它是一种开放标准,被设计用于在 Web 浏览器中实现高性能的 Web 应用程序,同时也可以在其他环境中运行,如服务器端、桌面端等。
- 通过使用 WebAssembly,开发者可以使用 C、C++、Rust 等语言编写高性能的代码,并将其编译为 WebAssembly 模块,然后在 Web 页面中调用这些模块,以实现各种复杂的计算和功能,例如游戏、图像处理、多媒体处理等。
2、什么是ffmpeg
- FFmpeg 是一个开源的跨平台音视频处理工具集,包含了许多用于处理音频、视频和多媒体流的库和工具。
- FFmpeg 提供了一系列命令行工具,可以进行音视频的录制、转码、裁剪、合并、解析、编解码等操作。它支持几乎所有流行的音视频格式,包括但不限于 MP4、AVI、MOV、FLV、MP3、AAC、H.264、H.265 等。因此,它被广泛用于音视频处理、媒体转换、流媒体服务、视频编辑等领域。
3、音视频为什么要编码与解码?
- 更小的体积、更快的传输。
- 以一个分辨率1920x1280,30FPS的视频为例:1920x1280x8x3=49766400bit=6220800byte~6.22MB;30帧/秒:186.6MB;每分钟:11GB;一部90分钟的高清电影,约是1000GB;如果是2K、4K、8K…?
4、视频数据能被大量压缩的原因
- 统计结果表明:在连续的几帧图像中,一般只有 10% 以内的像素有差别,亮度的差值变化不超过 2%,而色度的差值变化只在1%以内。