diff options
Diffstat (limited to 'dom/media/gtest/TestWebMWriter.cpp')
-rw-r--r-- | dom/media/gtest/TestWebMWriter.cpp | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/dom/media/gtest/TestWebMWriter.cpp b/dom/media/gtest/TestWebMWriter.cpp new file mode 100644 index 0000000000..e5fd8c7716 --- /dev/null +++ b/dom/media/gtest/TestWebMWriter.cpp @@ -0,0 +1,376 @@ +/* -*- 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 "OpusTrackEncoder.h" +#include "VP8TrackEncoder.h" +#include "WebMWriter.h" + +using namespace mozilla; + +class WebMOpusTrackEncoder : public OpusTrackEncoder +{ +public: + bool TestOpusCreation(int aChannels, int aSamplingRate) + { + if (NS_SUCCEEDED(Init(aChannels, aSamplingRate))) { + return true; + } + return false; + } +}; + +class WebMVP8TrackEncoder: public VP8TrackEncoder +{ +public: + explicit WebMVP8TrackEncoder(TrackRate aTrackRate = 90000) + : VP8TrackEncoder(aTrackRate) {} + + bool TestVP8Creation(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth, + int32_t aDisplayHeight) + { + if (NS_SUCCEEDED(Init(aWidth, aHeight, aDisplayWidth, aDisplayHeight))) { + return true; + } + return false; + } +}; + +const uint64_t FIXED_DURATION = 1000000; +const uint32_t FIXED_FRAMESIZE = 500; + +class TestWebMWriter: public WebMWriter +{ +public: + explicit TestWebMWriter(int aTrackTypes) + : WebMWriter(aTrackTypes), + mTimestamp(0) + {} + + void SetOpusMetadata(int aChannels, int aSampleRate) { + WebMOpusTrackEncoder opusEncoder; + EXPECT_TRUE(opusEncoder.TestOpusCreation(aChannels, aSampleRate)); + RefPtr<TrackMetadataBase> opusMeta = opusEncoder.GetMetadata(); + SetMetadata(opusMeta); + } + void SetVP8Metadata(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth, + int32_t aDisplayHeight,TrackRate aTrackRate) { + WebMVP8TrackEncoder vp8Encoder; + EXPECT_TRUE(vp8Encoder.TestVP8Creation(aWidth, aHeight, aDisplayWidth, + aDisplayHeight)); + RefPtr<TrackMetadataBase> vp8Meta = vp8Encoder.GetMetadata(); + SetMetadata(vp8Meta); + } + + // 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) { + EncodedFrameContainer encodedVideoData; + nsTArray<uint8_t> frameData; + RefPtr<EncodedFrame> videoData = new EncodedFrame(); + // Create dummy frame data. + frameData.SetLength(FIXED_FRAMESIZE); + videoData->SetFrameType(aFrameType); + videoData->SetTimeStamp(mTimestamp); + videoData->SetDuration(aDuration); + videoData->SwapInFrameData(frameData); + encodedVideoData.AppendEncodedFrame(videoData); + WriteEncodedTrack(encodedVideoData, 0); + mTimestamp += aDuration; + } + + bool HaveValidCluster() { + nsTArray<nsTArray<uint8_t> > encodedBuf; + GetContainerData(&encodedBuf, 0); + return (encodedBuf.Length() > 0) ? true : false; + } + + // Timestamp accumulator that increased by AppendDummyFrame. + // Keep it public that we can do some testcases about it. + uint64_t mTimestamp; +}; + +TEST(WebMWriter, Metadata) +{ + TestWebMWriter writer(ContainerWriter::CREATE_AUDIO_TRACK | + ContainerWriter::CREATE_VIDEO_TRACK); + + // 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); + + // Set opus metadata. + int channel = 1; + int sampleRate = 44100; + writer.SetOpusMetadata(channel, sampleRate); + + // No output data since we didn't set both audio/video + // metadata in writer. + writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); + EXPECT_TRUE(encodedBuf.Length() == 0); + writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED); + EXPECT_TRUE(encodedBuf.Length() == 0); + + // Set vp8 metadata + int32_t width = 640; + int32_t height = 480; + int32_t displayWidth = 640; + int32_t displayHeight = 480; + TrackRate aTrackRate = 90000; + writer.SetVP8Metadata(width, height, displayWidth, + displayHeight, aTrackRate); + + writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); + EXPECT_TRUE(encodedBuf.Length() > 0); +} + +TEST(WebMWriter, Cluster) +{ + TestWebMWriter writer(ContainerWriter::CREATE_AUDIO_TRACK | + ContainerWriter::CREATE_VIDEO_TRACK); + // Set opus metadata. + int channel = 1; + int sampleRate = 48000; + writer.SetOpusMetadata(channel, sampleRate); + // Set vp8 metadata + int32_t width = 320; + int32_t height = 240; + int32_t displayWidth = 320; + int32_t displayHeight = 240; + TrackRate aTrackRate = 90000; + writer.SetVP8Metadata(width, height, displayWidth, + displayHeight, aTrackRate); + + 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); + // No data because the cluster is not closed. + EXPECT_FALSE(writer.HaveValidCluster()); + + // The second I-Frame. + writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); + // Should have data because the first cluster is closed. + EXPECT_TRUE(writer.HaveValidCluster()); + + // P-Frame. + writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION); + // No data because the cluster is not closed. + EXPECT_FALSE(writer.HaveValidCluster()); + + // The third I-Frame. + writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); + // Should have data because the second cluster is closed. + EXPECT_TRUE(writer.HaveValidCluster()); +} + +TEST(WebMWriter, FLUSH_NEEDED) +{ + TestWebMWriter writer(ContainerWriter::CREATE_AUDIO_TRACK | + ContainerWriter::CREATE_VIDEO_TRACK); + // Set opus metadata. + int channel = 2; + int sampleRate = 44100; + writer.SetOpusMetadata(channel, sampleRate); + // Set vp8 metadata + int32_t width = 176; + int32_t height = 352; + int32_t displayWidth = 176; + int32_t displayHeight = 352; + TrackRate aTrackRate = 100000; + writer.SetVP8Metadata(width, height, displayWidth, + displayHeight, aTrackRate); + + // 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 the metadata is finished. + EXPECT_TRUE(writer.HaveValidCluster()); + // No data because the cluster is not closed and the metatdata had been + // retrieved + EXPECT_FALSE(writer.HaveValidCluster()); + + nsTArray<nsTArray<uint8_t> > encodedBuf; + // Have data because the flag ContainerWriter::FLUSH_NEEDED + writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED); + EXPECT_TRUE(encodedBuf.Length() > 0); + encodedBuf.Clear(); + + // P-Frame + writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION); + // No data because there is no cluster right now. The I-Frame had been + // flushed out. + EXPECT_FALSE(writer.HaveValidCluster()); + + // I-Frame + writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); + // No data because a cluster must starts form I-Frame and the + // cluster is not closed. + EXPECT_FALSE(writer.HaveValidCluster()); + + // I-Frame + writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); + // Have data because the previous cluster is closed. + 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(ContainerWriter::CREATE_AUDIO_TRACK | + ContainerWriter::CREATE_VIDEO_TRACK); + // Set opus metadata. + int channel = 1; + int sampleRate = 44100; + writer.SetOpusMetadata(channel, sampleRate); + // Set vp8 metadata + int32_t width = 640; + int32_t height = 480; + int32_t displayWidth = 1280; + int32_t displayHeight = 960; + TrackRate aTrackRate = 90000; + writer.SetVP8Metadata(width, height, displayWidth, + displayHeight, aTrackRate); + + // 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>(sampleRate), params.rate); + } + } + if (context) { + nestegg_destroy(context); + } +} + |