简体   繁体   English

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

[英]How can I get the data of 8bit wav file

I'm making a demo about sound in WindowsForm, I created 3 classes for taking data of wave file. 我在WindowsForm中制作了关于声音的演示,我创建了3个用于获取wave文件数据的类。 Code is below: 代码如下:

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

DataBlock 数据块

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

It works ok with only 16bit wave file, but when I choose a 8 bit wav file or another, the result of this command dataSize = binRead.ReadUInt32(); 它仅适用于16位波形文件,但是当我选择8位wav文件或其他文件时,此命令的结果为dataSize = binRead.ReadUInt32(); is only 4 although the file size is big. 虽然文件很大,但只有4个。

How I can get the data of 8bit, 24bit... wav file? 我怎样才能获得8bit,24bit ... wav文件的数据? Some solutions is appreciated, thank you very much. 感谢一些解决方案,非常感谢。

You could use the Naudio library: https://naudio.codeplex.com/ (take a look at their website, there are quite a lot of tutorials). 您可以使用Naudio库: https ://naudio.codeplex.com/(看看他们的网站,有很多教程)。

Hope this helps :). 希望这可以帮助 :)。

Your reading methodology is flawed. 你的阅读方法存在缺陷。 The length is correct but for an 8 bits per sample file you should be reading bytes not words; 长度是正确的,但对于每个样本文件8位,你应该读取字节而不是字; as it stands the data will be incorrect and the value returned by the NumSamples property will be wrong. 因为它的数据将是不正确的,NumSamples属性返回的值将是错误的。

In my case, the sub chunk size is 1160, the number of channels is 1 (mono) and the bits per sample is 8 (byte). 在我的例子中,子块大小是1160,通道数是1(单声道),每个样本的比特是8(字节)。 You will need to decode the bits per sample and adjust your reading accordingly. 您需要解码每个样本的位并相应地调整读数。 For the WAV file I used, your program allocated a data array the correct length but 16 bit, divided the data length by 2 and called this the number of samples (wrong, it should be 1160) and then proceeded to read 580 word values from the stream. 对于我使用的WAV文件,你的程序分配了一个正确长度但是16位的数据数组,将数据长度除以2并将其称为样本数(错误,应该是1160),然后继续从中读取580个字值流。


Edit: My ancient code will not cut it in the modern age (I seem to recall having to modify it some years ago to cope with at least one additional chunk type but the details escape me). 编辑:我的古代代码不会在现代时代削减它(我似乎记得几年前必须修改它以应对至少一个额外的块类型,但细节让我失望)。

This is what you get; 这就是你得到的; anything more and your question should read "Could someone write me a program to load WAV files" , as it is, we are way beyond the original question and it is time for you to knuckle down and make it work how you need it to :-) 更多的问题和你的问题应该是“有人可以给我写一个加载WAV文件的程序” ,因为它是,我们已经超越了原来的问题,现在是时候让你努力工作,让它按照你需要的方式工作: - )

References: 参考文献:

All are very useful. 一切都非常有用。

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

You will need to add properties as required and code to navigate the returned linked list. 您需要根据需要添加属性和代码以导航返回的链接列表。 Particular properties you may need are the sample rates in the format block, assuming you are going to process the data in some way. 假设您要以某种方式处理数据,您可能需要的特定属性是格式块中的采样率。

Adding 24 to 32 bits is simple, if you need to go beyond 32 bits you will have to switch to int64's. 添加24到32位很简单,如果你需要超过32位,你将不得不切换到int64。

I have tested it with some good samples from http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Samples.html , as well as your 8 bit file, and it decodes OK and seems to have the correct data. 我用http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Samples.html中的一些好样本以及你的8位文件对它进行了测试,它解码好了,似乎有了正确的数据。

You should be more than capable of making it work now, good luck. 你应该有能力让它现在运转,祝你好运。

Edit (again, like the proverbial dog...): 编辑(再次,像众所周知的狗......):

Of course, I should have sign extended the value, so DataChunk.GetSamples() now does that. 当然,我应该有符号扩展值,所以DataChunk.GetSamples()现在就这样做了。 Looking at the Codeproject files (it isn't the greatest code by the way, but the guy does say that he is only just learning C#, so fair play for tackling a graphical user control) it is obvious that the data is signed. 看看Codeproject文件(顺便说一句,这不是最好的代码,但是这个人确实说他只是学习C#,所以解决图形用户控件的公平竞争)显然数据是签名的。 It's a shame that he didn't standardise his source to be an array of Int32's, then it wouldn't matter how many bits the WAV was encoded in. 令人遗憾的是,他没有将他的源标准化为Int32的数组,那么编码WAV的位数无关紧要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM