diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/gtest/TestWebMWriter.cpp | 388 |
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, ¶ms); + 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, ¶ms); + 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); +} |