diff --git a/src/audio.cpp b/src/audio.cpp index 607a05a..a209557 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -1,6 +1,7 @@ #include "audio.h" #include +#include std::vector Audio::getDevicesList() { std::vector deviceInfos; @@ -23,6 +24,14 @@ std::vector Audio::getDevicesList() { } void Audio::selectDevice(size_t deviceIndex) { + if (m_lastDevicesList.empty()) { + spdlog::warn("Cannot select audio device: device list is empty"); + return; + } + if (deviceIndex >= m_lastDevicesList.size()) { + spdlog::warn("Device index {} is out of range. Falling back to 0.", deviceIndex); + deviceIndex = 0; + } m_selectedDeviceID = m_lastDevicesList[deviceIndex].id; } @@ -33,9 +42,39 @@ bool Audio::playAudioData(const int channels, const int sampleRate, const int bi return false; } + if (channels <= 0 || bitsPerSample <= 0 || sampleRate <= 0) { + spdlog::error("Invalid audio metadata: channels={}, sampleRate={}, bitsPerSample={}", channels, sampleRate, + bitsPerSample); + free((void*)buffer); + return false; + } + ma_format format = determineFormat(bitsPerSample); + if (format == ma_format_unknown) { + spdlog::error("Unsupported bits per sample value: {}", bitsPerSample); + free((void*)buffer); + return false; + } + if (bufferSize == 0) { + free((void*)buffer); + return true; + } + + auto devices = getDevicesList(); + if (devices.empty()) { + spdlog::error("No playback devices are available"); + free((void*)buffer); + return false; + } + if (std::find_if(devices.begin(), devices.end(), [&](const DeviceInfo& device) { + return ma_device_id_equal(&device.id, &m_selectedDeviceID); + }) == devices.end()) { + spdlog::warn("Selected audio device is unavailable. Falling back to index 0."); + m_selectedDeviceID = devices[0].id; + } + freeSounds(); updateDevice(); - updateResampler(determineFormat(bitsPerSample), channels, sampleRate, AUDIO_DEFAULT_SAMPLE_RATE); + updateResampler(format, channels, sampleRate, AUDIO_DEFAULT_SAMPLE_RATE); const ma_uint64 frameCountIn = (bufferSize * 8) / (channels * bitsPerSample); ma_uint64 frameCountOut = 0; ma_result result = @@ -70,8 +109,8 @@ bool Audio::playAudioData(const int channels, const int sampleRate, const int bi return true; } - ma_audio_buffer_config config = ma_audio_buffer_config_init(determineFormat(bitsPerSample), channels, frameCountOut, - pPayload->pcmData.data(), nullptr); + ma_audio_buffer_config config = + ma_audio_buffer_config_init(format, channels, frameCountOut, pPayload->pcmData.data(), nullptr); config.sampleRate = AUDIO_DEFAULT_SAMPLE_RATE; pPayload->audioBuffer = std::make_unique(); diff --git a/src/audio.h b/src/audio.h index 2fd6472..3afd92f 100644 --- a/src/audio.h +++ b/src/audio.h @@ -3,6 +3,7 @@ #include "singleton.h" #include +#include #include #include #include @@ -131,15 +132,18 @@ class CResampler { return resampler != nullptr ? ma_resampler_set_rate(&*resampler, sampleRateIn, sampleRateOut) : MA_ERROR; } - ma_result processAudioData(const void* pFramesIn, const ma_uint64& frameCountIn, void* pFramesOut, + ma_result processAudioData(const void* pFramesIn, ma_uint64 frameCountIn, void* pFramesOut, ma_uint64& frameCountOut) { if (pFramesIn == nullptr || pFramesOut == nullptr) { return MA_INVALID_ARGS; } - return resampler != nullptr ? ma_resampler_process_pcm_frames(&*resampler, pFramesIn, (ma_uint64*)&frameCountIn, - pFramesOut, &frameCountOut) - : MA_ERROR; + if (resampler == nullptr) { + return MA_ERROR; + } + + ma_uint64 inFrames = frameCountIn; + return ma_resampler_process_pcm_frames(&*resampler, pFramesIn, &inFrames, pFramesOut, &frameCountOut); } private: @@ -149,7 +153,17 @@ class CResampler { class Audio { public: - Audio() : m_device(nullptr) { m_selectedDeviceID = getDevicesList()[0].id; } + Audio() : m_device(nullptr), m_hasCurrentDevice(false) { + auto devices = getDevicesList(); + if (devices.empty()) { + spdlog::warn("No audio devices found during Audio initialization"); + std::memset(&m_selectedDeviceID, 0, sizeof(m_selectedDeviceID)); + std::memset(&m_currentDeviceID, 0, sizeof(m_currentDeviceID)); + return; + } + m_selectedDeviceID = devices[0].id; + std::memset(&m_currentDeviceID, 0, sizeof(m_currentDeviceID)); + } ~Audio() = default; std::vector getDevicesList(); @@ -164,16 +178,18 @@ class Audio { std::unique_ptr m_resampler; ma_device_id m_selectedDeviceID; ma_device_id m_currentDeviceID; + bool m_hasCurrentDevice; std::vector m_lastDevicesList; void updateDevice() { - if (ma_device_id_equal(&m_currentDeviceID, &m_selectedDeviceID)) { + if (m_hasCurrentDevice && ma_device_id_equal(&m_currentDeviceID, &m_selectedDeviceID)) { return; } spdlog::debug("Initializing new audio device"); m_device = std::make_unique(&m_selectedDeviceID, &Audio::audioDataCallback); ma_device_start(*m_device); m_currentDeviceID = m_selectedDeviceID; + m_hasCurrentDevice = true; } void updateResampler(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { diff --git a/src/speech.cpp b/src/speech.cpp index fc24d9b..5977487 100644 --- a/src/speech.cpp +++ b/src/speech.cpp @@ -4,6 +4,7 @@ #include "unsupportedVoicesFilter.h" #include +#include #include #include @@ -61,13 +62,22 @@ bool Speech::speak(const char* text) { spdlog::warn("Trying to speak with unsupported voice"); return false; } - uint64_t bufferSize; - int channels; - int sampleRate; - int bitsPerSample; + uint64_t bufferSize = 0; + int channels = 0; + int sampleRate = 0; + int bitsPerSample = 0; auto* data = SRAL_SpeakToMemoryEx(SRAL_ENGINE_SAPI, text, &bufferSize, &channels, &sampleRate, &bitsPerSample); - g_Audio.playAudioData(channels, sampleRate, bitsPerSample, bufferSize, data); - return true; + if (data == nullptr) { + spdlog::error("SRAL_SpeakToMemoryEx returned nullptr"); + return false; + } + if (channels <= 0 || sampleRate <= 0 || bitsPerSample <= 0) { + spdlog::error("SRAL returned invalid audio metadata: channels={}, sampleRate={}, bitsPerSample={}", channels, + sampleRate, bitsPerSample); + free(data); + return false; + } + return g_Audio.playAudioData(channels, sampleRate, bitsPerSample, bufferSize, data); } bool Speech::setRate(uint64_t rate) { diff --git a/src/ui.cpp b/src/ui.cpp index 9110d32..f4f50e7 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -94,6 +94,8 @@ void MainFrame::populateVoicesList() { auto voices = Speech::GetInstance().getVoicesList(); if (voices.empty()) { m_voicesList->AppendString("No voices available"); + spdlog::warn("No voices available, voice selection is disabled"); + return; } size_t voiceCounter = 0; bool isVoiceFoundByName = false; @@ -105,8 +107,12 @@ void MainFrame::populateVoicesList() { } voiceCounter++; } + if (m_cliVoiceIndex < 0 || static_cast(m_cliVoiceIndex) >= voices.size()) { + spdlog::warn("Voice index {} is out of range. Falling back to 0.", m_cliVoiceIndex); + m_cliVoiceIndex = 0; + } m_voicesList->SetSelection(m_cliVoiceIndex); - Speech::GetInstance().setVoice(m_cliVoiceIndex); + Speech::GetInstance().setVoice(static_cast(m_cliVoiceIndex)); } void MainFrame::populateDevicesList() { @@ -120,7 +126,12 @@ void MainFrame::populateDevicesList() { auto isDefaultStr = device.isDefault ? "[default]" : ""; m_outputDevicesList->AppendString(wxString::FromUTF8(std::format("{} {}", isDefaultStr, device.name))); } + if (m_cliOutputDeviceIndex < 0 || static_cast(m_cliOutputDeviceIndex) >= devices.size()) { + spdlog::warn("Device index {} is out of range. Falling back to 0.", m_cliOutputDeviceIndex); + m_cliOutputDeviceIndex = 0; + } m_outputDevicesList->SetSelection(m_cliOutputDeviceIndex); + g_Audio.selectDevice(static_cast(m_cliOutputDeviceIndex)); } void MainFrame::OnRateSliderChange(wxCommandEvent& event) { @@ -162,12 +173,20 @@ void MainFrame::OnMessageFieldKeyDown(wxKeyEvent& event) { void MainFrame::OnVoiceChange(wxCommandEvent& event) { int value = m_voicesList->GetSelection(); - Speech::GetInstance().setVoice(value); + if (value == wxNOT_FOUND) { + spdlog::warn("Voice selection event received with no selection"); + return; + } + Speech::GetInstance().setVoice(static_cast(value)); } void MainFrame::OnOutputDeviceChange(wxCommandEvent& event) { int value = m_outputDevicesList->GetSelection(); - g_Audio.selectDevice(value); + if (value == wxNOT_FOUND) { + spdlog::warn("Device selection event received with no selection"); + return; + } + g_Audio.selectDevice(static_cast(value)); } void MainFrame::OnCharEvent(wxKeyEvent& event) {