Files
micro_servers/bot4cj/src/cpp/snd.cpp
T
2026-07-28 12:44:07 +08:00

354 lines
12 KiB
C++

#ifdef _WIN32
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "user32.lib")
#define _USE_MATH_DEFINES
#include <cmath>
#else
#include <alsa/asoundlib.h>
#include <cmath>
#endif
#include <iostream>
#include <cstdint>
#include <vector>
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#ifndef M_LN2
#define M_LN2 0.693147180559945309417
#endif
#ifdef _WIN32
// Windows 回调结构
struct PlaybackContext {
volatile bool done;
WAVEHDR* header;
};
// Windows 播放完成回调函数
void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
if (uMsg == WOM_DONE) {
PlaybackContext* ctx = reinterpret_cast<PlaybackContext*>(dwInstance);
ctx->done = true;
}
}
#endif
extern "C" {
#ifdef _WIN32
#define SND_EXPORT __declspec(dllexport)
#else
#define SND_EXPORT
#endif
SND_EXPORT bool play_frequency(float frequency, int32_t duration_ms,
double volume, int32_t channels,
int32_t sample_rate) {
// 参数验证
if (frequency <= 0.0f || duration_ms <= 0 ||
volume <= 0.0 || volume > 1.0 ||
channels <= 0 || channels > 8 ||
sample_rate <= 0) {
std::cerr << "Invalid parameters" << std::endl;
return false;
}
// 精确计算帧数:使用 round 而非截断
// duration_ms / 1000.0 得到秒数,乘以采样率得到帧数
int64_t num_frames = static_cast<int64_t>(
std::round(static_cast<double>(sample_rate) * duration_ms / 1000.0)
);
if (num_frames <= 0) {
std::cerr << "Duration too short to generate any frames" << std::endl;
return false;
}
// 计算实际播放时长(用于调试)
double actual_duration_ms = static_cast<double>(num_frames) / sample_rate * 1000.0;
std::cout << "Requested duration: " << duration_ms << " ms, "
<< "Actual duration: " << actual_duration_ms << " ms, "
<< "Frames: " << num_frames << std::endl;
// 计算总采样点数
int32_t total_samples = static_cast<int32_t>(num_frames * channels);
std::vector<int16_t> buffer(total_samples);
// 计算振幅(应用对数音量缩放以获得更自然的音量控制)
int32_t amplitude = static_cast<int32_t>(32767.0 * volume);
// 生成正弦波数据
for (int64_t i = 0; i < num_frames; i++) {
// 使用 double 计算以提高精度
double t = static_cast<double>(i) / sample_rate;
double value = sin(2.0 * M_PI * frequency * t);
// 应用淡入淡出以避免咔嗒声
int64_t fade_samples = static_cast<int64_t>(sample_rate * 0.005); // 5ms 淡入淡出
double fade_factor = 1.0;
if (i < fade_samples) {
// 淡入:从 0 到 1
fade_factor = static_cast<double>(i) / fade_samples;
} else if (i >= num_frames - fade_samples) {
// 淡出:从 1 到 0
fade_factor = static_cast<double>(num_frames - 1 - i) / fade_samples;
}
int16_t sample = static_cast<int16_t>(
amplitude * value * fade_factor
);
// 填充所有声道
for (int32_t ch = 0; ch < channels; ch++) {
buffer[i * channels + ch] = sample;
}
}
#ifdef _WIN32
// ===== Windows 实现(带回调,精确计时) =====
WAVEFORMATEX wfx;
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = channels;
wfx.nSamplesPerSec = sample_rate;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = channels * 2; // 16-bit = 2 bytes per sample
wfx.nAvgBytesPerSec = sample_rate * wfx.nBlockAlign;
wfx.cbSize = 0;
PlaybackContext context;
context.done = false;
context.header = new WAVEHDR();
// 打开波形设备,使用回调函数
HWAVEOUT hWaveOut;
MMRESULT result = waveOutOpen(
&hWaveOut,
WAVE_MAPPER,
&wfx,
reinterpret_cast<DWORD_PTR>(waveOutProc), // 回调函数
reinterpret_cast<DWORD_PTR>(&context), // 传递给回调的数据
CALLBACK_FUNCTION
);
if (result != MMSYSERR_NOERROR) {
std::cerr << "Cannot open audio device. Error code: " << result << std::endl;
delete context.header;
return false;
}
// 准备 WAVEHDR
WAVEHDR& header = *context.header;
ZeroMemory(&header, sizeof(WAVEHDR));
header.lpData = reinterpret_cast<char*>(buffer.data());
header.dwBufferLength = static_cast<DWORD>(total_samples * sizeof(int16_t));
header.dwFlags = 0;
header.dwLoops = 0;
result = waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
if (result != MMSYSERR_NOERROR) {
std::cerr << "Failed to prepare audio header. Error code: " << result << std::endl;
waveOutClose(hWaveOut);
delete context.header;
return false;
}
// 写入音频数据
result = waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
if (result != MMSYSERR_NOERROR) {
std::cerr << "Failed to write audio data. Error code: " << result << std::endl;
waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
waveOutClose(hWaveOut);
delete context.header;
return false;
}
// 等待播放完成(使用事件等待而非轮询)
auto start_time = timeGetTime();
while (!context.done) {
// 使用 MsgWaitForMultipleObjects 等待消息或超时
// 这样可以在等待的同时处理 Windows 消息
DWORD wait_result = MsgWaitForMultipleObjects(0, NULL, FALSE, 100, QS_ALLINPUT);
if (wait_result == WAIT_OBJECT_0) {
// 处理 Windows 消息
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// 超时检查(防止无限等待)
DWORD elapsed = timeGetTime() - start_time;
if (elapsed > static_cast<DWORD>(duration_ms + 1000)) {
std::cerr << "Playback timeout" << std::endl;
waveOutReset(hWaveOut);
break;
}
}
auto end_time = timeGetTime();
DWORD actual_play_time = end_time - start_time;
std::cout << "Actual playback time: " << actual_play_time << " ms" << std::endl;
// 清理
waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
waveOutClose(hWaveOut);
delete context.header;
#else
// ===== Linux ALSA 实现 =====
snd_pcm_t *pcm_handle = nullptr;
const char* device = "default";
int rc = snd_pcm_open(&pcm_handle, device, SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0) {
std::cerr << "Cannot open device '" << device << "': "
<< snd_strerror(rc) << std::endl;
return false;
}
// 设置硬件参数
snd_pcm_hw_params_t *hw_params;
snd_pcm_hw_params_alloca(&hw_params);
rc = snd_pcm_hw_params_any(pcm_handle, hw_params);
if (rc < 0) {
std::cerr << "Cannot get hardware parameters: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_hw_params_set_access(pcm_handle, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (rc < 0) {
std::cerr << "Cannot set access type: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_hw_params_set_format(pcm_handle, hw_params,
SND_PCM_FORMAT_S16_LE);
if (rc < 0) {
std::cerr << "Cannot set sample format: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_hw_params_set_channels(pcm_handle, hw_params, channels);
if (rc < 0) {
std::cerr << "Cannot set channel count: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
unsigned int hw_sample_rate = static_cast<unsigned int>(sample_rate);
rc = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params,
&hw_sample_rate, 0);
if (rc < 0) {
std::cerr << "Cannot set sample rate: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
if (hw_sample_rate != static_cast<unsigned int>(sample_rate)) {
std::cout << "Requested sample rate " << sample_rate
<< " Hz not supported, using " << hw_sample_rate << " Hz" << std::endl;
}
// 设置缓冲区大小和周期
snd_pcm_uframes_t buffer_size;
rc = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
if (buffer_size > static_cast<snd_pcm_uframes_t>(num_frames)) {
snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params,
&reinterpret_cast<snd_pcm_uframes_t&>(num_frames));
}
rc = snd_pcm_hw_params(pcm_handle, hw_params);
if (rc < 0) {
std::cerr << "Cannot set hardware parameters: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
// 设置软件参数
snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_alloca(&sw_params);
rc = snd_pcm_sw_params_current(pcm_handle, sw_params);
if (rc < 0) {
std::cerr << "Cannot get software parameters: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params,
static_cast<snd_pcm_uframes_t>(num_frames));
if (rc < 0) {
std::cerr << "Cannot set start threshold: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_sw_params(pcm_handle, sw_params);
if (rc < 0) {
std::cerr << "Cannot set software parameters: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
// 播放音频(写入数据)
snd_pcm_sframes_t frames_written = 0;
snd_pcm_uframes_t frames_to_write = static_cast<snd_pcm_uframes_t>(num_frames);
// 循环写入直到所有数据都播放完毕
while (frames_written < static_cast<snd_pcm_sframes_t>(num_frames)) {
snd_pcm_uframes_t remaining = static_cast<snd_pcm_uframes_t>(num_frames - frames_written);
rc = snd_pcm_writei(pcm_handle,
buffer.data() + (frames_written * channels),
remaining);
if (rc == -EPIPE) {
// 缓冲区下溢,需要恢复
std::cerr << "Buffer underrun occurred" << std::endl;
snd_pcm_prepare(pcm_handle);
rc = snd_pcm_writei(pcm_handle,
buffer.data() + (frames_written * channels),
remaining);
}
if (rc < 0) {
std::cerr << "Error writing to PCM device: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
if (rc > 0) {
frames_written += rc;
}
}
// 等待所有数据播放完成
rc = snd_pcm_drain(pcm_handle);
if (rc < 0) {
std::cerr << "Error draining PCM device: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
snd_pcm_close(pcm_handle);
#endif
return true;
}
} // extern "C"