[Scummvm-git-logs] scummvm master -> 90becec9ddd3c4c4383383aced26d169ae65f360

Scorpeg noreply at scummvm.org
Thu Oct 22 11:09:13 UTC 2026


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
90becec9dd VIDEO: Move ASF demuxing to video and add ASF playback support


Commit: 90becec9ddd3c4c4383383aced26d169ae65f360
    https://github.com/scummvm/scummvm/commit/90becec9ddd3c4c4383383aced26d169ae65f360
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T14:09:01+03:00

Commit Message:
VIDEO: Move ASF demuxing to video and add ASF playback support

Changed paths:
  A video/asf.cpp
  A video/asf.h
  A video/asf_decoder.cpp
  A video/asf_decoder.h
  R audio/decoders/asf.cpp
  R audio/decoders/asf.h
    audio/module.mk
    engines/myst3/sound.cpp
    engines/testbed/video.cpp
    video/module.mk


diff --git a/audio/decoders/asf.cpp b/audio/decoders/asf.cpp
deleted file mode 100644
index 5a39c73401e..00000000000
--- a/audio/decoders/asf.cpp
+++ /dev/null
@@ -1,460 +0,0 @@
-/* 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 3 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, see <http://www.gnu.org/licenses/>.
- *
- */
-
-// Based on xoreos' ASF code which is in turn
-// Largely based on the ASF implementation found in FFmpeg.
-
-#include "common/textconsole.h"
-#include "common/stream.h"
-#include "common/util.h"
-
-#include "audio/audiostream.h"
-#include "audio/decoders/asf.h"
-#include "audio/decoders/wma.h"
-#include "audio/decoders/wave_types.h"
-
-namespace Audio {
-
-class ASFGUID {
-public:
-	ASFGUID(Common::SeekableReadStream &stream) {
-		stream.read(id, 16);
-	}
-
-	bool operator==(const byte gid[16]) const {
-		return !memcmp(gid, id, 16);
-	}
-
-	bool operator!=(const byte gid[16]) const {
-		return memcmp(gid, id, 16);
-	}
-
-	Common::String toString() const {
-		return Common::String::format("%02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x",
-				id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]);
-	}
-
-private:
-	byte id[16];
-};
-
-// GUID's that we need
-static const byte s_asfHeader[16]         = { 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C };
-static const byte s_asfFileHeader[16]     = { 0xA1, 0xDC, 0xAB, 0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 };
-static const byte s_asfHead1[16]          = { 0xb5, 0x03, 0xbf, 0x5f, 0x2E, 0xA9, 0xCF, 0x11, 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 };
-static const byte s_asfComment[16]        = { 0x33, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c };
-static const byte s_asfStreamHeader[16]   = { 0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 };
-static const byte s_asfCodecComment[16]   = { 0x40, 0x52, 0xD1, 0x86, 0x1D, 0x31, 0xD0, 0x11, 0xA3, 0xA4, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 };
-static const byte s_asfDataHeader[16]     = { 0x36, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c };
-static const byte s_asfAudioStream[16]    = { 0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B };
-static const byte s_asfExtendedHeader[16] = { 0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11, 0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50 };
-static const byte s_asfStreamBitRate[16]  = { 0xce, 0x75, 0xf8, 0x7b, 0x8d, 0x46, 0xd1, 0x11, 0x8d, 0x82, 0x00, 0x60, 0x97, 0xc9, 0xa2, 0xb2 };
-
-class ASFStream : public SeekableAudioStream {
-public:
-	ASFStream(Common::SeekableReadStream *stream,
-			DisposeAfterUse::Flag disposeAfterUse);
-	~ASFStream();
-
-	int readBuffer(int16 *buffer, const int numSamples) override;
-
-	bool endOfData() const override;
-	bool isStereo() const override { return _channels == 2; }
-	int getRate() const override { return _sampleRate; }
-	Timestamp getLength() const override { return Audio::Timestamp(_duration / 10000, _sampleRate); }
-	bool seek(const Timestamp &where) override;
-	bool rewind() override;
-
-private:
-	// Packet data
-	struct Packet {
-		Packet();
-		~Packet();
-
-		byte flags;
-		byte segmentType;
-		uint16 packetSize;
-		uint32 sendTime;
-		uint16 duration;
-
-		struct Segment {
-			byte streamID;
-			byte sequenceNumber;
-			bool isKeyframe;
-			Common::Array<Common::SeekableReadStream *> data;
-		};
-
-		Common::Array<Segment> segments;
-	};
-
-	Common::SeekableReadStream *_stream;
-	DisposeAfterUse::Flag _disposeAfterUse;
-
-	void parseStreamHeader();
-	void parseFileHeader();
-	Packet *readPacket();
-	Codec *createCodec();
-	AudioStream *createAudioStream();
-
-	uint32 _rewindPos;
-	uint64 _curPacket;
-	Packet *_lastPacket;
-	Codec *_codec;
-	AudioStream *_curAudioStream;
-	byte _curSequenceNumber;
-
-	// Header object variables
-	uint64 _packetCount;
-	uint64 _duration;
-	uint32 _minPacketSize, _maxPacketSize;
-
-	// Stream object variables
-	uint16 _streamID;
-	uint16 _compression;
-	uint16 _channels;
-	int _sampleRate;
-	uint32 _bitRate;
-	uint16 _blockAlign;
-	uint16 _bitsPerCodedSample;
-	Common::SeekableReadStream *_extraData;
-};
-
-ASFStream::Packet::Packet() {
-}
-
-ASFStream::Packet::~Packet() {
-	for (uint32 i = 0; i < segments.size(); i++)
-		for (uint32 j = 0; j < segments[i].data.size(); j++)
-				delete segments[i].data[j];
-}
-
-ASFStream::ASFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) : _stream(stream), _disposeAfterUse(disposeAfterUse) {
-	_extraData = nullptr;
-	_lastPacket = nullptr;
-	_curPacket = 0;
-	_codec = nullptr;
-	_curAudioStream = nullptr;
-	_curSequenceNumber = 1; // They always start at one
-
-	ASFGUID guid = ASFGUID(*_stream);
-	if (guid != s_asfHeader)
-		error("ASFStream: Missing asf header");
-
-	_stream->readUint64LE();
-	_stream->readUint32LE();
-	_stream->readByte();
-	_stream->readByte();
-
-	for (;;) {
-		uint64 startPos = _stream->pos();
-		guid = ASFGUID(*_stream);
-		uint64 size = _stream->readUint64LE();
-
-		if (_stream->eos())
-			error("ASFStream: Unexpected eos");
-
-		// TODO: Parse each chunk
-		if (guid == s_asfFileHeader) {
-			parseFileHeader();
-		} else if (guid == s_asfHead1) {
-			// Should be safe to ignore
-		} else if (guid == s_asfComment) {
-			// Ignored
-		} else if (guid == s_asfStreamHeader) {
-			parseStreamHeader();
-		} else if (guid == s_asfCodecComment) {
-			// TODO
-		} else if (guid == s_asfDataHeader) {
-			// Done parsing the header
-			break;
-		} else if (guid == s_asfExtendedHeader) {
-			// TODO
-		} else if (guid == s_asfStreamBitRate) {
-			// Ignored
-		} else
-			warning("Found unknown ASF GUID: %s", guid.toString().c_str());
-
-		_stream->seek(startPos + size);
-	}
-
-	// Skip to the beginning of the packets
-	_stream->skip(26);
-	_rewindPos = _stream->pos();
-}
-
-ASFStream::~ASFStream() {
-	if (_disposeAfterUse)
-		delete _stream;
-
-	delete _lastPacket;
-	delete _curAudioStream;
-	delete _codec;
-}
-
-void ASFStream::parseFileHeader() {
-	_stream->skip(16); // skip client GUID
-	/* uint64 fileSize = */ _stream->readUint64LE();
-	/* uint64 creationTime = */ _stream->readUint64LE();
-	_packetCount = _stream->readUint64LE();
-	/* uint64 endTimestamp = */ _stream->readUint64LE();
-	_duration = _stream->readUint64LE();
-	/* uint32 startTimestamp = */ _stream->readUint32LE();
-	/* uint32 unknown = */ _stream->readUint32LE();
-	/* uint32 flags = */ _stream->readUint32LE();
-	_minPacketSize = _stream->readUint32LE();
-	_maxPacketSize = _stream->readUint32LE();
-	/* uint32 uncFrameSize = */ _stream->readUint32LE();
-
-	// We only know how to support packets of one length
-	if (_minPacketSize != _maxPacketSize)
-		error("ASFStream::parseFileHeader(): Mismatched packet sizes: Min = %d, Max = %d", _minPacketSize, _maxPacketSize);
-}
-
-void ASFStream::parseStreamHeader() {
-	ASFGUID guid = ASFGUID(*_stream);
-
-	if (guid != s_asfAudioStream)
-		error("ASFStream::parseStreamHeader(): Found non-audio stream");
-
-	_stream->skip(16); // skip a guid
-	_stream->readUint64LE(); // total size
-	uint32 typeSpecificSize = _stream->readUint32LE();
-	_stream->readUint32LE();
-	_streamID = _stream->readUint16LE();
-	_stream->readUint32LE();
-
-	// Parse the wave header
-	_compression = _stream->readUint16LE();
-	_channels = _stream->readUint16LE();
-	_sampleRate = _stream->readUint32LE();
-	_bitRate = _stream->readUint32LE() * 8;
-	_blockAlign = _stream->readUint16LE();
-	_bitsPerCodedSample = (typeSpecificSize == 14) ? 8 : _stream->readUint16LE();
-
-	if (typeSpecificSize >= 18) {
-		uint32 cbSize = _stream->readUint16LE();
-		cbSize = MIN<int>(cbSize, typeSpecificSize - 18);
-		_extraData = _stream->readStream(cbSize);
-	}
-
-	_codec = createCodec();
-}
-
-bool ASFStream::seek(const Timestamp &where) {
-	if (where == 0) {
-		return rewind();
-	}
-
-	// Seeking is not supported
-	return false;
-}
-
-bool ASFStream::rewind() {
-	// Seek back to the start of the packets
-	_stream->seek(_rewindPos);
-
-	// Reset our packet counter
-	_curPacket = 0;
-	delete _lastPacket; _lastPacket = nullptr;
-
-	// Delete a stream if we have one
-	delete _curAudioStream; _curAudioStream = nullptr;
-
-	// Reset this too
-	_curSequenceNumber = 1;
-
-	return true;
-}
-
-ASFStream::Packet *ASFStream::readPacket() {
-	if (_curPacket == _packetCount)
-		error("ASFStream::readPacket(): Reading too many packets");
-
-	uint32 packetStartPos = _stream->pos();
-
-	// Read a single ASF packet
-	if (_stream->readByte() != 0x82)
-		error("ASFStream::readPacket(): Missing packet header");
-
-	if (_stream->readUint16LE() != 0)
-		error("ASFStream::readPacket(): Unknown is not zero");
-
-	Packet *packet = new Packet();
-	packet->flags = _stream->readByte();
-	packet->segmentType = _stream->readByte();
-	packet->packetSize = (packet->flags & 0x40) ? _stream->readUint16LE() : 0;
-
-	uint16 paddingSize = 0;
-	if (packet->flags & 0x10)
-		paddingSize = _stream->readUint16LE();
-	else if (packet->flags & 0x08)
-		paddingSize = _stream->readByte();
-
-	packet->sendTime = _stream->readUint32LE();
-	packet->duration = _stream->readUint16LE();
-
-	byte segmentCount = (packet->flags & 0x01) ? _stream->readByte() : 1;
-	packet->segments.resize(segmentCount & 0x3F);
-
-	for (uint32 i = 0; i < packet->segments.size(); i++) {
-		Packet::Segment &segment = packet->segments[i];
-
-		segment.streamID = _stream->readByte();
-		segment.sequenceNumber = _stream->readByte();
-		segment.isKeyframe = (segment.streamID & 0x80) != 0;
-		segment.streamID &= 0x7F;
-
-		uint32 fragmentOffset = 0;
-		if (packet->segmentType == 0x55)
-			fragmentOffset = _stream->readByte();
-		else if (packet->segmentType == 0x59)
-			fragmentOffset = _stream->readUint16LE();
-		else if (packet->segmentType == 0x5D)
-			fragmentOffset = _stream->readUint32LE();
-		else
-			error("ASFStream::readPacket(): Unknown packet segment type 0x%02x", packet->segmentType);
-
-		byte flags = _stream->readByte();
-		if (flags == 1) {
-			//uint32 objectStartTime = fragmentOffset; // reused purpose
-			_stream->readByte(); // unknown
-
-			uint32 dataLength = (packet->segments.size() == 1) ? (_maxPacketSize - (_stream->pos() - packetStartPos) - paddingSize) : _stream->readUint16LE();
-			uint32 startObjectPos = _stream->pos();
-
-			while ((uint32)_stream->pos() < dataLength + startObjectPos)
-				segment.data.push_back(_stream->readStream(_stream->readByte()));
-		} else if (flags == 8) {
-			/* uint32 objectLength = */ _stream->readUint32LE();
-			/* uint32 objectStartTime = */ _stream->readUint32LE();
-
-			uint32 dataLength = 0;
-			if (packet->segments.size() == 1)
-				dataLength = _maxPacketSize - (_stream->pos() - packetStartPos) - fragmentOffset - paddingSize;
-			else if (segmentCount & 0x40)
-				dataLength = _stream->readByte();
-			else
-				dataLength = _stream->readUint16LE();
-
-			_stream->skip(fragmentOffset);
-			segment.data.push_back(_stream->readStream(dataLength));
-		} else
-			error("ASFStream::readPacket(): Unknown packet flags 0x%02x", flags);
-	}
-
-	// Skip any padding
-	_stream->skip(paddingSize);
-
-	// We just read a packet
-	_curPacket++;
-
-	if ((uint32)_stream->pos() != packetStartPos + _maxPacketSize)
-		error("ASFStream::readPacket(): Mismatching packet pos: %d (should be %d)", (int)_stream->pos(), _maxPacketSize + packetStartPos);
-
-	return packet;
-}
-
-Codec *ASFStream::createCodec() {
-	switch (_compression) {
-	case kWaveFormatWMAv2:
-		return new WMACodec(2, _sampleRate, _channels, _bitRate, _blockAlign, _extraData);
-	default:
-		error("ASFStream::createAudioStream(): Unknown compression 0x%04x", _compression);
-	}
-
-	return nullptr;
-}
-
-AudioStream *ASFStream::createAudioStream() {
-	delete _lastPacket;
-	_lastPacket = readPacket();
-
-	// TODO
-	if (_lastPacket->segments.size() != 1)
-		error("ASFStream::createAudioStream(): Only single segment packets supported");
-
-	Packet::Segment &segment = _lastPacket->segments[0];
-
-	// We should only have one stream in a ASF audio file
-	if (segment.streamID != _streamID)
-		error("ASFStream::createAudioStream(): Packet stream ID mismatch");
-
-	// TODO
-	if (segment.sequenceNumber != _curSequenceNumber)
-		error("ASFStream::createAudioStream(): Only one sequence number per packet supported");
-
-	// This can overflow and needs to overflow!
-	_curSequenceNumber++;
-
-	// TODO
-	if (segment.data.size() != 1)
-		error("ASFStream::createAudioStream(): Packet grouping not supported");
-
-	Common::SeekableReadStream *stream = segment.data[0];
-	if (_codec)
-		return _codec->decodeFrame(*stream);
-
-	return nullptr;
-}
-
-int ASFStream::readBuffer(int16 *buffer, const int numSamples) {
-	int samplesDecoded = 0;
-
-	for (;;) {
-		if (_curAudioStream) {
-			samplesDecoded += _curAudioStream->readBuffer(buffer + samplesDecoded, numSamples - samplesDecoded);
-
-			if (_curAudioStream->endOfData()) {
-				delete _curAudioStream;
-				_curAudioStream = nullptr;
-			}
-		}
-
-		if (samplesDecoded == numSamples || endOfData())
-			break;
-
-		if (!_curAudioStream) {
-			_curAudioStream = createAudioStream();
-		}
-
-	}
-
-	return samplesDecoded;
-}
-
-bool ASFStream::endOfData() const {
-	return _curPacket == _packetCount && !_curAudioStream;
-}
-
-SeekableAudioStream *makeASFStream(
-	Common::SeekableReadStream *stream,
-	DisposeAfterUse::Flag disposeAfterUse) {
-	SeekableAudioStream *s = new ASFStream(stream, disposeAfterUse);
-
-	if (s && s->endOfData()) {
-		delete s;
-		return nullptr;
-	}
-
-	return s;
-}
-
-} // End of namespace Audio
diff --git a/audio/module.mk b/audio/module.mk
index aa35b458973..f2e060d70f4 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -35,7 +35,6 @@ MODULE_OBJS := \
 	decoders/adpcm.o \
 	decoders/aiff.o \
 	decoders/apc.o \
-	decoders/asf.o \
 	decoders/flac.o \
 	decoders/g711.o \
 	decoders/iff_sound.o \
diff --git a/engines/myst3/sound.cpp b/engines/myst3/sound.cpp
index e41fb3a200b..45ca2d3f5b0 100644
--- a/engines/myst3/sound.cpp
+++ b/engines/myst3/sound.cpp
@@ -26,13 +26,14 @@
 #include "engines/myst3/state.h"
 
 #include "audio/audiostream.h"
-#include "audio/decoders/asf.h"
 #include "audio/decoders/mp3.h"
 #include "audio/decoders/wave.h"
 
 #include "common/archive.h"
 #include "common/config-manager.h"
 
+#include "video/asf.h"
+
 namespace Myst3 {
 
 Sound::Sound(Myst3Engine *vm) :
@@ -511,7 +512,7 @@ Audio::RewindableAudioStream *SoundChannel::makeAudioStream(const Common::String
 		return NULL;
 #endif
 	} else if (isWMA) {
-		return Audio::makeASFStream(s, DisposeAfterUse::YES);
+		return Video::makeASFAudioStream(s, DisposeAfterUse::YES);
 	} else {
 		return Audio::makeWAVStream(s, DisposeAfterUse::YES);
 	}
diff --git a/engines/testbed/video.cpp b/engines/testbed/video.cpp
index 77e54cdc373..225ca6aa177 100644
--- a/engines/testbed/video.cpp
+++ b/engines/testbed/video.cpp
@@ -21,7 +21,9 @@
 
 #include "common/events.h"
 #include "common/file.h"
+#include "common/fs.h"
 #include "engines/util.h"
+#include "video/asf_decoder.h"
 #include "video/avi_decoder.h"
 #include "video/dxa_decoder.h"
 #include "video/flic_decoder.h"
@@ -43,6 +45,10 @@
 namespace Testbed {
 
 Common::Error Videotests::videoTest(const Common::Path &path) {
+	const Common::FSNode node(path);
+	if (node.exists() && !node.isDirectory())
+		return videoTest(node);
+
 	Common::File *file = new Common::File();
 	if (!file->open(path)) {
 		warning("Cannot open file %s", path.toString(Common::Path::kNativeSeparator).c_str());
@@ -66,7 +72,20 @@ Common::Error Videotests::videoTest(Common::SeekableReadStream *stream, const Co
 	Video::QuickTimeDecoder *qtVideo = nullptr;
 	Video::VideoDecoder *video = nullptr;
 
-	if (name.hasSuffixIgnoreCase(".avi")) {
+	// ASF movies may use game-specific extensions such as .dat.
+	static const byte asfHeader[] = {
+		0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11,
+		0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c
+	};
+	byte header[sizeof(asfHeader)];
+	const int64 start = stream->pos();
+	const bool isASF = stream->read(header, sizeof(header)) == sizeof(header) &&
+		memcmp(header, asfHeader, sizeof(header)) == 0;
+	stream->seek(start);
+
+	if (isASF) {
+		video = new Video::ASFDecoder();
+	} else if (name.hasSuffixIgnoreCase(".avi")) {
 		video = new Video::AVIDecoder();
 	} else if (name.hasSuffixIgnoreCase(".dxa")) {
 		video = new Video::DXADecoder();
@@ -208,8 +227,8 @@ Common::Error Videotests::videoTest(Common::SeekableReadStream *stream, const Co
 				}
 			}
 			g_system->updateScreen();
-			video->delayMillis(10);
 		}
+		video->delayMillis(10);
 	}
 	video->close();
 	delete video;
diff --git a/video/asf.cpp b/video/asf.cpp
new file mode 100644
index 00000000000..2cce7cc9884
--- /dev/null
+++ b/video/asf.cpp
@@ -0,0 +1,765 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * ASF demuxer.
+ *
+ * This is a ScummVM port of FFmpeg's libavformat/asfdec_f.c at revision
+ * c23123630e6a7e645c199599b8ade3fe7e9ab3db:
+ * https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/c23123630e6a7e645c199599b8ade3fe7e9ab3db
+ * The original code was Copyright (c) 2000, 2001 Fabrice Bellard and
+ * distributed under the GNU LGPL version 2.1 or later. The migrated path
+ * includes stream properties, payload extensions, packet synchronization,
+ * fragmented and compressed payloads, timestamps and audio descrambling.
+ * Audio handling is based on xoreos' ASF code, in turn based on FFmpeg.
+ */
+
+#include "video/asf.h"
+#include "audio/decoders/wave_types.h"
+#include "common/endian.h"
+#include "common/memstream.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+
+namespace Video {
+
+static const byte kHeader[16] = {0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C};
+static const byte kFile[16] = {0xA1, 0xDC, 0xAB, 0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65};
+static const byte kStream[16] = {0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65};
+static const byte kDataGuid[16] = {0x36, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c};
+static const byte kAudioGuid[16] = {0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B};
+static const byte kVideoGuid[16] = {0xC0, 0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B};
+static const byte kExtendedStream[16] = {0xCB, 0xA5, 0xE6, 0x14, 0x72, 0xC6, 0x32, 0x43, 0x83, 0x99, 0xA9, 0x69, 0x52, 0x06, 0x5B, 0x5A};
+static const byte kHeaderExtension[16] = {0xb5, 0x03, 0xbf, 0x5f, 0x2E, 0xA9, 0xCF, 0x11, 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65};
+static const byte kCommand[16] = {0xC0, 0xCF, 0xDA, 0x59, 0xE6, 0x59, 0xD0, 0x11, 0xA3, 0xAC, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6};
+static const byte kJFIF[16] = {0x00, 0xE1, 0x1B, 0xB6, 0x4E, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B};
+static const byte kEmbeddedStream[16] = {0xe2, 0x65, 0xfb, 0x3a, 0xEF, 0x47, 0xF2, 0x40, 0xac, 0x2c, 0x70, 0xa9, 0x0d, 0x71, 0xd3, 0x43};
+static const byte kEmbeddedAudio[16] = {0x9d, 0x8c, 0x17, 0x31, 0xE1, 0x03, 0x28, 0x45, 0xb5, 0x82, 0x3d, 0xf9, 0xdb, 0x22, 0xf5, 0x03};
+
+struct ASFDemuxer::Context {
+	struct Payload {
+		byte type;
+		uint16 size;
+	};
+	struct StreamState {
+		int index = -1;
+		Object object;
+		uint32 fragOffset = 0;
+		uint32 objectSize = 0;
+		bool clean = false;
+		bool discardZeroMPEG2 = false, paletteChanged = false;
+		Common::Array<uint32> palette;
+		uint8 dsSpan = 0;
+		uint16 dsPacketSize = 0, dsChunkSize = 0;
+		uint64 frameTime = 0;
+		Common::Array<Payload> payloads;
+	};
+	struct PacketState {
+		int sizeLeft = 0, flags = 0, property = 0, timestamp = 0;
+		int segSizeType = 0, segments = 0, seq = 0, replicaSize = 0;
+		int keyFrame = 0, padding = 0, multiSize = 0, timeDelta = 0;
+		uint32 fragOffset = 0, fragSize = 0;
+		int64 fragTimestamp = 0, timeStart = 0, position = 0;
+	};
+
+	Common::SeekableReadStream *input = nullptr;
+	Common::Array<Stream> streams;
+	StreamState states[128];
+	StreamState *current = nullptr;
+	PacketState packet;
+	uint32 minPacketSize = 0, maxPacketSize = 0, flags = 0;
+	uint64 dataStart = 0, dataEnd = 0, duration = 0, sendDuration = 0;
+	uint32 preroll = 0;
+	int usesStdECC = 0, streamID = 0;
+	bool timestampIsPTS = false, eof = false;
+
+	bool readHeader();
+	bool readStream(uint64 end);
+	bool readExtendedStream(uint64 end);
+	bool getPacket();
+	bool readFrameHeader();
+	int parsePacket(Object &object);
+	bool atEnd() const { return input->eos() || input->pos() >= input->size(); }
+	uint32 readValue(int bits, uint32 fallback, int &size);
+	bool skip(int64 amount) {
+		int64 position = input->pos();
+		if (amount < -position)
+			return false;
+		return seek(MIN<uint64>(position + amount, input->size()));
+	}
+	bool seek(uint64 position) { return position <= (uint64)input->size() && input->seek(position); }
+};
+
+uint32 ASFDemuxer::Context::readValue(int bits, uint32 fallback, int &size) {
+	switch (bits & 3) {
+	case 3: size += 4; return input->readUint32LE();
+	case 2: size += 2; return input->readUint16LE();
+	case 1: ++size; return input->readByte();
+	default: return fallback;
+	}
+}
+
+bool ASFDemuxer::Context::readHeader() {
+	byte guid[16];
+	if (input->read(guid, 16) != 16 || memcmp(guid, kHeader, 16))
+		return false;
+	skip(14);
+	bool haveFile = false;
+	while (input->size() - input->pos() >= 24) {
+		uint64 start = input->pos();
+		input->read(guid, 16);
+		uint64 size = input->readUint64LE();
+		if (!memcmp(guid, kDataGuid, 16)) {
+			if (!haveFile || streams.empty() || input->size() - input->pos() < 26)
+				return false;
+			dataEnd = (!(flags & 1) && size >= 100 && size <= (uint64)-1 - start)
+				? start + size : (uint64)input->size();
+			skip(26);
+			dataStart = input->pos();
+			return true;
+		}
+		if (size < 24 || size > (uint64)input->size() - start)
+			return false;
+		uint64 end = start + size;
+		if (!memcmp(guid, kFile, 16)) {
+			if (size < 104)
+				return false;
+			skip(40);
+			uint64 playTime = input->readUint64LE();
+			sendDuration = input->readUint64LE() / 10000;
+			preroll = input->readUint32LE();
+			input->readUint32LE();
+			flags = input->readUint32LE();
+			minPacketSize = input->readUint32LE();
+			maxPacketSize = input->readUint32LE();
+			if (minPacketSize >= (1U << 29))
+				return false;
+			duration = playTime / 10000 > preroll ? playTime / 10000 - preroll : 0;
+			haveFile = true;
+		} else if (!memcmp(guid, kStream, 16)) {
+			if (!readStream(end))
+				return false;
+		} else if (!memcmp(guid, kExtendedStream, 16)) {
+			if (!readExtendedStream(end))
+				return false;
+			continue;
+		} else if (!memcmp(guid, kHeaderExtension, 16)) {
+			if (end - input->pos() < 22)
+				return false;
+			skip(22);
+			continue;
+		}
+		if (!seek(end))
+			return false;
+	}
+	return false;
+}
+
+bool ASFDemuxer::Context::readStream(uint64 end) {
+	if (end - input->pos() < 54 || streams.size() == 127)
+		return false;
+	Stream stream;
+	byte guid[16];
+	input->read(guid, 16);
+	bool embedded = !memcmp(guid, kEmbeddedStream, 16);
+	if (!memcmp(guid, kAudioGuid, 16))
+		stream.type = kAudio;
+	else if (!memcmp(guid, kVideoGuid, 16) || !memcmp(guid, kJFIF, 16))
+		stream.type = kVideo;
+	else if (!memcmp(guid, kCommand, 16))
+		stream.type = kData;
+	else if (!embedded)
+		return false;
+	skip(16);
+	stream.timeOffset = input->readUint64LE();
+	uint32 typeSize = input->readUint32LE();
+	input->readUint32LE();
+	stream.id = input->readUint16LE() & 0x7f;
+	skip(4);
+	if (embedded) {
+		if (end - input->pos() < 16)
+			return false;
+		input->read(guid, 16);
+		if (!memcmp(guid, kEmbeddedAudio, 16)) {
+			if (end - input->pos() < 48)
+				return false;
+			stream.type = kAudio;
+			skip(48);
+		}
+	}
+	if (stream.type == kVideo && end - input->pos() >= 51) {
+		stream.typeData.resize(51);
+		if (input->read(stream.typeData.data(), 51) != 51)
+			return false;
+		uint32 bitmapSize = READ_LE_UINT32(stream.typeData.data() + 11);
+		uint32 extra = bitmapSize > 40 ? bitmapSize - 40 : 0;
+		if (extra > end - input->pos())
+			return false;
+		stream.typeData.resize(51 + extra);
+		if (input->read(stream.typeData.data() + 51, extra) != extra)
+			return false;
+	} else {
+		if (typeSize > end - input->pos())
+			return false;
+		stream.typeData.resize(typeSize);
+		if (input->read(stream.typeData.data(), typeSize) != typeSize)
+			return false;
+	}
+	uint32 errorSize = stream.type == kAudio && end - input->pos() >= 8 ? 8 : 0;
+	stream.errorData.resize(errorSize);
+	if (input->read(stream.errorData.data(), errorSize) != errorSize)
+		return false;
+	StreamState &state = states[stream.id];
+	state.index = streams.size();
+	stream.frameTime = state.frameTime;
+	if (stream.type == kAudio && errorSize >= 8) {
+		state.dsSpan = stream.errorData[0];
+		state.dsPacketSize = READ_LE_UINT16(stream.errorData.data() + 1);
+		state.dsChunkSize = READ_LE_UINT16(stream.errorData.data() + 3);
+		if (state.dsSpan > 1 && (!state.dsChunkSize || state.dsPacketSize / state.dsChunkSize <= 1 ||
+		                        state.dsPacketSize % state.dsChunkSize))
+			state.dsSpan = 0;
+	}
+	if (stream.type == kVideo && stream.typeData.size() >= 51) {
+		const byte *bitmap = stream.typeData.data() + 11;
+		uint32 tag = READ_BE_UINT32(bitmap + 16);
+		switch (tag) {
+		case MKTAG('m', 'p', 'g', '2'):
+		case MKTAG('M', 'P', 'E', 'G'):
+		case MKTAG('P', 'I', 'M', '2'):
+		case MKTAG('V', 'C', 'R', '2'):
+		case MKTAG( 2 ,  0 ,  0 ,  16):
+		case MKTAG('D', 'V', 'R', ' '):
+		case MKTAG('M', 'M', 'E', 'S'):
+		case MKTAG('L', 'M', 'P', '2'):
+		case MKTAG('s', 'l', 'i', 'f'):
+		case MKTAG('E', 'M', '2', 'V'):
+		case MKTAG('M', '7', '0', '1'):
+		case MKTAG('M', '7', '0', '2'):
+		case MKTAG('M', '7', '0', '3'):
+		case MKTAG('M', '7', '0', '4'):
+		case MKTAG('M', '7', '0', '5'):
+		case MKTAG('m', 'p', 'g', 'v'):
+			state.discardZeroMPEG2 = true;
+			break;
+		default:
+			break;
+		}
+		uint32 bitmapSize = READ_LE_UINT32(bitmap);
+		uint16 bits = READ_LE_UINT16(bitmap + 14);
+		if (bitmapSize > 40 && bits <= 8) {
+			uint32 bytes = MIN<uint32>(MIN<uint32>(bitmapSize - 40, stream.typeData.size() - 51), 1024);
+			state.palette.resize(256);
+			for (uint i = 0; i < bytes / 4; ++i)
+				state.palette[i] = READ_LE_UINT32(bitmap + 40 + i * 4);
+			state.paletteChanged = true;
+		}
+	}
+	streams.push_back(stream);
+	return true;
+}
+
+bool ASFDemuxer::Context::readExtendedStream(uint64 end) {
+	if (end - input->pos() < 64)
+		return false;
+	skip(48);
+	uint16 id = input->readUint16LE();
+	input->readUint16LE();
+	uint64 frameTime = input->readUint64LE();
+	uint16 names = input->readUint16LE();
+	uint16 extensions = input->readUint16LE();
+	if (id < 128) {
+		states[id].frameTime = frameTime;
+		states[id].payloads.clear();
+		if (states[id].index >= 0)
+			streams[states[id].index].frameTime = frameTime;
+	}
+	for (uint i = 0; i < names; ++i) {
+		if (end - input->pos() < 4)
+			return false;
+		input->readUint16LE();
+		uint16 length = input->readUint16LE();
+		if (length > end - input->pos())
+			return false;
+		skip(length);
+	}
+	for (uint i = 0; i < extensions; ++i) {
+		if (end - input->pos() < 22)
+			return false;
+		byte guid[16];
+		input->read(guid, 16);
+		Payload payload = {guid[0], input->readUint16LE()};
+		uint32 length = input->readUint32LE();
+		if (length > end - input->pos())
+			return false;
+		skip(length);
+		if (id < 128 && i < 8)
+			states[id].payloads.push_back(payload);
+	}
+	return true;
+}
+
+bool ASFDemuxer::Context::getPacket() {
+	int size = 8;
+	int c = -1, d = -1, e = -1;
+	if (usesStdECC > 0) {
+		int left = 32768;
+		while (left-- > 0) {
+			c = d;
+			d = e;
+			e = input->readByte();
+			if (c == 0x82 && !d && !e)
+				break;
+		}
+		if ((c & 0x8f) == 0x82) {
+			if (d || e)
+				return false;
+			c = input->readByte();
+			d = input->readByte();
+			size += 3;
+		} else if (!atEnd()) {
+			skip(-1);
+		}
+	} else {
+		c = input->readByte();
+		if (c & 0x80) {
+			++size;
+			if (!(c & 0x60)) {
+				d = input->readByte();
+				e = input->readByte();
+				skip((c & 0xf) - 2);
+				size += c & 0xf;
+			}
+			if (!usesStdECC)
+				usesStdECC = (c == 0x82 && !d && !e) ? 1 : -1;
+			c = input->readByte();
+		} else {
+			usesStdECC = -1;
+		}
+		d = input->readByte();
+	}
+	packet.flags = c;
+	packet.property = d;
+	uint32 length = readValue(packet.flags >> 5, maxPacketSize, size);
+	readValue(packet.flags >> 1, 0, size);
+	uint32 padding = readValue(packet.flags >> 3, 0, size);
+	if (!length || length >= (1U << 29) || padding >= length)
+		return false;
+	packet.timestamp = input->readUint32LE();
+	input->readUint16LE();
+	if (packet.flags & 1) {
+		packet.segSizeType = input->readByte();
+		++size;
+		packet.segments = packet.segSizeType & 0x3f;
+	} else {
+		packet.segments = 1;
+		packet.segSizeType = 0x80;
+	}
+	if ((uint32)size > length - padding) {
+		packet.sizeLeft = 0;
+		return false;
+	}
+	packet.sizeLeft = length - padding - size;
+	if (length < minPacketSize)
+		padding += minPacketSize - length;
+	packet.padding = padding;
+	return true;
+}
+
+bool ASFDemuxer::Context::readFrameHeader() {
+	int size = 1;
+	int num = input->readByte();
+	--packet.segments;
+	packet.keyFrame = num >> 7;
+	streamID = num & 0x7f;
+	StreamState &state = states[streamID];
+	packet.seq = readValue(packet.property >> 4, 0, size);
+	packet.fragOffset = readValue(packet.property >> 2, 0, size);
+	packet.replicaSize = readValue(packet.property, 0, size);
+	if (size + (int64)packet.replicaSize > packet.sizeLeft)
+		return false;
+	if (packet.replicaSize >= 8) {
+		uint64 end = input->pos() + packet.replicaSize;
+		state.objectSize = input->readUint32LE();
+		if (state.objectSize >= (1U << 24)) {
+			state.objectSize = 0;
+			return false;
+		}
+		packet.fragTimestamp = input->readUint32LE();
+		for (const auto &payload : state.payloads) {
+			int length = payload.size;
+			if (length == 0xffff)
+				length = input->readUint16LE();
+			uint64 payloadEnd = input->pos() + length;
+			if (payloadEnd > end)
+				break;
+			switch (payload.type) {
+			case 0x54: {
+				int aspectNum = input->readByte();
+				int den = input->readByte();
+				if (aspectNum > 0 && den > 0 && state.index >= 0) {
+					streams[state.index].aspectNum = aspectNum;
+					streams[state.index].aspectDen = den;
+				}
+				break;
+			}
+			case 0x2a: {
+				skip(8);
+				int64 ts = input->readSint64LE();
+				input->readUint64LE();
+				packet.fragTimestamp = ts != -1 ? ts / 10000 : kNoTimestamp;
+				timestampIsPTS = true;
+				break;
+			}
+			default:
+				break;
+			}
+			if (!seek(payloadEnd))
+				return false;
+		}
+		if (!seek(end))
+			return false;
+		size += packet.replicaSize;
+	} else if (packet.replicaSize == 1) {
+		packet.timeStart = packet.fragOffset;
+		packet.fragOffset = 0;
+		packet.fragTimestamp = packet.timestamp;
+		packet.timeDelta = input->readByte();
+		++size;
+	} else if (packet.replicaSize != 0) {
+		return false;
+	}
+	if (packet.flags & 1) {
+		packet.fragSize = readValue(packet.segSizeType >> 6, 0, size);
+		if (size > packet.sizeLeft)
+			return false;
+		if (packet.fragSize > (uint32)(packet.sizeLeft - size)) {
+			if (packet.fragSize > (uint32)(packet.sizeLeft - size + packet.padding))
+				return false;
+			int diff = packet.fragSize - (packet.sizeLeft - size);
+			packet.sizeLeft += diff;
+			packet.padding -= diff;
+		}
+	} else {
+		packet.fragSize = packet.sizeLeft - size;
+	}
+	if (packet.replicaSize == 1) {
+		packet.multiSize = packet.fragSize;
+		if (packet.multiSize > packet.sizeLeft)
+			return false;
+	}
+	packet.sizeLeft -= size;
+	return true;
+}
+
+int ASFDemuxer::Context::parsePacket(Object &object) {
+	for (;;) {
+		if (atEnd())
+			return -1;
+		if (packet.sizeLeft < 6 || (packet.segments < 1 && packet.timeStart == 0)) {
+			int remaining = packet.sizeLeft + packet.padding;
+			if (remaining < 0 || !skip(remaining))
+				return -1;
+			packet.position = input->pos();
+			if ((uint64)packet.position >= dataEnd)
+				return -1;
+			return 1;
+		}
+		if (packet.timeStart == 0) {
+			if (!readFrameHeader()) {
+				packet.timeStart = packet.segments = 0;
+				continue;
+			}
+			if (states[streamID].index < 0) {
+				packet.timeStart = 0;
+				skip(packet.fragSize);
+				packet.sizeLeft -= packet.fragSize;
+				continue;
+			}
+			current = &states[streamID];
+		}
+		StreamState &state = *current;
+		if (!state.fragOffset && packet.fragOffset) {
+			skip(packet.fragSize);
+			packet.sizeLeft -= packet.fragSize;
+			continue;
+		}
+		if (packet.replicaSize == 1) {
+			packet.fragTimestamp = packet.timeStart;
+			packet.timeStart += packet.timeDelta;
+			state.objectSize = packet.fragSize = input->readByte();
+			--packet.sizeLeft;
+			--packet.multiSize;
+			if (packet.multiSize < (int)state.objectSize) {
+				packet.timeStart = 0;
+				skip(packet.multiSize);
+				packet.sizeLeft -= packet.multiSize;
+				continue;
+			}
+			packet.multiSize -= state.objectSize;
+		}
+		if (state.object.data.size() != state.objectSize ||
+		    state.fragOffset + packet.fragSize > state.object.data.size()) {
+			if (!state.object.data.empty()) {
+				state.fragOffset = 0;
+				state.object = Object();
+			}
+			state.object.data.resize(state.objectSize);
+			state.object.sequence = packet.seq;
+			if (packet.fragTimestamp != kNoTimestamp) {
+				if (timestampIsPTS)
+					state.object.pts = packet.fragTimestamp - preroll;
+				else
+					state.object.dts = packet.fragTimestamp - preroll;
+			}
+			state.object.stream = streams[state.index].id;
+			state.object.position = packet.position;
+			state.clean = false;
+			if (state.paletteChanged) {
+				state.object.palette = state.palette;
+				state.paletteChanged = false;
+			}
+			if (streams[state.index].type == kAudio)
+				packet.keyFrame = 1;
+			state.object.keyframe = packet.keyFrame != 0;
+		}
+		packet.sizeLeft -= packet.fragSize;
+		if (packet.sizeLeft < 0)
+			continue;
+		if (packet.fragOffset >= state.object.data.size() ||
+		    packet.fragSize > state.object.data.size() - packet.fragOffset)
+			continue;
+		if (packet.fragOffset != state.fragOffset && !state.clean) {
+			memset(state.object.data.data() + state.fragOffset, 0, state.object.data.size() - state.fragOffset);
+			state.clean = true;
+		}
+		uint32 read = input->read(state.object.data.data() + packet.fragOffset, packet.fragSize);
+		if (read != packet.fragSize) {
+			if (packet.fragOffset + read == 0)
+				return -1;
+			if (state.dsSpan > 1) {
+				memset(state.object.data.data() + packet.fragOffset + read, 0, packet.fragSize - read);
+				read = packet.fragSize;
+			} else {
+				state.object.data.resize(packet.fragOffset + read);
+			}
+		}
+		state.fragOffset += read;
+		if (state.fragOffset != state.object.data.size())
+			continue;
+		if (state.discardZeroMPEG2 && state.object.data.size() > 100) {
+			uint i = 0;
+			while (i < state.object.data.size() && !state.object.data[i])
+				++i;
+			if (i == state.object.data.size()) {
+				state.fragOffset = 0;
+				state.object = Object();
+				continue;
+			}
+		}
+		if (state.dsSpan > 1 && state.object.data.size() == (uint32)state.dsPacketSize * state.dsSpan) {
+			Common::Array<byte> descrambled;
+			descrambled.resize(state.object.data.size());
+			for (uint offset = 0; offset < descrambled.size(); offset += state.dsChunkSize) {
+				uint off = offset / state.dsChunkSize;
+				uint row = off / state.dsSpan;
+				uint col = off % state.dsSpan;
+				uint index = row + col * state.dsPacketSize / state.dsChunkSize;
+				memcpy(descrambled.data() + offset, state.object.data.data() + index * state.dsChunkSize, state.dsChunkSize);
+			}
+			state.object.data = descrambled;
+		}
+		state.fragOffset = 0;
+		object = state.object;
+		state.object = Object();
+		return 0;
+	}
+}
+
+ASFDemuxer::ASFDemuxer() : _ctx(new Context()) {}
+ASFDemuxer::~ASFDemuxer() { delete _ctx; }
+
+void ASFDemuxer::close() {
+	delete _ctx;
+	_ctx = new Context();
+}
+
+bool ASFDemuxer::open(Common::SeekableReadStream *stream) {
+	close();
+	_ctx->input = stream;
+	if (!stream || !stream->seek(0) || !_ctx->readHeader()) {
+		close();
+		return false;
+	}
+	return true;
+}
+
+bool ASFDemuxer::rewind() {
+	if (!_ctx->input || !_ctx->input->seek(_ctx->dataStart))
+		return false;
+	_ctx->input->clearErr();
+	_ctx->packet = Context::PacketState();
+	_ctx->current = nullptr;
+	_ctx->eof = false;
+	for (auto &state : _ctx->states) {
+		state.object = Object();
+		state.objectSize = state.fragOffset = 0;
+	}
+	return true;
+}
+
+ASFDemuxer::ReadResult ASFDemuxer::readObject(Object &object) {
+	if (!_ctx->input)
+		return kError;
+	for (;;) {
+		int result = _ctx->parsePacket(object);
+		if (!result)
+			return kObject;
+		if (result < 0) {
+			_ctx->eof = true;
+			return _ctx->input->err() ? kError : kEnd;
+		}
+		int64 before = _ctx->input->pos();
+		bool ok = _ctx->getPacket();
+		_ctx->packet.timeStart = 0;
+		if (!ok && _ctx->input->pos() == before && !_ctx->atEnd())
+			return kError;
+	}
+}
+
+bool ASFDemuxer::endOfData() const {
+	if (!_ctx->input || _ctx->eof)
+		return true;
+	const Context::PacketState &p = _ctx->packet;
+	return (p.sizeLeft < 6 || (p.segments < 1 && !p.timeStart)) &&
+		(uint64)(_ctx->input->pos() + p.sizeLeft + p.padding) >= MIN<uint64>(_ctx->dataEnd, _ctx->input->size());
+}
+
+const Common::Array<ASFDemuxer::Stream> &ASFDemuxer::getStreams() const { return _ctx->streams; }
+uint64 ASFDemuxer::getDuration() const { return _ctx->duration; }
+uint64 ASFDemuxer::getSendDuration() const { return _ctx->sendDuration; }
+uint64 ASFDemuxer::getPreroll() const { return _ctx->preroll; }
+
+bool ASFAudioCodec::open(const ASFDemuxer::Stream &stream) {
+	if (stream.type != ASFDemuxer::kAudio || stream.typeData.size() < 14)
+		return false;
+	Common::MemoryReadStream format(stream.typeData.data(), stream.typeData.size());
+	uint16 compression = format.readUint16LE();
+	_channels = format.readUint16LE();
+	_rate = format.readUint32LE();
+	uint32 bitRate = format.readUint32LE() * 8;
+	uint16 blockAlign = format.readUint16LE();
+	if (compression != Audio::kWaveFormatWMAv2 || !_channels || _channels > 2 || !_rate || _rate > 50000)
+		return false;
+	if (stream.typeData.size() != 14)
+		format.readUint16LE();
+	Common::SeekableReadStream *extra = nullptr;
+	if (stream.typeData.size() >= 18) {
+		uint32 size = MIN<uint32>(format.readUint16LE(), stream.typeData.size() - 18);
+		extra = format.readStream(size);
+	}
+	delete _codec;
+	_codec = new Audio::WMACodec(2, _rate, _channels, bitRate, blockAlign, extra);
+	delete extra;
+	return true;
+}
+
+Audio::AudioStream *ASFAudioCodec::decode(const ASFDemuxer::Object &object) {
+	Common::MemoryReadStream packet(object.data.data(), object.data.size());
+	return _codec->decodeFrame(packet);
+}
+
+class ASFAudioStream : public Audio::SeekableAudioStream {
+public:
+	ASFAudioStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose)
+		: _input(stream), _dispose(dispose), _current(nullptr), _id(0), _ended(false) {}
+	~ASFAudioStream() override {
+		delete _current;
+		if (_dispose == DisposeAfterUse::YES)
+			delete _input;
+	}
+	bool open() {
+		if (!_demux.open(_input))
+			return false;
+		for (const auto &stream : _demux.getStreams()) {
+			if (stream.type == ASFDemuxer::kAudio) {
+				_id = stream.id;
+				return _codec.open(stream) && !_demux.endOfData();
+			}
+		}
+		return false;
+	}
+	int readBuffer(int16 *buffer, int count) override {
+		int read = 0;
+		while (read < count && !endOfData()) {
+			if (_current) {
+				read += _current->readBuffer(buffer + read, count - read);
+				if (_current->endOfData()) {
+					delete _current;
+					_current = nullptr;
+				}
+			} else {
+				ASFDemuxer::Object object;
+				ASFDemuxer::ReadResult result = _demux.readObject(object);
+				if (result != ASFDemuxer::kObject) {
+					_ended = true;
+					if (result == ASFDemuxer::kError)
+						warning("ASF: Invalid audio packet");
+				} else if (object.stream == _id) {
+					_current = _codec.decode(object);
+				}
+			}
+		}
+		return read;
+	}
+	bool endOfData() const override { return !_current && (_ended || _demux.endOfData()); }
+	bool isStereo() const override { return _codec.isStereo(); }
+	int getRate() const override { return _codec.getRate(); }
+	Audio::Timestamp getLength() const override { return Audio::Timestamp(_demux.getSendDuration(), getRate()); }
+	bool seek(const Audio::Timestamp &time) override { return time == 0 && rewind(); }
+	bool rewind() override {
+		if (!_demux.rewind())
+			return false;
+		delete _current;
+		_current = nullptr;
+		_ended = false;
+		return true;
+	}
+private:
+	Common::SeekableReadStream *_input;
+	DisposeAfterUse::Flag _dispose;
+	ASFDemuxer _demux;
+	ASFAudioCodec _codec;
+	Audio::AudioStream *_current;
+	uint8 _id;
+	bool _ended;
+};
+
+Audio::SeekableAudioStream *makeASFAudioStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+	ASFAudioStream *audio = new ASFAudioStream(stream, disposeAfterUse);
+	if (!audio->open()) {
+		delete audio;
+		return nullptr;
+	}
+	return audio;
+}
+
+} // End of namespace Video
diff --git a/video/asf.h b/video/asf.h
new file mode 100644
index 00000000000..c78b2443360
--- /dev/null
+++ b/video/asf.h
@@ -0,0 +1,95 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef VIDEO_ASF_H
+#define VIDEO_ASF_H
+
+#include "audio/audiostream.h"
+#include "audio/decoders/wma.h"
+#include "common/array.h"
+#include "common/stream.h"
+
+namespace Video {
+
+class ASFDemuxer {
+public:
+	static const int64 kNoTimestamp = (-0x7fffffffffffffffLL - 1);
+	enum StreamType { kAudio, kVideo, kData, kUnknown };
+	enum ReadResult { kObject, kEnd, kError };
+
+	struct Stream {
+		uint8 id = 0;
+		StreamType type = kUnknown;
+		uint64 timeOffset = 0;
+		uint64 frameTime = 0;
+		uint32 aspectNum = 0, aspectDen = 0;
+		Common::Array<byte> typeData;
+		Common::Array<byte> errorData;
+	};
+
+	struct Object {
+		uint8 stream = 0;
+		uint32 sequence = 0;
+		int64 pts = kNoTimestamp, dts = kNoTimestamp;
+		int64 position = -1;
+		bool keyframe = false;
+		Common::Array<byte> data;
+		Common::Array<uint32> palette;
+	};
+
+	ASFDemuxer();
+	~ASFDemuxer();
+	bool open(Common::SeekableReadStream *stream);
+	void close();
+	bool rewind();
+	ReadResult readObject(Object &object);
+	const Common::Array<Stream> &getStreams() const;
+	uint64 getDuration() const;
+	uint64 getSendDuration() const;
+	uint64 getPreroll() const;
+	bool endOfData() const;
+
+private:
+	ASFDemuxer(const ASFDemuxer &) = delete;
+	ASFDemuxer &operator=(const ASFDemuxer &) = delete;
+	struct Context;
+	Context *_ctx;
+};
+
+Audio::SeekableAudioStream *makeASFAudioStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
+
+class ASFAudioCodec {
+public:
+	ASFAudioCodec() : _codec(nullptr), _rate(0), _channels(0) {}
+	~ASFAudioCodec() { delete _codec; }
+	bool open(const ASFDemuxer::Stream &stream);
+	Audio::AudioStream *decode(const ASFDemuxer::Object &object);
+	int getRate() const { return _rate; }
+	bool isStereo() const { return _channels == 2; }
+private:
+	Audio::WMACodec *_codec;
+	uint32 _rate;
+	uint16 _channels;
+};
+
+} // End of namespace Video
+
+#endif
diff --git a/video/asf_decoder.cpp b/video/asf_decoder.cpp
new file mode 100644
index 00000000000..d55fd7c1dc5
--- /dev/null
+++ b/video/asf_decoder.cpp
@@ -0,0 +1,287 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "video/asf_decoder.h"
+#include "video/asf.h"
+#include "common/bufferedstream.h"
+#include "common/memstream.h"
+#include "common/textconsole.h"
+#include "image/codecs/codec.h"
+#ifdef USE_VC1
+#include "image/codecs/vc1.h"
+#endif
+#include "graphics/surface.h"
+
+namespace Video {
+
+class ASFDecoder::ASFVideoTrack : public VideoTrack {
+public:
+	ASFVideoTrack() : _codec(nullptr), _pending(nullptr), _width(0), _height(0),
+		_frame(-1), _time(0), _vc1(false) {}
+	~ASFVideoTrack() override { delete _codec; _surface.free(); }
+	bool open(const ASFDemuxer::Stream &stream) {
+		if (stream.typeData.size() < 51)
+			return false;
+		Common::MemoryReadStream format(stream.typeData.data(), stream.typeData.size());
+		format.skip(9);
+		format.readUint16LE();
+		uint32 headerSize = format.readUint32LE();
+		int32 width = format.readSint32LE();
+		int32 height = format.readSint32LE();
+		format.readUint16LE();
+		uint16 bits = format.readUint16LE();
+		uint32 tag = format.readUint32BE();
+		if (headerSize < 40 || headerSize > stream.typeData.size() - 11 ||
+		    width <= 0 || width > 65535 || height <= 0 || height > 65535)
+			return false;
+		_width = width;
+		_height = height;
+		_vc1 = tag == MKTAG('W','M','V','3') || tag == MKTAG('w','m','v','3') ||
+		       tag == MKTAG('W','V','C','1') || tag == MKTAG('w','v','c','1') || tag == MKTAG('W','M','V','A');
+		_codec = Image::createBitmapCodec(tag, 0, width, height, bits);
+		if (!_codec)
+			return false;
+#ifdef USE_VC1
+		if (_vc1) {
+			Common::MemoryReadStream extra(stream.typeData.data() + 51, headerSize - 40);
+			if (!static_cast<Image::WMV3Decoder *>(_codec)->setExtraData(extra))
+				return false;
+		}
+#endif
+		return true;
+	}
+	uint16 getWidth() const override { return _width; }
+	uint16 getHeight() const override { return _height; }
+	Graphics::PixelFormat getPixelFormat() const override { return _codec->getPixelFormat(); }
+	bool setOutputPixelFormat(const Graphics::PixelFormat &format) override {
+		return format == getPixelFormat();
+	}
+	int getCurFrame() const override { return _frame; }
+	uint32 getNextFrameStartTime() const override { return _time; }
+	bool endOfTrack() const override { return !_pending; }
+	const Graphics::Surface *decodeNextFrame() override { return _surface.getPixels() ? &_surface : nullptr; }
+	void consume() {
+		if (_pending) {
+			_surface.copyFrom(*_pending);
+			_pending = nullptr;
+			++_frame;
+		}
+	}
+	bool decode(const ASFDemuxer::Object &object) {
+		_times.push_back(object.pts != ASFDemuxer::kNoTimestamp ? object.pts : object.dts);
+		Common::MemoryReadStream data(object.data.data(), object.data.size());
+		_pending = _codec->decodeFrame(data);
+		return setTime();
+	}
+	bool drain() {
+#ifdef USE_VC1
+		if (_vc1)
+			_pending = static_cast<Image::WMV3Decoder *>(_codec)->drainFrame();
+#endif
+		return setTime();
+	}
+	bool failed() const {
+#ifdef USE_VC1
+		if (_vc1)
+			return static_cast<Image::WMV3Decoder *>(_codec)->hasError();
+#endif
+		return !_vc1 && !_pending;
+	}
+private:
+	bool setTime() {
+		if (!_pending)
+			return false;
+		if (_times.empty())
+			return false;
+		uint first = 0;
+		for (uint i = 1; i < _times.size(); ++i)
+			if (_times[i] < _times[first])
+				first = i;
+		_time = MAX<int64>(0, _times[first]);
+		_times.remove_at(first);
+		return true;
+	}
+	Image::Codec *_codec;
+	const Graphics::Surface *_pending;
+	Graphics::Surface _surface;
+	Common::Array<int64> _times;
+	uint16 _width, _height;
+	int _frame;
+	uint32 _time;
+	bool _vc1;
+};
+
+class ASFDecoder::ASFAudioTrack : public AudioTrack {
+public:
+	ASFAudioTrack(Audio::Mixer::SoundType soundType) : AudioTrack(soundType), _queue(nullptr) {}
+	~ASFAudioTrack() override { delete _queue; }
+	bool open(const ASFDemuxer::Stream &stream) {
+		if (!_codec.open(stream))
+			return false;
+		_queue = Audio::makeQueuingAudioStream(_codec.getRate(), _codec.isStereo());
+		return true;
+	}
+	void decode(const ASFDemuxer::Object &object) {
+		Audio::AudioStream *audio = _codec.decode(object);
+		if (audio)
+			_queue->queueAudioStream(audio, DisposeAfterUse::YES);
+	}
+	void finish() { _queue->finish(); }
+	bool hasEnoughPackets() const { return _queue->numQueuedStreams() > 25; }
+protected:
+	Audio::AudioStream *getAudioStream() const override { return _queue; }
+private:
+	ASFAudioCodec _codec;
+	Audio::QueuingAudioStream *_queue;
+};
+
+ASFDecoder::ASFDecoder() : _input(nullptr), _videoQueueBytes(0), _video(nullptr), _audio(nullptr),
+	_videoID(0), _audioID(0), _ended(false) {}
+
+ASFDecoder::~ASFDecoder() {
+	close();
+}
+
+void ASFDecoder::close() {
+	VideoDecoder::close();
+	_video = nullptr;
+	_audio = nullptr;
+	_demux.close();
+	_videoObjects.clear();
+	_videoQueueBytes = 0;
+	delete _input;
+	_input = nullptr;
+	_videoID = _audioID = 0;
+	_ended = false;
+}
+
+bool ASFDecoder::loadStream(Common::SeekableReadStream *stream) {
+	close();
+	_input = Common::wrapBufferedSeekableReadStream(stream, 1024 * 1024, DisposeAfterUse::YES);
+	if (!_demux.open(_input)) {
+		close();
+		return false;
+	}
+	for (const auto &info : _demux.getStreams()) {
+		if (info.type == ASFDemuxer::kVideo && !_video) {
+			ASFVideoTrack *track = new ASFVideoTrack();
+			if (!track->open(info)) {
+				delete track;
+				delete _audio;
+				_audio = nullptr;
+				close();
+				return false;
+			}
+			_video = track;
+			_videoID = info.id;
+		} else if (info.type == ASFDemuxer::kAudio && !_audio) {
+			ASFAudioTrack *track = new ASFAudioTrack(getSoundType());
+			if (!track->open(info)) {
+				delete track;
+				delete _video;
+				_video = nullptr;
+				close();
+				return false;
+			}
+			_audio = track;
+			_audioID = info.id;
+		}
+	}
+	if (!_video || !prepareFrame()) {
+		delete _video;
+		delete _audio;
+		_video = nullptr;
+		_audio = nullptr;
+		close();
+		return false;
+	}
+	bufferPackets();
+	addTrack(_video);
+	if (_audio)
+		addTrack(_audio);
+	return true;
+}
+
+bool ASFDecoder::readObject(ASFDemuxer::Object &object) {
+	ASFDemuxer::ReadResult result = _demux.readObject(object);
+	if (result == ASFDemuxer::kObject)
+		return true;
+	_ended = true;
+	if (_audio)
+		_audio->finish();
+	if (result == ASFDemuxer::kError)
+		warning("ASF: Invalid packet");
+	return false;
+}
+
+void ASFDecoder::bufferPackets() {
+	while (!_ended) {
+		if (_videoQueueBytes > 15 * 1024 * 1024 ||
+		    (_videoObjects.size() > 25 && (!_audio || _audio->hasEnoughPackets())))
+			break;
+		ASFDemuxer::Object object;
+		if (!readObject(object))
+			break;
+		if (_audio && object.stream == _audioID) {
+			_audio->decode(object);
+		} else if (object.stream == _videoID) {
+			_videoQueueBytes += object.data.size();
+			_videoObjects.push(object);
+		}
+	}
+}
+
+bool ASFDecoder::prepareFrame() {
+	while (!_videoObjects.empty() || !_ended) {
+		ASFDemuxer::Object object;
+		if (!_videoObjects.empty()) {
+			object = _videoObjects.pop();
+			_videoQueueBytes -= object.data.size();
+		} else if (!readObject(object))
+			break;
+		if (_audio && object.stream == _audioID) {
+			_audio->decode(object);
+		} else if (_video && object.stream == _videoID) {
+			if (_video->decode(object))
+				return true;
+			if (_video->failed()) {
+				warning("ASF: Video decoding failed");
+				_ended = true;
+				_videoObjects.clear();
+				_videoQueueBytes = 0;
+				if (_audio)
+					_audio->finish();
+				return false;
+			}
+		}
+	}
+	return _video && _video->drain();
+}
+
+void ASFDecoder::readNextPacket() {
+	if (_video && !_video->endOfTrack()) {
+		_video->consume();
+		prepareFrame();
+		bufferPackets();
+	}
+}
+
+} // End of namespace Video
diff --git a/audio/decoders/asf.h b/video/asf_decoder.h
similarity index 53%
rename from audio/decoders/asf.h
rename to video/asf_decoder.h
index f9c1a76e7a3..6d75d998f44 100644
--- a/audio/decoders/asf.h
+++ b/video/asf_decoder.h
@@ -19,27 +19,38 @@
  *
  */
 
-// Based on xoreos' ASF code
+#ifndef VIDEO_ASF_DECODER_H
+#define VIDEO_ASF_DECODER_H
 
-#ifndef AUDIO_DECODERS_ASF_H
-#define AUDIO_DECODERS_ASF_H
+#include "video/video_decoder.h"
+#include "video/asf.h"
+#include "common/queue.h"
 
-namespace Audio {
+namespace Video {
 
-/**
- * Try to load a ASF from the given seekable stream and create a RewindableAudioStream
- * from that data.
- *
- * @param stream          The SeekableReadStream from which to read the ASF data.
- * @param disposeAfterUse Whether to delete the stream after use.
- *
- * @return A new SeekableAudioStream, or 0, if an error occurred.
- */
-
-SeekableAudioStream *makeASFStream(
-	Common::SeekableReadStream *stream,
-	DisposeAfterUse::Flag disposeAfterUse);
-
-} // End of namespace Audio
+class ASFDecoder : public VideoDecoder {
+public:
+	ASFDecoder();
+	~ASFDecoder() override;
+	bool loadStream(Common::SeekableReadStream *stream) override;
+	void close() override;
+protected:
+	void readNextPacket() override;
+private:
+	class ASFVideoTrack;
+	class ASFAudioTrack;
+	bool prepareFrame();
+	bool readObject(ASFDemuxer::Object &object);
+	void bufferPackets();
+	Common::SeekableReadStream *_input;
+	ASFDemuxer _demux;
+	Common::Queue<ASFDemuxer::Object> _videoObjects;
+	uint32 _videoQueueBytes;
+	ASFVideoTrack *_video;
+	ASFAudioTrack *_audio;
+	uint8 _videoID, _audioID;
+	bool _ended;
+};
 
-#endif // AUDIO_DECODERS_ASF_H
+} // End of namespace Video
+#endif
diff --git a/video/module.mk b/video/module.mk
index 7e17e2ff21d..04e84b2d33d 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -4,6 +4,8 @@ MODULE_OBJS := \
 	3do_decoder.o \
 	4xm_decoder.o \
 	4xm_utils.o \
+	asf.o \
+	asf_decoder.o \
 	avi_decoder.o \
 	coktel_decoder.o \
 	dxa_decoder.o \




More information about the Scummvm-git-logs mailing list