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