分析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

本文的測試環境:

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;
        }

 

音頻合併的核心思想是改變頭文件表示長度的對應字節的數值,然後把兩個音頻的字節數組合併成一個字節數組

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章