日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

技术宝典 | WebRTC ADM 源码流程分析

發(fā)布時(shí)間:2025/3/8 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 技术宝典 | WebRTC ADM 源码流程分析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

導(dǎo)讀:?本文主要基于 WebRTC release-72 源碼及云信音視頻團(tuán)隊(duì)積累的相關(guān)經(jīng)驗(yàn)而成,主要分析以下問題:?ADM(Audio Device Manager)的架構(gòu)如何?ADM(Audio Device Manager)的啟動(dòng)流程如何?ADM(Audio Device Manager)的數(shù)據(jù)流向如何?本文主要是分析相關(guān)的核心流程,以便于大家有需求時(shí),能快速地定位到相關(guān)的模塊。

文|陳穩(wěn)穩(wěn)

網(wǎng)易云信資深音視頻客戶端開發(fā)工程師

一、ADM 基本架構(gòu)

?ADM 的架構(gòu)分析?

WebRTC 中,ADM(Audio Device Manager)的行為由 AudioDeviceModule 來定義,具體由 AudioDeviceModuleImpl 來實(shí)現(xiàn)。

從上面的架構(gòu)圖可以看出 AudioDeviceModule 定義了 ADM 相關(guān)的所有行為(上圖只列出了部分核心,更詳細(xì)的請(qǐng)參考源碼中的完整定義)。從 AudioDeviceModule 的定義可以看出?AudioDeviceModule 的主要職責(zé)如下:

  • 初始化音頻播放/采集設(shè)備;

  • 啟動(dòng)音頻播放/采集設(shè)備;

  • 停止音頻播放/采集設(shè)備;

  • 在音頻播放/采集設(shè)備工作時(shí),對(duì)其進(jìn)行操作(例如:Mute , Adjust Volume);

  • 平臺(tái)內(nèi)置 3A 開關(guān)的調(diào)整(主要是針對(duì) Android 平臺(tái));

  • 獲取當(dāng)前音頻播放/采集設(shè)備各種與此相關(guān)的狀態(tài)(類圖中未完全體現(xiàn),詳情參考源碼)

AudioDeviceModule 具體由 AudioDeviceModuleImpl 實(shí)現(xiàn),二者之間還有一個(gè) AudioDeviceModuleForTest,主要是添加了一些測試接口,對(duì)本文的分析無影響,可直接忽略。AudioDeviceModuleImpl 中有兩個(gè)非常重要的成員變量,一個(gè)是?audio_device_,它的具體類型是?std::unique_ptr,另一個(gè)是?audio_device_buffer_,它的具體類型是?AudioDeviceBuffer


其中 audio_device_ 是 AudioDeviceGeneric 類型,AudioDeviceGeneric 是各個(gè)平臺(tái)具體音頻采集和播放設(shè)備的一個(gè)抽象,由它承擔(dān) AudioDeviceModuleImpl 對(duì)具體設(shè)備的操作。涉及到具體設(shè)備的操作,AudioDeviceModuleImpl 除了做一些狀態(tài)的判斷具體的操作設(shè)備工作都由 AudioDeviceGeneric 來完成。AudioDeviceGeneric 的具體實(shí)現(xiàn)由各個(gè)平臺(tái)自己實(shí)現(xiàn),例如對(duì)于 iOS 平臺(tái)具體實(shí)現(xiàn)是 AudioDeviceIOS,Android 平臺(tái)具體實(shí)現(xiàn)是 AudioDeviceTemplate。至于各個(gè)平臺(tái)的具體實(shí)現(xiàn),有興趣的可以單個(gè)分析。這里說一下最重要的共同點(diǎn),從各個(gè)平臺(tái)具體實(shí)現(xiàn)的定義中可以發(fā)現(xiàn),他們都有一個(gè) audio_device_buffer 成員變量,而這個(gè)變量與前面提到的 AudioDeviceModuleImpl 中的另一個(gè)重要成員變量 audio_device_buffer_,其實(shí)二者是同一個(gè)。AudioDeviceModuleImpl 通過 AttachAudioBuffer() 方法,將自己的 audio_device_buffer_ 對(duì)象傳給具體的平臺(tái)實(shí)現(xiàn)對(duì)象。

audio_device_buffer_ 的具體類型是 AudioDeviceBuffer,AudioDeviceBuffer 中的 play_buffer_、rec_buffer_ 是 int16_t ?類型的 buffer,前者做為向下獲取播放 PCM 數(shù)據(jù)的 Buffer,后者做為向下傳遞采集 PCM 數(shù)據(jù)的 Buffer,具體的 PCM 數(shù)據(jù)流向在后面的數(shù)據(jù)流向章節(jié)具體分析,而另一個(gè)成員變量 audio_transport_cb_,類型為 AudioTransport,從 AudioTransport 接口定義的中的兩個(gè)核心方法不難看出他的作用,一是向下獲取播放 PCM 數(shù)據(jù)存儲(chǔ)在 play_buffer_,另一個(gè)把采集存儲(chǔ)在 rec_buffer_ 中的 PCM 數(shù)據(jù)向下傳遞,后續(xù)具體流程參考數(shù)據(jù)流向章節(jié)。

?關(guān)于 ADM 擴(kuò)展的思考?

從 WebRTC ADM 的實(shí)現(xiàn)來看,WebRTC 只實(shí)現(xiàn)對(duì)應(yīng)了各個(gè)平臺(tái)具體的硬件設(shè)備,并沒什么虛擬設(shè)備。但是在實(shí)際的項(xiàng)目,往往需要支持外部音頻輸入/輸出,就是由業(yè)務(wù)上層 push/pull 音頻數(shù)據(jù)(PCM ...),而不是直接啟動(dòng)平臺(tái)硬件進(jìn)行采集/播放。在這種情況下,雖然原生的 WebRTC 不支持,但是要改造也是非常的簡單,由于虛擬設(shè)備與平臺(tái)無關(guān),所以可以直接在 AudioDeviceModuleImpl 中增加一個(gè)與真實(shí)設(shè)備 audio_device_ 對(duì)應(yīng)的Virtual Device(變量名暫定為virtual_device_),virtual_device_ 也跟 audio_device_ 一樣,實(shí)現(xiàn) AudioDeviceGeneric 相關(guān)接口,然后參考 audio_device_ 的實(shí)現(xiàn)去實(shí)現(xiàn)數(shù)據(jù)的“采集”(push)與 “播放”(pull),無須對(duì)接具體平臺(tái)的硬件設(shè)備,唯一需要處理的就是物理設(shè)備 audio_device_ 與虛擬設(shè)備 virtual_device_ 之間的切換或協(xié)同工作。

二、ADM 設(shè)備的啟動(dòng)

?啟動(dòng)時(shí)機(jī)?

ADM 設(shè)備的啟動(dòng)時(shí)機(jī)并無什么特殊要求,只要 ADM 創(chuàng)建后即可,不過 WebRTC 的 Native 源碼中會(huì)在 SDP 協(xié)商好后去檢查一下是否需要啟動(dòng)相關(guān)的 ADM 設(shè)備,如果需要就會(huì)啟動(dòng)相關(guān)的 ADM 設(shè)備,采集與播放設(shè)備的啟動(dòng)二者是完全獨(dú)立的,但流程大同小異,相關(guān)觸發(fā)代碼如下,自上而下閱讀即可。

以下是采集設(shè)備啟動(dòng)的觸發(fā)源碼(前面幾步還有其他觸發(fā)入口,但后面是一樣的,這里只做核心流程展示):

//cricket::VoiceChannelvoid VoiceChannel::UpdateMediaSendRecvState_w() { //*************** bool send = IsReadyToSendMedia_w(); media_channel()->SetSend(send); } // cricket::WebRtcVoiceMediaChannelvoid WebRtcVoiceMediaChannel::SetSend(bool send) { //*************** for (auto& kv : send_streams_) { kv.second->SetSend(send); }} //cricket::WebRtcVoiceMediaChannel::WebRtcAudioSendStream void SetSend(bool send) { //*************** UpdateSendState(); } //cricket::WebRtcVoiceMediaChannel::WebRtcAudioSendStream void UpdateSendState() { //*************** if (send_ && source_ != nullptr && rtp_parameters_.encodings[0].active) { stream_->Start(); } else { // !send || source_ = nullptr stream_->Stop(); } } // webrtc::internal::WebRtcAudioSendStream void AudioSendStream::Start() { //*************** audio_state()->AddSendingStream(this, encoder_sample_rate_hz_, encoder_num_channels_);} // webrtc::internal::AudioStatevoid AudioState::AddSendingStream(webrtc::AudioSendStream* stream, int sample_rate_hz, size_t num_channels) { //*************** //檢查下采集設(shè)備是否已經(jīng)啟動(dòng),如果沒有,那么在這啟動(dòng) auto* adm = config_.audio_device_module.get(); if (!adm->Recording()) { if (adm->InitRecording() == 0) { if (recording_enabled_) { adm->StartRecording(); } } else { RTC_DLOG_F(LS_ERROR) << "Failed to initialize recording."; } }}

從上面采集設(shè)備啟動(dòng)的觸發(fā)源碼可以看出,如果需要發(fā)送音頻,不管前面采集設(shè)備是否啟動(dòng),在 SDP 協(xié)商好后,一定會(huì)啟動(dòng)采集設(shè)備。如果我們想把采集設(shè)備的啟動(dòng)時(shí)機(jī)掌握在上層業(yè)務(wù)手中,那么只要注釋上面 AddSendingStream 方法中啟動(dòng)設(shè)備那幾行代碼即可,然后在需要的時(shí)候自行通過 ADM 啟動(dòng)采集設(shè)備。

以下是播放設(shè)備啟動(dòng)的觸發(fā)源碼(前面幾步還有其他觸發(fā)入口,但后面是一樣的,這里只做核心流程展示):

//cricket::VoiceChannelvoid VoiceChannel::UpdateMediaSendRecvState_w() { //*************** bool recv = IsReadyToReceiveMedia_w(); media_channel()->SetPlayout(recv); } // cricket::WebRtcVoiceMediaChannelvoid WebRtcVoiceMediaChannel::SetPlayout(bool playout) { //*************** return ChangePlayout(desired_playout_);} // cricket::WebRtcVoiceMediaChannelvoid WebRtcVoiceMediaChannel::ChangePlayout(bool playout) {//*************** for (const auto& kv : recv_streams_) { kv.second->SetPlayout(playout); }} //cricket::WebRtcVoiceMediaChannel::WebRtcAudioReceiveStream void SetPlayout(bool playout) { //*************** if (playout) { stream_->Start(); } else { stream_->Stop(); } } // webrtc::internal::AudioReceiveStreamvoid AudioReceiveStream::Start() { //*************** audio_state()->AddReceivingStream(this);} //webrtc::internal::AudioStatevoid AudioState::AddReceivingStream(webrtc::AudioReceiveStream* stream) { //*************** // //檢查下播放設(shè)備是否已經(jīng)啟動(dòng),如果沒有,那么在這啟動(dòng) auto* adm = config_.audio_device_module.get(); if (!adm->Playing()) { if (adm->InitPlayout() == 0) { if (playout_enabled_) { adm->StartPlayout(); } } else { RTC_DLOG_F(LS_ERROR) << "Failed to initialize playout."; } }}

從上面播放設(shè)備啟動(dòng)的觸發(fā)源碼可以看出,如果需要播放音頻,不管前面播放設(shè)備是否啟動(dòng),在 SDP 協(xié)商好后,一定會(huì)啟動(dòng)播放設(shè)備。如果我們想把播放設(shè)備的啟動(dòng)時(shí)機(jī)掌握在上層業(yè)務(wù)手中,那么只要注釋上面 AddReceivingStream 方法中啟動(dòng)設(shè)備那幾行代碼即可,然后在需要的時(shí)候自行通過 ADM 啟動(dòng)播放設(shè)備。

?啟動(dòng)流程?

當(dāng)需要啟動(dòng) ADM 設(shè)備時(shí),先調(diào)用 ADM 的 InitXXX,接著是 ADM 的 StartXXX,當(dāng)然最終是透過上面的架構(gòu)層層調(diào)用具體平臺(tái)相應(yīng)的實(shí)現(xiàn),詳細(xì)流程如下圖:

?關(guān)于設(shè)備的停止?

了解了 ADM 設(shè)備的啟動(dòng),那么與之對(duì)應(yīng)的停止動(dòng)作,就無需多言。如果大家看了源碼,會(huì)發(fā)現(xiàn)其實(shí)停止的動(dòng)作及流程與啟動(dòng)基本上是一一對(duì)應(yīng)的。

三、ADM 音頻數(shù)據(jù)流向

音頻數(shù)據(jù)的發(fā)送?

?

上圖是音頻數(shù)據(jù)發(fā)送的核心流程,主要是核心函數(shù)的調(diào)用及線程的切換。PCM 數(shù)據(jù)從硬件設(shè)備中被采集出來,在采集線程做些簡單的數(shù)據(jù)封裝會(huì)很快進(jìn)入 APM 模塊做相應(yīng)的 3A 處理,從流程上看 APM 模塊很靠近原始 PCM 數(shù)據(jù),這一點(diǎn)對(duì) APM 的處理效果有非常大的幫助,感興趣的同學(xué)可以深入研究下 APM 相關(guān)的知識(shí)。之后數(shù)據(jù)就會(huì)被封裝成一個(gè) Task,投遞到一個(gè)叫 rtp_send_controller 的線程中,到此采集線程的工作就完成了,采集線程也能盡快開始下一輪數(shù)據(jù)的讀取,這樣能最大限度的減小對(duì)采集的影響,盡快讀取新的 PCM 數(shù)據(jù),防止 PCM 數(shù)據(jù)丟失或帶來不必要的延時(shí)。

接著數(shù)據(jù)就到了 rtp_send_controller 線程,rtp_send_controller 線程的在此的作用主要有三個(gè),一是做 rtp 發(fā)送的擁塞控制,二是做 PCM 數(shù)據(jù)的編碼,三是將編碼后的數(shù)據(jù)打包成 RtpPacketToSend(RtpPacket)格式。最終的 RtpPacket 數(shù)據(jù)會(huì)被投遞到一個(gè)叫 RoundRobinPacketQueue 的隊(duì)列中,至此 rtp_send_controller 線程的工作完成。

后面的 RtpPacket 數(shù)據(jù)將會(huì)在 SendControllerThread 中被處理,SendControllerThread 主要用于發(fā)送狀態(tài)及窗口擁塞的控制,最后數(shù)據(jù)通過消息的形式(type: MSG_SEND_RTP_PACKET)發(fā)送到 Webrtc 三大線程之一的網(wǎng)絡(luò)線程(Network Thread),再往后就是發(fā)送給網(wǎng)絡(luò)。到此整個(gè)發(fā)送過程結(jié)束。

?數(shù)據(jù)的接收與播放?

?

上圖是音頻數(shù)據(jù)接收及播放的核心流程。網(wǎng)絡(luò)線程(Network Thread)負(fù)責(zé)從網(wǎng)絡(luò)接收 RTP 數(shù)據(jù),隨后異步給工作線程(Work Thread)進(jìn)行解包及分發(fā)。如果接收多路音頻,那么就有多個(gè) ChannelReceive,每個(gè)的處理流程都一樣,最后未解碼的音頻數(shù)據(jù)存放在 NetEq 模塊的 packet_buffer_ 中。與此同時(shí)播放設(shè)備線程不斷的從當(dāng)前所有音頻 ChannelReceive 獲取音頻數(shù)據(jù)(10ms 長度),進(jìn)而觸發(fā) NetEq 請(qǐng)求解碼器進(jìn)行音頻解碼。對(duì)于音頻解碼,WebRTC 提供了統(tǒng)一的接口,具體的解碼器只需要實(shí)現(xiàn)相應(yīng)的接口即可,比如 WebRTC 默認(rèn)的音頻解碼器 opus 就是如此。當(dāng)遍歷并解碼完所有 ChannelReceive 中的數(shù)據(jù),后面就是通過 AudioMixer 混音,混完后交給 APM 模塊處理,處理完最后是給設(shè)備播放。

作者介紹?

陳穩(wěn)穩(wěn),網(wǎng)易云信資深音視頻客戶端開發(fā)工程師,主要負(fù)責(zé) Android 音視頻的開發(fā)及適配。

與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖

總結(jié)

以上是生活随笔為你收集整理的技术宝典 | WebRTC ADM 源码流程分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 国产伦精品一区二区三 | 国产电影一区二区三区爱妃记 | 久操社区 | 亚洲精品在 | 三上悠亚影音先锋 | 欧美日本高清 | 免费看欧美一级特黄a大片 国产免费的av | 亚洲视频在线网 | 91国产精品 | va欧美 | 日本成人免费在线视频 | 2022av在线 | 久久电影一区 | 制服丝袜一区 | 污视频在线免费观看 | 色爽爽一区二区三区 | 99re这里只有精品在线观看 | 国产破处av | 欧美日韩免费 | 亚洲日本在线观看 | 中文字幕人妻互换av久久 | 波多野结衣a级片 | 日韩欧美一区二区三区在线 | 女人被男人躁得好爽免费视频 | 久久这里精品 | 非洲黑寡妇性猛交视频 | 男人的天堂一级片 | 邪恶久久 | 国产一级特黄a高潮片 | 国产精品美女在线观看 | 蜜桃臀av一区二区三区 | 丰满人妻综合一区二区三区 | 91a视频| 欧美激情亚洲色图 | 视频福利在线观看 | 欧美xxxx69 | 免费黄色激情视频 | 免费在线观看的av | 成人在线免费高清视频 | 亚洲国产婷婷香蕉久久久久久99 | www一起操| 日本少妇xxxx | 欧美综合亚洲图片综合区 | 香蕉在线观看视频 | 给我免费观看片在线电影的 | 国产免费一区二区三区最新不卡 | 美女av网| 国产精品亚州 | 天天看天天摸天天操 | 极品美女被c| 国产草逼视频 | 成人深夜视频在线观看 | 亚洲一区二区三区欧美 | xxxx亚洲| 久久噜噜色综合一区二区 | 老头老夫妇自拍性tv | 国产久一 | 成人黄色在线看 | videos另类灌满极品另类 | 国产又粗又猛又爽又黄的视频一 | 日本黄页视频 | 激情高潮呻吟抽搐喷水 | 国产传媒在线播放 | 国产高清自拍视频 | av中文天堂| 久久久午夜精品 | 亚洲社区在线观看 | 日韩精品免费观看 | 日韩精品高清在线观看 | 欧美中文字幕第一页 | 辟里啪啦国语版免费观看 | 日本成人免费观看 | 亚洲女优在线观看 | 成年人免费网 | 亚洲黄色av网站 | 少妇性l交大片 | 暖暖免费观看日本版 | 精人妻无码一区二区三区 | 韩国一区二区三区视频 | 献给魔王伊伏洛基亚吧动漫在线观看 | 伊人精品在线观看 | 亚洲一区久久久 | 精品在线一区二区三区 | 国产精品88av| 好吊妞在线观看 | 极品色影视 | 视频福利在线观看 | 久久精品9| 国产女人水真多18毛片18精品 | 波多野结衣网址 | 亚洲自拍电影 | 久久国产二区 | 国产在线网站 | 骚虎视频最新网址 | 久久久国产精品免费 | 成人在线激情视频 | 欧美gv在线观看 | 高清欧美性猛交xxxx黑人猛交 | 成人在线播放av |