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 #include <dlfcn.h>
29 
30 #include <cmath>
31 #include <cstring>
32 #include <fstream>
33 #include <map>
34 #include <vector>
35 
36 #include "NativeMediaCommon.h"
37 
38 // Transcoding arrived in Android 12/S, which is api 31.
39 #define __TRANSCODING_MIN_API__ 31
40 
41 /**
42  * MuxerNativeTestHelper breaks a media file to elements that a muxer can use to rebuild its clone.
43  * While testing muxer, if the test doesn't use MediaCodecs class to generate elementary stream,
44  * but uses MediaExtractor, this class will be handy
45  */
46 class MuxerNativeTestHelper {
47   public:
MuxerNativeTestHelper(const char * srcPath,const char * mime=nullptr,int frameLimit=-1)48     explicit MuxerNativeTestHelper(const char* srcPath, const char* mime = nullptr,
49                                    int frameLimit = -1)
50         : mSrcPath(srcPath), mMime(mime), mTrackCount(0), mBuffer(nullptr) {
51         mFrameLimit = frameLimit < 0 ? INT_MAX : frameLimit;
52         splitMediaToMuxerParameters();
53     }
54 
~MuxerNativeTestHelper()55     ~MuxerNativeTestHelper() {
56         for (auto format : mFormat) AMediaFormat_delete(format);
57         delete[] mBuffer;
58         for (const auto& buffInfoTrack : mBufferInfo) {
59             for (auto info : buffInfoTrack) delete info;
60         }
61     }
62 
getTrackCount()63     int getTrackCount() { return mTrackCount; }
64 
getSampleCount(size_t trackId)65     size_t getSampleCount(size_t trackId) {
66         if (trackId >= mTrackCount) {
67             return 0;
68         }
69         return mBufferInfo[(mInpIndexMap.at(trackId))].size();
70     }
71 
getTrackFormat(size_t trackId)72     AMediaFormat* getTrackFormat(size_t trackId) {
73         if (trackId >= mTrackCount) {
74             return nullptr;
75         }
76         return mFormat[trackId];
77     }
78 
79     bool registerTrack(AMediaMuxer* muxer);
80 
81     bool insertSampleData(AMediaMuxer* muxer);
82 
83     bool writeAFewSamplesData(AMediaMuxer* muxer, uint32_t fromIndex, uint32_t toIndex);
84 
85     bool writeAFewSamplesDataFromTime(AMediaMuxer* muxer, int64_t *fromTime,
86                                             uint32_t numSamples, bool lastSplit);
87 
88     bool muxMedia(AMediaMuxer* muxer);
89 
90     bool appendMedia(AMediaMuxer *muxer, uint32_t fromIndex, uint32_t toIndex);
91 
92     bool appendMediaFromTime(AMediaMuxer *muxer, int64_t *appendFromTime,
93                                     uint32_t numSamples, bool lastSplit);
94 
95     bool combineMedias(AMediaMuxer* muxer, MuxerNativeTestHelper* that, const int* repeater);
96 
97     bool isSubsetOf(MuxerNativeTestHelper* that);
98 
99     void offsetTimeStamp(int64_t tsAudioOffsetUs, int64_t tsVideoOffsetUs, int sampleOffset);
100 
101   private:
102     void splitMediaToMuxerParameters();
103 
104     static const int STTS_TOLERANCE_US = 100;
105     const char* mSrcPath;
106     const char* mMime;
107     int mTrackCount;
108     std::vector<AMediaFormat*> mFormat;
109     uint8_t* mBuffer;
110     std::vector<std::vector<AMediaCodecBufferInfo*>> mBufferInfo;
111     std::map<int, int> mInpIndexMap;
112     std::vector<int> mTrackIdxOrder;
113     int mFrameLimit;
114     // combineMedias() uses local version of this variable
115     std::map<int, int> mOutIndexMap;
116 };
117 
splitMediaToMuxerParameters()118 void MuxerNativeTestHelper::splitMediaToMuxerParameters() {
119     FILE* ifp = fopen(mSrcPath, "rbe");
120     int fd;
121     int fileSize;
122     if (ifp) {
123         struct stat buf {};
124         stat(mSrcPath, &buf);
125         fileSize = buf.st_size;
126         fd = fileno(ifp);
127     } else {
128         return;
129     }
130     AMediaExtractor* extractor = AMediaExtractor_new();
131     if (extractor == nullptr) {
132         fclose(ifp);
133         return;
134     }
135     // Set up MediaExtractor to read from the source.
136     media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, 0, fileSize);
137     if (AMEDIA_OK != status) {
138         AMediaExtractor_delete(extractor);
139         fclose(ifp);
140         return;
141     }
142 
143     // Set up MediaFormat
144     int index = 0;
145     for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
146         AMediaExtractor_selectTrack(extractor, trackID);
147         AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
148         if (mMime == nullptr) {
149             mTrackCount++;
150             mFormat.push_back(format);
151             mInpIndexMap[trackID] = index++;
152         } else {
153             const char* mime;
154             bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
155             if (hasKey && !strcmp(mime, mMime)) {
156                 mTrackCount++;
157                 mFormat.push_back(format);
158                 mInpIndexMap[trackID] = index;
159                 break;
160             } else {
161                 AMediaFormat_delete(format);
162                 AMediaExtractor_unselectTrack(extractor, trackID);
163             }
164         }
165     }
166 
167     if (mTrackCount <= 0) {
168         AMediaExtractor_delete(extractor);
169         fclose(ifp);
170         return;
171     }
172 
173     // Set up location for elementary stream
174     int bufferSize = ((fileSize + 127) >> 7) << 7;
175     // Ideally, Sum of return values of extractor.readSampleData(...) should not exceed
176     // source file size. But in case of Vorbis, aosp extractor appends an additional 4 bytes to
177     // the data at every readSampleData() call. bufferSize <<= 1 empirically large enough to
178     // hold the excess 4 bytes per read call
179     bufferSize <<= 1;
180     mBuffer = new uint8_t[bufferSize];
181     if (mBuffer == nullptr) {
182         mTrackCount = 0;
183         AMediaExtractor_delete(extractor);
184         fclose(ifp);
185         return;
186     }
187 
188     // Let MediaExtractor do its thing
189     bool sawEOS = false;
190     int frameCount = 0;
191     int offset = 0;
192     mBufferInfo.resize(mTrackCount);
193     while (!sawEOS && frameCount < mFrameLimit) {
194         auto* bufferInfo = new AMediaCodecBufferInfo();
195         bufferInfo->offset = offset;
196         bufferInfo->size =
197                 AMediaExtractor_readSampleData(extractor, mBuffer + offset, (bufferSize - offset));
198         if (bufferInfo->size < 0) {
199             sawEOS = true;
200         } else {
201             bufferInfo->presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
202             bufferInfo->flags = AMediaExtractor_getSampleFlags(extractor);
203             int trackID = AMediaExtractor_getSampleTrackIndex(extractor);
204             mTrackIdxOrder.push_back(trackID);
205             mBufferInfo[(mInpIndexMap.at(trackID))].push_back(bufferInfo);
206             AMediaExtractor_advance(extractor);
207             frameCount++;
208         }
209         offset += bufferInfo->size;
210     }
211     ALOGV("frameCount:%d", frameCount);
212     AMediaExtractor_delete(extractor);
213     fclose(ifp);
214 }
215 
registerTrack(AMediaMuxer * muxer)216 bool MuxerNativeTestHelper::registerTrack(AMediaMuxer* muxer) {
217     for (int trackID = 0; trackID < mTrackCount; trackID++) {
218         int dstIndex = AMediaMuxer_addTrack(muxer, mFormat[trackID]);
219         if (dstIndex < 0) return false;
220         mOutIndexMap[trackID] = dstIndex;
221     }
222     return true;
223 }
224 
insertSampleData(AMediaMuxer * muxer)225 bool MuxerNativeTestHelper::insertSampleData(AMediaMuxer* muxer) {
226     // write all registered tracks in interleaved order
227     int* frameCount = new int[mTrackCount]{0};
228     for (int trackID : mTrackIdxOrder) {
229         int index = mInpIndexMap.at(trackID);
230         AMediaCodecBufferInfo* info = mBufferInfo[index][frameCount[index]];
231         if (AMediaMuxer_writeSampleData(muxer, mOutIndexMap.at(index), mBuffer, info) !=
232             AMEDIA_OK) {
233             delete[] frameCount;
234             return false;
235         }
236         ALOGV("Track: %d Timestamp: %" PRId64 "", trackID, info->presentationTimeUs);
237         frameCount[index]++;
238     }
239     delete[] frameCount;
240     ALOGV("Total track samples %zu", mTrackIdxOrder.size());
241     return true;
242 }
243 
writeAFewSamplesData(AMediaMuxer * muxer,uint32_t fromIndex,uint32_t toIndex)244 bool MuxerNativeTestHelper::writeAFewSamplesData(AMediaMuxer* muxer, uint32_t fromIndex,
245                                                         uint32_t toIndex) {
246     ALOGV("fromIndex:%u, toIndex:%u", fromIndex, toIndex);
247     // write all registered tracks in interleaved order
248     ALOGV("mTrackIdxOrder.size:%zu", mTrackIdxOrder.size());
249     if (fromIndex > toIndex || toIndex >= mTrackIdxOrder.size()) {
250         ALOGE("wrong index");
251         return false;
252     }
253     int* frameCount = new int[mTrackCount]{0};
254     for (int i = 0; i < fromIndex; ++i) {
255         ++frameCount[mInpIndexMap.at(mTrackIdxOrder[i])];
256     }
257     ALOGV("Initial samples skipped:");
258     for (int i = 0; i < mTrackCount; ++i) {
259         ALOGV("i:%d:%d", i, frameCount[i]);
260     }
261     for (int i = fromIndex; i <= toIndex; ++i) {
262         int trackID = mTrackIdxOrder[i];
263         int trackIndex = mInpIndexMap.at(trackID);
264         ALOGV("trackID:%d, trackIndex:%d, frameCount:%d", trackID, trackIndex,
265                         frameCount[trackIndex]);
266         AMediaCodecBufferInfo* info = mBufferInfo[trackIndex][frameCount[trackIndex]];
267         ALOGV("Got info offset:%d, size:%d", info->offset, info->size);
268         if (AMediaMuxer_writeSampleData(muxer, mOutIndexMap.at(trackIndex), mBuffer, info) !=
269             AMEDIA_OK) {
270             delete[] frameCount;
271             return false;
272         }
273         ALOGV("Track: %d Timestamp: %" PRId64 "", trackID, info->presentationTimeUs);
274         ++frameCount[trackIndex];
275     }
276     ALOGV("Last sample counts:");
277     for (int i = 0; i < mTrackCount; ++i) {
278         ALOGV("i:%d", frameCount[i]);
279     }
280     delete[] frameCount;
281     return true;
282 }
283 
writeAFewSamplesDataFromTime(AMediaMuxer * muxer,int64_t * fromTime,uint32_t numSamples,bool lastSplit)284 bool MuxerNativeTestHelper::writeAFewSamplesDataFromTime(AMediaMuxer* muxer, int64_t *fromTime,
285                                                             uint32_t numSamples, bool lastSplit) {
286     for(int tc = 0; tc < mTrackCount; ++tc) {
287         ALOGV("fromTime[%d]:%lld", tc, (long long)fromTime[tc]);
288     }
289     ALOGV("numSamples:%u", numSamples);
290     uint32_t samplesWritten = 0;
291     uint32_t i = 0;
292     int* frameCount = new int[mTrackCount]{0};
293     do {
294         int trackID = mTrackIdxOrder[i++];
295         int trackIndex = mInpIndexMap.at(trackID);
296         ALOGV("trackID:%d, trackIndex:%d, frameCount:%d", trackID, trackIndex,
297                                 frameCount[trackIndex]);
298         AMediaCodecBufferInfo* info = mBufferInfo[trackIndex][frameCount[trackIndex]];
299         ++frameCount[trackIndex];
300         ALOGV("Got info offset:%d, size:%d, PTS:%" PRId64 "", info->offset, info->size,
301                                     info->presentationTimeUs);
302         if (info->presentationTimeUs < fromTime[trackID]) {
303             ALOGV("skipped");
304             continue;
305         }
306         if (AMediaMuxer_writeSampleData(muxer, mOutIndexMap.at(trackIndex), mBuffer, info) !=
307             AMEDIA_OK) {
308             delete[] frameCount;
309             return false;
310         } else {
311             ++samplesWritten;
312         }
313     } while ((lastSplit) ? (i < mTrackIdxOrder.size()) : ((samplesWritten < numSamples) &&
314                 (i < mTrackIdxOrder.size())));
315     ALOGV("samplesWritten:%u", samplesWritten);
316 
317     delete[] frameCount;
318     return true;
319 }
320 
321 
muxMedia(AMediaMuxer * muxer)322 bool MuxerNativeTestHelper::muxMedia(AMediaMuxer* muxer) {
323     return (registerTrack(muxer) && (AMediaMuxer_start(muxer) == AMEDIA_OK) &&
324             insertSampleData(muxer) && (AMediaMuxer_stop(muxer) == AMEDIA_OK));
325 }
326 
appendMedia(AMediaMuxer * muxer,uint32_t fromIndex,uint32_t toIndex)327 bool MuxerNativeTestHelper::appendMedia(AMediaMuxer *muxer, uint32_t fromIndex, uint32_t toIndex) {
328     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
329         ALOGV("fromIndex:%u, toIndex:%u", fromIndex, toIndex);
330         if (fromIndex == 0) {
331             registerTrack(muxer);
332         } else {
333             size_t trackCount = AMediaMuxer_getTrackCount(muxer);
334             ALOGV("appendMedia:trackCount:%zu", trackCount);
335             for(size_t i = 0; i < trackCount; ++i) {
336                 ALOGV("track i:%zu", i);
337                 ALOGV("%s", AMediaFormat_toString(AMediaMuxer_getTrackFormat(muxer, i)));
338                 ALOGV("%s", AMediaFormat_toString(mFormat[i]));
339                 for(size_t j = 0; j < mFormat.size(); ++j) {
340                     const char* thatMime = nullptr;
341                     AMediaFormat_getString(AMediaMuxer_getTrackFormat(muxer, i),
342                                                 AMEDIAFORMAT_KEY_MIME, &thatMime);
343                     const char* thisMime = nullptr;
344                     AMediaFormat_getString(mFormat[j], AMEDIAFORMAT_KEY_MIME, &thisMime);
345                     ALOGV("strlen(thisMime)%zu", strlen(thisMime));
346                     if (strcmp(thatMime, thisMime) == 0) {
347                         ALOGV("appendMedia:i:%zu, j:%zu", i, j);
348                         mOutIndexMap[j]=i;
349                     }
350                 }
351             }
352         }
353         AMediaMuxer_start(muxer);
354         bool res = writeAFewSamplesData(muxer, fromIndex, toIndex);
355         AMediaMuxer_stop(muxer);
356         return res;
357     } else {
358         return false;
359     }
360 }
361 
appendMediaFromTime(AMediaMuxer * muxer,int64_t * appendFromTime,uint32_t numSamples,bool lastSplit)362 bool MuxerNativeTestHelper::appendMediaFromTime(AMediaMuxer *muxer, int64_t *appendFromTime,
363                                                         uint32_t numSamples, bool lastSplit) {
364     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
365         size_t trackCount = AMediaMuxer_getTrackCount(muxer);
366         ALOGV("appendMediaFromTime:trackCount:%zu", trackCount);
367         for(size_t i = 0; i < trackCount; ++i) {
368             ALOGV("track i:%zu", i);
369             ALOGV("%s", AMediaFormat_toString(AMediaMuxer_getTrackFormat(muxer, i)));
370             ALOGV("%s", AMediaFormat_toString(mFormat[i]));
371             for(size_t j = 0; j < mFormat.size(); ++j) {
372                 const char* thatMime = nullptr;
373                 AMediaFormat_getString(AMediaMuxer_getTrackFormat(muxer, i),
374                                             AMEDIAFORMAT_KEY_MIME, &thatMime);
375                 const char* thisMime = nullptr;
376                 AMediaFormat_getString(mFormat[j], AMEDIAFORMAT_KEY_MIME, &thisMime);
377                 ALOGV("strlen(thisMime)%zu", strlen(thisMime));
378                 if (strcmp(thatMime, thisMime) == 0) {
379                     ALOGV("appendMediaFromTime:i:%zu, j:%zu", i, j);
380                     mOutIndexMap[j]=i;
381                 }
382             }
383         }
384         AMediaMuxer_start(muxer);
385         bool res = writeAFewSamplesDataFromTime(muxer, appendFromTime, numSamples, lastSplit);
386         AMediaMuxer_stop(muxer);
387         return res;
388     } else {
389         return false;
390     }
391 }
combineMedias(AMediaMuxer * muxer,MuxerNativeTestHelper * that,const int * repeater)392 bool MuxerNativeTestHelper::combineMedias(AMediaMuxer* muxer, MuxerNativeTestHelper* that,
393                                           const int* repeater) {
394     if (that == nullptr) return false;
395     if (repeater == nullptr) return false;
396 
397     // register tracks
398     int totalTracksToAdd = repeater[0] * this->mTrackCount + repeater[1] * that->mTrackCount;
399     int outIndexMap[totalTracksToAdd];
400     MuxerNativeTestHelper* group[2]{this, that};
401     for (int k = 0, idx = 0; k < 2; k++) {
402         for (int j = 0; j < repeater[k]; j++) {
403             for (AMediaFormat* format : group[k]->mFormat) {
404                 int dstIndex = AMediaMuxer_addTrack(muxer, format);
405                 if (dstIndex < 0) return false;
406                 outIndexMap[idx++] = dstIndex;
407             }
408         }
409     }
410     // start
411     if (AMediaMuxer_start(muxer) != AMEDIA_OK) return false;
412     // write sample data
413     // write all registered tracks in planar order viz all samples of a track A then all
414     // samples of track B, ...
415     for (int k = 0, idx = 0; k < 2; k++) {
416         for (int j = 0; j < repeater[k]; j++) {
417             for (int i = 0; i < group[k]->mTrackCount; i++) {
418                 for (int p = 0; p < group[k]->mBufferInfo[i].size(); p++) {
419                     AMediaCodecBufferInfo* info = group[k]->mBufferInfo[i][p];
420                     if (AMediaMuxer_writeSampleData(muxer, outIndexMap[idx], group[k]->mBuffer,
421                                                     info) != AMEDIA_OK) {
422                         return false;
423                     }
424                     ALOGV("Track: %d Timestamp: %" PRId64 "", outIndexMap[idx],
425                           info->presentationTimeUs);
426                 }
427                 idx++;
428             }
429         }
430     }
431     // stop
432     return (AMediaMuxer_stop(muxer) == AMEDIA_OK);
433 }
434 
435 // returns true if 'this' stream is a subset of 'that'. That is all tracks in current media
436 // stream are present in ref media stream
isSubsetOf(MuxerNativeTestHelper * that)437 bool MuxerNativeTestHelper::isSubsetOf(MuxerNativeTestHelper* that) {
438     if (this == that) return true;
439     if (that == nullptr) return false;
440 
441     for (int i = 0; i < mTrackCount; i++) {
442         AMediaFormat* thisFormat = mFormat[i];
443         const char* thisMime = nullptr;
444         AMediaFormat_getString(thisFormat, AMEDIAFORMAT_KEY_MIME, &thisMime);
445         int tolerance = !strncmp(thisMime, "video/", strlen("video/")) ? STTS_TOLERANCE_US : 0;
446         tolerance += 1; // rounding error
447         int j = 0;
448         for (; j < that->mTrackCount; j++) {
449             AMediaFormat* thatFormat = that->mFormat[j];
450             const char* thatMime = nullptr;
451             AMediaFormat_getString(thatFormat, AMEDIAFORMAT_KEY_MIME, &thatMime);
452             if (thisMime != nullptr && thatMime != nullptr && !strcmp(thisMime, thatMime)) {
453                 if (!isFormatSimilar(thisFormat, thatFormat)) continue;
454                 if (mBufferInfo[i].size() <= that->mBufferInfo[j].size()) {
455                     int tolerance =
456                             !strncmp(thisMime, "video/", strlen("video/")) ? STTS_TOLERANCE_US : 0;
457                     tolerance += 1; // rounding error
458                     int k = 0;
459                     for (; k < mBufferInfo[i].size(); k++) {
460                         ALOGV("k:%d", k);
461                         AMediaCodecBufferInfo* thisInfo = mBufferInfo[i][k];
462                         AMediaCodecBufferInfo* thatInfo = that->mBufferInfo[j][k];
463                         if (thisInfo->flags != thatInfo->flags) {
464                             ALOGD("flags this:%u, that:%u", thisInfo->flags, thatInfo->flags);
465                             break;
466                         }
467                         if (thisInfo->size != thatInfo->size) {
468                             ALOGD("size  this:%d, that:%d", thisInfo->size, thatInfo->size);
469                             break;
470                         } else if (memcmp(mBuffer + thisInfo->offset,
471                                           that->mBuffer + thatInfo->offset, thisInfo->size)) {
472                             ALOGD("memcmp failed");
473                             break;
474                         }
475                         if (abs(thisInfo->presentationTimeUs - thatInfo->presentationTimeUs) >
476                             tolerance) {
477                             ALOGD("time this:%lld, that:%lld",
478                                     (long long)thisInfo->presentationTimeUs,
479                                     (long long)thatInfo->presentationTimeUs);
480                             break;
481                         }
482                     }
483                     if (k == mBufferInfo[i].size()) break;
484                 }
485             }
486         }
487         if (j == that->mTrackCount) {
488             AMediaFormat_getString(thisFormat, AMEDIAFORMAT_KEY_MIME, &thisMime);
489             ALOGV("For mime %s, Couldn't find a match", thisMime);
490             return false;
491         }
492     }
493     return true;
494 }
495 
offsetTimeStamp(int64_t tsAudioOffsetUs,int64_t tsVideoOffsetUs,int sampleOffset)496 void MuxerNativeTestHelper::offsetTimeStamp(int64_t tsAudioOffsetUs, int64_t tsVideoOffsetUs,
497                                             int sampleOffset) {
498     // offset pts of samples from index sampleOffset till the end by tsOffset for each audio and
499     // video track
500     for (int trackID = 0; trackID < mTrackCount; trackID++) {
501         int64_t tsOffsetUs = 0;
502         const char* thisMime = nullptr;
503         AMediaFormat_getString(mFormat[trackID], AMEDIAFORMAT_KEY_MIME, &thisMime);
504         if (thisMime != nullptr) {
505             if (strncmp(thisMime, "video/", strlen("video/")) == 0) {
506                 tsOffsetUs = tsVideoOffsetUs;
507             } else if (strncmp(thisMime, "audio/", strlen("audio/")) == 0) {
508                 tsOffsetUs = tsAudioOffsetUs;
509             }
510             for (int i = sampleOffset; i < mBufferInfo[trackID].size(); i++) {
511                 AMediaCodecBufferInfo *info = mBufferInfo[trackID][i];
512                 info->presentationTimeUs += tsOffsetUs;
513             }
514         }
515     }
516 }
517 
isCodecContainerPairValid(MuxerFormat format,const char * mime)518 static bool isCodecContainerPairValid(MuxerFormat format, const char* mime) {
519     static const std::map<MuxerFormat, std::vector<const char*>> codecListforType = {
520             {OUTPUT_FORMAT_MPEG_4,
521              {AMEDIA_MIMETYPE_VIDEO_MPEG4, AMEDIA_MIMETYPE_VIDEO_H263, AMEDIA_MIMETYPE_VIDEO_AVC,
522               AMEDIA_MIMETYPE_VIDEO_HEVC, AMEDIA_MIMETYPE_AUDIO_AAC}},
523             {OUTPUT_FORMAT_WEBM,
524              {AMEDIA_MIMETYPE_VIDEO_VP8, AMEDIA_MIMETYPE_VIDEO_VP9, AMEDIA_MIMETYPE_AUDIO_VORBIS,
525               AMEDIA_MIMETYPE_AUDIO_OPUS}},
526             {OUTPUT_FORMAT_THREE_GPP,
527              {AMEDIA_MIMETYPE_VIDEO_MPEG4, AMEDIA_MIMETYPE_VIDEO_H263, AMEDIA_MIMETYPE_VIDEO_AVC,
528               AMEDIA_MIMETYPE_AUDIO_AAC, AMEDIA_MIMETYPE_AUDIO_AMR_NB,
529               AMEDIA_MIMETYPE_AUDIO_AMR_WB}},
530             {OUTPUT_FORMAT_OGG, {AMEDIA_MIMETYPE_AUDIO_OPUS}},
531     };
532 
533     if (format == OUTPUT_FORMAT_MPEG_4 &&
534         strncmp(mime, "application/", strlen("application/")) == 0)
535         return true;
536 
537     auto it = codecListforType.find(format);
538     if (it != codecListforType.end())
539         for (auto it2 : it->second)
540             if (strcmp(it2, mime) == 0) return true;
541 
542     return false;
543 }
544 
nativeTestSetLocation(JNIEnv * env,jobject,jint jformat,jstring jsrcPath,jstring jdstPath)545 static jboolean nativeTestSetLocation(JNIEnv* env, jobject, jint jformat, jstring jsrcPath,
546                                       jstring jdstPath) {
547     bool isPass = true;
548     bool isGeoDataSupported;
549     const float atlanticLat = 14.59f;
550     const float atlanticLong = 28.67f;
551     const float tooFarNorth = 90.5f;
552     const float tooFarWest = -180.5f;
553     const float tooFarSouth = -90.5f;
554     const float tooFarEast = 180.5f;
555     const float annapurnaLat = 28.59f;
556     const float annapurnaLong = 83.82f;
557     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
558     FILE* ofp = fopen(cdstPath, "wbe+");
559     if (ofp) {
560         AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
561         media_status_t status = AMediaMuxer_setLocation(muxer, tooFarNorth, atlanticLong);
562         if (status == AMEDIA_OK) {
563             isPass = false;
564             ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarNorth, atlanticLong);
565         }
566         status = AMediaMuxer_setLocation(muxer, tooFarSouth, atlanticLong);
567         if (status == AMEDIA_OK) {
568             isPass = false;
569             ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarSouth, atlanticLong);
570         }
571         status = AMediaMuxer_setLocation(muxer, atlanticLat, tooFarWest);
572         if (status == AMEDIA_OK) {
573             isPass = false;
574             ALOGE("setLocation succeeds on bad args: (%f, %f)", atlanticLat, tooFarWest);
575         }
576         status = AMediaMuxer_setLocation(muxer, atlanticLat, tooFarEast);
577         if (status == AMEDIA_OK) {
578             isPass = false;
579             ALOGE("setLocation succeeds on bad args: (%f, %f)", atlanticLat, tooFarEast);
580         }
581         status = AMediaMuxer_setLocation(muxer, tooFarNorth, tooFarWest);
582         if (status == AMEDIA_OK) {
583             isPass = false;
584             ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarNorth, tooFarWest);
585         }
586         status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong);
587         isGeoDataSupported = (status == AMEDIA_OK);
588         if (isGeoDataSupported) {
589             status = AMediaMuxer_setLocation(muxer, annapurnaLat, annapurnaLong);
590             if (status != AMEDIA_OK) {
591                 isPass = false;
592                 ALOGE("setLocation fails on args: (%f, %f)", annapurnaLat, annapurnaLong);
593             }
594         } else {
595             isPass &= ((MuxerFormat)jformat != OUTPUT_FORMAT_MPEG_4 &&
596                        (MuxerFormat)jformat != OUTPUT_FORMAT_THREE_GPP);
597         }
598         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
599         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
600         if (mediaInfo->registerTrack(muxer) && AMediaMuxer_start(muxer) == AMEDIA_OK) {
601             status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong);
602             if (status == AMEDIA_OK) {
603                 isPass = false;
604                 ALOGE("setLocation succeeds after starting the muxer");
605             }
606             if (mediaInfo->insertSampleData(muxer) && AMediaMuxer_stop(muxer) == AMEDIA_OK) {
607                 status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong);
608                 if (status == AMEDIA_OK) {
609                     isPass = false;
610                     ALOGE("setLocation succeeds after stopping the muxer");
611                 }
612             } else {
613                 isPass = false;
614                 ALOGE("failed to writeSampleData or stop muxer");
615             }
616         } else {
617             isPass = false;
618             ALOGE("failed to addTrack or start muxer");
619         }
620         delete mediaInfo;
621         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
622         AMediaMuxer_delete(muxer);
623         fclose(ofp);
624     } else {
625         isPass = false;
626         ALOGE("failed to open output file %s", cdstPath);
627     }
628     env->ReleaseStringUTFChars(jdstPath, cdstPath);
629     return static_cast<jboolean>(isPass);
630 }
631 
nativeTestSetOrientationHint(JNIEnv * env,jobject,jint jformat,jstring jsrcPath,jstring jdstPath)632 static jboolean nativeTestSetOrientationHint(JNIEnv* env, jobject, jint jformat, jstring jsrcPath,
633                                              jstring jdstPath) {
634     bool isPass = true;
635     bool isOrientationSupported;
636     const int badRotation[] = {360, 45, -90};
637     const int oldRotation = 90;
638     const int currRotation = 180;
639     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
640     FILE* ofp = fopen(cdstPath, "wbe+");
641     if (ofp) {
642         AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
643         media_status_t status;
644         for (int degrees : badRotation) {
645             status = AMediaMuxer_setOrientationHint(muxer, degrees);
646             if (status == AMEDIA_OK) {
647                 isPass = false;
648                 ALOGE("setOrientationHint succeeds on bad args: %d", degrees);
649             }
650         }
651         status = AMediaMuxer_setOrientationHint(muxer, oldRotation);
652         isOrientationSupported = (status == AMEDIA_OK);
653         if (isOrientationSupported) {
654             status = AMediaMuxer_setOrientationHint(muxer, currRotation);
655             if (status != AMEDIA_OK) {
656                 isPass = false;
657                 ALOGE("setOrientationHint fails on args: %d", currRotation);
658             }
659         } else {
660             isPass &= ((MuxerFormat)jformat != OUTPUT_FORMAT_MPEG_4 &&
661                        (MuxerFormat)jformat != OUTPUT_FORMAT_THREE_GPP);
662         }
663         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
664         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
665         if (mediaInfo->registerTrack(muxer) && AMediaMuxer_start(muxer) == AMEDIA_OK) {
666             status = AMediaMuxer_setOrientationHint(muxer, currRotation);
667             if (status == AMEDIA_OK) {
668                 isPass = false;
669                 ALOGE("setOrientationHint succeeds after starting the muxer");
670             }
671             if (mediaInfo->insertSampleData(muxer) && AMediaMuxer_stop(muxer) == AMEDIA_OK) {
672                 status = AMediaMuxer_setOrientationHint(muxer, currRotation);
673                 if (status == AMEDIA_OK) {
674                     isPass = false;
675                     ALOGE("setOrientationHint succeeds after stopping the muxer");
676                 }
677             } else {
678                 isPass = false;
679                 ALOGE("failed to writeSampleData or stop muxer");
680             }
681         } else {
682             isPass = false;
683             ALOGE("failed to addTrack or start muxer");
684         }
685         delete mediaInfo;
686         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
687         AMediaMuxer_delete(muxer);
688         fclose(ofp);
689     } else {
690         isPass = false;
691         ALOGE("failed to open output file %s", cdstPath);
692     }
693     env->ReleaseStringUTFChars(jdstPath, cdstPath);
694     return static_cast<jboolean>(isPass);
695 }
696 
nativeTestMultiTrack(JNIEnv * env,jobject,jint jformat,jstring jsrcPathA,jstring jsrcPathB,jstring jrefPath,jstring jdstPath)697 static jboolean nativeTestMultiTrack(JNIEnv* env, jobject, jint jformat, jstring jsrcPathA,
698                                      jstring jsrcPathB, jstring jrefPath, jstring jdstPath) {
699     bool isPass = true;
700     const char* csrcPathA = env->GetStringUTFChars(jsrcPathA, nullptr);
701     const char* csrcPathB = env->GetStringUTFChars(jsrcPathB, nullptr);
702     auto* mediaInfoA = new MuxerNativeTestHelper(csrcPathA);
703     auto* mediaInfoB = new MuxerNativeTestHelper(csrcPathB);
704     if (mediaInfoA->getTrackCount() == 1 && mediaInfoB->getTrackCount() == 1) {
705         const char* crefPath = env->GetStringUTFChars(jrefPath, nullptr);
706         // number of times to repeat {mSrcFileA, mSrcFileB} in Output
707         // values should be in sync with testMultiTrack
708         static const int numTracks[][2] = {{1, 1}, {2, 0}, {0, 2}, {1, 2}, {2, 1}, {2, 2}};
709         // prepare reference
710         FILE* rfp = fopen(crefPath, "wbe+");
711         if (rfp) {
712             AMediaMuxer* muxer = AMediaMuxer_new(fileno(rfp), (OutputFormat)jformat);
713             bool muxStatus = mediaInfoA->combineMedias(muxer, mediaInfoB, numTracks[0]);
714             AMediaMuxer_delete(muxer);
715             fclose(rfp);
716             if (muxStatus) {
717                 auto* refInfo = new MuxerNativeTestHelper(crefPath);
718                 if (!mediaInfoA->isSubsetOf(refInfo) || !mediaInfoB->isSubsetOf(refInfo)) {
719                     isPass = false;
720                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing src A and src B "
721                           "failed", csrcPathA, csrcPathB, jformat);
722                 } else {
723                     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
724                     for (int i = 1; i < sizeof(numTracks) / sizeof(numTracks[0]) && isPass; i++) {
725                         FILE* ofp = fopen(cdstPath, "wbe+");
726                         if (ofp) {
727                             muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
728                             bool status =
729                                     mediaInfoA->combineMedias(muxer, mediaInfoB, numTracks[i]);
730                             AMediaMuxer_delete(muxer);
731                             fclose(ofp);
732                             if (status) {
733                                 auto* dstInfo = new MuxerNativeTestHelper(cdstPath);
734                                 if (!dstInfo->isSubsetOf(refInfo)) {
735                                     isPass = false;
736                                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing "
737                                           "src A: %d, src B: %d failed", csrcPathA, csrcPathB,
738                                           jformat, numTracks[i][0], numTracks[i][1]);
739                                 }
740                                 delete dstInfo;
741                             } else {
742                                 if ((MuxerFormat)jformat != OUTPUT_FORMAT_MPEG_4) {
743                                     isPass = false;
744                                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing "
745                                           "src A: %d, src B: %d failed", csrcPathA, csrcPathB,
746                                           jformat, numTracks[i][0], numTracks[i][1]);
747                                 }
748                             }
749                         } else {
750                             isPass = false;
751                             ALOGE("failed to open output file %s", cdstPath);
752                         }
753                     }
754                     env->ReleaseStringUTFChars(jdstPath, cdstPath);
755                 }
756                 delete refInfo;
757             } else {
758                 if ((MuxerFormat)jformat != OUTPUT_FORMAT_OGG) {
759                     isPass = false;
760                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing src A and src B "
761                           "failed", csrcPathA, csrcPathB, jformat);
762                 }
763             }
764         } else {
765             isPass = false;
766             ALOGE("failed to open reference output file %s", crefPath);
767         }
768         env->ReleaseStringUTFChars(jrefPath, crefPath);
769     } else {
770         isPass = false;
771         if (mediaInfoA->getTrackCount() != 1) {
772             ALOGE("error: file %s, track count exp/rec - %d/%d", csrcPathA, 1,
773                   mediaInfoA->getTrackCount());
774         }
775         if (mediaInfoB->getTrackCount() != 1) {
776             ALOGE("error: file %s, track count exp/rec - %d/%d", csrcPathB, 1,
777                   mediaInfoB->getTrackCount());
778         }
779     }
780     env->ReleaseStringUTFChars(jsrcPathA, csrcPathA);
781     env->ReleaseStringUTFChars(jsrcPathB, csrcPathB);
782     delete mediaInfoA;
783     delete mediaInfoB;
784     return static_cast<jboolean>(isPass);
785 }
786 
nativeTestOffsetPts(JNIEnv * env,jobject,jint format,jstring jsrcPath,jstring jdstPath,jintArray joffsetIndices)787 static jboolean nativeTestOffsetPts(JNIEnv* env, jobject, jint format, jstring jsrcPath,
788                                     jstring jdstPath, jintArray joffsetIndices) {
789     bool isPass = true;
790     // values should be in sync with testOffsetPresentationTime
791     static const int64_t OFFSET_TS_AUDIO_US[4] = {-23220LL, 0LL, 200000LL, 400000LL};
792     static const int64_t OFFSET_TS_VIDEO_US[3] = {0LL, 200000LL, 400000LL};
793     jsize len = env->GetArrayLength(joffsetIndices);
794     jint* coffsetIndices = env->GetIntArrayElements(joffsetIndices, nullptr);
795     const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
796     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
797     auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
798     if (mediaInfo->getTrackCount() != 0) {
799         for (int64_t audioOffsetUs : OFFSET_TS_AUDIO_US) {
800             for (int64_t videoOffsetUs : OFFSET_TS_VIDEO_US) {
801                 for (int i = 0; i < len; i++) {
802                     mediaInfo->offsetTimeStamp(audioOffsetUs, videoOffsetUs, coffsetIndices[i]);
803                 }
804                 FILE *ofp = fopen(cdstPath, "wbe+");
805                 if (ofp) {
806                     AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat) format);
807                     mediaInfo->muxMedia(muxer);
808                     AMediaMuxer_delete(muxer);
809                     fclose(ofp);
810                     auto *outInfo = new MuxerNativeTestHelper(cdstPath);
811                     isPass = mediaInfo->isSubsetOf(outInfo);
812                     if (!isPass) {
813                         ALOGE("Validation failed after adding timestamp offsets audio: %lld,"
814                               " video: %lld", (long long) audioOffsetUs, (long long) videoOffsetUs);
815                     }
816                     delete outInfo;
817                 } else {
818                     isPass = false;
819                     ALOGE("failed to open output file %s", cdstPath);
820                 }
821                 for (int i = len - 1; i >= 0; i--) {
822                     mediaInfo->offsetTimeStamp(-audioOffsetUs, -videoOffsetUs, coffsetIndices[i]);
823                 }
824             }
825         }
826     } else {
827         isPass = false;
828         ALOGE("no valid track found in input file %s", csrcPath);
829     }
830     env->ReleaseStringUTFChars(jdstPath, cdstPath);
831     env->ReleaseStringUTFChars(jsrcPath, csrcPath);
832     env->ReleaseIntArrayElements(joffsetIndices, coffsetIndices, 0);
833     delete mediaInfo;
834     return static_cast<jboolean>(isPass);
835 }
836 
nativeTestSimpleMux(JNIEnv * env,jobject,jstring jsrcPath,jstring jdstPath,jstring jmime,jstring jselector)837 static jboolean nativeTestSimpleMux(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath,
838                                     jstring jmime, jstring jselector) {
839     bool isPass = true;
840     const char* cmime = env->GetStringUTFChars(jmime, nullptr);
841     const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
842     const char* cselector = env->GetStringUTFChars(jselector, nullptr);
843     auto* mediaInfo = new MuxerNativeTestHelper(csrcPath, cmime);
844     static const std::map<MuxerFormat, const char*> formatStringPair = {
845             {OUTPUT_FORMAT_MPEG_4, "mp4"},
846             {OUTPUT_FORMAT_WEBM, "webm"},
847             {OUTPUT_FORMAT_THREE_GPP, "3gp"},
848             {OUTPUT_FORMAT_HEIF, "heif"},
849             {OUTPUT_FORMAT_OGG, "ogg"}};
850     if (mediaInfo->getTrackCount() == 1) {
851         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
852         for (int fmt = OUTPUT_FORMAT_START; fmt <= OUTPUT_FORMAT_LIST_END && isPass; fmt++) {
853             auto it = formatStringPair.find((MuxerFormat)fmt);
854             if (it == formatStringPair.end() || strstr(cselector, it->second) == nullptr) {
855                 continue;
856             }
857             if (fmt == OUTPUT_FORMAT_WEBM) continue;  // TODO(b/146923551)
858             FILE* ofp = fopen(cdstPath, "wbe+");
859             if (ofp) {
860                 AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)fmt);
861                 bool muxStatus = mediaInfo->muxMedia(muxer);
862                 bool result = true;
863                 AMediaMuxer_delete(muxer);
864                 fclose(ofp);
865                 if (muxStatus) {
866                     auto* outInfo = new MuxerNativeTestHelper(cdstPath, cmime);
867                     result = mediaInfo->isSubsetOf(outInfo);
868                     delete outInfo;
869                 }
870                 if ((muxStatus && !result) ||
871                     (!muxStatus && isCodecContainerPairValid((MuxerFormat)fmt, cmime))) {
872                     isPass = false;
873                     ALOGE("error: file %s, mime %s, output != clone(input) for format %d", csrcPath,
874                           cmime, fmt);
875                 }
876             } else {
877                 isPass = false;
878                 ALOGE("error: file %s, mime %s, failed to open output file %s", csrcPath, cmime,
879                       cdstPath);
880             }
881         }
882         env->ReleaseStringUTFChars(jdstPath, cdstPath);
883     } else {
884         isPass = false;
885         ALOGE("error: file %s, mime %s, track count exp/rec - %d/%d", csrcPath, cmime, 1,
886               mediaInfo->getTrackCount());
887     }
888     env->ReleaseStringUTFChars(jselector, cselector);
889     env->ReleaseStringUTFChars(jsrcPath, csrcPath);
890     env->ReleaseStringUTFChars(jmime, cmime);
891     delete mediaInfo;
892     return static_cast<jboolean>(isPass);
893 }
894 
895 /* Check whether AMediaMuxer_getTrackCount works as expected.
896  */
nativeTestGetTrackCount(JNIEnv * env,jobject,jstring jsrcPath,jstring jdstPath,jint jformat,jint jtrackCount)897 static jboolean nativeTestGetTrackCount(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath,
898                                             jint jformat, jint jtrackCount) {
899     bool isPass = true;
900     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
901         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
902         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
903         FILE* ofp = fopen(cdstPath, "w+");
904         if (ofp) {
905             AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
906             if (muxer) {
907                 auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
908                 if (!mediaInfo->registerTrack(muxer)) {
909                     isPass = false;
910                     ALOGE("register track failed");
911                 }
912                 if (AMediaMuxer_getTrackCount(muxer) != jtrackCount) {
913                     isPass = false;
914                     ALOGE("track counts are not equal");
915                 }
916                 delete mediaInfo;
917                 AMediaMuxer_delete(muxer);
918             } else {
919                 isPass = false;
920                 ALOGE("Failed to create muxer");
921             }
922             fclose(ofp);
923         } else {
924             isPass = false;
925             ALOGE("file open error: file  %s", csrcPath);
926         }
927         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
928         env->ReleaseStringUTFChars(jdstPath, cdstPath);
929     } else {
930         isPass = false;
931     }
932     return static_cast<jboolean>(isPass);
933 }
934 
935 /* Check whether AMediaMuxer_getTrackCount works as expected when the file is opened in
936  * append mode.
937  */
nativeTestAppendGetTrackCount(JNIEnv * env,jobject,jstring jsrcPath,jint jtrackCount)938 static jboolean nativeTestAppendGetTrackCount(JNIEnv* env, jobject, jstring jsrcPath,
939                                                         jint jtrackCount) {
940     bool isPass = true;
941     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
942         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
943         for (unsigned int mode = AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP;
944                     mode <= AMEDIAMUXER_APPEND_TO_EXISTING_DATA; ++mode) {
945             ALOGV("mode:%u", mode);
946             FILE* ofp = fopen(csrcPath, "r");
947             if (ofp) {
948                 AMediaMuxer *muxer = AMediaMuxer_append(fileno(ofp), (AppendMode)mode);
949                 if (muxer) {
950                     ssize_t trackCount = AMediaMuxer_getTrackCount(muxer);
951                     if ( trackCount != jtrackCount) {
952                         isPass = false;
953                         ALOGE("trackcounts are not equal, trackCount:%ld vs jtrackCount:%d",
954                                     (long)trackCount, jtrackCount);
955                     }
956                     AMediaMuxer_delete(muxer);
957                 } else {
958                     isPass = false;
959                     ALOGE("Failed to create muxer");
960                 }
961                 fclose(ofp);
962                 ofp = nullptr;
963             } else {
964                 isPass = false;
965                 ALOGE("file open error: file  %s", csrcPath);
966             }
967         }
968         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
969     } else {
970         isPass = false;
971     }
972     return static_cast<jboolean>(isPass);
973 }
974 
975 /* Checks whether AMediaMuxer_getTrackFormat works as expected in muxer mode.
976  */
nativeTestGetTrackFormat(JNIEnv * env,jobject,jstring jsrcPath,jstring jdstPath,jint joutFormat)977 static jboolean nativeTestGetTrackFormat(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath,
978                                                     jint joutFormat) {
979     bool isPass = true;
980     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
981         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
982         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
983         FILE* ofp = fopen(cdstPath, "w+");
984         if (ofp) {
985             AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)joutFormat);
986             if (muxer) {
987                 auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
988                 if (!mediaInfo->registerTrack(muxer)) {
989                     isPass = false;
990                     ALOGE("register track failed");
991                 }
992                 for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) {
993                     if (!isFormatSimilar(mediaInfo->getTrackFormat(i),
994                             AMediaMuxer_getTrackFormat(muxer, i))) {
995                         isPass = false;
996                         ALOGE("track formats are not similar");
997                     }
998                 }
999                 delete mediaInfo;
1000                 AMediaMuxer_delete(muxer);
1001             } else {
1002                 isPass = false;
1003                 ALOGE("Failed to create muxer");
1004             }
1005             fclose(ofp);
1006         } else {
1007             isPass = false;
1008             ALOGE("file open error: file  %s", csrcPath);
1009         }
1010         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
1011         env->ReleaseStringUTFChars(jdstPath, cdstPath);
1012     } else {
1013         isPass = false;
1014     }
1015     return static_cast<jboolean>(isPass);
1016 }
1017 
1018 /* Checks whether AMediaMuxer_getTrackFormat works as expected when the file is opened in
1019  * append mode.
1020  */
nativeTestAppendGetTrackFormat(JNIEnv * env,jobject,jstring jsrcPath)1021 static jboolean nativeTestAppendGetTrackFormat(JNIEnv* env, jobject, jstring jsrcPath) {
1022     bool isPass = true;
1023     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
1024         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
1025         for (unsigned int mode = AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP;
1026                     mode <= AMEDIAMUXER_APPEND_TO_EXISTING_DATA; ++mode) {
1027             ALOGV("mode:%u", mode);
1028             FILE* ofp = fopen(csrcPath, "r");
1029             if (ofp) {
1030                 AMediaMuxer *muxer = AMediaMuxer_append(fileno(ofp), (AppendMode)mode);
1031                 if (muxer) {
1032                     auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
1033                     for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) {
1034                         if (!isFormatSimilar(mediaInfo->getTrackFormat(i),
1035                                 AMediaMuxer_getTrackFormat(muxer, i))) {
1036                             isPass = false;
1037                             ALOGE("track formats are not similar");
1038                         }
1039                     }
1040                     delete mediaInfo;
1041                     AMediaMuxer_delete(muxer);
1042                 } else {
1043                     isPass = false;
1044                     ALOGE("Failed to create muxer");
1045                 }
1046                 fclose(ofp);
1047             } else {
1048                 isPass = false;
1049                 ALOGE("file open error: file  %s", csrcPath);
1050             }
1051         }
1052         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
1053     }
1054     else {
1055         isPass = false;
1056     }
1057     return static_cast<jboolean>(isPass);
1058 }
1059 
1060 /*
1061  * Checks if appending media data to the end of existing media data in a file works good.
1062  * Mode : AMEDIAMUXER_APPEND_TO_EXISTING_DATA.  Splits the contents of source file equally
1063  * starting from one and increasing the number of splits by one for every iteration.  Starts
1064  * with writing first split into a new file and appends the rest of the contents split by split.
1065  */
nativeTestSimpleAppend(JNIEnv * env,jobject,jint joutFormat,jstring jsrcPath,jstring jdstPath)1066 static jboolean nativeTestSimpleAppend(JNIEnv* env, jobject, jint joutFormat, jstring jsrcPath,
1067                         jstring jdstPath) {
1068     bool isPass = true;
1069     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
1070         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
1071         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
1072         ALOGV("csrcPath:%s", csrcPath);
1073         ALOGV("cdstPath:%s", cdstPath);
1074         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
1075         for (int numSplits = 1; numSplits <= 5; ++numSplits) {
1076             ALOGV("numSplits:%d", numSplits);
1077             size_t totalSampleCount = 0;
1078             AMediaMuxer *muxer = nullptr;
1079             // Start by writing first split into a new file.
1080             FILE* ofp = fopen(cdstPath, "w+");
1081             if (ofp) {
1082                 muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)joutFormat);
1083                 if (muxer) {
1084                     for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) {
1085                         ALOGV("getSampleCount:%d:%zu", i, mediaInfo->getSampleCount(i));
1086                         totalSampleCount += mediaInfo->getSampleCount(i);
1087                     }
1088                     mediaInfo->appendMedia(muxer, 0, (totalSampleCount/numSplits)-1);
1089                     AMediaMuxer_delete(muxer);
1090                 } else {
1091                     isPass = false;
1092                     ALOGE("Failed to create muxer");
1093                 }
1094                 fclose(ofp);
1095                 ofp = nullptr;
1096                 // Check if the contents in the new file is as same as in the source file.
1097                 if (isPass) {
1098                     auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr);
1099                     isPass = mediaInfoDest->isSubsetOf(mediaInfo);
1100                     delete mediaInfoDest;
1101                 }
1102             } else {
1103                 isPass = false;
1104                 ALOGE("failed to open output file %s", cdstPath);
1105             }
1106 
1107             // Append rest of the contents from the source file to the new file split by split.
1108             int curSplit = 1;
1109             while (curSplit < numSplits && isPass) {
1110                 ofp = fopen(cdstPath, "r+");
1111                 if (ofp) {
1112                     muxer = AMediaMuxer_append(fileno(ofp), AMEDIAMUXER_APPEND_TO_EXISTING_DATA);
1113                     if (muxer) {
1114                         ssize_t trackCount = AMediaMuxer_getTrackCount(muxer);
1115                         if (trackCount > 0) {
1116                             decltype(trackCount) tc = 0;
1117                             while(tc < trackCount) {
1118                                 AMediaFormat* format = AMediaMuxer_getTrackFormat(muxer, tc);
1119                                 int64_t val = 0;
1120                                 if (AMediaFormat_getInt64(format,
1121                                             AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND, &val)) {
1122                                     ALOGV("sample-time-before-append:%lld", (long long)val);
1123                                 }
1124                                 ++tc;
1125                             }
1126                             mediaInfo->appendMedia(muxer, totalSampleCount*curSplit/numSplits,
1127                                                         totalSampleCount*(curSplit+1)/numSplits-1);
1128                         } else {
1129                             isPass = false;
1130                             ALOGE("no tracks in the file");
1131                         }
1132                         AMediaMuxer_delete(muxer);
1133                     } else {
1134                         isPass = false;
1135                         ALOGE("failed to create muxer");
1136                     }
1137                     fclose(ofp);
1138                     ofp = nullptr;
1139                     if (isPass) {
1140                         auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr);
1141                         isPass = mediaInfoDest->isSubsetOf(mediaInfo);
1142                         delete mediaInfoDest;
1143                     }
1144                 } else {
1145                     isPass = false;
1146                     ALOGE("failed to open output file %s", cdstPath);
1147                 }
1148                 ++curSplit;
1149             }
1150         }
1151         delete mediaInfo;
1152         env->ReleaseStringUTFChars(jdstPath, cdstPath);
1153         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
1154     } else {
1155         isPass = false;
1156     }
1157     return static_cast<jboolean>(isPass);
1158 }
1159 
1160 /* Checks if opening a file to append data and closing it without actually appending data
1161  * works good in all append modes.
1162  */
nativeTestNoSamples(JNIEnv * env,jobject,jint joutFormat,jstring jinPath,jstring joutPath)1163 static jboolean nativeTestNoSamples(JNIEnv* env, jobject, jint joutFormat, jstring jinPath,
1164                                         jstring joutPath) {
1165     bool isPass = true;
1166     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
1167         const char* cinPath = env->GetStringUTFChars(jinPath, nullptr);
1168         const char* coutPath = env->GetStringUTFChars(joutPath, nullptr);
1169         ALOGV("cinPath:%s", cinPath);
1170         ALOGV("coutPath:%s", coutPath);
1171         auto* mediaInfo = new MuxerNativeTestHelper(cinPath);
1172         for (unsigned int mode = AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP;
1173                     mode <= AMEDIAMUXER_APPEND_TO_EXISTING_DATA; ++mode) {
1174             if (mediaInfo->getTrackCount() != 0) {
1175                 // Create a new file and write media data to it.
1176                 FILE *ofp = fopen(coutPath, "wbe+");
1177                 if (ofp) {
1178                     AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat) joutFormat);
1179                     mediaInfo->muxMedia(muxer);
1180                     AMediaMuxer_delete(muxer);
1181                     fclose(ofp);
1182                 } else {
1183                     isPass = false;
1184                     ALOGE("failed to open output file %s", coutPath);
1185                 }
1186             } else {
1187                 isPass = false;
1188                 ALOGE("no tracks in input file");
1189             }
1190             ALOGV("after file close");
1191             FILE* ofp = fopen(coutPath, "r+");
1192             if (ofp) {
1193                 ALOGV("create append muxer");
1194                 // Open the new file in one of the append modes and close it without writing data.
1195                 AMediaMuxer *muxer = AMediaMuxer_append(fileno(ofp), (AppendMode)mode);
1196                 if (muxer) {
1197                     AMediaMuxer_start(muxer);
1198                     AMediaMuxer_stop(muxer);
1199                     ALOGV("delete append muxer");
1200                     AMediaMuxer_delete(muxer);
1201                 } else {
1202                     isPass = false;
1203                     ALOGE("failed to create muxer");
1204                 }
1205                 fclose(ofp);
1206                 ofp = nullptr;
1207             } else {
1208                 isPass = false;
1209                 ALOGE("failed to open output file to append %s", coutPath);
1210             }
1211             // Check if contents written in the new file match with contents in the original file.
1212             auto* mediaInfoOut = new MuxerNativeTestHelper(coutPath, nullptr);
1213             isPass = mediaInfoOut->isSubsetOf(mediaInfo);
1214             delete mediaInfoOut;
1215         }
1216         delete mediaInfo;
1217         env->ReleaseStringUTFChars(jinPath, cinPath);
1218         env->ReleaseStringUTFChars(joutPath, coutPath);
1219     } else {
1220         isPass = false;
1221     }
1222     return static_cast<jboolean>(isPass);
1223 }
1224 
1225 /*
1226  * Checks if appending media data in AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP mode works good.
1227  * Splits the contents of source file equally starting from one and increasing the number of
1228  * splits by one for every iteration.  Starts with writing first split into a new file and
1229  * appends the rest of the contents split by split.
1230  */
nativeTestIgnoreLastGOPAppend(JNIEnv * env,jobject,jint joutFormat,jstring jsrcPath,jstring jdstPath)1231 static jboolean nativeTestIgnoreLastGOPAppend(JNIEnv* env, jobject, jint joutFormat,
1232                                     jstring jsrcPath, jstring jdstPath) {
1233     bool isPass = true;
1234     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
1235         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
1236         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
1237         ALOGV("csrcPath:%s", csrcPath);
1238         ALOGV("cdstPath:%s", cdstPath);
1239         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
1240         for (int numSplits = 1; numSplits <= 5 && isPass; ++numSplits) {
1241             ALOGV("numSplits:%d", numSplits);
1242             size_t totalSampleCount = 0;
1243             size_t totalSamplesWritten = 0;
1244             AMediaMuxer *muxer = nullptr;
1245             FILE* ofp = fopen(cdstPath, "w+");
1246             if (ofp) {
1247                 // Start by writing first split into a new file.
1248                 muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)joutFormat);
1249                 if (muxer) {
1250                     for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) {
1251                         ALOGV("getSampleCount:%d:%zu", i, mediaInfo->getSampleCount(i));
1252                         totalSampleCount += mediaInfo->getSampleCount(i);
1253                     }
1254                     mediaInfo->appendMedia(muxer, 0, (totalSampleCount/numSplits)-1);
1255                     totalSamplesWritten += (totalSampleCount/numSplits);
1256                     AMediaMuxer_delete(muxer);
1257                 } else {
1258                     isPass = false;
1259                     ALOGE("Failed to create muxer");
1260                 }
1261                 fclose(ofp);
1262                 ofp = nullptr;
1263                 if (isPass) {
1264                     // Check if the contents in the new file is as same as in the source file.
1265                     auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr);
1266                     isPass = mediaInfoDest->isSubsetOf(mediaInfo);
1267                     delete mediaInfoDest;
1268                 }
1269             } else {
1270                 isPass = false;
1271                 ALOGE("failed to open output file %s", cdstPath);
1272             }
1273 
1274             // Append rest of the contents from the source file to the new file split by split.
1275             int curSplit = 1;
1276             while (curSplit < numSplits && isPass) {
1277                 ofp = fopen(cdstPath, "r+");
1278                 if (ofp) {
1279                     muxer = AMediaMuxer_append(fileno(ofp),
1280                                 AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP);
1281                     if (muxer) {
1282                         auto trackCount = AMediaMuxer_getTrackCount(muxer);
1283                         if (trackCount > 0) {
1284                             decltype(trackCount) tc = 0;
1285                             int64_t* appendFromTime = new int64_t[trackCount]{0};
1286                             while(tc < trackCount) {
1287                                 AMediaFormat* format = AMediaMuxer_getTrackFormat(muxer, tc);
1288                                 int64_t val = 0;
1289                                 if (AMediaFormat_getInt64(format,
1290                                             AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND, &val)) {
1291                                     ALOGV("sample-time-before-append:%lld", (long long)val);
1292                                     appendFromTime[tc] = val;
1293                                 }
1294                                 ++tc;
1295                             }
1296                             bool lastSplit = (curSplit == numSplits-1) ? true : false;
1297                             mediaInfo->appendMediaFromTime(muxer, appendFromTime,
1298                                 totalSampleCount/numSplits + ((curSplit-1) * 30), lastSplit);
1299                             delete[] appendFromTime;
1300                         } else {
1301                             isPass = false;
1302                             ALOGE("no tracks in the file");
1303                         }
1304                         AMediaMuxer_delete(muxer);
1305                     } else {
1306                         isPass = false;
1307                         ALOGE("failed to create muxer");
1308                     }
1309                     fclose(ofp);
1310                     ofp = nullptr;
1311                     if (isPass) {
1312                         auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr);
1313                         isPass = mediaInfoDest->isSubsetOf(mediaInfo);
1314                         delete mediaInfoDest;
1315                     }
1316                 } else {
1317                     isPass = false;
1318                     ALOGE("failed to open output file %s", cdstPath);
1319                 }
1320                 ++curSplit;
1321             }
1322         }
1323         delete mediaInfo;
1324         env->ReleaseStringUTFChars(jdstPath, cdstPath);
1325         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
1326     } else {
1327         isPass = false;
1328     }
1329     return static_cast<jboolean>(isPass);
1330 }
1331 
registerAndroidMediaV2CtsMuxerTestApi(JNIEnv * env)1332 int registerAndroidMediaV2CtsMuxerTestApi(JNIEnv* env) {
1333     const JNINativeMethod methodTable[] = {
1334             {"nativeTestSetOrientationHint", "(ILjava/lang/String;Ljava/lang/String;)Z",
1335              (void*)nativeTestSetOrientationHint},
1336             {"nativeTestSetLocation", "(ILjava/lang/String;Ljava/lang/String;)Z",
1337              (void*)nativeTestSetLocation},
1338             {"nativeTestGetTrackCount", "(Ljava/lang/String;Ljava/lang/String;II)Z",
1339              (void*)nativeTestGetTrackCount},
1340             {"nativeTestGetTrackFormat", "(Ljava/lang/String;Ljava/lang/String;I)Z",
1341              (void*)nativeTestGetTrackFormat},
1342     };
1343     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestApi");
1344     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1345 }
1346 
registerAndroidMediaV2CtsMuxerTestMultiTrack(JNIEnv * env)1347 int registerAndroidMediaV2CtsMuxerTestMultiTrack(JNIEnv* env) {
1348     const JNINativeMethod methodTable[] = {
1349             {"nativeTestMultiTrack",
1350              "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
1351              (void*)nativeTestMultiTrack},
1352     };
1353     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestMultiTrack");
1354     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1355 }
1356 
registerAndroidMediaV2CtsMuxerTestOffsetPts(JNIEnv * env)1357 int registerAndroidMediaV2CtsMuxerTestOffsetPts(JNIEnv* env) {
1358     const JNINativeMethod methodTable[] = {
1359             {"nativeTestOffsetPts", "(ILjava/lang/String;Ljava/lang/String;[I)Z",
1360              (void*)nativeTestOffsetPts},
1361     };
1362     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestOffsetPts");
1363     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1364 }
1365 
registerAndroidMediaV2CtsMuxerTestSimpleMux(JNIEnv * env)1366 int registerAndroidMediaV2CtsMuxerTestSimpleMux(JNIEnv* env) {
1367     const JNINativeMethod methodTable[] = {
1368             {"nativeTestSimpleMux",
1369              "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
1370              (void*)nativeTestSimpleMux},
1371     };
1372     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestSimpleMux");
1373     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1374 }
1375 
registerAndroidMediaV2CtsMuxerTestSimpleAppend(JNIEnv * env)1376 int registerAndroidMediaV2CtsMuxerTestSimpleAppend(JNIEnv* env) {
1377     const JNINativeMethod methodTable[] = {
1378             {"nativeTestSimpleAppend",
1379              "(ILjava/lang/String;Ljava/lang/String;)Z",
1380              (void*)nativeTestSimpleAppend},
1381             {"nativeTestAppendGetTrackCount",
1382              "(Ljava/lang/String;I)Z",
1383              (void*)nativeTestAppendGetTrackCount},
1384             {"nativeTestAppendGetTrackFormat", "(Ljava/lang/String;)Z",
1385              (void*)nativeTestAppendGetTrackFormat},
1386              {"nativeTestNoSamples",
1387               "(ILjava/lang/String;Ljava/lang/String;)Z",
1388               (void*)nativeTestNoSamples},
1389             {"nativeTestIgnoreLastGOPAppend",
1390              "(ILjava/lang/String;Ljava/lang/String;)Z",
1391              (void*)nativeTestIgnoreLastGOPAppend},
1392     };
1393     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestSimpleAppend");
1394     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1395 }
1396 
1397 extern int registerAndroidMediaV2CtsMuxerUnitTestApi(JNIEnv* env);
1398 
JNI_OnLoad(JavaVM * vm,void *)1399 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
1400     JNIEnv* env;
1401     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
1402     if (registerAndroidMediaV2CtsMuxerTestApi(env) != JNI_OK) return JNI_ERR;
1403     if (registerAndroidMediaV2CtsMuxerTestMultiTrack(env) != JNI_OK) return JNI_ERR;
1404     if (registerAndroidMediaV2CtsMuxerTestOffsetPts(env) != JNI_OK) return JNI_ERR;
1405     if (registerAndroidMediaV2CtsMuxerTestSimpleMux(env) != JNI_OK) return JNI_ERR;
1406     if (registerAndroidMediaV2CtsMuxerTestSimpleAppend(env) != JNI_OK) return JNI_ERR;
1407     if (registerAndroidMediaV2CtsMuxerUnitTestApi(env) != JNI_OK) return JNI_ERR;
1408     return JNI_VERSION_1_6;
1409 }
1410