/* * Copyright (C) 2020 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 #include "../includes/common.h" using android::C2AllocatorIon; using std::chrono_literals::operator""ms; #define MAXIMUM_NUMBER_OF_RETRIES 20 #define QUEUE_TIMEOUT 400ms #define MAXIMUM_NUMBER_OF_INPUT_BUFFERS 8 #define ALIGN(_sz, _align) ((_sz + (_align - 1)) & ~(_align - 1)) void workDone(const std::shared_ptr &component, std::unique_ptr &work, std::list &flushedIndices, std::mutex &queueLock, std::condition_variable &queueCondition, std::list> &workQueue, bool &eos, bool &csd, uint32_t &framesReceived); struct FrameInfo { int bytesCount; uint32_t flags; int64_t timestamp; }; class LinearBuffer : public C2Buffer { public: explicit LinearBuffer(const std::shared_ptr &block) : C2Buffer({block->share(block->offset(), block->size(), ::C2Fence())}) {} explicit LinearBuffer(const std::shared_ptr &block, size_t size) : C2Buffer({block->share(block->offset(), size, ::C2Fence())}) {} }; /* * Handle Callback functions onWorkDone(), onTripped(), * onError(), onDeath(), onFramesRendered() */ struct CodecListener : public android::Codec2Client::Listener { public: CodecListener( const std::function> &workItems)> fn = nullptr) : callBack(fn) {} virtual void onWorkDone(const std::weak_ptr &comp, std::list> &workItems) override { (void)comp; if (callBack) { callBack(workItems); } } virtual void onTripped( const std::weak_ptr &comp, const std::vector> &settingResults) override { (void)comp; (void)settingResults; } virtual void onError(const std::weak_ptr &comp, uint32_t errorCode) override { (void)comp; (void)errorCode; } virtual void onDeath(const std::weak_ptr &comp) override { (void)comp; } virtual void onInputBufferDone(uint64_t frameIndex, size_t arrayIndex) override { (void)frameIndex; (void)arrayIndex; } virtual void onFrameRendered(uint64_t bufferQueueId, int32_t slotId, int64_t timestampNs) override { (void)bufferQueueId; (void)slotId; (void)timestampNs; } std::function> &workItems)> callBack; }; class Codec2VideoDecHidlTestBase { public: bool SetUp() { mClient = getClient(); if (!mClient) { return false; } mListener.reset(new CodecListener([this](std::list> &workItems) { handleWorkDone(workItems); })); if (!mListener) { return false; } if (android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener, &mComponent, &mClient) != C2_OK) { return false; } for (int i = 0; i < MAXIMUM_NUMBER_OF_INPUT_BUFFERS; ++i) { mWorkQueue.emplace_back(new C2Work); } std::shared_ptr store = android::GetCodec2PlatformAllocatorStore(); if (store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mLinearAllocator) != C2_OK) { return false; } mLinearPool = std::make_shared(mLinearAllocator, ++mBlockPoolId); if (!mLinearPool) { return false; } mEos = false; mHasVulnerability = false; mTimestampUs = 0u; mWorkResult = C2_OK; mFramesReceived = 0; return true; } ~Codec2VideoDecHidlTestBase() { if (mComponent != nullptr) { mComponent->release(); mComponent = nullptr; } } std::shared_ptr getClient() { auto instances = android::Codec2Client::GetServiceNames(); for (std::string instance : instances) { std::shared_ptr client = android::Codec2Client::CreateFromService(instance.c_str()); std::vector components = client->listComponents(); for (C2Component::Traits traits : components) { if (instance.compare(traits.owner)) { continue; } if (traits.domain == DOMAIN_VIDEO && traits.kind == KIND_DECODER && mComponentName.compare(traits.name)) { return android::Codec2Client:: CreateFromService(instance.c_str(), !bool(android::Codec2Client:: CreateFromService("default", true))); } } } return nullptr; } void checkBufferOK(std::unique_ptr &work) { const C2GraphicView output = work->worklets.front() ->output.buffers[0] ->data() .graphicBlocks() .front() .map() .get(); uint8_t *uPlane = const_cast(output.data()[C2PlanarLayout::PLANE_U]); uint8_t *vPlane = const_cast(output.data()[C2PlanarLayout::PLANE_V]); const uint8_t ul[] = {109, 109, 109, 109, 109, 109, 109}; const uint8_t vl[] = {121, 121, 121, 121, 121, 121, 121}; const uint8_t ur[] = {114, 114, 120, 120, 122, 127, 127}; const uint8_t vr[] = {126, 121, 123, 121, 123, 126, 126}; if (!memcmp(uPlane - 7, ul, 7) && !memcmp(vPlane - 7, vl, 7) && !memcmp(uPlane + 1, ur, 7) && !memcmp(vPlane + 1, vr, 7)) { mHasVulnerability |= true; } } // Config output pixel format bool configPixelFormat(uint32_t format) { std::vector> failures; C2StreamPixelFormatInfo::output pixelformat(0u, format); std::vector configParam{&pixelformat}; c2_status_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures); if (status == C2_OK && failures.size() == 0u) { return true; } return false; } // callback function to process onWorkDone received by Listener void handleWorkDone(std::list> &workItems) { for (std::unique_ptr &work : workItems) { if (!work->worklets.empty()) { // For decoder components current timestamp always exceeds // previous timestamp if output is in display order mWorkResult |= work->result; bool codecConfig = ((work->worklets.front()->output.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0); if (!codecConfig && !work->worklets.front()->output.buffers.empty()) { checkBufferOK(work); } bool mCsd = false; workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, mWorkQueue, mEos, mCsd, mFramesReceived); (void)mCsd; } } } const std::string mComponentName = "c2.android.hevc.decoder"; bool mEos; bool mHasVulnerability; uint64_t mTimestampUs; int32_t mWorkResult; uint32_t mFramesReceived; std::list mFlushedIndices; C2BlockPool::local_id_t mBlockPoolId; std::shared_ptr mLinearPool; std::shared_ptr mLinearAllocator; std::mutex mQueueLock; std::condition_variable mQueueCondition; std::list> mWorkQueue; std::shared_ptr mClient; std::shared_ptr mListener; std::shared_ptr mComponent; }; // process onWorkDone received by Listener void workDone(const std::shared_ptr &component, std::unique_ptr &work, std::list &flushedIndices, std::mutex &queueLock, std::condition_variable &queueCondition, std::list> &workQueue, bool &eos, bool &csd, uint32_t &framesReceived) { // handle configuration changes in work done if (work->worklets.front()->output.configUpdate.size() != 0) { std::vector> updates = std::move(work->worklets.front()->output.configUpdate); std::vector configParam; std::vector> failures; for (size_t i = 0; i < updates.size(); ++i) { C2Param *param = updates[i].get(); if (param->index() == C2StreamInitDataInfo::output::PARAM_TYPE) { C2StreamInitDataInfo::output *csdBuffer = (C2StreamInitDataInfo::output *)(param); size_t csdSize = csdBuffer->flexCount(); if (csdSize > 0) { csd = true; } } else if ((param->index() == C2StreamSampleRateInfo::output::PARAM_TYPE) || (param->index() == C2StreamChannelCountInfo::output::PARAM_TYPE) || (param->index() == C2StreamPictureSizeInfo::output::PARAM_TYPE)) { configParam.push_back(param); } } component->config(configParam, C2_DONT_BLOCK, &failures); assert(failures.size() == 0u); } if (work->worklets.front()->output.flags != C2FrameData::FLAG_INCOMPLETE) { ++framesReceived; eos = (work->worklets.front()->output.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; auto frameIndexIt = std::find(flushedIndices.begin(), flushedIndices.end(), work->input.ordinal.frameIndex.peeku()); work->input.buffers.clear(); work->worklets.clear(); { typedef std::unique_lock ULock; ULock l(queueLock); workQueue.push_back(std::move(work)); if (!flushedIndices.empty() && (frameIndexIt != flushedIndices.end())) { flushedIndices.erase(frameIndexIt); } queueCondition.notify_all(); } } } bool decodeNFrames(const std::shared_ptr &component, std::mutex &queueLock, std::condition_variable &queueCondition, std::list> &workQueue, std::list &flushedIndices, std::shared_ptr &linearPool, std::ifstream &ifStream, android::Vector *Info) { typedef std::unique_lock ULock; int frameID = 0; int retryCount = 0; while (1) { if (frameID == (int)Info->size()) { break; } uint32_t flags = 0; std::unique_ptr work; // Prepare C2Work while (!work && (retryCount < MAXIMUM_NUMBER_OF_RETRIES)) { ULock l(queueLock); if (!workQueue.empty()) { work.swap(workQueue.front()); workQueue.pop_front(); } else { queueCondition.wait_for(l, QUEUE_TIMEOUT); ++retryCount; } } if (!work && (retryCount >= MAXIMUM_NUMBER_OF_RETRIES)) { return false; // "Wait for generating C2Work exceeded timeout" } int64_t timestamp = (*Info)[frameID].timestamp; if ((*Info)[frameID].flags) { flags = (1 << ((*Info)[frameID].flags - 1)); } if (frameID == (int)Info->size() - 1) { flags |= C2FrameData::FLAG_END_OF_STREAM; } work->input.flags = (C2FrameData::flags_t)flags; work->input.ordinal.timestamp = timestamp; work->input.ordinal.frameIndex = frameID; { ULock l(queueLock); flushedIndices.emplace_back(frameID); } int size = (*Info)[frameID].bytesCount; char *data = (char *)malloc(size); if (!data) { return false; } ifStream.read(data, size); if (ifStream.gcount() != size) { return false; } work->input.buffers.clear(); auto alignedSize = ALIGN(size, PAGE_SIZE); if (size) { std::shared_ptr block; if (linearPool->fetchLinearBlock(alignedSize, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block) != C2_OK) { return false; } if (!block) { return false; } // Write View C2WriteView view = block->map().get(); if (view.error() != C2_OK) { return false; } if ((size_t)alignedSize != view.capacity()) { return false; } if (0u != view.offset()) { return false; } if ((size_t)alignedSize != view.size()) { return false; } memcpy(view.base(), data, size); work->input.buffers.emplace_back(new LinearBuffer(block, size)); free(data); } work->worklets.clear(); work->worklets.emplace_back(new C2Worklet); std::list> items; items.push_back(std::move(work)); // DO THE DECODING if (component->queue(&items) != C2_OK) { return false; } ++frameID; retryCount = 0; } return true; } // Wait for all the inputs to be consumed by the plugin. void waitOnInputConsumption(std::mutex &queueLock, std::condition_variable &queueCondition, std::list> &workQueue, size_t bufferCount = MAXIMUM_NUMBER_OF_INPUT_BUFFERS) { typedef std::unique_lock ULock; uint32_t queueSize; uint32_t retryCount = 0; { ULock l(queueLock); queueSize = workQueue.size(); } while ((retryCount < MAXIMUM_NUMBER_OF_RETRIES) && (queueSize < bufferCount)) { ULock l(queueLock); if (queueSize != workQueue.size()) { queueSize = workQueue.size(); retryCount = 0; } else { queueCondition.wait_for(l, QUEUE_TIMEOUT); ++retryCount; } } } // Populate Info vector and return number of CSDs int32_t populateInfoVector(std::string info, android::Vector *frameInfo) { std::ifstream eleInfo; eleInfo.open(info); if (!eleInfo.is_open()) { return -1; } int32_t numCsds = 0; int32_t bytesCount = 0; uint32_t flags = 0; uint32_t timestamp = 0; while (1) { if (!(eleInfo >> bytesCount)) { break; } eleInfo >> flags; eleInfo >> timestamp; bool codecConfig = flags ? ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0; if (codecConfig) { ++numCsds; } frameInfo->push_back({bytesCount, flags, timestamp}); } eleInfo.close(); return numCsds; } #define RETURN_FAILURE(condition) \ if ((condition)) { \ return EXIT_FAILURE; \ } int main(int argc, char **argv) { RETURN_FAILURE(argc != 3); Codec2VideoDecHidlTestBase handle; RETURN_FAILURE(!handle.SetUp()); RETURN_FAILURE(!handle.configPixelFormat(HAL_PIXEL_FORMAT_YCBCR_420_888)); std::string eleStreamInfo{argv[2]}; android::Vector Info; RETURN_FAILURE(populateInfoVector(eleStreamInfo, &Info) < 0); RETURN_FAILURE(handle.mComponent->start() != C2_OK); std::string eleStream{argv[1]}; std::ifstream ifStream; ifStream.open(eleStream, std::ifstream::binary); RETURN_FAILURE(!ifStream.is_open()); RETURN_FAILURE(!decodeNFrames(handle.mComponent, handle.mQueueLock, handle.mQueueCondition, handle.mWorkQueue, handle.mFlushedIndices, handle.mLinearPool, ifStream, &Info)); // blocking call to ensures application to Wait till all the inputs are // consumed if (!handle.mEos) { waitOnInputConsumption(handle.mQueueLock, handle.mQueueCondition, handle.mWorkQueue); } ifStream.close(); RETURN_FAILURE(handle.mFramesReceived != Info.size()); RETURN_FAILURE(handle.mComponent->stop() != C2_OK); RETURN_FAILURE(handle.mWorkResult != C2_OK); if (handle.mHasVulnerability) { return EXIT_VULNERABLE; } return EXIT_SUCCESS; }