Amazon Alexa show 踩坑指南

引言

本文由zlmediakit核心開發(fā)者 monktan(老衲不出家)編寫,夏楚審閱修訂;文章主要記錄了作者在對接亞馬遜Alexa設(shè)備時遇到的一些經(jīng)驗教訓(xùn),希望前人趟過的坑后人無需再趟。

一、背景

因業(yè)務(wù)發(fā)展,需要在亞馬遜Alexa設(shè)備上實現(xiàn)與訪客視頻對講;調(diào)研發(fā)現(xiàn)亞馬遜Lambad Alexa Skill平臺(以下簡稱亞馬遜平臺)支持WebRTCRTSP兩種方式接入,由于需要實現(xiàn)雙向?qū)χv,只能采用WebRTC方式與Alexa設(shè)備對接;至于門鈴設(shè)備端,硬件資源有限且不帶屏幕,我們采用的私有協(xié)議方式接入。為了便于讀者理解,我們省去了發(fā)現(xiàn)、認(rèn)證等流程,整體架構(gòu)流程圖如下:

圖片.png

二、開始趟坑

研究Alexa WebRTC接入相關(guān)文檔,發(fā)現(xiàn)其視頻支持H264編碼格式,音頻則支持Opus/PCMU/PCMA/AAC

圖片.png

由于WebRTC協(xié)議通常不支持AAC,為了節(jié)省時間,我們直接采用更簡單的PCMU(而不是Opus)來測試,然而測試發(fā)現(xiàn)Alexa設(shè)備竟然無法播放,于是我們對比了之前對接過的web demo,發(fā)現(xiàn)竟然是通的,其架構(gòu)方式也基本一致:

圖片.png

三、趟坑之路

由于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)行抓包分析對比:

圖片.png

對比發(fā)現(xiàn)兩個流同樣都是PCMU數(shù)據(jù),但是數(shù)據(jù)長度不一樣,上面的能播放,下面的無法播放語音,導(dǎo)致我初步懷疑是因為上面啟用了RTP擴(kuò)展導(dǎo)致可以播放,分析SRTP包發(fā)現(xiàn),web端推流確實多了RTP擴(kuò)展,所以長度多了8個字節(jié)。

圖片.png

此時的我雖然不太相信是由于RTP擴(kuò)展引起Alexa設(shè)備無法播放語音,但是對于Alexa黑盒來說,只有盡力一試了,通過修改服務(wù)端代碼,終于做成與web推斷流數(shù)據(jù)包一模一樣了;然而,結(jié)果并沒有什么不一樣,web端推流和設(shè)備推流到底問題在哪里,分析了數(shù)據(jù)長度,數(shù)據(jù)發(fā)送頻率,音頻時間間隔,時間戳增量,甚至嘗試過NTP時間發(fā)送,都是沒有任何效果,依然是播放不出來的。

圖片.png

把數(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的延遲(編碼機(jī)器linux cvm 8c16g主機(jī)),下面是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_size120幀, opus采樣率48000也就是2.5ms一幀,理論采樣延時大概300ms;從我測試的情況來看, 編碼延時很高(400+ms):

圖片.png

由于內(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 

不出意外,你將會得到一堆無用的日志信息:

圖片.png

3.5 趟坑之路五,柳暗花明

經(jīng)過長時間的嘗試,始終無法攻克這個問題,后續(xù)差點絕望到想放棄,實在沒辦法,于是只能在逐字逐句的查看文檔,看看能不能得到點線索,看到這里:

圖片.png

終于頓悟!看描述本意是,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ù)問題!

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

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

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