iOS上 H265+G.711A/AAC录像的坑

前段时间做公司的摄像头项目,视频用的是h265,音频原先用的是G.711A,后面改成了AAC。这里的录像功能指的是把用户直播看到的内容录制到系统相册里面,实现方式是通过ffmpeg把视频流数据加上一定的配置(相当于写了文件头)写入到iOS的相册。由于iOS相册的读取条件不透明,经常会有些不兼容的情况,又没有任何提示,因此遇到了不少的坑,这里记下来方便后来人避坑。

一、录像无法保存到相册

问题表现:录下来的视频用ffplay、VLC这些都能播放,但是无法保存到iOS相册,用quicktime也提示不兼容。 通过ffplay打印的信息看不出任何问题,视频内容也是正常的。
根本原因:视频编码器的extraData没有赋值,导致头部信息不对,苹果相册无法识别。
解决过程:一开始我猜测是文件头写的不对,我是用mp4作为容器来封装音视频数据的,因此专门去查看了mp4的格式说明(不熟悉的朋友可以看看这个文章,写的挺详细https://zhuanlan.zhihu.com/p/543684404?utm_id=0),对比文件头的内容,发现并没有什么问题。
我在网上搜了下iOS相册兼容的问题,发现有人提到iOS貌似对Annex-B支持的不太好,而需要用hvcc, 而嵌入式这边给过来的视频是Annex-B格式的(头部带有SPS/PPS/VPS等信息,用0x000001分开),那会不会是iOS相册不支持Annex-B视频的录制呢?我去社区里问了一圈,没有得到肯定的答复,然后又尝试用ffmpeg把视频帧从Annex-B 转为hvcc,这个过程中还发现了filter这种东西,虽然最后没用上,但也算是增长了知识。ffmpeg 是有annexTohvcc的代码的,但是没有在头文件公开出来,因此还需要拷贝源文件,修改方法名重新编译才能使用。费了大半天的工夫弄好之后,发现还是不行,转换后的视频调用write_frame方法的时候就报错了,看来这条路是走不通了。

于是我又跟嵌入式讨论,嵌入式那边也做了录像,他们录制的视频文件是可以用quicktime播放的,而且也是用ffmpeg写的,因此我跟他们对比了一下代码,发现内容也差不多,没有看出明显的区别,然后我又打开HexFriend对视频的内容进行对比。发现嵌入式那边录制的视频文件基本上每隔一段内容都会有类似0x000001....... 的内容,大概有20多个字节是重复的吧,而我这边是没有这么长的重复字节的,我就怀疑这是他们在每一帧前面加了些内容。 我拿着这个再问去他们,他们说这些就是SPS/PPS/VPS的内容,如果没有这个视频播放不了,发给我的视频数据里面也有这个内容,但是同样的内容,为什么客户端写进去就无法兼容iOS相册呢?然后我再仔细对比了一下代码,发现他们多了一段逻辑,是对avcodecContext的extraData 进行赋值,而我这边却没有,他们说这个就是SPS/PPS/VPS的内容,那会不会是extraData没赋值导致的问题呢?
带着疑问,我尝试从视频头部截取这部分内容,根据已知的规则,SPS/PPS/VPS每一段都是用0x000001来开头的,因此我借鉴了一下ffmpeg里面读取hevc视频头的源码,做了一些改动

int findHevcExtraDataEnd (const uint8_t *buf, int buf_size){
    int64_t state64 = -1;
    for (int i = 0; i < buf_size; i++) {
        int nut;
        
        state64 = (state64 << 8) | buf[i];
        
        if (((state64 >> 3 * 8) & 0xFFFFFF) != START_CODE)
            continue;
        
        nut = (state64 >> (2 * 8 + 1)) & 0x3F;
        if (nut == NAL_IDR_N_LP || nut == NAL_IDR_W_RADL ) {
            return i-6;
        }
    }
    return -1;
}

通过findHevcExtraDataEnd 这个方法可以从h265的数据里面抽取出SPS/PPS/VPS。然后再把这个填充到extraData里面,果然写到相册就不报错了。视频也能播放了,只是还有些其他的问题。

二、视频前面一小段是黑的

问题表现:视频前面一小段是黑的,黑的时间不规律,有时长达几秒,有时很短,但是有声音
根本原因:录制的时候没有从关键帧开始录,导致前面的P帧、B帧都解析不了。
解决过程: 这个问题其实相对比较好解,分析了一小段视频之后,就找到问题所在了。解决方案就是在录制前做一下判断,是否有拿到I帧,如果没有,则不进行录像。

三、没有声音(G.711A)

问题表现: 相册里面只有视频,无法播放声音,在ffplay和VLC上也无法播放。此时音频用的是g.711A格式,容器格式是mp4
根本原因:ffmpeg不支持将g.711A封装到mp4,需要改源码或者将文件扩展名改为.mov
解决过程:这个问题发现的时候,我第一时间就在google上搜索,得到了一些线索,因此解决起来不算麻烦,不过网上是h264+g.711a时出现问题,但本质上都一样。

四、声音只有前半段

问题表现: 只有前半段有声音,后半段没有。
根本原因: 音视频都写到一个流里了。
解决过程: 这个问题的解决过程还是走了一点弯路,一开始没有仔细去看代码,而是从音频的参数和PTS着手,怀疑是参数或者PTS设置的不对。因为PTS是控制播放速度的,所以一开始就是调整音频包的PTS,缩小到原来的一半,发现问题没有得到任何改善。 然后又去修改每帧的比特数和声道数。 发现比特数改了没有什么效果,把单声道改为双声道之后倒是能时间正常了,但是声音却失真了,杂音也变得更大。这是什么原因呢? 我用google 搜索、问chatGPT都没有得到满意的结果,于是就想会不会是数据写的有问题。于是又打开HexFriend,对比嵌入式那边录制的正常的音频和我录制的音频,发现有问题的音频的stsz单数sample 有值,双数为0,而正常的stsz 是每个sample都有值,而且看sample数量也发现有问题的音频sample数比正常的少了差不多一半。这是不是说明只录了一半的数据呢?带着这个疑惑我又去重新看我写的代码,才发现原本是分开应该是视频包写到视频流,音频包写到音频流的代码,变成了视频和音频都写到了音频流里。这才导致音频只有一半的长度。

========================================================
原本录像的问题到这里就差不都结束了,但后面音频改为了AAC,又引发了新的问题

五、没有声音(AAC)

问题表现: 相册录进去的视频没有声音,但是同样的文件用ffplay 和VLC能正常播放。
根本原因:音频数据没有加ADTS头
解决过程: 由于云端上传的限制,我们更换了音频格式到AAC,但嵌入式那边麦克风只支持8K和16K采样率,于是我们又换了编码器(AAC-FDK)。因为改动比较大,一开始我是怀疑音频的参数设置的不对或者是编码器问题。我对比了嵌入式那边的设置,改了几个参数之后发现没效果,又怀疑是苹果不支持16K的采样率。 于是我先用ffmpeg解复用,再用苹果的audioConverter来解码aac,发现解码的音频是能正常播放的,因此断定问题出在封装上,而不是音频本身。
有前面的经验作为参考,我觉得extraData 应该是有问题。我问嵌入式那边是否有设置extraData,他们说没有,于是我找他们拿了一段视频对比,看了下mp4 头的box, 没有找到明显差异。于是我又去对比音频内容,发现写进去的音频内容都有fff1开头,而且extraData是2个字节,跟正常的adts头部长度不一致(7个字节),而他们录进去的视频没有fff1,我怀疑是头部少了些内容。嵌入式那边跟我说这个不需要adts头也能播,我看了下他们确实没加头,这就很奇怪了。 我让他们把adts头加在音频前面看看,但他们说传给云端的音频不能加adts,否则传不上去。于是我只能再去对比代码,但反复对比仍然找不到问题所在,这时他们跟我说之前发给我的视频是另一个摄像头录的,跟我连的摄像头不是同一个,不排除是摄像头的问题。然后我让他们重新在我连的摄像头上烧录程序,重新录制,发现居然录出来的视频也有问题,然后我就怀疑是摄像头本身有问题了。
到这里我以为问题基本确定了,但后面他们查到一个规律,发现没有adts头,有一定的概率音频写进去播放不了,但加了adts头,就一定能播放。 但他们碍于云端限制又不能加头,于是我只能想办法自己加上去了。 音频跟视频不同,视频每一个I帧的头部都是一样的,而adts头部跟文件长度有关,之前的g.711A每一帧长度是固定的,但AAC每一帧长度不固定,所以没法直接从数据头部截取,只能自己拼接了。还好这个代码网上有现成的,直接抄过来就行。加上了ADTS头后我再试了试,还是不行,这又是怎么回事???
我只能再检查extraData ,发现一个很奇怪的现象,就是刚创建avcodeContext的时候,extraData 是空的,但录了一帧之后,发现extraData变成2个字节了,明显不是正常的,但是这是从哪来的呢?我一时半会找不到头绪,于是只能尝试在写入包之前清掉extraData,发现这样也还是有问题, 又尝试在avcodec_parameters_to_context 之前清除extraData,这下总算是正常了。

在这个问题的解决过程中,我还尝试了使用AVAssetWriter和AVAssetExporter来封装,发现都有这样那样的问题,要不就时间戳设置的不对,导致白屏,要不就导出失败,最后发现还是ffmpeg好用。

总结:

ffmpeg在音视频处理方面还是很强大的,虽然有些瑕疵,像不支持g.711a这样的音频写到mp4,然后extraData有些莫名其秒的数据之类的,但总体上还是比较好用,设计的也很合理,源码也有不少直接学习参考。对于新手来说,学习ffmpeg可以得到很多收获。 而苹果自带的框架也很强大,但是不开源,错误提示也不够清晰,对于新手不够友好。最关键的是,ffmpeg是跨平台的,代码可以多端使用,因此还是可以投入大量时间去学习研究的。作为端上的开发同学来说,两者都得会,特别是对于系统相册的特性,平时也要多一些了解,积累这方面的经验,否则出了还真是难以排查。

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

推荐阅读更多精彩内容