/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /* * The code in this file is based on information found at * http://www.borg.com/~jglatt/tech/aiff.htm * * Also partially based on libav's aiffdec.c */ #include "common/debug.h" #include "common/endian.h" #include "common/stream.h" #include "common/substream.h" #include "common/textconsole.h" #include "audio/audiostream.h" #include "audio/decoders/aiff.h" #include "audio/decoders/raw.h" #include "audio/decoders/3do.h" namespace Audio { uint32 readExtended(Common::SeekableReadStream &stream) { // The sample rate is stored as an "80 bit IEEE Standard 754 floating // point number (Standard Apple Numeric Environment [SANE] data type // Extended). byte buf[10]; uint32 mantissa; uint32 last = 0; byte exp; stream.read(buf, 10); mantissa = READ_BE_UINT32(buf + 2); exp = 30 - buf[1]; while (exp--) { last = mantissa; mantissa >>= 1; } if (last & 0x00000001) mantissa++; return mantissa; } // AIFF versions static const uint32 kVersionAIFF = MKTAG('A', 'I', 'F', 'F'); static const uint32 kVersionAIFC = MKTAG('A', 'I', 'F', 'C'); // Codecs static const uint32 kCodecPCM = MKTAG('N', 'O', 'N', 'E'); // very original RewindableAudioStream *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(); // file size uint32 version = stream->readUint32BE(); if (version != kVersionAIFF && version != kVersionAIFC) { warning("makeAIFFStream: No 'AIFF' or 'AIFC' header"); if (disposeAfterUse == DisposeAfterUse::YES) delete stream; return 0; } // From here on, we only care about the COMM and SSND chunks, which are // the only required chunks. bool foundCOMM = false; bool foundSSND = false; uint16 channels = 0, bitsPerSample = 0; uint32 rate = 0; uint32 codec = kCodecPCM; // AIFF default Common::SeekableReadStream *dataStream = 0; while (!(foundCOMM && foundSSND) && !stream->err() && !stream->eos()) { uint32 tag = stream->readUint32BE(); uint32 length = stream->readUint32BE(); uint32 pos = stream->pos(); if (stream->eos() || stream->err()) break; switch (tag) { case MKTAG('C', 'O', 'M', 'M'): foundCOMM = true; 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; /* uint32 offset = */ stream->readUint32BE(); /* uint32 blockAlign = */ stream->readUint32BE(); if (dataStream) delete dataStream; 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 + (length & 1)); // ensure we're also word-aligned } if (!foundCOMM) { warning("makeAIFFStream: Cound not find 'COMM' chunk"); if (!dataStream && disposeAfterUse == DisposeAfterUse::YES) delete stream; delete dataStream; return 0; } if (!foundSSND) { warning("makeAIFFStream: Cound not find 'SSND' chunk"); if (disposeAfterUse == DisposeAfterUse::YES) delete stream; return 0; } // We only implement a subset of the AIFF standard. if (channels < 1 || channels > 2) { warning("makeAIFFStream: Only 1 or 2 channels are supported, not %d", channels); delete dataStream; return 0; } // Seek to the start of dataStream, required for at least FileStream dataStream->seek(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: Use QT IMA ADPCM 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; case MKTAG('A', 'D', 'P', '4'): // ADP4 on 3DO return make3DO_ADP4AudioStream(dataStream, rate, channels == 2); case MKTAG('S', 'D', 'X', '2'): // SDX2 on 3DO return make3DO_SDX2AudioStream(dataStream, rate, channels == 2); default: warning("Unhandled AIFF-C compression tag '%s'", tag2str(codec)); } delete dataStream; return 0; } } // End of namespace Audio