From b76652120b5b57a4823a0d8c6de4b04e2f9a22d3 Mon Sep 17 00:00:00 2001 From: Cameron Cawley Date: Tue, 31 Mar 2020 23:23:41 +0100 Subject: [PATCH] AUDIO: Separate the XA ADPCM decoder from the PSX video decoder --- audio/decoders/adpcm.cpp | 105 +++++++++++++++++++++++++++++++++ audio/decoders/adpcm.h | 5 +- audio/decoders/adpcm_intern.h | 19 ++++++ video/psx_decoder.cpp | 106 +++++----------------------------- video/psx_decoder.h | 6 +- 5 files changed, 142 insertions(+), 99 deletions(-) diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp index e87661dbbbf..1f0ac3b6cf8 100644 --- a/audio/decoders/adpcm.cpp +++ b/audio/decoders/adpcm.cpp @@ -39,6 +39,8 @@ namespace Audio { // // In addition, also MS IMA ADPCM is supported. See // . +// +// 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(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(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: diff --git a/audio/decoders/adpcm.h b/audio/decoders/adpcm.h index 0f0d83c3c2d..4c75915ea8d 100644 --- a/audio/decoders/adpcm.h +++ b/audio/decoders/adpcm.h @@ -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 diff --git a/audio/decoders/adpcm_intern.h b/audio/decoders/adpcm_intern.h index 889b0a81184..a331cede5ab 100644 --- a/audio/decoders/adpcm_intern.h +++ b/audio/decoders/adpcm_intern.h @@ -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 diff --git a/video/psx_decoder.cpp b/video/psx_decoder.cpp index ef42a728733..48de547f48c 100644 --- a/video/psx_decoder.cpp +++ b/video/psx_decoder.cpp @@ -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(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(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 { diff --git a/video/psx_decoder.h b/video/psx_decoder.h index 183b6da317f..bf85fb11b1f 100644 --- a/video/psx_decoder.h +++ b/video/psx_decoder.h @@ -140,11 +140,9 @@ private: Audio::QueuingAudioStream *_audStream; - struct ADPCMStatus { - int16 sample[2]; - } _adpcmStatus[2]; - bool _endOfTrack; + bool _stereo; + uint _rate; }; CDSpeed _speed;