aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--audio/decoders/aiff.cpp199
1 files changed, 122 insertions, 77 deletions
diff --git a/audio/decoders/aiff.cpp b/audio/decoders/aiff.cpp
index ef666d1d23..1fc811973f 100644
--- a/audio/decoders/aiff.cpp
+++ b/audio/decoders/aiff.cpp
@@ -24,10 +24,10 @@
* The code in this file is based on information found at
* http://www.borg.com/~jglatt/tech/aiff.htm
*
- * We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX
- * (http://sox.sourceforge.net) may be a good place to start from.
+ * Also partially based on libav's aiffdec.c
*/
+#include "common/debug.h"
#include "common/endian.h"
#include "common/stream.h"
#include "common/substream.h"
@@ -63,23 +63,34 @@ uint32 readExtended(Common::SeekableReadStream &stream) {
return mantissa;
}
-bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) {
- byte buf[4];
+// AIFF versions
+static const uint32 kVersionAIFF = MKTAG('A', 'I', 'F', 'F');
+static const uint32 kVersionAIFC = MKTAG('A', 'I', 'F', 'C');
- stream.read(buf, 4);
- if (memcmp(buf, "FORM", 4) != 0) {
- warning("loadAIFFFromStream: No 'FORM' header");
- return false;
+// Codecs
+static const uint32 kCodecPCM = MKTAG('N', 'O', 'N', 'E'); // very original
+
+SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+ if (stream->readUint32BE() != MKTAG('F', 'O', 'R', 'M')) {
+ warning("makeAIFFStream: No 'FORM' header");
+
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+
+ return 0;
}
- stream.readUint32BE();
+ stream->readUint32BE(); // file size
+
+ uint32 version = stream->readUint32BE();
- // This could be AIFC, but we don't handle that case.
+ if (version != kVersionAIFF && version != kVersionAIFC) {
+ warning("makeAIFFStream: No 'AIFF' or 'AIFC' header");
+
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
- stream.read(buf, 4);
- if (memcmp(buf, "AIFF", 4) != 0) {
- warning("loadAIFFFromStream: No 'AIFF' header");
- return false;
+ return 0;
}
// From here on, we only care about the COMM and SSND chunks, which are
@@ -88,90 +99,124 @@ bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate
bool foundCOMM = false;
bool foundSSND = false;
- uint16 numChannels = 0, bitsPerSample = 0;
- uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0;
+ uint16 channels = 0, bitsPerSample = 0;
+ uint32 blockAlign = 0, rate = 0;
+ uint32 codec = kCodecPCM; // AIFF default
+ Common::SeekableReadStream *dataStream = 0;
- while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) {
- uint32 length, pos;
+ while (!(foundCOMM && foundSSND) && !stream->err() && !stream->eos()) {
+ uint32 tag = stream->readUint32BE();
+ uint32 length = stream->readUint32BE();
+ uint32 pos = stream->pos();
- stream.read(buf, 4);
- length = stream.readUint32BE();
- pos = stream.pos();
+ if (stream->eos() || stream->err())
+ break;
- if (memcmp(buf, "COMM", 4) == 0) {
+ switch (tag) {
+ case MKTAG('C', 'O', 'M', 'M'):
foundCOMM = true;
- numChannels = stream.readUint16BE();
- numSampleFrames = stream.readUint32BE();
- bitsPerSample = stream.readUint16BE();
- rate = readExtended(stream);
- size = numSampleFrames * numChannels * (bitsPerSample / 8);
- } else if (memcmp(buf, "SSND", 4) == 0) {
+ channels = stream->readUint16BE();
+ /* frameCount = */ stream->readUint32BE();
+ bitsPerSample = stream->readUint16BE();
+ rate = readExtended(*stream);
+
+ if (version == kVersionAIFC)
+ codec = stream->readUint32BE();
+ break;
+ case MKTAG('S', 'S', 'N', 'D'):
foundSSND = true;
- offset = stream.readUint32BE();
- blockSize = stream.readUint32BE();
- soundOffset = stream.pos();
+ /* uint32 offset = */ stream->readUint32BE();
+ blockAlign = stream->readUint32BE();
+ dataStream = new Common::SeekableSubReadStream(stream, stream->pos(), stream->pos() + length - 8, disposeAfterUse);
+ break;
+ case MKTAG('F', 'V', 'E', 'R'):
+ switch (stream->readUint32BE()) {
+ case 0:
+ version = kVersionAIFF;
+ break;
+ case 0xA2805140:
+ version = kVersionAIFC;
+ break;
+ default:
+ warning("Unknown AIFF version chunk version");
+ break;
+ }
+ break;
+ case MKTAG('w', 'a', 'v', 'e'):
+ warning("Found unhandled AIFF-C extra data chunk");
+
+ if (!dataStream && disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+
+ delete dataStream;
+ return 0;
+ default:
+ debug(1, "Skipping AIFF '%s' chunk", tag2str(tag));
+ break;
}
- stream.seek(pos + length);
+ stream->seek(pos + length + (length & 1)); // ensure we're also word-aligned
}
if (!foundCOMM) {
- warning("loadAIFFFromStream: Cound not find 'COMM' chunk");
- return false;
- }
-
- if (!foundSSND) {
- warning("loadAIFFFromStream: Cound not find 'SSND' chunk");
- return false;
- }
-
- // We only implement a subset of the AIFF standard.
-
- if (numChannels < 1 || numChannels > 2) {
- warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels);
- return false;
- }
+ warning("makeAIFFStream: Cound not find 'COMM' chunk");
- if (bitsPerSample != 8 && bitsPerSample != 16) {
- warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample);
- return false;
- }
+ if (!dataStream && disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
- if (offset != 0 || blockSize != 0) {
- warning("loadAIFFFromStream: Block-aligned data is not supported");
- return false;
+ delete dataStream;
+ return 0;
}
- // Samples are always signed, and big endian.
-
- flags = 0;
- if (bitsPerSample == 16)
- flags |= Audio::FLAG_16BITS;
- if (numChannels == 2)
- flags |= Audio::FLAG_STEREO;
-
- stream.seek(soundOffset);
-
- // Stream now points at the sample data
-
- return true;
-}
-
-SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream,
- DisposeAfterUse::Flag disposeAfterUse) {
- int size, rate;
- byte flags;
+ if (!foundSSND) {
+ warning("makeAIFFStream: Cound not find 'SSND' chunk");
- if (!loadAIFFFromStream(*stream, size, rate, flags)) {
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
+
return 0;
}
- Common::SeekableReadStream *dataStream = new Common::SeekableSubReadStream(stream, stream->pos(), stream->pos() + size, disposeAfterUse);
+ // We only implement a subset of the AIFF standard.
- // Since we allocated our own stream for the data, we must specify DisposeAfterUse::YES.
- return makeRawStream(dataStream, rate, flags);
+ if (channels < 1 || channels > 2) {
+ warning("makeAIFFStream: Only 1 or 2 channels are supported, not %d", channels);
+ delete dataStream;
+ return 0;
+ }
+
+ switch (codec) {
+ case kCodecPCM:
+ case MKTAG('t', 'w', 'o', 's'):
+ case MKTAG('s', 'o', 'w', 't'): {
+ // PCM samples are always signed.
+ byte rawFlags = 0;
+ if (bitsPerSample == 16)
+ rawFlags |= Audio::FLAG_16BITS;
+ if (channels == 2)
+ rawFlags |= Audio::FLAG_STEREO;
+ if (codec == MKTAG('s', 'o', 'w', 't'))
+ rawFlags |= Audio::FLAG_LITTLE_ENDIAN;
+
+ return makeRawStream(dataStream, rate, rawFlags);
+ }
+ case MKTAG('i', 'm', 'a', '4'):
+ // TODO: QT IMA ADPCM is not Seekable
+ // Need to make this function return only an AudioStream and adapt
+ // calling code to use dynamic_cast.
+ warning("Unhandled AIFF-C QT IMA ADPCM compression");
+ break;
+ case MKTAG('Q', 'D', 'M', '2'):
+ // TODO: Need to figure out how to integrate this
+ // (But hopefully never needed)
+ warning("Unhandled AIFF-C QDM2 compression");
+ break;
+ default:
+ warning("Unhandled AIFF-C compression tag '%s'", tag2str(codec));
+ }
+
+ delete dataStream;
+ return 0;
}
} // End of namespace Audio