反转 .wav 文件

Reversing a .wav file

我正在尝试用 C# 反转 .wav 文件。 我基本上是在读取转发 .wav 文件,然后将 format/meta 数据字节直接复制到一个新的字节数组中。我正在反向复制到新字节数组中的其余 "audio data" 个字节。然后我用这个反转的字节数组创建一个新的流并将它写入一个文件。

但是,当我播放这个反转的.wav文件时,虽然我能听到反转的单词,但音量大了很多,背景噪音也很明显。

如何才能完全反转 .wav 文件,使音量相同且没有背景噪音?

class Program
{
    static void Main(string[] args)
    {
        string localFolder = @"C:\Users\name\folder1";
        string forwardsWavFilePath = Path.Combine(localFolder, "forwardsFile.wav");
        FileStream forwardsWavFileStream = new FileStream(forwardsWavFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
        byte[] forwardsWavFileStreamByteArray = new byte[forwardsWavFileStream.Length];
        int numberOfBytesReadIntoForwardsWavFileStreamByteArray = forwardsWavFileStream.Read(forwardsWavFileStreamByteArray, 0, (int)forwardsWavFileStream.Length);

        int startIndexOfDataChunk = getStartIndexOfDataChunk(forwardsWavFileStreamByteArray);

        byte[] reversedWavFileStreamByteArray = new byte[forwardsWavFileStreamByteArray.Length];

        populateHeaderAndFormatChunk(forwardsWavFileStreamByteArray, startIndexOfDataChunk, reversedWavFileStreamByteArray);

        populateDataChunkInReverse(forwardsWavFileStreamByteArray, startIndexOfDataChunk, reversedWavFileStreamByteArray);

        string reversedWavFilePath = Path.Combine(localFolder, "reversedFile.wav");
        FileStream reversedFileStream = new FileStream(reversedWavFilePath, FileMode.Create, FileAccess.Write, FileShare.Write);
        reversedFileStream.Write(reversedWavFileStreamByteArray, 0, reversedWavFileStreamByteArray.Length);
    }

    private static void populateDataChunkInReverse(byte[] forwardsWavFileStreamByteArray, int startIndexOfAudioData, byte[] reversedWavFileStreamByteArray)
    {
        for (int i = forwardsWavFileStreamByteArray.Length - 1; i >= startIndexOfAudioData; i--)
        {
            reversedWavFileStreamByteArray[forwardsWavFileStreamByteArray.Length - 1 + startIndexOfAudioData - i] = forwardsWavFileStreamByteArray[i];
        }
    }

    private static void populateHeaderAndFormatChunk(byte[] forwardsWavFileStreamByteArray, int startIndexOfAudioData, byte[] reversedWavFileStreamByteArray)
    {
        for (int i = 0; i < startIndexOfAudioData; i++)
        {
            reversedWavFileStreamByteArray[i] = forwardsWavFileStreamByteArray[i];
        }
    }

    private static int getStartIndexOfDataChunk(byte[] forwardsWavFileStreamByteArray)
    {
        int startIndexOfAudioData = 12;
        int charDAsciiDecimalCode = 100; //'d'
        int charAAsciiDecimalCode = 97;  //'a'
        int charTAsciiDecimalCode = 116; //'t'

        //find "data" in the byte array
        while (!(forwardsWavFileStreamByteArray[startIndexOfAudioData] == charDAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 1] == charAAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 2] == charTAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 3] == charAAsciiDecimalCode))
        {
            startIndexOfAudioData += 4;
            int chunkSize = forwardsWavFileStreamByteArray[startIndexOfAudioData] + forwardsWavFileStreamByteArray[startIndexOfAudioData + 1] * 256 + forwardsWavFileStreamByteArray[startIndexOfAudioData + 2] * 65536 + forwardsWavFileStreamByteArray[startIndexOfAudioData + 3] * 16777216;
            startIndexOfAudioData += 4 + chunkSize;
        }
        startIndexOfAudioData += 8;
        return startIndexOfAudioData;
    }
}

您目前只是反转 字节流 - 但您应该做的是反转 样本流 。每个样本可能超过一个字节,因此这会导致失真。您可以从 header.

中找出样本大小和通道数(例如立体声)

有关 header 格式的说明,请参阅 here - 您需要每个样本的位数和通道数。

更新: 我为此发布了一个 NuGet 包: https://www.nuget.org/packages/WaveFileManipulator/

感谢 BrokenGlass 为我指明了正确的方向。 我确实在反转我只是为了反转样本的字节。 我计算出每个样本的字节数,然后反转样本:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace TestWavPlayer
{
    class Program
    {
        const int _bitsPerByte = 8;
        static int _bytesPerSample;

        static void Main(string[] args)
        {
            string localFolder = @"C:\Users\username\AppData\LocalState";

            string forwardsWavFilePath = Path.Combine(localFolder, "forwardsFile.wav");
            byte[] forwardsWavFileStreamByteArray = populateForwardsWavFileByteArray(forwardsWavFilePath);

            getWavMetadata(forwardsWavFileStreamByteArray);

            int startIndexOfDataChunk = getStartIndexOfDataChunk(forwardsWavFileStreamByteArray);

            byte[] reversedWavFileStreamByteArray = populateReversedWavFileByteArray(forwardsWavFileStreamByteArray, startIndexOfDataChunk, _bytesPerSample);

            string reversedWavFilePath = Path.Combine(localFolder, "reversedFile.wav");
            writeReversedWavFileByteArrayToFile(reversedWavFileStreamByteArray, reversedWavFilePath);
        }

        private static void getWavMetadata(byte[] forwardsWavFileStreamByteArray)
        {
            MetadataGatherer.GetRiffText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetFileSize(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetWaveText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetFmtText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetLengthOfFormatData(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetTypeOfFormat(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetNumOfChannels(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetSampleRate(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetBytesPerSecond(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetBlockAlign(forwardsWavFileStreamByteArray);
            _bytesPerSample = MetadataGatherer.GetBitsPerSample(forwardsWavFileStreamByteArray) / _bitsPerByte;
            MetadataGatherer.GetListText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetDataText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetDataSize(forwardsWavFileStreamByteArray);
        }

        private static void writeReversedWavFileByteArrayToFile(byte[] reversedWavFileStreamByteArray, string reversedWavFilePath)
        {
            FileStream reversedFileStream = new FileStream(reversedWavFilePath, FileMode.Create, FileAccess.Write, FileShare.Write);
            reversedFileStream.Write(reversedWavFileStreamByteArray, 0, reversedWavFileStreamByteArray.Length);
        }

        private static byte[] populateReversedWavFileByteArray(byte[] forwardsWavFileStreamByteArray, int startIndexOfDataChunk, int bytesPerSample)
        {
            byte[] forwardsArrayWithOnlyHeaders = createForwardsArrayWithOnlyHeaders(forwardsWavFileStreamByteArray, startIndexOfDataChunk);

            byte[] forwardsArrayWithOnlyAudioData = createForwardsArrayWithOnlyAudioData(forwardsWavFileStreamByteArray, startIndexOfDataChunk);

            byte[] reversedArrayWithOnlyAudioData = reverseTheForwardsArrayWithOnlyAudioData(bytesPerSample, forwardsArrayWithOnlyAudioData);

            byte[] reversedWavFileStreamByteArray = combineArrays(forwardsArrayWithOnlyHeaders, reversedArrayWithOnlyAudioData);

            return reversedWavFileStreamByteArray;
        }

        private static byte[] combineArrays(byte[] forwardsArrayWithOnlyHeaders, byte[] reversedArrayWithOnlyAudioData)
        {
            byte[] reversedWavFileStreamByteArray = new byte[forwardsArrayWithOnlyHeaders.Length + reversedArrayWithOnlyAudioData.Length];
            Array.Copy(forwardsArrayWithOnlyHeaders, reversedWavFileStreamByteArray, forwardsArrayWithOnlyHeaders.Length);
            Array.Copy(reversedArrayWithOnlyAudioData, 0, reversedWavFileStreamByteArray, forwardsArrayWithOnlyHeaders.Length, reversedArrayWithOnlyAudioData.Length);
            return reversedWavFileStreamByteArray;
        }

        private static byte[] reverseTheForwardsArrayWithOnlyAudioData(int bytesPerSample, byte[] forwardsArrayWithOnlyAudioData)
        {
            int length = forwardsArrayWithOnlyAudioData.Length;
            byte[] reversedArrayWithOnlyAudioData = new byte[length];

            int sampleIdentifier = 0;

            for (int i = 0; i < length; i++)
            {
                if (i != 0 && i % bytesPerSample == 0)
                {
                    sampleIdentifier += 2 * bytesPerSample;
                }
                int index = length - bytesPerSample - sampleIdentifier + i;
                reversedArrayWithOnlyAudioData[i] = forwardsArrayWithOnlyAudioData[index];
            }
            return reversedArrayWithOnlyAudioData;
        }

        private static byte[] createForwardsArrayWithOnlyAudioData(byte[] forwardsWavFileStreamByteArray, int startIndexOfDataChunk)
        {
            byte[] forwardsArrayWithOnlyAudioData = new byte[forwardsWavFileStreamByteArray.Length - startIndexOfDataChunk];
            Array.Copy(forwardsWavFileStreamByteArray, startIndexOfDataChunk, forwardsArrayWithOnlyAudioData, 0, forwardsWavFileStreamByteArray.Length - startIndexOfDataChunk);
            return forwardsArrayWithOnlyAudioData;
        }

        private static byte[] createForwardsArrayWithOnlyHeaders(byte[] forwardsWavFileStreamByteArray, int startIndexOfDataChunk)
        {
            byte[] forwardsArrayWithOnlyHeaders = new byte[startIndexOfDataChunk];
            Array.Copy(forwardsWavFileStreamByteArray, 0, forwardsArrayWithOnlyHeaders, 0, startIndexOfDataChunk);
            return forwardsArrayWithOnlyHeaders;
        }

        private static byte[] populateForwardsWavFileByteArray(string forwardsWavFilePath)
        {
            FileStream forwardsWavFileStream = new FileStream(forwardsWavFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            byte[] forwardsWavFileStreamByteArray = new byte[forwardsWavFileStream.Length];
            forwardsWavFileStream.Read(forwardsWavFileStreamByteArray, 0, (int)forwardsWavFileStream.Length);
            return forwardsWavFileStreamByteArray;
        }

        private static int getStartIndexOfDataChunk(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndexOfAudioData = 12;
            int charDAsciiDecimalCode = 100; //'d' //data is located at index 70 in my .wav file
            int charAAsciiDecimalCode = 97;  //'a'
            int charTAsciiDecimalCode = 116; //'t'

            int chunkSize;

            //find "data" in the byte array
            while (!(forwardsWavFileStreamByteArray[startIndexOfAudioData] == charDAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 1] == charAAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 2] == charTAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 3] == charAAsciiDecimalCode))
            {
                startIndexOfAudioData += 4;
                chunkSize = forwardsWavFileStreamByteArray[startIndexOfAudioData] + forwardsWavFileStreamByteArray[startIndexOfAudioData + 1] * 256 + forwardsWavFileStreamByteArray[startIndexOfAudioData + 2] * 65536 + forwardsWavFileStreamByteArray[startIndexOfAudioData + 3] * 16777216;
                startIndexOfAudioData += 4 + chunkSize;
            }
            startIndexOfAudioData += 8;
            return startIndexOfAudioData;
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestWavPlayer
{
    static class MetadataGatherer
    {
        internal static ushort GetTypeOfFormat(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 20;
            int endIndex = 21;
            byte[] typeOfFormatByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, startIndex, endIndex);
            ushort typeOfFormat = BitConverter.ToUInt16(typeOfFormatByteArray, 0);
            Console.WriteLine("Type of format (1 is PCM) = {0}", typeOfFormat);
            return typeOfFormat;
        }

        internal static void GetFmtText(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 12;
            int endIndex = 15;
            GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static string GetWaveText(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 8;
            int endIndex = 11;
            return GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static string GetRiffText(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 0;
            int endIndex = 3;
            return GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static uint GetLengthOfFormatData(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 16;
            int endIndex = 19;
            byte[] lengthOfFormatDataByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, startIndex, endIndex);
            uint lengthOfFormatData = BitConverter.ToUInt32(lengthOfFormatDataByteArray, 0);
            Console.WriteLine("Length of format data = {0}", lengthOfFormatData);
            return lengthOfFormatData;
        }

        internal static byte[] GetRelevantBytesIntoNewArray(byte[] forwardsWavFileStreamByteArray, int startIndex, int endIndex)
        {
            int length = endIndex - startIndex + 1;
            byte[] relevantBytesArray = new byte[length];
            Array.Copy(forwardsWavFileStreamByteArray, startIndex, relevantBytesArray, 0, length);
            return relevantBytesArray;
        }

        internal static uint GetFileSize(byte[] forwardsWavFileStreamByteArray)
        {
            int fileSizeStartIndex = 4;
            int fileSizeEndIndex = 7;
            byte[] fileSizeByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, fileSizeStartIndex, fileSizeEndIndex);
            uint fileSize = BitConverter.ToUInt32(fileSizeByteArray, 0) + 8; //need to add the size of the 
            Console.WriteLine("File size = {0}", fileSize);
            return fileSize;
        }

        internal static string GetAsciiText(byte[] forwardsWavFileStreamByteArray, int startIndex, int endIndex)
        {
            string asciiText = "";
            for (int i = startIndex; i <= endIndex; i++)
            {
                asciiText += Convert.ToChar(forwardsWavFileStreamByteArray[i]);
            }
            Console.WriteLine(asciiText);
            return asciiText;
        }

        internal static ushort GetNumOfChannels(byte[] forwardsWavFileStreamByteArray)
        {
            int numOfChannelsStartIndex = 22;
            int numOfChannelsEndIndex = 23;
            byte[] numOfChannelsByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, numOfChannelsStartIndex, numOfChannelsEndIndex);
            ushort numOfChannels = BitConverter.ToUInt16(numOfChannelsByteArray, 0); //need to add the size of the 
            Console.WriteLine("Number Of Channels = {0}", numOfChannels);
            return numOfChannels;
        }

        internal static uint GetSampleRate(byte[] forwardsWavFileStreamByteArray)
        {
            int sampleRateStartIndex = 24;
            int sampleRateEndIndex = 27;
            byte[] sampleRateByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, sampleRateStartIndex, sampleRateEndIndex);
            uint sampleRate = BitConverter.ToUInt32(sampleRateByteArray, 0); //need to add the size of the 
            Console.WriteLine("Sample Rate = {0}", sampleRate);
            return sampleRate;
        }

        internal static uint GetBytesPerSecond(byte[] forwardsWavFileStreamByteArray)
        {
            int bytesPerSecondStartIndex = 28;
            int bytesPerSecondEndIndex = 31;
            byte[] bytesPerSecondByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, bytesPerSecondStartIndex, bytesPerSecondEndIndex);
            uint bytesPerSecond = BitConverter.ToUInt32(bytesPerSecondByteArray, 0); //need to add the size of the 
            Console.WriteLine("Bytes Per Second = {0}", bytesPerSecond);
            return bytesPerSecond;
        }

        internal static ushort GetBlockAlign(byte[] forwardsWavFileStreamByteArray)
        {
            int blockAlignStartIndex = 32;
            int blockAlignEndIndex = 33;
            byte[] blockAlignByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, blockAlignStartIndex, blockAlignEndIndex);
            ushort blockAlign = BitConverter.ToUInt16(blockAlignByteArray, 0); //need to add the size of the 
            Console.WriteLine("Block Align = {0}", blockAlign);
            return blockAlign;
        }

        internal static ushort GetBitsPerSample(byte[] forwardsWavFileStreamByteArray)
        {
            int bitsPerSampleStartIndex = 34;
            int bitsPerSampleEndIndex = 35;
            byte[] bitsPerSampleByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, bitsPerSampleStartIndex, bitsPerSampleEndIndex);
            ushort bitsPerSample = BitConverter.ToUInt16(bitsPerSampleByteArray, 0); //need to add the size of the 
            Console.WriteLine("Bits Per Sample = {0}", bitsPerSample);
            return bitsPerSample;
        }

        internal static void GetDataText(byte[] forwardsWavFileStreamByteArray)
        {
            //should be these values according to http://www.topherlee.com/software/pcm-tut-wavformat.html
            //int startIndex = 36; //this is the index of "LIST" not "data" :S
            //int endIndex = 39;

            //data is located at index 70 in my .wav file
            int startIndex = 70;
            int endIndex = 73;
            GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static void GetListText(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 36; //this is the index of "LIST"
            int endIndex = 39;
            GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static uint GetDataSize(byte[] forwardsWavFileStreamByteArray)
        {
            int dataSizeStartIndex = 70;
            int dataSizeEndIndex = 73;
            byte[] dataSizeByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, dataSizeStartIndex, dataSizeEndIndex);
            uint dataSize = BitConverter.ToUInt16(dataSizeByteArray, 0); //need to add the size of the 
            Console.WriteLine("Data Size = {0}", dataSize);
            return dataSize;
        }
    }
}

这里tutorial描述了整个过程。