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

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

WAV文件结构

WAV是符合RIFF标准的多媒体文件,其文件结构可以如下:

WAV 文件结构
RIFF块
WAVE FOURCC
fmt 块
fact 块(可选)
data块(包含PCM数据)

首先是一个RIFF块,有块标识RIFF,指明该文件是符合RIFF标准的文件;接着是一个FourCC,WAVE,该文件为WAV文件;fmt块包含了音频的一些属性:采样率、码率、声道等;fact 块是一个可选块,不是PCM数据格式的需要该块;最后data块,则包含了音频的PCM数据。实际上,可以将一个WAV文件看着由两部分组成:文件头和PCM数据,则WAV文件头各字段的意义如下:

本文实现的是一个能够读取PCM数据格式的单声道或者双声道的WAV文件,是没有fact块以及扩展块。

结构体定义

通过上面的介绍发现,WAV的头文件所包含的内容有两种:RIFF文件格式标准中需要的数据和关于音频格式的信息。对于RIFF文件格式所需的信息,声明结构体如下:

// The basic chunk of RIFF file format
struct Base_chunk{FOURCC fcc;    // FourCC iduint32_t cb_size; // 数据域的大小Base_chunk(FOURCC fourcc): fcc(fourcc){cb_size = 0;}
};

chunk是RIFF文件的基本单元,首先一个4字节的标识FOURCC,用来指出该块的类型;cb_size则是改块数据域中数据的大小。

文件头中另一个信息则是音频的格式信息,实际上是frm chunk的数据域信息,其声明如下:

// Format chunk data field
struct Wave_format{uint16_t format_tag;      // WAVE的数据格式,PCM数据该值为1uint16_t channels;        // 声道数uint32_t sample_per_sec;  // 采样率uint32_t bytes_per_sec;   // 码率,channels * sample_per_sec * bits_per_sample / 8uint16_t block_align;     // 音频数据块,每次采样处理的数据大小,channels * bits_per_sample / 8uint16_t bits_per_sample; // 量化位数,8、16、32等uint16_t ex_size;         // 扩展块的大小,附加块的大小Wave_format(){format_tag      = 1; // PCM format dataex_size         = 0; // don't use extesion fieldchannels        = 0;sample_per_sec  = 0;bytes_per_sec   = 0;block_align     = 0;bits_per_sample = 0;}Wave_format(uint16_t nb_channel, uint32_t sample_rate, uint16_t sample_bits):channels(nb_channel), sample_per_sec(sample_rate), bits_per_sample(sample_bits){format_tag    = 0x01;                                            // PCM format databytes_per_sec = channels * sample_per_sec * bits_per_sample / 8; // 码率block_align   = channels * bits_per_sample / 8;ex_size       = 0;                                               // don't use extension field}
};

关于各个字段的信息,在上面图中有介绍,这里主要说明两个字段:

  • format_tag表示以何种数据格式存储音频的sample值,这里设置为0x01表示用PCM格式,非压缩格式,不需要fact块。
  • ex_size表示的是扩展块的大小。有两种方法来设置不使用扩展块,一种是设置fmt中的size字段为16(无ex_size字段);或者,有ex_size,设置其值为0.在本文中,使用第二种方法,设置ex_size的值为0,不使用扩展块。

有了上面两个结构体的定义,对于WAV的文件头,可以表示如下:

/*数据格式为PCM的WAV文件头--------------------------------| Base_chunk | RIFF |---------------------| WAVE              |---------------------| Base_chunk | fmt  |   Header---------------------| Wave_format|      |---------------------| Base_chunk | data |--------------------------------
*/
struct Wave_header{shared_ptr<Base_chunk> riff;FOURCC wave_fcc;shared_ptr<Base_chunk> fmt;shared_ptr<Wave_format>  fmt_data;shared_ptr<Base_chunk> data;Wave_header(uint16_t nb_channel, uint32_t sample_rate, uint16_t sample_bits){riff      = make_shared<Base_chunk>(MakeFOURCC<'R', 'I', 'F', 'F'>::value);fmt       = make_shared<Base_chunk>(MakeFOURCC<'f', 'm', 't', ' '>::value);fmt->cb_size = 18;fmt_data  = make_shared<Wave_format>(nb_channel, sample_rate, sample_bits);data      = make_shared<Base_chunk>(MakeFOURCC<'d', 'a', 't', 'a'>::value);wave_fcc = MakeFOURCC<'W', 'A', 'V', 'E'>::value;}Wave_header(){riff         = nullptr;fmt          = nullptr;fmt_data     = nullptr;data         = nullptr;wave_fcc     = 0;}
};

在WAV的文件头中有三种chunk,分别为:RIFF,fmt,data,然后是音频的格式信息Wave_format。在RIFF chunk的后面是一个4字节非FOURCC:WAVE,表示该文件为WAV文件。另外,Wave_format的构造函数只需要三个参数:声道数、采样率和量化精度,关于音频的其他信息都可以使用这三个数值计算得到。注意,这里设置fmt chunk的size为18。

实现

有了上面结构体后,再对WAV文件进行读写就比较简单了。由于RIFF文件中使用FOURCC老标识chunk的类型,这里有两个FOURCC的实现方法:使用宏和使用模板,具体如下:

#define FOURCC uint32_t #define MAKE_FOURCC(a,b,c,d) \
( ((uint32_t)d) | ( ((uint32_t)c) << 8 ) | ( ((uint32_t)b) << 16 ) | ( ((uint32_t)a) << 24 ) )
template <char ch0, char ch1, char ch2, char ch3> struct MakeFOURCC{ enum { value = (ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24) }; };

Write WAVE file

写WAV文件过程,首先是填充文件头信息,对于Wave_format只需要三个参数:声道数、采样率和量化精度,将文件头信息写入后,紧接这写入PCM数据就完成了WAV文件的写入。其过程如下:

Wave_header header(1, 48000, 16);uint32_t length = header.fmt_data->sample_per_sec * 10 * header.fmt_data->bits_per_sample / 8;uint8_t *data = new uint8_t[length];memset(data, 0x80, length);CWaveFile::write("e:\\test1.wav", header, data, length);

首先够着WAV文件头,然后写入文件即可。将数据写入的实现也比较简单,按照WAv的文件结构,依次将数据写入文件。在设置各个chunk的size值时要注意其不同的意义:

  • RIFF chunk 的size表示的是其数据的大小,其包含各个chunk的大小以及PCM数据的长度。该值 + 8 就是整个WAV文件的大小。
  • fmt chunk 的size是Wave_format的大小,这里为18
  • data chunk 的size 是写入的PCM数据的长度

Read WAVE file

知道了WAV的文件结构后,读取其数据就更为简单了。有一种直接的方法,按照PCM相对于文件起始的位置的偏移位置,直接读取PCM数据;或者是按照其文件结构依次读取信息,本文的将依次读取WAV文件的信息填充到相应的结构体中,其实现代码片段如下:

header = make_unique<Wave_header>();// Read RIFF chunkFOURCC fourcc;ifs.read((char*)&fourcc, sizeof(FOURCC));if (fourcc != MakeFOURCC<'R', 'I', 'F', 'F'>::value) // 判断是不是RIFFreturn false;Base_chunk riff_chunk(fourcc);ifs.read((char*)&riff_chunk.cb_size, sizeof(uint32_t));header->riff = make_shared<Base_chunk>(riff_chunk);// Read WAVE FOURCCifs.read((char*)&fourcc, sizeof(FOURCC));if (fourcc != MakeFOURCC<'W', 'A', 'V', 'E'>::value)return false;header->wave_fcc = fourcc;...

实例

调用本文的实现,写入一个单声道,16位量化精度,采样率为48000Hz的10秒钟WAV文件,代码如下:

 Wave_header header(1, 48000, 16);uint32_t length = header.fmt_data->sample_per_sec * 10 * header.fmt_data->bits_per_sample / 8;uint8_t *data = new uint8_t[length];memset(data, 0x80, length);CWaveFile::write("e:\\test1.wav", header, data, length);

这里将所有的sample按字节填充为0x80,以16进制打开该wav文件,结果如下:

可以参照上图给出的WAV文件头信息,看看各个字节的意义。音频的格式信息在FOURCC fmt后面

  • 4字节 00000012 fmt数据的长度 18字节
  • 2字节 0001 数据的存储格式为PCM
  • 2字节 0001 声道个数
  • 4字节 0000BB80 采样率 48000Hz
  • 4字节 00017700 码率 96000bps
  • 2字节 0002 数据块大小
  • 2字节 0010 量化精度 16位
  • 2字节 0000 扩展块的大小
  • 4字节 FOURCC data
  • 4字节 数据长度 0x000EA600

代码

最后将本文的代码封装在了类CWaveFile中,使用简单。

  • 写WAV文件
Wave_header header(1, 48000, 16);uint32_t length = header.fmt_data->sample_per_sec * 10 * header.fmt_data->bits_per_sample / 8;uint8_t *data = new uint8_t[length];memset(data, 0x80, length);CWaveFile::write("e:\\test1.wav", header, data, length);
  • 读取WAV文件
    CWaveFile wave;wave.read("e:\\test1.wav");wave.data // PCM数据

C++标准库实现WAV文件读写相关推荐

  1. python第三方库文件传输_Python第三方库在Excel文件读写中的应用

    Python第三方库在Excel文件读写中的应用 文/刘卫华1 史婷婷2 许学添1 [摘 要]摘 要 [期刊名称]<电子技术与软件工程> [年(卷),期]2019(000)016 [总页数 ...

  2. STM32F103标准库开发---SPI实验---读写 W25Q128 外部 Flash

    STM32F103标准库开发----目录 W25Q128读写----程序源码----点击下载 W25Qxx全系列数据手册-点击下载 一.实验前期准备 本次实验的 MCU 是 STM32F103C8T6 ...

  3. Python标准库判断图片文件和声音文件的格式

    每种文件都有自己独特的文件头结构和数据组织形式,这些都会在specification中进行详细说明和描述. GIF文件的头结构比较简单,前4个字节是GIF8,例如: 但是其他图片文件的结构就复杂很多了 ...

  4. c/c++标准库中的文件操作总结

    1 stdio.h是c标准库中的标准输入输出库 2 在c++中调用的方法 直接调用即可,但是最好在函数名前面加上::,以示区分类的内部函数和c标准库函数. 3 c标准输入输出库的使用 3.1 核心结构 ...

  5. 【C语言】标准库(头文件、静态库、动态库),windows与Linux平台下的常用C语言标准库

    一.Introduction 1.1 C语言标准库 1.2 历代C语言标准 1.3 主流C语言编译器 二.C语言标准库 2.1 常用标准头文件 2.2 常用标准静态库 三.windows平台 四.Li ...

  6. Go 学习笔记(21)— 标准库 os 操作文件(新建、打开、写入、读取、删除、关闭文件)

    Go 操作文本文件时,与其它语言一样也有新建文件.打开文件.写文件.读文件.删除文件等操作.主要有两个标准库来提供这些操作,分别为 os 和 ioutil .在该文中我们介绍 os 模块. 1. 新建 ...

  7. python标准库os的方法listdir_使用python标准库快速修改文件名字

    大家在追剧的时候会一次性下载很多电影,但是很烦人的是前面会有很多电影网站的广告前缀. 今天我将介绍一个简短的代码,快速修改这些文件的名字. 工具:os 首先在电影目录下新建一个py文件,并导入os i ...

  8. py pandas 库及 excel 文件读写

    文章目录 1. 简介 1.1 df 读数据快速参考 2. DataFrame类 2.0 构造函数 2.1 index 2.2 观察data 2.3 筛选与过滤 2.4 增加/删除 column 2.5 ...

  9. Python 标准库 csv —— csv 文件的读写

    csv 文件,逗号分割文件. 0. 读取 csv 到 list from csv import readerdef load_csv(csvfile):dataset = []with open(cs ...

最新文章

  1. 5 关于数据仓库维度数据处理的方法探究系列——缓慢变化维处理——全历史记录...
  2. 策略模式(stragegy)
  3. 如何在ASP.NET Core程序启动时运行异步任务(2)
  4. bp神经网络训练_数据分析模型6——神经网络基础(人工智能的底层模型)
  5. Maven Web项目解决跨域问题
  6. Java弱引用(WeakReference)的理解与使用
  7. RS232和RS485
  8. Django 博客开发教程 6 - 真正的 Django 博客首页视图
  9. 简直要吐槽!!enable-migrations fails on x64 Projects
  10. 数据结构实验之图论七:驴友计划(最新版)
  11. 百度编辑器 ueditor .net开发
  12. spark 窗口函数(Window)实战详解
  13. 破解路由器密码并限制和***邻居电脑
  14. 上海CISSP认证培训课程圆满举办
  15. CFPS数据处理:少儿代答库与成人库匹配
  16. 谁在制造房价泡沫:土地供应下滑 居民杠杆上升
  17. C++解题报告:连续的“包含”子串长度——(线段树+尺取法)
  18. Java算法之 循序搜寻法
  19. 【数学计算】判断两条线段是否相交+计算两条线段的交点和夹角
  20. react-native-beacons-manager使用(Beacons.startRangingBeaconsInRegion)闪退 --工作笔记

热门文章

  1. Educoder 移动端电商页面制作
  2. 华为鸿蒙系统是指芯片吗_华为要卖掉荣耀?假的!荣耀V40采用麒麟芯片和鸿蒙系统...
  3. 【源代码】一键分享各个社交平台_android
  4. “华数杯”建模学习思考(MatlabPython代码实现)
  5. ceil在c 语言中的用法,在C中实现ceil()
  6. 2022年智源社区年度热点推荐丨新春集锦
  7. sci期刊发表文章注意事项
  8. centos系统 用kubeadm 搭建高可用k8s集群
  9. jquery显示隐藏元素
  10. 码农小汪-Volatile和Transient