0x1 客户端总体框架
传统的直播客户端只能从流媒体服务器下载流媒体数据,RsPlayer作为一个P2P(peer to peer)的直播客户端,可以同时从流媒体服务器和其他客户端请求媒体数据,比传统的直播客户端相比可以节省流媒体服务器的带宽.
下图显示了客户端的框架图.
最上层是UI层,提供用户的交互操作,用来显示播放视频窗口.
然后是P2PCore层,该层采用P2P方式接收和发送媒体数据.
P2PCore的下面是network层,用来处理socket接收和发送.
ffmpeg层用来处理媒体数据,音视频分离,音视频解码,音视频输出等.
0x2 P2PCore模块
如下图所示,p2pcore模块需要和三类服务器进行通讯,先从web server得到需要播放的流媒体链接,然后从tracker得到super peer的相关信息,然后再连接到super peer下载所需要的直播流数据,同时也会从其他p2pcore处下载直播流数据.
下面会详细介绍一下和这三类服务器的交互,也会介绍一下和其他p2pcore的交互过程.
与web服务器交互
RsStreamer把p2p直播流链接存储在web服务器上,RsPlayer读取存储在web服务器上的p2p直播流链接,然后解析该链接,从中读取tracker服务器的地址,然后建立和tracker服务器的tcp连接.
与tracker服务器交互
p2pcore发送登录消息到tracker服务器.
p2pcore把p2p直播流链接(从web服务器下载得到)解析得到的直播流信息发送给tracker服务器.
tracker服务器返回消息给p2pcore,告诉将要播放的直播流对应的super peer地址信息,同时也告诉播放同一直播流的其他p2pcore的地址信息.
p2pcore得到super peer地址以后,请求与其建立连接,并请求其发送相应的媒体数据.
p2pcore得到其他p2pcore地址以后也会请求与其建立相应的连接.
p2pcore还会定期向tracker服务器请求直播流的相关信息, 如当前播放的数据包的编号.
tracker服务器发送信息告诉p2pcore当前直播播放的数据包的编号.
与super peer交互
p2pcore建立和super peer的连接以后,把需要播放的直播流资源的信息发送给super peer, 并把直播流的数据包编号发送给super peer.
super peer然后把需要播放的直播流的头文件信息发送给p2p client, 然后根据其请求的资源编号发送直播流数据包.
上面提到了直播流的数据包编号,这个编号用来标记直播数据包,可以在p2p服务器和客户端之间统一管理直播数据. 如客户端需要哪个数据包,只需要把编号发送给服务器或其他客户端.
与其他p2pcore交互
这部分是体现RsPlayer直播客户端中p2p的部分,RsPlayer的p2pcore模块需要分析哪些数据需要从super peer得到,哪些数据需要从其他p2pcore处得到。
p2pcore建立和其他p2pcore的连接以后,需要查询其他p2pcore上有哪些数据包,其他p2pcore把其拥有的数据包信息发送过来以后,RsPlayer经过分析,决定需要从其接收哪些数据包,然后把需要的数据包信息发送给其他p2pcore, 其他p2pcore根据其请求的数据包编号发送相应的媒体数据包.
0x3 多媒体解码模块
p2pcore模块中的接收线程接收网络码流放到buffer中,ffmpeg中的解码线程从buffer中取出码流并解码输出.
ffmpeg介绍
ffmpeg是一套开源的音视频处理库,包括文件格式处理库libavformat, 音视频编解码库libavcodec等,,目前各大视频网站提供的应用程序中都集成了ffmpeg.
p2pcore模块与ffmpeg模块的交互
修改ffmpeg中libavformat的代码,在ffmpeg中添加buffer protocol的输入接口,通过调用buffer protocol的输入接口,把外部读取buffer的函数指针作为参数传入到ffmpeg中,ffmpeg的解码线程需要数据的时候就调用这个函数指针,从而读取到需要的码流数据.
ffmpeg中添加buffer protocol的输入接口代码如下.
如何调用这个buffer protocol呢,我们的播放采用ffplay的流程,把按如下所示生成的mediafeeder_str作为ffplay的参数即可,mediafeeder_context是下面提到的类mediadatafeeder的对象指针,mediafeeder_handle是下面提到的类mediadatafeeder成员函数readdata()指针. ffplay先解析mediafeeder_str的内容,根据buff关键字匹配到buffer protocol的输入接口, buffer protocol的输入接口然后来调用mediadatafeeder::readdata().
前面提到ffmpeg解码的时候会调用buffer protocol中的buff_read()函数,然后再调用p2pcore中的mediadatafeeder::readdata ()函数. 然后再调用livebuffermgr::getblock()读取到码流.
下面是在p2pcore中实现buffer读取的函数.
p2pcore中的buffer管理如下.
getblock()接口供上面的readdata函数调用, 相当于ffmpeg线程中调用该函数.
putblock()会被p2pcore中的网络接收线程调用.
由于livebuffermgr的getblock()和putblock()会被不同的线程调用,所以里面的buffer数据访问需要加锁以避免多线程之间的数据访问冲突.
0x4 在Android平台上的实现
在Android平台上实现RsPlayer客户端的话,p2pcore和ffmpeg都封装成相应的so库, 需要设置Android NDK开发环境, 采用Android Studio来开发.