This commit is contained in:
parent
0defa04eef
commit
964a11a215
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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/`。
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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_; }
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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_; }
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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_; }
|
||||
};
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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_; }
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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_; }
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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_; }
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue