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