mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-03 17:33:05 +00:00
ALL: synced with scummvm
This commit is contained in:
parent
46eec672a8
commit
f52da5c053
@ -163,7 +163,7 @@ int MidiDriver_FluidSynth::open() {
|
||||
|
||||
Common::String interpolation = ConfMan.get("fluidsynth_misc_interpolation");
|
||||
int interpMethod = FLUID_INTERP_4THORDER;
|
||||
|
||||
|
||||
if (interpolation == "none") {
|
||||
interpMethod = FLUID_INTERP_NONE;
|
||||
} else if (interpolation == "linear") {
|
||||
|
@ -39,6 +39,12 @@ PlaybackFile::PlaybackFile() : _tmpRecordFile(_tmpBuffer, kRecordBuffSize), _tmp
|
||||
_writeStream = NULL;
|
||||
_screenshotsFile = NULL;
|
||||
_mode = kClosed;
|
||||
|
||||
_recordFile = 0;
|
||||
_headerDumped = false;
|
||||
_recordCount = 0;
|
||||
_eventsSize = 0;
|
||||
memset(_tmpBuffer, 1, kRecordBuffSize);
|
||||
}
|
||||
|
||||
PlaybackFile::~PlaybackFile() {
|
||||
|
5
configure
vendored
5
configure
vendored
@ -1782,7 +1782,8 @@ android | gamecube | psp | tizen | wii | webos)
|
||||
*)
|
||||
# ICC does not support pedantic, while GCC and clang do.
|
||||
if test "$have_icc" = no ; then
|
||||
CXXFLAGS="$CXXFLAGS -pedantic"
|
||||
# We *do* want the 'long long' extension.
|
||||
CXXFLAGS="$CXXFLAGS -pedantic -Wno-long-long"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
@ -1840,7 +1841,7 @@ esac
|
||||
# Determine a data type with the given length
|
||||
#
|
||||
find_type_with_size() {
|
||||
for datatype in int short char long "long long" unknown; do
|
||||
for datatype in int short char long "long long" __int64 "long long int" unknown; do
|
||||
cat > tmp_find_type_with_size.cpp << EOF
|
||||
typedef $datatype ac__type_sizeof_;
|
||||
int main() {
|
||||
|
@ -77,6 +77,19 @@ EventRecorder::EventRecorder() {
|
||||
_initialized = false;
|
||||
_needRedraw = false;
|
||||
_fastPlayback = false;
|
||||
|
||||
_fakeTimer = 0;
|
||||
_savedState = false;
|
||||
_needcontinueGame = false;
|
||||
_temporarySlot = 0;
|
||||
_realSaveManager = 0;
|
||||
_realMixerManager = 0;
|
||||
_controlPanel = 0;
|
||||
_lastMillis = 0;
|
||||
_lastScreenshotTime = 0;
|
||||
_screenshotPeriod = 0;
|
||||
_playbackFile = 0;
|
||||
|
||||
DebugMan.addDebugChannel(kDebugLevelEventRec, "EventRec", "Event recorder debug level");
|
||||
}
|
||||
|
||||
@ -96,8 +109,8 @@ void EventRecorder::deinit() {
|
||||
_recordMode = kPassthrough;
|
||||
delete _fakeMixerManager;
|
||||
_fakeMixerManager = NULL;
|
||||
controlPanel->close();
|
||||
delete controlPanel;
|
||||
_controlPanel->close();
|
||||
delete _controlPanel;
|
||||
debugC(1, kDebugLevelEventRec, "playback:action=stopplayback");
|
||||
g_system->getEventManager()->getEventDispatcher()->unregisterSource(this);
|
||||
_recordMode = kPassthrough;
|
||||
@ -127,7 +140,7 @@ void EventRecorder::processMillis(uint32 &millis, bool skipRecord) {
|
||||
millisDelay = millis - _lastMillis;
|
||||
_lastMillis = millis;
|
||||
_fakeTimer += millisDelay;
|
||||
controlPanel->setReplayedTime(_fakeTimer);
|
||||
_controlPanel->setReplayedTime(_fakeTimer);
|
||||
timerEvent.recordedtype = Common::kRecorderEventTypeTimer;
|
||||
timerEvent.time = _fakeTimer;
|
||||
_playbackFile->writeEvent(timerEvent);
|
||||
@ -150,7 +163,7 @@ void EventRecorder::processMillis(uint32 &millis, bool skipRecord) {
|
||||
}
|
||||
}
|
||||
millis = _fakeTimer;
|
||||
controlPanel->setReplayedTime(_fakeTimer);
|
||||
_controlPanel->setReplayedTime(_fakeTimer);
|
||||
break;
|
||||
case kRecorderPlaybackPause:
|
||||
millis = _fakeTimer;
|
||||
@ -173,7 +186,7 @@ void EventRecorder::checkForKeyCode(const Common::Event &event) {
|
||||
bool EventRecorder::pollEvent(Common::Event &ev) {
|
||||
if ((_recordMode != kRecorderPlayback) || !_initialized)
|
||||
return false;
|
||||
|
||||
|
||||
if ((_nextEvent.recordedtype == Common::kRecorderEventTypeTimer) || (_nextEvent.type == Common::EVENT_INVALID)) {
|
||||
return false;
|
||||
}
|
||||
@ -209,12 +222,12 @@ void EventRecorder::togglePause() {
|
||||
case kRecorderRecord:
|
||||
oldState = _recordMode;
|
||||
_recordMode = kRecorderPlaybackPause;
|
||||
controlPanel->runModal();
|
||||
_controlPanel->runModal();
|
||||
_recordMode = oldState;
|
||||
_initialized = true;
|
||||
break;
|
||||
case kRecorderPlaybackPause:
|
||||
controlPanel->close();
|
||||
_controlPanel->close();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -277,7 +290,7 @@ void EventRecorder::init(Common::String recordFileName, RecordMode mode) {
|
||||
return;
|
||||
}
|
||||
if (_recordMode != kPassthrough) {
|
||||
controlPanel = new GUI::OnScreenDialog(_recordMode == kRecorderRecord);
|
||||
_controlPanel = new GUI::OnScreenDialog(_recordMode == kRecorderRecord);
|
||||
}
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
applyPlaybackSettings();
|
||||
@ -318,7 +331,7 @@ bool EventRecorder::openRecordFile(const Common::String &fileName) {
|
||||
}
|
||||
|
||||
bool EventRecorder::checkGameHash(const ADGameDescription *gameDesc) {
|
||||
if ((gameDesc == NULL) && (_playbackFile->getHeader().hashRecords.size() != 0)) {
|
||||
if (_playbackFile->getHeader().hashRecords.size() != 0) {
|
||||
warning("Engine doesn't contain description table");
|
||||
return false;
|
||||
}
|
||||
@ -439,8 +452,8 @@ Common::List<Common::Event> EventRecorder::mapEvent(const Common::Event &ev, Com
|
||||
return Common::DefaultEventMapper::mapEvent(ev, source);
|
||||
break;
|
||||
case kRecorderRecord:
|
||||
g_gui.processEvent(evt, controlPanel);
|
||||
if (((evt.type == Common::EVENT_LBUTTONDOWN) || (evt.type == Common::EVENT_LBUTTONUP) || (evt.type == Common::EVENT_MOUSEMOVE)) && controlPanel->isMouseOver()) {
|
||||
g_gui.processEvent(evt, _controlPanel);
|
||||
if (((evt.type == Common::EVENT_LBUTTONDOWN) || (evt.type == Common::EVENT_LBUTTONUP) || (evt.type == Common::EVENT_MOUSEMOVE)) && _controlPanel->isMouseOver()) {
|
||||
return Common::List<Common::Event>();
|
||||
} else {
|
||||
Common::RecorderEvent e;
|
||||
@ -453,13 +466,13 @@ Common::List<Common::Event> EventRecorder::mapEvent(const Common::Event &ev, Com
|
||||
break;
|
||||
case kRecorderPlaybackPause: {
|
||||
Common::Event dialogEvent;
|
||||
if (controlPanel->isEditDlgVisible()) {
|
||||
if (_controlPanel->isEditDlgVisible()) {
|
||||
dialogEvent = ev;
|
||||
} else {
|
||||
dialogEvent = evt;
|
||||
}
|
||||
g_gui.processEvent(dialogEvent, controlPanel->getActiveDlg());
|
||||
if (((dialogEvent.type == Common::EVENT_LBUTTONDOWN) || (dialogEvent.type == Common::EVENT_LBUTTONUP) || (dialogEvent.type == Common::EVENT_MOUSEMOVE)) && controlPanel->isMouseOver()) {
|
||||
g_gui.processEvent(dialogEvent, _controlPanel->getActiveDlg());
|
||||
if (((dialogEvent.type == Common::EVENT_LBUTTONDOWN) || (dialogEvent.type == Common::EVENT_LBUTTONUP) || (dialogEvent.type == Common::EVENT_MOUSEMOVE)) && _controlPanel->isMouseOver()) {
|
||||
return Common::List<Common::Event>();
|
||||
}
|
||||
return Common::DefaultEventMapper::mapEvent(dialogEvent, source);
|
||||
@ -549,7 +562,7 @@ void EventRecorder::preDrawOverlayGui() {
|
||||
g_system->showOverlay();
|
||||
g_gui.theme()->clearAll();
|
||||
g_gui.theme()->openDialog(true, GUI::ThemeEngine::kShadingNone);
|
||||
controlPanel->drawDialog();
|
||||
_controlPanel->drawDialog();
|
||||
g_gui.theme()->finishBuffering();
|
||||
g_gui.theme()->updateScreen();
|
||||
_recordMode = oldMode;
|
||||
|
@ -193,7 +193,7 @@ private:
|
||||
DefaultTimerManager *_timerManager;
|
||||
RecorderSaveFileManager _fakeSaveManager;
|
||||
NullSdlMixerManager *_fakeMixerManager;
|
||||
GUI::OnScreenDialog *controlPanel;
|
||||
GUI::OnScreenDialog *_controlPanel;
|
||||
Common::RecorderEvent _nextEvent;
|
||||
|
||||
void setFileHeader();
|
||||
|
@ -46,7 +46,7 @@ enum {
|
||||
// 0 - 2 -- set a custom color:
|
||||
// 0 normal text
|
||||
// 1 highlighted text
|
||||
// 2 disabled text
|
||||
// 2 disabled text
|
||||
// TODO: Maybe add a tab/indent feature; that is, make it possible to specify
|
||||
// an amount by which that line shall be indented (the indent of course would have
|
||||
// to be considered while performing any word wrapping, too).
|
||||
@ -139,7 +139,7 @@ void AboutDialog::addLine(const char *str) {
|
||||
} else {
|
||||
Common::String format(str, 2);
|
||||
str += 2;
|
||||
|
||||
|
||||
static Common::String asciiStr;
|
||||
if (format[0] == 'A') {
|
||||
bool useAscii = false;
|
||||
|
@ -67,6 +67,7 @@ void ChooserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data
|
||||
break;
|
||||
case kCloseCmd:
|
||||
setResult(-1);
|
||||
// Fall through
|
||||
default:
|
||||
Dialog::handleCommand(sender, cmd, data);
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ void EditRecordDialog::setAuthor(const Common::String &author) {
|
||||
|
||||
const Common::String EditRecordDialog::getNotes() {
|
||||
return _notesEdit->getEditString();
|
||||
}
|
||||
}
|
||||
|
||||
void EditRecordDialog::setNotes(const Common::String &desc) {
|
||||
_notesEdit->setEditString(desc);
|
||||
|
@ -97,26 +97,28 @@ OnScreenDialog::OnScreenDialog(bool isRecord) : Dialog("OnScreenDialog") {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
GUI::ButtonWidget *btn;
|
||||
if (g_system->getOverlayWidth() > 320)
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.StopButton", "[ ]", _("Stop"), kStopCmd);
|
||||
new ButtonWidget(this, "OnScreenDialog.StopButton", "[ ]", _("Stop"), kStopCmd);
|
||||
else
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.StopButton", "[]", _("Stop"), kStopCmd);
|
||||
new ButtonWidget(this, "OnScreenDialog.StopButton", "[]", _("Stop"), kStopCmd);
|
||||
|
||||
if (isRecord) {
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.EditButton", "E", _("Edit record description"), kEditCmd);
|
||||
new ButtonWidget(this, "OnScreenDialog.EditButton", "E", _("Edit record description"), kEditCmd);
|
||||
} else {
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.SwitchModeButton", "G", _("Switch to Game"), kSwitchModeCmd);
|
||||
new ButtonWidget(this, "OnScreenDialog.SwitchModeButton", "G", _("Switch to Game"), kSwitchModeCmd);
|
||||
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.FastReplayButton", ">>", _("Fast replay"), kFastModeCmd);
|
||||
new ButtonWidget(this, "OnScreenDialog.FastReplayButton", ">>", _("Fast replay"), kFastModeCmd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
text = new GUI::StaticTextWidget(this, "OnScreenDialog.TimeLabel", "00:00:00");
|
||||
_text = new GUI::StaticTextWidget(this, "OnScreenDialog.TimeLabel", "00:00:00");
|
||||
_enableDrag = false;
|
||||
_mouseOver = false;
|
||||
_editDlgShown = false;
|
||||
|
||||
_lastTime = 0;
|
||||
_dlg = 0;
|
||||
}
|
||||
|
||||
void OnScreenDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
|
||||
@ -128,20 +130,20 @@ void OnScreenDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
|
||||
close();
|
||||
break;
|
||||
case kEditCmd:
|
||||
dlg = new EditRecordDialog(g_eventRec.getAuthor(), g_eventRec.getName(), g_eventRec.getNotes());
|
||||
_dlg = new EditRecordDialog(g_eventRec.getAuthor(), g_eventRec.getName(), g_eventRec.getNotes());
|
||||
CursorMan.lock(false);
|
||||
g_eventRec.setRedraw(false);
|
||||
g_system->showOverlay();
|
||||
_editDlgShown = true;
|
||||
dlg->runModal();
|
||||
_dlg->runModal();
|
||||
_editDlgShown = false;
|
||||
g_system->hideOverlay();
|
||||
g_eventRec.setRedraw(true);
|
||||
CursorMan.lock(true);
|
||||
g_eventRec.setAuthor(((EditRecordDialog *)dlg)->getAuthor());
|
||||
g_eventRec.setName(((EditRecordDialog *)dlg)->getName());
|
||||
g_eventRec.setNotes(((EditRecordDialog *)dlg)->getNotes());
|
||||
delete dlg;
|
||||
g_eventRec.setAuthor(((EditRecordDialog *)_dlg)->getAuthor());
|
||||
g_eventRec.setName(((EditRecordDialog *)_dlg)->getName());
|
||||
g_eventRec.setNotes(((EditRecordDialog *)_dlg)->getNotes());
|
||||
delete _dlg;
|
||||
break;
|
||||
case kSwitchModeCmd:
|
||||
if (g_eventRec.switchMode()) {
|
||||
@ -155,10 +157,10 @@ void OnScreenDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
|
||||
}
|
||||
|
||||
void OnScreenDialog::setReplayedTime(uint32 newTime) {
|
||||
if (newTime - lastTime > 1000) {
|
||||
if (newTime - _lastTime > 1000) {
|
||||
uint32 seconds = newTime / 1000;
|
||||
text->setLabel(Common::String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60));
|
||||
lastTime = newTime;
|
||||
_text->setLabel(Common::String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60));
|
||||
_lastTime = newTime;
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +220,7 @@ void OnScreenDialog::close() {
|
||||
|
||||
Dialog *OnScreenDialog::getActiveDlg() {
|
||||
if (_editDlgShown) {
|
||||
return dlg;
|
||||
return _dlg;
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
|
@ -30,14 +30,16 @@ namespace GUI {
|
||||
|
||||
class OnScreenDialog : public Dialog {
|
||||
private:
|
||||
uint32 lastTime;
|
||||
uint32 _lastTime;
|
||||
bool _enableDrag;
|
||||
bool _mouseOver;
|
||||
bool _editDlgShown;
|
||||
Common::Point _dragPoint;
|
||||
GUI::StaticTextWidget *text;
|
||||
Dialog *dlg;
|
||||
GUI::StaticTextWidget *_text;
|
||||
Dialog *_dlg;
|
||||
|
||||
bool isMouseOver(int x, int y);
|
||||
|
||||
public:
|
||||
OnScreenDialog(bool recordingMode);
|
||||
~OnScreenDialog();
|
||||
@ -56,7 +58,7 @@ public:
|
||||
bool isEditDlgVisible();
|
||||
Dialog *getActiveDlg();
|
||||
protected:
|
||||
virtual void releaseFocus();
|
||||
virtual void releaseFocus();
|
||||
};
|
||||
|
||||
} // End of namespace GUI
|
||||
|
@ -52,6 +52,12 @@ enum {
|
||||
};
|
||||
|
||||
RecorderDialog::RecorderDialog() : Dialog("RecorderDialog"), _list(0), _currentScreenshot(0) {
|
||||
_firstScreenshotUpdate = false;
|
||||
_screenShotsCount = 0;
|
||||
_currentScreenshotText = 0;
|
||||
_authorText = 0;
|
||||
_notesText = 0;
|
||||
|
||||
_backgroundType = ThemeEngine::kDialogBackgroundSpecial;
|
||||
|
||||
new StaticTextWidget(this, "SaveLoadChooser.Title", _("Recorder or Playback Gameplay"));
|
||||
@ -63,7 +69,7 @@ RecorderDialog::RecorderDialog() : Dialog("RecorderDialog"), _list(0), _currentS
|
||||
new GUI::ButtonWidget(this, "RecorderDialog.Cancel", _("Cancel"), 0, kCloseCmd);
|
||||
new GUI::ButtonWidget(this, "RecorderDialog.Record", _("Record"), 0, kRecordCmd);
|
||||
_playbackButton = new GUI::ButtonWidget(this, "RecorderDialog.Playback", _("Playback"), 0, kPlaybackCmd);
|
||||
|
||||
|
||||
_editButton = new GUI::ButtonWidget(this, "RecorderDialog.Edit", _("Edit"), 0, kEditRecordCmd);
|
||||
|
||||
_editButton->setEnabled(false);
|
||||
@ -74,7 +80,7 @@ RecorderDialog::RecorderDialog() : Dialog("RecorderDialog"), _list(0), _currentS
|
||||
_container = new GUI::ContainerWidget(this, 0, 0, 10, 10);
|
||||
if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") == 1) {
|
||||
new GUI::ButtonWidget(this,"RecorderDialog.NextScreenShotButton", "<", 0, kPrevScreenshotCmd);
|
||||
new GUI::ButtonWidget(this, "RecorderDialog.PreviousScreenShotButton", ">", 0, kNextScreenshotCmd);
|
||||
new GUI::ButtonWidget(this, "RecorderDialog.PreviousScreenShotButton", ">", 0, kNextScreenshotCmd);
|
||||
_currentScreenshotText = new StaticTextWidget(this, "RecorderDialog.currentScreenshot", "0/0");
|
||||
_authorText = new StaticTextWidget(this, "RecorderDialog.Author", _("Author: "));
|
||||
_notesText = new StaticTextWidget(this, "RecorderDialog.Notes", _("Notes: "));
|
||||
@ -185,10 +191,11 @@ void RecorderDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
|
||||
break;
|
||||
case kCloseCmd:
|
||||
setResult(kRecordDialogClose);
|
||||
// Fall through
|
||||
default:
|
||||
Dialog::handleCommand(sender, cmd, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RecorderDialog::updateList() {
|
||||
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
|
||||
|
@ -286,6 +286,7 @@ void SaveLoadChooserSimple::handleCommand(CommandSender *sender, uint32 cmd, uin
|
||||
break;
|
||||
case kCloseCmd:
|
||||
setResult(-1);
|
||||
// Fall through
|
||||
default:
|
||||
SaveLoadChooserDialog::handleCommand(sender, cmd, data);
|
||||
}
|
||||
@ -595,6 +596,7 @@ void SaveLoadChooserGrid::handleCommand(CommandSender *sender, uint32 cmd, uint3
|
||||
|
||||
case kCloseCmd:
|
||||
setResult(-1);
|
||||
// Fall through
|
||||
default:
|
||||
SaveLoadChooserDialog::handleCommand(sender, cmd, data);
|
||||
}
|
||||
|
164
test/common/hash-str.h
Normal file
164
test/common/hash-str.h
Normal file
@ -0,0 +1,164 @@
|
||||
#include <cxxtest/TestSuite.h>
|
||||
#include "common/hash-str.h"
|
||||
|
||||
/**
|
||||
* Test suite for common/hash-str.h
|
||||
* We test a number of case sensitive/insensitive hash and compare functions
|
||||
* using example strings and known hashes, trying to tackle
|
||||
* as much edge cases as possible.
|
||||
*/
|
||||
class HashStrTestSuite : public CxxTest::TestSuite {
|
||||
|
||||
public:
|
||||
void test_case_sensitive_string_equal_to() {
|
||||
|
||||
// Name says it all.
|
||||
// This verifies that the function returns true
|
||||
// for exactly the same string, false for the same
|
||||
// string in mixed case and false for some edge cases
|
||||
// with various spacings plus one character replaced
|
||||
// by itself+128 (if there's some processing done after
|
||||
// conversion to 7-bit ASCII this might yield funny results).
|
||||
|
||||
const Common::String lower("test");
|
||||
const Common::String lower1("test");
|
||||
const Common::String mixed("tESt");
|
||||
const Common::String spaced("test ");
|
||||
const Common::String doublespaced("test ");
|
||||
const Common::String tabbed("test\t");
|
||||
const Common::String plus128("t\345est");
|
||||
// 'e'+128 = 0xE5 = 0o345
|
||||
|
||||
Common::CaseSensitiveString_EqualTo css_et;
|
||||
TS_ASSERT_EQUALS(css_et(lower, mixed), false);
|
||||
TS_ASSERT_EQUALS(css_et(lower, lower1), true);
|
||||
TS_ASSERT_EQUALS(css_et(lower, lower), true);
|
||||
|
||||
// Different sorts of whitespace are to be treated differently.
|
||||
TS_ASSERT_EQUALS(css_et(lower, spaced), false);
|
||||
TS_ASSERT_EQUALS(css_et(lower, tabbed), false);
|
||||
TS_ASSERT_EQUALS(css_et(spaced, tabbed), false);
|
||||
TS_ASSERT_EQUALS(css_et(spaced, doublespaced), false);
|
||||
TS_ASSERT_EQUALS(css_et(lower, plus128), false);
|
||||
}
|
||||
|
||||
void test_ignore_case_equal_to() {
|
||||
|
||||
// This should be probably called case_insensitive_string_equal_to
|
||||
// or something,but it's basically the same thing as
|
||||
// test_case_sensitive_string_equal_to, only it's case
|
||||
// insensitive.
|
||||
|
||||
const Common::String lower("test");
|
||||
const Common::String lower1("test");
|
||||
const Common::String mixed("tESt");
|
||||
const Common::String spaced("test ");
|
||||
const Common::String mixedspaced("tESt ");
|
||||
const Common::String doublespaced("test ");
|
||||
const Common::String tabbed("test\t");
|
||||
const Common::String plus128("t\345est");
|
||||
|
||||
Common::IgnoreCase_EqualTo ic_et;
|
||||
TS_ASSERT_EQUALS(ic_et(lower, mixed), true);
|
||||
TS_ASSERT_EQUALS(ic_et(lower, lower1), true);
|
||||
TS_ASSERT_EQUALS(ic_et(lower, lower), true);
|
||||
// Edge case:
|
||||
TS_ASSERT_EQUALS(ic_et(spaced, mixedspaced), true);
|
||||
|
||||
// Different sorts of whitespace are to be treated differently.
|
||||
TS_ASSERT_EQUALS(ic_et(lower, spaced), false);
|
||||
TS_ASSERT_EQUALS(ic_et(lower, tabbed), false);
|
||||
TS_ASSERT_EQUALS(ic_et(spaced, tabbed), false);
|
||||
TS_ASSERT_EQUALS(ic_et(spaced, doublespaced), false);
|
||||
TS_ASSERT_EQUALS(ic_et(lower, plus128), false);
|
||||
}
|
||||
|
||||
void test_case_sensitive_string_hash() {
|
||||
|
||||
// Here we compute string hashes for different
|
||||
// strings and see that the functor is case sensitive
|
||||
// and does not ignore spaces.
|
||||
|
||||
const Common::String lower("test");
|
||||
const Common::String lower1("test");
|
||||
const Common::String mixed("tESt");
|
||||
const Common::String spaced("test ");
|
||||
const Common::String mixedspaced("tESt ");
|
||||
const Common::String doublespaced("test ");
|
||||
const Common::String tabbed("test\t");
|
||||
|
||||
Common::CaseSensitiveString_Hash css_h;
|
||||
TS_ASSERT_EQUALS(css_h(lower), css_h(lower1));
|
||||
TS_ASSERT_DIFFERS(css_h(mixed), css_h(lower));
|
||||
TS_ASSERT_DIFFERS(css_h(spaced), css_h(lower));
|
||||
TS_ASSERT_DIFFERS(css_h(tabbed), css_h(spaced));
|
||||
TS_ASSERT_DIFFERS(css_h(spaced), css_h(doublespaced));
|
||||
}
|
||||
|
||||
void test_ignore_case_hash() {
|
||||
// Same as test_case_sensitive_string_hash, but case insensitive.
|
||||
const Common::String lower("test");
|
||||
const Common::String lower1("test");
|
||||
const Common::String mixed("tESt");
|
||||
const Common::String spaced("test ");
|
||||
const Common::String mixedspaced("tESt ");
|
||||
const Common::String doublespaced("test ");
|
||||
const Common::String tabbed("test\t");
|
||||
|
||||
Common::IgnoreCase_Hash ic_h;
|
||||
TS_ASSERT_EQUALS(ic_h(lower), ic_h(lower1));
|
||||
TS_ASSERT_EQUALS(ic_h(mixed), ic_h(lower));
|
||||
TS_ASSERT_EQUALS(ic_h(spaced), ic_h(mixedspaced));
|
||||
TS_ASSERT_DIFFERS(ic_h(tabbed), ic_h(lower));
|
||||
TS_ASSERT_DIFFERS(ic_h(spaced), ic_h(doublespaced));
|
||||
}
|
||||
|
||||
void test_cpp_string_hash()
|
||||
{
|
||||
// We run the same tests with Hash<String>,
|
||||
// a template specialization of Hash, also a functor.
|
||||
// It is supposed to be case sensitive.
|
||||
|
||||
const Common::String lower("test");
|
||||
const Common::String lower1("test");
|
||||
const Common::String mixed("tESt");
|
||||
const Common::String spaced("test ");
|
||||
const Common::String mixedspaced("tESt ");
|
||||
const Common::String doublespaced("test ");
|
||||
const Common::String tabbed("test\t");
|
||||
|
||||
Common::Hash<Common::String> h;
|
||||
TS_ASSERT_EQUALS(h(lower), h(lower1));
|
||||
TS_ASSERT_DIFFERS(h(mixed), h(lower));
|
||||
TS_ASSERT_DIFFERS(h(spaced), h(lower));
|
||||
TS_ASSERT_DIFFERS(h(tabbed), h(spaced));
|
||||
TS_ASSERT_DIFFERS(h(spaced), h(doublespaced));
|
||||
}
|
||||
|
||||
void test_c_style_string_hash()
|
||||
{
|
||||
// Same as test_cpp_string_hash but with Hash<const char*>,
|
||||
// a template specialization of Hash, also a functor,
|
||||
// that works with C-Style strings.
|
||||
// It is supposed to be case sensitive.
|
||||
|
||||
char lower[] = "test";
|
||||
char lower1[] = "test";
|
||||
char mixed[] = "tESt";
|
||||
char spaced[] = "test ";
|
||||
char mixedspaced[] = "tESt ";
|
||||
char doublespaced[] = "test ";
|
||||
char tabbed[] = "test\t";
|
||||
|
||||
Common::Hash<const char *> h;
|
||||
TS_ASSERT_EQUALS(h(lower), h(lower1));
|
||||
TS_ASSERT_DIFFERS(h(mixed), h(lower));
|
||||
TS_ASSERT_DIFFERS(h(spaced), h(lower));
|
||||
TS_ASSERT_DIFFERS(h(spaced), h(mixedspaced));
|
||||
TS_ASSERT_DIFFERS(h(tabbed), h(spaced));
|
||||
TS_ASSERT_DIFFERS(h(spaced), h(doublespaced));
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
144
test/common/huffman.h
Normal file
144
test/common/huffman.h
Normal file
@ -0,0 +1,144 @@
|
||||
#include <cxxtest/TestSuite.h>
|
||||
#include "common/huffman.h"
|
||||
#include "common/bitstream.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
/**
|
||||
* A test suite for the Huffman decoder in common/huffman.h
|
||||
* The encoding used comes from the example on the Wikipedia page
|
||||
* for Huffman.
|
||||
* TODO: It could be improved by generating one at runtime.
|
||||
*/
|
||||
class HuffmanTestSuite : public CxxTest::TestSuite {
|
||||
public:
|
||||
void test_get_with_full_symbols() {
|
||||
|
||||
/*
|
||||
* The class can be initialized with or without providing
|
||||
* a max_length and a symbol table.
|
||||
* We test with a table.
|
||||
*
|
||||
* Encoding (arbitrary, for testing purpouses):
|
||||
* 0xA=010
|
||||
* 0xB=011
|
||||
* 0xC=11
|
||||
* 0xD=00
|
||||
* 0xE=10
|
||||
*/
|
||||
|
||||
uint32 codeCount = 5;
|
||||
uint8 maxLength = 3;
|
||||
const uint8 lengths[] = {3,3,2,2,2};
|
||||
const uint32 codes[] = {0x2, 0x3, 0x3, 0x0, 0x2};
|
||||
const uint32 symbols[] = {0xA, 0xB, 0xC, 0xD, 0xE};
|
||||
|
||||
Common::Huffman h(maxLength, codeCount, codes, lengths, symbols);
|
||||
|
||||
byte input[] = {0x4F, 0x20};
|
||||
// Provided input...
|
||||
uint32 expected[] = {0xA, 0xB, 0xC, 0xD, 0xE, 0xD, 0xD};
|
||||
// ..and expected output.
|
||||
|
||||
/*
|
||||
* What should be going on:
|
||||
* 010 011 11 00 10 00 00 = A B C D E D D
|
||||
* = 0100 1111 0010 0000 = 0x4F20
|
||||
*/
|
||||
|
||||
Common::MemoryReadStream ms(input, sizeof(input));
|
||||
Common::BitStream8MSB bs(ms);
|
||||
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[0]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[1]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[2]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[3]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[4]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[5]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[6]);
|
||||
}
|
||||
|
||||
void test_get_without_symbols() {
|
||||
|
||||
/*
|
||||
* This is basically the same as test_get_with_full_symbols, but
|
||||
* I only pass the minimal required arguments.
|
||||
* Specifically, I avoid passing the symbols table, so that
|
||||
* array indices are used instead.
|
||||
*
|
||||
* Encoding becomes:
|
||||
*
|
||||
* 0=010
|
||||
* 1=011
|
||||
* 2=11
|
||||
* 3=00
|
||||
* 4=10
|
||||
*/
|
||||
|
||||
uint32 codeCount = 5;
|
||||
const uint8 lengths[] = {3,3,2,2,2};
|
||||
const uint32 codes[] = {0x2, 0x3, 0x3, 0x0, 0x2};
|
||||
|
||||
Common::Huffman h(0, codeCount, codes, lengths, 0);
|
||||
|
||||
byte input[] = {0x4F, 0x20};
|
||||
uint32 expected[] = {0, 1, 2, 3, 4, 3 ,3};
|
||||
|
||||
Common::MemoryReadStream ms(input, sizeof(input));
|
||||
Common::BitStream8MSB bs(ms);
|
||||
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[0]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[1]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[2]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[3]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[4]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[5]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[6]);
|
||||
}
|
||||
|
||||
void test_get_after_set_symbols() {
|
||||
|
||||
/*
|
||||
* Another variation of test_get_with_full_symbols.
|
||||
* I use the setSymbols method to define, a posteriori,
|
||||
* an alphabet to be used in place of array indices.
|
||||
* The encoding is, at first,
|
||||
* 0=010
|
||||
* 1=011
|
||||
* 2=11
|
||||
* 3=00
|
||||
* 4=10
|
||||
* (=array indices).
|
||||
*/
|
||||
|
||||
uint32 codeCount = 5;
|
||||
const uint8 lengths[] = {3,3,2,2,2};
|
||||
const uint32 codes[] = {0x2, 0x3, 0x3, 0x0, 0x2};
|
||||
|
||||
Common::Huffman h(0, codeCount, codes, lengths, 0);
|
||||
|
||||
const uint32 symbols[] = {0xA, 0xB, 0xC, 0xD, 0xE};
|
||||
h.setSymbols(symbols);
|
||||
|
||||
byte input[] = {0x4F, 0x20};
|
||||
uint32 expected[] = {0xA, 0xB, 0xC, 0xD, 0xE, 0xD, 0xD};
|
||||
|
||||
Common::MemoryReadStream ms(input, sizeof(input));
|
||||
Common::BitStream8MSB bs(ms);
|
||||
|
||||
/* New symbols:
|
||||
* A=010
|
||||
* B=011
|
||||
* C=11
|
||||
* D=00
|
||||
* E=10
|
||||
*/
|
||||
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[0]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[1]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[2]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[3]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[4]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[5]);
|
||||
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[6]);
|
||||
}
|
||||
};
|
84
test/common/rendermode.h
Normal file
84
test/common/rendermode.h
Normal file
@ -0,0 +1,84 @@
|
||||
#include <cxxtest/TestSuite.h>
|
||||
#include "common/rendermode.h"
|
||||
#include "common/gui_options.h"
|
||||
#include "common/str.h"
|
||||
|
||||
class RenderModeTestSuite : public CxxTest::TestSuite {
|
||||
public:
|
||||
void test_parse_render_mode_good() {
|
||||
/*
|
||||
* Tests for parseRenderMode.
|
||||
* It takes a code (case-insensitive) and spits a RenderMode back at you.
|
||||
* These cases should work - the inputs are standard, there's just some
|
||||
* fun with caps being had in here.
|
||||
*/
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("fMTOwNs"), Common::kRenderFMTowns);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("hercGrEen"), Common::kRenderHercG);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("hercAmbeR"), Common::kRenderHercA);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("CgA"), Common::kRenderCGA);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("ega"), Common::kRenderEGA);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("Vga"), Common::kRenderVGA);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("AmigA"), Common::kRenderAmiga);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("pc9821"), Common::kRenderPC9821);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("PC9801"), Common::kRenderPC9801);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("0"), Common::kRenderDefault);
|
||||
}
|
||||
|
||||
|
||||
void test_parse_render_mode_bad() {
|
||||
/*
|
||||
* These cases, according to the specification, should return the default.
|
||||
* It is only mentioned that the function must be case insensitive.
|
||||
* Whitespaces, in particular, should not be automatically trimmed.
|
||||
*/
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("fmtowns "), Common::kRenderDefault);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("FM-TOWNS "), Common::kRenderDefault);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode(" cga"), Common::kRenderDefault);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("\tC g A"), Common::kRenderDefault);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("\t"), Common::kRenderDefault);
|
||||
// This is the only interesting bit: if the function was really, really
|
||||
// broken it could be tempted to test for +-0x20.
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("pc Y8 21 "), Common::kRenderDefault);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode(" PC\t9801 "), Common::kRenderDefault);
|
||||
TS_ASSERT_EQUALS(Common::parseRenderMode("0"), Common::kRenderDefault);
|
||||
}
|
||||
|
||||
void test_get_render_mode_code_back_and_forth() {
|
||||
/*
|
||||
* What does getRenderModeCode return?
|
||||
* Notably, the output should not be in mixed case.
|
||||
*/
|
||||
TS_ASSERT_SAME_DATA(Common::getRenderModeCode(Common::parseRenderMode("FMTOWNS")), "fmtowns", 7);
|
||||
TS_ASSERT_DIFFERS(Common::getRenderModeCode(Common::parseRenderMode("FMTOWNS")), "fmtowns");
|
||||
TS_ASSERT_SAME_DATA(Common::getRenderModeCode(Common::parseRenderMode("CGA")), "cga", 3);
|
||||
TS_ASSERT_SAME_DATA(Common::getRenderModeCode(Common::parseRenderMode("vga")), "vga", 3);
|
||||
TS_ASSERT_SAME_DATA(Common::getRenderModeCode(Common::parseRenderMode("Ega")), "ega", 3);
|
||||
TS_ASSERT_SAME_DATA(Common::getRenderModeCode(Common::parseRenderMode("AmiGa")), "amiga", 5);
|
||||
TS_ASSERT_SAME_DATA(Common::getRenderModeCode(Common::parseRenderMode("PC9821")), "pc9821", 6);
|
||||
TS_ASSERT_SAME_DATA(Common::getRenderModeCode(Common::parseRenderMode("PC9801")), "pc9801", 6);
|
||||
// Slightly more interesting:
|
||||
// Make sure that we get a null pointer for 0 (and not the "0" string or stuff)
|
||||
char *null_p = 0;
|
||||
TS_ASSERT_EQUALS(Common::getRenderModeCode(Common::kRenderDefault), null_p);
|
||||
}
|
||||
|
||||
void test_render_2_guio() {
|
||||
/*
|
||||
* Verify that a rendermode is taken and the corresponding
|
||||
* GUIO_xxxxx is returned.
|
||||
*/
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderHercG), GUIO_RENDERHERCGREEN);
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderHercA), GUIO_RENDERHERCAMBER);
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderCGA), GUIO_RENDERCGA);
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderEGA), GUIO_RENDEREGA);
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderVGA), GUIO_RENDERVGA);
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderAmiga), GUIO_RENDERAMIGA);
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderFMTowns), GUIO_RENDERFMTOWNS);
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderPC9821), GUIO_RENDERPC9821);
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderPC9801), GUIO_RENDERPC9801);
|
||||
// renderMode2GUIO is supposed to return an empty string
|
||||
// if given kRenderDefault as an argument
|
||||
Common::String empty;
|
||||
TS_ASSERT_EQUALS(Common::renderMode2GUIO(Common::kRenderDefault), empty);
|
||||
}
|
||||
};
|
237
test/common/util.h
Normal file
237
test/common/util.h
Normal file
@ -0,0 +1,237 @@
|
||||
#include <cxxtest/TestSuite.h>
|
||||
#include "common/util.h"
|
||||
|
||||
/**
|
||||
* Test suite for the functions in common/util.h
|
||||
*/
|
||||
class UtilTestSuite : public CxxTest::TestSuite {
|
||||
public:
|
||||
void test_parsebool_yesno() {
|
||||
|
||||
// First test for the parseBool function.
|
||||
// These are the mixed case yes/no cases that must work
|
||||
|
||||
bool valasbool;
|
||||
bool success;
|
||||
|
||||
Common::String string_1("Yes");
|
||||
success = Common::parseBool(string_1, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 1);
|
||||
TS_ASSERT_EQUALS(valasbool, 1);
|
||||
|
||||
Common::String string_2("nO");
|
||||
success = Common::parseBool(string_2, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 1);
|
||||
TS_ASSERT_EQUALS(valasbool, 0);
|
||||
}
|
||||
|
||||
void test_parsebool_truefalse() {
|
||||
|
||||
// First test for the parseBool function.
|
||||
// These are the mixed case true/false cases that must work
|
||||
|
||||
bool valasbool;
|
||||
bool success;
|
||||
|
||||
Common::String string_3("tRuE");
|
||||
success = Common::parseBool(string_3, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 1);
|
||||
TS_ASSERT_EQUALS(valasbool, 1);
|
||||
|
||||
Common::String string_4("fAlSe");
|
||||
success = Common::parseBool(string_4, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 1);
|
||||
TS_ASSERT_EQUALS(valasbool, 0);
|
||||
}
|
||||
|
||||
void test_parsebool_onezero() {
|
||||
|
||||
// Third test for the parseBool function.
|
||||
// These are the 1/0 cases that must work.
|
||||
// Note that while 'a-z'+0x20 must work just fine,
|
||||
// '0-1'+0x20 should NOT; this is addressed in
|
||||
// parsebool_bad
|
||||
|
||||
bool valasbool;
|
||||
bool success;
|
||||
|
||||
Common::String string_5("1");
|
||||
success = Common::parseBool(string_5, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 1);
|
||||
TS_ASSERT_EQUALS(valasbool, 1);
|
||||
|
||||
Common::String string_6("0");
|
||||
success = Common::parseBool(string_6, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 1);
|
||||
TS_ASSERT_EQUALS(valasbool, 0);
|
||||
|
||||
}
|
||||
|
||||
void test_parsebool_bad() {
|
||||
|
||||
bool valasbool;
|
||||
bool success;
|
||||
|
||||
// Bad cases that should not return success #1:
|
||||
// Random string
|
||||
Common::String string_1("u_f1ght_l1k3_a_c0w");
|
||||
success = Common::parseBool(string_1, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 0);
|
||||
|
||||
// Bad cases that should not return success #2, #3:
|
||||
// The function should NOT accept trailing whitespaces:
|
||||
Common::String string_2(" yes");
|
||||
success = Common::parseBool(string_2, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 0);
|
||||
|
||||
Common::String string_3("yes ");
|
||||
success = Common::parseBool(string_3, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 0);
|
||||
|
||||
// While 'a-z'+0x20 must work just fine,
|
||||
// '0-1'+0x20 should NOT. '2' is not good either.
|
||||
|
||||
Common::String string_4("\x50");
|
||||
success = Common::parseBool(string_4, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 0);
|
||||
|
||||
Common::String string_5("\x51");
|
||||
success = Common::parseBool(string_5, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 0);
|
||||
|
||||
Common::String string_6("2");
|
||||
success = Common::parseBool(string_6, valasbool);
|
||||
TS_ASSERT_EQUALS(success, 0);
|
||||
}
|
||||
|
||||
void test_is_al_num() {
|
||||
|
||||
// Test the isAlnum function
|
||||
// Should return true if and only if the input is an alphanumeric char
|
||||
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('a'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('A'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('z'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('Z'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('1'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('0'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('§'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('$'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum(' '), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('\n'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum('\b'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum(0), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlnum(255), 0);
|
||||
|
||||
}
|
||||
|
||||
void test_is_alpha() {
|
||||
|
||||
// Test the isAlpha function
|
||||
// Should return true if and only if the input is an alphanumeric char
|
||||
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('a'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('A'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('z'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('Z'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('1'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('0'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('§'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('$'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha(' '), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('\n'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha('\b'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha(0), 0);
|
||||
TS_ASSERT_EQUALS(Common::isAlpha(255), 0);
|
||||
|
||||
}
|
||||
|
||||
void test_is_digit() {
|
||||
|
||||
// Test the isDigit function
|
||||
// Should return true if and only if the input is a single digit
|
||||
|
||||
TS_ASSERT_EQUALS(Common::isDigit('a'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit('A'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit('z'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit('Z'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit('1'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isDigit('0'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isDigit('§'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit('$'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit(' '), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit('\n'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit('\b'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit(0), 0);
|
||||
TS_ASSERT_EQUALS(Common::isDigit(255), 0);
|
||||
|
||||
}
|
||||
|
||||
void test_is_lower() {
|
||||
|
||||
// Test the isLower function
|
||||
// Should return true if and only if the input is a lowercase char
|
||||
|
||||
TS_ASSERT_EQUALS(Common::isLower('a'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isLower('A'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower('z'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isLower('Z'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower('1'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower('0'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower('§'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower('$'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower(' '), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower('\n'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower('\b'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower(0), 0);
|
||||
TS_ASSERT_EQUALS(Common::isLower(255), 0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void test_is_upper() {
|
||||
|
||||
// Test the isUpper function
|
||||
// Should return true if and only if the input is an uppercase char
|
||||
|
||||
TS_ASSERT_EQUALS(Common::isUpper('a'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper('A'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isUpper('z'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper('Z'), 1);
|
||||
TS_ASSERT_EQUALS(Common::isUpper('1'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper('0'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper('§'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper('$'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper(' '), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper('\n'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper('\b'), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper(0), 0);
|
||||
TS_ASSERT_EQUALS(Common::isUpper(255), 0);
|
||||
|
||||
}
|
||||
void test_is_space() {
|
||||
// isSpace should return true iff the character is some kind of whitespace
|
||||
// or tab character
|
||||
for (int c = 0; c < 255; c++) {
|
||||
if (c == ' ' || c == '\t' ||
|
||||
c == '\r' || c == '\n' ||
|
||||
c == '\v' || c == '\f') {
|
||||
TS_ASSERT_EQUALS(Common::isSpace(c), 1);
|
||||
} else {
|
||||
TS_ASSERT_EQUALS(Common::isSpace(c), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_is_print() {
|
||||
// isPrint should return true iff the input is a printable ascii char.
|
||||
// That is to say, 0x20 to 0x7E.
|
||||
for (int c = 0; c < 255; c++) {
|
||||
if (c >= 0x20 && c <= 0x7E) {
|
||||
TS_ASSERT_EQUALS(Common::isPrint(c), 1);
|
||||
} else {
|
||||
TS_ASSERT_EQUALS(Common::isPrint(c), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
MODULE := video
|
||||
|
||||
MODULE_OBJS := \
|
||||
smk_decoder.o \
|
||||
video_decoder.o \
|
||||
mpegps_decoder.o
|
||||
ifdef USE_BINK
|
||||
|
863
video/smk_decoder.cpp
Normal file
863
video/smk_decoder.cpp
Normal file
@ -0,0 +1,863 @@
|
||||
/* 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 2
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based on http://wiki.multimedia.cx/index.php?title=Smacker
|
||||
// and the FFmpeg Smacker decoder (libavcodec/smacker.c), revision 16143
|
||||
// http://git.ffmpeg.org/?p=ffmpeg;a=blob;f=libavcodec/smacker.c;hb=b8437a00a2f14d4a437346455d624241d726128e
|
||||
|
||||
#include "video/smk_decoder.h"
|
||||
|
||||
#include "common/endian.h"
|
||||
#include "common/util.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/bitstream.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
|
||||
namespace Video {
|
||||
|
||||
enum SmkBlockTypes {
|
||||
SMK_BLOCK_MONO = 0,
|
||||
SMK_BLOCK_FULL = 1,
|
||||
SMK_BLOCK_SKIP = 2,
|
||||
SMK_BLOCK_FILL = 3
|
||||
};
|
||||
|
||||
/*
|
||||
* class SmallHuffmanTree
|
||||
* A Huffman-tree to hold 8-bit values.
|
||||
*/
|
||||
|
||||
class SmallHuffmanTree {
|
||||
public:
|
||||
SmallHuffmanTree(Common::BitStream &bs);
|
||||
|
||||
uint16 getCode(Common::BitStream &bs);
|
||||
private:
|
||||
enum {
|
||||
SMK_NODE = 0x8000
|
||||
};
|
||||
|
||||
uint16 decodeTree(uint32 prefix, int length);
|
||||
|
||||
uint16 _treeSize;
|
||||
uint16 _tree[511];
|
||||
|
||||
uint16 _prefixtree[256];
|
||||
byte _prefixlength[256];
|
||||
|
||||
Common::BitStream &_bs;
|
||||
};
|
||||
|
||||
SmallHuffmanTree::SmallHuffmanTree(Common::BitStream &bs)
|
||||
: _treeSize(0), _bs(bs) {
|
||||
uint32 bit = _bs.getBit();
|
||||
assert(bit);
|
||||
|
||||
for (uint16 i = 0; i < 256; ++i)
|
||||
_prefixtree[i] = _prefixlength[i] = 0;
|
||||
|
||||
decodeTree(0, 0);
|
||||
|
||||
bit = _bs.getBit();
|
||||
assert(!bit);
|
||||
}
|
||||
|
||||
uint16 SmallHuffmanTree::decodeTree(uint32 prefix, int length) {
|
||||
if (!_bs.getBit()) { // Leaf
|
||||
_tree[_treeSize] = _bs.getBits(8);
|
||||
|
||||
if (length <= 8) {
|
||||
for (int i = 0; i < 256; i += (1 << length)) {
|
||||
_prefixtree[prefix | i] = _treeSize;
|
||||
_prefixlength[prefix | i] = length;
|
||||
}
|
||||
}
|
||||
++_treeSize;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint16 t = _treeSize++;
|
||||
|
||||
if (length == 8) {
|
||||
_prefixtree[prefix] = t;
|
||||
_prefixlength[prefix] = 8;
|
||||
}
|
||||
|
||||
uint16 r1 = decodeTree(prefix, length + 1);
|
||||
|
||||
_tree[t] = (SMK_NODE | r1);
|
||||
|
||||
uint16 r2 = decodeTree(prefix | (1 << length), length + 1);
|
||||
|
||||
return r1+r2+1;
|
||||
}
|
||||
|
||||
uint16 SmallHuffmanTree::getCode(Common::BitStream &bs) {
|
||||
byte peek = bs.peekBits(MIN<uint32>(bs.size() - bs.pos(), 8));
|
||||
uint16 *p = &_tree[_prefixtree[peek]];
|
||||
bs.skip(_prefixlength[peek]);
|
||||
|
||||
while (*p & SMK_NODE) {
|
||||
if (bs.getBit())
|
||||
p += *p & ~SMK_NODE;
|
||||
p++;
|
||||
}
|
||||
|
||||
return *p;
|
||||
}
|
||||
|
||||
/*
|
||||
* class BigHuffmanTree
|
||||
* A Huffman-tree to hold 16-bit values.
|
||||
*/
|
||||
|
||||
class BigHuffmanTree {
|
||||
public:
|
||||
BigHuffmanTree(Common::BitStream &bs, int allocSize);
|
||||
~BigHuffmanTree();
|
||||
|
||||
void reset();
|
||||
uint32 getCode(Common::BitStream &bs);
|
||||
private:
|
||||
enum {
|
||||
SMK_NODE = 0x80000000
|
||||
};
|
||||
|
||||
uint32 decodeTree(uint32 prefix, int length);
|
||||
|
||||
uint32 _treeSize;
|
||||
uint32 *_tree;
|
||||
uint32 _last[3];
|
||||
|
||||
uint32 _prefixtree[256];
|
||||
byte _prefixlength[256];
|
||||
|
||||
/* Used during construction */
|
||||
Common::BitStream &_bs;
|
||||
uint32 _markers[3];
|
||||
SmallHuffmanTree *_loBytes;
|
||||
SmallHuffmanTree *_hiBytes;
|
||||
};
|
||||
|
||||
BigHuffmanTree::BigHuffmanTree(Common::BitStream &bs, int allocSize)
|
||||
: _bs(bs) {
|
||||
uint32 bit = _bs.getBit();
|
||||
if (!bit) {
|
||||
_tree = new uint32[1];
|
||||
_tree[0] = 0;
|
||||
_last[0] = _last[1] = _last[2] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < 256; ++i)
|
||||
_prefixtree[i] = _prefixlength[i] = 0;
|
||||
|
||||
_loBytes = new SmallHuffmanTree(_bs);
|
||||
_hiBytes = new SmallHuffmanTree(_bs);
|
||||
|
||||
_markers[0] = _bs.getBits(16);
|
||||
_markers[1] = _bs.getBits(16);
|
||||
_markers[2] = _bs.getBits(16);
|
||||
|
||||
_last[0] = _last[1] = _last[2] = 0xffffffff;
|
||||
|
||||
_treeSize = 0;
|
||||
_tree = new uint32[allocSize / 4];
|
||||
decodeTree(0, 0);
|
||||
bit = _bs.getBit();
|
||||
assert(!bit);
|
||||
|
||||
for (uint32 i = 0; i < 3; ++i) {
|
||||
if (_last[i] == 0xffffffff) {
|
||||
_last[i] = _treeSize;
|
||||
_tree[_treeSize++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
delete _loBytes;
|
||||
delete _hiBytes;
|
||||
}
|
||||
|
||||
BigHuffmanTree::~BigHuffmanTree() {
|
||||
delete[] _tree;
|
||||
}
|
||||
|
||||
void BigHuffmanTree::reset() {
|
||||
_tree[_last[0]] = _tree[_last[1]] = _tree[_last[2]] = 0;
|
||||
}
|
||||
|
||||
uint32 BigHuffmanTree::decodeTree(uint32 prefix, int length) {
|
||||
uint32 bit = _bs.getBit();
|
||||
|
||||
if (!bit) { // Leaf
|
||||
uint32 lo = _loBytes->getCode(_bs);
|
||||
uint32 hi = _hiBytes->getCode(_bs);
|
||||
|
||||
uint32 v = (hi << 8) | lo;
|
||||
|
||||
_tree[_treeSize] = v;
|
||||
|
||||
if (length <= 8) {
|
||||
for (int i = 0; i < 256; i += (1 << length)) {
|
||||
_prefixtree[prefix | i] = _treeSize;
|
||||
_prefixlength[prefix | i] = length;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (_markers[i] == v) {
|
||||
_last[i] = _treeSize;
|
||||
_tree[_treeSize] = 0;
|
||||
}
|
||||
}
|
||||
++_treeSize;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32 t = _treeSize++;
|
||||
|
||||
if (length == 8) {
|
||||
_prefixtree[prefix] = t;
|
||||
_prefixlength[prefix] = 8;
|
||||
}
|
||||
|
||||
uint32 r1 = decodeTree(prefix, length + 1);
|
||||
|
||||
_tree[t] = SMK_NODE | r1;
|
||||
|
||||
uint32 r2 = decodeTree(prefix | (1 << length), length + 1);
|
||||
return r1+r2+1;
|
||||
}
|
||||
|
||||
uint32 BigHuffmanTree::getCode(Common::BitStream &bs) {
|
||||
byte peek = bs.peekBits(MIN<uint32>(bs.size() - bs.pos(), 8));
|
||||
uint32 *p = &_tree[_prefixtree[peek]];
|
||||
bs.skip(_prefixlength[peek]);
|
||||
|
||||
while (*p & SMK_NODE) {
|
||||
if (bs.getBit())
|
||||
p += (*p) & ~SMK_NODE;
|
||||
p++;
|
||||
}
|
||||
|
||||
uint32 v = *p;
|
||||
if (v != _tree[_last[0]]) {
|
||||
_tree[_last[2]] = _tree[_last[1]];
|
||||
_tree[_last[1]] = _tree[_last[0]];
|
||||
_tree[_last[0]] = v;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
SmackerDecoder::SmackerDecoder(Audio::Mixer::SoundType soundType) : _soundType(soundType) {
|
||||
_fileStream = 0;
|
||||
_firstFrameStart = 0;
|
||||
_frameTypes = 0;
|
||||
_frameSizes = 0;
|
||||
}
|
||||
|
||||
SmackerDecoder::~SmackerDecoder() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||
close();
|
||||
|
||||
_fileStream = stream;
|
||||
|
||||
// Read in the Smacker header
|
||||
_header.signature = _fileStream->readUint32BE();
|
||||
|
||||
if (_header.signature != MKTAG('S', 'M', 'K', '2') && _header.signature != MKTAG('S', 'M', 'K', '4'))
|
||||
return false;
|
||||
|
||||
uint32 width = _fileStream->readUint32LE();
|
||||
uint32 height = _fileStream->readUint32LE();
|
||||
uint32 frameCount = _fileStream->readUint32LE();
|
||||
int32 frameDelay = _fileStream->readSint32LE();
|
||||
|
||||
// frame rate contains 2 digits after the comma, so 1497 is actually 14.97 fps
|
||||
Common::Rational frameRate;
|
||||
if (frameDelay > 0)
|
||||
frameRate = Common::Rational(1000, frameDelay);
|
||||
else if (frameDelay < 0)
|
||||
frameRate = Common::Rational(100000, -frameDelay);
|
||||
else
|
||||
frameRate = 1000;
|
||||
|
||||
// Flags are determined by which bit is set, which can be one of the following:
|
||||
// 0 - set to 1 if file contains a ring frame.
|
||||
// 1 - set to 1 if file is Y-interlaced
|
||||
// 2 - set to 1 if file is Y-doubled
|
||||
// If bits 1 or 2 are set, the frame should be scaled to twice its height
|
||||
// before it is displayed.
|
||||
_header.flags = _fileStream->readUint32LE();
|
||||
|
||||
SmackerVideoTrack *videoTrack = createVideoTrack(width, height, frameCount, frameRate, _header.flags, _header.signature);
|
||||
addTrack(videoTrack);
|
||||
|
||||
// TODO: should we do any extra processing for Smacker files with ring frames?
|
||||
|
||||
// TODO: should we do any extra processing for Y-doubled videos? Are they the
|
||||
// same as Y-interlaced videos?
|
||||
|
||||
uint32 i;
|
||||
for (i = 0; i < 7; ++i)
|
||||
_header.audioSize[i] = _fileStream->readUint32LE();
|
||||
|
||||
_header.treesSize = _fileStream->readUint32LE();
|
||||
_header.mMapSize = _fileStream->readUint32LE();
|
||||
_header.mClrSize = _fileStream->readUint32LE();
|
||||
_header.fullSize = _fileStream->readUint32LE();
|
||||
_header.typeSize = _fileStream->readUint32LE();
|
||||
|
||||
for (i = 0; i < 7; ++i) {
|
||||
// AudioRate - Frequency and format information for each sound track, up to 7 audio tracks.
|
||||
// The 32 constituent bits have the following meaning:
|
||||
// * bit 31 - indicates Huffman + DPCM compression
|
||||
// * bit 30 - indicates that audio data is present for this track
|
||||
// * bit 29 - 1 = 16-bit audio; 0 = 8-bit audio
|
||||
// * bit 28 - 1 = stereo audio; 0 = mono audio
|
||||
// * bit 27 - indicates Bink RDFT compression
|
||||
// * bit 26 - indicates Bink DCT compression
|
||||
// * bits 25-24 - unused
|
||||
// * bits 23-0 - audio sample rate
|
||||
uint32 audioInfo = _fileStream->readUint32LE();
|
||||
_header.audioInfo[i].hasAudio = audioInfo & 0x40000000;
|
||||
_header.audioInfo[i].is16Bits = audioInfo & 0x20000000;
|
||||
_header.audioInfo[i].isStereo = audioInfo & 0x10000000;
|
||||
_header.audioInfo[i].sampleRate = audioInfo & 0xFFFFFF;
|
||||
|
||||
if (audioInfo & 0x8000000)
|
||||
_header.audioInfo[i].compression = kCompressionRDFT;
|
||||
else if (audioInfo & 0x4000000)
|
||||
_header.audioInfo[i].compression = kCompressionDCT;
|
||||
else if (audioInfo & 0x80000000)
|
||||
_header.audioInfo[i].compression = kCompressionDPCM;
|
||||
else
|
||||
_header.audioInfo[i].compression = kCompressionNone;
|
||||
|
||||
if (_header.audioInfo[i].hasAudio) {
|
||||
if (_header.audioInfo[i].compression == kCompressionRDFT || _header.audioInfo[i].compression == kCompressionDCT)
|
||||
warning("Unhandled Smacker v2 audio compression");
|
||||
|
||||
if (i == 0)
|
||||
addTrack(new SmackerAudioTrack(_header.audioInfo[i], _soundType));
|
||||
}
|
||||
}
|
||||
|
||||
_header.dummy = _fileStream->readUint32LE();
|
||||
|
||||
_frameSizes = new uint32[frameCount];
|
||||
for (i = 0; i < frameCount; ++i)
|
||||
_frameSizes[i] = _fileStream->readUint32LE();
|
||||
|
||||
_frameTypes = new byte[frameCount];
|
||||
for (i = 0; i < frameCount; ++i)
|
||||
_frameTypes[i] = _fileStream->readByte();
|
||||
|
||||
byte *huffmanTrees = (byte *) malloc(_header.treesSize);
|
||||
_fileStream->read(huffmanTrees, _header.treesSize);
|
||||
|
||||
Common::BitStream8LSB bs(new Common::MemoryReadStream(huffmanTrees, _header.treesSize, DisposeAfterUse::YES), true);
|
||||
videoTrack->readTrees(bs, _header.mMapSize, _header.mClrSize, _header.fullSize, _header.typeSize);
|
||||
|
||||
_firstFrameStart = _fileStream->pos();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SmackerDecoder::close() {
|
||||
VideoDecoder::close();
|
||||
|
||||
delete _fileStream;
|
||||
_fileStream = 0;
|
||||
|
||||
delete[] _frameTypes;
|
||||
_frameTypes = 0;
|
||||
|
||||
delete[] _frameSizes;
|
||||
_frameSizes = 0;
|
||||
}
|
||||
|
||||
bool SmackerDecoder::rewind() {
|
||||
// Call the parent method to rewind the tracks first
|
||||
if (!VideoDecoder::rewind())
|
||||
return false;
|
||||
|
||||
// And seek back to where the first frame begins
|
||||
_fileStream->seek(_firstFrameStart);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SmackerDecoder::readNextPacket() {
|
||||
SmackerVideoTrack *videoTrack = (SmackerVideoTrack *)getTrack(0);
|
||||
|
||||
if (videoTrack->endOfTrack())
|
||||
return;
|
||||
|
||||
videoTrack->increaseCurFrame();
|
||||
|
||||
uint i;
|
||||
uint32 chunkSize = 0;
|
||||
uint32 dataSizeUnpacked = 0;
|
||||
|
||||
uint32 startPos = _fileStream->pos();
|
||||
|
||||
// Check if we got a frame with palette data, and
|
||||
// call back the virtual setPalette function to set
|
||||
// the current palette
|
||||
if (_frameTypes[videoTrack->getCurFrame()] & 1)
|
||||
videoTrack->unpackPalette(_fileStream);
|
||||
|
||||
// Load audio tracks
|
||||
for (i = 0; i < 7; ++i) {
|
||||
if (!(_frameTypes[videoTrack->getCurFrame()] & (2 << i)))
|
||||
continue;
|
||||
|
||||
chunkSize = _fileStream->readUint32LE();
|
||||
chunkSize -= 4; // subtract the first 4 bytes (chunk size)
|
||||
|
||||
if (_header.audioInfo[i].compression == kCompressionNone) {
|
||||
dataSizeUnpacked = chunkSize;
|
||||
} else {
|
||||
dataSizeUnpacked = _fileStream->readUint32LE();
|
||||
chunkSize -= 4; // subtract the next 4 bytes (unpacked data size)
|
||||
}
|
||||
|
||||
handleAudioTrack(i, chunkSize, dataSizeUnpacked);
|
||||
}
|
||||
|
||||
uint32 frameSize = _frameSizes[videoTrack->getCurFrame()] & ~3;
|
||||
// uint32 remainder = _frameSizes[videoTrack->getCurFrame()] & 3;
|
||||
|
||||
if (_fileStream->pos() - startPos > frameSize)
|
||||
error("Smacker actual frame size exceeds recorded frame size");
|
||||
|
||||
uint32 frameDataSize = frameSize - (_fileStream->pos() - startPos);
|
||||
|
||||
byte *frameData = (byte *)malloc(frameDataSize + 1);
|
||||
// Padding to keep the BigHuffmanTrees from reading past the data end
|
||||
frameData[frameDataSize] = 0x00;
|
||||
|
||||
_fileStream->read(frameData, frameDataSize);
|
||||
|
||||
Common::BitStream8LSB bs(new Common::MemoryReadStream(frameData, frameDataSize + 1, DisposeAfterUse::YES), true);
|
||||
videoTrack->decodeFrame(bs);
|
||||
|
||||
_fileStream->seek(startPos + frameSize);
|
||||
}
|
||||
|
||||
void SmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize) {
|
||||
if (_header.audioInfo[track].hasAudio && chunkSize > 0 && track == 0) {
|
||||
// Get the audio track, which start at offset 1 (first track is video)
|
||||
SmackerAudioTrack *audioTrack = (SmackerAudioTrack *)getTrack(track + 1);
|
||||
|
||||
// If it's track 0, play the audio data
|
||||
byte *soundBuffer = (byte *)malloc(chunkSize + 1);
|
||||
// Padding to keep the SmallHuffmanTrees from reading past the data end
|
||||
soundBuffer[chunkSize] = 0x00;
|
||||
|
||||
_fileStream->read(soundBuffer, chunkSize);
|
||||
|
||||
if (_header.audioInfo[track].compression == kCompressionRDFT || _header.audioInfo[track].compression == kCompressionDCT) {
|
||||
// TODO: Compressed audio (Bink RDFT/DCT encoded)
|
||||
free(soundBuffer);
|
||||
return;
|
||||
} else if (_header.audioInfo[track].compression == kCompressionDPCM) {
|
||||
// Compressed audio (Huffman DPCM encoded)
|
||||
audioTrack->queueCompressedBuffer(soundBuffer, chunkSize + 1, unpackedSize);
|
||||
free(soundBuffer);
|
||||
} else {
|
||||
// Uncompressed audio (PCM)
|
||||
audioTrack->queuePCM(soundBuffer, chunkSize);
|
||||
}
|
||||
} else {
|
||||
// Ignore the rest of the audio tracks, if they exist
|
||||
// TODO: Are there any Smacker videos with more than one audio stream?
|
||||
// If yes, we should play the rest of the audio streams as well
|
||||
if (chunkSize > 0)
|
||||
_fileStream->skip(chunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
SmackerDecoder::SmackerVideoTrack::SmackerVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) {
|
||||
_surface = new Graphics::Surface();
|
||||
_surface->create(width, height * (flags ? 2 : 1), Graphics::PixelFormat::createFormatCLUT8());
|
||||
_frameCount = frameCount;
|
||||
_frameRate = frameRate;
|
||||
_flags = flags;
|
||||
_signature = signature;
|
||||
_curFrame = -1;
|
||||
_dirtyPalette = false;
|
||||
_MMapTree = _MClrTree = _FullTree = _TypeTree = 0;
|
||||
memset(_palette, 0, 3 * 256);
|
||||
}
|
||||
|
||||
SmackerDecoder::SmackerVideoTrack::~SmackerVideoTrack() {
|
||||
_surface->free();
|
||||
delete _surface;
|
||||
|
||||
delete _MMapTree;
|
||||
delete _MClrTree;
|
||||
delete _FullTree;
|
||||
delete _TypeTree;
|
||||
}
|
||||
|
||||
uint16 SmackerDecoder::SmackerVideoTrack::getWidth() const {
|
||||
return _surface->w;
|
||||
}
|
||||
|
||||
uint16 SmackerDecoder::SmackerVideoTrack::getHeight() const {
|
||||
return _surface->h;
|
||||
}
|
||||
|
||||
Graphics::PixelFormat SmackerDecoder::SmackerVideoTrack::getPixelFormat() const {
|
||||
return _surface->format;
|
||||
}
|
||||
|
||||
void SmackerDecoder::SmackerVideoTrack::readTrees(Common::BitStream &bs, uint32 mMapSize, uint32 mClrSize, uint32 fullSize, uint32 typeSize) {
|
||||
_MMapTree = new BigHuffmanTree(bs, mMapSize);
|
||||
_MClrTree = new BigHuffmanTree(bs, mClrSize);
|
||||
_FullTree = new BigHuffmanTree(bs, fullSize);
|
||||
_TypeTree = new BigHuffmanTree(bs, typeSize);
|
||||
}
|
||||
|
||||
void SmackerDecoder::SmackerVideoTrack::decodeFrame(Common::BitStream &bs) {
|
||||
_MMapTree->reset();
|
||||
_MClrTree->reset();
|
||||
_FullTree->reset();
|
||||
_TypeTree->reset();
|
||||
|
||||
// Height needs to be doubled if we have flags (Y-interlaced or Y-doubled)
|
||||
uint doubleY = _flags ? 2 : 1;
|
||||
|
||||
uint bw = getWidth() / 4;
|
||||
uint bh = getHeight() / doubleY / 4;
|
||||
uint stride = getWidth();
|
||||
uint block = 0, blocks = bw*bh;
|
||||
|
||||
byte *out;
|
||||
uint type, run, j, mode;
|
||||
uint32 p1, p2, clr, map;
|
||||
byte hi, lo;
|
||||
uint i;
|
||||
|
||||
while (block < blocks) {
|
||||
type = _TypeTree->getCode(bs);
|
||||
run = getBlockRun((type >> 2) & 0x3f);
|
||||
|
||||
switch (type & 3) {
|
||||
case SMK_BLOCK_MONO:
|
||||
while (run-- && block < blocks) {
|
||||
clr = _MClrTree->getCode(bs);
|
||||
map = _MMapTree->getCode(bs);
|
||||
out = (byte *)_surface->pixels + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4;
|
||||
hi = clr >> 8;
|
||||
lo = clr & 0xff;
|
||||
for (i = 0; i < 4; i++) {
|
||||
for (j = 0; j < doubleY; j++) {
|
||||
out[0] = (map & 1) ? hi : lo;
|
||||
out[1] = (map & 2) ? hi : lo;
|
||||
out[2] = (map & 4) ? hi : lo;
|
||||
out[3] = (map & 8) ? hi : lo;
|
||||
out += stride;
|
||||
}
|
||||
map >>= 4;
|
||||
}
|
||||
++block;
|
||||
}
|
||||
break;
|
||||
case SMK_BLOCK_FULL:
|
||||
// Smacker v2 has one mode, Smacker v4 has three
|
||||
if (_signature == MKTAG('S','M','K','2')) {
|
||||
mode = 0;
|
||||
} else {
|
||||
// 00 - mode 0
|
||||
// 10 - mode 1
|
||||
// 01 - mode 2
|
||||
mode = 0;
|
||||
if (bs.getBit()) {
|
||||
mode = 1;
|
||||
} else if (bs.getBit()) {
|
||||
mode = 2;
|
||||
}
|
||||
}
|
||||
|
||||
while (run-- && block < blocks) {
|
||||
out = (byte *)_surface->pixels + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4;
|
||||
switch (mode) {
|
||||
case 0:
|
||||
for (i = 0; i < 4; ++i) {
|
||||
p1 = _FullTree->getCode(bs);
|
||||
p2 = _FullTree->getCode(bs);
|
||||
for (j = 0; j < doubleY; ++j) {
|
||||
out[2] = p1 & 0xff;
|
||||
out[3] = p1 >> 8;
|
||||
out[0] = p2 & 0xff;
|
||||
out[1] = p2 >> 8;
|
||||
out += stride;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
p1 = _FullTree->getCode(bs);
|
||||
out[0] = out[1] = p1 & 0xFF;
|
||||
out[2] = out[3] = p1 >> 8;
|
||||
out += stride;
|
||||
out[0] = out[1] = p1 & 0xFF;
|
||||
out[2] = out[3] = p1 >> 8;
|
||||
out += stride;
|
||||
p2 = _FullTree->getCode(bs);
|
||||
out[0] = out[1] = p2 & 0xFF;
|
||||
out[2] = out[3] = p2 >> 8;
|
||||
out += stride;
|
||||
out[0] = out[1] = p2 & 0xFF;
|
||||
out[2] = out[3] = p2 >> 8;
|
||||
out += stride;
|
||||
break;
|
||||
case 2:
|
||||
for (i = 0; i < 2; i++) {
|
||||
// We first get p2 and then p1
|
||||
// Check ffmpeg thread "[PATCH] Smacker video decoder bug fix"
|
||||
// http://article.gmane.org/gmane.comp.video.ffmpeg.devel/78768
|
||||
p2 = _FullTree->getCode(bs);
|
||||
p1 = _FullTree->getCode(bs);
|
||||
for (j = 0; j < doubleY; ++j) {
|
||||
out[0] = p1 & 0xff;
|
||||
out[1] = p1 >> 8;
|
||||
out[2] = p2 & 0xff;
|
||||
out[3] = p2 >> 8;
|
||||
out += stride;
|
||||
}
|
||||
for (j = 0; j < doubleY; ++j) {
|
||||
out[0] = p1 & 0xff;
|
||||
out[1] = p1 >> 8;
|
||||
out[2] = p2 & 0xff;
|
||||
out[3] = p2 >> 8;
|
||||
out += stride;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
++block;
|
||||
}
|
||||
break;
|
||||
case SMK_BLOCK_SKIP:
|
||||
while (run-- && block < blocks)
|
||||
block++;
|
||||
break;
|
||||
case SMK_BLOCK_FILL:
|
||||
uint32 col;
|
||||
mode = type >> 8;
|
||||
while (run-- && block < blocks) {
|
||||
out = (byte *)_surface->pixels + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4;
|
||||
col = mode * 0x01010101;
|
||||
for (i = 0; i < 4 * doubleY; ++i) {
|
||||
out[0] = out[1] = out[2] = out[3] = col;
|
||||
out += stride;
|
||||
}
|
||||
++block;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SmackerDecoder::SmackerVideoTrack::unpackPalette(Common::SeekableReadStream *stream) {
|
||||
uint startPos = stream->pos();
|
||||
uint32 len = 4 * stream->readByte();
|
||||
|
||||
byte *chunk = (byte *)malloc(len);
|
||||
stream->read(chunk, len);
|
||||
byte *p = chunk;
|
||||
|
||||
byte oldPalette[3 * 256];
|
||||
memcpy(oldPalette, _palette, 3 * 256);
|
||||
|
||||
byte *pal = _palette;
|
||||
|
||||
int sz = 0;
|
||||
byte b0;
|
||||
while (sz < 256) {
|
||||
b0 = *p++;
|
||||
if (b0 & 0x80) { // if top bit is 1 (0x80 = 10000000)
|
||||
sz += (b0 & 0x7f) + 1; // get lower 7 bits + 1 (0x7f = 01111111)
|
||||
pal += 3 * ((b0 & 0x7f) + 1);
|
||||
} else if (b0 & 0x40) { // if top 2 bits are 01 (0x40 = 01000000)
|
||||
byte c = (b0 & 0x3f) + 1; // get lower 6 bits + 1 (0x3f = 00111111)
|
||||
uint s = 3 * *p++;
|
||||
sz += c;
|
||||
|
||||
while (c--) {
|
||||
*pal++ = oldPalette[s + 0];
|
||||
*pal++ = oldPalette[s + 1];
|
||||
*pal++ = oldPalette[s + 2];
|
||||
s += 3;
|
||||
}
|
||||
} else { // top 2 bits are 00
|
||||
sz++;
|
||||
// get the lower 6 bits for each component (0x3f = 00111111)
|
||||
byte b = b0 & 0x3f;
|
||||
byte g = (*p++) & 0x3f;
|
||||
byte r = (*p++) & 0x3f;
|
||||
|
||||
assert(g < 0xc0 && b < 0xc0);
|
||||
|
||||
// upscale to full 8-bit color values by multiplying by 4
|
||||
*pal++ = b * 4;
|
||||
*pal++ = g * 4;
|
||||
*pal++ = r * 4;
|
||||
}
|
||||
}
|
||||
|
||||
stream->seek(startPos + len);
|
||||
free(chunk);
|
||||
|
||||
_dirtyPalette = true;
|
||||
}
|
||||
|
||||
SmackerDecoder::SmackerAudioTrack::SmackerAudioTrack(const AudioInfo &audioInfo, Audio::Mixer::SoundType soundType) :
|
||||
_audioInfo(audioInfo), _soundType(soundType) {
|
||||
_audioStream = Audio::makeQueuingAudioStream(_audioInfo.sampleRate, _audioInfo.isStereo);
|
||||
}
|
||||
|
||||
SmackerDecoder::SmackerAudioTrack::~SmackerAudioTrack() {
|
||||
delete _audioStream;
|
||||
}
|
||||
|
||||
bool SmackerDecoder::SmackerAudioTrack::rewind() {
|
||||
delete _audioStream;
|
||||
_audioStream = Audio::makeQueuingAudioStream(_audioInfo.sampleRate, _audioInfo.isStereo);
|
||||
return true;
|
||||
}
|
||||
|
||||
Audio::AudioStream *SmackerDecoder::SmackerAudioTrack::getAudioStream() const {
|
||||
return _audioStream;
|
||||
}
|
||||
|
||||
void SmackerDecoder::SmackerAudioTrack::queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize) {
|
||||
Common::BitStream8LSB audioBS(new Common::MemoryReadStream(buffer, bufferSize), true);
|
||||
bool dataPresent = audioBS.getBit();
|
||||
|
||||
if (!dataPresent)
|
||||
return;
|
||||
|
||||
bool isStereo = audioBS.getBit();
|
||||
assert(isStereo == _audioInfo.isStereo);
|
||||
bool is16Bits = audioBS.getBit();
|
||||
assert(is16Bits == _audioInfo.is16Bits);
|
||||
|
||||
int numBytes = 1 * (isStereo ? 2 : 1) * (is16Bits ? 2 : 1);
|
||||
|
||||
byte *unpackedBuffer = (byte *)malloc(unpackedSize);
|
||||
byte *curPointer = unpackedBuffer;
|
||||
uint32 curPos = 0;
|
||||
|
||||
SmallHuffmanTree *audioTrees[4];
|
||||
for (int k = 0; k < numBytes; k++)
|
||||
audioTrees[k] = new SmallHuffmanTree(audioBS);
|
||||
|
||||
// Base values, stored as big endian
|
||||
|
||||
int32 bases[2];
|
||||
|
||||
if (isStereo) {
|
||||
if (is16Bits) {
|
||||
bases[1] = SWAP_BYTES_16(audioBS.getBits(16));
|
||||
} else {
|
||||
bases[1] = audioBS.getBits(8);
|
||||
}
|
||||
}
|
||||
|
||||
if (is16Bits) {
|
||||
bases[0] = SWAP_BYTES_16(audioBS.getBits(16));
|
||||
} else {
|
||||
bases[0] = audioBS.getBits(8);
|
||||
}
|
||||
|
||||
// The bases are the first samples, too
|
||||
for (int i = 0; i < (isStereo ? 2 : 1); i++, curPointer += (is16Bits ? 2 : 1), curPos += (is16Bits ? 2 : 1)) {
|
||||
if (is16Bits)
|
||||
WRITE_BE_UINT16(curPointer, bases[i]);
|
||||
else
|
||||
*curPointer = (bases[i] & 0xFF) ^ 0x80;
|
||||
}
|
||||
|
||||
// Next follow the deltas, which are added to the corresponding base values and
|
||||
// are stored as little endian
|
||||
// We store the unpacked bytes in big endian format
|
||||
|
||||
while (curPos < unpackedSize) {
|
||||
// If the sample is stereo, the data is stored for the left and right channel, respectively
|
||||
// (the exact opposite to the base values)
|
||||
if (!is16Bits) {
|
||||
for (int k = 0; k < (isStereo ? 2 : 1); k++) {
|
||||
int8 delta = (int8) ((int16) audioTrees[k]->getCode(audioBS));
|
||||
bases[k] = (bases[k] + delta) & 0xFF;
|
||||
*curPointer++ = bases[k] ^ 0x80;
|
||||
curPos++;
|
||||
}
|
||||
} else {
|
||||
for (int k = 0; k < (isStereo ? 2 : 1); k++) {
|
||||
byte lo = audioTrees[k * 2]->getCode(audioBS);
|
||||
byte hi = audioTrees[k * 2 + 1]->getCode(audioBS);
|
||||
bases[k] += (int16) (lo | (hi << 8));
|
||||
|
||||
WRITE_BE_UINT16(curPointer, bases[k]);
|
||||
curPointer += 2;
|
||||
curPos += 2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (int k = 0; k < numBytes; k++)
|
||||
delete audioTrees[k];
|
||||
|
||||
queuePCM(unpackedBuffer, unpackedSize);
|
||||
}
|
||||
|
||||
void SmackerDecoder::SmackerAudioTrack::queuePCM(byte *buffer, uint32 bufferSize) {
|
||||
byte flags = 0;
|
||||
if (_audioInfo.is16Bits)
|
||||
flags |= Audio::FLAG_16BITS;
|
||||
if (_audioInfo.isStereo)
|
||||
flags |= Audio::FLAG_STEREO;
|
||||
|
||||
_audioStream->queueBuffer(buffer, bufferSize, DisposeAfterUse::YES, flags);
|
||||
}
|
||||
|
||||
SmackerDecoder::SmackerVideoTrack *SmackerDecoder::createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const {
|
||||
return new SmackerVideoTrack(width, height, frameCount, frameRate, flags, signature);
|
||||
}
|
||||
|
||||
} // End of namespace Video
|
193
video/smk_decoder.h
Normal file
193
video/smk_decoder.h
Normal file
@ -0,0 +1,193 @@
|
||||
/* 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 2
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef VIDEO_SMK_PLAYER_H
|
||||
#define VIDEO_SMK_PLAYER_H
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "graphics/pixelformat.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "video/video_decoder.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
namespace Audio {
|
||||
class QueuingAudioStream;
|
||||
}
|
||||
|
||||
namespace Common {
|
||||
class BitStream;
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Video {
|
||||
|
||||
class BigHuffmanTree;
|
||||
|
||||
/**
|
||||
* Decoder for Smacker v2/v4 videos.
|
||||
*
|
||||
* Based on http://wiki.multimedia.cx/index.php?title=Smacker
|
||||
* and the FFmpeg Smacker decoder (libavcodec/smacker.c), revision 16143
|
||||
* http://svn.ffmpeg.org/ffmpeg/trunk/libavcodec/smacker.c?revision=16143&view=markup
|
||||
*
|
||||
* Video decoder used in engines:
|
||||
* - agos
|
||||
* - saga
|
||||
* - scumm (he)
|
||||
* - sword1
|
||||
* - sword2
|
||||
* - toon
|
||||
*/
|
||||
class SmackerDecoder : public VideoDecoder {
|
||||
public:
|
||||
SmackerDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType);
|
||||
virtual ~SmackerDecoder();
|
||||
|
||||
virtual bool loadStream(Common::SeekableReadStream *stream);
|
||||
void close();
|
||||
|
||||
bool rewind();
|
||||
|
||||
protected:
|
||||
void readNextPacket();
|
||||
|
||||
virtual void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize);
|
||||
|
||||
class SmackerVideoTrack : public FixedRateVideoTrack {
|
||||
public:
|
||||
SmackerVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature);
|
||||
~SmackerVideoTrack();
|
||||
|
||||
bool isRewindable() const { return true; }
|
||||
bool rewind() { _curFrame = -1; return true; }
|
||||
|
||||
uint16 getWidth() const;
|
||||
uint16 getHeight() const;
|
||||
Graphics::PixelFormat getPixelFormat() const;
|
||||
int getCurFrame() const { return _curFrame; }
|
||||
int getFrameCount() const { return _frameCount; }
|
||||
const Graphics::Surface *decodeNextFrame() { return _surface; }
|
||||
const byte *getPalette() const { _dirtyPalette = false; return _palette; }
|
||||
bool hasDirtyPalette() const { return _dirtyPalette; }
|
||||
|
||||
void readTrees(Common::BitStream &bs, uint32 mMapSize, uint32 mClrSize, uint32 fullSize, uint32 typeSize);
|
||||
void increaseCurFrame() { _curFrame++; }
|
||||
void decodeFrame(Common::BitStream &bs);
|
||||
void unpackPalette(Common::SeekableReadStream *stream);
|
||||
|
||||
protected:
|
||||
Common::Rational getFrameRate() const { return _frameRate; }
|
||||
|
||||
Graphics::Surface *_surface;
|
||||
|
||||
private:
|
||||
Common::Rational _frameRate;
|
||||
uint32 _flags, _signature;
|
||||
|
||||
byte _palette[3 * 256];
|
||||
mutable bool _dirtyPalette;
|
||||
|
||||
int _curFrame;
|
||||
uint32 _frameCount;
|
||||
|
||||
BigHuffmanTree *_MMapTree;
|
||||
BigHuffmanTree *_MClrTree;
|
||||
BigHuffmanTree *_FullTree;
|
||||
BigHuffmanTree *_TypeTree;
|
||||
|
||||
// Possible runs of blocks
|
||||
static uint getBlockRun(int index) { return (index <= 58) ? index + 1 : 128 << (index - 59); }
|
||||
};
|
||||
|
||||
virtual SmackerVideoTrack *createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const;
|
||||
|
||||
Common::SeekableReadStream *_fileStream;
|
||||
|
||||
enum AudioCompression {
|
||||
kCompressionNone,
|
||||
kCompressionDPCM,
|
||||
kCompressionRDFT,
|
||||
kCompressionDCT
|
||||
};
|
||||
|
||||
struct AudioInfo {
|
||||
AudioCompression compression;
|
||||
bool hasAudio;
|
||||
bool is16Bits;
|
||||
bool isStereo;
|
||||
uint32 sampleRate;
|
||||
};
|
||||
|
||||
struct {
|
||||
uint32 signature;
|
||||
uint32 flags;
|
||||
uint32 audioSize[7];
|
||||
uint32 treesSize;
|
||||
uint32 mMapSize;
|
||||
uint32 mClrSize;
|
||||
uint32 fullSize;
|
||||
uint32 typeSize;
|
||||
AudioInfo audioInfo[7];
|
||||
uint32 dummy;
|
||||
} _header;
|
||||
|
||||
uint32 *_frameSizes;
|
||||
|
||||
private:
|
||||
|
||||
class SmackerAudioTrack : public AudioTrack {
|
||||
public:
|
||||
SmackerAudioTrack(const AudioInfo &audioInfo, Audio::Mixer::SoundType soundType);
|
||||
~SmackerAudioTrack();
|
||||
|
||||
bool isRewindable() const { return true; }
|
||||
bool rewind();
|
||||
|
||||
Audio::Mixer::SoundType getSoundType() const { return _soundType; }
|
||||
|
||||
void queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize);
|
||||
void queuePCM(byte *buffer, uint32 bufferSize);
|
||||
|
||||
protected:
|
||||
Audio::AudioStream *getAudioStream() const;
|
||||
|
||||
private:
|
||||
Audio::Mixer::SoundType _soundType;
|
||||
Audio::QueuingAudioStream *_audioStream;
|
||||
AudioInfo _audioInfo;
|
||||
};
|
||||
|
||||
// The FrameTypes section of a Smacker file contains an array of bytes, where
|
||||
// the 8 bits of each byte describe the contents of the corresponding frame.
|
||||
// The highest 7 bits correspond to audio frames (bit 7 is track 6, bit 6 track 5
|
||||
// and so on), so there can be up to 7 different audio tracks. When the lowest bit
|
||||
// (bit 0) is set, it denotes a frame that contains a palette record
|
||||
byte *_frameTypes;
|
||||
|
||||
uint32 _firstFrameStart;
|
||||
|
||||
Audio::Mixer::SoundType _soundType;
|
||||
};
|
||||
|
||||
} // End of namespace Video
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user