From 8f03934857b9572882934621bbaa56e5aca4457c Mon Sep 17 00:00:00 2001 From: XProger Date: Sat, 14 Jul 2018 04:52:08 +0300 Subject: [PATCH] #129 PSX FMVs support --- src/gameflow.h | 42 +++- src/sound.h | 145 +++++++++++- src/texture.h | 34 +-- src/utils.h | 114 +++++++-- src/video.h | 631 ++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 888 insertions(+), 78 deletions(-) diff --git a/src/gameflow.h b/src/gameflow.h index 0a4ac84..734c428 100644 --- a/src/gameflow.h +++ b/src/gameflow.h @@ -872,67 +872,107 @@ namespace TR { switch (id) { // TR1 case LVL_TR1_TITLE : + CHECK_FILE("FMV/CAFE.FMV"); CHECK_FILE("FMV/CAFE.RPL"); + CHECK_FILE("video/1/CAFE.FMV"); return "video/1/CAFE.RPL"; case LVL_TR1_GYM : + CHECK_FILE("FMV/MANSION.FMV"); CHECK_FILE("FMV/MANSION.RPL"); + CHECK_FILE("video/1/MANSION.FMV"); return "video/1/MANSION.RPL"; case LVL_TR1_1 : + CHECK_FILE("FMV/SNOW.FMV"); CHECK_FILE("FMV/SNOW.RPL"); + CHECK_FILE("video/1/SNOW.FMV"); return "video/1/SNOW.RPL"; case LVL_TR1_4 : + CHECK_FILE("FMV/LIFT.FMV"); CHECK_FILE("FMV/LIFT.RPL"); + CHECK_FILE("video/1/LIFT.FMV"); return "video/1/LIFT.RPL"; case LVL_TR1_8A : + CHECK_FILE("FMV/VISION.FMV"); CHECK_FILE("FMV/VISION.RPL"); + CHECK_FILE("video/1/VISION.FMV"); return "video/1/VISION.RPL"; case LVL_TR1_10A : + CHECK_FILE("FMV/CANYON.FMV"); CHECK_FILE("FMV/CANYON.RPL"); + CHECK_FILE("video/1/CANYON.FMV"); return "video/1/CANYON.RPL"; case LVL_TR1_10B : + CHECK_FILE("FMV/PIRAMID.FMV"); CHECK_FILE("FMV/PIRAMID.RPL"); + CHECK_FILE("video/1/PIRAMID.FMV"); return "video/1/PIRAMID.RPL"; case LVL_TR1_CUT_4 : + CHECK_FILE("FMV/PRISON.FMV"); CHECK_FILE("FMV/PRISON.RPL"); + CHECK_FILE("video/1/PRISON.FMV"); return "video/1/PRISON.RPL"; case LVL_TR1_EGYPT : + CHECK_FILE("FMV/END.FMV"); CHECK_FILE("FMV/END.RPL"); + CHECK_FILE("video/1/END.FMV"); return "video/1/END.RPL"; // TR2 case LVL_TR2_TITLE : + CHECK_FILE("FMV/ANCIENT.FMV"); CHECK_FILE("fmv/ANCIENT.RPL"); + CHECK_FILE("video/2/ANCIENT.FMV"); return "video/2/ANCIENT.RPL"; case LVL_TR2_WALL : + CHECK_FILE("FMV/MODERN.FMV"); CHECK_FILE("fmv/MODERN.RPL"); + CHECK_FILE("video/2/MODERN.FMV"); return "video/2/MODERN.RPL"; case LVL_TR2_RIG : + CHECK_FILE("FMV/LANDING.FMV"); CHECK_FILE("fmv/LANDING.RPL"); + CHECK_FILE("video/2/LANDING.FMV"); return "video/2/LANDING.RPL"; case LVL_TR2_UNWATER : + CHECK_FILE("FMV/MS.FMV"); CHECK_FILE("fmv/MS.RPL"); + CHECK_FILE("video/2/MS.FMV"); return "video/2/MS.RPL"; case LVL_TR2_SKIDOO : + CHECK_FILE("FMV/CRASH.FMV"); CHECK_FILE("fmv/CRASH.RPL"); + CHECK_FILE("video/2/CRASH.FMV"); return "video/2/CRASH.RPL"; case LVL_TR2_EMPRTOMB : + CHECK_FILE("FMV/JEEP.FMV"); CHECK_FILE("fmv/JEEP.RPL"); + CHECK_FILE("video/2/JEEP.FMV"); return "video/2/JEEP.RPL"; case LVL_TR2_HOUSE : + CHECK_FILE("FMV/END.FMV"); CHECK_FILE("fmv/END.RPL"); + CHECK_FILE("video/2/END.FMV"); return "video/2/END.RPL"; + // TR3 case LVL_TR3_TITLE : + CHECK_FILE("FMV/INTRO.FMV"); CHECK_FILE("fmv/Intr_Eng.rpl"); + CHECK_FILE("video/3/INTRO.FMV"); return "video/3/Intr_Eng.rpl"; case LVL_TR3_SHORE : + CHECK_FILE("FMV/LAGOON.FMV"); CHECK_FILE("fmv/Sail_Eng.rpl"); + CHECK_FILE("video/3/LAGOON.FMV"); return "video/3/Sail_Eng.rpl"; case LVL_TR3_ANTARC : + CHECK_FILE("FMV/HUEY.FMV"); CHECK_FILE("fmv/Crsh_Eng.rpl"); + CHECK_FILE("video/3/HUEY.FMV"); return "video/3/Crsh_Eng.rpl"; case LVL_TR3_STPAUL : + CHECK_FILE("FMV/END.FMV"); CHECK_FILE("fmv/Endgame.rpl"); + CHECK_FILE("video/3/END.FMV"); return "video/3/Endgame.rpl"; - // TR3 default : return NULL; } } diff --git a/src/sound.h b/src/sound.h index c7f997a..c8ac93d 100644 --- a/src/sound.h +++ b/src/sound.h @@ -1,9 +1,10 @@ #ifndef H_SOUND #define H_SOUND -#define DECODE_VAG #define DECODE_ADPCM #define DECODE_IMA +#define DECODE_VAG +#define DECODE_XA #define DECODE_OGG @@ -28,12 +29,25 @@ namespace Sound { + static const int8 SPU_POS[] = { 0, 60, 115, 98, 122 }; + static const int8 SPU_NEG[] = { 0, 0, -52, -55, -60 }; + + static const int16 SPU_ZIG_ZAG[7][30] = { + { 0, 0, 0, 0, 0, 0, -0x0002, +0x000A, -0x0022, +0x0041, -0x0054, +0x0034, +0x0009, -0x010A, +0x0400, -0x0A78, +0x234C, +0x6794, -0x1780, +0x0BCD, -0x0623, +0x0350, -0x016D, +0x006B, +0x000A, -0x0010, +0x0011, -0x0008, +0x0003, -0x0001 }, + { 0, 0, 0, 0, -0x0002, 0, +0x0003, -0x0013, +0x003C, -0x004B, +0x00A2, -0x00E3, +0x0132, -0x0043, -0x0267, +0x0C9D, +0x74BB, -0x11B4, +0x09B8, -0x05BF, +0x0372, -0x01A8, +0x00A6, -0x001B, +0x0005, +0x0006, -0x0008, +0x0003, -0x0001, 0 }, + { 0, 0, 0, -0x0001, +0x0003, -0x0002, -0x0005, +0x001F, -0x004A, +0x00B3, -0x0192, +0x02B1, -0x039E, +0x04F8, -0x05A6, +0x7939, -0x05A6, +0x04F8, -0x039E, +0x02B1, -0x0192, +0x00B3, -0x004A, +0x001F, -0x0005, -0x0002, +0x0003, -0x0001, 0, 0 }, + { 0, 0, -0x0001, +0x0003, -0x0008, +0x0006, +0x0005, -0x001B, +0x00A6, -0x01A8, +0x0372, -0x05BF, +0x09B8, -0x11B4, +0x74BB, +0x0C9D, -0x0267, -0x0043, +0x0132, -0x00E3, +0x00A2, -0x004B, +0x003C, -0x0013, +0x0003, 0, -0x0002, 0, 0, 0 }, + { 0, -0x0001, +0x0003, -0x0008, +0x0011, -0x0010, +0x000A, +0x006B, -0x016D, +0x0350, -0x0623, +0x0BCD, -0x1780, +0x6794, +0x234C, -0x0A78, +0x0400, -0x010A, +0x0009, +0x0034, -0x0054, +0x0041, -0x0022, +0x000A, -0x0001, 0, +0x0001, 0, 0, 0 }, + { 0, +0x0002, -0x0008, +0x0010, -0x0023, +0x002B, +0x001A, -0x00EB, +0x027B, -0x0548, +0x0AFA, -0x16FA, +0x53E0, +0x3C07, -0x1249, +0x080E, -0x0347, +0x015B, -0x0044, -0x0017, +0x0046, -0x0023, +0x0011, -0x0005, 0, 0, 0, 0, 0, 0 }, + { 0, -0x0005, +0x0011, -0x0023, +0x0046, -0x0017, -0x0044, +0x015B, -0x0347, +0x080E, -0x1249, +0x3C07, +0x53E0, -0x16FA, +0x0AFA, -0x0548, +0x027B, -0x00EB, +0x001A, +0x002B, -0x0023, +0x0010, -0x0008, +0x0002, 0, 0, 0, 0, 0, 0 }, + }; + struct Frame { - short L, R; + int16 L, R; }; struct FrameHI { - int L, R; + int32 L, R; }; namespace Filter { @@ -384,10 +398,7 @@ namespace Sound { VAG(Stream *stream) : Decoder(stream, 1, 11025), s1(0), s2(0), bufferSize(0) {} void predicate(short value) { - int inc[] = { 0, 60, 115, 98, 122 }; - int dec[] = { 0, 0, -52, -55, -60 }; - - int s = (s1 * inc[pred] + s2 * dec[pred]) >> 6; + int s = (s1 * SPU_POS[pred] + s2 * SPU_NEG[pred]) >> 6; s = clamp((value >> shift) + s, -32768, 32767); s2 = s1; s1 = s; @@ -447,6 +458,126 @@ namespace Sound { }; #endif +#ifdef DECODE_XA + // http://problemkaputt.de/psx-spx.htm#cdromxaaudioadpcmcompression + struct XA : Decoder { + uint8 pred, shift, flags; + int s1, s2; + + Frame buffer[18 * 112]; + int pos; + + struct Group { + uint8 params[16]; + uint8 data[112]; + } groups[18]; + + Frame prevFrames[2]; + + Frame lerpFrames[32]; + uint32 lerpPos; + + XA(Stream *stream) : Decoder(stream, 1, 11025), s1(0), s2(0), pos(COUNT(buffer)), lerpPos(0) { + memset(prevFrames, 0, sizeof(prevFrames)); + memset(lerpFrames, 0, sizeof(lerpFrames)); + } + + void decode28(Group &group, int block, int channel) { + int16 *dst = channel ? &buffer[pos].R : &buffer[pos].L; + int16 &old = channel ? prevFrames[0].R : prevFrames[0].L; + int16 &older = channel ? prevFrames[1].R : prevFrames[1].L; + + int shift = 12 - (group.params[4 + block * 2 + channel] & 0x0F); + int filter = (group.params[4 + block * 2 + channel] & 0x30) >> 4; + + int f0 = SPU_POS[filter]; + int f1 = SPU_NEG[filter]; + + for (int i = 0; i < 28; i++) { + int t = (group.data[block + i * 4] >> (channel * 4)) & 0x0F; + if (t & 8) + t -= 16; + int s = (t << shift) + ((old * f0 + older * f1 + 32) / 64); + s = clamp(s, -32768, 32767); + older = old; + old = s; + dst[0] = s; + dst += 2; // skip second channel + } + } + + void processBlock() { + if (stream->pos >= stream->size) + return; + + stream->seek(24); // skip sync header + stream->raw(groups, sizeof(groups)); + + pos = 0; + + for (int i = 0; i < COUNT(groups); i++) + for (int j = 0; j < 4; j++) { + decode28(groups[i], j, 0); + decode28(groups[i], j, 1); + pos += 28; + } + + pos = 0; + } + + void ZigZagOut(Frame &frame, uint8 p, const int16 *LUT) { + FrameHI sum; + sum.L = sum.R = 0; + + for (uint8 i = 1; i < 30; i++) { + Frame &f = lerpFrames[uint8(p - i) & 0x1F]; + sum.L += f.L * LUT[i]; + sum.R += f.R * LUT[i]; + } + + frame.L = clamp(sum.L >> 15, -32767, 32767); + frame.R = clamp(sum.R >> 15, -32767, 32767); + } + + virtual int decode(Frame *frames, int count) { + if (pos >= COUNT(buffer)) + processBlock(); + + ASSERT((int(COUNT(buffer)) - pos) % 6 == 0) + ASSERT(count % 7 == 0) + + count = min(count, (int(COUNT(buffer)) - pos) / 6 * 7); + + int i = 0; + while (i < count) { + ASSERT(pos < COUNT(buffer)); + lerpFrames[lerpPos++ & 0x1F] = buffer[pos++]; + lerpFrames[lerpPos++ & 0x1F] = buffer[pos++]; + lerpFrames[lerpPos++ & 0x1F] = buffer[pos++]; + lerpFrames[lerpPos++ & 0x1F] = buffer[pos++]; + lerpFrames[lerpPos++ & 0x1F] = buffer[pos++]; + lerpFrames[lerpPos++ & 0x1F] = buffer[pos++]; + ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[0]); + ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[1]); + ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[2]); + ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[3]); + ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[4]); + ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[5]); + ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[6]); + } + + ASSERT(i == count); + + return count; + } + + virtual void replay() { + stream->setPos(0); + s1 = s2 = 0; + } + }; +#endif + #ifdef DECODE_MP3 struct MP3 : Decoder { mp3_decoder_t mp3; diff --git a/src/texture.h b/src/texture.h index 714f0c9..374b925 100644 --- a/src/texture.h +++ b/src/texture.h @@ -474,16 +474,16 @@ struct Texture : GAPI::Texture { static void rncGetOffset(BitStream &bs, uint16 &offset) { offset = 0; - if (bs.readBit()) { - offset = bs.readBit(); + if (bs.readBitBE()) { + offset = bs.readBitBE(); - if (bs.readBit()) { - offset = ((offset << 1) | bs.readBit()) | 4; + if (bs.readBitBE()) { + offset = ((offset << 1) | bs.readBitBE()) | 4; - if (!bs.readBit()) - offset = (offset << 1) | bs.readBit(); + if (!bs.readBitBE()) + offset = (offset << 1) | bs.readBitBE(); } else if (!offset) - offset = bs.readBit() + 2; + offset = bs.readBitBE() + 2; } offset = ((offset << 8) | bs.readByte()) + 1; } @@ -514,17 +514,17 @@ struct Texture : GAPI::Texture { uint32 length = 0; uint16 offset = 0; - bs.readBits(2); + bs.readBE(2); while (bs.data < bs.end && dst < end) { - if (!bs.readBit()) { + if (!bs.readBitBE()) { *dst++ = bs.readByte(); } else { - if (bs.readBit()) { - if (bs.readBit()) { - if (bs.readBit()) { + if (bs.readBitBE()) { + if (bs.readBitBE()) { + if (bs.readBitBE()) { length = bs.readByte() + 8; if (length == 8) { - bs.readBit(); + bs.readBitBE(); continue; } } else @@ -541,9 +541,9 @@ struct Texture : GAPI::Texture { dst++; } } else { - length = bs.readBit() + 4; - if (bs.readBit()) - length = ((length - 1) << 1) + bs.readBit(); + length = bs.readBitBE() + 4; + if (bs.readBitBE()) + length = ((length - 1) << 1) + bs.readBitBE(); if (length != 9) { rncGetOffset(bs, offset); @@ -553,7 +553,7 @@ struct Texture : GAPI::Texture { dst++; } } else { - length = (bs.readBits(4) << 2) + 12; + length = (bs.readBE(4) << 2) + 12; while (length--) *dst++ = bs.readByte(); } diff --git a/src/utils.h b/src/utils.h index 1b97f5e..46c4958 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1438,38 +1438,61 @@ void osRWUnlockWrite(void *obj) { #endif +static const uint32 BIT_MASK[] = { + 0x00000000, + 0x00000001, 0x00000003, 0x00000007, 0x0000000F, + 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, + 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, + 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, + 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF, + 0x001FFFFF, 0x003FFFFF, 0x007FFFFF, 0x00FFFFFF, + 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, + 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, +}; + struct BitStream { - uint8 *data; - uint8 *end; - uint8 index; - uint8 value; + uint8 *data; + uint8 *end; + uint16 index; + uint16 value; BitStream(uint8 *data, int size) : data(data), end(data + size), index(0), value(0) {} - uint32 read(int count) { - uint32 bits = 0; + inline uint32 readBit() { + uint32 bit; - int m = count - 1; + if (!index--) { + value = *data++; + index = 7; + } + + bit = value & 1; + value >>= 1; + + return bit; + } + + uint32 read(int count) { + uint32 bits = 0, mask = 1; + + if (index == 0) { + if (count > 7) { + count -= 8; + mask <<= 8; + bits = *data++; + } + } while (count--) { - if (!index) { - ASSERT(data < end); - value = *data++; - index = 8; - } - - if (value & 1) - bits |= (1 << (m - count)); - - value >>= 1; - - index--; + if (readBit()) + bits += mask; + mask <<= 1; } return bits; } - uint8 readBits(int count) { + uint8 readBE(int count) { uint32 bits = 0; while (count--) { @@ -1491,14 +1514,61 @@ struct BitStream { return bits; } - uint8 readBit() { - return readBits(1); + uint8 readBitBE() { + return readBE(1); } uint8 readByte() { ASSERT(data < end); return *data++; } + + uint32 readWord(bool littleEndian) { + uint8 a, b; + if (littleEndian) { + a = data[0]; + b = data[1]; + } else { + b = data[0]; + a = data[1]; + } + data += 2; + return a + (b << 8); + } + + uint32 readU(int count) { + if (!index) { + value = readWord(true); + index = 16; + } + + uint32 bits; + if (count <= index) { + bits = (value >> (index - count)) & BIT_MASK[count]; + index -= count; + } else { + bits = value & BIT_MASK[index]; + count -= index; + index = 0; + + while (count >= 16) { + bits = (bits << 16) | readWord(true); + count -= 16; + } + + if (count > 0) { + value = readWord(true); + index = 16 - count; + bits = (bits << count) | (value >> index); + } + } + + return bits; + } + + void skip(int count) { + readU(count); + } }; diff --git a/src/video.h b/src/video.h index b024a47..9c4a925 100644 --- a/src/video.h +++ b/src/video.h @@ -5,6 +5,164 @@ #include "texture.h" #include "sound.h" +struct AC_ENTRY { + uint8 code; + uint8 skip; + uint8 ac; + uint8 length; +}; + +// ISO 13818-2 table B-14 +static const AC_ENTRY STR_AC[] = { +// signBit = (8 + shift) - length +// AC_LUT_1 (shift = 1) + { 0b11000000 , 1 , 1 , 4 }, + { 0b10000000 , 0 , 2 , 5 }, + { 0b10100000 , 2 , 1 , 5 }, + { 0b01010000 , 0 , 3 , 6 }, + { 0b01100000 , 4 , 1 , 6 }, + { 0b01110000 , 3 , 1 , 6 }, + { 0b00100000 , 7 , 1 , 7 }, + { 0b00101000 , 6 , 1 , 7 }, + { 0b00110000 , 1 , 2 , 7 }, + { 0b00111000 , 5 , 1 , 7 }, + { 0b00010000 , 2 , 2 , 8 }, + { 0b00010100 , 9 , 1 , 8 }, + { 0b00011000 , 0 , 4 , 8 }, + { 0b00011100 , 8 , 1 , 8 }, + { 0b01000000 , 13 , 1 , 9 }, + { 0b01000010 , 0 , 6 , 9 }, + { 0b01000100 , 12 , 1 , 9 }, + { 0b01000110 , 11 , 1 , 9 }, + { 0b01001000 , 3 , 2 , 9 }, + { 0b01001010 , 1 , 3 , 9 }, + { 0b01001100 , 0 , 5 , 9 }, + { 0b01001110 , 10 , 1 , 9 }, +// AC_LUT_6 (shift = 6) + { 0b10000000 , 16 , 1 , 11 }, + { 0b10010000 , 5 , 2 , 11 }, + { 0b10100000 , 0 , 7 , 11 }, + { 0b10110000 , 2 , 3 , 11 }, + { 0b11000000 , 1 , 4 , 11 }, + { 0b11010000 , 15 , 1 , 11 }, + { 0b11100000 , 14 , 1 , 11 }, + { 0b11110000 , 4 , 2 , 11 }, + { 0b01000000 , 0 , 11 , 13 }, + { 0b01000100 , 8 , 2 , 13 }, + { 0b01001000 , 4 , 3 , 13 }, + { 0b01001100 , 0 , 10 , 13 }, + { 0b01010000 , 2 , 4 , 13 }, + { 0b01010100 , 7 , 2 , 13 }, + { 0b01011000 , 21 , 1 , 13 }, + { 0b01011100 , 20 , 1 , 13 }, + { 0b01100000 , 0 , 9 , 13 }, + { 0b01100100 , 19 , 1 , 13 }, + { 0b01101000 , 18 , 1 , 13 }, + { 0b01101100 , 1 , 5 , 13 }, + { 0b01110000 , 3 , 3 , 13 }, + { 0b01110100 , 0 , 8 , 13 }, + { 0b01111000 , 6 , 2 , 13 }, + { 0b01111100 , 17 , 1 , 13 }, + { 0b00100000 , 10 , 2 , 14 }, + { 0b00100010 , 9 , 2 , 14 }, + { 0b00100100 , 5 , 3 , 14 }, + { 0b00100110 , 3 , 4 , 14 }, + { 0b00101000 , 2 , 5 , 14 }, + { 0b00101010 , 1 , 7 , 14 }, + { 0b00101100 , 1 , 6 , 14 }, + { 0b00101110 , 0 , 15 , 14 }, + { 0b00110000 , 0 , 14 , 14 }, + { 0b00110010 , 0 , 13 , 14 }, + { 0b00110100 , 0 , 12 , 14 }, + { 0b00110110 , 26 , 1 , 14 }, + { 0b00111000 , 25 , 1 , 14 }, + { 0b00111010 , 24 , 1 , 14 }, + { 0b00111100 , 23 , 1 , 14 }, + { 0b00111110 , 22 , 1 , 14 }, +// AC_LUT_9 (shift = 9) + { 0b10000000 , 0 , 31 , 15 }, + { 0b10001000 , 0 , 30 , 15 }, + { 0b10010000 , 0 , 29 , 15 }, + { 0b10011000 , 0 , 28 , 15 }, + { 0b10100000 , 0 , 27 , 15 }, + { 0b10101000 , 0 , 26 , 15 }, + { 0b10110000 , 0 , 25 , 15 }, + { 0b10111000 , 0 , 24 , 15 }, + { 0b11000000 , 0 , 23 , 15 }, + { 0b11001000 , 0 , 22 , 15 }, + { 0b11010000 , 0 , 21 , 15 }, + { 0b11011000 , 0 , 20 , 15 }, + { 0b11100000 , 0 , 19 , 15 }, + { 0b11101000 , 0 , 18 , 15 }, + { 0b11110000 , 0 , 17 , 15 }, + { 0b11111000 , 0 , 16 , 15 }, + { 0b01000000 , 0 , 40 , 16 }, + { 0b01000100 , 0 , 39 , 16 }, + { 0b01001000 , 0 , 38 , 16 }, + { 0b01001100 , 0 , 37 , 16 }, + { 0b01010000 , 0 , 36 , 16 }, + { 0b01010100 , 0 , 35 , 16 }, + { 0b01011000 , 0 , 34 , 16 }, + { 0b01011100 , 0 , 33 , 16 }, + { 0b01100000 , 0 , 32 , 16 }, + { 0b01100100 , 1 , 14 , 16 }, + { 0b01101000 , 1 , 13 , 16 }, + { 0b01101100 , 1 , 12 , 16 }, + { 0b01110000 , 1 , 11 , 16 }, + { 0b01110100 , 1 , 10 , 16 }, + { 0b01111000 , 1 , 9 , 16 }, + { 0b01111100 , 1 , 8 , 16 }, + { 0b00100000 , 1 , 18 , 17 }, + { 0b00100010 , 1 , 17 , 17 }, + { 0b00100100 , 1 , 16 , 17 }, + { 0b00100110 , 1 , 15 , 17 }, + { 0b00101000 , 6 , 3 , 17 }, + { 0b00101010 , 16 , 2 , 17 }, + { 0b00101100 , 15 , 2 , 17 }, + { 0b00101110 , 14 , 2 , 17 }, + { 0b00110000 , 13 , 2 , 17 }, + { 0b00110010 , 12 , 2 , 17 }, + { 0b00110100 , 11 , 2 , 17 }, + { 0b00110110 , 31 , 1 , 17 }, + { 0b00111000 , 30 , 1 , 17 }, + { 0b00111010 , 29 , 1 , 17 }, + { 0b00111100 , 28 , 1 , 17 }, + { 0b00111110 , 27 , 1 , 17 }, +}; + +static const uint8 STR_ZIG_ZAG[] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, +}; + +static const uint8 STR_QUANTIZATION[] = { + 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 +}; + +static const float STR_IDCT[] = { + 0.354f, 0.354f, 0.354f, 0.354f, 0.354f, 0.354f, 0.354f, 0.354f, + 0.490f, 0.416f, 0.278f, 0.098f, -0.098f, -0.278f, -0.416f, -0.490f, + 0.462f, 0.191f, -0.191f, -0.462f, -0.462f, -0.191f, 0.191f, 0.462f, + 0.416f, -0.098f, -0.490f, -0.278f, 0.278f, 0.490f, 0.098f, -0.416f, + 0.354f, -0.354f, -0.354f, 0.354f, 0.354f, -0.354f, -0.354f, 0.354f, + 0.278f, -0.490f, 0.098f, 0.416f, -0.416f, -0.098f, 0.490f, -0.278f, + 0.191f, -0.462f, 0.462f, -0.191f, -0.191f, 0.462f, -0.462f, 0.191f, + 0.098f, -0.278f, 0.416f, -0.490f, 0.490f, -0.416f, 0.278f, -0.098f, +}; + struct Video { union Color32 { @@ -28,12 +186,51 @@ struct Video { a = 255; } + void SetYCbCrPSX(int32 Y, int32 Cb, int32 Cr) { + int32 R = ((91893 * Cr) >> 16); + int32 G = ((-(22525 * Cb) - (46812 * Cr)) >> 16); + int32 B = ((116224 * Cb) >> 16); + + Y += 128; + r = clamp(Y + R, 0, 255); + g = clamp(Y + G, 0, 255); + b = clamp(Y + B, 0, 255); + a = 255; + } + + void SetYUV(int32 Y, int32 U, int32 V) { r = clamp(Y + (74698 * V >> 16), 0, 255); g = clamp(Y - ((25863 * U + 38049 * V) >> 16), 0, 255); b = clamp(Y + (133174 * U >> 16), 0, 255); a = 255; } + + static void YCbCr_420_PSX(int32 Y0, int32 Y1, int32 Y2, int32 Y3, int32 Cb, int32 Cr, Color32 &C0, Color32 &C1, Color32 &C2, Color32 &C3) { + int32 R = ((91893 * Cr) >> 16) + 128; + int32 G = ((-(22525 * Cb) - (46812 * Cr)) >> 16) + 128; + int32 B = ((116224 * Cb) >> 16) + 128; + + C0.r = clamp(Y0 + R, 0, 255); + C0.g = clamp(Y0 + G, 0, 255); + C0.b = clamp(Y0 + B, 0, 255); + C0.a = 255; + + C1.r = clamp(Y1 + R, 0, 255); + C1.g = clamp(Y1 + G, 0, 255); + C1.b = clamp(Y1 + B, 0, 255); + C1.a = 255; + + C2.r = clamp(Y2 + R, 0, 255); + C2.g = clamp(Y2 + G, 0, 255); + C2.b = clamp(Y2 + B, 0, 255); + C2.a = 255; + + C3.r = clamp(Y3 + R, 0, 255); + C3.g = clamp(Y3 + G, 0, 255); + C3.b = clamp(Y3 + B, 0, 255); + C3.a = 255; + } }; struct Decoder : Sound::Decoder { @@ -41,10 +238,10 @@ struct Video { Decoder(Stream *stream) : Sound::Decoder(stream, 2, 0) {} virtual ~Decoder() { /* delete stream; */ } - virtual bool decode(uint8 *frame) { return false; } + virtual bool decodeVideo(Color32 *pixels) { return false; } }; - // based on ffmpeg code https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/escape124.c + // based on ffmpeg https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/ implementation of escape codecs struct Escape : Decoder { int vfmt, bpp; int sfmt, rate, channels, bps; @@ -135,11 +332,12 @@ struct Video { if (sfmt == 1) audioDecoder = new Sound::PCM(stream, channels, rate, 0, bps); // TR2 - else if (sfmt == 101) + else if (sfmt == 101) { if (bps == 8) audioDecoder = new Sound::PCM(stream, channels, rate, 0, bps); // TR1 else audioDecoder = new Sound::IMA(stream, channels, rate); // TR3 + } } virtual ~Escape() { @@ -180,7 +378,7 @@ struct Video { int getSkip124(BitStream &bs) { int value; - if ((value = bs.read(1)) != 1 || + if ((value = bs.readBit()) != 1 || (value += bs.read(3)) != 8 || (value += bs.read(7)) != 135) return value; @@ -191,10 +389,10 @@ struct Video { int getSkip130(BitStream &bs) { int value; - if (value = bs.read(1)) return 0; - if (value = bs.read(3)) return value; - if (value = bs.read(8)) return value + 7; - if (value = bs.read(15)) return value + 262; + if ((value = bs.readBit())) return 0; + if ((value = bs.read(3))) return value; + if ((value = bs.read(8))) return value + 7; + if ((value = bs.read(15))) return value + 262; return -1; } @@ -208,10 +406,10 @@ struct Video { } void decodeMacroBlock(BitStream &bs, MacroBlock &mb, int &cbIndex, int sbIndex) { - int value = bs.read(1); + int value = bs.readBit(); if (value) { static const int8 trans[3][2] = { {2, 1}, {0, 2}, {1, 0} }; - value = bs.read(1); + value = bs.readBit(); cbIndex = trans[cbIndex][value]; } @@ -232,7 +430,7 @@ struct Video { dst[9] = mb.pixels[3]; } - bool decode(uint8 *frame) { + virtual bool decodeVideo(Color32 *pixels) { if (curVideoChunk >= chunksCount) return false; @@ -245,15 +443,15 @@ struct Video { stream->setPos(chunks[curVideoChunk].offset + curVideoPos); switch (vfmt) { - case 124 : return decode124(frame); - case 130 : return decode130(frame); + case 124 : return decode124(pixels); + case 130 : return decode130(pixels); default : ASSERT(false); } return false; } - bool decode124(uint8 *frame) { + bool decode124(Color32 *pixels) { uint32 flags, size; stream->read(flags); stream->read(size); @@ -340,7 +538,7 @@ struct Video { } else { copySuperBlock(sb.pixels, 8, src, width); - while (!bs.read(1)) { + while (!bs.readBit()) { decodeMacroBlock(bs, mb, cbIndex, sbIndex); uint16 mask = bs.read(16); multiMask |= mask; @@ -349,7 +547,7 @@ struct Video { insertMacroBlock(sb, mb, i); } - if (!bs.read(1)) { + if (!bs.readBit()) { uint16 invMask = bs.read(4); for (int i = 0; i < 4; i++) multiMask ^= ((invMask & (1 << i)) ? 0x0F : bs.read(4)) << (i * 4); @@ -360,7 +558,7 @@ struct Video { } } else if (flags & (1 << 16)) - while (!bs.read(1)) { + while (!bs.readBit()) { decodeMacroBlock(bs, mb, cbIndex, sbIndex); insertMacroBlock(sb, mb, bs.read(4)); } @@ -373,13 +571,13 @@ struct Video { delete[] data; - memcpy(frame, nextFrame, width * height * 4); + memcpy(pixels, nextFrame, width * height * 4); swap(prevFrame, nextFrame); return true; } - bool decode130(uint8 *frame) { + bool decode130(Color32 *pixels) { static const uint8 offsetLUT[] = { 2, 4, 10, 20 @@ -449,7 +647,7 @@ struct Video { V = oV[0]; luma = *lumaPtr; } else { - if (bs.read(1)) { + if (bs.readBit()) { uint32 sign = bs.read(6); uint32 diff = bs.read(2); @@ -458,15 +656,15 @@ struct Video { for (int i = 0; i < 4; i++) Y[i] = clamp(luma + offsetLUT[diff] * signLUT[sign][i], 0U, 63U); - } else if (bs.read(1)) { - luma = bs.read(1) ? bs.read(6) : ((luma + lumaLUT[bs.read(3)]) & 63); + } else if (bs.readBit()) { + luma = bs.readBit() ? bs.read(6) : ((luma + lumaLUT[bs.read(3)]) & 63); for (int i = 0; i < 4; i++) Y[i] = luma; } - if (bs.read(1)) { - if (bs.read(1)) { + if (bs.readBit()) { + if (bs.readBit()) { U = bs.read(5); V = bs.read(5); } else { @@ -502,7 +700,7 @@ struct Video { nU = nY + width * height; nV = nU + width * height / 4; - Color32 *p = (Color32*)frame; + Color32 *p = pixels; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int Y = nY[y * width + x] << 2; @@ -564,9 +762,366 @@ struct Video { } }; + // based on https://raw.githubusercontent.com/m35/jpsxdec/readme/jpsxdec/PlayStation1_STR_format.txt + struct STR : Decoder { + + enum { + MAGIC_STR = 0x80010160, + SECTOR_SIZE = 2352, + + VIDEO_CHUNK_SIZE = 2016, + VIDEO_MAX_CHUNKS = 16, + VIDEO_MAX_FRAMES = 4, + + AUDIO_CHUNK_SIZE = (16 + 112) * 18, // XA ADPCM data block size + AUDIO_MAX_FRAMES = 10000, + + BLOCK_EOD = 0xFE00, + }; + + struct SyncHeader { + uint32 sync[3]; + uint8 mins, secs, block, mode; + uint8 interleaved; + uint8 channel; + struct { + uint8 isEnd:1, isVideo:1, isAudio:1, isData:1, trigger:1, form:1, realtime:1, eof:1; + } submode; + struct { + uint8 stereo:1, :1, rate:1, :1, bps:1, :3; + } coding; + uint32 dup; + }; + + struct Sector { + uint32 magic; + uint16 chunkIndex; + uint16 chunksCount; + uint32 frameIndex; + uint32 chunkSize; + uint16 width, height; + uint16 blocks; + uint16 unk1; + uint16 qscale; + uint16 version; + uint32 unk2; + }; + + struct VideoFrame { + uint8 data[VIDEO_CHUNK_SIZE * VIDEO_MAX_CHUNKS]; + int size; + uint16 width; + uint16 height; + uint32 qscale; + }; + + struct AudioFrame { + int pos; + int size; + }; + + uint8 AC_LUT_1[256]; + uint8 AC_LUT_6[256]; + uint8 AC_LUT_9[256]; + + VideoFrame vFrames[VIDEO_MAX_FRAMES]; + AudioFrame aFrames[AUDIO_MAX_FRAMES]; + + int vFrameIndex; + int aFrameIndex; + + Sound::Decoder *audioDecoder; + + struct { + uint8 code; + uint8 length; + } vlc[176]; + + int curAudioPos; + int curAudioFrame; + + STR(Stream *stream) : Decoder(stream), vFrameIndex(-1), aFrameIndex(-1), audioDecoder(NULL) { + curAudioFrame = 0; + + if (stream->pos >= stream->size) { + LOG("Can't load STR format \"%s\"\n", stream->name); + ASSERT(false); + return; + } + + memset(AC_LUT_1, 255, sizeof(AC_LUT_1)); + memset(AC_LUT_6, 255, sizeof(AC_LUT_6)); + memset(AC_LUT_9, 255, sizeof(AC_LUT_9)); + + buildLUT(AC_LUT_1, 0, 22, 1); + buildLUT(AC_LUT_6, 22, 62, 6); + buildLUT(AC_LUT_9, 62, 110, 9); + + int pos = stream->pos; + nextFrame(); + + VideoFrame &frame = vFrames[vFrameIndex]; + width = (frame.width + 15) / 16 * 16; + height = (frame.height + 15) / 16 * 16; + fps = 150 / (frame.size / VIDEO_CHUNK_SIZE); + fps = (fps < 20) ? 15 : 30; + //fps = int(fps * 44100 / 37800); + stream->setPos(pos); + + vFrameIndex = aFrameIndex = -1; + memset(aFrames, 0, sizeof(aFrames)); + + audioDecoder = new Sound::XA(stream); + } + + virtual ~STR() { + if (audioDecoder) { + audioDecoder->stream = NULL; + delete audioDecoder; + } + } + + void buildLUT(uint8 *LUT, int start, int end, int shift) { + for (int i = start; i < end; i++) { + const AC_ENTRY &e = STR_AC[i]; + uint8 trash = (1 << (8 + shift - e.length + 1)); + // fill the value and all possible endings + while (trash--) + LUT[e.code | trash] = i; + } + } + + bool nextFrame() { + uint8 data[SECTOR_SIZE]; + + VideoFrame *vFrame = vFrames + vFrameIndex; + while (stream->pos < stream->size) { + if (stream->raw(data, sizeof(data)) != sizeof(data)) { + ASSERT(false); + return false; + } + + SyncHeader *syncHeader = (SyncHeader*)data; + + if (syncHeader->sync[0] != 0xFFFFFF00 || syncHeader->sync[1] != 0xFFFFFFFF || syncHeader->sync[2] != 0x00FFFFFF) { + ASSERT(false); + return false; + } + + if (syncHeader->submode.isVideo || syncHeader->submode.isData) { + Sector *sector = (Sector*)(data + sizeof(SyncHeader)); + + if (sector->magic == MAGIC_STR) { + + if (sector->chunkIndex == 0) { + vFrameIndex = (vFrameIndex + 1) % VIDEO_MAX_FRAMES; + vFrame = vFrames + vFrameIndex; + vFrame->size = 0; + vFrame->width = sector->width; + vFrame->height = sector->height; + vFrame->qscale = sector->qscale; + } + + ASSERT(vFrame->size + VIDEO_CHUNK_SIZE < sizeof(vFrame->data)); + memcpy(vFrame->data + vFrame->size, data + sizeof(SyncHeader) + sizeof(Sector), VIDEO_CHUNK_SIZE); + vFrame->size += VIDEO_CHUNK_SIZE; + + if (sector->chunkIndex == sector->chunksCount - 1) { + //LOG("frame %d: %dx%d %d\n", sector->frameIndex, frame->width, frame->height, frame->size); + return true; + } + } + + } else if (syncHeader->submode.isAudio) { + channels = syncHeader->coding.stereo ? 2 : 1; + freq = syncHeader->coding.rate ? 37800 : 18900; + + aFrameIndex = (aFrameIndex + 1) % AUDIO_MAX_FRAMES; + AudioFrame *aFrame = aFrames + aFrameIndex; + aFrame->pos = stream->pos - sizeof(data); + aFrame->size = AUDIO_CHUNK_SIZE; + }; + } + return false; + } + + // http://jpsxdec.blogspot.com/2011/06/decoding-mpeg-like-bitstreams.html + bool readCode(BitStream &bs, int16 &skip, int16 &ac) { + if (bs.readU(1)) { + if (bs.readU(1)) { + skip = 0; + ac = bs.readU(1) ? -1 : 1; + return true; + } + return false; // end of block + } + + int nz = 1; + while (!bs.readU(1)) + nz++; + + if (nz == 5) { // escape code == 0b1000001 + uint16 esc = bs.readU(16); + skip = esc >> 10; + ac = esc & 0x3FF; + if (ac & 0x200) + ac -= 0x400; + return true; + } + + uint8 *table, shift; + if (nz < 6) { + table = AC_LUT_1; + shift = 1; + } else if (nz < 9) { + table = AC_LUT_6; + shift = 6; + } else { + table = AC_LUT_9; + shift = 9; + } + + BitStream state = bs; + uint32 code = (1 << 7) | state.readU(7); + + code >>= nz - shift; + + ASSERT(table); + + int index = table[code]; + + ASSERT(index != 255); + + const AC_ENTRY &e = STR_AC[index]; + bs.skip(e.length - nz - 1); + skip = e.skip; + ac = (code & (1 << (8 + shift - e.length))) ? -e.ac : e.ac; + return true; + } + + void IDCT(int16 *b) { + float t[64]; + + for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) + t[x + y * 8] = b[x + 0 * 8] * STR_IDCT[0 * 8 + y] + + b[x + 1 * 8] * STR_IDCT[1 * 8 + y] + + b[x + 2 * 8] * STR_IDCT[2 * 8 + y] + + b[x + 3 * 8] * STR_IDCT[3 * 8 + y] + + b[x + 4 * 8] * STR_IDCT[4 * 8 + y] + + b[x + 5 * 8] * STR_IDCT[5 * 8 + y] + + b[x + 6 * 8] * STR_IDCT[6 * 8 + y] + + b[x + 7 * 8] * STR_IDCT[7 * 8 + y]; + + for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) { + int i = y * 8; + b[x + i] = int16( + t[0 + i] * STR_IDCT[x + 0 * 8] + + t[1 + i] * STR_IDCT[x + 1 * 8] + + t[2 + i] * STR_IDCT[x + 2 * 8] + + t[3 + i] * STR_IDCT[x + 3 * 8] + + t[4 + i] * STR_IDCT[x + 4 * 8] + + t[5 + i] * STR_IDCT[x + 5 * 8] + + t[6 + i] * STR_IDCT[x + 6 * 8] + + t[7 + i] * STR_IDCT[x + 7 * 8]); + } + } + + virtual bool decodeVideo(Color32 *pixels) { + if (!nextFrame()) + return false; + + VideoFrame *frame = vFrames + vFrameIndex; + + BitStream bs(frame->data + 8, frame->size - 8); // make bitstream without frame header + + int16 block[6][64]; // Cr, Cb, YTL, YTR, YBL, YBR + for (int bX = 0; bX < width / 16; bX++) + for (int bY = 0; bY < height / 16; bY++) { + memset(block, 0, sizeof(block)); + + for (int i = 0; i < 6; i++) { + bool nonZero = false; + + int16 *channel = block[i]; + channel[0] = bs.readU(10); + if (channel[0]) { + if (channel[0] & 0x200) + channel[0] -= 0x400; + channel[0] = channel[0] * STR_QUANTIZATION[0]; // DC + nonZero = true; + } + + int16 skip, ac; + int index = 0; + while (readCode(bs, skip, ac)) { + index += 1 + skip; + ASSERT(index < 64); + int zIndex = STR_ZIG_ZAG[index]; + channel[zIndex] = (ac * STR_QUANTIZATION[zIndex] * frame->qscale + 4) >> 3; + nonZero = true; + } + + if (nonZero) + IDCT(channel); + } + + Color32 *blockPixels = pixels + (width * bY * 16 + bX * 16); + + for (uint32 i = 0; i < 8 * 8; i++) { + int x = (i % 8) * 2; + int y = (i / 8) * 2; + int j = (x & 7) + (y & 7) * 8; + + Color32 *c = blockPixels + (width * y + x); + + int16 *b = block[(x < 8) ? ((y < 8) ? 2 : 4) : ((y < 8) ? 3 : 5)]; + + Color32::YCbCr_420_PSX(b[j], b[j + 1], b[j + 8], b[j + 8 + 1], block[1][i], block[0][i], c[0], c[1], c[width], c[width + 1]); + } + } + + return true; + } + + virtual int decode(Sound::Frame *frames, int count) { + if (!audioDecoder) return 0; + + int oldPos = stream->pos; + + Sound::XA *xa = (Sound::XA*)audioDecoder; + + int i = 0; + while (i < count) { + if (xa->pos >= COUNT(xa->buffer)) { + if (aFrames[curAudioFrame].size == 0) + curAudioFrame = (curAudioFrame + 1) % AUDIO_MAX_FRAMES; + + if (aFrames[curAudioFrame].size == 0) { + stream->setPos(oldPos); + nextFrame(); + oldPos = stream->pos; + curAudioFrame = aFrameIndex; + } + } + stream->setPos(aFrames[curAudioFrame].pos); + + i += audioDecoder->decode(&frames[i], count - i); + + if (xa->pos >= COUNT(xa->buffer)) + aFrames[curAudioFrame].size = 0; + } + + stream->setPos(oldPos); + + return count; + } + }; + Decoder *decoder; Texture *frameTex[2]; - uint8 *frameData; + Color32 *frameData; float time, step; bool isPlaying; bool needUpdate; @@ -577,9 +1132,17 @@ struct Video { if (!stream) return; - decoder = new Escape(stream); - frameData = new uint8[decoder->width * decoder->height * 4]; - memset(frameData, 0, decoder->width * decoder->height * 4); + uint32 magic; + stream->read(magic); + stream->seek(-4); + + if (magic == 0x6F4D5241) + decoder = new Escape(stream); + else + decoder = new STR(stream); + + frameData = new Color32[decoder->width * decoder->height]; + memset(frameData, 0, decoder->width * decoder->height * sizeof(Color32)); for (int i = 0; i < 2; i++) frameTex[i] = new Texture(decoder->width, decoder->height, FMT_RGBA, 0, frameData); @@ -607,8 +1170,14 @@ struct Video { if (time < step) return; time -= step; - - isPlaying = needUpdate = decoder->decode(frameData); + #ifdef VIDEO_TEST + int t = Core::getTime(); + while (decoder->decodeVideo(frameData)) {} + LOG("time: %d\n", Core::getTime() - t); + isPlaying = false; + #else + isPlaying = needUpdate = decoder->decodeVideo(frameData); + #endif } void render() { // just update GPU texture if it's necessary