基于pjmedia的音视频对讲(一)

这两天在搞音视频对讲,boss说不用搞太复杂,服务器不需要,输入对方IP直接连接通话即可,于是在我的建议下,选用了我之前做SIP应用用到的一个开源SIP库pjsip中的一个子库pjmedia。
我的思路是:信令直接用SDP包,不用SIP了。不用SIP的原因是我觉得现在的需求还用不到:需要连接时直接和对方negotiate SDP,成功后即可以采集–编码–RTP打包发送了,简单明了,需要断开时只要向对方发一个BYE的RTCP包即可;其实最重要的原因还是用SIP需要花额外的时间去读pjsip的代码,而鄙人向来收到的任务要求都是要快!总之,还是要听领导的吧,呵呵。。不过说到以后管理自己的团队时,我的想法还是要认真衡量,仔细调研的,一旦确定了,就要深入的研究自己选择的东西,仔细设计一下,以便于融入到团队的基础库中,避免以后重造轮子。
一边照着pjsua这个示例,一边参考着官方文档,搞了3天左右,自发自收可以了。但放到2台机器上试的时候,发现videodev中的dshow模块写的有问题,会失败。于是又开始硬着头皮研究dshow,哦天啊,Windows。pjsip调用的都是dshow的c接口,但是我查到的一些资料和示例都是C++写的,我也觉得既然是基于COM的东西,那就直接用C++呗,尤其是那代码写着多舒服,那CComPtr多好用,干嘛非得追求纯C实现的那个噱头,模块化嘛,还是尽量用最贴近实际操作系统的方式实现比较好。说到这,又想起了Google做的那个gyp,赞一个!
把dhsow模块自己重写了一遍,发现dshow其实不是那么恶心的,思路还是挺清晰的。这样双机通信就可以进行了,跟boss反馈了一下,就让测试人员介入了。反馈的第一个问题就是回音消除。因为我自己测试的时候只能台式机连笔记本,而台式机没有音响,所以根本就忘了加这个功能。还好pjmedia支持回音消除,选用的音频codec是speex,它本身也支持,所以查了下pjmedia的文档,在创建audio port的时候传入的option或上echo cancellation的选项就可以了,参数一律默认值。编译完了就发给了测试,试了下,效果还是有的,但有时候某一边还是会听到回音,只是不会像以前那样无限循环了。而且在连接刚建立的时候,如果有人说话,对方会听到特别刺耳的兹兹声。但是鉴于还有其他更重要的问题,而且说第二天可以拿有源MIC试下,音频就暂时先这样了。
视频的一个比较严重的问题是:连接上视频非常模糊,要等大约15秒刷新后才清晰。一开始我是完全没思路,想了一个土方法:掐着表算了下时间得出了15秒这个数值,然后在全工程里搜索15这个字符串,汗。。。(话说我挺喜欢全工程搜索这个功能的)结果是没有线索。得嘞,继续读pjmedia吧。就这样搞到了下午7点多,方才有了点发现。pjmedia的设计是这样的:一个video port会起一个线程,这个线程的流程就是从video dev取得原始数据,然后用codec编码,然后通过transport发出去;而打开dshow时我会注册一个回调函数,有视频数据进来时函数会被调用,所以这是另一个线程。所以这个问题就是一个多线程同步的问题:采集线程还没来得及将数据写入共享buffer,编码线程就从buffer取走数据去编码发送了,所以他取得的数据都是0xCC(搞Windows的应该都懂的吧),然后第一个关键帧就这样产生了,其后的预测帧都是在这张全粉色图像上算出来的,所以会花屏,直到第二个关键帧的诞生。这应该是一个bug,稍后会向pjsip项目组提交。
解决了这一个,又来了一个问题就是:刚连上时会马上刷一下屏,然后会变清晰。虽说不是大碍,但还是感觉不舒服,尤其是在测设组同学的敦促下,查吧。开始怀疑是丢包,但是局域网,不至于吧,抓了一下包,证明了不是丢包导致的(交换机:冤枉你了,呜呜)。然后加了些log,奇怪的发现还是丢包,因为发送方发出去的某seq的包没收到!呃,交换机,有wireshark作证,我不会怀疑你了,放心。。在翻wireshark抓到的包的时候,发现SDP的包有点问题:我想连的明明是192.168.0.119,但对方回给我的SDP中写的却是192.0.0.119。然后想起了我们公司好多人一台机器要设好几个IP。查了下测试同学的电脑,果然是这样。因此我猜测是不是因为最开始的时候发错了地址导致的丢包。虽然我不明白这样到底会不会导致丢,但是问题还得解决啊,于是只能把多余IP删了,每台机器只剩1个,再试!呃,还是原样。这时时间不早了,而且第二天就放假了,测试同学就走了,我继续查,查了1个小时,也没啥发现,就也回家了。
周末起床后,没事了,又想起了那个问题,继续。我又在pjlib的更底层加了些日志,顺便也拜读了下底层的代码,最后证明了:pjlib写的是没问题的,RTP包解析也是没问题的,问题出在防火墙上!比如A电脑开了防火墙,B没开,如果A在发数据之前就收到了B的数据,那么这时A的防火墙是不放行的,直到A从同一端口向B发数据为止,这就是Win 7防火墙的策略。这样的话,在公司的那台XP的电脑没这个问题也就得到解释了。赶紧把防火墙全部禁掉,问题随之而解决。
顺便也查了下程序中如何设置防火墙,嘿,资料还真不多,绝大多数都是教你鼠标操作的。终于找到一个,链接:http://msdn.microsoft.com/en-us/library/aa364726.aspx
另外的一个问题:长时间运行,某一方可能就会收不到视频数据了。之前的两次都是在公司跑出来的,晚上走之前开开,第二天去了一看就卡那了,远程附加上去瞅了一下,发现dshow的回调还是正常的,应该是编码线程卡住了,这时,测试同学把他的电脑网线拔了,然后换了个地方,然后就没然后了。今天在家跑了一会,也出来了,附加上去,发现是卡在ffmpeg的avcodec_encode_video2里了,我直接用的dev版的ffmpeg,所以只能到这,Google无果,只能明天再查了。
老婆明天要回家了,算了下,要2个月之后才能再见到,呃,写完这篇还是去陪陪老婆吧。。