WaveReader.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // Copyright (c) 2023 Xiaomi Corporation (authors: Fangjun Kuang)
  2. using System;
  3. using System.IO;
  4. using System.Runtime.InteropServices;
  5. namespace SherpaNcnn
  6. {
  7. [StructLayout(LayoutKind.Sequential)]
  8. public struct WaveHeader
  9. {
  10. public Int32 ChunkID;
  11. public Int32 ChunkSize;
  12. public Int32 Format;
  13. public Int32 SubChunk1ID;
  14. public Int32 SubChunk1Size;
  15. public Int16 AudioFormat;
  16. public Int16 NumChannels;
  17. public Int32 SampleRate;
  18. public Int32 ByteRate;
  19. public Int16 BlockAlign;
  20. public Int16 BitsPerSample;
  21. public Int32 SubChunk2ID;
  22. public Int32 SubChunk2Size;
  23. public bool Validate()
  24. {
  25. if (ChunkID != 0x46464952)
  26. {
  27. Console.WriteLine($"Invalid chunk ID: 0x{ChunkID:X}. Expect 0x46464952");
  28. return false;
  29. }
  30. // E V A W
  31. if (Format != 0x45564157)
  32. {
  33. Console.WriteLine($"Invalid format: 0x{Format:X}. Expect 0x45564157");
  34. return false;
  35. }
  36. // t m f
  37. if (SubChunk1ID != 0x20746d66)
  38. {
  39. Console.WriteLine($"Invalid SubChunk1ID: 0x{SubChunk1ID:X}. Expect 0x20746d66");
  40. return false;
  41. }
  42. if (SubChunk1Size != 16)
  43. {
  44. Console.WriteLine($"Invalid SubChunk1Size: {SubChunk1Size}. Expect 16");
  45. return false;
  46. }
  47. if (AudioFormat != 1)
  48. {
  49. Console.WriteLine($"Invalid AudioFormat: {AudioFormat}. Expect 1");
  50. return false;
  51. }
  52. if (NumChannels != 1)
  53. {
  54. Console.WriteLine($"Invalid NumChannels: {NumChannels}. Expect 1");
  55. return false;
  56. }
  57. if (ByteRate != (SampleRate * NumChannels * BitsPerSample / 8))
  58. {
  59. Console.WriteLine($"Invalid byte rate: {ByteRate}.");
  60. return false;
  61. }
  62. if (BlockAlign != (NumChannels * BitsPerSample / 8))
  63. {
  64. Console.WriteLine($"Invalid block align: {ByteRate}.");
  65. return false;
  66. }
  67. if (BitsPerSample != 16)
  68. { // we support only 16 bits per sample
  69. Console.WriteLine($"Invalid bits per sample: {BitsPerSample}. Expect 16");
  70. return false;
  71. }
  72. return true;
  73. }
  74. }
  75. // It supports only 16-bit, single channel WAVE format.
  76. // The sample rate can be any value.
  77. public class WaveReader
  78. {
  79. public WaveReader(String fileName)
  80. {
  81. if (!File.Exists(fileName))
  82. {
  83. throw new ApplicationException($"{fileName} does not exist!");
  84. }
  85. using (var stream = File.Open(fileName, FileMode.Open))
  86. {
  87. using (var reader = new BinaryReader(stream))
  88. {
  89. _header = ReadHeader(reader);
  90. if (!_header.Validate())
  91. {
  92. throw new ApplicationException($"Invalid wave file ${fileName}");
  93. }
  94. SkipMetaData(reader);
  95. // now read samples
  96. // _header.SubChunk2Size contains number of bytes in total.
  97. // we assume each sample is of type int16
  98. byte[] buffer = reader.ReadBytes(_header.SubChunk2Size);
  99. short[] samples_int16 = new short[_header.SubChunk2Size / 2];
  100. Buffer.BlockCopy(buffer, 0, samples_int16, 0, buffer.Length);
  101. _samples = new float[samples_int16.Length];
  102. for (var i = 0; i < samples_int16.Length; ++i)
  103. {
  104. _samples[i] = samples_int16[i] / 32768.0F;
  105. }
  106. }
  107. }
  108. }
  109. private static WaveHeader ReadHeader(BinaryReader reader)
  110. {
  111. byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(WaveHeader)));
  112. GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
  113. WaveHeader header = (WaveHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(WaveHeader));
  114. handle.Free();
  115. return header;
  116. }
  117. private void SkipMetaData(BinaryReader reader)
  118. {
  119. var bs = reader.BaseStream;
  120. Int32 subChunk2ID = _header.SubChunk2ID;
  121. Int32 subChunk2Size = _header.SubChunk2Size;
  122. while (bs.Position != bs.Length && subChunk2ID != 0x61746164)
  123. {
  124. bs.Seek(subChunk2Size, SeekOrigin.Current);
  125. subChunk2ID = reader.ReadInt32();
  126. subChunk2Size = reader.ReadInt32();
  127. }
  128. _header.SubChunk2ID = subChunk2ID;
  129. _header.SubChunk2Size = subChunk2Size;
  130. }
  131. private WaveHeader _header;
  132. // Samples are normalized to the range [-1, 1]
  133. private float[] _samples;
  134. public int SampleRate => _header.SampleRate;
  135. public float[] Samples => _samples;
  136. public static void Test(String fileName)
  137. {
  138. WaveReader reader = new WaveReader(fileName);
  139. Console.WriteLine($"samples length: {reader.Samples.Length}");
  140. Console.WriteLine($"samples rate: {reader.SampleRate}");
  141. }
  142. }
  143. }