通过文件流读取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
上面的链接被码云废掉了,直接去我的仓库下载:https://gitee.com/zxy15914507674/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;
}
音频合并的核心思想是改变头文件表示长度的对应字节的数值,然后把两个音频的字节数组合并成一个字节数组
两个音频轮流播放样本(不是混音,是我瞎折腾的)
C#代码:
/// <summary>
/// 混合两个音频
/// </summary>
/// <param name="srcBuffer1">需要混合的音频1的字节数组1</param>
/// <param name="srcBuffer2">需要混合的音频2的字节数组2</param>
/// <returns>合并后的字节数组</returns>
public byte[] MixedTwoAudio(byte[] srcBuffer1, byte[] srcBuffer2)
{
Console.WriteLine(srcBuffer1[40] + " " + srcBuffer1[41] + " " + srcBuffer1[42] + " " + srcBuffer1[43]);
Console.WriteLine("总长度:" + srcBuffer1.Length);
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];
//}
int src1Index = 44;
int src2Index = 44;
int totalLengthTmp = srcBuffer1.Length < srcBuffer2.Length ? srcBuffer1.Length*2 : srcBuffer2.Length * 2;
for (int i = 44; i < totalLengthTmp; i = i + 8)
{
if (src1Index < srcBuffer1.Length-4)
{
output[i] = srcBuffer1[src1Index];
output[i + 1] = srcBuffer1[src1Index + 1];
output[i + 2] = srcBuffer1[src1Index + 2];
output[i + 3] = srcBuffer1[src1Index + 3];
src1Index = src1Index + 4;
}
}
for (int i = 48; i < totalLengthTmp; i = i + 8)
{
if (src2Index < srcBuffer2.Length-4)
{
output[i] = srcBuffer2[src2Index];
output[i + 1] = srcBuffer2[src2Index + 1];
output[i + 2] = srcBuffer2[src2Index + 2];
output[i + 3] = srcBuffer2[src2Index + 3];
src2Index = src2Index + 4;
}
}
return output;
}