|
|
|
|
@ -307,36 +307,21 @@ void StreamPublishEngine::stop() {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool StreamPublishEngine::openMicrophone() { |
|
|
|
|
m_capture.close(); |
|
|
|
|
m->audioAnchor = -1; m->audioFrames = 0; |
|
|
|
|
auto config = this->config(); auto* context = SoundManager::instance()->context(); |
|
|
|
|
if (!context) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop stream audio context unavailable"); m_capture.retryLater(); return false; } |
|
|
|
|
ma_device_id device; bool selected = SoundManager::deviceId(config.microphone, &device); |
|
|
|
|
if (!config.microphone.isEmpty() && !selected) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop selected microphone ID is unavailable"); m_capture.retryLater(); return false; |
|
|
|
|
} |
|
|
|
|
for (int channels : {2, 1}) { |
|
|
|
|
ma_device_config capture = ma_device_config_init(ma_device_type_capture); |
|
|
|
|
capture.capture.format = ma_format_s16; capture.capture.channels = channels; capture.sampleRate = 48000; |
|
|
|
|
capture.capture.pDeviceID = selected ? &device : nullptr; |
|
|
|
|
auto callback = [this](const void* in, void*, unsigned frames) { captureAudio(in, frames); }; |
|
|
|
|
if (!m_capture.init(context, capture, "stream-capture", m->id, callback)) continue; |
|
|
|
|
m->channels = channels; |
|
|
|
|
if (!m_capture.start()) continue; |
|
|
|
|
int native = m_capture.device()->capture.internalChannels; |
|
|
|
|
return m_capture.open(config().microphone, AudioCapture::Role::Stream, 2, m->id, [this](int channels, int native) { |
|
|
|
|
m->audioAnchor = -1; m->audioFrames = 0; m->channels = channels; |
|
|
|
|
if (native == 1) DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop stream input is mono; stereo transport duplicates the input"); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop microphone opened requested=%d backend_channels=%d rate=%u", channels, native, m_capture.device()->sampleRate); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop microphone opened requested=%d backend_channels=%d rate=48000", channels, native); |
|
|
|
|
emit audioChannelsChanged(native == 1 ? 1 : channels); return true; |
|
|
|
|
} |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop stream cannot open microphone: %s", qPrintable(config.microphone)); return false; |
|
|
|
|
}, [] {}, [this](const AudioCapture::Frame& frame) { captureAudio(frame); }); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Hardware callback копирует максимум 20ms за элемент, без codec/DSP/allocations. */ |
|
|
|
|
void StreamPublishEngine::captureAudio(const void* input, unsigned frames) { |
|
|
|
|
const auto* pcm = static_cast<const int16_t*>(input); |
|
|
|
|
void StreamPublishEngine::captureAudio(const AudioCapture::Frame& frame) { |
|
|
|
|
const auto* pcm = frame.pcm; |
|
|
|
|
unsigned frames = frame.frames; |
|
|
|
|
if (!m->running) return; |
|
|
|
|
int64_t first = m_capture.callbackTimeUs() - m->origin; |
|
|
|
|
if (m_capture.timingMeasured() || m->audioAnchor < 0 || m_capture.captureMissing()) { |
|
|
|
|
int64_t first = frame.timeUs - m->origin; |
|
|
|
|
if (frame.measured || m->audioAnchor < 0 || frame.missing) { |
|
|
|
|
m->audioAnchor = first; m->audioFrames = 0; |
|
|
|
|
} |
|
|
|
|
first = m->audioAnchor + (int64_t)m->audioFrames * 1000000 / 48000; m->audioFrames += frames; |
|
|
|
|
@ -540,8 +525,9 @@ void StreamPublishEngine::tick() {
|
|
|
|
|
(unsigned long long)m->sourceGeneration, (unsigned long long)m->generation, (unsigned long long)m->confirmedGeneration, |
|
|
|
|
(unsigned long long)m->capturedFrames, (long long)m->framePts); |
|
|
|
|
} } |
|
|
|
|
if (m->wanted.audio && m_capture.needsRecovery()) { m_capture.close(); m_capture.retryLater(); } |
|
|
|
|
if (m->wanted.audio && m_capture.retryReady()) openMicrophone(); |
|
|
|
|
if (m->wanted.audio) { |
|
|
|
|
if (!m_capture.subscribed()) openMicrophone(); else m_capture.recover(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
unsigned dropped = m->audioDropped.exchange(0); |
|
|
|
|
if (dropped) DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop audio capture discarded blocks=%u", dropped); |
|
|
|
|
|