257 lines
5.9 KiB
C++
257 lines
5.9 KiB
C++
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include "DefaultHardware.h"
|
|
#include "Display.h"
|
|
#include "DrawContext.h"
|
|
#include "TimeSource.h"
|
|
#include "Timer.h"
|
|
#include "app/TomGameApp.h"
|
|
#include "recognition/KeywordRecognizer.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "recognition/PythonKeywordRecognizer.h"
|
|
#else
|
|
#include "recognition/TinyKwsRecognizer.h"
|
|
#endif
|
|
|
|
#ifdef USE_FRAMEBUFFER
|
|
#include "FBDisplay.h"
|
|
#else
|
|
#include "SDLDisplay.h"
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
const int32_t ScreenWidth = 1024;
|
|
const int32_t ScreenHeight = 600;
|
|
|
|
struct ProgramOptions
|
|
{
|
|
uint32_t target_fps;
|
|
std::string kws_model_path;
|
|
float kws_threshold;
|
|
bool show_help;
|
|
|
|
ProgramOptions()
|
|
: target_fps(Core::Timer::DefaultFps),
|
|
kws_model_path("src/Apps/Game/model/mlcommons-tiny-kws/models/kws_ref_model_float32.tflite"),
|
|
kws_threshold(0.75f),
|
|
show_help(false)
|
|
{
|
|
}
|
|
};
|
|
|
|
static Platform::IDisplay* CreateDisplay()
|
|
{
|
|
#ifdef USE_FRAMEBUFFER
|
|
return new Platform::FBDisplay();
|
|
#else
|
|
return new Platform::SDLDisplay();
|
|
#endif
|
|
}
|
|
|
|
static void PrintUsage(const char* program_name)
|
|
{
|
|
std::cout
|
|
<< "Usage: " << program_name << " [--fps 30|45|60] [--kws-model path] [--kws-threshold 0.75]\n"
|
|
<< " " << program_name << " [--fps=30|45|60] [--kws-model=path] [--kws-threshold=0.75]\n";
|
|
}
|
|
|
|
static ProgramOptions ParseProgramOptions(int argc, char* argv[])
|
|
{
|
|
ProgramOptions options;
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
const char* arg = argv[i];
|
|
const char* fps_value = nullptr;
|
|
const char* kws_model_value = nullptr;
|
|
const char* kws_threshold_value = nullptr;
|
|
|
|
if (std::strcmp(arg, "--help") == 0 || std::strcmp(arg, "-h") == 0)
|
|
{
|
|
options.show_help = true;
|
|
return options;
|
|
}
|
|
else if (std::strcmp(arg, "--fps") == 0)
|
|
{
|
|
if (i + 1 < argc)
|
|
{
|
|
fps_value = argv[++i];
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "Missing value for --fps, falling back to 30. Supported: 30, 45, 60.\n";
|
|
}
|
|
}
|
|
else if (std::strncmp(arg, "--fps=", 6) == 0)
|
|
{
|
|
fps_value = arg + 6;
|
|
}
|
|
else if (std::strcmp(arg, "--kws-model") == 0)
|
|
{
|
|
if (i + 1 < argc)
|
|
{
|
|
kws_model_value = argv[++i];
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "Missing value for --kws-model, using default model path.\n";
|
|
}
|
|
}
|
|
else if (std::strncmp(arg, "--kws-model=", 12) == 0)
|
|
{
|
|
kws_model_value = arg + 12;
|
|
}
|
|
else if (std::strcmp(arg, "--kws-threshold") == 0)
|
|
{
|
|
if (i + 1 < argc)
|
|
{
|
|
kws_threshold_value = argv[++i];
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "Missing value for --kws-threshold, using default 0.75.\n";
|
|
}
|
|
}
|
|
else if (std::strncmp(arg, "--kws-threshold=", 16) == 0)
|
|
{
|
|
kws_threshold_value = arg + 16;
|
|
}
|
|
|
|
if (fps_value != nullptr)
|
|
{
|
|
const uint32_t parsed_fps = static_cast<uint32_t>(std::strtoul(fps_value, nullptr, 10));
|
|
if (Core::Timer::is_supported_fps(parsed_fps))
|
|
{
|
|
options.target_fps = parsed_fps;
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "Unsupported FPS '" << fps_value << "', falling back to 30. Supported: 30, 45, 60.\n";
|
|
options.target_fps = Core::Timer::DefaultFps;
|
|
}
|
|
}
|
|
|
|
if (kws_model_value != nullptr && kws_model_value[0] != '\0')
|
|
{
|
|
options.kws_model_path = kws_model_value;
|
|
}
|
|
|
|
if (kws_threshold_value != nullptr)
|
|
{
|
|
char* threshold_end = nullptr;
|
|
const float parsed_threshold = std::strtof(kws_threshold_value, &threshold_end);
|
|
if (threshold_end != kws_threshold_value && *threshold_end == '\0' && parsed_threshold >= 0.0f)
|
|
{
|
|
options.kws_threshold = parsed_threshold;
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "Unsupported KWS threshold '" << kws_threshold_value << "', using default 0.75.\n";
|
|
options.kws_threshold = 0.75f;
|
|
}
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
static void SleepRemainingFrameTime(const Core::Timer& timer, const Platform::ITimeSource& time_source)
|
|
{
|
|
const uint32_t sleep_ms = timer.remaining_frame_ms(time_source.get_time_ms());
|
|
if (sleep_ms > 0u)
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
const ProgramOptions options = ParseProgramOptions(argc, argv);
|
|
if (options.show_help)
|
|
{
|
|
PrintUsage(argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
Platform::IDisplay* display = CreateDisplay();
|
|
if (!display->init(ScreenWidth, ScreenHeight))
|
|
{
|
|
delete display;
|
|
return -1;
|
|
}
|
|
|
|
Platform::DefaultAudioInput audioInput;
|
|
Platform::DefaultAudioOutput audioOutput;
|
|
Platform::DefaultButtonInput buttonInput;
|
|
Platform::DefaultPointerInput pointerInput;
|
|
|
|
#ifdef _WIN32
|
|
Game::PythonKeywordRecognizer keywordRecognizer(
|
|
"python",
|
|
"src/Apps/Game/tools/kws_python_test.py",
|
|
options.kws_model_path,
|
|
options.kws_threshold);
|
|
#else
|
|
Game::TinyKwsRecognizer keywordRecognizer(options.kws_model_path);
|
|
keywordRecognizer.set_confidence_threshold(options.kws_threshold);
|
|
#endif
|
|
|
|
if (!buttonInput.init())
|
|
{
|
|
std::cerr << "[WARN] Button input init failed; physical key trigger is disabled." << std::endl;
|
|
}
|
|
if (!pointerInput.init("", ScreenWidth, ScreenHeight))
|
|
{
|
|
std::cerr << "[WARN] Pointer input init failed; on-screen button trigger is disabled." << std::endl;
|
|
}
|
|
|
|
Core::DrawContext ctx(ScreenWidth, ScreenHeight);
|
|
Core::Timer timer(options.target_fps);
|
|
Platform::SteadyTimeSource time_source;
|
|
Game::TomGameApp app(
|
|
ScreenWidth,
|
|
ScreenHeight,
|
|
&audioInput,
|
|
&audioOutput,
|
|
&buttonInput,
|
|
&pointerInput,
|
|
&keywordRecognizer);
|
|
|
|
std::cout << "[INFO] Tom game started. Target FPS: " << timer.target_fps() << std::endl;
|
|
|
|
bool is_running = true;
|
|
while (is_running)
|
|
{
|
|
timer.begin_frame(time_source.get_time_ms());
|
|
|
|
bool should_quit = false;
|
|
display->poll_events(should_quit);
|
|
if (should_quit)
|
|
{
|
|
is_running = false;
|
|
}
|
|
|
|
app.update(timer.fixed_delta_ms());
|
|
app.draw(ctx);
|
|
ctx.present(display);
|
|
SleepRemainingFrameTime(timer, time_source);
|
|
}
|
|
|
|
pointerInput.shutdown();
|
|
buttonInput.shutdown();
|
|
audioInput.shutdown();
|
|
audioOutput.shutdown();
|
|
display->shutdown();
|
|
delete display;
|
|
return 0;
|
|
}
|