分析wav音频结构实现音频截取、音频二倍速播放、倒播和音频合并(C#实现)

通过文件流读取wav文件放入byte数组,其中byte数组的前44位是存储wav音频文件头信息,如编码格式、声道数和样本速率等信息,网上也有比较多的相关博文,可以参考:

WAV文件头分析

https://blog.csdn.net/hsy12342611/article/details/80075836?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.nonecase

带你分析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;
        }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zxy2847225301

测试使用

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值