Initial commit
This commit is contained in:
642
video/psx_decoder.cpp
Normal file
642
video/psx_decoder.cpp
Normal file
@@ -0,0 +1,642 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// PlayStation Stream demuxer based on FFmpeg/libav
|
||||
// MDEC video emulation based on https://web.archive.org/web/20170411092531/https://kenai.com/downloads/jpsxdec/Old/PlayStation1_STR_format1-00.txt
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/adpcm.h"
|
||||
#include "common/bitstream.h"
|
||||
#include "common/compression/huffman.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "graphics/yuv_to_rgb.h"
|
||||
#include "image/codecs/codec.h"
|
||||
|
||||
#include "video/psx_decoder.h"
|
||||
|
||||
namespace Video {
|
||||
|
||||
// Here are the codes/lengths/symbols that are used for decoding
|
||||
// DC coefficients (version 3 frames only)
|
||||
|
||||
#define DC_CODE_COUNT 9
|
||||
#define DC_HUFF_VAL(b, n, p) (((b) << 16) | ((n) << 8) | (p))
|
||||
#define GET_DC_BITS(x) ((x) >> 16)
|
||||
#define GET_DC_NEG(x) ((int)(((x) >> 8) & 0xff))
|
||||
#define GET_DC_POS(x) ((int)((x) & 0xff))
|
||||
|
||||
static const uint32 s_huffmanDCChromaCodes[DC_CODE_COUNT] = {
|
||||
254, 126, 62, 30, 14, 6, 2, 1, 0
|
||||
};
|
||||
|
||||
static const byte s_huffmanDCChromaLengths[DC_CODE_COUNT] = {
|
||||
8, 7, 6, 5, 4, 3, 2, 2, 2
|
||||
};
|
||||
|
||||
static const uint32 s_huffmanDCLumaCodes[DC_CODE_COUNT] = {
|
||||
126, 62, 30, 14, 6, 5, 1, 0, 4
|
||||
};
|
||||
|
||||
static const byte s_huffmanDCLumaLengths[DC_CODE_COUNT] = {
|
||||
7, 6, 5, 4, 3, 3, 2, 2, 3
|
||||
};
|
||||
|
||||
static const uint32 s_huffmanDCSymbols[DC_CODE_COUNT] = {
|
||||
DC_HUFF_VAL(8, 255, 128), DC_HUFF_VAL(7, 127, 64), DC_HUFF_VAL(6, 63, 32),
|
||||
DC_HUFF_VAL(5, 31, 16), DC_HUFF_VAL(4, 15, 8), DC_HUFF_VAL(3, 7, 4),
|
||||
DC_HUFF_VAL(2, 3, 2), DC_HUFF_VAL(1, 1, 1), DC_HUFF_VAL(0, 0, 0)
|
||||
};
|
||||
|
||||
// Here are the codes/lengths/symbols that are used for decoding
|
||||
// DC coefficients (version 2 and 3 frames)
|
||||
|
||||
#define AC_CODE_COUNT 113
|
||||
#define AC_HUFF_VAL(z, a) ((z << 8) | a)
|
||||
#define ESCAPE_CODE ((uint32)-1) // arbitrary, just so we can tell what code it is
|
||||
#define END_OF_BLOCK ((uint32)-2) // arbitrary, just so we can tell what code it is
|
||||
#define GET_AC_ZERO_RUN(code) (code >> 8)
|
||||
#define GET_AC_COEFFICIENT(code) ((int)(code & 0xff))
|
||||
|
||||
static const uint32 s_huffmanACCodes[AC_CODE_COUNT] = {
|
||||
// Regular codes
|
||||
3, 3, 4, 5, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7,
|
||||
32, 33, 34, 35, 36, 37, 38, 39, 8, 9, 10, 11,
|
||||
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17,
|
||||
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
|
||||
29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18,
|
||||
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 26, 27, 28, 29, 30, 31,
|
||||
|
||||
// Escape code
|
||||
1,
|
||||
// End of block code
|
||||
2
|
||||
};
|
||||
|
||||
static const byte s_huffmanACLengths[AC_CODE_COUNT] = {
|
||||
// Regular codes
|
||||
2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
|
||||
8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10,
|
||||
10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13,
|
||||
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
|
||||
13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14,
|
||||
14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15,
|
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16,
|
||||
|
||||
// Escape code
|
||||
6,
|
||||
// End of block code
|
||||
2
|
||||
};
|
||||
|
||||
static const uint32 s_huffmanACSymbols[AC_CODE_COUNT] = {
|
||||
// Regular codes
|
||||
AC_HUFF_VAL(0, 1), AC_HUFF_VAL(1, 1), AC_HUFF_VAL(0, 2), AC_HUFF_VAL(2, 1), AC_HUFF_VAL(0, 3),
|
||||
AC_HUFF_VAL(4, 1), AC_HUFF_VAL(3, 1), AC_HUFF_VAL(7, 1), AC_HUFF_VAL(6, 1), AC_HUFF_VAL(1, 2),
|
||||
AC_HUFF_VAL(5, 1), AC_HUFF_VAL(2, 2), AC_HUFF_VAL(9, 1), AC_HUFF_VAL(0, 4), AC_HUFF_VAL(8, 1),
|
||||
AC_HUFF_VAL(13, 1), AC_HUFF_VAL(0, 6), AC_HUFF_VAL(12, 1), AC_HUFF_VAL(11, 1), AC_HUFF_VAL(3, 2),
|
||||
AC_HUFF_VAL(1, 3), AC_HUFF_VAL(0, 5), AC_HUFF_VAL(10, 1), AC_HUFF_VAL(16, 1), AC_HUFF_VAL(5, 2),
|
||||
AC_HUFF_VAL(0, 7), AC_HUFF_VAL(2, 3), AC_HUFF_VAL(1, 4), AC_HUFF_VAL(15, 1), AC_HUFF_VAL(14, 1),
|
||||
AC_HUFF_VAL(4, 2), AC_HUFF_VAL(0, 11), AC_HUFF_VAL(8, 2), AC_HUFF_VAL(4, 3), AC_HUFF_VAL(0, 10),
|
||||
AC_HUFF_VAL(2, 4), AC_HUFF_VAL(7, 2), AC_HUFF_VAL(21, 1), AC_HUFF_VAL(20, 1), AC_HUFF_VAL(0, 9),
|
||||
AC_HUFF_VAL(19, 1), AC_HUFF_VAL(18, 1), AC_HUFF_VAL(1, 5), AC_HUFF_VAL(3, 3), AC_HUFF_VAL(0, 8),
|
||||
AC_HUFF_VAL(6, 2), AC_HUFF_VAL(17, 1), AC_HUFF_VAL(10, 2), AC_HUFF_VAL(9, 2), AC_HUFF_VAL(5, 3),
|
||||
AC_HUFF_VAL(3, 4), AC_HUFF_VAL(2, 5), AC_HUFF_VAL(1, 7), AC_HUFF_VAL(1, 6), AC_HUFF_VAL(0, 15),
|
||||
AC_HUFF_VAL(0, 14), AC_HUFF_VAL(0, 13), AC_HUFF_VAL(0, 12), AC_HUFF_VAL(26, 1), AC_HUFF_VAL(25, 1),
|
||||
AC_HUFF_VAL(24, 1), AC_HUFF_VAL(23, 1), AC_HUFF_VAL(22, 1), AC_HUFF_VAL(0, 31), AC_HUFF_VAL(0, 30),
|
||||
AC_HUFF_VAL(0, 29), AC_HUFF_VAL(0, 28), AC_HUFF_VAL(0, 27), AC_HUFF_VAL(0, 26), AC_HUFF_VAL(0, 25),
|
||||
AC_HUFF_VAL(0, 24), AC_HUFF_VAL(0, 23), AC_HUFF_VAL(0, 22), AC_HUFF_VAL(0, 21), AC_HUFF_VAL(0, 20),
|
||||
AC_HUFF_VAL(0, 19), AC_HUFF_VAL(0, 18), AC_HUFF_VAL(0, 17), AC_HUFF_VAL(0, 16), AC_HUFF_VAL(0, 40),
|
||||
AC_HUFF_VAL(0, 39), AC_HUFF_VAL(0, 38), AC_HUFF_VAL(0, 37), AC_HUFF_VAL(0, 36), AC_HUFF_VAL(0, 35),
|
||||
AC_HUFF_VAL(0, 34), AC_HUFF_VAL(0, 33), AC_HUFF_VAL(0, 32), AC_HUFF_VAL(1, 14), AC_HUFF_VAL(1, 13),
|
||||
AC_HUFF_VAL(1, 12), AC_HUFF_VAL(1, 11), AC_HUFF_VAL(1, 10), AC_HUFF_VAL(1, 9), AC_HUFF_VAL(1, 8),
|
||||
AC_HUFF_VAL(1, 18), AC_HUFF_VAL(1, 17), AC_HUFF_VAL(1, 16), AC_HUFF_VAL(1, 15), AC_HUFF_VAL(6, 3),
|
||||
AC_HUFF_VAL(16, 2), AC_HUFF_VAL(15, 2), AC_HUFF_VAL(14, 2), AC_HUFF_VAL(13, 2), AC_HUFF_VAL(12, 2),
|
||||
AC_HUFF_VAL(11, 2), AC_HUFF_VAL(31, 1), AC_HUFF_VAL(30, 1), AC_HUFF_VAL(29, 1), AC_HUFF_VAL(28, 1),
|
||||
AC_HUFF_VAL(27, 1),
|
||||
|
||||
// Escape code
|
||||
ESCAPE_CODE,
|
||||
// End of block code
|
||||
END_OF_BLOCK
|
||||
};
|
||||
|
||||
PSXStreamDecoder::PSXStreamDecoder(CDSpeed speed, uint32 frameCount) : _speed(speed), _frameCount(frameCount) {
|
||||
_stream = 0;
|
||||
_videoTrack = 0;
|
||||
_audioTrack = 0;
|
||||
}
|
||||
|
||||
PSXStreamDecoder::~PSXStreamDecoder() {
|
||||
close();
|
||||
}
|
||||
|
||||
#define RAW_CD_SECTOR_SIZE 2352
|
||||
|
||||
#define CDXA_TYPE_MASK 0x0E
|
||||
#define CDXA_TYPE_DATA 0x08
|
||||
#define CDXA_TYPE_AUDIO 0x04
|
||||
#define CDXA_TYPE_VIDEO 0x02
|
||||
|
||||
bool PSXStreamDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||
close();
|
||||
|
||||
_stream = stream;
|
||||
readNextPacket();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PSXStreamDecoder::close() {
|
||||
VideoDecoder::close();
|
||||
_audioTrack = 0;
|
||||
_videoTrack = 0;
|
||||
_frameCount = 0;
|
||||
|
||||
delete _stream;
|
||||
_stream = 0;
|
||||
}
|
||||
|
||||
#define VIDEO_DATA_CHUNK_SIZE 2016
|
||||
#define VIDEO_DATA_HEADER_SIZE 56
|
||||
|
||||
void PSXStreamDecoder::readNextPacket() {
|
||||
Common::SeekableReadStream *sector = 0;
|
||||
byte *partialFrame = 0;
|
||||
int sectorsRead = 0;
|
||||
int64 prevPos = _stream->pos();
|
||||
|
||||
while (_stream->pos() < _stream->size()) {
|
||||
sector = readSector();
|
||||
sectorsRead++;
|
||||
|
||||
if (!sector)
|
||||
error("Corrupt PSX stream sector");
|
||||
|
||||
sector->seek(0x11);
|
||||
byte channel = sector->readByte();
|
||||
if (channel >= 32) {
|
||||
warning("Bad PSX stream channel");
|
||||
return;
|
||||
}
|
||||
|
||||
byte sectorType = sector->readByte() & CDXA_TYPE_MASK;
|
||||
|
||||
switch (sectorType) {
|
||||
case CDXA_TYPE_DATA:
|
||||
case CDXA_TYPE_VIDEO: {
|
||||
if (!_videoTrack) {
|
||||
_videoTrack = new PSXVideoTrack(sector, _speed, _frameCount, channel);
|
||||
addTrack(_videoTrack);
|
||||
|
||||
// If no video track is initialized, we are called
|
||||
// by loadStream(). Stop here, and start rendering
|
||||
// the track from the next call.
|
||||
_stream->seek(prevPos);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_videoTrack->getChannel() != channel) {
|
||||
warning("Unhandled multi-channel video");
|
||||
return;
|
||||
}
|
||||
|
||||
sector->seek(28);
|
||||
uint16 curSector = sector->readUint16LE();
|
||||
uint16 sectorCount = sector->readUint16LE();
|
||||
sector->readUint32LE();
|
||||
uint16 frameSize = sector->readUint32LE();
|
||||
|
||||
if (curSector >= sectorCount)
|
||||
error("Bad sector");
|
||||
|
||||
if (!partialFrame)
|
||||
partialFrame = (byte *)malloc(sectorCount * VIDEO_DATA_CHUNK_SIZE);
|
||||
|
||||
sector->seek(VIDEO_DATA_HEADER_SIZE);
|
||||
sector->read(partialFrame + curSector * VIDEO_DATA_CHUNK_SIZE, VIDEO_DATA_CHUNK_SIZE);
|
||||
|
||||
if (curSector == sectorCount - 1) {
|
||||
// Done assembling the frame
|
||||
Common::BitStreamMemoryStream *frame = new Common::BitStreamMemoryStream(partialFrame, frameSize, DisposeAfterUse::YES);
|
||||
|
||||
_videoTrack->decodeFrame(frame, sectorsRead);
|
||||
|
||||
delete frame;
|
||||
delete sector;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CDXA_TYPE_AUDIO: {
|
||||
// We only handle one audio channel so far
|
||||
if (!_audioTrack) {
|
||||
_audioTrack = new PSXAudioTrack(sector, getSoundType(), channel);
|
||||
addTrack(_audioTrack);
|
||||
}
|
||||
|
||||
if (_audioTrack->getChannel() != channel) {
|
||||
warning("Unhandled multi-channel audio");
|
||||
}
|
||||
|
||||
_audioTrack->queueAudioFromSector(sector);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// This shows up way too often, but the other sectors
|
||||
// are safe to ignore
|
||||
//warning("Unknown PSX sector type 0x%x", sectorType);
|
||||
break;
|
||||
}
|
||||
|
||||
delete sector;
|
||||
}
|
||||
|
||||
if (_stream->pos() >= _stream->size()) {
|
||||
if (_videoTrack)
|
||||
_videoTrack->setEndOfTrack();
|
||||
|
||||
if (_audioTrack)
|
||||
_audioTrack->setEndOfTrack();
|
||||
}
|
||||
}
|
||||
|
||||
bool PSXStreamDecoder::useAudioSync() const {
|
||||
// Audio sync is disabled since most audio data comes after video
|
||||
// data.
|
||||
return false;
|
||||
}
|
||||
|
||||
static const byte s_syncHeader[12] = { 0x00, 0xff ,0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
|
||||
|
||||
Common::SeekableReadStream *PSXStreamDecoder::readSector() {
|
||||
assert(_stream);
|
||||
|
||||
Common::SeekableReadStream *stream = _stream->readStream(RAW_CD_SECTOR_SIZE);
|
||||
|
||||
byte syncHeader[12];
|
||||
stream->read(syncHeader, 12);
|
||||
if (!memcmp(s_syncHeader, syncHeader, 12))
|
||||
return stream;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Ha! It's palindromic!
|
||||
#define AUDIO_DATA_CHUNK_SIZE 2304
|
||||
#define AUDIO_DATA_SAMPLE_COUNT 4032
|
||||
|
||||
PSXStreamDecoder::PSXAudioTrack::PSXAudioTrack(Common::SeekableReadStream *sector, Audio::Mixer::SoundType soundType, byte channel) :
|
||||
AudioTrack(soundType), _channel(channel) {
|
||||
assert(sector);
|
||||
_endOfTrack = false;
|
||||
|
||||
sector->seek(19);
|
||||
byte format = sector->readByte();
|
||||
_stereo = (format & (1 << 0)) != 0;
|
||||
_rate = (format & (1 << 2)) ? 18900 : 37800;
|
||||
_audStream = Audio::makeQueuingAudioStream(_rate, _stereo);
|
||||
}
|
||||
|
||||
PSXStreamDecoder::PSXAudioTrack::~PSXAudioTrack() {
|
||||
delete _audStream;
|
||||
}
|
||||
|
||||
bool PSXStreamDecoder::PSXAudioTrack::endOfTrack() const {
|
||||
return AudioTrack::endOfTrack() && _endOfTrack;
|
||||
}
|
||||
|
||||
void PSXStreamDecoder::PSXAudioTrack::queueAudioFromSector(Common::SeekableReadStream *sector) {
|
||||
assert(sector);
|
||||
|
||||
sector->seek(24);
|
||||
|
||||
// Read the specified sector into memory
|
||||
Common::SeekableReadStream *compressedAudioStream = sector->readStream(AUDIO_DATA_CHUNK_SIZE);
|
||||
|
||||
Audio::SeekableAudioStream *audioStream = Audio::makeADPCMStream(compressedAudioStream, DisposeAfterUse::YES, AUDIO_DATA_CHUNK_SIZE, Audio::kADPCMXA, _rate, _stereo ? 2 : 1);
|
||||
if (audioStream) {
|
||||
_audStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
|
||||
} else {
|
||||
// in case there was an error
|
||||
delete compressedAudioStream;
|
||||
}
|
||||
}
|
||||
|
||||
Audio::AudioStream *PSXStreamDecoder::PSXAudioTrack::getAudioStream() const {
|
||||
return _audStream;
|
||||
}
|
||||
|
||||
|
||||
PSXStreamDecoder::PSXVideoTrack::PSXVideoTrack(Common::SeekableReadStream *firstSector, CDSpeed speed, int frameCount, byte channel) :
|
||||
_nextFrameStartTime(0, speed), _frameCount(frameCount), _channel(channel), _surface(nullptr) {
|
||||
assert(firstSector);
|
||||
|
||||
firstSector->seek(40);
|
||||
_width = firstSector->readUint16LE();
|
||||
_height = firstSector->readUint16LE();
|
||||
_pixelFormat = Image::Codec::getDefaultYUVFormat();
|
||||
|
||||
_macroBlocksW = (_width + 15) / 16;
|
||||
_macroBlocksH = (_height + 15) / 16;
|
||||
_yBuffer = new byte[_macroBlocksW * _macroBlocksH * 16 * 16];
|
||||
_cbBuffer = new byte[_macroBlocksW * _macroBlocksH * 8 * 8];
|
||||
_crBuffer = new byte[_macroBlocksW * _macroBlocksH * 8 * 8];
|
||||
|
||||
_endOfTrack = false;
|
||||
_curFrame = -1;
|
||||
_acHuffman = new HuffmanDecoder(0, AC_CODE_COUNT, s_huffmanACCodes, s_huffmanACLengths, s_huffmanACSymbols);
|
||||
_dcHuffmanChroma = new HuffmanDecoder(0, DC_CODE_COUNT, s_huffmanDCChromaCodes, s_huffmanDCChromaLengths, s_huffmanDCSymbols);
|
||||
_dcHuffmanLuma = new HuffmanDecoder(0, DC_CODE_COUNT, s_huffmanDCLumaCodes, s_huffmanDCLumaLengths, s_huffmanDCSymbols);
|
||||
}
|
||||
|
||||
PSXStreamDecoder::PSXVideoTrack::~PSXVideoTrack() {
|
||||
if (_surface) {
|
||||
_surface->free();
|
||||
delete _surface;
|
||||
}
|
||||
|
||||
delete[] _yBuffer;
|
||||
delete[] _cbBuffer;
|
||||
delete[] _crBuffer;
|
||||
delete _acHuffman;
|
||||
delete _dcHuffmanChroma;
|
||||
delete _dcHuffmanLuma;
|
||||
}
|
||||
|
||||
uint32 PSXStreamDecoder::PSXVideoTrack::getNextFrameStartTime() const {
|
||||
return _nextFrameStartTime.msecs();
|
||||
}
|
||||
|
||||
const Graphics::Surface *PSXStreamDecoder::PSXVideoTrack::decodeNextFrame() {
|
||||
return _surface;
|
||||
}
|
||||
|
||||
void PSXStreamDecoder::PSXVideoTrack::decodeFrame(Common::BitStreamMemoryStream *frame, uint sectorCount) {
|
||||
if (!_surface) {
|
||||
_surface = new Graphics::Surface();
|
||||
_surface->create(_width, _height, _pixelFormat);
|
||||
}
|
||||
|
||||
// A frame is essentially an MPEG-1 intra frame
|
||||
|
||||
Common::BitStreamMemory16LEMSB bits(frame);
|
||||
|
||||
bits.skip(16); // unknown
|
||||
bits.skip(16); // 0x3800
|
||||
uint16 scale = bits.getBits<16>();
|
||||
uint16 version = bits.getBits<16>();
|
||||
|
||||
if (version != 2 && version != 3) {
|
||||
warning("Unknown PSX stream frame version");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initalize default v3 DC here
|
||||
_lastDC[0] = _lastDC[1] = _lastDC[2] = 0;
|
||||
|
||||
for (int mbX = 0; mbX < _macroBlocksW; mbX++)
|
||||
for (int mbY = 0; mbY < _macroBlocksH; mbY++)
|
||||
decodeMacroBlock(&bits, mbX, mbY, scale, version);
|
||||
|
||||
// Output data onto the frame
|
||||
YUVToRGBMan.convert420(_surface, Graphics::YUVToRGBManager::kScaleFull, _yBuffer, _cbBuffer, _crBuffer, _surface->w, _surface->h, _macroBlocksW * 16, _macroBlocksW * 8);
|
||||
|
||||
_curFrame++;
|
||||
|
||||
// Increase the time by the amount of sectors we read
|
||||
// One may notice that this is still not the most precise
|
||||
// method since a frame takes up the time its sectors took
|
||||
// up instead of the amount of time it takes the next frame
|
||||
// to be read from the sectors. The actual frame rate should
|
||||
// be constant instead of variable, so the slight difference
|
||||
// in a frame's showing time is negligible (1/150 of a second).
|
||||
_nextFrameStartTime = _nextFrameStartTime.addFrames(sectorCount);
|
||||
}
|
||||
|
||||
void PSXStreamDecoder::PSXVideoTrack::decodeMacroBlock(Common::BitStreamMemory16LEMSB *bits, int mbX, int mbY, uint16 scale, uint16 version) {
|
||||
int pitchY = _macroBlocksW * 16;
|
||||
int pitchC = _macroBlocksW * 8;
|
||||
|
||||
// Note the strange order of red before blue
|
||||
decodeBlock(bits, _crBuffer + (mbY * pitchC + mbX) * 8, pitchC, scale, version, kPlaneV);
|
||||
decodeBlock(bits, _cbBuffer + (mbY * pitchC + mbX) * 8, pitchC, scale, version, kPlaneU);
|
||||
decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16, pitchY, scale, version, kPlaneY);
|
||||
decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8, pitchY, scale, version, kPlaneY);
|
||||
decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY, pitchY, scale, version, kPlaneY);
|
||||
decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY + 8, pitchY, scale, version, kPlaneY);
|
||||
}
|
||||
|
||||
// Standard JPEG/MPEG zig zag table
|
||||
static const byte s_zigZagTable[8 * 8] = {
|
||||
0, 1, 5, 6, 14, 15, 27, 28,
|
||||
2, 4, 7, 13, 16, 26, 29, 42,
|
||||
3, 8, 12, 17, 25, 30, 41, 43,
|
||||
9, 11, 18, 24, 31, 40, 44, 53,
|
||||
10, 19, 23, 32, 39, 45, 52, 54,
|
||||
20, 22, 33, 38, 46, 51, 55, 60,
|
||||
21, 34, 37, 47, 50, 56, 59, 61,
|
||||
35, 36, 48, 49, 57, 58, 62, 63
|
||||
};
|
||||
|
||||
// One byte different from the standard MPEG-1 table
|
||||
static const byte s_quantizationTable[8 * 8] = {
|
||||
2, 16, 19, 22, 26, 27, 29, 34,
|
||||
16, 16, 22, 24, 27, 29, 34, 37,
|
||||
19, 22, 26, 27, 29, 34, 34, 38,
|
||||
22, 22, 26, 27, 29, 34, 37, 40,
|
||||
22, 26, 27, 29, 32, 35, 40, 48,
|
||||
26, 27, 29, 32, 35, 40, 48, 58,
|
||||
26, 27, 29, 34, 38, 46, 56, 69,
|
||||
27, 29, 35, 38, 46, 56, 69, 83
|
||||
};
|
||||
|
||||
void PSXStreamDecoder::PSXVideoTrack::dequantizeBlock(int *coefficients, float *block, uint16 scale) {
|
||||
// Dequantize the data, un-zig-zagging as we go along
|
||||
for (int i = 0; i < 8 * 8; i++) {
|
||||
if (i == 0) // Special case for the DC coefficient
|
||||
block[i] = coefficients[i] * s_quantizationTable[i];
|
||||
else
|
||||
block[i] = (float)coefficients[s_zigZagTable[i]] * s_quantizationTable[i] * scale / 8;
|
||||
}
|
||||
}
|
||||
|
||||
int PSXStreamDecoder::PSXVideoTrack::readDC(Common::BitStreamMemory16LEMSB *bits, uint16 version, PlaneType plane) {
|
||||
// Version 2 just has its coefficient as 10-bits
|
||||
if (version == 2)
|
||||
return readSignedCoefficient(bits);
|
||||
|
||||
// Version 3 has it stored as huffman codes as a difference from the previous DC value
|
||||
|
||||
HuffmanDecoder *huffman = (plane == kPlaneY) ? _dcHuffmanLuma : _dcHuffmanChroma;
|
||||
|
||||
uint32 symbol = huffman->getSymbol(*bits);
|
||||
int dc = 0;
|
||||
|
||||
if (GET_DC_BITS(symbol) != 0) {
|
||||
bool negative = (bits->getBit() == 0);
|
||||
dc = bits->getBits(GET_DC_BITS(symbol) - 1);
|
||||
|
||||
if (negative)
|
||||
dc -= GET_DC_NEG(symbol);
|
||||
else
|
||||
dc += GET_DC_POS(symbol);
|
||||
}
|
||||
|
||||
_lastDC[plane] += dc * 4; // convert from 8-bit to 10-bit
|
||||
return _lastDC[plane];
|
||||
}
|
||||
|
||||
#define BLOCK_OVERFLOW_CHECK() \
|
||||
if (count > 63) \
|
||||
error("PSXStreamDecoder::readAC(): Too many coefficients")
|
||||
|
||||
void PSXStreamDecoder::PSXVideoTrack::readAC(Common::BitStreamMemory16LEMSB *bits, int *block) {
|
||||
// Clear the block first
|
||||
for (int i = 0; i < 63; i++)
|
||||
block[i] = 0;
|
||||
|
||||
int count = 0;
|
||||
|
||||
while (!bits->eos()) {
|
||||
uint32 symbol = _acHuffman->getSymbol(*bits);
|
||||
|
||||
if (symbol == ESCAPE_CODE) {
|
||||
// The escape code!
|
||||
int zeroes = bits->getBits<6>();
|
||||
count += zeroes + 1;
|
||||
BLOCK_OVERFLOW_CHECK();
|
||||
block += zeroes;
|
||||
*block++ = readSignedCoefficient(bits);
|
||||
} else if (symbol == END_OF_BLOCK) {
|
||||
// We're done
|
||||
break;
|
||||
} else {
|
||||
// Normal huffman code
|
||||
int zeroes = GET_AC_ZERO_RUN(symbol);
|
||||
count += zeroes + 1;
|
||||
BLOCK_OVERFLOW_CHECK();
|
||||
block += zeroes;
|
||||
|
||||
if (bits->getBit())
|
||||
*block++ = -GET_AC_COEFFICIENT(symbol);
|
||||
else
|
||||
*block++ = GET_AC_COEFFICIENT(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int PSXStreamDecoder::PSXVideoTrack::readSignedCoefficient(Common::BitStreamMemory16LEMSB *bits) {
|
||||
uint val = bits->getBits<10>();
|
||||
|
||||
// extend the sign
|
||||
uint shift = 8 * sizeof(int) - 10;
|
||||
return (int)(val << shift) >> shift;
|
||||
}
|
||||
|
||||
// IDCT table built with :
|
||||
// _idct8x8[x][y] = cos(((2 * x + 1) * y) * (M_PI / 16.0)) * 0.5;
|
||||
// _idct8x8[x][y] /= sqrt(2.0) if y == 0
|
||||
static const double s_idct8x8[8][8] = {
|
||||
{ 0.353553390593274, 0.490392640201615, 0.461939766255643, 0.415734806151273, 0.353553390593274, 0.277785116509801, 0.191341716182545, 0.097545161008064 },
|
||||
{ 0.353553390593274, 0.415734806151273, 0.191341716182545, -0.097545161008064, -0.353553390593274, -0.490392640201615, -0.461939766255643, -0.277785116509801 },
|
||||
{ 0.353553390593274, 0.277785116509801, -0.191341716182545, -0.490392640201615, -0.353553390593274, 0.097545161008064, 0.461939766255643, 0.415734806151273 },
|
||||
{ 0.353553390593274, 0.097545161008064, -0.461939766255643, -0.277785116509801, 0.353553390593274, 0.415734806151273, -0.191341716182545, -0.490392640201615 },
|
||||
{ 0.353553390593274, -0.097545161008064, -0.461939766255643, 0.277785116509801, 0.353553390593274, -0.415734806151273, -0.191341716182545, 0.490392640201615 },
|
||||
{ 0.353553390593274, -0.277785116509801, -0.191341716182545, 0.490392640201615, -0.353553390593273, -0.097545161008064, 0.461939766255643, -0.415734806151273 },
|
||||
{ 0.353553390593274, -0.415734806151273, 0.191341716182545, 0.097545161008064, -0.353553390593274, 0.490392640201615, -0.461939766255643, 0.277785116509801 },
|
||||
{ 0.353553390593274, -0.490392640201615, 0.461939766255643, -0.415734806151273, 0.353553390593273, -0.277785116509801, 0.191341716182545, -0.097545161008064 }
|
||||
};
|
||||
|
||||
void PSXStreamDecoder::PSXVideoTrack::idct(float *dequantData, float *result) {
|
||||
// IDCT code based on JPEG's IDCT code
|
||||
// TODO: Switch to the integer-based one mentioned in the docs
|
||||
// This is by far the costliest operation here
|
||||
|
||||
float tmp[8 * 8];
|
||||
|
||||
// Apply 1D IDCT to rows
|
||||
for (int y = 0; y < 8; y++) {
|
||||
for (int x = 0; x < 8; x++) {
|
||||
tmp[y + x * 8] = dequantData[0] * s_idct8x8[x][0]
|
||||
+ dequantData[1] * s_idct8x8[x][1]
|
||||
+ dequantData[2] * s_idct8x8[x][2]
|
||||
+ dequantData[3] * s_idct8x8[x][3]
|
||||
+ dequantData[4] * s_idct8x8[x][4]
|
||||
+ dequantData[5] * s_idct8x8[x][5]
|
||||
+ dequantData[6] * s_idct8x8[x][6]
|
||||
+ dequantData[7] * s_idct8x8[x][7];
|
||||
}
|
||||
|
||||
dequantData += 8;
|
||||
}
|
||||
|
||||
// Apply 1D IDCT to columns
|
||||
for (int x = 0; x < 8; x++) {
|
||||
const float *u = tmp + x * 8;
|
||||
for (int y = 0; y < 8; y++) {
|
||||
result[y * 8 + x] = u[0] * s_idct8x8[y][0]
|
||||
+ u[1] * s_idct8x8[y][1]
|
||||
+ u[2] * s_idct8x8[y][2]
|
||||
+ u[3] * s_idct8x8[y][3]
|
||||
+ u[4] * s_idct8x8[y][4]
|
||||
+ u[5] * s_idct8x8[y][5]
|
||||
+ u[6] * s_idct8x8[y][6]
|
||||
+ u[7] * s_idct8x8[y][7];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PSXStreamDecoder::PSXVideoTrack::decodeBlock(Common::BitStreamMemory16LEMSB *bits, byte *block, int pitch, uint16 scale, uint16 version, PlaneType plane) {
|
||||
// Version 2 just has signed 10 bits for DC
|
||||
// Version 3 has them huffman coded
|
||||
int coefficients[8 * 8];
|
||||
coefficients[0] = readDC(bits, version, plane);
|
||||
readAC(bits, &coefficients[1]); // Read in the AC
|
||||
|
||||
// Dequantize
|
||||
float dequantData[8 * 8];
|
||||
dequantizeBlock(coefficients, dequantData, scale);
|
||||
|
||||
// Perform IDCT
|
||||
float idctData[8 * 8];
|
||||
idct(dequantData, idctData);
|
||||
|
||||
// Now output the data
|
||||
for (int y = 0; y < 8; y++) {
|
||||
byte *dst = block + pitch * y;
|
||||
|
||||
// Convert the result to be in the range [0, 255]
|
||||
for (int x = 0; x < 8; x++)
|
||||
*dst++ = (int)CLIP<float>(idctData[y * 8 + x], -128.0f, 127.0f) + 128;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // End of namespace Video
|
||||
Reference in New Issue
Block a user