IMX6U-Game/src/Apps/Game/Main.cpp

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;
}