aboutsummaryrefslogtreecommitdiff
path: root/audio/decoders/adpcm.cpp
diff options
context:
space:
mode:
authorColin Snover2017-09-09 22:32:55 -0500
committerColin Snover2017-09-09 23:29:58 -0500
commit41506201b908347ae7fba88cfdd3bcc53e6b4056 (patch)
treeb09ce301fb52776f1234987ca3b615ce7a3125e3 /audio/decoders/adpcm.cpp
parent393c0d1f6250d0fd13f201c7ae28e51bd8c93215 (diff)
downloadscummvm-rg350-41506201b908347ae7fba88cfdd3bcc53e6b4056.tar.gz
scummvm-rg350-41506201b908347ae7fba88cfdd3bcc53e6b4056.tar.bz2
scummvm-rg350-41506201b908347ae7fba88cfdd3bcc53e6b4056.zip
AUDIO: Fix incorrect reading of DK3 ADPCM audio data
Previously, _topNibble was not reset at the beginning of a new audio block, and the alignment byte at the end of odd blocks was being read as audio data, which caused audible clicks and out-of-bounds sample generation. There may have also been read errors related to the use of continue/break keywords inside of a macro wrapped with do-while(0). The introduction of partial block reads in this code when it was converted from ffmpeg to a ReadStream interface was also confusing and somewhat inefficient (calling SeekableReadStream::pos frequently), so this code has been refactored for clarity and to improve efficiency by reducing the number of virtual calls. Error detection has also been improved somewhat by ensuring that there are enough bytes to read a block header, and that the step indexes in the header are within the valid range.
Diffstat (limited to 'audio/decoders/adpcm.cpp')
-rw-r--r--audio/decoders/adpcm.cpp80
1 files changed, 51 insertions, 29 deletions
diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp
index ffb61a49c0..7f578ddbae 100644
--- a/audio/decoders/adpcm.cpp
+++ b/audio/decoders/adpcm.cpp
@@ -334,67 +334,89 @@ int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
#pragma mark -
-
-#define DK3_READ_NIBBLE() \
+#define DK3_READ_NIBBLE(channelNo) \
do { \
if (_topNibble) { \
_nibble = _lastByte >> 4; \
_topNibble = false; \
} else { \
- if (_stream->pos() >= _endpos) \
- break; \
- if ((_stream->pos() % _blockAlign) == 0) \
- continue; \
_lastByte = _stream->readByte(); \
_nibble = _lastByte & 0xf; \
_topNibble = true; \
+ --blockBytesLeft; \
+ --audioBytesLeft; \
} \
-} while (0)
-
+ decodeIMA(_nibble, channelNo); \
+} while(0)
int DK3_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ assert((numSamples % 4) == 0);
+
+ const uint startOffset = _stream->pos() % _blockAlign;
+ uint audioBytesLeft = _endpos - _stream->pos();
+ uint 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;
- assert((numSamples % 4) == 0);
+ if (blockBytesLeft < 16) {
+ warning("Truncated DK3 ADPCM block header");
+ break;
+ }
+
+ _stream->skip(2);
+ const uint16 rate = _stream->readUint16LE();
+ assert(rate == getRate());
+ _stream->skip(6);
- while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
- if ((_stream->pos() % _blockAlign) == 0) {
- _stream->readUint16LE(); // Unknown
- uint16 rate = _stream->readUint16LE(); // Copy of rate
- _stream->skip(6); // Unknown
// 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));
- if (_stream->eos())
- break;
-
- // Sanity check
- assert(rate == getRate());
+ blockBytesLeft -= 16;
+ audioBytesLeft -= 16;
}
- DK3_READ_NIBBLE();
- decodeIMA(_nibble, 0);
+ DK3_READ_NIBBLE(0);
+ DK3_READ_NIBBLE(1);
- DK3_READ_NIBBLE();
- decodeIMA(_nibble, 1);
+ *buffer++ = _status.ima_ch[0].last + _status.ima_ch[1].last;
+ *buffer++ = _status.ima_ch[0].last - _status.ima_ch[1].last;
- buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last;
- buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last;
+ DK3_READ_NIBBLE(0);
- DK3_READ_NIBBLE();
- decodeIMA(_nibble, 0);
+ *buffer++ = _status.ima_ch[0].last + _status.ima_ch[1].last;
+ *buffer++ = _status.ima_ch[0].last - _status.ima_ch[1].last;
- buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last;
- buffer[samples++] = _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 -