Bug 951496 - Codec getStats. r=smaug, r=jesup
authorJan-Ivar Bruaroey <jib@mozilla.com>
Sat, 07 Jun 2014 17:27:26 -0400
changeset 207798 e30a256cb1fc14f1583c0fa3db9b4df7b16323f3
parent 207797 6dd8777a4453b583b406b486e213aefc3da0967a
child 207799 b8f0896469c7c8069742801de01e55cb17f9b55a
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jesup
bugs951496
milestone32.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 951496 - Codec getStats. r=smaug, r=jesup
dom/webidl/RTCStatsReport.webidl
media/webrtc/signaling/src/media-conduit/AudioConduit.h
media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp
media/webrtc/signaling/src/media-conduit/CodecStatistics.h
media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
media/webrtc/signaling/src/media-conduit/VideoConduit.h
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
toolkit/content/aboutWebrtc.xhtml
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -1,15 +1,16 @@
 /* -*- Mode: IDL; 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/.
  *
  * The origin of this IDL file is
  * http://dev.w3.org/2011/webrtc/editor/webrtc.html#rtcstatsreport-object
+ * http://www.w3.org/2011/04/webrtc/wiki/Stats
  */
 
 enum RTCStatsType {
   "inboundrtp",
   "outboundrtp",
   "session",
   "track",
   "transport",
@@ -26,44 +27,64 @@ dictionary RTCStats {
 
 dictionary RTCRTPStreamStats : RTCStats {
   DOMString ssrc;
   DOMString remoteId;
   boolean isRemote = false;
   DOMString mediaTrackId;
   DOMString transportId;
   DOMString codecId;
+
+  // Video encoder/decoder measurements (absent for rtcp)
+  double bitrateMean;
+  double bitrateStdDev;
+  double framerateMean;
+  double framerateStdDev;
 };
 
 dictionary RTCInboundRTPStreamStats : RTCRTPStreamStats {
   unsigned long packetsReceived;
   unsigned long long bytesReceived;
   double jitter;
   unsigned long packetsLost;
   long mozAvSyncDelay;
   long mozJitterBufferDelay;
   long mozRtt;
+
+  // Video decoder measurement (absent in rtcp case)
+  unsigned long discardedPackets;
 };
 
 dictionary RTCOutboundRTPStreamStats : RTCRTPStreamStats {
   unsigned long packetsSent;
   unsigned long long bytesSent;
+  double targetBitrate;  // config encoder bitrate target of this SSRC in bits/s
+
+  // Video encoder measurement (absent in rtcp case)
+  unsigned long droppedFrames;
 };
 
 dictionary RTCMediaStreamTrackStats : RTCStats {
   DOMString trackIdentifier;      // track.id property
   boolean remoteSource;
   sequence<DOMString> ssrcIds;
-  unsigned long audioLevel;       // Only for audio, the rest are only for video
+  // Stuff that makes sense for video
   unsigned long frameWidth;
   unsigned long frameHeight;
-  double framesPerSecond;         // The nominal FPS value
+  double framesPerSecond;        // The nominal FPS value
   unsigned long framesSent;
-  unsigned long framesReceived;   // Only for remoteSource=true
+  unsigned long framesReceived;  // Only for remoteSource=true
   unsigned long framesDecoded;
+  unsigned long framesDropped;   // See VideoPlaybackQuality.droppedVideoFrames
+  unsigned long framesCorrupted; // as above.
+  // Stuff that makes sense for audio
+  double audioLevel;               // linear, 1.0 = 0 dBov (from RFC 6464).
+  // AEC stuff on audio tracks sourced from a microphone where AEC is applied
+  double echoReturnLoss;           // in decibels from G.168 (2012) section 3.14
+  double echoReturnLossEnhancement; // as above, section 3.15
 };
 
 dictionary RTCMediaStreamStats : RTCStats {
   DOMString streamIdentifier;     // stream.id property
   sequence<DOMString> trackIds;   // Note: stats object ids, not track.id
 };
 
 dictionary RTCTransportStats: RTCStats {
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.h
@@ -173,16 +173,32 @@ public:
   virtual ~WebrtcAudioConduit();
 
   MediaConduitErrorCode Init(WebrtcAudioConduit *other);
 
   int GetChannel() { return mChannel; }
   webrtc::VoiceEngine* GetVoiceEngine() { return mVoiceEngine; }
   bool GetLocalSSRC(unsigned int* ssrc);
   bool GetRemoteSSRC(unsigned int* ssrc);
+  bool GetVideoEncoderStats(double* framerateMean,
+                            double* framerateStdDev,
+                            double* bitrateMean,
+                            double* bitrateStdDev,
+                            uint32_t* droppedFrames)
+  {
+    return false;
+  }
+  bool GetVideoDecoderStats(double* framerateMean,
+                            double* framerateStdDev,
+                            double* bitrateMean,
+                            double* bitrateStdDev,
+                            uint32_t* discardedPackets)
+  {
+    return false;
+  }
   bool GetAVStats(int32_t* jitterBufferDelayMs,
                   int32_t* playoutBufferDelayMs,
                   int32_t* avSyncOffsetMs);
   bool GetRTPStats(unsigned int* jitterMs, unsigned int* cumulativeLost);
   bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp,
                              uint32_t* jitterMs,
                              uint32_t* packetsReceived,
                              uint64_t* bytesReceived,
--- a/media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp
+++ b/media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp
@@ -7,32 +7,41 @@
 #include "CSFLog.h"
 
 using namespace mozilla;
 using namespace webrtc;
 
 // use the same tag as VideoConduit
 static const char* logTag ="WebrtcVideoSessionConduit";
 
-VideoCodecStatistics::VideoCodecStatistics(int channel, ViECodec* codec) :
+VideoCodecStatistics::VideoCodecStatistics(int channel,
+                                           ViECodec* codec,
+                                           bool encoder) :
   mChannel(channel),
   mSentRawFrames(0),
   mPtrViECodec(codec),
   mEncoderDroppedFrames(0),
-  mDecoderDiscardedPackets(0)
+  mDecoderDiscardedPackets(0),
+  mEncoderMode(encoder)
 {
   MOZ_ASSERT(mPtrViECodec);
-  mPtrViECodec->RegisterEncoderObserver(mChannel, *this);
-  mPtrViECodec->RegisterDecoderObserver(mChannel, *this);
+  if (mEncoderMode) {
+    mPtrViECodec->RegisterEncoderObserver(mChannel, *this);
+  } else {
+    mPtrViECodec->RegisterDecoderObserver(mChannel, *this);
+  }
 }
 
 VideoCodecStatistics::~VideoCodecStatistics()
 {
-  mPtrViECodec->DeregisterEncoderObserver(mChannel);
-  mPtrViECodec->DeregisterDecoderObserver(mChannel);
+  if (mEncoderMode) {
+    mPtrViECodec->DeregisterEncoderObserver(mChannel);
+  } else {
+    mPtrViECodec->DeregisterDecoderObserver(mChannel);
+  }
 }
 
 void VideoCodecStatistics::OutgoingRate(const int video_channel,
                                         const uint32_t framerate,
                                         const uint32_t bitrate)
 {
   unsigned int keyFrames, deltaFrames;
   mPtrViECodec->GetSendCodecStatistics(video_channel, keyFrames, deltaFrames);
--- a/media/webrtc/signaling/src/media-conduit/CodecStatistics.h
+++ b/media/webrtc/signaling/src/media-conduit/CodecStatistics.h
@@ -14,17 +14,17 @@
 namespace mozilla {
 
 // Statistics-gathering observer for Video Encoder and Decoder
 
 class VideoCodecStatistics : public webrtc::ViEEncoderObserver
                            , public webrtc::ViEDecoderObserver
 {
 public:
-  VideoCodecStatistics(int channel, webrtc::ViECodec* vieCodec);
+  VideoCodecStatistics(int channel, webrtc::ViECodec* vieCodec, bool encoder);
   ~VideoCodecStatistics();
 
   void SentFrame();
   virtual void OutgoingRate(const int video_channel,
     const unsigned int framerate, const unsigned int bitrate) MOZ_OVERRIDE;
 
   virtual void IncomingCodecChanged(const int video_channel,
     const webrtc::VideoCodec& video_codec) MOZ_OVERRIDE;
@@ -38,26 +38,56 @@ public:
   virtual void SuspendChange(int video_channel, bool is_suspended) MOZ_OVERRIDE {};
   virtual void DecoderTiming(int decode_ms,
                              int max_decode_ms,
                              int current_delay_ms,
                              int target_delay_ms,
                              int jitter_buffer_ms,
                              int min_playout_delay_ms,
                              int render_delay_ms) MOZ_OVERRIDE {}
+
+  bool GetEncoderStats(double* framerateMean,
+                       double* framerateStdDev,
+                       double* bitrateMean,
+                       double* bitrateStdDev,
+                       uint32_t* droppedFrames)
+  {
+    *framerateMean   = mEncoderFps.Mean();
+    *framerateStdDev = mEncoderFps.StandardDeviation();
+    *bitrateMean     = mEncoderBitRate.Mean();
+    *bitrateStdDev   = mEncoderBitRate.StandardDeviation();
+    *droppedFrames   = mEncoderDroppedFrames;
+    return true;
+  }
+
+  bool GetDecoderStats(double* framerateMean,
+                       double* framerateStdDev,
+                       double* bitrateMean,
+                       double* bitrateStdDev,
+                       uint32_t* discardedPackets)
+  {
+    *framerateMean    = mDecoderFps.Mean();
+    *framerateStdDev  = mDecoderFps.StandardDeviation();
+    *bitrateMean      = mDecoderBitRate.Mean();
+    *bitrateStdDev    = mDecoderBitRate.StandardDeviation();
+    *discardedPackets = mDecoderDiscardedPackets;
+    return true;
+  }
+
   void Dump();
 private:
   void Dump(RunningStat& s, const char *name);
 
   int mChannel;
   uint32_t mSentRawFrames;
   ScopedCustomReleasePtr<webrtc::ViECodec> mPtrViECodec; // back-pointer
 
   RunningStat mEncoderBitRate;
   RunningStat mEncoderFps;
   uint32_t mEncoderDroppedFrames;
   RunningStat mDecoderBitRate;
   RunningStat mDecoderFps;
   uint32_t mDecoderDiscardedPackets;
+  const bool mEncoderMode;
 };
 }
 
 #endif //CODEC_STATISTICS_H_
--- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
+++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
@@ -159,16 +159,26 @@ public:
   virtual MediaConduitErrorCode AttachTransport(RefPtr<TransportInterface> aTransport) = 0;
 
   virtual bool GetLocalSSRC(unsigned int* ssrc) = 0;
   virtual bool GetRemoteSSRC(unsigned int* ssrc) = 0;
 
   /**
    * Functions returning stats needed by w3c stats model.
    */
+  virtual bool GetVideoEncoderStats(double* framerateMean,
+                                    double* framerateStdDev,
+                                    double* bitrateMean,
+                                    double* bitrateStdDev,
+                                    uint32_t* droppedFrames) = 0;
+  virtual bool GetVideoDecoderStats(double* framerateMean,
+                                    double* framerateStdDev,
+                                    double* bitrateMean,
+                                    double* bitrateStdDev,
+                                    uint32_t* discardedPackets) = 0;
   virtual bool GetAVStats(int32_t* jitterBufferDelayMs,
                           int32_t* playoutBufferDelayMs,
                           int32_t* avSyncOffsetMs) = 0;
   virtual bool GetRTPStats(unsigned int* jitterMs,
                            unsigned int* cumulativeLost) = 0;
   virtual bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp,
                                      uint32_t* jitterMs,
                                      uint32_t* packetsReceived,
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -132,24 +132,58 @@ WebrtcVideoConduit::~WebrtcVideoConduit(
     // only one opener can call Delete.  Have it be the last to close.
     if(mVideoEngine)
     {
       webrtc::VideoEngine::Delete(mVideoEngine);
     }
   }
 }
 
-bool WebrtcVideoConduit::GetLocalSSRC(unsigned int* ssrc) {
+bool WebrtcVideoConduit::GetLocalSSRC(unsigned int* ssrc)
+{
   return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc);
 }
 
-bool WebrtcVideoConduit::GetRemoteSSRC(unsigned int* ssrc) {
+bool WebrtcVideoConduit::GetRemoteSSRC(unsigned int* ssrc)
+{
   return !mPtrRTP->GetRemoteSSRC(mChannel, *ssrc);
 }
 
+bool WebrtcVideoConduit::GetVideoEncoderStats(double* framerateMean,
+                                              double* framerateStdDev,
+                                              double* bitrateMean,
+                                              double* bitrateStdDev,
+                                              uint32_t* droppedFrames)
+{
+  if (!mEngineTransmitting) {
+    return false;
+  }
+  MOZ_ASSERT(mVideoCodecStat);
+  mVideoCodecStat->GetEncoderStats(framerateMean, framerateStdDev,
+                                   bitrateMean, bitrateStdDev,
+                                   droppedFrames);
+  return true;
+}
+
+bool WebrtcVideoConduit::GetVideoDecoderStats(double* framerateMean,
+                                              double* framerateStdDev,
+                                              double* bitrateMean,
+                                              double* bitrateStdDev,
+                                              uint32_t* discardedPackets)
+{
+  if (!mEngineReceiving) {
+    return false;
+  }
+  MOZ_ASSERT(mVideoCodecStat);
+  mVideoCodecStat->GetDecoderStats(framerateMean, framerateStdDev,
+                                   bitrateMean, bitrateStdDev,
+                                   discardedPackets);
+  return true;
+}
+
 bool WebrtcVideoConduit::GetAVStats(int32_t* jitterBufferDelayMs,
                                     int32_t* playoutBufferDelayMs,
                                     int32_t* avSyncOffsetMs) {
   return false;
 }
 
 bool WebrtcVideoConduit::GetRTPStats(unsigned int* jitterMs,
                                      unsigned int* cumulativeLost) {
@@ -566,17 +600,19 @@ WebrtcVideoConduit::ConfigureSendMediaCo
       CSFLogError(logTag, "%s Invalid Send Codec", __FUNCTION__);
       return kMediaConduitInvalidSendCodec;
     }
     CSFLogError(logTag, "%s SetSendCodec Failed %d ", __FUNCTION__,
                 mPtrViEBase->LastError());
     return kMediaConduitUnknownError;
   }
 
-  mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec);
+  if (!mVideoCodecStat) {
+    mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec, true);
+  }
 
   mSendingWidth = 0;
   mSendingHeight = 0;
 
   if(codecConfig->RtcpFbIsSet(SDP_RTCP_FB_NACK_BASIC)) {
     CSFLogDebug(logTag, "Enabling NACK (send) for video stream\n");
     if (mPtrRTP->SetNACKStatus(mChannel, true) != 0)
     {
@@ -736,16 +772,20 @@ WebrtcVideoConduit::ConfigureRecvMediaCo
   }//end for
 
   if(!success)
   {
     CSFLogError(logTag, "%s Setting Receive Codec Failed ", __FUNCTION__);
     return kMediaConduitInvalidReceiveCodec;
   }
 
+  if (!mVideoCodecStat) {
+    mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec, false);
+  }
+
   // XXX Currently, we gather up all of the feedback types that the remote
   // party indicated it supports for all video codecs and configure the entire
   // conduit based on those capabilities. This is technically out of spec,
   // as these values should be configured on a per-codec basis. However,
   // the video engine only provides this API on a per-conduit basis, so that's
   // how we have to do it. The approach of considering the remote capablities
   // for the entire conduit to be a union of all remote codec capabilities
   // (rather than the more conservative approach of using an intersection)
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h
@@ -246,16 +246,26 @@ public:
   virtual ~WebrtcVideoConduit() ;
 
   MediaConduitErrorCode Init(WebrtcVideoConduit *other);
 
   int GetChannel() { return mChannel; }
   webrtc::VideoEngine* GetVideoEngine() { return mVideoEngine; }
   bool GetLocalSSRC(unsigned int* ssrc);
   bool GetRemoteSSRC(unsigned int* ssrc);
+  bool GetVideoEncoderStats(double* framerateMean,
+                            double* framerateStdDev,
+                            double* bitrateMean,
+                            double* bitrateStdDev,
+                            uint32_t* droppedFrames);
+  bool GetVideoDecoderStats(double* framerateMean,
+                            double* framerateStdDev,
+                            double* bitrateMean,
+                            double* bitrateStdDev,
+                            uint32_t* discardedPackets);
   bool GetAVStats(int32_t* jitterBufferDelayMs,
                   int32_t* playoutBufferDelayMs,
                   int32_t* avSyncOffsetMs);
   bool GetRTPStats(unsigned int* jitterMs, unsigned int* cumulativeLost);
   bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp,
                              uint32_t* jitterMs,
                              uint32_t* packetsReceived,
                              uint64_t* bytesReceived,
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -2242,16 +2242,17 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
 
   for (size_t p = 0; p < query->pipelines.Length(); ++p) {
     const MediaPipeline& mp = *query->pipelines[p];
     bool isAudio = (mp.Conduit()->type() == MediaSessionConduit::AUDIO);
     nsString idstr = isAudio ?
         NS_LITERAL_STRING("audio_") : NS_LITERAL_STRING("video_");
     idstr.AppendInt(mp.trackid());
 
+    // Gather pipeline stats.
     switch (mp.direction()) {
       case MediaPipeline::TRANSMIT: {
         nsString localId = NS_LITERAL_STRING("outbound_rtp_") + idstr;
         nsString remoteId;
         nsString ssrc;
         unsigned int ssrcval;
         if (mp.Conduit()->GetLocalSSRC(&ssrcval)) {
           ssrc.AppendInt(ssrcval);
@@ -2297,16 +2298,36 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
           s.mType.Construct(RTCStatsType::Outboundrtp);
           if (ssrc.Length()) {
             s.mSsrc.Construct(ssrc);
           }
           s.mRemoteId.Construct(remoteId);
           s.mIsRemote = false;
           s.mPacketsSent.Construct(mp.rtp_packets_sent());
           s.mBytesSent.Construct(mp.rtp_bytes_sent());
+
+          // Lastly, fill in video encoder stats if this is video
+          if (!isAudio) {
+            double framerateMean;
+            double framerateStdDev;
+            double bitrateMean;
+            double bitrateStdDev;
+            uint32_t droppedFrames;
+            if (mp.Conduit()->GetVideoEncoderStats(&framerateMean,
+                                                   &framerateStdDev,
+                                                   &bitrateMean,
+                                                   &bitrateStdDev,
+                                                   &droppedFrames)) {
+              s.mFramerateMean.Construct(framerateMean);
+              s.mFramerateStdDev.Construct(framerateStdDev);
+              s.mBitrateMean.Construct(bitrateMean);
+              s.mBitrateStdDev.Construct(bitrateStdDev);
+              s.mDroppedFrames.Construct(droppedFrames);
+            }
+          }
           query->report->mOutboundRTPStreamStats.Value().AppendElement(s);
         }
         break;
       }
       case MediaPipeline::RECEIVE: {
         nsString localId = NS_LITERAL_STRING("inbound_rtp_") + idstr;
         nsString remoteId;
         nsString ssrc;
@@ -2362,16 +2383,35 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
           int32_t avSyncDelta;
           if (mp.Conduit()->GetAVStats(&jitterBufferDelay,
                                        &playoutBufferDelay,
                                        &avSyncDelta)) {
             s.mMozJitterBufferDelay.Construct(jitterBufferDelay);
             s.mMozAvSyncDelay.Construct(avSyncDelta);
           }
         }
+        // Lastly, fill in video decoder stats if this is video
+        if (!isAudio) {
+          double framerateMean;
+          double framerateStdDev;
+          double bitrateMean;
+          double bitrateStdDev;
+          uint32_t discardedPackets;
+          if (mp.Conduit()->GetVideoDecoderStats(&framerateMean,
+                                                 &framerateStdDev,
+                                                 &bitrateMean,
+                                                 &bitrateStdDev,
+                                                 &discardedPackets)) {
+            s.mFramerateMean.Construct(framerateMean);
+            s.mFramerateStdDev.Construct(framerateStdDev);
+            s.mBitrateMean.Construct(bitrateMean);
+            s.mBitrateStdDev.Construct(bitrateStdDev);
+            s.mDiscardedPackets.Construct(discardedPackets);
+          }
+        }
         query->report->mInboundRTPStreamStats.Value().AppendElement(s);
         break;
       }
     }
   }
 
   // Gather stats from ICE
   for (size_t s = 0; s != query->streams.Length(); ++s) {
--- a/toolkit/content/aboutWebrtc.xhtml
+++ b/toolkit/content/aboutWebrtc.xhtml
@@ -166,16 +166,46 @@ function dumpRtpStat(stat, label) {
     if (stat.bytesSent !== undefined) {
       statsString += " (" + round00(stat.bytesSent/1024) + " Kb)";
     }
   }
   div.appendChild(document.createTextNode(statsString));
   return div;
 }
 
+function dumpCoderStat(stat) {
+  var div = document.createElement('div');
+  if (stat.bitrateMean !== undefined ||
+      stat.framerateMean !== undefined ||
+      stat.droppedFrames !== undefined ||
+      stat.discardedPackets !== undefined) {
+    var statsString = (stat.packetsReceived !== undefined)? " Decoder:" : " Encoder:";
+    if (stat.bitrateMean !== undefined) {
+      statsString += " Avg. bitrate: " + (stat.bitrateMean/1000000).toFixed(2) + " Mbps";
+      if (stat.bitrateStdDev !== undefined) {
+        statsString += " (" + (stat.bitrateStdDev/1000000).toFixed(2) + " SD)";
+      }
+    }
+    if (stat.framerateMean !== undefined) {
+      statsString += " Avg. framerate: " + (stat.framerateMean).toFixed(2) + " fps";
+      if (stat.framerateStdDev !== undefined) {
+        statsString += " (" + stat.framerateStdDev.toFixed(2) + " SD)";
+      }
+    }
+    if (stat.droppedFrames !== undefined) {
+      statsString += " Dropped frames: " + stat.droppedFrames;
+    }
+    if (stat.discardedPackets !== undefined) {
+      statsString += " Discarded packets: " + stat.discardedPackets;
+    }
+    div.appendChild(document.createTextNode(statsString));
+  }
+  return div;
+}
+
 function buildPcDiv(stats, pcDivHeading) {
   var newPcDiv = document.createElement('div');
 
   var heading = document.createElement('h3');
 
   if (stats.closed) {
     heading.appendChild(document.createTextNode("Closed "));
   }
@@ -295,16 +325,17 @@ function buildPcDiv(stats, pcDivHeading)
   var addRtpStatPairToDocument = function (rtpStat) {
     if (!rtpStat.isRemote) {
       newPcDiv.appendChild(document.createElement('h5'))
               .appendChild(document.createTextNode(rtpStat.id));
       if (rtpStat.mozAvSyncDelay !== undefined ||
           rtpStat.mozJitterBufferDelay !== undefined) {
         newPcDiv.appendChild(dumpAvStat(rtpStat));
       }
+      newPcDiv.appendChild(dumpCoderStat(rtpStat));
       newPcDiv.appendChild(dumpRtpStat(rtpStat, "Local: "));
 
       // Might not be receiving RTCP, so we have no idea what the
       // statistics look like from the perspective of the other end.
       if (rtpStat.remoteId) {
         var remoteRtpStat = remoteRtpStatsMap[rtpStat.remoteId];
         newPcDiv.appendChild(dumpRtpStat(remoteRtpStat, "Remote: "));
       }