/src/mozilla-central/dom/media/AudioSegment.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
4 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "AudioSegment.h" |
7 | | |
8 | | #include "AudioMixer.h" |
9 | | #include "AudioChannelFormat.h" |
10 | | #include "Latency.h" |
11 | | #include <speex/speex_resampler.h> |
12 | | |
13 | | namespace mozilla { |
14 | | |
15 | | const uint8_t SilentChannel::gZeroChannel[MAX_AUDIO_SAMPLE_SIZE*SilentChannel::AUDIO_PROCESSING_FRAMES] = {0}; |
16 | | |
17 | | template<> |
18 | | const float* SilentChannel::ZeroChannel<float>() |
19 | 0 | { |
20 | 0 | return reinterpret_cast<const float*>(SilentChannel::gZeroChannel); |
21 | 0 | } |
22 | | |
23 | | template<> |
24 | | const int16_t* SilentChannel::ZeroChannel<int16_t>() |
25 | 0 | { |
26 | 0 | return reinterpret_cast<const int16_t*>(SilentChannel::gZeroChannel); |
27 | 0 | } |
28 | | |
29 | | void |
30 | | AudioSegment::ApplyVolume(float aVolume) |
31 | 0 | { |
32 | 0 | for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { |
33 | 0 | ci->mVolume *= aVolume; |
34 | 0 | } |
35 | 0 | } |
36 | | |
37 | | void AudioSegment::ResampleChunks(SpeexResamplerState* aResampler, uint32_t aInRate, uint32_t aOutRate) |
38 | 0 | { |
39 | 0 | if (mChunks.IsEmpty()) { |
40 | 0 | return; |
41 | 0 | } |
42 | 0 | |
43 | 0 | MOZ_ASSERT(aResampler || IsNull(), "We can only be here without a resampler if this segment is null."); |
44 | 0 |
|
45 | 0 | AudioSampleFormat format = AUDIO_FORMAT_SILENCE; |
46 | 0 | for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { |
47 | 0 | if (ci->mBufferFormat != AUDIO_FORMAT_SILENCE) { |
48 | 0 | format = ci->mBufferFormat; |
49 | 0 | } |
50 | 0 | } |
51 | 0 |
|
52 | 0 | switch (format) { |
53 | 0 | // If the format is silence at this point, all the chunks are silent. The |
54 | 0 | // actual function we use does not matter, it's just a matter of changing |
55 | 0 | // the chunks duration. |
56 | 0 | case AUDIO_FORMAT_SILENCE: |
57 | 0 | case AUDIO_FORMAT_FLOAT32: |
58 | 0 | Resample<float>(aResampler, aInRate, aOutRate); |
59 | 0 | break; |
60 | 0 | case AUDIO_FORMAT_S16: |
61 | 0 | Resample<int16_t>(aResampler, aInRate, aOutRate); |
62 | 0 | break; |
63 | 0 | default: |
64 | 0 | MOZ_ASSERT(false); |
65 | 0 | break; |
66 | 0 | } |
67 | 0 | } |
68 | | |
69 | | // This helps to to safely get a pointer to the position we want to start |
70 | | // writing a planar audio buffer, depending on the channel and the offset in the |
71 | | // buffer. |
72 | | static AudioDataValue* |
73 | | PointerForOffsetInChannel(AudioDataValue* aData, size_t aLengthSamples, |
74 | | uint32_t aChannelCount, uint32_t aChannel, |
75 | | uint32_t aOffsetSamples) |
76 | 0 | { |
77 | 0 | size_t samplesPerChannel = aLengthSamples / aChannelCount; |
78 | 0 | size_t beginningOfChannel = samplesPerChannel * aChannel; |
79 | 0 | MOZ_ASSERT(aChannel * samplesPerChannel + aOffsetSamples < aLengthSamples, |
80 | 0 | "Offset request out of bounds."); |
81 | 0 | return aData + beginningOfChannel + aOffsetSamples; |
82 | 0 | } |
83 | | |
84 | | void |
85 | | AudioSegment::Mix(AudioMixer& aMixer, uint32_t aOutputChannels, |
86 | | uint32_t aSampleRate) |
87 | 0 | { |
88 | 0 | AutoTArray<AudioDataValue, SilentChannel::AUDIO_PROCESSING_FRAMES* GUESS_AUDIO_CHANNELS> |
89 | 0 | buf; |
90 | 0 | AutoTArray<const AudioDataValue*, GUESS_AUDIO_CHANNELS> channelData; |
91 | 0 | uint32_t offsetSamples = 0; |
92 | 0 | uint32_t duration = GetDuration(); |
93 | 0 |
|
94 | 0 | if (duration <= 0) { |
95 | 0 | MOZ_ASSERT(duration == 0); |
96 | 0 | return; |
97 | 0 | } |
98 | 0 |
|
99 | 0 | uint32_t outBufferLength = duration * aOutputChannels; |
100 | 0 | buf.SetLength(outBufferLength); |
101 | 0 |
|
102 | 0 | for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { |
103 | 0 | AudioChunk& c = *ci; |
104 | 0 | uint32_t frames = c.mDuration; |
105 | 0 |
|
106 | 0 | // If the chunk is silent, simply write the right number of silence in the |
107 | 0 | // buffers. |
108 | 0 | if (c.mBufferFormat == AUDIO_FORMAT_SILENCE) { |
109 | 0 | for (uint32_t channel = 0; channel < aOutputChannels; channel++) { |
110 | 0 | AudioDataValue* ptr = |
111 | 0 | PointerForOffsetInChannel(buf.Elements(), outBufferLength, |
112 | 0 | aOutputChannels, channel, offsetSamples); |
113 | 0 | PodZero(ptr, frames); |
114 | 0 | } |
115 | 0 | } else { |
116 | 0 | // Othewise, we need to upmix or downmix appropriately, depending on the |
117 | 0 | // desired input and output channels. |
118 | 0 | channelData.SetLength(c.mChannelData.Length()); |
119 | 0 | for (uint32_t i = 0; i < channelData.Length(); ++i) { |
120 | 0 | channelData[i] = static_cast<const AudioDataValue*>(c.mChannelData[i]); |
121 | 0 | } |
122 | 0 | if (channelData.Length() < aOutputChannels) { |
123 | 0 | // Up-mix. |
124 | 0 | AudioChannelsUpMix(&channelData, aOutputChannels, SilentChannel::ZeroChannel<AudioDataValue>()); |
125 | 0 | for (uint32_t channel = 0; channel < aOutputChannels; channel++) { |
126 | 0 | AudioDataValue* ptr = |
127 | 0 | PointerForOffsetInChannel(buf.Elements(), outBufferLength, |
128 | 0 | aOutputChannels, channel, offsetSamples); |
129 | 0 | PodCopy(ptr, reinterpret_cast<const AudioDataValue*>(channelData[channel]), |
130 | 0 | frames); |
131 | 0 | } |
132 | 0 | MOZ_ASSERT(channelData.Length() == aOutputChannels); |
133 | 0 | } else if (channelData.Length() > aOutputChannels) { |
134 | 0 | // Down mix. |
135 | 0 | AutoTArray<AudioDataValue*, GUESS_AUDIO_CHANNELS> outChannelPtrs; |
136 | 0 | outChannelPtrs.SetLength(aOutputChannels); |
137 | 0 | uint32_t offsetSamples = 0; |
138 | 0 | for (uint32_t channel = 0; channel < aOutputChannels; channel++) { |
139 | 0 | outChannelPtrs[channel] = |
140 | 0 | PointerForOffsetInChannel(buf.Elements(), outBufferLength, |
141 | 0 | aOutputChannels, channel, offsetSamples); |
142 | 0 | } |
143 | 0 | AudioChannelsDownMix(channelData, outChannelPtrs.Elements(), |
144 | 0 | aOutputChannels, frames); |
145 | 0 | } else { |
146 | 0 | // The channel count is already what we want, just copy it over. |
147 | 0 | for (uint32_t channel = 0; channel < aOutputChannels; channel++) { |
148 | 0 | AudioDataValue* ptr = |
149 | 0 | PointerForOffsetInChannel(buf.Elements(), outBufferLength, |
150 | 0 | aOutputChannels, channel, offsetSamples); |
151 | 0 | PodCopy(ptr, reinterpret_cast<const AudioDataValue*>(channelData[channel]), |
152 | 0 | frames); |
153 | 0 | } |
154 | 0 | } |
155 | 0 | } |
156 | 0 | offsetSamples += frames; |
157 | 0 | } |
158 | 0 |
|
159 | 0 | if (offsetSamples) { |
160 | 0 | MOZ_ASSERT(offsetSamples == outBufferLength / aOutputChannels, |
161 | 0 | "We forgot to write some samples?"); |
162 | 0 | aMixer.Mix(buf.Elements(), aOutputChannels, offsetSamples, aSampleRate); |
163 | 0 | } |
164 | 0 | } |
165 | | |
166 | | void |
167 | | AudioSegment::WriteTo(uint64_t aID, AudioMixer& aMixer, uint32_t aOutputChannels, uint32_t aSampleRate) |
168 | 0 | { |
169 | 0 | AutoTArray<AudioDataValue,SilentChannel::AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> buf; |
170 | 0 | // Offset in the buffer that will be written to the mixer, in samples. |
171 | 0 | uint32_t offset = 0; |
172 | 0 |
|
173 | 0 | if (GetDuration() <= 0) { |
174 | 0 | MOZ_ASSERT(GetDuration() == 0); |
175 | 0 | return; |
176 | 0 | } |
177 | 0 |
|
178 | 0 | uint32_t outBufferLength = GetDuration() * aOutputChannels; |
179 | 0 | buf.SetLength(outBufferLength); |
180 | 0 |
|
181 | 0 |
|
182 | 0 | for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) { |
183 | 0 | AudioChunk& c = *ci; |
184 | 0 |
|
185 | 0 | switch (c.mBufferFormat) { |
186 | 0 | case AUDIO_FORMAT_S16: |
187 | 0 | WriteChunk<int16_t>(c, aOutputChannels, buf.Elements() + offset); |
188 | 0 | break; |
189 | 0 | case AUDIO_FORMAT_FLOAT32: |
190 | 0 | WriteChunk<float>(c, aOutputChannels, buf.Elements() + offset); |
191 | 0 | break; |
192 | 0 | case AUDIO_FORMAT_SILENCE: |
193 | 0 | // The mixer is expecting interleaved data, so this is ok. |
194 | 0 | PodZero(buf.Elements() + offset, c.mDuration * aOutputChannels); |
195 | 0 | break; |
196 | 0 | default: |
197 | 0 | MOZ_ASSERT(false, "Not handled"); |
198 | 0 | } |
199 | 0 |
|
200 | 0 | offset += c.mDuration * aOutputChannels; |
201 | 0 |
|
202 | 0 | if (!c.mTimeStamp.IsNull()) { |
203 | 0 | TimeStamp now = TimeStamp::Now(); |
204 | 0 | // would be more efficient to c.mTimeStamp to ms on create time then pass here |
205 | 0 | LogTime(AsyncLatencyLogger::AudioMediaStreamTrack, aID, |
206 | 0 | (now - c.mTimeStamp).ToMilliseconds(), c.mTimeStamp); |
207 | 0 | } |
208 | 0 | } |
209 | 0 |
|
210 | 0 | if (offset) { |
211 | 0 | aMixer.Mix(buf.Elements(), aOutputChannels, offset / aOutputChannels, aSampleRate); |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | | } // namespace mozilla |