/decoders/audio/wav.d

http://github.com/wilkie/djehuty · D · 439 lines · 260 code · 89 blank · 90 comment · 82 complexity · ad5f1d459dc714d6fafcf012e39f3cde MD5 · raw file

  1. /*
  2. * wav.d
  3. *
  4. * This file implements the WAV audio standard. A "wave" is a chunked format
  5. * and a container format. It contains audio data in a myriad of different
  6. * encodings.
  7. *
  8. * Author: Dave Wilkinson
  9. *
  10. */
  11. module decoders.audio.wav;
  12. import decoders.audio.decoder;
  13. import decoders.audio.mp2 : MP2Decoder;
  14. import decoders.decoder;
  15. import core.stream;
  16. import core.time;
  17. import core.string;
  18. import core.definitions;
  19. import io.wavelet;
  20. import io.audio;
  21. import io.console;
  22. // Section: Codecs/Audio
  23. // Description: This is the Microsoft Wave file codec.
  24. class WAVDecoder : AudioDecoder {
  25. override string name() {
  26. return "Microsoft Wave";
  27. }
  28. override string extension() {
  29. return "wav;wave";
  30. }
  31. StreamData decode(Stream stream, Wavelet toBuffer, ref AudioInfo wi) {
  32. for (;;) {
  33. switch (decoderState) {
  34. case WAVE_STATE_INIT:
  35. //initial stuff
  36. decoderState = WAVE_STATE_READ_RIFF;
  37. // *** fall through *** //
  38. case WAVE_STATE_READ_RIFF:
  39. // read in the RIFF header
  40. // and check the validity
  41. //reading the header
  42. if (!stream.read(&RIFFHeader, RIFFHeader.sizeof)) {
  43. return StreamData.Required;
  44. }
  45. if (RIFFHeader.magic != *(cast(uint*)"RIFF"c.ptr)) {
  46. // RIFF header is wrong
  47. Console.putln("WAVE: Invalid RIFF Header");
  48. return StreamData.Invalid;
  49. }
  50. decoderState = WAVE_STATE_READ_CHUNK;
  51. // *** fall through *** //
  52. case WAVE_STATE_READ_CHUNK:
  53. // read in a chunk header
  54. if (!stream.read(&curChunk, curChunk.sizeof)) {
  55. return StreamData.Required;
  56. }
  57. char arr[4] = (cast(char*)&curChunk.chunkID)[0..4];
  58. // figure out what the chunk means
  59. if (curChunk.chunkID == *(cast(uint*)"data"c.ptr)) {
  60. // DATA Chunk
  61. decoderState = WAVE_STATE_CHUNK_DATA;
  62. // Get Audio Length
  63. //Console.putln(curChunk.chunkSize, " , ", FMTHeader.avgBytesPerSecond, " = ", (cast(float)curChunk.chunkSize / cast(float)FMTHeader.avgBytesPerSecond));
  64. wi.totalTime = cast(long)((cast(float)curChunk.chunkSize / cast(float)FMTHeader.avgBytesPerSecond) * 1000.0);
  65. dataToRead = curChunk.chunkSize;
  66. continue;
  67. }
  68. else if (curChunk.chunkID == *(cast(uint*)"fmt "c.ptr)) {
  69. // FMT Chunk
  70. decoderState = WAVE_STATE_CHUNK_FMT;
  71. continue;
  72. }
  73. else {
  74. // Unknown Chunk
  75. // Just skip it
  76. decoderState = WAVE_STATE_SKIP_CHUNK;
  77. }
  78. // *** fall through on an unknown chunk *** //
  79. case WAVE_STATE_SKIP_CHUNK:
  80. if (!stream.skip(curChunk.chunkSize)) {
  81. return StreamData.Required;
  82. }
  83. decoderState = WAVE_STATE_READ_CHUNK;
  84. continue;
  85. // -- Process a Chunk -- //
  86. case WAVE_STATE_CHUNK_DATA:
  87. // if the buffer is null, this is acceptable
  88. // in this case, the decoder will wait
  89. /* Console.putln("to seek");
  90. toSeek.toString();
  91. Console.putln("cur");
  92. bufferTime.toString();*/
  93. uint bufferSize = FMTHeader.avgBytesPerSecond << 1 ;
  94. if (isSeek && !isSeekBack && (toSeek < (curTime + bufferTime))) {
  95. // seeking
  96. Console.putln("seek no more");
  97. isSeek = false;
  98. return StreamData.Accepted;
  99. }
  100. else if (isSeek && isSeekBack && (toSeek >= curTime)) {
  101. // seeking
  102. Console.putln("seek no more");
  103. isSeek = false;
  104. return StreamData.Accepted;
  105. }
  106. else if (toBuffer is null && isSeek == false) {
  107. return StreamData.Accepted;
  108. }
  109. if (isSeek && isSeekBack) {
  110. // go backwards
  111. if (dataToRead == 0 && (curChunk.chunkSize % bufferSize) != 0) {
  112. if (!stream.rewind(curChunk.chunkSize % bufferSize)) {
  113. Console.putln("Audio Codec : Data Required");
  114. return StreamData.Required;
  115. }
  116. dataToRead = curChunk.chunkSize % bufferSize;
  117. curTime -= bufferTime;
  118. }
  119. else {
  120. if (!stream.rewind(FMTHeader.avgBytesPerSecond << 1)) {
  121. Console.putln("Audio Codec : Data Required");
  122. return StreamData.Required;
  123. }
  124. curTime -= bufferTime;
  125. dataToRead += FMTHeader.avgBytesPerSecond << 1;
  126. }
  127. continue;
  128. }
  129. //writeln("Audio Codec : Decoding Data Chunk");
  130. if (dataToRead == 0) {
  131. return StreamData.Complete;
  132. }
  133. // we should have a format header
  134. if (!formatHeaderFound) {
  135. // no format header, yet...
  136. // this file is invalid
  137. return StreamData.Invalid;
  138. }
  139. if (FMTHeader.compressionCode == 0x50) {
  140. if (embeddedCodec is null) {
  141. embeddedCodec = new MP2Decoder();
  142. }
  143. return embeddedCodec.decode(stream, toBuffer, wi);
  144. }
  145. else if (FMTHeader.compressionCode == 1 || FMTHeader.compressionCode == 3) {
  146. // are we getting the last piece?
  147. if (dataToRead < bufferSize) {
  148. //writeln("Audio Codec : Allocating ", dataToRead, " bytes (last chunk)");
  149. // allocate a whole buffer
  150. if (toBuffer !is null) {
  151. toBuffer.setAudioFormat(wf);
  152. if (toBuffer.length() != dataToRead) {
  153. // allocate
  154. // (this may look redundant, but this may occur
  155. // when there is only one chunk in the file)
  156. // Console.putln("Audio Codec : Resizing : " , toBuffer.length(), " : ", dataToRead);
  157. // toBuffer.resize(dataToRead);
  158. }
  159. toBuffer.rewind();
  160. //writeln("Audio Codec : Appending ", dataToRead, " bytes (last chunk)");
  161. // this is the last chunk of data
  162. if (!toBuffer.write(stream, dataToRead)) {
  163. Console.putln("Audio Codec : Data Required");
  164. return StreamData.Required;
  165. }
  166. }
  167. else {
  168. if (!stream.skip(dataToRead)) {
  169. //writeln("Audio Codec : Data Required");
  170. return StreamData.Required;
  171. }
  172. }
  173. dataToRead = 0;
  174. curTime += bufferTime;
  175. // go back to reading chunks
  176. //decoderState = WAVE_STATE_READ_CHUNK;
  177. return StreamData.Complete;
  178. }
  179. else {
  180. //writeln("Audio Codec : Allocating ", bufferSize, " bytes");
  181. // allocate a whole buffer
  182. if (toBuffer !is null) {
  183. toBuffer.setAudioFormat(wf);
  184. if (toBuffer.length() != bufferSize) {
  185. // allocate
  186. // Console.putln("Audio Codec : Resizing : " , toBuffer.length(), " : ", bufferSize);
  187. // toBuffer.resize(bufferSize);
  188. }
  189. toBuffer.rewind();
  190. //writeln("Audio Codec : Appending ", bufferSize, " bytes");
  191. // Read in a second worth of information
  192. if (!toBuffer.write(stream, bufferSize)) {
  193. //writeln("Audio Codec : Data Required");
  194. return StreamData.Required;
  195. }
  196. curTime += bufferTime;
  197. curTime.toString();
  198. dataToRead -= bufferSize;
  199. return StreamData.Accepted;
  200. }
  201. else {
  202. if (!stream.skip(bufferSize)) {
  203. //writeln("Audio Codec : Data Required");
  204. return StreamData.Required;
  205. }
  206. dataToRead -= bufferSize;
  207. curTime += bufferTime;
  208. }
  209. // add the amount of time to decoder's time counter
  210. // this is the time stamp of the next data block
  211. }
  212. }
  213. continue;
  214. case WAVE_STATE_CHUNK_FMT:
  215. // Read in the format information
  216. if (!stream.read(&FMTHeader, FMTHeader.sizeof)) {
  217. return StreamData.Required;
  218. }
  219. // TODO: just have a state selector (can use nextState or subState within CodecState decoder!)
  220. if (FMTHeader.compressionCode != 0x01 &&
  221. FMTHeader.compressionCode != 0x03 &&
  222. FMTHeader.compressionCode != 0x50) {
  223. Console.putln("WAVE: Unsupported Compression Type");
  224. return StreamData.Invalid;
  225. }
  226. if (FMTHeader.compressionCode == 1 || FMTHeader.compressionCode == 3) {
  227. wf.compressionType = FMTHeader.compressionCode;
  228. wf.numChannels = FMTHeader.numChannels;
  229. wf.samplesPerSecond = FMTHeader.sampleRate;
  230. wf.averageBytesPerSecond =
  231. //FMTHeader.sampleRate * (FMTHeader.significantBitsPerSample / 8) * FMTHeader.numChannels;
  232. FMTHeader.avgBytesPerSecond;
  233. wf.blockAlign = FMTHeader.blockAlign;
  234. wf.bitsPerSample = FMTHeader.significantBitsPerSample;
  235. // Output for sanity check
  236. /*
  237. Console.putln(" Comression Code: ", wf.compressionType);
  238. Console.putln(" Sample Frequency: ", wf.samplesPerSecond);
  239. Console.putln(" Average Bytes Per Second: ", wf.averageBytesPerSecond);
  240. Console.putln(" Number of Channels: ", wf.numChannels);
  241. Console.putln(" Bits per Sample: ", wf.bitsPerSample);
  242. Console.putln(" Block Align: ", wf.blockAlign );
  243. Console.putln("");
  244. */
  245. bufferTime = new Time(2000000);
  246. }
  247. else {
  248. Console.putln("WAVE: Alternate Codec Requested Via Compression Code");
  249. embeddedCodec = null;
  250. }
  251. formatHeaderFound = true;
  252. stream.rewind(FMTHeader.sizeof);
  253. decoderState = WAVE_STATE_SKIP_CHUNK;
  254. continue;
  255. default:
  256. // -- Default for corrupt files -- //
  257. break;
  258. }
  259. break;
  260. }
  261. return StreamData.Invalid;
  262. }
  263. // Description: This function will advance the stream to the beginning of the buffer that contains the time requested.
  264. StreamData seek(Stream stream, AudioFormat wf, AudioInfo wi, ref Time amount) {
  265. if (decoderState == 0) {
  266. // not inited?
  267. return StreamData.Invalid;
  268. }
  269. if (FMTHeader.compressionCode == 0x50 && embeddedCodec !is null) {
  270. StreamData ret = embeddedCodec.seek(stream, wf, wi, amount);
  271. amount -= embeddedCodec.getCurrentTime();
  272. return ret;
  273. }
  274. else if (!(FMTHeader.compressionCode == 1 || FMTHeader.compressionCode == 3)) {
  275. return StreamData.Invalid;
  276. }
  277. if (amount == curTime) {
  278. Console.putln("ON TIME");
  279. return StreamData.Accepted;
  280. }
  281. if (amount > curTime) {
  282. Console.putln("WE NEED TO GO AHEAD");
  283. // good!
  284. // simply find the section we need to be
  285. // we are buffering 2 seconds...
  286. toSeek = amount;
  287. isSeekBack = false;
  288. isSeek = true;
  289. StreamData ret = decode(stream, null, wi);
  290. amount -= curTime;
  291. return ret;
  292. }
  293. else {
  294. Console.putln("WE NEED TO FALL BEHIND");
  295. // for wave files, this is not altogether a bad thing
  296. // for other types of files, it might be
  297. // mp3 can be variable, and would require a seek from the
  298. // beginning or maintaining a cache of some sort.
  299. toSeek = amount;
  300. isSeekBack = true;
  301. isSeek = true;
  302. StreamData ret = decode(stream, null, wi);
  303. amount -= curTime;
  304. return ret;
  305. }
  306. }
  307. Time length(Stream stream, ref AudioFormat wf, ref AudioInfo wi) {
  308. Time tme = Time.init;
  309. return tme;
  310. }
  311. Time lengthQuick(Stream stream, ref AudioFormat wf, ref AudioInfo wi) {
  312. Time tme = Time.init;
  313. return tme;
  314. }
  315. private:
  316. align(2) struct _djehuty_wave_riff_header {
  317. uint magic;
  318. uint filesize;
  319. uint rifftype;
  320. }
  321. align(2) struct _djehuty_wave_chunk_header {
  322. uint chunkID;
  323. uint chunkSize;
  324. }
  325. struct _djehuty_wave_format_chunk {
  326. ushort compressionCode;
  327. ushort numChannels;
  328. uint sampleRate;
  329. uint avgBytesPerSecond;
  330. ushort blockAlign;
  331. ushort significantBitsPerSample;
  332. ushort extraBytes;
  333. }
  334. const auto WAVE_STATE_INIT = 0;
  335. const auto WAVE_STATE_READ_RIFF = 1;
  336. const auto WAVE_STATE_READ_CHUNK = 2;
  337. const auto WAVE_STATE_SKIP_CHUNK = 3;
  338. const auto WAVE_STATE_CHUNK_FMT = 4;
  339. const auto WAVE_STATE_CHUNK_DATA = 5;
  340. protected:
  341. _djehuty_wave_riff_header RIFFHeader;
  342. _djehuty_wave_format_chunk FMTHeader;
  343. _djehuty_wave_chunk_header curChunk;
  344. bool formatHeaderFound = false;
  345. uint dataToRead = 0;
  346. AudioDecoder embeddedCodec;
  347. AudioFormat wf;
  348. Time bufferTime;
  349. }