AUDIO: Fix remaining AAC bugs by decoding with the same AAC context

This introduces a new Audio::Codec class, based on DrMcCoy's solution for WMA in eos.
This commit is contained in:
Matthew Hoops 2011-07-10 15:20:33 -04:00
parent 0d02cc0ef5
commit c46aa548d6
5 changed files with 127 additions and 98 deletions

View file

@ -28,74 +28,34 @@
#ifdef USE_FAAD #ifdef USE_FAAD
#include "common/debug.h" #include "common/debug.h"
#include "common/memstream.h"
#include "common/stream.h" #include "common/stream.h"
#include "common/textconsole.h" #include "common/textconsole.h"
#include "common/util.h" #include "common/util.h"
#include "audio/audiostream.h" #include "audio/audiostream.h"
#include "audio/decoders/codec.h"
#include "audio/decoders/raw.h"
#include <neaacdec.h> #include <neaacdec.h>
namespace Audio { namespace Audio {
class AACStream : public AudioStream { class AACDecoder : public Codec {
public: public:
AACStream(Common::SeekableReadStream *stream, AACDecoder(Common::SeekableReadStream *extraData,
DisposeAfterUse::Flag disposeStream,
Common::SeekableReadStream *extraData,
DisposeAfterUse::Flag disposeExtraData); DisposeAfterUse::Flag disposeExtraData);
~AACStream(); ~AACDecoder();
int readBuffer(int16 *buffer, const int numSamples); AudioStream *decodeFrame(Common::SeekableReadStream &stream);
bool endOfData() const { return _inBufferPos >= _inBufferSize && !_remainingSamples; }
bool isStereo() const { return _channels == 2; }
int getRate() const { return _rate; }
private: private:
NeAACDecHandle _handle; NeAACDecHandle _handle;
byte _channels; byte _channels;
unsigned long _rate; unsigned long _rate;
byte *_inBuffer;
uint32 _inBufferSize;
uint32 _inBufferPos;
int16 *_remainingSamples;
uint32 _remainingSamplesSize;
uint32 _remainingSamplesPos;
void init(Common::SeekableReadStream *extraData);
}; };
AACStream::AACStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeStream, AACDecoder::AACDecoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) {
Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) {
_remainingSamples = 0;
_inBufferPos = 0;
init(extraData);
// Copy all the data to a pointer so it can be passed through
// (At least MPEG-4 chunks shouldn't be large)
_inBufferSize = stream->size();
_inBuffer = new byte[_inBufferSize];
stream->read(_inBuffer, _inBufferSize);
if (disposeStream == DisposeAfterUse::YES)
delete stream;
if (disposeExtraData == DisposeAfterUse::YES)
delete extraData;
}
AACStream::~AACStream() {
NeAACDecClose(_handle);
delete[] _inBuffer;
delete[] _remainingSamples;
}
void AACStream::init(Common::SeekableReadStream *extraData) {
// Open the library // Open the library
_handle = NeAACDecOpen(); _handle = NeAACDecOpen();
@ -117,59 +77,55 @@ void AACStream::init(Common::SeekableReadStream *extraData) {
if (err < 0) if (err < 0)
error("Could not initialize AAC decoder: %s", NeAACDecGetErrorMessage(err)); error("Could not initialize AAC decoder: %s", NeAACDecGetErrorMessage(err));
if (disposeExtraData == DisposeAfterUse::YES)
delete extraData;
} }
int AACStream::readBuffer(int16 *buffer, const int numSamples) { AACDecoder::~AACDecoder() {
int samples = 0; NeAACDecClose(_handle);
}
assert((numSamples % _channels) == 0); AudioStream *AACDecoder::decodeFrame(Common::SeekableReadStream &stream) {
// read everything into a buffer
uint32 inBufferPos = 0;
uint32 inBufferSize = stream.size();
byte *inBuffer = new byte[inBufferSize];
stream.read(inBuffer, inBufferSize);
// Dip into our remaining samples pool if it's available QueuingAudioStream *audioStream = makeQueuingAudioStream(_rate, _channels == 2);
if (_remainingSamples) {
samples = MIN<int>(numSamples, _remainingSamplesSize - _remainingSamplesPos);
memcpy(buffer, _remainingSamples + _remainingSamplesPos, samples * 2);
_remainingSamplesPos += samples;
if (_remainingSamplesPos == _remainingSamplesSize) {
delete[] _remainingSamples;
_remainingSamples = 0;
}
}
// Decode until we have enough samples (or there's no more left) // Decode until we have enough samples (or there's no more left)
while (samples < numSamples && !endOfData()) { while (inBufferPos < inBufferSize) {
NeAACDecFrameInfo frameInfo; NeAACDecFrameInfo frameInfo;
uint16 *decodedSamples = (uint16 *)NeAACDecDecode(_handle, &frameInfo, _inBuffer + _inBufferPos, _inBufferSize - _inBufferPos); void *decodedSamples = NeAACDecDecode(_handle, &frameInfo, inBuffer + inBufferPos, inBufferSize - inBufferPos);
if (frameInfo.error != 0) if (frameInfo.error != 0)
error("Failed to decode AAC frame: %s", NeAACDecGetErrorMessage(frameInfo.error)); error("Failed to decode AAC frame: %s", NeAACDecGetErrorMessage(frameInfo.error));
int decodedSampleSize = frameInfo.samples; byte *buffer = (byte *)malloc(frameInfo.samples * 2);
int copySamples = (decodedSampleSize > (numSamples - samples)) ? (numSamples - samples) : decodedSampleSize; memcpy(buffer, decodedSamples, frameInfo.samples * 2);
memcpy(buffer + samples, decodedSamples, copySamples * 2); byte flags = FLAG_16BITS;
samples += copySamples;
// Copy leftover samples for use in a later readBuffer() call if (_channels == 2)
if (copySamples != decodedSampleSize) { flags |= FLAG_STEREO;
_remainingSamplesSize = decodedSampleSize - copySamples;
_remainingSamples = new int16[_remainingSamplesSize];
_remainingSamplesPos = 0;
memcpy(_remainingSamples, decodedSamples + copySamples, _remainingSamplesSize * 2);
}
_inBufferPos += frameInfo.bytesconsumed; #ifdef SCUMM_LITTLE_ENDIAN
flags |= FLAG_LITTLE_ENDIAN;
#endif
audioStream->queueBuffer(buffer, frameInfo.samples * 2, DisposeAfterUse::YES, flags);
inBufferPos += frameInfo.bytesconsumed;
} }
return samples; return audioStream;
} }
// Factory function // Factory function
AudioStream *makeAACStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeStream, Codec *makeAACDecoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) {
Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) { return new AACDecoder(extraData, disposeExtraData);
return new AACStream(stream, disposeStream, extraData, disposeExtraData);
} }
} // End of namespace Audio } // End of namespace Audio

View file

@ -43,23 +43,19 @@ namespace Common {
namespace Audio { namespace Audio {
class AudioStream; class Codec;
/** /**
* Create a new AudioStream from the AAC data of an MPEG-4 file in the given stream. * Create a new Codec for decoding AAC data of an MPEG-4 file in the given stream.
* *
* @note This should *only* be called by our QuickTime/MPEG-4 decoder since it relies * @note This should *only* be called by our QuickTime/MPEG-4 decoder since it relies
* on the MPEG-4 extra data. If you want to decode a file using AAC, go use * on the MPEG-4 extra data. If you want to decode a file using AAC, go use
* makeQuickTimeStream() instead! * makeQuickTimeStream() instead!
* @param stream the SeekableReadStream from which to read the AAC data
* @param disposeStream whether to delete the stream after use
* @param extraData the SeekableReadStream from which to read the AAC extra data * @param extraData the SeekableReadStream from which to read the AAC extra data
* @param disposeExtraData whether to delete the extra data stream after use * @param disposeExtraData whether to delete the extra data stream after use
* @return a new AudioStream, or NULL, if an error occurred * @return a new Codec, or NULL, if an error occurred
*/ */
AudioStream *makeAACStream( Codec *makeAACDecoder(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeStream,
Common::SeekableReadStream *extraData, Common::SeekableReadStream *extraData,
DisposeAfterUse::Flag disposeExtraData = DisposeAfterUse::NO); DisposeAfterUse::Flag disposeExtraData = DisposeAfterUse::NO);

47
audio/decoders/codec.h Normal file
View file

@ -0,0 +1,47 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#ifndef AUDIO_DECODERS_CODEC_H
#define AUDIO_DECODERS_CODEC_H
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class AudioStream;
class Codec {
public:
Codec() {}
virtual ~Codec() {}
virtual AudioStream *decodeFrame(Common::SeekableReadStream &data) = 0;
};
} // End of namespace Audio
#endif

View file

@ -30,6 +30,7 @@
#include "common/textconsole.h" #include "common/textconsole.h"
#include "audio/audiostream.h" #include "audio/audiostream.h"
#include "audio/decoders/codec.h"
#include "audio/decoders/quicktime.h" #include "audio/decoders/quicktime.h"
#include "audio/decoders/quicktime_intern.h" #include "audio/decoders/quicktime_intern.h"
@ -86,6 +87,9 @@ void QuickTimeAudioDecoder::init() {
// Make sure the bits per sample transfers to the sample size // Make sure the bits per sample transfers to the sample size
if (entry->getCodecTag() == MKTAG('r', 'a', 'w', ' ') || entry->getCodecTag() == MKTAG('t', 'w', 'o', 's')) if (entry->getCodecTag() == MKTAG('r', 'a', 'w', ' ') || entry->getCodecTag() == MKTAG('t', 'w', 'o', 's'))
_tracks[_audioTrackIndex]->sampleSize = (entry->_bitsPerSample / 8) * entry->_channels; _tracks[_audioTrackIndex]->sampleSize = (entry->_bitsPerSample / 8) * entry->_channels;
// Initialize the codec (if necessary)
entry->initCodec();
} }
} }
} }
@ -217,6 +221,9 @@ void QuickTimeAudioDecoder::setAudioStreamPos(const Timestamp &where) {
Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_tracks[_audioTrackIndex]->sampleDescs[0]; Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_tracks[_audioTrackIndex]->sampleDescs[0];
_audStream = Audio::makeQueuingAudioStream(entry->_sampleRate, entry->_channels == 2); _audStream = Audio::makeQueuingAudioStream(entry->_sampleRate, entry->_channels == 2);
// Reinitialize the codec
entry->initCodec();
// First, we need to track down what audio sample we need // First, we need to track down what audio sample we need
Audio::Timestamp curAudioTime = where.convertToFramerate(_tracks[_audioTrackIndex]->timeScale); Audio::Timestamp curAudioTime = where.convertToFramerate(_tracks[_audioTrackIndex]->timeScale);
uint32 sample = curAudioTime.totalNumberOfFrames(); uint32 sample = curAudioTime.totalNumberOfFrames();
@ -266,6 +273,11 @@ QuickTimeAudioDecoder::AudioSampleDesc::AudioSampleDesc(Common::QuickTimeParser:
_samplesPerFrame = 0; _samplesPerFrame = 0;
_bytesPerFrame = 0; _bytesPerFrame = 0;
_bitsPerSample = 0; _bitsPerSample = 0;
_codec = 0;
}
QuickTimeAudioDecoder::AudioSampleDesc::~AudioSampleDesc() {
delete _codec;
} }
bool QuickTimeAudioDecoder::AudioSampleDesc::isAudioCodecSupported() const { bool QuickTimeAudioDecoder::AudioSampleDesc::isAudioCodecSupported() const {
@ -313,7 +325,12 @@ AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::S
if (!stream) if (!stream)
return 0; return 0;
if (_codecTag == MKTAG('t', 'w', 'o', 's') || _codecTag == MKTAG('r', 'a', 'w', ' ')) { if (_codec) {
// If we've loaded a codec, make sure we use first
AudioStream *audioStream = _codec->decodeFrame(*stream);
delete stream;
return audioStream;
} else if (_codecTag == MKTAG('t', 'w', 'o', 's') || _codecTag == MKTAG('r', 'a', 'w', ' ')) {
// Fortunately, most of the audio used in Myst videos is raw... // Fortunately, most of the audio used in Myst videos is raw...
uint16 flags = 0; uint16 flags = 0;
if (_codecTag == MKTAG('r', 'a', 'w', ' ')) if (_codecTag == MKTAG('r', 'a', 'w', ' '))
@ -330,12 +347,6 @@ AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::S
} else if (_codecTag == MKTAG('i', 'm', 'a', '4')) { } else if (_codecTag == MKTAG('i', 'm', 'a', '4')) {
// Riven uses this codec (as do some Myst ME videos) // Riven uses this codec (as do some Myst ME videos)
return makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), kADPCMApple, _sampleRate, _channels, 34); return makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), kADPCMApple, _sampleRate, _channels, 34);
} else if (_codecTag == MKTAG('m', 'p', '4', 'a')) {
// The 7th Guest iOS uses an MPEG-4 codec
#ifdef USE_FAAD
if (_parentTrack->objectTypeMP4 == 0x40)
return makeAACStream(stream, DisposeAfterUse::YES, _parentTrack->extraData);
#endif
#ifdef AUDIO_QDM2_H #ifdef AUDIO_QDM2_H
} else if (_codecTag == MKTAG('Q', 'D', 'M', '2')) { } else if (_codecTag == MKTAG('Q', 'D', 'M', '2')) {
// Myst ME uses this codec for many videos // Myst ME uses this codec for many videos
@ -344,10 +355,24 @@ AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::S
} }
error("Unsupported audio codec"); error("Unsupported audio codec");
return NULL; return NULL;
} }
void QuickTimeAudioDecoder::AudioSampleDesc::initCodec() {
delete _codec; _codec = 0;
switch (_codecTag) {
case MKTAG('m', 'p', '4', 'a'):
#ifdef USE_FAAD
if (_parentTrack->objectTypeMP4 == 0x40)
_codec = makeAACDecoder(_parentTrack->extraData);
#endif
break;
default:
break;
}
}
/** /**
* A wrapper around QuickTimeAudioDecoder that implements the RewindableAudioStream API * A wrapper around QuickTimeAudioDecoder that implements the RewindableAudioStream API
*/ */

View file

@ -45,6 +45,7 @@ namespace Common {
namespace Audio { namespace Audio {
class AudioStream; class AudioStream;
class Codec;
class QueuingAudioStream; class QueuingAudioStream;
class QuickTimeAudioDecoder : public Common::QuickTimeParser { class QuickTimeAudioDecoder : public Common::QuickTimeParser {
@ -68,10 +69,12 @@ protected:
class AudioSampleDesc : public Common::QuickTimeParser::SampleDesc { class AudioSampleDesc : public Common::QuickTimeParser::SampleDesc {
public: public:
AudioSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); AudioSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag);
~AudioSampleDesc();
bool isAudioCodecSupported() const; bool isAudioCodecSupported() const;
uint32 getAudioChunkSampleCount(uint chunk) const; uint32 getAudioChunkSampleCount(uint chunk) const;
AudioStream *createAudioStream(Common::SeekableReadStream *stream) const; AudioStream *createAudioStream(Common::SeekableReadStream *stream) const;
void initCodec();
// TODO: Make private in the long run // TODO: Make private in the long run
uint16 _bitsPerSample; uint16 _bitsPerSample;
@ -79,6 +82,8 @@ protected:
uint32 _sampleRate; uint32 _sampleRate;
uint32 _samplesPerFrame; uint32 _samplesPerFrame;
uint32 _bytesPerFrame; uint32 _bytesPerFrame;
Codec *_codec;
}; };
// Common::QuickTimeParser API // Common::QuickTimeParser API