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