ffmpeg开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)
来源:互联网 发布:知画的孩子叫什么 编辑:程序博客网 时间:2024/06/04 19:15
ffmpeg开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)
(原文链接:http://blog.csdn.net/andrexpert/article/details/72523408)
1. AAC编码格式分析
(1) AAC简介
高级音频编码(AdvancedAudio Coding,AAC)一种基于MPEG-4的音频编码技术,它由杜比实验室、AT&T等公司共同研发,目的是替换MP3编码方式。作为一种高压缩比的音频压缩算法,AAC的数据压缩比约为18:1,压缩后的音质可以同未压缩的CD音质相媲美。因此,相对于MP3、WMA等音频编码标准来说,在相同质量下码率更低,有效地节约了传输带宽,被广泛得应用于互联网流媒体、IPTV等领域(低码率,高音质)。主要有以下特点:
a) 比特率:AAC- 最高512kbps(双声道时)/MP3- 32~320kbps
b) 采样率:AAC- 最高96kHz / MP3 - 最高48kHz
c) 声道数:AAC– 最高48个全音域声道/MP3 - 两声道
d) 采样精度:AAC- 最高32bit / MP3 - 最高16bit
AAC的不足之处是,它属于有损压缩的格式,相对于APE和FLAC等主流无损压缩,音色“饱满度”差距比较大。另外,除了流媒体网络传输,其所能支持的设备较少。
(2) AAC编码封装格式
音频数据在压缩编码之前,要先进行采样与量化,以样值的形式存在。音频压缩编码的输出码流,以音频帧的形式存在。每个音频帧包含若干个音频采样的压缩数据,AAC的一个音频帧包含960或1024个样值,这些压缩编码后的音频帧称为原始数据块(RawData Block),由于原始数据块以帧的形式存在,即简称为原始帧。原始帧是可变的,如果对原始帧进行ADTS的封装,得到的原始帧为ADTS帧;如果对原始帧进行ADIF封装,得到的原始帧为ADIF帧。它们的区别如下:
a) ADIF:AudioData Interchange Format,音频数据交换格式。这种格式明确解码必须在明确定义的音频数据流的开始处进行,常用于磁盘文件中;
b) ADTS:AudioData Transport Stream,音频数据传输流。这种格式的特点是它一个有同步字的比特流,且允许在音频数据流的任意帧解码,也就是说,它每一帧都有信息头。
ADTS封装格式的码流以帧为单位,一个ADTS帧由帧头、帧净荷组成。其中,帧头定义了音频采样率、音频声道数、帧长度等关键信息,它由两部分组成,共占7个字节:固定头信息adts_fixed_header、可变头信息adts_variable_header。固定头信息中的数据每一帧都相同,而可变头信息则在帧与帧之间可变;帧净荷主要由1~4个原始帧组成,它包含的数据用于解析与解码。
a) 固定信息头
说明:
*syncword:同步头,表示一个ADTS帧的开始,固定值11111111 1111(0xFFF);
*ID:表示MPEG的版本,0为MPGE-4,0为MPGE-2;
*Layer:默认”00”;
*profile:表示使用哪个级别的AAC,值00、01、10分别对应Mainprofile、LC、SSR;
*sampling_frequency_index:表示使用的采样率下标,通过这个下标在Sampling Frequencies[ ]数组中查找得知采样率的值,如1011,对应的采样率为8000Hz;
*channel_configuration:表示声道数
注:有些芯片只支持AAC LC
(b)可变信息头
说明:
*frame_length:一个ADTS帧的长度包括ADTS头和AAC原始流
*adts_buffer_fullness:0x7FF说明是码率可变的码流
(3) 将AAC打包成ADTS格式
通过上述对ADTS封装格式的了解,我们只需要获得相关的音频采样率、声道数、元数据长度、AAC格式类型等信息,就可以在每个ACC原始流前面添加ADTS头。以下是ffmpeg中添加ADTS信息头核心代码:
2. MP4封装格式分析
由于MP4格式较为复杂,本文只对其做个简单的介绍。MP4封装格式是基于QuickTime容器格式定义,媒体描述与媒体数据分开,目前被广泛应用于封装h.263视频和AAC音频,是高清视频/HDV的代表。MP4文件中所有数据都封装在box中(d对应QuickTime中的atom),即MP4文件是由若干个box组成,每个box有长度和类型,每个box中还可以包含另外的子box。box的基本结构如下:
其中,size指明了整个box所占用的大小,包括header部分。如果box很大(例如存放具体视频数据的mdatbox),超过了uint32的最大数值,size就被设置为1,并用接下来的8位uint64来存放大小。通常,一个MP4文件由若干box组成,常见的mp4文件结构:
一般来说,解析媒体文件,最关心的部分是视频文件的宽高、时长、码率、编码格式、帧列表、关键帧列表,以及所对应的时戳和在文件中的位置,这些信息,在mp4中,是以特定的算法分开存放在stblbox下属的几个box中的,需要解析stbl下面所有的box,来还原媒体信息。下表是对于以上几个重要的box存放信息的说明:
3. 将H.264和AAC封装成MP4文件
为了深入的理解H.264、AAC编码格式,接下来我们将通过AndroidAPI中提供的MediaCodec和MediaMuxer实现对硬件采集的YUV格式视频数据和PCM格式音频数据进行压缩编码,并将编码好的数据封装成MP4格式文件。MediaCodec被引入于Android4.1,它能够访问系统底层的硬件编码器,我们可以通过指定MIME类型指定相应编码器,来实现对采集音、视频进行编解码;MediaMuxer是一个混合器,它能够将H.264视频流和ACC音频流混合封装成一个MP4文件,也可以只输入H.264视频流。
(1) 将YUV视频数据编码为H.264
首先,创建并配置一个MediaCodec对象,通过指定该对象MIME类型为"video/avc",将其映射到底层的H.264硬件编码器。然后再调用MediaCodec的configure方法来对编码器进行配置,比如指定视频编码器的码率、帧率、颜色格式等信息。
其次,每个编译器都拥有多个输入、输出缓存区,当API<=20时,可以通过getInputBuffers()和getOutputBuffers()方法来获得编码器拥有的所有输入/输出缓存区。当通过MediaCodec的start()方法启动编码器后,APP此时并没有获取所需的输入、输出缓冲区,还需要调用MediaCodec的dequeueInputBuffer(long)和dequeueOutputBuffer(MediaCodec.BufferInfo,long)来对APP和缓存区进行绑定,然后返回与输入/输出缓存区对应的句柄。APP一旦拥有了可用的输入缓存区,就可以将有效的数据流填充到缓存区中,并通过MediaCodec的queueInputBuffer(int,int,int,long,int)方法将数据流(块)提交到编码器中自动进行编码处理。
原始数据流被编码处理后,编码好的数据会保存到被APP绑定的输出缓存区,通过调用MediaCodec的dequeueOutputBuffer(MediaCodec.BufferInfo,long)实现。当输出缓存区的数据被处理完毕后(比如推流、混合成MP4),就可以调用MediaCodec的releaseOutputBuffer(int,boolean)方法将输出缓存区还给编码器。
这里有几点需要说明下,因为如果处理不当,可能会导致MediaMuxer合成MP4文件失败或者录制的MP4文件播放时开始会出现大量马赛克或者音视频不同步异常。
a) 如何保证音、视频同步?
要保证录制的MP4文件能够音视频同步,需要做到两点:其一当我们获得输出缓存区的句柄outputBufferIndex等于MediaCodec.INFO_OUTPUT_FORMAT_CHANGED,需要将视频轨道(MediaFormat)设置给MediaMuxer,同时只有在确定音频轨道也被添加后,才能启动MediaMuxer混合器;其二就是传入MediaCodec的queueInputBuffer中PTUs时间参数应该是单调递增的,比如:
b) 录制的MP4文件播放的前几帧有马赛克?
出现马赛克的原因主要是因为MP4文件的第一帧不是关键帧(I帧),根据H.264编码原理可以知道,H.264码流的一个序列是由SPS、PPS、关键帧、B帧、P帧…构造,而B帧、P帧是预测帧,承载的图像信息是不全的,所以一帧图像没有信息的部分就会出现马赛克。为此,我们可以使用丢帧策略来处理,即如果是普通帧就丢弃,只有在关键帧已经插入的情况下才开始插普通帧。需要注意的是,由于MediaMuxer不需要SPS、PPS,如果当遇到SPS、PPS帧时忽略即可。
MediaMuxer报stop muxer failed异常通常是由于没有正确插入同步帧(关键帧)所引起的
d) 录制的视频画面出行花屏、叠影
对YUV数据进行编码出现花屏或叠影情况,是由于Camera采集YUV图像帧颜色空间与MediaCodec编码器所需输入的颜色空间不同所导致的,也就是说Camera支持的颜色空间为YV12(YUV4:2:0planar)和NV21(YUV4:2:0 semi-planar),而MediaCodec编码器支持的颜色空间则为COLOR_FormatYUV420Planar(I420)、COLOR_FormatYUV420SemiPlanar (NV12)等格式,不同的Android设备的编码器所支持的颜色空间会有所不同,其中I420颜色格式(YYYYUU VV)与YV12(YYYY VV UU)数据结构相似,是一种标准的YUV420颜色格式。
(2) 将PCM音频数据编码为AAC
由于使用MediaCodec编码音视频的原理是一致的,这里就不做过多介绍,相关音频参数配置,可参照我这篇博文。另外,这里是使用AudioRecord来获得PCM音频流,也比较简单,详情可参考这篇博文。代码如下:
MediaCodec编码核心与视频相似,由于MediaMuxer不需要ADTS信息头,这里就没有在每桢数据添加信息头如果是使用AAC数据来进行推流,这就需要为每桢音频数据添加ADTS头。参考ADTS头信息格式,以及ffmpeg函数中的相关设置,在Java中ADTS信息头配置信息可为:
其中,packetLen为原始帧数据长度,mSamplingRateIndex为自定义采样率数组下标;
(3)使用MediaMuxer混合H.264+AAC生成MP4文件
MediaMuxer的使用比较简单,但需要严格按照以下三个步骤进行:
第一步:配置混合器音、视频轨道
第二步:音、视频轨道均添加,启动混合器
第三步:添加音视频数据到混合器
效果演示:
3. 开源项目:AndroidRecordMp4
1. 添加依赖
(1) 在工程build.gradle中添加
allprojects {repositories {...maven { url 'https://jitpack.io' }}}
(2) 在module的gradle中添加
dependencies { compile 'com.github.jiangdongguo:AndroidRecordMp4:v1.0.0'}
2. 使用方法
(1) 初始化引擎
RecordMp4 mRecMp4 = RecordMp4.getRecordMp4Instance(); mRecMp4.init(this); // 上下文
(2) 配置编码参数
EncoderParams mParams = new EncoderParams(); mParams.setVideoPath(RecordMp4.ROOT_PATH+ File.separator + System.currentTimeMillis() + ".mp4"); // 视频文件路径 mParams.setFrameWidth(CameraManager.PREVIEW_WIDTH); // 分辨率 mParams.setFrameHeight(CameraManager.PREVIEW_HEIGHT); mParams.setBitRateQuality(H264EncodeConsumer.Quality.MIDDLE); // 视频编码码率 mParams.setFrameRateDegree(H264EncodeConsumer.FrameRate._30fps);// 视频编码帧率 mParams.setFrontCamera((mRecMp4!=null&&mRecMp4.isFrontCamera()) ? true:false); // 摄像头方向 mParams.setPhoneHorizontal(false); // 是否为横屏拍摄 mParams.setAudioBitrate(AACEncodeConsumer.DEFAULT_BIT_RATE); // 音频比特率 mParams.setAudioSampleRate(AACEncodeConsumer.DEFAULT_SAMPLE_RATE); // 音频采样率 mParams.setAudioChannelConfig(AACEncodeConsumer.CHANNEL_IN_MONO);// 单声道 mParams.setAudioChannelCount(AACEncodeConsumer.CHANNEL_COUNT_MONO); // 单声道通道数量 mParams.setAudioFormat(AACEncodeConsumer.ENCODING_PCM_16BIT); // 采样精度为16位 mParams.setAudioSouce(AACEncodeConsumer.SOURCE_MIC); // 音频源为MIC mRecMp4.setEncodeParams(getEncodeParams());
(3) 开始 /停止录制
mRecMp4.startRecord(); mRecMp4.stopRecord();
(4) Camera渲染
public class MainActivity extends Activity implements SurfaceHolder.Callback{ @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { if(mRecMp4 != null){ mRecMp4.startCamera(surfaceHolder); } } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { if(mRecMp4 != null){ mRecMp4.stopCamera(); } }
(5) 摄像头控制
// 对焦 mRecMp4.enableFocus(new CameraManager.OnCameraFocusResult() { @Override public void onFocusResult(boolean result) { if(result){ showMsg("对焦成功"); } } }); // 切换摄像头 if(mRecMp4 != null){ mRecMp4.switchCamera(); } // 切换分辨率 if(mRecMp4 != null){ mRecMp4.setPreviewSize(1280,720); }
(6) JPG图片抓拍
mRecMp4.capturePicture(picPath, new SaveYuvImageTask.OnSaveYuvResultListener() { @Override public void onSaveResult(boolean result, String savePath) { Log.i("MainActivity","抓拍结果:"+result+"保存路径:"+savePath); } });
最后,不要忘记添加权限哈
<uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Github项目地址:https://github.com/jiangdongguo/AndroidRecordMp4,欢迎大家star & clone~
参考:
音频编码格式介绍:https://wenku.baidu.com/view/0e8115fcfab069dc502201f0.html?re=viewMP4封装格式:http://blog.csdn.net/u010246197/article/details/52924365
- ffmpeg开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)
- ffmpeg开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)
- ffmpeg开发之旅(4):MP3编码格式分析与lame库编译封装
- ffmpeg开发之旅(4):MP3编码格式分析与lame库编译封装
- FFMPEG录屏软件开发之YUV AAC合成MP4
- MediaMuxer+MediaCodec生成MP4视频报错
- FFMPEG解析MP4格式封装的视频为YUV和AAC
- Android-->MediaMuxer,MediaCodec,AudioRecord及Camera实现音频视频混合MP4文件
- 【转载】Android-->MediaMuxer,MediaCodec,AudioRecord及Camera实现音频视频混合MP4文件
- Android-->MediaMuxer,MediaCodec,AudioRecord及Camera实现音频视频混合MP4文件
- Mp4V2 AAC/PCM 封装MP4 iOS 很容易,用FFmpeg也一样,直接写文件。
- MediaCodec编码aac
- 转: FFMpeg 封装MP4 文件
- FFMPEG录屏软件开发之编码AAC
- 基于FFmpeg的封装格式MP4(TS)
- MP4文件封装格式详解
- 安卓MediaCodec编码aac
- H264编码封装成MP4格式
- easyUI layout布局
- JVM体系结构与工作方式概览
- java中文乱码解决之道(八)-----解决URL中文乱码问题
- ZooKeeper基本原理和使用
- 测试应注意的事项
- ffmpeg开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)
- SVN:Previous operation has not finished; run 'cleanup' if it was interrupted
- 本地时间和UTC时间 转换
- Javascript、Jquery获取浏览器和屏幕各种高度宽度
- 区块链中的共识机制
- 代数加法运算去除高斯噪声及matlab实现
- Spring框架
- ffmpeg开发之旅(4):MP3编码格式分析与lame库编译封装
- XML文件报错:EntityRef: expecting ';'或者xmlParseEntityRef: no name on line