#include "miniaudio.h" #include "sound_manager.h" #include #include #include "../../lib/debug_config.h" SoundManager* SoundManager::instance() { static SoundManager s; return &s; } SoundManager::~SoundManager() { DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: static destructor entered"); shutdown(); } bool SoundManager::init() { if (m_initialized) return true; ma_engine* engine = new ma_engine; ma_result result = ma_engine_init(NULL, engine); if (result != MA_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "SoundManager: ma_engine_init failed (%d)", result); delete engine; return false; } m_engine = engine; m_context = ma_device_get_context(ma_engine_get_device(engine)); m_initialized = true; ma_device* dev = ma_engine_get_device(engine); DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SoundManager: initialized engine=%p ctx=%p dev='%s' fmt=%d ch=%u sr=%u internalSr=%u", (void*)engine, (void*)m_context, dev ? dev->playback.name : "?", dev ? (int)dev->playback.format : -1, dev ? dev->playback.channels : 0, dev ? dev->sampleRate : 0, dev ? dev->playback.internalSampleRate : 0); return true; } void SoundManager::clearCurrentPcm() { if (m_currentPcmSound) { ma_sound_uninit(m_currentPcmSound); delete m_currentPcmSound; m_currentPcmSound = nullptr; } if (m_currentPcmBuffer) { ma_audio_buffer* buf = (ma_audio_buffer*)m_currentPcmBuffer; ma_audio_buffer_uninit(buf); delete buf; m_currentPcmBuffer = nullptr; } m_currentPcmSampleRate = 0; } void SoundManager::shutdown() { if (!m_initialized) return; clearCurrentPcm(); for (auto it = m_sounds.begin(); it != m_sounds.end(); ++it) { if (it->sound) { ma_sound_uninit(it->sound); delete it->sound; } if (it->decoder) { ma_decoder_uninit(it->decoder); delete it->decoder; } } m_sounds.clear(); DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: before ma_engine_uninit engine=%p", (void*)m_engine); if (m_engine) { ma_engine_uninit(m_engine); delete m_engine; m_engine = nullptr; } DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: after ma_engine_uninit"); m_initialized = false; m_loopName.clear(); } void SoundManager::setEnabled(bool enabled) { m_enabled = enabled; } bool SoundManager::isEnabled() const { return m_enabled; } void SoundManager::setVolume(float volume) { if (volume < 0.0f) volume = 0.0f; if (volume > 1.0f) volume = 1.0f; m_volume = volume; } float SoundManager::volume() const { return m_volume; } void SoundManager::setEvent(const QString& name, bool enabled, float volume) { if (volume < 0.0f) volume = 0.0f; if (volume > 1.0f) volume = 1.0f; EventSettings es; es.enabled = enabled; es.volume = volume; m_eventSettings[name] = es; DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: event '%s' enabled=%d vol=%.2f", qPrintable(name), enabled, volume); } SoundManager::EventSettings SoundManager::eventSettings(const QString& name) const { return m_eventSettings.value(name); } bool SoundManager::loadSound(const QString& name, const QString& filePath) { if (!m_initialized) { qWarning("SoundManager: not initialized, cannot load %s", qPrintable(name)); return false; } if (m_sounds.contains(name)) { qWarning("SoundManager: sound '%s' already loaded", qPrintable(name)); return false; } QFile f(filePath); if (!f.open(QIODevice::ReadOnly)) { qWarning("SoundManager: cannot open %s", qPrintable(filePath)); return false; } LoadedSound ls; ls.mp3Data = f.readAll(); f.close(); ls.decoder = new ma_decoder; ma_decoder_config decConfig = ma_decoder_config_init_default(); ma_result result = ma_decoder_init_memory(ls.mp3Data.constData(), ls.mp3Data.size(), &decConfig, ls.decoder); if (result != MA_SUCCESS) { qWarning("SoundManager: ma_decoder_init_memory failed for %s (%d)", qPrintable(name), result); delete ls.decoder; return false; } ls.sound = new ma_sound; result = ma_sound_init_from_data_source(m_engine, (ma_data_source*)&ls.decoder->ds, 0, NULL, ls.sound); if (result != MA_SUCCESS) { qWarning("SoundManager: sound init failed for %s (%d)", qPrintable(name), result); ma_decoder_uninit(ls.decoder); delete ls.decoder; delete ls.sound; return false; } m_sounds.insert(name, ls); qDebug("SoundManager: loaded '%s' from %s (%lld bytes)", qPrintable(name), qPrintable(filePath), (long long)ls.mp3Data.size()); return true; } void SoundManager::play(const QString& name) { if (!m_enabled) return; if (!m_initialized) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager::play: not initialized"); return; } EventSettings es = m_eventSettings.value(name); if (!es.enabled) return; auto it = m_sounds.find(name); if (it == m_sounds.end()) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager::play: sound '%s' not loaded", qPrintable(name)); return; } ma_sound* s = it->sound; float effectiveVolume = m_volume * es.volume; ma_sound_set_volume(s, effectiveVolume); ma_sound_stop(s); ma_sound_seek_to_pcm_frame(s, 0); ma_sound_start(s); } void SoundManager::playLoop(const QString& name) { if (!m_enabled) return; if (!m_initialized) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager::playLoop: not initialized"); return; } /* остановить предыдущий цикл (иначе call_connecting + call_ringing звучат вместе) */ stopLoop(); EventSettings es = m_eventSettings.value(name); if (!es.enabled) return; auto it = m_sounds.find(name); if (it == m_sounds.end()) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager::playLoop: sound '%s' not loaded", qPrintable(name)); return; } ma_sound* s = it->sound; ma_sound_stop(s); ma_sound_seek_to_pcm_frame(s, 0); ma_sound_set_volume(s, m_volume * es.volume); ma_sound_set_looping(s, MA_TRUE); ma_sound_start(s); m_loopName = name; } void SoundManager::stopLoop() { if (m_loopName.isEmpty()) return; auto it = m_sounds.find(m_loopName); m_loopName.clear(); if (it == m_sounds.end()) return; ma_sound* s = it->sound; ma_sound_stop(s); ma_sound_set_looping(s, MA_FALSE); ma_sound_seek_to_pcm_frame(s, 0); } ma_context* SoundManager::context() { return m_context; } void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRate, int channels) { if (!m_engine || !m_initialized) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "SoundManager::playRawPcm: not initialized (engine=%p init=%d)", (void*)m_engine, (int)m_initialized); return; } clearCurrentPcm(); m_currentPcmSampleRate = sampleRate; DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "SoundManager::playRawPcm: %d samples %dHz %dch volume=%.2f", sampleCount, sampleRate, channels, m_volume); ma_audio_buffer_config bufCfg = ma_audio_buffer_config_init( ma_format_s16, (ma_uint32)channels, (ma_uint64)sampleCount, pcm, NULL); ma_audio_buffer* buf = new ma_audio_buffer; m_currentPcmBuffer = buf; ma_result r = ma_audio_buffer_init_copy(&bufCfg, buf); if (r != MA_SUCCESS) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "SoundManager: audio_buffer_init_copy failed (%d)", r); delete buf; m_currentPcmBuffer = nullptr; return; } m_currentPcmSound = new ma_sound; r = ma_sound_init_from_data_source(m_engine, (ma_data_source*)buf, 0, nullptr, m_currentPcmSound); if (r != MA_SUCCESS) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "SoundManager: PCM sound init failed (%d)", r); ma_audio_buffer_uninit(buf); delete buf; m_currentPcmBuffer = nullptr; delete m_currentPcmSound; m_currentPcmSound = nullptr; return; } ma_sound_set_volume(m_currentPcmSound, m_volume); r = ma_sound_start(m_currentPcmSound); ma_uint64 cursor = 0; ma_sound_get_cursor_in_pcm_frames(m_currentPcmSound, &cursor); DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SoundManager::playRawPcm: started rc=%d is_playing=%d cursor=%llu/%d", (int)r, (int)ma_sound_is_playing(m_currentPcmSound), (unsigned long long)cursor, sampleCount); } void SoundManager::pauseCurrentPcm() { if (m_currentPcmSound) ma_sound_stop(m_currentPcmSound); } void SoundManager::resumeCurrentPcm() { if (m_currentPcmSound) ma_sound_start(m_currentPcmSound); } void SoundManager::stopCurrentPcm() { clearCurrentPcm(); } unsigned long long SoundManager::currentPcmCursor() const { if (!m_currentPcmSound) return 0; ma_uint64 cursor = 0; ma_sound_get_cursor_in_pcm_frames(m_currentPcmSound, &cursor); return (unsigned long long)cursor; } bool SoundManager::isCurrentPcmPlaying() const { if (!m_currentPcmSound) return false; return ma_sound_is_playing(m_currentPcmSound) != MA_FALSE; } void SoundManager::seekCurrentPcm(unsigned long long pcmFrame) { if (m_currentPcmSound) ma_sound_seek_to_pcm_frame(m_currentPcmSound, pcmFrame); } static QString decodeDeviceName(const char* name) { QString s = QString::fromUtf8(name); if (s.contains(QChar(0xFFFD))) s = QString::fromLocal8Bit(name); return s; } QVector SoundManager::enumeratePlaybackDevices() { QVector result; ma_context* ctx = instance()->m_context; if (!ctx) return result; ma_device_info* pDevices = nullptr; ma_uint32 count = 0; if (ma_context_get_devices(ctx, &pDevices, &count, nullptr, nullptr) != MA_SUCCESS) return result; for (ma_uint32 i = 0; i < count; i++) result.append({decodeDeviceName(pDevices[i].name), pDevices[i].isDefault != MA_FALSE}); return result; } QVector SoundManager::enumerateCaptureDevices() { QVector result; ma_context* ctx = instance()->m_context; if (!ctx) return result; ma_device_info* pDevices = nullptr; ma_uint32 count = 0; if (ma_context_get_devices(ctx, nullptr, nullptr, &pDevices, &count) != MA_SUCCESS) return result; for (ma_uint32 i = 0; i < count; i++) result.append({decodeDeviceName(pDevices[i].name), pDevices[i].isDefault != MA_FALSE}); return result; }