AVI格式解说

我不知道妳想從那種文件中獲取音頻信息,我做過讀取AVI,WAV文件的音頻及視頻數據獲取。


首先妳應該研究以×。***未后綴的文件二進製數據結搆,通過UltraEdit軟件可以直接打開這些文件,顯示的是一些十六進製的數據。


可以暸解其中的結搆。


簡單說一種關于AVI文件的視頻和音頻的處理:
AVI(Audio Video Interleaved的缩写)是一种RIFF(Resource Interchange File Format的缩写)文件格式,多用于音视频捕捉、编辑、回放等应用程序中。通常情况下,一个AVI文件可以包含多个不同类型的媒体流(典型的情况下有一个音频流和一个视频流),不过含有单一音频流或单一视频流的AVI文件也是合法的。AVI可以算是Windows操作系统上最基本的、也是最常用的一种媒体文件格式。


先来介绍RIFF文件格式。RIFF文件使用四字符码FOURCC(four-character code)来表征数据类型,比如‘RIFF’、‘AVI ’、‘LIST’等。注意,Windows操作系统使用的字节顺序是little-endian,因此一个四字符码‘abcd’实际的DWORD值应为0x64636261。另外,四字符码中像‘AVI ’一样含有空格也是合法的。


最开始的4个字节是一个四字符码‘RIFF’,表示这是一个RIFF文件;紧跟着后面用4个字节表示此RIFF文件的大小;然后又是一个四字符码说明文件的具体类型(比如AVI、WAVE等);最后就是实际的数据。注意文件大小值的计算方法为:实际数据长度 + 4(文件类型域的大小);也就是说,文件大小的值不包括‘RIFF’域和“文件大小”域本身的大小。


RIFF文件的实际数据中,通常还使用了列表(List)和块(Chunk)的形式来组织。列表可以嵌套子列表和块。其中,列表的结构为:‘LIST’ listSize listType listData ——‘LIST’是一个四字符码,表示这是一个列表;listSize占用4字节,记录了整个列表的大小;listType也是一个四字符码,表示本列表的具体类型;listData就是实际的列表数据。注意listSize值的计算方法为:实际的列表数据长度 + 4(listType域的大小);也就是说listSize值不包括‘LIST’域和listSize域本身的大小。再来看块的结构:ckID ckSize ckData ——ckID是一个表示块类型的四字符码;ckSize占用4字节,记录了整个块的大小;ckData为实际的块数据。注意ckSize值指的是实际的块数据长度,而不包括ckID域和ckSize域本身的大小。(注意:在下面的内容中,将以LIST ( listType ( listData ) )的形式来表示一个列表,以ckID ( ckData )的形式来表示一个块,如[ optional element ]中括号中的元素表示为可选项。)
接下来介绍AVI文件格式。AVI文件类型用一个四字符码‘AVI ’来表示。整个AVI文件的结构为:一个RIFF头 + 两个列表(一个用于描述媒体流格式、一个用于保存媒体流数据) + 一个可选的索引块。AVI文件的展开结构大致如下:
RIFF (‘AVI ’
      LIST (‘hdrl’
            ‘avih’(主AVI信息头数据)
            LIST (‘strl’
                  ‘strh’ (流的头信息数据)
                  ‘strf’ (流的格式信息数据)
                  [‘strd’ (可选的额外的头信息数据) ]
                  [‘strn’ (可选的流的名字) ]
                  ...
                 )
             ...
           )
      LIST (‘movi’
            { SubChunk | LIST (‘rec ’
                              SubChunk1
                              SubChunk2
                              ...
                             )
               ...
            }
          ...
           )[‘idx1’ (可选的AVI索引块数据) ]
     )


首先,RIFF (‘AVI ’…)表征了AVI文件类型。然后就是AVI文件必需的第一个列表——‘hdrl’列表,用于描述AVI文件中各个流的格式信息(AVI文件中的每一路媒体数据都称为一个流)。‘hdrl’列表嵌套了一系列块和子列表——首先是一个‘avih’块,用于记录AVI文件的全局信息,比如流的数量、视频图像的宽和高等,可以使用一个AVIMAINHEADER数据结构来操作:


typedef struct _avimainheader {
    FOURCC fcc;   // 必须为‘avih’
    DWORD  cb;    // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
    DWORD  dwMicroSecPerFrame;   // 视频帧间隔时间(以毫秒为单位)
    DWORD  dwMaxBytesPerSec;     // 这个AVI文件的最大数据率
    DWORD  dwPaddingGranularity; // 数据填充的粒度
    DWORD  dwFlags;         // AVI文件的全局标记,比如是否含有索引块等
    DWORD  dwTotalFrames;   // 总帧数
    DWORD  dwInitialFrames; // 为交互格式指定初始帧数(非交互格式应该指定为0)
    DWORD  dwStreams;       // 本文件包含的流的个数
    DWORD  dwSuggestedBufferSize; // 建议读取本文件的缓存大小(应能容纳最大的块)
    DWORD  dwWidth;         // 视频图像的宽(以像素为单位)
    DWORD  dwHeight;        // 视频图像的高(以像素为单位)
    DWORD  dwReserved[4];   // 保留
} AVIMAINHEADER;


然后,就是一个或多个‘strl’子列表。(文件中有多少个流,这里就对应有多少个‘strl’子列表。)每个‘strl’子列表至少包含一个‘strh’块和一个‘strf’块,而‘strd’块(保存编解码器需要的一些配置信息)和‘strn’块(保存流的名字)是可选的。首先是‘strh’块,用于说明这个流的头信息,可以使用一个AVISTREAMHEADER数据结构来操作:


typedef struct _avistreamheader {
     FOURCC fcc;  // 必须为‘strh’
     DWORD  cb;   // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
FOURCC fccType;    // 流的类型:‘auds’(音频流)、‘vids’(视频流)、
                   //‘mids’(MIDI流)、‘txts’(文字流)
     FOURCC fccHandler; // 指定流的处理者,对于音视频来说就是解码器
     DWORD  dwFlags;    // 标记:是否允许这个流输出?调色板是否变化?
     WORD   wPriority;  // 流的优先级(当有多个相同类型的流时优先级最高的为默认流)
     WORD   wLanguage;
     DWORD  dwInitialFrames; // 为交互格式指定初始帧数
     DWORD  dwScale;   // 这个流使用的时间尺度
     DWORD  dwRate;
     DWORD  dwStart;   // 流的开始时间
     DWORD  dwLength;  // 流的长度(单位与dwScale和dwRate的定义有关)
     DWORD  dwSuggestedBufferSize; // 读取这个流数据建议使用的缓存大小
     DWORD  dwQuality;    // 流数据的质量指标(0 ~ 10,000)
     DWORD  dwSampleSize; // Sample的大小
     struct {
         short int left;
         short int top;
         short int right;
         short int bottom;
}  rcFrame;  // 指定这个流(视频流或文字流)在视频主窗口中的显示位置
             // 视频主窗口由AVIMAINHEADER结构中的dwWidth和dwHeight决定
} AVISTREAMHEADER;


然后是‘strf’块,用于说明流的具体格式。如果是视频流,则使用一个BITMAPINFO数据结构来描述;如果是音频流,则使用一个WAVEFORMATEX数据结构来描述。


当AVI文件中的所有流都使用一个‘strl’子列表说明了以后(注意:‘strl’子列表出现的顺序与媒体流的编号是对应的,比如第一个‘strl’子列表说明的是第一个流(Stream 0),第二个‘strl’子列表说明的是第二个流(Stream 1),以此类推),‘hdrl’列表的任务也就完成了,随后跟着的就是AVI文件必需的第二个列表——‘movi’列表,用于保存真正的媒体流数据(视频图像帧数据或音频采样数据等)。那么,怎么来组织这些数据呢?可以将数据块直接嵌在‘movi’列表里面,也可以将几个数据块分组成一个‘rec ’列表后再编排进‘movi’列表。(注意:在读取AVI文件内容时,建议将一个‘rec ’列表中的所有数据块一次性读出。)但是,当AVI文件中包含有多个流的时候,数据块与数据块之间如何来区别呢?于是数据块使用了一个四字符码来表征它的类型,这个四字符码由2个字节的类型码和2个字节的流编号组成。标准的类型码定义如下:‘db’(非压缩视频帧)、‘dc’(压缩视频帧)、‘pc’(改用新的调色板)、‘wb’(音缩视频)。比如第一个流(Stream 0)是音频,则表征音频数据块的四字符码为‘00wb’;第二个流(Stream 1)是视频,则表征视频数据块的四字符码为‘00db’或‘00dc’。对于视频数据来说,在AVI数据序列中间还可以定义一个新的调色板,每个改变的调色板数据块用‘xxpc’来表征,新的调色板使用一个数据结构AVIPALCHANGE来定义。(注意:如果一个流的调色办中途可能改变,则应在这个流格式的描述中,也就是AVISTREAMHEADER结构的dwFlags中包含一个AVISF_VIDEO_PALCHANGES标记。)另外,文字流数据块可以使用随意的类型码表征。




最后,紧跟在‘hdrl’列表和‘movi’列表之后的,就是AVI文件可选的索引块。这个索引块为AVI文件中每一个媒体数据块进行索引,并且记录它们在文件中的偏移(可能相对于‘movi’列表,也可能相对于AVI文件开头)。索引块使用一个四字符码‘idx1’来表征,索引信息使用一个数据结构来AVIOLDINDEX定义。


typedef struct _avioldindex {
   FOURCC  fcc;  // 必须为‘idx1’
   DWORD   cb;   // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
   struct _avioldindex_entry {
      DWORD   dwChunkId;   // 表征本数据块的四字符码
      DWORD   dwFlags;     // 说明本数据块是不是关键帧、是不是‘rec ’列表等信息
      DWORD   dwOffset;    // 本数据块在文件中的偏移量
      DWORD   dwSize;      // 本数据块的大小
  } aIndex[]; // 这是一个数组!为每个媒体数据块都定义一个索引信息
} AVIOLDINDEX;


注意:如果一个AVI文件包含有索引块,则应在主AVI信息头的描述中,也就是AVIMAINHEADER结构的dwFlags中包含一个AVIF_HASINDEX标记。


AVI(Audio/Video Interleaved)文件是MS-Windows的视频文件,其文件扩展名为“.AVI”。它一般由三部分构成:信息区、数据区和索引区(可缺省),即两个LIST快和一个idx1块。这些区域,通常由一些子块组成,它们多用于为播放软件提供更为系统的数据信息;或为播放时进行快速数据定位及播放,并提供详细资料和识别手段(关于“区域”与“子块”具体包容关系请见图6)。 
说明:本例由“四部分”构成,即在两个LIST块中间夹了个JUNK块。这个JUNK块纯属人为添加的自定义块,即在AVI文件中从未有对此块的定义。换句话说,我们也可以添加自己的决,只要遵循如下原则:在这三个标准块其中的某个后面,定义一个四字节的块识别码(不要与本文用到的识别码相同,最好字母用大写),紧跟一个长整数来表示你自定义的块的大小,随后便可以在定义的大小范围内写入你想表述的信息。同样,在这三个标准块的内部,也可以用上述方法添加自定义干块。 
注:下文的说明内容部分取自华中理工大学出版的<>一文和<>的HELP文件。 
以下就是作为例子的文件内容(数据D)及AVI文件标准结构图。 
还有一种特殊的数据块,用一个四字符码‘JUNK’来表征,它用于内部数据的队齐(填充),应用程序应该忽略这些数据块的实际意义。
1.从(00000000-000007F3)为一个WindowsAVI文件的信息区部分。


它是文件的第一个LIST块。在它的内部记录着整个文件的系统构成,如告诉播放软件“我是一个AVI文件”“在我的体内有几个数据流”“每个数据流包含着什么数据类型——图像、声音或其他”“如果是图像数据流,那么它的大小、颜色、压缩方式、播放速度等,等是怎样规定的”“如果是声音数据流,那么它的压缩方式、播放效果等等又将有何规定”......在信息区中还有多个附属的LIST块,也就是我们前面提到的“子块”,它们用来记录每个数据流的全部信息。而这些附间LIST块与数据流之间保持着—一对应的关系,即
第一个附属LIST块对应于00号数据流第二个附属LIST块对应于01号数据流......要想解释数据流,我们必须先了解AVI文件中数据块是什么。在AVI文件中,数据块是被放置在数据区中的一个有起始标志(由“数据流识别码”和“数据块存储方式识别码"组成,请参见对数据区部分的说明),并指明大小和数据内容的数据段.那么,数据流就是那些相互之间具有联系的同种数据类型的数据块集合. 00000000-00000003多媒体文件识别码:RIFF
00000004-00000007文件大小(10EDICh字节)-8字节
00000008-0000000BAVI文件识别码
0000000C-0000000F第一个LIST块识别码
00000010-00000013第一个LIST块的大小(168h字节)
00000014-00000017hdrl部分识别码,以下数据记录着此文件的格式
00000018-0000001Bhdrl部分所包含的avih块识别码,此模块记录着本文件的初始化信息
0000001C-0000001Favih块大小(38h字节)
00000020-00000023每帧画面显示所维持多少个百万分之一秒,本例为1046Bh,即66667百万分之一秒,约0.07秒。所以在播放此文件时,你看到的画面约每秒15帧
首先声明:本文的内容都是我从开发过程中总结出来的,以我的理解在尽可能短的篇幅里对 DV AVI 文件的分析作介绍。真要作开发还需要参考原始的文档。


AVI 文件总是以 12 个字节开始的,就是 'RIFF' + size + 'AVI '。这里 size 是一个 4 字节的整数,声明其后的字节数(包括'AVI '这4个字节数)


现在问题就出来了,这样的格式就是限定了 size 的最大取值只能是 4G,后来人们就扩展了 AVI 的结构——当分析到声明的字节数后,如果后面是扩展格式,那么就继续分析。


扩展部分类似 AVI 的格式,只不过从 'AVI ' 变成了 'AVIX',而且可能有多个扩展部分。因此这一部分的分析代码就是:


head = struct.unpack('<4sI4s', avifile.read(12))
if head[0] != 'RIFF' or head[2] != 'AVI ':
    return None
while True:
    xread = readChunk(avifile, head[1]-4, 0)  # 分析剩余的数据
    s = avifile.read(12)
    if 0 == len(s):                           # 如果没有什么可读的了,自然是分析完了
        break
    head = struct.unpack('<4sI4s', s)
    if head[0] != 'RIFF' or head[2] != 'AVIX':
        break由于 AVI 内部嵌套的数据块的格式也类似 4bytes info + size + data 这样的结构,因此 readChunk 被设计成一个递归函数,返回值为 0 或 -1,中途解析失败就返回 -1,根据此返回值退出嵌套调用。(回过头来看这样一段程序,递归调用分析的可读性很糟糕,主要是因为开始编程的时候对 Python 没有太多的认识所致)


可能是为了便于编程,各个数据块被设计成 4 字节对齐的,但 data 的大小未必是 4 的整数倍,从文件中读出来的 size 只是表示 data 的长度,有时候必须计算对齐。下面两行语句就是作这个的:


page = (head[1] - 1)/4
chunksize = (page + 1) * 4为了便于播放器去 seek 一个特定的位置,比如从文件的第 12 分 32 秒开始播放,需要一个索引方案可以快速定位到相应的数据。这就是 'idx1' chunk 里面定义的东东。但传统的定义里面偏移量最大只能为 4G,因此扩展格式里面增加了 super index,或者说 index 的 index,里面可以放 longlong 的 64 位整数来避免这种寻址困境,估计在我有生之年都不会有这么大个的数据文件问世。


readChunk 函数的主要功能就是生成一个 index 列表,然后从这个列表的最前面和最后面分别 seek 到相应的数据存储区域,找出时间码。如果发现 AVI 里面有 super index,就在 readChunk 返回后,再根据 super index 生成 index 列表。程序里面这个列表变量名为 offset


分析 DV 格式获取时间的函数是 readtime。DV 可能是每次记录 12000 字节数据(类似磁盘扇区的概念??),因此在每 12000 字节数据里面都会存储一个时间码。我的当时参考的代码里面在每个 index 指向的数据块里循环了 15 次还是 10 次,但我发现我这里只能循环 12 次就碰到了数据的尽头,后来估计是 PAL/NTSC 的差异,也就没有继续追究下去。
首先声明:本文的内容都是我从开发过程中总结出来的,以我的理解在尽可能短的篇幅里对 DV AVI 文件的分析作介绍。真要作开发还需要参考原始的文档。
AVI 文件总是以 12 个字节开始的,就是 'RIFF' + size + 'AVI '。这里 size 是一个 4 字节的整数,声明其后的字节数(包括'AVI '这4个字节数)
现在问题就出来了,这样的格式就是限定了 size 的最大取值只能是 4G,后来人们就扩展了 AVI 的结构——当分析到声明的字节数后,如果后面是扩展格式,那么就继续分析。
扩展部分类似 AVI 的格式,只不过从 'AVI ' 变成了 'AVIX',而且可能有多个扩展部分。因此这一部分的分析代码就是:
head = struct.unpack('<4sI4s', avifile.read(12))
if head[0] != 'RIFF' or head[2] != 'AVI ':
    return None
while True:
    xread = readChunk(avifile, head[1]-4, 0)  # 分析剩余的数据
    s = avifile.read(12)
    if 0 == len(s):                           # 如果没有什么可读的了,自然是分析完了
        break
    head = struct.unpack('<4sI4s', s)
    if head[0] != 'RIFF' or head[2] != 'AVIX':
        break
由于 AVI 内部嵌套的数据块的格式也类似 4bytes info + size + data 这样的结构,因此 readChunk 被设计成一个递归函数,返回值为 0 或 -1,中途解析失败就返回 -1,根据此返回值退出嵌套调用。(回过头来看这样一段程序,递归调用分析的可读性很糟糕,主要是因为开始编程的时候对 Python 没有太多的认识所致)
可能是为了便于编程,各个数据块被设计成 4 字节对齐的,但 data 的大小未必是 4 的整数倍,从文件中读出来的 size 只是表示 data 的长度,有时候必须计算对齐。下面两行语句就是作这个的:
page = (head[1] - 1)/4
chunksize = (page + 1) * 4
为了便于播放器去 seek 一个特定的位置,比如从文件的第 12 分 32 秒开始播放,需要一个索引方案可以快速定位到相应的数据。这就是 'idx1' chunk 里面定义的东东。但传统的定义里面偏移量最大只能为 4G,因此扩展格式里面增加了 super index,或者说 index 的 index,里面可以放 longlong 的 64 位整数来避免这种寻址困境,估计在我有生之年都不会有这么大个的数据文件问世。
readChunk 函数的主要功能就是生成一个 index 列表,然后从这个列表的最前面和最后面分别 seek 到相应的数据存储区域,找出时间码。如果发现 AVI 里面有 super index,就在 readChunk 返回后,再根据 super index 生成 index 列表。程序里面这个列表变量名为 offset
分析 DV 格式获取时间的函数是 readtime。DV 可能是每次记录 12000 字节数据(类似磁盘扇区的概念??),因此在每 12000 字节数据里面都会存储一个时间码。我的当时参考的代码里面在每个 index 指向的数据块里循环了 15 次还是 10 次,但我发现我这里只能循环 12 次就碰到了数据的尽头,后来估计是 PAL/NTSC 的差异,也就没有继续追究下去
要实现图象的处理,访问任意象素点的象素值是必需的操作。在访问位图文件时有两点需要注意,一是图象位数组的存储是按从下到上进行的。也就是说,图象的最底行的数据存在位数组的最开始位置。另一个特点是,图象的每行象素所占的空间是双字的整数倍,不足的用零填充。每行象素的实际存储大小可由以下公式加以计算。 
  WidthBytes=(((biWidth*biBitCount)+31)&~31)>>3            (1) 
  假设位数组的起始指针为lpStartBits屏幕坐标(x,y)在的象素值的指针可用下式计算。 
lpBits=lpStartBits + (WidthBytes*(Height-y-1) + x*biBitCount);            (2) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值