2016-11-17
兩個WAV音樂首尾連接生成一個文件,只要把文件頭分離出來,將Data數據連接就可以了,但多音軌合並確存在着不小的問題,前幾天研究了一下,也參考別人的一些代碼,就出來了這個C#類,在這個C#類中,可以打開、讀、寫WAV音頻文件,支持8位和16位音頻,單聲道或立體聲都可以。主要做的特殊功能之一是可以混合WAV音頻文件。任何WAV文件都可以混合在一起,唯一的限制是,它們都需要有相同的采樣率。
所有的音頻處理和混音都在WAVFile類中進行,不在需要任何外部庫。
class WAVFile
{
/// <summary>
/// 枚舉該類指定的文件操作模式,讀、寫、讀寫
/// </summary>
public enum WAVFileMode
{
READ,
WRITE,
READ_WRITE
}
static WAVFile()
{
mDataStartPos = 44;
}
public WAVFile()
{
InitMembers();
}
/// <summary>
/// 析構 確保文件被關閉
/// </summary>
~WAVFile()
{
Close();
}
/// <summary>
/// 打開一個WAV文件,並讀取文件頭和音頻信息。
/// </summary>
/// <param name="pFilename">要打開的文件名</param>
/// <param name="pMode">打開模式,讀、讀寫,如果只想寫入的話,去看Create().</param>
/// <returns>返回信息</returns>
public String Open(String pFilename, WAVFileMode pMode)
{
mFilename = pFilename;
return (Open(pMode));
}
/// <summary>
/// 打開 mFilename 指定的WAV文件,並讀取文件頭和音頻信息,但不讀取音頻數據。
/// </summary>
/// <param name="pMode">The file opening mode. Only READ and READ_WRITE are valid. If you want to write only, then use Create().</param>
/// <param name="pMode">打開模式,讀、讀寫,如果只想寫入的話,去看Create().</param>
/// <returns>返回信息</returns>
public String Open(WAVFileMode pMode)
{
if (mFileStream != null)
{
mFileStream.Close();
mFileStream.Dispose();
mFileStream = null;
}
String filenameBackup = mFilename;
InitMembers();
if ((pMode != WAVFileMode.READ) && (pMode != WAVFileMode.READ_WRITE))
throw new WAVFileException("File mode not supported: " + WAVFileModeStr(pMode), "WAVFile.Open()");
if (!File.Exists(filenameBackup))
return ("File does not exist: " + filenameBackup);
if (!IsWaveFile(filenameBackup))
return ("File is not a WAV file: " + filenameBackup);
mFilename = filenameBackup;
String retval = "";
try
{
mFileStream = File.Open(mFilename, FileMode.Open);
mFileMode = pMode;
// RIFF chunk (共12字節)
// 文件頭 (前4字節)
byte[] buffer = new byte[4];
mFileStream.Read(buffer, 0, 4);
buffer.CopyTo(mWAVHeader, 0);
// 讀文件大小 (4字節)
mFileStream.Read(buffer, 0, 4);
//mFileSizeBytes = BitConverter.ToInt32(buffer, 0);
// 讀RIFF類型 (4字節)
mFileStream.Read(buffer, 0, 4);
buffer.CopyTo(mRIFFType, 0);
// Format chunk (共24字節)
mFileStream.Read(buffer, 0, 4);
mFileStream.Read(buffer, 0, 4);
mFileStream.Read(buffer, 0, 2);
mFileStream.Read(buffer, 2, 2);
mNumChannels = (BitConverter.IsLittleEndian ? buffer[2] : buffer[3]);
mFileStream.Read(buffer, 0, 4);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer);
mSampleRateHz = BitConverter.ToInt32(buffer, 0);
mFileStream.Read(buffer, 0, 4);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer);
mBytesPerSec = BitConverter.ToInt32(buffer, 0);
mFileStream.Read(buffer, 2, 2);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer, 2, 2);
mBytesPerSample = BitConverter.ToInt16(buffer, 2);
mFileStream.Read(buffer, 2, 2);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer, 2, 2);
mBitsPerSample = BitConverter.ToInt16(buffer, 2);
// Data chunk
mFileStream.Read(buffer, 0, 4);
mFileStream.Read(buffer, 0, 4);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer);
mDataSizeBytes = BitConverter.ToInt32(buffer, 0);
// 前面就是文件頭,如果沒有,直接從下面開始
if (mDataSizeBytes != FileSizeBytes - 36)
mDataSizeBytes = (int)(FileSizeBytes - 36);
// 文件的其余部分是音頻數據,
// 可以讀連續調用到NextSample()。
mNumSamplesRemaining = NumSamples;
}
catch (Exception exc)
{
retval = exc.Message;
}
return (retval);
}
/// <summary>
/// 關閉文件
/// </summary>
public void Close()
{
if (mFileStream != null)
{
// 寫和讀寫模式下,寫入文件頭
if ((mFileMode == WAVFileMode.WRITE) || (mFileMode == WAVFileMode.READ_WRITE))
{
mFileStream.Seek(4, 0);
int size = (int)FileSizeBytes - 8;
byte[] buffer = BitConverter.GetBytes(size);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer);
mFileStream.Write(buffer, 0, 4);
mFileStream.Seek(40, 0);
//mFileStream.Write(BitConverter.GetBytes(mDataBytesWritten), 0, 4);
size = (int)(FileSizeBytes - mDataStartPos);
buffer = BitConverter.GetBytes(size);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer);
mFileStream.Write(buffer, 0, 4);
}
mFileStream.Close();
mFileStream.Dispose();
mFileStream = null;
}
// 重置
InitMembers();
}
public byte[] GetNextSample_ByteArray()
{
byte[] audioSample = null;
if (mFileStream == null)
throw new WAVFileReadException("Read attempted with null internal file stream.", "WAVFile.GetNextSample_ByteArray()");
if ((mFileMode != WAVFileMode.READ) && (mFileMode != WAVFileMode.READ_WRITE))
throw new WAVFileReadException("Read attempted in incorrect mode: " + WAVFileModeStr(mFileMode), "WAVFile.GetNextSample_ByteArray()");
try
{
int numBytes = mBitsPerSample / 8;
audioSample = new byte[numBytes];
mFileStream.Read(audioSample, 0, numBytes);
--mNumSamplesRemaining;
}
catch (Exception exc)
{
audioSample = null;
throw new WAVFileReadException(exc.Message, "WAVFile.GetNextSample_ByteArray()");
}
return audioSample;
}
public byte GetNextSample_8bit()
{
if (mBitsPerSample != 8)
throw new WAVFileReadException("Attempted to retrieve an 8-bit sample when audio is not 8-bit.", "WAVFile.GetNextSample_8bit()");
byte sample8 = 0;
byte[] sample = GetNextSample_ByteArray();
if (sample != null)
sample8 = sample[0];
return sample8;
}
public short GetNextSample_16bit()
{
if (mBitsPerSample != 16)
throw new WAVFileReadException("Attempted to retrieve a 16-bit sample when audio is not 16-bit.", "WAVFile.GetNextSample_16bit()");
short sample16 = 0;
byte[] sample = GetNextSample_ByteArray();
if (sample != null)
sample16 = BitConverter.ToInt16(sample, 0);
return sample16;
}
public short GetNextSampleAs16Bit()
{
short sample_16bit = 0;
if (mBitsPerSample == 8)
sample_16bit = ScaleByteToShort(GetNextSample_8bit());
else if (mBitsPerSample == 16)
sample_16bit = GetNextSample_16bit();
return sample_16bit;
}
public byte GetNextSampleAs8Bit()
{
byte sample_8bit = 0;
if (mBitsPerSample == 8)
sample_8bit = GetNextSample_8bit();
else if (mBitsPerSample == 16)
sample_8bit = ScaleShortToByte(GetNextSample_16bit());
return sample_8bit;
}
public void AddSample_ByteArray(byte[] pSample)
{
if (pSample != null)
{
if ((mFileMode != WAVFileMode.WRITE) && (mFileMode != WAVFileMode.READ_WRITE))
throw new WAVFileWriteException("Write attempted in incorrect mode: " + WAVFileModeStr(mFileMode),
"WAVFile.AddSample_ByteArray()");
if (mFileStream == null)
throw new WAVFileWriteException("Write attempted with null internal file stream.", "WAVFile.AddSample_ByteArray()");
if (pSample.GetLength(0) != (mBitsPerSample / 8))
throw new WAVFileWriteException("Attempt to add an audio sample of incorrect size.", "WAVFile.AddSample_ByteArray()");
try
{
int numBytes = pSample.GetLength(0);
mFileStream.Write(pSample, 0, numBytes);
//mDataBytesWritten += numBytes;
}
catch (Exception exc)
{
throw new WAVFileWriteException(exc.Message, "WAVFile.AddSample_ByteArray()");
}
}
}
public void AddSample_8bit(byte pSample)
{
if (mBitsPerSample != 8)
throw new WAVFileWriteException("Attempted to add an 8-bit sample when audio file is not 8-bit.", "WAVFile.AddSample_8bit()");
byte[] sample = new byte[1];
sample[0] = pSample;
AddSample_ByteArray(sample);
}
public void AddSample_16bit(short pSample)
{
if (mBitsPerSample != 16)
throw new WAVFileWriteException("Attempted to add a 16-bit sample when audio file is not 16-bit.", "WAVFile.AddSample_16bit()");
byte[] buffer = BitConverter.GetBytes(pSample);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer);
AddSample_ByteArray(buffer);
}
/// <summary>
/// 生成一個新的wav文件.
/// </summary>
/// <param name="pFilename">文件名</param>
/// <param name="pStereo">是否為立體聲,如果為false,則輸出單聲道文件</param>
/// <param name="pSampleRate">采樣率</param>
/// <param name="pBitsPerSample">樣本的比特率</param>
/// <param name="pOverwrite">判斷文件是否存在,如果存在就拋出System.IO.IOException</param>
public void Create(String pFilename, bool pStereo, int pSampleRate, short pBitsPerSample, bool pOverwrite)
{
// 如果文件已經打開,就關閉
Close();
// 采樣率如果不支持,拋出異常
if (!SupportedSampleRate(pSampleRate))
throw new WAVFileSampleRateException("Unsupported sample rate: " + pSampleRate.ToString(), "WAVFile.Create()", pSampleRate);
// 比特率如果不支持,拋出異常
if (!SupportedBitsPerSample(pBitsPerSample))
throw new WAVFileBitsPerSampleException("Unsupported number of bits per sample: " + pBitsPerSample.ToString(), "WAVFile.Create()", pBitsPerSample);
try
{
//創建該文件。如果pOverwrite為TRUE,使用FileMode.Create覆蓋該文件
//否則,使用FileMode.CreateNew生成新文件,否則,將拋出一個System.IO.IOException異常。
if (pOverwrite)
mFileStream = File.Open(pFilename, FileMode.Create);
else
mFileStream = File.Open(pFilename, FileMode.CreateNew);
mFileMode = WAVFileMode.WRITE;
mNumChannels = pStereo ? (byte)2 : (byte)1;
mSampleRateHz = pSampleRate;
mBitsPerSample = pBitsPerSample;
// 寫文件頭.
byte[] buffer = StrToByteArray("RIFF");
mFileStream.Write(buffer, 0, 4);
if (mWAVHeader == null)
mWAVHeader = new char[4];
"RIFF".CopyTo(0, mWAVHeader, 0, 4);
Array.Clear(buffer, 0, buffer.GetLength(0));
mFileStream.Write(buffer, 0, 4);
buffer = StrToByteArray("WAVE");
mFileStream.Write(buffer, 0, 4);
if (mRIFFType == null)
mRIFFType = new char[4];
"WAVE".CopyTo(0, mRIFFType, 0, 4);
buffer = StrToByteArray("fmt ");
mFileStream.Write(buffer, 0, 4);
Array.Clear(buffer, 0, buffer.GetLength(0));
buffer[0] = 16;
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer);
mFileStream.Write(buffer, 0, 4);
Array.Clear(buffer, 0, buffer.GetLength(0));
buffer[0] = 1;
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer, 0, 2);
mFileStream.Write(buffer, 0, 2);
Array.Clear(buffer, 0, buffer.GetLength(0));
buffer[0] = mNumChannels;
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer, 0, 2);
mFileStream.Write(buffer, 0, 2);
buffer = BitConverter.GetBytes(mSampleRateHz);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer);
mFileStream.Write(buffer, 0, 4);
short bytesPerSample = 0;
if (pStereo)
bytesPerSample = (short)((mBitsPerSample / 8) * 2);
else
bytesPerSample = (short)(mBitsPerSample / 8);
mBytesPerSec = mSampleRateHz * bytesPerSample;
buffer = BitConverter.GetBytes(mBytesPerSec);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer);
mFileStream.Write(buffer, 0, 4);
byte[] buffer_2bytes = BitConverter.GetBytes(bytesPerSample);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer_2bytes);
mFileStream.Write(buffer_2bytes, 0, 2);
buffer_2bytes = BitConverter.GetBytes(mBitsPerSample);
if (!BitConverter.IsLittleEndian)
Array.Reverse(buffer_2bytes);
mFileStream.Write(buffer_2bytes, 0, 2);
buffer = StrToByteArray("data");
mFileStream.Write(buffer, 0, 4);
Array.Clear(buffer, 0, buffer.GetLength(0));
mFileStream.Write(buffer, 0, 4);
mDataSizeBytes = 0;
}
catch (Exception exc)
{
throw new WAVFileException(exc.Message, "WAVFile.Create()");
}
}
/// <summary>
/// 生成一個wav文件,上面Create()的重載
/// </summary>
public void Create(String pFilename, bool pStereo, int pSampleRate, short pBitsPerSample)
{
Create(pFilename, pStereo, pSampleRate, pBitsPerSample, true);
}
/// <summary>
/// 判斷兩個文件格式是否相同
/// </summary>
/// <param name="pWAVFile">另一個WAV文件</param>
/// <returns></returns>
public bool FormatMatches(WAVFile pWAVFile)
{
bool retval = false;
if (pWAVFile != null)
retval = ((mNumChannels == pWAVFile.mNumChannels) &&
(mSampleRateHz == pWAVFile.mSampleRateHz) &&
(mBitsPerSample == pWAVFile.mBitsPerSample));
return retval;
}
/// <summary>
/// 返回一個WAV文件的WAVFormat結構,包含音頻WAV文件的格式信息(#通道,采樣率,比特率)。
/// </summary>
public static WAVFormat GetAudioFormat(String pFilename)
{
WAVFormat format = new WAVFormat();
WAVFile audioFile = new WAVFile();
if (audioFile.Open(pFilename, WAVFileMode.READ) == "")
{
format.BitsPerSample = audioFile.mBitsPerSample;
format.NumChannels = audioFile.mNumChannels;
format.SampleRateHz = audioFile.mSampleRateHz;
audioFile.Close();
}
return (format);
}
/// <summary>
/// 判斷一個文件是否是WAV文件.
/// </summary>
/// <param name="pFilename">文件名</param>
/// <returns>如果是WAV文件,返回真</returns>
static public bool IsWaveFile(String pFilename)
{
bool retval = false;
if (File.Exists(pFilename))
{
try
{
FileStream fileStream = File.Open(pFilename, FileMode.Open);
char[] fileID = new char[4];
char[] RIFFType = new char[4];
byte[] buffer = new byte[4];
fileStream.Read(buffer, 0, 4);
buffer.CopyTo(fileID, 0);
fileStream.Read(buffer, 0, 4);
fileStream.Read(buffer, 0, 4);
buffer.CopyTo(RIFFType, 0);
fileStream.Close();
String fileIDStr = new String(fileID);
String RIFFTypeStr = new String(RIFFType);
retval = ((fileIDStr == "RIFF") && (RIFFTypeStr == "WAVE"));
}
catch (Exception exc)
{
throw new WAVFileException(exc.Message, "WAVFile.IsWaveFile()");
}
}
return retval;
}
/// <summary>
/// 返回的WAV音頻文件最高的采樣值
/// </summary>
public static byte[] HighestSampleValue(String pFilename, out short pBitsPerSample)
{
pBitsPerSample = 0;
byte[] highestSampleValue = null;
WAVFile audioFile = new WAVFile();
try
{
if (audioFile.Open(pFilename, WAVFileMode.READ) == "")
{
pBitsPerSample = audioFile.mBitsPerSample;
if (audioFile.mBitsPerSample == 8)
{
byte sample = 0;
byte highestSample = 0;
for (int i = 0; i < audioFile.NumSamples; ++i)
{
sample = audioFile.GetNextSample_8bit();
if (sample > highestSample)
highestSample = sample;
}
highestSampleValue = new byte[1];
highestSampleValue[0] = highestSample;
}
else if (audioFile.mBitsPerSample == 16)
{
short sample = 0;
short highestSample = 0;
for (int i = 0; i < audioFile.NumSamples; ++i)
{
sample = audioFile.GetNextSample_16bit();
if (sample > highestSample)
highestSample = sample;
}
highestSampleValue = BitConverter.GetBytes(highestSample);
if (!BitConverter.IsLittleEndian)
Array.Reverse(highestSampleValue);
}
audioFile.Close();
}
}
catch (Exception)
{
}
return (highestSampleValue);
}
public static byte HighestSampleValue_8bit(String pFilename)
{
byte highestSampleVal = 0;
short bitsPerSample;
byte[] highestSample = HighestSampleValue(pFilename, out bitsPerSample);
if (highestSample != null)
{
if (bitsPerSample == 8)
highestSampleVal = highestSample[0];
else
throw new WAVFileReadException("Attempt to get largest 8-bit sample from audio file that is not 8-bit.", "WAVFile.HighestSampleValue_8bit()");
}
return highestSampleVal;
}
public static byte HighestSampleValue_8bit(String[] pFilenames)
{
byte sampleValue = 0;
byte highestSampleValue = 0;
foreach (String filename in pFilenames)
{
sampleValue = HighestSampleValue_8bit(filename);
if (sampleValue > highestSampleValue)
highestSampleValue = sampleValue;
}
return highestSampleValue;
}
public static short HighestSampleValueAs16Bit(String pFilename)
{
short highestSample = 0;
try
{
WAVFile audioFile = new WAVFile();
if (audioFile.Open(pFilename, WAVFileMode.READ) == "")
{
if (audioFile.BitsPerSample == 8)
{
short sample = 0;
for (int i = 0; i < audioFile.NumSamples; ++i)
{
sample = ScaleByteToShort(audioFile.GetNextSample_8bit());
if (sample > highestSample)
highestSample = sample;
}
}
else if (audioFile.BitsPerSample == 16)
{
short sample = 0;
for (int i = 0; i < audioFile.NumSamples; ++i)
{
sample = audioFile.GetNextSample_16bit();
if (sample > highestSample)
highestSample = sample;
}
}
audioFile.Close();
}
}
catch (Exception)
{
}
return highestSample;
}
public static short HighestSampleValueAs16Bit(String[] pFilenames)
{
short highestSampleOverall = 0;
short highestSample = 0;
foreach (String filename in pFilenames)
{
highestSample = HighestSampleValueAs16Bit(filename);
if (highestSample > highestSampleOverall)
highestSampleOverall = highestSample;
}
return highestSampleOverall;
}
public static short HighestBitsPerSample(String[] pFilenames)
{
short bitsPerSample = 0;
if (pFilenames != null)
{
WAVFile audioFile = new WAVFile();
String retval = "";
foreach (String filename in pFilenames)
{
try
{
retval = audioFile.Open(filename, WAVFileMode.READ);
if (retval == "")
{
if (audioFile.BitsPerSample > bitsPerSample)
bitsPerSample = audioFile.BitsPerSample;
audioFile.Close();
}
}
catch (Exception)
{
}
}
}
return bitsPerSample;
}
public static byte HighestNumChannels(String[] pFilenames)
{
byte numChannels = 0;
if (pFilenames != null)
{
WAVFile audioFile = new WAVFile();
String retval = "";
foreach (String filename in pFilenames)
{
try
{
retval = audioFile.Open(filename, WAVFileMode.READ);
if (retval == "")
{
if (audioFile.NumChannels > numChannels)
numChannels = audioFile.NumChannels;
audioFile.Close();
}
}
catch (Exception)
{
}
}
}
return numChannels;
}
/// <summary>
/// 音量調整
/// </summary>
public static void AdjustVolume_Copy(String pSrcFilename, String pDestFilename, double pMultiplier)
{
// 空文件返回錯誤.
if (pSrcFilename == "")
throw new WAVFileReadException("Blank filename specified.", "WAVFile.AdjustVolume_Copy()");
if (pDestFilename == "")
throw new WAVFileWriteException("Blank filename specified.", "WAVFile.AdjustVolume_Copy()");
// 打開一個源文件
WAVFile srcFile = new WAVFile();
String retval = srcFile.Open(pSrcFilename, WAVFileMode.READ);
if (retval == "")
{
// 檢查以確保輸入文件的比特/采樣和采樣率合適。如果不合適,則拋出一個異常.
if (!SupportedBitsPerSample(srcFile.BitsPerSample))
{
WAVFileBitsPerSampleException exc = new WAVFileBitsPerSampleException(pSrcFilename +
" has unsupported bits/sample ("
+ srcFile.BitsPerSample.ToString() + ")",
"WAVFile.AdjustVolume_Copy()", srcFile.BitsPerSample);
srcFile.Close();
throw exc;
}
// 打開並開始調整音量
WAVFile destFile = new WAVFile();
destFile.Create(pDestFilename, srcFile.IsStereo, srcFile.SampleRateHz, srcFile.BitsPerSample);
if (srcFile.BitsPerSample == 8)
{
byte sample = 0;
for (int i = 0; i < srcFile.NumSamples; ++i)
{
sample = srcFile.GetNextSample_8bit();
if (pMultiplier != 1.0)
sample = (byte)((double)sample * pMultiplier);
destFile.AddSample_8bit(sample);
}
}
else if (srcFile.BitsPerSample == 16)
{
short sample = 0;
for (int i = 0; i < srcFile.NumSamples; ++i)
{
sample = srcFile.GetNextSample_16bit();
if (pMultiplier != 1.0)
sample = (short)((double)sample * pMultiplier);
destFile.AddSample_16bit(sample);
}
}
srcFile.Close();
destFile.Close();
}
else
throw new WAVFileReadException(retval, "WAVFile.AdjustVolume_Copy()");
}
public static void AdjustVolumeInPlace(String pFilename, double pMultiplier)
{
if (pMultiplier == 1.0)
return;
WAVFile audioFile = new WAVFile();
String retval = audioFile.Open(pFilename, WAVFileMode.READ_WRITE);
if (retval == "")
{
if (!SupportedBitsPerSample(audioFile.BitsPerSample))
{
short bitsPerSample = audioFile.BitsPerSample;
audioFile.Close();
throw new WAVFileBitsPerSampleException(pFilename + " has unsupported bits/sample ("
+ bitsPerSample.ToString() + ")",
"WAVFile.AdjustVolumeInPlace()", bitsPerSample);
}
if (!SupportedSampleRate(audioFile.SampleRateHz))
{
int sampleRate = audioFile.SampleRateHz;
audioFile.Close();
throw new WAVFileSampleRateException(pFilename + " has unsupported sample rate ("
+ sampleRate.ToString() + ")",
"WAVFile.AdjustVolumeInPlace()", sampleRate);
}
if (audioFile.BitsPerSample == 8)
{
byte sample = 0;
for (int sampleNum = 0; sampleNum < audioFile.NumSamples; ++sampleNum)
{
sample = (byte)((double)audioFile.GetNextSample_8bit() * pMultiplier);
audioFile.SeekToAudioSample(sampleNum);
audioFile.AddSample_8bit(sample);
}
}
else if (audioFile.BitsPerSample == 16)
{
short sample = 0;
for (int sampleNum = 0; sampleNum < audioFile.NumSamples; ++sampleNum)
{
sample = (short)((double)audioFile.GetNextSample_16bit() * pMultiplier);
audioFile.SeekToAudioSample(sampleNum);
audioFile.AddSample_16bit(sample);
}
}
audioFile.Close();
}
else
throw new WAVFileReadException(retval, "WAVFile.AdjustVolumeInPlace()");
}
public static void AdjustVolume_Copy_8BitTo16Bit(String pSrcFilename, String pDestFilename, double pMultiplier)
{
if (pSrcFilename == "")
throw new WAVFileReadException("Blank filename specified.", "WAVFile.AdjustVolume_Copy_8BitTo16Bit()");
if (pDestFilename == "")
throw new WAVFileWriteException("Blank filename specified.", "WAVFile.AdjustVolume_Copy_8BitTo16Bit()");
WAVFile srcFile = new WAVFile();
String retval = srcFile.Open(pSrcFilename, WAVFileMode.READ);
if (retval == "")
{
if (srcFile.BitsPerSample != 8)
{
WAVFileBitsPerSampleException exc = new WAVFileBitsPerSampleException(pSrcFilename +
": 8 bits per sample required, and the file has " +
srcFile.BitsPerSample.ToString() + " bits per sample.",
"WAVFile.AdjustVolume_Copy_8BitTo16Bit()",
srcFile.BitsPerSample);
srcFile.Close();
throw exc;
}
WAVFile destFile = new WAVFile();
destFile.Create(pDestFilename, srcFile.IsStereo, srcFile.SampleRateHz, 16, true);
short sample_16bit = 0;
while (srcFile.NumSamplesRemaining > 0)
{
sample_16bit = ScaleByteToShort(srcFile.GetNextSample_8bit());
if (pMultiplier != 1.0)
sample_16bit = (short)((double)sample_16bit * pMultiplier);
destFile.AddSample_16bit(sample_16bit);
}
srcFile.Close();
destFile.Close();
}
else
throw new WAVFileReadException(retval, "WAVFile.AdjustVolume_Copy_8BitTo16Bit()");
}
public static void Convert_8BitTo16Bit_Copy(String pSrcFilename, String pDestFilename)
{
AdjustVolume_Copy_8BitTo16Bit(pSrcFilename, pDestFilename, 1.0);
}
/// <summary>
/// 轉換WAV文件的比特/采樣和采樣率,通道等到另一個WAV文件. 重載
/// </summary>
/// <param name="pSrcFilename">要轉換的文件名</param>
/// <param name="pDestFilename">目標文件</param>
/// <param name="pBitsPerSample">目標文件的比特/采樣和采樣率</param>
/// <param name="pStereo">目標文件是否立體聲</param>
public static void CopyAndConvert(String pSrcFilename, String pDestFilename, short pBitsPerSample, bool pStereo)
{
CopyAndConvert(pSrcFilename, pDestFilename, pBitsPerSample, pStereo, 1.0);
}
/// <summary>
/// 轉換WAV文件的比特/采樣和采樣率,通道等到另一個WAV文件.
/// </summary>
/// <param name="pSrcFilename">要轉換的文件名</param>
/// <param name="pDestFilename">目標文件</param>
/// <param name="pBitsPerSample">目標文件的比特/采樣和采樣率</param>
/// <param name="pStereo">目標文件是否立體聲</param>
/// <param name="pVolumeMultiplier">目標是否要調整音量</param>
public static void CopyAndConvert(String pSrcFilename, String pDestFilename, short pBitsPerSample, bool pStereo, double pVolumeMultiplier)
{
WAVFile srcFile = new WAVFile();
String retval = srcFile.Open(pSrcFilename, WAVFileMode.READ);
if (retval != "")
throw new WAVFileException(retval, "WAVFile.Convert_Copy()");
WAVFile destFile = new WAVFile();
destFile.Create(pDestFilename, pStereo, srcFile.SampleRateHz, pBitsPerSample);
if ((srcFile.BitsPerSample == 8) && (pBitsPerSample == 8))
{
byte sample = 0;
if (srcFile.IsStereo && !pStereo)
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = (byte)((short)((short)srcFile.GetNextSample_8bit() + (short)srcFile.GetNextSample_8bit()) / 2);
if (pVolumeMultiplier != 1.0)
sample = (byte)((double)sample * pVolumeMultiplier);
destFile.AddSample_8bit(sample);
}
}
else if ((srcFile.IsStereo && pStereo) || (!srcFile.IsStereo && !pStereo))
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = srcFile.GetNextSample_8bit();
if (pVolumeMultiplier != 1.0)
sample = (byte)((double)sample * pVolumeMultiplier);
destFile.AddSample_8bit(sample);
}
}
else if (!srcFile.IsStereo && pStereo)
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = srcFile.GetNextSample_8bit();
if (pVolumeMultiplier != 1.0)
sample = (byte)((double)sample * pVolumeMultiplier);
destFile.AddSample_8bit(sample);
destFile.AddSample_8bit(sample);
}
}
}
else if ((srcFile.BitsPerSample == 8) && (pBitsPerSample == 16))
{
short sample = 0;
if (srcFile.IsStereo && !pStereo)
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = (short)((int)((int)srcFile.GetNextSampleAs16Bit() + (int)srcFile.GetNextSampleAs16Bit()) / 2);
if (pVolumeMultiplier != 1.0)
sample = (short)((double)sample * pVolumeMultiplier);
destFile.AddSample_16bit(sample);
}
}
else if ((srcFile.IsStereo && pStereo) || (!srcFile.IsStereo && !pStereo))
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = srcFile.GetNextSampleAs16Bit();
if (pVolumeMultiplier != 1.0)
sample = (short)((double)sample * pVolumeMultiplier);
destFile.AddSample_16bit(sample);
}
}
else if (!srcFile.IsStereo && pStereo)
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = srcFile.GetNextSampleAs16Bit();
if (pVolumeMultiplier != 1.0)
sample = (short)((double)sample * pVolumeMultiplier);
destFile.AddSample_16bit(sample);
destFile.AddSample_16bit(sample);
}
}
}
else if ((srcFile.BitsPerSample == 16) && (pBitsPerSample == 8))
{
byte sample = 0;
if (srcFile.IsStereo && !pStereo)
{
short sample_16bit = 0;
while (srcFile.NumSamplesRemaining > 0)
{
sample_16bit = (short)((int)srcFile.GetNextSample_16bit() + (int)srcFile.GetNextSample_16bit() / 2);
if (pVolumeMultiplier != 1.0)
sample_16bit = (short)((double)sample_16bit * pVolumeMultiplier);
sample = ScaleShortToByte(sample_16bit);
destFile.AddSample_8bit(sample);
}
}
else if ((srcFile.IsStereo && pStereo) || (!srcFile.IsStereo && !pStereo))
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = ScaleShortToByte(srcFile.GetNextSample_16bit());
if (pVolumeMultiplier != 1.0)
sample = (byte)((double)sample * pVolumeMultiplier);
destFile.AddSample_8bit(sample);
}
}
else if (!srcFile.IsStereo && pStereo)
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = ScaleShortToByte(srcFile.GetNextSample_16bit());
if (pVolumeMultiplier != 1.0)
sample = (byte)((double)sample * pVolumeMultiplier);
destFile.AddSample_8bit(sample);
destFile.AddSample_8bit(sample);
}
}
}
else if ((srcFile.BitsPerSample == 16) && (pBitsPerSample == 16))
{
short sample = 0;
if (srcFile.IsStereo && !pStereo)
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = (short)((int)((int)srcFile.GetNextSample_16bit() + (int)srcFile.GetNextSample_16bit()) / 2);
if (pVolumeMultiplier != 1.0)
sample = (short)((double)sample * pVolumeMultiplier);
destFile.AddSample_16bit(sample);
}
}
else if ((srcFile.IsStereo && pStereo) || (!srcFile.IsStereo && !pStereo))
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = srcFile.GetNextSample_16bit();
if (pVolumeMultiplier != 1.0)
sample = (short)((double)sample * pVolumeMultiplier);
destFile.AddSample_16bit(sample);
}
}
else if (!srcFile.IsStereo && pStereo)
{
while (srcFile.NumSamplesRemaining > 0)
{
sample = srcFile.GetNextSample_16bit();
if (pVolumeMultiplier != 1.0)
sample = (short)((double)sample * pVolumeMultiplier);
destFile.AddSample_16bit(sample);
destFile.AddSample_16bit(sample);
}
}
}
destFile.Close();
srcFile.Close();
}
/// <summary>
/// 采樣率是否支持
/// </summary>
public static bool SupportedSampleRate(int pSampleRateHz)
{
return ((pSampleRateHz == 8000) || (pSampleRateHz == 11025) ||
(pSampleRateHz == 16000) || (pSampleRateHz == 18900) ||
(pSampleRateHz == 22050) || (pSampleRateHz == 32000) ||
(pSampleRateHz == 37800) || (pSampleRateHz == 44056) ||
(pSampleRateHz == 44100) || (pSampleRateHz == 48000));
}
/// <summary>
/// 比特率是否支持.
/// </summary>
public static bool SupportedBitsPerSample(short pBitsPerSample)
{
return ((pBitsPerSample == 8) || (pBitsPerSample == 16));
}
/// <summary>
/// 合並音樂文件
/// </summary>
/// <param name="pFileList">要合並的文件的數組</param>
/// <param name="pOutputFilename">輸出文件名</param>
/// <param name="pTempDir">用來存儲文件的臨時目錄,如果沒有則創建,用完后刪除</param>
public static void MergeAudioFiles(String[] pFileList, String pOutputFilename, String pTempDir)
{
if (pFileList == null)
return;
if (pFileList.GetLength(0) == 0)
return;
// 確保所有的音頻文件采樣率相同。如果采樣率不匹配,則拋出一個異常。
if (!SampleRatesEqual(pFileList))
throw new WAVFileAudioMergeException("The sample rates of the audio files differ.", "WAVFile.MergeAudioFiles()");
// 1、檢查是否格式相同
WAVFormat firstFileAudioFormat = GetAudioFormat(pFileList[0]);
if (!SupportedBitsPerSample(firstFileAudioFormat.BitsPerSample))
throw new WAVFileBitsPerSampleException("Unsupported number of bits per sample: " + firstFileAudioFormat.BitsPerSample.ToString(), "WAVFile.MergeAudioFiles()", firstFileAudioFormat.BitsPerSample);
if (!SupportedSampleRate(firstFileAudioFormat.SampleRateHz))
throw new WAVFileSampleRateException("Unsupported sample rate: " + firstFileAudioFormat.SampleRateHz.ToString(), "WAVFile.MergeAudioFiles()", firstFileAudioFormat.SampleRateHz);
// 2、創建臨時目錄
bool tempDirExisted = Directory.Exists(pTempDir);
if (!tempDirExisted)
{
try
{
Directory.CreateDirectory(pTempDir);
if (!Directory.Exists(pTempDir))
throw new WAVFileAudioMergeException("Unable to create temporary work directory (" + pTempDir + ")", "WAVFile.MergeAudioFiles()");
}
catch (System.Exception exc)
{
throw new WAVFileAudioMergeException("Unable to create temporary work directory (" + pTempDir + "): "
+ exc.Message, "WAVFile.MergeAudioFiles()");
}
}
// 3. 找到所有文件的采樣值,並計算基數
int numTracks = pFileList.GetLength(0);
double multiplier = 0.0;
short highestSampleValue = 0;
short highestBitsPerSample = HighestBitsPerSample(pFileList);
bool outputStereo = (HighestNumChannels(pFileList) > 1);
if (highestBitsPerSample == 8)
{
//獲取所有文件的采樣值,8位
byte highestSample = HighestSampleValue_8bit(pFileList);
highestSampleValue = (short)highestSample;
byte difference = (byte)(highestSample - (byte.MaxValue / (byte)numTracks));
multiplier = 1.0 - ((double)difference / (double)highestSample);
}
else if (highestBitsPerSample == 16)
{
//獲取所有文件的采樣值,16位
highestSampleValue = HighestSampleValueAs16Bit(pFileList);
short difference = (short)(highestSampleValue - (short.MaxValue / (short)numTracks));
multiplier = 1.0 - ((double)difference / (double)highestSampleValue);
}
if (double.IsInfinity(multiplier) || (multiplier == 0.0))
{
// 如果臨時目錄存在,刪除
if (!tempDirExisted)
DeleteDir(pTempDir);
throw new WAVFileAudioMergeException("Could not calculate first volume multiplier.", "WAVFile.MergeAudioFiles()");
}
if (multiplier < 0.0)
multiplier = -multiplier;
// 4. 縮減源文件的音頻水平,並輸出保存在臨時目錄中。
WAVFile[] scaledAudioFiles = new WAVFile[pFileList.GetLength(0)];
String filename = "";
WAVFile inputFile = new WAVFile();
WAVFile outputFile = new WAVFile();
for (int i = 0; i < pFileList.GetLength(0); ++i)
{
// pFileList[i] 包含文件的全路徑名.
filename = pTempDir + "\" + Path.GetFileName(pFileList[i]);
// 復制文件到臨時目錄,調整比特率、采樣及通道。
CopyAndConvert(pFileList[i], filename, highestBitsPerSample, outputStereo, multiplier);
// 創建在scaledAudioFiles序列中的WAVFile的對象,並打開。
scaledAudioFiles[i] = new WAVFile();
scaledAudioFiles[i].Open(filename, WAVFileMode.READ);
}
// 5、 現在生成音頻混音的文件輸出
outputFile.Create(pOutputFilename, outputStereo, firstFileAudioFormat.SampleRateHz,
highestBitsPerSample);
// 6. 合並
if (highestBitsPerSample == 8)
{
byte sample = 0;
while (SamplesRemain(scaledAudioFiles))
{
sample = 0;
for (int i = 0; i < scaledAudioFiles.GetLength(0); ++i)
{
if (scaledAudioFiles[i].NumSamplesRemaining > 0)
sample += scaledAudioFiles[i].GetNextSample_8bit();
}
sample /= (byte)(scaledAudioFiles.GetLength(0));
outputFile.AddSample_8bit(sample);
}
}
else if (highestBitsPerSample == 16)
{
short sample = 0;
while (SamplesRemain(scaledAudioFiles))
{
sample = 0;
for (int i = 0; i < scaledAudioFiles.GetLength(0); ++i)
{
if (scaledAudioFiles[i].NumSamplesRemaining > 0)
sample += scaledAudioFiles[i].GetNextSampleAs16Bit();
}
sample /= (short)(scaledAudioFiles.GetLength(0));
outputFile.AddSample_16bit(sample);
}
}
outputFile.Close();
// 7. 刪除輸入文件,釋放磁盤空間
foreach (WAVFile audioFile in scaledAudioFiles)
{
filename = audioFile.Filename;
audioFile.Close();
File.Delete(filename);
}
// 8. 調整輸出文件的音量
if (highestBitsPerSample == 8)
{
byte highestSampleVal = WAVFile.HighestSampleValue_8bit(pOutputFilename);
byte maxValue = byte.MaxValue / 4 * 3;
multiplier = (double)maxValue / (double)highestSampleVal;
}
else if (highestBitsPerSample == 16)
{
short finalMixFileHighestSample = WAVFile.HighestSampleValueAs16Bit(pOutputFilename);
multiplier = (double)short.MaxValue / (double)finalMixFileHighestSample;
}
if (multiplier < 0.0)
multiplier = -multiplier;
AdjustVolumeInPlace(pOutputFilename, multiplier);
// 如果臨時目錄存在,刪除它
if (!tempDirExisted)
{
String retval = DeleteDir(pTempDir);
if (retval != "")
throw new WAVFileAudioMergeException("Unable to remove temp directory (" + pTempDir + "): " + retval,
"WAVFile.MergeAudioFiles()");
}
}
/// <summary>
/// 移動指針,返回音頻數據的開始。
/// </summary>
public void SeekToAudioDataStart()
{
SeekToAudioSample(0);
// 在讀和讀寫模式下,更新mNumSamplesRemaining.
if ((mFileMode == WAVFileMode.READ) || (mFileMode == WAVFileMode.READ_WRITE))
mNumSamplesRemaining = NumSamples;
}
/// <summary>
/// 移動文件指針到一個給定的音頻樣本
/// </summary>
/// <param name="pSampleNum">移動文件指針的樣本數</param>
public void SeekToAudioSample(long pSampleNum)
{
if (mFileStream != null)
{
long bytesPerSample = mBitsPerSample / 8;
try
{
mFileStream.Seek(mDataStartPos + (bytesPerSample * pSampleNum), 0);
}
catch (System.IO.IOException exc)
{
throw new WAVFileIOException("Unable to to seek to sample " + pSampleNum.ToString() + ": " + exc.Message,
"WAVFile.SeekToAudioSample()");
}
catch (System.NotSupportedException exc)
{
throw new WAVFileIOException("Unable to to seek to sample " + pSampleNum.ToString() + ": " + exc.Message,
"WAVFile.SeekToAudioSample()");
}
catch (Exception exc)
{
throw new WAVFileException(exc.Message, "WAVFile.SeekToAudioSample()");
}
}
}
public static String WAVFileModeStr(WAVFileMode pMode)
{
String retval = "";
switch (pMode)
{
case WAVFileMode.READ:
retval = "READ";
break;
case WAVFileMode.WRITE:
retval = "WRITE";
break;
case WAVFileMode.READ_WRITE:
retval = "READ_WRITE";
break;
default:
retval = "Unknown";
break;
}
return retval;
}
public String Filename
{
get { return mFilename; }
}
public char[] WAVHeader
{
get { return mWAVHeader; }
}
public String WAVHeaderString
{
get
{
return new String(mWAVHeader);
}
}
public char[] RIFFType
{
get { return mRIFFType; }
}
public String RIFFTypeString
{
get
{
return new String(mRIFFType);
}
}
public byte NumChannels
{
get { return mNumChannels; }
}
public bool IsStereo
{
get { return (mNumChannels == 2); }
}
public int SampleRateHz
{
get { return mSampleRateHz; }
}
public int BytesPerSec
{
get { return mBytesPerSec; }
}
public short BytesPerSample
{
get { return mBytesPerSample; }
}
public short BitsPerSample
{
get { return mBitsPerSample; }
}
public int DataSizeBytes
{
get { return mDataSizeBytes; }
}
public long FileSizeBytes
{
get { return mFileStream.Length; }
}
public int NumSamples
{
get { return (mDataSizeBytes / (int)(mBitsPerSample / 8)); }
}
public int NumSamplesRemaining
{
get { return mNumSamplesRemaining; }
}
public WAVFileMode FileOpenMode
{
get { return mFileMode; }
}
public WAVFormat AudioFormat
{
get
{
return (new WAVFormat(mNumChannels, mSampleRateHz, mBitsPerSample));
}
}
public long FilePosition
{
get { return (mFileStream != null ? mFileStream.Position : 0); }
}
private void InitMembers()
{
mFilename = null;
mFileStream = null;
mWAVHeader = new char[4];
mDataSizeBytes = 0;
mBytesPerSample = 0;
mRIFFType = new char[4];
mNumChannels = 2;
mSampleRateHz = 44100;
mBytesPerSec = 176400;
mBitsPerSample = 16;
mFileMode = WAVFileMode.READ;
mNumSamplesRemaining = 0;
}
private static bool SampleRatesEqual(String[] pFileList)
{
bool sampleRatesMatch = true;
if (pFileList != null)
{
int numFiles = pFileList.GetLength(0);
if (numFiles > 1)
{
int firstSampleRate = GetAudioFormat(pFileList[0]).SampleRateHz;
for (int i = 1; i < numFiles; ++i)
{
if (GetAudioFormat(pFileList[i]).SampleRateHz != firstSampleRate)
{
sampleRatesMatch = false;
break;
}
}
}
}
else
sampleRatesMatch = false;
return sampleRatesMatch;
}
static private bool SamplesRemain(WAVFile[] WAVFileArray)
{
bool samplesRemain = false;
if (WAVFileArray != null)
{
for (int i = 0; i < WAVFileArray.GetLength(0); ++i)
{
if (WAVFileArray[i].NumSamplesRemaining > 0)
{
samplesRemain = true;
break;
}
}
}
return (samplesRemain);
}
/// <summary>
/// 移除目錄,MergeAudioFiles()中進行調用.
/// </summary>
private static String DeleteDir(String pPath)
{
String retval = "";
if (Directory.Exists(pPath))
{
try
{
// Delete all the files
String[] filenames = Directory.GetFiles(pPath);
foreach (String filename in filenames)
File.Delete(filename);
// Delete the directory
Directory.Delete(pPath, true);
}
catch (System.Exception exc)
{
retval = exc.Message;
}
}
return retval;
}
private static byte[] StrToByteArray(String pStr)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
return encoding.GetBytes(pStr);
}
private static short ScaleByteToShort(byte pByteVal)
{
short val_16bit = 0;
double scaleMultiplier = 0.0;
if (pByteVal > 0)
{
scaleMultiplier = (double)pByteVal / (double)byte.MaxValue;
val_16bit = (short)((double)short.MaxValue * scaleMultiplier);
}
else if (pByteVal < 0)
{
scaleMultiplier = (double)pByteVal / (double)byte.MinValue;
val_16bit = (short)((double)short.MinValue * scaleMultiplier);
}
return val_16bit;
}
private static byte ScaleShortToByte(short pShortVal)
{
byte val_8bit = 0;
double scaleMultiplier = 0.0;
if (pShortVal > 0)
{
scaleMultiplier = (double)pShortVal / (double)short.MaxValue;
val_8bit = (byte)((double)byte.MaxValue * scaleMultiplier);
}
else if (pShortVal < 0)
{
scaleMultiplier = (double)pShortVal / (double)short.MinValue;
val_8bit = (byte)((double)byte.MinValue * scaleMultiplier);
}
return val_8bit;
}
private String mFilename;
private FileStream mFileStream;
// File header information
private char[] mWAVHeader;
//private int mFileSizeBytes;
private char[] mRIFFType;
// Audio format information
private byte mNumChannels;
private int mSampleRateHz;
private int mBytesPerSec;
private short mBytesPerSample;
private short mBitsPerSample;
private int mDataSizeBytes;
private WAVFileMode mFileMode;
private int mNumSamplesRemaining;
private static short mDataStartPos;
}
出處:http://blog.sina.com.cn/s/blog_45ef30330101131y.html