diff --git a/docs/APP_AND_CORE_ARCHITECTURE.md b/docs/APP_AND_CORE_ARCHITECTURE.md index 017a615..140d5ef 100644 --- a/docs/APP_AND_CORE_ARCHITECTURE.md +++ b/docs/APP_AND_CORE_ARCHITECTURE.md @@ -11,7 +11,7 @@ src/ Core/ # FrameBuffer、DepthBuffer、Renderer、Timer Draw2D/ # Core::DrawContext Math/ # Vector、Matrix、MathUtil - Platform/ # IDisplay、SDLDisplay、FBDisplay、ITimeSource + Platform/ # 显示、时间、音频、按键等平台接口和后端 Rasterizer/ # 线段和三角形光栅化 RenderData/ # Color、Image、Triangle、Tilemap 等数据结构 Scene/ # Camera、Transform、Mesh、Model @@ -58,6 +58,7 @@ Core 只提供底层能力: - 提供基础数学、颜色、图片、三角形、tilemap 等数据结构。 - 封装 SDL2 / framebuffer 显示提交。 - 提供独立时间源 `Platform::ITimeSource`。 +- 提供音频输入、音频输出和按键输入的抽象接口。 - 使用离线转换后的运行时资源,不在热路径解码 PNG/TTF。 Core 不做: @@ -112,6 +113,8 @@ ctx.present(display); ## 显示后端 +平台层采用“抽象接口 + 多套后端实现”的模式。 + 显示层通过 `Platform::IDisplay` 抽象: ```text @@ -120,6 +123,33 @@ Platform::IDisplay FBDisplay # Linux /dev/fb0 后端 ``` +音频输入通过 `Platform::IAudioInput` 抽象: + +```text +Platform::IAudioInput + SdlAudioInput # PC / SDL2 麦克风后端 + AlsaAudioInput # Linux ALSA 录音后端 +``` + +音频输出通过 `Platform::IAudioOutput` 抽象: + +```text +Platform::IAudioOutput + SdlAudioOutput # PC / SDL2 扬声器后端 + AlsaAudioOutput # Linux ALSA 播放后端 +``` + +按键输入通过 `Platform::IButtonInput` 抽象: + +```text +Platform::IButtonInput + SdlKeyboardButtonInput # PC / SDL2 键盘后端,默认空格键 + EvdevButtonInput # Linux evdev 按键后端 +``` + +游戏代码只能依赖这些 `I*` 接口。ALSA、evdev、SDL2、`/dev/fb0` 等平台细节只能出现在 `src/Core/Platform` 或明确的平台适配代码中。 +如果只需要当前构建平台的默认后端,可以使用 `Platform::DefaultAudioInput`、`Platform::DefaultAudioOutput` 和 `Platform::DefaultButtonInput`。 + CMake 通过 `USE_FRAMEBUFFER` 选择实现: ```cmake diff --git a/src/Apps/Game/README.md b/src/Apps/Game/README.md deleted file mode 100644 index 700a094..0000000 --- a/src/Apps/Game/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Tom Game - -`src/Apps/Game` 是 Tom 游戏应用目录。仓库根目录下的 `src/Core` 继续作为渲染、数学、光栅化和平台显示层;Game 目录只放游戏入口、游戏逻辑、素材和素材转换工具。 - -## 当前入口 - -主构建目标 `IMX6U-Game` 现在从下面的文件启动: - -```text -src/Apps/Game/Main.cpp -``` - -运行后默认启动 Tom,不再需要 `--tom` 参数。可选参数只保留帧率: - -```bash -IMX6U-Game.exe --fps 30 -IMX6U-Game.exe --fps=60 -``` - -## 素材目录 - -```text -src/Apps/Game/assets/raw/ PNG 源素材 -src/Apps/Game/assets/sprites/ 转换后的 .sprite 运行时素材 -src/Apps/Game/tools/ 素材转换等开发工具 -src/Apps/Game/src/ 旧版 TomGameApp、音频、动画等游戏模块 -src/Apps/Game/tests/ 手工测试或后续测试代码 -``` - -板端运行时不直接解码 PNG。开发时把 PNG 放在 `src/Apps/Game/assets/raw/`,再转换为 `src/Apps/Game/assets/sprites/*.sprite`。 - -## 素材转换 - -`.sprite` 是项目自定义的 RGBA8888 二进制格式: - -```text -offset size content -0 4 magic: "SPRT" -4 4 version: 1, little-endian uint32 -8 4 width, little-endian uint32 -12 4 height, little-endian uint32 -16 4 format: 1 means RGBA8888 -20 * width * height pixels, little-endian uint32, 0xRRGGBBAA -``` - -常用命令: - -```bash -cmake --build build-win --config Release --target SpriteAssetTool -cmake --build build-win --config Release --target ConvertTomSprites -``` - -单张转换示例: - -```bash -./build-win/Release/SpriteAssetTool.exe src/Apps/Game/assets/raw/ui-record.png src/Apps/Game/assets/sprites/ui-record.sprite -./build-win/Release/SpriteAssetTool.exe src/Apps/Game/assets/raw/Tom-stand.png src/Apps/Game/assets/sprites/Tom-stand.sprite --fit 560 360 -``` - -## 运行 - -Windows PC: - -```bash -./build-win/Release/IMX6U-Game.exe -``` - -Linux framebuffer / IMX6U: - -```bash -./IMX6U-Game --fps 30 -``` - -部署到板端时,把可执行文件和 `src/Apps/Game/assets/sprites/` 一起拷贝,并保持相对路径;也可以把 `.sprite` 放到 `/usr/local/share/imx6u-game/sprites/`。 diff --git a/src/Apps/Game/src/app/TomGameApp.cpp b/src/Apps/Game/src/app/TomGameApp.cpp index d24f7f7..114482b 100644 --- a/src/Apps/Game/src/app/TomGameApp.cpp +++ b/src/Apps/Game/src/app/TomGameApp.cpp @@ -1,8 +1,8 @@ #include "TomGameApp.h" #include "../audio/VoiceEffect.h" -#include "../hardware/AudioInput.h" -#include "../hardware/AudioOutput.h" -#include "../hardware/ButtonInput.h" +#include "AudioInput.h" +#include "AudioOutput.h" +#include "ButtonInput.h" #include "FrameBuffer.h" #include "SpriteRasterizer.h" #include "Image.h" @@ -12,9 +12,9 @@ namespace Game TomGameApp::TomGameApp( Core::FrameBuffer* frameBuffer, Rasterizer::SpriteRasterizer* spriteRasterizer, - AudioInput* audioInput, - AudioOutput* audioOutput, - ButtonInput* buttonInput) : + Platform::IAudioInput* audioInput, + Platform::IAudioOutput* audioOutput, + Platform::IButtonInput* buttonInput) : frameBuffer(frameBuffer), spriteRasterizer(spriteRasterizer), audioInput(audioInput), diff --git a/src/Apps/Game/src/app/TomGameApp.h b/src/Apps/Game/src/app/TomGameApp.h index f68e8d4..3afbbb6 100644 --- a/src/Apps/Game/src/app/TomGameApp.h +++ b/src/Apps/Game/src/app/TomGameApp.h @@ -21,12 +21,15 @@ namespace RenderData class Image; } +namespace Platform +{ + class IAudioInput; + class IAudioOutput; + class IButtonInput; +} + namespace Game { - class AudioInput; - class AudioOutput; - class ButtonInput; - enum class TomGameState { Idle, @@ -63,9 +66,9 @@ namespace Game private: Core::FrameBuffer* frameBuffer; Rasterizer::SpriteRasterizer* spriteRasterizer; - AudioInput* audioInput; - AudioOutput* audioOutput; - ButtonInput* buttonInput; + Platform::IAudioInput* audioInput; + Platform::IAudioOutput* audioOutput; + Platform::IButtonInput* buttonInput; TomGameAssets assets; TomGameState state; @@ -99,9 +102,9 @@ namespace Game TomGameApp( Core::FrameBuffer* frameBuffer, Rasterizer::SpriteRasterizer* spriteRasterizer, - AudioInput* audioInput, - AudioOutput* audioOutput, - ButtonInput* buttonInput = nullptr); + Platform::IAudioInput* audioInput, + Platform::IAudioOutput* audioOutput, + Platform::IButtonInput* buttonInput = nullptr); void set_assets(const TomGameAssets& assets); void configure_audio(uint32_t sampleRate = 16000, uint32_t channels = 1); diff --git a/src/Apps/Game/src/audio/VoicePlayer.cpp b/src/Apps/Game/src/audio/VoicePlayer.cpp index 226e02b..ffa2045 100644 --- a/src/Apps/Game/src/audio/VoicePlayer.cpp +++ b/src/Apps/Game/src/audio/VoicePlayer.cpp @@ -1,5 +1,4 @@ #include "VoicePlayer.h" -#include "../hardware/AudioOutput.h" #include namespace Game @@ -59,7 +58,7 @@ namespace Game finished = false; } - int VoicePlayer::update(AudioOutput& audioOutput, int maxSamplesPerUpdate) + int VoicePlayer::update(Platform::IAudioOutput& audioOutput, int maxSamplesPerUpdate) { if (!playing || samples.empty()) { diff --git a/src/Apps/Game/src/audio/VoicePlayer.h b/src/Apps/Game/src/audio/VoicePlayer.h index 2bf7563..2d097f9 100644 --- a/src/Apps/Game/src/audio/VoicePlayer.h +++ b/src/Apps/Game/src/audio/VoicePlayer.h @@ -2,11 +2,10 @@ #include #include #include +#include "AudioOutput.h" namespace Game { - class AudioOutput; - class VoicePlayer { private: @@ -27,7 +26,7 @@ namespace Game void stop(); void reset(); void clear(); - int update(AudioOutput& audioOutput, int maxSamplesPerUpdate = 1024); + int update(Platform::IAudioOutput& audioOutput, int maxSamplesPerUpdate = 1024); const std::vector& get_samples() const { return samples; } size_t get_sample_count() const { return samples.size(); } diff --git a/src/Apps/Game/src/audio/VoiceRecorder.cpp b/src/Apps/Game/src/audio/VoiceRecorder.cpp index 2a894d3..d78c55f 100644 --- a/src/Apps/Game/src/audio/VoiceRecorder.cpp +++ b/src/Apps/Game/src/audio/VoiceRecorder.cpp @@ -1,5 +1,4 @@ #include "VoiceRecorder.h" -#include "../hardware/AudioInput.h" #include #include #include @@ -78,7 +77,7 @@ namespace Game finishedBySilence = false; } - int VoiceRecorder::update(AudioInput& audioInput, int sampleCount) + int VoiceRecorder::update(Platform::IAudioInput& audioInput, int sampleCount) { if (!recording || sampleCount <= 0) { diff --git a/src/Apps/Game/src/audio/VoiceRecorder.h b/src/Apps/Game/src/audio/VoiceRecorder.h index 5236afc..0c656a6 100644 --- a/src/Apps/Game/src/audio/VoiceRecorder.h +++ b/src/Apps/Game/src/audio/VoiceRecorder.h @@ -2,11 +2,10 @@ #include #include #include +#include "AudioInput.h" namespace Game { - class AudioInput; - class VoiceRecorder { private: @@ -42,7 +41,7 @@ namespace Game void start(); void stop(); void clear(); - int update(AudioInput& audioInput, int sampleCount = 512); + int update(Platform::IAudioInput& audioInput, int sampleCount = 512); const std::vector& get_samples() const { return samples; } size_t get_sample_count() const { return samples.size(); } diff --git a/src/Apps/Game/src/hardware/AudioInput.cpp b/src/Apps/Game/src/hardware/AudioInput.cpp deleted file mode 100644 index d6f1bfc..0000000 --- a/src/Apps/Game/src/hardware/AudioInput.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "AudioInput.h" -#include -#include -#include - -#if defined(__linux__) -#include -#endif - -namespace Game -{ - AudioInput::AudioInput() : - deviceName("default"), - sampleRate(16000), - channels(1), - opened(false) -#if defined(__linux__) - , - handle(nullptr) -#endif - {} - - AudioInput::~AudioInput() - { - shutdown(); - } - - bool AudioInput::init(const std::string& deviceName, uint32_t sampleRate, uint32_t channels) - { - shutdown(); - - if (sampleRate == 0 || channels == 0) - { - std::cerr << "AudioInput invalid params." << std::endl; - return false; - } - - this->deviceName = deviceName; - this->sampleRate = sampleRate; - this->channels = channels; - -#if defined(__linux__) - int result = snd_pcm_open(&handle, deviceName.c_str(), SND_PCM_STREAM_CAPTURE, 0); - if (result < 0) - { - std::cerr << "AudioInput open failed: " << snd_strerror(result) << std::endl; - handle = nullptr; - return false; - } - - result = snd_pcm_set_params( - handle, - SND_PCM_FORMAT_S16_LE, - SND_PCM_ACCESS_RW_INTERLEAVED, - channels, - sampleRate, - 1, - 50000); - if (result < 0) - { - std::cerr << "AudioInput set params failed: " << snd_strerror(result) << std::endl; - shutdown(); - return false; - } - - opened = true; - return true; -#else - std::cerr << "AudioInput is only implemented on Linux ALSA." << std::endl; - return false; -#endif - } - - int AudioInput::read_samples(int16_t* buffer, int sampleCount) - { - if (buffer == nullptr || sampleCount <= 0 || !opened) - { - return 0; - } - -#if defined(__linux__) - const snd_pcm_uframes_t frames = static_cast(sampleCount / channels); - int result = snd_pcm_readi(handle, buffer, frames); - if (result == -EPIPE) - { - snd_pcm_prepare(handle); - return 0; - } - if (result < 0) - { - result = snd_pcm_recover(handle, result, 0); - if (result < 0) - { - std::cerr << "AudioInput read failed: " << snd_strerror(result) << std::endl; - return 0; - } - } - - return result * static_cast(channels); -#else - return 0; -#endif - } - - float AudioInput::read_volume(int sampleCount) - { - if (sampleCount <= 0) - { - return 0.0f; - } - - std::vector samples(static_cast(sampleCount), 0); - const int count = read_samples(samples.data(), sampleCount); - if (count <= 0) - { - return 0.0f; - } - - double sum = 0.0; - for (int i = 0; i < count; ++i) - { - const double value = static_cast(samples[i]) / 32768.0; - sum += value * value; - } - - return static_cast(std::sqrt(sum / count)); - } - - void AudioInput::shutdown() - { -#if defined(__linux__) - if (handle != nullptr) - { - snd_pcm_close(handle); - handle = nullptr; - } -#endif - opened = false; - } -} diff --git a/src/Apps/Game/src/hardware/AudioInput.h b/src/Apps/Game/src/hardware/AudioInput.h deleted file mode 100644 index c385eda..0000000 --- a/src/Apps/Game/src/hardware/AudioInput.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include -#include -#include - -#if defined(__linux__) -typedef struct _snd_pcm snd_pcm_t; -#endif - -namespace Game -{ - class AudioInput - { - private: - std::string deviceName; - uint32_t sampleRate; - uint32_t channels; - bool opened; -#if defined(__linux__) - snd_pcm_t* handle; -#endif - - public: - AudioInput(); - ~AudioInput(); - - bool init( - const std::string& deviceName = "default", - uint32_t sampleRate = 16000, - uint32_t channels = 1); - int read_samples(int16_t* buffer, int sampleCount); - float read_volume(int sampleCount = 512); - void shutdown(); - - bool is_open() const { return opened; } - uint32_t get_sample_rate() const { return sampleRate; } - uint32_t get_channels() const { return channels; } - const std::string& get_device_name() const { return deviceName; } - }; -} diff --git a/src/Apps/Game/src/hardware/AudioOutput.cpp b/src/Apps/Game/src/hardware/AudioOutput.cpp deleted file mode 100644 index 6162b1b..0000000 --- a/src/Apps/Game/src/hardware/AudioOutput.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include "AudioOutput.h" -#include -#include -#include -#include -#include - -#if defined(__linux__) -#include -#endif - -namespace -{ - struct WavHeader - { - char riff[4]; - uint32_t fileSize; - char wave[4]; - char fmt[4]; - uint32_t fmtSize; - uint16_t audioFormat; - uint16_t channels; - uint32_t sampleRate; - uint32_t byteRate; - uint16_t blockAlign; - uint16_t bitsPerSample; - char data[4]; - uint32_t dataSize; - }; - - static bool IsSupportedWav(const WavHeader& header) - { - return std::memcmp(header.riff, "RIFF", 4) == 0 && - std::memcmp(header.wave, "WAVE", 4) == 0 && - std::memcmp(header.fmt, "fmt ", 4) == 0 && - std::memcmp(header.data, "data", 4) == 0 && - header.audioFormat == 1 && - header.bitsPerSample == 16 && - header.fmtSize == 16; - } -} - -namespace Game -{ - AudioOutput::AudioOutput() : - deviceName("default"), - sampleRate(16000), - channels(1), - opened(false) -#if defined(__linux__) - , - handle(nullptr) -#endif - {} - - AudioOutput::~AudioOutput() - { - shutdown(); - } - - bool AudioOutput::init(const std::string& deviceName, uint32_t sampleRate, uint32_t channels) - { - shutdown(); - - if (sampleRate == 0 || channels == 0) - { - std::cerr << "AudioOutput invalid params." << std::endl; - return false; - } - - this->deviceName = deviceName; - this->sampleRate = sampleRate; - this->channels = channels; - -#if defined(__linux__) - int result = snd_pcm_open(&handle, deviceName.c_str(), SND_PCM_STREAM_PLAYBACK, 0); - if (result < 0) - { - std::cerr << "AudioOutput open failed: " << snd_strerror(result) << std::endl; - handle = nullptr; - return false; - } - - result = snd_pcm_set_params( - handle, - SND_PCM_FORMAT_S16_LE, - SND_PCM_ACCESS_RW_INTERLEAVED, - channels, - sampleRate, - 1, - 50000); - if (result < 0) - { - std::cerr << "AudioOutput set params failed: " << snd_strerror(result) << std::endl; - shutdown(); - return false; - } - - opened = true; - return true; -#else - std::cerr << "AudioOutput is only implemented on Linux ALSA." << std::endl; - return false; -#endif - } - - int AudioOutput::write_samples(const int16_t* samples, int sampleCount) - { - if (samples == nullptr || sampleCount <= 0 || !opened) - { - return 0; - } - -#if defined(__linux__) - const snd_pcm_uframes_t frames = static_cast(sampleCount / channels); - int result = snd_pcm_writei(handle, samples, frames); - if (result == -EPIPE) - { - snd_pcm_prepare(handle); - return 0; - } - if (result < 0) - { - result = snd_pcm_recover(handle, result, 0); - if (result < 0) - { - std::cerr << "AudioOutput write failed: " << snd_strerror(result) << std::endl; - return 0; - } - } - - return result * static_cast(channels); -#else - return 0; -#endif - } - - bool AudioOutput::play_wav(const std::string& path) - { - std::ifstream file(path.c_str(), std::ios::binary); - if (!file.good()) - { - std::cerr << "Open wav failed: " << path << std::endl; - return false; - } - - WavHeader header; - file.read(reinterpret_cast(&header), sizeof(header)); - if (!file.good() || !IsSupportedWav(header)) - { - std::cerr << "Unsupported wav: " << path << std::endl; - return false; - } - - if (!opened || sampleRate != header.sampleRate || channels != header.channels) - { - if (!init(deviceName, header.sampleRate, header.channels)) - { - return false; - } - } - - std::vector samples(header.dataSize / sizeof(int16_t), 0); - file.read(reinterpret_cast(samples.data()), header.dataSize); - if (!file.good()) - { - return false; - } - - size_t offset = 0; - while (offset < samples.size()) - { - const int count = static_cast(std::min(4096, samples.size() - offset)); - const int written = write_samples(samples.data() + offset, count); - if (written <= 0) - { - return false; - } - - offset += static_cast(written); - } - -#if defined(__linux__) - if (handle != nullptr) - { - snd_pcm_drain(handle); - } -#endif - return true; - } - - void AudioOutput::shutdown() - { -#if defined(__linux__) - if (handle != nullptr) - { - snd_pcm_close(handle); - handle = nullptr; - } -#endif - opened = false; - } -} diff --git a/src/Apps/Game/src/hardware/AudioOutput.h b/src/Apps/Game/src/hardware/AudioOutput.h deleted file mode 100644 index 3b5839b..0000000 --- a/src/Apps/Game/src/hardware/AudioOutput.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include -#include - -#if defined(__linux__) -typedef struct _snd_pcm snd_pcm_t; -#endif - -namespace Game -{ - class AudioOutput - { - private: - std::string deviceName; - uint32_t sampleRate; - uint32_t channels; - bool opened; -#if defined(__linux__) - snd_pcm_t* handle; -#endif - - public: - AudioOutput(); - ~AudioOutput(); - - bool init( - const std::string& deviceName = "default", - uint32_t sampleRate = 16000, - uint32_t channels = 1); - int write_samples(const int16_t* samples, int sampleCount); - bool play_wav(const std::string& path); - void shutdown(); - - bool is_open() const { return opened; } - uint32_t get_sample_rate() const { return sampleRate; } - uint32_t get_channels() const { return channels; } - const std::string& get_device_name() const { return deviceName; } - }; -} diff --git a/src/Apps/Game/src/hardware/ButtonInput.cpp b/src/Apps/Game/src/hardware/ButtonInput.cpp deleted file mode 100644 index f95efaa..0000000 --- a/src/Apps/Game/src/hardware/ButtonInput.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "ButtonInput.h" -#include - -#if defined(__linux__) -#include -#include -#include -#include -#endif - -namespace Game -{ - ButtonInput::ButtonInput() : - devicePath("/dev/input/event0"), - keyCode(28), - opened(false), - down(false), - pressed(false), - released(false) -#if defined(__linux__) - , - fd(-1) -#endif - {} - - ButtonInput::~ButtonInput() - { - shutdown(); - } - - bool ButtonInput::init(const std::string& devicePath, int keyCode) - { - shutdown(); - - this->devicePath = devicePath; - this->keyCode = keyCode; - down = false; - pressed = false; - released = false; - -#if defined(__linux__) - fd = open(devicePath.c_str(), O_RDONLY | O_NONBLOCK); - if (fd < 0) - { - std::cerr << "ButtonInput open failed: " << devicePath << std::endl; - return false; - } - - opened = true; - return true; -#else - std::cerr << "ButtonInput is only implemented on Linux evdev." << std::endl; - return false; -#endif - } - - void ButtonInput::update() - { - pressed = false; - released = false; - - if (!opened) - { - return; - } - -#if defined(__linux__) - input_event event; - while (true) - { - const ssize_t bytes = read(fd, &event, sizeof(event)); - if (bytes == static_cast(sizeof(event))) - { - if (event.type != EV_KEY || event.code != keyCode) - { - continue; - } - - if (event.value == 1) - { - if (!down) - { - pressed = true; - } - down = true; - } - else if (event.value == 0) - { - if (down) - { - released = true; - } - down = false; - } - } - else - { - if (errno != EAGAIN && errno != EWOULDBLOCK) - { - std::cerr << "ButtonInput read failed." << std::endl; - } - break; - } - } -#endif - } - - void ButtonInput::shutdown() - { -#if defined(__linux__) - if (fd >= 0) - { - close(fd); - fd = -1; - } -#endif - opened = false; - down = false; - pressed = false; - released = false; - } -} diff --git a/src/Apps/Game/src/hardware/ButtonInput.h b/src/Apps/Game/src/hardware/ButtonInput.h deleted file mode 100644 index 6f58fd6..0000000 --- a/src/Apps/Game/src/hardware/ButtonInput.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include -#include - -namespace Game -{ - class ButtonInput - { - private: - std::string devicePath; - int keyCode; - bool opened; - bool down; - bool pressed; - bool released; -#if defined(__linux__) - int fd; -#endif - - public: - ButtonInput(); - ~ButtonInput(); - - bool init(const std::string& devicePath = "/dev/input/event0", int keyCode = 28); - void update(); - void shutdown(); - - bool is_open() const { return opened; } - bool is_down() const { return down; } - bool was_pressed() const { return pressed; } - bool was_released() const { return released; } - const std::string& get_device_path() const { return devicePath; } - int get_key_code() const { return keyCode; } - }; -} diff --git a/src/Core/Platform/AlsaAudioInput.cpp b/src/Core/Platform/AlsaAudioInput.cpp new file mode 100644 index 0000000..c42d7e2 --- /dev/null +++ b/src/Core/Platform/AlsaAudioInput.cpp @@ -0,0 +1,141 @@ +#include "AlsaAudioInput.h" +#include +#include +#include + +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) +#include +#endif + +namespace Platform +{ + AlsaAudioInput::AlsaAudioInput() + : device_name_("default"), + sample_rate_(16000), + channels_(1), + opened_(false) +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + , + handle_(nullptr) +#endif + { + } + + AlsaAudioInput::~AlsaAudioInput() + { + shutdown(); + } + + bool AlsaAudioInput::init(const std::string& device_name, uint32_t sample_rate, uint32_t channels) + { + shutdown(); + + if (sample_rate == 0 || channels == 0) + { + std::cerr << "AlsaAudioInput invalid params." << std::endl; + return false; + } + + device_name_ = device_name; + sample_rate_ = sample_rate; + channels_ = channels; + +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + int result = snd_pcm_open(&handle_, device_name_.c_str(), SND_PCM_STREAM_CAPTURE, 0); + if (result < 0) + { + std::cerr << "AlsaAudioInput open failed: " << snd_strerror(result) << std::endl; + handle_ = nullptr; + return false; + } + + result = snd_pcm_set_params( + handle_, + SND_PCM_FORMAT_S16_LE, + SND_PCM_ACCESS_RW_INTERLEAVED, + channels_, + sample_rate_, + 1, + 50000); + if (result < 0) + { + std::cerr << "AlsaAudioInput set params failed: " << snd_strerror(result) << std::endl; + shutdown(); + return false; + } + + opened_ = true; + return true; +#else + std::cerr << "AlsaAudioInput backend is unavailable. Enable ALSA development libraries." << std::endl; + return false; +#endif + } + + int AlsaAudioInput::read_samples(int16_t* buffer, int sample_count) + { + if (buffer == nullptr || sample_count <= 0 || !opened_) + { + return 0; + } + +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + const snd_pcm_uframes_t frames = static_cast(sample_count / channels_); + int result = snd_pcm_readi(handle_, buffer, frames); + if (result == -EPIPE) + { + snd_pcm_prepare(handle_); + return 0; + } + if (result < 0) + { + result = snd_pcm_recover(handle_, result, 0); + if (result < 0) + { + std::cerr << "AlsaAudioInput read failed: " << snd_strerror(result) << std::endl; + return 0; + } + } + + return result * static_cast(channels_); +#else + return 0; +#endif + } + + float AlsaAudioInput::read_volume(int sample_count) + { + if (sample_count <= 0) + { + return 0.0f; + } + + std::vector samples(static_cast(sample_count), 0); + const int count = read_samples(samples.data(), sample_count); + if (count <= 0) + { + return 0.0f; + } + + double sum = 0.0; + for (int i = 0; i < count; ++i) + { + const double value = static_cast(samples[i]) / 32768.0; + sum += value * value; + } + + return static_cast(std::sqrt(sum / count)); + } + + void AlsaAudioInput::shutdown() + { +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + if (handle_ != nullptr) + { + snd_pcm_close(handle_); + handle_ = nullptr; + } +#endif + opened_ = false; + } +} diff --git a/src/Core/Platform/AlsaAudioInput.h b/src/Core/Platform/AlsaAudioInput.h new file mode 100644 index 0000000..75cb8b9 --- /dev/null +++ b/src/Core/Platform/AlsaAudioInput.h @@ -0,0 +1,39 @@ +#pragma once + +#include "AudioInput.h" + +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) +typedef struct _snd_pcm snd_pcm_t; +#endif + +namespace Platform +{ + class AlsaAudioInput : public IAudioInput + { + private: + std::string device_name_; + uint32_t sample_rate_; + uint32_t channels_; + bool opened_; +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + snd_pcm_t* handle_; +#endif + + public: + AlsaAudioInput(); + ~AlsaAudioInput(); + + bool init( + const std::string& device_name = "default", + uint32_t sample_rate = 16000, + uint32_t channels = 1) override; + int read_samples(int16_t* buffer, int sample_count) override; + float read_volume(int sample_count = 512) override; + void shutdown() override; + + bool is_open() const override { return opened_; } + uint32_t get_sample_rate() const override { return sample_rate_; } + uint32_t get_channels() const override { return channels_; } + const std::string& get_device_name() const override { return device_name_; } + }; +} diff --git a/src/Core/Platform/AlsaAudioOutput.cpp b/src/Core/Platform/AlsaAudioOutput.cpp new file mode 100644 index 0000000..1f6d3aa --- /dev/null +++ b/src/Core/Platform/AlsaAudioOutput.cpp @@ -0,0 +1,204 @@ +#include "AlsaAudioOutput.h" +#include +#include +#include +#include +#include + +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) +#include +#endif + +namespace +{ + struct WavHeader + { + char riff[4]; + uint32_t file_size; + char wave[4]; + char fmt[4]; + uint32_t fmt_size; + uint16_t audio_format; + uint16_t channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; + char data[4]; + uint32_t data_size; + }; + + static bool IsSupportedWav(const WavHeader& header) + { + return std::memcmp(header.riff, "RIFF", 4) == 0 && + std::memcmp(header.wave, "WAVE", 4) == 0 && + std::memcmp(header.fmt, "fmt ", 4) == 0 && + std::memcmp(header.data, "data", 4) == 0 && + header.audio_format == 1 && + header.bits_per_sample == 16 && + header.fmt_size == 16; + } +} + +namespace Platform +{ + AlsaAudioOutput::AlsaAudioOutput() + : device_name_("default"), + sample_rate_(16000), + channels_(1), + opened_(false) +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + , + handle_(nullptr) +#endif + { + } + + AlsaAudioOutput::~AlsaAudioOutput() + { + shutdown(); + } + + bool AlsaAudioOutput::init(const std::string& device_name, uint32_t sample_rate, uint32_t channels) + { + shutdown(); + + if (sample_rate == 0 || channels == 0) + { + std::cerr << "AlsaAudioOutput invalid params." << std::endl; + return false; + } + + device_name_ = device_name; + sample_rate_ = sample_rate; + channels_ = channels; + +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + int result = snd_pcm_open(&handle_, device_name_.c_str(), SND_PCM_STREAM_PLAYBACK, 0); + if (result < 0) + { + std::cerr << "AlsaAudioOutput open failed: " << snd_strerror(result) << std::endl; + handle_ = nullptr; + return false; + } + + result = snd_pcm_set_params( + handle_, + SND_PCM_FORMAT_S16_LE, + SND_PCM_ACCESS_RW_INTERLEAVED, + channels_, + sample_rate_, + 1, + 50000); + if (result < 0) + { + std::cerr << "AlsaAudioOutput set params failed: " << snd_strerror(result) << std::endl; + shutdown(); + return false; + } + + opened_ = true; + return true; +#else + std::cerr << "AlsaAudioOutput backend is unavailable. Enable ALSA development libraries." << std::endl; + return false; +#endif + } + + int AlsaAudioOutput::write_samples(const int16_t* samples, int sample_count) + { + if (samples == nullptr || sample_count <= 0 || !opened_) + { + return 0; + } + +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + const snd_pcm_uframes_t frames = static_cast(sample_count / channels_); + int result = snd_pcm_writei(handle_, samples, frames); + if (result == -EPIPE) + { + snd_pcm_prepare(handle_); + return 0; + } + if (result < 0) + { + result = snd_pcm_recover(handle_, result, 0); + if (result < 0) + { + std::cerr << "AlsaAudioOutput write failed: " << snd_strerror(result) << std::endl; + return 0; + } + } + + return result * static_cast(channels_); +#else + return 0; +#endif + } + + bool AlsaAudioOutput::play_wav(const std::string& path) + { + std::ifstream file(path.c_str(), std::ios::binary); + if (!file.good()) + { + std::cerr << "Open wav failed: " << path << std::endl; + return false; + } + + WavHeader header; + file.read(reinterpret_cast(&header), sizeof(header)); + if (!file.good() || !IsSupportedWav(header)) + { + std::cerr << "Unsupported wav: " << path << std::endl; + return false; + } + + if (!opened_ || sample_rate_ != header.sample_rate || channels_ != header.channels) + { + if (!init(device_name_, header.sample_rate, header.channels)) + { + return false; + } + } + + std::vector samples(header.data_size / sizeof(int16_t), 0); + file.read(reinterpret_cast(samples.data()), header.data_size); + if (!file.good()) + { + return false; + } + + size_t offset = 0; + while (offset < samples.size()) + { + const int count = static_cast(std::min(4096, samples.size() - offset)); + const int written = write_samples(samples.data() + offset, count); + if (written <= 0) + { + return false; + } + + offset += static_cast(written); + } + +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + if (handle_ != nullptr) + { + snd_pcm_drain(handle_); + } +#endif + return true; + } + + void AlsaAudioOutput::shutdown() + { +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + if (handle_ != nullptr) + { + snd_pcm_close(handle_); + handle_ = nullptr; + } +#endif + opened_ = false; + } +} diff --git a/src/Core/Platform/AlsaAudioOutput.h b/src/Core/Platform/AlsaAudioOutput.h new file mode 100644 index 0000000..8e0b391 --- /dev/null +++ b/src/Core/Platform/AlsaAudioOutput.h @@ -0,0 +1,39 @@ +#pragma once + +#include "AudioOutput.h" + +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) +typedef struct _snd_pcm snd_pcm_t; +#endif + +namespace Platform +{ + class AlsaAudioOutput : public IAudioOutput + { + private: + std::string device_name_; + uint32_t sample_rate_; + uint32_t channels_; + bool opened_; +#if defined(__linux__) && defined(PLATFORM_HAS_ALSA) + snd_pcm_t* handle_; +#endif + + public: + AlsaAudioOutput(); + ~AlsaAudioOutput(); + + bool init( + const std::string& device_name = "default", + uint32_t sample_rate = 16000, + uint32_t channels = 1) override; + int write_samples(const int16_t* samples, int sample_count) override; + bool play_wav(const std::string& path) override; + void shutdown() override; + + bool is_open() const override { return opened_; } + uint32_t get_sample_rate() const override { return sample_rate_; } + uint32_t get_channels() const override { return channels_; } + const std::string& get_device_name() const override { return device_name_; } + }; +} diff --git a/src/Core/Platform/AudioInput.h b/src/Core/Platform/AudioInput.h new file mode 100644 index 0000000..7869291 --- /dev/null +++ b/src/Core/Platform/AudioInput.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace Platform +{ + class IAudioInput + { + public: + virtual ~IAudioInput() {} + + virtual bool init( + const std::string& device_name = "default", + uint32_t sample_rate = 16000, + uint32_t channels = 1) = 0; + virtual int read_samples(int16_t* buffer, int sample_count) = 0; + virtual float read_volume(int sample_count = 512) = 0; + virtual void shutdown() = 0; + + virtual bool is_open() const = 0; + virtual uint32_t get_sample_rate() const = 0; + virtual uint32_t get_channels() const = 0; + virtual const std::string& get_device_name() const = 0; + }; +} diff --git a/src/Core/Platform/AudioOutput.h b/src/Core/Platform/AudioOutput.h new file mode 100644 index 0000000..3a16755 --- /dev/null +++ b/src/Core/Platform/AudioOutput.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace Platform +{ + class IAudioOutput + { + public: + virtual ~IAudioOutput() {} + + virtual bool init( + const std::string& device_name = "default", + uint32_t sample_rate = 16000, + uint32_t channels = 1) = 0; + virtual int write_samples(const int16_t* samples, int sample_count) = 0; + virtual bool play_wav(const std::string& path) = 0; + virtual void shutdown() = 0; + + virtual bool is_open() const = 0; + virtual uint32_t get_sample_rate() const = 0; + virtual uint32_t get_channels() const = 0; + virtual const std::string& get_device_name() const = 0; + }; +} diff --git a/src/Core/Platform/ButtonInput.h b/src/Core/Platform/ButtonInput.h new file mode 100644 index 0000000..b31f161 --- /dev/null +++ b/src/Core/Platform/ButtonInput.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace Platform +{ + class IButtonInput + { + public: + virtual ~IButtonInput() {} + + virtual bool init(const std::string& device_path = "", int key_code = 0) = 0; + virtual void update() = 0; + virtual void shutdown() = 0; + + virtual bool is_open() const = 0; + virtual bool is_down() const = 0; + virtual bool was_pressed() const = 0; + virtual bool was_released() const = 0; + virtual const std::string& get_device_path() const = 0; + virtual int get_key_code() const = 0; + }; +} diff --git a/src/Core/Platform/DefaultHardware.h b/src/Core/Platform/DefaultHardware.h new file mode 100644 index 0000000..0352102 --- /dev/null +++ b/src/Core/Platform/DefaultHardware.h @@ -0,0 +1,24 @@ +#pragma once + +#ifdef USE_FRAMEBUFFER +#include "AlsaAudioInput.h" +#include "AlsaAudioOutput.h" +#include "EvdevButtonInput.h" +#else +#include "SdlAudioInput.h" +#include "SdlAudioOutput.h" +#include "SdlKeyboardButtonInput.h" +#endif + +namespace Platform +{ +#ifdef USE_FRAMEBUFFER + typedef AlsaAudioInput DefaultAudioInput; + typedef AlsaAudioOutput DefaultAudioOutput; + typedef EvdevButtonInput DefaultButtonInput; +#else + typedef SdlAudioInput DefaultAudioInput; + typedef SdlAudioOutput DefaultAudioOutput; + typedef SdlKeyboardButtonInput DefaultButtonInput; +#endif +} diff --git a/src/Core/Platform/EvdevButtonInput.cpp b/src/Core/Platform/EvdevButtonInput.cpp new file mode 100644 index 0000000..b646c2e --- /dev/null +++ b/src/Core/Platform/EvdevButtonInput.cpp @@ -0,0 +1,123 @@ +#include "EvdevButtonInput.h" +#include + +#if defined(__linux__) +#include +#include +#include +#include +#endif + +namespace Platform +{ + EvdevButtonInput::EvdevButtonInput() + : device_path_("/dev/input/event0"), + key_code_(28), + opened_(false), + down_(false), + pressed_(false), + released_(false) +#if defined(__linux__) + , + fd_(-1) +#endif + { + } + + EvdevButtonInput::~EvdevButtonInput() + { + shutdown(); + } + + bool EvdevButtonInput::init(const std::string& device_path, int key_code) + { + shutdown(); + + device_path_ = device_path.empty() ? "/dev/input/event0" : device_path; + key_code_ = key_code == 0 ? 28 : key_code; + down_ = false; + pressed_ = false; + released_ = false; + +#if defined(__linux__) + fd_ = open(device_path_.c_str(), O_RDONLY | O_NONBLOCK); + if (fd_ < 0) + { + std::cerr << "EvdevButtonInput open failed: " << device_path_ << std::endl; + return false; + } + + opened_ = true; + return true; +#else + std::cerr << "EvdevButtonInput is only implemented on Linux evdev." << std::endl; + return false; +#endif + } + + void EvdevButtonInput::update() + { + pressed_ = false; + released_ = false; + + if (!opened_) + { + return; + } + +#if defined(__linux__) + input_event event; + while (true) + { + const ssize_t bytes = read(fd_, &event, sizeof(event)); + if (bytes == static_cast(sizeof(event))) + { + if (event.type != EV_KEY || event.code != key_code_) + { + continue; + } + + if (event.value == 1) + { + if (!down_) + { + pressed_ = true; + } + down_ = true; + } + else if (event.value == 0) + { + if (down_) + { + released_ = true; + } + down_ = false; + } + } + else + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + std::cerr << "EvdevButtonInput read failed." << std::endl; + } + break; + } + } +#endif + } + + void EvdevButtonInput::shutdown() + { +#if defined(__linux__) + if (fd_ >= 0) + { + close(fd_); + fd_ = -1; + } +#endif + opened_ = false; + down_ = false; + pressed_ = false; + released_ = false; + } +} diff --git a/src/Core/Platform/EvdevButtonInput.h b/src/Core/Platform/EvdevButtonInput.h new file mode 100644 index 0000000..c29a2a8 --- /dev/null +++ b/src/Core/Platform/EvdevButtonInput.h @@ -0,0 +1,35 @@ +#pragma once + +#include "ButtonInput.h" + +namespace Platform +{ + class EvdevButtonInput : public IButtonInput + { + private: + std::string device_path_; + int key_code_; + bool opened_; + bool down_; + bool pressed_; + bool released_; +#if defined(__linux__) + int fd_; +#endif + + public: + EvdevButtonInput(); + ~EvdevButtonInput(); + + bool init(const std::string& device_path = "/dev/input/event0", int key_code = 28) override; + void update() override; + void shutdown() override; + + bool is_open() const override { return opened_; } + bool is_down() const override { return down_; } + bool was_pressed() const override { return pressed_; } + bool was_released() const override { return released_; } + const std::string& get_device_path() const override { return device_path_; } + int get_key_code() const override { return key_code_; } + }; +} diff --git a/src/Core/Platform/SDLDisplay.cpp b/src/Core/Platform/SDLDisplay.cpp index 88bac6d..a305f6b 100644 --- a/src/Core/Platform/SDLDisplay.cpp +++ b/src/Core/Platform/SDLDisplay.cpp @@ -9,11 +9,17 @@ namespace Platform width = w; height = h; - if (SDL_Init(SDL_INIT_VIDEO) < 0) + if (SDL_WasInit(0) == 0 && SDL_Init(0) < 0) { std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl; return false; } + if ((SDL_WasInit(SDL_INIT_VIDEO) & SDL_INIT_VIDEO) == 0 && + SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) + { + std::cerr << "SDL video init failed: " << SDL_GetError() << std::endl; + return false; + } window = SDL_CreateWindow( "IMX6U-Game", @@ -69,7 +75,7 @@ namespace Platform { should_quit = true; } - if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_SPACE) + if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) { should_quit = true; } @@ -93,6 +99,6 @@ namespace Platform SDL_DestroyWindow(window); window = nullptr; } - SDL_Quit(); + SDL_QuitSubSystem(SDL_INIT_VIDEO); } } diff --git a/src/Core/Platform/SdlAudioInput.cpp b/src/Core/Platform/SdlAudioInput.cpp new file mode 100644 index 0000000..fe04da8 --- /dev/null +++ b/src/Core/Platform/SdlAudioInput.cpp @@ -0,0 +1,153 @@ +#include "SdlAudioInput.h" +#include +#include +#include +#include + +namespace +{ + static bool EnsureSdlAudio() + { + if (SDL_WasInit(0) == 0 && SDL_Init(0) < 0) + { + std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl; + return false; + } + + if ((SDL_WasInit(SDL_INIT_AUDIO) & SDL_INIT_AUDIO) == 0 && + SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + { + std::cerr << "SDL audio init failed: " << SDL_GetError() << std::endl; + return false; + } + + return true; + } +} + +namespace Platform +{ + SdlAudioInput::SdlAudioInput() + : device_name_("default"), + sample_rate_(16000), + channels_(1), + opened_(false), + device_id_(0) + { + } + + SdlAudioInput::~SdlAudioInput() + { + shutdown(); + } + + bool SdlAudioInput::init(const std::string& device_name, uint32_t sample_rate, uint32_t channels) + { + shutdown(); + + if (sample_rate == 0 || channels == 0) + { + std::cerr << "SdlAudioInput invalid params." << std::endl; + return false; + } + if (!EnsureSdlAudio()) + { + return false; + } + + SDL_AudioSpec desired; + SDL_AudioSpec obtained; + SDL_zero(desired); + SDL_zero(obtained); + desired.freq = static_cast(sample_rate); + desired.format = AUDIO_S16SYS; + desired.channels = static_cast(channels); + desired.samples = 512; + desired.callback = nullptr; + + const char* sdl_device_name = nullptr; + if (!device_name.empty() && device_name != "default") + { + sdl_device_name = device_name.c_str(); + } + + device_id_ = SDL_OpenAudioDevice( + sdl_device_name, + 1, + &desired, + &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE); + if (device_id_ == 0) + { + std::cerr << "SdlAudioInput open failed: " << SDL_GetError() << std::endl; + return false; + } + if (obtained.format != AUDIO_S16SYS) + { + std::cerr << "SdlAudioInput unsupported sample format." << std::endl; + shutdown(); + return false; + } + + device_name_ = device_name.empty() ? "default" : device_name; + sample_rate_ = static_cast(obtained.freq); + channels_ = static_cast(obtained.channels); + opened_ = true; + + SDL_PauseAudioDevice(device_id_, 0); + return true; + } + + int SdlAudioInput::read_samples(int16_t* buffer, int sample_count) + { + if (buffer == nullptr || sample_count <= 0 || !opened_) + { + return 0; + } + + const Uint32 requested_bytes = static_cast(sample_count * static_cast(sizeof(int16_t))); + const Uint32 available_bytes = SDL_GetQueuedAudioSize(device_id_); + const Uint32 read_bytes = std::min(requested_bytes, available_bytes); + if (read_bytes == 0) + { + return 0; + } + + const Uint32 dequeued = SDL_DequeueAudio(device_id_, buffer, read_bytes); + return static_cast(dequeued / sizeof(int16_t)); + } + + float SdlAudioInput::read_volume(int sample_count) + { + if (sample_count <= 0) + { + return 0.0f; + } + + std::vector samples(static_cast(sample_count), 0); + const int count = read_samples(samples.data(), sample_count); + if (count <= 0) + { + return 0.0f; + } + + double sum = 0.0; + for (int i = 0; i < count; ++i) + { + const double value = static_cast(samples[i]) / 32768.0; + sum += value * value; + } + + return static_cast(std::sqrt(sum / count)); + } + + void SdlAudioInput::shutdown() + { + if (device_id_ != 0) + { + SDL_CloseAudioDevice(device_id_); + device_id_ = 0; + } + opened_ = false; + } +} diff --git a/src/Core/Platform/SdlAudioInput.h b/src/Core/Platform/SdlAudioInput.h new file mode 100644 index 0000000..70f107b --- /dev/null +++ b/src/Core/Platform/SdlAudioInput.h @@ -0,0 +1,34 @@ +#pragma once + +#include "AudioInput.h" +#include + +namespace Platform +{ + class SdlAudioInput : public IAudioInput + { + private: + std::string device_name_; + uint32_t sample_rate_; + uint32_t channels_; + bool opened_; + SDL_AudioDeviceID device_id_; + + public: + SdlAudioInput(); + ~SdlAudioInput(); + + bool init( + const std::string& device_name = "default", + uint32_t sample_rate = 16000, + uint32_t channels = 1) override; + int read_samples(int16_t* buffer, int sample_count) override; + float read_volume(int sample_count = 512) override; + void shutdown() override; + + bool is_open() const override { return opened_; } + uint32_t get_sample_rate() const override { return sample_rate_; } + uint32_t get_channels() const override { return channels_; } + const std::string& get_device_name() const override { return device_name_; } + }; +} diff --git a/src/Core/Platform/SdlAudioOutput.cpp b/src/Core/Platform/SdlAudioOutput.cpp new file mode 100644 index 0000000..97feca8 --- /dev/null +++ b/src/Core/Platform/SdlAudioOutput.cpp @@ -0,0 +1,209 @@ +#include "SdlAudioOutput.h" +#include +#include +#include +#include +#include + +namespace +{ + struct WavHeader + { + char riff[4]; + uint32_t file_size; + char wave[4]; + char fmt[4]; + uint32_t fmt_size; + uint16_t audio_format; + uint16_t channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; + char data[4]; + uint32_t data_size; + }; + + static bool EnsureSdlAudio() + { + if (SDL_WasInit(0) == 0 && SDL_Init(0) < 0) + { + std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl; + return false; + } + + if ((SDL_WasInit(SDL_INIT_AUDIO) & SDL_INIT_AUDIO) == 0 && + SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + { + std::cerr << "SDL audio init failed: " << SDL_GetError() << std::endl; + return false; + } + + return true; + } + + static bool IsSupportedWav(const WavHeader& header) + { + return std::memcmp(header.riff, "RIFF", 4) == 0 && + std::memcmp(header.wave, "WAVE", 4) == 0 && + std::memcmp(header.fmt, "fmt ", 4) == 0 && + std::memcmp(header.data, "data", 4) == 0 && + header.audio_format == 1 && + header.bits_per_sample == 16 && + header.fmt_size == 16; + } +} + +namespace Platform +{ + SdlAudioOutput::SdlAudioOutput() + : device_name_("default"), + sample_rate_(16000), + channels_(1), + opened_(false), + device_id_(0), + max_queued_samples_(0) + { + } + + SdlAudioOutput::~SdlAudioOutput() + { + shutdown(); + } + + bool SdlAudioOutput::init(const std::string& device_name, uint32_t sample_rate, uint32_t channels) + { + shutdown(); + + if (sample_rate == 0 || channels == 0) + { + std::cerr << "SdlAudioOutput invalid params." << std::endl; + return false; + } + if (!EnsureSdlAudio()) + { + return false; + } + + SDL_AudioSpec desired; + SDL_AudioSpec obtained; + SDL_zero(desired); + SDL_zero(obtained); + desired.freq = static_cast(sample_rate); + desired.format = AUDIO_S16SYS; + desired.channels = static_cast(channels); + desired.samples = 512; + desired.callback = nullptr; + + const char* sdl_device_name = nullptr; + if (!device_name.empty() && device_name != "default") + { + sdl_device_name = device_name.c_str(); + } + + device_id_ = SDL_OpenAudioDevice( + sdl_device_name, + 0, + &desired, + &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE); + if (device_id_ == 0) + { + std::cerr << "SdlAudioOutput open failed: " << SDL_GetError() << std::endl; + return false; + } + if (obtained.format != AUDIO_S16SYS) + { + std::cerr << "SdlAudioOutput unsupported sample format." << std::endl; + shutdown(); + return false; + } + + device_name_ = device_name.empty() ? "default" : device_name; + sample_rate_ = static_cast(obtained.freq); + channels_ = static_cast(obtained.channels); + max_queued_samples_ = std::max(channels_, sample_rate_ * channels_ / 8); + opened_ = true; + + SDL_PauseAudioDevice(device_id_, 0); + return true; + } + + int SdlAudioOutput::write_samples(const int16_t* samples, int sample_count) + { + if (samples == nullptr || sample_count <= 0 || !opened_) + { + return 0; + } + + const Uint32 queued_samples = SDL_GetQueuedAudioSize(device_id_) / sizeof(int16_t); + if (queued_samples >= max_queued_samples_) + { + return 0; + } + + const uint32_t available_samples = max_queued_samples_ - queued_samples; + const int write_count = static_cast(std::min( + static_cast(sample_count), + available_samples)); + if (write_count <= 0) + { + return 0; + } + + const Uint32 bytes = static_cast(write_count * static_cast(sizeof(int16_t))); + if (SDL_QueueAudio(device_id_, samples, bytes) != 0) + { + std::cerr << "SdlAudioOutput queue failed: " << SDL_GetError() << std::endl; + return 0; + } + + return write_count; + } + + bool SdlAudioOutput::play_wav(const std::string& path) + { + std::ifstream file(path.c_str(), std::ios::binary); + if (!file.good()) + { + std::cerr << "Open wav failed: " << path << std::endl; + return false; + } + + WavHeader header; + file.read(reinterpret_cast(&header), sizeof(header)); + if (!file.good() || !IsSupportedWav(header)) + { + std::cerr << "Unsupported wav: " << path << std::endl; + return false; + } + + if (!opened_ || sample_rate_ != header.sample_rate || channels_ != header.channels) + { + if (!init(device_name_, header.sample_rate, header.channels)) + { + return false; + } + } + + std::vector samples(header.data_size / sizeof(int16_t), 0); + file.read(reinterpret_cast(samples.data()), header.data_size); + if (!file.good()) + { + return false; + } + + return SDL_QueueAudio(device_id_, samples.data(), header.data_size) == 0; + } + + void SdlAudioOutput::shutdown() + { + if (device_id_ != 0) + { + SDL_ClearQueuedAudio(device_id_); + SDL_CloseAudioDevice(device_id_); + device_id_ = 0; + } + opened_ = false; + } +} diff --git a/src/Core/Platform/SdlAudioOutput.h b/src/Core/Platform/SdlAudioOutput.h new file mode 100644 index 0000000..e635b23 --- /dev/null +++ b/src/Core/Platform/SdlAudioOutput.h @@ -0,0 +1,35 @@ +#pragma once + +#include "AudioOutput.h" +#include + +namespace Platform +{ + class SdlAudioOutput : public IAudioOutput + { + private: + std::string device_name_; + uint32_t sample_rate_; + uint32_t channels_; + bool opened_; + SDL_AudioDeviceID device_id_; + uint32_t max_queued_samples_; + + public: + SdlAudioOutput(); + ~SdlAudioOutput(); + + bool init( + const std::string& device_name = "default", + uint32_t sample_rate = 16000, + uint32_t channels = 1) override; + int write_samples(const int16_t* samples, int sample_count) override; + bool play_wav(const std::string& path) override; + void shutdown() override; + + bool is_open() const override { return opened_; } + uint32_t get_sample_rate() const override { return sample_rate_; } + uint32_t get_channels() const override { return channels_; } + const std::string& get_device_name() const override { return device_name_; } + }; +} diff --git a/src/Core/Platform/SdlKeyboardButtonInput.cpp b/src/Core/Platform/SdlKeyboardButtonInput.cpp new file mode 100644 index 0000000..fbc50ff --- /dev/null +++ b/src/Core/Platform/SdlKeyboardButtonInput.cpp @@ -0,0 +1,99 @@ +#include "SdlKeyboardButtonInput.h" +#include + +namespace +{ + static bool EnsureSdlEvents() + { + if (SDL_WasInit(0) == 0 && SDL_Init(0) < 0) + { + std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl; + return false; + } + + if ((SDL_WasInit(SDL_INIT_EVENTS) & SDL_INIT_EVENTS) == 0 && + SDL_InitSubSystem(SDL_INIT_EVENTS) < 0) + { + std::cerr << "SDL events init failed: " << SDL_GetError() << std::endl; + return false; + } + + return true; + } + + static int NormalizeScancode(const std::string& device_path, int key_code) + { + const bool looks_like_default_evdev = device_path.empty() || device_path == "keyboard" || device_path == "/dev/input/event0"; + if ((key_code == 0 || key_code == 28) && looks_like_default_evdev) + { + return SDL_SCANCODE_SPACE; + } + if (key_code < 0 || key_code >= SDL_NUM_SCANCODES) + { + return SDL_SCANCODE_SPACE; + } + + return key_code; + } +} + +namespace Platform +{ + SdlKeyboardButtonInput::SdlKeyboardButtonInput() + : device_path_("keyboard"), + key_code_(SDL_SCANCODE_SPACE), + opened_(false), + down_(false), + pressed_(false), + released_(false) + { + } + + SdlKeyboardButtonInput::~SdlKeyboardButtonInput() + { + shutdown(); + } + + bool SdlKeyboardButtonInput::init(const std::string& device_path, int key_code) + { + if (!EnsureSdlEvents()) + { + return false; + } + + device_path_ = device_path.empty() ? "keyboard" : device_path; + key_code_ = NormalizeScancode(device_path_, key_code); + opened_ = true; + down_ = false; + pressed_ = false; + released_ = false; + return true; + } + + void SdlKeyboardButtonInput::update() + { + pressed_ = false; + released_ = false; + + if (!opened_) + { + return; + } + + SDL_PumpEvents(); + const Uint8* keyboard_state = SDL_GetKeyboardState(nullptr); + const bool current_down = keyboard_state != nullptr && keyboard_state[key_code_] != 0; + + pressed_ = current_down && !down_; + released_ = !current_down && down_; + down_ = current_down; + } + + void SdlKeyboardButtonInput::shutdown() + { + opened_ = false; + down_ = false; + pressed_ = false; + released_ = false; + } +} diff --git a/src/Core/Platform/SdlKeyboardButtonInput.h b/src/Core/Platform/SdlKeyboardButtonInput.h new file mode 100644 index 0000000..1191189 --- /dev/null +++ b/src/Core/Platform/SdlKeyboardButtonInput.h @@ -0,0 +1,33 @@ +#pragma once + +#include "ButtonInput.h" +#include + +namespace Platform +{ + class SdlKeyboardButtonInput : public IButtonInput + { + private: + std::string device_path_; + int key_code_; + bool opened_; + bool down_; + bool pressed_; + bool released_; + + public: + SdlKeyboardButtonInput(); + ~SdlKeyboardButtonInput(); + + bool init(const std::string& device_path = "keyboard", int key_code = SDL_SCANCODE_SPACE) override; + void update() override; + void shutdown() override; + + bool is_open() const override { return opened_; } + bool is_down() const override { return down_; } + bool was_pressed() const override { return pressed_; } + bool was_released() const override { return released_; } + const std::string& get_device_path() const override { return device_path_; } + int get_key_code() const override { return key_code_; } + }; +}