зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 14 changesets (bug 804387) because of Android M2 crashes
Backed out changeset 80e8530f06ea (bug 804387) Backed out changeset 3de2271ad47f (bug 804387) Backed out changeset 00f86870931c (bug 804837) Backed out changeset 0e3f20927c50 (bug 804387) Backed out changeset e6ef90038007 (bug 804387) Backed out changeset 0ad6f67a95f9 (bug 804387) Backed out changeset d0772aba503c (bug 804387) Backed out changeset 5477b87ff03e (bug 804387) Backed out changeset 1d7ec5adc49f (bug 804387) Backed out changeset 11f4d740cd6c (bug 804387) Backed out changeset e6254d8997ab (bug 804387) Backed out changeset 372322f3264d (bug 804387) Backed out changeset 53d5ed687612 (bug 804387) Backed out changeset 000b88ac40a7 (bug 804387)
This commit is contained in:
Родитель
3f79e8f932
Коммит
81075674d3
|
@ -2450,13 +2450,13 @@ public:
|
|||
} else {
|
||||
event = NS_NewRunnableMethod(this, &StreamListener::DoNotifyUnblocked);
|
||||
}
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event);
|
||||
}
|
||||
virtual void NotifyFinished(MediaStreamGraph* aGraph)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &StreamListener::DoNotifyFinished);
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event);
|
||||
}
|
||||
virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph,
|
||||
bool aHasCurrentData)
|
||||
|
@ -2471,7 +2471,7 @@ public:
|
|||
if (aHasCurrentData) {
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData);
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event);
|
||||
}
|
||||
}
|
||||
virtual void NotifyOutput(MediaStreamGraph* aGraph)
|
||||
|
@ -2482,7 +2482,7 @@ public:
|
|||
mPendingNotifyOutput = true;
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &StreamListener::DoNotifyOutput);
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event);
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "AudioNodeEngine.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void
|
||||
AllocateAudioBlock(uint32_t aChannelCount, AudioChunk* aChunk)
|
||||
{
|
||||
// XXX for SIMD purposes we should do something here to make sure the
|
||||
// channel buffers are 16-byte aligned.
|
||||
nsRefPtr<SharedBuffer> buffer =
|
||||
SharedBuffer::Create(WEBAUDIO_BLOCK_SIZE*aChannelCount*sizeof(float));
|
||||
aChunk->mDuration = WEBAUDIO_BLOCK_SIZE;
|
||||
aChunk->mChannelData.SetLength(aChannelCount);
|
||||
float* data = static_cast<float*>(buffer->Data());
|
||||
for (uint32_t i = 0; i < aChannelCount; ++i) {
|
||||
aChunk->mChannelData[i] = data + i*WEBAUDIO_BLOCK_SIZE;
|
||||
}
|
||||
aChunk->mBuffer = buffer.forget();
|
||||
aChunk->mVolume = 1.0f;
|
||||
aChunk->mBufferFormat = AUDIO_FORMAT_FLOAT32;
|
||||
}
|
||||
|
||||
void
|
||||
WriteZeroesToAudioBlock(AudioChunk* aChunk, uint32_t aStart, uint32_t aLength)
|
||||
{
|
||||
MOZ_ASSERT(aStart + aLength <= WEBAUDIO_BLOCK_SIZE);
|
||||
if (aLength == 0)
|
||||
return;
|
||||
for (uint32_t i = 0; i < aChunk->mChannelData.Length(); ++i) {
|
||||
memset(static_cast<float*>(const_cast<void*>(aChunk->mChannelData[i])) + aStart,
|
||||
0, aLength*sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioBlockAddChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
|
||||
float aScale,
|
||||
float aOutput[WEBAUDIO_BLOCK_SIZE])
|
||||
{
|
||||
if (aScale == 1.0f) {
|
||||
for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
|
||||
aOutput[i] += aInput[i];
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
|
||||
aOutput[i] += aInput[i]*aScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
|
||||
float aScale,
|
||||
float aOutput[WEBAUDIO_BLOCK_SIZE])
|
||||
{
|
||||
if (aScale == 1.0f) {
|
||||
memcpy(aOutput, aInput, WEBAUDIO_BLOCK_SIZE*sizeof(float));
|
||||
} else {
|
||||
for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
|
||||
aOutput[i] = aInput[i]*aScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 MOZILLA_AUDIONODEENGINE_H_
|
||||
#define MOZILLA_AUDIONODEENGINE_H_
|
||||
|
||||
#include "AudioSegment.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AudioNodeStream;
|
||||
|
||||
// We ensure that the graph advances in steps that are multiples of the Web
|
||||
// Audio block size
|
||||
const uint32_t WEBAUDIO_BLOCK_SIZE_BITS = 7;
|
||||
const uint32_t WEBAUDIO_BLOCK_SIZE = 1 << WEBAUDIO_BLOCK_SIZE_BITS;
|
||||
|
||||
/**
|
||||
* This class holds onto a set of immutable channel buffers. The storage
|
||||
* for the buffers must be malloced, but the buffer pointers and the malloc
|
||||
* pointers can be different (e.g. if the buffers are contained inside
|
||||
* some malloced object).
|
||||
*/
|
||||
class ThreadSharedFloatArrayBufferList : public ThreadSharedObject {
|
||||
public:
|
||||
/**
|
||||
* Construct with null data.
|
||||
*/
|
||||
ThreadSharedFloatArrayBufferList(uint32_t aCount)
|
||||
{
|
||||
mContents.SetLength(aCount);
|
||||
}
|
||||
|
||||
struct Storage {
|
||||
Storage()
|
||||
{
|
||||
mDataToFree = nullptr;
|
||||
mSampleData = nullptr;
|
||||
}
|
||||
~Storage() { free(mDataToFree); }
|
||||
void* mDataToFree;
|
||||
const float* mSampleData;
|
||||
};
|
||||
|
||||
/**
|
||||
* This can be called on any thread.
|
||||
*/
|
||||
uint32_t GetChannels() const { return mContents.Length(); }
|
||||
/**
|
||||
* This can be called on any thread.
|
||||
*/
|
||||
const float* GetData(uint32_t aIndex) const { return mContents[aIndex].mSampleData; }
|
||||
|
||||
/**
|
||||
* Call this only during initialization, before the object is handed to
|
||||
* any other thread.
|
||||
*/
|
||||
void SetData(uint32_t aIndex, void* aDataToFree, const float* aData)
|
||||
{
|
||||
Storage* s = &mContents[aIndex];
|
||||
free(s->mDataToFree);
|
||||
s->mDataToFree = aDataToFree;
|
||||
s->mSampleData = aData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put this object into an error state where there are no channels.
|
||||
*/
|
||||
void Clear() { mContents.Clear(); }
|
||||
|
||||
private:
|
||||
AutoFallibleTArray<Storage,2> mContents;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocates an AudioChunk with fresh buffers of WEBAUDIO_BLOCK_SIZE float samples.
|
||||
* AudioChunk::mChannelData's entries can be cast to float* for writing.
|
||||
*/
|
||||
void AllocateAudioBlock(uint32_t aChannelCount, AudioChunk* aChunk);
|
||||
|
||||
/**
|
||||
* aChunk must have been allocated by AllocateAudioBlock.
|
||||
*/
|
||||
void WriteZeroesToAudioBlock(AudioChunk* aChunk, uint32_t aStart, uint32_t aLength);
|
||||
|
||||
/**
|
||||
* Pointwise multiply-add operation. aScale == 1.0f should be optimized.
|
||||
*/
|
||||
void AudioBlockAddChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
|
||||
float aScale,
|
||||
float aOutput[WEBAUDIO_BLOCK_SIZE]);
|
||||
|
||||
/**
|
||||
* Pointwise copy-scaled operation. aScale == 1.0f should be optimized.
|
||||
*/
|
||||
void AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
|
||||
float aScale,
|
||||
float aOutput[WEBAUDIO_BLOCK_SIZE]);
|
||||
|
||||
/**
|
||||
* All methods of this class and its subclasses are called on the
|
||||
* MediaStreamGraph thread.
|
||||
*/
|
||||
class AudioNodeEngine {
|
||||
public:
|
||||
AudioNodeEngine() {}
|
||||
virtual ~AudioNodeEngine() {}
|
||||
|
||||
virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam)
|
||||
{
|
||||
NS_ERROR("Invalid SetStreamTimeParameter index");
|
||||
}
|
||||
virtual void SetDoubleParameter(uint32_t aIndex, double aParam)
|
||||
{
|
||||
NS_ERROR("Invalid SetDoubleParameter index");
|
||||
}
|
||||
virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
|
||||
{
|
||||
NS_ERROR("Invalid SetInt32Parameter index");
|
||||
}
|
||||
virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
|
||||
{
|
||||
NS_ERROR("SetBuffer called on engine that doesn't support it");
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the next block of audio samples, given input samples aInput
|
||||
* (the mixed data for input 0).
|
||||
* By default, simply returns the mixed input.
|
||||
* aInput is guaranteed to have float sample format (if it has samples at all)
|
||||
* and to have been resampled to IdealAudioRate(), and to have exactly
|
||||
* WEBAUDIO_BLOCK_SIZE samples.
|
||||
* *aFinished is set to false by the caller. If the callee sets it to true,
|
||||
* we'll finish the stream and not call this again.
|
||||
*/
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
{
|
||||
*aOutput = aInput;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MOZILLA_AUDIONODEENGINE_H_ */
|
|
@ -1,270 +0,0 @@
|
|||
/* -*- 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 "AudioNodeStream.h"
|
||||
|
||||
#include "MediaStreamGraphImpl.h"
|
||||
#include "AudioNodeEngine.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* An AudioNodeStream produces a single audio track with ID
|
||||
* AUDIO_NODE_STREAM_TRACK_ID. This track has rate IdealAudioRate().
|
||||
* Each chunk in the track is a single block of WEBAUDIO_BLOCK_SIZE samples.
|
||||
*/
|
||||
static const int AUDIO_NODE_STREAM_TRACK_ID = 1;
|
||||
|
||||
AudioNodeStream::~AudioNodeStream()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
AudioNodeStream::SetStreamTimeParameter(uint32_t aIndex, MediaStream* aRelativeToStream,
|
||||
double aStreamTime)
|
||||
{
|
||||
class Message : public ControlMessage {
|
||||
public:
|
||||
Message(AudioNodeStream* aStream, uint32_t aIndex, MediaStream* aRelativeToStream,
|
||||
double aStreamTime)
|
||||
: ControlMessage(aStream), mStreamTime(aStreamTime),
|
||||
mRelativeToStream(aRelativeToStream), mIndex(aIndex) {}
|
||||
virtual void Run()
|
||||
{
|
||||
static_cast<AudioNodeStream*>(mStream)->
|
||||
SetStreamTimeParameterImpl(mIndex, mRelativeToStream, mStreamTime);
|
||||
}
|
||||
double mStreamTime;
|
||||
MediaStream* mRelativeToStream;
|
||||
uint32_t mIndex;
|
||||
};
|
||||
|
||||
MOZ_ASSERT(this);
|
||||
GraphImpl()->AppendMessage(new Message(this, aIndex, aRelativeToStream, aStreamTime));
|
||||
}
|
||||
|
||||
void
|
||||
AudioNodeStream::SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
|
||||
double aStreamTime)
|
||||
{
|
||||
StreamTime streamTime = std::max<MediaTime>(0, SecondsToMediaTime(aStreamTime));
|
||||
GraphTime graphTime = aRelativeToStream->StreamTimeToGraphTime(streamTime);
|
||||
StreamTime thisStreamTime = GraphTimeToStreamTimeOptimistic(graphTime);
|
||||
TrackTicks ticks = TimeToTicksRoundDown(IdealAudioRate(), thisStreamTime);
|
||||
mEngine->SetStreamTimeParameter(aIndex, ticks);
|
||||
}
|
||||
|
||||
void
|
||||
AudioNodeStream::SetDoubleParameter(uint32_t aIndex, double aValue)
|
||||
{
|
||||
class Message : public ControlMessage {
|
||||
public:
|
||||
Message(AudioNodeStream* aStream, uint32_t aIndex, double aValue)
|
||||
: ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
|
||||
virtual void Run()
|
||||
{
|
||||
static_cast<AudioNodeStream*>(mStream)->Engine()->
|
||||
SetDoubleParameter(mIndex, mValue);
|
||||
}
|
||||
double mValue;
|
||||
uint32_t mIndex;
|
||||
};
|
||||
|
||||
MOZ_ASSERT(this);
|
||||
GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
|
||||
}
|
||||
|
||||
void
|
||||
AudioNodeStream::SetInt32Parameter(uint32_t aIndex, int32_t aValue)
|
||||
{
|
||||
class Message : public ControlMessage {
|
||||
public:
|
||||
Message(AudioNodeStream* aStream, uint32_t aIndex, int32_t aValue)
|
||||
: ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
|
||||
virtual void Run()
|
||||
{
|
||||
static_cast<AudioNodeStream*>(mStream)->Engine()->
|
||||
SetInt32Parameter(mIndex, mValue);
|
||||
}
|
||||
int32_t mValue;
|
||||
uint32_t mIndex;
|
||||
};
|
||||
|
||||
MOZ_ASSERT(this);
|
||||
GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
|
||||
}
|
||||
|
||||
void
|
||||
AudioNodeStream::SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
|
||||
{
|
||||
class Message : public ControlMessage {
|
||||
public:
|
||||
Message(AudioNodeStream* aStream,
|
||||
already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
|
||||
: ControlMessage(aStream), mBuffer(aBuffer) {}
|
||||
virtual void Run()
|
||||
{
|
||||
static_cast<AudioNodeStream*>(mStream)->Engine()->
|
||||
SetBuffer(mBuffer.forget());
|
||||
}
|
||||
nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
|
||||
};
|
||||
|
||||
MOZ_ASSERT(this);
|
||||
GraphImpl()->AppendMessage(new Message(this, aBuffer));
|
||||
}
|
||||
|
||||
StreamBuffer::Track*
|
||||
AudioNodeStream::EnsureTrack()
|
||||
{
|
||||
StreamBuffer::Track* track = mBuffer.FindTrack(AUDIO_NODE_STREAM_TRACK_ID);
|
||||
if (!track) {
|
||||
nsAutoPtr<MediaSegment> segment(new AudioSegment());
|
||||
for (uint32_t j = 0; j < mListeners.Length(); ++j) {
|
||||
MediaStreamListener* l = mListeners[j];
|
||||
l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID, IdealAudioRate(), 0,
|
||||
MediaStreamListener::TRACK_EVENT_CREATED,
|
||||
*segment);
|
||||
}
|
||||
track = &mBuffer.AddTrack(AUDIO_NODE_STREAM_TRACK_ID, IdealAudioRate(), 0, segment.forget());
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
AudioChunk*
|
||||
AudioNodeStream::ObtainInputBlock(AudioChunk* aTmpChunk)
|
||||
{
|
||||
uint32_t inputCount = mInputs.Length();
|
||||
uint32_t outputChannelCount = 0;
|
||||
nsAutoTArray<AudioChunk*,250> inputChunks;
|
||||
for (uint32_t i = 0; i < inputCount; ++i) {
|
||||
MediaStream* s = mInputs[i]->GetSource();
|
||||
AudioNodeStream* a = s->AsAudioNodeStream();
|
||||
MOZ_ASSERT(a);
|
||||
if (a->IsFinishedOnGraphThread()) {
|
||||
continue;
|
||||
}
|
||||
AudioChunk* chunk = a->mLastChunk;
|
||||
// XXX when we implement DelayNode, this will no longer be true and we'll
|
||||
// need to treat a null chunk (when the DelayNode hasn't had a chance
|
||||
// to produce data yet) as silence here.
|
||||
MOZ_ASSERT(chunk);
|
||||
if (chunk->IsNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
inputChunks.AppendElement(chunk);
|
||||
outputChannelCount =
|
||||
GetAudioChannelsSuperset(outputChannelCount, chunk->mChannelData.Length());
|
||||
}
|
||||
|
||||
uint32_t inputChunkCount = inputChunks.Length();
|
||||
if (inputChunkCount == 0) {
|
||||
aTmpChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
return aTmpChunk;
|
||||
}
|
||||
|
||||
if (inputChunkCount == 1) {
|
||||
return inputChunks[0];
|
||||
}
|
||||
|
||||
AllocateAudioBlock(outputChannelCount, aTmpChunk);
|
||||
|
||||
for (uint32_t i = 0; i < inputChunkCount; ++i) {
|
||||
AudioChunk* chunk = inputChunks[i];
|
||||
nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channels;
|
||||
channels.AppendElements(chunk->mChannelData);
|
||||
if (channels.Length() < outputChannelCount) {
|
||||
AudioChannelsUpMix(&channels, outputChannelCount, nullptr);
|
||||
NS_ASSERTION(outputChannelCount == channels.Length(),
|
||||
"We called GetAudioChannelsSuperset to avoid this");
|
||||
}
|
||||
|
||||
for (uint32_t c = 0; c < channels.Length(); ++c) {
|
||||
const float* inputData = static_cast<const float*>(channels[c]);
|
||||
float* outputData = static_cast<float*>(const_cast<void*>(aTmpChunk->mChannelData[c]));
|
||||
if (inputData) {
|
||||
if (i == 0) {
|
||||
AudioBlockCopyChannelWithScale(inputData, chunk->mVolume, outputData);
|
||||
} else {
|
||||
AudioBlockAddChannelWithScale(inputData, chunk->mVolume, outputData);
|
||||
}
|
||||
} else {
|
||||
if (i == 0) {
|
||||
memset(outputData, 0, WEBAUDIO_BLOCK_SIZE*sizeof(float));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aTmpChunk;
|
||||
}
|
||||
|
||||
// The MediaStreamGraph guarantees that this is actually one block, for
|
||||
// AudioNodeStreams.
|
||||
void
|
||||
AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
|
||||
{
|
||||
StreamBuffer::Track* track = EnsureTrack();
|
||||
|
||||
AudioChunk outputChunk;
|
||||
AudioSegment* segment = track->Get<AudioSegment>();
|
||||
|
||||
if (mInCycle) {
|
||||
// XXX DelayNode not supported yet so just produce silence
|
||||
outputChunk.SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
} else {
|
||||
AudioChunk tmpChunk;
|
||||
AudioChunk* inputChunk = ObtainInputBlock(&tmpChunk);
|
||||
bool finished = false;
|
||||
mEngine->ProduceAudioBlock(this, *inputChunk, &outputChunk, &finished);
|
||||
if (finished) {
|
||||
FinishOutput();
|
||||
}
|
||||
}
|
||||
|
||||
mLastChunk = segment->AppendAndConsumeChunk(&outputChunk);
|
||||
|
||||
for (uint32_t j = 0; j < mListeners.Length(); ++j) {
|
||||
MediaStreamListener* l = mListeners[j];
|
||||
AudioChunk copyChunk = *mLastChunk;
|
||||
AudioSegment tmpSegment;
|
||||
tmpSegment.AppendAndConsumeChunk(©Chunk);
|
||||
l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID,
|
||||
IdealAudioRate(), segment->GetDuration(), 0,
|
||||
tmpSegment);
|
||||
}
|
||||
}
|
||||
|
||||
TrackTicks
|
||||
AudioNodeStream::GetCurrentPosition()
|
||||
{
|
||||
return EnsureTrack()->Get<AudioSegment>()->GetDuration();
|
||||
}
|
||||
|
||||
void
|
||||
AudioNodeStream::FinishOutput()
|
||||
{
|
||||
if (IsFinishedOnGraphThread()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StreamBuffer::Track* track = EnsureTrack();
|
||||
track->SetEnded();
|
||||
FinishOnGraphThread();
|
||||
|
||||
for (uint32_t j = 0; j < mListeners.Length(); ++j) {
|
||||
MediaStreamListener* l = mListeners[j];
|
||||
AudioSegment emptySegment;
|
||||
l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID,
|
||||
IdealAudioRate(),
|
||||
track->GetSegment()->GetDuration(),
|
||||
MediaStreamListener::TRACK_EVENT_ENDED, emptySegment);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/* -*- 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 MOZILLA_AUDIONODESTREAM_H_
|
||||
#define MOZILLA_AUDIONODESTREAM_H_
|
||||
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "AudioChannelFormat.h"
|
||||
#include "AudioNodeEngine.h"
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
|
||||
#else
|
||||
#define LOG(type, msg)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ThreadSharedFloatArrayBufferList;
|
||||
|
||||
/**
|
||||
* An AudioNodeStream produces one audio track with ID AUDIO_TRACK.
|
||||
* The start time of the AudioTrack is aligned to the start time of the
|
||||
* AudioContext's destination node stream, plus some multiple of BLOCK_SIZE
|
||||
* samples.
|
||||
*
|
||||
* An AudioNodeStream has an AudioNodeEngine plugged into it that does the
|
||||
* actual audio processing. AudioNodeStream contains the glue code that
|
||||
* integrates audio processing with the MediaStreamGraph.
|
||||
*/
|
||||
class AudioNodeStream : public ProcessedMediaStream {
|
||||
public:
|
||||
enum { AUDIO_TRACK = 1 };
|
||||
|
||||
/**
|
||||
* Transfers ownership of aEngine to the new AudioNodeStream.
|
||||
*/
|
||||
explicit AudioNodeStream(AudioNodeEngine* aEngine)
|
||||
: ProcessedMediaStream(nullptr), mEngine(aEngine), mLastChunk(nullptr)
|
||||
{
|
||||
}
|
||||
~AudioNodeStream();
|
||||
|
||||
// Control API
|
||||
/**
|
||||
* Sets a parameter that's a time relative to some stream's played time.
|
||||
* This time is converted to a time relative to this stream when it's set.
|
||||
*/
|
||||
void SetStreamTimeParameter(uint32_t aIndex, MediaStream* aRelativeToStream,
|
||||
double aStreamTime);
|
||||
void SetDoubleParameter(uint32_t aIndex, double aValue);
|
||||
void SetInt32Parameter(uint32_t aIndex, int32_t aValue);
|
||||
void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer);
|
||||
|
||||
virtual AudioNodeStream* AsAudioNodeStream() { return this; }
|
||||
|
||||
// Graph thread only
|
||||
void SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
|
||||
double aStreamTime);
|
||||
virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo);
|
||||
TrackTicks GetCurrentPosition();
|
||||
|
||||
// Any thread
|
||||
AudioNodeEngine* Engine() { return mEngine; }
|
||||
|
||||
protected:
|
||||
void FinishOutput();
|
||||
|
||||
StreamBuffer::Track* EnsureTrack();
|
||||
AudioChunk* ObtainInputBlock(AudioChunk* aTmpChunk);
|
||||
|
||||
// The engine that will generate output for this node.
|
||||
nsAutoPtr<AudioNodeEngine> mEngine;
|
||||
// The last block produced by this node.
|
||||
AudioChunk* mLastChunk;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MOZILLA_AUDIONODESTREAM_H_ */
|
|
@ -89,6 +89,7 @@ AudioSegment::ApplyVolume(float aVolume)
|
|||
}
|
||||
|
||||
static const int AUDIO_PROCESSING_FRAMES = 640; /* > 10ms of 48KHz audio */
|
||||
static const int GUESS_AUDIO_CHANNELS = 2;
|
||||
static const uint8_t gZeroChannel[MAX_AUDIO_SAMPLE_SIZE*AUDIO_PROCESSING_FRAMES] = {0};
|
||||
|
||||
void
|
||||
|
|
|
@ -15,11 +15,6 @@ namespace mozilla {
|
|||
|
||||
class AudioStream;
|
||||
|
||||
/**
|
||||
* For auto-arrays etc, guess this as the common number of channels.
|
||||
*/
|
||||
const int GUESS_AUDIO_CHANNELS = 2;
|
||||
|
||||
/**
|
||||
* An AudioChunk represents a multi-channel buffer of audio samples.
|
||||
* It references an underlying ThreadSharedObject which manages the lifetime
|
||||
|
|
|
@ -19,8 +19,6 @@ endif # !_MSC_VER
|
|||
EXPORTS = \
|
||||
AbstractMediaDecoder.h \
|
||||
AudioChannelFormat.h \
|
||||
AudioNodeEngine.h \
|
||||
AudioNodeStream.h \
|
||||
AudioSampleFormat.h \
|
||||
AudioSegment.h \
|
||||
BufferMediaResource.h \
|
||||
|
@ -48,8 +46,6 @@ EXPORTS = \
|
|||
|
||||
CPPSRCS = \
|
||||
AudioChannelFormat.cpp \
|
||||
AudioNodeEngine.cpp \
|
||||
AudioNodeStream.cpp \
|
||||
AudioSegment.cpp \
|
||||
DecoderTraits.cpp \
|
||||
FileBlockCache.cpp \
|
||||
|
|
|
@ -157,36 +157,30 @@ void MediaDecoder::ConnectDecodedStreamToOutputStream(OutputStreamData* aStream)
|
|||
}
|
||||
|
||||
MediaDecoder::DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder,
|
||||
int64_t aInitialTime,
|
||||
SourceMediaStream* aStream)
|
||||
int64_t aInitialTime,
|
||||
SourceMediaStream* aStream)
|
||||
: mLastAudioPacketTime(-1),
|
||||
mLastAudioPacketEndTime(-1),
|
||||
mAudioFramesWritten(0),
|
||||
mInitialTime(aInitialTime),
|
||||
mNextVideoTime(aInitialTime),
|
||||
mDecoder(aDecoder),
|
||||
mStreamInitialized(false),
|
||||
mHaveSentFinish(false),
|
||||
mHaveSentFinishAudio(false),
|
||||
mHaveSentFinishVideo(false),
|
||||
mStream(aStream),
|
||||
mMainThreadListener(new DecodedStreamMainThreadListener(aDecoder)),
|
||||
mHaveBlockedForPlayState(false)
|
||||
{
|
||||
mStream->AddMainThreadListener(this);
|
||||
mStream->AddMainThreadListener(mMainThreadListener);
|
||||
}
|
||||
|
||||
MediaDecoder::DecodedStreamData::~DecodedStreamData()
|
||||
{
|
||||
mStream->RemoveMainThreadListener(this);
|
||||
mStream->RemoveMainThreadListener(mMainThreadListener);
|
||||
mStream->Destroy();
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoder::DecodedStreamData::NotifyMainThreadStateChanged()
|
||||
{
|
||||
mDecoder->NotifyDecodedStreamMainThreadStateChanged();
|
||||
}
|
||||
|
||||
void MediaDecoder::DestroyDecodedStream()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
|
|
@ -235,6 +235,7 @@ class MediaDecoder : public nsIObserver,
|
|||
{
|
||||
public:
|
||||
typedef mozilla::layers::Image Image;
|
||||
class DecodedStreamMainThreadListener;
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
@ -339,7 +340,7 @@ public:
|
|||
// replaying after the input as ended. In the latter case, the new source is
|
||||
// not connected to streams created by captureStreamUntilEnded.
|
||||
|
||||
struct DecodedStreamData MOZ_FINAL : public MainThreadMediaStreamListener {
|
||||
struct DecodedStreamData {
|
||||
DecodedStreamData(MediaDecoder* aDecoder,
|
||||
int64_t aInitialTime, SourceMediaStream* aStream);
|
||||
~DecodedStreamData();
|
||||
|
@ -357,7 +358,6 @@ public:
|
|||
// Therefore video packets starting at or after this time need to be copied
|
||||
// to the output stream.
|
||||
int64_t mNextVideoTime; // microseconds
|
||||
MediaDecoder* mDecoder;
|
||||
// The last video image sent to the stream. Useful if we need to replicate
|
||||
// the image.
|
||||
nsRefPtr<Image> mLastVideoImage;
|
||||
|
@ -372,11 +372,12 @@ public:
|
|||
// The decoder is responsible for calling Destroy() on this stream.
|
||||
// Can be read from any thread.
|
||||
const nsRefPtr<SourceMediaStream> mStream;
|
||||
// A listener object that receives notifications when mStream's
|
||||
// main-thread-visible state changes. Used on the main thread only.
|
||||
const nsRefPtr<DecodedStreamMainThreadListener> mMainThreadListener;
|
||||
// True when we've explicitly blocked this stream because we're
|
||||
// not in PLAY_STATE_PLAYING. Used on the main thread only.
|
||||
bool mHaveBlockedForPlayState;
|
||||
|
||||
virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
|
||||
};
|
||||
struct OutputStreamData {
|
||||
void Init(ProcessedMediaStream* aStream, bool aFinishWhenEnded)
|
||||
|
@ -420,6 +421,16 @@ public:
|
|||
GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
return mDecodedStream;
|
||||
}
|
||||
class DecodedStreamMainThreadListener : public MainThreadMediaStreamListener {
|
||||
public:
|
||||
DecodedStreamMainThreadListener(MediaDecoder* aDecoder)
|
||||
: mDecoder(aDecoder) {}
|
||||
virtual void NotifyMainThreadStateChanged()
|
||||
{
|
||||
mDecoder->NotifyDecodedStreamMainThreadStateChanged();
|
||||
}
|
||||
MediaDecoder* mDecoder;
|
||||
};
|
||||
|
||||
// Add an output stream. All decoder output will be sent to the stream.
|
||||
// The stream is initially blocked. The decoder is responsible for unblocking
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
* 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 "MediaStreamGraphImpl.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "AudioSegment.h"
|
||||
#include "VideoSegment.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
@ -19,8 +21,6 @@
|
|||
#include "TrackUnionStream.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "AudioChannelCommon.h"
|
||||
#include "AudioNodeEngine.h"
|
||||
#include "AudioNodeStream.h"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace mozilla::layers;
|
||||
|
@ -30,8 +30,486 @@ namespace mozilla {
|
|||
|
||||
#ifdef PR_LOGGING
|
||||
PRLogModuleInfo* gMediaStreamGraphLog;
|
||||
#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
|
||||
#else
|
||||
#define LOG(type, msg)
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Assume we can run an iteration of the MediaStreamGraph loop in this much time
|
||||
* or less.
|
||||
* We try to run the control loop at this rate.
|
||||
*/
|
||||
const int MEDIA_GRAPH_TARGET_PERIOD_MS = 10;
|
||||
|
||||
/**
|
||||
* Assume that we might miss our scheduled wakeup of the MediaStreamGraph by
|
||||
* this much.
|
||||
*/
|
||||
const int SCHEDULE_SAFETY_MARGIN_MS = 10;
|
||||
|
||||
/**
|
||||
* Try have this much audio buffered in streams and queued to the hardware.
|
||||
* The maximum delay to the end of the next control loop
|
||||
* is 2*MEDIA_GRAPH_TARGET_PERIOD_MS + SCHEDULE_SAFETY_MARGIN_MS.
|
||||
* There is no point in buffering more audio than this in a stream at any
|
||||
* given time (until we add processing).
|
||||
* This is not optimal yet.
|
||||
*/
|
||||
const int AUDIO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
|
||||
SCHEDULE_SAFETY_MARGIN_MS;
|
||||
|
||||
/**
|
||||
* Try have this much video buffered. Video frames are set
|
||||
* near the end of the iteration of the control loop. The maximum delay
|
||||
* to the setting of the next video frame is 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
|
||||
* SCHEDULE_SAFETY_MARGIN_MS. This is not optimal yet.
|
||||
*/
|
||||
const int VIDEO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
|
||||
SCHEDULE_SAFETY_MARGIN_MS;
|
||||
|
||||
/**
|
||||
* A per-stream update message passed from the media graph thread to the
|
||||
* main thread.
|
||||
*/
|
||||
struct StreamUpdate {
|
||||
int64_t mGraphUpdateIndex;
|
||||
nsRefPtr<MediaStream> mStream;
|
||||
StreamTime mNextMainThreadCurrentTime;
|
||||
bool mNextMainThreadFinished;
|
||||
};
|
||||
|
||||
/**
|
||||
* This represents a message passed from the main thread to the graph thread.
|
||||
* A ControlMessage always references a particular affected stream.
|
||||
*/
|
||||
class ControlMessage {
|
||||
public:
|
||||
ControlMessage(MediaStream* aStream) : mStream(aStream)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ControlMessage);
|
||||
}
|
||||
// All these run on the graph thread
|
||||
virtual ~ControlMessage()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ControlMessage);
|
||||
}
|
||||
// Do the action of this message on the MediaStreamGraph thread. Any actions
|
||||
// affecting graph processing should take effect at mStateComputedTime.
|
||||
// All stream data for times < mStateComputedTime has already been
|
||||
// computed.
|
||||
virtual void Run() = 0;
|
||||
// When we're shutting down the application, most messages are ignored but
|
||||
// some cleanup messages should still be processed (on the main thread).
|
||||
virtual void RunDuringShutdown() {}
|
||||
MediaStream* GetStream() { return mStream; }
|
||||
|
||||
protected:
|
||||
// We do not hold a reference to mStream. The graph will be holding
|
||||
// a reference to the stream until the Destroy message is processed. The
|
||||
// last message referencing a stream is the Destroy message for that stream.
|
||||
MediaStream* mStream;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of a media stream graph. This class is private to this
|
||||
* file. It's not in the anonymous namespace because MediaStream needs to
|
||||
* be able to friend it.
|
||||
*
|
||||
* Currently we only have one per process.
|
||||
*/
|
||||
class MediaStreamGraphImpl : public MediaStreamGraph {
|
||||
public:
|
||||
MediaStreamGraphImpl();
|
||||
~MediaStreamGraphImpl()
|
||||
{
|
||||
NS_ASSERTION(IsEmpty(),
|
||||
"All streams should have been destroyed by messages from the main thread");
|
||||
LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p destroyed", this));
|
||||
}
|
||||
|
||||
// Main thread only.
|
||||
/**
|
||||
* This runs every time we need to sync state from the media graph thread
|
||||
* to the main thread while the main thread is not in the middle
|
||||
* of a script. It runs during a "stable state" (per HTML5) or during
|
||||
* an event posted to the main thread.
|
||||
*/
|
||||
void RunInStableState();
|
||||
/**
|
||||
* Ensure a runnable to run RunInStableState is posted to the appshell to
|
||||
* run at the next stable state (per HTML5).
|
||||
* See EnsureStableStateEventPosted.
|
||||
*/
|
||||
void EnsureRunInStableState();
|
||||
/**
|
||||
* Called to apply a StreamUpdate to its stream.
|
||||
*/
|
||||
void ApplyStreamUpdate(StreamUpdate* aUpdate);
|
||||
/**
|
||||
* Append a ControlMessage to the message queue. This queue is drained
|
||||
* during RunInStableState; the messages will run on the graph thread.
|
||||
*/
|
||||
void AppendMessage(ControlMessage* aMessage);
|
||||
/**
|
||||
* Make this MediaStreamGraph enter forced-shutdown state. This state
|
||||
* will be noticed by the media graph thread, which will shut down all streams
|
||||
* and other state controlled by the media graph thread.
|
||||
* This is called during application shutdown.
|
||||
*/
|
||||
void ForceShutDown();
|
||||
/**
|
||||
* Shutdown() this MediaStreamGraph's threads and return when they've shut down.
|
||||
*/
|
||||
void ShutdownThreads();
|
||||
|
||||
// The following methods run on the graph thread (or possibly the main thread if
|
||||
// mLifecycleState > LIFECYCLE_RUNNING)
|
||||
/**
|
||||
* Runs main control loop on the graph thread. Normally a single invocation
|
||||
* of this runs for the entire lifetime of the graph thread.
|
||||
*/
|
||||
void RunThread();
|
||||
/**
|
||||
* Call this to indicate that another iteration of the control loop is
|
||||
* required on its regular schedule. The monitor must not be held.
|
||||
*/
|
||||
void EnsureNextIteration();
|
||||
/**
|
||||
* As above, but with the monitor already held.
|
||||
*/
|
||||
void EnsureNextIterationLocked(MonitorAutoLock& aLock);
|
||||
/**
|
||||
* Call this to indicate that another iteration of the control loop is
|
||||
* required immediately. The monitor must already be held.
|
||||
*/
|
||||
void EnsureImmediateWakeUpLocked(MonitorAutoLock& aLock);
|
||||
/**
|
||||
* Ensure there is an event posted to the main thread to run RunInStableState.
|
||||
* mMonitor must be held.
|
||||
* See EnsureRunInStableState
|
||||
*/
|
||||
void EnsureStableStateEventPosted();
|
||||
/**
|
||||
* Generate messages to the main thread to update it for all state changes.
|
||||
* mMonitor must be held.
|
||||
*/
|
||||
void PrepareUpdatesToMainThreadState();
|
||||
// The following methods are the various stages of RunThread processing.
|
||||
/**
|
||||
* Compute a new current time for the graph and advance all on-graph-thread
|
||||
* state to the new current time.
|
||||
*/
|
||||
void UpdateCurrentTime();
|
||||
/**
|
||||
* Update the consumption state of aStream to reflect whether its data
|
||||
* is needed or not.
|
||||
*/
|
||||
void UpdateConsumptionState(SourceMediaStream* aStream);
|
||||
/**
|
||||
* Extract any state updates pending in aStream, and apply them.
|
||||
*/
|
||||
void ExtractPendingInput(SourceMediaStream* aStream,
|
||||
GraphTime aDesiredUpToTime,
|
||||
bool* aEnsureNextIteration);
|
||||
/**
|
||||
* Update "have enough data" flags in aStream.
|
||||
*/
|
||||
void UpdateBufferSufficiencyState(SourceMediaStream* aStream);
|
||||
/*
|
||||
* If aStream hasn't already been ordered, push it onto aStack and order
|
||||
* its children.
|
||||
*/
|
||||
void UpdateStreamOrderForStream(nsTArray<MediaStream*>* aStack,
|
||||
already_AddRefed<MediaStream> aStream);
|
||||
/**
|
||||
* Mark aStream and all its inputs (recursively) as consumed.
|
||||
*/
|
||||
static void MarkConsumed(MediaStream* aStream);
|
||||
/**
|
||||
* Sort mStreams so that every stream not in a cycle is after any streams
|
||||
* it depends on, and every stream in a cycle is marked as being in a cycle.
|
||||
* Also sets mIsConsumed on every stream.
|
||||
*/
|
||||
void UpdateStreamOrder();
|
||||
/**
|
||||
* Compute the blocking states of streams from mStateComputedTime
|
||||
* until the desired future time aEndBlockingDecisions.
|
||||
* Updates mStateComputedTime and sets MediaStream::mBlocked
|
||||
* for all streams.
|
||||
*/
|
||||
void RecomputeBlocking(GraphTime aEndBlockingDecisions);
|
||||
// The following methods are used to help RecomputeBlocking.
|
||||
/**
|
||||
* If aStream isn't already in aStreams, add it and recursively call
|
||||
* AddBlockingRelatedStreamsToSet on all the streams whose blocking
|
||||
* status could depend on or affect the state of aStream.
|
||||
*/
|
||||
void AddBlockingRelatedStreamsToSet(nsTArray<MediaStream*>* aStreams,
|
||||
MediaStream* aStream);
|
||||
/**
|
||||
* Mark a stream blocked at time aTime. If this results in decisions that need
|
||||
* to be revisited at some point in the future, *aEnd will be reduced to the
|
||||
* first time in the future to recompute those decisions.
|
||||
*/
|
||||
void MarkStreamBlocking(MediaStream* aStream);
|
||||
/**
|
||||
* Recompute blocking for the streams in aStreams for the interval starting at aTime.
|
||||
* If this results in decisions that need to be revisited at some point
|
||||
* in the future, *aEnd will be reduced to the first time in the future to
|
||||
* recompute those decisions.
|
||||
*/
|
||||
void RecomputeBlockingAt(const nsTArray<MediaStream*>& aStreams,
|
||||
GraphTime aTime, GraphTime aEndBlockingDecisions,
|
||||
GraphTime* aEnd);
|
||||
/**
|
||||
* Returns true if aStream will underrun at aTime for its own playback.
|
||||
* aEndBlockingDecisions is when we plan to stop making blocking decisions.
|
||||
* *aEnd will be reduced to the first time in the future to recompute these
|
||||
* decisions.
|
||||
*/
|
||||
bool WillUnderrun(MediaStream* aStream, GraphTime aTime,
|
||||
GraphTime aEndBlockingDecisions, GraphTime* aEnd);
|
||||
/**
|
||||
* Given a graph time aTime, convert it to a stream time taking into
|
||||
* account the time during which aStream is scheduled to be blocked.
|
||||
*/
|
||||
StreamTime GraphTimeToStreamTime(MediaStream* aStream, GraphTime aTime);
|
||||
enum {
|
||||
INCLUDE_TRAILING_BLOCKED_INTERVAL = 0x01
|
||||
};
|
||||
/**
|
||||
* Given a stream time aTime, convert it to a graph time taking into
|
||||
* account the time during which aStream is scheduled to be blocked.
|
||||
* aTime must be <= mStateComputedTime since blocking decisions
|
||||
* are only known up to that point.
|
||||
* If aTime is exactly at the start of a blocked interval, then the blocked
|
||||
* interval is included in the time returned if and only if
|
||||
* aFlags includes INCLUDE_TRAILING_BLOCKED_INTERVAL.
|
||||
*/
|
||||
GraphTime StreamTimeToGraphTime(MediaStream* aStream, StreamTime aTime,
|
||||
uint32_t aFlags = 0);
|
||||
/**
|
||||
* Get the current audio position of the stream's audio output.
|
||||
*/
|
||||
GraphTime GetAudioPosition(MediaStream* aStream);
|
||||
/**
|
||||
* Call NotifyHaveCurrentData on aStream's listeners.
|
||||
*/
|
||||
void NotifyHasCurrentData(MediaStream* aStream);
|
||||
/**
|
||||
* If aStream needs an audio stream but doesn't have one, create it.
|
||||
* If aStream doesn't need an audio stream but has one, destroy it.
|
||||
*/
|
||||
void CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTime,
|
||||
MediaStream* aStream);
|
||||
/**
|
||||
* Queue audio (mix of stream audio and silence for blocked intervals)
|
||||
* to the audio output stream.
|
||||
*/
|
||||
void PlayAudio(MediaStream* aStream, GraphTime aFrom, GraphTime aTo);
|
||||
/**
|
||||
* Set the correct current video frame for stream aStream.
|
||||
*/
|
||||
void PlayVideo(MediaStream* aStream);
|
||||
/**
|
||||
* No more data will be forthcoming for aStream. The stream will end
|
||||
* at the current buffer end point. The StreamBuffer's tracks must be
|
||||
* explicitly set to finished by the caller.
|
||||
*/
|
||||
void FinishStream(MediaStream* aStream);
|
||||
/**
|
||||
* Compute how much stream data we would like to buffer for aStream.
|
||||
*/
|
||||
StreamTime GetDesiredBufferEnd(MediaStream* aStream);
|
||||
/**
|
||||
* Returns true when there are no active streams.
|
||||
*/
|
||||
bool IsEmpty() { return mStreams.IsEmpty() && mPortCount == 0; }
|
||||
|
||||
// For use by control messages
|
||||
/**
|
||||
* Identify which graph update index we are currently processing.
|
||||
*/
|
||||
int64_t GetProcessingGraphUpdateIndex() { return mProcessingGraphUpdateIndex; }
|
||||
/**
|
||||
* Add aStream to the graph and initializes its graph-specific state.
|
||||
*/
|
||||
void AddStream(MediaStream* aStream);
|
||||
/**
|
||||
* Remove aStream from the graph. Ensures that pending messages about the
|
||||
* stream back to the main thread are flushed.
|
||||
*/
|
||||
void RemoveStream(MediaStream* aStream);
|
||||
/**
|
||||
* Remove aPort from the graph and release it.
|
||||
*/
|
||||
void DestroyPort(MediaInputPort* aPort);
|
||||
|
||||
// Data members
|
||||
|
||||
/**
|
||||
* Media graph thread.
|
||||
* Readonly after initialization on the main thread.
|
||||
*/
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
|
||||
// The following state is managed on the graph thread only, unless
|
||||
// mLifecycleState > LIFECYCLE_RUNNING in which case the graph thread
|
||||
// is not running and this state can be used from the main thread.
|
||||
|
||||
nsTArray<nsRefPtr<MediaStream> > mStreams;
|
||||
/**
|
||||
* The current graph time for the current iteration of the RunThread control
|
||||
* loop.
|
||||
*/
|
||||
GraphTime mCurrentTime;
|
||||
/**
|
||||
* Blocking decisions and all stream contents have been computed up to this
|
||||
* time. The next batch of updates from the main thread will be processed
|
||||
* at this time. Always >= mCurrentTime.
|
||||
*/
|
||||
GraphTime mStateComputedTime;
|
||||
/**
|
||||
* This is only used for logging.
|
||||
*/
|
||||
TimeStamp mInitialTimeStamp;
|
||||
/**
|
||||
* The real timestamp of the latest run of UpdateCurrentTime.
|
||||
*/
|
||||
TimeStamp mCurrentTimeStamp;
|
||||
/**
|
||||
* Which update batch we are currently processing.
|
||||
*/
|
||||
int64_t mProcessingGraphUpdateIndex;
|
||||
/**
|
||||
* Number of active MediaInputPorts
|
||||
*/
|
||||
int32_t mPortCount;
|
||||
|
||||
// mMonitor guards the data below.
|
||||
// MediaStreamGraph normally does its work without holding mMonitor, so it is
|
||||
// not safe to just grab mMonitor from some thread and start monkeying with
|
||||
// the graph. Instead, communicate with the graph thread using provided
|
||||
// mechanisms such as the ControlMessage queue.
|
||||
Monitor mMonitor;
|
||||
|
||||
// Data guarded by mMonitor (must always be accessed with mMonitor held,
|
||||
// regardless of the value of mLifecycleState.
|
||||
|
||||
/**
|
||||
* State to copy to main thread
|
||||
*/
|
||||
nsTArray<StreamUpdate> mStreamUpdates;
|
||||
/**
|
||||
* Runnables to run after the next update to main thread state.
|
||||
*/
|
||||
nsTArray<nsCOMPtr<nsIRunnable> > mUpdateRunnables;
|
||||
struct MessageBlock {
|
||||
int64_t mGraphUpdateIndex;
|
||||
nsTArray<nsAutoPtr<ControlMessage> > mMessages;
|
||||
};
|
||||
/**
|
||||
* A list of batches of messages to process. Each batch is processed
|
||||
* as an atomic unit.
|
||||
*/
|
||||
nsTArray<MessageBlock> mMessageQueue;
|
||||
/**
|
||||
* This enum specifies where this graph is in its lifecycle. This is used
|
||||
* to control shutdown.
|
||||
* Shutdown is tricky because it can happen in two different ways:
|
||||
* 1) Shutdown due to inactivity. RunThread() detects that it has no
|
||||
* pending messages and no streams, and exits. The next RunInStableState()
|
||||
* checks if there are new pending messages from the main thread (true only
|
||||
* if new stream creation raced with shutdown); if there are, it revives
|
||||
* RunThread(), otherwise it commits to shutting down the graph. New stream
|
||||
* creation after this point will create a new graph. An async event is
|
||||
* dispatched to Shutdown() the graph's threads and then delete the graph
|
||||
* object.
|
||||
* 2) Forced shutdown at application shutdown. A flag is set, RunThread()
|
||||
* detects the flag and exits, the next RunInStableState() detects the flag,
|
||||
* and dispatches the async event to Shutdown() the graph's threads. However
|
||||
* the graph object is not deleted. New messages for the graph are processed
|
||||
* synchronously on the main thread if necessary. When the last stream is
|
||||
* destroyed, the graph object is deleted.
|
||||
*/
|
||||
enum LifecycleState {
|
||||
// The graph thread hasn't started yet.
|
||||
LIFECYCLE_THREAD_NOT_STARTED,
|
||||
// RunThread() is running normally.
|
||||
LIFECYCLE_RUNNING,
|
||||
// In the following states, the graph thread is not running so
|
||||
// all "graph thread only" state in this class can be used safely
|
||||
// on the main thread.
|
||||
// RunThread() has exited and we're waiting for the next
|
||||
// RunInStableState(), at which point we can clean up the main-thread
|
||||
// side of the graph.
|
||||
LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP,
|
||||
// RunInStableState() posted a ShutdownRunnable, and we're waiting for it
|
||||
// to shut down the graph thread(s).
|
||||
LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN,
|
||||
// Graph threads have shut down but we're waiting for remaining streams
|
||||
// to be destroyed. Only happens during application shutdown since normally
|
||||
// we'd only shut down a graph when it has no streams.
|
||||
LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION
|
||||
};
|
||||
LifecycleState mLifecycleState;
|
||||
/**
|
||||
* This enum specifies the wait state of the graph thread.
|
||||
*/
|
||||
enum WaitState {
|
||||
// RunThread() is running normally
|
||||
WAITSTATE_RUNNING,
|
||||
// RunThread() is paused waiting for its next iteration, which will
|
||||
// happen soon
|
||||
WAITSTATE_WAITING_FOR_NEXT_ITERATION,
|
||||
// RunThread() is paused indefinitely waiting for something to change
|
||||
WAITSTATE_WAITING_INDEFINITELY,
|
||||
// Something has signaled RunThread() to wake up immediately,
|
||||
// but it hasn't done so yet
|
||||
WAITSTATE_WAKING_UP
|
||||
};
|
||||
WaitState mWaitState;
|
||||
/**
|
||||
* True when another iteration of the control loop is required.
|
||||
*/
|
||||
bool mNeedAnotherIteration;
|
||||
/**
|
||||
* True when we need to do a forced shutdown during application shutdown.
|
||||
*/
|
||||
bool mForceShutDown;
|
||||
/**
|
||||
* True when we have posted an event to the main thread to run
|
||||
* RunInStableState() and the event hasn't run yet.
|
||||
*/
|
||||
bool mPostedRunInStableStateEvent;
|
||||
|
||||
// Main thread only
|
||||
|
||||
/**
|
||||
* Messages posted by the current event loop task. These are forwarded to
|
||||
* the media graph thread during RunInStableState. We can't forward them
|
||||
* immediately because we want all messages between stable states to be
|
||||
* processed as an atomic batch.
|
||||
*/
|
||||
nsTArray<nsAutoPtr<ControlMessage> > mCurrentTaskMessageQueue;
|
||||
/**
|
||||
* True when RunInStableState has determined that mLifecycleState is >
|
||||
* LIFECYCLE_RUNNING. Since only the main thread can reset mLifecycleState to
|
||||
* LIFECYCLE_RUNNING, this can be relied on to not change unexpectedly.
|
||||
*/
|
||||
bool mDetectedNotRunning;
|
||||
/**
|
||||
* True when a stable state runner has been posted to the appshell to run
|
||||
* RunInStableState at the next stable state.
|
||||
*/
|
||||
bool mPostedRunInStableState;
|
||||
};
|
||||
|
||||
/**
|
||||
* The singleton graph instance.
|
||||
*/
|
||||
|
@ -226,16 +704,7 @@ MediaStreamGraphImpl::GraphTimeToStreamTime(MediaStream* aStream,
|
|||
t = end;
|
||||
}
|
||||
return std::max<StreamTime>(0, s);
|
||||
}
|
||||
|
||||
StreamTime
|
||||
MediaStreamGraphImpl::GraphTimeToStreamTimeOptimistic(MediaStream* aStream,
|
||||
GraphTime aTime)
|
||||
{
|
||||
GraphTime computedUpToTime = std::min(mStateComputedTime, aTime);
|
||||
StreamTime s = GraphTimeToStreamTime(aStream, computedUpToTime);
|
||||
return s + (aTime - computedUpToTime);
|
||||
}
|
||||
}
|
||||
|
||||
GraphTime
|
||||
MediaStreamGraphImpl::StreamTimeToGraphTime(MediaStream* aStream,
|
||||
|
@ -303,7 +772,15 @@ MediaStreamGraphImpl::UpdateCurrentTime()
|
|||
SecondsToMediaTime((now - mCurrentTimeStamp).ToSeconds()) + mCurrentTime;
|
||||
if (mStateComputedTime < nextCurrentTime) {
|
||||
LOG(PR_LOG_WARNING, ("Media graph global underrun detected"));
|
||||
nextCurrentTime = mStateComputedTime;
|
||||
LOG(PR_LOG_DEBUG, ("Advancing mStateComputedTime from %f to %f",
|
||||
MediaTimeToSeconds(mStateComputedTime),
|
||||
MediaTimeToSeconds(nextCurrentTime)));
|
||||
// Advance mStateComputedTime to nextCurrentTime by
|
||||
// adding blocked time to all streams starting at mStateComputedTime
|
||||
for (uint32_t i = 0; i < mStreams.Length(); ++i) {
|
||||
mStreams[i]->mBlocked.SetAtAndAfter(mStateComputedTime, true);
|
||||
}
|
||||
mStateComputedTime = nextCurrentTime;
|
||||
}
|
||||
mCurrentTimeStamp = now;
|
||||
|
||||
|
@ -876,40 +1353,6 @@ MediaStreamGraphImpl::EnsureNextIterationLocked(MonitorAutoLock& aLock)
|
|||
}
|
||||
}
|
||||
|
||||
static GraphTime
|
||||
RoundUpToAudioBlock(GraphTime aTime)
|
||||
{
|
||||
TrackRate rate = IdealAudioRate();
|
||||
int64_t ticksAtIdealRate = (aTime*rate) >> MEDIA_TIME_FRAC_BITS;
|
||||
// Round up to nearest block boundary
|
||||
int64_t blocksAtIdealRate =
|
||||
(ticksAtIdealRate + (WEBAUDIO_BLOCK_SIZE - 1)) >>
|
||||
WEBAUDIO_BLOCK_SIZE_BITS;
|
||||
// Round up to nearest MediaTime unit
|
||||
return
|
||||
((((blocksAtIdealRate + 1)*WEBAUDIO_BLOCK_SIZE) << MEDIA_TIME_FRAC_BITS)
|
||||
+ rate - 1)/rate;
|
||||
}
|
||||
|
||||
void
|
||||
MediaStreamGraphImpl::ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex,
|
||||
GraphTime aFrom,
|
||||
GraphTime aTo)
|
||||
{
|
||||
GraphTime t = aFrom;
|
||||
while (t < aTo) {
|
||||
GraphTime next = RoundUpToAudioBlock(t + 1);
|
||||
for (uint32_t i = aStreamIndex; i < mStreams.Length(); ++i) {
|
||||
ProcessedMediaStream* ps = mStreams[i]->AsProcessedStream();
|
||||
if (ps) {
|
||||
ps->ProduceOutput(t, next);
|
||||
}
|
||||
}
|
||||
t = next;
|
||||
}
|
||||
NS_ASSERTION(t == aTo, "Something went wrong with rounding to block boundaries");
|
||||
}
|
||||
|
||||
void
|
||||
MediaStreamGraphImpl::RunThread()
|
||||
{
|
||||
|
@ -941,8 +1384,9 @@ MediaStreamGraphImpl::RunThread()
|
|||
|
||||
UpdateStreamOrder();
|
||||
|
||||
int32_t writeAudioUpTo = AUDIO_TARGET_MS;
|
||||
GraphTime endBlockingDecisions =
|
||||
RoundUpToAudioBlock(mCurrentTime + MillisecondsToMediaTime(AUDIO_TARGET_MS));
|
||||
mCurrentTime + MillisecondsToMediaTime(writeAudioUpTo);
|
||||
bool ensureNextIteration = false;
|
||||
|
||||
// Grab pending stream input.
|
||||
|
@ -961,27 +1405,15 @@ MediaStreamGraphImpl::RunThread()
|
|||
// Play stream contents.
|
||||
uint32_t audioStreamsActive = 0;
|
||||
bool allBlockedForever = true;
|
||||
// True when we've done ProduceOutput for all processed streams.
|
||||
bool doneAllProducing = false;
|
||||
// Figure out what each stream wants to do
|
||||
for (uint32_t i = 0; i < mStreams.Length(); ++i) {
|
||||
MediaStream* stream = mStreams[i];
|
||||
if (!doneAllProducing && !stream->IsFinishedOnGraphThread()) {
|
||||
ProcessedMediaStream* ps = stream->AsProcessedStream();
|
||||
if (ps) {
|
||||
AudioNodeStream* n = stream->AsAudioNodeStream();
|
||||
if (n) {
|
||||
// Since an AudioNodeStream is present, go ahead and
|
||||
// produce audio block by block for all the rest of the streams.
|
||||
ProduceDataForStreamsBlockByBlock(i, prevComputedTime, mStateComputedTime);
|
||||
doneAllProducing = true;
|
||||
} else {
|
||||
ps->ProduceOutput(prevComputedTime, mStateComputedTime);
|
||||
NS_ASSERTION(stream->mBuffer.GetEnd() >=
|
||||
GraphTimeToStreamTime(stream, mStateComputedTime),
|
||||
"Stream did not produce enough data");
|
||||
}
|
||||
}
|
||||
ProcessedMediaStream* ps = stream->AsProcessedStream();
|
||||
if (ps && !ps->mFinished) {
|
||||
ps->ProduceOutput(prevComputedTime, mStateComputedTime);
|
||||
NS_ASSERTION(stream->mBuffer.GetEnd() >=
|
||||
GraphTimeToStreamTime(stream, mStateComputedTime),
|
||||
"Stream did not produce enough data");
|
||||
}
|
||||
NotifyHasCurrentData(stream);
|
||||
CreateOrDestroyAudioStreams(prevComputedTime, stream);
|
||||
|
@ -1338,18 +1770,6 @@ MediaStream::GraphTimeToStreamTime(GraphTime aTime)
|
|||
return GraphImpl()->GraphTimeToStreamTime(this, aTime);
|
||||
}
|
||||
|
||||
StreamTime
|
||||
MediaStream::GraphTimeToStreamTimeOptimistic(GraphTime aTime)
|
||||
{
|
||||
return GraphImpl()->GraphTimeToStreamTimeOptimistic(this, aTime);
|
||||
}
|
||||
|
||||
GraphTime
|
||||
MediaStream::StreamTimeToGraphTime(StreamTime aTime)
|
||||
{
|
||||
return GraphImpl()->StreamTimeToGraphTime(this, aTime, 0);
|
||||
}
|
||||
|
||||
void
|
||||
MediaStream::FinishOnGraphThread()
|
||||
{
|
||||
|
@ -1796,11 +2216,9 @@ MediaInputPort::Graph()
|
|||
return gGraph;
|
||||
}
|
||||
|
||||
already_AddRefed<MediaInputPort>
|
||||
MediaInputPort*
|
||||
ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, uint32_t aFlags)
|
||||
{
|
||||
// This method creates two references to the MediaInputPort: one for
|
||||
// the main thread, and one for the MediaStreamGraph.
|
||||
class Message : public ControlMessage {
|
||||
public:
|
||||
Message(MediaInputPort* aPort)
|
||||
|
@ -1809,14 +2227,13 @@ ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, uint32_t aFlags)
|
|||
virtual void Run()
|
||||
{
|
||||
mPort->Init();
|
||||
// The graph holds its reference implicitly
|
||||
mPort.forget();
|
||||
}
|
||||
nsRefPtr<MediaInputPort> mPort;
|
||||
MediaInputPort* mPort;
|
||||
};
|
||||
nsRefPtr<MediaInputPort> port = new MediaInputPort(aStream, this, aFlags);
|
||||
MediaInputPort* port = new MediaInputPort(aStream, this, aFlags);
|
||||
NS_ADDREF(port);
|
||||
GraphImpl()->AppendMessage(new Message(port));
|
||||
return port.forget();
|
||||
return port;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1843,7 +2260,7 @@ ProcessedMediaStream::SetAutofinish(bool aAutofinish)
|
|||
: ControlMessage(aStream), mAutofinish(aAutofinish) {}
|
||||
virtual void Run()
|
||||
{
|
||||
static_cast<ProcessedMediaStream*>(mStream)->SetAutofinishImpl(mAutofinish);
|
||||
mStream->AsProcessedStream()->SetAutofinishImpl(mAutofinish);
|
||||
}
|
||||
bool mAutofinish;
|
||||
};
|
||||
|
@ -1943,13 +2360,4 @@ MediaStreamGraph::CreateTrackUnionStream(nsDOMMediaStream* aWrapper)
|
|||
return stream;
|
||||
}
|
||||
|
||||
AudioNodeStream*
|
||||
MediaStreamGraph::CreateAudioNodeStream(AudioNodeEngine* aEngine)
|
||||
{
|
||||
AudioNodeStream* stream = new AudioNodeStream(aEngine);
|
||||
NS_ADDREF(stream);
|
||||
static_cast<MediaStreamGraphImpl*>(this)->AppendMessage(new CreateMessage(stream));
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -172,16 +172,19 @@ public:
|
|||
* This callback is invoked on the main thread when the main-thread-visible
|
||||
* state of a stream has changed.
|
||||
*
|
||||
* These methods are called with the media graph monitor held, so
|
||||
* reentry into general media graph methods is not possible.
|
||||
* These methods are called without the media graph monitor held, so
|
||||
* reentry into media graph methods is possible, although very much discouraged!
|
||||
* You should do something non-blocking and non-reentrant (e.g. dispatch an
|
||||
* event) and return. DispatchFromMainThreadAfterNextStreamStateUpdate
|
||||
* would be a good choice.
|
||||
* event) and return.
|
||||
* The listener is allowed to synchronously remove itself from the stream, but
|
||||
* not add or remove any other listeners.
|
||||
*/
|
||||
class MainThreadMediaStreamListener {
|
||||
public:
|
||||
virtual ~MainThreadMediaStreamListener() {}
|
||||
|
||||
NS_INLINE_DECL_REFCOUNTING(MainThreadMediaStreamListener)
|
||||
|
||||
virtual void NotifyMainThreadStateChanged() = 0;
|
||||
};
|
||||
|
||||
|
@ -189,9 +192,6 @@ class MediaStreamGraphImpl;
|
|||
class SourceMediaStream;
|
||||
class ProcessedMediaStream;
|
||||
class MediaInputPort;
|
||||
class AudioNodeStream;
|
||||
class AudioNodeEngine;
|
||||
struct AudioChunk;
|
||||
|
||||
/**
|
||||
* A stream of synchronized audio and video data. All (not blocked) streams
|
||||
|
@ -274,12 +274,7 @@ public:
|
|||
, mMainThreadDestroyed(false)
|
||||
{
|
||||
}
|
||||
virtual ~MediaStream()
|
||||
{
|
||||
NS_ASSERTION(mMainThreadDestroyed, "Should have been destroyed already");
|
||||
NS_ASSERTION(mMainThreadListeners.IsEmpty(),
|
||||
"All main thread listeners should have been removed");
|
||||
}
|
||||
virtual ~MediaStream() {}
|
||||
|
||||
/**
|
||||
* Returns the graph that owns this stream.
|
||||
|
@ -308,15 +303,11 @@ public:
|
|||
// Events will be dispatched by calling methods of aListener.
|
||||
void AddListener(MediaStreamListener* aListener);
|
||||
void RemoveListener(MediaStreamListener* aListener);
|
||||
// Events will be dispatched by calling methods of aListener. It is the
|
||||
// responsibility of the caller to remove aListener before it is destroyed.
|
||||
void AddMainThreadListener(MainThreadMediaStreamListener* aListener)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
|
||||
mMainThreadListeners.AppendElement(aListener);
|
||||
}
|
||||
// It's safe to call this even if aListener is not currently a listener;
|
||||
// the call will be ignored.
|
||||
void RemoveMainThreadListener(MainThreadMediaStreamListener* aListener)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
|
||||
|
@ -348,7 +339,6 @@ public:
|
|||
|
||||
virtual SourceMediaStream* AsSourceStream() { return nullptr; }
|
||||
virtual ProcessedMediaStream* AsProcessedStream() { return nullptr; }
|
||||
virtual AudioNodeStream* AsAudioNodeStream() { return nullptr; }
|
||||
|
||||
// media graph thread only
|
||||
void Init();
|
||||
|
@ -374,7 +364,7 @@ public:
|
|||
{
|
||||
mVideoOutputs.RemoveElement(aContainer);
|
||||
}
|
||||
void ChangeExplicitBlockerCountImpl(GraphTime aTime, int32_t aDelta)
|
||||
void ChangeExplicitBlockerCountImpl(StreamTime aTime, int32_t aDelta)
|
||||
{
|
||||
mExplicitBlockerCount.SetAtAndAfter(aTime, mExplicitBlockerCount.GetAt(aTime) + aDelta);
|
||||
}
|
||||
|
@ -392,24 +382,7 @@ public:
|
|||
}
|
||||
const StreamBuffer& GetStreamBuffer() { return mBuffer; }
|
||||
GraphTime GetStreamBufferStartTime() { return mBufferStartTime; }
|
||||
/**
|
||||
* Convert graph time to stream time. aTime must be <= mStateComputedTime
|
||||
* to ensure we know exactly how much time this stream will be blocked during
|
||||
* the interval.
|
||||
*/
|
||||
StreamTime GraphTimeToStreamTime(GraphTime aTime);
|
||||
/**
|
||||
* Convert graph time to stream time. aTime can be > mStateComputedTime,
|
||||
* in which case we optimistically assume the stream will not be blocked
|
||||
* after mStateComputedTime.
|
||||
*/
|
||||
StreamTime GraphTimeToStreamTimeOptimistic(GraphTime aTime);
|
||||
/**
|
||||
* Convert stream time to graph time. The result can be > mStateComputedTime,
|
||||
* in which case we did the conversion optimistically assuming the stream
|
||||
* will not be blocked after mStateComputedTime.
|
||||
*/
|
||||
GraphTime StreamTimeToGraphTime(StreamTime aTime);
|
||||
bool IsFinishedOnGraphThread() { return mFinished; }
|
||||
void FinishOnGraphThread();
|
||||
|
||||
|
@ -451,7 +424,7 @@ protected:
|
|||
// API, minus the number of times it has been explicitly unblocked.
|
||||
TimeVarying<GraphTime,uint32_t> mExplicitBlockerCount;
|
||||
nsTArray<nsRefPtr<MediaStreamListener> > mListeners;
|
||||
nsTArray<MainThreadMediaStreamListener*> mMainThreadListeners;
|
||||
nsTArray<nsRefPtr<MainThreadMediaStreamListener> > mMainThreadListeners;
|
||||
|
||||
// Precomputed blocking status (over GraphTime).
|
||||
// This is only valid between the graph's mCurrentTime and
|
||||
|
@ -770,8 +743,7 @@ public:
|
|||
* Allocates a new input port attached to source aStream.
|
||||
* This stream can be removed by calling MediaInputPort::Remove().
|
||||
*/
|
||||
already_AddRefed<MediaInputPort> AllocateInputPort(MediaStream* aStream,
|
||||
uint32_t aFlags = 0);
|
||||
MediaInputPort* AllocateInputPort(MediaStream* aStream, uint32_t aFlags = 0);
|
||||
/**
|
||||
* Force this stream into the finished state.
|
||||
*/
|
||||
|
@ -823,20 +795,12 @@ protected:
|
|||
bool mInCycle;
|
||||
};
|
||||
|
||||
// Returns ideal audio rate for processing
|
||||
inline TrackRate IdealAudioRate() { return 48000; }
|
||||
|
||||
/**
|
||||
* Initially, at least, we will have a singleton MediaStreamGraph per
|
||||
* process.
|
||||
*/
|
||||
class MediaStreamGraph {
|
||||
public:
|
||||
// We ensure that the graph current time advances in multiples of
|
||||
// IdealAudioBlockSize()/IdealAudioRate(). A stream that never blocks
|
||||
// and has a track with the ideal audio rate will produce audio in
|
||||
// multiples of the block size.
|
||||
|
||||
// Main thread only
|
||||
static MediaStreamGraph* GetInstance();
|
||||
// Control API.
|
||||
|
@ -860,11 +824,6 @@ public:
|
|||
* particular tracks of each input stream.
|
||||
*/
|
||||
ProcessedMediaStream* CreateTrackUnionStream(nsDOMMediaStream* aWrapper);
|
||||
/**
|
||||
* Create a stream that will process audio for an AudioNode.
|
||||
* Takes ownership of aEngine.
|
||||
*/
|
||||
AudioNodeStream* CreateAudioNodeStream(AudioNodeEngine* aEngine);
|
||||
/**
|
||||
* Returns the number of graph updates sent. This can be used to track
|
||||
* whether a given update has been processed by the graph thread and reflected
|
||||
|
@ -878,9 +837,9 @@ public:
|
|||
* main-thread stream state has been next updated.
|
||||
* Should only be called during MediaStreamListener callbacks.
|
||||
*/
|
||||
void DispatchToMainThreadAfterStreamStateUpdate(already_AddRefed<nsIRunnable> aRunnable)
|
||||
void DispatchToMainThreadAfterStreamStateUpdate(nsIRunnable* aRunnable)
|
||||
{
|
||||
*mPendingUpdateRunnables.AppendElement() = aRunnable;
|
||||
mPendingUpdateRunnables.AppendElement(aRunnable);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
|
|
@ -1,517 +0,0 @@
|
|||
/* -*- 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 MOZILLA_MEDIASTREAMGRAPHIMPL_H_
|
||||
#define MOZILLA_MEDIASTREAMGRAPHIMPL_H_
|
||||
|
||||
#include "MediaStreamGraph.h"
|
||||
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsIThread.h"
|
||||
#include "nsIRunnable.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gMediaStreamGraphLog;
|
||||
#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
|
||||
#else
|
||||
#define LOG(type, msg)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Assume we can run an iteration of the MediaStreamGraph loop in this much time
|
||||
* or less.
|
||||
* We try to run the control loop at this rate.
|
||||
*/
|
||||
static const int MEDIA_GRAPH_TARGET_PERIOD_MS = 10;
|
||||
|
||||
/**
|
||||
* Assume that we might miss our scheduled wakeup of the MediaStreamGraph by
|
||||
* this much.
|
||||
*/
|
||||
static const int SCHEDULE_SAFETY_MARGIN_MS = 10;
|
||||
|
||||
/**
|
||||
* Try have this much audio buffered in streams and queued to the hardware.
|
||||
* The maximum delay to the end of the next control loop
|
||||
* is 2*MEDIA_GRAPH_TARGET_PERIOD_MS + SCHEDULE_SAFETY_MARGIN_MS.
|
||||
* There is no point in buffering more audio than this in a stream at any
|
||||
* given time (until we add processing).
|
||||
* This is not optimal yet.
|
||||
*/
|
||||
static const int AUDIO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
|
||||
SCHEDULE_SAFETY_MARGIN_MS;
|
||||
|
||||
/**
|
||||
* Try have this much video buffered. Video frames are set
|
||||
* near the end of the iteration of the control loop. The maximum delay
|
||||
* to the setting of the next video frame is 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
|
||||
* SCHEDULE_SAFETY_MARGIN_MS. This is not optimal yet.
|
||||
*/
|
||||
static const int VIDEO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
|
||||
SCHEDULE_SAFETY_MARGIN_MS;
|
||||
|
||||
/**
|
||||
* A per-stream update message passed from the media graph thread to the
|
||||
* main thread.
|
||||
*/
|
||||
struct StreamUpdate {
|
||||
int64_t mGraphUpdateIndex;
|
||||
nsRefPtr<MediaStream> mStream;
|
||||
StreamTime mNextMainThreadCurrentTime;
|
||||
bool mNextMainThreadFinished;
|
||||
};
|
||||
|
||||
/**
|
||||
* This represents a message passed from the main thread to the graph thread.
|
||||
* A ControlMessage always references a particular affected stream.
|
||||
*/
|
||||
class ControlMessage {
|
||||
public:
|
||||
ControlMessage(MediaStream* aStream) : mStream(aStream)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ControlMessage);
|
||||
}
|
||||
// All these run on the graph thread
|
||||
virtual ~ControlMessage()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ControlMessage);
|
||||
}
|
||||
// Do the action of this message on the MediaStreamGraph thread. Any actions
|
||||
// affecting graph processing should take effect at mStateComputedTime.
|
||||
// All stream data for times < mStateComputedTime has already been
|
||||
// computed.
|
||||
virtual void Run() = 0;
|
||||
// When we're shutting down the application, most messages are ignored but
|
||||
// some cleanup messages should still be processed (on the main thread).
|
||||
virtual void RunDuringShutdown() {}
|
||||
MediaStream* GetStream() { return mStream; }
|
||||
|
||||
protected:
|
||||
// We do not hold a reference to mStream. The graph will be holding
|
||||
// a reference to the stream until the Destroy message is processed. The
|
||||
// last message referencing a stream is the Destroy message for that stream.
|
||||
MediaStream* mStream;
|
||||
};
|
||||
|
||||
/**
|
||||
* The implementation of a media stream graph. This class is private to this
|
||||
* file. It's not in the anonymous namespace because MediaStream needs to
|
||||
* be able to friend it.
|
||||
*
|
||||
* Currently we only have one per process.
|
||||
*/
|
||||
class MediaStreamGraphImpl : public MediaStreamGraph {
|
||||
public:
|
||||
MediaStreamGraphImpl();
|
||||
~MediaStreamGraphImpl()
|
||||
{
|
||||
NS_ASSERTION(IsEmpty(),
|
||||
"All streams should have been destroyed by messages from the main thread");
|
||||
LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p destroyed", this));
|
||||
}
|
||||
|
||||
// Main thread only.
|
||||
/**
|
||||
* This runs every time we need to sync state from the media graph thread
|
||||
* to the main thread while the main thread is not in the middle
|
||||
* of a script. It runs during a "stable state" (per HTML5) or during
|
||||
* an event posted to the main thread.
|
||||
*/
|
||||
void RunInStableState();
|
||||
/**
|
||||
* Ensure a runnable to run RunInStableState is posted to the appshell to
|
||||
* run at the next stable state (per HTML5).
|
||||
* See EnsureStableStateEventPosted.
|
||||
*/
|
||||
void EnsureRunInStableState();
|
||||
/**
|
||||
* Called to apply a StreamUpdate to its stream.
|
||||
*/
|
||||
void ApplyStreamUpdate(StreamUpdate* aUpdate);
|
||||
/**
|
||||
* Append a ControlMessage to the message queue. This queue is drained
|
||||
* during RunInStableState; the messages will run on the graph thread.
|
||||
*/
|
||||
void AppendMessage(ControlMessage* aMessage);
|
||||
/**
|
||||
* Make this MediaStreamGraph enter forced-shutdown state. This state
|
||||
* will be noticed by the media graph thread, which will shut down all streams
|
||||
* and other state controlled by the media graph thread.
|
||||
* This is called during application shutdown.
|
||||
*/
|
||||
void ForceShutDown();
|
||||
/**
|
||||
* Shutdown() this MediaStreamGraph's threads and return when they've shut down.
|
||||
*/
|
||||
void ShutdownThreads();
|
||||
|
||||
// The following methods run on the graph thread (or possibly the main thread if
|
||||
// mLifecycleState > LIFECYCLE_RUNNING)
|
||||
/**
|
||||
* Runs main control loop on the graph thread. Normally a single invocation
|
||||
* of this runs for the entire lifetime of the graph thread.
|
||||
*/
|
||||
void RunThread();
|
||||
/**
|
||||
* Call this to indicate that another iteration of the control loop is
|
||||
* required on its regular schedule. The monitor must not be held.
|
||||
*/
|
||||
void EnsureNextIteration();
|
||||
/**
|
||||
* As above, but with the monitor already held.
|
||||
*/
|
||||
void EnsureNextIterationLocked(MonitorAutoLock& aLock);
|
||||
/**
|
||||
* Call this to indicate that another iteration of the control loop is
|
||||
* required immediately. The monitor must already be held.
|
||||
*/
|
||||
void EnsureImmediateWakeUpLocked(MonitorAutoLock& aLock);
|
||||
/**
|
||||
* Ensure there is an event posted to the main thread to run RunInStableState.
|
||||
* mMonitor must be held.
|
||||
* See EnsureRunInStableState
|
||||
*/
|
||||
void EnsureStableStateEventPosted();
|
||||
/**
|
||||
* Generate messages to the main thread to update it for all state changes.
|
||||
* mMonitor must be held.
|
||||
*/
|
||||
void PrepareUpdatesToMainThreadState();
|
||||
// The following methods are the various stages of RunThread processing.
|
||||
/**
|
||||
* Compute a new current time for the graph and advance all on-graph-thread
|
||||
* state to the new current time.
|
||||
*/
|
||||
void UpdateCurrentTime();
|
||||
/**
|
||||
* Update the consumption state of aStream to reflect whether its data
|
||||
* is needed or not.
|
||||
*/
|
||||
void UpdateConsumptionState(SourceMediaStream* aStream);
|
||||
/**
|
||||
* Extract any state updates pending in aStream, and apply them.
|
||||
*/
|
||||
void ExtractPendingInput(SourceMediaStream* aStream,
|
||||
GraphTime aDesiredUpToTime,
|
||||
bool* aEnsureNextIteration);
|
||||
/**
|
||||
* Update "have enough data" flags in aStream.
|
||||
*/
|
||||
void UpdateBufferSufficiencyState(SourceMediaStream* aStream);
|
||||
/*
|
||||
* If aStream hasn't already been ordered, push it onto aStack and order
|
||||
* its children.
|
||||
*/
|
||||
void UpdateStreamOrderForStream(nsTArray<MediaStream*>* aStack,
|
||||
already_AddRefed<MediaStream> aStream);
|
||||
/**
|
||||
* Mark aStream and all its inputs (recursively) as consumed.
|
||||
*/
|
||||
static void MarkConsumed(MediaStream* aStream);
|
||||
/**
|
||||
* Sort mStreams so that every stream not in a cycle is after any streams
|
||||
* it depends on, and every stream in a cycle is marked as being in a cycle.
|
||||
* Also sets mIsConsumed on every stream.
|
||||
*/
|
||||
void UpdateStreamOrder();
|
||||
/**
|
||||
* Compute the blocking states of streams from mStateComputedTime
|
||||
* until the desired future time aEndBlockingDecisions.
|
||||
* Updates mStateComputedTime and sets MediaStream::mBlocked
|
||||
* for all streams.
|
||||
*/
|
||||
void RecomputeBlocking(GraphTime aEndBlockingDecisions);
|
||||
// The following methods are used to help RecomputeBlocking.
|
||||
/**
|
||||
* If aStream isn't already in aStreams, add it and recursively call
|
||||
* AddBlockingRelatedStreamsToSet on all the streams whose blocking
|
||||
* status could depend on or affect the state of aStream.
|
||||
*/
|
||||
void AddBlockingRelatedStreamsToSet(nsTArray<MediaStream*>* aStreams,
|
||||
MediaStream* aStream);
|
||||
/**
|
||||
* Mark a stream blocked at time aTime. If this results in decisions that need
|
||||
* to be revisited at some point in the future, *aEnd will be reduced to the
|
||||
* first time in the future to recompute those decisions.
|
||||
*/
|
||||
void MarkStreamBlocking(MediaStream* aStream);
|
||||
/**
|
||||
* Recompute blocking for the streams in aStreams for the interval starting at aTime.
|
||||
* If this results in decisions that need to be revisited at some point
|
||||
* in the future, *aEnd will be reduced to the first time in the future to
|
||||
* recompute those decisions.
|
||||
*/
|
||||
void RecomputeBlockingAt(const nsTArray<MediaStream*>& aStreams,
|
||||
GraphTime aTime, GraphTime aEndBlockingDecisions,
|
||||
GraphTime* aEnd);
|
||||
/**
|
||||
* Produce data for all streams >= aStreamIndex for the given time interval.
|
||||
* Advances block by block, each iteration producing data for all streams
|
||||
* for a single block.
|
||||
* This is needed if there are WebAudio delay nodes, whose output for a block
|
||||
* may depend on the output of any other node (including itself) for the
|
||||
* previous block. This is probably also more performant due to better memory
|
||||
* locality.
|
||||
* This is called whenever we have an AudioNodeStream in the graph.
|
||||
*/
|
||||
void ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex,
|
||||
GraphTime aFrom,
|
||||
GraphTime aTo);
|
||||
/**
|
||||
* Returns true if aStream will underrun at aTime for its own playback.
|
||||
* aEndBlockingDecisions is when we plan to stop making blocking decisions.
|
||||
* *aEnd will be reduced to the first time in the future to recompute these
|
||||
* decisions.
|
||||
*/
|
||||
bool WillUnderrun(MediaStream* aStream, GraphTime aTime,
|
||||
GraphTime aEndBlockingDecisions, GraphTime* aEnd);
|
||||
/**
|
||||
* Given a graph time aTime, convert it to a stream time taking into
|
||||
* account the time during which aStream is scheduled to be blocked.
|
||||
*/
|
||||
StreamTime GraphTimeToStreamTime(MediaStream* aStream, GraphTime aTime);
|
||||
/**
|
||||
* Given a graph time aTime, convert it to a stream time taking into
|
||||
* account the time during which aStream is scheduled to be blocked, and
|
||||
* when we don't know whether it's blocked or not, we assume it's not blocked.
|
||||
*/
|
||||
StreamTime GraphTimeToStreamTimeOptimistic(MediaStream* aStream, GraphTime aTime);
|
||||
enum {
|
||||
INCLUDE_TRAILING_BLOCKED_INTERVAL = 0x01
|
||||
};
|
||||
/**
|
||||
* Given a stream time aTime, convert it to a graph time taking into
|
||||
* account the time during which aStream is scheduled to be blocked.
|
||||
* aTime must be <= mStateComputedTime since blocking decisions
|
||||
* are only known up to that point.
|
||||
* If aTime is exactly at the start of a blocked interval, then the blocked
|
||||
* interval is included in the time returned if and only if
|
||||
* aFlags includes INCLUDE_TRAILING_BLOCKED_INTERVAL.
|
||||
*/
|
||||
GraphTime StreamTimeToGraphTime(MediaStream* aStream, StreamTime aTime,
|
||||
uint32_t aFlags = 0);
|
||||
/**
|
||||
* Get the current audio position of the stream's audio output.
|
||||
*/
|
||||
GraphTime GetAudioPosition(MediaStream* aStream);
|
||||
/**
|
||||
* Call NotifyHaveCurrentData on aStream's listeners.
|
||||
*/
|
||||
void NotifyHasCurrentData(MediaStream* aStream);
|
||||
/**
|
||||
* If aStream needs an audio stream but doesn't have one, create it.
|
||||
* If aStream doesn't need an audio stream but has one, destroy it.
|
||||
*/
|
||||
void CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTime,
|
||||
MediaStream* aStream);
|
||||
/**
|
||||
* Queue audio (mix of stream audio and silence for blocked intervals)
|
||||
* to the audio output stream.
|
||||
*/
|
||||
void PlayAudio(MediaStream* aStream, GraphTime aFrom, GraphTime aTo);
|
||||
/**
|
||||
* Set the correct current video frame for stream aStream.
|
||||
*/
|
||||
void PlayVideo(MediaStream* aStream);
|
||||
/**
|
||||
* No more data will be forthcoming for aStream. The stream will end
|
||||
* at the current buffer end point. The StreamBuffer's tracks must be
|
||||
* explicitly set to finished by the caller.
|
||||
*/
|
||||
void FinishStream(MediaStream* aStream);
|
||||
/**
|
||||
* Compute how much stream data we would like to buffer for aStream.
|
||||
*/
|
||||
StreamTime GetDesiredBufferEnd(MediaStream* aStream);
|
||||
/**
|
||||
* Returns true when there are no active streams.
|
||||
*/
|
||||
bool IsEmpty() { return mStreams.IsEmpty() && mPortCount == 0; }
|
||||
|
||||
// For use by control messages
|
||||
/**
|
||||
* Identify which graph update index we are currently processing.
|
||||
*/
|
||||
int64_t GetProcessingGraphUpdateIndex() { return mProcessingGraphUpdateIndex; }
|
||||
/**
|
||||
* Add aStream to the graph and initializes its graph-specific state.
|
||||
*/
|
||||
void AddStream(MediaStream* aStream);
|
||||
/**
|
||||
* Remove aStream from the graph. Ensures that pending messages about the
|
||||
* stream back to the main thread are flushed.
|
||||
*/
|
||||
void RemoveStream(MediaStream* aStream);
|
||||
/**
|
||||
* Remove aPort from the graph and release it.
|
||||
*/
|
||||
void DestroyPort(MediaInputPort* aPort);
|
||||
|
||||
// Data members
|
||||
|
||||
/**
|
||||
* Media graph thread.
|
||||
* Readonly after initialization on the main thread.
|
||||
*/
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
|
||||
// The following state is managed on the graph thread only, unless
|
||||
// mLifecycleState > LIFECYCLE_RUNNING in which case the graph thread
|
||||
// is not running and this state can be used from the main thread.
|
||||
|
||||
nsTArray<nsRefPtr<MediaStream> > mStreams;
|
||||
/**
|
||||
* The current graph time for the current iteration of the RunThread control
|
||||
* loop.
|
||||
*/
|
||||
GraphTime mCurrentTime;
|
||||
/**
|
||||
* Blocking decisions and all stream contents have been computed up to this
|
||||
* time. The next batch of updates from the main thread will be processed
|
||||
* at this time. Always >= mCurrentTime.
|
||||
*/
|
||||
GraphTime mStateComputedTime;
|
||||
/**
|
||||
* This is only used for logging.
|
||||
*/
|
||||
TimeStamp mInitialTimeStamp;
|
||||
/**
|
||||
* The real timestamp of the latest run of UpdateCurrentTime.
|
||||
*/
|
||||
TimeStamp mCurrentTimeStamp;
|
||||
/**
|
||||
* Which update batch we are currently processing.
|
||||
*/
|
||||
int64_t mProcessingGraphUpdateIndex;
|
||||
/**
|
||||
* Number of active MediaInputPorts
|
||||
*/
|
||||
int32_t mPortCount;
|
||||
|
||||
// mMonitor guards the data below.
|
||||
// MediaStreamGraph normally does its work without holding mMonitor, so it is
|
||||
// not safe to just grab mMonitor from some thread and start monkeying with
|
||||
// the graph. Instead, communicate with the graph thread using provided
|
||||
// mechanisms such as the ControlMessage queue.
|
||||
Monitor mMonitor;
|
||||
|
||||
// Data guarded by mMonitor (must always be accessed with mMonitor held,
|
||||
// regardless of the value of mLifecycleState.
|
||||
|
||||
/**
|
||||
* State to copy to main thread
|
||||
*/
|
||||
nsTArray<StreamUpdate> mStreamUpdates;
|
||||
/**
|
||||
* Runnables to run after the next update to main thread state.
|
||||
*/
|
||||
nsTArray<nsCOMPtr<nsIRunnable> > mUpdateRunnables;
|
||||
struct MessageBlock {
|
||||
int64_t mGraphUpdateIndex;
|
||||
nsTArray<nsAutoPtr<ControlMessage> > mMessages;
|
||||
};
|
||||
/**
|
||||
* A list of batches of messages to process. Each batch is processed
|
||||
* as an atomic unit.
|
||||
*/
|
||||
nsTArray<MessageBlock> mMessageQueue;
|
||||
/**
|
||||
* This enum specifies where this graph is in its lifecycle. This is used
|
||||
* to control shutdown.
|
||||
* Shutdown is tricky because it can happen in two different ways:
|
||||
* 1) Shutdown due to inactivity. RunThread() detects that it has no
|
||||
* pending messages and no streams, and exits. The next RunInStableState()
|
||||
* checks if there are new pending messages from the main thread (true only
|
||||
* if new stream creation raced with shutdown); if there are, it revives
|
||||
* RunThread(), otherwise it commits to shutting down the graph. New stream
|
||||
* creation after this point will create a new graph. An async event is
|
||||
* dispatched to Shutdown() the graph's threads and then delete the graph
|
||||
* object.
|
||||
* 2) Forced shutdown at application shutdown. A flag is set, RunThread()
|
||||
* detects the flag and exits, the next RunInStableState() detects the flag,
|
||||
* and dispatches the async event to Shutdown() the graph's threads. However
|
||||
* the graph object is not deleted. New messages for the graph are processed
|
||||
* synchronously on the main thread if necessary. When the last stream is
|
||||
* destroyed, the graph object is deleted.
|
||||
*/
|
||||
enum LifecycleState {
|
||||
// The graph thread hasn't started yet.
|
||||
LIFECYCLE_THREAD_NOT_STARTED,
|
||||
// RunThread() is running normally.
|
||||
LIFECYCLE_RUNNING,
|
||||
// In the following states, the graph thread is not running so
|
||||
// all "graph thread only" state in this class can be used safely
|
||||
// on the main thread.
|
||||
// RunThread() has exited and we're waiting for the next
|
||||
// RunInStableState(), at which point we can clean up the main-thread
|
||||
// side of the graph.
|
||||
LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP,
|
||||
// RunInStableState() posted a ShutdownRunnable, and we're waiting for it
|
||||
// to shut down the graph thread(s).
|
||||
LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN,
|
||||
// Graph threads have shut down but we're waiting for remaining streams
|
||||
// to be destroyed. Only happens during application shutdown since normally
|
||||
// we'd only shut down a graph when it has no streams.
|
||||
LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION
|
||||
};
|
||||
LifecycleState mLifecycleState;
|
||||
/**
|
||||
* This enum specifies the wait state of the graph thread.
|
||||
*/
|
||||
enum WaitState {
|
||||
// RunThread() is running normally
|
||||
WAITSTATE_RUNNING,
|
||||
// RunThread() is paused waiting for its next iteration, which will
|
||||
// happen soon
|
||||
WAITSTATE_WAITING_FOR_NEXT_ITERATION,
|
||||
// RunThread() is paused indefinitely waiting for something to change
|
||||
WAITSTATE_WAITING_INDEFINITELY,
|
||||
// Something has signaled RunThread() to wake up immediately,
|
||||
// but it hasn't done so yet
|
||||
WAITSTATE_WAKING_UP
|
||||
};
|
||||
WaitState mWaitState;
|
||||
/**
|
||||
* True when another iteration of the control loop is required.
|
||||
*/
|
||||
bool mNeedAnotherIteration;
|
||||
/**
|
||||
* True when we need to do a forced shutdown during application shutdown.
|
||||
*/
|
||||
bool mForceShutDown;
|
||||
/**
|
||||
* True when we have posted an event to the main thread to run
|
||||
* RunInStableState() and the event hasn't run yet.
|
||||
*/
|
||||
bool mPostedRunInStableStateEvent;
|
||||
|
||||
// Main thread only
|
||||
|
||||
/**
|
||||
* Messages posted by the current event loop task. These are forwarded to
|
||||
* the media graph thread during RunInStableState. We can't forward them
|
||||
* immediately because we want all messages between stable states to be
|
||||
* processed as an atomic batch.
|
||||
*/
|
||||
nsTArray<nsAutoPtr<ControlMessage> > mCurrentTaskMessageQueue;
|
||||
/**
|
||||
* True when RunInStableState has determined that mLifecycleState is >
|
||||
* LIFECYCLE_RUNNING. Since only the main thread can reset mLifecycleState to
|
||||
* LIFECYCLE_RUNNING, this can be relied on to not change unexpectedly.
|
||||
*/
|
||||
bool mDetectedNotRunning;
|
||||
/**
|
||||
* True when a stable state runner has been posted to the appshell to run
|
||||
* RunInStableState at the next stable state.
|
||||
*/
|
||||
bool mPostedRunInStableState;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MEDIASTREAMGRAPHIMPL_H_ */
|
|
@ -35,18 +35,18 @@ const TrackRate TRACK_RATE_MAX = 1 << MEDIA_TIME_FRAC_BITS;
|
|||
typedef int32_t TrackID;
|
||||
const TrackID TRACK_NONE = 0;
|
||||
|
||||
inline TrackTicks TimeToTicksRoundUp(TrackRate aRate, StreamTime aTime)
|
||||
inline TrackTicks TimeToTicksRoundUp(TrackRate aRate, StreamTime aMicroseconds)
|
||||
{
|
||||
NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate");
|
||||
NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time");
|
||||
return (aTime*aRate + (1 << MEDIA_TIME_FRAC_BITS) - 1) >> MEDIA_TIME_FRAC_BITS;
|
||||
NS_ASSERTION(0 <= aMicroseconds && aMicroseconds <= STREAM_TIME_MAX, "Bad microseconds");
|
||||
return (aMicroseconds*aRate + (1 << MEDIA_TIME_FRAC_BITS) - 1) >> MEDIA_TIME_FRAC_BITS;
|
||||
}
|
||||
|
||||
inline TrackTicks TimeToTicksRoundDown(TrackRate aRate, StreamTime aTime)
|
||||
inline TrackTicks TimeToTicksRoundDown(TrackRate aRate, StreamTime aMicroseconds)
|
||||
{
|
||||
NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate");
|
||||
NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time");
|
||||
return (aTime*aRate) >> MEDIA_TIME_FRAC_BITS;
|
||||
NS_ASSERTION(0 <= aMicroseconds && aMicroseconds <= STREAM_TIME_MAX, "Bad microseconds");
|
||||
return (aMicroseconds*aRate) >> MEDIA_TIME_FRAC_BITS;
|
||||
}
|
||||
|
||||
inline StreamTime TicksToTimeRoundUp(TrackRate aRate, TrackTicks aTicks)
|
||||
|
|
|
@ -10,14 +10,13 @@
|
|||
#include "AudioContext.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "AudioSegment.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioBuffer)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mJSChannels)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannels)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
tmp->ClearJSChannels();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
@ -29,8 +28,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AudioBuffer)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
for (uint32_t i = 0; i < tmp->mJSChannels.Length(); ++i) {
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSChannels[i])
|
||||
for (uint32_t i = 0; i < tmp->mChannels.Length(); ++i) {
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mChannels[i])
|
||||
}
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
|
@ -42,7 +41,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioBuffer)
|
|||
NS_INTERFACE_MAP_END
|
||||
|
||||
AudioBuffer::AudioBuffer(AudioContext* aContext, uint32_t aLength,
|
||||
float aSampleRate)
|
||||
uint32_t aSampleRate)
|
||||
: mContext(aContext),
|
||||
mLength(aLength),
|
||||
mSampleRate(aSampleRate)
|
||||
|
@ -60,14 +59,14 @@ AudioBuffer::~AudioBuffer()
|
|||
void
|
||||
AudioBuffer::ClearJSChannels()
|
||||
{
|
||||
mJSChannels.Clear();
|
||||
mChannels.Clear();
|
||||
NS_DROP_JS_OBJECTS(this, AudioBuffer);
|
||||
}
|
||||
|
||||
bool
|
||||
AudioBuffer::InitializeBuffers(uint32_t aNumberOfChannels, JSContext* aJSContext)
|
||||
{
|
||||
if (!mJSChannels.SetCapacity(aNumberOfChannels)) {
|
||||
if (!mChannels.SetCapacity(aNumberOfChannels)) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < aNumberOfChannels; ++i) {
|
||||
|
@ -75,7 +74,7 @@ AudioBuffer::InitializeBuffers(uint32_t aNumberOfChannels, JSContext* aJSContext
|
|||
if (!array) {
|
||||
return false;
|
||||
}
|
||||
mJSChannels.AppendElement(array);
|
||||
mChannels.AppendElement(array);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -88,37 +87,15 @@ AudioBuffer::WrapObject(JSContext* aCx, JSObject* aScope,
|
|||
return AudioBufferBinding::Wrap(aCx, aScope, this, aTriedToWrap);
|
||||
}
|
||||
|
||||
void
|
||||
AudioBuffer::RestoreJSChannelData(JSContext* aJSContext)
|
||||
{
|
||||
if (mSharedChannels) {
|
||||
for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
|
||||
const float* data = mSharedChannels->GetData(i);
|
||||
// The following code first zeroes the array and then copies our data
|
||||
// into it. We could avoid this with additional JS APIs to construct
|
||||
// an array (or ArrayBuffer) containing initial data.
|
||||
JSObject* array = JS_NewFloat32Array(aJSContext, mLength);
|
||||
memcpy(JS_GetFloat32ArrayData(array), data, sizeof(float)*mLength);
|
||||
mJSChannels[i] = array;
|
||||
}
|
||||
|
||||
mSharedChannels = nullptr;
|
||||
mResampledChannels = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JSObject*
|
||||
AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel,
|
||||
ErrorResult& aRv)
|
||||
ErrorResult& aRv) const
|
||||
{
|
||||
if (aChannel >= NumberOfChannels()) {
|
||||
if (aChannel >= mChannels.Length()) {
|
||||
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RestoreJSChannelData(aJSContext);
|
||||
|
||||
return mJSChannels[aChannel];
|
||||
return GetChannelData(aChannel);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -126,83 +103,13 @@ AudioBuffer::SetChannelDataFromArrayBufferContents(JSContext* aJSContext,
|
|||
uint32_t aChannel,
|
||||
void* aContents)
|
||||
{
|
||||
RestoreJSChannelData(aJSContext);
|
||||
|
||||
MOZ_ASSERT(aChannel < NumberOfChannels());
|
||||
MOZ_ASSERT(aChannel < mChannels.Length());
|
||||
JSObject* arrayBuffer = JS_NewArrayBufferWithContents(aJSContext, aContents);
|
||||
mJSChannels[aChannel] = JS_NewFloat32ArrayWithBuffer(aJSContext, arrayBuffer,
|
||||
0, -1);
|
||||
MOZ_ASSERT(mLength == JS_GetTypedArrayLength(mJSChannels[aChannel]));
|
||||
mChannels[aChannel] = JS_NewFloat32ArrayWithBuffer(aJSContext, arrayBuffer,
|
||||
0, -1);
|
||||
MOZ_ASSERT(mLength == JS_GetTypedArrayLength(mChannels[aChannel]));
|
||||
}
|
||||
|
||||
static already_AddRefed<ThreadSharedFloatArrayBufferList>
|
||||
StealJSArrayDataIntoThreadSharedFloatArrayBufferList(JSContext* aJSContext,
|
||||
const nsTArray<JSObject*>& aJSArrays)
|
||||
{
|
||||
nsRefPtr<ThreadSharedFloatArrayBufferList> result =
|
||||
new ThreadSharedFloatArrayBufferList(aJSArrays.Length());
|
||||
for (uint32_t i = 0; i < aJSArrays.Length(); ++i) {
|
||||
JSObject* arrayBuffer = JS_GetArrayBufferViewBuffer(aJSArrays[i]);
|
||||
void* dataToFree = nullptr;
|
||||
uint8_t* stolenData = nullptr;
|
||||
if (arrayBuffer &&
|
||||
JS_StealArrayBufferContents(aJSContext, arrayBuffer, &dataToFree,
|
||||
&stolenData)) {
|
||||
result->SetData(i, dataToFree, reinterpret_cast<float*>(stolenData));
|
||||
} else {
|
||||
result->Clear();
|
||||
return result.forget();
|
||||
}
|
||||
}
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
ThreadSharedFloatArrayBufferList*
|
||||
AudioBuffer::GetThreadSharedChannelsForRate(JSContext* aJSContext, uint32_t aRate,
|
||||
uint32_t* aLength)
|
||||
{
|
||||
if (mResampledChannels && mResampledChannelsRate == aRate) {
|
||||
// return cached data
|
||||
*aLength = mResampledChannelsLength;
|
||||
return mResampledChannels;
|
||||
}
|
||||
|
||||
if (!mSharedChannels) {
|
||||
// Steal JS data
|
||||
mSharedChannels =
|
||||
StealJSArrayDataIntoThreadSharedFloatArrayBufferList(aJSContext, mJSChannels);
|
||||
}
|
||||
|
||||
if (mSampleRate == aRate) {
|
||||
*aLength = mLength;
|
||||
return mSharedChannels;
|
||||
}
|
||||
|
||||
mResampledChannels = new ThreadSharedFloatArrayBufferList(NumberOfChannels());
|
||||
|
||||
double newLengthD = ceil(Duration()*aRate);
|
||||
uint32_t newLength = uint32_t(newLengthD);
|
||||
*aLength = newLength;
|
||||
double size = sizeof(float)*NumberOfChannels()*newLengthD;
|
||||
if (size != uint32_t(size)) {
|
||||
return mResampledChannels;
|
||||
}
|
||||
float* outputData = static_cast<float*>(malloc(uint32_t(size)));
|
||||
if (!outputData) {
|
||||
return mResampledChannels;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < NumberOfChannels(); ++i) {
|
||||
NS_ERROR("Resampling not supported yet");
|
||||
// const float* inputData = mSharedChannels->GetData(i);
|
||||
// Resample(inputData, mLength, mSampleRate, outputData, newLength, aRate);
|
||||
mResampledChannels->SetData(i, i == 0 ? outputData : nullptr, outputData);
|
||||
outputData += newLength;
|
||||
}
|
||||
mResampledChannelsRate = aRate;
|
||||
mResampledChannelsLength = newLength;
|
||||
return mResampledChannels;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
#include "nsAutoPtr.h"
|
||||
#include "nsTArray.h"
|
||||
#include "AudioContext.h"
|
||||
#include "AudioSegment.h"
|
||||
#include "AudioNodeEngine.h"
|
||||
|
||||
struct JSContext;
|
||||
class JSObject;
|
||||
|
@ -26,18 +24,13 @@ class ErrorResult;
|
|||
|
||||
namespace dom {
|
||||
|
||||
/**
|
||||
* An AudioBuffer keeps its data either in the mJSChannels objects, which
|
||||
* are Float32Arrays, or in mSharedChannels if the mJSChannels objects have
|
||||
* been neutered.
|
||||
*/
|
||||
class AudioBuffer MOZ_FINAL : public nsISupports,
|
||||
public nsWrapperCache,
|
||||
public EnableWebAudioCheck
|
||||
{
|
||||
public:
|
||||
AudioBuffer(AudioContext* aContext, uint32_t aLength,
|
||||
float aSampleRate);
|
||||
uint32_t aSampleRate);
|
||||
~AudioBuffer();
|
||||
|
||||
// This function needs to be called in order to allocate
|
||||
|
@ -61,7 +54,7 @@ public:
|
|||
return mSampleRate;
|
||||
}
|
||||
|
||||
int32_t Length() const
|
||||
uint32_t Length() const
|
||||
{
|
||||
return mLength;
|
||||
}
|
||||
|
@ -73,54 +66,28 @@ public:
|
|||
|
||||
uint32_t NumberOfChannels() const
|
||||
{
|
||||
return mJSChannels.Length();
|
||||
return mChannels.Length();
|
||||
}
|
||||
|
||||
/**
|
||||
* If mSharedChannels is non-null, copies its contents to
|
||||
* new Float32Arrays in mJSChannels. Returns a Float32Array.
|
||||
*/
|
||||
JSObject* GetChannelData(JSContext* aJSContext, uint32_t aChannel,
|
||||
ErrorResult& aRv);
|
||||
|
||||
ErrorResult& aRv) const;
|
||||
JSObject* GetChannelData(uint32_t aChannel) const {
|
||||
// Doesn't perform bounds checking
|
||||
MOZ_ASSERT(aChannel < mJSChannels.Length());
|
||||
return mJSChannels[aChannel];
|
||||
MOZ_ASSERT(aChannel < mChannels.Length());
|
||||
return mChannels[aChannel];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ThreadSharedFloatArrayBufferList containing the sample data
|
||||
* at aRate. Sets *aLength to the number of samples per channel.
|
||||
*/
|
||||
ThreadSharedFloatArrayBufferList* GetThreadSharedChannelsForRate(JSContext* aContext,
|
||||
uint32_t aRate,
|
||||
uint32_t* aLength);
|
||||
|
||||
// aContents should either come from JS_AllocateArrayBufferContents or
|
||||
// JS_StealArrayBufferContents.
|
||||
void SetChannelDataFromArrayBufferContents(JSContext* aJSContext,
|
||||
uint32_t aChannel,
|
||||
void* aContents);
|
||||
|
||||
protected:
|
||||
void RestoreJSChannelData(JSContext* aJSContext);
|
||||
private:
|
||||
void ClearJSChannels();
|
||||
|
||||
nsRefPtr<AudioContext> mContext;
|
||||
// Float32Arrays
|
||||
nsAutoTArray<JSObject*,2> mJSChannels;
|
||||
|
||||
// mSharedChannels aggregates the data from mJSChannels. This is non-null
|
||||
// if and only if the mJSChannels are neutered.
|
||||
nsRefPtr<ThreadSharedFloatArrayBufferList> mSharedChannels;
|
||||
|
||||
// One-element cache of resampled data. Can be non-null only if mSharedChannels
|
||||
// is non-null.
|
||||
nsRefPtr<ThreadSharedFloatArrayBufferList> mResampledChannels;
|
||||
uint32_t mResampledChannelsRate;
|
||||
uint32_t mResampledChannelsLength;
|
||||
|
||||
FallibleTArray<JSObject*> mChannels;
|
||||
uint32_t mLength;
|
||||
float mSampleRate;
|
||||
};
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
|
||||
#include "AudioBufferSourceNode.h"
|
||||
#include "mozilla/dom/AudioBufferSourceNodeBinding.h"
|
||||
#include "nsMathUtils.h"
|
||||
#include "AudioNodeEngine.h"
|
||||
#include "AudioNodeStream.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
@ -21,122 +18,9 @@ NS_INTERFACE_MAP_END_INHERITING(AudioSourceNode)
|
|||
NS_IMPL_ADDREF_INHERITED(AudioBufferSourceNode, AudioSourceNode)
|
||||
NS_IMPL_RELEASE_INHERITED(AudioBufferSourceNode, AudioSourceNode)
|
||||
|
||||
class AudioBufferSourceNodeEngine : public AudioNodeEngine
|
||||
{
|
||||
public:
|
||||
AudioBufferSourceNodeEngine() :
|
||||
mStart(0), mStop(TRACK_TICKS_MAX), mOffset(0), mDuration(0) {}
|
||||
|
||||
// START, OFFSET and DURATION are always set by start() (along with setting
|
||||
// mBuffer to something non-null).
|
||||
// STOP is set by stop().
|
||||
enum Parameters {
|
||||
START,
|
||||
STOP,
|
||||
OFFSET,
|
||||
DURATION
|
||||
};
|
||||
virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam)
|
||||
{
|
||||
switch (aIndex) {
|
||||
case START: mStart = aParam; break;
|
||||
case STOP: mStop = aParam; break;
|
||||
default:
|
||||
NS_ERROR("Bad AudioBufferSourceNodeEngine StreamTimeParameter");
|
||||
}
|
||||
}
|
||||
virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
|
||||
{
|
||||
switch (aIndex) {
|
||||
case OFFSET: mOffset = aParam; break;
|
||||
case DURATION: mDuration = aParam; break;
|
||||
default:
|
||||
NS_ERROR("Bad AudioBufferSourceNodeEngine Int32Parameter");
|
||||
}
|
||||
}
|
||||
virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
|
||||
{
|
||||
mBuffer = aBuffer;
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
{
|
||||
if (!mBuffer)
|
||||
return;
|
||||
TrackTicks currentPosition = aStream->GetCurrentPosition();
|
||||
if (currentPosition + WEBAUDIO_BLOCK_SIZE <= mStart) {
|
||||
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
return;
|
||||
}
|
||||
TrackTicks endTime = std::min(mStart + mDuration, mStop);
|
||||
// Don't set *aFinished just because we passed mStop. Maybe someone
|
||||
// will call stop() again with a different value.
|
||||
if (currentPosition + WEBAUDIO_BLOCK_SIZE >= mStart + mDuration) {
|
||||
*aFinished = true;
|
||||
}
|
||||
if (currentPosition >= endTime || mStart >= endTime) {
|
||||
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t channels = mBuffer->GetChannels();
|
||||
if (!channels) {
|
||||
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPosition >= mStart &&
|
||||
currentPosition + WEBAUDIO_BLOCK_SIZE <= endTime) {
|
||||
// Data is entirely within the buffer. Avoid copying it.
|
||||
aOutput->mDuration = WEBAUDIO_BLOCK_SIZE;
|
||||
aOutput->mBuffer = mBuffer;
|
||||
aOutput->mChannelData.SetLength(channels);
|
||||
for (uint32_t i = 0; i < channels; ++i) {
|
||||
aOutput->mChannelData[i] =
|
||||
mBuffer->GetData(i) + uintptr_t(currentPosition - mStart + mOffset);
|
||||
}
|
||||
aOutput->mVolume = 1.0f;
|
||||
aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32;
|
||||
return;
|
||||
}
|
||||
|
||||
AllocateAudioBlock(channels, aOutput);
|
||||
TrackTicks start = std::max(currentPosition, mStart);
|
||||
TrackTicks end = std::min(currentPosition + WEBAUDIO_BLOCK_SIZE, endTime);
|
||||
WriteZeroesToAudioBlock(aOutput, 0, uint32_t(start - currentPosition));
|
||||
for (uint32_t i = 0; i < channels; ++i) {
|
||||
memcpy(static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i])) +
|
||||
uint32_t(start - currentPosition),
|
||||
mBuffer->GetData(i) +
|
||||
uintptr_t(start - mStart + mOffset),
|
||||
uint32_t(end - start));
|
||||
}
|
||||
uint32_t endOffset = uint32_t(end - currentPosition);
|
||||
WriteZeroesToAudioBlock(aOutput, endOffset, WEBAUDIO_BLOCK_SIZE - endOffset);
|
||||
}
|
||||
|
||||
TrackTicks mStart;
|
||||
TrackTicks mStop;
|
||||
nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
|
||||
int32_t mOffset;
|
||||
int32_t mDuration;
|
||||
};
|
||||
|
||||
AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* aContext)
|
||||
: AudioSourceNode(aContext)
|
||||
, mStartCalled(false)
|
||||
{
|
||||
SetProduceOwnOutput(true);
|
||||
mStream = aContext->Graph()->CreateAudioNodeStream(new AudioBufferSourceNodeEngine());
|
||||
mStream->AddMainThreadListener(this);
|
||||
}
|
||||
|
||||
AudioBufferSourceNode::~AudioBufferSourceNode()
|
||||
{
|
||||
DestroyMediaStream();
|
||||
}
|
||||
|
||||
JSObject*
|
||||
|
@ -146,75 +30,11 @@ AudioBufferSourceNode::WrapObject(JSContext* aCx, JSObject* aScope)
|
|||
}
|
||||
|
||||
void
|
||||
AudioBufferSourceNode::Start(JSContext* aCx, double aWhen, double aOffset,
|
||||
const Optional<double>& aDuration, ErrorResult& aRv)
|
||||
AudioBufferSourceNode::SetBuffer(AudioBuffer* aBuffer)
|
||||
{
|
||||
if (mStartCalled) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
mStartCalled = true;
|
||||
|
||||
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
|
||||
if (!mBuffer || !ns) {
|
||||
// Nothing to play, or we're already dead for some reason
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t rate = Context()->GetRate();
|
||||
uint32_t lengthSamples;
|
||||
nsRefPtr<ThreadSharedFloatArrayBufferList> data =
|
||||
mBuffer->GetThreadSharedChannelsForRate(aCx, rate, &lengthSamples);
|
||||
double length = double(lengthSamples)/rate;
|
||||
double offset = std::max(0.0, aOffset);
|
||||
double endOffset = aDuration.WasPassed() ?
|
||||
std::min(aOffset + aDuration.Value(), length) : length;
|
||||
if (offset >= endOffset) {
|
||||
return;
|
||||
}
|
||||
|
||||
ns->SetBuffer(data.forget());
|
||||
// Don't set parameter unnecessarily
|
||||
if (aWhen > 0.0) {
|
||||
ns->SetStreamTimeParameter(AudioBufferSourceNodeEngine::START,
|
||||
Context()->DestinationStream(),
|
||||
aWhen);
|
||||
}
|
||||
int32_t offsetTicks = NS_lround(offset*rate);
|
||||
// Don't set parameter unnecessarily
|
||||
if (offsetTicks > 0) {
|
||||
ns->SetInt32Parameter(AudioBufferSourceNodeEngine::OFFSET, offsetTicks);
|
||||
}
|
||||
ns->SetInt32Parameter(AudioBufferSourceNodeEngine::DURATION,
|
||||
NS_lround(endOffset*rate) - offsetTicks);
|
||||
}
|
||||
|
||||
void
|
||||
AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv)
|
||||
{
|
||||
if (!mStartCalled) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
|
||||
if (!ns) {
|
||||
// We've already stopped and had our stream shut down
|
||||
return;
|
||||
}
|
||||
|
||||
ns->SetStreamTimeParameter(AudioBufferSourceNodeEngine::STOP,
|
||||
Context()->DestinationStream(),
|
||||
std::max(0.0, aWhen));
|
||||
}
|
||||
|
||||
void
|
||||
AudioBufferSourceNode::NotifyMainThreadStateChanged()
|
||||
{
|
||||
if (mStream->IsFinished()) {
|
||||
SetProduceOwnOutput(false);
|
||||
}
|
||||
mBuffer = aBuffer;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,53 +9,31 @@
|
|||
|
||||
#include "AudioSourceNode.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class AudioBufferSourceNode : public AudioSourceNode,
|
||||
public MainThreadMediaStreamListener
|
||||
class AudioBufferSourceNode : public AudioSourceNode
|
||||
{
|
||||
public:
|
||||
explicit AudioBufferSourceNode(AudioContext* aContext);
|
||||
virtual ~AudioBufferSourceNode();
|
||||
|
||||
virtual void DestroyMediaStream() MOZ_OVERRIDE
|
||||
{
|
||||
if (mStream) {
|
||||
mStream->RemoveMainThreadListener(this);
|
||||
}
|
||||
AudioSourceNode::DestroyMediaStream();
|
||||
}
|
||||
virtual bool SupportsMediaStreams() const MOZ_OVERRIDE
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioBufferSourceNode, AudioSourceNode)
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope);
|
||||
|
||||
void Start(JSContext* aCx, double aWhen, double aOffset,
|
||||
const Optional<double>& aDuration, ErrorResult& aRv);
|
||||
void Stop(double aWhen, ErrorResult& aRv);
|
||||
void Start(double) { /* no-op for now */ }
|
||||
void Stop(double) { /* no-op for now */ }
|
||||
|
||||
AudioBuffer* GetBuffer() const
|
||||
{
|
||||
return mBuffer;
|
||||
}
|
||||
void SetBuffer(AudioBuffer* aBuffer)
|
||||
{
|
||||
mBuffer = aBuffer;
|
||||
}
|
||||
|
||||
virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
|
||||
void SetBuffer(AudioBuffer* aBuffer);
|
||||
|
||||
private:
|
||||
nsRefPtr<AudioBuffer> mBuffer;
|
||||
bool mStartCalled;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "nsContentUtils.h"
|
||||
#include "nsIDOMWindow.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "AudioDestinationNode.h"
|
||||
#include "AudioBufferSourceNode.h"
|
||||
#include "AudioBuffer.h"
|
||||
|
@ -29,14 +28,11 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_3(AudioContext,
|
|||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioContext, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioContext, Release)
|
||||
|
||||
static uint8_t gWebAudioOutputKey;
|
||||
|
||||
AudioContext::AudioContext(nsIDOMWindow* aWindow)
|
||||
: mWindow(aWindow)
|
||||
, mDestination(new AudioDestinationNode(this, MediaStreamGraph::GetInstance()))
|
||||
, mDestination(new AudioDestinationNode(this))
|
||||
, mSampleRate(44100) // hard-code for now
|
||||
{
|
||||
// Actually play audio
|
||||
mDestination->Stream()->AddAudioOutput(&gWebAudioOutputKey);
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
|
@ -79,18 +75,11 @@ AudioContext::CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
|
|||
uint32_t aLength, float aSampleRate,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (aLength > INT32_MAX) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<AudioBuffer> buffer =
|
||||
new AudioBuffer(this, int32_t(aLength), aSampleRate);
|
||||
nsRefPtr<AudioBuffer> buffer = new AudioBuffer(this, aLength, aSampleRate);
|
||||
if (!buffer->InitializeBuffers(aNumberOfChannels, aJSContext)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return buffer.forget();
|
||||
}
|
||||
|
||||
|
@ -175,17 +164,6 @@ AudioContext::RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob)
|
|||
mDecodeJobs.RemoveElement(aDecodeJob);
|
||||
}
|
||||
|
||||
MediaStreamGraph*
|
||||
AudioContext::Graph() const
|
||||
{
|
||||
return Destination()->Stream()->Graph();
|
||||
}
|
||||
}
|
||||
|
||||
MediaStream*
|
||||
AudioContext::DestinationStream() const
|
||||
{
|
||||
return Destination()->Stream();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,6 @@
|
|||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/AudioContextBinding.h"
|
||||
#include "MediaBufferDecoder.h"
|
||||
#include "StreamBuffer.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "nsIDOMWindow.h"
|
||||
|
||||
struct JSContext;
|
||||
class JSObject;
|
||||
|
@ -47,9 +44,10 @@ class AudioContext MOZ_FINAL : public nsWrapperCache,
|
|||
public EnableWebAudioCheck
|
||||
{
|
||||
explicit AudioContext(nsIDOMWindow* aParentWindow);
|
||||
~AudioContext();
|
||||
|
||||
public:
|
||||
virtual ~AudioContext();
|
||||
|
||||
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioContext)
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AudioContext)
|
||||
|
||||
|
@ -76,7 +74,7 @@ public:
|
|||
|
||||
float SampleRate() const
|
||||
{
|
||||
return float(IdealAudioRate());
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
AudioListener* Listener();
|
||||
|
@ -107,11 +105,6 @@ public:
|
|||
DecodeSuccessCallback& aSuccessCallback,
|
||||
const Optional<OwningNonNull<DecodeErrorCallback> >& aFailureCallback);
|
||||
|
||||
uint32_t GetRate() const { return IdealAudioRate(); }
|
||||
|
||||
MediaStreamGraph* Graph() const;
|
||||
MediaStream* DestinationStream() const;
|
||||
|
||||
private:
|
||||
void RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob);
|
||||
|
||||
|
@ -123,6 +116,7 @@ private:
|
|||
nsRefPtr<AudioListener> mListener;
|
||||
MediaBufferDecoder mDecoder;
|
||||
nsTArray<nsAutoPtr<WebAudioDecodeJob> > mDecodeJobs;
|
||||
float mSampleRate;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
|
||||
#include "AudioDestinationNode.h"
|
||||
#include "mozilla/dom/AudioDestinationNodeBinding.h"
|
||||
#include "AudioNodeEngine.h"
|
||||
#include "AudioNodeStream.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -24,10 +21,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
|||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AudioDestinationNode)
|
||||
|
||||
AudioDestinationNode::AudioDestinationNode(AudioContext* aContext, MediaStreamGraph* aGraph)
|
||||
AudioDestinationNode::AudioDestinationNode(AudioContext* aContext)
|
||||
: AudioNode(aContext)
|
||||
{
|
||||
mStream = aGraph->CreateAudioNodeStream(new AudioNodeEngine());
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class AudioDestinationNode : public AudioNode,
|
|||
public nsWrapperCache
|
||||
{
|
||||
public:
|
||||
AudioDestinationNode(AudioContext* aContext, MediaStreamGraph* aGraph);
|
||||
explicit AudioDestinationNode(AudioContext* aContext);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(AudioDestinationNode,
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "AudioContext.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "AudioNodeStream.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
@ -41,31 +40,16 @@ NS_INTERFACE_MAP_END
|
|||
|
||||
AudioNode::AudioNode(AudioContext* aContext)
|
||||
: mContext(aContext)
|
||||
, mJSBindingFinalized(false)
|
||||
, mCanProduceOwnOutput(false)
|
||||
, mOutputEnded(false)
|
||||
{
|
||||
MOZ_ASSERT(aContext);
|
||||
}
|
||||
|
||||
AudioNode::~AudioNode()
|
||||
{
|
||||
DestroyMediaStream();
|
||||
MOZ_ASSERT(mInputNodes.IsEmpty());
|
||||
MOZ_ASSERT(mOutputNodes.IsEmpty());
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
FindIndexOfNode(const nsTArray<AudioNode::InputNode>& aInputNodes, const AudioNode* aNode)
|
||||
{
|
||||
for (uint32_t i = 0; i < aInputNodes.Length(); ++i) {
|
||||
if (aInputNodes[i].mInputNode == aNode) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return nsTArray<AudioNode::InputNode>::NoIndex;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
FindIndexOfNodeWithPorts(const nsTArray<AudioNode::InputNode>& aInputNodes, const AudioNode* aNode,
|
||||
uint32_t aInputPort, uint32_t aOutputPort)
|
||||
|
@ -80,54 +64,6 @@ FindIndexOfNodeWithPorts(const nsTArray<AudioNode::InputNode>& aInputNodes, cons
|
|||
return nsTArray<AudioNode::InputNode>::NoIndex;
|
||||
}
|
||||
|
||||
void
|
||||
AudioNode::UpdateOutputEnded()
|
||||
{
|
||||
if (mOutputEnded) {
|
||||
// Already ended, so nothing to do.
|
||||
return;
|
||||
}
|
||||
if (mCanProduceOwnOutput ||
|
||||
!mInputNodes.IsEmpty() ||
|
||||
(!mJSBindingFinalized && NumberOfInputs() > 0)) {
|
||||
// This node could still produce output in the future.
|
||||
return;
|
||||
}
|
||||
|
||||
mOutputEnded = true;
|
||||
|
||||
// Addref this temporarily so the refcount bumping below doesn't destroy us
|
||||
// prematurely
|
||||
nsRefPtr<AudioNode> kungFuDeathGrip = this;
|
||||
|
||||
// The idea here is that we remove connections one by one, and at each step
|
||||
// the graph is in a valid state.
|
||||
|
||||
// Disconnect inputs. We don't need them anymore.
|
||||
while (!mInputNodes.IsEmpty()) {
|
||||
uint32_t i = mInputNodes.Length() - 1;
|
||||
nsRefPtr<AudioNode> input = mInputNodes[i].mInputNode.forget();
|
||||
mInputNodes.RemoveElementAt(i);
|
||||
NS_ASSERTION(mOutputNodes.Contains(this), "input/output inconsistency");
|
||||
input->mOutputNodes.RemoveElement(this);
|
||||
}
|
||||
|
||||
while (!mOutputNodes.IsEmpty()) {
|
||||
uint32_t i = mOutputNodes.Length() - 1;
|
||||
nsRefPtr<AudioNode> output = mOutputNodes[i].forget();
|
||||
mOutputNodes.RemoveElementAt(i);
|
||||
uint32_t inputIndex = FindIndexOfNode(output->mInputNodes, this);
|
||||
NS_ASSERTION(inputIndex != nsTArray<AudioNode::InputNode>::NoIndex, "input/output inconsistency");
|
||||
// It doesn't matter which one we remove, since we're going to remove all
|
||||
// entries for this node anyway.
|
||||
output->mInputNodes.RemoveElementAt(inputIndex);
|
||||
|
||||
output->UpdateOutputEnded();
|
||||
}
|
||||
|
||||
DestroyMediaStream();
|
||||
}
|
||||
|
||||
void
|
||||
AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
|
||||
uint32_t aInput, ErrorResult& aRv)
|
||||
|
@ -143,11 +79,6 @@ AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
|
|||
return;
|
||||
}
|
||||
|
||||
if (IsOutputEnded() || aDestination.IsOutputEnded()) {
|
||||
// No need to connect since we're not going to produce anything other
|
||||
// than silence.
|
||||
return;
|
||||
}
|
||||
if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, this, aInput, aOutput) !=
|
||||
nsTArray<AudioNode::InputNode>::NoIndex) {
|
||||
// connection already exists.
|
||||
|
@ -165,14 +96,6 @@ AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
|
|||
input->mInputNode = this;
|
||||
input->mInputPort = aInput;
|
||||
input->mOutputPort = aOutput;
|
||||
if (SupportsMediaStreams() && aDestination.mStream) {
|
||||
// Connect streams in the MediaStreamGraph
|
||||
MOZ_ASSERT(aDestination.mStream->AsProcessedStream());
|
||||
ProcessedMediaStream* ps =
|
||||
static_cast<ProcessedMediaStream*>(aDestination.mStream.get());
|
||||
input->mStreamPort =
|
||||
ps->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -202,10 +125,6 @@ AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < outputsToUpdate.Length(); ++i) {
|
||||
outputsToUpdate[i]->UpdateOutputEnded();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "nsAutoPtr.h"
|
||||
#include "nsTArray.h"
|
||||
#include "AudioContext.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
|
||||
struct JSContext;
|
||||
|
||||
|
@ -23,25 +22,6 @@ class ErrorResult;
|
|||
|
||||
namespace dom {
|
||||
|
||||
/**
|
||||
* The DOM object representing a Web Audio AudioNode.
|
||||
*
|
||||
* Each AudioNode has a MediaStream representing the actual
|
||||
* real-time processing and output of this AudioNode.
|
||||
*
|
||||
* We track the incoming and outgoing connections to other AudioNodes.
|
||||
* All connections are strong and thus rely on cycle collection to break them.
|
||||
* However, we also track whether an AudioNode is capable of producing output
|
||||
* in the future. If it isn't, then we break its connections to its inputs
|
||||
* and outputs, allowing nodes to be immediately disconnected. This
|
||||
* disconnection is done internally, invisible to DOM users.
|
||||
*
|
||||
* We say that a node cannot produce output in the future if it has no inputs
|
||||
* that can, and it is not producing output itself without any inputs, and
|
||||
* either it can never have any inputs or it has no JS wrapper. (If it has a
|
||||
* JS wrapper and can accept inputs, then a new input could be added in
|
||||
* the future.)
|
||||
*/
|
||||
class AudioNode : public nsISupports,
|
||||
public EnableWebAudioCheck
|
||||
{
|
||||
|
@ -49,34 +29,9 @@ public:
|
|||
explicit AudioNode(AudioContext* aContext);
|
||||
virtual ~AudioNode();
|
||||
|
||||
// This should be idempotent (safe to call multiple times).
|
||||
// This should be called in the destructor of every class that overrides
|
||||
// this method.
|
||||
virtual void DestroyMediaStream()
|
||||
{
|
||||
if (mStream) {
|
||||
mStream->Destroy();
|
||||
mStream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// This method should be overridden to return true in nodes
|
||||
// which support being hooked up to the Media Stream graph.
|
||||
virtual bool SupportsMediaStreams() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS(AudioNode)
|
||||
|
||||
void JSBindingFinalized()
|
||||
{
|
||||
NS_ASSERTION(!mJSBindingFinalized, "JS binding already finalized");
|
||||
mJSBindingFinalized = true;
|
||||
UpdateOutputEnded();
|
||||
}
|
||||
|
||||
AudioContext* GetParentObject() const
|
||||
{
|
||||
return mContext;
|
||||
|
@ -98,49 +53,19 @@ public:
|
|||
virtual uint32_t NumberOfInputs() const { return 1; }
|
||||
virtual uint32_t NumberOfOutputs() const { return 1; }
|
||||
|
||||
// This could possibly delete 'this'.
|
||||
void UpdateOutputEnded();
|
||||
bool IsOutputEnded() const { return mOutputEnded; }
|
||||
|
||||
struct InputNode {
|
||||
~InputNode()
|
||||
{
|
||||
if (mStreamPort) {
|
||||
mStreamPort->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Strong reference.
|
||||
// May be null if the source node has gone away.
|
||||
nsRefPtr<AudioNode> mInputNode;
|
||||
nsRefPtr<MediaInputPort> mStreamPort;
|
||||
// The index of the input port this node feeds into.
|
||||
uint32_t mInputPort;
|
||||
// The index of the output port this node comes out of.
|
||||
uint32_t mOutputPort;
|
||||
};
|
||||
|
||||
MediaStream* Stream() { return mStream; }
|
||||
|
||||
// Set this to true when the node can produce its own output even if there
|
||||
// are no inputs.
|
||||
void SetProduceOwnOutput(bool aCanProduceOwnOutput)
|
||||
{
|
||||
mCanProduceOwnOutput = aCanProduceOwnOutput;
|
||||
if (!aCanProduceOwnOutput) {
|
||||
UpdateOutputEnded();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<AudioContext> mContext;
|
||||
|
||||
protected:
|
||||
// Must be set in the constructor. Must not be null.
|
||||
// If MaxNumberOfInputs() is > 0, then mStream must be a ProcessedMediaStream.
|
||||
nsRefPtr<MediaStream> mStream;
|
||||
|
||||
private:
|
||||
// For every InputNode, there is a corresponding entry in mOutputNodes of the
|
||||
// InputNode's mInputNode.
|
||||
nsTArray<InputNode> mInputNodes;
|
||||
|
@ -149,15 +74,6 @@ private:
|
|||
// exact matching entry, since mOutputNodes doesn't include the port
|
||||
// identifiers and the same node could be connected on multiple ports.
|
||||
nsTArray<nsRefPtr<AudioNode> > mOutputNodes;
|
||||
// True if the JS binding has been finalized (so script no longer has
|
||||
// a reference to this node).
|
||||
bool mJSBindingFinalized;
|
||||
// True if this node can produce its own output even when all inputs
|
||||
// have ended their output.
|
||||
bool mCanProduceOwnOutput;
|
||||
// True if this node can never produce anything except silence in the future.
|
||||
// Updated by UpdateOutputEnded().
|
||||
bool mOutputEnded;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@ NS_IMPL_RELEASE_INHERITED(BiquadFilterNode, AudioNode)
|
|||
static float
|
||||
Nyquist(AudioContext* aContext)
|
||||
{
|
||||
return 0.5f * aContext->SampleRate();
|
||||
// TODO: Replace the hardcoded 44100 here with AudioContext::SampleRate()
|
||||
// when we implement that.
|
||||
return 0.5f * 44100;
|
||||
}
|
||||
|
||||
BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(function() {
|
||||
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
|
||||
var context = new AudioContext();
|
||||
var buffer = context.createBuffer(2, 2048, context.sampleRate);
|
||||
var ac = new AudioContext();
|
||||
var buffer = ac.createBuffer(2, 2048, 44100);
|
||||
SpecialPowers.gc(); // Make sure that our channels are accessible after GC
|
||||
ok(buffer, "Buffer was allocated successfully");
|
||||
is(buffer.sampleRate, context.sampleRate, "Correct sample rate");
|
||||
is(buffer.sampleRate, 44100, "Correct sample rate");
|
||||
is(buffer.length, 2048, "Correct length");
|
||||
ok(Math.abs(buffer.duration - 2048 / context.sampleRate) < 0.0001, "Correct duration");
|
||||
ok(Math.abs(buffer.duration - 2048 / 44100) < 0.0001, "Correct duration");
|
||||
is(buffer.numberOfChannels, 2, "Correct number of channels");
|
||||
for (var i = 0; i < buffer.numberOfChannels; ++i) {
|
||||
var buf = buffer.getChannelData(i);
|
||||
|
|
|
@ -22,7 +22,7 @@ addLoadEvent(function() {
|
|||
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
|
||||
var ac = new AudioContext();
|
||||
ok(ac, "Create a AudioContext object");
|
||||
is(ac.sampleRate, 48000, "Correct sample rate");
|
||||
is(ac.sampleRate, 44100, "Correct sample rate");
|
||||
SpecialPowers.clearUserPref("media.webaudio.enabled");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
|
|
@ -19,9 +19,9 @@ addLoadEvent(function() {
|
|||
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
|
||||
|
||||
var context = new AudioContext();
|
||||
var buffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
var buffer = context.createBuffer(1, 2048, 44100);
|
||||
for (var i = 0; i < 2048; ++i) {
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
|
||||
}
|
||||
|
||||
var destination = context.destination;
|
||||
|
@ -38,7 +38,7 @@ addLoadEvent(function() {
|
|||
// Verify default values
|
||||
is(filter.type, 0, "Correct default value for type");
|
||||
near(filter.frequency.minValue, 10, "Correct min value for filter frequency");
|
||||
near(filter.frequency.maxValue, context.sampleRate/2, "Correct max value for filter frequency");
|
||||
near(filter.frequency.maxValue, 22050, "Correct max value for filter frequency");
|
||||
near(filter.frequency.defaultValue, 350, "Correct default value for filter frequency");
|
||||
near(filter.Q.minValue, 0.001, "Correct min value for filter Q");
|
||||
near(filter.Q.maxValue, 1000, "Correct max value for filter Q");
|
||||
|
|
|
@ -254,13 +254,8 @@ expectTypeError(function() {
|
|||
cx.decodeAudioData(new Uint8Array(100), callbackShouldNeverRun, callbackShouldNeverRun);
|
||||
});
|
||||
|
||||
if (cx.sampleRate == 44100) {
|
||||
// Now, let's get real!
|
||||
runNextTest();
|
||||
} else {
|
||||
todo(false, "Decoded data tests disabled; context sampleRate " + cx.sampleRate + " not supported");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
// Now, let's get real!
|
||||
runNextTest();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
|
|
@ -15,9 +15,9 @@ addLoadEvent(function() {
|
|||
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
|
||||
|
||||
var context = new AudioContext();
|
||||
var buffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
var buffer = context.createBuffer(1, 2048, 44100);
|
||||
for (var i = 0; i < 2048; ++i) {
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
|
||||
}
|
||||
|
||||
var destination = context.destination;
|
||||
|
|
|
@ -18,9 +18,9 @@ addLoadEvent(function() {
|
|||
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
|
||||
|
||||
var context = new AudioContext();
|
||||
var buffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
var buffer = context.createBuffer(1, 2048, 44100);
|
||||
for (var i = 0; i < 2048; ++i) {
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
|
||||
}
|
||||
|
||||
var destination = context.destination;
|
||||
|
|
|
@ -14,9 +14,9 @@ addLoadEvent(function() {
|
|||
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
|
||||
|
||||
var context = new AudioContext();
|
||||
var buffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
var buffer = context.createBuffer(1, 2048, 44100);
|
||||
for (var i = 0; i < 2048; ++i) {
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
|
||||
}
|
||||
|
||||
var destination = context.destination;
|
||||
|
|
|
@ -18,9 +18,9 @@ addLoadEvent(function() {
|
|||
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
|
||||
|
||||
var context = new AudioContext();
|
||||
var buffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
var buffer = context.createBuffer(1, 2048, 44100);
|
||||
for (var i = 0; i < 2048; ++i) {
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
|
||||
}
|
||||
|
||||
var destination = context.destination;
|
||||
|
|
|
@ -14,9 +14,9 @@ addLoadEvent(function() {
|
|||
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
|
||||
|
||||
var context = new AudioContext();
|
||||
var buffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
var buffer = context.createBuffer(1, 2048, 44100);
|
||||
for (var i = 0; i < 2048; ++i) {
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
|
||||
}
|
||||
|
||||
var destination = context.destination;
|
||||
|
|
|
@ -98,7 +98,6 @@ DOMInterfaces = {
|
|||
},
|
||||
|
||||
'AudioBufferSourceNode': {
|
||||
'implicitJSContext': [ 'start' ],
|
||||
'wrapperCache': False
|
||||
},
|
||||
|
||||
|
|
|
@ -27,9 +27,9 @@ interface AudioBufferSourceNode : AudioSourceNode {
|
|||
//attribute AudioParam playbackRate;
|
||||
//attribute boolean loop;
|
||||
|
||||
[Throws]
|
||||
void start(optional double when = 0, optional double grainOffset = 0,
|
||||
optional double grainDuration);
|
||||
[Throws]
|
||||
void stop(optional double when = 0);
|
||||
void start(double when);
|
||||
//void start(double when, double grainOffset, double grainDuration);
|
||||
void stop(double when);
|
||||
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче