Раздели PCM-сессии и исправь частоту и перемотку звука

This commit is contained in:
evgeny committed 2026-10-02 02:00:50 +03:00
1 parent 40b974b29c
commit 217e5795bf
13 files changed
+226 -134

No files matched your search

+95 -62
View File
@@ -89,6 +89,10 @@ void SoundManager::openOutput() {
void SoundManager::recoverAudio() {
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)) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "SoundManager: context failed, closing dependent devices");
emit contextAboutToReset();
@@ -112,14 +116,34 @@ void SoundManager::setDevices(const QString& output, const QString& input) {
recoverAudio();
}
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;
/* Detach ждёт читателя графа; владелец PCM освобождается последним. */
SoundManager::PcmSession::~PcmSession() {
if (sound) { ma_sound_uninit(sound); delete sound; }
if (buffer) { ma_audio_buffer_uninit(buffer); delete buffer; }
}
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;
m_currentPcmOwner.reset();
session.sound = sound;
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() {
@@ -128,7 +152,7 @@ void SoundManager::shutdown() {
m_initialized = false;
emit contextAboutToReset();
m_output.close();
clearCurrentPcm();
m_pcmSessions.clear();
for (auto it = m_sounds.begin(); it != m_sounds.end(); ++it) {
if (it->sound) { ma_sound_uninit(it->sound); delete it->sound; }
@@ -278,75 +302,84 @@ ma_context* SoundManager::context() {
return m_context;
}
void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRate, int channels,
std::shared_ptr<std::vector<int16_t>> owner) {
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;
SoundManager::PlaybackId SoundManager::playRawPcm(const int16_t* pcm, int frameCount, int sampleRate, int channels,
std::shared_ptr<std::vector<int16_t>> owner, bool transient) {
if (!m_engine || !m_initialized || !pcm || frameCount <= 0 || sampleRate <= 0 || channels < 1 || channels > 2) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: invalid PCM frames=%d rate=%d ch=%d ready=%d",
frameCount, sampleRate, channels, m_initialized);
return 0;
}
clearCurrentPcm();
m_currentPcmOwner = std::move(owner);
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 = 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;
if (owner && (pcm != owner->data() || (size_t)frameCount > owner->size() / channels)) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: PCM owner does not contain requested frames");
return 0;
}
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;
auto session = std::make_unique<PcmSession>();
session->owner = std::move(owner);
session->frames = frameCount; session->rate = sampleRate; session->transient = transient;
ma_audio_buffer_config config = ma_audio_buffer_config_init(ma_format_s16, channels, frameCount, pcm, nullptr);
config.sampleRate = sampleRate;
auto* buffer = new ma_audio_buffer{};
ma_result result = session->owner ? ma_audio_buffer_init(&config, buffer) : ma_audio_buffer_init_copy(&config, buffer);
if (result != MA_SUCCESS) {
delete buffer;
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "playback: PCM buffer init failed result=%d", result);
return 0;
}
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);
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;
}
void SoundManager::pauseCurrentPcm() {
if (m_currentPcmSound) ma_sound_stop(m_currentPcmSound);
void SoundManager::pausePcm(PlaybackId id) {
auto* session = pcmSession(id);
if (session && session->sound) ma_sound_stop(session->sound);
}
void SoundManager::resumeCurrentPcm() {
if (m_currentPcmSound) ma_sound_start(m_currentPcmSound);
void SoundManager::resumePcm(PlaybackId id) {
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::stopCurrentPcm() {
clearCurrentPcm();
void SoundManager::stopPcm(PlaybackId id) {
if (m_pcmSessions.erase(id)) DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "playback: stopped id=%llu", (unsigned long long)id);
}
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;
uint64_t SoundManager::pcmCursor(PlaybackId id) const {
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);
}
bool SoundManager::isCurrentPcmPlaying() const {
if (!m_currentPcmSound) return false;
return ma_sound_is_playing(m_currentPcmSound) != MA_FALSE;
bool SoundManager::isPcmPlaying(PlaybackId id) const {
auto* session = pcmSession(id);
return session && session->sound && ma_sound_is_playing(session->sound);
}
void SoundManager::seekCurrentPcm(unsigned long long pcmFrame) {
if (m_currentPcmSound) ma_sound_seek_to_pcm_frame(m_currentPcmSound, pcmFrame);
int SoundManager::pcmSampleRate(PlaybackId id) const {
auto* session = pcmSession(id);
return session ? session->rate : 0;
}
bool SoundManager::seekPcm(PlaybackId id, uint64_t frame) {
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) {