Fri, 16 Jan 2015 04:50:19 +0100
Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | #include "gtest/gtest.h" |
michael@0 | 6 | #include <algorithm> |
michael@0 | 7 | |
michael@0 | 8 | #include "mozilla/ArrayUtils.h" |
michael@0 | 9 | #include "VP8TrackEncoder.h" |
michael@0 | 10 | #include "ImageContainer.h" |
michael@0 | 11 | #include "MediaStreamGraph.h" |
michael@0 | 12 | #include "WebMWriter.h" // TODO: it's weird to include muxer header to get the class definition of VP8 METADATA |
michael@0 | 13 | |
michael@0 | 14 | using ::testing::TestWithParam; |
michael@0 | 15 | using ::testing::Values; |
michael@0 | 16 | |
michael@0 | 17 | using namespace mozilla::layers; |
michael@0 | 18 | using namespace mozilla; |
michael@0 | 19 | |
michael@0 | 20 | // A helper object to generate of different YUV planes. |
michael@0 | 21 | class YUVBufferGenerator { |
michael@0 | 22 | public: |
michael@0 | 23 | YUVBufferGenerator() {} |
michael@0 | 24 | |
michael@0 | 25 | void Init(const mozilla::gfx::IntSize &aSize) |
michael@0 | 26 | { |
michael@0 | 27 | mImageSize = aSize; |
michael@0 | 28 | |
michael@0 | 29 | int yPlaneLen = aSize.width * aSize.height; |
michael@0 | 30 | int cbcrPlaneLen = (yPlaneLen + 1) / 2; |
michael@0 | 31 | int frameLen = yPlaneLen + cbcrPlaneLen; |
michael@0 | 32 | |
michael@0 | 33 | // Generate source buffer. |
michael@0 | 34 | mSourceBuffer.SetLength(frameLen); |
michael@0 | 35 | |
michael@0 | 36 | // Fill Y plane. |
michael@0 | 37 | memset(mSourceBuffer.Elements(), 0x10, yPlaneLen); |
michael@0 | 38 | |
michael@0 | 39 | // Fill Cb/Cr planes. |
michael@0 | 40 | memset(mSourceBuffer.Elements() + yPlaneLen, 0x80, cbcrPlaneLen); |
michael@0 | 41 | } |
michael@0 | 42 | |
michael@0 | 43 | mozilla::gfx::IntSize GetSize() const |
michael@0 | 44 | { |
michael@0 | 45 | return mImageSize; |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | void Generate(nsTArray<nsRefPtr<Image> > &aImages) |
michael@0 | 49 | { |
michael@0 | 50 | aImages.AppendElement(CreateI420Image()); |
michael@0 | 51 | aImages.AppendElement(CreateNV12Image()); |
michael@0 | 52 | aImages.AppendElement(CreateNV21Image()); |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | private: |
michael@0 | 56 | Image *CreateI420Image() |
michael@0 | 57 | { |
michael@0 | 58 | PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin()); |
michael@0 | 59 | PlanarYCbCrData data; |
michael@0 | 60 | |
michael@0 | 61 | const uint32_t yPlaneSize = mImageSize.width * mImageSize.height; |
michael@0 | 62 | const uint32_t halfWidth = (mImageSize.width + 1) / 2; |
michael@0 | 63 | const uint32_t halfHeight = (mImageSize.height + 1) / 2; |
michael@0 | 64 | const uint32_t uvPlaneSize = halfWidth * halfHeight; |
michael@0 | 65 | |
michael@0 | 66 | // Y plane. |
michael@0 | 67 | uint8_t *y = mSourceBuffer.Elements(); |
michael@0 | 68 | data.mYChannel = y; |
michael@0 | 69 | data.mYSize.width = mImageSize.width; |
michael@0 | 70 | data.mYSize.height = mImageSize.height; |
michael@0 | 71 | data.mYStride = mImageSize.width; |
michael@0 | 72 | data.mYSkip = 0; |
michael@0 | 73 | |
michael@0 | 74 | // Cr plane. |
michael@0 | 75 | uint8_t *cr = y + yPlaneSize + uvPlaneSize; |
michael@0 | 76 | data.mCrChannel = cr; |
michael@0 | 77 | data.mCrSkip = 0; |
michael@0 | 78 | |
michael@0 | 79 | // Cb plane |
michael@0 | 80 | uint8_t *cb = y + yPlaneSize; |
michael@0 | 81 | data.mCbChannel = cb; |
michael@0 | 82 | data.mCbSkip = 0; |
michael@0 | 83 | |
michael@0 | 84 | // CrCb plane vectors. |
michael@0 | 85 | data.mCbCrStride = halfWidth; |
michael@0 | 86 | data.mCbCrSize.width = halfWidth; |
michael@0 | 87 | data.mCbCrSize.height = halfHeight; |
michael@0 | 88 | |
michael@0 | 89 | image->SetData(data); |
michael@0 | 90 | return image; |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | Image *CreateNV12Image() |
michael@0 | 94 | { |
michael@0 | 95 | PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin()); |
michael@0 | 96 | PlanarYCbCrData data; |
michael@0 | 97 | |
michael@0 | 98 | const uint32_t yPlaneSize = mImageSize.width * mImageSize.height; |
michael@0 | 99 | const uint32_t halfWidth = (mImageSize.width + 1) / 2; |
michael@0 | 100 | const uint32_t halfHeight = (mImageSize.height + 1) / 2; |
michael@0 | 101 | |
michael@0 | 102 | // Y plane. |
michael@0 | 103 | uint8_t *y = mSourceBuffer.Elements(); |
michael@0 | 104 | data.mYChannel = y; |
michael@0 | 105 | data.mYSize.width = mImageSize.width; |
michael@0 | 106 | data.mYSize.height = mImageSize.height; |
michael@0 | 107 | data.mYStride = mImageSize.width; |
michael@0 | 108 | data.mYSkip = 0; |
michael@0 | 109 | |
michael@0 | 110 | // Cr plane. |
michael@0 | 111 | uint8_t *cr = y + yPlaneSize; |
michael@0 | 112 | data.mCrChannel = cr; |
michael@0 | 113 | data.mCrSkip = 1; |
michael@0 | 114 | |
michael@0 | 115 | // Cb plane |
michael@0 | 116 | uint8_t *cb = y + yPlaneSize + 1; |
michael@0 | 117 | data.mCbChannel = cb; |
michael@0 | 118 | data.mCbSkip = 1; |
michael@0 | 119 | |
michael@0 | 120 | // 4:2:0. |
michael@0 | 121 | data.mCbCrStride = mImageSize.width; |
michael@0 | 122 | data.mCbCrSize.width = halfWidth; |
michael@0 | 123 | data.mCbCrSize.height = halfHeight; |
michael@0 | 124 | |
michael@0 | 125 | image->SetData(data); |
michael@0 | 126 | return image; |
michael@0 | 127 | } |
michael@0 | 128 | |
michael@0 | 129 | Image *CreateNV21Image() |
michael@0 | 130 | { |
michael@0 | 131 | PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin()); |
michael@0 | 132 | PlanarYCbCrData data; |
michael@0 | 133 | |
michael@0 | 134 | const uint32_t yPlaneSize = mImageSize.width * mImageSize.height; |
michael@0 | 135 | const uint32_t halfWidth = (mImageSize.width + 1) / 2; |
michael@0 | 136 | const uint32_t halfHeight = (mImageSize.height + 1) / 2; |
michael@0 | 137 | |
michael@0 | 138 | // Y plane. |
michael@0 | 139 | uint8_t *y = mSourceBuffer.Elements(); |
michael@0 | 140 | data.mYChannel = y; |
michael@0 | 141 | data.mYSize.width = mImageSize.width; |
michael@0 | 142 | data.mYSize.height = mImageSize.height; |
michael@0 | 143 | data.mYStride = mImageSize.width; |
michael@0 | 144 | data.mYSkip = 0; |
michael@0 | 145 | |
michael@0 | 146 | // Cr plane. |
michael@0 | 147 | uint8_t *cr = y + yPlaneSize + 1; |
michael@0 | 148 | data.mCrChannel = cr; |
michael@0 | 149 | data.mCrSkip = 1; |
michael@0 | 150 | |
michael@0 | 151 | // Cb plane |
michael@0 | 152 | uint8_t *cb = y + yPlaneSize; |
michael@0 | 153 | data.mCbChannel = cb; |
michael@0 | 154 | data.mCbSkip = 1; |
michael@0 | 155 | |
michael@0 | 156 | // 4:2:0. |
michael@0 | 157 | data.mCbCrStride = mImageSize.width; |
michael@0 | 158 | data.mCbCrSize.width = halfWidth; |
michael@0 | 159 | data.mCbCrSize.height = halfHeight; |
michael@0 | 160 | |
michael@0 | 161 | image->SetData(data); |
michael@0 | 162 | return image; |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | private: |
michael@0 | 166 | mozilla::gfx::IntSize mImageSize; |
michael@0 | 167 | nsTArray<uint8_t> mSourceBuffer; |
michael@0 | 168 | }; |
michael@0 | 169 | |
michael@0 | 170 | struct InitParam { |
michael@0 | 171 | bool mShouldSucceed; // This parameter should cause success or fail result |
michael@0 | 172 | int mWidth; // frame width |
michael@0 | 173 | int mHeight; // frame height |
michael@0 | 174 | mozilla::TrackRate mTrackRate; // track rate. 90K is the most commond track rate. |
michael@0 | 175 | }; |
michael@0 | 176 | |
michael@0 | 177 | class TestVP8TrackEncoder: public VP8TrackEncoder |
michael@0 | 178 | { |
michael@0 | 179 | public: |
michael@0 | 180 | ::testing::AssertionResult TestInit(const InitParam &aParam) |
michael@0 | 181 | { |
michael@0 | 182 | nsresult result = Init(aParam.mWidth, aParam.mHeight, aParam.mWidth, aParam.mHeight, aParam.mTrackRate); |
michael@0 | 183 | |
michael@0 | 184 | if (((NS_FAILED(result) && aParam.mShouldSucceed)) || (NS_SUCCEEDED(result) && !aParam.mShouldSucceed)) |
michael@0 | 185 | { |
michael@0 | 186 | return ::testing::AssertionFailure() |
michael@0 | 187 | << " width = " << aParam.mWidth |
michael@0 | 188 | << " height = " << aParam.mHeight |
michael@0 | 189 | << " TrackRate = " << aParam.mTrackRate << "."; |
michael@0 | 190 | } |
michael@0 | 191 | else |
michael@0 | 192 | { |
michael@0 | 193 | return ::testing::AssertionSuccess(); |
michael@0 | 194 | } |
michael@0 | 195 | } |
michael@0 | 196 | }; |
michael@0 | 197 | |
michael@0 | 198 | // Init test |
michael@0 | 199 | TEST(VP8VideoTrackEncoder, Initialization) |
michael@0 | 200 | { |
michael@0 | 201 | InitParam params[] = { |
michael@0 | 202 | // Failure cases. |
michael@0 | 203 | { false, 640, 480, 0 }, // Trackrate should be larger than 1. |
michael@0 | 204 | { false, 640, 480, -1 }, // Trackrate should be larger than 1. |
michael@0 | 205 | { false, 0, 0, 90000 }, // Height/ width should be larger than 1. |
michael@0 | 206 | { false, 0, 1, 90000 }, // Height/ width should be larger than 1. |
michael@0 | 207 | { false, 1, 0, 90000}, // Height/ width should be larger than 1. |
michael@0 | 208 | |
michael@0 | 209 | // Success cases |
michael@0 | 210 | { true, 640, 480, 90000}, // Standard VGA |
michael@0 | 211 | { true, 800, 480, 90000}, // Standard WVGA |
michael@0 | 212 | { true, 960, 540, 90000}, // Standard qHD |
michael@0 | 213 | { true, 1280, 720, 90000} // Standard HD |
michael@0 | 214 | }; |
michael@0 | 215 | |
michael@0 | 216 | for (size_t i = 0; i < ArrayLength(params); i++) |
michael@0 | 217 | { |
michael@0 | 218 | TestVP8TrackEncoder encoder; |
michael@0 | 219 | EXPECT_TRUE(encoder.TestInit(params[i])); |
michael@0 | 220 | } |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | // Get MetaData test |
michael@0 | 224 | TEST(VP8VideoTrackEncoder, FetchMetaData) |
michael@0 | 225 | { |
michael@0 | 226 | InitParam params[] = { |
michael@0 | 227 | // Success cases |
michael@0 | 228 | { true, 640, 480, 90000}, // Standard VGA |
michael@0 | 229 | { true, 800, 480, 90000}, // Standard WVGA |
michael@0 | 230 | { true, 960, 540, 90000}, // Standard qHD |
michael@0 | 231 | { true, 1280, 720, 90000} // Standard HD |
michael@0 | 232 | }; |
michael@0 | 233 | |
michael@0 | 234 | for (size_t i = 0; i < ArrayLength(params); i++) |
michael@0 | 235 | { |
michael@0 | 236 | TestVP8TrackEncoder encoder; |
michael@0 | 237 | EXPECT_TRUE(encoder.TestInit(params[i])); |
michael@0 | 238 | |
michael@0 | 239 | nsRefPtr<TrackMetadataBase> meta = encoder.GetMetadata(); |
michael@0 | 240 | nsRefPtr<VP8Metadata> vp8Meta(static_cast<VP8Metadata*>(meta.get())); |
michael@0 | 241 | |
michael@0 | 242 | // METADATA should be depend on how to initiate encoder. |
michael@0 | 243 | EXPECT_TRUE(vp8Meta->mWidth == params[i].mWidth); |
michael@0 | 244 | EXPECT_TRUE(vp8Meta->mHeight == params[i].mHeight); |
michael@0 | 245 | } |
michael@0 | 246 | } |
michael@0 | 247 | |
michael@0 | 248 | // Encode test |
michael@0 | 249 | // XXX(bug 1018402): Disable this test when compiled with VS2013 because it |
michael@0 | 250 | // crashes. |
michael@0 | 251 | #if !defined(_MSC_VER) || _MSC_VER < 1800 |
michael@0 | 252 | TEST(VP8VideoTrackEncoder, FrameEncode) |
michael@0 | 253 | { |
michael@0 | 254 | // Initiate VP8 encoder |
michael@0 | 255 | TestVP8TrackEncoder encoder; |
michael@0 | 256 | InitParam param = {true, 640, 480, 90000}; |
michael@0 | 257 | encoder.TestInit(param); |
michael@0 | 258 | |
michael@0 | 259 | // Create YUV images as source. |
michael@0 | 260 | nsTArray<nsRefPtr<Image>> images; |
michael@0 | 261 | YUVBufferGenerator generator; |
michael@0 | 262 | generator.Init(mozilla::gfx::IntSize(640, 480)); |
michael@0 | 263 | generator.Generate(images); |
michael@0 | 264 | |
michael@0 | 265 | // Put generated YUV frame into video segment. |
michael@0 | 266 | // Duration of each frame is 1 second. |
michael@0 | 267 | VideoSegment segment; |
michael@0 | 268 | for (nsTArray<nsRefPtr<Image>>::size_type i = 0; i < images.Length(); i++) |
michael@0 | 269 | { |
michael@0 | 270 | nsRefPtr<Image> image = images[i]; |
michael@0 | 271 | segment.AppendFrame(image.forget(), mozilla::TrackTicks(90000), generator.GetSize()); |
michael@0 | 272 | } |
michael@0 | 273 | |
michael@0 | 274 | // track change notification. |
michael@0 | 275 | encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, 0, 0, segment); |
michael@0 | 276 | |
michael@0 | 277 | // Pull Encoded Data back from encoder. |
michael@0 | 278 | EncodedFrameContainer container; |
michael@0 | 279 | EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); |
michael@0 | 280 | } |
michael@0 | 281 | #endif // _MSC_VER |
michael@0 | 282 | |
michael@0 | 283 | // EOS test |
michael@0 | 284 | TEST(VP8VideoTrackEncoder, EncodeComplete) |
michael@0 | 285 | { |
michael@0 | 286 | // Initiate VP8 encoder |
michael@0 | 287 | TestVP8TrackEncoder encoder; |
michael@0 | 288 | InitParam param = {true, 640, 480, 90000}; |
michael@0 | 289 | encoder.TestInit(param); |
michael@0 | 290 | |
michael@0 | 291 | // track end notification. |
michael@0 | 292 | VideoSegment segment; |
michael@0 | 293 | encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, 0, MediaStreamListener::TRACK_EVENT_ENDED, segment); |
michael@0 | 294 | |
michael@0 | 295 | // Pull Encoded Data back from encoder. Since we have sent |
michael@0 | 296 | // EOS to encoder, encoder.GetEncodedTrack should return |
michael@0 | 297 | // NS_OK immidiately. |
michael@0 | 298 | EncodedFrameContainer container; |
michael@0 | 299 | EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); |
michael@0 | 300 | } |