/* -*- 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 "AudioSegment.h" #include "AudioMixer.h" #include "AudioChannelFormat.h" #include namespace mozilla { const uint8_t SilentChannel::gZeroChannel[MAX_AUDIO_SAMPLE_SIZE * SilentChannel::AUDIO_PROCESSING_FRAMES] = {0}; template <> const float* SilentChannel::ZeroChannel() { return reinterpret_cast(SilentChannel::gZeroChannel); } template <> const int16_t* SilentChannel::ZeroChannel() { return reinterpret_cast(SilentChannel::gZeroChannel); } void AudioSegment::ApplyVolume(float aVolume) { for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { ci->mVolume *= aVolume; } } void AudioSegment::ResampleChunks(nsAutoRef& aResampler, uint32_t* aResamplerChannelCount, uint32_t aInRate, uint32_t aOutRate) { if (mChunks.IsEmpty()) { return; } AudioSampleFormat format = AUDIO_FORMAT_SILENCE; for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { if (ci->mBufferFormat != AUDIO_FORMAT_SILENCE) { format = ci->mBufferFormat; } } switch (format) { // If the format is silence at this point, all the chunks are silent. The // actual function we use does not matter, it's just a matter of changing // the chunks duration. case AUDIO_FORMAT_SILENCE: case AUDIO_FORMAT_FLOAT32: Resample(aResampler, aResamplerChannelCount, aInRate, aOutRate); break; case AUDIO_FORMAT_S16: Resample(aResampler, aResamplerChannelCount, aInRate, aOutRate); break; default: MOZ_ASSERT(false); break; } } size_t AudioSegment::WriteToInterleavedBuffer(nsTArray& aBuffer, uint32_t aChannels) const { size_t offset = 0; if (GetDuration() <= 0) { MOZ_ASSERT(GetDuration() == 0); return offset; } // Calculate how many samples in this segment size_t frames = static_cast(GetDuration()); CheckedInt samples(frames); samples *= static_cast(aChannels); MOZ_ASSERT(samples.isValid()); if (!samples.isValid()) { return offset; } // Enlarge buffer space if needed if (samples.value() > aBuffer.Capacity()) { aBuffer.SetCapacity(samples.value()); } aBuffer.SetLengthAndRetainStorage(samples.value()); aBuffer.ClearAndRetainStorage(); // Convert the de-interleaved chunks into an interleaved buffer. Note that // we may upmix or downmix the audio data if the channel in the chunks // mismatch with aChannels for (ConstChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { const AudioChunk& c = *ci; size_t samplesInChunk = static_cast(c.mDuration) * aChannels; switch (c.mBufferFormat) { case AUDIO_FORMAT_S16: WriteChunk(c, aChannels, c.mVolume, aBuffer.Elements() + offset); break; case AUDIO_FORMAT_FLOAT32: WriteChunk(c, aChannels, c.mVolume, aBuffer.Elements() + offset); break; case AUDIO_FORMAT_SILENCE: PodZero(aBuffer.Elements() + offset, samplesInChunk); break; default: MOZ_ASSERT_UNREACHABLE("Unknown format"); PodZero(aBuffer.Elements() + offset, samplesInChunk); break; } offset += samplesInChunk; } MOZ_DIAGNOSTIC_ASSERT(samples.value() == offset, "Segment's duration is incorrect"); aBuffer.SetLengthAndRetainStorage(offset); return offset; } // This helps to to safely get a pointer to the position we want to start // writing a planar audio buffer, depending on the channel and the offset in the // buffer. static AudioDataValue* PointerForOffsetInChannel(AudioDataValue* aData, size_t aLengthSamples, uint32_t aChannelCount, uint32_t aChannel, uint32_t aOffsetSamples) { size_t samplesPerChannel = aLengthSamples / aChannelCount; size_t beginningOfChannel = samplesPerChannel * aChannel; MOZ_ASSERT(aChannel * samplesPerChannel + aOffsetSamples < aLengthSamples, "Offset request out of bounds."); return aData + beginningOfChannel + aOffsetSamples; } void AudioSegment::Mix(AudioMixer& aMixer, uint32_t aOutputChannels, uint32_t aSampleRate) { AutoTArray buf; AutoTArray channelData; uint32_t offsetSamples = 0; uint32_t duration = GetDuration(); if (duration <= 0) { MOZ_ASSERT(duration == 0); return; } uint32_t outBufferLength = duration * aOutputChannels; buf.SetLength(outBufferLength); for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { AudioChunk& c = *ci; uint32_t frames = c.mDuration; // If the chunk is silent, simply write the right number of silence in the // buffers. if (c.mBufferFormat == AUDIO_FORMAT_SILENCE) { for (uint32_t channel = 0; channel < aOutputChannels; channel++) { AudioDataValue* ptr = PointerForOffsetInChannel(buf.Elements(), outBufferLength, aOutputChannels, channel, offsetSamples); PodZero(ptr, frames); } } else { // Othewise, we need to upmix or downmix appropriately, depending on the // desired input and output channels. channelData.SetLength(c.mChannelData.Length()); for (uint32_t i = 0; i < channelData.Length(); ++i) { channelData[i] = static_cast(c.mChannelData[i]); } if (channelData.Length() < aOutputChannels) { // Up-mix. AudioChannelsUpMix(&channelData, aOutputChannels, SilentChannel::ZeroChannel()); for (uint32_t channel = 0; channel < aOutputChannels; channel++) { AudioDataValue* ptr = PointerForOffsetInChannel( buf.Elements(), outBufferLength, aOutputChannels, channel, offsetSamples); PodCopy(ptr, reinterpret_cast(channelData[channel]), frames); } MOZ_ASSERT(channelData.Length() == aOutputChannels); } else if (channelData.Length() > aOutputChannels) { // Down mix. AutoTArray outChannelPtrs; outChannelPtrs.SetLength(aOutputChannels); uint32_t offsetSamples = 0; for (uint32_t channel = 0; channel < aOutputChannels; channel++) { outChannelPtrs[channel] = PointerForOffsetInChannel( buf.Elements(), outBufferLength, aOutputChannels, channel, offsetSamples); } AudioChannelsDownMix(channelData, outChannelPtrs.Elements(), aOutputChannels, frames); } else { // The channel count is already what we want, just copy it over. for (uint32_t channel = 0; channel < aOutputChannels; channel++) { AudioDataValue* ptr = PointerForOffsetInChannel( buf.Elements(), outBufferLength, aOutputChannels, channel, offsetSamples); PodCopy(ptr, reinterpret_cast(channelData[channel]), frames); } } } offsetSamples += frames; } if (offsetSamples) { MOZ_ASSERT(offsetSamples == outBufferLength / aOutputChannels, "We forgot to write some samples?"); aMixer.Mix(buf.Elements(), aOutputChannels, offsetSamples, aSampleRate); } } void AudioSegment::WriteTo(AudioMixer& aMixer, uint32_t aOutputChannels, uint32_t aSampleRate) { AutoTArray buf; // Offset in the buffer that will be written to the mixer, in samples. uint32_t offset = 0; if (GetDuration() <= 0) { MOZ_ASSERT(GetDuration() == 0); return; } uint32_t outBufferLength = GetDuration() * aOutputChannels; buf.SetLength(outBufferLength); for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { AudioChunk& c = *ci; switch (c.mBufferFormat) { case AUDIO_FORMAT_S16: WriteChunk(c, aOutputChannels, c.mVolume, buf.Elements() + offset); break; case AUDIO_FORMAT_FLOAT32: WriteChunk(c, aOutputChannels, c.mVolume, buf.Elements() + offset); break; case AUDIO_FORMAT_SILENCE: // The mixer is expecting interleaved data, so this is ok. PodZero(buf.Elements() + offset, c.mDuration * aOutputChannels); break; default: MOZ_ASSERT(false, "Not handled"); } offset += c.mDuration * aOutputChannels; } if (offset) { aMixer.Mix(buf.Elements(), aOutputChannels, offset / aOutputChannels, aSampleRate); } } } // namespace mozilla