2013年12月

由于要写一段iOS的代码,对obj C太不熟悉了,连《N小时精通XX语言》的教材都没看过。想用一个可读写的buffer的数据结构,但是找不到现成的实现。google并在stackoverflow寻找类似于javanio buffer或者PipiedInput/OutputStream的实现,没有收获。有人说使用NSMutableData的append来实现,但是觉得挺复杂而且内存难以回收。

自己最初采用pipe(pid_t[2])的方式, 不过这个情况下read去读fid,不能保证读取准确的buffer size。因此最终采用维护malloc一段"大"内存,然后用两个游标标记positon和end的办法,有填充时扩充end,有读取时移动position, 然后当position大于一定值时,把剩余数据memcpy到buffer的开头,position归零。 算是自己简单得实现了一个简陋的buffer, 事实上,使用起来很高效,因为我的数据本来也是C的bytes数组,所以不需要NSData的转换过程,直接可以从buffer读写。

结果很满意, 看来自己还是只会写C语言。。

(这篇开始采用markdown书写。)

    最近一个月,为了实现音频直播的功能,不断研究rtmp协议,以及flv和aac的封装编码的实现,俨然变身成了音频处理达人了。

    首先,服务器的选择上,FMS和AS5之类的直接就没有考虑了, 试用了crtmpserver以后发现完全算是实验产品, 没法用于生产, 于是转向了nginx-rtmp-module, 这个算很顺利。

    然后开始了漫长的客户端实现的经历。android我用了比较hack的办法,用processbuilder另开一个ffmpeg的进程,用进程间pipe的方式实现了,这个是最简单的,而且兼容性好,唯一的难点就在于ndk编译ffmpeg上,调整好configure参数,只打开需要用到的 de/muxer,de/encoder, protocol, 最终编译出了一个小于1M的可执行文件。其中有2个坑, 一个是在部分很搓的中信手机上无法执行,还有一个是aac参数需要加 -strict -2, 幸好都完美解决了。

    而写iOS代码就是一个悲剧的历程了。在ios平台不能fork进程,只能引入静态库文件.a,然后写C的code。所以第一个问题就是交叉编译出i386, armv7, armv7s的.a文件,然后lipo合到一块,这个折腾了不少时间,坑不少。然后第二个问题,因为ffmpeg在1.x以后,decode aac默认输出的是fltp,不是s16le, 因此需要从float转成2个channel的s16。播放rtmp搞定以后,开始折腾publish,由于专利原因 ffmpeg不带aac encoder的功能,需要自己编译libvo_aacenc/libaacplus/libfaac的库文件,然后再打包, 因此又重新折腾了一遍交叉编译,品尝了酸甜苦辣。打包aac的时候,又出现了adts的问题,于是加上aac_adtstoasc的filter,重新打包,在代码里面启用以后,总算能够让vlc和ffplay播放了。

    但是不完美,因为音频的pts有错, 这个花了很久的时间,google了各种代码终于用合适的方式解掉。接着,开始着手解决网页端flash player不能播放(播放没有声音)的bug,这个是一个大坑,因为没有任何错误提示和可以debug的地方,只能各种猜测错误的方向。后来实在没办法以后,决定用tcpdump抓包,对比和正常rtmp发布端的hex字节, 这样下来,已经对rtmp的协议实现了解到每个字节段的程度了。。。终于,发现了一个rtmp包区别, 简单说就是正常的rtmp是 af 00 12 10, 我的C代码是af 00, 不含有12 10这个AudioSpecificConfig, 为了让我的代码能够加入这2个字节,想了N种方法,最后尝试给ffmpeg加个patch,在 libavformat/flvenc.c 加入了一行,

 avio_wb16(pb, 0x1210 ); 

    测试OK,终于搞定。后来尝试了不用patch的路子,在AVFormatContext的extradata上做文章,填入了AudioSpecificConfig的字节,算是完美解决。

     其实整个过程还有各种坑存在,只是不断被解决。因为研究这个rtmp和ffmpeg的人不多,至少网上的“正确”的代码太少,都是各种抄文章和ffmpeg< 0.8的老代码。所以算是花了很大心思在研究上面, 以及自己写test代码不断尝试。幸好结局很满意,算是实现了ios+android+flash 的直播功能, 就等大量上用户等着并发压力的时候了。