scummvm/video/avi_decoder.cpp
Filippos Karapetis 866961bde9 VIDEO: Hook some more of our ADPCM decoder variants to our AVI video decoder
Information for the AVI audio track format IDs has been taken from libav.
Thanks to clone2727 for his great help on this.
2013-01-26 03:43:15 +02:00

486 lines
15 KiB
C++

/* 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.
*
*/
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "video/avi_decoder.h"
// Audio Codecs
#include "audio/decoders/adpcm.h"
#include "audio/decoders/raw.h"
// Video Codecs
#include "video/codecs/cinepak.h"
#include "video/codecs/indeo3.h"
#include "video/codecs/msvideo1.h"
#include "video/codecs/msrle.h"
#include "video/codecs/truemotion1.h"
namespace Video {
#define UNKNOWN_HEADER(a) error("Unknown header found -- \'%s\'", tag2str(a))
// IDs used throughout the AVI files
// that will be handled by this player
#define ID_RIFF MKTAG('R','I','F','F')
#define ID_AVI MKTAG('A','V','I',' ')
#define ID_LIST MKTAG('L','I','S','T')
#define ID_HDRL MKTAG('h','d','r','l')
#define ID_AVIH MKTAG('a','v','i','h')
#define ID_STRL MKTAG('s','t','r','l')
#define ID_STRH MKTAG('s','t','r','h')
#define ID_VIDS MKTAG('v','i','d','s')
#define ID_AUDS MKTAG('a','u','d','s')
#define ID_MIDS MKTAG('m','i','d','s')
#define ID_TXTS MKTAG('t','x','t','s')
#define ID_JUNK MKTAG('J','U','N','K')
#define ID_STRF MKTAG('s','t','r','f')
#define ID_MOVI MKTAG('m','o','v','i')
#define ID_REC MKTAG('r','e','c',' ')
#define ID_VEDT MKTAG('v','e','d','t')
#define ID_IDX1 MKTAG('i','d','x','1')
#define ID_STRD MKTAG('s','t','r','d')
#define ID_00AM MKTAG('0','0','A','M')
//#define ID_INFO MKTAG('I','N','F','O')
// Codec tags
#define ID_RLE MKTAG('R','L','E',' ')
#define ID_CRAM MKTAG('C','R','A','M')
#define ID_MSVC MKTAG('m','s','v','c')
#define ID_WHAM MKTAG('W','H','A','M')
#define ID_CVID MKTAG('c','v','i','d')
#define ID_IV32 MKTAG('i','v','3','2')
#define ID_DUCK MKTAG('D','U','C','K')
static byte char2num(char c) {
c = tolower((byte)c);
return (c >= 'a' && c <= 'f') ? c - 'a' + 10 : c - '0';
}
static byte getStreamIndex(uint32 tag) {
return char2num((tag >> 24) & 0xFF) << 4 | char2num((tag >> 16) & 0xFF);
}
static uint16 getStreamType(uint32 tag) {
return tag & 0xffff;
}
AVIDecoder::AVIDecoder(Audio::Mixer::SoundType soundType) : _soundType(soundType) {
_decodedHeader = false;
_fileStream = 0;
memset(&_ixInfo, 0, sizeof(_ixInfo));
memset(&_header, 0, sizeof(_header));
}
AVIDecoder::~AVIDecoder() {
close();
}
void AVIDecoder::runHandle(uint32 tag) {
assert(_fileStream);
if (_fileStream->eos())
return;
debug(3, "Decoding tag %s", tag2str(tag));
switch (tag) {
case ID_RIFF:
/*_filesize = */_fileStream->readUint32LE();
if (_fileStream->readUint32BE() != ID_AVI)
error("RIFF file is not an AVI video");
break;
case ID_LIST:
handleList();
break;
case ID_AVIH:
_header.size = _fileStream->readUint32LE();
_header.microSecondsPerFrame = _fileStream->readUint32LE();
_header.maxBytesPerSecond = _fileStream->readUint32LE();
_header.padding = _fileStream->readUint32LE();
_header.flags = _fileStream->readUint32LE();
_header.totalFrames = _fileStream->readUint32LE();
_header.initialFrames = _fileStream->readUint32LE();
_header.streams = _fileStream->readUint32LE();
_header.bufferSize = _fileStream->readUint32LE();
_header.width = _fileStream->readUint32LE();
_header.height = _fileStream->readUint32LE();
// Ignore 16 bytes of reserved data
_fileStream->skip(16);
break;
case ID_STRH:
handleStreamHeader();
break;
case ID_STRD: // Extra stream info, safe to ignore
case ID_VEDT: // Unknown, safe to ignore
case ID_JUNK: // Alignment bytes, should be ignored
{
uint32 junkSize = _fileStream->readUint32LE();
_fileStream->skip(junkSize + (junkSize & 1)); // Alignment
} break;
case ID_IDX1:
_ixInfo.size = _fileStream->readUint32LE();
_ixInfo.indices = new OldIndex::Index[_ixInfo.size / 16];
debug(0, "%d Indices", (_ixInfo.size / 16));
for (uint32 i = 0; i < (_ixInfo.size / 16); i++) {
_ixInfo.indices[i].id = _fileStream->readUint32BE();
_ixInfo.indices[i].flags = _fileStream->readUint32LE();
_ixInfo.indices[i].offset = _fileStream->readUint32LE();
_ixInfo.indices[i].size = _fileStream->readUint32LE();
debug(0, "Index %d == Tag \'%s\', Offset = %d, Size = %d", i, tag2str(_ixInfo.indices[i].id), _ixInfo.indices[i].offset, _ixInfo.indices[i].size);
}
break;
default:
error("Unknown tag \'%s\' found", tag2str(tag));
}
}
void AVIDecoder::handleList() {
uint32 listSize = _fileStream->readUint32LE() - 4; // Subtract away listType's 4 bytes
uint32 listType = _fileStream->readUint32BE();
uint32 curPos = _fileStream->pos();
debug(0, "Found LIST of type %s", tag2str(listType));
while ((_fileStream->pos() - curPos) < listSize)
runHandle(_fileStream->readUint32BE());
// We now have all the header data
if (listType == ID_HDRL)
_decodedHeader = true;
}
void AVIDecoder::handleStreamHeader() {
AVIStreamHeader sHeader;
sHeader.size = _fileStream->readUint32LE();
sHeader.streamType = _fileStream->readUint32BE();
if (sHeader.streamType == ID_MIDS || sHeader.streamType == ID_TXTS)
error("Unhandled MIDI/Text stream");
sHeader.streamHandler = _fileStream->readUint32BE();
sHeader.flags = _fileStream->readUint32LE();
sHeader.priority = _fileStream->readUint16LE();
sHeader.language = _fileStream->readUint16LE();
sHeader.initialFrames = _fileStream->readUint32LE();
sHeader.scale = _fileStream->readUint32LE();
sHeader.rate = _fileStream->readUint32LE();
sHeader.start = _fileStream->readUint32LE();
sHeader.length = _fileStream->readUint32LE();
sHeader.bufferSize = _fileStream->readUint32LE();
sHeader.quality = _fileStream->readUint32LE();
sHeader.sampleSize = _fileStream->readUint32LE();
_fileStream->skip(sHeader.size - 48); // Skip over the remainder of the chunk (frame)
if (_fileStream->readUint32BE() != ID_STRF)
error("Could not find STRF tag");
uint32 strfSize = _fileStream->readUint32LE();
uint32 startPos = _fileStream->pos();
if (sHeader.streamType == ID_VIDS) {
BitmapInfoHeader bmInfo;
bmInfo.size = _fileStream->readUint32LE();
bmInfo.width = _fileStream->readUint32LE();
bmInfo.height = _fileStream->readUint32LE();
bmInfo.planes = _fileStream->readUint16LE();
bmInfo.bitCount = _fileStream->readUint16LE();
bmInfo.compression = _fileStream->readUint32BE();
bmInfo.sizeImage = _fileStream->readUint32LE();
bmInfo.xPelsPerMeter = _fileStream->readUint32LE();
bmInfo.yPelsPerMeter = _fileStream->readUint32LE();
bmInfo.clrUsed = _fileStream->readUint32LE();
bmInfo.clrImportant = _fileStream->readUint32LE();
if (bmInfo.clrUsed == 0)
bmInfo.clrUsed = 256;
if (sHeader.streamHandler == 0)
sHeader.streamHandler = bmInfo.compression;
AVIVideoTrack *track = new AVIVideoTrack(_header.totalFrames, sHeader, bmInfo);
if (bmInfo.bitCount == 8) {
byte *palette = const_cast<byte *>(track->getPalette());
for (uint32 i = 0; i < bmInfo.clrUsed; i++) {
palette[i * 3 + 2] = _fileStream->readByte();
palette[i * 3 + 1] = _fileStream->readByte();
palette[i * 3] = _fileStream->readByte();
_fileStream->readByte();
}
track->markPaletteDirty();
}
addTrack(track);
} else if (sHeader.streamType == ID_AUDS) {
PCMWaveFormat wvInfo;
wvInfo.tag = _fileStream->readUint16LE();
wvInfo.channels = _fileStream->readUint16LE();
wvInfo.samplesPerSec = _fileStream->readUint32LE();
wvInfo.avgBytesPerSec = _fileStream->readUint32LE();
wvInfo.blockAlign = _fileStream->readUint16LE();
wvInfo.size = _fileStream->readUint16LE();
// AVI seems to treat the sampleSize as including the second
// channel as well, so divide for our sake.
if (wvInfo.channels == 2)
sHeader.sampleSize /= 2;
addTrack(new AVIAudioTrack(sHeader, wvInfo, _soundType));
}
// Ensure that we're at the end of the chunk
_fileStream->seek(startPos + strfSize);
}
bool AVIDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
_fileStream = stream;
_decodedHeader = false;
// Read chunks until we have decoded the header
while (!_decodedHeader)
runHandle(_fileStream->readUint32BE());
uint32 nextTag = _fileStream->readUint32BE();
// Throw out any JUNK section
if (nextTag == ID_JUNK) {
runHandle(ID_JUNK);
nextTag = _fileStream->readUint32BE();
}
// Ignore the 'movi' LIST
if (nextTag == ID_LIST) {
_fileStream->readUint32BE(); // Skip size
if (_fileStream->readUint32BE() != ID_MOVI)
error("Expected 'movi' LIST");
} else {
error("Expected 'movi' LIST");
}
return true;
}
void AVIDecoder::close() {
VideoDecoder::close();
delete _fileStream;
_fileStream = 0;
_decodedHeader = false;
delete[] _ixInfo.indices;
memset(&_ixInfo, 0, sizeof(_ixInfo));
memset(&_header, 0, sizeof(_header));
}
void AVIDecoder::readNextPacket() {
uint32 nextTag = _fileStream->readUint32BE();
if (_fileStream->eos())
return;
if (nextTag == ID_LIST) {
// A list of audio/video chunks
uint32 listSize = _fileStream->readUint32LE() - 4;
int32 startPos = _fileStream->pos();
if (_fileStream->readUint32BE() != ID_REC)
error("Expected 'rec ' LIST");
// Decode chunks in the list
while (_fileStream->pos() < startPos + (int32)listSize)
readNextPacket();
return;
} else if (nextTag == ID_JUNK || nextTag == ID_IDX1) {
runHandle(nextTag);
return;
}
Track *track = getTrack(getStreamIndex(nextTag));
if (!track)
error("Cannot get track from tag '%s'", tag2str(nextTag));
uint32 chunkSize = _fileStream->readUint32LE();
Common::SeekableReadStream *chunk = 0;
if (chunkSize != 0) {
chunk = _fileStream->readStream(chunkSize);
_fileStream->skip(chunkSize & 1);
}
if (track->getTrackType() == Track::kTrackTypeAudio) {
if (getStreamType(nextTag) != MKTAG16('w', 'b'))
error("Invalid audio track tag '%s'", tag2str(nextTag));
assert(chunk);
((AVIAudioTrack *)track)->queueSound(chunk);
} else {
AVIVideoTrack *videoTrack = (AVIVideoTrack *)track;
if (getStreamType(nextTag) == MKTAG16('p', 'c')) {
// Palette Change
assert(chunk);
byte firstEntry = chunk->readByte();
uint16 numEntries = chunk->readByte();
chunk->readUint16LE(); // Reserved
// 0 entries means all colors are going to be changed
if (numEntries == 0)
numEntries = 256;
byte *palette = const_cast<byte *>(videoTrack->getPalette());
for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
palette[i * 3] = chunk->readByte();
palette[i * 3 + 1] = chunk->readByte();
palette[i * 3 + 2] = chunk->readByte();
chunk->readByte(); // Flags that don't serve us any purpose
}
delete chunk;
videoTrack->markPaletteDirty();
} else if (getStreamType(nextTag) == MKTAG16('d', 'b')) {
// TODO: Check if this really is uncompressed. Many videos
// falsely put compressed data in here.
error("Uncompressed AVI frame found");
} else {
// Otherwise, assume it's a compressed frame
videoTrack->decodeFrame(chunk);
}
}
}
AVIDecoder::AVIVideoTrack::AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader)
: _frameCount(frameCount), _vidsHeader(streamHeader), _bmInfo(bitmapInfoHeader) {
memset(_palette, 0, sizeof(_palette));
_videoCodec = createCodec();
_dirtyPalette = false;
_lastFrame = 0;
_curFrame = -1;
}
AVIDecoder::AVIVideoTrack::~AVIVideoTrack() {
delete _videoCodec;
}
void AVIDecoder::AVIVideoTrack::decodeFrame(Common::SeekableReadStream *stream) {
if (stream) {
if (_videoCodec)
_lastFrame = _videoCodec->decodeImage(stream);
} else {
// Empty frame
_lastFrame = 0;
}
delete stream;
_curFrame++;
}
Graphics::PixelFormat AVIDecoder::AVIVideoTrack::getPixelFormat() const {
if (_videoCodec)
return _videoCodec->getPixelFormat();
return Graphics::PixelFormat();
}
Codec *AVIDecoder::AVIVideoTrack::createCodec() {
switch (_vidsHeader.streamHandler) {
case ID_CRAM:
case ID_MSVC:
case ID_WHAM:
return new MSVideo1Decoder(_bmInfo.width, _bmInfo.height, _bmInfo.bitCount);
case ID_RLE:
return new MSRLEDecoder(_bmInfo.width, _bmInfo.height, _bmInfo.bitCount);
case ID_CVID:
return new CinepakDecoder(_bmInfo.bitCount);
case ID_IV32:
return new Indeo3Decoder(_bmInfo.width, _bmInfo.height);
#ifdef VIDEO_CODECS_TRUEMOTION1_H
case ID_DUCK:
return new TrueMotion1Decoder(_bmInfo.width, _bmInfo.height);
#endif
default:
warning("Unknown/Unhandled compression format \'%s\'", tag2str(_vidsHeader.streamHandler));
}
return 0;
}
AVIDecoder::AVIAudioTrack::AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType)
: _audsHeader(streamHeader), _wvInfo(waveFormat), _soundType(soundType) {
_audStream = createAudioStream();
}
AVIDecoder::AVIAudioTrack::~AVIAudioTrack() {
delete _audStream;
}
void AVIDecoder::AVIAudioTrack::queueSound(Common::SeekableReadStream *stream) {
if (_audStream) {
if (_wvInfo.tag == kWaveFormatPCM) {
byte flags = 0;
if (_audsHeader.sampleSize == 2)
flags |= Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
else
flags |= Audio::FLAG_UNSIGNED;
if (_wvInfo.channels == 2)
flags |= Audio::FLAG_STEREO;
_audStream->queueAudioStream(Audio::makeRawStream(stream, _wvInfo.samplesPerSec, flags, DisposeAfterUse::YES), DisposeAfterUse::YES);
} else if (_wvInfo.tag == kWaveFormatMSADPCM) {
_audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMMS, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES);
} else if (_wvInfo.tag == kWaveFormatMSIMAADPCM) {
_audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMMSIma, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES);
} else if (_wvInfo.tag == kWaveFormatDK3) {
_audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMDK3, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES);
}
} else {
delete stream;
}
}
Audio::AudioStream *AVIDecoder::AVIAudioTrack::getAudioStream() const {
return _audStream;
}
Audio::QueuingAudioStream *AVIDecoder::AVIAudioTrack::createAudioStream() {
if (_wvInfo.tag == kWaveFormatPCM || _wvInfo.tag == kWaveFormatMSADPCM || _wvInfo.tag == kWaveFormatMSIMAADPCM || _wvInfo.tag == kWaveFormatDK3)
return Audio::makeQueuingAudioStream(_wvInfo.samplesPerSec, _wvInfo.channels == 2);
else if (_wvInfo.tag != kWaveFormatNone) // No sound
warning("Unsupported AVI audio format %d", _wvInfo.tag);
return 0;
}
} // End of namespace Video