1.视频采集与编码模块设计
本工程是基于Linux系统实现,在Linux中,读取摄像头数据需要用到V4L2 API接口,从摄像头获取的原始数据是YUV422格式,而利用Flash播放视频数据时,需要最终解码为YUV420格式,所以将原始的YUV422数据映射到用户缓冲区后,需要通过转换函数将YUV422转换为YUV420数据。再通过FFmpeg提供的通用编码接口调用X264开源库,将YUV420数据编码压缩为H264数据。
视频采集与编码模块主要功能有:
(1)采集usb摄像头数据,格式为yuv422;
(2)将yuv422数据转为yuv420,将获取的yuv420数据通过目标检测与跟踪模块进行处理。
(3)调用x264库对其进行编码(如果服务器采用的是基于云平台的RTMP服务器,会将编码后的数据直接发送给服务器);
视频采集与编码模块如图1-1所示。
2.H264压缩编码过程及实现
本工程使用的是软编码方式对YUV数据进行编码,基于X264开源库,在Linux和Android系统下,可以编译包含了X264库的FFmpeg的动态库。最后基于FFmpeg的提供的编码API将YUV420数据编码为H264数据。使用FFmpeg库可以为用户提供基于不同编码库的相同编码API,便与统一管理以及以后对编码技术的升级,Hevc编码技术的出现,为4K传输的实现提供可能,在不久的将来,当编码技术与硬件条件达标之后,我们可以方便的为工程升级到Hevc编码技术。
FFmpeg编码视频的流程如图2-1所示。
使用FFmpeg获取我们所需的格式,首先通过av_register_all()函数向系统注册FFmpeg所支持的所有编码库,将FFmpeg所支持的所有编码格式注册到工程环境当中,然后我们通过avcodec_find_encoder()函数来寻找选定我们所需要的编码格式,接下来为编码前和编码后数据分配一些数据的缓存空间,最后启动采集编码,实时的编码获取的视频数据,将编码后的数据放到缓存中。下面介绍FFmpeg解码流程中需要用到的函数。
av_register_all():该函数是ffmpeg注册复用器、编码器等的函数。
avformat_alloc_output_context2():该函数为结构体AVFormatContext进行初始化,这个结构体主要记录集采到的视频文件的信息参数,主要有文件格式、视音频流的个数、视音频流、时长等。
avio_open():打开输出文件,可以是本地文件,也可以输出的URL地址。
av_new_stream():创建输出码流的AVStream,可以是视频流,或者是音频流。
avcodec_find_encoder():根据事先设置好的编码格式来查找编码器。我们选择的是H264编码器,因此编码器选择为AV_CODEC_ID_H264。
avcodec_open2():打开上一个函数所查找的编码器。
avformat_write_header():带文件头的封装格式需要调用这个函数写文件头,主要作用是将指定格式文件的一些相关信息写入到文件当中。
avcodec_encode_video2():编码一帧视频。将从V4L2采集到的AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。
av_write_frame():将编码后的数据写入发送文件。
flush_encoder():用于输出编码器中剩余的AVPacket。
av_write_trailer():写文件尾。
调用read_and_encode_frame()函数,将从V4L2模块取得的YUV数据保存在Camera结构体内y420p_buffer指针指向的内存中,此时可以调用智能跟踪算法对目标进行跟踪。接下来将获取的YUV数据送入FFmepg函数avcodec_encode_video2()中,会将编码好H264数据Nalu的结构体保存在cam->encode.pkt中,实现编码的核心代码为:
int encode(Camera*cam)
{
int got_output,ret;
unsigned char *yuv420p;
int y_size;
cam->encode.pkt.data = NULL;/cam->encode.pkt.size = 0;
//Read raw YUV data
read_and_encode_frame(cam);
y_size = cam->encode.pCodecCtx->width * cam->encode.pCodecCtx->height;
yuv420p=cam->y420p_buffer;
cam->encode.pFrame->data[0]=yuv420p;
cam->encode.pFrame->data[1]=yuv420p+y_size;
cam->encode.pFrame->data[2]=yuv420p+5*y_size/4;
cam->encode.pFrame->pts = cam->encode.framecnt;
// encode the image
ret = avcodec_encode_video2(cam->encode.pCodecCtx, &cam->encode.pkt, cam->encode.pFrame, &got_output);
free(cam->y420p_buffer);
if (ret < 0)
{
printf("Error encoding frame\n");
return -1;
}
return 0;
}
此时可以获取已经编码好的H264数据,为了适用与RTMP传输,每一帧YUV数据编译为H264数据使用了单线程,所以得到的每一个Nalu含有完整的一副图像,便与接收与显示。
至此,完成了支持轻量级RTMP多媒体视频服务器的采集编码端的编写。