mirror of
https://github.com/ZDoom/Raze.git
synced 2024-11-15 17:01:28 +00:00
1151 lines
29 KiB
C++
1151 lines
29 KiB
C++
/*
|
|
* libsmackerdec - Smacker video decoder
|
|
* Copyright (C) 2011 Barry Duncan
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/* This code is heavily based on smacker.c from the FFmpeg project which can be obtained from http://www.ffmpeg.org/
|
|
* below is the license from smacker.c
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
/*
|
|
* Smacker decoder
|
|
* Copyright (c) 2006 Konstantin Shishkov
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "SmackerDecoder.h"
|
|
#include "HuffmanVLC.h"
|
|
#include "LogError.h"
|
|
#include "printf.h"
|
|
#include "limits.h"
|
|
#include <assert.h>
|
|
#include <algorithm>
|
|
|
|
std::vector<class SmackerDecoder*> classInstances;
|
|
|
|
SmackerHandle Smacker_Open(const char* fileName)
|
|
{
|
|
SmackerHandle newHandle;
|
|
newHandle.isValid = false;
|
|
newHandle.instanceIndex = -1;
|
|
|
|
SmackerDecoder *newDecoder = new SmackerDecoder();
|
|
if (!newDecoder->Open(fileName))
|
|
{
|
|
delete newDecoder;
|
|
return newHandle;
|
|
}
|
|
|
|
// add instance to global instance vector
|
|
classInstances.push_back(newDecoder);
|
|
|
|
// get a handle ID
|
|
newHandle.instanceIndex = classInstances.size() - 1;
|
|
|
|
// loaded ok, make handle valid
|
|
newHandle.isValid = true;
|
|
|
|
return newHandle;
|
|
}
|
|
|
|
void Smacker_Close(SmackerHandle &handle)
|
|
{
|
|
if (!classInstances.at(handle.instanceIndex))
|
|
{
|
|
// invalid handle
|
|
return;
|
|
}
|
|
|
|
// close bink decoder
|
|
delete classInstances[handle.instanceIndex];
|
|
classInstances[handle.instanceIndex] = 0;
|
|
|
|
handle.instanceIndex = -1;
|
|
handle.isValid = false;
|
|
}
|
|
|
|
uint32_t Smacker_GetNumAudioTracks(SmackerHandle &)
|
|
{
|
|
// TODO: fixme
|
|
return 1;
|
|
}
|
|
|
|
SmackerAudioInfo Smacker_GetAudioTrackDetails(SmackerHandle &handle, uint32_t trackIndex)
|
|
{
|
|
return classInstances[handle.instanceIndex]->GetAudioTrackDetails(trackIndex);
|
|
}
|
|
|
|
/* Get a frame's worth of audio data.
|
|
*
|
|
* 'data' needs to be a pointer to allocated memory that this function will fill.
|
|
* You can find the size (in bytes) to make this buffer by calling Bink_GetAudioTrackDetails()
|
|
* and checking the 'idealBufferSize' member in the returned AudioInfo struct
|
|
*/
|
|
uint32_t Smacker_GetAudioData(SmackerHandle &handle, uint32_t trackIndex, int16_t *data)
|
|
{
|
|
return classInstances[handle.instanceIndex]->GetAudioData(trackIndex, data);
|
|
}
|
|
|
|
uint32_t Smacker_GetNumFrames(SmackerHandle &handle)
|
|
{
|
|
return classInstances[handle.instanceIndex]->GetNumFrames();
|
|
}
|
|
|
|
void Smacker_GetFrameSize(SmackerHandle &handle, uint32_t &width, uint32_t &height)
|
|
{
|
|
width = classInstances[handle.instanceIndex]->frameWidth;
|
|
height = classInstances[handle.instanceIndex]->frameHeight;
|
|
}
|
|
|
|
uint32_t Smacker_GetCurrentFrameNum(SmackerHandle &handle)
|
|
{
|
|
return classInstances[handle.instanceIndex]->GetCurrentFrameNum();
|
|
}
|
|
|
|
uint32_t Smacker_GetNextFrame(SmackerHandle &handle)
|
|
{
|
|
SmackerDecoder *decoder = classInstances[handle.instanceIndex];
|
|
|
|
uint32_t frameIndex = decoder->GetCurrentFrameNum();
|
|
|
|
decoder->GetNextFrame();
|
|
|
|
return frameIndex;
|
|
}
|
|
|
|
float Smacker_GetFrameRate(SmackerHandle &handle)
|
|
{
|
|
return classInstances[handle.instanceIndex]->GetFrameRate();
|
|
}
|
|
|
|
void Smacker_GetPalette(SmackerHandle &handle, uint8_t *palette)
|
|
{
|
|
classInstances[handle.instanceIndex]->GetPalette(palette);
|
|
}
|
|
|
|
void Smacker_GetFrame(SmackerHandle &handle, uint8_t *frame)
|
|
{
|
|
classInstances[handle.instanceIndex]->GetFrame(frame);
|
|
}
|
|
|
|
void Smacker_GotoFrame(SmackerHandle &handle, uint32_t frameNum)
|
|
{
|
|
classInstances[handle.instanceIndex]->GotoFrame(frameNum);
|
|
}
|
|
|
|
SmackerDecoder::SmackerDecoder()
|
|
{
|
|
isVer4 = false;
|
|
currentFrame = 0;
|
|
picture = 0;
|
|
nextPos = 0;
|
|
|
|
for (int i = 0; i < kMaxAudioTracks; i++)
|
|
{
|
|
audioTracks[i].buffer = 0;
|
|
}
|
|
}
|
|
|
|
SmackerDecoder::~SmackerDecoder()
|
|
{
|
|
for (int i = 0; i < kMaxAudioTracks; i++)
|
|
{
|
|
delete[] audioTracks[i].buffer;
|
|
}
|
|
|
|
delete[] picture;
|
|
}
|
|
|
|
// from bswap.h
|
|
static /*av_always_inline av_const*/ uint16_t av_bswap16(uint16_t x)
|
|
{
|
|
x= (x>>8) | (x<<8);
|
|
return x;
|
|
}
|
|
|
|
/* possible runs of blocks */
|
|
static const int block_runs[64] = {
|
|
1, 2, 3, 4, 5, 6, 7, 8,
|
|
9, 10, 11, 12, 13, 14, 15, 16,
|
|
17, 18, 19, 20, 21, 22, 23, 24,
|
|
25, 26, 27, 28, 29, 30, 31, 32,
|
|
33, 34, 35, 36, 37, 38, 39, 40,
|
|
41, 42, 43, 44, 45, 46, 47, 48,
|
|
49, 50, 51, 52, 53, 54, 55, 56,
|
|
57, 58, 59, 128, 256, 512, 1024, 2048 };
|
|
|
|
enum SmkBlockTypes {
|
|
SMK_BLK_MONO = 0,
|
|
SMK_BLK_FULL = 1,
|
|
SMK_BLK_SKIP = 2,
|
|
SMK_BLK_FILL = 3 };
|
|
|
|
/* palette used in Smacker */
|
|
static const uint8_t smk_pal[64] = {
|
|
0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C,
|
|
0x20, 0x24, 0x28, 0x2C, 0x30, 0x34, 0x38, 0x3C,
|
|
0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D,
|
|
0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D,
|
|
0x82, 0x86, 0x8A, 0x8E, 0x92, 0x96, 0x9A, 0x9E,
|
|
0xA2, 0xA6, 0xAA, 0xAE, 0xB2, 0xB6, 0xBA, 0xBE,
|
|
0xC3, 0xC7, 0xCB, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF,
|
|
0xE3, 0xE7, 0xEB, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF
|
|
};
|
|
|
|
enum SAudFlags {
|
|
SMK_AUD_PACKED = 0x80000000,
|
|
SMK_AUD_16BITS = 0x20000000,
|
|
SMK_AUD_STEREO = 0x10000000,
|
|
SMK_AUD_BINKAUD = 0x08000000,
|
|
SMK_AUD_USEDCT = 0x04000000
|
|
};
|
|
|
|
const int kSMKpal = 0x01;
|
|
const int kFlagRingFrame = 0x01;
|
|
|
|
const int kTreeBits = 9;
|
|
const int kSMKnode = 0x80000000;
|
|
|
|
const char *kSMK2iD = "SMK2";
|
|
const char *kSMK4iD = "SMK4";
|
|
|
|
|
|
/**
|
|
* Context used for code reconstructing
|
|
*/
|
|
typedef struct HuffContext {
|
|
int length;
|
|
int maxlength;
|
|
int current;
|
|
|
|
std::vector<uint32_t> bits;
|
|
std::vector<int> lengths;
|
|
std::vector<int> values;
|
|
|
|
} HuffContext;
|
|
|
|
/* common parameters used for decode_bigtree */
|
|
typedef struct DBCtx {
|
|
SmackerCommon::VLCtable v1;
|
|
SmackerCommon::VLCtable v2;
|
|
std::vector<int> recode1, recode2;
|
|
int escapes[3];
|
|
int *last;
|
|
int lcur;
|
|
} DBCtx;
|
|
|
|
|
|
static void last_reset(std::vector<int> &recode, int *last) {
|
|
recode[last[0]] = recode[last[1]] = recode[last[2]] = 0;
|
|
}
|
|
|
|
/* get code and update history */
|
|
int SmackerDecoder::GetCode(SmackerCommon::BitReader &bits, std::vector<int> &recode, int *last)
|
|
{
|
|
int *table = &recode[0];
|
|
|
|
int v, b;
|
|
|
|
b = bits.GetPosition();
|
|
|
|
while (*table & kSMKnode)
|
|
{
|
|
if (bits.GetBit())
|
|
table += (*table) & (~kSMKnode);
|
|
table++;
|
|
}
|
|
v = *table;
|
|
b = bits.GetPosition() - b;
|
|
|
|
if (v != recode[last[0]]) {
|
|
recode[last[2]] = recode[last[1]];
|
|
recode[last[1]] = recode[last[0]];
|
|
recode[last[0]] = v;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
bool SmackerDecoder::Open(const char *fileName)
|
|
{
|
|
// open the file (read only)
|
|
file.Open(fileName);
|
|
if (!file.Is_Open())
|
|
{
|
|
Printf("SmackerDecoder::Open() - Can't open file %s\n", fileName);
|
|
return false;
|
|
}
|
|
|
|
// check the file signature
|
|
file.ReadBytes((uint8_t*)signature, 4);
|
|
if (memcmp(signature, kSMK2iD, 4) != 0
|
|
&& memcmp(signature, kSMK4iD, 4) != 0)
|
|
{
|
|
Printf("SmackerDecoder::Open() - Unknown Smacker signature\n");
|
|
return false;
|
|
}
|
|
|
|
if (!memcmp(signature, kSMK4iD, 4)) {
|
|
isVer4 = true;
|
|
}
|
|
|
|
frameWidth = file.ReadUint32LE();
|
|
frameHeight = file.ReadUint32LE();
|
|
nFrames = file.ReadUint32LE();
|
|
|
|
picture = new uint8_t[frameWidth * frameHeight];
|
|
|
|
int32_t frameRate = file.ReadUint32LE();
|
|
|
|
if (frameRate > 0)
|
|
fps = 1000 / frameRate;
|
|
else if (frameRate < 0)
|
|
fps = 100000 / (-frameRate);
|
|
else
|
|
fps = 10;
|
|
|
|
uint32_t flags = file.ReadUint32LE();
|
|
|
|
if (flags & kFlagRingFrame) {
|
|
nFrames++;
|
|
}
|
|
|
|
for (int i = 0; i < kMaxAudioTracks; i++) {
|
|
audioTracks[i].sizeInBytes = file.ReadUint32LE();
|
|
}
|
|
|
|
treeSize = file.ReadUint32LE();
|
|
mMapSize = file.ReadUint32LE();
|
|
MClrSize = file.ReadUint32LE();
|
|
fullSize = file.ReadUint32LE();
|
|
typeSize = file.ReadUint32LE();
|
|
|
|
for (int i = 0; i < kMaxAudioTracks; i++) {
|
|
audioTracks[i].flags = file.ReadUint32LE();
|
|
}
|
|
|
|
// skip pad
|
|
file.Skip(4);
|
|
|
|
if (nFrames > 0xFFFFFF)
|
|
{
|
|
Printf("SmackerDecoder::Open() - Too many frames\n");
|
|
return false;
|
|
}
|
|
|
|
frameSizes.resize(nFrames);
|
|
frameFlags.resize(nFrames);
|
|
|
|
// read frame info
|
|
for (uint32_t i = 0; i < nFrames; i++) {
|
|
frameSizes[i] = file.ReadUint32LE();
|
|
}
|
|
for (uint32_t i = 0; i < nFrames; i++) {
|
|
frameFlags[i] = file.ReadByte();
|
|
}
|
|
|
|
// handle possible audio streams
|
|
for (int i = 0; i < kMaxAudioTracks; i++)
|
|
{
|
|
audioTracks[i].buffer = 0;
|
|
audioTracks[i].bufferSize = 0;
|
|
audioTracks[i].bytesReadThisFrame = 0;
|
|
|
|
if (audioTracks[i].flags & 0xFFFFFF)
|
|
{
|
|
/*
|
|
if (audioTracks[i].flags & SMK_AUD_BINKAUD) {
|
|
audioTracks[i].compressionType = SMK_AUD_BINKAUD;
|
|
} else if (audioTracks[i].flags & SMK_AUD_USEDCT) {
|
|
audioTracks[i].compressionType = SMK_AUD_USEDCT;
|
|
} else if (audioTracks[i].flags & SMK_AUD_PACKED){
|
|
ast[i]->codec->codec_id = CODEC_ID_SMACKAUDIO;
|
|
ast[i]->codec->codec_tag = MKTAG('S', 'M', 'K', 'A');
|
|
} else {
|
|
ast[i]->codec->codec_id = CODEC_ID_PCM_U8;
|
|
}
|
|
*/
|
|
audioTracks[i].nChannels = (audioTracks[i].flags & SMK_AUD_STEREO) ? 2 : 1;
|
|
audioTracks[i].sampleRate = audioTracks[i].flags & 0xFFFFFF;
|
|
audioTracks[i].bitsPerSample = (audioTracks[i].flags & SMK_AUD_16BITS) ? 16 : 8;
|
|
}
|
|
}
|
|
|
|
memset(palette, 0, 768);
|
|
|
|
DecodeHeaderTrees();
|
|
|
|
// set nextPos to where we are now, as next data is frame 1
|
|
nextPos = file.GetPosition();
|
|
firstFrameFilePos = nextPos;
|
|
|
|
// determine max buffer sizes for audio tracks
|
|
// file.Seek(nextPos, SmackerCommon::FileStream::kSeekStart);
|
|
|
|
uint8_t frameFlag = frameFlags[0];
|
|
|
|
// skip over palette
|
|
if (frameFlag & kSMKpal)
|
|
{
|
|
uint32_t size = file.ReadByte();
|
|
size = size * 4 - 1;
|
|
file.Skip(size);
|
|
}
|
|
|
|
frameFlag >>= 1;
|
|
|
|
for (int i = 0; i < kMaxAudioTracks; i++)
|
|
{
|
|
if (frameFlag & 1)
|
|
{
|
|
// skip size
|
|
file.Skip(4);
|
|
|
|
uint32_t unpackedSize = file.ReadUint32LE();
|
|
|
|
audioTracks[i].bufferSize = unpackedSize;
|
|
audioTracks[i].buffer = new uint8_t[unpackedSize];
|
|
}
|
|
frameFlag >>= 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// static int smacker_decode_tree(GetBitContext *gb, HuffContext *hc, uint32_t prefix, int length)
|
|
int SmackerDecoder::DecodeTree(SmackerCommon::BitReader &bits, HuffContext *hc, uint32_t prefix, int length)
|
|
{
|
|
if (!bits.GetBit()) // Leaf
|
|
{
|
|
if (hc->current >= 256){
|
|
Printf("SmackerDecoder::DecodeTree() - Tree size exceeded\n");
|
|
return -1;
|
|
}
|
|
if (length){
|
|
hc->bits[hc->current] = prefix;
|
|
hc->lengths[hc->current] = length;
|
|
} else {
|
|
hc->bits[hc->current] = 0;
|
|
hc->lengths[hc->current] = 0;
|
|
}
|
|
hc->values[hc->current] = bits.GetBits(8);
|
|
|
|
hc->current++;
|
|
if (hc->maxlength < length)
|
|
hc->maxlength = length;
|
|
return 0;
|
|
} else { //Node
|
|
int r;
|
|
length++;
|
|
r = DecodeTree(bits, hc, prefix, length);
|
|
if (r)
|
|
return r;
|
|
return DecodeTree(bits, hc, prefix | (1 << (length - 1)), length);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decode header tree
|
|
*/
|
|
int SmackerDecoder::DecodeBigTree(SmackerCommon::BitReader &bits, HuffContext *hc, DBCtx *ctx)
|
|
{
|
|
if (!bits.GetBit()) // Leaf
|
|
{
|
|
int val, i1, i2, b1, b2;
|
|
|
|
i1 = 0;
|
|
i2 = 0;
|
|
|
|
if (hc->current >= hc->length){
|
|
Printf("SmackerDecoder::DecodeBigTree() - Tree size exceeded");
|
|
return -1;
|
|
}
|
|
|
|
b1 = bits.GetPosition();
|
|
|
|
if (VLC_GetSize(ctx->v1))
|
|
{
|
|
i1 = VLC_GetCodeBits(bits, ctx->v1);
|
|
}
|
|
|
|
b1 = bits.GetPosition() - b1;
|
|
b2 = bits.GetPosition();
|
|
|
|
if (VLC_GetSize(ctx->v2))
|
|
{
|
|
i2 = VLC_GetCodeBits(bits, ctx->v2);
|
|
}
|
|
|
|
b2 = bits.GetPosition() - b2;
|
|
if (i1 < 0 || i2 < 0)
|
|
return -1;
|
|
val = ctx->recode1[i1] | (ctx->recode2[i2] << 8);
|
|
if (val == ctx->escapes[0]) {
|
|
ctx->last[0] = hc->current;
|
|
val = 0;
|
|
} else if (val == ctx->escapes[1]) {
|
|
ctx->last[1] = hc->current;
|
|
val = 0;
|
|
} else if (val == ctx->escapes[2]) {
|
|
ctx->last[2] = hc->current;
|
|
val = 0;
|
|
}
|
|
|
|
hc->values[hc->current++] = val;
|
|
return 1;
|
|
} else { //Node
|
|
int r = 0, t;
|
|
|
|
t = hc->current++;
|
|
r = DecodeBigTree(bits, hc, ctx);
|
|
if (r < 0)
|
|
return r;
|
|
hc->values[t] = kSMKnode | r;
|
|
r++;
|
|
r += DecodeBigTree(bits, hc, ctx);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store large tree as Libav's vlc codes
|
|
*/
|
|
int SmackerDecoder::DecodeHeaderTree(SmackerCommon::BitReader &bits, std::vector<int> &recodes, int *last, int size)
|
|
{
|
|
HuffContext huff;
|
|
HuffContext tmp1, tmp2;
|
|
int escapes[3];
|
|
DBCtx ctx;
|
|
|
|
if ((uint32_t)size >= UINT_MAX>>4)
|
|
{
|
|
Printf("SmackerDecoder::DecodeHeaderTree() - Size too large\n");
|
|
return -1;
|
|
}
|
|
|
|
tmp1.length = 256;
|
|
tmp1.maxlength = 0;
|
|
tmp1.current = 0;
|
|
|
|
tmp1.bits.resize(256);
|
|
tmp1.lengths.resize(256);
|
|
tmp1.values.resize(256);
|
|
|
|
tmp2.length = 256;
|
|
tmp2.maxlength = 0;
|
|
tmp2.current = 0;
|
|
|
|
tmp2.bits.resize(256);
|
|
tmp2.lengths.resize(256);
|
|
tmp2.values.resize(256);
|
|
|
|
// low byte tree
|
|
if (bits.GetBit()) // 1: Read Tag
|
|
{
|
|
DecodeTree(bits, &tmp1, 0, 0);
|
|
|
|
bits.SkipBits(1);
|
|
|
|
VLC_InitTable(ctx.v1, tmp1.maxlength, tmp1.current, &tmp1.lengths[0], &tmp1.bits[0]);
|
|
}
|
|
else
|
|
{
|
|
// Skipping low bytes tree
|
|
}
|
|
|
|
// high byte tree
|
|
if (bits.GetBit())
|
|
{
|
|
DecodeTree(bits, &tmp2, 0, 0);
|
|
|
|
bits.SkipBits(1);
|
|
|
|
VLC_InitTable(ctx.v2, tmp2.maxlength, tmp2.current, &tmp2.lengths[0], &tmp2.bits[0]);
|
|
}
|
|
else
|
|
{
|
|
// Skipping high bytes tree
|
|
}
|
|
|
|
escapes[0] = bits.GetBits(8);
|
|
escapes[0] |= bits.GetBits(8) << 8;
|
|
escapes[1] = bits.GetBits(8);
|
|
escapes[1] |= bits.GetBits(8) << 8;
|
|
escapes[2] = bits.GetBits(8);
|
|
escapes[2] |= bits.GetBits(8) << 8;
|
|
|
|
last[0] = last[1] = last[2] = -1;
|
|
|
|
ctx.escapes[0] = escapes[0];
|
|
ctx.escapes[1] = escapes[1];
|
|
ctx.escapes[2] = escapes[2];
|
|
|
|
ctx.recode1 = tmp1.values;
|
|
ctx.recode2 = tmp2.values;
|
|
ctx.last = last;
|
|
|
|
huff.length = ((size + 3) >> 2) + 3;
|
|
huff.maxlength = 0;
|
|
huff.current = 0;
|
|
huff.values.resize(huff.length);
|
|
|
|
DecodeBigTree(bits, &huff, &ctx);
|
|
|
|
bits.SkipBits(1);
|
|
|
|
if (ctx.last[0] == -1) ctx.last[0] = huff.current++;
|
|
if (ctx.last[1] == -1) ctx.last[1] = huff.current++;
|
|
if (ctx.last[2] == -1) ctx.last[2] = huff.current++;
|
|
|
|
recodes = huff.values;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// static int decode_header_trees(SmackVContext *smk) {
|
|
bool SmackerDecoder::DecodeHeaderTrees()
|
|
{
|
|
SmackerCommon::BitReader bits(file, treeSize);
|
|
|
|
if (!bits.GetBit())
|
|
{
|
|
// Skipping MMAP tree
|
|
mmap_tbl.resize(2);
|
|
mmap_tbl[0] = 0;
|
|
mmap_last[0] = mmap_last[1] = mmap_last[2] = 1;
|
|
}
|
|
else
|
|
{
|
|
DecodeHeaderTree(bits, mmap_tbl, mmap_last, mMapSize);
|
|
}
|
|
|
|
if (!bits.GetBit())
|
|
{
|
|
// Skipping MCLR tree
|
|
mclr_tbl.resize(2);
|
|
mclr_tbl[0] = 0;
|
|
mclr_last[0] = mclr_last[1] = mclr_last[2] = 1;
|
|
}
|
|
else
|
|
{
|
|
DecodeHeaderTree(bits, mclr_tbl, mclr_last, MClrSize);
|
|
}
|
|
|
|
if (!bits.GetBit())
|
|
{
|
|
// Skipping FULL tree
|
|
full_tbl.resize(2);
|
|
full_tbl[0] = 0;
|
|
full_last[0] = full_last[1] = full_last[2] = 1;
|
|
}
|
|
else
|
|
{
|
|
DecodeHeaderTree(bits, full_tbl, full_last, fullSize);
|
|
}
|
|
|
|
if (!bits.GetBit())
|
|
{
|
|
// Skipping TYPE tree
|
|
type_tbl.resize(2);
|
|
type_tbl[0] = 0;
|
|
type_last[0] = type_last[1] = type_last[2] = 1;
|
|
}
|
|
else
|
|
{
|
|
DecodeHeaderTree(bits, type_tbl, type_last, typeSize);
|
|
}
|
|
|
|
/* FIXME - we don't seems to read/use EVERY bit we 'load' into the bit reader
|
|
* and as my bitreader reads from the file rather than a buffer read from file
|
|
* of size 'treeSize', I need to make sure I consume the remaining bits (and thus increment
|
|
* the file read position to where the code expects it to be when this function returns (ie
|
|
* 'treeSize' number of bytes must be read
|
|
*/
|
|
uint32_t left = bits.GetSize() - bits.GetPosition();
|
|
bits.SkipBits(left);
|
|
|
|
return true;
|
|
}
|
|
|
|
void SmackerDecoder::GetNextFrame()
|
|
{
|
|
ReadPacket();
|
|
}
|
|
|
|
int SmackerDecoder::ReadPacket()
|
|
{
|
|
// test-remove
|
|
if (currentFrame >= nFrames)
|
|
return 1;
|
|
|
|
// seek to next frame position
|
|
file.Seek(nextPos, SmackerCommon::FileStream::kSeekStart);
|
|
|
|
uint32_t frameSize = frameSizes[currentFrame] & (~3);
|
|
uint8_t frameFlag = frameFlags[currentFrame];
|
|
|
|
// handle palette change
|
|
if (frameFlag & kSMKpal)
|
|
{
|
|
int size, sz, t, off, j, pos;
|
|
uint8_t *pal = palette;
|
|
uint8_t oldpal[768];
|
|
|
|
memcpy(oldpal, pal, 768);
|
|
size = file.ReadByte();
|
|
size = size * 4 - 1;
|
|
frameSize -= size;
|
|
frameSize--;
|
|
sz = 0;
|
|
pos = file.GetPosition() + size;
|
|
|
|
while (sz < 256)
|
|
{
|
|
t = file.ReadByte();
|
|
if (t & 0x80){ /* skip palette entries */
|
|
sz += (t & 0x7F) + 1;
|
|
pal += ((t & 0x7F) + 1) * 3;
|
|
} else if (t & 0x40){ /* copy with offset */
|
|
off = file.ReadByte() * 3;
|
|
j = (t & 0x3F) + 1;
|
|
while (j-- && sz < 256) {
|
|
*pal++ = oldpal[off + 0];
|
|
*pal++ = oldpal[off + 1];
|
|
*pal++ = oldpal[off + 2];
|
|
sz++;
|
|
off += 3;
|
|
}
|
|
} else { /* new entries */
|
|
*pal++ = smk_pal[t];
|
|
*pal++ = smk_pal[file.ReadByte() & 0x3F];
|
|
*pal++ = smk_pal[file.ReadByte() & 0x3F];
|
|
sz++;
|
|
}
|
|
}
|
|
|
|
file.Seek(pos, SmackerCommon::FileStream::kSeekStart);
|
|
}
|
|
|
|
frameFlag >>= 1;
|
|
|
|
// check for and handle audio
|
|
for (int i = 0; i < kMaxAudioTracks; i++)
|
|
{
|
|
audioTracks[i].bytesReadThisFrame = 0;
|
|
|
|
if (frameFlag & 1)
|
|
{
|
|
uint32_t size = file.ReadUint32LE() - 4;
|
|
frameSize -= size;
|
|
frameSize -= 4;
|
|
|
|
DecodeAudio(size, audioTracks[i]);
|
|
}
|
|
frameFlag >>= 1;
|
|
}
|
|
|
|
if (frameSize == 0) {
|
|
return -1;
|
|
}
|
|
|
|
DecodeFrame(frameSize);
|
|
|
|
currentFrame++;
|
|
|
|
nextPos = file.GetPosition();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SmackerDecoder::DecodeFrame(uint32_t frameSize)
|
|
{
|
|
last_reset(mmap_tbl, mmap_last);
|
|
last_reset(mclr_tbl, mclr_last);
|
|
last_reset(full_tbl, full_last);
|
|
last_reset(type_tbl, type_last);
|
|
|
|
int blocks, blk, bw, bh;
|
|
int i;
|
|
int stride;
|
|
|
|
uint8_t *out = picture; // set to output image
|
|
|
|
blk = 0;
|
|
bw = frameWidth >> 2;
|
|
bh = frameHeight >> 2;
|
|
blocks = bw * bh;
|
|
|
|
stride = frameWidth;
|
|
|
|
SmackerCommon::BitReader bits(file, frameSize);
|
|
|
|
while (blk < blocks)
|
|
{
|
|
int type, run, mode;
|
|
uint16_t pix;
|
|
|
|
type = GetCode(bits, type_tbl, type_last);
|
|
run = block_runs[(type >> 2) & 0x3F];
|
|
switch (type & 3)
|
|
{
|
|
case SMK_BLK_MONO:
|
|
while (run-- && blk < blocks)
|
|
{
|
|
int clr, map;
|
|
int hi, lo;
|
|
clr = GetCode(bits, mclr_tbl, mclr_last);
|
|
map = GetCode(bits, mmap_tbl, mmap_last);
|
|
|
|
out = picture + (blk / bw) * (stride * 4) + (blk % bw) * 4;
|
|
|
|
hi = clr >> 8;
|
|
lo = clr & 0xFF;
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (map & 1) out[0] = hi; else out[0] = lo;
|
|
if (map & 2) out[1] = hi; else out[1] = lo;
|
|
if (map & 4) out[2] = hi; else out[2] = lo;
|
|
if (map & 8) out[3] = hi; else out[3] = lo;
|
|
map >>= 4;
|
|
out += stride;
|
|
}
|
|
blk++;
|
|
}
|
|
break;
|
|
case SMK_BLK_FULL:
|
|
mode = 0;
|
|
if (isVer4) // In case of Smacker v4 we have three modes
|
|
{
|
|
if (bits.GetBit()) mode = 1;
|
|
else if (bits.GetBit()) mode = 2;
|
|
}
|
|
|
|
while (run-- && blk < blocks)
|
|
{
|
|
out = picture + (blk / bw) * (stride * 4) + (blk % bw) * 4;
|
|
switch (mode)
|
|
{
|
|
case 0:
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
pix = GetCode(bits, full_tbl, full_last);
|
|
// FIX AV_WL16(out+2, pix);
|
|
out[2] = pix & 0xff;
|
|
out[3] = pix >> 8;
|
|
|
|
pix = GetCode(bits, full_tbl, full_last);
|
|
// FIX AV_WL16(out, pix);
|
|
out[0] = pix & 0xff;
|
|
out[1] = pix >> 8;
|
|
out += stride;
|
|
}
|
|
break;
|
|
case 1:
|
|
pix = GetCode(bits, full_tbl, full_last);
|
|
out[0] = out[1] = pix & 0xFF;
|
|
out[2] = out[3] = pix >> 8;
|
|
out += stride;
|
|
out[0] = out[1] = pix & 0xFF;
|
|
out[2] = out[3] = pix >> 8;
|
|
out += stride;
|
|
pix = GetCode(bits, full_tbl, full_last);
|
|
out[0] = out[1] = pix & 0xFF;
|
|
out[2] = out[3] = pix >> 8;
|
|
out += stride;
|
|
out[0] = out[1] = pix & 0xFF;
|
|
out[2] = out[3] = pix >> 8;
|
|
out += stride;
|
|
break;
|
|
case 2:
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
uint16_t pix1, pix2;
|
|
pix2 = GetCode(bits, full_tbl, full_last);
|
|
pix1 = GetCode(bits, full_tbl, full_last);
|
|
|
|
// FIX AV_WL16(out, pix1);
|
|
// FIX AV_WL16(out+2, pix2);
|
|
out[0] = pix1 & 0xff;
|
|
out[1] = pix1 >> 8;
|
|
out[2] = pix2 & 0xff;
|
|
out[3] = pix2 >> 8;
|
|
|
|
out += stride;
|
|
|
|
// FIX AV_WL16(out, pix1);
|
|
// FIX AV_WL16(out+2, pix2);
|
|
out[0] = pix1 & 0xff;
|
|
out[1] = pix1 >> 8;
|
|
out[2] = pix2 & 0xff;
|
|
out[3] = pix2 >> 8;
|
|
|
|
out += stride;
|
|
}
|
|
break;
|
|
}
|
|
blk++;
|
|
}
|
|
break;
|
|
case SMK_BLK_SKIP:
|
|
while (run-- && blk < blocks)
|
|
blk++;
|
|
break;
|
|
case SMK_BLK_FILL:
|
|
mode = type >> 8;
|
|
while (run-- && blk < blocks)
|
|
{
|
|
uint32_t col;
|
|
out = picture + (blk / bw) * (stride * 4) + (blk % bw) * 4;
|
|
col = mode * 0x01010101;
|
|
for (i = 0; i < 4; i++) {
|
|
*((uint32_t*)out) = col;
|
|
out += stride;
|
|
}
|
|
blk++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* FIXME - we don't seems to read/use EVERY bit we 'load' into the bit reader
|
|
* and as my bitreader reads from the file rather than a buffer read from file
|
|
* of size 'frameSize', I need to make sure I consume the remaining bits (and thus increment
|
|
* the file read position to where the code expects it to be when this function returns (ie
|
|
* 'frameSize' number of bytes must be read
|
|
*/
|
|
uint32_t left = bits.GetSize() - bits.GetPosition();
|
|
bits.SkipBits(left);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Decode Smacker audio data
|
|
*/
|
|
int SmackerDecoder::DecodeAudio(uint32_t size, SmackerAudioTrack &track)
|
|
{
|
|
HuffContext h[4];
|
|
SmackerCommon::VLCtable vlc[4];
|
|
int val;
|
|
int i, res;
|
|
int unpackedSize;
|
|
int sampleBits, stereo;
|
|
int pred[2] = {0, 0};
|
|
|
|
int16_t *samples = reinterpret_cast<int16_t*>(track.buffer);
|
|
int8_t *samples8 = reinterpret_cast<int8_t*>(track.buffer);
|
|
|
|
int buf_size = track.bufferSize;
|
|
|
|
if (buf_size <= 4) {
|
|
Printf("SmackerDecoder::DecodeAudio() - Packet is too small\n");
|
|
return -1;
|
|
}
|
|
|
|
SmackerCommon::BitReader bits(file, size);
|
|
|
|
unpackedSize = bits.GetBits(32);
|
|
|
|
if (!bits.GetBit()) {
|
|
// no sound data
|
|
return 1;
|
|
}
|
|
|
|
stereo = bits.GetBit();
|
|
sampleBits = bits.GetBit();
|
|
|
|
if (stereo ^ (track.nChannels != 1)) {
|
|
Printf("SmackerDecoder::DecodeAudio() - Channels mismatch\n");
|
|
return -1;
|
|
}
|
|
|
|
memset(h, 0, sizeof(HuffContext) * 4);
|
|
|
|
// Initialize
|
|
for (i = 0; i < (1 << (sampleBits + stereo)); i++) {
|
|
h[i].length = 256;
|
|
h[i].maxlength = 0;
|
|
h[i].current = 0;
|
|
h[i].bits.resize(256);
|
|
h[i].lengths.resize(256);
|
|
h[i].values.resize(256);
|
|
|
|
bits.SkipBits(1);
|
|
DecodeTree(bits, &h[i], 0, 0);
|
|
bits.SkipBits(1);
|
|
|
|
if (h[i].current > 1) {
|
|
VLC_InitTable(vlc[i], h[i].maxlength, h[i].current, &h[i].lengths[0], &h[i].bits[0]);
|
|
}
|
|
}
|
|
if (sampleBits) { //decode 16-bit data
|
|
for (i = stereo; i >= 0; i--)
|
|
pred[i] = av_bswap16(bits.GetBits(16));
|
|
for (i = 0; i <= stereo; i++)
|
|
*samples++ = pred[i];
|
|
for (; i < unpackedSize / 2; i++) {
|
|
if (i & stereo) {
|
|
if (VLC_GetSize(vlc[2]))
|
|
res = VLC_GetCodeBits(bits, vlc[2]);
|
|
else
|
|
res = 0;
|
|
val = h[2].values[res];
|
|
if (VLC_GetSize(vlc[3]))
|
|
res = VLC_GetCodeBits(bits, vlc[3]);
|
|
else
|
|
res = 0;
|
|
val |= h[3].values[res] << 8;
|
|
pred[1] += (int16_t)val;
|
|
*samples++ = pred[1];
|
|
} else {
|
|
if (VLC_GetSize(vlc[0]))
|
|
res = VLC_GetCodeBits(bits, vlc[0]);
|
|
else
|
|
res = 0;
|
|
val = h[0].values[res];
|
|
if (VLC_GetSize(vlc[1]))
|
|
res = VLC_GetCodeBits(bits, vlc[1]);
|
|
else
|
|
res = 0;
|
|
val |= h[1].values[res] << 8;
|
|
pred[0] += val;
|
|
*samples++ = pred[0];
|
|
}
|
|
}
|
|
}
|
|
else { //8-bit data
|
|
for (i = stereo; i >= 0; i--)
|
|
pred[i] = bits.GetBits(8);
|
|
for (i = 0; i <= stereo; i++)
|
|
*samples8++ = pred[i];
|
|
for (; i < unpackedSize; i++) {
|
|
if (i & stereo){
|
|
if (VLC_GetSize(vlc[1]))
|
|
res = VLC_GetCodeBits(bits, vlc[1]);
|
|
else
|
|
res = 0;
|
|
pred[1] += (int8_t)h[1].values[res];
|
|
*samples8++ = pred[1];
|
|
} else {
|
|
if (VLC_GetSize(vlc[0]))
|
|
res = VLC_GetCodeBits(bits, vlc[0]);
|
|
else
|
|
res = 0;
|
|
pred[0] += (int8_t)h[0].values[res];
|
|
*samples8++ = pred[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
track.bytesReadThisFrame = unpackedSize;
|
|
|
|
uint32_t left = bits.GetSize() - bits.GetPosition();
|
|
bits.SkipBits(left);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SmackerDecoder::GetPalette(uint8_t *palette)
|
|
{
|
|
memcpy(palette, this->palette, 768);
|
|
}
|
|
|
|
void SmackerDecoder::GetFrame(uint8_t *frame)
|
|
{
|
|
memcpy(frame, this->picture, frameWidth * frameHeight);
|
|
}
|
|
|
|
void SmackerDecoder::GetFrameSize(uint32_t &width, uint32_t &height)
|
|
{
|
|
width = this->frameWidth;
|
|
height = this->frameHeight;
|
|
}
|
|
|
|
uint32_t SmackerDecoder::GetNumFrames()
|
|
{
|
|
return nFrames;
|
|
}
|
|
|
|
uint32_t SmackerDecoder::GetCurrentFrameNum()
|
|
{
|
|
return currentFrame;
|
|
}
|
|
|
|
float SmackerDecoder::GetFrameRate()
|
|
{
|
|
return (float)fps;
|
|
}
|
|
|
|
void SmackerDecoder::GotoFrame(uint32_t frameNum)
|
|
{
|
|
if (frameNum > nFrames) {
|
|
Printf("SmackerDecoder::GotoFrame() - Invalid frame number\n");
|
|
return;
|
|
}
|
|
|
|
// file.Seek(firstFrameFilePos, SmackerCommon::FileStream::kSeekStart);
|
|
|
|
currentFrame = 0;
|
|
nextPos = firstFrameFilePos;
|
|
|
|
for (int i = 0; i < frameNum + 1; i++)
|
|
GetNextFrame();
|
|
}
|
|
|
|
SmackerAudioInfo SmackerDecoder::GetAudioTrackDetails(uint32_t trackIndex)
|
|
{
|
|
SmackerAudioInfo info;
|
|
SmackerAudioTrack *track = &audioTracks[trackIndex];
|
|
|
|
info.sampleRate = track->sampleRate;
|
|
info.nChannels = track->nChannels;
|
|
info.bitsPerSample = track->bitsPerSample;
|
|
|
|
// audio buffer size in bytes
|
|
info.idealBufferSize = track->bufferSize;
|
|
|
|
return info;
|
|
}
|
|
|
|
uint32_t SmackerDecoder::GetAudioData(uint32_t trackIndex, int16_t *audioBuffer)
|
|
{
|
|
if (!audioBuffer) {
|
|
return 0;
|
|
}
|
|
|
|
SmackerAudioTrack *track = &audioTracks[trackIndex];
|
|
|
|
if (track->bytesReadThisFrame) {
|
|
memcpy(audioBuffer, track->buffer, std::min(track->bufferSize, track->bytesReadThisFrame));
|
|
}
|
|
|
|
return track->bytesReadThisFrame;
|
|
}
|