|
|
|
@ -89,6 +89,10 @@ void SoundManager::openOutput() { |
|
|
|
|
|
|
|
|
|
|
|
void SoundManager::recoverAudio() { |
|
|
|
void SoundManager::recoverAudio() { |
|
|
|
if (!m_initialized) return; |
|
|
|
if (!m_initialized) return; |
|
|
|
|
|
|
|
for (auto it = m_pcmSessions.begin(); it != m_pcmSessions.end();) { |
|
|
|
|
|
|
|
if (it->second->transient && !isPcmPlaying(it->first)) it = m_pcmSessions.erase(it); |
|
|
|
|
|
|
|
else ++it; |
|
|
|
|
|
|
|
} |
|
|
|
if (m_context && audio_context_failed(m_context)) { |
|
|
|
if (m_context && audio_context_failed(m_context)) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "SoundManager: context failed, closing dependent devices"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "SoundManager: context failed, closing dependent devices"); |
|
|
|
emit contextAboutToReset(); |
|
|
|
emit contextAboutToReset(); |
|
|
|
@ -112,14 +116,34 @@ void SoundManager::setDevices(const QString& output, const QString& input) { |
|
|
|
recoverAudio(); |
|
|
|
recoverAudio(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void SoundManager::clearCurrentPcm() { |
|
|
|
/* Detach ждёт читателя графа; владелец PCM освобождается последним. */ |
|
|
|
if (m_currentPcmSound) { ma_sound_uninit(m_currentPcmSound); delete m_currentPcmSound; m_currentPcmSound = nullptr; } |
|
|
|
SoundManager::PcmSession::~PcmSession() { |
|
|
|
if (m_currentPcmBuffer) { |
|
|
|
if (sound) { ma_sound_uninit(sound); delete sound; } |
|
|
|
ma_audio_buffer* buf = (ma_audio_buffer*)m_currentPcmBuffer; |
|
|
|
if (buffer) { ma_audio_buffer_uninit(buffer); delete buffer; } |
|
|
|
ma_audio_buffer_uninit(buf); delete buf; m_currentPcmBuffer = nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SoundManager::PcmSession* SoundManager::pcmSession(PlaybackId id) const { |
|
|
|
|
|
|
|
auto it = m_pcmSessions.find(id); |
|
|
|
|
|
|
|
return it == m_pcmSessions.end() ? nullptr : it->second.get(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Новый узел имеет пустой read/resampler cache; общий PCM и handle сохраняются. */ |
|
|
|
|
|
|
|
bool SoundManager::createPcmNode(PcmSession& session, bool playing) { |
|
|
|
|
|
|
|
auto* sound = new ma_sound{}; |
|
|
|
|
|
|
|
ma_result result = ma_sound_init_from_data_source(m_engine, (ma_data_source*)session.buffer, 0, nullptr, sound); |
|
|
|
|
|
|
|
if (result != MA_SUCCESS) { |
|
|
|
|
|
|
|
delete sound; |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: node init failed result=%d", result); |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
m_currentPcmSampleRate = 0; |
|
|
|
session.sound = sound; |
|
|
|
m_currentPcmOwner.reset(); |
|
|
|
ma_sound_set_volume(sound, m_volume); |
|
|
|
|
|
|
|
if (playing && ma_sound_start(sound) != MA_SUCCESS) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: node start failed"); |
|
|
|
|
|
|
|
ma_sound_uninit(sound); delete sound; session.sound = nullptr; |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void SoundManager::shutdown() { |
|
|
|
void SoundManager::shutdown() { |
|
|
|
@ -128,7 +152,7 @@ void SoundManager::shutdown() { |
|
|
|
m_initialized = false; |
|
|
|
m_initialized = false; |
|
|
|
emit contextAboutToReset(); |
|
|
|
emit contextAboutToReset(); |
|
|
|
m_output.close(); |
|
|
|
m_output.close(); |
|
|
|
clearCurrentPcm(); |
|
|
|
m_pcmSessions.clear(); |
|
|
|
|
|
|
|
|
|
|
|
for (auto it = m_sounds.begin(); it != m_sounds.end(); ++it) { |
|
|
|
for (auto it = m_sounds.begin(); it != m_sounds.end(); ++it) { |
|
|
|
if (it->sound) { ma_sound_uninit(it->sound); delete it->sound; } |
|
|
|
if (it->sound) { ma_sound_uninit(it->sound); delete it->sound; } |
|
|
|
@ -278,75 +302,84 @@ ma_context* SoundManager::context() { |
|
|
|
return m_context; |
|
|
|
return m_context; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRate, int channels, |
|
|
|
SoundManager::PlaybackId SoundManager::playRawPcm(const int16_t* pcm, int frameCount, int sampleRate, int channels, |
|
|
|
std::shared_ptr<std::vector<int16_t>> owner) { |
|
|
|
std::shared_ptr<std::vector<int16_t>> owner, bool transient) { |
|
|
|
if (!m_engine || !m_initialized) { |
|
|
|
if (!m_engine || !m_initialized || !pcm || frameCount <= 0 || sampleRate <= 0 || channels < 1 || channels > 2) { |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "SoundManager::playRawPcm: not initialized (engine=%p init=%d)", |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: invalid PCM frames=%d rate=%d ch=%d ready=%d", |
|
|
|
(void*)m_engine, (int)m_initialized); |
|
|
|
frameCount, sampleRate, channels, m_initialized); |
|
|
|
return; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
} |
|
|
|
clearCurrentPcm(); |
|
|
|
if (owner && (pcm != owner->data() || (size_t)frameCount > owner->size() / channels)) { |
|
|
|
m_currentPcmOwner = std::move(owner); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: PCM owner does not contain requested frames"); |
|
|
|
m_currentPcmSampleRate = sampleRate; |
|
|
|
return 0; |
|
|
|
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 = m_currentPcmOwner ? ma_audio_buffer_init(&bufCfg, buf) : 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; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
auto session = std::make_unique<PcmSession>(); |
|
|
|
m_currentPcmSound = new ma_sound; |
|
|
|
session->owner = std::move(owner); |
|
|
|
r = ma_sound_init_from_data_source(m_engine, (ma_data_source*)buf, 0, nullptr, m_currentPcmSound); |
|
|
|
session->frames = frameCount; session->rate = sampleRate; session->transient = transient; |
|
|
|
if (r != MA_SUCCESS) { |
|
|
|
ma_audio_buffer_config config = ma_audio_buffer_config_init(ma_format_s16, channels, frameCount, pcm, nullptr); |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "SoundManager: PCM sound init failed (%d)", r); |
|
|
|
config.sampleRate = sampleRate; |
|
|
|
ma_audio_buffer_uninit(buf); delete buf; m_currentPcmBuffer = nullptr; |
|
|
|
auto* buffer = new ma_audio_buffer{}; |
|
|
|
delete m_currentPcmSound; m_currentPcmSound = nullptr; |
|
|
|
ma_result result = session->owner ? ma_audio_buffer_init(&config, buffer) : ma_audio_buffer_init_copy(&config, buffer); |
|
|
|
return; |
|
|
|
if (result != MA_SUCCESS) { |
|
|
|
|
|
|
|
delete buffer; |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: PCM buffer init failed result=%d", result); |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
session->buffer = buffer; |
|
|
|
|
|
|
|
if (!createPcmNode(*session, true)) return 0; |
|
|
|
|
|
|
|
PlaybackId id = ++m_nextPlaybackId; |
|
|
|
|
|
|
|
m_pcmSessions.emplace(id, std::move(session)); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "playback: started id=%llu frames=%d rate=%d ch=%d transient=%d", |
|
|
|
|
|
|
|
(unsigned long long)id, frameCount, sampleRate, channels, transient); |
|
|
|
|
|
|
|
return id; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ma_sound_set_volume(m_currentPcmSound, m_volume); |
|
|
|
void SoundManager::pausePcm(PlaybackId id) { |
|
|
|
r = ma_sound_start(m_currentPcmSound); |
|
|
|
auto* session = pcmSession(id); |
|
|
|
ma_uint64 cursor = 0; |
|
|
|
if (session && session->sound) ma_sound_stop(session->sound); |
|
|
|
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() { |
|
|
|
void SoundManager::resumePcm(PlaybackId id) { |
|
|
|
if (m_currentPcmSound) ma_sound_stop(m_currentPcmSound); |
|
|
|
auto* session = pcmSession(id); |
|
|
|
|
|
|
|
if (session && session->sound && ma_sound_start(session->sound) != MA_SUCCESS) |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: resume failed id=%llu", (unsigned long long)id); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void SoundManager::resumeCurrentPcm() { |
|
|
|
void SoundManager::stopPcm(PlaybackId id) { |
|
|
|
if (m_currentPcmSound) ma_sound_start(m_currentPcmSound); |
|
|
|
if (m_pcmSessions.erase(id)) DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "playback: stopped id=%llu", (unsigned long long)id); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void SoundManager::stopCurrentPcm() { |
|
|
|
uint64_t SoundManager::pcmCursor(PlaybackId id) const { |
|
|
|
clearCurrentPcm(); |
|
|
|
auto* session = pcmSession(id); |
|
|
|
|
|
|
|
if (!session || !session->sound) return 0; |
|
|
|
|
|
|
|
uint64_t rendered = ma_sound_get_time_in_pcm_frames(session->sound); |
|
|
|
|
|
|
|
return std::min(session->frames, session->origin + rendered * (uint64_t)session->rate / 48000); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
unsigned long long SoundManager::currentPcmCursor() const { |
|
|
|
bool SoundManager::isPcmPlaying(PlaybackId id) const { |
|
|
|
if (!m_currentPcmSound) return 0; |
|
|
|
auto* session = pcmSession(id); |
|
|
|
ma_uint64 cursor = 0; |
|
|
|
return session && session->sound && ma_sound_is_playing(session->sound); |
|
|
|
ma_sound_get_cursor_in_pcm_frames(m_currentPcmSound, &cursor); |
|
|
|
|
|
|
|
return (unsigned long long)cursor; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool SoundManager::isCurrentPcmPlaying() const { |
|
|
|
int SoundManager::pcmSampleRate(PlaybackId id) const { |
|
|
|
if (!m_currentPcmSound) return false; |
|
|
|
auto* session = pcmSession(id); |
|
|
|
return ma_sound_is_playing(m_currentPcmSound) != MA_FALSE; |
|
|
|
return session ? session->rate : 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void SoundManager::seekCurrentPcm(unsigned long long pcmFrame) { |
|
|
|
bool SoundManager::seekPcm(PlaybackId id, uint64_t frame) { |
|
|
|
if (m_currentPcmSound) ma_sound_seek_to_pcm_frame(m_currentPcmSound, pcmFrame); |
|
|
|
auto* session = pcmSession(id); |
|
|
|
|
|
|
|
if (!session || !session->sound) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "playback: seek missing id=%llu", (unsigned long long)id); return false; } |
|
|
|
|
|
|
|
bool playing = ma_sound_is_playing(session->sound); |
|
|
|
|
|
|
|
ma_sound_uninit(session->sound); delete session->sound; session->sound = nullptr; |
|
|
|
|
|
|
|
session->origin = std::min(frame, session->frames); |
|
|
|
|
|
|
|
ma_result result = ma_audio_buffer_seek_to_pcm_frame(session->buffer, session->origin); |
|
|
|
|
|
|
|
if (result != MA_SUCCESS || !createPcmNode(*session, playing)) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: seek failed id=%llu frame=%llu result=%d", |
|
|
|
|
|
|
|
(unsigned long long)id, (unsigned long long)frame, result); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "playback: seek id=%llu frame=%llu", (unsigned long long)id, (unsigned long long)session->origin); |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static QString decodeDeviceName(const char* name) { |
|
|
|
static QString decodeDeviceName(const char* name) { |
|
|
|
|