iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++标准库实现WAV文件读写的操作
  • 382
分享到

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

2024-04-02 19:04:59 382人浏览 泡泡鱼
摘要

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

在上一篇文章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 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 field
struct 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 data
		ex_size         = 0; // don't use extesion field
		channels        = 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 data
		bytes_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的文件头,可以表示如下:


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 chunk
    FOURCC fourcc;
    ifs.read((char*)&fourcc, sizeof(FOURCC));

    if (fourcc != MakeFOURCC<'R', 'I', 'F', 'F'>::value) // 判断是不是RIFF
        return 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 FOURCC
    ifs.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数据

源代码只有一个不到300行的cpp文件,编程网下载

到此这篇关于C++标准库实现WAV文件读写的文章就介绍到这了,更多相关C++ WAV文件读写内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++标准库实现WAV文件读写的操作

本文链接: https://www.lsjlt.com/news/163849.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++标准库实现WAV文件读写的操作
    在上一篇文章RIFF和WAVE音频文件格式中对WAV的文件格式做了介绍,本文将使用标准C++库实现对数据为PCM格式的WAV文件的读写操作,只使用标准C++库函数,不依赖于其他的库。...
    99+
    2022-11-13
  • C#CSV文件读写的实现
    目录为什么要用csv文件一、DataTable数据写入CSV文件二、读取CSV文件到DataTable三、修改文件名称四、CSV文件的数据写入CSV是一种通用的、相对简单的文件格式,...
    99+
    2023-03-03
    C# CSV文件读写 C# CSV 读写
  • C语言Iniparser库实现ini文件读写
    目录一、概述二、使用下载方式一方式二三、API函数四、演示一、概述 iniparser是针对INI文件的解析器。ini文件则是一些系统或者软件的配置文件。iniparser库的API...
    99+
    2023-03-20
    C语言 ini文件读写 C语言 ini文件 C语言 Iniparser库
  • java如何实现文件读写操作
    这篇文章将为大家详细讲解有关java如何实现文件读写操作,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。File类它是文件和目录路径名的抽象表示。文件和目录是可以通过File封装成对象的。对于File而言,...
    99+
    2023-06-29
  • VB.NET中怎么实现读写文本文件操作
    VB.NET中怎么实现读写文本文件操作,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。VB.NET读写文本文件为了把text保存到文件,创建一个基于FileStream的Stre...
    99+
    2023-06-17
  • 如何进行C++ Builder中的文件读写操作
    这期内容当中小编将会给大家带来有关如何进行C++ Builder中的文件读写操作,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。在C++中进行编程时,代码文件的操作是一个经常遇到的问题,在C++ Build...
    99+
    2023-06-17
  • Python中CSV文件的读写库操作方法
    目录文件的基本读写用字典模式处理数据非标准格式的处理dialectSnifferCSV 格式的全称是 Comma Separated Values,意思是逗号分割的数据,是最常见的电...
    99+
    2022-12-08
    CSV文件读写库 CSV文件读写 CSV文件
  • c语言文件读写的操作方法有哪些
    本篇内容介绍了“c语言文件读写的操作方法有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!c语言的文件读写操作:1、用fgetc()和fp...
    99+
    2023-07-04
  • c语言怎么操作文件的读取和写入
    这篇文章主要介绍“c语言怎么操作文件的读取和写入”,在日常操作中,相信很多人在c语言怎么操作文件的读取和写入问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”c语言怎么操作文件的读取和写入”的疑惑有所帮助!接下来...
    99+
    2023-06-20
  • 关于Java中如何实现文件的读写操作
    目录文件读取FileInputStream:BufferedReader:文件写入FileOutputStream:PrintWriter:文件复制文件删除文件重命名总结:在Java...
    99+
    2023-05-18
    Java读写 Java文件读写
  • C++利用jsoncpp库实现写入和读取json文件
    目录一、jsoncpp库二、json文件三、读写json文件一、jsoncpp库 我们都知道由于Json语法是 JavaScript 对象表示语法的子集。所以在Java,JavaSc...
    99+
    2023-05-16
    C++ jsoncpp写入读取json文件 C++ jsoncpp读写json文件 C++ jsoncpp json文件 C++ jsoncpp
  • R语言实现二进制文件读写操作
    二进制文件是一个文件,其中包含仅以位和字节形式存储的信息(0和1),它们是不可读的,因为其中的字节转换为包含许多其他不可打印字符的字符和符号,随便我们尝试使用任何文本编辑器读取二进制...
    99+
    2022-11-11
  • PHP文件函数详解:实现文件的读写和操作功能
    PHP是一种高性能的脚本语言,广泛用于Web开发。在PHP中,文件操作是一项非常常见而重要的功能。本文将详细介绍PHP中文件函数的使用,以帮助读者实现文件的读写和操作功能。一、文件的打开和关闭在PHP中,打开文件使用的是fopen函数,语法...
    99+
    2023-11-20
    PHP文件操作 文件读写 文件函数详解
  • R语言开发之CSV文件的读写操作实现
    在R中,我们可以从存储在R环境外部的文件读取数据,还可以将数据写入由操作系统存储和访问的文件。这个csv文件应该存在于当前工作目录中,以方便R可以读取它, 当然,也可以设置自己的目录...
    99+
    2022-11-11
  • 如何在Go中实现高效的文件读写操作?
    Go是一门高效、并发、简洁的编程语言,常被用于处理大规模数据和高并发的网络应用。在Go中,文件读写是一项非常常见的操作,而如何实现高效的文件读写操作是一个值得深入研究的话题。 在本文中,我们将介绍一些在Go中实现高效文件读写操作的技巧和最...
    99+
    2023-09-05
    缓存 日志 文件
  • 如何在PHP中实现高效的文件读写操作?
    PHP 是一种广泛使用的编程语言,它在网站开发中扮演着重要的角色。在 PHP 中,文件读写操作是经常使用的功能之一,它可以帮助我们读取和写入文件,以便存储和处理数据。然而,文件读写操作可能会降低代码的性能,因此我们需要采取一些措施来提高 P...
    99+
    2023-08-27
    大数据 编程算法 文件
  • C++实现ini文件读写的示例代码
    目录介绍1.使用INIReader.h头文件1.INIReader.h2.test.ini3.INIReaderTest.cpp2.使用ini.h头文件1.ini.h2.config...
    99+
    2022-11-13
  • C#实现CSV文件读写的示例详解
    目录CSV文件标准文件示例RFC 4180简化标准读写CSV文件使用CsvHelper使用自定义方法总结项目中经常遇到CSV文件的读写需求,其中的难点主要是CSV文件的解析。本文会介...
    99+
    2023-05-19
    C#读写CSV文件方法 C#读写CSV文件 C#读写CSV C# CSV
  • C#实现读写CSV文件的方法详解
    目录CSV文件标准文件示例RFC 4180简化标准读写CSV文件使用CsvHelper使用自定义方法总结项目中经常遇到CSV文件的读写需求,其中的难点主要是CSV文件的解析。本文会介...
    99+
    2022-11-13
  • 怎么使用C语言Iniparser库实现ini文件读写
    这篇“怎么使用C语言Iniparser库实现ini文件读写”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么使用C语言Ini...
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作