• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "NativeMuxerTest"
19 #include <log/log.h>
20 
21 #include <NdkMediaExtractor.h>
22 #include <NdkMediaFormat.h>
23 #include <NdkMediaMuxer.h>
24 #include <fcntl.h>
25 #include <jni.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 
29 #include <cmath>
30 #include <cstring>
31 #include <fstream>
32 #include <map>
33 #include <vector>
34 
35 #include "NativeMediaCommon.h"
36 
37 /**
38  * MuxerNativeTestHelper breaks a media file to elements that a muxer can use to rebuild its clone.
39  * While testing muxer, if the test doesn't use MediaCodecs class to generate elementary stream,
40  * but uses MediaExtractor, this class will be handy
41  */
42 class MuxerNativeTestHelper {
43   public:
MuxerNativeTestHelper(const char * srcPath,const char * mime=nullptr,int frameLimit=-1)44     explicit MuxerNativeTestHelper(const char* srcPath, const char* mime = nullptr,
45                                    int frameLimit = -1)
46         : mSrcPath(srcPath), mMime(mime), mTrackCount(0), mBuffer(nullptr) {
47         mFrameLimit = frameLimit < 0 ? INT_MAX : frameLimit;
48         splitMediaToMuxerParameters();
49     }
50 
~MuxerNativeTestHelper()51     ~MuxerNativeTestHelper() {
52         for (auto format : mFormat) AMediaFormat_delete(format);
53         delete[] mBuffer;
54         for (const auto& buffInfoTrack : mBufferInfo) {
55             for (auto info : buffInfoTrack) delete info;
56         }
57     }
58 
getTrackCount()59     int getTrackCount() { return mTrackCount; }
60 
61     bool registerTrack(AMediaMuxer* muxer);
62 
63     bool insertSampleData(AMediaMuxer* muxer);
64 
65     bool muxMedia(AMediaMuxer* muxer);
66 
67     bool combineMedias(AMediaMuxer* muxer, MuxerNativeTestHelper* that, const int* repeater);
68 
69     bool isSubsetOf(MuxerNativeTestHelper* that);
70 
71     void offsetTimeStamp(int trackID, long tsOffset, int sampleOffset);
72 
73   private:
74     void splitMediaToMuxerParameters();
75 
76     static const int STTS_TOLERANCE_US = 100;
77     const char* mSrcPath;
78     const char* mMime;
79     int mTrackCount;
80     std::vector<AMediaFormat*> mFormat;
81     uint8_t* mBuffer;
82     std::vector<std::vector<AMediaCodecBufferInfo*>> mBufferInfo;
83     std::map<int, int> mInpIndexMap;
84     std::vector<int> mTrackIdxOrder;
85     int mFrameLimit;
86     // combineMedias() uses local version of this variable
87     std::map<int, int> mOutIndexMap;
88 };
89 
splitMediaToMuxerParameters()90 void MuxerNativeTestHelper::splitMediaToMuxerParameters() {
91     FILE* ifp = fopen(mSrcPath, "rbe");
92     int fd;
93     int fileSize;
94     if (ifp) {
95         struct stat buf {};
96         stat(mSrcPath, &buf);
97         fileSize = buf.st_size;
98         fd = fileno(ifp);
99     } else {
100         return;
101     }
102     AMediaExtractor* extractor = AMediaExtractor_new();
103     if (extractor == nullptr) {
104         fclose(ifp);
105         return;
106     }
107     // Set up MediaExtractor to read from the source.
108     media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, 0, fileSize);
109     if (AMEDIA_OK != status) {
110         AMediaExtractor_delete(extractor);
111         fclose(ifp);
112         return;
113     }
114 
115     // Set up MediaFormat
116     int index = 0;
117     for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
118         AMediaExtractor_selectTrack(extractor, trackID);
119         AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
120         if (mMime == nullptr) {
121             mTrackCount++;
122             mFormat.push_back(format);
123             mInpIndexMap[trackID] = index++;
124         } else {
125             const char* mime;
126             bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
127             if (hasKey && !strcmp(mime, mMime)) {
128                 mTrackCount++;
129                 mFormat.push_back(format);
130                 mInpIndexMap[trackID] = index;
131                 break;
132             } else {
133                 AMediaFormat_delete(format);
134                 AMediaExtractor_unselectTrack(extractor, trackID);
135             }
136         }
137     }
138 
139     if (mTrackCount <= 0) {
140         AMediaExtractor_delete(extractor);
141         fclose(ifp);
142         return;
143     }
144 
145     // Set up location for elementary stream
146     int bufferSize = ((fileSize + 127) >> 7) << 7;
147     // Ideally, Sum of return values of extractor.readSampleData(...) should not exceed
148     // source file size. But in case of Vorbis, aosp extractor appends an additional 4 bytes to
149     // the data at every readSampleData() call. bufferSize <<= 1 empirically large enough to
150     // hold the excess 4 bytes per read call
151     bufferSize <<= 1;
152     mBuffer = new uint8_t[bufferSize];
153     if (mBuffer == nullptr) {
154         mTrackCount = 0;
155         AMediaExtractor_delete(extractor);
156         fclose(ifp);
157         return;
158     }
159 
160     // Let MediaExtractor do its thing
161     bool sawEOS = false;
162     int frameCount = 0;
163     int offset = 0;
164     mBufferInfo.resize(mTrackCount);
165     while (!sawEOS && frameCount < mFrameLimit) {
166         auto* bufferInfo = new AMediaCodecBufferInfo();
167         bufferInfo->offset = offset;
168         bufferInfo->size =
169                 AMediaExtractor_readSampleData(extractor, mBuffer + offset, (bufferSize - offset));
170         if (bufferInfo->size < 0) {
171             sawEOS = true;
172         } else {
173             bufferInfo->presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
174             bufferInfo->flags = AMediaExtractor_getSampleFlags(extractor);
175             int trackID = AMediaExtractor_getSampleTrackIndex(extractor);
176             mTrackIdxOrder.push_back(trackID);
177             mBufferInfo[(mInpIndexMap.at(trackID))].push_back(bufferInfo);
178             AMediaExtractor_advance(extractor);
179             frameCount++;
180         }
181         offset += bufferInfo->size;
182     }
183 
184     AMediaExtractor_delete(extractor);
185     fclose(ifp);
186 }
187 
registerTrack(AMediaMuxer * muxer)188 bool MuxerNativeTestHelper::registerTrack(AMediaMuxer* muxer) {
189     for (int trackID = 0; trackID < mTrackCount; trackID++) {
190         int dstIndex = AMediaMuxer_addTrack(muxer, mFormat[trackID]);
191         if (dstIndex < 0) return false;
192         mOutIndexMap[trackID] = dstIndex;
193     }
194     return true;
195 }
196 
insertSampleData(AMediaMuxer * muxer)197 bool MuxerNativeTestHelper::insertSampleData(AMediaMuxer* muxer) {
198     // write all registered tracks in interleaved order
199     int* frameCount = new int[mTrackCount]{0};
200     for (int trackID : mTrackIdxOrder) {
201         int index = mInpIndexMap.at(trackID);
202         AMediaCodecBufferInfo* info = mBufferInfo[index][frameCount[index]];
203         if (AMediaMuxer_writeSampleData(muxer, mOutIndexMap.at(index), mBuffer, info) !=
204             AMEDIA_OK) {
205             delete[] frameCount;
206             return false;
207         }
208         ALOGV("Track: %d Timestamp: %d", trackID, (int)info->presentationTimeUs);
209         frameCount[index]++;
210     }
211     delete[] frameCount;
212     ALOGV("Total track samples %d", (int)mTrackIdxOrder.size());
213     return true;
214 }
215 
muxMedia(AMediaMuxer * muxer)216 bool MuxerNativeTestHelper::muxMedia(AMediaMuxer* muxer) {
217     return (registerTrack(muxer) && (AMediaMuxer_start(muxer) == AMEDIA_OK) &&
218             insertSampleData(muxer) && (AMediaMuxer_stop(muxer) == AMEDIA_OK));
219 }
220 
combineMedias(AMediaMuxer * muxer,MuxerNativeTestHelper * that,const int * repeater)221 bool MuxerNativeTestHelper::combineMedias(AMediaMuxer* muxer, MuxerNativeTestHelper* that,
222                                           const int* repeater) {
223     if (that == nullptr) return false;
224     if (repeater == nullptr) return false;
225 
226     // register tracks
227     int totalTracksToAdd = repeater[0] * this->mTrackCount + repeater[1] * that->mTrackCount;
228     int outIndexMap[totalTracksToAdd];
229     MuxerNativeTestHelper* group[2]{this, that};
230     for (int k = 0, idx = 0; k < 2; k++) {
231         for (int j = 0; j < repeater[k]; j++) {
232             for (AMediaFormat* format : group[k]->mFormat) {
233                 int dstIndex = AMediaMuxer_addTrack(muxer, format);
234                 if (dstIndex < 0) return false;
235                 outIndexMap[idx++] = dstIndex;
236             }
237         }
238     }
239     // start
240     if (AMediaMuxer_start(muxer) != AMEDIA_OK) return false;
241     // write sample data
242     // write all registered tracks in planar order viz all samples of a track A then all
243     // samples of track B, ...
244     for (int k = 0, idx = 0; k < 2; k++) {
245         for (int j = 0; j < repeater[k]; j++) {
246             for (int i = 0; i < group[k]->mTrackCount; i++) {
247                 for (int p = 0; p < group[k]->mBufferInfo[i].size(); p++) {
248                     AMediaCodecBufferInfo* info = group[k]->mBufferInfo[i][p];
249                     if (AMediaMuxer_writeSampleData(muxer, outIndexMap[idx], group[k]->mBuffer,
250                                                     info) != AMEDIA_OK) {
251                         return false;
252                     }
253                     ALOGV("Track: %d Timestamp: %d", outIndexMap[idx],
254                           (int)info->presentationTimeUs);
255                 }
256                 idx++;
257             }
258         }
259     }
260     // stop
261     return (AMediaMuxer_stop(muxer) == AMEDIA_OK);
262 }
263 
264 // returns true if 'this' stream is a subset of 'o'. That is all tracks in current media
265 // stream are present in ref media stream
isSubsetOf(MuxerNativeTestHelper * that)266 bool MuxerNativeTestHelper::isSubsetOf(MuxerNativeTestHelper* that) {
267     if (this == that) return true;
268     if (that == nullptr) return false;
269 
270     for (int i = 0; i < mTrackCount; i++) {
271         AMediaFormat* thisFormat = mFormat[i];
272         const char* thisMime = nullptr;
273         AMediaFormat_getString(thisFormat, AMEDIAFORMAT_KEY_MIME, &thisMime);
274         int j = 0;
275         for (; j < that->mTrackCount; j++) {
276             AMediaFormat* thatFormat = that->mFormat[j];
277             const char* thatMime = nullptr;
278             AMediaFormat_getString(thatFormat, AMEDIAFORMAT_KEY_MIME, &thatMime);
279             if (thisMime != nullptr && thatMime != nullptr && !strcmp(thisMime, thatMime)) {
280                 if (!isCSDIdentical(thisFormat, thatFormat)) continue;
281                 if (mBufferInfo[i].size() == that->mBufferInfo[j].size()) {
282                     int tolerance =
283                             !strncmp(thisMime, "video/", strlen("video/")) ? STTS_TOLERANCE_US : 0;
284                     tolerance += 1; // rounding error
285                     int k = 0;
286                     for (; k < mBufferInfo[i].size(); k++) {
287                         AMediaCodecBufferInfo* thisInfo = mBufferInfo[i][k];
288                         AMediaCodecBufferInfo* thatInfo = that->mBufferInfo[j][k];
289                         if (thisInfo->flags != thatInfo->flags) {
290                             break;
291                         }
292                         if (thisInfo->size != thatInfo->size) {
293                             break;
294                         } else if (memcmp(mBuffer + thisInfo->offset,
295                                           that->mBuffer + thatInfo->offset, thisInfo->size)) {
296                             break;
297                         }
298                         if (abs(thisInfo->presentationTimeUs - thatInfo->presentationTimeUs) >
299                             tolerance) {
300                             break;
301                         }
302                     }
303                     if (k == mBufferInfo[i].size()) break;
304                 }
305             }
306         }
307         if (j == that->mTrackCount) {
308             ALOGV("For mime %s, Couldn't find a match", thisMime);
309             return false;
310         }
311     }
312     return true;
313 }
314 
offsetTimeStamp(int trackID,long tsOffset,int sampleOffset)315 void MuxerNativeTestHelper::offsetTimeStamp(int trackID, long tsOffset, int sampleOffset) {
316     // offset pts of samples from index sampleOffset till the end by tsOffset
317     if (trackID < mTrackCount) {
318         for (int i = sampleOffset; i < mBufferInfo[trackID].size(); i++) {
319             AMediaCodecBufferInfo* info = mBufferInfo[trackID][i];
320             info->presentationTimeUs += tsOffset;
321         }
322     }
323 }
324 
isCodecContainerPairValid(MuxerFormat format,const char * mime)325 static bool isCodecContainerPairValid(MuxerFormat format, const char* mime) {
326     static const std::map<MuxerFormat, std::vector<const char*>> codecListforType = {
327             {OUTPUT_FORMAT_MPEG_4,
328              {AMEDIA_MIMETYPE_VIDEO_MPEG4, AMEDIA_MIMETYPE_VIDEO_H263, AMEDIA_MIMETYPE_VIDEO_AVC,
329               AMEDIA_MIMETYPE_VIDEO_HEVC, AMEDIA_MIMETYPE_AUDIO_AAC}},
330             {OUTPUT_FORMAT_WEBM,
331              {AMEDIA_MIMETYPE_VIDEO_VP8, AMEDIA_MIMETYPE_VIDEO_VP9, AMEDIA_MIMETYPE_AUDIO_VORBIS,
332               AMEDIA_MIMETYPE_AUDIO_OPUS}},
333             {OUTPUT_FORMAT_THREE_GPP,
334              {AMEDIA_MIMETYPE_VIDEO_MPEG4, AMEDIA_MIMETYPE_VIDEO_H263, AMEDIA_MIMETYPE_VIDEO_AVC,
335               AMEDIA_MIMETYPE_AUDIO_AAC, AMEDIA_MIMETYPE_AUDIO_AMR_NB,
336               AMEDIA_MIMETYPE_AUDIO_AMR_WB}},
337             {OUTPUT_FORMAT_OGG, {AMEDIA_MIMETYPE_AUDIO_OPUS}},
338     };
339 
340     if (format == OUTPUT_FORMAT_MPEG_4 &&
341         strncmp(mime, "application/", strlen("application/")) == 0)
342         return true;
343 
344     auto it = codecListforType.find(format);
345     if (it != codecListforType.end())
346         for (auto it2 : it->second)
347             if (strcmp(it2, mime) == 0) return true;
348 
349     return false;
350 }
351 
nativeTestSetLocation(JNIEnv * env,jobject,jint jformat,jstring jsrcPath,jstring jdstPath)352 static jboolean nativeTestSetLocation(JNIEnv* env, jobject, jint jformat, jstring jsrcPath,
353                                       jstring jdstPath) {
354     bool isPass = true;
355     bool isGeoDataSupported;
356     const float atlanticLat = 14.59f;
357     const float atlanticLong = 28.67f;
358     const float tooFarNorth = 90.5f;
359     const float tooFarWest = -180.5f;
360     const float tooFarSouth = -90.5f;
361     const float tooFarEast = 180.5f;
362     const float annapurnaLat = 28.59f;
363     const float annapurnaLong = 83.82f;
364     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
365     FILE* ofp = fopen(cdstPath, "wbe+");
366     if (ofp) {
367         AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
368         media_status_t status = AMediaMuxer_setLocation(muxer, tooFarNorth, atlanticLong);
369         if (status == AMEDIA_OK) {
370             isPass = false;
371             ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarNorth, atlanticLong);
372         }
373         status = AMediaMuxer_setLocation(muxer, tooFarSouth, atlanticLong);
374         if (status == AMEDIA_OK) {
375             isPass = false;
376             ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarSouth, atlanticLong);
377         }
378         status = AMediaMuxer_setLocation(muxer, atlanticLat, tooFarWest);
379         if (status == AMEDIA_OK) {
380             isPass = false;
381             ALOGE("setLocation succeeds on bad args: (%f, %f)", atlanticLat, tooFarWest);
382         }
383         status = AMediaMuxer_setLocation(muxer, atlanticLat, tooFarEast);
384         if (status == AMEDIA_OK) {
385             isPass = false;
386             ALOGE("setLocation succeeds on bad args: (%f, %f)", atlanticLat, tooFarEast);
387         }
388         status = AMediaMuxer_setLocation(muxer, tooFarNorth, tooFarWest);
389         if (status == AMEDIA_OK) {
390             isPass = false;
391             ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarNorth, tooFarWest);
392         }
393         status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong);
394         isGeoDataSupported = (status == AMEDIA_OK);
395         if (isGeoDataSupported) {
396             status = AMediaMuxer_setLocation(muxer, annapurnaLat, annapurnaLong);
397             if (status != AMEDIA_OK) {
398                 isPass = false;
399                 ALOGE("setLocation fails on args: (%f, %f)", annapurnaLat, annapurnaLong);
400             }
401         } else {
402             isPass &= ((MuxerFormat)jformat != OUTPUT_FORMAT_MPEG_4 &&
403                        (MuxerFormat)jformat != OUTPUT_FORMAT_THREE_GPP);
404         }
405         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
406         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
407         if (mediaInfo->registerTrack(muxer) && AMediaMuxer_start(muxer) == AMEDIA_OK) {
408             status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong);
409             if (status == AMEDIA_OK) {
410                 isPass = false;
411                 ALOGE("setLocation succeeds after starting the muxer");
412             }
413             if (mediaInfo->insertSampleData(muxer) && AMediaMuxer_stop(muxer) == AMEDIA_OK) {
414                 status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong);
415                 if (status == AMEDIA_OK) {
416                     isPass = false;
417                     ALOGE("setLocation succeeds after stopping the muxer");
418                 }
419             } else {
420                 isPass = false;
421                 ALOGE("failed to writeSampleData or stop muxer");
422             }
423         } else {
424             isPass = false;
425             ALOGE("failed to addTrack or start muxer");
426         }
427         delete mediaInfo;
428         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
429         AMediaMuxer_delete(muxer);
430         fclose(ofp);
431     } else {
432         isPass = false;
433         ALOGE("failed to open output file %s", cdstPath);
434     }
435     env->ReleaseStringUTFChars(jdstPath, cdstPath);
436     return static_cast<jboolean>(isPass);
437 }
438 
nativeTestSetOrientationHint(JNIEnv * env,jobject,jint jformat,jstring jsrcPath,jstring jdstPath)439 static jboolean nativeTestSetOrientationHint(JNIEnv* env, jobject, jint jformat, jstring jsrcPath,
440                                              jstring jdstPath) {
441     bool isPass = true;
442     bool isOrientationSupported;
443     const int badRotation[] = {360, 45, -90};
444     const int oldRotation = 90;
445     const int currRotation = 180;
446     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
447     FILE* ofp = fopen(cdstPath, "wbe+");
448     if (ofp) {
449         AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
450         media_status_t status;
451         for (int degrees : badRotation) {
452             status = AMediaMuxer_setOrientationHint(muxer, degrees);
453             if (status == AMEDIA_OK) {
454                 isPass = false;
455                 ALOGE("setOrientationHint succeeds on bad args: %d", degrees);
456             }
457         }
458         status = AMediaMuxer_setOrientationHint(muxer, oldRotation);
459         isOrientationSupported = (status == AMEDIA_OK);
460         if (isOrientationSupported) {
461             status = AMediaMuxer_setOrientationHint(muxer, currRotation);
462             if (status != AMEDIA_OK) {
463                 isPass = false;
464                 ALOGE("setOrientationHint fails on args: %d", currRotation);
465             }
466         } else {
467             isPass &= ((MuxerFormat)jformat != OUTPUT_FORMAT_MPEG_4 &&
468                        (MuxerFormat)jformat != OUTPUT_FORMAT_THREE_GPP);
469         }
470         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
471         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
472         if (mediaInfo->registerTrack(muxer) && AMediaMuxer_start(muxer) == AMEDIA_OK) {
473             status = AMediaMuxer_setOrientationHint(muxer, currRotation);
474             if (status == AMEDIA_OK) {
475                 isPass = false;
476                 ALOGE("setOrientationHint succeeds after starting the muxer");
477             }
478             if (mediaInfo->insertSampleData(muxer) && AMediaMuxer_stop(muxer) == AMEDIA_OK) {
479                 status = AMediaMuxer_setOrientationHint(muxer, currRotation);
480                 if (status == AMEDIA_OK) {
481                     isPass = false;
482                     ALOGE("setOrientationHint succeeds after stopping the muxer");
483                 }
484             } else {
485                 isPass = false;
486                 ALOGE("failed to writeSampleData or stop muxer");
487             }
488         } else {
489             isPass = false;
490             ALOGE("failed to addTrack or start muxer");
491         }
492         delete mediaInfo;
493         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
494         AMediaMuxer_delete(muxer);
495         fclose(ofp);
496     } else {
497         isPass = false;
498         ALOGE("failed to open output file %s", cdstPath);
499     }
500     env->ReleaseStringUTFChars(jdstPath, cdstPath);
501     return static_cast<jboolean>(isPass);
502 }
503 
nativeTestMultiTrack(JNIEnv * env,jobject,jint jformat,jstring jsrcPathA,jstring jsrcPathB,jstring jrefPath,jstring jdstPath)504 static jboolean nativeTestMultiTrack(JNIEnv* env, jobject, jint jformat, jstring jsrcPathA,
505                                      jstring jsrcPathB, jstring jrefPath, jstring jdstPath) {
506     bool isPass = true;
507     const char* csrcPathA = env->GetStringUTFChars(jsrcPathA, nullptr);
508     const char* csrcPathB = env->GetStringUTFChars(jsrcPathB, nullptr);
509     auto* mediaInfoA = new MuxerNativeTestHelper(csrcPathA);
510     auto* mediaInfoB = new MuxerNativeTestHelper(csrcPathB);
511     if (mediaInfoA->getTrackCount() == 1 && mediaInfoB->getTrackCount() == 1) {
512         const char* crefPath = env->GetStringUTFChars(jrefPath, nullptr);
513         // number of times to repeat {mSrcFileA, mSrcFileB} in Output
514         int numTracks[][2]{{1, 1}, {2, 0}, {0, 2}, {1, 2}, {2, 1}};
515         // prepare reference
516         FILE* rfp = fopen(crefPath, "wbe+");
517         if (rfp) {
518             AMediaMuxer* muxer = AMediaMuxer_new(fileno(rfp), (OutputFormat)jformat);
519             bool muxStatus = mediaInfoA->combineMedias(muxer, mediaInfoB, numTracks[0]);
520             AMediaMuxer_delete(muxer);
521             fclose(rfp);
522             if (muxStatus) {
523                 auto* refInfo = new MuxerNativeTestHelper(crefPath);
524                 if (!mediaInfoA->isSubsetOf(refInfo) || !mediaInfoB->isSubsetOf(refInfo)) {
525                     isPass = false;
526                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing src A and src B "
527                           "failed", csrcPathA, csrcPathB, jformat);
528                 } else {
529                     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
530                     for (int i = 1; i < sizeof(numTracks) / sizeof(numTracks[0]) && isPass; i++) {
531                         FILE* ofp = fopen(cdstPath, "wbe+");
532                         if (ofp) {
533                             muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
534                             bool status =
535                                     mediaInfoA->combineMedias(muxer, mediaInfoB, numTracks[i]);
536                             AMediaMuxer_delete(muxer);
537                             fclose(ofp);
538                             if (status) {
539                                 auto* dstInfo = new MuxerNativeTestHelper(cdstPath);
540                                 if (!dstInfo->isSubsetOf(refInfo)) {
541                                     isPass = false;
542                                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing "
543                                           "src A: %d, src B: %d failed", csrcPathA, csrcPathB,
544                                           jformat, numTracks[i][0], numTracks[i][1]);
545                                 }
546                                 delete dstInfo;
547                             } else {
548                                 if ((MuxerFormat)jformat != OUTPUT_FORMAT_MPEG_4) {
549                                     isPass = false;
550                                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing "
551                                           "src A: %d, src B: %d failed", csrcPathA, csrcPathB,
552                                           jformat, numTracks[i][0], numTracks[i][1]);
553                                 }
554                             }
555                         } else {
556                             isPass = false;
557                             ALOGE("failed to open output file %s", cdstPath);
558                         }
559                     }
560                     env->ReleaseStringUTFChars(jdstPath, cdstPath);
561                 }
562                 delete refInfo;
563             } else {
564                 if ((MuxerFormat)jformat != OUTPUT_FORMAT_OGG) {
565                     isPass = false;
566                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing src A and src B "
567                           "failed", csrcPathA, csrcPathB, jformat);
568                 }
569             }
570         } else {
571             isPass = false;
572             ALOGE("failed to open reference output file %s", crefPath);
573         }
574         env->ReleaseStringUTFChars(jrefPath, crefPath);
575     } else {
576         isPass = false;
577         if (mediaInfoA->getTrackCount() != 1) {
578             ALOGE("error: file %s, track count exp/rec - %d/%d", csrcPathA, 1,
579                   mediaInfoA->getTrackCount());
580         }
581         if (mediaInfoB->getTrackCount() != 1) {
582             ALOGE("error: file %s, track count exp/rec - %d/%d", csrcPathB, 1,
583                   mediaInfoB->getTrackCount());
584         }
585     }
586     env->ReleaseStringUTFChars(jsrcPathA, csrcPathA);
587     env->ReleaseStringUTFChars(jsrcPathB, csrcPathB);
588     delete mediaInfoA;
589     delete mediaInfoB;
590     return static_cast<jboolean>(isPass);
591 }
592 
nativeTestOffsetPts(JNIEnv * env,jobject,jint format,jstring jsrcPath,jstring jdstPath,jintArray joffsetIndices)593 static jboolean nativeTestOffsetPts(JNIEnv* env, jobject, jint format, jstring jsrcPath,
594                                     jstring jdstPath, jintArray joffsetIndices) {
595     bool isPass = true;
596     const int OFFSET_TS = 111000;
597     jsize len = env->GetArrayLength(joffsetIndices);
598     jint* coffsetIndices = env->GetIntArrayElements(joffsetIndices, nullptr);
599     const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
600     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
601     auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
602     if (mediaInfo->getTrackCount() != 0) {
603         for (int trackID = 0; trackID < mediaInfo->getTrackCount() && isPass; trackID++) {
604             for (int i = 0; i < len; i++) {
605                 mediaInfo->offsetTimeStamp(trackID, OFFSET_TS, coffsetIndices[i]);
606             }
607             FILE* ofp = fopen(cdstPath, "wbe+");
608             if (ofp) {
609                 AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)format);
610                 mediaInfo->muxMedia(muxer);
611                 AMediaMuxer_delete(muxer);
612                 fclose(ofp);
613                 auto* outInfo = new MuxerNativeTestHelper(cdstPath);
614                 isPass = mediaInfo->isSubsetOf(outInfo);
615                 if (!isPass) {
616                     ALOGE("Validation failed after adding timestamp offset to track %d", trackID);
617                 }
618                 delete outInfo;
619             } else {
620                 isPass = false;
621                 ALOGE("failed to open output file %s", cdstPath);
622             }
623             for (int i = len - 1; i >= 0; i--) {
624                 mediaInfo->offsetTimeStamp(trackID, -OFFSET_TS, coffsetIndices[i]);
625             }
626         }
627     } else {
628         isPass = false;
629         ALOGE("no valid track found in input file %s", csrcPath);
630     }
631     env->ReleaseStringUTFChars(jdstPath, cdstPath);
632     env->ReleaseStringUTFChars(jsrcPath, csrcPath);
633     env->ReleaseIntArrayElements(joffsetIndices, coffsetIndices, 0);
634     delete mediaInfo;
635     return static_cast<jboolean>(isPass);
636 }
637 
nativeTestSimpleMux(JNIEnv * env,jobject,jstring jsrcPath,jstring jdstPath,jstring jmime,jstring jselector)638 static jboolean nativeTestSimpleMux(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath,
639                                     jstring jmime, jstring jselector) {
640     bool isPass = true;
641     const char* cmime = env->GetStringUTFChars(jmime, nullptr);
642     const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
643     const char* cselector = env->GetStringUTFChars(jselector, nullptr);
644     auto* mediaInfo = new MuxerNativeTestHelper(csrcPath, cmime);
645     static const std::map<MuxerFormat, const char*> formatStringPair = {
646             {OUTPUT_FORMAT_MPEG_4, "mp4"},
647             {OUTPUT_FORMAT_WEBM, "webm"},
648             {OUTPUT_FORMAT_THREE_GPP, "3gp"},
649             {OUTPUT_FORMAT_HEIF, "heif"},
650             {OUTPUT_FORMAT_OGG, "ogg"}};
651     if (mediaInfo->getTrackCount() == 1) {
652         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
653         for (int fmt = OUTPUT_FORMAT_START; fmt <= OUTPUT_FORMAT_LIST_END && isPass; fmt++) {
654             auto it = formatStringPair.find((MuxerFormat)fmt);
655             if (it == formatStringPair.end() || strstr(cselector, it->second) == nullptr) {
656                 continue;
657             }
658             if (fmt == OUTPUT_FORMAT_WEBM) continue;  // TODO(b/146923551)
659             FILE* ofp = fopen(cdstPath, "wbe+");
660             if (ofp) {
661                 AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)fmt);
662                 bool muxStatus = mediaInfo->muxMedia(muxer);
663                 bool result = true;
664                 AMediaMuxer_delete(muxer);
665                 fclose(ofp);
666                 if (muxStatus) {
667                     auto* outInfo = new MuxerNativeTestHelper(cdstPath, cmime);
668                     result = mediaInfo->isSubsetOf(outInfo);
669                     delete outInfo;
670                 }
671                 if ((muxStatus && !result) ||
672                     (!muxStatus && isCodecContainerPairValid((MuxerFormat)fmt, cmime))) {
673                     isPass = false;
674                     ALOGE("error: file %s, mime %s, output != clone(input) for format %d", csrcPath,
675                           cmime, fmt);
676                 }
677             } else {
678                 isPass = false;
679                 ALOGE("error: file %s, mime %s, failed to open output file %s", csrcPath, cmime,
680                       cdstPath);
681             }
682         }
683         env->ReleaseStringUTFChars(jdstPath, cdstPath);
684     } else {
685         isPass = false;
686         ALOGE("error: file %s, mime %s, track count exp/rec - %d/%d", csrcPath, cmime, 1,
687               mediaInfo->getTrackCount());
688     }
689     env->ReleaseStringUTFChars(jselector, cselector);
690     env->ReleaseStringUTFChars(jsrcPath, csrcPath);
691     env->ReleaseStringUTFChars(jmime, cmime);
692     delete mediaInfo;
693     return static_cast<jboolean>(isPass);
694 }
695 
registerAndroidMediaV2CtsMuxerTestApi(JNIEnv * env)696 int registerAndroidMediaV2CtsMuxerTestApi(JNIEnv* env) {
697     const JNINativeMethod methodTable[] = {
698             {"nativeTestSetOrientationHint", "(ILjava/lang/String;Ljava/lang/String;)Z",
699              (void*)nativeTestSetOrientationHint},
700             {"nativeTestSetLocation", "(ILjava/lang/String;Ljava/lang/String;)Z",
701              (void*)nativeTestSetLocation},
702     };
703     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestApi");
704     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
705 }
706 
registerAndroidMediaV2CtsMuxerTestMultiTrack(JNIEnv * env)707 int registerAndroidMediaV2CtsMuxerTestMultiTrack(JNIEnv* env) {
708     const JNINativeMethod methodTable[] = {
709             {"nativeTestMultiTrack",
710              "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
711              (void*)nativeTestMultiTrack},
712     };
713     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestMultiTrack");
714     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
715 }
716 
registerAndroidMediaV2CtsMuxerTestOffsetPts(JNIEnv * env)717 int registerAndroidMediaV2CtsMuxerTestOffsetPts(JNIEnv* env) {
718     const JNINativeMethod methodTable[] = {
719             {"nativeTestOffsetPts", "(ILjava/lang/String;Ljava/lang/String;[I)Z",
720              (void*)nativeTestOffsetPts},
721     };
722     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestOffsetPts");
723     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
724 }
725 
registerAndroidMediaV2CtsMuxerTestSimpleMux(JNIEnv * env)726 int registerAndroidMediaV2CtsMuxerTestSimpleMux(JNIEnv* env) {
727     const JNINativeMethod methodTable[] = {
728             {"nativeTestSimpleMux",
729              "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
730              (void*)nativeTestSimpleMux},
731     };
732     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestSimpleMux");
733     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
734 }
735 
736 extern int registerAndroidMediaV2CtsMuxerUnitTestApi(JNIEnv* env);
737 
JNI_OnLoad(JavaVM * vm,void *)738 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
739     JNIEnv* env;
740     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
741     if (registerAndroidMediaV2CtsMuxerTestApi(env) != JNI_OK) return JNI_ERR;
742     if (registerAndroidMediaV2CtsMuxerTestMultiTrack(env) != JNI_OK) return JNI_ERR;
743     if (registerAndroidMediaV2CtsMuxerTestOffsetPts(env) != JNI_OK) return JNI_ERR;
744     if (registerAndroidMediaV2CtsMuxerTestSimpleMux(env) != JNI_OK) return JNI_ERR;
745     if (registerAndroidMediaV2CtsMuxerUnitTestApi(env) != JNI_OK) return JNI_ERR;
746     return JNI_VERSION_1_6;
747 }