summaryrefslogtreecommitdiffstats
path: root/dom/media/webaudio/AudioNodeExternalInputTrack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webaudio/AudioNodeExternalInputTrack.cpp')
-rw-r--r--dom/media/webaudio/AudioNodeExternalInputTrack.cpp225
1 files changed, 225 insertions, 0 deletions
diff --git a/dom/media/webaudio/AudioNodeExternalInputTrack.cpp b/dom/media/webaudio/AudioNodeExternalInputTrack.cpp
new file mode 100644
index 0000000000..2142752d39
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeExternalInputTrack.cpp
@@ -0,0 +1,225 @@
+/* -*- 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 "AlignedTArray.h"
+#include "AlignmentUtils.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeExternalInputTrack.h"
+#include "AudioChannelFormat.h"
+#include "mozilla/dom/MediaStreamAudioSourceNode.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+AudioNodeExternalInputTrack::AudioNodeExternalInputTrack(
+ AudioNodeEngine* aEngine, TrackRate aSampleRate)
+ : AudioNodeTrack(aEngine, NO_TRACK_FLAGS, aSampleRate) {
+ MOZ_COUNT_CTOR(AudioNodeExternalInputTrack);
+}
+
+AudioNodeExternalInputTrack::~AudioNodeExternalInputTrack() {
+ MOZ_COUNT_DTOR(AudioNodeExternalInputTrack);
+}
+
+/* static */
+already_AddRefed<AudioNodeExternalInputTrack>
+AudioNodeExternalInputTrack::Create(MediaTrackGraph* aGraph,
+ AudioNodeEngine* aEngine) {
+ AudioContext* ctx = aEngine->NodeMainThread()->Context();
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aGraph == ctx->Graph());
+
+ RefPtr<AudioNodeExternalInputTrack> track =
+ new AudioNodeExternalInputTrack(aEngine, aGraph->GraphRate());
+ track->mSuspendedCount += ctx->ShouldSuspendNewTrack();
+ aGraph->AddTrack(track);
+ return track.forget();
+}
+
+/**
+ * Copies the data in aInput to aOffsetInBlock within aBlock.
+ * aBlock must have been allocated with AllocateInputBlock and have a channel
+ * count that's a superset of the channels in aInput.
+ */
+template <typename T>
+static void CopyChunkToBlock(AudioChunk& aInput, AudioBlock* aBlock,
+ uint32_t aOffsetInBlock) {
+ uint32_t blockChannels = aBlock->ChannelCount();
+ AutoTArray<const T*, 2> channels;
+ if (aInput.IsNull()) {
+ channels.SetLength(blockChannels);
+ PodZero(channels.Elements(), blockChannels);
+ } else {
+ const nsTArray<const T*>& inputChannels = aInput.ChannelData<T>();
+ channels.SetLength(inputChannels.Length());
+ PodCopy(channels.Elements(), inputChannels.Elements(), channels.Length());
+ if (channels.Length() != blockChannels) {
+ // We only need to upmix here because aBlock's channel count has been
+ // chosen to be a superset of the channel count of every chunk.
+ AudioChannelsUpMix(&channels, blockChannels, static_cast<T*>(nullptr));
+ }
+ }
+
+ for (uint32_t c = 0; c < blockChannels; ++c) {
+ float* outputData = aBlock->ChannelFloatsForWrite(c) + aOffsetInBlock;
+ if (channels[c]) {
+ ConvertAudioSamplesWithScale(channels[c], outputData,
+ aInput.GetDuration(), aInput.mVolume);
+ } else {
+ PodZero(outputData, aInput.GetDuration());
+ }
+ }
+}
+
+/**
+ * Converts the data in aSegment to a single chunk aBlock. aSegment must have
+ * duration WEBAUDIO_BLOCK_SIZE. aFallbackChannelCount is a superset of the
+ * channels in every chunk of aSegment. aBlock must be float format or null.
+ */
+static void ConvertSegmentToAudioBlock(AudioSegment* aSegment,
+ AudioBlock* aBlock,
+ int32_t aFallbackChannelCount) {
+ NS_ASSERTION(aSegment->GetDuration() == WEBAUDIO_BLOCK_SIZE,
+ "Bad segment duration");
+
+ {
+ AudioSegment::ChunkIterator ci(*aSegment);
+ NS_ASSERTION(!ci.IsEnded(), "Should be at least one chunk!");
+ if (ci->GetDuration() == WEBAUDIO_BLOCK_SIZE &&
+ (ci->IsNull() || ci->mBufferFormat == AUDIO_FORMAT_FLOAT32)) {
+ bool aligned = true;
+ for (size_t i = 0; i < ci->mChannelData.Length(); ++i) {
+ if (!IS_ALIGNED16(ci->mChannelData[i])) {
+ aligned = false;
+ break;
+ }
+ }
+
+ // Return this chunk directly to avoid copying data.
+ if (aligned) {
+ *aBlock = *ci;
+ return;
+ }
+ }
+ }
+
+ aBlock->AllocateChannels(aFallbackChannelCount);
+
+ uint32_t duration = 0;
+ for (AudioSegment::ChunkIterator ci(*aSegment); !ci.IsEnded(); ci.Next()) {
+ switch (ci->mBufferFormat) {
+ case AUDIO_FORMAT_S16: {
+ CopyChunkToBlock<int16_t>(*ci, aBlock, duration);
+ break;
+ }
+ case AUDIO_FORMAT_FLOAT32: {
+ CopyChunkToBlock<float>(*ci, aBlock, duration);
+ break;
+ }
+ case AUDIO_FORMAT_SILENCE: {
+ // The actual type of the sample does not matter here, but we still need
+ // to send some audio to the graph.
+ CopyChunkToBlock<float>(*ci, aBlock, duration);
+ break;
+ }
+ }
+ duration += ci->GetDuration();
+ }
+}
+
+void AudioNodeExternalInputTrack::ProcessInput(GraphTime aFrom, GraphTime aTo,
+ uint32_t aFlags) {
+ // According to spec, number of outputs is always 1.
+ MOZ_ASSERT(mLastChunks.Length() == 1);
+
+ // GC stuff can result in our input track being destroyed before this track.
+ // Handle that.
+ if (!IsEnabled() || mInputs.IsEmpty() || mPassThrough) {
+ mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ MOZ_ASSERT(mInputs.Length() == 1);
+
+ MediaTrack* source = mInputs[0]->GetSource();
+ AutoTArray<AudioSegment, 1> audioSegments;
+ uint32_t inputChannels = 0;
+
+ MOZ_ASSERT(source->GetData()->GetType() == MediaSegment::AUDIO,
+ "AudioNodeExternalInputTrack shouldn't have a video input");
+
+ const AudioSegment& inputSegment =
+ *mInputs[0]->GetSource()->GetData<AudioSegment>();
+ if (!inputSegment.IsNull()) {
+ AudioSegment& segment = *audioSegments.AppendElement();
+ GraphTime next;
+ for (GraphTime t = aFrom; t < aTo; t = next) {
+ MediaInputPort::InputInterval interval =
+ MediaInputPort::GetNextInputInterval(mInputs[0], t);
+ interval.mEnd = std::min(interval.mEnd, aTo);
+ if (interval.mStart >= interval.mEnd) {
+ break;
+ }
+ next = interval.mEnd;
+
+ // We know this track does not block during the processing interval ---
+ // we're not finished, we don't underrun, and we're not suspended.
+ TrackTime outputStart = GraphTimeToTrackTime(interval.mStart);
+ TrackTime outputEnd = GraphTimeToTrackTime(interval.mEnd);
+ TrackTime ticks = outputEnd - outputStart;
+
+ if (interval.mInputIsBlocked) {
+ segment.AppendNullData(ticks);
+ } else {
+ // The input track is not blocked in this interval, so no need to call
+ // GraphTimeToTrackTimeWithBlocking.
+ TrackTime inputStart =
+ std::min(inputSegment.GetDuration(),
+ source->GraphTimeToTrackTime(interval.mStart));
+ TrackTime inputEnd =
+ std::min(inputSegment.GetDuration(),
+ source->GraphTimeToTrackTime(interval.mEnd));
+
+ segment.AppendSlice(inputSegment, inputStart, inputEnd);
+ // Pad if we're looking past the end of the track
+ segment.AppendNullData(ticks - (inputEnd - inputStart));
+ }
+ }
+
+ for (AudioSegment::ChunkIterator iter(segment); !iter.IsEnded();
+ iter.Next()) {
+ inputChannels =
+ GetAudioChannelsSuperset(inputChannels, iter->ChannelCount());
+ }
+ }
+
+ uint32_t accumulateIndex = 0;
+ if (inputChannels) {
+ DownmixBufferType downmixBuffer;
+ ASSERT_ALIGNED16(downmixBuffer.Elements());
+ for (auto& audioSegment : audioSegments) {
+ AudioBlock tmpChunk;
+ ConvertSegmentToAudioBlock(&audioSegment, &tmpChunk, inputChannels);
+ if (!tmpChunk.IsNull()) {
+ if (accumulateIndex == 0) {
+ mLastChunks[0].AllocateChannels(inputChannels);
+ }
+ AccumulateInputChunk(accumulateIndex, tmpChunk, &mLastChunks[0],
+ &downmixBuffer);
+ accumulateIndex++;
+ }
+ }
+ }
+ if (accumulateIndex == 0) {
+ mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+}
+
+bool AudioNodeExternalInputTrack::IsEnabled() {
+ return ((MediaStreamAudioSourceNodeEngine*)Engine())->IsEnabled();
+}
+
+} // namespace mozilla