第二章 音頻播放和錄制
1. 音頻會話
音頻會話分類
Category | 播放類型 | 后臺播放 | 靜音或屏幕關閉 | 音頻輸入 | 音頻輸出 | 作用 |
---|---|---|---|---|---|---|
AVAudioSessionCategoryAmbient | 混合播放 | 有影響 | 支持 | 游戲背景音樂 | ||
AVAudioSessionCategorySoloAmbient(默認) | 獨占播放 | 有影響 | 支持 | 微信中播放語音 | ||
AVAudioSessionCategoryPlayback | 可選 | 支持 | 支持 | 音頻播放器 | ||
AVAudioSessionCategoryRecord | 獨占錄音 | 支持 | 支持 | 微信中錄制語音 | ||
AVAudioSessionCategoryPlayAndRecord | 可選 | 支持 | 支持 | 支持 | 微信語音聊天 | |
AVAudioSessionCategoryAudioProcessing | —— | —— | —— | 硬件解碼音頻 | ||
AVAudioSessionCategoryMultiRoute | 支持 | 支持 | 多設備輸入輸出 |
上述分類所提供的幾種常見行為可以滿足大部分應用程序的需要,如果需要更復雜的功能,上述其中一種分類可以通過使用 options 和 modes 方法進一步自定義開發。
激活音頻會話
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
if (![session setCategory:AVAudioSessionCategoryPlayback error:&error]) {
NSLog(@"Category Error : %@", error.localizedDescription);
}
if (![session setActive:YES error:&error]) {
NSLog(@"Activation Error : %@", error.localizedDescription);
}
return YES;
}
如果分類允許后臺播放,則應該打開 Capabilities 中的 Background Modes 繼而勾選后臺播放音頻選項
2. 音頻播放
除非需要從網絡流中播放音頻、需要訪問原始音頻樣本,或者需要非常低的時延,否則 AVAudioPlayer 都能勝任
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"白潔01" withExtension:@"mp3"];
// Must Maintain a strong reference to player
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
if (self.player) {
self.player.numberOfLoops = -1; // 循環播放
[self.player prepareToPlay];
}
}
- (IBAction)play {
[self.player play];
}
prepareToPlay 方法是可選的,在調用 play 方法前也會自動調用,作用是取得需要的音頻硬件并預加載 Audio Queue 的緩沖區,降低調用 play 方法后的延時。
- (IBAction)pause {
[self.player pause];
}
- (IBAction)stop {
[self.player stop];
self.player.currentTime = 0.0f;
}
通過 pause 和 stop 方法停止的音頻都會繼續播放。最主要的區別在底層處理上,調用 stop 方法會撤銷調用 prepareToPlay 時所做的設置,而調用 pause 方法則不會。
// 音量 0 ~ 1
- (IBAction)voice:(UISlider *)sender {
self.player.volume = sender.value;
}
// 聲道 -1 ~ 1
- (IBAction)pan:(UISlider *)sender {
self.player.pan = sender.value;
}
// 速率 0.5 ~ 2
- (IBAction)speed:(UISlider *)sender {
self.player.rate = sender.value;
}
如果要改變速率,在初始化 AVAudioPlayer 時應做出如下設置
self.player.enableRate = YES;
3. 處理中斷事件
當有電話呼入、鬧鐘響起的時候,播放中的音頻會慢慢消失和暫停,但是終止通話后,播放、停止按鈕的控件和音頻的播放沒有恢復。為了優化用戶體驗,需要監聽這些事件,并作出處理:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
- (void)handleInterruption:(NSNotification *)notification
{
NSDictionary *info = notification.userInfo;
AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (type == AVAudioSessionInterruptionTypeBegan) {
// 中斷開始,設置停止音樂
[self.player pause];
} else {
// 中斷結束,判斷是否允許繼續播放
AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
if (options == AVAudioSessionInterruptionOptionShouldResume) {
// 允許繼續播放,則繼續播放
[self.player play];
}
}
}
4. 對線路改變的響應
播放音頻期間插入耳機,音頻輸出線路變成耳機插孔并繼續播放。斷開耳機連接,音頻線路再次回到設備的內置揚聲器播放。雖然線路變化和預期一樣,不過按照蘋果官方文檔,認為該音頻應該處于靜音狀態。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChange:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]];
- (void)handleRouteChange:(NSNotification *)notification
{
NSDictionary *info = notification.userInfo;
AVAudioSessionRouteChangeReason reason = [info[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
AVAudioSessionRouteDescription *previousRoute = info[AVAudioSessionRouteChangePreviousRouteKey];
AVAudioSessionPortDescription *previousOutput = previousRoute.outputs[0];
if ([previousOutput.portType isEqualToString:AVAudioSessionPortHeadphones]) {
// 停止播放音樂
[self.player stop];
}
}
}
5. 音頻錄制
一般情況存在錄音功能,必然會有播放功能,所以不能使用默認的錄制音頻會話,應該使用既可以錄制又能播放的 AVAudioSessionCategoryPlayAndRecord
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL fileURLWithPath:[@"Users/mayan/Desktop" stringByAppendingPathComponent:@"voice.caf"]];
NSDictionary *settings = @{
AVFormatIDKey : @(kAudioFormatAppleIMA4),
AVSampleRateKey : @22050.0f,
AVNumberOfChannelsKey : @1,
};
self.recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:nil];
if (self.recorder) {
self.recorder.delegate = self;
[self.recorder prepareToRecord];
}
}
- (IBAction)record {
[self.recorder record];
}
- (IBAction)recordFinish {
[self.recorder stop];
}
// 音頻錄制完成調用
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
{
if (flag) {
// 一般把錄制好的音頻復制或者剪切到目的文件夾下
NSURL *srcURL = self.recorder.url;
NSURL *destURL = [NSURL URLWithString:@"目的文件路徑/音頻文件名稱.caf"];
[[NSFileManager defaultManager] copyItemAtURL:srcURL toURL:destURL error:nil];
}
}
在錄制音頻過程中,Core Audio Format(CAF)通常是最好的容器格式,因為它和內容無關可以保存 Core Audio 支持的任何音頻格式。在設置字典中指定的鍵值信息也值得討論一番:
音頻格式
AVFormatIDKey 定義了寫入內容的音頻格式,下面是常用格式:
- kAudioFormatLinearPCM:將未壓縮的音頻流寫入到文件中。保真度最高,文件也最大;
- kAudioFormatMPEG4AAC(AAC) 或 kAudioFormat-AppleIMA4(Apple IMA4):文件顯著縮小,還能保證高質量音頻
采樣率
AVSampleRateKey 定義了錄音器的采樣率,采樣率定義了對輸入的模擬音頻信號每一秒的采樣數。采樣率越高,越能得到高質量的內容,不過文件相對越大。標準的采樣率:8000、16000、22050、44100
通道數
AVNumberOfChannelsKey 定義記錄音頻內容的通道數。默認值 1 是單聲道錄制,2 是立體聲錄制。除非使用外部硬件錄制,否則應該創建單聲道錄音。
6. 音頻測量
AVAudioPlayer 和 AVAudioRecorder 中最實用的功能就是對音頻進行測量。Audio Metering 可以讀取音頻的平均分貝和峰值分貝數據,并使用這些數據以可視化方式將聲音大小呈現給用戶。
首先在初始化 AVAudioPlayer 或 AVAudioRecorder 時應做出如下設置
self.player.meteringEnabled = YES;
self.recorder.meteringEnabled = YES;
點擊音頻播放或者音頻錄制,開始測量
- (void)startMeterTimer
{
[self.link invalidate];
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateMeter)];
self.link.frameInterval = 4; // 時間間隔為刷新率的 1/4
[self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)stopMeterTimer
{
[self.link invalidate];
self.link = nil;
}
下面分別是音頻播放情況下測量、音頻錄制情況下測量
- (void)updateMeter
{
[self.player updateMeters]; // 刷新
CGFloat num1 = [self.player averagePowerForChannel:0];
CGFloat num2 = [self.player peakPowerForChannel:0];
NSLog(@"平均分貝:%f, 峰值分貝:%f", num1, num2);
}
- (void)updateMeter
{
[self.recorder updateMeters]; // 刷新
CGFloat num1 = [self.recorder averagePowerForChannel:0];
CGFloat num2 = [self.recorder peakPowerForChannel:0];
NSLog(@"平均分貝:%f, 峰值分貝:%f", num1, num2);
}
上面方法都會返回用于表示聲音分貝(dB)等級的浮點值,這個值的范圍是 -160dB ~ 0dB