1 /*
2 * Copyright (C) 2020 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 "NativeCodecDecoderTest"
19 #include <log/log.h>
20 #include <android/native_window_jni.h>
21 #include <NdkMediaExtractor.h>
22 #include <jni.h>
23 #include <sys/stat.h>
24
25 #include <array>
26 #include <fstream>
27 #include <string>
28
29 #include "NativeCodecTestBase.h"
30 #include "NativeMediaCommon.h"
31
32 class CodecDecoderTest final : public CodecTestBase {
33 private:
34 bool mIsInterlaced;
35 uint8_t* mRefData;
36 size_t mRefLength;
37 AMediaExtractor* mExtractor;
38 AMediaFormat* mInpDecFormat;
39 AMediaFormat* mInpDecDupFormat;
40 std::vector<std::pair<void*, size_t>> mCsdBuffers;
41 int mCurrCsdIdx;
42 ANativeWindow* mWindow;
43
44 void setUpAudioReference(const char* refFile);
45 void deleteReference();
46 bool setUpExtractor(const char* srcFile);
47 void deleteExtractor();
48 bool configureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame,
49 bool isEncoder) override;
50 bool enqueueInput(size_t bufferIndex) override;
51 bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) override;
52 bool queueCodecConfig();
53 bool enqueueCodecConfig(int32_t bufferIndex);
54 bool decodeToMemory(const char* decoder, AMediaFormat* format, int frameLimit,
55 OutputManager* ref, int64_t pts, SeekMode mode);
56
57 public:
58 explicit CodecDecoderTest(const char* mime, ANativeWindow* window);
59 ~CodecDecoderTest();
60
61 bool testSimpleDecode(const char* decoder, const char* testFile, const char* refFile,
62 float rmsError, uLong checksum);
63 bool testFlush(const char* decoder, const char* testFile);
64 bool testOnlyEos(const char* decoder, const char* testFile);
65 bool testSimpleDecodeQueueCSD(const char* decoder, const char* testFile);
66 };
67
CodecDecoderTest(const char * mime,ANativeWindow * window)68 CodecDecoderTest::CodecDecoderTest(const char* mime, ANativeWindow* window)
69 : CodecTestBase(mime),
70 mRefData(nullptr),
71 mRefLength(0),
72 mExtractor(nullptr),
73 mInpDecFormat(nullptr),
74 mInpDecDupFormat(nullptr),
75 mCurrCsdIdx(0),
76 mWindow{window} {}
77
~CodecDecoderTest()78 CodecDecoderTest::~CodecDecoderTest() {
79 deleteReference();
80 deleteExtractor();
81 }
82
setUpAudioReference(const char * refFile)83 void CodecDecoderTest::setUpAudioReference(const char* refFile) {
84 FILE* fp = fopen(refFile, "rbe");
85 struct stat buf {};
86 if (fp && !fstat(fileno(fp), &buf)) {
87 deleteReference();
88 mRefLength = buf.st_size;
89 mRefData = new uint8_t[mRefLength];
90 fread(mRefData, sizeof(uint8_t), mRefLength, fp);
91 } else {
92 ALOGE("unable to open input file %s", refFile);
93 }
94 if (fp) fclose(fp);
95 }
96
deleteReference()97 void CodecDecoderTest::deleteReference() {
98 if (mRefData) {
99 delete[] mRefData;
100 mRefData = nullptr;
101 }
102 mRefLength = 0;
103 }
104
setUpExtractor(const char * srcFile)105 bool CodecDecoderTest::setUpExtractor(const char* srcFile) {
106 FILE* fp = fopen(srcFile, "rbe");
107 struct stat buf {};
108 if (fp && !fstat(fileno(fp), &buf)) {
109 deleteExtractor();
110 mExtractor = AMediaExtractor_new();
111 media_status_t res =
112 AMediaExtractor_setDataSourceFd(mExtractor, fileno(fp), 0, buf.st_size);
113 if (res != AMEDIA_OK) {
114 deleteExtractor();
115 } else {
116 for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(mExtractor);
117 trackID++) {
118 AMediaFormat* currFormat = AMediaExtractor_getTrackFormat(mExtractor, trackID);
119 const char* mime = nullptr;
120 AMediaFormat_getString(currFormat, AMEDIAFORMAT_KEY_MIME, &mime);
121 if (mime && strcmp(mMime, mime) == 0) {
122 AMediaExtractor_selectTrack(mExtractor, trackID);
123 if (!mIsAudio) {
124 AMediaFormat_setInt32(currFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
125 COLOR_FormatYUV420Flexible);
126 }
127 mInpDecFormat = currFormat;
128 // TODO: determine this from the extractor format when it becomes exposed.
129 mIsInterlaced = strstr(srcFile, "_interlaced_") != nullptr;
130 break;
131 }
132 AMediaFormat_delete(currFormat);
133 }
134 }
135 }
136 if (fp) fclose(fp);
137 return mInpDecFormat != nullptr;
138 }
139
deleteExtractor()140 void CodecDecoderTest::deleteExtractor() {
141 if (mExtractor) {
142 AMediaExtractor_delete(mExtractor);
143 mExtractor = nullptr;
144 }
145 if (mInpDecFormat) {
146 AMediaFormat_delete(mInpDecFormat);
147 mInpDecFormat = nullptr;
148 }
149 if (mInpDecDupFormat) {
150 AMediaFormat_delete(mInpDecDupFormat);
151 mInpDecDupFormat = nullptr;
152 }
153 }
154
configureCodec(AMediaFormat * format,bool isAsync,bool signalEOSWithLastFrame,bool isEncoder)155 bool CodecDecoderTest::configureCodec(AMediaFormat* format, bool isAsync,
156 bool signalEOSWithLastFrame, bool isEncoder) {
157 resetContext(isAsync, signalEOSWithLastFrame);
158 CHECK_STATUS(mAsyncHandle.setCallBack(mCodec, isAsync),
159 "AMediaCodec_setAsyncNotifyCallback failed");
160 CHECK_STATUS(AMediaCodec_configure(mCodec, format, mWindow, nullptr,
161 isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0),
162 "AMediaCodec_configure failed");
163 return true;
164 }
165
enqueueCodecConfig(int32_t bufferIndex)166 bool CodecDecoderTest::enqueueCodecConfig(int32_t bufferIndex) {
167 size_t bufSize;
168 uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize);
169 if (buf == nullptr) {
170 ALOGE("AMediaCodec_getInputBuffer failed");
171 return false;
172 }
173 void* csdBuffer = mCsdBuffers[mCurrCsdIdx].first;
174 size_t csdSize = mCsdBuffers[mCurrCsdIdx].second;
175 if (bufSize < csdSize) {
176 ALOGE("csd exceeds input buffer size, csdSize: %zu bufSize: %zu", csdSize, bufSize);
177 return false;
178 }
179 memcpy((void*)buf, csdBuffer, csdSize);
180 uint32_t flags = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;
181 CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, csdSize, 0, flags),
182 "AMediaCodec_queueInputBuffer failed");
183 return !hasSeenError();
184 }
185
enqueueInput(size_t bufferIndex)186 bool CodecDecoderTest::enqueueInput(size_t bufferIndex) {
187 if (AMediaExtractor_getSampleSize(mExtractor) < 0) {
188 return enqueueEOS(bufferIndex);
189 } else {
190 uint32_t flags = 0;
191 size_t bufSize;
192 uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize);
193 if (buf == nullptr) {
194 ALOGE("AMediaCodec_getInputBuffer failed");
195 return false;
196 }
197 ssize_t size = AMediaExtractor_getSampleSize(mExtractor);
198 int64_t pts = AMediaExtractor_getSampleTime(mExtractor);
199 if (size > bufSize) {
200 ALOGE("extractor sample size exceeds codec input buffer size %zu %zu", size, bufSize);
201 return false;
202 }
203 if (size != AMediaExtractor_readSampleData(mExtractor, buf, bufSize)) {
204 ALOGE("AMediaExtractor_readSampleData failed");
205 return false;
206 }
207 if (!AMediaExtractor_advance(mExtractor) && mSignalEOSWithLastFrame) {
208 flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
209 mSawInputEOS = true;
210 }
211 CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, size, pts, flags),
212 "AMediaCodec_queueInputBuffer failed");
213 ALOGV("input: id: %zu size: %zu pts: %" PRId64 " flags: %d", bufferIndex, size, pts,
214 flags);
215 if (size > 0) {
216 mOutputBuff->saveInPTS(pts);
217 mInputCount++;
218 }
219 }
220 return !hasSeenError();
221 }
222
dequeueOutput(size_t bufferIndex,AMediaCodecBufferInfo * info)223 bool CodecDecoderTest::dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* info) {
224 if ((info->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
225 mSawOutputEOS = true;
226 }
227 if (info->size > 0 && (info->flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) {
228 if (mSaveToMem) {
229 size_t buffSize;
230 uint8_t* buf = AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize);
231 if (mIsAudio) {
232 mOutputBuff->saveToMemory(buf, info);
233 mOutputBuff->updateChecksum(buf, info);
234 } else {
235 AMediaFormat* format =
236 mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() : mOutFormat;
237 int32_t width, height, stride;
238 AMediaFormat_getInt32(format, "width", &width);
239 AMediaFormat_getInt32(format, "height", &height);
240 AMediaFormat_getInt32(format, "stride", &stride);
241 mOutputBuff->updateChecksum(buf, info, width, height, stride);
242 }
243 }
244 mOutputBuff->saveOutPTS(info->presentationTimeUs);
245 mOutputCount++;
246 }
247 ALOGV("output: id: %zu size: %d pts: %" PRId64 " flags: %d", bufferIndex, info->size,
248 info->presentationTimeUs, info->flags);
249 CHECK_STATUS(AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, mWindow != nullptr),
250 "AMediaCodec_releaseOutputBuffer failed");
251 return !hasSeenError();
252 }
253
queueCodecConfig()254 bool CodecDecoderTest::queueCodecConfig() {
255 bool isOk = true;
256 if (mIsCodecInAsyncMode) {
257 for (mCurrCsdIdx = 0; !hasSeenError() && isOk && mCurrCsdIdx < mCsdBuffers.size();
258 mCurrCsdIdx++) {
259 callbackObject element = mAsyncHandle.getInput();
260 if (element.bufferIndex >= 0) {
261 isOk = enqueueCodecConfig(element.bufferIndex);
262 }
263 }
264 } else {
265 int bufferIndex;
266 for (mCurrCsdIdx = 0; isOk && mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) {
267 bufferIndex = AMediaCodec_dequeueInputBuffer(mCodec, -1);
268 if (bufferIndex >= 0) {
269 isOk = enqueueCodecConfig(bufferIndex);
270 } else {
271 ALOGE("unexpected return value from *_dequeueInputBuffer: %d", bufferIndex);
272 return false;
273 }
274 }
275 }
276 return !hasSeenError() && isOk;
277 }
278
decodeToMemory(const char * decoder,AMediaFormat * format,int frameLimit,OutputManager * ref,int64_t pts,SeekMode mode)279 bool CodecDecoderTest::decodeToMemory(const char* decoder, AMediaFormat* format, int frameLimit,
280 OutputManager* ref, int64_t pts, SeekMode mode) {
281 mSaveToMem = (mWindow == nullptr);
282 mOutputBuff = ref;
283 AMediaExtractor_seekTo(mExtractor, pts, mode);
284 mCodec = AMediaCodec_createCodecByName(decoder);
285 if (!mCodec) {
286 ALOGE("unable to create codec %s", decoder);
287 return false;
288 }
289 if (!configureCodec(format, false, true, false)) return false;
290 CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
291 if (!doWork(frameLimit)) return false;
292 if (!queueEOS()) return false;
293 if (!waitForAllOutputs()) return false;
294 CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
295 CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
296 mCodec = nullptr;
297 mSaveToMem = false;
298 return !hasSeenError();
299 }
300
testSimpleDecode(const char * decoder,const char * testFile,const char * refFile,float rmsError,uLong checksum)301 bool CodecDecoderTest::testSimpleDecode(const char* decoder, const char* testFile,
302 const char* refFile, float rmsError, uLong checksum) {
303 bool isPass = true;
304 if (!setUpExtractor(testFile)) return false;
305 mSaveToMem = (mWindow == nullptr);
306 auto ref = &mRefBuff;
307 auto test = &mTestBuff;
308 const bool boolStates[]{true, false};
309 int loopCounter = 0;
310 for (auto eosType : boolStates) {
311 if (!isPass) break;
312 for (auto isAsync : boolStates) {
313 if (!isPass) break;
314 bool validateFormat = true;
315 char log[1000];
316 snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s, eos type: %s:: \n",
317 decoder, testFile, (isAsync ? "async" : "sync"),
318 (eosType ? "eos with last frame" : "eos separate"));
319 mOutputBuff = loopCounter == 0 ? ref : test;
320 mOutputBuff->reset();
321 AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
322 /* TODO(b/147348711) */
323 /* Instead of create and delete codec at every iteration, we would like to create
324 * once and use it for all iterations and delete before exiting */
325 mCodec = AMediaCodec_createCodecByName(decoder);
326 if (!mCodec) {
327 ALOGE("unable to create codec %s", decoder);
328 return false;
329 }
330 char* name = nullptr;
331 if (AMEDIA_OK == AMediaCodec_getName(mCodec, &name)) {
332 if (!name || strcmp(name, decoder) != 0) {
333 ALOGE("%s error codec-name act/got: %s/%s", log, name, decoder);
334 if (name) AMediaCodec_releaseName(mCodec, name);
335 return false;
336 }
337 } else {
338 ALOGE("AMediaCodec_getName failed unexpectedly");
339 return false;
340 }
341 if (name) AMediaCodec_releaseName(mCodec, name);
342 if (!configureCodec(mInpDecFormat, isAsync, eosType, false)) return false;
343 AMediaFormat* decFormat = AMediaCodec_getOutputFormat(mCodec);
344 if (isFormatSimilar(mInpDecFormat, decFormat)) {
345 ALOGD("Input format is same as default for format for %s", decoder);
346 validateFormat = false;
347 }
348 AMediaFormat_delete(decFormat);
349 CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
350 if (!doWork(INT32_MAX)) return false;
351 if (!queueEOS()) return false;
352 if (!waitForAllOutputs()) return false;
353 CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
354 CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
355 mCodec = nullptr;
356 CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
357 CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
358 CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
359 CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
360 CHECK_ERR(
361 loopCounter == 0 && mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
362 log, "pts is not strictly increasing", isPass);
363 // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
364 // produce multiple progressive frames?) For now, do not verify timestamps.
365 if (!mIsInterlaced) {
366 CHECK_ERR(loopCounter == 0 && !mIsAudio &&
367 (!ref->isOutPtsListIdenticalToInpPtsList(false)),
368 log, "input pts list and output pts list are not identical", isPass);
369 }
370 if (validateFormat) {
371 if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
372 : !mSignalledOutFormatChanged) {
373 ALOGE("%s%s", log, "not received format change");
374 isPass = false;
375 } else if (!isFormatSimilar(mInpDecFormat, mIsCodecInAsyncMode
376 ? mAsyncHandle.getOutputFormat()
377 : mOutFormat)) {
378 ALOGE("%s%s", log, "configured format and output format are not similar");
379 isPass = false;
380 }
381 }
382 if (checksum != ref->getChecksum()) {
383 ALOGE("%s%s", log, "sdk output and ndk output differ");
384 isPass = false;
385 }
386 loopCounter++;
387 }
388 }
389 if (mSaveToMem && refFile && rmsError >= 0) {
390 setUpAudioReference(refFile);
391 float currError = ref->getRmsError(mRefData, mRefLength);
392 float errMargin = rmsError * kRmsErrorTolerance;
393 if (currError > errMargin) {
394 isPass = false;
395 ALOGE("rms error too high for file %s, ref/exp/got: %f/%f/%f", testFile, rmsError,
396 errMargin, currError);
397 }
398 }
399 return isPass;
400 }
401
testFlush(const char * decoder,const char * testFile)402 bool CodecDecoderTest::testFlush(const char* decoder, const char* testFile) {
403 bool isPass = true;
404 if (!setUpExtractor(testFile)) return false;
405 mCsdBuffers.clear();
406 for (int i = 0;; i++) {
407 char csdName[16];
408 void* csdBuffer;
409 size_t csdSize;
410 snprintf(csdName, sizeof(csdName), "csd-%d", i);
411 if (AMediaFormat_getBuffer(mInpDecFormat, csdName, &csdBuffer, &csdSize)) {
412 mCsdBuffers.push_back(std::make_pair(csdBuffer, csdSize));
413 } else break;
414 }
415 const int64_t pts = 500000;
416 const SeekMode mode = AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC;
417 auto ref = &mRefBuff;
418 if (!decodeToMemory(decoder, mInpDecFormat, INT32_MAX, ref, pts, mode)) {
419 ALOGE("decodeToMemory failed for file: %s codec: %s", testFile, decoder);
420 return false;
421 }
422 CHECK_ERR(mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)), "",
423 "pts is not strictly increasing", isPass);
424 // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
425 // produce multiple progressive frames?) For now, do not verify timestamps.
426 if (!mIsInterlaced) {
427 CHECK_ERR(!mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(false)), "",
428 "input pts list and output pts list are not identical", isPass);
429 }
430 if (!isPass) return false;
431
432 auto test = &mTestBuff;
433 mOutputBuff = test;
434 const bool boolStates[]{true, false};
435 for (auto isAsync : boolStates) {
436 if (!isPass) break;
437 char log[1000];
438 snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s:: \n", decoder, testFile,
439 (isAsync ? "async" : "sync"));
440 /* TODO(b/147348711) */
441 /* Instead of create and delete codec at every iteration, we would like to create
442 * once and use it for all iterations and delete before exiting */
443 mCodec = AMediaCodec_createCodecByName(decoder);
444 if (!mCodec) {
445 ALOGE("unable to create codec %s", decoder);
446 return false;
447 }
448 AMediaExtractor_seekTo(mExtractor, 0, mode);
449 if (!configureCodec(mInpDecFormat, isAsync, true, false)) return false;
450 AMediaFormat* defFormat = AMediaCodec_getOutputFormat(mCodec);
451 bool validateFormat = true;
452 if (isFormatSimilar(mInpDecFormat, defFormat)) {
453 ALOGD("Input format is same as default for format for %s", decoder);
454 validateFormat = false;
455 }
456 AMediaFormat_delete(defFormat);
457 CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
458
459 /* test flush in running state before queuing input */
460 if (!flushCodec()) return false;
461 if (mIsCodecInAsyncMode) {
462 CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
463 }
464 if (!queueCodecConfig()) return false; /* flushed codec too soon, resubmit csd */
465 if (!doWork(1)) return false;
466
467 if (!flushCodec()) return false;
468 if (mIsCodecInAsyncMode) {
469 CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
470 }
471 if (!queueCodecConfig()) return false; /* flushed codec too soon, resubmit csd */
472 AMediaExtractor_seekTo(mExtractor, 0, mode);
473 test->reset();
474 if (!doWork(23)) return false;
475 CHECK_ERR(!test->isPtsStrictlyIncreasing(mPrevOutputPts), "",
476 "pts is not strictly increasing", isPass);
477
478 /* test flush in running state */
479 if (!flushCodec()) return false;
480 if (mIsCodecInAsyncMode) {
481 CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
482 }
483 mSaveToMem = (mWindow == nullptr);
484 test->reset();
485 AMediaExtractor_seekTo(mExtractor, pts, mode);
486 if (!doWork(INT32_MAX)) return false;
487 if (!queueEOS()) return false;
488 if (!waitForAllOutputs()) return false;
489 CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
490 CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
491 CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
492 CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
493 if (!isPass) continue;
494
495 /* test flush in eos state */
496 if (!flushCodec()) return false;
497 if (mIsCodecInAsyncMode) {
498 CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
499 }
500 test->reset();
501 AMediaExtractor_seekTo(mExtractor, pts, mode);
502 if (!doWork(INT32_MAX)) return false;
503 if (!queueEOS()) return false;
504 if (!waitForAllOutputs()) return false;
505 CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
506 CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
507 mCodec = nullptr;
508 CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
509 CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
510 CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
511 CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
512 if (validateFormat) {
513 if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
514 : !mSignalledOutFormatChanged) {
515 ALOGE("%s%s", log, "not received format change");
516 isPass = false;
517 } else if (!isFormatSimilar(mInpDecFormat, mIsCodecInAsyncMode
518 ? mAsyncHandle.getOutputFormat()
519 : mOutFormat)) {
520 ALOGE("%s%s", log, "configured format and output format are not similar");
521 isPass = false;
522 }
523 }
524 mSaveToMem = false;
525 }
526 return isPass;
527 }
528
testOnlyEos(const char * decoder,const char * testFile)529 bool CodecDecoderTest::testOnlyEos(const char* decoder, const char* testFile) {
530 bool isPass = true;
531 if (!setUpExtractor(testFile)) return false;
532 mSaveToMem = (mWindow == nullptr);
533 auto ref = &mRefBuff;
534 auto test = &mTestBuff;
535 const bool boolStates[]{true, false};
536 int loopCounter = 0;
537 for (auto isAsync : boolStates) {
538 if (!isPass) break;
539 char log[1000];
540 snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s:: \n", decoder, testFile,
541 (isAsync ? "async" : "sync"));
542 mOutputBuff = loopCounter == 0 ? ref : test;
543 mOutputBuff->reset();
544 /* TODO(b/147348711) */
545 /* Instead of create and delete codec at every iteration, we would like to create
546 * once and use it for all iterations and delete before exiting */
547 mCodec = AMediaCodec_createCodecByName(decoder);
548 if (!mCodec) {
549 ALOGE("unable to create codec %s", decoder);
550 return false;
551 }
552 if (!configureCodec(mInpDecFormat, isAsync, false, false)) return false;
553 CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
554 if (!queueEOS()) return false;
555 if (!waitForAllOutputs()) return false;
556 CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
557 CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
558 mCodec = nullptr;
559 CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
560 CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
561 CHECK_ERR(loopCounter == 0 && mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
562 log, "pts is not strictly increasing", isPass);
563 CHECK_ERR(loopCounter == 0 && !mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(false)),
564 log, "input pts list and output pts list are not identical", isPass);
565 loopCounter++;
566 }
567 return isPass;
568 }
569
testSimpleDecodeQueueCSD(const char * decoder,const char * testFile)570 bool CodecDecoderTest::testSimpleDecodeQueueCSD(const char* decoder, const char* testFile) {
571 bool isPass = true;
572 if (!setUpExtractor(testFile)) return false;
573 std::vector<AMediaFormat*> formats;
574 formats.push_back(mInpDecFormat);
575 mInpDecDupFormat = AMediaFormat_new();
576 AMediaFormat_copy(mInpDecDupFormat, mInpDecFormat);
577 formats.push_back(mInpDecDupFormat);
578 mCsdBuffers.clear();
579 for (int i = 0;; i++) {
580 char csdName[16];
581 void* csdBuffer;
582 size_t csdSize;
583 snprintf(csdName, sizeof(csdName), "csd-%d", i);
584 if (AMediaFormat_getBuffer(mInpDecDupFormat, csdName, &csdBuffer, &csdSize)) {
585 mCsdBuffers.push_back(std::make_pair(csdBuffer, csdSize));
586 AMediaFormat_setBuffer(mInpDecFormat, csdName, nullptr, 0);
587 } else break;
588 }
589
590 const bool boolStates[]{true, false};
591 mSaveToMem = true;
592 auto ref = &mRefBuff;
593 auto test = &mTestBuff;
594 int loopCounter = 0;
595 for (int i = 0; i < formats.size() && isPass; i++) {
596 auto fmt = formats[i];
597 for (auto eosType : boolStates) {
598 if (!isPass) break;
599 for (auto isAsync : boolStates) {
600 if (!isPass) break;
601 bool validateFormat = true;
602 char log[1000];
603 snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s, eos type: %s:: \n",
604 decoder, testFile, (isAsync ? "async" : "sync"),
605 (eosType ? "eos with last frame" : "eos separate"));
606 mOutputBuff = loopCounter == 0 ? ref : test;
607 mOutputBuff->reset();
608 /* TODO(b/147348711) */
609 /* Instead of create and delete codec at every iteration, we would like to create
610 * once and use it for all iterations and delete before exiting */
611 mCodec = AMediaCodec_createCodecByName(decoder);
612 if (!mCodec) {
613 ALOGE("unable to create codec");
614 return false;
615 }
616 AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
617 if (!configureCodec(fmt, isAsync, eosType, false)) return false;
618 AMediaFormat* defFormat = AMediaCodec_getOutputFormat(mCodec);
619 if (isFormatSimilar(defFormat, mInpDecFormat)) {
620 ALOGD("Input format is same as default for format for %s", decoder);
621 validateFormat = false;
622 }
623 AMediaFormat_delete(defFormat);
624 CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
625 /* formats[0] doesn't contain csd-data, so queuing csd separately, formats[1]
626 * contain csd-data */
627 if (i == 0 && !queueCodecConfig()) return false;
628 if (!doWork(INT32_MAX)) return false;
629 if (!queueEOS()) return false;
630 if (!waitForAllOutputs()) return false;
631 CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
632 CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
633 mCodec = nullptr;
634 CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
635 CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
636 CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
637 CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
638 CHECK_ERR(loopCounter == 0 && mIsAudio &&
639 (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
640 log, "pts is not strictly increasing", isPass);
641 CHECK_ERR(loopCounter == 0 && !mIsAudio &&
642 (!ref->isOutPtsListIdenticalToInpPtsList(false)),
643 log, "input pts list and output pts list are not identical", isPass);
644 if (validateFormat) {
645 if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
646 : !mSignalledOutFormatChanged) {
647 ALOGE("%s%s", log, "not received format change");
648 isPass = false;
649 } else if (!isFormatSimilar(mInpDecFormat,
650 mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat()
651 : mOutFormat)) {
652 ALOGE("%s%s", log, "configured format and output format are not similar");
653 isPass = false;
654 }
655 }
656 loopCounter++;
657 }
658 }
659 }
660 mSaveToMem = false;
661 return isPass;
662 }
663
nativeTestSimpleDecode(JNIEnv * env,jobject,jstring jDecoder,jobject surface,jstring jMime,jstring jtestFile,jstring jrefFile,jfloat jrmsError,jlong jChecksum)664 static jboolean nativeTestSimpleDecode(JNIEnv* env, jobject, jstring jDecoder, jobject surface,
665 jstring jMime, jstring jtestFile, jstring jrefFile,
666 jfloat jrmsError, jlong jChecksum) {
667 const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
668 const char* cMime = env->GetStringUTFChars(jMime, nullptr);
669 const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
670 const char* cRefFile = env->GetStringUTFChars(jrefFile, nullptr);
671 float cRmsError = jrmsError;
672 uLong cChecksum = jChecksum;
673 ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr;
674 auto* codecDecoderTest = new CodecDecoderTest(cMime, window);
675 bool isPass =
676 codecDecoderTest->testSimpleDecode(cDecoder, cTestFile, cRefFile, cRmsError, cChecksum);
677 delete codecDecoderTest;
678 if (window) {
679 ANativeWindow_release(window);
680 window = nullptr;
681 }
682 env->ReleaseStringUTFChars(jDecoder, cDecoder);
683 env->ReleaseStringUTFChars(jMime, cMime);
684 env->ReleaseStringUTFChars(jtestFile, cTestFile);
685 env->ReleaseStringUTFChars(jrefFile, cRefFile);
686 return static_cast<jboolean>(isPass);
687 }
688
nativeTestOnlyEos(JNIEnv * env,jobject,jstring jDecoder,jstring jMime,jstring jtestFile)689 static jboolean nativeTestOnlyEos(JNIEnv* env, jobject, jstring jDecoder, jstring jMime,
690 jstring jtestFile) {
691 const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
692 const char* cMime = env->GetStringUTFChars(jMime, nullptr);
693 const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
694 auto* codecDecoderTest = new CodecDecoderTest(cMime, nullptr);
695 bool isPass = codecDecoderTest->testOnlyEos(cDecoder, cTestFile);
696 delete codecDecoderTest;
697 env->ReleaseStringUTFChars(jDecoder, cDecoder);
698 env->ReleaseStringUTFChars(jMime, cMime);
699 env->ReleaseStringUTFChars(jtestFile, cTestFile);
700 return static_cast<jboolean>(isPass);
701 }
702
nativeTestFlush(JNIEnv * env,jobject,jstring jDecoder,jobject surface,jstring jMime,jstring jtestFile)703 static jboolean nativeTestFlush(JNIEnv* env, jobject, jstring jDecoder, jobject surface,
704 jstring jMime, jstring jtestFile) {
705 const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
706 const char* cMime = env->GetStringUTFChars(jMime, nullptr);
707 const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
708 ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr;
709 auto* codecDecoderTest = new CodecDecoderTest(cMime, window);
710 bool isPass = codecDecoderTest->testFlush(cDecoder, cTestFile);
711 delete codecDecoderTest;
712 if (window) {
713 ANativeWindow_release(window);
714 window = nullptr;
715 }
716 env->ReleaseStringUTFChars(jDecoder, cDecoder);
717 env->ReleaseStringUTFChars(jMime, cMime);
718 env->ReleaseStringUTFChars(jtestFile, cTestFile);
719 return static_cast<jboolean>(isPass);
720 }
721
nativeTestSimpleDecodeQueueCSD(JNIEnv * env,jobject,jstring jDecoder,jstring jMime,jstring jtestFile)722 static jboolean nativeTestSimpleDecodeQueueCSD(JNIEnv* env, jobject, jstring jDecoder,
723 jstring jMime, jstring jtestFile) {
724 const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
725 const char* cMime = env->GetStringUTFChars(jMime, nullptr);
726 const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
727 auto codecDecoderTest = new CodecDecoderTest(cMime, nullptr);
728 bool isPass = codecDecoderTest->testSimpleDecodeQueueCSD(cDecoder, cTestFile);
729 delete codecDecoderTest;
730 env->ReleaseStringUTFChars(jDecoder, cDecoder);
731 env->ReleaseStringUTFChars(jMime, cMime);
732 env->ReleaseStringUTFChars(jtestFile, cTestFile);
733 return static_cast<jboolean>(isPass);
734 }
735
registerAndroidMediaV2CtsDecoderTest(JNIEnv * env)736 int registerAndroidMediaV2CtsDecoderTest(JNIEnv* env) {
737 const JNINativeMethod methodTable[] = {
738 {"nativeTestSimpleDecode",
739 "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;Ljava/"
740 "lang/String;FJ)Z",
741 (void*)nativeTestSimpleDecode},
742 {"nativeTestOnlyEos", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
743 (void*)nativeTestOnlyEos},
744 {"nativeTestFlush",
745 "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;)Z",
746 (void*)nativeTestFlush},
747 {"nativeTestSimpleDecodeQueueCSD",
748 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
749 (void*)nativeTestSimpleDecodeQueueCSD},
750 };
751 jclass c = env->FindClass("android/mediav2/cts/CodecDecoderTest");
752 return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
753 }
754
registerAndroidMediaV2CtsDecoderSurfaceTest(JNIEnv * env)755 int registerAndroidMediaV2CtsDecoderSurfaceTest(JNIEnv* env) {
756 const JNINativeMethod methodTable[] = {
757 {"nativeTestSimpleDecode",
758 "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;Ljava/"
759 "lang/String;FJ)Z",
760 (void*)nativeTestSimpleDecode},
761 {"nativeTestFlush",
762 "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;)Z",
763 (void*)nativeTestFlush},
764 };
765 jclass c = env->FindClass("android/mediav2/cts/CodecDecoderSurfaceTest");
766 return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
767 }
768