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