音视频小记 - 实现 MediaCodec 播放 scrcpy 视频流
我们今天要实现的是安卓通过scrcpy-server显示另一台安卓的屏幕
要实现的是从自定义协议(scrcpy-server)中播放视频,所以直接用现成的视频播放器是不现实的。
前言
在动手写前,从各个博客补了一些知识,几乎每一篇在实现 MediaCodec 解码播放的时候都会说,这个通常和 MediaExtracter 配合使用,然后具体解码就是借助 MediaExtracter 解码到 MediaCodec 中,找了很久没找到纯 MediaCodec 解码播放视频的。
包括搜了一些 stackoverflow ,搜到的是以下代码。https://stackoverflow.com/questions/24622816/android-using-mediaextractor-and-mediacodec-from-a-socket-streaming-mpeg
setDataSource 不再指向路径,而是指向 socket 得到的 fd(文件描述符),看起来很合理的代码。
代码一跑就挂了😕,输出如下。
所以大致得出结论,用 MediaExtracter 估计是行不通了。
问题分析
最后在这两篇博客找到一些有用的信息。mediaext中的readSimpleData显然是有内部的防止粘包的逻辑的,包括advice方法跳到下一帧,就说明这样的方式解码,它就根据视频的协议,例如h264等,知道多长的位置是这一帧的结尾,所以使用MediaCodec+MediaExtracter的时候,我们不用考虑会出现数据流粘包的问题。
粘包解决
解决粘包还是得从协议本身上来,最后在 scrcpt/
-server,发现服务端在发送一帧数据包前,会先发送pts 信息,和包长度,这是很重要的数据。
所以在客户端进行解码这部分的视频流的时候,先取出12个字节,其中8个字节是long类型,为pts时间戳,4个字节是数据包长度,意味着这一帧的长度,只有将这个长度的内容完整读取,才能正常渲染,如果某一次因为缓冲没有读取完,那只有等待下次继续读取,直到读取到数据的尾部。
最后送显,这部分代码在各种各样的轮子中都能看到。
趟了一个非常蠢的坑,将getDecoderByType写成了getEncoderByType,还定位了非常久,crash 的堆栈会跟其它的问题混淆,例如给定size的width或者height为奇数,也是一模一样的crash 堆栈,离谱!
整体流程
服务端
1.启动一台手机的 scrcpy-server
2.转发这台手机的 scrcpy 端口到 PC的:10000端口上
3.等待另一台手机通过 $PC_IP:10000 获取数据流
客户端部分
连接视频流套接字
用上面的$PC_IP:10000
连接控制事件套接字
构造 MediaCodec
MediaFormat 构造并插值
开始解码
我们不再借助 MediaExtracter 往 MediaCodec 喂数据,现在通过从 socket 中读取每帧的数据交给 MediaCodec,这是与普通的视频播放器的差异点。
看下最后实现的效果。
音视频小记 - 实现 MediaCodec 播放 scrcpy 视频流
http://blog.nightmare.press/2021/12/24/音视频小记-实现-MediaCodec-播放-scrcpy-视频流/