WAV是一种以RIFF为基础的无压缩音频编码格式,该格式以Header、Format Chunk及Data Chunk三部分构成。

本文简要解析了各部分的构成要素,概述了如何使用C++对文件头进行解析以及提取音频数据。

上图展示了WAV文件格式,包括每一field的大小与端序

Header

  • ChunkID: 4字节大端序。文件从此处开始,对于WAV或AVI文件,其值总为“RIFF”。
  • ChunkSize: 4字节小端序。表示文件总字节数减8,减去的8字节表示ChunkID与ChunkSize本身所占字节数。
  • Format: 4字节大端序。对于WAV文件,其值总为“WAVE”

Format Chunk

  • Subchunk1ID: 4字节大端序。其值总为“fmt ”,表示Format Chunk从此处开始。
  • Subchunk1Size: 4字节小端序。表示Format Chunk的总字节数减8。
  • AudioFormat: 2字节小端序。对于WAV文件,其值总为1。
  • NumChannels: 2字节小端序。表示总声道个数。
  • SampleRate: 4字节小端序。表示在每个通道上每秒包含多少帧。
  • ByteRate: 4字节小端序。大小等于SampleRate * BlockAlign,表示每秒共包含多少字节。
  • BlockAlign: 2字节小端序。大小等于NumChannels * BitsPerSample / 8, 表示每帧的多通道总字节数。
  • BitsPerSample: 2字节小端序。表示每帧包含多少比特。

Data Chunk

  • Subchunk2ID: 4字节大端序。其值总为“data”,表示Data Chunk从此处开始。
  • Subchunk2Size: 4字节小端序。表示data的总字节数。
  • data: 小端序。表示音频波形的帧数据,各声道按帧交叉排列。

使用C++解析WAV文件

文件头结构

定义结构体WaveHeader来保存WAV文件头,即Header、Format Chunk及Data Chunk的非data部分,此外在该结构体中添加了num_frame字段,用来保存文件总帧数,由于Header、Format Chunk与Data Chunk之间可能有其他说明信息,所以还添加了start_pos字段用来保存真正的data开始的位置。

typedef struct WaveHeader {
char chunk_id[4] = { 0 };
unsigned int chunk_size = 0;
char format[4] = { 0 };
char fmt_chunk_id[4] = { 0 };
unsigned int fmt_chunk_size = 0;
unsigned short audio_fomat = 0;
unsigned short num_channels = 0;
unsigned int sample_rate = 0;
unsigned int byte_rate = 0;
unsigned short block_align = 0;
unsigned short bits_per_sample = 0;
char data_chunk_id[4] = { 0 };
unsigned int data_chunk_size = 0;
int num_frame = 0;
int start_pos = 0;
};

提取文件头

/*
* fname: 文件路径
* wh: 用来保存文件头的结构体实例
*/
void getHead(string fname, WaveHeader &wh) { /*
*由于事先并不知道文件大小,故定义足量大小的char数组覆盖文件头
*之后可根据提取到的ChunkSize来定义提取音频数据用的数组
*/
const int HEAD_LENGTH = 256 * 1024;//256kb
char buf[HEAD_LENGTH]; FILE *stream;
freopen_s(&stream, fname.c_str(), "rb", stderr);
fread(buf, 1, HEAD_LENGTH, stream); //记录文件读取位置
int pos = 0; //寻找“RIFF”标记
while (pos < HEAD_LENGTH) {
if (buf[pos] == 'R'&&buf[pos + 1] == 'I'&&buf[pos + 2] == 'F'&buf[pos + 3] == 'F') {
wh.chunk_id[0] = 'R';
wh.chunk_id[1] = 'I';
wh.chunk_id[2] = 'F';
wh.chunk_id[3] = 'F';
pos += 4;
break;
}
++pos;
} //读取Header部分
wh.chunk_size = *(int *)&buf[pos];
pos += 4;
wh.format[0] = buf[pos];
wh.format[1] = buf[pos + 1];
wh.format[2] = buf[pos + 2];
wh.format[3] = buf[pos + 3];
pos += 4; //寻找“fmt”标记
while (pos < HEAD_LENGTH) {
if (buf[pos] == 'f'&&buf[pos + 1] == 'm'&&buf[pos + 2] == 't') {
wh.fmt_chunk_id[0] = 'f';
wh.fmt_chunk_id[1] = 'm';
wh.fmt_chunk_id[2] = 't';
pos += 4;
break;
}
++pos;
} //读取Format Chunk部分
wh.fmt_chunk_size = *(int *)&buf[pos];
pos += 4;
wh.audio_fomat = *(short *)&buf[pos];
pos += 2;
wh.num_channels = *(short *)&buf[pos];
pos += 2;
wh.sample_rate = *(int *)&buf[pos];
pos += 4;
wh.byte_rate = *(int *)&buf[pos];
pos += 4;
wh.block_align = *(short *)&buf[pos];
pos += 2;
wh.bits_per_sample = *(short *)&buf[pos];
pos += 2; //寻找“data”标记
while (pos < HEAD_LENGTH) {
if (buf[pos] == 'd'&&buf[pos + 1] == 'a'&&buf[pos + 2] == 't'&buf[pos + 3] == 'a') {
wh.data_chunk_id[0] = 'd';
wh.data_chunk_id[1] = 'a';
wh.data_chunk_id[2] = 't';
wh.data_chunk_id[3] = 'a';
pos += 4;
break;
}
++pos;
} //读取Data Chunk的非data部分
wh.data_chunk_size = *(int *)&buf[pos];
pos += 4; //记录真正音频数据的开始位置
wh.start_pos = pos; //计算文件总帧数
wh.num_frame = wh.data_chunk_size / (wh.num_channels*(wh.bits_per_sample / 8));
}

提取波形数据(data)

/*
* fname: 文件路径
* wh: 对应的文件头结构体实例
*/
void getData(string fname, WaveHeader &wh){ //记录文件读取位置
int pos = wh.start_pos; //为加快处理速度,根据ChunkSize将文件一次读入内存
FILE *stream;
freopen_s(&stream, fname.c_str(), "rb", stderr);
char* file_data = new char[wh.chunk_size + 8];
fread(file_data, 1, wh.chunk_size + 8, stream); //以每帧2字节为例
short left_data;
short right_data; while(pos < wh.start_pos + wh.data_chunk_size){
left_data = *(short*)&file_data[pos];
//TODO: 处理左声道数据
pos += 2; right_data = *(short*)&file_data[pos];
//TODO: 处理右声道数据
pos += 2;
}
}

WAV文件读取的更多相关文章

  1. [Audio processing] wav音频文件读取int和double数组的关系

    直接读取wav文件是int数组,但是有一些实现返回的是double数组,还有些输入是double数组:那我们要互相调用的时候还是要看看两者到底有什么关系,其实很简单. 以单身道,16bit为例 /** ...

  2. C# 读取WAV文件(详细)

    class WAVReader { #region RIFF WAVE Chunk private string Id; //文件标识 private double Size; //文件大小 priv ...

  3. C#读取wav文件

    private void showWAVForm(string filepath) //此函数只能用于读取16bit量化单声道的WAV文件 { FileStream fs = new FileStre ...

  4. Python 读取WAV文件并绘制波形图

    aa Python 读取WAV文件并绘制波形图 ffmpeg -i test_pcm_mulaw.wav -f wav -codec:a pcm_s16le -ar 8000 -ac 1 out.wa ...

  5. C++标准库实现WAV文件读写

    在上一篇文章RIFF和WAVE音频文件格式中对WAV的文件格式做了介绍,本文将使用标准C++库实现对数据为PCM格式的WAV文件的读写操作,只使用标准C++库函数,不依赖于其他的库. WAV文件结构 ...

  6. python 播放 wav 文件

    未使用其他库, 只是使用 pywin32 调用系统底层 API 播放 wav 文件. # Our raison d'etre - playing sounds import pywintypes im ...

  7. PC-1500的代码存入WAV文件

    目录 第1章保存    1 1.1 操作    1 1.2 波形说明    4 1.3 波形整形    5 1.4 压缩    8 第2章载入    9 2.1 操作    9 2.2 音量    9 ...

  8. Windwos平台上ffmpeg解码音频并且保存到wav文件中

    先附上代码,测试通过 #include <stdio.h> #include <math.h> #include "libavutil/avstring.h" ...

  9. wav文件系列_2_Python实现读写

    本文介绍了 Python 实现音频读写的方法.Python wave 模块提供便捷的 wav 文件操作.该模块并不支持压缩与解压,但支持单声道/立体声的转换. 参考: [1] wave — Read ...

随机推荐

  1. ent 基本使用十三 debug 模式

    ent 生成的代码client 包中包含了一个方便的方法Debug(), 记得昨天我为了查看生成的sql 查询通过配置mysql 启用慢查询,同时设置记录慢查询为0,实际上client Debug 方 ...

  2. Java中lambda表达式学习

    一.Lambda表达式的基础语法: Java8中引入了一个新的操作符"->"该操作符称为箭头操作符或Lambda操作符,箭头操作符将Lambda表达式拆分为两部分: 左侧:L ...

  3. 求等差数列前$n$项和$S_n$的最值

    一.方法依据: 已知数列\(\{a_n\}\)是等差数列,首项为\(a_1\),公差为\(d\),前\(n\)项和为\(S_n\),则求\(S_n\)的最值常用方法有两种: (1).函数法:由于\(S ...

  4. git 中文文件名乱码

    git 默认中文文件名是 \xxx\xxx 等八进制形式,是因为 对0x80以上的字符进行quote. 只需要设置core.quotepath设为false,就不会对0x80以上的字符进行quote. ...

  5. About me recently

    About me recently Recently I fell that memory has always been problematic.Maybe I hava bee too tired ...

  6. Lombok的使用详解与插件安装

    JAVA面向对象编程中的封闭性和安全性.封闭性即对类中的域变量进行封闭操作,即用private来修饰他们,如此一来其他类则不能对该变量访问.这样我们就将这些变量封闭在了类内部,这样就提高了数据的安全性 ...

  7. JVM 最多支持多少个线程?

    阅读本文大概需要 2.8 分钟. 原文:www.jb51.net/article/49087.htm McGovernTheory 在 StackOverflow 提了这样一个问题: Java 虚拟机 ...

  8. Windows 文件过滤驱动经验总结

    Windows 文件过滤驱动经验总结作者:sinister 本文转载自驱动开发网 看了 ChuKuangRen 的第二版<文件过滤驱动开发教程>后,颇有感触.我想,交流都是建立在平等的基础 ...

  9. js使浏览器窗口最大化(适用于IE的方法)

    这里使用的方法是IE的私有特性,只能在IE中有效.主要是window.moveTo和 window.resizeTo方法.       效果和点击最大化按钮差不多,有一点区别.点击最大化按钮后,浏览器 ...

  10. DICOM中的UID

    UID形式上是一个字符串,用于唯一标识DICOM标准中各种不同信息对象,如数据元素的值表示类型.DICOM抽象语法名.传输语法.应用程序上下文名字等,以保证在各个不同的国家.地区.生产商.设备使用时的 ...