/Client/Audio/AudioLib/Source/WaveFormatEx.cs

http://clipflair.codeplex.com · C# · 300 lines · 184 code · 39 blank · 77 comment · 29 complexity · f98f3bcee020bf40ca98aaaf596224fc MD5 · raw file

  1. //Filename: WaveFormatEx.cs
  2. //Version: 20120912
  3. //-----------------------------------------------------------------------
  4. // <copyright file="WaveFormatEx.cs" company="Gilles Khouzam">
  5. // (c) Copyright Gilles Khouzam
  6. // Based on the sample from Larry Olson
  7. // This source is subject to the Microsoft Public License (Ms-PL)
  8. // All other rights reserved.
  9. // </copyright>
  10. //-----------------------------------------------------------------------
  11. using System;
  12. namespace ClipFlair.AudioLib
  13. {
  14. /// <summary>
  15. /// Class WAVEFORMATEX
  16. /// Implementation of a standard WAVEFORMATEX structure
  17. /// </summary>
  18. public class WAVEFORMATEX
  19. {
  20. #region Data
  21. /// <summary>
  22. /// The size of the basic structure
  23. /// </summary>
  24. public const uint SizeOf = 18;
  25. /// <summary>
  26. /// The different formats allowable. For now PCM is the only one we support
  27. /// </summary>
  28. private const short FormatPCM = 1;
  29. /// <summary>
  30. /// Gets or sets the FormatTag
  31. /// </summary>
  32. public short FormatTag
  33. {
  34. get;
  35. set;
  36. }
  37. /// <summary>
  38. /// Gets or sets the number of Channels
  39. /// </summary>
  40. public short Channels
  41. {
  42. get;
  43. set;
  44. }
  45. /// <summary>
  46. /// Gets or sets the number of samples per second
  47. /// </summary>
  48. public int SamplesPerSec
  49. {
  50. get;
  51. set;
  52. }
  53. /// <summary>
  54. /// Gets or sets the average bytes per second
  55. /// </summary>
  56. public int AvgBytesPerSec
  57. {
  58. get;
  59. set;
  60. }
  61. /// <summary>
  62. /// Gets or sets the alignment of the blocks
  63. /// </summary>
  64. public short BlockAlign
  65. {
  66. get;
  67. set;
  68. }
  69. /// <summary>
  70. /// Gets or sets the number of bits per sample (8 or 16)
  71. /// </summary>
  72. public short BitsPerSample
  73. {
  74. get;
  75. set;
  76. }
  77. /// <summary>
  78. /// Gets or sets the size of the structure
  79. /// </summary>
  80. public short Size
  81. {
  82. get;
  83. set;
  84. }
  85. /// <summary>
  86. /// Gets or sets the extension buffer
  87. /// </summary>
  88. public byte[] Ext
  89. {
  90. get;
  91. set;
  92. }
  93. #endregion Data
  94. /// <summary>
  95. /// Convert a BigEndian string to a LittleEndian string
  96. /// </summary>
  97. /// <param name="bigEndianString">A big endian string</param>
  98. /// <returns>The little endian string</returns>
  99. public static string ToLittleEndianString(string bigEndianString)
  100. {
  101. if (bigEndianString == null)
  102. {
  103. return string.Empty;
  104. }
  105. char[] bigEndianChars = bigEndianString.ToCharArray();
  106. // Guard
  107. if (bigEndianChars.Length % 2 != 0)
  108. {
  109. return string.Empty;
  110. }
  111. int i, ai, bi, ci, di;
  112. char a, b, c, d;
  113. for (i = 0; i < bigEndianChars.Length / 2; i += 2)
  114. {
  115. // front byte
  116. ai = i;
  117. bi = i + 1;
  118. // back byte
  119. ci = bigEndianChars.Length - 2 - i;
  120. di = bigEndianChars.Length - 1 - i;
  121. a = bigEndianChars[ai];
  122. b = bigEndianChars[bi];
  123. c = bigEndianChars[ci];
  124. d = bigEndianChars[di];
  125. bigEndianChars[ci] = a;
  126. bigEndianChars[di] = b;
  127. bigEndianChars[ai] = c;
  128. bigEndianChars[bi] = d;
  129. }
  130. return new string(bigEndianChars);
  131. }
  132. /// <summary>
  133. /// Convert the data to a hex string
  134. /// </summary>
  135. /// <returns>A string in hexadecimal</returns>
  136. public string ToHexString()
  137. {
  138. string s = string.Empty;
  139. s += ToLittleEndianString(string.Format("{0:X4}", this.FormatTag));
  140. s += ToLittleEndianString(string.Format("{0:X4}", this.Channels));
  141. s += ToLittleEndianString(string.Format("{0:X8}", this.SamplesPerSec));
  142. s += ToLittleEndianString(string.Format("{0:X8}", this.AvgBytesPerSec));
  143. s += ToLittleEndianString(string.Format("{0:X4}", this.BlockAlign));
  144. s += ToLittleEndianString(string.Format("{0:X4}", this.BitsPerSample));
  145. s += ToLittleEndianString(string.Format("{0:X4}", this.Size));
  146. return s;
  147. }
  148. /// <summary>
  149. /// Set the data from a byte array (usually read from a file)
  150. /// </summary>
  151. /// <param name="byteArray">The array used as input to the stucture</param>
  152. public void SetFromByteArray(byte[] byteArray)
  153. {
  154. if ((byteArray.Length + 2) < SizeOf)
  155. {
  156. throw new ArgumentException("Byte array is too small");
  157. }
  158. this.FormatTag = BitConverter.ToInt16(byteArray, 0);
  159. this.Channels = BitConverter.ToInt16(byteArray, 2);
  160. this.SamplesPerSec = BitConverter.ToInt32(byteArray, 4);
  161. this.AvgBytesPerSec = BitConverter.ToInt32(byteArray, 8);
  162. this.BlockAlign = BitConverter.ToInt16(byteArray, 12);
  163. this.BitsPerSample = BitConverter.ToInt16(byteArray, 14);
  164. if (byteArray.Length >= SizeOf)
  165. {
  166. this.Size = BitConverter.ToInt16(byteArray, 16);
  167. }
  168. else
  169. {
  170. this.Size = 0;
  171. }
  172. if (byteArray.Length > WAVEFORMATEX.SizeOf)
  173. {
  174. this.Ext = new byte[byteArray.Length - WAVEFORMATEX.SizeOf];
  175. Array.Copy(byteArray, (int)WAVEFORMATEX.SizeOf, this.Ext, 0, this.Ext.Length);
  176. }
  177. else
  178. {
  179. this.Ext = null;
  180. }
  181. }
  182. /// <summary>
  183. /// Ouput the data into a string.
  184. /// </summary>
  185. /// <returns>A string representing the WAVEFORMATEX</returns>
  186. public override string ToString()
  187. {
  188. char[] rawData = new char[18];
  189. BitConverter.GetBytes(this.FormatTag).CopyTo(rawData, 0);
  190. BitConverter.GetBytes(this.Channels).CopyTo(rawData, 2);
  191. BitConverter.GetBytes(this.SamplesPerSec).CopyTo(rawData, 4);
  192. BitConverter.GetBytes(this.AvgBytesPerSec).CopyTo(rawData, 8);
  193. BitConverter.GetBytes(this.BlockAlign).CopyTo(rawData, 12);
  194. BitConverter.GetBytes(this.BitsPerSample).CopyTo(rawData, 14);
  195. BitConverter.GetBytes(this.Size).CopyTo(rawData, 16);
  196. return new string(rawData);
  197. }
  198. /// <summary>
  199. /// Calculate the duration of audio based on the size of the buffer
  200. /// </summary>
  201. /// <param name="audioDataSize">the buffer size in bytes</param>
  202. /// <returns>The duration of that buffer</returns>
  203. public long AudioDurationFromBufferSize(uint audioDataSize)
  204. {
  205. if (this.AvgBytesPerSec == 0)
  206. {
  207. return 0;
  208. }
  209. return (long)audioDataSize * 10000000 / this.AvgBytesPerSec;
  210. }
  211. /// <summary>
  212. /// Calculate the buffer size necessary for a duration of audio
  213. /// </summary>
  214. /// <param name="duration">the duration</param>
  215. /// <returns>the size of the buffer necessary</returns>
  216. public long BufferSizeFromAudioDuration(long duration)
  217. {
  218. long size = duration * this.AvgBytesPerSec / 10000000;
  219. uint remainder = (uint)(size % this.BlockAlign);
  220. if (remainder != 0)
  221. {
  222. size += this.BlockAlign - remainder;
  223. }
  224. return size;
  225. }
  226. /// <summary>
  227. /// Validate that the Wave format is consistent.
  228. /// </summary>
  229. public void ValidateWaveFormat()
  230. {
  231. if (this.FormatTag != FormatPCM)
  232. {
  233. throw new InvalidOperationException("Only PCM format is supported");
  234. }
  235. if (this.Channels != 1 && this.Channels != 2)
  236. {
  237. throw new InvalidOperationException("Only 1 or 2 channels are supported");
  238. }
  239. if (this.BitsPerSample != 8 && this.BitsPerSample != 16)
  240. {
  241. throw new InvalidOperationException("Only 8 or 16 bit samples are supported");
  242. }
  243. if (this.Size != 0)
  244. {
  245. throw new InvalidOperationException("Size must be 0");
  246. }
  247. if (this.BlockAlign != this.Channels * (this.BitsPerSample / 8))
  248. {
  249. throw new InvalidOperationException("Block Alignment is incorrect");
  250. }
  251. if (this.SamplesPerSec > (uint.MaxValue / this.BlockAlign))
  252. {
  253. throw new InvalidOperationException("SamplesPerSec overflows");
  254. }
  255. if (this.AvgBytesPerSec != this.SamplesPerSec * this.BlockAlign)
  256. {
  257. throw new InvalidOperationException("AvgBytesPerSec is wrong");
  258. }
  259. }
  260. }
  261. }