Amazon Alexa show 踩坑指南

引言

本文由zlmediakit核心开发者 monktan(老衲不出家)编写,夏楚审阅修订;文章主要记录了作者在对接亚马逊Alexa设备时遇到的一些经验教训,希望前人趟过的坑后人无需再趟。

一、背景

因业务发展,需要在亚马逊Alexa设备上实现与访客视频对讲;调研发现亚马逊Lambad Alexa Skill平台(以下简称亚马逊平台)支持WebRTCRTSP两种方式接入,由于需要实现双向对讲,只能采用WebRTC方式与Alexa设备对接;至于门铃设备端,硬件资源有限且不带屏幕,我们采用的私有协议方式接入。为了便于读者理解,我们省去了发现、认证等流程,整体架构流程图如下:

图片.png

二、开始趟坑

研究Alexa WebRTC接入相关文档,发现其视频支持H264编码格式,音频则支持Opus/PCMU/PCMA/AAC

图片.png

由于WebRTC协议通常不支持AAC,为了节省时间,我们直接采用更简单的PCMU(而不是Opus)来测试,然而测试发现Alexa设备竟然无法播放,于是我们对比了之前对接过的web demo,发现竟然是通的,其架构方式也基本一致:

图片.png

三、趟坑之路

由于Alexa设备死活无法播放门铃的音视频流而web demo却一切正常,我做了大量的努力和尝试,包括sdp的分析对比,rtp的分析对比、变换音频编码格式(因为单视频模式有播放成功的案例,原因是单视频模式请求链路时间不一样,时间更短)、音频编码切片长度、音视频时间戳同步、分析设备日志等工作。

3.1 趟坑之路一,分析对比SDP

  • Alexa设备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
  • 平台回复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

这里咋一看好像没啥问题;仔细发现,Alexa PCMU和PCMA在SDP中没有出现a=rtpmap,可能导致协商不成功,于是我修改了SDP,添加了:a=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\n,然而发现并没什么用,还是对接Alexa设备无输出。

3.2 趟坑之路二,抓包分析

web demo推流,Alexa设备可以正常播放,但是拉取门铃设备流无法播放,分别对起流进行抓包分析对比:

图片.png

对比发现两个流同样都是PCMU数据,但是数据长度不一样,上面的能播放,下面的无法播放语音,导致我初步怀疑是因为上面启用了RTP扩展导致可以播放,分析SRTP包发现,web端推流确实多了RTP扩展,所以长度多了8个字节

图片.png

此时的我虽然不太相信是由于RTP扩展引起Alexa设备无法播放语音,但是对于Alexa黑盒来说,只有尽力一试了,通过修改服务端代码,终于做成与web推断流数据包一模一样了;然而,结果并没有什么不一样,web端推流和设备推流到底问题在哪里,分析了数据长度,数据发送频率,音频时间间隔,时间戳增量,甚至尝试过NTP时间发送,都是没有任何效果,依然是播放不出来的。

图片.png

把数据分析数据发给其他WebRTC领域专家们分析,他们也看不出什么问题,建议我使用opus编码尝试一下,毕竟它在Alexa官网是preferred codec,鉴于此决定先用opus尝试。

3.3 趟坑之路三,换Opus编码

opus编码在FFmpeg中直接采用AV_CODEC_ID_OPUS方式查找的解码器,找到的是内置opus编码器,实测发现编码延时很大,达到了普遍350ms~450ms的延迟(编码机器linux cvm 8c16g主机),下面是FFmpeg内部源码:

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 },
};

最后定位内置的opusenc.c(注意, 不是libopusenc.c)设置的frame_size120帧, opus采样率48000也就是2.5ms一帧,理论采样延时大概300ms;从我测试的情况来看, 编码延时很高(400+ms):

图片.png

由于内置opusenc.c编码器延迟实在太大,显然不适合WebRTC低延时场景,开始有点不知所以,后来在朋友的指导下,发现FFmpeg还有个libopus编码器,于是决定使用libopus来编码,FFmpeg中libopus信息如下:

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内,效果符合预期的;然而在做了音视频同步后,Alexa依然播放不出opus语音,此时已经快到了黔驴技穷的边缘了。

3.4 趟坑之路四,分析Alexa日志

对于Alexa这个黑盒,我们极度缺乏调试手段,只能通过给Amazon提交工单,很遗憾,工单并未得到相应回复,通过内部关系找到Alexa中国区负责对接人,对方需要提供公司对接商务信息,才能予以支持,且需要走商务流程;没办法,只能抓取Android端Alexa智能这个APP日志看能否找到相应线索。

于是搭建Android adb环境,抓取com.amazon.dee.app:alexa包日志:

# 查看进程号
adb shell ps
# 抓取日志, xxx为进程号
adb logcat xxx 

不出意外,你将会得到一堆无用的日志信息:

图片.png

3.5 趟坑之路五,柳暗花明

经过长时间的尝试,始终无法攻克这个问题,后续差点绝望到想放弃,实在没办法,于是只能在逐字逐句的查看文档,看看能不能得到点线索,看到这里:

图片.png

终于顿悟!看描述本意是,Alexa设备发起offer请求后,需要在6s内回复相应的Answer SDP,然而最后实测发现这个6s是需要包含音视频数据的,如果6s内没有音视频数据发送,Alexa建立连接失败,但是 不会有任何提示,不会有任何提示,不会有任何提示!

  • 经过一番调整,终于完美播放:


    图片.png

    图片.png

四、总结

经过这番折腾,最后复盘下事情的来龙去脉,开始死活不通的原因如下:

  • 亚马逊的服务器部署在海外,整个信令交互延时很高,大大降低了在6秒钟内完成交互的成功率,这也是一直失败的最大原因。

  • 门铃设备的唤醒、控制延时较高,加大了整个链路的的延时。

  • 门铃设备音频采集、编码、输出时间比视频晚几百毫秒,导致单视频成功率较高,但是复合流时成功率很低,从而产生音频数据是否有问题的误导,浪费很多时间花在排查音频切片(确保20ms一个包)、编码、时间戳等问题上。

  • Alexa设备是个封闭的黑盒设备,无法获取准确的失败原因;另外,其文档描述也不准确;这些坑必须一个一个趟出来,没有前人指导,很难注意到这些问题,而国内在这方面的实践较少,相关技术文章不多。

最后,感谢整个过程一直支持我的小伙伴们,感谢他们的悉心指导,遇事不要气馁,自己短期解决不了的问题,不要死磕牛角尖,尽量集思广益,从不同角度去尝试,最终你会发现,这可能根本就不是一个技术问题!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容