WiiFlow_Lite/source/gui/gcvid.cpp
fix94.1 550ac3356b -made sfmt, wfmt and gprintf faster and more simple
-set down wiiflows entry point from 0x80A00000 to 0x8062000
(saves ALOT of MEM1 for lists etc, so wiiflow should be able to read
in alot of more games in for example plugin coverflow)
-recompiled libjpeg
-removed alot of annoying debug prints
-made the trailer code faster
2012-09-23 12:10:25 +00:00

829 lines
18 KiB
C++

/***************************************************************************
* Copyright (C) 2010
* by thakis
*
* Modification and adjustment for the Wii by Dimok
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any
* damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any
* purpose, including commercial applications, and to alter it and
* redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you
* must not claim that you wrote the original software. If you use
* this software in a product, an acknowledgment in the product
* documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and
* must not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
* gcvid.cpp
***************************************************************************/
#include <cstdlib> //NULL
#include <cstring> //memcmp
#include <string>
#include <cassert>
#include "gcvid.h"
#include "loader/utils.h"
#include "memory/mem2.hpp"
using namespace std;
void readThpHeader(FILE* f, ThpHeader& h)
{
fread(&h, sizeof(h), 1, f);
}
void readThpComponents(FILE* f, ThpComponents& c)
{
fread(&c, sizeof(c), 1, f);
}
void readThpVideoInfo(FILE* f, ThpVideoInfo& i, bool isVersion11)
{
fread(&i, sizeof(i), 1, f);
if(!isVersion11)
{
i.unknown = 0;
fseek(f, -4, SEEK_CUR);
}
}
void readThpAudioInfo(FILE* f, ThpAudioInfo& i, bool isVersion11)
{
fread(&i, sizeof(i), 1, f);
if(!isVersion11)
{
i.numData = 1;
fseek(f, -4, SEEK_CUR);
}
}
void readMthHeader(FILE* f, MthHeader& h)
{
fread(&h, sizeof(h), 1, f);
}
struct DecStruct
{
const u8* currSrcByte;
u32 blockCount;
u8 index;
u8 shift;
};
void thpAudioInitialize(DecStruct& s, const u8* srcStart)
{
s.currSrcByte = srcStart;
s.blockCount = 2;
s.index = (*s.currSrcByte >> 4) & 0x7;
s.shift = *s.currSrcByte & 0xf;
++s.currSrcByte;
}
s32 thpAudioGetNewSample(DecStruct& s)
{
//the following if is executed all 14 calls
//to thpAudioGetNewSample() (once for each
//microblock) because mask & 0xf can contain
//16 different values and starts with 2
if((s.blockCount & 0xf) == 0)
{
s.index = (*s.currSrcByte >> 4) & 0x7;
s.shift = *s.currSrcByte & 0xf;
++s.currSrcByte;
s.blockCount += 2;
}
s32 ret;
if((s.blockCount & 1) != 0)
{
s32 t = (*s.currSrcByte << 28) & 0xf0000000;
ret = t >> 28; //this has to be an arithmetic shift
++s.currSrcByte;
}
else
{
s32 t = (*s.currSrcByte << 24) & 0xf0000000;
ret = t >> 28; //this has to be an arithmetic shift
}
++s.blockCount;
return ret;
}
int thpAudioDecode(s16 * destBuffer, const u8* srcBuffer, bool separateChannelsInOutput, bool isInputStereo)
{
if(destBuffer == NULL || srcBuffer == NULL)
return 0;
ThpAudioBlockHeader* head = (ThpAudioBlockHeader*)srcBuffer;
u32 channelInSize = head->channelSize;
u32 numSamples = head->numSamples;
const u8* srcChannel1 = srcBuffer + sizeof(ThpAudioBlockHeader);
const u8* srcChannel2 = srcChannel1 + channelInSize;
s16* table1 = head->table1;
s16* table2 = head->table2;
s16* destChannel1, * destChannel2;
u32 delta;
if(separateChannelsInOutput)
{
//separated channels in output
destChannel1 = destBuffer;
destChannel2 = destBuffer + numSamples;
delta = 1;
}
else
{
//interleaved channels in output
destChannel1 = destBuffer;
destChannel2 = destBuffer + 1;
delta = 2;
}
DecStruct s;
if(!isInputStereo)
{
//mono channel in input
thpAudioInitialize(s, srcChannel1);
s16 prev1 = *(s16*)(srcBuffer + 72);
s16 prev2 = *(s16*)(srcBuffer + 74);
for(u32 i = 0; i < numSamples; ++i)
{
s64 res = (s64)thpAudioGetNewSample(s);
res = ((res << s.shift) << 11); //convert to 53.11 fixedpoint
//these values are 53.11 fixed point numbers
s64 val1 = table1[2*s.index];
s64 val2 = table1[2*s.index + 1];
//convert to 48.16 fixed point
res = (val1*prev1 + val2*prev2 + res) << 5;
//rounding:
u16 decimalPlaces = res & 0xffff;
if(decimalPlaces > 0x8000) //i.e. > 0.5
//round up
++res;
else if(decimalPlaces == 0x8000) //i.e. == 0.5
if((res & 0x10000) != 0)
//round up every other number
++res;
//get nonfractional parts of number, clamp to [-32768, 32767]
s32 final = (res >> 16);
if(final > 32767) final = 32767;
else if(final < -32768) final = -32768;
prev2 = prev1;
prev1 = final;
*destChannel1 = (s16)final;
*destChannel2 = (s16)final;
destChannel1 += delta;
destChannel2 += delta;
}
}
else
{
//two channels in input - nearly the same as for one channel,
//so no comments here (different lines are marked with XXX)
thpAudioInitialize(s, srcChannel1);
s16 prev1 = *(s16*)(srcBuffer + 72);
s16 prev2 = *(s16*)(srcBuffer + 74);
for(u32 i = 0; i < numSamples; ++i)
{
s64 res = (s64)thpAudioGetNewSample(s);
res = ((res << s.shift) << 11);
s64 val1 = table1[2*s.index];
s64 val2 = table1[2*s.index + 1];
res = (val1*prev1 + val2*prev2 + res) << 5;
u16 decimalPlaces = res & 0xffff;
if(decimalPlaces > 0x8000)
++res;
else if(decimalPlaces == 0x8000)
if((res & 0x10000) != 0)
++res;
s32 final = (res >> 16);
if(final > 32767) final = 32767;
else if(final < -32768) final = -32768;
prev2 = prev1;
prev1 = final;
*destChannel1 = (s16)final;
destChannel1 += delta;
}
thpAudioInitialize(s, srcChannel2);//XXX
prev1 = *(s16*)(srcBuffer + 76);//XXX
prev2 = *(s16*)(srcBuffer + 78);//XXX
for(u32 j = 0; j < numSamples; ++j)
{
s64 res = (s64)thpAudioGetNewSample(s);
res = ((res << s.shift) << 11);
s64 val1 = table2[2*s.index];//XXX
s64 val2 = table2[2*s.index + 1];//XXX
res = (val1*prev1 + val2*prev2 + res) << 5;
u16 decimalPlaces = res & 0xffff;
if(decimalPlaces > 0x8000)
++res;
else if(decimalPlaces == 0x8000)
if((res & 0x10000) != 0)
++res;
s32 final = (res >> 16);
if(final > 32767) final = 32767;
else if(final < -32768) final = -32768;
prev2 = prev1;
prev1 = final;
*destChannel2 = (s16)final;
destChannel2 += delta;
}
}
return numSamples;
}
VideoFrame::VideoFrame()
: _data(NULL), _w(0), _h(0), _p(0)
{}
VideoFrame::~VideoFrame()
{ dealloc(); }
void VideoFrame::resize(int width, int height)
{
if(width == _w && height == _h)
return;
dealloc();
_w = width;
_h = height;
//24 bpp, 4 byte padding
_p = 3*width;
_p += (4 - _p%4)%4;
_data = (u8 *)malloc(_p * _h);
}
int VideoFrame::getWidth() const
{ return _w; }
int VideoFrame::getHeight() const
{ return _h; }
int VideoFrame::getPitch() const
{ return _p; }
u8* VideoFrame::getData()
{ return _data; }
const u8* VideoFrame::getData() const
{ return _data; }
void VideoFrame::dealloc()
{
if(_data != NULL)
free(_data);
_w = _h = _p = 0;
}
//swaps red and blue channel of a video frame
void swapRB(VideoFrame& f)
{
u8* currLine = f.getData();
int hyt = f.getHeight();
int pitch = f.getPitch();
for(int y = 0; y < hyt; ++y)
{
for(int x = 0, x2 = 2; x < pitch; x += 3, x2 += 3)
{
u8 t = currLine[x];
currLine[x] = currLine[x2];
currLine[x2] = t;
}
currLine += pitch;
}
}
enum FILETYPE
{
THP, MTH, JPG,
UNKNOWN = -1
};
FILETYPE getFiletype(FILE* f)
{
long t = ftell(f);
fseek(f, 0, SEEK_SET);
u8 buff[4];
fread(buff, 1, 4, f);
FILETYPE ret = UNKNOWN;
if(memcmp("THP\0", buff, 4) == 0)
ret = THP;
else if(memcmp("MTHP", buff, 4) == 0)
ret = MTH;
else if(buff[0] == 0xff && buff[1] == 0xd8)
ret = JPG;
fseek(f, t, SEEK_SET);
return ret;
}
long getFilesize(FILE* f)
{
long t = ftell(f);
fseek(f, 0, SEEK_END);
long ret = ftell(f);
fseek(f, t, SEEK_SET);
return ret;
}
void decodeJpeg(const u8* data, int size, VideoFrame& dest);
VideoFile::VideoFile(FILE* f)
: loop(true), _f(f)
{}
VideoFile::~VideoFile()
{
fclose(_f);
}
int VideoFile::getWidth() const
{ return 0; }
int VideoFile::getHeight() const
{ return 0; }
float VideoFile::getFps() const
{ return 0.f; }
int VideoFile::getFrameCount() const
{ return 0; }
int VideoFile::getCurrentFrameNr() const
{ return 0; }
bool VideoFile::hasSound() const
{ return false; }
int VideoFile::getNumChannels() const
{ return 0; }
int VideoFile::getFrequency() const
{ return 0; }
int VideoFile::getMaxAudioSamples() const
{ return 0; }
int VideoFile::getCurrentBuffer(s16*) const
{ return 0; }
void VideoFile::loadFrame(VideoFrame& frame, const u8* data, int size) const
{
decodeJpeg(data, size, frame);
}
ThpVideoFile::ThpVideoFile(FILE* f)
: VideoFile(f)
{
readThpHeader(f, _head);
//this is just to find files that have this field != 0, i
//have no such a file
assert(_head.offsetsDataOffset == 0);
readThpComponents(f, _components);
for(u32 i = 0; i < _components.numComponents; ++i)
{
if(_components.componentTypes[i] == 0) //video
readThpVideoInfo(_f, _videoInfo, _head.version == 0x00011000);
else if(_components.componentTypes[i] == 1) //audio
{
readThpAudioInfo(_f, _audioInfo, _head.version == 0x00011000);
assert(_head.maxAudioSamples != 0);
}
}
_numInts = 3;
if(_head.maxAudioSamples != 0)
_numInts = 4;
_currFrameNr = -1;
_nextFrameOffset = _head.firstFrameOffset;
_nextFrameSize = _head.firstFrameSize;
_currFrameData.resize(_head.maxBufferSize); //include some padding
loadNextFrame();
}
int ThpVideoFile::getWidth() const
{ return _videoInfo.width; }
int ThpVideoFile::getHeight() const
{ return _videoInfo.height; }
float ThpVideoFile::getFps() const
{ return _head.fps; }
int ThpVideoFile::getFrameCount() const
{ return _head.numFrames; }
int ThpVideoFile::getCurrentFrameNr() const
{ return _currFrameNr; }
bool ThpVideoFile::loadNextFrame()
{
++_currFrameNr;
if(_currFrameNr >= (int) _head.numFrames)
{
if (!loop)
return false;
_currFrameNr = 0;
_nextFrameOffset = _head.firstFrameOffset;
_nextFrameSize = _head.firstFrameSize;
}
fseek(_f, _nextFrameOffset, SEEK_SET);
fread(&_currFrameData[0], 1, _nextFrameSize, _f);
_nextFrameOffset += _nextFrameSize;
_nextFrameSize = *(u32*)&_currFrameData[0];
return true;
}
void ThpVideoFile::getCurrentFrame(VideoFrame& f) const
{
int size = *(u32*)(&_currFrameData[0] + 8);
loadFrame(f, &_currFrameData[0] + 4*_numInts, size);
}
bool ThpVideoFile::hasSound() const
{ return _head.maxAudioSamples != 0; }
int ThpVideoFile::getNumChannels() const
{
if(hasSound())
return _audioInfo.numChannels;
else
return 0;
}
int ThpVideoFile::getFrequency() const
{
if(hasSound())
return _audioInfo.frequency;
else
return 0;
}
int ThpVideoFile::getMaxAudioSamples() const
{ return _head.maxAudioSamples; }
int ThpVideoFile::getCurrentBuffer(s16* data) const
{
if(!hasSound())
return 0;
int jpegSize = *(u32*)(&_currFrameData[0] + 8);
const u8* src = &_currFrameData[0] + _numInts*4 + jpegSize;
return thpAudioDecode(data, src, false, _audioInfo.numChannels == 2);
}
MthVideoFile::MthVideoFile(FILE* f)
: VideoFile(f)
{
readMthHeader(f, _head);
_currFrameNr = -1;
_nextFrameOffset = _head.offset;
_nextFrameSize = _head.firstFrameSize;
_thisFrameSize = 0;
_currFrameData.resize(_head.maxFrameSize);
loadNextFrame();
}
int MthVideoFile::getWidth() const
{ return _head.width; }
int MthVideoFile::getHeight() const
{ return _head.height; }
float MthVideoFile::getFps() const
{
return (float) 1.0f*_head.fps; //TODO: This has to be in there somewhere
}
int MthVideoFile::getFrameCount() const
{
return _head.numFrames;
}
int MthVideoFile::getCurrentFrameNr() const
{ return _currFrameNr; }
bool MthVideoFile::loadNextFrame()
{
++_currFrameNr;
if(_currFrameNr >= (int) _head.numFrames)
{
if (!loop)
return false;
_currFrameNr = 0;
_nextFrameOffset = _head.offset;
_nextFrameSize = _head.firstFrameSize;
}
fseek(_f, _nextFrameOffset, SEEK_SET);
_currFrameData.resize(_nextFrameSize);
fread(&_currFrameData[0], 1, _nextFrameSize, _f);
_thisFrameSize = _nextFrameSize;
u32 nextSize;
nextSize = *(u32*)(&_currFrameData[0]);
_nextFrameOffset += _nextFrameSize;
_nextFrameSize = nextSize;
return true;
}
void MthVideoFile::getCurrentFrame(VideoFrame& f) const
{
int size = _thisFrameSize;
loadFrame(f, &_currFrameData[0] + 4, size - 4);
}
JpgVideoFile::JpgVideoFile(FILE* f)
: VideoFile(f)
{
vector<u8> data(getFilesize(f));
fread(&data[0], 1, getFilesize(f), f);
loadFrame(_currFrame, &data[0], getFilesize(f));
}
int JpgVideoFile::getWidth() const
{ return _currFrame.getWidth(); }
int JpgVideoFile::getHeight() const
{ return _currFrame.getHeight(); }
int JpgVideoFile::getFrameCount() const
{ return 1; }
void JpgVideoFile::getCurrentFrame(VideoFrame& f) const
{
f.resize(_currFrame.getWidth(), _currFrame.getHeight());
memcpy(f.getData(), _currFrame.getData(),f.getPitch()*f.getHeight());
}
VideoFile* openVideo(const string& fileName)
{
FILE* f = fopen(fileName.c_str(), "rb");
if(f == NULL)
return NULL;
FILETYPE type = getFiletype(f);
switch(type)
{
case THP:
return new ThpVideoFile(f);
case MTH:
return new MthVideoFile(f);
case JPG:
return new JpgVideoFile(f);
default:
fclose(f);
return NULL;
}
}
void closeVideo(VideoFile*& vf)
{
if(vf != NULL)
delete vf;
vf = NULL;
}
//as mentioned above, we have to convert 0xff to 0xff 0x00
//after the image date has begun (ie, after the 0xff 0xda marker)
//but we must not convert the end-of-image-marker (0xff 0xd9)
//this way. There may be 0xff 0xd9 bytes embedded in the image
//data though, so I add 4 bytes to the input buffer
//and fill them with zeroes and check for 0xff 0xd9 0 0
//as end-of-image marker. this is not correct, but works
//and is easier to code... ;-)
//a better solution would be to patch jpeglib so that this conversion
//is not neccessary
u8 endBytesThp[] = { 0xff, 0xd9, 0, 0 }; //used in thp files
u8 endBytesMth[] = { 0xff, 0xd9, 0xff, 0 }; //used in mth files
int countRequiredSize(const u8* data, int size, int& start, int& end)
{
start = 2*size;
end = size;
int count = 0;
int j;
for(j = size - 1; data[j] == 0; --j)
; //search end of data
if(data[j] == 0xd9) //thp file
end = j - 1;
else if(data[j] == 0xff) //mth file
end = j - 2;
for(int i = 0; i < end; ++i)
{
if(data[i] == 0xff)
{
//if i == srcSize - 1, then this would normally overrun src - that's why 4 padding
//bytes are included at the end of src
if(data[i + 1] == 0xda && start == 2*size)
start = i;
if(i > start)
++count;
}
}
return size + count;
}
void convertToRealJpeg(u8* dest, const u8* src, int srcSize, int start, int end)
{
int di = 0;
for(int i = 0; i < srcSize; ++i, ++di)
{
dest[di] = src[i];
//if i == srcSize - 1, then this would normally overrun src - that's why 4 padding
//bytes are included at the end of src
if(src[i] == 0xff && i > start && i < end)
{
++di;
dest[di] = 0;
}
}
}
void decodeJpeg(const u8* data, int size, VideoFrame& dest)
{
//convert format so jpeglib understands it...
int start, end;
int newSize = countRequiredSize(data, size, start, end);
u8* buff = new u8[newSize];
convertToRealJpeg(buff, data, size, start, end);
//...and feed it to jpeglib
decodeRealJpeg(buff, newSize, dest);
delete [] buff;
}
extern "C"
{
#include "jpeglib.h"
#include <setjmp.h>
}
//the following functions are needed to let
//libjpeg read from memory instead of from a file...
//it's a little clumsy to do :-|
const u8* g_jpegBuffer;
int g_jpegSize;
bool g_isLoading = false;
void jpegInitSource(j_decompress_ptr)
{}
boolean jpegFillInputBuffer(j_decompress_ptr cinfo)
{
cinfo->src->next_input_byte = g_jpegBuffer;
cinfo->src->bytes_in_buffer = g_jpegSize;
return TRUE;
}
void jpegSkipInputData(j_decompress_ptr cinfo, long num_bytes)
{
cinfo->src->next_input_byte += num_bytes;
cinfo->src->bytes_in_buffer -= num_bytes;
}
boolean jpegResyncToRestart(j_decompress_ptr cinfo, int desired)
{
jpeg_resync_to_restart(cinfo, desired);
return TRUE;
}
void jpegTermSource(j_decompress_ptr)
{}
void jpegErrorHandler(j_common_ptr cinfo)
{
char buff[1024];
(*cinfo->err->format_message)(cinfo, buff);
//MessageBox(g_hWnd, buff, "JpegLib error:", MB_OK);
}
void decodeRealJpeg(const u8* data, int size, VideoFrame& dest, bool fancy)
{
if(g_isLoading)
return;
g_isLoading = true;
//decompressor state
jpeg_decompress_struct cinfo;
jpeg_error_mgr errorMgr;
//read from memory manager
jpeg_source_mgr sourceMgr;
cinfo.err = jpeg_std_error(&errorMgr);
errorMgr.error_exit = jpegErrorHandler;
jpeg_create_decompress(&cinfo);
//setup read-from-memory
g_jpegBuffer = data;
g_jpegSize = size;
sourceMgr.bytes_in_buffer = size;
sourceMgr.next_input_byte = data;
sourceMgr.init_source = jpegInitSource;
sourceMgr.fill_input_buffer = jpegFillInputBuffer;
sourceMgr.skip_input_data = jpegSkipInputData;
sourceMgr.resync_to_restart = jpegResyncToRestart;
sourceMgr.term_source = jpegTermSource;
cinfo.src = &sourceMgr;
jpeg_read_header(&cinfo, TRUE);
if(fancy)
{
cinfo.do_fancy_upsampling = TRUE;
cinfo.do_block_smoothing = TRUE;
cinfo.dct_method = JDCT_ISLOW;
jpeg_start_decompress(&cinfo);
dest.resize(ALIGN(4, cinfo.output_width), ALIGN(4, cinfo.output_height));
}
else
{
cinfo.do_fancy_upsampling = FALSE;
cinfo.do_block_smoothing = FALSE;
jpeg_start_decompress(&cinfo);
dest.resize(cinfo.output_width, cinfo.output_height);
}
if(cinfo.num_components == 3)
{
int y = 0;
while(cinfo.output_scanline < cinfo.output_height)
{
//invert image because windows wants it downside up
u8* destBuffer = &dest.getData()[(dest.getHeight() - y - 1)*dest.getPitch()];
//NO idea why jpeglib wants a pointer to a pointer
jpeg_read_scanlines(&cinfo, &destBuffer, 1);
++y;
}
//jpeglib gives an error in jpeg_finish_decompress() if no all
//scanlines are read by the application... :-|
//(but because we read all scanlines, it's not really needed)
cinfo.output_scanline = cinfo.output_height;
}
else
{
//MessageBox(g_hWnd, "Only RGB videos are currently supported.", "oops?", MB_OK);
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
g_isLoading = false;
}