C# WAV 文件类,音频混合和一些轻量级音频处理






4.92/5 (27投票s)
一个用于读取和写入 WAV 文件、轻量级音频处理以及 WAV 文件混合的 C# 类。
引言
我对音乐的兴趣和爱好之一。最近,我创建了一个 C# 类,可以用来打开、读取和写入 WAV 音频文件。我的 WAVFile
类支持 8 位和 16 位音频,单声道或立体声。它的一个特殊功能是提供一个可以混合 WAV 音频文件的方法,以便可以同时听到每个源 WAV 文件的音频。可以混合任意数量的 WAV 文件。唯一的限制是它们都需要具有相同的采样率。否则,WAV 文件可以是单声道或立体声,包含 8 位或 16 位音频。
所有音频处理和混合都在 WAVFile
类内部完成,不需要任何外部库。
除了 WAVFile
类之外,我还包含了一个用于混合 WAV 音频文件的应用程序。编译后的应用程序和完整源代码包含在文章顶部的链接中。
包含的应用程序
下面是应用程序的屏幕截图
要将 WAV 文件添加到列表中,您可以将文件拖放到 GUI 上,或选择“文件”>“添加音频文件”。
必备组件
编译后的应用程序需要 .NET 运行时版本 3.5。提供的代码是用 Visual C# 2008 创建的;早期版本的 Visual C# 可能无法打开或编译代码。
背景
WAV 音频文件由文件开头的头组成,其中包含标识文件类型的字符串(“RIFF”和“WAVE”),以及有关文件中音频的信息(声道数、采样率、每声道位数、数据大小等)。头后面是所有音频数据。数字音频数据是数值:每个样本是一个整数,表示该时间点的音频信号的电平。
在混合音频(以及一般处理音频)时需要处理的一个问题是 音频削波。数字音频削波是由样本大小(8 位或 16 位)的数值范围限制引起的:当音频样本被处理时(例如,如果音量增加),结果值可能会超出数值范围。发生这种情况时,会产生(通常很响的)爆裂声和咔嗒声,这是不希望的。因此,为了混合 WAV 文件,我的 WAVFile
类中的 MergeAudioFiles()
方法将首先分析每个音频文件以确定最高的音频样本,然后降低所有音频文件的音量,并将它们复制到一个临时目录。音频音量降低的程度足以确保在混合过程中添加音频样本时,不会超出数据的数值范围。混合后,最终混合文件的音量会提高到可接受的水平。在某些情况下,混合过程可能需要一两分钟,这是正常的。
Using the Code
WAVFile
类将是一个用于读取和写入 WAV 音频文件的有用类。该类可以打开、读取和写入 WAV 音频。以下是一些最重要的方法(请注意,它们都将为严重或潜在严重的错误抛出异常)
string Open(String pFilename, WAVFileMode pMode)
: 打开现有的 WAV 文件。返回一个string
,成功时为空,失败时包含失败原因。此方法将为更严重的错误抛出异常。void Create(String pFilename, bool pStereo, int pSampleRate, short pBitsPerSample, bool pOverwrite)
: 创建一个新的 WAV 文件。byte[] GetNextSample_ByteArray()
: 在读取或读/写模式下,此方法将下一个样本作为字节数组返回,对于 8 位音频将包含一个字节,对于 16 位音频将包含两个字节。byte GetNextSample_8bit()
: 这是一个方便的方法,以字节(8 位)的形式返回下一个音频样本。这仅在处理 8 位音频时有效。short GetNextSample_16bit()
: 这是一个方便的方法,以短整型(16 位)的形式返回下一个音频样本。这仅在处理 16 位音频时有效。short GetNextSampleAs16Bit()
: 与上述方法类似,除了它将始终返回一个 16 位值,无论音频是 8 位还是 16 位。此外,如果音频是 8 位,该值将被缩放到 16 位。例如,如果音频是 8 位,那么最大值的 50% 在缩放到 16 位值后仍然是最大值的 50%。byte GetNextSampleAs8Bit()
: 与上述方法类似,无论音频是 8 位还是 16 位,它始终以 8 位值返回下一个样本,并且 16 位值将被缩减到 8 位。AddSample_ByteArray(byte[] pSample)
: 在写入或读/写模式下,此方法将音频样本添加到 WAV 文件。它接受一个字节数组作为参数;对于 8 位音频,数组需要包含一个字节,对于 16 位音频,需要包含两个字节。void AddSample_8bit(byte pSample)
: 一个方便的方法,用于将 8 位值添加到 WAV 文件。WAV 文件必须包含 8 位音频。void AddSample_16bit(short pSample)
: 一个方便的方法,用于将 16 位值添加到 WAV 文件。WAV 文件必须包含 16 位音频。void Close()
: 关闭音频文件。
此外,以下是一些额外的实用程序函数(它们都是静态的)
void MergeAudioFiles(String[] pFileList, String pOutputFilename, String pTempDir)
: 合并音频文件。此方法是静态的。void AdjustVolume_Copy(String pSrcFilename, String pDestFilename, double pMultiplier)
: 调整 WAV 文件的音量,将其复制到新文件。void AdjustVolumeInPlace(String pFilename, double pMultiplier)
: 调整 WAV 文件的音量(会修改原始文件)。void AdjustVolume_Copy_8BitTo16Bit(String pSrcFilename, String pDestFilename, double pMultiplier)
: 调整 WAV 文件的音量,并将 8 位样本转换为 16 位样本,然后将其复制到新文件。void Convert_8BitTo16Bit_Copy(String pSrcFilename, String pDestFilename)
: 将 8 位 WAV 文件转换为 16 位 WAV 文件,然后将其复制到新文件。void CopyAndConvert(String pSrcFilename, String pDestFilename, short pBitsPerSample, bool pStereo)
: 将 WAV 文件复制到新文件,并更改每样本位数和/或声道数。
以下是一些(只读)属性
SampleRateHz
: 音频采样率(以 Hz 为单位)。BitsPerSample
: 每样本位数(即 8 或 16)。NumChannels
: 音频声道数 - 1(单声道)或 2(立体声)。IsStereo
: 布尔值,表示音频是否为立体声(有两个声道)。NumSamples
: 音频样本数。NumSamplesRemaining
: 剩余音频样本数(当读取现有 WAV 文件时)。
一个打开 WAV 文件并循环读取每个音频样本的示例(假设音频文件包含 16 位音频)
WAVFile audioFile = new WAVFile();
String warning = audioFile.Open("someAudioFile.wav", WAVFile.WAVFileMode.READ);
if (warning == "")
{
short audioSample = 0;
for (int sampleNum = 0; sampleNum < audioFile.NumSamples; ++sampleNum)
{
audioSample = audioFile.GetNextSampleAs16Bit();
}
}
MergeAudioFiles()
方法是静态的,因此您无需 WAVFile
实例即可调用它。其参数包括一个字符串数组(源 WAV 文件名)、目标文件名以及可作为临时目录的目录。例如:
// Create an array of filenames for the WAVFile class
String[] audioFilenames = new String[3];
audioFilenames[0] = "SourceFile1.wav";
audioFilenames[1] = "SourceFile2.wav";
audioFilenames[2] = "SourceFile3.wav";
// Mix the audio files together into Output.wav
WAVFile.MergeAudioFiles(audioFilenames, "Output.wav", "C:\\Temp");
异常
以下(在 WAVFileExceptions.cs 中声明的)异常由 WAVFile
类抛出:
WAVFileReadException
: 在读取 WAV 文件时发生问题时抛出。WAVFileWriteException
: 在写入 WAV 文件时发生问题时抛出。WAVFileBitsPerSampleException
: 遇到不支持的每样本位数时抛出。BitsPerSample
属性将包含无效的每样本位数。WAVFileSampleRateException
: 遇到不支持的采样率时抛出。SampleRate
属性将包含无效的采样率。WAVFileAudioMergeException
: 由WAVFile.MergeAudioFiles()
抛出,在混合 WAV 文件时发生问题。WAVFileIOException
: 发生一般性 I/O 问题时抛出。WAVFileException
: 发生一般/未分类问题时抛出。
除了异常类提供的 Message
属性之外,这些异常类还提供另一个属性 ThrowingMethodName
,它是一个 string
,包含抛出异常的方法名称。
关注点
值得注意的是,根据规范,WAV 音频文件中的数据始终是小端序。在大端序系统上,在处理音频数据之前必须反转字节顺序,并且在将样本保存到 WAV 文件之前必须反转样本的字节顺序。我的 WAVFile
类会自动处理此问题;例如,如果系统是大端序,那么在使用 GetNextSample_16bit()
检索 WAV 文件中的音频样本时,或在使用 AddSample_16bit()
将 16 位样本添加到 WAV 文件时,字节顺序将自动反转,以便数据顺序正确。
在创建 WAVFile
类时,有必要查找 WAV 文件格式规范。我找到了许多描述 WAV 文件格式的网页。每个页面基本上都有相同的信息,但带有不同的注释。我发现以下四个页面很有用:
- http://ccrma.stanford.edu/courses/422/projects/WaveFormat
- http://www.sonicspot.com/guide/wavefiles.html
- http://technology.niagarac.on.ca/courses/ctec1631/WavFileFormat.html
- http://www.ringthis.com/dev/wave_format.htm
历史
- 2009 年 4 月 20 日:创建。