C++标准库实现WAV文件读写的操作
在上一篇文章RIFF和WAVE音频文件格式中对WAV的文件格式做了介绍,本文将使用标准C++库实现对数据为PCM格式的WAV文件的读写操作,只使用标准C++库函数,不依赖于其他的库。
WAV文件结构
WAV是符合RIFF标准的多媒体文件,其文件结构可以如下:
WAV 文件结构 |
---|
RIFF块 |
WAVE FOURCC |
fmt 块 |
fact 块(可选) |
data块(包含PCM数据) |
文章图片
本文实现的是一个能够读取PCM数据格式的单声道或者双声道的WAV文件,是没有fact块以及扩展块。
结构体定义 通过上面的介绍发现,WAV的头文件所包含的内容有两种:RIFF文件格式标准中需要的数据和关于音频格式的信息。对于RIFF文件格式所需的信息,声明结构体如下:
// The basic chunk of RIFF file formatstruct Base_chunk{ FOURCC fcc; // FourCC id uint32_t cb_size; // 数据域的大小 Base_chunk(FOURCC fourcc): fcc(fourcc) {cb_size = 0; }};
chunk是RIFF文件的基本单元,首先一个4字节的标识FOURCC,用来指出该块的类型;
cb_size
则是改块数据域中数据的大小。文件头中另一个信息则是音频的格式信息,实际上是frm chunk的数据域信息,其声明如下:
// Format chunk data fieldstruct Wave_format{ uint16_t format_tag; // WAVE的数据格式,PCM数据该值为1 uint16_t channels; // 声道数 uint32_t sample_per_sec; // 采样率 uint32_t bytes_per_sec; // 码率,channels * sample_per_sec * bits_per_sample / 8 uint16_t block_align; // 音频数据块,每次采样处理的数据大小,channels * bits_per_sample / 8 uint16_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,不使用扩展块。
/* 数据格式为PCM的WAV文件头 -------------------------------- | Base_chunk | RIFF | ---------------------| WAVE| | Base_chunk | fmt| Header | Wave_format|| | Base_chunk | data |*/struct Wave_header{ shared_ptrriff; FOURCC wave_fcc; shared_ptr fmt; shared_ptr fmt_data; shared_ptr data; Wave_header(uint16_t nb_channel, uint32_t sample_rate, uint16_t sample_bits) {riff= make_shared (MakeFOURCC<'R', 'I', 'F', 'F'>::value); fmt= make_shared (MakeFOURCC<'f', 'm', 't', ' '>::value); fmt->cb_size = 18; fmt_data= https://www.it610.com/article/make_shared (nb_channel, sample_rate, sample_bits); data= https://www.it610.com/article/make_shared (MakeFOURCC<'d', 'a', 't', 'a'>::value); wave_fcc = MakeFOURCC<'W', 'A', 'V', 'E'>::value; } Wave_header()riff= nullptr; fmt= nullptr; fmt_data= https://www.it610.com/article/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 ) )templatestruct MakeFOURCC{ enum { value = https://www.it610.com/article/(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 = https://www.it610.com/article/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数据的长度
header = make_unique(); // 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 (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 = https://www.it610.com/article/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 = https://www.it610.com/article/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数据
源代码只有一个不到300行的cpp文件,脚本之家下载
【C++标准库实现WAV文件读写的操作】到此这篇关于C++标准库实现WAV文件读写的文章就介绍到这了,更多相关C++ WAV文件读写内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- C++实现LeetCode(38.计数和读法)
- 20个你应该了解的Flutter库
- DBCHM|DBCHM -最简单、最实用的数据库文档生成工具
- C++|C++ 派生类函数重载与虚函数继承详解
- 数据库|【分布式系列01期】常见的分布式ID生成方案浅析及大厂方案调研
- npm 直接安装 GitHub/GitLab 仓库代码及 npm link 本地调试
- 蓝桥杯评分标准_第十届蓝桥杯软件个人赛校内选拔赛评分标准和选拔标准.doc...
- 微服务|微服务架构下的数据库拆分
- 数据库基础理论 - 绪论
- 企业不会搭建团队知识库(用好无忧·企业文档帮助新成员快速融入团队)