void AudioMixerSlave::addStreams(Node& listener, AudioMixerClientData& listenerData) { auto& ignoredNodeIDs = listener.getIgnoredNodeIDs(); auto& ignoringNodeIDs = listenerData.getIgnoringNodeIDs(); auto& streams = listenerData.getStreams(); // add data for newly created streams to our vector if (!listenerData.getHasReceivedFirstMix()) { // when this listener is new, we need to fill its added streams object with all available streams std::for_each(_begin, _end, [&](const SharedNodePointer& node) { AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData()); if (nodeData) { for (auto& stream : nodeData->getAudioStreams()) { bool ignoredByListener = contains(ignoredNodeIDs, node->getUUID()); bool ignoringListener = contains(ignoringNodeIDs, node->getUUID()); if (ignoredByListener || ignoringListener) { streams.skipped.emplace_back(node->getUUID(), node->getLocalID(), stream->getStreamIdentifier(), stream.get()); // pre-populate ignored and ignoring flags for this stream streams.skipped.back().ignoredByListener = ignoredByListener; streams.skipped.back().ignoringListener = ignoringListener; } else { streams.active.emplace_back(node->getUUID(), node->getLocalID(), stream->getStreamIdentifier(), stream.get()); } } } }); // flag this listener as having received their first mix so we know we don't need to enumerate all nodes again listenerData.setHasReceivedFirstMix(true); } else { for (const auto& newStream : _sharedData.addedStreams) { bool ignoredByListener = contains(ignoredNodeIDs, newStream.nodeIDStreamID.nodeID); bool ignoringListener = contains(ignoringNodeIDs, newStream.nodeIDStreamID.nodeID); if (ignoredByListener || ignoringListener) { streams.skipped.emplace_back(newStream.nodeIDStreamID, newStream.positionalStream); // pre-populate ignored and ignoring flags for this stream streams.skipped.back().ignoredByListener = ignoredByListener; streams.skipped.back().ignoringListener = ignoringListener; } else { streams.active.emplace_back(newStream.nodeIDStreamID, newStream.positionalStream); } } } }
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; }