Bug 1030007 - Throttle updating the preview window when CPU low and/or encoder falls behind. r=mikeh, r=cpearce, a=2.0+
authorAndrew Osmond <aosmond>
Thu, 03 Jul 2014 10:18:00 -0400
changeset 207728 c80b2f2af26ecdc6ab13e194f3e5f0ce7b7aa9b1
parent 207727 4272b20e0667245ac3c692024ad621b669e4ab00
child 207729 9a73d3e7acf52262d50a008048671f0f8e01dbd5
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikeh, cpearce, 2
bugs1030007
milestone32.0a2
Bug 1030007 - Throttle updating the preview window when CPU low and/or encoder falls behind. r=mikeh, r=cpearce, a=2.0+
dom/camera/CameraControlImpl.cpp
dom/camera/CameraControlImpl.h
dom/camera/CameraControlListener.h
dom/camera/CameraPreviewMediaStream.cpp
dom/camera/CameraPreviewMediaStream.h
dom/camera/DOMCameraControl.h
dom/camera/DOMCameraControlListener.cpp
dom/camera/DOMCameraControlListener.h
dom/camera/GonkCameraControl.cpp
dom/camera/GonkCameraControl.h
dom/camera/GonkCameraHwMgr.cpp
dom/camera/GonkCameraHwMgr.h
dom/camera/GonkCameraSource.cpp
dom/camera/GonkCameraSource.h
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -243,16 +243,30 @@ CameraControlImpl::OnPreviewStateChange(
   mPreviewState = aNewState;
 
   for (uint32_t i = 0; i < mListeners.Length(); ++i) {
     CameraControlListener* l = mListeners[i];
     l->OnPreviewStateChange(mPreviewState);
   }
 }
 
+void
+CameraControlImpl::OnRateLimitPreview(bool aLimit)
+{
+  // This function runs on neither the Main Thread nor the Camera Thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  DOM_CAMERA_LOGI("OnRateLimitPreview: %d\n", aLimit);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnRateLimitPreview(aLimit);
+  }
+}
+
 bool
 CameraControlImpl::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight)
 {
   // This function runs on neither the Main Thread nor the Camera Thread.
   //  On Gonk, it is called from the camera driver's preview thread.
   RwLockAutoEnterRead lock(mListenerLock);
 
   DOM_CAMERA_LOGI("OnNewPreviewFrame: we have %d preview frame listener(s)\n",
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -62,16 +62,17 @@ public:
   void OnAutoFocusMoving(bool aIsMoving);
 
 protected:
   // Event handlers.
   void OnAutoFocusComplete(bool aAutoFocusSucceeded);
   void OnFacesDetected(const nsTArray<Face>& aFaces);
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
 
+  void OnRateLimitPreview(bool aLimit);
   bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
   void OnRecorderStateChange(CameraControlListener::RecorderState aState,
                              int32_t aStatus = -1, int32_t aTrackNumber = -1);
   void OnPreviewStateChange(CameraControlListener::PreviewState aState);
   void OnHardwareStateChange(CameraControlListener::HardwareState aState);
   void OnConfigurationChange();
 
   // When we create a new CameraThread, we keep a static reference to it so
--- a/dom/camera/CameraControlListener.h
+++ b/dom/camera/CameraControlListener.h
@@ -59,16 +59,17 @@ public:
     kMediaRecorderFailed,
     kMediaServerFailed
 #endif
   };
   enum { kNoTrackNumber = -1 };
   virtual void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum) { }
 
   virtual void OnShutter() { }
+  virtual void OnRateLimitPreview(bool aLimit) { }
   virtual bool OnNewPreviewFrame(layers::Image* aFrame, uint32_t aWidth, uint32_t aHeight)
   {
     return false;
   }
 
   class CameraListenerConfiguration : public ICameraControl::Configuration
   {
   public:
--- a/dom/camera/CameraPreviewMediaStream.cpp
+++ b/dom/camera/CameraPreviewMediaStream.cpp
@@ -1,24 +1,35 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
 /* 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 "CameraPreviewMediaStream.h"
+#include "CameraCommon.h"
+
+/**
+ * Maximum number of outstanding invalidates before we start to drop frames;
+ * if we hit this threshold, it is an indicator that the main thread is
+ * either very busy or the device is busy elsewhere (e.g. encoding or
+ * persisting video data).
+ */
+#define MAX_INVALIDATE_PENDING 4
 
 using namespace mozilla::layers;
 using namespace mozilla::dom;
 
 namespace mozilla {
 
 CameraPreviewMediaStream::CameraPreviewMediaStream(DOMMediaStream* aWrapper)
   : MediaStream(aWrapper)
   , mMutex("mozilla::camera::CameraPreviewMediaStream")
-  , mFrameCallback(nullptr)
+  , mInvalidatePending(0)
+  , mDiscardedFrames(0)
+  , mRateLimit(false)
 {
   SetGraphImpl(MediaStreamGraph::GetInstance());
   mIsConsumed = false;
 }
 
 void
 CameraPreviewMediaStream::AddAudioOutput(void* aKey)
 {
@@ -98,40 +109,71 @@ CameraPreviewMediaStream::RemoveListener
 void
 CameraPreviewMediaStream::Destroy()
 {
   MutexAutoLock lock(mMutex);
   DestroyImpl();
 }
 
 void
-CameraPreviewMediaStream::SetCurrentFrame(const gfxIntSize& aIntrinsicSize, Image* aImage)
+CameraPreviewMediaStream::Invalidate()
 {
   MutexAutoLock lock(mMutex);
-
-  TimeStamp now = TimeStamp::Now();
-  for (uint32_t i = 0; i < mVideoOutputs.Length(); ++i) {
+  --mInvalidatePending;
+  for (nsTArray<nsRefPtr<VideoFrameContainer> >::size_type i = 0; i < mVideoOutputs.Length(); ++i) {
     VideoFrameContainer* output = mVideoOutputs[i];
-    output->SetCurrentFrame(aIntrinsicSize, aImage, now);
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(output, &VideoFrameContainer::Invalidate);
-    NS_DispatchToMainThread(event);
+    output->Invalidate();
+  }
+}
+
+void
+CameraPreviewMediaStream::RateLimit(bool aLimit)
+{
+  mRateLimit = aLimit;
+}
+
+void
+CameraPreviewMediaStream::SetCurrentFrame(const gfxIntSize& aIntrinsicSize, Image* aImage)
+{
+  {
+    MutexAutoLock lock(mMutex);
+
+    if (mInvalidatePending > 0) {
+      if (mRateLimit || mInvalidatePending > MAX_INVALIDATE_PENDING) {
+        ++mDiscardedFrames;
+        DOM_CAMERA_LOGW("Discard preview frame %d, %d invalidation(s) pending",
+          mDiscardedFrames, mInvalidatePending);
+        return;
+      }
+
+      DOM_CAMERA_LOGI("Update preview frame, %d invalidation(s) pending",
+        mInvalidatePending);
+    }
+    mDiscardedFrames = 0;
+
+    TimeStamp now = TimeStamp::Now();
+    for (nsTArray<nsRefPtr<VideoFrameContainer> >::size_type i = 0; i < mVideoOutputs.Length(); ++i) {
+      VideoFrameContainer* output = mVideoOutputs[i];
+      output->SetCurrentFrame(aIntrinsicSize, aImage, now);
+    }
+
+    ++mInvalidatePending;
   }
 
-  if (mFrameCallback) {
-    mFrameCallback->OnNewFrame(aIntrinsicSize, aImage);
-  }
+  nsCOMPtr<nsIRunnable> event =
+    NS_NewRunnableMethod(this, &CameraPreviewMediaStream::Invalidate);
+  NS_DispatchToMainThread(event);
 }
 
 void
 CameraPreviewMediaStream::ClearCurrentFrame()
 {
   MutexAutoLock lock(mMutex);
 
-  for (uint32_t i = 0; i < mVideoOutputs.Length(); ++i) {
+  for (nsTArray<nsRefPtr<VideoFrameContainer> >::size_type i = 0; i < mVideoOutputs.Length(); ++i) {
     VideoFrameContainer* output = mVideoOutputs[i];
     output->ClearCurrentFrame();
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(output, &VideoFrameContainer::Invalidate);
     NS_DispatchToMainThread(event);
   }
 }
 
--- a/dom/camera/CameraPreviewMediaStream.h
+++ b/dom/camera/CameraPreviewMediaStream.h
@@ -6,23 +6,18 @@
 #define DOM_CAMERA_CAMERAPREVIEWMEDIASTREAM_H
 
 #include "VideoFrameContainer.h"
 #include "MediaStreamGraph.h"
 #include "mozilla/Mutex.h"
 
 namespace mozilla {
 
-class CameraPreviewFrameCallback {
-public:
-  virtual void OnNewFrame(const gfxIntSize& aIntrinsicSize, layers::Image* aImage) = 0;
-};
-
 /**
- * This is a stream for camere preview.
+ * This is a stream for camera preview.
  *
  * XXX It is a temporary fix of SourceMediaStream.
  * A camera preview requests no delay and no buffering stream.
  * But the SourceMediaStream do not support it.
  */
 class CameraPreviewMediaStream : public MediaStream
 {
   typedef mozilla::layers::Image Image;
@@ -35,27 +30,28 @@ public:
   virtual void RemoveAudioOutput(void* aKey) MOZ_OVERRIDE;
   virtual void AddVideoOutput(VideoFrameContainer* aContainer) MOZ_OVERRIDE;
   virtual void RemoveVideoOutput(VideoFrameContainer* aContainer) MOZ_OVERRIDE;
   virtual void ChangeExplicitBlockerCount(int32_t aDelta) MOZ_OVERRIDE;
   virtual void AddListener(MediaStreamListener* aListener) MOZ_OVERRIDE;
   virtual void RemoveListener(MediaStreamListener* aListener) MOZ_OVERRIDE;
   virtual void Destroy();
 
+  void Invalidate();
+
   // Call these on any thread.
   void SetCurrentFrame(const gfxIntSize& aIntrinsicSize, Image* aImage);
   void ClearCurrentFrame();
-
-  void SetFrameCallback(CameraPreviewFrameCallback* aCallback) {
-    mFrameCallback = aCallback;
-  }
+  void RateLimit(bool aLimit);
 
 protected:
   // mMutex protects all the class' fields.
   // This class is not registered to MediaStreamGraph.
   // It needs to protect all the fields.
   Mutex mMutex;
-  CameraPreviewFrameCallback* mFrameCallback;
+  int32_t mInvalidatePending;
+  uint32_t mDiscardedFrames;
+  bool mRateLimit;
 };
 
 }
 
 #endif // DOM_CAMERA_CAMERAPREVIEWMEDIASTREAM_H
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -209,17 +209,17 @@ protected:
   nsRefPtr<dom::CameraFaceDetectionCallback>    mOnFacesDetectedCb;
 
   // Camera event listener; we only need this weak reference so that
   //  we can remove the listener from the camera when we're done
   //  with it.
   DOMCameraControlListener* mListener;
 
   // our viewfinder stream
-  CameraPreviewMediaStream* mInput;
+  nsRefPtr<CameraPreviewMediaStream> mInput;
 
   // set once when this object is created
   nsCOMPtr<nsPIDOMWindow>   mWindow;
 
   dom::CameraStartRecordingOptions mOptions;
   nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
 
 private:
--- a/dom/camera/DOMCameraControlListener.cpp
+++ b/dom/camera/DOMCameraControlListener.cpp
@@ -282,16 +282,22 @@ DOMCameraControlListener::OnShutter()
     {
       aDOMCameraControl->OnShutter();
     }
   };
 
   NS_DispatchToMainThread(new Callback(mDOMCameraControl));
 }
 
+void
+DOMCameraControlListener::OnRateLimitPreview(bool aLimit)
+{
+  mStream->RateLimit(aLimit);
+}
+
 bool
 DOMCameraControlListener::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight)
 {
   DOM_CAMERA_LOGI("OnNewPreviewFrame: got %d x %d frame\n", aWidth, aHeight);
 
   mStream->SetCurrentFrame(gfxIntSize(aWidth, aHeight), aImage);
   return true;
 }
--- a/dom/camera/DOMCameraControlListener.h
+++ b/dom/camera/DOMCameraControlListener.h
@@ -23,16 +23,17 @@ public:
   virtual void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces) MOZ_OVERRIDE;
   virtual void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) MOZ_OVERRIDE;
 
   virtual void OnHardwareStateChange(HardwareState aState) MOZ_OVERRIDE;
   virtual void OnPreviewStateChange(PreviewState aState) MOZ_OVERRIDE;
   virtual void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum) MOZ_OVERRIDE;
   virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) MOZ_OVERRIDE;
   virtual void OnShutter() MOZ_OVERRIDE;
+  virtual void OnRateLimitPreview(bool aLimit) MOZ_OVERRIDE;
   virtual bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) MOZ_OVERRIDE;
   virtual void OnUserError(UserContext aContext, nsresult aError) MOZ_OVERRIDE;
 
 protected:
   virtual ~DOMCameraControlListener();
 
   nsMainThreadPtrHandle<nsDOMCameraControl> mDOMCameraControl;
   CameraPreviewMediaStream* mStream;
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -1672,16 +1672,22 @@ nsGonkCameraControl::GetGonkRecorderProf
 already_AddRefed<RecorderProfileManager>
 nsGonkCameraControl::GetRecorderProfileManagerImpl()
 {
   nsRefPtr<RecorderProfileManager> profileMgr = GetGonkRecorderProfileManager();
   return profileMgr.forget();
 }
 
 void
+nsGonkCameraControl::OnRateLimitPreview(bool aLimit)
+{
+  CameraControlImpl::OnRateLimitPreview(aLimit);
+}
+
+void
 nsGonkCameraControl::OnNewPreviewFrame(layers::TextureClient* aBuffer)
 {
   nsRefPtr<Image> frame = mImageContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR);
 
   GrallocImage* videoImage = static_cast<GrallocImage*>(frame.get());
 
   GrallocImage::GrallocData data;
   data.mGraphicBuffer = aBuffer;
@@ -1734,16 +1740,22 @@ OnAutoFocusMoving(nsGonkCameraControl* g
 
 void
 OnFacesDetected(nsGonkCameraControl* gc, camera_frame_metadata_t* aMetaData)
 {
   gc->OnFacesDetected(aMetaData);
 }
 
 void
+OnRateLimitPreview(nsGonkCameraControl* gc, bool aLimit)
+{
+  gc->OnRateLimitPreview(aLimit);
+}
+
+void
 OnNewPreviewFrame(nsGonkCameraControl* gc, layers::TextureClient* aBuffer)
 {
   gc->OnNewPreviewFrame(aBuffer);
 }
 
 void
 OnShutter(nsGonkCameraControl* gc)
 {
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -47,20 +47,21 @@ class nsGonkCameraControl : public Camer
 {
 public:
   nsGonkCameraControl(uint32_t aCameraId);
 
   void OnAutoFocusComplete(bool aSuccess);
   void OnFacesDetected(camera_frame_metadata_t* aMetaData);
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength);
   void OnTakePictureError();
+  void OnRateLimitPreview(bool aLimit);
   void OnNewPreviewFrame(layers::TextureClient* aBuffer);
   void OnRecorderEvent(int msg, int ext1, int ext2);
   void OnSystemError(CameraControlListener::SystemContext aWhere, nsresult aError);
- 
+
   // See ICameraControl.h for getter/setter return values.
   virtual nsresult Set(uint32_t aKey, const nsAString& aValue) MOZ_OVERRIDE;
   virtual nsresult Get(uint32_t aKey, nsAString& aValue) MOZ_OVERRIDE;
   virtual nsresult Set(uint32_t aKey, double aValue) MOZ_OVERRIDE;
   virtual nsresult Get(uint32_t aKey, double& aValue) MOZ_OVERRIDE;
   virtual nsresult Set(uint32_t aKey, int32_t aValue) MOZ_OVERRIDE;
   virtual nsresult Get(uint32_t aKey, int32_t& aValue) MOZ_OVERRIDE;
   virtual nsresult Set(uint32_t aKey, int64_t aValue) MOZ_OVERRIDE;
@@ -79,16 +80,17 @@ public:
   virtual nsresult Get(uint32_t aKey, nsTArray<double>& aValues) MOZ_OVERRIDE;
 
   nsresult PushParameters();
   nsresult PullParameters();
 
 protected:
   ~nsGonkCameraControl();
 
+  using CameraControlImpl::OnRateLimitPreview;
   using CameraControlImpl::OnNewPreviewFrame;
   using CameraControlImpl::OnAutoFocusComplete;
   using CameraControlImpl::OnFacesDetected;
   using CameraControlImpl::OnTakePictureComplete;
   using CameraControlImpl::OnConfigurationChange;
   using CameraControlImpl::OnUserError;
 
   virtual void BeginBatchParameterSet() MOZ_OVERRIDE;
@@ -169,16 +171,17 @@ protected:
   ReentrantMonitor          mReentrantMonitor;
 
 private:
   nsGonkCameraControl(const nsGonkCameraControl&) MOZ_DELETE;
   nsGonkCameraControl& operator=(const nsGonkCameraControl&) MOZ_DELETE;
 };
 
 // camera driver callbacks
+void OnRateLimitPreview(nsGonkCameraControl* gc, bool aLimit);
 void OnTakePictureComplete(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength);
 void OnTakePictureError(nsGonkCameraControl* gc);
 void OnAutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess);
 void OnAutoFocusMoving(nsGonkCameraControl* gc, bool aIsMoving);
 void OnFacesDetected(nsGonkCameraControl* gc, camera_frame_metadata_t* aMetaData);
 void OnNewPreviewFrame(nsGonkCameraControl* gc, layers::TextureClient* aBuffer);
 void OnShutter(nsGonkCameraControl* gc);
 void OnClosed(nsGonkCameraControl* gc);
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -40,16 +40,22 @@ GonkCameraHardware::GonkCameraHardware(m
   , mCamera(aCamera)
   , mTarget(aTarget)
   , mSensorOrientation(0)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p (aTarget=%p)\n", __func__, __LINE__, (void*)this, (void*)aTarget);
 }
 
 void
+GonkCameraHardware::OnRateLimitPreview(bool aLimit)
+{
+  ::OnRateLimitPreview(mTarget, aLimit);
+}
+
+void
 GonkCameraHardware::OnNewFrame()
 {
   if (mClosing) {
     return;
   }
   RefPtr<TextureClient> buffer = mNativeWindow->getCurrentBuffer();
   if (!buffer) {
     DOM_CAMERA_LOGW("received null frame");
--- a/dom/camera/GonkCameraHwMgr.h
+++ b/dom/camera/GonkCameraHwMgr.h
@@ -50,16 +50,18 @@ protected:
   //  - NS_OK on success;
   //  - NS_ERROR_NOT_INITIALIZED if the interface could not be initialized.
   virtual nsresult Init();
 
 public:
   static sp<GonkCameraHardware> Connect(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId);
   virtual void Close();
 
+  virtual void OnRateLimitPreview(bool aLimit);
+
   // derived from GonkNativeWindowNewFrameCallback
   virtual void OnNewFrame() MOZ_OVERRIDE;
 
   // derived from CameraListener
   virtual void notify(int32_t aMsgType, int32_t ext1, int32_t ext2);
   virtual void postData(int32_t aMsgType, const sp<IMemory>& aDataPtr, camera_frame_metadata_t* metadata);
   virtual void postDataTimestamp(nsecs_t aTimestamp, int32_t aMsgType, const sp<IMemory>& aDataPtr);
 
--- a/dom/camera/GonkCameraSource.cpp
+++ b/dom/camera/GonkCameraSource.cpp
@@ -161,16 +161,17 @@ GonkCameraSource::GonkCameraSource(
     : mCameraFlags(0),
       mNumInputBuffers(0),
       mVideoFrameRate(-1),
       mNumFramesReceived(0),
       mLastFrameTimestampUs(0),
       mStarted(false),
       mNumFramesEncoded(0),
       mTimeBetweenFrameCaptureUs(0),
+      mRateLimit(false),
       mFirstFrameTimeUs(0),
       mNumFramesDropped(0),
       mNumGlitches(0),
       mGlitchDurationThresholdUs(200000),
       mCollectStats(false),
       mCameraHw(aCameraHw) {
     mVideoSize.width  = -1;
     mVideoSize.height = -1;
@@ -580,16 +581,20 @@ status_t GonkCameraSource::reset() {
         if (NO_ERROR !=
             mFrameCompleteCondition.waitRelative(mLock,
                     mTimeBetweenFrameCaptureUs * 1000LL + CAMERA_SOURCE_TIMEOUT_NS)) {
             CS_LOGW("Timed out waiting for outstanding frames being encoded: %d",
                 mFramesBeingEncoded.size());
         }
     }
     stopCameraRecording();
+    if (mRateLimit) {
+      mRateLimit = false;
+      mCameraHw->OnRateLimitPreview(false);
+    }
     releaseCamera();
 
     if (mCollectStats) {
         CS_LOGI("Frames received/encoded/dropped: %d/%d/%d in %lld us",
                 mNumFramesReceived, mNumFramesEncoded, mNumFramesDropped,
                 mLastFrameTimestampUs - mFirstFrameTimeUs);
     }
 
@@ -683,61 +688,75 @@ status_t GonkCameraSource::read(
         (*buffer)->add_ref();
         (*buffer)->meta_data()->setInt64(kKeyTime, frameTime);
     }
     return OK;
 }
 
 void GonkCameraSource::dataCallbackTimestamp(int64_t timestampUs,
         int32_t msgType, const sp<IMemory> &data) {
+    bool rateLimit;
+    bool prevRateLimit;
     CS_LOGV("dataCallbackTimestamp: timestamp %lld us", timestampUs);
-    Mutex::Autolock autoLock(mLock);
-    if (!mStarted || (mNumFramesReceived == 0 && timestampUs < mStartTimeUs)) {
-        CS_LOGV("Drop frame at %lld/%lld us", timestampUs, mStartTimeUs);
-        releaseOneRecordingFrame(data);
-        return;
-    }
+    {
+        Mutex::Autolock autoLock(mLock);
+        if (!mStarted || (mNumFramesReceived == 0 && timestampUs < mStartTimeUs)) {
+            CS_LOGV("Drop frame at %lld/%lld us", timestampUs, mStartTimeUs);
+            releaseOneRecordingFrame(data);
+            return;
+        }
+
+        if (mNumFramesReceived > 0) {
+            CHECK(timestampUs > mLastFrameTimestampUs);
+            if (timestampUs - mLastFrameTimestampUs > mGlitchDurationThresholdUs) {
+                ++mNumGlitches;
+            }
+        }
+
+        // May need to skip frame or modify timestamp. Currently implemented
+        // by the subclass CameraSourceTimeLapse.
+        if (skipCurrentFrame(timestampUs)) {
+            releaseOneRecordingFrame(data);
+            return;
+        }
 
-    if (mNumFramesReceived > 0) {
-        CHECK(timestampUs > mLastFrameTimestampUs);
-        if (timestampUs - mLastFrameTimestampUs > mGlitchDurationThresholdUs) {
-            ++mNumGlitches;
+        mLastFrameTimestampUs = timestampUs;
+        if (mNumFramesReceived == 0) {
+            mFirstFrameTimeUs = timestampUs;
+            // Initial delay
+            if (mStartTimeUs > 0) {
+                if (timestampUs < mStartTimeUs) {
+                    // Frame was captured before recording was started
+                    // Drop it without updating the statistical data.
+                    releaseOneRecordingFrame(data);
+                    return;
+                }
+                mStartTimeUs = timestampUs - mStartTimeUs;
+            }
         }
-    }
+        ++mNumFramesReceived;
+
+        // If a backlog is building up in the receive queue, we are likely
+        // resource constrained and we need to throttle
+        prevRateLimit = mRateLimit;
+        rateLimit = mFramesReceived.empty();
+        mRateLimit = rateLimit;
 
-    // May need to skip frame or modify timestamp. Currently implemented
-    // by the subclass CameraSourceTimeLapse.
-    if (skipCurrentFrame(timestampUs)) {
-        releaseOneRecordingFrame(data);
-        return;
+        CHECK(data != NULL && data->size() > 0);
+        mFramesReceived.push_back(data);
+        int64_t timeUs = mStartTimeUs + (timestampUs - mFirstFrameTimeUs);
+        mFrameTimes.push_back(timeUs);
+        CS_LOGV("initial delay: %lld, current time stamp: %lld",
+            mStartTimeUs, timeUs);
+        mFrameAvailableCondition.signal();
     }
 
-    mLastFrameTimestampUs = timestampUs;
-    if (mNumFramesReceived == 0) {
-        mFirstFrameTimeUs = timestampUs;
-        // Initial delay
-        if (mStartTimeUs > 0) {
-            if (timestampUs < mStartTimeUs) {
-                // Frame was captured before recording was started
-                // Drop it without updating the statistical data.
-                releaseOneRecordingFrame(data);
-                return;
-            }
-            mStartTimeUs = timestampUs - mStartTimeUs;
-        }
+    if(prevRateLimit != rateLimit) {
+        mCameraHw->OnRateLimitPreview(rateLimit);
     }
-    ++mNumFramesReceived;
-
-    CHECK(data != NULL && data->size() > 0);
-    mFramesReceived.push_back(data);
-    int64_t timeUs = mStartTimeUs + (timestampUs - mFirstFrameTimeUs);
-    mFrameTimes.push_back(timeUs);
-    CS_LOGV("initial delay: %lld, current time stamp: %lld",
-        mStartTimeUs, timeUs);
-    mFrameAvailableCondition.signal();
 }
 
 bool GonkCameraSource::isMetaDataStoredInVideoBuffers() const {
     CS_LOGV("isMetaDataStoredInVideoBuffers");
     return mIsMetaDataStoredInVideoBuffers;
 }
 
 }  // namespace android
--- a/dom/camera/GonkCameraSource.h
+++ b/dom/camera/GonkCameraSource.h
@@ -122,16 +122,17 @@ protected:
 private:
 
     Mutex mLock;
     Condition mFrameAvailableCondition;
     Condition mFrameCompleteCondition;
     List<sp<IMemory> > mFramesReceived;
     List<sp<IMemory> > mFramesBeingEncoded;
     List<int64_t> mFrameTimes;
+    bool mRateLimit;
 
     int64_t mFirstFrameTimeUs;
     int32_t mNumFramesDropped;
     int32_t mNumGlitches;
     int64_t mGlitchDurationThresholdUs;
     bool mCollectStats;
     bool mIsMetaDataStoredInVideoBuffers;
     sp<GonkCameraHardware> mCameraHw;