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 "NativeCodecEncoderTest"
19 #include <log/log.h>
20
21 #include <jni.h>
22 #include <sys/stat.h>
23
24 #include "NativeCodecTestBase.h"
25 #include "NativeMediaCommon.h"
26
27 class CodecEncoderTest final : public CodecTestBase {
28 private:
29 uint8_t* mInputData;
30 size_t mInputLength;
31 int mInputBufferReadOffset;
32 int mNumBytesSubmitted;
33 int mLoopBackFrameLimit;
34 bool mIsLoopBack;
35 int64_t mInputOffsetPts;
36 std::vector<AMediaFormat*> mFormats;
37 int mNumSyncFramesReceived;
38 std::vector<int> mSyncFramesPos;
39
40 int mWidth, mHeight;
41 int mChannels;
42 int mSampleRate;
43 int mColorFormat;
44 int mMaxBFrames;
45 int mDefFrameRate;
46 const int kInpFrmWidth = 352;
47 const int kInpFrmHeight = 288;
48
49 void convertyuv420ptoyuv420sp();
50 void setUpSource(const char* srcPath);
51 void deleteSource();
52 void deleteParams();
53 bool configureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame,
54 bool isEncoder) override;
55 void resetContext(bool isAsync, bool signalEOSWithLastFrame) override;
56 bool enqueueInput(size_t bufferIndex) override;
57 bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) override;
58 bool doWork(int frameLimit) override;
59 bool isTestStateValid() override;
60 bool initFormat(AMediaFormat* format);
61 bool encodeToMemory(const char* file, const char* encoder, int frameLimit, AMediaFormat* format,
62 OutputManager* ref);
63 void fillByteBuffer(uint8_t* inputBuffer);
64 void forceSyncFrame(AMediaFormat* format);
65 void updateBitrate(AMediaFormat* format, int bitrate);
66
67 public:
68 CodecEncoderTest(const char* mediaType, const char* cfgParams, const char* cfgReconfigParams,
69 const char* separator);
70 ~CodecEncoderTest();
71
72 bool testSimpleEncode(const char* encoder, const char* srcPath);
73 bool testReconfigure(const char* encoder, const char* srcPath);
74 bool testSetForceSyncFrame(const char* encoder, const char* srcPath);
75 bool testAdaptiveBitRate(const char* encoder, const char* srcPath);
76 bool testOnlyEos(const char* encoder);
77 };
78
CodecEncoderTest(const char * mediaType,const char * cfgParams,const char * cfgReconfigParams,const char * separator)79 CodecEncoderTest::CodecEncoderTest(const char* mediaType, const char* cfgParams,
80 const char* cfgReconfigParams, const char* separator)
81 : CodecTestBase(mediaType) {
82 mFormats.push_back(deSerializeMediaFormat(cfgParams, separator));
83 if (cfgReconfigParams) {
84 mFormats.push_back(deSerializeMediaFormat(cfgReconfigParams, separator));
85 }
86 if (mIsVideo && mFormats[0] != nullptr) {
87 AMediaFormat_getInt32(mFormats[0], AMEDIAFORMAT_KEY_COLOR_FORMAT, &mColorFormat);
88 }
89 mInputData = nullptr;
90 mInputLength = 0;
91 mInputBufferReadOffset = 0;
92 mNumBytesSubmitted = 0;
93 mLoopBackFrameLimit = 0;
94 mIsLoopBack = false;
95 mInputOffsetPts = 0;
96 }
97
~CodecEncoderTest()98 CodecEncoderTest::~CodecEncoderTest() {
99 deleteSource();
100 deleteParams();
101 }
102
convertyuv420ptoyuv420sp()103 void CodecEncoderTest::convertyuv420ptoyuv420sp() {
104 int ySize = kInpFrmWidth * kInpFrmHeight;
105 int uSize = kInpFrmWidth * kInpFrmHeight / 4;
106 int frameSize = ySize + uSize * 2;
107 int totalFrames = mInputLength / frameSize;
108 uint8_t* u = new uint8_t[uSize];
109 uint8_t* v = new uint8_t[uSize];
110 uint8_t* frameBase = mInputData;
111 for (int i = 0; i < totalFrames; i++) {
112 uint8_t* uvBase = frameBase + ySize;
113 memcpy(u, uvBase, uSize);
114 memcpy(v, uvBase + uSize, uSize);
115 for (int j = 0, idx = 0; j < uSize; j++, idx += 2) {
116 uvBase[idx] = u[j];
117 uvBase[idx + 1] = v[j];
118 }
119 frameBase += frameSize;
120 }
121 delete[] u;
122 delete[] v;
123 }
124
setUpSource(const char * srcPath)125 void CodecEncoderTest::setUpSource(const char* srcPath) {
126 FILE* fp = fopen(srcPath, "rbe");
127 struct stat buf {};
128 if (fp && !fstat(fileno(fp), &buf)) {
129 deleteSource();
130 mInputLength = buf.st_size;
131 mInputData = new uint8_t[mInputLength];
132 fread(mInputData, sizeof(uint8_t), mInputLength, fp);
133 if (mColorFormat == COLOR_FormatYUV420SemiPlanar) {
134 convertyuv420ptoyuv420sp();
135 }
136 } else {
137 ALOGE("unable to open input file %s", srcPath);
138 }
139 if (fp) fclose(fp);
140 }
141
deleteSource()142 void CodecEncoderTest::deleteSource() {
143 if (mInputData) {
144 delete[] mInputData;
145 mInputData = nullptr;
146 }
147 mInputLength = 0;
148 }
149
deleteParams()150 void CodecEncoderTest::deleteParams() {
151 for (auto format : mFormats) AMediaFormat_delete(format);
152 mFormats.clear();
153 }
154
configureCodec(AMediaFormat * format,bool isAsync,bool signalEOSWithLastFrame,bool isEncoder)155 bool CodecEncoderTest::configureCodec(AMediaFormat* format, bool isAsync,
156 bool signalEOSWithLastFrame, bool isEncoder) {
157 if (!initFormat(format)) return false;
158 return CodecTestBase::configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder);
159 }
160
resetContext(bool isAsync,bool signalEOSWithLastFrame)161 void CodecEncoderTest::resetContext(bool isAsync, bool signalEOSWithLastFrame) {
162 CodecTestBase::resetContext(isAsync, signalEOSWithLastFrame);
163 mInputBufferReadOffset = 0;
164 mNumBytesSubmitted = 0;
165 mInputOffsetPts = 0;
166 mNumSyncFramesReceived = 0;
167 mSyncFramesPos.clear();
168 }
169
fillByteBuffer(uint8_t * inputBuffer)170 void CodecEncoderTest::fillByteBuffer(uint8_t* inputBuffer) {
171 int width, height, tileWidth, tileHeight;
172 int offset = 0, frmOffset = mInputBufferReadOffset;
173 int numOfPlanes;
174 if (mColorFormat == COLOR_FormatYUV420SemiPlanar) {
175 numOfPlanes = 2;
176 } else {
177 numOfPlanes = 3;
178 }
179 for (int plane = 0; plane < numOfPlanes; plane++) {
180 if (plane == 0) {
181 width = mWidth;
182 height = mHeight;
183 tileWidth = kInpFrmWidth;
184 tileHeight = kInpFrmHeight;
185 } else {
186 if (mColorFormat == COLOR_FormatYUV420SemiPlanar) {
187 width = mWidth;
188 tileWidth = kInpFrmWidth;
189 } else {
190 width = mWidth / 2;
191 tileWidth = kInpFrmWidth / 2;
192 }
193 height = mHeight / 2;
194 tileHeight = kInpFrmHeight / 2;
195 }
196 for (int k = 0; k < height; k += tileHeight) {
197 int rowsToCopy = std::min(height - k, tileHeight);
198 for (int j = 0; j < rowsToCopy; j++) {
199 for (int i = 0; i < width; i += tileWidth) {
200 int colsToCopy = std::min(width - i, tileWidth);
201 memcpy(inputBuffer + (offset + (k + j) * width + i),
202 mInputData + (frmOffset + j * tileWidth), colsToCopy);
203 }
204 }
205 }
206 offset += width * height;
207 frmOffset += tileWidth * tileHeight;
208 }
209 }
210
enqueueInput(size_t bufferIndex)211 bool CodecEncoderTest::enqueueInput(size_t bufferIndex) {
212 if (mInputBufferReadOffset >= mInputLength) {
213 if (!mIsLoopBack) return enqueueEOS(bufferIndex);
214 mInputBufferReadOffset = 0; // loop back to beginning
215 }
216 {
217 int size = 0;
218 int flags = 0;
219 int64_t pts = mInputOffsetPts;
220 size_t buffSize;
221 uint8_t* inputBuffer = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &buffSize);
222 RETURN_IF_NULL(inputBuffer, std::string{"AMediaCodec_getInputBuffer returned nullptr"})
223 if (mIsAudio) {
224 pts += mNumBytesSubmitted * 1000000LL / (2 * mChannels * mSampleRate);
225 size = std::min(buffSize, mInputLength - mInputBufferReadOffset);
226 memcpy(inputBuffer, mInputData + mInputBufferReadOffset, size);
227 if (mSignalEOSWithLastFrame) {
228 if (mIsLoopBack ? (mInputCount + 1 >= mLoopBackFrameLimit)
229 : (mInputBufferReadOffset + size >= mInputLength)) {
230 flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
231 mSawInputEOS = true;
232 }
233 }
234 mInputBufferReadOffset += size;
235 } else {
236 pts += mInputCount * 1000000LL / mDefFrameRate;
237 size = mWidth * mHeight * 3 / 2;
238 int frmSize = kInpFrmWidth * kInpFrmHeight * 3 / 2;
239 RETURN_IF_TRUE(mInputBufferReadOffset + frmSize > mInputLength,
240 std::string{"received partial frame to encode"})
241 RETURN_IF_TRUE(size > buffSize,
242 StringFormat("frame size exceeds buffer capacity of input buffer %d %zu",
243 size, buffSize))
244 if (mWidth == kInpFrmWidth && mHeight == kInpFrmHeight) {
245 memcpy(inputBuffer, mInputData + mInputBufferReadOffset, size);
246 } else {
247 fillByteBuffer(inputBuffer);
248 }
249 if (mSignalEOSWithLastFrame) {
250 if (mIsLoopBack ? (mInputCount + 1 >= mLoopBackFrameLimit)
251 : (mInputBufferReadOffset + frmSize >= mInputLength)) {
252 flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
253 mSawInputEOS = true;
254 }
255 }
256 mInputBufferReadOffset += frmSize;
257 }
258 mNumBytesSubmitted += size;
259 RETURN_IF_FAIL(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, size, pts, flags),
260 "AMediaCodec_queueInputBuffer failed")
261 ALOGV("input: id: %zu size: %d pts: %" PRId64 " flags: %d", bufferIndex, size, pts,
262 flags);
263 mOutputBuff->saveInPTS(pts);
264 mInputCount++;
265 }
266 return !hasSeenError();
267 }
268
dequeueOutput(size_t bufferIndex,AMediaCodecBufferInfo * info)269 bool CodecEncoderTest::dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* info) {
270 if ((info->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
271 mSawOutputEOS = true;
272 }
273 if (info->size > 0) {
274 if (mSaveToMem) {
275 size_t buffSize;
276 uint8_t* buf = AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize);
277 RETURN_IF_NULL(buf, std::string{"AMediaCodec_getOutputBuffer returned nullptr"})
278 // NdkMediaCodec calls ABuffer::data, which already adds offset
279 info->offset = 0;
280 mOutputBuff->saveToMemory(buf, info);
281 mOutputBuff->updateChecksum(buf, info);
282 }
283 if ((info->flags & AMEDIACODEC_BUFFER_FLAG_KEY_FRAME) != 0) {
284 mNumSyncFramesReceived += 1;
285 mSyncFramesPos.push_back(mOutputCount);
286 }
287 if ((info->flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) {
288 mOutputBuff->saveOutPTS(info->presentationTimeUs);
289 mOutputCount++;
290 }
291 }
292 ALOGV("output: id: %zu size: %d pts: %" PRId64 " flags: %d", bufferIndex, info->size,
293 info->presentationTimeUs, info->flags);
294 RETURN_IF_FAIL(AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, false),
295 "AMediaCodec_releaseOutputBuffer failed")
296 return !hasSeenError();
297 }
298
doWork(int frameLimit)299 bool CodecEncoderTest::doWork(int frameLimit) {
300 mLoopBackFrameLimit = frameLimit;
301 return CodecTestBase::doWork(frameLimit);
302 }
303
isTestStateValid()304 bool CodecEncoderTest::isTestStateValid() {
305 if (!CodecTestBase::isTestStateValid()) return false;
306 RETURN_IF_TRUE((mIsAudio || (mIsVideo && mMaxBFrames == 0)) &&
307 !mOutputBuff->isPtsStrictlyIncreasing(mPrevOutputPts),
308 std::string{"Output timestamps are not strictly increasing \n"}.append(
309 mOutputBuff->getErrorMsg()))
310 RETURN_IF_TRUE(mIsVideo && !mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0),
311 std::string{"Input pts list and Output pts list are not identical \n"}.append(
312 mOutputBuff->getErrorMsg()))
313 return true;
314 }
315
316 // @ApiTest = AMEDIAFORMAT_KEY_CHANNEL_COUNT
317 // @ApiTest = AMEDIAFORMAT_KEY_COLOR_FORMAT
318 // @ApiTest = AMEDIAFORMAT_KEY_FRAME_RATE
319 // @ApiTest = AMEDIAFORMAT_KEY_HEIGHT
320 // @ApiTest = AMEDIAFORMAT_KEY_MAX_B_FRAMES
321 // @ApiTest = AMEDIAFORMAT_KEY_SAMPLE_RATE
322 // @ApiTest = AMEDIAFORMAT_KEY_WIDTH
323 //
initFormat(AMediaFormat * format)324 bool CodecEncoderTest::initFormat(AMediaFormat* format) {
325 if (mIsAudio) {
326 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &mSampleRate),
327 StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_SAMPLE_RATE))
328 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &mChannels),
329 StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_CHANNEL_COUNT))
330 } else {
331 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth),
332 StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_WIDTH))
333 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight),
334 StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_HEIGHT))
335 // key formalized in Android U (sdk==34).
336 // Use internally-defined when running on earlier releases, such as happens with MTS
337 if (__builtin_available(android __ANDROID_API_U__, *)) {
338 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_MAX_B_FRAMES,
339 &mMaxBFrames),
340 StringFormat("format does not have key %s",
341 AMEDIAFORMAT_KEY_MAX_B_FRAMES))
342 } else {
343 RETURN_IF_FALSE(AMediaFormat_getInt32(format, COMPATIBLE_AMEDIAFORMAT_KEY_MAX_B_FRAMES,
344 &mMaxBFrames),
345 StringFormat("format does not have key %s",
346 COMPATIBLE_AMEDIAFORMAT_KEY_MAX_B_FRAMES))
347 }
348 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &mDefFrameRate),
349 StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_FRAME_RATE))
350 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, &mColorFormat),
351 StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_COLOR_FORMAT))
352 }
353 return true;
354 }
355
encodeToMemory(const char * file,const char * encoder,int32_t frameLimit,AMediaFormat * format,OutputManager * ref)356 bool CodecEncoderTest::encodeToMemory(const char* file, const char* encoder, int32_t frameLimit,
357 AMediaFormat* format, OutputManager* ref) {
358 /* TODO(b/149027258) */
359 if (true) mSaveToMem = false;
360 else mSaveToMem = true;
361 mOutputBuff = ref;
362 mCodec = AMediaCodec_createCodecByName(encoder);
363 RETURN_IF_NULL(mCodec, StringFormat("unable to create codec by name %s \n", encoder))
364 setUpSource(file);
365 RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", file))
366 if (!configureCodec(format, false, true, true)) return false;
367 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
368 if (!doWork(frameLimit)) return false;
369 if (!queueEOS()) return false;
370 if (!waitForAllOutputs()) return false;
371 RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
372 RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
373 mCodec = nullptr;
374 mSaveToMem = false;
375 return !hasSeenError();
376 }
377
forceSyncFrame(AMediaFormat * format)378 void CodecEncoderTest::forceSyncFrame(AMediaFormat* format) {
379 AMediaFormat_setInt32(format, TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
380 ALOGV("requesting key frame");
381 AMediaCodec_setParameters(mCodec, format);
382 }
383
updateBitrate(AMediaFormat * format,int bitrate)384 void CodecEncoderTest::updateBitrate(AMediaFormat* format, int bitrate) {
385 AMediaFormat_setInt32(format, TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrate);
386 ALOGV("requesting bitrate to be changed to %d", bitrate);
387 AMediaCodec_setParameters(mCodec, format);
388 }
389
testSimpleEncode(const char * encoder,const char * srcPath)390 bool CodecEncoderTest::testSimpleEncode(const char* encoder, const char* srcPath) {
391 setUpSource(srcPath);
392 RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", srcPath))
393 /* TODO(b/149027258) */
394 if (true) mSaveToMem = false;
395 else mSaveToMem = true;
396 auto ref = mRefBuff;
397 auto test = mTestBuff;
398 const bool boolStates[]{true, false};
399 for (auto format : mFormats) {
400 RETURN_IF_NULL(format,
401 std::string{"encountered error during deserialization of media format"})
402 int loopCounter = 0;
403 for (auto eosType : boolStates) {
404 for (auto isAsync : boolStates) {
405 mOutputBuff = loopCounter == 0 ? ref : test;
406 mOutputBuff->reset();
407 /* TODO(b/147348711) */
408 /* Instead of create and delete codec at every iteration, we would like to create
409 * once and use it for all iterations and delete before exiting */
410 mCodec = AMediaCodec_createCodecByName(encoder);
411 RETURN_IF_NULL(mCodec, StringFormat("unable to create codec %s", encoder))
412 char* name = nullptr;
413 RETURN_IF_FAIL(AMediaCodec_getName(mCodec, &name), "AMediaCodec_getName failed")
414 RETURN_IF_NULL(name, std::string{"AMediaCodec_getName returned null"})
415 auto res = strcmp(name, encoder) != 0;
416 AMediaCodec_releaseName(mCodec, name);
417 RETURN_IF_TRUE(res,
418 StringFormat("Codec name mismatch act/got: %s/%s", encoder, name))
419 if (!configureCodec(format, isAsync, eosType, true)) return false;
420 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
421 if (!doWork(INT32_MAX)) return false;
422 if (!queueEOS()) return false;
423 if (!waitForAllOutputs()) return false;
424 RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
425 RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
426 mCodec = nullptr;
427 RETURN_IF_TRUE((loopCounter != 0 && !ref->equals(test)),
428 std::string{"Encoder output is not consistent across runs \n"}
429 .append(test->getErrorMsg()))
430 loopCounter++;
431 }
432 }
433 }
434 return true;
435 }
436
testReconfigure(const char * encoder,const char * srcPath)437 bool CodecEncoderTest::testReconfigure(const char* encoder, const char* srcPath) {
438 setUpSource(srcPath);
439 RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", srcPath))
440 auto configRef = mReconfBuff;
441 if (mFormats.size() > 1) {
442 auto format = mFormats[1];
443 RETURN_IF_NULL(format,
444 std::string{"encountered error during deserialization of media format"})
445 RETURN_IF_FALSE(encodeToMemory(srcPath, encoder, INT32_MAX, format, configRef),
446 StringFormat("encodeToMemory failed for file: %s codec: %s \n format: %s",
447 srcPath, encoder, AMediaFormat_toString(format)))
448 }
449 auto format = mFormats[0];
450 RETURN_IF_NULL(format, std::string{"encountered error during deserialization of media format"})
451 auto ref = mRefBuff;
452 RETURN_IF_FALSE(encodeToMemory(srcPath, encoder, INT32_MAX, format, ref),
453 StringFormat("encodeToMemory failed for file: %s codec: %s \n format: %s",
454 srcPath, encoder, AMediaFormat_toString(format)))
455
456 auto test = mTestBuff;
457 mOutputBuff = test;
458 const bool boolStates[]{true, false};
459 for (auto isAsync : boolStates) {
460 /* TODO(b/147348711) */
461 /* Instead of create and delete codec at every iteration, we would like to create
462 * once and use it for all iterations and delete before exiting */
463 mCodec = AMediaCodec_createCodecByName(encoder);
464 RETURN_IF_NULL(mCodec, StringFormat("unable to create codec %s", encoder))
465 if (!configureCodec(format, isAsync, true, true)) return false;
466 /* test reconfigure in init state */
467 if (!reConfigureCodec(format, !isAsync, false, true)) return false;
468 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
469
470 /* test reconfigure in running state before queuing input */
471 if (!reConfigureCodec(format, !isAsync, false, true)) return false;
472 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
473 if (!doWork(23)) return false;
474
475 /* test reconfigure codec in running state */
476 if (!reConfigureCodec(format, isAsync, true, true)) return false;
477 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
478
479 /* TODO(b/149027258) */
480 if (true) mSaveToMem = false;
481 else mSaveToMem = true;
482 test->reset();
483 if (!doWork(INT32_MAX)) return false;
484 if (!queueEOS()) return false;
485 if (!waitForAllOutputs()) return false;
486 RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
487 RETURN_IF_TRUE(!ref->equals(test),
488 std::string{"Encoder output is not consistent across runs \n"}.append(
489 test->getErrorMsg()))
490
491 /* test reconfigure codec at eos state */
492 if (!reConfigureCodec(format, !isAsync, false, true)) return false;
493 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
494 test->reset();
495 if (!doWork(INT32_MAX)) return false;
496 if (!queueEOS()) return false;
497 if (!waitForAllOutputs()) return false;
498 RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
499 RETURN_IF_TRUE(!ref->equals(test),
500 std::string{"Encoder output is not consistent across runs \n"}.append(
501 test->getErrorMsg()))
502
503 /* test reconfigure codec for new format */
504 if (mFormats.size() > 1) {
505 if (!reConfigureCodec(mFormats[1], isAsync, false, true)) return false;
506 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
507 test->reset();
508 if (!doWork(INT32_MAX)) return false;
509 if (!queueEOS()) return false;
510 if (!waitForAllOutputs()) return false;
511 RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
512 RETURN_IF_TRUE(!configRef->equals(test),
513 std::string{"Encoder output is not consistent across runs \n"}.append(
514 test->getErrorMsg()))
515 }
516 mSaveToMem = false;
517 RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
518 mCodec = nullptr;
519 }
520 return true;
521 }
522
testOnlyEos(const char * encoder)523 bool CodecEncoderTest::testOnlyEos(const char* encoder) {
524 /* TODO(b/149027258) */
525 if (true) mSaveToMem = false;
526 else mSaveToMem = true;
527 auto ref = mRefBuff;
528 auto test = mTestBuff;
529 const bool boolStates[]{true, false};
530 AMediaFormat* format = mFormats[0];
531 RETURN_IF_NULL(format, std::string{"encountered error during deserialization of media format"})
532 int loopCounter = 0;
533 for (auto isAsync : boolStates) {
534 mOutputBuff = loopCounter == 0 ? ref : test;
535 mOutputBuff->reset();
536 /* TODO(b/147348711) */
537 /* Instead of create and delete codec at every iteration, we would like to create
538 * once and use it for all iterations and delete before exiting */
539 mCodec = AMediaCodec_createCodecByName(encoder);
540 RETURN_IF_NULL(mCodec, StringFormat("unable to create codec by name %s", encoder))
541 if (!configureCodec(format, isAsync, false, true)) return false;
542 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
543 if (!queueEOS()) return false;
544 if (!waitForAllOutputs()) return false;
545 RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
546 RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
547 mCodec = nullptr;
548 RETURN_IF_TRUE((loopCounter != 0 && !ref->equals(test)),
549 std::string{"Encoder output is not consistent across runs \n"}.append(
550 test->getErrorMsg()))
551 loopCounter++;
552 }
553 return true;
554 }
555
testSetForceSyncFrame(const char * encoder,const char * srcPath)556 bool CodecEncoderTest::testSetForceSyncFrame(const char* encoder, const char* srcPath) {
557 setUpSource(srcPath);
558 RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", srcPath))
559 AMediaFormat* format = mFormats[0];
560 RETURN_IF_NULL(format, std::string{"encountered error during deserialization of media format"})
561 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &mDefFrameRate),
562 StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_FRAME_RATE))
563 // Maximum allowed key frame interval variation from the target value.
564 int kMaxKeyFrameIntervalVariation = 3;
565 int kKeyFrameInterval = 2; // force key frame every 2 seconds.
566 int kKeyFramePos = mDefFrameRate * kKeyFrameInterval;
567 int kNumKeyFrameRequests = 7;
568 AMediaFormat* params = AMediaFormat_new();
569 mFormats.push_back(params);
570 mOutputBuff = mTestBuff;
571 const bool boolStates[]{true, false};
572 for (auto isAsync : boolStates) {
573 mOutputBuff->reset();
574 /* TODO(b/147348711) */
575 /* Instead of create and delete codec at every iteration, we would like to create
576 * once and use it for all iterations and delete before exiting */
577 mCodec = AMediaCodec_createCodecByName(encoder);
578 RETURN_IF_NULL(mCodec, StringFormat("unable to create codec by name%s", encoder))
579 if (!configureCodec(format, isAsync, false, true)) return false;
580 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
581 for (int i = 0; i < kNumKeyFrameRequests; i++) {
582 if (!doWork(kKeyFramePos)) return false;
583 RETURN_IF_TRUE(mSawInputEOS,
584 StringFormat("Unable to encode %d frames as the input resource contains "
585 "only %d frames \n",
586 kKeyFramePos, mInputCount))
587 forceSyncFrame(params);
588 mInputBufferReadOffset = 0;
589 }
590 if (!queueEOS()) return false;
591 if (!waitForAllOutputs()) return false;
592 RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
593 RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
594 mCodec = nullptr;
595 RETURN_IF_TRUE((mNumSyncFramesReceived < kNumKeyFrameRequests),
596 StringFormat("Received only %d key frames for %d key frame requests \n",
597 mNumSyncFramesReceived, kNumKeyFrameRequests))
598 ALOGD("mNumSyncFramesReceived %d", mNumSyncFramesReceived);
599 for (int i = 0, expPos = 0, index = 0; i < kNumKeyFrameRequests; i++) {
600 int j = index;
601 for (; j < mSyncFramesPos.size(); j++) {
602 // Check key frame intervals:
603 // key frame position should not be greater than target value + 3
604 // key frame position should not be less than target value - 3
605 if (abs(expPos - mSyncFramesPos.at(j)) <= kMaxKeyFrameIntervalVariation) {
606 index = j;
607 break;
608 }
609 }
610 if (j == mSyncFramesPos.size()) {
611 ALOGW("requested key frame at frame index %d none found near by", expPos);
612 }
613 expPos += kKeyFramePos;
614 }
615 }
616 return true;
617 }
618
testAdaptiveBitRate(const char * encoder,const char * srcPath)619 bool CodecEncoderTest::testAdaptiveBitRate(const char* encoder, const char* srcPath) {
620 setUpSource(srcPath);
621 RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", srcPath))
622 AMediaFormat* format = mFormats[0];
623 RETURN_IF_NULL(format, std::string{"encountered error during deserialization of media format"})
624 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &mDefFrameRate),
625 StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_FRAME_RATE))
626 int kAdaptiveBitrateInterval = 3; // change bitrate every 3 seconds.
627 int kAdaptiveBitrateDurationFrame = mDefFrameRate * kAdaptiveBitrateInterval;
628 int kBitrateChangeRequests = 7;
629 // TODO(b/251265293) Reduce the allowed deviation after improving the test conditions
630 float kMaxBitrateDeviation = 60.0; // allowed bitrate deviation in %
631 AMediaFormat* params = AMediaFormat_new();
632 mFormats.push_back(params);
633 mOutputBuff = mTestBuff;
634 mSaveToMem = true;
635 const bool boolStates[]{true, false};
636 for (auto isAsync : boolStates) {
637 mOutputBuff->reset();
638 /* TODO(b/147348711) */
639 /* Instead of create and delete codec at every iteration, we would like to create
640 * once and use it for all iterations and delete before exiting */
641 mCodec = AMediaCodec_createCodecByName(encoder);
642 RETURN_IF_NULL(mCodec, StringFormat("unable to create codec by name %s", encoder))
643 if (!configureCodec(format, isAsync, false, true)) return false;
644 RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
645 int expOutSize = 0;
646 int bitrate;
647 RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate),
648 StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_BIT_RATE))
649 for (int i = 0; i < kBitrateChangeRequests; i++) {
650 if (!doWork(kAdaptiveBitrateDurationFrame)) return false;
651 RETURN_IF_TRUE(mSawInputEOS,
652 StringFormat("Unable to encode %d frames as the input resource contains "
653 "only %d frames \n",
654 kAdaptiveBitrateDurationFrame, mInputCount))
655 expOutSize += kAdaptiveBitrateInterval * bitrate;
656 if ((i & 1) == 1) bitrate *= 2;
657 else bitrate /= 2;
658 updateBitrate(params, bitrate);
659 mInputBufferReadOffset = 0;
660 }
661 if (!queueEOS()) return false;
662 if (!waitForAllOutputs()) return false;
663 RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
664 RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
665 mCodec = nullptr;
666 /* TODO: validate output br with sliding window constraints Sec 5.2 cdd */
667 int outSize = mOutputBuff->getOutStreamSize() * 8;
668 float brDev = abs(expOutSize - outSize) * 100.0f / expOutSize;
669 RETURN_IF_TRUE(brDev > kMaxBitrateDeviation,
670 StringFormat("Relative Bitrate error is too large : %f %%\n", brDev))
671 }
672 return true;
673 }
674
nativeTestSimpleEncode(JNIEnv * env,jobject,jstring jEncoder,jstring jsrcPath,jstring jMediaType,jstring jCfgParams,jstring jSeparator,jobject jRetMsg)675 static jboolean nativeTestSimpleEncode(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
676 jstring jMediaType, jstring jCfgParams, jstring jSeparator,
677 jobject jRetMsg) {
678 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
679 const char* cMediaType = env->GetStringUTFChars(jMediaType, nullptr);
680 const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
681 const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
682 const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
683 auto codecEncoderTest = new CodecEncoderTest(cMediaType, cCfgParams, nullptr, cSeparator);
684 bool isPass = codecEncoderTest->testSimpleEncode(cEncoder, csrcPath);
685 std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
686 delete codecEncoderTest;
687 jclass clazz = env->GetObjectClass(jRetMsg);
688 jmethodID mId =
689 env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
690 env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
691 env->ReleaseStringUTFChars(jCfgParams, cCfgParams);
692 env->ReleaseStringUTFChars(jSeparator, cSeparator);
693 env->ReleaseStringUTFChars(jEncoder, cEncoder);
694 env->ReleaseStringUTFChars(jMediaType, cMediaType);
695 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
696 return static_cast<jboolean>(isPass);
697 }
698
nativeTestReconfigure(JNIEnv * env,jobject,jstring jEncoder,jstring jsrcPath,jstring jMediaType,jstring jCfgParams,jstring jReconfigCfgParams,jstring jSeparator,jobject jRetMsg)699 static jboolean nativeTestReconfigure(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
700 jstring jMediaType, jstring jCfgParams,
701 jstring jReconfigCfgParams, jstring jSeparator,
702 jobject jRetMsg) {
703 bool isPass;
704 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
705 const char* cMediaType = env->GetStringUTFChars(jMediaType, nullptr);
706 const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
707 const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
708 const char* cReconfigCfgParams = jReconfigCfgParams != nullptr
709 ? env->GetStringUTFChars(jReconfigCfgParams, nullptr)
710 : nullptr;
711 const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
712 auto codecEncoderTest =
713 new CodecEncoderTest(cMediaType, cCfgParams, cReconfigCfgParams, cSeparator);
714 isPass = codecEncoderTest->testReconfigure(cEncoder, csrcPath);
715 std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
716 delete codecEncoderTest;
717 jclass clazz = env->GetObjectClass(jRetMsg);
718 jmethodID mId =
719 env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
720 env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
721 env->ReleaseStringUTFChars(jCfgParams, cCfgParams);
722 if (cReconfigCfgParams != nullptr) {
723 env->ReleaseStringUTFChars(jReconfigCfgParams, cReconfigCfgParams);
724 }
725 env->ReleaseStringUTFChars(jSeparator, cSeparator);
726 env->ReleaseStringUTFChars(jEncoder, cEncoder);
727 env->ReleaseStringUTFChars(jMediaType, cMediaType);
728 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
729 return static_cast<jboolean>(isPass);
730 }
731
nativeTestSetForceSyncFrame(JNIEnv * env,jobject,jstring jEncoder,jstring jsrcPath,jstring jMediaType,jstring jCfgParams,jstring jSeparator,jobject jRetMsg)732 static jboolean nativeTestSetForceSyncFrame(JNIEnv* env, jobject, jstring jEncoder,
733 jstring jsrcPath, jstring jMediaType,
734 jstring jCfgParams, jstring jSeparator,
735 jobject jRetMsg) {
736 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
737 const char* cMediaType = env->GetStringUTFChars(jMediaType, nullptr);
738 const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
739 const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
740 const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
741 auto codecEncoderTest = new CodecEncoderTest(cMediaType, cCfgParams, nullptr, cSeparator);
742 bool isPass = codecEncoderTest->testSetForceSyncFrame(cEncoder, csrcPath);
743 std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
744 delete codecEncoderTest;
745 jclass clazz = env->GetObjectClass(jRetMsg);
746 jmethodID mId =
747 env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
748 env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
749 env->ReleaseStringUTFChars(jCfgParams, cCfgParams);
750 env->ReleaseStringUTFChars(jSeparator, cSeparator);
751 env->ReleaseStringUTFChars(jEncoder, cEncoder);
752 env->ReleaseStringUTFChars(jMediaType, cMediaType);
753 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
754 return static_cast<jboolean>(isPass);
755 }
756
nativeTestAdaptiveBitRate(JNIEnv * env,jobject,jstring jEncoder,jstring jsrcPath,jstring jMediaType,jstring jCfgParams,jstring jSeparator,jobject jRetMsg)757 static jboolean nativeTestAdaptiveBitRate(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
758 jstring jMediaType, jstring jCfgParams,
759 jstring jSeparator, jobject jRetMsg) {
760 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
761 const char* cMediaType = env->GetStringUTFChars(jMediaType, nullptr);
762 const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
763 const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
764 const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
765 auto codecEncoderTest = new CodecEncoderTest(cMediaType, cCfgParams, nullptr, cSeparator);
766 bool isPass = codecEncoderTest->testAdaptiveBitRate(cEncoder, csrcPath);
767 std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
768 delete codecEncoderTest;
769 jclass clazz = env->GetObjectClass(jRetMsg);
770 jmethodID mId =
771 env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
772 env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
773 env->ReleaseStringUTFChars(jCfgParams, cCfgParams);
774 env->ReleaseStringUTFChars(jSeparator, cSeparator);
775 env->ReleaseStringUTFChars(jEncoder, cEncoder);
776 env->ReleaseStringUTFChars(jMediaType, cMediaType);
777 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
778 return static_cast<jboolean>(isPass);
779 }
780
nativeTestOnlyEos(JNIEnv * env,jobject,jstring jEncoder,jstring jMediaType,jstring jCfgParams,jstring jSeparator,jobject jRetMsg)781 static jboolean nativeTestOnlyEos(JNIEnv* env, jobject, jstring jEncoder, jstring jMediaType,
782 jstring jCfgParams, jstring jSeparator, jobject jRetMsg) {
783 const char* cMediaType = env->GetStringUTFChars(jMediaType, nullptr);
784 const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
785 const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
786 const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
787 auto codecEncoderTest = new CodecEncoderTest(cMediaType, cCfgParams, nullptr, cSeparator);
788 bool isPass = codecEncoderTest->testOnlyEos(cEncoder);
789 std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
790 delete codecEncoderTest;
791 jclass clazz = env->GetObjectClass(jRetMsg);
792 jmethodID mId =
793 env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
794 env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
795 env->ReleaseStringUTFChars(jCfgParams, cCfgParams);
796 env->ReleaseStringUTFChars(jSeparator, cSeparator);
797 env->ReleaseStringUTFChars(jEncoder, cEncoder);
798 env->ReleaseStringUTFChars(jMediaType, cMediaType);
799 return static_cast<jboolean>(isPass);
800 }
801
registerAndroidMediaV2CtsEncoderTest(JNIEnv * env)802 int registerAndroidMediaV2CtsEncoderTest(JNIEnv* env) {
803 const JNINativeMethod methodTable[] = {
804 {"nativeTestSimpleEncode",
805 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
806 "String;Ljava/lang/StringBuilder;)Z",
807 (void*)nativeTestSimpleEncode},
808 {"nativeTestReconfigure",
809 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
810 "String;Ljava/lang/String;Ljava/lang/StringBuilder;)Z",
811 (void*)nativeTestReconfigure},
812 {"nativeTestSetForceSyncFrame",
813 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
814 "String;Ljava/lang/StringBuilder;)Z",
815 (void*)nativeTestSetForceSyncFrame},
816 {"nativeTestAdaptiveBitRate",
817 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
818 "String;Ljava/lang/StringBuilder;)Z",
819 (void*)nativeTestAdaptiveBitRate},
820 {"nativeTestOnlyEos",
821 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
822 "StringBuilder;)Z",
823 (void*)nativeTestOnlyEos},
824 };
825 jclass c = env->FindClass("android/mediav2/cts/CodecEncoderTest");
826 return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
827 }
828
JNI_OnLoad(JavaVM * vm,void *)829 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
830 JNIEnv* env;
831 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
832 if (registerAndroidMediaV2CtsEncoderTest(env) != JNI_OK) return JNI_ERR;
833 return JNI_VERSION_1_6;
834 }
835