diff options
author | Matthew Hoops | 2012-03-17 18:15:06 -0400 |
---|---|---|
committer | Matthew Hoops | 2012-03-19 12:04:46 -0400 |
commit | be8c557645bf73e96a463c60bd0422b39a3edfef (patch) | |
tree | 2460844c5a521c7bc25af984938cd8ca095d0211 /audio/decoders | |
parent | ee0a05c32ee85c276ba98a1bb1882b618be5ca57 (diff) | |
download | scummvm-rg350-be8c557645bf73e96a463c60bd0422b39a3edfef.tar.gz scummvm-rg350-be8c557645bf73e96a463c60bd0422b39a3edfef.tar.bz2 scummvm-rg350-be8c557645bf73e96a463c60bd0422b39a3edfef.zip |
AUDIO: Add support for multiple QuickTime audio tracks
This also cleans up the QuickTime audio code to make it a bit more manageable too
Diffstat (limited to 'audio/decoders')
-rw-r--r-- | audio/decoders/quicktime.cpp | 465 | ||||
-rw-r--r-- | audio/decoders/quicktime_intern.h | 60 |
2 files changed, 411 insertions, 114 deletions
diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp index dcf80ea1c6..762e86959d 100644 --- a/audio/decoders/quicktime.cpp +++ b/audio/decoders/quicktime.cpp @@ -26,7 +26,6 @@ #include "common/stream.h" #include "common/textconsole.h" -#include "audio/audiostream.h" #include "audio/decoders/codec.h" #include "audio/decoders/quicktime.h" #include "audio/decoders/quicktime_intern.h" @@ -39,12 +38,70 @@ namespace Audio { +/** + * An AudioStream that just returns silent samples and runs infinitely. + * Used to fill in the "empty edits" in the track queue which are just + * supposed to be no sound playing. + */ +class SilentAudioStream : public AudioStream { +public: + SilentAudioStream(int rate, bool stereo) : _rate(rate), _isStereo(stereo) {} + + int readBuffer(int16 *buffer, const int numSamples) { + memset(buffer, 0, numSamples * 2); + return numSamples; + } + + bool endOfData() const { return false; } // it never ends! + bool isStereo() const { return _isStereo; } + int getRate() const { return _rate; } + +private: + int _rate; + bool _isStereo; +}; + +/** + * An AudioStream wrapper that cuts off the amount of samples read after a + * given time length is reached. + */ +class LimitingAudioStream : public AudioStream { +public: + LimitingAudioStream(AudioStream *parentStream, const Audio::Timestamp &length, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES) : + _parentStream(parentStream), _samplesRead(0), _disposeAfterUse(disposeAfterUse), + _totalSamples(length.convertToFramerate(getRate()).totalNumberOfFrames() * getChannels()) {} + + ~LimitingAudioStream() { + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _parentStream; + } + + int readBuffer(int16 *buffer, const int numSamples) { + // Cap us off so we don't read past _totalSamples + int samplesRead = _parentStream->readBuffer(buffer, MIN<int>(numSamples, _totalSamples - _samplesRead)); + _samplesRead += samplesRead; + return samplesRead; + } + + bool endOfData() const { return _parentStream->endOfData() || _samplesRead >= _totalSamples; } + bool isStereo() const { return _parentStream->isStereo(); } + int getRate() const { return _parentStream->getRate(); } + +private: + int getChannels() const { return isStereo() ? 2 : 1; } + + AudioStream *_parentStream; + DisposeAfterUse::Flag _disposeAfterUse; + uint32 _totalSamples, _samplesRead; +}; + QuickTimeAudioDecoder::QuickTimeAudioDecoder() : Common::QuickTimeParser() { - _audStream = 0; } QuickTimeAudioDecoder::~QuickTimeAudioDecoder() { - delete _audStream; + for (uint32 i = 0; i < _audioTracks.size(); i++) + delete _audioTracks[i]; } bool QuickTimeAudioDecoder::loadAudioFile(const Common::String &filename) { @@ -66,32 +123,11 @@ bool QuickTimeAudioDecoder::loadAudioStream(Common::SeekableReadStream *stream, void QuickTimeAudioDecoder::init() { Common::QuickTimeParser::init(); - _audioTrackIndex = -1; - - // Find an audio stream + // Initialize all the audio streams + // But ignore any streams we don't support for (uint32 i = 0; i < _tracks.size(); i++) - if (_tracks[i]->codecType == CODEC_TYPE_AUDIO && _audioTrackIndex < 0) - _audioTrackIndex = i; - - // Initialize audio, if present - if (_audioTrackIndex >= 0) { - AudioSampleDesc *entry = (AudioSampleDesc *)_tracks[_audioTrackIndex]->sampleDescs[0]; - - if (entry->isAudioCodecSupported()) { - _audStream = makeQueuingAudioStream(entry->_sampleRate, entry->_channels == 2); - _curAudioChunk = 0; - - // 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(); - - if (_tracks[_audioTrackIndex]->editCount > 1) - warning("Multiple edit list entries in an audio track. Things may go awry"); - } - } + if (_tracks[i]->codecType == CODEC_TYPE_AUDIO && ((AudioSampleDesc *)_tracks[i]->sampleDescs[0])->isAudioCodecSupported()) + _audioTracks.push_back(new QuickTimeAudioTrack(this, _tracks[i])); } Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format) { @@ -130,8 +166,7 @@ Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track return 0; } - // Version 0 videos (such as the Riven ones) don't have this set, - // but we need it later on. Add it in here. + // Version 0 files don't have some variables set, so we'll do that here if (format == MKTAG('i', 'm', 'a', '4')) { entry->_samplesPerFrame = 64; entry->_bytesPerFrame = 34 * entry->_channels; @@ -146,20 +181,164 @@ Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track return 0; } -bool QuickTimeAudioDecoder::isOldDemuxing() const { - assert(_audioTrackIndex >= 0); - return _tracks[_audioTrackIndex]->timeToSampleCount == 1 && _tracks[_audioTrackIndex]->timeToSample[0].duration == 1; +QuickTimeAudioDecoder::QuickTimeAudioTrack::QuickTimeAudioTrack(QuickTimeAudioDecoder *decoder, Common::QuickTimeParser::Track *parentTrack) { + _decoder = decoder; + _parentTrack = parentTrack; + _queue = createStream(); + _samplesQueued = 0; + + AudioSampleDesc *entry = (AudioSampleDesc *)_parentTrack->sampleDescs[0]; + + if (entry->getCodecTag() == MKTAG('r', 'a', 'w', ' ') || entry->getCodecTag() == MKTAG('t', 'w', 'o', 's')) + _parentTrack->sampleSize = (entry->_bitsPerSample / 8) * entry->_channels; + + // Initialize our edit parser too + _curEdit = 0; + enterNewEdit(Timestamp()); + + // If the edit doesn't start on a nice boundary, set us up to skip some samples + Timestamp editStartTime(0, _parentTrack->editList[_curEdit].mediaTime, _parentTrack->timeScale); + Timestamp trackPosition = getCurrentTrackTime(); + if (_parentTrack->editList[_curEdit].mediaTime != -1 && trackPosition != editStartTime) + _skipSamples = editStartTime.convertToFramerate(getRate()) - trackPosition; +} + +QuickTimeAudioDecoder::QuickTimeAudioTrack::~QuickTimeAudioTrack() { + delete _queue; +} + +void QuickTimeAudioDecoder::QuickTimeAudioTrack::queueAudio(const Timestamp &length) { + if (allDataRead() || (length.totalNumberOfFrames() != 0 && Timestamp(0, _samplesQueued, getRate()) >= length)) + return; + + do { + Timestamp nextEditTime(0, _parentTrack->editList[_curEdit].timeOffset + _parentTrack->editList[_curEdit].trackDuration, _decoder->_timeScale); + + if (_parentTrack->editList[_curEdit].mediaTime == -1) { + // We've got an empty edit, so fill it with silence + Timestamp editLength(0, _parentTrack->editList[_curEdit].trackDuration, _decoder->_timeScale); + + // If we seek into the middle of an empty edit, we need to adjust + if (_skipSamples != Timestamp()) { + editLength = editLength - _skipSamples; + _skipSamples = Timestamp(); + } + + queueStream(new LimitingAudioStream(new SilentAudioStream(getRate(), isStereo()), editLength), editLength); + _curEdit++; + enterNewEdit(nextEditTime); + } else { + // Normal audio + AudioStream *stream = readAudioChunk(_curChunk); + Timestamp chunkLength = getChunkLength(_curChunk, _skipAACPrimer); + _skipAACPrimer = false; + _curChunk++; + + // If we have any samples that we need to skip (ie. we seeked into + // the middle of a chunk), skip them here. + if (_skipSamples != Timestamp()) { + skipSamples(_skipSamples, stream); + _curMediaPos = _curMediaPos + _skipSamples; + chunkLength = chunkLength - _skipSamples; + _skipSamples = Timestamp(); + } + + // Calculate our overall position within the media + Timestamp trackPosition = getCurrentTrackTime() + chunkLength; + + // If we have reached the end of this edit (or have no more media to read), + // we move on to the next edit + if (trackPosition >= nextEditTime || _curChunk >= _parentTrack->chunkCount) { + chunkLength = nextEditTime.convertToFramerate(getRate()) - getCurrentTrackTime(); + stream = new LimitingAudioStream(stream, chunkLength); + _curEdit++; + enterNewEdit(nextEditTime); + + // Next time around, we'll know how much to skip + trackPosition = getCurrentTrackTime(); + if (!allDataRead() && _parentTrack->editList[_curEdit].mediaTime != -1 && nextEditTime != trackPosition) + _skipSamples = nextEditTime.convertToFramerate(getRate()) - trackPosition; + } else { + _curMediaPos = _curMediaPos + chunkLength.convertToFramerate(_curMediaPos.framerate()); + } + + queueStream(stream, chunkLength); + } + } while (!allDataRead() && Timestamp(0, _samplesQueued, getRate()) < length); +} + +Timestamp QuickTimeAudioDecoder::QuickTimeAudioTrack::getCurrentTrackTime() const { + if (allDataRead()) + return getLength().convertToFramerate(getRate()); + + return Timestamp(0, _parentTrack->editList[_curEdit].timeOffset, _decoder->_timeScale).convertToFramerate(getRate()) + + _curMediaPos - Timestamp(0, _parentTrack->editList[_curEdit].mediaTime, _parentTrack->timeScale).convertToFramerate(getRate()); +} + +void QuickTimeAudioDecoder::QuickTimeAudioTrack::queueRemainingAudio() { + queueAudio(getLength()); +} + +int QuickTimeAudioDecoder::QuickTimeAudioTrack::readBuffer(int16 *buffer, const int numSamples) { + int samplesRead = _queue->readBuffer(buffer, numSamples); + _samplesQueued -= samplesRead / (isStereo() ? 2 : 1); + return samplesRead; +} + +bool QuickTimeAudioDecoder::QuickTimeAudioTrack::allDataRead() const { + return _curEdit == _parentTrack->editCount; +} + +bool QuickTimeAudioDecoder::QuickTimeAudioTrack::endOfData() const { + return allDataRead() && _queue->endOfData(); +} + +bool QuickTimeAudioDecoder::QuickTimeAudioTrack::seek(const Timestamp &where) { + // Recreate the queue + delete _queue; + _queue = createStream(); + _samplesQueued = 0; + + if (where > getLength()) { + // We're done + _curEdit = _parentTrack->editCount; + return true; + } + + // Find where we are in the stream + findEdit(where); + + // Now queue up some audio and skip whatever we need to skip + Timestamp samplesToSkip = where.convertToFramerate(getRate()) - getCurrentTrackTime(); + queueAudio(); + if (_parentTrack->editList[_curEdit].mediaTime != -1) + skipSamples(samplesToSkip, _queue); + + return true; +} + +Timestamp QuickTimeAudioDecoder::QuickTimeAudioTrack::getLength() const { + return Timestamp(0, _parentTrack->duration, _decoder->_timeScale); +} + +QueuingAudioStream *QuickTimeAudioDecoder::QuickTimeAudioTrack::createStream() const { + AudioSampleDesc *entry = (AudioSampleDesc *)_parentTrack->sampleDescs[0]; + return makeQueuingAudioStream(entry->_sampleRate, entry->_channels == 2); +} + +bool QuickTimeAudioDecoder::QuickTimeAudioTrack::isOldDemuxing() const { + return _parentTrack->timeToSampleCount == 1 && _parentTrack->timeToSample[0].duration == 1; } -void QuickTimeAudioDecoder::queueNextAudioChunk() { - AudioSampleDesc *entry = (AudioSampleDesc *)_tracks[_audioTrackIndex]->sampleDescs[0]; +AudioStream *QuickTimeAudioDecoder::QuickTimeAudioTrack::readAudioChunk(uint chunk) { + AudioSampleDesc *entry = (AudioSampleDesc *)_parentTrack->sampleDescs[0]; Common::MemoryWriteStreamDynamic *wStream = new Common::MemoryWriteStreamDynamic(); - _fd->seek(_tracks[_audioTrackIndex]->chunkOffsets[_curAudioChunk]); + _decoder->_fd->seek(_parentTrack->chunkOffsets[chunk]); // First, we have to get the sample count - uint32 sampleCount = entry->getAudioChunkSampleCount(_curAudioChunk); - assert(sampleCount); + uint32 sampleCount = getAudioChunkSampleCount(chunk); + assert(sampleCount != 0); if (isOldDemuxing()) { // Old-style audio demuxing @@ -176,12 +355,12 @@ void QuickTimeAudioDecoder::queueNextAudioChunk() { size = (samples / entry->_samplesPerFrame) * entry->_bytesPerFrame; } else { samples = MIN<uint32>(1024, sampleCount); - size = samples * _tracks[_audioTrackIndex]->sampleSize; + size = samples * _parentTrack->sampleSize; } // Now, we read in the data for this data and output it byte *data = (byte *)malloc(size); - _fd->read(data, size); + _decoder->_fd->read(data, size); wStream->write(data, size); free(data); sampleCount -= samples; @@ -191,41 +370,87 @@ void QuickTimeAudioDecoder::queueNextAudioChunk() { // Find our starting sample uint32 startSample = 0; - for (uint32 i = 0; i < _curAudioChunk; i++) - startSample += entry->getAudioChunkSampleCount(i); + for (uint32 i = 0; i < chunk; i++) + startSample += getAudioChunkSampleCount(i); for (uint32 i = 0; i < sampleCount; i++) { - uint32 size = (_tracks[_audioTrackIndex]->sampleSize != 0) ? _tracks[_audioTrackIndex]->sampleSize : _tracks[_audioTrackIndex]->sampleSizes[i + startSample]; + uint32 size = (_parentTrack->sampleSize != 0) ? _parentTrack->sampleSize : _parentTrack->sampleSizes[i + startSample]; // Now, we read in the data for this data and output it byte *data = (byte *)malloc(size); - _fd->read(data, size); + _decoder->_fd->read(data, size); wStream->write(data, size); free(data); } } - // Now queue the buffer - _audStream->queueAudioStream(entry->createAudioStream(new Common::MemoryReadStream(wStream->getData(), wStream->size(), DisposeAfterUse::YES))); + AudioStream *audioStream = entry->createAudioStream(new Common::MemoryReadStream(wStream->getData(), wStream->size(), DisposeAfterUse::YES)); delete wStream; - _curAudioChunk++; + return audioStream; } -void QuickTimeAudioDecoder::setAudioStreamPos(const Timestamp &where) { - if (!_audStream) +void QuickTimeAudioDecoder::QuickTimeAudioTrack::skipSamples(const Timestamp &length, AudioStream *stream) { + uint32 sampleCount = length.convertToFramerate(getRate()).totalNumberOfFrames(); + + if (sampleCount == 0) return; - // Re-create the audio stream - delete _audStream; - Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_tracks[_audioTrackIndex]->sampleDescs[0]; - _audStream = Audio::makeQueuingAudioStream(entry->_sampleRate, entry->_channels == 2); + if (isStereo()) + sampleCount *= 2; + + int16 *tempBuffer = new int16[sampleCount]; + uint32 result = stream->readBuffer(tempBuffer, sampleCount); + delete[] tempBuffer; + + // If this is the queue, make sure we subtract this number from the + // amount queued + if (stream == _queue) + _samplesQueued -= result / (isStereo() ? 2 : 1); +} + +void QuickTimeAudioDecoder::QuickTimeAudioTrack::findEdit(const Timestamp &position) { + for (_curEdit = 0; _curEdit < _parentTrack->editCount && position < Timestamp(0, _parentTrack->editList[_curEdit].timeOffset, _decoder->_timeScale); _curEdit++) + ; + + enterNewEdit(position); +} + +void QuickTimeAudioDecoder::QuickTimeAudioTrack::enterNewEdit(const Timestamp &position) { + _skipSamples = Timestamp(); // make sure our skip variable doesn't remain around + + // If we're at the end of the edit list, there's nothing else for us to do here + if (allDataRead()) + return; + + // For an empty edit, we may need to adjust the start time + if (_parentTrack->editList[_curEdit].mediaTime == -1) { + // Just invalidate the current media position (and make sure the scale + // is in terms of our rate so it simplifies things later) + _curMediaPos = Timestamp(0, 0, getRate()); + + // Also handle shortening of the empty edit if needed + if (position != Timestamp()) + _skipSamples = position.convertToFramerate(_decoder->_timeScale) - Timestamp(0, _parentTrack->editList[_curEdit].timeOffset, _decoder->_timeScale); + return; + } + + // I really hope I never need to implement this :P + // But, I'll throw in this error just to make sure I catch anything with this... + if (_parentTrack->editList[_curEdit].mediaRate != 1) + error("Unhandled QuickTime audio rate change"); // Reinitialize the codec - entry->initCodec(); + ((AudioSampleDesc *)_parentTrack->sampleDescs[0])->initCodec(); + _skipAACPrimer = true; // First, we need to track down what audio sample we need - Audio::Timestamp curAudioTime = where.convertToFramerate(_tracks[_audioTrackIndex]->timeScale); + // Convert our variables from the media time (position) and the edit time (based on position) + // and the media time + Timestamp curAudioTime = Timestamp(0, _parentTrack->editList[_curEdit].mediaTime, _parentTrack->timeScale) + + position.convertToFramerate(_parentTrack->timeScale) + - Timestamp(0, _parentTrack->editList[_curEdit].timeOffset, _decoder->_timeScale).convertToFramerate(_parentTrack->timeScale); + uint32 sample = curAudioTime.totalNumberOfFrames(); uint32 seekSample = sample; @@ -236,24 +461,24 @@ void QuickTimeAudioDecoder::setAudioStreamPos(const Timestamp &where) { uint32 curSample = 0; seekSample = 0; - for (int32 i = 0; i < _tracks[_audioTrackIndex]->timeToSampleCount; i++) { - uint32 sampleCount = _tracks[_audioTrackIndex]->timeToSample[i].count * _tracks[_audioTrackIndex]->timeToSample[i].duration; + for (int32 i = 0; i < _parentTrack->timeToSampleCount; i++) { + uint32 sampleCount = _parentTrack->timeToSample[i].count * _parentTrack->timeToSample[i].duration; if (sample < curSample + sampleCount) { - seekSample += (sample - curSample) / _tracks[_audioTrackIndex]->timeToSample[i].duration; + seekSample += (sample - curSample) / _parentTrack->timeToSample[i].duration; break; } - seekSample += _tracks[_audioTrackIndex]->timeToSample[i].count; + seekSample += _parentTrack->timeToSample[i].count; curSample += sampleCount; } } // Now to track down what chunk it's in uint32 totalSamples = 0; - _curAudioChunk = 0; - for (uint32 i = 0; i < _tracks[_audioTrackIndex]->chunkCount; i++, _curAudioChunk++) { - uint32 chunkSampleCount = entry->getAudioChunkSampleCount(i); + _curChunk = 0; + for (uint32 i = 0; i < _parentTrack->chunkCount; i++, _curChunk++) { + uint32 chunkSampleCount = getAudioChunkSampleCount(i); if (seekSample < totalSamples + chunkSampleCount) break; @@ -261,17 +486,68 @@ void QuickTimeAudioDecoder::setAudioStreamPos(const Timestamp &where) { totalSamples += chunkSampleCount; } - // Reposition the audio stream - queueNextAudioChunk(); - if (sample != totalSamples) { - // HACK: Skip a certain amount of samples from the stream - // (There's got to be a better way to do this!) - int skipSamples = (sample - totalSamples) * entry->_channels; + // Now we get to have fun and convert *back* to an actual time + // We don't want the sample count to be modified at this point, though + if (!isOldDemuxing()) + totalSamples = getAACSampleTime(totalSamples); + + _curMediaPos = Timestamp(0, totalSamples, getRate()); +} + +void QuickTimeAudioDecoder::QuickTimeAudioTrack::queueStream(AudioStream *stream, const Timestamp &length) { + _queue->queueAudioStream(stream, DisposeAfterUse::YES); + _samplesQueued += length.convertToFramerate(getRate()).totalNumberOfFrames(); +} + +uint32 QuickTimeAudioDecoder::QuickTimeAudioTrack::getAudioChunkSampleCount(uint chunk) const { + uint32 sampleCount = 0; + + for (uint32 i = 0; i < _parentTrack->sampleToChunkCount; i++) + if (chunk >= _parentTrack->sampleToChunk[i].first) + sampleCount = _parentTrack->sampleToChunk[i].count; + + return sampleCount; +} + +Timestamp QuickTimeAudioDecoder::QuickTimeAudioTrack::getChunkLength(uint chunk, bool skipAACPrimer) const { + uint32 chunkSampleCount = getAudioChunkSampleCount(chunk); + + if (isOldDemuxing()) + return Timestamp(0, chunkSampleCount, getRate()); + + // AAC needs some extra handling, of course + return Timestamp(0, getAACSampleTime(chunkSampleCount, skipAACPrimer), getRate()); +} + +uint32 QuickTimeAudioDecoder::QuickTimeAudioTrack::getAACSampleTime(uint32 totalSampleCount, bool skipAACPrimer) const{ + uint32 curSample = 0; + uint32 time = 0; + + for (int32 i = 0; i < _parentTrack->timeToSampleCount; i++) { + uint32 sampleCount = _parentTrack->timeToSample[i].count; + + if (totalSampleCount < curSample + sampleCount) { + time += (totalSampleCount - curSample) * _parentTrack->timeToSample[i].duration; + break; + } + + time += _parentTrack->timeToSample[i].count * _parentTrack->timeToSample[i].duration; + curSample += sampleCount; + } - int16 *tempBuffer = new int16[skipSamples]; - _audStream->readBuffer(tempBuffer, skipSamples); - delete[] tempBuffer; + // The first chunk of AAC contains "duration" samples that are used as a primer + // We need to subtract that number from the duration for the first chunk. See: + // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFAppenG/QTFFAppenG.html#//apple_ref/doc/uid/TP40000939-CH2-SW1 + // The skipping of both the primer and the remainder are handled by the AAC code, + // whereas the timing of the remainder are handled by this time-to-sample chunk + // code already. + // We have to do this after each time we reinitialize the codec + if (skipAACPrimer) { + assert(_parentTrack->timeToSampleCount > 0); + time -= _parentTrack->timeToSample[0].duration; } + + return time; } QuickTimeAudioDecoder::AudioSampleDesc::AudioSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) : Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) { @@ -312,22 +588,13 @@ bool QuickTimeAudioDecoder::AudioSampleDesc::isAudioCodecSupported() const { break; } warning("No MPEG-4 audio (%s) support", audioType.c_str()); - } else + } else { warning("Audio Codec Not Supported: \'%s\'", tag2str(_codecTag)); + } return false; } -uint32 QuickTimeAudioDecoder::AudioSampleDesc::getAudioChunkSampleCount(uint chunk) const { - uint32 sampleCount = 0; - - for (uint32 j = 0; j < _parentTrack->sampleToChunkCount; j++) - if (chunk >= _parentTrack->sampleToChunk[j].first) - sampleCount = _parentTrack->sampleToChunk[j].count; - - return sampleCount; -} - AudioStream *QuickTimeAudioDecoder::AudioSampleDesc::createAudioStream(Common::SeekableReadStream *stream) const { if (!stream) return 0; @@ -381,7 +648,7 @@ void QuickTimeAudioDecoder::AudioSampleDesc::initCodec() { } /** - * A wrapper around QuickTimeAudioDecoder that implements the RewindableAudioStream API + * A wrapper around QuickTimeAudioDecoder that implements the SeekableAudioStream API */ class QuickTimeAudioStream : public SeekableAudioStream, public QuickTimeAudioDecoder { public: @@ -389,11 +656,11 @@ public: ~QuickTimeAudioStream() {} bool openFromFile(const Common::String &filename) { - return QuickTimeAudioDecoder::loadAudioFile(filename) && _audioTrackIndex >= 0 && _audStream; + return QuickTimeAudioDecoder::loadAudioFile(filename) && !_audioTracks.empty(); } bool openFromStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle) { - return QuickTimeAudioDecoder::loadAudioStream(stream, disposeFileHandle) && _audioTrackIndex >= 0 && _audStream; + return QuickTimeAudioDecoder::loadAudioStream(stream, disposeFileHandle) && !_audioTracks.empty(); } // AudioStream API @@ -401,33 +668,21 @@ public: int samples = 0; while (samples < numSamples && !endOfData()) { - if (_audStream->numQueuedStreams() == 0) - queueNextAudioChunk(); - - samples += _audStream->readBuffer(buffer + samples, numSamples - samples); + if (!_audioTracks[0]->hasDataInQueue()) + _audioTracks[0]->queueAudio(); + samples += _audioTracks[0]->readBuffer(buffer + samples, numSamples - samples); } return samples; } - bool isStereo() const { return _audStream->isStereo(); } - int getRate() const { return _audStream->getRate(); } - bool endOfData() const { return _curAudioChunk >= _tracks[_audioTrackIndex]->chunkCount && _audStream->endOfData(); } + bool isStereo() const { return _audioTracks[0]->isStereo(); } + int getRate() const { return _audioTracks[0]->getRate(); } + bool endOfData() const { return _audioTracks[0]->endOfData(); } // SeekableAudioStream API - bool seek(const Timestamp &where) { - if (where > getLength()) - return false; - - setAudioStreamPos(where); - return true; - } - - Timestamp getLength() const { - // TODO: Switch to the other one when audio edits are supported - //return Timestamp(0, _tracks[_audioTrackIndex]->duration, _timeScale); - return Timestamp(0, _tracks[_audioTrackIndex]->mediaDuration, _tracks[_audioTrackIndex]->timeScale); - } + bool seek(const Timestamp &where) { return _audioTracks[0]->seek(where); } + Timestamp getLength() const { return _audioTracks[0]->getLength(); } }; SeekableAudioStream *makeQuickTimeStream(const Common::String &filename) { diff --git a/audio/decoders/quicktime_intern.h b/audio/decoders/quicktime_intern.h index e31a1d3872..efc97cbd13 100644 --- a/audio/decoders/quicktime_intern.h +++ b/audio/decoders/quicktime_intern.h @@ -34,6 +34,8 @@ #include "common/scummsys.h" #include "common/types.h" +#include "audio/audiostream.h" + namespace Common { class SeekableReadStream; class String; @@ -41,9 +43,7 @@ namespace Common { namespace Audio { -class AudioStream; class Codec; -class QueuingAudioStream; class QuickTimeAudioDecoder : public Common::QuickTimeParser { public: @@ -63,13 +63,59 @@ public: bool loadAudioStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeFileHandle); protected: + class QuickTimeAudioTrack : public SeekableAudioStream { + public: + QuickTimeAudioTrack(QuickTimeAudioDecoder *decoder, Track *parentTrack); + ~QuickTimeAudioTrack(); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return _queue->isStereo(); } + int getRate() const { return _queue->getRate(); } + bool endOfData() const; + + // SeekableAudioStream API + bool seek(const Timestamp &where); + Timestamp getLength() const; + + // Queue *at least* "length" audio + // If length is zero, it queues the next logical block of audio whether + // that be a whole edit or just one chunk within an edit + void queueAudio(const Timestamp &length = Timestamp()); + Track *getParent() const { return _parentTrack; } + void queueRemainingAudio(); + bool hasDataInQueue() const { return _samplesQueued != 0; } + + private: + QuickTimeAudioDecoder *_decoder; + Track *_parentTrack; + QueuingAudioStream *_queue; + uint _curChunk; + Timestamp _curMediaPos, _skipSamples; + uint32 _curEdit, _samplesQueued; + bool _skipAACPrimer; + + QueuingAudioStream *createStream() const; + AudioStream *readAudioChunk(uint chunk); + bool isOldDemuxing() const; + void skipSamples(const Timestamp &length, AudioStream *stream); + void findEdit(const Timestamp &position); + bool allDataRead() const; + void enterNewEdit(const Timestamp &position); + void queueStream(AudioStream *stream, const Timestamp &length); + uint32 getAudioChunkSampleCount(uint chunk) const; + Timestamp getChunkLength(uint chunk, bool skipAACPrimer = false) const; + uint32 getAACSampleTime(uint32 totalSampleCount, bool skipAACPrimer = false) const; + Timestamp getCurrentTrackTime() const; + }; + 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(); @@ -80,6 +126,7 @@ protected: uint32 _samplesPerFrame; uint32 _bytesPerFrame; + private: Codec *_codec; }; @@ -87,13 +134,8 @@ protected: virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format); void init(); - void setAudioStreamPos(const Timestamp &where); - bool isOldDemuxing() const; - void queueNextAudioChunk(); - int _audioTrackIndex; - uint _curAudioChunk; - QueuingAudioStream *_audStream; + Common::Array<QuickTimeAudioTrack *> _audioTracks; }; } // End of namespace Audio |