bool shouldBeSkipped(MixableStream& stream, const Node& listener, const AvatarAudioStream& listenerAudioStream, const AudioMixerClientData& listenerData) { if (stream.nodeStreamID.nodeLocalID == listener.getLocalID()) { return !stream.positionalStream->shouldLoopbackForNode(); } // grab the unprocessed ignores and unignores from and for this listener const auto& nodesIgnoredByListener = listenerData.getNewIgnoredNodeIDs(); const auto& nodesUnignoredByListener = listenerData.getNewUnignoredNodeIDs(); const auto& nodesIgnoringListener = listenerData.getNewIgnoringNodeIDs(); const auto& nodesUnignoringListener = listenerData.getNewUnignoringNodeIDs(); // this stream was previously not ignored by the listener and we have some newly ignored streams // check now if it is one of the ignored streams and flag it as such if (stream.ignoredByListener) { stream.ignoredByListener = !contains(nodesUnignoredByListener, stream.nodeStreamID.nodeID); } else { stream.ignoredByListener = contains(nodesIgnoredByListener, stream.nodeStreamID.nodeID); } if (stream.ignoringListener) { stream.ignoringListener = !contains(nodesUnignoringListener, stream.nodeStreamID.nodeID); } else { stream.ignoringListener = contains(nodesIgnoringListener, stream.nodeStreamID.nodeID); } bool listenerIsAdmin = listenerData.getRequestsDomainListData() && listener.getCanKick(); if (stream.ignoredByListener || (stream.ignoringListener && !listenerIsAdmin)) { return true; } if (!listenerData.getSoloedNodes().empty()) { return !contains(listenerData.getSoloedNodes(), stream.nodeStreamID.nodeID); } bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() || stream.positionalStream->isIgnoreBoxEnabled()); if (shouldCheckIgnoreBox && listenerAudioStream.getIgnoreBox().touches(stream.positionalStream->getIgnoreBox())) { return true; } return false; };
bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) { AvatarAudioStream* listenerAudioStream = static_cast<AudioMixerClientData*>(listener->getLinkedData())->getAvatarAudioStream(); AudioMixerClientData* listenerData = static_cast<AudioMixerClientData*>(listener->getLinkedData()); // zero out the mix for this listener memset(_mixSamples, 0, sizeof(_mixSamples)); bool isThrottling = _numToRetain != -1; bool isSoloing = !listenerData->getSoloedNodes().empty(); auto& streams = listenerData->getStreams(); addStreams(*listener, *listenerData); // Process skipped streams erase_if(streams.skipped, [&](MixableStream& stream) { if (shouldBeRemoved(stream, _sharedData)) { return true; } if (!shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) { if (shouldBeInactive(stream)) { streams.inactive.push_back(move(stream)); ++stats.skippedToInactive; } else { streams.active.push_back(move(stream)); ++stats.skippedToActive; } return true; } if (!isThrottling) { updateHRTFParameters(stream, *listenerAudioStream, listenerData->getMasterAvatarGain()); } return false; }); // Process inactive streams erase_if(streams.inactive, [&](MixableStream& stream) { if (shouldBeRemoved(stream, _sharedData)) { return true; } if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) { streams.skipped.push_back(move(stream)); ++stats.inactiveToSkipped; return true; } if (!shouldBeInactive(stream)) { streams.active.push_back(move(stream)); ++stats.inactiveToActive; return true; } if (!isThrottling) { updateHRTFParameters(stream, *listenerAudioStream, listenerData->getMasterAvatarGain()); } return false; }); // Process active streams erase_if(streams.active, [&](MixableStream& stream) { if (shouldBeRemoved(stream, _sharedData)) { return true; } if (isThrottling) { // we're throttling, so we need to update the approximate volume for any un-skipped streams // unless this is simply for an echo (in which case the approx volume is 1.0) stream.approximateVolume = approximateVolume(stream, listenerAudioStream); } else { if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) { addStream(stream, *listenerAudioStream, 0.0f, isSoloing); streams.skipped.push_back(move(stream)); ++stats.activeToSkipped; return true; } addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), isSoloing); if (shouldBeInactive(stream)) { // To reduce artifacts we still call render to flush the HRTF for every silent // sources on the first frame where the source becomes silent // this ensures the correct tail from last mixed block streams.inactive.push_back(move(stream)); ++stats.activeToInactive; return true; } } return false; }); if (isThrottling) { // since we're throttling, we need to partition the mixable into throttled and unthrottled streams int numToRetain = min(_numToRetain, (int)streams.active.size()); // Make sure we don't overflow auto throttlePoint = begin(streams.active) + numToRetain; std::nth_element(streams.active.begin(), throttlePoint, streams.active.end(), [](const auto& a, const auto& b) { return a.approximateVolume > b.approximateVolume; }); SegmentedEraseIf<MixableStreamsVector> erase(streams.active); erase.iterateTo(throttlePoint, [&](MixableStream& stream) { if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) { resetHRTFState(stream); streams.skipped.push_back(move(stream)); ++stats.activeToSkipped; return true; } addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), isSoloing); if (shouldBeInactive(stream)) { // To reduce artifacts we still call render to flush the HRTF for every silent // sources on the first frame where the source becomes silent // this ensures the correct tail from last mixed block streams.inactive.push_back(move(stream)); ++stats.activeToInactive; return true; } return false; }); erase.iterateTo(end(streams.active), [&](MixableStream& stream) { // To reduce artifacts we reset the HRTF state for every throttled // sources on the first frame where the source becomes throttled // this ensures at least remove the tail from last mixed block // preventing excessive artifacts on the next first block resetHRTFState(stream); if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) { streams.skipped.push_back(move(stream)); ++stats.activeToSkipped; return true; } if (shouldBeInactive(stream)) { streams.inactive.push_back(move(stream)); ++stats.activeToInactive; return true; } return false; }); } stats.skipped += (int)streams.skipped.size(); stats.inactive += (int)streams.inactive.size(); stats.active += (int)streams.active.size(); // clear the newly ignored, un-ignored, ignoring, and un-ignoring streams now that we've processed them listenerData->clearStagedIgnoreChanges(); #ifdef HIFI_AUDIO_MIXER_DEBUG auto mixEnd = p_high_resolution_clock::now(); auto mixTime = std::chrono::duration_cast<std::chrono::nanoseconds>(mixEnd - mixStart); stats.mixTime += mixTime.count(); #endif // check for silent audio before limiting // limiting uses a dither and can only guarantee abs(sample) <= 1 bool hasAudio = false; for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; ++i) { if (_mixSamples[i] != 0.0f) { hasAudio = true; break; } } // use the per listener AudioLimiter to render the mixed data listenerData->audioLimiter.render(_mixSamples, _bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); return hasAudio; }