欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

转: WAV 音频格式文件

程序员文章站 2022-03-03 16:13:18
...

WAV音频文件

象棋小子    1048272975

WAV是一种保存音频信息的文件格式,广泛应用于Windows及其应用程序中,如今主流的音频播放器都支持WAV音频文件的播放。

1. WAV音频格式

WAV是录音时用的标准Windows文件格式,文件扩展名为”.wav”,数据本身的格式为PCM或压缩型,它是由微软与IBM联合开发的用于音频数字存储的标准,采用RIFF文件格式结构。

RIFF全称资源互换文件格式,是Windows下大部分多媒体文件遵循的一种文件结构,除了本文所说的波形格式数据(.wav),采用RIFF格式结构的文件还有音频视频交错格式(.avi)、位图格式(.rdi)、MIDI格式(.rmi)、调色板格式(.pal)、多媒体电影(.rmn)、动画光标(.ani)。

RIFF结构的基本单元为chunk,每个chunk必须包含一个4字节的chunk id,一个4字节的数据大小和对应的chunk数据。它的结构如下:

struct chunk {

unsigned int  id; /*  块标志 */

unsignedint  size; /* 块大小*/

unsigned chardata[size]; /* 块内容 */

}

id为4个ascii字符组成,用来识别块中所包含的数据,如”RIFF”、”WAV ”、”data”、”fmt ”等,size是存储在data域中数据的长度,不包括id与size域的大小,data[size]为该块保存的数据,以字为单位排列。

WAV音频文件作为RIFF结构,其由若干个chunk组成,按照在文件中的位置包括:RIFF chunk,fmt chunk,fact chunk(可选),data chunk。所有RIFF结构文件均会首先包含RIFF chunk,并指明RIFF类型,此处为”WAVE”。WAV文件在fmt chunk中指明音频格式信息,例如采样位数、采样频率、声道数、编码方式等。对于压缩型WAV音频,如ADPCM、A律、U律等等,还会有一个fact chunk,用以指明解压后音频数据的大小,对于PCM非压缩WAV文件,并没有该chunk。音频数据保存在data chunk中,根据fmt chunk中指明的声道数以及采样位数,WAV音频数据存放形式有不同的方式。

一个简单的PCM格式WAV结构定义如下:

#define PCM_WAVE_FORMAT 1

typedef struct RIFF_HEADER {

       charRiffId[4];

       uint32_tRiffSize;

       charRiffFormat[4];

} RIFF_HEADER;

 

typedef struct WAVE_FORMAT {

       uint16_tFormatTag; //声音的格式代号

       uint16_tChannels; //声音通道

       uint32_tSamplesPerSec; //采样率

       uint32_tAvgBytesPerSec; //采样率*块对齐单位

       uint16_tBlockAlign; //块对齐单位=每个取样所需位数*声音通道/8

       uint16_tBitsPerSample; //每个取样所需位数

} WAVE_FORMAT;

 

typedef struct FMT_CHUNK {

       charFmtID[4];

       uint32_tFmtSize;

       WAVE_FORMATWaveFormat;

} FMT_CHUNK;

 

typedef struct DATA_CHUNK {

       charDataId[4];

       uint32_t  DataSize;

} DATA_CHUNK;

 

typedef struct WAVE_HEADER {

       RIFF_HEADERRiffHeader;

       FMT_CHUNK  FmtChunk;

       DATA_CHUNK       DataChunk;

} WAVE_HEADER;

 

static WAVE_HEADER WaveHeader = {

       ‘R’,’I’, ‘F’, ‘F’,

       0,

       ‘W’,’A’, ‘V’, ‘E’,

       ‘f’,’m’, ‘t’, ’ ‘,

       16,

       PCM_WAVE_FORMAT,// PCM编码

       1, // 单声道

       0,// 采样率初始化0

       0,// 每秒字节流初始化0

       2,// 每个采样2字节

       16,// 采样16位

       ‘d’,’a’, ‘t’, ‘a’,

       0

};

2、WAV音频播放

WAV音频的播放涉及到音频驱动、SD卡读写文件的实现,可以参考前面的章节。播放实现主要流程如下:

a.   用f_open()打开SD卡里的WAV文件。

b.   用Wave_ReadHeader()函数解析WAV头,获取采样位数、采样频率、声道数等等音频格式,此处只支持PCM 16位音频格式。

int Wave_ReadHeader(FIL *File,WAVE_FORMAT *WaveFormat)

{

uint32_t ByteRead;

int DataBytes;

char Buffer[512];

char *pBuffer;

      

if (f_lseek(File, 0) != RES_OK) {

       return-1;

}

if (f_read(File, Buffer, sizeof(Buffer),&ByteRead) != RES_OK) {

       return-2;

}

if (!Wav_FindChunk(Buffer,”RIFF”, sizeof(Buffer))) {

       return-3;

}

if (!Wav_FindChunk(Buffer,”WAVE”, sizeof(Buffer))) {

       return-4;

}

pBuffer = Wav_FindChunk(Buffer,”fmt “, sizeof(Buffer));

if (!pBuffer) {

       return-5;

}

pBuffer += 8; // Move past “fmt”, fmt size

memcpy(WaveFormat, pBuffer,sizeof(WAVE_FORMAT));

if (WaveFormat->FormatTag !=PCM_WAVE_FORMAT) {

       return-6;

}

pBuffer = Wav_FindChunk(Buffer,”data”, sizeof(Buffer));

if (!pBuffer) {

       return-7;

}

pBuffer += 4; // Move past”data”

memcpy(&DataBytes, pBuffer,sizeof(uint32_t));

if (WaveFormat->BitsPerSample != 16){

       return-8;

}

return DataBytes;

}

c.   根据解析的音频格式,对I2S音频驱动初始化。

PRINTF(“Playing %s\r\n”,WavFilesList[FileIndex]);

PRINTF(“Mode: %s\r\n”,WaveFormat.Channels==1?”Mono”:”Stereo”);

PRINTF(“Samplerate: %dHz\r\n”, WaveFormat.SamplesPerSec);

PRINTF(“Bitrate: %d bps\r\n”,WaveFormat.AvgBytesPerSec*8);

PRINTF(“Samples: %d\r\n”,DataBytes / WaveFormat.BlockAlign);

I2S_SetSamplerate(WaveFormat.SamplesPerSec);

I2S_TxStart();

d.   采用双缓存(缓存0和缓存1)实现SD卡音频数据的不断读取,当任一个缓存空的时候,用f_read()从SD卡读取音频数据到空缓存中,如果缓存满,则等待音频帧数据播放完,然后把缓存中的数据清空到音频输出流中。

if (Playing &&(!BufferState.Buffer0Full || !BufferState.Buffer1Full)) {

       Res= f_read(&file, BufferState.Buffer[BufferState.WriteIndex],sizeof(BufferState.Buffer[0]), &ByteRead);

       if(Res != RES_OK) {

              f_close(&file);

              PRINTF(“Readdata error\r\n”);

              State= 0;

              break;   

       }

       if(ByteRead < sizeof(BufferState.Buffer[0])) {

              f_close(&file);// 文件结束

              Playing= 0; // 结束播放

       }

       if(BufferState.WriteIndex) {

              BufferState.Buffer1Full= 1;

              BufferState.WriteIndex= 0;

       }else {

              BufferState.Buffer0Full= 1;

              BufferState.WriteIndex= 1;

       }

}

SD卡读取的音频数据需要不断加载到音频输出缓存中,实现音频的连续播放。当I2S音频输出流播放完一帧后,就可以从准备好数据的双缓存中加载一帧的音频数据到输出帧中,直到这一缓存加载完,置缓存空,告知SD卡可以读取数据到这个空缓存。

if (WriteIndex != I2SState.TxReadIndex){

if (BufferState.ReadIndex <BUFFER_NUM*2) {

       if(!BufferState.Buffer0Full) {

              break;

       }

} else {

       if(!BufferState.Buffer1Full) {

              break;

       }

}

             

pBuffer = (int16_t *)BufferState.Buffer+ AUDIO_FRAME_SIZE*BufferState.ReadIndex;

if (WaveFormat.Channels == 1) {

       for(i=0; i<AUDIO_FRAME_SIZE; i++) {

              I2SState.TxBuffer[I2SState.TxWriteIndex][i]= ((int16_t *)pBuffer)[i];

       }

       TotalSize+= AUDIO_FRAME_SIZE * sizeof(int16_t);

       BufferState.ReadIndex++;

} else {

       for(i=0; i<AUDIO_FRAME_SIZE; i++) {

              I2SState.TxBuffer[I2SState.TxWriteIndex][i]= ((int32_t *)pBuffer)[i];

       }

       TotalSize+= AUDIO_FRAME_SIZE * sizeof(int32_t);

       BufferState.ReadIndex+= 2;

}

                           

if (BufferState.ReadIndex ==BUFFER_NUM*2) {

       BufferState.Buffer0Full= 0;

} else if (BufferState.ReadIndex ==BUFFER_NUM*4) {

       BufferState.Buffer1Full= 0;

       BufferState.ReadIndex= 0;

}

 

I2SState.TxWriteIndex = WriteIndex;

if (WriteIndex >=AUDIO_NUM_BUFFERS-1) {

       WriteIndex= 0;

} else {

       WriteIndex++;

}

                           

if (!Playing) {

       if(TotalSize >= DataBytes) {

              I2S_TxStop();

              PRINTF(“Play over\r\n”);

              State = 0;

       }

}

}

转: WAV 音频格式文件

3、WAV音频录制

WAV音频的录制涉及到数字麦克风驱动、SD卡读写文件的实现,可以参考前面的章节。录音实现主要流程如下:

a.   用f_open()创建SD卡里的WAV录音文件。

b.   用f_lseek()开始从音频数据位置开始写入数据。16K采样率、单声道初始化数字麦克风。

if (f_lseek(&file,sizeof(WAVE_HEADER)) != RES_OK) {

       f_close(&file);

       State= 0;

       break;

}

PRINTF(“Recordingsound.wav\r\n”);

PRINTF(“Mode: Mono\r\n”);

PRINTF(“Samplerate: 16000Hz\r\n”);

PRINTF(“Bitrate: %d bps\r\n”,16000*2*8);

Dmic_Start();

c.   不断把麦克风录制的帧数据保存到空的双缓存中,当某一缓存填充满的时候,置位相应的缓存通道,告知SD卡可以把这一缓存通道的数据写入后清空。

if(DmicState.Event) {

pBuffer = (int16_t *)BufferState.Buffer+ AUDIO_FRAME_SIZE*BufferState.WriteIndex;

for (i=0; i<AUDIO_FRAME_SIZE; i++) {

       ((int16_t*)pBuffer)[i] = DmicState.Buffer[DmicState.ReadIndex][i];

}

BufferState.WriteIndex++;

if (BufferState.WriteIndex ==BUFFER_NUM*2) {

       BufferState.Buffer0Full= 1;

} else if (BufferState.WriteIndex == BUFFER_NUM*4){

       BufferState.Buffer1Full= 1;

       BufferState.WriteIndex= 0;

}

if(DmicState.ReadIndex >=AUDIO_NUM_BUFFERS-1) {

DmicState.ReadIndex=0;

}else {

DmicState.ReadIndex++;

}

DmicState.Event= 0;

}

用双缓存不断把录制的音频数据写入到SD卡,当双缓存中的某一缓存填充满,用f_write()把音频数据写入到SD卡,并清空这一缓存,告知麦克风可以把录制帧数据保存到这一空缓存中。

if ((BufferState.Buffer0Full ||BufferState.Buffer1Full)) {

       Res= f_write(&file, BufferState.Buffer[BufferState.ReadIndex],sizeof(BufferState.Buffer[0]), &ByteWrite);

       if(Res != RES_OK) {

              Dmic_Stop();

              f_close(&file);

              PRINTF(“Writedata error\r\n”);

              State= 0;

              break;   

       }

       if(BufferState.ReadIndex) {

              BufferState.Buffer1Full= 0;

              BufferState.ReadIndex= 0;

       }else {

              BufferState.Buffer0Full= 0;

              BufferState.ReadIndex= 1;

       }

       DataBytes += sizeof(BufferState.Buffer[0]);

}

d.   结束录制(通过按键)后,根据实际录制的音频数据大小,通过Wave_WriteHeader()更新WAV文件头。

int Wave_WriteHeader(FIL *File, uint32_tSamplerate, uint32_t DataBytes)

{

uint32_t ByteWrite;

if (f_lseek(File, 0) != RES_OK) {

       return-1;

}

WaveHeader.FmtChunk.WaveFormat.SamplesPerSec= Samplerate;

WaveHeader.FmtChunk.WaveFormat.AvgBytesPerSec= Samplerate * 2;

WaveHeader.DataChunk.DataSize =DataBytes;

WaveHeader.RiffHeader.RiffSize =DataBytes + sizeof(WaveHeader) - 8;

if (f_write(File, (uint8_t*)&WaveHeader, sizeof(WaveHeader), &ByteWrite) != RES_OK) {

       return-2;

}

return 0;

}

4. 附录

MDK工程,包含SD卡文件读写代码,I2S、数字麦克风音频录制播放驱动,WAV音频文件播放、录制的实现。

https://pan.baidu.com/s/1c6kxdk

 

            </div>