当前位置: 首页 > news >正文

【C++】wav文件解析(兼容性强)

wav文件解析

一、前言    

        最近将项目改为跨平台,于是音频模块从微软的XAudio2改用OpenAL库。之前使用MSDN的代码,所以现在改为了C++标准的写法,适用性更广。

        一开始本来在网上找代码,不过改了好几个都不是很好用。因为很多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;
}

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

#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

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

        接着,查找并读取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格式的解析),也可以用下面的:DND/src/DND.Audio.Reader.ixx · 略游/DND - Gitee.com

        如果有用,欢迎点赞、收藏、关注,我将更新更多C++相关的文章。

//.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;
}

相关文章:

  • 免费查题接口搭建
  • 多目标优化算法|用于全局和工程设计优化的多目标原子轨道搜索 (MOAOS)算法(Matlab代码实现)
  • [C++]:for循环for(int num : nums)
  • 3年测试经验,去面试连25K都拿不到了吗?现在测试这么坑?
  • 网课查题公众号 免费授权搜题接口
  • 一篇文章搞懂java中类以及static关键字执行顺序
  • 新手设计师一定要逛这几个网站
  • 多目标优化算法|基于拥挤距离的有效多目标人工蜂鸟算法,用于解决工程设计问题(Matlab代码实现)
  • yara分析
  • 早上一上班发现产品出现重大事故,作为产品经理该怎么办?
  • PaddleHub开源模型400+,三行代码也可实现无限AI创意梦想!
  • [Linux] CE知识随笔含Ansible、防火墙、VIM、其他服务
  • java架构知识点-中间件
  • 基于SSM的视频管理系统【完整项目源码】
  • 做到年收入一百万需要怎样做?
  • [PHP内核探索]PHP中的哈希表
  • ES6指北【2】—— 箭头函数
  • 【剑指offer】让抽象问题具体化
  • angular学习第一篇-----环境搭建
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • ECS应用管理最佳实践
  • ES6核心特性
  • Laravel 菜鸟晋级之路
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • overflow: hidden IE7无效
  • Spring Cloud Feign的两种使用姿势
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 讲清楚之javascript作用域
  • 前端临床手札——文件上传
  • 推荐一款sublime text 3 支持JSX和es201x 代码格式化的插件
  • 我有几个粽子,和一个故事
  • 用jquery写贪吃蛇
  • 原生JS动态加载JS、CSS文件及代码脚本
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • #if和#ifdef区别
  • (16)Reactor的测试——响应式Spring的道法术器
  • (NSDate) 时间 (time )比较
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (二)PySpark3:SparkSQL编程
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)基于SpringBoot和Vue的厨到家服务平台的设计与实现 毕业设计 063133
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (七)Knockout 创建自定义绑定
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)Mysql的优化设置
  • .md即markdown文件的基本常用编写语法
  • .net 7 上传文件踩坑