Fri, 16 Jan 2015 04:50:19 +0100
Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "AudioSegment.h" |
michael@0 | 7 | |
michael@0 | 8 | #include "AudioStream.h" |
michael@0 | 9 | #include "AudioMixer.h" |
michael@0 | 10 | #include "AudioChannelFormat.h" |
michael@0 | 11 | #include "Latency.h" |
michael@0 | 12 | #include "speex/speex_resampler.h" |
michael@0 | 13 | |
michael@0 | 14 | namespace mozilla { |
michael@0 | 15 | |
michael@0 | 16 | template <class SrcT, class DestT> |
michael@0 | 17 | static void |
michael@0 | 18 | InterleaveAndConvertBuffer(const SrcT** aSourceChannels, |
michael@0 | 19 | int32_t aLength, float aVolume, |
michael@0 | 20 | int32_t aChannels, |
michael@0 | 21 | DestT* aOutput) |
michael@0 | 22 | { |
michael@0 | 23 | DestT* output = aOutput; |
michael@0 | 24 | for (int32_t i = 0; i < aLength; ++i) { |
michael@0 | 25 | for (int32_t channel = 0; channel < aChannels; ++channel) { |
michael@0 | 26 | float v = AudioSampleToFloat(aSourceChannels[channel][i])*aVolume; |
michael@0 | 27 | *output = FloatToAudioSample<DestT>(v); |
michael@0 | 28 | ++output; |
michael@0 | 29 | } |
michael@0 | 30 | } |
michael@0 | 31 | } |
michael@0 | 32 | |
michael@0 | 33 | void |
michael@0 | 34 | InterleaveAndConvertBuffer(const void** aSourceChannels, |
michael@0 | 35 | AudioSampleFormat aSourceFormat, |
michael@0 | 36 | int32_t aLength, float aVolume, |
michael@0 | 37 | int32_t aChannels, |
michael@0 | 38 | AudioDataValue* aOutput) |
michael@0 | 39 | { |
michael@0 | 40 | switch (aSourceFormat) { |
michael@0 | 41 | case AUDIO_FORMAT_FLOAT32: |
michael@0 | 42 | InterleaveAndConvertBuffer(reinterpret_cast<const float**>(aSourceChannels), |
michael@0 | 43 | aLength, |
michael@0 | 44 | aVolume, |
michael@0 | 45 | aChannels, |
michael@0 | 46 | aOutput); |
michael@0 | 47 | break; |
michael@0 | 48 | case AUDIO_FORMAT_S16: |
michael@0 | 49 | InterleaveAndConvertBuffer(reinterpret_cast<const int16_t**>(aSourceChannels), |
michael@0 | 50 | aLength, |
michael@0 | 51 | aVolume, |
michael@0 | 52 | aChannels, |
michael@0 | 53 | aOutput); |
michael@0 | 54 | break; |
michael@0 | 55 | case AUDIO_FORMAT_SILENCE: |
michael@0 | 56 | // nothing to do here. |
michael@0 | 57 | break; |
michael@0 | 58 | } |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | void |
michael@0 | 62 | AudioSegment::ApplyVolume(float aVolume) |
michael@0 | 63 | { |
michael@0 | 64 | for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { |
michael@0 | 65 | ci->mVolume *= aVolume; |
michael@0 | 66 | } |
michael@0 | 67 | } |
michael@0 | 68 | |
michael@0 | 69 | static const int AUDIO_PROCESSING_FRAMES = 640; /* > 10ms of 48KHz audio */ |
michael@0 | 70 | static const uint8_t gZeroChannel[MAX_AUDIO_SAMPLE_SIZE*AUDIO_PROCESSING_FRAMES] = {0}; |
michael@0 | 71 | |
michael@0 | 72 | void |
michael@0 | 73 | DownmixAndInterleave(const nsTArray<const void*>& aChannelData, |
michael@0 | 74 | AudioSampleFormat aSourceFormat, int32_t aDuration, |
michael@0 | 75 | float aVolume, uint32_t aOutputChannels, |
michael@0 | 76 | AudioDataValue* aOutput) |
michael@0 | 77 | { |
michael@0 | 78 | nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channelData; |
michael@0 | 79 | nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixConversionBuffer; |
michael@0 | 80 | nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixOutputBuffer; |
michael@0 | 81 | |
michael@0 | 82 | channelData.SetLength(aChannelData.Length()); |
michael@0 | 83 | if (aSourceFormat != AUDIO_FORMAT_FLOAT32) { |
michael@0 | 84 | NS_ASSERTION(aSourceFormat == AUDIO_FORMAT_S16, "unknown format"); |
michael@0 | 85 | downmixConversionBuffer.SetLength(aDuration*aChannelData.Length()); |
michael@0 | 86 | for (uint32_t i = 0; i < aChannelData.Length(); ++i) { |
michael@0 | 87 | float* conversionBuf = downmixConversionBuffer.Elements() + (i*aDuration); |
michael@0 | 88 | const int16_t* sourceBuf = static_cast<const int16_t*>(aChannelData[i]); |
michael@0 | 89 | for (uint32_t j = 0; j < (uint32_t)aDuration; ++j) { |
michael@0 | 90 | conversionBuf[j] = AudioSampleToFloat(sourceBuf[j]); |
michael@0 | 91 | } |
michael@0 | 92 | channelData[i] = conversionBuf; |
michael@0 | 93 | } |
michael@0 | 94 | } else { |
michael@0 | 95 | for (uint32_t i = 0; i < aChannelData.Length(); ++i) { |
michael@0 | 96 | channelData[i] = aChannelData[i]; |
michael@0 | 97 | } |
michael@0 | 98 | } |
michael@0 | 99 | |
michael@0 | 100 | downmixOutputBuffer.SetLength(aDuration*aOutputChannels); |
michael@0 | 101 | nsAutoTArray<float*,GUESS_AUDIO_CHANNELS> outputChannelBuffers; |
michael@0 | 102 | nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> outputChannelData; |
michael@0 | 103 | outputChannelBuffers.SetLength(aOutputChannels); |
michael@0 | 104 | outputChannelData.SetLength(aOutputChannels); |
michael@0 | 105 | for (uint32_t i = 0; i < (uint32_t)aOutputChannels; ++i) { |
michael@0 | 106 | outputChannelData[i] = outputChannelBuffers[i] = |
michael@0 | 107 | downmixOutputBuffer.Elements() + aDuration*i; |
michael@0 | 108 | } |
michael@0 | 109 | if (channelData.Length() > aOutputChannels) { |
michael@0 | 110 | AudioChannelsDownMix(channelData, outputChannelBuffers.Elements(), |
michael@0 | 111 | aOutputChannels, aDuration); |
michael@0 | 112 | } |
michael@0 | 113 | InterleaveAndConvertBuffer(outputChannelData.Elements(), AUDIO_FORMAT_FLOAT32, |
michael@0 | 114 | aDuration, aVolume, aOutputChannels, aOutput); |
michael@0 | 115 | } |
michael@0 | 116 | |
michael@0 | 117 | void AudioSegment::ResampleChunks(SpeexResamplerState* aResampler) |
michael@0 | 118 | { |
michael@0 | 119 | uint32_t inRate, outRate; |
michael@0 | 120 | |
michael@0 | 121 | if (mChunks.IsEmpty()) { |
michael@0 | 122 | return; |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | speex_resampler_get_rate(aResampler, &inRate, &outRate); |
michael@0 | 126 | |
michael@0 | 127 | AudioSampleFormat format = AUDIO_FORMAT_SILENCE; |
michael@0 | 128 | for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { |
michael@0 | 129 | if (ci->mBufferFormat != AUDIO_FORMAT_SILENCE) { |
michael@0 | 130 | format = ci->mBufferFormat; |
michael@0 | 131 | } |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | switch (format) { |
michael@0 | 135 | // If the format is silence at this point, all the chunks are silent. The |
michael@0 | 136 | // actual function we use does not matter, it's just a matter of changing |
michael@0 | 137 | // the chunks duration. |
michael@0 | 138 | case AUDIO_FORMAT_SILENCE: |
michael@0 | 139 | case AUDIO_FORMAT_FLOAT32: |
michael@0 | 140 | Resample<float>(aResampler, inRate, outRate); |
michael@0 | 141 | break; |
michael@0 | 142 | case AUDIO_FORMAT_S16: |
michael@0 | 143 | Resample<int16_t>(aResampler, inRate, outRate); |
michael@0 | 144 | break; |
michael@0 | 145 | default: |
michael@0 | 146 | MOZ_ASSERT(false); |
michael@0 | 147 | break; |
michael@0 | 148 | } |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | void |
michael@0 | 152 | AudioSegment::WriteTo(uint64_t aID, AudioStream* aOutput, AudioMixer* aMixer) |
michael@0 | 153 | { |
michael@0 | 154 | uint32_t outputChannels = aOutput->GetChannels(); |
michael@0 | 155 | nsAutoTArray<AudioDataValue,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> buf; |
michael@0 | 156 | nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channelData; |
michael@0 | 157 | // Offset in the buffer that will end up sent to the AudioStream, in samples. |
michael@0 | 158 | uint32_t offset = 0; |
michael@0 | 159 | |
michael@0 | 160 | if (!GetDuration()) { |
michael@0 | 161 | return; |
michael@0 | 162 | } |
michael@0 | 163 | |
michael@0 | 164 | uint32_t outBufferLength = GetDuration() * outputChannels; |
michael@0 | 165 | buf.SetLength(outBufferLength); |
michael@0 | 166 | |
michael@0 | 167 | |
michael@0 | 168 | for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { |
michael@0 | 169 | AudioChunk& c = *ci; |
michael@0 | 170 | uint32_t frames = c.mDuration; |
michael@0 | 171 | |
michael@0 | 172 | // If we have written data in the past, or we have real (non-silent) data |
michael@0 | 173 | // to write, we can proceed. Otherwise, it means we just started the |
michael@0 | 174 | // AudioStream, and we don't have real data to write to it (just silence). |
michael@0 | 175 | // To avoid overbuffering in the AudioStream, we simply drop the silence, |
michael@0 | 176 | // here. The stream will underrun and output silence anyways. |
michael@0 | 177 | if (c.mBuffer || aOutput->GetWritten()) { |
michael@0 | 178 | if (c.mBuffer && c.mBufferFormat != AUDIO_FORMAT_SILENCE) { |
michael@0 | 179 | channelData.SetLength(c.mChannelData.Length()); |
michael@0 | 180 | for (uint32_t i = 0; i < channelData.Length(); ++i) { |
michael@0 | 181 | channelData[i] = c.mChannelData[i]; |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | if (channelData.Length() < outputChannels) { |
michael@0 | 185 | // Up-mix. Note that this might actually make channelData have more |
michael@0 | 186 | // than outputChannels temporarily. |
michael@0 | 187 | AudioChannelsUpMix(&channelData, outputChannels, gZeroChannel); |
michael@0 | 188 | } |
michael@0 | 189 | |
michael@0 | 190 | if (channelData.Length() > outputChannels) { |
michael@0 | 191 | // Down-mix. |
michael@0 | 192 | DownmixAndInterleave(channelData, c.mBufferFormat, frames, |
michael@0 | 193 | c.mVolume, outputChannels, buf.Elements() + offset); |
michael@0 | 194 | } else { |
michael@0 | 195 | InterleaveAndConvertBuffer(channelData.Elements(), c.mBufferFormat, |
michael@0 | 196 | frames, c.mVolume, |
michael@0 | 197 | outputChannels, |
michael@0 | 198 | buf.Elements() + offset); |
michael@0 | 199 | } |
michael@0 | 200 | } else { |
michael@0 | 201 | // Assumes that a bit pattern of zeroes == 0.0f |
michael@0 | 202 | memset(buf.Elements() + offset, 0, outputChannels * frames * sizeof(AudioDataValue)); |
michael@0 | 203 | } |
michael@0 | 204 | offset += frames * outputChannels; |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | if (!c.mTimeStamp.IsNull()) { |
michael@0 | 208 | TimeStamp now = TimeStamp::Now(); |
michael@0 | 209 | // would be more efficient to c.mTimeStamp to ms on create time then pass here |
michael@0 | 210 | LogTime(AsyncLatencyLogger::AudioMediaStreamTrack, aID, |
michael@0 | 211 | (now - c.mTimeStamp).ToMilliseconds(), c.mTimeStamp); |
michael@0 | 212 | } |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | aOutput->Write(buf.Elements(), offset / outputChannels, &(mChunks[mChunks.Length() - 1].mTimeStamp)); |
michael@0 | 216 | |
michael@0 | 217 | if (aMixer) { |
michael@0 | 218 | aMixer->Mix(buf.Elements(), outputChannels, GetDuration(), aOutput->GetRate()); |
michael@0 | 219 | } |
michael@0 | 220 | aOutput->Start(); |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | } |