如何获取8bit wav文件的数据

时间:2017-09-12 08:32:05

标签: c# winforms audio

我正在WindowsForm中制作关于声音的演示,我创建了3个用于获取wave文件数据的类。代码如下:

RiffBlock

public class RiffBlock
    {
        private byte[] riffID;
        private uint riffSize;
        private byte[] riffFormat;
        public byte[] RiffID
        {
            get { return riffID; }
        }

        public uint RiffSize
        {
            get { return (riffSize); }
        }

        public byte[] RiffFormat
        {
            get { return riffFormat; }
        }
        public RiffBlock()
        {
            riffID = new byte[4];
            riffFormat = new byte[4];
        }
        public void ReadRiff(FileStream inFS)
        {
            inFS.Read(riffID, 0, 4);
            BinaryReader binRead = new BinaryReader(inFS);
            riffSize = binRead.ReadUInt32();
            inFS.Read(riffFormat, 0, 4);
        }
    }

FormatBlock

public class FormatBlock
        {
            private byte[] fmtID;
            private uint fmtSize;
            private ushort fmtTag;
            private ushort fmtChannels;
            private uint fmtSamplesPerSec;
            private uint fmtAverageBytesPerSec;
            private ushort fmtBlockAlign;
            private ushort fmtBitsPerSample;
            public byte[] FmtID
            {
                get { return fmtID; }
            }

            public uint FmtSize
            {
                get { return fmtSize; }
            }

            public ushort FmtTag
            {
                get { return fmtTag; }
            }

            public ushort Channels
            {
                get { return fmtChannels; }
            }

            public uint SamplesPerSec
            {
                get { return fmtSamplesPerSec; }
            }

            public uint AverageBytesPerSec
            {
                get { return fmtAverageBytesPerSec; }
            }

            public ushort BlockAlign
            {
                get { return fmtBlockAlign; }
            }

            public ushort BitsPerSample
            {
                get { return fmtBitsPerSample; }
            }
            public FormatBlock()
            {
                fmtID = new byte[4];
            }
            public void ReadFmt(FileStream inFS)
            {
                inFS.Read(fmtID, 0, 4);

                BinaryReader binRead = new BinaryReader(inFS);

                fmtSize = binRead.ReadUInt32();
                fmtTag = binRead.ReadUInt16();
                fmtChannels = binRead.ReadUInt16();
                fmtSamplesPerSec = binRead.ReadUInt32();
                fmtAverageBytesPerSec = binRead.ReadUInt32();
                fmtBlockAlign = binRead.ReadUInt16();
                fmtBitsPerSample = binRead.ReadUInt16();

                // This accounts for the variable format header size 
                // 12 bytes of Riff Header, 4 bytes for FormatId, 4 bytes for FormatSize & the Actual size of the Format Header 
                inFS.Seek(fmtSize + 20, System.IO.SeekOrigin.Begin);
            }
        }

数据块

public class DataBlock
        {
            private byte[] dataID;
            private uint dataSize;
            private Int16[] data;
            private int dataNumSamples;

            public byte[] DataID
            {
                get { return dataID; }
            }

            public uint DataSize
            {
                get { return dataSize; }
            }

            public Int16 this[int pos]
            {
                get { return data[pos]; }
            }

            public int NumSamples
            {
                get { return dataNumSamples; }
            }
            public DataBlock()
            {
                dataID = new byte[4];
            }

            public void ReadData(FileStream inFS)
            {
                inFS.Read(dataID, 0, 4);

                BinaryReader binRead = new BinaryReader(inFS);

                dataSize = binRead.ReadUInt32();

                data = new Int16[dataSize];

                inFS.Seek(40, SeekOrigin.Begin);

                dataNumSamples = (int)(dataSize / 2);

                for (int i = 0; i < dataNumSamples; i++)
                {
                    data[i] = binRead.ReadInt16();
                }
            }
        }

只有16位波形文件可以正常工作,但是当我选择8位wav文件时,这个命令dataSize = binRead.ReadUInt32();的结果只有4,尽管文件大小很大。

如何获取8bit,24bit ... wav文件的数据? 感谢一些解决方案,非常感谢。

2 个答案:

答案 0 :(得分:2)

您可以使用Naudio库: https://naudio.codeplex.com/(看看他们的网站,有很多教程)。

希望这会有所帮助:)。

答案 1 :(得分:2)

您的阅读方法存在缺陷。长度是正确的,但对于每个样本文件8位,你应该读取字节而不是字;因为它的数据将是不正确的,NumSamples属性返回的值将是错误的。

在我的例子中,子块大小是1160,通道数是1(单声道),每个样本的比特是8(字节)。您需要解码每个样本的位并相应地调整读数。对于我使用的WAV文件,你的程序分配了一个正确长度但是16位的数据数组,将数据长度除以2并将其称为样本数(错误,应该是1160),然后继续从中读取580个字值流。

编辑:我的古代代码不会在现代时代削减它(我似乎记得几年前不得不修改它以应对至少一个额外的块类型但细节让我失望)。

这就是你得到的;还有更多,你的问题应该是&#34;有人可以给我写一个加载WAV文件的程序&#34; ,因为它是,我们已经超越了原来的问题,现在是你的时间了指责并使其工作如何你需要它: - )

参考文献:

一切都非常有用。

///<summary>
///Reads up to 16 bit WAV files
///</summary>
///<remarks> Things have really changed in the last 15 years</remarks>
public class RiffLoader
{
    public enum RiffStatus { Unknown = 0, OK, FileError, FormatError, UnsupportedFormat };

    LinkedList<Chunk> chunks = new LinkedList<Chunk>();

    RiffStatus status = RiffStatus.Unknown;
    List<String> errorMessages = new List<string>();
    String source;

    public String SourceName { get { return source; } }
    public RiffStatus Status { get { return status; } }
    public String[] Messages { get { return errorMessages.ToArray(); } }

    enum chunkType { Unknown = 0, NoMore, Riff, Fmt, Fact, Data, Error = -1 };

    static Int32 scan32bits(byte[] source, int offset = 0)
    {
        return source[offset] | source[offset + 1] << 8 | source[offset + 2] << 16 | source[offset + 3] << 24;
    }
    static Int32 scan16bits(byte[] source, int offset = 0)
    {
        return source[offset] | source[offset + 1] << 8;
    }

    static Int32 scan8bits(byte[] source, int offset = 0)
    {
        return source[offset];
    }

    abstract class Chunk
    {
        public chunkType Ident = chunkType.Unknown;
        public int ByteCount = 0;
    }

    class RiffChunk : Chunk
    {
        public RiffChunk(int count)
        {
            this.Ident = chunkType.Riff;
            this.ByteCount = count;
        }
    }

    class FmtChunk : Chunk
    {
        int formatCode = 0;
        int channels = 0;
        int samplesPerSec = 0;
        int avgBytesPerSec = 0;
        int blockAlign = 0;
        int bitsPerSample = 0;
        int significantBits = 0;

        public int Format { get { return formatCode; } }
        public int Channels { get { return channels; } }
        public int BlockAlign { get { return blockAlign; } }
        public int BytesPerSample { get { return bitsPerSample / 8 + ((bitsPerSample % 8) > 0 ? 1 : 0); } }
        public int BitsPerSample
        {
            get
            {
                if (significantBits > 0)
                    return significantBits;
                return bitsPerSample;
            }
        }

        public FmtChunk(byte[] buffer) : base()
        {
            int size = buffer.Length;

            // if the length is 18 then buffer 16,17 should be 00 00 (I don't bother checking)
            if (size != 16 && size != 18 && size != 40)
                return;
            formatCode = scan16bits(buffer, 0);
            channels = scan16bits(buffer, 2);
            samplesPerSec = scan32bits(buffer, 4);
            avgBytesPerSec = scan32bits(buffer, 8);
            blockAlign = scan16bits(buffer, 12);
            bitsPerSample = scan16bits(buffer, 14);

            if (formatCode == 0xfffe) // EXTENSIBLE
            {
                if (size != 40)
                    return;
                    significantBits = scan16bits(buffer, 18);
                    // skiping speaker map
                    formatCode = scan16bits(buffer, 24); // first two bytes of the GUID
                    // the rest of the GUID is fixed, decode it and check it if you wish
            }

            this.Ident = chunkType.Fmt;
            this.ByteCount = size;
        }

    }
    class DataChunk : Chunk
    {
        byte[] samples = null;
        ///<summary>
        ///Create a data chunk
        ///<para>
        ///The supplied buffer must be correctly sized with zero offset and must be purely for this class
        ///</para>
        ///<summary>
        ///<param name="buffer">source array</param>
        public DataChunk(byte[] buffer)
        {
            this.Ident = chunkType.Data;
            this.ByteCount = buffer.Length;
            samples = buffer;
        }

        public enum SampleStatus { OK, Duff }
        public class Samples
        {
            public SampleStatus Status = SampleStatus.Duff;
            public List<int[]> Channels = new List<int[]>();
#if false // debugger helper method
            /*
            ** Change #if false to #if true to include this
            ** Break at end of GetSamples on "return retval"
            ** open immediate window and type retval.DumpLast(16)
            ** look in output window for dump of last 16 entries
            */
            public int DumpLast(int count)
            {
                for (int i = Channels[0].Length - count; i < Channels[0].Length; i++)
                    Console.WriteLine(String.Format("{0:X4} {1:X4},{2:X4}", i, Channels[0][i], Channels[1][i]));
                return 0;
            }
#endif
        }
        /*
        ** Return the decoded samples
        */
        public Samples GetSamples(FmtChunk format)
        {
            Samples retval = new Samples();
            int samplesPerChannel = this.ByteCount / (format.BytesPerSample *  format.Channels);
            int mask = 0, sign=0;
            int [][] samples = new int [format.Channels][];

            for (int c = 0; c < format.Channels; c++)
                samples[c] = new int[samplesPerChannel];

            if (format.BitsPerSample >= 8 && format.BitsPerSample <= 16) // 24+ is left as an excercise
            {
                sign = (int)Math.Floor(Math.Pow(2, format.BitsPerSample - 1));
                mask = (int)Math.Floor(Math.Pow(2, format.BitsPerSample)) - 1;
                int offset = 0, index = 0;
                int s = 0;
                while (index < samplesPerChannel)
                {
                    for (int c = 0; c < format.Channels; c++)
                    {
                        switch (format.BytesPerSample)
                        {
                        case 1:
                            s = scan8bits(this.samples, offset) & mask;
                            break;
                        case 2:
                            s = scan16bits(this.samples, offset) & mask;
                            break;
                        }
                        // sign extend the data to Int32
                        samples[c][index] = s | ((s & sign) != 0 ? ~mask : 0);
                        offset += format.BytesPerSample;
                    }
                    ++index;
                }
                retval.Channels = new List<int[]>(samples);
                retval.Status = SampleStatus.OK;
            }
            return retval;
        }
    }

    class FactChunk : Chunk
    {
        int samplesPerChannel;

        public int SamplesPerChannel { get { return samplesPerChannel; } }
        public FactChunk(byte[] buffer)
        {
            this.Ident = chunkType.Fact;
            this.ByteCount = buffer.Length;

            if (buffer.Length >= 4)
                samplesPerChannel = scan32bits(buffer);
        }
    }
    class DummyChunk : Chunk
    {
        public DummyChunk(int size, chunkType type = chunkType.Unknown)
        {
            this.Ident = type;
            this.ByteCount = size;
        }
    }

    public RiffLoader(String fileName)
    {
        source = fileName;
        try
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            using (BinaryReader reader = new BinaryReader(fs))
            {
                Chunk c = getChunk(fs, reader);
                if (c.Ident != chunkType.Riff)
                {
                    status = RiffStatus.FileError;
                    errorMessages.Add(String.Format("Error loading \"{0}\": No valid header"));
                }
                chunks.AddLast(c);
                c = getChunk(fs, reader);
                if (c.Ident != chunkType.Fmt)
                {
                    status = RiffStatus.FileError;
                    errorMessages.Add(String.Format("Error loading \"{0}\": No format chunk"));
                }
                chunks.AddLast(c);
                /*
                ** From now on we just keep scanning to the end of the file
                */
                while (fs.Position < fs.Length)
                {
                    c = getChunk(fs, reader);
                    switch (c.Ident)
                    {
                    case chunkType.Fact:
                    case chunkType.Data:
                        chunks.AddLast(c);
                        break;
                    case chunkType.Unknown:
                        break; // skip it - don't care what it is
                    }
                }
                FmtChunk format = null;

                foreach (var chunk in chunks)
                {

                    switch(chunk.Ident)
                    {
                    case chunkType.Fmt:
                        format = chunk as FmtChunk;
                        break;
                    case chunkType.Data:
                        if (format != null)
                        {
                            DataChunk dc = chunk as DataChunk;
                            var x = dc.GetSamples(format);
                        }
                        break;
                    }
                }
            }

        }
        catch (Exception e)
        {
            status = RiffStatus.FileError;
            errorMessages.Add(String.Format("Error loading \"{0}\": {1}", e.Message));
        }
    }


    /*
    ** Get a chunk of data from the file - knows nothing of the internal format of the chunk.
    */
    Chunk getChunk(FileStream stream, BinaryReader reader)
    {
        byte[] buffer;
        int size;

        buffer = reader.ReadBytes(8);
        if (buffer.Length == 8)
        {
            String prefix = new String(Encoding.ASCII.GetChars(buffer, 0, 4));
            size = scan32bits(buffer, 4);

            if (size + stream.Position <= stream.Length) // skip if there isn't enough data
            {
                if (String.Compare(prefix, "RIFF") == 0)
                {
                    /* 
                    ** only "WAVE" type is acceptable
                    **
                    ** Don't read size bytes or the entire file will end up in the RIFF chunk
                    */
                    if (size >= 4)
                    {
                        buffer = reader.ReadBytes(4);
                        String ident = new String(Encoding.ASCII.GetChars(buffer, 0, 4));
                        if (String.CompareOrdinal(ident, "WAVE") == 0)
                            return new RiffChunk(size - 4);
                    }
                }
                else if (String.Compare(prefix, "fmt ") == 0)
                {
                    if (size >= 16)
                    {
                        buffer = reader.ReadBytes(size);
                        if (buffer.Length == size)
                            return new FmtChunk(buffer);
                    }
                }
                else if (String.Compare(prefix, "fact") == 0)
                {
                    if (size >= 4)
                    {
                        buffer = reader.ReadBytes(4);
                        if (buffer.Length == size)
                            return new FactChunk(buffer);
                    }
                }
                else if (String.Compare(prefix, "data") == 0)
                {
                    // assume that there has to be data
                    if (size > 0)
                    {
                        buffer = reader.ReadBytes(size);
                        if ((size & 1) != 0) // odd length?
                        {
                            if (stream.Position < stream.Length)
                                reader.ReadByte();
                            else
                                size = -1; // force an error - there should be a pad byte
                        }
                        if (buffer.Length == size)
                            return new DataChunk(buffer);
                    }
                }
                else
                {
                    /*
                    ** there are a number of weird and wonderful block types - assume there has to be data
                    */
                    if (size > 0)
                    {
                        buffer = reader.ReadBytes(size);
                        if ((size & 1) != 0) // odd length?
                        {
                            if (stream.Position < stream.Length)
                                reader.ReadByte();
                            else
                                size = -1; // force an error - there should be a pad byte
                        }
                        if (buffer.Length == size)
                        {
                            DummyChunk skip = new DummyChunk(size);
                            return skip;
                        }
                    }
                }
            }
        }
        return new DummyChunk(0, chunkType.Error);
    }
}

您需要根据需要添加属性和代码以导航返回的链接列表。假设您要以某种方式处理数据,您可能需要的特定属性是格式块中的采样率。

添加24到32位很简单,如果你需要超过32位,你必须切换到int64&#39; s。

我用http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Samples.html的一些好样本以及你的8位文件对它进行了测试,它解码好了,似乎有正确的数据。

你应该能够让它现在正常运转,祝你好运。

编辑(再次,就像众所周知的狗......):

当然,我应该有符号扩展值,所以DataChunk.GetSamples()现在可以做到。查看Codeproject文件(顺便说一句,它不是最好的代码,但是他确实说他只是学习C#,因此对于处理图形用户控件非常公平)显然数据是签名的。遗憾的是,他没有将他的来源标准化为Int32的数组,那么编码WAV的位数并不重要。