为QCM-08DL行车记录仪开发264至AVI视频转换器

实际上,本文致力于开发一种程序,用于将DVR视频从一个容器重新包装到另一个容器(如果可以将其称为转换的话)。 虽然,我一生都认为转换器从事视频格式的转换(转码)。 本文是我上一期出版物的第二部分,在此我详细介绍了如何访问DVR的所有录像。 但是,在该出版物的开头,我设定了另一个任务:研究标准264-avi repacker程序的工作算法,并创建可以执行相同操作的相同程序,但是该程序不能对一个文件执行,而是对整个文件组执行。 “一键式”。

我将再次用简单的语言解释所有事物的本质。

用户有一个录像机,例如,流行的型号QCM-08DL。 他需要一个特定日期和时间的视频。 他可以通过USB闪存驱动器或通过DVR的基于Web的界面将其删除到计算机。 提取的视频文件(扩展名.264)将仅在DVR附带的播放器程序中打开。 播放器非常不舒服。 您仍然可以通过在多路分解设置(高级用户设置)中设置RAW H264模式在VLC播放器中将其打开。 但与此同时,显然,音频流块(被解释为视频,没有声音)会干扰正常播放。 为了在任何播放器中打开视频,必须首先将.264文件转换为某些流行的格式,例如avi。 DVR还包含一个转换程序。 但是她也很不舒服。 涉及一个或多个文件时,没有问题。 但是,当任务是访问硬盘驱动器上的所有视频,甚至将其全部转换为流行格式时,标准工具实际上不适合。



访问所有文件的问题已解决。 这是上一期出版物的主题。 我们着手解决第二个问题。 我已经得到“实用建议”:将文件名的扩展名从“ 264”重命名为“ avi”就足够了,他们说,一切都会出错,他们说,没有任何麻烦。 但这是普通用户最普遍的错误,通常,他们不了解相关问题。

在以前的出版物中,我已经简要地撰写了有关.264源文件的结构。 让我提醒你。

音频和视频流的主要信息起源于65,536字节的偏移量。 视频流块以8字节的标头“ 01dcH264”(也称为“ 00dcH264”)开头。 以下4个字节以字节为单位描述视频流当前块的大小。 在4个字节的零(00 00 00 00)之后,视频流块本身开始。 音频流块的标题为“ 03wb”(尽管根据我的观察,在某些情况下,标题的第一个字符可选为“ 0”)。 之后-我尚未弄清12字节的信息。 从第17个字节开始-固定长度为160个字节的音频流。 文件末尾没有标签。

我将对以上内容发表评论。 直到65,536字节的偏移量的所有内容都尚未解决和不必要。 从65,536字节的偏移量到流的第一个标头,有一个小的间隙,其内容也无法解析,而且,正如我所检查的那样,常规程序转换后它不会出现在输出avi文件中。

视频流的每个块代表一帧。 视频流的块标题中的第一个字符可选为“ 0”。 我没有弄清楚他的目的,因为,正如我发现的那样,这不是解决任务的关键。 视频流标题的第二个字符可以是“ 1”或“ 0”。 在第二种情况下,视频流块的内容是所谓的参考帧。 并且在第一种情况下,视频流块的内容是取决于参考帧的编码的压缩帧。 参考帧的内容的大小显着大于压缩子帧的内容的大小。 参考帧重复周期最有可能取决于DVR中的压缩率设置。 但就我而言,重复周期是1帧/秒。


将视频从“ 264”容器重新包装到“ avi”容器的常规程序在帧频方面给出了不同的结果。 对于以高分辨率模式(704 * 576)录制的视频,帧速率为20帧/秒。 并且在低分辨率(352 * 288)的情况下-25帧/秒。 该信息由MediaInfo实用程序提供,并且还指出视频大小在任何情况下都是相同的:720 * 576,视频流(此实用程序报告)的大小为704 * 576或352 * 288。 大多数播放器是专门为视频流的大小而部署的。 但是,我遇到了一个播放器,该播放器在播放352 * 288文件时错误地显示了半屏模式。 我想通过查看视频流内容的字节并从此处提取有关帧大小的信息来修复全职重新打包程序中的此小缺陷。 但是我急着不能做。 下图显示了以上内容。



现在介绍一下帧速率。 正如我发现的那样,常规的重新打包程序不会访问“ 264”容器的任何标头字段。 它通过计算视频块和音频流的数量之比来判断帧速率。 从上图可以看出,计算中的这个值甚至没有四舍五入到最接近的整数(绿色圆圈)。 正如我发现的那样,每个时间单位的音频流块的数量始终无处不在(在任何文件中),即每秒25个块。 如果您以20帧/秒的频率检查视频文件,则参考帧(块)每19压缩帧出现一次,在25帧/秒的情况下出现。 -每24个压缩帧。

我们继续研究视频流头的结构。 我们计算出前八个字节:这是参考帧或压缩帧的标签,加上关键字“ H264”。 据我发现,以下四个字节描述的不是视频流内容的确切大小,而是近似大小。 常规的重新打包程序会完全抛出此内容的所有字节,然后将结果大小写入avi容器的相应字段中。 并且此值与源.264文件的相应字段中指定的值不同。

在某种程度上,我猜测音频流块的标题之后有十二个字节的信息。 无论如何,关键元素是最后4个字节,之后音频流开始。 这是两个16位数字,描述了从ADPCM到PCM的迭代解码方案的初始参数。 解码将音频流的大小增加4倍。 事先检查文件时,我发现专职重新打包程序可以解码音频,但视频内容保持不变。

没有很深的知识,我试了很长时间才弄清楚我的情况下使用了哪种解码算法。 凭直觉已经猜到了采用了压缩方法ADPCM。 更准确地说,不是直观的,而是采用一种有效的方法,基于音频流被精确压缩4倍的事实。 当我在Adobe Audition中以各种格式将片段作为RAW打开(并在与常规程序重新打包后比较相同的片段)时,ADPCM给了我一个非常相似(但不准确)的声音结果。 为了分析压缩算法,网站wiki.multimedia.cx/index.php/IMA_ADPCM上的信息对我有所帮助。 在这里,我了解了两个初始解码参数,然后使用“戳方法”,我意识到这些初始参数在音频流开始之前以4个字节记录。 我将描述算法的操作并给出一个粗略的数学解释(在剧透下)。

ADPCM解码算法详细信息
有一系列样本 x 0 x 1 x 2 \点 此外,如前所述,有两个初始参数 ÿ 0s 0 。 需要获取新的样本序列 y 0 y 1 y 2 \点 。 您已经猜到了,第一个输出样本是已知的:它与一个初始参数一致 ÿ 0 。 这不是“初始位移”。 值得注意的是,输入(源)样本被编码为四个比特。 对于有符号类型,从-8到7(包括8和7)之间的整数属于编码范围。 实际上,最高有效位是造成数字符号的原因。 解码后获得的输出PCM样本具有带符号的16位标准格式。

分析C语言中的算法代码,您可以看到两个表。 它们在下面列出。

int ima_index_table[] = { -1, -1, -1, -1, 2, 4, 6, 8 }; 


 int ima_step_table[] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; 


可以说,这两个“魔术”数组是表格函数,在其参数中替换了相同的两个初始参数。 在迭代过程中,每一步都将重新计算参数,并将其再次代入这些表中。 首先,让我们看看如何在代码中实现它。

我们声明必要的,包括辅助变量。

 int current1; int step; int stepindex1; int diff; int current; int stepindex; int value; //  ; 


在开始迭代之前,您需要将初始参数分配给当前变量 ÿ 0 ,而变量stepindex为 s 0 。 这是在相关算法之外完成的,因此我不会在代码中反映出来。 以下是循环(循环)执行的转换。

  value = read(input_sample); //   ; current1 = current; stepindex1 = stepindex; step = ima_step_table[stepindex1]; diff = step>>3; if(value & 1){ diff += step >> 2; } if(value & 2){ diff += step >> 1; } if(value & 4){ diff += step; } if(value & 8){ current1 -= diff; if(current1 < -32768){ //,  ""; current1 = -32768; } }else{ current1 += diff; if(current1 > 32767){ //,  ""; current1 = 32767; } } //      :   current1; stepindex1 += ima_index_table[value & 7]; if(stepindex1 < 0){ // ""; stepindex1 = 0; } if(stepindex1 > 88){ // ""; stepindex1 = 88; } output_sample = curent1; //   ; current = current1; stepindex = stepindex1; 


在ima_step_table数组的辅助变量step中,写入索引stepindex1的值。 对于第一次迭代,这是初始参数 s 0 ,对于进一步的迭代,这是一个重新计算的参数 si 。 然后,通过向右移位操作将该数组中的值除以8(显然是完全),并且由于该除法而将diff变量初始化。 然后,分析输入样本值的三个最低有效位,并且根据它们的状态,可以通过三个项来调整diff变量。 这些术语是diff值由4(>> 2),2(>> 1)或diff不变的相似整数除法(一般情况下为1除法)。 然后,分析输入样本值的最高有效(有符号)位。 根据其状态,将在此变量之前生成的diff变量添加或减去到变量current1中。 这将是输出样本的值。 为了正确起见,这些值仅限于顶部和底部。 然后,通过将ima_index_table数组中的值与输入样本的值的索引相加(符号位重置为零)来调整stepindex1。 Stepindex1值也受限制。 最后,在重复此算法之前,将当前和stepindex值分配给刚重计数的current1和stepindex1值,然后再次重复该算法。

您可以尝试解决这个问题,以便大致了解diff变量的形成方式。 让 fi=fsi 。 这些是迭代的每个第i步的step变量的值,作为参数的函数(数组)的值 si 在哪里 i=012\点 。 为了方便起见,我们将diff变量表示为 d 。 按照上述推理的逻辑,我们有:

di= fracfi8+x0i fracfi4+x1i fracfi2+x2ifi

在哪里 x0ix1ix2i -数字的低3位 xi 。 导致一个公分母,我们将此表达式转换为更方便的形式:

di= fracfi8 Bigg1+2x0i+4x1i+8x2i Bigg=

= fracfi8 Bigg1+2 Bigx0i+2x1i+4x2i Big Bigg= fracfi82xi+1

最后一次转换基于以下事实:在某种意义上,数字的最低三位(0或1) xi 使用给出的系数,除了写出该数字的绝对值以及该数字的最高有效位外,还有其他事情 xi 将匹配整个表达式的符号。 进一步根据公式

yi+1=yi+di

根据旧样本值计算出一个新样本值。 另外,将计算一个新的变量值。 s

si+1=si+t|xi|

公式中的模块指示变量 xi 起作用 t 不包括最高有效符号位,这在代码中得到了体现。 功能 t 是ima_index_table数组的值,索引与该参数对应。

在公式的描述中,我忽略了上面和下面的限制操作。 总迭代方案如下所示:

y0; s0;  x0x1x2\点

di= fracfsi8\大2xi+1\大

yi+1=yi+di

si+1=si+t|xi|

i=012\点



我没有深入研究编码/解码ADPCM的理论。 但是,ima_step_table数组(共89个)的表值通过它们在图形上的反射(请参见下图)判断,描述了相对于零线的样本的概率分布。 实际上,通常是这样:样本越靠近零线,发生的次数就越多。 因此,ADPCM是基于概率模型的,决不能将任何16位PCM样本源集正确地转换为4位ADPCM样本。 一般来说,ADPCM是具有可变量化步长的PCM。 显然,该图表反映了这一非常可变的步骤。 在实践中,根据音频数据的分布规律正确选择了他。



现在让我们继续描述avi容器的结构。 实际上,这是一个复杂的层次结构。


但是,在简化了特殊情况下的任务后,我以线性形式介绍了avi结构。 结果是:avi文件由一个较大的标头,零跳过字节(JUNK),一个音频和视频流区域(及其标头和内容大小)以及索引列表组成。 后者尤其用于滚动播放器中的视频。 没有此列表,滚动将不起作用(选中)。 它只是一个目录,列出了流块的键名(与块标题中的名称匹配),内容的相应大小以及相对于流区域开始的偏移量(地址)的值。

现在,您可以继续开发程序。 问题的具体描述如下。

在X部分的根部:有一个“ DVR”目录。 该目录包含许多非空子目录(仅子目录),其名称与某些日期相对应。 在这些子目录中的每个子目录中,有许多文件具有不同的名称和扩展名“ 264”。 在Y部分中需要:创建“ DVR”目录,并在其中创建与X:部分相同的子目录。 使用具有相同对应名称的文件填充这些子目录中的每个子目录,但扩展名不是“ 264”,而是“ avi”。 这些avi文件必须通过处理从原始264文件中获取,以一种或另一种方式重复现有程序的算法。 处理包括直接重新打包视频流,重新打包解码音频流,格式化avi文件。 该程序应从命令行启动,如下所示:“ 264toavi.exe X:Y:”,其中“ 264toavi.exe”是程序的名称,“ X:”是源节,“ Y:”是目标节。

实际上,为了简化任务,可以编写一个只处理一个文件的转换(重新打包)的程序,从而使程序成为两天:输入文件的名称和输出文件的名称。 然后,仅实施组重新打包,您可以使用其他工具(例如Excel)编写命令批处理文件(bat)。 但是我实施了一个完整的程序,非常麻烦。 源代码不太可能引起读者的注意。 我将描述程序代码的结构。

该程序是在带有WinAPI元素的Dev-C ++开发环境中用C编写的。 该程序实现了三个大型辅助功能:生成初始avi头的功能,解码音频样本的功能以及按字扫描源文件“ 264”的功能。 换句话说,我称其为4个字节的一部分。 已经观察到,标题的大小和所有流的内容是四个字节的倍数。 扫描功能可以返回五个值:0-如果是通常要重新打包的视频流的4个字节,则1-如果它是参考帧的视频流的块的标题,则2-如果是压缩帧的视频流的块的标题,则3-如果是音频流的块的标题,则4-如果是重新打包期间将忽略“损坏”的块。 非常非常罕见,但是确实发生了。 损坏的块(如我所说的)是类似于“ \ 0 \ 0 \ 0 \ 0H264”的标头,其中“ \ 0”是零字节。 常规重新打包程序将忽略此类块。 当然,这样一个块的内容可能确实可以正常工作,但是我忽略了这样的块以使我的程序更接近于标准块。

在主要功能中,除了组织目录之外,扫描功能还可以读取输入文件。 根据此函数返回的内容,将发生进一步的操作。 如果这些是视频流的标头,则在输出avi文件中形成相应的标头。 在这里,它们的称呼不同:“ 00db”是参考帧的视频流的块的标题,“ 00dc”是压缩帧的。 在新遇到的新标题之前的重新打包操作(重写单词)之后,将计算重新包装的内容的大小,并将此值写入紧随刚刚处理的流的标题之后的字段。 如果在扫描过程中遇到音频流标头,则在输出avi文件中生成标头名称“ 03wb”,并在将解码内容写入avi文件的同时,在循环中将音频流从ADPCM解码为PCM。 连同以上所有内容,简要信息(目录)被记录在临时索引文件“ index”中。 扫描功能无法完成,但全部写入主功能中。 但是,该程序将非常麻烦并且几乎难以阅读。

在整个操作结束时,当输入文件“ 264”结束时,在移至新文件之前,程序将完全完成所有操作。 首先,调整avi文件头中的某些字段,其值取决于读取流的大小和数量,然后将临时“索引”文件的内容附加到几乎完成的avi文件中,然后将其删除。 完成这些操作后,输出的avi文件就可以播放了。

程序运行时,在命令行上进行文本可视化,以显示当前目录,文件以及每个参考帧的视频流的块号以及视频的相应时间(以分钟和秒为单位)。 并且,如果输入文件的名称不是原始文件名,而是原始文件(包含频道号,记录开始的日期和时间),则将基于日期时间算法进行更具交互性的可视化。

在测试和调试程序时,我在使用声音解码时遇到的主要问题。 如果在解码函数中声明变量时,我不能正确键入类型,则简单算术将无法正常工作。 因此,一些音频流块被破坏了,耳朵发出喀哒声。 我无法弄清楚的原始264文件的一些头文件头字段对结果不敏感。 与常规程序不同,我的程序不会从重新打包操作中抛出最后一个不完整的流程块。 虽然,它的缺失将不会发挥任何实际作用。 与我的不同,另一个常规程序在索引后的avi文件的末尾留下少量“垃圾”(这是最后一个流的内容)。 为此,视频播放几乎相同。 并且该程序执行重新打包的时间与常规程序相同。

总之,我将以其中一个文件为例,演示.264文件(在WinHex十六进制编辑器中)中的流组织结构,并以打开包装后的avi文件的形式显示RiffPad程序。 该程序大大简化了研究avi文件结构的过程。 它清楚地演示了层次结构,显示了该结构的每个成员的字节内容,甚至以参数列表的形式巧妙地解释了标头的内容。 特别是图片显示了这样一个事实,视频流的内容被覆盖而没有任何变化。



Source: https://habr.com/ru/post/zh-CN422163/


All Articles