通过解决问题深入学习 avi 的文件格式(RIFF)以及相关编码(MJPG)方式,结论就是定位问题一整天,修复问题五分钟。

起因

据说最近有人在用 AVI 录像的结果作为学习的样本的跟踪,这个很有意思喔。

大概是昨天(2020年5月11日)的事情了,然后看了一天的 AVI 格式的构成和编码,了解了一下历史,在这里做一下记录。

解决问题不是重点,重点是如何分析问题和掌握知识。

如果很在意解决问题的方案的话,如下截图就是了。

提交记录

现在进入正题吧,修复问题确实只用了不到 5 分钟,那其他时间都用来做什么了呢?

没错,时间都浪费在就现有实例定位问题和来深入理解了编码与解码的处理逻辑和过程。

AVI 格式?RIFF 格式?MJPG 编码?这都是些什么东西呢?

这才是我写这篇博客的主要目的,留下一条供他人跟着学习的路线,那么进入正题吧。

RIFF 是什么?

RIFF全称为资源互换文件格式(ResourcesInterchange FileFormat),RIFF文件是windows环境下大部分多媒体文件遵循的一种文件结构,RIFF文件所包含的数据类型由该文件的扩展名来标识,能以RIFF文件存储的数据包括:音频视频交错格式数据(.AVI) 波形格式数据(.WAV) 位图格式数据(.RDI) MIDI格式数据(.RMI)调色板格式(.PAL)多媒体电影(.RMN)动画光标(.ANI)其它RIFF文件(.BND)

它是早期 巨硬 和 IBM 爸爸共同协商的一种资源交换结构,文件表现为 header + data 组织,分别是 定义数据读取的头 、 对应的数据 ,在 视频流中会额外定义 索引 属于数据的一种,有趣的是它会在每次覆写数据后可以重建帧索引。

MJPG 是什么?

关键词 Motion-JPEG2000 ,想找个规范的介绍说明都没有,只能借鉴一下英文资料了。

The Joint Photographic Experts Group (JPEG) standard [1,2,3] defines
a family of compression algorithms for continuous-tone, still images.
This still image compression standard can be applied to video by
compressing each frame of video as an independent still image and
transmitting them in series. Video coded in this fashion is often
called Motion-JPEG.

简单来说我们把这种每帧属于 JPEG 图片的视频流称为 MJPG 编码,以 JPEG 图片为帧依次写入视频中。

AVI 是什么?

AVI是音频视频交错(Audio Video Interleaved)的英文缩写,它是Microsoft公司开发的一种符合RIFF文件规范的数字音频与视频文件格式,原先用于Microsoft Video Windows(简称VFW)环境,现在已被Windows95/98,OS/2等多数操作系统直接支持。AVI格式允许视频和音频交错在一起同步播放,支持256色和RLE压缩,但AVI文件并未限定压缩标准,因此,AVI文件格式只是作为控制界面上的标准,不具有兼容性,用不同压缩算法生成的AVI文件,必须使用相应的解压缩算法才能播放出来,常用的AVI播放驱动程序,主要是Microsoft Video for Windows或Windows98/98中的Video1,以及Intel公司的Indeo Video.

在介绍AVI文件前,我们要先来看看它的RIFF文件结构。AVI文件采用的是RIFF文件结构方式,RIFF(Resource Interchange File Format,资源互换文件格式)是微软公司定义的一种用户管理Windows环境中多媒体数据的文件格式,波形音频wave,MIDI和数字视频AVI都采用这种格式存储。RIFF文件的实际数据中,使用了列表(List)和块(Chunk)的形式来组织。列表可以嵌套列表和块。整个RIFF文件可以看成一个数据库,其数据块ID为RIFF ,称为RIFF块。一个RIFF文件中只允许存在一个RIFF块。RIFF块中包含一系列的子块,其中有一种子块的ID为“List”,称为LIST块,LIST块中可以再包含一系列的子块,但除了LIST块外的其他所有的子块都不能再包含子块。
RIFF和LIST块分别比普通的数据块多了一个被称为形式类型(Form Type)或者列表类型(List Type)的数据域。

说白了,基于上述两者的组合,形成的文件就是 AVI 视频文件辣。

那么现在放到一起要如何理解呢?

首先我们要有 RIFF 的基础概念,知道它是用来定义文件的编码数据的,我们把数据编码编码成 RIFF 解码器可以识别的数据,围绕这个流程去实现我们对应的功能。

所以这里解码器就是一个黑盒,是需要我们去对应实现的,而它的实现规则就是符合 RIFF 资源交换的规则。

理解 AVI 之前可以先理解 WAV 文件,它也是 RIFF 的文件格式,可以自己私底下看看这篇文章 wav文件格式分析与详解,我从中借一张图出来。

实际上它在代码中是这样对应的。


typedef struct
{
	uint32_t  riff_id;  /* {'r', 'i', 'f', 'f'}  */
	uint32_t  file_size;
	uint32_t  wave_id;
} file_chunk_t __attribute__((aligned(8)));

typedef struct
{
  uint32_t fmt_ID;  /* {'f', 'm', 't', ' '} */
  uint32_t chunk_size;
  uint16_t format_tag;
  uint16_t numchannels;
  uint32_t samplerate;
  uint32_t byterate;
  uint16_t blockalign;
  uint16_t bitspersample;
  /* Note: there may be additional fields here, 
     depending upon wFormatTag. */
} format_chunk_t;

typedef struct
{
  uint32_t data_ID;  /* {'d', 'a', 't', 'a'}  */
  uint32_t chunk_size;
} data_chunk_t;

注意到很多资料都没有和具体的代码结合来讲,所以我这里先以 WAV 为例帮助理解,再拿 AVI 的编码过程的代码为例。

实际上我们只需要把上述定义的结构体写入文件中就完成了 header 的设定,这里还不涉及到大小端的问题,出来的数据就和实例分析的一样,写入的代码如下所示。


//write head data
vfs_internal_seek(audio->fp,0,VFS_SEEK_SET,&err_code);//
wav_encode_t* wav_encode = audio->record_obj;
wav_encode->file.file_size = 44 - 8 + wav_encode->data.chunk_size;
vfs_internal_write(audio->fp, &wav_encode->file, 12, &err_code);//write file chunk

// 省略,注意 format_chunk_t 的 wav_fmt

format_chunk_t* wav_fmt = &wav_encode->format;

wav_fmt->numchannels = 2;//always is 2,because i2s_play only play 2-channels audoi
wav_fmt->blockalign = wav_fmt->numchannels * (wav_fmt->bitspersample/8);// channel * (bit_per_second / 8)
wav_fmt->byterate = wav_fmt->samplerate * wav_fmt->blockalign; // samplerate * blockalign

vfs_internal_write(audio->fp, &wav_encode->format, 24, &err_code);//write fromate chunk

// 省略 直接写入 wav_encode->format 也就是 wav_fmt 结构体变量。

vfs_internal_write(audio->fp, &wav_encode->data, 8 ,&err_code);//write data chunk

// 省略

实际上就是这么简单,而这里需要注意的是,数据块(chunk)的写入是任何时候都可以继续的,而数据是有前者 format_chunk_t 所指定的数据协议,它可能是其他类型的数据,知道了这些后,不妨直接去看我发是 wav文件格式分析与详解 的第 6 章的实例分析。

现在你有了这个基础,那就可以来学习一下 AVI 的编码格式拉。

来我们继续说, AVI 和 WAV 采用的都是 RIFF 结构来存储定义,但 AVI 的 LIST 要复杂的多,也就是 WAV 中的 format_chunk_t 定义要变成符合的 LIST 数据定义,只要你理解前面的基础了,后来的也就是更复杂的定义而已,并不难理解。

看这个图整理的很好。

我们需要知道 avi 文件先定义了 RIFF 格式,记录了文件大小的基本信息,接着定义 Header 列表(LIST),这个列表会包含 avi 视频的基础参数,如下结构。


typedef struct
{	
	uint32_t block_id;
	uint32_t block_size;
	uint32_t usec_per_frame;
	uint32_t max_byte_sec;
	uint32_t padding_franularity;
	uint32_t flags;
	uint32_t total_frame;
	uint32_t init_frames;
	uint32_t streams;
	uint32_t ref_buf_size;
	uint32_t width;
	uint32_t height;
	uint32_t reserved[4];
}avih_header_t;

定义了这个结构后,还会接着继续定义更多的 LIST 结构,如视频数据格式、音频数据格式。

其实你知道了这些基础概念后,就可以结合代码来看了,代码的实现方式和上述的 WAV 的是一致的,不一样的地方在于定义的数据内容更多了。

我这里额外推荐一份代码供理解 mjpeg-avi , 也有对应的示例视频供你理解文件的 HEX 数据。

https://github.com/Ricardicus/mjpeg-avi/blob/9110ae847947c67799aeb0d851e96060dceafe69/src/avi.c#L152

以上就是理解 AVI 文件的格式基础,但注意每个接口的定义和格式,还是需要自己去好好辨识的,在这里我们就不拎出来讲解拉,具体问题具体分析吧。

例如当我学完这些基础的知识后,我就看到了 MaixPy 在输出 avi 中定义了数据,但实际上并没有对应写入对应的数据块,那作为黑盒子的播放器当然是读取失败拉,当然也有些播放器也可以解开,如 VLC 播放器。

既然我们不能改变播放器的代码,那就只能改变我们编码文件的过程符合规范,就这么简单。

最后附上一个别人写好的 AVI 的文件编码实例分析,AVI文件详细解析

现在,聪明的你,知道怎么理解了吗?

另外 VLC 播发器是开源的喔,所以你懂的 vlc/download-sources

2020年5月14日 留。

原文地址:https://www.cnblogs.com/juwan/p/12870786.html