Bug 945135 - Part 2: Add VideoTrackEncoder. r=roc
authorShelly Lin <slin@mozilla.com>
Mon, 02 Dec 2013 15:52:54 +0800
changeset 173921 5dddd0b5ff519329703fd5c6d265c2f0589226de
parent 173920 72f2f506fe013bfba7d51b31dfe63e63d20e315c
child 173922 dc22387e4db60594c66fc0435c003904be76161d
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs945135
milestone28.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 945135 - Part 2: Add VideoTrackEncoder. r=roc
content/media/VideoSegment.cpp
content/media/VideoSegment.h
content/media/encoder/TrackEncoder.cpp
content/media/encoder/TrackEncoder.h
--- a/content/media/VideoSegment.cpp
+++ b/content/media/VideoSegment.cpp
@@ -27,16 +27,17 @@ VideoFrame::SetNull() {
   mIntrinsicSize = gfxIntSize(0, 0);
 }
 
 void
 VideoFrame::TakeFrom(VideoFrame* aFrame)
 {
   mImage = aFrame->mImage.forget();
   mIntrinsicSize = aFrame->mIntrinsicSize;
+  mForceBlack = aFrame->GetForceBlack();
 }
 
 VideoChunk::VideoChunk()
 {}
 
 VideoChunk::~VideoChunk()
 {}
 
--- a/content/media/VideoSegment.h
+++ b/content/media/VideoSegment.h
@@ -33,17 +33,17 @@ public:
            ((mForceBlack && aFrame.mForceBlack) || mImage == aFrame.mImage);
   }
   bool operator!=(const VideoFrame& aFrame) const
   {
     return !operator==(aFrame);
   }
 
   Image* GetImage() const { return mImage; }
-  void SetForceBlack(bool aForceBlack) { mForceBlack = true; }
+  void SetForceBlack(bool aForceBlack) { mForceBlack = aForceBlack; }
   bool GetForceBlack() const { return mForceBlack; }
   const gfxIntSize& GetIntrinsicSize() const { return mIntrinsicSize; }
   void SetNull();
   void TakeFrom(VideoFrame* aFrame);
 
 protected:
   // mImage can be null to indicate "no video" (aka "empty frame"). It can
   // still have an intrinsic size in this case.
--- a/content/media/encoder/TrackEncoder.cpp
+++ b/content/media/encoder/TrackEncoder.cpp
@@ -14,16 +14,19 @@
 #else
 #define LOG(args, ...)
 #endif
 
 namespace mozilla {
 
 static const int DEFAULT_CHANNELS = 1;
 static const int DEFAULT_SAMPLING_RATE = 16000;
+static const int DEFAULT_FRAME_WIDTH = 640;
+static const int DEFAULT_FRAME_HEIGHT = 480;
+static const int DEFAULT_TRACK_RATE = USECS_PER_S;
 
 void
 AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
                                             TrackID aID,
                                             TrackRate aTrackRate,
                                             TrackTicks aTrackOffset,
                                             uint32_t aTrackEvents,
                                             const MediaSegment& aQueuedMedia)
@@ -119,9 +122,108 @@ AudioTrackEncoder::InterleaveTrackData(A
                          aChunk.mVolume, mChannels, aOutput);
   } else {
     InterleaveAndConvertBuffer(aChunk.mChannelData.Elements(),
                                aChunk.mBufferFormat, aDuration, aChunk.mVolume,
                                mChannels, aOutput);
   }
 }
 
+void
+VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
+                                            TrackID aID,
+                                            TrackRate aTrackRate,
+                                            TrackTicks aTrackOffset,
+                                            uint32_t aTrackEvents,
+                                            const MediaSegment& aQueuedMedia)
+{
+  if (mCanceled) {
+    return;
+  }
+
+  const VideoSegment& video = static_cast<const VideoSegment&>(aQueuedMedia);
+
+   // Check and initialize parameters for codec encoder.
+  if (!mInitialized) {
+    VideoSegment::ChunkIterator iter(const_cast<VideoSegment&>(video));
+    while (!iter.IsEnded()) {
+      VideoChunk chunk = *iter;
+      if (!chunk.IsNull()) {
+        gfxIntSize imgsize = chunk.mFrame.GetImage()->GetSize();
+        int width = (imgsize.width + 1) / 2 * 2;
+        int height = (imgsize.height + 1) / 2 * 2;
+        nsresult rv = Init(width, height, aTrackRate);
+        if (NS_FAILED(rv)) {
+          LOG("[VideoTrackEncoder]: Fail to initialize the encoder!");
+          NotifyCancel();
+        }
+        break;
+      }
+
+      iter.Next();
+    }
+  }
+
+  AppendVideoSegment(video);
+
+  // The stream has stopped and reached the end of track.
+  if (aTrackEvents == MediaStreamListener::TRACK_EVENT_ENDED) {
+    LOG("[VideoTrackEncoder]: Receive TRACK_EVENT_ENDED .");
+    NotifyEndOfStream();
+  }
+
 }
+
+nsresult
+VideoTrackEncoder::AppendVideoSegment(const VideoSegment& aSegment)
+{
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+  // Append all video segments from MediaStreamGraph, including null an
+  // non-null frames.
+  VideoSegment::ChunkIterator iter(const_cast<VideoSegment&>(aSegment));
+  while (!iter.IsEnded()) {
+    VideoChunk chunk = *iter;
+    nsRefPtr<layers::Image> image = chunk.mFrame.GetImage();
+    mRawSegment.AppendFrame(image.forget(), chunk.GetDuration(),
+                            chunk.mFrame.GetIntrinsicSize());
+    iter.Next();
+  }
+
+  if (mRawSegment.GetDuration() > 0) {
+    mReentrantMonitor.NotifyAll();
+  }
+
+  return NS_OK;
+}
+
+void
+VideoTrackEncoder::NotifyEndOfStream()
+{
+  // If source video track is muted till the end of encoding, initialize the
+  // encoder with default frame width, frame height, and track rate.
+  if (!mCanceled && !mInitialized) {
+    Init(DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, DEFAULT_TRACK_RATE);
+  }
+
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  mEndOfStream = true;
+  mReentrantMonitor.NotifyAll();
+}
+
+void
+VideoTrackEncoder::CreateMutedFrame(nsTArray<uint8_t>* aOutputBuffer)
+{
+  NS_ENSURE_TRUE_VOID(aOutputBuffer);
+
+  // Supports YUV420 image format only.
+  int yPlaneLen = mFrameWidth * mFrameHeight;
+  int cbcrPlaneLen = yPlaneLen / 2;
+  int frameLen = yPlaneLen + cbcrPlaneLen;
+
+  aOutputBuffer->SetLength(frameLen);
+  // Fill Y plane.
+  memset(aOutputBuffer->Elements(), 0x10, yPlaneLen);
+  // Fill Cb/Cr planes.
+  memset(aOutputBuffer->Elements() + yPlaneLen, 0x80, cbcrPlaneLen);
+}
+
+}
--- a/content/media/encoder/TrackEncoder.h
+++ b/content/media/encoder/TrackEncoder.h
@@ -7,16 +7,17 @@
 #define TrackEncoder_h_
 
 #include "mozilla/ReentrantMonitor.h"
 
 #include "AudioSegment.h"
 #include "EncodedFrameContainer.h"
 #include "StreamBuffer.h"
 #include "TrackMetadataBase.h"
+#include "VideoSegment.h"
 
 namespace mozilla {
 
 class MediaStreamGraph;
 
 /**
  * Base class of AudioTrackEncoder and VideoTrackEncoder. Lifetimes managed by
  * MediaEncoder. Most methods can only be called on the MediaEncoder's thread,
@@ -203,13 +204,91 @@ protected:
   /**
    * A segment queue of audio track data, protected by mReentrantMonitor.
    */
   AudioSegment mRawSegment;
 };
 
 class VideoTrackEncoder : public TrackEncoder
 {
+public:
+  VideoTrackEncoder()
+    : TrackEncoder()
+    , mFrameWidth(0)
+    , mFrameHeight(0)
+    , mTrackRate(0)
+    , mTotalFrameDuration(0)
+  {}
 
+  /**
+   * Notified by the same callbcak of MediaEncoder when it has received a track
+   * change from MediaStreamGraph. Called on the MediaStreamGraph thread.
+   */
+  void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
+                                TrackRate aTrackRate,
+                                TrackTicks aTrackOffset,
+                                uint32_t aTrackEvents,
+                                const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
+
+protected:
+  /**
+   * Initialized the video encoder. In order to collect the value of width and
+   * height of source frames, this initialization is delayed until we have
+   * received the first valid video frame from MediaStreamGraph;
+   * mReentrantMonitor will be notified after it has successfully initialized,
+   * and this method is called on the MediaStramGraph thread.
+   */
+  virtual nsresult Init(int aWidth, int aHeight, TrackRate aTrackRate) = 0;
+
+  /**
+   * Appends source video frames to mRawSegment. We only append the source chunk
+   * if it is unique to mLastChunk. Called on the MediaStreamGraph thread.
+   */
+  nsresult AppendVideoSegment(const VideoSegment& aSegment);
+
+  /**
+   * Tells the video track encoder that we've reached the end of source stream,
+   * and wakes up mReentrantMonitor if encoder is waiting for more track data.
+   * Called on the MediaStreamGraph thread.
+   */
+  virtual void NotifyEndOfStream() MOZ_OVERRIDE;
+
+  /**
+   * Create a buffer of black image in format of YUV:420. Called on the worker
+   * thread.
+   */
+  void CreateMutedFrame(nsTArray<uint8_t>* aOutputBuffer);
+
+  /**
+   * The width of source video frame, ceiled if the source width is odd.
+   */
+  int mFrameWidth;
+
+  /**
+   * The height of source video frame, ceiled if the source height is odd.
+   */
+  int mFrameHeight;
+
+  /**
+   * The track rate of source video.
+   */
+  TrackRate mTrackRate;
+
+  /**
+   * The total duration of frames in encoded video in TrackTicks, kept track of
+   * in subclasses.
+   */
+  TrackTicks mTotalFrameDuration;
+
+  /**
+   * The last unique frame we've sent to track encoder, kept track of in
+   * subclasses.
+   */
+  VideoFrame mLastFrame;
+
+  /**
+   * A segment queue of audio track data, protected by mReentrantMonitor.
+   */
+  VideoSegment mRawSegment;
 };
 
 }
 #endif