mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-08 16:27:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
7
dom/media/gtest/Cargo.toml
Normal file
7
dom/media/gtest/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "mp4parse-gtest"
|
||||
version = "0.1.0"
|
||||
authors = ["nobody@mozilla.org"]
|
||||
|
||||
[lib]
|
||||
path = "hello.rs"
|
||||
47
dom/media/gtest/GMPTestMonitor.h
Normal file
47
dom/media/gtest/GMPTestMonitor.h
Normal 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__
|
||||
54
dom/media/gtest/MockMediaDecoderOwner.h
Normal file
54
dom/media/gtest/MockMediaDecoderOwner.h
Normal 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
|
||||
116
dom/media/gtest/MockMediaResource.cpp
Normal file
116
dom/media/gtest/MockMediaResource.cpp
Normal 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
|
||||
83
dom/media/gtest/MockMediaResource.h
Normal file
83
dom/media/gtest/MockMediaResource.h
Normal 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
|
||||
57
dom/media/gtest/TestAudioBuffers.cpp
Normal file
57
dom/media/gtest/TestAudioBuffers.cpp
Normal 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);
|
||||
}
|
||||
145
dom/media/gtest/TestAudioCompactor.cpp
Normal file
145
dom/media/gtest/TestAudioCompactor.cpp
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
165
dom/media/gtest/TestAudioMixer.cpp
Normal file
165
dom/media/gtest/TestAudioMixer.cpp
Normal 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
|
||||
167
dom/media/gtest/TestAudioPacketizer.cpp
Normal file
167
dom/media/gtest/TestAudioPacketizer.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
257
dom/media/gtest/TestAudioSegment.cpp
Normal file
257
dom/media/gtest/TestAudioSegment.cpp
Normal 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
|
||||
1552
dom/media/gtest/TestGMPCrossOrigin.cpp
Normal file
1552
dom/media/gtest/TestGMPCrossOrigin.cpp
Normal file
File diff suppressed because it is too large
Load diff
491
dom/media/gtest/TestGMPRemoveAndDelete.cpp
Normal file
491
dom/media/gtest/TestGMPRemoveAndDelete.cpp
Normal 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;
|
||||
}
|
||||
61
dom/media/gtest/TestGMPUtils.cpp
Normal file
61
dom/media/gtest/TestGMPUtils.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
821
dom/media/gtest/TestIntervalSet.cpp
Normal file
821
dom/media/gtest/TestIntervalSet.cpp
Normal 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);
|
||||
}
|
||||
454
dom/media/gtest/TestMP3Demuxer.cpp
Normal file
454
dom/media/gtest/TestMP3Demuxer.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
463
dom/media/gtest/TestMP4Demuxer.cpp
Normal file
463
dom/media/gtest/TestMP4Demuxer.cpp
Normal 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
|
||||
217
dom/media/gtest/TestMP4Reader.cpp
Normal file
217
dom/media/gtest/TestMP4Reader.cpp
Normal 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);
|
||||
}
|
||||
72
dom/media/gtest/TestMediaDataDecoder.cpp
Normal file
72
dom/media/gtest/TestMediaDataDecoder.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
337
dom/media/gtest/TestMediaEventSource.cpp
Normal file
337
dom/media/gtest/TestMediaEventSource.cpp
Normal 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();
|
||||
}
|
||||
248
dom/media/gtest/TestMediaFormatReader.cpp
Normal file
248
dom/media/gtest/TestMediaFormatReader.cpp
Normal 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);
|
||||
}
|
||||
257
dom/media/gtest/TestMozPromise.cpp
Normal file
257
dom/media/gtest/TestMozPromise.cpp
Normal 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
|
||||
9
dom/media/gtest/TestRust.cpp
Normal file
9
dom/media/gtest/TestRust.cpp
Normal 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.");
|
||||
}
|
||||
22
dom/media/gtest/TestTimeUnit.cpp
Normal file
22
dom/media/gtest/TestTimeUnit.cpp
Normal 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);
|
||||
}
|
||||
94
dom/media/gtest/TestTrackEncoder.cpp
Normal file
94
dom/media/gtest/TestTrackEncoder.cpp
Normal 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);
|
||||
}
|
||||
103
dom/media/gtest/TestVPXDecoding.cpp
Normal file
103
dom/media/gtest/TestVPXDecoding.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
51
dom/media/gtest/TestVideoSegment.cpp
Normal file
51
dom/media/gtest/TestVideoSegment.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
351
dom/media/gtest/TestVideoTrackEncoder.cpp
Normal file
351
dom/media/gtest/TestVideoTrackEncoder.cpp
Normal 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)));
|
||||
}
|
||||
80
dom/media/gtest/TestVideoUtils.cpp
Normal file
80
dom/media/gtest/TestVideoUtils.cpp
Normal 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() << "\"";
|
||||
}
|
||||
}
|
||||
119
dom/media/gtest/TestWebMBuffered.cpp
Normal file
119
dom/media/gtest/TestWebMBuffered.cpp
Normal 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]);
|
||||
}
|
||||
}
|
||||
376
dom/media/gtest/TestWebMWriter.cpp
Normal file
376
dom/media/gtest/TestWebMWriter.cpp
Normal 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, ¶ms);
|
||||
EXPECT_EQ(rv, 0);
|
||||
EXPECT_EQ(width, static_cast<int32_t>(params.width));
|
||||
EXPECT_EQ(height, static_cast<int32_t>(params.height));
|
||||
EXPECT_EQ(displayWidth, static_cast<int32_t>(params.display_width));
|
||||
EXPECT_EQ(displayHeight, static_cast<int32_t>(params.display_height));
|
||||
} else if (type == NESTEGG_TRACK_AUDIO) {
|
||||
nestegg_audio_params params;
|
||||
rv = nestegg_track_audio_params(context, track, ¶ms);
|
||||
EXPECT_EQ(rv, 0);
|
||||
EXPECT_EQ(channel, static_cast<int>(params.channels));
|
||||
EXPECT_EQ(static_cast<double>(sampleRate), params.rate);
|
||||
}
|
||||
}
|
||||
if (context) {
|
||||
nestegg_destroy(context);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
dom/media/gtest/dash_dashinit.mp4
Normal file
BIN
dom/media/gtest/dash_dashinit.mp4
Normal file
Binary file not shown.
6
dom/media/gtest/hello.rs
Normal file
6
dom/media/gtest/hello.rs
Normal 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()
|
||||
}
|
||||
BIN
dom/media/gtest/id3v2header.mp3
Normal file
BIN
dom/media/gtest/id3v2header.mp3
Normal file
Binary file not shown.
BIN
dom/media/gtest/mediasource_test.mp4
Normal file
BIN
dom/media/gtest/mediasource_test.mp4
Normal file
Binary file not shown.
80
dom/media/gtest/moz.build
Normal file
80
dom/media/gtest/moz.build
Normal 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',
|
||||
]
|
||||
BIN
dom/media/gtest/negative_duration.mp4
Normal file
BIN
dom/media/gtest/negative_duration.mp4
Normal file
Binary file not shown.
BIN
dom/media/gtest/noise.mp3
Normal file
BIN
dom/media/gtest/noise.mp3
Normal file
Binary file not shown.
BIN
dom/media/gtest/noise_vbr.mp3
Normal file
BIN
dom/media/gtest/noise_vbr.mp3
Normal file
Binary file not shown.
BIN
dom/media/gtest/short-zero-in-moov.mp4
Normal file
BIN
dom/media/gtest/short-zero-in-moov.mp4
Normal file
Binary file not shown.
BIN
dom/media/gtest/short-zero-inband.mov
Normal file
BIN
dom/media/gtest/short-zero-inband.mov
Normal file
Binary file not shown.
BIN
dom/media/gtest/small-shot-false-positive.mp3
Normal file
BIN
dom/media/gtest/small-shot-false-positive.mp3
Normal file
Binary file not shown.
BIN
dom/media/gtest/small-shot.mp3
Normal file
BIN
dom/media/gtest/small-shot.mp3
Normal file
Binary file not shown.
BIN
dom/media/gtest/test.webm
Normal file
BIN
dom/media/gtest/test.webm
Normal file
Binary file not shown.
BIN
dom/media/gtest/test_case_1224361.vp8.ivf
Normal file
BIN
dom/media/gtest/test_case_1224361.vp8.ivf
Normal file
Binary file not shown.
BIN
dom/media/gtest/test_case_1224363.vp8.ivf
Normal file
BIN
dom/media/gtest/test_case_1224363.vp8.ivf
Normal file
Binary file not shown.
BIN
dom/media/gtest/test_case_1224369.vp8.ivf
Normal file
BIN
dom/media/gtest/test_case_1224369.vp8.ivf
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue