// Copyright (c) 2010-2014 SharpDX - Alexandre Mutel // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // ----------------------------------------------------------------------------- // Original code from NAudio project. http://naudio.codeplex.com/ // Greetings to Mark Heath. // ----------------------------------------------------------------------------- using System; using System.Globalization; using System.IO; using System.Runtime.InteropServices; namespace SharpDX.Multimedia { /// /// Represents a Wave file format /// /// WAVEFORMATEX 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; [StructLayout(LayoutKind.Sequential, Pack = 2)] internal struct __Native { public __PcmNative pcmWaveFormat; /// number of following bytes public short extraSize; // Method to free native struct internal unsafe void __MarshalFree() { } } internal unsafe void __MarshalFree(ref __Native @ref) { @ref.__MarshalFree(); } // Method to marshal from native to managed struct internal unsafe void __MarshalFrom(ref __Native @ref) { this.waveFormatTag = @ref.pcmWaveFormat.waveFormatTag; this.channels = @ref.pcmWaveFormat.channels; this.sampleRate = @ref.pcmWaveFormat.sampleRate; this.averageBytesPerSecond = @ref.pcmWaveFormat.averageBytesPerSecond; this.blockAlign = @ref.pcmWaveFormat.blockAlign; this.bitsPerSample = @ref.pcmWaveFormat.bitsPerSample; this.extraSize = @ref.extraSize; } // Method to marshal from managed struct tot native internal unsafe void __MarshalTo(ref __Native @ref) { @ref.pcmWaveFormat.waveFormatTag = this.waveFormatTag; @ref.pcmWaveFormat.channels = this.channels; @ref.pcmWaveFormat.sampleRate = this.sampleRate; @ref.pcmWaveFormat.averageBytesPerSecond = this.averageBytesPerSecond; @ref.pcmWaveFormat.blockAlign = this.blockAlign; @ref.pcmWaveFormat.bitsPerSample = this.bitsPerSample; @ref.extraSize = this.extraSize; } [StructLayout(LayoutKind.Sequential, Pack = 2)] internal struct __PcmNative { /// format type public WaveFormatEncoding waveFormatTag; /// number of channels public short channels; /// sample rate public int sampleRate; /// for buffer estimation public int averageBytesPerSecond; /// block size of data public short blockAlign; /// number of bits per sample of mono data public short bitsPerSample; // Method to free native struct internal unsafe void __MarshalFree() { } } internal unsafe void __MarshalFree(ref __PcmNative @ref) { @ref.__MarshalFree(); } // Method to marshal from native to managed struct internal unsafe void __MarshalFrom(ref __PcmNative @ref) { this.waveFormatTag = @ref.waveFormatTag; this.channels = @ref.channels; this.sampleRate = @ref.sampleRate; this.averageBytesPerSecond = @ref.averageBytesPerSecond; this.blockAlign = @ref.blockAlign; this.bitsPerSample = @ref.bitsPerSample; this.extraSize = 0; } // Method to marshal from managed struct tot native internal unsafe void __MarshalTo(ref __PcmNative @ref) { @ref.waveFormatTag = this.waveFormatTag; @ref.channels = this.channels; @ref.sampleRate = this.sampleRate; @ref.averageBytesPerSecond = this.averageBytesPerSecond; @ref.blockAlign = this.blockAlign; @ref.bitsPerSample = this.bitsPerSample; } /// /// 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) { var waveFormat = new WaveFormat { waveFormatTag = tag, channels = (short) channels, sampleRate = sampleRate, averageBytesPerSecond = averageBytesPerSecond, blockAlign = (short) blockAlign, bitsPerSample = (short) bitsPerSample, 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", "Channels must be 1 or greater"); } // minimum 16 bytes, sometimes 18 for PCM this.waveFormatTag = bits<32?WaveFormatEncoding.Pcm:WaveFormatEncoding.IeeeFloat; 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) { var wf = new WaveFormat { waveFormatTag = WaveFormatEncoding.IeeeFloat, channels = (short) channels, bitsPerSample = 32, sampleRate = sampleRate, blockAlign = (short) (4*channels) }; wf.averageBytesPerSecond = sampleRate * wf.blockAlign; wf.extraSize = 0; return wf; } /// /// Helper function to retrieve a WaveFormat structure from a pointer /// /// Buffer to the WaveFormat rawdata /// WaveFormat structure public unsafe static WaveFormat MarshalFrom(byte[] rawdata) { fixed (void* pRawData = rawdata) return MarshalFrom((IntPtr)pRawData); } /// /// Helper function to retrieve a WaveFormat structure from a pointer /// /// Pointer to the WaveFormat rawdata /// WaveFormat structure public unsafe static WaveFormat MarshalFrom(IntPtr pointer) { if (pointer == IntPtr.Zero) return null; var pcmWaveFormat = *(__PcmNative*)pointer; var encoding = pcmWaveFormat.waveFormatTag; // Load simple PcmWaveFormat if channels <= 2 and encoding is Pcm, IeeFloat, Wmaudio2, Wmaudio3 // See http://msdn.microsoft.com/en-us/library/microsoft.directx_sdk.xaudio2.waveformatex%28v=vs.85%29.aspx if (pcmWaveFormat.channels <= 2 && (encoding == WaveFormatEncoding.Pcm || encoding == WaveFormatEncoding.IeeeFloat || encoding == WaveFormatEncoding.Wmaudio2 || encoding == WaveFormatEncoding.Wmaudio3)) { var waveFormat = new WaveFormat(); waveFormat.__MarshalFrom(ref pcmWaveFormat); return waveFormat; } if (encoding == WaveFormatEncoding.Extensible) { var waveFormat = new WaveFormatExtensible(); waveFormat.__MarshalFrom(ref *(WaveFormatExtensible.__Native*)pointer); return waveFormat; } if (encoding == WaveFormatEncoding.Adpcm) { var waveFormat = new WaveFormatAdpcm(); waveFormat.__MarshalFrom(ref *(WaveFormatAdpcm.__Native*)pointer); return waveFormat; } throw new InvalidOperationException(string.Format("Unsupported WaveFormat [{0}]", encoding)); } protected unsafe virtual IntPtr MarshalToPtr() { var result = Marshal.AllocHGlobal(Utilities.SizeOf()); __MarshalTo(ref *(WaveFormat.__Native*)result); return result; } /// /// 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) { if (format == null) return IntPtr.Zero; return format.MarshalToPtr(); } /// /// 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 SharpDXException("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(); this.extraSize = 0; if (formatChunkLength > 16) { this.extraSize = br.ReadInt16(); if (this.extraSize > formatChunkLength - 18) { //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(CultureInfo.InvariantCulture, "{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) { if (!(obj is WaveFormat)) return false; WaveFormat other = (WaveFormat)obj; return waveFormatTag == other.waveFormatTag && channels == other.channels && sampleRate == other.sampleRate && averageBytesPerSecond == other.averageBytesPerSecond && blockAlign == other.blockAlign && bitsPerSample == other.bitsPerSample; } /// /// Provides a hash code for this WaveFormat /// /// A hash code 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; } } /// /// 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 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; } } } }