最近实验需要在树莓派上搭建一个简单的视频服务,而且,希望画质一定的情况下,消耗的带宽越少越好。关于带宽的问题,其实开始并没有考虑太多,但是在尝试用uv4l
工具创建 mpeg 流的时候发现,尽管分辨率很低(720p)不到,需要的数据率却达到了大约 5MB/s。我们待测试的通信层不具备这样高的传输传输能力。因此需要想办法把数据率降下来。综上,我们需要产生一个编码后的视频流,如 H264。
幸运的是我发现了h264-live-player这个项目。这个项目是基于 Node.js 的工程,利用 Websocket 传输 H264 编码数据,在客户端用Broadway
解码,而服务端的 H264 流通过raspivid
产生。
在接下来的部分,我先简要介绍一下 Raspivid 的使用,然后介绍一下h264-live-player
的情况。如果只是想上手使用,可以直接拉到最后。
1 Raspivid
raspivid
是一个在树莓派上用于捕捉视频数据的命令行工具。在h264-live-player
中,lib/raspivid.js
文件调用了这个命令来产生 H264 的视频流。在这个文件中使用的命令是:
1 | raspivid -t 0 -o - -w WIDTH -h HEIGHT -fps FPS |
其中,-t 0
表示捕捉的时间不限。-o -
表示将 H264 流输出到stdout
。后面的-w
, -h
, -fps
则分别是制定画面的宽高还有帧率。在raspivid
命令产生 H264 流后,h264-live-player
会通过一系列的回调函数通过 Websocket 将 H264 数据发送给前端。
2 h264-live-player 关键代码解析
注意,原作者的工程里面存在一些问题,其中重点是客户端刷新后视频流解析会出现异常。我在我的 fork 中修复了这些问题,还做了一些其他的改进。因此这里的介绍都以我的 fork 中的代码为准。
2.1 后端
首先还是要看lib/raspivid.js
这个文件。RpiServer
这个类继承于Server
,Server
中预留了get_feed
给子类实现,器作用是产生视频流。
1 | get_feed() { |
这个函数返回的是raspivid
子进程的stdout
流,也即 H264 流。
然后我们来看lib/_server.js
文件中_Server
的定义。注意start_feed
这个函数:
1 | start_feed() { |
这个函数在客户端发起播放流的请求后调用。这里Server
调用子类实现的get_feed
函数获取视频流,然后视频流上注册data
事件的回调函数。
这里需要解释一下
readStream = readStream.pipe(new Splitter(NALseparator));
这行代码。这里我们为视频流增加了一个Splitter
,生成Splitter
的参数为一个Buffer
。
1 const NALseparator = new Buffer([0, 0, 0, 1]); //NAL break
1 | > |
这里的代码非常简单,核心就是通过socket.send
将数据发送给客户端。注意这里的数据的内容是Buffer.concat([NALseperator, data])
。这是因为Splitter
会截断分隔符。
2.2 前端
前端的代码集中在vendor/wsavc/index.js
中。重点是下面这段代码:
1 | var framesList = []; |
在接收到服务器发送的数据时,数据会被转换成Uint8Array
,然后压入到一个队列中。而在shiftFrame
这个函数会周期性的调用,从队列中取出数据进行解码。解码后会触发Broadway
解码器的onPictureDecoded
回调,在这个回调中canvas
中的图像会被更新。
3 h264-live-player 的部署和使用
3.1 安装 Node.js 到树莓派
SSH 登录到树莓派,然后运行
1 | sudo apt-get update |
使用下面的命令来验证安装成功:
1 | node -v |
3.2 安装 h264-live-player
1 | 下载仓库 |
3.3 运行
1 | cd player |
上面的运行方法会在 terminal 中启动服务脚本。如果要这个程序常驻后台,可以尝试使用pm2
1 | sudo npm install -g pm2 # 安装pm2,这里的-g表示安装到全局环境下 |
3.4 在网页端访问摄像头
1 | http://rasp_ip:8080 |
可以通过添加/?r
的 query 参数来上下翻转画面。