2013-01-13 14:46:57 -08:00
|
|
|
/* -*- 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"
|
2013-03-12 08:16:42 -07:00
|
|
|
#include "ThreeDPoint.h"
|
2013-01-13 14:46:57 -08:00
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
2013-04-01 13:06:55 -07:00
|
|
|
MOZ_COUNT_DTOR(AudioNodeStream);
|
2013-01-13 14:46:57 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2013-01-28 14:42:27 -08:00
|
|
|
void
|
|
|
|
AudioNodeStream::SetTimelineParameter(uint32_t aIndex,
|
|
|
|
const AudioEventTimeline<ErrorResult>& aValue)
|
|
|
|
{
|
|
|
|
class Message : public ControlMessage {
|
|
|
|
public:
|
|
|
|
Message(AudioNodeStream* aStream, uint32_t aIndex,
|
|
|
|
const AudioEventTimeline<ErrorResult>& aValue)
|
|
|
|
: ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
|
|
|
|
virtual void Run()
|
|
|
|
{
|
|
|
|
static_cast<AudioNodeStream*>(mStream)->Engine()->
|
|
|
|
SetTimelineParameter(mIndex, mValue);
|
|
|
|
}
|
|
|
|
AudioEventTimeline<ErrorResult> mValue;
|
|
|
|
uint32_t mIndex;
|
|
|
|
};
|
|
|
|
GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
|
|
|
|
}
|
|
|
|
|
2013-03-12 08:16:42 -07:00
|
|
|
void
|
|
|
|
AudioNodeStream::SetThreeDPointParameter(uint32_t aIndex, const ThreeDPoint& aValue)
|
|
|
|
{
|
|
|
|
class Message : public ControlMessage {
|
|
|
|
public:
|
|
|
|
Message(AudioNodeStream* aStream, uint32_t aIndex, const ThreeDPoint& aValue)
|
|
|
|
: ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
|
|
|
|
virtual void Run()
|
|
|
|
{
|
|
|
|
static_cast<AudioNodeStream*>(mStream)->Engine()->
|
|
|
|
SetThreeDPointParameter(mIndex, mValue);
|
|
|
|
}
|
|
|
|
ThreeDPoint mValue;
|
|
|
|
uint32_t mIndex;
|
|
|
|
};
|
|
|
|
|
|
|
|
MOZ_ASSERT(this);
|
|
|
|
GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
|
|
|
|
}
|
|
|
|
|
2013-01-13 14:46:57 -08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-04-14 18:52:55 -07:00
|
|
|
bool
|
|
|
|
AudioNodeStream::AllInputsFinished() const
|
|
|
|
{
|
|
|
|
uint32_t inputCount = mInputs.Length();
|
|
|
|
for (uint32_t i = 0; i < inputCount; ++i) {
|
|
|
|
if (!mInputs[i]->GetSource()->IsFinishedOnGraphThread()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return !!inputCount;
|
|
|
|
}
|
|
|
|
|
2013-01-13 14:46:57 -08:00
|
|
|
AudioChunk*
|
|
|
|
AudioNodeStream::ObtainInputBlock(AudioChunk* aTmpChunk)
|
|
|
|
{
|
|
|
|
uint32_t inputCount = mInputs.Length();
|
2013-04-13 18:37:04 -07:00
|
|
|
uint32_t outputChannelCount = mNumberOfInputChannels;
|
2013-01-13 14:46:57 -08:00
|
|
|
nsAutoTArray<AudioChunk*,250> inputChunks;
|
|
|
|
for (uint32_t i = 0; i < inputCount; ++i) {
|
|
|
|
MediaStream* s = mInputs[i]->GetSource();
|
2013-03-17 18:26:45 -07:00
|
|
|
AudioNodeStream* a = static_cast<AudioNodeStream*>(s);
|
|
|
|
MOZ_ASSERT(a == s->AsAudioNodeStream());
|
2013-01-13 14:46:57 -08:00
|
|
|
if (a->IsFinishedOnGraphThread()) {
|
|
|
|
continue;
|
|
|
|
}
|
2013-03-17 17:37:47 -07:00
|
|
|
AudioChunk* chunk = &a->mLastChunk;
|
2013-01-13 14:46:57 -08:00
|
|
|
MOZ_ASSERT(chunk);
|
|
|
|
if (chunk->IsNull()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
inputChunks.AppendElement(chunk);
|
2013-04-13 18:37:04 -07:00
|
|
|
if (!mNumberOfInputChannels) {
|
|
|
|
outputChannelCount =
|
|
|
|
GetAudioChannelsSuperset(outputChannelCount, chunk->mChannelData.Length());
|
|
|
|
}
|
2013-01-13 14:46:57 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t inputChunkCount = inputChunks.Length();
|
|
|
|
if (inputChunkCount == 0) {
|
|
|
|
aTmpChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
|
|
|
|
return aTmpChunk;
|
|
|
|
}
|
|
|
|
|
2013-04-13 18:37:04 -07:00
|
|
|
if (inputChunkCount == 1 &&
|
|
|
|
inputChunks[0]->mChannelData.Length() == outputChannelCount) {
|
2013-01-13 14:46:57 -08:00
|
|
|
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");
|
2013-04-13 18:37:04 -07:00
|
|
|
} else if (channels.Length() > outputChannelCount) {
|
|
|
|
nsAutoTArray<float*,GUESS_AUDIO_CHANNELS> outputChannels;
|
|
|
|
outputChannels.SetLength(outputChannelCount);
|
|
|
|
for (uint32_t i = 0; i < outputChannelCount; ++i) {
|
|
|
|
outputChannels[i] =
|
|
|
|
const_cast<float*>(static_cast<const float*>(aTmpChunk->mChannelData[i]));
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioChannelsDownMix(channels, outputChannels.Elements(),
|
|
|
|
outputChannelCount, WEBAUDIO_BLOCK_SIZE);
|
|
|
|
|
|
|
|
channels.SetLength(outputChannelCount);
|
|
|
|
for (uint32_t i = 0; i < channels.Length(); ++i) {
|
|
|
|
channels[i] = outputChannels[i];
|
|
|
|
}
|
2013-01-13 14:46:57 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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>();
|
|
|
|
|
2013-03-21 02:56:40 -07:00
|
|
|
outputChunk.SetNull(0);
|
|
|
|
|
2013-01-13 14:46:57 -08:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-17 17:37:47 -07:00
|
|
|
mLastChunk = outputChunk;
|
|
|
|
if (mKind == MediaStreamGraph::EXTERNAL_STREAM) {
|
|
|
|
segment->AppendAndConsumeChunk(&outputChunk);
|
|
|
|
} else {
|
|
|
|
segment->AppendNullData(outputChunk.GetDuration());
|
|
|
|
}
|
2013-01-13 14:46:57 -08:00
|
|
|
|
|
|
|
for (uint32_t j = 0; j < mListeners.Length(); ++j) {
|
|
|
|
MediaStreamListener* l = mListeners[j];
|
2013-03-17 17:37:47 -07:00
|
|
|
AudioChunk copyChunk = outputChunk;
|
2013-01-13 14:46:57 -08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|