/* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { class MediaCodecSanityTest : public ::testing::Test { public: MediaCodecSanityTest() : looper(new ALooper), cfg(new AMessage), ifmt(new AMessage), ofmt(new AMessage) { ProcessState::self()->startThreadPool(); looper->start(); } ~MediaCodecSanityTest() { if (codec != nullptr) { codec->release(); } looper->stop(); } sp looper; sp codec; sp cfg; sp ifmt; sp ofmt; }; const static size_t kLinearBufferSize = 1048576; // data for a codec input frame struct FrameData { const uint8_t *data; size_t size; template constexpr FrameData(const uint8_t(&data_)[N]) : data(data_), size(N) { } }; // one yellow frame of 240x180 (albeit 4:4:4) const uint8_t avcStream_A1[] = { // IDR frame 0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x0d, 0xac, 0xd9, 0x41, 0x41, 0xfa, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xf1, 0x42, 0x99, 0x60, 0x00, 0x00, 0x00, 0x01, 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x00, 0x2b, 0xff, 0xfe, 0xd8, 0xe7, 0xf3, 0x2c, 0xa5, 0x60, 0xca, 0xbb, 0xf1, 0x5c, 0x44, 0x7c, 0x9a, 0xa5, 0xc3, 0xab, 0x2f, 0x77, 0x0a, 0x94, 0x0d, 0x19, 0x43, 0x3b, 0x4f, 0x25, 0xea, 0x66, 0x00, 0x01, 0x24, 0xcd, 0x35, 0x5f, 0xc2, 0x34, 0x89, 0xd1, 0xa5, 0x60, 0x09, 0x98, 0x00, 0x01, 0x1b, 0x0e, 0xcb, 0x0d, 0x04, 0x86, 0x94, 0xe2, 0x32, 0x3c, 0xdd, 0x0f, }; FrameData avcStream_A[] __unused = { avcStream_A1 }; // AVC stream of 2 yellow frames (240x180) const uint8_t avcStream_B1[] = { // IDR frame 0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x0c, 0xac, 0xd9, 0x41, 0x41, 0xfa, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x02, 0x80, 0xf1, 0x42, 0x99, 0x60, 0x00, 0x00, 0x00, 0x01, 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x00, 0x33, 0xff, 0xfe, 0xdf, 0x32, 0xf8, 0x14, 0xd6, 0x25, 0xd0, 0x74, 0x42, 0x50, 0x84, 0x6f, 0xf4, 0xc2, 0x5c, 0x76, 0x37, 0x17, 0x72, 0xac, 0x52, 0xfc, 0xd6, 0x1f, 0xd2, 0xd0, 0x60, 0xb2, 0x20, 0x00, 0x10, 0x3d, 0x2a, 0xc0, 0xe4, 0x27, 0xcb, 0xce, 0xea, 0x25, 0x00, 0x81, 0x00, 0x00, 0x0f, 0x40, 0xbc, 0x81, 0x15, 0xc1, 0x65, 0x20, 0x80, 0x81, 0x7a, 0x57, 0x51, }; const uint8_t avcStream_B2[] = { // P frame 0x00, 0x00, 0x00, 0x01, 0x41, 0x9a, 0x21, 0x6c, 0x42, 0xbf, 0xfe, 0x38, 0x40, 0x00, 0x0d, 0x48, }; FrameData avcStream_B[] = { avcStream_B1, avcStream_B2 }; class MediaCodecInputBufferSizeTest : public MediaCodecSanityTest, public ::testing::WithParamInterface { }; TEST_P(MediaCodecInputBufferSizeTest, TestAvcDecoder) { codec = MediaCodec::CreateByComponentName(looper, "c2.android.avc.decoder"); cfg->setInt32("width", 320); cfg->setInt32("height", 240); cfg->setString("mime", MIMETYPE_VIDEO_AVC); const int32_t InputSize = GetParam(); if (InputSize >= 0) { cfg->setInt32("max-input-size", InputSize); } EXPECT_EQ(codec->configure(cfg, nullptr, nullptr, 0), OK); EXPECT_EQ(codec->getInputFormat(&ifmt), OK); int32_t maxInputSize; ASSERT_TRUE(ifmt->findInt32("max-input-size", &maxInputSize)); if (InputSize > 0) { EXPECT_EQ(maxInputSize, InputSize); } else { EXPECT_GE(maxInputSize, 1 << 20); // 1 MB } EXPECT_EQ(codec->start(), OK); size_t ix; EXPECT_EQ(codec->dequeueInputBuffer(&ix, 1000000), OK); sp buf; EXPECT_EQ(codec->getInputBuffer(ix, &buf), OK); EXPECT_GE(buf->size(), (size_t)maxInputSize); EXPECT_LE(buf->size(), (size_t)maxInputSize + 4096u); } TEST_P(MediaCodecInputBufferSizeTest, TestVideoDecoder) { codec = MediaCodec::CreateByComponentName(looper, "c2.android.vp8.decoder"); cfg->setInt32("width", 320); cfg->setInt32("height", 240); cfg->setString("mime", MIMETYPE_VIDEO_VP8); const int32_t InputSize = GetParam(); if (InputSize >= 0) { cfg->setInt32("max-input-size", InputSize); } EXPECT_EQ(codec->configure(cfg, nullptr, nullptr, 0), OK); EXPECT_EQ(codec->getInputFormat(&ifmt), OK); int32_t maxInputSize; ASSERT_TRUE(ifmt->findInt32("max-input-size", &maxInputSize)); if (InputSize > 0) { EXPECT_EQ(maxInputSize, InputSize); } else { EXPECT_GE(maxInputSize, 1 << 20); // 1 MB } EXPECT_EQ(codec->start(), OK); size_t ix; EXPECT_EQ(codec->dequeueInputBuffer(&ix, 1000000), OK); sp buf; EXPECT_EQ(codec->getInputBuffer(ix, &buf), OK); EXPECT_GE(buf->size(), (size_t)maxInputSize); EXPECT_LE(buf->size(), (size_t)maxInputSize + 4096u); } TEST_P(MediaCodecInputBufferSizeTest, TestAudioDecoder) { codec = MediaCodec::CreateByComponentName(looper, "c2.android.aac.decoder"); cfg->setInt32("sample-rate", 44100); cfg->setInt32("channel-count", 2); cfg->setString("mime", MIMETYPE_AUDIO_AAC); const int32_t InputSize = GetParam(); if (InputSize >= 0) { cfg->setInt32("max-input-size", InputSize); } EXPECT_EQ(codec->configure(cfg, nullptr, nullptr, 0), OK); EXPECT_EQ(codec->getInputFormat(&ifmt), OK); int32_t maxInputSize; if (InputSize > 0) { ASSERT_TRUE(ifmt->findInt32("max-input-size", &maxInputSize)); EXPECT_EQ(maxInputSize, InputSize); } else { if (ifmt->findInt32("max-input-size", &maxInputSize)) { EXPECT_EQ(maxInputSize, 1 << 19); // 512 KB } maxInputSize = kLinearBufferSize; // input size is set by channel } EXPECT_EQ(codec->start(), OK); size_t ix; EXPECT_EQ(codec->dequeueInputBuffer(&ix, 1000000), OK); sp buf; EXPECT_EQ(codec->getInputBuffer(ix, &buf), OK); EXPECT_GE(buf->size(), (size_t)maxInputSize); EXPECT_LE(buf->size(), (size_t)maxInputSize + 4096u); } INSTANTIATE_TEST_CASE_P(InputSizes, MediaCodecInputBufferSizeTest, ::testing::Values(-1, 1234, 12345678)); TEST_F(MediaCodecSanityTest, TestAvcDecoderHdrStaticInfo) { codec = MediaCodec::CreateByComponentName(looper, "c2.android.avc.decoder"); cfg->setInt32("width", 320); cfg->setInt32("height", 240); cfg->setString("mime", MIMETYPE_VIDEO_AVC); HDRStaticInfo info = { .mID = HDRStaticInfo::kType1, .sType1 = { .mR = { .x = 35400, .y = 14600 }, .mG = { .x = 8500, .y = 39850 }, .mB = { .x = 6550, .y = 2300 }, .mW = { .x = 15635, .y = 16450 }, .mMaxDisplayLuminance = 1000, .mMinDisplayLuminance = 1000, .mMaxContentLightLevel = 1000, .mMaxFrameAverageLightLevel = 120 } }; cfg->setBuffer("hdr-static-info", ABuffer::CreateAsCopy(&info, sizeof(info))); EXPECT_EQ(codec->configure(cfg, nullptr, nullptr, 0), OK); EXPECT_EQ(codec->getOutputFormat(&ofmt), OK); sp oinfo; ASSERT_TRUE(ofmt->findBuffer("hdr-static-info", &oinfo)); ASSERT_EQ(oinfo->size(), sizeof(info)); EXPECT_EQ(memcmp(oinfo->data(), &info, sizeof(info)), 0); EXPECT_EQ(codec->start(), OK); // assume we can submit all input before dequeuing output size_t frameIx = 0; size_t ix; sp buf; for (const FrameData &frame : avcStream_B) { EXPECT_EQ(codec->dequeueInputBuffer(&ix, 1000000), OK); EXPECT_EQ(codec->getInputBuffer(ix, &buf), OK); ASSERT_GE(buf->capacity(), frame.size); memcpy(buf->base(), frame.data, frame.size); EXPECT_EQ(buf->setRange(0, frame.size), OK); bool eos = ++frameIx == NELEM(avcStream_B); EXPECT_EQ(codec->queueInputBuffer(ix, 0, frame.size, frameIx * 33333, eos ? BUFFER_FLAG_END_OF_STREAM : 0), OK); } size_t offset, size; int64_t ts; uint32_t flags; bool mInfoFormatChangedOk = true; bool mInfoBuffersChangedOk = true; while (true) { status_t err = codec->dequeueOutputBuffer(&ix, &offset, &size, &ts, &flags, 1000000); if (err == INFO_FORMAT_CHANGED && mInfoFormatChangedOk) { mInfoFormatChangedOk = false; } else if (err == INFO_OUTPUT_BUFFERS_CHANGED && mInfoBuffersChangedOk) { mInfoBuffersChangedOk = false; } else { ASSERT_EQ(err, OK); break; } } EXPECT_EQ(codec->getOutputBuffer(ix, &buf), OK); EXPECT_EQ(codec->getOutputFormat(ix, &ofmt), OK); ASSERT_TRUE(ofmt->findBuffer("hdr-static-info", &oinfo)); ASSERT_EQ(oinfo->size(), sizeof(info)); EXPECT_EQ(memcmp(oinfo->data(), &info, sizeof(info)), 0); } TEST_F(MediaCodecSanityTest, TestVideoDecoderHdrStaticInfo) { codec = MediaCodec::CreateByComponentName(looper, "c2.android.mpeg4.decoder"); cfg->setInt32("width", 320); cfg->setInt32("height", 240); cfg->setString("mime", MIMETYPE_VIDEO_MPEG4); HDRStaticInfo info = { .mID = HDRStaticInfo::kType1, .sType1 = { .mR = { .x = 35400, .y = 14600 }, .mG = { .x = 8500, .y = 39850 }, .mB = { .x = 6550, .y = 2300 }, .mW = { .x = 15635, .y = 16450 }, .mMaxDisplayLuminance = 1000, .mMinDisplayLuminance = 1000, .mMaxContentLightLevel = 1000, .mMaxFrameAverageLightLevel = 120 } }; cfg->setBuffer("hdr-static-info", ABuffer::CreateAsCopy(&info, sizeof(info))); EXPECT_EQ(codec->configure(cfg, nullptr, nullptr, 0), OK); EXPECT_EQ(codec->getOutputFormat(&ofmt), OK); sp oinfo; ASSERT_TRUE(ofmt->findBuffer("hdr-static-info", &oinfo)); ASSERT_EQ(oinfo->size(), sizeof(info)); EXPECT_EQ(memcmp(oinfo->data(), &info, sizeof(info)), 0); } class MediaCodecByteBufferTest : public MediaCodecSanityTest, public ::testing::WithParamInterface { }; TEST_P(MediaCodecByteBufferTest, TestVideoDecoder420Planar) { codec = MediaCodec::CreateByComponentName(looper, "c2.android.avc.decoder"); // codec = MediaCodec::CreateByComponentName(looper, "OMX.google.h264.decoder"); cfg->setInt32("width", 320); cfg->setInt32("height", 240); cfg->setString("mime", MIMETYPE_VIDEO_AVC); const int32_t Color = GetParam(); if (Color >= 0) { cfg->setInt32("color-format", Color); } int32_t xcolor = Color == -1 ? COLOR_FormatYUV420Planar : Color; EXPECT_EQ(codec->configure(cfg, nullptr, nullptr, 0), OK); EXPECT_EQ(codec->getOutputFormat(&ofmt), OK); int32_t ocolor = -1; EXPECT_TRUE(ofmt->findInt32("color-format", &ocolor)); EXPECT_EQ(ocolor, xcolor); EXPECT_EQ(codec->start(), OK); // assume we can submit all input before dequeuing output size_t frameIx = 0; size_t ix; sp buf; for (const FrameData &frame : avcStream_A) { EXPECT_EQ(codec->dequeueInputBuffer(&ix, 1000000), OK); EXPECT_EQ(codec->getInputBuffer(ix, &buf), OK); ASSERT_GE(buf->capacity(), frame.size); memcpy(buf->base(), frame.data, frame.size); EXPECT_EQ(buf->setRange(0, frame.size), OK); bool eos = ++frameIx == NELEM(avcStream_A); EXPECT_EQ(codec->queueInputBuffer(ix, 0, frame.size, frameIx * 33333, eos ? BUFFER_FLAG_END_OF_STREAM : 0), OK); } size_t offset, size; int64_t ts; uint32_t flags; bool mInfoFormatChangedOk = true; bool mInfoBuffersChangedOk = true; while (true) { status_t err = codec->dequeueOutputBuffer(&ix, &offset, &size, &ts, &flags, 1000000); if (err == INFO_FORMAT_CHANGED && mInfoFormatChangedOk) { mInfoFormatChangedOk = false; } else if (err == INFO_OUTPUT_BUFFERS_CHANGED && mInfoBuffersChangedOk) { mInfoBuffersChangedOk = false; } else { ASSERT_EQ(err, OK); break; } } EXPECT_EQ(codec->getOutputBuffer(ix, &buf), OK); EXPECT_EQ(codec->getOutputFormat(ix, &ofmt), OK); ASSERT_TRUE(ofmt->findInt32("color-format", &ocolor)); EXPECT_EQ(ocolor, xcolor) << ofmt->debugString(8).c_str() << buf->meta()->debugString(8).c_str(); // expect an image-data in both format and meta sp imgBuf, imgBuf2; ASSERT_TRUE(ofmt->findBuffer("image-data", &imgBuf)); ASSERT_TRUE(buf->meta()->findBuffer("image-data", &imgBuf2)); EXPECT_EQ(imgBuf->size(), sizeof(MediaImage2)); ASSERT_EQ(imgBuf->size(), imgBuf2->size()); EXPECT_EQ(0, memcmp(imgBuf->data(), imgBuf2->data(), imgBuf->size())); MediaImage2 *img = (MediaImage2*)imgBuf->data(); EXPECT_EQ(img->mType, img->MEDIA_IMAGE_TYPE_YUV); EXPECT_EQ(img->mNumPlanes, 3u); EXPECT_EQ(img->mWidth, 320u); EXPECT_EQ(img->mHeight, 240u); EXPECT_EQ(img->mBitDepth, 8u); EXPECT_EQ(img->mBitDepthAllocated, 8u); // read strides from format int32_t stride, vstride; ofmt->findInt32("stride", &stride) || ofmt->findInt32("width", &stride); ofmt->findInt32("slice-height", &vstride) || ofmt->findInt32("height", &vstride); EXPECT_EQ(img->mPlane[img->Y].mHorizSubsampling, 1u); EXPECT_EQ(img->mPlane[img->Y].mVertSubsampling, 1u); EXPECT_EQ(img->mPlane[img->U].mHorizSubsampling, 2u); EXPECT_EQ(img->mPlane[img->U].mVertSubsampling, 2u); EXPECT_EQ(img->mPlane[img->V].mHorizSubsampling, 2u); EXPECT_EQ(img->mPlane[img->V].mVertSubsampling, 2u); switch (xcolor) { // defined formats case COLOR_FormatYUV420Planar: case COLOR_FormatYUV420PackedPlanar: EXPECT_EQ(img->mPlane[img->Y].mOffset, 0u); EXPECT_EQ(img->mPlane[img->Y].mColInc, 1); EXPECT_EQ(img->mPlane[img->Y].mRowInc, stride); EXPECT_EQ(img->mPlane[img->U].mOffset, (uint32_t)(stride * vstride)); EXPECT_EQ(img->mPlane[img->U].mColInc, 1); EXPECT_EQ(img->mPlane[img->U].mRowInc, stride / 2); EXPECT_EQ(img->mPlane[img->V].mOffset, (uint32_t)(stride * vstride * 5 / 4)); EXPECT_EQ(img->mPlane[img->V].mColInc, 1); EXPECT_EQ(img->mPlane[img->V].mRowInc, stride / 2); EXPECT_GE(size, (size_t)(stride * vstride * 5 / 4 + stride / 2 * 119 + 160)); EXPECT_LE(size, (size_t)(stride * vstride * 3 / 2)); break; case COLOR_FormatYUV420SemiPlanar: case COLOR_FormatYUV420PackedSemiPlanar: EXPECT_EQ(img->mPlane[img->Y].mOffset, 0u); EXPECT_EQ(img->mPlane[img->Y].mColInc, 1); EXPECT_EQ(img->mPlane[img->Y].mRowInc, stride); EXPECT_EQ(img->mPlane[img->U].mOffset, (uint32_t)(stride * vstride)); EXPECT_EQ(img->mPlane[img->U].mColInc, 2); EXPECT_EQ(img->mPlane[img->U].mRowInc, stride); EXPECT_EQ(img->mPlane[img->V].mOffset, (uint32_t)(stride * vstride + 1)); EXPECT_EQ(img->mPlane[img->V].mColInc, 2); EXPECT_EQ(img->mPlane[img->V].mRowInc, stride); EXPECT_GE(size, (size_t)(stride * vstride + stride * 119 + 320)); EXPECT_LE(size, (size_t)(stride * vstride * 3 / 2)); break; case COLOR_FormatYUV420Flexible: // anything goes, but stride should match Y plane EXPECT_EQ(img->mPlane[img->Y].mRowInc, stride); EXPECT_GE(size, std::max({ img->mPlane[img->Y].mOffset + 239 * img->mPlane[img->Y].mRowInc + 319 * img->mPlane[img->Y].mColInc + 1, img->mPlane[img->U].mOffset + 119 * img->mPlane[img->U].mRowInc + 159 * img->mPlane[img->U].mColInc + 1, img->mPlane[img->V].mOffset + 119 * img->mPlane[img->V].mRowInc + 159 * img->mPlane[img->V].mColInc + 1 })); break; default: break; } // validate all pixels #if 0 fprintf(stderr, "MediaImage { F(%ux%u) @%u+%d+%d @%u+%d+%d @%u+%d+%d }\n", img->mWidth, img->mHeight, img->mPlane[0].mOffset, img->mPlane[0].mColInc, img->mPlane[0].mRowInc, img->mPlane[1].mOffset, img->mPlane[1].mColInc, img->mPlane[1].mRowInc, img->mPlane[2].mOffset, img->mPlane[2].mColInc, img->mPlane[2].mRowInc); #endif for (ix = 0; ix < 3; ++ix) { const static uint8_t expected[] = { 210, 16, 146 }; for (uint32_t y = 0; y < img->mHeight / img->mPlane[ix].mVertSubsampling ; ++y) { for (uint32_t x = 0; x < img->mWidth / img->mPlane[ix].mHorizSubsampling; ++x) { uint8_t val = buf->data()[img->mPlane[ix].mOffset + img->mPlane[ix].mColInc * x + img->mPlane[ix].mRowInc * y]; ASSERT_EQ(val, expected[ix]) << "incorrect value for plane " << ix << " at x=" << x << ", y=" << y; } } } } INSTANTIATE_TEST_CASE_P(InputSizes, MediaCodecByteBufferTest, ::testing::Values( -1, COLOR_FormatYUV420Planar, COLOR_FormatYUV420SemiPlanar, COLOR_FormatYUV420PackedPlanar, COLOR_FormatYUV420PackedSemiPlanar, COLOR_FormatYUV420Flexible)); } // namespace android