michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "AudioNodeStream.h" michael@0: michael@0: #include "MediaStreamGraphImpl.h" michael@0: #include "AudioNodeEngine.h" michael@0: #include "ThreeDPoint.h" michael@0: #include "AudioChannelFormat.h" michael@0: #include "AudioParamTimeline.h" michael@0: #include "AudioContext.h" michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: namespace mozilla { michael@0: michael@0: /** michael@0: * An AudioNodeStream produces a single audio track with ID michael@0: * AUDIO_TRACK. This track has rate AudioContext::sIdealAudioRate michael@0: * for regular audio contexts, and the rate requested by the web content michael@0: * for offline audio contexts. michael@0: * Each chunk in the track is a single block of WEBAUDIO_BLOCK_SIZE samples. michael@0: * Note: This must be a different value than MEDIA_STREAM_DEST_TRACK_ID michael@0: */ michael@0: michael@0: AudioNodeStream::~AudioNodeStream() michael@0: { michael@0: MOZ_COUNT_DTOR(AudioNodeStream); michael@0: } michael@0: michael@0: size_t michael@0: AudioNodeStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = 0; michael@0: michael@0: // Not reported: michael@0: // - mEngine michael@0: michael@0: amount += ProcessedMediaStream::SizeOfExcludingThis(aMallocSizeOf); michael@0: amount += mLastChunks.SizeOfExcludingThis(aMallocSizeOf); michael@0: for (size_t i = 0; i < mLastChunks.Length(); i++) { michael@0: // NB: This is currently unshared only as there are instances of michael@0: // double reporting in DMD otherwise. michael@0: amount += mLastChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); michael@0: } michael@0: michael@0: return amount; michael@0: } michael@0: michael@0: size_t michael@0: AudioNodeStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SizeOfAudioNodesIncludingThis(MallocSizeOf aMallocSizeOf, michael@0: AudioNodeSizes& aUsage) const michael@0: { michael@0: // Explicitly separate out the stream memory. michael@0: aUsage.mStream = SizeOfIncludingThis(aMallocSizeOf); michael@0: michael@0: if (mEngine) { michael@0: // This will fill out the rest of |aUsage|. michael@0: mEngine->SizeOfIncludingThis(aMallocSizeOf, aUsage); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetStreamTimeParameter(uint32_t aIndex, AudioContext* aContext, michael@0: double aStreamTime) michael@0: { michael@0: class Message : public ControlMessage { michael@0: public: michael@0: Message(AudioNodeStream* aStream, uint32_t aIndex, MediaStream* aRelativeToStream, michael@0: double aStreamTime) michael@0: : ControlMessage(aStream), mStreamTime(aStreamTime), michael@0: mRelativeToStream(aRelativeToStream), mIndex(aIndex) {} michael@0: virtual void Run() michael@0: { michael@0: static_cast(mStream)-> michael@0: SetStreamTimeParameterImpl(mIndex, mRelativeToStream, mStreamTime); michael@0: } michael@0: double mStreamTime; michael@0: MediaStream* mRelativeToStream; michael@0: uint32_t mIndex; michael@0: }; michael@0: michael@0: MOZ_ASSERT(this); michael@0: GraphImpl()->AppendMessage(new Message(this, aIndex, michael@0: aContext->DestinationStream(), michael@0: aContext->DOMTimeToStreamTime(aStreamTime))); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream, michael@0: double aStreamTime) michael@0: { michael@0: TrackTicks ticks = TicksFromDestinationTime(aRelativeToStream, aStreamTime); michael@0: mEngine->SetStreamTimeParameter(aIndex, ticks); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetDoubleParameter(uint32_t aIndex, double aValue) michael@0: { michael@0: class Message : public ControlMessage { michael@0: public: michael@0: Message(AudioNodeStream* aStream, uint32_t aIndex, double aValue) michael@0: : ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {} michael@0: virtual void Run() michael@0: { michael@0: static_cast(mStream)->Engine()-> michael@0: SetDoubleParameter(mIndex, mValue); michael@0: } michael@0: double mValue; michael@0: uint32_t mIndex; michael@0: }; michael@0: michael@0: MOZ_ASSERT(this); michael@0: GraphImpl()->AppendMessage(new Message(this, aIndex, aValue)); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetInt32Parameter(uint32_t aIndex, int32_t aValue) michael@0: { michael@0: class Message : public ControlMessage { michael@0: public: michael@0: Message(AudioNodeStream* aStream, uint32_t aIndex, int32_t aValue) michael@0: : ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {} michael@0: virtual void Run() michael@0: { michael@0: static_cast(mStream)->Engine()-> michael@0: SetInt32Parameter(mIndex, mValue); michael@0: } michael@0: int32_t mValue; michael@0: uint32_t mIndex; michael@0: }; michael@0: michael@0: MOZ_ASSERT(this); michael@0: GraphImpl()->AppendMessage(new Message(this, aIndex, aValue)); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetTimelineParameter(uint32_t aIndex, michael@0: const AudioParamTimeline& aValue) michael@0: { michael@0: class Message : public ControlMessage { michael@0: public: michael@0: Message(AudioNodeStream* aStream, uint32_t aIndex, michael@0: const AudioParamTimeline& aValue) michael@0: : ControlMessage(aStream), michael@0: mValue(aValue), michael@0: mSampleRate(aStream->SampleRate()), michael@0: mIndex(aIndex) {} michael@0: virtual void Run() michael@0: { michael@0: static_cast(mStream)->Engine()-> michael@0: SetTimelineParameter(mIndex, mValue, mSampleRate); michael@0: } michael@0: AudioParamTimeline mValue; michael@0: TrackRate mSampleRate; michael@0: uint32_t mIndex; michael@0: }; michael@0: GraphImpl()->AppendMessage(new Message(this, aIndex, aValue)); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetThreeDPointParameter(uint32_t aIndex, const ThreeDPoint& aValue) michael@0: { michael@0: class Message : public ControlMessage { michael@0: public: michael@0: Message(AudioNodeStream* aStream, uint32_t aIndex, const ThreeDPoint& aValue) michael@0: : ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {} michael@0: virtual void Run() michael@0: { michael@0: static_cast(mStream)->Engine()-> michael@0: SetThreeDPointParameter(mIndex, mValue); michael@0: } michael@0: ThreeDPoint mValue; michael@0: uint32_t mIndex; michael@0: }; michael@0: michael@0: MOZ_ASSERT(this); michael@0: GraphImpl()->AppendMessage(new Message(this, aIndex, aValue)); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetBuffer(already_AddRefed&& aBuffer) michael@0: { michael@0: class Message : public ControlMessage { michael@0: public: michael@0: Message(AudioNodeStream* aStream, michael@0: already_AddRefed& aBuffer) michael@0: : ControlMessage(aStream), mBuffer(aBuffer) {} michael@0: virtual void Run() michael@0: { michael@0: static_cast(mStream)->Engine()-> michael@0: SetBuffer(mBuffer.forget()); michael@0: } michael@0: nsRefPtr mBuffer; michael@0: }; michael@0: michael@0: MOZ_ASSERT(this); michael@0: GraphImpl()->AppendMessage(new Message(this, aBuffer)); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetRawArrayData(nsTArray& aData) michael@0: { michael@0: class Message : public ControlMessage { michael@0: public: michael@0: Message(AudioNodeStream* aStream, michael@0: nsTArray& aData) michael@0: : ControlMessage(aStream) michael@0: { michael@0: mData.SwapElements(aData); michael@0: } michael@0: virtual void Run() michael@0: { michael@0: static_cast(mStream)->Engine()->SetRawArrayData(mData); michael@0: } michael@0: nsTArray mData; michael@0: }; michael@0: michael@0: MOZ_ASSERT(this); michael@0: GraphImpl()->AppendMessage(new Message(this, aData)); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetChannelMixingParameters(uint32_t aNumberOfChannels, michael@0: ChannelCountMode aChannelCountMode, michael@0: ChannelInterpretation aChannelInterpretation) michael@0: { michael@0: class Message : public ControlMessage { michael@0: public: michael@0: Message(AudioNodeStream* aStream, michael@0: uint32_t aNumberOfChannels, michael@0: ChannelCountMode aChannelCountMode, michael@0: ChannelInterpretation aChannelInterpretation) michael@0: : ControlMessage(aStream), michael@0: mNumberOfChannels(aNumberOfChannels), michael@0: mChannelCountMode(aChannelCountMode), michael@0: mChannelInterpretation(aChannelInterpretation) michael@0: {} michael@0: virtual void Run() michael@0: { michael@0: static_cast(mStream)-> michael@0: SetChannelMixingParametersImpl(mNumberOfChannels, mChannelCountMode, michael@0: mChannelInterpretation); michael@0: } michael@0: uint32_t mNumberOfChannels; michael@0: ChannelCountMode mChannelCountMode; michael@0: ChannelInterpretation mChannelInterpretation; michael@0: }; michael@0: michael@0: MOZ_ASSERT(this); michael@0: GraphImpl()->AppendMessage(new Message(this, aNumberOfChannels, michael@0: aChannelCountMode, michael@0: aChannelInterpretation)); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::SetChannelMixingParametersImpl(uint32_t aNumberOfChannels, michael@0: ChannelCountMode aChannelCountMode, michael@0: ChannelInterpretation aChannelInterpretation) michael@0: { michael@0: // Make sure that we're not clobbering any significant bits by fitting these michael@0: // values in 16 bits. michael@0: MOZ_ASSERT(int(aChannelCountMode) < INT16_MAX); michael@0: MOZ_ASSERT(int(aChannelInterpretation) < INT16_MAX); michael@0: michael@0: mNumberOfInputChannels = aNumberOfChannels; michael@0: mChannelCountMode = aChannelCountMode; michael@0: mChannelInterpretation = aChannelInterpretation; michael@0: } michael@0: michael@0: uint32_t michael@0: AudioNodeStream::ComputedNumberOfChannels(uint32_t aInputChannelCount) michael@0: { michael@0: switch (mChannelCountMode) { michael@0: case ChannelCountMode::Explicit: michael@0: // Disregard the channel count we've calculated from inputs, and just use michael@0: // mNumberOfInputChannels. michael@0: return mNumberOfInputChannels; michael@0: case ChannelCountMode::Clamped_max: michael@0: // Clamp the computed output channel count to mNumberOfInputChannels. michael@0: return std::min(aInputChannelCount, mNumberOfInputChannels); michael@0: default: michael@0: case ChannelCountMode::Max: michael@0: // Nothing to do here, just shut up the compiler warning. michael@0: return aInputChannelCount; michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::ObtainInputBlock(AudioChunk& aTmpChunk, uint32_t aPortIndex) michael@0: { michael@0: uint32_t inputCount = mInputs.Length(); michael@0: uint32_t outputChannelCount = 1; michael@0: nsAutoTArray inputChunks; michael@0: for (uint32_t i = 0; i < inputCount; ++i) { michael@0: if (aPortIndex != mInputs[i]->InputNumber()) { michael@0: // This input is connected to a different port michael@0: continue; michael@0: } michael@0: MediaStream* s = mInputs[i]->GetSource(); michael@0: AudioNodeStream* a = static_cast(s); michael@0: MOZ_ASSERT(a == s->AsAudioNodeStream()); michael@0: if (a->IsAudioParamStream()) { michael@0: continue; michael@0: } michael@0: michael@0: // It is possible for mLastChunks to be empty here, because `a` might be a michael@0: // AudioNodeStream that has not been scheduled yet, because it is further michael@0: // down the graph _but_ as a connection to this node. Because we enforce the michael@0: // presence of at least one DelayNode, with at least one block of delay, and michael@0: // because the output of a DelayNode when it has been fed less that michael@0: // `delayTime` amount of audio is silence, we can simply continue here, michael@0: // because this input would not influence the output of this node. Next michael@0: // iteration, a->mLastChunks.IsEmpty() will be false, and everthing will michael@0: // work as usual. michael@0: if (a->mLastChunks.IsEmpty()) { michael@0: continue; michael@0: } michael@0: michael@0: AudioChunk* chunk = &a->mLastChunks[mInputs[i]->OutputNumber()]; michael@0: MOZ_ASSERT(chunk); michael@0: if (chunk->IsNull() || chunk->mChannelData.IsEmpty()) { michael@0: continue; michael@0: } michael@0: michael@0: inputChunks.AppendElement(chunk); michael@0: outputChannelCount = michael@0: GetAudioChannelsSuperset(outputChannelCount, chunk->mChannelData.Length()); michael@0: } michael@0: michael@0: outputChannelCount = ComputedNumberOfChannels(outputChannelCount); michael@0: michael@0: uint32_t inputChunkCount = inputChunks.Length(); michael@0: if (inputChunkCount == 0 || michael@0: (inputChunkCount == 1 && inputChunks[0]->mChannelData.Length() == 0)) { michael@0: aTmpChunk.SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: return; michael@0: } michael@0: michael@0: if (inputChunkCount == 1 && michael@0: inputChunks[0]->mChannelData.Length() == outputChannelCount) { michael@0: aTmpChunk = *inputChunks[0]; michael@0: return; michael@0: } michael@0: michael@0: if (outputChannelCount == 0) { michael@0: aTmpChunk.SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: return; michael@0: } michael@0: michael@0: AllocateAudioBlock(outputChannelCount, &aTmpChunk); michael@0: // The static storage here should be 1KB, so it's fine michael@0: nsAutoTArray downmixBuffer; michael@0: michael@0: for (uint32_t i = 0; i < inputChunkCount; ++i) { michael@0: AccumulateInputChunk(i, *inputChunks[i], &aTmpChunk, &downmixBuffer); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::AccumulateInputChunk(uint32_t aInputIndex, const AudioChunk& aChunk, michael@0: AudioChunk* aBlock, michael@0: nsTArray* aDownmixBuffer) michael@0: { michael@0: nsAutoTArray channels; michael@0: UpMixDownMixChunk(&aChunk, aBlock->mChannelData.Length(), channels, *aDownmixBuffer); michael@0: michael@0: for (uint32_t c = 0; c < channels.Length(); ++c) { michael@0: const float* inputData = static_cast(channels[c]); michael@0: float* outputData = static_cast(const_cast(aBlock->mChannelData[c])); michael@0: if (inputData) { michael@0: if (aInputIndex == 0) { michael@0: AudioBlockCopyChannelWithScale(inputData, aChunk.mVolume, outputData); michael@0: } else { michael@0: AudioBlockAddChannelWithScale(inputData, aChunk.mVolume, outputData); michael@0: } michael@0: } else { michael@0: if (aInputIndex == 0) { michael@0: PodZero(outputData, WEBAUDIO_BLOCK_SIZE); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::UpMixDownMixChunk(const AudioChunk* aChunk, michael@0: uint32_t aOutputChannelCount, michael@0: nsTArray& aOutputChannels, michael@0: nsTArray& aDownmixBuffer) michael@0: { michael@0: static const float silenceChannel[WEBAUDIO_BLOCK_SIZE] = {0.f}; michael@0: michael@0: aOutputChannels.AppendElements(aChunk->mChannelData); michael@0: if (aOutputChannels.Length() < aOutputChannelCount) { michael@0: if (mChannelInterpretation == ChannelInterpretation::Speakers) { michael@0: AudioChannelsUpMix(&aOutputChannels, aOutputChannelCount, nullptr); michael@0: NS_ASSERTION(aOutputChannelCount == aOutputChannels.Length(), michael@0: "We called GetAudioChannelsSuperset to avoid this"); michael@0: } else { michael@0: // Fill up the remaining aOutputChannels by zeros michael@0: for (uint32_t j = aOutputChannels.Length(); j < aOutputChannelCount; ++j) { michael@0: aOutputChannels.AppendElement(silenceChannel); michael@0: } michael@0: } michael@0: } else if (aOutputChannels.Length() > aOutputChannelCount) { michael@0: if (mChannelInterpretation == ChannelInterpretation::Speakers) { michael@0: nsAutoTArray outputChannels; michael@0: outputChannels.SetLength(aOutputChannelCount); michael@0: aDownmixBuffer.SetLength(aOutputChannelCount * WEBAUDIO_BLOCK_SIZE); michael@0: for (uint32_t j = 0; j < aOutputChannelCount; ++j) { michael@0: outputChannels[j] = &aDownmixBuffer[j * WEBAUDIO_BLOCK_SIZE]; michael@0: } michael@0: michael@0: AudioChannelsDownMix(aOutputChannels, outputChannels.Elements(), michael@0: aOutputChannelCount, WEBAUDIO_BLOCK_SIZE); michael@0: michael@0: aOutputChannels.SetLength(aOutputChannelCount); michael@0: for (uint32_t j = 0; j < aOutputChannels.Length(); ++j) { michael@0: aOutputChannels[j] = outputChannels[j]; michael@0: } michael@0: } else { michael@0: // Drop the remaining aOutputChannels michael@0: aOutputChannels.RemoveElementsAt(aOutputChannelCount, michael@0: aOutputChannels.Length() - aOutputChannelCount); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // The MediaStreamGraph guarantees that this is actually one block, for michael@0: // AudioNodeStreams. michael@0: void michael@0: AudioNodeStream::ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) michael@0: { michael@0: EnsureTrack(AUDIO_TRACK, mSampleRate); michael@0: // No more tracks will be coming michael@0: mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX); michael@0: michael@0: uint16_t outputCount = std::max(uint16_t(1), mEngine->OutputCount()); michael@0: mLastChunks.SetLength(outputCount); michael@0: michael@0: // Consider this stream blocked if it has already finished output. Normally michael@0: // mBlocked would reflect this, but due to rounding errors our audio track may michael@0: // appear to extend slightly beyond aFrom, so we might not be blocked yet. michael@0: bool blocked = mFinished || mBlocked.GetAt(aFrom); michael@0: // If the stream has finished at this time, it will be blocked. michael@0: if (mMuted || blocked) { michael@0: for (uint16_t i = 0; i < outputCount; ++i) { michael@0: mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: } michael@0: } else { michael@0: // We need to generate at least one input michael@0: uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount()); michael@0: OutputChunks inputChunks; michael@0: inputChunks.SetLength(maxInputs); michael@0: for (uint16_t i = 0; i < maxInputs; ++i) { michael@0: ObtainInputBlock(inputChunks[i], i); michael@0: } michael@0: bool finished = false; michael@0: if (maxInputs <= 1 && mEngine->OutputCount() <= 1) { michael@0: mEngine->ProcessBlock(this, inputChunks[0], &mLastChunks[0], &finished); michael@0: } else { michael@0: mEngine->ProcessBlocksOnPorts(this, inputChunks, mLastChunks, &finished); michael@0: } michael@0: for (uint16_t i = 0; i < outputCount; ++i) { michael@0: NS_ASSERTION(mLastChunks[i].GetDuration() == WEBAUDIO_BLOCK_SIZE, michael@0: "Invalid WebAudio chunk size"); michael@0: } michael@0: if (finished) { michael@0: mMarkAsFinishedAfterThisBlock = true; michael@0: } michael@0: michael@0: if (mDisabledTrackIDs.Contains(static_cast(AUDIO_TRACK))) { michael@0: for (uint32_t i = 0; i < outputCount; ++i) { michael@0: mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!blocked) { michael@0: // Don't output anything while blocked michael@0: AdvanceOutputSegment(); michael@0: if (mMarkAsFinishedAfterThisBlock && (aFlags & ALLOW_FINISH)) { michael@0: // This stream was finished the last time that we looked at it, and all michael@0: // of the depending streams have finished their output as well, so now michael@0: // it's time to mark this stream as finished. michael@0: FinishOutput(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::AdvanceOutputSegment() michael@0: { michael@0: StreamBuffer::Track* track = EnsureTrack(AUDIO_TRACK, mSampleRate); michael@0: AudioSegment* segment = track->Get(); michael@0: michael@0: if (mKind == MediaStreamGraph::EXTERNAL_STREAM) { michael@0: segment->AppendAndConsumeChunk(&mLastChunks[0]); michael@0: } else { michael@0: segment->AppendNullData(mLastChunks[0].GetDuration()); michael@0: } michael@0: michael@0: for (uint32_t j = 0; j < mListeners.Length(); ++j) { michael@0: MediaStreamListener* l = mListeners[j]; michael@0: AudioChunk copyChunk = mLastChunks[0]; michael@0: AudioSegment tmpSegment; michael@0: tmpSegment.AppendAndConsumeChunk(©Chunk); michael@0: l->NotifyQueuedTrackChanges(Graph(), AUDIO_TRACK, michael@0: mSampleRate, segment->GetDuration(), 0, michael@0: tmpSegment); michael@0: } michael@0: } michael@0: michael@0: TrackTicks michael@0: AudioNodeStream::GetCurrentPosition() michael@0: { michael@0: return EnsureTrack(AUDIO_TRACK, mSampleRate)->Get()->GetDuration(); michael@0: } michael@0: michael@0: void michael@0: AudioNodeStream::FinishOutput() michael@0: { michael@0: if (IsFinishedOnGraphThread()) { michael@0: return; michael@0: } michael@0: michael@0: StreamBuffer::Track* track = EnsureTrack(AUDIO_TRACK, mSampleRate); michael@0: track->SetEnded(); michael@0: FinishOnGraphThread(); michael@0: michael@0: for (uint32_t j = 0; j < mListeners.Length(); ++j) { michael@0: MediaStreamListener* l = mListeners[j]; michael@0: AudioSegment emptySegment; michael@0: l->NotifyQueuedTrackChanges(Graph(), AUDIO_TRACK, michael@0: mSampleRate, michael@0: track->GetSegment()->GetDuration(), michael@0: MediaStreamListener::TRACK_EVENT_ENDED, emptySegment); michael@0: } michael@0: } michael@0: michael@0: double michael@0: AudioNodeStream::TimeFromDestinationTime(AudioNodeStream* aDestination, michael@0: double aSeconds) michael@0: { michael@0: MOZ_ASSERT(aDestination->SampleRate() == SampleRate()); michael@0: michael@0: double destinationSeconds = std::max(0.0, aSeconds); michael@0: StreamTime streamTime = SecondsToMediaTime(destinationSeconds); michael@0: // MediaTime does not have the resolution of double michael@0: double offset = destinationSeconds - MediaTimeToSeconds(streamTime); michael@0: michael@0: GraphTime graphTime = aDestination->StreamTimeToGraphTime(streamTime); michael@0: StreamTime thisStreamTime = GraphTimeToStreamTimeOptimistic(graphTime); michael@0: double thisSeconds = MediaTimeToSeconds(thisStreamTime) + offset; michael@0: MOZ_ASSERT(thisSeconds >= 0.0); michael@0: return thisSeconds; michael@0: } michael@0: michael@0: TrackTicks michael@0: AudioNodeStream::TicksFromDestinationTime(MediaStream* aDestination, michael@0: double aSeconds) michael@0: { michael@0: AudioNodeStream* destination = aDestination->AsAudioNodeStream(); michael@0: MOZ_ASSERT(destination); michael@0: michael@0: double thisSeconds = TimeFromDestinationTime(destination, aSeconds); michael@0: // Round to nearest michael@0: TrackTicks ticks = thisSeconds * SampleRate() + 0.5; michael@0: return ticks; michael@0: } michael@0: michael@0: double michael@0: AudioNodeStream::DestinationTimeFromTicks(AudioNodeStream* aDestination, michael@0: TrackTicks aPosition) michael@0: { michael@0: MOZ_ASSERT(SampleRate() == aDestination->SampleRate()); michael@0: StreamTime sourceTime = TicksToTimeRoundDown(SampleRate(), aPosition); michael@0: GraphTime graphTime = StreamTimeToGraphTime(sourceTime); michael@0: StreamTime destinationTime = aDestination->GraphTimeToStreamTimeOptimistic(graphTime); michael@0: return MediaTimeToSeconds(destinationTime); michael@0: } michael@0: michael@0: }