fdk-aac编解码

Posted 代码整改砖家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了fdk-aac编解码相关的知识,希望对你有一定的参考价值。

前言

最近在调试fdk-aac编解码,总结了一下需要注意的点,代码在公司内网懒得再敲了。本文介绍的是实时码流的编解码。有问题欢迎指出、讨论。

编译

1. fdk-aac开源代码下载:

2. 根据所使用的交叉编译器来设置cmake中的toolchain,例如我是在Hi3516上使用的,toolchain需要设置为hisiv300,就需要在下载的开源代码根目录下CMakeList.txt中添加命令:

# SET (CMAKE_C_COMPILER path) // path代指编译器路径

3. 执行cmake ./

4. 步骤3执行成功后会生成一个Makefile,终端下输入make开始编译

        a) fdk-aac文件夹不可以放在共享目录中(我实在虚拟机中使用的,所以会有共享目录)

        b) 编译成功后会生成libfdk-aac.so、libfdk-aac.so.2、libfdk-aac.so.2.0.2三个文件

5. 使用时需要将3个库都放到lib中

编码

1. 参考开源库中aac_aenc.c中的demo即可实现编码。

2. 需要注意的一点是,每次送入encoder的数据的size是有要求的。

AACENC_InfoStruct aac_info;

/* 中间省略了参数设置、初始化encoder的过程 */

/*
 * input_size: 输入PCM数据的大小
 * channels:通道数
 * frameLength:每帧每个通道的采样点数
 */
int channels = 2; // 2:双通道
int input_size = channels * 2 * aac_info.frameLength;

3. 当然,如果你和我碰到一样的情况,采集的PCM数据大小和input_size不同,也可以通过加一个缓冲buffer来解决。每当采集到PCM数据,就将其存储到缓冲buffer中,当缓冲buffer中数据长度达到input_size时就取出input_size个字节进行编码。

RTP打包

打包编码后的aac数据这位博主写的很详细。

从零开始写一个RTSP服务器(五)RTP传输AAC_JT同学的博客-CSDN博客_rtp发送aac

解码

fdk-aac解码主要有两种模式:RAW模式和ADTS模式。

RAW模式

基本流程如下:

aacDecoder_Open();
aacDecoder_ConfigRaw();
loop
  aacDecoder_Fill();
  aacDecoder_DecodeFrame();

aacDecoder_Close();

1. RAW模式需要在初始化decoder时,调用aacDecoder_ConfigRaw()传入AudiospecInfo(ASC),这个结构体中存储着送入的raw数据的samplerate、channel等,送数据时需要去掉ADTS头。

2. 自己构建ASC数据可能会出现问题,建议在编码的时候就保存下来。在用fdk-aac编码时调用aacEncInfo()会获取到一个结构体AACENC_InfoStruct,该结构体中最后两个成员confBuf64confSize就分别对应ASC和ASC的size。

ADTS模式

基本流程如下:

aacDecoder_Open();
loop
  /* 添加 增加adts头的代码 */
  aacDecoder_Fill();
  aacDecoder_DecodeFrame();

aacDecoder_Close();

1. ADTS模式初始化时不需要传入AudioSpecInfo(ASC),送数据时需要加上ADTS头。我编码时设置的是AAC_LC,采样率48K,双通道。参数不同的可以参考下面图片。

/* 添加ADTS头
 * 一帧完整AAC数据 = ADTS头(7字节) + AAC源数据
 * 
 * aac_frame
 * aac_frame_size
 *
 */

void addADTSHead(unsigned char *aac_frame, int aac_frame_size)

    int profile = 2; // AAC_LC
    int freqIdx = 3; // sampleRate: 48K
    int chanCfg = 2; // channel: stero

    aac_frame[0] = 0xFF;
    aac_frame[1] = 0xF1;
    aac_frame[2] = (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
    aac_frame[3] = (((chanCfg & 3) << 6) + (aac_frame_size >> 11));
    aac_frame[4] = ((aac_frame_size & 0x7FF) >> 3);
    aac_frame[5] = (((aac_frame_size & 7) << 5) + 0x1F);
    aac_frame[6] = 0xFC;

 

 

 

视频的编解码-编码篇

参考技术A 四、视频的编解码-编码篇

时间 2016-08-05 10:22:59 Twenty's 时间念

原文 http://blog.img421.com/si-shi-pin-de-bian-jie-ma-bian-ma-pian/

主题 iOS开发 MacOS

在此之前我们通常使用的FFmpeg多媒体库,利用CPU来进行视频的编解码,占用CPU资源,效率低下,俗称软编解码.而苹果在2014年的iOS8中,开放了VideoToolbox.framwork框架,此框架使用GPU或专用的处理器来进行编解码,俗称硬编解码.而此框架在此之前只有MAC OS系统中可以使用,在iOS作为私有框架.终于苹果在iOS8.0中得到开放引入.

2014年的WWDC Direct Access to Video Encoding and Decoding 中,苹果介绍了使用videoToolbox硬编解码.

使用硬编解码有几个优点: * 提高性能; * 增加效率; * 延长电量的使用

对于编解码,AVFoundation框架只有以下几个功能: 1. 直接解压后显示;

2. 直接压缩到一个文件当中;

而对于Video Toolbox,我们可以通过以下功能获取到数据,进行网络流传输等多种保存: 1. 解压为图像的数据结构;

2. 压缩为视频图像的容器数据结构.

一、videoToolbox的基本数据

Video Toolbox视频编解码前后需要应用的数据结构进行说明。

CVPixelBuffer:编码前和解码后的图像数据结构。此内容包含一系列的CVPixelBufferPool内容

CMTime、CMClock和CMTimebase:时间戳相关。时间以64-bit/32-bit的形式出现。

pixelBufferAttributes:字典设置.可能包括Width/height、pixel format type、• Compatibility (e.g., OpenGL ES, Core Animation)

CMBlockBuffer:编码后,结果图像的数据结构。

CMVideoFormatDescription:图像存储方式,编解码器等格式描述。

(CMSampleBuffer:存放编解码前后的视频图像的容器数据结构。

CMClock

CMTimebase: 关于CMClock的一个控制视图,包含CMClock、时间映射(Time mapping)、速率控制(Rate control)

由二、采集视频数据可知,我们获取到的数据(CMSampleBufferRef)sampleBuffer为未编码的数据;

图1.1

上图中,编码前后的视频图像都封装在CMSampleBuffer中,编码前以CVPixelBuffer进行存储;编码后以CMBlockBuffer进行存储。除此之外两者都包括CMTime、CMVideoFormatDesc.

二、视频数据流编码并上传到服务器

1.将CVPixelBuffer使用VTCompressionSession进行数据流的硬编码。

(1)初始化VTCompressionSession

VT_EXPORT OSStatus VTCompressionSessionCreate(    CM_NULLABLE CFAllocatorRef                          allocator,    int32_t                                            width,    int32_t                                            height,    CMVideoCodecType                                    codecType,    CM_NULLABLE CFDictionaryRef                        encoderSpecification,    CM_NULLABLE CFDictionaryRef                        sourceImageBufferAttributes,    CM_NULLABLE CFAllocatorRef                          compressedDataAllocator,    CM_NULLABLE VTCompressionOutputCallback            outputCallback,    void * CM_NULLABLE                                  outputCallbackRefCon,    CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTCompressionSessionRef * CM_NONNULL compressionSessionOut)    __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_8_0);

VTCompressionSession的初始化参数说明:

allocator:分配器,设置NULL为默认分配

width: 宽

height: 高

codecType: 编码类型,如kCMVideoCodecType_H264

encoderSpecification: 编码规范。设置NULL由videoToolbox自己选择

sourceImageBufferAttributes: 源像素缓冲区属性.设置NULL不让videToolbox创建,而自己创建

compressedDataAllocator: 压缩数据分配器.设置NULL,默认的分配

outputCallback: 当VTCompressionSessionEncodeFrame被调用压缩一次后会被异步调用.注:当你设置NULL的时候,你需要调用VTCompressionSessionEncodeFrameWithOutputHandler方法进行压缩帧处理,支持iOS9.0以上

outputCallbackRefCon: 回调客户定义的参考值.

compressionSessionOut: 压缩会话变量。

(2)配置VTCompressionSession

使用VTSessionSetProperty()调用进行配置compression。 * kVTCompressionPropertyKey AllowFrameReordering: 允许帧重新排序.默认为true * kVTCompressionPropertyKey AverageBitRate: 设置需要的平均编码率 * kVTCompressionPropertyKey H264EntropyMode:H264的 熵编码 模式。有两种模式:一种基于上下文的二进制算数编码CABAC和可变长编码VLC.在slice层之上(picture和sequence)使用定长或变长的二进制编码,slice层及其以下使用VLC或CABAC. 详情请参考 * kVTCompressionPropertyKey RealTime: 视频编码压缩是否是实时压缩。可设置CFBoolean或NULL.默认为NULL * kVTCompressionPropertyKey ProfileLevel: 对于编码流指定配置和标准 .比如kVTProfileLevel H264 Main AutoLevel

配置过VTCompressionSession后,可以可选的调用VTCompressionSessionPrepareToEncodeFrames进行准备工作编码帧。

(3)开始硬编码流入的数据

使用VTCompressionSessionEncodeFrame方法进行编码.当编码结束后调用outputCallback回调函数。

VT_EXPORT OSStatus  VTCompressionSessionEncodeFrame(      CM_NONNULL VTCompressionSessionRef  session,    CM_NONNULL CVImageBufferRef        imageBuffer,    CMTime                              presentationTimeStamp,    CMTime                              duration,// may be kCMTimeInvalidCM_NULLABLE CFDictionaryRef        frameProperties,void* CM_NULLABLE                  sourceFrameRefCon,    VTEncodeInfoFlags * CM_NULLABLE    infoFlagsOut )    __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_8_0);

presentationTimeStamp: 获取到的这个sample buffer数据的展示时间戳。每一个传给这个session的时间戳都要大于前一个展示时间戳.

duration: 对于获取到sample buffer数据,这个帧的展示时间.如果没有时间信息,可设置kCMTimeInvalid.

frameProperties: 包含这个帧的属性.帧的改变会影响后边的编码帧.

sourceFrameRefCon: 回调函数会引用你设置的这个帧的参考值.

infoFlagsOut: 指向一个VTEncodeInfoFlags来接受一个编码操作.如果使用异步运行,kVTEncodeInfo_Asynchronous被设置;同步运行,kVTEncodeInfo_FrameDropped被设置;设置NULL为不想接受这个信息.

(4)执行VTCompressionOutputCallback回调函数

typedefvoid(*VTCompressionOutputCallback)(void* CM_NULLABLE outputCallbackRefCon,void* CM_NULLABLE sourceFrameRefCon,        OSStatus status,        VTEncodeInfoFlags infoFlags,        CM_NULLABLE CMSampleBufferRef sampleBuffer );

outputCallbackRefCon: 回调函数的参考值

sourceFrameRefCon: VTCompressionSessionEncodeFrame函数中设置的帧的参考值

status: 压缩的成功为noErr,如失败有错误码

infoFlags: 包含编码操作的信息标识

sampleBuffer: 如果压缩成功或者帧不丢失,则包含这个已压缩的数据CMSampleBuffer,否则为NULL

(5)将压缩成功的sampleBuffer数据进行处理为基本流NSData上传到服务器

MPEG-4是一套用于音频、视频信息的压缩编码标准.

由 图1.1 可知,已压缩 $$CMSampleBuffer = CMTime(可选) + CMBlockBuffer + CMVideoFormatDesc$$。

5.1 先判断压缩的数据是否正确

//不存在则代表压缩不成功或帧丢失if(!sampleBuffer)return;if(status != noErr)return;//返回sampleBuffer中包括可变字典的不可变数组,如果有错误则为NULLCFArrayRefarray=  CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,true);if(!array)return;  CFDictionaryRef dic = CFArrayGetValueAtIndex(array,0);if(!dic)return;//issue 3:kCMSampleAttachmentKey_NotSync:没有这个键意味着同步, yes: 异步. no:同步BOOL keyframe = !CFDictionaryContainsKey(dic, kCMSampleAttachmentKey_NotSync);//此代表为同步

而对于 issue 3 从字面意思理解即为以上的说明,但是网上看到很多都是做为查询是否是视频关键帧,而查询文档看到有此关键帧key值kCMSampleBufferAttachmentKey_ForceKeyFrame存在,因此对此值如若有了解情况者敬请告知详情.

5.2 获取CMVideoFormatDesc数据由 三、解码篇 可知CMVideoFormatDesc 包括编码所用的profile,level,图像的宽和高,deblock滤波器等.具体包含 第一个NALU的SPS (Sequence Parameter Set)和 第二个NALU的PPS (Picture Parameter Set).

//if (keyframe && !encoder -> sps)     //获取sample buffer 中的 CMVideoFormatDesc    CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);    //获取H264参数集合中的SPS和PPS    const uint8_t * sparameterSet;size_t sparameterSetSize,sparameterSetCount ;  OSStatus statusCode =    CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sparameterSet, &sparameterSetSize, &sparameterSetCount,0);if (statusCode == noErr)         size_t pparameterSetSize, pparameterSetCount;        const uint8_t *pparameterSet;OSStatus statusCode =    CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount,0);if (statusCode == noErr)             encoder->sps = [NSData dataWithBytes:sparameterSetlength:sparameterSetSize];encoder->pps = [NSData dataWithBytes:pparameterSetlength:pparameterSetSize];   

5.3 获取CMBlockBuffer并转换成数据

CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);    size_t  lengthAtOffset,totalLength;char*dataPointer;//接收到的数据展示OSStatus blockBufferStatus = CMBlockBufferGetDataPointer(blockBuffer,0, &lengthAtOffset, &totalLength, &dataPointer);if(blockBufferStatus != kCMBlockBufferNoErr)            size_t bufferOffset =0;staticconstintAVCCHeaderLength =4;while(bufferOffset < totalLength -  AVCCHeaderLength) // Read the NAL unit lengthuint32_t NALUnitLength =0;/**

*  void *memcpy(void *dest, const void *src, size_t n);

*  从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

*/memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);//字节从高位反转到低位NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);            RTAVVideoFrame * frame = [RTAVVideoFramenew];            frame.sps = encoder -> sps;            frame.pps = encoder -> pps;            frame.data = [NSData dataWithBytes:(dataPointer+bufferOffset+AVCCHeaderLength) length:NALUnitLength];            bufferOffset += NALUnitLength + AVCCHeaderLength;           

此得到的H264数据应用于后面的RTMP协议做推流准备。

以上是关于fdk-aac编解码的主要内容,如果未能解决你的问题,请参考以下文章

视频的编解码-编码篇

音视频编解码之路:JPEG编码

音视频编解码之路:JPEG编码

音视频编解码之路:JPEG编码

视频编码解码学习之二:编解码框架

Android 音视频编解码 -- 视频编码和H264格式原理讲解