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
|
// In addition, also MS IMA ADPCM is supported. See
|
||||||
// <http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM>.
|
// <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)
|
ADPCMStream::ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
|
||||||
: _stream(stream, disposeAfterUse),
|
: _stream(stream, disposeAfterUse),
|
||||||
|
@ -119,6 +121,106 @@ int16 Oki_ADPCMStream::decodeOKI(byte code) {
|
||||||
#pragma mark -
|
#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 DVI_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||||
int samples;
|
int samples;
|
||||||
byte data;
|
byte data;
|
||||||
|
@ -474,6 +576,8 @@ SeekableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, Dispose
|
||||||
return new Apple_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
return new Apple_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||||
case kADPCMDK3:
|
case kADPCMDK3:
|
||||||
return new DK3_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
return new DK3_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||||
|
case kADPCMXA:
|
||||||
|
return new XA_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
|
||||||
default:
|
default:
|
||||||
error("Unsupported ADPCM encoding");
|
error("Unsupported ADPCM encoding");
|
||||||
break;
|
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)
|
// Filter out types we can't support (they're not fully stateless)
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case kADPCMOki:
|
case kADPCMOki:
|
||||||
|
case kADPCMXA:
|
||||||
case kADPCMDVI:
|
case kADPCMDVI:
|
||||||
return 0;
|
return 0;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -59,7 +59,8 @@ enum ADPCMType {
|
||||||
kADPCMMS, // Microsoft ADPCM
|
kADPCMMS, // Microsoft ADPCM
|
||||||
kADPCMDVI, // Intel DVI IMA ADPCM
|
kADPCMDVI, // Intel DVI IMA ADPCM
|
||||||
kADPCMApple, // Apple QuickTime 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.
|
* packets as individual AudioStreams like returned by makeADPCMStream.
|
||||||
*
|
*
|
||||||
* Due to the ADPCM types not necessarily supporting stateless
|
* 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.
|
* and will return NULL.
|
||||||
*
|
*
|
||||||
* @param type the compression type used
|
* @param type the compression type used
|
||||||
|
|
|
@ -54,6 +54,7 @@ protected:
|
||||||
struct {
|
struct {
|
||||||
int32 last;
|
int32 last;
|
||||||
int32 stepIndex;
|
int32 stepIndex;
|
||||||
|
int16 sample[2];
|
||||||
} ima_ch[2];
|
} ima_ch[2];
|
||||||
} _status;
|
} _status;
|
||||||
|
|
||||||
|
@ -97,6 +98,24 @@ private:
|
||||||
int16 _decodedSamples[2];
|
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 {
|
class Ima_ADPCMStream : public ADPCMStream {
|
||||||
protected:
|
protected:
|
||||||
int16 decodeIMA(byte code, int channel = 0); // Default to using the left channel/using one channel
|
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
|
// MDEC video emulation based on http://kenai.com/downloads/jpsxdec/Old/PlayStation1_STR_format1-00.txt
|
||||||
|
|
||||||
#include "audio/audiostream.h"
|
#include "audio/audiostream.h"
|
||||||
#include "audio/decoders/raw.h"
|
#include "audio/decoders/adpcm.h"
|
||||||
#include "common/bitstream.h"
|
#include "common/bitstream.h"
|
||||||
#include "common/huffman.h"
|
#include "common/huffman.h"
|
||||||
#include "common/stream.h"
|
#include "common/stream.h"
|
||||||
|
@ -299,14 +299,6 @@ Common::SeekableReadStream *PSXStreamDecoder::readSector() {
|
||||||
#define AUDIO_DATA_CHUNK_SIZE 2304
|
#define AUDIO_DATA_CHUNK_SIZE 2304
|
||||||
#define AUDIO_DATA_SAMPLE_COUNT 4032
|
#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) :
|
PSXStreamDecoder::PSXAudioTrack::PSXAudioTrack(Common::SeekableReadStream *sector, Audio::Mixer::SoundType soundType) :
|
||||||
AudioTrack(soundType) {
|
AudioTrack(soundType) {
|
||||||
assert(sector);
|
assert(sector);
|
||||||
|
@ -314,11 +306,9 @@ PSXStreamDecoder::PSXAudioTrack::PSXAudioTrack(Common::SeekableReadStream *secto
|
||||||
|
|
||||||
sector->seek(19);
|
sector->seek(19);
|
||||||
byte format = sector->readByte();
|
byte format = sector->readByte();
|
||||||
bool stereo = (format & (1 << 0)) != 0;
|
_stereo = (format & (1 << 0)) != 0;
|
||||||
uint rate = (format & (1 << 2)) ? 18900 : 37800;
|
_rate = (format & (1 << 2)) ? 18900 : 37800;
|
||||||
_audStream = Audio::makeQueuingAudioStream(rate, stereo);
|
_audStream = Audio::makeQueuingAudioStream(_rate, _stereo);
|
||||||
|
|
||||||
memset(&_adpcmStatus, 0, sizeof(_adpcmStatus));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PSXStreamDecoder::PSXAudioTrack::~PSXAudioTrack() {
|
PSXStreamDecoder::PSXAudioTrack::~PSXAudioTrack() {
|
||||||
|
@ -334,86 +324,16 @@ void PSXStreamDecoder::PSXAudioTrack::queueAudioFromSector(Common::SeekableReadS
|
||||||
|
|
||||||
sector->seek(24);
|
sector->seek(24);
|
||||||
|
|
||||||
// This XA audio is different (yet similar) from normal XA audio! Watch out!
|
// Read the specified sector into memory
|
||||||
// TODO: It's probably similar enough to normal XA that we can merge it somehow...
|
Common::SeekableReadStream *compressedAudioStream = sector->readStream(AUDIO_DATA_CHUNK_SIZE);
|
||||||
// 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);
|
|
||||||
|
|
||||||
int channels = _audStream->isStereo() ? 2 : 1;
|
Audio::SeekableAudioStream *audioStream = Audio::makeADPCMStream(compressedAudioStream, DisposeAfterUse::YES, AUDIO_DATA_CHUNK_SIZE, Audio::kADPCMXA, _rate, _stereo ? 2 : 1);
|
||||||
int16 *dst = new int16[AUDIO_DATA_SAMPLE_COUNT];
|
if (audioStream) {
|
||||||
int16 *leftChannel = dst;
|
_audStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
|
||||||
int16 *rightChannel = dst + 1;
|
} else {
|
||||||
|
// in case there was an error
|
||||||
for (byte *src = buf; src < buf + AUDIO_DATA_CHUNK_SIZE; src += 128) {
|
delete compressedAudioStream;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
Audio::AudioStream *PSXStreamDecoder::PSXAudioTrack::getAudioStream() const {
|
||||||
|
|
|
@ -140,11 +140,9 @@ private:
|
||||||
|
|
||||||
Audio::QueuingAudioStream *_audStream;
|
Audio::QueuingAudioStream *_audStream;
|
||||||
|
|
||||||
struct ADPCMStatus {
|
|
||||||
int16 sample[2];
|
|
||||||
} _adpcmStatus[2];
|
|
||||||
|
|
||||||
bool _endOfTrack;
|
bool _endOfTrack;
|
||||||
|
bool _stereo;
|
||||||
|
uint _rate;
|
||||||
};
|
};
|
||||||
|
|
||||||
CDSpeed _speed;
|
CDSpeed _speed;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue