使用iOS自带AAC编码器

AAC(Advanced Audio Coding),中文名:高级音频编码,出现于1997年,基于MPEG-2的音频编码技术。由Fraunhofer
IIS、杜比实验室AT&TSony等公司共同开发,目的是取代MP3格式。2000年,MPEG-4标准出现后,AAC重新集成了其特性,加入了SBR技术和PS技术,为了区别于传统的MPEG-2
AAC又称为MPEG-4 AAC。

iOS平台支持AAC编码器,主要使用AudioToolbox中的AudioConverter API。之所以做AAC编码器是因为在做一个HLS的功能,HLS要求的TS文件,需要视频采用H264编码,音频采用AAC编码。H264可以使用硬件或软件编码器,前面已经介绍。AAC也可以使用硬件或者软件编码,iOS全都支持。

首先需要创建一个Converter,也就是一个AAC Encoder,使用如下接口:

extern OSStatus
AudioConverterNew(      const AudioStreamBasicDescription*  inSourceFormat,
                        const AudioStreamBasicDescription*  inDestinationFormat,
                        AudioConverterRef*                  outAudioConverter)      __OSX_AVAILABLE_STARTING(__MAC_10_1,__IPHONE_2_0);

输入参数分别是源和目的的数据格式。

在AAC编码的场景下,源格式就是采集到的PCM数据,目的格式就是AAC。

    AudioStreamBasicDescription inAudioStreamBasicDescription;

//    FillOutASBDForLPCM()
    inAudioStreamBasicDescription.mFormatID = kAudioFormatLinearPCM;
    inAudioStreamBasicDescription.mSampleRate = 44100;
    inAudioStreamBasicDescription.mBitsPerChannel = 16;
    inAudioStreamBasicDescription.mFramesPerPacket = 1;
    inAudioStreamBasicDescription.mBytesPerFrame = 2;
    inAudioStreamBasicDescription.mBytesPerPacket = inAudioStreamBasicDescription.mBytesPerFrame * inAudioStreamBasicDescription.mFramesPerPacket;
    inAudioStreamBasicDescription.mChannelsPerFrame = 1;
    inAudioStreamBasicDescription.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsNonInterleaved;
    inAudioStreamBasicDescription.mReserved = 0;

    AudioStreamBasicDescription outAudioStreamBasicDescription = {0}; // Always initialize the fields of a new audio stream basic description structure to zero, as shown here: ...

    outAudioStreamBasicDescription.mChannelsPerFrame = 1;
    outAudioStreamBasicDescription.mFormatID = kAudioFormatMPEG4AAC;
    UInt32 size = sizeof(outAudioStreamBasicDescription);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &outAudioStreamBasicDescription);

    OSStatus status = AudioConverterNew(&inAudioStreamBasicDescription, &outAudioStreamBasicDescription, &_audioConverter);
    if(status != 0) {NSLog(@"setup converter failed: %d", (int)status);}

这样就创建了AAC编码器,默认情况下,Apple会创建一个硬件编码器,如果硬件不可用,会创建软件编码器。

经过我的测试,硬件AAC编码器的编码时延很高,需要buffer大约2秒的数据才会开始编码。而软件编码器的编码时延就是正常的,只要喂给1024个样点,就会开始编码。

那么如何在创建的时候指定使用软件编码器呢?需要用到下面的接口:

- (AudioClassDescription *)getAudioClassDescriptionWithType:(UInt32)type
                                           fromManufacturer:(UInt32)manufacturer
{
    static AudioClassDescription desc;

    UInt32 encoderSpecifier = type;
    OSStatus st;

    UInt32 size;
    st = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
                                    sizeof(encoderSpecifier),
                                    &encoderSpecifier,
                                    &size);
    if (st) {
        NSLog(@"error getting audio format propery info: %d", (int)(st));
        return nil;
    }

    unsigned int count = size / sizeof(AudioClassDescription);
    AudioClassDescription descriptions[count];
    st = AudioFormatGetProperty(kAudioFormatProperty_Encoders,
                                sizeof(encoderSpecifier),
                                &encoderSpecifier,
                                &size,
                                descriptions);
    if (st) {
        NSLog(@"error getting audio format propery: %d", (int)(st));
        return nil;
    }

    for (unsigned int i = 0; i < count; i++) {
        if ((type == descriptions[i].mSubType) &&
            (manufacturer == descriptions[i].mManufacturer)) {
            memcpy(&desc, &(descriptions[i]), sizeof(desc));
            return &desc;
        }
    }

    return nil;
}

AudioClassDescription *desc = [self getAudioClassDescriptionWithType:kAudioFormatMPEG4AAC
 fromManufacturer:kAppleSoftwareAudioCodecManufacturer];
 OSStatus status = AudioConverterNewSpecific(&inAudioStreamBasicDescription, &outAudioStreamBasicDescription, 1, desc, &_audioConverter);

如果要正确的编码,编码码率参数是必须设置的。否则编码时会返回560226676错误码(!dat)。

        UInt32 ulBitRate = 64000;
        UInt32 ulSize = sizeof(ulBitRate);
        status = AudioConverterSetProperty(_audioConverter, kAudioConverterEncodeBitRate, ulSize, &ulBitRate);

需要注意,AAC并不是随便的码率都可以支持。比如如果PCM采样率是44100KHz,那么码率可以设置64000bps,如果是16K,可以设置为32000bps。

创建完成Converter和设置完Bitrate之后,可以查询一下最大编码输出的大小,后续会用到。

    UInt32 value = 0;
    size = sizeof(value);
    AudioConverterGetProperty(_audioConverter, kAudioConverterPropertyMaximumOutputPacketSize, &size, &value);

获取出来的Value表示编码器最大输出的包大小。

然后调用AudioConverterFillCOmplexBuffer进行编码:

        AudioBufferList outAudioBufferList = {0};
        outAudioBufferList.mNumberBuffers = 1;
        outAudioBufferList.mBuffers[0].mNumberChannels = 1;
        outAudioBufferList.mBuffers[0].mDataByteSize = value;//value是上面查询到的值
        outAudioBufferList.mBuffers[0].mData = new int8[value];

        UInt32 ioOutputDataPacketSize = 1;
        status = AudioConverterFillComplexBuffer(_audioConverter, inInputDataProc, (__bridge void *)(self), &ioOutputDataPacketSize, &outAudioBufferList, NULL);

编码接口中,inInputDataProc是一个输入数据的回调函数。用来喂PCM数据给Converter,ioOutputDataPacketSize为1表示编码产生1帧数据即返回。outAudioBufferList用来存放编码后的数据。

inInputDataProc中的处理如下:

static OSStatus inInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
{

    AACEncoder *encoder = (__bridge AACEncoder *)(inUserData);
    UInt32 requestedPackets = *ioNumberDataPackets;
    uint8_t *buffer;
    uint32_t bufferLength = requestedPackets * 2;
    uint32_t bufferRead;
    bufferRead = [encoder.pcmPool readBuffer:&buffer withLength:bufferLength];
    if (bufferRead == 0) {
        *ioNumberDataPackets = 0;
        return -1;
    }

    ioData->mBuffers[0].mData = buffer;
    ioData->mBuffers[0].mDataByteSize = bufferRead;
    ioData->mNumberBuffers = 1;
    ioData->mBuffers[0].mNumberChannels = 1;

    *ioNumberDataPackets = bufferRead >> 1;
    return noErr;
}

pcmPool是一个用于存放PCM数据的环形缓冲区。

因为采集输入每次不一定有1024样点,所以可以将数据缓存起来,再满足1024样点时再调用编码。

另外,对于TS文件来说,每个AAC数据需要增加一个adts头,adts头是一个7bit的数据,通过adts可以得知AAC数据的编码参数,方便解码器进行解码。

adts头的计算方法如下:

- (NSData*) adtsDataForPacketLength:(NSUInteger)packetLength {
    int adtsLength = 7;
    char *packet = (char *)malloc(sizeof(char) * adtsLength);
    // Variables Recycled by addADTStoPacket
    int profile = 2;  //AAC LC
    //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
    int freqIdx = 8;  //16KHz
    int chanCfg = 1;  //MPEG-4 Audio Channel Configuration. 1 Channel front-center
    NSUInteger fullLength = adtsLength + packetLength;
    // fill in ADTS data
    packet[0] = (char)0xFF;	// 11111111  	= syncword
    packet[1] = (char)0xF9;	// 1111 1 00 1  = syncword MPEG-2 Layer CRC
    packet[2] = (char)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
    packet[3] = (char)(((chanCfg&3)<<6) + (fullLength>>11));
    packet[4] = (char)((fullLength&0x7FF) >> 3);
    packet[5] = (char)(((fullLength&7)<<5) + 0x1F);
    packet[6] = (char)0xFC;
    NSData *data = [NSData dataWithBytesNoCopy:packet length:adtsLength freeWhenDone:YES];
    return data;
}

adts头的计算需要几个参数:profile/frequency/channels/length,具体可参考http://wiki.multimedia.cx/index.php?title=ADTS

参考文献:

Audio Converter Services Reference

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-25 23:21:33

使用iOS自带AAC编码器的相关文章

iOS 自带定位功能

第一步:导入头文件 #import <CoreLocation/CoreLocation.h> #import <CoreLocation/CLLocationManagerDelegate.h> 第二步:设置代理 CLLocationManagerDelegate 第三步:创建一个属性 @property(nonatomic, strong) CLLocationManager *locationManager; 第四步:初始化     //定位服务管理对象初始化     _lo

ios 自带xml 解析,TBXMLParser解析

今天看了下苹果xml 解析,写了个小demo 心想还是 在博客上写点东西吧,毕竟很久很久都没有上来了 先上个效果图把 接下来 看下 工程目录图吧 本demo 分两种解析模式,一是苹果自带的, 首先先看下苹果自带的吧,工程文件为 NoteXMLParser 文件 ,另一种解析模式 是 NotesTBXMLParser文件 NoteXMLParser.h 文件代码如下 : // // NoteXMLParser.h // TestXML // // Created by choni on 14-5-

iOS 自带刷新功能

这里只介绍UIRefreshControl的使用方法,虽然EGO已经用得挺舒服的了,但是官方给的.毕竟还是蛮简单的 ================================================== UIRefreshControl 具有一个默认的高度和宽度 一旦创建,便自动管理.只有当用户用力刷新才能刷新,尤其...那朵菊花,用力越大,转速越快,感觉还不错哦.(瞬间...邪恶了) 下面介绍一下如何使用:     self.refreshControl = [[UIRefres

IOS自带输入法中文不触发KEYUP事件导致vue双向绑定错误问题

先上图: 可以看到输入框中的内容和弹出框的内容不一致, <input class="am-fr labRight" id="txcode" type="text" placeholder="请输入纳税人识别号" v-model="invBuyer.TaxCode" /> 文本框使用的是vue的v-model双向绑定,在android中是ok的,在IOS上不行, 导致问题出现的原因是IOS自带输入

iOS自带TTS技术的实现即语音播报

文本转语音技术, 也叫TTS, 是Text To Speech的缩写. iOS如果想做有声书等功能的时候, 会用到这门技术. 一,使用iOS自带TTS需要注意的几点: iOS7之后才有该功能 需要 AVFoundation 库 AVSpeechSynthesizer: 语音合成器, 可以假想成一个可以说话的人, 是最主要的接口 AVSpeechSynthesisVoice: 可以假想成人的声音 AVSpeechUtterance: 可以假想成要说的一段话 二,代码示例, 播放语音 //语音播报

新浪微博项目---首页技术点三.上拉刷新,下拉加载的实现(使用ios自带的小菊花实现)

一.上拉刷新,下拉加载的实现(使用ios自带的小菊花实现) 1.下拉刷新 #pragma mark ---集成下*拉刷新控件 -(void)setupDownRefresh { //1.添加刷新控件 UIRefreshControl *control = [[UIRefreshControl alloc] init]; //只有用户通过手动下拉刷新,才会触发UIControlEventValueChanged事件 [control addTarget:self action:@selector(

最简单的基于FFmpeg的移动端例子附件:IOS自带播放器

前几篇文章记录的都是IOS下基于FFmpeg的多媒体程序.本文记录一个IOS下自带的视频播放器.相比于移植FFmpeg来说,IOS自带的多媒体框架播放视频要简单很多--只需要调用几个接口就可以了. 源代码 项目的源代码位于ViewController.m文件中,内容如下所示. /** * 最简单的IOS视频播放器 * Simplest IOS Player * * 雷霄骅 Lei Xiaohua * [email protected] * 中国传媒大学/数字电视技术 * Communicatio

iOS 自带地图定位失败原因 Code=0和Code=1区别

1:没有选择位置 Error Domain=kCLErrorDomain Code=0 "The operation couldn't be completed. (kCLErrorDomain error 0.)" 解决方案: 2:软件没有获得定位许可 Error Domain=kCLErrorDomain Code=1 "The operation couldn't be completed. (kCLErrorDomain error 1.)" 解决方案: i

即时搜索:对于ios自带输入法输入中文时多次触发input事件的处理

实现移动端的即时搜索的最佳方案,一定是使用input propertychange事件了,但是在ios设备上遇到了问题,使用ios自带输入法输入汉字时,会出现多次触发input事件的情况,一开始可能由于搜索的关键字不得法,没有即时找到合适的方案,后来终于在网上找到了解决方案,现记录如下: 代码实现 /** * @param flag: 用于标记是否是非直接的文字输入 */ var flag = false; $('#id').on({ 'compositionstart': function()