Core Audio
Core Audio提供了數字音頻服務為iOS與OS X, 它提供了一系列框架去處理音頻.
Core Audio中包含我們最常用的Audio Toolbox與Audio Unit框架.
- 使用Audio Queue做錄制,播放,暫停,循環與同步音頻
- 使用Audio File, Converter, Codec Services去從磁盤讀取與寫入以及執行音頻轉換等功能.
- 使用Audio Unit與Audio Processing Graph在應用程序中管理音頻單元.在OS X中可以自定義audio units.
- 使用Music Sequencing Services播放基于MIDI控制的音頻數據
- 使用Core Audio Clock Services用于音頻和MIDI同步以及時間格式管理
- 使用System Sound Services播放系統聲音與界面的音效.
Core Audio在iOS中針對移動平臺的計算資源作出了優化,同時,音頻服務必須嚴格由系統進行管理,特別是HAL與I/O Kit,然而Apple也提供了只在iOS平臺中才有的服務,如Audio Session Service將幫助我們管理音頻上下文.
1. Digital Audio與Linear PCM
PCM是最常用的無損壓縮數字音頻格式數據,根據采樣率以規則間隔測量模擬(真實世界)數字音頻信號并將每個采集到的樣本轉換為數值來創建PCM數據.如標準光盤(CD)音頻使用44.1 kHz的采樣率,16位整數描述每個樣本 - 構成分辨率或位深度。
- sample:一個采樣點是對單聲道采集到聲音的數值
- frame:一幀數據是一組時間一致的samples,如雙聲道聲音文件中一幀有兩個samples,一個左聲道,一個右聲道.
- packet:一個或多個連續幀的集合.在線性PCM中,一個packet總是單幀.在其他壓縮格式中,一個packet定義給定音頻數據格式的最小有意義的幀組。
iOS中使用integer與fixed-point音頻數據,目的是在處理音頻數據時增加計算速度,減小電池能耗.iOS也提供了來自Audio Converter Services的Converter audio unit服務.
iOS與OS X中,Core Audio提供了最常用的文件格式用于存儲域播放音頻數據.
2.Audio Unit
Apple針對移動平臺對iOS的Audio Unit作出了效率與性能優化,在開發中我們必須將audio unit靜態編譯進APP,所以無法使用別的APP中的Audio Unit.
3.HAL(Hardware Abstraction Layer)
大多情況下,我們無法直接與HAL進行交互,Apple提供了一個特別的audio unit,即OS X中的AUHAL, iOS中的AURemoteIO, 我們可以通過它們讓音頻與硬件交互.
4.Properties, Scopes, and Elements
Core Audio接口中使用property管理對象的行為與狀態.
- 屬性通常用易記憶的關鍵字格式,如kAudioFilePropertyFileFormat or kAudioQueueDeviceProperty_NumberChannels.
- 屬性值適用于特定的數據類型,如void*, Float64, AudioChannelLayout...
- Core Audio對象有一個內部結構,其中每一部分都有屬于自己的屬性,如一個audio unit對象都有一個input scope, output scope, global scope. 每個scope由一個或多個elements(類似于音頻總線)組成.
5.回調函數
Core Audio中常用回調函數以實現音頻數據通信,回調函數常有一下功能
- 提供給應用程序音頻數據(如:用麥克風進行錄制,將麥克風采集的數據通過回調函數傳給使用者)
- 從應用程序中請求音頻數據(如:播放回調)
- 監聽某個對象狀態的變化
為了去使用回調函數,我們需要做以下兩件事情
- 注冊回調函數(如實現錄制,播放回調,需要我們在初始化時提供一個函數)
- 實現回調函數的功能.(實現初始化時提供的函數)
Note: 在OC中,回調函數是一個C語言形式的函數,我們回調OC本類對象作為對象傳入其中, 所以回調函數中不能直接引用
self.xxx
,需要借助傳入的OC對象去實現本類的功能.
6. 音頻數據格式
Core Audio封裝了音頻數據格式,我們只需要對給定結構體賦正確的參數即可。
struct AudioStreamBasicDescription {
Float64 mSampleRate;
UInt32 mFormatID;
UInt32 mFormatFlags;
UInt32 mBytesPerPacket;
UInt32 mFramesPerPacket;
UInt32 mBytesPerFrame;
UInt32 mChannelsPerFrame;
UInt32 mBitsPerChannel;
UInt32 mReserved;
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
struct AudioStreamPacketDescription {
SInt64 mStartOffset;
UInt32 mVariableFramesInPacket;
UInt32 mDataByteSize;
};
typedef struct AudioStreamPacketDescription AudioStreamPacketDescription;
注意,上面結構體中mReserved
是Apple的保留參數,必須為0. 其他一些參數在特定情況下也需為0,如:壓縮音頻格式每個sample使用不同數量的bits。對于這些格式,mBitsPerChannel成員的值為0。
- 為AudioStreamBasicDescription賦值
你可以手動為ASBD的成員賦值,如果有些值是你不知道的,可以賦0,Core Audio將自動選擇適當的值。
- 標準的音頻數據格式
iOS: 線性PCM 16bit integer, Noninterleaved linear PCM 8.24bit 定點samples
struct AudioStreamBasicDescription {
mSampleRate = 44100.0;
mFormatID = kAudioFormatLinearPCM;
mFormatFlags = kAudioFormatFlagsAudioUnitCanonical;
mBitsPerChannel = 8 * sizeof (AudioUnitSampleType); // 32 bits
mChannelsPerFrame = 2;
mBytesPerFrame = mChannelsPerFrame * sizeof (AudioUnitSampleType); // 8 bytes
mFramesPerPacket = 1;
mBytesPerPacket = mFramesPerPacket * mBytesPerFrame; // 8 bytes
mReserved = 0;
};
7. Magic Cookie
在Core Audio中,magic cookie表示被附加到壓縮音頻數據(文件或流)中的元數據(metadata)。元數據為解碼器提供了正確解碼文件或流所需要的詳細信息。Core Audio可以復制,讀取,使用元數據包含的信息。
下面的例子展示了如何將一個文件中magic cookie拷貝提供給audio queue.
- (void) copyMagicCookieToQueue: (AudioQueueRef) queue fromFile: (AudioFileID) file {
UInt32 propertySize = sizeof (UInt32);
OSStatus result = AudioFileGetPropertyInfo (
file,
kAudioFilePropertyMagicCookieData,
&propertySize,
NULL
);
if (!result && propertySize) {
char *cookie = (char *) malloc (propertySize);
AudioFileGetProperty (
file,
kAudioFilePropertyMagicCookieData,
&propertySize,
cookie
);
AudioQueueSetProperty (
queue,
kAudioQueueProperty_MagicCookie,
cookie,
propertySize
);
free (cookie);
}
}
8.Audio Data Packets
音頻數據包(packet)是一個或多個幀的集合,對于特定音頻格式,它是有意義的最小幀集合,因此它是最佳表示一段時間音頻數據的單位。
- CBR(固定的比特率):PCM,IMA,ADPCM,所有packet具有相同size.
- VBR(可變的比特率):AAC,MP3,Apple Lossless,所有packet都具有相同的幀數,但是每一幀中的位數不同。
- VFR(可變的幀率): 每個包中具有不同的幀數,沒有這種類型常用的格式。
在CBR,VBR的格式中,對于給定的音頻文件或流,每秒鐘的包數是固定的,
9.數據格式轉換
使用audio converter可以改變音頻采樣率,交錯或不交錯,以及壓縮與未壓縮數據格式相互轉換。
- 將壓縮數據格式(如AAC)轉成線性PCM格式
- 將線性PCM格式轉成其他格式
- 在16位signed integer線性PCM與8.24定點PCM間相互轉換。
10.音頻文件
Core Audio中使用Audio File Service為創建與訪問音頻文件及包含在其中元數據提供了一個強大的抽象。我們不僅可以使用文件的ID,type,數據格式,還可以添加標記,循環,回放等等功能。
- 創建一個音頻文件
- 確定文件路徑(CFURL/NSURL)
- 確定文件標識符(ex CAF:kAudioFileCAFType)
- 放在文件中的ABSD。
AudioFileCreateWithURL (
audioFileURL,
kAudioFileCAFType,
&audioFormat,
kAudioFileFlags_EraseFile,
&audioFileID // the function provides the new file object here
);
- 打開一個音頻文件
使用AudioFileOpenURL
函數打開一個文件,提供URL,文件類型,訪問權限成功后返回一個文件ID,使用這個ID以及常用函數可以檢索我們需要的文件信息。下面列舉了一些常用函數
kAudioFilePropertyFileFormat
kAudioFilePropertyDataFormat
kAudioFilePropertyMagicCookieData
kAudioFilePropertyChannelLayout
當一個VBR文件過大時,檢索信息速度會較慢,可以使用kAudioFilePropertyPacketSizeUpperBound and kAudioFilePropertyEstimatedDuration.
這兩個函數快速獲取近似值。
-
讀寫文件
- 讀寫包僅僅針對VBR數據
- 使用基于包的操作更容易計算時間
擴展
Core Audio提供了一個的API,稱為擴展音頻文件服務。該接口包含音頻文件服務和音頻轉換器服務中的基本功能,提供與線性PCM之間的自動數據轉換iPhone 支持的Audio file格式
Format name | Format filename extensions |
---|---|
AIFF | .aif,.aiff |
CAF | .caf |
MPEG-1,layer 3 | .mp3 |
MPEG-2 or MPEG-4 ADTS | .aac |
MPEG-4 | .m4a, .mp4 |
WAV | .wav |
AC-3 (Dolby Digital) | .ac3 |
Enhanced AC-3 (Dolby Digital Plus) | .ec3 |
iOS與OS X中原生音頻文件格式為CAF(Core Audio Format),它可以支持平臺中任意音頻數據格式。它沒有大小限制,可以支持多種元數據,如聲道信息,文本注釋等
11.音頻流
與音頻文件不同,我們無法確定一個audio file stream(音頻流)的開始與結束點.因為我們往往是通過網絡接受音頻流數據,開始與結束的時機取決于用戶的交互,并且,音頻流數據也無法保證一定可以獲取,因為網絡傳輸中可能會存儲在丟幀,暫停等等情況.
Audio File Service可以通過解析(parse)讓我們使用音頻流.通過回調函數獲取parse到的一系列音頻數據.
12.Audio Sessions: 配合Core Audio工作
在iOS中,有時我們需要處理高優先級任務,如接電話,如果當前APP正在播放視頻,我們必須做出符合用戶期望的事情以協調APP與系統電話.Audio Session對象充當了兩者之間的一個中介.每個iPhone應用程序只有一個audio session,通過配置其屬性以使用.
開始之前,我們要明確下面幾個問題
- 如何讓應用程序響應一些意外中斷,如接電話
- 你打算讓你的音頻與其他應用程序中的音頻混合起來,還是打算對它們做靜音操作
- 應用程序如何響應音頻線路改變,如用戶插拔耳機
為了解決上面的問題,我們需要配置audio session使用如下特性
Audio Session feature | Description |
---|---|
Categories | 一個category標識著一組音頻行為的鍵,通過設置分類,可以表明音頻的行為,如鎖屏時是否應該繼續播放音頻. |
Interruptions and route changes | 當音頻被中斷或音頻線路發生改變時,audio session將發送一個通知,通過接收通知以作出相應響應. |
Hardware characteristics | 通過audio session可以查詢當前設備的一些硬件信息,如采樣率,聲道數,輸入源設備等 |
- Audio Session默認行為
- 當用戶將手機按鍵中的靜音撥動時,音頻將會被靜音
- 當用戶按下鎖屏鍵時,音頻會被靜音
- 當你的音頻啟用時,當前正在播放的其他應用程序的音頻會被靜音
以上行為是audio session默認分類(kAudioSessionCategory_SoloAmbientSound)的行為
- 中斷:停用與激活(Deactivation and Activation)
默認的audio session中缺少一個重要功能就是在中斷后無法恢復.audio session有兩個重要狀態:激活,停用.音頻僅能夠在激活狀態下使用.
啟動時,默認的audio session是激活狀態,然而,如果有電話打進來(interruption),audio session馬上處于停用狀態且應用程序音頻停止.如果用戶選擇忽略當前電話,你的應用程序繼續運行,但是audio session仍是未激活狀態,音頻無法繼續工作.
如果應用程序中使用OpenAL, I/O unit, Audio Queue Services,我們必須寫一個監聽中斷的回調函數,在中斷結束后重新激活audio session.
- 決定輸入源是否可用
使用錄制功能的APP是否能錄制取決于當前選擇的硬件音頻輸入端,使用kAudioSessionProperty_AudioInputAvailable
可以測試當前輸入端是否可用
UInt32 audioInputIsAvailable;
UInt32 propertySize = sizeof (audioInputIsAvailable);
AudioSessionGetProperty (
kAudioSessionProperty_AudioInputAvailable,
&propertySize,
&audioInputIsAvailable // A nonzero value on output means that
// audio input is available
);
- 使用Audio Session
應用程序僅有一個audio session分類在同一時間(此規則的一個例外是使用System Sound Services播放的音頻 - 用于警報和用戶界面聲音效果的API。此類音頻始終使用最低優先級的音頻會話類別),
13.使用AVAudioPlayer播放
如果你的應用程序不需要雙聲道,精確同步以及播放網絡流音頻,可以使用AVAudioPlayer
類實現簡單的音頻播放.
以下使用范圍
- 播放任意時間段音頻
- 從文件或內存中播放音頻
- 循環音頻
- 同時播放多個音頻
- 控制正在播放每個聲音相對播放水平
- 尋找音頻文件中特定點,支持快進快退
- 獲取音量
NSString *soundFilePath =
[[NSBundle mainBundle] pathForResource: @"sound"
ofType: @"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
AVAudioPlayer *newPlayer =
[[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
error: nil];
[fileURL release];
self.player = newPlayer;
[newPlayer release];
[self.player prepareToPlay];
[self.player setDelegate: self];
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) player
successfully: (BOOL) flag {
if (flag == YES) {
[self.button setTitle: @"Play" forState: UIControlStateNormal];
}
}
- (IBAction) playOrPause: (id) sender {
// if already playing, then pause
if (self.player.playing) {
[self.button setTitle: @"Play" forState: UIControlStateHighlighted];
[self.button setTitle: @"Play" forState: UIControlStateNormal];
[self.player pause];
// if stopped or paused, start playing
} else {
[self.button setTitle: @"Pause" forState: UIControlStateHighlighted];
[self.button setTitle: @"Pause" forState: UIControlStateNormal];
[self.player play];
}
[self.player setVolume: 1.0]; // available range is 0.0 through 1.0
14.錄制與播放 Audio Queue Services
Audio Queue Services提供了一種低開銷,直接的方式去錄制和播放音頻,它使你的應用程序使用硬件(麥克風與揚聲器)錄制與播放并且無需了解硬件接口.它也讓我們使用復雜的編解碼器而無需了解編解碼器的工作原理.
Audio Queue提供了更精確的定時播放以支持預定播放與同步,你可以使用它去同步多個音頻播放隊列,同時播放聲音,獨立控制每個隊里的音量以及循環播放.
Audio Queue與AVAudioPlayer兩者是在iPhone上播放音頻的唯一方式
- 錄制與播放的回調函數
通過屬性與回調函數讓我們與audio queue對象間交互.對于錄制,我們通過回調函數接收音頻數據.
對于播放回調,當你的音頻播放隊列需要播放一個音頻數據時它將被調用.你的回調函數將從磁盤讀取指定數量的音頻數據包然后將它們封裝在audio queue對象的buffer中.audio queue將按順序播放這些buffer.
- 創建Audio Queue
- AudioQueueNewInput:創建錄制audio queue對象
- AudioQueueNewOutput: 創建播放audio queue對象
實現一個播放隊列
a. 創建一個結構體管理audio queue需要的信息,如音頻格式,采樣率等等
b. 定義一個回調函數管理audio queue buffers,這個回調函數使用Audio File Services去讀取你想要播放的文件.
c. 初始化audio queue并且使用AudioQueueNewOutput創建對象.
static const int kNumberBuffers = 3;
// Create a data structure to manage information needed by the audio queue
struct myAQStruct {
AudioFileID mAudioFile;
CAStreamBasicDescription mDataFormat;
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers[kNumberBuffers];
SInt64 mCurrentPacket;
UInt32 mNumPacketsToRead;
AudioStreamPacketDescription *mPacketDescs;
bool mDone;
};
// Define a playback audio queue callback function
static void AQTestBufferCallback(
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inCompleteAQBuffer
) {
myAQStruct *myInfo = (myAQStruct *)inUserData;
if (myInfo->mDone) return;
UInt32 numBytes;
UInt32 nPackets = myInfo->mNumPacketsToRead;
AudioFileReadPackets (
myInfo->mAudioFile,
false,
&numBytes,
myInfo->mPacketDescs,
myInfo->mCurrentPacket,
&nPackets,
inCompleteAQBuffer->mAudioData
);
if (nPackets > 0) {
inCompleteAQBuffer->mAudioDataByteSize = numBytes;
AudioQueueEnqueueBuffer (
inAQ,
inCompleteAQBuffer,
(myInfo->mPacketDescs ? nPackets : 0),
myInfo->mPacketDescs
);
myInfo->mCurrentPacket += nPackets;
} else {
AudioQueueStop (
myInfo->mQueue,
false
);
myInfo->mDone = true;
}
}
// Instantiate an audio queue object
AudioQueueNewOutput (
&myInfo.mDataFormat,
AQTestBufferCallback,
&myInfo,
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes,
0,
&myInfo.mQueue
);
- 控制Audio Queue播放的音量
Audio queue對象提供了兩種方式控制播放音量,一種是直接設置,如下,設置后可以立即生效.
Float32 volume = 1;
AudioQueueSetParameter (
myAQstruct.audioQueueObject,
kAudioQueueParam_Volume,
volume
);
另一種是使用AudioQueueEnqueueBufferWithParameters
,設置后在audio queue buffer開始播放時生效.
- Indicating Audio Queue Playback Level
通過查詢audio queue對象的kAudioQueueProperty_CurrentLevelMeterDB
屬性可以獲取當前播放的級別.
typedef struct AudioQueueLevelMeterState {
Float32 mAveragePower;
Float32 mPeakPower;
}; AudioQueueLevelMeterState;
- 同時播放多個聲音
為了同時播放多個音頻,需要為每個音頻創建一個播放audio queue對象.對于每個audio queue,使用AudioQueueEnqueueBufferWithParameters
函數安排第一個音頻buffer同時啟動。
同時播放多個音頻,音頻格式顯得至關重要,因為iOS中某些音頻格式使用了高效的硬件編解碼器,只能在設備上播放以下格式之一的單個實例.
a. AAC
b. ALAC
c. MP3
如果要播放高質量同步的音頻,需要使用線性PCM或IMA4格式.
a. 線性PCM和IMA / ADPCM(IMA4)音頻您可以在iOS中同時播放多個線性PCM或IMA4格式聲音,而不會產生CPU資源問題。
b. AAC,MP3和Apple Lossless(ALAC)一次只能播放一首此類聲音
15.使用OpenAL定位播放
開源的OpenAL音頻API(可在OpenAL框架中使用,構建于Core Audio之上)針對播放期間的聲音定位進行了優化。使用OpenGL建模的界面,OpenAL可以輕松播放,定位,混合和移動聲音,OpenAL和OpenGL共享一個通用坐標系統,使您可以同步音頻和視頻。OpenAL直接使用Core Audio的I / O audio unit),從而實現最低延遲播放。OpenAL是在iPhone和iPod touch上播放游戲應用中的聲音效果的最佳選擇。
16.系統聲音
Audio Toolbox中的AudioServices.h提供了系統的聲音服務,當你僅僅想播放一個系統的短音頻時,它將是最好的選擇,iOS中播放系統聲音最不不能超過30秒.
在iOS中,調用AudioServicesPlaySystemSound
可以立即播放,你也可以調用AudioServicesPlayAlertSound
提示用戶是否播放.
調用AudioServicesPlaySystemSound
時使用kSystemSoundID_Vibrate
常量可以顯式設置振動效果.
#include <AudioToolbox/AudioToolbox.h>
#include <CoreFoundation/CoreFoundation.h>
// Define a callback to be called when the sound is finished
// playing. Useful when you need to free memory after playing.
static void MyCompletionCallback (
SystemSoundID mySSID,
void * myURLRef
) {
AudioServicesDisposeSystemSoundID (mySSID);
CFRelease (myURLRef);
CFRunLoopStop (CFRunLoopGetCurrent());
}
int main (int argc, const char * argv[]) {
// Set up the pieces needed to play a sound.
SystemSoundID mySSID;
CFURLRef myURLRef;
myURLRef = CFURLCreateWithFileSystemPath (
kCFAllocatorDefault,
CFSTR ("../../ComedyHorns.aif"),
kCFURLPOSIXPathStyle,
FALSE
);
// create a system sound ID to represent the sound file
OSStatus error = AudioServicesCreateSystemSoundID (myURLRef, &mySSID);
// Register the sound completion callback.
// Again, useful when you need to free memory after playing.
AudioServicesAddSystemSoundCompletion (
mySSID,
NULL,
NULL,
MyCompletionCallback,
(void *) myURLRef
);
// Play the sound file.
AudioServicesPlaySystemSound (mySSID);
// Invoke a run loop on the current thread to keep the application
// running long enough for the sound to play; the sound completion
// callback later stops this run loop.
CFRunLoopRun ();
return 0;
}
17.Audio Unit
在iOS中,Audio Unit為應用程序提供了實現低延遲輸入和輸出的機制。它們還提供某些DSP功能.
iOS中Audio Unit輸入輸出使用8.24位定點線性PCM音頻數據.唯一例外的是以下情況.
- 3D mix unit: 允許任意數量的單聲道輸入,每個輸入可以是8位或16位線性PCM.在8.24位定點PCM中提供一個立體聲輸出,3D混音器單元對其輸入執行采樣率轉換,并對每個輸入通道提供大量控制。此控件包括這些更改的音量,靜音,平移,距離衰減和速率控制。以編程方式,這是kAudioUnitSubType_AU3DMixerEmbedded單元。
- Multichannel mixer unit: 允許任意數量的單聲道或立體聲輸入,每個輸入可以是16位線性或8.24位定點PCM。在8.24位定點PCM中提供一個立體聲輸出。您的應用程序可以靜音和取消靜音每個輸入通道以及控制其音量。以編程方式,這是kAudioUnitSubType_MultiChannelMixer單元。
- Converter unit: 提供采樣率,位深度和位格式(線性到定點)轉換。 iPhone converter unit’s的規范數據格式是8.24位定點PCM。它轉換為此格式或從此格式轉換。以編程方式,這是kAudioUnitSubType_AUConverter單元。
- I/O unit:提供實時音頻輸入和輸出,并根據需要執行采樣率轉換。以編程方式,這是kAudioUnitSubType_RemoteIO單元。
- iPod EQ unit:提供可在應用程序中使用的簡單均衡器,并在內置iPhone iPod應用程序中提供相同的預設。 iPod EQ單元使用8.24位定點PCM輸入和輸出。以編程方式,這是kAudioUnitSubType_AUiPodEQ單元。
每個Audio Unit的唯一標識符由類型,子類型,制造商代碼(type, subtype, and manufacturer code)確定.每種子類型更加精確的描述了audio unit的用途.Audio Unit使用屬性配置音頻信息,如 Properties, Scopes, and Elements.每種audio unit需要一些指定屬性,
18.編解碼器
iOS中可以用的錄制和播放編解碼器來平衡音頻質量,應用程序開發的靈活性,硬件功能和電池壽命。
19.Audio Processing Graphs
AUGraph:定義了一組復雜的音頻執行任務.