最近学习了视频硬编解码,觉得这种视频传输的方式还挺不错的,写了个使用wifi点对点投屏传输demo,支持h264、h265。分享一下。
代码
https://gitee.com/flying-guy/practical-demo/tree/master/VideoTest
流程

视频传输流程图
具体步骤如下:
1.申请权限
android.permission.CAMERA
android.permission.INTERNET
android.permission.RECORD_AUDIO
2.创建工具类
权限申请成功以后,创建视频截屏工具MediaProjection、视频传输工具类SocketLive
3.开启服务端
开启服务端WebSocketServer等待客户端连接
4.解码准备
创建解码工具类CodeLiveH264、配置MediaFormat、MediaCodec、将MediaCodec与MediaProjection绑定,使用MediaProjection采集好的数据进行解码,因为MediaProjection的数据都是采集好的,不用再编码了,直接拿来解码。
5.解码
需要将通过MediaCodec拿到的数据解码成一帧一帧的,传到客户端后,客户端能够解码播放。
6.服务端发送数据
视频解码播放需要sps(基础配置帧)和pps(全量配置帧),编码好的数据只有视频开始时才会有一帧sps、pps,因此需要将sps和pps缓存,等客户端连上的时候先把spspps配置帧添加到I帧后面,这样每一个I帧都是完整的可以独立解析显示出画面的帧了,通过WebSocket发过去,再发后续解码出来的视频帧。
7.客户端接受数据
客户端通过WebSocketClient的onMessage接口接收到ByteBuffer
8.客户端展示数据
将接受到的数据渲染到Surface上进行展示,服务端一直发送数据,客户端就会一直显示。
效果图

点对点视频传输效果图
效果图就是可以在客户端看到服务端的屏幕。
- 如果将服务端的投屏的SurfaceView改成后置摄像头预览画面,这样就是传的服务端录的像啦
- 也可以直接把MediaProjection采集数据改成使用摄像头采集数据然后再自己编码一下,效果一样的。
- 把服务端和客户端的MediaCodec的类型配置成"video/hevc",配置帧改成VPS就可以实现h265高清录屏传输了。
- 使用WebSocketClient接受/发送数据,使用WebSocketServer发送/接受数据就可以完成视频通话啦,如下图所示。


点对点双向视频传输效果图
核心代码
/**
* 处理帧
* sps+pps I帧
*
* @param byteBuffer
* @param bufferInfo
*/
private void dealFrame(ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo) {
int offset = 4;
if (byteBuffer.get(2) == 0x01) {
offset = 3;
}
int type = (byteBuffer.get(offset) & 0x1F);
if (type == NAL_SPS) { //收到了sps,sps只会输出一份
Log.d(TAG, "dealFrame: sps & pps");
sps_pps_buf = new byte[bufferInfo.size];
byteBuffer.get(sps_pps_buf); //将sps和pps缓存到数组
} else if (type == NAL_I) { //I帧
Log.d(TAG, "dealFrame: I frame");
final byte[] bytes = new byte[bufferInfo.size];
byteBuffer.get(bytes); //45459 I帧数据
byte[] newBuf = new byte[sps_pps_buf.length + bytes.length];
//将sps和pps拷贝到容器newBuf前面,从0开始,拷贝sps_pps_buf.length这么长
System.arraycopy(sps_pps_buf, 0, newBuf, 0, sps_pps_buf.length);
//将bytes I帧数据拷贝到新容器newBuf后面,从sps_pps_buf.length开始,拷贝bytes.length这么长
System.arraycopy(bytes,0,newBuf,sps_pps_buf.length,bytes.length);
socketLive.sendData(newBuf);
} else {
final byte[] bytes = new byte[bufferInfo.size];
byteBuffer.get(bytes);
socketLive.sendData(bytes);
Log.d(TAG, "dealFrame: 视频数据 " + Arrays.toString(bytes));
}
}
服务端解码核心代码
视频解码播放需要sps(基础配置帧)和pps(全量配置帧),配置帧里面携带了该视频的宽高、显示格式、纠错单元、优先级顺序、策略信息、编码等级等基础信息,相当于该视频的个人信息。编码好的数据只有视频开始时才会有一帧sps、pps,因此需要将sps和pps缓存,把配置帧怼到I帧前面才能让客户端解码成功.
原理

视频数据图

一个完整的NALU单元结构图
抓个编码好的视频数据看看,视频的开始就是一个spspps配置帧,且只出现一次。(如果是yuv原始数据,就会有多个,因为怕spspps丢失导致视频播放失败,不过使用yuv数据也需要自己记忆spspps,不然丢失以后需要等到下一个spspps的出现视频才能恢复播放)
| 帧 | NALU Type | 包含 | 作用 |
| sps(全量配置帧) | 67 | 纠错单元 优先级顺序 策略信息 编码等级 | 记录了编码的 Profile、level、图像宽高等 |
| pps(基础配置帧) | 68 | 显示格式(yuv420、421...) 视频宽高 | 每一帧编码后数据所依赖的参数保存于 PPS 中 |
| I帧 | 65 | 视频数据 | 每一帧编码好的数据 |
优点
代码简单、使用方便、能完成基础的视频传输工作、与其他视频传输协议原理相似、好理解、核心数据被编码成byte
待改进的地方
功能简单、只适用于安卓端点对点、没有做安卓版本兼容、没有声音(因为声音是ADTS,待补充)、wifi问题(连着几分钟自己就断了、距离远连不上),可以重连,不影响客户端观看效果
视频传输建议
若功能复杂,可使用RTMP协议,webRTC进行视频传输,OBS进行录屏


4746

被折叠的 条评论
为什么被折叠?



