亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

討論在線教室 iOS 端聲音問題綜合解決方案

 更新時(shí)間:2021年04月13日 16:31:13   作者:字節(jié)跳動技術(shù)團(tuán)隊(duì)  
這篇文章主要介紹了在線教室 IOS 端聲音問題綜合解決方案,從背景介紹,到技術(shù)總結(jié),再到行業(yè)現(xiàn)狀,都進(jìn)行了細(xì)致的說明,建議同學(xué)們仔細(xì)看一下

背景介紹

在線教室場景下,聲音是最重要的內(nèi)容傳輸渠道之一,保障聲音的穩(wěn)定可靠,是在線教室質(zhì)量非常重要的一環(huán)。同時(shí)在線教室里許多功能模塊都與聲音有關(guān)聯(lián),如何處理好各個模塊間的聲音沖突成為一個重要話題。

AVAudioSession

在 iOS 端,說到聲音的話題就繞不開 AVAudioSession。AVAudioSession 的作用是管理音頻這一唯一硬件資源的分配,通過調(diào)優(yōu)合適的 AVAudioSession 來適配我們的 APP 對于音頻的功能需求。切換音頻場景的時(shí)候,需要相應(yīng)的切換 AVAudioSession。

 AVAudioSessionCategory

教育場景下主要使用到的音頻場景有:

AVAudioSessionMode

iOS 提供 AVAudioSessionMode[1] 用于與 AVAudioSessionCategory[2] 搭配使用,教育場景下使用到的音頻模式主要有:

 AVAudioSessionOptions

我們可以使用 options 去微調(diào) Category 行為,教育場景下常用的有:

通話音量與媒體音量

一般而言,通話音量指的是進(jìn)行語音、視頻通話時(shí)的音量。媒體音量指的是播放音樂、視頻或游戲的音效、背景音的音量。

在實(shí)際使用中,兩者的差異在于,通話音量有較好的回聲消除,媒體音量有較好的聲音表現(xiàn)力。媒體音量可以調(diào)整到 0,而通話音量不可以。

通話音量與媒體音量只能二選一,因此需要區(qū)分系統(tǒng)音量走的是通話音量還是媒體音量。系統(tǒng)音量走通話音量,是指在設(shè)備上調(diào)整音量時(shí),調(diào)整的是通話音量。媒體音量同理。媒體音量和通話音量分別屬于 2 個不同的、獨(dú)立的系統(tǒng),一個設(shè)置不會影響到另外一個。

進(jìn)入通話后,音效的播放音量由通話音量控制。退出通話后,則由媒體音量控制。一般在教育場景下,學(xué)生作為觀眾拉流時(shí),使用的媒體音量,老師說話的聲音更加立體飽滿,當(dāng)學(xué)生連麥時(shí),使用的通話音量,以保證通話聲音的質(zhì)量。

簡單來說,非連麥模式下會使用媒體音量控制,連麥模式下會使用通話音量控制,兩者有獨(dú)立的音量控制機(jī)制。

當(dāng)播放媒體資源時(shí),使用播放器(如 AVPlayer)播放音頻,播放器底層 AudioUnit 的 description 為 VoiceProcessingIO。

RTC SDK 內(nèi)部維護(hù)了一個 AudioUnit,通話音量下 AudioUnit 的 description 為 RemoteIO,媒體音量下為 VoiceProcessingIO,當(dāng)出現(xiàn)模式切換時(shí),會銷毀原來的 AudioUnit,再創(chuàng)建新的 AudioUnit,始終保持一個 AudioUnit 來進(jìn)行音頻播放。

通話音量下,AVPlayer 內(nèi) VoiceProcessingIO 的 AudioUnit 聲音會被抑制。同樣的,在媒體音量下,RTC SDK 內(nèi)的 AudioUnit 的 description 設(shè)置為 VoiceProcessingIO,如果此時(shí)其他模塊通過設(shè)置 AVAudioSession 切換到通話音量,RTC 的聲音也會被抑制。

行業(yè)現(xiàn)狀

在線教室場景下,很多功能都需要播放聲音,包括課中音視頻直播、課后回放、webview 內(nèi)嵌課件聲音(包括音頻、視頻、音效)、課堂音頻、課堂視頻、課堂游戲聲音、音效聲音等。除此之外,教室內(nèi)還包括很多需要聲音錄制的功能,包括連麥、跟讀、集體發(fā)言、聊天語音輸入、語音識別等。

教室內(nèi)這些功能存在各種組合,且對 AVAudioSession 的設(shè)置要求存在差異,而 AVAudioSession 又是一個單例,如果沒有一個統(tǒng)一管理的邏輯,很容易就出現(xiàn)設(shè)置混亂的問題。

目前行業(yè)內(nèi)碰到的比較多的問題主要是聽不見 RTC 聲音與媒體聲音被抑制。

聽不見 RTC 聲音

聽不見 RTC 聲音的主要原因是其他功能在設(shè)置 AVAudioSession 時(shí),AVAudioSessionOptions 未包含 AVAudioSessionCategoryOptionMixWithOthers 混音模式,導(dǎo)致 RTC 聲音被高優(yōu)進(jìn)程打斷。比如在非混音模式下播放 webview 的內(nèi)嵌音頻,因?yàn)?webview 是使用系統(tǒng)進(jìn)程來播放聲音,優(yōu)先級最高,所以 APP 進(jìn)程下的 RTC 聲音就會被抑制導(dǎo)致無法正常發(fā)聲。

這類問題一般都比較隱蔽,因?yàn)楹唵蔚膱鼍叭绻袉栴},在上線之前一般都能測試出來,而當(dāng)多個功能場景串起來之后才觸發(fā)問題,往往就很難在測試期間發(fā)現(xiàn),且如果線上沒有完備的日志查詢體系,針對線上這類問題排查起來難度也非常大,往往因?yàn)槎ㄎ徊坏皆蚨L期遺留。

媒體聲音被抑制

在通話音量模式下,媒體聲音會被壓低,導(dǎo)致聲音變小。比較常見的場景是在小班場景下,學(xué)生在推流時(shí)播放課堂音視頻等媒體資源,聲音會比 RTC 的聲音要小,導(dǎo)致媒體聲音聽不清楚。

通話模式下(連麥時(shí))媒體聲音會被壓低,原因是 iOS 手機(jī)系統(tǒng)會開啟回聲消除以保證人聲體驗(yàn),因此會壓低媒體通道的聲音,也會壓低背景音效。

教育行業(yè)內(nèi)部分頭部 APP 也沒有從根本上解決該問題,很多都是通過從產(chǎn)品功能層面上規(guī)避問題,通過產(chǎn)品妥協(xié)來為技術(shù)問題讓步。比如在播放課堂音視頻資源時(shí),默認(rèn)將所有學(xué)生都強(qiáng)制關(guān)麥,關(guān)麥時(shí)學(xué)生處于媒體音量,就不存在被壓低的問題了,等到課堂音視頻播放結(jié)束后,再允許學(xué)生開麥。這種通過規(guī)避問題場景來解決問題的方式,不具有可復(fù)制性。

RTC 聲音變小

RTC 聲音變小,主要原因是聲音通過聽筒發(fā)聲,而沒有正常通過揚(yáng)聲器發(fā)聲,造成聲音變小的假象。另外在 iOS14 系統(tǒng)下,使用過 RTC 的通話模式并切回媒體模式后,再調(diào)用 setCategory:PlayAndRecord + DefaultToSpeaker 就會必現(xiàn)聲音小的問題。

解決方案

針對上述行業(yè)痛點(diǎn),通過底層原理的分析與實(shí)際項(xiàng)目經(jīng)驗(yàn),從代碼規(guī)范、問題兜底、問題報(bào)警梳理出一套可行的解決方案。

聽不見 RTC 聲音、RTC 聲音變小

RTC 的聲音問題基本是因?yàn)槠渌K功能對 AVAudioSession 進(jìn)行了更改,且在功能結(jié)束之后,也沒有將 AVAudioSession 重置到 RTC 需要的設(shè)置。本身音視頻 SDK(如 agora、zego 等)對這種情況會有一定的兜底邏輯,但是這種兜底如果存在侵入性,也是不合理的,因此具有一定的局限性。

AudioSession 修改規(guī)范

由于系統(tǒng)無法區(qū)分同一個進(jìn)程中是哪個模塊對 AudioSession 進(jìn)行了更改,所以為了避免聽不見 RTC 聲音的問題,在使用 RTC 時(shí),其它模塊對 AudioSession 的調(diào)用更改,需要遵循以下原則:

  1. 模塊調(diào)用 setCategory 前先判斷下,當(dāng)前 AudioSession 如已滿足使用需要,不用再次設(shè)置,避免觸發(fā) iOS 14 系統(tǒng) Bug
  2. 模塊需要錄音時(shí),Category 應(yīng)該使用 PlayAndRecord(為了防止打斷正在播放的音頻,不要使用僅錄音的 CategoryRecord),當(dāng)前 category 不是 PlayAndRecord 的情況下再調(diào)用 setCategory
  3. 模塊僅需要播放時(shí),當(dāng)前 category 為 PlayAndRecord 或 Playback、Ambient 的情況下不需要 setCategory
  4. 若當(dāng)前的 category 不滿足模塊使用,在 setCategory 之前應(yīng)該先保存當(dāng)前的 AudioSession 狀態(tài),然后再 setCategory、使用音頻功能,使用結(jié)束后,應(yīng)該重新 setCategory 恢復(fù)到之前的 AudioSession 狀態(tài)
  5. 在設(shè)置 audioSession 時(shí),categoryOptions 都應(yīng)該包含 AVAudioSessionCategoryOptionDefaultToSpeakerAVAudioSessionCategoryOptionMixWithOthers,iOS10 系統(tǒng)及以上還應(yīng)包含 AVAudioSessionCategoryOptionAllowBluetooth。

核心代碼如下:

//需要錄音時(shí),AudioSession的設(shè)置代碼如下:
if ([AVAudioSession sharedInstance].category != AVAudioSessionCategoryPlayAndRecord) {
            [RTCAudioSessionCacheManager cacheCurrentAudioSession];
            AVAudioSessionCategoryOptions categoryOptions = AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionMixWithOthers;
            if (@available(iOS 10.0, *)) {
                categoryOptions |= AVAudioSessionCategoryOptionAllowBluetooth;
            }
            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:categoryOptions error:nil];
            [[AVAudioSession sharedInstance] setActive:YES error:nil];
}
 
//功能結(jié)束時(shí)重置audioSession
[RTCAudioSessionCacheManager resetToCachedAudioSession];
static AVAudioSessionCategory cachedCategory = nil;
static AVAudioSessionCategoryOptions cachedCategoryOptions = nil;
 
@implementation RTCAudioSessionCacheManager
 
//更改audioSession前緩存RTC當(dāng)下的設(shè)置
+ (void)cacheCurrentAudioSession {
    if (![[AVAudioSession sharedInstance].category isEqualToString:AVAudioSessionCategoryPlayback] && ![[AVAudioSession sharedInstance].category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
        return;
    }
    @synchronized (self) {
        cachedCategory = [AVAudioSession sharedInstance].category;
        cachedCategoryOptions = [AVAudioSession sharedInstance].categoryOptions;
    }
}
 
//重置到緩存的audioSession設(shè)置
+ (void)resetToCachedAudioSession {
    if (!cachedCategory || !cachedCategoryOptions) {
        return;
    }
    BOOL needResetAudioSession = ![[AVAudioSession sharedInstance].category isEqualToString:cachedCategory] || [AVAudioSession sharedInstance].categoryOptions != cachedCategoryOptions;
    if (needResetAudioSession) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [[AVAudioSession sharedInstance] setCategory:cachedCategory withOptions:cachedCategoryOptions error:nil];
            [[AVAudioSession sharedInstance] setActive:YES error:nil];
            @synchronized (self) {
                cachedCategory = nil;
                cachedCategoryOptions = nil;
            }
        });
    }
}
 
@end

兜底策略

考慮到在線教室場景的復(fù)雜度,讓教室內(nèi)所有功能代碼都遵循 AVAudioSession 的修改規(guī)范,雖然有嚴(yán)格的 codeReview,但是也存在一定的人為因素風(fēng)險(xiǎn),隨著業(yè)務(wù)功能不斷迭代,無法完全保證線上不出問題,因此一套可靠的兜底策略顯得非常有必要。

兜底策略的基本邏輯是 hook 到 AVAudioSession 的變化,當(dāng)各模塊對 AVAudioSession 的設(shè)置不符合規(guī)范要求時(shí),我們在不影響功能的前提下強(qiáng)制進(jìn)行修正,比如對 options 補(bǔ)充上混音模式。

通過方法交換我們可以 hook 到 AVAudioSession 的更改。比如用 kk_setCategory:withOptions: error: 與系統(tǒng)的 setCategory:withOptions: error: 進(jìn)行交換,在交換的方法里,我們判斷 options 是否包含 AVAudioSessionCategoryOptionMixWithOthers,如果沒有包含我們就進(jìn)行追加。

- (BOOL)kk_setCategory:(AVAudioSessionCategory)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError {
    //在需要進(jìn)行對audioSession進(jìn)行修正的場景下(RTC直播),修改options時(shí)未包含mixWithOther,則給options追加mixWithOther
    BOOL addMixWithOthersEnable = shouldFixAudioSession && !(options & AVAudioSessionCategoryOptionMixWithOthers)];
    if (addMixWithOthersEnable) {
        return [self kk_setCategory:category withOptions:options | AVAudioSessionCategoryOptionMixWithOthers error:outError];;
    }
    return [self kk_setCategory:category withOptions:options error:outError];
}

但上述方法只對通過調(diào)用 setCategory:withOptions: error: 來設(shè)置 audioSession 有效,如果調(diào)用了 setCategory:error: 來更改 audioSession,則會造成調(diào)用死循環(huán)的問題。在 iOS 底層實(shí)現(xiàn)中,調(diào)用 setCategory:error: 時(shí),內(nèi)部會再調(diào)用 setCategory:withOptions: error: 方法,因?yàn)檫M(jìn)行了方法交換,從而出現(xiàn)嵌套調(diào)用問題。

針對該問題,我們通過監(jiān)聽 AVAudioSessionRouteChangeNotification 通知,來 hookcategory 的變化,AVAudioSessionRouteChangeNotification 在調(diào)用 setCategory:error: 時(shí)會觸發(fā),而不會在調(diào)用 setCategory:withOptions: error: 時(shí)直接觸發(fā),進(jìn)而與上述方法形成了很好的互補(bǔ)。

 //添加對AVAudioSessionRouteChange的監(jiān)聽
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChangeNotification:) name:AVAudioSessionRouteChangeNotification object:nil];
 
- (void)handleRouteChangeNotification:(NSNotification *)notification {
  NSNumber* reasonNumber =
      notification.userInfo[AVAudioSessionRouteChangeReasonKey];
  AVAudioSessionRouteChangeReason reason =
      (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
    if (reason == AVAudioSessionRouteChangeReasonCategoryChange) {
        AVAudioSessionCategoryOptions currentCategoryOptions = [AVAudioSession sharedInstance].categoryOptions;
        AVAudioSessionCategory currentCategory = [AVAudioSession sharedInstance].category;
        //在需要進(jìn)行對audioSession進(jìn)行修正的場景下(RTC直播),修改category時(shí)options未包含mixWithOther,則給options追加mixWithOther
        if (shouldFixAudioSession  && !(currentCategoryOptions & AVAudioSessionCategoryOptionMixWithOthers)) {
            [[AVAudioSession sharedInstance] setCategory:currentCategory withOptions:currentCategoryOptions | AVAudioSessionCategoryOptionMixWithOthers error:nil];
        }
    }
}

報(bào)警機(jī)制

即使有修改規(guī)范與兜底策略的保障,隨著教室業(yè)務(wù)迭代與 iOS 系統(tǒng)升級,也無法保證線上完全不出問題,因此我們建立了問題報(bào)警機(jī)制,當(dāng)線上出現(xiàn)問題時(shí),能在工作群里及時(shí)收到警報(bào),根據(jù)警報(bào)的問題信息,通過日志進(jìn)一步排查問題。通過報(bào)警機(jī)制,我們可以更快速的對線上問題作出反應(yīng),不被動依賴于學(xué)生的投訴反饋,以最快的速度推進(jìn)問題解決。

當(dāng) RTC 聲音被打斷時(shí),底層音視頻 SDK 會回調(diào)警告錯誤碼(如 agora 的 warningCode 為 1025),當(dāng)出現(xiàn)對應(yīng)的警告碼時(shí),結(jié)合 slardar 的報(bào)警功能,在飛書群里以消息的形式進(jìn)行同步。同時(shí)在 hook 到 AVAudioSession 的變更時(shí),通過獲取堆棧信息,可以定位到是哪個模塊觸發(fā)的更改,結(jié)合報(bào)警用戶信息,可以更方便的定位問題。

媒體聲音被抑制

媒體聲音在媒體音量下開啟播放,播放途中因?yàn)檫B麥而切換到了通話音量,此時(shí)因?yàn)橄到y(tǒng)特性,媒體音量會被通話音量抑制而導(dǎo)致聲音變小。

針對該問題,我們使用音視頻 SDK 提供的混音、混流功能來規(guī)避。基本原理是播放媒體資源時(shí),我們拿到資源的 pcm 音頻數(shù)據(jù),將數(shù)據(jù)拋給 RTC 的 audioUnit 進(jìn)行混合,由 RTC 音頻播放單元統(tǒng)一播放,如果此時(shí) RTC 使用的是通話音量,則媒體資源也是使用的通話音量播放,反之亦然。以此來保證媒體資源與 RTC 始終保持統(tǒng)一的音量控制機(jī)制,而避免聲音大小存在差異。

混音是指給到音頻的本地文件路徑,或者播放的 url,由 SDK 進(jìn)行數(shù)據(jù)讀取與播放?;炝魇侵羔槍σ曨l文件,播放器只解碼播放視頻數(shù)據(jù),將音頻數(shù)據(jù)實(shí)時(shí)拋出來給到 SDK,SDK 將傳入的實(shí)時(shí)音頻數(shù)據(jù)與 RTC 音頻數(shù)據(jù)進(jìn)行混合與播放。項(xiàng)目中我們使用點(diǎn)播 SDK TTVideoEngine 來實(shí)現(xiàn)視頻播放與音頻外拋。

總結(jié)

通過上線上述綜合解決方案,聲音問題得到了有效的解決,同時(shí)也能從容應(yīng)對快速迭代的教室需求,有效提升了在線教室的體驗(yàn)。

到此,這篇關(guān)于討論在線教室 iOS 端聲音問題綜合解決方案的文章就介紹到這了,更多相關(guān)在線教室IOS端聲音解決方案內(nèi)容,請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!

相關(guān)文章

  • Dispatch Source Timer的使用及注意事項(xiàng)介紹

    Dispatch Source Timer的使用及注意事項(xiàng)介紹

    這篇文章主要給大家介紹了關(guān)于Dispatch Source Timer使用和一些注意事項(xiàng)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)下吧。
    2017-09-09
  • iOS實(shí)現(xiàn)圓角箭頭視圖

    iOS實(shí)現(xiàn)圓角箭頭視圖

    這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)圓角箭頭視圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-04-04
  • 如何在IOS中使用IBeacon

    如何在IOS中使用IBeacon

    這篇文章主要介紹了如何在IOS中使用IBeacon,想了解IBeacon的同學(xué),一定要看一下
    2021-04-04
  • iPhone/iPad開發(fā)通過LocalNotification實(shí)現(xiàn)iOS定時(shí)本地推送功能

    iPhone/iPad開發(fā)通過LocalNotification實(shí)現(xiàn)iOS定時(shí)本地推送功能

    這篇文章主要介紹了iPhone/iPad開發(fā)之通過LocalNotification實(shí)現(xiàn)iOS定時(shí)本地推送功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • 探究iOS多線程究竟不安全在哪里?

    探究iOS多線程究竟不安全在哪里?

    iOS多線程安全的概念在很多地方都會遇到,為什么不安全,不安全又該怎么去定義,其實(shí)是個值得深究的話題。那么通過下面這篇文章小編和大家一起來探究了iOS多線程究竟不安全在哪里?需要的朋友可以參考學(xué)習(xí)。
    2017-02-02
  • 快速上手IOS UIBezierPath(貝塞爾曲線)

    快速上手IOS UIBezierPath(貝塞爾曲線)

    本文主要介紹了IOS 貝塞爾曲線(UIBezierPath)的基礎(chǔ)知識。具有很好的參考價(jià)值。下面跟著小編一起來看下吧
    2017-03-03
  • IOS第三方庫ZXEasyCoding

    IOS第三方庫ZXEasyCoding

    本文給大家簡單介紹了object-c的第三方庫ZXEasyCoding的安裝、示例以及github地址,有需要的小伙伴可以參考下
    2016-11-11
  • iOS中關(guān)于信鴿推送的使用demo詳解

    iOS中關(guān)于信鴿推送的使用demo詳解

    這篇文章主要介紹了iOS中關(guān)于信鴿推送的使用demo詳解,非常不錯,具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-09-09
  • IOS 開發(fā)之UISearchBar 詳解及實(shí)例

    IOS 開發(fā)之UISearchBar 詳解及實(shí)例

    這篇文章主要介紹了IOS 開發(fā)之UISearchBar 詳解及實(shí)例的相關(guān)資料,主要介紹 IOS UISearchBar的使用,附有實(shí)例代碼,需要的朋友可以參考下
    2016-12-12
  • iOS實(shí)現(xiàn)懸浮按鈕

    iOS實(shí)現(xiàn)懸浮按鈕

    這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)懸浮按鈕,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-01-01

最新評論