michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CSFLog.h" michael@0: #include "nspr.h" michael@0: michael@0: #ifdef HAVE_NETINET_IN_H michael@0: #include michael@0: #elif defined XP_WIN michael@0: #include michael@0: #endif michael@0: michael@0: #include "AudioConduit.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsThreadUtils.h" michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: #include "Latency.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #endif michael@0: michael@0: #include "webrtc/voice_engine/include/voe_errors.h" michael@0: #include "webrtc/system_wrappers/interface/clock.h" michael@0: michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: #include "AndroidJNIWrapper.h" michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: michael@0: static const char* logTag ="WebrtcAudioSessionConduit"; michael@0: michael@0: // 32 bytes is what WebRTC CodecInst expects michael@0: const unsigned int WebrtcAudioConduit::CODEC_PLNAME_SIZE = 32; michael@0: michael@0: /** michael@0: * Factory Method for AudioConduit michael@0: */ michael@0: mozilla::RefPtr AudioSessionConduit::Create(AudioSessionConduit *aOther) michael@0: { michael@0: CSFLogDebug(logTag, "%s ", __FUNCTION__); michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: // unit tests create their own "main thread" michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: #endif michael@0: michael@0: WebrtcAudioConduit* obj = new WebrtcAudioConduit(); michael@0: if(obj->Init(static_cast(aOther)) != kMediaConduitNoError) michael@0: { michael@0: CSFLogError(logTag, "%s AudioConduit Init Failed ", __FUNCTION__); michael@0: delete obj; michael@0: return nullptr; michael@0: } michael@0: CSFLogDebug(logTag, "%s Successfully created AudioConduit ", __FUNCTION__); michael@0: return obj; michael@0: } michael@0: michael@0: /** michael@0: * Destruction defines for our super-classes michael@0: */ michael@0: WebrtcAudioConduit::~WebrtcAudioConduit() michael@0: { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: // unit tests create their own "main thread" michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: #endif michael@0: michael@0: CSFLogDebug(logTag, "%s ", __FUNCTION__); michael@0: for(std::vector::size_type i=0;i < mRecvCodecList.size();i++) michael@0: { michael@0: delete mRecvCodecList[i]; michael@0: } michael@0: delete mCurSendCodecConfig; michael@0: michael@0: // The first one of a pair to be deleted shuts down media for both michael@0: if(mPtrVoEXmedia) michael@0: { michael@0: if (!mShutDown) { michael@0: mPtrVoEXmedia->SetExternalRecordingStatus(false); michael@0: mPtrVoEXmedia->SetExternalPlayoutStatus(false); michael@0: } michael@0: } michael@0: michael@0: //Deal with the transport michael@0: if(mPtrVoENetwork) michael@0: { michael@0: if (!mShutDown) { michael@0: mPtrVoENetwork->DeRegisterExternalTransport(mChannel); michael@0: } michael@0: } michael@0: michael@0: if(mPtrVoEBase) michael@0: { michael@0: if (!mShutDown) { michael@0: mPtrVoEBase->StopPlayout(mChannel); michael@0: mPtrVoEBase->StopSend(mChannel); michael@0: mPtrVoEBase->StopReceive(mChannel); michael@0: mPtrVoEBase->DeleteChannel(mChannel); michael@0: mPtrVoEBase->Terminate(); michael@0: } michael@0: } michael@0: michael@0: if (mOtherDirection) michael@0: { michael@0: // mOtherDirection owns these now! michael@0: mOtherDirection->mOtherDirection = nullptr; michael@0: // let other side we terminated the channel michael@0: mOtherDirection->mShutDown = true; michael@0: mVoiceEngine = nullptr; michael@0: } else { michael@0: // We shouldn't delete the VoiceEngine until all these are released! michael@0: // And we can't use a Scoped ptr, since the order is arbitrary michael@0: mPtrVoENetwork = nullptr; michael@0: mPtrVoEBase = nullptr; michael@0: mPtrVoECodec = nullptr; michael@0: mPtrVoEXmedia = nullptr; michael@0: mPtrVoEProcessing = nullptr; michael@0: mPtrVoEVideoSync = nullptr; michael@0: mPtrVoERTP_RTCP = nullptr; michael@0: mPtrRTP = nullptr; michael@0: michael@0: // only one opener can call Delete. Have it be the last to close. michael@0: if(mVoiceEngine) michael@0: { michael@0: webrtc::VoiceEngine::Delete(mVoiceEngine); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool WebrtcAudioConduit::GetLocalSSRC(unsigned int* ssrc) { michael@0: return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc); michael@0: } michael@0: michael@0: bool WebrtcAudioConduit::GetRemoteSSRC(unsigned int* ssrc) { michael@0: return !mPtrRTP->GetRemoteSSRC(mChannel, *ssrc); michael@0: } michael@0: michael@0: bool WebrtcAudioConduit::GetAVStats(int32_t* jitterBufferDelayMs, michael@0: int32_t* playoutBufferDelayMs, michael@0: int32_t* avSyncOffsetMs) { michael@0: return !mPtrVoEVideoSync->GetDelayEstimate(mChannel, michael@0: jitterBufferDelayMs, michael@0: playoutBufferDelayMs, michael@0: avSyncOffsetMs); michael@0: } michael@0: michael@0: bool WebrtcAudioConduit::GetRTPStats(unsigned int* jitterMs, michael@0: unsigned int* cumulativeLost) { michael@0: unsigned int maxJitterMs = 0; michael@0: unsigned int discardedPackets; michael@0: *jitterMs = 0; michael@0: *cumulativeLost = 0; michael@0: return !mPtrRTP->GetRTPStatistics(mChannel, *jitterMs, maxJitterMs, michael@0: discardedPackets, *cumulativeLost); michael@0: } michael@0: michael@0: DOMHighResTimeStamp michael@0: NTPtoDOMHighResTimeStamp(uint32_t ntpHigh, uint32_t ntpLow) { michael@0: return (uint32_t(ntpHigh - webrtc::kNtpJan1970) + michael@0: double(ntpLow) / webrtc::kMagicNtpFractionalUnit) * 1000; michael@0: } michael@0: michael@0: bool WebrtcAudioConduit::GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, michael@0: uint32_t* jitterMs, michael@0: uint32_t* packetsReceived, michael@0: uint64_t* bytesReceived, michael@0: uint32_t* cumulativeLost, michael@0: int32_t* rttMs) { michael@0: uint32_t ntpHigh, ntpLow; michael@0: uint16_t fractionLost; michael@0: bool result = !mPtrRTP->GetRemoteRTCPReceiverInfo(mChannel, ntpHigh, ntpLow, michael@0: *packetsReceived, michael@0: *bytesReceived, michael@0: *jitterMs, michael@0: fractionLost, michael@0: *cumulativeLost, michael@0: *rttMs); michael@0: if (result) { michael@0: *timestamp = NTPtoDOMHighResTimeStamp(ntpHigh, ntpLow); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: bool WebrtcAudioConduit::GetRTCPSenderReport(DOMHighResTimeStamp* timestamp, michael@0: unsigned int* packetsSent, michael@0: uint64_t* bytesSent) { michael@0: struct webrtc::SenderInfo senderInfo; michael@0: bool result = !mPtrRTP->GetRemoteRTCPSenderInfo(mChannel, &senderInfo); michael@0: if (result) { michael@0: *timestamp = NTPtoDOMHighResTimeStamp(senderInfo.NTP_timestamp_high, michael@0: senderInfo.NTP_timestamp_low); michael@0: *packetsSent = senderInfo.sender_packet_count; michael@0: *bytesSent = senderInfo.sender_octet_count; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /* michael@0: * WebRTCAudioConduit Implementation michael@0: */ michael@0: MediaConduitErrorCode WebrtcAudioConduit::Init(WebrtcAudioConduit *other) michael@0: { michael@0: CSFLogDebug(logTag, "%s this=%p other=%p", __FUNCTION__, this, other); michael@0: michael@0: if (other) { michael@0: MOZ_ASSERT(!other->mOtherDirection); michael@0: other->mOtherDirection = this; michael@0: mOtherDirection = other; michael@0: michael@0: // only one can call ::Create()/GetVoiceEngine() michael@0: MOZ_ASSERT(other->mVoiceEngine); michael@0: mVoiceEngine = other->mVoiceEngine; michael@0: } else { michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: jobject context = jsjni_GetGlobalContextRef(); michael@0: michael@0: // get the JVM michael@0: JavaVM *jvm = jsjni_GetVM(); michael@0: JNIEnv* jenv = jsjni_GetJNIForThread(); michael@0: michael@0: if (webrtc::VoiceEngine::SetAndroidObjects(jvm, jenv, (void*)context) != 0) { michael@0: CSFLogError(logTag, "%s Unable to set Android objects", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: #endif michael@0: michael@0: // Per WebRTC APIs below function calls return nullptr on failure michael@0: if(!(mVoiceEngine = webrtc::VoiceEngine::Create())) michael@0: { michael@0: CSFLogError(logTag, "%s Unable to create voice engine", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: PRLogModuleInfo *logs = GetWebRTCLogInfo(); michael@0: if (!gWebrtcTraceLoggingOn && logs && logs->level > 0) { michael@0: // no need to a critical section or lock here michael@0: gWebrtcTraceLoggingOn = 1; michael@0: michael@0: const char *file = PR_GetEnv("WEBRTC_TRACE_FILE"); michael@0: if (!file) { michael@0: file = "WebRTC.log"; michael@0: } michael@0: CSFLogDebug(logTag, "%s Logging webrtc to %s level %d", __FUNCTION__, michael@0: file, logs->level); michael@0: mVoiceEngine->SetTraceFilter(logs->level); michael@0: mVoiceEngine->SetTraceFile(file); michael@0: } michael@0: } michael@0: michael@0: if(!(mPtrVoEBase = VoEBase::GetInterface(mVoiceEngine))) michael@0: { michael@0: CSFLogError(logTag, "%s Unable to initialize VoEBase", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: if(!(mPtrVoENetwork = VoENetwork::GetInterface(mVoiceEngine))) michael@0: { michael@0: CSFLogError(logTag, "%s Unable to initialize VoENetwork", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: if(!(mPtrVoECodec = VoECodec::GetInterface(mVoiceEngine))) michael@0: { michael@0: CSFLogError(logTag, "%s Unable to initialize VoEBCodec", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: if(!(mPtrVoEProcessing = VoEAudioProcessing::GetInterface(mVoiceEngine))) michael@0: { michael@0: CSFLogError(logTag, "%s Unable to initialize VoEProcessing", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: if(!(mPtrVoEXmedia = VoEExternalMedia::GetInterface(mVoiceEngine))) michael@0: { michael@0: CSFLogError(logTag, "%s Unable to initialize VoEExternalMedia", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: if(!(mPtrVoERTP_RTCP = VoERTP_RTCP::GetInterface(mVoiceEngine))) michael@0: { michael@0: CSFLogError(logTag, "%s Unable to initialize VoERTP_RTCP", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: if(!(mPtrVoEVideoSync = VoEVideoSync::GetInterface(mVoiceEngine))) michael@0: { michael@0: CSFLogError(logTag, "%s Unable to initialize VoEVideoSync", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: if (!(mPtrRTP = webrtc::VoERTP_RTCP::GetInterface(mVoiceEngine))) michael@0: { michael@0: CSFLogError(logTag, "%s Unable to get audio RTP/RTCP interface ", michael@0: __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: if (other) { michael@0: mChannel = other->mChannel; michael@0: } else { michael@0: // init the engine with our audio device layer michael@0: if(mPtrVoEBase->Init() == -1) michael@0: { michael@0: CSFLogError(logTag, "%s VoiceEngine Base Not Initialized", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: if( (mChannel = mPtrVoEBase->CreateChannel()) == -1) michael@0: { michael@0: CSFLogError(logTag, "%s VoiceEngine Channel creation failed",__FUNCTION__); michael@0: return kMediaConduitChannelError; michael@0: } michael@0: michael@0: CSFLogDebug(logTag, "%s Channel Created %d ",__FUNCTION__, mChannel); michael@0: michael@0: if(mPtrVoENetwork->RegisterExternalTransport(mChannel, *this) == -1) michael@0: { michael@0: CSFLogError(logTag, "%s VoiceEngine, External Transport Failed",__FUNCTION__); michael@0: return kMediaConduitTransportRegistrationFail; michael@0: } michael@0: michael@0: if(mPtrVoEXmedia->SetExternalRecordingStatus(true) == -1) michael@0: { michael@0: CSFLogError(logTag, "%s SetExternalRecordingStatus Failed %d",__FUNCTION__, michael@0: mPtrVoEBase->LastError()); michael@0: return kMediaConduitExternalPlayoutError; michael@0: } michael@0: michael@0: if(mPtrVoEXmedia->SetExternalPlayoutStatus(true) == -1) michael@0: { michael@0: CSFLogError(logTag, "%s SetExternalPlayoutStatus Failed %d ",__FUNCTION__, michael@0: mPtrVoEBase->LastError()); michael@0: return kMediaConduitExternalRecordingError; michael@0: } michael@0: CSFLogDebug(logTag , "%s AudioSessionConduit Initialization Done (%p)",__FUNCTION__, this); michael@0: } michael@0: return kMediaConduitNoError; michael@0: } michael@0: michael@0: // AudioSessionConduit Implementation michael@0: MediaConduitErrorCode michael@0: WebrtcAudioConduit::AttachTransport(mozilla::RefPtr aTransport) michael@0: { michael@0: CSFLogDebug(logTag, "%s ", __FUNCTION__); michael@0: michael@0: if(!aTransport) michael@0: { michael@0: CSFLogError(logTag, "%s NULL Transport", __FUNCTION__); michael@0: return kMediaConduitInvalidTransport; michael@0: } michael@0: // set the transport michael@0: mTransport = aTransport; michael@0: return kMediaConduitNoError; michael@0: } michael@0: michael@0: MediaConduitErrorCode michael@0: WebrtcAudioConduit::ConfigureSendMediaCodec(const AudioCodecConfig* codecConfig) michael@0: { michael@0: CSFLogDebug(logTag, "%s ", __FUNCTION__); michael@0: MediaConduitErrorCode condError = kMediaConduitNoError; michael@0: int error = 0;//webrtc engine errors michael@0: webrtc::CodecInst cinst; michael@0: michael@0: //validate codec param michael@0: if((condError = ValidateCodecConfig(codecConfig, true)) != kMediaConduitNoError) michael@0: { michael@0: return condError; michael@0: } michael@0: michael@0: //are we transmitting already, stop and apply the send codec michael@0: if(mEngineTransmitting) michael@0: { michael@0: CSFLogDebug(logTag, "%s Engine Already Sending. Attemping to Stop ", __FUNCTION__); michael@0: if(mPtrVoEBase->StopSend(mChannel) == -1) michael@0: { michael@0: CSFLogError(logTag, "%s StopSend() Failed %d ", __FUNCTION__, michael@0: mPtrVoEBase->LastError()); michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: } michael@0: michael@0: mEngineTransmitting = false; michael@0: michael@0: if(!CodecConfigToWebRTCCodec(codecConfig,cinst)) michael@0: { michael@0: CSFLogError(logTag,"%s CodecConfig to WebRTC Codec Failed ",__FUNCTION__); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: if(mPtrVoECodec->SetSendCodec(mChannel, cinst) == -1) michael@0: { michael@0: error = mPtrVoEBase->LastError(); michael@0: CSFLogError(logTag, "%s SetSendCodec - Invalid Codec %d ",__FUNCTION__, michael@0: error); michael@0: michael@0: if(error == VE_CANNOT_SET_SEND_CODEC || error == VE_CODEC_ERROR) michael@0: { michael@0: CSFLogError(logTag, "%s Invalid Send Codec", __FUNCTION__); michael@0: return kMediaConduitInvalidSendCodec; michael@0: } michael@0: CSFLogError(logTag, "%s SetSendCodec Failed %d ", __FUNCTION__, michael@0: mPtrVoEBase->LastError()); michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: // TEMPORARY - see bug 694814 comment 2 michael@0: nsresult rv; michael@0: nsCOMPtr prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr branch = do_QueryInterface(prefs); michael@0: michael@0: if (branch) { michael@0: branch->GetIntPref("media.peerconnection.capture_delay", &mCaptureDelay); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: //Let's Send Transport State-machine on the Engine michael@0: if(mPtrVoEBase->StartSend(mChannel) == -1) michael@0: { michael@0: error = mPtrVoEBase->LastError(); michael@0: CSFLogError(logTag, "%s StartSend failed %d", __FUNCTION__, error); michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: michael@0: //Copy the applied config for future reference. michael@0: delete mCurSendCodecConfig; michael@0: michael@0: mCurSendCodecConfig = new AudioCodecConfig(codecConfig->mType, michael@0: codecConfig->mName, michael@0: codecConfig->mFreq, michael@0: codecConfig->mPacSize, michael@0: codecConfig->mChannels, michael@0: codecConfig->mRate, michael@0: codecConfig->mLoadManager); michael@0: michael@0: mEngineTransmitting = true; michael@0: return kMediaConduitNoError; michael@0: } michael@0: michael@0: MediaConduitErrorCode michael@0: WebrtcAudioConduit::ConfigureRecvMediaCodecs( michael@0: const std::vector& codecConfigList) michael@0: { michael@0: CSFLogDebug(logTag, "%s ", __FUNCTION__); michael@0: MediaConduitErrorCode condError = kMediaConduitNoError; michael@0: int error = 0; //webrtc engine errors michael@0: bool success = false; michael@0: michael@0: // Are we receiving already? If so, stop receiving and playout michael@0: // since we can't apply new recv codec when the engine is playing. michael@0: if(mEngineReceiving) michael@0: { michael@0: CSFLogDebug(logTag, "%s Engine Already Receiving. Attemping to Stop ", __FUNCTION__); michael@0: // AudioEngine doesn't fail fatally on stopping reception. Ref:voe_errors.h. michael@0: // hence we need not be strict in failing here on errors michael@0: mPtrVoEBase->StopReceive(mChannel); michael@0: CSFLogDebug(logTag, "%s Attemping to Stop playout ", __FUNCTION__); michael@0: if(mPtrVoEBase->StopPlayout(mChannel) == -1) michael@0: { michael@0: if( mPtrVoEBase->LastError() == VE_CANNOT_STOP_PLAYOUT) michael@0: { michael@0: CSFLogDebug(logTag, "%s Stop-Playout Failed %d", __FUNCTION__, mPtrVoEBase->LastError()); michael@0: return kMediaConduitPlayoutError; michael@0: } michael@0: } michael@0: } michael@0: michael@0: mEngineReceiving = false; michael@0: michael@0: if(codecConfigList.empty()) michael@0: { michael@0: CSFLogError(logTag, "%s Zero number of codecs to configure", __FUNCTION__); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: // Try Applying the codecs in the list. michael@0: // We succeed if at least one codec was applied and reception was michael@0: // started successfully. michael@0: for(std::vector::size_type i=0 ;iSetRecPayloadType(mChannel,cinst) == -1) michael@0: { michael@0: error = mPtrVoEBase->LastError(); michael@0: CSFLogError(logTag, "%s SetRecvCodec Failed %d ",__FUNCTION__, error); michael@0: continue; michael@0: } else { michael@0: CSFLogDebug(logTag, "%s Successfully Set RecvCodec %s", __FUNCTION__, michael@0: codecConfigList[i]->mName.c_str()); michael@0: //copy this to local database michael@0: if(CopyCodecToDB(codecConfigList[i])) michael@0: { michael@0: success = true; michael@0: } else { michael@0: CSFLogError(logTag,"%s Unable to updated Codec Database", __FUNCTION__); michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: michael@0: } michael@0: michael@0: } //end for michael@0: michael@0: if(!success) michael@0: { michael@0: CSFLogError(logTag, "%s Setting Receive Codec Failed ", __FUNCTION__); michael@0: return kMediaConduitInvalidReceiveCodec; michael@0: } michael@0: michael@0: //If we are here, atleast one codec should have been set michael@0: if(mPtrVoEBase->StartReceive(mChannel) == -1) michael@0: { michael@0: error = mPtrVoEBase->LastError(); michael@0: CSFLogError(logTag , "%s StartReceive Failed %d ",__FUNCTION__, error); michael@0: if(error == VE_RECV_SOCKET_ERROR) michael@0: { michael@0: return kMediaConduitSocketError; michael@0: } michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: michael@0: michael@0: if(mPtrVoEBase->StartPlayout(mChannel) == -1) michael@0: { michael@0: CSFLogError(logTag, "%s Starting playout Failed", __FUNCTION__); michael@0: return kMediaConduitPlayoutError; michael@0: } michael@0: //we should be good here for setting this. michael@0: mEngineReceiving = true; michael@0: DumpCodecDB(); michael@0: return kMediaConduitNoError; michael@0: } michael@0: MediaConduitErrorCode michael@0: WebrtcAudioConduit::EnableAudioLevelExtension(bool enabled, uint8_t id) michael@0: { michael@0: CSFLogDebug(logTag, "%s %d %d ", __FUNCTION__, enabled, id); michael@0: michael@0: if (mPtrVoERTP_RTCP->SetRTPAudioLevelIndicationStatus(mChannel, enabled, id) == -1) michael@0: { michael@0: CSFLogError(logTag, "%s SetRTPAudioLevelIndicationStatus Failed", __FUNCTION__); michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: michael@0: return kMediaConduitNoError; michael@0: } michael@0: michael@0: MediaConduitErrorCode michael@0: WebrtcAudioConduit::SendAudioFrame(const int16_t audio_data[], michael@0: int32_t lengthSamples, michael@0: int32_t samplingFreqHz, michael@0: int32_t capture_delay) michael@0: { michael@0: CSFLogDebug(logTag, "%s ", __FUNCTION__); michael@0: // Following checks need to be performed michael@0: // 1. Non null audio buffer pointer, michael@0: // 2. invalid sampling frequency - less than 0 or unsupported ones michael@0: // 3. Appropriate Sample Length for 10 ms audio-frame. This represents michael@0: // block size the VoiceEngine feeds into encoder for passed in audio-frame michael@0: // Ex: for 16000 sampling rate , valid block-length is 160 michael@0: // Similarly for 32000 sampling rate, valid block length is 320 michael@0: // We do the check by the verify modular operator below to be zero michael@0: michael@0: if(!audio_data || (lengthSamples <= 0) || michael@0: (IsSamplingFreqSupported(samplingFreqHz) == false) || michael@0: ((lengthSamples % (samplingFreqHz / 100) != 0)) ) michael@0: { michael@0: CSFLogError(logTag, "%s Invalid Parameters ",__FUNCTION__); michael@0: MOZ_ASSERT(PR_FALSE); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: //validate capture time michael@0: if(capture_delay < 0 ) michael@0: { michael@0: CSFLogError(logTag,"%s Invalid Capture Delay ", __FUNCTION__); michael@0: MOZ_ASSERT(PR_FALSE); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: // if transmission is not started .. conduit cannot insert frames michael@0: if(!mEngineTransmitting) michael@0: { michael@0: CSFLogError(logTag, "%s Engine not transmitting ", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) { michael@0: struct Processing insert = { TimeStamp::Now(), 0 }; michael@0: mProcessing.AppendElement(insert); michael@0: } michael@0: #endif michael@0: michael@0: capture_delay = mCaptureDelay; michael@0: //Insert the samples michael@0: if(mPtrVoEXmedia->ExternalRecordingInsertData(audio_data, michael@0: lengthSamples, michael@0: samplingFreqHz, michael@0: capture_delay) == -1) michael@0: { michael@0: int error = mPtrVoEBase->LastError(); michael@0: CSFLogError(logTag, "%s Inserting audio data Failed %d", __FUNCTION__, error); michael@0: if(error == VE_RUNTIME_REC_ERROR) michael@0: { michael@0: return kMediaConduitRecordingError; michael@0: } michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: // we should be good here michael@0: return kMediaConduitNoError; michael@0: } michael@0: michael@0: MediaConduitErrorCode michael@0: WebrtcAudioConduit::GetAudioFrame(int16_t speechData[], michael@0: int32_t samplingFreqHz, michael@0: int32_t capture_delay, michael@0: int& lengthSamples) michael@0: { michael@0: michael@0: CSFLogDebug(logTag, "%s ", __FUNCTION__); michael@0: unsigned int numSamples = 0; michael@0: michael@0: //validate params michael@0: if(!speechData ) michael@0: { michael@0: CSFLogError(logTag,"%s Null Audio Buffer Pointer", __FUNCTION__); michael@0: MOZ_ASSERT(PR_FALSE); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: // Validate sample length michael@0: if((numSamples = GetNum10msSamplesForFrequency(samplingFreqHz)) == 0 ) michael@0: { michael@0: CSFLogError(logTag,"%s Invalid Sampling Frequency ", __FUNCTION__); michael@0: MOZ_ASSERT(PR_FALSE); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: //validate capture time michael@0: if(capture_delay < 0 ) michael@0: { michael@0: CSFLogError(logTag,"%s Invalid Capture Delay ", __FUNCTION__); michael@0: MOZ_ASSERT(PR_FALSE); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: //Conduit should have reception enabled before we ask for decoded michael@0: // samples michael@0: if(!mEngineReceiving) michael@0: { michael@0: CSFLogError(logTag, "%s Engine not Receiving ", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: michael@0: lengthSamples = 0; //output paramter michael@0: michael@0: if(mPtrVoEXmedia->ExternalPlayoutGetData( speechData, michael@0: samplingFreqHz, michael@0: capture_delay, michael@0: lengthSamples) == -1) michael@0: { michael@0: int error = mPtrVoEBase->LastError(); michael@0: CSFLogError(logTag, "%s Getting audio data Failed %d", __FUNCTION__, error); michael@0: if(error == VE_RUNTIME_PLAY_ERROR) michael@0: { michael@0: return kMediaConduitPlayoutError; michael@0: } michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: michael@0: // Not #ifdef DEBUG or on a log module so we can use it for about:webrtc/etc michael@0: mSamples += lengthSamples; michael@0: if (mSamples >= mLastSyncLog + samplingFreqHz) { michael@0: int jitter_buffer_delay_ms; michael@0: int playout_buffer_delay_ms; michael@0: int avsync_offset_ms; michael@0: if (GetAVStats(&jitter_buffer_delay_ms, michael@0: &playout_buffer_delay_ms, michael@0: &avsync_offset_ms)) { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (avsync_offset_ms < 0) { michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_AVSYNC_WHEN_VIDEO_LAGS_AUDIO_MS, michael@0: -avsync_offset_ms); michael@0: } else { michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_AVSYNC_WHEN_AUDIO_LAGS_VIDEO_MS, michael@0: avsync_offset_ms); michael@0: } michael@0: #endif michael@0: CSFLogError(logTag, michael@0: "A/V sync: sync delta: %dms, audio jitter delay %dms, playout delay %dms", michael@0: avsync_offset_ms, jitter_buffer_delay_ms, playout_buffer_delay_ms); michael@0: } else { michael@0: CSFLogError(logTag, "A/V sync: GetAVStats failed"); michael@0: } michael@0: mLastSyncLog = mSamples; michael@0: } michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) { michael@0: if (mProcessing.Length() > 0) { michael@0: unsigned int now; michael@0: mPtrVoEVideoSync->GetPlayoutTimestamp(mChannel, now); michael@0: if (static_cast(now) != mLastTimestamp) { michael@0: mLastTimestamp = static_cast(now); michael@0: // Find the block that includes this timestamp in the network input michael@0: while (mProcessing.Length() > 0) { michael@0: // FIX! assumes 20ms @ 48000Hz michael@0: // FIX handle wrap-around michael@0: if (mProcessing[0].mRTPTimeStamp + 20*(48000/1000) >= now) { michael@0: TimeDuration t = TimeStamp::Now() - mProcessing[0].mTimeStamp; michael@0: // Wrap-around? michael@0: int64_t delta = t.ToMilliseconds() + (now - mProcessing[0].mRTPTimeStamp)/(48000/1000); michael@0: LogTime(AsyncLatencyLogger::AudioRecvRTP, ((uint64_t) this), delta); michael@0: break; michael@0: } michael@0: mProcessing.RemoveElementAt(0); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: CSFLogDebug(logTag,"%s GetAudioFrame:Got samples: length %d ",__FUNCTION__, michael@0: lengthSamples); michael@0: return kMediaConduitNoError; michael@0: } michael@0: michael@0: // Transport Layer Callbacks michael@0: MediaConduitErrorCode michael@0: WebrtcAudioConduit::ReceivedRTPPacket(const void *data, int len) michael@0: { michael@0: CSFLogDebug(logTag, "%s : channel %d", __FUNCTION__, mChannel); michael@0: michael@0: if(mEngineReceiving) michael@0: { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) { michael@0: // timestamp is at 32 bits in ([1]) michael@0: struct Processing insert = { TimeStamp::Now(), michael@0: ntohl(static_cast(data)[1]) }; michael@0: mProcessing.AppendElement(insert); michael@0: } michael@0: #endif michael@0: michael@0: if(mPtrVoENetwork->ReceivedRTPPacket(mChannel,data,len) == -1) michael@0: { michael@0: int error = mPtrVoEBase->LastError(); michael@0: CSFLogError(logTag, "%s RTP Processing Error %d", __FUNCTION__, error); michael@0: if(error == VE_RTP_RTCP_MODULE_ERROR) michael@0: { michael@0: return kMediaConduitRTPRTCPModuleError; michael@0: } michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: } else { michael@0: CSFLogError(logTag, "Error: %s when not receiving", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: michael@0: return kMediaConduitNoError; michael@0: } michael@0: michael@0: MediaConduitErrorCode michael@0: WebrtcAudioConduit::ReceivedRTCPPacket(const void *data, int len) michael@0: { michael@0: CSFLogDebug(logTag, "%s : channel %d",__FUNCTION__, mChannel); michael@0: michael@0: if(mEngineTransmitting) michael@0: { michael@0: if(mPtrVoENetwork->ReceivedRTCPPacket(mChannel, data, len) == -1) michael@0: { michael@0: int error = mPtrVoEBase->LastError(); michael@0: CSFLogError(logTag, "%s RTCP Processing Error %d", __FUNCTION__, error); michael@0: if(error == VE_RTP_RTCP_MODULE_ERROR) michael@0: { michael@0: return kMediaConduitRTPRTCPModuleError; michael@0: } michael@0: return kMediaConduitUnknownError; michael@0: } michael@0: } else { michael@0: CSFLogError(logTag, "Error: %s when not receiving", __FUNCTION__); michael@0: return kMediaConduitSessionNotInited; michael@0: } michael@0: return kMediaConduitNoError; michael@0: } michael@0: michael@0: //WebRTC::RTP Callback Implementation michael@0: int WebrtcAudioConduit::SendPacket(int channel, const void* data, int len) michael@0: { michael@0: CSFLogDebug(logTag, "%s : channel %d %s", __FUNCTION__, channel, michael@0: (mEngineReceiving && mOtherDirection) ? "(using mOtherDirection)" : ""); michael@0: michael@0: if (mEngineReceiving) michael@0: { michael@0: if (mOtherDirection) michael@0: { michael@0: return mOtherDirection->SendPacket(channel, data, len); michael@0: } michael@0: CSFLogDebug(logTag, "%s : Asked to send RTP without an RTP sender on channel %d", michael@0: __FUNCTION__, channel); michael@0: return -1; michael@0: } else { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) { michael@0: if (mProcessing.Length() > 0) { michael@0: TimeStamp started = mProcessing[0].mTimeStamp; michael@0: mProcessing.RemoveElementAt(0); michael@0: mProcessing.RemoveElementAt(0); // 20ms packetization! Could automate this by watching sizes michael@0: TimeDuration t = TimeStamp::Now() - started; michael@0: int64_t delta = t.ToMilliseconds(); michael@0: LogTime(AsyncLatencyLogger::AudioSendRTP, ((uint64_t) this), delta); michael@0: } michael@0: } michael@0: #endif michael@0: if(mTransport && (mTransport->SendRtpPacket(data, len) == NS_OK)) michael@0: { michael@0: CSFLogDebug(logTag, "%s Sent RTP Packet ", __FUNCTION__); michael@0: return len; michael@0: } else { michael@0: CSFLogError(logTag, "%s RTP Packet Send Failed ", __FUNCTION__); michael@0: return -1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: int WebrtcAudioConduit::SendRTCPPacket(int channel, const void* data, int len) michael@0: { michael@0: CSFLogDebug(logTag, "%s : channel %d", __FUNCTION__, channel); michael@0: michael@0: if (mEngineTransmitting) michael@0: { michael@0: if (mOtherDirection) michael@0: { michael@0: return mOtherDirection->SendRTCPPacket(channel, data, len); michael@0: } michael@0: } michael@0: michael@0: // We come here if we have only one pipeline/conduit setup, michael@0: // such as for unidirectional streams. michael@0: // We also end up here if we are receiving michael@0: if(mTransport && mTransport->SendRtcpPacket(data, len) == NS_OK) michael@0: { michael@0: CSFLogDebug(logTag, "%s Sent RTCP Packet ", __FUNCTION__); michael@0: return len; michael@0: } else { michael@0: CSFLogError(logTag, "%s RTCP Packet Send Failed ", __FUNCTION__); michael@0: return -1; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Converts between CodecConfig to WebRTC Codec Structure. michael@0: */ michael@0: michael@0: bool michael@0: WebrtcAudioConduit::CodecConfigToWebRTCCodec(const AudioCodecConfig* codecInfo, michael@0: webrtc::CodecInst& cinst) michael@0: { michael@0: const unsigned int plNameLength = codecInfo->mName.length()+1; michael@0: memset(&cinst, 0, sizeof(webrtc::CodecInst)); michael@0: if(sizeof(cinst.plname) < plNameLength) michael@0: { michael@0: CSFLogError(logTag, "%s Payload name buffer capacity mismatch ", michael@0: __FUNCTION__); michael@0: return false; michael@0: } michael@0: memcpy(cinst.plname, codecInfo->mName.c_str(),codecInfo->mName.length()); michael@0: cinst.plname[plNameLength]='\0'; michael@0: cinst.pltype = codecInfo->mType; michael@0: cinst.rate = codecInfo->mRate; michael@0: cinst.pacsize = codecInfo->mPacSize; michael@0: cinst.plfreq = codecInfo->mFreq; michael@0: cinst.channels = codecInfo->mChannels; michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Supported Sampling Frequncies. michael@0: */ michael@0: bool michael@0: WebrtcAudioConduit::IsSamplingFreqSupported(int freq) const michael@0: { michael@0: if(GetNum10msSamplesForFrequency(freq)) michael@0: { michael@0: return true; michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Return block-length of 10 ms audio frame in number of samples */ michael@0: unsigned int michael@0: WebrtcAudioConduit::GetNum10msSamplesForFrequency(int samplingFreqHz) const michael@0: { michael@0: switch(samplingFreqHz) michael@0: { michael@0: case 16000: return 160; //160 samples michael@0: case 32000: return 320; //320 samples michael@0: case 44100: return 441; //441 samples michael@0: case 48000: return 480; //480 samples michael@0: default: return 0; // invalid or unsupported michael@0: } michael@0: } michael@0: michael@0: //Copy the codec passed into Conduit's database michael@0: bool michael@0: WebrtcAudioConduit::CopyCodecToDB(const AudioCodecConfig* codecInfo) michael@0: { michael@0: michael@0: AudioCodecConfig* cdcConfig = new AudioCodecConfig(codecInfo->mType, michael@0: codecInfo->mName, michael@0: codecInfo->mFreq, michael@0: codecInfo->mPacSize, michael@0: codecInfo->mChannels, michael@0: codecInfo->mRate, michael@0: codecInfo->mLoadManager); michael@0: mRecvCodecList.push_back(cdcConfig); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Checks if 2 codec structs are same michael@0: */ michael@0: bool michael@0: WebrtcAudioConduit::CheckCodecsForMatch(const AudioCodecConfig* curCodecConfig, michael@0: const AudioCodecConfig* codecInfo) const michael@0: { michael@0: if(!curCodecConfig) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: if(curCodecConfig->mType == codecInfo->mType && michael@0: (curCodecConfig->mName.compare(codecInfo->mName) == 0) && michael@0: curCodecConfig->mFreq == codecInfo->mFreq && michael@0: curCodecConfig->mPacSize == codecInfo->mPacSize && michael@0: curCodecConfig->mChannels == codecInfo->mChannels && michael@0: curCodecConfig->mRate == codecInfo->mRate) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Checks if the codec is already in Conduit's database michael@0: */ michael@0: bool michael@0: WebrtcAudioConduit::CheckCodecForMatch(const AudioCodecConfig* codecInfo) const michael@0: { michael@0: //the db should have atleast one codec michael@0: for(std::vector::size_type i=0;i < mRecvCodecList.size();i++) michael@0: { michael@0: if(CheckCodecsForMatch(mRecvCodecList[i],codecInfo)) michael@0: { michael@0: //match michael@0: return true; michael@0: } michael@0: } michael@0: //no match or empty local db michael@0: return false; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Perform validation on the codecConfig to be applied. michael@0: * Verifies if the codec is already applied. michael@0: */ michael@0: MediaConduitErrorCode michael@0: WebrtcAudioConduit::ValidateCodecConfig(const AudioCodecConfig* codecInfo, michael@0: bool send) const michael@0: { michael@0: bool codecAppliedAlready = false; michael@0: michael@0: if(!codecInfo) michael@0: { michael@0: CSFLogError(logTag, "%s Null CodecConfig ", __FUNCTION__); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: if((codecInfo->mName.empty()) || michael@0: (codecInfo->mName.length() >= CODEC_PLNAME_SIZE)) michael@0: { michael@0: CSFLogError(logTag, "%s Invalid Payload Name Length ", __FUNCTION__); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: //Only mono or stereo channels supported michael@0: if( (codecInfo->mChannels != 1) && (codecInfo->mChannels != 2)) michael@0: { michael@0: CSFLogError(logTag, "%s Channel Unsupported ", __FUNCTION__); michael@0: return kMediaConduitMalformedArgument; michael@0: } michael@0: michael@0: //check if we have the same codec already applied michael@0: if(send) michael@0: { michael@0: codecAppliedAlready = CheckCodecsForMatch(mCurSendCodecConfig,codecInfo); michael@0: } else { michael@0: codecAppliedAlready = CheckCodecForMatch(codecInfo); michael@0: } michael@0: michael@0: if(codecAppliedAlready) michael@0: { michael@0: CSFLogDebug(logTag, "%s Codec %s Already Applied ", __FUNCTION__, codecInfo->mName.c_str()); michael@0: return kMediaConduitCodecInUse; michael@0: } michael@0: return kMediaConduitNoError; michael@0: } michael@0: michael@0: void michael@0: WebrtcAudioConduit::DumpCodecDB() const michael@0: { michael@0: for(std::vector::size_type i=0;i < mRecvCodecList.size();i++) michael@0: { michael@0: CSFLogDebug(logTag,"Payload Name: %s", mRecvCodecList[i]->mName.c_str()); michael@0: CSFLogDebug(logTag,"Payload Type: %d", mRecvCodecList[i]->mType); michael@0: CSFLogDebug(logTag,"Payload Frequency: %d", mRecvCodecList[i]->mFreq); michael@0: CSFLogDebug(logTag,"Payload PacketSize: %d", mRecvCodecList[i]->mPacSize); michael@0: CSFLogDebug(logTag,"Payload Channels: %d", mRecvCodecList[i]->mChannels); michael@0: CSFLogDebug(logTag,"Payload Sampling Rate: %d", mRecvCodecList[i]->mRate); michael@0: } michael@0: } michael@0: }// end namespace