基于Android4.2系统的H264视频数据的获取
0引言
Andriod系统本身不支持H264视频原始压缩数据(即符合H264压缩标准,具有NAL头的裸数据)的实时获取,基本上都是利用MediaRecorder类的setOutputFile()函数中可以接收文件描述符的特点,使用LocalSocket从MPEG4文件流中获得H264裸数据,实现数据存储或传输。在作者的测试中,该方法存在两个问题:(1)MPEG4文件流中的H264压缩数据因为去掉了NAL头,全靠4字节的帧数据长度来获得一帧数据;当因为某些原因造成长度数据错误,而没有NAL同步头则造成一段时间内不能获得正确的H264原始数据;(2)H264数据的SPS和PPS帧不能简单确定,需要人工填写,容易造成不能解码。本文正是针对该缺点,从Android4.2.2的HAL层面上解决了该问题,并且实现了H264原始数据的文件保存,同时兼容MPEG4的所有处理方式,提高了获取数据的效率和稳定性。
1 Android MediaRecorder介绍
Android的MediaRecorder类从功能的角度包含音频,视频记录,视频预览的功能,从上到下的类调用顺序如下:
MediaRecorder.java
+- android_media_MediaRecorder.cpp
+-MediaRecorder.cpp
+-MediaPlayerService.cpp
+-MediaRecorderClient.cpp
+-StagefrightRecorder.cpp
+- MPEG4Writer.cpp,AACWriter.cpp等
本文利用系统缺省的文件输出格式,即在MediaRecorder类调用时,设置输出方式为MediaRecorder.OutputFormat.DEFAULT即可,通过在StagefrightRecorder类中对缺省编码方式实现特殊处理,并且参照MPEG4Writer类实现了自定义的H264Writer类,从而实现了H264原始数据的文件存储和数据传输功能。
2 StagefrightRecorder类修改
StagefrightRecorder的构造函数中调用reset函数进行了初始化设置。默认OutputFormat为3GPP, AudioEncoder为AMR NB,VideoEncoder为H264。图1为StagefrightRecorder支持输出的文件格式。
图1
本文就是将default选项提取出来,进行单独的处理,如图2所示。
图2
该类源代码所在目录路径如下:
./frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp,代码修改如下:
(1) 添加头文件
#include <media/stagefright/H264Writer.h>
(2) 修改setOutputFormat()函数
屏蔽对OUTPUT_FORMAT_DEFAULT的处理,直接将参数of赋值给mOutputFormat;
(3) 修改start函数
屏蔽系统对OUTPUT_FORMAT_DEFAULT的默认处理,添加单独处理代码:
case OUTPUT_FORMAT_DEFAULT:
startH264Recording();
break;
(4) 仿照setupMPEG4Recording()函数,创建setupH264Recording()函数,将newMPEG4Writer(outputFd)替换为new H264Writer(outputFd);
(5) 仿照startMPEG4Recording()函数,创建startH264Recording()函数;
3 MPEG4Writer类介绍
MPEG4Writer类主要实现将以H264压缩编码获得的H264原始数据,以MPEG4文件格式进行一系列的处理,如填写MPEG4文件头块ftpy等。MPEG4Writer封装好的视频的格式如图3所示:
图3
在该封装格式中,首先填写fypy块,然后填写mdat。H264的原始数据就存放在mdat块中,以4个字节表示一帧数据的长度,一帧数据一帧数据存储的。当停止保存(录像等)动作后,再回填moov等box数据,包含该段视频总帧数,时长等信息。
4构建H264Writer类
H264Writer类主要实现H264编码原始数据的读取,然后不做任何处理,直接存储到Java层传递来的文件描述符的句柄中。
(1) 仿照MPEG4Writer.cpp,MPEG4Writer.h生成相应的H2644Writer.cpp,H2644Writer.h,并且将头文件拷贝到相应的include目录;
(2) 修改start()函数
屏蔽写FtypBox的过程,屏蔽代码从writeFtypBox(param)开始到status_t err =startWriterThread()之前结束;
(3) 修改reset()函数,该函数主要实现moov等各种box块的填写,屏蔽代码从if (mUse32BitOffset)开始到CHECK(mBoxes.empty())之前结束;
(4) 修改Track::threadEntry()函数
A) 屏蔽if(buffer->meta_data()->findInt32(kKeyIsCodecConfig, &isCodecConfig)的整个处理过程;
B) 屏蔽代码从mOwner->trackProgressStatus(mTrackId,-1, err)开始到sendTrackSummary(hasMultipleTracks)结束;
(5) 修改addLengthPrefixedSample()函数,将代码:
uint8_t x = length>> 24;
::write(mFd, &x, 1);
x = (length >> 16) & 0xff;
::write(mFd, &x, 1);
x = (length >> 8) & 0xff;
::write(mFd, &x, 1);
x = length & 0xff;
::write(mFd, &x, 1);
修改为:
uint8_t x = 0x00;
::write(mFd, &x, 1);
::write(mFd, &x, 1);
::write(mFd,&x, 1);
x = 0x01;
::write(mFd,&x, 1);
(6) 修改MPEG4Writer.cpp文件所在目录的Android.mk文件,添加H264Writer.cpp
5编译
编译整个安卓系统,生成新的system.img镜像,更新系统重启后即可在Java层通过设置DEFAULT输出方式,实现H264原始数据的获取(文件保存或数据传输)。
6总结
通过以上的实现方法,作者实现了H264原始数据的文件存储,并且在移植了live555的源代码后,利用管道文件的方式,实现了手机端作为RTSP服务器的功能,从而在其他设备上实现了实时视频流播放功能。