aboutsummaryrefslogtreecommitdiff
path: root/video
diff options
context:
space:
mode:
Diffstat (limited to 'video')
-rw-r--r--video/avi_decoder.cpp138
-rw-r--r--video/avi_decoder.h23
-rw-r--r--video/bink_decoder.cpp10
-rw-r--r--video/bink_decoder.h2
-rw-r--r--video/codecs/jpeg.cpp66
-rw-r--r--video/codecs/jpeg.h60
-rw-r--r--video/codecs/mjpeg.cpp163
-rw-r--r--video/codecs/mjpeg.h8
-rw-r--r--video/codecs/mpeg.h2
-rw-r--r--video/codecs/truemotion1.h4
-rw-r--r--video/module.mk1
-rw-r--r--video/qt_decoder.cpp4
-rw-r--r--video/smk_decoder.cpp40
-rw-r--r--video/smk_decoder.h2
-rw-r--r--video/video_decoder.cpp60
-rw-r--r--video/video_decoder.h44
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