C++解析wav文件方法介绍

目录
  • 一、前言
  • 二、接口
  • 三、具体步骤
  • 四、完整源码

一、前言

一开始本来在网上找代码,不过改了好几个都不是很好用。因为很多wav文件的fmt块后面并不是data块,经常还带有其他块,正确的方法应该是按MSDN的方法,找到data块再读取。

二、接口

最后接口如下:

class AudioReader
{
public:
	struct PCM
	{
		int _numChannel;//通道数 1,2 AL_FORMAT_MONO8,AL_FORMAT_STEREO8
		int _bitPerSample;//采样数 8,16
		byte* _data;
		size_t _size;
		size_t _freq;//采样率
		void Delete() { delete[] _data; }
	};
	static bool ReadWAV(string_view path_name, PCM& pcm);
};

三、具体步骤

打开文件,这里就是普通的文件流,按二进制、只读打开文件即可:

ifstream ifs;
if (!g_file->GetFile(path_name, ifs))
{
	debug_err(format("打开文件失败:{}", path_name));
	return false;
}

查找riff块:

uint32_t dwChunkSize;
uint32_t dwChunkPosition;
//查找riff块
FindChunk(ifs, fourccRIFF, dwChunkSize, dwChunkPosition);
uint32_t filetype;
ReadChunkData(ifs, &filetype, sizeof(uint32_t), dwChunkPosition);
if (filetype != fourccWAVE)
{
	debug_err(format("匹配标记失败(fourccWAVE):{}", path_name));
	return false;
}

其中fourccRIFF和fourccWAVE是我们定义的标记,也就是处理了下大小端,如下:

#ifdef DND_ENDIAN_BIG
#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'
#endif
#ifdef DND_ENDIAN_LITTLE
#define fourccRIFF 'FFIR'
#define fourccDATA 'atad'
#define fourccFMT ' tmf'
#define fourccWAVE 'EVAW'
#define fourccXWMA 'AMWX'
#define fourccDPDS 'sdpd'
#endif

而FindChunk和ReadChunkData两个函数,分别是查找一个块,和读取一个块。代码实现有点长,可以参考后面我给出的完整源码。

接着,查找并读取fmt块,这个块描述了wav文件的音频属性,结构如下(部分字段会用到):

//16字节
struct WAVEFormat
{
	int16_t audioFormat;
	int16_t numChannels;
	int32_t sampleRate;
	int32_t byteRate;
	int16_t blockAlign;
	int16_t bitsPerSample;
};
//查找fmt块
if (!FindChunk(ifs, fourccFMT, dwChunkSize, dwChunkPosition))
{
	debug_err(format("查找块失败(fourccFMT):{}", path_name));
	return false;
}
//读wave信息
WAVEFormat wave_format;
if (!ReadChunkData(ifs, &wave_format, dwChunkSize, dwChunkPosition))
{
	debug_err(format("读取块失败(wave_format):{}", path_name));
	return false;
};

接下来查找data块,根据返回的大小分配内存:

//查找音频数据
if (!FindChunk(ifs, fourccDATA, dwChunkSize, dwChunkPosition))
{
	debug_err(format("查找块失败(fourccDATA):{}", path_name));
	return false;
};
pcm._data = new byte[dwChunkSize];

然后读取data块,将数据读取到我们分配的内存pcm._data。然后记录下一些重要的字段。由于OpenaAL不能直接播放32位(只8、16)的数据,这里简单返回失败。

if (!ReadChunkData(ifs, pcm._data, dwChunkSize, dwChunkPosition))
{
	debug_err(format("读取块失败(pcm数据):{}", path_name));
	pcm.Delete();
	return false;
};
pcm._size = dwChunkSize;
pcm._numChannel = wave_format.numChannels;
pcm._bitPerSample = wave_format.bitsPerSample;
pcm._freq = wave_format.sampleRate;
if (pcm._bitPerSample == 32)
{
	debug_err(format("不支持32位:{}", path_name));
	pcm.Delete();
	return false;
}
return true;

四、完整源码

可以此处获取最新的源码(我将来会添加ogg格式的解析),也可以用下面的:传送门

//.h
class AudioReader
{
public:
	struct PCM
	{
		int _numChannel;//通道数 1,2 AL_FORMAT_MONO8,AL_FORMAT_STEREO8
		int _bitPerSample;//采样数 8,16
		byte* _data;
		size_t _size;
		size_t _freq;//采样率
		void Delete() { delete[] _data; }
	};
	static bool ReadWAV(string_view path_name, PCM& pcm);
};
//16字节
struct WAVEFormat
{
	int16_t audioFormat;
	int16_t numChannels;
	int32_t sampleRate;
	int32_t byteRate;
	int16_t blockAlign;
	int16_t bitsPerSample;
};
//.cpp
#ifdef DND_ENDIAN_BIG
#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'
#endif
#ifdef DND_ENDIAN_LITTLE
#define fourccRIFF 'FFIR'
#define fourccDATA 'atad'
#define fourccFMT ' tmf'
#define fourccWAVE 'EVAW'
#define fourccXWMA 'AMWX'
#define fourccDPDS 'sdpd'
#endif
bool FindChunk(ifstream& ifs, uint32_t fourcc, uint32_t& size, uint32_t& pos)
{
	bool ret = true;
	ifs.seekg(0);
	if (ifs.fail())
		return false;
	uint32_t dwChunkType;
	uint32_t dwChunkDataSize;
	uint32_t dwRIFFDataSize = 0;
	uint32_t dwFileType;
	uint32_t bytesRead = 0;
	uint32_t dwOffset = 0;
	while (ret)
	{
		ifs.read((char*)&dwChunkType, sizeof(uint32_t));
		ifs.read((char*)&dwChunkDataSize, sizeof(uint32_t));
		switch (dwChunkType)
		{
		case fourccRIFF:
			dwRIFFDataSize = dwChunkDataSize;
			dwChunkDataSize = 4;
			ifs.read((char*)&dwFileType, sizeof(uint32_t));
			break;
		default:
			ifs.seekg(dwChunkDataSize, std::ios::cur);
			if (ifs.fail())
				return false;
			break;
		}
		dwOffset += sizeof(uint32_t) * 2;
		if (dwChunkType == fourcc)
		{
			size = dwChunkDataSize;
			pos = dwOffset;
			return true;
		}
		dwOffset += dwChunkDataSize;
		if (bytesRead >= dwRIFFDataSize)
			return false;
	}
	return true;
}
bool ReadChunkData(ifstream& ifs, void* buffer, uint32_t size, uint32_t pos)
{
	ifs.seekg(pos);
	if (ifs.fail())
		return false;
	ifs.read((char*)buffer, size);
	return true;
}
bool AudioReader::ReadWAV(string_view path_name, PCM& pcm)
{
	ifstream ifs;
	if (!g_file->GetFile(path_name, ifs))
	{
		debug_err(format("打开文件失败:{}", path_name));
		return false;
	}
	uint32_t dwChunkSize;
	uint32_t dwChunkPosition;
	//查找riff块
	FindChunk(ifs, fourccRIFF, dwChunkSize, dwChunkPosition);
	uint32_t filetype;
	ReadChunkData(ifs, &filetype, sizeof(uint32_t), dwChunkPosition);
	if (filetype != fourccWAVE)
	{
		debug_err(format("匹配标记失败(fourccWAVE):{}", path_name));
		return false;
	}
	//查找fmt块
	if (!FindChunk(ifs, fourccFMT, dwChunkSize, dwChunkPosition))
	{
		debug_err(format("查找块失败(fourccFMT):{}", path_name));
		return false;
	}
	//读wave信息
	WAVEFormat wave_format;
	if (!ReadChunkData(ifs, &wave_format, dwChunkSize, dwChunkPosition))
	{
		debug_err(format("读取块失败(wave_format):{}", path_name));
		return false;
	};
	//查找音频数据
	if (!FindChunk(ifs, fourccDATA, dwChunkSize, dwChunkPosition))
	{
		debug_err(format("查找块失败(fourccDATA):{}", path_name));
		return false;
	};
	pcm._data = new byte[dwChunkSize];
	if (!ReadChunkData(ifs, pcm._data, dwChunkSize, dwChunkPosition))
	{
		debug_err(format("读取块失败(pcm数据):{}", path_name));
		pcm.Delete();
		return false;
	};
	pcm._size = dwChunkSize;
	pcm._numChannel = wave_format.numChannels;
	pcm._bitPerSample = wave_format.bitsPerSample;
	pcm._freq = wave_format.sampleRate;
	if (pcm._bitPerSample == 32)
	{
		debug_err(format("不支持32位:{}", path_name));
		pcm.Delete();
		return false;
	}
	return true;
}

到此这篇关于C++解析wav文件方法介绍的文章就介绍到这了,更多相关C++解析wav内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++读取WAV音频文件的头部数据的实现方法

    C++读取WAV音频文件的头部数据的实现方法 前言: 在这里分享一下自己的心得,希望和大家一起分享技术,如果有什么不足,还请大家指正.写出这篇目的,就是希望大家一起成长,我也相信技术之间没有高低,只有互补,只有分享,才能使彼此更加成长. 实现代码: #include <iostream> #include <string> #include <fstream> using namespace std; using std::string; using std::fstr

  • C++读取wav文件中的PCM数据

    前言 wav文件通常会使用PCM格式数据存储音频,这种格式的数据读取出来直接就可以播放,要在wav文件中读取数据,我们首先要获取头部信息,wav的文件结构里面分为多个chunk,我们要做的就是识别这些chunk的信息,获取音频的格式以及数据. 一.如何实现? 首先需要构造wav头部,wav文件音频信息全部保存在头部,我们要做的就是读取wav头部信息,并且记录PCM的相关参数. 1.定义头结构 只定义PCM格式的wav文件头,对于PCM格式的数据只需要下面3个结构体即可. struct WaveR

  • 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,该文件为WA

  • C++将音频PCM数据封装成wav文件的方法

    前言 使用声音设备采集的声音数据通常是PCM数据,直接写入文件是无法播放的,通常的做法是将其封装成wav格式,这样播放器就能够识别且播放了.本文将介绍如何将PCM封装成wav的方法. 一.如何实现? 首先需要构造wav头部,wav文件音频信息全部保存在头部,我们要做的就是在PCM数据的前面加入wav头,并且记录PCM的相关参数. 1.定义头结构 只定义PCM格式的wav文件头 //WAV头部结构-PCM格式 struct WavPCMFileHeader; 2.预留头部空间 创建文件时预留头部空

  • C++解析wav文件方法介绍

    目录 一.前言 二.接口 三.具体步骤 四.完整源码 一.前言 一开始本来在网上找代码,不过改了好几个都不是很好用.因为很多wav文件的fmt块后面并不是data块,经常还带有其他块,正确的方法应该是按MSDN的方法,找到data块再读取. 二.接口 最后接口如下: class AudioReader { public: struct PCM { int _numChannel;//通道数 1,2 AL_FORMAT_MONO8,AL_FORMAT_STEREO8 int _bitPerSamp

  • C++解析obj模型文件方法介绍

    目录 一.前言 二.中间文件 三.使用 四.完整代码 一.前言 tinyobjloader地址: 传送门 而tinyobjloader库只有一个头文件,可以很方便的读取obj文件.支持材质,不过不支持骨骼动画,vulkan官方教程便是使用的它.不过没有骨骼动画还是有很大的局限性,这里只是分享一下怎么读取材质和拆分网格. 二.中间文件 我抽象了一个ModelObject类表示模型数据,而一个ModelObject包含多个Sub模型,每个Sub模型使用同一材质(有的人称为图元Primitive或Dr

  • Oracle RMAN自动备份控制文件方法介绍

    RMAN(Recovery Manager)是一种用于备份(backup).还原(restore)和恢复(recover) 数据库的 Oracle 工具.RMAN只能用于ORACLE8或更高的版本中.它能够备份整个数据库或数据库部件,如表空间.数据文件.控制文件.归档文件以及Spfile参数文件.RMAN也允许您进行增量数据块级别的备份,增量RMAN备份是时间和空间有效的,因为他们只备份自上次备份以来有变化的那些数据块.而且,通过RMAN提供的接口,第三方的备份与恢复软件如veritas将提供更

  • oracle数据库导入TXT文件方法介绍

    客户端连接数据库导入 1. 安装有oracle客户端,配好监听. 2. 以oracle数据库app用户的表user_svc_info为例 <span style="color:#3333ff;">CREATE TABLE USER_SVC_INFO( PHONE varchar2(20) NOT NULL, SVC_ID varchar2(32) NOT NULL, P_USERNAME varchar2(100) NULL, USER_STATUS number NOT

  • Android中使用PULL方式解析XML文件深入介绍

    一.基本介绍 Android中极力推荐xmlpull方式解析xml. xmlpull不仅可用在Android上同样也适用于javase,但在javase环境中需自己获取xmlpull所依赖的类库,kxml2-2.3.0.jar,xmlpull_1_1_3_4c.jar. jar包下载网址 http://www.xmlpull.org/ http://kxml.sourceforge.net/ 二.例子 读取到xml的声明返回数字0 START_DOCUMENT; 读取到xml的结束返回数字1 E

  • Python读写Excel文件方法介绍

    一.读取excel 这里介绍一个不错的包xlrs,可以工作在任何平台.这也就意味着你可以在Linux下读取Excel文件. 首先,打开workbook: 复制代码 代码如下: import xlrd wb = xlrd.open_workbook('myworkbook.xls') 检查表单名字: 复制代码 代码如下: wb.sheet_names() 得到第一张表单,两种方式:索引和名字 复制代码 代码如下: sh = wb.sheet_by_index(0) sh = wb.sheet_by

  • Java中使用DOM4J生成xml文件并解析xml文件的操作

    目录 一.前言 二.准备依赖 三.生成xml文件生成标准展示 四.解析xml文件 五.总结 一.前言 现在有不少需求,是需要我们解析xml文件中的数据,然后导入到数据库中,当然解析xml文件也有好多种方法,小编觉得还是DOM4J用的最多最广泛也最好理解的吧.小编也是最近需求里遇到了,就来整理一下自己的理解,只适合刚刚学习的,一起理解!今天我们把解析xml文件和生成xml文件在一起来展示. 二.准备依赖 <dependency> <groupId>dom4j</groupId&

  • oracle中通配符和运算符的使用方法介绍

    用于where比较条件的有: 等于:=.<.<=.>.>=.<> 包含:in.not in exists.not exists 范围:between...and.not between....and 匹配测试:like.not like Null测试:is null.is not null 布尔链接:and.or.not 通配符: 在where子句中,通配符可与like条件一起使用.在Oracle中: %(百分号): 用来表示任意数量的字符,或者可能根本没有字符. _(

  • Python解析pcap文件示例

    引言 近期做一些基于TCP协议的项目,跟其他接口方调试时经常出现不一致的问题,而程序日志又不能完成保证公正,就只能通过tcpdump抓包的方式来排查问题了.由于是自定义的协议,用wireshark只能解析成16进制的报文,排查起来并不方便,而实现相关的插件又要用到C++或者LUA语言,这两者我都极少接触,因此,只能临时用Python写程序来解析了~ 首先,需要安装对应的依赖: pip install dpkt 我们用tcpdump或者wireshark抓到对应的内容后,保存为 tcp-log.p

  • Java实现多个wav文件合成一个的方法示例

    本文实例讲述了Java实现多个wav文件合成一个的方法.分享给大家供大家参考,具体如下: 前面一篇介绍了java切割wav音频文件的方法,这里再给出合并多个wav音频文件的方法. package com.cmos.nomsapp.utils.wavmeger; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; impor

随机推荐