Bug 672755 - Add memory reporters for decoded video and audio queues. r=cpearce r=joe
authorMatthew Gregan <kinetik@flim.org>
Fri, 22 Jul 2011 15:17:23 +1200
changeset 73530 b5705468f96f474a06527e0f549b3c17ef91afdb
parent 73529 799636ad8a0dabd78cb7e5070a09fe28739c29ad
child 73531 a7260d755392e526286d133345198721ece27ddc
push id20885
push usereakhgari@mozilla.com
push dateFri, 29 Jul 2011 15:46:16 +0000
treeherdermozilla-central@8fb752f5e1fa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, joe
bugs672755
milestone8.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 672755 - Add memory reporters for decoded video and audio queues. r=cpearce r=joe
content/media/nsBuiltinDecoder.h
content/media/nsBuiltinDecoderReader.cpp
content/media/nsBuiltinDecoderReader.h
content/media/nsBuiltinDecoderStateMachine.h
content/media/nsMediaDecoder.cpp
content/media/nsMediaDecoder.h
gfx/layers/ImageLayers.h
gfx/layers/basic/BasicImages.cpp
gfx/layers/d3d10/ImageLayerD3D10.cpp
gfx/layers/d3d10/ImageLayerD3D10.h
gfx/layers/d3d9/ImageLayerD3D9.cpp
gfx/layers/d3d9/ImageLayerD3D9.h
gfx/layers/opengl/ImageLayerOGL.h
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -322,16 +322,19 @@ public:
   // and an invalidate of the frame being dispatched asynchronously if
   // there is no such event currently queued.
   // Only called on the decoder thread. Must be called with
   // the decode monitor held.
   virtual void UpdatePlaybackPosition(PRInt64 aTime) = 0;
 
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered) = 0;
 
+  virtual PRInt64 VideoQueueMemoryInUse() = 0;
+  virtual PRInt64 AudioQueueMemoryInUse() = 0;
+
   virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) = 0;
 
   // Causes the state machine to switch to buffering state, and to
   // immediately stop playback and buffer downloaded data. Must be called
   // with the decode monitor held. Called on the state machine thread and
   // the main thread.
   virtual void StartBuffering() = 0;
 
@@ -466,16 +469,30 @@ class nsBuiltinDecoder : public nsMediaD
   // are buffered and playable.
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered) {
     if (mDecoderStateMachine) {
       return mDecoderStateMachine->GetBuffered(aBuffered);
     }
     return NS_ERROR_FAILURE;
   }
 
+  virtual PRInt64 VideoQueueMemoryInUse() {
+    if (mDecoderStateMachine) {
+      return mDecoderStateMachine->VideoQueueMemoryInUse();
+    }
+    return 0;
+  }
+
+  virtual PRInt64 AudioQueueMemoryInUse() {
+    if (mDecoderStateMachine) {
+      return mDecoderStateMachine->AudioQueueMemoryInUse();
+    }
+    return 0;
+  }
+
   virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
     return mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset);
   }
 
   // Sets the length of the framebuffer used in MozAudioAvailable events.
   // The new size must be between 512 and 16384.
   virtual nsresult RequestFrameBufferLength(PRUint32 aLength);
 
--- a/content/media/nsBuiltinDecoderReader.cpp
+++ b/content/media/nsBuiltinDecoderReader.cpp
@@ -45,18 +45,16 @@
 #include "nsBuiltinDecoderStateMachine.h"
 #include "mozilla/mozalloc.h"
 #include "VideoUtils.h"
 
 using namespace mozilla;
 using mozilla::layers::ImageContainer;
 using mozilla::layers::PlanarYCbCrImage;
 
-using mozilla::layers::PlanarYCbCrImage;
-
 // Verify these values are sane. Once we've checked the frame sizes, we then
 // can do less integer overflow checking.
 PR_STATIC_ASSERT(MAX_VIDEO_WIDTH < PlanarYCbCrImage::MAX_DIMENSION);
 PR_STATIC_ASSERT(MAX_VIDEO_HEIGHT < PlanarYCbCrImage::MAX_DIMENSION);
 PR_STATIC_ASSERT(PlanarYCbCrImage::MAX_DIMENSION < PR_UINT32_MAX / PlanarYCbCrImage::MAX_DIMENSION);
 
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
--- a/content/media/nsBuiltinDecoderReader.h
+++ b/content/media/nsBuiltinDecoderReader.h
@@ -385,18 +385,23 @@ template <class T> class MediaQueue : pr
     if (GetSize() < 2) {
       return 0;
     }
     T* last = Peek();
     T* first = PeekFront();
     return last->mTime - first->mTime;
   }
 
+  void LockedForEach(nsDequeFunctor& aFunctor) const {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    ForEach(aFunctor);
+  }
+
 private:
-  ReentrantMonitor mReentrantMonitor;
+  mutable ReentrantMonitor mReentrantMonitor;
 
   // PR_TRUE when we've decoded the last frame of data in the
   // bitstream for which we're queueing sample-data.
   PRBool mEndOfStream;
 };
 
 // Encapsulates the decoding and reading of media data. Reading can only be
 // done on the decode thread thread. Never hold the decoder monitor when
@@ -460,16 +465,58 @@ public:
 
   // Populates aBuffered with the time ranges which are buffered. aStartTime
   // must be the presentation time of the first sample/frame in the media, e.g.
   // the media time corresponding to playback time/position 0. This function
   // should only be called on the main thread.
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered,
                                PRInt64 aStartTime) = 0;
 
+  class VideoQueueMemoryFunctor : public nsDequeFunctor {
+  public:
+    VideoQueueMemoryFunctor() : mResult(0) {}
+
+    virtual void* operator()(void* anObject) {
+      const VideoData* v = static_cast<const VideoData*>(anObject);
+      NS_ASSERTION(v->mImage->GetFormat() == mozilla::layers::Image::PLANAR_YCBCR,
+                   "Wrong format?");
+      mozilla::layers::PlanarYCbCrImage* vi = static_cast<mozilla::layers::PlanarYCbCrImage*>(v->mImage.get());
+
+      mResult += vi->GetDataSize();
+      return nsnull;
+    }
+
+    PRInt64 mResult;
+  };
+
+  PRInt64 VideoQueueMemoryInUse() {
+    VideoQueueMemoryFunctor functor;
+    mVideoQueue.LockedForEach(functor);
+    return functor.mResult;
+  }
+
+  class AudioQueueMemoryFunctor : public nsDequeFunctor {
+  public:
+    AudioQueueMemoryFunctor() : mResult(0) {}
+
+    virtual void* operator()(void* anObject) {
+      const SoundData* soundData = static_cast<const SoundData*>(anObject);
+      mResult += soundData->mSamples * soundData->mChannels * sizeof(SoundDataValue);
+      return nsnull;
+    }
+
+    PRInt64 mResult;
+  };
+
+  PRInt64 AudioQueueMemoryInUse() {
+    AudioQueueMemoryFunctor functor;
+    mAudioQueue.LockedForEach(functor);
+    return functor.mResult;
+  }
+
   // Only used by nsWebMReader for now, so stub here rather than in every
   // reader than inherits from nsBuiltinDecoderReader.
   virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {}
 
 protected:
 
   // Pumps the decode until we reach frames/samples required to play at
   // time aTarget (usecs).
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -223,16 +223,30 @@ public:
   // The decoder monitor must be obtained before modifying this state.
   // NotifyAll on the monitor must be called when the state is changed so
   // that interested threads can wake up and alter behaviour if appropriate
   // Accessed on state machine, audio, main, and AV thread.
   State mState;
 
   nsresult GetBuffered(nsTimeRanges* aBuffered);
 
+  PRInt64 VideoQueueMemoryInUse() {
+    if (mReader) {
+      return mReader->VideoQueueMemoryInUse();
+    }
+    return 0;
+  }
+
+  PRInt64 AudioQueueMemoryInUse() {
+    if (mReader) {
+      return mReader->AudioQueueMemoryInUse();
+    }
+    return 0;
+  }
+
   void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
   }
 
   PRInt64 GetEndMediaTime() const {
     mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
     return mEndTime;
--- a/content/media/nsMediaDecoder.cpp
+++ b/content/media/nsMediaDecoder.cpp
@@ -47,16 +47,17 @@
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsNetUtil.h"
 #include "nsHTMLMediaElement.h"
 #include "gfxContext.h"
 #include "nsPresContext.h"
 #include "nsDOMError.h"
 #include "nsDisplayList.h"
 #include "nsSVGEffects.h"
+#include "VideoUtils.h"
 
 using namespace mozilla;
 
 // Number of milliseconds between progress events as defined by spec
 #define PROGRESS_MS 350
 
 // Number of milliseconds of no data before a stall event is fired as defined by spec
 #define STALL_MS 3000
@@ -76,21 +77,23 @@ nsMediaDecoder::nsMediaDecoder() :
   mVideoUpdateLock("nsMediaDecoder.mVideoUpdateLock"),
   mFrameBufferLength(0),
   mPinnedForSeek(PR_FALSE),
   mSizeChanged(PR_FALSE),
   mImageContainerSizeChanged(PR_FALSE),
   mShuttingDown(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsMediaDecoder);
+  MediaMemoryReporter::AddMediaDecoder(this);
 }
 
 nsMediaDecoder::~nsMediaDecoder()
 {
   MOZ_COUNT_DTOR(nsMediaDecoder);
+  MediaMemoryReporter::RemoveMediaDecoder(this);
 }
 
 PRBool nsMediaDecoder::Init(nsHTMLMediaElement* aElement)
 {
   mElement = aElement;
   return PR_TRUE;
 }
 
@@ -300,8 +303,42 @@ PRBool nsMediaDecoder::CanPlayThrough()
   // our download rate or decode rate estimation is otherwise inaccurate,
   // we don't suddenly discover that we need to buffer. This is particularly
   // required near the start of the media, when not much data is downloaded.
   PRInt64 readAheadMargin =
     static_cast<PRInt64>(stats.mPlaybackRate * CAN_PLAY_THROUGH_MARGIN);
   return stats.mTotalBytes == stats.mDownloadPosition ||
          stats.mDownloadPosition > stats.mPlaybackPosition + readAheadMargin;
 }
+
+namespace mozilla {
+
+MediaMemoryReporter* MediaMemoryReporter::sUniqueInstance;
+
+NS_MEMORY_REPORTER_IMPLEMENT(MediaDecodedVideoMemory,
+                             "explicit/media/decoded-video",
+                             KIND_HEAP,
+                             UNITS_BYTES,
+                             MediaMemoryReporter::GetDecodedVideoMemory,
+                             "Memory used by decoded video frames.")
+
+NS_MEMORY_REPORTER_IMPLEMENT(MediaDecodedAudioMemory,
+                             "explicit/media/decoded-audio",
+                             KIND_HEAP,
+                             UNITS_BYTES,
+                             MediaMemoryReporter::GetDecodedAudioMemory,
+                             "Memory used by decoded audio chunks.")
+
+MediaMemoryReporter::MediaMemoryReporter()
+  : mMediaDecodedVideoMemory(new NS_MEMORY_REPORTER_NAME(MediaDecodedVideoMemory))
+  , mMediaDecodedAudioMemory(new NS_MEMORY_REPORTER_NAME(MediaDecodedAudioMemory))
+{
+  NS_RegisterMemoryReporter(mMediaDecodedVideoMemory);
+  NS_RegisterMemoryReporter(mMediaDecodedAudioMemory);
+}
+
+MediaMemoryReporter::~MediaMemoryReporter()
+{
+  NS_UnregisterMemoryReporter(mMediaDecodedVideoMemory);
+  NS_UnregisterMemoryReporter(mMediaDecodedAudioMemory);
+}
+
+} // namespace mozilla
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -44,16 +44,17 @@
 #include "nsSize.h"
 #include "prlog.h"
 #include "gfxContext.h"
 #include "gfxRect.h"
 #include "nsITimer.h"
 #include "ImageLayers.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Mutex.h"
+#include "nsIMemoryReporter.h"
 
 class nsHTMLMediaElement;
 class nsMediaStream;
 class nsIStreamListener;
 class nsTimeRanges;
 
 // The size to use for audio data frames in MozAudioAvailable events.
 // This value is per channel, and is chosen to give ~43 fps of events,
@@ -364,16 +365,21 @@ public:
   // Constructs the time ranges representing what segments of the media
   // are buffered and playable.
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered) = 0;
 
   // Returns PR_TRUE if we can play the entire media through without stopping
   // to buffer, given the current download and playback rates.
   PRBool CanPlayThrough();
 
+  // Returns the size, in bytes, of the heap memory used by the currently
+  // queued decoded video and audio data.
+  virtual PRInt64 VideoQueueMemoryInUse() = 0;
+  virtual PRInt64 AudioQueueMemoryInUse() = 0;
+
 protected:
 
   // Start timer to update download progress information.
   nsresult StartProgress();
 
   // Stop progress information timer.
   nsresult StopProgress();
 
@@ -453,9 +459,67 @@ protected:
 
   // True if the decoder is being shutdown. At this point all events that
   // are currently queued need to return immediately to prevent javascript
   // being run that operates on the element and decoder during shutdown.
   // Read/Write from the main thread only.
   PRPackedBool mShuttingDown;
 };
 
+namespace mozilla {
+class MediaMemoryReporter
+{
+  MediaMemoryReporter();
+  ~MediaMemoryReporter();
+  static MediaMemoryReporter* sUniqueInstance;
+
+  static MediaMemoryReporter* UniqueInstance() {
+    if (!sUniqueInstance) {
+      sUniqueInstance = new MediaMemoryReporter;
+    }
+    return sUniqueInstance;
+  }
+
+  typedef nsTArray<nsMediaDecoder*> DecodersArray;
+  static DecodersArray& Decoders() {
+    return UniqueInstance()->mDecoders;
+  }
+
+  DecodersArray mDecoders;
+
+  nsCOMPtr<nsIMemoryReporter> mMediaDecodedVideoMemory;
+  nsCOMPtr<nsIMemoryReporter> mMediaDecodedAudioMemory;
+
+public:
+  static void AddMediaDecoder(nsMediaDecoder* aDecoder) {
+    Decoders().AppendElement(aDecoder);
+  }
+
+  static void RemoveMediaDecoder(nsMediaDecoder* aDecoder) {
+    DecodersArray& decoders = Decoders();
+    decoders.RemoveElement(aDecoder);
+    if (decoders.IsEmpty()) {
+      delete sUniqueInstance;
+      sUniqueInstance = nsnull;
+    }
+  }
+
+  static PRInt64 GetDecodedVideoMemory() {
+    DecodersArray& decoders = Decoders();
+    PRInt64 result = 0;
+    for (size_t i = 0; i < decoders.Length(); ++i) {
+      result += decoders[i]->VideoQueueMemoryInUse();
+    }
+    return result;
+  }
+
+  static PRInt64 GetDecodedAudioMemory() {
+    DecodersArray& decoders = Decoders();
+    PRInt64 result = 0;
+    for (size_t i = 0; i < decoders.Length(); ++i) {
+      result += decoders[i]->AudioQueueMemoryInUse();
+    }
+    return result;
+  }
+};
+
+} //namespace mozilla
 #endif
--- a/gfx/layers/ImageLayers.h
+++ b/gfx/layers/ImageLayers.h
@@ -449,16 +449,21 @@ public:
    * @param aData           Input image data.
    * @return                Raw data pointer for the planes or nsnull on failure.
    */
   PRUint8 *CopyData(Data& aDest, gfxIntSize& aDestSize,
                     PRUint32& aDestBufferSize, const Data& aData);
 
   virtual PRUint8* AllocateBuffer(PRUint32 aSize);
 
+  /**
+   * Return the number of bytes of heap memory used to store this image.
+   */
+  virtual PRUint32 GetDataSize() = 0;
+
 protected:
   PlanarYCbCrImage(void* aImplData) : Image(aImplData, PLANAR_YCBCR) {}
 };
 
 /**
  * Currently, the data in a CairoImage surface is treated as being in the
  * device output color space.
  */
--- a/gfx/layers/basic/BasicImages.cpp
+++ b/gfx/layers/basic/BasicImages.cpp
@@ -124,16 +124,18 @@ public:
 
   virtual already_AddRefed<gfxASurface> GetAsSurface();
 
   const Data* GetData() { return &mData; }
 
   void SetOffscreenFormat(gfxImageFormat aFormat) { mOffscreenFormat = aFormat; }
   gfxImageFormat GetOffscreenFormat() { return mOffscreenFormat; }
 
+  PRUint32 GetDataSize() { return mBuffer ? mDelayedConversion ? mBufferSize : mSize.height * mStride : 0; }
+
 protected:
   nsAutoArrayPtr<PRUint8>              mBuffer;
   nsCountedRef<nsMainThreadSurfaceRef> mSurface;
   gfxIntSize                           mScaleHint;
   PRInt32                              mStride;
   gfxImageFormat                       mOffscreenFormat;
   Data                                 mData;
   PRUint32                             mBufferSize;
--- a/gfx/layers/d3d10/ImageLayerD3D10.cpp
+++ b/gfx/layers/d3d10/ImageLayerD3D10.cpp
@@ -366,26 +366,26 @@ ImageLayerD3D10::RenderLayer()
       SetFloatVector(ShaderConstantRectD3D10(0, 0, 1.0f, 1.0f));
   }
 
   GetContainer()->NotifyPaintedImage(image);
 }
 
 PlanarYCbCrImageD3D10::PlanarYCbCrImageD3D10(ID3D10Device1 *aDevice)
   : PlanarYCbCrImage(static_cast<ImageD3D10*>(this))
+  , mBufferSize(0)
   , mDevice(aDevice)
   , mHasData(PR_FALSE)
 {
 }
 
 void
 PlanarYCbCrImageD3D10::SetData(const PlanarYCbCrImage::Data &aData)
 {
-  PRUint32 dummy;
-  mBuffer = CopyData(mData, mSize, dummy, aData);
+  mBuffer = CopyData(mData, mSize, mBufferSize, aData);
 
   AllocateTextures();
 
   mHasData = PR_TRUE;
 }
 
 void
 PlanarYCbCrImageD3D10::AllocateTextures()
--- a/gfx/layers/d3d10/ImageLayerD3D10.h
+++ b/gfx/layers/d3d10/ImageLayerD3D10.h
@@ -109,19 +109,22 @@ public:
   /*
    * Upload the data from out mData into our textures. For now we use this to
    * make sure the textures are created and filled on the main thread.
    */
   void AllocateTextures();
 
   PRBool HasData() { return mHasData; }
 
+  PRUint32 GetDataSize() { return mBuffer ? mBufferSize : 0; }
+
   virtual already_AddRefed<gfxASurface> GetAsSurface();
 
   nsAutoArrayPtr<PRUint8> mBuffer;
+  PRUint32 mBufferSize;
   nsRefPtr<ID3D10Device1> mDevice;
   Data mData;
   gfxIntSize mSize;
   nsRefPtr<ID3D10Texture2D> mYTexture;
   nsRefPtr<ID3D10Texture2D> mCrTexture;
   nsRefPtr<ID3D10Texture2D> mCbTexture;
   nsRefPtr<ID3D10ShaderResourceView> mYView;
   nsRefPtr<ID3D10ShaderResourceView> mCbView;
--- a/gfx/layers/d3d9/ImageLayerD3D9.cpp
+++ b/gfx/layers/d3d9/ImageLayerD3D9.cpp
@@ -392,25 +392,25 @@ ImageLayerD3D9::RenderLayer()
     }
   }
 
   GetContainer()->NotifyPaintedImage(image);
 }
 
 PlanarYCbCrImageD3D9::PlanarYCbCrImageD3D9()
   : PlanarYCbCrImage(static_cast<ImageD3D9*>(this))
+  , mBufferSize(0)
   , mHasData(PR_FALSE)
 {
 }
 
 void
 PlanarYCbCrImageD3D9::SetData(const PlanarYCbCrImage::Data &aData)
 {
-  PRUint32 dummy;
-  mBuffer = CopyData(mData, mSize, dummy, aData);
+  mBuffer = CopyData(mData, mSize, mBufferSize, aData);
 
   mHasData = PR_TRUE;
 }
 
 void
 PlanarYCbCrImageD3D9::AllocateTextures(IDirect3DDevice9 *aDevice)
 {
   D3DLOCKED_RECT lockrectY;
--- a/gfx/layers/d3d9/ImageLayerD3D9.h
+++ b/gfx/layers/d3d9/ImageLayerD3D9.h
@@ -119,19 +119,22 @@ public:
    * Free the textures, we call this from the main thread when we're done
    * drawing this frame. We cannot free this from the constructor since it may
    * be destroyed off the main-thread and might not be able to properly clean
    * up its textures
    */
   void FreeTextures();
   PRBool HasData() { return mHasData; }
 
+  PRUint32 GetDataSize() { return mBuffer ? mBufferSize : 0; }
+
   virtual already_AddRefed<gfxASurface> GetAsSurface();
 
   nsAutoArrayPtr<PRUint8> mBuffer;
+  PRUint32 mBufferSize;
   LayerManagerD3D9 *mManager;
   Data mData;
   gfxIntSize mSize;
   nsRefPtr<IDirect3DTexture9> mYTexture;
   nsRefPtr<IDirect3DTexture9> mCrTexture;
   nsRefPtr<IDirect3DTexture9> mCbTexture;
   PRPackedBool mHasData;
 };
--- a/gfx/layers/opengl/ImageLayerOGL.h
+++ b/gfx/layers/opengl/ImageLayerOGL.h
@@ -206,16 +206,18 @@ public:
     return mTextures[0].IsAllocated() && mTextures[1].IsAllocated() &&
            mTextures[2].IsAllocated();
   }
 
   PRUint8* AllocateBuffer(PRUint32 aSize) {
     return mRecycleBin->GetBuffer(aSize);
   }
 
+  PRUint32 GetDataSize() { return mBuffer ? mBufferSize : 0; }
+
   nsAutoArrayPtr<PRUint8> mBuffer;
   PRUint32 mBufferSize;
   nsRefPtr<RecycleBin> mRecycleBin;
   GLTexture mTextures[3];
   Data mData;
   gfxIntSize mSize;
   PRPackedBool mHasData;
 };