通過文件流讀取wav文件放入byte數組,其中byte數組的前44位是存儲wav音頻文件頭信息,如編碼格式、聲道數和樣本速率等信息,網上也有比較多的相關博文,可以參考:
WAV文件頭分析
帶你分析wav音頻文件結構(實例+代碼)
https://blog.csdn.net/ljrsunshine/article/details/8932002
先分析鏈接http://soundfile.sapp.org/doc/WaveFormat/中的頭文件結構圖,如下圖:
從上圖中可知:一個樣本佔4個字節,這很重要,從下面的分析就可知,因爲不管是截取音頻還是倒着播放音頻,都不能把一個樣本拆開來處理
本文的Demo源碼下載鏈接:http://zxy15914507674.gitee.io/shared_resource_name/Audio處理.rar
本文的測試環境:
win7
vistual studio 2012
1 音頻截取的業務邏輯分析如下(以截取wav音頻的後半部分爲例):
先把wav文件通過文件流讀取進一個byte[] srcBuffer數組中,同時創建一個寫入輸出文件的byte [] outbuffer數組,長度爲(srcBuffer.Length-44)/2+44,長度爲什麼是這個呢?因爲outbuffer前44個字節必須與srcBuffer的前44字節一樣,不然就破壞wav文件而導致無法播放,然後把srcBuffer數據的後半部分寫入到outbuffer中(注意:srcBufffer開始寫入的位置startIndex必須滿足(startIndex-44)%4=0,因爲一個樣本數據佔4個字節)
2 倒着播放音頻的業務邏輯分析如下圖(頭文件處理和音頻截取的業務邏輯一樣):
3 二倍速播放業務邏輯如下圖(頭文件處理和音頻截取的業務邏輯一樣,至於非整數倍的倍速播放,我還沒有想到辦法):
C#代碼如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioOperation
{
class Program
{
static void Main(string[] args)
{
//讀取音頻文件
FileStream fs = new FileStream("test.wav", FileMode.Open);
byte[] buffer=new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
fs.Close();
new AudioManager().PrintAudioInfo(buffer);
//byte[] outbuffer = new AudioManager().PlayBackForward(buffer);
////輸出文件
//FileStream fw = new FileStream("out.wav", FileMode.Create);
//fw.Write(outbuffer, 0, outbuffer.Length);
//fw.Close();
//Console.WriteLine("輸出完畢");
Console.ReadKey();
}
}
public class AudioManager {
/// <summary>
/// 輸出音頻信息
/// </summary>
/// <param name="srcBuffer"></param>
public void PrintAudioInfo(byte[] srcBuffer)
{
//輸出信息爲 2 0 ,對應十六進制數爲02 00
Console.WriteLine("聲道數信息:"+srcBuffer[22]+" "+srcBuffer[23]);
//輸出信息爲 16 0, 對應十六進制數爲 10 00
Console.WriteLine("每個樣本對應的字節信息:"+srcBuffer[34]+" "+srcBuffer[35]);
}
/// <summary>
/// 截取後半部分音頻
/// </summary>
/// <param name="srcBuffer">輸入的音頻字節數組</param>
/// <returns>後半部分的字節數組</returns>
public byte[] CaptureLastHalfAudio(byte []srcBuffer) {
//輸出字節流
byte[] outbuffer = new byte[(srcBuffer.Length - 44) / 2 + 44];
//讀取頭字節
for (int i = 0; i < 44; i++)
{
outbuffer[i] = srcBuffer[i];
}
int startIndex =(srcBuffer.Length-44 - (srcBuffer.Length - 44) % 4)/2;
for (int i = 44; i < outbuffer.Length; i++)
{
outbuffer[i] = srcBuffer[i + startIndex];
}
return outbuffer;
}
/// <summary>
/// 倒着播放
/// </summary>
/// <param name="buffer">輸入的音頻字節數組</param>
/// <returns>處理完畢後的字節數組</returns>
public byte[] PlayBackForward(byte[] buffer)
{
//輸出字節流
byte[] outbuffer = new byte[buffer.Length];
//讀取頭字節
for (int i = 0; i < 44; i++)
{
outbuffer[i] = buffer[i];
}
int time = (buffer.Length - 44) / 4;
time = time - 1;
for (int i = 44; i <buffer.Length-4; i=i+4)
{
outbuffer[i] = buffer[4*time+44];
outbuffer[i+1] = buffer[4 * time+1+ 44];
outbuffer[i+2] = buffer[4 * time+2+ 44];
outbuffer[i+3] = buffer[4 * time+3+ 44];
time--;
}
return outbuffer;
}
/// <summary>
/// 二倍速播放
/// </summary>
/// <param name="srcBuffer"></param>
/// <returns></returns>
public byte[] SpeedUpPlay(byte[] srcBuffer) {
byte[] outbuffer = new byte[(srcBuffer.Length-44)/2+44];
for (int i = 0; i < 44; i++)
{
outbuffer[i] = srcBuffer[i];
}
int time = 1;
for (int i = 44; i < outbuffer.Length-4; i=i+4)
{
outbuffer[i]=srcBuffer[i+time*4];
outbuffer[i+1] = srcBuffer[i + time * 4+1];
outbuffer[i+2] = srcBuffer[i + time * 4+2];
outbuffer[i+3] = srcBuffer[i + time * 4+3];
time++;
}
return outbuffer;
}
}
}
文件頭信息與代碼中的對應關係:
當然上面處理後的音頻由於沒有對頭信息的長度進行修改,所以截取的音頻和二倍速的音頻的長度信息是不對的,就懶得修改了,免得增加代碼的複雜度
兩個音頻合併的代碼:
/// <summary>
/// 合併兩個音頻
/// </summary>
/// <param name="srcBuffer1">需要合併的音頻1的字節數組1</param>
/// <param name="srcBuffer2">需要合併的音頻2的字節數組2</param>
/// <returns>合併後的字節數組</returns>
public byte[] MergeTwoAudio(byte[] srcBuffer1, byte[] srcBuffer2)
{
byte []output=new byte[srcBuffer1.Length+srcBuffer2.Length-44];
int totalLength = srcBuffer1.Length + srcBuffer2.Length;
int bit41_flag = 0;
int bit42_flag = 0;
int bit43_flag = 0;
int bit40 = 0;
int bit41 = 0;
int bit42 = 0;
int bit43 = 0;
//第40位操作
if ((srcBuffer1[40] + srcBuffer2[40]) < 255)
{
bit40 = srcBuffer1[40] + srcBuffer2[40];
}
else
{
bit40 = (srcBuffer1[40] + srcBuffer2[40]) % 255;
bit41_flag = 1;
}
//操作第41位
if ((srcBuffer1[41] + srcBuffer2[41]+bit41_flag) < 255)
{
bit41 = srcBuffer1[41] + srcBuffer2[41] + bit41_flag;
}
else
{
bit41 = (srcBuffer1[41] + srcBuffer2[41] + bit41_flag) % 255;
bit42_flag = 1;
}
//操作第42位
if ((srcBuffer1[42] + srcBuffer2[42] + bit42_flag) < 255)
{
bit42 = srcBuffer1[42] + srcBuffer2[42] + bit42_flag;
}
else
{
bit42 = (srcBuffer1[42] + srcBuffer2[42] + bit42_flag) % 255;
bit43_flag = 1;
}
//操作第43位
if ((srcBuffer1[43] + srcBuffer2[43]+bit43_flag) < 255)
{
bit43 = srcBuffer1[43] + srcBuffer2[43]+bit43_flag;
}
else
{
bit43 = (srcBuffer1[43] + srcBuffer2[43]+bit43_flag) % 255;
}
for (int i = 0; i <44; i++)
{
if (i < 40)
{
output[i]=srcBuffer1[i];
}
else if (i == 40)
{
output[i] = Convert.ToByte(bit40);
}
else if (i == 41)
{
output[i] = Convert.ToByte(bit41);
}
else if (i == 42)
{
output[i] = Convert.ToByte(bit42);
}
else if (i == 43)
{
output[i] = Convert.ToByte(bit43);
}
}
for (int i = 44; i < srcBuffer1.Length; i++)
{
output[i]=srcBuffer1[i];
}
for (int i = srcBuffer1.Length; i < output.Length; i++)
{
output[i] = srcBuffer2[44 + i - srcBuffer1.Length];
}
return output;
}
音頻合併的核心思想是改變頭文件表示長度的對應字節的數值,然後把兩個音頻的字節數組合併成一個字節數組