/* 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 "AudioConduit.h" #include "common/browser_logging/CSFLog.h" #include "MediaConduitControl.h" #include "mozilla/media/MediaUtils.h" #include "mozilla/Telemetry.h" #include "transport/runnable_utils.h" #include "transport/SrtpFlow.h" // For SRTP_MAX_EXPANSION #include "WebrtcCallWrapper.h" // libwebrtc includes #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "audio/audio_receive_stream.h" #include "media/base/media_constants.h" // for ntohs #ifdef HAVE_NETINET_IN_H # include #elif defined XP_WIN # include #endif #ifdef MOZ_WIDGET_ANDROID # include "AndroidBridge.h" #endif namespace mozilla { namespace { static const char* acLogTag = "WebrtcAudioSessionConduit"; #ifdef LOGTAG # undef LOGTAG #endif #define LOGTAG acLogTag using namespace cricket; using LocalDirection = MediaSessionConduitLocalDirection; const char kCodecParamCbr[] = "cbr"; } // namespace /** * Factory Method for AudioConduit */ RefPtr AudioSessionConduit::Create( RefPtr aCall, nsCOMPtr aStsThread) { CSFLogDebug(LOGTAG, "%s ", __FUNCTION__); MOZ_ASSERT(NS_IsMainThread()); return MakeRefPtr(std::move(aCall), std::move(aStsThread)); } #define INIT_MIRROR(name, val) \ name(aCallThread, val, "WebrtcAudioConduit::Control::" #name " (Mirror)") WebrtcAudioConduit::Control::Control(const RefPtr& aCallThread) : INIT_MIRROR(mReceiving, false), INIT_MIRROR(mTransmitting, false), INIT_MIRROR(mLocalSsrcs, Ssrcs()), INIT_MIRROR(mLocalCname, std::string()), INIT_MIRROR(mMid, std::string()), INIT_MIRROR(mRemoteSsrc, 0), INIT_MIRROR(mSyncGroup, std::string()), INIT_MIRROR(mLocalRecvRtpExtensions, RtpExtList()), INIT_MIRROR(mLocalSendRtpExtensions, RtpExtList()), INIT_MIRROR(mSendCodec, Nothing()), INIT_MIRROR(mRecvCodecs, std::vector()) {} #undef INIT_MIRROR RefPtr WebrtcAudioConduit::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); return InvokeAsync(mCallThread, "WebrtcAudioConduit::Shutdown (main thread)", [this, self = RefPtr(this)] { mControl.mReceiving.DisconnectIfConnected(); mControl.mTransmitting.DisconnectIfConnected(); mControl.mLocalSsrcs.DisconnectIfConnected(); mControl.mLocalCname.DisconnectIfConnected(); mControl.mMid.DisconnectIfConnected(); mControl.mRemoteSsrc.DisconnectIfConnected(); mControl.mSyncGroup.DisconnectIfConnected(); mControl.mLocalRecvRtpExtensions.DisconnectIfConnected(); mControl.mLocalSendRtpExtensions.DisconnectIfConnected(); mControl.mSendCodec.DisconnectIfConnected(); mControl.mRecvCodecs.DisconnectIfConnected(); mControl.mOnDtmfEventListener.DisconnectIfExists(); mWatchManager.Shutdown(); { AutoWriteLock lock(mLock); DeleteSendStream(); DeleteRecvStream(); } return GenericPromise::CreateAndResolve( true, "WebrtcAudioConduit::Shutdown (call thread)"); }); } WebrtcAudioConduit::WebrtcAudioConduit( RefPtr aCall, nsCOMPtr aStsThread) : mCall(std::move(aCall)), mSendTransport(this), mRecvTransport(this), mRecvStreamConfig(), mRecvStream(nullptr), mSendStreamConfig(&mSendTransport), mSendStream(nullptr), mSendStreamRunning(false), mRecvStreamRunning(false), mDtmfEnabled(false), mLock("WebrtcAudioConduit::mLock"), mCallThread(std::move(mCall->mCallThread)), mStsThread(std::move(aStsThread)), mControl(mCall->mCallThread), mWatchManager(this, mCall->mCallThread) { mRecvStreamConfig.rtcp_send_transport = &mRecvTransport; mRecvStreamConfig.rtp.rtcp_event_observer = this; } /** * Destruction defines for our super-classes */ WebrtcAudioConduit::~WebrtcAudioConduit() { CSFLogDebug(LOGTAG, "%s ", __FUNCTION__); MOZ_ASSERT(!mSendStream && !mRecvStream, "Call DeleteStreams prior to ~WebrtcAudioConduit."); } #define CONNECT(aCanonical, aMirror) \ do { \ (aMirror).Connect(aCanonical); \ mWatchManager.Watch(aMirror, &WebrtcAudioConduit::OnControlConfigChange); \ } while (0) void WebrtcAudioConduit::InitControl(AudioConduitControlInterface* aControl) { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); CONNECT(aControl->CanonicalReceiving(), mControl.mReceiving); CONNECT(aControl->CanonicalTransmitting(), mControl.mTransmitting); CONNECT(aControl->CanonicalLocalSsrcs(), mControl.mLocalSsrcs); CONNECT(aControl->CanonicalLocalCname(), mControl.mLocalCname); CONNECT(aControl->CanonicalMid(), mControl.mMid); CONNECT(aControl->CanonicalRemoteSsrc(), mControl.mRemoteSsrc); CONNECT(aControl->CanonicalSyncGroup(), mControl.mSyncGroup); CONNECT(aControl->CanonicalLocalRecvRtpExtensions(), mControl.mLocalRecvRtpExtensions); CONNECT(aControl->CanonicalLocalSendRtpExtensions(), mControl.mLocalSendRtpExtensions); CONNECT(aControl->CanonicalAudioSendCodec(), mControl.mSendCodec); CONNECT(aControl->CanonicalAudioRecvCodecs(), mControl.mRecvCodecs); mControl.mOnDtmfEventListener = aControl->OnDtmfEvent().Connect( mCall->mCallThread, this, &WebrtcAudioConduit::OnDtmfEvent); } #undef CONNECT void WebrtcAudioConduit::OnDtmfEvent(const DtmfEvent& aEvent) { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); MOZ_ASSERT(mSendStream); MOZ_ASSERT(mDtmfEnabled); mSendStream->SendTelephoneEvent(aEvent.mPayloadType, aEvent.mPayloadFrequency, aEvent.mEventCode, aEvent.mLengthMs); } void WebrtcAudioConduit::OnControlConfigChange() { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); bool recvStreamReconfigureNeeded = false; bool sendStreamReconfigureNeeded = false; bool recvStreamRecreationNeeded = false; bool sendStreamRecreationNeeded = false; if (!mControl.mLocalSsrcs.Ref().empty()) { if (mControl.mLocalSsrcs.Ref()[0] != mSendStreamConfig.rtp.ssrc) { sendStreamRecreationNeeded = true; // For now... recvStreamRecreationNeeded = true; } mRecvStreamConfig.rtp.local_ssrc = mControl.mLocalSsrcs.Ref()[0]; mSendStreamConfig.rtp.ssrc = mControl.mLocalSsrcs.Ref()[0]; // In the future we can do this instead of recreating the recv stream: // if (mRecvStream) { // mCall->Call()->OnLocalSsrcUpdated(mRecvStream, // mControl.mLocalSsrcs.Ref()[0]); // } } if (mControl.mLocalCname.Ref() != mSendStreamConfig.rtp.c_name) { mSendStreamConfig.rtp.c_name = mControl.mLocalCname.Ref(); sendStreamReconfigureNeeded = true; } if (mControl.mMid.Ref() != mSendStreamConfig.rtp.mid) { mSendStreamConfig.rtp.mid = mControl.mMid.Ref(); sendStreamReconfigureNeeded = true; } if (mControl.mRemoteSsrc.Ref() != mControl.mConfiguredRemoteSsrc) { mRecvStreamConfig.rtp.remote_ssrc = mControl.mConfiguredRemoteSsrc = mControl.mRemoteSsrc.Ref(); recvStreamRecreationNeeded = true; } if (mControl.mSyncGroup.Ref() != mRecvStreamConfig.sync_group) { mRecvStreamConfig.sync_group = mControl.mSyncGroup.Ref(); // For now... recvStreamRecreationNeeded = true; // In the future we can do this instead of recreating the recv stream: // if (mRecvStream) { // mCall->Call()->OnUpdateSyncGroup(mRecvStream, // mRecvStreamConfig.sync_group); // } } if (auto filteredExtensions = FilterExtensions( LocalDirection::kRecv, mControl.mLocalRecvRtpExtensions); filteredExtensions != mRecvStreamConfig.rtp.extensions) { mRecvStreamConfig.rtp.extensions = std::move(filteredExtensions); // For now... recvStreamRecreationNeeded = true; // In the future we can do this instead of recreating the recv stream: // if (mRecvStream) { // mRecvStream->SetRtpExtensions(mRecvStreamConfig.rtp.extensions); //} } if (auto filteredExtensions = FilterExtensions( LocalDirection::kSend, mControl.mLocalSendRtpExtensions); filteredExtensions != mSendStreamConfig.rtp.extensions) { // At the very least, we need a reconfigure. Recreation needed if the // extmap for any extension has changed, but not for adding/removing // extensions. sendStreamReconfigureNeeded = true; for (const auto& newExt : filteredExtensions) { if (sendStreamRecreationNeeded) { break; } for (const auto& oldExt : mSendStreamConfig.rtp.extensions) { if (newExt.uri == oldExt.uri) { if (newExt.id != oldExt.id) { sendStreamRecreationNeeded = true; } // We're done handling newExt, one way or another break; } } } mSendStreamConfig.rtp.extensions = std::move(filteredExtensions); } mControl.mSendCodec.Ref().apply([&](const auto& aConfig) { if (mControl.mConfiguredSendCodec != mControl.mSendCodec.Ref()) { mControl.mConfiguredSendCodec = mControl.mSendCodec; if (ValidateCodecConfig(aConfig, true) == kMediaConduitNoError) { mSendStreamConfig.encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory(); webrtc::AudioSendStream::Config::SendCodecSpec spec( aConfig.mType, CodecConfigToLibwebrtcFormat(aConfig)); mSendStreamConfig.send_codec_spec = spec; mDtmfEnabled = aConfig.mDtmfEnabled; sendStreamReconfigureNeeded = true; } } }); if (mControl.mConfiguredRecvCodecs != mControl.mRecvCodecs.Ref()) { mControl.mConfiguredRecvCodecs = mControl.mRecvCodecs; mRecvStreamConfig.decoder_factory = mCall->mAudioDecoderFactory; mRecvStreamConfig.decoder_map.clear(); for (const auto& codec : mControl.mRecvCodecs.Ref()) { if (ValidateCodecConfig(codec, false) != kMediaConduitNoError) { continue; } mRecvStreamConfig.decoder_map.emplace( codec.mType, CodecConfigToLibwebrtcFormat(codec)); } recvStreamReconfigureNeeded = true; } if (!recvStreamReconfigureNeeded && !sendStreamReconfigureNeeded && !recvStreamRecreationNeeded && !sendStreamRecreationNeeded && mControl.mReceiving == mRecvStreamRunning && mControl.mTransmitting == mSendStreamRunning) { // No changes applied -- no need to lock. return; } if (recvStreamRecreationNeeded) { recvStreamReconfigureNeeded = false; } if (sendStreamRecreationNeeded) { sendStreamReconfigureNeeded = false; } { AutoWriteLock lock(mLock); // Recreate/Stop/Start streams as needed. if (recvStreamRecreationNeeded) { DeleteRecvStream(); } if (mControl.mReceiving) { CreateRecvStream(); } if (sendStreamRecreationNeeded) { DeleteSendStream(); } if (mControl.mTransmitting) { CreateSendStream(); } } // We make sure to not hold the lock while stopping/starting/reconfiguring // streams, so as to not cause deadlocks. These methods can cause our platform // codecs to dispatch sync runnables to main, and main may grab the lock. if (mRecvStream && recvStreamReconfigureNeeded) { MOZ_ASSERT(!recvStreamRecreationNeeded); mRecvStream->SetDecoderMap(mRecvStreamConfig.decoder_map); } if (mSendStream && sendStreamReconfigureNeeded) { MOZ_ASSERT(!sendStreamRecreationNeeded); // TODO: Pass a callback here, so we can react to RTCErrors thrown by // libwebrtc. mSendStream->Reconfigure(mSendStreamConfig, nullptr); } if (!mControl.mReceiving) { StopReceiving(); } if (!mControl.mTransmitting) { StopTransmitting(); } if (mControl.mReceiving) { StartReceiving(); } if (mControl.mTransmitting) { StartTransmitting(); } } std::vector WebrtcAudioConduit::GetLocalSSRCs() const { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); return std::vector(1, mRecvStreamConfig.rtp.local_ssrc); } bool WebrtcAudioConduit::OverrideRemoteSSRC(uint32_t aSsrc) { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); if (mRecvStreamConfig.rtp.remote_ssrc == aSsrc) { return true; } mRecvStreamConfig.rtp.remote_ssrc = aSsrc; const bool wasReceiving = mRecvStreamRunning; const bool hadRecvStream = mRecvStream; StopReceiving(); if (hadRecvStream) { AutoWriteLock lock(mLock); DeleteRecvStream(); CreateRecvStream(); } if (wasReceiving) { StartReceiving(); } return true; } Maybe WebrtcAudioConduit::GetRemoteSSRC() const { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); // libwebrtc uses 0 to mean a lack of SSRC. That is not to spec. return mRecvStreamConfig.rtp.remote_ssrc == 0 ? Nothing() : Some(mRecvStreamConfig.rtp.remote_ssrc); } Maybe WebrtcAudioConduit::GetReceiverStats() const { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); if (!mRecvStream) { return Nothing(); } return Some(mRecvStream->GetStats()); } Maybe WebrtcAudioConduit::GetSenderStats() const { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); if (!mSendStream) { return Nothing(); } return Some(mSendStream->GetStats()); } Maybe WebrtcAudioConduit::GetCallStats() const { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); if (!mCall->Call()) { return Nothing(); } return Some(mCall->Call()->GetStats()); } void WebrtcAudioConduit::OnRtcpBye() { mRtcpByeEvent.Notify(); } void WebrtcAudioConduit::OnRtcpTimeout() { mRtcpTimeoutEvent.Notify(); } void WebrtcAudioConduit::SetTransportActive(bool aActive) { MOZ_ASSERT(mStsThread->IsOnCurrentThread()); if (mTransportActive == aActive) { return; } // If false, This stops us from sending mTransportActive = aActive; // We queue this because there might be notifications to these listeners // pending, and we don't want to drop them by letting this jump ahead of // those notifications. We move the listeners into the lambda in case the // transport comes back up before we disconnect them. (The Connect calls // happen in MediaPipeline) // We retain a strong reference to ourself, because the listeners are holding // a non-refcounted reference to us, and moving them into the lambda could // conceivably allow them to outlive us. if (!aActive) { MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction( __func__, [self = RefPtr(this), recvRtpListener = std::move(mReceiverRtpEventListener), recvRtcpListener = std::move(mReceiverRtcpEventListener), sendRtcpListener = std::move(mSenderRtcpEventListener)]() mutable { recvRtpListener.DisconnectIfExists(); recvRtcpListener.DisconnectIfExists(); sendRtcpListener.DisconnectIfExists(); }))); } } // AudioSessionConduit Implementation MediaConduitErrorCode WebrtcAudioConduit::SendAudioFrame( std::unique_ptr frame) { CSFLogDebug(LOGTAG, "%s ", __FUNCTION__); // Following checks need to be performed // 1. Non null audio buffer pointer, and // 2. Valid sample rate, and // 3. Appropriate Sample Length for 10 ms audio-frame. This represents the // block size used upstream for processing. // Ex: for 16000 sample rate , valid block-length is 160. // Similarly for 32000 sample rate, valid block length is 320. if (!frame->data() || (IsSamplingFreqSupported(frame->sample_rate_hz()) == false) || ((frame->samples_per_channel() % (frame->sample_rate_hz() / 100) != 0))) { CSFLogError(LOGTAG, "%s Invalid Parameters ", __FUNCTION__); MOZ_ASSERT(PR_FALSE); return kMediaConduitMalformedArgument; } // This is the AudioProxyThread, blocking it for a bit is fine. AutoReadLock lock(mLock); if (!mSendStreamRunning) { CSFLogError(LOGTAG, "%s Engine not transmitting ", __FUNCTION__); return kMediaConduitSessionNotInited; } mSendStream->SendAudioData(std::move(frame)); return kMediaConduitNoError; } MediaConduitErrorCode WebrtcAudioConduit::GetAudioFrame( int32_t samplingFreqHz, webrtc::AudioFrame* frame) { CSFLogDebug(LOGTAG, "%s ", __FUNCTION__); // validate params if (!frame) { CSFLogError(LOGTAG, "%s Null Audio Buffer Pointer", __FUNCTION__); MOZ_ASSERT(PR_FALSE); return kMediaConduitMalformedArgument; } // Validate sample length if (GetNum10msSamplesForFrequency(samplingFreqHz) == 0) { CSFLogError(LOGTAG, "%s Invalid Sampling Frequency ", __FUNCTION__); MOZ_ASSERT(PR_FALSE); return kMediaConduitMalformedArgument; } // If the lock is taken, skip this chunk to avoid blocking the audio thread. AutoTryReadLock tryLock(mLock); if (!tryLock) { CSFLogError(LOGTAG, "%s Conduit going through negotiation ", __FUNCTION__); return kMediaConduitPlayoutError; } // Conduit should have reception enabled before we ask for decoded // samples if (!mRecvStreamRunning) { CSFLogError(LOGTAG, "%s Engine not Receiving ", __FUNCTION__); return kMediaConduitSessionNotInited; } // Unfortunate to have to cast to an internal class, but that looks like the // only way short of interfacing with a layer above (which mixes all streams, // which we don't want) or a layer below (which we try to avoid because it is // less stable). auto info = static_cast(mRecvStream) ->GetAudioFrameWithInfo(samplingFreqHz, frame); if (info == webrtc::AudioMixer::Source::AudioFrameInfo::kError) { CSFLogError(LOGTAG, "%s Getting audio frame failed", __FUNCTION__); return kMediaConduitPlayoutError; } CSFLogDebug(LOGTAG, "%s Got %zu channels of %zu samples", __FUNCTION__, frame->num_channels(), frame->samples_per_channel()); return kMediaConduitNoError; } // Transport Layer Callbacks void WebrtcAudioConduit::OnRtpReceived(webrtc::RtpPacketReceived&& aPacket, webrtc::RTPHeader&& aHeader) { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); if (mAllowSsrcChange && mRecvStreamConfig.rtp.remote_ssrc != aHeader.ssrc) { CSFLogDebug(LOGTAG, "%s: switching from SSRC %u to %u", __FUNCTION__, mRecvStreamConfig.rtp.remote_ssrc, aHeader.ssrc); OverrideRemoteSSRC(aHeader.ssrc); } CSFLogVerbose(LOGTAG, "%s: seq# %u, Len %zu, SSRC %u (0x%x) ", __FUNCTION__, aPacket.SequenceNumber(), aPacket.size(), aPacket.Ssrc(), aPacket.Ssrc()); mRtpPacketEvent.Notify(); if (mCall->Call()) { mCall->Call()->Receiver()->DeliverRtpPacket( webrtc::MediaType::AUDIO, std::move(aPacket), [self = RefPtr(this)]( const webrtc::RtpPacketReceived& packet) { CSFLogVerbose( LOGTAG, "AudioConduit %p: failed demuxing packet, ssrc: %u seq: %u", self.get(), packet.Ssrc(), packet.SequenceNumber()); return false; }); } } void WebrtcAudioConduit::OnRtcpReceived(MediaPacket&& aPacket) { CSFLogDebug(LOGTAG, "%s", __FUNCTION__); MOZ_ASSERT(mCallThread->IsOnCurrentThread()); if (mCall->Call()) { mCall->Call()->Receiver()->DeliverRtcpPacket( rtc::CopyOnWriteBuffer(aPacket.data(), aPacket.len())); } } Maybe WebrtcAudioConduit::RtpSendBaseSeqFor(uint32_t aSsrc) const { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); auto it = mRtpSendBaseSeqs.find(aSsrc); if (it == mRtpSendBaseSeqs.end()) { return Nothing(); } return Some(it->second); } const dom::RTCStatsTimestampMaker& WebrtcAudioConduit::GetTimestampMaker() const { return mCall->GetTimestampMaker(); } void WebrtcAudioConduit::StopTransmitting() { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread()); if (!mSendStreamRunning) { return; } if (mSendStream) { CSFLogDebug(LOGTAG, "%s Stopping send stream", __FUNCTION__); mSendStream->Stop(); } mSendStreamRunning = false; } void WebrtcAudioConduit::StartTransmitting() { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); MOZ_ASSERT(mSendStream); MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread()); if (mSendStreamRunning) { return; } CSFLogDebug(LOGTAG, "%s Starting send stream", __FUNCTION__); mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::AUDIO, webrtc::kNetworkUp); mSendStream->Start(); mSendStreamRunning = true; } void WebrtcAudioConduit::StopReceiving() { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread()); if (!mRecvStreamRunning) { return; } if (mRecvStream) { CSFLogDebug(LOGTAG, "%s Stopping recv stream", __FUNCTION__); mRecvStream->Stop(); } mRecvStreamRunning = false; } void WebrtcAudioConduit::StartReceiving() { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); MOZ_ASSERT(mRecvStream); MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread()); if (mRecvStreamRunning) { return; } CSFLogDebug(LOGTAG, "%s Starting receive stream (SSRC %u (0x%x))", __FUNCTION__, mRecvStreamConfig.rtp.remote_ssrc, mRecvStreamConfig.rtp.remote_ssrc); mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::AUDIO, webrtc::kNetworkUp); mRecvStream->Start(); mRecvStreamRunning = true; } bool WebrtcAudioConduit::SendRtp(const uint8_t* aData, size_t aLength, const webrtc::PacketOptions& aOptions) { MOZ_ASSERT(aLength >= 12); const uint16_t seqno = ntohs(*((uint16_t*)&aData[2])); const uint32_t ssrc = ntohl(*((uint32_t*)&aData[8])); CSFLogVerbose( LOGTAG, "AudioConduit %p: Sending RTP Packet seq# %u, len %zu, SSRC %u (0x%x)", this, seqno, aLength, ssrc, ssrc); if (!mTransportActive) { CSFLogError(LOGTAG, "AudioConduit %p: RTP Packet Send Failed ", this); return false; } MediaPacket packet; packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION); packet.SetType(MediaPacket::RTP); mSenderRtpSendEvent.Notify(std::move(packet)); // Parse the sequence number of the first rtp packet as base_seq. const auto inserted = mRtpSendBaseSeqs_n.insert({ssrc, seqno}).second; if (inserted || aOptions.packet_id >= 0) { int64_t now_ms = PR_Now() / 1000; MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction( __func__, [this, self = RefPtr(this), packet_id = aOptions.packet_id, now_ms, ssrc, seqno] { mRtpSendBaseSeqs.insert({ssrc, seqno}); if (packet_id >= 0) { if (mCall->Call()) { // TODO: This notification should ideally happen after the // transport layer has sent the packet on the wire. mCall->Call()->OnSentPacket({packet_id, now_ms}); } } }))); } return true; } bool WebrtcAudioConduit::SendSenderRtcp(const uint8_t* aData, size_t aLength) { CSFLogVerbose( LOGTAG, "AudioConduit %p: Sending RTCP SR Packet, len %zu, SSRC %u (0x%x)", this, aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])), (uint32_t)ntohl(*((uint32_t*)&aData[4]))); if (!mTransportActive) { CSFLogError(LOGTAG, "%s RTCP SR Packet Send Failed ", __FUNCTION__); return false; } MediaPacket packet; packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION); packet.SetType(MediaPacket::RTCP); mSenderRtcpSendEvent.Notify(std::move(packet)); return true; } bool WebrtcAudioConduit::SendReceiverRtcp(const uint8_t* aData, size_t aLength) { CSFLogVerbose( LOGTAG, "AudioConduit %p: Sending RTCP RR Packet, len %zu, SSRC %u (0x%x)", this, aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])), (uint32_t)ntohl(*((uint32_t*)&aData[4]))); if (!mTransportActive) { CSFLogError(LOGTAG, "AudioConduit %p: RTCP RR Packet Send Failed", this); return false; } MediaPacket packet; packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION); packet.SetType(MediaPacket::RTCP); mReceiverRtcpSendEvent.Notify(std::move(packet)); return true; } /** * Supported Sampling Frequencies. */ bool WebrtcAudioConduit::IsSamplingFreqSupported(int freq) const { return GetNum10msSamplesForFrequency(freq) != 0; } std::vector WebrtcAudioConduit::GetUpstreamRtpSources() const { MOZ_ASSERT(NS_IsMainThread()); std::vector sources; { AutoReadLock lock(mLock); if (mRecvStream) { sources = mRecvStream->GetSources(); } } return sources; } /* Return block-length of 10 ms audio frame in number of samples */ unsigned int WebrtcAudioConduit::GetNum10msSamplesForFrequency( int samplingFreqHz) const { switch (samplingFreqHz) { case 16000: return 160; // 160 samples case 32000: return 320; // 320 samples case 44100: return 441; // 441 samples case 48000: return 480; // 480 samples default: return 0; // invalid or unsupported } } /** * Perform validation on the codecConfig to be applied. * Verifies if the codec is already applied. */ MediaConduitErrorCode WebrtcAudioConduit::ValidateCodecConfig( const AudioCodecConfig& codecInfo, bool send) { if (codecInfo.mName.empty()) { CSFLogError(LOGTAG, "%s Empty Payload Name ", __FUNCTION__); return kMediaConduitMalformedArgument; } // Only mono or stereo channels supported if ((codecInfo.mChannels != 1) && (codecInfo.mChannels != 2)) { CSFLogError(LOGTAG, "%s Channel Unsupported ", __FUNCTION__); return kMediaConduitMalformedArgument; } return kMediaConduitNoError; } RtpExtList WebrtcAudioConduit::FilterExtensions(LocalDirection aDirection, const RtpExtList& aExtensions) { const bool isSend = aDirection == LocalDirection::kSend; RtpExtList filteredExtensions; for (const auto& extension : aExtensions) { // ssrc-audio-level RTP header extension if (extension.uri == webrtc::RtpExtension::kAudioLevelUri) { filteredExtensions.push_back( webrtc::RtpExtension(extension.uri, extension.id)); } // csrc-audio-level RTP header extension if (extension.uri == webrtc::RtpExtension::kCsrcAudioLevelsUri) { if (isSend) { continue; } filteredExtensions.push_back( webrtc::RtpExtension(extension.uri, extension.id)); } // MID RTP header extension if (extension.uri == webrtc::RtpExtension::kMidUri) { if (!isSend) { // TODO: recv mid support, see also bug 1727211 continue; } filteredExtensions.push_back( webrtc::RtpExtension(extension.uri, extension.id)); } } return filteredExtensions; } webrtc::SdpAudioFormat WebrtcAudioConduit::CodecConfigToLibwebrtcFormat( const AudioCodecConfig& aConfig) { webrtc::SdpAudioFormat::Parameters parameters; if (aConfig.mName == kOpusCodecName) { if (aConfig.mChannels == 2) { parameters[kCodecParamStereo] = kParamValueTrue; } if (aConfig.mFECEnabled) { parameters[kCodecParamUseInbandFec] = kParamValueTrue; } if (aConfig.mDTXEnabled) { parameters[kCodecParamUseDtx] = kParamValueTrue; } if (aConfig.mMaxPlaybackRate) { parameters[kCodecParamMaxPlaybackRate] = std::to_string(aConfig.mMaxPlaybackRate); } if (aConfig.mMaxAverageBitrate) { parameters[kCodecParamMaxAverageBitrate] = std::to_string(aConfig.mMaxAverageBitrate); } if (aConfig.mFrameSizeMs) { parameters[kCodecParamPTime] = std::to_string(aConfig.mFrameSizeMs); } if (aConfig.mMinFrameSizeMs) { parameters[kCodecParamMinPTime] = std::to_string(aConfig.mMinFrameSizeMs); } if (aConfig.mMaxFrameSizeMs) { parameters[kCodecParamMaxPTime] = std::to_string(aConfig.mMaxFrameSizeMs); } if (aConfig.mCbrEnabled) { parameters[kCodecParamCbr] = kParamValueTrue; } } return webrtc::SdpAudioFormat(aConfig.mName, aConfig.mFreq, aConfig.mChannels, parameters); } void WebrtcAudioConduit::DeleteSendStream() { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); if (!mSendStream) { return; } mCall->Call()->DestroyAudioSendStream(mSendStream); mSendStreamRunning = false; mSendStream = nullptr; // Reset base_seqs in case ssrcs get re-used. mRtpSendBaseSeqs.clear(); } void WebrtcAudioConduit::CreateSendStream() { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); if (mSendStream) { return; } mSendStream = mCall->Call()->CreateAudioSendStream(mSendStreamConfig); } void WebrtcAudioConduit::DeleteRecvStream() { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); if (!mRecvStream) { return; } mCall->Call()->DestroyAudioReceiveStream(mRecvStream); mRecvStreamRunning = false; mRecvStream = nullptr; } void WebrtcAudioConduit::CreateRecvStream() { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); if (mRecvStream) { return; } mRecvStream = mCall->Call()->CreateAudioReceiveStream(mRecvStreamConfig); // Ensure that we set the jitter buffer target on this stream. mRecvStream->SetBaseMinimumPlayoutDelayMs(mJitterBufferTargetMs); } void WebrtcAudioConduit::SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) { MOZ_RELEASE_ASSERT(aTargetMs <= std::numeric_limits::max()); MOZ_RELEASE_ASSERT(aTargetMs >= 0); MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction( __func__, [this, self = RefPtr(this), targetMs = aTargetMs] { mJitterBufferTargetMs = static_cast(targetMs); if (mRecvStream) { mRecvStream->SetBaseMinimumPlayoutDelayMs(targetMs); } }))); } void WebrtcAudioConduit::DeliverPacket(rtc::CopyOnWriteBuffer packet, PacketType type) { // Currently unused. MOZ_ASSERT(false); } Maybe WebrtcAudioConduit::ActiveSendPayloadType() const { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); auto stats = GetSenderStats(); if (!stats) { return Nothing(); } if (!stats->codec_payload_type) { return Nothing(); } return Some(*stats->codec_payload_type); } Maybe WebrtcAudioConduit::ActiveRecvPayloadType() const { MOZ_ASSERT(mCallThread->IsOnCurrentThread()); auto stats = GetReceiverStats(); if (!stats) { return Nothing(); } if (!stats->codec_payload_type) { return Nothing(); } return Some(*stats->codec_payload_type); } } // namespace mozilla