AUDIO: Separate the XA ADPCM decoder from the PSX video decoder

This commit is contained in:
Cameron Cawley 2020-03-31 23:23:41 +01:00 committed by Eugene Sandulenko
parent 2c1065188e
commit b76652120b
5 changed files with 142 additions and 99 deletions

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -140,11 +140,9 @@ private:
Audio::QueuingAudioStream *_audStream;
struct ADPCMStatus {
int16 sample[2];
} _adpcmStatus[2];
bool _endOfTrack;
bool _stereo;
uint _rate;
};
CDSpeed _speed;