概述
Waveform Audio File Format(WAVE,又或者是因为扩展名而被大众所知的WAV),是微软与IBM公司所开发在个人电脑存储音频流的编码格式,在Windows平台的应用软件受到广泛的支持,地位上类似于麦金塔电脑里的AIFF。[2] 此格式属于资源交换文件格式(RIFF)的应用之一,通常会将采用脉冲编码调制的音频资存储在区块中。也是其音乐发烧友中常用的指定规格之一。由于此音频格式未经过压缩,所以在音质方面不会出现失真的情况,但文件的体积因而在众多音频格式中较为大。
格式解析
格式详解
- WAV文件格式详解
- WAV
代码
- 定义上下文结构体
struct StWAVContext
{std::string name;std::ifstream fileHanler;std::vector> chunks; // 所有的chunk集合StAudioFormat format;char* pcmBuf; // pcm bufferuint32_t pcmSize; // pcm数据的大小
};
- 获取当前chunk的ID、Size
void getNextChunk(StWAVContext *context, std::string &id, uint32_t *size)
{char chunk[5] = { 0 }; // ID字段 4bytesstd::ifstream &fp = context->fileHanler;// 获取IDfp.read(chunk, 4);id = std::string(chunk);// 获取Sizememset(chunk, 0, 5);fp.read(chunk, 4);memset(size, 0, sizeof(uint32_t));memcpy(size, chunk, sizeof(uint32_t));
}
- 循环解析所有chunk,其中对chunkID的处理基于自己的业务需求。
std::string chunkID;uint32_t chunkSize = 0;uint32_t curPos = 0;do{chunkID.clear();getNextChunk(context, chunkID, &chunkSize);context->chunks.push_back(std::make_pair(chunkID, chunkSize));debugf("Find chunk: ID[%s] Size[%ld]", chunkID.c_str(), chunkSize);curPos = context->fileHanler.tellg();if (chunkID.empty()){errorf("Parse nothing.");break;}if (chunkID == "RIFF"){context->fileHanler.seekg(4, context->fileHanler.cur);}else if (chunkID == "fmt "){parseFormatChunk(context, chunkSize);}else if (chunkID == "data"){parseDataChunk(context, chunkSize);break;}else{context->fileHanler.seekg(chunkSize, context->fileHanler.cur);}} while (true);
- 以chunkID == "fmt "为例(注意:fmt后还有一个空格,这是因为chunkID占用了4个字节,而fmt仅占用3个字节的原因),解析特定chunk如下:
void parseFormatChunk(StWAVContext *context, uint32_t chunkSize)
{char str_chunk4B[5] = { 0 };char str_chunk2B[3] = { 0 };uint32_t digit_chunk4B = 0;uint16_t digit_chunk2B = 0;std::ifstream &fp = context->fileHanler;// 解析AudioFormatmemset(&str_chunk2B, 0, sizeof(str_chunk2B));fp.read(str_chunk2B, 2);digit_chunk2B = 0;memcpy(&digit_chunk2B, str_chunk2B, sizeof(digit_chunk2B));if (is_bigEndian()){digit_chunk2B = ntohs(digit_chunk2B);}context->format.type = digit_chunk2B;debugf("File[%s] type: %d.", context->name.c_str(), context->format.type);// 解析channelmemset(&str_chunk2B, 0, sizeof(str_chunk2B));fp.read(str_chunk2B, 2);digit_chunk2B = 0;memcpy(&digit_chunk2B, str_chunk2B, sizeof(digit_chunk2B));if (is_bigEndian()){digit_chunk2B = ntohs(digit_chunk2B);}context->format.channels = digit_chunk2B;debugf("File[%s] channels: %d.", context->name.c_str(), context->format.channels);// sample_ratememset(&str_chunk4B, 0, sizeof(str_chunk4B));fp.read(str_chunk4B, 4);digit_chunk4B = 0;memcpy(&digit_chunk4B, str_chunk4B, sizeof(digit_chunk4B));if (is_bigEndian()){digit_chunk4B = ntohl(digit_chunk4B);}context->format.sample_rate = digit_chunk4B;debugf("File[%s] sample_rate: %ld.", context->name.c_str(), context->format.sample_rate);// bytes per secondmemset(&str_chunk4B, 0, sizeof(str_chunk4B));fp.read(str_chunk4B, 4);digit_chunk4B = 0;memcpy(&digit_chunk4B, str_chunk4B, sizeof(digit_chunk4B));if (is_bigEndian()){digit_chunk4B = ntohl(digit_chunk4B);}context->format.bytes_per_second = digit_chunk4B;debugf("File[%s] bytes_per_second: %ld.", context->name.c_str(), context->format.bytes_per_second);// block align sizememset(&str_chunk2B, 0, sizeof(str_chunk2B));fp.read(str_chunk2B, 2);digit_chunk2B = 0;memcpy(&digit_chunk2B, str_chunk2B, sizeof(digit_chunk2B));if (is_bigEndian()){digit_chunk2B = ntohs(digit_chunk2B);}context->format.block_size = digit_chunk2B;debugf("File[%s] block_size: %ld.", context->name.c_str(), context->format.block_size);// bits per samplememset(&str_chunk2B, 0, sizeof(str_chunk2B));fp.read(str_chunk2B, 2);digit_chunk2B = 0;memcpy(&digit_chunk2B, str_chunk2B, sizeof(digit_chunk2B));if (is_bigEndian()){digit_chunk2B = ntohs(digit_chunk2B);}context->format.bits_per_sample = digit_chunk2B;debugf("File[%s] bits_per_sample: %ld.", context->name.c_str(), context->format.bits_per_sample);
}
字节序
在wave的每一个chunk中,涉及到了大量的大小端字节序转换,由此带来一个问题,判断当前主机cpu的字节序,代码如下:
bool is_bigEndian()
{static union { char c; unsigned long mylong; } endian_test = { 0x01 };// bit_endian: 00 00 00 01// little_endian: 01 00 00 00return endian_test.c == 0x00;
}