Continue streaming without audio after a 300 ms microphone timeout
This commit is contained in:
1 parent
83183f9885
commit
f7096bba7d
2 files changed
+23
-16
No files matched your search
@@ -23,6 +23,7 @@ extern "C" {
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr int64_t MicrophoneTimeoutUs = 300000;
|
||||
int64_t monotonicUs() { return call_video_playout_now_us(); }
|
||||
QString text(const char* value) { return QCoreApplication::translate("ChatGui", value); }
|
||||
|
||||
@@ -344,21 +345,19 @@ bool StreamPublishEngine::start(uint64_t group, const StreamPublishConfig& reque
|
||||
emit configurationChanged(); return true;
|
||||
}
|
||||
|
||||
bool StreamPublishEngine::reconfigure(const StreamPublishConfig& config) {
|
||||
bool StreamPublishEngine::reconfigure(const StreamPublishConfig& requested) {
|
||||
if (!m->running) return false;
|
||||
auto config = requested;
|
||||
StreamPublishConfig previous = this->config();
|
||||
config.audio = previous.audio; // Аудио меняется независимо от video rollback и отложенного уточнения размера screen.
|
||||
if (config.adaptiveQp && (config.video.maxQp < 0 || config.video.codec > 2)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop invalid QP adaptation codec=%d max_qp=%d", config.video.codec, config.video.maxQp);
|
||||
emit errorOccurred(text("Select a maximum QP before enabling adaptation")); return false;
|
||||
}
|
||||
StreamPublishConfig previous = this->config();
|
||||
if (previous.source.id == "portal" && config.source.sameSource(previous.source) && config.showCursor != previous.showCursor) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "Wayland capture cursor mode cannot change during a portal session");
|
||||
emit errorOccurred(text("Change cursor visibility before starting screen sharing")); return false;
|
||||
}
|
||||
if (config.audio != previous.audio) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop audio format changed during live publication");
|
||||
emit errorOccurred(text("Change the audio source before starting the stream")); return false;
|
||||
}
|
||||
if (config.showCursor != previous.showCursor)
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop cursor selected enabled=%d supported=%d", config.showCursor, cursorSupported(config.source));
|
||||
bool sourceChange = !config.source.sameSource(previous.source) || config.video.fps != previous.video.fps
|
||||
@@ -497,8 +496,8 @@ void StreamPublishEngine::captureAudio(const AudioCapture::Frame& frame) {
|
||||
void StreamPublishEngine::audioLoop() {
|
||||
const uint64_t id = m->id;
|
||||
{ std::unique_lock<std::mutex> lock(m->lock);
|
||||
m->wake.wait(lock, [this] { return !m->running || m->videoReady; }); }
|
||||
if (!m->running) return;
|
||||
m->wake.wait(lock, [this] { return !m->running || !m->wanted.audio || m->videoReady; }); }
|
||||
if (!m->running || !config().audio) return;
|
||||
auto* encoder = opus_codec_encoder_create(48000, 2); auto* compressor = audio_compressor_create();
|
||||
int result = encoder && compressor ? opus_codec_encoder_application_set(encoder, OPUS_CODEC_APP_AUDIO) : -1;
|
||||
if (result >= 0) result = opus_codec_encoder_bitrate_set(encoder, 128000);
|
||||
@@ -508,7 +507,7 @@ void StreamPublishEngine::audioLoop() {
|
||||
} else {
|
||||
int16_t pcm[1920]{}; unsigned used = 0; uint64_t sequence = 0;
|
||||
int64_t pts = 0, expected = -1, lastSent = -1; int settings[3] = {-1, -1, -1};
|
||||
while (m->running) {
|
||||
while (m->running && config().audio) {
|
||||
Impl::Audio record;
|
||||
if (!m->audio.pop(record)) { std::unique_lock<std::mutex> lock(m->lock); m->wake.wait_for(lock, std::chrono::milliseconds(5)); continue; }
|
||||
if (monotonicUs() - record.created > 250000) { m->audioDropped++; used = 0; continue; }
|
||||
@@ -698,7 +697,7 @@ void StreamPublishEngine::videoLoop(StreamPublishConfig initial) {
|
||||
m->videoProgress("publish-frame");
|
||||
uint64_t frameBytes = 0;
|
||||
stream_media_format format{(uint16_t)config.video.width, (uint16_t)config.video.height, (uint16_t)config.video.rotation,
|
||||
(uint8_t)config.video.codec, (uint8_t)(config.audio ? STREAM_MEDIA_OPUS_STEREO : 0), revision};
|
||||
(uint8_t)config.video.codec, (uint8_t)(initial.audio ? STREAM_MEDIA_OPUS_STEREO : 0), revision};
|
||||
for (const auto& encoded : output) {
|
||||
frameBytes += encoded.data.size();
|
||||
m->encodedBytes += encoded.data.size();
|
||||
@@ -784,11 +783,19 @@ void StreamPublishEngine::tick() {
|
||||
}
|
||||
if (m->running) {
|
||||
int64_t audioAt = m->lastAudioAt.load(std::memory_order_acquire);
|
||||
if (m->wanted.audio && monotonicUs() - (audioAt ? audioAt : m->microphoneStarted) >= 1000000) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop microphone has no PCM id=%llu received=%d idle_us=%lld",
|
||||
(unsigned long long)m->id, audioAt != 0, (long long)(monotonicUs() - (audioAt ? audioAt : m->microphoneStarted)));
|
||||
publicationFailed(m->id, text("The microphone is not delivering audio. Select another microphone or disable stream audio.")); return;
|
||||
if (m->wanted.audio && monotonicUs() - (audioAt ? audioAt : m->microphoneStarted) >= MicrophoneTimeoutUs) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop microphone timeout id=%llu received=%d idle_us=%lld limit_us=%lld; continuing without audio",
|
||||
(unsigned long long)m->id, audioAt != 0, (long long)(monotonicUs() - (audioAt ? audioAt : m->microphoneStarted)),
|
||||
(long long)MicrophoneTimeoutUs);
|
||||
if (!gui_stream_publish_audio(m->id, false)) {
|
||||
publicationFailed(m->id, text("Could not change stream audio")); return;
|
||||
}
|
||||
{ std::lock_guard<std::mutex> guard(m->lock); m->wanted.audio = false; }
|
||||
m_capture.closeAsync(); m->wake.notify_all();
|
||||
if (m->audioThread.joinable()) m->audioThread.join();
|
||||
emit configurationChanged();
|
||||
}
|
||||
if (!m->wanted.audio && !m_capture.closing()) m_microphone.release();
|
||||
bool confirmed, waitingSelection; int64_t started;
|
||||
{ std::lock_guard<std::mutex> guard(m->lock); confirmed = m->confirmedGeneration == m->generation; started = m->started;
|
||||
waitingSelection = m->wanted.source.id == "portal" && m->framePts < 0; }
|
||||
|
||||
@@ -33,7 +33,7 @@ struct StreamVideoSource {
|
||||
struct StreamPublishConfig {
|
||||
StreamVideoSource source; // Выбранный capture; camera-режим должен поддерживать video.width/height/fps.
|
||||
LiveVideoConfig video; // Параметры encoder; поддерживаемый codec, fps 1..60, размеры в допустимом диапазоне encoder.
|
||||
bool audio = true; // Микрофон и stereo Opus 48кГц/128кбит/с; при отказе открытия стартует без аудио, live меняется только mute.
|
||||
bool audio = true; // Микрофон и stereo Opus 48кГц/128кбит/с; отказ открытия/нет PCM 300мс отключает аудио, видео продолжается.
|
||||
bool startMuted = false; // Начальный mute применяется до запуска capture; дальнейшее переключение через setMuted().
|
||||
bool showCursor = true; // Учитывается поддерживаемым screen backend; portal разрешает выбор только до открытия сессии.
|
||||
bool nativeSize = false, automaticBitrate = false; // Screen: исходные размеры до 4096 и пересчёт bitrate при уточнении размера.
|
||||
@@ -45,7 +45,7 @@ public:
|
||||
explicit StreamPublishEngine(QObject* parent = nullptr);
|
||||
~StreamPublishEngine() override;
|
||||
bool start(uint64_t group, const StreamPublishConfig& config); // true — запуск принят; результат/ошибки ещё асинхронны. false может начать stop.
|
||||
bool reconfigure(const StreamPublishConfig& config); // true — принято, не подтверждение KEY; audio и cursor сессии portal менять нельзя.
|
||||
bool reconfigure(const StreamPublishConfig& config); // true — принято, не подтверждение KEY; audio сохраняется текущий, cursor portal менять нельзя.
|
||||
void stop(); // Идемпотентный запрос остановки; освобождение workers/microphone и доотправка actor завершаются через tick.
|
||||
bool active() const; // Actor-id ещё принадлежит engine, включая запуск и остановку/дренирование.
|
||||
bool running() const; // Capture/encoder ещё работают либо ждут выбора portal/разрешения CPU; stop сразу даёт false.
|
||||
|
||||
Reference in new issue
Block a user