1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webrtc/MediaEngineDefault.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,516 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "MediaEngineDefault.h" 1.9 + 1.10 +#include "nsCOMPtr.h" 1.11 +#include "nsDOMFile.h" 1.12 +#include "nsILocalFile.h" 1.13 +#include "Layers.h" 1.14 +#include "ImageContainer.h" 1.15 +#include "ImageTypes.h" 1.16 +#include "prmem.h" 1.17 +#include "nsContentUtils.h" 1.18 + 1.19 +#include "nsIFilePicker.h" 1.20 +#include "nsIPrefService.h" 1.21 +#include "nsIPrefBranch.h" 1.22 + 1.23 +#ifdef MOZ_WIDGET_ANDROID 1.24 +#include "AndroidBridge.h" 1.25 +#include "nsISupportsUtils.h" 1.26 +#endif 1.27 + 1.28 +#if defined(MOZ_WEBRTC) && defined(MOZ_WEBRTC_SIGNALING) 1.29 +#include "YuvStamper.h" 1.30 +#endif 1.31 + 1.32 +#define VIDEO_RATE USECS_PER_S 1.33 +#define AUDIO_RATE 16000 1.34 +#define AUDIO_FRAME_LENGTH ((AUDIO_RATE * MediaEngine::DEFAULT_AUDIO_TIMER_MS) / 1000) 1.35 +namespace mozilla { 1.36 + 1.37 +using namespace mozilla::gfx; 1.38 + 1.39 +NS_IMPL_ISUPPORTS(MediaEngineDefaultVideoSource, nsITimerCallback) 1.40 +/** 1.41 + * Default video source. 1.42 + */ 1.43 + 1.44 +MediaEngineDefaultVideoSource::MediaEngineDefaultVideoSource() 1.45 + : mTimer(nullptr), mMonitor("Fake video") 1.46 +{ 1.47 + mImageContainer = layers::LayerManager::CreateImageContainer(); 1.48 + mState = kReleased; 1.49 +} 1.50 + 1.51 +MediaEngineDefaultVideoSource::~MediaEngineDefaultVideoSource() 1.52 +{} 1.53 + 1.54 +void 1.55 +MediaEngineDefaultVideoSource::GetName(nsAString& aName) 1.56 +{ 1.57 + aName.Assign(NS_LITERAL_STRING("Default Video Device")); 1.58 + return; 1.59 +} 1.60 + 1.61 +void 1.62 +MediaEngineDefaultVideoSource::GetUUID(nsAString& aUUID) 1.63 +{ 1.64 + aUUID.Assign(NS_LITERAL_STRING("1041FCBD-3F12-4F7B-9E9B-1EC556DD5676")); 1.65 + return; 1.66 +} 1.67 + 1.68 +nsresult 1.69 +MediaEngineDefaultVideoSource::Allocate(const VideoTrackConstraintsN &aConstraints, 1.70 + const MediaEnginePrefs &aPrefs) 1.71 +{ 1.72 + if (mState != kReleased) { 1.73 + return NS_ERROR_FAILURE; 1.74 + } 1.75 + 1.76 + mOpts = aPrefs; 1.77 + mOpts.mWidth = mOpts.mWidth ? mOpts.mWidth : MediaEngine::DEFAULT_43_VIDEO_WIDTH; 1.78 + mOpts.mHeight = mOpts.mHeight ? mOpts.mHeight : MediaEngine::DEFAULT_43_VIDEO_HEIGHT; 1.79 + mState = kAllocated; 1.80 + return NS_OK; 1.81 +} 1.82 + 1.83 +nsresult 1.84 +MediaEngineDefaultVideoSource::Deallocate() 1.85 +{ 1.86 + if (mState != kStopped && mState != kAllocated) { 1.87 + return NS_ERROR_FAILURE; 1.88 + } 1.89 + mState = kReleased; 1.90 + return NS_OK; 1.91 +} 1.92 + 1.93 +static void AllocateSolidColorFrame(layers::PlanarYCbCrData& aData, 1.94 + int aWidth, int aHeight, 1.95 + int aY, int aCb, int aCr) 1.96 +{ 1.97 + MOZ_ASSERT(!(aWidth&1)); 1.98 + MOZ_ASSERT(!(aHeight&1)); 1.99 + // Allocate a single frame with a solid color 1.100 + int yLen = aWidth*aHeight; 1.101 + int cbLen = yLen>>2; 1.102 + int crLen = cbLen; 1.103 + uint8_t* frame = (uint8_t*) PR_Malloc(yLen+cbLen+crLen); 1.104 + memset(frame, aY, yLen); 1.105 + memset(frame+yLen, aCb, cbLen); 1.106 + memset(frame+yLen+cbLen, aCr, crLen); 1.107 + 1.108 + aData.mYChannel = frame; 1.109 + aData.mYSize = IntSize(aWidth, aHeight); 1.110 + aData.mYStride = aWidth; 1.111 + aData.mCbCrStride = aWidth>>1; 1.112 + aData.mCbChannel = frame + yLen; 1.113 + aData.mCrChannel = aData.mCbChannel + cbLen; 1.114 + aData.mCbCrSize = IntSize(aWidth>>1, aHeight>>1); 1.115 + aData.mPicX = 0; 1.116 + aData.mPicY = 0; 1.117 + aData.mPicSize = IntSize(aWidth, aHeight); 1.118 + aData.mStereoMode = StereoMode::MONO; 1.119 +} 1.120 + 1.121 +static void ReleaseFrame(layers::PlanarYCbCrData& aData) 1.122 +{ 1.123 + PR_Free(aData.mYChannel); 1.124 +} 1.125 + 1.126 +nsresult 1.127 +MediaEngineDefaultVideoSource::Start(SourceMediaStream* aStream, TrackID aID) 1.128 +{ 1.129 + if (mState != kAllocated) { 1.130 + return NS_ERROR_FAILURE; 1.131 + } 1.132 + 1.133 + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); 1.134 + if (!mTimer) { 1.135 + return NS_ERROR_FAILURE; 1.136 + } 1.137 + 1.138 + aStream->AddTrack(aID, VIDEO_RATE, 0, new VideoSegment()); 1.139 + aStream->AdvanceKnownTracksTime(STREAM_TIME_MAX); 1.140 + 1.141 + // Remember TrackID so we can end it later 1.142 + mTrackID = aID; 1.143 + 1.144 + // Start timer for subsequent frames 1.145 +#if defined(MOZ_WIDGET_GONK) && defined(DEBUG) 1.146 +// B2G emulator debug is very, very slow and has problems dealing with realtime audio inputs 1.147 + mTimer->InitWithCallback(this, (1000 / mOpts.mFPS)*10, nsITimer::TYPE_REPEATING_SLACK); 1.148 +#else 1.149 + mTimer->InitWithCallback(this, 1000 / mOpts.mFPS, nsITimer::TYPE_REPEATING_SLACK); 1.150 +#endif 1.151 + mState = kStarted; 1.152 + 1.153 + return NS_OK; 1.154 +} 1.155 + 1.156 +nsresult 1.157 +MediaEngineDefaultVideoSource::Stop(SourceMediaStream *aSource, TrackID aID) 1.158 +{ 1.159 + if (mState != kStarted) { 1.160 + return NS_ERROR_FAILURE; 1.161 + } 1.162 + if (!mTimer) { 1.163 + return NS_ERROR_FAILURE; 1.164 + } 1.165 + 1.166 + mTimer->Cancel(); 1.167 + mTimer = nullptr; 1.168 + 1.169 + aSource->EndTrack(aID); 1.170 + aSource->Finish(); 1.171 + 1.172 + mState = kStopped; 1.173 + return NS_OK; 1.174 +} 1.175 + 1.176 +nsresult 1.177 +MediaEngineDefaultVideoSource::Snapshot(uint32_t aDuration, nsIDOMFile** aFile) 1.178 +{ 1.179 + *aFile = nullptr; 1.180 + 1.181 +#ifndef MOZ_WIDGET_ANDROID 1.182 + return NS_ERROR_NOT_IMPLEMENTED; 1.183 +#else 1.184 + nsAutoString filePath; 1.185 + nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1"); 1.186 + if (!filePicker) 1.187 + return NS_ERROR_FAILURE; 1.188 + 1.189 + nsXPIDLString title; 1.190 + nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, "Browse", title); 1.191 + int16_t mode = static_cast<int16_t>(nsIFilePicker::modeOpen); 1.192 + 1.193 + nsresult rv = filePicker->Init(nullptr, title, mode); 1.194 + NS_ENSURE_SUCCESS(rv, rv); 1.195 + filePicker->AppendFilters(nsIFilePicker::filterImages); 1.196 + 1.197 + // XXX - This API should be made async 1.198 + PRInt16 dialogReturn; 1.199 + rv = filePicker->Show(&dialogReturn); 1.200 + NS_ENSURE_SUCCESS(rv, rv); 1.201 + if (dialogReturn == nsIFilePicker::returnCancel) { 1.202 + *aFile = nullptr; 1.203 + return NS_OK; 1.204 + } 1.205 + 1.206 + nsCOMPtr<nsIFile> localFile; 1.207 + filePicker->GetFile(getter_AddRefs(localFile)); 1.208 + 1.209 + if (!localFile) { 1.210 + *aFile = nullptr; 1.211 + return NS_OK; 1.212 + } 1.213 + 1.214 + nsCOMPtr<nsIDOMFile> domFile = new nsDOMFileFile(localFile); 1.215 + domFile.forget(aFile); 1.216 + return NS_OK; 1.217 +#endif 1.218 +} 1.219 + 1.220 +NS_IMETHODIMP 1.221 +MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer) 1.222 +{ 1.223 + // Update the target color 1.224 + if (mCr <= 16) { 1.225 + if (mCb < 240) { 1.226 + mCb++; 1.227 + } else { 1.228 + mCr++; 1.229 + } 1.230 + } else if (mCb >= 240) { 1.231 + if (mCr < 240) { 1.232 + mCr++; 1.233 + } else { 1.234 + mCb--; 1.235 + } 1.236 + } else if (mCr >= 240) { 1.237 + if (mCb > 16) { 1.238 + mCb--; 1.239 + } else { 1.240 + mCr--; 1.241 + } 1.242 + } else { 1.243 + mCr--; 1.244 + } 1.245 + 1.246 + // Allocate a single solid color image 1.247 + nsRefPtr<layers::Image> image = mImageContainer->CreateImage(ImageFormat::PLANAR_YCBCR); 1.248 + nsRefPtr<layers::PlanarYCbCrImage> ycbcr_image = 1.249 + static_cast<layers::PlanarYCbCrImage*>(image.get()); 1.250 + layers::PlanarYCbCrData data; 1.251 + AllocateSolidColorFrame(data, mOpts.mWidth, mOpts.mHeight, 0x80, mCb, mCr); 1.252 + 1.253 +#if defined(MOZ_WEBRTC) && defined(MOZ_WEBRTC_SIGNALING) 1.254 + uint64_t timestamp = PR_Now(); 1.255 + YuvStamper::Encode(mOpts.mWidth, mOpts.mHeight, mOpts.mWidth, 1.256 + data.mYChannel, 1.257 + reinterpret_cast<unsigned char*>(×tamp), sizeof(timestamp), 1.258 + 0, 0); 1.259 +#endif 1.260 + 1.261 + ycbcr_image->SetData(data); 1.262 + // SetData copies data, so we can free the frame 1.263 + ReleaseFrame(data); 1.264 + 1.265 + MonitorAutoLock lock(mMonitor); 1.266 + 1.267 + // implicitly releases last image 1.268 + mImage = ycbcr_image.forget(); 1.269 + 1.270 + return NS_OK; 1.271 +} 1.272 + 1.273 +void 1.274 +MediaEngineDefaultVideoSource::NotifyPull(MediaStreamGraph* aGraph, 1.275 + SourceMediaStream *aSource, 1.276 + TrackID aID, 1.277 + StreamTime aDesiredTime, 1.278 + TrackTicks &aLastEndTime) 1.279 +{ 1.280 + // AddTrack takes ownership of segment 1.281 + VideoSegment segment; 1.282 + MonitorAutoLock lock(mMonitor); 1.283 + if (mState != kStarted) { 1.284 + return; 1.285 + } 1.286 + 1.287 + // Note: we're not giving up mImage here 1.288 + nsRefPtr<layers::Image> image = mImage; 1.289 + TrackTicks target = TimeToTicksRoundUp(USECS_PER_S, aDesiredTime); 1.290 + TrackTicks delta = target - aLastEndTime; 1.291 + 1.292 + if (delta > 0) { 1.293 + // nullptr images are allowed 1.294 + IntSize size(image ? mOpts.mWidth : 0, image ? mOpts.mHeight : 0); 1.295 + segment.AppendFrame(image.forget(), delta, size); 1.296 + // This can fail if either a) we haven't added the track yet, or b) 1.297 + // we've removed or finished the track. 1.298 + if (aSource->AppendToTrack(aID, &segment)) { 1.299 + aLastEndTime = target; 1.300 + } 1.301 + } 1.302 +} 1.303 + 1.304 +// generate 1k sine wave per second 1.305 +class SineWaveGenerator 1.306 +{ 1.307 +public: 1.308 + static const int bytesPerSample = 2; 1.309 + static const int millisecondsPerSecond = 1000; 1.310 + static const int frequency = 1000; 1.311 + 1.312 + SineWaveGenerator(int aSampleRate) : 1.313 + mTotalLength(aSampleRate / frequency), 1.314 + mReadLength(0) { 1.315 + MOZ_ASSERT(mTotalLength * frequency == aSampleRate); 1.316 + mAudioBuffer = new int16_t[mTotalLength]; 1.317 + for(int i = 0; i < mTotalLength; i++) { 1.318 + // Set volume to -20db. It's from 32768.0 * 10^(-20/20) = 3276.8 1.319 + mAudioBuffer[i] = (3276.8f * sin(2 * M_PI * i / mTotalLength)); 1.320 + } 1.321 + } 1.322 + 1.323 + // NOTE: only safely called from a single thread (MSG callback) 1.324 + void generate(int16_t* aBuffer, int16_t aLengthInSamples) { 1.325 + int16_t remaining = aLengthInSamples; 1.326 + 1.327 + while (remaining) { 1.328 + int16_t processSamples = 0; 1.329 + 1.330 + if (mTotalLength - mReadLength >= remaining) { 1.331 + processSamples = remaining; 1.332 + } else { 1.333 + processSamples = mTotalLength - mReadLength; 1.334 + } 1.335 + memcpy(aBuffer, mAudioBuffer + mReadLength, processSamples * bytesPerSample); 1.336 + aBuffer += processSamples; 1.337 + mReadLength += processSamples; 1.338 + remaining -= processSamples; 1.339 + if (mReadLength == mTotalLength) { 1.340 + mReadLength = 0; 1.341 + } 1.342 + } 1.343 + } 1.344 + 1.345 +private: 1.346 + nsAutoArrayPtr<int16_t> mAudioBuffer; 1.347 + int16_t mTotalLength; 1.348 + int16_t mReadLength; 1.349 +}; 1.350 + 1.351 +/** 1.352 + * Default audio source. 1.353 + */ 1.354 +NS_IMPL_ISUPPORTS(MediaEngineDefaultAudioSource, nsITimerCallback) 1.355 + 1.356 +MediaEngineDefaultAudioSource::MediaEngineDefaultAudioSource() 1.357 + : mTimer(nullptr) 1.358 +{ 1.359 + mState = kReleased; 1.360 +} 1.361 + 1.362 +MediaEngineDefaultAudioSource::~MediaEngineDefaultAudioSource() 1.363 +{} 1.364 + 1.365 +void 1.366 +MediaEngineDefaultAudioSource::GetName(nsAString& aName) 1.367 +{ 1.368 + aName.Assign(NS_LITERAL_STRING("Default Audio Device")); 1.369 + return; 1.370 +} 1.371 + 1.372 +void 1.373 +MediaEngineDefaultAudioSource::GetUUID(nsAString& aUUID) 1.374 +{ 1.375 + aUUID.Assign(NS_LITERAL_STRING("B7CBD7C1-53EF-42F9-8353-73F61C70C092")); 1.376 + return; 1.377 +} 1.378 + 1.379 +nsresult 1.380 +MediaEngineDefaultAudioSource::Allocate(const AudioTrackConstraintsN &aConstraints, 1.381 + const MediaEnginePrefs &aPrefs) 1.382 +{ 1.383 + if (mState != kReleased) { 1.384 + return NS_ERROR_FAILURE; 1.385 + } 1.386 + 1.387 + mState = kAllocated; 1.388 + // generate 1Khz sine wave 1.389 + mSineGenerator = new SineWaveGenerator(AUDIO_RATE); 1.390 + return NS_OK; 1.391 +} 1.392 + 1.393 +nsresult 1.394 +MediaEngineDefaultAudioSource::Deallocate() 1.395 +{ 1.396 + if (mState != kStopped && mState != kAllocated) { 1.397 + return NS_ERROR_FAILURE; 1.398 + } 1.399 + mState = kReleased; 1.400 + return NS_OK; 1.401 +} 1.402 + 1.403 +nsresult 1.404 +MediaEngineDefaultAudioSource::Start(SourceMediaStream* aStream, TrackID aID) 1.405 +{ 1.406 + if (mState != kAllocated) { 1.407 + return NS_ERROR_FAILURE; 1.408 + } 1.409 + 1.410 + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); 1.411 + if (!mTimer) { 1.412 + return NS_ERROR_FAILURE; 1.413 + } 1.414 + 1.415 + mSource = aStream; 1.416 + 1.417 + // AddTrack will take ownership of segment 1.418 + AudioSegment* segment = new AudioSegment(); 1.419 + mSource->AddTrack(aID, AUDIO_RATE, 0, segment); 1.420 + 1.421 + // We aren't going to add any more tracks 1.422 + mSource->AdvanceKnownTracksTime(STREAM_TIME_MAX); 1.423 + 1.424 + // Remember TrackID so we can finish later 1.425 + mTrackID = aID; 1.426 + 1.427 + // 1 Audio frame per 10ms 1.428 +#if defined(MOZ_WIDGET_GONK) && defined(DEBUG) 1.429 +// B2G emulator debug is very, very slow and has problems dealing with realtime audio inputs 1.430 + mTimer->InitWithCallback(this, MediaEngine::DEFAULT_AUDIO_TIMER_MS*10, 1.431 + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); 1.432 +#else 1.433 + mTimer->InitWithCallback(this, MediaEngine::DEFAULT_AUDIO_TIMER_MS, 1.434 + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); 1.435 +#endif 1.436 + mState = kStarted; 1.437 + 1.438 + return NS_OK; 1.439 +} 1.440 + 1.441 +nsresult 1.442 +MediaEngineDefaultAudioSource::Stop(SourceMediaStream *aSource, TrackID aID) 1.443 +{ 1.444 + if (mState != kStarted) { 1.445 + return NS_ERROR_FAILURE; 1.446 + } 1.447 + if (!mTimer) { 1.448 + return NS_ERROR_FAILURE; 1.449 + } 1.450 + 1.451 + mTimer->Cancel(); 1.452 + mTimer = nullptr; 1.453 + 1.454 + aSource->EndTrack(aID); 1.455 + aSource->Finish(); 1.456 + 1.457 + mState = kStopped; 1.458 + return NS_OK; 1.459 +} 1.460 + 1.461 +nsresult 1.462 +MediaEngineDefaultAudioSource::Snapshot(uint32_t aDuration, nsIDOMFile** aFile) 1.463 +{ 1.464 + return NS_ERROR_NOT_IMPLEMENTED; 1.465 +} 1.466 + 1.467 +NS_IMETHODIMP 1.468 +MediaEngineDefaultAudioSource::Notify(nsITimer* aTimer) 1.469 +{ 1.470 + AudioSegment segment; 1.471 + nsRefPtr<SharedBuffer> buffer = SharedBuffer::Create(AUDIO_FRAME_LENGTH * sizeof(int16_t)); 1.472 + int16_t* dest = static_cast<int16_t*>(buffer->Data()); 1.473 + 1.474 + mSineGenerator->generate(dest, AUDIO_FRAME_LENGTH); 1.475 + nsAutoTArray<const int16_t*,1> channels; 1.476 + channels.AppendElement(dest); 1.477 + segment.AppendFrames(buffer.forget(), channels, AUDIO_FRAME_LENGTH); 1.478 + mSource->AppendToTrack(mTrackID, &segment); 1.479 + 1.480 + return NS_OK; 1.481 +} 1.482 + 1.483 +void 1.484 +MediaEngineDefault::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >* aVSources) { 1.485 + MutexAutoLock lock(mMutex); 1.486 + 1.487 + // We once had code here to find a VideoSource with the same settings and re-use that. 1.488 + // This no longer is possible since the resolution is being set in Allocate(). 1.489 + 1.490 + nsRefPtr<MediaEngineVideoSource> newSource = new MediaEngineDefaultVideoSource(); 1.491 + mVSources.AppendElement(newSource); 1.492 + aVSources->AppendElement(newSource); 1.493 + 1.494 + return; 1.495 +} 1.496 + 1.497 +void 1.498 +MediaEngineDefault::EnumerateAudioDevices(nsTArray<nsRefPtr<MediaEngineAudioSource> >* aASources) { 1.499 + MutexAutoLock lock(mMutex); 1.500 + int32_t len = mASources.Length(); 1.501 + 1.502 + for (int32_t i = 0; i < len; i++) { 1.503 + nsRefPtr<MediaEngineAudioSource> source = mASources.ElementAt(i); 1.504 + if (source->IsAvailable()) { 1.505 + aASources->AppendElement(source); 1.506 + } 1.507 + } 1.508 + 1.509 + // All streams are currently busy, just make a new one. 1.510 + if (aASources->Length() == 0) { 1.511 + nsRefPtr<MediaEngineAudioSource> newSource = 1.512 + new MediaEngineDefaultAudioSource(); 1.513 + mASources.AppendElement(newSource); 1.514 + aASources->AppendElement(newSource); 1.515 + } 1.516 + return; 1.517 +} 1.518 + 1.519 +} // namespace mozilla