CWave - 一个简单的 C++ 类来操作 WAV 文件
一篇关于用于操作 WAV 文件的简单 C++ 类的文章

引言
本文介绍一个名为 CWave
的简单 C++ .WAV 文件操作类。这个类的主要功能是加载、保存、播放和混合 .wav 文件。它只能处理 PCM .wav 文件(8 位或 16 位)。编写它的目的只是为了向开发人员展示如何
- 将 .WAV 文件从磁盘加载到内存中(解析 .WAV 文件的 RIFF 结构)
- 将 .WAV 文件从内存保存到磁盘(创建 .WAV 文件的 RIFF 结构)
- 使用默认的
WAVE_MAPPER
设备播放 .WAV 文件 - 离线混合两个 .WAV 文件(8 位和 16 位混合,不使用 DirectX)
背景
这里的目标是找到一种 .WAV 文件混合算法,该算法不会产生伪像或扭曲原始 .WAV 文件。我使用 Google 搜索引擎搜索现有的解决方案,结果发现 - 信不信由你 - 只有 1 或 2 个(!?)。我真的很惊讶,所以我尝试“修复”建议的解决方案,以获得我能获得的最佳输出。这个类包含了我的研究结果。
在 Code Project 上,你可以找到不同的 .WAV 文件加载和播放解决方案,仅使用 Platform SDK 而不使用 DirectX,所以请在仅使用此类之前也看看它们。一些解决方案还提供 .WAV 文件录制...
Using the Code
要使用 CWave
类,请执行以下操作
//
#include "Wave.h"
//
//
// Load .WAV files from the disk
CWave wave1, wave2;
wave1.Load(_T("Enter first .WAV file path here..."));
wave2.Load(_T("Enter second .WAV file path here..."));
//
// Mix .WAVs
wave1.Mix(wave2);
//
// Start .WAV playback (will run in a separate thread, so your program continues)
wave1.Play();
//
// Wait some time (ie. 10 seconds, for the .WAV file to finish playback)
Sleep(10000);
//
// Save the .WAV file on the disk
wave1.Save(_T("Enter destination .WAV file path here..."));
//
以下是 CWave
类的 public
方法的列表
BOOL Load(LPTSTR lpszFilePath);
BOOL Save(LPTSTR lpszFilePath);
BOOL Play();
BOOL Stop();
BOOL Pause();
BOOL Mix(CWave& wave);
BOOL IsValid() {return (m_lpData != NULL);}
BOOL IsPlaying() {return (!m_bStopped && !m_bPaused);}
BOOL IsStopped() {return m_bStopped;}
BOOL IsPaused() {return m_bPaused;}
LPBYTE GetData() {return m_lpData;}
DWORD GetSize() {return m_dwSize;}
SHORT GetChannels() {return m_Format.channels;}
DWORD GetSampleRate() {return m_Format.sampleRate;}
SHORT GetBitsPerSample() {return m_Format.bitsPerSample;}
关于 .WAV 文件混合的一般说明
请参阅下面典型 .WAV 文件的内部结构

所以,正如你所看到的,解析这种文件类型应该不会太困难。这被称为 RIFF 结构,由不同的“块”构建而成。第一个“块”是 DESCRIPTOR,用于解释 RIFF 文件类型。接下来是 FORMAT “块”,它解释了数据格式。.WAV 声音数据可以是 8 位或 16 位、单声道或立体声、可以具有不同的采样率、可以压缩或不压缩等等。我们使用此信息来初始化主机 PC 上的声音输入或声音输出设备。
下一个称为 DATA “块”的“块”保存声音数据,请参见下文

因此,实际上我们处理的是这种声音数据,它表示原始音频信号的样本,以某个高频率(通常为 11kHz、22kHz、44kHz)进行采样。数据样本可以是 8 位、16 位、24 位、32 位。数据可以未压缩(如 PCM)或压缩(如 MP3)。不同的音频解压缩器负责压缩的声音数据,不同的声音输出设备可以“播放”这些数据。此外,可以使用不同的 ACM(Audio Compression Managers)将声音数据从一种格式转换为另一种格式。
这里的主要目标是“混合”两个不同的 .WAV 文件。DirectX 组件(称为 DirectSound)可以很容易地做到这一点,但我希望使用一种老式的方法,使用 Platform SDK 和旧的 Microsoft winmm.lib 库进行声音播放。两个不同的声音数据缓冲区使用以下公式混合
destination = destination + source
是不是很简单? 嗯,它是...几乎。 然而,这是你会找到的主要声音混合等式。 好吧...如果你找到的话,最好说。 我确信声音专家会不同意我的最后声明,但是没有更好的解决方案可用。 在开源世界中没有。 因此,在这种情况下,我们将在输出中得到什么,在大多数情况下,会非常好(有时甚至非常棒)。 但是,通常会在扬声器上注意到(听到)一些伪像。 这是由于方法本身。 它进行简单的波叠加。 因此,如果波幅差异很大,则输出可能会失真。 为了避免这种情况,我使用了 8 位 .WAV 文件的中值,并获得了良好的输出,降低了音量。 我将幅度增加了 log10(20) 倍,得到了更好的结果。 对于 16 位 .WAV 文件,我检查了幅度的绝对乘积。 如果它高于 0.5,那么我使用了简单的叠加(没有音量校正)。 如果它低于 0.5,那么我使用了较低的幅度。 输出结果最终对于单声道和立体声 .WAV 文件都足够好。
然而,我确信这个解决方案不是最好的也不是最终的解决方案。我的目标是为开发人员提供进一步研究和改进声音数据处理技术的可能性。我还希望这将成为所有想要改进这一部分的 CodeProject 开发人员的良好起点。
关注点
我一直对声音数据的处理感兴趣。在撰写本文时,我学习了许多关于 .WAV 文件格式以及声音数据处理技术的知识。理解和实现它并不容易,但最终的结果证明了所付出的努力是值得的。
历史
- 2008 年 9 月 24 日:初始版本