summaryrefslogtreecommitdiffstats
path: root/dom/media/gtest/TestWebMWriter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/gtest/TestWebMWriter.cpp')
-rw-r--r--dom/media/gtest/TestWebMWriter.cpp388
1 files changed, 388 insertions, 0 deletions
diff --git a/dom/media/gtest/TestWebMWriter.cpp b/dom/media/gtest/TestWebMWriter.cpp
new file mode 100644
index 0000000000..ee1f387311
--- /dev/null
+++ b/dom/media/gtest/TestWebMWriter.cpp
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "nestegg/nestegg.h"
+#include "DriftCompensation.h"
+#include "OpusTrackEncoder.h"
+#include "VP8TrackEncoder.h"
+#include "WebMWriter.h"
+
+using namespace mozilla;
+
+class WebMOpusTrackEncoder : public OpusTrackEncoder {
+ public:
+ explicit WebMOpusTrackEncoder(TrackRate aTrackRate)
+ : OpusTrackEncoder(aTrackRate, mEncodedAudioQueue) {}
+ bool TestOpusCreation(int aChannels) {
+ if (NS_SUCCEEDED(Init(aChannels))) {
+ return true;
+ }
+ return false;
+ }
+ MediaQueue<EncodedFrame> mEncodedAudioQueue;
+};
+
+class WebMVP8TrackEncoder : public VP8TrackEncoder {
+ public:
+ explicit WebMVP8TrackEncoder(TrackRate aTrackRate = 90000)
+ : VP8TrackEncoder(nullptr, aTrackRate, mEncodedVideoQueue,
+ FrameDroppingMode::DISALLOW) {}
+
+ bool TestVP8Creation(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
+ int32_t aDisplayHeight) {
+ if (NS_SUCCEEDED(
+ Init(aWidth, aHeight, aDisplayWidth, aDisplayHeight, 30))) {
+ return true;
+ }
+ return false;
+ }
+ MediaQueue<EncodedFrame> mEncodedVideoQueue;
+};
+
+static void GetOpusMetadata(int aChannels, TrackRate aTrackRate,
+ nsTArray<RefPtr<TrackMetadataBase>>& aMeta) {
+ WebMOpusTrackEncoder opusEncoder(aTrackRate);
+ EXPECT_TRUE(opusEncoder.TestOpusCreation(aChannels));
+ aMeta.AppendElement(opusEncoder.GetMetadata());
+}
+
+static void GetVP8Metadata(int32_t aWidth, int32_t aHeight,
+ int32_t aDisplayWidth, int32_t aDisplayHeight,
+ TrackRate aTrackRate,
+ nsTArray<RefPtr<TrackMetadataBase>>& aMeta) {
+ WebMVP8TrackEncoder vp8Encoder;
+ EXPECT_TRUE(vp8Encoder.TestVP8Creation(aWidth, aHeight, aDisplayWidth,
+ aDisplayHeight));
+ aMeta.AppendElement(vp8Encoder.GetMetadata());
+}
+
+const uint64_t FIXED_DURATION = 1000000;
+const uint32_t FIXED_FRAMESIZE = 500;
+
+class TestWebMWriter : public WebMWriter {
+ public:
+ TestWebMWriter() : WebMWriter() {}
+
+ // When we append an I-Frame into WebM muxer, the muxer will treat previous
+ // data as "a cluster".
+ // In these test cases, we will call the function many times to enclose the
+ // previous cluster so that we can retrieve data by |GetContainerData|.
+ void AppendDummyFrame(EncodedFrame::FrameType aFrameType,
+ uint64_t aDuration) {
+ nsTArray<RefPtr<EncodedFrame>> encodedVideoData;
+ auto frameData = MakeRefPtr<EncodedFrame::FrameData>();
+ // Create dummy frame data.
+ frameData->SetLength(FIXED_FRAMESIZE);
+ encodedVideoData.AppendElement(
+ MakeRefPtr<EncodedFrame>(mTimestamp, aDuration, PR_USEC_PER_SEC,
+ aFrameType, std::move(frameData)));
+ WriteEncodedTrack(encodedVideoData, 0);
+ mTimestamp += media::TimeUnit::FromMicroseconds(aDuration);
+ }
+
+ bool HaveValidCluster() {
+ nsTArray<nsTArray<uint8_t>> encodedBuf;
+ GetContainerData(&encodedBuf, 0);
+ return !encodedBuf.IsEmpty();
+ }
+
+ // Timestamp accumulator that increased by AppendDummyFrame.
+ // Keep it public that we can do some testcases about it.
+ media::TimeUnit mTimestamp;
+};
+
+TEST(WebMWriter, Metadata)
+{
+ TestWebMWriter writer;
+
+ // The output should be empty since we didn't set any metadata in writer.
+ nsTArray<nsTArray<uint8_t>> encodedBuf;
+ writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER);
+ EXPECT_TRUE(encodedBuf.Length() == 0);
+ writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED);
+ EXPECT_TRUE(encodedBuf.Length() == 0);
+
+ nsTArray<RefPtr<TrackMetadataBase>> meta;
+
+ TrackRate trackRate = 44100;
+
+ // Get opus metadata.
+ int channel = 1;
+ GetOpusMetadata(channel, trackRate, meta);
+
+ // Get vp8 metadata
+ int32_t width = 640;
+ int32_t height = 480;
+ int32_t displayWidth = 640;
+ int32_t displayHeight = 480;
+ GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta);
+
+ // Set metadata
+ writer.SetMetadata(meta);
+
+ writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER);
+ EXPECT_TRUE(encodedBuf.Length() > 0);
+}
+
+TEST(WebMWriter, Cluster)
+{
+ TestWebMWriter writer;
+ nsTArray<RefPtr<TrackMetadataBase>> meta;
+ TrackRate trackRate = 48000;
+ // Get opus metadata.
+ int channel = 1;
+ GetOpusMetadata(channel, trackRate, meta);
+ // Get vp8 metadata
+ int32_t width = 320;
+ int32_t height = 240;
+ int32_t displayWidth = 320;
+ int32_t displayHeight = 240;
+ GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta);
+ writer.SetMetadata(meta);
+
+ nsTArray<nsTArray<uint8_t>> encodedBuf;
+ writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER);
+ EXPECT_TRUE(encodedBuf.Length() > 0);
+ encodedBuf.Clear();
+
+ // write the first I-Frame.
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
+ EXPECT_TRUE(writer.HaveValidCluster());
+
+ // The second I-Frame.
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
+ EXPECT_TRUE(writer.HaveValidCluster());
+
+ // P-Frame.
+ writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION);
+ EXPECT_TRUE(writer.HaveValidCluster());
+
+ // The third I-Frame.
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
+ EXPECT_TRUE(writer.HaveValidCluster());
+}
+
+TEST(WebMWriter, FLUSH_NEEDED)
+{
+ TestWebMWriter writer;
+ nsTArray<RefPtr<TrackMetadataBase>> meta;
+ TrackRate trackRate = 44100;
+ // Get opus metadata.
+ int channel = 2;
+ GetOpusMetadata(channel, trackRate, meta);
+ // Get vp8 metadata
+ int32_t width = 176;
+ int32_t height = 352;
+ int32_t displayWidth = 176;
+ int32_t displayHeight = 352;
+ GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta);
+ writer.SetMetadata(meta);
+ // Have data because the metadata is finished.
+ EXPECT_TRUE(writer.HaveValidCluster());
+
+ // write the first I-Frame.
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
+
+ // P-Frame
+ writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION);
+ // Have data because frames were written.
+ EXPECT_TRUE(writer.HaveValidCluster());
+ // No data because the previous check emptied it.
+ EXPECT_FALSE(writer.HaveValidCluster());
+
+ nsTArray<nsTArray<uint8_t>> encodedBuf;
+ // No data because the flag ContainerWriter::FLUSH_NEEDED does nothing.
+ writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED);
+ EXPECT_TRUE(encodedBuf.IsEmpty());
+ encodedBuf.Clear();
+
+ // P-Frame
+ writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION);
+ // Have data because we continue the previous cluster.
+ EXPECT_TRUE(writer.HaveValidCluster());
+
+ // I-Frame
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
+ // Have data with a new cluster.
+ EXPECT_TRUE(writer.HaveValidCluster());
+
+ // I-Frame
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
+ // Have data with a new cluster.
+ EXPECT_TRUE(writer.HaveValidCluster());
+}
+
+struct WebMioData {
+ nsTArray<uint8_t> data;
+ CheckedInt<size_t> offset;
+};
+
+static int webm_read(void* aBuffer, size_t aLength, void* aUserData) {
+ NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
+ WebMioData* ioData = static_cast<WebMioData*>(aUserData);
+
+ // Check the read length.
+ if (aLength > ioData->data.Length()) {
+ return 0;
+ }
+
+ // Check eos.
+ if (ioData->offset.value() >= ioData->data.Length()) {
+ return 0;
+ }
+
+ size_t oldOffset = ioData->offset.value();
+ ioData->offset += aLength;
+ if (!ioData->offset.isValid() ||
+ (ioData->offset.value() > ioData->data.Length())) {
+ return -1;
+ }
+ memcpy(aBuffer, ioData->data.Elements() + oldOffset, aLength);
+ return 1;
+}
+
+static int webm_seek(int64_t aOffset, int aWhence, void* aUserData) {
+ NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
+ WebMioData* ioData = static_cast<WebMioData*>(aUserData);
+
+ if (Abs(aOffset) > ioData->data.Length()) {
+ NS_ERROR("Invalid aOffset");
+ return -1;
+ }
+
+ switch (aWhence) {
+ case NESTEGG_SEEK_END: {
+ CheckedInt<size_t> tempOffset = ioData->data.Length();
+ ioData->offset = tempOffset + aOffset;
+ break;
+ }
+ case NESTEGG_SEEK_CUR:
+ ioData->offset += aOffset;
+ break;
+ case NESTEGG_SEEK_SET:
+ ioData->offset = aOffset;
+ break;
+ default:
+ NS_ERROR("Unknown whence");
+ return -1;
+ }
+
+ if (!ioData->offset.isValid()) {
+ NS_ERROR("Invalid offset");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int64_t webm_tell(void* aUserData) {
+ NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
+ WebMioData* ioData = static_cast<WebMioData*>(aUserData);
+ return ioData->offset.isValid() ? ioData->offset.value() : -1;
+}
+
+TEST(WebMWriter, bug970774_aspect_ratio)
+{
+ TestWebMWriter writer;
+ nsTArray<RefPtr<TrackMetadataBase>> meta;
+ TrackRate trackRate = 44100;
+ // Get opus metadata.
+ int channel = 1;
+ GetOpusMetadata(channel, trackRate, meta);
+ // Set vp8 metadata
+ int32_t width = 640;
+ int32_t height = 480;
+ int32_t displayWidth = 1280;
+ int32_t displayHeight = 960;
+ GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta);
+ writer.SetMetadata(meta);
+
+ // write the first I-Frame.
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
+
+ // write the second I-Frame.
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
+
+ // Get the metadata and the first cluster.
+ nsTArray<nsTArray<uint8_t>> encodedBuf;
+ writer.GetContainerData(&encodedBuf, 0);
+ // Flatten the encodedBuf.
+ WebMioData ioData;
+ ioData.offset = 0;
+ for (uint32_t i = 0; i < encodedBuf.Length(); ++i) {
+ ioData.data.AppendElements(encodedBuf[i]);
+ }
+
+ // Use nestegg to verify the information in metadata.
+ nestegg* context = nullptr;
+ nestegg_io io;
+ io.read = webm_read;
+ io.seek = webm_seek;
+ io.tell = webm_tell;
+ io.userdata = static_cast<void*>(&ioData);
+ int rv = nestegg_init(&context, io, nullptr, -1);
+ EXPECT_EQ(rv, 0);
+ unsigned int ntracks = 0;
+ rv = nestegg_track_count(context, &ntracks);
+ EXPECT_EQ(rv, 0);
+ EXPECT_EQ(ntracks, (unsigned int)2);
+ for (unsigned int track = 0; track < ntracks; ++track) {
+ int id = nestegg_track_codec_id(context, track);
+ EXPECT_NE(id, -1);
+ int type = nestegg_track_type(context, track);
+ if (type == NESTEGG_TRACK_VIDEO) {
+ nestegg_video_params params;
+ rv = nestegg_track_video_params(context, track, &params);
+ EXPECT_EQ(rv, 0);
+ EXPECT_EQ(width, static_cast<int32_t>(params.width));
+ EXPECT_EQ(height, static_cast<int32_t>(params.height));
+ EXPECT_EQ(displayWidth, static_cast<int32_t>(params.display_width));
+ EXPECT_EQ(displayHeight, static_cast<int32_t>(params.display_height));
+ } else if (type == NESTEGG_TRACK_AUDIO) {
+ nestegg_audio_params params;
+ rv = nestegg_track_audio_params(context, track, &params);
+ EXPECT_EQ(rv, 0);
+ EXPECT_EQ(channel, static_cast<int>(params.channels));
+ EXPECT_EQ(static_cast<double>(trackRate), params.rate);
+ }
+ }
+ if (context) {
+ nestegg_destroy(context);
+ }
+}
+
+/**
+ * Test that we don't crash when writing two video frames that are too far apart
+ * to fit in the same cluster (>32767ms).
+ */
+TEST(WebMWriter, LongVideoGap)
+{
+ TestWebMWriter writer;
+ nsTArray<RefPtr<TrackMetadataBase>> meta;
+ TrackRate trackRate = 44100;
+ // Set vp8 metadata
+ int32_t width = 640;
+ int32_t height = 480;
+ int32_t displayWidth = 640;
+ int32_t displayHeight = 480;
+ GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta);
+ writer.SetMetadata(meta);
+
+ // write the first I-Frame.
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME,
+ media::TimeUnit::FromSeconds(33).ToMicroseconds());
+
+ // write the second I-Frame.
+ writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME,
+ media::TimeUnit::FromSeconds(0.33).ToMicroseconds());
+
+ nsTArray<nsTArray<uint8_t>> encodedBuf;
+ writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER);
+ // metadata + 2 frames
+ EXPECT_EQ(encodedBuf.Length(), 3U);
+}