1 /*
2  * Copyright (C) 2019 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 "ExtractorUnitTest"
19 #include <utils/Log.h>
20 
21 #include <datasource/FileSource.h>
22 #include <media/stagefright/MediaBufferGroup.h>
23 #include <media/stagefright/MediaDefs.h>
24 #include <media/stagefright/MetaDataUtils.h>
25 
26 #include "aac/AACExtractor.h"
27 #include "amr/AMRExtractor.h"
28 #include "flac/FLACExtractor.h"
29 #include "midi/MidiExtractor.h"
30 #include "mkv/MatroskaExtractor.h"
31 #include "mp3/MP3Extractor.h"
32 #include "mp4/MPEG4Extractor.h"
33 #include "mp4/SampleTable.h"
34 #include "mpeg2/MPEG2PSExtractor.h"
35 #include "mpeg2/MPEG2TSExtractor.h"
36 #include "ogg/OggExtractor.h"
37 #include "wav/WAVExtractor.h"
38 
39 #include "ExtractorUnitTestEnvironment.h"
40 
41 using namespace android;
42 
43 #define OUTPUT_DUMP_FILE "/data/local/tmp/extractorOutput"
44 
45 constexpr int32_t kMaxCount = 10;
46 constexpr int32_t kOpusSeekPreRollUs = 80000;  // 80 ms;
47 
48 static ExtractorUnitTestEnvironment *gEnv = nullptr;
49 
50 class ExtractorUnitTest : public ::testing::TestWithParam<pair<string, string>> {
51   public:
ExtractorUnitTest()52     ExtractorUnitTest() : mInputFp(nullptr), mDataSource(nullptr), mExtractor(nullptr) {}
53 
~ExtractorUnitTest()54     ~ExtractorUnitTest() {
55         if (mInputFp) {
56             fclose(mInputFp);
57             mInputFp = nullptr;
58         }
59         if (mDataSource) {
60             mDataSource.clear();
61             mDataSource = nullptr;
62         }
63         if (mExtractor) {
64             delete mExtractor;
65             mExtractor = nullptr;
66         }
67     }
68 
SetUp()69     virtual void SetUp() override {
70         mExtractorName = unknown_comp;
71         mDisableTest = false;
72 
73         static const std::map<std::string, standardExtractors> mapExtractor = {
74                 {"aac", AAC},     {"amr", AMR},         {"mp3", MP3},        {"ogg", OGG},
75                 {"wav", WAV},     {"mkv", MKV},         {"flac", FLAC},      {"midi", MIDI},
76                 {"mpeg4", MPEG4}, {"mpeg2ts", MPEG2TS}, {"mpeg2ps", MPEG2PS}};
77         // Find the component type
78         string writerFormat = GetParam().first;
79         if (mapExtractor.find(writerFormat) != mapExtractor.end()) {
80             mExtractorName = mapExtractor.at(writerFormat);
81         }
82         if (mExtractorName == standardExtractors::unknown_comp) {
83             cout << "[   WARN   ] Test Skipped. Invalid extractor\n";
84             mDisableTest = true;
85         }
86     }
87 
88     int32_t setDataSource(string inputFileName);
89 
90     int32_t createExtractor();
91 
92     enum standardExtractors {
93         AAC,
94         AMR,
95         FLAC,
96         MIDI,
97         MKV,
98         MP3,
99         MPEG4,
100         MPEG2PS,
101         MPEG2TS,
102         OGG,
103         WAV,
104         unknown_comp,
105     };
106 
107     bool mDisableTest;
108     standardExtractors mExtractorName;
109 
110     FILE *mInputFp;
111     sp<DataSource> mDataSource;
112     MediaExtractorPluginHelper *mExtractor;
113 };
114 
setDataSource(string inputFileName)115 int32_t ExtractorUnitTest::setDataSource(string inputFileName) {
116     mInputFp = fopen(inputFileName.c_str(), "rb");
117     if (!mInputFp) {
118         ALOGE("Unable to open input file for reading");
119         return -1;
120     }
121     struct stat buf;
122     stat(inputFileName.c_str(), &buf);
123     int32_t fd = fileno(mInputFp);
124     mDataSource = new FileSource(dup(fd), 0, buf.st_size);
125     if (!mDataSource) return -1;
126     return 0;
127 }
128 
createExtractor()129 int32_t ExtractorUnitTest::createExtractor() {
130     switch (mExtractorName) {
131         case AAC:
132             mExtractor = new AACExtractor(new DataSourceHelper(mDataSource->wrap()), 0);
133             break;
134         case AMR:
135             mExtractor = new AMRExtractor(new DataSourceHelper(mDataSource->wrap()));
136             break;
137         case MP3:
138             mExtractor = new MP3Extractor(new DataSourceHelper(mDataSource->wrap()), nullptr);
139             break;
140         case OGG:
141             mExtractor = new OggExtractor(new DataSourceHelper(mDataSource->wrap()));
142             break;
143         case WAV:
144             mExtractor = new WAVExtractor(new DataSourceHelper(mDataSource->wrap()));
145             break;
146         case MKV:
147             mExtractor = new MatroskaExtractor(new DataSourceHelper(mDataSource->wrap()));
148             break;
149         case FLAC:
150             mExtractor = new FLACExtractor(new DataSourceHelper(mDataSource->wrap()));
151             break;
152         case MPEG4:
153             mExtractor = new MPEG4Extractor(new DataSourceHelper(mDataSource->wrap()));
154             break;
155         case MPEG2TS:
156             mExtractor = new MPEG2TSExtractor(new DataSourceHelper(mDataSource->wrap()));
157             break;
158         case MPEG2PS:
159             mExtractor = new MPEG2PSExtractor(new DataSourceHelper(mDataSource->wrap()));
160             break;
161         case MIDI:
162             mExtractor = new MidiExtractor(mDataSource->wrap());
163             break;
164         default:
165             return -1;
166     }
167     if (!mExtractor) return -1;
168     return 0;
169 }
170 
getSeekablePoints(vector<int64_t> & seekablePoints,MediaTrackHelper * track)171 void getSeekablePoints(vector<int64_t> &seekablePoints, MediaTrackHelper *track) {
172     int32_t status = 0;
173     if (!seekablePoints.empty()) {
174         seekablePoints.clear();
175     }
176     int64_t timeStamp;
177     while (status != AMEDIA_ERROR_END_OF_STREAM) {
178         MediaBufferHelper *buffer = nullptr;
179         status = track->read(&buffer);
180         if (buffer) {
181             AMediaFormat *metaData = buffer->meta_data();
182             int32_t isSync = 0;
183             AMediaFormat_getInt32(metaData, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, &isSync);
184             if (isSync) {
185                 AMediaFormat_getInt64(metaData, AMEDIAFORMAT_KEY_TIME_US, &timeStamp);
186                 seekablePoints.push_back(timeStamp);
187             }
188             buffer->release();
189         }
190     }
191 }
192 
TEST_P(ExtractorUnitTest,CreateExtractorTest)193 TEST_P(ExtractorUnitTest, CreateExtractorTest) {
194     if (mDisableTest) return;
195 
196     ALOGV("Checks if a valid extractor is created for a given input file");
197     string inputFileName = gEnv->getRes() + GetParam().second;
198 
199     ASSERT_EQ(setDataSource(inputFileName), 0)
200             << "SetDataSource failed for" << GetParam().first << "extractor";
201 
202     ASSERT_EQ(createExtractor(), 0)
203             << "Extractor creation failed for" << GetParam().first << "extractor";
204 
205     // A valid extractor instace should return success for following calls
206     ASSERT_GT(mExtractor->countTracks(), 0);
207 
208     AMediaFormat *format = AMediaFormat_new();
209     ASSERT_NE(format, nullptr) << "AMediaFormat_new returned null AMediaformat";
210 
211     ASSERT_EQ(mExtractor->getMetaData(format), AMEDIA_OK);
212     AMediaFormat_delete(format);
213 }
214 
TEST_P(ExtractorUnitTest,ExtractorTest)215 TEST_P(ExtractorUnitTest, ExtractorTest) {
216     if (mDisableTest) return;
217 
218     ALOGV("Validates %s Extractor for a given input file", GetParam().first.c_str());
219     string inputFileName = gEnv->getRes() + GetParam().second;
220 
221     int32_t status = setDataSource(inputFileName);
222     ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
223 
224     status = createExtractor();
225     ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
226 
227     int32_t numTracks = mExtractor->countTracks();
228     ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
229 
230     for (int32_t idx = 0; idx < numTracks; idx++) {
231         MediaTrackHelper *track = mExtractor->getTrack(idx);
232         ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
233 
234         CMediaTrack *cTrack = wrap(track);
235         ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
236 
237         MediaBufferGroup *bufferGroup = new MediaBufferGroup();
238         status = cTrack->start(track, bufferGroup->wrap());
239         ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
240 
241         FILE *outFp = fopen((OUTPUT_DUMP_FILE + to_string(idx)).c_str(), "wb");
242         if (!outFp) {
243             ALOGW("Unable to open output file for dumping extracted stream");
244         }
245 
246         while (status != AMEDIA_ERROR_END_OF_STREAM) {
247             MediaBufferHelper *buffer = nullptr;
248             status = track->read(&buffer);
249             ALOGV("track->read Status = %d buffer %p", status, buffer);
250             if (buffer) {
251                 ALOGV("buffer->data %p buffer->size() %zu buffer->range_length() %zu",
252                       buffer->data(), buffer->size(), buffer->range_length());
253                 if (outFp) fwrite(buffer->data(), 1, buffer->range_length(), outFp);
254                 buffer->release();
255             }
256         }
257         if (outFp) fclose(outFp);
258         status = cTrack->stop(track);
259         ASSERT_EQ(OK, status) << "Failed to stop the track";
260         delete bufferGroup;
261         delete track;
262     }
263 }
264 
TEST_P(ExtractorUnitTest,MetaDataComparisonTest)265 TEST_P(ExtractorUnitTest, MetaDataComparisonTest) {
266     if (mDisableTest) return;
267 
268     ALOGV("Validates Extractor's meta data for a given input file");
269     string inputFileName = gEnv->getRes() + GetParam().second;
270 
271     int32_t status = setDataSource(inputFileName);
272     ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
273 
274     status = createExtractor();
275     ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
276 
277     int32_t numTracks = mExtractor->countTracks();
278     ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
279 
280     AMediaFormat *extractorFormat = AMediaFormat_new();
281     ASSERT_NE(extractorFormat, nullptr) << "AMediaFormat_new returned null AMediaformat";
282     AMediaFormat *trackFormat = AMediaFormat_new();
283     ASSERT_NE(trackFormat, nullptr) << "AMediaFormat_new returned null AMediaformat";
284 
285     for (int32_t idx = 0; idx < numTracks; idx++) {
286         MediaTrackHelper *track = mExtractor->getTrack(idx);
287         ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
288 
289         CMediaTrack *cTrack = wrap(track);
290         ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
291 
292         MediaBufferGroup *bufferGroup = new MediaBufferGroup();
293         status = cTrack->start(track, bufferGroup->wrap());
294         ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
295 
296         status = mExtractor->getTrackMetaData(extractorFormat, idx, 1);
297         ASSERT_EQ(OK, (media_status_t)status) << "Failed to get trackMetaData";
298 
299         status = track->getFormat(trackFormat);
300         ASSERT_EQ(OK, (media_status_t)status) << "Failed to get track meta data";
301 
302         const char *extractorMime, *trackMime;
303         AMediaFormat_getString(extractorFormat, AMEDIAFORMAT_KEY_MIME, &extractorMime);
304         AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &trackMime);
305         ASSERT_TRUE(!strcmp(extractorMime, trackMime))
306                 << "Extractor's format doesn't match track format";
307 
308         if (!strncmp(extractorMime, "audio/", 6)) {
309             int32_t exSampleRate, exChannelCount;
310             int32_t trackSampleRate, trackChannelCount;
311             ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
312                                               &exChannelCount));
313             ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE,
314                                               &exSampleRate));
315             ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
316                                               &trackChannelCount));
317             ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE,
318                                               &trackSampleRate));
319             ASSERT_EQ(exChannelCount, trackChannelCount) << "ChannelCount not as expected";
320             ASSERT_EQ(exSampleRate, trackSampleRate) << "SampleRate not as expected";
321         } else {
322             int32_t exWidth, exHeight;
323             int32_t trackWidth, trackHeight;
324             ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_WIDTH, &exWidth));
325             ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_HEIGHT, &exHeight));
326             ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_WIDTH, &trackWidth));
327             ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_HEIGHT, &trackHeight));
328             ASSERT_EQ(exWidth, trackWidth) << "Width not as expected";
329             ASSERT_EQ(exHeight, trackHeight) << "Height not as expected";
330         }
331         status = cTrack->stop(track);
332         ASSERT_EQ(OK, status) << "Failed to stop the track";
333         delete bufferGroup;
334         delete track;
335     }
336     AMediaFormat_delete(trackFormat);
337     AMediaFormat_delete(extractorFormat);
338 }
339 
TEST_P(ExtractorUnitTest,MultipleStartStopTest)340 TEST_P(ExtractorUnitTest, MultipleStartStopTest) {
341     if (mDisableTest) return;
342 
343     ALOGV("Test %s extractor for multiple start and stop calls", GetParam().first.c_str());
344     string inputFileName = gEnv->getRes() + GetParam().second;
345 
346     int32_t status = setDataSource(inputFileName);
347     ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
348 
349     status = createExtractor();
350     ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
351 
352     int32_t numTracks = mExtractor->countTracks();
353     ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
354 
355     // start/stop the tracks multiple times
356     for (int32_t count = 0; count < kMaxCount; count++) {
357         for (int32_t idx = 0; idx < numTracks; idx++) {
358             MediaTrackHelper *track = mExtractor->getTrack(idx);
359             ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
360 
361             CMediaTrack *cTrack = wrap(track);
362             ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
363 
364             MediaBufferGroup *bufferGroup = new MediaBufferGroup();
365             status = cTrack->start(track, bufferGroup->wrap());
366             ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
367             MediaBufferHelper *buffer = nullptr;
368             status = track->read(&buffer);
369             if (buffer) {
370                 ALOGV("buffer->data %p buffer->size() %zu buffer->range_length() %zu",
371                       buffer->data(), buffer->size(), buffer->range_length());
372                 buffer->release();
373             }
374             status = cTrack->stop(track);
375             ASSERT_EQ(OK, status) << "Failed to stop the track";
376             delete bufferGroup;
377             delete track;
378         }
379     }
380 }
381 
TEST_P(ExtractorUnitTest,SeekTest)382 TEST_P(ExtractorUnitTest, SeekTest) {
383     // Both Flac and Wav extractor can give samples from any pts and mark the given sample as
384     // sync frame. So, this seek test is not applicable to FLAC and WAV extractors
385     if (mDisableTest || mExtractorName == FLAC || mExtractorName == WAV) return;
386 
387     ALOGV("Validates %s Extractor behaviour for different seek modes", GetParam().first.c_str());
388     string inputFileName = gEnv->getRes() + GetParam().second;
389 
390     int32_t status = setDataSource(inputFileName);
391     ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
392 
393     status = createExtractor();
394     ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
395 
396     int32_t numTracks = mExtractor->countTracks();
397     ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
398 
399     uint32_t seekFlag = mExtractor->flags();
400     if (!(seekFlag & MediaExtractorPluginHelper::CAN_SEEK)) {
401         cout << "[   WARN   ] Test Skipped. " << GetParam().first
402              << " Extractor doesn't support seek\n";
403         return;
404     }
405 
406     vector<int64_t> seekablePoints;
407     for (int32_t idx = 0; idx < numTracks; idx++) {
408         MediaTrackHelper *track = mExtractor->getTrack(idx);
409         ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
410 
411         CMediaTrack *cTrack = wrap(track);
412         ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
413 
414         // Get all the seekable points of a given input
415         MediaBufferGroup *bufferGroup = new MediaBufferGroup();
416         status = cTrack->start(track, bufferGroup->wrap());
417         ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
418         getSeekablePoints(seekablePoints, track);
419         ASSERT_GT(seekablePoints.size(), 0)
420                 << "Failed to get seekable points for " << GetParam().first << " extractor";
421 
422         AMediaFormat *trackFormat = AMediaFormat_new();
423         ASSERT_NE(trackFormat, nullptr) << "AMediaFormat_new returned null format";
424         status = track->getFormat(trackFormat);
425         ASSERT_EQ(OK, (media_status_t)status) << "Failed to get track meta data";
426 
427         bool isOpus = false;
428         const char *mime;
429         AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
430         if (!strcmp(mime, "audio/opus")) isOpus = true;
431         AMediaFormat_delete(trackFormat);
432 
433         int32_t seekIdx = 0;
434         size_t seekablePointsSize = seekablePoints.size();
435         for (int32_t mode = CMediaTrackReadOptions::SEEK_PREVIOUS_SYNC;
436              mode <= CMediaTrackReadOptions::SEEK_CLOSEST; mode++) {
437             for (int32_t seekCount = 0; seekCount < kMaxCount; seekCount++) {
438                 seekIdx = rand() % seekablePointsSize + 1;
439                 if (seekIdx >= seekablePointsSize) seekIdx = seekablePointsSize - 1;
440 
441                 int64_t seekToTimeStamp = seekablePoints[seekIdx];
442                 if (seekablePointsSize > 1) {
443                     int64_t prevTimeStamp = seekablePoints[seekIdx - 1];
444                     seekToTimeStamp = seekToTimeStamp - ((seekToTimeStamp - prevTimeStamp) >> 3);
445                 }
446 
447                 // Opus has a seekPreRollUs. TimeStamp returned by the
448                 // extractor is calculated based on (seekPts - seekPreRollUs).
449                 // So we add the preRoll value to the timeStamp we want to seek to.
450                 if (isOpus) {
451                     seekToTimeStamp += kOpusSeekPreRollUs;
452                 }
453 
454                 MediaTrackHelper::ReadOptions *options = new MediaTrackHelper::ReadOptions(
455                         mode | CMediaTrackReadOptions::SEEK, seekToTimeStamp);
456                 ASSERT_NE(options, nullptr) << "Cannot create read option";
457 
458                 MediaBufferHelper *buffer = nullptr;
459                 status = track->read(&buffer, options);
460                 if (status == AMEDIA_ERROR_END_OF_STREAM) {
461                     delete options;
462                     continue;
463                 }
464                 if (buffer) {
465                     AMediaFormat *metaData = buffer->meta_data();
466                     int64_t timeStamp;
467                     AMediaFormat_getInt64(metaData, AMEDIAFORMAT_KEY_TIME_US, &timeStamp);
468                     buffer->release();
469 
470                     // CMediaTrackReadOptions::SEEK is 8. Using mask 0111b to get true modes
471                     switch (mode & 0x7) {
472                         case CMediaTrackReadOptions::SEEK_PREVIOUS_SYNC:
473                             if (seekablePointsSize == 1) {
474                                 EXPECT_EQ(timeStamp, seekablePoints[seekIdx]);
475                             } else {
476                                 EXPECT_EQ(timeStamp, seekablePoints[seekIdx - 1]);
477                             }
478                             break;
479                         case CMediaTrackReadOptions::SEEK_NEXT_SYNC:
480                         case CMediaTrackReadOptions::SEEK_CLOSEST_SYNC:
481                         case CMediaTrackReadOptions::SEEK_CLOSEST:
482                             EXPECT_EQ(timeStamp, seekablePoints[seekIdx]);
483                             break;
484                         default:
485                             break;
486                     }
487                 }
488                 delete options;
489             }
490         }
491         status = cTrack->stop(track);
492         ASSERT_EQ(OK, status) << "Failed to stop the track";
493         delete bufferGroup;
494         delete track;
495     }
496     seekablePoints.clear();
497 }
498 
499 // TODO: (b/145332185)
500 // Add MIDI inputs
501 INSTANTIATE_TEST_SUITE_P(ExtractorUnitTestAll, ExtractorUnitTest,
502                          ::testing::Values(make_pair("aac", "loudsoftaac.aac"),
503                                            make_pair("amr", "testamr.amr"),
504                                            make_pair("amr", "amrwb.wav"),
505                                            make_pair("ogg", "john_cage.ogg"),
506                                            make_pair("wav", "monotestgsm.wav"),
507                                            make_pair("mpeg2ts", "segment000001.ts"),
508                                            make_pair("flac", "sinesweepflac.flac"),
509                                            make_pair("ogg", "testopus.opus"),
510                                            make_pair("mkv", "sinesweepvorbis.mkv"),
511                                            make_pair("mpeg4", "sinesweepoggmp4.mp4"),
512                                            make_pair("mp3", "sinesweepmp3lame.mp3"),
513                                            make_pair("mkv", "swirl_144x136_vp9.webm"),
514                                            make_pair("mkv", "swirl_144x136_vp8.webm"),
515                                            make_pair("mpeg2ps", "swirl_144x136_mpeg2.mpg"),
516                                            make_pair("mpeg4", "swirl_132x130_mpeg4.mp4")));
517 
main(int argc,char ** argv)518 int main(int argc, char **argv) {
519     gEnv = new ExtractorUnitTestEnvironment();
520     ::testing::AddGlobalTestEnvironment(gEnv);
521     ::testing::InitGoogleTest(&argc, argv);
522     int status = gEnv->initFromOptions(argc, argv);
523     if (status == 0) {
524         status = RUN_ALL_TESTS();
525         ALOGV("Test result = %d\n", status);
526     }
527     return status;
528 }
529