| /* |
| * Copyright (C) 2017 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_TAG "media_omx_hidl_video_enc_test" |
| #ifdef __LP64__ |
| #define OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS |
| #endif |
| |
| #include <android-base/logging.h> |
| |
| #include <android/hardware/graphics/bufferqueue/1.0/IGraphicBufferProducer.h> |
| #include <android/hardware/graphics/bufferqueue/1.0/IProducerListener.h> |
| #include <android/hardware/media/omx/1.0/IGraphicBufferSource.h> |
| #include <android/hardware/media/omx/1.0/IOmx.h> |
| #include <android/hardware/media/omx/1.0/IOmxBufferSource.h> |
| #include <android/hardware/media/omx/1.0/IOmxNode.h> |
| #include <android/hardware/media/omx/1.0/IOmxObserver.h> |
| #include <android/hardware/media/omx/1.0/types.h> |
| #include <android/hidl/allocator/1.0/IAllocator.h> |
| #include <android/hidl/memory/1.0/IMapper.h> |
| #include <android/hidl/memory/1.0/IMemory.h> |
| #include <gtest/gtest.h> |
| #include <hidl/GtestPrinter.h> |
| |
| using ::android::hardware::graphics::bufferqueue::V1_0::IGraphicBufferProducer; |
| using ::android::hardware::graphics::bufferqueue::V1_0::IProducerListener; |
| using ::android::hardware::graphics::common::V1_0::BufferUsage; |
| using ::android::hardware::graphics::common::V1_0::PixelFormat; |
| using ::android::hardware::media::omx::V1_0::IGraphicBufferSource; |
| using ::android::hardware::media::omx::V1_0::IOmxBufferSource; |
| using ::android::hardware::media::omx::V1_0::IOmx; |
| using ::android::hardware::media::omx::V1_0::IOmxObserver; |
| using ::android::hardware::media::omx::V1_0::IOmxNode; |
| using ::android::hardware::media::omx::V1_0::Message; |
| using ::android::hardware::media::omx::V1_0::CodecBuffer; |
| using ::android::hardware::media::omx::V1_0::PortMode; |
| using ::android::hidl::allocator::V1_0::IAllocator; |
| using ::android::hidl::memory::V1_0::IMemory; |
| using ::android::hidl::memory::V1_0::IMapper; |
| using ::android::hardware::Return; |
| using ::android::hardware::Void; |
| using ::android::hardware::hidl_vec; |
| using ::android::hardware::hidl_string; |
| using ::android::sp; |
| |
| #include <getopt.h> |
| #include <media/hardware/HardwareAPI.h> |
| #include <media_video_hidl_test_common.h> |
| #include <system/window.h> |
| #include <fstream> |
| #include <variant> |
| |
| // Resource directory |
| std::string sResourceDir = ""; |
| |
| // video encoder test fixture class |
| class VideoEncHidlTest |
| : public ::testing::TestWithParam<std::tuple<std::string, std::string, std::string>> { |
| public: |
| ::std::string getTestCaseInfo() const { |
| return ::std::string() + "Component: " + component_ + " | " + "Role: " + role_ + " | " + |
| "Instance: " + instance_ + " | " + "Res: " + sResourceDir; |
| } |
| |
| virtual void SetUp() override { |
| instance_ = std::get<0>(GetParam()); |
| component_ = std::get<1>(GetParam()); |
| role_ = std::get<2>(GetParam()); |
| ASSERT_NE(sResourceDir.empty(), true); |
| |
| disableTest = false; |
| android::hardware::media::omx::V1_0::Status status; |
| omx = IOmx::getService(instance_); |
| ASSERT_NE(omx, nullptr); |
| observer = |
| new CodecObserver([this](Message msg, const BufferInfo* buffer) { |
| handleMessage(msg, buffer); |
| }); |
| ASSERT_NE(observer, nullptr); |
| if (component_.find("OMX.") != 0) disableTest = true; |
| EXPECT_TRUE(omx->allocateNode(component_, observer, |
| [&](android::hardware::media::omx::V1_0::Status _s, |
| sp<IOmxNode> const& _nl) { |
| status = _s; |
| this->omxNode = _nl; |
| }) |
| .isOk()); |
| if (status == android::hardware::media::omx::V1_0::Status::NAME_NOT_FOUND) { |
| disableTest = true; |
| std::cout << "[ WARN ] Test Disabled, component not present\n"; |
| return; |
| } |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| ASSERT_NE(omxNode, nullptr); |
| ASSERT_NE(role_.empty(), true) << "Invalid Component Role"; |
| struct StringToName { |
| const char* Name; |
| standardComp CompName; |
| }; |
| const StringToName kStringToName[] = { |
| {"h263", h263}, {"avc", avc}, {"mpeg4", mpeg4}, |
| {"hevc", hevc}, {"vp8", vp8}, {"vp9", vp9}, |
| }; |
| const size_t kNumStringToName = |
| sizeof(kStringToName) / sizeof(kStringToName[0]); |
| const char* pch; |
| char substring[OMX_MAX_STRINGNAME_SIZE]; |
| strcpy(substring, role_.c_str()); |
| pch = strchr(substring, '.'); |
| ASSERT_NE(pch, nullptr); |
| compName = unknown_comp; |
| for (size_t i = 0; i < kNumStringToName; ++i) { |
| if (!strcasecmp(pch + 1, kStringToName[i].Name)) { |
| compName = kStringToName[i].CompName; |
| break; |
| } |
| } |
| if (compName == unknown_comp) disableTest = true; |
| struct CompToCompression { |
| standardComp CompName; |
| OMX_VIDEO_CODINGTYPE eCompressionFormat; |
| }; |
| static const CompToCompression kCompToCompression[] = { |
| {h263, OMX_VIDEO_CodingH263}, {avc, OMX_VIDEO_CodingAVC}, |
| {mpeg4, OMX_VIDEO_CodingMPEG4}, {hevc, OMX_VIDEO_CodingHEVC}, |
| {vp8, OMX_VIDEO_CodingVP8}, {vp9, OMX_VIDEO_CodingVP9}, |
| }; |
| static const size_t kNumCompToCompression = |
| sizeof(kCompToCompression) / sizeof(kCompToCompression[0]); |
| size_t i; |
| for (i = 0; i < kNumCompToCompression; ++i) { |
| if (kCompToCompression[i].CompName == compName) { |
| eCompressionFormat = kCompToCompression[i].eCompressionFormat; |
| break; |
| } |
| } |
| if (i == kNumCompToCompression) disableTest = true; |
| eosFlag = false; |
| prependSPSPPS = false; |
| timestampDevTest = false; |
| producer = nullptr; |
| source = nullptr; |
| isSecure = false; |
| size_t suffixLen = strlen(".secure"); |
| if (component_.rfind(".secure") == component_.length() - suffixLen) { |
| isSecure = true; |
| } |
| if (isSecure) disableTest = true; |
| if (disableTest) std::cout << "[ WARN ] Test Disabled \n"; |
| } |
| |
| virtual void TearDown() override { |
| if (omxNode != nullptr) { |
| // If you have encountered a fatal failure, it is possible that |
| // freeNode() will not go through. Instead of hanging the app. |
| // let it pass through and report errors |
| if (::testing::Test::HasFatalFailure()) return; |
| EXPECT_TRUE((omxNode->freeNode()).isOk()); |
| omxNode = nullptr; |
| } |
| } |
| |
| // callback function to process messages received by onMessages() from IL |
| // client. |
| void handleMessage(Message msg, const BufferInfo* buffer) { |
| (void)buffer; |
| |
| if (msg.type == Message::Type::FILL_BUFFER_DONE) { |
| if (msg.data.extendedBufferData.flags & OMX_BUFFERFLAG_EOS) { |
| eosFlag = true; |
| } |
| if (msg.data.extendedBufferData.rangeLength != 0) { |
| // Test if current timestamp is among the list of queued |
| // timestamps |
| if (timestampDevTest && ((msg.data.extendedBufferData.flags & |
| OMX_BUFFERFLAG_CODECCONFIG) == 0)) { |
| bool tsHit = false; |
| android::List<uint64_t>::iterator it = |
| timestampUslist.begin(); |
| while (it != timestampUslist.end()) { |
| if (*it == msg.data.extendedBufferData.timestampUs) { |
| timestampUslist.erase(it); |
| tsHit = true; |
| break; |
| } |
| it++; |
| } |
| if (tsHit == false) { |
| if (timestampUslist.empty() == false) { |
| EXPECT_EQ(tsHit, true) |
| << "TimeStamp not recognized"; |
| } else { |
| std::cout << "[ INFO ] Received non-zero " |
| "output / TimeStamp not recognized \n"; |
| } |
| } |
| } |
| #define WRITE_OUTPUT 0 |
| #if WRITE_OUTPUT |
| static int count = 0; |
| FILE* ofp = nullptr; |
| if (count) |
| ofp = fopen("out.bin", "ab"); |
| else |
| ofp = fopen("out.bin", "wb"); |
| if (ofp != nullptr) { |
| fwrite(static_cast<void*>(buffer->mMemory->getPointer()), |
| sizeof(char), |
| msg.data.extendedBufferData.rangeLength, ofp); |
| fclose(ofp); |
| count++; |
| } |
| #endif |
| } |
| } |
| } |
| |
| enum standardComp { |
| h263, |
| avc, |
| mpeg4, |
| hevc, |
| vp8, |
| vp9, |
| unknown_comp, |
| }; |
| |
| std::string component_; |
| std::string role_; |
| std::string instance_; |
| |
| sp<IOmx> omx; |
| sp<CodecObserver> observer; |
| sp<IOmxNode> omxNode; |
| standardComp compName; |
| OMX_VIDEO_CODINGTYPE eCompressionFormat; |
| bool disableTest; |
| bool eosFlag; |
| bool prependSPSPPS; |
| ::android::List<uint64_t> timestampUslist; |
| bool timestampDevTest; |
| bool isSecure; |
| sp<IGraphicBufferProducer> producer; |
| sp<IGraphicBufferSource> source; |
| |
| protected: |
| static void description(const std::string& description) { |
| RecordProperty("description", description); |
| } |
| }; |
| |
| // CodecProducerListener class |
| struct CodecProducerListener : public IProducerListener { |
| public: |
| CodecProducerListener(int a, int b) |
| : freeBuffers(a), minUnDequeuedCount(b) {} |
| virtual ::android::hardware::Return<void> onBufferReleased() override { |
| android::Mutex::Autolock autoLock(bufferLock); |
| freeBuffers += 1; |
| return Void(); |
| } |
| virtual ::android::hardware::Return<bool> needsReleaseNotify() override { |
| return true; |
| } |
| void reduceCount() { |
| android::Mutex::Autolock autoLock(bufferLock); |
| freeBuffers -= 1; |
| EXPECT_GE(freeBuffers, minUnDequeuedCount); |
| } |
| |
| size_t freeBuffers; |
| size_t minUnDequeuedCount; |
| android::Mutex bufferLock; |
| }; |
| |
| // Mock IOmxBufferSource class. GraphicBufferSource.cpp in libstagefright/omx/ |
| // implements this class. Below is dummy class introduced to test if callback |
| // functions are actually being called or not |
| struct DummyBufferSource : public IOmxBufferSource { |
| public: |
| DummyBufferSource(sp<IOmxNode> node) { |
| callback = 0; |
| executing = false; |
| omxNode = node; |
| } |
| virtual Return<void> onOmxExecuting(); |
| virtual Return<void> onOmxIdle(); |
| virtual Return<void> onOmxLoaded(); |
| virtual Return<void> onInputBufferAdded(uint32_t buffer); |
| virtual Return<void> onInputBufferEmptied( |
| uint32_t buffer, const ::android::hardware::hidl_handle& fence); |
| |
| int callback; |
| bool executing; |
| sp<IOmxNode> omxNode; |
| android::Vector<BufferInfo> iBuffer, oBuffer; |
| }; |
| |
| Return<void> DummyBufferSource::onOmxExecuting() { |
| executing = true; |
| callback |= 0x1; |
| size_t index; |
| // Fetch a client owned input buffer and send an EOS |
| if ((index = getEmptyBufferID(&iBuffer)) < iBuffer.size()) { |
| android::hardware::media::omx::V1_0::Status status; |
| CodecBuffer t = iBuffer[index].omxBuffer; |
| t.type = CodecBuffer::Type::ANW_BUFFER; |
| native_handle_t* fenceNh = native_handle_create(0, 0); |
| EXPECT_NE(fenceNh, nullptr); |
| status = omxNode->emptyBuffer(iBuffer[index].id, t, OMX_BUFFERFLAG_EOS, |
| 0, fenceNh); |
| native_handle_close(fenceNh); |
| native_handle_delete(fenceNh); |
| EXPECT_EQ(status, android::hardware::media::omx::V1_0::Status::OK); |
| iBuffer.editItemAt(index).owner = component; |
| } |
| return Void(); |
| }; |
| |
| Return<void> DummyBufferSource::onOmxIdle() { |
| callback |= 0x2; |
| executing = false; |
| return Void(); |
| }; |
| |
| Return<void> DummyBufferSource::onOmxLoaded() { |
| callback |= 0x4; |
| return Void(); |
| }; |
| |
| Return<void> DummyBufferSource::onInputBufferAdded(uint32_t buffer) { |
| (void)buffer; |
| EXPECT_EQ(executing, false); |
| callback |= 0x8; |
| return Void(); |
| }; |
| |
| Return<void> DummyBufferSource::onInputBufferEmptied( |
| uint32_t buffer, const ::android::hardware::hidl_handle& fence) { |
| (void)fence; |
| callback |= 0x10; |
| size_t i; |
| for (i = 0; i < iBuffer.size(); i++) { |
| if (iBuffer[i].id == buffer) { |
| iBuffer.editItemAt(i).owner = client; |
| break; |
| } |
| } |
| return Void(); |
| }; |
| |
| // request VOP refresh |
| void requestIDR(sp<IOmxNode> omxNode, OMX_U32 portIndex) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_CONFIG_INTRAREFRESHVOPTYPE param; |
| param.IntraRefreshVOP = OMX_TRUE; |
| status = setPortConfig(omxNode, OMX_IndexConfigVideoIntraVOPRefresh, |
| portIndex, ¶m); |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) |
| std::cout << "[ INFO ] unable to request IDR \n"; |
| } |
| |
| // modify bitrate |
| void changeBitrate(sp<IOmxNode> omxNode, OMX_U32 portIndex, uint32_t nBitrate) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_VIDEO_CONFIG_BITRATETYPE param; |
| param.nEncodeBitrate = nBitrate; |
| status = |
| setPortConfig(omxNode, OMX_IndexConfigVideoBitrate, portIndex, ¶m); |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) |
| std::cout << "[ INFO ] unable to change Bitrate \n"; |
| } |
| |
| // modify framerate |
| Return<android::hardware::media::omx::V1_0::Status> changeFrameRate( |
| sp<IOmxNode> omxNode, OMX_U32 portIndex, uint32_t xFramerate) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_CONFIG_FRAMERATETYPE param; |
| param.xEncodeFramerate = xFramerate; |
| status = setPortConfig(omxNode, OMX_IndexConfigVideoFramerate, portIndex, |
| ¶m); |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) |
| std::cout << "[ INFO ] unable to change Framerate \n"; |
| return status; |
| } |
| |
| // modify intra refresh interval |
| void changeRefreshPeriod(sp<IOmxNode> omxNode, OMX_U32 portIndex, |
| uint32_t nRefreshPeriod) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_VIDEO_CONFIG_ANDROID_INTRAREFRESHTYPE param; |
| param.nRefreshPeriod = nRefreshPeriod; |
| status = setPortConfig(omxNode, |
| (OMX_INDEXTYPE)OMX_IndexConfigAndroidIntraRefresh, |
| portIndex, ¶m); |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) |
| std::cout << "[ INFO ] unable to change Refresh Period\n"; |
| } |
| |
| // set intra refresh interval |
| void setRefreshPeriod(sp<IOmxNode> omxNode, OMX_U32 portIndex, |
| uint32_t nRefreshPeriod) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_VIDEO_PARAM_INTRAREFRESHTYPE param; |
| param.eRefreshMode = OMX_VIDEO_IntraRefreshCyclic; |
| param.nCirMBs = 0; |
| if (nRefreshPeriod == 0) |
| param.nCirMBs = 0; |
| else { |
| OMX_PARAM_PORTDEFINITIONTYPE portDef; |
| status = getPortParam(omxNode, OMX_IndexParamPortDefinition, portIndex, |
| &portDef); |
| if (status == ::android::hardware::media::omx::V1_0::Status::OK) { |
| param.nCirMBs = |
| ((portDef.format.video.nFrameWidth + 15) >> |
| 4 * (portDef.format.video.nFrameHeight + 15) >> 4) / |
| nRefreshPeriod; |
| } |
| } |
| status = setPortParam(omxNode, OMX_IndexParamVideoIntraRefresh, portIndex, |
| ¶m); |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) |
| std::cout << "[ INFO ] unable to set Refresh Period \n"; |
| } |
| |
| void setLatency(sp<IOmxNode> omxNode, OMX_U32 portIndex, uint32_t latency) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_PARAM_U32TYPE param; |
| param.nU32 = (OMX_U32)latency; |
| status = setPortConfig(omxNode, (OMX_INDEXTYPE)OMX_IndexConfigLatency, |
| portIndex, ¶m); |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) |
| std::cout << "[ INFO ] unable to set latency\n"; |
| } |
| |
| void getLatency(sp<IOmxNode> omxNode, OMX_U32 portIndex, uint32_t* latency) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_PARAM_U32TYPE param; |
| status = getPortConfig(omxNode, (OMX_INDEXTYPE)OMX_IndexConfigLatency, |
| portIndex, ¶m); |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) |
| std::cout << "[ INFO ] unable to get latency\n"; |
| else |
| *latency = param.nU32; |
| } |
| |
| // Set Default port param. |
| void setDefaultPortParam(sp<IOmxNode> omxNode, OMX_U32 portIndex, |
| OMX_VIDEO_CODINGTYPE eCompressionFormat, |
| OMX_U32 nFrameWidth, OMX_U32 nFrameHeight, |
| OMX_U32 nBitrate, OMX_U32 xFramerate) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_PARAM_PORTDEFINITIONTYPE portDef; |
| status = getPortParam(omxNode, OMX_IndexParamPortDefinition, portIndex, |
| &portDef); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| portDef.format.video.nFrameWidth = nFrameWidth; |
| portDef.format.video.nFrameHeight = nFrameHeight; |
| portDef.format.video.nBitrate = nBitrate; |
| portDef.format.video.xFramerate = xFramerate; |
| portDef.format.video.bFlagErrorConcealment = OMX_TRUE; |
| portDef.format.video.eCompressionFormat = eCompressionFormat; |
| portDef.format.video.eColorFormat = OMX_COLOR_FormatUnused; |
| status = setPortParam(omxNode, OMX_IndexParamPortDefinition, portIndex, |
| &portDef); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| std::vector<int32_t> arrProfile; |
| std::vector<int32_t> arrLevel; |
| enumerateProfileAndLevel(omxNode, portIndex, &arrProfile, &arrLevel); |
| if (arrProfile.empty() == true || arrLevel.empty() == true) |
| ASSERT_TRUE(false); |
| int32_t profile = arrProfile[0]; |
| int32_t level = arrLevel[0]; |
| |
| switch ((int)eCompressionFormat) { |
| case OMX_VIDEO_CodingAVC: |
| setupAVCPort(omxNode, portIndex, |
| static_cast<OMX_VIDEO_AVCPROFILETYPE>(profile), |
| static_cast<OMX_VIDEO_AVCLEVELTYPE>(level), |
| xFramerate); |
| break; |
| case OMX_VIDEO_CodingHEVC: |
| setupHEVCPort(omxNode, portIndex, |
| static_cast<OMX_VIDEO_HEVCPROFILETYPE>(profile), |
| static_cast<OMX_VIDEO_HEVCLEVELTYPE>(level)); |
| break; |
| case OMX_VIDEO_CodingH263: |
| setupH263Port(omxNode, portIndex, |
| static_cast<OMX_VIDEO_H263PROFILETYPE>(profile), |
| static_cast<OMX_VIDEO_H263LEVELTYPE>(level), |
| xFramerate); |
| break; |
| case OMX_VIDEO_CodingMPEG4: |
| setupMPEG4Port(omxNode, portIndex, |
| static_cast<OMX_VIDEO_MPEG4PROFILETYPE>(profile), |
| static_cast<OMX_VIDEO_MPEG4LEVELTYPE>(level), |
| xFramerate); |
| break; |
| case OMX_VIDEO_CodingVP8: |
| setupVPXPort(omxNode, portIndex, xFramerate); |
| setupVP8Port(omxNode, portIndex, |
| static_cast<OMX_VIDEO_VP8PROFILETYPE>(profile), |
| static_cast<OMX_VIDEO_VP8LEVELTYPE>(level)); |
| break; |
| case OMX_VIDEO_CodingVP9: |
| setupVPXPort(omxNode, portIndex, xFramerate); |
| setupVP9Port(omxNode, portIndex, |
| static_cast<OMX_VIDEO_VP9PROFILETYPE>(profile), |
| static_cast<OMX_VIDEO_VP9LEVELTYPE>(level)); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // LookUpTable of clips and metadata for component testing |
| void GetURLForComponent(char* URL) { |
| strcat(URL, "bbb_352x288_420p_30fps_32frames.yuv"); |
| } |
| |
| // blocking call to ensures application to Wait till all the inputs are consumed |
| void waitOnInputConsumption(sp<IOmxNode> omxNode, sp<CodecObserver> observer, |
| android::Vector<BufferInfo>* iBuffer, |
| android::Vector<BufferInfo>* oBuffer, |
| bool inputDataIsMeta = false, |
| sp<CodecProducerListener> listener = nullptr) { |
| android::hardware::media::omx::V1_0::Status status; |
| Message msg; |
| int timeOut = TIMEOUT_COUNTER_Q; |
| |
| while (timeOut--) { |
| size_t i = 0; |
| status = |
| observer->dequeueMessage(&msg, DEFAULT_TIMEOUT_Q, iBuffer, oBuffer); |
| ASSERT_EQ(status, |
| android::hardware::media::omx::V1_0::Status::TIMED_OUT); |
| // status == TIMED_OUT, it could be due to process time being large |
| // than DEFAULT_TIMEOUT or component needs output buffers to start |
| // processing. |
| if (inputDataIsMeta) { |
| if (listener->freeBuffers == iBuffer->size()) break; |
| } else { |
| for (; i < iBuffer->size(); i++) { |
| if ((*iBuffer)[i].owner != client) break; |
| } |
| if (i == iBuffer->size()) break; |
| } |
| |
| // Dispatch an output buffer assuming outQueue.empty() is true |
| size_t index; |
| if ((index = getEmptyBufferID(oBuffer)) < oBuffer->size()) { |
| ASSERT_NO_FATAL_FAILURE( |
| dispatchOutputBuffer(omxNode, oBuffer, index)); |
| timeOut = TIMEOUT_COUNTER_Q; |
| } |
| } |
| } |
| |
| int colorFormatConversion(BufferInfo* buffer, buffer_handle_t buff, PixelFormat format, |
| std::ifstream& eleStream) { |
| android::GraphicBufferMapper& gbmapper = android::GraphicBufferMapper::get(); |
| |
| android::Rect rect(0, 0, buffer->omxBuffer.attr.anwBuffer.width, |
| buffer->omxBuffer.attr.anwBuffer.height); |
| android_ycbcr ycbcrLayout; |
| android::status_t error = android::NO_ERROR; |
| |
| if (format == PixelFormat::YV12 || format == PixelFormat::YCRCB_420_SP || |
| format == PixelFormat::YCBCR_420_888) { |
| error = gbmapper.lockYCbCr(buff, buffer->omxBuffer.attr.anwBuffer.usage, rect, |
| &ycbcrLayout); |
| EXPECT_EQ(error, android::NO_ERROR); |
| if (error != android::NO_ERROR) return 1; |
| |
| int size = ((rect.getWidth() * rect.getHeight() * 3) >> 1); |
| char* img = new char[size]; |
| if (img == nullptr) return 1; |
| eleStream.read(img, size); |
| if (eleStream.gcount() != size) { |
| delete[] img; |
| return 1; |
| } |
| |
| char* imgTmp = img; |
| char* ipBuffer = static_cast<char*>(ycbcrLayout.y); |
| for (size_t y = rect.getHeight(); y > 0; --y) { |
| memcpy(ipBuffer, imgTmp, rect.getWidth()); |
| ipBuffer += ycbcrLayout.ystride; |
| imgTmp += rect.getWidth(); |
| } |
| |
| if (format == PixelFormat::YV12) |
| EXPECT_EQ(ycbcrLayout.chroma_step, 1U); |
| else if (format == PixelFormat::YCRCB_420_SP) |
| EXPECT_EQ(ycbcrLayout.chroma_step, 2U); |
| |
| ipBuffer = static_cast<char*>(ycbcrLayout.cb); |
| for (size_t y = rect.getHeight() >> 1; y > 0; --y) { |
| for (int32_t x = 0; x < (rect.getWidth() >> 1); ++x) { |
| ipBuffer[ycbcrLayout.chroma_step * x] = *imgTmp++; |
| } |
| ipBuffer += ycbcrLayout.cstride; |
| } |
| ipBuffer = static_cast<char*>(ycbcrLayout.cr); |
| for (size_t y = rect.getHeight() >> 1; y > 0; --y) { |
| for (int32_t x = 0; x < (rect.getWidth() >> 1); ++x) { |
| ipBuffer[ycbcrLayout.chroma_step * x] = *imgTmp++; |
| } |
| ipBuffer += ycbcrLayout.cstride; |
| } |
| |
| delete[] img; |
| |
| error = gbmapper.unlock(buff); |
| EXPECT_EQ(error, android::NO_ERROR); |
| if (error != android::NO_ERROR) return 1; |
| } else { |
| void* data; |
| int32_t outBytesPerPixel; |
| int32_t outBytesPerStride; |
| error = gbmapper.lock(buff, buffer->omxBuffer.attr.anwBuffer.usage, rect, &data, |
| &outBytesPerPixel, &outBytesPerStride); |
| EXPECT_EQ(error, android::NO_ERROR); |
| if (error != android::NO_ERROR) return 1; |
| |
| if (format == PixelFormat::BGRA_8888) { |
| char* ipBuffer = static_cast<char*>(data); |
| for (size_t y = rect.getHeight(); y > 0; --y) { |
| eleStream.read(ipBuffer, rect.getWidth() * 4); |
| if (eleStream.gcount() != rect.getWidth() * 4) return 1; |
| ipBuffer += buffer->omxBuffer.attr.anwBuffer.stride * 4; |
| } |
| } else { |
| EXPECT_TRUE(false) << "un expected pixel format"; |
| return 1; |
| } |
| |
| error = gbmapper.unlock(buff); |
| EXPECT_EQ(error, android::NO_ERROR); |
| if (error != android::NO_ERROR) return 1; |
| } |
| |
| return 0; |
| } |
| |
| int fillGraphicBuffer(BufferInfo* buffer, PixelFormat format, |
| std::ifstream& eleStream) { |
| android::GraphicBufferMapper& gbmapper = android::GraphicBufferMapper::get(); |
| buffer_handle_t buff; |
| android::status_t error = android::NO_ERROR; |
| gbmapper.importBuffer( |
| buffer->omxBuffer.nativeHandle, buffer->omxBuffer.attr.anwBuffer.width, |
| buffer->omxBuffer.attr.anwBuffer.height, buffer->omxBuffer.attr.anwBuffer.layerCount, |
| static_cast<android::PixelFormat>(format), buffer->omxBuffer.attr.anwBuffer.usage, |
| buffer->omxBuffer.attr.anwBuffer.stride, &buff); |
| EXPECT_EQ(error, android::NO_ERROR); |
| if (error != android::NO_ERROR) return 1; |
| |
| if (colorFormatConversion(buffer, buff, format, eleStream)) return 1; |
| |
| error = gbmapper.freeBuffer(buff); |
| EXPECT_EQ(error, android::NO_ERROR); |
| if (error != android::NO_ERROR) return 1; |
| |
| return 0; |
| } |
| |
| int dispatchGraphicBuffer(sp<IOmxNode> omxNode, |
| sp<IGraphicBufferProducer> producer, |
| sp<CodecProducerListener> listener, |
| android::Vector<BufferInfo>* buffArray, |
| OMX_U32 portIndex, std::ifstream& eleStream, |
| uint64_t timestamp) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_PARAM_PORTDEFINITIONTYPE portDef; |
| |
| status = getPortParam(omxNode, OMX_IndexParamPortDefinition, portIndex, |
| &portDef); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) return 1; |
| |
| enum { |
| // A flag returned by dequeueBuffer when the client needs to call |
| // requestBuffer immediately thereafter. |
| BUFFER_NEEDS_REALLOCATION = 0x1, |
| // A flag returned by dequeueBuffer when all mirrored slots should be |
| // released by the client. This flag should always be processed first. |
| RELEASE_ALL_BUFFERS = 0x2, |
| }; |
| |
| int32_t slot; |
| int32_t result; |
| ::android::hardware::hidl_handle fence; |
| IGraphicBufferProducer::FrameEventHistoryDelta outTimestamps; |
| ::android::hardware::media::V1_0::AnwBuffer AnwBuffer; |
| PixelFormat format = PixelFormat::YCBCR_420_888; |
| producer->dequeueBuffer( |
| portDef.format.video.nFrameWidth, portDef.format.video.nFrameHeight, |
| format, BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN, |
| true, [&](int32_t _s, int32_t const& _n1, |
| ::android::hardware::hidl_handle const& _n2, |
| IGraphicBufferProducer::FrameEventHistoryDelta const& _n3) { |
| result = _s; |
| slot = _n1; |
| fence = _n2; |
| outTimestamps = _n3; |
| }); |
| if (result & BUFFER_NEEDS_REALLOCATION) { |
| producer->requestBuffer( |
| slot, [&](int32_t _s, |
| ::android::hardware::media::V1_0::AnwBuffer const& _n1) { |
| result = _s; |
| AnwBuffer = _n1; |
| }); |
| EXPECT_EQ(result, 0); |
| if (result != 0) return 1; |
| size_t i; |
| for (i = 0; i < buffArray->size(); i++) { |
| if ((*buffArray)[i].slot == -1) { |
| buffArray->editItemAt(i).slot = slot; |
| buffArray->editItemAt(i).omxBuffer.nativeHandle = |
| AnwBuffer.nativeHandle; |
| buffArray->editItemAt(i).omxBuffer.attr.anwBuffer = |
| AnwBuffer.attr; |
| break; |
| } |
| } |
| EXPECT_NE(i, buffArray->size()); |
| if (i == buffArray->size()) return 1; |
| } |
| EXPECT_EQ(result, 0); |
| if (result != 0) return 1; |
| |
| // fill Buffer |
| BufferInfo buffer; |
| size_t i; |
| for (i = 0; i < buffArray->size(); i++) { |
| if ((*buffArray)[i].slot == slot) { |
| buffer = (*buffArray)[i]; |
| break; |
| } |
| } |
| EXPECT_NE(i, buffArray->size()); |
| if (i == buffArray->size()) return 1; |
| if (fillGraphicBuffer(&buffer, format, eleStream)) return 1; |
| |
| // queue Buffer |
| IGraphicBufferProducer::QueueBufferOutput output; |
| IGraphicBufferProducer::QueueBufferInput input; |
| android::hardware::media::V1_0::Rect rect; |
| rect.left = 0; |
| rect.top = 0; |
| rect.right = buffer.omxBuffer.attr.anwBuffer.width; |
| rect.bottom = buffer.omxBuffer.attr.anwBuffer.height; |
| input.timestamp = timestamp; |
| input.isAutoTimestamp = false; |
| input.dataSpace = |
| android::hardware::graphics::common::V1_0::Dataspace::UNKNOWN; |
| input.crop = rect; |
| input.scalingMode = 0; |
| input.transform = 0; |
| input.stickyTransform = 0; |
| input.fence = android::hardware::hidl_handle(); |
| input.surfaceDamage = |
| android::hardware::hidl_vec<android::hardware::media::V1_0::Rect>{rect}; |
| input.getFrameTimestamps = false; |
| producer->queueBuffer( |
| buffer.slot, input, |
| [&](int32_t _s, const IGraphicBufferProducer::QueueBufferOutput& _n1) { |
| result = _s; |
| output = _n1; |
| }); |
| EXPECT_EQ(result, 0); |
| if (result != 0) return 1; |
| |
| listener->reduceCount(); |
| |
| return 0; |
| } |
| |
| int fillByteBuffer(sp<IOmxNode> omxNode, char* ipBuffer, OMX_U32 portIndexInput, |
| std::ifstream& eleStream) { |
| android::hardware::media::omx::V1_0::Status status; |
| OMX_PARAM_PORTDEFINITIONTYPE portDef; |
| uint32_t i, j; |
| |
| status = getPortParam(omxNode, OMX_IndexParamPortDefinition, portIndexInput, |
| &portDef); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| int size = ((portDef.format.video.nFrameWidth * |
| portDef.format.video.nFrameHeight * 3) >> |
| 1); |
| char* img = new char[size]; |
| if (img == nullptr) return 1; |
| eleStream.read(img, size); |
| if (eleStream.gcount() != size) { |
| delete[] img; |
| return 1; |
| } |
| |
| char* Y = ipBuffer; |
| char* imgTmp = img; |
| for (j = 0; j < portDef.format.video.nFrameHeight; ++j) { |
| memcpy(Y, imgTmp, portDef.format.video.nFrameWidth); |
| Y += portDef.format.video.nStride; |
| imgTmp += portDef.format.video.nFrameWidth; |
| } |
| |
| if (portDef.format.video.eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar) { |
| char* Cb = ipBuffer + (portDef.format.video.nFrameHeight * |
| portDef.format.video.nStride); |
| char* Cr = Cb + 1; |
| for (j = 0; j<portDef.format.video.nFrameHeight>> 1; ++j) { |
| for (i = 0; i < (portDef.format.video.nFrameWidth >> 1); ++i) { |
| Cb[2 * i] = *imgTmp++; |
| } |
| Cb += portDef.format.video.nStride; |
| } |
| for (j = 0; j<portDef.format.video.nFrameHeight>> 1; ++j) { |
| for (i = 0; i < (portDef.format.video.nFrameWidth >> 1); ++i) { |
| Cr[2 * i] = *imgTmp++; |
| } |
| Cr += portDef.format.video.nStride; |
| } |
| } else if (portDef.format.video.eColorFormat == |
| OMX_COLOR_FormatYUV420Planar) { |
| char* Cb = ipBuffer + (portDef.format.video.nFrameHeight * |
| portDef.format.video.nStride); |
| char* Cr = Cb + ((portDef.format.video.nFrameHeight * |
| portDef.format.video.nStride) >> |
| 2); |
| for (j = 0; j<portDef.format.video.nFrameHeight>> 1; ++j) { |
| memcpy(Cb, imgTmp, (portDef.format.video.nFrameWidth >> 1)); |
| Cb += (portDef.format.video.nStride >> 1); |
| imgTmp += (portDef.format.video.nFrameWidth >> 1); |
| } |
| for (j = 0; j<portDef.format.video.nFrameHeight>> 1; ++j) { |
| memcpy(Cr, imgTmp, (portDef.format.video.nFrameWidth >> 1)); |
| Cr += (portDef.format.video.nStride >> 1); |
| imgTmp += (portDef.format.video.nFrameWidth >> 1); |
| } |
| } |
| |
| delete[] img; |
| return 0; |
| } |
| |
| // Encode N Frames |
| void encodeNFrames(sp<IOmxNode> omxNode, sp<CodecObserver> observer, |
| OMX_U32 portIndexInput, OMX_U32 portIndexOutput, |
| android::Vector<BufferInfo>* iBuffer, |
| android::Vector<BufferInfo>* oBuffer, uint32_t nFrames, |
| uint32_t xFramerate, int bytesCount, |
| std::ifstream& eleStream, |
| ::android::List<uint64_t>* timestampUslist = nullptr, |
| bool signalEOS = true, bool inputDataIsMeta = false, |
| sp<IGraphicBufferProducer> producer = nullptr, |
| sp<CodecProducerListener> listener = nullptr) { |
| android::hardware::media::omx::V1_0::Status status; |
| Message msg; |
| uint64_t timestamp = 0; |
| uint32_t flags = 0; |
| int timeOut = TIMEOUT_COUNTER_Q; |
| bool iQueued, oQueued; |
| |
| uint32_t ipCount = 0; |
| if (ipCount == 0) { |
| status = changeFrameRate(omxNode, portIndexOutput, (24U << 16)); |
| if (status == ::android::hardware::media::omx::V1_0::Status::OK) |
| xFramerate = (24U << 16); |
| } |
| int32_t timestampIncr = (int)((float)1000000 / (xFramerate >> 16)); |
| if (inputDataIsMeta) timestampIncr *= 1000; // timestamp scale: Nano sec |
| |
| while (1) { |
| iQueued = oQueued = false; |
| status = |
| observer->dequeueMessage(&msg, DEFAULT_TIMEOUT_Q, iBuffer, oBuffer); |
| // Port Reconfiguration |
| if (status == android::hardware::media::omx::V1_0::Status::OK) { |
| ASSERT_EQ(msg.type, Message::Type::EVENT); |
| if (msg.data.eventData.event == OMX_EventPortSettingsChanged) { |
| ASSERT_EQ(msg.data.eventData.data1, portIndexOutput); |
| ASSERT_EQ(msg.data.eventData.data2, |
| OMX_IndexConfigAndroidIntraRefresh); |
| } else if (msg.data.eventData.event == OMX_EventError) { |
| ASSERT_TRUE(false) << "Received OMX_EventError, not sure why"; |
| } else if (msg.data.eventData.event == OMX_EventDataSpaceChanged) { |
| // TODO: how am i supposed to respond now? |
| std::cout << "[ INFO ] OMX_EventDataSpaceChanged \n"; |
| } else { |
| ASSERT_TRUE(false); |
| } |
| } |
| |
| if (nFrames == 0) break; |
| |
| // Dispatch input buffer |
| size_t index = 0; |
| if (inputDataIsMeta) { |
| if (listener->freeBuffers > listener->minUnDequeuedCount) { |
| if (dispatchGraphicBuffer(omxNode, producer, listener, iBuffer, |
| portIndexInput, eleStream, |
| timestamp)) { |
| if (::testing::Test::HasFailure()) |
| ASSERT_TRUE(false); |
| else |
| break; |
| } |
| timestamp += timestampIncr; |
| nFrames--; |
| ipCount++; |
| iQueued = true; |
| } |
| } else { |
| if ((index = getEmptyBufferID(iBuffer)) < iBuffer->size()) { |
| char* ipBuffer = static_cast<char*>(static_cast<void*>( |
| (*iBuffer)[index].mMemory->getPointer())); |
| ASSERT_LE( |
| bytesCount, |
| static_cast<int>((*iBuffer)[index].mMemory->getSize())); |
| if (fillByteBuffer(omxNode, ipBuffer, portIndexInput, |
| eleStream)) |
| break; |
| flags = OMX_BUFFERFLAG_ENDOFFRAME; |
| if (signalEOS && (nFrames == 1)) flags |= OMX_BUFFERFLAG_EOS; |
| ASSERT_NO_FATAL_FAILURE(dispatchInputBuffer( |
| omxNode, iBuffer, index, bytesCount, flags, timestamp)); |
| if (timestampUslist) timestampUslist->push_back(timestamp); |
| timestamp += timestampIncr; |
| nFrames--; |
| ipCount++; |
| iQueued = true; |
| } |
| } |
| // Dispatch output buffer |
| if ((index = getEmptyBufferID(oBuffer)) < oBuffer->size()) { |
| ASSERT_NO_FATAL_FAILURE( |
| dispatchOutputBuffer(omxNode, oBuffer, index)); |
| oQueued = true; |
| } |
| // Reset Counters when either input or output buffer is dispatched |
| if (iQueued || oQueued) |
| timeOut = TIMEOUT_COUNTER_Q; |
| else |
| timeOut--; |
| if (timeOut == 0) { |
| ASSERT_TRUE(false) << "Wait on Input/Output is found indefinite"; |
| } |
| // Runtime Param Configuration |
| if (ipCount == 15) { |
| changeBitrate(omxNode, portIndexOutput, 768000); |
| requestIDR(omxNode, portIndexOutput); |
| changeRefreshPeriod(omxNode, portIndexOutput, 15); |
| } |
| } |
| } |
| |
| // set component role |
| TEST_P(VideoEncHidlTest, SetRole) { |
| description("Test Set Component Role"); |
| if (disableTest) return; |
| android::hardware::media::omx::V1_0::Status status; |
| status = setRole(omxNode, role_); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| } |
| |
| // port format enumeration |
| TEST_P(VideoEncHidlTest, EnumeratePortFormat) { |
| description("Test Component on Mandatory Port Parameters (Port Format)"); |
| if (disableTest) return; |
| android::hardware::media::omx::V1_0::Status status; |
| uint32_t kPortIndexInput = 0, kPortIndexOutput = 1; |
| OMX_COLOR_FORMATTYPE eColorFormat = OMX_COLOR_FormatYUV420Planar; |
| OMX_U32 xFramerate = (30U << 16); |
| status = setRole(omxNode, role_); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| OMX_PORT_PARAM_TYPE params; |
| status = getParam(omxNode, OMX_IndexParamVideoInit, ¶ms); |
| if (status == ::android::hardware::media::omx::V1_0::Status::OK) { |
| ASSERT_EQ(params.nPorts, 2U); |
| kPortIndexInput = params.nStartPortNumber; |
| kPortIndexOutput = kPortIndexInput + 1; |
| } |
| status = |
| setVideoPortFormat(omxNode, kPortIndexInput, OMX_VIDEO_CodingUnused, |
| eColorFormat, xFramerate); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| status = setVideoPortFormat(omxNode, kPortIndexOutput, eCompressionFormat, |
| OMX_COLOR_FormatUnused, 0U); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| } |
| |
| // Test IOmxBufferSource CallBacks |
| TEST_P(VideoEncHidlTest, BufferSourceCallBacks) { |
| description("Test IOmxBufferSource CallBacks"); |
| if (disableTest) return; |
| android::hardware::media::omx::V1_0::Status status; |
| uint32_t kPortIndexInput = 0, kPortIndexOutput = 1; |
| status = setRole(omxNode, role_); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| OMX_PORT_PARAM_TYPE params; |
| status = getParam(omxNode, OMX_IndexParamVideoInit, ¶ms); |
| if (status == ::android::hardware::media::omx::V1_0::Status::OK) { |
| ASSERT_EQ(params.nPorts, 2U); |
| kPortIndexInput = params.nStartPortNumber; |
| kPortIndexOutput = kPortIndexInput + 1; |
| } |
| |
| // Configure input port |
| uint32_t nFrameWidth = 352; |
| uint32_t nFrameHeight = 288; |
| uint32_t xFramerate = (30U << 16); |
| OMX_COLOR_FORMATTYPE eColorFormat = OMX_COLOR_FormatAndroidOpaque; |
| setupRAWPort(omxNode, kPortIndexInput, nFrameWidth, nFrameHeight, 0, |
| xFramerate, eColorFormat); |
| |
| sp<DummyBufferSource> buffersource = new DummyBufferSource(omxNode); |
| ASSERT_NE(buffersource, nullptr); |
| status = omxNode->setInputSurface(buffersource); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| // set port mode |
| PortMode portMode[2]; |
| portMode[0] = PortMode::DYNAMIC_ANW_BUFFER; |
| portMode[1] = PortMode::PRESET_BYTE_BUFFER; |
| status = omxNode->setPortMode(kPortIndexInput, portMode[0]); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = omxNode->setPortMode(kPortIndexOutput, portMode[1]); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| // set state to idle |
| ASSERT_NO_FATAL_FAILURE(changeStateLoadedtoIdle( |
| omxNode, observer, &buffersource->iBuffer, &buffersource->oBuffer, |
| kPortIndexInput, kPortIndexOutput, portMode)); |
| // set state to executing |
| ASSERT_NO_FATAL_FAILURE(changeStateIdletoExecute(omxNode, observer)); |
| ASSERT_NO_FATAL_FAILURE(testEOS(omxNode, observer, &buffersource->iBuffer, |
| &buffersource->oBuffer, false, eosFlag)); |
| // set state to idle |
| ASSERT_NO_FATAL_FAILURE(changeStateExecutetoIdle( |
| omxNode, observer, &buffersource->iBuffer, &buffersource->oBuffer)); |
| // set state to executing |
| ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &buffersource->iBuffer, |
| &buffersource->oBuffer, kPortIndexInput, |
| kPortIndexOutput, portMode)); |
| // test for callbacks |
| EXPECT_EQ(buffersource->callback, 31); |
| } |
| |
| // test raw stream encode (input is byte buffers) |
| TEST_P(VideoEncHidlTest, EncodeTest) { |
| description("Test Encode"); |
| if (disableTest) return; |
| android::hardware::media::omx::V1_0::Status status; |
| uint32_t kPortIndexInput = 0, kPortIndexOutput = 1; |
| status = setRole(omxNode, role_); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| OMX_PORT_PARAM_TYPE params; |
| status = getParam(omxNode, OMX_IndexParamVideoInit, ¶ms); |
| if (status == ::android::hardware::media::omx::V1_0::Status::OK) { |
| ASSERT_EQ(params.nPorts, 2U); |
| kPortIndexInput = params.nStartPortNumber; |
| kPortIndexOutput = kPortIndexInput + 1; |
| } |
| char mURL[512]; |
| strcpy(mURL, sResourceDir.c_str()); |
| GetURLForComponent(mURL); |
| |
| std::ifstream eleStream; |
| |
| timestampDevTest = true; |
| |
| // Configure input port |
| uint32_t nFrameWidth = 352; |
| uint32_t nFrameHeight = 288; |
| uint32_t xFramerate = (30U << 16); |
| OMX_COLOR_FORMATTYPE eColorFormat = OMX_COLOR_FormatUnused; |
| OMX_VIDEO_PARAM_PORTFORMATTYPE portFormat; |
| portFormat.nIndex = 0; |
| while (1) { |
| status = getPortParam(omxNode, OMX_IndexParamVideoPortFormat, |
| kPortIndexInput, &portFormat); |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) break; |
| EXPECT_EQ(portFormat.eCompressionFormat, OMX_VIDEO_CodingUnused); |
| if (OMX_COLOR_FormatYUV420SemiPlanar == portFormat.eColorFormat || |
| OMX_COLOR_FormatYUV420Planar == portFormat.eColorFormat) { |
| eColorFormat = portFormat.eColorFormat; |
| break; |
| } |
| portFormat.nIndex++; |
| if (portFormat.nIndex == 512) break; |
| } |
| ASSERT_NE(eColorFormat, OMX_COLOR_FormatUnused); |
| setupRAWPort(omxNode, kPortIndexInput, nFrameWidth, nFrameHeight, 0, |
| xFramerate, eColorFormat); |
| |
| // Configure output port |
| uint32_t nBitRate = 512000; |
| ASSERT_NO_FATAL_FAILURE( |
| setDefaultPortParam(omxNode, kPortIndexOutput, eCompressionFormat, |
| nFrameWidth, nFrameHeight, nBitRate, xFramerate)); |
| setRefreshPeriod(omxNode, kPortIndexOutput, 0); |
| |
| unsigned int index; |
| omxNode->getExtensionIndex( |
| "OMX.google.android.index.prependSPSPPSToIDRFrames", |
| [&status, &index](android::hardware::media::omx::V1_0::Status _s, |
| unsigned int _nl) { |
| status = _s; |
| index = _nl; |
| }); |
| if (status == ::android::hardware::media::omx::V1_0::Status::OK) { |
| android::PrependSPSPPSToIDRFramesParams param; |
| param.bEnable = OMX_TRUE; |
| status = setParam(omxNode, static_cast<OMX_INDEXTYPE>(index), ¶m); |
| } |
| if (status != ::android::hardware::media::omx::V1_0::Status::OK) |
| std::cout << "[ INFO ] unable to prependSPSPPSToIDRFrames\n"; |
| else |
| prependSPSPPS = true; |
| |
| // set port mode |
| PortMode portMode[2]; |
| portMode[0] = portMode[1] = PortMode::PRESET_BYTE_BUFFER; |
| status = omxNode->setPortMode(kPortIndexInput, portMode[0]); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = omxNode->setPortMode(kPortIndexOutput, portMode[1]); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| uint32_t latency = 0; |
| setLatency(omxNode, kPortIndexInput, latency); |
| getLatency(omxNode, kPortIndexInput, &latency); |
| |
| android::Vector<BufferInfo> iBuffer, oBuffer; |
| |
| // set state to idle |
| ASSERT_NO_FATAL_FAILURE( |
| changeStateLoadedtoIdle(omxNode, observer, &iBuffer, &oBuffer, |
| kPortIndexInput, kPortIndexOutput, portMode)); |
| // set state to executing |
| ASSERT_NO_FATAL_FAILURE(changeStateIdletoExecute(omxNode, observer)); |
| |
| eleStream.open(mURL, std::ifstream::binary); |
| ASSERT_EQ(eleStream.is_open(), true); |
| ASSERT_NO_FATAL_FAILURE(encodeNFrames( |
| omxNode, observer, kPortIndexInput, kPortIndexOutput, &iBuffer, |
| &oBuffer, 32, xFramerate, (nFrameWidth * nFrameHeight * 3) >> 1, |
| eleStream, ×tampUslist)); |
| eleStream.close(); |
| ASSERT_NO_FATAL_FAILURE( |
| waitOnInputConsumption(omxNode, observer, &iBuffer, &oBuffer)); |
| ASSERT_NO_FATAL_FAILURE( |
| testEOS(omxNode, observer, &iBuffer, &oBuffer, false, eosFlag)); |
| if (timestampDevTest) EXPECT_EQ(timestampUslist.empty(), true); |
| |
| // set state to idle |
| ASSERT_NO_FATAL_FAILURE( |
| changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); |
| // set state to executing |
| ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, |
| kPortIndexInput, kPortIndexOutput, portMode)); |
| } |
| |
| // test raw stream encode (input is ANW buffers) |
| TEST_P(VideoEncHidlTest, EncodeTestBufferMetaModes) { |
| description("Test Encode Input buffer metamodes"); |
| if (disableTest) return; |
| android::hardware::media::omx::V1_0::Status status; |
| uint32_t kPortIndexInput = 0, kPortIndexOutput = 1; |
| status = setRole(omxNode, role_); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| OMX_PORT_PARAM_TYPE params; |
| status = getParam(omxNode, OMX_IndexParamVideoInit, ¶ms); |
| if (status == ::android::hardware::media::omx::V1_0::Status::OK) { |
| ASSERT_EQ(params.nPorts, 2U); |
| kPortIndexInput = params.nStartPortNumber; |
| kPortIndexOutput = kPortIndexInput + 1; |
| } |
| |
| // Configure input port |
| uint32_t nFrameWidth = 352; |
| uint32_t nFrameHeight = 288; |
| uint32_t xFramerate = (30U << 16); |
| OMX_COLOR_FORMATTYPE eColorFormat = OMX_COLOR_FormatAndroidOpaque; |
| setupRAWPort(omxNode, kPortIndexInput, nFrameWidth, nFrameHeight, 0, |
| xFramerate, eColorFormat); |
| |
| // Configure output port |
| uint32_t nBitRate = 512000; |
| ASSERT_NO_FATAL_FAILURE( |
| setDefaultPortParam(omxNode, kPortIndexOutput, eCompressionFormat, |
| nFrameWidth, nFrameHeight, nBitRate, xFramerate)); |
| // CreateInputSurface |
| EXPECT_TRUE(omx->createInputSurface( |
| [&](android::hardware::media::omx::V1_0::Status _s, |
| sp<IGraphicBufferProducer> const& _nl, |
| sp<IGraphicBufferSource> const& _n2) { |
| status = _s; |
| producer = _nl; |
| source = _n2; |
| }) |
| .isOk()); |
| ASSERT_NE(producer, nullptr); |
| ASSERT_NE(source, nullptr); |
| |
| // setMaxDequeuedBufferCount |
| int32_t returnval; |
| int32_t value; |
| producer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, |
| [&returnval, &value](int32_t _s, int32_t _n1) { |
| returnval = _s; |
| value = _n1; |
| }); |
| ASSERT_EQ(returnval, 0); |
| OMX_PARAM_PORTDEFINITIONTYPE portDef; |
| status = getPortParam(omxNode, OMX_IndexParamPortDefinition, |
| kPortIndexInput, &portDef); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| ASSERT_EQ(::android::OK, |
| producer->setMaxDequeuedBufferCount(portDef.nBufferCountActual)); |
| |
| // Connect :: Mock Producer Listener |
| IGraphicBufferProducer::QueueBufferOutput qbo; |
| sp<CodecProducerListener> listener = |
| new CodecProducerListener(portDef.nBufferCountActual + value, value); |
| producer->connect( |
| listener, NATIVE_WINDOW_API_CPU, false, |
| [&](int32_t _s, IGraphicBufferProducer::QueueBufferOutput const& _n1) { |
| returnval = _s; |
| qbo = _n1; |
| }); |
| ASSERT_EQ(returnval, 0); |
| |
| portDef.nBufferCountActual = portDef.nBufferCountActual + value; |
| status = setPortParam(omxNode, OMX_IndexParamPortDefinition, |
| kPortIndexInput, &portDef); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| // Do setInputSurface() |
| // enable MetaMode on input port |
| status = source->configure( |
| omxNode, android::hardware::graphics::common::V1_0::Dataspace::UNKNOWN); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| // set port mode |
| PortMode portMode[2]; |
| portMode[0] = PortMode::DYNAMIC_ANW_BUFFER; |
| portMode[1] = PortMode::PRESET_BYTE_BUFFER; |
| status = omxNode->setPortMode(kPortIndexInput, portMode[0]); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = omxNode->setPortMode(kPortIndexOutput, portMode[1]); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| char mURL[512]; |
| strcpy(mURL, sResourceDir.c_str()); |
| GetURLForComponent(mURL); |
| |
| uint32_t latency = 0; |
| setLatency(omxNode, kPortIndexInput, latency); |
| getLatency(omxNode, kPortIndexInput, &latency); |
| |
| std::ifstream eleStream; |
| |
| status = source->setSuspend(false, 0); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = source->setRepeatPreviousFrameDelayUs(100000); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = source->setMaxFps(24.0f); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = source->setTimeLapseConfig(24.0, 24.0); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = source->setTimeOffsetUs(-100); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = source->setStartTimeUs(10); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = source->setStopTimeUs(1000000); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| ::android::hardware::media::omx::V1_0::ColorAspects aspects; |
| aspects.range = |
| ::android::hardware::media::omx::V1_0::ColorAspects::Range::UNSPECIFIED; |
| aspects.primaries = ::android::hardware::media::omx::V1_0::ColorAspects:: |
| Primaries::UNSPECIFIED; |
| aspects.transfer = ::android::hardware::media::omx::V1_0::ColorAspects:: |
| Transfer::UNSPECIFIED; |
| aspects.matrixCoeffs = ::android::hardware::media::omx::V1_0::ColorAspects:: |
| MatrixCoeffs::UNSPECIFIED; |
| status = source->setColorAspects(aspects); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| int64_t stopTimeOffsetUs; |
| source->getStopTimeOffsetUs( |
| [&](android::hardware::media::omx::V1_0::Status _s, int64_t _n1) { |
| status = _s; |
| stopTimeOffsetUs = _n1; |
| }); |
| EXPECT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| android::Vector<BufferInfo> iBuffer, oBuffer; |
| // set state to idle |
| ASSERT_NO_FATAL_FAILURE( |
| changeStateLoadedtoIdle(omxNode, observer, &iBuffer, &oBuffer, |
| kPortIndexInput, kPortIndexOutput, portMode)); |
| // set state to executing |
| ASSERT_NO_FATAL_FAILURE(changeStateIdletoExecute(omxNode, observer)); |
| |
| eleStream.open(mURL, std::ifstream::binary); |
| ASSERT_EQ(eleStream.is_open(), true); |
| ASSERT_NO_FATAL_FAILURE(encodeNFrames( |
| omxNode, observer, kPortIndexInput, kPortIndexOutput, &iBuffer, |
| &oBuffer, 1024, xFramerate, (nFrameWidth * nFrameHeight * 3) >> 1, |
| eleStream, nullptr, false, true, producer, listener)); |
| eleStream.close(); |
| ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(omxNode, observer, &iBuffer, |
| &oBuffer, true, listener)); |
| ASSERT_NO_FATAL_FAILURE( |
| testEOS(omxNode, observer, &iBuffer, &oBuffer, false, eosFlag)); |
| |
| // set state to idle |
| ASSERT_NO_FATAL_FAILURE( |
| changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); |
| EXPECT_EQ(portDef.nBufferCountActual, listener->freeBuffers); |
| // set state to executing |
| ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, |
| kPortIndexInput, kPortIndexOutput, portMode)); |
| |
| returnval = producer->disconnect( |
| NATIVE_WINDOW_API_CPU, IGraphicBufferProducer::DisconnectMode::API); |
| ASSERT_EQ(returnval, 0); |
| } |
| |
| // Test end of stream |
| TEST_P(VideoEncHidlTest, EncodeTestEOS) { |
| description("Test EOS"); |
| if (disableTest) return; |
| android::hardware::media::omx::V1_0::Status status; |
| uint32_t kPortIndexInput = 0, kPortIndexOutput = 1; |
| status = setRole(omxNode, role_); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| OMX_PORT_PARAM_TYPE params; |
| status = getParam(omxNode, OMX_IndexParamVideoInit, ¶ms); |
| if (status == ::android::hardware::media::omx::V1_0::Status::OK) { |
| ASSERT_EQ(params.nPorts, 2U); |
| kPortIndexInput = params.nStartPortNumber; |
| kPortIndexOutput = kPortIndexInput + 1; |
| } |
| |
| // Configure input port |
| uint32_t nFrameWidth = 352; |
| uint32_t nFrameHeight = 288; |
| uint32_t xFramerate = (30U << 16); |
| OMX_COLOR_FORMATTYPE eColorFormat = OMX_COLOR_FormatAndroidOpaque; |
| setupRAWPort(omxNode, kPortIndexInput, nFrameWidth, nFrameHeight, 0, |
| xFramerate, eColorFormat); |
| |
| // CreateInputSurface |
| EXPECT_TRUE(omx->createInputSurface( |
| [&](android::hardware::media::omx::V1_0::Status _s, |
| sp<IGraphicBufferProducer> const& _nl, |
| sp<IGraphicBufferSource> const& _n2) { |
| status = _s; |
| producer = _nl; |
| source = _n2; |
| }) |
| .isOk()); |
| ASSERT_NE(producer, nullptr); |
| ASSERT_NE(source, nullptr); |
| |
| // setMaxDequeuedBufferCount |
| int32_t returnval; |
| int32_t value; |
| producer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, |
| [&returnval, &value](int32_t _s, int32_t _n1) { |
| returnval = _s; |
| value = _n1; |
| }); |
| ASSERT_EQ(returnval, 0); |
| OMX_PARAM_PORTDEFINITIONTYPE portDef; |
| status = getPortParam(omxNode, OMX_IndexParamPortDefinition, |
| kPortIndexInput, &portDef); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| ASSERT_EQ(::android::OK, |
| producer->setMaxDequeuedBufferCount(portDef.nBufferCountActual)); |
| |
| // Connect :: Mock Producer Listener |
| IGraphicBufferProducer::QueueBufferOutput qbo; |
| sp<CodecProducerListener> listener = |
| new CodecProducerListener(portDef.nBufferCountActual + value, value); |
| producer->connect( |
| listener, NATIVE_WINDOW_API_CPU, false, |
| [&](int32_t _s, IGraphicBufferProducer::QueueBufferOutput const& _n1) { |
| returnval = _s; |
| qbo = _n1; |
| }); |
| ASSERT_EQ(returnval, 0); |
| |
| portDef.nBufferCountActual = portDef.nBufferCountActual + value; |
| status = setPortParam(omxNode, OMX_IndexParamPortDefinition, |
| kPortIndexInput, &portDef); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| // Do setInputSurface() |
| // enable MetaMode on input port |
| status = source->configure( |
| omxNode, android::hardware::graphics::common::V1_0::Dataspace::UNKNOWN); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| // set port mode |
| PortMode portMode[2]; |
| portMode[0] = PortMode::DYNAMIC_ANW_BUFFER; |
| portMode[1] = PortMode::PRESET_BYTE_BUFFER; |
| status = omxNode->setPortMode(kPortIndexInput, portMode[0]); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| status = omxNode->setPortMode(kPortIndexOutput, portMode[1]); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| |
| android::Vector<BufferInfo> iBuffer, oBuffer; |
| // set state to idle |
| ASSERT_NO_FATAL_FAILURE( |
| changeStateLoadedtoIdle(omxNode, observer, &iBuffer, &oBuffer, |
| kPortIndexInput, kPortIndexOutput, portMode)); |
| // set state to executing |
| ASSERT_NO_FATAL_FAILURE(changeStateIdletoExecute(omxNode, observer)); |
| |
| // send EOS |
| status = source->signalEndOfInputStream(); |
| ASSERT_EQ(status, ::android::hardware::media::omx::V1_0::Status::OK); |
| ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(omxNode, observer, &iBuffer, |
| &oBuffer, true, listener)); |
| ASSERT_NO_FATAL_FAILURE( |
| testEOS(omxNode, observer, &iBuffer, &oBuffer, false, eosFlag)); |
| |
| // set state to idle |
| ASSERT_NO_FATAL_FAILURE( |
| changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer)); |
| EXPECT_EQ(portDef.nBufferCountActual, listener->freeBuffers); |
| // set state to executing |
| ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer, |
| kPortIndexInput, kPortIndexOutput, portMode)); |
| |
| returnval = producer->disconnect( |
| NATIVE_WINDOW_API_CPU, IGraphicBufferProducer::DisconnectMode::API); |
| ASSERT_EQ(returnval, 0); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(PerInstance, VideoEncHidlTest, testing::ValuesIn(kTestParameters), |
| android::hardware::PrintInstanceTupleNameToString<>); |
| |
| int main(int argc, char** argv) { |
| kTestParameters = getTestParameters("video_encoder"); |
| ::testing::InitGoogleTest(&argc, argv); |
| |
| // Set the resource directory based on command line args. |
| // Test will fail to set up if the argument is not set. |
| for (int i = 1; i < argc; i++) { |
| if (strcmp(argv[i], "-P") == 0 && i < argc - 1) { |
| sResourceDir = argv[i + 1]; |
| break; |
| } |
| } |
| |
| return RUN_ALL_TESTS(); |
| } |