diff options
Diffstat (limited to 'video')
-rw-r--r-- | video/avi_decoder.cpp | 138 | ||||
-rw-r--r-- | video/avi_decoder.h | 23 | ||||
-rw-r--r-- | video/bink_decoder.cpp | 10 | ||||
-rw-r--r-- | video/bink_decoder.h | 2 | ||||
-rw-r--r-- | video/codecs/jpeg.cpp | 66 | ||||
-rw-r--r-- | video/codecs/jpeg.h | 60 | ||||
-rw-r--r-- | video/codecs/mjpeg.cpp | 163 | ||||
-rw-r--r-- | video/codecs/mjpeg.h | 8 | ||||
-rw-r--r-- | video/codecs/mpeg.h | 2 | ||||
-rw-r--r-- | video/codecs/truemotion1.h | 4 | ||||
-rw-r--r-- | video/module.mk | 1 | ||||
-rw-r--r-- | video/qt_decoder.cpp | 4 | ||||
-rw-r--r-- | video/smk_decoder.cpp | 40 | ||||
-rw-r--r-- | video/smk_decoder.h | 2 | ||||
-rw-r--r-- | video/video_decoder.cpp | 60 | ||||
-rw-r--r-- | video/video_decoder.h | 44 |
16 files changed, 557 insertions, 70 deletions
diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp index 5e4b91d1bd..bae5b7babd 100644 --- a/video/avi_decoder.cpp +++ b/video/avi_decoder.cpp @@ -36,6 +36,7 @@ // Video Codecs #include "video/codecs/cinepak.h" #include "video/codecs/indeo3.h" +#include "video/codecs/mjpeg.h" #include "video/codecs/mpeg.h" #include "video/codecs/msvideo1.h" #include "video/codecs/msrle.h" @@ -59,6 +60,8 @@ namespace Video { #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_JUNQ MKTAG('J','U','N','Q') +#define ID_DMLH MKTAG('d','m','l','h') #define ID_STRF MKTAG('s','t','r','f') #define ID_MOVI MKTAG('m','o','v','i') #define ID_REC MKTAG('r','e','c',' ') @@ -69,6 +72,7 @@ namespace Video { #define ID_ISFT MKTAG('I','S','F','T') #define ID_DISP MKTAG('D','I','S','P') #define ID_PRMI MKTAG('P','R','M','I') +#define ID_STRN MKTAG('s','t','r','n') // Codec tags #define ID_RLE MKTAG('R','L','E',' ') @@ -79,6 +83,7 @@ namespace Video { #define ID_IV32 MKTAG('i','v','3','2') #define ID_DUCK MKTAG('D','U','C','K') #define ID_MPG2 MKTAG('m','p','g','2') +#define ID_MJPG MKTAG('m','j','p','g') // Stream Types enum { @@ -101,10 +106,15 @@ AVIDecoder::~AVIDecoder() { close(); } +AVIDecoder::AVIAudioTrack *AVIDecoder::createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo) { + return new AVIAudioTrack(sHeader, wvInfo, _soundType); +} + void AVIDecoder::initCommon() { _decodedHeader = false; _foundMovieList = false; _movieListStart = 0; + _movieListEnd = 0; _fileStream = 0; memset(&_header, 0, sizeof(_header)); } @@ -149,21 +159,15 @@ bool AVIDecoder::parseNextChunk() { case ID_STRD: // Extra stream info, safe to ignore case ID_VEDT: // Unknown, safe to ignore case ID_JUNK: // Alignment bytes, should be ignored + case ID_JUNQ: // Same as JUNK, safe to ignore case ID_ISFT: // Metadata, safe to ignore case ID_DISP: // Metadata, should be safe to ignore + case ID_STRN: // Metadata, safe to ignore + case ID_DMLH: // OpenDML extension, contains an extra total frames field, safe to ignore skipChunk(size); break; case ID_IDX1: - debug(0, "%d Indices", size / 16); - for (uint32 i = 0; i < size / 16; i++) { - OldIndex indexEntry; - indexEntry.id = _fileStream->readUint32BE(); - indexEntry.flags = _fileStream->readUint32LE(); - indexEntry.offset = _fileStream->readUint32LE() + _movieListStart - 4; // Adjust to absolute - indexEntry.size = _fileStream->readUint32LE(); - _indexEntries.push_back(indexEntry); - debug(0, "Index %d == Tag \'%s\', Offset = %d, Size = %d (Flags = %d)", i, tag2str(indexEntry.id), indexEntry.offset, indexEntry.size, indexEntry.flags); - } + readOldIndex(size); break; default: error("Unknown tag \'%s\' found", tag2str(tag)); @@ -189,6 +193,7 @@ void AVIDecoder::handleList(uint32 listSize) { // We found the movie block _foundMovieList = true; _movieListStart = curPos; + _movieListEnd = _movieListStart + listSize + (listSize & 1); _fileStream->skip(listSize); return; case ID_HDRL: // Header List @@ -293,7 +298,7 @@ void AVIDecoder::handleStreamHeader(uint32 size) { if (wvInfo.channels == 2) sHeader.sampleSize /= 2; - addTrack(new AVIAudioTrack(sHeader, wvInfo, _soundType)); + addTrack(createAudioTrack(sHeader, wvInfo)); } // Ensure that we're at the end of the chunk @@ -349,17 +354,27 @@ void AVIDecoder::close() { _decodedHeader = false; _foundMovieList = false; _movieListStart = 0; + _movieListEnd = 0; _indexEntries.clear(); memset(&_header, 0, sizeof(_header)); } void AVIDecoder::readNextPacket() { + if ((uint32)_fileStream->pos() >= _movieListEnd) { + // Ugh, reached the end premature. + forceVideoEnd(); + return; + } + uint32 nextTag = _fileStream->readUint32BE(); uint32 size = _fileStream->readUint32LE(); - if (_fileStream->eos()) + if (_fileStream->eos()) { + // Also premature end. + forceVideoEnd(); return; + } if (nextTag == ID_LIST) { // A list of audio/video chunks @@ -428,12 +443,10 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { if (time > getDuration()) return false; - // Track down our video track (optionally audio too). - // We only support seeking with one track right now. + // Track down our video track. + // We only support seeking with one video track right now. AVIVideoTrack *videoTrack = 0; - AVIAudioTrack *audioTrack = 0; int videoIndex = -1; - int audioIndex = -1; uint trackID = 0; for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++, trackID++) { @@ -446,15 +459,6 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { videoTrack = (AVIVideoTrack *)*it; videoIndex = trackID; - } else if ((*it)->getTrackType() == Track::kTrackTypeAudio) { - if (audioTrack) { - // Already have one - // -> Not supported - return false; - } - - audioTrack = (AVIAudioTrack *)*it; - audioIndex = trackID; } } @@ -467,8 +471,9 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { if (time == getDuration()) { videoTrack->setCurFrame(videoTrack->getFrameCount() - 1); - if (audioTrack) - audioTrack->resetStream(); + for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++) + if ((*it)->getTrackType() == Track::kTrackTypeAudio) + ((AVIAudioTrack *)*it)->resetStream(); return true; } @@ -529,7 +534,15 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { if (frameIndex < 0) // This shouldn't happen. return false; - if (audioTrack) { + // Update all the audio tracks + uint audioIndex = 0; + + for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++, audioIndex++) { + if ((*it)->getTrackType() != Track::kTrackTypeAudio) + continue; + + AVIAudioTrack *audioTrack = (AVIAudioTrack *)*it; + // We need to find where the start of audio should be. // Which is exactly 'initialFrames' audio chunks back from where // our found frame is. @@ -617,6 +630,69 @@ byte AVIDecoder::getStreamIndex(uint32 tag) const { return strtol(string, 0, 16); } +void AVIDecoder::readOldIndex(uint32 size) { + uint32 entryCount = size / 16; + + debug(0, "Old Index: %d entries", entryCount); + + if (entryCount == 0) + return; + + // Read the first index separately + OldIndex firstEntry; + firstEntry.id = _fileStream->readUint32BE(); + firstEntry.flags = _fileStream->readUint32LE(); + firstEntry.offset = _fileStream->readUint32LE(); + firstEntry.size = _fileStream->readUint32LE(); + + // Check if the offset is already absolute + // If it's absolute, the offset will equal the start of the movie list + bool isAbsolute = firstEntry.offset == _movieListStart; + + debug(1, "Old index is %s", isAbsolute ? "absolute" : "relative"); + + if (!isAbsolute) + firstEntry.offset += _movieListStart - 4; + + debug(0, "Index 0: Tag '%s', Offset = %d, Size = %d (Flags = %d)", tag2str(firstEntry.id), firstEntry.offset, firstEntry.size, firstEntry.flags); + _indexEntries.push_back(firstEntry); + + for (uint32 i = 1; i < entryCount; i++) { + OldIndex indexEntry; + indexEntry.id = _fileStream->readUint32BE(); + indexEntry.flags = _fileStream->readUint32LE(); + indexEntry.offset = _fileStream->readUint32LE(); + indexEntry.size = _fileStream->readUint32LE(); + + // Adjust to absolute, if necessary + if (!isAbsolute) + indexEntry.offset += _movieListStart - 4; + + _indexEntries.push_back(indexEntry); + debug(0, "Index %d: Tag '%s', Offset = %d, Size = %d (Flags = %d)", i, tag2str(indexEntry.id), indexEntry.offset, indexEntry.size, indexEntry.flags); + } +} + +void AVIDecoder::forceVideoEnd() { + // Horrible AVI video has a premature end + // Force the frame to be the last frame + debug(0, "Forcing end of AVI video"); + + for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++) + if ((*it)->getTrackType() == Track::kTrackTypeVideo) + ((AVIVideoTrack *)*it)->forceTrackEnd(); +} + +VideoDecoder::AudioTrack *AVIDecoder::getAudioTrack(int index) { + // AVI audio track indexes are relative to the first track + Track *track = getTrack(index); + + if (!track || track->getTrackType() != Track::kTrackTypeAudio) + return 0; + + return (AudioTrack *)track; +} + AVIDecoder::AVIVideoTrack::AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader, byte *initialPalette) : _frameCount(frameCount), _vidsHeader(streamHeader), _bmInfo(bitmapInfoHeader), _initialPalette(initialPalette) { _videoCodec = createCodec(); @@ -712,6 +788,8 @@ Codec *AVIDecoder::AVIVideoTrack::createCodec() { case ID_MPG2: return new MPEGDecoder(); #endif + case ID_MJPG: + return new MJPEGDecoder(); default: warning("Unknown/Unhandled compression format \'%s\'", tag2str(_vidsHeader.streamHandler)); } @@ -719,6 +797,10 @@ Codec *AVIDecoder::AVIVideoTrack::createCodec() { return 0; } +void AVIDecoder::AVIVideoTrack::forceTrackEnd() { + _curFrame = _frameCount - 1; +} + AVIDecoder::AVIAudioTrack::AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType) : _audsHeader(streamHeader), _wvInfo(waveFormat), _soundType(soundType) { _audStream = createAudioStream(); diff --git a/video/avi_decoder.h b/video/avi_decoder.h index fffcbfbe16..811f7f82f7 100644 --- a/video/avi_decoder.h +++ b/video/avi_decoder.h @@ -54,6 +54,7 @@ class Codec; * - sci * - sword1 * - sword2 + * - zvision */ class AVIDecoder : public VideoDecoder { public: @@ -71,10 +72,12 @@ public: bool isSeekable() const; protected: - void readNextPacket(); - bool seekIntern(const Audio::Timestamp &time); + // VideoDecoder API + void readNextPacket(); + bool seekIntern(const Audio::Timestamp &time); + bool supportsAudioTrackSwitching() const { return true; } + AudioTrack *getAudioTrack(int index); -private: struct BitmapInfoHeader { uint32 size; uint32 width; @@ -166,6 +169,7 @@ private: ~AVIVideoTrack(); void decodeFrame(Common::SeekableReadStream *stream); + void forceTrackEnd(); uint16 getWidth() const { return _bmInfo.width; } uint16 getHeight() const { return _bmInfo.height; } @@ -203,7 +207,7 @@ private: AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType); ~AVIAudioTrack(); - void queueSound(Common::SeekableReadStream *stream); + virtual void queueSound(Common::SeekableReadStream *stream); Audio::Mixer::SoundType getSoundType() const { return _soundType; } void skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime); void resetStream(); @@ -214,7 +218,6 @@ private: protected: Audio::AudioStream *getAudioStream() const; - private: // Audio Codecs enum { kWaveFormatNone = 0, @@ -231,13 +234,15 @@ private: Audio::QueuingAudioStream *createAudioStream(); }; - Common::Array<OldIndex> _indexEntries; AVIHeader _header; + void readOldIndex(uint32 size); + Common::Array<OldIndex> _indexEntries; + Common::SeekableReadStream *_fileStream; bool _decodedHeader; bool _foundMovieList; - uint32 _movieListStart; + uint32 _movieListStart, _movieListEnd; Audio::Mixer::SoundType _soundType; Common::Rational _frameRateOverride; @@ -249,6 +254,10 @@ private: void handleStreamHeader(uint32 size); uint16 getStreamType(uint32 tag) const { return tag & 0xFFFF; } byte getStreamIndex(uint32 tag) const; + void forceVideoEnd(); + +public: + virtual AVIAudioTrack *createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo); }; } // End of namespace Video diff --git a/video/bink_decoder.cpp b/video/bink_decoder.cpp index 45dec0887b..bad34e8373 100644 --- a/video/bink_decoder.cpp +++ b/video/bink_decoder.cpp @@ -214,6 +214,16 @@ void BinkDecoder::readNextPacket() { frame.bits = 0; } +VideoDecoder::AudioTrack *BinkDecoder::getAudioTrack(int index) { + // Bink audio track indexes are relative to the first audio track + Track *track = getTrack(index + 1); + + if (!track || track->getTrackType() != Track::kTrackTypeAudio) + return 0; + + return (AudioTrack *)track; +} + BinkDecoder::VideoFrame::VideoFrame() : bits(0) { } diff --git a/video/bink_decoder.h b/video/bink_decoder.h index 08800c2223..fb2998fb6e 100644 --- a/video/bink_decoder.h +++ b/video/bink_decoder.h @@ -74,6 +74,8 @@ public: protected: void readNextPacket(); + bool supportsAudioTrackSwitching() const { return true; } + AudioTrack *getAudioTrack(int index); private: static const int kAudioChannelsMax = 2; diff --git a/video/codecs/jpeg.cpp b/video/codecs/jpeg.cpp new file mode 100644 index 0000000000..f3886f334a --- /dev/null +++ b/video/codecs/jpeg.cpp @@ -0,0 +1,66 @@ +/* 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/system.h" +#include "common/textconsole.h" +#include "graphics/surface.h" +#include "graphics/decoders/jpeg.h" + +#include "video/codecs/jpeg.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Video { + +JPEGDecoder::JPEGDecoder() : Codec() { + _pixelFormat = g_system->getScreenFormat(); + _surface = NULL; +} + +JPEGDecoder::~JPEGDecoder() { + if (_surface) { + _surface->free(); + delete _surface; + } +} + +const Graphics::Surface *JPEGDecoder::decodeImage(Common::SeekableReadStream *stream) { + Graphics::JPEGDecoder jpeg; + + if (!jpeg.loadStream(*stream)) { + warning("Failed to decode JPEG frame"); + return 0; + } + + if (_surface) { + _surface->free(); + delete _surface; + } + + _surface = jpeg.getSurface()->convertTo(_pixelFormat); + + return _surface; +} + +} // End of namespace Video diff --git a/video/codecs/jpeg.h b/video/codecs/jpeg.h new file mode 100644 index 0000000000..1023d97779 --- /dev/null +++ b/video/codecs/jpeg.h @@ -0,0 +1,60 @@ +/* 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. + * + */ + +#ifndef VIDEO_CODECS_JPEG_H +#define VIDEO_CODECS_JPEG_H + +#include "video/codecs/codec.h" +#include "graphics/pixelformat.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Graphics { +struct Surface; +} + +namespace Video { + +/** + * JPEG decoder. + * + * Used in video: + * - QuickTimeDecoder + */ +class JPEGDecoder : public Codec { +public: + JPEGDecoder(); + ~JPEGDecoder(); + + const Graphics::Surface *decodeImage(Common::SeekableReadStream *stream); + Graphics::PixelFormat getPixelFormat() const { return _pixelFormat; } + +private: + Graphics::PixelFormat _pixelFormat; + Graphics::Surface *_surface; +}; + +} // End of namespace Video + +#endif diff --git a/video/codecs/mjpeg.cpp b/video/codecs/mjpeg.cpp index 10fd9d7c50..61f9eb5881 100644 --- a/video/codecs/mjpeg.cpp +++ b/video/codecs/mjpeg.cpp @@ -20,6 +20,12 @@ * */ +// Based on LGPL MJPEG/AVI to JPEG/JFIF conversion code from libav +// Copyright (c) 2010 Adrian Daerr and Nicolas George +// That in turn was adapted from mjpeg2jpeg.c, with original copyright: +// Paris 2010 Adrian Daerr, public domain + +#include "common/memstream.h" #include "common/system.h" #include "common/textconsole.h" #include "graphics/surface.h" @@ -33,23 +39,168 @@ class SeekableReadStream; namespace Video { -JPEGDecoder::JPEGDecoder() : Codec() { +MJPEGDecoder::MJPEGDecoder() : Codec() { _pixelFormat = g_system->getScreenFormat(); - _surface = NULL; + _surface = 0; } -JPEGDecoder::~JPEGDecoder() { +MJPEGDecoder::~MJPEGDecoder() { if (_surface) { _surface->free(); delete _surface; } } -const Graphics::Surface *JPEGDecoder::decodeImage(Common::SeekableReadStream *stream) { +// Header to be inserted +static const byte s_jpegHeader[] = { + 0xff, 0xd8, // SOI + 0xff, 0xe0, // APP0 + 0x00, 0x10, // APP0 header size (including + // this field, but excluding preceding) + 'J', 'F', 'I', 'F', 0x00, // ID string 'JFIF\0' + 0x01, 0x01, // version + 0x00, // bits per type + 0x00, 0x00, // X density + 0x00, 0x00, // Y density + 0x00, // X thumbnail size + 0x00 +}; + +enum { + DHT_SEGMENT_SIZE = 420 +}; + +static const byte s_dhtSegmentHead[] = { 0xFF, 0xC4, 0x01, 0xA2, 0x00 }; +static const byte s_dhtSegmentFrag[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// Set up the standard Huffman tables (cf. JPEG standard section K.3) +// IMPORTANT: these are only valid for 8-bit data precision! +static const byte s_mjpegBitsDCLuminance[17] = { + /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 +}; + +static const byte s_mjpegValDC[12] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 +}; + +static const byte s_mjpegBitsDCChrominance[17] = { + /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 +}; + +static const byte s_mjpegBitsACLuminance[17] = { + /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d +}; + +static const byte s_mjpegValACLuminance[] = { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa +}; + +static const byte s_mjpegBitsACChrominance[17] = { + /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 +}; + +static const byte s_mjpegValACChrominance[] = { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa +}; + +const Graphics::Surface *MJPEGDecoder::decodeImage(Common::SeekableReadStream *stream) { + // We need to reconstruct an actual JPEG stream here, then feed it to the JPEG decoder + // Yes, this is a pain. + + stream->readUint32BE(); // Skip nonsense JPEG header + uint16 inputSkip = stream->readUint16BE() + 4; + uint32 tag = stream->readUint32BE(); + + if (tag != MKTAG('A', 'V', 'I', '1')) { + warning("Invalid MJPEG tag found"); + return 0; + } + + uint32 outputSize = stream->size() - inputSkip + sizeof(s_jpegHeader) + DHT_SEGMENT_SIZE; + byte *data = (byte *)malloc(outputSize); + + if (!data) { + warning("Failed to allocate data for MJPEG conversion"); + return 0; + } + + // Copy the header + memcpy(data, s_jpegHeader, sizeof(s_jpegHeader)); + uint32 dataOffset = sizeof(s_jpegHeader); + + // Write the fake DHT segment + memcpy(data + dataOffset, s_dhtSegmentHead, sizeof(s_dhtSegmentHead)); + dataOffset += sizeof(s_dhtSegmentHead); + memcpy(data + dataOffset, s_mjpegBitsDCLuminance + 1, 16); + dataOffset += 16; + memcpy(data + dataOffset, s_dhtSegmentFrag, sizeof(s_dhtSegmentFrag)); + dataOffset += sizeof(s_dhtSegmentFrag); + memcpy(data + dataOffset, s_mjpegValDC, 12); + dataOffset += 12; + data[dataOffset++] = 0x10; + memcpy(data + dataOffset, s_mjpegBitsACLuminance + 1, 16); + dataOffset += 16; + memcpy(data + dataOffset, s_mjpegValACLuminance, 162); + dataOffset += 162; + data[dataOffset++] = 0x11; + memcpy(data + dataOffset, s_mjpegBitsACChrominance + 1, 16); + dataOffset += 16; + memcpy(data + dataOffset, s_mjpegValACChrominance, 162); + dataOffset += 162; + + // Write the actual data + stream->seek(inputSkip); + stream->read(data + dataOffset, stream->size() - inputSkip); + + Common::MemoryReadStream convertedStream(data, outputSize, DisposeAfterUse::YES); Graphics::JPEGDecoder jpeg; - if (!jpeg.loadStream(*stream)) { - warning("Failed to decode JPEG frame"); + if (!jpeg.loadStream(convertedStream)) { + warning("Failed to decode MJPEG frame"); return 0; } diff --git a/video/codecs/mjpeg.h b/video/codecs/mjpeg.h index d71454799c..16eefa68a7 100644 --- a/video/codecs/mjpeg.h +++ b/video/codecs/mjpeg.h @@ -40,12 +40,12 @@ namespace Video { * Motion JPEG decoder. * * Used in video: - * - QuickTimeDecoder + * - AVIDecoder */ -class JPEGDecoder : public Codec { +class MJPEGDecoder : public Codec { public: - JPEGDecoder(); - ~JPEGDecoder(); + MJPEGDecoder(); + ~MJPEGDecoder(); const Graphics::Surface *decodeImage(Common::SeekableReadStream *stream); Graphics::PixelFormat getPixelFormat() const { return _pixelFormat; } diff --git a/video/codecs/mpeg.h b/video/codecs/mpeg.h index 0082844537..3560f4b8b7 100644 --- a/video/codecs/mpeg.h +++ b/video/codecs/mpeg.h @@ -37,7 +37,7 @@ typedef signed short int16_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) || defined (__SYMBIAN32__) typedef signed char int8_t; typedef signed short int16_t; typedef unsigned char uint8_t; diff --git a/video/codecs/truemotion1.h b/video/codecs/truemotion1.h index b2a35cf873..6ac09af24b 100644 --- a/video/codecs/truemotion1.h +++ b/video/codecs/truemotion1.h @@ -22,8 +22,8 @@ // Based on the TrueMotion 1 decoder by Alex Beregszaszi & Mike Melanson in FFmpeg -// Only compile if SCI32 is enabled or if we're building dynamic modules -#if defined(ENABLE_SCI32) || defined(DYNAMIC_MODULES) +// Only compile if SCI32 is enabled, ZVISION is enabled, or if we're building dynamic modules +#if defined(ENABLE_SCI32) || defined(ENABLE_ZVISION) || defined(DYNAMIC_MODULES) #ifndef VIDEO_CODECS_TRUEMOTION1_H #define VIDEO_CODECS_TRUEMOTION1_H diff --git a/video/module.mk b/video/module.mk index a491947aaf..d836371182 100644 --- a/video/module.mk +++ b/video/module.mk @@ -12,6 +12,7 @@ MODULE_OBJS := \ codecs/cdtoons.o \ codecs/cinepak.o \ codecs/indeo3.o \ + codecs/jpeg.o \ codecs/mjpeg.o \ codecs/msrle.o \ codecs/msvideo1.o \ diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp index 7539d4a1e2..11a27beb10 100644 --- a/video/qt_decoder.cpp +++ b/video/qt_decoder.cpp @@ -40,7 +40,7 @@ // Video codecs #include "video/codecs/cinepak.h" -#include "video/codecs/mjpeg.h" +#include "video/codecs/jpeg.h" #include "video/codecs/qtrle.h" #include "video/codecs/rpza.h" #include "video/codecs/smc.h" @@ -296,7 +296,7 @@ void QuickTimeDecoder::VideoSampleDesc::initCodec() { warning("Sorenson Video 3 not yet supported"); break; case MKTAG('j','p','e','g'): - // Motion JPEG: Used by some Myst ME 10th Anniversary videos. + // JPEG: Used by some Myst ME 10th Anniversary videos. _videoCodec = new JPEGDecoder(); break; case MKTAG('Q','k','B','k'): diff --git a/video/smk_decoder.cpp b/video/smk_decoder.cpp index 3dbcebcde4..b31ad109ad 100644 --- a/video/smk_decoder.cpp +++ b/video/smk_decoder.cpp @@ -369,8 +369,7 @@ bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) { if (_header.audioInfo[i].compression == kCompressionRDFT || _header.audioInfo[i].compression == kCompressionDCT) warning("Unhandled Smacker v2 audio compression"); - if (i == 0) - addTrack(new SmackerAudioTrack(_header.audioInfo[i], _soundType)); + addTrack(new SmackerAudioTrack(_header.audioInfo[i], _soundType)); } } @@ -477,7 +476,10 @@ void SmackerDecoder::readNextPacket() { } void SmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize) { - if (_header.audioInfo[track].hasAudio && chunkSize > 0 && track == 0) { + if (chunkSize == 0) + return; + + if (_header.audioInfo[track].hasAudio) { // Get the audio track, which start at offset 1 (first track is video) SmackerAudioTrack *audioTrack = (SmackerAudioTrack *)getTrack(track + 1); @@ -501,14 +503,21 @@ void SmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpac audioTrack->queuePCM(soundBuffer, chunkSize); } } else { - // Ignore the rest of the audio tracks, if they exist - // TODO: Are there any Smacker videos with more than one audio stream? - // If yes, we should play the rest of the audio streams as well - if (chunkSize > 0) - _fileStream->skip(chunkSize); + // Ignore possibly unused data + _fileStream->skip(chunkSize); } } +VideoDecoder::AudioTrack *SmackerDecoder::getAudioTrack(int index) { + // Smacker audio track indexes are relative to the first audio track + Track *track = getTrack(index + 1); + + if (!track || track->getTrackType() != Track::kTrackTypeAudio) + return 0; + + return (AudioTrack *)track; +} + SmackerDecoder::SmackerVideoTrack::SmackerVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) { _surface = new Graphics::Surface(); _surface->create(width, height * (flags ? 2 : 1), Graphics::PixelFormat::createFormatCLUT8()); @@ -726,16 +735,15 @@ void SmackerDecoder::SmackerVideoTrack::unpackPalette(Common::SeekableReadStream } else { // top 2 bits are 00 sz++; // get the lower 6 bits for each component (0x3f = 00111111) - byte b = b0 & 0x3f; + byte r = b0 & 0x3f; byte g = (*p++) & 0x3f; - byte r = (*p++) & 0x3f; - - assert(g < 0xc0 && b < 0xc0); + byte b = (*p++) & 0x3f; - // upscale to full 8-bit color values by multiplying by 4 - *pal++ = b * 4; - *pal++ = g * 4; - *pal++ = r * 4; + // upscale to full 8-bit color values. The Multimedia Wiki suggests + // a lookup table for this, but this should produce the same result. + *pal++ = (r * 4 + r / 16); + *pal++ = (g * 4 + g / 16); + *pal++ = (b * 4 + b / 16); } } diff --git a/video/smk_decoder.h b/video/smk_decoder.h index e4bc9bab42..5298158833 100644 --- a/video/smk_decoder.h +++ b/video/smk_decoder.h @@ -69,6 +69,8 @@ public: protected: void readNextPacket(); + bool supportsAudioTrackSwitching() const { return true; } + AudioTrack *getAudioTrack(int index); virtual void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize); diff --git a/video/video_decoder.cpp b/video/video_decoder.cpp index 0ab1478727..931bde20d0 100644 --- a/video/video_decoder.cpp +++ b/video/video_decoder.cpp @@ -46,6 +46,7 @@ VideoDecoder::VideoDecoder() { _endTime = 0; _endTimeSet = false; _nextVideoTrack = 0; + _mainAudioTrack = 0; // Find the best format for output _defaultHighColorFormat = g_system->getScreenFormat(); @@ -75,6 +76,7 @@ void VideoDecoder::close() { _endTime = 0; _endTimeSet = false; _nextVideoTrack = 0; + _mainAudioTrack = 0; } bool VideoDecoder::loadFile(const Common::String &filename) { @@ -553,6 +555,9 @@ Audio::Timestamp VideoDecoder::FixedRateVideoTrack::getDuration() const { return getFrameTime(getFrameCount()); } +VideoDecoder::AudioTrack::AudioTrack() : _volume(Audio::Mixer::kMaxChannelVolume), _balance(0), _muted(false) { +} + bool VideoDecoder::AudioTrack::endOfTrack() const { Audio::AudioStream *stream = getAudioStream(); return !stream || !g_system->getMixer()->isSoundHandleActive(_handle) || stream->endOfData(); @@ -562,7 +567,7 @@ void VideoDecoder::AudioTrack::setVolume(byte volume) { _volume = volume; if (g_system->getMixer()->isSoundHandleActive(_handle)) - g_system->getMixer()->setChannelVolume(_handle, _volume); + g_system->getMixer()->setChannelVolume(_handle, _muted ? 0 : _volume); } void VideoDecoder::AudioTrack::setBalance(int8 balance) { @@ -578,7 +583,7 @@ void VideoDecoder::AudioTrack::start() { Audio::AudioStream *stream = getAudioStream(); assert(stream); - g_system->getMixer()->playStream(getSoundType(), &_handle, stream, -1, getVolume(), getBalance(), DisposeAfterUse::NO); + g_system->getMixer()->playStream(getSoundType(), &_handle, stream, -1, _muted ? 0 : getVolume(), getBalance(), DisposeAfterUse::NO); // Pause the audio again if we're still paused if (isPaused()) @@ -597,7 +602,7 @@ void VideoDecoder::AudioTrack::start(const Audio::Timestamp &limit) { stream = Audio::makeLimitingAudioStream(stream, limit, DisposeAfterUse::NO); - g_system->getMixer()->playStream(getSoundType(), &_handle, stream, -1, getVolume(), getBalance(), DisposeAfterUse::YES); + g_system->getMixer()->playStream(getSoundType(), &_handle, stream, -1, _muted ? 0 : getVolume(), getBalance(), DisposeAfterUse::YES); // Pause the audio again if we're still paused if (isPaused()) @@ -611,6 +616,16 @@ uint32 VideoDecoder::AudioTrack::getRunningTime() const { return 0; } +void VideoDecoder::AudioTrack::setMute(bool mute) { + // Update the mute settings, if required + if (_muted != mute) { + _muted = mute; + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->setChannelVolume(_handle, mute ? 0 : _volume); + } +} + void VideoDecoder::AudioTrack::pauseIntern(bool shouldPause) { if (g_system->getMixer()->isSoundHandleActive(_handle)) g_system->getMixer()->pauseHandle(_handle, shouldPause); @@ -669,6 +684,17 @@ void VideoDecoder::addTrack(Track *track, bool isExternal) { // Update volume settings if it's an audio track ((AudioTrack *)track)->setVolume(_audioVolume); ((AudioTrack *)track)->setBalance(_audioBalance); + + if (!isExternal && supportsAudioTrackSwitching()) { + if (_mainAudioTrack) { + // The main audio track has already been found + ((AudioTrack *)track)->setMute(true); + } else { + // First audio track found -> now the main one + _mainAudioTrack = (AudioTrack *)track; + _mainAudioTrack->setMute(false); + } + } } else if (track->getTrackType() == Track::kTrackTypeVideo) { // If this track has a better time, update _nextVideoTrack if (!_nextVideoTrack || ((VideoTrack *)track)->getNextFrameStartTime() < _nextVideoTrack->getNextFrameStartTime()) @@ -701,6 +727,34 @@ bool VideoDecoder::addStreamFileTrack(const Common::String &baseName) { return result; } +bool VideoDecoder::setAudioTrack(int index) { + if (!supportsAudioTrackSwitching()) + return false; + + AudioTrack *audioTrack = getAudioTrack(index); + + if (!audioTrack) + return false; + + if (_mainAudioTrack == audioTrack) + return true; + + _mainAudioTrack->setMute(true); + audioTrack->setMute(false); + _mainAudioTrack = audioTrack; + return true; +} + +uint VideoDecoder::getAudioTrackCount() const { + uint count = 0; + + for (TrackList::const_iterator it = _internalTracks.begin(); it != _internalTracks.end(); it++) + if ((*it)->getTrackType() == Track::kTrackTypeAudio) + count++; + + return count; +} + void VideoDecoder::setEndTime(const Audio::Timestamp &endTime) { Audio::Timestamp startTime = 0; diff --git a/video/video_decoder.h b/video/video_decoder.h index ac6586d8dd..99161d6f97 100644 --- a/video/video_decoder.h +++ b/video/video_decoder.h @@ -407,6 +407,21 @@ public: */ bool addStreamFileTrack(const Common::String &baseName); + /** + * Set the internal audio track. + * + * Has no effect if the container does not support this. + * @see supportsAudioTrackSwitching() + * + * @param index The index of the track, whose meaning is dependent on the container + */ + bool setAudioTrack(int index); + + /** + * Get the number of internal audio tracks. + */ + uint getAudioTrackCount() const; + protected: /** * An abstract representation of a track in a movie. Since tracks here are designed @@ -612,7 +627,7 @@ protected: */ class AudioTrack : public Track { public: - AudioTrack() {} + AudioTrack(); virtual ~AudioTrack() {} TrackType getTrackType() const { return kTrackTypeAudio; } @@ -662,6 +677,11 @@ protected: */ virtual Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kPlainSoundType; } + /** + * Mute the track + */ + void setMute(bool mute); + protected: void pauseIntern(bool shouldPause); @@ -674,6 +694,7 @@ protected: Audio::SoundHandle _handle; byte _volume; int8 _balance; + bool _muted; }; /** @@ -833,6 +854,25 @@ protected: */ virtual bool seekIntern(const Audio::Timestamp &time); + /** + * Does this video format support switching between audio tracks? + * + * Returning true implies this format supports multiple audio tracks, + * can switch tracks, and defaults to playing the first found audio + * track. + */ + virtual bool supportsAudioTrackSwitching() const { return false; } + + /** + * Get the audio track for the given index. + * + * This is used only if supportsAudioTrackSwitching() returns true. + * + * @param index The index of the track, whose meaning is dependent on the container + * @return The audio track for the index, or 0 if not found + */ + virtual AudioTrack *getAudioTrack(int index) { return 0; } + private: // Tracks owned by this VideoDecoder TrackList _tracks; @@ -865,6 +905,8 @@ private: uint32 _pauseStartTime; byte _audioVolume; int8 _audioBalance; + + AudioTrack *_mainAudioTrack; }; } // End of namespace Video |