/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MediaConduitInterface.h" #include "nsTArray.h" #include "mozilla/Assertions.h" #include "MainThreadUtils.h" #include "SystemTime.h" #include "system_wrappers/include/clock.h" namespace mozilla { void MediaSessionConduit::GetRtpSources( nsTArray& outSources) const { MOZ_ASSERT(NS_IsMainThread()); if (mSourcesUpdateNeeded) { UpdateRtpSources(GetUpstreamRtpSources()); OnSourcesUpdated(); } outSources.Clear(); for (auto& [key, entry] : mSourcesCache) { (void)key; outSources.AppendElement(entry); } struct TimestampComparator { bool LessThan(const dom::RTCRtpSourceEntry& aLhs, const dom::RTCRtpSourceEntry& aRhs) const { // Sort descending! return aLhs.mTimestamp > aRhs.mTimestamp; } bool Equals(const dom::RTCRtpSourceEntry& aLhs, const dom::RTCRtpSourceEntry& aRhs) const { return aLhs.mTimestamp == aRhs.mTimestamp; } }; // *sigh* We have to re-sort this by JS timestamp; we can run into cases // where the libwebrtc timestamps are not in exactly the same order as JS // timestamps due to clock differences (wibbly-wobbly, timey-wimey stuff) outSources.Sort(TimestampComparator()); } static double rtpToDomAudioLevel(uint8_t aAudioLevel) { if (aAudioLevel == 127) { // Spec indicates that a value of 127 should be set to 0 return 0; } // All other values are calculated as 10^(-rfc_level/20) return std::pow(10, -aAudioLevel / 20.0); } void MediaSessionConduit::UpdateRtpSources( const std::vector& aSources) const { MOZ_ASSERT(NS_IsMainThread()); // Empty out the cache; we'll copy things back as needed auto cache = std::move(mSourcesCache); for (const auto& source : aSources) { SourceKey key(source); auto it = cache.find(key); if (it != cache.end()) { // This source entry was already in the cache, and should continue to be // present in exactly the same form as before. This means we do _not_ // want to perform the timestamp adjustment again, since it might yield a // slightly different result. This is why we copy this entry from the old // cache instead of simply rebuilding it, and is also why we key the // cache based on timestamp (keying the cache based on timestamp also // gets us the ordering we want, conveniently). mSourcesCache[key] = it->second; continue; } // This is something we did not already have in the cache. dom::RTCRtpSourceEntry domEntry; domEntry.mSource = source.source_id(); switch (source.source_type()) { case webrtc::RtpSourceType::SSRC: domEntry.mSourceType = dom::RTCRtpSourceEntryType::Synchronization; break; case webrtc::RtpSourceType::CSRC: domEntry.mSourceType = dom::RTCRtpSourceEntryType::Contributing; break; default: MOZ_CRASH("Unexpected RTCRtpSourceEntryType"); } if (source.audio_level()) { domEntry.mAudioLevel.Construct(rtpToDomAudioLevel(*source.audio_level())); } // These timestamps are always **rounded** to milliseconds. That means they // can jump up to half a millisecond into the future. We compensate for that // here so that things seem consistent to js. domEntry.mTimestamp = dom::RTCStatsTimestamp::FromRealtime( GetTimestampMaker(), webrtc::Timestamp::Millis(source.timestamp_ms()) - webrtc::TimeDelta::Micros(500)) .ToDom(); domEntry.mRtpTimestamp = source.rtp_timestamp(); mSourcesCache[key] = domEntry; } } void MediaSessionConduit::OnSourcesUpdated() const { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mSourcesUpdateNeeded); mSourcesUpdateNeeded = false; // Reset the updateNeeded flag and clear the cache in a direct task, i.e., // as soon as the current task has finished. AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask( NS_NewRunnableFunction( __func__, [this, self = RefPtr(this)] { mSourcesUpdateNeeded = true; mSourcesCache.clear(); })); } void MediaSessionConduit::InsertAudioLevelForContributingSource( const uint32_t aCsrcSource, const int64_t aTimestamp, const uint32_t aRtpTimestamp, const bool aHasAudioLevel, const uint8_t aAudioLevel) { MOZ_ASSERT(NS_IsMainThread()); if (mSourcesUpdateNeeded) { OnSourcesUpdated(); } dom::RTCRtpSourceEntry domEntry; domEntry.mSource = aCsrcSource; domEntry.mSourceType = dom::RTCRtpSourceEntryType::Contributing; domEntry.mTimestamp = aTimestamp; domEntry.mRtpTimestamp = aRtpTimestamp; if (aHasAudioLevel) { domEntry.mAudioLevel.Construct(rtpToDomAudioLevel(aAudioLevel)); } auto now = GetTimestampMaker().GetNow(); webrtc::Timestamp convertedTimestamp = now.ToRealtime() - webrtc::TimeDelta::Millis(now.ToDom() - aTimestamp); SourceKey key(convertedTimestamp.ms(), aCsrcSource); mSourcesCache[key] = domEntry; } } // namespace mozilla