audio: clarify waveout device contract
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Audio {
|
||||
namespace WASAPI {
|
||||
namespace WaveOut {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -49,18 +49,25 @@ std::string ConvertWaveOutDeviceName(const TCHAR* deviceName) {
|
||||
|
||||
} // namespace
|
||||
|
||||
WASAPIBackend::WASAPIBackend()
|
||||
WaveOutBackend::WaveOutBackend()
|
||||
: m_audioBuffer1(BufferSize * 2)
|
||||
, m_audioBuffer2(BufferSize * 2)
|
||||
{
|
||||
}
|
||||
|
||||
WASAPIBackend::~WASAPIBackend() {
|
||||
WaveOutBackend::~WaveOutBackend() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool WASAPIBackend::Initialize(const AudioConfig& config) {
|
||||
bool WaveOutBackend::Initialize(const AudioConfig& config) {
|
||||
m_config = config;
|
||||
const size_t bufferSampleCount = static_cast<size_t>(config.bufferSize) * config.channels;
|
||||
m_audioBuffer1.assign(bufferSampleCount, 0);
|
||||
m_audioBuffer2.assign(bufferSampleCount, 0);
|
||||
m_pendingMixBuffer.assign(bufferSampleCount, 0.0f);
|
||||
m_hasPendingMix = false;
|
||||
m_buffer1Available = true;
|
||||
m_buffer2Available = true;
|
||||
|
||||
m_waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
m_waveFormat.nChannels = static_cast<WORD>(config.channels);
|
||||
@@ -84,7 +91,7 @@ bool WASAPIBackend::Initialize(const AudioConfig& config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void WASAPIBackend::Shutdown() {
|
||||
void WaveOutBackend::Shutdown() {
|
||||
if (m_isRunning.load()) {
|
||||
Stop();
|
||||
}
|
||||
@@ -97,22 +104,36 @@ void WASAPIBackend::Shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string WASAPIBackend::GetDeviceName() const {
|
||||
std::string WaveOutBackend::GetDeviceName() const {
|
||||
return m_deviceName;
|
||||
}
|
||||
|
||||
void WASAPIBackend::GetAvailableDevices(std::vector<std::string>& devices) {
|
||||
void WaveOutBackend::GetAvailableDevices(std::vector<std::string>& devices) {
|
||||
devices.clear();
|
||||
for (const auto& caps : m_waveOutCaps) {
|
||||
devices.push_back(ConvertWaveOutDeviceName(caps.szPname));
|
||||
}
|
||||
}
|
||||
|
||||
bool WASAPIBackend::SetDevice(const std::string& deviceName) {
|
||||
bool WaveOutBackend::SetDevice(const std::string& deviceName) {
|
||||
const bool useDefaultDevice = deviceName.empty() || deviceName == "Default Device";
|
||||
const std::string requestedName = useDefaultDevice ? "Default Device" : deviceName;
|
||||
|
||||
if (m_hWaveOut != nullptr || m_isRunning.load()) {
|
||||
return requestedName == m_deviceName;
|
||||
}
|
||||
|
||||
if (useDefaultDevice) {
|
||||
m_requestedDeviceName.clear();
|
||||
m_deviceName = "Default Device";
|
||||
return true;
|
||||
}
|
||||
|
||||
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
|
||||
WAVEOUTCAPS caps;
|
||||
waveOutGetDevCaps(i, &caps, sizeof(WAVEOUTCAPS));
|
||||
if (deviceName == ConvertWaveOutDeviceName(caps.szPname)) {
|
||||
m_requestedDeviceName = deviceName;
|
||||
m_deviceName = deviceName;
|
||||
return true;
|
||||
}
|
||||
@@ -120,46 +141,51 @@ bool WASAPIBackend::SetDevice(const std::string& deviceName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float WASAPIBackend::GetMasterVolume() const {
|
||||
float WaveOutBackend::GetMasterVolume() const {
|
||||
return m_masterVolume.load();
|
||||
}
|
||||
|
||||
void WASAPIBackend::SetMasterVolume(float volume) {
|
||||
void WaveOutBackend::SetMasterVolume(float volume) {
|
||||
m_masterVolume.store(std::max(0.0f, std::min(1.0f, volume)));
|
||||
}
|
||||
|
||||
bool WASAPIBackend::IsMuted() const {
|
||||
bool WaveOutBackend::IsMuted() const {
|
||||
return m_muted.load();
|
||||
}
|
||||
|
||||
void WASAPIBackend::SetMuted(bool muted) {
|
||||
void WaveOutBackend::SetMuted(bool muted) {
|
||||
m_muted.store(muted);
|
||||
}
|
||||
|
||||
void WASAPIBackend::Start() {
|
||||
void WaveOutBackend::Start() {
|
||||
if (m_isRunning.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_isRunning = true;
|
||||
m_audioThread = std::thread(&WASAPIBackend::AudioThreadProc, this);
|
||||
m_audioThread = std::thread(&WaveOutBackend::AudioThreadProc, this);
|
||||
}
|
||||
|
||||
void WASAPIBackend::Stop() {
|
||||
void WaveOutBackend::Stop() {
|
||||
m_isRunning = false;
|
||||
m_dataReadyCond.notify_all();
|
||||
|
||||
if (m_hWaveOut != nullptr) {
|
||||
waveOutReset(m_hWaveOut);
|
||||
}
|
||||
|
||||
if (m_audioThread.joinable()) {
|
||||
m_audioThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPIBackend::Suspend() {
|
||||
void WaveOutBackend::Suspend() {
|
||||
if (m_hWaveOut != nullptr) {
|
||||
waveOutReset(m_hWaveOut);
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPIBackend::Resume() {
|
||||
void WaveOutBackend::Resume() {
|
||||
if (m_hWaveOut != nullptr) {
|
||||
MMRESULT result = waveOutRestart(m_hWaveOut);
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
@@ -168,52 +194,62 @@ void WASAPIBackend::Resume() {
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPIBackend::ProcessAudio(float* buffer, uint32 bufferSize,
|
||||
void WaveOutBackend::ProcessAudio(float* buffer, uint32 frameCount,
|
||||
uint32 channels, uint32 sampleRate) {
|
||||
if (m_muted.load() || buffer == nullptr) {
|
||||
(void)sampleRate;
|
||||
|
||||
if (buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
float volume = m_masterVolume.load();
|
||||
if (volume < 0.001f) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 sampleCount = bufferSize / sizeof(float);
|
||||
int16_t* backBuffer = m_isBuffer1Front ? m_audioBuffer2.data() : m_audioBuffer1.data();
|
||||
uint32 bufferSamples = static_cast<uint32>(m_audioBuffer1.size());
|
||||
|
||||
for (uint32 i = 0; i < sampleCount && i < bufferSamples; ++i) {
|
||||
float sample = buffer[i] * volume;
|
||||
sample = std::max(-1.0f, std::min(1.0f, sample));
|
||||
backBuffer[i] = static_cast<int16_t>(sample * 32767.0f);
|
||||
}
|
||||
|
||||
const uint32 sampleCount = frameCount * channels;
|
||||
std::lock_guard<std::mutex> lock(m_bufferMutex);
|
||||
m_dataReady = true;
|
||||
m_pendingMixBuffer.assign(m_pendingMixBuffer.size(), 0.0f);
|
||||
for (uint32 i = 0; i < sampleCount && i < m_pendingMixBuffer.size(); ++i) {
|
||||
m_pendingMixBuffer[i] = buffer[i];
|
||||
}
|
||||
m_hasPendingMix = true;
|
||||
m_dataReadyCond.notify_one();
|
||||
}
|
||||
|
||||
MMRESULT WASAPIBackend::InitDevice() {
|
||||
MMRESULT WaveOutBackend::InitDevice() {
|
||||
m_waveOutCaps.clear();
|
||||
WAVEOUTCAPS waveOutCapsTemp;
|
||||
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
|
||||
waveOutGetDevCaps(i, &waveOutCapsTemp, sizeof(WAVEOUTCAPS));
|
||||
m_waveOutCaps.push_back(waveOutCapsTemp);
|
||||
}
|
||||
|
||||
MMRESULT result = waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_waveFormat,
|
||||
(DWORD_PTR)&WASAPIBackend::StaticAudioCallback,
|
||||
UINT deviceId = WAVE_MAPPER;
|
||||
if (!m_requestedDeviceName.empty()) {
|
||||
bool foundRequestedDevice = false;
|
||||
for (UINT i = 0; i < m_waveOutCaps.size(); ++i) {
|
||||
if (m_requestedDeviceName == ConvertWaveOutDeviceName(m_waveOutCaps[i].szPname)) {
|
||||
deviceId = i;
|
||||
foundRequestedDevice = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundRequestedDevice) {
|
||||
std::cout << "Requested audio device is no longer available" << std::endl;
|
||||
return MMSYSERR_BADDEVICEID;
|
||||
}
|
||||
}
|
||||
|
||||
MMRESULT result = waveOutOpen(&m_hWaveOut, deviceId, &m_waveFormat,
|
||||
(DWORD_PTR)&WaveOutBackend::StaticAudioCallback,
|
||||
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cout << "Failed to open audio device" << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
m_deviceName = "Default Device";
|
||||
m_deviceName = m_requestedDeviceName.empty() ? "Default Device" : m_requestedDeviceName;
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
MMRESULT WASAPIBackend::InitBuffer() {
|
||||
MMRESULT WaveOutBackend::InitBuffer() {
|
||||
m_waveHeader1.lpData = (LPSTR)m_audioBuffer1.data();
|
||||
m_waveHeader1.dwBufferLength = static_cast<DWORD>(m_audioBuffer1.size() * sizeof(int16_t));
|
||||
m_waveHeader1.dwFlags = 0;
|
||||
@@ -238,66 +274,95 @@ MMRESULT WASAPIBackend::InitBuffer() {
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
DWORD WINAPI WASAPIBackend::AudioThreadProc(LPVOID lpParameter) {
|
||||
WASAPIBackend* backend = static_cast<WASAPIBackend*>(lpParameter);
|
||||
DWORD WINAPI WaveOutBackend::AudioThreadProc(LPVOID lpParameter) {
|
||||
WaveOutBackend* backend = static_cast<WaveOutBackend*>(lpParameter);
|
||||
if (backend) {
|
||||
backend->AudioThread();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WASAPIBackend::AudioThread() {
|
||||
PlayFrontData();
|
||||
SwapBuffer();
|
||||
PlayFrontData();
|
||||
|
||||
void WaveOutBackend::AudioThread() {
|
||||
while (m_isRunning.load()) {
|
||||
std::unique_lock<std::mutex> lock(m_bufferMutex);
|
||||
m_dataReadyCond.wait_for(lock, std::chrono::milliseconds(10), [this] { return m_dataReady || !m_isRunning.load(); });
|
||||
m_dataReadyCond.wait(lock, [this] {
|
||||
return !m_isRunning.load() ||
|
||||
(m_hasPendingMix && (m_buffer1Available || m_buffer2Available));
|
||||
});
|
||||
|
||||
if (m_dataReady) {
|
||||
PrepareBackData();
|
||||
SwapBuffer();
|
||||
PlayFrontData();
|
||||
m_dataReady = false;
|
||||
if (!m_isRunning.load()) {
|
||||
break;
|
||||
}
|
||||
|
||||
WAVEHDR* targetHeader = nullptr;
|
||||
std::vector<int16_t>* targetBuffer = nullptr;
|
||||
|
||||
if (m_buffer1Available) {
|
||||
targetHeader = &m_waveHeader1;
|
||||
targetBuffer = &m_audioBuffer1;
|
||||
m_buffer1Available = false;
|
||||
} else if (m_buffer2Available) {
|
||||
targetHeader = &m_waveHeader2;
|
||||
targetBuffer = &m_audioBuffer2;
|
||||
m_buffer2Available = false;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
FillPcm16Buffer(*targetBuffer, m_pendingMixBuffer, m_masterVolume.load(), m_muted.load());
|
||||
m_hasPendingMix = false;
|
||||
lock.unlock();
|
||||
|
||||
if (SubmitBuffer(*targetHeader, *targetBuffer) != MMSYSERR_NOERROR) {
|
||||
std::lock_guard<std::mutex> restoreLock(m_bufferMutex);
|
||||
if (targetHeader == &m_waveHeader1) {
|
||||
m_buffer1Available = true;
|
||||
} else {
|
||||
m_buffer2Available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPIBackend::OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
|
||||
void WaveOutBackend::OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
(void)hwo;
|
||||
(void)dwInstance;
|
||||
(void)dwParam2;
|
||||
|
||||
if (uMsg == WOM_DONE) {
|
||||
PrepareBackData();
|
||||
SwapBuffer();
|
||||
PlayFrontData();
|
||||
std::lock_guard<std::mutex> lock(m_bufferMutex);
|
||||
WAVEHDR* completedHeader = reinterpret_cast<WAVEHDR*>(dwParam1);
|
||||
if (completedHeader == &m_waveHeader1) {
|
||||
m_buffer1Available = true;
|
||||
} else if (completedHeader == &m_waveHeader2) {
|
||||
m_buffer2Available = true;
|
||||
}
|
||||
m_dataReadyCond.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void CALLBACK WASAPIBackend::StaticAudioCallback(HWAVEOUT hwo, UINT uMsg,
|
||||
void CALLBACK WaveOutBackend::StaticAudioCallback(HWAVEOUT hwo, UINT uMsg,
|
||||
DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
WASAPIBackend* backend = reinterpret_cast<WASAPIBackend*>(dwInstance);
|
||||
WaveOutBackend* backend = reinterpret_cast<WaveOutBackend*>(dwInstance);
|
||||
if (backend != nullptr) {
|
||||
backend->OnAudioCallback(hwo, uMsg, dwInstance, dwParam1, dwParam2);
|
||||
}
|
||||
}
|
||||
|
||||
MMRESULT WASAPIBackend::PlayFrontData() {
|
||||
WAVEHDR* frontHeader = m_isBuffer1Front ? &m_waveHeader1 : &m_waveHeader2;
|
||||
MMRESULT result = waveOutWrite(m_hWaveOut, frontHeader, sizeof(WAVEHDR));
|
||||
MMRESULT WaveOutBackend::SubmitBuffer(WAVEHDR& header, std::vector<int16_t>& samples) {
|
||||
header.lpData = reinterpret_cast<LPSTR>(samples.data());
|
||||
header.dwBufferLength = static_cast<DWORD>(samples.size() * sizeof(int16_t));
|
||||
header.dwFlags &= ~WHDR_DONE;
|
||||
|
||||
MMRESULT result = waveOutWrite(m_hWaveOut, &header, sizeof(WAVEHDR));
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cout << "Failed to write audio data" << std::endl;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void WASAPIBackend::PrepareBackData() {
|
||||
}
|
||||
|
||||
void WASAPIBackend::SwapBuffer() {
|
||||
m_isBuffer1Front = !m_isBuffer1Front;
|
||||
}
|
||||
|
||||
} // namespace WASAPI
|
||||
} // namespace WaveOut
|
||||
} // namespace Audio
|
||||
} // namespace XCEngine
|
||||
|
||||
Reference in New Issue
Block a user