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 <dlfcn.h>
22 #include <fcntl.h>
23 #include <jni.h>
24 #include <media/NdkMediaExtractor.h>
25 #include <media/NdkMediaFormat.h>
26 #include <media/NdkMediaMuxer.h>
27 #include <sys/stat.h>
28 #include <unistd.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 * mediaType=nullptr,int frameLimit=-1)48     explicit MuxerNativeTestHelper(const char* srcPath, const char* mediaType = nullptr,
49                                    int frameLimit = -1)
50         : mSrcPath(srcPath), mMediaType(mediaType), 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, uint32_t numSamples,
86                                       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* mMediaType;
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 (mMediaType == nullptr) {
149             mTrackCount++;
150             mFormat.push_back(format);
151             mInpIndexMap[trackID] = index++;
152         } else {
153             const char* mediaType;
154             bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mediaType);
155             if (hasKey && !strcmp(mediaType, mMediaType)) {
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())
314                          : ((samplesWritten < numSamples) && (i < mTrackIdxOrder.size())));
315     ALOGV("samplesWritten:%u", samplesWritten);
316 
317     delete[] frameCount;
318     return true;
319 }
320 
muxMedia(AMediaMuxer * muxer)321 bool MuxerNativeTestHelper::muxMedia(AMediaMuxer* muxer) {
322     return (registerTrack(muxer) && (AMediaMuxer_start(muxer) == AMEDIA_OK) &&
323             insertSampleData(muxer) && (AMediaMuxer_stop(muxer) == AMEDIA_OK));
324 }
325 
appendMedia(AMediaMuxer * muxer,uint32_t fromIndex,uint32_t toIndex)326 bool MuxerNativeTestHelper::appendMedia(AMediaMuxer *muxer, uint32_t fromIndex, uint32_t toIndex) {
327     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
328         ALOGV("fromIndex:%u, toIndex:%u", fromIndex, toIndex);
329         if (fromIndex == 0) {
330             registerTrack(muxer);
331         } else {
332             size_t trackCount = AMediaMuxer_getTrackCount(muxer);
333             ALOGV("appendMedia:trackCount:%zu", trackCount);
334             for(size_t i = 0; i < trackCount; ++i) {
335                 ALOGV("track i:%zu", i);
336                 ALOGV("%s", AMediaFormat_toString(AMediaMuxer_getTrackFormat(muxer, i)));
337                 ALOGV("%s", AMediaFormat_toString(mFormat[i]));
338                 for(size_t j = 0; j < mFormat.size(); ++j) {
339                     const char* thatMediaType = nullptr;
340                     AMediaFormat_getString(AMediaMuxer_getTrackFormat(muxer, i),
341                                                 AMEDIAFORMAT_KEY_MIME, &thatMediaType);
342                     const char* thisMediaType = nullptr;
343                     AMediaFormat_getString(mFormat[j], AMEDIAFORMAT_KEY_MIME, &thisMediaType);
344                     ALOGV("strlen(thisMediaType)%zu", strlen(thisMediaType));
345                     if (strcmp(thatMediaType, thisMediaType) == 0) {
346                         ALOGV("appendMedia:i:%zu, j:%zu", i, j);
347                         mOutIndexMap[j]=i;
348                     }
349                 }
350             }
351         }
352         AMediaMuxer_start(muxer);
353         bool res = writeAFewSamplesData(muxer, fromIndex, toIndex);
354         AMediaMuxer_stop(muxer);
355         return res;
356     } else {
357         return false;
358     }
359 }
360 
appendMediaFromTime(AMediaMuxer * muxer,int64_t * appendFromTime,uint32_t numSamples,bool lastSplit)361 bool MuxerNativeTestHelper::appendMediaFromTime(AMediaMuxer *muxer, int64_t *appendFromTime,
362                                                         uint32_t numSamples, bool lastSplit) {
363     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
364         size_t trackCount = AMediaMuxer_getTrackCount(muxer);
365         ALOGV("appendMediaFromTime:trackCount:%zu", trackCount);
366         for(size_t i = 0; i < trackCount; ++i) {
367             ALOGV("track i:%zu", i);
368             ALOGV("%s", AMediaFormat_toString(AMediaMuxer_getTrackFormat(muxer, i)));
369             ALOGV("%s", AMediaFormat_toString(mFormat[i]));
370             for(size_t j = 0; j < mFormat.size(); ++j) {
371                 const char* thatMediaType = nullptr;
372                 AMediaFormat_getString(AMediaMuxer_getTrackFormat(muxer, i),
373                                        AMEDIAFORMAT_KEY_MIME, &thatMediaType);
374                 const char* thisMediaType = nullptr;
375                 AMediaFormat_getString(mFormat[j], AMEDIAFORMAT_KEY_MIME, &thisMediaType);
376                 ALOGV("strlen(thisMediaType)%zu", strlen(thisMediaType));
377                 if (strcmp(thatMediaType, thisMediaType) == 0) {
378                     ALOGV("appendMediaFromTime:i:%zu, j:%zu", i, j);
379                     mOutIndexMap[j]=i;
380                 }
381             }
382         }
383         AMediaMuxer_start(muxer);
384         bool res = writeAFewSamplesDataFromTime(muxer, appendFromTime, numSamples, lastSplit);
385         AMediaMuxer_stop(muxer);
386         return res;
387     } else {
388         return false;
389     }
390 }
combineMedias(AMediaMuxer * muxer,MuxerNativeTestHelper * that,const int * repeater)391 bool MuxerNativeTestHelper::combineMedias(AMediaMuxer* muxer, MuxerNativeTestHelper* that,
392                                           const int* repeater) {
393     if (that == nullptr) return false;
394     if (repeater == nullptr) return false;
395 
396     // register tracks
397     int totalTracksToAdd = repeater[0] * this->mTrackCount + repeater[1] * that->mTrackCount;
398     int outIndexMap[totalTracksToAdd];
399     MuxerNativeTestHelper* group[2]{this, that};
400     for (int k = 0, idx = 0; k < 2; k++) {
401         for (int j = 0; j < repeater[k]; j++) {
402             for (AMediaFormat* format : group[k]->mFormat) {
403                 int dstIndex = AMediaMuxer_addTrack(muxer, format);
404                 if (dstIndex < 0) return false;
405                 outIndexMap[idx++] = dstIndex;
406             }
407         }
408     }
409     // start
410     if (AMediaMuxer_start(muxer) != AMEDIA_OK) return false;
411     // write sample data
412     // write all registered tracks in planar order viz all samples of a track A then all
413     // samples of track B, ...
414     for (int k = 0, idx = 0; k < 2; k++) {
415         for (int j = 0; j < repeater[k]; j++) {
416             for (int i = 0; i < group[k]->mTrackCount; i++) {
417                 for (int p = 0; p < group[k]->mBufferInfo[i].size(); p++) {
418                     AMediaCodecBufferInfo* info = group[k]->mBufferInfo[i][p];
419                     if (AMediaMuxer_writeSampleData(muxer, outIndexMap[idx], group[k]->mBuffer,
420                                                     info) != AMEDIA_OK) {
421                         return false;
422                     }
423                     ALOGV("Track: %d Timestamp: %" PRId64 "", outIndexMap[idx],
424                           info->presentationTimeUs);
425                 }
426                 idx++;
427             }
428         }
429     }
430     // stop
431     return (AMediaMuxer_stop(muxer) == AMEDIA_OK);
432 }
433 
434 // returns true if 'this' stream is a subset of 'that'. That is all tracks in current media
435 // stream are present in ref media stream
isSubsetOf(MuxerNativeTestHelper * that)436 bool MuxerNativeTestHelper::isSubsetOf(MuxerNativeTestHelper* that) {
437     if (this == that) return true;
438     if (that == nullptr) return false;
439 
440     for (int i = 0; i < mTrackCount; i++) {
441         AMediaFormat* thisFormat = mFormat[i];
442         const char* thisMediaType = nullptr;
443         AMediaFormat_getString(thisFormat, AMEDIAFORMAT_KEY_MIME, &thisMediaType);
444         int tolerance = !strncmp(thisMediaType, "video/", strlen("video/")) ? STTS_TOLERANCE_US : 0;
445         tolerance += 1;  // rounding error
446         int j = 0;
447         for (; j < that->mTrackCount; j++) {
448             AMediaFormat* thatFormat = that->mFormat[j];
449             const char* thatMediaType = nullptr;
450             AMediaFormat_getString(thatFormat, AMEDIAFORMAT_KEY_MIME, &thatMediaType);
451             if (thisMediaType != nullptr && thatMediaType != nullptr &&
452                 !strcmp(thisMediaType, thatMediaType)) {
453                 if (!isFormatSimilar(thisFormat, thatFormat)) continue;
454                 if (mBufferInfo[i].size() <= that->mBufferInfo[j].size()) {
455                     int tolerance = !strncmp(thisMediaType, "video/", strlen("video/"))
456                                             ? STTS_TOLERANCE_US
457                                             : 0;
458                     tolerance += 1;  // rounding error
459                     int k = 0;
460                     for (; k < mBufferInfo[i].size(); k++) {
461                         ALOGV("k:%d", k);
462                         AMediaCodecBufferInfo* thisInfo = mBufferInfo[i][k];
463                         AMediaCodecBufferInfo* thatInfo = that->mBufferInfo[j][k];
464                         if (thisInfo->flags != thatInfo->flags) {
465                             ALOGD("flags this:%u, that:%u", thisInfo->flags, thatInfo->flags);
466                             break;
467                         }
468                         if (thisInfo->size != thatInfo->size) {
469                             ALOGD("size  this:%d, that:%d", thisInfo->size, thatInfo->size);
470                             break;
471                         } else if (memcmp(mBuffer + thisInfo->offset,
472                                           that->mBuffer + thatInfo->offset, thisInfo->size)) {
473                             ALOGD("memcmp failed");
474                             break;
475                         }
476                         if (abs(thisInfo->presentationTimeUs - thatInfo->presentationTimeUs) >
477                             tolerance) {
478                             ALOGD("time this:%lld, that:%lld",
479                                   (long long)thisInfo->presentationTimeUs,
480                                   (long long)thatInfo->presentationTimeUs);
481                             break;
482                         }
483                     }
484                     if (k == mBufferInfo[i].size()) break;
485                 }
486             }
487         }
488         if (j == that->mTrackCount) {
489             AMediaFormat_getString(thisFormat, AMEDIAFORMAT_KEY_MIME, &thisMediaType);
490             ALOGV("For media type %s, Couldn't find a match", thisMediaType);
491             return false;
492         }
493     }
494     return true;
495 }
496 
offsetTimeStamp(int64_t tsAudioOffsetUs,int64_t tsVideoOffsetUs,int sampleOffset)497 void MuxerNativeTestHelper::offsetTimeStamp(int64_t tsAudioOffsetUs, int64_t tsVideoOffsetUs,
498                                             int sampleOffset) {
499     // offset pts of samples from index sampleOffset till the end by tsOffset for each audio and
500     // video track
501     for (int trackID = 0; trackID < mTrackCount; trackID++) {
502         int64_t tsOffsetUs = 0;
503         const char* thisMediaType = nullptr;
504         AMediaFormat_getString(mFormat[trackID], AMEDIAFORMAT_KEY_MIME, &thisMediaType);
505         if (thisMediaType != nullptr) {
506             if (strncmp(thisMediaType, "video/", strlen("video/")) == 0) {
507                 tsOffsetUs = tsVideoOffsetUs;
508             } else if (strncmp(thisMediaType, "audio/", strlen("audio/")) == 0) {
509                 tsOffsetUs = tsAudioOffsetUs;
510             }
511             for (int i = sampleOffset; i < mBufferInfo[trackID].size(); i++) {
512                 AMediaCodecBufferInfo* info = mBufferInfo[trackID][i];
513                 info->presentationTimeUs += tsOffsetUs;
514             }
515         }
516     }
517 }
518 
isCodecContainerPairValid(OutputFormat format,const char * mediaType)519 static bool isCodecContainerPairValid(OutputFormat format, const char* mediaType) {
520     static const std::map<OutputFormat, std::vector<const char*>> codecListforType = {
521             {AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4,
522              {AMEDIA_MIMETYPE_VIDEO_MPEG4, AMEDIA_MIMETYPE_VIDEO_H263, AMEDIA_MIMETYPE_VIDEO_AVC,
523               AMEDIA_MIMETYPE_VIDEO_HEVC, AMEDIA_MIMETYPE_AUDIO_AAC}},
524             {AMEDIAMUXER_OUTPUT_FORMAT_WEBM,
525              {AMEDIA_MIMETYPE_VIDEO_VP8, AMEDIA_MIMETYPE_VIDEO_VP9, AMEDIA_MIMETYPE_AUDIO_VORBIS,
526               AMEDIA_MIMETYPE_AUDIO_OPUS}},
527             {AMEDIAMUXER_OUTPUT_FORMAT_THREE_GPP,
528              {AMEDIA_MIMETYPE_VIDEO_MPEG4, AMEDIA_MIMETYPE_VIDEO_H263, AMEDIA_MIMETYPE_VIDEO_AVC,
529               AMEDIA_MIMETYPE_AUDIO_AAC, AMEDIA_MIMETYPE_AUDIO_AMR_NB,
530               AMEDIA_MIMETYPE_AUDIO_AMR_WB}},
531             {AMEDIAMUXER_OUTPUT_FORMAT_OGG, {AMEDIA_MIMETYPE_AUDIO_OPUS}},
532     };
533 
534     if (format == AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4 &&
535         strncmp(mediaType, "application/", strlen("application/")) == 0)
536         return true;
537 
538     auto it = codecListforType.find(format);
539     if (it != codecListforType.end())
540         for (auto it2 : it->second)
541             if (strcmp(it2, mediaType) == 0) return true;
542 
543     return false;
544 }
545 
nativeTestSetLocation(JNIEnv * env,jobject,jint jformat,jstring jsrcPath,jstring jdstPath)546 static jboolean nativeTestSetLocation(JNIEnv* env, jobject, jint jformat, jstring jsrcPath,
547                                       jstring jdstPath) {
548     bool isPass = true;
549     bool isGeoDataSupported;
550     const float atlanticLat = 14.59f;
551     const float atlanticLong = 28.67f;
552     const float tooFarNorth = 90.5f;
553     const float tooFarWest = -180.5f;
554     const float tooFarSouth = -90.5f;
555     const float tooFarEast = 180.5f;
556     const float annapurnaLat = 28.59f;
557     const float annapurnaLong = 83.82f;
558     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
559     FILE* ofp = fopen(cdstPath, "wbe+");
560     if (ofp) {
561         AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
562         media_status_t status = AMediaMuxer_setLocation(muxer, tooFarNorth, atlanticLong);
563         if (status == AMEDIA_OK) {
564             isPass = false;
565             ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarNorth, atlanticLong);
566         }
567         status = AMediaMuxer_setLocation(muxer, tooFarSouth, atlanticLong);
568         if (status == AMEDIA_OK) {
569             isPass = false;
570             ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarSouth, atlanticLong);
571         }
572         status = AMediaMuxer_setLocation(muxer, atlanticLat, tooFarWest);
573         if (status == AMEDIA_OK) {
574             isPass = false;
575             ALOGE("setLocation succeeds on bad args: (%f, %f)", atlanticLat, tooFarWest);
576         }
577         status = AMediaMuxer_setLocation(muxer, atlanticLat, tooFarEast);
578         if (status == AMEDIA_OK) {
579             isPass = false;
580             ALOGE("setLocation succeeds on bad args: (%f, %f)", atlanticLat, tooFarEast);
581         }
582         status = AMediaMuxer_setLocation(muxer, tooFarNorth, tooFarWest);
583         if (status == AMEDIA_OK) {
584             isPass = false;
585             ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarNorth, tooFarWest);
586         }
587         status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong);
588         isGeoDataSupported = (status == AMEDIA_OK);
589         if (isGeoDataSupported) {
590             status = AMediaMuxer_setLocation(muxer, annapurnaLat, annapurnaLong);
591             if (status != AMEDIA_OK) {
592                 isPass = false;
593                 ALOGE("setLocation fails on args: (%f, %f)", annapurnaLat, annapurnaLong);
594             }
595         } else {
596             isPass &= ((OutputFormat)jformat != AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4 &&
597                        (OutputFormat)jformat != AMEDIAMUXER_OUTPUT_FORMAT_THREE_GPP);
598         }
599         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
600         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
601         if (mediaInfo->registerTrack(muxer) && AMediaMuxer_start(muxer) == AMEDIA_OK) {
602             status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong);
603             if (status == AMEDIA_OK) {
604                 isPass = false;
605                 ALOGE("setLocation succeeds after starting the muxer");
606             }
607             if (mediaInfo->insertSampleData(muxer) && AMediaMuxer_stop(muxer) == AMEDIA_OK) {
608                 status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong);
609                 if (status == AMEDIA_OK) {
610                     isPass = false;
611                     ALOGE("setLocation succeeds after stopping the muxer");
612                 }
613             } else {
614                 isPass = false;
615                 ALOGE("failed to writeSampleData or stop muxer");
616             }
617         } else {
618             isPass = false;
619             ALOGE("failed to addTrack or start muxer");
620         }
621         delete mediaInfo;
622         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
623         AMediaMuxer_delete(muxer);
624         fclose(ofp);
625     } else {
626         isPass = false;
627         ALOGE("failed to open output file %s", cdstPath);
628     }
629     env->ReleaseStringUTFChars(jdstPath, cdstPath);
630     return static_cast<jboolean>(isPass);
631 }
632 
633 // @ApiTest = AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4
634 // @ApiTest = AMEDIAMUXER_OUTPUT_FORMAT_THREE_GPP
635 //
nativeTestSetOrientationHint(JNIEnv * env,jobject,jint jformat,jstring jsrcPath,jstring jdstPath)636 static jboolean nativeTestSetOrientationHint(JNIEnv* env, jobject, jint jformat, jstring jsrcPath,
637                                              jstring jdstPath) {
638     bool isPass = true;
639     bool isOrientationSupported;
640     const int badRotation[] = {360, 45, -90};
641     const int oldRotation = 90;
642     const int currRotation = 180;
643     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
644     FILE* ofp = fopen(cdstPath, "wbe+");
645     if (ofp) {
646         AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
647         media_status_t status;
648         for (int degrees : badRotation) {
649             status = AMediaMuxer_setOrientationHint(muxer, degrees);
650             if (status == AMEDIA_OK) {
651                 isPass = false;
652                 ALOGE("setOrientationHint succeeds on bad args: %d", degrees);
653             }
654         }
655         status = AMediaMuxer_setOrientationHint(muxer, oldRotation);
656         isOrientationSupported = (status == AMEDIA_OK);
657         if (isOrientationSupported) {
658             status = AMediaMuxer_setOrientationHint(muxer, currRotation);
659             if (status != AMEDIA_OK) {
660                 isPass = false;
661                 ALOGE("setOrientationHint fails on args: %d", currRotation);
662             }
663         } else {
664             isPass &= ((OutputFormat)jformat != AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4 &&
665                        (OutputFormat)jformat != AMEDIAMUXER_OUTPUT_FORMAT_THREE_GPP);
666         }
667         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
668         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
669         if (mediaInfo->registerTrack(muxer) && AMediaMuxer_start(muxer) == AMEDIA_OK) {
670             status = AMediaMuxer_setOrientationHint(muxer, currRotation);
671             if (status == AMEDIA_OK) {
672                 isPass = false;
673                 ALOGE("setOrientationHint succeeds after starting the muxer");
674             }
675             if (mediaInfo->insertSampleData(muxer) && AMediaMuxer_stop(muxer) == AMEDIA_OK) {
676                 status = AMediaMuxer_setOrientationHint(muxer, currRotation);
677                 if (status == AMEDIA_OK) {
678                     isPass = false;
679                     ALOGE("setOrientationHint succeeds after stopping the muxer");
680                 }
681             } else {
682                 isPass = false;
683                 ALOGE("failed to writeSampleData or stop muxer");
684             }
685         } else {
686             isPass = false;
687             ALOGE("failed to addTrack or start muxer");
688         }
689         delete mediaInfo;
690         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
691         AMediaMuxer_delete(muxer);
692         fclose(ofp);
693     } else {
694         isPass = false;
695         ALOGE("failed to open output file %s", cdstPath);
696     }
697     env->ReleaseStringUTFChars(jdstPath, cdstPath);
698     return static_cast<jboolean>(isPass);
699 }
700 
nativeTestMultiTrack(JNIEnv * env,jobject,jint jformat,jstring jsrcPathA,jstring jsrcPathB,jstring jrefPath,jstring jdstPath)701 static jboolean nativeTestMultiTrack(JNIEnv* env, jobject, jint jformat, jstring jsrcPathA,
702                                      jstring jsrcPathB, jstring jrefPath, jstring jdstPath) {
703     bool isPass = true;
704     const char* csrcPathA = env->GetStringUTFChars(jsrcPathA, nullptr);
705     const char* csrcPathB = env->GetStringUTFChars(jsrcPathB, nullptr);
706     auto* mediaInfoA = new MuxerNativeTestHelper(csrcPathA);
707     auto* mediaInfoB = new MuxerNativeTestHelper(csrcPathB);
708     if (mediaInfoA->getTrackCount() == 1 && mediaInfoB->getTrackCount() == 1) {
709         const char* crefPath = env->GetStringUTFChars(jrefPath, nullptr);
710         // number of times to repeat {mSrcFileA, mSrcFileB} in Output
711         // values should be in sync with testMultiTrack
712         static const int numTracks[][2] = {{1, 1}, {2, 0}, {0, 2}, {1, 2}, {2, 1}, {2, 2}};
713         // prepare reference
714         FILE* rfp = fopen(crefPath, "wbe+");
715         if (rfp) {
716             AMediaMuxer* muxer = AMediaMuxer_new(fileno(rfp), (OutputFormat)jformat);
717             bool muxStatus = mediaInfoA->combineMedias(muxer, mediaInfoB, numTracks[0]);
718             AMediaMuxer_delete(muxer);
719             fclose(rfp);
720             if (muxStatus) {
721                 auto* refInfo = new MuxerNativeTestHelper(crefPath);
722                 if (!mediaInfoA->isSubsetOf(refInfo) || !mediaInfoB->isSubsetOf(refInfo)) {
723                     isPass = false;
724                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing src A and src B "
725                           "failed", csrcPathA, csrcPathB, jformat);
726                 } else {
727                     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
728                     for (int i = 1; i < sizeof(numTracks) / sizeof(numTracks[0]) && isPass; i++) {
729                         FILE* ofp = fopen(cdstPath, "wbe+");
730                         if (ofp) {
731                             muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
732                             bool status =
733                                     mediaInfoA->combineMedias(muxer, mediaInfoB, numTracks[i]);
734                             AMediaMuxer_delete(muxer);
735                             fclose(ofp);
736                             if (status) {
737                                 auto* dstInfo = new MuxerNativeTestHelper(cdstPath);
738                                 if (!dstInfo->isSubsetOf(refInfo)) {
739                                     isPass = false;
740                                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing "
741                                           "src A: %d, src B: %d failed", csrcPathA, csrcPathB,
742                                           jformat, numTracks[i][0], numTracks[i][1]);
743                                 }
744                                 delete dstInfo;
745                             } else {
746                                 if ((OutputFormat)jformat != AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4) {
747                                     isPass = false;
748                                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing "
749                                           "src A: %d, src B: %d failed", csrcPathA, csrcPathB,
750                                           jformat, numTracks[i][0], numTracks[i][1]);
751                                 }
752                             }
753                         } else {
754                             isPass = false;
755                             ALOGE("failed to open output file %s", cdstPath);
756                         }
757                     }
758                     env->ReleaseStringUTFChars(jdstPath, cdstPath);
759                 }
760                 delete refInfo;
761             } else {
762                 if ((OutputFormat)jformat != AMEDIAMUXER_OUTPUT_FORMAT_OGG) {
763                     isPass = false;
764                     ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing src A and src B "
765                           "failed", csrcPathA, csrcPathB, jformat);
766                 }
767             }
768         } else {
769             isPass = false;
770             ALOGE("failed to open reference output file %s", crefPath);
771         }
772         env->ReleaseStringUTFChars(jrefPath, crefPath);
773     } else {
774         isPass = false;
775         if (mediaInfoA->getTrackCount() != 1) {
776             ALOGE("error: file %s, track count exp/rec - %d/%d", csrcPathA, 1,
777                   mediaInfoA->getTrackCount());
778         }
779         if (mediaInfoB->getTrackCount() != 1) {
780             ALOGE("error: file %s, track count exp/rec - %d/%d", csrcPathB, 1,
781                   mediaInfoB->getTrackCount());
782         }
783     }
784     env->ReleaseStringUTFChars(jsrcPathA, csrcPathA);
785     env->ReleaseStringUTFChars(jsrcPathB, csrcPathB);
786     delete mediaInfoA;
787     delete mediaInfoB;
788     return static_cast<jboolean>(isPass);
789 }
790 
nativeTestOffsetPts(JNIEnv * env,jobject,jint format,jstring jsrcPath,jstring jdstPath,jintArray joffsetIndices)791 static jboolean nativeTestOffsetPts(JNIEnv* env, jobject, jint format, jstring jsrcPath,
792                                     jstring jdstPath, jintArray joffsetIndices) {
793     bool isPass = true;
794     // values should be in sync with testOffsetPresentationTime
795     static const int64_t OFFSET_TS_AUDIO_US[4] = {-23220LL, 0LL, 200000LL, 400000LL};
796     static const int64_t OFFSET_TS_VIDEO_US[3] = {0LL, 200000LL, 400000LL};
797     jsize len = env->GetArrayLength(joffsetIndices);
798     jint* coffsetIndices = env->GetIntArrayElements(joffsetIndices, nullptr);
799     const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
800     const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
801     auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
802     if (mediaInfo->getTrackCount() != 0) {
803         for (int64_t audioOffsetUs : OFFSET_TS_AUDIO_US) {
804             for (int64_t videoOffsetUs : OFFSET_TS_VIDEO_US) {
805                 for (int i = 0; i < len; i++) {
806                     mediaInfo->offsetTimeStamp(audioOffsetUs, videoOffsetUs, coffsetIndices[i]);
807                 }
808                 FILE* ofp = fopen(cdstPath, "wbe+");
809                 if (ofp) {
810                     AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)format);
811                     mediaInfo->muxMedia(muxer);
812                     AMediaMuxer_delete(muxer);
813                     fclose(ofp);
814                     auto* outInfo = new MuxerNativeTestHelper(cdstPath);
815                     isPass = mediaInfo->isSubsetOf(outInfo);
816                     if (!isPass) {
817                         ALOGE("Validation failed after adding timestamp offsets audio: %lld,"
818                               " video: %lld", (long long) audioOffsetUs, (long long) videoOffsetUs);
819                     }
820                     delete outInfo;
821                 } else {
822                     isPass = false;
823                     ALOGE("failed to open output file %s", cdstPath);
824                 }
825                 for (int i = len - 1; i >= 0; i--) {
826                     mediaInfo->offsetTimeStamp(-audioOffsetUs, -videoOffsetUs, coffsetIndices[i]);
827                 }
828             }
829         }
830     } else {
831         isPass = false;
832         ALOGE("no valid track found in input file %s", csrcPath);
833     }
834     env->ReleaseStringUTFChars(jdstPath, cdstPath);
835     env->ReleaseStringUTFChars(jsrcPath, csrcPath);
836     env->ReleaseIntArrayElements(joffsetIndices, coffsetIndices, 0);
837     delete mediaInfo;
838     return static_cast<jboolean>(isPass);
839 }
840 
841 // simple muxer tests, including varying the output container format.
842 //
843 // @ApiTest = AMEDIAMUXER_OUTPUT_FORMAT_HEIF
844 // @ApiTest = AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4
845 // @ApiTest = AMEDIAMUXER_OUTPUT_FORMAT_OGG
846 // @ApiTest = AMEDIAMUXER_OUTPUT_FORMAT_THREE_GPP
847 // @ApiTest = AMEDIAMUXER_OUTPUT_FORMAT_WEBM
848 //
nativeTestSimpleMux(JNIEnv * env,jobject,jstring jsrcPath,jstring jdstPath,jstring jMediaType,jstring jselector)849 static jboolean nativeTestSimpleMux(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath,
850                                     jstring jMediaType, jstring jselector) {
851     bool isPass = true;
852     const char* cMediaType = env->GetStringUTFChars(jMediaType, nullptr);
853     const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
854     const char* cselector = env->GetStringUTFChars(jselector, nullptr);
855     auto* mediaInfo = new MuxerNativeTestHelper(csrcPath, cMediaType);
856     static const std::map<OutputFormat, const char*> formatStringPair = {
857             {AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4, "mp4"},
858             {AMEDIAMUXER_OUTPUT_FORMAT_WEBM, "webm"},
859             {AMEDIAMUXER_OUTPUT_FORMAT_THREE_GPP, "3gp"},
860             {AMEDIAMUXER_OUTPUT_FORMAT_HEIF, "heif"},
861             {AMEDIAMUXER_OUTPUT_FORMAT_OGG, "ogg"}};
862     if (mediaInfo->getTrackCount() == 1) {
863         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
864         for (int fmt = LOCAL_AMEDIAMUXER_OUTPUT_FORMAT_FIRST;
865              fmt <= LOCAL_AMEDIAMUXER_OUTPUT_FORMAT_LAST && isPass; fmt++) {
866             auto it = formatStringPair.find((OutputFormat)fmt);
867             if (it == formatStringPair.end() || strstr(cselector, it->second) == nullptr) {
868                 continue;
869             }
870             if (fmt == AMEDIAMUXER_OUTPUT_FORMAT_WEBM) continue;  // TODO(b/146923551)
871             FILE* ofp = fopen(cdstPath, "wbe+");
872             if (ofp) {
873                 AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)fmt);
874                 bool muxStatus = mediaInfo->muxMedia(muxer);
875                 bool result = true;
876                 AMediaMuxer_delete(muxer);
877                 fclose(ofp);
878                 if (muxStatus) {
879                     auto* outInfo = new MuxerNativeTestHelper(cdstPath, cMediaType);
880                     result = mediaInfo->isSubsetOf(outInfo);
881                     delete outInfo;
882                 }
883                 if ((muxStatus && !result) ||
884                     (!muxStatus && isCodecContainerPairValid((OutputFormat)fmt, cMediaType))) {
885                     isPass = false;
886                     ALOGE("error: file %s, mediaType %s, output != clone(input) for format %d",
887                           csrcPath, cMediaType, fmt);
888                 }
889             } else {
890                 isPass = false;
891                 ALOGE("error: file %s, mediaType %s, failed to open output file %s", csrcPath,
892                       cMediaType, cdstPath);
893             }
894         }
895         env->ReleaseStringUTFChars(jdstPath, cdstPath);
896     } else {
897         isPass = false;
898         ALOGE("error: file %s, mediaType %s, track count exp/rec - %d/%d", csrcPath, cMediaType, 1,
899               mediaInfo->getTrackCount());
900     }
901     env->ReleaseStringUTFChars(jselector, cselector);
902     env->ReleaseStringUTFChars(jsrcPath, csrcPath);
903     env->ReleaseStringUTFChars(jMediaType, cMediaType);
904     delete mediaInfo;
905     return static_cast<jboolean>(isPass);
906 }
907 
908 /* Check whether AMediaMuxer_getTrackCount works as expected.
909  */
nativeTestGetTrackCount(JNIEnv * env,jobject,jstring jsrcPath,jstring jdstPath,jint jformat,jint jtrackCount)910 static jboolean nativeTestGetTrackCount(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath,
911                                             jint jformat, jint jtrackCount) {
912     bool isPass = true;
913     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
914         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
915         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
916         FILE* ofp = fopen(cdstPath, "w+");
917         if (ofp) {
918             AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat);
919             if (muxer) {
920                 auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
921                 if (!mediaInfo->registerTrack(muxer)) {
922                     isPass = false;
923                     ALOGE("register track failed");
924                 }
925                 if (AMediaMuxer_getTrackCount(muxer) != jtrackCount) {
926                     isPass = false;
927                     ALOGE("track counts are not equal");
928                 }
929                 delete mediaInfo;
930                 AMediaMuxer_delete(muxer);
931             } else {
932                 isPass = false;
933                 ALOGE("Failed to create muxer");
934             }
935             fclose(ofp);
936         } else {
937             isPass = false;
938             ALOGE("file open error: file  %s", csrcPath);
939         }
940         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
941         env->ReleaseStringUTFChars(jdstPath, cdstPath);
942     } else {
943         isPass = false;
944     }
945     return static_cast<jboolean>(isPass);
946 }
947 
948 /* Check whether AMediaMuxer_getTrackCount works as expected when the file is opened in
949  * append mode.
950  */
nativeTestAppendGetTrackCount(JNIEnv * env,jobject,jstring jsrcPath,jint jtrackCount)951 static jboolean nativeTestAppendGetTrackCount(JNIEnv* env, jobject, jstring jsrcPath,
952                                                         jint jtrackCount) {
953     bool isPass = true;
954     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
955         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
956         for (unsigned int mode = AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP;
957                     mode <= AMEDIAMUXER_APPEND_TO_EXISTING_DATA; ++mode) {
958             ALOGV("mode:%u", mode);
959             FILE* ofp = fopen(csrcPath, "r");
960             if (ofp) {
961                 AMediaMuxer *muxer = AMediaMuxer_append(fileno(ofp), (AppendMode)mode);
962                 if (muxer) {
963                     ssize_t trackCount = AMediaMuxer_getTrackCount(muxer);
964                     if ( trackCount != jtrackCount) {
965                         isPass = false;
966                         ALOGE("trackcounts are not equal, trackCount:%ld vs jtrackCount:%d",
967                                     (long)trackCount, jtrackCount);
968                     }
969                     AMediaMuxer_delete(muxer);
970                 } else {
971                     isPass = false;
972                     ALOGE("Failed to create muxer");
973                 }
974                 fclose(ofp);
975                 ofp = nullptr;
976             } else {
977                 isPass = false;
978                 ALOGE("file open error: file  %s", csrcPath);
979             }
980         }
981         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
982     } else {
983         isPass = false;
984     }
985     return static_cast<jboolean>(isPass);
986 }
987 
988 /* Checks whether AMediaMuxer_getTrackFormat works as expected in muxer mode.
989  */
nativeTestGetTrackFormat(JNIEnv * env,jobject,jstring jsrcPath,jstring jdstPath,jint joutFormat)990 static jboolean nativeTestGetTrackFormat(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath,
991                                                     jint joutFormat) {
992     bool isPass = true;
993     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
994         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
995         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
996         FILE* ofp = fopen(cdstPath, "w+");
997         if (ofp) {
998             AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)joutFormat);
999             if (muxer) {
1000                 auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
1001                 if (!mediaInfo->registerTrack(muxer)) {
1002                     isPass = false;
1003                     ALOGE("register track failed");
1004                 }
1005                 for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) {
1006                     if (!isFormatSimilar(mediaInfo->getTrackFormat(i),
1007                             AMediaMuxer_getTrackFormat(muxer, i))) {
1008                         isPass = false;
1009                         ALOGE("track formats are not similar");
1010                     }
1011                 }
1012                 delete mediaInfo;
1013                 AMediaMuxer_delete(muxer);
1014             } else {
1015                 isPass = false;
1016                 ALOGE("Failed to create muxer");
1017             }
1018             fclose(ofp);
1019         } else {
1020             isPass = false;
1021             ALOGE("file open error: file  %s", csrcPath);
1022         }
1023         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
1024         env->ReleaseStringUTFChars(jdstPath, cdstPath);
1025     } else {
1026         isPass = false;
1027     }
1028     return static_cast<jboolean>(isPass);
1029 }
1030 
1031 /* Checks whether AMediaMuxer_getTrackFormat works as expected when the file is opened in
1032  * append mode.
1033  */
nativeTestAppendGetTrackFormat(JNIEnv * env,jobject,jstring jsrcPath)1034 static jboolean nativeTestAppendGetTrackFormat(JNIEnv* env, jobject, jstring jsrcPath) {
1035     bool isPass = true;
1036     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
1037         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
1038         for (unsigned int mode = AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP;
1039                     mode <= AMEDIAMUXER_APPEND_TO_EXISTING_DATA; ++mode) {
1040             ALOGV("mode:%u", mode);
1041             FILE* ofp = fopen(csrcPath, "r");
1042             if (ofp) {
1043                 AMediaMuxer *muxer = AMediaMuxer_append(fileno(ofp), (AppendMode)mode);
1044                 if (muxer) {
1045                     auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
1046                     for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) {
1047                         if (!isFormatSimilar(mediaInfo->getTrackFormat(i),
1048                                 AMediaMuxer_getTrackFormat(muxer, i))) {
1049                             isPass = false;
1050                             ALOGE("track formats are not similar");
1051                         }
1052                     }
1053                     delete mediaInfo;
1054                     AMediaMuxer_delete(muxer);
1055                 } else {
1056                     isPass = false;
1057                     ALOGE("Failed to create muxer");
1058                 }
1059                 fclose(ofp);
1060             } else {
1061                 isPass = false;
1062                 ALOGE("file open error: file  %s", csrcPath);
1063             }
1064         }
1065         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
1066     }
1067     else {
1068         isPass = false;
1069     }
1070     return static_cast<jboolean>(isPass);
1071 }
1072 
1073 /*
1074  * Checks if appending media data to the end of existing media data in a file works good.
1075  * Mode : AMEDIAMUXER_APPEND_TO_EXISTING_DATA.  Splits the contents of source file equally
1076  * starting from one and increasing the number of splits by one for every iteration.  Starts
1077  * with writing first split into a new file and appends the rest of the contents split by split.
1078  */
nativeTestSimpleAppend(JNIEnv * env,jobject,jint joutFormat,jstring jsrcPath,jstring jdstPath)1079 static jboolean nativeTestSimpleAppend(JNIEnv* env, jobject, jint joutFormat, jstring jsrcPath,
1080                         jstring jdstPath) {
1081     bool isPass = true;
1082     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
1083         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
1084         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
1085         ALOGV("csrcPath:%s", csrcPath);
1086         ALOGV("cdstPath:%s", cdstPath);
1087         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
1088         for (int numSplits = 1; numSplits <= 5; ++numSplits) {
1089             ALOGV("numSplits:%d", numSplits);
1090             size_t totalSampleCount = 0;
1091             AMediaMuxer *muxer = nullptr;
1092             // Start by writing first split into a new file.
1093             FILE* ofp = fopen(cdstPath, "w+");
1094             if (ofp) {
1095                 muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)joutFormat);
1096                 if (muxer) {
1097                     for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) {
1098                         ALOGV("getSampleCount:%d:%zu", i, mediaInfo->getSampleCount(i));
1099                         totalSampleCount += mediaInfo->getSampleCount(i);
1100                     }
1101                     mediaInfo->appendMedia(muxer, 0, (totalSampleCount/numSplits)-1);
1102                     AMediaMuxer_delete(muxer);
1103                 } else {
1104                     isPass = false;
1105                     ALOGE("Failed to create muxer");
1106                 }
1107                 fclose(ofp);
1108                 ofp = nullptr;
1109                 // Check if the contents in the new file is as same as in the source file.
1110                 if (isPass) {
1111                     auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr);
1112                     isPass = mediaInfoDest->isSubsetOf(mediaInfo);
1113                     delete mediaInfoDest;
1114                 }
1115             } else {
1116                 isPass = false;
1117                 ALOGE("failed to open output file %s", cdstPath);
1118             }
1119 
1120             // Append rest of the contents from the source file to the new file split by split.
1121             int curSplit = 1;
1122             while (curSplit < numSplits && isPass) {
1123                 ofp = fopen(cdstPath, "r+");
1124                 if (ofp) {
1125                     muxer = AMediaMuxer_append(fileno(ofp), AMEDIAMUXER_APPEND_TO_EXISTING_DATA);
1126                     if (muxer) {
1127                         ssize_t trackCount = AMediaMuxer_getTrackCount(muxer);
1128                         if (trackCount > 0) {
1129                             decltype(trackCount) tc = 0;
1130                             while(tc < trackCount) {
1131                                 AMediaFormat* format = AMediaMuxer_getTrackFormat(muxer, tc);
1132                                 int64_t val = 0;
1133                                 if (AMediaFormat_getInt64(format,
1134                                             AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND, &val)) {
1135                                     ALOGV("sample-time-before-append:%lld", (long long)val);
1136                                 }
1137                                 ++tc;
1138                             }
1139                             mediaInfo->appendMedia(muxer, totalSampleCount*curSplit/numSplits,
1140                                                         totalSampleCount*(curSplit+1)/numSplits-1);
1141                         } else {
1142                             isPass = false;
1143                             ALOGE("no tracks in the file");
1144                         }
1145                         AMediaMuxer_delete(muxer);
1146                     } else {
1147                         isPass = false;
1148                         ALOGE("failed to create muxer");
1149                     }
1150                     fclose(ofp);
1151                     ofp = nullptr;
1152                     if (isPass) {
1153                         auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr);
1154                         isPass = mediaInfoDest->isSubsetOf(mediaInfo);
1155                         delete mediaInfoDest;
1156                     }
1157                 } else {
1158                     isPass = false;
1159                     ALOGE("failed to open output file %s", cdstPath);
1160                 }
1161                 ++curSplit;
1162             }
1163         }
1164         delete mediaInfo;
1165         env->ReleaseStringUTFChars(jdstPath, cdstPath);
1166         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
1167     } else {
1168         isPass = false;
1169     }
1170     return static_cast<jboolean>(isPass);
1171 }
1172 
1173 /* Checks if opening a file to append data and closing it without actually appending data
1174  * works good in all append modes.
1175  */
nativeTestNoSamples(JNIEnv * env,jobject,jint joutFormat,jstring jinPath,jstring joutPath)1176 static jboolean nativeTestNoSamples(JNIEnv* env, jobject, jint joutFormat, jstring jinPath,
1177                                         jstring joutPath) {
1178     bool isPass = true;
1179     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
1180         const char* cinPath = env->GetStringUTFChars(jinPath, nullptr);
1181         const char* coutPath = env->GetStringUTFChars(joutPath, nullptr);
1182         ALOGV("cinPath:%s", cinPath);
1183         ALOGV("coutPath:%s", coutPath);
1184         auto* mediaInfo = new MuxerNativeTestHelper(cinPath);
1185         for (unsigned int mode = AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP;
1186                     mode <= AMEDIAMUXER_APPEND_TO_EXISTING_DATA; ++mode) {
1187             if (mediaInfo->getTrackCount() != 0) {
1188                 // Create a new file and write media data to it.
1189                 FILE *ofp = fopen(coutPath, "wbe+");
1190                 if (ofp) {
1191                     AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat) joutFormat);
1192                     mediaInfo->muxMedia(muxer);
1193                     AMediaMuxer_delete(muxer);
1194                     fclose(ofp);
1195                 } else {
1196                     isPass = false;
1197                     ALOGE("failed to open output file %s", coutPath);
1198                 }
1199             } else {
1200                 isPass = false;
1201                 ALOGE("no tracks in input file");
1202             }
1203             ALOGV("after file close");
1204             FILE* ofp = fopen(coutPath, "r+");
1205             if (ofp) {
1206                 ALOGV("create append muxer");
1207                 // Open the new file in one of the append modes and close it without writing data.
1208                 AMediaMuxer *muxer = AMediaMuxer_append(fileno(ofp), (AppendMode)mode);
1209                 if (muxer) {
1210                     AMediaMuxer_start(muxer);
1211                     AMediaMuxer_stop(muxer);
1212                     ALOGV("delete append muxer");
1213                     AMediaMuxer_delete(muxer);
1214                 } else {
1215                     isPass = false;
1216                     ALOGE("failed to create muxer");
1217                 }
1218                 fclose(ofp);
1219                 ofp = nullptr;
1220             } else {
1221                 isPass = false;
1222                 ALOGE("failed to open output file to append %s", coutPath);
1223             }
1224             // Check if contents written in the new file match with contents in the original file.
1225             auto* mediaInfoOut = new MuxerNativeTestHelper(coutPath, nullptr);
1226             isPass = mediaInfoOut->isSubsetOf(mediaInfo);
1227             delete mediaInfoOut;
1228         }
1229         delete mediaInfo;
1230         env->ReleaseStringUTFChars(jinPath, cinPath);
1231         env->ReleaseStringUTFChars(joutPath, coutPath);
1232     } else {
1233         isPass = false;
1234     }
1235     return static_cast<jboolean>(isPass);
1236 }
1237 
1238 /*
1239  * Checks if appending media data in AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP mode works good.
1240  * Splits the contents of source file equally starting from one and increasing the number of
1241  * splits by one for every iteration.  Starts with writing first split into a new file and
1242  * appends the rest of the contents split by split.
1243  */
nativeTestIgnoreLastGOPAppend(JNIEnv * env,jobject,jint joutFormat,jstring jsrcPath,jstring jdstPath)1244 static jboolean nativeTestIgnoreLastGOPAppend(JNIEnv* env, jobject, jint joutFormat,
1245                                     jstring jsrcPath, jstring jdstPath) {
1246     bool isPass = true;
1247     if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
1248         const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
1249         const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
1250         ALOGV("csrcPath:%s", csrcPath);
1251         ALOGV("cdstPath:%s", cdstPath);
1252         auto* mediaInfo = new MuxerNativeTestHelper(csrcPath);
1253         for (int numSplits = 1; numSplits <= 5 && isPass; ++numSplits) {
1254             ALOGV("numSplits:%d", numSplits);
1255             size_t totalSampleCount = 0;
1256             size_t totalSamplesWritten = 0;
1257             AMediaMuxer *muxer = nullptr;
1258             FILE* ofp = fopen(cdstPath, "w+");
1259             if (ofp) {
1260                 // Start by writing first split into a new file.
1261                 muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)joutFormat);
1262                 if (muxer) {
1263                     for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) {
1264                         ALOGV("getSampleCount:%d:%zu", i, mediaInfo->getSampleCount(i));
1265                         totalSampleCount += mediaInfo->getSampleCount(i);
1266                     }
1267                     mediaInfo->appendMedia(muxer, 0, (totalSampleCount/numSplits)-1);
1268                     totalSamplesWritten += (totalSampleCount/numSplits);
1269                     AMediaMuxer_delete(muxer);
1270                 } else {
1271                     isPass = false;
1272                     ALOGE("Failed to create muxer");
1273                 }
1274                 fclose(ofp);
1275                 ofp = nullptr;
1276                 if (isPass) {
1277                     // Check if the contents in the new file is as same as in the source file.
1278                     auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr);
1279                     isPass = mediaInfoDest->isSubsetOf(mediaInfo);
1280                     delete mediaInfoDest;
1281                 }
1282             } else {
1283                 isPass = false;
1284                 ALOGE("failed to open output file %s", cdstPath);
1285             }
1286 
1287             // Append rest of the contents from the source file to the new file split by split.
1288             int curSplit = 1;
1289             while (curSplit < numSplits && isPass) {
1290                 ofp = fopen(cdstPath, "r+");
1291                 if (ofp) {
1292                     muxer = AMediaMuxer_append(fileno(ofp),
1293                                 AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP);
1294                     if (muxer) {
1295                         auto trackCount = AMediaMuxer_getTrackCount(muxer);
1296                         if (trackCount > 0) {
1297                             decltype(trackCount) tc = 0;
1298                             int64_t* appendFromTime = new int64_t[trackCount]{0};
1299                             while(tc < trackCount) {
1300                                 AMediaFormat* format = AMediaMuxer_getTrackFormat(muxer, tc);
1301                                 int64_t val = 0;
1302                                 if (AMediaFormat_getInt64(format,
1303                                             AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND, &val)) {
1304                                     ALOGV("sample-time-before-append:%lld", (long long)val);
1305                                     appendFromTime[tc] = val;
1306                                 }
1307                                 ++tc;
1308                             }
1309                             bool lastSplit = (curSplit == numSplits-1) ? true : false;
1310                             mediaInfo->appendMediaFromTime(muxer, appendFromTime,
1311                                 totalSampleCount/numSplits + ((curSplit-1) * 30), lastSplit);
1312                             delete[] appendFromTime;
1313                         } else {
1314                             isPass = false;
1315                             ALOGE("no tracks in the file");
1316                         }
1317                         AMediaMuxer_delete(muxer);
1318                     } else {
1319                         isPass = false;
1320                         ALOGE("failed to create muxer");
1321                     }
1322                     fclose(ofp);
1323                     ofp = nullptr;
1324                     if (isPass) {
1325                         auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr);
1326                         isPass = mediaInfoDest->isSubsetOf(mediaInfo);
1327                         delete mediaInfoDest;
1328                     }
1329                 } else {
1330                     isPass = false;
1331                     ALOGE("failed to open output file %s", cdstPath);
1332                 }
1333                 ++curSplit;
1334             }
1335         }
1336         delete mediaInfo;
1337         env->ReleaseStringUTFChars(jdstPath, cdstPath);
1338         env->ReleaseStringUTFChars(jsrcPath, csrcPath);
1339     } else {
1340         isPass = false;
1341     }
1342     return static_cast<jboolean>(isPass);
1343 }
1344 
registerAndroidMediaV2CtsMuxerTestApi(JNIEnv * env)1345 int registerAndroidMediaV2CtsMuxerTestApi(JNIEnv* env) {
1346     const JNINativeMethod methodTable[] = {
1347             {"nativeTestSetOrientationHint", "(ILjava/lang/String;Ljava/lang/String;)Z",
1348              (void*)nativeTestSetOrientationHint},
1349             {"nativeTestSetLocation", "(ILjava/lang/String;Ljava/lang/String;)Z",
1350              (void*)nativeTestSetLocation},
1351             {"nativeTestGetTrackCount", "(Ljava/lang/String;Ljava/lang/String;II)Z",
1352              (void*)nativeTestGetTrackCount},
1353             {"nativeTestGetTrackFormat", "(Ljava/lang/String;Ljava/lang/String;I)Z",
1354              (void*)nativeTestGetTrackFormat},
1355     };
1356     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestApi");
1357     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1358 }
1359 
registerAndroidMediaV2CtsMuxerTestMultiTrack(JNIEnv * env)1360 int registerAndroidMediaV2CtsMuxerTestMultiTrack(JNIEnv* env) {
1361     const JNINativeMethod methodTable[] = {
1362             {"nativeTestMultiTrack",
1363              "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
1364              (void*)nativeTestMultiTrack},
1365     };
1366     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestMultiTrack");
1367     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1368 }
1369 
registerAndroidMediaV2CtsMuxerTestOffsetPts(JNIEnv * env)1370 int registerAndroidMediaV2CtsMuxerTestOffsetPts(JNIEnv* env) {
1371     const JNINativeMethod methodTable[] = {
1372             {"nativeTestOffsetPts", "(ILjava/lang/String;Ljava/lang/String;[I)Z",
1373              (void*)nativeTestOffsetPts},
1374     };
1375     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestOffsetPts");
1376     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1377 }
1378 
registerAndroidMediaV2CtsMuxerTestSimpleMux(JNIEnv * env)1379 int registerAndroidMediaV2CtsMuxerTestSimpleMux(JNIEnv* env) {
1380     const JNINativeMethod methodTable[] = {
1381             {"nativeTestSimpleMux",
1382              "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
1383              (void*)nativeTestSimpleMux},
1384     };
1385     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestSimpleMux");
1386     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1387 }
1388 
registerAndroidMediaV2CtsMuxerTestSimpleAppend(JNIEnv * env)1389 int registerAndroidMediaV2CtsMuxerTestSimpleAppend(JNIEnv* env) {
1390     const JNINativeMethod methodTable[] = {
1391             {"nativeTestSimpleAppend",
1392              "(ILjava/lang/String;Ljava/lang/String;)Z",
1393              (void*)nativeTestSimpleAppend},
1394             {"nativeTestAppendGetTrackCount",
1395              "(Ljava/lang/String;I)Z",
1396              (void*)nativeTestAppendGetTrackCount},
1397             {"nativeTestAppendGetTrackFormat", "(Ljava/lang/String;)Z",
1398              (void*)nativeTestAppendGetTrackFormat},
1399              {"nativeTestNoSamples",
1400               "(ILjava/lang/String;Ljava/lang/String;)Z",
1401               (void*)nativeTestNoSamples},
1402             {"nativeTestIgnoreLastGOPAppend",
1403              "(ILjava/lang/String;Ljava/lang/String;)Z",
1404              (void*)nativeTestIgnoreLastGOPAppend},
1405     };
1406     jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestSimpleAppend");
1407     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
1408 }
1409 
1410 extern int registerAndroidMediaV2CtsMuxerUnitTestApi(JNIEnv* env);
1411 
JNI_OnLoad(JavaVM * vm,void *)1412 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
1413     JNIEnv* env;
1414     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
1415     if (registerAndroidMediaV2CtsMuxerTestApi(env) != JNI_OK) return JNI_ERR;
1416     if (registerAndroidMediaV2CtsMuxerTestMultiTrack(env) != JNI_OK) return JNI_ERR;
1417     if (registerAndroidMediaV2CtsMuxerTestOffsetPts(env) != JNI_OK) return JNI_ERR;
1418     if (registerAndroidMediaV2CtsMuxerTestSimpleMux(env) != JNI_OK) return JNI_ERR;
1419     if (registerAndroidMediaV2CtsMuxerTestSimpleAppend(env) != JNI_OK) return JNI_ERR;
1420     if (registerAndroidMediaV2CtsMuxerUnitTestApi(env) != JNI_OK) return JNI_ERR;
1421     return JNI_VERSION_1_6;
1422 }
1423