Android USB 摄像头 RTMP 推流

对这一段时间来,一直调查的方向做个总结。

UVC Camera

Android 设备要连接 USB 外置摄像头,需要对接 UVC 协议,这方面,大多数推荐的库为 saki4510t/UVCCamera。绝大多数的第三方库都基于此库进行修改、完善和封装。

百度百科:UVC全称为 USB Video Class,即:USB视频类,是一种为USB视频捕获设备定义的协议标准。是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,已成为USB org标准之一。

RTMP

通用的流程一般就是调用 Camera/Camera2 以及麦克风进行音视频采集之后合成推流 1

Android 上支持流媒体解析或推流的库主要有以下几个:

  • yasea
    • 纯 Java 编译
    • 不支持 UVC
  • EasyRTMP
    • 平台支持:Windows、Linux、ARM(各种交叉编译工具链)、Android、iOS
    • 支持 UVC
    • 收费
  • rtmp-rtsp-stream-client-java
    • 支持 RTMP、RTSP
    • Java 编写
    • 不支持 UVC
  • Native - 通过 JNI 调用 C++,不支持 UVC
    • librtmp/RTMPDump,参考 1
    • ffmpeg,参考 2
  • AndroidUSBCamera
    • 基于 saki4510t
    • USB 摄像头 (UVC) 视频录制和图片抓拍,支持边推流边录像
    • 需手动适配设备本身的 Camera
  • JavaCV
    • 用 JNI 封装了OpenCV,FFmpeg,libdc1394,PGR FlyCapture,OpenKinect,librealsense,CL PS3 Eye Driverx,videoInput,ARToolKitPlus以及flandmark 等的接口,从而为 Java 操作提供便捷方式
    • 全平台支持 RTMP、RTSP
    • 因为有 opencv,所以可以做图像处理方面的工作
    • 不支持 UVC
  • 其他SDK

所以,综上考虑一下,采用 AndroidUSBCamera+JavaCV是一个很好的方式,一个负责画面展示,一个负责音频以及数据解析:
本身 AndroidUSBCamera 提供预览接口,所以我们可以设置变量在录制时通过 setOnPreviewFrameListener 处理传来的视频 nv21 信息;而 JavaCV 则可以处理音频数据,然后借助 ffmpeg 合成推流。主要参考就是示例工程中的 RecordActivity 3

AndroidUSBCamera

这个 saki4510t UVCCamera 的依赖库最好还是直接下载 aarjar 更快一些,也避免一些编译错误。

JavaCV

因为是全平台的,所以最好删减一下,但是在使用过程中,会爆出 couldn't find "libjniavutil.so" 5 的错误,我这边的解决方式为:

1
2
3
4
5
6
7
8
9
ndk {
abiFilters 'armeabi-v7a'
}

compile group: 'org.bytedeco', name: 'javacv', version: '1.4.4'
compile group: 'org.bytedeco.javacpp-presets', name: 'opencv', version: '4.0.1-1.4.4', classifier: 'android-arm'
compile group: 'org.bytedeco.javacpp-presets', name: 'opencv', version: '4.0.1-1.4.4', classifier: 'android-arm64'
compile group: 'org.bytedeco.javacpp-presets', name: 'ffmpeg', version: '4.1-1.4.4', classifier: 'android-arm'
compile group: 'org.bytedeco.javacpp-presets', name: 'ffmpeg', version: '4.1-1.4.4', classifier: 'android-arm64'

测试服务器

优化

这里需要对 ffmpeg 的参数有所了解,通过一些控制参数来降低推流的延迟7

  • 关闭 sync-lookahead
  • 降低 rc-lookahead,但别小于 10,默认是 -1
  • 降低 threads(比如从 12 降到 6)
  • 禁用 rc-lookahead
  • 禁用 b-frames
  • 缩小 GOP
  • 开启 x264 的 -preset fast/faster/verfast/superfast/ultrafast 参数
  • 使用 -tune zerolatency 参数

具体到 JavaCV 代码中9

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// FFmpegFrameRecorder(String filename, int imageWidth, int imageHeight,
// int audioChannels) fileName可以是本地文件(会自动创建),也可以是RTMP路径(发布到流媒体服务器)
// imageWidth = width (为捕获器设置宽) imageHeight = height (为捕获器设置高)
// audioChannels = 2(立体声);1(单声道);0(无音频)
recorder = new FFmpegFrameRecorder(ffmpeg_link, imageWidth, imageHeight, 1);

// 该参数用于降低延迟 参考FFMPEG官方文档:https://trac.ffmpeg.org/wiki/StreamingGuide
// 官方原文参考:ffmpeg -f dshow -i video="Virtual-Camera" -vcodec libx264
// -tune zerolatency -b 900k -f mpegts udp://10.1.0.102:1234
recorder.setVideoOption("tune", "zerolatency");

// 权衡quality(视频质量)和encode speed(编码速度) values(值):
// ultrafast(终极快),superfast(超级快), veryfast(非常快), faster(很快), fast(快),
// medium(中等), slow(慢), slower(很慢), veryslow(非常慢)
// ultrafast(终极快)提供最少的压缩(低编码器CPU)和最大的视频流大小;而veryslow(非常慢)提供最佳的压缩(高编码器CPU)的同时降低视频流的大小
// 参考:https://trac.ffmpeg.org/wiki/Encode/H.264 官方原文参考:-preset ultrafast
// as the name implies provides for the fastest possible encoding. If
// some tradeoff between quality and encode speed, go for the speed.
// This might be needed if you are going to be transcoding multiple
// streams on one machine.
recorder.setVideoOption("preset", "ultrafast");

// 参考转流命令: ffmpeg
// -i'udp://localhost:5000?fifo_size=1000000&overrun_nonfatal=1' -crf 30
// -preset ultrafast -acodec aac -strict experimental -ar 44100 -ac
// 2-b:a 96k -vcodec libx264 -r 25 -b:v 500k -f flv 'rtmp://wowza
// serverIP/live/cam0' -crf 30
// -设置内容速率因子,这是一个x264的动态比特率参数,它能够在复杂场景下(使用不同比特率,即可变比特率)保持视频质量;
// 可以设置更低的质量(quality)和比特率(bit rate),参考Encode/H.264 -preset ultrafast
// -参考上面preset参数,与视频压缩率(视频大小)和速度有关,需要根据情况平衡两大点:压缩率(视频大小),编/解码速度 -acodec
// aac -设置音频编/解码器 (内部AAC编码) -strict experimental
// -允许使用一些实验的编解码器(比如上面的内部AAC属于实验编解码器) -ar 44100 设置音频采样率(audio sample
// rate) -ac 2 指定双通道音频(即立体声) -b:a 96k 设置音频比特率(bit rate) -vcodec libx264
// 设置视频编解码器(codec) -r 25 -设置帧率(frame rate) -b:v 500k -设置视频比特率(bit
// rate),比特率越高视频越清晰,视频体积也会变大,需要根据实际选择合理范围 -f flv
// -提供输出流封装格式(rtmp协议只支持flv封装格式) 'rtmp://FMS server
// IP/live/cam0'-流媒体服务器地址
recorder.setVideoOption("crf", "25");

// 2000 kb/s, 720P视频的合理比特率范围
recorder.setVideoBitrate(2000000);

// 音频比特率
//recorder.setAudioBitrate(192000);

// 最高质量
//recorder.setAudioQuality(0);
recorder.setSampleRate(sampleAudioRateInHz);

// 双通道(立体声) -- 注意:立体声和单声道处理方法不一样
// recorder.setAudioChannels(2);

结语

从前年直播答题开始的预热,到去年抖音、快手的直播、连麦、短视频、KOL 带货等的推动,加之现在疫情期间在线授课、远程工作的加持,借助 4/5G 网络,这算是当前一直很火热的领域了;以前觉得集成集成一个就行,而现在用户经过培育之后,体验方面要求也越来越高,稳定性、安全性更不用说了,真是需要积累的。这也算是一种倒逼吧。
经过这段时间的摸索,感觉流媒体技术方面是个很大的“坑”——网上的资料大部分都是初级的入门教程,却没有一个深入的讲解,绝大部分不能用于生产环境。除去说的资源和教程少,单单拿出的细分领域也多,而协议更多,比如 RTMP、RTSP、HLS、webRTC……以及还要对各家的摄像头和 NVR 的对接要做的了然于胸。深深感觉到自己的菜,自研的话技术壁垒确实高,入门曲线特别抖,核心代码一般都是 C++,而且还要交叉编译到各个平台,😵。而借助开源库虽然可以实现一些功能,但是要改动的话还是要对一些库做功课,这个不是一朝一夕的功夫,如果有个带头大哥真是再好不过。

参考

-------------本文结束感谢您的阅读-------------