行车记录仪 - 录像 - 文件缓存

背景

  • 基于ffmpeg实现录像功能,性能不理想,前后路摄像头视频码率相加只有28Mbps加上音频也只有4MB/s左右,使用class 10的sd卡 + 2秒 ringbuffer缓存的情况下,依然出现写卡不及时导致的丢帧现象,class 4 sd卡表现更差。

分析

  • 使用dd命令测试class 10 SD卡在该平台上的读写速度,写速度能达到7~8MB/s,因此丢帧问题不是SD卡写性能不足导致的,是录像逻辑的问题。
  • 通过测试定位到ffmpeg接口av_write_frame(写一帧数据)耗时异常,异常部分log如下:
01-01 00:01:13.064   139   310 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE/2022_01_01_000059_00.MOV]: av_write_frame take [19] ms
01-01 00:01:13.067   139   313 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/2022_01_01_000059_00_b.MOV]: av_write_frame take [23] ms
01-01 00:01:13.170   139   313 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/2022_01_01_000059_00_b.MOV]: av_write_frame take [102] ms
01-01 00:01:13.185   139   310 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE/2022_01_01_000059_00.MOV]: av_write_frame take [119] ms
01-01 00:01:13.187   139   313 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/2022_01_01_000059_00_b.MOV]: av_write_frame take [16] ms
01-01 00:01:13.190   139   310 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE/2022_01_01_000059_00.MOV]: av_write_frame take [4] ms
01-01 00:01:13.194   139   313 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/2022_01_01_000059_00_b.MOV]: av_write_frame take [6] ms
....
01-01 00:18:57.202   139   479 I write_packet@CviMuxer.cpp:577 [/mnt/sd/CARDV/MOVIE_b/2022_01_01_001833_00_b.MOV]: av_write_frame take [2249] ms
  • 异常现象总结
  1. av_write_frame耗时不稳定,普遍耗时几毫秒,和预期缓存未满时写缓存(内存拷贝)耗时较少不符。
  2. 间隔性出现耗时100ms以上,时常出现200、300,甚至500ms以上,偶尔出现耗时1s ~ 2s,一帧I帧视频数据才100KB~200KB,耗时完全不符合预期。

ffmpeg 实现

  • ffmpeg录像写文件操作的代码(libavformat/file.c)是调用的open,write,read,seek等系统接口,使用open接口打开文件是没有应用层缓存,因此需要ffmpeg自己实现缓存机制,在file_open中可以设置URLContext 的min_packet_size和max_packet_size来定义缓存大小,代码(libavformat/aviobuf.c)会根据定义的缓存大小申请内存创建缓存,av_write_frame时会先将数据写入该缓存,缓存满了或者主动调用avio_flush接口会调用write接口将缓存数据传递给linux内核。

文件缓存

  • 定义一定大小的文件缓存可以减少系统接口write的调用频率,以及内存拷贝的次数,以提高性能。
  1. 缓存大小需要是内存页,文件系统块(Block),文件系统IO操作块(IO Block)大小的整倍数,以避免跨页操作
  • 系统读写SD卡都是以块为单位,而不是一个字节一个字节的读写,因此缓存大小如果出现不满一块的余量,系统也要将一整块写入sd卡,并且为了保证存储数据的正确性,系统需要先从sd卡读取出该块中缓存未覆盖到的数据,补充到写入数据中,浪费了一定的性能。
  • 不同文件系统或者同一个文件系统,不同SD卡的IO Block也可能不同,需要注意:
[root]/mnt/usb# stat xxx 
  File: xxx
  Size: 49        	Blocks: 128        IO Block: 65536  regular file
...
[root]/mnt/usb# stat b.txt 
  File: b.txt
  Size: 6         	Blocks: 64         IO Block: 32768  regular file
...
# stat testfile              
  File: `testfile'
  Size: 102             Blocks: 8          IO Block: 4096   regular file
...
  1. 缓存大小需要根据实际情况进行调整
  • 同步IO情况下,缓存大小也会造成一定的性能影响,需要参考数据量,写文件间隔等因素,例如:视频25fps,写文件间隔40ms,缓存的数据最好保证在40ms内传递给linux内核,耗时多了会阻塞下一帧,一些嵌入式平台会影响帧率,耗时少了会增加write的调用次数。

异常原因

  • 现有代码设置了的ffmpeg缓存大小为32KB,理论上不应该出现问题。
  • 分析自有代码和ffmpeg源码可知上面的异常现象中的间隔性耗时过长应该是缓存满了调用write导致的,av_write_frame普遍耗时几毫秒以及偶发性耗时过长是录像逻辑破坏了缓存机制出现。
  • 缓存机制被破坏
  1. 为满足其它功能,我们修改了ffmpeg源码,在一些条件下会做seek和write操作,这种情况下就会导致缓存机制被破坏。
  2. MOV,MP4文件有固定的头部数据 ftyp 和wide box 再加上mdat box 的HEAD一共36个字节,ffmpeg写完这部分数据后会主动调用avio_flush 先将这部分数据写入SD卡,后续再按缓存大小写数据,虽然写的数据大小是满足块的整倍数,但是已经发生错位和跨页。
源码:libavformat/movenc.c
@@ -6744,7 +6744,7 @@ static int mov_write_header(AVFormatContext *s)
         }
     }
    ....
    avio_flush(pb); //需要注释掉
 
     if (mov->flags & FF_MOV_FLAG_ISML)
         mov_write_isml_manifest(pb, mov, s);
    ...    

修复结果

  • 修复完后测试结果
01-01 08:03:54.641   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [4] ms
01-01 08:03:54.641   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.641   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.641   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.641   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.642   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.642   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.642   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.644   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [1] ms
01-01 08:03:54.644   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.644   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.644   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
01-01 08:03:54.644   139   329 I write_packet@CviMuxer.cpp:575 [/mnt/sd/CARDV/MOVIE_b/1970_01_01_080317_00_b.MOV]: av_write_frame take [0] ms
  • 大部分情况下av_write_frame耗时1ms以下,缓存满时耗时会稍微多点,最高时也不超过20ms。
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值