AUDIO: Separate the XA ADPCM decoder from the PSX video decoder
This commit is contained in:
parent
2c1065188e
commit
b76652120b
5 changed files with 142 additions and 99 deletions
|
@ -39,6 +39,8 @@ namespace Audio {
|
|||
//
|
||||
// In addition, also MS IMA ADPCM is supported. See
|
||||
// <http://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),
|
||||
|
@ -119,6 +121,106 @@ int16 Oki_ADPCMStream::decodeOKI(byte code) {
|
|||
#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;
|
||||
|
@ -474,6 +576,8 @@ SeekableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, Dispose
|
|||
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;
|
||||
|
@ -501,6 +605,7 @@ PacketizedAudioStream *makePacketizedADPCMStream(ADPCMType type, int rate, int c
|
|||
// Filter out types we can't support (they're not fully stateless)
|
||||
switch (type) {
|
||||
case kADPCMOki:
|
||||
case kADPCMXA:
|
||||
case kADPCMDVI:
|
||||
return 0;
|
||||
default:
|
||||
|
|
|
@ -59,7 +59,8 @@ enum ADPCMType {
|
|||
kADPCMMS, // Microsoft ADPCM
|
||||
kADPCMDVI, // Intel DVI IMA ADPCM
|
||||
kADPCMApple, // Apple QuickTime IMA ADPCM
|
||||
kADPCMDK3 // Duck DK3 IMA ADPCM
|
||||
kADPCMDK3, // Duck DK3 IMA ADPCM
|
||||
kADPCMXA // XA ADPCM
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -88,7 +89,7 @@ SeekableAudioStream *makeADPCMStream(
|
|||
* packets as individual AudioStreams like returned by makeADPCMStream.
|
||||
*
|
||||
* Due to the ADPCM types not necessarily supporting stateless
|
||||
* streaming, OKI and DVI are not supported by this function
|
||||
* streaming, OKI, XA and DVI are not supported by this function
|
||||
* and will return NULL.
|
||||
*
|
||||
* @param type the compression type used
|
||||
|
|
|
@ -54,6 +54,7 @@ protected:
|
|||
struct {
|
||||
int32 last;
|
||||
int32 stepIndex;
|
||||
int16 sample[2];
|
||||
} ima_ch[2];
|
||||
} _status;
|
||||
|
||||
|
@ -97,6 +98,24 @@ private:
|
|||
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
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
*
|
||||
*/
|
||||
|
||||
// PlayStation Stream demuxer and XA audio decoder based on FFmpeg/libav
|
||||
// PlayStation Stream demuxer based on FFmpeg/libav
|
||||
// MDEC video emulation based on http://kenai.com/downloads/jpsxdec/Old/PlayStation1_STR_format1-00.txt
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/adpcm.h"
|
||||
#include "common/bitstream.h"
|
||||
#include "common/huffman.h"
|
||||
#include "common/stream.h"
|
||||
|
@ -299,14 +299,6 @@ Common::SeekableReadStream *PSXStreamDecoder::readSector() {
|
|||
#define AUDIO_DATA_CHUNK_SIZE 2304
|
||||
#define AUDIO_DATA_SAMPLE_COUNT 4032
|
||||
|
||||
static const int s_xaTable[5][2] = {
|
||||
{ 0, 0 },
|
||||
{ 60, 0 },
|
||||
{ 115, -52 },
|
||||
{ 98, -55 },
|
||||
{ 122, -60 }
|
||||
};
|
||||
|
||||
PSXStreamDecoder::PSXAudioTrack::PSXAudioTrack(Common::SeekableReadStream *sector, Audio::Mixer::SoundType soundType) :
|
||||
AudioTrack(soundType) {
|
||||
assert(sector);
|
||||
|
@ -314,11 +306,9 @@ PSXStreamDecoder::PSXAudioTrack::PSXAudioTrack(Common::SeekableReadStream *secto
|
|||
|
||||
sector->seek(19);
|
||||
byte format = sector->readByte();
|
||||
bool stereo = (format & (1 << 0)) != 0;
|
||||
uint rate = (format & (1 << 2)) ? 18900 : 37800;
|
||||
_audStream = Audio::makeQueuingAudioStream(rate, stereo);
|
||||
|
||||
memset(&_adpcmStatus, 0, sizeof(_adpcmStatus));
|
||||
_stereo = (format & (1 << 0)) != 0;
|
||||
_rate = (format & (1 << 2)) ? 18900 : 37800;
|
||||
_audStream = Audio::makeQueuingAudioStream(_rate, _stereo);
|
||||
}
|
||||
|
||||
PSXStreamDecoder::PSXAudioTrack::~PSXAudioTrack() {
|
||||
|
@ -334,86 +324,16 @@ void PSXStreamDecoder::PSXAudioTrack::queueAudioFromSector(Common::SeekableReadS
|
|||
|
||||
sector->seek(24);
|
||||
|
||||
// This XA audio is different (yet similar) from normal XA audio! Watch out!
|
||||
// TODO: It's probably similar enough to normal XA that we can merge it somehow...
|
||||
// TODO: RTZ PSX needs the same audio code in a regular AudioStream class. Probably
|
||||
// will do something similar to QuickTime and creating a base class 'ISOMode2Parser'
|
||||
// or something similar.
|
||||
byte *buf = new byte[AUDIO_DATA_CHUNK_SIZE];
|
||||
sector->read(buf, AUDIO_DATA_CHUNK_SIZE);
|
||||
// Read the specified sector into memory
|
||||
Common::SeekableReadStream *compressedAudioStream = sector->readStream(AUDIO_DATA_CHUNK_SIZE);
|
||||
|
||||
int channels = _audStream->isStereo() ? 2 : 1;
|
||||
int16 *dst = new int16[AUDIO_DATA_SAMPLE_COUNT];
|
||||
int16 *leftChannel = dst;
|
||||
int16 *rightChannel = dst + 1;
|
||||
|
||||
for (byte *src = buf; src < buf + AUDIO_DATA_CHUNK_SIZE; src += 128) {
|
||||
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 = _adpcmStatus[0].sample[0];
|
||||
int16 s_2 = _adpcmStatus[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;
|
||||
}
|
||||
|
||||
if (channels == 2) {
|
||||
_adpcmStatus[0].sample[0] = s_1;
|
||||
_adpcmStatus[0].sample[1] = s_2;
|
||||
s_1 = _adpcmStatus[1].sample[0];
|
||||
s_2 = _adpcmStatus[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;
|
||||
}
|
||||
}
|
||||
|
||||
if (channels == 2) {
|
||||
_adpcmStatus[1].sample[0] = s_1;
|
||||
_adpcmStatus[1].sample[1] = s_2;
|
||||
} else {
|
||||
_adpcmStatus[0].sample[0] = s_1;
|
||||
_adpcmStatus[0].sample[1] = s_2;
|
||||
}
|
||||
}
|
||||
Audio::SeekableAudioStream *audioStream = Audio::makeADPCMStream(compressedAudioStream, DisposeAfterUse::YES, AUDIO_DATA_CHUNK_SIZE, Audio::kADPCMXA, _rate, _stereo ? 2 : 1);
|
||||
if (audioStream) {
|
||||
_audStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
|
||||
} else {
|
||||
// in case there was an error
|
||||
delete compressedAudioStream;
|
||||
}
|
||||
|
||||
int flags = Audio::FLAG_16BITS;
|
||||
|
||||
if (_audStream->isStereo())
|
||||
flags |= Audio::FLAG_STEREO;
|
||||
|
||||
#ifdef SCUMM_LITTLE_ENDIAN
|
||||
flags |= Audio::FLAG_LITTLE_ENDIAN;
|
||||
#endif
|
||||
|
||||
_audStream->queueBuffer((byte *)dst, AUDIO_DATA_SAMPLE_COUNT * 2, DisposeAfterUse::YES, flags);
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
Audio::AudioStream *PSXStreamDecoder::PSXAudioTrack::getAudioStream() const {
|
||||
|
|
|
@ -140,11 +140,9 @@ private:
|
|||
|
||||
Audio::QueuingAudioStream *_audStream;
|
||||
|
||||
struct ADPCMStatus {
|
||||
int16 sample[2];
|
||||
} _adpcmStatus[2];
|
||||
|
||||
bool _endOfTrack;
|
||||
bool _stereo;
|
||||
uint _rate;
|
||||
};
|
||||
|
||||
CDSpeed _speed;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue