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
#include "common/debug.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "audio/audiostream.h"
#include "audio/decoders/codec.h"
#include "audio/decoders/raw.h"
#include <neaacdec.h>
namespace Audio {
class AACStream : public AudioStream {
class AACDecoder : public Codec {
public:
AACStream(Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeStream,
Common::SeekableReadStream *extraData,
AACDecoder(Common::SeekableReadStream *extraData,
DisposeAfterUse::Flag disposeExtraData);
~AACStream();
~AACDecoder();
int readBuffer(int16 *buffer, const int numSamples);
bool endOfData() const { return _inBufferPos >= _inBufferSize && !_remainingSamples; }
bool isStereo() const { return _channels == 2; }
int getRate() const { return _rate; }
AudioStream *decodeFrame(Common::SeekableReadStream &stream);
private:
NeAACDecHandle _handle;
byte _channels;
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,
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) {
AACDecoder::AACDecoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) {
// Open the library
_handle = NeAACDecOpen();
@ -117,59 +77,55 @@ void AACStream::init(Common::SeekableReadStream *extraData) {
if (err < 0)
error("Could not initialize AAC decoder: %s", NeAACDecGetErrorMessage(err));
if (disposeExtraData == DisposeAfterUse::YES)
delete extraData;
}
int AACStream::readBuffer(int16 *buffer, const int numSamples) {
int samples = 0;
AACDecoder::~AACDecoder() {
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
if (_remainingSamples) {
samples = MIN<int>(numSamples, _remainingSamplesSize - _remainingSamplesPos);
memcpy(buffer, _remainingSamples + _remainingSamplesPos, samples * 2);
_remainingSamplesPos += samples;
if (_remainingSamplesPos == _remainingSamplesSize) {
delete[] _remainingSamples;
_remainingSamples = 0;
}
}
QueuingAudioStream *audioStream = makeQueuingAudioStream(_rate, _channels == 2);
// Decode until we have enough samples (or there's no more left)
while (samples < numSamples && !endOfData()) {
while (inBufferPos < inBufferSize) {
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)
error("Failed to decode AAC frame: %s", NeAACDecGetErrorMessage(frameInfo.error));
int decodedSampleSize = frameInfo.samples;
int copySamples = (decodedSampleSize > (numSamples - samples)) ? (numSamples - samples) : decodedSampleSize;
byte *buffer = (byte *)malloc(frameInfo.samples * 2);
memcpy(buffer, decodedSamples, frameInfo.samples * 2);
memcpy(buffer + samples, decodedSamples, copySamples * 2);
samples += copySamples;
byte flags = FLAG_16BITS;
// Copy leftover samples for use in a later readBuffer() call
if (copySamples != decodedSampleSize) {
_remainingSamplesSize = decodedSampleSize - copySamples;
_remainingSamples = new int16[_remainingSamplesSize];
_remainingSamplesPos = 0;
memcpy(_remainingSamples, decodedSamples + copySamples, _remainingSamplesSize * 2);
}
if (_channels == 2)
flags |= FLAG_STEREO;
_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
AudioStream *makeAACStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeStream,
Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) {
return new AACStream(stream, disposeStream, extraData, disposeExtraData);
Codec *makeAACDecoder(Common::SeekableReadStream *extraData, DisposeAfterUse::Flag disposeExtraData) {
return new AACDecoder(extraData, disposeExtraData);
}
} // End of namespace Audio

View File

@ -43,23 +43,19 @@ namespace Common {
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
* on the MPEG-4 extra data. If you want to decode a file using AAC, go use
* 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 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(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeStream,
Codec *makeAACDecoder(
Common::SeekableReadStream *extraData,
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 "audio/audiostream.h"
#include "audio/decoders/codec.h"
#include "audio/decoders/quicktime.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
if (entry->getCodecTag() == MKTAG('r', 'a', 'w', ' ') || entry->getCodecTag() == MKTAG('t', 'w', 'o', 's'))
_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];
_audStream = Audio::makeQueuingAudioStream(entry->_sampleRate, entry->_channels == 2);
// Reinitialize the codec
entry->initCodec();
// First, we need to track down what audio sample we need
Audio::Timestamp curAudioTime = where.convertToFramerate(_tracks[_audioTrackIndex]->timeScale);
uint32 sample = curAudioTime.totalNumberOfFrames();
@ -266,6 +273,11 @@ QuickTimeAudioDecoder::AudioSampleDesc::AudioSampleDesc(Common::QuickTimeParser:
_samplesPerFrame = 0;
_bytesPerFrame = 0;
_bitsPerSample = 0;
_codec = 0;
}
QuickTimeAudioDecoder::AudioSampleDesc::~AudioSampleDesc() {
delete _codec;
}
bool QuickTimeAudioDecoder::AudioSampleDesc::isAudioCodecSupported() const {
@ -313,7 +325,12 @@ AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::S
if (!stream)
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...
uint16 flags = 0;
if (_codecTag == MKTAG('r', 'a', 'w', ' '))
@ -330,12 +347,6 @@ AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::S
} else if (_codecTag == MKTAG('i', 'm', 'a', '4')) {
// Riven uses this codec (as do some Myst ME videos)
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
} else if (_codecTag == MKTAG('Q', 'D', 'M', '2')) {
// Myst ME uses this codec for many videos
@ -344,10 +355,24 @@ AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::S
}
error("Unsupported audio codec");
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
*/

View File

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