michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gtest/gtest.h" michael@0: #include michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "VP8TrackEncoder.h" michael@0: #include "ImageContainer.h" michael@0: #include "MediaStreamGraph.h" michael@0: #include "WebMWriter.h" // TODO: it's weird to include muxer header to get the class definition of VP8 METADATA michael@0: michael@0: using ::testing::TestWithParam; michael@0: using ::testing::Values; michael@0: michael@0: using namespace mozilla::layers; michael@0: using namespace mozilla; michael@0: michael@0: // A helper object to generate of different YUV planes. michael@0: class YUVBufferGenerator { michael@0: public: michael@0: YUVBufferGenerator() {} michael@0: michael@0: void Init(const mozilla::gfx::IntSize &aSize) michael@0: { michael@0: mImageSize = aSize; michael@0: michael@0: int yPlaneLen = aSize.width * aSize.height; michael@0: int cbcrPlaneLen = (yPlaneLen + 1) / 2; michael@0: int frameLen = yPlaneLen + cbcrPlaneLen; michael@0: michael@0: // Generate source buffer. michael@0: mSourceBuffer.SetLength(frameLen); michael@0: michael@0: // Fill Y plane. michael@0: memset(mSourceBuffer.Elements(), 0x10, yPlaneLen); michael@0: michael@0: // Fill Cb/Cr planes. michael@0: memset(mSourceBuffer.Elements() + yPlaneLen, 0x80, cbcrPlaneLen); michael@0: } michael@0: michael@0: mozilla::gfx::IntSize GetSize() const michael@0: { michael@0: return mImageSize; michael@0: } michael@0: michael@0: void Generate(nsTArray > &aImages) michael@0: { michael@0: aImages.AppendElement(CreateI420Image()); michael@0: aImages.AppendElement(CreateNV12Image()); michael@0: aImages.AppendElement(CreateNV21Image()); michael@0: } michael@0: michael@0: private: michael@0: Image *CreateI420Image() michael@0: { michael@0: PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin()); michael@0: PlanarYCbCrData data; michael@0: michael@0: const uint32_t yPlaneSize = mImageSize.width * mImageSize.height; michael@0: const uint32_t halfWidth = (mImageSize.width + 1) / 2; michael@0: const uint32_t halfHeight = (mImageSize.height + 1) / 2; michael@0: const uint32_t uvPlaneSize = halfWidth * halfHeight; michael@0: michael@0: // Y plane. michael@0: uint8_t *y = mSourceBuffer.Elements(); michael@0: data.mYChannel = y; michael@0: data.mYSize.width = mImageSize.width; michael@0: data.mYSize.height = mImageSize.height; michael@0: data.mYStride = mImageSize.width; michael@0: data.mYSkip = 0; michael@0: michael@0: // Cr plane. michael@0: uint8_t *cr = y + yPlaneSize + uvPlaneSize; michael@0: data.mCrChannel = cr; michael@0: data.mCrSkip = 0; michael@0: michael@0: // Cb plane michael@0: uint8_t *cb = y + yPlaneSize; michael@0: data.mCbChannel = cb; michael@0: data.mCbSkip = 0; michael@0: michael@0: // CrCb plane vectors. michael@0: data.mCbCrStride = halfWidth; michael@0: data.mCbCrSize.width = halfWidth; michael@0: data.mCbCrSize.height = halfHeight; michael@0: michael@0: image->SetData(data); michael@0: return image; michael@0: } michael@0: michael@0: Image *CreateNV12Image() michael@0: { michael@0: PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin()); michael@0: PlanarYCbCrData data; michael@0: michael@0: const uint32_t yPlaneSize = mImageSize.width * mImageSize.height; michael@0: const uint32_t halfWidth = (mImageSize.width + 1) / 2; michael@0: const uint32_t halfHeight = (mImageSize.height + 1) / 2; michael@0: michael@0: // Y plane. michael@0: uint8_t *y = mSourceBuffer.Elements(); michael@0: data.mYChannel = y; michael@0: data.mYSize.width = mImageSize.width; michael@0: data.mYSize.height = mImageSize.height; michael@0: data.mYStride = mImageSize.width; michael@0: data.mYSkip = 0; michael@0: michael@0: // Cr plane. michael@0: uint8_t *cr = y + yPlaneSize; michael@0: data.mCrChannel = cr; michael@0: data.mCrSkip = 1; michael@0: michael@0: // Cb plane michael@0: uint8_t *cb = y + yPlaneSize + 1; michael@0: data.mCbChannel = cb; michael@0: data.mCbSkip = 1; michael@0: michael@0: // 4:2:0. michael@0: data.mCbCrStride = mImageSize.width; michael@0: data.mCbCrSize.width = halfWidth; michael@0: data.mCbCrSize.height = halfHeight; michael@0: michael@0: image->SetData(data); michael@0: return image; michael@0: } michael@0: michael@0: Image *CreateNV21Image() michael@0: { michael@0: PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin()); michael@0: PlanarYCbCrData data; michael@0: michael@0: const uint32_t yPlaneSize = mImageSize.width * mImageSize.height; michael@0: const uint32_t halfWidth = (mImageSize.width + 1) / 2; michael@0: const uint32_t halfHeight = (mImageSize.height + 1) / 2; michael@0: michael@0: // Y plane. michael@0: uint8_t *y = mSourceBuffer.Elements(); michael@0: data.mYChannel = y; michael@0: data.mYSize.width = mImageSize.width; michael@0: data.mYSize.height = mImageSize.height; michael@0: data.mYStride = mImageSize.width; michael@0: data.mYSkip = 0; michael@0: michael@0: // Cr plane. michael@0: uint8_t *cr = y + yPlaneSize + 1; michael@0: data.mCrChannel = cr; michael@0: data.mCrSkip = 1; michael@0: michael@0: // Cb plane michael@0: uint8_t *cb = y + yPlaneSize; michael@0: data.mCbChannel = cb; michael@0: data.mCbSkip = 1; michael@0: michael@0: // 4:2:0. michael@0: data.mCbCrStride = mImageSize.width; michael@0: data.mCbCrSize.width = halfWidth; michael@0: data.mCbCrSize.height = halfHeight; michael@0: michael@0: image->SetData(data); michael@0: return image; michael@0: } michael@0: michael@0: private: michael@0: mozilla::gfx::IntSize mImageSize; michael@0: nsTArray mSourceBuffer; michael@0: }; michael@0: michael@0: struct InitParam { michael@0: bool mShouldSucceed; // This parameter should cause success or fail result michael@0: int mWidth; // frame width michael@0: int mHeight; // frame height michael@0: mozilla::TrackRate mTrackRate; // track rate. 90K is the most commond track rate. michael@0: }; michael@0: michael@0: class TestVP8TrackEncoder: public VP8TrackEncoder michael@0: { michael@0: public: michael@0: ::testing::AssertionResult TestInit(const InitParam &aParam) michael@0: { michael@0: nsresult result = Init(aParam.mWidth, aParam.mHeight, aParam.mWidth, aParam.mHeight, aParam.mTrackRate); michael@0: michael@0: if (((NS_FAILED(result) && aParam.mShouldSucceed)) || (NS_SUCCEEDED(result) && !aParam.mShouldSucceed)) michael@0: { michael@0: return ::testing::AssertionFailure() michael@0: << " width = " << aParam.mWidth michael@0: << " height = " << aParam.mHeight michael@0: << " TrackRate = " << aParam.mTrackRate << "."; michael@0: } michael@0: else michael@0: { michael@0: return ::testing::AssertionSuccess(); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: // Init test michael@0: TEST(VP8VideoTrackEncoder, Initialization) michael@0: { michael@0: InitParam params[] = { michael@0: // Failure cases. michael@0: { false, 640, 480, 0 }, // Trackrate should be larger than 1. michael@0: { false, 640, 480, -1 }, // Trackrate should be larger than 1. michael@0: { false, 0, 0, 90000 }, // Height/ width should be larger than 1. michael@0: { false, 0, 1, 90000 }, // Height/ width should be larger than 1. michael@0: { false, 1, 0, 90000}, // Height/ width should be larger than 1. michael@0: michael@0: // Success cases michael@0: { true, 640, 480, 90000}, // Standard VGA michael@0: { true, 800, 480, 90000}, // Standard WVGA michael@0: { true, 960, 540, 90000}, // Standard qHD michael@0: { true, 1280, 720, 90000} // Standard HD michael@0: }; michael@0: michael@0: for (size_t i = 0; i < ArrayLength(params); i++) michael@0: { michael@0: TestVP8TrackEncoder encoder; michael@0: EXPECT_TRUE(encoder.TestInit(params[i])); michael@0: } michael@0: } michael@0: michael@0: // Get MetaData test michael@0: TEST(VP8VideoTrackEncoder, FetchMetaData) michael@0: { michael@0: InitParam params[] = { michael@0: // Success cases michael@0: { true, 640, 480, 90000}, // Standard VGA michael@0: { true, 800, 480, 90000}, // Standard WVGA michael@0: { true, 960, 540, 90000}, // Standard qHD michael@0: { true, 1280, 720, 90000} // Standard HD michael@0: }; michael@0: michael@0: for (size_t i = 0; i < ArrayLength(params); i++) michael@0: { michael@0: TestVP8TrackEncoder encoder; michael@0: EXPECT_TRUE(encoder.TestInit(params[i])); michael@0: michael@0: nsRefPtr meta = encoder.GetMetadata(); michael@0: nsRefPtr vp8Meta(static_cast(meta.get())); michael@0: michael@0: // METADATA should be depend on how to initiate encoder. michael@0: EXPECT_TRUE(vp8Meta->mWidth == params[i].mWidth); michael@0: EXPECT_TRUE(vp8Meta->mHeight == params[i].mHeight); michael@0: } michael@0: } michael@0: michael@0: // Encode test michael@0: // XXX(bug 1018402): Disable this test when compiled with VS2013 because it michael@0: // crashes. michael@0: #if !defined(_MSC_VER) || _MSC_VER < 1800 michael@0: TEST(VP8VideoTrackEncoder, FrameEncode) michael@0: { michael@0: // Initiate VP8 encoder michael@0: TestVP8TrackEncoder encoder; michael@0: InitParam param = {true, 640, 480, 90000}; michael@0: encoder.TestInit(param); michael@0: michael@0: // Create YUV images as source. michael@0: nsTArray> images; michael@0: YUVBufferGenerator generator; michael@0: generator.Init(mozilla::gfx::IntSize(640, 480)); michael@0: generator.Generate(images); michael@0: michael@0: // Put generated YUV frame into video segment. michael@0: // Duration of each frame is 1 second. michael@0: VideoSegment segment; michael@0: for (nsTArray>::size_type i = 0; i < images.Length(); i++) michael@0: { michael@0: nsRefPtr image = images[i]; michael@0: segment.AppendFrame(image.forget(), mozilla::TrackTicks(90000), generator.GetSize()); michael@0: } michael@0: michael@0: // track change notification. michael@0: encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, 0, 0, segment); michael@0: michael@0: // Pull Encoded Data back from encoder. michael@0: EncodedFrameContainer container; michael@0: EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); michael@0: } michael@0: #endif // _MSC_VER michael@0: michael@0: // EOS test michael@0: TEST(VP8VideoTrackEncoder, EncodeComplete) michael@0: { michael@0: // Initiate VP8 encoder michael@0: TestVP8TrackEncoder encoder; michael@0: InitParam param = {true, 640, 480, 90000}; michael@0: encoder.TestInit(param); michael@0: michael@0: // track end notification. michael@0: VideoSegment segment; michael@0: encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, 0, MediaStreamListener::TRACK_EVENT_ENDED, segment); michael@0: michael@0: // Pull Encoded Data back from encoder. Since we have sent michael@0: // EOS to encoder, encoder.GetEncodedTrack should return michael@0: // NS_OK immidiately. michael@0: EncodedFrameContainer container; michael@0: EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); michael@0: }