#include "SdlAudioOutput.h" #include #include #include #include #include namespace { struct WavHeader { char riff[4]; uint32_t file_size; char wave[4]; char fmt[4]; uint32_t fmt_size; uint16_t audio_format; uint16_t channels; uint32_t sample_rate; uint32_t byte_rate; uint16_t block_align; uint16_t bits_per_sample; char data[4]; uint32_t data_size; }; static bool EnsureSdlAudio() { if (SDL_WasInit(0) == 0 && SDL_Init(0) < 0) { std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl; return false; } if ((SDL_WasInit(SDL_INIT_AUDIO) & SDL_INIT_AUDIO) == 0 && SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { std::cerr << "SDL audio init failed: " << SDL_GetError() << std::endl; return false; } return true; } static bool IsSupportedWav(const WavHeader& header) { return std::memcmp(header.riff, "RIFF", 4) == 0 && std::memcmp(header.wave, "WAVE", 4) == 0 && std::memcmp(header.fmt, "fmt ", 4) == 0 && std::memcmp(header.data, "data", 4) == 0 && header.audio_format == 1 && header.bits_per_sample == 16 && header.fmt_size == 16; } } namespace Platform { SdlAudioOutput::SdlAudioOutput() : device_name_("default"), sample_rate_(16000), channels_(1), opened_(false), device_id_(0), max_queued_samples_(0) { } SdlAudioOutput::~SdlAudioOutput() { shutdown(); } bool SdlAudioOutput::init(const std::string& device_name, uint32_t sample_rate, uint32_t channels) { shutdown(); if (sample_rate == 0 || channels == 0) { std::cerr << "SdlAudioOutput invalid params." << std::endl; return false; } if (!EnsureSdlAudio()) { return false; } SDL_AudioSpec desired; SDL_AudioSpec obtained; SDL_zero(desired); SDL_zero(obtained); desired.freq = static_cast(sample_rate); desired.format = AUDIO_S16SYS; desired.channels = static_cast(channels); desired.samples = 512; desired.callback = nullptr; const char* sdl_device_name = nullptr; if (!device_name.empty() && device_name != "default") { sdl_device_name = device_name.c_str(); } device_id_ = SDL_OpenAudioDevice( sdl_device_name, 0, &desired, &obtained, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE); if (device_id_ == 0) { std::cerr << "SdlAudioOutput open failed: " << SDL_GetError() << std::endl; return false; } if (obtained.format != AUDIO_S16SYS) { std::cerr << "SdlAudioOutput unsupported sample format." << std::endl; shutdown(); return false; } device_name_ = device_name.empty() ? "default" : device_name; sample_rate_ = static_cast(obtained.freq); channels_ = static_cast(obtained.channels); max_queued_samples_ = std::max(channels_, sample_rate_ * channels_ / 8); opened_ = true; SDL_PauseAudioDevice(device_id_, 0); return true; } int SdlAudioOutput::write_samples(const int16_t* samples, int sample_count) { if (samples == nullptr || sample_count <= 0 || !opened_) { return 0; } const Uint32 queued_samples = SDL_GetQueuedAudioSize(device_id_) / sizeof(int16_t); if (queued_samples >= max_queued_samples_) { return 0; } const uint32_t available_samples = max_queued_samples_ - queued_samples; const int write_count = static_cast(std::min( static_cast(sample_count), available_samples)); if (write_count <= 0) { return 0; } const Uint32 bytes = static_cast(write_count * static_cast(sizeof(int16_t))); if (SDL_QueueAudio(device_id_, samples, bytes) != 0) { std::cerr << "SdlAudioOutput queue failed: " << SDL_GetError() << std::endl; return 0; } return write_count; } bool SdlAudioOutput::play_wav(const std::string& path) { std::ifstream file(path.c_str(), std::ios::binary); if (!file.good()) { std::cerr << "Open wav failed: " << path << std::endl; return false; } WavHeader header; file.read(reinterpret_cast(&header), sizeof(header)); if (!file.good() || !IsSupportedWav(header)) { std::cerr << "Unsupported wav: " << path << std::endl; return false; } if (!opened_ || sample_rate_ != header.sample_rate || channels_ != header.channels) { if (!init(device_name_, header.sample_rate, header.channels)) { return false; } } std::vector samples(header.data_size / sizeof(int16_t), 0); file.read(reinterpret_cast(samples.data()), header.data_size); if (!file.good()) { return false; } return SDL_QueueAudio(device_id_, samples.data(), header.data_size) == 0; } void SdlAudioOutput::shutdown() { if (device_id_ != 0) { SDL_ClearQueuedAudio(device_id_); SDL_CloseAudioDevice(device_id_); device_id_ = 0; } opened_ = false; } }