using System; using System.IO; using System.Runtime.InteropServices; namespace NAudio.Wave { /// /// Represents a Wave file format /// [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi, Pack=2)] public class WaveFormat { /// format type protected WaveFormatEncoding waveFormatTag; /// number of channels protected short channels; /// sample rate protected int sampleRate; /// for buffer estimation protected int averageBytesPerSecond; /// block size of data protected short blockAlign; /// number of bits per sample of mono data protected short bitsPerSample; /// number of following bytes protected short extraSize; /// /// Creates a new PCM 44.1Khz stereo 16 bit format /// public WaveFormat() : this(44100,16,2) { } /// /// Creates a new 16 bit wave format with the specified sample /// rate and channel count /// /// Sample Rate /// Number of channels public WaveFormat(int sampleRate, int channels) : this(sampleRate, 16, channels) { } /// /// Gets the size of a wave buffer equivalent to the latency in milliseconds. /// /// The milliseconds. /// public int ConvertLatencyToByteSize(int milliseconds) { int bytes = (int) ((AverageBytesPerSecond/1000.0)*milliseconds); if ((bytes%BlockAlign) != 0) { // Return the upper BlockAligned bytes = bytes + BlockAlign - (bytes % BlockAlign); } return bytes; } /// /// Creates a WaveFormat with custom members /// /// The encoding /// Sample Rate /// Number of channels /// Average Bytes Per Second /// Block Align /// Bits Per Sample /// public static WaveFormat CreateCustomFormat(WaveFormatEncoding tag, int sampleRate, int channels, int averageBytesPerSecond, int blockAlign, int bitsPerSample) { WaveFormat waveFormat = new WaveFormat(); waveFormat.waveFormatTag = tag; waveFormat.channels = (short)channels; waveFormat.sampleRate = sampleRate; waveFormat.averageBytesPerSecond = averageBytesPerSecond; waveFormat.blockAlign = (short)blockAlign; waveFormat.bitsPerSample = (short)bitsPerSample; waveFormat.extraSize = 0; return waveFormat; } /// /// Creates an A-law wave format /// /// Sample Rate /// Number of Channels /// Wave Format public static WaveFormat CreateALawFormat(int sampleRate, int channels) { return CreateCustomFormat(WaveFormatEncoding.ALaw, sampleRate, channels, sampleRate * channels, 1, 8); } /// /// Creates a Mu-law wave format /// /// Sample Rate /// Number of Channels /// Wave Format public static WaveFormat CreateMuLawFormat(int sampleRate, int channels) { return CreateCustomFormat(WaveFormatEncoding.MuLaw, sampleRate, channels, sampleRate * channels, 1, 8); } /// /// Creates a new PCM format with the specified sample rate, bit depth and channels /// public WaveFormat(int rate, int bits, int channels) { if (channels < 1) { throw new ArgumentOutOfRangeException("Channels must be 1 or greater", "channels"); } // minimum 16 bytes, sometimes 18 for PCM this.waveFormatTag = WaveFormatEncoding.Pcm; this.channels = (short)channels; this.sampleRate = rate; this.bitsPerSample = (short)bits; this.extraSize = 0; this.blockAlign = (short)(channels * (bits / 8)); this.averageBytesPerSecond = this.sampleRate * this.blockAlign; } /// /// Creates a new 32 bit IEEE floating point wave format /// /// sample rate /// number of channels public static WaveFormat CreateIeeeFloatWaveFormat(int sampleRate, int channels) { WaveFormat wf = new WaveFormat(); wf.waveFormatTag = WaveFormatEncoding.IeeeFloat; wf.channels = (short)channels; wf.bitsPerSample = 32; wf.sampleRate = sampleRate; wf.blockAlign = (short) (4*channels); wf.averageBytesPerSecond = sampleRate * wf.blockAlign; wf.extraSize = 0; return wf; } /// /// Helper function to retrieve a WaveFormat structure from a pointer /// /// WaveFormat structure /// public static WaveFormat MarshalFromPtr(IntPtr pointer) { WaveFormat waveFormat = (WaveFormat)Marshal.PtrToStructure(pointer, typeof(WaveFormat)); switch (waveFormat.Encoding) { case WaveFormatEncoding.Pcm: // can't rely on extra size even being there for PCM so blank it to avoid reading // corrupt data waveFormat.extraSize = 0; break; case WaveFormatEncoding.Extensible: waveFormat = (WaveFormatExtensible)Marshal.PtrToStructure(pointer, typeof(WaveFormatExtensible)); break; case WaveFormatEncoding.Adpcm: waveFormat = (AdpcmWaveFormat)Marshal.PtrToStructure(pointer, typeof(AdpcmWaveFormat)); break; default: if (waveFormat.ExtraSize > 0) { waveFormat = (WaveFormatExtraData)Marshal.PtrToStructure(pointer, typeof(WaveFormatExtraData)); } break; } return waveFormat; } /// /// Helper function to marshal WaveFormat to an IntPtr /// /// WaveFormat /// IntPtr to WaveFormat structure (needs to be freed by callee) public static IntPtr MarshalToPtr(WaveFormat format) { int formatSize = Marshal.SizeOf(format); IntPtr formatPointer = Marshal.AllocHGlobal(formatSize); Marshal.StructureToPtr(format, formatPointer, false); return formatPointer; } /// /// Reads a new WaveFormat object from a stream /// /// A binary reader that wraps the stream public WaveFormat(BinaryReader br) { int formatChunkLength = br.ReadInt32(); if(formatChunkLength < 16) throw new ApplicationException("Invalid WaveFormat Structure"); this.waveFormatTag = (WaveFormatEncoding) br.ReadUInt16(); this.channels = br.ReadInt16(); this.sampleRate = br.ReadInt32(); this.averageBytesPerSecond = br.ReadInt32(); this.blockAlign = br.ReadInt16(); this.bitsPerSample = br.ReadInt16(); if (formatChunkLength > 16) { this.extraSize = br.ReadInt16(); if (this.extraSize > formatChunkLength - 18) { Console.WriteLine("Format chunk mismatch"); //RRL GSM exhibits this bug. Don't throw an exception //throw new ApplicationException("Format chunk length mismatch"); this.extraSize = (short) (formatChunkLength - 18); } // read any extra data // br.ReadBytes(extraSize); } } /// /// Reports this WaveFormat as a string /// /// String describing the wave format public override string ToString() { switch (this.waveFormatTag) { case WaveFormatEncoding.Pcm: case WaveFormatEncoding.Extensible: // extensible just has some extra bits after the PCM header return String.Format("{0} bit PCM: {1}kHz {2} channels", bitsPerSample, sampleRate / 1000, channels); default: return this.waveFormatTag.ToString(); } } /// /// Compares with another WaveFormat object /// /// Object to compare to /// True if the objects are the same public override bool Equals(object obj) { WaveFormat other = obj as WaveFormat; if(other != null) { return waveFormatTag == other.waveFormatTag && channels == other.channels && sampleRate == other.sampleRate && averageBytesPerSecond == other.averageBytesPerSecond && blockAlign == other.blockAlign && bitsPerSample == other.bitsPerSample; } return false; } /// /// Provides a Hashcode for this WaveFormat /// /// A hashcode public override int GetHashCode() { return (int) waveFormatTag ^ (int) channels ^ sampleRate ^ averageBytesPerSecond ^ (int) blockAlign ^ (int) bitsPerSample; } /// /// Returns the encoding type used /// public WaveFormatEncoding Encoding { get { return waveFormatTag; } } /// /// Writes this WaveFormat object to a stream /// /// the output stream public virtual void Serialize(BinaryWriter writer) { writer.Write((int)(18 + extraSize)); // wave format length writer.Write((short)Encoding); writer.Write((short)Channels); writer.Write((int)SampleRate); writer.Write((int)AverageBytesPerSecond); writer.Write((short)BlockAlign); writer.Write((short)BitsPerSample); writer.Write((short)extraSize); } /// /// Returns the number of channels (1=mono,2=stereo etc) /// public int Channels { get { return channels; } } /// /// Returns the sample rate (samples per second) /// public int SampleRate { get { return sampleRate; } } /// /// Returns the average number of bytes used per second /// public int AverageBytesPerSecond { get { return averageBytesPerSecond; } } /// /// Returns the block alignment /// public virtual int BlockAlign { get { return blockAlign; } } /// /// Returns the number of bits per sample (usually 16 or 32, sometimes 24 or 8) /// Can be 0 for some codecs /// public int BitsPerSample { get { return bitsPerSample; } } /// /// Returns the number of extra bytes used by this waveformat. Often 0, /// except for compressed formats which store extra data after the WAVEFORMATEX header /// public int ExtraSize { get { return extraSize; } } } }