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