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