Mix live stream PCM into the shared desktop audio output
This commit is contained in:
1 parent
94697c1af0
commit
a0b6dd7ef5
2 files changed
+49
-4
No files matched your search
@@ -82,6 +82,7 @@ bool SoundManager::openContext() {
|
||||
|
||||
void SoundManager::openOutput() {
|
||||
for (auto* voice : m_voices) if (voice) voice->resetPlayback();
|
||||
++m_outputGeneration;
|
||||
if (m_output.initialized() && m_output.start()) return;
|
||||
m_restoreSelections = true;
|
||||
ma_device_config config = ma_device_config_init(ma_device_type_playback);
|
||||
@@ -93,10 +94,13 @@ void SoundManager::openOutput() {
|
||||
if (selected) config.playback.pDeviceID = &id;
|
||||
auto callback = [this, previousRoute = uint64_t(0)](const void*, void* out, unsigned int frames) mutable {
|
||||
uint64_t route = m_output.route();
|
||||
if (previousRoute && route != previousRoute) for (auto* voice : m_voices) if (voice) voice->resetPlayback();
|
||||
if (previousRoute && route != previousRoute) {
|
||||
++m_outputGeneration;
|
||||
for (auto* voice : m_voices) if (voice) voice->resetPlayback();
|
||||
}
|
||||
previousRoute = route;
|
||||
m_renderErrors.fetch_add(renderOutput(m_engine, m_voices, static_cast<float*>(out), frames,
|
||||
m_output.callbackTimeUs(), m_output.timingMeasured()));
|
||||
m_output.callbackTimeUs(), m_output.timingMeasured(), this));
|
||||
};
|
||||
if (m_output.init(m_context, config, "sound-engine", 0, callback) && m_output.start()) {
|
||||
m_actualOutput = resolveOutput(m_playbackId); return;
|
||||
@@ -155,7 +159,7 @@ void SoundManager::recoverAudio() {
|
||||
|
||||
/* Последнее смешивание и ограничение: тот же PCM передаётся устройству и всем AEC этого выхода. */
|
||||
unsigned SoundManager::renderOutput(ma_engine* engine, const std::array<VoiceAudioIo*, 2>& voices,
|
||||
float* out, unsigned frames, int64_t firstTimeUs, bool measured) {
|
||||
float* out, unsigned frames, int64_t firstTimeUs, bool measured, SoundManager* streams) {
|
||||
unsigned errors = 0;
|
||||
for (unsigned offset = 0; offset < frames;) {
|
||||
unsigned count = std::min(frames - offset, 960u);
|
||||
@@ -166,6 +170,19 @@ unsigned SoundManager::renderOutput(ma_engine* engine, const std::array<VoiceAud
|
||||
}
|
||||
for (auto* voice : voices) if (voice)
|
||||
voice->mix(destination, count, firstTimeUs + (int64_t)offset * 1000000 / 48000, measured, frames);
|
||||
if (streams) {
|
||||
std::lock_guard<std::mutex> guard(streams->m_streamLock);
|
||||
for (const auto& [id, mix] : streams->m_streams)
|
||||
mix(destination, count, firstTimeUs + (int64_t)offset * 1000000 / 48000, streams->m_outputGeneration.load());
|
||||
if (!streams->m_streams.empty() || streams->m_streamLimiter < 0.9999f) {
|
||||
for (unsigned i = 0; i < count; ++i) {
|
||||
float peak = std::max(std::abs(destination[i * 2]), std::abs(destination[i * 2 + 1]));
|
||||
float target = peak > 1.0f ? 1.0f / peak : 1.0f;
|
||||
streams->m_streamLimiter = std::min(target, streams->m_streamLimiter + (1.0f - streams->m_streamLimiter) / 4800.0f);
|
||||
destination[i * 2] *= streams->m_streamLimiter; destination[i * 2 + 1] *= streams->m_streamLimiter;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (unsigned i = 0; i < count * 2; ++i) {
|
||||
float value = destination[i];
|
||||
destination[i] = std::isfinite(value) ? std::clamp(value, -1.0f, 1.0f) : 0.0f;
|
||||
@@ -177,6 +194,22 @@ unsigned SoundManager::renderOutput(ma_engine* engine, const std::array<VoiceAud
|
||||
return errors;
|
||||
}
|
||||
|
||||
bool SoundManager::attachStream(uint64_t id, StreamMix mix) {
|
||||
if (!id || !mix || !m_initialized) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "audio stream attach invalid/uninitialized"); return false; }
|
||||
std::lock_guard<std::mutex> guard(m_streamLock);
|
||||
if (!m_streams.emplace(id, std::move(mix)).second) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "audio stream duplicate id=%llu", (unsigned long long)id); return false;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "audio stream attached id=%llu count=%zu", (unsigned long long)id, m_streams.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void SoundManager::detachStream(uint64_t id) {
|
||||
std::lock_guard<std::mutex> guard(m_streamLock);
|
||||
if (m_streams.erase(id))
|
||||
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "audio stream detached id=%llu count=%zu", (unsigned long long)id, m_streams.size());
|
||||
}
|
||||
|
||||
/* Default и явный ID одного устройства получают один микшер. */
|
||||
QString SoundManager::resolveOutput(const QString& requested) {
|
||||
auto devices = enumeratePlaybackDevices();
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <QTimer>
|
||||
#include "audio_device.h"
|
||||
|
||||
@@ -56,6 +57,13 @@ public:
|
||||
/* GUI: voice живёт до detachVoice; изменение списка выполняется при остановленном output. */
|
||||
bool attachVoice(VoiceAudioIo* voice, const QString& output);
|
||||
void detachVoice(VoiceAudioIo* voice);
|
||||
/* GUI: callback добавляет PCM 48k stereo в общий float output; firstTimeUs — время первого sample.
|
||||
* generation меняется после restart/смены устройства. detach ждёт текущий callback, не останавливая других.
|
||||
* ID/владельцы отдельны от голосовых звонков; у каждого источника собственные media-часы. */
|
||||
using StreamMix = std::function<void(float*, unsigned, int64_t, uint64_t)>;
|
||||
bool attachStream(uint64_t id, StreamMix mix);
|
||||
void detachStream(uint64_t id);
|
||||
bool outputAvailable() const { return m_output.initialized() && !m_resetting; }
|
||||
|
||||
using PlaybackId = uint64_t;
|
||||
/* GUI: независимая сессия. owner удерживает PCM до detach; без owner данные копируются. */
|
||||
@@ -111,7 +119,11 @@ private:
|
||||
bool openVoiceRoute(VoiceRoute& route);
|
||||
void mergeVoiceRoutes();
|
||||
static unsigned renderOutput(ma_engine* engine, const std::array<VoiceAudioIo*, 2>& voices,
|
||||
float* out, unsigned frames, int64_t firstTimeUs, bool measured);
|
||||
float* out, unsigned frames, int64_t firstTimeUs, bool measured, SoundManager* streams = nullptr);
|
||||
std::mutex m_streamLock;
|
||||
std::map<uint64_t, StreamMix> m_streams;
|
||||
std::atomic<uint64_t> m_outputGeneration{0};
|
||||
float m_streamLimiter = 1.0f; // Только output callback; быстрый attack, release 100ms.
|
||||
std::atomic<unsigned> m_renderErrors{0};
|
||||
std::array<VoiceAudioIo*, 2> m_voices{};
|
||||
std::map<QString, std::unique_ptr<VoiceRoute>> m_voiceRoutes;
|
||||
|
||||
Reference in new issue
Block a user