diff options
Diffstat (limited to 'media/webrtc/signaling/src/media-conduit/AudioConduit.cpp')
-rwxr-xr-x | media/webrtc/signaling/src/media-conduit/AudioConduit.cpp | 1134 |
1 files changed, 1134 insertions, 0 deletions
diff --git a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp new file mode 100755 index 000000000..2c57431e7 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp @@ -0,0 +1,1134 @@ +/* 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 "CSFLog.h" +#include "nspr.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#elif defined XP_WIN +#include <winsock2.h> +#endif + +#include "AudioConduit.h" +#include "nsCOMPtr.h" +#include "mozilla/Services.h" +#include "nsServiceManagerUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsThreadUtils.h" +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#include "Latency.h" +#include "mozilla/Telemetry.h" +#endif + +#include "webrtc/common.h" +#include "webrtc/modules/audio_processing/include/audio_processing.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h" +#include "webrtc/voice_engine/include/voe_dtmf.h" +#include "webrtc/voice_engine/include/voe_errors.h" +#include "webrtc/voice_engine/voice_engine_impl.h" +#include "webrtc/system_wrappers/interface/clock.h" + +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidJNIWrapper.h" +#endif + +namespace mozilla { + +static const char* logTag ="WebrtcAudioSessionConduit"; + +// 32 bytes is what WebRTC CodecInst expects +const unsigned int WebrtcAudioConduit::CODEC_PLNAME_SIZE = 32; + +/** + * Factory Method for AudioConduit + */ +RefPtr<AudioSessionConduit> AudioSessionConduit::Create() +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); + + WebrtcAudioConduit* obj = new WebrtcAudioConduit(); + if(obj->Init() != kMediaConduitNoError) + { + CSFLogError(logTag, "%s AudioConduit Init Failed ", __FUNCTION__); + delete obj; + return nullptr; + } + CSFLogDebug(logTag, "%s Successfully created AudioConduit ", __FUNCTION__); + return obj; +} + +/** + * Destruction defines for our super-classes + */ +WebrtcAudioConduit::~WebrtcAudioConduit() +{ + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); + + CSFLogDebug(logTag, "%s ", __FUNCTION__); + for(std::vector<AudioCodecConfig*>::size_type i=0;i < mRecvCodecList.size();i++) + { + delete mRecvCodecList[i]; + } + + // The first one of a pair to be deleted shuts down media for both + if(mPtrVoEXmedia) + { + mPtrVoEXmedia->SetExternalRecordingStatus(false); + mPtrVoEXmedia->SetExternalPlayoutStatus(false); + } + + //Deal with the transport + if(mPtrVoENetwork) + { + mPtrVoENetwork->DeRegisterExternalTransport(mChannel); + } + + if(mPtrVoEBase) + { + mPtrVoEBase->StopPlayout(mChannel); + mPtrVoEBase->StopSend(mChannel); + mPtrVoEBase->StopReceive(mChannel); + mPtrVoEBase->DeleteChannel(mChannel); + mPtrVoEBase->Terminate(); + } + + // We shouldn't delete the VoiceEngine until all these are released! + // And we can't use a Scoped ptr, since the order is arbitrary + mPtrVoENetwork = nullptr; + mPtrVoEBase = nullptr; + mPtrVoECodec = nullptr; + mPtrVoEXmedia = nullptr; + mPtrVoEProcessing = nullptr; + mPtrVoEVideoSync = nullptr; + mPtrVoERTP_RTCP = nullptr; + mPtrRTP = nullptr; + + if(mVoiceEngine) + { + webrtc::VoiceEngine::Delete(mVoiceEngine); + } +} + +bool WebrtcAudioConduit::SetLocalSSRC(unsigned int ssrc) +{ + unsigned int oldSsrc; + if (!GetLocalSSRC(&oldSsrc)) { + MOZ_ASSERT(false, "GetLocalSSRC failed"); + return false; + } + + if (oldSsrc == ssrc) { + return true; + } + + bool wasTransmitting = mEngineTransmitting; + if (StopTransmitting() != kMediaConduitNoError) { + return false; + } + + if (mPtrRTP->SetLocalSSRC(mChannel, ssrc)) { + return false; + } + + if (wasTransmitting) { + if (StartTransmitting() != kMediaConduitNoError) { + return false; + } + } + return true; +} + +bool WebrtcAudioConduit::GetLocalSSRC(unsigned int* ssrc) { + return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc); +} + +bool WebrtcAudioConduit::GetRemoteSSRC(unsigned int* ssrc) { + return !mPtrRTP->GetRemoteSSRC(mChannel, *ssrc); +} + +bool WebrtcAudioConduit::SetLocalCNAME(const char* cname) +{ + char temp[256]; + strncpy(temp, cname, sizeof(temp) - 1); + temp[sizeof(temp) - 1] = 0; + return !mPtrRTP->SetRTCP_CNAME(mChannel, temp); +} + +bool WebrtcAudioConduit::GetAVStats(int32_t* jitterBufferDelayMs, + int32_t* playoutBufferDelayMs, + int32_t* avSyncOffsetMs) { + return !mPtrVoEVideoSync->GetDelayEstimate(mChannel, + jitterBufferDelayMs, + playoutBufferDelayMs, + avSyncOffsetMs); +} + +bool WebrtcAudioConduit::GetRTPStats(unsigned int* jitterMs, + unsigned int* cumulativeLost) { + unsigned int maxJitterMs = 0; + unsigned int discardedPackets; + *jitterMs = 0; + *cumulativeLost = 0; + return !mPtrRTP->GetRTPStatistics(mChannel, *jitterMs, maxJitterMs, + discardedPackets, *cumulativeLost); +} + +DOMHighResTimeStamp +NTPtoDOMHighResTimeStamp(uint32_t ntpHigh, uint32_t ntpLow) { + return (uint32_t(ntpHigh - webrtc::kNtpJan1970) + + double(ntpLow) / webrtc::kMagicNtpFractionalUnit) * 1000; +} + +bool WebrtcAudioConduit::GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, + uint32_t* jitterMs, + uint32_t* packetsReceived, + uint64_t* bytesReceived, + uint32_t* cumulativeLost, + int32_t* rttMs) { + uint32_t ntpHigh, ntpLow; + uint16_t fractionLost; + bool result = !mPtrRTP->GetRemoteRTCPReceiverInfo(mChannel, ntpHigh, ntpLow, + *packetsReceived, + *bytesReceived, + *jitterMs, + fractionLost, + *cumulativeLost, + *rttMs); + if (result) { + *timestamp = NTPtoDOMHighResTimeStamp(ntpHigh, ntpLow); + } + return result; +} + +bool WebrtcAudioConduit::GetRTCPSenderReport(DOMHighResTimeStamp* timestamp, + unsigned int* packetsSent, + uint64_t* bytesSent) { + webrtc::RTCPSenderInfo senderInfo; + webrtc::RtpRtcp * rtpRtcpModule; + webrtc::RtpReceiver * rtp_receiver; + bool result = + !mPtrVoEVideoSync->GetRtpRtcp(mChannel,&rtpRtcpModule,&rtp_receiver) && + !rtpRtcpModule->RemoteRTCPStat(&senderInfo); + if (result){ + *timestamp = NTPtoDOMHighResTimeStamp(senderInfo.NTPseconds, + senderInfo.NTPfraction); + *packetsSent = senderInfo.sendPacketCount; + *bytesSent = senderInfo.sendOctetCount; + } + return result; + } + +bool WebrtcAudioConduit::SetDtmfPayloadType(unsigned char type) { + CSFLogInfo(logTag, "%s : setting dtmf payload %d", __FUNCTION__, (int)type); + + ScopedCustomReleasePtr<webrtc::VoEDtmf> mPtrVoEDtmf; + mPtrVoEDtmf = webrtc::VoEDtmf::GetInterface(mVoiceEngine); + if (!mPtrVoEDtmf) { + CSFLogError(logTag, "%s Unable to initialize VoEDtmf", __FUNCTION__); + return false; + } + + int result = mPtrVoEDtmf->SetSendTelephoneEventPayloadType(mChannel, type); + if (result == -1) { + CSFLogError(logTag, "%s Failed call to SetSendTelephoneEventPayloadType", + __FUNCTION__); + } + return result != -1; +} + +bool WebrtcAudioConduit::InsertDTMFTone(int channel, int eventCode, + bool outOfBand, int lengthMs, + int attenuationDb) { + NS_ASSERTION(!NS_IsMainThread(), "Do not call on main thread"); + + if (!mVoiceEngine || !mDtmfEnabled) { + return false; + } + + webrtc::VoiceEngineImpl* s = static_cast<webrtc::VoiceEngineImpl*>(mVoiceEngine); + int result = s->SendTelephoneEvent(channel, eventCode, outOfBand, lengthMs, attenuationDb); + return result != -1; +} + +/* + * WebRTCAudioConduit Implementation + */ +MediaConduitErrorCode WebrtcAudioConduit::Init() +{ + CSFLogDebug(logTag, "%s this=%p", __FUNCTION__, this); + +#ifdef MOZ_WIDGET_ANDROID + jobject context = jsjni_GetGlobalContextRef(); + // get the JVM + JavaVM *jvm = jsjni_GetVM(); + + if (webrtc::VoiceEngine::SetAndroidObjects(jvm, (void*)context) != 0) { + CSFLogError(logTag, "%s Unable to set Android objects", __FUNCTION__); + return kMediaConduitSessionNotInited; + } +#endif + + // Per WebRTC APIs below function calls return nullptr on failure + if(!(mVoiceEngine = webrtc::VoiceEngine::Create())) + { + CSFLogError(logTag, "%s Unable to create voice engine", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoEBase = VoEBase::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEBase", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoENetwork = VoENetwork::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoENetwork", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoECodec = VoECodec::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEBCodec", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoEProcessing = VoEAudioProcessing::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEProcessing", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + if(!(mPtrVoEXmedia = VoEExternalMedia::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEExternalMedia", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + if(!(mPtrVoERTP_RTCP = VoERTP_RTCP::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoERTP_RTCP", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoEVideoSync = VoEVideoSync::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEVideoSync", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + if (!(mPtrRTP = webrtc::VoERTP_RTCP::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to get audio RTP/RTCP interface ", + __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + // init the engine with our audio device layer + if(mPtrVoEBase->Init() == -1) + { + CSFLogError(logTag, "%s VoiceEngine Base Not Initialized", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if( (mChannel = mPtrVoEBase->CreateChannel()) == -1) + { + CSFLogError(logTag, "%s VoiceEngine Channel creation failed",__FUNCTION__); + return kMediaConduitChannelError; + } + + CSFLogDebug(logTag, "%s Channel Created %d ",__FUNCTION__, mChannel); + + if(mPtrVoENetwork->RegisterExternalTransport(mChannel, *this) == -1) + { + CSFLogError(logTag, "%s VoiceEngine, External Transport Failed",__FUNCTION__); + return kMediaConduitTransportRegistrationFail; + } + + if(mPtrVoEXmedia->SetExternalRecordingStatus(true) == -1) + { + CSFLogError(logTag, "%s SetExternalRecordingStatus Failed %d",__FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitExternalPlayoutError; + } + + if(mPtrVoEXmedia->SetExternalPlayoutStatus(true) == -1) + { + CSFLogError(logTag, "%s SetExternalPlayoutStatus Failed %d ",__FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitExternalRecordingError; + } + + CSFLogDebug(logTag , "%s AudioSessionConduit Initialization Done (%p)",__FUNCTION__, this); + return kMediaConduitNoError; +} + +// AudioSessionConduit Implementation +MediaConduitErrorCode +WebrtcAudioConduit::SetTransmitterTransport(RefPtr<TransportInterface> aTransport) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + + ReentrantMonitorAutoEnter enter(mTransportMonitor); + // set the transport + mTransmitterTransport = aTransport; + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::SetReceiverTransport(RefPtr<TransportInterface> aTransport) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + + ReentrantMonitorAutoEnter enter(mTransportMonitor); + // set the transport + mReceiverTransport = aTransport; + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::ConfigureSendMediaCodec(const AudioCodecConfig* codecConfig) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + MediaConduitErrorCode condError = kMediaConduitNoError; + int error = 0;//webrtc engine errors + webrtc::CodecInst cinst; + + { + //validate codec param + if((condError = ValidateCodecConfig(codecConfig, true)) != kMediaConduitNoError) + { + return condError; + } + } + + condError = StopTransmitting(); + if (condError != kMediaConduitNoError) { + return condError; + } + + if(!CodecConfigToWebRTCCodec(codecConfig,cinst)) + { + CSFLogError(logTag,"%s CodecConfig to WebRTC Codec Failed ",__FUNCTION__); + return kMediaConduitMalformedArgument; + } + + if(mPtrVoECodec->SetSendCodec(mChannel, cinst) == -1) + { + error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s SetSendCodec - Invalid Codec %d ",__FUNCTION__, + error); + + if(error == VE_CANNOT_SET_SEND_CODEC || error == VE_CODEC_ERROR) + { + CSFLogError(logTag, "%s Invalid Send Codec", __FUNCTION__); + return kMediaConduitInvalidSendCodec; + } + CSFLogError(logTag, "%s SetSendCodec Failed %d ", __FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitUnknownError; + } + + // This must be called after SetSendCodec + if (mPtrVoECodec->SetFECStatus(mChannel, codecConfig->mFECEnabled) == -1) { + CSFLogError(logTag, "%s SetFECStatus Failed %d ", __FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitFECStatusError; + } + + mDtmfEnabled = codecConfig->mDtmfEnabled; + + if (codecConfig->mName == "opus" && codecConfig->mMaxPlaybackRate) { + if (mPtrVoECodec->SetOpusMaxPlaybackRate( + mChannel, + codecConfig->mMaxPlaybackRate) == -1) { + CSFLogError(logTag, "%s SetOpusMaxPlaybackRate Failed %d ", __FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitUnknownError; + } + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // TEMPORARY - see bug 694814 comment 2 + nsresult rv; + nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); + + if (branch) { + branch->GetIntPref("media.peerconnection.capture_delay", &mCaptureDelay); + } + } +#endif + + condError = StartTransmitting(); + if (condError != kMediaConduitNoError) { + return condError; + } + + { + MutexAutoLock lock(mCodecMutex); + + //Copy the applied config for future reference. + mCurSendCodecConfig = new AudioCodecConfig(codecConfig->mType, + codecConfig->mName, + codecConfig->mFreq, + codecConfig->mPacSize, + codecConfig->mChannels, + codecConfig->mRate, + codecConfig->mFECEnabled); + } + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::ConfigureRecvMediaCodecs( + const std::vector<AudioCodecConfig*>& codecConfigList) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + MediaConduitErrorCode condError = kMediaConduitNoError; + int error = 0; //webrtc engine errors + bool success = false; + + // Are we receiving already? If so, stop receiving and playout + // since we can't apply new recv codec when the engine is playing. + condError = StopReceiving(); + if (condError != kMediaConduitNoError) { + return condError; + } + + if(codecConfigList.empty()) + { + CSFLogError(logTag, "%s Zero number of codecs to configure", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + // Try Applying the codecs in the list. + // We succeed if at least one codec was applied and reception was + // started successfully. + for(std::vector<AudioCodecConfig*>::size_type i=0 ;i<codecConfigList.size();i++) + { + //if the codec param is invalid or diplicate, return error + if((condError = ValidateCodecConfig(codecConfigList[i],false)) != kMediaConduitNoError) + { + return condError; + } + + webrtc::CodecInst cinst; + if(!CodecConfigToWebRTCCodec(codecConfigList[i],cinst)) + { + CSFLogError(logTag,"%s CodecConfig to WebRTC Codec Failed ",__FUNCTION__); + continue; + } + + if(mPtrVoECodec->SetRecPayloadType(mChannel,cinst) == -1) + { + error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s SetRecvCodec Failed %d ",__FUNCTION__, error); + continue; + } else { + CSFLogDebug(logTag, "%s Successfully Set RecvCodec %s", __FUNCTION__, + codecConfigList[i]->mName.c_str()); + //copy this to local database + if(CopyCodecToDB(codecConfigList[i])) + { + success = true; + } else { + CSFLogError(logTag,"%s Unable to updated Codec Database", __FUNCTION__); + return kMediaConduitUnknownError; + } + + } + + } //end for + + if(!success) + { + CSFLogError(logTag, "%s Setting Receive Codec Failed ", __FUNCTION__); + return kMediaConduitInvalidReceiveCodec; + } + + //If we are here, atleast one codec should have been set + condError = StartReceiving(); + if (condError != kMediaConduitNoError) { + return condError; + } + + DumpCodecDB(); + return kMediaConduitNoError; +} +MediaConduitErrorCode +WebrtcAudioConduit::EnableAudioLevelExtension(bool enabled, uint8_t id) +{ + CSFLogDebug(logTag, "%s %d %d ", __FUNCTION__, enabled, id); + + if (mPtrVoERTP_RTCP->SetSendAudioLevelIndicationStatus(mChannel, enabled, id) == -1) + { + CSFLogError(logTag, "%s SetSendAudioLevelIndicationStatus Failed", __FUNCTION__); + return kMediaConduitUnknownError; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::SendAudioFrame(const int16_t audio_data[], + int32_t lengthSamples, + int32_t samplingFreqHz, + int32_t capture_delay) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + // Following checks need to be performed + // 1. Non null audio buffer pointer, + // 2. invalid sampling frequency - less than 0 or unsupported ones + // 3. Appropriate Sample Length for 10 ms audio-frame. This represents + // block size the VoiceEngine feeds into encoder for passed in audio-frame + // Ex: for 16000 sampling rate , valid block-length is 160 + // Similarly for 32000 sampling rate, valid block length is 320 + // We do the check by the verify modular operator below to be zero + + if(!audio_data || (lengthSamples <= 0) || + (IsSamplingFreqSupported(samplingFreqHz) == false) || + ((lengthSamples % (samplingFreqHz / 100) != 0)) ) + { + CSFLogError(logTag, "%s Invalid Parameters ",__FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + //validate capture time + if(capture_delay < 0 ) + { + CSFLogError(logTag,"%s Invalid Capture Delay ", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + // if transmission is not started .. conduit cannot insert frames + if(!mEngineTransmitting) + { + CSFLogError(logTag, "%s Engine not transmitting ", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (MOZ_LOG_TEST(GetLatencyLog(), LogLevel::Debug)) { + struct Processing insert = { TimeStamp::Now(), 0 }; + mProcessing.AppendElement(insert); + } +#endif + + capture_delay = mCaptureDelay; + //Insert the samples + if(mPtrVoEXmedia->ExternalRecordingInsertData(audio_data, + lengthSamples, + samplingFreqHz, + capture_delay) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s Inserting audio data Failed %d", __FUNCTION__, error); + if(error == VE_RUNTIME_REC_ERROR) + { + return kMediaConduitRecordingError; + } + return kMediaConduitUnknownError; + } + // we should be good here + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::GetAudioFrame(int16_t speechData[], + int32_t samplingFreqHz, + int32_t capture_delay, + int& lengthSamples) +{ + + CSFLogDebug(logTag, "%s ", __FUNCTION__); + unsigned int numSamples = 0; + + //validate params + if(!speechData ) + { + CSFLogError(logTag,"%s Null Audio Buffer Pointer", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + // Validate sample length + if((numSamples = GetNum10msSamplesForFrequency(samplingFreqHz)) == 0 ) + { + CSFLogError(logTag,"%s Invalid Sampling Frequency ", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + //validate capture time + if(capture_delay < 0 ) + { + CSFLogError(logTag,"%s Invalid Capture Delay ", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + //Conduit should have reception enabled before we ask for decoded + // samples + if(!mEngineReceiving) + { + CSFLogError(logTag, "%s Engine not Receiving ", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + + lengthSamples = 0; //output paramter + + if(mPtrVoEXmedia->ExternalPlayoutGetData( speechData, + samplingFreqHz, + capture_delay, + lengthSamples) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s Getting audio data Failed %d", __FUNCTION__, error); + if(error == VE_RUNTIME_PLAY_ERROR) + { + return kMediaConduitPlayoutError; + } + return kMediaConduitUnknownError; + } + + // Not #ifdef DEBUG or on a log module so we can use it for about:webrtc/etc + mSamples += lengthSamples; + if (mSamples >= mLastSyncLog + samplingFreqHz) { + int jitter_buffer_delay_ms; + int playout_buffer_delay_ms; + int avsync_offset_ms; + if (GetAVStats(&jitter_buffer_delay_ms, + &playout_buffer_delay_ms, + &avsync_offset_ms)) { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (avsync_offset_ms < 0) { + Telemetry::Accumulate(Telemetry::WEBRTC_AVSYNC_WHEN_VIDEO_LAGS_AUDIO_MS, + -avsync_offset_ms); + } else { + Telemetry::Accumulate(Telemetry::WEBRTC_AVSYNC_WHEN_AUDIO_LAGS_VIDEO_MS, + avsync_offset_ms); + } +#endif + CSFLogError(logTag, + "A/V sync: sync delta: %dms, audio jitter delay %dms, playout delay %dms", + avsync_offset_ms, jitter_buffer_delay_ms, playout_buffer_delay_ms); + } else { + CSFLogError(logTag, "A/V sync: GetAVStats failed"); + } + mLastSyncLog = mSamples; + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (MOZ_LOG_TEST(GetLatencyLog(), LogLevel::Debug)) { + if (mProcessing.Length() > 0) { + unsigned int now; + mPtrVoEVideoSync->GetPlayoutTimestamp(mChannel, now); + if (static_cast<uint32_t>(now) != mLastTimestamp) { + mLastTimestamp = static_cast<uint32_t>(now); + // Find the block that includes this timestamp in the network input + while (mProcessing.Length() > 0) { + // FIX! assumes 20ms @ 48000Hz + // FIX handle wrap-around + if (mProcessing[0].mRTPTimeStamp + 20*(48000/1000) >= now) { + TimeDuration t = TimeStamp::Now() - mProcessing[0].mTimeStamp; + // Wrap-around? + int64_t delta = t.ToMilliseconds() + (now - mProcessing[0].mRTPTimeStamp)/(48000/1000); + LogTime(AsyncLatencyLogger::AudioRecvRTP, ((uint64_t) this), delta); + break; + } + mProcessing.RemoveElementAt(0); + } + } + } + } +#endif + CSFLogDebug(logTag,"%s GetAudioFrame:Got samples: length %d ",__FUNCTION__, + lengthSamples); + return kMediaConduitNoError; +} + +// Transport Layer Callbacks +MediaConduitErrorCode +WebrtcAudioConduit::ReceivedRTPPacket(const void *data, int len) +{ + CSFLogDebug(logTag, "%s : channel %d", __FUNCTION__, mChannel); + + if(mEngineReceiving) + { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (MOZ_LOG_TEST(GetLatencyLog(), LogLevel::Debug)) { + // timestamp is at 32 bits in ([1]) + struct Processing insert = { TimeStamp::Now(), + ntohl(static_cast<const uint32_t *>(data)[1]) }; + mProcessing.AppendElement(insert); + } +#endif + + // XXX we need to get passed the time the packet was received + if(mPtrVoENetwork->ReceivedRTPPacket(mChannel, data, len) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s RTP Processing Error %d", __FUNCTION__, error); + if(error == VE_RTP_RTCP_MODULE_ERROR) + { + return kMediaConduitRTPRTCPModuleError; + } + return kMediaConduitUnknownError; + } + } else { + CSFLogError(logTag, "Error: %s when not receiving", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::ReceivedRTCPPacket(const void *data, int len) +{ + CSFLogDebug(logTag, "%s : channel %d",__FUNCTION__, mChannel); + + if(mPtrVoENetwork->ReceivedRTCPPacket(mChannel, data, len) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s RTCP Processing Error %d", __FUNCTION__, error); + if(error == VE_RTP_RTCP_MODULE_ERROR) + { + return kMediaConduitRTPRTCPModuleError; + } + return kMediaConduitUnknownError; + } + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::StopTransmitting() +{ + if(mEngineTransmitting) + { + CSFLogDebug(logTag, "%s Engine Already Sending. Attemping to Stop ", __FUNCTION__); + if(mPtrVoEBase->StopSend(mChannel) == -1) + { + CSFLogError(logTag, "%s StopSend() Failed %d ", __FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitUnknownError; + } + mEngineTransmitting = false; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::StartTransmitting() +{ + if (!mEngineTransmitting) { + //Let's Send Transport State-machine on the Engine + if(mPtrVoEBase->StartSend(mChannel) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s StartSend failed %d", __FUNCTION__, error); + return kMediaConduitUnknownError; + } + mEngineTransmitting = true; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::StopReceiving() +{ + if(mEngineReceiving) + { + CSFLogDebug(logTag, "%s Engine Already Receiving. Attemping to Stop ", __FUNCTION__); + // AudioEngine doesn't fail fatally on stopping reception. Ref:voe_errors.h. + // hence we need not be strict in failing here on errors + mPtrVoEBase->StopReceive(mChannel); + CSFLogDebug(logTag, "%s Attemping to Stop playout ", __FUNCTION__); + if(mPtrVoEBase->StopPlayout(mChannel) == -1) + { + if( mPtrVoEBase->LastError() == VE_CANNOT_STOP_PLAYOUT) + { + CSFLogDebug(logTag, "%s Stop-Playout Failed %d", __FUNCTION__, mPtrVoEBase->LastError()); + return kMediaConduitPlayoutError; + } + } + mEngineReceiving = false; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::StartReceiving() +{ + if (!mEngineReceiving) { + if(mPtrVoEBase->StartReceive(mChannel) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag , "%s StartReceive Failed %d ",__FUNCTION__, error); + if(error == VE_RECV_SOCKET_ERROR) + { + return kMediaConduitSocketError; + } + return kMediaConduitUnknownError; + } + + + if(mPtrVoEBase->StartPlayout(mChannel) == -1) + { + CSFLogError(logTag, "%s Starting playout Failed", __FUNCTION__); + return kMediaConduitPlayoutError; + } + mEngineReceiving = true; + } + + return kMediaConduitNoError; +} + +//WebRTC::RTP Callback Implementation +// Called on AudioGUM or MSG thread +int WebrtcAudioConduit::SendPacket(int channel, const void* data, size_t len) +{ + CSFLogDebug(logTag, "%s : channel %d", __FUNCTION__, channel); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (MOZ_LOG_TEST(GetLatencyLog(), LogLevel::Debug)) { + if (mProcessing.Length() > 0) { + TimeStamp started = mProcessing[0].mTimeStamp; + mProcessing.RemoveElementAt(0); + mProcessing.RemoveElementAt(0); // 20ms packetization! Could automate this by watching sizes + TimeDuration t = TimeStamp::Now() - started; + int64_t delta = t.ToMilliseconds(); + LogTime(AsyncLatencyLogger::AudioSendRTP, ((uint64_t) this), delta); + } + } +#endif + ReentrantMonitorAutoEnter enter(mTransportMonitor); + if(mTransmitterTransport && + (mTransmitterTransport->SendRtpPacket(data, len) == NS_OK)) + { + CSFLogDebug(logTag, "%s Sent RTP Packet ", __FUNCTION__); + return len; + } else { + CSFLogError(logTag, "%s RTP Packet Send Failed ", __FUNCTION__); + return -1; + } +} + +// Called on WebRTC Process thread and perhaps others +int WebrtcAudioConduit::SendRTCPPacket(int channel, const void* data, size_t len) +{ + CSFLogDebug(logTag, "%s : channel %d , len %lu, first rtcp = %u ", + __FUNCTION__, + channel, + (unsigned long) len, + static_cast<unsigned>(((uint8_t *) data)[1])); + + // We come here if we have only one pipeline/conduit setup, + // such as for unidirectional streams. + // We also end up here if we are receiving + ReentrantMonitorAutoEnter enter(mTransportMonitor); + if(mReceiverTransport && + mReceiverTransport->SendRtcpPacket(data, len) == NS_OK) + { + // Might be a sender report, might be a receiver report, we don't know. + CSFLogDebug(logTag, "%s Sent RTCP Packet ", __FUNCTION__); + return len; + } else if(mTransmitterTransport && + (mTransmitterTransport->SendRtcpPacket(data, len) == NS_OK)) { + CSFLogDebug(logTag, "%s Sent RTCP Packet (sender report) ", __FUNCTION__); + return len; + } else { + CSFLogError(logTag, "%s RTCP Packet Send Failed ", __FUNCTION__); + return -1; + } +} + +/** + * Converts between CodecConfig to WebRTC Codec Structure. + */ + +bool +WebrtcAudioConduit::CodecConfigToWebRTCCodec(const AudioCodecConfig* codecInfo, + webrtc::CodecInst& cinst) + { + const unsigned int plNameLength = codecInfo->mName.length(); + memset(&cinst, 0, sizeof(webrtc::CodecInst)); + if(sizeof(cinst.plname) < plNameLength+1) + { + CSFLogError(logTag, "%s Payload name buffer capacity mismatch ", + __FUNCTION__); + return false; + } + memcpy(cinst.plname, codecInfo->mName.c_str(), plNameLength); + cinst.plname[plNameLength]='\0'; + cinst.pltype = codecInfo->mType; + cinst.rate = codecInfo->mRate; + cinst.pacsize = codecInfo->mPacSize; + cinst.plfreq = codecInfo->mFreq; + if (codecInfo->mName == "G722") { + // Compensate for G.722 spec error in RFC 1890 + cinst.plfreq = 16000; + } + cinst.channels = codecInfo->mChannels; + return true; + } + +/** + * Supported Sampling Frequncies. + */ +bool +WebrtcAudioConduit::IsSamplingFreqSupported(int freq) const +{ + if(GetNum10msSamplesForFrequency(freq)) + { + return true; + } else { + return false; + } +} + +/* 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 + } +} + +//Copy the codec passed into Conduit's database +bool +WebrtcAudioConduit::CopyCodecToDB(const AudioCodecConfig* codecInfo) +{ + + AudioCodecConfig* cdcConfig = new AudioCodecConfig(codecInfo->mType, + codecInfo->mName, + codecInfo->mFreq, + codecInfo->mPacSize, + codecInfo->mChannels, + codecInfo->mRate, + codecInfo->mFECEnabled); + mRecvCodecList.push_back(cdcConfig); + return true; +} + +/** + * Checks if 2 codec structs are same + */ +bool +WebrtcAudioConduit::CheckCodecsForMatch(const AudioCodecConfig* curCodecConfig, + const AudioCodecConfig* codecInfo) const +{ + if(!curCodecConfig) + { + return false; + } + + if(curCodecConfig->mType == codecInfo->mType && + (curCodecConfig->mName.compare(codecInfo->mName) == 0) && + curCodecConfig->mFreq == codecInfo->mFreq && + curCodecConfig->mPacSize == codecInfo->mPacSize && + curCodecConfig->mChannels == codecInfo->mChannels && + curCodecConfig->mRate == codecInfo->mRate) + { + return true; + } + + return false; +} + +/** + * Checks if the codec is already in Conduit's database + */ +bool +WebrtcAudioConduit::CheckCodecForMatch(const AudioCodecConfig* codecInfo) const +{ + //the db should have atleast one codec + for(std::vector<AudioCodecConfig*>::size_type i=0;i < mRecvCodecList.size();i++) + { + if(CheckCodecsForMatch(mRecvCodecList[i],codecInfo)) + { + //match + return true; + } + } + //no match or empty local db + return false; +} + + +/** + * Perform validation on the codecConfig to be applied. + * Verifies if the codec is already applied. + */ +MediaConduitErrorCode +WebrtcAudioConduit::ValidateCodecConfig(const AudioCodecConfig* codecInfo, + bool send) +{ + bool codecAppliedAlready = false; + + if(!codecInfo) + { + CSFLogError(logTag, "%s Null CodecConfig ", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + if((codecInfo->mName.empty()) || + (codecInfo->mName.length() >= CODEC_PLNAME_SIZE)) + { + CSFLogError(logTag, "%s Invalid Payload Name Length ", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + //Only mono or stereo channels supported + if( (codecInfo->mChannels != 1) && (codecInfo->mChannels != 2)) + { + CSFLogError(logTag, "%s Channel Unsupported ", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + //check if we have the same codec already applied + if(send) + { + MutexAutoLock lock(mCodecMutex); + + codecAppliedAlready = CheckCodecsForMatch(mCurSendCodecConfig,codecInfo); + } else { + codecAppliedAlready = CheckCodecForMatch(codecInfo); + } + + if(codecAppliedAlready) + { + CSFLogDebug(logTag, "%s Codec %s Already Applied ", __FUNCTION__, codecInfo->mName.c_str()); + } + return kMediaConduitNoError; +} + +void +WebrtcAudioConduit::DumpCodecDB() const + { + for(std::vector<AudioCodecConfig*>::size_type i=0;i < mRecvCodecList.size();i++) + { + CSFLogDebug(logTag,"Payload Name: %s", mRecvCodecList[i]->mName.c_str()); + CSFLogDebug(logTag,"Payload Type: %d", mRecvCodecList[i]->mType); + CSFLogDebug(logTag,"Payload Frequency: %d", mRecvCodecList[i]->mFreq); + CSFLogDebug(logTag,"Payload PacketSize: %d", mRecvCodecList[i]->mPacSize); + CSFLogDebug(logTag,"Payload Channels: %d", mRecvCodecList[i]->mChannels); + CSFLogDebug(logTag,"Payload Sampling Rate: %d", mRecvCodecList[i]->mRate); + } + } +}// end namespace |