6

我目前正在尝试使用此算法对波形文件进行音高转换

https://sites.google.com/site/mikescoderama/pitch-shifting

这是我使用上述实现的代码,但没有运气。输出的波形文件似乎已损坏或无效。

代码很简单,除了音高偏移算法:)

  1. 它加载一个波形文件,读取波形文件数据并将其放入 byte[] 数组中。
  2. 然后它将字节数据“标准化”为 -1.0f 到 1.0f 格式(根据音高移位算法的创建者的要求)。
  3. 它应用音高移位算法,然后将标准化数据转换回 bytes[] 数组。
  4. 最后保存一个与原始波形文件具有相同标题和音高偏移数据的波形文件。

我错过了什么吗?

        static void Main(string[] args)
    {
        // Read the wave file data bytes

        byte[] waveheader = null;
        byte[] wavedata = null;
        using (BinaryReader reader = new BinaryReader(File.OpenRead("sound.wav")))
        {
            // Read first 44 bytes (header);
            waveheader= reader.ReadBytes(44);

            // Read data
            wavedata = reader.ReadBytes((int)reader.BaseStream.Length - 44);
        }

        short nChannels = BitConverter.ToInt16(waveheader, 22);
        int sampleRate = BitConverter.ToInt32(waveheader, 24);
        short bitRate = BitConverter.ToInt16(waveheader, 34);

        // Normalized data store. Store values in the format -1.0 to 1.0
        float[] in_data = new float[wavedata.Length / 2];

        // Normalize wave data into -1.0 to 1.0 values
        using(BinaryReader reader = new BinaryReader(new MemoryStream(wavedata)))
        {
            for (int i = 0; i < in_data.Length; i++)
            {
                if(bitRate == 16)
                    in_data[i] = reader.ReadInt16() / 32768f;

                if (bitRate == 8)                
                    in_data[i] = (reader.ReadByte() - 128) / 128f;
            }
        }

        //PitchShifter.PitchShift(1f, in_data.Length, (long)1024, (long)32, sampleRate, in_data);

        // Backup wave data
        byte[] copydata = new byte[wavedata.Length];
        Array.Copy(wavedata, copydata, wavedata.Length);

        // Revert data to byte format
        Array.Clear(wavedata, 0, wavedata.Length);
        using (BinaryWriter writer = new BinaryWriter(new MemoryStream(wavedata)))
        {
            for (int i = 0; i < in_data.Length; i++)
            {
                if(bitRate == 16)
                    writer.Write((short)(in_data[i] * 32768f));

                if (bitRate == 8)
                    writer.Write((byte)((in_data[i] * 128f) + 128));
            }
        }

        // Compare new wavedata with copydata
        if (wavedata.SequenceEqual(copydata))
        {
            Console.WriteLine("Data has no changes");
        }
        else
        {
            Console.WriteLine("Data has changed!");
        }

        // Save modified wavedata

        string targetFilePath = "sound_low.wav";
        if (File.Exists(targetFilePath))
            File.Delete(targetFilePath);

        using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(targetFilePath)))
        {
            writer.Write(waveheader);
            writer.Write(wavedata);
        }

        Console.ReadLine();
    }
4

2 回答 2

4

这里的算法工作正常

https://sites.google.com/site/mikescoderama/pitch-shifting

我的错误在于我是如何读取波头和波数据的。我在这里发布了完整的工作代码

警告:此代码仅适用于 PCM 16 位(立体声/单声道)波。可以很容易地适应 PCM 8 位。

    static void Main(string[] args)
    {
        // Read header, data and channels as separated data

        // Normalized data stores. Store values in the format -1.0 to 1.0
        byte[] waveheader = null;
        byte[] wavedata = null;

        int sampleRate = 0;

        float[] in_data_l = null;
        float[] in_data_r = null;

        GetWaveData("sound.wav", out waveheader, out wavedata, out sampleRate, out in_data_l, out in_data_r); 

        //
        // Apply Pitch Shifting
        //

        if(in_data_l != null)
            PitchShifter.PitchShift(2f, in_data_l.Length, (long)1024, (long)10, sampleRate, in_data_l);

        if(in_data_r != null)
            PitchShifter.PitchShift(2f, in_data_r.Length, (long)1024, (long)10, sampleRate, in_data_r);

        //
        // Time to save the processed data
        //

        // Backup wave data
        byte[] copydata = new byte[wavedata.Length];
        Array.Copy(wavedata, copydata, wavedata.Length);

        GetWaveData(in_data_l, in_data_r, ref wavedata);

        //
        // Check if data actually changed
        //

        bool noChanges = true;
        for (int i = 0; i < wavedata.Length; i++)
        {
            if (wavedata[i] != copydata[i])
            {
                noChanges = false;
                Console.WriteLine("Data has changed!");
                break;
            }
        }

        if(noChanges)
            Console.WriteLine("Data has no changes");

        // Save modified wavedata

        string targetFilePath = "sound_low.wav";
        if (File.Exists(targetFilePath))
            File.Delete(targetFilePath);

        using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(targetFilePath)))
        {
            writer.Write(waveheader);
            writer.Write(wavedata);
        }

        Console.ReadLine();
    }

    // Returns left and right float arrays. 'right' will be null if sound is mono.
    public static void GetWaveData(string filename, out byte[] header, out byte[] data, out int sampleRate, out float[] left, out float[] right)
    {
        byte[] wav = File.ReadAllBytes(filename);

        // Determine if mono or stereo
        int channels = wav[22];     // Forget byte 23 as 99.999% of WAVs are 1 or 2 channels

        // Get sample rate
        sampleRate = BitConverter.ToInt32(wav, 24);

        int pos = 12;

        // Keep iterating until we find the data chunk (i.e. 64 61 74 61 ...... (i.e. 100 97 116 97 in decimal))
        while(!(wav[pos]==100 && wav[pos+1]==97 && wav[pos+2]==116 && wav[pos+3]==97)) {
            pos += 4;
            int chunkSize = wav[pos] + wav[pos + 1] * 256 + wav[pos + 2] * 65536 + wav[pos + 3] * 16777216;
            pos += 4 + chunkSize;
        }

        pos += 4;

        int subchunk2Size = BitConverter.ToInt32(wav, pos);
        pos += 4;

        // Pos is now positioned to start of actual sound data.
        int samples = subchunk2Size / 2;     // 2 bytes per sample (16 bit sound mono)
        if (channels == 2) 
            samples /= 2;        // 4 bytes per sample (16 bit stereo)

        // Allocate memory (right will be null if only mono sound)
        left = new float[samples];

        if (channels == 2) 
            right = new float[samples];
        else 
            right = null;

        header = new byte[pos];
        Array.Copy(wav, header, pos);

        data = new byte[subchunk2Size];
        Array.Copy(wav, pos, data, 0, subchunk2Size);

        // Write to float array/s:
        int i=0;            
        while (pos < subchunk2Size) 
        {

            left[i] = BytesToNormalized_16(wav[pos], wav[pos + 1]);
            pos += 2;
            if (channels == 2) 
            {
                right[i] = BytesToNormalized_16(wav[pos], wav[pos + 1]);
                pos += 2;
            }
            i++;
        }
    }

    // Return byte data from left and right float data. Ignore right when sound is mono
    public static void GetWaveData(float[] left, float[] right, ref byte[] data)
    {
        // Calculate k
        // This value will be used to convert float to Int16
        // We are not using Int16.Max to avoid peaks due to overflow conversions            
        float k = (float)Int16.MaxValue / left.Select(x => Math.Abs(x)).Max();          

        // Revert data to byte format
        Array.Clear(data, 0, data.Length);
        int dataLenght = left.Length;
        int byteId = -1;
        using (BinaryWriter writer = new BinaryWriter(new MemoryStream(data)))
        {
            for (int i = 0; i < dataLenght; i++)
            {
                byte byte1 = 0;
                byte byte2 = 0;

                byteId++;
                NormalizedToBytes_16(left[i], k, out byte1, out byte2);
                writer.Write(byte1);
                writer.Write(byte2);

                if (right != null)
                {
                    byteId++;
                    NormalizedToBytes_16(right[i], k, out byte1, out byte2);
                    writer.Write(byte1);
                    writer.Write(byte2);                        
                }
            }
        }        
    }

    // Convert two bytes to one double in the range -1 to 1
    static float BytesToNormalized_16(byte firstByte, byte secondByte) 
    {
        // convert two bytes to one short (little endian)
        short s = (short)((secondByte << 8) | firstByte);
        // convert to range from -1 to (just below) 1
        return s / 32678f;
    }

    // Convert a float value into two bytes (use k as conversion value and not Int16.MaxValue to avoid peaks)
    static void NormalizedToBytes_16(float value, float k, out byte firstByte, out byte secondByte)
    {
        short s = (short)(value * k);
        firstByte  = (byte)(s & 0x00FF);
        secondByte = (byte)(s >> 8);
    }
于 2013-10-01T14:48:02.203 回答
0

很抱歉恢复这个,但我尝试了那个音高移位器类,虽然它有效,但我在向下俯仰(0.5f)时音频中出现噼啪声。你有办法解决吗?

于 2014-05-22T13:38:22.573 回答