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