TestWebMWriter.cpp (12538B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "DriftCompensation.h" 8 #include "OpusTrackEncoder.h" 9 #include "VP8TrackEncoder.h" 10 #include "WebMWriter.h" 11 #include "gtest/gtest.h" 12 #include "mozilla/CheckedInt.h" 13 #include "mozilla/MathAlgorithms.h" 14 #include "nestegg/nestegg.h" 15 16 using namespace mozilla; 17 18 class WebMOpusTrackEncoder : public OpusTrackEncoder { 19 public: 20 explicit WebMOpusTrackEncoder(TrackRate aTrackRate) 21 : OpusTrackEncoder(aTrackRate, mEncodedAudioQueue) {} 22 bool TestOpusCreation(int aChannels) { 23 if (NS_SUCCEEDED(Init(aChannels))) { 24 return true; 25 } 26 return false; 27 } 28 MediaQueue<EncodedFrame> mEncodedAudioQueue; 29 }; 30 31 class WebMVP8TrackEncoder : public VP8TrackEncoder { 32 public: 33 explicit WebMVP8TrackEncoder(TrackRate aTrackRate = 90000) 34 : VP8TrackEncoder(nullptr, aTrackRate, mEncodedVideoQueue, 35 FrameDroppingMode::DISALLOW) {} 36 37 bool TestVP8Creation(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth, 38 int32_t aDisplayHeight) { 39 if (NS_SUCCEEDED( 40 Init(aWidth, aHeight, aDisplayWidth, aDisplayHeight, 30))) { 41 return true; 42 } 43 return false; 44 } 45 MediaQueue<EncodedFrame> mEncodedVideoQueue; 46 }; 47 48 static void GetOpusMetadata(int aChannels, TrackRate aTrackRate, 49 nsTArray<RefPtr<TrackMetadataBase>>& aMeta) { 50 WebMOpusTrackEncoder opusEncoder(aTrackRate); 51 EXPECT_TRUE(opusEncoder.TestOpusCreation(aChannels)); 52 aMeta.AppendElement(opusEncoder.GetMetadata()); 53 } 54 55 static void GetVP8Metadata(int32_t aWidth, int32_t aHeight, 56 int32_t aDisplayWidth, int32_t aDisplayHeight, 57 TrackRate aTrackRate, 58 nsTArray<RefPtr<TrackMetadataBase>>& aMeta) { 59 WebMVP8TrackEncoder vp8Encoder; 60 EXPECT_TRUE(vp8Encoder.TestVP8Creation(aWidth, aHeight, aDisplayWidth, 61 aDisplayHeight)); 62 aMeta.AppendElement(vp8Encoder.GetMetadata()); 63 } 64 65 const uint64_t FIXED_DURATION = 1000000; 66 const uint32_t FIXED_FRAMESIZE = 500; 67 68 class TestWebMWriter : public WebMWriter { 69 public: 70 TestWebMWriter() = default; 71 72 // When we append an I-Frame into WebM muxer, the muxer will treat previous 73 // data as "a cluster". 74 // In these test cases, we will call the function many times to enclose the 75 // previous cluster so that we can retrieve data by |GetContainerData|. 76 void AppendDummyFrame(EncodedFrame::FrameType aFrameType, 77 uint64_t aDuration) { 78 nsTArray<RefPtr<EncodedFrame>> encodedVideoData; 79 auto frameData = MakeRefPtr<EncodedFrame::FrameData>(); 80 // Create dummy frame data. 81 frameData->SetLength(FIXED_FRAMESIZE); 82 encodedVideoData.AppendElement( 83 MakeRefPtr<EncodedFrame>(mTimestamp, aDuration, PR_USEC_PER_SEC, 84 aFrameType, std::move(frameData))); 85 WriteEncodedTrack(encodedVideoData, 0); 86 mTimestamp += media::TimeUnit::FromMicroseconds(aDuration); 87 } 88 89 bool HaveValidCluster() { 90 nsTArray<nsTArray<uint8_t>> encodedBuf; 91 GetContainerData(&encodedBuf, 0); 92 return !encodedBuf.IsEmpty(); 93 } 94 95 // Timestamp accumulator that increased by AppendDummyFrame. 96 // Keep it public that we can do some testcases about it. 97 media::TimeUnit mTimestamp; 98 }; 99 100 TEST(WebMWriter, Metadata) 101 { 102 TestWebMWriter writer; 103 104 // The output should be empty since we didn't set any metadata in writer. 105 nsTArray<nsTArray<uint8_t>> encodedBuf; 106 writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); 107 EXPECT_TRUE(encodedBuf.Length() == 0); 108 writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED); 109 EXPECT_TRUE(encodedBuf.Length() == 0); 110 111 nsTArray<RefPtr<TrackMetadataBase>> meta; 112 113 TrackRate trackRate = 44100; 114 115 // Get opus metadata. 116 int channel = 1; 117 GetOpusMetadata(channel, trackRate, meta); 118 119 // Get vp8 metadata 120 int32_t width = 640; 121 int32_t height = 480; 122 int32_t displayWidth = 640; 123 int32_t displayHeight = 480; 124 GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta); 125 126 // Set metadata 127 writer.SetMetadata(meta); 128 129 writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); 130 EXPECT_TRUE(encodedBuf.Length() > 0); 131 } 132 133 TEST(WebMWriter, Cluster) 134 { 135 TestWebMWriter writer; 136 nsTArray<RefPtr<TrackMetadataBase>> meta; 137 TrackRate trackRate = 48000; 138 // Get opus metadata. 139 int channel = 1; 140 GetOpusMetadata(channel, trackRate, meta); 141 // Get vp8 metadata 142 int32_t width = 320; 143 int32_t height = 240; 144 int32_t displayWidth = 320; 145 int32_t displayHeight = 240; 146 GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta); 147 writer.SetMetadata(meta); 148 149 nsTArray<nsTArray<uint8_t>> encodedBuf; 150 writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); 151 EXPECT_TRUE(encodedBuf.Length() > 0); 152 encodedBuf.Clear(); 153 154 // write the first I-Frame. 155 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); 156 EXPECT_TRUE(writer.HaveValidCluster()); 157 158 // The second I-Frame. 159 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); 160 EXPECT_TRUE(writer.HaveValidCluster()); 161 162 // P-Frame. 163 writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION); 164 EXPECT_TRUE(writer.HaveValidCluster()); 165 166 // The third I-Frame. 167 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); 168 EXPECT_TRUE(writer.HaveValidCluster()); 169 } 170 171 TEST(WebMWriter, FLUSH_NEEDED) 172 { 173 TestWebMWriter writer; 174 nsTArray<RefPtr<TrackMetadataBase>> meta; 175 TrackRate trackRate = 44100; 176 // Get opus metadata. 177 int channel = 2; 178 GetOpusMetadata(channel, trackRate, meta); 179 // Get vp8 metadata 180 int32_t width = 176; 181 int32_t height = 352; 182 int32_t displayWidth = 176; 183 int32_t displayHeight = 352; 184 GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta); 185 writer.SetMetadata(meta); 186 // Have data because the metadata is finished. 187 EXPECT_TRUE(writer.HaveValidCluster()); 188 189 // write the first I-Frame. 190 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); 191 192 // P-Frame 193 writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION); 194 // Have data because frames were written. 195 EXPECT_TRUE(writer.HaveValidCluster()); 196 // No data because the previous check emptied it. 197 EXPECT_FALSE(writer.HaveValidCluster()); 198 199 nsTArray<nsTArray<uint8_t>> encodedBuf; 200 // No data because the flag ContainerWriter::FLUSH_NEEDED does nothing. 201 writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED); 202 EXPECT_TRUE(encodedBuf.IsEmpty()); 203 encodedBuf.Clear(); 204 205 // P-Frame 206 writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION); 207 // Have data because we continue the previous cluster. 208 EXPECT_TRUE(writer.HaveValidCluster()); 209 210 // I-Frame 211 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); 212 // Have data with a new cluster. 213 EXPECT_TRUE(writer.HaveValidCluster()); 214 215 // I-Frame 216 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); 217 // Have data with a new cluster. 218 EXPECT_TRUE(writer.HaveValidCluster()); 219 } 220 221 struct WebMioData { 222 nsTArray<uint8_t> data; 223 CheckedInt<size_t> offset; 224 }; 225 226 static int webm_read(void* aBuffer, size_t aLength, void* aUserData) { 227 MOZ_RELEASE_ASSERT(aUserData, "aUserData must point to a valid WebMioData"); 228 WebMioData* ioData = static_cast<WebMioData*>(aUserData); 229 230 // Check the read length. 231 if (aLength > ioData->data.Length()) { 232 return 0; 233 } 234 235 // Check eos. 236 if (ioData->offset.value() >= ioData->data.Length()) { 237 return 0; 238 } 239 240 size_t oldOffset = ioData->offset.value(); 241 ioData->offset += aLength; 242 if (!ioData->offset.isValid() || 243 (ioData->offset.value() > ioData->data.Length())) { 244 return -1; 245 } 246 memcpy(aBuffer, ioData->data.Elements() + oldOffset, aLength); 247 return 1; 248 } 249 250 static int webm_seek(int64_t aOffset, int aWhence, void* aUserData) { 251 MOZ_RELEASE_ASSERT(aUserData, "aUserData must point to a valid WebMioData"); 252 WebMioData* ioData = static_cast<WebMioData*>(aUserData); 253 254 if (Abs(aOffset) > ioData->data.Length()) { 255 NS_ERROR("Invalid aOffset"); 256 return -1; 257 } 258 259 switch (aWhence) { 260 case NESTEGG_SEEK_END: { 261 CheckedInt<size_t> tempOffset = ioData->data.Length(); 262 ioData->offset = tempOffset + aOffset; 263 break; 264 } 265 case NESTEGG_SEEK_CUR: 266 ioData->offset += aOffset; 267 break; 268 case NESTEGG_SEEK_SET: 269 ioData->offset = aOffset; 270 break; 271 default: 272 NS_ERROR("Unknown whence"); 273 return -1; 274 } 275 276 if (!ioData->offset.isValid()) { 277 NS_ERROR("Invalid offset"); 278 return -1; 279 } 280 281 return 0; 282 } 283 284 static int64_t webm_tell(void* aUserData) { 285 MOZ_RELEASE_ASSERT(aUserData, "aUserData must point to a valid WebMioData"); 286 WebMioData* ioData = static_cast<WebMioData*>(aUserData); 287 return ioData->offset.isValid() ? ioData->offset.value() : -1; 288 } 289 290 TEST(WebMWriter, bug970774_aspect_ratio) 291 { 292 TestWebMWriter writer; 293 nsTArray<RefPtr<TrackMetadataBase>> meta; 294 TrackRate trackRate = 44100; 295 // Get opus metadata. 296 int channel = 1; 297 GetOpusMetadata(channel, trackRate, meta); 298 // Set vp8 metadata 299 int32_t width = 640; 300 int32_t height = 480; 301 int32_t displayWidth = 1280; 302 int32_t displayHeight = 960; 303 GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta); 304 writer.SetMetadata(meta); 305 306 // write the first I-Frame. 307 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); 308 309 // write the second I-Frame. 310 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); 311 312 // Get the metadata and the first cluster. 313 nsTArray<nsTArray<uint8_t>> encodedBuf; 314 writer.GetContainerData(&encodedBuf, 0); 315 // Flatten the encodedBuf. 316 WebMioData ioData; 317 ioData.offset = 0; 318 for (uint32_t i = 0; i < encodedBuf.Length(); ++i) { 319 ioData.data.AppendElements(encodedBuf[i]); 320 } 321 322 // Use nestegg to verify the information in metadata. 323 nestegg* context = nullptr; 324 nestegg_io io; 325 io.read = webm_read; 326 io.seek = webm_seek; 327 io.tell = webm_tell; 328 io.userdata = static_cast<void*>(&ioData); 329 int rv = nestegg_init(&context, io, nullptr, -1); 330 EXPECT_EQ(rv, 0); 331 unsigned int ntracks = 0; 332 rv = nestegg_track_count(context, &ntracks); 333 EXPECT_EQ(rv, 0); 334 EXPECT_EQ(ntracks, (unsigned int)2); 335 for (unsigned int track = 0; track < ntracks; ++track) { 336 int id = nestegg_track_codec_id(context, track); 337 EXPECT_NE(id, -1); 338 int type = nestegg_track_type(context, track); 339 if (type == NESTEGG_TRACK_VIDEO) { 340 nestegg_video_params params; 341 rv = nestegg_track_video_params(context, track, ¶ms); 342 EXPECT_EQ(rv, 0); 343 EXPECT_EQ(width, static_cast<int32_t>(params.width)); 344 EXPECT_EQ(height, static_cast<int32_t>(params.height)); 345 EXPECT_EQ(displayWidth, static_cast<int32_t>(params.display_width)); 346 EXPECT_EQ(displayHeight, static_cast<int32_t>(params.display_height)); 347 } else if (type == NESTEGG_TRACK_AUDIO) { 348 nestegg_audio_params params; 349 rv = nestegg_track_audio_params(context, track, ¶ms); 350 EXPECT_EQ(rv, 0); 351 EXPECT_EQ(channel, static_cast<int>(params.channels)); 352 EXPECT_EQ(static_cast<double>(trackRate), params.rate); 353 } 354 } 355 if (context) { 356 nestegg_destroy(context); 357 } 358 } 359 360 /** 361 * Test that we don't crash when writing two video frames that are too far apart 362 * to fit in the same cluster (>32767ms). 363 */ 364 TEST(WebMWriter, LongVideoGap) 365 { 366 TestWebMWriter writer; 367 nsTArray<RefPtr<TrackMetadataBase>> meta; 368 TrackRate trackRate = 44100; 369 // Set vp8 metadata 370 int32_t width = 640; 371 int32_t height = 480; 372 int32_t displayWidth = 640; 373 int32_t displayHeight = 480; 374 GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta); 375 writer.SetMetadata(meta); 376 377 // write the first I-Frame. 378 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, 379 media::TimeUnit::FromSeconds(33).ToMicroseconds()); 380 381 // write the second I-Frame. 382 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, 383 media::TimeUnit::FromSeconds(0.33).ToMicroseconds()); 384 385 nsTArray<nsTArray<uint8_t>> encodedBuf; 386 writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); 387 // metadata + 2 frames 388 EXPECT_EQ(encodedBuf.Length(), 3U); 389 }