Monday, September 25, 2017

How can I get the data of 8bit wav file

Leave a Comment

I'm making a demo about sound in WindowsForm, I created 3 classes for taking data of wave file. Code is below:

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

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();is only 4 although the file size is big.

How I can get the data of 8bit, 24bit... wav file? Some solutions is appreciated, thank you very much.

2 Answers

Answers 1

Your reading methodology is flawed. The length is correct but for an 8 bits per sample file you should be reading bytes not words; as it stands the data will be incorrect and the value returned by the NumSamples property will be wrong.

In my case, the sub chunk size is 1160, the number of channels is 1 (mono) and the bits per sample is 8 (byte). 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.


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 :-)

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.

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.

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. 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. 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.

Answers 2

You could use the Naudio library: https://naudio.codeplex.com/ (take a look at their website, there are quite a lot of tutorials).

Hope this helps :).

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment