210 lines
4.7 KiB
C++
210 lines
4.7 KiB
C++
#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;
|
|
}
|
|
}
|