音頻信息是如何捕捉的呢?主要通過圖一的過程:
自然界中的聲音非常復雜,波形極其復雜,通常我們采用的是脈沖代碼調制編碼,即PCM編碼。PCM通過抽樣、量化、編碼三個步驟將連續變化的模擬信號轉換為數字編碼。
- 抽樣:對模擬信號進行周期性掃描,把時間上連續的信號變成時間上離散的信號;
- 量化:用一組規定的電平,把瞬時抽樣值用最接近的電平值來表示,通常是用二進制表示;
- 編碼:用一組二進制碼組來表示每一個有固定電平的量化值;
采樣后的數據大小 = 采樣率值×采樣大小值×聲道數 bps。一個采樣率為44.1KHz,采樣大小為16bit,雙聲道的PCM編碼的WAV文件,它的數據速率=44.1K×16×2 bps=1411.2 Kbps= 176.4 KB/s。
AAC高級音頻編碼
AAC(Advanced Audio Coding),中文名:高級音頻編碼,出現于1997年,基于MPEG-2的音頻編碼技術。由Fraunhofer IIS、杜比實驗室、AT&T、Sony等公司共同開發,目的是取代MP3格式。
AAC音頻格式
AAC音頻格式有ADIF和ADTS:
- ADIF:Audio Data Interchange Format 音頻數據交換格式。這種格式的特征是可以確定的找到這個音頻數據的開始,不需進行在音頻數據流中間開始的解碼,即它的解碼必須在明確定義的開始處進行。故這種格式常用在磁盤文件中。
-
ADTS:Audio Data Transport Stream 音頻數據傳輸流。這種格式的特征是它是一個有同步字的比特流,解碼可以在這個流中任何位置開始。它的特征類似于mp3數據流格式。
image.png
iOS上把PCM音頻編碼成AAC音頻流
- 設置編碼器(codec),并開始錄制;
- 收集到PCM數據,傳給編碼器;
-
編碼完成回調callback,寫入文件。
image.png
音頻壓縮原理
- 時域冗余
- 頻域冗余
- 聽覺冗余
這部分過濾原理可以百度看下,簡單點說就是在時間空間維度上過濾掉一部分人耳聽不到的部分頻率。
AudioToolbox 的具體代碼實現
首先定義一個類來做配置,我們要了解下幾個概念
采樣頻率(sampleRate):也稱為采樣速度或者采樣率,定義了每秒從連續信號中提取并組成離散信號的采樣個數,它用赫茲(Hz)來表示。采樣頻率的倒數是采樣周期,它是采樣之間的時間間隔。通俗的講采樣頻率是指計算機每秒鐘采集多少個信號樣本。采樣頻率越高聲音的還原就越真實越自然。
8,000 Hz是電話所用采樣率, 對于人的說話已經足夠
11,025 Hz是AM調幅廣播所用采樣率
22,050 Hz和24,000 Hz- FM是調頻廣播所用采樣率
32,000 Hz是miniDV 數碼視頻 camcorder、DAT (LP mode)所用采樣率
44,100 Hz是音頻 CD, 也常用于 MPEG-1 音頻(VCD, SVCD, MP3)所用采樣率 (超過該采樣率,人耳很難分辨)
47,250 Hz是商用 PCM 錄音機所用采樣率
48,000 Hz是miniDV、數字電視、DVD、DAT、電影和專業音頻所用的數字聲音所用采樣率
50,000 Hz是商用數字錄音機所用采樣率
96,000 或者 192,000 Hz - DVD-Audio、一些 LPCM DVD 音軌、BD-ROM(藍光盤)音軌、和 HD-DVD (高清晰度 DVD)音軌所用所用采樣率
2.8224 MHz是Direct Stream Digital 的 1 位 sigma-delta modulation 過程所用采樣率。
音頻比特率
音頻的比特率公式: 比特率=采樣率 * 單個的周期音頻數據長度 (sampleSize)。
如16bit 單聲道(channelCount) 48KHz音頻的比特率:
48KHz * (16 * 1) = 1536kbps = 192 kBps
@interface SQAudioConfig : NSObject
@property(nonatomic,assign)NSInteger bitrate;
@property(nonatomic,assign)NSInteger channelCount;
/**采樣率*/
@property (nonatomic, assign) NSInteger sampleRate;//(默認44100)
/**采樣點量化*/
@property (nonatomic, assign) NSInteger sampleSize;//(16)
@implementation SQAudioConfig
+ (instancetype)defaultConifg {
return [[SQAudioConfig alloc] init];
}
- (instancetype)init
{
self = [super init];
if (self) {
self.bitrate = 96000;
self.channelCount = 1;
self.sampleSize = 16;
self.sampleRate = 44100;
//比特率 = 44100*1*16;
}
return self;
}
@end
接下來我們要配置我們的編碼器,通過AudioConverterNewSpecific來創建一個新專用編碼器,根據輸入音頻數據描述參數和輸出音頻數據描述,用AudioStreamBasicDescription封裝描述信息。
//配置音頻編碼參數
-(void)setupEncoderWithSampleBuffer: (CMSampleBufferRef)sampleBuffer{
//通過輸入的sampleBuffer來獲取輸入描述信息
AudioStreamBasicDescription inputAduioDes =* CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
//設置輸出參數
AudioStreamBasicDescription outputAudioDes ={0};
outputAudioDes.mSampleRate = (Float64)_config.sampleRate; //采樣率
outputAudioDes.mFormatID = kAudioFormatMPEG4AAC; //輸出格式
outputAudioDes.mFormatFlags = kMPEG4Object_AAC_LC; // 如果設為0 代表無損編碼
outputAudioDes.mBytesPerPacket = 0; //自己確定每個packet 大小
outputAudioDes.mFramesPerPacket = 1024; //每一個packet幀數 AAC-1024;
outputAudioDes.mBytesPerFrame = 0; //每一幀大小
outputAudioDes.mChannelsPerFrame = (uint32_t)_config.channelCount; //輸出聲道數
outputAudioDes.mBitsPerChannel = 0; //數據幀中每個通道的采樣位數。
outputAudioDes.mReserved = 0; //對其方式 0(8字節對齊)
//填充輸出相關信息
UInt32 outDesSize = sizeof(outputAudioDes);
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outDesSize, &outputAudioDes);
//獲取編碼器的描述信息(只能傳入software)
AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
/** 創建converter
參數1:輸入音頻格式描述
參數2:輸出音頻格式描述
參數3:class desc的數量
參數4:class desc
參數5:創建的解碼器
*/
OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
if (status != noErr) {
NSLog(@"Error!:硬編碼AAC創建失敗, status= %d", (int)status);
return;
}
// 設置編解碼質量
/*
kAudioConverterQuality_Max = 0x7F,
kAudioConverterQuality_High = 0x60,
kAudioConverterQuality_Medium = 0x40,
kAudioConverterQuality_Low = 0x20,
kAudioConverterQuality_Min = 0
*/
UInt32 temp = kAudioConverterQuality_High;
//編解碼器的呈現質量
AudioConverterSetProperty(_audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp);
//設置比特率
uint32_t audioBitrate = (uint32_t)self.config.bitrate;
uint32_t audioBitrateSize = sizeof(audioBitrate);
status = AudioConverterSetProperty(_audioConverter, kAudioConverterEncodeBitRate, audioBitrateSize, &audioBitrate);
if (status != noErr) {
NSLog(@"Error!:硬編碼AAC 設置比特率失敗");
}
}
/**
獲取編碼器類型描述
參數1:類型
*/
- (AudioClassDescription *)getAudioCalssDescriptionWithType: (AudioFormatID)type fromManufacture: (uint32_t)manufacture {
static AudioClassDescription desc;
UInt32 encoderSpecific = type;
//獲取滿足AAC編碼器的總大小
UInt32 size;
/**
參數1:編碼器類型
參數2:類型描述大小
參數3:類型描述
參數4:大小
*/
OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size);
if(status != noErr){
NSLog(@"Error!:硬編碼AAC get info 失敗, status= %d", (int)status);
return nil;
}
//計算aac編碼器的個數
unsigned int count = size / sizeof(AudioClassDescription);
//創建一個包含count個編碼器的數組
AudioClassDescription description[count];
//將滿足aac編碼的編碼器的信息寫入數組
status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size, &description);
for (unsigned int i = 0; i < count; i++) {
if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
desc = description[i];
return &desc;
}
}
return nil;
}
對編碼器做好配置后,我們就可以對捕獲到的PCM數據進行處理,AVFoudation捕捉到的數據是封裝在CMSampleBufferRef這個結構體里。通過CMSampleBufferRef可以獲取數據所在結構體CMBlockBufferRef,然后調用CMBlockBufferGetDataPointer來獲取數據內存地址。然后通過AudioConverterFillComplexBuffer來實現編碼,需要注意的是這個函數需要將pcm數據再進行一次封裝(有點繞),封裝成AudioBufferList.然后編寫我們的回調函數AudioConverterFillComplexBuffer,這個函數主要用于向提供輸入數據,也就是你要轉換的數據,在這里封裝成AudioBufferList。誤區:
這外面定義的outAudioBufferList和里面的參數ioData是不同的AudioBufferList一個是輸出一個是輸入。
/**編碼*/
- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer{
CFRetain(sampleBuffer);
if(!_audioConverter){
[self setupEncoderWithSampleBuffer:sampleBuffer];
}
__weak typeof(self) weakSelf=self;
dispatch_async(_encoderQueue, ^{
//3.獲取CMBlockBuffer, 這里面保存了PCM數據
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
CFRetain(blockBuffer);
OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
//5.判斷status狀態
NSError *error = nil;
if (status != kCMBlockBufferNoErr) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
NSLog(@"Error: ACC encode get data point error: %@",error);
return;
}
uint8_t *pcmBuffer = malloc(weakSelf.pcmBufferSize);
memset(pcmBuffer, 0, weakSelf.pcmBufferSize);
//3.輸出buffer
/*
typedef struct AudioBufferList {
UInt32 mNumberBuffers;
AudioBuffer mBuffers[1];
} AudioBufferList;
struct AudioBuffer
{
UInt32 mNumberChannels;
UInt32 mDataByteSize;
void* __nullable mData;
};
typedef struct AudioBuffer AudioBuffer;
*/
//將pcmBuffer數據填充到outAudioBufferList 對象中
AudioBufferList outAudioBufferList = {0};
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)_config.channelCount;
outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)_pcmBufferSize;
outAudioBufferList.mBuffers[0].mData = pcmBuffer;
//輸出包大小為1
UInt32 outputDataPacketSize = 1;
//配置填充函數,獲取輸出數據
//轉換由輸入回調函數提供的數據
/*
參數1: inAudioConverter 音頻轉換器
參數2: inInputDataProc 回調函數.提供要轉換的音頻數據的回調函數。當轉換器準備好接受新的輸入數據時,會重復調用此回調.
參數3: inInputDataProcUserData
參數4: inInputDataProcUserData,self
參數5: ioOutputDataPacketSize,輸出緩沖區的大小
參數6: outOutputData,需要轉換的音頻數據
參數7: outPacketDescription,輸出包信息
*/
status = AudioConverterFillComplexBuffer(_audioConverter, aacEncodeInputDataProc, (__bridge void * _Nullable)(self), &outputDataPacketSize, &outAudioBufferList, NULL);
if (status == noErr) {
//獲取數據
NSData *rawAAC = [NSData dataWithBytes: outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
//釋放pcmBuffer
free(pcmBuffer);
//添加ADTS頭,想要獲取裸流時,請忽略添加ADTS頭,寫入文件時,必須添加
// NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
// NSMutableData *fullData = [NSMutableData dataWithCapacity:adtsHeader.length + rawAAC.length];;
// [fullData appendData:adtsHeader];
// [fullData appendData:rawAAC];
//將數據傳遞到回調隊列中
dispatch_async(weakSelf.callbackQueue, ^{
[_delegate audioEncodeCallBack:rawAAC];
});
} else {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
}
CFRelease(blockBuffer);
CFRelease(sampleBuffer);
if (error) {
NSLog(@"error: AAC編碼失敗 %@",error);
}
});
}
static OSStatus aacEncodeInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
//獲取self
SQAudioEncoder *aacEncoder = (__bridge SQAudioEncoder *)(inUserData);
//判斷pcmBuffsize大小
if (!aacEncoder.pcmBufferSize) {
*ioNumberDataPackets = 0;
return - 1;
}
//填充
ioData->mBuffers[0].mData = aacEncoder.pcmBuffer;
ioData->mBuffers[0].mDataByteSize = (uint32_t)aacEncoder.pcmBufferSize;
ioData->mBuffers[0].mNumberChannels = (uint32_t)aacEncoder.config.channelCount;
//填充完畢,則清空數據
aacEncoder.pcmBufferSize = 0;
*ioNumberDataPackets = 1;
return noErr;
}
完整源碼
//
// SQAudioEncoder.h
// CPDemo
//
// Created by Sem on 2020/8/13.
// Copyright ? 2020 SEM. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import "SQAVConfig.h"
NS_ASSUME_NONNULL_BEGIN
@protocol SQAudioEncoderDelegate<NSObject>
-(void)audioEncodeCallBack:(NSData *)aacData;
@end
@interface SQAudioEncoder : NSObject
/**編碼器配置*/
@property (nonatomic, strong) SQAudioConfig *config;
@property (nonatomic, weak) id<SQAudioEncoderDelegate> delegate;
/**初始化傳入編碼器配置*/
- (instancetype)initWithConfig:(SQAudioConfig*)config;
/**編碼*/
- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer;
@end
NS_ASSUME_NONNULL_END
//
// SQAudioEncoder.m
// CPDemo
//
// Created by Sem on 2020/8/13.
// Copyright ? 2020 SEM. All rights reserved.
//
#import "SQAudioEncoder.h"
#import "SQAVConfig.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
@interface SQAudioEncoder()
@property (nonatomic, strong) dispatch_queue_t encoderQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;
//對音頻轉換器對象
@property (nonatomic, unsafe_unretained) AudioConverterRef audioConverter;
//PCM緩存區
@property (nonatomic) char *pcmBuffer;
//PCM緩存區大小
@property (nonatomic) size_t pcmBufferSize;
@end
@implementation SQAudioEncoder
- (instancetype)initWithConfig:(SQAudioConfig*)config{
self = [super init];
if(self){
//音頻編碼隊列
_encoderQueue = dispatch_queue_create("aac hard encoder queue", DISPATCH_QUEUE_SERIAL);
//音頻回調隊列
_callbackQueue = dispatch_queue_create("aac hard encoder callback queue", DISPATCH_QUEUE_SERIAL);
//音頻轉換器
_audioConverter = NULL;
_pcmBufferSize = 0;
_pcmBuffer = NULL;
_config = config;
if (config == nil) {
_config = [[SQAudioConfig alloc] init];
}
}
return self;
}
//配置音頻編碼參數
-(void)setupEncoderWithSampleBuffer: (CMSampleBufferRef)sampleBuffer{
AudioStreamBasicDescription inputAduioDes =* CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
//設置輸出參數
AudioStreamBasicDescription outputAudioDes ={0};
outputAudioDes.mSampleRate = (Float64)_config.sampleRate; //采樣率
outputAudioDes.mFormatID = kAudioFormatMPEG4AAC; //輸出格式
outputAudioDes.mFormatFlags = kMPEG4Object_AAC_LC; // 如果設為0 代表無損編碼
outputAudioDes.mBytesPerPacket = 0; //壓縮的時候設置0
outputAudioDes.mFramesPerPacket = 1024; //每一個packet幀數 AAC-1024;
outputAudioDes.mBytesPerFrame = 0; //壓縮的時候設置0
outputAudioDes.mChannelsPerFrame = (uint32_t)_config.channelCount; //輸出聲道數
outputAudioDes.mBitsPerChannel = 0; //數據幀中每個通道的采樣位數。壓縮的時候設置0
outputAudioDes.mReserved = 0; //對其方式 0(8字節對齊)
//填充輸出相關信息
UInt32 outDesSize = sizeof(outputAudioDes);
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outDesSize, &outputAudioDes);
//獲取編碼器的描述信息(只能傳入software)
AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
/** 創建converter
參數1:輸入音頻格式描述
參數2:輸出音頻格式描述
參數3:class desc的數量
參數4:class desc
參數5:創建的解碼器
*/
OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
if (status != noErr) {
NSLog(@"Error!:硬編碼AAC創建失敗, status= %d", (int)status);
return;
}
// 設置編解碼質量
/*
kAudioConverterQuality_Max = 0x7F,
kAudioConverterQuality_High = 0x60,
kAudioConverterQuality_Medium = 0x40,
kAudioConverterQuality_Low = 0x20,
kAudioConverterQuality_Min = 0
*/
UInt32 temp = kAudioConverterQuality_High;
//編解碼器的呈現質量
AudioConverterSetProperty(_audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp);
//設置比特率
uint32_t audioBitrate = (uint32_t)self.config.bitrate;
uint32_t audioBitrateSize = sizeof(audioBitrate);
status = AudioConverterSetProperty(_audioConverter, kAudioConverterEncodeBitRate, audioBitrateSize, &audioBitrate);
if (status != noErr) {
NSLog(@"Error!:硬編碼AAC 設置比特率失敗");
}
}
/**
獲取編碼器類型描述
參數1:類型
*/
- (AudioClassDescription *)getAudioCalssDescriptionWithType: (AudioFormatID)type fromManufacture: (uint32_t)manufacture {
static AudioClassDescription desc;
UInt32 encoderSpecific = type;
//獲取滿足AAC編碼器的總大小
UInt32 size;
/**
參數1:編碼器類型
參數2:類型描述大小
參數3:類型描述
參數4:大小
*/
OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size);
if(status != noErr){
NSLog(@"Error!:硬編碼AAC get info 失敗, status= %d", (int)status);
return nil;
}
//計算aac編碼器的個數
unsigned int count = size / sizeof(AudioClassDescription);
//創建一個包含count個編碼器的數組
AudioClassDescription description[count];
//將滿足aac編碼的編碼器的信息寫入數組
status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size, &description);
for (unsigned int i = 0; i < count; i++) {
if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
desc = description[i];
return &desc;
}
}
return nil;
}
/**編碼*/
- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer{
CFRetain(sampleBuffer);
if(!_audioConverter){
[self setupEncoderWithSampleBuffer:sampleBuffer];
}
__weak typeof(self) weakSelf=self;
dispatch_async(_encoderQueue, ^{
//3.獲取CMBlockBuffer, 這里面保存了PCM數據
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
CFRetain(blockBuffer);
OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
//5.判斷status狀態
NSError *error = nil;
if (status != kCMBlockBufferNoErr) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
NSLog(@"Error: ACC encode get data point error: %@",error);
return;
}
uint8_t *pcmBuffer = malloc(weakSelf.pcmBufferSize);
memset(pcmBuffer, 0, weakSelf.pcmBufferSize);
//3.輸出buffer
/*
typedef struct AudioBufferList {
UInt32 mNumberBuffers;
AudioBuffer mBuffers[1];
} AudioBufferList;
struct AudioBuffer
{
UInt32 mNumberChannels;
UInt32 mDataByteSize;
void* __nullable mData;
};
typedef struct AudioBuffer AudioBuffer;
*/
//將pcmBuffer數據填充到outAudioBufferList 對象中
AudioBufferList outAudioBufferList = {0};
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)_config.channelCount;
outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)_pcmBufferSize;
outAudioBufferList.mBuffers[0].mData = pcmBuffer;
//輸出包大小為1
UInt32 outputDataPacketSize = 1;
//配置填充函數,獲取輸出數據
//轉換由輸入回調函數提供的數據
/*
參數1: inAudioConverter 音頻轉換器
參數2: inInputDataProc 回調函數.提供要轉換的音頻數據的回調函數。當轉換器準備好接受新的輸入數據時,會重復調用此回調.
參數3: inInputDataProcUserData
參數4: inInputDataProcUserData,self
參數5: ioOutputDataPacketSize,輸出緩沖區的大小
參數6: outOutputData,需要轉換的音頻數據
參數7: outPacketDescription,輸出包信息
*/
status = AudioConverterFillComplexBuffer(_audioConverter, aacEncodeInputDataProc, (__bridge void * _Nullable)(self), &outputDataPacketSize, &outAudioBufferList, NULL);
if (status == noErr) {
//獲取數據
NSData *rawAAC = [NSData dataWithBytes: outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
//釋放pcmBuffer
free(pcmBuffer);
//添加ADTS頭,想要獲取裸流時,請忽略添加ADTS頭,寫入文件時,必須添加
// NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
// NSMutableData *fullData = [NSMutableData dataWithCapacity:adtsHeader.length + rawAAC.length];;
// [fullData appendData:adtsHeader];
// [fullData appendData:rawAAC];
//將數據傳遞到回調隊列中
dispatch_async(weakSelf.callbackQueue, ^{
[_delegate audioEncodeCallBack:rawAAC];
});
} else {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
}
CFRelease(blockBuffer);
CFRelease(sampleBuffer);
if (error) {
NSLog(@"error: AAC編碼失敗 %@",error);
}
});
}
static OSStatus aacEncodeInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
//獲取self
SQAudioEncoder *aacEncoder = (__bridge SQAudioEncoder *)(inUserData);
//判斷pcmBuffsize大小
if (!aacEncoder.pcmBufferSize) {
*ioNumberDataPackets = 0;
return - 1;
}
//填充
ioData->mBuffers[0].mData = aacEncoder.pcmBuffer;
ioData->mBuffers[0].mDataByteSize = (uint32_t)aacEncoder.pcmBufferSize;
ioData->mBuffers[0].mNumberChannels = (uint32_t)aacEncoder.config.channelCount;
//填充完畢,則清空數據
aacEncoder.pcmBufferSize = 0;
*ioNumberDataPackets = 1;
return noErr;
}
- (void)dealloc {
if (_audioConverter) {
AudioConverterDispose(_audioConverter);
_audioConverter = NULL;
}
}
@end
解碼AAC
解碼是相反的,我們從AAC輸入 ,然后輸出PCM,其他和編碼差不多,還是通過AudioConverterNewSpecific函數創建音頻轉換器
AudioStreamBasicDescription outputAudioDes = {0};
outputAudioDes.mSampleRate = (Float64)_config.sampleRate; //采樣率
outputAudioDes.mChannelsPerFrame = (UInt32)_config.channelCount; //輸出聲道數
outputAudioDes.mFormatID = kAudioFormatLinearPCM; //輸出格式
outputAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //編碼 12
outputAudioDes.mFramesPerPacket = 1; //每一個packet幀數 ;
outputAudioDes.mBitsPerChannel = 16; //數據幀中每個通道的采樣位數。
outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame; //每一幀大小(采樣位數 / 8 *聲道數)
outputAudioDes.mBytesPerPacket = outputAudioDes.mBytesPerFrame * outputAudioDes.mFramesPerPacket; //每個packet大小(幀大小 * 幀數)
outputAudioDes.mReserved = 0; //對其方式 0(8字節對齊)
//輸入參數
AudioStreamBasicDescription inputAduioDes = {0};
inputAduioDes.mSampleRate = (Float64)_config.sampleRate;
inputAduioDes.mFormatID = kAudioFormatMPEG4AAC;
inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
inputAduioDes.mFramesPerPacket = 1024;
inputAduioDes.mChannelsPerFrame = (UInt32)_config.channelCount;
解碼數據的時候我們自己定義一個結構體來封裝ACC數據
typedef struct {
char * data;
UInt32 size;
UInt32 channelCount;
AudioStreamPacketDescription packetDesc;
} SQAudioUserData;
接下來基本和編碼一樣,解碼源碼
//
// SQAudioDecode.h
// CPDemo
//
// Created by Sem on 2020/8/13.
// Copyright ? 2020 SEM. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@class SQAudioConfig;
NS_ASSUME_NONNULL_BEGIN
/**AAC解碼回調代理*/
@protocol SQAudioDecoderDelegate <NSObject>
- (void)audioDecodeCallback:(NSData *)pcmData;
@end
@interface SQAudioDecode : NSObject
@property (nonatomic, strong) SQAudioConfig *config;
@property (nonatomic, weak) id<SQAudioDecoderDelegate> delegate;
//初始化 傳入解碼配置
- (instancetype)initWithConfig:(SQAudioConfig *)config;
/**解碼aac*/
- (void)decodeAudioAACData: (NSData *)aacData;
@end
NS_ASSUME_NONNULL_END
//
// SQAudioDecode.m
// CPDemo
//
// Created by Sem on 2020/8/13.
// Copyright ? 2020 SEM. All rights reserved.
//
#import "SQAudioDecode.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import "SQAVConfig.h"
typedef struct {
char * data;
UInt32 size;
UInt32 channelCount;
AudioStreamPacketDescription packetDesc;
} SQAudioUserData;
@interface SQAudioDecode()
@property (strong, nonatomic) NSCondition *converterCond;
@property (nonatomic, strong) dispatch_queue_t decoderQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;
@property (nonatomic) AudioConverterRef audioConverter;
@property (nonatomic) char *aacBuffer;
@property (nonatomic) UInt32 aacBufferSize;
@property (nonatomic) AudioStreamPacketDescription *packetDesc;
@end
@implementation SQAudioDecode
//初始化 傳入解碼配置
- (instancetype)initWithConfig:(SQAudioConfig *)config{
self =[super init];
if(self){
_decoderQueue = dispatch_queue_create("aac hard decoder queue", DISPATCH_QUEUE_SERIAL);
_callbackQueue = dispatch_queue_create("aac hard decoder callback queue", DISPATCH_QUEUE_SERIAL);
_audioConverter = NULL;
_aacBufferSize = 0;
_aacBuffer = NULL;
_config = config;
if (_config == nil) {
_config = [[SQAudioConfig alloc] init];
}
AudioStreamPacketDescription desc = {0};
_packetDesc = &desc;
[self setupEncoder];
}
return self;
}
- (void)setupEncoder {
AudioStreamBasicDescription outputAudioDes = {0};
outputAudioDes.mSampleRate = (Float64)_config.sampleRate; //采樣率
outputAudioDes.mChannelsPerFrame = (UInt32)_config.channelCount; //輸出聲道數
outputAudioDes.mFormatID = kAudioFormatLinearPCM; //輸出格式
outputAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //編碼 12
outputAudioDes.mFramesPerPacket = 1; //每一個packet幀數 ;
outputAudioDes.mBitsPerChannel = 16; //數據幀中每個通道的采樣位數。
outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame; //每一幀大小(采樣位數 / 8 *聲道數)一般是1或者2
outputAudioDes.mBytesPerPacket = outputAudioDes.mBytesPerFrame * outputAudioDes.mFramesPerPacket; //每個packet大小(幀大小 * 幀數)
outputAudioDes.mReserved = 0; //對其方式 0(8字節對齊)
//輸入參數
AudioStreamBasicDescription inputAduioDes = {0};
inputAduioDes.mSampleRate = (Float64)_config.sampleRate;
inputAduioDes.mFormatID = kAudioFormatMPEG4AAC;
inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
inputAduioDes.mFramesPerPacket = 1024;
inputAduioDes.mChannelsPerFrame = (UInt32)_config.channelCount;
//填充輸出相關信息
UInt32 inDesSize = sizeof(inputAduioDes);
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inputAduioDes);
//獲取解碼器的描述信息(只能傳入software)
AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
/** 創建converter
參數1:輸入音頻格式描述
參數2:輸出音頻格式描述
參數3:class desc的數量
參數4:class desc
參數5:創建的解碼器
*/
OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
if (status != noErr) {
NSLog(@"Error!:硬解碼AAC創建失敗, status= %d", (int)status);
return;
}
}
/**
獲取解碼器類型描述
參數1:類型
*/
- (AudioClassDescription *)getAudioCalssDescriptionWithType: (AudioFormatID)type fromManufacture: (uint32_t)manufacture {
static AudioClassDescription desc;
UInt32 decoderSpecific = type;
//獲取滿足AAC解碼器的總大小
UInt32 size;
/**
參數1:編碼器類型(解碼)
參數2:類型描述大小
參數3:類型描述
參數4:大小
*/
OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Decoders, sizeof(decoderSpecific), &decoderSpecific, &size);
if (status != noErr) {
NSLog(@"Error!:硬解碼AAC get info 失敗, status= %d", (int)status);
return nil;
}
//計算aac解碼器的個數
unsigned int count = size / sizeof(AudioClassDescription);
//創建一個包含count個解碼器的數組
AudioClassDescription description[count];
//將滿足aac解碼的解碼器的信息寫入數組
status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(decoderSpecific), &decoderSpecific, &size, &description);
if (status != noErr) {
NSLog(@"Error!:硬解碼AAC get propery 失敗, status= %d", (int)status);
return nil;
}
for (unsigned int i = 0; i < count; i++) {
if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
desc = description[i];
return &desc;
}
}
return nil;
}
- (void)dealloc {
if (_audioConverter) {
AudioConverterDispose(_audioConverter);
_audioConverter = NULL;
}
}
/**解碼aac*/
- (void)decodeAudioAACData: (NSData *)aacData{
if (!_audioConverter) { return; }
__weak typeof(self) weakSelf=self;
dispatch_async(_decoderQueue, ^{
//記錄aac 作為參數參入解碼回調函數
SQAudioUserData userData = {0};
userData.channelCount = (UInt32)weakSelf.config.channelCount;
userData.data = (char *)[aacData bytes];
userData.size = (UInt32)aacData.length;
userData.packetDesc.mDataByteSize = (UInt32)aacData.length;
userData.packetDesc.mStartOffset = 0;
userData.packetDesc.mVariableFramesInPacket = 0;
//輸出大小和packet個數
UInt32 pcmBufferSize = (UInt32)(2048 * _config.channelCount);
UInt32 pcmDataPacketSize = 1024;
//創建臨時容器pcm
uint8_t *pcmBuffer = malloc(pcmBufferSize);
memset(pcmBuffer, 0, pcmBufferSize);
//輸出buffer
AudioBufferList outAudioBufferList = {0};
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)_config.channelCount;
outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)pcmBufferSize;
outAudioBufferList.mBuffers[0].mData = pcmBuffer;
//輸出描述
AudioStreamPacketDescription outputPacketDesc = {0};
//配置填充函數,獲取輸出數據
OSStatus status = AudioConverterFillComplexBuffer(_audioConverter, &AudioDecoderConverterComplexInputDataProc, &userData, &pcmDataPacketSize, &outAudioBufferList, &outputPacketDesc);
if (status != noErr) {
NSLog(@"Error: AAC Decoder error, status=%d",(int)status);
return;
}
//如果獲取到數據
if (outAudioBufferList.mBuffers[0].mDataByteSize > 0) {
NSData *rawData = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
dispatch_async(weakSelf.callbackQueue, ^{
[weakSelf.delegate audioDecodeCallback:rawData];
});
}
free(pcmBuffer);
});
}
//解碼器回調函數
static OSStatus AudioDecoderConverterComplexInputDataProc( AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
SQAudioUserData *audioDecoder = (SQAudioUserData *)(inUserData);
if (audioDecoder->size <= 0) {
ioNumberDataPackets = 0;
return -1;
}
//填充數據
*outDataPacketDescription = &audioDecoder->packetDesc;
(*outDataPacketDescription)[0].mStartOffset = 0;
(*outDataPacketDescription)[0].mDataByteSize = audioDecoder->size;
(*outDataPacketDescription)[0].mVariableFramesInPacket = 0;
ioData->mBuffers[0].mData = audioDecoder->data;
ioData->mBuffers[0].mDataByteSize = audioDecoder->size;
ioData->mBuffers[0].mNumberChannels = audioDecoder->channelCount;
return noErr;
}
@end