import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,7 @@
[package]
name = "mp4parse-gtest"
version = "0.1.0"
authors = ["nobody@mozilla.org"]
[lib]
path = "hello.rs"

View file

@ -0,0 +1,47 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "nsThreadUtils.h"
#ifndef __GMPTestMonitor_h__
#define __GMPTestMonitor_h__
class GMPTestMonitor
{
public:
GMPTestMonitor()
: mFinished(false)
{
}
void AwaitFinished()
{
MOZ_ASSERT(NS_IsMainThread());
while (!mFinished) {
NS_ProcessNextEvent(nullptr, true);
}
mFinished = false;
}
private:
void MarkFinished()
{
MOZ_ASSERT(NS_IsMainThread());
mFinished = true;
}
public:
void SetFinished()
{
NS_DispatchToMainThread(mozilla::NewNonOwningRunnableMethod(this,
&GMPTestMonitor::MarkFinished));
}
private:
bool mFinished;
};
#endif // __GMPTestMonitor_h__

View file

@ -0,0 +1,54 @@
/* 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/. */
#ifndef MOCK_MEDIA_DECODER_OWNER_H_
#define MOCK_MEDIA_DECODER_OWNER_H_
#include "MediaDecoderOwner.h"
#include "nsAutoPtr.h"
namespace mozilla
{
class MockMediaDecoderOwner : public MediaDecoderOwner
{
public:
nsresult DispatchAsyncEvent(const nsAString& aName) override
{
return NS_OK;
}
void FireTimeUpdate(bool aPeriodic) override {}
bool GetPaused() override { return false; }
void MetadataLoaded(const MediaInfo* aInfo,
nsAutoPtr<const MetadataTags> aTags) override
{
}
void NetworkError() override {}
void DecodeError(const MediaResult& aError) override {}
bool HasError() const override { return false; }
void LoadAborted() override {}
void PlaybackEnded() override {}
void SeekStarted() override {}
void SeekCompleted() override {}
void DownloadProgressed() override {}
void UpdateReadyState() override {}
void FirstFrameLoaded() override {}
void DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
const nsAString& aInitDataType) override {}
bool IsActive() const override { return true; }
bool IsHidden() const override { return false; }
void DownloadSuspended() override {}
void DownloadResumed(bool aForceNetworkLoading) override {}
void NotifySuspendedByCache(bool aIsSuspended) override {}
void NotifyDecoderPrincipalChanged() override {}
VideoFrameContainer* GetVideoFrameContainer() override
{
return nullptr;
}
void SetAudibleState(bool aAudible) override {}
void NotifyXPCOMShutdown() override {}
};
}
#endif

View file

@ -0,0 +1,116 @@
/* 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 "MockMediaResource.h"
#include <sys/types.h>
#include <sys/stat.h>
namespace mozilla
{
MockMediaResource::MockMediaResource(const char* aFileName, const nsACString& aContentType)
: mFileHandle(nullptr)
, mFileName(aFileName)
, mContentType(aContentType)
{
}
nsresult
MockMediaResource::Open(nsIStreamListener** aStreamListener)
{
mFileHandle = fopen(mFileName, "rb");
if (mFileHandle == nullptr) {
printf_stderr("Can't open %s\n", mFileName);
return NS_ERROR_FAILURE;
}
return NS_OK;
}
MockMediaResource::~MockMediaResource()
{
if (mFileHandle != nullptr) {
fclose(mFileHandle);
}
}
nsresult
MockMediaResource::ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount,
uint32_t* aBytes)
{
if (mFileHandle == nullptr) {
return NS_ERROR_FAILURE;
}
// Make it fail if we're re-entrant
if (mEntry++) {
MOZ_ASSERT(false);
return NS_ERROR_FAILURE;
}
fseek(mFileHandle, aOffset, SEEK_SET);
size_t objectsRead = fread(aBuffer, aCount, 1, mFileHandle);
*aBytes = objectsRead == 1 ? aCount : 0;
mEntry--;
return ferror(mFileHandle) ? NS_ERROR_FAILURE : NS_OK;
}
int64_t
MockMediaResource::GetLength()
{
if (mFileHandle == nullptr) {
return -1;
}
fseek(mFileHandle, 0, SEEK_END);
return ftell(mFileHandle);
}
void
MockMediaResource::MockClearBufferedRanges()
{
mRanges.Clear();
}
void
MockMediaResource::MockAddBufferedRange(int64_t aStart, int64_t aEnd)
{
mRanges += MediaByteRange(aStart, aEnd);
}
int64_t
MockMediaResource::GetNextCachedData(int64_t aOffset)
{
if (!aOffset) {
return mRanges.Length() ? mRanges[0].mStart : -1;
}
for (size_t i = 0; i < mRanges.Length(); i++) {
if (aOffset == mRanges[i].mStart) {
++i;
return i < mRanges.Length() ? mRanges[i].mStart : -1;
}
}
return -1;
}
int64_t
MockMediaResource::GetCachedDataEnd(int64_t aOffset)
{
for (size_t i = 0; i < mRanges.Length(); i++) {
if (aOffset == mRanges[i].mStart) {
return mRanges[i].mEnd;
}
}
return -1;
}
nsresult
MockMediaResource::GetCachedRanges(MediaByteRangeSet& aRanges)
{
aRanges = mRanges;
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,83 @@
/* 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/. */
#ifndef MOCK_MEDIA_RESOURCE_H_
#define MOCK_MEDIA_RESOURCE_H_
#include "MediaResource.h"
#include "nsTArray.h"
#include "mozilla/Atomics.h"
namespace mozilla
{
class MockMediaResource : public MediaResource
{
public:
explicit MockMediaResource(const char* aFileName, const nsACString& aMimeType = NS_LITERAL_CSTRING("video/mp4"));
nsIURI* URI() const override { return nullptr; }
nsresult Close() override { return NS_OK; }
void Suspend(bool aCloseImmediately) override {}
void Resume() override {}
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override
{
return nullptr;
}
bool CanClone() override { return false; }
already_AddRefed<MediaResource> CloneData(MediaResourceCallback*)
override
{
return nullptr;
}
void SetReadMode(MediaCacheStream::ReadMode aMode) override {}
void SetPlaybackRate(uint32_t aBytesPerSecond) override {}
nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount,
uint32_t* aBytes) override;
int64_t Tell() override { return 0; }
void Pin() override {}
void Unpin() override {}
double GetDownloadRate(bool* aIsReliable) override { return 0; }
int64_t GetLength() override;
int64_t GetNextCachedData(int64_t aOffset) override;
int64_t GetCachedDataEnd(int64_t aOffset) override;
bool IsDataCachedToEndOfResource(int64_t aOffset) override
{
return false;
}
bool IsSuspendedByCache() override { return false; }
bool IsSuspended() override { return false; }
nsresult ReadFromCache(char* aBuffer, int64_t aOffset,
uint32_t aCount) override
{
uint32_t bytesRead = 0;
nsresult rv = ReadAt(aOffset, aBuffer, aCount, &bytesRead);
NS_ENSURE_SUCCESS(rv, rv);
return bytesRead == aCount ? NS_OK : NS_ERROR_FAILURE;
}
bool IsTransportSeekable() override { return true; }
nsresult Open(nsIStreamListener** aStreamListener) override;
nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override;
const nsCString& GetContentType() const override
{
return mContentType;
}
void MockClearBufferedRanges();
void MockAddBufferedRange(int64_t aStart, int64_t aEnd);
protected:
virtual ~MockMediaResource();
private:
FILE* mFileHandle;
const char* mFileName;
MediaByteRangeSet mRanges;
Atomic<int> mEntry;
const nsCString mContentType;
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,57 @@
/* -*- 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 <stdint.h>
#include "AudioBufferUtils.h"
#include "gtest/gtest.h"
const uint32_t FRAMES = 256;
const uint32_t CHANNELS = 2;
const uint32_t SAMPLES = CHANNELS * FRAMES;
TEST(AudioBuffers, Test)
{
mozilla::AudioCallbackBufferWrapper<float, CHANNELS> mBuffer;
mozilla::SpillBuffer<float, 128, CHANNELS> b;
float fromCallback[SAMPLES];
float other[SAMPLES];
for (uint32_t i = 0; i < SAMPLES; i++) {
other[i] = 1.0;
fromCallback[i] = 0.0;
}
// Set the buffer in the wrapper from the callback
mBuffer.SetBuffer(fromCallback, FRAMES);
// Fill the SpillBuffer with data.
ASSERT_TRUE(b.Fill(other, 15) == 15);
ASSERT_TRUE(b.Fill(other, 17) == 17);
for (uint32_t i = 0; i < 32 * CHANNELS; i++) {
other[i] = 0.0;
}
// Empty it in the AudioCallbackBufferWrapper
ASSERT_TRUE(b.Empty(mBuffer) == 32);
// Check available return something reasonnable
ASSERT_TRUE(mBuffer.Available() == FRAMES - 32);
// Fill the buffer with the rest of the data
mBuffer.WriteFrames(other + 32 * CHANNELS, FRAMES - 32);
// Check the buffer is now full
ASSERT_TRUE(mBuffer.Available() == 0);
for (uint32_t i = 0 ; i < SAMPLES; i++) {
ASSERT_TRUE(fromCallback[i] == 1.0) <<
"Difference at " << i << " (" << fromCallback[i] << " != " << 1.0 <<
")\n";
}
ASSERT_TRUE(b.Fill(other, FRAMES) == 128);
ASSERT_TRUE(b.Fill(other, FRAMES) == 0);
ASSERT_TRUE(b.Empty(mBuffer) == 0);
}

View file

@ -0,0 +1,145 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "AudioCompactor.h"
#include "MediaDecoderReader.h"
using mozilla::AudioCompactor;
using mozilla::AudioData;
using mozilla::AudioDataValue;
using mozilla::MediaDecoderReader;
using mozilla::MediaQueue;
class MemoryFunctor : public nsDequeFunctor {
public:
MemoryFunctor() : mSize(0) {}
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
void* operator()(void* aObject) override {
const AudioData* audioData = static_cast<const AudioData*>(aObject);
mSize += audioData->SizeOfIncludingThis(MallocSizeOf);
return nullptr;
}
size_t mSize;
};
class TestCopy
{
public:
TestCopy(uint32_t aFrames, uint32_t aChannels,
uint32_t &aCallCount, uint32_t &aFrameCount)
: mFrames(aFrames)
, mChannels(aChannels)
, mCallCount(aCallCount)
, mFrameCount(aFrameCount)
{ }
uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples)
{
mCallCount += 1;
uint32_t frames = std::min(mFrames - mFrameCount, aSamples / mChannels);
mFrameCount += frames;
return frames;
}
private:
const uint32_t mFrames;
const uint32_t mChannels;
uint32_t &mCallCount;
uint32_t &mFrameCount;
};
static void TestAudioCompactor(size_t aBytes)
{
MediaQueue<AudioData> queue;
AudioCompactor compactor(queue);
uint64_t offset = 0;
uint64_t time = 0;
uint32_t sampleRate = 44000;
uint32_t channels = 2;
uint32_t frames = aBytes / (channels * sizeof(AudioDataValue));
size_t maxSlop = aBytes / AudioCompactor::MAX_SLOP_DIVISOR;
uint32_t callCount = 0;
uint32_t frameCount = 0;
compactor.Push(offset, time, sampleRate, frames, channels,
TestCopy(frames, channels, callCount, frameCount));
EXPECT_GT(callCount, 0U) << "copy functor never called";
EXPECT_EQ(frames, frameCount) << "incorrect number of frames copied";
MemoryFunctor memoryFunc;
queue.LockedForEach(memoryFunc);
size_t allocSize = memoryFunc.mSize - (callCount * sizeof(AudioData));
size_t slop = allocSize - aBytes;
EXPECT_LE(slop, maxSlop) << "allowed too much allocation slop";
}
TEST(Media, AudioCompactor_4000)
{
TestAudioCompactor(4000);
}
TEST(Media, AudioCompactor_4096)
{
TestAudioCompactor(4096);
}
TEST(Media, AudioCompactor_5000)
{
TestAudioCompactor(5000);
}
TEST(Media, AudioCompactor_5256)
{
TestAudioCompactor(5256);
}
TEST(Media, AudioCompactor_NativeCopy)
{
const uint32_t channels = 2;
const size_t srcBytes = 32;
const uint32_t srcSamples = srcBytes / sizeof(AudioDataValue);
const uint32_t srcFrames = srcSamples / channels;
uint8_t src[srcBytes];
for (uint32_t i = 0; i < srcBytes; ++i) {
src[i] = i;
}
AudioCompactor::NativeCopy copy(src, srcBytes, channels);
const uint32_t dstSamples = srcSamples * 2;
AudioDataValue dst[dstSamples];
const AudioDataValue notCopied = 0xffff;
for (uint32_t i = 0; i < dstSamples; ++i) {
dst[i] = notCopied;
}
const uint32_t copyCount = 8;
uint32_t copiedFrames = 0;
uint32_t nextSample = 0;
for (uint32_t i = 0; i < copyCount; ++i) {
uint32_t copySamples = dstSamples / copyCount;
copiedFrames += copy(dst + nextSample, copySamples);
nextSample += copySamples;
}
EXPECT_EQ(srcFrames, copiedFrames) << "copy exact number of source frames";
// Verify that the only the correct bytes were copied.
for (uint32_t i = 0; i < dstSamples; ++i) {
if (i < srcSamples) {
EXPECT_NE(notCopied, dst[i]) << "should have copied over these bytes";
} else {
EXPECT_EQ(notCopied, dst[i]) << "should not have copied over these bytes";
}
}
}

View file

@ -0,0 +1,165 @@
/* -*- 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 "AudioMixer.h"
#include "gtest/gtest.h"
using mozilla::AudioDataValue;
using mozilla::AudioSampleFormat;
namespace audio_mixer {
struct MixerConsumer : public mozilla::MixerCallbackReceiver
{
/* In this test, the different audio stream and channels are always created to
* cancel each other. */
void MixerCallback(AudioDataValue* aData, AudioSampleFormat aFormat, uint32_t aChannels, uint32_t aFrames, uint32_t aSampleRate)
{
bool silent = true;
for (uint32_t i = 0; i < aChannels * aFrames; i++) {
if (aData[i] != 0.0) {
if (aFormat == mozilla::AUDIO_FORMAT_S16) {
fprintf(stderr, "Sample at %d is not silent: %d\n", i, (short)aData[i]);
} else {
fprintf(stderr, "Sample at %d is not silent: %f\n", i, (float)aData[i]);
}
silent = false;
}
}
ASSERT_TRUE(silent);
}
};
/* Helper function to give us the maximum and minimum value that don't clip,
* for a given sample format (integer or floating-point). */
template<typename T>
T GetLowValue();
template<typename T>
T GetHighValue();
template<>
float GetLowValue<float>() {
return -1.0;
}
template<>
short GetLowValue<short>() {
return -INT16_MAX;
}
template<>
float GetHighValue<float>() {
return 1.0;
}
template<>
short GetHighValue<short>() {
return INT16_MAX;
}
void FillBuffer(AudioDataValue* aBuffer, uint32_t aLength, AudioDataValue aValue)
{
AudioDataValue* end = aBuffer + aLength;
while (aBuffer != end) {
*aBuffer++ = aValue;
}
}
TEST(AudioMixer, Test)
{
const uint32_t CHANNEL_LENGTH = 256;
const uint32_t AUDIO_RATE = 44100;
MixerConsumer consumer;
AudioDataValue a[CHANNEL_LENGTH * 2];
AudioDataValue b[CHANNEL_LENGTH * 2];
FillBuffer(a, CHANNEL_LENGTH, GetLowValue<AudioDataValue>());
FillBuffer(a + CHANNEL_LENGTH, CHANNEL_LENGTH, GetHighValue<AudioDataValue>());
FillBuffer(b, CHANNEL_LENGTH, GetHighValue<AudioDataValue>());
FillBuffer(b + CHANNEL_LENGTH, CHANNEL_LENGTH, GetLowValue<AudioDataValue>());
{
int iterations = 2;
mozilla::AudioMixer mixer;
mixer.AddCallback(&consumer);
fprintf(stderr, "Test AudioMixer constant buffer length.\n");
while (iterations--) {
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.FinishMixing();
}
}
{
mozilla::AudioMixer mixer;
mixer.AddCallback(&consumer);
fprintf(stderr, "Test AudioMixer variable buffer length.\n");
FillBuffer(a, CHANNEL_LENGTH / 2, GetLowValue<AudioDataValue>());
FillBuffer(a + CHANNEL_LENGTH / 2, CHANNEL_LENGTH / 2, GetLowValue<AudioDataValue>());
FillBuffer(b, CHANNEL_LENGTH / 2, GetHighValue<AudioDataValue>());
FillBuffer(b + CHANNEL_LENGTH / 2, CHANNEL_LENGTH / 2, GetHighValue<AudioDataValue>());
mixer.Mix(a, 2, CHANNEL_LENGTH / 2, AUDIO_RATE);
mixer.Mix(b, 2, CHANNEL_LENGTH / 2, AUDIO_RATE);
mixer.FinishMixing();
FillBuffer(a, CHANNEL_LENGTH, GetLowValue<AudioDataValue>());
FillBuffer(a + CHANNEL_LENGTH, CHANNEL_LENGTH, GetHighValue<AudioDataValue>());
FillBuffer(b, CHANNEL_LENGTH, GetHighValue<AudioDataValue>());
FillBuffer(b + CHANNEL_LENGTH, CHANNEL_LENGTH, GetLowValue<AudioDataValue>());
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.FinishMixing();
FillBuffer(a, CHANNEL_LENGTH / 2, GetLowValue<AudioDataValue>());
FillBuffer(a + CHANNEL_LENGTH / 2, CHANNEL_LENGTH / 2, GetLowValue<AudioDataValue>());
FillBuffer(b, CHANNEL_LENGTH / 2, GetHighValue<AudioDataValue>());
FillBuffer(b + CHANNEL_LENGTH / 2, CHANNEL_LENGTH / 2, GetHighValue<AudioDataValue>());
mixer.Mix(a, 2, CHANNEL_LENGTH / 2, AUDIO_RATE);
mixer.Mix(b, 2, CHANNEL_LENGTH / 2, AUDIO_RATE);
mixer.FinishMixing();
}
FillBuffer(a, CHANNEL_LENGTH, GetLowValue<AudioDataValue>());
FillBuffer(b, CHANNEL_LENGTH, GetHighValue<AudioDataValue>());
{
mozilla::AudioMixer mixer;
mixer.AddCallback(&consumer);
fprintf(stderr, "Test AudioMixer variable channel count.\n");
mixer.Mix(a, 1, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(b, 1, CHANNEL_LENGTH, AUDIO_RATE);
mixer.FinishMixing();
mixer.Mix(a, 1, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(b, 1, CHANNEL_LENGTH, AUDIO_RATE);
mixer.FinishMixing();
mixer.Mix(a, 1, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(b, 1, CHANNEL_LENGTH, AUDIO_RATE);
mixer.FinishMixing();
}
{
mozilla::AudioMixer mixer;
mixer.AddCallback(&consumer);
fprintf(stderr, "Test AudioMixer variable stream count.\n");
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.FinishMixing();
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.FinishMixing();
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
mixer.FinishMixing();
}
}
} // namespace audio_mixer

View file

@ -0,0 +1,167 @@
/* -*- 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 <stdint.h>
#include <math.h>
#include "../AudioPacketizer.h"
#include "gtest/gtest.h"
using namespace mozilla;
template<typename T>
class AutoBuffer
{
public:
explicit AutoBuffer(size_t aLength)
{
mStorage = new T[aLength];
}
~AutoBuffer() {
delete [] mStorage;
}
T* Get() {
return mStorage;
}
private:
T* mStorage;
};
int16_t Sequence(int16_t* aBuffer, uint32_t aSize, uint32_t aStart = 0)
{
uint32_t i;
for (i = 0; i < aSize; i++) {
aBuffer[i] = aStart + i;
}
return aStart + i;
}
void IsSequence(int16_t* aBuffer, uint32_t aSize, uint32_t aStart = 0)
{
for (uint32_t i = 0; i < aSize; i++) {
ASSERT_TRUE(aBuffer[i] == static_cast<int64_t>(aStart + i)) <<
"Buffer is not a sequence at offset " << i << std::endl;
}
// Buffer is a sequence.
}
void Zero(int16_t* aBuffer, uint32_t aSize)
{
for (uint32_t i = 0; i < aSize; i++) {
ASSERT_TRUE(aBuffer[i] == 0) <<
"Buffer is not null at offset " << i << std::endl;
}
}
double sine(uint32_t aPhase) {
return sin(aPhase * 2 * M_PI * 440 / 44100);
}
TEST(AudioPacketizer, Test)
{
for (int16_t channels = 1; channels < 2; channels++) {
// Test that the packetizer returns zero on underrun
{
AudioPacketizer<int16_t, int16_t> ap(441, channels);
for (int16_t i = 0; i < 10; i++) {
int16_t* out = ap.Output();
Zero(out, 441);
delete[] out;
}
}
// Simple test, with input/output buffer size aligned on the packet size,
// alternating Input and Output calls.
{
AudioPacketizer<int16_t, int16_t> ap(441, channels);
int16_t seqEnd = 0;
for (int16_t i = 0; i < 10; i++) {
AutoBuffer<int16_t> b(441 * channels);
int16_t prevEnd = seqEnd;
seqEnd = Sequence(b.Get(), channels * 441, prevEnd);
ap.Input(b.Get(), 441);
int16_t* out = ap.Output();
IsSequence(out, 441 * channels, prevEnd);
delete[] out;
}
}
// Simple test, with input/output buffer size aligned on the packet size,
// alternating two Input and Output calls.
{
AudioPacketizer<int16_t, int16_t> ap(441, channels);
int16_t seqEnd = 0;
for (int16_t i = 0; i < 10; i++) {
AutoBuffer<int16_t> b(441 * channels);
AutoBuffer<int16_t> b1(441 * channels);
int16_t prevEnd0 = seqEnd;
seqEnd = Sequence(b.Get(), 441 * channels, prevEnd0);
int16_t prevEnd1 = seqEnd;
seqEnd = Sequence(b1.Get(), 441 * channels, seqEnd);
ap.Input(b.Get(), 441);
ap.Input(b1.Get(), 441);
int16_t* out = ap.Output();
int16_t* out2 = ap.Output();
IsSequence(out, 441 * channels, prevEnd0);
IsSequence(out2, 441 * channels, prevEnd1);
delete[] out;
delete[] out2;
}
}
// Input/output buffer size not aligned on the packet size,
// alternating two Input and Output calls.
{
AudioPacketizer<int16_t, int16_t> ap(441, channels);
int16_t prevEnd = 0;
int16_t prevSeq = 0;
for (int16_t i = 0; i < 10; i++) {
AutoBuffer<int16_t> b(480 * channels);
AutoBuffer<int16_t> b1(480 * channels);
prevSeq = Sequence(b.Get(), 480 * channels, prevSeq);
prevSeq = Sequence(b1.Get(), 480 * channels, prevSeq);
ap.Input(b.Get(), 480);
ap.Input(b1.Get(), 480);
int16_t* out = ap.Output();
int16_t* out2 = ap.Output();
IsSequence(out, 441 * channels, prevEnd);
prevEnd += 441 * channels;
IsSequence(out2, 441 * channels, prevEnd);
prevEnd += 441 * channels;
delete[] out;
delete[] out2;
}
printf("Available: %d\n", ap.PacketsAvailable());
}
// "Real-life" test case: streaming a sine wave through a packetizer, and
// checking that we have the right output.
// 128 is, for example, the size of a Web Audio API block, and 441 is the
// size of a webrtc.org packet when the sample rate is 44100 (10ms)
{
AudioPacketizer<int16_t, int16_t> ap(441, channels);
AutoBuffer<int16_t> b(128 * channels);
uint32_t phase = 0;
uint32_t outPhase = 0;
for (int16_t i = 0; i < 1000; i++) {
for (int32_t j = 0; j < 128; j++) {
for (int32_t c = 0; c < channels; c++) {
// int16_t sinewave at 440Hz/44100Hz sample rate
b.Get()[j * channels + c] = (2 << 14) * sine(phase);
}
phase++;
}
ap.Input(b.Get(), 128);
while (ap.PacketsAvailable()) {
int16_t* packet = ap.Output();
for (uint32_t k = 0; k < ap.PacketSize(); k++) {
for (int32_t c = 0; c < channels; c++) {
ASSERT_TRUE(packet[k * channels + c] ==
static_cast<int16_t>(((2 << 14) * sine(outPhase))));
}
outPhase++;
}
delete [] packet;
}
}
}
}
}

View file

@ -0,0 +1,257 @@
/* -*- 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 "AudioSegment.h"
#include <iostream>
#include "gtest/gtest.h"
using namespace mozilla;
namespace audio_segment {
/* Helper function to give us the maximum and minimum value that don't clip,
* for a given sample format (integer or floating-point). */
template<typename T>
T GetLowValue();
template<typename T>
T GetHighValue();
template<typename T>
T GetSilentValue();
template<>
float GetLowValue<float>() {
return -1.0;
}
template<>
int16_t GetLowValue<short>() {
return -INT16_MAX;
}
template<>
float GetHighValue<float>() {
return 1.0;
}
template<>
int16_t GetHighValue<short>() {
return INT16_MAX;
}
template<>
float GetSilentValue() {
return 0.0;
}
template<>
int16_t GetSilentValue() {
return 0;
}
// Get an array of planar audio buffers that has the inverse of the index of the
// channel (1-indexed) as samples.
template<typename T>
const T* const* GetPlanarChannelArray(size_t aChannels, size_t aSize)
{
T** channels = new T*[aChannels];
for (size_t c = 0; c < aChannels; c++) {
channels[c] = new T[aSize];
for (size_t i = 0; i < aSize; i++) {
channels[c][i] = FloatToAudioSample<T>(1. / (c + 1));
}
}
return channels;
}
template<typename T>
void DeletePlanarChannelsArray(const T* const* aArrays, size_t aChannels)
{
for (size_t channel = 0; channel < aChannels; channel++) {
delete [] aArrays[channel];
}
delete [] aArrays;
}
template<typename T>
T** GetPlanarArray(size_t aChannels, size_t aSize)
{
T** channels = new T*[aChannels];
for (size_t c = 0; c < aChannels; c++) {
channels[c] = new T[aSize];
for (size_t i = 0; i < aSize; i++) {
channels[c][i] = 0.0f;
}
}
return channels;
}
template<typename T>
void DeletePlanarArray(T** aArrays, size_t aChannels)
{
for (size_t channel = 0; channel < aChannels; channel++) {
delete [] aArrays[channel];
}
delete [] aArrays;
}
// Get an array of audio samples that have the inverse of the index of the
// channel (1-indexed) as samples.
template<typename T>
const T* GetInterleavedChannelArray(size_t aChannels, size_t aSize)
{
size_t sampleCount = aChannels * aSize;
T* samples = new T[sampleCount];
for (size_t i = 0; i < sampleCount; i++) {
uint32_t channel = (i % aChannels) + 1;
samples[i] = FloatToAudioSample<T>(1. / channel);
}
return samples;
}
template<typename T>
void DeleteInterleavedChannelArray(const T* aArray)
{
delete [] aArray;
}
bool FuzzyEqual(float aLhs, float aRhs) {
return std::abs(aLhs - aRhs) < 0.01;
}
template<typename SrcT, typename DstT>
void TestInterleaveAndConvert()
{
size_t arraySize = 1024;
size_t maxChannels = 8; // 7.1
for (uint32_t channels = 1; channels < maxChannels; channels++) {
const SrcT* const* src = GetPlanarChannelArray<SrcT>(channels, arraySize);
DstT* dst = new DstT[channels * arraySize];
InterleaveAndConvertBuffer(src, arraySize, 1.0, channels, dst);
uint32_t channelIndex = 0;
for (size_t i = 0; i < arraySize * channels; i++) {
ASSERT_TRUE(FuzzyEqual(dst[i],
FloatToAudioSample<DstT>(1. / (channelIndex + 1))));
channelIndex++;
channelIndex %= channels;
}
DeletePlanarChannelsArray(src, channels);
delete [] dst;
}
}
template<typename SrcT, typename DstT>
void TestDeinterleaveAndConvert()
{
size_t arraySize = 1024;
size_t maxChannels = 8; // 7.1
for (uint32_t channels = 1; channels < maxChannels; channels++) {
const SrcT* src = GetInterleavedChannelArray<SrcT>(channels, arraySize);
DstT** dst = GetPlanarArray<DstT>(channels, arraySize);
DeinterleaveAndConvertBuffer(src, arraySize, channels, dst);
for (size_t channel = 0; channel < channels; channel++) {
for (size_t i = 0; i < arraySize; i++) {
ASSERT_TRUE(FuzzyEqual(dst[channel][i],
FloatToAudioSample<DstT>(1. / (channel + 1))));
}
}
DeleteInterleavedChannelArray(src);
DeletePlanarArray(dst, channels);
}
}
uint8_t gSilence[4096] = {0};
template<typename T>
T* SilentChannel()
{
return reinterpret_cast<T*>(gSilence);
}
template<typename T>
void TestUpmixStereo()
{
size_t arraySize = 1024;
nsTArray<T*> channels;
nsTArray<const T*> channelsptr;
channels.SetLength(1);
channelsptr.SetLength(1);
channels[0] = new T[arraySize];
for (size_t i = 0; i < arraySize; i++) {
channels[0][i] = GetHighValue<T>();
}
channelsptr[0] = channels[0];
AudioChannelsUpMix(&channelsptr, 2, SilentChannel<T>());
for (size_t channel = 0; channel < 2; channel++) {
for (size_t i = 0; i < arraySize; i++) {
ASSERT_TRUE(channelsptr[channel][i] == GetHighValue<T>());
}
}
delete channels[0];
}
template<typename T>
void TestDownmixStereo()
{
const size_t arraySize = 1024;
nsTArray<const T*> inputptr;
nsTArray<T*> input;
T** output;
output = new T*[1];
output[0] = new T[arraySize];
input.SetLength(2);
inputptr.SetLength(2);
for (size_t channel = 0; channel < input.Length(); channel++) {
input[channel] = new T[arraySize];
for (size_t i = 0; i < arraySize; i++) {
input[channel][i] = channel == 0 ? GetLowValue<T>() : GetHighValue<T>();
}
inputptr[channel] = input[channel];
}
AudioChannelsDownMix(inputptr, output, 1, arraySize);
for (size_t i = 0; i < arraySize; i++) {
ASSERT_TRUE(output[0][i] == GetSilentValue<T>());
ASSERT_TRUE(output[0][i] == GetSilentValue<T>());
}
delete output[0];
delete output;
}
TEST(AudioSegment, Test)
{
TestInterleaveAndConvert<float, float>();
TestInterleaveAndConvert<float, int16_t>();
TestInterleaveAndConvert<int16_t, float>();
TestInterleaveAndConvert<int16_t, int16_t>();
TestDeinterleaveAndConvert<float, float>();
TestDeinterleaveAndConvert<float, int16_t>();
TestDeinterleaveAndConvert<int16_t, float>();
TestDeinterleaveAndConvert<int16_t, int16_t>();
TestUpmixStereo<float>();
TestUpmixStereo<int16_t>();
TestDownmixStereo<float>();
TestDownmixStereo<int16_t>();
}
} // namespace audio_segment

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,491 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "GMPService.h"
#include "GMPTestMonitor.h"
#include "gmp-api/gmp-video-host.h"
#include "gtest/gtest.h"
#include "mozilla/Services.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIObserverService.h"
#include "GMPVideoDecoderProxy.h"
#include "GMPServiceParent.h"
#include "GMPService.h"
#include "GMPUtils.h"
#include "mozilla/StaticPtr.h"
#include "MediaPrefs.h"
#define GMP_DIR_NAME NS_LITERAL_STRING("gmp-fakeopenh264")
#define GMP_OLD_VERSION NS_LITERAL_STRING("1.0")
#define GMP_NEW_VERSION NS_LITERAL_STRING("1.1")
#define GMP_DELETED_TOPIC "gmp-directory-deleted"
#define EXPECT_OK(X) EXPECT_TRUE(NS_SUCCEEDED(X))
using namespace mozilla;
using namespace mozilla::gmp;
class GMPRemoveTest : public nsIObserver
, public GMPVideoDecoderCallbackProxy
{
public:
GMPRemoveTest();
NS_DECL_THREADSAFE_ISUPPORTS
// Called when a GMP plugin directory has been successfully deleted.
// |aData| will contain the directory path.
NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) override;
// Create a new GMP plugin directory that we can trash and add it to the GMP
// service. Remove the original plugin directory. Original plugin directory
// gets re-added at destruction.
void Setup();
bool CreateVideoDecoder(nsCString aNodeId = EmptyCString());
void CloseVideoDecoder();
void DeletePluginDirectory(bool aCanDefer);
// Decode a dummy frame.
GMPErr Decode();
// Wait until TestMonitor has been signaled.
void Wait();
// Did we get a Terminated() callback from the plugin?
bool IsTerminated();
// From GMPVideoDecoderCallbackProxy
// Set mDecodeResult; unblock TestMonitor.
virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
virtual void Error(GMPErr aError) override;
// From GMPVideoDecoderCallbackProxy
// We expect this to be called when a plugin has been forcibly closed.
virtual void Terminated() override;
// Ignored GMPVideoDecoderCallbackProxy members
virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override {}
virtual void ReceivedDecodedFrame(const uint64_t aPictureId) override {}
virtual void InputDataExhausted() override {}
virtual void DrainComplete() override {}
virtual void ResetComplete() override {}
private:
virtual ~GMPRemoveTest();
void gmp_Decode();
void gmp_GetVideoDecoder(nsCString aNodeId,
GMPVideoDecoderProxy** aOutDecoder,
GMPVideoHost** aOutHost);
void GeneratePlugin();
GMPTestMonitor mTestMonitor;
nsCOMPtr<nsIThread> mGMPThread;
bool mIsTerminated;
// Path to the cloned GMP we have created.
nsString mTmpPath;
nsCOMPtr<nsIFile> mTmpDir;
// Path to the original GMP. Store so that we can re-add it after we're done
// testing.
nsString mOriginalPath;
GMPVideoDecoderProxy* mDecoder;
GMPVideoHost* mHost;
GMPErr mDecodeResult;
};
/*
* Simple test that the plugin is deleted when forcibly removed and deleted.
*/
TEST(GeckoMediaPlugins, RemoveAndDeleteForcedSimple)
{
RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
test->Setup();
test->DeletePluginDirectory(false /* force immediate */);
test->Wait();
}
/*
* Simple test that the plugin is deleted when deferred deletion is allowed.
*/
TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredSimple)
{
RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
test->Setup();
test->DeletePluginDirectory(true /* can defer */);
test->Wait();
}
/*
* Test that the plugin is unavailable immediately after a forced
* RemoveAndDelete, and that the plugin is deleted afterwards.
*/
TEST(GeckoMediaPlugins, RemoveAndDeleteForcedInUse)
{
RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
test->Setup();
EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
// Test that we can decode a frame.
GMPErr err = test->Decode();
EXPECT_EQ(err, GMPNoErr);
test->DeletePluginDirectory(false /* force immediate */);
test->Wait();
// Test that the VideoDecoder is no longer available.
EXPECT_FALSE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
// Test that we were notified of the plugin's destruction.
EXPECT_TRUE(test->IsTerminated());
}
/*
* Test that the plugin is still usable after a deferred RemoveAndDelete, and
* that the plugin is deleted afterwards.
*/
TEST(GeckoMediaPlugins, RemoveAndDeleteDeferredInUse)
{
RefPtr<GMPRemoveTest> test(new GMPRemoveTest());
test->Setup();
EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
// Make sure decoding works before we do anything.
GMPErr err = test->Decode();
EXPECT_EQ(err, GMPNoErr);
test->DeletePluginDirectory(true /* can defer */);
// Test that decoding still works.
err = test->Decode();
EXPECT_EQ(err, GMPNoErr);
// Test that this origin is still able to fetch the video decoder.
EXPECT_TRUE(test->CreateVideoDecoder(NS_LITERAL_CSTRING("thisOrigin")));
test->CloseVideoDecoder();
test->Wait();
}
static StaticRefPtr<GeckoMediaPluginService> gService;
static StaticRefPtr<GeckoMediaPluginServiceParent> gServiceParent;
static GeckoMediaPluginService*
GetService()
{
if (!gService) {
RefPtr<GeckoMediaPluginService> service =
GeckoMediaPluginService::GetGeckoMediaPluginService();
gService = service;
}
return gService.get();
}
static GeckoMediaPluginServiceParent*
GetServiceParent()
{
if (!gServiceParent) {
RefPtr<GeckoMediaPluginServiceParent> parent =
GeckoMediaPluginServiceParent::GetSingleton();
gServiceParent = parent;
}
return gServiceParent.get();
}
NS_IMPL_ISUPPORTS(GMPRemoveTest, nsIObserver)
GMPRemoveTest::GMPRemoveTest()
: mIsTerminated(false)
, mDecoder(nullptr)
, mHost(nullptr)
{
}
GMPRemoveTest::~GMPRemoveTest()
{
bool exists;
EXPECT_TRUE(NS_SUCCEEDED(mTmpDir->Exists(&exists)) && !exists);
EXPECT_OK(GetServiceParent()->AddPluginDirectory(mOriginalPath));
}
void
GMPRemoveTest::Setup()
{
// Initialize media preferences.
MediaPrefs::GetSingleton();
GeneratePlugin();
GetService()->GetThread(getter_AddRefs(mGMPThread));
// Spin the event loop until the GMP service has had a chance to complete
// adding GMPs from MOZ_GMP_PATH. Otherwise, the RemovePluginDirectory()
// below may complete before we're finished adding GMPs from MOZ_GMP_PATH,
// and we'll end up not removing the GMP, and the test will fail.
RefPtr<AbstractThread> thread(GetServiceParent()->GetAbstractGMPThread());
EXPECT_TRUE(thread);
GMPTestMonitor* mon = &mTestMonitor;
GetServiceParent()->EnsureInitialized()->Then(thread, __func__,
[mon]() { mon->SetFinished(); },
[mon]() { mon->SetFinished(); }
);
mTestMonitor.AwaitFinished();
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->AddObserver(this, GMP_DELETED_TOPIC, false /* strong ref */);
EXPECT_OK(GetServiceParent()->RemovePluginDirectory(mOriginalPath));
GetServiceParent()->AsyncAddPluginDirectory(mTmpPath)->Then(thread, __func__,
[mon]() { mon->SetFinished(); },
[mon]() { mon->SetFinished(); }
);
mTestMonitor.AwaitFinished();
}
bool
GMPRemoveTest::CreateVideoDecoder(nsCString aNodeId)
{
GMPVideoHost* host;
GMPVideoDecoderProxy* decoder = nullptr;
mGMPThread->Dispatch(
NewNonOwningRunnableMethod<nsCString, GMPVideoDecoderProxy**, GMPVideoHost**>(
this, &GMPRemoveTest::gmp_GetVideoDecoder, aNodeId, &decoder, &host),
NS_DISPATCH_NORMAL);
mTestMonitor.AwaitFinished();
if (!decoder) {
return false;
}
GMPVideoCodec codec;
memset(&codec, 0, sizeof(codec));
codec.mGMPApiVersion = 33;
nsTArray<uint8_t> empty;
mGMPThread->Dispatch(
NewNonOwningRunnableMethod<const GMPVideoCodec&, const nsTArray<uint8_t>&, GMPVideoDecoderCallbackProxy*, int32_t>(
decoder, &GMPVideoDecoderProxy::InitDecode,
codec, empty, this, 1 /* core count */),
NS_DISPATCH_SYNC);
if (mDecoder) {
CloseVideoDecoder();
}
mDecoder = decoder;
mHost = host;
return true;
}
void
GMPRemoveTest::gmp_GetVideoDecoder(nsCString aNodeId,
GMPVideoDecoderProxy** aOutDecoder,
GMPVideoHost** aOutHost)
{
nsTArray<nsCString> tags;
tags.AppendElement(NS_LITERAL_CSTRING("h264"));
tags.AppendElement(NS_LITERAL_CSTRING("fake"));
class Callback : public GetGMPVideoDecoderCallback
{
public:
Callback(GMPTestMonitor* aMonitor, GMPVideoDecoderProxy** aDecoder, GMPVideoHost** aHost)
: mMonitor(aMonitor), mDecoder(aDecoder), mHost(aHost) { }
virtual void Done(GMPVideoDecoderProxy* aDecoder, GMPVideoHost* aHost) override {
*mDecoder = aDecoder;
*mHost = aHost;
mMonitor->SetFinished();
}
private:
GMPTestMonitor* mMonitor;
GMPVideoDecoderProxy** mDecoder;
GMPVideoHost** mHost;
};
UniquePtr<GetGMPVideoDecoderCallback>
cb(new Callback(&mTestMonitor, aOutDecoder, aOutHost));
if (NS_FAILED(GetService()->GetGMPVideoDecoder(nullptr, &tags, aNodeId, Move(cb)))) {
mTestMonitor.SetFinished();
}
}
void
GMPRemoveTest::CloseVideoDecoder()
{
mGMPThread->Dispatch(
NewNonOwningRunnableMethod(mDecoder, &GMPVideoDecoderProxy::Close),
NS_DISPATCH_SYNC);
mDecoder = nullptr;
mHost = nullptr;
}
void
GMPRemoveTest::DeletePluginDirectory(bool aCanDefer)
{
GetServiceParent()->RemoveAndDeletePluginDirectory(mTmpPath, aCanDefer);
}
GMPErr
GMPRemoveTest::Decode()
{
mGMPThread->Dispatch(
NewNonOwningRunnableMethod(this, &GMPRemoveTest::gmp_Decode),
NS_DISPATCH_NORMAL);
mTestMonitor.AwaitFinished();
return mDecodeResult;
}
void
GMPRemoveTest::gmp_Decode()
{
// from gmp-fake.cpp
struct EncodedFrame {
uint32_t length_;
uint8_t h264_compat_;
uint32_t magic_;
uint32_t width_;
uint32_t height_;
uint8_t y_;
uint8_t u_;
uint8_t v_;
uint32_t timestamp_;
};
GMPVideoFrame* absFrame;
GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &absFrame);
EXPECT_EQ(err, GMPNoErr);
GMPUniquePtr<GMPVideoEncodedFrame>
frame(static_cast<GMPVideoEncodedFrame*>(absFrame));
err = frame->CreateEmptyFrame(sizeof(EncodedFrame) /* size */);
EXPECT_EQ(err, GMPNoErr);
EncodedFrame* frameData = reinterpret_cast<EncodedFrame*>(frame->Buffer());
frameData->magic_ = 0x4652414d;
frameData->width_ = frameData->height_ = 16;
nsTArray<uint8_t> empty;
nsresult rv = mDecoder->Decode(Move(frame), false /* aMissingFrames */, empty);
EXPECT_OK(rv);
}
void
GMPRemoveTest::Wait()
{
mTestMonitor.AwaitFinished();
}
bool
GMPRemoveTest::IsTerminated()
{
return mIsTerminated;
}
// nsIObserver
NS_IMETHODIMP
GMPRemoveTest::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
EXPECT_TRUE(!strcmp(GMP_DELETED_TOPIC, aTopic));
nsString data(aData);
if (mTmpPath.Equals(data)) {
mTestMonitor.SetFinished();
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->RemoveObserver(this, GMP_DELETED_TOPIC);
}
return NS_OK;
}
// GMPVideoDecoderCallbackProxy
void
GMPRemoveTest::Decoded(GMPVideoi420Frame* aDecodedFrame)
{
aDecodedFrame->Destroy();
mDecodeResult = GMPNoErr;
mTestMonitor.SetFinished();
}
// GMPVideoDecoderCallbackProxy
void
GMPRemoveTest::Error(GMPErr aError)
{
mDecodeResult = aError;
mTestMonitor.SetFinished();
}
// GMPVideoDecoderCallbackProxy
void
GMPRemoveTest::Terminated()
{
mIsTerminated = true;
if (mDecoder) {
mDecoder->Close();
mDecoder = nullptr;
}
}
void
GMPRemoveTest::GeneratePlugin()
{
nsresult rv;
nsCOMPtr<nsIFile> gmpDir;
nsCOMPtr<nsIFile> origDir;
nsCOMPtr<nsIFile> tmpDir;
rv = NS_GetSpecialDirectory(NS_GRE_DIR,
getter_AddRefs(gmpDir));
EXPECT_OK(rv);
rv = gmpDir->Append(GMP_DIR_NAME);
EXPECT_OK(rv);
rv = gmpDir->Clone(getter_AddRefs(origDir));
EXPECT_OK(rv);
rv = origDir->Append(GMP_OLD_VERSION);
EXPECT_OK(rv);
rv = gmpDir->Clone(getter_AddRefs(tmpDir));
EXPECT_OK(rv);
rv = tmpDir->Append(GMP_NEW_VERSION);
EXPECT_OK(rv);
bool exists = false;
rv = tmpDir->Exists(&exists);
EXPECT_OK(rv);
if (exists) {
rv = tmpDir->Remove(true);
EXPECT_OK(rv);
}
rv = origDir->CopyTo(gmpDir, GMP_NEW_VERSION);
EXPECT_OK(rv);
rv = gmpDir->Clone(getter_AddRefs(tmpDir));
EXPECT_OK(rv);
rv = tmpDir->Append(GMP_NEW_VERSION);
EXPECT_OK(rv);
EXPECT_OK(origDir->GetPath(mOriginalPath));
EXPECT_OK(tmpDir->GetPath(mTmpPath));
mTmpDir = tmpDir;
}

View file

@ -0,0 +1,61 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "GMPUtils.h"
#include "nsString.h"
#include "MediaPrefs.h"
#include <string>
#include <vector>
using namespace std;
using namespace mozilla;
void TestSplitAt(const char* aInput,
const char* aDelims,
size_t aNumExpectedTokens,
const char* aExpectedTokens[])
{
// Initialize media preferences.
MediaPrefs::GetSingleton();
nsCString input(aInput);
nsTArray<nsCString> tokens;
SplitAt(aDelims, input, tokens);
EXPECT_EQ(tokens.Length(), aNumExpectedTokens) << "Should get expected number of tokens";
for (size_t i = 0; i < tokens.Length(); i++) {
EXPECT_TRUE(tokens[i].EqualsASCII(aExpectedTokens[i]))
<< "Tokenize fail; expected=" << aExpectedTokens[i] << " got=" <<
tokens[i].BeginReading();
}
}
TEST(GeckoMediaPlugins, GMPUtils) {
{
const char* input = "1,2,3,4";
const char* delims = ",";
const char* tokens[] = { "1", "2", "3", "4" };
TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
}
{
const char* input = "a simple, comma, seperated, list";
const char* delims = ",";
const char* tokens[] = { "a simple", " comma", " seperated", " list" };
TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
}
{
const char* input = // Various platform line endings...
"line1\r\n" // Windows
"line2\r" // Old MacOSX
"line3\n" // Unix
"line4";
const char* delims = "\r\n";
const char* tokens[] = { "line1", "line2", "line3", "line4" };
TestSplitAt(input, delims, MOZ_ARRAY_LENGTH(tokens), tokens);
}
}

View file

@ -0,0 +1,821 @@
/* -*- 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/dom/TimeRanges.h"
#include "TimeUnits.h"
#include "Intervals.h"
#include <algorithm>
#include <vector>
using namespace mozilla;
typedef media::Interval<uint8_t> ByteInterval;
typedef media::Interval<int> IntInterval;
typedef media::IntervalSet<int> IntIntervals;
ByteInterval CreateByteInterval(int32_t aStart, int32_t aEnd)
{
ByteInterval test(aStart, aEnd);
return test;
}
media::IntervalSet<uint8_t> CreateByteIntervalSet(int32_t aStart, int32_t aEnd)
{
media::IntervalSet<uint8_t> test;
test += ByteInterval(aStart, aEnd);
return test;
}
TEST(IntervalSet, Constructors)
{
const int32_t start = 1;
const int32_t end = 2;
const int32_t fuzz = 0;
// Compiler exercise.
ByteInterval test1(start, end);
ByteInterval test2(test1);
ByteInterval test3(start, end, fuzz);
ByteInterval test4(test3);
ByteInterval test5 = CreateByteInterval(start, end);
media::IntervalSet<uint8_t> blah1(test1);
media::IntervalSet<uint8_t> blah2 = blah1;
media::IntervalSet<uint8_t> blah3 = blah1 + test1;
media::IntervalSet<uint8_t> blah4 = test1 + blah1;
media::IntervalSet<uint8_t> blah5 = CreateByteIntervalSet(start, end);
(void)test1; (void)test2; (void)test3; (void)test4; (void)test5;
(void)blah1; (void)blah2; (void)blah3; (void)blah4; (void)blah5;
}
media::TimeInterval CreateTimeInterval(int32_t aStart, int32_t aEnd)
{
// Copy constructor test
media::Microseconds startus(aStart);
media::TimeUnit start(startus);
media::TimeUnit end;
// operator= test
end = media::Microseconds(aEnd);
media::TimeInterval ti(start, end);
return ti;
}
media::TimeIntervals CreateTimeIntervals(int32_t aStart, int32_t aEnd)
{
media::TimeIntervals test;
test += CreateTimeInterval(aStart, aEnd);
return test;
}
TEST(IntervalSet, TimeIntervalsConstructors)
{
const media::Microseconds start(1);
const media::Microseconds end(2);
const media::Microseconds fuzz;
// Compiler exercise.
media::TimeInterval test1(start, end);
media::TimeInterval test2(test1);
media::TimeInterval test3(start, end, fuzz);
media::TimeInterval test4(test3);
media::TimeInterval test5 = CreateTimeInterval(start.mValue, end.mValue);
media::TimeIntervals blah1(test1);
media::TimeIntervals blah2(blah1);
media::TimeIntervals blah3 = blah1 + test1;
media::TimeIntervals blah4 = test1 + blah1;
media::TimeIntervals blah5 = CreateTimeIntervals(start.mValue, end.mValue);
(void)test1; (void)test2; (void)test3; (void)test4; (void)test5;
(void)blah1; (void)blah2; (void)blah3; (void)blah4; (void)blah5;
media::TimeIntervals i0{media::TimeInterval(media::TimeUnit::FromSeconds(0),
media::TimeUnit::FromSeconds(0))};
EXPECT_EQ(0u, i0.Length()); // Constructing with an empty time interval.
}
TEST(IntervalSet, Length)
{
IntInterval i(15, 25);
EXPECT_EQ(10, i.Length());
}
TEST(IntervalSet, Intersects)
{
EXPECT_TRUE(IntInterval(1,5).Intersects(IntInterval(3,4)));
EXPECT_TRUE(IntInterval(1,5).Intersects(IntInterval(3,7)));
EXPECT_TRUE(IntInterval(1,5).Intersects(IntInterval(-1,3)));
EXPECT_TRUE(IntInterval(1,5).Intersects(IntInterval(-1,7)));
EXPECT_FALSE(IntInterval(1,5).Intersects(IntInterval(6,7)));
EXPECT_FALSE(IntInterval(1,5).Intersects(IntInterval(-1,0)));
// End boundary is exclusive of the interval.
EXPECT_FALSE(IntInterval(1,5).Intersects(IntInterval(5,7)));
EXPECT_FALSE(IntInterval(1,5).Intersects(IntInterval(0,1)));
// Empty identical interval do not intersect.
EXPECT_FALSE(IntInterval(1,1).Intersects(IntInterval(1,1)));
// Empty interval do not intersect.
EXPECT_FALSE(IntInterval(1,1).Intersects(IntInterval(2,2)));
}
TEST(IntervalSet, Intersection)
{
IntInterval i0(10, 20);
IntInterval i1(15, 25);
IntInterval i = i0.Intersection(i1);
EXPECT_EQ(15, i.mStart);
EXPECT_EQ(20, i.mEnd);
IntInterval j0(10, 20);
IntInterval j1(20, 25);
IntInterval j = j0.Intersection(j1);
EXPECT_TRUE(j.IsEmpty());
IntInterval k0(2, 2);
IntInterval k1(2, 2);
IntInterval k = k0.Intersection(k1);
EXPECT_TRUE(k.IsEmpty());
}
TEST(IntervalSet, Equals)
{
IntInterval i0(10, 20);
IntInterval i1(10, 20);
EXPECT_EQ(i0, i1);
IntInterval i2(5, 20);
EXPECT_NE(i0, i2);
IntInterval i3(10, 15);
EXPECT_NE(i0, i2);
}
TEST(IntervalSet, IntersectionIntervalSet)
{
IntIntervals i0;
i0 += IntInterval(5, 10);
i0 += IntInterval(20, 25);
i0 += IntInterval(40, 60);
IntIntervals i1;
i1.Add(IntInterval(7, 15));
i1.Add(IntInterval(16, 27));
i1.Add(IntInterval(45, 50));
i1.Add(IntInterval(53, 57));
IntIntervals i = media::Intersection(i0, i1);
EXPECT_EQ(4u, i.Length());
EXPECT_EQ(7, i[0].mStart);
EXPECT_EQ(10, i[0].mEnd);
EXPECT_EQ(20, i[1].mStart);
EXPECT_EQ(25, i[1].mEnd);
EXPECT_EQ(45, i[2].mStart);
EXPECT_EQ(50, i[2].mEnd);
EXPECT_EQ(53, i[3].mStart);
EXPECT_EQ(57, i[3].mEnd);
}
template<typename T>
static void Compare(const media::IntervalSet<T>& aI1,
const media::IntervalSet<T>& aI2)
{
EXPECT_EQ(aI1.Length(), aI2.Length());
if (aI1.Length() != aI2.Length()) {
return;
}
for (uint32_t i = 0; i < aI1.Length(); i++) {
EXPECT_EQ(aI1[i].mStart, aI2[i].mStart);
EXPECT_EQ(aI1[i].mEnd, aI2[i].mEnd);
}
}
static void GeneratePermutations(IntIntervals aI1,
IntIntervals aI2)
{
IntIntervals i_ref = media::Intersection(aI1, aI2);
// Test all permutations possible
std::vector<uint32_t> comb1;
for (uint32_t i = 0; i < aI1.Length(); i++) {
comb1.push_back(i);
}
std::vector<uint32_t> comb2;
for (uint32_t i = 0; i < aI2.Length(); i++) {
comb2.push_back(i);
}
do {
do {
// Create intervals according to new indexes.
IntIntervals i_0;
for (uint32_t i = 0; i < comb1.size(); i++) {
i_0 += aI1[comb1[i]];
}
// Test that intervals are always normalized.
Compare(aI1, i_0);
IntIntervals i_1;
for (uint32_t i = 0; i < comb2.size(); i++) {
i_1 += aI2[comb2[i]];
}
Compare(aI2, i_1);
// Check intersections yield the same result.
Compare(i_0.Intersection(i_1), i_ref);
} while (std::next_permutation(comb2.begin(), comb2.end()));
} while (std::next_permutation(comb1.begin(), comb1.end()));
}
TEST(IntervalSet, IntersectionNormalizedIntervalSet)
{
IntIntervals i0;
i0 += IntInterval(5, 10);
i0 += IntInterval(20, 25);
i0 += IntInterval(40, 60);
IntIntervals i1;
i1.Add(IntInterval(7, 15));
i1.Add(IntInterval(16, 27));
i1.Add(IntInterval(45, 50));
i1.Add(IntInterval(53, 57));
GeneratePermutations(i0, i1);
}
TEST(IntervalSet, IntersectionUnorderedNonNormalizedIntervalSet)
{
IntIntervals i0;
i0 += IntInterval(5, 10);
i0 += IntInterval(8, 25);
i0 += IntInterval(24, 60);
IntIntervals i1;
i1.Add(IntInterval(7, 15));
i1.Add(IntInterval(10, 27));
i1.Add(IntInterval(45, 50));
i1.Add(IntInterval(53, 57));
GeneratePermutations(i0, i1);
}
TEST(IntervalSet, IntersectionNonNormalizedInterval)
{
IntIntervals i0;
i0 += IntInterval(5, 10);
i0 += IntInterval(8, 25);
i0 += IntInterval(30, 60);
media::Interval<int> i1(9, 15);
i0.Intersection(i1);
EXPECT_EQ(1u, i0.Length());
EXPECT_EQ(i0[0].mStart, i1.mStart);
EXPECT_EQ(i0[0].mEnd, i1.mEnd);
}
TEST(IntervalSet, IntersectionUnorderedNonNormalizedInterval)
{
IntIntervals i0;
i0 += IntInterval(1, 3);
i0 += IntInterval(1, 10);
i0 += IntInterval(9, 12);
i0 += IntInterval(12, 15);
i0 += IntInterval(8, 25);
i0 += IntInterval(30, 60);
i0 += IntInterval(5, 10);
i0 += IntInterval(30, 60);
media::Interval<int> i1(9, 15);
i0.Intersection(i1);
EXPECT_EQ(1u, i0.Length());
EXPECT_EQ(i0[0].mStart, i1.mStart);
EXPECT_EQ(i0[0].mEnd, i1.mEnd);
}
static IntIntervals Duplicate(const IntIntervals& aValue)
{
IntIntervals value(aValue);
return value;
}
TEST(IntervalSet, Normalize)
{
IntIntervals i;
// Test IntervalSet<T> + Interval<T> operator.
i = i + IntInterval(20, 30);
// Test Internal<T> + IntervalSet<T> operator.
i = IntInterval(2, 7) + i;
// Test Interval<T> + IntervalSet<T> operator
i = IntInterval(1, 8) + i;
IntIntervals interval;
interval += IntInterval(5, 10);
// Test += with rval move.
i += Duplicate(interval);
// Test = with move and add with move.
i = Duplicate(interval) + i;
EXPECT_EQ(2u, i.Length());
EXPECT_EQ(1, i[0].mStart);
EXPECT_EQ(10, i[0].mEnd);
EXPECT_EQ(20, i[1].mStart);
EXPECT_EQ(30, i[1].mEnd);
media::TimeIntervals ti;
ti += media::TimeInterval(media::TimeUnit::FromSeconds(0.0),
media::TimeUnit::FromSeconds(3.203333));
ti += media::TimeInterval(media::TimeUnit::FromSeconds(3.203366),
media::TimeUnit::FromSeconds(10.010065));
EXPECT_EQ(2u, ti.Length());
ti += media::TimeInterval(ti.Start(0), ti.End(0), media::TimeUnit::FromMicroseconds(35000));
EXPECT_EQ(1u, ti.Length());
}
TEST(IntervalSet, ContainValue)
{
IntIntervals i0;
i0 += IntInterval(0, 10);
i0 += IntInterval(15, 20);
i0 += IntInterval(30, 50);
EXPECT_TRUE(i0.Contains(0)); // start is inclusive.
EXPECT_TRUE(i0.Contains(17));
EXPECT_FALSE(i0.Contains(20)); // end boundary is exclusive.
EXPECT_FALSE(i0.Contains(25));
}
TEST(IntervalSet, ContainValueWithFuzz)
{
IntIntervals i0;
i0 += IntInterval(0, 10);
i0 += IntInterval(15, 20, 1);
i0 += IntInterval(30, 50);
EXPECT_TRUE(i0.Contains(0)); // start is inclusive.
EXPECT_TRUE(i0.Contains(17));
EXPECT_TRUE(i0.Contains(20)); // end boundary is exclusive but we have a fuzz of 1.
EXPECT_FALSE(i0.Contains(25));
}
TEST(IntervalSet, ContainInterval)
{
IntIntervals i0;
i0 += IntInterval(0, 10);
i0 += IntInterval(15, 20);
i0 += IntInterval(30, 50);
EXPECT_TRUE(i0.Contains(IntInterval(2, 8)));
EXPECT_TRUE(i0.Contains(IntInterval(31, 50)));
EXPECT_TRUE(i0.Contains(IntInterval(0, 10)));
EXPECT_FALSE(i0.Contains(IntInterval(0, 11)));
EXPECT_TRUE(i0.Contains(IntInterval(0, 5)));
EXPECT_FALSE(i0.Contains(IntInterval(8, 15)));
EXPECT_FALSE(i0.Contains(IntInterval(15, 30)));
EXPECT_FALSE(i0.Contains(IntInterval(30, 55)));
}
TEST(IntervalSet, ContainIntervalWithFuzz)
{
IntIntervals i0;
i0 += IntInterval(0, 10);
i0 += IntInterval(15, 20);
i0 += IntInterval(30, 50);
EXPECT_TRUE(i0.Contains(IntInterval(2, 8)));
EXPECT_TRUE(i0.Contains(IntInterval(31, 50)));
EXPECT_TRUE(i0.Contains(IntInterval(0, 11, 1)));
EXPECT_TRUE(i0.Contains(IntInterval(0, 5)));
EXPECT_FALSE(i0.Contains(IntInterval(8, 15)));
EXPECT_FALSE(i0.Contains(IntInterval(15, 21)));
EXPECT_FALSE(i0.Contains(IntInterval(15, 30)));
EXPECT_FALSE(i0.Contains(IntInterval(30, 55)));
IntIntervals i1;
i1 += IntInterval(0, 10, 1);
i1 += IntInterval(15, 20, 1);
i1 += IntInterval(30, 50, 1);
EXPECT_TRUE(i1.Contains(IntInterval(2, 8)));
EXPECT_TRUE(i1.Contains(IntInterval(29, 51)));
EXPECT_TRUE(i1.Contains(IntInterval(0, 11, 1)));
EXPECT_TRUE(i1.Contains(IntInterval(15, 21)));
}
TEST(IntervalSet, Span)
{
IntInterval i0(0,10);
IntInterval i1(20,30);
IntInterval i{i0.Span(i1)};
EXPECT_EQ(i.mStart, 0);
EXPECT_EQ(i.mEnd, 30);
}
TEST(IntervalSet, Union)
{
IntIntervals i0;
i0 += IntInterval(5, 10);
i0 += IntInterval(20, 25);
i0 += IntInterval(40, 60);
IntIntervals i1;
i1.Add(IntInterval(7, 15));
i1.Add(IntInterval(16, 27));
i1.Add(IntInterval(45, 50));
i1.Add(IntInterval(53, 57));
IntIntervals i = media::Union(i0, i1);
EXPECT_EQ(3u, i.Length());
EXPECT_EQ(5, i[0].mStart);
EXPECT_EQ(15, i[0].mEnd);
EXPECT_EQ(16, i[1].mStart);
EXPECT_EQ(27, i[1].mEnd);
EXPECT_EQ(40, i[2].mStart);
EXPECT_EQ(60, i[2].mEnd);
}
TEST(IntervalSet, UnionNotOrdered)
{
IntIntervals i0;
i0 += IntInterval(20, 25);
i0 += IntInterval(40, 60);
i0 += IntInterval(5, 10);
IntIntervals i1;
i1.Add(IntInterval(16, 27));
i1.Add(IntInterval(7, 15));
i1.Add(IntInterval(53, 57));
i1.Add(IntInterval(45, 50));
IntIntervals i = media::Union(i0, i1);
EXPECT_EQ(3u, i.Length());
EXPECT_EQ(5, i[0].mStart);
EXPECT_EQ(15, i[0].mEnd);
EXPECT_EQ(16, i[1].mStart);
EXPECT_EQ(27, i[1].mEnd);
EXPECT_EQ(40, i[2].mStart);
EXPECT_EQ(60, i[2].mEnd);
}
TEST(IntervalSet, NormalizeFuzz)
{
IntIntervals i0;
i0 += IntInterval(11, 25, 0);
i0 += IntInterval(5, 10, 1);
i0 += IntInterval(40, 60, 1);
EXPECT_EQ(2u, i0.Length());
EXPECT_EQ(5, i0[0].mStart);
EXPECT_EQ(25, i0[0].mEnd);
EXPECT_EQ(40, i0[1].mStart);
EXPECT_EQ(60, i0[1].mEnd);
}
TEST(IntervalSet, UnionFuzz)
{
IntIntervals i0;
i0 += IntInterval(5, 10, 1);
i0 += IntInterval(11, 25, 0);
i0 += IntInterval(40, 60, 1);
EXPECT_EQ(2u, i0.Length());
EXPECT_EQ(5, i0[0].mStart);
EXPECT_EQ(25, i0[0].mEnd);
EXPECT_EQ(40, i0[1].mStart);
EXPECT_EQ(60, i0[1].mEnd);
IntIntervals i1;
i1.Add(IntInterval(7, 15, 1));
i1.Add(IntInterval(16, 27, 1));
i1.Add(IntInterval(45, 50, 1));
i1.Add(IntInterval(53, 57, 1));
EXPECT_EQ(3u, i1.Length());
EXPECT_EQ(7, i1[0].mStart);
EXPECT_EQ(27, i1[0].mEnd);
EXPECT_EQ(45, i1[1].mStart);
EXPECT_EQ(50, i1[1].mEnd);
EXPECT_EQ(53, i1[2].mStart);
EXPECT_EQ(57, i1[2].mEnd);
IntIntervals i = media::Union(i0, i1);
EXPECT_EQ(2u, i.Length());
EXPECT_EQ(5, i[0].mStart);
EXPECT_EQ(27, i[0].mEnd);
EXPECT_EQ(40, i[1].mStart);
EXPECT_EQ(60, i[1].mEnd);
}
TEST(IntervalSet, Contiguous)
{
EXPECT_FALSE(IntInterval(5, 10).Contiguous(IntInterval(11, 25)));
EXPECT_TRUE(IntInterval(5, 10).Contiguous(IntInterval(10, 25)));
EXPECT_TRUE(IntInterval(5, 10, 1).Contiguous(IntInterval(11, 25)));
EXPECT_TRUE(IntInterval(5, 10).Contiguous(IntInterval(11, 25, 1)));
}
TEST(IntervalSet, TimeRangesSeconds)
{
media::TimeIntervals i0;
i0 += media::TimeInterval(media::TimeUnit::FromSeconds(20), media::TimeUnit::FromSeconds(25));
i0 += media::TimeInterval(media::TimeUnit::FromSeconds(40), media::TimeUnit::FromSeconds(60));
i0 += media::TimeInterval(media::TimeUnit::FromSeconds(5), media::TimeUnit::FromSeconds(10));
media::TimeIntervals i1;
i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(16), media::TimeUnit::FromSeconds(27)));
i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(7), media::TimeUnit::FromSeconds(15)));
i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(53), media::TimeUnit::FromSeconds(57)));
i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(45), media::TimeUnit::FromSeconds(50)));
media::TimeIntervals i(i0 + i1);
RefPtr<dom::TimeRanges> tr = new dom::TimeRanges();
i.ToTimeRanges(tr);
EXPECT_EQ(tr->Length(), i.Length());
for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
ErrorResult rv;
EXPECT_EQ(tr->Start(index, rv), i[index].mStart.ToSeconds());
EXPECT_EQ(tr->Start(index, rv), i.Start(index).ToSeconds());
EXPECT_EQ(tr->End(index, rv), i[index].mEnd.ToSeconds());
EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds());
}
}
static void CheckTimeRanges(dom::TimeRanges* aTr, const media::TimeIntervals& aTi)
{
RefPtr<dom::TimeRanges> tr = new dom::TimeRanges;
tr->Union(aTr, 0); // This will normalize the time range.
EXPECT_EQ(tr->Length(), aTi.Length());
for (dom::TimeRanges::index_type i = 0; i < tr->Length(); i++) {
ErrorResult rv;
EXPECT_EQ(tr->Start(i, rv), aTi[i].mStart.ToSeconds());
EXPECT_EQ(tr->Start(i, rv), aTi.Start(i).ToSeconds());
EXPECT_EQ(tr->End(i, rv), aTi[i].mEnd.ToSeconds());
EXPECT_EQ(tr->End(i, rv), aTi.End(i).ToSeconds());
}
}
TEST(IntervalSet, TimeRangesConversion)
{
RefPtr<dom::TimeRanges> tr = new dom::TimeRanges();
tr->Add(20, 25);
tr->Add(40, 60);
tr->Add(5, 10);
tr->Add(16, 27);
tr->Add(53, 57);
tr->Add(45, 50);
// explicit copy constructor
media::TimeIntervals i1(tr);
CheckTimeRanges(tr, i1);
// static FromTimeRanges
media::TimeIntervals i2 = media::TimeIntervals::FromTimeRanges(tr);
CheckTimeRanges(tr, i2);
media::TimeIntervals i3;
// operator=(TimeRanges*)
i3 = tr;
CheckTimeRanges(tr, i3);
// operator= test
i1 = tr.get();
CheckTimeRanges(tr, i1);
}
TEST(IntervalSet, TimeRangesMicroseconds)
{
media::TimeIntervals i0;
// Test media::Microseconds and TimeUnit interchangeability (compilation only)
media::TimeUnit time1{media::Microseconds(5)};
media::Microseconds microseconds(5);
media::TimeUnit time2 = media::TimeUnit(microseconds);
EXPECT_EQ(time1, time2);
i0 += media::TimeInterval(media::Microseconds(20), media::Microseconds(25));
i0 += media::TimeInterval(media::Microseconds(40), media::Microseconds(60));
i0 += media::TimeInterval(media::Microseconds(5), media::Microseconds(10));
media::TimeIntervals i1;
i1.Add(media::TimeInterval(media::Microseconds(16), media::Microseconds(27)));
i1.Add(media::TimeInterval(media::Microseconds(7), media::Microseconds(15)));
i1.Add(media::TimeInterval(media::Microseconds(53), media::Microseconds(57)));
i1.Add(media::TimeInterval(media::Microseconds(45), media::Microseconds(50)));
media::TimeIntervals i(i0 + i1);
RefPtr<dom::TimeRanges> tr = new dom::TimeRanges();
i.ToTimeRanges(tr);
EXPECT_EQ(tr->Length(), i.Length());
for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
ErrorResult rv;
EXPECT_EQ(tr->Start(index, rv), i[index].mStart.ToSeconds());
EXPECT_EQ(tr->Start(index, rv), i.Start(index).ToSeconds());
EXPECT_EQ(tr->End(index, rv), i[index].mEnd.ToSeconds());
EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds());
}
tr->Normalize();
EXPECT_EQ(tr->Length(), i.Length());
for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
ErrorResult rv;
EXPECT_EQ(tr->Start(index, rv), i[index].mStart.ToSeconds());
EXPECT_EQ(tr->Start(index, rv), i.Start(index).ToSeconds());
EXPECT_EQ(tr->End(index, rv), i[index].mEnd.ToSeconds());
EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds());
}
// Check infinity values aren't lost in the conversion.
tr = new dom::TimeRanges();
tr->Add(0, 30);
tr->Add(50, std::numeric_limits<double>::infinity());
media::TimeIntervals i_oo{media::TimeIntervals::FromTimeRanges(tr)};
RefPtr<dom::TimeRanges> tr2 = new dom::TimeRanges();
i_oo.ToTimeRanges(tr2);
EXPECT_EQ(tr->Length(), tr2->Length());
for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
ErrorResult rv;
EXPECT_EQ(tr->Start(index, rv), tr2->Start(index, rv));
EXPECT_EQ(tr->End(index, rv), tr2->End(index, rv));
}
}
template<typename T>
class Foo
{
public:
Foo()
: mArg1(1)
, mArg2(2)
, mArg3(3)
{}
Foo(T a1, T a2, T a3)
: mArg1(a1)
, mArg2(a2)
, mArg3(a3)
{}
Foo<T> operator+ (const Foo<T>& aOther) const
{
Foo<T> blah;
blah.mArg1 += aOther.mArg1;
blah.mArg2 += aOther.mArg2;
blah.mArg3 += aOther.mArg3;
return blah;
}
Foo<T> operator- (const Foo<T>& aOther) const
{
Foo<T> blah;
blah.mArg1 -= aOther.mArg1;
blah.mArg2 -= aOther.mArg2;
blah.mArg3 -= aOther.mArg3;
return blah;
}
bool operator< (const Foo<T>& aOther) const
{
return mArg1 < aOther.mArg1;
}
bool operator== (const Foo<T>& aOther) const
{
return mArg1 == aOther.mArg1;
}
bool operator<= (const Foo<T>& aOther) const
{
return mArg1 <= aOther.mArg1;
}
private:
int32_t mArg1;
int32_t mArg2;
int32_t mArg3;
};
TEST(IntervalSet, FooIntervalSet)
{
media::Interval<Foo<int>> i(Foo<int>(), Foo<int>(4,5,6));
media::IntervalSet<Foo<int>> is;
is += i;
is += i;
is.Add(i);
is = is + i;
is = i + is;
EXPECT_EQ(1u, is.Length());
EXPECT_EQ(Foo<int>(), is[0].mStart);
EXPECT_EQ(Foo<int>(4,5,6), is[0].mEnd);
}
TEST(IntervalSet, StaticAssert)
{
media::Interval<int> i;
static_assert(mozilla::IsSame<nsTArray_CopyChooser<IntIntervals>::Type, nsTArray_CopyWithConstructors<IntIntervals>>::value, "Must use copy constructor");
static_assert(mozilla::IsSame<nsTArray_CopyChooser<media::TimeIntervals>::Type, nsTArray_CopyWithConstructors<media::TimeIntervals>>::value, "Must use copy constructor");
}
TEST(IntervalSet, Substraction)
{
IntIntervals i0;
i0 += IntInterval(5, 10);
i0 += IntInterval(20, 25);
i0 += IntInterval(40, 60);
IntInterval i1(8, 15);
i0 -= i1;
EXPECT_EQ(3u, i0.Length());
EXPECT_EQ(5, i0[0].mStart);
EXPECT_EQ(8, i0[0].mEnd);
EXPECT_EQ(20, i0[1].mStart);
EXPECT_EQ(25, i0[1].mEnd);
EXPECT_EQ(40, i0[2].mStart);
EXPECT_EQ(60, i0[2].mEnd);
i0 = IntIntervals();
i0 += IntInterval(5, 10);
i0 += IntInterval(20, 25);
i0 += IntInterval(40, 60);
i1 = IntInterval(0, 60);
i0 -= i1;
EXPECT_EQ(0u, i0.Length());
i0 = IntIntervals();
i0 += IntInterval(5, 10);
i0 += IntInterval(20, 25);
i0 += IntInterval(40, 60);
i1 = IntInterval(0, 45);
i0 -= i1;
EXPECT_EQ(1u, i0.Length());
EXPECT_EQ(45, i0[0].mStart);
EXPECT_EQ(60, i0[0].mEnd);
i0 = IntIntervals();
i0 += IntInterval(5, 10);
i0 += IntInterval(20, 25);
i0 += IntInterval(40, 60);
i1 = IntInterval(8, 45);
i0 -= i1;
EXPECT_EQ(2u, i0.Length());
EXPECT_EQ(5, i0[0].mStart);
EXPECT_EQ(8, i0[0].mEnd);
EXPECT_EQ(45, i0[1].mStart);
EXPECT_EQ(60, i0[1].mEnd);
i0 = IntIntervals();
i0 += IntInterval(5, 10);
i0 += IntInterval(20, 25);
i0 += IntInterval(40, 60);
i1 = IntInterval(8, 70);
i0 -= i1;
EXPECT_EQ(1u, i0.Length());
EXPECT_EQ(5, i0[0].mStart);
EXPECT_EQ(8, i0[0].mEnd);
i0 = IntIntervals();
i0 += IntInterval(0, 10);
IntIntervals i2;
i2 += IntInterval(4, 6);
i0 -= i2;
EXPECT_EQ(2u, i0.Length());
EXPECT_EQ(0, i0[0].mStart);
EXPECT_EQ(4, i0[0].mEnd);
EXPECT_EQ(6, i0[1].mStart);
EXPECT_EQ(10, i0[1].mEnd);
i0 = IntIntervals();
i0 += IntInterval(0, 1);
i0 += IntInterval(3, 10);
EXPECT_EQ(2u, i0.Length());
// This fuzz should collapse i0 into [0,10).
i0.SetFuzz(1);
EXPECT_EQ(1u, i0.Length());
EXPECT_EQ(1, i0[0].mFuzz);
i2 = IntInterval(4, 6);
i0 -= i2;
EXPECT_EQ(2u, i0.Length());
EXPECT_EQ(0, i0[0].mStart);
EXPECT_EQ(4, i0[0].mEnd);
EXPECT_EQ(6, i0[1].mStart);
EXPECT_EQ(10, i0[1].mEnd);
EXPECT_EQ(1, i0[0].mFuzz);
EXPECT_EQ(1, i0[1].mFuzz);
i0 = IntIntervals();
i0 += IntInterval(0, 10);
// [4,6) with fuzz 1 used to fail because the complementary interval set
// [0,4)+[6,10) would collapse into [0,10).
i2 = IntInterval(4, 6);
i2.SetFuzz(1);
i0 -= i2;
EXPECT_EQ(2u, i0.Length());
EXPECT_EQ(0, i0[0].mStart);
EXPECT_EQ(4, i0[0].mEnd);
EXPECT_EQ(6, i0[1].mStart);
EXPECT_EQ(10, i0[1].mEnd);
}

View file

@ -0,0 +1,454 @@
/* -*- 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 <vector>
#include "MP3Demuxer.h"
#include "mozilla/ArrayUtils.h"
#include "MockMediaResource.h"
using namespace mozilla;
using namespace mozilla::mp3;
using media::TimeUnit;
// Regular MP3 file mock resource.
class MockMP3MediaResource : public MockMediaResource {
public:
explicit MockMP3MediaResource(const char* aFileName)
: MockMediaResource(aFileName)
{}
protected:
virtual ~MockMP3MediaResource() {}
};
// MP3 stream mock resource.
class MockMP3StreamMediaResource : public MockMP3MediaResource {
public:
explicit MockMP3StreamMediaResource(const char* aFileName)
: MockMP3MediaResource(aFileName)
{}
int64_t GetLength() override { return -1; }
protected:
virtual ~MockMP3StreamMediaResource() {}
};
struct MP3Resource {
const char* mFilePath;
bool mIsVBR;
int64_t mFileSize;
int32_t mMPEGLayer;
int32_t mMPEGVersion;
uint8_t mID3MajorVersion;
uint8_t mID3MinorVersion;
uint8_t mID3Flags;
uint32_t mID3Size;
int64_t mDuration;
float mDurationError;
float mSeekError;
int32_t mSampleRate;
int32_t mSamplesPerFrame;
uint32_t mNumSamples;
// TODO: temp solution, we could parse them instead or account for them
// otherwise.
int32_t mNumTrailingFrames;
int32_t mBitrate;
int32_t mSlotSize;
int32_t mPrivate;
// The first n frame offsets.
std::vector<int32_t> mSyncOffsets;
RefPtr<MockMP3MediaResource> mResource;
RefPtr<MP3TrackDemuxer> mDemuxer;
};
class MP3DemuxerTest : public ::testing::Test {
protected:
void SetUp() override {
{
MP3Resource res;
res.mFilePath = "noise.mp3";
res.mIsVBR = false;
res.mFileSize = 965257;
res.mMPEGLayer = 3;
res.mMPEGVersion = 1;
res.mID3MajorVersion = 3;
res.mID3MinorVersion = 0;
res.mID3Flags = 0;
res.mID3Size = 2141;
res.mDuration = 30067000;
res.mDurationError = 0.001f;
res.mSeekError = 0.02f;
res.mSampleRate = 44100;
res.mSamplesPerFrame = 1152;
res.mNumSamples = 1325952;
res.mNumTrailingFrames = 2;
res.mBitrate = 256000;
res.mSlotSize = 1;
res.mPrivate = 0;
const int syncs[] = { 2151, 2987, 3823, 4659, 5495, 6331 };
res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 6);
// No content length can be estimated for CBR stream resources.
MP3Resource streamRes = res;
streamRes.mFileSize = -1;
streamRes.mDuration = -1;
streamRes.mDurationError = 0.0f;
res.mResource = new MockMP3MediaResource(res.mFilePath);
res.mDemuxer = new MP3TrackDemuxer(res.mResource);
mTargets.push_back(res);
streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
mTargets.push_back(streamRes);
}
{
MP3Resource res;
// This file trips up the MP3 demuxer if ID3v2 tags aren't properly skipped. If skipping is
// not properly implemented, depending on the strictness of the MPEG frame parser a false
// sync will be detected somewhere within the metadata at or after 112087, or failing
// that, at the artificially added extraneous header at 114532.
res.mFilePath = "id3v2header.mp3";
res.mIsVBR = false;
res.mFileSize = 191302;
res.mMPEGLayer = 3;
res.mMPEGVersion = 1;
res.mID3MajorVersion = 3;
res.mID3MinorVersion = 0;
res.mID3Flags = 0;
res.mID3Size = 115304;
res.mDuration = 3160816;
res.mDurationError = 0.001f;
res.mSeekError = 0.02f;
res.mSampleRate = 44100;
res.mSamplesPerFrame = 1152;
res.mNumSamples = 139392;
res.mNumTrailingFrames = 0;
res.mBitrate = 192000;
res.mSlotSize = 1;
res.mPrivate = 1;
const int syncs[] = { 115314, 115941, 116568, 117195, 117822, 118449 };
res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 6);
// No content length can be estimated for CBR stream resources.
MP3Resource streamRes = res;
streamRes.mFileSize = -1;
streamRes.mDuration = -1;
streamRes.mDurationError = 0.0f;
res.mResource = new MockMP3MediaResource(res.mFilePath);
res.mDemuxer = new MP3TrackDemuxer(res.mResource);
mTargets.push_back(res);
streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
mTargets.push_back(streamRes);
}
{
MP3Resource res;
res.mFilePath = "noise_vbr.mp3";
res.mIsVBR = true;
res.mFileSize = 583679;
res.mMPEGLayer = 3;
res.mMPEGVersion = 1;
res.mID3MajorVersion = 3;
res.mID3MinorVersion = 0;
res.mID3Flags = 0;
res.mID3Size = 2221;
res.mDuration = 30081000;
res.mDurationError = 0.005f;
res.mSeekError = 0.02f;
res.mSampleRate = 44100;
res.mSamplesPerFrame = 1152;
res.mNumSamples = 1326575;
res.mNumTrailingFrames = 3;
res.mBitrate = 154000;
res.mSlotSize = 1;
res.mPrivate = 0;
const int syncs[] = { 2231, 2648, 2752, 3796, 4318, 4735 };
res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 6);
// VBR stream resources contain header info on total frames numbers, which
// is used to estimate the total duration.
MP3Resource streamRes = res;
streamRes.mFileSize = -1;
res.mResource = new MockMP3MediaResource(res.mFilePath);
res.mDemuxer = new MP3TrackDemuxer(res.mResource);
mTargets.push_back(res);
streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
mTargets.push_back(streamRes);
}
{
MP3Resource res;
res.mFilePath = "small-shot.mp3";
res.mIsVBR = true;
res.mFileSize = 6825;
res.mMPEGLayer = 3;
res.mMPEGVersion = 1;
res.mID3MajorVersion = 4;
res.mID3MinorVersion = 0;
res.mID3Flags = 0;
res.mID3Size = 24;
res.mDuration = 336686;
res.mDurationError = 0.01f;
res.mSeekError = 0.2f;
res.mSampleRate = 44100;
res.mSamplesPerFrame = 1152;
res.mNumSamples = 12;
res.mNumTrailingFrames = 0;
res.mBitrate = 256000;
res.mSlotSize = 1;
res.mPrivate = 0;
const int syncs[] = { 34, 556, 1078, 1601, 2123, 2646, 3168, 3691, 4213,
4736, 5258, 5781, 6303 };
res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 13);
// No content length can be estimated for CBR stream resources.
MP3Resource streamRes = res;
streamRes.mFileSize = -1;
res.mResource = new MockMP3MediaResource(res.mFilePath);
res.mDemuxer = new MP3TrackDemuxer(res.mResource);
mTargets.push_back(res);
streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
mTargets.push_back(streamRes);
}
{
MP3Resource res;
// This file contains a false frame sync at 34, just after the ID3 tag,
// which should be identified as a false positive and skipped.
res.mFilePath = "small-shot-false-positive.mp3";
res.mIsVBR = true;
res.mFileSize = 6845;
res.mMPEGLayer = 3;
res.mMPEGVersion = 1;
res.mID3MajorVersion = 4;
res.mID3MinorVersion = 0;
res.mID3Flags = 0;
res.mID3Size = 24;
res.mDuration = 336686;
res.mDurationError = 0.01f;
res.mSeekError = 0.2f;
res.mSampleRate = 44100;
res.mSamplesPerFrame = 1152;
res.mNumSamples = 12;
res.mNumTrailingFrames = 0;
res.mBitrate = 256000;
res.mSlotSize = 1;
res.mPrivate = 0;
const int syncs[] = { 54, 576, 1098, 1621, 2143, 2666, 3188, 3711, 4233,
4756, 5278, 5801, 6323 };
res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 13);
// No content length can be estimated for CBR stream resources.
MP3Resource streamRes = res;
streamRes.mFileSize = -1;
res.mResource = new MockMP3MediaResource(res.mFilePath);
res.mDemuxer = new MP3TrackDemuxer(res.mResource);
mTargets.push_back(res);
streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
mTargets.push_back(streamRes);
}
for (auto& target: mTargets) {
ASSERT_EQ(NS_OK, target.mResource->Open(nullptr));
ASSERT_TRUE(target.mDemuxer->Init());
}
}
std::vector<MP3Resource> mTargets;
};
TEST_F(MP3DemuxerTest, ID3Tags) {
for (const auto& target: mTargets) {
RefPtr<MediaRawData> frame(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frame);
const auto& id3 = target.mDemuxer->ID3Header();
ASSERT_TRUE(id3.IsValid());
EXPECT_EQ(target.mID3MajorVersion, id3.MajorVersion());
EXPECT_EQ(target.mID3MinorVersion, id3.MinorVersion());
EXPECT_EQ(target.mID3Flags, id3.Flags());
EXPECT_EQ(target.mID3Size, id3.Size());
}
}
TEST_F(MP3DemuxerTest, VBRHeader) {
for (const auto& target: mTargets) {
RefPtr<MediaRawData> frame(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frame);
const auto& vbr = target.mDemuxer->VBRInfo();
if (target.mIsVBR) {
EXPECT_EQ(FrameParser::VBRHeader::XING, vbr.Type());
// TODO: find reference number which accounts for trailing headers.
// EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame, vbr.NumAudioFrames().value());
} else {
EXPECT_EQ(FrameParser::VBRHeader::NONE, vbr.Type());
EXPECT_FALSE(vbr.NumAudioFrames());
}
}
}
TEST_F(MP3DemuxerTest, FrameParsing) {
for (const auto& target: mTargets) {
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frameData);
EXPECT_EQ(target.mFileSize, target.mDemuxer->StreamLength());
const auto& id3 = target.mDemuxer->ID3Header();
ASSERT_TRUE(id3.IsValid());
int64_t parsedLength = id3.Size();
int64_t bitrateSum = 0;
int32_t numFrames = 0;
int32_t numSamples = 0;
while (frameData) {
if (static_cast<int64_t>(target.mSyncOffsets.size()) > numFrames) {
// Test sync offsets.
EXPECT_EQ(target.mSyncOffsets[numFrames], frameData->mOffset);
}
++numFrames;
parsedLength += frameData->Size();
const auto& frame = target.mDemuxer->LastFrame();
const auto& header = frame.Header();
ASSERT_TRUE(header.IsValid());
numSamples += header.SamplesPerFrame();
EXPECT_EQ(target.mMPEGLayer, header.Layer());
EXPECT_EQ(target.mSampleRate, header.SampleRate());
EXPECT_EQ(target.mSamplesPerFrame, header.SamplesPerFrame());
EXPECT_EQ(target.mSlotSize, header.SlotSize());
EXPECT_EQ(target.mPrivate, header.Private());
if (target.mIsVBR) {
// Used to compute the average bitrate for VBR streams.
bitrateSum += target.mBitrate;
} else {
EXPECT_EQ(target.mBitrate, header.Bitrate());
}
frameData = target.mDemuxer->DemuxSample();
}
// TODO: find reference number which accounts for trailing headers.
// EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame, numFrames);
// EXPECT_EQ(target.mNumSamples, numSamples);
// There may be trailing headers which we don't parse, so the stream length
// is the upper bound.
if (target.mFileSize > 0) {
EXPECT_GE(target.mFileSize, parsedLength);
}
if (target.mIsVBR) {
ASSERT_TRUE(numFrames);
EXPECT_EQ(target.mBitrate, static_cast<int32_t>(bitrateSum / numFrames));
}
}
}
TEST_F(MP3DemuxerTest, Duration) {
for (const auto& target: mTargets) {
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frameData);
EXPECT_EQ(target.mFileSize, target.mDemuxer->StreamLength());
while (frameData) {
EXPECT_NEAR(target.mDuration, target.mDemuxer->Duration().ToMicroseconds(),
target.mDurationError * target.mDuration);
frameData = target.mDemuxer->DemuxSample();
}
}
// Seek out of range tests.
for (const auto& target: mTargets) {
// Skip tests for stream media resources because of lacking duration.
if (target.mFileSize <= 0) {
continue;
}
target.mDemuxer->Reset();
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frameData);
const int64_t duration = target.mDemuxer->Duration().ToMicroseconds();
const int64_t pos = duration + 1e6;
// Attempt to seek 1 second past the end of stream.
target.mDemuxer->Seek(TimeUnit::FromMicroseconds(pos));
// The seek should bring us to the end of the stream.
EXPECT_NEAR(duration, target.mDemuxer->SeekPosition().ToMicroseconds(),
target.mSeekError * duration);
// Since we're at the end of the stream, there should be no frames left.
frameData = target.mDemuxer->DemuxSample();
ASSERT_FALSE(frameData);
}
}
TEST_F(MP3DemuxerTest, Seek) {
for (const auto& target: mTargets) {
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frameData);
const int64_t seekTime = TimeUnit::FromSeconds(1).ToMicroseconds();
int64_t pos = target.mDemuxer->SeekPosition().ToMicroseconds();
while (frameData) {
EXPECT_NEAR(pos, target.mDemuxer->SeekPosition().ToMicroseconds(),
target.mSeekError * pos);
pos += seekTime;
target.mDemuxer->Seek(TimeUnit::FromMicroseconds(pos));
frameData = target.mDemuxer->DemuxSample();
}
}
// Seeking should work with in-between resets, too.
for (const auto& target: mTargets) {
target.mDemuxer->Reset();
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
ASSERT_TRUE(frameData);
const int64_t seekTime = TimeUnit::FromSeconds(1).ToMicroseconds();
int64_t pos = target.mDemuxer->SeekPosition().ToMicroseconds();
while (frameData) {
EXPECT_NEAR(pos, target.mDemuxer->SeekPosition().ToMicroseconds(),
target.mSeekError * pos);
pos += seekTime;
target.mDemuxer->Reset();
target.mDemuxer->Seek(TimeUnit::FromMicroseconds(pos));
frameData = target.mDemuxer->DemuxSample();
}
}
}

View file

@ -0,0 +1,463 @@
/* -*- 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 "MP4Demuxer.h"
#include "MP4Stream.h"
#include "mozilla/MozPromise.h"
#include "MediaDataDemuxer.h"
#include "mozilla/SharedThreadPool.h"
#include "mozilla/TaskQueue.h"
#include "mozilla/ArrayUtils.h"
#include "MockMediaResource.h"
#include "VideoUtils.h"
using namespace mozilla;
using namespace mp4_demuxer;
class AutoTaskQueue;
#define DO_FAIL [binding]()->void { EXPECT_TRUE(false); binding->mTaskQueue->BeginShutdown(); }
class MP4DemuxerBinding
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MP4DemuxerBinding);
RefPtr<MockMediaResource> resource;
RefPtr<MP4Demuxer> mDemuxer;
RefPtr<TaskQueue> mTaskQueue;
RefPtr<MediaTrackDemuxer> mAudioTrack;
RefPtr<MediaTrackDemuxer> mVideoTrack;
uint32_t mIndex;
nsTArray<RefPtr<MediaRawData>> mSamples;
nsTArray<int64_t> mKeyFrameTimecodes;
MozPromiseHolder<GenericPromise> mCheckTrackKeyFramePromise;
MozPromiseHolder<GenericPromise> mCheckTrackSamples;
explicit MP4DemuxerBinding(const char* aFileName = "dash_dashinit.mp4")
: resource(new MockMediaResource(aFileName))
, mDemuxer(new MP4Demuxer(resource))
, mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK)))
, mIndex(0)
{
EXPECT_EQ(NS_OK, resource->Open(nullptr));
}
template<typename Function>
void RunTestAndWait(const Function& aFunction)
{
Function func(aFunction);
RefPtr<MP4DemuxerBinding> binding = this;
mDemuxer->Init()->Then(mTaskQueue, __func__, Move(func), DO_FAIL);
mTaskQueue->AwaitShutdownAndIdle();
}
RefPtr<GenericPromise>
CheckTrackKeyFrame(MediaTrackDemuxer* aTrackDemuxer)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
RefPtr<MediaTrackDemuxer> track = aTrackDemuxer;
RefPtr<MP4DemuxerBinding> binding = this;
int64_t time = -1;
while (mIndex < mSamples.Length()) {
uint32_t i = mIndex++;
if (mSamples[i]->mKeyframe) {
time = mSamples[i]->mTime;
break;
}
}
RefPtr<GenericPromise> p = mCheckTrackKeyFramePromise.Ensure(__func__);
if (time == -1) {
mCheckTrackKeyFramePromise.Resolve(true, __func__);
return p;
}
DispatchTask(
[track, time, binding] () {
track->Seek(media::TimeUnit::FromMicroseconds(time))->Then(binding->mTaskQueue, __func__,
[track, time, binding] () {
track->GetSamples()->Then(binding->mTaskQueue, __func__,
[track, time, binding] (RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples) {
EXPECT_EQ(time, aSamples->mSamples[0]->mTime);
binding->CheckTrackKeyFrame(track);
},
DO_FAIL
);
},
DO_FAIL
);
}
);
return p;
}
RefPtr<GenericPromise>
CheckTrackSamples(MediaTrackDemuxer* aTrackDemuxer)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
RefPtr<MediaTrackDemuxer> track = aTrackDemuxer;
RefPtr<MP4DemuxerBinding> binding = this;
RefPtr<GenericPromise> p = mCheckTrackSamples.Ensure(__func__);
DispatchTask(
[track, binding] () {
track->GetSamples()->Then(binding->mTaskQueue, __func__,
[track, binding] (RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples) {
if (aSamples->mSamples.Length()) {
binding->mSamples.AppendElements(aSamples->mSamples);
binding->CheckTrackSamples(track);
}
},
[binding] (const MediaResult& aError) {
if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
EXPECT_TRUE(binding->mSamples.Length() > 1);
for (uint32_t i = 0; i < (binding->mSamples.Length() - 1); i++) {
EXPECT_LT(binding->mSamples[i]->mTimecode, binding->mSamples[i + 1]->mTimecode);
if (binding->mSamples[i]->mKeyframe) {
binding->mKeyFrameTimecodes.AppendElement(binding->mSamples[i]->mTimecode);
}
}
binding->mCheckTrackSamples.Resolve(true, __func__);
} else {
EXPECT_TRUE(false);
binding->mCheckTrackSamples.Reject(aError, __func__);
}
}
);
}
);
return p;
}
private:
template<typename FunctionType>
void
DispatchTask(FunctionType aFun)
{
RefPtr<Runnable> r = NS_NewRunnableFunction(aFun);
mTaskQueue->Dispatch(r.forget());
}
virtual ~MP4DemuxerBinding()
{
}
};
TEST(MP4Demuxer, Seek)
{
RefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding();
binding->RunTestAndWait([binding] () {
binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
binding->CheckTrackSamples(binding->mVideoTrack)
->Then(binding->mTaskQueue, __func__,
[binding] () {
binding->CheckTrackKeyFrame(binding->mVideoTrack)
->Then(binding->mTaskQueue, __func__,
[binding] () {
binding->mTaskQueue->BeginShutdown();
}, DO_FAIL);
}, DO_FAIL);
});
}
static nsCString
ToCryptoString(const CryptoSample& aCrypto)
{
nsCString res;
if (aCrypto.mValid) {
res.AppendPrintf("%d %d ", aCrypto.mMode, aCrypto.mIVSize);
for (size_t i = 0; i < aCrypto.mKeyId.Length(); i++) {
res.AppendPrintf("%02x", aCrypto.mKeyId[i]);
}
res.Append(" ");
for (size_t i = 0; i < aCrypto.mIV.Length(); i++) {
res.AppendPrintf("%02x", aCrypto.mIV[i]);
}
EXPECT_EQ(aCrypto.mPlainSizes.Length(), aCrypto.mEncryptedSizes.Length());
for (size_t i = 0; i < aCrypto.mPlainSizes.Length(); i++) {
res.AppendPrintf(" %d,%d", aCrypto.mPlainSizes[i],
aCrypto.mEncryptedSizes[i]);
}
} else {
res.Append("no crypto");
}
return res;
}
#ifndef XP_WIN // VC2013 doesn't support C++11 array initialization.
TEST(MP4Demuxer, CENCFragVideo)
{
const char* video[] = {
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000000 5,684 5,16980",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000450 5,1826",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000004c3 5,1215",
"1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000050f 5,1302",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000561 5,939",
"1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000059c 5,763",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000005cc 5,672",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000005f6 5,748",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000625 5,1025",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000666 5,730",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000694 5,897",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000006cd 5,643",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000006f6 5,556",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000719 5,527",
"1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000073a 5,606",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000760 5,701",
"1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000078c 5,531",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000007ae 5,562",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000007d2 5,576",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000007f6 5,514",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000817 5,404",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000831 5,635",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000859 5,433",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000875 5,478",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000893 5,474",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000008b1 5,462",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000008ce 5,473",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000008ec 5,437",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000908 5,418",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000923 5,475",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000941 5,23133",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000ee7 5,475",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f05 5,402",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f1f 5,415",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f39 5,408",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f53 5,442",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f6f 5,385",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f88 5,368",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f9f 5,354",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000fb6 5,400",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000fcf 5,399",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000fe8 5,1098",
"1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000102d 5,1508",
"1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000108c 5,1345",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000010e1 5,1945",
"1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000115b 5,1824",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000011cd 5,2133",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001253 5,2486",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000012ef 5,1739",
"1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000135c 5,1836",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000013cf 5,2367",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001463 5,2571",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001504 5,3008",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000015c0 5,3255",
"1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000168c 5,3225",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001756 5,3118",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001819 5,2407",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000018b0 5,2400",
"1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001946 5,2158",
"1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000019cd 5,2392",
};
RefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("gizmo-frag.mp4");
binding->RunTestAndWait([binding, video] () {
// grab all video samples.
binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
binding->CheckTrackSamples(binding->mVideoTrack)
->Then(binding->mTaskQueue, __func__,
[binding, video] () {
for (uint32_t i = 0; i < binding->mSamples.Length(); i++) {
nsCString text = ToCryptoString(binding->mSamples[i]->mCrypto);
EXPECT_STREQ(video[i++], text.get());
}
EXPECT_EQ(ArrayLength(video), binding->mSamples.Length());
binding->mTaskQueue->BeginShutdown();
}, DO_FAIL);
});
}
TEST(MP4Demuxer, CENCFragAudio)
{
const char* audio[] = {
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000000 0,281",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000012 0,257",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000023 0,246",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000033 0,257",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000044 0,260",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000055 0,260",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000066 0,272",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000077 0,280",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000089 0,284",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000009b 0,290",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000ae 0,278",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000c0 0,268",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000d1 0,307",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000e5 0,290",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000f8 0,304",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000010b 0,316",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000011f 0,308",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000133 0,301",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000146 0,318",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000015a 0,311",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000016e 0,303",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000181 0,325",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000196 0,334",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001ab 0,344",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001c1 0,344",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001d7 0,387",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001f0 0,396",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000209 0,368",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000220 0,373",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000238 0,425",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000253 0,428",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000026e 0,426",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000289 0,427",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002a4 0,424",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002bf 0,447",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002db 0,446",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002f7 0,442",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000313 0,444",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000032f 0,374",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000347 0,405",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000361 0,372",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000379 0,395",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000392 0,435",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003ae 0,426",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003c9 0,430",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003e4 0,390",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003fd 0,335",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000412 0,339",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000428 0,352",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000043e 0,364",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000455 0,398",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000046e 0,451",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000048b 0,448",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004a7 0,436",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004c3 0,424",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004de 0,428",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004f9 0,413",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000513 0,430",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000052e 0,450",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000054b 0,386",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000564 0,320",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000578 0,347",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000058e 0,382",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005a6 0,437",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005c2 0,387",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005db 0,340",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005f1 0,337",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000607 0,389",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000620 0,428",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000063b 0,426",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000656 0,446",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000672 0,456",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000068f 0,468",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000006ad 0,468",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000006cb 0,463",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000006e8 0,467",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000706 0,460",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000723 0,446",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000073f 0,453",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000075c 0,448",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000778 0,446",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000794 0,439",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000007b0 0,436",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000007cc 0,441",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000007e8 0,465",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000806 0,448",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000822 0,448",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000083e 0,469",
"1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000085c 0,431",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000877 0,437",
"1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000893 0,474",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008b1 0,436",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008cd 0,433",
"1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008e9 0,481",
};
RefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("gizmo-frag.mp4");
binding->RunTestAndWait([binding, audio] () {
// grab all audio samples.
binding->mAudioTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
binding->CheckTrackSamples(binding->mAudioTrack)
->Then(binding->mTaskQueue, __func__,
[binding, audio] () {
EXPECT_TRUE(binding->mSamples.Length() > 1);
for (uint32_t i = 0; i < binding->mSamples.Length(); i++) {
nsCString text = ToCryptoString(binding->mSamples[i]->mCrypto);
EXPECT_STREQ(audio[i++], text.get());
}
EXPECT_EQ(ArrayLength(audio), binding->mSamples.Length());
binding->mTaskQueue->BeginShutdown();
}, DO_FAIL);
});
}
#endif
TEST(MP4Demuxer, GetNextKeyframe)
{
RefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("gizmo-frag.mp4");
binding->RunTestAndWait([binding] () {
// Insert a [0,end] buffered range, to simulate Moof's being buffered
// via MSE.
auto len = binding->resource->GetLength();
binding->resource->MockAddBufferedRange(0, len);
// gizmp-frag has two keyframes; one at dts=cts=0, and another at
// dts=cts=1000000. Verify we get expected results.
media::TimeUnit time;
binding->mVideoTrack = binding->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
binding->mVideoTrack->Reset();
binding->mVideoTrack->GetNextRandomAccessPoint(&time);
EXPECT_EQ(time.ToMicroseconds(), 0);
binding->mVideoTrack->GetSamples()->Then(binding->mTaskQueue, __func__,
[binding] () {
media::TimeUnit time;
binding->mVideoTrack->GetNextRandomAccessPoint(&time);
EXPECT_EQ(time.ToMicroseconds(), 1000000);
binding->mTaskQueue->BeginShutdown();
},
DO_FAIL
);
});
}
TEST(MP4Demuxer, ZeroInLastMoov)
{
RefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("short-zero-in-moov.mp4");
binding->RunTestAndWait([binding] () {
// It demuxes without error. That is sufficient.
binding->mTaskQueue->BeginShutdown();
});
}
TEST(MP4Demuxer, ZeroInMoovQuickTime)
{
RefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("short-zero-inband.mov");
binding->RunTestAndWait([binding] () {
// It demuxes without error. That is sufficient.
binding->mTaskQueue->BeginShutdown();
});
}
TEST(MP4Demuxer, IgnoreMinus1Duration)
{
RefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("negative_duration.mp4");
binding->RunTestAndWait([binding] () {
// It demuxes without error. That is sufficient.
binding->mTaskQueue->BeginShutdown();
});
}
#undef DO_FAIL

View file

@ -0,0 +1,217 @@
/* -*- 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 "MP4Reader.h"
#include "MP4Decoder.h"
#include "mozilla/SharedThreadPool.h"
#include "MockMediaResource.h"
#include "MockMediaDecoderOwner.h"
#include "mozilla/Preferences.h"
#include "TimeUnits.h"
using namespace mozilla;
using namespace mozilla::dom;
class TestBinding
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestBinding);
RefPtr<MP4Decoder> decoder;
RefPtr<MockMediaResource> resource;
RefPtr<MP4Reader> reader;
explicit TestBinding(const char* aFileName = "gizmo.mp4")
: decoder(new MP4Decoder())
, resource(new MockMediaResource(aFileName))
, reader(new MP4Reader(decoder))
{
EXPECT_EQ(NS_OK, Preferences::SetBool(
"media.use-blank-decoder", true));
EXPECT_EQ(NS_OK, resource->Open(nullptr));
decoder->SetResource(resource);
reader->Init(nullptr);
// This needs to be done before invoking GetBuffered. This is normally
// done by MediaDecoderStateMachine.
reader->DispatchSetStartTime(0);
}
void Init() {
nsCOMPtr<nsIThread> thread;
nsCOMPtr<nsIRunnable> r = NewRunnableMethod(this, &TestBinding::ReadMetadata);
nsresult rv = NS_NewThread(getter_AddRefs(thread), r);
EXPECT_EQ(NS_OK, rv);
thread->Shutdown();
}
private:
virtual ~TestBinding()
{
{
RefPtr<TaskQueue> queue = reader->OwnerThread();
nsCOMPtr<nsIRunnable> task = NewRunnableMethod(reader, &MP4Reader::Shutdown);
// Hackily bypass the tail dispatcher so that we can AwaitShutdownAndIdle.
// In production code we'd use BeginShutdown + promises.
queue->Dispatch(task.forget(), AbstractThread::AssertDispatchSuccess,
AbstractThread::TailDispatch);
queue->AwaitShutdownAndIdle();
}
decoder = nullptr;
resource = nullptr;
reader = nullptr;
SharedThreadPool::SpinUntilEmpty();
}
void ReadMetadata()
{
MediaInfo info;
MetadataTags* tags;
EXPECT_EQ(NS_OK, reader->ReadMetadata(&info, &tags));
}
};
TEST(MP4Reader, BufferedRange)
{
RefPtr<TestBinding> b = new TestBinding();
b->Init();
// Video 3-4 sec, audio 2.986666-4.010666 sec
b->resource->MockAddBufferedRange(248400, 327455);
media::TimeIntervals ranges = b->reader->GetBuffered();
EXPECT_EQ(1U, ranges.Length());
EXPECT_NEAR(270000 / 90000.0, ranges.Start(0).ToSeconds(), 0.000001);
EXPECT_NEAR(360000 / 90000.0, ranges.End(0).ToSeconds(), 0.000001);
}
TEST(MP4Reader, BufferedRangeMissingLastByte)
{
RefPtr<TestBinding> b = new TestBinding();
b->Init();
// Dropping the last byte of the video
b->resource->MockClearBufferedRanges();
b->resource->MockAddBufferedRange(248400, 324912);
b->resource->MockAddBufferedRange(324913, 327455);
media::TimeIntervals ranges = b->reader->GetBuffered();
EXPECT_EQ(1U, ranges.Length());
EXPECT_NEAR(270000.0 / 90000.0, ranges.Start(0).ToSeconds(), 0.000001);
EXPECT_NEAR(357000 / 90000.0, ranges.End(0).ToSeconds(), 0.000001);
}
TEST(MP4Reader, BufferedRangeSyncFrame)
{
RefPtr<TestBinding> b = new TestBinding();
b->Init();
// Check that missing the first byte at 2 seconds skips right through to 3
// seconds because of a missing sync frame
b->resource->MockClearBufferedRanges();
b->resource->MockAddBufferedRange(146336, 327455);
media::TimeIntervals ranges = b->reader->GetBuffered();
EXPECT_EQ(1U, ranges.Length());
EXPECT_NEAR(270000.0 / 90000.0, ranges.Start(0).ToSeconds(), 0.000001);
EXPECT_NEAR(360000 / 90000.0, ranges.End(0).ToSeconds(), 0.000001);
}
TEST(MP4Reader, CompositionOrder)
{
RefPtr<TestBinding> b = new TestBinding("mediasource_test.mp4");
b->Init();
// The first 5 video samples of this file are:
// Video timescale=2500
// Frame Start Size Time Duration Sync
// 1 48 5455 166 83 Yes
// 2 5503 145 249 83
// 3 6228 575 581 83
// 4 7383 235 415 83
// 5 8779 183 332 83
// 6 9543 191 498 83
//
// Audio timescale=44100
// 1 5648 580 0 1024 Yes
// 2 6803 580 1024 1058 Yes
// 3 7618 581 2082 1014 Yes
// 4 8199 580 3096 1015 Yes
// 5 8962 581 4111 1014 Yes
// 6 9734 580 5125 1014 Yes
// 7 10314 581 6139 1059 Yes
// 8 11207 580 7198 1014 Yes
// 9 12035 581 8212 1014 Yes
// 10 12616 580 9226 1015 Yes
// 11 13220 581 10241 1014 Yes
b->resource->MockClearBufferedRanges();
// First two frames in decoding + first audio frame
b->resource->MockAddBufferedRange(48, 5503); // Video 1
b->resource->MockAddBufferedRange(5503, 5648); // Video 2
b->resource->MockAddBufferedRange(6228, 6803); // Video 3
// Audio - 5 frames; 0 - 139206 us
b->resource->MockAddBufferedRange(5648, 6228);
b->resource->MockAddBufferedRange(6803, 7383);
b->resource->MockAddBufferedRange(7618, 8199);
b->resource->MockAddBufferedRange(8199, 8779);
b->resource->MockAddBufferedRange(8962, 9563);
b->resource->MockAddBufferedRange(9734, 10314);
b->resource->MockAddBufferedRange(10314, 10895);
b->resource->MockAddBufferedRange(11207, 11787);
b->resource->MockAddBufferedRange(12035, 12616);
b->resource->MockAddBufferedRange(12616, 13196);
b->resource->MockAddBufferedRange(13220, 13901);
media::TimeIntervals ranges = b->reader->GetBuffered();
EXPECT_EQ(2U, ranges.Length());
EXPECT_NEAR(166.0 / 2500.0, ranges.Start(0).ToSeconds(), 0.000001);
EXPECT_NEAR(332.0 / 2500.0, ranges.End(0).ToSeconds(), 0.000001);
EXPECT_NEAR(581.0 / 2500.0, ranges.Start(1).ToSeconds(), 0.000001);
EXPECT_NEAR(11255.0 / 44100.0, ranges.End(1).ToSeconds(), 0.000001);
}
TEST(MP4Reader, Normalised)
{
RefPtr<TestBinding> b = new TestBinding("mediasource_test.mp4");
b->Init();
// The first 5 video samples of this file are:
// Video timescale=2500
// Frame Start Size Time Duration Sync
// 1 48 5455 166 83 Yes
// 2 5503 145 249 83
// 3 6228 575 581 83
// 4 7383 235 415 83
// 5 8779 183 332 83
// 6 9543 191 498 83
//
// Audio timescale=44100
// 1 5648 580 0 1024 Yes
// 2 6803 580 1024 1058 Yes
// 3 7618 581 2082 1014 Yes
// 4 8199 580 3096 1015 Yes
// 5 8962 581 4111 1014 Yes
// 6 9734 580 5125 1014 Yes
// 7 10314 581 6139 1059 Yes
// 8 11207 580 7198 1014 Yes
// 9 12035 581 8212 1014 Yes
// 10 12616 580 9226 1015 Yes
// 11 13220 581 10241 1014 Yes
b->resource->MockClearBufferedRanges();
b->resource->MockAddBufferedRange(48, 13901);
media::TimeIntervals ranges = b->reader->GetBuffered();
EXPECT_EQ(1U, ranges.Length());
EXPECT_NEAR(166.0 / 2500.0, ranges.Start(0).ToSeconds(), 0.000001);
EXPECT_NEAR(11255.0 / 44100.0, ranges.End(0).ToSeconds(), 0.000001);
}

View file

@ -0,0 +1,72 @@
/* -*- 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 "Benchmark.h"
#include "MockMediaResource.h"
#include "DecoderTraits.h"
#include "MP4Demuxer.h"
#include "WebMDemuxer.h"
using namespace mozilla;
class BenchmarkRunner
{
public:
explicit BenchmarkRunner(Benchmark* aBenchmark)
: mBenchmark(aBenchmark) {}
uint32_t Run()
{
bool done = false;
uint32_t result = 0;
mBenchmark->Init();
mBenchmark->Run()->Then(
AbstractThread::MainThread(), __func__,
[&](uint32_t aDecodeFps) { result = aDecodeFps; done = true; },
[&]() { done = true; });
// Wait until benchmark completes.
while (!done) {
NS_ProcessNextEvent();
}
return result;
}
private:
RefPtr<Benchmark> mBenchmark;
};
TEST(MediaDataDecoder, H264)
{
if (!DecoderTraits::IsMP4TypeAndEnabled(NS_LITERAL_CSTRING("video/mp4")
, /* DecoderDoctorDiagnostics* */ nullptr)) {
EXPECT_TRUE(true);
} else {
RefPtr<MediaResource> resource =
new MockMediaResource("gizmo.mp4", NS_LITERAL_CSTRING("video/mp4"));
nsresult rv = resource->Open(nullptr);
EXPECT_TRUE(NS_SUCCEEDED(rv));
BenchmarkRunner runner(new Benchmark(new MP4Demuxer(resource)));
EXPECT_GT(runner.Run(), 0u);
}
}
TEST(MediaDataDecoder, VP9)
{
if (!DecoderTraits::IsWebMTypeAndEnabled(NS_LITERAL_CSTRING("video/webm"))) {
EXPECT_TRUE(true);
} else {
RefPtr<MediaResource> resource =
new MockMediaResource("vp9cake.webm", NS_LITERAL_CSTRING("video/webm"));
nsresult rv = resource->Open(nullptr);
EXPECT_TRUE(NS_SUCCEEDED(rv));
BenchmarkRunner runner(new Benchmark(new WebMDemuxer(resource)));
EXPECT_GT(runner.Run(), 0u);
}
}

View file

@ -0,0 +1,337 @@
/* -*- 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/TaskQueue.h"
#include "mozilla/UniquePtr.h"
#include "MediaEventSource.h"
#include "VideoUtils.h"
using namespace mozilla;
/*
* Test if listeners receive the event data correctly.
*/
TEST(MediaEventSource, SingleListener)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducer<int> source;
int i = 0;
auto func = [&] (int j) { i += j; };
MediaEventListener listener = source.Connect(queue, func);
// Call Notify 3 times. The listener should be also called 3 times.
source.Notify(3);
source.Notify(5);
source.Notify(7);
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
// Verify the event data is passed correctly to the listener.
EXPECT_EQ(i, 15); // 3 + 5 + 7
listener.Disconnect();
}
TEST(MediaEventSource, MultiListener)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducer<int> source;
int i = 0;
int j = 0;
auto func1 = [&] (int k) { i = k * 2; };
auto func2 = [&] (int k) { j = k * 3; };
MediaEventListener listener1 = source.Connect(queue, func1);
MediaEventListener listener2 = source.Connect(queue, func2);
// Both listeners should receive the event.
source.Notify(11);
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
// Verify the event data is passed correctly to the listener.
EXPECT_EQ(i, 22); // 11 * 2
EXPECT_EQ(j, 33); // 11 * 3
listener1.Disconnect();
listener2.Disconnect();
}
/*
* Test if disconnecting a listener prevents events from coming.
*/
TEST(MediaEventSource, DisconnectAfterNotification)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducer<int> source;
int i = 0;
MediaEventListener listener;
auto func = [&] (int j) { i += j; listener.Disconnect(); };
listener = source.Connect(queue, func);
// Call Notify() twice. Since we disconnect the listener when receiving
// the 1st event, the 2nd event should not reach the listener.
source.Notify(11);
source.Notify(11);
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
// Check only the 1st event is received.
EXPECT_EQ(i, 11);
}
TEST(MediaEventSource, DisconnectBeforeNotification)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducer<int> source;
int i = 0;
int j = 0;
auto func1 = [&] (int k) { i = k * 2; };
auto func2 = [&] (int k) { j = k * 3; };
MediaEventListener listener1 = source.Connect(queue, func1);
MediaEventListener listener2 = source.Connect(queue, func2);
// Disconnect listener2 before notification. Only listener1 should receive
// the event.
listener2.Disconnect();
source.Notify(11);
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
EXPECT_EQ(i, 22); // 11 * 2
EXPECT_EQ(j, 0); // event not received
listener1.Disconnect();
}
/*
* Test we don't hit the assertion when calling Connect() and Disconnect()
* repeatedly.
*/
TEST(MediaEventSource, DisconnectAndConnect)
{
RefPtr<TaskQueue> queue;
MediaEventProducerExc<int> source;
MediaEventListener listener = source.Connect(queue, [](){});
listener.Disconnect();
listener = source.Connect(queue, [](){});
listener.Disconnect();
}
/*
* Test void event type.
*/
TEST(MediaEventSource, VoidEventType)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducer<void> source;
int i = 0;
// Test function object.
auto func = [&] () { ++i; };
MediaEventListener listener1 = source.Connect(queue, func);
// Test member function.
struct Foo {
Foo() : j(1) {}
void OnNotify() {
j *= 2;
}
int j;
} foo;
MediaEventListener listener2 = source.Connect(queue, &foo, &Foo::OnNotify);
// Call Notify 2 times. The listener should be also called 2 times.
source.Notify();
source.Notify();
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
// Verify the event data is passed correctly to the listener.
EXPECT_EQ(i, 2); // ++i called twice
EXPECT_EQ(foo.j, 4); // |j *= 2| called twice
listener1.Disconnect();
listener2.Disconnect();
}
/*
* Test listeners can take various event types (T, T&&, const T& and void).
*/
TEST(MediaEventSource, ListenerType1)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducer<int> source;
int i = 0;
// Test various argument types.
auto func1 = [&] (int&& j) { i += j; };
auto func2 = [&] (const int& j) { i += j; };
auto func3 = [&] () { i += 1; };
MediaEventListener listener1 = source.Connect(queue, func1);
MediaEventListener listener2 = source.Connect(queue, func2);
MediaEventListener listener3 = source.Connect(queue, func3);
source.Notify(1);
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
EXPECT_EQ(i, 3);
listener1.Disconnect();
listener2.Disconnect();
listener3.Disconnect();
}
TEST(MediaEventSource, ListenerType2)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducer<int> source;
struct Foo {
Foo() : mInt(0) {}
void OnNotify1(int&& i) { mInt += i; }
void OnNotify2(const int& i) { mInt += i; }
void OnNotify3() { mInt += 1; }
void OnNotify4(int i) const { mInt += i; }
void OnNotify5(int i) volatile { mInt += i; }
mutable int mInt;
} foo;
// Test member functions which might be CV qualified.
MediaEventListener listener1 = source.Connect(queue, &foo, &Foo::OnNotify1);
MediaEventListener listener2 = source.Connect(queue, &foo, &Foo::OnNotify2);
MediaEventListener listener3 = source.Connect(queue, &foo, &Foo::OnNotify3);
MediaEventListener listener4 = source.Connect(queue, &foo, &Foo::OnNotify4);
MediaEventListener listener5 = source.Connect(queue, &foo, &Foo::OnNotify5);
source.Notify(1);
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
EXPECT_EQ(foo.mInt, 5);
listener1.Disconnect();
listener2.Disconnect();
listener3.Disconnect();
listener4.Disconnect();
listener5.Disconnect();
}
struct SomeEvent {
explicit SomeEvent(int& aCount) : mCount(aCount) {}
// Increment mCount when copy constructor is called to know how many times
// the event data is copied.
SomeEvent(const SomeEvent& aOther) : mCount(aOther.mCount) {
++mCount;
}
int& mCount;
};
/*
* Test we don't have unnecessary copies of the event data.
*/
TEST(MediaEventSource, CopyEvent1)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducer<SomeEvent> source;
int i = 0;
auto func = [] (SomeEvent&& aEvent) {};
struct Foo {
void OnNotify(SomeEvent&& aEvent) {}
} foo;
MediaEventListener listener1 = source.Connect(queue, func);
MediaEventListener listener2 = source.Connect(queue, &foo, &Foo::OnNotify);
// We expect i to be 2 since SomeEvent should be copied only once when
// passing to each listener.
source.Notify(SomeEvent(i));
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
EXPECT_EQ(i, 2);
listener1.Disconnect();
listener2.Disconnect();
}
TEST(MediaEventSource, CopyEvent2)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducer<SomeEvent> source;
int i = 0;
auto func = [] () {};
struct Foo {
void OnNotify() {}
} foo;
MediaEventListener listener1 = source.Connect(queue, func);
MediaEventListener listener2 = source.Connect(queue, &foo, &Foo::OnNotify);
// SomeEvent won't be copied at all since the listeners take no arguments.
source.Notify(SomeEvent(i));
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
EXPECT_EQ(i, 0);
listener1.Disconnect();
listener2.Disconnect();
}
/*
* Test move-only types.
*/
TEST(MediaEventSource, MoveOnly)
{
RefPtr<TaskQueue> queue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLAYBACK));
MediaEventProducerExc<UniquePtr<int>> source;
auto func = [] (UniquePtr<int>&& aEvent) {
EXPECT_EQ(*aEvent, 20);
};
MediaEventListener listener = source.Connect(queue, func);
// It is OK to pass an rvalue which is move-only.
source.Notify(UniquePtr<int>(new int(20)));
// It is an error to pass an lvalue which is move-only.
// UniquePtr<int> event(new int(30));
// source.Notify(event);
queue->BeginShutdown();
queue->AwaitShutdownAndIdle();
listener.Disconnect();
}

View file

@ -0,0 +1,248 @@
/* -*- 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/dom/HTMLMediaElement.h"
#include "mozilla/Preferences.h"
#include "mozilla/TaskQueue.h"
#include "ImageContainer.h"
#include "Layers.h"
#include "MediaData.h"
#include "MediaFormatReader.h"
#include "MP4Decoder.h"
#include "MockMediaDecoderOwner.h"
#include "MockMediaResource.h"
#include "VideoFrameContainer.h"
using namespace mozilla;
using namespace mozilla::dom;
class MockMP4Decoder : public MP4Decoder
{
public:
MockMP4Decoder()
: MP4Decoder(new MockMediaDecoderOwner())
{}
// Avoid the assertion.
AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override
{
return nullptr;
}
};
class MediaFormatReaderBinding
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaFormatReaderBinding);
RefPtr<MockMP4Decoder> mDecoder;
RefPtr<MockMediaResource> mResource;
RefPtr<MediaDecoderReader> mReader;
RefPtr<TaskQueue> mTaskQueue;
explicit MediaFormatReaderBinding(const char* aFileName = "gizmo.mp4")
: mDecoder(new MockMP4Decoder())
, mResource(new MockMediaResource(aFileName))
, mReader(new MediaFormatReader(mDecoder,
new MP4Demuxer(mResource),
new VideoFrameContainer(
(HTMLMediaElement*)(0x1),
layers::LayerManager::CreateImageContainer(
layers::ImageContainer::ASYNCHRONOUS))
))
, mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK)))
{
}
bool Init() {
// Init Resource.
nsresult rv = mResource->Open(nullptr);
if (NS_FAILED(rv)) {
return false;
}
mDecoder->SetResource(mResource);
// Init Reader.
rv = mReader->Init();
if (NS_FAILED(rv)) {
return false;
}
return true;
}
void OnMetadataReadAudio(MetadataHolder* aMetadata)
{
EXPECT_TRUE(aMetadata);
mReader->RequestAudioData()
->Then(mReader->OwnerThread(), __func__, this,
&MediaFormatReaderBinding::OnAudioRawDataDemuxed,
&MediaFormatReaderBinding::OnNotDemuxed);
}
void OnMetadataReadVideo(MetadataHolder* aMetadata)
{
EXPECT_TRUE(aMetadata);
mReader->RequestVideoData(true, 0)
->Then(mReader->OwnerThread(), __func__, this,
&MediaFormatReaderBinding::OnVideoRawDataDemuxed,
&MediaFormatReaderBinding::OnNotDemuxed);
}
void OnMetadataNotRead(const MediaResult& aError) {
EXPECT_TRUE(false);
ReaderShutdown();
}
void OnAudioRawDataDemuxed(MediaData* aAudioSample)
{
EXPECT_TRUE(aAudioSample);
EXPECT_EQ(MediaData::RAW_DATA, aAudioSample->mType);
ReaderShutdown();
}
void OnVideoRawDataDemuxed(MediaData* aVideoSample)
{
EXPECT_TRUE(aVideoSample);
EXPECT_EQ(MediaData::RAW_DATA, aVideoSample->mType);
ReaderShutdown();
}
void OnNotDemuxed(const MediaResult& aReason)
{
EXPECT_TRUE(false);
ReaderShutdown();
}
void ReaderShutdown()
{
RefPtr<MediaFormatReaderBinding> self = this;
mReader->Shutdown()
->Then(mTaskQueue, __func__,
[self]() {
self->mTaskQueue->BeginShutdown();
},
[self]() {
EXPECT_TRUE(false);
self->mTaskQueue->BeginShutdown();
}); //Then
}
template<class Function>
void RunTestAndWait(Function&& aFunction)
{
RefPtr<Runnable> r = NS_NewRunnableFunction(Forward<Function>(aFunction));
mTaskQueue->Dispatch(r.forget());
mTaskQueue->AwaitShutdownAndIdle();
}
private:
~MediaFormatReaderBinding()
{
mDecoder->Shutdown();
}
};
template <typename T>
T GetPref(const char* aPrefKey);
template <>
bool GetPref<bool>(const char* aPrefKey)
{
return Preferences::GetBool(aPrefKey);
}
template <typename T>
void SetPref(const char* a, T value);
template <>
void SetPref<bool>(const char* aPrefKey, bool aValue)
{
Unused << Preferences::SetBool(aPrefKey, aValue);
}
template <typename T>
class PreferencesRAII
{
public:
explicit PreferencesRAII(const char* aPrefKey, T aValue)
: mPrefKey(aPrefKey)
{
mDefaultPref = GetPref<T>(aPrefKey);
SetPref(aPrefKey, aValue);
}
~PreferencesRAII()
{
SetPref(mPrefKey, mDefaultPref);
}
private:
T mDefaultPref;
const char* mPrefKey;
};
TEST(MediaFormatReader, RequestAudioRawData)
{
PreferencesRAII<bool> pref =
PreferencesRAII<bool>("media.use-blank-decoder",
true);
RefPtr<MediaFormatReaderBinding> b = new MediaFormatReaderBinding();
if (!b->Init())
{
EXPECT_TRUE(false);
// Stop the test since initialization failed.
return;
}
if (!b->mReader->IsDemuxOnlySupported())
{
EXPECT_TRUE(false);
// Stop the test since the reader cannot support demuxed-only demand.
return;
}
// Switch to demuxed-only mode.
b->mReader->SetDemuxOnly(true);
// To ensure the MediaDecoderReader::InitializationTask and
// MediaDecoderReader::SetDemuxOnly can be done.
NS_ProcessNextEvent();
auto testCase = [b]() {
InvokeAsync(b->mReader->OwnerThread(),
b->mReader.get(),
__func__,
&MediaDecoderReader::AsyncReadMetadata)
->Then(b->mReader->OwnerThread(), __func__, b.get(),
&MediaFormatReaderBinding::OnMetadataReadAudio,
&MediaFormatReaderBinding::OnMetadataNotRead);
};
b->RunTestAndWait(testCase);
}
TEST(MediaFormatReader, RequestVideoRawData)
{
PreferencesRAII<bool> pref =
PreferencesRAII<bool>("media.use-blank-decoder",
true);
RefPtr<MediaFormatReaderBinding> b = new MediaFormatReaderBinding();
if (!b->Init())
{
EXPECT_TRUE(false);
// Stop the test since initialization failed.
return;
}
if (!b->mReader->IsDemuxOnlySupported())
{
EXPECT_TRUE(false);
// Stop the test since the reader cannot support demuxed-only demand.
return;
}
// Switch to demuxed-only mode.
b->mReader->SetDemuxOnly(true);
// To ensure the MediaDecoderReader::InitializationTask and
// MediaDecoderReader::SetDemuxOnly can be done.
NS_ProcessNextEvent();
auto testCase = [b]() {
InvokeAsync(b->mReader->OwnerThread(),
b->mReader.get(),
__func__,
&MediaDecoderReader::AsyncReadMetadata)
->Then(b->mReader->OwnerThread(), __func__, b.get(),
&MediaFormatReaderBinding::OnMetadataReadVideo,
&MediaFormatReaderBinding::OnMetadataNotRead);
};
b->RunTestAndWait(testCase);
}

View file

@ -0,0 +1,257 @@
/* -*- 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/TaskQueue.h"
#include "mozilla/MozPromise.h"
#include "nsISupportsImpl.h"
#include "mozilla/SharedThreadPool.h"
#include "VideoUtils.h"
using namespace mozilla;
typedef MozPromise<int, double, false> TestPromise;
typedef TestPromise::ResolveOrRejectValue RRValue;
class MOZ_STACK_CLASS AutoTaskQueue
{
public:
AutoTaskQueue()
: mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK)))
{}
~AutoTaskQueue()
{
mTaskQueue->AwaitShutdownAndIdle();
}
TaskQueue* Queue() { return mTaskQueue; }
private:
RefPtr<TaskQueue> mTaskQueue;
};
class DelayedResolveOrReject : public Runnable
{
public:
DelayedResolveOrReject(TaskQueue* aTaskQueue,
TestPromise::Private* aPromise,
TestPromise::ResolveOrRejectValue aValue,
int aIterations)
: mTaskQueue(aTaskQueue)
, mPromise(aPromise)
, mValue(aValue)
, mIterations(aIterations)
{}
NS_IMETHOD Run() override
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
if (!mPromise) {
// Canceled.
return NS_OK;
}
if (--mIterations == 0) {
mPromise->ResolveOrReject(mValue, __func__);
} else {
nsCOMPtr<nsIRunnable> r = this;
mTaskQueue->Dispatch(r.forget());
}
return NS_OK;
}
void Cancel() {
mPromise = nullptr;
}
protected:
~DelayedResolveOrReject() {}
private:
RefPtr<TaskQueue> mTaskQueue;
RefPtr<TestPromise::Private> mPromise;
TestPromise::ResolveOrRejectValue mValue;
int mIterations;
};
template<typename FunctionType>
void
RunOnTaskQueue(TaskQueue* aQueue, FunctionType aFun)
{
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(aFun);
aQueue->Dispatch(r.forget());
}
// std::function can't come soon enough. :-(
#define DO_FAIL []()->void { EXPECT_TRUE(false); }
TEST(MozPromise, BasicResolve)
{
AutoTaskQueue atq;
RefPtr<TaskQueue> queue = atq.Queue();
RunOnTaskQueue(queue, [queue] () -> void {
TestPromise::CreateAndResolve(42, __func__)->Then(queue, __func__,
[queue] (int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); queue->BeginShutdown(); },
DO_FAIL);
});
}
TEST(MozPromise, BasicReject)
{
AutoTaskQueue atq;
RefPtr<TaskQueue> queue = atq.Queue();
RunOnTaskQueue(queue, [queue] () -> void {
TestPromise::CreateAndReject(42.0, __func__)->Then(queue, __func__,
DO_FAIL,
[queue] (int aRejectValue) -> void { EXPECT_EQ(aRejectValue, 42.0); queue->BeginShutdown(); });
});
}
TEST(MozPromise, AsyncResolve)
{
AutoTaskQueue atq;
RefPtr<TaskQueue> queue = atq.Queue();
RunOnTaskQueue(queue, [queue] () -> void {
RefPtr<TestPromise::Private> p = new TestPromise::Private(__func__);
// Kick off three racing tasks, and make sure we get the one that finishes earliest.
RefPtr<DelayedResolveOrReject> a = new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(32), 10);
RefPtr<DelayedResolveOrReject> b = new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(42), 5);
RefPtr<DelayedResolveOrReject> c = new DelayedResolveOrReject(queue, p, RRValue::MakeReject(32.0), 7);
nsCOMPtr<nsIRunnable> ref = a.get();
queue->Dispatch(ref.forget());
ref = b.get();
queue->Dispatch(ref.forget());
ref = c.get();
queue->Dispatch(ref.forget());
p->Then(queue, __func__, [queue, a, b, c] (int aResolveValue) -> void {
EXPECT_EQ(aResolveValue, 42);
a->Cancel();
b->Cancel();
c->Cancel();
queue->BeginShutdown();
}, DO_FAIL);
});
}
TEST(MozPromise, CompletionPromises)
{
bool invokedPass = false;
AutoTaskQueue atq;
RefPtr<TaskQueue> queue = atq.Queue();
RunOnTaskQueue(queue, [queue, &invokedPass] () -> void {
TestPromise::CreateAndResolve(40, __func__)
->Then(queue, __func__,
[] (int aVal) -> RefPtr<TestPromise> { return TestPromise::CreateAndResolve(aVal + 10, __func__); },
DO_FAIL)
->CompletionPromise()
->Then(queue, __func__, [&invokedPass] () -> void { invokedPass = true; }, DO_FAIL)
->CompletionPromise()
->Then(queue, __func__,
[queue] (int aVal) -> RefPtr<TestPromise> {
RefPtr<TestPromise::Private> p = new TestPromise::Private(__func__);
nsCOMPtr<nsIRunnable> resolver = new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(aVal - 8), 10);
queue->Dispatch(resolver.forget());
return RefPtr<TestPromise>(p);
},
DO_FAIL)
->CompletionPromise()
->Then(queue, __func__,
[queue] (int aVal) -> RefPtr<TestPromise> { return TestPromise::CreateAndReject(double(aVal - 42) + 42.0, __func__); },
DO_FAIL)
->CompletionPromise()
->Then(queue, __func__,
DO_FAIL,
[queue, &invokedPass] (double aVal) -> void { EXPECT_EQ(aVal, 42.0); EXPECT_TRUE(invokedPass); queue->BeginShutdown(); });
});
}
TEST(MozPromise, PromiseAllResolve)
{
AutoTaskQueue atq;
RefPtr<TaskQueue> queue = atq.Queue();
RunOnTaskQueue(queue, [queue] () -> void {
nsTArray<RefPtr<TestPromise>> promises;
promises.AppendElement(TestPromise::CreateAndResolve(22, __func__));
promises.AppendElement(TestPromise::CreateAndResolve(32, __func__));
promises.AppendElement(TestPromise::CreateAndResolve(42, __func__));
TestPromise::All(queue, promises)->Then(queue, __func__,
[queue] (const nsTArray<int>& aResolveValues) -> void {
EXPECT_EQ(aResolveValues.Length(), 3UL);
EXPECT_EQ(aResolveValues[0], 22);
EXPECT_EQ(aResolveValues[1], 32);
EXPECT_EQ(aResolveValues[2], 42);
queue->BeginShutdown();
},
DO_FAIL
);
});
}
TEST(MozPromise, PromiseAllReject)
{
AutoTaskQueue atq;
RefPtr<TaskQueue> queue = atq.Queue();
RunOnTaskQueue(queue, [queue] () -> void {
nsTArray<RefPtr<TestPromise>> promises;
promises.AppendElement(TestPromise::CreateAndResolve(22, __func__));
promises.AppendElement(TestPromise::CreateAndReject(32.0, __func__));
promises.AppendElement(TestPromise::CreateAndResolve(42, __func__));
// Ensure that more than one rejection doesn't cause a crash (bug #1207312)
promises.AppendElement(TestPromise::CreateAndReject(52.0, __func__));
TestPromise::All(queue, promises)->Then(queue, __func__,
DO_FAIL,
[queue] (float aRejectValue) -> void {
EXPECT_EQ(aRejectValue, 32.0);
queue->BeginShutdown();
}
);
});
}
// Test we don't hit the assertions in MozPromise when exercising promise
// chaining upon task queue shutdown.
TEST(MozPromise, Chaining)
{
AutoTaskQueue atq;
RefPtr<TaskQueue> queue = atq.Queue();
MozPromiseRequestHolder<TestPromise> holder;
RunOnTaskQueue(queue, [queue, &holder] () {
auto p = TestPromise::CreateAndResolve(42, __func__);
const size_t kIterations = 100;
for (size_t i = 0; i < kIterations; ++i) {
p = p->Then(queue, __func__,
[] (int aVal) {
EXPECT_EQ(aVal, 42);
},
[] () {}
)->CompletionPromise();
if (i == kIterations / 2) {
p->Then(queue, __func__,
[queue, &holder] () {
holder.Disconnect();
queue->BeginShutdown();
},
DO_FAIL);
}
}
// We will hit the assertion if we don't disconnect the leaf Request
// in the promise chain.
holder.Begin(p->Then(queue, __func__, [] () {}, [] () {}));
});
}
#undef DO_FAIL

View file

@ -0,0 +1,9 @@
#include <stdint.h>
#include "gtest/gtest.h"
extern "C" uint8_t* test_rust();
TEST(rust, CallFromCpp) {
auto greeting = test_rust();
EXPECT_STREQ(reinterpret_cast<char*>(greeting), "hello from rust.");
}

View file

@ -0,0 +1,22 @@
/* -*- 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 "TimeUnits.h"
#include <algorithm>
#include <vector>
using namespace mozilla;
TEST(TimeUnit, Rounding)
{
int64_t usecs = 66261715;
double seconds = media::TimeUnit::FromMicroseconds(usecs).ToSeconds();
EXPECT_EQ(media::TimeUnit::FromSeconds(seconds).ToMicroseconds(), usecs);
seconds = 4.169470;
usecs = 4169470;
EXPECT_EQ(media::TimeUnit::FromSeconds(seconds).ToMicroseconds(), usecs);
}

View file

@ -0,0 +1,94 @@
/* -*- 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 "OpusTrackEncoder.h"
using namespace mozilla;
class TestOpusTrackEncoder : public OpusTrackEncoder
{
public:
// Return true if it has successfully initialized the Opus encoder.
bool TestOpusCreation(int aChannels, int aSamplingRate)
{
if (Init(aChannels, aSamplingRate) == NS_OK) {
if (GetPacketDuration()) {
return true;
}
}
return false;
}
// Return the sample rate of data to be fed to the Opus encoder, could be
// re-sampled if it was not one of the Opus supported sampling rates.
// Init() is expected to be called first.
int TestGetOutputSampleRate()
{
return mInitialized ? GetOutputSampleRate() : 0;
}
};
static bool
TestOpusInit(int aChannels, int aSamplingRate)
{
TestOpusTrackEncoder encoder;
return encoder.TestOpusCreation(aChannels, aSamplingRate);
}
static int
TestOpusResampler(int aChannels, int aSamplingRate)
{
TestOpusTrackEncoder encoder;
EXPECT_TRUE(encoder.TestOpusCreation(aChannels, aSamplingRate));
return encoder.TestGetOutputSampleRate();
}
TEST(Media, OpusEncoder_Init)
{
// Expect false with 0 or negative channels of input signal.
EXPECT_FALSE(TestOpusInit(0, 16000));
EXPECT_FALSE(TestOpusInit(-1, 16000));
// The Opus format supports up to 8 channels, and supports multitrack audio up
// to 255 channels, but the current implementation supports only mono and
// stereo, and downmixes any more than that.
// Expect false with channels of input signal exceed the max supported number.
EXPECT_FALSE(TestOpusInit(8 + 1, 16000));
// Should accept channels within valid range.
for (int i = 1; i <= 8; i++) {
EXPECT_TRUE(TestOpusInit(i, 16000));
}
// Expect false with 0 or negative sampling rate of input signal.
EXPECT_FALSE(TestOpusInit(1, 0));
EXPECT_FALSE(TestOpusInit(1, -1));
// Verify sample rate bounds checking.
EXPECT_FALSE(TestOpusInit(2, 2000));
EXPECT_FALSE(TestOpusInit(2, 4000));
EXPECT_FALSE(TestOpusInit(2, 7999));
EXPECT_TRUE(TestOpusInit(2, 8000));
EXPECT_TRUE(TestOpusInit(2, 192000));
EXPECT_FALSE(TestOpusInit(2, 192001));
EXPECT_FALSE(TestOpusInit(2, 200000));
}
TEST(Media, OpusEncoder_Resample)
{
// Sampling rates of data to be fed to Opus encoder, should remain unchanged
// if it is one of Opus supported rates (8000, 12000, 16000, 24000 and 48000
// (kHz)) at initialization.
EXPECT_TRUE(TestOpusResampler(1, 8000) == 8000);
EXPECT_TRUE(TestOpusResampler(1, 12000) == 12000);
EXPECT_TRUE(TestOpusResampler(1, 16000) == 16000);
EXPECT_TRUE(TestOpusResampler(1, 24000) == 24000);
EXPECT_TRUE(TestOpusResampler(1, 48000) == 48000);
// Otherwise, it should be resampled to 48kHz by resampler.
EXPECT_FALSE(TestOpusResampler(1, 9600) == 9600);
EXPECT_FALSE(TestOpusResampler(1, 44100) == 44100);
}

View file

@ -0,0 +1,103 @@
/* 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/ArrayUtils.h"
#include "nsTArray.h"
#include "VPXDecoder.h"
#include <stdio.h>
using namespace mozilla;
static void
ReadVPXFile(const char* aPath, nsTArray<uint8_t>& aBuffer)
{
FILE* f = fopen(aPath, "rb");
ASSERT_NE(f, (FILE *) nullptr);
int r = fseek(f, 0, SEEK_END);
ASSERT_EQ(r, 0);
long size = ftell(f);
ASSERT_NE(size, -1);
aBuffer.SetLength(size);
r = fseek(f, 0, SEEK_SET);
ASSERT_EQ(r, 0);
size_t got = fread(aBuffer.Elements(), 1, size, f);
ASSERT_EQ(got, size_t(size));
r = fclose(f);
ASSERT_EQ(r, 0);
}
static
vpx_codec_iface_t*
ParseIVFConfig(nsTArray<uint8_t>& data, vpx_codec_dec_cfg_t& config)
{
if (data.Length() < 32 + 12) {
// Not enough data for file & first frame headers.
return nullptr;
}
if (data[0] != 'D' || data[1] != 'K' || data[2] != 'I' || data[3] != 'F') {
// Expect 'DKIP'
return nullptr;
}
if (data[4] != 0 || data[5] != 0) {
// Expect version==0.
return nullptr;
}
if (data[8] != 'V' || data[9] != 'P'
|| (data[10] != '8' && data[10] != '9')
|| data[11] != '0') {
// Expect 'VP80' or 'VP90'.
return nullptr;
}
config.w = uint32_t(data[12]) || (uint32_t(data[13]) << 8);
config.h = uint32_t(data[14]) || (uint32_t(data[15]) << 8);
vpx_codec_iface_t* codec = (data[10] == '8')
? vpx_codec_vp8_dx()
: vpx_codec_vp9_dx();
// Remove headers, to just leave raw VPx data to be decoded.
data.RemoveElementsAt(0, 32 + 12);
return codec;
}
struct TestFileData {
const char* mFilename;
vpx_codec_err_t mDecodeResult;
};
static const TestFileData testFiles[] = {
{ "test_case_1224361.vp8.ivf", VPX_CODEC_OK },
{ "test_case_1224363.vp8.ivf", VPX_CODEC_CORRUPT_FRAME },
{ "test_case_1224369.vp8.ivf", VPX_CODEC_CORRUPT_FRAME }
};
TEST(libvpx, test_cases)
{
for (size_t test = 0; test < ArrayLength(testFiles); ++test) {
nsTArray<uint8_t> data;
ReadVPXFile(testFiles[test].mFilename, data);
ASSERT_GT(data.Length(), 0u);
vpx_codec_dec_cfg_t config;
vpx_codec_iface_t* dx = ParseIVFConfig(data, config);
ASSERT_TRUE(dx);
config.threads = 2;
vpx_codec_ctx_t ctx;
PodZero(&ctx);
vpx_codec_err_t r = vpx_codec_dec_init(&ctx, dx, &config, 0);
ASSERT_EQ(VPX_CODEC_OK, r);
r = vpx_codec_decode(&ctx, data.Elements(), data.Length(), nullptr, 0);
// This test case is known to be corrupt.
EXPECT_EQ(testFiles[test].mDecodeResult, r);
r = vpx_codec_destroy(&ctx);
EXPECT_EQ(VPX_CODEC_OK, r);
}
}

View file

@ -0,0 +1,51 @@
/* 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 "VideoSegment.h"
using namespace mozilla;
namespace mozilla {
namespace layer {
class Image;
} // namespace layer
} // namespace mozilla
TEST(VideoSegment, TestAppendFrameForceBlack)
{
RefPtr<layers::Image> testImage = nullptr;
VideoSegment segment;
segment.AppendFrame(testImage.forget(),
mozilla::StreamTime(90000),
mozilla::gfx::IntSize(640, 480),
PRINCIPAL_HANDLE_NONE,
true);
VideoSegment::ChunkIterator iter(segment);
while (!iter.IsEnded()) {
VideoChunk chunk = *iter;
EXPECT_TRUE(chunk.mFrame.GetForceBlack());
iter.Next();
}
}
TEST(VideoSegment, TestAppendFrameNotForceBlack)
{
RefPtr<layers::Image> testImage = nullptr;
VideoSegment segment;
segment.AppendFrame(testImage.forget(),
mozilla::StreamTime(90000),
mozilla::gfx::IntSize(640, 480),
PRINCIPAL_HANDLE_NONE);
VideoSegment::ChunkIterator iter(segment);
while (!iter.IsEnded()) {
VideoChunk chunk = *iter;
EXPECT_FALSE(chunk.mFrame.GetForceBlack());
iter.Next();
}
}

View file

@ -0,0 +1,351 @@
/* 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 <algorithm>
#include "mozilla/ArrayUtils.h"
#include "VP8TrackEncoder.h"
#include "ImageContainer.h"
#include "MediaStreamGraph.h"
#include "MediaStreamListener.h"
#include "WebMWriter.h" // TODO: it's weird to include muxer header to get the class definition of VP8 METADATA
using ::testing::TestWithParam;
using ::testing::Values;
using namespace mozilla::layers;
using namespace mozilla;
// A helper object to generate of different YUV planes.
class YUVBufferGenerator {
public:
YUVBufferGenerator() {}
void Init(const mozilla::gfx::IntSize &aSize)
{
mImageSize = aSize;
int yPlaneLen = aSize.width * aSize.height;
int cbcrPlaneLen = (yPlaneLen + 1) / 2;
int frameLen = yPlaneLen + cbcrPlaneLen;
// Generate source buffer.
mSourceBuffer.SetLength(frameLen);
// Fill Y plane.
memset(mSourceBuffer.Elements(), 0x10, yPlaneLen);
// Fill Cb/Cr planes.
memset(mSourceBuffer.Elements() + yPlaneLen, 0x80, cbcrPlaneLen);
}
mozilla::gfx::IntSize GetSize() const
{
return mImageSize;
}
void Generate(nsTArray<RefPtr<Image> > &aImages)
{
aImages.AppendElement(CreateI420Image());
aImages.AppendElement(CreateNV12Image());
aImages.AppendElement(CreateNV21Image());
}
private:
Image *CreateI420Image()
{
PlanarYCbCrImage *image = new RecyclingPlanarYCbCrImage(new BufferRecycleBin());
PlanarYCbCrData data;
data.mPicSize = mImageSize;
const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
const uint32_t halfWidth = (mImageSize.width + 1) / 2;
const uint32_t halfHeight = (mImageSize.height + 1) / 2;
const uint32_t uvPlaneSize = halfWidth * halfHeight;
// Y plane.
uint8_t *y = mSourceBuffer.Elements();
data.mYChannel = y;
data.mYSize.width = mImageSize.width;
data.mYSize.height = mImageSize.height;
data.mYStride = mImageSize.width;
data.mYSkip = 0;
// Cr plane.
uint8_t *cr = y + yPlaneSize + uvPlaneSize;
data.mCrChannel = cr;
data.mCrSkip = 0;
// Cb plane
uint8_t *cb = y + yPlaneSize;
data.mCbChannel = cb;
data.mCbSkip = 0;
// CrCb plane vectors.
data.mCbCrStride = halfWidth;
data.mCbCrSize.width = halfWidth;
data.mCbCrSize.height = halfHeight;
image->CopyData(data);
return image;
}
Image *CreateNV12Image()
{
PlanarYCbCrImage *image = new RecyclingPlanarYCbCrImage(new BufferRecycleBin());
PlanarYCbCrData data;
data.mPicSize = mImageSize;
const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
const uint32_t halfWidth = (mImageSize.width + 1) / 2;
const uint32_t halfHeight = (mImageSize.height + 1) / 2;
// Y plane.
uint8_t *y = mSourceBuffer.Elements();
data.mYChannel = y;
data.mYSize.width = mImageSize.width;
data.mYSize.height = mImageSize.height;
data.mYStride = mImageSize.width;
data.mYSkip = 0;
// Cr plane.
uint8_t *cr = y + yPlaneSize;
data.mCrChannel = cr;
data.mCrSkip = 1;
// Cb plane
uint8_t *cb = y + yPlaneSize + 1;
data.mCbChannel = cb;
data.mCbSkip = 1;
// 4:2:0.
data.mCbCrStride = mImageSize.width;
data.mCbCrSize.width = halfWidth;
data.mCbCrSize.height = halfHeight;
image->CopyData(data);
return image;
}
Image *CreateNV21Image()
{
PlanarYCbCrImage *image = new RecyclingPlanarYCbCrImage(new BufferRecycleBin());
PlanarYCbCrData data;
data.mPicSize = mImageSize;
const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
const uint32_t halfWidth = (mImageSize.width + 1) / 2;
const uint32_t halfHeight = (mImageSize.height + 1) / 2;
// Y plane.
uint8_t *y = mSourceBuffer.Elements();
data.mYChannel = y;
data.mYSize.width = mImageSize.width;
data.mYSize.height = mImageSize.height;
data.mYStride = mImageSize.width;
data.mYSkip = 0;
// Cr plane.
uint8_t *cr = y + yPlaneSize + 1;
data.mCrChannel = cr;
data.mCrSkip = 1;
// Cb plane
uint8_t *cb = y + yPlaneSize;
data.mCbChannel = cb;
data.mCbSkip = 1;
// 4:2:0.
data.mCbCrStride = mImageSize.width;
data.mCbCrSize.width = halfWidth;
data.mCbCrSize.height = halfHeight;
image->CopyData(data);
return image;
}
private:
mozilla::gfx::IntSize mImageSize;
nsTArray<uint8_t> mSourceBuffer;
};
struct InitParam {
bool mShouldSucceed; // This parameter should cause success or fail result
int mWidth; // frame width
int mHeight; // frame height
};
class TestVP8TrackEncoder: public VP8TrackEncoder
{
public:
explicit TestVP8TrackEncoder(TrackRate aTrackRate = 90000)
: VP8TrackEncoder(aTrackRate) {}
::testing::AssertionResult TestInit(const InitParam &aParam)
{
nsresult result = Init(aParam.mWidth, aParam.mHeight, aParam.mWidth, aParam.mHeight);
if (((NS_FAILED(result) && aParam.mShouldSucceed)) || (NS_SUCCEEDED(result) && !aParam.mShouldSucceed))
{
return ::testing::AssertionFailure()
<< " width = " << aParam.mWidth
<< " height = " << aParam.mHeight;
}
else
{
return ::testing::AssertionSuccess();
}
}
};
// Init test
TEST(VP8VideoTrackEncoder, Initialization)
{
InitParam params[] = {
// Failure cases.
{ false, 0, 0}, // Height/ width should be larger than 1.
{ false, 0, 1}, // Height/ width should be larger than 1.
{ false, 1, 0}, // Height/ width should be larger than 1.
// Success cases
{ true, 640, 480}, // Standard VGA
{ true, 800, 480}, // Standard WVGA
{ true, 960, 540}, // Standard qHD
{ true, 1280, 720} // Standard HD
};
for (size_t i = 0; i < ArrayLength(params); i++)
{
TestVP8TrackEncoder encoder;
EXPECT_TRUE(encoder.TestInit(params[i]));
}
}
// Get MetaData test
TEST(VP8VideoTrackEncoder, FetchMetaData)
{
InitParam params[] = {
// Success cases
{ true, 640, 480}, // Standard VGA
{ true, 800, 480}, // Standard WVGA
{ true, 960, 540}, // Standard qHD
{ true, 1280, 720} // Standard HD
};
for (size_t i = 0; i < ArrayLength(params); i++)
{
TestVP8TrackEncoder encoder;
EXPECT_TRUE(encoder.TestInit(params[i]));
RefPtr<TrackMetadataBase> meta = encoder.GetMetadata();
RefPtr<VP8Metadata> vp8Meta(static_cast<VP8Metadata*>(meta.get()));
// METADATA should be depend on how to initiate encoder.
EXPECT_TRUE(vp8Meta->mWidth == params[i].mWidth);
EXPECT_TRUE(vp8Meta->mHeight == params[i].mHeight);
}
}
// Encode test
TEST(VP8VideoTrackEncoder, FrameEncode)
{
// Initiate VP8 encoder
TestVP8TrackEncoder encoder;
InitParam param = {true, 640, 480};
encoder.TestInit(param);
// Create YUV images as source.
nsTArray<RefPtr<Image>> images;
YUVBufferGenerator generator;
generator.Init(mozilla::gfx::IntSize(640, 480));
generator.Generate(images);
// Put generated YUV frame into video segment.
// Duration of each frame is 1 second.
VideoSegment segment;
for (nsTArray<RefPtr<Image>>::size_type i = 0; i < images.Length(); i++)
{
RefPtr<Image> image = images[i];
segment.AppendFrame(image.forget(),
mozilla::StreamTime(90000),
generator.GetSize(),
PRINCIPAL_HANDLE_NONE);
}
// track change notification.
encoder.SetCurrentFrames(segment);
// Pull Encoded Data back from encoder.
EncodedFrameContainer container;
EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
}
// Test encoding a track that has to skip frames.
TEST(VP8VideoTrackEncoder, SkippedFrames)
{
// Initiate VP8 encoder
TestVP8TrackEncoder encoder;
InitParam param = {true, 640, 480};
encoder.TestInit(param);
nsTArray<RefPtr<Image>> images;
YUVBufferGenerator generator;
generator.Init(mozilla::gfx::IntSize(640, 480));
TimeStamp now = TimeStamp::Now();
VideoSegment segment;
while (images.Length() < 100) {
generator.Generate(images);
}
// Pass 100 frames of the shortest possible duration where we don't get
// rounding errors between input/output rate.
for (uint32_t i = 0; i < 100; ++i) {
segment.AppendFrame(images[i].forget(),
mozilla::StreamTime(90), // 1ms
generator.GetSize(),
PRINCIPAL_HANDLE_NONE,
false,
now + TimeDuration::FromMilliseconds(i));
}
encoder.SetCurrentFrames(segment);
// End the track.
segment.Clear();
encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, TrackEventCommand::TRACK_EVENT_ENDED, segment);
EncodedFrameContainer container;
ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
EXPECT_TRUE(encoder.IsEncodingComplete());
// Verify total duration being 100 * 1ms = 100ms in terms of 30fps frame
// durations (3 * 1/30s).
uint64_t totalDuration = 0;
for (auto& frame : container.GetEncodedFrames()) {
totalDuration += frame->GetDuration();
}
const uint64_t threeFrames = (PR_USEC_PER_SEC / 30) * 3;
EXPECT_EQ(threeFrames, totalDuration);
}
// EOS test
TEST(VP8VideoTrackEncoder, EncodeComplete)
{
// Initiate VP8 encoder
TestVP8TrackEncoder encoder;
InitParam param = {true, 640, 480};
encoder.TestInit(param);
// track end notification.
VideoSegment segment;
encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, TrackEventCommand::TRACK_EVENT_ENDED, segment);
// Pull Encoded Data back from encoder. Since we have sent
// EOS to encoder, encoder.GetEncodedTrack should return
// NS_OK immidiately.
EncodedFrameContainer container;
EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
}

View file

@ -0,0 +1,80 @@
/* -*- 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 "nsString.h"
#include "VideoUtils.h"
using namespace mozilla;
TEST(StringListRange, MakeStringListRange)
{
static const struct
{
const char* mList;
const char* mExpected;
} tests[] =
{
{ "", "" },
{ " ", "" },
{ ",", "" },
{ " , ", "" },
{ "a", "a|" },
{ " a ", "a|" },
{ "aa,bb", "aa|bb|" },
{ " a a , b b ", "a a|b b|" },
{ " , ,a 1,, ,b 2,", "a 1|b 2|" }
};
for (const auto& test : tests) {
nsCString list(test.mList);
nsCString out;
for (const auto& item : MakeStringListRange(list)) {
out += item;
out += "|";
}
EXPECT_STREQ(test.mExpected, out.Data());
}
}
TEST(StringListRange, StringListContains)
{
static const struct
{
const char* mList;
const char* mItemToSearch;
bool mExpected;
} tests[] =
{
{ "", "", false },
{ "", "a", false },
{ " ", "a", false },
{ ",", "a", false },
{ " , ", "", false },
{ " , ", "a", false },
{ "a", "a", true },
{ "a", "b", false },
{ " a ", "a", true },
{ "aa,bb", "aa", true },
{ "aa,bb", "bb", true },
{ "aa,bb", "cc", false },
{ "aa,bb", " aa ", false },
{ " a a , b b ", "a a", true },
{ " , ,a 1,, ,b 2,", "a 1", true },
{ " , ,a 1,, ,b 2,", "b 2", true },
{ " , ,a 1,, ,b 2,", "", false },
{ " , ,a 1,, ,b 2,", " ", false },
{ " , ,a 1,, ,b 2,", "A 1", false },
{ " , ,A 1,, ,b 2,", "a 1", false }
};
for (const auto& test : tests) {
nsCString list(test.mList);
nsCString itemToSearch(test.mItemToSearch);
EXPECT_EQ(test.mExpected, StringListContains(list, itemToSearch))
<< "trying to find \"" << itemToSearch.Data()
<< "\" in \"" << list.Data() << "\"";
}
}

View file

@ -0,0 +1,119 @@
/* 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/ArrayUtils.h"
#include <stdio.h>
#include "nsTArray.h"
#include "WebMBufferedParser.h"
using namespace mozilla;
// "test.webm" contains 8 SimpleBlocks in a single Cluster. The blocks with
// timecodes 100000000 and are 133000000 skipped by WebMBufferedParser
// because they occur after a block with timecode 160000000 and the parser
// expects in-order timecodes per the WebM spec. The remaining 6
// SimpleBlocks have the following attributes:
static const uint64_t gTimecodes[] = { 66000000, 160000000, 166000000, 200000000, 233000000, 320000000 };
static const int64_t gEndOffsets[] = { 501, 772, 1244, 1380, 1543, 2015 };
TEST(WebMBuffered, BasicTests)
{
ReentrantMonitor dummy("dummy");
WebMBufferedParser parser(0);
nsTArray<WebMTimeDataOffset> mapping;
parser.Append(nullptr, 0, mapping, dummy);
EXPECT_TRUE(mapping.IsEmpty());
EXPECT_EQ(parser.mStartOffset, 0);
EXPECT_EQ(parser.mCurrentOffset, 0);
unsigned char buf[] = { 0x1a, 0x45, 0xdf, 0xa3 };
parser.Append(buf, ArrayLength(buf), mapping, dummy);
EXPECT_TRUE(mapping.IsEmpty());
EXPECT_EQ(parser.mStartOffset, 0);
EXPECT_EQ(parser.mCurrentOffset, 4);
}
static void
ReadFile(const char* aPath, nsTArray<uint8_t>& aBuffer)
{
FILE* f = fopen(aPath, "rb");
ASSERT_NE(f, (FILE *) nullptr);
int r = fseek(f, 0, SEEK_END);
ASSERT_EQ(r, 0);
long size = ftell(f);
ASSERT_NE(size, -1);
aBuffer.SetLength(size);
r = fseek(f, 0, SEEK_SET);
ASSERT_EQ(r, 0);
size_t got = fread(aBuffer.Elements(), 1, size, f);
ASSERT_EQ(got, size_t(size));
r = fclose(f);
ASSERT_EQ(r, 0);
}
TEST(WebMBuffered, RealData)
{
ReentrantMonitor dummy("dummy");
WebMBufferedParser parser(0);
nsTArray<uint8_t> webmData;
ReadFile("test.webm", webmData);
nsTArray<WebMTimeDataOffset> mapping;
parser.Append(webmData.Elements(), webmData.Length(), mapping, dummy);
EXPECT_EQ(mapping.Length(), 6u);
EXPECT_EQ(parser.mStartOffset, 0);
EXPECT_EQ(parser.mCurrentOffset, int64_t(webmData.Length()));
EXPECT_EQ(parser.GetTimecodeScale(), 500000u);
for (uint32_t i = 0; i < mapping.Length(); ++i) {
EXPECT_EQ(mapping[i].mEndOffset, gEndOffsets[i]);
EXPECT_EQ(mapping[i].mSyncOffset, 361);
EXPECT_EQ(mapping[i].mTimecode, gTimecodes[i]);
}
}
TEST(WebMBuffered, RealDataAppend)
{
ReentrantMonitor dummy("dummy");
WebMBufferedParser parser(0);
nsTArray<WebMTimeDataOffset> mapping;
nsTArray<uint8_t> webmData;
ReadFile("test.webm", webmData);
uint32_t arrayEntries = mapping.Length();
size_t offset = 0;
while (offset < webmData.Length()) {
parser.Append(webmData.Elements() + offset, 1, mapping, dummy);
offset += 1;
EXPECT_EQ(parser.mCurrentOffset, int64_t(offset));
if (mapping.Length() != arrayEntries) {
arrayEntries = mapping.Length();
ASSERT_LE(arrayEntries, 6u);
uint32_t i = arrayEntries - 1;
EXPECT_EQ(mapping[i].mEndOffset, gEndOffsets[i]);
EXPECT_EQ(mapping[i].mSyncOffset, 361);
EXPECT_EQ(mapping[i].mTimecode, gTimecodes[i]);
EXPECT_EQ(parser.GetTimecodeScale(), 500000u);
}
}
EXPECT_EQ(mapping.Length(), 6u);
EXPECT_EQ(parser.mStartOffset, 0);
EXPECT_EQ(parser.mCurrentOffset, int64_t(webmData.Length()));
EXPECT_EQ(parser.GetTimecodeScale(), 500000u);
for (uint32_t i = 0; i < mapping.Length(); ++i) {
EXPECT_EQ(mapping[i].mEndOffset, gEndOffsets[i]);
EXPECT_EQ(mapping[i].mSyncOffset, 361);
EXPECT_EQ(mapping[i].mTimecode, gTimecodes[i]);
}
}

View file

@ -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, &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>(sampleRate), params.rate);
}
}
if (context) {
nestegg_destroy(context);
}
}

Binary file not shown.

6
dom/media/gtest/hello.rs Normal file
View file

@ -0,0 +1,6 @@
#[no_mangle]
pub extern fn test_rust() -> *const u8 {
// NB: rust &str aren't null terminated.
let greeting = "hello from rust.\0";
greeting.as_ptr()
}

Binary file not shown.

Binary file not shown.

80
dom/media/gtest/moz.build Normal file
View file

@ -0,0 +1,80 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
UNIFIED_SOURCES += [
'MockMediaResource.cpp',
'TestAudioBuffers.cpp',
'TestAudioCompactor.cpp',
'TestAudioMixer.cpp',
'TestAudioPacketizer.cpp',
'TestAudioSegment.cpp',
'TestGMPCrossOrigin.cpp',
'TestGMPRemoveAndDelete.cpp',
'TestGMPUtils.cpp',
'TestIntervalSet.cpp',
'TestMediaDataDecoder.cpp',
'TestMediaEventSource.cpp',
'TestMediaFormatReader.cpp',
'TestMozPromise.cpp',
'TestMP3Demuxer.cpp',
'TestMP4Demuxer.cpp',
# 'TestMP4Reader.cpp', disabled so we can turn check tests back on (bug 1175752)
'TestTrackEncoder.cpp',
'TestVideoSegment.cpp',
'TestVideoUtils.cpp',
'TestVPXDecoding.cpp',
'TestWebMBuffered.cpp',
]
if CONFIG['MOZ_WEBM_ENCODER']:
UNIFIED_SOURCES += [
'TestVideoTrackEncoder.cpp',
'TestWebMWriter.cpp',
]
if CONFIG['MOZ_RUST']:
UNIFIED_SOURCES += ['TestRust.cpp',]
TEST_HARNESS_FILES.gtest += [
'../test/gizmo-frag.mp4',
'../test/gizmo.mp4',
'../test/vp9cake.webm',
'dash_dashinit.mp4',
'id3v2header.mp3',
'mediasource_test.mp4',
'negative_duration.mp4',
'noise.mp3',
'noise_vbr.mp3',
'short-zero-in-moov.mp4',
'short-zero-inband.mov',
'small-shot-false-positive.mp3',
'small-shot.mp3',
'test.webm',
'test_case_1224361.vp8.ivf',
'test_case_1224363.vp8.ivf',
'test_case_1224369.vp8.ivf',
]
include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
'/dom/media',
'/dom/media/encoder',
'/dom/media/fmp4',
'/dom/media/gmp',
'/security/certverifier',
'/security/pkix/include',
]
FINAL_LIBRARY = 'xul-gtest'
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']
USE_LIBS += [
'rlz',
]

Binary file not shown.

BIN
dom/media/gtest/noise.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dom/media/gtest/test.webm Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.