Bug 951044 - [MediaEncoder::GTest] Implement unit test of VP8 video track encoder. r=rillian
authorCJKu <cku@mozilla.com>
Tue, 15 Apr 2014 12:51:19 -0400
changeset 197180 b9da46ef0a77768829d59a016b6b8a253234f034
parent 197179 37e7cae3d8c8f8dbef17b1774ced7ddb11cedb36
child 197181 ea688f9353895db67bcadc3b0da136c9e0176de8
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrillian
bugs951044
milestone31.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 951044 - [MediaEncoder::GTest] Implement unit test of VP8 video track encoder. r=rillian
content/media/gtest/TestVideoTrackEncoder.cpp
content/media/gtest/moz.build
new file mode 100644
--- /dev/null
+++ b/content/media/gtest/TestVideoTrackEncoder.cpp
@@ -0,0 +1,296 @@
+/* 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 "gtest/gtest.h"
+#include <algorithm>
+
+#include "mozilla/ArrayUtils.h"
+#include "VP8TrackEncoder.h"
+#include "ImageContainer.h"
+#include "MediaStreamGraph.h"
+#include "WebMWriter.h" // TODO: it's weird to include muxer header to get the class definition of VP8 METADATA
+
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+using namespace mozilla::layers;
+using namespace mozilla;
+
+// A helper object to generate of different YUV planes.
+class YUVBufferGenerator {
+public:
+  YUVBufferGenerator() {}
+
+  void Init(const mozilla::gfx::IntSize &aSize)
+  {
+    mImageSize = aSize;
+
+    int yPlaneLen = aSize.width * aSize.height;
+    int cbcrPlaneLen = (yPlaneLen + 1) / 2;
+    int frameLen = yPlaneLen + cbcrPlaneLen;
+
+    // Generate source buffer.
+    mSourceBuffer.SetLength(frameLen);
+
+    // Fill Y plane.
+    memset(mSourceBuffer.Elements(), 0x10, yPlaneLen);
+
+    // Fill Cb/Cr planes.
+    memset(mSourceBuffer.Elements() + yPlaneLen, 0x80, cbcrPlaneLen);
+  }
+
+  mozilla::gfx::IntSize GetSize() const
+  {
+    return mImageSize;
+  }
+
+  void Generate(nsTArray<nsRefPtr<Image> > &aImages)
+  {
+    aImages.AppendElement(CreateI420Image());
+    aImages.AppendElement(CreateNV12Image());
+    aImages.AppendElement(CreateNV21Image());
+  }
+
+private:
+  Image *CreateI420Image()
+  {
+    PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin());
+    PlanarYCbCrData data;
+
+    const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
+    const uint32_t halfWidth = (mImageSize.width + 1) / 2;
+    const uint32_t halfHeight = (mImageSize.height + 1) / 2;
+    const uint32_t uvPlaneSize = halfWidth * halfHeight;
+
+    // Y plane.
+    uint8_t *y = mSourceBuffer.Elements();
+    data.mYChannel = y;
+    data.mYSize.width = mImageSize.width;
+    data.mYSize.height = mImageSize.height;
+    data.mYStride = mImageSize.width;
+    data.mYSkip = 0;
+
+    // Cr plane.
+    uint8_t *cr = y + yPlaneSize + uvPlaneSize;
+    data.mCrChannel = cr;
+    data.mCrSkip = 0;
+
+    // Cb plane
+    uint8_t *cb = y + yPlaneSize;
+    data.mCbChannel = cb;
+    data.mCbSkip = 0;
+
+    // CrCb plane vectors.
+    data.mCbCrStride = halfWidth;
+    data.mCbCrSize.width = halfWidth;
+    data.mCbCrSize.height = halfHeight;
+
+    image->SetData(data);
+    return image;
+  }
+
+  Image *CreateNV12Image()
+  {
+    PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin());
+    PlanarYCbCrData data;
+
+    const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
+    const uint32_t halfWidth = (mImageSize.width + 1) / 2;
+    const uint32_t halfHeight = (mImageSize.height + 1) / 2;
+
+    // Y plane.
+    uint8_t *y = mSourceBuffer.Elements();
+    data.mYChannel = y;
+    data.mYSize.width = mImageSize.width;
+    data.mYSize.height = mImageSize.height;
+    data.mYStride = mImageSize.width;
+    data.mYSkip = 0;
+
+    // Cr plane.
+    uint8_t *cr = y + yPlaneSize;
+    data.mCrChannel = cr;
+    data.mCrSkip = 1;
+
+    // Cb plane
+    uint8_t *cb = y + yPlaneSize + 1;
+    data.mCbChannel = cb;
+    data.mCbSkip = 1;
+
+    // 4:2:0.
+    data.mCbCrStride = mImageSize.width;
+    data.mCbCrSize.width = halfWidth;
+    data.mCbCrSize.height = halfHeight;
+
+    image->SetData(data);
+    return image;
+  }
+
+  Image *CreateNV21Image()
+  {
+    PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin());
+    PlanarYCbCrData data;
+
+    const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
+    const uint32_t halfWidth = (mImageSize.width + 1) / 2;
+    const uint32_t halfHeight = (mImageSize.height + 1) / 2;
+
+    // Y plane.
+    uint8_t *y = mSourceBuffer.Elements();
+    data.mYChannel = y;
+    data.mYSize.width = mImageSize.width;
+    data.mYSize.height = mImageSize.height;
+    data.mYStride = mImageSize.width;
+    data.mYSkip = 0;
+
+    // Cr plane.
+    uint8_t *cr = y + yPlaneSize + 1;
+    data.mCrChannel = cr;
+    data.mCrSkip = 1;
+
+    // Cb plane
+    uint8_t *cb = y + yPlaneSize;
+    data.mCbChannel = cb;
+    data.mCbSkip = 1;
+
+    // 4:2:0.
+    data.mCbCrStride = mImageSize.width;
+    data.mCbCrSize.width = halfWidth;
+    data.mCbCrSize.height = halfHeight;
+
+    image->SetData(data);
+    return image;
+  }
+
+private:
+  mozilla::gfx::IntSize mImageSize;
+  nsTArray<uint8_t> mSourceBuffer;
+};
+
+struct InitParam {
+  bool mShouldSucceed;  // This parameter should cause success or fail result
+  int  mWidth;          // frame width
+  int  mHeight;         // frame height
+  mozilla::TrackRate mTrackRate; // track rate. 90K is the most commond track rate.
+};
+
+class TestVP8TrackEncoder: public VP8TrackEncoder
+{
+public:
+  ::testing::AssertionResult TestInit(const InitParam &aParam)
+  {
+    nsresult result = Init(aParam.mWidth, aParam.mHeight, aParam.mWidth, aParam.mHeight, aParam.mTrackRate);
+
+    if (((NS_FAILED(result) && aParam.mShouldSucceed)) || (NS_SUCCEEDED(result) && !aParam.mShouldSucceed))
+    {
+      return ::testing::AssertionFailure()
+                << " width = " << aParam.mWidth
+                << " height = " << aParam.mHeight
+                << " TrackRate = " << aParam.mTrackRate << ".";
+    }
+    else
+    {
+      return ::testing::AssertionSuccess();
+    }
+  }
+};
+
+// Init test
+TEST(VP8VideoTrackEncoder, Initialization)
+{
+  InitParam params[] = {
+    // Failure cases.
+    { false, 640, 480, 0 },      // Trackrate should be larger than 1.
+    { false, 640, 480, -1 },     // Trackrate should be larger than 1.
+    { false, 0, 0, 90000 },      // Height/ width should be larger than 1.
+    { false, 0, 1, 90000 },      // Height/ width should be larger than 1.
+    { false, 1, 0, 90000},       // Height/ width should be larger than 1.
+
+    // Success cases
+    { true, 640, 480, 90000},    // Standard VGA
+    { true, 800, 480, 90000},    // Standard WVGA
+    { true, 960, 540, 90000},    // Standard qHD
+    { true, 1280, 720, 90000}    // Standard HD
+  };
+
+  for (size_t i = 0; i < ArrayLength(params); i++)
+  {
+    TestVP8TrackEncoder encoder;
+    EXPECT_TRUE(encoder.TestInit(params[i]));
+  }
+}
+
+// Get MetaData test
+TEST(VP8VideoTrackEncoder, FetchMetaData)
+{
+  InitParam params[] = {
+    // Success cases
+    { true, 640, 480, 90000},    // Standard VGA
+    { true, 800, 480, 90000},    // Standard WVGA
+    { true, 960, 540, 90000},    // Standard qHD
+    { true, 1280, 720, 90000}    // Standard HD
+  };
+
+  for (size_t i = 0; i < ArrayLength(params); i++)
+  {
+    TestVP8TrackEncoder encoder;
+    EXPECT_TRUE(encoder.TestInit(params[i]));
+
+    nsRefPtr<TrackMetadataBase> meta = encoder.GetMetadata();
+    nsRefPtr<VP8Metadata> vp8Meta(static_cast<VP8Metadata*>(meta.get()));
+
+    // METADATA should be depend on how to initiate encoder.
+    EXPECT_TRUE(vp8Meta->mWidth == params[i].mWidth);
+    EXPECT_TRUE(vp8Meta->mHeight == params[i].mHeight);
+  }
+}
+
+// Encode test
+TEST(VP8VideoTrackEncoder, FrameEncode)
+{
+  // Initiate VP8 encoder
+  TestVP8TrackEncoder encoder;
+  InitParam param = {true, 640, 480, 90000};
+  encoder.TestInit(param);
+
+  // Create YUV images as source.
+  nsTArray<nsRefPtr<Image>> images;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  generator.Generate(images);
+
+  // Put generated YUV frame into video segment.
+  // Duration of each frame is 1 second.
+  VideoSegment segment;
+  for (nsTArray<nsRefPtr<Image>>::size_type i = 0; i < images.Length(); i++)
+  {
+    nsRefPtr<Image> image = images[i];
+    segment.AppendFrame(image.forget(), mozilla::TrackTicks(90000), generator.GetSize());
+  }
+
+  // track change notification.
+  encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, 0, 0, segment);
+
+  // Pull Encoded Data back from encoder.
+  EncodedFrameContainer container;
+  EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
+}
+
+// EOS test
+TEST(VP8VideoTrackEncoder, EncodeComplete)
+{
+  // Initiate VP8 encoder
+  TestVP8TrackEncoder encoder;
+  InitParam param = {true, 640, 480, 90000};
+  encoder.TestInit(param);
+
+  // track end notification.
+  VideoSegment segment;
+  encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, 0, MediaStreamListener::TRACK_EVENT_ENDED, segment);
+
+  // Pull Encoded Data back from encoder. Since we have sent
+  // EOS to encoder, encoder.GetEncodedTrack should return
+  // NS_OK immidiately.
+  EncodedFrameContainer container;
+  EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
+}
--- a/content/media/gtest/moz.build
+++ b/content/media/gtest/moz.build
@@ -6,16 +6,20 @@
 
 LIBRARY_NAME = 'media_gtest'
 
 UNIFIED_SOURCES += [
     'TestAudioCompactor.cpp',
     'TestTrackEncoder.cpp',
 ]
 
+if CONFIG['MOZ_WEBM_ENCODER']:
+    UNIFIED_SOURCES += ['TestVideoTrackEncoder.cpp',
+]
+
 EXPORT_LIBRARY = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '/content/media/encoder',
 ]