Initial commit
This commit is contained in:
341
audio/decoders/3do.cpp
Normal file
341
audio/decoders/3do.cpp
Normal file
@@ -0,0 +1,341 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/textconsole.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/decoders/3do.h"
|
||||
#include "audio/decoders/adpcm_intern.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Reuses ADPCM table
|
||||
#define audio_3DO_ADP4_stepSizeTable Ima_ADPCMStream::_imaTable
|
||||
#define audio_3DO_ADP4_stepSizeIndex ADPCMStream::_stepAdjustTable
|
||||
|
||||
RewindableAudioStream *make3DO_ADP4AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace) {
|
||||
if (stereo) {
|
||||
warning("make3DO_ADP4Stream(): stereo currently not supported");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (audioLengthMSecsPtr) {
|
||||
// Caller requires the milliseconds of audio
|
||||
uint32 audioLengthMSecs = stream->size() * 2 * 1000 / sampleRate; // 1 byte == 2 16-bit sample
|
||||
if (stereo) {
|
||||
audioLengthMSecs /= 2;
|
||||
}
|
||||
*audioLengthMSecsPtr = audioLengthMSecs;
|
||||
}
|
||||
|
||||
return new Audio3DO_ADP4_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace);
|
||||
}
|
||||
|
||||
Audio3DO_ADP4_Stream::Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace)
|
||||
: _sampleRate(sampleRate), _stereo(stereo),
|
||||
_stream(stream, disposeAfterUse) {
|
||||
|
||||
_callerDecoderData = persistentSpace;
|
||||
memset(&_initialDecoderData, 0, sizeof(_initialDecoderData));
|
||||
_initialRead = true;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Audio3DO_ADP4_Stream::reset() {
|
||||
memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData));
|
||||
_streamBytesLeft = _stream->size();
|
||||
_stream->seek(0);
|
||||
}
|
||||
|
||||
bool Audio3DO_ADP4_Stream::rewind() {
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
int16 Audio3DO_ADP4_Stream::decodeSample(byte compressedNibble) {
|
||||
int16 currentStep = audio_3DO_ADP4_stepSizeTable[_curDecoderData.stepIndex];
|
||||
int32 decodedSample = _curDecoderData.lastSample;
|
||||
int16 delta = currentStep >> 3;
|
||||
|
||||
if (compressedNibble & 1)
|
||||
delta += currentStep >> 2;
|
||||
|
||||
if (compressedNibble & 2)
|
||||
delta += currentStep >> 1;
|
||||
|
||||
if (compressedNibble & 4)
|
||||
delta += currentStep;
|
||||
|
||||
if (compressedNibble & 8) {
|
||||
decodedSample -= delta;
|
||||
} else {
|
||||
decodedSample += delta;
|
||||
}
|
||||
|
||||
_curDecoderData.lastSample = CLIP<int32>(decodedSample, -32768, 32767);
|
||||
|
||||
_curDecoderData.stepIndex += audio_3DO_ADP4_stepSizeIndex[compressedNibble & 0x07];
|
||||
_curDecoderData.stepIndex = CLIP<int16>(_curDecoderData.stepIndex, 0, ARRAYSIZE(audio_3DO_ADP4_stepSizeTable) - 1);
|
||||
|
||||
return _curDecoderData.lastSample;
|
||||
}
|
||||
|
||||
// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written
|
||||
int Audio3DO_ADP4_Stream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int8 byteCache[AUDIO_3DO_CACHE_SIZE];
|
||||
int8 *byteCachePtr = nullptr;
|
||||
int byteCacheSize = 0;
|
||||
int requestedBytesLeft = 0;
|
||||
int decodedSamplesCount = 0;
|
||||
|
||||
int8 compressedByte = 0;
|
||||
|
||||
if (endOfData())
|
||||
return 0; // no more bytes left
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data over
|
||||
memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData));
|
||||
if (_initialRead) {
|
||||
_initialRead = false;
|
||||
memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData));
|
||||
}
|
||||
}
|
||||
|
||||
requestedBytesLeft = numSamples >> 1; // 1 byte for 2 16-bit sample
|
||||
if (requestedBytesLeft > _streamBytesLeft)
|
||||
requestedBytesLeft = _streamBytesLeft; // not enough bytes left
|
||||
|
||||
// in case caller requests an uneven amount of samples, we will return an even amount
|
||||
|
||||
// buffering, so that direct decoding of files and such runs way faster
|
||||
while (requestedBytesLeft) {
|
||||
if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) {
|
||||
byteCacheSize = AUDIO_3DO_CACHE_SIZE;
|
||||
} else {
|
||||
byteCacheSize = requestedBytesLeft;
|
||||
}
|
||||
|
||||
requestedBytesLeft -= byteCacheSize;
|
||||
_streamBytesLeft -= byteCacheSize;
|
||||
|
||||
// Fill our byte cache
|
||||
_stream->read(byteCache, byteCacheSize);
|
||||
|
||||
byteCachePtr = byteCache;
|
||||
|
||||
// Mono
|
||||
while (byteCacheSize) {
|
||||
compressedByte = *byteCachePtr++;
|
||||
byteCacheSize--;
|
||||
|
||||
buffer[decodedSamplesCount] = decodeSample(compressedByte >> 4);
|
||||
decodedSamplesCount++;
|
||||
buffer[decodedSamplesCount] = decodeSample(compressedByte & 0x0f);
|
||||
decodedSamplesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data back
|
||||
memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData));
|
||||
}
|
||||
|
||||
return decodedSamplesCount;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
static const int16 audio_3DO_SDX2_SquareTable[256] = {
|
||||
-32768,-32258,-31752,-31250,-30752,-30258,-29768,-29282,-28800,-28322,
|
||||
-27848,-27378,-26912,-26450,-25992,-25538,-25088,-24642,-24200,-23762,
|
||||
-23328,-22898,-22472,-22050,-21632,-21218,-20808,-20402,-20000,-19602,
|
||||
-19208,-18818,-18432,-18050,-17672,-17298,-16928,-16562,-16200,-15842,
|
||||
-15488,-15138,-14792,-14450,-14112,-13778,-13448,-13122,-12800,-12482,
|
||||
-12168,-11858,-11552,-11250,-10952,-10658,-10368,-10082, -9800, -9522,
|
||||
-9248, -8978, -8712, -8450, -8192, -7938, -7688, -7442, -7200, -6962,
|
||||
-6728, -6498, -6272, -6050, -5832, -5618, -5408, -5202, -5000, -4802,
|
||||
-4608, -4418, -4232, -4050, -3872, -3698, -3528, -3362, -3200, -3042,
|
||||
-2888, -2738, -2592, -2450, -2312, -2178, -2048, -1922, -1800, -1682,
|
||||
-1568, -1458, -1352, -1250, -1152, -1058, -968, -882, -800, -722,
|
||||
-648, -578, -512, -450, -392, -338, -288, -242, -200, -162,
|
||||
-128, -98, -72, -50, -32, -18, -8, -2, 0, 2,
|
||||
8, 18, 32, 50, 72, 98, 128, 162, 200, 242,
|
||||
288, 338, 392, 450, 512, 578, 648, 722, 800, 882,
|
||||
968, 1058, 1152, 1250, 1352, 1458, 1568, 1682, 1800, 1922,
|
||||
2048, 2178, 2312, 2450, 2592, 2738, 2888, 3042, 3200, 3362,
|
||||
3528, 3698, 3872, 4050, 4232, 4418, 4608, 4802, 5000, 5202,
|
||||
5408, 5618, 5832, 6050, 6272, 6498, 6728, 6962, 7200, 7442,
|
||||
7688, 7938, 8192, 8450, 8712, 8978, 9248, 9522, 9800, 10082,
|
||||
10368, 10658, 10952, 11250, 11552, 11858, 12168, 12482, 12800, 13122,
|
||||
13448, 13778, 14112, 14450, 14792, 15138, 15488, 15842, 16200, 16562,
|
||||
16928, 17298, 17672, 18050, 18432, 18818, 19208, 19602, 20000, 20402,
|
||||
20808, 21218, 21632, 22050, 22472, 22898, 23328, 23762, 24200, 24642,
|
||||
25088, 25538, 25992, 26450, 26912, 27378, 27848, 28322, 28800, 29282,
|
||||
29768, 30258, 30752, 31250, 31752, 32258
|
||||
};
|
||||
|
||||
Audio3DO_SDX2_Stream::Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace)
|
||||
: _sampleRate(sampleRate), _stereo(stereo),
|
||||
_stream(stream, disposeAfterUse) {
|
||||
|
||||
_callerDecoderData = persistentSpace;
|
||||
memset(&_initialDecoderData, 0, sizeof(_initialDecoderData));
|
||||
_initialRead = true;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Audio3DO_SDX2_Stream::reset() {
|
||||
memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData));
|
||||
_streamBytesLeft = _stream->size();
|
||||
_stream->seek(0);
|
||||
}
|
||||
|
||||
bool Audio3DO_SDX2_Stream::rewind() {
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written
|
||||
int Audio3DO_SDX2_Stream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int8 byteCache[AUDIO_3DO_CACHE_SIZE];
|
||||
int8 *byteCachePtr = nullptr;
|
||||
int byteCacheSize = 0;
|
||||
int requestedBytesLeft = numSamples; // 1 byte per 16-bit sample
|
||||
int decodedSamplesCount = 0;
|
||||
|
||||
int8 compressedByte = 0;
|
||||
uint8 squareTableOffset = 0;
|
||||
int16 decodedSample = 0;
|
||||
|
||||
if (endOfData())
|
||||
return 0; // no more bytes left
|
||||
|
||||
if (_stereo) {
|
||||
// We expect numSamples to be even in case of Stereo audio
|
||||
assert((numSamples & 1) == 0);
|
||||
}
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data over
|
||||
memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData));
|
||||
if (_initialRead) {
|
||||
_initialRead = false;
|
||||
memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData));
|
||||
}
|
||||
}
|
||||
|
||||
requestedBytesLeft = numSamples;
|
||||
if (requestedBytesLeft > _streamBytesLeft)
|
||||
requestedBytesLeft = _streamBytesLeft; // not enough bytes left
|
||||
|
||||
// buffering, so that direct decoding of files and such runs way faster
|
||||
while (requestedBytesLeft) {
|
||||
if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) {
|
||||
byteCacheSize = AUDIO_3DO_CACHE_SIZE;
|
||||
} else {
|
||||
byteCacheSize = requestedBytesLeft;
|
||||
}
|
||||
|
||||
requestedBytesLeft -= byteCacheSize;
|
||||
_streamBytesLeft -= byteCacheSize;
|
||||
|
||||
// Fill our byte cache
|
||||
_stream->read(byteCache, byteCacheSize);
|
||||
|
||||
byteCachePtr = byteCache;
|
||||
|
||||
if (!_stereo) {
|
||||
// Mono
|
||||
while (byteCacheSize) {
|
||||
compressedByte = *byteCachePtr++;
|
||||
byteCacheSize--;
|
||||
squareTableOffset = compressedByte + 128;
|
||||
|
||||
if (!(compressedByte & 1))
|
||||
_curDecoderData.lastSample1 = 0;
|
||||
|
||||
decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset];
|
||||
_curDecoderData.lastSample1 = decodedSample;
|
||||
|
||||
buffer[decodedSamplesCount] = decodedSample;
|
||||
decodedSamplesCount++;
|
||||
}
|
||||
} else {
|
||||
// Stereo
|
||||
while (byteCacheSize) {
|
||||
compressedByte = *byteCachePtr++;
|
||||
byteCacheSize--;
|
||||
squareTableOffset = compressedByte + 128;
|
||||
|
||||
if (!(decodedSamplesCount & 1)) {
|
||||
// First channel
|
||||
if (!(compressedByte & 1))
|
||||
_curDecoderData.lastSample1 = 0;
|
||||
|
||||
decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset];
|
||||
_curDecoderData.lastSample1 = decodedSample;
|
||||
} else {
|
||||
// Second channel
|
||||
if (!(compressedByte & 1))
|
||||
_curDecoderData.lastSample2 = 0;
|
||||
|
||||
decodedSample = _curDecoderData.lastSample2 + audio_3DO_SDX2_SquareTable[squareTableOffset];
|
||||
_curDecoderData.lastSample2 = decodedSample;
|
||||
}
|
||||
|
||||
buffer[decodedSamplesCount] = decodedSample;
|
||||
decodedSamplesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data back
|
||||
memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData));
|
||||
}
|
||||
|
||||
return decodedSamplesCount;
|
||||
}
|
||||
|
||||
RewindableAudioStream *make3DO_SDX2AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace) {
|
||||
if (stereo) {
|
||||
if (stream->size() & 1) {
|
||||
warning("make3DO_SDX2Stream(): stereo data is uneven size");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioLengthMSecsPtr) {
|
||||
// Caller requires the milliseconds of audio
|
||||
uint32 audioLengthMSecs = stream->size() * 1000 / sampleRate; // 1 byte == 1 16-bit sample
|
||||
if (stereo) {
|
||||
audioLengthMSecs /= 2;
|
||||
}
|
||||
*audioLengthMSecsPtr = audioLengthMSecs;
|
||||
}
|
||||
|
||||
return new Audio3DO_SDX2_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
150
audio/decoders/3do.h
Normal file
150
audio/decoders/3do.h
Normal file
@@ -0,0 +1,150 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - sherlock (3DO version of Serrated Scalpel)
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_3DO_SDX2_H
|
||||
#define AUDIO_3DO_SDX2_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// amount of bytes to be used within the decoder classes as buffers
|
||||
#define AUDIO_3DO_CACHE_SIZE 1024
|
||||
|
||||
// persistent spaces
|
||||
struct audio_3DO_ADP4_PersistentSpace {
|
||||
int16 lastSample;
|
||||
int16 stepIndex;
|
||||
};
|
||||
|
||||
struct audio_3DO_SDX2_PersistentSpace {
|
||||
int16 lastSample1;
|
||||
int16 lastSample2;
|
||||
};
|
||||
|
||||
class Audio3DO_ADP4_Stream : public RewindableAudioStream {
|
||||
public:
|
||||
Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace);
|
||||
|
||||
protected:
|
||||
const uint16 _sampleRate;
|
||||
const bool _stereo;
|
||||
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream;
|
||||
int32 _streamBytesLeft;
|
||||
|
||||
void reset();
|
||||
bool rewind();
|
||||
bool endOfData() const { return (_stream->pos() >= _stream->size()); }
|
||||
bool isStereo() const { return _stereo; }
|
||||
int getRate() const { return _sampleRate; }
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
bool _initialRead;
|
||||
audio_3DO_ADP4_PersistentSpace *_callerDecoderData;
|
||||
audio_3DO_ADP4_PersistentSpace _initialDecoderData;
|
||||
audio_3DO_ADP4_PersistentSpace _curDecoderData;
|
||||
|
||||
private:
|
||||
int16 decodeSample(byte compressedNibble);
|
||||
};
|
||||
|
||||
class Audio3DO_SDX2_Stream : public RewindableAudioStream {
|
||||
public:
|
||||
Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpacePtr);
|
||||
|
||||
protected:
|
||||
const uint16 _sampleRate;
|
||||
const bool _stereo;
|
||||
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream;
|
||||
int32 _streamBytesLeft;
|
||||
|
||||
void reset();
|
||||
bool rewind();
|
||||
bool endOfData() const { return (_stream->pos() >= _stream->size()); }
|
||||
bool isStereo() const { return _stereo; }
|
||||
int getRate() const { return _sampleRate; }
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
bool _initialRead;
|
||||
audio_3DO_SDX2_PersistentSpace *_callerDecoderData;
|
||||
audio_3DO_SDX2_PersistentSpace _initialDecoderData;
|
||||
audio_3DO_SDX2_PersistentSpace _curDecoderData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to decode 3DO ADP4 data from the given seekable stream and create a SeekableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the 3DO SDX2 data
|
||||
* @sampleRate sample rate
|
||||
* @stereo if it's stereo or mono
|
||||
* @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds
|
||||
* @disposeAfterUse disposeAfterUse whether to delete the stream after use
|
||||
* @persistentSpacePtr pointer to the persistent space structure
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
RewindableAudioStream *make3DO_ADP4AudioStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
uint16 sampleRate,
|
||||
bool stereo,
|
||||
uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES,
|
||||
audio_3DO_ADP4_PersistentSpace *persistentSpacePtr = NULL
|
||||
);
|
||||
|
||||
/**
|
||||
* Try to decode 3DO SDX2 data from the given seekable stream and create a SeekableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the 3DO SDX2 data
|
||||
* @sampleRate sample rate
|
||||
* @stereo if it's stereo or mono
|
||||
* @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds
|
||||
* @disposeAfterUse disposeAfterUse whether to delete the stream after use
|
||||
* @persistentSpacePtr pointer to the persistent space structure
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
RewindableAudioStream *make3DO_SDX2AudioStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
uint16 sampleRate,
|
||||
bool stereo,
|
||||
uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES,
|
||||
audio_3DO_SDX2_PersistentSpace *persistentSpacePtr = NULL
|
||||
);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
130
audio/decoders/aac.cpp
Normal file
130
audio/decoders/aac.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/decoders/aac.h"
|
||||
|
||||
#ifdef USE_FAAD
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/codec.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
|
||||
#include <neaacdec.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class AACDecoder : public Codec {
|
||||
public:
|
||||
AACDecoder(Common::SeekableReadStream *extraData,
|
||||
DisposeAfterUse::Flag disposeExtraData);
|
||||
~AACDecoder();
|
||||
|
||||
AudioStream *decodeFrame(Common::SeekableReadStream &stream) override;
|
||||
|
||||
private:
|
||||
NeAACDecHandle _handle;
|
||||
byte _channels;
|
||||
unsigned long _rate;
|
||||
};
|
||||
|
||||
AACDecoder::AACDecoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) {
|
||||
// Open the library
|
||||
_handle = NeAACDecOpen();
|
||||
|
||||
// Configure the library to our needs
|
||||
NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(_handle);
|
||||
conf->outputFormat = FAAD_FMT_16BIT; // We only support 16bit audio
|
||||
conf->downMatrix = 1; // Convert from 5.1 to stereo if required
|
||||
NeAACDecSetConfiguration(_handle, conf);
|
||||
|
||||
// Copy the extra data to a buffer
|
||||
extraData->seek(0);
|
||||
byte *extraDataBuf = new byte[extraData->size()];
|
||||
extraData->read(extraDataBuf, extraData->size());
|
||||
|
||||
// Initialize with our extra data
|
||||
// NOTE: This code assumes the extra data is coming from an MPEG-4 file!
|
||||
int err = NeAACDecInit2(_handle, extraDataBuf, extraData->size(), &_rate, &_channels);
|
||||
delete[] extraDataBuf;
|
||||
|
||||
if (err < 0)
|
||||
error("Could not initialize AAC decoder: %s", NeAACDecGetErrorMessage(err));
|
||||
|
||||
if (disposeExtraData == DisposeAfterUse::YES)
|
||||
delete extraData;
|
||||
}
|
||||
|
||||
AACDecoder::~AACDecoder() {
|
||||
NeAACDecClose(_handle);
|
||||
}
|
||||
|
||||
AudioStream *AACDecoder::decodeFrame(Common::SeekableReadStream &stream) {
|
||||
// read everything into a buffer
|
||||
uint32 inBufferPos = 0;
|
||||
uint32 inBufferSize = stream.size();
|
||||
byte *inBuffer = new byte[inBufferSize];
|
||||
stream.read(inBuffer, inBufferSize);
|
||||
|
||||
QueuingAudioStream *audioStream = makeQueuingAudioStream(_rate, _channels == 2);
|
||||
|
||||
// Decode until we have enough samples (or there's no more left)
|
||||
while (inBufferPos < inBufferSize) {
|
||||
NeAACDecFrameInfo frameInfo;
|
||||
void *decodedSamples = NeAACDecDecode(_handle, &frameInfo, inBuffer + inBufferPos, inBufferSize - inBufferPos);
|
||||
|
||||
if (frameInfo.error != 0)
|
||||
error("Failed to decode AAC frame: %s", NeAACDecGetErrorMessage(frameInfo.error));
|
||||
|
||||
byte *buffer = (byte *)malloc(frameInfo.samples * 2);
|
||||
memcpy(buffer, decodedSamples, frameInfo.samples * 2);
|
||||
|
||||
byte flags = FLAG_16BITS;
|
||||
|
||||
if (_channels == 2)
|
||||
flags |= FLAG_STEREO;
|
||||
|
||||
#ifdef SCUMM_LITTLE_ENDIAN
|
||||
flags |= FLAG_LITTLE_ENDIAN;
|
||||
#endif
|
||||
|
||||
audioStream->queueBuffer(buffer, frameInfo.samples * 2, DisposeAfterUse::YES, flags);
|
||||
|
||||
inBufferPos += frameInfo.bytesconsumed;
|
||||
}
|
||||
|
||||
audioStream->finish();
|
||||
return audioStream;
|
||||
}
|
||||
|
||||
// Factory function
|
||||
Codec *makeAACDecoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) {
|
||||
return new AACDecoder(extraData, disposeExtraData);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_FAAD
|
||||
61
audio/decoders/aac.h
Normal file
61
audio/decoders/aac.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - groovie
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_AAC_H
|
||||
#define AUDIO_AAC_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#ifdef USE_FAAD
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class Codec;
|
||||
|
||||
/**
|
||||
* Create a new Codec for decoding AAC data of an MPEG-4 file in the given stream.
|
||||
*
|
||||
* @note This should *only* be called by our QuickTime/MPEG-4 decoder since it relies
|
||||
* on the MPEG-4 extra data. If you want to decode a file using AAC, go use
|
||||
* makeQuickTimeStream() instead!
|
||||
* @param extraData the SeekableReadStream from which to read the AAC extra data
|
||||
* @param disposeExtraData whether to delete the extra data stream after use
|
||||
* @return a new Codec, or NULL, if an error occurred
|
||||
*/
|
||||
Codec *makeAACDecoder(
|
||||
Common::SeekableReadStream *extraData,
|
||||
DisposeAfterUse::Flag disposeExtraData = DisposeAfterUse::NO);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_FAAD
|
||||
#endif // #ifndef AUDIO_AAC_H
|
||||
202
audio/decoders/ac3.cpp
Normal file
202
audio/decoders/ac3.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/ptr.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/ac3.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
|
||||
extern "C" {
|
||||
#include <a52dec/a52.h>
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class AC3Stream : public PacketizedAudioStream {
|
||||
public:
|
||||
AC3Stream(double decibel);
|
||||
~AC3Stream();
|
||||
|
||||
bool init(Common::SeekableReadStream &firstPacket);
|
||||
void deinit();
|
||||
|
||||
// AudioStream API
|
||||
int readBuffer(int16 *buffer, const int numSamples) override { return _audStream->readBuffer(buffer, numSamples); }
|
||||
bool isStereo() const override { return _audStream->isStereo(); }
|
||||
int getRate() const override { return _audStream->getRate(); }
|
||||
bool endOfData() const override { return _audStream->endOfData(); }
|
||||
bool endOfStream() const override { return _audStream->endOfStream(); }
|
||||
|
||||
// PacketizedAudioStream API
|
||||
void queuePacket(Common::SeekableReadStream *data) override;
|
||||
void finish() override { _audStream->finish(); }
|
||||
|
||||
private:
|
||||
Common::ScopedPtr<QueuingAudioStream> _audStream;
|
||||
a52_state_t *_a52State;
|
||||
uint32 _frameSize;
|
||||
byte _inBuf[4096];
|
||||
byte *_inBufPtr;
|
||||
int _flags;
|
||||
int _sampleRate;
|
||||
double _audioGain;
|
||||
};
|
||||
|
||||
AC3Stream::AC3Stream(double decibel) : _a52State(nullptr), _frameSize(0), _inBufPtr(nullptr), _flags(0), _sampleRate(0) {
|
||||
_audioGain = pow(2, decibel / 6);
|
||||
}
|
||||
|
||||
AC3Stream::~AC3Stream() {
|
||||
deinit();
|
||||
}
|
||||
|
||||
enum {
|
||||
HEADER_SIZE = 7
|
||||
};
|
||||
|
||||
bool AC3Stream::init(Common::SeekableReadStream &firstPacket) {
|
||||
deinit();
|
||||
|
||||
// In theory, I should pass mm_accel() to a52_init(), but I don't know
|
||||
// where that's supposed to be defined.
|
||||
_a52State = a52_init(0);
|
||||
|
||||
// Go through the header to find sync
|
||||
byte buf[HEADER_SIZE];
|
||||
_sampleRate = -1;
|
||||
|
||||
for (uint i = 0; i < firstPacket.size() - sizeof(buf); i++) {
|
||||
int flags, bitRate;
|
||||
firstPacket.seek(i);
|
||||
firstPacket.read(buf, sizeof(buf));
|
||||
|
||||
if (a52_syncinfo(buf, &flags, &_sampleRate, &bitRate) > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure we have a valid sample rate
|
||||
if (_sampleRate <= 0) {
|
||||
deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
_audStream.reset(makeQueuingAudioStream(_sampleRate, true));
|
||||
_inBufPtr = _inBuf;
|
||||
_flags = 0;
|
||||
_frameSize = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AC3Stream::deinit() {
|
||||
if (!_a52State)
|
||||
return;
|
||||
|
||||
_audStream.reset();
|
||||
a52_free(_a52State);
|
||||
_a52State = nullptr;
|
||||
}
|
||||
|
||||
void AC3Stream::queuePacket(Common::SeekableReadStream *data) {
|
||||
Common::ScopedPtr<Common::SeekableReadStream> packet(data);
|
||||
|
||||
while (packet->pos() < packet->size()) {
|
||||
uint32 leftSize = packet->size() - packet->pos();
|
||||
uint32 len = _inBufPtr - _inBuf;
|
||||
if (_frameSize == 0) {
|
||||
// No header seen: find one
|
||||
len = HEADER_SIZE - len;
|
||||
if (len > leftSize)
|
||||
len = leftSize;
|
||||
packet->read(_inBufPtr, len);
|
||||
leftSize -= len;
|
||||
_inBufPtr += len;
|
||||
if ((_inBufPtr - _inBuf) == HEADER_SIZE) {
|
||||
int sampleRate, bitRate;
|
||||
len = a52_syncinfo(_inBuf, &_flags, &sampleRate, &bitRate);
|
||||
if (len == 0) {
|
||||
memmove(_inBuf, _inBuf + 1, HEADER_SIZE - 1);
|
||||
_inBufPtr--;
|
||||
} else {
|
||||
_frameSize = len;
|
||||
}
|
||||
}
|
||||
} else if (len < _frameSize) {
|
||||
len = _frameSize - len;
|
||||
if (len > leftSize)
|
||||
len = leftSize;
|
||||
|
||||
assert(len < sizeof(_inBuf) - (_inBufPtr - _inBuf));
|
||||
packet->read(_inBufPtr, len);
|
||||
leftSize -= len;
|
||||
_inBufPtr += len;
|
||||
} else {
|
||||
// TODO: Eventually support more than just stereo max
|
||||
int flags = A52_STEREO | A52_ADJUST_LEVEL;
|
||||
sample_t level = 32767 * _audioGain;
|
||||
|
||||
if (a52_frame(_a52State, _inBuf, &flags, &level, 0) != 0)
|
||||
error("Frame fail");
|
||||
|
||||
int16 *outputBuffer = (int16 *)malloc(6 * 256 * 2 * 2);
|
||||
int16 *outputPtr = outputBuffer;
|
||||
int outputLength = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (a52_block(_a52State) == 0) {
|
||||
sample_t *samples = a52_samples(_a52State);
|
||||
for (int j = 0; j < 256; j++) {
|
||||
*outputPtr++ = (int16)CLIP<sample_t>(samples[j], -32768, 32767);
|
||||
*outputPtr++ = (int16)CLIP<sample_t>(samples[j + 256], -32768, 32767);
|
||||
}
|
||||
|
||||
outputLength += 1024;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputLength > 0) {
|
||||
flags = FLAG_STEREO | FLAG_16BITS;
|
||||
|
||||
#ifdef SCUMM_LITTLE_ENDIAN
|
||||
flags |= FLAG_LITTLE_ENDIAN;
|
||||
#endif
|
||||
|
||||
_audStream->queueBuffer((byte *)outputBuffer, outputLength, DisposeAfterUse::YES, flags);
|
||||
}
|
||||
|
||||
_inBufPtr = _inBuf;
|
||||
_frameSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PacketizedAudioStream *makeAC3Stream(Common::SeekableReadStream &firstPacket, double decibel) {
|
||||
Common::ScopedPtr<AC3Stream> stream(new AC3Stream(decibel));
|
||||
if (!stream->init(firstPacket))
|
||||
return nullptr;
|
||||
|
||||
return stream.release();
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
50
audio/decoders/ac3.h
Normal file
50
audio/decoders/ac3.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_DECODERS_AC3_H
|
||||
#define AUDIO_DECODERS_AC3_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
#ifdef USE_A52
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
} // End of namespace Common
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class PacketizedAudioStream;
|
||||
|
||||
/**
|
||||
* Create a PacketizedAudioStream that decodes AC-3 sound
|
||||
*
|
||||
* @param firstPacket The stream containing the first packet of data
|
||||
* @return A new PacketizedAudioStream, or NULL on error
|
||||
*/
|
||||
PacketizedAudioStream *makeAC3Stream(Common::SeekableReadStream &firstPacket, double decibel = 0.0);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
619
audio/decoders/adpcm.cpp
Normal file
619
audio/decoders/adpcm.cpp
Normal file
@@ -0,0 +1,619 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/decoders/adpcm.h"
|
||||
#include "audio/decoders/adpcm_intern.h"
|
||||
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Routines to convert 12 bit linear samples to the
|
||||
// Dialogic or Oki ADPCM coding format aka VOX.
|
||||
// See also
|
||||
// <https://web.archive.org/web/20050421220515/http://www.comptek.ru/telephony/tnotes/tt1-13.html>
|
||||
//
|
||||
// IMA ADPCM support is based on
|
||||
// <https://wiki.multimedia.cx/index.php?title=IMA_ADPCM>
|
||||
//
|
||||
// In addition, also MS IMA ADPCM is supported. See
|
||||
// <https://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM>.
|
||||
//
|
||||
// XA ADPCM support is based on FFmpeg/libav
|
||||
|
||||
ADPCMStream::ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||
: _stream(stream, disposeAfterUse),
|
||||
_startpos(stream->pos()),
|
||||
_endpos(_startpos + size),
|
||||
_channels(channels),
|
||||
_blockAlign(blockAlign),
|
||||
_rate(rate) {
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void ADPCMStream::reset() {
|
||||
memset(&_status, 0, sizeof(_status));
|
||||
_blockPos[0] = _blockPos[1] = _blockAlign; // To make sure first header is read
|
||||
}
|
||||
|
||||
bool ADPCMStream::rewind() {
|
||||
// TODO: Error checking.
|
||||
reset();
|
||||
_stream->seek(_startpos);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
int Oki_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samples;
|
||||
byte data;
|
||||
|
||||
for (samples = 0; samples < numSamples && !endOfData(); samples++) {
|
||||
if (_decodedSampleCount == 0) {
|
||||
data = _stream->readByte();
|
||||
_decodedSamples[0] = decodeOKI((data >> 4) & 0x0f);
|
||||
_decodedSamples[1] = decodeOKI((data >> 0) & 0x0f);
|
||||
_decodedSampleCount = 2;
|
||||
}
|
||||
|
||||
// (1 - (count - 1)) ensures that _decodedSamples acts as a FIFO of depth 2
|
||||
buffer[samples] = _decodedSamples[1 - (_decodedSampleCount - 1)];
|
||||
_decodedSampleCount--;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
static const int16 okiStepSize[49] = {
|
||||
16, 17, 19, 21, 23, 25, 28, 31,
|
||||
34, 37, 41, 45, 50, 55, 60, 66,
|
||||
73, 80, 88, 97, 107, 118, 130, 143,
|
||||
157, 173, 190, 209, 230, 253, 279, 307,
|
||||
337, 371, 408, 449, 494, 544, 598, 658,
|
||||
724, 796, 876, 963, 1060, 1166, 1282, 1411,
|
||||
1552
|
||||
};
|
||||
|
||||
// Decode Linear to ADPCM
|
||||
int16 Oki_ADPCMStream::decodeOKI(byte code) {
|
||||
int16 diff, E, samp;
|
||||
|
||||
E = (2 * (code & 0x7) + 1) * okiStepSize[_status.ima_ch[0].stepIndex] / 8;
|
||||
diff = (code & 0x08) ? -E : E;
|
||||
samp = _status.ima_ch[0].last + diff;
|
||||
// Clip the values to +/- 2^11 (supposed to be 12 bits)
|
||||
samp = CLIP<int16>(samp, -2048, 2047);
|
||||
|
||||
_status.ima_ch[0].last = samp;
|
||||
_status.ima_ch[0].stepIndex += _stepAdjustTable[code];
|
||||
_status.ima_ch[0].stepIndex = CLIP<int32>(_status.ima_ch[0].stepIndex, 0, ARRAYSIZE(okiStepSize) - 1);
|
||||
|
||||
// * 16 effectively converts 12-bit input to 16-bit output
|
||||
return samp * 16;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
int XA_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samples;
|
||||
byte *data = new byte[128];
|
||||
|
||||
for (samples = 0; samples < numSamples && !endOfData(); samples++) {
|
||||
if (_decodedSampleCount == 0) {
|
||||
uint32 bytesLeft = _stream->size() - _stream->pos();
|
||||
if (bytesLeft < 128) {
|
||||
_stream->skip(bytesLeft);
|
||||
memset(&buffer[samples], 0, (numSamples - samples) * sizeof(uint16));
|
||||
samples = numSamples;
|
||||
break;
|
||||
}
|
||||
_stream->read(data, 128);
|
||||
decodeXA(data);
|
||||
_decodedSampleIndex = 0;
|
||||
}
|
||||
|
||||
// _decodedSamples acts as a FIFO of depth 2 or 4;
|
||||
buffer[samples] = _decodedSamples[_decodedSampleIndex++];
|
||||
_decodedSampleCount--;
|
||||
}
|
||||
|
||||
delete[] data;
|
||||
return samples;
|
||||
}
|
||||
|
||||
static const int s_xaTable[5][2] = {
|
||||
{ 0, 0 },
|
||||
{ 60, 0 },
|
||||
{ 115, -52 },
|
||||
{ 98, -55 },
|
||||
{ 122, -60 }
|
||||
};
|
||||
|
||||
void XA_ADPCMStream::decodeXA(const byte *src) {
|
||||
int16 *leftChannel = _decodedSamples;
|
||||
int16 *rightChannel = _decodedSamples + 1;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int shift = 12 - (src[4 + i * 2] & 0xf);
|
||||
int filter = src[4 + i * 2] >> 4;
|
||||
int f0 = s_xaTable[filter][0];
|
||||
int f1 = s_xaTable[filter][1];
|
||||
int16 s_1 = _status.ima_ch[0].sample[0];
|
||||
int16 s_2 = _status.ima_ch[0].sample[1];
|
||||
|
||||
for (int j = 0; j < 28; j++) {
|
||||
byte d = src[16 + i + j * 4];
|
||||
int t = (int8)(d << 4) >> 4;
|
||||
int s = (t << shift) + ((s_1 * f0 + s_2 * f1 + 32) >> 6);
|
||||
s_2 = s_1;
|
||||
s_1 = CLIP<int>(s, -32768, 32767);
|
||||
*leftChannel = s_1;
|
||||
leftChannel += _channels;
|
||||
_decodedSampleCount++;
|
||||
}
|
||||
|
||||
if (_channels == 2) {
|
||||
_status.ima_ch[0].sample[0] = s_1;
|
||||
_status.ima_ch[0].sample[1] = s_2;
|
||||
s_1 = _status.ima_ch[1].sample[0];
|
||||
s_2 = _status.ima_ch[1].sample[1];
|
||||
}
|
||||
|
||||
shift = 12 - (src[5 + i * 2] & 0xf);
|
||||
filter = src[5 + i * 2] >> 4;
|
||||
f0 = s_xaTable[filter][0];
|
||||
f1 = s_xaTable[filter][1];
|
||||
|
||||
for (int j = 0; j < 28; j++) {
|
||||
byte d = src[16 + i + j * 4];
|
||||
int t = (int8)d >> 4;
|
||||
int s = (t << shift) + ((s_1 * f0 + s_2 * f1 + 32) >> 6);
|
||||
s_2 = s_1;
|
||||
s_1 = CLIP<int>(s, -32768, 32767);
|
||||
|
||||
if (_channels == 2) {
|
||||
*rightChannel = s_1;
|
||||
rightChannel += 2;
|
||||
} else {
|
||||
*leftChannel++ = s_1;
|
||||
}
|
||||
_decodedSampleCount++;
|
||||
}
|
||||
|
||||
if (_channels == 2) {
|
||||
_status.ima_ch[1].sample[0] = s_1;
|
||||
_status.ima_ch[1].sample[1] = s_2;
|
||||
} else {
|
||||
_status.ima_ch[0].sample[0] = s_1;
|
||||
_status.ima_ch[0].sample[1] = s_2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
int DVI_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samples;
|
||||
byte data;
|
||||
|
||||
for (samples = 0; samples < numSamples && !endOfData(); samples++) {
|
||||
if (_decodedSampleCount == 0) {
|
||||
data = _stream->readByte();
|
||||
_decodedSamples[0] = decodeIMA((data >> 4) & 0x0f, 0);
|
||||
_decodedSamples[1] = decodeIMA((data >> 0) & 0x0f, _channels == 2 ? 1 : 0);
|
||||
_decodedSampleCount = 2;
|
||||
}
|
||||
|
||||
// (1 - (count - 1)) ensures that _decodedSamples acts as a FIFO of depth 2
|
||||
buffer[samples] = _decodedSamples[1 - (_decodedSampleCount - 1)];
|
||||
_decodedSampleCount--;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
int Apple_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
// Need to write at least one samples per channel
|
||||
assert((numSamples % _channels) == 0);
|
||||
|
||||
// Current sample positions
|
||||
int samples[2] = { 0, 0};
|
||||
|
||||
// Number of samples per channel
|
||||
int chanSamples = numSamples / _channels;
|
||||
|
||||
for (int i = 0; i < _channels; i++) {
|
||||
_stream->seek(_streamPos[i]);
|
||||
|
||||
while ((samples[i] < chanSamples) &&
|
||||
// Last byte read and a new one needed
|
||||
!((_stream->eos() || (_stream->pos() >= _endpos)) && (_chunkPos[i] == 0))) {
|
||||
|
||||
if (_blockPos[i] == _blockAlign) {
|
||||
// 2 byte header per block
|
||||
uint16 temp = _stream->readUint16BE();
|
||||
|
||||
// First 9 bits are the upper bits of the predictor
|
||||
_status.ima_ch[i].last = (int16) (temp & 0xFF80);
|
||||
// Lower 7 bits are the step index
|
||||
_status.ima_ch[i].stepIndex = temp & 0x007F;
|
||||
|
||||
// Clip the step index
|
||||
_status.ima_ch[i].stepIndex = CLIP<int32>(_status.ima_ch[i].stepIndex, 0, 88);
|
||||
|
||||
_blockPos[i] = 2;
|
||||
}
|
||||
|
||||
if (_chunkPos[i] == 0) {
|
||||
// Decode data
|
||||
byte data = _stream->readByte();
|
||||
_buffer[i][0] = decodeIMA(data & 0x0F, i);
|
||||
_buffer[i][1] = decodeIMA(data >> 4, i);
|
||||
}
|
||||
|
||||
// The original is interleaved block-wise, we want it sample-wise
|
||||
buffer[_channels * samples[i] + i] = _buffer[i][_chunkPos[i]];
|
||||
|
||||
if (++_chunkPos[i] > 1) {
|
||||
// We're about to decode the next byte, so advance the block position
|
||||
_chunkPos[i] = 0;
|
||||
_blockPos[i]++;
|
||||
}
|
||||
|
||||
samples[i]++;
|
||||
|
||||
if (_channels == 2)
|
||||
if (_blockPos[i] == _blockAlign)
|
||||
// We're at the end of the block.
|
||||
// Since the channels are interleaved, skip the next block
|
||||
_stream->skip(MIN<uint32>(_blockAlign, _endpos - _stream->pos()));
|
||||
|
||||
_streamPos[i] = _stream->pos();
|
||||
}
|
||||
}
|
||||
|
||||
return samples[0] + samples[1];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
int MSIma_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
// Need to write at least one sample per channel
|
||||
assert((numSamples % _channels) == 0);
|
||||
|
||||
int samples = 0;
|
||||
|
||||
while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
|
||||
if (_blockPos[0] == _blockAlign) {
|
||||
for (int i = 0; i < _channels; i++) {
|
||||
// read block header
|
||||
_status.ima_ch[i].last = _stream->readSint16LE();
|
||||
_status.ima_ch[i].stepIndex = _stream->readSint16LE();
|
||||
}
|
||||
|
||||
_blockPos[0] = _channels * 4;
|
||||
}
|
||||
|
||||
// Decode a set of samples
|
||||
for (int i = 0; i < _channels; i++) {
|
||||
// The stream encodes four bytes per channel at a time
|
||||
for (int j = 0; j < 4; j++) {
|
||||
byte data = _stream->readByte();
|
||||
_blockPos[0]++;
|
||||
_buffer[i][j * 2] = decodeIMA(data & 0x0f, i);
|
||||
_buffer[i][j * 2 + 1] = decodeIMA((data >> 4) & 0x0f, i);
|
||||
_samplesLeft[i] += 2;
|
||||
}
|
||||
}
|
||||
|
||||
while (samples < numSamples && _samplesLeft[0] != 0) {
|
||||
for (int i = 0; i < _channels; i++) {
|
||||
buffer[samples + i] = _buffer[i][8 - _samplesLeft[i]];
|
||||
_samplesLeft[i]--;
|
||||
}
|
||||
|
||||
samples += _channels;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
static const int MSADPCMAdaptCoeff1[] = {
|
||||
256, 512, 0, 192, 240, 460, 392
|
||||
};
|
||||
|
||||
static const int MSADPCMAdaptCoeff2[] = {
|
||||
0, -256, 0, 64, 0, -208, -232
|
||||
};
|
||||
|
||||
static const int MSADPCMAdaptationTable[] = {
|
||||
230, 230, 230, 230, 307, 409, 512, 614,
|
||||
768, 614, 512, 409, 307, 230, 230, 230
|
||||
};
|
||||
|
||||
int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
|
||||
int32 predictor;
|
||||
|
||||
predictor = (((c->sample1) * (c->coeff1)) + ((c->sample2) * (c->coeff2))) / 256;
|
||||
predictor += (signed)((code & 0x08) ? (code - 0x10) : (code)) * c->delta;
|
||||
|
||||
predictor = CLIP<int32>(predictor, -32768, 32767);
|
||||
|
||||
c->sample2 = c->sample1;
|
||||
c->sample1 = predictor;
|
||||
c->delta = (MSADPCMAdaptationTable[(int)code] * c->delta) >> 8;
|
||||
|
||||
if (c->delta < 16)
|
||||
c->delta = 16;
|
||||
|
||||
return (int16)predictor;
|
||||
}
|
||||
|
||||
int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samples;
|
||||
byte data;
|
||||
int i;
|
||||
|
||||
for (samples = 0; samples < numSamples && !endOfData(); samples++) {
|
||||
if (_decodedSampleCount == 0) {
|
||||
if (_blockPos[0] == _blockAlign) {
|
||||
// read block header
|
||||
for (i = 0; i < _channels; i++) {
|
||||
_status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
|
||||
_status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor];
|
||||
_status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor];
|
||||
}
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
_status.ch[i].delta = _stream->readSint16LE();
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
_status.ch[i].sample1 = _stream->readSint16LE();
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
_decodedSamples[_decodedSampleCount++] = _status.ch[i].sample2 = _stream->readSint16LE();
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
_decodedSamples[_decodedSampleCount++] = _status.ch[i].sample1;
|
||||
|
||||
_blockPos[0] = _channels * 7;
|
||||
} else {
|
||||
data = _stream->readByte();
|
||||
_blockPos[0]++;
|
||||
_decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
|
||||
_decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
|
||||
}
|
||||
_decodedSampleIndex = 0;
|
||||
}
|
||||
|
||||
// _decodedSamples acts as a FIFO of depth 2 or 4;
|
||||
buffer[samples] = _decodedSamples[_decodedSampleIndex++];
|
||||
_decodedSampleCount--;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
#define DK3_READ_NIBBLE(channelNo) \
|
||||
do { \
|
||||
if (_topNibble) { \
|
||||
_nibble = _lastByte >> 4; \
|
||||
_topNibble = false; \
|
||||
} else { \
|
||||
_lastByte = _stream->readByte(); \
|
||||
_nibble = _lastByte & 0xf; \
|
||||
_topNibble = true; \
|
||||
--blockBytesLeft; \
|
||||
--audioBytesLeft; \
|
||||
} \
|
||||
decodeIMA(_nibble, channelNo); \
|
||||
} while(0)
|
||||
|
||||
int DK3_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
assert((numSamples % 4) == 0);
|
||||
|
||||
const uint32 startOffset = _stream->pos() % _blockAlign;
|
||||
uint32 audioBytesLeft = _endpos - _stream->pos();
|
||||
uint32 blockBytesLeft;
|
||||
if (startOffset != 0) {
|
||||
blockBytesLeft = _blockAlign - startOffset;
|
||||
} else {
|
||||
blockBytesLeft = 0;
|
||||
}
|
||||
|
||||
int samples = 0;
|
||||
while (samples < numSamples && audioBytesLeft) {
|
||||
if (blockBytesLeft == 0) {
|
||||
blockBytesLeft = MIN(_blockAlign, audioBytesLeft);
|
||||
_topNibble = false;
|
||||
|
||||
if (blockBytesLeft < 16) {
|
||||
warning("Truncated DK3 ADPCM block header");
|
||||
break;
|
||||
}
|
||||
|
||||
_stream->skip(2);
|
||||
const uint16 rate = _stream->readUint16LE();
|
||||
assert(rate == getRate());
|
||||
(void)rate;
|
||||
_stream->skip(6);
|
||||
|
||||
// Get predictor for both sum/diff channels
|
||||
_status.ima_ch[0].last = _stream->readSint16LE();
|
||||
_status.ima_ch[1].last = _stream->readSint16LE();
|
||||
|
||||
// Get index for both sum/diff channels
|
||||
_status.ima_ch[0].stepIndex = _stream->readByte();
|
||||
_status.ima_ch[1].stepIndex = _stream->readByte();
|
||||
assert(_status.ima_ch[0].stepIndex < ARRAYSIZE(_imaTable));
|
||||
assert(_status.ima_ch[1].stepIndex < ARRAYSIZE(_imaTable));
|
||||
|
||||
blockBytesLeft -= 16;
|
||||
audioBytesLeft -= 16;
|
||||
}
|
||||
|
||||
DK3_READ_NIBBLE(0);
|
||||
DK3_READ_NIBBLE(1);
|
||||
|
||||
*buffer++ = _status.ima_ch[0].last + _status.ima_ch[1].last;
|
||||
*buffer++ = _status.ima_ch[0].last - _status.ima_ch[1].last;
|
||||
|
||||
DK3_READ_NIBBLE(0);
|
||||
|
||||
*buffer++ = _status.ima_ch[0].last + _status.ima_ch[1].last;
|
||||
*buffer++ = _status.ima_ch[0].last - _status.ima_ch[1].last;
|
||||
|
||||
samples += 4;
|
||||
|
||||
// if the last sample of a block ends on an odd byte, the encoder adds
|
||||
// an extra alignment byte
|
||||
if (!_topNibble && blockBytesLeft == 1) {
|
||||
_stream->skip(1);
|
||||
--blockBytesLeft;
|
||||
--audioBytesLeft;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
#undef DK3_READ_NIBBLE
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
// This table is used to adjust the step for use on the next sample.
|
||||
// We could half the table, but since the lookup index used is always
|
||||
// a 4-bit nibble, it's more efficient to just keep it as it is.
|
||||
const int16 ADPCMStream::_stepAdjustTable[16] = {
|
||||
-1, -1, -1, -1, 2, 4, 6, 8,
|
||||
-1, -1, -1, -1, 2, 4, 6, 8
|
||||
};
|
||||
|
||||
const int16 Ima_ADPCMStream::_imaTable[89] = {
|
||||
7, 8, 9, 10, 11, 12, 13, 14,
|
||||
16, 17, 19, 21, 23, 25, 28, 31,
|
||||
34, 37, 41, 45, 50, 55, 60, 66,
|
||||
73, 80, 88, 97, 107, 118, 130, 143,
|
||||
157, 173, 190, 209, 230, 253, 279, 307,
|
||||
337, 371, 408, 449, 494, 544, 598, 658,
|
||||
724, 796, 876, 963, 1060, 1166, 1282, 1411,
|
||||
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
|
||||
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
|
||||
7132, 7845, 8630, 9493,10442,11487,12635,13899,
|
||||
15289,16818,18500,20350,22385,24623,27086,29794,
|
||||
32767
|
||||
};
|
||||
|
||||
int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
|
||||
int32 E = (2 * (code & 0x7) + 1) * _imaTable[_status.ima_ch[channel].stepIndex] / 8;
|
||||
int32 diff = (code & 0x08) ? -E : E;
|
||||
int32 samp = CLIP<int32>(_status.ima_ch[channel].last + diff, -32768, 32767);
|
||||
|
||||
_status.ima_ch[channel].last = samp;
|
||||
_status.ima_ch[channel].stepIndex += _stepAdjustTable[code];
|
||||
_status.ima_ch[channel].stepIndex = CLIP<int32>(_status.ima_ch[channel].stepIndex, 0, ARRAYSIZE(_imaTable) - 1);
|
||||
|
||||
return samp;
|
||||
}
|
||||
|
||||
SeekableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, ADPCMType type, int rate, int channels, uint32 blockAlign) {
|
||||
// If size is 0, report the entire size of the stream
|
||||
if (!size)
|
||||
size = stream->size();
|
||||
|
||||
switch (type) {
|
||||
case kADPCMOki:
|
||||
return new Oki_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||
case kADPCMMSIma:
|
||||
return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||
case kADPCMMS:
|
||||
return new MS_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||
case kADPCMDVI:
|
||||
return new DVI_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||
case kADPCMApple:
|
||||
return new Apple_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||
case kADPCMDK3:
|
||||
return new DK3_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||
case kADPCMXA:
|
||||
return new XA_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||
default:
|
||||
error("Unsupported ADPCM encoding");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class PacketizedADPCMStream : public StatelessPacketizedAudioStream {
|
||||
public:
|
||||
PacketizedADPCMStream(ADPCMType type, int rate, int channels, uint32 blockAlign) :
|
||||
StatelessPacketizedAudioStream(rate, channels), _type(type), _blockAlign(blockAlign) {}
|
||||
|
||||
protected:
|
||||
AudioStream *makeStream(Common::SeekableReadStream *data) override;
|
||||
|
||||
private:
|
||||
ADPCMType _type;
|
||||
uint32 _blockAlign;
|
||||
};
|
||||
|
||||
AudioStream *PacketizedADPCMStream::makeStream(Common::SeekableReadStream *data) {
|
||||
return makeADPCMStream(data, DisposeAfterUse::YES, data->size(), _type, getRate(), getChannels(), _blockAlign);
|
||||
}
|
||||
|
||||
PacketizedAudioStream *makePacketizedADPCMStream(ADPCMType type, int rate, int channels, uint32 blockAlign) {
|
||||
// Filter out types we can't support (they're not fully stateless)
|
||||
switch (type) {
|
||||
case kADPCMOki:
|
||||
case kADPCMXA:
|
||||
case kADPCMDVI:
|
||||
return nullptr;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return new PacketizedADPCMStream(type, rate, channels, blockAlign);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
108
audio/decoders/adpcm.h
Normal file
108
audio/decoders/adpcm.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - agos
|
||||
* - lastexpress
|
||||
* - mohawk
|
||||
* - saga
|
||||
* - sci (DK3 ADPCM for Phantasmagoria 2)
|
||||
* - scumm
|
||||
* - tinsel
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_ADPCM_H
|
||||
#define AUDIO_ADPCM_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class PacketizedAudioStream;
|
||||
class SeekableAudioStream;
|
||||
|
||||
// There are several types of ADPCM encoding, only some are supported here
|
||||
// For all the different encodings, refer to:
|
||||
// https://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs
|
||||
// Usually, if the audio stream we're trying to play has the FourCC header
|
||||
// string intact, it's easy to discern which encoding is used
|
||||
enum ADPCMType {
|
||||
kADPCMOki, // Dialogic/Oki ADPCM (aka VOX)
|
||||
kADPCMMSIma, // Microsoft IMA ADPCM
|
||||
kADPCMMS, // Microsoft ADPCM
|
||||
kADPCMDVI, // Intel DVI IMA ADPCM
|
||||
kADPCMApple, // Apple QuickTime IMA ADPCM
|
||||
kADPCMDK3, // Duck DK3 IMA ADPCM
|
||||
kADPCMXA // XA ADPCM
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes an input stream containing ADPCM compressed sound data and creates
|
||||
* a RewindableAudioStream from that.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the ADPCM data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @param size how many bytes to read from the stream (0 = all)
|
||||
* @param type the compression type used
|
||||
* @param rate the sampling rate
|
||||
* @param channels the number of channels
|
||||
* @param blockAlign block alignment ???
|
||||
* @return a new RewindableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeADPCMStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse,
|
||||
uint32 size, ADPCMType type,
|
||||
int rate,
|
||||
int channels,
|
||||
uint32 blockAlign = 0);
|
||||
|
||||
/**
|
||||
* Creates a PacketizedAudioStream that will automatically queue
|
||||
* packets as individual AudioStreams like returned by makeADPCMStream.
|
||||
*
|
||||
* Due to the ADPCM types not necessarily supporting stateless
|
||||
* streaming, OKI, XA and DVI are not supported by this function
|
||||
* and will return NULL.
|
||||
*
|
||||
* @param type the compression type used
|
||||
* @param rate the sampling rate
|
||||
* @param channels the number of channels
|
||||
* @param blockAlign block alignment ???
|
||||
* @return The new PacketizedAudioStream or NULL, if the type isn't supported.
|
||||
*/
|
||||
PacketizedAudioStream *makePacketizedADPCMStream(
|
||||
ADPCMType type,
|
||||
int rate,
|
||||
int channels,
|
||||
uint32 blockAlign = 0);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
267
audio/decoders/adpcm_intern.h
Normal file
267
audio/decoders/adpcm_intern.h
Normal file
@@ -0,0 +1,267 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal interfaces to the ADPCM decoders.
|
||||
*
|
||||
* These can be used to make custom ADPCM decoder subclasses,
|
||||
* or to at least share some common data tables between various
|
||||
* ADPCM decoder implementations.
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_ADPCM_INTERN_H
|
||||
#define AUDIO_ADPCM_INTERN_H
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/ptr.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class ADPCMStream : public SeekableAudioStream {
|
||||
protected:
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream;
|
||||
int32 _startpos;
|
||||
const int32 _endpos;
|
||||
const int _channels;
|
||||
const uint32 _blockAlign;
|
||||
uint32 _blockPos[2];
|
||||
const int _rate;
|
||||
|
||||
struct ADPCMStatus {
|
||||
// OKI/IMA
|
||||
struct {
|
||||
int32 last;
|
||||
int32 stepIndex;
|
||||
int16 sample[2];
|
||||
} ima_ch[2];
|
||||
} _status;
|
||||
|
||||
virtual void reset();
|
||||
|
||||
public:
|
||||
ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign);
|
||||
|
||||
virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos); }
|
||||
virtual bool isStereo() const { return _channels == 2; }
|
||||
virtual int getRate() const { return _rate; }
|
||||
|
||||
virtual bool rewind();
|
||||
virtual bool seek(const Timestamp &where) { return false; }
|
||||
virtual Timestamp getLength() const { return Timestamp(); }
|
||||
|
||||
/**
|
||||
* This table is used by some ADPCM variants (IMA and OKI) to adjust the
|
||||
* step for use on the next sample.
|
||||
* The first 8 entries are identical to the second 8 entries. Hence, we
|
||||
* could half the table in size. But since the lookup index is always a
|
||||
* 4-bit nibble, it is more efficient to just keep it as it is.
|
||||
*/
|
||||
static const int16 _stepAdjustTable[16];
|
||||
};
|
||||
|
||||
class Oki_ADPCMStream : public ADPCMStream {
|
||||
public:
|
||||
Oki_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||
: ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { _decodedSampleCount = 0; }
|
||||
|
||||
virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos) && (_decodedSampleCount == 0); }
|
||||
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
protected:
|
||||
int16 decodeOKI(byte);
|
||||
|
||||
private:
|
||||
uint8 _decodedSampleCount;
|
||||
int16 _decodedSamples[2];
|
||||
};
|
||||
|
||||
class XA_ADPCMStream : public ADPCMStream {
|
||||
public:
|
||||
XA_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||
: ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { _decodedSampleCount = 0; }
|
||||
|
||||
virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos) && (_decodedSampleCount == 0); }
|
||||
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
protected:
|
||||
void decodeXA(const byte *src);
|
||||
|
||||
private:
|
||||
uint8 _decodedSampleCount;
|
||||
uint8 _decodedSampleIndex;
|
||||
int16 _decodedSamples[28 * 2 * 4];
|
||||
};
|
||||
|
||||
class Ima_ADPCMStream : public ADPCMStream {
|
||||
protected:
|
||||
int16 decodeIMA(byte code, int channel = 0); // Default to using the left channel/using one channel
|
||||
|
||||
public:
|
||||
Ima_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||
: ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {}
|
||||
|
||||
/**
|
||||
* This table is used by decodeIMA.
|
||||
*/
|
||||
static const int16 _imaTable[89];
|
||||
};
|
||||
|
||||
class DVI_ADPCMStream : public Ima_ADPCMStream {
|
||||
public:
|
||||
DVI_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||
: Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { _decodedSampleCount = 0; }
|
||||
|
||||
virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos) && (_decodedSampleCount == 0); }
|
||||
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
private:
|
||||
uint8 _decodedSampleCount;
|
||||
int16 _decodedSamples[2];
|
||||
};
|
||||
|
||||
class Apple_ADPCMStream : public Ima_ADPCMStream {
|
||||
protected:
|
||||
// Apple QuickTime IMA ADPCM
|
||||
int32 _streamPos[2];
|
||||
int16 _buffer[2][2];
|
||||
uint8 _chunkPos[2];
|
||||
|
||||
void reset() {
|
||||
Ima_ADPCMStream::reset();
|
||||
_chunkPos[0] = 0;
|
||||
_chunkPos[1] = 0;
|
||||
_streamPos[0] = 0;
|
||||
_streamPos[1] = _blockAlign;
|
||||
}
|
||||
|
||||
public:
|
||||
Apple_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||
: Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
|
||||
_chunkPos[0] = 0;
|
||||
_chunkPos[1] = 0;
|
||||
_streamPos[0] = 0;
|
||||
_streamPos[1] = _blockAlign;
|
||||
}
|
||||
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
};
|
||||
|
||||
class MSIma_ADPCMStream : public Ima_ADPCMStream {
|
||||
public:
|
||||
MSIma_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||
: Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
|
||||
|
||||
if (blockAlign == 0)
|
||||
error("MSIma_ADPCMStream(): blockAlign isn't specified");
|
||||
|
||||
if (blockAlign % (_channels * 4))
|
||||
error("MSIma_ADPCMStream(): invalid blockAlign");
|
||||
|
||||
_samplesLeft[0] = 0;
|
||||
_samplesLeft[1] = 0;
|
||||
}
|
||||
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
void reset() {
|
||||
Ima_ADPCMStream::reset();
|
||||
_samplesLeft[0] = 0;
|
||||
_samplesLeft[1] = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int16 _buffer[2][8];
|
||||
int _samplesLeft[2];
|
||||
};
|
||||
|
||||
class MS_ADPCMStream : public ADPCMStream {
|
||||
protected:
|
||||
struct ADPCMChannelStatus {
|
||||
byte predictor;
|
||||
int16 delta;
|
||||
int16 coeff1;
|
||||
int16 coeff2;
|
||||
int16 sample1;
|
||||
int16 sample2;
|
||||
};
|
||||
|
||||
struct {
|
||||
// MS ADPCM
|
||||
ADPCMChannelStatus ch[2];
|
||||
} _status;
|
||||
|
||||
void reset() {
|
||||
ADPCMStream::reset();
|
||||
memset(&_status, 0, sizeof(_status));
|
||||
}
|
||||
|
||||
public:
|
||||
MS_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||
: ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
|
||||
if (blockAlign == 0)
|
||||
error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM");
|
||||
memset(&_status, 0, sizeof(_status));
|
||||
_decodedSampleCount = 0;
|
||||
_decodedSampleIndex = 0;
|
||||
}
|
||||
|
||||
virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos) && (_decodedSampleCount == 0); }
|
||||
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
protected:
|
||||
int16 decodeMS(ADPCMChannelStatus *c, byte);
|
||||
|
||||
private:
|
||||
uint8 _decodedSampleCount;
|
||||
uint8 _decodedSampleIndex;
|
||||
int16 _decodedSamples[4];
|
||||
};
|
||||
|
||||
// Duck DK3 IMA ADPCM Decoder
|
||||
// Based on FFmpeg's decoder and https://wiki.multimedia.cx/index.php?title=Duck_DK3_IMA_ADPCM
|
||||
|
||||
class DK3_ADPCMStream : public Ima_ADPCMStream {
|
||||
public:
|
||||
DK3_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||
: Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
|
||||
|
||||
// DK3 only works as a stereo stream
|
||||
assert(channels == 2);
|
||||
}
|
||||
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
private:
|
||||
byte _nibble, _lastByte;
|
||||
bool _topNibble;
|
||||
};
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
250
audio/decoders/aiff.cpp
Normal file
250
audio/decoders/aiff.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* The code in this file is based on information found at
|
||||
* https://web.archive.org/web/20070409184854/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/adpcm.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
|
||||
|
||||
AIFFHeader *AIFFHeader::readAIFFHeader(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 nullptr;
|
||||
}
|
||||
|
||||
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 nullptr;
|
||||
}
|
||||
|
||||
// From here on, we only care about the COMM and SSND chunks, which are
|
||||
// the only required chunks.
|
||||
|
||||
bool foundCOMM = false;
|
||||
bool foundSSND = false;
|
||||
|
||||
AIFFHeader *aiffHeader = new AIFFHeader;
|
||||
aiffHeader->_codec = kCodecPCM; // AIFF Default;
|
||||
|
||||
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;
|
||||
aiffHeader->_channels = stream->readUint16BE();
|
||||
aiffHeader->_frameCount = stream->readUint32BE();
|
||||
aiffHeader->_bitsPerSample = stream->readUint16BE();
|
||||
aiffHeader->_rate = readExtended(*stream);
|
||||
|
||||
if (version == kVersionAIFC)
|
||||
aiffHeader->_codec = stream->readUint32BE();
|
||||
break;
|
||||
case MKTAG('S', 'S', 'N', 'D'):
|
||||
foundSSND = true;
|
||||
/* uint32 offset = */ stream->readUint32BE();
|
||||
/* uint32 blockAlign = */ stream->readUint32BE();
|
||||
delete aiffHeader->_dataStream;
|
||||
aiffHeader->_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 (!aiffHeader->_dataStream && disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
|
||||
delete aiffHeader->_dataStream;
|
||||
delete aiffHeader;
|
||||
return nullptr;
|
||||
default:
|
||||
debug(1, "Skipping AIFF '%s' chunk", tag2str(tag));
|
||||
break;
|
||||
}
|
||||
|
||||
uint32 seekPos = pos + length;
|
||||
if (seekPos < (uint32)stream->size()) {
|
||||
seekPos += (length & 1); // ensure we're word-aligned
|
||||
}
|
||||
stream->seek(seekPos);
|
||||
}
|
||||
|
||||
if (!foundCOMM) {
|
||||
warning("makeAIFFStream: Could not find 'COMM' chunk");
|
||||
|
||||
if (!aiffHeader->_dataStream && disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
|
||||
delete aiffHeader;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!foundSSND) {
|
||||
warning("makeAIFFStream: Could not find 'SSND' chunk");
|
||||
|
||||
if (disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
|
||||
delete aiffHeader;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return aiffHeader;
|
||||
}
|
||||
|
||||
RewindableAudioStream *AIFFHeader::makeAIFFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
|
||||
// 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 nullptr;
|
||||
}
|
||||
|
||||
// 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, disposeAfterUse);
|
||||
}
|
||||
case MKTAG('i', 'm', 'a', '4'):
|
||||
// Apple QuickTime IMA ADPCM
|
||||
return makeADPCMStream(_dataStream, disposeAfterUse, 0, kADPCMApple, _rate, _channels, 34);
|
||||
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, NULL, disposeAfterUse);
|
||||
case MKTAG('S', 'D', 'X', '2'):
|
||||
// SDX2 on 3DO
|
||||
return make3DO_SDX2AudioStream(_dataStream, _rate, _channels == 2, NULL, disposeAfterUse);
|
||||
default:
|
||||
warning("Unhandled AIFF-C compression tag '%s'", tag2str(_codec));
|
||||
}
|
||||
|
||||
delete _dataStream;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RewindableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
|
||||
AIFFHeader *aiffHeader = AIFFHeader::readAIFFHeader(stream, disposeAfterUse);
|
||||
if (aiffHeader == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto res = aiffHeader->makeAIFFStream(stream, disposeAfterUse);
|
||||
delete aiffHeader;
|
||||
return res;
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
77
audio/decoders/aiff.h
Normal file
77
audio/decoders/aiff.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - bbvs
|
||||
* - pegasus
|
||||
* - saga
|
||||
* - sci
|
||||
* - sword1
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_AIFF_H
|
||||
#define AUDIO_AIFF_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class RewindableAudioStream;
|
||||
|
||||
class AIFFHeader {
|
||||
public:
|
||||
static AIFFHeader *readAIFFHeader(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
|
||||
RewindableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
uint32 getFrameCount() const { return _frameCount; }
|
||||
uint32 getFrameRate() const { return _rate; }
|
||||
|
||||
private:
|
||||
uint16 _channels = 0;
|
||||
uint32 _frameCount = 0;
|
||||
uint16 _bitsPerSample = 0;
|
||||
uint32 _rate = 0;
|
||||
uint32 _codec = 0;
|
||||
Common::SeekableReadStream *_dataStream = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to load an AIFF from the given seekable stream and create an AudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the AIFF data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
RewindableAudioStream *makeAIFFStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
185
audio/decoders/apc.cpp
Normal file
185
audio/decoders/apc.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/ptr.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/decoders/adpcm_intern.h"
|
||||
#include "audio/decoders/apc.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class APCStreamImpl : public APCStream {
|
||||
public:
|
||||
// These parameters are completely optional and only used in HNM videos to make sure data is consistent
|
||||
APCStreamImpl(uint32 sampleRate = uint(-1), byte stereo = byte(-1));
|
||||
|
||||
bool init(Common::SeekableReadStream &header) override;
|
||||
|
||||
// AudioStream API
|
||||
int readBuffer(int16 *buffer, const int numSamples) override { return _audStream->readBuffer(buffer, numSamples); }
|
||||
bool isStereo() const override { return _audStream->isStereo(); }
|
||||
int getRate() const override { return _audStream->getRate(); }
|
||||
bool endOfData() const override { return _audStream->endOfData(); }
|
||||
bool endOfStream() const override { return _audStream->endOfStream(); }
|
||||
|
||||
// PacketizedAudioStream API
|
||||
void queuePacket(Common::SeekableReadStream *data) override;
|
||||
void finish() override { _audStream->finish(); }
|
||||
|
||||
private:
|
||||
struct Status {
|
||||
int32 last;
|
||||
uint32 stepIndex;
|
||||
int16 step;
|
||||
};
|
||||
|
||||
FORCEINLINE static int16 decodeIMA(byte code, Status *status);
|
||||
|
||||
Common::ScopedPtr<QueuingAudioStream> _audStream;
|
||||
byte _stereo;
|
||||
uint32 _sampleRate;
|
||||
|
||||
Status _status[2];
|
||||
};
|
||||
|
||||
APCStreamImpl::APCStreamImpl(uint32 sampleRate, byte stereo) :
|
||||
_sampleRate(sampleRate), _stereo(stereo) {
|
||||
memset(_status, 0, sizeof(_status));
|
||||
if (_sampleRate != uint32(-1) && _stereo != byte(-1)) {
|
||||
_audStream.reset(makeQueuingAudioStream(_sampleRate, _stereo));
|
||||
}
|
||||
}
|
||||
|
||||
bool APCStreamImpl::init(Common::SeekableReadStream &header) {
|
||||
// Header size
|
||||
if (header.size() < 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read magic and version at once
|
||||
byte magicVersion[12];
|
||||
if (header.read(magicVersion, sizeof(magicVersion)) != sizeof(magicVersion)) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(magicVersion, "CRYO_APC1.20", sizeof(magicVersion))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//uint32 num_samples = header.readUint32LE();
|
||||
header.skip(4);
|
||||
|
||||
uint32 samplerate = header.readUint32LE();
|
||||
if (_sampleRate != uint32(-1) && _sampleRate != samplerate) {
|
||||
error("Samplerate mismatch");
|
||||
return false;
|
||||
}
|
||||
_sampleRate = samplerate;
|
||||
|
||||
uint32 leftSample = header.readSint32LE();
|
||||
uint32 rightSample = header.readSint32LE();
|
||||
|
||||
uint32 audioFlags = header.readUint32LE();
|
||||
byte stereo = (audioFlags & 1);
|
||||
if (_stereo != byte(-1) && _stereo != stereo) {
|
||||
error("Channels mismatch");
|
||||
return false;
|
||||
|
||||
}
|
||||
_stereo = stereo;
|
||||
|
||||
_status[0].last = leftSample;
|
||||
_status[1].last = rightSample;
|
||||
_status[0].stepIndex = _status[1].stepIndex = 0;
|
||||
_status[0].step = _status[1].step = Ima_ADPCMStream::_imaTable[0];
|
||||
|
||||
if (!_audStream) {
|
||||
_audStream.reset(makeQueuingAudioStream(_sampleRate, _stereo));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void APCStreamImpl::queuePacket(Common::SeekableReadStream *data) {
|
||||
Common::ScopedPtr<Common::SeekableReadStream> packet(data);
|
||||
|
||||
uint32 size = packet->size() - packet->pos();
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// From 4-bits samples to 16-bits
|
||||
int16 *outputBuffer = (int16 *)malloc(size * 4);
|
||||
int16 *outputPtr = outputBuffer;
|
||||
|
||||
int channelOffset = (_stereo ? 1 : 0);
|
||||
|
||||
for (uint32 counter = size; counter > 0; counter--) {
|
||||
byte word = packet->readByte();
|
||||
*(outputPtr++) = decodeIMA((word >> 4) & 0x0f, _status);
|
||||
*(outputPtr++) = decodeIMA((word >> 0) & 0x0f, _status + channelOffset);
|
||||
}
|
||||
|
||||
byte flags = FLAG_16BITS;
|
||||
if (_stereo) {
|
||||
flags |= FLAG_STEREO;
|
||||
}
|
||||
#ifdef SCUMM_LITTLE_ENDIAN
|
||||
flags |= Audio::FLAG_LITTLE_ENDIAN;
|
||||
#endif
|
||||
_audStream->queueBuffer((byte *)outputBuffer, size * 4, DisposeAfterUse::YES, flags);
|
||||
}
|
||||
|
||||
int16 APCStreamImpl::decodeIMA(byte code, Status *status) {
|
||||
int32 E = (2 * (code & 0x7) + 1) * status->step / 8;
|
||||
int32 diff = (code & 0x08) ? -E : E;
|
||||
// In Cryo APC data is only truncated and not CLIPed as expected
|
||||
int16 samp = (status->last + diff);
|
||||
|
||||
int32 index = status->stepIndex + Ima_ADPCMStream::_stepAdjustTable[code];
|
||||
index = CLIP<int32>(index, 0, ARRAYSIZE(Ima_ADPCMStream::_imaTable) - 1);
|
||||
|
||||
status->last = samp;
|
||||
status->stepIndex = index;
|
||||
status->step = Ima_ADPCMStream::_imaTable[index];
|
||||
|
||||
return samp;
|
||||
}
|
||||
|
||||
PacketizedAudioStream *makeAPCStream(Common::SeekableReadStream &header) {
|
||||
Common::ScopedPtr<APCStream> stream(new APCStreamImpl());
|
||||
if (!stream->init(header)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return stream.release();
|
||||
}
|
||||
|
||||
APCStream *makeAPCStream(uint sampleRate, bool stereo) {
|
||||
assert((sampleRate % 11025) == 0);
|
||||
return new APCStreamImpl(sampleRate, stereo ? 1 : 0);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
61
audio/decoders/apc.h
Normal file
61
audio/decoders/apc.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_DECODERS_APC_H
|
||||
#define AUDIO_DECODERS_APC_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
} // End of namespace Common
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class APCStream : public PacketizedAudioStream {
|
||||
public:
|
||||
virtual bool init(Common::SeekableReadStream &header) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a PacketizedAudioStream that decodes Cryo APC sound from stream
|
||||
*
|
||||
* @param header The stream containing the header
|
||||
* queuePacket must be called after
|
||||
* @return A new PacketizedAudioStream, or nullptr on error
|
||||
*/
|
||||
PacketizedAudioStream *makeAPCStream(Common::SeekableReadStream &header);
|
||||
|
||||
/**
|
||||
* Create a PacketizedAudioStream that decodes Cryo APC sound using predefined settings
|
||||
* This is used by HNM6 video decoder and shouldn't be called elsewhere.
|
||||
*
|
||||
* @param sampleRate The sample rate of the stream
|
||||
* @param stereo Whether the stream will be stereo
|
||||
* @return A new APCStream
|
||||
*/
|
||||
APCStream *makeAPCStream(uint sampleRate, bool stereo);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
|
||||
460
audio/decoders/asf.cpp
Normal file
460
audio/decoders/asf.cpp
Normal file
@@ -0,0 +1,460 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based on xoreos' ASF code which is in turn
|
||||
// Largely based on the ASF implementation found in FFmpeg.
|
||||
|
||||
#include "common/textconsole.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/asf.h"
|
||||
#include "audio/decoders/wma.h"
|
||||
#include "audio/decoders/wave_types.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class ASFGUID {
|
||||
public:
|
||||
ASFGUID(Common::SeekableReadStream &stream) {
|
||||
stream.read(id, 16);
|
||||
}
|
||||
|
||||
bool operator==(const byte gid[16]) const {
|
||||
return !memcmp(gid, id, 16);
|
||||
}
|
||||
|
||||
bool operator!=(const byte gid[16]) const {
|
||||
return memcmp(gid, id, 16);
|
||||
}
|
||||
|
||||
Common::String toString() const {
|
||||
return Common::String::format("%02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x",
|
||||
id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]);
|
||||
}
|
||||
|
||||
private:
|
||||
byte id[16];
|
||||
};
|
||||
|
||||
// GUID's that we need
|
||||
static const byte s_asfHeader[16] = { 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C };
|
||||
static const byte s_asfFileHeader[16] = { 0xA1, 0xDC, 0xAB, 0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 };
|
||||
static const byte s_asfHead1[16] = { 0xb5, 0x03, 0xbf, 0x5f, 0x2E, 0xA9, 0xCF, 0x11, 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 };
|
||||
static const byte s_asfComment[16] = { 0x33, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c };
|
||||
static const byte s_asfStreamHeader[16] = { 0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 };
|
||||
static const byte s_asfCodecComment[16] = { 0x40, 0x52, 0xD1, 0x86, 0x1D, 0x31, 0xD0, 0x11, 0xA3, 0xA4, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 };
|
||||
static const byte s_asfDataHeader[16] = { 0x36, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c };
|
||||
static const byte s_asfAudioStream[16] = { 0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B };
|
||||
static const byte s_asfExtendedHeader[16] = { 0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11, 0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50 };
|
||||
static const byte s_asfStreamBitRate[16] = { 0xce, 0x75, 0xf8, 0x7b, 0x8d, 0x46, 0xd1, 0x11, 0x8d, 0x82, 0x00, 0x60, 0x97, 0xc9, 0xa2, 0xb2 };
|
||||
|
||||
class ASFStream : public SeekableAudioStream {
|
||||
public:
|
||||
ASFStream(Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
~ASFStream();
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
|
||||
bool endOfData() const override;
|
||||
bool isStereo() const override { return _channels == 2; }
|
||||
int getRate() const override { return _sampleRate; }
|
||||
Timestamp getLength() const override { return Audio::Timestamp(_duration / 10000, _sampleRate); }
|
||||
bool seek(const Timestamp &where) override;
|
||||
bool rewind() override;
|
||||
|
||||
private:
|
||||
// Packet data
|
||||
struct Packet {
|
||||
Packet();
|
||||
~Packet();
|
||||
|
||||
byte flags;
|
||||
byte segmentType;
|
||||
uint16 packetSize;
|
||||
uint32 sendTime;
|
||||
uint16 duration;
|
||||
|
||||
struct Segment {
|
||||
byte streamID;
|
||||
byte sequenceNumber;
|
||||
bool isKeyframe;
|
||||
Common::Array<Common::SeekableReadStream *> data;
|
||||
};
|
||||
|
||||
Common::Array<Segment> segments;
|
||||
};
|
||||
|
||||
Common::SeekableReadStream *_stream;
|
||||
DisposeAfterUse::Flag _disposeAfterUse;
|
||||
|
||||
void parseStreamHeader();
|
||||
void parseFileHeader();
|
||||
Packet *readPacket();
|
||||
Codec *createCodec();
|
||||
AudioStream *createAudioStream();
|
||||
|
||||
uint32 _rewindPos;
|
||||
uint64 _curPacket;
|
||||
Packet *_lastPacket;
|
||||
Codec *_codec;
|
||||
AudioStream *_curAudioStream;
|
||||
byte _curSequenceNumber;
|
||||
|
||||
// Header object variables
|
||||
uint64 _packetCount;
|
||||
uint64 _duration;
|
||||
uint32 _minPacketSize, _maxPacketSize;
|
||||
|
||||
// Stream object variables
|
||||
uint16 _streamID;
|
||||
uint16 _compression;
|
||||
uint16 _channels;
|
||||
int _sampleRate;
|
||||
uint32 _bitRate;
|
||||
uint16 _blockAlign;
|
||||
uint16 _bitsPerCodedSample;
|
||||
Common::SeekableReadStream *_extraData;
|
||||
};
|
||||
|
||||
ASFStream::Packet::Packet() {
|
||||
}
|
||||
|
||||
ASFStream::Packet::~Packet() {
|
||||
for (uint32 i = 0; i < segments.size(); i++)
|
||||
for (uint32 j = 0; j < segments[i].data.size(); j++)
|
||||
delete segments[i].data[j];
|
||||
}
|
||||
|
||||
ASFStream::ASFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) : _stream(stream), _disposeAfterUse(disposeAfterUse) {
|
||||
_extraData = nullptr;
|
||||
_lastPacket = nullptr;
|
||||
_curPacket = 0;
|
||||
_codec = nullptr;
|
||||
_curAudioStream = nullptr;
|
||||
_curSequenceNumber = 1; // They always start at one
|
||||
|
||||
ASFGUID guid = ASFGUID(*_stream);
|
||||
if (guid != s_asfHeader)
|
||||
error("ASFStream: Missing asf header");
|
||||
|
||||
_stream->readUint64LE();
|
||||
_stream->readUint32LE();
|
||||
_stream->readByte();
|
||||
_stream->readByte();
|
||||
|
||||
for (;;) {
|
||||
uint64 startPos = _stream->pos();
|
||||
guid = ASFGUID(*_stream);
|
||||
uint64 size = _stream->readUint64LE();
|
||||
|
||||
if (_stream->eos())
|
||||
error("ASFStream: Unexpected eos");
|
||||
|
||||
// TODO: Parse each chunk
|
||||
if (guid == s_asfFileHeader) {
|
||||
parseFileHeader();
|
||||
} else if (guid == s_asfHead1) {
|
||||
// Should be safe to ignore
|
||||
} else if (guid == s_asfComment) {
|
||||
// Ignored
|
||||
} else if (guid == s_asfStreamHeader) {
|
||||
parseStreamHeader();
|
||||
} else if (guid == s_asfCodecComment) {
|
||||
// TODO
|
||||
} else if (guid == s_asfDataHeader) {
|
||||
// Done parsing the header
|
||||
break;
|
||||
} else if (guid == s_asfExtendedHeader) {
|
||||
// TODO
|
||||
} else if (guid == s_asfStreamBitRate) {
|
||||
// Ignored
|
||||
} else
|
||||
warning("Found unknown ASF GUID: %s", guid.toString().c_str());
|
||||
|
||||
_stream->seek(startPos + size);
|
||||
}
|
||||
|
||||
// Skip to the beginning of the packets
|
||||
_stream->skip(26);
|
||||
_rewindPos = _stream->pos();
|
||||
}
|
||||
|
||||
ASFStream::~ASFStream() {
|
||||
if (_disposeAfterUse)
|
||||
delete _stream;
|
||||
|
||||
delete _lastPacket;
|
||||
delete _curAudioStream;
|
||||
delete _codec;
|
||||
}
|
||||
|
||||
void ASFStream::parseFileHeader() {
|
||||
_stream->skip(16); // skip client GUID
|
||||
/* uint64 fileSize = */ _stream->readUint64LE();
|
||||
/* uint64 creationTime = */ _stream->readUint64LE();
|
||||
_packetCount = _stream->readUint64LE();
|
||||
/* uint64 endTimestamp = */ _stream->readUint64LE();
|
||||
_duration = _stream->readUint64LE();
|
||||
/* uint32 startTimestamp = */ _stream->readUint32LE();
|
||||
/* uint32 unknown = */ _stream->readUint32LE();
|
||||
/* uint32 flags = */ _stream->readUint32LE();
|
||||
_minPacketSize = _stream->readUint32LE();
|
||||
_maxPacketSize = _stream->readUint32LE();
|
||||
/* uint32 uncFrameSize = */ _stream->readUint32LE();
|
||||
|
||||
// We only know how to support packets of one length
|
||||
if (_minPacketSize != _maxPacketSize)
|
||||
error("ASFStream::parseFileHeader(): Mismatched packet sizes: Min = %d, Max = %d", _minPacketSize, _maxPacketSize);
|
||||
}
|
||||
|
||||
void ASFStream::parseStreamHeader() {
|
||||
ASFGUID guid = ASFGUID(*_stream);
|
||||
|
||||
if (guid != s_asfAudioStream)
|
||||
error("ASFStream::parseStreamHeader(): Found non-audio stream");
|
||||
|
||||
_stream->skip(16); // skip a guid
|
||||
_stream->readUint64LE(); // total size
|
||||
uint32 typeSpecificSize = _stream->readUint32LE();
|
||||
_stream->readUint32LE();
|
||||
_streamID = _stream->readUint16LE();
|
||||
_stream->readUint32LE();
|
||||
|
||||
// Parse the wave header
|
||||
_compression = _stream->readUint16LE();
|
||||
_channels = _stream->readUint16LE();
|
||||
_sampleRate = _stream->readUint32LE();
|
||||
_bitRate = _stream->readUint32LE() * 8;
|
||||
_blockAlign = _stream->readUint16LE();
|
||||
_bitsPerCodedSample = (typeSpecificSize == 14) ? 8 : _stream->readUint16LE();
|
||||
|
||||
if (typeSpecificSize >= 18) {
|
||||
uint32 cbSize = _stream->readUint16LE();
|
||||
cbSize = MIN<int>(cbSize, typeSpecificSize - 18);
|
||||
_extraData = _stream->readStream(cbSize);
|
||||
}
|
||||
|
||||
_codec = createCodec();
|
||||
}
|
||||
|
||||
bool ASFStream::seek(const Timestamp &where) {
|
||||
if (where == 0) {
|
||||
return rewind();
|
||||
}
|
||||
|
||||
// Seeking is not supported
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ASFStream::rewind() {
|
||||
// Seek back to the start of the packets
|
||||
_stream->seek(_rewindPos);
|
||||
|
||||
// Reset our packet counter
|
||||
_curPacket = 0;
|
||||
delete _lastPacket; _lastPacket = nullptr;
|
||||
|
||||
// Delete a stream if we have one
|
||||
delete _curAudioStream; _curAudioStream = nullptr;
|
||||
|
||||
// Reset this too
|
||||
_curSequenceNumber = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ASFStream::Packet *ASFStream::readPacket() {
|
||||
if (_curPacket == _packetCount)
|
||||
error("ASFStream::readPacket(): Reading too many packets");
|
||||
|
||||
uint32 packetStartPos = _stream->pos();
|
||||
|
||||
// Read a single ASF packet
|
||||
if (_stream->readByte() != 0x82)
|
||||
error("ASFStream::readPacket(): Missing packet header");
|
||||
|
||||
if (_stream->readUint16LE() != 0)
|
||||
error("ASFStream::readPacket(): Unknown is not zero");
|
||||
|
||||
Packet *packet = new Packet();
|
||||
packet->flags = _stream->readByte();
|
||||
packet->segmentType = _stream->readByte();
|
||||
packet->packetSize = (packet->flags & 0x40) ? _stream->readUint16LE() : 0;
|
||||
|
||||
uint16 paddingSize = 0;
|
||||
if (packet->flags & 0x10)
|
||||
paddingSize = _stream->readUint16LE();
|
||||
else if (packet->flags & 0x08)
|
||||
paddingSize = _stream->readByte();
|
||||
|
||||
packet->sendTime = _stream->readUint32LE();
|
||||
packet->duration = _stream->readUint16LE();
|
||||
|
||||
byte segmentCount = (packet->flags & 0x01) ? _stream->readByte() : 1;
|
||||
packet->segments.resize(segmentCount & 0x3F);
|
||||
|
||||
for (uint32 i = 0; i < packet->segments.size(); i++) {
|
||||
Packet::Segment &segment = packet->segments[i];
|
||||
|
||||
segment.streamID = _stream->readByte();
|
||||
segment.sequenceNumber = _stream->readByte();
|
||||
segment.isKeyframe = (segment.streamID & 0x80) != 0;
|
||||
segment.streamID &= 0x7F;
|
||||
|
||||
uint32 fragmentOffset = 0;
|
||||
if (packet->segmentType == 0x55)
|
||||
fragmentOffset = _stream->readByte();
|
||||
else if (packet->segmentType == 0x59)
|
||||
fragmentOffset = _stream->readUint16LE();
|
||||
else if (packet->segmentType == 0x5D)
|
||||
fragmentOffset = _stream->readUint32LE();
|
||||
else
|
||||
error("ASFStream::readPacket(): Unknown packet segment type 0x%02x", packet->segmentType);
|
||||
|
||||
byte flags = _stream->readByte();
|
||||
if (flags == 1) {
|
||||
//uint32 objectStartTime = fragmentOffset; // reused purpose
|
||||
_stream->readByte(); // unknown
|
||||
|
||||
uint32 dataLength = (packet->segments.size() == 1) ? (_maxPacketSize - (_stream->pos() - packetStartPos) - paddingSize) : _stream->readUint16LE();
|
||||
uint32 startObjectPos = _stream->pos();
|
||||
|
||||
while ((uint32)_stream->pos() < dataLength + startObjectPos)
|
||||
segment.data.push_back(_stream->readStream(_stream->readByte()));
|
||||
} else if (flags == 8) {
|
||||
/* uint32 objectLength = */ _stream->readUint32LE();
|
||||
/* uint32 objectStartTime = */ _stream->readUint32LE();
|
||||
|
||||
uint32 dataLength = 0;
|
||||
if (packet->segments.size() == 1)
|
||||
dataLength = _maxPacketSize - (_stream->pos() - packetStartPos) - fragmentOffset - paddingSize;
|
||||
else if (segmentCount & 0x40)
|
||||
dataLength = _stream->readByte();
|
||||
else
|
||||
dataLength = _stream->readUint16LE();
|
||||
|
||||
_stream->skip(fragmentOffset);
|
||||
segment.data.push_back(_stream->readStream(dataLength));
|
||||
} else
|
||||
error("ASFStream::readPacket(): Unknown packet flags 0x%02x", flags);
|
||||
}
|
||||
|
||||
// Skip any padding
|
||||
_stream->skip(paddingSize);
|
||||
|
||||
// We just read a packet
|
||||
_curPacket++;
|
||||
|
||||
if ((uint32)_stream->pos() != packetStartPos + _maxPacketSize)
|
||||
error("ASFStream::readPacket(): Mismatching packet pos: %d (should be %d)", (int)_stream->pos(), _maxPacketSize + packetStartPos);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
Codec *ASFStream::createCodec() {
|
||||
switch (_compression) {
|
||||
case kWaveFormatWMAv2:
|
||||
return new WMACodec(2, _sampleRate, _channels, _bitRate, _blockAlign, _extraData);
|
||||
default:
|
||||
error("ASFStream::createAudioStream(): Unknown compression 0x%04x", _compression);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioStream *ASFStream::createAudioStream() {
|
||||
delete _lastPacket;
|
||||
_lastPacket = readPacket();
|
||||
|
||||
// TODO
|
||||
if (_lastPacket->segments.size() != 1)
|
||||
error("ASFStream::createAudioStream(): Only single segment packets supported");
|
||||
|
||||
Packet::Segment &segment = _lastPacket->segments[0];
|
||||
|
||||
// We should only have one stream in a ASF audio file
|
||||
if (segment.streamID != _streamID)
|
||||
error("ASFStream::createAudioStream(): Packet stream ID mismatch");
|
||||
|
||||
// TODO
|
||||
if (segment.sequenceNumber != _curSequenceNumber)
|
||||
error("ASFStream::createAudioStream(): Only one sequence number per packet supported");
|
||||
|
||||
// This can overflow and needs to overflow!
|
||||
_curSequenceNumber++;
|
||||
|
||||
// TODO
|
||||
if (segment.data.size() != 1)
|
||||
error("ASFStream::createAudioStream(): Packet grouping not supported");
|
||||
|
||||
Common::SeekableReadStream *stream = segment.data[0];
|
||||
if (_codec)
|
||||
return _codec->decodeFrame(*stream);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ASFStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samplesDecoded = 0;
|
||||
|
||||
for (;;) {
|
||||
if (_curAudioStream) {
|
||||
samplesDecoded += _curAudioStream->readBuffer(buffer + samplesDecoded, numSamples - samplesDecoded);
|
||||
|
||||
if (_curAudioStream->endOfData()) {
|
||||
delete _curAudioStream;
|
||||
_curAudioStream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (samplesDecoded == numSamples || endOfData())
|
||||
break;
|
||||
|
||||
if (!_curAudioStream) {
|
||||
_curAudioStream = createAudioStream();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return samplesDecoded;
|
||||
}
|
||||
|
||||
bool ASFStream::endOfData() const {
|
||||
return _curPacket == _packetCount && !_curAudioStream;
|
||||
}
|
||||
|
||||
SeekableAudioStream *makeASFStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse) {
|
||||
SeekableAudioStream *s = new ASFStream(stream, disposeAfterUse);
|
||||
|
||||
if (s && s->endOfData()) {
|
||||
delete s;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
45
audio/decoders/asf.h
Normal file
45
audio/decoders/asf.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based on xoreos' ASF code
|
||||
|
||||
#ifndef AUDIO_DECODERS_ASF_H
|
||||
#define AUDIO_DECODERS_ASF_H
|
||||
|
||||
namespace Audio {
|
||||
|
||||
/**
|
||||
* Try to load a ASF from the given seekable stream and create a RewindableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream The SeekableReadStream from which to read the ASF data.
|
||||
* @param disposeAfterUse Whether to delete the stream after use.
|
||||
*
|
||||
* @return A new SeekableAudioStream, or 0, if an error occurred.
|
||||
*/
|
||||
|
||||
SeekableAudioStream *makeASFStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // AUDIO_DECODERS_ASF_H
|
||||
50
audio/decoders/codec.h
Normal file
50
audio/decoders/codec.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_DECODERS_CODEC_H
|
||||
#define AUDIO_DECODERS_CODEC_H
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class AudioStream;
|
||||
|
||||
/**
|
||||
* @deprecated The old method of handling audio codecs that rely
|
||||
* on the state remaining the same between calls. This should
|
||||
* only be used for old code.
|
||||
*
|
||||
* DEPRECATED; USE PacketizedAudioStream INSTEAD!
|
||||
*/
|
||||
class Codec {
|
||||
public:
|
||||
Codec() {}
|
||||
virtual ~Codec() {}
|
||||
|
||||
virtual AudioStream *decodeFrame(Common::SeekableReadStream &data) = 0;
|
||||
};
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
643
audio/decoders/flac.cpp
Normal file
643
audio/decoders/flac.cpp
Normal file
@@ -0,0 +1,643 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Disable symbol overrides for FILE as that is used in FLAC headers
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
|
||||
|
||||
#include "audio/decoders/flac.h"
|
||||
|
||||
#ifdef USE_FLAC
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
#define FLAC__NO_DLL // that MS-magic gave me headaches - just link the library you like
|
||||
#include <FLAC/export.h>
|
||||
#include <FLAC/stream_decoder.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- FLAC stream ---
|
||||
#pragma mark -
|
||||
|
||||
static const uint MAX_OUTPUT_CHANNELS = 2;
|
||||
|
||||
|
||||
class FLACStream : public SeekableAudioStream {
|
||||
protected:
|
||||
Common::SeekableReadStream *_inStream;
|
||||
bool _disposeAfterUse;
|
||||
|
||||
::FLAC__StreamDecoder *_decoder;
|
||||
|
||||
/** Header of the stream */
|
||||
FLAC__StreamMetadata_StreamInfo _streaminfo;
|
||||
|
||||
/** index + 1(!) of the last sample to be played */
|
||||
FLAC__uint64 _lastSample;
|
||||
|
||||
/** total play time */
|
||||
Timestamp _length;
|
||||
|
||||
/** true if the last sample was decoded from the FLAC-API - there might still be data in the buffer */
|
||||
bool _lastSampleWritten;
|
||||
|
||||
typedef int16 SampleType;
|
||||
enum { BUFTYPE_BITS = 16 };
|
||||
|
||||
enum {
|
||||
// Maximal buffer size. According to the FLAC format specification, the block size is
|
||||
// a 16 bit value (in fact it seems the maximal block size is 32768, but we play it safe).
|
||||
BUFFER_SIZE = 65536
|
||||
};
|
||||
|
||||
struct {
|
||||
SampleType bufData[BUFFER_SIZE];
|
||||
SampleType *bufReadPos;
|
||||
uint bufFill;
|
||||
} _sampleCache;
|
||||
|
||||
SampleType *_outBuffer;
|
||||
uint _requestedSamples;
|
||||
|
||||
typedef void (*PFCONVERTBUFFERS)(SampleType*, const FLAC__int32*[], uint, const uint, const uint8);
|
||||
PFCONVERTBUFFERS _methodConvertBuffers;
|
||||
|
||||
|
||||
public:
|
||||
FLACStream(Common::SeekableReadStream *inStream, bool dispose);
|
||||
virtual ~FLACStream();
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
|
||||
bool isStereo() const override { return _streaminfo.channels >= 2; }
|
||||
int getRate() const override { return _streaminfo.sample_rate; }
|
||||
bool endOfData() const override {
|
||||
// End of data is reached if there either is no valid stream data available,
|
||||
// or if we reached the last sample and completely emptied the sample cache.
|
||||
return _streaminfo.channels == 0 || (_lastSampleWritten && _sampleCache.bufFill == 0);
|
||||
}
|
||||
|
||||
bool seek(const Timestamp &where) override;
|
||||
Timestamp getLength() const override { return _length; }
|
||||
|
||||
bool isStreamDecoderReady() const { return getStreamDecoderState() == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC; }
|
||||
protected:
|
||||
uint getChannels() const { return MIN<uint>(_streaminfo.channels, MAX_OUTPUT_CHANNELS); }
|
||||
|
||||
bool allocateBuffer(uint minSamples);
|
||||
|
||||
inline FLAC__StreamDecoderState getStreamDecoderState() const;
|
||||
|
||||
inline bool processSingleBlock();
|
||||
inline bool processUntilEndOfMetadata();
|
||||
bool seekAbsolute(FLAC__uint64 sample);
|
||||
|
||||
inline ::FLAC__StreamDecoderReadStatus callbackRead(FLAC__byte buffer[], size_t *bytes);
|
||||
inline ::FLAC__StreamDecoderSeekStatus callbackSeek(FLAC__uint64 absoluteByteOffset);
|
||||
inline ::FLAC__StreamDecoderTellStatus callbackTell(FLAC__uint64 *absoluteByteOffset);
|
||||
inline ::FLAC__StreamDecoderLengthStatus callbackLength(FLAC__uint64 *streamLength);
|
||||
inline bool callbackEOF();
|
||||
inline ::FLAC__StreamDecoderWriteStatus callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]);
|
||||
inline void callbackMetadata(const ::FLAC__StreamMetadata *metadata);
|
||||
inline void callbackError(::FLAC__StreamDecoderErrorStatus status);
|
||||
|
||||
private:
|
||||
static ::FLAC__StreamDecoderReadStatus callWrapRead(const ::FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *clientData);
|
||||
static ::FLAC__StreamDecoderSeekStatus callWrapSeek(const ::FLAC__StreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData);
|
||||
static ::FLAC__StreamDecoderTellStatus callWrapTell(const ::FLAC__StreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData);
|
||||
static ::FLAC__StreamDecoderLengthStatus callWrapLength(const ::FLAC__StreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData);
|
||||
static FLAC__bool callWrapEOF(const ::FLAC__StreamDecoder *decoder, void *clientData);
|
||||
static ::FLAC__StreamDecoderWriteStatus callWrapWrite(const ::FLAC__StreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData);
|
||||
static void callWrapMetadata(const ::FLAC__StreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData);
|
||||
static void callWrapError(const ::FLAC__StreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData);
|
||||
|
||||
void setBestConvertBufferMethod();
|
||||
static void convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
|
||||
static void convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
|
||||
static void convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
|
||||
static void convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
|
||||
static void convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
|
||||
};
|
||||
|
||||
FLACStream::FLACStream(Common::SeekableReadStream *inStream, bool dispose)
|
||||
: _decoder(::FLAC__stream_decoder_new()),
|
||||
_inStream(inStream),
|
||||
_disposeAfterUse(dispose),
|
||||
_length(0, 1000), _lastSample(0),
|
||||
_outBuffer(nullptr), _requestedSamples(0), _lastSampleWritten(false),
|
||||
_methodConvertBuffers(&FLACStream::convertBuffersGeneric)
|
||||
{
|
||||
assert(_inStream);
|
||||
memset(&_streaminfo, 0, sizeof(_streaminfo));
|
||||
|
||||
_sampleCache.bufReadPos = nullptr;
|
||||
_sampleCache.bufFill = 0;
|
||||
|
||||
_methodConvertBuffers = &FLACStream::convertBuffersGeneric;
|
||||
|
||||
bool success;
|
||||
success = (::FLAC__stream_decoder_init_stream(
|
||||
_decoder,
|
||||
&FLACStream::callWrapRead,
|
||||
&FLACStream::callWrapSeek,
|
||||
&FLACStream::callWrapTell,
|
||||
&FLACStream::callWrapLength,
|
||||
&FLACStream::callWrapEOF,
|
||||
&FLACStream::callWrapWrite,
|
||||
&FLACStream::callWrapMetadata,
|
||||
&FLACStream::callWrapError,
|
||||
(void *)this
|
||||
) == FLAC__STREAM_DECODER_INIT_STATUS_OK);
|
||||
if (success) {
|
||||
if (processUntilEndOfMetadata() && _streaminfo.channels > 0) {
|
||||
_lastSample = _streaminfo.total_samples + 1;
|
||||
_length = Timestamp(0, _lastSample - 1, getRate());
|
||||
return; // no error occurred
|
||||
}
|
||||
}
|
||||
|
||||
warning("FLACStream: could not create audio stream");
|
||||
}
|
||||
|
||||
FLACStream::~FLACStream() {
|
||||
if (_decoder != nullptr) {
|
||||
(void) ::FLAC__stream_decoder_finish(_decoder);
|
||||
::FLAC__stream_decoder_delete(_decoder);
|
||||
}
|
||||
if (_disposeAfterUse)
|
||||
delete _inStream;
|
||||
}
|
||||
|
||||
inline FLAC__StreamDecoderState FLACStream::getStreamDecoderState() const {
|
||||
assert(_decoder != nullptr);
|
||||
return ::FLAC__stream_decoder_get_state(_decoder);
|
||||
}
|
||||
|
||||
inline bool FLACStream::processSingleBlock() {
|
||||
assert(_decoder != nullptr);
|
||||
return 0 != ::FLAC__stream_decoder_process_single(_decoder);
|
||||
}
|
||||
|
||||
inline bool FLACStream::processUntilEndOfMetadata() {
|
||||
assert(_decoder != nullptr);
|
||||
return 0 != ::FLAC__stream_decoder_process_until_end_of_metadata(_decoder);
|
||||
}
|
||||
|
||||
bool FLACStream::seekAbsolute(FLAC__uint64 sample) {
|
||||
assert(_decoder != nullptr);
|
||||
const bool result = (0 != ::FLAC__stream_decoder_seek_absolute(_decoder, sample));
|
||||
if (result) {
|
||||
_lastSampleWritten = (_lastSample != 0 && sample >= _lastSample); // only set if we are SURE
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FLACStream::seek(const Timestamp &where) {
|
||||
_sampleCache.bufFill = 0;
|
||||
_sampleCache.bufReadPos = nullptr;
|
||||
// FLAC uses the sample pair number, thus we always use "false" for the isStereo parameter
|
||||
// of the convertTimeToStreamPos helper.
|
||||
return seekAbsolute((FLAC__uint64)convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames());
|
||||
}
|
||||
|
||||
int FLACStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
const uint numChannels = getChannels();
|
||||
|
||||
if (numChannels == 0) {
|
||||
warning("FLACStream: Stream not successfully initialized, can't playback");
|
||||
return -1; // streaminfo wasn't read!
|
||||
}
|
||||
|
||||
assert(numSamples % numChannels == 0); // must be multiple of channels!
|
||||
assert(buffer != nullptr);
|
||||
assert(_outBuffer == nullptr);
|
||||
assert(_requestedSamples == 0);
|
||||
|
||||
_outBuffer = buffer;
|
||||
_requestedSamples = numSamples;
|
||||
|
||||
// If there is still data in our buffer from the last time around,
|
||||
// copy that first.
|
||||
if (_sampleCache.bufFill > 0) {
|
||||
assert(_sampleCache.bufReadPos >= _sampleCache.bufData);
|
||||
assert(_sampleCache.bufFill % numChannels == 0);
|
||||
|
||||
const uint copySamples = MIN((uint)numSamples, _sampleCache.bufFill);
|
||||
memcpy(buffer, _sampleCache.bufReadPos, copySamples*sizeof(buffer[0]));
|
||||
|
||||
_outBuffer = buffer + copySamples;
|
||||
_requestedSamples = numSamples - copySamples;
|
||||
_sampleCache.bufReadPos += copySamples;
|
||||
_sampleCache.bufFill -= copySamples;
|
||||
}
|
||||
|
||||
bool decoderOk = true;
|
||||
|
||||
FLAC__StreamDecoderState state = getStreamDecoderState();
|
||||
|
||||
// Keep poking FLAC to process more samples until we completely satisfied the request
|
||||
// respectively until we run out of data.
|
||||
while (!_lastSampleWritten && _requestedSamples > 0 && state == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC) {
|
||||
assert(_sampleCache.bufFill == 0);
|
||||
assert(_requestedSamples % numChannels == 0);
|
||||
processSingleBlock();
|
||||
state = getStreamDecoderState();
|
||||
|
||||
if (state == FLAC__STREAM_DECODER_END_OF_STREAM)
|
||||
_lastSampleWritten = true;
|
||||
}
|
||||
|
||||
// Error handling
|
||||
switch (state) {
|
||||
case FLAC__STREAM_DECODER_END_OF_STREAM:
|
||||
_lastSampleWritten = true;
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
|
||||
break;
|
||||
default:
|
||||
decoderOk = false;
|
||||
warning("FLACStream: An error occurred while decoding. DecoderState is: %d", getStreamDecoderState());
|
||||
}
|
||||
|
||||
// Compute how many samples we actually produced
|
||||
const int samples = (int)(_outBuffer - buffer);
|
||||
assert(samples % numChannels == 0);
|
||||
|
||||
_outBuffer = nullptr; // basically unnecessary, only for the purpose of the asserts
|
||||
_requestedSamples = 0; // basically unnecessary, only for the purpose of the asserts
|
||||
|
||||
return decoderOk ? samples : -1;
|
||||
}
|
||||
|
||||
inline ::FLAC__StreamDecoderReadStatus FLACStream::callbackRead(FLAC__byte buffer[], size_t *bytes) {
|
||||
if (*bytes == 0) {
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_ABORT; /* abort to avoid a deadlock */
|
||||
}
|
||||
|
||||
const uint32 bytesRead = _inStream->read(buffer, *bytes);
|
||||
|
||||
if (bytesRead == 0) {
|
||||
return _inStream->eos() ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
||||
}
|
||||
|
||||
*bytes = static_cast<uint>(bytesRead);
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
void FLACStream::setBestConvertBufferMethod() {
|
||||
PFCONVERTBUFFERS tempMethod = &FLACStream::convertBuffersGeneric;
|
||||
|
||||
const uint numChannels = getChannels();
|
||||
const uint8 numBits = (uint8)_streaminfo.bits_per_sample;
|
||||
|
||||
assert(numChannels >= 1);
|
||||
assert(numBits >= 4 && numBits <=32);
|
||||
|
||||
if (numChannels == 1) {
|
||||
if (numBits == 8)
|
||||
tempMethod = &FLACStream::convertBuffersMono8Bit;
|
||||
if (numBits == BUFTYPE_BITS)
|
||||
tempMethod = &FLACStream::convertBuffersMonoNS;
|
||||
} else if (numChannels == 2) {
|
||||
if (numBits == 8)
|
||||
tempMethod = &FLACStream::convertBuffersStereo8Bit;
|
||||
if (numBits == BUFTYPE_BITS)
|
||||
tempMethod = &FLACStream::convertBuffersStereoNS;
|
||||
} /* else ... */
|
||||
|
||||
_methodConvertBuffers = tempMethod;
|
||||
}
|
||||
|
||||
// 1 channel, no scaling
|
||||
void FLACStream::convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
|
||||
assert(numChannels == 1);
|
||||
assert(numBits == BUFTYPE_BITS);
|
||||
|
||||
FLAC__int32 const* inChannel1 = inChannels[0];
|
||||
|
||||
while (numSamples >= 4) {
|
||||
bufDestination[0] = static_cast<SampleType>(inChannel1[0]);
|
||||
bufDestination[1] = static_cast<SampleType>(inChannel1[1]);
|
||||
bufDestination[2] = static_cast<SampleType>(inChannel1[2]);
|
||||
bufDestination[3] = static_cast<SampleType>(inChannel1[3]);
|
||||
bufDestination += 4;
|
||||
inChannel1 += 4;
|
||||
numSamples -= 4;
|
||||
}
|
||||
|
||||
for (; numSamples > 0; --numSamples) {
|
||||
*bufDestination++ = static_cast<SampleType>(*inChannel1++);
|
||||
}
|
||||
|
||||
inChannels[0] = inChannel1;
|
||||
assert(numSamples == 0); // dint copy too many samples
|
||||
}
|
||||
|
||||
// 1 channel, scaling from 8Bit
|
||||
void FLACStream::convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
|
||||
assert(numChannels == 1);
|
||||
assert(numBits == 8);
|
||||
assert(8 < BUFTYPE_BITS);
|
||||
|
||||
FLAC__int32 const* inChannel1 = inChannels[0];
|
||||
|
||||
while (numSamples >= 4) {
|
||||
bufDestination[0] = static_cast<SampleType>(inChannel1[0]) << (BUFTYPE_BITS - 8);
|
||||
bufDestination[1] = static_cast<SampleType>(inChannel1[1]) << (BUFTYPE_BITS - 8);
|
||||
bufDestination[2] = static_cast<SampleType>(inChannel1[2]) << (BUFTYPE_BITS - 8);
|
||||
bufDestination[3] = static_cast<SampleType>(inChannel1[3]) << (BUFTYPE_BITS - 8);
|
||||
bufDestination += 4;
|
||||
inChannel1 += 4;
|
||||
numSamples -= 4;
|
||||
}
|
||||
|
||||
for (; numSamples > 0; --numSamples) {
|
||||
*bufDestination++ = static_cast<SampleType>(*inChannel1++) << (BUFTYPE_BITS - 8);
|
||||
}
|
||||
|
||||
inChannels[0] = inChannel1;
|
||||
assert(numSamples == 0); // dint copy too many samples
|
||||
}
|
||||
|
||||
// 2 channels, no scaling
|
||||
void FLACStream::convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
|
||||
assert(numChannels == 2);
|
||||
assert(numBits == BUFTYPE_BITS);
|
||||
assert(numSamples % 2 == 0); // must be integral multiply of channels
|
||||
|
||||
|
||||
FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel
|
||||
FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel
|
||||
|
||||
while (numSamples >= 2*2) {
|
||||
bufDestination[0] = static_cast<SampleType>(inChannel1[0]);
|
||||
bufDestination[1] = static_cast<SampleType>(inChannel2[0]);
|
||||
bufDestination[2] = static_cast<SampleType>(inChannel1[1]);
|
||||
bufDestination[3] = static_cast<SampleType>(inChannel2[1]);
|
||||
bufDestination += 2 * 2;
|
||||
inChannel1 += 2;
|
||||
inChannel2 += 2;
|
||||
numSamples -= 2 * 2;
|
||||
}
|
||||
|
||||
while (numSamples > 0) {
|
||||
bufDestination[0] = static_cast<SampleType>(*inChannel1++);
|
||||
bufDestination[1] = static_cast<SampleType>(*inChannel2++);
|
||||
bufDestination += 2;
|
||||
numSamples -= 2;
|
||||
}
|
||||
|
||||
inChannels[0] = inChannel1;
|
||||
inChannels[1] = inChannel2;
|
||||
assert(numSamples == 0); // dint copy too many samples
|
||||
}
|
||||
|
||||
// 2 channels, scaling from 8Bit
|
||||
void FLACStream::convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
|
||||
assert(numChannels == 2);
|
||||
assert(numBits == 8);
|
||||
assert(numSamples % 2 == 0); // must be integral multiply of channels
|
||||
assert(8 < BUFTYPE_BITS);
|
||||
|
||||
FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel
|
||||
FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel
|
||||
|
||||
while (numSamples >= 2*2) {
|
||||
bufDestination[0] = static_cast<SampleType>(inChannel1[0]) << (BUFTYPE_BITS - 8);
|
||||
bufDestination[1] = static_cast<SampleType>(inChannel2[0]) << (BUFTYPE_BITS - 8);
|
||||
bufDestination[2] = static_cast<SampleType>(inChannel1[1]) << (BUFTYPE_BITS - 8);
|
||||
bufDestination[3] = static_cast<SampleType>(inChannel2[1]) << (BUFTYPE_BITS - 8);
|
||||
bufDestination += 2 * 2;
|
||||
inChannel1 += 2;
|
||||
inChannel2 += 2;
|
||||
numSamples -= 2 * 2;
|
||||
}
|
||||
|
||||
while (numSamples > 0) {
|
||||
bufDestination[0] = static_cast<SampleType>(*inChannel1++) << (BUFTYPE_BITS - 8);
|
||||
bufDestination[1] = static_cast<SampleType>(*inChannel2++) << (BUFTYPE_BITS - 8);
|
||||
bufDestination += 2;
|
||||
numSamples -= 2;
|
||||
}
|
||||
|
||||
inChannels[0] = inChannel1;
|
||||
inChannels[1] = inChannel2;
|
||||
assert(numSamples == 0); // dint copy too many samples
|
||||
}
|
||||
|
||||
// all Purpose-conversion - slowest of em all
|
||||
void FLACStream::convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
|
||||
assert(numSamples % numChannels == 0); // must be integral multiply of channels
|
||||
|
||||
if (numBits < BUFTYPE_BITS) {
|
||||
const uint8 kPower = (uint8)(BUFTYPE_BITS - numBits);
|
||||
|
||||
for (; numSamples > 0; numSamples -= numChannels) {
|
||||
for (uint i = 0; i < numChannels; ++i)
|
||||
*bufDestination++ = static_cast<SampleType>(*(inChannels[i]++)) << kPower;
|
||||
}
|
||||
} else if (numBits > BUFTYPE_BITS) {
|
||||
const uint8 kPower = (uint8)(numBits - BUFTYPE_BITS);
|
||||
|
||||
for (; numSamples > 0; numSamples -= numChannels) {
|
||||
for (uint i = 0; i < numChannels; ++i)
|
||||
*bufDestination++ = static_cast<SampleType>(*(inChannels[i]++) >> kPower);
|
||||
}
|
||||
} else {
|
||||
for (; numSamples > 0; numSamples -= numChannels) {
|
||||
for (uint i = 0; i < numChannels; ++i)
|
||||
*bufDestination++ = static_cast<SampleType>(*(inChannels[i]++));
|
||||
}
|
||||
}
|
||||
|
||||
assert(numSamples == 0); // dint copy too many samples
|
||||
}
|
||||
|
||||
inline ::FLAC__StreamDecoderWriteStatus FLACStream::callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) {
|
||||
assert(frame->header.channels == _streaminfo.channels);
|
||||
assert(frame->header.sample_rate == _streaminfo.sample_rate);
|
||||
assert(frame->header.bits_per_sample == _streaminfo.bits_per_sample);
|
||||
assert(frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER || _streaminfo.min_blocksize == _streaminfo.max_blocksize);
|
||||
|
||||
// We require that either the sample cache is empty, or that no samples were requested
|
||||
assert(_sampleCache.bufFill == 0 || _requestedSamples == 0);
|
||||
|
||||
uint numSamples = frame->header.blocksize;
|
||||
const uint numChannels = getChannels();
|
||||
const uint8 numBits = (uint8)_streaminfo.bits_per_sample;
|
||||
|
||||
assert(_requestedSamples % numChannels == 0); // must be integral multiply of channels
|
||||
|
||||
const FLAC__uint64 firstSampleNumber = (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER) ?
|
||||
frame->header.number.sample_number : (static_cast<FLAC__uint64>(frame->header.number.frame_number)) * _streaminfo.max_blocksize;
|
||||
|
||||
// Check whether we are about to reach beyond the last sample we are supposed to play.
|
||||
if (_lastSample != 0 && firstSampleNumber + numSamples >= _lastSample) {
|
||||
numSamples = (uint)(firstSampleNumber >= _lastSample ? 0 : _lastSample - firstSampleNumber);
|
||||
_lastSampleWritten = true;
|
||||
}
|
||||
|
||||
// The value in _requestedSamples counts raw samples, so if there are more than one
|
||||
// channel, we have to multiply the number of available sample "pairs" by numChannels
|
||||
numSamples *= numChannels;
|
||||
|
||||
const FLAC__int32 *inChannels[MAX_OUTPUT_CHANNELS];
|
||||
for (uint i = 0; i < numChannels; ++i)
|
||||
inChannels[i] = buffer[i];
|
||||
|
||||
// write the incoming samples directly into the buffer provided to us by the mixer
|
||||
if (_requestedSamples > 0) {
|
||||
assert(_requestedSamples % numChannels == 0);
|
||||
assert(_outBuffer != nullptr);
|
||||
|
||||
// Copy & convert the available samples (limited both by how many we have available, and
|
||||
// by how many are actually needed).
|
||||
const uint copySamples = MIN(_requestedSamples, numSamples);
|
||||
(*_methodConvertBuffers)(_outBuffer, inChannels, copySamples, numChannels, numBits);
|
||||
|
||||
_requestedSamples -= copySamples;
|
||||
numSamples -= copySamples;
|
||||
_outBuffer += copySamples;
|
||||
}
|
||||
|
||||
// Write all remaining samples (i.e. those which didn't fit into the mixer buffer)
|
||||
// into the sample cache.
|
||||
if (_sampleCache.bufFill == 0)
|
||||
_sampleCache.bufReadPos = _sampleCache.bufData;
|
||||
const uint cacheSpace = (_sampleCache.bufData + BUFFER_SIZE) - (_sampleCache.bufReadPos + _sampleCache.bufFill);
|
||||
assert(numSamples <= cacheSpace);
|
||||
(void)cacheSpace;
|
||||
(*_methodConvertBuffers)(_sampleCache.bufReadPos + _sampleCache.bufFill, inChannels, numSamples, numChannels, numBits);
|
||||
|
||||
_sampleCache.bufFill += numSamples;
|
||||
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
inline ::FLAC__StreamDecoderSeekStatus FLACStream::callbackSeek(FLAC__uint64 absoluteByteOffset) {
|
||||
_inStream->seek(absoluteByteOffset, SEEK_SET);
|
||||
const bool result = (absoluteByteOffset == (FLAC__uint64)_inStream->pos());
|
||||
|
||||
return result ? FLAC__STREAM_DECODER_SEEK_STATUS_OK : FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
|
||||
}
|
||||
|
||||
inline ::FLAC__StreamDecoderTellStatus FLACStream::callbackTell(FLAC__uint64 *absoluteByteOffset) {
|
||||
*absoluteByteOffset = static_cast<FLAC__uint64>(_inStream->pos());
|
||||
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
|
||||
}
|
||||
|
||||
inline ::FLAC__StreamDecoderLengthStatus FLACStream::callbackLength(FLAC__uint64 *streamLength) {
|
||||
*streamLength = static_cast<FLAC__uint64>(_inStream->size());
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
|
||||
}
|
||||
|
||||
inline bool FLACStream::callbackEOF() {
|
||||
return _inStream->eos();
|
||||
}
|
||||
|
||||
|
||||
inline void FLACStream::callbackMetadata(const ::FLAC__StreamMetadata *metadata) {
|
||||
assert(_decoder != nullptr);
|
||||
assert(metadata->type == FLAC__METADATA_TYPE_STREAMINFO); // others arent really interesting
|
||||
|
||||
_streaminfo = metadata->data.stream_info;
|
||||
setBestConvertBufferMethod(); // should be set after getting stream-information. FLAC always parses the info first
|
||||
}
|
||||
inline void FLACStream::callbackError(::FLAC__StreamDecoderErrorStatus status) {
|
||||
// some of these are non-critical-Errors
|
||||
debug(1, "FLACStream: An error occurred while decoding. DecoderStateError is: %d", status);
|
||||
}
|
||||
|
||||
/* Static Callback Wrappers */
|
||||
::FLAC__StreamDecoderReadStatus FLACStream::callWrapRead(const ::FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *clientData) {
|
||||
FLACStream *instance = (FLACStream *)clientData;
|
||||
assert(nullptr != instance);
|
||||
return instance->callbackRead(buffer, bytes);
|
||||
}
|
||||
|
||||
::FLAC__StreamDecoderSeekStatus FLACStream::callWrapSeek(const ::FLAC__StreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData) {
|
||||
FLACStream *instance = (FLACStream *)clientData;
|
||||
assert(nullptr != instance);
|
||||
return instance->callbackSeek(absoluteByteOffset);
|
||||
}
|
||||
|
||||
::FLAC__StreamDecoderTellStatus FLACStream::callWrapTell(const ::FLAC__StreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData) {
|
||||
FLACStream *instance = (FLACStream *)clientData;
|
||||
assert(nullptr != instance);
|
||||
return instance->callbackTell(absoluteByteOffset);
|
||||
}
|
||||
|
||||
::FLAC__StreamDecoderLengthStatus FLACStream::callWrapLength(const ::FLAC__StreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData) {
|
||||
FLACStream *instance = (FLACStream *)clientData;
|
||||
assert(nullptr != instance);
|
||||
return instance->callbackLength(streamLength);
|
||||
}
|
||||
|
||||
FLAC__bool FLACStream::callWrapEOF(const ::FLAC__StreamDecoder *decoder, void *clientData) {
|
||||
FLACStream *instance = (FLACStream *)clientData;
|
||||
assert(nullptr != instance);
|
||||
return instance->callbackEOF();
|
||||
}
|
||||
|
||||
::FLAC__StreamDecoderWriteStatus FLACStream::callWrapWrite(const ::FLAC__StreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData) {
|
||||
FLACStream *instance = (FLACStream *)clientData;
|
||||
assert(nullptr != instance);
|
||||
return instance->callbackWrite(frame, buffer);
|
||||
}
|
||||
|
||||
void FLACStream::callWrapMetadata(const ::FLAC__StreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData) {
|
||||
FLACStream *instance = (FLACStream *)clientData;
|
||||
assert(nullptr != instance);
|
||||
instance->callbackMetadata(metadata);
|
||||
}
|
||||
|
||||
void FLACStream::callWrapError(const ::FLAC__StreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData) {
|
||||
FLACStream *instance = (FLACStream *)clientData;
|
||||
assert(nullptr != instance);
|
||||
instance->callbackError(status);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- FLAC factory functions ---
|
||||
#pragma mark -
|
||||
|
||||
SeekableAudioStream *makeFLACStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse) {
|
||||
SeekableAudioStream *s = new FLACStream(stream, disposeAfterUse);
|
||||
if (s && s->endOfData()) {
|
||||
delete s;
|
||||
return nullptr;
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_FLAC
|
||||
69
audio/decoders/flac.h
Normal file
69
audio/decoders/flac.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - agos
|
||||
* - draci
|
||||
* - kyra
|
||||
* - queen
|
||||
* - saga
|
||||
* - sci
|
||||
* - scumm
|
||||
* - sword1
|
||||
* - sword2
|
||||
* - touche
|
||||
* - tucker
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_FLAC_H
|
||||
#define AUDIO_FLAC_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#ifdef USE_FLAC
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Create a new SeekableAudioStream from the FLAC data in the given stream.
|
||||
* Allows for seeking (which is why we require a SeekableReadStream).
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the FLAC data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeFLACStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_FLAC
|
||||
#endif // #ifndef AUDIO_FLAC_H
|
||||
127
audio/decoders/g711.cpp
Normal file
127
audio/decoders/g711.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/decoders/g711.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/util.h"
|
||||
|
||||
/* from g711.c by SUN microsystems (unrestricted use) */
|
||||
|
||||
#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */
|
||||
#define QUANT_MASK (0xf) /* Quantization field mask. */
|
||||
#define SEG_SHIFT (4) /* Left shift for segment number. */
|
||||
#define SEG_MASK (0x70) /* Segment field mask. */
|
||||
#define BIAS (0x84) /* Bias for linear code. */
|
||||
|
||||
namespace Audio {
|
||||
|
||||
/**
|
||||
* Logarithmic PCM (G.711)
|
||||
* https://en.wikipedia.org/wiki/G.711
|
||||
* https://wiki.multimedia.cx/index.php/PCM#Logarithmic_PCM
|
||||
*/
|
||||
class G711AudioStream : public SeekableAudioStream {
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream;
|
||||
const int _rate;
|
||||
const int _channels;
|
||||
|
||||
protected:
|
||||
virtual int16 decodeSample(uint8 val) = 0;
|
||||
|
||||
public:
|
||||
G711AudioStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate, int channels) :
|
||||
_stream(stream, disposeAfterUse),
|
||||
_rate(rate),
|
||||
_channels(channels) {
|
||||
}
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override {
|
||||
int samples;
|
||||
|
||||
for (samples = 0; samples < numSamples; samples++) {
|
||||
uint8 val = _stream->readByte();
|
||||
if (endOfData())
|
||||
break;
|
||||
buffer[samples] = decodeSample(val);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
bool isStereo() const override { return (_channels == 2); }
|
||||
int getRate() const override { return _rate; }
|
||||
bool endOfData() const override { return _stream->eos(); }
|
||||
bool seek(const Timestamp &where) override {
|
||||
const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames();
|
||||
return _stream->seek(seekSample, SEEK_SET);
|
||||
}
|
||||
Timestamp getLength() const override {
|
||||
return Timestamp(0, _stream->size() / _channels, _rate);
|
||||
}
|
||||
};
|
||||
|
||||
class G711ALawStream : public G711AudioStream {
|
||||
int16 decodeSample(uint8 val) override {
|
||||
val ^= 0x55;
|
||||
|
||||
int t = val & QUANT_MASK;
|
||||
int seg = ((unsigned)val & SEG_MASK) >> SEG_SHIFT;
|
||||
if (seg)
|
||||
t = (t + t + 1 + 32) << (seg + 2);
|
||||
else
|
||||
t = (t + t + 1) << 3;
|
||||
|
||||
return (val & SIGN_BIT) ? t : -t;
|
||||
}
|
||||
|
||||
public:
|
||||
G711ALawStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate, int channels) :
|
||||
G711AudioStream(stream, disposeAfterUse, rate, channels) {
|
||||
}
|
||||
};
|
||||
|
||||
SeekableAudioStream *makeALawStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate, int channels) {
|
||||
return new G711ALawStream(stream, disposeAfterUse, rate, channels);
|
||||
}
|
||||
|
||||
class G711MuLawStream : public G711AudioStream {
|
||||
int16 decodeSample(uint8 val) override {
|
||||
val = ~val;
|
||||
|
||||
int t = ((val & QUANT_MASK) << 3) + BIAS;
|
||||
t <<= ((unsigned)val & SEG_MASK) >> SEG_SHIFT;
|
||||
|
||||
return (val & SIGN_BIT) ? (BIAS - t) : (t - BIAS);
|
||||
}
|
||||
|
||||
public:
|
||||
G711MuLawStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate, int channels) :
|
||||
G711AudioStream(stream, disposeAfterUse, rate, channels) {
|
||||
}
|
||||
};
|
||||
|
||||
SeekableAudioStream *makeMuLawStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate, int channels) {
|
||||
return new G711MuLawStream(stream, disposeAfterUse, rate, channels);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
76
audio/decoders/g711.h
Normal file
76
audio/decoders/g711.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - trecision
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_G711_H
|
||||
#define AUDIO_G711_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Takes an input stream containing G711 A-law compressed sound data and creates
|
||||
* a SeekableAudioStream from that.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the PCM data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @param rate the sampling rate
|
||||
* @param channels the number of channels
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeALawStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse,
|
||||
int rate,
|
||||
int channels);
|
||||
|
||||
/**
|
||||
* Takes an input stream containing G711 μ-law compressed sound data and creates
|
||||
* a SeekableAudioStream from that.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the PCM data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @param rate the sampling rate
|
||||
* @param channels the number of channels
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeMuLawStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse,
|
||||
int rate,
|
||||
int channels);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
131
audio/decoders/iff_sound.cpp
Normal file
131
audio/decoders/iff_sound.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/decoders/iff_sound.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "common/formats/iff_container.h"
|
||||
#include "common/func.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
struct Voice8Header {
|
||||
uint32 oneShotHiSamples;
|
||||
uint32 repeatHiSamples;
|
||||
uint32 samplesPerHiCycle;
|
||||
uint16 samplesPerSec;
|
||||
byte octaves;
|
||||
byte compression;
|
||||
uint32 volume;
|
||||
|
||||
Voice8Header() {
|
||||
memset(this, 0, sizeof(Voice8Header));
|
||||
}
|
||||
|
||||
void load(Common::ReadStream &stream);
|
||||
};
|
||||
|
||||
void Voice8Header::load(Common::ReadStream &stream) {
|
||||
oneShotHiSamples = stream.readUint32BE();
|
||||
repeatHiSamples = stream.readUint32BE();
|
||||
samplesPerHiCycle = stream.readUint32BE();
|
||||
samplesPerSec = stream.readUint16BE();
|
||||
octaves = stream.readByte();
|
||||
compression = stream.readByte();
|
||||
volume = stream.readUint32BE();
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct A8SVXLoader {
|
||||
Voice8Header _header;
|
||||
int8 *_data;
|
||||
uint32 _dataSize;
|
||||
|
||||
void load(Common::ReadStream &input) {
|
||||
Common::IFFParser parser(&input);
|
||||
Common::Functor1Mem< Common::IFFChunk&, bool, A8SVXLoader > c(this, &A8SVXLoader::callback);
|
||||
parser.parse(c);
|
||||
}
|
||||
|
||||
bool callback(Common::IFFChunk &chunk) {
|
||||
switch (chunk._type) {
|
||||
case ID_VHDR:
|
||||
_header.load(*chunk._stream);
|
||||
break;
|
||||
|
||||
case ID_BODY:
|
||||
_dataSize = chunk._size;
|
||||
_data = (int8 *)malloc(_dataSize);
|
||||
assert(_data);
|
||||
loadData(chunk._stream);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void loadData(Common::ReadStream *stream) {
|
||||
switch (_header.compression) {
|
||||
case 0:
|
||||
stream->read(_data, _dataSize);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// implement other formats here
|
||||
error("compressed IFF audio is not supported");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
AudioStream *make8SVXStream(Common::ReadStream &input, bool loop) {
|
||||
A8SVXLoader loader;
|
||||
loader.load(input);
|
||||
|
||||
SeekableAudioStream *stream = Audio::makeRawStream((byte *)loader._data, loader._dataSize, loader._header.samplesPerSec, 0);
|
||||
|
||||
uint32 loopStart = 0, loopEnd = 0;
|
||||
if (loop) {
|
||||
// the standard way to loop 8SVX audio implies use of the oneShotHiSamples and
|
||||
// repeatHiSamples fields
|
||||
loopStart = 0;
|
||||
loopEnd = loader._header.oneShotHiSamples + loader._header.repeatHiSamples;
|
||||
|
||||
if (loopStart != loopEnd) {
|
||||
return new SubLoopingAudioStream(stream, 0,
|
||||
Timestamp(0, loopStart, loader._header.samplesPerSec),
|
||||
Timestamp(0, loopEnd, loader._header.samplesPerSec));
|
||||
}
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
43
audio/decoders/iff_sound.h
Normal file
43
audio/decoders/iff_sound.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - parallaction
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_IFF_H
|
||||
#define AUDIO_IFF_H
|
||||
|
||||
namespace Common {
|
||||
class ReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class AudioStream;
|
||||
|
||||
AudioStream *make8SVXStream(Common::ReadStream &stream, bool loop);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
107
audio/decoders/mac_snd.cpp
Normal file
107
audio/decoders/mac_snd.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* The code in this file is based on information found at
|
||||
* https://developer.apple.com/library/archive/documentation/mac/Sound/Sound-60.html#HEADING60-15
|
||||
*
|
||||
* We implement both type 1 and type 2 snd resources, but only those that are sampled
|
||||
*/
|
||||
|
||||
#include "common/textconsole.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/substream.h"
|
||||
|
||||
#include "audio/decoders/mac_snd.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
SeekableAudioStream *makeMacSndStream(Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse) {
|
||||
|
||||
uint16 sndType = stream->readUint16BE();
|
||||
|
||||
if (sndType == 1) {
|
||||
// "normal" snd resources
|
||||
if (stream->readUint16BE() != 1) {
|
||||
warning("makeMacSndStream(): Unsupported data type count");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (stream->readUint16BE() != 5) {
|
||||
// 5 == sampled
|
||||
warning("makeMacSndStream(): Unsupported data type");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
stream->readUint32BE(); // initialization option
|
||||
} else if (sndType == 2) {
|
||||
// old HyperCard snd resources
|
||||
stream->readUint16BE(); // reference count (unused)
|
||||
} else {
|
||||
warning("makeMacSndStream(): Unknown format type %d", sndType);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We really should never get this as long as we have sampled data only
|
||||
if (stream->readUint16BE() != 1) {
|
||||
warning("makeMacSndStream(): Unsupported command count");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint16 command = stream->readUint16BE();
|
||||
|
||||
// 0x8050 - soundCmd (with dataOffsetFlag set): install a sampled sound as a voice
|
||||
// 0x8051 - bufferCmd (with dataOffsetFlag set): play a sample sound
|
||||
if (command != 0x8050 && command != 0x8051) {
|
||||
warning("makeMacSndStream(): Unsupported command %04x", command);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
stream->readUint16BE(); // 0
|
||||
uint32 soundHeaderOffset = stream->readUint32BE();
|
||||
|
||||
stream->seek(soundHeaderOffset);
|
||||
|
||||
uint32 soundDataOffset = stream->readUint32BE();
|
||||
uint32 size = stream->readUint32BE();
|
||||
uint16 rate = stream->readUint32BE() >> 16; // Really fixed point, but we only support integer rates
|
||||
stream->readUint32BE(); // loop start
|
||||
stream->readUint32BE(); // loop end
|
||||
byte encoding = stream->readByte();
|
||||
stream->readByte(); // base frequency
|
||||
|
||||
if (encoding != 0) {
|
||||
// 0 == PCM
|
||||
warning("makeMacSndStream(): Unsupported compression %d", encoding);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
stream->skip(soundDataOffset);
|
||||
|
||||
Common::SeekableReadStream *dataStream = new Common::SeekableSubReadStream(stream, stream->pos(), stream->pos() + size, disposeAfterUse);
|
||||
|
||||
// Since we allocated our own stream for the data, we must specify DisposeAfterUse::YES.
|
||||
return makeRawStream(dataStream, rate, Audio::FLAG_UNSIGNED);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
57
audio/decoders/mac_snd.h
Normal file
57
audio/decoders/mac_snd.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - saga
|
||||
* - sci
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_MAC_SND_H
|
||||
#define AUDIO_MAC_SND_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Try to load a Mac snd resource from the given seekable stream and create a SeekableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the snd data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeMacSndStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
563
audio/decoders/mp3.cpp
Normal file
563
audio/decoders/mp3.cpp
Normal file
@@ -0,0 +1,563 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/decoders/mp3.h"
|
||||
|
||||
#ifdef USE_MAD
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/ptr.h"
|
||||
#include "common/queue.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/substream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
#include <mad.h>
|
||||
|
||||
#if defined(__PSP__)
|
||||
#include "backends/platform/psp/mp3.h"
|
||||
#endif
|
||||
namespace Audio {
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- MP3 (MAD) stream ---
|
||||
#pragma mark -
|
||||
|
||||
|
||||
class BaseMP3Stream : public virtual AudioStream {
|
||||
public:
|
||||
BaseMP3Stream();
|
||||
virtual ~BaseMP3Stream();
|
||||
|
||||
bool endOfData() const override { return _state == MP3_STATE_EOS; }
|
||||
bool isStereo() const override { return _channels == 2; }
|
||||
int getRate() const override { return _rate; }
|
||||
|
||||
protected:
|
||||
void decodeMP3Data(Common::ReadStream &stream);
|
||||
void readMP3Data(Common::ReadStream &stream);
|
||||
|
||||
void initStream(Common::ReadStream &stream);
|
||||
void readHeader(Common::ReadStream &stream);
|
||||
void deinitStream();
|
||||
|
||||
int fillBuffer(Common::ReadStream &stream, int16 *buffer, const int numSamples);
|
||||
|
||||
enum State {
|
||||
MP3_STATE_INIT, // Need to init the decoder
|
||||
MP3_STATE_READY, // ready for processing data
|
||||
MP3_STATE_EOS // end of data reached (may need to loop)
|
||||
};
|
||||
|
||||
uint _posInFrame;
|
||||
State _state;
|
||||
|
||||
mad_timer_t _curTime;
|
||||
|
||||
mad_stream _stream;
|
||||
mad_frame _frame;
|
||||
mad_synth _synth;
|
||||
|
||||
uint _channels;
|
||||
uint _rate;
|
||||
|
||||
enum {
|
||||
BUFFER_SIZE = 5 * 8192
|
||||
};
|
||||
|
||||
// This buffer contains a slab of input data
|
||||
byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD];
|
||||
};
|
||||
|
||||
class MP3Stream : private BaseMP3Stream, public SeekableAudioStream {
|
||||
public:
|
||||
MP3Stream(Common::SeekableReadStream *inStream,
|
||||
DisposeAfterUse::Flag dispose);
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
bool seek(const Timestamp &where) override;
|
||||
Timestamp getLength() const override { return _length; }
|
||||
|
||||
protected:
|
||||
Common::ScopedPtr<Common::SeekableReadStream> _inStream;
|
||||
|
||||
Timestamp _length;
|
||||
|
||||
private:
|
||||
static Common::SeekableReadStream *skipID3(Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose);
|
||||
};
|
||||
|
||||
class PacketizedMP3Stream : private BaseMP3Stream, public PacketizedAudioStream {
|
||||
public:
|
||||
PacketizedMP3Stream(Common::SeekableReadStream &firstPacket);
|
||||
PacketizedMP3Stream(uint channels, uint rate);
|
||||
~PacketizedMP3Stream();
|
||||
|
||||
// AudioStream API
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
bool endOfData() const override;
|
||||
bool endOfStream() const override;
|
||||
|
||||
// PacketizedAudioStream API
|
||||
void queuePacket(Common::SeekableReadStream *packet) override;
|
||||
void finish() override;
|
||||
|
||||
private:
|
||||
Common::Mutex _mutex;
|
||||
Common::Queue<Common::SeekableReadStream *> _queue;
|
||||
bool _finished;
|
||||
};
|
||||
|
||||
|
||||
BaseMP3Stream::BaseMP3Stream() :
|
||||
_posInFrame(0),
|
||||
_state(MP3_STATE_INIT),
|
||||
_curTime(mad_timer_zero) {
|
||||
|
||||
// The MAD_BUFFER_GUARD must always contain zeros (the reason
|
||||
// for this is that the Layer III Huffman decoder of libMAD
|
||||
// may read a few bytes beyond the end of the input buffer).
|
||||
memset(_buf + BUFFER_SIZE, 0, MAD_BUFFER_GUARD);
|
||||
}
|
||||
|
||||
BaseMP3Stream::~BaseMP3Stream() {
|
||||
deinitStream();
|
||||
}
|
||||
|
||||
void BaseMP3Stream::decodeMP3Data(Common::ReadStream &stream) {
|
||||
do {
|
||||
if (_state == MP3_STATE_INIT)
|
||||
initStream(stream);
|
||||
|
||||
if (_state == MP3_STATE_EOS)
|
||||
return;
|
||||
|
||||
// If necessary, load more data into the stream decoder
|
||||
if (_stream.error == MAD_ERROR_BUFLEN)
|
||||
readMP3Data(stream);
|
||||
|
||||
while (_state == MP3_STATE_READY) {
|
||||
_stream.error = MAD_ERROR_NONE;
|
||||
|
||||
// Decode the next frame
|
||||
if (mad_frame_decode(&_frame, &_stream) == -1) {
|
||||
if (_stream.error == MAD_ERROR_BUFLEN) {
|
||||
break; // Read more data
|
||||
} else if (MAD_RECOVERABLE(_stream.error)) {
|
||||
// Note: we will occasionally see MAD_ERROR_BADDATAPTR errors here.
|
||||
// These are normal and expected (caused by our frame skipping (i.e. "seeking")
|
||||
// code above).
|
||||
debug(6, "MP3Stream: Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
|
||||
continue;
|
||||
} else {
|
||||
warning("MP3Stream: Unrecoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Sum up the total playback time so far
|
||||
mad_timer_add(&_curTime, _frame.header.duration);
|
||||
// Synthesize PCM data
|
||||
mad_synth_frame(&_synth, &_frame);
|
||||
_posInFrame = 0;
|
||||
break;
|
||||
}
|
||||
} while (_state != MP3_STATE_EOS && _stream.error == MAD_ERROR_BUFLEN);
|
||||
|
||||
if (_stream.error != MAD_ERROR_NONE)
|
||||
_state = MP3_STATE_EOS;
|
||||
}
|
||||
|
||||
void BaseMP3Stream::readMP3Data(Common::ReadStream &stream) {
|
||||
uint32 remaining = 0;
|
||||
|
||||
// Give up immediately if we already used up all data in the stream
|
||||
if (stream.eos()) {
|
||||
_state = MP3_STATE_EOS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_stream.next_frame) {
|
||||
// If there is still data in the MAD stream, we need to preserve it.
|
||||
// Note that we use memmove, as we are reusing the same buffer,
|
||||
// and hence the data regions we copy from and to may overlap.
|
||||
remaining = _stream.bufend - _stream.next_frame;
|
||||
assert(remaining < BUFFER_SIZE); // Paranoia check
|
||||
memmove(_buf, _stream.next_frame, remaining);
|
||||
}
|
||||
|
||||
// Try to read the next block
|
||||
uint32 size = stream.read(_buf + remaining, BUFFER_SIZE - remaining);
|
||||
if (size <= 0) {
|
||||
_state = MP3_STATE_EOS;
|
||||
return;
|
||||
}
|
||||
|
||||
// Feed the data we just read into the stream decoder
|
||||
_stream.error = MAD_ERROR_NONE;
|
||||
mad_stream_buffer(&_stream, _buf, size + remaining);
|
||||
}
|
||||
|
||||
void BaseMP3Stream::initStream(Common::ReadStream &stream) {
|
||||
if (_state != MP3_STATE_INIT)
|
||||
deinitStream();
|
||||
|
||||
// Init MAD
|
||||
mad_stream_init(&_stream);
|
||||
mad_frame_init(&_frame);
|
||||
mad_synth_init(&_synth);
|
||||
|
||||
// Reset the stream data
|
||||
_curTime = mad_timer_zero;
|
||||
_posInFrame = 0;
|
||||
|
||||
// Update state
|
||||
_state = MP3_STATE_READY;
|
||||
|
||||
// Read the first few sample bytes
|
||||
readMP3Data(stream);
|
||||
}
|
||||
|
||||
void BaseMP3Stream::readHeader(Common::ReadStream &stream) {
|
||||
if (_state != MP3_STATE_READY)
|
||||
return;
|
||||
|
||||
// If necessary, load more data into the stream decoder
|
||||
if (_stream.error == MAD_ERROR_BUFLEN)
|
||||
readMP3Data(stream);
|
||||
|
||||
while (_state != MP3_STATE_EOS) {
|
||||
_stream.error = MAD_ERROR_NONE;
|
||||
|
||||
// Decode the next header. Note: mad_frame_decode would do this for us, too.
|
||||
// However, for seeking we don't want to decode the full frame (else it would
|
||||
// be far too slow). Hence we perform this explicitly in a separate step.
|
||||
if (mad_header_decode(&_frame.header, &_stream) == -1) {
|
||||
if (_stream.error == MAD_ERROR_BUFLEN) {
|
||||
readMP3Data(stream); // Read more data
|
||||
continue;
|
||||
} else if (MAD_RECOVERABLE(_stream.error)) {
|
||||
debug(6, "MP3Stream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
|
||||
continue;
|
||||
} else {
|
||||
warning("MP3Stream: Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Sum up the total playback time so far
|
||||
mad_timer_add(&_curTime, _frame.header.duration);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_stream.error != MAD_ERROR_NONE)
|
||||
_state = MP3_STATE_EOS;
|
||||
}
|
||||
|
||||
void BaseMP3Stream::deinitStream() {
|
||||
if (_state == MP3_STATE_INIT)
|
||||
return;
|
||||
|
||||
// Deinit MAD
|
||||
mad_synth_finish(&_synth);
|
||||
mad_frame_finish(&_frame);
|
||||
mad_stream_finish(&_stream);
|
||||
|
||||
_state = MP3_STATE_EOS;
|
||||
}
|
||||
|
||||
static inline int scaleSample(mad_fixed_t sample) {
|
||||
// round
|
||||
sample += (1L << (MAD_F_FRACBITS - 16));
|
||||
|
||||
// clip
|
||||
if (sample > MAD_F_ONE - 1)
|
||||
sample = MAD_F_ONE - 1;
|
||||
else if (sample < -MAD_F_ONE)
|
||||
sample = -MAD_F_ONE;
|
||||
|
||||
// quantize and scale to not saturate when mixing a lot of channels
|
||||
return sample >> (MAD_F_FRACBITS + 1 - 16);
|
||||
}
|
||||
|
||||
int BaseMP3Stream::fillBuffer(Common::ReadStream &stream, int16 *buffer, const int numSamples) {
|
||||
int samples = 0;
|
||||
// Keep going as long as we have input available
|
||||
while (samples < numSamples && _state != MP3_STATE_EOS) {
|
||||
const int len = MIN(numSamples, samples + (int)(_synth.pcm.length - _posInFrame) * MAD_NCHANNELS(&_frame.header));
|
||||
while (samples < len) {
|
||||
*buffer++ = (int16)scaleSample(_synth.pcm.samples[0][_posInFrame]);
|
||||
samples++;
|
||||
if (MAD_NCHANNELS(&_frame.header) == 2) {
|
||||
*buffer++ = (int16)scaleSample(_synth.pcm.samples[1][_posInFrame]);
|
||||
samples++;
|
||||
}
|
||||
_posInFrame++;
|
||||
}
|
||||
if (_posInFrame >= _synth.pcm.length) {
|
||||
// We used up all PCM data in the current frame -- read & decode more
|
||||
decodeMP3Data(stream);
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
MP3Stream::MP3Stream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
|
||||
BaseMP3Stream(),
|
||||
_inStream(skipID3(inStream, dispose)),
|
||||
_length(0, 1000) {
|
||||
|
||||
// Initialize the stream with some data and set the channels and rate
|
||||
// variables
|
||||
decodeMP3Data(*_inStream);
|
||||
_channels = MAD_NCHANNELS(&_frame.header);
|
||||
_rate = _frame.header.samplerate;
|
||||
|
||||
// Calculate the length of the stream
|
||||
while (_state != MP3_STATE_EOS)
|
||||
readHeader(*_inStream);
|
||||
|
||||
// To rule out any invalid sample rate to be encountered here, say in case the
|
||||
// MP3 stream is invalid, we just check the MAD error code here.
|
||||
// We need to assure this, since else we might trigger an assertion in Timestamp
|
||||
// (When getRate() returns 0 or a negative number to be precise).
|
||||
// Note that we allow "MAD_ERROR_BUFLEN" as error code here, since according
|
||||
// to mad.h it is also set on EOF.
|
||||
if ((_stream.error == MAD_ERROR_NONE || _stream.error == MAD_ERROR_BUFLEN) && getRate() > 0)
|
||||
_length = Timestamp(mad_timer_count(_curTime, MAD_UNITS_MILLISECONDS), getRate());
|
||||
|
||||
deinitStream();
|
||||
|
||||
// Reinit stream
|
||||
_state = MP3_STATE_INIT;
|
||||
_inStream->seek(0);
|
||||
|
||||
// Decode the first chunk of data to set up the stream again.
|
||||
decodeMP3Data(*_inStream);
|
||||
}
|
||||
|
||||
int MP3Stream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
return fillBuffer(*_inStream, buffer, numSamples);
|
||||
}
|
||||
|
||||
bool MP3Stream::seek(const Timestamp &where) {
|
||||
if (where == _length) {
|
||||
_state = MP3_STATE_EOS;
|
||||
return true;
|
||||
} else if (where > _length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32 time = where.msecs();
|
||||
|
||||
mad_timer_t destination;
|
||||
mad_timer_set(&destination, time / 1000, time % 1000, 1000);
|
||||
|
||||
if (_state != MP3_STATE_READY || mad_timer_compare(destination, _curTime) < 0) {
|
||||
_inStream->seek(0);
|
||||
initStream(*_inStream);
|
||||
}
|
||||
|
||||
while (mad_timer_compare(destination, _curTime) > 0 && _state != MP3_STATE_EOS)
|
||||
readHeader(*_inStream);
|
||||
|
||||
decodeMP3Data(*_inStream);
|
||||
|
||||
return (_state != MP3_STATE_EOS);
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *MP3Stream::skipID3(Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose) {
|
||||
// Skip ID3 TAG if any
|
||||
// ID3v1 (beginning with with 'TAG') is located at the end of files. So we can ignore those.
|
||||
// ID3v2 can be located at the start of files and begins with a 10 bytes header, the first 3 bytes being 'ID3'.
|
||||
// The tag size is coded on the last 4 bytes of the 10 bytes header as a 32 bit synchsafe integer.
|
||||
// See https://id3.org/id3v2.4.0-structure for details.
|
||||
char data[10];
|
||||
stream->read(data, sizeof(data));
|
||||
|
||||
uint32 offset = 0;
|
||||
if (!stream->eos() && data[0] == 'I' && data[1] == 'D' && data[2] == '3') {
|
||||
uint32 size = data[9] + 128 * (data[8] + 128 * (data[7] + 128 * data[6]));
|
||||
// This size does not include an optional 10 bytes footer. Check if it is present.
|
||||
if (data[5] & 0x10)
|
||||
size += 10;
|
||||
|
||||
// Add in the 10 bytes we read in
|
||||
size += sizeof(data);
|
||||
debug(0, "Skipping ID3 TAG (%d bytes)", size);
|
||||
offset = size;
|
||||
}
|
||||
|
||||
return new Common::SeekableSubReadStream(stream, offset, stream->size(), dispose);
|
||||
}
|
||||
|
||||
PacketizedMP3Stream::PacketizedMP3Stream(Common::SeekableReadStream &firstPacket) :
|
||||
BaseMP3Stream(),
|
||||
_finished(false) {
|
||||
|
||||
// Load some data to get the channels/rate
|
||||
_queue.push(&firstPacket);
|
||||
decodeMP3Data(firstPacket);
|
||||
_channels = MAD_NCHANNELS(&_frame.header);
|
||||
_rate = _frame.header.samplerate;
|
||||
|
||||
// Clear everything
|
||||
deinitStream();
|
||||
_state = MP3_STATE_INIT;
|
||||
_queue.clear();
|
||||
}
|
||||
|
||||
PacketizedMP3Stream::PacketizedMP3Stream(uint channels, uint rate) :
|
||||
BaseMP3Stream(),
|
||||
_finished(false) {
|
||||
_channels = channels;
|
||||
_rate = rate;
|
||||
}
|
||||
|
||||
PacketizedMP3Stream::~PacketizedMP3Stream() {
|
||||
Common::StackLock lock(_mutex);
|
||||
while (!_queue.empty()) {
|
||||
delete _queue.front();
|
||||
_queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
int PacketizedMP3Stream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samples = 0;
|
||||
|
||||
Common::StackLock lock(_mutex);
|
||||
while (samples < numSamples) {
|
||||
// Empty? Bail out for now, and mark the stream as ended
|
||||
if (_queue.empty()) {
|
||||
// EOS state is only valid once a packet has been received at least
|
||||
// once
|
||||
if (_state == MP3_STATE_READY)
|
||||
_state = MP3_STATE_EOS;
|
||||
return samples;
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *packet = _queue.front();
|
||||
|
||||
if (_state == MP3_STATE_INIT) {
|
||||
// Initialize everything
|
||||
decodeMP3Data(*packet);
|
||||
} else if (_state == MP3_STATE_EOS) {
|
||||
// Reset the end-of-stream setting
|
||||
_state = MP3_STATE_READY;
|
||||
}
|
||||
|
||||
samples += fillBuffer(*packet, buffer + samples, numSamples - samples);
|
||||
|
||||
// If the stream is done, kill it
|
||||
if (packet->pos() >= packet->size()) {
|
||||
_queue.pop();
|
||||
delete packet;
|
||||
}
|
||||
}
|
||||
|
||||
// This will happen if the audio runs out just as the last sample is
|
||||
// decoded. But there may still be more audio queued up.
|
||||
if (_state == MP3_STATE_EOS && !_queue.empty()) {
|
||||
_state = MP3_STATE_READY;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
bool PacketizedMP3Stream::endOfData() const {
|
||||
Common::StackLock lock(_mutex);
|
||||
return BaseMP3Stream::endOfData();
|
||||
}
|
||||
|
||||
bool PacketizedMP3Stream::endOfStream() const {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (!endOfData())
|
||||
return false;
|
||||
|
||||
if (!_queue.empty())
|
||||
return false;
|
||||
|
||||
return _finished;
|
||||
}
|
||||
|
||||
void PacketizedMP3Stream::queuePacket(Common::SeekableReadStream *packet) {
|
||||
Common::StackLock lock(_mutex);
|
||||
assert(!_finished);
|
||||
_queue.push(packet);
|
||||
|
||||
// If the audio had finished (buffer underrun?), there is more to
|
||||
// decode now.
|
||||
if (_state == MP3_STATE_EOS) {
|
||||
_state = MP3_STATE_READY;
|
||||
}
|
||||
}
|
||||
|
||||
void PacketizedMP3Stream::finish() {
|
||||
Common::StackLock lock(_mutex);
|
||||
_finished = true;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- MP3 factory functions ---
|
||||
#pragma mark -
|
||||
|
||||
SeekableAudioStream *makeMP3Stream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse) {
|
||||
|
||||
#if defined(__PSP__)
|
||||
SeekableAudioStream *s = 0;
|
||||
|
||||
if (Mp3PspStream::isOkToCreateStream())
|
||||
s = new Mp3PspStream(stream, disposeAfterUse);
|
||||
|
||||
if (!s) // go to regular MAD mp3 stream if ME fails
|
||||
s = new MP3Stream(stream, disposeAfterUse);
|
||||
#else
|
||||
SeekableAudioStream *s = new MP3Stream(stream, disposeAfterUse);
|
||||
#endif
|
||||
if (s && s->endOfData()) {
|
||||
delete s;
|
||||
return nullptr;
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
PacketizedAudioStream *makePacketizedMP3Stream(Common::SeekableReadStream &firstPacket) {
|
||||
return new PacketizedMP3Stream(firstPacket);
|
||||
}
|
||||
|
||||
PacketizedAudioStream *makePacketizedMP3Stream(uint channels, uint rate) {
|
||||
return new PacketizedMP3Stream(channels, rate);
|
||||
}
|
||||
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_MAD
|
||||
93
audio/decoders/mp3.h
Normal file
93
audio/decoders/mp3.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - agos
|
||||
* - draci
|
||||
* - glk
|
||||
* - kyra
|
||||
* - mohawk
|
||||
* - queen
|
||||
* - saga
|
||||
* - sci
|
||||
* - scumm
|
||||
* - sword1
|
||||
* - sword2
|
||||
* - titanic
|
||||
* - touche
|
||||
* - tucker
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_MP3_H
|
||||
#define AUDIO_MP3_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#ifdef USE_MAD
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class PacketizedAudioStream;
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Create a new SeekableAudioStream from the MP3 data in the given stream.
|
||||
* Allows for seeking (which is why we require a SeekableReadStream).
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the MP3 data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeMP3Stream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
/**
|
||||
* Create a new PacketizedAudioStream from the first packet in the given
|
||||
* stream. It does not own the packet and must be queued again later.
|
||||
*
|
||||
* @param firstPacket the SeekableReadStream from which to read the MP3 data
|
||||
* @return a new PacketizedAudioStream
|
||||
*/
|
||||
PacketizedAudioStream *makePacketizedMP3Stream(
|
||||
Common::SeekableReadStream &firstPacket);
|
||||
|
||||
/**
|
||||
* Create a new PacketizedAudioStream for a given number of channels
|
||||
* and sample rate.
|
||||
*
|
||||
* @param firstPacket the SeekableReadStream from which to read the MP3 data
|
||||
* @return a new PacketizedAudioStream
|
||||
*/
|
||||
PacketizedAudioStream *makePacketizedMP3Stream(
|
||||
uint channels, uint rate);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_MAD
|
||||
#endif // #ifndef AUDIO_MP3_H
|
||||
338
audio/decoders/mpc.cpp
Normal file
338
audio/decoders/mpc.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
|
||||
#include "audio/decoders/mpc.h"
|
||||
|
||||
#ifdef USE_MPCDEC
|
||||
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
#include <mpcdec/mpcdec.h>
|
||||
#else
|
||||
#include <mpc/mpcdec.h>
|
||||
#endif
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// These are wrapper functions to allow using a SeekableReadStream object to
|
||||
// provide data to the mpc_reader object.
|
||||
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
static mpc_int32_t read_stream(void *data, void *ptr, mpc_int32_t size) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)data;
|
||||
#else
|
||||
static mpc_int32_t read_stream(mpc_reader *p_reader, void *ptr, mpc_int32_t size) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)p_reader->data;
|
||||
#endif
|
||||
|
||||
return stream->read(ptr, size);
|
||||
}
|
||||
|
||||
/// Seeks to byte position offset.
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
static mpc_bool_t seek_stream(void *data, mpc_int32_t offset) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)data;
|
||||
#else
|
||||
static mpc_bool_t seek_stream(mpc_reader *p_reader, mpc_int32_t offset) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)p_reader->data;
|
||||
#endif
|
||||
|
||||
return stream->seek(offset);
|
||||
}
|
||||
|
||||
/// Returns the current byte offset in the stream.
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
static mpc_int32_t tell_stream(void *data) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)data;
|
||||
#else
|
||||
static mpc_int32_t tell_stream(mpc_reader *p_reader) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)p_reader->data;
|
||||
#endif
|
||||
|
||||
return stream->pos();
|
||||
}
|
||||
|
||||
/// Returns the total length of the source stream, in bytes.
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
static mpc_int32_t get_size_stream(void *data) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)data;
|
||||
#else
|
||||
static mpc_int32_t get_size_stream(mpc_reader *p_reader) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)p_reader->data;
|
||||
#endif
|
||||
|
||||
return stream->size();
|
||||
}
|
||||
|
||||
/// True if the stream is a seekable stream.
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
static mpc_bool_t canseek_stream(void *p_reader) {
|
||||
return TRUE;
|
||||
}
|
||||
#else
|
||||
static mpc_bool_t canseek_stream(mpc_reader *p_reader) {
|
||||
return MPC_TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- Musepack stream ---
|
||||
#pragma mark -
|
||||
|
||||
|
||||
class MPCStream : public SeekableAudioStream {
|
||||
protected:
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _inStream;
|
||||
|
||||
bool _isStereo;
|
||||
int _rate;
|
||||
|
||||
Timestamp _length;
|
||||
|
||||
mpc_reader _reader;
|
||||
mpc_streaminfo _si;
|
||||
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
mpc_decoder _decoder;
|
||||
#else
|
||||
mpc_demux *_demux;
|
||||
#endif
|
||||
|
||||
MPC_SAMPLE_FORMAT _bufferDec[MPC_DECODER_BUFFER_LENGTH];
|
||||
uint16 _buffer[MPC_DECODER_BUFFER_LENGTH];
|
||||
const uint16 *_bufferEnd;
|
||||
const uint16 *_pos;
|
||||
|
||||
public:
|
||||
// startTime / duration are in milliseconds
|
||||
MPCStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose);
|
||||
~MPCStream();
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
|
||||
bool endOfData() const override { return _pos >= _bufferEnd; }
|
||||
bool isStereo() const override { return _isStereo; }
|
||||
int getRate() const override { return _rate; }
|
||||
|
||||
bool seek(const Timestamp &where) override;
|
||||
Timestamp getLength() const override { return _length; }
|
||||
protected:
|
||||
bool refill();
|
||||
};
|
||||
|
||||
MPCStream::MPCStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
|
||||
_inStream(inStream, dispose),
|
||||
_length(0, 1000),
|
||||
_bufferEnd(ARRAYEND(_buffer)) {
|
||||
|
||||
_pos = _bufferEnd; // This will return endOfBuffer() if we're not properly inited
|
||||
|
||||
_reader.read = read_stream;
|
||||
_reader.seek = seek_stream;
|
||||
_reader.tell = tell_stream;
|
||||
_reader.get_size = get_size_stream;
|
||||
_reader.canseek = canseek_stream;
|
||||
_reader.data = (void *)inStream;
|
||||
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
mpc_streaminfo_init(&_si);
|
||||
if (mpc_streaminfo_read(&_si, &_reader) < 0) {
|
||||
warning("Cannot read musepack stream info");
|
||||
return;
|
||||
}
|
||||
mpc_decoder_setup(&_decoder, &_reader);
|
||||
mpc_decoder_scale_output (&_decoder, 1.0);
|
||||
if (!mpc_decoder_initialize(&_decoder, &_si)) {
|
||||
warning("Cannot initialize musepack decoder");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
_demux = mpc_demux_init(&_reader);
|
||||
|
||||
if (!_demux) {
|
||||
warning("Cannot init musepack demuxer");
|
||||
return;
|
||||
}
|
||||
|
||||
mpc_demux_get_info(_demux, &_si);
|
||||
#endif
|
||||
|
||||
_isStereo = _si.channels >= 2;
|
||||
_rate = _si.sample_freq;
|
||||
_length = Timestamp(uint32(mpc_streaminfo_get_length(&_si) * 1000.0), getRate());
|
||||
|
||||
int time = (int)mpc_streaminfo_get_length(&_si);
|
||||
int minutes = time / 60;
|
||||
int seconds = time % 60;
|
||||
|
||||
debug(9, "stream version %d", _si.stream_version);
|
||||
debug(9, "encoder: %s", _si.encoder);
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
debug(9, "profile: %s (q=%d)", _si.profile_name, _si.profile);
|
||||
#else
|
||||
debug(9, "profile: %s (q=%0.2f)", _si.profile_name, _si.profile - 5);
|
||||
debug(9, "PNS: %s", _si.pns == 0xFF ? "unknow" : _si.pns ? "on" : "off");
|
||||
#endif
|
||||
debug(9, "mid/side stereo: %s", _si.ms ? "on" : "off");
|
||||
debug(9, "gapless: %s", _si.is_true_gapless ? "on" : "off");
|
||||
debug(9, "average bitrate: %6.1f kbps", _si.average_bitrate * 1.e-3);
|
||||
debug(9, "samplerate: %d Hz", _si.sample_freq);
|
||||
debug(9, "channels: %d", _si.channels);
|
||||
debug(9, "length: %d:%.2d (%u samples)", minutes, seconds, (mpc_uint32_t)mpc_streaminfo_get_length_samples(&_si));
|
||||
debug(9, "file size: %d Bytes", _si.total_file_length);
|
||||
debug(9, "track peak: %2.2f dB", _si.peak_title / 256.f);
|
||||
debug(9, "track gain: %2.2f dB / %2.2f dB", _si.gain_title / 256.f, _si.gain_title == 0 ? 0 : 64.82f - _si.gain_title / 256.f);
|
||||
debug(9, "album peak: %2.2f dB", _si.peak_album / 256.f);
|
||||
debug(9, "album gain: %2.2f dB / %2.2f dB", _si.gain_album / 256.f, _si.gain_album == 0 ? 0 : 64.82f - _si.gain_album / 256.f);
|
||||
|
||||
if (!refill())
|
||||
return;
|
||||
}
|
||||
|
||||
MPCStream::~MPCStream() {
|
||||
#ifndef USE_MPCDEC_OLD_API
|
||||
mpc_demux_exit(_demux);
|
||||
#endif
|
||||
}
|
||||
|
||||
int MPCStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samples = 0;
|
||||
while (samples < numSamples && _pos < _bufferEnd) {
|
||||
const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
|
||||
memcpy(buffer, _pos, len * 2);
|
||||
buffer += len;
|
||||
_pos += len;
|
||||
samples += len;
|
||||
if (_pos >= _bufferEnd) {
|
||||
if (!refill())
|
||||
break;
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
bool MPCStream::seek(const Timestamp &where) {
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
bool res = (mpc_decoder_seek_seconds(&_decoder, (double)where.msecs() / 1000.0) == TRUE);
|
||||
#else
|
||||
bool res = (mpc_demux_seek_second(_demux, (double)where.msecs() / 1000.0) == MPC_STATUS_OK);
|
||||
#endif
|
||||
if (!res) {
|
||||
warning("Error seeking in musepack stream");
|
||||
_pos = _bufferEnd;
|
||||
return false;
|
||||
}
|
||||
|
||||
return refill();
|
||||
}
|
||||
|
||||
bool MPCStream::refill() {
|
||||
bool result;
|
||||
uint32 samples;
|
||||
|
||||
#ifdef USE_MPCDEC_OLD_API
|
||||
uint32 vbr_update_acc, vbr_update_bits;
|
||||
samples = mpc_decoder_decode(&_decoder, _bufferDec, &vbr_update_acc, &vbr_update_bits);
|
||||
|
||||
if (samples == 0) { // End of stream
|
||||
_pos = _buffer;
|
||||
_bufferEnd = _buffer;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (samples == -1u) { // Corruptd stream
|
||||
result = false;
|
||||
samples = 0;
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
#else
|
||||
mpc_frame_info frame;
|
||||
frame.buffer = _bufferDec;
|
||||
|
||||
result = (mpc_demux_decode(_demux, &frame) == MPC_STATUS_OK);
|
||||
|
||||
if (frame.bits == -1) { // End of stream
|
||||
_pos = _buffer;
|
||||
_bufferEnd = _buffer;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
samples = frame.samples;
|
||||
#endif
|
||||
|
||||
if (!result) {
|
||||
// Possibly recoverable, just warn about it
|
||||
warning("Corrupted data in musepack file");
|
||||
}
|
||||
|
||||
#ifdef MPC_FIXED_POINT
|
||||
for(int i = 0; i < MPC_DECODER_BUFFER_LENGTH; i++) {
|
||||
int tmp = _bufferDec[i] >> MPC_FIXED_POINT_FRACTPART;
|
||||
if (tmp > ((1 << 15) - 1)) tmp = ((1 << 15) - 1);
|
||||
if (tmp < -(1 << 15)) tmp = -(1 << 15);
|
||||
_buffer[i] = tmp;
|
||||
}
|
||||
#else
|
||||
for (int i = 0; i < MPC_DECODER_BUFFER_LENGTH; i++) {
|
||||
int tmp = nearbyintf(_bufferDec[i] * (1 << 15));
|
||||
if (tmp > ((1 << 15) - 1))
|
||||
tmp = ((1 << 15) - 1);
|
||||
if (tmp < -(1 << 15))
|
||||
tmp = -(1 << 15);
|
||||
_buffer[i] = (uint16)tmp;
|
||||
}
|
||||
#endif
|
||||
|
||||
_pos = _buffer;
|
||||
_bufferEnd = &_buffer[samples * _si.channels];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- Ogg Vorbis factory functions ---
|
||||
#pragma mark -
|
||||
|
||||
SeekableAudioStream *makeMPCStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse) {
|
||||
SeekableAudioStream *s = new MPCStream(stream, disposeAfterUse);
|
||||
if (s && s->endOfData()) {
|
||||
delete s;
|
||||
return nullptr;
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_MPCDEC
|
||||
59
audio/decoders/mpc.h
Normal file
59
audio/decoders/mpc.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - qdengine
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_MPC_H
|
||||
#define AUDIO_MPC_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#ifdef USE_MPCDEC
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Create a new SeekableAudioStream from the Ogg Vorbis data in the given stream.
|
||||
* Allows for seeking (which is why we require a SeekableReadStream).
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the Ogg Vorbis data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeMPCStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_MPCDEC
|
||||
#endif // #ifndef AUDIO_MPC_H
|
||||
2611
audio/decoders/qdm2.cpp
Normal file
2611
audio/decoders/qdm2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
53
audio/decoders/qdm2.h
Normal file
53
audio/decoders/qdm2.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef AUDIO_QDM2_H
|
||||
#define AUDIO_QDM2_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
#ifdef USE_QDM2
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class Codec;
|
||||
|
||||
/**
|
||||
* Create a new Codec from the QDM2 data in the given stream.
|
||||
*
|
||||
* @param extraData the QuickTime extra data stream
|
||||
* @param disposeExtraData the QuickTime extra data stream
|
||||
* @return a new Codec, or NULL, if an error occurred
|
||||
*/
|
||||
Codec *makeQDM2Decoder(Common::SeekableReadStream *extraData,
|
||||
DisposeAfterUse::Flag disposeExtraData = DisposeAfterUse::NO);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // USE_QDM2
|
||||
#endif // AUDIO_QDM2_H
|
||||
527
audio/decoders/qdm2data.h
Normal file
527
audio/decoders/qdm2data.h
Normal file
@@ -0,0 +1,527 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_QDM2DATA_H
|
||||
#define AUDIO_QDM2DATA_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
/// VLC TABLES
|
||||
|
||||
// values in this table range from -1..23; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_level_huffcodes[24] = {
|
||||
0x037c, 0x0004, 0x003c, 0x004c, 0x003a, 0x002c, 0x001c, 0x001a,
|
||||
0x0024, 0x0014, 0x0001, 0x0002, 0x0000, 0x0003, 0x0007, 0x0005,
|
||||
0x0006, 0x0008, 0x0009, 0x000a, 0x000c, 0x00fc, 0x007c, 0x017c
|
||||
};
|
||||
|
||||
static const byte vlc_tab_level_huffbits[24] = {
|
||||
10, 6, 7, 7, 6, 6, 6, 6, 6, 5, 4, 4, 4, 3, 3, 3, 3, 4, 4, 5, 7, 8, 9, 10
|
||||
};
|
||||
|
||||
// values in this table range from -1..36; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_diff_huffcodes[37] = {
|
||||
0x1c57, 0x0004, 0x0000, 0x0001, 0x0003, 0x0002, 0x000f, 0x000e,
|
||||
0x0007, 0x0016, 0x0037, 0x0027, 0x0026, 0x0066, 0x0006, 0x0097,
|
||||
0x0046, 0x01c6, 0x0017, 0x0786, 0x0086, 0x0257, 0x00d7, 0x0357,
|
||||
0x00c6, 0x0386, 0x0186, 0x0000, 0x0157, 0x0c57, 0x0057, 0x0000,
|
||||
0x0b86, 0x0000, 0x1457, 0x0000, 0x0457
|
||||
};
|
||||
|
||||
static const byte vlc_tab_diff_huffbits[37] = {
|
||||
13, 3, 3, 2, 3, 3, 4, 4, 6, 5, 6, 6, 7, 7, 8, 8,
|
||||
8, 9, 8, 11, 9, 10, 8, 10, 9, 12, 10, 0, 10, 13, 11, 0,
|
||||
12, 0, 13, 0, 13
|
||||
};
|
||||
|
||||
// values in this table range from -1..5; adjust retrieved value by -1
|
||||
static const byte vlc_tab_run_huffcodes[6] = {
|
||||
0x1f, 0x00, 0x01, 0x03, 0x07, 0x0f
|
||||
};
|
||||
|
||||
static const byte vlc_tab_run_huffbits[6] = {
|
||||
5, 1, 2, 3, 4, 5
|
||||
};
|
||||
|
||||
// values in this table range from -1..19; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_tone_level_idx_hi1_huffcodes[20] = {
|
||||
0x5714, 0x000c, 0x0002, 0x0001, 0x0000, 0x0004, 0x0034, 0x0054,
|
||||
0x0094, 0x0014, 0x0114, 0x0214, 0x0314, 0x0614, 0x0e14, 0x0f14,
|
||||
0x2714, 0x0714, 0x1714, 0x3714
|
||||
};
|
||||
|
||||
static const byte vlc_tab_tone_level_idx_hi1_huffbits[20] = {
|
||||
15, 4, 2, 1, 3, 5, 6, 7, 8, 10, 10, 11, 11, 12, 12, 12, 14, 14, 15, 14
|
||||
};
|
||||
|
||||
// values in this table range from -1..23; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_tone_level_idx_mid_huffcodes[24] = {
|
||||
0x0fea, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0x0000, 0x03ea, 0x00ea, 0x002a, 0x001a,
|
||||
0x0006, 0x0001, 0x0000, 0x0002, 0x000a, 0x006a, 0x01ea, 0x07ea
|
||||
};
|
||||
|
||||
static const byte vlc_tab_tone_level_idx_mid_huffbits[24] = {
|
||||
12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 7, 5, 3, 1, 2, 4, 6, 8, 10, 12
|
||||
};
|
||||
|
||||
// values in this table range from -1..23; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_tone_level_idx_hi2_huffcodes[24] = {
|
||||
0x0664, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0064, 0x00e4,
|
||||
0x00a4, 0x0068, 0x0004, 0x0008, 0x0014, 0x0018, 0x0000, 0x0001,
|
||||
0x0002, 0x0003, 0x000c, 0x0028, 0x0024, 0x0164, 0x0000, 0x0264
|
||||
};
|
||||
|
||||
static const byte vlc_tab_tone_level_idx_hi2_huffbits[24] = {
|
||||
11, 0, 0, 0, 0, 0, 10, 8, 8, 7, 6, 6, 5, 5, 4, 2, 2, 2, 4, 7, 8, 9, 0, 11
|
||||
};
|
||||
|
||||
// values in this table range from -1..8; adjust retrieved value by -1
|
||||
static const byte vlc_tab_type30_huffcodes[9] = {
|
||||
0x3c, 0x06, 0x00, 0x01, 0x03, 0x02, 0x04, 0x0c, 0x1c
|
||||
};
|
||||
|
||||
static const byte vlc_tab_type30_huffbits[9] = {
|
||||
6, 3, 3, 2, 2, 3, 4, 5, 6
|
||||
};
|
||||
|
||||
// values in this table range from -1..9; adjust retrieved value by -1
|
||||
static const byte vlc_tab_type34_huffcodes[10] = {
|
||||
0x18, 0x00, 0x01, 0x04, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08
|
||||
};
|
||||
|
||||
static const byte vlc_tab_type34_huffbits[10] = {
|
||||
5, 4, 3, 3, 3, 3, 3, 3, 3, 5
|
||||
};
|
||||
|
||||
// values in this table range from -1..22; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_fft_tone_offset_0_huffcodes[23] = {
|
||||
0x038e, 0x0001, 0x0000, 0x0022, 0x000a, 0x0006, 0x0012, 0x0002,
|
||||
0x001e, 0x003e, 0x0056, 0x0016, 0x000e, 0x0032, 0x0072, 0x0042,
|
||||
0x008e, 0x004e, 0x00f2, 0x002e, 0x0036, 0x00c2, 0x018e
|
||||
};
|
||||
|
||||
static const byte vlc_tab_fft_tone_offset_0_huffbits[23] = {
|
||||
10, 1, 2, 6, 4, 5, 6, 7, 6, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 6, 6, 8, 10
|
||||
};
|
||||
|
||||
// values in this table range from -1..27; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_fft_tone_offset_1_huffcodes[28] = {
|
||||
0x07a4, 0x0001, 0x0020, 0x0012, 0x001c, 0x0008, 0x0006, 0x0010,
|
||||
0x0000, 0x0014, 0x0004, 0x0032, 0x0070, 0x000c, 0x0002, 0x003a,
|
||||
0x001a, 0x002c, 0x002a, 0x0022, 0x0024, 0x000a, 0x0064, 0x0030,
|
||||
0x0062, 0x00a4, 0x01a4, 0x03a4
|
||||
};
|
||||
|
||||
static const byte vlc_tab_fft_tone_offset_1_huffbits[28] = {
|
||||
11, 1, 6, 6, 5, 4, 3, 6, 6, 5, 6, 6, 7, 6, 6, 6,
|
||||
6, 6, 6, 7, 8, 6, 7, 7, 7, 9, 10, 11
|
||||
};
|
||||
|
||||
// values in this table range from -1..31; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_fft_tone_offset_2_huffcodes[32] = {
|
||||
0x1760, 0x0001, 0x0000, 0x0082, 0x000c, 0x0006, 0x0003, 0x0007,
|
||||
0x0008, 0x0004, 0x0010, 0x0012, 0x0022, 0x001a, 0x0000, 0x0020,
|
||||
0x000a, 0x0040, 0x004a, 0x006a, 0x002a, 0x0042, 0x0002, 0x0060,
|
||||
0x00aa, 0x00e0, 0x00c2, 0x01c2, 0x0160, 0x0360, 0x0760, 0x0f60
|
||||
};
|
||||
|
||||
static const byte vlc_tab_fft_tone_offset_2_huffbits[32] = {
|
||||
13, 2, 0, 8, 4, 3, 3, 3, 4, 4, 5, 5, 6, 5, 7, 7,
|
||||
7, 7, 7, 7, 8, 8, 8, 9, 8, 8, 9, 9, 10, 11, 13, 12
|
||||
};
|
||||
|
||||
// values in this table range from -1..34; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_fft_tone_offset_3_huffcodes[35] = {
|
||||
0x33ea, 0x0005, 0x0000, 0x000c, 0x0000, 0x0006, 0x0003, 0x0008,
|
||||
0x0002, 0x0001, 0x0004, 0x0007, 0x001a, 0x000f, 0x001c, 0x002c,
|
||||
0x000a, 0x001d, 0x002d, 0x002a, 0x000d, 0x004c, 0x008c, 0x006a,
|
||||
0x00cd, 0x004d, 0x00ea, 0x020c, 0x030c, 0x010c, 0x01ea, 0x07ea,
|
||||
0x0bea, 0x03ea, 0x13ea
|
||||
};
|
||||
|
||||
static const byte vlc_tab_fft_tone_offset_3_huffbits[35] = {
|
||||
14, 4, 0, 10, 4, 3, 3, 4, 4, 3, 4, 4, 5, 4, 5, 6,
|
||||
6, 5, 6, 7, 7, 7, 8, 8, 8, 8, 9, 10, 10, 10, 10, 11,
|
||||
12, 13, 14
|
||||
};
|
||||
|
||||
// values in this table range from -1..37; adjust retrieved value by -1
|
||||
static const uint16 vlc_tab_fft_tone_offset_4_huffcodes[38] = {
|
||||
0x5282, 0x0016, 0x0000, 0x0136, 0x0004, 0x0000, 0x0007, 0x000a,
|
||||
0x000e, 0x0003, 0x0001, 0x000d, 0x0006, 0x0009, 0x0012, 0x0005,
|
||||
0x0025, 0x0022, 0x0015, 0x0002, 0x0076, 0x0035, 0x0042, 0x00c2,
|
||||
0x0182, 0x00b6, 0x0036, 0x03c2, 0x0482, 0x01c2, 0x0682, 0x0882,
|
||||
0x0a82, 0x0082, 0x0282, 0x1282, 0x3282, 0x2282
|
||||
};
|
||||
|
||||
static const byte vlc_tab_fft_tone_offset_4_huffbits[38] = {
|
||||
15, 6, 0, 9, 3, 3, 3, 4, 4, 3, 4, 4, 5, 4, 5, 6,
|
||||
6, 6, 6, 8, 7, 6, 8, 9, 9, 8, 9, 10, 11, 10, 11, 12,
|
||||
12, 12, 14, 15, 14, 14
|
||||
};
|
||||
|
||||
/// FFT TABLES
|
||||
|
||||
// values in this table range from -1..27; adjust retrieved value by -1
|
||||
static const uint16 fft_level_exp_alt_huffcodes[28] = {
|
||||
0x1ec6, 0x0006, 0x00c2, 0x0142, 0x0242, 0x0246, 0x00c6, 0x0046,
|
||||
0x0042, 0x0146, 0x00a2, 0x0062, 0x0026, 0x0016, 0x000e, 0x0005,
|
||||
0x0004, 0x0003, 0x0000, 0x0001, 0x000a, 0x0012, 0x0002, 0x0022,
|
||||
0x01c6, 0x02c6, 0x06c6, 0x0ec6
|
||||
};
|
||||
|
||||
static const byte fft_level_exp_alt_huffbits[28] = {
|
||||
13, 7, 8, 9, 10, 10, 10, 10, 10, 9, 8, 7, 6, 5, 4, 3,
|
||||
3, 2, 3, 3, 4, 5, 7, 8, 9, 11, 12, 13
|
||||
};
|
||||
|
||||
// values in this table range from -1..19; adjust retrieved value by -1
|
||||
static const uint16 fft_level_exp_huffcodes[20] = {
|
||||
0x0f24, 0x0001, 0x0002, 0x0000, 0x0006, 0x0005, 0x0007, 0x000c,
|
||||
0x000b, 0x0014, 0x0013, 0x0004, 0x0003, 0x0023, 0x0064, 0x00a4,
|
||||
0x0024, 0x0124, 0x0324, 0x0724
|
||||
};
|
||||
|
||||
static const byte fft_level_exp_huffbits[20] = {
|
||||
12, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6, 6, 7, 8, 9, 10, 11, 12
|
||||
};
|
||||
|
||||
// values in this table range from -1..6; adjust retrieved value by -1
|
||||
static const byte fft_stereo_exp_huffcodes[7] = {
|
||||
0x3e, 0x01, 0x00, 0x02, 0x06, 0x0e, 0x1e
|
||||
};
|
||||
|
||||
static const byte fft_stereo_exp_huffbits[7] = {
|
||||
6, 1, 2, 3, 4, 5, 6
|
||||
};
|
||||
|
||||
// values in this table range from -1..8; adjust retrieved value by -1
|
||||
static const byte fft_stereo_phase_huffcodes[9] = {
|
||||
0x35, 0x02, 0x00, 0x01, 0x0d, 0x15, 0x05, 0x09, 0x03
|
||||
};
|
||||
|
||||
static const byte fft_stereo_phase_huffbits[9] = {
|
||||
6, 2, 2, 4, 4, 6, 5, 4, 2
|
||||
};
|
||||
|
||||
static const int fft_cutoff_index_table[4][2] = {
|
||||
{ 1, 2 }, {-1, 0 }, {-1,-2 }, { 0, 0 }
|
||||
};
|
||||
|
||||
static const int16 fft_level_index_table[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
};
|
||||
|
||||
static const byte last_coeff[3] = {
|
||||
4, 7, 10
|
||||
};
|
||||
|
||||
static const byte coeff_per_sb_for_avg[3][30] = {
|
||||
{ 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
|
||||
{ 0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 },
|
||||
{ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9 }
|
||||
};
|
||||
|
||||
static const uint32 dequant_table[3][10][30] = {
|
||||
{ { 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 256, 256, 205, 154, 102, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 51, 102, 154, 205, 256, 238, 219, 201, 183, 165, 146, 128, 110, 91, 73, 55, 37, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 18, 37, 55, 73, 91, 110, 128, 146, 165, 183, 201, 219, 238, 256, 228, 199, 171, 142, 114, 85, 57, 28 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
|
||||
{ { 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 256, 171, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 85, 171, 256, 171, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 85, 171, 256, 219, 183, 146, 110, 73, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 73, 110, 146, 183, 219, 256, 228, 199, 171, 142, 114, 85, 57, 28, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 57, 85, 114, 142, 171, 199, 228, 256, 213, 171, 128, 85, 43 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
|
||||
{ { 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 256, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 256, 171, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 85, 171, 256, 192, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 128, 192, 256, 205, 154, 102, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 102, 154, 205, 256, 213, 171, 128, 85, 43, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 85, 128, 171, 213, 256, 213, 171, 128, 85, 43 } }
|
||||
};
|
||||
|
||||
static const byte coeff_per_sb_for_dequant[3][30] = {
|
||||
{ 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
|
||||
{ 0, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6 },
|
||||
{ 0, 1, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9 }
|
||||
};
|
||||
|
||||
// first index is subband, 2nd index is 0, 1 or 3 (2 is unused)
|
||||
static const int8 tone_level_idx_offset_table[30][4] = {
|
||||
{ -50, -50, 0, -50 },
|
||||
{ -50, -50, 0, -50 },
|
||||
{ -50, -9, 0, -19 },
|
||||
{ -16, -6, 0, -12 },
|
||||
{ -11, -4, 0, -8 },
|
||||
{ -8, -3, 0, -6 },
|
||||
{ -7, -3, 0, -5 },
|
||||
{ -6, -2, 0, -4 },
|
||||
{ -5, -2, 0, -3 },
|
||||
{ -4, -1, 0, -3 },
|
||||
{ -4, -1, 0, -2 },
|
||||
{ -3, -1, 0, -2 },
|
||||
{ -3, -1, 0, -2 },
|
||||
{ -3, -1, 0, -2 },
|
||||
{ -2, -1, 0, -1 },
|
||||
{ -2, -1, 0, -1 },
|
||||
{ -2, -1, 0, -1 },
|
||||
{ -2, 0, 0, -1 },
|
||||
{ -2, 0, 0, -1 },
|
||||
{ -1, 0, 0, -1 },
|
||||
{ -1, 0, 0, -1 },
|
||||
{ -1, 0, 0, -1 },
|
||||
{ -1, 0, 0, -1 },
|
||||
{ -1, 0, 0, -1 },
|
||||
{ -1, 0, 0, -1 },
|
||||
{ -1, 0, 0, -1 },
|
||||
{ -1, 0, 0, 0 },
|
||||
{ -1, 0, 0, 0 },
|
||||
{ -1, 0, 0, 0 },
|
||||
{ -1, 0, 0, 0 }
|
||||
};
|
||||
|
||||
/* all my samples have 1st index 0 or 1 */
|
||||
/* second index is subband, only indexes 0-29 seem to be used */
|
||||
static const int8 coding_method_table[5][30] = {
|
||||
{ 34, 30, 24, 24, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10, 10,
|
||||
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
|
||||
},
|
||||
{ 34, 30, 24, 24, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10, 10,
|
||||
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
|
||||
},
|
||||
{ 34, 30, 30, 30, 24, 24, 16, 16, 16, 16, 16, 16, 10, 10, 10,
|
||||
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
|
||||
},
|
||||
{ 34, 34, 30, 30, 24, 24, 24, 24, 16, 16, 16, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10, 10, 10
|
||||
},
|
||||
{ 34, 34, 30, 30, 30, 30, 30, 30, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16
|
||||
},
|
||||
};
|
||||
|
||||
static const int vlc_stage3_values[60] = {
|
||||
0, 1, 2, 3, 4, 6, 8, 10, 12, 16, 20, 24,
|
||||
28, 36, 44, 52, 60, 76, 92, 108, 124, 156, 188, 220,
|
||||
252, 316, 380, 444, 508, 636, 764, 892, 1020, 1276, 1532, 1788,
|
||||
2044, 2556, 3068, 3580, 4092, 5116, 6140, 7164, 8188, 10236, 12284, 14332,
|
||||
16380, 20476, 24572, 28668, 32764, 40956, 49148, 57340, 65532, 81916, 98300,114684
|
||||
};
|
||||
|
||||
static const float fft_tone_sample_table[4][16][5] = {
|
||||
{ { .0100000000f,-.0037037037f,-.0020000000f,-.0069444444f,-.0018416207f },
|
||||
{ .0416666667f, .0000000000f, .0000000000f,-.0208333333f,-.0123456791f },
|
||||
{ .1250000000f, .0558035709f, .0330687836f,-.0164473690f,-.0097465888f },
|
||||
{ .1562500000f, .0625000000f, .0370370370f,-.0062500000f,-.0037037037f },
|
||||
{ .1996007860f, .0781250000f, .0462962948f, .0022727272f, .0013468013f },
|
||||
{ .2000000000f, .0625000000f, .0370370373f, .0208333333f, .0074074073f },
|
||||
{ .2127659619f, .0555555556f, .0329218097f, .0208333333f, .0123456791f },
|
||||
{ .2173913121f, .0473484844f, .0280583613f, .0347222239f, .0205761325f },
|
||||
{ .2173913121f, .0347222239f, .0205761325f, .0473484844f, .0280583613f },
|
||||
{ .2127659619f, .0208333333f, .0123456791f, .0555555556f, .0329218097f },
|
||||
{ .2000000000f, .0208333333f, .0074074073f, .0625000000f, .0370370370f },
|
||||
{ .1996007860f, .0022727272f, .0013468013f, .0781250000f, .0462962948f },
|
||||
{ .1562500000f,-.0062500000f,-.0037037037f, .0625000000f, .0370370370f },
|
||||
{ .1250000000f,-.0164473690f,-.0097465888f, .0558035709f, .0330687836f },
|
||||
{ .0416666667f,-.0208333333f,-.0123456791f, .0000000000f, .0000000000f },
|
||||
{ .0100000000f,-.0069444444f,-.0018416207f,-.0037037037f,-.0020000000f } },
|
||||
|
||||
{ { .0050000000f,-.0200000000f, .0125000000f,-.3030303030f, .0020000000f },
|
||||
{ .1041666642f, .0400000000f,-.0250000000f, .0333333333f,-.0200000000f },
|
||||
{ .1250000000f, .0100000000f, .0142857144f,-.0500000007f,-.0200000000f },
|
||||
{ .1562500000f,-.0006250000f,-.00049382716f,-.000625000f,-.00049382716f },
|
||||
{ .1562500000f,-.0006250000f,-.00049382716f,-.000625000f,-.00049382716f },
|
||||
{ .1250000000f,-.0500000000f,-.0200000000f, .0100000000f, .0142857144f },
|
||||
{ .1041666667f, .0333333333f,-.0200000000f, .0400000000f,-.0250000000f },
|
||||
{ .0050000000f,-.3030303030f, .0020000001f,-.0200000000f, .0125000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f } },
|
||||
|
||||
{ { .1428571492f, .1250000000f,-.0285714287f,-.0357142873f, .0208333333f },
|
||||
{ .1818181818f, .0588235296f, .0333333333f, .0212765951f, .0100000000f },
|
||||
{ .1818181818f, .0212765951f, .0100000000f, .0588235296f, .0333333333f },
|
||||
{ .1428571492f,-.0357142873f, .0208333333f, .1250000000f,-.0285714287f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f } },
|
||||
|
||||
{ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
|
||||
{ .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f } }
|
||||
};
|
||||
|
||||
static const float fft_tone_level_table[2][64] = { {
|
||||
// pow ~ (i > 46) ? 0 : (((((i & 1) ? 431 : 304) << (i >> 1))) / 1024.0);
|
||||
0.17677669f, 0.42677650f, 0.60355347f, 0.85355347f,
|
||||
1.20710683f, 1.68359375f, 2.37500000f, 3.36718750f,
|
||||
4.75000000f, 6.73437500f, 9.50000000f, 13.4687500f,
|
||||
19.0000000f, 26.9375000f, 38.0000000f, 53.8750000f,
|
||||
76.0000000f, 107.750000f, 152.000000f, 215.500000f,
|
||||
304.000000f, 431.000000f, 608.000000f, 862.000000f,
|
||||
1216.00000f, 1724.00000f, 2432.00000f, 3448.00000f,
|
||||
4864.00000f, 6896.00000f, 9728.00000f, 13792.0000f,
|
||||
19456.0000f, 27584.0000f, 38912.0000f, 55168.0000f,
|
||||
77824.0000f, 110336.000f, 155648.000f, 220672.000f,
|
||||
311296.000f, 441344.000f, 622592.000f, 882688.000f,
|
||||
1245184.00f, 1765376.00f, 2490368.00f, 0.00000000f,
|
||||
0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
|
||||
0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
|
||||
0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
|
||||
0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
|
||||
}, {
|
||||
// pow = (i > 45) ? 0 : ((((i & 1) ? 431 : 304) << (i >> 1)) / 512.0);
|
||||
0.59375000f, 0.84179688f, 1.18750000f, 1.68359375f,
|
||||
2.37500000f, 3.36718750f, 4.75000000f, 6.73437500f,
|
||||
9.50000000f, 13.4687500f, 19.0000000f, 26.9375000f,
|
||||
38.0000000f, 53.8750000f, 76.0000000f, 107.750000f,
|
||||
152.000000f, 215.500000f, 304.000000f, 431.000000f,
|
||||
608.000000f, 862.000000f, 1216.00000f, 1724.00000f,
|
||||
2432.00000f, 3448.00000f, 4864.00000f, 6896.00000f,
|
||||
9728.00000f, 13792.0000f, 19456.0000f, 27584.0000f,
|
||||
38912.0000f, 55168.0000f, 77824.0000f, 110336.000f,
|
||||
155648.000f, 220672.000f, 311296.000f, 441344.000f,
|
||||
622592.000f, 882688.000f, 1245184.00f, 1765376.00f,
|
||||
2490368.00f, 3530752.00f, 0.00000000f, 0.00000000f,
|
||||
0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
|
||||
0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
|
||||
0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
|
||||
0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f
|
||||
} };
|
||||
|
||||
static const float fft_tone_envelope_table[4][31] = {
|
||||
{ .009607375f, .038060248f, .084265202f, .146446645f, .222214907f, .308658302f,
|
||||
.402454883f, .500000060f, .597545207f, .691341758f, .777785182f, .853553414f,
|
||||
.915734828f, .961939812f, .990392685f, 1.00000000f, .990392625f, .961939752f,
|
||||
.915734768f, .853553295f, .777785063f, .691341639f, .597545087f, .500000000f,
|
||||
.402454853f, .308658272f, .222214878f, .146446615f, .084265172f, .038060218f,
|
||||
.009607345f },
|
||||
{ .038060248f, .146446645f, .308658302f, .500000060f, .691341758f, .853553414f,
|
||||
.961939812f, 1.00000000f, .961939752f, .853553295f, .691341639f, .500000000f,
|
||||
.308658272f, .146446615f, .038060218f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f },
|
||||
{ .146446645f, .500000060f, .853553414f, 1.00000000f, .853553295f, .500000000f,
|
||||
.146446615f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f },
|
||||
{ .500000060f, 1.00000000f, .500000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
|
||||
.000000000f }
|
||||
};
|
||||
|
||||
static const float sb_noise_attenuation[32] = {
|
||||
0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 0.7f, 1.0f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
};
|
||||
|
||||
static const byte fft_subpackets[32] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0
|
||||
};
|
||||
|
||||
// first index is joined_stereo, second index is 0 or 2 (1 is unused)
|
||||
static const float dequant_1bit[2][3] = {
|
||||
{-0.920000f, 0.000000f, 0.920000f },
|
||||
{-0.890000f, 0.000000f, 0.890000f }
|
||||
};
|
||||
|
||||
static const float type30_dequant[8] = {
|
||||
-1.0f,-0.625f,-0.291666656732559f,0.0f,
|
||||
0.25f,0.5f,0.75f,1.0f,
|
||||
};
|
||||
|
||||
static const float type34_delta[10] = { // FIXME: covers 8 entries..
|
||||
-1.0f,-0.60947573184967f,-0.333333343267441f,-0.138071194291115f,0.0f,
|
||||
0.138071194291115f,0.333333343267441f,0.60947573184967f,1.0f,0.0f,
|
||||
};
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
712
audio/decoders/quicktime.cpp
Normal file
712
audio/decoders/quicktime.cpp
Normal file
@@ -0,0 +1,712 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/util.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/decoders/codec.h"
|
||||
#include "audio/decoders/quicktime.h"
|
||||
#include "audio/decoders/quicktime_intern.h"
|
||||
|
||||
// Codecs
|
||||
#include "audio/decoders/aac.h"
|
||||
#include "audio/decoders/adpcm.h"
|
||||
#include "audio/decoders/qdm2.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/g711.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
/**
|
||||
* An AudioStream wrapper that forces audio to be played in mono.
|
||||
* It currently just ignores the right channel if stereo.
|
||||
*/
|
||||
class ForcedMonoAudioStream : public AudioStream {
|
||||
public:
|
||||
ForcedMonoAudioStream(AudioStream *parentStream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES) :
|
||||
_parentStream(parentStream), _disposeAfterUse(disposeAfterUse) {}
|
||||
|
||||
~ForcedMonoAudioStream() {
|
||||
if (_disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete _parentStream;
|
||||
}
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override {
|
||||
if (!_parentStream->isStereo())
|
||||
return _parentStream->readBuffer(buffer, numSamples);
|
||||
|
||||
int16 temp[2];
|
||||
int samples = 0;
|
||||
|
||||
while (samples < numSamples && !endOfData()) {
|
||||
_parentStream->readBuffer(temp, 2);
|
||||
*buffer++ = temp[0];
|
||||
samples++;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
bool endOfData() const override { return _parentStream->endOfData(); }
|
||||
bool isStereo() const override { return false; }
|
||||
int getRate() const override { return _parentStream->getRate(); }
|
||||
|
||||
private:
|
||||
AudioStream *_parentStream;
|
||||
DisposeAfterUse::Flag _disposeAfterUse;
|
||||
};
|
||||
|
||||
QuickTimeAudioDecoder::QuickTimeAudioDecoder() : Common::QuickTimeParser() {
|
||||
}
|
||||
|
||||
QuickTimeAudioDecoder::~QuickTimeAudioDecoder() {
|
||||
for (uint32 i = 0; i < _audioTracks.size(); i++)
|
||||
delete _audioTracks[i];
|
||||
}
|
||||
|
||||
bool QuickTimeAudioDecoder::loadAudioFile(const Common::Path &filename) {
|
||||
if (!Common::QuickTimeParser::parseFile(filename))
|
||||
return false;
|
||||
|
||||
init();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QuickTimeAudioDecoder::loadAudioStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle) {
|
||||
if (!Common::QuickTimeParser::parseStream(stream, disposeFileHandle))
|
||||
return false;
|
||||
|
||||
init();
|
||||
return true;
|
||||
}
|
||||
|
||||
void QuickTimeAudioDecoder::init() {
|
||||
Common::QuickTimeParser::init();
|
||||
|
||||
// Initialize all the audio streams
|
||||
// But ignore any streams we don't support
|
||||
for (uint32 i = 0; i < _tracks.size(); i++)
|
||||
if (_tracks[i]->codecType == CODEC_TYPE_AUDIO && ((AudioSampleDesc *)_tracks[i]->sampleDescs[0])->isAudioCodecSupported())
|
||||
_audioTracks.push_back(new QuickTimeAudioTrack(this, _tracks[i]));
|
||||
}
|
||||
|
||||
Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format, uint32 descSize) {
|
||||
if (track->codecType == CODEC_TYPE_AUDIO) {
|
||||
debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format));
|
||||
|
||||
AudioSampleDesc *entry = new AudioSampleDesc(track, format);
|
||||
|
||||
uint16 stsdVersion = _fd->readUint16BE();
|
||||
_fd->readUint16BE(); // revision level
|
||||
_fd->readUint32BE(); // vendor
|
||||
|
||||
entry->_channels = _fd->readUint16BE(); // channel count
|
||||
entry->_bitsPerSample = _fd->readUint16BE(); // sample size
|
||||
|
||||
_fd->readUint16BE(); // compression id = 0
|
||||
_fd->readUint16BE(); // packet size = 0
|
||||
|
||||
entry->_sampleRate = (_fd->readUint32BE() >> 16);
|
||||
|
||||
debug(0, "stsd version =%d", stsdVersion);
|
||||
if (stsdVersion == 0) {
|
||||
// Not used, except in special cases. See below.
|
||||
entry->_samplesPerFrame = entry->_bytesPerFrame = 0;
|
||||
} else if (stsdVersion == 1) {
|
||||
// Read QT version 1 fields. In version 0 these dont exist.
|
||||
entry->_samplesPerFrame = _fd->readUint32BE();
|
||||
debug(0, "stsd samples_per_frame =%d",entry->_samplesPerFrame);
|
||||
_fd->readUint32BE(); // bytes per packet
|
||||
entry->_bytesPerFrame = _fd->readUint32BE();
|
||||
debug(0, "stsd bytes_per_frame =%d", entry->_bytesPerFrame);
|
||||
_fd->readUint32BE(); // bytes per sample
|
||||
} else {
|
||||
warning("Unsupported QuickTime STSD audio version %d", stsdVersion);
|
||||
delete entry;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Version 0 files don't have some variables set, so we'll do that here
|
||||
if (format == MKTAG('i', 'm', 'a', '4')) {
|
||||
entry->_samplesPerFrame = 64;
|
||||
entry->_bytesPerFrame = 34 * entry->_channels;
|
||||
}
|
||||
|
||||
if (entry->_sampleRate == 0 && track->timeScale > 1)
|
||||
entry->_sampleRate = track->timeScale;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QuickTimeAudioDecoder::QuickTimeAudioTrack::QuickTimeAudioTrack(QuickTimeAudioDecoder *decoder, Common::QuickTimeParser::Track *parentTrack) {
|
||||
_decoder = decoder;
|
||||
_parentTrack = parentTrack;
|
||||
_queue = createStream();
|
||||
_samplesQueued = 0;
|
||||
|
||||
AudioSampleDesc *entry = (AudioSampleDesc *)_parentTrack->sampleDescs[0];
|
||||
|
||||
if (entry->getCodecTag() == MKTAG('r', 'a', 'w', ' ') || entry->getCodecTag() == MKTAG('t', 'w', 'o', 's'))
|
||||
_parentTrack->sampleSize = (entry->_bitsPerSample / 8) * entry->_channels;
|
||||
|
||||
// Initialize our edit parser too
|
||||
_curEdit = 0;
|
||||
enterNewEdit(Timestamp());
|
||||
|
||||
// If the edit doesn't start on a nice boundary, set us up to skip some samples
|
||||
Timestamp editStartTime(0, _parentTrack->editList[_curEdit].mediaTime, _parentTrack->timeScale);
|
||||
Timestamp trackPosition = getCurrentTrackTime();
|
||||
if (_parentTrack->editList[_curEdit].mediaTime != -1 && trackPosition != editStartTime)
|
||||
_skipSamples = editStartTime.convertToFramerate(getRate()) - trackPosition;
|
||||
}
|
||||
|
||||
QuickTimeAudioDecoder::QuickTimeAudioTrack::~QuickTimeAudioTrack() {
|
||||
delete _queue;
|
||||
}
|
||||
|
||||
void QuickTimeAudioDecoder::QuickTimeAudioTrack::queueAudio(const Timestamp &length) {
|
||||
if (allDataRead() || (length.totalNumberOfFrames() != 0 && Timestamp(0, _samplesQueued, getRate()) >= length))
|
||||
return;
|
||||
|
||||
do {
|
||||
Timestamp nextEditTime(0, _parentTrack->editList[_curEdit].timeOffset + _parentTrack->editList[_curEdit].trackDuration, _decoder->_timeScale);
|
||||
|
||||
if (_parentTrack->editList[_curEdit].mediaTime == -1) {
|
||||
// We've got an empty edit, so fill it with silence
|
||||
Timestamp editLength(0, _parentTrack->editList[_curEdit].trackDuration, _decoder->_timeScale);
|
||||
|
||||
// If we seek into the middle of an empty edit, we need to adjust
|
||||
if (_skipSamples != Timestamp()) {
|
||||
editLength = editLength - _skipSamples;
|
||||
_skipSamples = Timestamp();
|
||||
}
|
||||
|
||||
queueStream(makeLimitingAudioStream(makeSilentAudioStream(getRate(), isStereo()), editLength), editLength);
|
||||
_curEdit++;
|
||||
enterNewEdit(nextEditTime);
|
||||
} else {
|
||||
// Normal audio
|
||||
AudioStream *stream = readAudioChunk(_curChunk);
|
||||
Timestamp chunkLength = getChunkLength(_curChunk, _skipAACPrimer);
|
||||
_skipAACPrimer = false;
|
||||
_curChunk++;
|
||||
|
||||
// If we have any samples that we need to skip (ie. we seek'ed into
|
||||
// the middle of a chunk), skip them here.
|
||||
if (_skipSamples != Timestamp()) {
|
||||
if (_skipSamples > chunkLength) {
|
||||
// If the amount we need to skip is greater than the size
|
||||
// of the chunk, just skip it altogether.
|
||||
_curMediaPos = _curMediaPos + chunkLength;
|
||||
_skipSamples = _skipSamples - chunkLength;
|
||||
delete stream;
|
||||
continue;
|
||||
}
|
||||
|
||||
skipSamples(_skipSamples, stream);
|
||||
_curMediaPos = _curMediaPos + _skipSamples;
|
||||
chunkLength = chunkLength - _skipSamples;
|
||||
_skipSamples = Timestamp();
|
||||
}
|
||||
|
||||
// Calculate our overall position within the media
|
||||
Timestamp trackPosition = getCurrentTrackTime() + chunkLength;
|
||||
|
||||
// If we have reached the end of this edit (or have no more media to read),
|
||||
// we move on to the next edit
|
||||
if (trackPosition >= nextEditTime || _curChunk >= _parentTrack->chunkCount) {
|
||||
chunkLength = nextEditTime.convertToFramerate(getRate()) - getCurrentTrackTime();
|
||||
stream = makeLimitingAudioStream(stream, chunkLength);
|
||||
_curEdit++;
|
||||
enterNewEdit(nextEditTime);
|
||||
|
||||
// Next time around, we'll know how much to skip
|
||||
trackPosition = getCurrentTrackTime();
|
||||
if (!allDataRead() && _parentTrack->editList[_curEdit].mediaTime != -1 && nextEditTime != trackPosition)
|
||||
_skipSamples = nextEditTime.convertToFramerate(getRate()) - trackPosition;
|
||||
} else {
|
||||
_curMediaPos = _curMediaPos + chunkLength.convertToFramerate(_curMediaPos.framerate());
|
||||
}
|
||||
|
||||
queueStream(stream, chunkLength);
|
||||
}
|
||||
} while (!allDataRead() && Timestamp(0, _samplesQueued, getRate()) < length);
|
||||
}
|
||||
|
||||
Timestamp QuickTimeAudioDecoder::QuickTimeAudioTrack::getCurrentTrackTime() const {
|
||||
if (allDataRead())
|
||||
return getLength().convertToFramerate(getRate());
|
||||
|
||||
return Timestamp(0, _parentTrack->editList[_curEdit].timeOffset, _decoder->_timeScale).convertToFramerate(getRate())
|
||||
+ _curMediaPos - Timestamp(0, _parentTrack->editList[_curEdit].mediaTime, _parentTrack->timeScale).convertToFramerate(getRate());
|
||||
}
|
||||
|
||||
void QuickTimeAudioDecoder::QuickTimeAudioTrack::queueRemainingAudio() {
|
||||
queueAudio(getLength());
|
||||
}
|
||||
|
||||
int QuickTimeAudioDecoder::QuickTimeAudioTrack::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samplesRead = _queue->readBuffer(buffer, numSamples);
|
||||
_samplesQueued -= samplesRead / (isStereo() ? 2 : 1);
|
||||
return samplesRead;
|
||||
}
|
||||
|
||||
bool QuickTimeAudioDecoder::QuickTimeAudioTrack::allDataRead() const {
|
||||
return _curEdit == _parentTrack->editList.size();
|
||||
}
|
||||
|
||||
bool QuickTimeAudioDecoder::QuickTimeAudioTrack::endOfData() const {
|
||||
return allDataRead() && _queue->endOfData();
|
||||
}
|
||||
|
||||
bool QuickTimeAudioDecoder::QuickTimeAudioTrack::seek(const Timestamp &where) {
|
||||
// Recreate the queue
|
||||
delete _queue;
|
||||
_queue = createStream();
|
||||
_samplesQueued = 0;
|
||||
|
||||
if (where >= getLength()) {
|
||||
// We're done
|
||||
_curEdit = _parentTrack->editList.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find where we are in the stream
|
||||
findEdit(where);
|
||||
|
||||
// Now queue up some audio and skip whatever we need to skip
|
||||
Timestamp samplesToSkip = where.convertToFramerate(getRate()) - getCurrentTrackTime();
|
||||
queueAudio();
|
||||
skipSamples(samplesToSkip, _queue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Timestamp QuickTimeAudioDecoder::QuickTimeAudioTrack::getLength() const {
|
||||
return Timestamp(0, _parentTrack->duration, _decoder->_timeScale);
|
||||
}
|
||||
|
||||
QueuingAudioStream *QuickTimeAudioDecoder::QuickTimeAudioTrack::createStream() const {
|
||||
AudioSampleDesc *entry = (AudioSampleDesc *)_parentTrack->sampleDescs[0];
|
||||
return makeQueuingAudioStream(entry->_sampleRate, entry->_channels == 2);
|
||||
}
|
||||
|
||||
bool QuickTimeAudioDecoder::QuickTimeAudioTrack::isOldDemuxing() const {
|
||||
return _parentTrack->timeToSampleCount == 1 && _parentTrack->timeToSample[0].duration == 1;
|
||||
}
|
||||
|
||||
AudioStream *QuickTimeAudioDecoder::QuickTimeAudioTrack::readAudioChunk(uint chunk) {
|
||||
AudioSampleDesc *entry = (AudioSampleDesc *)_parentTrack->sampleDescs[0];
|
||||
Common::MemoryWriteStreamDynamic *wStream = new Common::MemoryWriteStreamDynamic(DisposeAfterUse::NO);
|
||||
|
||||
_decoder->_fd->seek(_parentTrack->chunkOffsets[chunk]);
|
||||
|
||||
// First, we have to get the sample count
|
||||
uint32 sampleCount = getAudioChunkSampleCount(chunk);
|
||||
assert(sampleCount != 0);
|
||||
|
||||
if (isOldDemuxing()) {
|
||||
// Old-style audio demuxing
|
||||
|
||||
// Then calculate the right sizes
|
||||
while (sampleCount > 0) {
|
||||
uint32 samples = 0, size = 0;
|
||||
|
||||
if (entry->_samplesPerFrame >= 160) {
|
||||
samples = entry->_samplesPerFrame;
|
||||
size = entry->_bytesPerFrame;
|
||||
} else if (entry->_samplesPerFrame > 1) {
|
||||
samples = MIN<uint32>((1024 / entry->_samplesPerFrame) * entry->_samplesPerFrame, sampleCount);
|
||||
size = (samples / entry->_samplesPerFrame) * entry->_bytesPerFrame;
|
||||
} else {
|
||||
samples = MIN<uint32>(1024, sampleCount);
|
||||
size = samples * _parentTrack->sampleSize;
|
||||
}
|
||||
|
||||
// Now, we read in the data for this data and output it
|
||||
byte *data = (byte *)malloc(size);
|
||||
_decoder->_fd->read(data, size);
|
||||
wStream->write(data, size);
|
||||
free(data);
|
||||
sampleCount -= samples;
|
||||
}
|
||||
} else {
|
||||
// New-style audio demuxing
|
||||
|
||||
// Find our starting sample
|
||||
uint32 startSample = 0;
|
||||
for (uint32 i = 0; i < chunk; i++)
|
||||
startSample += getAudioChunkSampleCount(i);
|
||||
|
||||
for (uint32 i = 0; i < sampleCount; i++) {
|
||||
uint32 size = (_parentTrack->sampleSize != 0) ? _parentTrack->sampleSize : _parentTrack->sampleSizes[i + startSample];
|
||||
|
||||
// Now, we read in the data for this data and output it
|
||||
byte *data = (byte *)malloc(size);
|
||||
_decoder->_fd->read(data, size);
|
||||
wStream->write(data, size);
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
AudioStream *audioStream = entry->createAudioStream(new Common::MemoryReadStream(wStream->getData(), wStream->size(), DisposeAfterUse::YES));
|
||||
delete wStream;
|
||||
|
||||
return audioStream;
|
||||
}
|
||||
|
||||
void QuickTimeAudioDecoder::QuickTimeAudioTrack::skipSamples(const Timestamp &length, AudioStream *stream) {
|
||||
int32 sampleCount = length.convertToFramerate(getRate()).totalNumberOfFrames();
|
||||
|
||||
if (sampleCount <= 0)
|
||||
return;
|
||||
|
||||
if (isStereo())
|
||||
sampleCount *= 2;
|
||||
|
||||
int16 *tempBuffer = new int16[sampleCount];
|
||||
uint32 result = stream->readBuffer(tempBuffer, sampleCount);
|
||||
delete[] tempBuffer;
|
||||
|
||||
// If this is the queue, make sure we subtract this number from the
|
||||
// amount queued
|
||||
if (stream == _queue)
|
||||
_samplesQueued -= result / (isStereo() ? 2 : 1);
|
||||
}
|
||||
|
||||
void QuickTimeAudioDecoder::QuickTimeAudioTrack::findEdit(const Timestamp &position) {
|
||||
// Go through the edits look for where we find out we need to be. As long
|
||||
// as the position is >= to the edit's start time, it is considered to be in that
|
||||
// edit. seek() already figured out if we reached the last edit, so we don't need
|
||||
// to handle that case here.
|
||||
for (_curEdit = 0; _curEdit < _parentTrack->editList.size() - 1; _curEdit++) {
|
||||
Timestamp nextEditTime(0, _parentTrack->editList[_curEdit + 1].timeOffset, _decoder->_timeScale);
|
||||
if (position < nextEditTime)
|
||||
break;
|
||||
}
|
||||
|
||||
enterNewEdit(position);
|
||||
}
|
||||
|
||||
void QuickTimeAudioDecoder::QuickTimeAudioTrack::enterNewEdit(const Timestamp &position) {
|
||||
_skipSamples = Timestamp(); // make sure our skip variable doesn't remain around
|
||||
|
||||
// If we're at the end of the edit list, there's nothing else for us to do here
|
||||
if (allDataRead())
|
||||
return;
|
||||
|
||||
// For an empty edit, we may need to adjust the start time
|
||||
if (_parentTrack->editList[_curEdit].mediaTime == -1) {
|
||||
// Just invalidate the current media position (and make sure the scale
|
||||
// is in terms of our rate so it simplifies things later)
|
||||
_curMediaPos = Timestamp(0, 0, getRate());
|
||||
|
||||
// Also handle shortening of the empty edit if needed
|
||||
if (position != Timestamp())
|
||||
_skipSamples = position.convertToFramerate(_decoder->_timeScale) - Timestamp(0, _parentTrack->editList[_curEdit].timeOffset, _decoder->_timeScale);
|
||||
return;
|
||||
}
|
||||
|
||||
// I really hope I never need to implement this :P
|
||||
// But, I'll throw in this error just to make sure I catch anything with this...
|
||||
if (_parentTrack->editList[_curEdit].mediaRate != 1)
|
||||
warning("QuickTimeAudioDecoder: Unhandled QuickTime audio rate change");
|
||||
|
||||
// Reinitialize the codec
|
||||
((AudioSampleDesc *)_parentTrack->sampleDescs[0])->initCodec();
|
||||
_skipAACPrimer = true;
|
||||
|
||||
// First, we need to track down what audio sample we need
|
||||
// Convert our variables from the media time (position) and the edit time (based on position)
|
||||
// and the media time
|
||||
Timestamp curAudioTime = Timestamp(0, _parentTrack->editList[_curEdit].mediaTime, _parentTrack->timeScale)
|
||||
+ position.convertToFramerate(_parentTrack->timeScale)
|
||||
- Timestamp(0, _parentTrack->editList[_curEdit].timeOffset, _decoder->_timeScale).convertToFramerate(_parentTrack->timeScale);
|
||||
|
||||
uint32 sample = curAudioTime.totalNumberOfFrames();
|
||||
uint32 seekSample = sample;
|
||||
|
||||
if (!isOldDemuxing()) {
|
||||
// For MPEG-4 style demuxing, we need to track down the sample based on the time
|
||||
// The old style demuxing doesn't require this because each "sample"'s duration
|
||||
// is just 1
|
||||
uint32 curSample = 0;
|
||||
seekSample = 0;
|
||||
|
||||
for (int32 i = 0; i < _parentTrack->timeToSampleCount; i++) {
|
||||
uint32 sampleCount = _parentTrack->timeToSample[i].count * _parentTrack->timeToSample[i].duration;
|
||||
|
||||
if (sample < curSample + sampleCount) {
|
||||
seekSample += (sample - curSample) / _parentTrack->timeToSample[i].duration;
|
||||
break;
|
||||
}
|
||||
|
||||
seekSample += _parentTrack->timeToSample[i].count;
|
||||
curSample += sampleCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Now to track down what chunk it's in
|
||||
uint32 totalSamples = 0;
|
||||
_curChunk = 0;
|
||||
for (uint32 i = 0; i < _parentTrack->chunkCount; i++, _curChunk++) {
|
||||
uint32 chunkSampleCount = getAudioChunkSampleCount(i);
|
||||
|
||||
if (seekSample < totalSamples + chunkSampleCount)
|
||||
break;
|
||||
|
||||
totalSamples += chunkSampleCount;
|
||||
}
|
||||
|
||||
// Now we get to have fun and convert *back* to an actual time
|
||||
// We don't want the sample count to be modified at this point, though
|
||||
if (!isOldDemuxing())
|
||||
totalSamples = getAACSampleTime(totalSamples);
|
||||
|
||||
_curMediaPos = Timestamp(0, totalSamples, getRate());
|
||||
}
|
||||
|
||||
void QuickTimeAudioDecoder::QuickTimeAudioTrack::queueStream(AudioStream *stream, const Timestamp &length) {
|
||||
// If the samples are stereo and the container is mono, force the samples
|
||||
// to be mono.
|
||||
if (stream->isStereo() && !isStereo())
|
||||
_queue->queueAudioStream(new ForcedMonoAudioStream(stream, DisposeAfterUse::YES), DisposeAfterUse::YES);
|
||||
else
|
||||
_queue->queueAudioStream(stream, DisposeAfterUse::YES);
|
||||
|
||||
_samplesQueued += length.convertToFramerate(getRate()).totalNumberOfFrames();
|
||||
}
|
||||
|
||||
uint32 QuickTimeAudioDecoder::QuickTimeAudioTrack::getAudioChunkSampleCount(uint chunk) const {
|
||||
uint32 sampleCount = 0;
|
||||
|
||||
for (uint32 i = 0; i < _parentTrack->sampleToChunkCount; i++)
|
||||
if (chunk >= _parentTrack->sampleToChunk[i].first)
|
||||
sampleCount = _parentTrack->sampleToChunk[i].count;
|
||||
|
||||
return sampleCount;
|
||||
}
|
||||
|
||||
Timestamp QuickTimeAudioDecoder::QuickTimeAudioTrack::getChunkLength(uint chunk, bool skipAACPrimer) const {
|
||||
uint32 chunkSampleCount = getAudioChunkSampleCount(chunk);
|
||||
|
||||
if (isOldDemuxing())
|
||||
return Timestamp(0, chunkSampleCount, getRate());
|
||||
|
||||
// AAC needs some extra handling, of course
|
||||
return Timestamp(0, getAACSampleTime(chunkSampleCount, skipAACPrimer), getRate());
|
||||
}
|
||||
|
||||
uint32 QuickTimeAudioDecoder::QuickTimeAudioTrack::getAACSampleTime(uint32 totalSampleCount, bool skipAACPrimer) const{
|
||||
uint32 curSample = 0;
|
||||
uint32 time = 0;
|
||||
|
||||
for (int32 i = 0; i < _parentTrack->timeToSampleCount; i++) {
|
||||
uint32 sampleCount = _parentTrack->timeToSample[i].count;
|
||||
|
||||
if (totalSampleCount < curSample + sampleCount) {
|
||||
time += (totalSampleCount - curSample) * _parentTrack->timeToSample[i].duration;
|
||||
break;
|
||||
}
|
||||
|
||||
time += _parentTrack->timeToSample[i].count * _parentTrack->timeToSample[i].duration;
|
||||
curSample += sampleCount;
|
||||
}
|
||||
|
||||
// The first chunk of AAC contains "duration" samples that are used as a primer
|
||||
// We need to subtract that number from the duration for the first chunk. See:
|
||||
// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFAppenG/QTFFAppenG.html#//apple_ref/doc/uid/TP40000939-CH2-SW1
|
||||
// The skipping of both the primer and the remainder are handled by the AAC code,
|
||||
// whereas the timing of the remainder are handled by this time-to-sample chunk
|
||||
// code already.
|
||||
// We have to do this after each time we reinitialize the codec
|
||||
if (skipAACPrimer) {
|
||||
assert(_parentTrack->timeToSampleCount > 0);
|
||||
time -= _parentTrack->timeToSample[0].duration;
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
QuickTimeAudioDecoder::AudioSampleDesc::AudioSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) : Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) {
|
||||
_channels = 0;
|
||||
_sampleRate = 0;
|
||||
_samplesPerFrame = 0;
|
||||
_bytesPerFrame = 0;
|
||||
_bitsPerSample = 0;
|
||||
_codec = nullptr;
|
||||
}
|
||||
|
||||
QuickTimeAudioDecoder::AudioSampleDesc::~AudioSampleDesc() {
|
||||
delete _codec;
|
||||
}
|
||||
|
||||
bool QuickTimeAudioDecoder::AudioSampleDesc::isAudioCodecSupported() const {
|
||||
// Check if the codec is a supported codec
|
||||
if (_codecTag == MKTAG('t', 'w', 'o', 's') || _codecTag == MKTAG('r', 'a', 'w', ' ') || _codecTag == MKTAG('i', 'm', 'a', '4'))
|
||||
return true;
|
||||
|
||||
#ifdef USE_QDM2
|
||||
if (_codecTag == MKTAG('Q', 'D', 'M', '2'))
|
||||
return true;
|
||||
#endif
|
||||
|
||||
if (_codecTag == MKTAG('m', 'p', '4', 'a')) {
|
||||
Common::String audioType;
|
||||
switch (_objectTypeMP4) {
|
||||
case 0x40: // AAC
|
||||
#ifdef USE_FAAD
|
||||
return true;
|
||||
#else
|
||||
audioType = "AAC";
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
audioType = "Unknown";
|
||||
break;
|
||||
}
|
||||
warning("No MPEG-4 audio (%s) support", audioType.c_str());
|
||||
} else {
|
||||
warning("Audio Codec Not Supported: \'%s\'", tag2str(_codecTag));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::SeekableReadStream *stream) const {
|
||||
if (!stream)
|
||||
return nullptr;
|
||||
|
||||
if (_codec) {
|
||||
// If we've loaded a codec, make sure we use first
|
||||
AudioStream *audioStream = _codec->decodeFrame(*stream);
|
||||
delete stream;
|
||||
return audioStream;
|
||||
} else if (_codecTag == MKTAG('t', 'w', 'o', 's') || _codecTag == MKTAG('r', 'a', 'w', ' ')) {
|
||||
// Fortunately, most of the audio used in Myst videos is raw...
|
||||
uint16 flags = 0;
|
||||
if (_codecTag == MKTAG('r', 'a', 'w', ' '))
|
||||
flags |= FLAG_UNSIGNED;
|
||||
if (_channels == 2)
|
||||
flags |= FLAG_STEREO;
|
||||
if (_bitsPerSample == 16)
|
||||
flags |= FLAG_16BITS;
|
||||
return makeRawStream(stream, _sampleRate, flags);
|
||||
} else if (_codecTag == MKTAG('i', 'm', 'a', '4')) {
|
||||
// Riven uses this codec (as do some Myst ME videos)
|
||||
return makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), kADPCMApple, _sampleRate, _channels, 34);
|
||||
} else if (_codecTag == MKTAG('a', 'l', 'a', 'w')) {
|
||||
return makeALawStream(stream, DisposeAfterUse::YES, _sampleRate, _channels);
|
||||
} else if (_codecTag == MKTAG('u', 'l', 'a', 'w')) {
|
||||
return makeMuLawStream(stream, DisposeAfterUse::YES, _sampleRate, _channels);
|
||||
}
|
||||
|
||||
error("Unsupported audio codec");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void QuickTimeAudioDecoder::AudioSampleDesc::initCodec() {
|
||||
delete _codec; _codec = nullptr;
|
||||
|
||||
switch (_codecTag) {
|
||||
case MKTAG('Q', 'D', 'M', '2'):
|
||||
#ifdef USE_QDM2
|
||||
_codec = makeQDM2Decoder(_extraData);
|
||||
#endif
|
||||
break;
|
||||
case MKTAG('m', 'p', '4', 'a'):
|
||||
#ifdef USE_FAAD
|
||||
if (_objectTypeMP4 == 0x40)
|
||||
_codec = makeAACDecoder(_extraData);
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around QuickTimeAudioDecoder that implements the SeekableAudioStream API
|
||||
*/
|
||||
class QuickTimeAudioStream : public SeekableAudioStream, public QuickTimeAudioDecoder {
|
||||
public:
|
||||
QuickTimeAudioStream() {}
|
||||
~QuickTimeAudioStream() {}
|
||||
|
||||
bool openFromFile(const Common::Path &filename) {
|
||||
return QuickTimeAudioDecoder::loadAudioFile(filename) && !_audioTracks.empty();
|
||||
}
|
||||
|
||||
bool openFromStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle) {
|
||||
return QuickTimeAudioDecoder::loadAudioStream(stream, disposeFileHandle) && !_audioTracks.empty();
|
||||
}
|
||||
|
||||
// AudioStream API
|
||||
int readBuffer(int16 *buffer, const int numSamples) override {
|
||||
int samples = 0;
|
||||
|
||||
while (samples < numSamples && !endOfData()) {
|
||||
if (!_audioTracks[0]->hasDataInQueue())
|
||||
_audioTracks[0]->queueAudio();
|
||||
samples += _audioTracks[0]->readBuffer(buffer + samples, numSamples - samples);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
bool isStereo() const override { return _audioTracks[0]->isStereo(); }
|
||||
int getRate() const override { return _audioTracks[0]->getRate(); }
|
||||
bool endOfData() const override { return _audioTracks[0]->endOfData(); }
|
||||
|
||||
// SeekableAudioStream API
|
||||
bool seek(const Timestamp &where) override { return _audioTracks[0]->seek(where); }
|
||||
Timestamp getLength() const override { return _audioTracks[0]->getLength(); }
|
||||
};
|
||||
|
||||
SeekableAudioStream *makeQuickTimeStream(const Common::Path &filename) {
|
||||
QuickTimeAudioStream *audioStream = new QuickTimeAudioStream();
|
||||
|
||||
if (!audioStream->openFromFile(filename)) {
|
||||
delete audioStream;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return audioStream;
|
||||
}
|
||||
|
||||
SeekableAudioStream *makeQuickTimeStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
|
||||
QuickTimeAudioStream *audioStream = new QuickTimeAudioStream();
|
||||
|
||||
if (!audioStream->openFromStream(stream, disposeAfterUse)) {
|
||||
delete audioStream;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return audioStream;
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
68
audio/decoders/quicktime.h
Normal file
68
audio/decoders/quicktime.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - groovie
|
||||
* - mohawk
|
||||
* - pegasus
|
||||
* - sci
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_QUICKTIME_H
|
||||
#define AUDIO_QUICKTIME_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class Path;
|
||||
class SeekableReadStream;
|
||||
class String;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Try to load a QuickTime sound file from the given file name and create a SeekableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param filename the filename of the file from which to read the data
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeQuickTimeStream(const Common::Path &filename);
|
||||
|
||||
/**
|
||||
* Try to load a QuickTime sound file from the given seekable stream and create a SeekableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeQuickTimeStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
142
audio/decoders/quicktime_intern.h
Normal file
142
audio/decoders/quicktime_intern.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal interface to the QuickTime audio decoder.
|
||||
*
|
||||
* This is available so that the QuickTimeVideoDecoder can use
|
||||
* this directly.
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_QUICKTIME_INTERN_H
|
||||
#define AUDIO_QUICKTIME_INTERN_H
|
||||
|
||||
#include "common/formats/quicktime.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
class String;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class Codec;
|
||||
|
||||
class QuickTimeAudioDecoder : public Common::QuickTimeParser {
|
||||
public:
|
||||
QuickTimeAudioDecoder();
|
||||
virtual ~QuickTimeAudioDecoder();
|
||||
|
||||
/**
|
||||
* Load a QuickTime audio file
|
||||
* @param filename the filename to load
|
||||
*/
|
||||
bool loadAudioFile(const Common::Path &filename);
|
||||
|
||||
/**
|
||||
* Load a QuickTime audio file from a SeekableReadStream
|
||||
* @param stream the stream to load
|
||||
*/
|
||||
bool loadAudioStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle);
|
||||
|
||||
protected:
|
||||
class QuickTimeAudioTrack : public SeekableAudioStream {
|
||||
public:
|
||||
QuickTimeAudioTrack(QuickTimeAudioDecoder *decoder, Track *parentTrack);
|
||||
~QuickTimeAudioTrack();
|
||||
|
||||
// AudioStream API
|
||||
int readBuffer(int16 *buffer, const int numSamples);
|
||||
bool isStereo() const { return _queue->isStereo(); }
|
||||
int getRate() const { return _queue->getRate(); }
|
||||
bool endOfData() const;
|
||||
|
||||
// SeekableAudioStream API
|
||||
bool seek(const Timestamp &where);
|
||||
Timestamp getLength() const;
|
||||
|
||||
// Queue *at least* "length" audio
|
||||
// If length is zero, it queues the next logical block of audio whether
|
||||
// that be a whole edit or just one chunk within an edit
|
||||
void queueAudio(const Timestamp &length = Timestamp());
|
||||
Track *getParent() const { return _parentTrack; }
|
||||
void queueRemainingAudio();
|
||||
bool hasDataInQueue() const { return _samplesQueued != 0; }
|
||||
|
||||
private:
|
||||
QuickTimeAudioDecoder *_decoder;
|
||||
Track *_parentTrack;
|
||||
QueuingAudioStream *_queue;
|
||||
uint _curChunk;
|
||||
Timestamp _curMediaPos, _skipSamples;
|
||||
uint32 _curEdit, _samplesQueued;
|
||||
bool _skipAACPrimer;
|
||||
|
||||
QueuingAudioStream *createStream() const;
|
||||
AudioStream *readAudioChunk(uint chunk);
|
||||
bool isOldDemuxing() const;
|
||||
void skipSamples(const Timestamp &length, AudioStream *stream);
|
||||
void findEdit(const Timestamp &position);
|
||||
bool allDataRead() const;
|
||||
void enterNewEdit(const Timestamp &position);
|
||||
void queueStream(AudioStream *stream, const Timestamp &length);
|
||||
uint32 getAudioChunkSampleCount(uint chunk) const;
|
||||
Timestamp getChunkLength(uint chunk, bool skipAACPrimer = false) const;
|
||||
uint32 getAACSampleTime(uint32 totalSampleCount, bool skipAACPrimer = false) const;
|
||||
Timestamp getCurrentTrackTime() const;
|
||||
};
|
||||
|
||||
class AudioSampleDesc : public Common::QuickTimeParser::SampleDesc {
|
||||
public:
|
||||
AudioSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag);
|
||||
~AudioSampleDesc();
|
||||
|
||||
bool isAudioCodecSupported() const;
|
||||
|
||||
AudioStream *createAudioStream(Common::SeekableReadStream *stream) const;
|
||||
void initCodec();
|
||||
|
||||
// TODO: Make private in the long run
|
||||
uint16 _bitsPerSample;
|
||||
uint16 _channels;
|
||||
uint32 _sampleRate;
|
||||
uint32 _samplesPerFrame;
|
||||
uint32 _bytesPerFrame;
|
||||
|
||||
private:
|
||||
Codec *_codec;
|
||||
};
|
||||
|
||||
// Common::QuickTimeParser API
|
||||
virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize);
|
||||
|
||||
void init();
|
||||
|
||||
Common::Array<QuickTimeAudioTrack *> _audioTracks;
|
||||
};
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
245
audio/decoders/raw.cpp
Normal file
245
audio/decoders/raw.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/endian.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- RawStream ---
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
* This is a stream, which allows for playing raw PCM data from a stream.
|
||||
*/
|
||||
template<int bytesPerSample, bool isUnsigned, bool isLE>
|
||||
class RawStream : public SeekableAudioStream {
|
||||
public:
|
||||
RawStream(int rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream)
|
||||
: _rate(rate), _isStereo(stereo), _playtime(0, rate), _stream(stream, disposeStream), _endOfData(false), _buffer(nullptr) {
|
||||
// Setup our buffer for readBuffer
|
||||
_buffer = new byte[kSampleBufferLength * bytesPerSample];
|
||||
assert(_buffer);
|
||||
|
||||
// Calculate the total playtime of the stream
|
||||
_playtime = Timestamp(0, _stream->size() / (_isStereo ? 2 : 1) / bytesPerSample, rate);
|
||||
}
|
||||
|
||||
~RawStream() {
|
||||
delete[] _buffer;
|
||||
}
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
|
||||
bool isStereo() const override { return _isStereo; }
|
||||
bool endOfData() const override { return _endOfData; }
|
||||
|
||||
int getRate() const override { return _rate; }
|
||||
Timestamp getLength() const override { return _playtime; }
|
||||
|
||||
bool seek(const Timestamp &where) override;
|
||||
private:
|
||||
const int _rate; ///< Sample rate of stream
|
||||
const bool _isStereo; ///< Whether this is a stereo stream
|
||||
Timestamp _playtime; ///< Calculated total play time
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream; ///< Stream to read data from
|
||||
bool _endOfData; ///< Whether the stream end has been reached
|
||||
|
||||
byte *_buffer; ///< Buffer used in readBuffer
|
||||
enum {
|
||||
/**
|
||||
* How many samples we can buffer at once.
|
||||
*
|
||||
* TODO: Check whether this size suffices
|
||||
* for systems with slow disk I/O.
|
||||
*/
|
||||
kSampleBufferLength = 2048
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill the temporary sample buffer used in readBuffer.
|
||||
*
|
||||
* @param maxSamples Maximum samples to read.
|
||||
* @return actual count of samples read.
|
||||
*/
|
||||
int fillBuffer(int maxSamples);
|
||||
};
|
||||
|
||||
template<int bytesPerSample, bool isUnsigned, bool isLE>
|
||||
int RawStream<bytesPerSample, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samplesLeft = numSamples;
|
||||
|
||||
while (samplesLeft > 0) {
|
||||
// Try to read up to "samplesLeft" samples.
|
||||
int len = fillBuffer(samplesLeft);
|
||||
|
||||
// In case we were not able to read any samples
|
||||
// we will stop reading here.
|
||||
if (!len)
|
||||
break;
|
||||
|
||||
// Adjust the samples left to read.
|
||||
samplesLeft -= len;
|
||||
|
||||
// Copy the data to the caller's buffer.
|
||||
const byte *src = _buffer;
|
||||
while (len-- > 0) {
|
||||
if (bytesPerSample == 1)
|
||||
*buffer++ = (*src << 8) ^ (isUnsigned ? 0x8000 : 0);
|
||||
else if (bytesPerSample == 2)
|
||||
*buffer++ = ((isLE ? READ_LE_UINT16(src) : READ_BE_UINT16(src)) ^ (isUnsigned ? 0x8000 : 0));
|
||||
else // if (bytesPerSample == 3)
|
||||
*buffer++ = (((int16)((isLE ? READ_LE_UINT24(src) : READ_BE_UINT24(src)) >> 8)) ^ (isUnsigned ? 0x8000 : 0));
|
||||
|
||||
src += bytesPerSample;
|
||||
}
|
||||
}
|
||||
|
||||
return numSamples - samplesLeft;
|
||||
}
|
||||
|
||||
template<int bytesPerSample, bool isUnsigned, bool isLE>
|
||||
int RawStream<bytesPerSample, isUnsigned, isLE>::fillBuffer(int maxSamples) {
|
||||
int bufferedSamples = 0;
|
||||
byte *dst = _buffer;
|
||||
|
||||
// We can only read up to "kSampleBufferLength" samples
|
||||
// so we take this into consideration, when trying to
|
||||
// read up to maxSamples.
|
||||
maxSamples = MIN<int>(kSampleBufferLength, maxSamples);
|
||||
|
||||
// We will only read up to maxSamples
|
||||
while (maxSamples > 0 && !endOfData()) {
|
||||
// Try to read all the sample data and update the
|
||||
// destination pointer.
|
||||
const int bytesRead = _stream->read(dst, maxSamples * bytesPerSample);
|
||||
dst += bytesRead;
|
||||
|
||||
// Calculate how many samples we actually read.
|
||||
const int samplesRead = bytesRead / bytesPerSample;
|
||||
|
||||
// Update all status variables
|
||||
bufferedSamples += samplesRead;
|
||||
maxSamples -= samplesRead;
|
||||
|
||||
// We stop stream playback, when we reached the end of the data stream.
|
||||
// We also stop playback when an error occures.
|
||||
if (_stream->pos() == _stream->size() || _stream->err() || _stream->eos())
|
||||
_endOfData = true;
|
||||
}
|
||||
|
||||
return bufferedSamples;
|
||||
}
|
||||
|
||||
template<int bytesPerSample, bool isUnsigned, bool isLE>
|
||||
bool RawStream<bytesPerSample, isUnsigned, isLE>::seek(const Timestamp &where) {
|
||||
_endOfData = true;
|
||||
|
||||
if (where > _playtime)
|
||||
return false;
|
||||
|
||||
const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames();
|
||||
_stream->seek(seekSample * bytesPerSample, SEEK_SET);
|
||||
|
||||
// In case of an error we will not continue stream playback.
|
||||
if (!_stream->err() && !_stream->eos() && _stream->pos() != _stream->size())
|
||||
_endOfData = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- Raw stream factories ---
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
* The following templated function is a helper to simplify the public makeRawStream function
|
||||
*/
|
||||
template <bool isUnsigned>
|
||||
static FORCEINLINE SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, int rate, bool isStereo, DisposeAfterUse::Flag disposeAfterUse, bool isLE, int bytesPerSample) {
|
||||
switch (bytesPerSample) {
|
||||
case 3:
|
||||
if (isLE) {
|
||||
return new RawStream<3, isUnsigned, true>(rate, isStereo, disposeAfterUse, stream);
|
||||
} else {
|
||||
return new RawStream<3, isUnsigned, false>(rate, isStereo, disposeAfterUse, stream);
|
||||
}
|
||||
case 2:
|
||||
if (isLE) {
|
||||
return new RawStream<2, isUnsigned, true>(rate, isStereo, disposeAfterUse, stream);
|
||||
} else {
|
||||
return new RawStream<2, isUnsigned, false>(rate, isStereo, disposeAfterUse, stream);
|
||||
}
|
||||
default:
|
||||
return new RawStream<1, isUnsigned, false>(rate, isStereo, disposeAfterUse, stream);
|
||||
}
|
||||
}
|
||||
|
||||
SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
|
||||
int rate, byte flags,
|
||||
DisposeAfterUse::Flag disposeAfterUse) {
|
||||
const bool isStereo = (flags & Audio::FLAG_STEREO) != 0;
|
||||
const int bytesPerSample = (flags & Audio::FLAG_24BITS ? 3 : (flags & Audio::FLAG_16BITS ? 2 : 1));
|
||||
const bool isUnsigned = (flags & Audio::FLAG_UNSIGNED) != 0;
|
||||
const bool isLE = (flags & Audio::FLAG_LITTLE_ENDIAN) != 0;
|
||||
|
||||
assert(stream->size() % (bytesPerSample * (isStereo ? 2 : 1)) == 0);
|
||||
|
||||
if (isUnsigned) {
|
||||
return makeRawStream<true>(stream, rate, isStereo, disposeAfterUse, isLE, bytesPerSample);
|
||||
} else {
|
||||
return makeRawStream<false>(stream, rate, isStereo, disposeAfterUse, isLE, bytesPerSample);
|
||||
}
|
||||
}
|
||||
|
||||
SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size,
|
||||
int rate, byte flags,
|
||||
DisposeAfterUse::Flag disposeAfterUse) {
|
||||
return makeRawStream(new Common::MemoryReadStream(buffer, size, disposeAfterUse), rate, flags, DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
class PacketizedRawStream : public StatelessPacketizedAudioStream {
|
||||
public:
|
||||
PacketizedRawStream(int rate, byte flags) :
|
||||
StatelessPacketizedAudioStream(rate, ((flags & FLAG_STEREO) != 0) ? 2 : 1), _flags(flags) {}
|
||||
|
||||
protected:
|
||||
AudioStream *makeStream(Common::SeekableReadStream *data) override;
|
||||
|
||||
private:
|
||||
byte _flags;
|
||||
};
|
||||
|
||||
AudioStream *PacketizedRawStream::makeStream(Common::SeekableReadStream *data) {
|
||||
return makeRawStream(data, getRate(), _flags);
|
||||
}
|
||||
|
||||
PacketizedAudioStream *makePacketizedRawStream(int rate, byte flags) {
|
||||
return new PacketizedRawStream(rate, flags);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
108
audio/decoders/raw.h
Normal file
108
audio/decoders/raw.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_RAW_H
|
||||
#define AUDIO_RAW_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include "common/list.h"
|
||||
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class PacketizedAudioStream;
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Various flags which can be bit-ORed and then passed to
|
||||
* makeRawStream and some other AudioStream factories
|
||||
* to control their behavior.
|
||||
*
|
||||
* Engine authors are advised not to rely on a certain value or
|
||||
* order of these flags (in particular, do not store them verbatim
|
||||
* in savestates).
|
||||
*/
|
||||
enum RawFlags {
|
||||
/** unsigned samples (default: signed) */
|
||||
FLAG_UNSIGNED = 1 << 0,
|
||||
|
||||
/** sound is 16 bits wide (default: 8bit) */
|
||||
FLAG_16BITS = 1 << 1,
|
||||
|
||||
/** sound is 24 bits wide (default: 8bit) */
|
||||
FLAG_24BITS = 1 << 2,
|
||||
|
||||
/** samples are little endian (default: big endian) */
|
||||
FLAG_LITTLE_ENDIAN = 1 << 3,
|
||||
|
||||
/** sound is in stereo (default: mono) */
|
||||
FLAG_STEREO = 1 << 4
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an audio stream, which plays from the given buffer.
|
||||
*
|
||||
* @param buffer Buffer to play from.
|
||||
* @param size Size of the buffer in bytes.
|
||||
* @param rate Rate of the sound data.
|
||||
* @param flags Audio flags combination.
|
||||
* @see RawFlags
|
||||
* @param disposeAfterUse Whether to free the buffer after use (with free!).
|
||||
* @return The new SeekableAudioStream (or 0 on failure).
|
||||
*/
|
||||
SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size,
|
||||
int rate, byte flags,
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
|
||||
|
||||
/**
|
||||
* Creates an audio stream, which plays from the given stream.
|
||||
*
|
||||
* @param stream Stream object to play from.
|
||||
* @param rate Rate of the sound data.
|
||||
* @param flags Audio flags combination.
|
||||
* @see RawFlags
|
||||
* @param disposeAfterUse Whether to delete the stream after use.
|
||||
* @return The new SeekableAudioStream (or 0 on failure).
|
||||
*/
|
||||
SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
|
||||
int rate, byte flags,
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
|
||||
|
||||
/**
|
||||
* Creates a PacketizedAudioStream that will automatically queue
|
||||
* packets as individual AudioStreams like returned by makeRawStream.
|
||||
*
|
||||
* @param rate Rate of the sound data.
|
||||
* @param flags Audio flags combination.
|
||||
* @see RawFlags
|
||||
* @return The new PacketizedAudioStream.
|
||||
*/
|
||||
PacketizedAudioStream *makePacketizedRawStream(int rate, byte flags);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
52
audio/decoders/util.h
Normal file
52
audio/decoders/util.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_DECODERS_UTIL_H
|
||||
#define AUDIO_DECODERS_UTIL_H
|
||||
|
||||
#include "common/types.h"
|
||||
#include "common/util.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Convert one float sample into a int16 sample
|
||||
static inline int16 floatToInt16(float src) {
|
||||
return (int16) CLIP<int>((int) floor(src + 0.5), -32768, 32767);
|
||||
}
|
||||
|
||||
// Convert planar float samples into interleaved int16 samples
|
||||
static inline void floatToInt16Interleave(int16 *dst, const float **src,
|
||||
uint32 length, uint8 channels) {
|
||||
if (channels == 2) {
|
||||
for (uint32 i = 0; i < length; i++) {
|
||||
dst[2 * i ] = floatToInt16(src[0][i]);
|
||||
dst[2 * i + 1] = floatToInt16(src[1][i]);
|
||||
}
|
||||
} else {
|
||||
for (uint8 c = 0; c < channels; c++)
|
||||
for (uint32 i = 0, j = c; i < length; i++, j += channels)
|
||||
dst[j] = floatToInt16(src[c][i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // AUDIO_DECODERS_UTIL_H
|
||||
490
audio/decoders/voc.cpp
Normal file
490
audio/decoders/voc.cpp
Normal file
@@ -0,0 +1,490 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/decoders/voc.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/util.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/list.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/voc.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
bool checkVOCHeader(Common::ReadStream &stream) {
|
||||
VocFileHeader fileHeader;
|
||||
|
||||
if (stream.read(&fileHeader, 8) != 8)
|
||||
return false;
|
||||
|
||||
if (!memcmp(&fileHeader, "VTLK", 4)) {
|
||||
if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader))
|
||||
return false;
|
||||
} else if (!memcmp(&fileHeader, "Creative", 8)) {
|
||||
if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8)
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0)
|
||||
return false;
|
||||
//if (fileHeader.desc[19] != 0x1A)
|
||||
// debug(3, "checkVOCHeader: Partially invalid header");
|
||||
|
||||
int32 offset = FROM_LE_16(fileHeader.datablock_offset);
|
||||
int16 version = FROM_LE_16(fileHeader.version);
|
||||
int16 code = FROM_LE_16(fileHeader.id);
|
||||
|
||||
if (offset != sizeof(VocFileHeader))
|
||||
return false;
|
||||
|
||||
// 0x100 is an invalid VOC version used by German version of DOTT (Disk) and
|
||||
// French version of Simon the Sorcerer 2 (CD)
|
||||
if (version != 0x010A && version != 0x0114 && version != 0x0100)
|
||||
return false;
|
||||
|
||||
if (code != ~version + 0x1234)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VocStream::VocStream(Common::SeekableReadStream *stream, bool isUnsigned, DisposeAfterUse::Flag disposeAfterUse)
|
||||
: _stream(stream), _disposeAfterUse(disposeAfterUse), _isUnsigned(isUnsigned), _rate(0),
|
||||
_length(), _blocks(), _curBlock(_blocks.end()), _blockLeft(0), _buffer() {
|
||||
preProcess();
|
||||
}
|
||||
|
||||
VocStream::~VocStream() {
|
||||
if (_disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete _stream;
|
||||
}
|
||||
|
||||
int VocStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samplesLeft = numSamples;
|
||||
while (samplesLeft > 0) {
|
||||
// Try to read up to "samplesLeft" samples.
|
||||
int len = fillBuffer(samplesLeft);
|
||||
|
||||
// In case we were not able to read any samples
|
||||
// we will stop reading here.
|
||||
if (!len)
|
||||
break;
|
||||
|
||||
// Adjust the samples left to read.
|
||||
samplesLeft -= len;
|
||||
|
||||
// Copy the data to the caller's buffer.
|
||||
const byte *src = _buffer;
|
||||
while (len-- > 0)
|
||||
*buffer++ = (*src++ << 8) ^ (_isUnsigned ? 0x8000 : 0);
|
||||
}
|
||||
|
||||
return numSamples - samplesLeft;
|
||||
}
|
||||
|
||||
void VocStream::updateBlockIfNeeded() {
|
||||
// Have we now finished this block? If so, read the next block
|
||||
if (_blockLeft == 0 && _curBlock != _blocks.end()) {
|
||||
// Find the next sample block
|
||||
while (true) {
|
||||
// Next block
|
||||
++_curBlock;
|
||||
|
||||
// Check whether we reached the end of the stream
|
||||
// yet.
|
||||
if (_curBlock == _blocks.end())
|
||||
return;
|
||||
|
||||
// Skip all none sample blocks for now
|
||||
if (_curBlock->code != 1 && _curBlock->code != 9)
|
||||
continue;
|
||||
|
||||
_stream->seek(_curBlock->sampleBlock.offset, SEEK_SET);
|
||||
|
||||
// In case of an error we will stop
|
||||
// stream playback.
|
||||
if (_stream->err()) {
|
||||
_blockLeft = 0;
|
||||
_curBlock = _blocks.end();
|
||||
} else {
|
||||
_blockLeft = _curBlock->sampleBlock.samples;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int VocStream::fillBuffer(int maxSamples) {
|
||||
int bufferedSamples = 0;
|
||||
byte *dst = _buffer;
|
||||
|
||||
// We can only read up to "kSampleBufferLength" samples
|
||||
// so we take this into consideration, when trying to
|
||||
// read up to maxSamples.
|
||||
maxSamples = MIN<int>(kSampleBufferLength, maxSamples);
|
||||
|
||||
// We will only read up to maxSamples
|
||||
while (maxSamples > 0 && !endOfData()) {
|
||||
// Calculate how many samples we can safely read
|
||||
// from the current block.
|
||||
const int len = MIN<int>(maxSamples, _blockLeft);
|
||||
|
||||
// Try to read all the sample data and update the
|
||||
// destination pointer.
|
||||
const int bytesRead = _stream->read(dst, len);
|
||||
dst += bytesRead;
|
||||
|
||||
// Calculate how many samples we actually read.
|
||||
const int samplesRead = bytesRead;
|
||||
|
||||
// Update all status variables
|
||||
bufferedSamples += samplesRead;
|
||||
maxSamples -= samplesRead;
|
||||
_blockLeft -= samplesRead;
|
||||
|
||||
// In case of an error or end of stream we will stop
|
||||
// stream playback.
|
||||
if (_stream->err() || _stream->eos()) {
|
||||
_blockLeft = 0;
|
||||
_curBlock = _blocks.end();
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance to the next block in case the current
|
||||
// one is already finished.
|
||||
updateBlockIfNeeded();
|
||||
}
|
||||
|
||||
return bufferedSamples;
|
||||
}
|
||||
|
||||
bool VocStream::seek(const Timestamp &where) {
|
||||
// Invalidate stream
|
||||
_blockLeft = 0;
|
||||
_curBlock = _blocks.end();
|
||||
|
||||
if (where > _length)
|
||||
return false;
|
||||
|
||||
// Search for the block containing the requested sample
|
||||
const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames();
|
||||
uint32 curSample = 0;
|
||||
|
||||
for (_curBlock = _blocks.begin(); _curBlock != _blocks.end(); ++_curBlock) {
|
||||
// Skip all none sample blocks for now
|
||||
if (_curBlock->code != 1 && _curBlock->code != 9)
|
||||
continue;
|
||||
|
||||
uint32 nextBlockSample = curSample + _curBlock->sampleBlock.samples;
|
||||
|
||||
if (nextBlockSample > seekSample)
|
||||
break;
|
||||
|
||||
curSample = nextBlockSample;
|
||||
}
|
||||
|
||||
if (_curBlock == _blocks.end()) {
|
||||
return ((seekSample - curSample) == 0);
|
||||
} else {
|
||||
const uint32 offset = seekSample - curSample;
|
||||
|
||||
_stream->seek(_curBlock->sampleBlock.offset + offset, SEEK_SET);
|
||||
|
||||
// In case of an error we will stop
|
||||
// stream playback.
|
||||
if (_stream->err()) {
|
||||
_blockLeft = 0;
|
||||
_curBlock = _blocks.end();
|
||||
} else {
|
||||
_blockLeft = _curBlock->sampleBlock.samples - offset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void VocStream::preProcess() {
|
||||
Block block;
|
||||
|
||||
// Scan through the file and collect all blocks
|
||||
while (true) {
|
||||
block.code = _stream->readByte();
|
||||
block.length = 0;
|
||||
|
||||
// If we hit EOS here we found the end of the VOC file.
|
||||
// According to https://wiki.multimedia.cx/index.php?title=Creative_Voice
|
||||
// there is no need for an "Terminator" block to be present.
|
||||
// In case we hit a "Terminator" block we also break here.
|
||||
if (_stream->eos() || block.code == 0)
|
||||
break;
|
||||
// We will allow invalid block numbers as terminators. This is needed,
|
||||
// since some games ship broken VOC files. The following occasions are
|
||||
// known:
|
||||
// - 128 is used as terminator in Simon 1 Amiga CD32
|
||||
// - Full Throttle contains a VOC file with an incorrect block length
|
||||
// resulting in a sample (127) to be read as block code.
|
||||
if (block.code > 9) {
|
||||
warning("VocStream::preProcess: Caught %d as terminator", block.code);
|
||||
break;
|
||||
}
|
||||
|
||||
block.length = _stream->readByte();
|
||||
block.length |= _stream->readByte() << 8;
|
||||
block.length |= _stream->readByte() << 16;
|
||||
|
||||
// Premature end of stream => error!
|
||||
if (_stream->eos() || _stream->err()) {
|
||||
warning("VocStream::preProcess: Reading failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 skip = 0;
|
||||
|
||||
switch (block.code) {
|
||||
// Sound data
|
||||
case 1:
|
||||
// Sound data (New format)
|
||||
case 9:
|
||||
if (block.code == 1) {
|
||||
if (block.length < 2) {
|
||||
warning("Invalid sound data block length %d in VOC file", block.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read header data
|
||||
int freqDiv = _stream->readByte();
|
||||
// Prevent division through 0
|
||||
if (freqDiv == 256) {
|
||||
warning("Invalid frequency divisor 256 in VOC file");
|
||||
return;
|
||||
}
|
||||
block.sampleBlock.rate = getSampleRateFromVOCRate(freqDiv);
|
||||
|
||||
int codec = _stream->readByte();
|
||||
// We only support 8bit PCM
|
||||
if (codec != 0) {
|
||||
warning("Unhandled codec %d in VOC file", codec);
|
||||
return;
|
||||
}
|
||||
|
||||
block.sampleBlock.samples = skip = block.length - 2;
|
||||
block.sampleBlock.offset = _stream->pos();
|
||||
|
||||
// Check the last block if there is any
|
||||
if (_blocks.size() > 0) {
|
||||
BlockList::iterator lastBlock = _blocks.end();
|
||||
--lastBlock;
|
||||
// When we have found a block 8 as predecessor
|
||||
// we need to use its settings
|
||||
if (lastBlock->code == 8) {
|
||||
block.sampleBlock.rate = lastBlock->sampleBlock.rate;
|
||||
// Remove the block since we don't need it anymore
|
||||
_blocks.erase(lastBlock);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (block.length < 12) {
|
||||
warning("Invalid sound data (wew format) block length %d in VOC file", block.length);
|
||||
return;
|
||||
}
|
||||
|
||||
block.sampleBlock.rate = _stream->readUint32LE();
|
||||
int bitsPerSample = _stream->readByte();
|
||||
// We only support 8bit PCM
|
||||
if (bitsPerSample != 8) {
|
||||
warning("Unhandled bits per sample %d in VOC file", bitsPerSample);
|
||||
return;
|
||||
}
|
||||
int channels = _stream->readByte();
|
||||
// We only support mono
|
||||
if (channels != 1) {
|
||||
warning("Unhandled channel count %d in VOC file", channels);
|
||||
return;
|
||||
}
|
||||
int codec = _stream->readUint16LE();
|
||||
// We only support 8bit PCM
|
||||
if (codec != 0) {
|
||||
warning("Unhandled codec %d in VOC file", codec);
|
||||
return;
|
||||
}
|
||||
/*uint32 reserved = */_stream->readUint32LE();
|
||||
block.sampleBlock.offset = _stream->pos();
|
||||
block.sampleBlock.samples = skip = block.length - 12;
|
||||
}
|
||||
|
||||
// Check whether we found a new highest rate
|
||||
if (_rate < block.sampleBlock.rate)
|
||||
_rate = block.sampleBlock.rate;
|
||||
break;
|
||||
|
||||
// Silence
|
||||
case 3: {
|
||||
if (block.length != 3) {
|
||||
warning("Invalid silence block length %d in VOC file", block.length);
|
||||
return;
|
||||
}
|
||||
|
||||
block.sampleBlock.offset = 0;
|
||||
|
||||
block.sampleBlock.samples = _stream->readUint16LE() + 1;
|
||||
int freqDiv = _stream->readByte();
|
||||
// Prevent division through 0
|
||||
if (freqDiv == 256) {
|
||||
warning("Invalid frequency divisor 256 in VOC file");
|
||||
return;
|
||||
}
|
||||
block.sampleBlock.rate = getSampleRateFromVOCRate(freqDiv);
|
||||
} break;
|
||||
|
||||
// Repeat start
|
||||
case 6:
|
||||
if (block.length != 2) {
|
||||
warning("Invalid repeat start block length %d in VOC file", block.length);
|
||||
return;
|
||||
}
|
||||
|
||||
block.loopBlock.count = _stream->readUint16LE() + 1;
|
||||
break;
|
||||
|
||||
// Repeat end
|
||||
case 7:
|
||||
break;
|
||||
|
||||
// Extra info
|
||||
case 8: {
|
||||
if (block.length != 4)
|
||||
return;
|
||||
|
||||
int freqDiv = _stream->readUint16LE();
|
||||
// Prevent division through 0
|
||||
if (freqDiv == 65536) {
|
||||
warning("Invalid frequency divisor 65536 in VOC file");
|
||||
return;
|
||||
}
|
||||
|
||||
int codec = _stream->readByte();
|
||||
// We only support RAW 8bit PCM.
|
||||
if (codec != 0) {
|
||||
warning("Unhandled codec %d in VOC file", codec);
|
||||
return;
|
||||
}
|
||||
|
||||
int channels = _stream->readByte() + 1;
|
||||
// We only support mono sound right now
|
||||
if (channels != 1) {
|
||||
warning("Unhandled channel count %d in VOC file", channels);
|
||||
return;
|
||||
}
|
||||
|
||||
block.sampleBlock.offset = 0;
|
||||
block.sampleBlock.samples = 0;
|
||||
block.sampleBlock.rate = 256000000L / (65536L - freqDiv);
|
||||
} break;
|
||||
|
||||
default:
|
||||
warning("Unhandled code %d in VOC file (len %d)", block.code, block.length);
|
||||
// Skip the whole block and try to use the next one.
|
||||
skip = block.length;
|
||||
}
|
||||
|
||||
// Premature end of stream => error!
|
||||
if (_stream->eos() || _stream->err()) {
|
||||
warning("VocStream::preProcess: Reading failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip the rest of the block
|
||||
if (skip)
|
||||
_stream->skip(skip);
|
||||
|
||||
_blocks.push_back(block);
|
||||
}
|
||||
|
||||
// Since we determined the sample rate we need for playback now, we will
|
||||
// initialize the play length.
|
||||
_length = Timestamp(0, _rate);
|
||||
|
||||
// Calculate the total play time and do some more sanity checks
|
||||
for (const auto &curBlock : _blocks) {
|
||||
// Check whether we found a block 8 which survived, this is not
|
||||
// allowed to happen!
|
||||
if (curBlock.code == 8) {
|
||||
warning("VOC file contains unused block 8");
|
||||
return;
|
||||
}
|
||||
|
||||
// For now only use blocks with actual samples
|
||||
if (curBlock.code != 1 && curBlock.code != 9)
|
||||
continue;
|
||||
|
||||
// Check the sample rate
|
||||
if (curBlock.sampleBlock.rate != _rate) {
|
||||
warning("VOC file contains chunks with different sample rates (%d != %d)", _rate, curBlock.sampleBlock.rate);
|
||||
return;
|
||||
}
|
||||
|
||||
_length = _length.addFrames(curBlock.sampleBlock.samples);
|
||||
}
|
||||
|
||||
// Set the current block to the first block in the stream
|
||||
rewind();
|
||||
}
|
||||
|
||||
int getSampleRateFromVOCRate(int vocSR) {
|
||||
if (vocSR == 0xa5 || vocSR == 0xa6) {
|
||||
return 11025;
|
||||
} else if (vocSR == 0xd2 || vocSR == 0xd3) {
|
||||
return 22050;
|
||||
} else {
|
||||
int sr = 1000000L / (256L - vocSR);
|
||||
// inexact sampling rates occur e.g. in the kitchen in Monkey Island,
|
||||
// very easy to reach right from the start of the game.
|
||||
//warning("inexact sample rate used: %i (0x%x)", sr, vocSR);
|
||||
return sr;
|
||||
}
|
||||
}
|
||||
|
||||
SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) {
|
||||
if (!checkVOCHeader(*stream)) {
|
||||
if (disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SeekableAudioStream *audioStream = new VocStream(stream, (flags & Audio::FLAG_UNSIGNED) != 0, disposeAfterUse);
|
||||
|
||||
if (audioStream->endOfData()) {
|
||||
delete audioStream;
|
||||
return nullptr;
|
||||
} else {
|
||||
return audioStream;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
172
audio/decoders/voc.h
Normal file
172
audio/decoders/voc.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - agos
|
||||
* - chewy (subclass)
|
||||
* - kyra
|
||||
* - saga
|
||||
* - scumm
|
||||
* - touche
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_VOC_H
|
||||
#define AUDIO_VOC_H
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
#include "common/list.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class ReadStream;
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class AudioStream;
|
||||
class SeekableAudioStream;
|
||||
|
||||
|
||||
#include "common/pack-start.h" // START STRUCT PACKING
|
||||
|
||||
struct VocFileHeader {
|
||||
uint8 desc[20];
|
||||
uint16 datablock_offset;
|
||||
uint16 version;
|
||||
uint16 id;
|
||||
} PACKED_STRUCT;
|
||||
|
||||
struct VocBlockHeader {
|
||||
uint8 blocktype;
|
||||
uint8 size[3];
|
||||
uint8 sr;
|
||||
uint8 pack;
|
||||
} PACKED_STRUCT;
|
||||
|
||||
#include "common/pack-end.h" // END STRUCT PACKING
|
||||
|
||||
class VocStream : public SeekableAudioStream {
|
||||
public:
|
||||
VocStream(Common::SeekableReadStream *stream, bool isUnsigned, DisposeAfterUse::Flag disposeAfterUse);
|
||||
virtual ~VocStream();
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
|
||||
bool isStereo() const override { return false; }
|
||||
|
||||
int getRate() const override { return _rate; }
|
||||
|
||||
bool endOfData() const override { return (_curBlock == _blocks.end()) && (_blockLeft == 0); }
|
||||
|
||||
bool seek(const Timestamp &where) override;
|
||||
|
||||
Timestamp getLength() const override { return _length; }
|
||||
|
||||
protected:
|
||||
void preProcess();
|
||||
|
||||
Common::SeekableReadStream *const _stream;
|
||||
const DisposeAfterUse::Flag _disposeAfterUse;
|
||||
|
||||
const bool _isUnsigned;
|
||||
|
||||
int _rate;
|
||||
Timestamp _length;
|
||||
|
||||
struct Block {
|
||||
uint8 code;
|
||||
uint32 length;
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint32 offset;
|
||||
int rate;
|
||||
int samples;
|
||||
} sampleBlock;
|
||||
|
||||
struct {
|
||||
int count;
|
||||
} loopBlock;
|
||||
};
|
||||
};
|
||||
|
||||
typedef Common::List<Block> BlockList;
|
||||
BlockList _blocks;
|
||||
|
||||
BlockList::const_iterator _curBlock;
|
||||
uint32 _blockLeft;
|
||||
|
||||
/**
|
||||
* Advance one block in the stream in case
|
||||
* the current one is empty.
|
||||
*/
|
||||
void updateBlockIfNeeded();
|
||||
|
||||
// Do some internal buffering for systems with really slow slow disk i/o
|
||||
enum {
|
||||
/**
|
||||
* How many samples we can buffer at once.
|
||||
*
|
||||
* TODO: Check whether this size suffices
|
||||
* for systems with slow disk I/O.
|
||||
*/
|
||||
kSampleBufferLength = 2048
|
||||
};
|
||||
byte _buffer[kSampleBufferLength];
|
||||
|
||||
/**
|
||||
* Fill the temporary sample buffer used in readBuffer.
|
||||
*
|
||||
* @param maxSamples Maximum samples to read.
|
||||
* @return actual count of samples read.
|
||||
*/
|
||||
int fillBuffer(int maxSamples);
|
||||
};
|
||||
|
||||
/**
|
||||
* Take a sample rate parameter as it occurs in a VOC sound header, and
|
||||
* return the corresponding sample frequency.
|
||||
*
|
||||
* This method has special cases for the standard rates of 11025 and 22050 kHz,
|
||||
* which due to limitations of the format, cannot be encoded exactly in a VOC
|
||||
* file. As a consequence, many game files have sound data sampled with those
|
||||
* rates, but the VOC marks them incorrectly as 11111 or 22222 kHz. This code
|
||||
* works around that and "unrounds" the sampling rates.
|
||||
*/
|
||||
extern int getSampleRateFromVOCRate(int vocSR);
|
||||
|
||||
/**
|
||||
* Try to load a VOC from the given seekable stream and create an AudioStream
|
||||
* from that data. Currently this function only supports uncompressed raw PCM
|
||||
* data.
|
||||
*
|
||||
* This does not use any of the looping features of VOC files!
|
||||
*/
|
||||
SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::NO);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
252
audio/decoders/vorbis.cpp
Normal file
252
audio/decoders/vorbis.cpp
Normal file
@@ -0,0 +1,252 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Disable symbol overrides for FILE and fseek as those are used in the
|
||||
// Vorbis headers.
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_fseek
|
||||
|
||||
#include "audio/decoders/vorbis.h"
|
||||
|
||||
#ifdef USE_VORBIS
|
||||
|
||||
#include "common/ptr.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
#ifdef USE_TREMOR
|
||||
#include <tremor/ivorbisfile.h>
|
||||
#else
|
||||
#define OV_EXCLUDE_STATIC_CALLBACKS
|
||||
#include <vorbis/vorbisfile.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// These are wrapper functions to allow using a SeekableReadStream object to
|
||||
// provide data to the OggVorbis_File object.
|
||||
|
||||
static size_t read_stream_wrap(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
|
||||
|
||||
uint32 result = stream->read(ptr, size * nmemb);
|
||||
|
||||
return result / size;
|
||||
}
|
||||
|
||||
static int seek_stream_wrap(void *datasource, ogg_int64_t offset, int whence) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
|
||||
stream->seek((int32)offset, whence);
|
||||
return stream->pos();
|
||||
}
|
||||
|
||||
static int close_stream_wrap(void *datasource) {
|
||||
// Do nothing -- we leave it up to the VorbisStream to free memory as appropriate.
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long tell_stream_wrap(void *datasource) {
|
||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
|
||||
return stream->pos();
|
||||
}
|
||||
|
||||
static const ov_callbacks g_stream_wrap = {
|
||||
read_stream_wrap, seek_stream_wrap, close_stream_wrap, tell_stream_wrap
|
||||
};
|
||||
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- Ogg Vorbis stream ---
|
||||
#pragma mark -
|
||||
|
||||
|
||||
class VorbisStream : public SeekableAudioStream {
|
||||
protected:
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _inStream;
|
||||
|
||||
bool _isStereo;
|
||||
int _rate;
|
||||
|
||||
Timestamp _length;
|
||||
|
||||
OggVorbis_File _ovFile;
|
||||
|
||||
int16 _buffer[4096];
|
||||
const int16 *_bufferEnd;
|
||||
const int16 *_pos;
|
||||
|
||||
public:
|
||||
// startTime / duration are in milliseconds
|
||||
VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose);
|
||||
~VorbisStream();
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
|
||||
bool endOfData() const override { return _pos >= _bufferEnd; }
|
||||
bool isStereo() const override { return _isStereo; }
|
||||
int getRate() const override { return _rate; }
|
||||
|
||||
bool seek(const Timestamp &where) override;
|
||||
Timestamp getLength() const override { return _length; }
|
||||
protected:
|
||||
bool refill();
|
||||
};
|
||||
|
||||
VorbisStream::VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
|
||||
_inStream(inStream, dispose),
|
||||
_length(0, 1000),
|
||||
_bufferEnd(ARRAYEND(_buffer)) {
|
||||
|
||||
int res = ov_open_callbacks(inStream, &_ovFile, nullptr, 0, g_stream_wrap);
|
||||
if (res < 0) {
|
||||
warning("Could not create Vorbis stream (%d)", res);
|
||||
_pos = _bufferEnd;
|
||||
return;
|
||||
}
|
||||
|
||||
// Read in initial data
|
||||
if (!refill())
|
||||
return;
|
||||
|
||||
// Setup some header information
|
||||
_isStereo = ov_info(&_ovFile, -1)->channels >= 2;
|
||||
_rate = ov_info(&_ovFile, -1)->rate;
|
||||
|
||||
#ifdef USE_TREMOR
|
||||
_length = Timestamp(ov_time_total(&_ovFile, -1), getRate());
|
||||
#else
|
||||
_length = Timestamp(uint32(ov_time_total(&_ovFile, -1) * 1000.0), getRate());
|
||||
#endif
|
||||
}
|
||||
|
||||
VorbisStream::~VorbisStream() {
|
||||
ov_clear(&_ovFile);
|
||||
}
|
||||
|
||||
int VorbisStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samples = 0;
|
||||
while (samples < numSamples && _pos < _bufferEnd) {
|
||||
const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
|
||||
memcpy(buffer, _pos, len * 2);
|
||||
buffer += len;
|
||||
_pos += len;
|
||||
samples += len;
|
||||
if (_pos >= _bufferEnd) {
|
||||
if (!refill())
|
||||
break;
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
bool VorbisStream::seek(const Timestamp &where) {
|
||||
// Vorbisfile uses the sample pair number, thus we always use "false" for the isStereo parameter
|
||||
// of the convertTimeToStreamPos helper.
|
||||
int res = ov_pcm_seek(&_ovFile, convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames());
|
||||
if (res) {
|
||||
warning("Error seeking in Vorbis stream (%d)", res);
|
||||
_pos = _bufferEnd;
|
||||
return false;
|
||||
}
|
||||
|
||||
return refill();
|
||||
}
|
||||
|
||||
bool VorbisStream::refill() {
|
||||
// Read the samples
|
||||
uint len_left = sizeof(_buffer);
|
||||
char *read_pos = (char *)_buffer;
|
||||
|
||||
while (len_left > 0) {
|
||||
long result;
|
||||
|
||||
#ifdef USE_TREMOR
|
||||
// Tremor ov_read() always returns data as signed 16 bit interleaved PCM
|
||||
// in host byte order. As such, it does not take arguments to request
|
||||
// specific signedness, byte order or bit depth as in Vorbisfile.
|
||||
result = ov_read(&_ovFile, read_pos, len_left,
|
||||
NULL);
|
||||
#else
|
||||
#ifdef SCUMM_BIG_ENDIAN
|
||||
result = ov_read(&_ovFile, read_pos, len_left,
|
||||
1,
|
||||
2, // 16 bit
|
||||
1, // signed
|
||||
NULL);
|
||||
#else
|
||||
result = ov_read(&_ovFile, read_pos, len_left,
|
||||
0,
|
||||
2, // 16 bit
|
||||
1, // signed
|
||||
nullptr);
|
||||
#endif
|
||||
#endif
|
||||
if (result == OV_HOLE) {
|
||||
// Possibly recoverable, just warn about it
|
||||
warning("Corrupted data in Vorbis file");
|
||||
} else if (result == 0) {
|
||||
//warning("End of file while reading from Vorbis file");
|
||||
//_pos = _bufferEnd;
|
||||
//return false;
|
||||
break;
|
||||
} else if (result < 0) {
|
||||
warning("Error reading from Vorbis stream (%d)", int(result));
|
||||
_pos = _bufferEnd;
|
||||
// Don't delete it yet, that causes problems in
|
||||
// the CD player emulation code.
|
||||
return false;
|
||||
} else {
|
||||
len_left -= result;
|
||||
read_pos += result;
|
||||
}
|
||||
}
|
||||
|
||||
_pos = _buffer;
|
||||
_bufferEnd = (int16 *)read_pos;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark --- Ogg Vorbis factory functions ---
|
||||
#pragma mark -
|
||||
|
||||
SeekableAudioStream *makeVorbisStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse) {
|
||||
SeekableAudioStream *s = new VorbisStream(stream, disposeAfterUse);
|
||||
if (s && s->endOfData()) {
|
||||
delete s;
|
||||
return nullptr;
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_VORBIS
|
||||
73
audio/decoders/vorbis.h
Normal file
73
audio/decoders/vorbis.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - agos
|
||||
* - draci
|
||||
* - kyra
|
||||
* - qdengine
|
||||
* - queen
|
||||
* - saga
|
||||
* - sci
|
||||
* - scumm
|
||||
* - sword1
|
||||
* - sword2
|
||||
* - sword25
|
||||
* - touche
|
||||
* - tucker
|
||||
* - vcruise
|
||||
* - wintermute
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_VORBIS_H
|
||||
#define AUDIO_VORBIS_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#ifdef USE_VORBIS
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Create a new SeekableAudioStream from the Ogg Vorbis data in the given stream.
|
||||
* Allows for seeking (which is why we require a SeekableReadStream).
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the Ogg Vorbis data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeVorbisStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // #ifdef USE_VORBIS
|
||||
#endif // #ifndef AUDIO_VORBIS_H
|
||||
280
audio/decoders/wave.cpp
Normal file
280
audio/decoders/wave.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/substream.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/wave_types.h"
|
||||
#include "audio/decoders/wave.h"
|
||||
#include "audio/decoders/adpcm.h"
|
||||
#include "audio/decoders/mp3.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/g711.h"
|
||||
|
||||
#define EXT_CHUNKS 8
|
||||
|
||||
namespace Audio {
|
||||
|
||||
bool loadWAVFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags, uint16 *wavType, int *blockAlign_, int *samplesPerBlock_) {
|
||||
const int32 initialPos = stream.pos();
|
||||
byte buf[4+1];
|
||||
|
||||
buf[4] = 0;
|
||||
|
||||
const char *extensionChunks[EXT_CHUNKS] = {
|
||||
"JUNK",
|
||||
"bext",
|
||||
"iXML",
|
||||
"qlty",
|
||||
"mext",
|
||||
"levl",
|
||||
"link",
|
||||
"axml"
|
||||
};
|
||||
|
||||
stream.read(buf, 4);
|
||||
if (memcmp(buf, "RIFF", 4) != 0) {
|
||||
warning("getWavInfo: No 'RIFF' header");
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 wavLength = stream.readUint32LE();
|
||||
|
||||
stream.read(buf, 4);
|
||||
if (memcmp(buf, "WAVE", 4) != 0) {
|
||||
warning("getWavInfo: No 'WAVE' header");
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.read(buf, 4);
|
||||
if (memcmp(buf, "fact", 4) == 0) {
|
||||
// Initial fact chunk, so skip over it
|
||||
uint32 factLen = stream.readUint32LE();
|
||||
stream.skip(factLen);
|
||||
stream.read(buf, 4);
|
||||
}
|
||||
|
||||
while (1) { // skip junk/bext... chunks
|
||||
int i;
|
||||
for (i = 0; (i < EXT_CHUNKS) && (memcmp(buf, extensionChunks[i], 4) != 0); i++)
|
||||
;
|
||||
if (i != EXT_CHUNKS) { // found a known chunk
|
||||
uint32 chunkSize = stream.readUint32LE();
|
||||
// skip junk/ext chunk (add 1 byte if odd)
|
||||
stream.skip(chunkSize + (chunkSize % 2));
|
||||
stream.read(buf, 4);
|
||||
debug(0, "Skipped %s chunk in wav file!", extensionChunks[i]);
|
||||
} else // skipped all chunks, or found something unexpected
|
||||
break;
|
||||
}
|
||||
|
||||
if (memcmp(buf, "fmt ", 4) != 0) {
|
||||
warning("getWavInfo: No 'fmt' header! Found %s", buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 fmtLength = stream.readUint32LE();
|
||||
if (fmtLength < 16) {
|
||||
// A valid fmt chunk always contains at least 16 bytes
|
||||
warning("getWavInfo: 'fmt' header is too short");
|
||||
return false;
|
||||
}
|
||||
uint32 fmtRemaining = fmtLength;
|
||||
|
||||
// Next comes the "type" field of the fmt header. Some typical
|
||||
// values for it:
|
||||
// 1 -> uncompressed PCM
|
||||
// 17 -> IMA ADPCM compressed WAVE
|
||||
// See <https://mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html>
|
||||
// for a more complete list of common WAVE compression formats...
|
||||
uint16 type = stream.readUint16LE(); // == 1 for PCM data
|
||||
uint16 numChannels = stream.readUint16LE(); // 1 for mono, 2 for stereo
|
||||
uint32 samplesPerSec = stream.readUint32LE(); // in Hz
|
||||
uint32 avgBytesPerSec = stream.readUint32LE(); // == SampleRate * NumChannels * BitsPerSample/8
|
||||
|
||||
uint16 blockAlign = stream.readUint16LE(); // == NumChannels * BitsPerSample/8
|
||||
uint16 bitsPerSample = stream.readUint16LE(); // 8, 16 ...
|
||||
// 8 bit data is unsigned, 16 bit data signed
|
||||
fmtRemaining -= 16;
|
||||
|
||||
uint16 samplesPerBlock = 1;
|
||||
if (type == kWaveFormatMSADPCM) {
|
||||
// TODO: There is a samplesPerBlock in this header. It should be parsed and the below warning removed.
|
||||
// (NB: The FMT header for MSADPCM has different information from the MSIMAADPCM)
|
||||
warning("getWavInfo: 'fmt' header not parsed in entirety for MSADPCM");
|
||||
} else if (type == kWaveFormatMSIMAADPCM) {
|
||||
if (fmtRemaining != 4) {
|
||||
// A valid IMA ADPCM fmt chunk is always 20 bytes long
|
||||
warning("getWavInfo: 'fmt' header is wrong length for IMA ADPCM");
|
||||
return false;
|
||||
}
|
||||
stream.readUint16LE(); // cbSize
|
||||
samplesPerBlock = stream.readUint16LE();
|
||||
fmtRemaining -= 4;
|
||||
}
|
||||
|
||||
if (wavType != nullptr)
|
||||
*wavType = type;
|
||||
|
||||
if (blockAlign_ != nullptr)
|
||||
*blockAlign_ = blockAlign;
|
||||
|
||||
if (samplesPerBlock_ != nullptr)
|
||||
*samplesPerBlock_ = samplesPerBlock;
|
||||
#if 0
|
||||
debug("WAVE information:");
|
||||
debug(" total size: %d", wavLength);
|
||||
debug(" fmt size: %d", fmtLength);
|
||||
debug(" type: %d", type);
|
||||
debug(" numChannels: %d", numChannels);
|
||||
debug(" samplesPerSec: %d", samplesPerSec);
|
||||
debug(" avgBytesPerSec: %d", avgBytesPerSec);
|
||||
debug(" blockAlign: %d", blockAlign);
|
||||
debug(" bitsPerSample: %d", bitsPerSample);
|
||||
#endif
|
||||
|
||||
switch (type) {
|
||||
case kWaveFormatPCM:
|
||||
case kWaveFormatMSADPCM:
|
||||
case kWaveFormatALawPCM:
|
||||
case kWaveFormatMuLawPCM:
|
||||
case kWaveFormatMSIMAADPCM:
|
||||
#ifdef USE_MAD
|
||||
case kWaveFormatMP3:
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
warning("getWavInfo: unsupported format (type %d)", type);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == kWaveFormatMP3) {
|
||||
bitsPerSample = 8;
|
||||
} else if (type != kWaveFormatMSADPCM && type != kWaveFormatMSIMAADPCM) {
|
||||
if (blockAlign != numChannels * bitsPerSample / 8) {
|
||||
debug(0, "getWavInfo: blockAlign is invalid");
|
||||
}
|
||||
|
||||
if (avgBytesPerSec != samplesPerSec * blockAlign) {
|
||||
debug(0, "getWavInfo: avgBytesPerSec is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the return values.
|
||||
rate = samplesPerSec;
|
||||
|
||||
flags = 0;
|
||||
if (bitsPerSample == 8) // 8 bit data is unsigned
|
||||
flags |= Audio::FLAG_UNSIGNED;
|
||||
else if (bitsPerSample == 16) // 16 bit data is signed little endian
|
||||
flags |= (Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
|
||||
else if (bitsPerSample == 24) // 24 bit data is signed little endian
|
||||
flags |= (Audio::FLAG_24BITS | Audio::FLAG_LITTLE_ENDIAN);
|
||||
else if (bitsPerSample == 4 && (type == kWaveFormatMSADPCM || type == kWaveFormatMSIMAADPCM))
|
||||
flags |= Audio::FLAG_16BITS;
|
||||
else {
|
||||
warning("getWavInfo: unsupported bitsPerSample %d", bitsPerSample);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (numChannels == 2)
|
||||
flags |= Audio::FLAG_STEREO;
|
||||
else if (numChannels != 1) {
|
||||
warning("getWavInfo: unsupported number of channels %d", numChannels);
|
||||
return false;
|
||||
}
|
||||
|
||||
// It's almost certainly a WAV file, but we still need to find its
|
||||
// 'data' chunk.
|
||||
|
||||
// Skip over the rest of the fmt chunk.
|
||||
int offset = fmtRemaining;
|
||||
|
||||
do {
|
||||
stream.seek(offset, SEEK_CUR);
|
||||
if (stream.pos() >= initialPos + wavLength + 8) {
|
||||
warning("getWavInfo: Can't find 'data' chunk");
|
||||
return false;
|
||||
}
|
||||
stream.read(buf, 4);
|
||||
offset = stream.readUint32LE();
|
||||
|
||||
#if 0
|
||||
debug(" found a '%s' tag of size %d", buf, offset);
|
||||
#endif
|
||||
} while (memcmp(buf, "data", 4) != 0);
|
||||
|
||||
// Stream now points at 'offset' bytes of sample data...
|
||||
size = offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SeekableAudioStream *makeWAVStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
|
||||
int size, rate;
|
||||
byte flags;
|
||||
uint16 type;
|
||||
int blockAlign;
|
||||
|
||||
if (!loadWAVFromStream(*stream, size, rate, flags, &type, &blockAlign)) {
|
||||
if (disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
return nullptr;
|
||||
}
|
||||
int channels = (flags & Audio::FLAG_STEREO) ? 2 : 1;
|
||||
int bytesPerSample = (flags & Audio::FLAG_24BITS) ? 3 : ((flags & Audio::FLAG_16BITS) ? 2 : 1);
|
||||
|
||||
// Raw PCM, make sure the last packet is complete
|
||||
if (type == kWaveFormatPCM) {
|
||||
uint sampleSize = bytesPerSample * channels;
|
||||
if (size % sampleSize != 0) {
|
||||
warning("makeWAVStream: Trying to play a WAVE file with an incomplete PCM packet");
|
||||
size &= ~(sampleSize - 1);
|
||||
}
|
||||
}
|
||||
Common::SeekableReadStream *dataStream = new Common::SeekableSubReadStream(stream, stream->pos(), stream->pos() + size, disposeAfterUse);
|
||||
|
||||
switch (type) {
|
||||
case kWaveFormatMSIMAADPCM:
|
||||
return makeADPCMStream(dataStream, DisposeAfterUse::YES, 0, Audio::kADPCMMSIma, rate, channels, blockAlign);
|
||||
case kWaveFormatMSADPCM:
|
||||
return makeADPCMStream(dataStream, DisposeAfterUse::YES, 0, Audio::kADPCMMS, rate, channels, blockAlign);
|
||||
#ifdef USE_MAD
|
||||
case kWaveFormatMP3:
|
||||
return makeMP3Stream(dataStream, DisposeAfterUse::YES);
|
||||
#endif
|
||||
case kWaveFormatALawPCM:
|
||||
return makeALawStream(dataStream, DisposeAfterUse::YES, rate, channels);
|
||||
case kWaveFormatMuLawPCM:
|
||||
return makeMuLawStream(dataStream, DisposeAfterUse::YES, rate, channels);
|
||||
case kWaveFormatPCM:
|
||||
return makeRawStream(dataStream, rate, flags);
|
||||
}
|
||||
|
||||
// If the format is unsupported, we already returned earlier, but just in case
|
||||
delete dataStream;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
98
audio/decoders/wave.h
Normal file
98
audio/decoders/wave.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - access
|
||||
* - agos
|
||||
* - buried
|
||||
* - cge
|
||||
* - cge2
|
||||
* - fullpipe
|
||||
* - glk
|
||||
* - gob
|
||||
* - hopkins
|
||||
* - mohawk
|
||||
* - prince
|
||||
* - qdengine
|
||||
* - saga
|
||||
* - sci
|
||||
* - scumm
|
||||
* - sherlock
|
||||
* - sword1
|
||||
* - sword2
|
||||
* - titanic
|
||||
* - tony
|
||||
* - trecision
|
||||
* - tucker
|
||||
* - wintermute
|
||||
* - zvision
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_WAVE_H
|
||||
#define AUDIO_WAVE_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Try to load a WAVE from the given seekable stream. Returns true if
|
||||
* successful. In that case, the stream's seek position will be set to the
|
||||
* start of the audio data, and size, rate and flags contain information
|
||||
* necessary for playback. Currently this function supports uncompressed
|
||||
* raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally).
|
||||
*/
|
||||
extern bool loadWAVFromStream(
|
||||
Common::SeekableReadStream &stream,
|
||||
int &size,
|
||||
int &rate,
|
||||
byte &flags,
|
||||
uint16 *wavType = 0,
|
||||
int *blockAlign = 0,
|
||||
int *samplesPerBlock = 0);
|
||||
|
||||
/**
|
||||
* Try to load a WAVE from the given seekable stream and create an AudioStream
|
||||
* from that data. Currently this function supports uncompressed
|
||||
* raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally).
|
||||
*
|
||||
* This function uses loadWAVFromStream() internally.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the WAVE data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeWAVStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
44
audio/decoders/wave_types.h
Normal file
44
audio/decoders/wave_types.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_DECODERS_WAVE_TYPES_H
|
||||
#define AUDIO_DECODERS_WAVE_TYPES_H
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Audio Codecs
|
||||
enum WaveCompressionType {
|
||||
kWaveFormatNone = 0x0000,
|
||||
kWaveFormatPCM = 0x0001,
|
||||
kWaveFormatMSADPCM = 0x0002,
|
||||
kWaveFormatALawPCM = 0x0006,
|
||||
kWaveFormatMuLawPCM = 0x0007,
|
||||
kWaveFormatMSIMAADPCM = 0x0011,
|
||||
kWaveFormatMP3 = 0x0055,
|
||||
kWaveFormatDK3 = 0x0062, // rogue format number
|
||||
kWaveFormatMSIMAADPCM2 = 0x0069,
|
||||
kWaveFormatWMAv2 = 0x0161,
|
||||
kWaveFormatXanDPCM = 0x594a // 'JY', Crusader: No Regret videos
|
||||
};
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
1512
audio/decoders/wma.cpp
Normal file
1512
audio/decoders/wma.cpp
Normal file
File diff suppressed because it is too large
Load Diff
225
audio/decoders/wma.h
Normal file
225
audio/decoders/wma.h
Normal file
@@ -0,0 +1,225 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based on xoreos' WMA code which is in turn
|
||||
// Largely based on the WMA implementation found in FFmpeg.
|
||||
|
||||
#ifndef AUDIO_DECODERS_WMA_H
|
||||
#define AUDIO_DECODERS_WMA_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/bitstream.h"
|
||||
|
||||
#include "audio/decoders/codec.h"
|
||||
|
||||
namespace Common {
|
||||
template <class BITSTREAM>
|
||||
class Huffman;
|
||||
}
|
||||
|
||||
namespace Math {
|
||||
class MDCT;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
struct WMACoefHuffmanParam;
|
||||
|
||||
class WMACodec : public Codec {
|
||||
public:
|
||||
WMACodec(int version, uint32 sampleRate, uint8 channels,
|
||||
uint32 bitRate, uint32 blockAlign, Common::SeekableReadStream *extraData = 0);
|
||||
~WMACodec();
|
||||
|
||||
AudioStream *decodeFrame(Common::SeekableReadStream &data);
|
||||
|
||||
private:
|
||||
static const int kChannelsMax = 2; ///< Max number of channels we support.
|
||||
|
||||
static const int kBlockBitsMin = 7; ///< Min number of bits in a block.
|
||||
static const int kBlockBitsMax = 11; ///< Max number of bits in a block.
|
||||
|
||||
/** Max number of bytes in a block. */
|
||||
static const int kBlockSizeMax = (1 << kBlockBitsMax);
|
||||
|
||||
static const int kBlockNBSizes = (kBlockBitsMax - kBlockBitsMin + 1);
|
||||
|
||||
/** Max size of a superframe. */
|
||||
static const int kSuperframeSizeMax = 16384;
|
||||
|
||||
/** Max size of a high band. */
|
||||
static const int kHighBandSizeMax = 16;
|
||||
|
||||
/** Size of the noise table. */
|
||||
static const int kNoiseTabSize = 8192;
|
||||
|
||||
/** Number of bits for the LSP power value. */
|
||||
static const int kLSPPowBits = 7;
|
||||
|
||||
int _version; ///< WMA version.
|
||||
|
||||
uint32 _sampleRate; ///< Output sample rate.
|
||||
uint8 _channels; ///< Output channel count.
|
||||
uint32 _bitRate; ///< Input bit rate.
|
||||
uint32 _blockAlign; ///< Input block align.
|
||||
byte _audioFlags; ///< Output flags.
|
||||
|
||||
bool _useExpHuffman; ///< Exponents in Huffman code? Otherwise, in LSP.
|
||||
bool _useBitReservoir; ///< Is each frame packet a "superframe"?
|
||||
bool _useVariableBlockLen; ///< Are the block lengths variable?
|
||||
bool _useNoiseCoding; ///< Should perceptual noise be added?
|
||||
|
||||
bool _resetBlockLengths; ///< Do we need new block lengths?
|
||||
|
||||
int _curFrame; ///< The number of the frame we're currently in.
|
||||
int _frameLen; ///< The frame length.
|
||||
int _frameLenBits; ///< log2 of the frame length.
|
||||
int _blockSizeCount; ///< Number of block sizes.
|
||||
int _framePos; ///< The position within the frame we're currently in.
|
||||
|
||||
int _curBlock; ///< The number of the block we're currently in.
|
||||
int _blockLen; ///< Current block length.
|
||||
int _blockLenBits; ///< log2 of current block length.
|
||||
int _nextBlockLenBits; ///< log2 of next block length.
|
||||
int _prevBlockLenBits; ///< log2 of previous block length.
|
||||
|
||||
int _byteOffsetBits;
|
||||
|
||||
// Coefficients
|
||||
int _coefsStart; ///< First coded coef
|
||||
int _coefsEnd[kBlockNBSizes]; ///< Max number of coded coefficients
|
||||
int _exponentSizes[kBlockNBSizes];
|
||||
uint16 _exponentBands[kBlockNBSizes][25];
|
||||
int _highBandStart[kBlockNBSizes]; ///< Index of first coef in high band
|
||||
int _exponentHighSizes[kBlockNBSizes];
|
||||
int _exponentHighBands[kBlockNBSizes][kHighBandSizeMax];
|
||||
|
||||
typedef Common::Huffman<Common::BitStream8MSB> HuffmanDecoder;
|
||||
HuffmanDecoder *_coefHuffman[2]; ///< Coefficients Huffman codes.
|
||||
const WMACoefHuffmanParam *_coefHuffmanParam[2]; ///< Params for coef Huffman codes.
|
||||
|
||||
uint16 *_coefHuffmanRunTable[2]; ///< Run table for the coef Huffman.
|
||||
float *_coefHuffmanLevelTable[2]; ///< Level table for the coef Huffman.
|
||||
uint16 *_coefHuffmanIntTable[2]; ///< Int tablre for the coef Huffman.
|
||||
|
||||
// Noise
|
||||
float _noiseMult; ///< Noise multiplier.
|
||||
float _noiseTable[kNoiseTabSize]; ///< Noise table.
|
||||
int _noiseIndex;
|
||||
|
||||
HuffmanDecoder *_hgainHuffman; ///< Perceptual noise huffman code.
|
||||
|
||||
// Exponents
|
||||
int _exponentsBSize[kChannelsMax];
|
||||
float _exponents[kChannelsMax][kBlockSizeMax];
|
||||
float _maxExponent[kChannelsMax];
|
||||
|
||||
HuffmanDecoder *_expHuffman; ///< Exponents huffman code.
|
||||
|
||||
// Coded values in high bands
|
||||
bool _highBandCoded [kChannelsMax][kHighBandSizeMax];
|
||||
int _highBandValues[kChannelsMax][kHighBandSizeMax];
|
||||
|
||||
// Coefficients
|
||||
float _coefs1[kChannelsMax][kBlockSizeMax];
|
||||
float _coefs [kChannelsMax][kBlockSizeMax];
|
||||
|
||||
// Line spectral pairs
|
||||
float _lspCosTable[kBlockSizeMax];
|
||||
float _lspPowETable[256];
|
||||
float _lspPowMTable1[(1 << kLSPPowBits)];
|
||||
float _lspPowMTable2[(1 << kLSPPowBits)];
|
||||
|
||||
// MDCT
|
||||
Common::Array<Math::MDCT *> _mdct; ///< MDCT contexts.
|
||||
Common::Array<const float *> _mdctWindow; ///< MDCT window functions.
|
||||
|
||||
/** Overhang from the last superframe. */
|
||||
byte _lastSuperframe[kSuperframeSizeMax + 4];
|
||||
int _lastSuperframeLen; ///< Size of the overhang data. */
|
||||
int _lastBitoffset; ///< Bit position within the overhang. */
|
||||
|
||||
// Output
|
||||
float _output[kBlockSizeMax * 2];
|
||||
float _frameOut[kChannelsMax][kBlockSizeMax * 2];
|
||||
|
||||
|
||||
// Init helpers
|
||||
|
||||
void init(Common::SeekableReadStream *extraData);
|
||||
|
||||
uint16 getFlags(Common::SeekableReadStream *extraData);
|
||||
void evalFlags(uint16 flags, Common::SeekableReadStream *extraData);
|
||||
int getFrameBitLength();
|
||||
int getBlockSizeCount(uint16 flags);
|
||||
uint32 getNormalizedSampleRate();
|
||||
bool useNoiseCoding(float &highFreq, float &bps);
|
||||
void evalMDCTScales(float highFreq);
|
||||
void initNoise();
|
||||
void initCoefHuffman(float bps);
|
||||
void initMDCT();
|
||||
void initExponents();
|
||||
|
||||
HuffmanDecoder *initCoefHuffman(uint16 *&runTable, float *&levelTable,
|
||||
uint16 *&intTable, const WMACoefHuffmanParam ¶ms);
|
||||
void initLSPToCurve();
|
||||
|
||||
// Decoding
|
||||
|
||||
Common::SeekableReadStream *decodeSuperFrame(Common::SeekableReadStream &data);
|
||||
bool decodeFrame(Common::BitStream8MSB &bits, int16 *outputData);
|
||||
int decodeBlock(Common::BitStream8MSB &bits);
|
||||
|
||||
// Decoding helpers
|
||||
|
||||
bool evalBlockLength(Common::BitStream8MSB &bits);
|
||||
bool decodeChannels(Common::BitStream8MSB &bits, int bSize, bool msStereo, bool *hasChannel);
|
||||
bool calculateIMDCT(int bSize, bool msStereo, bool *hasChannel);
|
||||
|
||||
void calculateCoefCount(int *coefCount, int bSize) const;
|
||||
bool decodeNoise(Common::BitStream8MSB &bits, int bSize, bool *hasChannel, int *coefCount);
|
||||
bool decodeExponents(Common::BitStream8MSB &bits, int bSize, bool *hasChannel);
|
||||
bool decodeSpectralCoef(Common::BitStream8MSB &bits, bool msStereo, bool *hasChannel,
|
||||
int *coefCount, int coefBitCount);
|
||||
float getNormalizedMDCTLength() const;
|
||||
void calculateMDCTCoefficients(int bSize, bool *hasChannel,
|
||||
int *coefCount, int totalGain, float mdctNorm);
|
||||
|
||||
bool decodeExpHuffman(Common::BitStream8MSB &bits, int ch);
|
||||
bool decodeExpLSP(Common::BitStream8MSB &bits, int ch);
|
||||
bool decodeRunLevel(Common::BitStream8MSB &bits, const HuffmanDecoder &huffman,
|
||||
const float *levelTable, const uint16 *runTable, int version, float *ptr,
|
||||
int offset, int numCoefs, int blockLen, int frameLenBits, int coefNbBits);
|
||||
|
||||
void lspToCurve(float *out, float *val_max_ptr, int n, float *lsp);
|
||||
|
||||
void window(float *out) const;
|
||||
|
||||
float pow_m1_4(float x) const;
|
||||
|
||||
static int readTotalGain(Common::BitStream8MSB &bits);
|
||||
static int totalGainToBits(int totalGain);
|
||||
static uint32 getLargeVal(Common::BitStream8MSB &bits);
|
||||
};
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // AUDIO_DECODERS_WMA_H
|
||||
1434
audio/decoders/wmadata.h
Normal file
1434
audio/decoders/wmadata.h
Normal file
File diff suppressed because it is too large
Load Diff
163
audio/decoders/xa.cpp
Normal file
163
audio/decoders/xa.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/decoders/xa.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class XAStream : public Audio::RewindableAudioStream {
|
||||
public:
|
||||
XAStream(Common::SeekableReadStream *stream, int rate, DisposeAfterUse::Flag disposeAfterUse);
|
||||
~XAStream();
|
||||
|
||||
bool isStereo() const override { return false; }
|
||||
bool endOfData() const override { return _endOfData && _samplesRemaining == 0; }
|
||||
int getRate() const override { return _rate; }
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
|
||||
bool rewind() override;
|
||||
private:
|
||||
Common::SeekableReadStream *_stream;
|
||||
DisposeAfterUse::Flag _disposeAfterUse;
|
||||
|
||||
void seekToPos(uint pos);
|
||||
|
||||
byte _predictor;
|
||||
double _samples[28];
|
||||
byte _samplesRemaining;
|
||||
int _rate;
|
||||
double _s1, _s2;
|
||||
uint _loopPoint;
|
||||
bool _endOfData;
|
||||
};
|
||||
|
||||
XAStream::XAStream(Common::SeekableReadStream *stream, int rate, DisposeAfterUse::Flag disposeAfterUse)
|
||||
: _stream(stream), _disposeAfterUse(disposeAfterUse) {
|
||||
_samplesRemaining = 0;
|
||||
_predictor = 0;
|
||||
_s1 = _s2 = 0.0;
|
||||
_rate = rate;
|
||||
_loopPoint = 0;
|
||||
_endOfData = false;
|
||||
}
|
||||
|
||||
|
||||
XAStream::~XAStream() {
|
||||
if (_disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete _stream;
|
||||
}
|
||||
|
||||
static const double s_xaDataTable[5][2] = {
|
||||
{ 0.0, 0.0 },
|
||||
{ 60.0 / 64.0, 0.0 },
|
||||
{ 115.0 / 64.0, -52.0 / 64.0 },
|
||||
{ 98.0 / 64.0, -55.0 / 64.0 },
|
||||
{ 122.0 / 64.0, -60.0 / 64.0 }
|
||||
};
|
||||
|
||||
int XAStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int32 samplesDecoded = 0;
|
||||
|
||||
for (int i = 28 - _samplesRemaining; i < 28 && samplesDecoded < numSamples; i++) {
|
||||
_samples[i] = _samples[i] + _s1 * s_xaDataTable[_predictor][0] + _s2 * s_xaDataTable[_predictor][1];
|
||||
_s2 = _s1;
|
||||
_s1 = _samples[i];
|
||||
int16 d = (int)(_samples[i] + 0.5);
|
||||
buffer[samplesDecoded] = d;
|
||||
samplesDecoded++;
|
||||
_samplesRemaining--;
|
||||
}
|
||||
|
||||
if (endOfData())
|
||||
return samplesDecoded;
|
||||
|
||||
while (samplesDecoded < numSamples) {
|
||||
byte i = 0;
|
||||
|
||||
_predictor = _stream->readByte();
|
||||
byte shift = _predictor & 0xf;
|
||||
_predictor >>= 4;
|
||||
|
||||
byte flags = _stream->readByte();
|
||||
if (flags == 3) {
|
||||
// Loop
|
||||
seekToPos(_loopPoint);
|
||||
continue;
|
||||
} else if (flags == 6) {
|
||||
// Set loop point
|
||||
_loopPoint = _stream->pos() - 2;
|
||||
} else if (flags == 7) {
|
||||
// End of stream
|
||||
_endOfData = true;
|
||||
return samplesDecoded;
|
||||
}
|
||||
|
||||
for (i = 0; i < 28; i += 2) {
|
||||
byte d = _stream->readByte();
|
||||
int16 s = (d & 0xf) << 12;
|
||||
if (s & 0x8000)
|
||||
s |= 0xffff0000;
|
||||
_samples[i] = (double)(s >> shift);
|
||||
s = (d & 0xf0) << 8;
|
||||
if (s & 0x8000)
|
||||
s |= 0xffff0000;
|
||||
_samples[i + 1] = (double)(s >> shift);
|
||||
}
|
||||
|
||||
for (i = 0; i < 28 && samplesDecoded < numSamples; i++) {
|
||||
_samples[i] = _samples[i] + _s1 * s_xaDataTable[_predictor][0] + _s2 * s_xaDataTable[_predictor][1];
|
||||
_s2 = _s1;
|
||||
_s1 = _samples[i];
|
||||
int16 d = (int)(_samples[i] + 0.5);
|
||||
buffer[samplesDecoded] = d;
|
||||
samplesDecoded++;
|
||||
}
|
||||
|
||||
if (i != 28)
|
||||
_samplesRemaining = 28 - i;
|
||||
|
||||
if (_stream->pos() >= _stream->size())
|
||||
_endOfData = true;
|
||||
}
|
||||
|
||||
return samplesDecoded;
|
||||
}
|
||||
|
||||
bool XAStream::rewind() {
|
||||
seekToPos(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void XAStream::seekToPos(uint pos) {
|
||||
_stream->seek(pos);
|
||||
_samplesRemaining = 0;
|
||||
_predictor = 0;
|
||||
_s1 = _s2 = 0.0;
|
||||
_endOfData = false;
|
||||
}
|
||||
|
||||
RewindableAudioStream *makeXAStream(Common::SeekableReadStream *stream, int rate, DisposeAfterUse::Flag disposeAfterUse) {
|
||||
return new XAStream(stream, rate, disposeAfterUse);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
59
audio/decoders/xa.h
Normal file
59
audio/decoders/xa.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - sword1 (PSX port of the game)
|
||||
* - sword2 (PSX port of the game)
|
||||
* - tinsel (PSX port of the game)
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_DECODERS_XA_H
|
||||
#define AUDIO_DECODERS_XA_H
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class RewindableAudioStream;
|
||||
|
||||
/**
|
||||
* Takes an input stream containing XA ADPCM sound data and creates
|
||||
* a RewindableAudioStream from that.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the XA ADPCM data
|
||||
* @param rate the sampling rate
|
||||
* @param disposeAfterUse whether to delete the stream after use.
|
||||
* @return a new RewindableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
RewindableAudioStream *makeXAStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
int rate,
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
104
audio/decoders/xan_dpcm.cpp
Normal file
104
audio/decoders/xan_dpcm.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/stream.h"
|
||||
#include "common/util.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/xan_dpcm.h"
|
||||
|
||||
|
||||
namespace Audio {
|
||||
|
||||
/**
|
||||
* Implements the Xan DPCM decoder used in Crusader: No Regret and Wing
|
||||
* Commander IV movies. Implementation based on the description on the
|
||||
* MultiMedia Wiki:
|
||||
* https://wiki.multimedia.cx/index.php/Xan_DPCM
|
||||
*/
|
||||
class Xan_DPCMStream : public Audio::AudioStream {
|
||||
public:
|
||||
Xan_DPCMStream(int rate, int channels, Common::SeekableReadStream *data) : _rate(rate), _channels(channels), _data(data) {
|
||||
assert(channels == 1 || channels == 2);
|
||||
_pred[0] = data->readSint16LE();
|
||||
if (channels == 2)
|
||||
_pred[1] = data->readSint16LE();
|
||||
_shift[0] = 4;
|
||||
_shift[1] = 4;
|
||||
};
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples) override {
|
||||
int i = 0;
|
||||
for (; i < numSamples; i++) {
|
||||
int32 *pshift = ((_channels == 2 && (i % 2)) ? _shift + 1 : _shift);
|
||||
int32 *ppred = ((_channels == 2 && (i % 2)) ? _pred + 1 : _pred);
|
||||
const uint8 b = _data->readByte();
|
||||
const int diff = static_cast<int8>(b & 0xFC) * 256;
|
||||
if ((b & 3) == 3)
|
||||
*pshift += 1;
|
||||
else
|
||||
*pshift -= (2 * (b & 3));
|
||||
if (*pshift < 0)
|
||||
*pshift = 0;
|
||||
if (*pshift > 15) {
|
||||
warning("Xan DPCM shift should not go over 15, corrupt data?");
|
||||
*pshift = 15;
|
||||
}
|
||||
*ppred += (diff >> *pshift);
|
||||
*ppred = CLIP(*ppred, (int32)-32768, (int32)32767);
|
||||
*buffer = *ppred;
|
||||
buffer++;
|
||||
if (_data->eos())
|
||||
break;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
bool isStereo() const override {
|
||||
return _channels == 2;
|
||||
}
|
||||
|
||||
int getRate() const override {
|
||||
return _rate;
|
||||
}
|
||||
|
||||
bool endOfData() const override {
|
||||
return _data->eos();
|
||||
}
|
||||
|
||||
private:
|
||||
int _channels;
|
||||
int _rate;
|
||||
Common::SeekableReadStream *_data;
|
||||
int32 _pred[2];
|
||||
int32 _shift[2];
|
||||
};
|
||||
|
||||
XanDPCMStream::XanDPCMStream(int rate, int channels) :
|
||||
Audio::StatelessPacketizedAudioStream(rate, channels) {
|
||||
}
|
||||
|
||||
AudioStream *XanDPCMStream::makeStream(Common::SeekableReadStream *data) {
|
||||
return new Xan_DPCMStream(getRate(), getChannels(), data);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
65
audio/decoders/xan_dpcm.h
Normal file
65
audio/decoders/xan_dpcm.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - ultima8 (Crusader: No Regret)
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_XAN_DPCM_H
|
||||
#define AUDIO_XAN_DPCM_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class StatelessPacketizedAudioStream;
|
||||
|
||||
/**
|
||||
* Implements the Xan DPCM decoder used in Crusader: No Regret and Wing
|
||||
* Commander IV movies. Implementation based on the description on the
|
||||
* MultiMedia Wiki:
|
||||
* https://wiki.multimedia.cx/index.php/Xan_DPCM
|
||||
*/
|
||||
class XanDPCMStream : public StatelessPacketizedAudioStream {
|
||||
public:
|
||||
/**
|
||||
* Create a Xan DPCM stream
|
||||
* @param rate sampling rate (samples per second)
|
||||
* @param channels number of channels to decode
|
||||
* @return a new XanDPCMStream, or NULL, if an error occurred
|
||||
*/
|
||||
XanDPCMStream(int rate, int channels);
|
||||
|
||||
protected:
|
||||
AudioStream *makeStream(Common::SeekableReadStream *data) override;
|
||||
};
|
||||
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user