tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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, &params);
    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, &params);
    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 }