【问题标题】:How to read the data in a wav file to an array如何将wav文件中的数据读入数组
【发布时间】:2012-02-03 23:06:14
【问题描述】:

我需要将 wav 文件的所有样本放入一个数组(或两个,如果您需要这样做以保持立体声),以便我可以对它们进行一些修改。我想知道这是否容易完成(最好没有外部库)。我没有阅读声音文件的经验,所以我对这个主题了解不多。

【问题讨论】:

  • 为什么不想使用库?如果是许可问题,请查找 LGPL 或类似许可。如果不是,那么,.NET 是基于库构建的,因此您不必自己编写所有代码。安装 NuGet,获取 NAudio(或其他音频库),不要重新发明轮子 :)。或者在 C 中执行 ;)
  • 为什么重新发明轮子是一件坏事?您不仅会为自己解决问题而感觉良好,而且在此过程中您也会学到很多东西。如果您是为了好玩而编码并且没有时间限制,那么我会说肯定要重新发明轮子!

标签: c# file-io audio


【解决方案1】:

这段代码应该可以解决问题。它将波形文件转换为规范化双精度数组(-1 到 1),但将其改为 int/short 数组应该很简单(删除/32768.0 位并添加 32768)。如果发现加载的 wav 文件是单声道的,right[] 数组将被设置为 null。

我不能声称它是完全防弹的(潜在的错误),但是在创建 65536 样本数组并创建从 -1 到 1 的波之后,没有一个样本似乎“通过”天花板或地板。

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

// Returns left and right double arrays. 'right' will be null if sound is mono.
public void openWav(string filename, out double[] left, out double[] 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 past all the other sub chunks to get to the data subchunk:
    int pos = 12;   // First Subchunk ID from 12 to 16

    // 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 += 8;

    // Pos is now positioned to start of actual sound data.
    int samples = (wav.Length - pos)/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 double[samples];
    if (channels == 2) right = new double[samples];
    else right = null;

    // Write to double array/s:
    int i=0;
    while (pos < length) {
        left[i] = bytesToDouble(wav[pos], wav[pos + 1]);
        pos += 2;
        if (channels == 2) {
            right[i] = bytesToDouble(wav[pos], wav[pos + 1]);
            pos += 2;
        }
        i++;
    }
}

【讨论】:

  • 如果我错了,请有人纠正我。你不能对像byte 这样的8 位类型执行(secondByte &lt;&lt; 8)byte 是一个 8 位有符号整数,不能将其位移 8 位,因为没有空间来推动这 8 位。
  • @MartinBerger 字节在大多数情况下会自动提升为 int,包括移位。
  • @Clément 所以在移位期间,变量被提升为平台特定的 int,通常是 32 位?不知道,谢谢。
  • @MartinBerger 是的,请参阅msdn.microsoft.com/en-us/library/aa691330(v=vs.71).aspx,尤其是最后一条规则:Otherwise, both operands are converted to type int.
  • @MartinBerger 仅供参考,没有“平台特定的 int”之类的东西。 C# 的 int(映射到 .NET 中的 Int32 结构)总是 32 位。
【解决方案2】:

假设您的 WAV 文件包含 16 位 PCM(这是最常见的),您可以使用 NAudio 将其读取到字节数组中,然后将其复制到 16 位整数数组中以方便使用。如果是立体声,样本会左右交错。

using (WaveFileReader reader = new WaveFileReader("myfile.wav"))
{
    Assert.AreEqual(16, reader.WaveFormat.BitsPerSample, "Only works with 16 bit audio");
    byte[] buffer = new byte[reader.Length];
    int read = reader.Read(buffer, 0, buffer.Length);
    short[] sampleBuffer = new short[read / 2];
    Buffer.BlockCopy(buffer, 0, sampleBuffer, 0, read);
}

我知道您想避免使用第三方库,但如果您想确保处理带有额外块的 WAV 文件,我建议避免使用仅在文件中查找 44 字节的方法。

【讨论】:

    【解决方案3】:

    在撰写本文时,没有人处理过 32 位或 64 位编码的 WAV。

    以下代码处理 16/32/64 位和单声道/立体声:

    static bool readWav( string filename, out float[] L, out float[] R )
    {
        L = R = null;
    
        try {
            using (FileStream fs = File.Open(filename,FileMode.Open))
            {
                BinaryReader reader = new BinaryReader(fs);
    
                // chunk 0
                int chunkID       = reader.ReadInt32();
                int fileSize      = reader.ReadInt32();
                int riffType      = reader.ReadInt32();
    
    
                // chunk 1
                int fmtID         = reader.ReadInt32();
                int fmtSize       = reader.ReadInt32(); // bytes for this chunk (expect 16 or 18)
    
                // 16 bytes coming...
                int fmtCode       = reader.ReadInt16();
                int channels      = reader.ReadInt16();
                int sampleRate    = reader.ReadInt32();
                int byteRate      = reader.ReadInt32();
                int fmtBlockAlign = reader.ReadInt16();
                int bitDepth      = reader.ReadInt16();
    
                if (fmtSize == 18)
                {
                    // Read any extra values
                    int fmtExtraSize = reader.ReadInt16();
                    reader.ReadBytes(fmtExtraSize);
                }
    
                // chunk 2
                int dataID = reader.ReadInt32();
                int bytes = reader.ReadInt32();
    
                // DATA!
                byte[] byteArray = reader.ReadBytes(bytes);
    
                int bytesForSamp = bitDepth/8;
                int nValues = bytes / bytesForSamp;
    
    
                float[] asFloat = null;
                switch( bitDepth ) {
                    case 64:
                        double[] 
                            asDouble = new double[nValues];  
                        Buffer.BlockCopy(byteArray, 0, asDouble, 0, bytes);
                        asFloat = Array.ConvertAll( asDouble, e => (float)e );
                        break;
                    case 32:
                        asFloat = new float[nValues];   
                        Buffer.BlockCopy(byteArray, 0, asFloat, 0, bytes);
                        break;
                    case 16:
                        Int16 [] 
                            asInt16 = new Int16[nValues];   
                        Buffer.BlockCopy(byteArray, 0, asInt16, 0, bytes);
                        asFloat = Array.ConvertAll( asInt16, e => e / (float)(Int16.MaxValue+1) );
                        break;
                    default:
                        return false;
                }
    
                switch( channels ) {
                case 1:
                    L = asFloat;
                    R = null;
                    return true;
                case 2:
                    // de-interleave
                    int nSamps = nValues / 2;
                    L = new float[nSamps];
                    R = new float[nSamps];
                    for( int s=0, v=0; s<nSamps; s++ ) {
                        L[s] = asFloat[v++];
                        R[s] = asFloat[v++];
                    }
                    return true;
                default:
                    return false;
                }
            }
        }
        catch {
                Debug.Log( "...Failed to load: " + filename );
                return false;
        }
    
        return false;
    }
    

    【讨论】:

    • L 和 R 的长度不应该是 (samps / 2),因为 asFloat 的大小是 samps?
    • oo 我认为你是对的。另外我应该使用 Float32 和 Float64 而不是 float 和 double。
    • 我认为 float 和 double 是对的。 C# 没有 Float32 和 Float64,而是 System.Single 和 System.Double。无论如何,感谢代码,它帮助我解决了我的一个恼人的错误。
    • 太棒了! -- 阅读此答案以及本文以更好地了解正在发生的事情:soundfile.sapp.org/doc/WaveFormat
    • 案例 2 的内部循环将循环 i =0 到 i = samps,s++ 将执行 2x 采样时间。但 asFloat 是样本的大小。保证数组越界。我认为应该是 i
    【解决方案4】:

    WAV 文件(至少是未压缩的)相当简单。有一个标题,然后是数据。

    这是一个很好的参考:https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ (mirror)

    【讨论】:

    • 这样我就可以把数据弄进去,然后把偏移量44之后的字节隔离出来,然后再用同一个头来保存文件,对吧?
    • 并非总是如此,WAV 文件也可以在 fmt 块之后和数据块之前包含其他块。最好正确解析 RIFF 块
    • WAV 文件中的数据实际上不会是浮点数吧?具体来说,它会是实际时域波形的浮点表示吗?
    • 如果“fmt ”子块中的 AudioFormat0x0003 (WAVE_FORMAT_IEEE_FLOAT)
    • 该链接是一个很好的介绍,但要小心。今天许多 wav 的标头长度超过 44 个字节。 46 字节的标头特别常见,并且该介绍没有讨论要解析的额外数据的可能性。
    【解决方案5】:

    http://hourlyapps.blogspot.com/2008/07/open-source-wave-graph-c-net-control.html
    这是一个控件,它显示 Wav 文件的频谱,它还提供解码 Wav 文件的字节[],您可以在其中播放和/或更改它们的值。

    只需下载控件,它就非常适合 WAV 文件操作。

    【讨论】:

      【解决方案6】:

      要将 wav 文件放入数组中,您可以这样做:

      byte[] data = File.ReadAllBytes("FilePath");

      但就像 Fletch 所说,您需要将数据与标头隔离开来。它应该只是一个简单的偏移量。

      【讨论】:

        【解决方案7】:

        试试Play audio data from array

        PlayerEx pl = new PlayerEx();
        
        private static void PlayArray(PlayerEx pl)
        {
            double fs = 8000; // sample freq
            double freq = 1000; // desired tone
            short[] mySound = new short[4000];
            for (int i = 0; i < 4000; i++)
            {
                double t = (double)i / fs; // current time
                mySound[i] = (short)(Math.Cos(t * freq) * (short.MaxValue));
            }
            IntPtr format = AudioCompressionManager.GetPcmFormat(1, 16, (int)fs);
            pl.OpenPlayer(format);
            byte[] mySoundByte = new byte[mySound.Length * 2];
            Buffer.BlockCopy(mySound, 0, mySoundByte, 0, mySoundByte.Length);
            pl.AddData(mySoundByte);
            pl.StartPlay();
        }
        

        【讨论】:

        • 此答案与提出的问题不符,但可能对正确的问题有所帮助。
        猜你喜欢
        • 2012-09-01
        • 1970-01-01
        • 2015-04-08
        • 1970-01-01
        • 2015-12-02
        • 2013-04-11
        • 2012-12-05
        相关资源
        最近更新 更多