iOS下Apple为咱们提供了很是方便的音频编解码工具AudioToolbox。该工具中包含了常见的编解码库,如AAC、iLBC、OPUS等。今天咱们就介绍一下如何使用 AudioToolbox 进行AAC音频的编码工做。数组
在 iOS 中进行AAC编码的流程比较简单,按如下几步便可完成。bash
下面咱们详细介绍每一步。ide
在建立编码器以前,咱们首先要设置好编码器的输入数据格式和输出数据格式。好比输入数据是单声道仍是双声道,数据是什么格式的,采样率是多少等。一样的,输出参数是AAC,仍是OPUS? 每一个传输包的大小等。只有这样,AudioToolbox才清楚他要建立一个什么样的编解码器。函数
固然,这与建立编码器的函数也有关。该函数的前两个输入参数就是音频输入格式和输出格式。函数原型以下:工具
AudioConverterNewSpecific(
inSourceFormat: AudioStreamBasicDescription, //输入参数
inDestinationFormat: AudioStreamBasicDescription, //输出参数
inNumberClassDescriptions: UInt32, //音频描述符数量
inClassDescriptions: AudioClassDescription, //音频描述符数组
outAudioConverter: AudioConverterRef //编码器
) -> OSStatus
复制代码
因此,基于以上两个缘由,在建立编码器以前必定要先将输入、输出格式设置好。ui
下面咱们来看一下设置输入、输出格式的代码。this
AudioStreamBasicDescription inAudioStreamBasicDescription =
*CMAudioFormatDescriptionGetStreamBasicDescription((CMAudioFormatDescriptionRef)
CMSampleBufferGetFormatDescription(sampleBuffer));
复制代码
上面这段代码就是输入格式的设置。这里用到了一个小技巧,设置编码器的输入格式是经过传入的第一个音频数据包来得到的。由于,在iOS中每一个音视频的输入数据中都包含了必要的参数。而iOS也为咱们提供了提取这些数据的方法,很是方便。编码
下面的代码是对编码器输出格式的设置。 注释已经写的很是详细了。spa
// 先将输出描述符清0
AudioStreamBasicDescription outAudioStreamBasicDescription = {0};
// 设置采样率,有 32K, 44.1K,48K
outAudioStreamBasicDescription.mSampleRate = 44100;
// 音频格式能够设置为 :
// kAudioFormatMPEG4AAC_HE
// kAudioFormatMPEG4AAC_HE_V2
// kAudioFormatMPEG4AAC
outAudioStreamBasicDescription.mFormatID = kAudioFormatMPEG4AAC;
// 指明格式的细节. 设置为 0 说明没有子格式。
// 若是 mFormatID 设置为 kAudioFormatMPEG4AAC_HE 该值应该为0
outAudioStreamBasicDescription.mFormatFlags = kMPEG4Object_AAC_LC;
// 每一个音频包的字节数.
// 该字段设置为 0, 代表包里的字节数是变化的。
// 对于使用可变包大小的格式,请使用AudioStreamPacketDescription结构指定每一个数据包的大小。
outAudioStreamBasicDescription.mBytesPerPacket = 0;
// 每一个音频包帧的数量. 对于未压缩的数据设置为 1.
// 动态码率格式,这个值是一个较大的固定数字,好比说AAC的1024。
// 若是是动态帧数(好比Ogg格式)设置为0。
outAudioStreamBasicDescription.mFramesPerPacket = 1024;
// 每一个帧的字节数。对于压缩数据,设置为 0.
outAudioStreamBasicDescription.mBytesPerFrame = 0;
// 音频声道数
outAudioStreamBasicDescription.mChannelsPerFrame = 1;
// 压缩数据,该值设置为0.
outAudioStreamBasicDescription.mBitsPerChannel = 0;
// 用于字节对齐,必须是0.
outAudioStreamBasicDescription.mReserved = 0;
复制代码
下一步,咱们来建立编码器。指针
建立编码器除了上面说的要设置输入输出数据格式外,还要告诉 AudioToolbox 是建立编码器仍是建立解码器;是建立 AAC 的,仍是建立OPUS的;是硬编码仍是软编码。
iOS为咱们提供了 AudioClassDescription 来描述这些信息。它包括下面三个字段:
struct AudioClassDescription {
OSType mType;
OSType mSubType;
OSType mManufacturer;
};
复制代码
kAudioDecoderComponentType/kAudioEncoderComponentType。
了解了上面的信息后,咱们再来看下面的代码就很好理解了。
kMPEG4Object_AAC_LC
...
AudioClassDescription audioClassDescription;
memset(&audioClassDescription, 0, sizeof(audioClassDescription));
UInt32 size;
//根据编码格式,获取描述符个数。
NSAssert(AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
sizeof(outAudioStreamBasicDescription.mFormatID),
&outAudioStreamBasicDescription.mFormatID,
&size) == noErr, nil);
uint32_t count = size / sizeof(AudioClassDescription);
//取出全部的描述符
AudioClassDescription descriptions[count];
NSAssert(AudioFormatGetProperty(kAudioFormatProperty_Encoders,
sizeof(outAudioStreamBasicDescription.mFormatID),
&outAudioStreamBasicDescription.mFormatID,
&size,
descriptions) == noErr, nil);
//找出与输出格式一致的软编描述符
for (uint32_t i = 0; i < count; i++) {
if ((outAudioStreamBasicDescription.mFormatID == descriptions[i].mSubType) &&
(kAppleSoftwareAudioCodecManufacturer == descriptions[i].mManufacturer)) {
memcpy(&audioClassDescription, &descriptions[i], sizeof(audioClassDescription));
}
}
//建立软编码器
NSAssert(audioClassDescription.mSubType == outAudioStreamBasicDescription.mFormatID &&
audioClassDescription.mManufacturer == kAppleSoftwareAudioCodecManufacturer, nil);
AudioConverterRef audioConverter;
memset(&audioConverter, 0, sizeof(audioConverter));
NSAssert(AudioConverterNewSpecific(&inAudioStreamBasicDescription,
&outAudioStreamBasicDescription,
1,
&audioClassDescription,
&audioConverter) == 0, nil);
...
复制代码
建立好编码器后,还要修改一下编码器的码率。若是要正确的编码,编码码率参数是必须设置的。代码以下:
...
UInt32 outputBitrate = 64000;
UInt32 propSize = sizeof(outputBitrate);
if(result == noErr) {
result = AudioConverterSetProperty(audioConverter,
kAudioConverterEncodeBitRate,
propSize,
&outputBitrate);
}
...
复制代码
须要注意,AAC并非随便的码率均可以支持。好比,若是PCM采样率是44100KHz,那么码率能够设置64000bps,若是是16K,能够设置为32000bps。
设置好码率后,能够经过 AudioConverterGetProperty 方法查询一下是否已经设置成功。代码以下:
UInt32 value = 0;
size = sizeof(value);
AudioConverterGetProperty(audioConverter,
kAudioConverterPropertyMaximumOutputPacketSize,
&size,
&value);
复制代码
下面咱们来看下如何进行转码。
iOS 使用 AudioConverterFillComplexBuffer 方法进行转码。它的参数以下:
AudioConverterFillComplexBuffer(
inAudioConverter: AudioConverterRef,
inInputDataProc: AudioConverterComplexInputDataProc,
inInputDataProcUserData: UnsafeMutablePointer,
ioOutputDataPacketSize: UnsafeMutablePointer<UInt32>,
outOutputData: UnsafeMutablePointer<AudioBufferList>,
outPacketDescription: AudioStreamPacketDescription
) -> OSStatus
复制代码
下面是转码的具体代码:
//设置输入
AudioBufferList inAaudioBufferList;
CMBlockBufferRef blockBuffer;
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &inAaudioBufferList, sizeof(inAaudioBufferList), NULL, NULL, 0, &blockBuffer);
NSAssert(inAaudioBufferList.mNumberBuffers == 1, nil);
//设置输出
uint32_t bufferSize = inAaudioBufferList.mBuffers[0].mDataByteSize;
uint8_t *buffer = (uint8_t *)malloc(bufferSize);
memset(buffer, 0, bufferSize);
AudioBufferList outAudioBufferList;
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = inAaudioBufferList.mBuffers[0].mNumberChannels;
outAudioBufferList.mBuffers[0].mDataByteSize = bufferSize;
outAudioBufferList.mBuffers[0].mData = buffer;
UInt32 ioOutputDataPacketSize = 1;
//转码
NSAssert(
AudioConverterFillComplexBuffer(audioConverter,
inInputDataProc,
&inAaudioBufferList,
&ioOutputDataPacketSize,
&outAudioBufferList, NULL) == 0,
nil);
//将输出数据变成 NSData 数据
NSData *data = [NSData
dataWithBytes:outAudioBufferList.mBuffers[0].mData
length:outAudioBufferList.mBuffers[0].mDataByteSize];
free(buffer);
CFRelease(blockBuffer);
复制代码
下面咱们看一下 inInputDataProc 这个回调函数的具体实现。其中 inUserData
就是在 AudioConverterFillComplexBuffer 方法中传入的第三个参数,也就是输入数据。
inInputDataProc 回调函数的做用就是将输入数据拷贝到 ioData 中。ioData 就是编码器编码时用到的真正输入缓冲区。
OSStatus inInputDataProc(AudioConverterRef inAudioConverter,
UInt32 *ioNumberDataPackets,
AudioBufferList *ioData,
AudioStreamPacketDescription **outDataPacketDescription,
void *inUserData)
{
AudioBufferList audioBufferList = *(AudioBufferList *)inUserData;
ioData->mBuffers[0].mData = audioBufferList.mBuffers[0].mData;
ioData->mBuffers[0].mDataByteSize = audioBufferList.mBuffers[0].mDataByteSize;
return noErr;
}
复制代码
至此,AAC编码部分就已经分析完了。但不少时候咱们须要将 AAC 数据保存成文件。若是咱们直接将一帧一帧的AAC数据直接写入文件,再从AAC文件中读取数据交由解码器解码,是没法成功的。缘由很简单,解码器搞不清楚文件里每一个 AAC 帧到底有多大。
解决的办法是在每一帧前加一个头。这是一个比较通用的作法。在AAC中加的头格式咱们称为 ADTS头。
ADTS共7或9个字节。通常状况下使用 7 字节。它的结构以下:
Structure
AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
Letter Length (bits) Description
下面是具体代码。经过上面的描述就很是容易理解了。
- (NSData*) adtsDataForPacketLength:(NSUInteger)packetLength {
int adtsLength = 7;
char *packet = malloc(sizeof(char) * adtsLength);
// Variables Recycled by addADTStoPacket
int profile = 2; //AAC LC
//39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
int freqIdx = 4; //44.1KHz
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;
}
复制代码
本文主要讲解了 iOS 下 如何进行 AAC 编码。它的流程丰常简单。包括:
这里的难点是参数的设置。并且不少参数之间是联动的,因此设置时要特别当心。
另外,经过本文你能够了解到,其实在iOS下,其它音频编码的流程与AAC编码的流程都是同样的,咱们只须要调整不一样的参数便可。
但愿本文能对您有所帮助。并请多多关注。谢谢!