| /* |
| * 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 <C2AllocatorIon.h> |
| #include <C2Buffer.h> |
| #include <C2BufferPriv.h> |
| #include <C2Config.h> |
| #include <C2Debug.h> |
| #include <codec2/hidl/client.h> |
| #include <gui/BufferQueue.h> |
| #include <gui/IConsumerListener.h> |
| #include <gui/IProducerListener.h> |
| |
| #include <chrono> |
| #include <condition_variable> |
| #include <fstream> |
| #include <iostream> |
| |
| #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<android::Codec2Client::Component>& component, |
| std::unique_ptr<C2Work>& work, std::list<uint64_t>& flushedIndices, |
| std::mutex& queueLock, std::condition_variable& queueCondition, |
| std::list<std::unique_ptr<C2Work>>& 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<C2LinearBlock>& block) |
| : C2Buffer({block->share(block->offset(), block->size(), ::C2Fence())}) {} |
| |
| explicit LinearBuffer(const std::shared_ptr<C2LinearBlock>& 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<void(std::list<std::unique_ptr<C2Work>>& workItems)> fn = nullptr) |
| : callBack(fn) {} |
| virtual void onWorkDone(const std::weak_ptr<android::Codec2Client::Component>& comp, |
| std::list<std::unique_ptr<C2Work>>& workItems) override { |
| (void)comp; |
| if (callBack) { |
| callBack(workItems); |
| } |
| } |
| |
| virtual void onTripped( |
| const std::weak_ptr<android::Codec2Client::Component>& comp, |
| const std::vector<std::shared_ptr<C2SettingResult>>& settingResults) override { |
| (void)comp; |
| (void)settingResults; |
| } |
| |
| virtual void onError(const std::weak_ptr<android::Codec2Client::Component>& comp, |
| uint32_t errorCode) override { |
| (void)comp; |
| (void)errorCode; |
| } |
| |
| virtual void onDeath(const std::weak_ptr<android::Codec2Client::Component>& 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<void(std::list<std::unique_ptr<C2Work>>& workItems)> callBack; |
| }; |
| |
| class Codec2VideoDecHidlTestBase { |
| public: |
| bool SetUp() { |
| mClient = getClient(); |
| if (!mClient) { |
| return false; |
| } |
| mListener.reset(new CodecListener( |
| [this](std::list<std::unique_ptr<C2Work>>& workItems) { handleWorkDone(workItems); })); |
| if (!mListener) { |
| return false; |
| } |
| mComponent = android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener, |
| &mClient); |
| if (!mComponent) { |
| return false; |
| } |
| for (int i = 0; i < MAXIMUM_NUMBER_OF_INPUT_BUFFERS; ++i) { |
| mWorkQueue.emplace_back(new C2Work); |
| } |
| std::shared_ptr<C2AllocatorStore> store = android::GetCodec2PlatformAllocatorStore(); |
| if (store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mLinearAllocator) != C2_OK) { |
| return false; |
| } |
| mLinearPool = std::make_shared<C2PooledBlockPool>(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<android::Codec2Client> getClient() { |
| auto instances = android::Codec2Client::GetServiceNames(); |
| for (std::string instance : instances) { |
| std::shared_ptr<android::Codec2Client> client = |
| android::Codec2Client::CreateFromService(instance.c_str()); |
| std::vector<C2Component::Traits> 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<C2Work>& work) { |
| const C2GraphicView output = |
| work->worklets.front()->output.buffers[0]->data().graphicBlocks().front().map().get(); |
| uint8_t* uPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_U]); |
| uint8_t* vPlane = const_cast<uint8_t*>(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<std::unique_ptr<C2SettingResult>> failures; |
| C2StreamPixelFormatInfo::output pixelformat(0u, format); |
| |
| std::vector<C2Param*> 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<std::unique_ptr<C2Work>>& workItems) { |
| for (std::unique_ptr<C2Work>& 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<uint64_t> mFlushedIndices; |
| |
| C2BlockPool::local_id_t mBlockPoolId; |
| std::shared_ptr<C2BlockPool> mLinearPool; |
| std::shared_ptr<C2Allocator> mLinearAllocator; |
| |
| std::mutex mQueueLock; |
| std::condition_variable mQueueCondition; |
| std::list<std::unique_ptr<C2Work>> mWorkQueue; |
| |
| std::shared_ptr<android::Codec2Client> mClient; |
| std::shared_ptr<android::Codec2Client::Listener> mListener; |
| std::shared_ptr<android::Codec2Client::Component> mComponent; |
| }; |
| |
| // process onWorkDone received by Listener |
| void workDone(const std::shared_ptr<android::Codec2Client::Component>& component, |
| std::unique_ptr<C2Work>& work, std::list<uint64_t>& flushedIndices, |
| std::mutex& queueLock, std::condition_variable& queueCondition, |
| std::list<std::unique_ptr<C2Work>>& workQueue, bool& eos, bool& csd, |
| uint32_t& framesReceived) { |
| // handle configuration changes in work done |
| if (work->worklets.front()->output.configUpdate.size() != 0) { |
| std::vector<std::unique_ptr<C2Param>> updates = |
| std::move(work->worklets.front()->output.configUpdate); |
| std::vector<C2Param*> configParam; |
| std::vector<std::unique_ptr<C2SettingResult>> 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<std::mutex> 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<android::Codec2Client::Component>& component, |
| std::mutex& queueLock, std::condition_variable& queueCondition, |
| std::list<std::unique_ptr<C2Work>>& workQueue, |
| std::list<uint64_t>& flushedIndices, std::shared_ptr<C2BlockPool>& linearPool, |
| std::ifstream& ifStream, android::Vector<FrameInfo>* Info) { |
| typedef std::unique_lock<std::mutex> ULock; |
| int frameID = 0; |
| int retryCount = 0; |
| while (1) { |
| if (frameID == (int)Info->size()) { |
| break; |
| } |
| uint32_t flags = 0; |
| std::unique_ptr<C2Work> 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<C2LinearBlock> 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<std::unique_ptr<C2Work>> 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<std::unique_ptr<C2Work>>& workQueue, |
| size_t bufferCount = MAXIMUM_NUMBER_OF_INPUT_BUFFERS) { |
| typedef std::unique_lock<std::mutex> 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>* 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<FrameInfo> 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; |
| } |