This commit is contained in:
HP 2026-06-07 14:38:42 +08:00
parent 0defa04eef
commit 964a11a215
31 changed files with 1308 additions and 683 deletions

View File

@ -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

View File

@ -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/`

View File

@ -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),

View File

@ -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);

View File

@ -1,5 +1,4 @@
#include "VoicePlayer.h"
#include "../hardware/AudioOutput.h"
#include <algorithm>
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())
{

View File

@ -2,11 +2,10 @@
#include <cstddef>
#include <cstdint>
#include <vector>
#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<int16_t>& get_samples() const { return samples; }
size_t get_sample_count() const { return samples.size(); }

View File

@ -1,5 +1,4 @@
#include "VoiceRecorder.h"
#include "../hardware/AudioInput.h"
#include <algorithm>
#include <cmath>
#include <vector>
@ -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)
{

View File

@ -2,11 +2,10 @@
#include <cstddef>
#include <cstdint>
#include <vector>
#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<int16_t>& get_samples() const { return samples; }
size_t get_sample_count() const { return samples.size(); }

View File

@ -1,140 +0,0 @@
#include "AudioInput.h"
#include <cmath>
#include <cstdlib>
#include <iostream>
#if defined(__linux__)
#include <alsa/asoundlib.h>
#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<snd_pcm_uframes_t>(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<int>(channels);
#else
return 0;
#endif
}
float AudioInput::read_volume(int sampleCount)
{
if (sampleCount <= 0)
{
return 0.0f;
}
std::vector<int16_t> samples(static_cast<size_t>(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<double>(samples[i]) / 32768.0;
sum += value * value;
}
return static_cast<float>(std::sqrt(sum / count));
}
void AudioInput::shutdown()
{
#if defined(__linux__)
if (handle != nullptr)
{
snd_pcm_close(handle);
handle = nullptr;
}
#endif
opened = false;
}
}

View File

@ -1,40 +0,0 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#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; }
};
}

View File

@ -1,203 +0,0 @@
#include "AudioOutput.h"
#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
#if defined(__linux__)
#include <alsa/asoundlib.h>
#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<snd_pcm_uframes_t>(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<int>(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<char*>(&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<int16_t> samples(header.dataSize / sizeof(int16_t), 0);
file.read(reinterpret_cast<char*>(samples.data()), header.dataSize);
if (!file.good())
{
return false;
}
size_t offset = 0;
while (offset < samples.size())
{
const int count = static_cast<int>(std::min<size_t>(4096, samples.size() - offset));
const int written = write_samples(samples.data() + offset, count);
if (written <= 0)
{
return false;
}
offset += static_cast<size_t>(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;
}
}

View File

@ -1,39 +0,0 @@
#pragma once
#include <cstdint>
#include <string>
#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; }
};
}

View File

@ -1,122 +0,0 @@
#include "ButtonInput.h"
#include <iostream>
#if defined(__linux__)
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <unistd.h>
#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<ssize_t>(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;
}
}

View File

@ -1,35 +0,0 @@
#pragma once
#include <cstdint>
#include <string>
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; }
};
}

View File

@ -0,0 +1,141 @@
#include "AlsaAudioInput.h"
#include <cmath>
#include <iostream>
#include <vector>
#if defined(__linux__) && defined(PLATFORM_HAS_ALSA)
#include <alsa/asoundlib.h>
#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<snd_pcm_uframes_t>(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<int>(channels_);
#else
return 0;
#endif
}
float AlsaAudioInput::read_volume(int sample_count)
{
if (sample_count <= 0)
{
return 0.0f;
}
std::vector<int16_t> samples(static_cast<size_t>(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<double>(samples[i]) / 32768.0;
sum += value * value;
}
return static_cast<float>(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;
}
}

View File

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

View File

@ -0,0 +1,204 @@
#include "AlsaAudioOutput.h"
#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
#if defined(__linux__) && defined(PLATFORM_HAS_ALSA)
#include <alsa/asoundlib.h>
#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<snd_pcm_uframes_t>(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<int>(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<char*>(&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<int16_t> samples(header.data_size / sizeof(int16_t), 0);
file.read(reinterpret_cast<char*>(samples.data()), header.data_size);
if (!file.good())
{
return false;
}
size_t offset = 0;
while (offset < samples.size())
{
const int count = static_cast<int>(std::min<size_t>(4096, samples.size() - offset));
const int written = write_samples(samples.data() + offset, count);
if (written <= 0)
{
return false;
}
offset += static_cast<size_t>(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;
}
}

View File

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

View File

@ -0,0 +1,26 @@
#pragma once
#include <cstdint>
#include <string>
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;
};
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <cstdint>
#include <string>
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;
};
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <string>
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;
};
}

View File

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

View File

@ -0,0 +1,123 @@
#include "EvdevButtonInput.h"
#include <iostream>
#if defined(__linux__)
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <unistd.h>
#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<ssize_t>(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;
}
}

View File

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

View File

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

View File

@ -0,0 +1,153 @@
#include "SdlAudioInput.h"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <vector>
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<int>(sample_rate);
desired.format = AUDIO_S16SYS;
desired.channels = static_cast<Uint8>(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<uint32_t>(obtained.freq);
channels_ = static_cast<uint32_t>(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<Uint32>(sample_count * static_cast<int>(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<int>(dequeued / sizeof(int16_t));
}
float SdlAudioInput::read_volume(int sample_count)
{
if (sample_count <= 0)
{
return 0.0f;
}
std::vector<int16_t> samples(static_cast<size_t>(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<double>(samples[i]) / 32768.0;
sum += value * value;
}
return static_cast<float>(std::sqrt(sum / count));
}
void SdlAudioInput::shutdown()
{
if (device_id_ != 0)
{
SDL_CloseAudioDevice(device_id_);
device_id_ = 0;
}
opened_ = false;
}
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "AudioInput.h"
#include <SDL.h>
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_; }
};
}

View File

@ -0,0 +1,209 @@
#include "SdlAudioOutput.h"
#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
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<int>(sample_rate);
desired.format = AUDIO_S16SYS;
desired.channels = static_cast<Uint8>(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<uint32_t>(obtained.freq);
channels_ = static_cast<uint32_t>(obtained.channels);
max_queued_samples_ = std::max<uint32_t>(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<int>(std::min<uint32_t>(
static_cast<uint32_t>(sample_count),
available_samples));
if (write_count <= 0)
{
return 0;
}
const Uint32 bytes = static_cast<Uint32>(write_count * static_cast<int>(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<char*>(&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<int16_t> samples(header.data_size / sizeof(int16_t), 0);
file.read(reinterpret_cast<char*>(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;
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#include "AudioOutput.h"
#include <SDL.h>
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_; }
};
}

View File

@ -0,0 +1,99 @@
#include "SdlKeyboardButtonInput.h"
#include <iostream>
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;
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "ButtonInput.h"
#include <SDL.h>
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_; }
};
}