客户端设计
本项目为满足多平台监控需求,设计了Linux、Android和网页客户端。其中网页客户端基于JWplayer。Linux与Android客户端需要完成的功能主要有以下几部分:
- 完成与服务器的通信,接收RTMP数据;
- 从获取的FLV数据中提取H264 Nalu单元数据;
- 完成H264的解码,获取YUV数据;
- 将YUV数据通过SDL(Android系统通过Opengl)显示到屏幕上。
客户端的的架构图如图1-1所示。
客户端架构
接收RTMP数据部分由RMTP协议模块负责,在介绍服务器与客户端通信有详细介绍,接收从服务器发送过来的FLV数据。FLV解析模块从FLV中提取Nalu单元,放入共享内存,然后调用FFmepg的解码接口从共享内存中读取H264数据解码为YUV数据,最后将YUV数据通过显示模块显示到屏幕上。
客户端的实现
Linux下播放器的实现
RTMP协议模块的实现
按照Linux客户端的设计流程,首先我们需要完成的是RTMP协议模块。客户端需要用到Librtmp来接收RTMP流数据,函数的执行流程如图1-2所示。
接收数据流程图函数RTMP_Alloc()、RTMP_Init()在RTMP服务器中已经使用过,用来初始化用于RTMP通信的RMTP结构体。接下来的RTMP_Connect()、RTMP_ConnectStream()、RTMP_Read()实现和RTMP服务器建立RTMP流媒体传输的握手、连接、创建流以及播放过程。
FLV解析模块的实现
本项目实现的FLV解析模块设计的初衷是在包含H264数据的FLV流中提取出H264 Nalu数据。在第二章中有介绍过FLV流的结构,但是通过对FLV二进制流的分析后发现,一个video Tag数据中不仅仅包含视频数据,而且还包含访问分割符。因此,在提取Nalu数据时,需要对video tag中不同的信息进行区别。在本模块中,对非SPS、PPS、I帧和P帧的数据进行舍弃。
首先对从RTMP传递过来的FLV Tag数据buf进行分析,如果时Tag数据包含的是视频数据就进行下一步分析,如果不是则舍弃。如果获得的是视频数据,那么对Video Tag的数据部分进行分析,首先根据前两个字符来区分关键帧和非关键帧,再保存Nalu长度,分析Nalu包的前两个字符,如果是单元分割符或者SEI则舍弃,如果是数据帧,则加上Nalu头即0x00000001,然后送入缓冲区,等待解码播放。核心代码如下:
nalu_type=buf[naltail_pos+1]&0xff;
if(nalu_type==1)
{
naltail_pos+=5;
while(naltail_pos<nRead-5)
{
naltail_pos++;
size=(buf[naltail_pos++]&0xff)*65536;
size+=(buf[naltail_pos++]&0xff)*256;
size+=buf[naltail_pos++]&0xff;
nalu_type=buf[naltail_pos]&0xff;
if(nalu_type==9||nalu_type==6)
{
naltail_pos+=size;
continue;
}
body = (char*)malloc(size+4);
memset(body,0,size+4);
i=0;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x01;
memcpy(&body[i],buf+naltail_pos,size);
int error;
error=decode(handler,body,size+4);
free(body);
naltail_pos+=size;
break;
}
}
解码模块的实现
解码模块和编码模块一样,使用到了FFmpeg,和编码流程相类似,本项目的解码流程如图1-3所示。
Ffmpeg解码流程图流程图中关键函数的作用:
- av_register_all():注册所有的编解码器。
- avcodec_find_decoder():查找解码器,这里我们的需要查找的编码器的类型为AV_CODEC_ID_H264。
- avcodec_alloc_context3():为AVCodecContext分配内存。
- avcodec_open2():打开解码器。
- putdata():从缓存区循环提取数据,并调用解码函数进行解码。
- avcodec_decode_video2():解码一帧数据(循环解码中用到的就是这个函数)。
显示模块的实现
Linux客户端的显示模块基于SDL实现,SDL模块主要的功能是将YUV图像显示到屏幕上面,由于是从服务器端接收数据,时间戳不用我们来设置,每接收一帧视频数据就调用解码端和显示端进行解码显示。解码和显示是连接在一起的,每解码得到YUV数据就立即通过FFmpeg函数提供的sws_scale()函数对其进行图像拉伸,以适用于显示,最后调用SDL_UpdateTexture ()函数显示到SDL开辟的View当中。
最后首先运行RTMP服务器,然后运行Linux客户端,播放监控数据,运行如图1-4所示。
网友评论