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

一、录像无法保存到相册

问题体现:录下来的视频用ffplay、VLC这些都能播映,可是无法保存到iOS相册,用quicktime也提示不兼容。 经过ffplay打印的信息看不出任何问题,视频内容也是正常的。
根本原因:视频编码器的extraData没有赋值,导致头部信息不对,苹果相册无法识别。
处理进程:一开始我猜想是文件头写的不对,我是用mp4作为容器来封装音视频数据的,因而专门去查看了mp4的格局阐明(不熟悉的朋友能够看看这个文章,写的挺具体zhuanlan.zhihu.com/p/543684404…),比照文件头的内容,发现并没有什么问题。
我在网上搜了下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是跨渠道的,代码能够多端运用,因而仍是能够投入大量时间去学习研究的。作为端上的开发同学来说,两者都得会,特别是对于系统相册的特性,平时也要多一些了解,积累这方面的经历,不然出了还真是难以排查。