From 19492a03d4d61d52f86f8210a917f1933199f990 Mon Sep 17 00:00:00 2001 From: Manisha Jajoo Date: Mon, 14 Oct 2019 16:38:17 +0530 Subject: [PATCH] Benchmark: Add test for Codec2 encoders Test: C2EncoderTest -P /data/local/tmp/MediaBenchmark/res/ Bug: 140051680 Change-Id: Ia1cda290736e55f0bfb1ab95a4fcdeb443cc03f3 --- media/tests/benchmark/README.md | 9 + .../src/native/common/BenchmarkCommon.h | 1 + .../benchmark/src/native/encoder/Android.bp | 20 ++ .../src/native/encoder/C2Encoder.cpp | 264 ++++++++++++++++++ .../benchmark/src/native/encoder/C2Encoder.h | 66 +++++ media/tests/benchmark/tests/Android.bp | 17 ++ media/tests/benchmark/tests/C2EncoderTest.cpp | 177 ++++++++++++ 7 files changed, 554 insertions(+) create mode 100644 media/tests/benchmark/src/native/encoder/C2Encoder.cpp create mode 100644 media/tests/benchmark/src/native/encoder/C2Encoder.h create mode 100644 media/tests/benchmark/tests/C2EncoderTest.cpp diff --git a/media/tests/benchmark/README.md b/media/tests/benchmark/README.md index 6627462950..05fbe6f3df 100644 --- a/media/tests/benchmark/README.md +++ b/media/tests/benchmark/README.md @@ -145,3 +145,12 @@ Setup steps are same as [extractor](#extractor). ``` adb shell /data/local/tmp/C2DecoderTest -P /data/local/tmp/MediaBenchmark/res/ ``` +## C2 Encoder + +The test encodes input stream and benchmarks the codec2 encoders available in device. + +Setup steps are same as [extractor](#extractor). + +``` +adb shell /data/local/tmp/C2EncoderTest -P /data/local/tmp/MediaBenchmark/res/ +``` diff --git a/media/tests/benchmark/src/native/common/BenchmarkCommon.h b/media/tests/benchmark/src/native/common/BenchmarkCommon.h index 8153a864c6..e13158afc4 100644 --- a/media/tests/benchmark/src/native/common/BenchmarkCommon.h +++ b/media/tests/benchmark/src/native/common/BenchmarkCommon.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/media/tests/benchmark/src/native/encoder/Android.bp b/media/tests/benchmark/src/native/encoder/Android.bp index 239f378684..57865efe39 100644 --- a/media/tests/benchmark/src/native/encoder/Android.bp +++ b/media/tests/benchmark/src/native/encoder/Android.bp @@ -31,3 +31,23 @@ cc_library_static { ldflags: ["-Wl,-Bsymbolic"] } + +cc_library_static { + name: "libmediabenchmark_codec2_encoder", + defaults: [ + "libmediabenchmark_common-defaults", + "libmediabenchmark_codec2_common-defaults", + ], + + srcs: ["C2Encoder.cpp"], + + static_libs: [ + "libmediabenchmark_codec2_common", + "libmediabenchmark_extractor", + "libmediabenchmark_decoder", + ], + + export_include_dirs: ["."], + + ldflags: ["-Wl,-Bsymbolic"] +} diff --git a/media/tests/benchmark/src/native/encoder/C2Encoder.cpp b/media/tests/benchmark/src/native/encoder/C2Encoder.cpp new file mode 100644 index 0000000000..33429efff4 --- /dev/null +++ b/media/tests/benchmark/src/native/encoder/C2Encoder.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2019 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "C2Encoder" + +#include "C2Encoder.h" + +int32_t C2Encoder::createCodec2Component(string compName, AMediaFormat *format) { + ALOGV("In %s", __func__); + mListener.reset(new CodecListener( + [this](std::list> &workItems) { handleWorkDone(workItems); })); + if (!mListener) return -1; + + const char *mime = nullptr; + AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime); + if (!mime) { + ALOGE("Error in AMediaFormat_getString"); + return -1; + } + // Configure the plugin with Input properties + std::vector configParam; + if (!strncmp(mime, "audio/", 6)) { + mIsAudioEncoder = true; + int32_t numChannels; + if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &mSampleRate)) { + ALOGE("AMEDIAFORMAT_KEY_SAMPLE_RATE not set"); + return -1; + } + if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &numChannels)) { + ALOGE("AMEDIAFORMAT_KEY_CHANNEL_COUNT not set"); + return -1; + } + C2StreamSampleRateInfo::input sampleRateInfo(0u, mSampleRate); + C2StreamChannelCountInfo::input channelCountInfo(0u, numChannels); + configParam.push_back(&sampleRateInfo); + configParam.push_back(&channelCountInfo); + } else { + mIsAudioEncoder = false; + if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth)) { + ALOGE("AMEDIAFORMAT_KEY_WIDTH not set"); + return -1; + } + if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight)) { + ALOGE("AMEDIAFORMAT_KEY_HEIGHT not set"); + return -1; + } + C2StreamPictureSizeInfo::input inputSize(0u, mWidth, mHeight); + configParam.push_back(&inputSize); + + if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &mFrameRate) || + (mFrameRate <= 0)) { + mFrameRate = KDefaultFrameRate; + } + } + + int64_t sTime = mStats->getCurTime(); + mComponent = mClient->CreateComponentByName(compName.c_str(), mListener, &mClient); + if (mComponent == nullptr) { + ALOGE("Create component failed for %s", compName.c_str()); + return -1; + } + std::vector> failures; + int32_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures); + if (failures.size() != 0) { + ALOGE("Invalid Configuration"); + return -1; + } + + status |= mComponent->start(); + int64_t eTime = mStats->getCurTime(); + int64_t timeTaken = mStats->getTimeDiff(sTime, eTime); + mStats->setInitTime(timeTaken); + return status; +} + +// In encoder components, fetch the size of input buffer allocated +int32_t C2Encoder::getInputMaxBufSize() { + int32_t bitStreamInfo[1] = {0}; + std::vector> inParams; + c2_status_t status = mComponent->query({}, {C2StreamMaxBufferSizeInfo::input::PARAM_TYPE}, + C2_DONT_BLOCK, &inParams); + if (status != C2_OK && inParams.size() == 0) { + ALOGE("Query MaxBufferSizeInfo failed => %d", status); + return status; + } else { + size_t offset = sizeof(C2Param); + for (size_t i = 0; i < inParams.size(); ++i) { + C2Param *param = inParams[i].get(); + bitStreamInfo[i] = *(int32_t *)((uint8_t *)param + offset); + } + } + mInputMaxBufSize = bitStreamInfo[0]; + if (mInputMaxBufSize < 0) { + ALOGE("Invalid mInputMaxBufSize %d\n", mInputMaxBufSize); + return -1; + } + return status; +} + +int32_t C2Encoder::encodeFrames(ifstream &eleStream, size_t inputBufferSize) { + ALOGV("In %s", __func__); + int32_t frameSize = 0; + if (!mIsAudioEncoder) { + frameSize = mWidth * mHeight * 3 / 2; + } else { + frameSize = DEFAULT_AUDIO_FRAME_SIZE; + if (getInputMaxBufSize() != 0) return -1; + if (frameSize > mInputMaxBufSize) { + frameSize = mInputMaxBufSize; + } + } + int32_t numFrames = (inputBufferSize + frameSize - 1) / frameSize; + // Temporary buffer to read data from the input file + char *data = (char *)malloc(frameSize); + if (!data) { + ALOGE("Insufficient memory to read from input file"); + return -1; + } + + typedef std::unique_lock ULock; + uint64_t presentationTimeUs = 0; + size_t offset = 0; + c2_status_t status = C2_OK; + + mStats->setStartTime(); + while (numFrames > 0) { + std::unique_ptr work; + // Prepare C2Work + { + ULock l(mQueueLock); + if (mWorkQueue.empty()) mQueueCondition.wait_for(l, MAX_RETRY * TIME_OUT); + if (!mWorkQueue.empty()) { + mStats->addInputTime(); + work.swap(mWorkQueue.front()); + mWorkQueue.pop_front(); + } else { + cout << "Wait for generating C2Work exceeded timeout" << endl; + return -1; + } + } + + if (mIsAudioEncoder) { + presentationTimeUs = mNumInputFrame * frameSize * (1000000 / mSampleRate); + } else { + presentationTimeUs = mNumInputFrame * (1000000 / mFrameRate); + } + uint32_t flags = 0; + if (numFrames == 1) flags |= C2FrameData::FLAG_END_OF_STREAM; + + work->input.flags = (C2FrameData::flags_t)flags; + work->input.ordinal.timestamp = presentationTimeUs; + work->input.ordinal.frameIndex = mNumInputFrame; + work->input.buffers.clear(); + + if (inputBufferSize - offset < frameSize) { + frameSize = inputBufferSize - offset; + } + eleStream.read(data, frameSize); + if (eleStream.gcount() != frameSize) { + ALOGE("read() from file failed. Incorrect bytes read"); + return -1; + } + offset += frameSize; + + if (frameSize) { + if (mIsAudioEncoder) { + std::shared_ptr block; + status = mLinearPool->fetchLinearBlock( + frameSize, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block); + if (status != C2_OK || !block) { + cout << "fetchLinearBlock failed : " << status << endl; + return status; + } + C2WriteView view = block->map().get(); + if (view.error() != C2_OK) { + cout << "C2LinearBlock::map() failed : " << view.error() << endl; + return view.error(); + } + + memcpy(view.base(), data, frameSize); + work->input.buffers.emplace_back(new LinearBuffer(block)); + } else { + std::shared_ptr block; + status = mGraphicPool->fetchGraphicBlock( + mWidth, mHeight, HAL_PIXEL_FORMAT_YV12, + {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block); + if (status != C2_OK || !block) { + cout << "fetchGraphicBlock failed : " << status << endl; + return status; + } + C2GraphicView view = block->map().get(); + if (view.error() != C2_OK) { + cout << "C2GraphicBlock::map() failed : " << view.error() << endl; + return view.error(); + } + + uint8_t *pY = view.data()[C2PlanarLayout::PLANE_Y]; + uint8_t *pU = view.data()[C2PlanarLayout::PLANE_U]; + uint8_t *pV = view.data()[C2PlanarLayout::PLANE_V]; + memcpy(pY, data, mWidth * mHeight); + memcpy(pU, data + mWidth * mHeight, (mWidth * mHeight >> 2)); + memcpy(pV, data + (mWidth * mHeight * 5 >> 2), mWidth * mHeight >> 2); + work->input.buffers.emplace_back(new GraphicBuffer(block)); + } + mStats->addFrameSize(frameSize); + } + + work->worklets.clear(); + work->worklets.emplace_back(new C2Worklet); + + std::list> items; + items.push_back(std::move(work)); + // queue() invokes process() function of C2 Plugin. + status = mComponent->queue(&items); + if (status != C2_OK) { + ALOGE("queue failed"); + return status; + } + ALOGV("Frame #%d size = %d queued", mNumInputFrame, frameSize); + numFrames--; + mNumInputFrame++; + } + free(data); + return status; +} + +void C2Encoder::deInitCodec() { + ALOGV("In %s", __func__); + if (!mComponent) return; + + int64_t sTime = mStats->getCurTime(); + mComponent->stop(); + mComponent->release(); + mComponent = nullptr; + int64_t eTime = mStats->getCurTime(); + int64_t timeTaken = mStats->getTimeDiff(sTime, eTime); + mStats->setDeInitTime(timeTaken); +} + +void C2Encoder::dumpStatistics(string inputReference, int64_t durationUs) { + string operation = "c2encode"; + mStats->dumpStatistics(operation, inputReference, durationUs); +} + +void C2Encoder::resetEncoder() { + mIsAudioEncoder = false; + mNumInputFrame = 0; + mEos = false; + if (mStats) mStats->reset(); +} diff --git a/media/tests/benchmark/src/native/encoder/C2Encoder.h b/media/tests/benchmark/src/native/encoder/C2Encoder.h new file mode 100644 index 0000000000..a4ca09767a --- /dev/null +++ b/media/tests/benchmark/src/native/encoder/C2Encoder.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2019 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. + */ + +#ifndef __C2_ENCODER_H__ +#define __C2_ENCODER_H__ + +#include +#include +#include + +#include "BenchmarkC2Common.h" + +#define DEFAULT_AUDIO_FRAME_SIZE 4096 + +constexpr int32_t KDefaultFrameRate = 25; + +class C2Encoder : public BenchmarkC2Common { + public: + C2Encoder() + : mIsAudioEncoder(false), + mWidth(0), + mHeight(0), + mNumInputFrame(0), + mComponent(nullptr) {} + + int32_t createCodec2Component(string codecName, AMediaFormat *format); + + int32_t encodeFrames(ifstream &eleStream, size_t inputBufferSize); + + int32_t getInputMaxBufSize(); + + void deInitCodec(); + + void dumpStatistics(string inputReference, int64_t durationUs); + + void resetEncoder(); + + private: + bool mIsAudioEncoder; + + int32_t mWidth; + int32_t mHeight; + int32_t mFrameRate; + int32_t mSampleRate; + + int32_t mNumInputFrame; + int32_t mInputMaxBufSize; + + std::shared_ptr mListener; + std::shared_ptr mComponent; +}; + +#endif // __C2_ENCODER_H__ diff --git a/media/tests/benchmark/tests/Android.bp b/media/tests/benchmark/tests/Android.bp index 128d055da2..de6a8bf177 100644 --- a/media/tests/benchmark/tests/Android.bp +++ b/media/tests/benchmark/tests/Android.bp @@ -92,3 +92,20 @@ cc_test { "libmediabenchmark_codec2_decoder", ], } + +cc_test { + name: "C2EncoderTest", + gtest: true, + defaults: [ + "libmediabenchmark_codec2_common-defaults", + ], + + srcs: ["C2EncoderTest.cpp"], + + static_libs: [ + "libmediabenchmark_extractor", + "libmediabenchmark_decoder", + "libmediabenchmark_codec2_common", + "libmediabenchmark_codec2_encoder", + ], +} diff --git a/media/tests/benchmark/tests/C2EncoderTest.cpp b/media/tests/benchmark/tests/C2EncoderTest.cpp new file mode 100644 index 0000000000..7eb5ff28ed --- /dev/null +++ b/media/tests/benchmark/tests/C2EncoderTest.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2019 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "C2EncoderTest" + +#include +#include +#include +#include + +#include "BenchmarkTestEnvironment.h" +#include "C2Encoder.h" +#include "Decoder.h" + +static BenchmarkTestEnvironment *gEnv = nullptr; + +class C2EncoderTest : public ::testing::TestWithParam> { + public: + C2EncoderTest() : mEncoder(nullptr) { setupC2EncoderTest(); } + + void setupC2EncoderTest(); + + vector mCodecList; + C2Encoder *mEncoder; +}; + +void C2EncoderTest::setupC2EncoderTest() { + mEncoder = new C2Encoder(); + ASSERT_NE(mEncoder, nullptr) << "C2Encoder creation failed"; + + int32_t status = mEncoder->setupCodec2(); + ASSERT_EQ(status, 0) << "Codec2 setup failed"; + + mCodecList = mEncoder->getSupportedComponentList(true /* isEncoder*/); + ASSERT_GT(mCodecList.size(), 0) << "Codec2 client didn't recognise any component"; +} + +TEST_P(C2EncoderTest, Codec2Encode) { + ALOGV("Encodes the input using codec2 framework"); + string inputFile = gEnv->getRes() + GetParam().first; + FILE *inputFp = fopen(inputFile.c_str(), "rb"); + ASSERT_NE(inputFp, nullptr) << "Unable to open input file for reading"; + + Decoder *decoder = new Decoder(); + ASSERT_NE(decoder, nullptr) << "Decoder creation failed"; + + Extractor *extractor = decoder->getExtractor(); + ASSERT_NE(extractor, nullptr) << "Extractor creation failed"; + + // Read file properties + struct stat buf; + stat(inputFile.c_str(), &buf); + size_t fileSize = buf.st_size; + int32_t fd = fileno(inputFp); + + ASSERT_LE(fileSize, kMaxBufferSize) + << "Input file size is greater than the threshold memory dedicated to the test"; + + int32_t trackCount = extractor->initExtractor(fd, fileSize); + ASSERT_GT(trackCount, 0) << "initExtractor failed"; + + for (int curTrack = 0; curTrack < trackCount; curTrack++) { + int32_t status = extractor->setupTrackFormat(curTrack); + ASSERT_EQ(status, 0) << "Track Format invalid"; + + uint8_t *inputBuffer = (uint8_t *)malloc(fileSize); + ASSERT_NE(inputBuffer, nullptr) << "Insufficient memory"; + + vector frameInfo; + AMediaCodecBufferInfo info; + uint32_t inputBufferOffset = 0; + + // Get frame data + while (1) { + status = extractor->getFrameSample(info); + if (status || !info.size) break; + // copy the meta data and buffer to be passed to decoder + ASSERT_LE(inputBufferOffset + info.size, fileSize) << "Memory allocated not sufficient"; + + memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(), info.size); + frameInfo.push_back(info); + inputBufferOffset += info.size; + } + + string decName = ""; + string outputFileName = "decode.out"; + FILE *outFp = fopen(outputFileName.c_str(), "wb"); + ASSERT_NE(outFp, nullptr) << "Unable to open output file" << outputFileName + << " for dumping decoder's output"; + + decoder->setupDecoder(); + status = decoder->decode(inputBuffer, frameInfo, decName, false /*asyncMode */, outFp); + ASSERT_EQ(status, AMEDIA_OK) << "Decode returned error : " << status; + + // Encode the given input stream for all C2 codecs supported by device + AMediaFormat *format = extractor->getFormat(); + ifstream eleStream; + eleStream.open(outputFileName.c_str(), ifstream::binary | ifstream::ate); + ASSERT_EQ(eleStream.is_open(), true) << outputFileName.c_str() << " - file not found"; + size_t eleSize = eleStream.tellg(); + + for (string codecName : mCodecList) { + if (codecName.find(GetParam().second) != string::npos) { + status = mEncoder->createCodec2Component(codecName, format); + ASSERT_EQ(status, 0) << "Create component failed for " << codecName; + + // Send the inputs to C2 Encoder and wait till all buffers are returned. + eleStream.seekg(0, ifstream::beg); + status = mEncoder->encodeFrames(eleStream, eleSize); + ASSERT_EQ(status, 0) << "Encoder failed for " << codecName; + + mEncoder->waitOnInputConsumption(); + ASSERT_TRUE(mEncoder->mEos) << "Test Failed. Didn't receive EOS \n"; + + mEncoder->deInitCodec(); + int64_t durationUs = extractor->getClipDuration(); + cout << "codec: " << codecName << endl; + mEncoder->dumpStatistics(GetParam().first, durationUs); + mEncoder->resetEncoder(); + } + } + + // Destroy the decoder for the given input + decoder->deInitCodec(); + decoder->resetDecoder(); + free(inputBuffer); + } + fclose(inputFp); + extractor->deInitExtractor(); + delete decoder; + delete mEncoder; +} + +INSTANTIATE_TEST_SUITE_P( + AudioEncoderTest, C2EncoderTest, + ::testing::Values( + make_pair("bbb_44100hz_2ch_128kbps_aac_30sec.mp4", "aac"), + make_pair("bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", "amrnb"), + make_pair("bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", "amrwb"), + make_pair("bbb_44100hz_2ch_600kbps_flac_30sec.mp4", "flac"), + make_pair("bbb_48000hz_2ch_100kbps_opus_30sec.webm", "opus"))); + +INSTANTIATE_TEST_SUITE_P( + VideoEncoderTest, C2EncoderTest, + ::testing::Values( + make_pair("crowd_1920x1080_25fps_4000kbps_vp9.webm", "vp9"), + make_pair("crowd_1920x1080_25fps_4000kbps_vp8.webm", "vp8"), + make_pair("crowd_176x144_25fps_6000kbps_mpeg4.mp4", "mpeg4"), + make_pair("crowd_176x144_25fps_6000kbps_h263.3gp", "h263"), + make_pair("crowd_1920x1080_25fps_6700kbps_h264.ts", "avc"), + make_pair("crowd_1920x1080_25fps_4000kbps_h265.mkv", "hevc"))); + +int main(int argc, char **argv) { + gEnv = new BenchmarkTestEnvironment(); + ::testing::AddGlobalTestEnvironment(gEnv); + ::testing::InitGoogleTest(&argc, argv); + int status = gEnv->initFromOptions(argc, argv); + if (status == 0) { + status = RUN_ALL_TESTS(); + ALOGV("C2 Encoder Test result = %d\n", status); + } + return status; +}