scummvm/graphics/video/avi_decoder.cpp
Sven Hesse b5b47d50c8 VIDEO: Make Indeo3 a proper video codec
This way, Indeo3 is properly usable in various container VideoDecoder,
for example AVI, and not just VMD.
Since VMD is not yet ready for this, we're disabling Indeo3 in VMDs
for now.

svn-id: r51849
2010-08-08 00:33:13 +00:00

434 lines
13 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.
*
* $URL$
* $Id$
*
*/
#include "common/endian.h"
#include "common/file.h"
#include "common/stream.h"
#include "common/events.h"
#include "sound/audiostream.h"
#include "sound/mixer.h"
#include "sound/decoders/raw.h"
#include "graphics/video/avi_decoder.h"
// Codecs
#include "graphics/video/codecs/cinepak.h"
#include "graphics/video/codecs/msvideo1.h"
#include "graphics/video/codecs/msrle.h"
#include "graphics/video/codecs/indeo3.h"
namespace Graphics {
AviDecoder::AviDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : _mixer(mixer) {
_soundType = soundType;
_videoCodec = NULL;
_decodedHeader = false;
_audStream = NULL;
_fileStream = NULL;
_audHandle = new Audio::SoundHandle();
_dirtyPalette = false;
memset(_palette, 0, sizeof(_palette));
memset(&_wvInfo, 0, sizeof(PCMWAVEFORMAT));
memset(&_bmInfo, 0, sizeof(BITMAPINFOHEADER));
memset(&_vidsHeader, 0, sizeof(AVIStreamHeader));
memset(&_audsHeader, 0, sizeof(AVIStreamHeader));
memset(&_ixInfo, 0, sizeof(AVIOLDINDEX));
}
AviDecoder::~AviDecoder() {
close();
delete _audHandle;
}
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 AVIOLDINDEX::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();
if (sHeader.streamType == ID_VIDS) {
_vidsHeader = sHeader;
_bmInfo.size = _fileStream->readUint32LE();
_bmInfo.width = _fileStream->readUint32LE();
assert (_header.width == _bmInfo.width);
_bmInfo.height = _fileStream->readUint32LE();
assert (_header.height == _bmInfo.height);
_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.bitCount == 8) {
if (_bmInfo.clrUsed == 0)
_bmInfo.clrUsed = 256;
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();
}
_dirtyPalette = true;
}
if (!_vidsHeader.streamHandler)
_vidsHeader.streamHandler = _bmInfo.compression;
} else if (sHeader.streamType == ID_AUDS) {
_audsHeader = sHeader;
_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)
_audsHeader.sampleSize /= 2;
}
}
bool AviDecoder::load(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");
// Now, create the codec
_videoCodec = createCodec();
// Initialize the video stuff too
_audStream = createAudioStream();
if (_audStream)
_mixer->playStream(_soundType, _audHandle, _audStream);
debug (0, "Frames = %d, Dimensions = %d x %d", _header.totalFrames, _header.width, _header.height);
debug (0, "Frame Rate = %d", _vidsHeader.rate / _vidsHeader.scale);
if ((_audsHeader.scale != 0) && (_header.flags & AVIF_ISINTERLEAVED))
debug (0, "Sound Rate = %d", AUDIO_RATE);
debug (0, "Video Codec = \'%s\'", tag2str(_vidsHeader.streamHandler));
if (!_videoCodec)
return false;
return true;
}
void AviDecoder::close() {
if (!_fileStream)
return;
delete _fileStream;
_fileStream = 0;
// Deinitialize sound
_mixer->stopHandle(*_audHandle);
_audStream = 0;
_decodedHeader = false;
delete _videoCodec;
_videoCodec = 0;
delete[] _ixInfo.indices;
_ixInfo.indices = 0;
memset(_palette, 0, sizeof(_palette));
memset(&_wvInfo, 0, sizeof(PCMWAVEFORMAT));
memset(&_bmInfo, 0, sizeof(BITMAPINFOHEADER));
memset(&_vidsHeader, 0, sizeof(AVIStreamHeader));
memset(&_audsHeader, 0, sizeof(AVIStreamHeader));
memset(&_ixInfo, 0, sizeof(AVIOLDINDEX));
reset();
}
uint32 AviDecoder::getElapsedTime() const {
if (_audStream)
return _mixer->getSoundElapsedTime(*_audHandle);
return VideoDecoder::getElapsedTime();
}
Surface *AviDecoder::decodeNextFrame() {
uint32 nextTag = _fileStream->readUint32BE();
if (_fileStream->eos())
return NULL;
if (_curFrame == -1)
_startTime = g_system->getMillis();
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 and see if we get a frame
Surface *frame = NULL;
while (_fileStream->pos() < startPos + (int32)listSize) {
Surface *temp = decodeNextFrame();
if (temp)
frame = temp;
}
return frame;
} else if (getStreamType(nextTag) == 'wb') {
// Audio Chunk
uint32 chunkSize = _fileStream->readUint32LE();
byte *data = (byte *)malloc(chunkSize);
_fileStream->read(data, chunkSize);
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->queueBuffer(data, chunkSize, DisposeAfterUse::YES, flags);
_fileStream->skip(chunkSize & 1); // Alignment
} else if (getStreamType(nextTag) == 'dc' || getStreamType(nextTag) == 'id' ||
getStreamType(nextTag) == 'AM' || getStreamType(nextTag) == '32') {
// Compressed Frame
_curFrame++;
uint32 chunkSize = _fileStream->readUint32LE();
if (chunkSize == 0) // Keep last frame on screen
return NULL;
Common::SeekableReadStream *frameData = _fileStream->readStream(chunkSize);
Graphics::Surface *surface = _videoCodec->decodeImage(frameData);
delete frameData;
_fileStream->skip(chunkSize & 1); // Alignment
return surface;
} else if (getStreamType(nextTag) == 'pc') {
// Palette Change
_fileStream->readUint32LE(); // Chunk size, not needed here
byte firstEntry = _fileStream->readByte();
uint16 numEntries = _fileStream->readByte();
_fileStream->readUint16LE(); // Reserved
// 0 entries means all colors are going to be changed
if (numEntries == 0)
numEntries = 256;
for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
_palette[i * 3] = _fileStream->readByte();
_palette[i * 3 + 1] = _fileStream->readByte();
_palette[i * 3 + 2] = _fileStream->readByte();
_fileStream->readByte(); // Flags that don't serve us any purpose
}
_dirtyPalette = true;
// No alignment necessary. It's always even.
} else if (nextTag == ID_JUNK) {
runHandle(ID_JUNK);
} else if (nextTag == ID_IDX1) {
runHandle(ID_IDX1);
} else
error("Tag = \'%s\', %d", tag2str(nextTag), _fileStream->pos());
return NULL;
}
Codec *AviDecoder::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();
#ifdef USE_INDEO3
case ID_IV32:
return new Indeo3Decoder(_bmInfo.width, _bmInfo.height);
#endif
default:
warning ("Unknown/Unhandled compression format \'%s\'", tag2str(_vidsHeader.streamHandler));
}
return NULL;
}
PixelFormat AviDecoder::getPixelFormat() const {
assert(_videoCodec);
return _videoCodec->getPixelFormat();
}
Audio::QueuingAudioStream *AviDecoder::createAudioStream() {
if (_wvInfo.tag == AVI_WAVE_FORMAT_PCM)
return Audio::makeQueuingAudioStream(AUDIO_RATE, _wvInfo.channels == 2);
if (_wvInfo.tag != 0) // No sound
warning ("Unsupported AVI audio format %d", _wvInfo.tag);
return NULL;
}
byte AviDecoder::char2num(char c) {
return (c >= 48 && c <= 57) ? c - 48 : 0;
}
byte AviDecoder::getStreamNum(uint32 tag) {
return char2num((char)(tag >> 24)) * 16 + char2num((char)(tag >> 16));
}
uint16 AviDecoder::getStreamType(uint32 tag) {
return tag & 0xffff;
}
} // End of namespace Graphics