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 #include <iostream>
8 #include "gtest/gtest.h"
9
10 using namespace mozilla;
11
12 namespace audio_segment {
13
14 /* Helper function to give us the maximum and minimum value that don't clip,
15 * for a given sample format (integer or floating-point). */
16 template <typename T>
17 T GetLowValue();
18
19 template <typename T>
20 T GetHighValue();
21
22 template <typename T>
23 T GetSilentValue();
24
25 template <>
GetLowValue()26 float GetLowValue<float>() {
27 return -1.0;
28 }
29
30 template <>
GetLowValue()31 int16_t GetLowValue<short>() {
32 return -INT16_MAX;
33 }
34
35 template <>
GetHighValue()36 float GetHighValue<float>() {
37 return 1.0;
38 }
39
40 template <>
GetHighValue()41 int16_t GetHighValue<short>() {
42 return INT16_MAX;
43 }
44
45 template <>
GetSilentValue()46 float GetSilentValue() {
47 return 0.0;
48 }
49
50 template <>
GetSilentValue()51 int16_t GetSilentValue() {
52 return 0;
53 }
54
55 // Get an array of planar audio buffers that has the inverse of the index of the
56 // channel (1-indexed) as samples.
57 template <typename T>
GetPlanarChannelArray(size_t aChannels,size_t aSize)58 const T* const* GetPlanarChannelArray(size_t aChannels, size_t aSize) {
59 T** channels = new T*[aChannels];
60 for (size_t c = 0; c < aChannels; c++) {
61 channels[c] = new T[aSize];
62 for (size_t i = 0; i < aSize; i++) {
63 channels[c][i] = FloatToAudioSample<T>(1. / (c + 1));
64 }
65 }
66 return channels;
67 }
68
69 template <typename T>
DeletePlanarChannelsArray(const T * const * aArrays,size_t aChannels)70 void DeletePlanarChannelsArray(const T* const* aArrays, size_t aChannels) {
71 for (size_t channel = 0; channel < aChannels; channel++) {
72 delete[] aArrays[channel];
73 }
74 delete[] aArrays;
75 }
76
77 template <typename T>
GetPlanarArray(size_t aChannels,size_t aSize)78 T** GetPlanarArray(size_t aChannels, size_t aSize) {
79 T** channels = new T*[aChannels];
80 for (size_t c = 0; c < aChannels; c++) {
81 channels[c] = new T[aSize];
82 for (size_t i = 0; i < aSize; i++) {
83 channels[c][i] = 0.0f;
84 }
85 }
86 return channels;
87 }
88
89 template <typename T>
DeletePlanarArray(T ** aArrays,size_t aChannels)90 void DeletePlanarArray(T** aArrays, size_t aChannels) {
91 for (size_t channel = 0; channel < aChannels; channel++) {
92 delete[] aArrays[channel];
93 }
94 delete[] aArrays;
95 }
96
97 // Get an array of audio samples that have the inverse of the index of the
98 // channel (1-indexed) as samples.
99 template <typename T>
GetInterleavedChannelArray(size_t aChannels,size_t aSize)100 const T* GetInterleavedChannelArray(size_t aChannels, size_t aSize) {
101 size_t sampleCount = aChannels * aSize;
102 T* samples = new T[sampleCount];
103 for (size_t i = 0; i < sampleCount; i++) {
104 uint32_t channel = (i % aChannels) + 1;
105 samples[i] = FloatToAudioSample<T>(1. / channel);
106 }
107 return samples;
108 }
109
110 template <typename T>
DeleteInterleavedChannelArray(const T * aArray)111 void DeleteInterleavedChannelArray(const T* aArray) {
112 delete[] aArray;
113 }
114
FuzzyEqual(float aLhs,float aRhs)115 bool FuzzyEqual(float aLhs, float aRhs) { return std::abs(aLhs - aRhs) < 0.01; }
116
117 template <typename SrcT, typename DstT>
TestInterleaveAndConvert()118 void TestInterleaveAndConvert() {
119 size_t arraySize = 1024;
120 size_t maxChannels = 8; // 7.1
121 for (uint32_t channels = 1; channels < maxChannels; channels++) {
122 const SrcT* const* src = GetPlanarChannelArray<SrcT>(channels, arraySize);
123 DstT* dst = new DstT[channels * arraySize];
124
125 InterleaveAndConvertBuffer(src, arraySize, 1.0, channels, dst);
126
127 uint32_t channelIndex = 0;
128 for (size_t i = 0; i < arraySize * channels; i++) {
129 ASSERT_TRUE(FuzzyEqual(
130 dst[i], FloatToAudioSample<DstT>(1. / (channelIndex + 1))));
131 channelIndex++;
132 channelIndex %= channels;
133 }
134
135 DeletePlanarChannelsArray(src, channels);
136 delete[] dst;
137 }
138 }
139
140 template <typename SrcT, typename DstT>
TestDeinterleaveAndConvert()141 void TestDeinterleaveAndConvert() {
142 size_t arraySize = 1024;
143 size_t maxChannels = 8; // 7.1
144 for (uint32_t channels = 1; channels < maxChannels; channels++) {
145 const SrcT* src = GetInterleavedChannelArray<SrcT>(channels, arraySize);
146 DstT** dst = GetPlanarArray<DstT>(channels, arraySize);
147
148 DeinterleaveAndConvertBuffer(src, arraySize, channels, dst);
149
150 for (size_t channel = 0; channel < channels; channel++) {
151 for (size_t i = 0; i < arraySize; i++) {
152 ASSERT_TRUE(FuzzyEqual(dst[channel][i],
153 FloatToAudioSample<DstT>(1. / (channel + 1))));
154 }
155 }
156
157 DeleteInterleavedChannelArray(src);
158 DeletePlanarArray(dst, channels);
159 }
160 }
161
162 uint8_t gSilence[4096] = {0};
163
164 template <typename T>
SilentChannel()165 T* SilentChannel() {
166 return reinterpret_cast<T*>(gSilence);
167 }
168
169 template <typename T>
TestUpmixStereo()170 void TestUpmixStereo() {
171 size_t arraySize = 1024;
172 nsTArray<T*> channels;
173 nsTArray<const T*> channelsptr;
174
175 channels.SetLength(1);
176 channelsptr.SetLength(1);
177
178 channels[0] = new T[arraySize];
179
180 for (size_t i = 0; i < arraySize; i++) {
181 channels[0][i] = GetHighValue<T>();
182 }
183 channelsptr[0] = channels[0];
184
185 AudioChannelsUpMix(&channelsptr, 2, SilentChannel<T>());
186
187 for (size_t channel = 0; channel < 2; channel++) {
188 for (size_t i = 0; i < arraySize; i++) {
189 ASSERT_TRUE(channelsptr[channel][i] == GetHighValue<T>());
190 }
191 }
192 delete[] channels[0];
193 }
194
195 template <typename T>
TestDownmixStereo()196 void TestDownmixStereo() {
197 const size_t arraySize = 1024;
198 nsTArray<const T*> inputptr;
199 nsTArray<T*> input;
200 T** output;
201
202 output = new T*[1];
203 output[0] = new T[arraySize];
204
205 input.SetLength(2);
206 inputptr.SetLength(2);
207
208 for (size_t channel = 0; channel < input.Length(); channel++) {
209 input[channel] = new T[arraySize];
210 for (size_t i = 0; i < arraySize; i++) {
211 input[channel][i] = channel == 0 ? GetLowValue<T>() : GetHighValue<T>();
212 }
213 inputptr[channel] = input[channel];
214 }
215
216 AudioChannelsDownMix(inputptr, output, 1, arraySize);
217
218 for (size_t i = 0; i < arraySize; i++) {
219 ASSERT_TRUE(output[0][i] == GetSilentValue<T>());
220 ASSERT_TRUE(output[0][i] == GetSilentValue<T>());
221 }
222
223 delete[] output[0];
224 delete[] output;
225 }
226
TEST(AudioSegment,Test)227 TEST(AudioSegment, Test)
228 {
229 TestInterleaveAndConvert<float, float>();
230 TestInterleaveAndConvert<float, int16_t>();
231 TestInterleaveAndConvert<int16_t, float>();
232 TestInterleaveAndConvert<int16_t, int16_t>();
233 TestDeinterleaveAndConvert<float, float>();
234 TestDeinterleaveAndConvert<float, int16_t>();
235 TestDeinterleaveAndConvert<int16_t, float>();
236 TestDeinterleaveAndConvert<int16_t, int16_t>();
237 TestUpmixStereo<float>();
238 TestUpmixStereo<int16_t>();
239 TestDownmixStereo<float>();
240 TestDownmixStereo<int16_t>();
241 }
242
243 template <class T, uint32_t Channels>
fillChunk(AudioChunk * aChunk,int aDuration)244 void fillChunk(AudioChunk* aChunk, int aDuration) {
245 static_assert(Channels != 0, "Filling 0 channels is a no-op");
246
247 aChunk->mDuration = aDuration;
248
249 AutoTArray<nsTArray<T>, Channels> buffer;
250 buffer.SetLength(Channels);
251 aChunk->mChannelData.ClearAndRetainStorage();
252 aChunk->mChannelData.SetCapacity(Channels);
253 for (nsTArray<T>& channel : buffer) {
254 T* ch = channel.AppendElements(aDuration);
255 for (int i = 0; i < aDuration; ++i) {
256 ch[i] = GetHighValue<T>();
257 }
258 aChunk->mChannelData.AppendElement(ch);
259 }
260
261 aChunk->mBuffer = new mozilla::SharedChannelArrayBuffer<T>(&buffer);
262 aChunk->mBufferFormat = AudioSampleTypeToFormat<T>::Format;
263 }
264
TEST(AudioSegment,FlushAfter_ZeroDuration)265 TEST(AudioSegment, FlushAfter_ZeroDuration)
266 {
267 AudioChunk c;
268 fillChunk<float, 2>(&c, 10);
269
270 AudioSegment s;
271 s.AppendAndConsumeChunk(&c);
272 s.FlushAfter(0);
273 EXPECT_EQ(s.GetDuration(), 0);
274 }
275
TEST(AudioSegment,FlushAfter_SmallerDuration)276 TEST(AudioSegment, FlushAfter_SmallerDuration)
277 {
278 // It was crashing when the first chunk was silence (null) and FlushAfter
279 // was called for a duration, smaller or equal to the duration of the
280 // first chunk.
281 TrackTime duration = 10;
282 TrackTime smaller_duration = 8;
283 AudioChunk c1;
284 c1.SetNull(duration);
285 AudioChunk c2;
286 fillChunk<float, 2>(&c2, duration);
287
288 AudioSegment s;
289 s.AppendAndConsumeChunk(&c1);
290 s.AppendAndConsumeChunk(&c2);
291 s.FlushAfter(smaller_duration);
292 EXPECT_EQ(s.GetDuration(), smaller_duration) << "Check new duration";
293
294 TrackTime chunkByChunkDuration = 0;
295 for (AudioSegment::ChunkIterator iter(s); !iter.IsEnded(); iter.Next()) {
296 chunkByChunkDuration += iter->GetDuration();
297 }
298 EXPECT_EQ(s.GetDuration(), chunkByChunkDuration)
299 << "Confirm duration chunk by chunk";
300 }
301
TEST(AudioSegment,MemoizedOutputChannelCount)302 TEST(AudioSegment, MemoizedOutputChannelCount)
303 {
304 AudioSegment s;
305 EXPECT_EQ(s.MaxChannelCount(), 0U) << "0 channels on init";
306
307 s.AppendNullData(1);
308 EXPECT_EQ(s.MaxChannelCount(), 0U) << "Null data has 0 channels";
309
310 s.Clear();
311 EXPECT_EQ(s.MaxChannelCount(), 0U) << "Still 0 after clearing";
312
313 AudioChunk c;
314 fillChunk<float, 1>(&c, 1);
315 s.AppendAndConsumeChunk(&c);
316 EXPECT_EQ(s.MaxChannelCount(), 1U) << "A single chunk's channel count";
317
318 fillChunk<float, 2>(&c, 1);
319 s.AppendAndConsumeChunk(&c);
320 EXPECT_EQ(s.MaxChannelCount(), 2U) << "The max of two chunks' channel count";
321
322 s.ForgetUpTo(2);
323 EXPECT_EQ(s.MaxChannelCount(), 2U) << "Memoized value with null chunks";
324
325 s.Clear();
326 EXPECT_EQ(s.MaxChannelCount(), 2U) << "Still memoized after clearing";
327
328 fillChunk<float, 1>(&c, 1);
329 s.AppendAndConsumeChunk(&c);
330 EXPECT_EQ(s.MaxChannelCount(), 1U) << "Real chunk trumps memoized value";
331
332 s.Clear();
333 EXPECT_EQ(s.MaxChannelCount(), 1U) << "Memoized value was updated";
334 }
335
336 } // namespace audio_segment
337