C++实现PCM数据封装为WAV文件的完整指南

陈默
陈默
发布时间:2026-10-16| 7人浏览过
原创

声音设备采集的原始数据通常为PCM格式,直接保存无法被常规播放器识别。本文将详细讲解如何使用C++构造WAV头部结构(Header),通过将头部信息后置写入的方式,将连续的PCM音频流正确封装为标准的WAV文件。

一、实现原理

WAV文件由“头部”和“数据块”组成,头部记录了声道数、采样率、位深等关键参数,而数据块仅包含原始的PCM样本。由于在录制过程中数据的总长度是未知的,因此不能直接在文件开头写入头部。标准的做法如下:

  1. 预留头部空间:以读写模式打开新文件,文件指针跳过头部大小的位置,先写入PCM数据。
  2. 记录数据长度:在写入每一个数据块时,累加统计总的数据字节数。
  3. 回填头部信息:所有数据写入完毕后,关闭文件前,将文件指针移回文件起始处,根据已记录的参数和长度重新计算并写入完整的WAV Header。

二、核心数据结构与操作

要实现这一功能,首先需要定义符合WAV规范的Header结构体,并确保内存对齐。以下是关键步骤的代码解析:

第1步:定义WAV头部结构

C++标准库中的 struct 默认可能存在内存对齐差异,导致生成的文件大小或格式不规范。我们需要手动指定各字段的大小和内容。

//WAV头部结构-PCM格式
struct WavPCMFileHeader
{
    struct RIFF {
        const char riff[4] = { 'R','I', 'F', 'F' };
        uint32_t fileLength; // 剩余文件长度 = 36 + dataSize
        const char wave[4] = { 'W','A', 'V', 'E' };
    }riff;
    struct Format
    {
        const char fmt[4] = { 'f','m', 't', ' ' };
        uint32_t blockSize = 16;
        uint16_t formatTag = 1; // PCM类型
        uint16_t channels;
        uint32_t samplesPerSec;
        uint32_t avgBytesPerSec;
        uint16_t blockAlign;
        uint16_t bitsPerSample;
    }format;
    struct Data
    {
        const char data[4] = { 'd','a', 't', 'a' };
        uint32_t dataLength;
    }data;
};

第2步:创建文件并预留空间

调用 fopen 打开文件后,使用 fseek 跳过 sizeof(WavPCMFileHeader) 字节,确保后续写入的PCM数据紧接着空出的头部区域之后。

// wb+ 表示二进制写入模式,且支持读写以便稍后回填头部
FILE* f = fopen(fileName.c_str(), "wb+");
fseek(f, sizeof(WavPCMFileHeader), SEEK_SET);

第3步:循环写入PCM数据

直接使用 fwrite 将PCM缓冲区的二进制数据写入文件,并在内存中维护一个计数器来累加数据长度。

// data为PCM数据指针, dataLength为本次写入的字节数
fwrite(data, 1, dataLength, f);
_totalDataLength += dataLength;

第4步:回填头部并关闭文件

写入完成后,将文件指针重置到 0 位置,实例化Header结构体填入具体参数(采样率、声道、位深、数据总长),最后 fclose。

// 回到文件头
fseek(f, 0, SEEK_SET);
WavPCMFileHeader h(_channels, _sampleRate, _bitsPerSample, _totalDataLength);
fwrite(&h, 1, sizeof(h), f);
fclose(f);

三、完整工程代码

为了方便复用,我们将上述逻辑封装为一个 C++ 类 WavWapper。

1. 头文件 (WavWapper.h)

#pragma once
#include<string>
namespace AC {
	class WavWapper {
	public:
		WavWapper();
		~WavWapper();
		/// <summary>
		/// 创建wav文件
		/// </summary>
		/// <param name="fileName">文件名</param>
		/// <param name="channels">声道数</param>
		/// <param name="sampleRate">采样率,单位hz</param>
		/// <param name="bitsPerSample">位深</param>
		void CreateWavFile(const std::string &fileName, int channels, int sampleRate, int bitsPerSample);
		/// <summary>
		/// 写入PCM数据
		/// </summary>
		void WriteToFile(unsigned char* data, int dataLength);
		/// <summary>
		/// 关闭文件
		/// </summary>
		void CloseFile();
	private:
		void* _file=nullptr;
		uint32_t _totalDataLength=0;
		int _channels;
		int _sampleRate;
		int _bitsPerSample;
	};
}

2. 实现文件 (WavWapper.cpp)

#include"WavWapper.h"
#include<stdio.h>
namespace AC {
	// WAV头部结构-PCM格式定义(同上文)...
	struct WavPCMFileHeader {
	    struct RIFF {
	        const char riff[4] = { 'R','I', 'F', 'F' };
	        uint32_t fileLength;
	        const char wave[4] = { 'W','A', 'V', 'E' };
	    }riff;
	    struct Format
	    {
	        const char fmt[4] = { 'f','m', 't', ' ' };
	        uint32_t blockSize = 16;
	        uint16_t formatTag = 1;
	        uint16_t channels;
	        uint32_t samplesPerSec;
	        uint32_t avgBytesPerSec;
	        uint16_t blockAlign;
	        uint16_t bitsPerSample;
	    }format;
	    struct Data
	    {
	        const char data[4] = { 'd','a', 't', 'a' };
	        uint32_t dataLength;
	    }data;
	
		// 构造函数用于初始化Header内容
		WavPCMFileHeader(int nCh, int nSampleRate, int bitsPerSample, int dataSize) {
			riff.fileLength = 36 + dataSize;
			format.formatTag = 1;
			format.channels = nCh;
			format.samplesPerSec = nSampleRate;
			format.avgBytesPerSec = nSampleRate * nCh * bitsPerSample / 8;
			format.blockAlign = nCh * bitsPerSample / 8;
			format.bitsPerSample = bitsPerSample;
			data.dataLength = dataSize;
		}
	};

	WavWapper::WavWapper()
	{
	}
	WavWapper::~WavWapper()
	{
		CloseFile();
	}

	void WavWapper::CreateWavFile(const std::string& fileName, int channels, int sampleRate, int bitsPerSample)
	{
		if (!_file)
		{
			_channels = channels;
			_sampleRate = sampleRate;
			_bitsPerSample = bitsPerSample;
			_totalDataLength = 0;
			_file = fopen(fileName.c_str(), "wb+");
			// 预留头部位置
			fseek(static_cast<FILE*>(_file), sizeof(WavPCMFileHeader), SEEK_SET);
		}
	}

	void WavWapper::WriteToFile(unsigned char* data, int dataLength)
	{
		fwrite(data, 1, dataLength, static_cast<FILE*>(_file));
		_totalDataLength += dataLength;
	}

	void WavWapper::CloseFile()
	{
		if (_file)
		{
			if (_totalDataLength > 0)
			{
				// 写入头部信息
				fseek(static_cast<FILE*>(_file), 0, SEEK_SET);
				WavPCMFileHeader h(_channels, _sampleRate, _bitsPerSample, _totalDataLength);
				fwrite(&h, 1, sizeof(h), static_cast<FILE*>(_file));
			}
			fclose(static_cast<FILE*>(_file));
			_file = nullptr;
		}
	}
}

四、使用示例

以下示例展示了如何创建一个双声道、44.1kHz采样率、16位深度的WAV文件,并模拟写入PCM数据的过程。

#include "WavWapper.h"
#include<Windows.h>

int main()
{   
	AC::WavWapper ww;
	// 创建wav文件:文件名,声道,采样率,位深
	ww.CreateWavFile("sound.wav", 2, 44100, 16);
	
	bool flag = true; // 模拟录音标志
	while (flag)
	{
		// 1. 获取PCM数据(此处略过实际采集逻辑)
		unsigned char* data = ... 
		int dataLength = ...;
		
		// 2. 写入PCM数据
		ww.WriteToFile(data, dataLength);
		
		// 模拟结束条件
		if (...){ flag = false; }
	}
	// 3. 关闭文件,触发头部写入
	ww.CloseFile();   
	return 0;
}

五、常见问题排查

  • 播放器提示格式错误或无法识别:通常是因为 Header 结构体的大小不对。请检查编译器是否插入了额外的对齐字节,建议在结构体前添加 #pragma pack(push, 1) 强制单字节对齐,或在 Linux/GCC 环境下确认 __attribute__((packed)) 的使用。
  • 文件时长或音量异常:检查传入的 avgBytesPerSec(每秒字节数)和 blockAlign(块对齐)计算是否正确。公式为:平均速率 = 采样率 × 声道数 × 位深 ÷ 8。
  • 生成的文件尾部有多余乱码:CloseFile 方法必须调用,否则 Header 未回填,部分严格遵循格式的播放器可能拒绝读取整个文件。

以上就是C++将PCM数据封装成WAV文件的详细内容,更多关于C++音频处理技巧的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。