Coverage Report

Created: 2018-09-25 14:53

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