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:定義了一組複雜的音訊執行任務.