IMX6U-Game/src/Core/Platform/SdlAudioOutput.cpp

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