AVFoundation開發(fā)秘籍筆記-02播放和錄制音頻

一、音頻會(huì)話 AVAudioSession

音頻會(huì)話在應(yīng)用程序和操作系統(tǒng)之間扮演著中間人的角色,提供一種簡單實(shí)用的方法是OS得知應(yīng)用程序應(yīng)該如何與iOS音頻環(huán)境進(jìn)行交互。

AVAudioSessionAVFOundation框架引入。每個(gè)iOS應(yīng)用程序都有自己的一個(gè)音頻會(huì)話,這個(gè)會(huì)話可以被AVAudioSession的類方法sharedInstance訪問。

音頻會(huì)話是一個(gè)單例對象,可以使用它來設(shè)置應(yīng)用程序的音頻上下文環(huán)境,并向系統(tǒng)表達(dá)您的應(yīng)用程序音頻行為的意圖。

AVAudioSession *audioSession = [AVAudioSession sharedInstance];

使用它可以實(shí)現(xiàn):

  • 啟用或停用應(yīng)用程序中的音頻工作
  • 設(shè)置音頻會(huì)話類別和模式,
  • 配置音頻設(shè)置,如采樣率,I/O緩沖區(qū)持續(xù)時(shí)間和通道數(shù)
  • 處理音頻輸出更改
  • 相應(yīng)重要的音頻時(shí)間,如更改底層Media Services守護(hù)程序的可用性。

1、音頻會(huì)話分類/類別

  • Ambient 游戲、效率應(yīng)用程序 AVAudioSessionCategoryAmbient / kAudioSessionCategory_AmbientSound 使用這個(gè)分類應(yīng)用會(huì)隨著靜音鍵和屏幕關(guān)閉而靜音,且不會(huì)終止其他應(yīng)用播放的聲音,可以和其他自帶應(yīng)用如iPod、Safari同時(shí)播放聲音。該類別無法在后臺(tái)播放聲音

  • Solo Ambient(默認(rèn)) 游戲、效率應(yīng)用程序 AVAudioSessionCategorySoloAmbient/kAudioSessionCategory_SoloAmbientSound 類似Ambient不同之處在于它會(huì)終止其它應(yīng)用播放聲音。該類別無法在后臺(tái)播放聲音

  • Playback 音頻和視頻播放 AVAudioSessionCategoryPlayback / kAudioSessionCategory_MediaPlayback 用于以音頻為主的應(yīng)用,不會(huì)隨著靜音鍵和平不關(guān)閉而靜音。可在后臺(tái)播放聲音。

  • Record 錄音機(jī)、視頻捕捉 AVAudioSessionCategoryRecord / kAudioSessionCategory_RecordAudio 錄音應(yīng)用,除了來電鈴聲、鬧鐘、日歷提醒之外的其他系統(tǒng)聲音不會(huì)被播放。只提供單純錄音功能。

  • Play and Record VoIP、語音聊天 AVAudioSessionCategoryPlayAndRecord / kAudioSessionCategory_PlayAndRecord 提供錄音和播放功能,如果應(yīng)用需要用到iPhone上的聽筒,這個(gè)類別是你唯一的選擇,在這個(gè)類別下,聲音的默認(rèn)出口為聽筒或者耳機(jī)。

  • Audio Processing 離線會(huì)話和處理 AVAudioSessionCategoryAudioProcessing / kAudioSessionCategory_AudioProcessing 在不播放或錄制音頻時(shí)使用音頻硬件編解碼器或信號(hào)處理器的類別。例如在執(zhí)行離線音頻格式轉(zhuǎn)換時(shí),此類別禁用播放和禁用錄音。應(yīng)用處于后臺(tái)時(shí),音頻處理通常不會(huì)繼續(xù),但是可以在應(yīng)用移至后臺(tái)時(shí),請求更多時(shí)間來完成處理。

  • Multi-Route 使用外部硬件的高級A/V應(yīng)用程序 AVAudioSessionCategoryMultiRoute 通過可以用的音頻輔助設(shè)備和內(nèi)置音頻硬件設(shè)備,我們可以自定義使用類型

并不是一個(gè)應(yīng)用只能使用一個(gè)category,可以根據(jù)實(shí)際需求來切換設(shè)置不同的category。

通過音頻會(huì)話單例對象的setCategory: error:設(shè)置iOS應(yīng)用音頻會(huì)話類別和模式。

NSError *error;
if (![_audioSession setCategory:AVAudioSessionCategoryPlayback error:&error]) { //設(shè)置類別
    NSLog(@"Category error :%@",[error localizedDescription]);
}

2、配置音頻會(huì)話

音頻會(huì)話在應(yīng)用程序的生命周期中是可以修改的,一般在應(yīng)用程序啟動(dòng)時(shí),對其進(jìn)行配置。配置音頻會(huì)話的最佳位置就是應(yīng)用程序委托的application: didFinishLaunchingWithOptions:方法。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSError *error;
    
    if (![audioSession setCategory:AVAudioSessionCategoryPlayback error:&error]) {
        NSLog(@"Category error :%@",[error localizedDescription]);
    }
    if (![audioSession setActive:YES error:&error]) {
        NSLog(@"Activation Error :%@",[error localizedDescription]);
    }
    
    return YES;
}

AVAudioSession 提供了與應(yīng)用程序音頻會(huì)話交互的接口,通過設(shè)置合適的的分類,可以音頻的播放指定需要的音頻會(huì)話,定制一些行為。最后告知該音頻會(huì)話激活該配置setActive:YES error:。

  • 配置可以在后臺(tái)運(yùn)行:
    info.plist文件天劍一個(gè)新的Required background modes類型的數(shù)組,在其中添加名為App plays audio or streams audio/video using AirPlay選項(xiàng)。或者右擊info.plist文件,在相應(yīng)的XML部分編輯plist,以及選擇Open as Source Code,添加下面標(biāo)簽到文件底部的</dict>前:
<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>

兩種方式效果一樣,只是添加的方式不同。

三、使用AVAudioPlayer播放音頻

AVAudioPlayer提供了簡單地從文本或內(nèi)存中播放音頻的方法。

AVAudioPlayer構(gòu)建于Core Audio中的C-based Audio Queue Services的最頂層。它可以提供在Audio Queue Service中所能找到的核心功能。除非需要從網(wǎng)絡(luò)流中播放音頻、需要訪問原始音頻樣本或者需要非常低的時(shí)延,否則它都能勝任。

1、創(chuàng)建AVAudioPlayer

兩種方法創(chuàng)建AVAudioPlayerinitWithData: error:nilinitWithContentsOfURL: error:nil

使用包含要播放音頻的內(nèi)存的NSData,或者本地音頻文件的NSURL。

NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"崔健-假行僧" withExtension:@"mp3"];
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:nil];
if (self.audioPlayer) {
    [self.audioPlayer prepareToPlay];
}

如果返回一個(gè)有效的播放實(shí)例,建議調(diào)用prepareToPlay方法。這樣會(huì)取得需要音頻硬件并預(yù)加載Audio Queue的緩沖區(qū)。調(diào)用這個(gè)方法是可選的,在調(diào)用play方法時(shí)會(huì)隱性激活,不過在創(chuàng)建時(shí)準(zhǔn)備播放器可以降低調(diào)用paly方法和聽到聲音輸出之間的延時(shí)。

2、對播放進(jìn)行控制

常規(guī)方法:

  • play -- 立即播放音頻
  • pause -- 暫停播放
  • stop -- 停止播放

pausestop都能停止播放,并且再次播放的時(shí)候繼續(xù)播放。區(qū)別是stop方法會(huì)撤銷調(diào)用prepareToPlay是所做的設(shè)置,而調(diào)用pause不會(huì)。

其他方法:

  • 修改播放器音量:播放器的音量獨(dú)立于系統(tǒng)的音量,可以通過對播放器音量的處理實(shí)現(xiàn)一些效果,比如聲音漸隱效果。音量或播放增益定義為0.0(靜音)到1.0之間的浮點(diǎn)值。
  • 修改播放器pan值:允許使用立體聲播放聲音,pan值范圍-1.0(極左)-1.0(極右),默認(rèn)是為1.0(居中)
  • 調(diào)整播放率:允許用戶在不改變音調(diào)的情況下調(diào)整播放率,范圍從0.5(半速)-2.0(2倍速)
  • 通過設(shè)置numberOfLoops實(shí)現(xiàn)音頻無縫循環(huán):給這個(gè)屬性設(shè)置一個(gè)大于0的數(shù),可以實(shí)現(xiàn)播放器n次循環(huán)播放。相反如果為-1導(dǎo)致播放器無限循環(huán)。音頻循環(huán)可以是未壓縮的線性PCM音頻,也可以是AAC之類的壓縮格式音頻。MP3格式片段可以實(shí)現(xiàn)無縫循環(huán),但是MP3格式用作循環(huán)格式不被推崇。MP3格式的音頻要實(shí)現(xiàn)循環(huán)的目的通常需要使用特殊工具進(jìn)行處理。如果希望使用壓縮格式的資源,建議使用AAC或者AppleLossless格式的內(nèi)容。
  • 進(jìn)行音頻計(jì)量:播放發(fā)生時(shí)從播放器讀取播放力度的平均值和峰值。將這些數(shù)據(jù)提供給VU計(jì)量器或其他可視化元件。向用戶提供可視化的反饋效果。

三、創(chuàng)建Audio Looper

四、處理中斷事件

音頻會(huì)話通知

添加通知監(jiān)聽,監(jiān)聽是否發(fā)生中斷事件。通知名稱為AVAudioSessionInterruptionNotification

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];

推送的消息會(huì)包含許多重要信息的userInfo字典,通過關(guān)鍵字AVAudioSessionInterruptionTypeKey獲取中斷類型AVAudioSessionInterruptionType,根據(jù)中斷狀態(tài)執(zhí)行不同操作

- (void)handleInterruption:(NSNotification *)notification {
    NSDictionary *infoDict = notification.userInfo;
    AVAudioSessionInterruptionType type = [infoDict[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
    
    if (type == AVAudioSessionInterruptionTypeBegan) {
        [self stop];//開始中斷,停止播放

    } else {

        AVAudioSessionInterruptionOptions options = [infoDict[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
        if (options == AVAudioSessionInterruptionOptionShouldResume) {
            [self play];
        }
    }
}

如果中斷類型為AVAudioSessionInterruptionTypeEnded,userInfo字典里會(huì)包含一個(gè)通過keyAVAudioSessionInterruptionOptionKey取的AVAudioSessionInterruptionOptions類型值,表示音頻會(huì)話是否已經(jīng)重新激活以及是否可以再次播放。上例中,如果options為AVAudioSessionInterruptionOptionShouldResume,可以調(diào)用播放的方法繼續(xù)播放音頻。

五、對線路改變的響應(yīng)

在iOS設(shè)備上添加或移除音頻輸入、輸出線路時(shí),會(huì)發(fā)生線路改變,有多重原因會(huì)導(dǎo)致線路的變化,比如插入耳機(jī)或斷開USB麥克風(fēng)。當(dāng)這些時(shí)間發(fā)生時(shí),音頻會(huì)根據(jù)情況改變輸入或輸出線路,同時(shí)AVAudioSession會(huì)廣播一個(gè)描述該變化的通知給所有相關(guān)的監(jiān)聽者。

添加監(jiān)聽的通知名稱:AVAudioSessionRouteChangeNotification。該通知同樣包含一個(gè)userInfo字典,帶有相應(yīng)通知發(fā)送的原因一前一個(gè)線路的描述,以此可以確定線路變化的情況。

判斷線路變更發(fā)生的原因,取keyAVAudioSessionRouteChangeReasonKey對應(yīng)的AVAudioSessionRouteChangeReason類型值。根據(jù)變更原因,作相應(yīng)處理。

typedef NS_ENUM(NSUInteger, AVAudioSessionRouteChangeReason)
{
    AVAudioSessionRouteChangeReasonUnknown = 0,
    原因不明;
    AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
    有新設(shè)備可用,如耳機(jī)插入
    AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
    一個(gè)舊設(shè)備不可用,如耳機(jī)拔出
    AVAudioSessionRouteChangeReasonCategoryChange = 3,
    音頻類別被改變,如Audio從Play back 變成Play And Record
    
    AVAudioSessionRouteChangeReasonOverride = 4,
    音頻線路(route)改變,如類別是Play and Record,輸出社誒已經(jīng)從默認(rèn)的接收器改變成為揚(yáng)聲器
    AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
    設(shè)備從休眠中醒來
    
    AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
    沒有路徑返回當(dāng)前的類別,如Record雷彪當(dāng)前沒有輸入設(shè)備
    AVAudioSessionRouteChangeReasonRouteConfigurationChange NS_ENUM_AVAILABLE_IOS(7_0) = 8
    當(dāng)前輸入/輸出口沒變,但設(shè)置修改,如一個(gè)端口的數(shù)據(jù)選擇已經(jīng)改變。
}

知道有設(shè)備斷開連接后,需要向userInfo字典提出請求,一會(huì)的其中用于描述前一個(gè)線路的AVAudioSessionRouteDescription,其對應(yīng)的key為AVAudioSessionRouteChangePreviousRouteKey。線路的描述信息整合在一個(gè)輸入NSArray和一個(gè)輸出NSArray中。數(shù)組中的元素都是AVAudioSessionPortDescription的實(shí)例。用于描述不同的I/O接口屬性??梢詮木€路描述中找到第一個(gè)輸出接口,即前一次的接口。

輸入口不同類型,input port type
AVAudioSessionPortLineIn
AVAudioSessionPortBuiltInMic :內(nèi)置麥克風(fēng)
AVAudioSessionPortHeadsetMic :耳機(jī)線中的麥克風(fēng)
輸出口不同類型,output port type
AVAudioSessionPortLineOut
AVAudioSessionPortHeadphones :耳機(jī)或者耳機(jī)式輸出設(shè)備
AVAudioSessionPortBuiltInReceiver :帖耳朵時(shí)候內(nèi)置揚(yáng)聲器(打電話的時(shí)候的聽筒)
AVAudioSessionPortBuiltInSpeaker :iOS設(shè)備的揚(yáng)聲器
AVAudioSessionPortBluetoothA2DP :A2DP協(xié)議式的藍(lán)牙設(shè)備
AVAudioSessionPortHDMI :高保真多媒體接口設(shè)備
AVAudioSessionPortAirPlay :遠(yuǎn)程AirPlay設(shè)備
AVAudioSessionPortBluetoothLE :藍(lán)牙低電量輸出設(shè)備

一個(gè)簡單實(shí)例,拔出耳機(jī)之后,默認(rèn)停止播放:

- (void)handleRouteChange:(NSNotification *)notification {
    NSDictionary *infoDict = notification.userInfo;
    AVAudioSessionRouteChangeReason reason = [infoDict[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
    if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
//        AVAudioSessionRouteDescription
//        AVAudioSessionPortDescription
        AVAudioSessionRouteDescription *previousRoute = infoDict[AVAudioSessionRouteChangePreviousRouteKey];
        //取出所有線路描述
        NSLog(@"count :%zd",previousRoute.outputs.count);
        AVAudioSessionPortDescription *previousOutput = previousRoute.outputs[0];
        //取出前一次線路描述
        NSString *portType = previousOutput.portType;
        if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
            [self stop];
        }
        
    }
}

六、使用AVAudioRecorder錄制音頻

AVAudioRecorder構(gòu)建于Audio Queue Services之上,可以再iOS設(shè)備上使用這個(gè)類從內(nèi)置的麥克風(fēng)錄制音頻,也可以從外部音頻設(shè)備進(jìn)行錄制,比如數(shù)字音頻接口或USB麥克風(fēng)等。

1、創(chuàng)建AVAudioRecorder

- (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary<NSString *, id> *)settings error:(NSError **)outError;

第一個(gè)參數(shù):音頻流寫入文件的本地文件URL,第二個(gè)參數(shù):包含用于配置錄音會(huì)話信息,第三個(gè)參數(shù):捕捉初始化階段錯(cuò)誤。

成功創(chuàng)建AVAudioRecorder實(shí)例,建議調(diào)用prepareToRecord。與AVAudioPlayerprepareToPlay方法類似,執(zhí)行底層Audio Queue初始化的必要過程。在URL參數(shù)指定位置創(chuàng)建一個(gè)文件,將錄制啟動(dòng)時(shí)的延時(shí)降到最小。

NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"1.m4a"];
NSURL *fileUrl = [NSURL fileURLWithPath:path];
NSDictionary *settings = @{
                           AVFormatIDKey:@(kAudioFormatMPEG4AAC),
                           AVSampleRateKey:@22050.0f,
                           AVNumberOfChannelsKey:@1,
                           };
NSError *error;
_audioRecorder = [[AVAudioRecorder alloc] initWithURL:fileUrl settings:settings error:&error];
    
if (_audioRecorder) {
    [_audioRecorder prepareToRecord];
} else {
    NSLog(@"init error :%@",[error localizedDescription]);
}

配置錄音會(huì)話參數(shù):

  • AVFormatIDKey --寫入內(nèi)容的音頻格式,常用的音頻格式支持的值:
kAudioFormatLinearPCM
kAudioFormatMPEG4AAC
kAudioFormatAppleLossless
kAudioFormatAppleIMA4
kAudioFormatiLBC
kAudioFormatULaw

kAudioFormatLinearPCM -會(huì)將未壓縮的音頻流寫入文件中,
這種格式的保真度最高,相應(yīng)的文件也最大。
AAC或Apple IMA4的壓縮格式會(huì)顯著縮小文件,還能保證高質(zhì)量的音頻內(nèi)容。
  • AVSampleRateKey --定義錄音器采樣率。采樣率定義了對輸入的模擬音頻信號(hào)每一秒內(nèi)的采樣數(shù)。采樣率決定音頻的質(zhì)量及最終文件大小。一般標(biāo)準(zhǔn)的采樣率:8k、16k、22.5k、44.1k。
  • AVNumbeOfChannelsKey -- 定義記錄音頻通道數(shù)。默認(rèn)值1,單聲道錄制。設(shè)置2-立體聲錄制。除非使用外部硬件進(jìn)行錄制,一般應(yīng)該創(chuàng)建單聲道錄音。

2、控制錄音過程

record --開始或繼續(xù)錄音

stop --停止錄音,并關(guān)閉文件

pause --暫停錄音

七、使用Audio Metering

AVAudioPlayerAVAudioRecorder中最強(qiáng)大和最實(shí)用的功能是對音頻進(jìn)行測量,Audio Metering可讓開發(fā)者讀取音頻的平均分貝和峰值分貝數(shù)據(jù),并使這些數(shù)據(jù)以可視化方式將聲音大小呈獻(xiàn)給用戶。

通過averagePowerForChannel:peakPowerForChannel:獲取平均分貝和峰值分貝,返回一個(gè)用于表示聲音分貝(dB)等級的浮點(diǎn)值,這個(gè)值的范圍是從表示最大分貝的0dB(full scale)到最小分貝或靜音的-160dB。獲取這兩個(gè)值之前,要先設(shè)置屬性meteringEnabled為YES,才能對音頻進(jìn)行測量。另,每當(dāng)需要讀取值時(shí),需要先調(diào)用updateMeters方法才能獲取最新的值。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容