前言
AAC音频数据格式是一个很常见的音频压缩编码数据格式,经常会有这样的需求,通过麦克风采集到的PCM格式音频数据,编码为AAC格式音频数据,然后通过网络发给另一端,网络的另一端接收到AAC格式音频数据后,解码为PCM格式音频数据。本文将介绍用AudioToobox框架实现音频的编解码
AAC数据格式封装格式ADTS解析
原始音频编码后形成的裸的AAC数据是无法直接解码的,一般会在前面添加能够描述音频信息(如采样率,采样格式,声道数)的头部信息,常见的有ADTS封装格式,如下:
(ADTS头部)(压缩的AAC音频数据)
ADTS头部
它描述了音频的采样率,采样格式,声道数等信息;具体格式参考如下网站:
http://wiki.multimedia.cx/index.php?title=ADTS
http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Channel_Configurations
下面是由压缩的AAC数据长度/声道数,采样率为44.1kHz生成ADTS头部的代码:
- (NSData *)getADTSDataWithPacketLength:(NSInteger)packetLength channel:(int)channel
{
int adtsLength = 7;
char *packet = malloc(sizeof(char) * adtsLength);
// Variables Recycled by addADTStoPacket
int profile = 2; //AAC LC 编码压缩级别
int freqIdx = 4; //44.1KHz // 采样率
int chanCfg = channel; // 声道数
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[1] = (char)0xF1; // 1111 0 00 1 = syncword MPEG-4 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数据解析出声道数,采样率的代码
// 解析ADTS 头部的采样率,声道数等信息
- (ADAudioFormat)getADTSInfo:(NSData *)adtsData
{
const unsigned char buff[10];
[adtsData getBytes:(void*)buff length:adtsData.length];
unsigned long long adts = 0;
const unsigned char *p = buff;
adts |= *p ++; adts <<= 8;
adts |= *p ++; adts <<= 8;
adts |= *p ++; adts <<= 8;
adts |= *p ++; adts <<= 8;
adts |= *p ++; adts <<= 8;
adts |= *p ++; adts <<= 8;
adts |= *p ++;
ADAudioFormat format;
// 获取声道数
format.channels = (adts >> 30) & 0x07;
// 获取采样率
format.samplerate = (adts >> 34) & 0x0f;
return format;
}
AudioToolbox编码PCM音频数据为AAC音频数据
1、获取原始PCM音频数据
这里用AVCaptureSession实现。代码如下
// 1、初始化AVCaptureSession
self.captureSession = [[AVCaptureSession alloc] init];
// 2、获取音频设备对象
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
// 3、根据音频设备对象生成对应的音频输入对象,并将该音频输入对象添加到AVCaptureSession中
self.audioCaptureInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:nil];
if ([self.captureSession canAddInput:self.audioCaptureInput]) {
NSLog(@"添加了输入");
[self.captureSession addInput:self.audioCaptureInput];
}
// 4、创建音频数据输出对象并将该输出对象添加到AVCaptureSession中
self.audioCaptureOutput = [[AVCaptureAudioDataOutput alloc] init];
if ([self.captureSession canAddOutput:self.audioCaptureOutput]) {
NSLog(@"添加了输出");
[self.captureSession addOutput:self.audioCaptureOutput];
}
// 5、设置音频输出对象的回调
[self.audioCaptureOutput setSampleBufferDelegate:self queue:_audioQueue];
// 6、启动运行
[self.captureSession startRunning];
2、在回调中获取原始音频数据和数据格式
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
if (self.audioCaptureOutput == output) { // 音频输出
}
}
这里解释下CMSampleBufferRef,它是一个描述音视频数据对象的结构体,它既可以用于描述压缩的音/视频,也可以用于描述原始的音/视频;位于CoreMedia下的CMSampleBuffer.h头文件下,一般是AVCaptureOutput生成的对象
1、它包含了音/视频的数据格式
2、它包含了具体的音/视频数据(压缩或者未压缩的)
然后将CMSampleBufferRef中的原始音频数据的格式及具体的PCM音频数据提取出来
// 提取音频数据
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
CFRetain(blockBuffer);
size_t dataSize=0;
char *buffer = (char*)malloc(1024*10*4);
OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &dataSize, &buffer);
if (status != kCMBlockBufferNoErr) {
NSLog(@"没有出错");
} else {
NSLog(@"获取的数据大小 %ld",dataSize);
}
// 提取音频数据格式
// 初始化
AudioStreamBasicDescription inASBD = *CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
3、初始化编码器
首先定义压缩后的aac格式描述对象
AudioStreamBasicDescription destASBD = {0};
destASBD.mFormatID = kAudioFormatMPEG4AAC; // 表示AAC编码
destASBD.mFormatFlags = kMPEG4Object_AAC_LC; // aac 编码的 profile
destASBD.mSampleRate = sourceASBD.mSampleRate; // 采样率不变,与原始PCM数据保持一致
destASBD.mChannelsPerFrame = sourceASBD.mChannelsPerFrame; // 声道数不变,与原始PCM数据保持一致
destASBD.mFramesPerPacket = 1024; // 对于aac的固定码率方式 值为1024
destASBD.mBitsPerChannel = 0; // 填0就好
destASBD.mBytesPerFrame = 0; // 填0就好
destASBD.mBytesPerPacket = 0; // 填0就好
destASBD.mReserved = 0;
然后根据输入PCM音频数据数据格式和这里定义的压缩后的AAC编码的数据格式创建编码转换器对象AudioConverterRef
这里解释下AudioConverterRef
位于AudioToolbox下的AudioConvert.h中定义,它的作用如下:
1、编码
2、解码
3、采样率/声道数/采样格式的转换
- (id)initWithSourceASBD:(AudioStreamBasicDescription)sourceASBD destASBD:(AudioStreamBasicDescription)destASBD
{
if (self = [super init]) {
// 创建用于格式转化的缓冲区,避免重复创建内存
_buffer = (uint8_t*)malloc(max_buffer_size);
_bufferSize = max_buffer_size;
memset(_buffer, 0, _bufferSize);
_sourceASBD = sourceASBD;
_destASBD = destASBD;
// 编码器参数
AudioClassDescription classspecific = [self classDesWithFormatPropertyId:kAudioFormatProperty_Encoders subType:kAudioFormatMPEG4AAC manufacturer:kAppleHardwareAudioCodecManufacturer];
// 根据指定的格式参数创建格式转换器
CheckStatusReturn(AudioConverterNewSpecific(&sourceASBD, &destASBD, 1, &classspecific, &_audioConverter),@"AudioConverterNewSpecific fail");
// //也可以用下面这种方式
// const OSType subtype = kAudioFormatMPEG4AAC;
// AudioClassDescription classspecific[2] = {
// {
// kAudioEncoderComponentType,
// subtype,
// kAppleSoftwareAudioCodecManufacturer
// },
// };
// CheckStatusReturn(AudioConverterNewSpecific(&sourceASBD, &destASBD, 2, classspecific, &_audioConverter),@"AudioConverterNewSpecific fail");
}
return self;
}
先看一下classDesWithFormatPropertyId方法,它定义了编码相关的参数,比如是使用硬编码还是软编码等等
/** 创建格式转换器时的参数
*/
- (AudioClassDescription)classDesWithFormatPropertyId:(AudioFormatPropertyID)formatPropertyId
subType:(AudioFormatID)type
manufacturer:(UInt32)manufacturer
{
AudioClassDescription returnClassDes = {0};
OSStatus status = noErr;
// 获取指定的AudioFormatPropertyID的格式参数下inSpecifier类型为type的有多少个AudioClassDescription表示的属性
UInt32 size;
status = AudioFormatGetPropertyInfo(formatPropertyId, sizeof(type), &type, &size);
if (status != noErr) {
NSLog(@"AudioFormatGetPropertyInfo fail %d",status);
}
// 获取指定的AudioFormatPropertyID的格式参数下inSpecifier类型为type的指定数目的AudioClassDescription表示的属性
UInt32 count = size / sizeof(AudioClassDescription);
AudioClassDescription descriptions[count];
status = AudioFormatGetProperty(formatPropertyId,
sizeof(type),
&type,
&size,
descriptions);
if (status) {
NSLog(@"error getting audio format propery: %d", (int)(status));
}
// 匹配指定的属性,然后拷贝过去
for (unsigned int i = 0; i < count; i++) {
if ((type == descriptions[i].mSubType) &&
(manufacturer == descriptions[i].mManufacturer)) {
memcpy(&returnClassDes, &(descriptions[i]), sizeof(returnClassDes));
}
}
return returnClassDes;
}
然后使用AudioConverterNewSpecific()方法创建编码器
4、传入原始音频数据进行编码
对于第二步的回调函数提供的音频数据是CMSampleBufferRef类型的数据,我们可以将其中的音频数据提取出来组装成AudioUnitBuffList格式,如下
// 输入音频的数据格式
size_t size;
const AudioChannelLayout *layout = CMAudioFormatDescriptionGetChannelLayout(CMSampleBufferGetFormatDescription(sampleBuffer), &size);
AudioBufferList inputBufferlist;
inputBufferlist.mNumberBuffers = 1;
inputBufferlist.mBuffers[0].mNumberChannels = AudioChannelLayoutTag_GetNumberOfChannels(layout->mChannelLayoutTag);
inputBufferlist.mBuffers[0].mData = malloc(dataSize);
inputBufferlist.mBuffers[0].mDataByteSize = (UInt32)dataSize;
memcpy(inputBufferlist.mBuffers[0].mData, buffer, dataSize);
// 将PCM数据编码为ADTS封装的AAC数据格式
- (BOOL)doEncodeBufferList:(AudioBufferList)fromBufferList toADTSData:(NSData**)todata
{
if (!_audioConverter) {
NSLog(@"转换器还没有创建");
return NO;
}
int size = fromBufferList.mBuffers[0].mDataByteSize;
if (size<= 0) {
NSLog(@"fromBufferList 中没有数据");
return NO;
}
// 对于编码数据来说 没有planner的概念
UInt32 channel = fromBufferList.mBuffers[0].mNumberChannels;
AudioBufferList outAudioBufferList = {0};
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = channel;
outAudioBufferList.mBuffers[0].mDataByteSize = _bufferSize;
outAudioBufferList.mBuffers[0].mData = _buffer;
UInt32 ioOutputDataPacketSize = 1;
OSStatus status = AudioConverterFillComplexBuffer(_audioConverter,inInputDataProc, &fromBufferList, &ioOutputDataPacketSize,&outAudioBufferList, NULL);
if (status == 0){
NSData *rawAAC = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
NSData *adtsHeader = [self getADTSDataWithPacketLength:rawAAC.length channel:channel];
NSMutableData *fullData = [NSMutableData dataWithData:adtsHeader];
[fullData appendData:rawAAC];
*todata = fullData;
}else{
NSLog(@"音频编码失败");
return NO;
}
return YES;
}
请看NSData *adtsHeader = [self getADTSDataWithPacketLength:rawAAC.length channel:channel];这一行
,他的作用是调用前面介绍的ADTS头部生成函数生成7个字节的ADTS头部
最后将ADTS头部与原始的AAC裸数据合并组成ADTS封装格式的数据然后返回
以上就是音频编码为AAC并封装为ADTS格式的全过程,最后我们可以将最后一步获取到的adts格式数据直接写入到.aac后缀结尾的文件中,生成的文件可以直接用PC或者手机播放
AAC数据解码为PCM音频数据
前面我们知道,要解码AAC格式的压缩数据,必须要有头部信息才行,比如ADTS封装格式的AAC数据,接下来我们就简单点,直接解码前面第四步获取到的ADTS数据
1、首先创建解码器
解码器和编码器一样,都是由AudioConverterRef实现的,他们是个逆过程,也就是前面第3步中代码行的AudioConverterNewSpecific(&sourceASBD,&destASBD, 1, &classspecific, &_audioConverter))中的参数sourceASBD和destASBD调换一下即可
其它调用
2、AAC解码为PCM
解码之前首先得将ADTS头部信息剥离出来,并且解析出采样率,采样格式等等信息
Byte crcFlag;
[fromData getBytes:&crcFlag range:NSMakeRange(1, 1)];
// 先去掉头部
NSData *realyData = nil;
NSData *adtsData = nil;
if (crcFlag & 0x08) { // 说明ADTS头部占用7个字节
realyData = [fromData subdataWithRange:NSMakeRange(7, fromData.length-7)];
adtsData = [fromData subdataWithRange:NSMakeRange(0,7)];
} else { // 说明ADTS头部占用9个字节
realyData = [fromData subdataWithRange:NSMakeRange(9, fromData.length-9)];
adtsData = [fromData subdataWithRange:NSMakeRange(0,9)];
}
ADAudioFormat format = [self getADTSInfo:adtsData];
AudioBufferList fromBufferlist;
fromBufferlist.mNumberBuffers = 1;
fromBufferlist.mBuffers[0].mNumberChannels = format.channels;
fromBufferlist.mBuffers[0].mData = (void*)malloc(realyData.length);
fromBufferlist.mBuffers[0].mDataByteSize = (UInt32)realyData.length;
其它代码与编码一模一样