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 { 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 %srn",WavFilesList[FileIndex]); PRINTF("Mode: %srn",WaveFormat.Channels==1?"Mono":"Stereo"); PRINTF("Samplerate: %dHzrn", WaveFormat.SamplesPerSec); PRINTF("Bitrate: %d bpsrn",WaveFormat.AvgBytesPerSec*8); PRINTF("Samples: %drn",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 errorrn"); 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 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 I2SState.TxBuffer[I2SState.TxWriteIndex]= ((int16_t *)pBuffer); } TotalSize+= AUDIO_FRAME_SIZE * sizeof(int16_t); BufferState.ReadIndex++; } else { for(i=0; i I2SState.TxBuffer[I2SState.TxWriteIndex]= ((int32_t *)pBuffer); } 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 overrn"); State = 0; } } }
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.wavrn"); PRINTF("Mode: Monorn"); PRINTF("Samplerate: 16000Hzrn"); PRINTF("Bitrate: %d bpsrn",16000*2*8); Dmic_Start(); c. 不断把麦克风录制的帧数据保存到空的双缓存中,当某一缓存填充满的时候,置位相应的缓存通道,告知SD卡可以把这一缓存通道的数据写入后清空。 if(DmicState.Event) { pBuffer = (int16_t *)BufferState.Buffer+ AUDIO_FRAME_SIZE*BufferState.WriteIndex; for (i=0; i ((int16_t*)pBuffer) = DmicState.Buffer[DmicState.ReadIndex]; } 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 errorrn"); 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
|