WAV音频文件


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文件怎么用libroso保存 python .wav文件_spi

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