引言
本文由zlmediakit
核心開發(fā)者 monktan(老衲不出家)
編寫,夏楚
審閱修訂;文章主要記錄了作者在對接亞馬遜Alexa
設(shè)備時遇到的一些經(jīng)驗教訓(xùn)术辐,希望前人趟過的坑后人無需再趟。
一、背景
因業(yè)務(wù)發(fā)展陵究,需要在亞馬遜Alexa
設(shè)備上實現(xiàn)與訪客視頻對講;調(diào)研發(fā)現(xiàn)亞馬遜Lambad Alexa Skill平臺
(以下簡稱亞馬遜平臺)支持WebRTC
和RTSP
兩種方式接入奥帘,由于需要實現(xiàn)雙向?qū)χv铜邮,只能采用WebRTC
方式與Alexa設(shè)備對接;至于門鈴設(shè)備端寨蹋,硬件資源有限且不帶屏幕松蒜,我們采用的私有協(xié)議方式接入。為了便于讀者理解钥庇,我們省去了發(fā)現(xiàn)牍鞠、認(rèn)證等流程,整體架構(gòu)流程圖如下:
二评姨、開始趟坑
研究Alexa WebRTC
接入相關(guān)文檔难述,發(fā)現(xiàn)其視頻支持H264
編碼格式萤晴,音頻則支持Opus/PCMU/PCMA/AAC
:
由于WebRTC
協(xié)議通常不支持AAC
,為了節(jié)省時間胁后,我們直接采用更簡單的PCMU
(而不是Opus
)來測試店读,然而測試發(fā)現(xiàn)Alexa
設(shè)備竟然無法播放,于是我們對比了之前對接過的web demo
攀芯,發(fā)現(xiàn)竟然是通的屯断,其架構(gòu)方式也基本一致:
三、趟坑之路
由于Alexa
設(shè)備死活無法播放門鈴的音視頻流而web demo卻一切正常侣诺,我做了大量的努力和嘗試殖演,包括sdp的分析對比,rtp的分析對比年鸳、變換音頻編碼格式(因為單視頻模式有播放成功的案例趴久,原因是單視頻模式請求鏈路時間不一樣,時間更短)搔确、音頻編碼切片長度彼棍、音視頻時間戳同步、分析設(shè)備日志等工作膳算。
3.1 趟坑之路一座硕,分析對比SDP
- Alexa設(shè)備Offer
v=0
o=- 3889820441 3889820441 IN IP4 0.0.0.0
s=a 2 z
c=IN IP4 0.0.0.0
t=0 0
a=group:BUNDLE audio0 video0
m=audio 1 UDP/TLS/RTP/SAVPF 96 0 8
a=candidate:1 1 UDP 2013266431 **** 53179 typ host
a=candidate:2 1 TCP 1015021823 **** 9 typ host tcptype active
a=candidate:3 1 TCP 1010827519 **** 58004 typ host tcptype passive
a=candidate:1 2 UDP 2013266430 **** 49423 typ host
a=candidate:2 2 TCP 1015021822 **** 9 typ host tcptype active
a=candidate:3 2 TCP 1010827518 **** 51167 typ host tcptype passive
a=setup:actpass
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=rtpmap:96 opus/48000/2
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-mux
a=sendrecv
a=mid:audio0
a=ssrc:724561565 cname:user2420442903@host-e8501a47
a=ice-ufrag:E9vw
a=ice-pwd:CWbMx5SvmNls7LJ23gJJUk
a=fingerprint:sha-256 2D:A0:F3:7D:0A:58:7E:B9:CC:79:C7:10:FB:BB:F9:F7:7D:EE:92:84:F5:08:D2:BC:25:76:C7:75:FF:8B:DB:75
m=video 1 UDP/TLS/RTP/SAVPF 99
a=candidate:1 1 UDP 2013266431 **** 53179 typ host
a=candidate:3 1 TCP 1010827519 **** 58004 typ host tcptype passive
a=candidate:2 1 TCP 1015021823 **** 9 typ host tcptype active
a=candidate:1 2 UDP 2013266430 **** 49423 typ host
a=candidate:2 2 TCP 1015021822 **** 9 typ host tcptype active
a=candidate:3 2 TCP 1010827518 **** 51167 typ host tcptype passive
b=AS:2500
a=setup:actpass
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=rtpmap:99 H264/90000
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-mux
a=sendrecv
a=mid:video0
a=rtcp-fb:99 nack
a=rtcp-fb:99 nack pli
a=rtcp-fb:99 ccm fir
a=ssrc:3568304867 cname:user2420442903@host-e8501a47
a=ice-ufrag:E9vw
a=ice-pwd:CWbMx5SvmNls7LJ23gJJUk
a=fingerprint:sha-256 2D:A0:F3:7D:0A:58:7E:B9:CC:79:C7:10:FB:BB:F9:F7:7D:EE:92:84:F5:08:D2:BC:25:76:C7:75:FF:8B:DB:75
- 平臺回復(fù)Answer
v=0
o=- 0 0 IN IP4 127.0.0.1
s=webrtc_core
t=0 0
a=ice-lite
a=group:BUNDLE audio0 video0
a=rtcp-mux
a=msid-semantic: WMS alexa_test
m=audio 9 UDP/TLS/RTP/SAVPF 0
a=rtcp:9 IN IP4 0.0.0.0
c=IN IP4 0.0.0.0
a=ice-ufrag:93b7543b756a8408
a=ice-pwd:b97ec11486ce7a693d060e80
a=fingerprint:sha-256 4D:1A:F7:3D:CD:5E:E3:24:E5:30:40:F5:E4:1A:9B:E4:14:C6:83:A8:B3:EE:33:0D:D7:62:84:CE:14:DA:C0:8C
a=setup:passive
a=sendrecv
a=mid:audio0
a=msid:alexa_test MainAudio
a=rtcp-mux
a=rtpmap:0 PCMU/8000
a=ssrc:2159555873 cname:webrtccore
a=ssrc:2159555873 msid:alexa_test MainAudio
a=ssrc:2159555873 mslabel:alexa_test
a=ssrc:2159555873 label:MainAudio
a=candidate:foundation 1 udp 100 **** 8000 typ srflx raddr **** rport 8000 generation 0
m=video 9 UDP/TLS/RTP/SAVPF 99
a=rtcp:9 IN IP4 0.0.0.0
c=IN IP4 0.0.0.0
a=ice-ufrag:93b7543b756a8408
a=ice-pwd:b97ec11486ce7a693d060e80
a=fingerprint:sha-256 4D:1A:F7:3D:CD:5E:E3:24:E5:30:40:F5:E4:1A:9B:E4:14:C6:83:A8:B3:EE:33:0D:D7:62:84:CE:14:DA:C0:8C
a=setup:passive
a=sendrecv
a=mid:video0
a=msid:alexa_test MainVideo
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:99 H264/90000
a=rtcp-fb:99 nack
a=rtcp-fb:99 nack pli
a=rtcp-fb:99 ccm fir
a=fmtp:99 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=420015
a=ssrc-group:FID 28521173 3056259
a=ssrc:28521173 cname:webrtccore
a=ssrc:28521173 msid:alexa_test MainVideo
a=ssrc:28521173 mslabel:alexa_test
a=ssrc:28521173 label:MainVideo
a=ssrc:3056259 cname:webrtccore
a=ssrc:3056259 msid:alexa_test MainVideo
a=ssrc:3056259 mslabel:alexa_test
a=ssrc:3056259 label:MainVideo
a=candidate:foundation 1 udp 100 101.33.240.139 8000 typ srflx raddr 101.33.240.139 rport 8000 generation 0
這里咋一看好像沒啥問題;仔細(xì)發(fā)現(xiàn)涕蜂,Alexa PCMU和PCMA在SDP中沒有出現(xiàn)a=rtpmap
华匾,可能導(dǎo)致協(xié)商不成功,于是我修改了SDP宇葱,添加了:a=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\n
,然而發(fā)現(xiàn)并沒什么用瘦真,還是對接Alexa
設(shè)備無輸出。
3.2 趟坑之路二黍瞧,抓包分析
因web demo
推流诸尽,Alexa
設(shè)備可以正常播放,但是拉取門鈴設(shè)備流無法播放印颤,分別對起流進(jìn)行抓包分析對比:
對比發(fā)現(xiàn)兩個流同樣都是PCMU
數(shù)據(jù)您机,但是數(shù)據(jù)長度不一樣,上面的能播放年局,下面的無法播放語音际看,導(dǎo)致我初步懷疑是因為上面啟用了RTP擴展
導(dǎo)致可以播放,分析SRTP包發(fā)現(xiàn)矢否,web端推流確實多了RTP擴展仲闽,所以長度多了8個字節(jié)。
此時的我雖然不太相信是由于RTP擴展引起Alexa設(shè)備無法播放語音僵朗,但是對于Alexa黑盒來說赖欣,只有盡力一試了屑彻,通過修改服務(wù)端代碼,終于做成與web推斷流數(shù)據(jù)包一模一樣了顶吮;然而社牲,結(jié)果并沒有什么不一樣,web端推流和設(shè)備推流到底問題在哪里悴了,分析了數(shù)據(jù)長度搏恤,數(shù)據(jù)發(fā)送頻率,音頻時間間隔湃交,時間戳增量熟空,甚至嘗試過NTP時間發(fā)送
,都是沒有任何效果巡揍,依然是播放不出來的痛阻。
把數(shù)據(jù)分析數(shù)據(jù)發(fā)給其他WebRTC
領(lǐng)域?qū)<覀兎治觯麄円部床怀鍪裁磫栴}腮敌,建議我使用opus
編碼嘗試一下,畢竟它在Alexa
官網(wǎng)是preferred codec
俏扩,鑒于此決定先用opus嘗試糜工。
3.3 趟坑之路三,換Opus編碼
opus編碼在FFmpeg
中直接采用AV_CODEC_ID_OPUS
方式查找的解碼器录淡,找到的是內(nèi)置opus編碼器
捌木,實測發(fā)現(xiàn)編碼延時很大,達(dá)到了普遍350ms~450ms
的延遲(編碼機器linux cvm 8c16g主機)嫉戚,下面是FFmpeg內(nèi)部源碼:
AVCodec ff_opus_encoder = {
.name = "opus",
.long_name = NULL_IF_CONFIG_SMALL("Opus"),
.type = AVMEDIA_TYPE_AUDIO,
.id = AV_CODEC_ID_OPUS,
.defaults = opusenc_defaults,
.priv_class = &opusenc_class,
.priv_data_size = sizeof(OpusEncContext),
.init = opus_encode_init,
.encode2 = opus_encode_frame,
.close = opus_encode_end,
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_INIT_CLEANUP,
.capabilities = AV_CODEC_CAP_EXPERIMENTAL | AV_CODEC_CAP_SMALL_LAST_FRAME | AV_CODEC_CAP_DELAY,
.supported_samplerates = (const int []){ 48000, 0 },
.channel_layouts = (const uint64_t []){ AV_CH_LAYOUT_MONO,
AV_CH_LAYOUT_STEREO, 0 },
.sample_fmts = (const enum AVSampleFormat[]){ AV_SAMPLE_FMT_FLTP,
AV_SAMPLE_FMT_NONE },
};
最后定位內(nèi)置的opusenc.c
(注意, 不是libopusenc.c
)設(shè)置的frame_size
是120幀
, opus采樣率48000也就是2.5ms一幀刨裆,理論采樣延時大概300ms
;從我測試的情況來看, 編碼延時很高(400+ms
):
由于內(nèi)置opusenc.c編碼器延遲實在太大彬檀,顯然不適合WebRTC低延時場景帆啃,開始有點不知所以,后來在朋友的指導(dǎo)下窍帝,發(fā)現(xiàn)FFmpeg
還有個libopus
編碼器努潘,于是決定使用libopus來編碼,F(xiàn)Fmpeg中l(wèi)ibopus信息如下:
AVCodec ff_libopus_encoder = {
.name = "libopus",
.long_name = NULL_IF_CONFIG_SMALL("libopus Opus"),
.type = AVMEDIA_TYPE_AUDIO,
.id = AV_CODEC_ID_OPUS,
.priv_data_size = sizeof(LibopusEncContext),
.init = libopus_encode_init,
.encode2 = libopus_encode,
.close = libopus_encode_close,
.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SMALL_LAST_FRAME,
.sample_fmts = (const enum AVSampleFormat[]){ AV_SAMPLE_FMT_S16,
AV_SAMPLE_FMT_FLT,
AV_SAMPLE_FMT_NONE },
.supported_samplerates = libopus_sample_rates,
.priv_class = &libopus_class,
.defaults = libopus_defaults,
.wrapper_name = "libopus",
};
替換libopus
編碼后坤学,編碼延時在40~50ms
內(nèi)疯坤,效果符合預(yù)期的;然而在做了音視頻同步后深浮,Alexa
依然播放不出opus
語音压怠,此時已經(jīng)快到了黔驢技窮的邊緣了。
3.4 趟坑之路四飞苇,分析Alexa日志
對于Alexa這個黑盒菌瘫,我們極度缺乏調(diào)試手段洋闽,只能通過給Amazon提交工單,很遺憾突梦,工單并未得到相應(yīng)回復(fù)诫舅,通過內(nèi)部關(guān)系找到Alexa中國區(qū)負(fù)責(zé)對接人,對方需要提供公司對接商務(wù)信息宫患,才能予以支持刊懈,且需要走商務(wù)流程;沒辦法娃闲,只能抓取Android端Alexa智能這個APP日志看能否找到相應(yīng)線索虚汛。
于是搭建Android adb環(huán)境,抓取com.amazon.dee.app:alexa
包日志:
# 查看進(jìn)程號
adb shell ps
# 抓取日志皇帮, xxx為進(jìn)程號
adb logcat xxx
不出意外卷哩,你將會得到一堆無用的日志信息:
3.5 趟坑之路五,柳暗花明
經(jīng)過長時間的嘗試属拾,始終無法攻克這個問題将谊,后續(xù)差點絕望到想放棄,實在沒辦法渐白,于是只能在逐字逐句的查看文檔尊浓,看看能不能得到點線索,看到這里:
終于頓悟纯衍!看描述本意是栋齿,Alexa
設(shè)備發(fā)起offer請求后,需要在6s內(nèi)回復(fù)相應(yīng)的Answer SDP
襟诸,然而最后實測發(fā)現(xiàn)這個6s是需要包含音視頻數(shù)據(jù)的瓦堵,如果6s內(nèi)沒有音視頻數(shù)據(jù)發(fā)送,Alexa建立連接失敗歌亲,但是 不會有任何提示菇用,不會有任何提示,不會有任何提示应结!刨疼。
-
經(jīng)過一番調(diào)整,終于完美播放:
圖片.png
圖片.png
四鹅龄、總結(jié)
經(jīng)過這番折騰揩慕,最后復(fù)盤下事情的來龍去脈,開始死活不通的原因如下:
亞馬遜的服務(wù)器部署在海外扮休,整個信令交互延時很高迎卤,大大降低了在6秒鐘內(nèi)完成交互的成功率,這也是一直失敗的最大原因玷坠。
門鈴設(shè)備的喚醒蜗搔、控制延時較高劲藐,加大了整個鏈路的的延時。
門鈴設(shè)備音頻采集樟凄、編碼聘芜、輸出時間比視頻晚幾百毫秒,導(dǎo)致單視頻成功率較高缝龄,但是復(fù)合流時成功率很低汰现,從而產(chǎn)生音頻數(shù)據(jù)是否有問題的誤導(dǎo),浪費很多時間花在排查音頻切片(確保20ms一個包)叔壤、編碼瞎饲、時間戳等問題上。
Alexa設(shè)備是個封閉的黑盒設(shè)備炼绘,無法獲取準(zhǔn)確的失敗原因嗅战;另外,其文檔描述也不準(zhǔn)確俺亮;這些坑必須一個一個趟出來驮捍,沒有前人指導(dǎo),很難注意到這些問題铅辞,而國內(nèi)在這方面的實踐較少厌漂,相關(guān)技術(shù)文章不多。
最后斟珊,感謝整個過程一直支持我的小伙伴們,感謝他們的悉心指導(dǎo)富纸,遇事不要氣餒囤踩,自己短期解決不了的問題,不要死磕牛角尖晓褪,盡量集思廣益堵漱,從不同角度去嘗試,最終你會發(fā)現(xiàn)涣仿,這可能根本就不是一個技術(shù)問題勤庐!