mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 20:35:50 +00:00
Merge b2g-inbound to m-c.
This commit is contained in:
commit
d0a9cd5005
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "77a90d19d2c6feb621f894685068bd131c0a0fdb",
|
||||
"revision": "b61e95024e07f0ad99e20802953fb8b62ae02505",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -37,8 +37,6 @@ PRLogModuleInfo* gMediaEncoderLog;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static nsIThread* sEncoderThread = nullptr;
|
||||
|
||||
void
|
||||
MediaEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
TrackID aID,
|
||||
@ -75,11 +73,6 @@ MediaEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
MediaEncoder::OnEncoderThread()
|
||||
{
|
||||
return NS_GetCurrentThread() == sEncoderThread;
|
||||
}
|
||||
/* static */
|
||||
already_AddRefed<MediaEncoder>
|
||||
MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint8_t aTrackTypes)
|
||||
@ -179,9 +172,7 @@ MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
|
||||
nsAString& aMIMEType)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
if (!sEncoderThread) {
|
||||
sEncoderThread = NS_GetCurrentThread();
|
||||
}
|
||||
|
||||
aMIMEType = mMIMEType;
|
||||
|
||||
bool reloop = true;
|
||||
|
@ -94,10 +94,6 @@ public :
|
||||
*/
|
||||
static already_AddRefed<MediaEncoder> CreateEncoder(const nsAString& aMIMEType,
|
||||
uint8_t aTrackTypes = ContainerWriter::HAS_AUDIO);
|
||||
/**
|
||||
* Check if run on Encoder thread
|
||||
*/
|
||||
static bool OnEncoderThread();
|
||||
/**
|
||||
* Encodes the raw track data and returns the final container data. Assuming
|
||||
* it is called on a single worker thread. The buffer of container data is
|
||||
|
@ -18,7 +18,9 @@ public:
|
||||
enum MetadataKind {
|
||||
METADATA_OPUS, // Represent the Opus metadata
|
||||
METADATA_VP8,
|
||||
METADATA_UNKNOW // Metadata Kind not set
|
||||
METADATA_AVC,
|
||||
METADATA_AAC,
|
||||
METADATA_UNKNOWN // Metadata Kind not set
|
||||
};
|
||||
virtual ~TrackMetadataBase() {}
|
||||
// Return the specific metadata kind
|
||||
|
120
content/media/encoder/fmp4_muxer/AVCBox.cpp
Normal file
120
content/media/encoder/fmp4_muxer/AVCBox.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
/* -*- 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 <climits>
|
||||
#include "ISOControl.h"
|
||||
#include "ISOMediaBoxes.h"
|
||||
#include "AVCBox.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
nsresult
|
||||
VisualSampleEntry::Generate(uint32_t* aBoxSize)
|
||||
{
|
||||
// both fields occupy 16 bits defined in 14496-2 6.2.3.
|
||||
width = mMeta.mVidMeta->Width;
|
||||
height = mMeta.mVidMeta->Height;
|
||||
|
||||
uint32_t avc_box_size = 0;
|
||||
nsresult rv;
|
||||
rv = avcConfigBox->Generate(&avc_box_size);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
size += avc_box_size +
|
||||
sizeof(reserved) +
|
||||
sizeof(width) +
|
||||
sizeof(height) +
|
||||
sizeof(horizresolution) +
|
||||
sizeof(vertresolution) +
|
||||
sizeof(reserved2) +
|
||||
sizeof(frame_count) +
|
||||
sizeof(compressorName) +
|
||||
sizeof(depth) +
|
||||
sizeof(pre_defined);
|
||||
|
||||
*aBoxSize = size;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
VisualSampleEntry::Write()
|
||||
{
|
||||
BoxSizeChecker checker(mControl, size);
|
||||
SampleEntryBox::Write();
|
||||
|
||||
mControl->Write(reserved, sizeof(reserved));
|
||||
mControl->Write(width);
|
||||
mControl->Write(height);
|
||||
mControl->Write(horizresolution);
|
||||
mControl->Write(vertresolution);
|
||||
mControl->Write(reserved2);
|
||||
mControl->Write(frame_count);
|
||||
mControl->Write(compressorName, sizeof(compressorName));
|
||||
mControl->Write(depth);
|
||||
mControl->Write(pre_defined);
|
||||
nsresult rv = avcConfigBox->Write();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
VisualSampleEntry::VisualSampleEntry(ISOControl* aControl)
|
||||
: SampleEntryBox(NS_LITERAL_CSTRING("avc1"), Video_Track, aControl)
|
||||
, width(0)
|
||||
, height(0)
|
||||
, horizresolution(resolution_72_dpi)
|
||||
, vertresolution(resolution_72_dpi)
|
||||
, reserved2(0)
|
||||
, frame_count(1)
|
||||
, depth(video_depth)
|
||||
, pre_defined(-1)
|
||||
{
|
||||
memset(reserved, 0 , sizeof(reserved));
|
||||
memset(compressorName, 0 , sizeof(compressorName));
|
||||
avcConfigBox = new AVCConfigurationBox(aControl);
|
||||
MOZ_COUNT_CTOR(VisualSampleEntry);
|
||||
}
|
||||
|
||||
VisualSampleEntry::~VisualSampleEntry()
|
||||
{
|
||||
MOZ_COUNT_DTOR(VisualSampleEntry);
|
||||
}
|
||||
|
||||
AVCConfigurationBox::AVCConfigurationBox(ISOControl* aControl)
|
||||
: Box(NS_LITERAL_CSTRING("avcC"), aControl)
|
||||
{
|
||||
MOZ_COUNT_CTOR(AVCConfigurationBox);
|
||||
}
|
||||
|
||||
AVCConfigurationBox::~AVCConfigurationBox()
|
||||
{
|
||||
MOZ_COUNT_DTOR(AVCConfigurationBox);
|
||||
}
|
||||
|
||||
nsresult
|
||||
AVCConfigurationBox::Generate(uint32_t* aBoxSize)
|
||||
{
|
||||
nsresult rv;
|
||||
FragmentBuffer* frag = mControl->GetFragment(Video_Track);
|
||||
rv = frag->GetCSD(avcConfig);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
size += avcConfig.Length();
|
||||
*aBoxSize = size;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
AVCConfigurationBox::Write()
|
||||
{
|
||||
BoxSizeChecker checker(mControl, size);
|
||||
Box::Write();
|
||||
|
||||
mControl->Write(avcConfig.Elements(), avcConfig.Length());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
}
|
73
content/media/encoder/fmp4_muxer/AVCBox.h
Normal file
73
content/media/encoder/fmp4_muxer/AVCBox.h
Normal file
@ -0,0 +1,73 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef AVCBox_h_
|
||||
#define AVCBox_h_
|
||||
|
||||
#include "nsTArray.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "ISOMediaBoxes.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ISOControl;
|
||||
|
||||
// 14496-12 8.5.2.2
|
||||
#define resolution_72_dpi 0x00480000
|
||||
#define video_depth 0x0018
|
||||
|
||||
// 14496-15 5.3.4.1 'Sample description name and format'
|
||||
// Box type: 'avcC'
|
||||
class AVCConfigurationBox : public Box {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
|
||||
// avcConfig is CodecSpecificData from 14496-15 '5.3.4.1 Sample description
|
||||
// name and format.
|
||||
// These data are generated by encoder and we encapsulated the generated
|
||||
// bitstream into box directly.
|
||||
nsTArray<uint8_t> avcConfig;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// AVCConfigurationBox methods
|
||||
AVCConfigurationBox(ISOControl* aControl);
|
||||
~AVCConfigurationBox();
|
||||
};
|
||||
|
||||
// 14496-15 5.3.4.1 'Sample description name and format'
|
||||
// Box type: 'avc1'
|
||||
class VisualSampleEntry : public SampleEntryBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint8_t reserved[16];
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
|
||||
uint32_t horizresolution; // 72 dpi
|
||||
uint32_t vertresolution; // 72 dpi
|
||||
uint32_t reserved2;
|
||||
uint16_t frame_count; // 1, defined in 14496-12 8.5.2.2
|
||||
|
||||
uint8_t compressorName[32];
|
||||
uint16_t depth; // 0x0018, defined in 14496-12 8.5.2.2;
|
||||
uint16_t pre_defined; // -1, defined in 14496-12 8.5.2.2;
|
||||
|
||||
nsRefPtr<AVCConfigurationBox> avcConfigBox;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// VisualSampleEntry methods
|
||||
VisualSampleEntry(ISOControl* aControl);
|
||||
~VisualSampleEntry();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // AVCBox_h_
|
385
content/media/encoder/fmp4_muxer/ISOControl.cpp
Normal file
385
content/media/encoder/fmp4_muxer/ISOControl.cpp
Normal file
@ -0,0 +1,385 @@
|
||||
/* -*- 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 <time.h>
|
||||
#include "nsAutoPtr.h"
|
||||
#include "ISOControl.h"
|
||||
#include "ISOMediaBoxes.h"
|
||||
#include "EncodedFrameContainer.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// For MP4 creation_time and modification_time offset from January 1, 1904 to
|
||||
// January 1, 1970.
|
||||
#define iso_time_offset 2082844800
|
||||
|
||||
const static uint32_t MUXING_BUFFER_SIZE = 512*1024;
|
||||
|
||||
FragmentBuffer::FragmentBuffer(uint32_t aTrackType, uint32_t aFragDuration,
|
||||
TrackMetadataBase* aMetadata)
|
||||
: mTrackType(aTrackType)
|
||||
, mFragDuration(aFragDuration)
|
||||
, mMediaStartTime(0)
|
||||
, mFragmentNumber(0)
|
||||
, mEOS(false)
|
||||
{
|
||||
mFragArray.AppendElement();
|
||||
if (mTrackType == Audio_Track) {
|
||||
nsRefPtr<AACTrackMetadata> audMeta = static_cast<AACTrackMetadata*>(aMetadata);
|
||||
MOZ_ASSERT(audMeta);
|
||||
} else {
|
||||
nsRefPtr<AVCTrackMetadata> vidMeta = static_cast<AVCTrackMetadata*>(aMetadata);
|
||||
MOZ_ASSERT(vidMeta);
|
||||
}
|
||||
MOZ_COUNT_CTOR(FragmentBuffer);
|
||||
}
|
||||
|
||||
FragmentBuffer::~FragmentBuffer()
|
||||
{
|
||||
MOZ_COUNT_DTOR(FragmentBuffer);
|
||||
}
|
||||
|
||||
bool
|
||||
FragmentBuffer::HasEnoughData()
|
||||
{
|
||||
// Audio or video frame is enough to form a moof.
|
||||
return (mFragArray.Length() > 1);
|
||||
}
|
||||
|
||||
nsresult
|
||||
FragmentBuffer::GetCSD(nsTArray<uint8_t>& aCSD)
|
||||
{
|
||||
if (!mCSDFrame) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
aCSD.AppendElements(mCSDFrame->GetFrameData().Elements(),
|
||||
mCSDFrame->GetFrameData().Length());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
FragmentBuffer::AddFrame(EncodedFrame* aFrame)
|
||||
{
|
||||
// already EOS, it rejects all new data.
|
||||
if (mEOS) {
|
||||
MOZ_ASSERT(0);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
EncodedFrame::FrameType type = aFrame->GetFrameType();
|
||||
if (type == EncodedFrame::AAC_CSD || type == EncodedFrame::AVC_CSD) {
|
||||
mCSDFrame = aFrame;
|
||||
// Ue CSD's timestamp as the start time. Encoder should send CSD frame first
|
||||
// and data frames.
|
||||
mMediaStartTime = aFrame->GetTimeStamp();
|
||||
mFragmentNumber = 1;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// if the timestamp is incorrect, abort it.
|
||||
if (aFrame->GetTimeStamp() < mMediaStartTime) {
|
||||
MOZ_ASSERT(false);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mFragArray.LastElement().AppendElement(aFrame);
|
||||
|
||||
// check if current fragment is reach the fragment duration.
|
||||
if ((aFrame->GetTimeStamp() - mMediaStartTime) > (mFragDuration * mFragmentNumber)) {
|
||||
mFragArray.AppendElement();
|
||||
mFragmentNumber++;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
FragmentBuffer::GetFirstFragment(nsTArray<nsRefPtr<EncodedFrame>>& aFragment,
|
||||
bool aFlush)
|
||||
{
|
||||
// It should be called only if there is a complete fragment in mFragArray.
|
||||
if (mFragArray.Length() <= 1 && !mEOS) {
|
||||
MOZ_ASSERT(false);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (aFlush) {
|
||||
aFragment.SwapElements(mFragArray.ElementAt(0));
|
||||
mFragArray.RemoveElementAt(0);
|
||||
} else {
|
||||
aFragment.AppendElements(mFragArray.ElementAt(0));
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
FragmentBuffer::GetFirstFragmentSampleNumber()
|
||||
{
|
||||
return mFragArray.ElementAt(0).Length();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
FragmentBuffer::GetFirstFragmentSampleSize()
|
||||
{
|
||||
uint32_t size = 0;
|
||||
uint32_t len = mFragArray.ElementAt(0).Length();
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
size += mFragArray.ElementAt(0).ElementAt(i)->GetFrameData().Length();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
ISOControl::ISOControl()
|
||||
: mAudioFragmentBuffer(nullptr)
|
||||
, mVideoFragmentBuffer(nullptr)
|
||||
, mFragNum(0)
|
||||
, mOutputSize(0)
|
||||
, mBitCount(0)
|
||||
, mBit(0)
|
||||
{
|
||||
mOutBuffer.SetCapacity(MUXING_BUFFER_SIZE);
|
||||
MOZ_COUNT_CTOR(ISOControl);
|
||||
}
|
||||
|
||||
ISOControl::~ISOControl()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ISOControl);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ISOControl::GetNextTrackID()
|
||||
{
|
||||
return (mMetaArray.Length() + 1);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ISOControl::GetTrackID(uint32_t aTrackType)
|
||||
{
|
||||
TrackMetadataBase::MetadataKind kind;
|
||||
if (aTrackType == Audio_Track) {
|
||||
kind = TrackMetadataBase::METADATA_AAC;
|
||||
} else {
|
||||
kind = TrackMetadataBase::METADATA_AVC;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < mMetaArray.Length(); i++) {
|
||||
if (mMetaArray[i]->GetKind() == kind) {
|
||||
return (i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOControl::SetMetadata(TrackMetadataBase* aTrackMeta)
|
||||
{
|
||||
if (aTrackMeta->GetKind() == TrackMetadataBase::METADATA_AAC ||
|
||||
aTrackMeta->GetKind() == TrackMetadataBase::METADATA_AVC) {
|
||||
mMetaArray.AppendElement(aTrackMeta);
|
||||
return NS_OK;
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOControl::GetAudioMetadata(nsRefPtr<AACTrackMetadata>& aAudMeta)
|
||||
{
|
||||
for (uint32_t i = 0; i < mMetaArray.Length() ; i++) {
|
||||
if (mMetaArray[i]->GetKind() == TrackMetadataBase::METADATA_AAC) {
|
||||
aAudMeta = static_cast<AACTrackMetadata*>(mMetaArray[i].get());
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOControl::GetVideoMetadata(nsRefPtr<AVCTrackMetadata>& aVidMeta)
|
||||
{
|
||||
for (uint32_t i = 0; i < mMetaArray.Length() ; i++) {
|
||||
if (mMetaArray[i]->GetKind() == TrackMetadataBase::METADATA_AVC) {
|
||||
aVidMeta = static_cast<AVCTrackMetadata*>(mMetaArray[i].get());
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
bool
|
||||
ISOControl::HasAudioTrack()
|
||||
{
|
||||
nsRefPtr<AACTrackMetadata> audMeta;
|
||||
GetAudioMetadata(audMeta);
|
||||
return audMeta;
|
||||
}
|
||||
|
||||
bool
|
||||
ISOControl::HasVideoTrack()
|
||||
{
|
||||
nsRefPtr<AVCTrackMetadata> vidMeta;
|
||||
GetVideoMetadata(vidMeta);
|
||||
return vidMeta;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOControl::SetFragment(FragmentBuffer* aFragment)
|
||||
{
|
||||
if (aFragment->GetType() == Audio_Track) {
|
||||
mAudioFragmentBuffer = aFragment;
|
||||
} else {
|
||||
mVideoFragmentBuffer = aFragment;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
FragmentBuffer*
|
||||
ISOControl::GetFragment(uint32_t aType)
|
||||
{
|
||||
if (aType == Audio_Track) {
|
||||
return mAudioFragmentBuffer;
|
||||
} else if (aType == Video_Track){
|
||||
return mVideoFragmentBuffer;
|
||||
}
|
||||
MOZ_ASSERT(0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOControl::GetBuf(nsTArray<uint8_t>& aOutBuf)
|
||||
{
|
||||
mOutputSize += mOutBuffer.Length();
|
||||
aOutBuf.SwapElements(mOutBuffer);
|
||||
return FlushBuf();
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOControl::FlushBuf()
|
||||
{
|
||||
mOutBuffer.SetCapacity(MUXING_BUFFER_SIZE);
|
||||
mLastWrittenBoxPos = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ISOControl::WriteBits(uint64_t aBits, size_t aNumBits)
|
||||
{
|
||||
uint8_t output_byte = 0;
|
||||
|
||||
MOZ_ASSERT(aNumBits <= 64);
|
||||
// TODO: rewritten following with bitset?
|
||||
for (size_t i = aNumBits; i > 0; i--) {
|
||||
mBit |= (((aBits >> (i - 1)) & 1) << (8 - ++mBitCount));
|
||||
if (mBitCount == 8) {
|
||||
Write(&mBit, sizeof(uint8_t));
|
||||
mBit = 0;
|
||||
mBitCount = 0;
|
||||
output_byte++;
|
||||
}
|
||||
}
|
||||
return output_byte;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ISOControl::Write(uint8_t* aBuf, uint32_t aSize)
|
||||
{
|
||||
mOutBuffer.AppendElements(aBuf, aSize);
|
||||
return aSize;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ISOControl::Write(uint8_t aData)
|
||||
{
|
||||
MOZ_ASSERT(!mBitCount);
|
||||
Write((uint8_t*)&aData, sizeof(uint8_t));
|
||||
return sizeof(uint8_t);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ISOControl::WriteFourCC(const char* aType)
|
||||
{
|
||||
// Bit operation should be aligned to byte before writing any byte data.
|
||||
MOZ_ASSERT(!mBitCount);
|
||||
|
||||
uint32_t size = strlen(aType);
|
||||
if (size == 4) {
|
||||
return Write((uint8_t*)aType, size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOControl::GenerateFtyp()
|
||||
{
|
||||
nsresult rv;
|
||||
uint32_t size;
|
||||
nsAutoPtr<FileTypeBox> type_box(new FileTypeBox(this));
|
||||
rv = type_box->Generate(&size);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = type_box->Write();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOControl::GenerateMoov()
|
||||
{
|
||||
nsresult rv;
|
||||
uint32_t size;
|
||||
nsAutoPtr<MovieBox> moov_box(new MovieBox(this));
|
||||
rv = moov_box->Generate(&size);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = moov_box->Write();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOControl::GenerateMoof(uint32_t aTrackType)
|
||||
{
|
||||
mFragNum++;
|
||||
|
||||
nsresult rv;
|
||||
uint32_t size;
|
||||
uint64_t first_sample_offset = mOutputSize + mLastWrittenBoxPos;
|
||||
nsAutoPtr<MovieFragmentBox> moof_box(new MovieFragmentBox(aTrackType, this));
|
||||
nsAutoPtr<MediaDataBox> mdat_box(new MediaDataBox(aTrackType, this));
|
||||
|
||||
rv = moof_box->Generate(&size);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
first_sample_offset += size;
|
||||
rv = mdat_box->Generate(&size);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
first_sample_offset += mdat_box->FirstSampleOffsetInMediaDataBox();
|
||||
|
||||
// correct offset info
|
||||
nsTArray<nsRefPtr<MuxerOperation>> tfhds;
|
||||
rv = moof_box->Find(NS_LITERAL_CSTRING("tfhd"), tfhds);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
uint32_t len = tfhds.Length();
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
TrackFragmentHeaderBox* tfhd = (TrackFragmentHeaderBox*) tfhds.ElementAt(i).get();
|
||||
rv = tfhd->UpdateBaseDataOffset(first_sample_offset);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
rv = moof_box->Write();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mdat_box->Write();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ISOControl::GetTime()
|
||||
{
|
||||
return (uint64_t)time(nullptr) + iso_time_offset;
|
||||
}
|
||||
|
||||
}
|
221
content/media/encoder/fmp4_muxer/ISOControl.h
Normal file
221
content/media/encoder/fmp4_muxer/ISOControl.h
Normal file
@ -0,0 +1,221 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef ISOCOMPOSITOR_H_
|
||||
#define ISOCOMPOSITOR_H_
|
||||
|
||||
#include "mozilla/Endian.h"
|
||||
#include "TrackMetadataBase.h"
|
||||
#include "nsTArray.h"
|
||||
#include "ISOTrackMetadata.h"
|
||||
#include "EncodedFrameContainer.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class Box;
|
||||
class ISOControl;
|
||||
|
||||
/**
|
||||
* This class collects elementary stream data to form a fragment.
|
||||
* ISOMediaWriter will check if the data is enough; if yes, the corresponding
|
||||
* moof will be created and write to ISOControl.
|
||||
* Each audio and video has its own fragment and only one during the whole
|
||||
* life cycle, when a fragment is formed in ISOControl, Flush() needs to
|
||||
* be called to reset it.
|
||||
*/
|
||||
class FragmentBuffer {
|
||||
public:
|
||||
// aTrackType: it could be Audio_Track or Video_Track.
|
||||
// aFragDuration: it is the fragment duration. (microsecond per unit)
|
||||
// Audio and video have the same fragment duration.
|
||||
FragmentBuffer(uint32_t aTrackType, uint32_t aFragDuration,
|
||||
TrackMetadataBase* aMetadata);
|
||||
~FragmentBuffer();
|
||||
|
||||
// Get samples of first fragment, that will swap all the elements in the
|
||||
// mFragArray[0] when aFlush = true, and caller is responsible for drop
|
||||
// EncodedFrame reference count.
|
||||
nsresult GetFirstFragment(nsTArray<nsRefPtr<EncodedFrame>>& aFragment,
|
||||
bool aFlush = false);
|
||||
|
||||
// Add sample frame to the last element fragment of mFragArray. If sample
|
||||
// number is enough, it will append a new fragment element. And the new
|
||||
// sample will be added to the new fragment element of mFragArray.
|
||||
nsresult AddFrame(EncodedFrame* aFrame);
|
||||
|
||||
// Get total sample size of first complete fragment size.
|
||||
uint32_t GetFirstFragmentSampleSize();
|
||||
|
||||
// Get sample number of first complete fragment.
|
||||
uint32_t GetFirstFragmentSampleNumber();
|
||||
|
||||
// Check if it accumulates enough frame data.
|
||||
// It returns true when data is enough to form a fragment.
|
||||
bool HasEnoughData();
|
||||
|
||||
// Called by ISOMediaWriter when TrackEncoder has sent the last frame. The
|
||||
// remains frame data will form the last moof and move the state machine to
|
||||
// in ISOMediaWriter to last phrase.
|
||||
nsresult SetEndOfStream() {
|
||||
mEOS = true;
|
||||
return NS_OK;
|
||||
}
|
||||
bool EOS() { return mEOS; }
|
||||
|
||||
// CSD (codec specific data), it is generated by encoder and the data depends
|
||||
// on codec type. This data will be sent as a special frame from encoder to
|
||||
// ISOMediaWriter and pass to this class via AddFrame().
|
||||
nsresult GetCSD(nsTArray<uint8_t>& aCSD);
|
||||
|
||||
bool HasCSD() { return mCSDFrame; }
|
||||
|
||||
uint32_t GetType() { return mTrackType; }
|
||||
|
||||
private:
|
||||
uint32_t mTrackType;
|
||||
|
||||
// Fragment duration, microsecond per unit.
|
||||
uint32_t mFragDuration;
|
||||
|
||||
// Media start time, microsecond per unit.
|
||||
// Together with mFragDuration, mFragmentNumber and EncodedFrame->GetTimeStamp(),
|
||||
// when the difference between current frame time and mMediaStartTime is
|
||||
// exceeded current fragment ceiling timeframe, that means current fragment has
|
||||
// enough data and a new element in mFragArray will be added.
|
||||
uint64_t mMediaStartTime;
|
||||
|
||||
// Current fragment number. It will be increase when a new element of
|
||||
// mFragArray is created.
|
||||
// Note:
|
||||
// It only means the fragment number of current accumulated frames, not
|
||||
// the current 'creating' fragment mFragNum in ISOControl.
|
||||
uint32_t mFragmentNumber;
|
||||
|
||||
// Array of fragments, each element has enough samples to form a
|
||||
// complete fragment.
|
||||
nsTArray<nsTArray<nsRefPtr<EncodedFrame>>> mFragArray;
|
||||
|
||||
// Codec specific data frame, it will be generated by encoder and send to
|
||||
// ISOMediaWriter through WriteEncodedTrack(). The data will be vary depends
|
||||
// on codec type.
|
||||
nsRefPtr<EncodedFrame> mCSDFrame;
|
||||
|
||||
// END_OF_STREAM from ContainerWriter
|
||||
bool mEOS;
|
||||
};
|
||||
|
||||
/**
|
||||
* ISOControl will be carried to each box when box is created. It is the main
|
||||
* bridge for box to output stream to ContainerWriter and retrieve information.
|
||||
* ISOControl acts 3 different roles:
|
||||
* 1. Holds the pointer of audio metadata, video metadata, fragment and
|
||||
* pass them to boxes.
|
||||
* 2. Provide the functions to generate the base structure of MP4; they are
|
||||
* GenerateFtyp, GenerateMoov, GenerateMoof, and GenerateMfra.
|
||||
* 3. The actually writer used by MuxOperation::Write() in each box. It provides
|
||||
* writing methods for different kind of data; they are Write, WriteArray,
|
||||
* WriteBits...etc.
|
||||
*/
|
||||
class ISOControl {
|
||||
|
||||
friend class Box;
|
||||
|
||||
public:
|
||||
ISOControl();
|
||||
~ISOControl();
|
||||
|
||||
nsresult GenerateFtyp();
|
||||
nsresult GenerateMoov();
|
||||
nsresult GenerateMoof(uint32_t aTrackType);
|
||||
|
||||
uint32_t Write(uint8_t* aBuf, uint32_t aSize);
|
||||
|
||||
uint32_t Write(uint8_t aData);
|
||||
|
||||
template <typename T>
|
||||
uint32_t Write(T aData) {
|
||||
MOZ_ASSERT(!mBitCount);
|
||||
|
||||
aData = NativeEndian::swapToNetworkOrder(aData);
|
||||
Write((uint8_t*)&aData, sizeof(T));
|
||||
return sizeof(T);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
uint32_t WriteArray(const T &aArray, uint32_t aSize) {
|
||||
MOZ_ASSERT(!mBitCount);
|
||||
|
||||
uint32_t size = 0;
|
||||
for (uint32_t i = 0; i < aSize; i++) {
|
||||
size += Write(aArray[i]);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
uint32_t WriteFourCC(const char* aType);
|
||||
|
||||
// Bit writing. Note: it needs to be byte-boundary before using
|
||||
// others non-bit writing function.
|
||||
uint32_t WriteBits(uint64_t aBits, size_t aNumBits);
|
||||
|
||||
// This is called by GetContainerData and swap the buffer to aOutBuf.
|
||||
nsresult GetBuf(nsTArray<uint8_t>& aOutBuf);
|
||||
|
||||
// Presentation time in seconds since midnight, Jan. 1, 1904, in UTC time.
|
||||
uint32_t GetTime();
|
||||
|
||||
// current fragment number
|
||||
uint32_t GetCurFragmentNumber() { return mFragNum; }
|
||||
|
||||
nsresult SetFragment(FragmentBuffer* aFragment);
|
||||
FragmentBuffer* GetFragment(uint32_t aType);
|
||||
|
||||
nsresult SetMetadata(TrackMetadataBase* aTrackMeta);
|
||||
nsresult GetAudioMetadata(nsRefPtr<AACTrackMetadata>& aAudMeta);
|
||||
nsresult GetVideoMetadata(nsRefPtr<AVCTrackMetadata>& aVidMeta);
|
||||
|
||||
// Track ID is the Metadata index in mMetaArray.
|
||||
uint32_t GetTrackID(uint32_t aTrackType);
|
||||
uint32_t GetNextTrackID();
|
||||
|
||||
bool HasAudioTrack();
|
||||
bool HasVideoTrack();
|
||||
|
||||
private:
|
||||
uint32_t GetBufPos() { return mOutBuffer.Length(); }
|
||||
nsresult FlushBuf();
|
||||
|
||||
// Audio and video fragments are owned by ISOMediaWriter.
|
||||
// They don't need to worry about pointer going stale.
|
||||
FragmentBuffer* mAudioFragmentBuffer;
|
||||
FragmentBuffer* mVideoFragmentBuffer;
|
||||
|
||||
// Generated fragment number
|
||||
uint32_t mFragNum;
|
||||
|
||||
// The (index + 1) will be the track ID.
|
||||
nsTArray<nsRefPtr<TrackMetadataBase>> mMetaArray;
|
||||
|
||||
// TODO:
|
||||
// ContainerWriter accepts a array of uint8_t array so it is possible to
|
||||
// create a serial of small box header + swap the raw data pointer from
|
||||
// WriteEncodedTrack to another array without any memory copy.
|
||||
nsTArray<uint8_t> mOutBuffer;
|
||||
|
||||
// Last written position of current box, it is for box checking purpose.
|
||||
uint32_t mLastWrittenBoxPos;
|
||||
|
||||
// Accumulate size of output fragments.
|
||||
uint64_t mOutputSize;
|
||||
|
||||
// Bit writing operation. Note: the mBitCount should be 0 before any
|
||||
// byte-boundary writing method be called (Write(uint32_t), Write(uint16_t)...etc);
|
||||
// otherwise, there will be assertion on these functions.
|
||||
uint8_t mBitCount;
|
||||
uint8_t mBit;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
1333
content/media/encoder/fmp4_muxer/ISOMediaBoxes.cpp
Normal file
1333
content/media/encoder/fmp4_muxer/ISOMediaBoxes.cpp
Normal file
File diff suppressed because it is too large
Load Diff
735
content/media/encoder/fmp4_muxer/ISOMediaBoxes.h
Normal file
735
content/media/encoder/fmp4_muxer/ISOMediaBoxes.h
Normal file
@ -0,0 +1,735 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef ISOMediaBoxes_h_
|
||||
#define ISOMediaBoxes_h_
|
||||
|
||||
#include <bitset>
|
||||
#include "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "MuxerOperation.h"
|
||||
|
||||
#define WRITE_FULLBOX(_compositor, _size) \
|
||||
BoxSizeChecker checker(_compositor, _size); \
|
||||
FullBox::Write();
|
||||
|
||||
#define FOURCC(a, b, c, d) ( ((a) << 24) | ((b) << 16) | ((c) << 8) | (d) )
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AACTrackMetadata;
|
||||
class AVCTrackMetadata;
|
||||
class ES_Descriptor;
|
||||
class ISOControl;
|
||||
|
||||
/**
|
||||
* This is the base class for all ISO media format boxes.
|
||||
* It provides the fields of box type(four CC) and size.
|
||||
* The data members in the beginning of a Box (or its descendants)
|
||||
* are the 14496-12 defined member. Other members prefix with 'm'
|
||||
* are private control data.
|
||||
*
|
||||
* This class is for inherited only, it shouldn't be instanced directly.
|
||||
*/
|
||||
class Box : public MuxerOperation {
|
||||
protected:
|
||||
// ISO BMFF members
|
||||
uint32_t size; // 14496-12 4-2 'Object Structure'. Size of this box.
|
||||
nsCString boxType; // four CC name, all table names are listed in
|
||||
// 14496-12 table 1.
|
||||
|
||||
public:
|
||||
// MuxerOperation methods
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
nsresult Find(const nsACString& aType,
|
||||
nsTArray<nsRefPtr<MuxerOperation>>& aOperations) MOZ_OVERRIDE;
|
||||
|
||||
// A helper class to check box written bytes number; it will compare
|
||||
// the size generated from Box::Generate() and the actually written length in
|
||||
// Box::Write().
|
||||
class MetaHelper {
|
||||
public:
|
||||
nsresult Init(ISOControl* aControl);
|
||||
bool AudioOnly() {
|
||||
if (mAudMeta && !mVidMeta) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
nsRefPtr<AACTrackMetadata> mAudMeta;
|
||||
nsRefPtr<AVCTrackMetadata> mVidMeta;
|
||||
};
|
||||
|
||||
// This helper class will compare the written size in Write() and the size in
|
||||
// Generate(). If their are not equal, it will assert.
|
||||
class BoxSizeChecker {
|
||||
public:
|
||||
BoxSizeChecker(ISOControl* aControl, uint32_t aSize);
|
||||
~BoxSizeChecker();
|
||||
|
||||
uint32_t ori_size;
|
||||
uint32_t box_size;
|
||||
ISOControl* mControl;
|
||||
};
|
||||
|
||||
protected:
|
||||
Box() MOZ_DELETE;
|
||||
Box(const nsACString& aType, ISOControl* aControl);
|
||||
|
||||
ISOControl* mControl;
|
||||
};
|
||||
|
||||
/**
|
||||
* FullBox (and its descendants) is the box which contains the 'real' data
|
||||
* members. It is the edge in the ISO box structure and it doesn't contain
|
||||
* any box.
|
||||
*
|
||||
* This class is for inherited only, it shouldn't be instanced directly.
|
||||
*/
|
||||
class FullBox : public Box {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint8_t version; // 14496-12 4.2 'Object Structure'
|
||||
std::bitset<24> flags; //
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
// FullBox methods
|
||||
FullBox(const nsACString& aType, uint8_t aVersion, uint32_t aFlags,
|
||||
ISOControl* aControl);
|
||||
FullBox() MOZ_DELETE;
|
||||
};
|
||||
|
||||
/**
|
||||
* The default implementation of the container box.
|
||||
* Basically, the container box inherits this class and overrides the
|
||||
* constructor only.
|
||||
*
|
||||
* According to 14496-12 3.1.1 'container box', a container box is
|
||||
* 'box whose sole purpose is to contain and group a set of related boxes'
|
||||
*
|
||||
* This class is for inherited only, it shouldn't be instanced directly.
|
||||
*/
|
||||
class DefaultContainerImpl : public Box {
|
||||
public:
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
nsresult Find(const nsACString& aType,
|
||||
nsTArray<nsRefPtr<MuxerOperation>>& aOperations) MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
// DefaultContainerImpl methods
|
||||
DefaultContainerImpl(const nsACString& aType, ISOControl* aControl);
|
||||
DefaultContainerImpl() MOZ_DELETE;
|
||||
|
||||
nsTArray<nsRefPtr<MuxerOperation>> boxes;
|
||||
};
|
||||
|
||||
// 14496-12 4.3 'File Type Box'
|
||||
// Box type: 'ftyp'
|
||||
class FileTypeBox : public Box {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
nsCString major_brand; // four chars
|
||||
uint32_t minor_version;
|
||||
nsTArray<nsCString> compatible_brands;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// FileTypeBox methods
|
||||
FileTypeBox(ISOControl* aControl);
|
||||
~FileTypeBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.2.1 'Movie Box'
|
||||
// Box type: 'moov'
|
||||
// MovieBox contains MovieHeaderBox, TrackBox and MovieExtendsBox.
|
||||
class MovieBox : public DefaultContainerImpl {
|
||||
public:
|
||||
MovieBox(ISOControl* aControl);
|
||||
~MovieBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.2.2 'Movie Header Box'
|
||||
// Box type: 'mvhd'
|
||||
class MovieHeaderBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint32_t creation_time;
|
||||
uint32_t modification_time;
|
||||
uint32_t timescale;
|
||||
uint32_t duration;
|
||||
uint32_t rate;
|
||||
uint16_t volume;
|
||||
uint16_t reserved16;
|
||||
uint32_t reserved32[2];
|
||||
uint32_t matrix[9];
|
||||
uint32_t pre_defined[6];
|
||||
uint32_t next_track_ID;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// MovieHeaderBox methods
|
||||
MovieHeaderBox(ISOControl* aControl);
|
||||
~MovieHeaderBox();
|
||||
uint32_t GetTimeScale();
|
||||
|
||||
protected:
|
||||
MetaHelper mMeta;
|
||||
};
|
||||
|
||||
// 14496-12 8.4.2 'Media Header Box'
|
||||
// Box type: 'mdhd'
|
||||
class MediaHeaderBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint32_t creation_time;
|
||||
uint32_t modification_time;
|
||||
uint32_t timescale;
|
||||
uint32_t duration;
|
||||
std::bitset<1> pad;
|
||||
std::bitset<5> lang1;
|
||||
std::bitset<5> lang2;
|
||||
std::bitset<5> lang3;
|
||||
uint16_t pre_defined;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// MediaHeaderBox methods
|
||||
MediaHeaderBox(uint32_t aType, ISOControl* aControl);
|
||||
~MediaHeaderBox();
|
||||
uint32_t GetTimeScale();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
MetaHelper mMeta;
|
||||
};
|
||||
|
||||
// 14496-12 8.3.1 'Track Box'
|
||||
// Box type: 'trak'
|
||||
// TrackBox contains TrackHeaderBox and MediaBox.
|
||||
class TrackBox : public DefaultContainerImpl {
|
||||
public:
|
||||
TrackBox(uint32_t aTrackType, ISOControl* aControl);
|
||||
~TrackBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.1.1 'Media Data Box'
|
||||
// Box type: 'mdat'
|
||||
class MediaDataBox : public Box {
|
||||
public:
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// MediaDataBox methods
|
||||
uint32_t GetAllSampleSize() { return mAllSampleSize; }
|
||||
uint32_t FirstSampleOffsetInMediaDataBox() { return mFirstSampleOffset; }
|
||||
MediaDataBox(uint32_t aTrackType, ISOControl* aControl);
|
||||
~MediaDataBox();
|
||||
|
||||
protected:
|
||||
uint32_t mAllSampleSize; // All audio and video sample size in this box.
|
||||
uint32_t mFirstSampleOffset; // The offset of first sample in this box from
|
||||
// the beginning of this mp4 file.
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// flags for TrackRunBox::flags, 14496-12 8.8.8.1.
|
||||
#define flags_data_offset_present 0x000001
|
||||
#define flags_first_sample_flags_present 0x000002
|
||||
#define flags_sample_duration_present 0x000100
|
||||
#define flags_sample_size_present 0x000200
|
||||
#define flags_sample_flags_present 0x000400
|
||||
#define flags_sample_composition_time_offsets_present 0x000800
|
||||
|
||||
// flag for TrackRunBox::tbl::sample_flags and TrackExtendsBox::default_sample_flags
|
||||
// which is defined in 14496-12 8.8.3.1.
|
||||
uint32_t set_sample_flags(bool aSync);
|
||||
|
||||
// 14496-12 8.8.8 'Track Fragment Run Box'
|
||||
// Box type: 'trun'
|
||||
class TrackRunBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
typedef struct {
|
||||
uint32_t sample_duration;
|
||||
uint32_t sample_size;
|
||||
uint32_t sample_flags;
|
||||
uint32_t sample_composition_time_offset;
|
||||
} tbl;
|
||||
|
||||
uint32_t sample_count;
|
||||
// the following are optional fields
|
||||
uint32_t data_offset; // data offset exists when audio/video are present in file.
|
||||
uint32_t first_sample_flags;
|
||||
nsAutoArrayPtr<tbl> sample_info_table;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// TrackRunBox methods
|
||||
uint32_t GetAllSampleSize() { return mAllSampleSize; }
|
||||
nsresult SetDataOffset(uint32_t aOffset);
|
||||
|
||||
TrackRunBox(uint32_t aType, uint32_t aFlags, ISOControl* aControl);
|
||||
~TrackRunBox();
|
||||
|
||||
protected:
|
||||
uint32_t fillSampleTable();
|
||||
|
||||
uint32_t mAllSampleSize;
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// tf_flags in TrackFragmentHeaderBox, 14496-12 8.8.7.1.
|
||||
#define base_data_offset_present 0x000001
|
||||
#define sample_description_index_present 0x000002
|
||||
#define default_sample_duration_present 0x000008
|
||||
#define default_sample_size_present 0x000010
|
||||
#define default_sample_flags_present 0x000020
|
||||
#define duration_is_empty 0x010000
|
||||
#define default_base_is_moof 0x020000
|
||||
|
||||
// 14496-12 8.8.7 'Track Fragment Header Box'
|
||||
// Box type: 'tfhd'
|
||||
class TrackFragmentHeaderBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint32_t track_ID;
|
||||
uint64_t base_data_offset;
|
||||
uint32_t default_sample_duration;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// TrackFragmentHeaderBox methods
|
||||
nsresult UpdateBaseDataOffset(uint64_t aOffset); // The offset of the first
|
||||
// sample in file.
|
||||
|
||||
TrackFragmentHeaderBox(uint32_t aType, ISOControl* aControl);
|
||||
~TrackFragmentHeaderBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
MetaHelper mMeta;
|
||||
};
|
||||
|
||||
// 14496-12 8.8.6 'Track Fragment Box'
|
||||
// Box type: 'traf'
|
||||
// TrackFragmentBox cotains TrackFragmentHeaderBox and TrackRunBox.
|
||||
class TrackFragmentBox : public DefaultContainerImpl {
|
||||
public:
|
||||
TrackFragmentBox(uint32_t aType, uint32_t aFlags, ISOControl* aControl);
|
||||
~TrackFragmentBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// 14496-12 8.8.5 'Movie Fragment Header Box'
|
||||
// Box type: 'mfhd'
|
||||
class MovieFragmentHeaderBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint32_t sequence_number;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// MovieFragmentHeaderBox methods
|
||||
MovieFragmentHeaderBox(uint32_t aType, ISOControl* aControl);
|
||||
~MovieFragmentHeaderBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// 14496-12 8.8.4 'Movie Fragment Box'
|
||||
// Box type: 'moof'
|
||||
// MovieFragmentBox contains MovieFragmentHeaderBox and TrackFragmentBox.
|
||||
class MovieFragmentBox : public DefaultContainerImpl {
|
||||
public:
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
|
||||
// MovieFragmentBox methods
|
||||
MovieFragmentBox(uint32_t aType, ISOControl* aControl);
|
||||
~MovieFragmentBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// 14496-12 8.8.3 'Track Extends Box'
|
||||
// Box type: 'trex'
|
||||
class TrackExtendsBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint32_t track_ID;
|
||||
uint32_t default_sample_description_index;
|
||||
uint32_t default_sample_duration;
|
||||
uint32_t default_sample_size;
|
||||
uint32_t default_sample_flags;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// TrackExtendsBox methods
|
||||
TrackExtendsBox(uint32_t aType, ISOControl* aControl);
|
||||
~TrackExtendsBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
MetaHelper mMeta;
|
||||
};
|
||||
|
||||
// 14496-12 8.8.1 'Movie Extends Box'
|
||||
// Box type: 'mvex'
|
||||
// MovieExtendsBox contains TrackExtendsBox.
|
||||
class MovieExtendsBox : public DefaultContainerImpl {
|
||||
public:
|
||||
MovieExtendsBox(ISOControl* aControl);
|
||||
~MovieExtendsBox();
|
||||
|
||||
protected:
|
||||
MetaHelper mMeta;
|
||||
};
|
||||
|
||||
// 14496-12 8.7.5 'Chunk Offset Box'
|
||||
// Box type: 'stco'
|
||||
class ChunkOffsetBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
typedef struct {
|
||||
uint32_t chunk_offset;
|
||||
} tbl;
|
||||
|
||||
uint32_t entry_count;
|
||||
nsAutoArrayPtr<tbl> sample_tbl;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// ChunkOffsetBox methods
|
||||
ChunkOffsetBox(uint32_t aType, ISOControl* aControl);
|
||||
~ChunkOffsetBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// 14496-12 8.7.4 'Sample To Chunk Box'
|
||||
// Box type: 'stsc'
|
||||
class SampleToChunkBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
typedef struct {
|
||||
uint32_t first_chunk;
|
||||
uint32_t sample_per_chunk;
|
||||
uint32_t sample_description_index;
|
||||
} tbl;
|
||||
|
||||
uint32_t entry_count;
|
||||
nsAutoArrayPtr<tbl> sample_tbl;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// SampleToChunkBox methods
|
||||
SampleToChunkBox(uint32_t aType, ISOControl* aControl);
|
||||
~SampleToChunkBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// 14496-12 8.6.1.2 'Decoding Time to Sample Box'
|
||||
// Box type: 'stts'
|
||||
class TimeToSampleBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
typedef struct {
|
||||
uint32_t sample_count;
|
||||
uint32_t sample_delta;
|
||||
} tbl;
|
||||
|
||||
uint32_t entry_count;
|
||||
nsAutoArrayPtr<tbl> sample_tbl;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// TimeToSampleBox methods
|
||||
TimeToSampleBox(uint32_t aType, ISOControl* aControl);
|
||||
~TimeToSampleBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
/**
|
||||
* 14496-12 8.5.2 'Sample Description Box'
|
||||
* This is the base class for VisualSampleEntry and MP4AudioSampleEntry.
|
||||
*
|
||||
* This class is for inherited only, it shouldn't be instanced directly.
|
||||
*/
|
||||
class SampleEntryBox : public Box {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint8_t reserved[6];
|
||||
uint16_t data_reference_index;
|
||||
|
||||
// SampleEntryBox methods
|
||||
SampleEntryBox(const nsACString& aFormat, uint32_t aTrackType,
|
||||
ISOControl* aControl);
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
SampleEntryBox() MOZ_DELETE;
|
||||
|
||||
uint32_t mTrackType;
|
||||
MetaHelper mMeta;
|
||||
};
|
||||
|
||||
// 14496-12 8.5.2 'Sample Description Box'
|
||||
// Box type: 'stsd'
|
||||
class SampleDescriptionBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint32_t entry_count;
|
||||
nsRefPtr<SampleEntryBox> sample_entry_box;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// SampleDescriptionBox methods
|
||||
SampleDescriptionBox(uint32_t aType, ISOControl* aControl);
|
||||
~SampleDescriptionBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// 14496-12 8.7.3.2 'Sample Size Box'
|
||||
// Box type: 'stsz'
|
||||
class SampleSizeBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint32_t sample_size;
|
||||
uint32_t sample_count;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// SampleSizeBox methods
|
||||
SampleSizeBox(ISOControl* aControl);
|
||||
~SampleSizeBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.5.1 'Sample Table Box'
|
||||
// Box type: 'stbl'
|
||||
//
|
||||
// SampleTableBox contains SampleDescriptionBox,
|
||||
// TimeToSampleBox,
|
||||
// SampleToChunkBox,
|
||||
// SampleSizeBox and
|
||||
// ChunkOffsetBox.
|
||||
class SampleTableBox : public DefaultContainerImpl {
|
||||
public:
|
||||
SampleTableBox(uint32_t aType, ISOControl* aControl);
|
||||
~SampleTableBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.7.2 'Data Reference Box'
|
||||
// Box type: 'url '
|
||||
class DataEntryUrlBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
// flags in DataEntryUrlBox::flags
|
||||
const static uint16_t flags_media_at_the_same_file = 0x0001;
|
||||
|
||||
nsCString location;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// DataEntryUrlBox methods
|
||||
DataEntryUrlBox();
|
||||
DataEntryUrlBox(ISOControl* aControl);
|
||||
DataEntryUrlBox(const DataEntryUrlBox& aBox);
|
||||
~DataEntryUrlBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.7.2 'Data Reference Box'
|
||||
// Box type: 'dref'
|
||||
class DataReferenceBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint32_t entry_count;
|
||||
nsTArray<nsAutoPtr<DataEntryUrlBox>> urls;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// DataReferenceBox methods
|
||||
DataReferenceBox(ISOControl* aControl);
|
||||
~DataReferenceBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.7.1 'Data Information Box'
|
||||
// Box type: 'dinf'
|
||||
// DataInformationBox contains DataReferenceBox.
|
||||
class DataInformationBox : public DefaultContainerImpl {
|
||||
public:
|
||||
DataInformationBox(ISOControl* aControl);
|
||||
~DataInformationBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.4.5.2 'Video Media Header Box'
|
||||
// Box type: 'vmhd'
|
||||
class VideoMediaHeaderBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint16_t graphicsmode;
|
||||
uint16_t opcolor[3];
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// VideoMediaHeaderBox methods
|
||||
VideoMediaHeaderBox(ISOControl* aControl);
|
||||
~VideoMediaHeaderBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.4.5.3 'Sound Media Header Box'
|
||||
// Box type: 'smhd'
|
||||
class SoundMediaHeaderBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint16_t balance;
|
||||
uint16_t reserved;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// SoundMediaHeaderBox methods
|
||||
SoundMediaHeaderBox(ISOControl* aControl);
|
||||
~SoundMediaHeaderBox();
|
||||
};
|
||||
|
||||
// 14496-12 8.4.4 'Media Information Box'
|
||||
// Box type: 'minf'
|
||||
// MediaInformationBox contains SoundMediaHeaderBox, DataInformationBox and
|
||||
// SampleTableBox.
|
||||
class MediaInformationBox : public DefaultContainerImpl {
|
||||
public:
|
||||
MediaInformationBox(uint32_t aType, ISOControl* aControl);
|
||||
~MediaInformationBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// flags for TrackHeaderBox::flags.
|
||||
#define flags_track_enabled 0x000001
|
||||
#define flags_track_in_movie 0x000002
|
||||
#define flags_track_in_preview 0x000004
|
||||
|
||||
// 14496-12 8.3.2 'Track Header Box'
|
||||
// Box type: 'tkhd'
|
||||
class TrackHeaderBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
// version = 0
|
||||
uint32_t creation_time;
|
||||
uint32_t modification_time;
|
||||
uint32_t track_ID;
|
||||
uint32_t reserved;
|
||||
uint32_t duration;
|
||||
|
||||
uint32_t reserved2[2];
|
||||
uint16_t layer;
|
||||
uint16_t alternate_group;
|
||||
uint16_t volume;
|
||||
uint16_t reserved3;
|
||||
uint32_t matrix[9];
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// TrackHeaderBox methods
|
||||
TrackHeaderBox(uint32_t aType, ISOControl* aControl);
|
||||
~TrackHeaderBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
MetaHelper mMeta;
|
||||
};
|
||||
|
||||
// 14496-12 8.4.3 'Handler Reference Box'
|
||||
// Box type: 'hdlr'
|
||||
class HandlerBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint32_t pre_defined;
|
||||
uint32_t handler_type;
|
||||
uint32_t reserved[3];
|
||||
nsCString name;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// HandlerBox methods
|
||||
HandlerBox(uint32_t aType, ISOControl* aControl);
|
||||
~HandlerBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
// 14496-12 8.4.1 'Media Box'
|
||||
// Box type: 'mdia'
|
||||
// MediaBox contains MediaHeaderBox, HandlerBox, and MediaInformationBox.
|
||||
class MediaBox : public DefaultContainerImpl {
|
||||
public:
|
||||
MediaBox(uint32_t aType, ISOControl* aControl);
|
||||
~MediaBox();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
};
|
||||
|
||||
}
|
||||
#endif // ISOMediaBoxes_h_
|
226
content/media/encoder/fmp4_muxer/ISOMediaWriter.cpp
Normal file
226
content/media/encoder/fmp4_muxer/ISOMediaWriter.cpp
Normal file
@ -0,0 +1,226 @@
|
||||
/* -*- 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 "ISOMediaWriter.h"
|
||||
#include "ISOControl.h"
|
||||
#include "ISOMediaBoxes.h"
|
||||
#include "ISOTrackMetadata.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "MediaEncoder.h"
|
||||
|
||||
#undef LOG
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <android/log.h>
|
||||
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
|
||||
#else
|
||||
#define LOG(args, ...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
const static uint32_t FRAG_DURATION = 2000000; // microsecond per unit
|
||||
|
||||
ISOMediaWriter::ISOMediaWriter(uint32_t aType)
|
||||
: ContainerWriter()
|
||||
, mState(MUXING_HEAD)
|
||||
, mBlobReady(false)
|
||||
, mType(0)
|
||||
{
|
||||
// TODO: replace Audio_Track/Video_Track with HAS_AUDIO/HAS_VIDEO
|
||||
if (aType & HAS_AUDIO) {
|
||||
mType |= Audio_Track;
|
||||
}
|
||||
if (aType & HAS_VIDEO) {
|
||||
mType |= Video_Track;
|
||||
}
|
||||
mControl = new ISOControl();
|
||||
MOZ_COUNT_CTOR(ISOMediaWriter);
|
||||
}
|
||||
|
||||
ISOMediaWriter::~ISOMediaWriter()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ISOMediaWriter);
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOMediaWriter::RunState()
|
||||
{
|
||||
nsresult rv;
|
||||
switch (mState) {
|
||||
case MUXING_HEAD:
|
||||
{
|
||||
rv = mControl->GenerateFtyp();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mControl->GenerateMoov();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mState = MUXING_FRAG;
|
||||
break;
|
||||
}
|
||||
case MUXING_FRAG:
|
||||
{
|
||||
rv = mControl->GenerateMoof(mType);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool EOS;
|
||||
if (ReadyToRunState(EOS) && EOS) {
|
||||
mState = MUXING_DONE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MUXING_DONE:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
mBlobReady = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOMediaWriter::WriteEncodedTrack(const EncodedFrameContainer& aData,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
// Muxing complete, it doesn't allowed to reentry again.
|
||||
if (mState == MUXING_DONE) {
|
||||
MOZ_ASSERT(false);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
FragmentBuffer* frag = nullptr;
|
||||
uint32_t len = aData.GetEncodedFrames().Length();
|
||||
|
||||
if (!len) {
|
||||
// no frame? why bother to WriteEncodedTrack
|
||||
return NS_OK;
|
||||
}
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
nsRefPtr<EncodedFrame> frame(aData.GetEncodedFrames()[i]);
|
||||
EncodedFrame::FrameType type = frame->GetFrameType();
|
||||
if (type == EncodedFrame::AUDIO_FRAME ||
|
||||
type == EncodedFrame::AAC_CSD) {
|
||||
frag = mAudioFragmentBuffer;
|
||||
} else if (type == EncodedFrame::I_FRAME ||
|
||||
type == EncodedFrame::P_FRAME ||
|
||||
type == EncodedFrame::B_FRAME ||
|
||||
type == EncodedFrame::AVC_CSD) {
|
||||
frag = mVideoFragmentBuffer;
|
||||
} else {
|
||||
MOZ_ASSERT(0);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
frag->AddFrame(frame);
|
||||
}
|
||||
|
||||
// Encoder should send CSD (codec specific data) frame before sending the
|
||||
// audio/video frames. When CSD data is ready, it is sufficient to generate a
|
||||
// moov data. If encoder doesn't send CSD yet, muxer needs to wait before
|
||||
// generating anything.
|
||||
if (mType & Audio_Track && !mAudioFragmentBuffer->HasCSD()) {
|
||||
return NS_OK;
|
||||
}
|
||||
if (mType & Video_Track && !mVideoFragmentBuffer->HasCSD()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Only one FrameType in EncodedFrameContainer so it doesn't need to be
|
||||
// inside the for-loop.
|
||||
if (frag && (aFlags & END_OF_STREAM)) {
|
||||
frag->SetEndOfStream();
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
bool EOS;
|
||||
if (ReadyToRunState(EOS)) {
|
||||
// TODO:
|
||||
// The MediaEncoder doesn't use nsRunnable, so thread will be
|
||||
// stocked on that part and the new added nsRunnable won't get to run
|
||||
// before MediaEncoder completing. Before MediaEncoder change, it needs
|
||||
// to call RunState directly.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=950429
|
||||
rv = RunState();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
ISOMediaWriter::ReadyToRunState(bool& aEOS)
|
||||
{
|
||||
aEOS = false;
|
||||
bool bReadyToMux = true;
|
||||
if ((mType & Audio_Track) && (mType & Video_Track)) {
|
||||
if (!mAudioFragmentBuffer->HasEnoughData()) {
|
||||
bReadyToMux = false;
|
||||
}
|
||||
if (!mVideoFragmentBuffer->HasEnoughData()) {
|
||||
bReadyToMux = false;
|
||||
}
|
||||
|
||||
if (mAudioFragmentBuffer->EOS() && mVideoFragmentBuffer->EOS()) {
|
||||
aEOS = true;
|
||||
bReadyToMux = true;
|
||||
}
|
||||
} else if (mType == Audio_Track) {
|
||||
if (!mAudioFragmentBuffer->HasEnoughData()) {
|
||||
bReadyToMux = false;
|
||||
}
|
||||
if (mAudioFragmentBuffer->EOS()) {
|
||||
aEOS = true;
|
||||
bReadyToMux = true;
|
||||
}
|
||||
} else if (mType == Video_Track) {
|
||||
if (!mVideoFragmentBuffer->HasEnoughData()) {
|
||||
bReadyToMux = false;
|
||||
}
|
||||
if (mAudioFragmentBuffer->EOS()) {
|
||||
aEOS = true;
|
||||
bReadyToMux = true;
|
||||
}
|
||||
}
|
||||
|
||||
return bReadyToMux;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOMediaWriter::GetContainerData(nsTArray<nsTArray<uint8_t>>* aOutputBufs,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
if (mBlobReady) {
|
||||
if (mState == MUXING_DONE) {
|
||||
mIsWritingComplete = true;
|
||||
}
|
||||
mBlobReady = false;
|
||||
aOutputBufs->AppendElement();
|
||||
return mControl->GetBuf(aOutputBufs->LastElement());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ISOMediaWriter::SetMetadata(TrackMetadataBase* aMetadata)
|
||||
{
|
||||
if (aMetadata->GetKind() == TrackMetadataBase::METADATA_AAC ) {
|
||||
mControl->SetMetadata(aMetadata);
|
||||
mAudioFragmentBuffer = new FragmentBuffer(Audio_Track,
|
||||
FRAG_DURATION,
|
||||
aMetadata);
|
||||
mControl->SetFragment(mAudioFragmentBuffer);
|
||||
return NS_OK;
|
||||
}
|
||||
if (aMetadata->GetKind() == TrackMetadataBase::METADATA_AVC) {
|
||||
mControl->SetMetadata(aMetadata);
|
||||
mVideoFragmentBuffer = new FragmentBuffer(Video_Track,
|
||||
FRAG_DURATION,
|
||||
aMetadata);
|
||||
mControl->SetFragment(mVideoFragmentBuffer);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
89
content/media/encoder/fmp4_muxer/ISOMediaWriter.h
Normal file
89
content/media/encoder/fmp4_muxer/ISOMediaWriter.h
Normal file
@ -0,0 +1,89 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef ISOMediaWriter_h_
|
||||
#define ISOMediaWriter_h_
|
||||
|
||||
#include "ContainerWriter.h"
|
||||
#include "nsIRunnable.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ISOControl;
|
||||
class FragmentBuffer;
|
||||
class AACTrackMetadata;
|
||||
class AVCTrackMetadata;
|
||||
class ISOMediaWriterRunnable;
|
||||
|
||||
class ISOMediaWriter : public ContainerWriter
|
||||
{
|
||||
public:
|
||||
nsresult WriteEncodedTrack(const EncodedFrameContainer &aData,
|
||||
uint32_t aFlags = 0) MOZ_OVERRIDE;
|
||||
|
||||
nsresult GetContainerData(nsTArray<nsTArray<uint8_t>>* aOutputBufs,
|
||||
uint32_t aFlags = 0) MOZ_OVERRIDE;
|
||||
|
||||
nsresult SetMetadata(TrackMetadataBase* aMetadata) MOZ_OVERRIDE;
|
||||
|
||||
ISOMediaWriter(uint32_t aType);
|
||||
~ISOMediaWriter();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The state of each state will generate one or more blob.
|
||||
* Each blob will be a moov, moof, moof... until receiving EOS.
|
||||
* The generated sequence is:
|
||||
*
|
||||
* moov -> moof -> moof -> ... -> moof -> moof
|
||||
*
|
||||
* Following is the details of each state.
|
||||
* MUXING_HEAD:
|
||||
* It collects the metadata to generate a moov. The state transits to
|
||||
* MUXING_HEAD after output moov blob.
|
||||
*
|
||||
* MUXING_FRAG:
|
||||
* It collects enough audio/video data to generate a fragment blob. This
|
||||
* will be repeated until END_OF_STREAM and then transiting to MUXING_DONE.
|
||||
*
|
||||
* MUXING_DONE:
|
||||
* End of ISOMediaWriter life cycle.
|
||||
*/
|
||||
enum MuxState {
|
||||
MUXING_HEAD,
|
||||
MUXING_FRAG,
|
||||
MUXING_DONE,
|
||||
};
|
||||
|
||||
private:
|
||||
nsresult RunState();
|
||||
|
||||
// True if one of following conditions hold:
|
||||
// 1. Audio/Video accumulates enough data to generate a moof.
|
||||
// 2. Get EOS signal.
|
||||
// aEOS will be assigned to true if it gets EOS signal.
|
||||
bool ReadyToRunState(bool& aEOS);
|
||||
|
||||
// The main class to generate and iso box. Its life time is same as
|
||||
// ISOMediaWriter and deleted only if ISOMediaWriter is destroyed.
|
||||
nsAutoPtr<ISOControl> mControl;
|
||||
|
||||
// Buffers to keep audio/video data frames, they are created when metadata is
|
||||
// received. Only one instance for each media type is allowed and they will be
|
||||
// deleted only if ISOMediaWriter is destroyed.
|
||||
nsAutoPtr<FragmentBuffer> mAudioFragmentBuffer;
|
||||
nsAutoPtr<FragmentBuffer> mVideoFragmentBuffer;
|
||||
|
||||
MuxState mState;
|
||||
|
||||
// A flag to indicate the output buffer is ready to blob out.
|
||||
bool mBlobReady;
|
||||
|
||||
// Combination of HAS_AUDIO or HAS_VIDEO.
|
||||
uint32_t mType;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
#endif // ISOMediaWriter_h_
|
57
content/media/encoder/fmp4_muxer/ISOTrackMetadata.h
Normal file
57
content/media/encoder/fmp4_muxer/ISOTrackMetadata.h
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/. */
|
||||
|
||||
#ifndef ISOTrackMetadata_h_
|
||||
#define ISOTrackMetadata_h_
|
||||
|
||||
#include "TrackMetadataBase.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* track type from spec 8.4.3.3
|
||||
*/
|
||||
#define Audio_Track 0x01
|
||||
#define Video_Track 0x02
|
||||
|
||||
class AACTrackMetadata : public TrackMetadataBase {
|
||||
public:
|
||||
uint32_t SampleRate; // From 14496-3 table 1.16, it could be 7350 ~ 96000.
|
||||
uint32_t FrameDuration; // Audio frame duration based on SampleRate.
|
||||
uint32_t FrameSize; // Audio frame size, 0 is variant size.
|
||||
uint32_t Channels; // Channel number, it should be 1 or 2.
|
||||
|
||||
AACTrackMetadata()
|
||||
: SampleRate(0)
|
||||
, FrameDuration(0)
|
||||
, FrameSize(0)
|
||||
, Channels(0) {
|
||||
MOZ_COUNT_CTOR(AACTrackMetadata);
|
||||
}
|
||||
~AACTrackMetadata() { MOZ_COUNT_DTOR(AACTrackMetadata); }
|
||||
MetadataKind GetKind() const MOZ_OVERRIDE { return METADATA_AAC; }
|
||||
};
|
||||
|
||||
class AVCTrackMetadata : public TrackMetadataBase {
|
||||
public:
|
||||
uint32_t Height;
|
||||
uint32_t Width;
|
||||
uint32_t VideoFrequency; // for AVC, it should be 90k Hz.
|
||||
uint32_t FrameRate; // frames per second
|
||||
|
||||
AVCTrackMetadata()
|
||||
: Height(0)
|
||||
, Width(0)
|
||||
, VideoFrequency(0)
|
||||
, FrameRate(0) {
|
||||
MOZ_COUNT_CTOR(AVCTrackMetadata);
|
||||
}
|
||||
~AVCTrackMetadata() { MOZ_COUNT_DTOR(AVCTrackMetadata); }
|
||||
MetadataKind GetKind() const MOZ_OVERRIDE { return METADATA_AVC; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // ISOTrackMetadata_h_
|
170
content/media/encoder/fmp4_muxer/MP4ESDS.cpp
Normal file
170
content/media/encoder/fmp4_muxer/MP4ESDS.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
/* -*- 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 <climits>
|
||||
#include "ISOControl.h"
|
||||
#include "ISOMediaBoxes.h"
|
||||
#include "MP4ESDS.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
nsresult
|
||||
MP4AudioSampleEntry::Generate(uint32_t* aBoxSize)
|
||||
{
|
||||
sound_version = 0;
|
||||
// step reserved2
|
||||
sample_size = 16;
|
||||
channels = mMeta.mAudMeta->Channels;
|
||||
compressionId = 0;
|
||||
packet_size = 0;
|
||||
timeScale = mMeta.mAudMeta->SampleRate << 16;
|
||||
|
||||
size += sizeof(sound_version) +
|
||||
sizeof(reserved2) +
|
||||
sizeof(sample_size) +
|
||||
sizeof(channels) +
|
||||
sizeof(packet_size) +
|
||||
sizeof(compressionId) +
|
||||
sizeof(timeScale);
|
||||
|
||||
uint32_t box_size;
|
||||
nsresult rv = es->Generate(&box_size);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
size += box_size;
|
||||
|
||||
*aBoxSize = size;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MP4AudioSampleEntry::Write()
|
||||
{
|
||||
BoxSizeChecker checker(mControl, size);
|
||||
SampleEntryBox::Write();
|
||||
mControl->Write(sound_version);
|
||||
mControl->Write(reserved2, sizeof(reserved2));
|
||||
mControl->Write(channels);
|
||||
mControl->Write(sample_size);
|
||||
mControl->Write(compressionId);
|
||||
mControl->Write(packet_size);
|
||||
mControl->Write(timeScale);
|
||||
|
||||
nsresult rv = es->Write();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MP4AudioSampleEntry::MP4AudioSampleEntry(ISOControl* aControl)
|
||||
: SampleEntryBox(NS_LITERAL_CSTRING("mp4a"), Audio_Track, aControl)
|
||||
, sound_version(0)
|
||||
, channels(2)
|
||||
, sample_size(16)
|
||||
, compressionId(0)
|
||||
, packet_size(0)
|
||||
, timeScale(0)
|
||||
{
|
||||
es = new ESDBox(aControl);
|
||||
mMeta.Init(mControl);
|
||||
memset(reserved2, 0 , sizeof(reserved2));
|
||||
MOZ_COUNT_CTOR(MP4AudioSampleEntry);
|
||||
}
|
||||
|
||||
MP4AudioSampleEntry::~MP4AudioSampleEntry()
|
||||
{
|
||||
MOZ_COUNT_DTOR(MP4AudioSampleEntry);
|
||||
}
|
||||
|
||||
nsresult
|
||||
ESDBox::Generate(uint32_t* aBoxSize)
|
||||
{
|
||||
uint32_t box_size;
|
||||
es_descriptor->Generate(&box_size);
|
||||
size += box_size;
|
||||
*aBoxSize = size;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ESDBox::Write()
|
||||
{
|
||||
WRITE_FULLBOX(mControl, size)
|
||||
es_descriptor->Write();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
ESDBox::ESDBox(ISOControl* aControl)
|
||||
: FullBox(NS_LITERAL_CSTRING("esds"), 0, 0, aControl)
|
||||
{
|
||||
es_descriptor = new ES_Descriptor(aControl);
|
||||
MOZ_COUNT_CTOR(ESDBox);
|
||||
}
|
||||
|
||||
ESDBox::~ESDBox()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ESDBox);
|
||||
}
|
||||
|
||||
nsresult
|
||||
ES_Descriptor::Find(const nsACString& aType,
|
||||
nsTArray<nsRefPtr<MuxerOperation>>& aOperations)
|
||||
{
|
||||
// ES_Descriptor is not a real ISOMediaBox, so we return nothing here.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ES_Descriptor::Write()
|
||||
{
|
||||
mControl->Write(tag);
|
||||
mControl->Write(length);
|
||||
mControl->Write(ES_ID);
|
||||
mControl->WriteBits(streamDependenceFlag.to_ulong(), streamDependenceFlag.size());
|
||||
mControl->WriteBits(URL_Flag.to_ulong(), URL_Flag.size());
|
||||
mControl->WriteBits(reserved.to_ulong(), reserved.size());
|
||||
mControl->WriteBits(streamPriority.to_ulong(), streamPriority.size());
|
||||
mControl->Write(DecodeSpecificInfo.Elements(), DecodeSpecificInfo.Length());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ES_Descriptor::Generate(uint32_t* aBoxSize)
|
||||
{
|
||||
nsresult rv;
|
||||
// 14496-1 '8.3.4 DecoderConfigDescriptor'
|
||||
// 14496-1 '10.2.3 SL Packet Header Configuration'
|
||||
Box::MetaHelper meta;
|
||||
meta.Init(mControl);
|
||||
FragmentBuffer* frag = mControl->GetFragment(Audio_Track);
|
||||
rv = frag->GetCSD(DecodeSpecificInfo);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
length = sizeof(ES_ID) + 1;
|
||||
length += DecodeSpecificInfo.Length();
|
||||
|
||||
*aBoxSize = sizeof(tag) + sizeof(length) + length;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
ES_Descriptor::ES_Descriptor(ISOControl* aControl)
|
||||
: tag(ESDescrTag)
|
||||
, length(0)
|
||||
, ES_ID(0)
|
||||
, streamDependenceFlag(0)
|
||||
, URL_Flag(0)
|
||||
, reserved(0)
|
||||
, streamPriority(0)
|
||||
, mControl(aControl)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ES_Descriptor);
|
||||
}
|
||||
|
||||
ES_Descriptor::~ES_Descriptor()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ES_Descriptor);
|
||||
}
|
||||
|
||||
}
|
98
content/media/encoder/fmp4_muxer/MP4ESDS.h
Normal file
98
content/media/encoder/fmp4_muxer/MP4ESDS.h
Normal file
@ -0,0 +1,98 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef MP4ESDS_h_
|
||||
#define MP4ESDS_h_
|
||||
|
||||
#include "nsTArray.h"
|
||||
#include "MuxerOperation.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ISOControl;
|
||||
|
||||
/**
|
||||
* ESDS tag
|
||||
*/
|
||||
#define ESDescrTag 0x03
|
||||
|
||||
/**
|
||||
* 14496-1 '8.3.3 ES_Descriptor'.
|
||||
* It will get DecoderConfigDescriptor and SLConfigDescriptor from
|
||||
* AAC CSD data.
|
||||
*/
|
||||
class ES_Descriptor : public MuxerOperation {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint8_t tag; // ESDescrTag
|
||||
uint8_t length;
|
||||
uint16_t ES_ID;
|
||||
std::bitset<1> streamDependenceFlag;
|
||||
std::bitset<1> URL_Flag;
|
||||
std::bitset<1> reserved;
|
||||
std::bitset<5> streamPriority;
|
||||
|
||||
nsTArray<uint8_t> DecodeSpecificInfo;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
nsresult Find(const nsACString& aType,
|
||||
nsTArray<nsRefPtr<MuxerOperation>>& aOperations) MOZ_OVERRIDE;
|
||||
|
||||
// ES_Descriptor methods
|
||||
ES_Descriptor(ISOControl* aControl);
|
||||
~ES_Descriptor();
|
||||
|
||||
protected:
|
||||
ISOControl* mControl;
|
||||
};
|
||||
|
||||
// 14496-14 5.6 'Sample Description Boxes'
|
||||
// Box type: 'esds'
|
||||
class ESDBox : public FullBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
nsRefPtr<ES_Descriptor> es_descriptor;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// ESDBox methods
|
||||
ESDBox(ISOControl* aControl);
|
||||
~ESDBox();
|
||||
};
|
||||
|
||||
// 14496-14 5.6 'Sample Description Boxes'
|
||||
// Box type: 'mp4a'
|
||||
class MP4AudioSampleEntry : public SampleEntryBox {
|
||||
public:
|
||||
// ISO BMFF members
|
||||
uint16_t sound_version;
|
||||
uint8_t reserved2[6];
|
||||
uint16_t channels;
|
||||
uint16_t sample_size;
|
||||
uint16_t compressionId;
|
||||
uint16_t packet_size;
|
||||
uint32_t timeScale;
|
||||
nsRefPtr<ESDBox> es;
|
||||
|
||||
// MuxerOperation methods
|
||||
nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
|
||||
nsresult Write() MOZ_OVERRIDE;
|
||||
|
||||
// MP4AudioSampleEntry methods
|
||||
MP4AudioSampleEntry(ISOControl* aControl);
|
||||
~MP4AudioSampleEntry();
|
||||
|
||||
protected:
|
||||
uint32_t mTrackType;
|
||||
MetaHelper mMeta;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MP4ESDS_h_
|
56
content/media/encoder/fmp4_muxer/MuxerOperation.h
Normal file
56
content/media/encoder/fmp4_muxer/MuxerOperation.h
Normal file
@ -0,0 +1,56 @@
|
||||
/* -*- 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 "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
#ifndef MuxerOperation_h_
|
||||
#define MuxerOperation_h_
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* The interface for ISO box. All Boxes inherit from this interface.
|
||||
* Generate() and Write() are needed to be called to produce a complete box.
|
||||
*
|
||||
* Generate() will generate all the data structures and their size.
|
||||
*
|
||||
* Write() will write all data into muxing output stream (ISOControl actually)
|
||||
* and update the data which can't be known at Generate() (for example, the
|
||||
* offset of the video data in mp4 file).
|
||||
*
|
||||
* ISO base media format is composed of several container boxes and the contained
|
||||
* boxes. The container boxes hold a list of MuxerOperation which is implemented
|
||||
* by contained boxes. The contained boxes will be called via the list.
|
||||
* For example:
|
||||
* MovieBox (container) ---> boxes (array of MuxerOperation)
|
||||
* |---> MovieHeaderBox (full box)
|
||||
* |---> TrakBox (container)
|
||||
* |---> MovieExtendsBox (container)
|
||||
*
|
||||
* The complete box structure can be found at 14496-12 E.2 "The‘isom’brand".
|
||||
*/
|
||||
class MuxerOperation {
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MuxerOperation)
|
||||
|
||||
// Generate data of this box and its contained box, and calculate box size.
|
||||
virtual nsresult Generate(uint32_t* aBoxSize) = 0;
|
||||
|
||||
// Write data to stream.
|
||||
virtual nsresult Write() = 0;
|
||||
|
||||
// Find the box type via its name (name is the box type defined in 14496-12;
|
||||
// for example, 'moov' is the name of MovieBox).
|
||||
// It can only look child boxes including itself and the box in the boxes
|
||||
// list if exists. It can't look parent boxes.
|
||||
virtual nsresult Find(const nsACString& aType,
|
||||
nsTArray<nsRefPtr<MuxerOperation>>& aOperations) = 0;
|
||||
|
||||
virtual ~MuxerOperation() {}
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
22
content/media/encoder/fmp4_muxer/moz.build
Normal file
22
content/media/encoder/fmp4_muxer/moz.build
Normal file
@ -0,0 +1,22 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; 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/.
|
||||
|
||||
EXPORTS += [
|
||||
'ISOMediaWriter.h',
|
||||
'ISOTrackMetadata.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AVCBox.cpp',
|
||||
'ISOControl.cpp',
|
||||
'ISOMediaBoxes.cpp',
|
||||
'ISOMediaWriter.cpp',
|
||||
'MP4ESDS.cpp',
|
||||
]
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
||||
FINAL_LIBRARY = 'gklayout'
|
@ -4,6 +4,9 @@
|
||||
# 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/.
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
|
||||
PARALLEL_DIRS += ['fmp4_muxer']
|
||||
|
||||
EXPORTS += [
|
||||
'ContainerWriter.h',
|
||||
'EncodedFrameContainer.h',
|
||||
|
@ -75,6 +75,7 @@ this.Keyboard = {
|
||||
mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this);
|
||||
mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this);
|
||||
mm.addMessageListener('Forms:SendKey:Result:OK', this);
|
||||
mm.addMessageListener('Forms:SendKey:Result:Error', this);
|
||||
mm.addMessageListener('Forms:SequenceError', this);
|
||||
mm.addMessageListener('Forms:GetContext:Result:OK', this);
|
||||
mm.addMessageListener('Forms:SetComposition:Result:OK', this);
|
||||
@ -125,6 +126,7 @@ this.Keyboard = {
|
||||
case 'Forms:SetSelectionRange:Result:OK':
|
||||
case 'Forms:ReplaceSurroundingText:Result:OK':
|
||||
case 'Forms:SendKey:Result:OK':
|
||||
case 'Forms:SendKey:Result:Error':
|
||||
case 'Forms:SequenceError':
|
||||
case 'Forms:GetContext:Result:OK':
|
||||
case 'Forms:SetComposition:Result:OK':
|
||||
|
@ -498,6 +498,7 @@ MozInputContext.prototype = {
|
||||
"Keyboard:SetSelectionRange:Result:OK",
|
||||
"Keyboard:ReplaceSurroundingText:Result:OK",
|
||||
"Keyboard:SendKey:Result:OK",
|
||||
"Keyboard:SendKey:Result:Error",
|
||||
"Keyboard:SetComposition:Result:OK",
|
||||
"Keyboard:EndComposition:Result:OK",
|
||||
"Keyboard:SequenceError"]);
|
||||
@ -543,6 +544,9 @@ MozInputContext.prototype = {
|
||||
case "Keyboard:SendKey:Result:OK":
|
||||
resolver.resolve();
|
||||
break;
|
||||
case "Keyboard:SendKey:Result:Error":
|
||||
resolver.reject(json.error);
|
||||
break;
|
||||
case "Keyboard:GetText:Result:OK":
|
||||
resolver.resolve(json.text);
|
||||
break;
|
||||
|
@ -521,11 +521,17 @@ let FormAssistant = {
|
||||
json.charCode, json.modifiers);
|
||||
this._editing = false;
|
||||
|
||||
if (json.requestId) {
|
||||
if (json.requestId && doKeypress) {
|
||||
sendAsyncMessage("Forms:SendKey:Result:OK", {
|
||||
requestId: json.requestId
|
||||
});
|
||||
}
|
||||
else if (json.requestId && !doKeypress) {
|
||||
sendAsyncMessage("Forms:SendKey:Result:Error", {
|
||||
requestId: json.requestId,
|
||||
error: "Keydown event got canceled"
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "Forms:Select:Choice":
|
||||
|
14
dom/inputmethod/mochitest/file_test_sendkey_cancel.html
Normal file
14
dom/inputmethod/mochitest/file_test_sendkey_cancel.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<input id="test-input" type="text" value="Yolo"/>
|
||||
<script type="application/javascript;version=1.7">
|
||||
let input = document.getElementById('test-input');
|
||||
input.focus();
|
||||
|
||||
input.addEventListener('keydown', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,9 +1,10 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
inputmethod_common.js
|
||||
file_test_app.html
|
||||
file_inputmethod.html
|
||||
file_test_app.html
|
||||
file_test_sendkey_cancel.html
|
||||
|
||||
[test_basic.html]
|
||||
[test_bug944397.html]
|
||||
|
||||
[test_sendkey_cancel.html]
|
||||
|
67
dom/inputmethod/mochitest/test_sendkey_cancel.html
Normal file
67
dom/inputmethod/mochitest/test_sendkey_cancel.html
Normal file
@ -0,0 +1,67 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=952080
|
||||
-->
|
||||
<head>
|
||||
<title>SendKey with canceled keydown test for InputMethod API.</title>
|
||||
<script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=952080">Mozilla Bug 952080</a>
|
||||
<p id="display"></p>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="application/javascript;version=1.7">
|
||||
|
||||
// The input context.
|
||||
var gContext = null;
|
||||
|
||||
inputmethod_setup(function() {
|
||||
runTest();
|
||||
});
|
||||
|
||||
function runTest() {
|
||||
let im = navigator.mozInputMethod;
|
||||
|
||||
im.oninputcontextchange = function() {
|
||||
ok(true, 'inputcontextchange event was fired.');
|
||||
im.oninputcontextchange = null;
|
||||
|
||||
gContext = im.inputcontext;
|
||||
if (!gContext) {
|
||||
ok(false, 'Should have a non-null inputcontext.');
|
||||
inputmethod_cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
test();
|
||||
};
|
||||
|
||||
// Set current page as an input method.
|
||||
SpecialPowers.wrap(im).setActive(true);
|
||||
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.src = 'file_test_sendkey_cancel.html';
|
||||
iframe.setAttribute('mozbrowser', true);
|
||||
document.body.appendChild(iframe);
|
||||
}
|
||||
|
||||
function test() {
|
||||
gContext.sendKey(0, 'j', 0).then(function() {
|
||||
ok(false, 'sendKey was incorrectly resolved');
|
||||
|
||||
inputmethod_cleanup();
|
||||
}, function(e) {
|
||||
ok(true, 'sendKey was rejected');
|
||||
|
||||
inputmethod_cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -14,14 +14,20 @@ interface nsIDOMMozNetworkStatsData : nsISupports
|
||||
readonly attribute jsval date; // Date.
|
||||
};
|
||||
|
||||
[scriptable, builtinclass, uuid(b6fc4b14-628d-4c99-bf4e-e4ed56916cbe)]
|
||||
[scriptable, builtinclass, uuid(f1996e44-1057-4d4b-8ff8-919e76c4cfa9)]
|
||||
interface nsIDOMMozNetworkStats : nsISupports
|
||||
{
|
||||
/**
|
||||
* Manifest URL of an application for specifying the per-app
|
||||
* stats of the specified app. If null, system stats are returned.
|
||||
* App manifest URL of an application for specifying the per-app stats of the
|
||||
* specified app.
|
||||
*/
|
||||
readonly attribute DOMString manifestURL;
|
||||
readonly attribute DOMString appManifestURL;
|
||||
|
||||
/**
|
||||
* Service type is used to retrieve the corresponding "system-only" stats.
|
||||
* E.g., "Tethering", "OTA", etc.
|
||||
*/
|
||||
readonly attribute DOMString serviceType;
|
||||
|
||||
/**
|
||||
* Network the returned data belongs to.
|
||||
|
@ -6,6 +6,22 @@
|
||||
|
||||
interface nsIDOMDOMRequest;
|
||||
|
||||
/**
|
||||
* Provide the detailed options for specifying different kinds of data filtering
|
||||
* in getSamples function.
|
||||
*/
|
||||
dictionary NetworkStatsGetOptions
|
||||
{
|
||||
/**
|
||||
* App manifest URL is used to filter network stats by app, while service type
|
||||
* is used to filter stats by system service.
|
||||
* Note that, these two options cannot be specified at the same time for now;
|
||||
* others, an NS_ERROR_NOT_IMPLMENTED exception will be thrown.
|
||||
*/
|
||||
DOMString appManifestURL;
|
||||
DOMString serviceType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a data interface for which the manager is recording statistics.
|
||||
*/
|
||||
@ -35,7 +51,7 @@ interface nsIDOMMozNetworkStatsAlarm : nsISupports
|
||||
readonly attribute jsval data;
|
||||
};
|
||||
|
||||
[scriptable, uuid(50d109b8-0d7f-4208-81fe-5f07a759f159)]
|
||||
[scriptable, uuid(8a66f4c1-0c25-4a66-9fc5-0106947b91f9)]
|
||||
interface nsIDOMMozNetworkStatsManager : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -47,15 +63,15 @@ interface nsIDOMMozNetworkStatsManager : nsISupports
|
||||
/**
|
||||
* Find samples between two dates start and end, both included.
|
||||
*
|
||||
* If manifestURL is provided, per-app usage is retrieved,
|
||||
* otherwise the target will be system usage.
|
||||
* If options is provided, per-app or per-system service usage will be
|
||||
* retrieved; otherwise the target will be overall system usage.
|
||||
*
|
||||
* If success, the request result will be an nsIDOMMozNetworkStats object.
|
||||
*/
|
||||
nsIDOMDOMRequest getSamples(in nsIDOMMozNetworkStatsInterface network,
|
||||
in jsval start,
|
||||
in jsval end,
|
||||
[optional] in DOMString manifestURL);
|
||||
[optional] in jsval options /* NetworkStatsGetOptions */);
|
||||
|
||||
/**
|
||||
* Install an alarm on a network. The network must be in the return of
|
||||
@ -107,6 +123,11 @@ interface nsIDOMMozNetworkStatsManager : nsISupports
|
||||
*/
|
||||
nsIDOMDOMRequest getAvailableNetworks(); // array of nsIDOMMozNetworkStatsInterface.
|
||||
|
||||
/**
|
||||
* Return available service types that used to be saved in the database.
|
||||
*/
|
||||
nsIDOMDOMRequest getAvailableServiceTypes(); // array of string.
|
||||
|
||||
/**
|
||||
* Minimum time in milliseconds between samples stored in the database.
|
||||
*/
|
||||
|
@ -16,7 +16,7 @@ interface nsINetworkStatsServiceProxyCallback : nsISupports
|
||||
void notify(in boolean aResult, in jsval aMessage);
|
||||
};
|
||||
|
||||
[scriptable, uuid(facef032-3fd9-4509-a396-83d94c1a11ae)]
|
||||
[scriptable, uuid(705c01d6-8574-464c-8ec9-ac1522a45546)]
|
||||
interface nsINetworkStatsServiceProxy : nsISupports
|
||||
{
|
||||
/*
|
||||
@ -26,6 +26,7 @@ interface nsINetworkStatsServiceProxy : nsISupports
|
||||
* @param aTimeStamp time stamp
|
||||
* @param aRxBytes received data amount
|
||||
* @param aTxBytes transmitted data amount
|
||||
* @param aIsAccumulative is stats accumulative
|
||||
* @param aCallback an optional callback
|
||||
*/
|
||||
void saveAppStats(in unsigned long aAppId,
|
||||
@ -33,5 +34,24 @@ interface nsINetworkStatsServiceProxy : nsISupports
|
||||
in unsigned long long aTimeStamp,
|
||||
in unsigned long long aRxBytes,
|
||||
in unsigned long long aTxBytes,
|
||||
in boolean aIsAccumulative,
|
||||
[optional] in nsINetworkStatsServiceProxyCallback aCallback);
|
||||
|
||||
/*
|
||||
* An interface used to record per-system service traffic data.
|
||||
* @param aServiceType system service type
|
||||
* @param aNetworkInterface network
|
||||
* @param aTimeStamp time stamp
|
||||
* @param aRxBytes received data amount
|
||||
* @param aTxBytes transmitted data amount
|
||||
* @param aIsAccumulative is stats accumulative
|
||||
* @param aCallback an optional callback
|
||||
*/
|
||||
void saveServiceStats(in string aServiceType,
|
||||
in nsINetworkInterface aNetwork,
|
||||
in unsigned long long aTimeStamp,
|
||||
in unsigned long long aRxBytes,
|
||||
in unsigned long long aTxBytes,
|
||||
in boolean aIsAccumulative,
|
||||
[optional] in nsINetworkStatsServiceProxyCallback aCallback);
|
||||
};
|
||||
|
@ -16,8 +16,9 @@ Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
|
||||
Cu.importGlobalProperties(["indexedDB"]);
|
||||
|
||||
const DB_NAME = "net_stats";
|
||||
const DB_VERSION = 5;
|
||||
const STATS_STORE_NAME = "net_stats";
|
||||
const DB_VERSION = 6;
|
||||
const DEPRECATED_STORE_NAME = "net_stats";
|
||||
const STATS_STORE_NAME = "net_stats_store";
|
||||
const ALARMS_STORE_NAME = "net_alarm";
|
||||
|
||||
// Constant defining the maximum values allowed per interface. If more, older
|
||||
@ -59,7 +60,7 @@ NetworkStatsDB.prototype = {
|
||||
* Create the initial database schema.
|
||||
*/
|
||||
|
||||
objectStore = db.createObjectStore(STATS_STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
|
||||
objectStore = db.createObjectStore(DEPRECATED_STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
|
||||
objectStore.createIndex("connectionType", "connectionType", { unique: false });
|
||||
objectStore.createIndex("timestamp", "timestamp", { unique: false });
|
||||
objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
|
||||
@ -78,9 +79,9 @@ NetworkStatsDB.prototype = {
|
||||
// to modify the keyPath is mandatory to delete the object store
|
||||
// and create it again. Old data is going to be deleted because the
|
||||
// networkId for each sample can not be set.
|
||||
db.deleteObjectStore(STATS_STORE_NAME);
|
||||
db.deleteObjectStore(DEPRECATED_STORE_NAME);
|
||||
|
||||
objectStore = db.createObjectStore(STATS_STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
|
||||
objectStore = db.createObjectStore(DEPRECATED_STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
|
||||
objectStore.createIndex("appId", "appId", { unique: false });
|
||||
objectStore.createIndex("network", "network", { unique: false });
|
||||
objectStore.createIndex("networkType", "networkType", { unique: false });
|
||||
@ -95,7 +96,7 @@ NetworkStatsDB.prototype = {
|
||||
}
|
||||
} else if (currVersion == 3) {
|
||||
// Delete redundent indexes (leave "network" only).
|
||||
objectStore = aTransaction.objectStore(STATS_STORE_NAME);
|
||||
objectStore = aTransaction.objectStore(DEPRECATED_STORE_NAME);
|
||||
if (objectStore.indexNames.contains("appId")) {
|
||||
objectStore.deleteIndex("appId");
|
||||
}
|
||||
@ -124,7 +125,7 @@ NetworkStatsDB.prototype = {
|
||||
} else if (currVersion == 4) {
|
||||
// In order to manage alarms, it is necessary to use a global counter
|
||||
// (totalBytes) that will increase regardless of the system reboot.
|
||||
objectStore = aTransaction.objectStore(STATS_STORE_NAME);
|
||||
objectStore = aTransaction.objectStore(DEPRECATED_STORE_NAME);
|
||||
|
||||
// Now, systemBytes will hold the old totalBytes and totalBytes will
|
||||
// keep the increasing counter. |counters| will keep the track of
|
||||
@ -183,12 +184,42 @@ NetworkStatsDB.prototype = {
|
||||
if (DEBUG) {
|
||||
debug("Created alarms store for version 5");
|
||||
}
|
||||
} else if (currVersion == 5) {
|
||||
// In contrast to "per-app" traffic data, "system-only" traffic data
|
||||
// refers to data which can not be identified by any applications.
|
||||
// To further support "system-only" data storage, the data can be
|
||||
// saved by service type (e.g., Tethering, OTA). Thus it's needed to
|
||||
// have a new key ("serviceType") for the ojectStore.
|
||||
let newObjectStore;
|
||||
newObjectStore = db.createObjectStore(STATS_STORE_NAME,
|
||||
{ keyPath: ["appId", "serviceType", "network", "timestamp"] });
|
||||
newObjectStore.createIndex("network", "network", { unique: false });
|
||||
|
||||
// Copy the data from the original objectStore to the new objectStore.
|
||||
objectStore = aTransaction.objectStore(DEPRECATED_STORE_NAME);
|
||||
objectStore.openCursor().onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
db.deleteObjectStore(DEPRECATED_STORE_NAME);
|
||||
return;
|
||||
}
|
||||
|
||||
let newStats = cursor.value;
|
||||
newStats.serviceType = "";
|
||||
newObjectStore.put(newStats);
|
||||
cursor.continue();
|
||||
};
|
||||
|
||||
if (DEBUG) {
|
||||
debug("Added new key 'serviceType' for version 6");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
importData: function importData(aStats) {
|
||||
let stats = { appId: aStats.appId,
|
||||
serviceType: aStats.serviceType,
|
||||
network: [aStats.networkId, aStats.networkType],
|
||||
timestamp: aStats.timestamp,
|
||||
rxBytes: aStats.rxBytes,
|
||||
@ -203,6 +234,7 @@ NetworkStatsDB.prototype = {
|
||||
|
||||
exportData: function exportData(aStats) {
|
||||
let stats = { appId: aStats.appId,
|
||||
serviceType: aStats.serviceType,
|
||||
networkId: aStats.network[0],
|
||||
networkType: aStats.network[1],
|
||||
timestamp: aStats.timestamp,
|
||||
@ -223,18 +255,20 @@ NetworkStatsDB.prototype = {
|
||||
},
|
||||
|
||||
saveStats: function saveStats(aStats, aResultCb) {
|
||||
let isAccumulative = aStats.isAccumulative;
|
||||
let timestamp = this.normalizeDate(aStats.date);
|
||||
|
||||
let stats = { appId: aStats.appId,
|
||||
serviceType: aStats.serviceType,
|
||||
networkId: aStats.networkId,
|
||||
networkType: aStats.networkType,
|
||||
timestamp: timestamp,
|
||||
rxBytes: (aStats.appId == 0) ? 0 : aStats.rxBytes,
|
||||
txBytes: (aStats.appId == 0) ? 0 : aStats.txBytes,
|
||||
rxSystemBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
|
||||
txSystemBytes: (aStats.appId == 0) ? aStats.txBytes : 0,
|
||||
rxTotalBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
|
||||
txTotalBytes: (aStats.appId == 0) ? aStats.txBytes : 0 };
|
||||
rxBytes: (isAccumulative) ? 0 : aStats.rxBytes,
|
||||
txBytes: (isAccumulative) ? 0 : aStats.txBytes,
|
||||
rxSystemBytes: (isAccumulative) ? aStats.rxBytes : 0,
|
||||
txSystemBytes: (isAccumulative) ? aStats.txBytes : 0,
|
||||
rxTotalBytes: (isAccumulative) ? aStats.rxBytes : 0,
|
||||
txTotalBytes: (isAccumulative) ? aStats.txBytes : 0 };
|
||||
|
||||
stats = this.importData(stats);
|
||||
|
||||
@ -244,7 +278,7 @@ NetworkStatsDB.prototype = {
|
||||
debug("New stats: " + JSON.stringify(stats));
|
||||
}
|
||||
|
||||
let request = aStore.index("network").openCursor(stats.network, "prev");
|
||||
let request = aStore.index("network").openCursor(stats.network, "prev");
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
@ -254,7 +288,7 @@ NetworkStatsDB.prototype = {
|
||||
// interface comes up and the point when the database is initialized.
|
||||
// In this short interval some traffic data are generated but are not
|
||||
// registered by the first sample.
|
||||
if (stats.appId == 0) {
|
||||
if (isAccumulative) {
|
||||
stats.rxBytes = stats.rxTotalBytes;
|
||||
stats.txBytes = stats.txTotalBytes;
|
||||
}
|
||||
@ -263,21 +297,24 @@ NetworkStatsDB.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stats.appId != cursor.value.appId) {
|
||||
let value = cursor.value;
|
||||
if (stats.appId != value.appId ||
|
||||
(stats.appId == 0 && stats.serviceType != value.serviceType)) {
|
||||
cursor.continue();
|
||||
return;
|
||||
}
|
||||
|
||||
// There are old samples
|
||||
if (DEBUG) {
|
||||
debug("Last value " + JSON.stringify(cursor.value));
|
||||
debug("Last value " + JSON.stringify(value));
|
||||
}
|
||||
|
||||
// Remove stats previous to now - VALUE_MAX_LENGTH
|
||||
this._removeOldStats(aTxn, aStore, stats.appId, stats.network, stats.timestamp);
|
||||
this._removeOldStats(aTxn, aStore, stats.appId, stats.serviceType,
|
||||
stats.network, stats.timestamp);
|
||||
|
||||
// Process stats before save
|
||||
this._processSamplesDiff(aTxn, aStore, cursor, stats);
|
||||
this._processSamplesDiff(aTxn, aStore, cursor, stats, isAccumulative);
|
||||
}.bind(this);
|
||||
}.bind(this), aResultCb);
|
||||
},
|
||||
@ -286,7 +323,11 @@ NetworkStatsDB.prototype = {
|
||||
* This function check that stats are saved in the database following the sample rate.
|
||||
* In this way is easier to find elements when stats are requested.
|
||||
*/
|
||||
_processSamplesDiff: function _processSamplesDiff(aTxn, aStore, aLastSampleCursor, aNewSample) {
|
||||
_processSamplesDiff: function _processSamplesDiff(aTxn,
|
||||
aStore,
|
||||
aLastSampleCursor,
|
||||
aNewSample,
|
||||
aIsAccumulative) {
|
||||
let lastSample = aLastSampleCursor.value;
|
||||
|
||||
// Get difference between last and new sample.
|
||||
@ -303,14 +344,14 @@ NetworkStatsDB.prototype = {
|
||||
lastSample.timestamp + " - diff: " + diff);
|
||||
}
|
||||
|
||||
// If the incoming data is obtained from netd (|newSample.appId| is 0),
|
||||
// the new |txBytes|/|rxBytes| is assigend by the differnce between the new
|
||||
// If the incoming data has a accumulation feature, the new
|
||||
// |txBytes|/|rxBytes| is assigend by differnces between the new
|
||||
// |txTotalBytes|/|rxTotalBytes| and the last |txTotalBytes|/|rxTotalBytes|.
|
||||
// Else, the incoming data is per-app data (|newSample.appId| is not 0),
|
||||
// the |txBytes|/|rxBytes| is directly the new |txBytes|/|rxBytes|.
|
||||
// Else, if incoming data is non-accumulative, the |txBytes|/|rxBytes|
|
||||
// is the new |txBytes|/|rxBytes|.
|
||||
let rxDiff = 0;
|
||||
let txDiff = 0;
|
||||
if (aNewSample.appId == 0) {
|
||||
if (aIsAccumulative) {
|
||||
rxDiff = aNewSample.rxSystemBytes - lastSample.rxSystemBytes;
|
||||
txDiff = aNewSample.txSystemBytes - lastSample.txSystemBytes;
|
||||
if (rxDiff < 0 || txDiff < 0) {
|
||||
@ -330,10 +371,10 @@ NetworkStatsDB.prototype = {
|
||||
if (diff == 1) {
|
||||
// New element.
|
||||
|
||||
// If the incoming data is per-app data, new |rxTotalBytes|/|txTotalBytes|
|
||||
// needs to be obtained by adding new |rxBytes|/|txBytes| to last
|
||||
// |rxTotalBytes|/|txTotalBytes|.
|
||||
if (aNewSample.appId != 0) {
|
||||
// If the incoming data is non-accumulative, the new
|
||||
// |rxTotalBytes|/|txTotalBytes| needs to be updated by adding new
|
||||
// |rxBytes|/|txBytes| to the last |rxTotalBytes|/|txTotalBytes|.
|
||||
if (!aIsAccumulative) {
|
||||
aNewSample.rxTotalBytes = aNewSample.rxBytes + lastSample.rxTotalBytes;
|
||||
aNewSample.txTotalBytes = aNewSample.txBytes + lastSample.txTotalBytes;
|
||||
}
|
||||
@ -353,6 +394,7 @@ NetworkStatsDB.prototype = {
|
||||
for (let i = diff - 2; i >= 0; i--) {
|
||||
let time = aNewSample.timestamp - SAMPLE_RATE * (i + 1);
|
||||
let sample = { appId: aNewSample.appId,
|
||||
serviceType: aNewSample.serviceType,
|
||||
network: aNewSample.network,
|
||||
timestamp: time,
|
||||
rxBytes: 0,
|
||||
@ -406,11 +448,12 @@ NetworkStatsDB.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_removeOldStats: function _removeOldStats(aTxn, aStore, aAppId, aNetwork, aDate) {
|
||||
_removeOldStats: function _removeOldStats(aTxn, aStore, aAppId, aServiceType,
|
||||
aNetwork, aDate) {
|
||||
// Callback function to remove old items when new ones are added.
|
||||
let filterDate = aDate - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
|
||||
let lowerFilter = [aAppId, aNetwork, 0];
|
||||
let upperFilter = [aAppId, aNetwork, filterDate];
|
||||
let lowerFilter = [aAppId, aServiceType, aNetwork, 0];
|
||||
let upperFilter = [aAppId, aServiceType, aNetwork, filterDate];
|
||||
let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
|
||||
let lastSample = null;
|
||||
let self = this;
|
||||
@ -469,6 +512,7 @@ NetworkStatsDB.prototype = {
|
||||
timestamp = self.normalizeDate(timestamp);
|
||||
sample.timestamp = timestamp;
|
||||
sample.appId = 0;
|
||||
sample.serviceType = "";
|
||||
sample.rxBytes = 0;
|
||||
sample.txBytes = 0;
|
||||
|
||||
@ -528,22 +572,24 @@ NetworkStatsDB.prototype = {
|
||||
}.bind(this), aResultCb);
|
||||
},
|
||||
|
||||
find: function find(aResultCb, aNetwork, aStart, aEnd, aAppId, aManifestURL) {
|
||||
find: function find(aResultCb, aAppId, aServiceType, aNetwork,
|
||||
aStart, aEnd, aAppManifestURL) {
|
||||
let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
|
||||
let start = this.normalizeDate(aStart);
|
||||
let end = this.normalizeDate(aEnd);
|
||||
|
||||
if (DEBUG) {
|
||||
debug("Find samples for appId: " + aAppId + " network " +
|
||||
JSON.stringify(aNetwork) + " from " + start + " until " + end);
|
||||
debug("Find samples for appId: " + aAppId + " serviceType: " +
|
||||
aServiceType + " network: " + JSON.stringify(aNetwork) + " from " +
|
||||
start + " until " + end);
|
||||
debug("Start time: " + new Date(start));
|
||||
debug("End time: " + new Date(end));
|
||||
}
|
||||
|
||||
this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
|
||||
let network = [aNetwork.id, aNetwork.type];
|
||||
let lowerFilter = [aAppId, network, start];
|
||||
let upperFilter = [aAppId, network, end];
|
||||
let lowerFilter = [aAppId, aServiceType, network, start];
|
||||
let upperFilter = [aAppId, aServiceType, network, end];
|
||||
let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
|
||||
|
||||
let data = [];
|
||||
@ -566,7 +612,8 @@ NetworkStatsDB.prototype = {
|
||||
// now - VALUES_MAX_LENGTH, fill with empty samples.
|
||||
this.fillResultSamples(start + offset, end + offset, data);
|
||||
|
||||
aTxn.result.manifestURL = aManifestURL;
|
||||
aTxn.result.appManifestURL = aAppManifestURL;
|
||||
aTxn.result.serviceType = aServiceType;
|
||||
aTxn.result.network = aNetwork;
|
||||
aTxn.result.start = aStart;
|
||||
aTxn.result.end = aEnd;
|
||||
@ -634,6 +681,24 @@ NetworkStatsDB.prototype = {
|
||||
}, aResultCb);
|
||||
},
|
||||
|
||||
getAvailableServiceTypes: function getAvailableServiceTypes(aResultCb) {
|
||||
this.dbNewTxn("readonly", function(aTxn, aStore) {
|
||||
if (!aTxn.result) {
|
||||
aTxn.result = [];
|
||||
}
|
||||
|
||||
let request = aStore.index("serviceType").openKeyCursor(null, "nextunique");
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
let cursor = event.target.result;
|
||||
if (cursor) {
|
||||
aTxn.result.push({ serviceType: cursor.key });
|
||||
cursor.continue();
|
||||
return;
|
||||
}
|
||||
};
|
||||
}, aResultCb);
|
||||
},
|
||||
|
||||
get sampleRate () {
|
||||
return SAMPLE_RATE;
|
||||
},
|
||||
|
@ -86,14 +86,15 @@ NetworkStatsInterface.prototype = {
|
||||
|
||||
// NetworkStats
|
||||
const NETWORKSTATS_CONTRACTID = "@mozilla.org/networkstats;1";
|
||||
const NETWORKSTATS_CID = Components.ID("{b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}");
|
||||
const NETWORKSTATS_CID = Components.ID("{f1996e44-1057-4d4b-8ff8-919e76c4cfa9}");
|
||||
const nsIDOMMozNetworkStats = Ci.nsIDOMMozNetworkStats;
|
||||
|
||||
function NetworkStats(aWindow, aStats) {
|
||||
if (DEBUG) {
|
||||
debug("NetworkStats Constructor");
|
||||
}
|
||||
this.manifestURL = aStats.manifestURL || null;
|
||||
this.appManifestURL = aStats.appManifestURL || null;
|
||||
this.serviceType = aStats.serviceType || null;
|
||||
this.network = new NetworkStatsInterface(aStats.network);
|
||||
this.start = aStats.start || null;
|
||||
this.end = aStats.end || null;
|
||||
@ -106,7 +107,8 @@ function NetworkStats(aWindow, aStats) {
|
||||
|
||||
NetworkStats.prototype = {
|
||||
__exposedProps__: {
|
||||
manifestURL: 'r',
|
||||
appManifestURL: 'r',
|
||||
serviceType: 'r',
|
||||
network: 'r',
|
||||
start: 'r',
|
||||
end: 'r',
|
||||
@ -157,7 +159,7 @@ NetworkStatsAlarm.prototype = {
|
||||
// NetworkStatsManager
|
||||
|
||||
const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
|
||||
const NETWORKSTATSMANAGER_CID = Components.ID("{50d109b8-0d7f-4208-81fe-5f07a759f159}");
|
||||
const NETWORKSTATSMANAGER_CID = Components.ID("{8a66f4c1-0c25-4a66-9fc5-0106947b91f9}");
|
||||
const nsIDOMMozNetworkStatsManager = Ci.nsIDOMMozNetworkStatsManager;
|
||||
|
||||
function NetworkStatsManager() {
|
||||
@ -175,7 +177,7 @@ NetworkStatsManager.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
getSamples: function getSamples(aNetwork, aStart, aEnd, aManifestURL) {
|
||||
getSamples: function getSamples(aNetwork, aStart, aEnd, aOptions) {
|
||||
this.checkPrivileges();
|
||||
|
||||
if (aStart.constructor.name !== "Date" ||
|
||||
@ -184,6 +186,16 @@ NetworkStatsManager.prototype = {
|
||||
throw Components.results.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
let appManifestURL = null;
|
||||
let serviceType = null;
|
||||
if (aOptions) {
|
||||
if (aOptions.appManifestURL && aOptions.serviceType) {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
appManifestURL = aOptions.appManifestURL;
|
||||
serviceType = aOptions.serviceType;
|
||||
}
|
||||
|
||||
// TODO Bug 929410 Date object cannot correctly pass through cpmm/ppmm IPC
|
||||
// This is just a work-around by passing timestamp numbers.
|
||||
aStart = aStart.getTime();
|
||||
@ -194,7 +206,8 @@ NetworkStatsManager.prototype = {
|
||||
{ network: aNetwork,
|
||||
start: aStart,
|
||||
end: aEnd,
|
||||
manifestURL: aManifestURL,
|
||||
appManifestURL: appManifestURL,
|
||||
serviceType: serviceType,
|
||||
id: this.getRequestId(request) });
|
||||
return request;
|
||||
},
|
||||
@ -273,6 +286,15 @@ NetworkStatsManager.prototype = {
|
||||
return request;
|
||||
},
|
||||
|
||||
getAvailableServiceTypes: function getAvailableServiceTypes() {
|
||||
this.checkPrivileges();
|
||||
|
||||
let request = this.createRequest();
|
||||
cpmm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes",
|
||||
{ id: this.getRequestId(request) });
|
||||
return request;
|
||||
},
|
||||
|
||||
get sampleRate() {
|
||||
this.checkPrivileges();
|
||||
return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0];
|
||||
@ -325,6 +347,20 @@ NetworkStatsManager.prototype = {
|
||||
Services.DOMRequest.fireSuccess(req, networks);
|
||||
break;
|
||||
|
||||
case "NetworkStats:GetAvailableServiceTypes:Return":
|
||||
if (msg.error) {
|
||||
Services.DOMRequest.fireError(req, msg.error);
|
||||
return;
|
||||
}
|
||||
|
||||
let serviceTypes = Cu.createArrayIn(this._window);
|
||||
for (let i = 0; i < msg.result.length; i++) {
|
||||
serviceTypes.push(msg.result[i]);
|
||||
}
|
||||
|
||||
Services.DOMRequest.fireSuccess(req, serviceTypes);
|
||||
break;
|
||||
|
||||
case "NetworkStats:Clear:Return":
|
||||
case "NetworkStats:ClearAll:Return":
|
||||
if (msg.error) {
|
||||
@ -391,6 +427,7 @@ NetworkStatsManager.prototype = {
|
||||
|
||||
this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
|
||||
"NetworkStats:GetAvailableNetworks:Return",
|
||||
"NetworkStats:GetAvailableServiceTypes:Return",
|
||||
"NetworkStats:Clear:Return",
|
||||
"NetworkStats:ClearAll:Return",
|
||||
"NetworkStats:SetAlarm:Return",
|
||||
|
@ -1,8 +1,8 @@
|
||||
component {3b16fe17-5583-483a-b486-b64a3243221c} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
|
||||
|
||||
component {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStats;1 {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}
|
||||
component {f1996e44-1057-4d4b-8ff8-919e76c4cfa9} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStats;1 {f1996e44-1057-4d4b-8ff8-919e76c4cfa9}
|
||||
|
||||
component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
|
||||
@ -10,6 +10,6 @@ contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5
|
||||
component {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkstatsalarm;1 {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c}
|
||||
|
||||
component {50d109b8-0d7f-4208-81fe-5f07a759f159} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStatsManager;1 {50d109b8-0d7f-4208-81fe-5f07a759f159}
|
||||
component {8a66f4c1-0c25-4a66-9fc5-0106947b91f9} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStatsManager;1 {8a66f4c1-0c25-4a66-9fc5-0106947b91f9}
|
||||
category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1
|
||||
|
@ -29,7 +29,7 @@ const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
|
||||
const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
|
||||
const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
|
||||
|
||||
// The maximum traffic amount can be saved in the |cachedAppStats|.
|
||||
// The maximum traffic amount can be saved in the |cachedStats|.
|
||||
const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
||||
@ -98,6 +98,7 @@ this.NetworkStatsService = {
|
||||
"NetworkStats:GetAlarms",
|
||||
"NetworkStats:RemoveAlarms",
|
||||
"NetworkStats:GetAvailableNetworks",
|
||||
"NetworkStats:GetAvailableServiceTypes",
|
||||
"NetworkStats:SampleRate",
|
||||
"NetworkStats:MaxStorageAge"];
|
||||
|
||||
@ -111,9 +112,9 @@ this.NetworkStatsService = {
|
||||
this.timer.initWithCallback(this, this._db.sampleRate,
|
||||
Ci.nsITimer.TYPE_REPEATING_PRECISE);
|
||||
|
||||
// App stats are firstly stored in the cached.
|
||||
this.cachedAppStats = Object.create(null);
|
||||
this.cachedAppStatsDate = new Date();
|
||||
// Stats not from netd are firstly stored in the cached.
|
||||
this.cachedStats = Object.create(null);
|
||||
this.cachedStatsDate = new Date();
|
||||
|
||||
this.updateQueue = [];
|
||||
this.isQueueRunning = false;
|
||||
@ -154,6 +155,9 @@ this.NetworkStatsService = {
|
||||
case "NetworkStats:GetAvailableNetworks":
|
||||
this.getAvailableNetworks(mm, msg);
|
||||
break;
|
||||
case "NetworkStats:GetAvailableServiceTypes":
|
||||
this.getAvailableServiceTypes(mm, msg);
|
||||
break;
|
||||
case "NetworkStats:SampleRate":
|
||||
// This message is sync.
|
||||
return this._db.sampleRate;
|
||||
@ -307,6 +311,13 @@ this.NetworkStatsService = {
|
||||
});
|
||||
},
|
||||
|
||||
getAvailableServiceTypes: function getAvailableServiceTypes(mm, msg) {
|
||||
this._db.getAvailableServiceTypes(function onGetServiceTypes(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
});
|
||||
},
|
||||
|
||||
initAlarms: function initAlarms() {
|
||||
debug("Init usage alarms");
|
||||
let self = this;
|
||||
@ -340,17 +351,20 @@ this.NetworkStatsService = {
|
||||
let netId = this.getNetworkId(network.id, network.type);
|
||||
|
||||
let appId = 0;
|
||||
let manifestURL = msg.manifestURL;
|
||||
if (manifestURL) {
|
||||
appId = appsService.getAppLocalIdByManifestURL(manifestURL);
|
||||
let appManifestURL = msg.appManifestURL;
|
||||
if (appManifestURL) {
|
||||
appId = appsService.getAppLocalIdByManifestURL(appManifestURL);
|
||||
|
||||
if (!appId) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: "Invalid manifestURL", result: null });
|
||||
{ id: msg.id,
|
||||
error: "Invalid appManifestURL", result: null });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let serviceType = msg.serviceType || "";
|
||||
|
||||
let start = new Date(msg.start);
|
||||
let end = new Date(msg.end);
|
||||
|
||||
@ -359,13 +373,13 @@ this.NetworkStatsService = {
|
||||
if (this._networks[netId]) {
|
||||
this.updateStats(netId, function onStatsUpdated(aResult, aMessage) {
|
||||
debug("getstats for network " + network.id + " of type " + network.type);
|
||||
debug("appId: " + appId + " from manifestURL: " + manifestURL);
|
||||
debug("appId: " + appId + " from appManifestURL: " + appManifestURL);
|
||||
|
||||
self.updateCachedAppStats(function onAppStatsUpdated(aResult, aMessage) {
|
||||
self.updateCachedStats(function onStatsUpdated(aResult, aMessage) {
|
||||
self._db.find(function onStatsFound(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
}, network, start, end, appId, manifestURL);
|
||||
}, appId, serviceType, network, start, end, appManifestURL);
|
||||
});
|
||||
});
|
||||
return;
|
||||
@ -394,7 +408,7 @@ this.NetworkStatsService = {
|
||||
self._db.find(function onStatsFound(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
}, network, start, end, appId, manifestURL);
|
||||
}, appId, serviceType, network, start, end, appManifestURL);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -453,8 +467,8 @@ this.NetworkStatsService = {
|
||||
},
|
||||
|
||||
updateAllStats: function updateAllStats(aCallback) {
|
||||
// Update |cachedAppStats|.
|
||||
this.updateCachedAppStats();
|
||||
// Update |cachedStats|.
|
||||
this.updateCachedStats();
|
||||
|
||||
let elements = [];
|
||||
let lastElement;
|
||||
@ -601,12 +615,14 @@ this.NetworkStatsService = {
|
||||
return;
|
||||
}
|
||||
|
||||
let stats = { appId: 0,
|
||||
networkId: this._networks[aNetId].network.id,
|
||||
networkType: this._networks[aNetId].network.type,
|
||||
date: aDate,
|
||||
rxBytes: aTxBytes,
|
||||
txBytes: aRxBytes };
|
||||
let stats = { appId: 0,
|
||||
serviceType: "",
|
||||
networkId: this._networks[aNetId].network.id,
|
||||
networkType: this._networks[aNetId].network.type,
|
||||
date: aDate,
|
||||
rxBytes: aTxBytes,
|
||||
txBytes: aRxBytes,
|
||||
isAccumulative: true };
|
||||
|
||||
debug("Update stats for: " + JSON.stringify(stats));
|
||||
|
||||
@ -623,9 +639,11 @@ this.NetworkStatsService = {
|
||||
},
|
||||
|
||||
/*
|
||||
* Function responsible for receiving per-app stats.
|
||||
* Function responsible for receiving stats which are not from netd.
|
||||
*/
|
||||
saveAppStats: function saveAppStats(aAppId, aNetwork, aTimeStamp, aRxBytes, aTxBytes, aCallback) {
|
||||
saveStats: function saveStats(aAppId, aServiceType, aNetwork, aTimeStamp,
|
||||
aRxBytes, aTxBytes, aIsAccumulative,
|
||||
aCallback) {
|
||||
let netId = this.convertNetworkInterface(aNetwork);
|
||||
if (!netId) {
|
||||
if (aCallback) {
|
||||
@ -634,36 +652,43 @@ this.NetworkStatsService = {
|
||||
return;
|
||||
}
|
||||
|
||||
debug("saveAppStats: " + aAppId + " " + netId + " " +
|
||||
debug("saveStats: " + aAppId + " " + aServiceType + " " + netId + " " +
|
||||
aTimeStamp + " " + aRxBytes + " " + aTxBytes);
|
||||
|
||||
// Check if |aAppId| and |aConnectionType| are valid.
|
||||
if (!aAppId || !this._networks[netId]) {
|
||||
debug("Invalid appId or network interface");
|
||||
// Check if |aConnectionType|, |aAppId| and |aServiceType| are valid.
|
||||
// There are two invalid cases for the combination of |aAppId| and
|
||||
// |aServiceType|:
|
||||
// a. Both |aAppId| is non-zero and |aServiceType| is non-empty.
|
||||
// b. Both |aAppId| is zero and |aServiceType| is empty.
|
||||
if (!this._networks[netId] || (aAppId && aServiceType) ||
|
||||
(!aAppId && !aServiceType)) {
|
||||
debug("Invalid network interface, appId or serviceType");
|
||||
return;
|
||||
}
|
||||
|
||||
let stats = { appId: aAppId,
|
||||
networkId: this._networks[netId].network.id,
|
||||
networkType: this._networks[netId].network.type,
|
||||
date: new Date(aTimeStamp),
|
||||
rxBytes: aRxBytes,
|
||||
txBytes: aTxBytes };
|
||||
let stats = { appId: aAppId,
|
||||
serviceType: aServiceType,
|
||||
networkId: this._networks[netId].network.id,
|
||||
networkType: this._networks[netId].network.type,
|
||||
date: new Date(aTimeStamp),
|
||||
rxBytes: aRxBytes,
|
||||
txBytes: aTxBytes,
|
||||
isAccumulative: aIsAccumulative };
|
||||
|
||||
// Generate an unique key from |appId| and |connectionType|,
|
||||
// which is used to retrieve data in |cachedAppStats|.
|
||||
let key = stats.appId + "" + netId;
|
||||
// Generate an unique key from |appId|, |serviceType| and |netId|,
|
||||
// which is used to retrieve data in |cachedStats|.
|
||||
let key = stats.appId + "" + stats.serviceType + "" + netId;
|
||||
|
||||
// |cachedAppStats| only keeps the data with the same date.
|
||||
// If the incoming date is different from |cachedAppStatsDate|,
|
||||
// both |cachedAppStats| and |cachedAppStatsDate| will get updated.
|
||||
// |cachedStats| only keeps the data with the same date.
|
||||
// If the incoming date is different from |cachedStatsDate|,
|
||||
// both |cachedStats| and |cachedStatsDate| will get updated.
|
||||
let diff = (this._db.normalizeDate(stats.date) -
|
||||
this._db.normalizeDate(this.cachedAppStatsDate)) /
|
||||
this._db.normalizeDate(this.cachedStatsDate)) /
|
||||
this._db.sampleRate;
|
||||
if (diff != 0) {
|
||||
this.updateCachedAppStats(function onUpdated(success, message) {
|
||||
this.cachedAppStatsDate = stats.date;
|
||||
this.cachedAppStats[key] = stats;
|
||||
this.updateCachedStats(function onUpdated(success, message) {
|
||||
this.cachedStatsDate = stats.date;
|
||||
this.cachedStats[key] = stats;
|
||||
|
||||
if (!aCallback) {
|
||||
return;
|
||||
@ -682,36 +707,36 @@ this.NetworkStatsService = {
|
||||
|
||||
// Try to find the matched row in the cached by |appId| and |connectionType|.
|
||||
// If not found, save the incoming data into the cached.
|
||||
let appStats = this.cachedAppStats[key];
|
||||
if (!appStats) {
|
||||
this.cachedAppStats[key] = stats;
|
||||
let cachedStats = this.cachedStats[key];
|
||||
if (!cachedStats) {
|
||||
this.cachedStats[key] = stats;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find matched row, accumulate the traffic amount.
|
||||
appStats.rxBytes += stats.rxBytes;
|
||||
appStats.txBytes += stats.txBytes;
|
||||
cachedStats.rxBytes += stats.rxBytes;
|
||||
cachedStats.txBytes += stats.txBytes;
|
||||
|
||||
// If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC
|
||||
// the corresponding row will be saved to indexedDB.
|
||||
// Then, the row will be removed from the cached.
|
||||
if (appStats.rxBytes > MAX_CACHED_TRAFFIC ||
|
||||
appStats.txBytes > MAX_CACHED_TRAFFIC) {
|
||||
this._db.saveStats(appStats,
|
||||
if (cachedStats.rxBytes > MAX_CACHED_TRAFFIC ||
|
||||
cachedStats.txBytes > MAX_CACHED_TRAFFIC) {
|
||||
this._db.saveStats(cachedStats,
|
||||
function (error, result) {
|
||||
debug("Application stats inserted in indexedDB");
|
||||
}
|
||||
);
|
||||
delete this.cachedAppStats[key];
|
||||
delete this.cachedStats[key];
|
||||
}
|
||||
},
|
||||
|
||||
updateCachedAppStats: function updateCachedAppStats(aCallback) {
|
||||
debug("updateCachedAppStats: " + this.cachedAppStatsDate);
|
||||
updateCachedStats: function updateCachedStats(aCallback) {
|
||||
debug("updateCachedStats: " + this.cachedStatsDate);
|
||||
|
||||
let stats = Object.keys(this.cachedAppStats);
|
||||
let stats = Object.keys(this.cachedStats);
|
||||
if (stats.length == 0) {
|
||||
// |cachedAppStats| is empty, no need to update.
|
||||
// |cachedStats| is empty, no need to update.
|
||||
if (aCallback) {
|
||||
aCallback(true, "no need to update");
|
||||
}
|
||||
@ -720,15 +745,15 @@ this.NetworkStatsService = {
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
this._db.saveStats(this.cachedAppStats[stats[index]],
|
||||
this._db.saveStats(this.cachedStats[stats[index]],
|
||||
function onSavedStats(error, result) {
|
||||
if (DEBUG) {
|
||||
debug("Application stats inserted in indexedDB");
|
||||
}
|
||||
|
||||
// Clean up the |cachedAppStats| after updating.
|
||||
// Clean up the |cachedStats| after updating.
|
||||
if (index == stats.length - 1) {
|
||||
this.cachedAppStats = Object.create(null);
|
||||
this.cachedStats = Object.create(null);
|
||||
|
||||
if (!aCallback) {
|
||||
return;
|
||||
@ -745,7 +770,7 @@ this.NetworkStatsService = {
|
||||
|
||||
// Update is not finished, keep updating.
|
||||
index += 1;
|
||||
this._db.saveStats(this.cachedAppStats[stats[index]],
|
||||
this._db.saveStats(this.cachedStats[stats[index]],
|
||||
onSavedStats.bind(this, error, result));
|
||||
}.bind(this));
|
||||
},
|
||||
|
@ -15,7 +15,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetworkStatsService.jsm");
|
||||
|
||||
const NETWORKSTATSSERVICEPROXY_CONTRACTID = "@mozilla.org/networkstatsServiceProxy;1";
|
||||
const NETWORKSTATSSERVICEPROXY_CID = Components.ID("8fbd115d-f590-474c-96dc-e2b6803ca975");
|
||||
const NETWORKSTATSSERVICEPROXY_CID = Components.ID("705c01d6-8574-464c-8ec9-ac1522a45546");
|
||||
const nsINetworkStatsServiceProxy = Ci.nsINetworkStatsServiceProxy;
|
||||
|
||||
function NetworkStatsServiceProxy() {
|
||||
@ -30,7 +30,8 @@ NetworkStatsServiceProxy.prototype = {
|
||||
* to pass the per-app stats to NetworkStatsService.
|
||||
*/
|
||||
saveAppStats: function saveAppStats(aAppId, aNetwork, aTimeStamp,
|
||||
aRxBytes, aTxBytes, aCallback) {
|
||||
aRxBytes, aTxBytes, aIsAccumulative,
|
||||
aCallback) {
|
||||
if (!aNetwork) {
|
||||
if (DEBUG) {
|
||||
debug("|aNetwork| is not specified. Failed to save stats. Returning.");
|
||||
@ -39,12 +40,38 @@ NetworkStatsServiceProxy.prototype = {
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("saveAppStats: " + aAppId + " connectionType " + aNetwork.type +
|
||||
" " + aTimeStamp + " " + aRxBytes + " " + aTxBytes);
|
||||
debug("saveAppStats: " + aAppId + " " + aNetwork.type + " " + aTimeStamp +
|
||||
" " + aRxBytes + " " + aTxBytes + " " + aIsAccumulative);
|
||||
}
|
||||
|
||||
NetworkStatsService.saveAppStats(aAppId, aNetwork, aTimeStamp,
|
||||
aRxBytes, aTxBytes, aCallback);
|
||||
NetworkStatsService.saveStats(aAppId, "", aNetwork, aTimeStamp,
|
||||
aRxBytes, aTxBytes, aIsAccumulative,
|
||||
aCallback);
|
||||
},
|
||||
|
||||
/*
|
||||
* Function called in the points of different system services
|
||||
* to pass the per-serive stats to NetworkStatsService.
|
||||
*/
|
||||
saveServiceStats: function saveServiceStats(aServiceType, aNetwork,
|
||||
aTimeStamp, aRxBytes, aTxBytes,
|
||||
aIsAccumulative, aCallback) {
|
||||
if (!aNetwork) {
|
||||
if (DEBUG) {
|
||||
debug("|aNetwork| is not specified. Failed to save stats. Returning.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("saveServiceStats: " + aServiceType + " " + aNetwork.type + " " +
|
||||
aTimeStamp + " " + aRxBytes + " " + aTxBytes + " " +
|
||||
aIsAccumulative);
|
||||
}
|
||||
|
||||
NetworkStatsService.saveStats(0, aServiceType ,aNetwork, aTimeStamp,
|
||||
aRxBytes, aTxBytes, aIsAccumulative,
|
||||
aCallback);
|
||||
},
|
||||
|
||||
classID : NETWORKSTATSSERVICEPROXY_CID,
|
||||
|
@ -1,2 +1,2 @@
|
||||
component {8fbd115d-f590-474c-96dc-e2b6803ca975} NetworkStatsServiceProxy.js
|
||||
contract @mozilla.org/networkstatsServiceProxy;1 {8fbd115d-f590-474c-96dc-e2b6803ca975}
|
||||
component {705c01d6-8574-464c-8ec9-ac1522a45546} NetworkStatsServiceProxy.js
|
||||
contract @mozilla.org/networkstatsServiceProxy;1 {705c01d6-8574-464c-8ec9-ac1522a45546}
|
||||
|
@ -361,7 +361,7 @@ TCPSocket.prototype = {
|
||||
return;
|
||||
}
|
||||
nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(),
|
||||
this._rxBytes, this._txBytes);
|
||||
this._rxBytes, this._txBytes, false);
|
||||
|
||||
// Reset the counters once the statistics is saved to NetworkStatsServiceProxy.
|
||||
this._txBytes = this._rxBytes = 0;
|
||||
|
@ -22,7 +22,7 @@ function clearStore(store, callback) {
|
||||
add_test(function prepareDatabase() {
|
||||
// Clear whole database to avoid starting tests with unknown state
|
||||
// due to the previous tests.
|
||||
clearStore('net_stats', function() {
|
||||
clearStore('net_stats_store', function() {
|
||||
clearStore('net_alarm', function() {
|
||||
run_next_test();
|
||||
});
|
||||
@ -139,6 +139,7 @@ add_test(function test_internalSaveStats_singleSample() {
|
||||
var networks = getNetworks();
|
||||
|
||||
var stats = { appId: 0,
|
||||
serviceType: "",
|
||||
network: [networks[0].id, networks[0].type],
|
||||
timestamp: Date.now(),
|
||||
rxBytes: 0,
|
||||
@ -148,7 +149,7 @@ add_test(function test_internalSaveStats_singleSample() {
|
||||
rxTotalBytes: 1234,
|
||||
txTotalBytes: 1234 };
|
||||
|
||||
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
|
||||
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
|
||||
netStatsDb._saveStats(txn, store, stats);
|
||||
}, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
@ -157,6 +158,7 @@ add_test(function test_internalSaveStats_singleSample() {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.length, 1);
|
||||
do_check_eq(result[0].appId, stats.appId);
|
||||
do_check_eq(result[0].serviceType, stats.serviceType);
|
||||
do_check_true(compareNetworks(result[0].network, stats.network));
|
||||
do_check_eq(result[0].timestamp, stats.timestamp);
|
||||
do_check_eq(result[0].rxBytes, stats.rxBytes);
|
||||
@ -182,6 +184,7 @@ add_test(function test_internalSaveStats_arraySamples() {
|
||||
var stats = [];
|
||||
for (var i = 0; i < samples; i++) {
|
||||
stats.push({ appId: 0,
|
||||
serviceType: "",
|
||||
network: network,
|
||||
timestamp: Date.now() + (10 * i),
|
||||
rxBytes: 0,
|
||||
@ -192,7 +195,7 @@ add_test(function test_internalSaveStats_arraySamples() {
|
||||
txTotalBytes: 1234 });
|
||||
}
|
||||
|
||||
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
|
||||
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
|
||||
netStatsDb._saveStats(txn, store, stats);
|
||||
}, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
@ -207,6 +210,7 @@ add_test(function test_internalSaveStats_arraySamples() {
|
||||
var success = true;
|
||||
for (var i = 1; i < samples; i++) {
|
||||
if (result[i].appId != stats[i].appId ||
|
||||
result[i].serviceType != stats[i].serviceType ||
|
||||
!compareNetworks(result[i].network, stats[i].network) ||
|
||||
result[i].timestamp != stats[i].timestamp ||
|
||||
result[i].rxBytes != stats[i].rxBytes ||
|
||||
@ -236,24 +240,24 @@ add_test(function test_internalRemoveOldStats() {
|
||||
var samples = 10;
|
||||
var stats = [];
|
||||
for (var i = 0; i < samples - 1; i++) {
|
||||
stats.push({ appId: 0,
|
||||
stats.push({ appId: 0, serviceType: "",
|
||||
network: network, timestamp: Date.now() + (10 * i),
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxSystemBytes: 1234, txSystemBytes: 1234,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234 });
|
||||
}
|
||||
|
||||
stats.push({ appId: 0,
|
||||
stats.push({ appId: 0, serviceType: "",
|
||||
network: network, timestamp: Date.now() + (10 * samples),
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxSystemBytes: 1234, txSystemBytes: 1234,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234 });
|
||||
|
||||
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
|
||||
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
|
||||
netStatsDb._saveStats(txn, store, stats);
|
||||
var date = stats[stats.length - 1].timestamp
|
||||
+ (netStatsDb.sampleRate * netStatsDb.maxStorageSamples - 1) - 1;
|
||||
netStatsDb._removeOldStats(txn, store, 0, network, date);
|
||||
netStatsDb._removeOldStats(txn, store, 0, "", network, date);
|
||||
}, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
@ -270,15 +274,15 @@ add_test(function test_internalRemoveOldStats() {
|
||||
function processSamplesDiff(networks, lastStat, newStat, callback) {
|
||||
netStatsDb.clearStats(networks, function (error, result){
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
|
||||
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
|
||||
netStatsDb._saveStats(txn, store, lastStat);
|
||||
}, function(error, result) {
|
||||
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
|
||||
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
|
||||
let request = store.index("network").openCursor(newStat.network, "prev");
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
let cursor = event.target.result;
|
||||
do_check_neq(cursor, null);
|
||||
netStatsDb._processSamplesDiff(txn, store, cursor, newStat);
|
||||
netStatsDb._processSamplesDiff(txn, store, cursor, newStat, true);
|
||||
};
|
||||
}, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
@ -298,13 +302,13 @@ add_test(function test_processSamplesDiffSameSample() {
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
var date = filterTimestamp(new Date());
|
||||
|
||||
var lastStat = { appId: 0,
|
||||
var lastStat = { appId: 0, serviceType: "",
|
||||
network: network, timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxSystemBytes: 1234, txSystemBytes: 1234,
|
||||
rxTotalBytes: 2234, txTotalBytes: 2234 };
|
||||
|
||||
var newStat = { appId: 0,
|
||||
var newStat = { appId: 0, serviceType: "",
|
||||
network: network, timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxSystemBytes: 2234, txSystemBytes: 2234,
|
||||
@ -313,6 +317,7 @@ add_test(function test_processSamplesDiffSameSample() {
|
||||
processSamplesDiff(networks, lastStat, newStat, function(result) {
|
||||
do_check_eq(result.length, 1);
|
||||
do_check_eq(result[0].appId, newStat.appId);
|
||||
do_check_eq(result[0].serviceType, newStat.serviceType);
|
||||
do_check_true(compareNetworks(result[0].network, newStat.network));
|
||||
do_check_eq(result[0].timestamp, newStat.timestamp);
|
||||
do_check_eq(result[0].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
|
||||
@ -332,13 +337,13 @@ add_test(function test_processSamplesDiffNextSample() {
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
var date = filterTimestamp(new Date());
|
||||
|
||||
var lastStat = { appId: 0,
|
||||
var lastStat = { appId: 0, serviceType: "",
|
||||
network: network, timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxSystemBytes: 1234, txSystemBytes: 1234,
|
||||
rxTotalBytes: 2234, txTotalBytes: 2234 };
|
||||
|
||||
var newStat = { appId: 0,
|
||||
var newStat = { appId: 0, serviceType: "",
|
||||
network: network, timestamp: date + sampleRate,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxSystemBytes: 1734, txSystemBytes: 1734,
|
||||
@ -347,6 +352,7 @@ add_test(function test_processSamplesDiffNextSample() {
|
||||
processSamplesDiff(networks, lastStat, newStat, function(result) {
|
||||
do_check_eq(result.length, 2);
|
||||
do_check_eq(result[1].appId, newStat.appId);
|
||||
do_check_eq(result[1].serviceType, newStat.serviceType);
|
||||
do_check_true(compareNetworks(result[1].network, newStat.network));
|
||||
do_check_eq(result[1].timestamp, newStat.timestamp);
|
||||
do_check_eq(result[1].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
|
||||
@ -365,13 +371,13 @@ add_test(function test_processSamplesDiffSamplesLost() {
|
||||
var samples = 5;
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
var date = filterTimestamp(new Date());
|
||||
var lastStat = { appId: 0,
|
||||
var lastStat = { appId: 0, serviceType: "",
|
||||
network: network, timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxSystemBytes: 1234, txSystemBytes: 1234,
|
||||
rxTotalBytes: 2234, txTotalBytes: 2234};
|
||||
|
||||
var newStat = { appId: 0,
|
||||
var newStat = { appId: 0, serviceType: "",
|
||||
network: network, timestamp: date + (sampleRate * samples),
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxSystemBytes: 2234, txSystemBytes: 2234,
|
||||
@ -380,6 +386,7 @@ add_test(function test_processSamplesDiffSamplesLost() {
|
||||
processSamplesDiff(networks, lastStat, newStat, function(result) {
|
||||
do_check_eq(result.length, samples + 1);
|
||||
do_check_eq(result[0].appId, newStat.appId);
|
||||
do_check_eq(result[0].serviceType, newStat.serviceType);
|
||||
do_check_true(compareNetworks(result[samples].network, newStat.network));
|
||||
do_check_eq(result[samples].timestamp, newStat.timestamp);
|
||||
do_check_eq(result[samples].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
|
||||
@ -396,20 +403,23 @@ add_test(function test_saveStats() {
|
||||
var networks = getNetworks();
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
|
||||
var stats = { appId: 0,
|
||||
networkId: networks[0].id,
|
||||
networkType: networks[0].type,
|
||||
date: new Date(),
|
||||
rxBytes: 2234,
|
||||
txBytes: 2234};
|
||||
var stats = { appId: 0,
|
||||
serviceType: "",
|
||||
networkId: networks[0].id,
|
||||
networkType: networks[0].type,
|
||||
date: new Date(),
|
||||
rxBytes: 2234,
|
||||
txBytes: 2234,
|
||||
isAccumulative: true };
|
||||
|
||||
clearStore('net_stats', function() {
|
||||
clearStore('net_stats_store', function() {
|
||||
netStatsDb.saveStats(stats, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.logAllRecords(function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.length, 1);
|
||||
do_check_eq(result[0].appId, stats.appId);
|
||||
do_check_eq(result[0].serviceType, stats.serviceType);
|
||||
do_check_true(compareNetworks(result[0].network, network));
|
||||
let timestamp = filterTimestamp(stats.date);
|
||||
do_check_eq(result[0].timestamp, timestamp);
|
||||
@ -429,12 +439,14 @@ add_test(function test_saveAppStats() {
|
||||
var networks = getNetworks();
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
|
||||
var stats = { appId: 1,
|
||||
networkId: networks[0].id,
|
||||
networkType: networks[0].type,
|
||||
date: new Date(),
|
||||
rxBytes: 2234,
|
||||
txBytes: 2234};
|
||||
var stats = { appId: 1,
|
||||
serviceType: "",
|
||||
networkId: networks[0].id,
|
||||
networkType: networks[0].type,
|
||||
date: new Date(),
|
||||
rxBytes: 2234,
|
||||
txBytes: 2234,
|
||||
isAccumulative: false };
|
||||
|
||||
netStatsDb.clearStats(networks, function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
@ -449,6 +461,47 @@ add_test(function test_saveAppStats() {
|
||||
// past tests and the new one for appId 1
|
||||
do_check_eq(result.length, 2);
|
||||
do_check_eq(result[1].appId, stats.appId);
|
||||
do_check_eq(result[1].serviceType, stats.serviceType);
|
||||
do_check_true(compareNetworks(result[1].network, network));
|
||||
let timestamp = filterTimestamp(stats.date);
|
||||
do_check_eq(result[1].timestamp, timestamp);
|
||||
do_check_eq(result[1].rxBytes, stats.rxBytes);
|
||||
do_check_eq(result[1].txBytes, stats.txBytes);
|
||||
do_check_eq(result[1].rxSystemBytes, 0);
|
||||
do_check_eq(result[1].txSystemBytes, 0);
|
||||
do_check_eq(result[1].rxTotalBytes, 0);
|
||||
do_check_eq(result[1].txTotalBytes, 0);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_saveServiceStats() {
|
||||
var networks = getNetworks();
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
|
||||
var stats = { appId: 0,
|
||||
serviceType: "FakeType",
|
||||
networkId: networks[0].id,
|
||||
networkType: networks[0].type,
|
||||
date: new Date(),
|
||||
rxBytes: 2234,
|
||||
txBytes: 2234,
|
||||
isAccumulative: false };
|
||||
|
||||
netStatsDb.clearStats(networks, function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.saveStats(stats, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.logAllRecords(function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
// Again, at this point, we have two records, one for the appId 0 and
|
||||
// empty serviceType used in past tests and the new one for appId 0 and
|
||||
// non-empty serviceType.
|
||||
do_check_eq(result.length, 2);
|
||||
do_check_eq(result[1].appId, stats.appId);
|
||||
do_check_eq(result[1].serviceType, stats.serviceType);
|
||||
do_check_true(compareNetworks(result[1].network, network));
|
||||
let timestamp = filterTimestamp(stats.date);
|
||||
do_check_eq(result[1].timestamp, timestamp);
|
||||
@ -467,7 +520,7 @@ add_test(function test_saveAppStats() {
|
||||
function prepareFind(network, stats, callback) {
|
||||
netStatsDb.clearStats(network, function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
|
||||
netStatsDb.dbNewTxn("net_stats_store", "readwrite", function(txn, store) {
|
||||
netStatsDb._saveStats(txn, store, stats);
|
||||
}, function(error, result) {
|
||||
callback(error, result);
|
||||
@ -480,6 +533,7 @@ add_test(function test_find () {
|
||||
var networkWifi = [networks[0].id, networks[0].type];
|
||||
var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
|
||||
var appId = 0;
|
||||
var serviceType = "";
|
||||
|
||||
var samples = 5;
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
@ -489,24 +543,25 @@ add_test(function test_find () {
|
||||
start = new Date(start - sampleRate);
|
||||
var stats = [];
|
||||
for (var i = 0; i < samples; i++) {
|
||||
stats.push({ appId: appId,
|
||||
stats.push({ appId: appId, serviceType: serviceType,
|
||||
network: networkWifi, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxSystemBytes: 0, txSystemBytes: 0,
|
||||
rxTotalBytes: 0, txTotalBytes: 0});
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
|
||||
|
||||
stats.push({ appId: appId,
|
||||
stats.push({ appId: appId, serviceType: serviceType,
|
||||
network: networkMobile, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxSystemBytes: 0, txSystemBytes: 0,
|
||||
rxTotalBytes: 0, txTotalBytes: 0});
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
}
|
||||
|
||||
prepareFind(networks[0], stats, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.find(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.serviceType, serviceType);
|
||||
do_check_eq(result.network.id, networks[0].id);
|
||||
do_check_eq(result.network.type, networks[0].type);
|
||||
do_check_eq(result.start.getTime(), start.getTime());
|
||||
@ -516,7 +571,7 @@ add_test(function test_find () {
|
||||
do_check_eq(result.data[1].rxBytes, 0);
|
||||
do_check_eq(result.data[samples].rxBytes, 0);
|
||||
run_next_test();
|
||||
}, networks[0], start, end, appId);
|
||||
}, appId, serviceType, networks[0], start, end);
|
||||
});
|
||||
});
|
||||
|
||||
@ -524,6 +579,8 @@ add_test(function test_findAppStats () {
|
||||
var networks = getNetworks();
|
||||
var networkWifi = [networks[0].id, networks[0].type];
|
||||
var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
|
||||
var appId = 1;
|
||||
var serviceType = "";
|
||||
|
||||
var samples = 5;
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
@ -533,12 +590,12 @@ add_test(function test_findAppStats () {
|
||||
start = new Date(start - sampleRate);
|
||||
var stats = [];
|
||||
for (var i = 0; i < samples; i++) {
|
||||
stats.push({ appId: 1,
|
||||
stats.push({ appId: appId, serviceType: serviceType,
|
||||
network: networkWifi, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
|
||||
stats.push({ appId: 1,
|
||||
stats.push({ appId: appId, serviceType: serviceType,
|
||||
network: networkMobile, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
@ -548,6 +605,7 @@ add_test(function test_findAppStats () {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.find(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.serviceType, serviceType);
|
||||
do_check_eq(result.network.id, networks[0].id);
|
||||
do_check_eq(result.network.type, networks[0].type);
|
||||
do_check_eq(result.start.getTime(), start.getTime());
|
||||
@ -557,7 +615,51 @@ add_test(function test_findAppStats () {
|
||||
do_check_eq(result.data[1].rxBytes, 0);
|
||||
do_check_eq(result.data[samples].rxBytes, 0);
|
||||
run_next_test();
|
||||
}, networks[0], start, end, 1);
|
||||
}, appId, serviceType, networks[0], start, end);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_findServiceStats () {
|
||||
var networks = getNetworks();
|
||||
var networkWifi = [networks[0].id, networks[0].type];
|
||||
var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
|
||||
var appId = 0;
|
||||
var serviceType = "FakeType";
|
||||
|
||||
var samples = 5;
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
var start = Date.now();
|
||||
var saveDate = filterTimestamp(new Date());
|
||||
var end = new Date(start + (sampleRate * (samples - 1)));
|
||||
start = new Date(start - sampleRate);
|
||||
var stats = [];
|
||||
for (var i = 0; i < samples; i++) {
|
||||
stats.push({ appId: appId, serviceType: serviceType,
|
||||
network: networkWifi, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
|
||||
stats.push({ appId: appId, serviceType: serviceType,
|
||||
network: networkMobile, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
}
|
||||
|
||||
prepareFind(networks[0], stats, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.find(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.serviceType, serviceType);
|
||||
do_check_eq(result.network.id, networks[0].id);
|
||||
do_check_eq(result.network.type, networks[0].type);
|
||||
do_check_eq(result.start.getTime(), start.getTime());
|
||||
do_check_eq(result.end.getTime(), end.getTime());
|
||||
do_check_eq(result.data.length, samples + 1);
|
||||
do_check_eq(result.data[0].rxBytes, null);
|
||||
do_check_eq(result.data[1].rxBytes, 0);
|
||||
do_check_eq(result.data[samples].rxBytes, 0);
|
||||
run_next_test();
|
||||
}, appId, serviceType, networks[0], start, end);
|
||||
});
|
||||
});
|
||||
|
||||
@ -568,22 +670,51 @@ add_test(function test_saveMultipleAppStats () {
|
||||
|
||||
var saveDate = filterTimestamp(new Date());
|
||||
var cached = Object.create(null);
|
||||
var serviceType = "FakeType";
|
||||
var wifiNetId = networkWifi.id + '' + networkWifi.type;
|
||||
var mobileNetId = networkMobile.id + '' + networkMobile.type;
|
||||
|
||||
cached['1wifi'] = { appId: 1, date: new Date(),
|
||||
networkId: networkWifi.id, networkType: networkWifi.type,
|
||||
rxBytes: 0, txBytes: 10 };
|
||||
cached[0 + '' + serviceType + wifiNetId] = {
|
||||
appId: 0, date: new Date(),
|
||||
networkId: networkWifi.id, networkType: networkWifi.type,
|
||||
rxBytes: 0, txBytes: 10,
|
||||
serviceType: serviceType, isAccumulative: false
|
||||
};
|
||||
|
||||
cached['1mobile'] = { appId: 1, date: new Date(),
|
||||
networkId: networkMobile.id, networkType: networkMobile.type,
|
||||
rxBytes: 0, txBytes: 10 };
|
||||
cached[0 + '' + serviceType + mobileNetId] = {
|
||||
appId: 0, date: new Date(),
|
||||
networkId: networkMobile.id, networkType: networkMobile.type,
|
||||
rxBytes: 0, txBytes: 10,
|
||||
serviceType: serviceType, isAccumulative: false
|
||||
};
|
||||
|
||||
cached['2wifi'] = { appId: 2, date: new Date(),
|
||||
networkId: networkWifi.id, networkType: networkWifi.type,
|
||||
rxBytes: 0, txBytes: 10 };
|
||||
cached[1 + '' + wifiNetId] = {
|
||||
appId: 1, date: new Date(),
|
||||
networkId: networkWifi.id, networkType: networkWifi.type,
|
||||
rxBytes: 0, txBytes: 10,
|
||||
serviceType: "", isAccumulative: false
|
||||
};
|
||||
|
||||
cached['2mobile'] = { appId: 2, date: new Date(),
|
||||
networkId: networkMobile.id, networkType: networkMobile.type,
|
||||
rxBytes: 0, txBytes: 10 };
|
||||
cached[1 + '' + mobileNetId] = {
|
||||
appId: 1, date: new Date(),
|
||||
networkId: networkMobile.id, networkType: networkMobile.type,
|
||||
rxBytes: 0, txBytes: 10,
|
||||
serviceType: "", isAccumulative: false
|
||||
};
|
||||
|
||||
cached[2 + '' + wifiNetId] = {
|
||||
appId: 2, date: new Date(),
|
||||
networkId: networkWifi.id, networkType: networkWifi.type,
|
||||
rxBytes: 0, txBytes: 10,
|
||||
serviceType: "", isAccumulative: false
|
||||
};
|
||||
|
||||
cached[2 + '' + mobileNetId] = {
|
||||
appId: 2, date: new Date(),
|
||||
networkId: networkMobile.id, networkType: networkMobile.type,
|
||||
rxBytes: 0, txBytes: 10,
|
||||
serviceType: "", isAccumulative: false
|
||||
};
|
||||
|
||||
let keys = Object.keys(cached);
|
||||
let index = 0;
|
||||
@ -605,8 +736,9 @@ add_test(function test_saveMultipleAppStats () {
|
||||
result.shift();
|
||||
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.length, 4);
|
||||
do_check_eq(result[0].appId, 1);
|
||||
do_check_eq(result.length, 6);
|
||||
do_check_eq(result[0].serviceType, serviceType);
|
||||
do_check_eq(result[3].appId, 1);
|
||||
do_check_true(compareNetworks(result[0].network, [networkWifi.id, networkWifi.type]));
|
||||
do_check_eq(result[0].rxBytes, 0);
|
||||
do_check_eq(result[0].txBytes, 10);
|
||||
|
@ -34,8 +34,8 @@ function mokConvertNetworkInterface() {
|
||||
}
|
||||
|
||||
add_test(function test_saveAppStats() {
|
||||
var cachedAppStats = NetworkStatsService.cachedAppStats;
|
||||
var timestamp = NetworkStatsService.cachedAppStatsDate.getTime();
|
||||
var cachedStats = NetworkStatsService.cachedStats;
|
||||
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
|
||||
var samples = 5;
|
||||
|
||||
// Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
|
||||
@ -47,101 +47,160 @@ add_test(function test_saveAppStats() {
|
||||
// Insert fake mobile network interface in NetworkStatsService
|
||||
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
|
||||
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 0);
|
||||
do_check_eq(Object.keys(cachedStats).length, 0);
|
||||
|
||||
for (var i = 0; i < samples; i++) {
|
||||
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20);
|
||||
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20, false);
|
||||
|
||||
nssProxy.saveAppStats(1, mobile, timestamp, 10, 20);
|
||||
nssProxy.saveAppStats(1, mobile, timestamp, 10, 20, false);
|
||||
}
|
||||
|
||||
var key1 = 1 + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
|
||||
var key2 = 1 + mobileNetId;
|
||||
var key1 = 1 + "" + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
|
||||
var key2 = 1 + "" + mobileNetId + "";
|
||||
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 2);
|
||||
do_check_eq(cachedAppStats[key1].appId, 1);
|
||||
do_check_eq(cachedAppStats[key1].networkId, wifi.id);
|
||||
do_check_eq(cachedAppStats[key1].networkType, wifi.type);
|
||||
do_check_eq(new Date(cachedAppStats[key1].date).getTime() / 1000,
|
||||
do_check_eq(Object.keys(cachedStats).length, 2);
|
||||
do_check_eq(cachedStats[key1].appId, 1);
|
||||
do_check_eq(cachedStats[key1].serviceType.length, 0);
|
||||
do_check_eq(cachedStats[key1].networkId, wifi.id);
|
||||
do_check_eq(cachedStats[key1].networkType, wifi.type);
|
||||
do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000,
|
||||
Math.floor(timestamp / 1000));
|
||||
do_check_eq(cachedAppStats[key1].rxBytes, 50);
|
||||
do_check_eq(cachedAppStats[key1].txBytes, 100);
|
||||
do_check_eq(cachedAppStats[key2].appId, 1);
|
||||
do_check_eq(cachedAppStats[key2].networkId, mobile.id);
|
||||
do_check_eq(cachedAppStats[key2].networkType, mobile.type);
|
||||
do_check_eq(new Date(cachedAppStats[key2].date).getTime() / 1000,
|
||||
do_check_eq(cachedStats[key1].rxBytes, 50);
|
||||
do_check_eq(cachedStats[key1].txBytes, 100);
|
||||
do_check_eq(cachedStats[key2].appId, 1);
|
||||
do_check_eq(cachedStats[key1].serviceType.length, 0);
|
||||
do_check_eq(cachedStats[key2].networkId, mobile.id);
|
||||
do_check_eq(cachedStats[key2].networkType, mobile.type);
|
||||
do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000,
|
||||
Math.floor(timestamp / 1000));
|
||||
do_check_eq(cachedAppStats[key2].rxBytes, 50);
|
||||
do_check_eq(cachedAppStats[key2].txBytes, 100);
|
||||
do_check_eq(cachedStats[key2].rxBytes, 50);
|
||||
do_check_eq(cachedStats[key2].txBytes, 100);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_saveAppStatsWithDifferentDates() {
|
||||
var today = NetworkStatsService.cachedAppStatsDate;
|
||||
add_test(function test_saveServiceStats() {
|
||||
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
|
||||
var samples = 5;
|
||||
|
||||
// Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
|
||||
// be instantiated, these two vars will emulate it by filling the properties
|
||||
// that will be used.
|
||||
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
|
||||
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
|
||||
|
||||
// Insert fake mobile network interface in NetworkStatsService
|
||||
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
|
||||
|
||||
NetworkStatsService.updateCachedStats(
|
||||
function (success, msg) {
|
||||
do_check_eq(success, true);
|
||||
|
||||
var cachedStats = NetworkStatsService.cachedStats;
|
||||
do_check_eq(Object.keys(cachedStats).length, 0);
|
||||
|
||||
var serviceType = 'FakeType';
|
||||
for (var i = 0; i < samples; i++) {
|
||||
nssProxy.saveServiceStats(serviceType, wifi, timestamp, 10, 20, false);
|
||||
|
||||
nssProxy.saveServiceStats(serviceType, mobile, timestamp, 10, 20, false);
|
||||
}
|
||||
|
||||
var key1 = 0 + "" + serviceType +
|
||||
NetworkStatsService.getNetworkId(wifi.id, wifi.type);
|
||||
var key2 = 0 + "" + serviceType + mobileNetId + "";
|
||||
|
||||
do_check_eq(Object.keys(cachedStats).length, 2);
|
||||
do_check_eq(cachedStats[key1].appId, 0);
|
||||
do_check_eq(cachedStats[key1].serviceType, serviceType);
|
||||
do_check_eq(cachedStats[key1].networkId, wifi.id);
|
||||
do_check_eq(cachedStats[key1].networkType, wifi.type);
|
||||
do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000,
|
||||
Math.floor(timestamp / 1000));
|
||||
do_check_eq(cachedStats[key1].rxBytes, 50);
|
||||
do_check_eq(cachedStats[key1].txBytes, 100);
|
||||
do_check_eq(cachedStats[key2].appId, 0);
|
||||
do_check_eq(cachedStats[key1].serviceType, serviceType);
|
||||
do_check_eq(cachedStats[key2].networkId, mobile.id);
|
||||
do_check_eq(cachedStats[key2].networkType, mobile.type);
|
||||
do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000,
|
||||
Math.floor(timestamp / 1000));
|
||||
do_check_eq(cachedStats[key2].rxBytes, 50);
|
||||
do_check_eq(cachedStats[key2].txBytes, 100);
|
||||
}
|
||||
);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_saveStatsWithDifferentDates() {
|
||||
var today = NetworkStatsService.cachedStatsDate;
|
||||
var tomorrow = new Date(today.getTime() + (24 * 60 * 60 * 1000));
|
||||
|
||||
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
|
||||
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
|
||||
|
||||
var key = 1 + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
|
||||
var key = 1 + "" + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
|
||||
|
||||
NetworkStatsService.updateCachedAppStats(
|
||||
NetworkStatsService.updateCachedStats(
|
||||
function (success, msg) {
|
||||
do_check_eq(success, true);
|
||||
|
||||
do_check_eq(Object.keys(NetworkStatsService.cachedAppStats).length, 0);
|
||||
do_check_eq(Object.keys(NetworkStatsService.cachedStats).length, 0);
|
||||
|
||||
nssProxy.saveAppStats(1, wifi, today.getTime(), 10, 20);
|
||||
nssProxy.saveAppStats(1, wifi, today.getTime(), 10, 20, false);
|
||||
|
||||
nssProxy.saveAppStats(1, mobile, today.getTime(), 10, 20);
|
||||
nssProxy.saveAppStats(1, mobile, today.getTime(), 10, 20, false);
|
||||
|
||||
var saveAppStatsCb = {
|
||||
var saveStatsCb = {
|
||||
notify: function notify(success, message) {
|
||||
do_check_eq(success, true);
|
||||
|
||||
var cachedAppStats = NetworkStatsService.cachedAppStats;
|
||||
var key = 2 + NetworkStatsService.getNetworkId(mobile.id, mobile.type);
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 1);
|
||||
do_check_eq(cachedAppStats[key].appId, 2);
|
||||
do_check_eq(cachedAppStats[key].networkId, mobile.id);
|
||||
do_check_eq(cachedAppStats[key].networkType, mobile.type);
|
||||
do_check_eq(new Date(cachedAppStats[key].date).getTime() / 1000,
|
||||
var cachedStats = NetworkStatsService.cachedStats;
|
||||
var key = 2 + "" +
|
||||
NetworkStatsService.getNetworkId(mobile.id, mobile.type);
|
||||
do_check_eq(Object.keys(cachedStats).length, 1);
|
||||
do_check_eq(cachedStats[key].appId, 2);
|
||||
do_check_eq(cachedStats[key].networkId, mobile.id);
|
||||
do_check_eq(cachedStats[key].networkType, mobile.type);
|
||||
do_check_eq(new Date(cachedStats[key].date).getTime() / 1000,
|
||||
Math.floor(tomorrow.getTime() / 1000));
|
||||
do_check_eq(cachedAppStats[key].rxBytes, 30);
|
||||
do_check_eq(cachedAppStats[key].txBytes, 40);
|
||||
do_check_eq(cachedStats[key].rxBytes, 30);
|
||||
do_check_eq(cachedStats[key].txBytes, 40);
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
};
|
||||
|
||||
nssProxy.saveAppStats(2, mobile, tomorrow.getTime(), 30, 40, saveAppStatsCb);
|
||||
nssProxy.saveAppStats(2, mobile, tomorrow.getTime(), 30, 40, false,
|
||||
saveStatsCb);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
add_test(function test_saveAppStatsWithMaxCachedTraffic() {
|
||||
var timestamp = NetworkStatsService.cachedAppStatsDate.getTime();
|
||||
add_test(function test_saveStatsWithMaxCachedTraffic() {
|
||||
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
|
||||
var maxtraffic = NetworkStatsService.maxCachedTraffic;
|
||||
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
|
||||
|
||||
NetworkStatsService.updateCachedAppStats(
|
||||
NetworkStatsService.updateCachedStats(
|
||||
function (success, msg) {
|
||||
do_check_eq(success, true);
|
||||
|
||||
var cachedAppStats = NetworkStatsService.cachedAppStats;
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 0);
|
||||
var cachedStats = NetworkStatsService.cachedStats;
|
||||
do_check_eq(Object.keys(cachedStats).length, 0);
|
||||
|
||||
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20);
|
||||
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20, false);
|
||||
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 1);
|
||||
do_check_eq(Object.keys(cachedStats).length, 1);
|
||||
|
||||
nssProxy.saveAppStats(1, wifi, timestamp, maxtraffic, 20);
|
||||
nssProxy.saveAppStats(1, wifi, timestamp, maxtraffic, 20, false);
|
||||
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 0);
|
||||
do_check_eq(Object.keys(cachedStats).length, 0);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
|
@ -2873,6 +2873,12 @@ RadioInterface.prototype = {
|
||||
}
|
||||
|
||||
let callback = (function(response) {
|
||||
if (response.errorMsg) {
|
||||
// Request fails. Rollback to the original radiostate.
|
||||
let state = message.enabled ? RIL.GECKO_DETAILED_RADIOSTATE_DISABLED
|
||||
: RIL.GECKO_DETAILED_RADIOSTATE_ENABLED;
|
||||
this.handleDetailedRadioStateChanged(state);
|
||||
}
|
||||
this.setRadioEnabledResponse(target, response);
|
||||
return false;
|
||||
}).bind(this);
|
||||
|
@ -5265,6 +5265,7 @@ RIL[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
|
||||
this.sendChromeMessage(options);
|
||||
};
|
||||
RIL[REQUEST_DTMF] = null;
|
||||
|
@ -2386,6 +2386,7 @@ NS_IsSrcdocChannel(nsIChannel *aChannel)
|
||||
|
||||
// The following members are used for network per-app metering.
|
||||
const static uint64_t NETWORK_STATS_THRESHOLD = 65536;
|
||||
const static char NETWORK_STATS_NO_SERVICE_TYPE[] = "";
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
inline nsresult
|
||||
|
@ -2225,9 +2225,13 @@ nsFtpState::SaveNetworkStats(bool enforce)
|
||||
return rv;
|
||||
}
|
||||
|
||||
networkStatsServiceProxy->SaveAppStats(appId, mActiveNetwork,
|
||||
PR_Now() / 1000, mCountRecv,
|
||||
0, nullptr);
|
||||
networkStatsServiceProxy->SaveAppStats(appId,
|
||||
mActiveNetwork,
|
||||
PR_Now() / 1000,
|
||||
mCountRecv,
|
||||
0,
|
||||
false,
|
||||
nullptr);
|
||||
|
||||
// Reset the counters after saving.
|
||||
mCountRecv = 0;
|
||||
|
@ -756,6 +756,7 @@ public:
|
||||
PR_Now() / 1000,
|
||||
mCountRecv,
|
||||
mCountSent,
|
||||
false,
|
||||
nullptr);
|
||||
|
||||
return NS_OK;
|
||||
|
@ -3304,6 +3304,7 @@ public:
|
||||
PR_Now() / 1000,
|
||||
mCountRecv,
|
||||
mCountSent,
|
||||
false,
|
||||
nullptr);
|
||||
|
||||
return NS_OK;
|
||||
|
Loading…
Reference in New Issue
Block a user