iOS平台上音频编码成aac

小程之前介绍解码aac时,曾经使用了fadd,并且有提到,如果想编码成aac格式,可以使用facc、fdk-aac等,但使用fdk-aac等编码方式,都是软编码,在cpu的消耗上会明显大于硬件编码。

硬编码的优势是可以用硬件芯片集成的功能,高速且低功耗地完成编码任务。

在iOS平台,也提供了硬编码的能力,APP开发时只需要调用相应的SDK接口就可以了。

这个SDK接口就是AudioConverter。

本文介绍iOS平台上,如何调用AudioConverter来完成aac的硬编码。

从名字来看,AudioConverter就是格式转换器,这里小程使用它,把pcm格式的数据,转换成aac格式的数据。

对于媒体格式(编码格式或封装格式),读者可以关注“广州小程”公众号,并在“音视频->基础概念与流程”菜单中查阅相关文章。

AudioConverter在内存中实现转换,并不需要写文件,而ExtAudioFile接口则是对文件的操作,并且内部使用AudioConerter来转换格式,也就是说,读者在某种场景下,也可以使用ExtAudioFile接口。

如何使用AudioConverter呢?基本上,对接口的调用都需要阅读对应的头文件,通过看文档注释来理解怎么调用。

小程这里演示一下,怎么把pcm格式的数据转换成aac格式的数据。

在演示代码之后,小程只做简单的解释,有需要的读者请耐心阅读代码来理解,并应用到自己的开发场景中。

下面的例子演示从pcm转aac的实现(比如把录音数据保存成aac的实现)。

typedef struct
{
    void *source;
    UInt32 sourceSize;
    UInt32 channelCount;
    AudioStreamPacketDescription *packetDescriptions;
}FillComplexInputParam;

// 填写源数据,即pcm数据
OSStatus audioConverterComplexInputDataProc(  AudioConverterRef               inAudioConverter,
                                            UInt32*                         ioNumberDataPackets,
                                            AudioBufferList*                ioData,
                                            AudioStreamPacketDescription**  outDataPacketDescription,
                                            void*                           inUserData)
{
    FillComplexInputParam* param = (FillComplexInputParam*)inUserData;
    if (param->sourceSize <= 0) {
        *ioNumberDataPackets = 0;
        return -1;
    }
    ioData->mBuffers[0].mData = param->source;
    ioData->mBuffers[0].mNumberChannels = param->channelCount;
    ioData->mBuffers[0].mDataByteSize = param->sourceSize;
    *ioNumberDataPackets = 1;
    param->sourceSize = 0;
    param->source = NULL;
    return noErr;
}

typedef struct _tagConvertContext {
    AudioConverterRef converter;
    int samplerate;
    int channels;
}ConvertContext;

// init
// 最终用AudioConverterNewSpecific创建ConvertContext,并设置比特率之类的属性
void* convert_init(int sample_rate, int channel_count)
{
    AudioStreamBasicDescription sourceDes;
    memset(&sourceDes, 0, sizeof(sourceDes));
    sourceDes.mSampleRate = sample_rate;
    sourceDes.mFormatID = kAudioFormatLinearPCM;
    sourceDes.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger;
    sourceDes.mChannelsPerFrame = channel_count;
    sourceDes.mBitsPerChannel = 16;
    sourceDes.mBytesPerFrame = sourceDes.mBitsPerChannel/8*sourceDes.mChannelsPerFrame;
    sourceDes.mBytesPerPacket = sourceDes.mBytesPerFrame;
    sourceDes.mFramesPerPacket = 1;
    sourceDes.mReserved = 0;

    AudioStreamBasicDescription targetDes;
    memset(&targetDes, 0, sizeof(targetDes));
    targetDes.mFormatID = kAudioFormatMPEG4AAC;
    targetDes.mSampleRate = sample_rate;
    targetDes.mChannelsPerFrame = channel_count;
    UInt32 size = sizeof(targetDes);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &targetDes);

    AudioClassDescription audioClassDes;
    memset(&audioClassDes, 0, sizeof(AudioClassDescription));
    AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(targetDes.mFormatID), &targetDes.mFormatID, &size);
    int encoderCount = size / sizeof(AudioClassDescription);
    AudioClassDescription descriptions[encoderCount];
    AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(targetDes.mFormatID), &targetDes.mFormatID, &size, descriptions);
    for (int pos = 0; pos < encoderCount; pos ++) {
        if (targetDes.mFormatID == descriptions[pos].mSubType && descriptions[pos].mManufacturer == kAppleSoftwareAudioCodecManufacturer) {
            memcpy(&audioClassDes, &descriptions[pos], sizeof(AudioClassDescription));
            break;
        }
    }

    ConvertContext *convertContex = malloc(sizeof(ConvertContext));
    OSStatus ret = AudioConverterNewSpecific(&sourceDes, &targetDes, 1, &audioClassDes, &convertContex->converter);
    if (ret == noErr) {
        AudioConverterRef converter = convertContex->converter;

        tmp = kAudioConverterQuality_High;
        AudioConverterSetProperty(converter, kAudioConverterCodecQuality, sizeof(tmp), &tmp);

        UInt32 bitRate = 96000;
        UInt32 size = sizeof(bitRate);
        ret = AudioConverterSetProperty(converter, kAudioConverterEncodeBitRate, size, &bitRate);
    }
    else {
        free(convertContex);
        convertContex = NULL;
    }

    return convertContex;
}

// converting
void convert(void* convertContext, void* srcdata, int srclen, void** outdata, int* outlen)
{
    ConvertContext* convertCxt = (ConvertContext*)convertContext;
    if (convertCxt && convertCxt->converter) {
        UInt32 theOuputBufSize = srclen;
        UInt32 packetSize = 1;
        void *outBuffer = malloc(theOuputBufSize);
        memset(outBuffer, 0, theOuputBufSize);

        AudioStreamPacketDescription *outputPacketDescriptions = NULL;
        outputPacketDescriptions = (AudioStreamPacketDescription*)malloc(sizeof(AudioStreamPacketDescription) * packetSize);

        FillComplexInputParam userParam;
        userParam.source = srcdata;
        userParam.sourceSize = srclen;
        userParam.channelCount = convertCxt->channels;
        userParam.packetDescriptions = NULL;

        OSStatus ret = noErr;

        AudioBufferList* bufferList = malloc(sizeof(AudioBufferList));
        AudioBufferList outputBuffers = *bufferList;
        outputBuffers.mNumberBuffers = 1;
        outputBuffers.mBuffers[0].mNumberChannels = convertCxt->channels;
        outputBuffers.mBuffers[0].mData = outBuffer;
        outputBuffers.mBuffers[0].mDataByteSize = theOuputBufSize;
        ret = AudioConverterFillComplexBuffer(convertCxt->converter, audioConverterComplexInputDataProc, &userParam, &packetSize, &outputBuffers, outputPacketDescriptions);
        if (ret == noErr) {
            if (outputBuffers.mBuffers[0].mDataByteSize > 0) {

                NSData* rawAAC = [NSData dataWithBytes:outputBuffers.mBuffers[0].mData length:outputBuffers.mBuffers[0].mDataByteSize];
                *outdata = malloc([rawAAC length]);
                memcpy(*outdata, [rawAAC bytes], [rawAAC length]);
                *outlen = (int)[rawAAC length];
// 测试转换出来的aac数据,保存成adts-aac文件
#if 1
                int headerLength = 0;
                char* packetHeader = newAdtsDataForPacketLength((int)[rawAAC length], convertCxt->samplerate, convertCxt->channels, &headerLength);
                NSData* adtsPacketHeader = [NSData dataWithBytes:packetHeader length:headerLength];
                free(packetHeader);
                NSMutableData* fullData = [NSMutableData dataWithData:adtsPacketHeader];
                [fullData appendData:rawAAC];

                NSFileManager *fileMgr = [NSFileManager defaultManager];
                NSString *filepath = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/test%p.aac", convertCxt->converter];
                NSFileHandle *file = nil;
                if (![fileMgr fileExistsAtPath:filepath]) {
                    [fileMgr createFileAtPath:filepath contents:nil attributes:nil];
                }
                file = [NSFileHandle fileHandleForWritingAtPath:filepath];
                [file seekToEndOfFile];
                [file writeData:fullData];
                [file closeFile];
#endif
            }
        }

        free(outBuffer);
        if (outputPacketDescriptions) {
            free(outputPacketDescriptions);
        }
    }
}

// uninit
// ...

int freqIdxForAdtsHeader(int samplerate)
{
    /**
     0: 96000 Hz
     1: 88200 Hz
     2: 64000 Hz
     3: 48000 Hz
     4: 44100 Hz
     5: 32000 Hz
     6: 24000 Hz
     7: 22050 Hz
     8: 16000 Hz
     9: 12000 Hz
     10: 11025 Hz
     11: 8000 Hz
     12: 7350 Hz
     13: Reserved
     14: Reserved
     15: frequency is written explictly
     */
    int idx = 4;
    if (samplerate >= 7350 && samplerate < 8000) {
        idx = 12;
    }
    else if (samplerate >= 8000 && samplerate < 11025) {
        idx = 11;
    }
    else if (samplerate >= 11025 && samplerate < 12000) {
        idx = 10;
    }
    else if (samplerate >= 12000 && samplerate < 16000) {
        idx = 9;
    }
    else if (samplerate >= 16000 && samplerate < 22050) {
        idx = 8;
    }
    else if (samplerate >= 22050 && samplerate < 24000) {
        idx = 7;
    }
    else if (samplerate >= 24000 && samplerate < 32000) {
        idx = 6;
    }
    else if (samplerate >= 32000 && samplerate < 44100) {
        idx = 5;
    }
    else if (samplerate >= 44100 && samplerate < 48000) {
        idx = 4;
    }
    else if (samplerate >= 48000 && samplerate < 64000) {
        idx = 3;
    }
    else if (samplerate >= 64000 && samplerate < 88200) {
        idx = 2;
    }
    else if (samplerate >= 88200 && samplerate < 96000) {
        idx = 1;
    }
    else if (samplerate >= 96000) {
        idx = 0;
    }

    return idx;
}

int channelIdxForAdtsHeader(int channelCount)
{
    /**
     0: Defined in AOT Specifc Config
     1: 1 channel: front-center
     2: 2 channels: front-left, front-right
     3: 3 channels: front-center, front-left, front-right
     4: 4 channels: front-center, front-left, front-right, back-center
     5: 5 channels: front-center, front-left, front-right, back-left, back-right
     6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
     7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
     8-15: Reserved
     */
    int ret = 2;
    if (channelCount == 1) {
        ret = 1;
    }
    else if (channelCount == 2) {
        ret = 2;
    }

    return ret;
}

/**
 *  Add ADTS header at the beginning of each and every AAC packet.
 *  This is needed as MediaCodec encoder generates a packet of raw
 *  AAC data.
 *
 *  Note the packetLen must count in the ADTS header itself.
 *  See: http://wiki.multimedia.cx/index.php?title=ADTS
 *  Also: http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Channel_Configurations
 **/
char* newAdtsDataForPacketLength(int packetLength, int samplerate, int channelCount, int* ioHeaderLen) {
    int adtsLength = 7;
    char *packet = malloc(sizeof(char) * adtsLength);
    // Variables Recycled by addADTStoPacket
    int profile = 2;  //AAC LC
    //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
    int freqIdx = freqIdxForAdtsHeader(samplerate);
    int chanCfg = channelIdxForAdtsHeader(channelCount);  //MPEG-4 Audio Channel Configuration.
    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;
    *ioHeaderLen = adtsLength;
    return packet;
}

以上代码,有两个函数比较重要,一个是初始化函数,这个函数创建了AudioConverterRef,另一个是转换函数,这个函数应该被反复调用,对不同的pcm数据进行转换。

另外,示例中,把pcm转换出来的aac数据,进行了保存,保存出来的文件可以用于播放。

注意,AudioConverter转换出来的都是音频裸数据,至于组合成adts-aac,还是封装成苹果的m4a文件,由程序决定。

这里解释一下,adts-aac是aac数据的一种表示方式,也就是在每帧aac裸数据前面,增加一个帧信息(包括每帧的长度、采样率、声道数等),加上帧信息后,每帧aac可以单独播放。而且,adts-aac是没有封装的,也就是没有特定的文件头以及文件结构等。

adts是Audio Data Transport Stream的缩写。

当然,读者也可以把转换出来的aac数据,封装成m4a格式,这种封装格式,先是文件头,然后就是祼音频数据:

{packet-table}{audio_data}{trailer},头信息之后就是音频裸数据,音频数据不带packet信息。

至此,iOS平台把pcm转换成aac数据的实现就介绍完毕了。



总结一下,本文介绍了如何使用iOS平台提供的AudioConverter接口,把pcm格式的数据转换成aac格式。文章也介绍了怎么保存成adts-aac文件,读者可以通过这个办法检验转换出来的aac数据是否正确。

原文地址:http://blog.51cto.com/13136504/2107632

时间: 2024-07-29 21:17:28

iOS平台上音频编码成aac的相关文章

多媒体开发(13):iOS上音频编码成aac

如前面我所说,对于音频的解码,一般你都不用考虑硬解,用软解就足够了,这时可以选择faad或FFmpeg等.但是,如果是音频的编码呢?这可不一样,编码比解码明显耗时,为了快跟低功耗(特别对于低端机器),要优先考虑硬编码(不能再使用fdk-aac或faac之类的软编码),硬编码的优势是可以用硬件芯片集成的功能,高速且低功耗地完成编码任务. iOS平台,也提供了硬编码的能力,APP开发时只需要调用相应的SDK接口就能达成目标,这个SDK接口就是AudioConverter. 本文介绍iOS平台上,如何

使用Vitamio开发iOS平台上的万能播放器

迅速了解 Vitamio是干什么的?看官方怎么说: "Vitamio SDK for iOS是Yixia Ltd官方推出的 iOS 平台上使用的软件开发工具包(SDK),为iOS开发者提供简单.快捷的接口,帮助开发者实现 iOS 平台上的媒体播放应用." 说白了,就是可以帮助你便捷地开发自己的iOS播放器. 本文目标 从0开始建工程,使用VitamioSDK开发一个最简单的播放器:打开app之后,能看到一个播放按钮,点击按钮,就开始播放事先放在Documents目录下的视频文件,再按一

iOS平台上的音视频即时通讯应用开发

现在IOS很是火热,一大堆开发人员在捣鼓IOS平台的开发,相信大家也使用过QQ的语音视频对话功能,但是不知道大家有没有试过自己来开发一个基于IOS平台的音视频即时通讯的应用,这个应用必须能够做到跨平台 支持iOS平台设备上的音频即时通讯应用开发 提供Objective-C语言API接口,开放示例源代码 集成H.264.AAC.AMR等编解码技术 封装音视频的采集.编解码.传输.显示和播放等模块 支持Android.Web.PC等设备和iOS之间的互联互通 想要在IOS平台下实现音视频通信,最快捷

实战FFmpeg + OpenGLES--iOS平台上视频解码和播放

一个星期的努力终于搞定了视频的播放,利用FFmpeg解码视频,将解码的数据通过OpenGLES渲染播放.搞清楚了自己想知道的和完成了自己的学习计划,有点小兴奋.明天就是“五一”,放假三天,更开心啦. 本文实现视频文件的播放是在自己之前写的文章实战FFmpeg--iOS平台使用FFmpeg将视频文件转换为YUV文件 . 实战OpenGLES--iOS平台使用OpenGLES渲染YUV图片 的基础上改进合成来完成的.不多种解释,直接上代码,清晰明了. NSString *path = [[NSBun

[iOS]FFmpeg框架在iOS平台上的编译和使用

使用环境 Mac OS Yosemite 10.10.5 开发工具 Xcode 7.0 Terminal 需要的文件链接 gas-preprocessor yasm FFmpeg-iOS-build-script ffmpeg-2.8 kxmovie 编译适用于iOS平台的FFmpeg静态库 打开终端Terminal进入下载后的gas-preprocessor文件夹 将文件夹内的gas-preprocessor.pl文件拷贝到/usr/sbin/目录下 修改/usr/sbin/gas-prepr

实战AudioToolbox--在iOS平台上播放音频

上午看了关于AudioToolbox.framework相关的资料,结合网上的资料对AudioToolbox的基本使用有了整体上的认识,上一篇文章 笔谈AudioToolbox(一) 中提到使用AudioQueue来实现音频播放功能时最主要的步骤: 1. 打开播放音频文件 2. 取得播放音频文件的数据格式 3. 准备播放用的队列 4. 将缓冲中的数据移动到队列中 5. 开始播放 6. 在回调函数中进行队列处理 按照这个步骤实现了音频的播放. OSStatus status;     NSStri

【cocos2d-js官方文档】二十三、如何在IOS平台上使用js直接调用OC方法

在Cocos2d-JS v3.0 RC2中,与Android上js调用Java一样,Cocos2d-JS也提供了在iOS和Mac上js直接调用Objective-C的方法,示例代码如下: var ojb = jsb.reflection.callStaticMethod(className, methodNmae, arg1, arg2, .....); 在jsb.reflection.callStaticMethod方法中,我们通过传入OC的类名,方法名,参数就可以直接调用OC的静态方法,并且

iOS平台如何实现跨平台通讯

苹果手机带动了IOS的火热,一大堆开发人员在捣鼓IOS平台的开发,相信大家也使用过QQ的语音视频对话功能,但是不知道大家有没有试过自己来开发一个基于IOS平台的音视频即时通讯的应用,这个应用必须能够做到跨平台.现在介绍两种方法使用IOS与其他平台的音视频通话. 首先就是使用一个跨平台的音视频通讯平台即可,这是作为非专业技术人员也可以做到的,下面就先介绍第一种:只需三个条件: 1.手机有网络连接: 2.有AnyChat for iOS SDK: 3.对方PC或手机有以上条件. AnyChat音视频

【转】iOS平台的应用程序调试与分析

转自:看雪学院的文章 iOS平台的应用程序调试与分析 作者:zhuliang转载请保证文章完整并注明来自看雪或cd-team 本文阐述如何在iOS平台上对应用程序进行调试与分析,旨在指导新手分析iOS程序,高手请无视.内容包括软件硬件的准备.代码的解密.符号信息的获取.用gdb调试等,最后以京东LeBook为例子进行演示.1.为什么要进行调试与分析研究iOS程序有很多用处,比如:找bug或者漏洞,想知道某程序有没有漏洞或者bug.某程序能实现某功能,我想知道如何实现,如ios6发短信功能,还有比