1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "SonivoxTest"
19 #include <utils/Log.h>
20 
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <fstream>
24 
25 #include <libsonivox/eas.h>
26 #include <libsonivox/eas_reverb.h>
27 
28 #include "SonivoxTestEnvironment.h"
29 
30 #define OUTPUT_FILE "/data/local/tmp/output_midi.pcm"
31 
32 // number of Sonivox output buffers to aggregate into one MediaBuffer
33 static constexpr uint32_t kNumBuffersToCombine = 4;
34 static constexpr uint32_t kSeekBeyondPlayTimeOffsetMs = 10;
35 
36 static SonivoxTestEnvironment *gEnv = nullptr;
37 static int readAt(void *, void *, int, int);
38 static int getSize(void *);
39 
40 class SonivoxTest : public ::testing::TestWithParam<tuple</*fileName*/ string,
41                                                           /*audioPlayTimeMs*/ uint32_t,
42                                                           /*totalChannels*/ uint32_t,
43                                                           /*sampleRateHz*/ uint32_t>> {
44   public:
SonivoxTest()45     SonivoxTest()
46         : mFd(-1),
47           mInputFp(nullptr),
48           mEASDataHandle(nullptr),
49           mEASStreamHandle(nullptr),
50           mPCMBuffer(nullptr),
51           mAudioBuffer(nullptr),
52           mEASConfig(nullptr) {}
53 
~SonivoxTest()54     ~SonivoxTest() {
55         if (mInputFp) fclose(mInputFp);
56         if (mFd >= 0) close(mFd);
57         if (mPCMBuffer) {
58             delete[] mPCMBuffer;
59             mPCMBuffer = nullptr;
60         }
61         if (mAudioBuffer) {
62             delete[] mAudioBuffer;
63             mAudioBuffer = nullptr;
64         }
65         if (gEnv->cleanUp()) remove(OUTPUT_FILE);
66     }
67 
SetUp()68     virtual void SetUp() override {
69         tuple<string, uint32_t, uint32_t, uint32_t> params = GetParam();
70         mInputMediaFile = gEnv->getRes() + get<0>(params);
71         mAudioplayTimeMs = get<1>(params);
72         mTotalAudioChannels = get<2>(params);
73         mAudioSampleRate = get<3>(params);
74 
75         mFd = open(mInputMediaFile.c_str(), O_RDONLY | O_LARGEFILE);
76         ASSERT_GE(mFd, 0) << "Failed to get the file descriptor for file: " << mInputMediaFile;
77 
78         struct stat buf;
79         int8_t err = stat(mInputMediaFile.c_str(), &buf);
80         ASSERT_EQ(err, 0) << "Failed to get information for file: " << mInputMediaFile;
81 
82         mBase = 0;
83         mLength = buf.st_size;
84         mEasFile.handle = this;
85         mEasFile.readAt = ::readAt;
86         mEasFile.size = ::getSize;
87 
88         EAS_RESULT result = EAS_Init(&mEASDataHandle);
89         ASSERT_EQ(result, EAS_SUCCESS) << "Failed to initialize synthesizer library";
90 
91         ASSERT_NE(mEASDataHandle, nullptr) << "Failed to initialize EAS data handle";
92 
93         result = EAS_OpenFile(mEASDataHandle, &mEasFile, &mEASStreamHandle);
94         ASSERT_EQ(result, EAS_SUCCESS) << "Failed to open file";
95 
96         ASSERT_NE(mEASStreamHandle, nullptr) << "Failed to initialize EAS stream handle";
97 
98         result = EAS_Prepare(mEASDataHandle, mEASStreamHandle);
99         ASSERT_EQ(result, EAS_SUCCESS) << "Failed to prepare EAS data and stream handles";
100 
101         EAS_I32 playTimeMs;
102         result = EAS_ParseMetaData(mEASDataHandle, mEASStreamHandle, &playTimeMs);
103         ASSERT_EQ(result, EAS_SUCCESS) << "Failed to parse meta data";
104 
105         ASSERT_EQ(playTimeMs, mAudioplayTimeMs)
106                 << "Invalid audio play time found for file: " << mInputMediaFile;
107 
108         EAS_I32 locationMs = -1;
109         /* EAS_ParseMetaData resets the parser to the starting of file */
110         result = EAS_GetLocation(mEASDataHandle, mEASStreamHandle, &locationMs);
111         ASSERT_EQ(result, EAS_SUCCESS) << "Failed to get the location after parsing meta data";
112 
113         ASSERT_EQ(locationMs, 0) << "Expected position: 0, found: " << locationMs;
114 
115         mEASConfig = EAS_Config();
116         ASSERT_NE(mEASConfig, nullptr) << "Failed to configure the library";
117 
118         ASSERT_GT(mEASConfig->mixBufferSize, 0) << "Mix buffer size must be greater than 0";
119 
120         ASSERT_GT(mEASConfig->numChannels, 0) << "Number of channels must be greater than 0";
121 
122         mPCMBufferSize = sizeof(EAS_PCM) * mEASConfig->mixBufferSize * mEASConfig->numChannels *
123                          kNumBuffersToCombine;
124 
125         mPCMBuffer = new (std::nothrow) EAS_PCM[mPCMBufferSize];
126         ASSERT_NE(mPCMBuffer, nullptr) << "Failed to allocate a memory of size: " << mPCMBufferSize;
127 
128         mAudioBuffer =
129                 new (std::nothrow) EAS_PCM[mEASConfig->mixBufferSize * mEASConfig->numChannels];
130         ASSERT_NE(mAudioBuffer, nullptr) << "Failed to allocate a memory of size: "
131                                          << mEASConfig->mixBufferSize * mEASConfig->numChannels;
132     }
133 
TearDown()134     virtual void TearDown() {
135         EAS_RESULT result;
136         if (mEASDataHandle) {
137             if (mEASStreamHandle) {
138                 result = EAS_CloseFile(mEASDataHandle, mEASStreamHandle);
139                 ASSERT_EQ(result, EAS_SUCCESS) << "Failed to close audio file/stream";
140             }
141             result = EAS_Shutdown(mEASDataHandle);
142             ASSERT_EQ(result, EAS_SUCCESS)
143                     << "Failed to deallocate the resources for synthesizer library";
144         }
145     }
146 
147     bool seekToLocation(EAS_I32);
148     bool renderAudio();
149     int readAt(void *buf, int offset, int size);
150     int getSize();
151 
152     string mInputMediaFile;
153     uint32_t mAudioplayTimeMs;
154     uint32_t mTotalAudioChannels;
155     uint32_t mAudioSampleRate;
156     off64_t mBase;
157     int64_t mLength;
158     int mFd;
159 
160     FILE *mInputFp;
161     EAS_DATA_HANDLE mEASDataHandle;
162     EAS_HANDLE mEASStreamHandle;
163     EAS_FILE mEasFile;
164     EAS_PCM *mPCMBuffer;
165     EAS_PCM *mAudioBuffer;
166     EAS_I32 mPCMBufferSize;
167     const S_EAS_LIB_CONFIG *mEASConfig;
168 };
169 
readAt(void * handle,void * buffer,int offset,int size)170 static int readAt(void *handle, void *buffer, int offset, int size) {
171     return ((SonivoxTest *)handle)->readAt(buffer, offset, size);
172 }
173 
getSize(void * handle)174 static int getSize(void *handle) {
175     return ((SonivoxTest *)handle)->getSize();
176 }
177 
readAt(void * buffer,int offset,int size)178 int SonivoxTest::readAt(void *buffer, int offset, int size) {
179     if (offset > mLength) offset = mLength;
180     lseek(mFd, mBase + offset, SEEK_SET);
181     if (offset + size > mLength) {
182         size = mLength - offset;
183     }
184 
185     return read(mFd, buffer, size);
186 }
187 
getSize()188 int SonivoxTest::getSize() {
189     return mLength;
190 }
191 
seekToLocation(EAS_I32 locationExpectedMs)192 bool SonivoxTest::seekToLocation(EAS_I32 locationExpectedMs) {
193     EAS_RESULT result = EAS_Locate(mEASDataHandle, mEASStreamHandle, locationExpectedMs, false);
194     if (result != EAS_SUCCESS) return false;
195 
196     // position in milliseconds
197     EAS_I32 locationReceivedMs;
198     result = EAS_GetLocation(mEASDataHandle, mEASStreamHandle, &locationReceivedMs);
199     if (result != EAS_SUCCESS) return false;
200 
201     if (locationReceivedMs != locationExpectedMs) return false;
202 
203     return true;
204 }
205 
renderAudio()206 bool SonivoxTest::renderAudio() {
207     EAS_I32 count = -1;
208     EAS_PCM *pcm = mAudioBuffer;
209 
210     EAS_RESULT result = EAS_Render(mEASDataHandle, pcm, mEASConfig->mixBufferSize, &count);
211     if (result != EAS_SUCCESS) {
212         ALOGE("Failed to render audio");
213         return false;
214     }
215     if (count != mEASConfig->mixBufferSize) {
216         ALOGE("%ld of %ld bytes rendered", count, mEASConfig->mixBufferSize);
217         return false;
218     }
219 
220     return true;
221 }
222 
TEST_P(SonivoxTest,DecodeTest)223 TEST_P(SonivoxTest, DecodeTest) {
224     EAS_I32 totalChannels = mEASConfig->numChannels;
225     ASSERT_EQ(totalChannels, mTotalAudioChannels)
226             << "Expected: " << mTotalAudioChannels << " channels, Found: " << totalChannels;
227 
228     EAS_I32 sampleRate = mEASConfig->sampleRate;
229     ASSERT_EQ(sampleRate, mAudioSampleRate)
230             << "Expected: " << mAudioSampleRate << " sample rate, Found: " << sampleRate;
231 
232     // TODO(b/158231824): Check and verify the output with other parameters present at eas_reverb.h
233     // select reverb preset and enable
234     EAS_RESULT result = EAS_SetParameter(mEASDataHandle, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET,
235                                          EAS_PARAM_REVERB_CHAMBER);
236     ASSERT_EQ(result, EAS_SUCCESS)
237             << "Failed to set reverberation preset parameter in reverb module";
238 
239     result =
240             EAS_SetParameter(mEASDataHandle, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
241     ASSERT_EQ(result, EAS_SUCCESS)
242             << "Failed to set reverberation bypass parameter in reverb module";
243 
244     EAS_I32 count;
245     EAS_STATE state;
246 
247     FILE *filePtr = fopen(OUTPUT_FILE, "wb");
248     ASSERT_NE(filePtr, nullptr) << "Failed to open file: " << OUTPUT_FILE;
249 
250     while (1) {
251         EAS_PCM *pcm = mPCMBuffer;
252         int32_t numBytesOutput = 0;
253         result = EAS_State(mEASDataHandle, mEASStreamHandle, &state);
254         ASSERT_EQ(result, EAS_SUCCESS) << "Failed to get EAS State";
255 
256         ASSERT_NE(state, EAS_STATE_ERROR) << "Error state found";
257 
258         /* is playback complete */
259         if (state == EAS_STATE_STOPPED) {
260             break;
261         }
262 
263         EAS_I32 locationMs;
264         result = EAS_GetLocation(mEASDataHandle, mEASStreamHandle, &locationMs);
265         ASSERT_EQ(result, EAS_SUCCESS) << "Failed to get the current location in ms";
266 
267         if (locationMs >= mAudioplayTimeMs) {
268             ASSERT_NE(state, EAS_STATE_STOPPED)
269                     << "Invalid state reached when rendering is complete";
270 
271             break;
272         }
273 
274         for (uint32_t i = 0; i < kNumBuffersToCombine; i++) {
275             result = EAS_Render(mEASDataHandle, pcm, mEASConfig->mixBufferSize, &count);
276             ASSERT_EQ(result, EAS_SUCCESS) << "Failed to render the audio data";
277 
278             pcm += count * mEASConfig->numChannels;
279             numBytesOutput += count * mEASConfig->numChannels * sizeof(EAS_PCM);
280         }
281         int32_t numBytes = fwrite(mPCMBuffer, 1, numBytesOutput, filePtr);
282         ASSERT_EQ(numBytes, numBytesOutput)
283                 << "Wrote " << numBytes << " of " << numBytesOutput << " to file: " << OUTPUT_FILE;
284     }
285     fclose(filePtr);
286 }
287 
TEST_P(SonivoxTest,SeekTest)288 TEST_P(SonivoxTest, SeekTest) {
289     bool status = seekToLocation(0);
290     ASSERT_TRUE(status) << "Seek test failed for location(ms): 0";
291 
292     status = seekToLocation(mAudioplayTimeMs / 2);
293     ASSERT_TRUE(status) << "Seek test failed for location(ms): " << mAudioplayTimeMs / 2;
294 
295     status = seekToLocation(mAudioplayTimeMs);
296     ASSERT_TRUE(status) << "Seek test failed for location(ms): " << mAudioplayTimeMs;
297 
298     status = seekToLocation(mAudioplayTimeMs + kSeekBeyondPlayTimeOffsetMs);
299     ASSERT_FALSE(status) << "Invalid seek position: "
300                          << mAudioplayTimeMs + kSeekBeyondPlayTimeOffsetMs;
301 }
302 
TEST_P(SonivoxTest,DecodePauseResumeTest)303 TEST_P(SonivoxTest, DecodePauseResumeTest) {
304     EAS_I32 seekPosition = mAudioplayTimeMs / 2;
305     // go to middle of the audio
306     EAS_RESULT result = EAS_Locate(mEASDataHandle, mEASStreamHandle, seekPosition, false);
307     ASSERT_EQ(result, EAS_SUCCESS) << "Failed to locate to location(ms): " << seekPosition;
308 
309     bool status = renderAudio();
310     ASSERT_TRUE(status) << "Failed to render audio";
311 
312     result = EAS_Pause(mEASDataHandle, mEASStreamHandle);
313     ASSERT_EQ(result, EAS_SUCCESS) << "Failed to pause";
314 
315     // will render previous audio again, no change in audio position
316     status = renderAudio();
317     ASSERT_TRUE(status) << "should not move audio position, since we're paused";
318 
319     // current position in milliseconds
320     EAS_I32 currentPosMs = -1;
321     result = EAS_GetLocation(mEASDataHandle, mEASStreamHandle, &currentPosMs);
322     ASSERT_EQ(result, EAS_SUCCESS) << "Failed to get current location";
323 
324     ASSERT_EQ(currentPosMs, seekPosition) << "Must not move the audio position after pause";
325 
326     EAS_STATE state;
327     result = EAS_State(mEASDataHandle, mEASStreamHandle, &state);
328     ASSERT_EQ(result, EAS_SUCCESS) << "Failed to get EAS state";
329 
330     ASSERT_EQ(state, EAS_STATE_PAUSED) << "Invalid state reached when paused";
331 
332     result = EAS_Resume(mEASDataHandle, mEASStreamHandle);
333     ASSERT_EQ(result, EAS_SUCCESS) << "Failed to resume";
334 
335     status = renderAudio();
336     ASSERT_TRUE(status) << "Failed to render audio after resume";
337 
338     currentPosMs = -1;
339     result = EAS_GetLocation(mEASDataHandle, mEASStreamHandle, &currentPosMs);
340     ASSERT_EQ(result, EAS_SUCCESS) << "Failed to get current location";
341 
342     ASSERT_GT(currentPosMs, seekPosition) << "Invalid position after resuming";
343 
344     result = EAS_State(mEASDataHandle, mEASStreamHandle, &state);
345     ASSERT_EQ(result, EAS_SUCCESS) << "Failed to get EAS state";
346 
347     ASSERT_EQ(state, EAS_STATE_PLAY) << "Invalid state reached when resumed";
348 }
349 
350 INSTANTIATE_TEST_SUITE_P(SonivoxTestAll, SonivoxTest,
351                          ::testing::Values(make_tuple("midi_a.mid", 2000, 2, 22050),
352                                            make_tuple("midi8sec.mid", 8002, 2, 22050),
353                                            make_tuple("midi_cs.mid", 2000, 2, 22050),
354                                            make_tuple("midi_gs.mid", 2000, 2, 22050),
355                                            make_tuple("ants.mid", 17233, 2, 22050),
356                                            make_tuple("testmxmf.mxmf", 29095, 2, 22050)));
357 
main(int argc,char ** argv)358 int main(int argc, char **argv) {
359     gEnv = new SonivoxTestEnvironment();
360     ::testing::AddGlobalTestEnvironment(gEnv);
361     ::testing::InitGoogleTest(&argc, argv);
362     int status = gEnv->initFromOptions(argc, argv);
363     if (status == 0) {
364         status = RUN_ALL_TESTS();
365         ALOGV("Test result = %d\n", status);
366     }
367     return status;
368 }
369