在本文中,将探讨一个用C#编写的WAV音频文件处理类,这个类能够打开、读取和写入WAV音频文件。它支持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()方法将首先分析每个音频文件,以确定最高的音频样本,然后降低所有音频文件的音量,将它们复制到临时目录。降低音量的程度足以确保在混合过程中将音频样本相加时,数据的数值范围不会被超出。混合后,最终混合文件的音量将被调回到可接受的水平。在某些情况下,混合过程可能需要一分钟左右,这是正常的。
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位音频将包含2个字节。
byte GetNextSample_8bit()
: 这是一个方便的方法,返回下一个音频样本作为一个字节(8位)。这只在处理8位音频时有效。
short GetNextSample_16bit()
: 这是一个方便的方法,返回下一个音频样本作为一个short(16位)。这只在处理16位音频时有效。
short GetNextSampleAs16Bit()
: 与上述方法类似,无论音频是8位还是16位,它总是返回一个16位的值。此外,如果音频是8位,该值将被缩放到16位。例如,如果音频是8位,一个最大值50%的值在缩放到16位值后仍然是最大值的50%。
byte GetNextSampleAs8Bit()
: 与上述方法类似,它总是返回下一个样本作为8位值,无论音频是8位还是16位,16位值将被缩减到8位。
AddSample_ByteArray(byte[] pSample)
: 当处于写入或读写模式时,向WAV文件添加一个音频样本。它接受一个字节数组作为参数;该数组需要包含8位音频的一个字节或16位音频的2个字节。
void AddSample_8bit(byte pSample)
: 一个方便的方法,向WAV文件添加一个8位值。WAV文件必须包含8位音频。
void AddSample_16bit(short pSample)
: 一个方便的方法,向WAV文件添加一个16位值。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
: 布尔值,音频是否为立体声(有2个通道)。
NumSamples
: 音频样本数。
NumSamplesRemaining
: 剩余的音频样本数(在读取现有WAV文件时)。
以下是一个打开WAV文件并循环获取每个音频样本的示例(假设音频文件包含16位音频):
C#
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文件名),目标文件名和一个临时目录。例如:
C#
// 创建一个用于WAVFile类的文件名数组
String[] audioFilenames = new String[3];
audioFilenames[0] = "SourceFile1.wav";
audioFilenames[1] = "SourceFile2.wav";
audioFilenames[2] = "SourceFile3.wav";
// 将音频文件混合到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,它是一个包含抛出异常的方法名称的字符串。
值得注意的一点是,根据规范,WAV音频文件中的数据总是小端字节序。在大端字节序的系统上,必须在操作音频数据之前反转字节顺序,并且在将样本保存到WAV文件之前,必须反转样本的字节顺序。WAVFile类会自动处理这个问题;例如,如果系统是大端字节序,那么在使用GetNextSample_16bit()从WAV文件中检索音频样本或使用AddSample_16bit()向WAV文件添加16位样本时,字节顺序将自动反转,以便数据处于正确的顺序。