澳门新浦京电子游戏wav音频文件头解析

本文由码农网 –
风满楼原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

音频处理 (一) 音频文件,音频处理音频文件

音频文件

 
 音频文件是对声音进行数字转换之后存放的数据文件,了解音频数据必须先知道几个重要概念。

1.
采样:对声音信息录入时,行进的最小操作单位,一般一次采样具有左右2个声道,每个声道用1或2个字节来存储;

这样采样的量化位数是8位,或16位(样本位宽),量化位数越高声音音质越好;就像11位电话号码表示的号码比7位要多得多;

2.
采样频率:每秒采样次数,单位Hz,一般的音频文件有11.025kHz、22.05kHz、44.10kHz等;显然,这种模-数信息的转换,每秒采样次数越多,声音就越精确;

3.
码率:每秒编码的bit数,单位是kb/s;计算方式:位宽×声道数×采样频率;(单位是bit不是字节)

4.
声道数,固定值为1-单声道,或者2-双声道,双声道时,每个采样样本中包含左声道、右声道的音频数据,因此两者的数据是交错排列的;

 

 (一)Wave 格式

   
WAVE是微软开发的声音文件格式,用于保存Windows平台的音频信息资源,文件后缀名*.wav;支持多种压缩算法、多种音频位数、采样频率和声道;

   
标准的wav文件采用44.1kHz采样频率,16位量化位数,声音文件质量几与CD相当;Wave格式不对源数据做任何处理,如果源数据是无损的,编码后的Wav文件也是无损的;如果源数据是有损的,编码后的Wav文件也是有损的;

  1. Wave文件的构成:
RIFF 标 志 4B "RIFF"
数据大小 4B
格式 4B "WAVE"
fmt 标志 4B "fmt "
结构体大小 4B 16/18
结构体 16B/18B  
data 标志 4B "data"
声音数据大小 4B
data

 

 

 

 

 

 

 

 

 

 

 

 

 

澳门新浦京电子游戏 1

  1. Wave文件的详细结构:

    // RIFF 标准媒体流文件头
    struct Riff_Header
    {
    char szRiffId[4]; // ‘R’,’I’,’F’,’F’

     DWORD dwRiffSize;            // Size, 除了这 8 个字节之外,文件剩余大小,等于文件总字节数-8
     char szRiffFormat[4];          // 'W','A','V','E'
    

    };

    struct Fmt_Block
    {
    char szFmtId[4]; // ‘f’, ‘m’, ‘t’,’ ‘

      DWORD dwFmtSize;                 // Size 为 16 或 18
    
     WORD wFormatTag;       // 编码方式,一般为 0x0001
     WORD wChannels;                     // 声道数 1--单声道 2--双声道
     DWORD dwSamplesPerSec;      // 采样频率 /Hz
     DWORD dwAvgBytesPerSec;    // 每秒字节数
     WORD wBlockAlign;                 // 数据块对齐单位(每个采样需要的字节数)
     WORD wBitsPerSample;           // 每个采样需要的 bit
    

    // WORD wBits; // 可能有可能没有,由dwFmtSize字段决定
    };

    //Fact_Block 块,有些 wav 文件中没有
    struct Fact澳门新浦京电子游戏 ,_Block
    {

    char szFactId[4];                 // 'f','a','c','t'
         DWORD dwFactSize;           //
    

    };

    //数据块
    struct Data_Block
    {

    char szDataId[4];                //'d','a,','t','a'
        DWORD dwDataSize;          // 音频数据大小
    //data ...
    

    };

 说明:

  1. RIFF块里面的 dwRiffSize 表示的是整个文件除开头8个字节之外的大小,0x24
    0xCD 0x01 0x00,即 118,052 Byte,通过文件属性查得文件大小是118,060Byte;

  2. dwFmtSize 为 0x10 0x00 0x00
    0x00,即为16;fmt块的剩余部分是一个波形信息结构,是微软定义的:

澳门新浦京电子游戏 2

/*
 *  extended waveform format structure used for all non-PCM formats. this
 *  structure is common to all non-PCM formats.
 */
typedef struct tWAVEFORMATEX
{
    WORD        wFormatTag;         /* format type */
    WORD        nChannels;          /* number of channels (i.e. mono, stereo...) */
    DWORD       nSamplesPerSec;     /* sample rate */
    DWORD       nAvgBytesPerSec;    /* for buffer estimation */
    WORD        nBlockAlign;        /* block size of data */
    WORD        wBitsPerSample;     /* number of bits per sample of mono data */
    WORD        cbSize;             /* the count in bytes of the size of */
                                                                                      /* extra information (after cbSize) */
} WAVEFORMATEX, *PWAVEFORMATEX, NEAR *NPWAVEFORMATEX, FAR *LPWAVEFORMATEX;

WAVEFORMATEX

  1. Data块:dwDataSize表示音频数据的大小,0x00 0x01 0xCD
    0x00,即118,016,略小于118,052,说明文件末有部分无效数据;

 

 (二) MP3格式

     MP3格式(待续 …)

 

 

 

 

 

(一) 音频文件,音频处理音频文件 音频文件
音频文件是对声音进行数字转换之后存放的数据文件,了解音频数据必须先知道几个…

wav概述

WAV为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(Resource
Interchange File
Format)文件规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持,该格式也支持MSADPCM,CCITT
A
LAW等多种压缩运算法,支持多种音频数字,取样频率和声道,标准格式化的WAV文件和CD格式一样,也是44.1K的取样频率,16位量化数字,因此在声音文件质量和CD相差无几!
WAV打开工具是WINDOWS的媒体播放器。

通常使用三个参数来表示声音,量化位数,取样频率和采样点振幅。量化位数分为8位,16位,24位三种,声道有单声道和立体声之分,单声道振幅数据为n*1矩阵点,立体声为n*2矩阵点,取样频率一般有11025Hz(11kHz)
,22050Hz(22kHz)和44100Hz(44kHz)
三种,不过尽管音质出色,但在压缩后的文件体积过大!相对其他音频格式而言是一个缺点,其文件大小的计算方式为:WAV格式文件所占容量(B)
= (取样频率 X量化位数X 声道)
X 时间 / 8 (字节=
8bit)
每一分钟WAV格式的音频文件的大小为10MB,其大小不随音量大小及清晰度的变化而变化。

支持WAV设计的手机主要为智能手机,如索尼爱立信P910和诺基亚N90以及采用Windows
Moblie的多普达等手机还有微软Windows
Phone系列手机,而其它一些非智能手机的产品,如果宣传支持WAV格式则多半属于只是支持单声道的。

介绍

我最近遇到一个基于输入文本生成摩斯代码音频文件的需求。几番搜索无果之后,我决定自己编写一个生成器。

下载源代码 – 2.63
KB

澳门新浦京电子游戏 3

因为我希望通过web的方式访问我的摩斯代码音频文件,所以我决定采用PHP作为我主要的编程语言。上面的截图显示了一个开始生成莫斯代码的网页。在下载的zip文件中,包含了用于提交文本的网页以及用于生成和展现音频文件的PHP源文件。如果你想测试PHP代码,你需要将网页和相关的PHP文件复制到启用了PHP的服务器上。

对于许多人来说,莫斯代码就像一些老电影中表现的那样,就是一些“点”和“横线”的序列,或者一连串的哔哔声。显然,如果你想用计算机代码来生成莫斯代码,这样的了解是远远不够的。这篇文章将会介绍生成莫斯代码的要素,如何生成WAVE
格式的音频文件,以及如何用PHP将莫斯代码转化成音频文件。

格式解析

WAVE文件是非常简单的一种RIFF文件,它的格式类型为”WAVE”。RIFF块包含两个子块,这两个子块的ID分别是”fmt”和”data”,其中”fmt”子块由结构PCMWAVEFORMAT所组成,其子块的大小就是sizeofof(PCMWAVEFORMAT),数据组成就是PCMWAVEFORMAT结构中的数据。
整个头长度44byte.

标志符(RIFF)
余下所有数据的长度
格式类型("WAVE")
"fmt"
PCMWAVEFORMAT的长度
PCMWAVEFORMAT
"data"
声音数据大小
声音数据

莫斯代码

莫斯代码是一种文本编码方式。它的优点是编码方便,而且用人耳就能够方便的解码。本质上,是通过音频(或者无线电频)的开和关,从而形成或短或长的音频脉冲,一般称作点(dot)和线(dash),或者用无线电术语称作“嘀”和“嗒”。用现代数字通信术语,莫斯代码是一种振幅键控(amplitude
shift keying ,ASK)。

在莫斯代码中,字符(字母,数字,标点符号和特殊符号)被编码成一个“嘀”和“嗒”的序列。所以为了把文本转化成莫斯代码,我们首先要确定如何来表示“嘀”和“嗒”。一个很显然的选择就是,用0表示“嘀”,用1表示“嗒”,或者反过来。不幸的是,莫斯代码采用的是可变长编码方案。所以我们也必须要使用一种可变长序列,或者采取一种方式,把数据打包成一种计算机内存通用的固定位宽(fixed
bit-size)的格式。另外,需要特别注意的是,莫斯代码并不区分字母大小写,而且对一些特殊符号无法编码。在我们这个实现中,未定义的字符和符号将会被忽略。

在这个项目中,内存占用并不是一个需要特别考虑的问题。所以,我们提出一个简单的编码方案,即用“0”来表示每个“嘀”,用“1”来表示每个“嗒”,并且把他们放在一个字符串关联数组中。定义莫斯代码编码表的PHP代码就像下面这样:

$CWCODE = array ('A'=>'01','B'=>'1000','C'=>'1010','D'=>'100','E'=>'0',
     'F'=>'0010','G'=>'110','H'=>'0000','I'=>'00','J'=>'0111',
     'K'=>'101','L'=>'0100','M'=>'11','N'=>'10', 'O'=>'111',
     'P'=>'0110','Q'=>'1101','R'=>'010','S'=>'000','T'=>'1',
     'U'=>'001','V'=>'0001','W'=>'011','X'=>'1001','Y'=>'1011',
     'Z'=>'1100', '0'=>'11111','1'=>'01111','2'=>'00111',
     '3'=>'00011','4'=>'00001','5'=>'00000','6'=>'10000',
     '7'=>'11000','8'=>'11100','9'=>'11110','.'=>'010101',
     ','=>'110011','/'=>'10010','-'=>'10001','~'=>'01010',
     '?'=>'001100','@'=>'00101');

需要注意的是,如果你特别在意内存占用的话,上面的代码可以解释为位(bit)。给每个代码增加一个开始位,就可以形成一个位的模式,每个字符就可以用一个字节来储存。同时,当解析最终编码的时候,要删除开始位左边的位(bit),从而获得真正的变长编码。

尽管许多人没有意识到,事实上“时间间隔”是定义莫斯代码的主要因素,所以理解这一点是生成莫斯代码的关键。所以,我们要做的第一件事,就是定义莫斯代码的内部码(即“嘀”和“嗒”)的时间间隔。为了方便起见,我们定义一个“嘀”的声音长度为一个时间单位dt,“嘀”和“嗒”之间的间隔也是一个时间单位dt;定义一个“嗒”的长度为3个dt,字符(letters)之间的间隔也是3个dt;定义单词(words)之间的间隔是7个dt。所以,总结起来,我们的时间间隔表就像下面这样:

项目

时间长度

dt

“嘀”/“嗒”之间的间隔

dt

“嗒”

3*dt

字符之间的间隔

3*dt

单词之间的间隔

7*dt

在莫斯代码中,编码声音的“播放速度”通常用 单词数/分钟(WPM)
来表示。由于英文单词有不同的长度,而且字符也有不同数量的“嘀”和“嗒”,所以,从WPM转化成(音频)数字采样并不是看上去那样简单。在一份被国际组织采用的方案中,采用5个字符作为单词的平均长度,同时,一个数字或标点符号被当做2个字符。这样,平均一个单词就是50个时间单位dt。这样,如果你指定了WPM,那么我们总的播放时间就是
50 *
WPM的时间单位/分钟,每个“嘀”(即一个时间单位dt)的长度等于1.2/WPM秒。这样,给出一个“嘀”的时间长度,其他元素的时间长度很容易就能够计算出来。

你可能已经注意到,在上面显示的网页中,对于低于15WPM的选项,我们使用了“Farnsworth
spacing”。那么这个“Farnsworth spacing”又是个什么鬼?

当报务员学习用耳朵来解码莫斯代码的时候,他就会意识到,当播放速度变化的时候,字符出现的节奏也会跟着变化。当播放速度低于10WPM的时候,他能够从容的识别“嘀”和“嗒”,并且知道发送的哪个字符。但是当播放速度超过10WPM的时候,报务员的识别就会出错,他识别出来的字符会多于实际的“嘀”和“嗒”。当一个学习的时候习惯低速莫斯代码的人,在处理高速播放代码的时候,就会出现问题。因为节奏变了,他潜意识的识别就会出错。

为了解决这个问题,“Farnsworth
spacing”就被发明出来了。本质上来讲,字母和符号的播放速度依然采取高于15WPM的速度,同时,通过在字符之间插入更多的空格,来使整体的播放速度降低。这样,报务员就能够以一个合理的速度和节奏来识别每个字符,一旦所有的字符都学习完毕,就可以增加速度,而接收员只需要加快识别字符的速度就可以了。本质上来说,“Farnsworth
spacing”这个技巧解决了节奏变化这个问题,使接收员能够快速学习。

所以,在整个系统中,对于更低的播放速度,都统一成15WPM。相对应的,一个“嘀”的长度是0.08秒,但是字符之间和单词之间的间隔就不再是3个dit或者7个dit,而是进行的调整以适应整体速度。

wav头结构体定义

[cpp] view
plaincopy

 

  1. /* RIFF WAVE file struct. 
  2.  * For details see WAVE file format documentation  
  3.  * (for example at <a href=”).” target=”_blank”>;  */  
  4. typedef struct WAV_HEADER_S  
  5. {  
  6.     char            riffType[4];    //4byte,资源交换文件标志:RIFF     
  7.     unsigned int    riffSize;       //4byte,从下个地址到文件结尾的总字节数   
  8.     char            waveType[4];    //4byte,wav文件标志:WAVE      
  9.     char            formatType[4];  //4byte,波形文件标志:FMT(最后一位空格符)   
  10.     unsigned int    formatSize;     //4byte,音频属性(compressionCode,numChannels,sampleRate,bytesPerSecond,blockAlign,bitsPerSample)所占字节数  
  11.     unsigned short  compressionCode;//2byte,格式种类(1-线性pcm-WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM)  
  12.     unsigned short  numChannels;    //2byte,通道数  
  13.     unsigned int    sampleRate;     //4byte,采样率  
  14.     unsigned int    bytesPerSecond; //4byte,传输速率  
  15.     unsigned short  blockAlign;     //2byte,数据块的对齐,即DATA数据块长度  
  16.     unsigned short  bitsPerSample;  //2byte,采样精度-PCM位宽  
  17.     char            dataType[4];    //4byte,数据标志:data  
  18.     unsigned int    dataSize;       //4byte,从下个地址到文件结尾的总字节数,即除了wav header以外的pcm data length  
  19. }WAV_HEADER;  

 

生成声音

在PHP代码中,一个字符(即前面数组的索引)代表一组由“嘀”、“嗒”和空白间隔组成的莫斯声音。我们用数字采样来组成音频序列,并且将其写入到文件中,同时加上适当的头信息来将其定义成WAVE格式。

生成声音的代码其实相当简单,你可以在项目中PHP文件中找到它们。我发现定义一个“数字振荡器”相当方便。每调用一次osc(),它就会返回一个从正玄波产生的定时采样。运用声音采样和声频规范,生成WAVE格式的音频已经足够了。在产生的正玄波中的-1到+1之间是被移动和调整过的,这样声音的字节数据可以用0到255来表示,同时128表示零振幅。

同时,在生成声音方面我们还要考虑另外一个问题。一般来讲,我们是通过正玄波的开关来生成莫斯代码。但是你直接这样来做的话,就会发现你生成的信号会占用非常大的带宽。所以,通常无线电设备会对其加以修正,以减少带宽占用。

在我们的项目中,也会做这样的修正,只不过是用数字的方式。既然我们已经知道了一个最小声音样本“嘀”的时间长度,那么,可以证明,最小带宽的声幅发生在长度等于“嘀”的正玄波半周期。事实上,我们使用低通滤波器(low
pass
filter)来过滤音频信号也能达到同样的效果。不过,既然我们已经知道所有的信号字符,我们直接简单的过滤一下每一个字符信号就可以了。

生成“嘀”、“嗒”和空白信号的PHP代码就像下面这样:

while ($dt < $DitTime) {
  $x = Osc();
  if ($dt < (0.5*$DitTime)) {
    // Generate the rising part of a dit and dah up to half the dit-time
    $x = $x*sin((M_PI/2.0)*$dt/(0.5*$DitTime));
    $ditstr .= chr(floor(120*$x+128));
    $dahstr .= chr(floor(120*$x+128));
    }
  else if ($dt > (0.5*$DitTime)) {
    // For a dah, the second part of the dit-time is constant amplitude
    $dahstr .= chr(floor(120*$x+128));
    // For a dit, the second half decays with a sine shape
    $x = $x*sin((M_PI/2.0)*($DitTime-$dt)/(0.5*$DitTime));
    $ditstr .= chr(floor(120*$x+128));
    }
  else {
    $ditstr .= chr(floor(120*$x+128));
    $dahstr .= chr(floor(120*$x+128));
    }
  // a space has an amplitude of 0 shifted to 128
  $spcstr .= chr(128);
  $dt += $sampleDT;
  }
// At this point the dit sound has been generated
// For another dit-time unit the dah sound has a constant amplitude
$dt = 0;
while ($dt < $DitTime) {
  $x = Osc();
  $dahstr .= chr(floor(120*$x+128));
  $dt += $sampleDT;
  }
// Finally during the 3rd dit-time, the dah sound must be completed
// and decay during the final half dit-time
$dt = 0;
while ($dt < $DitTime) {
  $x = Osc();
  if ($dt > (0.5*$DitTime)) {
    $x = $x*sin((M_PI/2.0)*($DitTime-$dt)/(0.5*$DitTime));
    $dahstr .= chr(floor(120*$x+128));
    }
  else {
    $dahstr .= chr(floor(120*$x+128));
    }
  $dt += $sampleDT;
  }

头解析程序示例

WAVE格式的文件

WAVE是一种通用的音频格式。从最简单的形式来看,WAVE文件通过在头部包含一个整数序列来表示指定采样率的音频振幅。关于WAVE文件的详细信息请查看这里Audio
File Format Specifications
website。对于产生莫斯代码,我们并不需要用到WAVE格式的所有参数选项,仅仅需要一个8位的单声道就可以了,所以,so
easy。需要注意的是,多字节数据需要采用低位优先(little-endian)的字节顺序。WAVE文件使用一种由叫做“块(chunks)”的记录组成的RIFF格式。

WAVE文件由一个ASCII标识符RIFF开始,紧跟着一个4字节的“块”,然后是一个包含ASCII字符WAVE的头信息,最后是定义格式的数据和声音数据。

在我们的程序中,第一个“块”包含了一个格式说明符,它由ASCII字符fmt和一个4倍字节的“块”。在这里,由于我使用的是普通脉冲编码调制(plain
vanilla
PCM)格式,所以每个“块”都是16字节。然后,我们还需要这些数据:声道数、声音采样/秒、平均字节/秒、一个区块(block)对齐指示器、位(bit)/声音采样。另外,由于我们不需要高质量立体声,我们只采用单声道,我们使用 11050采样/秒(标准的CD质量音频的采样率是 44200采样/秒)的采样率来生成声音,并且用8位(bit)保存。

最后,真实的音频数据储存在接下来的“块”中。其中包含ASCII字符data,一个4字节的“块”,最后是由字节序列(因为我们采用的是8位(bit)/采样)组成的真实音频数据。

在程序中,由8位音频振幅序列组成的声音保存在变量$soundstr中。一旦音频数据生成完毕,就可以计算出所有的“块”大小,然后就可以把它们合并在一起写入磁盘文件中。下面的代码展示了如何生成头信息和音频“块”。需要注意的是,$riffstr表示RIFF头,$fmtstr表示“块”格式,$soundstr表示音频数据“块”。

$riffstr = 'RIFF'.$NSizeStr.'WAVE';
$x = SAMPLERATE;
$SampRateStr = '';
for ($i=0; $i<4; $i++) {
  $SampRateStr .= chr($x % 256);
  $x = floor($x/256);
  }
$fmtstr = 'fmt '.chr(16).chr(0).chr(0).chr(0).chr(1).chr(0).chr(1).chr(0)
          .$SampRateStr.$SampRateStr.chr(1).chr(0).chr(8).chr(0);
$x = $n;
$NSampStr = '';
for ($i=0; $i<4; $i++) {
  $NSampStr .= chr($x % 256);
  $x = floor($x/256);
  }
$soundstr = 'data'.$NSampStr.$soundstr;

wav.h

#ifndef __WAV_H__  
#define __WAV_H__  

#define debug(fmt...) do   
            {   
                printf("[%s::%d] ", __func__, __LINE__);  
                printf(fmt);   
            }while(0)  

/* RIFF WAVE file struct. 
 * For details see WAVE file format documentation  
 * (for example at <a href="http://www.wotsit.org)." target="_blank">http://www.wotsit.org).</a>  */  
typedef struct WAV_HEADER_S  
{  
    char            riffType[4];    //4byte,资源交换文件标志:RIFF     
    unsigned int    riffSize;       //4byte,从下个地址到文件结尾的总字节数   
    char            waveType[4];    //4byte,wave文件标志:WAVE     
    char            formatType[4];  //4byte,波形文件标志:FMT    
    unsigned int        formatSize;     //4byte,音频属性(compressionCode,numChannels,sampleRate,bytesPerSecond,blockAlign,bitsPerSample)所占字节数  
    unsigned short  compressionCode;//2byte,编码格式(1-线性pcm-WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM)  
    unsigned short  numChannels;    //2byte,通道数  
    unsigned int    sampleRate;     //4byte,采样率  
    unsigned int    bytesPerSecond; //4byte,传输速率  
    unsigned short  blockAlign;     //2byte,数据块的对齐  
    unsigned short  bitsPerSample;  //2byte,采样精度  
    char            dataType[4];    //4byte,数据标志:data  
    unsigned int    dataSize;       //4byte,从下个地址到文件结尾的总字节数,即除了wav header以外的pcm data length  
}WAV_HEADER;  

typedef struct WAV_INFO_S  
{  
  WAV_HEADER    header;  
  FILE          *fp;  
  unsigned int  channelMask;  
}WAV_INFO;  

#endif  

 

 

总结和评论

我们的文本莫斯代码生成器目前看起来还不错。当然,我们还可以对它做很多的修改和完善,比如使用其他字符集、直接从文件中读取文本、生成压缩音频等等。因为我们这个项目的目的是使其能够在网络上方便的使用,所以我们这个简单的方案,已经达到我们的目的了。

当然,一如既往的,希望大家对这些简单粗暴的代码提出建议。这些年来虽然一直有人在教我,但我还是缺乏莫斯代码相关背景知识,所以,如果出现任何的错误或遗漏都算是我的错。

wav.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wav.h"

/* func     : endian judge
 * return   : 0-big-endian othes-little-endian
 */
int IS_LITTLE_ENDIAN(void) 
{
    int __dummy = 1;
    return ( *( (unsigned char*)(&(__dummy) ) ) );
}

unsigned int readHeader(void *dst, signed int size, signed int nmemb, FILE *fp) 
{
    unsigned int n, s0, s1, err;
    unsigned char tmp, *ptr;

    if ((err = fread(dst, size, nmemb, fp)) != nmemb) 
    {
        return err;
    }
    if (!IS_LITTLE_ENDIAN() && size > 1) 
    {
        //debug("big-endian n");
        ptr = (unsigned char*)dst;
        for (n=0; n<nmemb; n++) 
        {
            for (s0=0, s1=size-1; s0 < s1; s0++, s1--) 
            {
                tmp = ptr[s0];
                ptr[s0] = ptr[s1];
                ptr[s1] = tmp;
            }
            ptr += size;
        }
    }
    else
    {
        //debug("little-endian n");
    }

    return err;
}

void dumpWavInfo(WAV_INFO wavInfo)
{
    debug("compressionCode:%d n",wavInfo.header.compressionCode);
    debug("numChannels:%d n",wavInfo.header.numChannels);
    debug("sampleRate:%d n",wavInfo.header.sampleRate);
    debug("bytesPerSecond:%d n",wavInfo.header.bytesPerSecond);
    debug("blockAlign:%d n",wavInfo.header.blockAlign);
    debug("bitsPerSample:%d n",wavInfo.header.bitsPerSample);

}

int wavInputOpen(WAV_INFO *pWav, const char *filename)
{
    signed int offset;
    WAV_INFO *wav = pWav ;

    if (wav == NULL) 
    {
      debug("Unable to allocate WAV struct.n");
      goto error;
    }
    wav->fp = fopen(filename, "rb");
    if (wav->fp == NULL) 
    {
      debug("Unable to open wav file. %sn", filename);
      goto error;
    }

    /* RIFF标志符判断 */
    if (fread(&(wav->header.riffType), 1, 4, wav->fp) != 4) 
    {
      debug("couldn't read RIFF_IDn");
      goto error;  /* bad error "couldn't read RIFF_ID" */
    }
    if (strncmp("RIFF", wav->header.riffType, 4)) 
    {
        debug("RIFF descriptor not found.n") ;
        goto error;
    }
    debug("Find RIFF n");

    /* Read RIFF size. Ignored. */
    readHeader(&(wav->header.riffSize), 4, 1, wav->fp);
    debug("wav->header.riffSize:%d n",wav->header.riffSize);

    /* WAVE标志符判断 */
    if (fread(&wav->header.waveType, 1, 4, wav->fp) !=4) 
    {
        debug("couldn't read formatn");
        goto error;  /* bad error "couldn't read format" */
    }
    if (strncmp("WAVE", wav->header.waveType, 4)) 
    {
        debug("WAVE chunk ID not found.n") ;
        goto error;
    }
    debug("Find WAVE n");

    /* fmt标志符判断 */
    if (fread(&(wav->header.formatType), 1, 4, wav->fp) != 4) 
    {
        debug("couldn't read format_IDn");
        goto error;  /* bad error "couldn't read format_ID" */
    }
    if (strncmp("fmt", wav->header.formatType, 3)) 
    {
        debug("fmt chunk format not found.n") ;
        goto error;
    }
    debug("Find fmt n");

    readHeader(&wav->header.formatSize, 4, 1, wav->fp);  // Ignored
    debug("wav->header.formatSize:%d n",wav->header.formatSize);

    /* read  info */
    readHeader(&(wav->header.compressionCode), 2, 1, wav->fp);
    readHeader(&(wav->header.numChannels), 2, 1, wav->fp);
    readHeader(&(wav->header.sampleRate), 4, 1, wav->fp);
    readHeader(&(wav->header.bytesPerSecond), 4, 1, wav->fp);
    readHeader(&(wav->header.blockAlign), 2, 1, wav->fp);
    readHeader(&(wav->header.bitsPerSample), 2, 1, wav->fp);

    offset = wav->header.formatSize - 16;

    /* Wav format extensible */
    if (wav->header.compressionCode == 0xFFFE) 
    {
        static const unsigned char guidPCM[16] = {
            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
            0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
        };
        unsigned short extraFormatBytes, validBitsPerSample;
        unsigned char guid[16];
        signed int i;

        /* read extra bytes */
        readHeader(&(extraFormatBytes), 2, 1, wav->fp);
        offset -= 2;

        if (extraFormatBytes >= 22) 
        {
            readHeader(&(validBitsPerSample), 2, 1, wav->fp);
            readHeader(&(wav->channelMask), 4, 1, wav->fp);
            readHeader(&(guid), 16, 1, wav->fp);

            /* check for PCM GUID */
            for (i = 0; i < 16; i++) if (guid[i] != guidPCM[i]) break;
            if (i == 16) wav->header.compressionCode = 0x01;

            offset -= 22;
        }
    }
    debug("wav->header.compressionCode:%d n",wav->header.compressionCode);

    /* Skip rest of fmt header if any. */
    for (;offset > 0; offset--) 
    {
        fread(&wav->header.formatSize, 1, 1, wav->fp);
    }

    #if 1
    do 
    {
        /* Read data chunk ID */
        if (fread(wav->header.dataType, 1, 4, wav->fp) != 4) 
        {
            debug("Unable to read data chunk ID.n");
            free(wav);
            goto error;
        }
        /* Read chunk length. */
        readHeader(&offset, 4, 1, wav->fp);

        /* Check for data chunk signature. */
        if (strncmp("data", wav->header.dataType, 4) == 0) 
        {
            debug("Find data n");
            wav->header.dataSize = offset;
            break;
        }

        /* Jump over non data chunk. */
        for (;offset > 0; offset--) 
        {
            fread(&(wav->header.dataSize), 1, 1, wav->fp);
        }
    } while (!feof(wav->fp));
    debug("wav->header.dataSize:%d n",wav->header.dataSize);
    #endif  

    /* return success */
    return 0;

/* Error path */
error:
    if (wav) 
    {
      if (wav->fp) 
      {
        fclose(wav->fp);
        wav->fp = NULL;
      }
      //free(wav);
    }
    return -1; 
}

#if 0
int main(int argc,char **argv)

{
    WAV_INFO wavInfo;
    char fileName[128];
    if(argc<2 || strlen(&argv[1][0])>=sizeof(fileName))
    {
        debug("argument error !!! n");
        return -1 ;
    }
    debug("size : %d n",sizeof(WAV_HEADER));
    strcpy(fileName,argv[1]);
    wavInputOpen(&wavInfo, fileName);
    return 0;
}
#endif

附:FIFF文件知识点

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

  2. CHUNK

chunk是组成RIFF文件的基本单元,它的基本结构如下:

struct chunk{

u32 id; /* 块标志 */

u32 size; /* 块大小 */

u8 dat[size]; /*
块内容 */

};

id
由4个ASCII字符组成,用以识别块中所包含的数据。如:’RIFF’,’LIST’,’fmt’,’data’,’WAV’,’AVI’等等,由于这种文件结构最初是由Microsoft和IBM为PC机所定义,RIFF文件是按照little-endian[2] 字节顺序写入的。

size(块大小)
是存储在data域中数据的长度,id与size域的大小则不包括在该值内。

dat(块内容)
中所包含的数据是以字(WORD)为单位排列的,如果该数据结构长度是奇数,则在最后添加一个空(NULL)字节。

chunk块中有且仅有两种类型块:’RIFF’和’LIST’类型可以包含其他块,而其它块仅能含有数据。

‘RIFF’和’LIST’类型的chunk结构如下

structchunk{

u32 id; /* 块标志 */

u32 size; /* 块大小 */

/*此时的dat = type + restdat */

u32 type ; /* 类型 */

u8 restdat[size] /* dat中除type4个字节后剩余的数据*/

};

可以看出,’RIFF’和’LIST’也是chunk,只是它的dat由两部分组成type和restdat。

type,由4个ASCII字符组成,代表RIFF文件的类型,如’WAV’,’AVI
‘;或者’LIST’块的类型,如avi文件中的列表’hdrl’,’movi’。

restdat,dat中除type4个字节后剩余的数据,包括块内容,包含若干chunk和’LIST’

2.1 FOURCC
一个FOURCC(fourcharacter
code
)是一个占4个字节的数据,一般表示4个ASCII字符。在RIFF文件格式中,FOURCC非常普遍,structchunk
中的id成员,’LIST’,’RIFF’的type成员,起始标识等信息都是用FOURCC表示的。FOURCC一般是四个字符,如’abcd’这样的形式,也可以三个字符包含一个空格,如’abc’这样的形式。

RIFF文件的FileData部分由若干个’LIST’和chunk组成,而’LIST’的ListData又可以由若干个’LIST’和chunk组成,即’LIST’是可以嵌套的。

‘RIFF’,FileType,’LIST’,ListType,ChunkID都是FOURCC,即使用4字节的ASIIC字符标识类型。

FileSize,ListSize,ChunkSize为little-endian32-bit正整数,表示Type(只有’RIFF’,’LIST’chunk有Type)+Data一起的大小,注意它是little-endian表示,如:0x00123456,存储地址由低到高,在little-endian系统中的存储表示为0x56341200(字节由低位到高位存储),而在big-endian为0x00123456(字节由高位到低位存储)。32bit整数0x00123456存储地址低———>;高little-endian(字节由低位到高位存储)56341200big-endian(字节由高位到低位存储)00123456

发表评论

电子邮件地址不会被公开。 必填项已用*标注