1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <C2AllocatorIon.h>
18 #include <C2Buffer.h>
19 #include <C2BufferPriv.h>
20 #include <C2Config.h>
21 #include <C2Debug.h>
22 #include <codec2/hidl/client.h>
23 #include <gui/BufferQueue.h>
24 #include <gui/IConsumerListener.h>
25 #include <gui/IProducerListener.h>
26
27 #include <chrono>
28 #include <condition_variable>
29 #include <fstream>
30 #include <iostream>
31
32 #include "../includes/common.h"
33
34 using android::C2AllocatorIon;
35 using std::chrono_literals::operator""ms;
36
37 #define MAXIMUM_NUMBER_OF_RETRIES 20
38 #define QUEUE_TIMEOUT 400ms
39 #define MAXIMUM_NUMBER_OF_INPUT_BUFFERS 8
40 #define ALIGN(_sz, _align) ((_sz + (_align - 1)) & ~(_align - 1))
41
42 void workDone(const std::shared_ptr<android::Codec2Client::Component> &component,
43 std::unique_ptr<C2Work> &work, std::list<uint64_t> &flushedIndices,
44 std::mutex &queueLock, std::condition_variable &queueCondition,
45 std::list<std::unique_ptr<C2Work>> &workQueue, bool &eos, bool &csd,
46 uint32_t &framesReceived);
47
48 struct FrameInfo {
49 int bytesCount;
50 uint32_t flags;
51 int64_t timestamp;
52 };
53
54 class LinearBuffer : public C2Buffer {
55 public:
LinearBuffer(const std::shared_ptr<C2LinearBlock> & block)56 explicit LinearBuffer(const std::shared_ptr<C2LinearBlock> &block)
57 : C2Buffer({block->share(block->offset(), block->size(), ::C2Fence())}) {}
58
LinearBuffer(const std::shared_ptr<C2LinearBlock> & block,size_t size)59 explicit LinearBuffer(const std::shared_ptr<C2LinearBlock> &block, size_t size)
60 : C2Buffer({block->share(block->offset(), size, ::C2Fence())}) {}
61 };
62
63 /*
64 * Handle Callback functions onWorkDone(), onTripped(),
65 * onError(), onDeath(), onFramesRendered()
66 */
67 struct CodecListener : public android::Codec2Client::Listener {
68 public:
CodecListenerCodecListener69 CodecListener(
70 const std::function<void(std::list<std::unique_ptr<C2Work>> &workItems)> fn = nullptr)
71 : callBack(fn) {}
onWorkDoneCodecListener72 virtual void onWorkDone(const std::weak_ptr<android::Codec2Client::Component> &comp,
73 std::list<std::unique_ptr<C2Work>> &workItems) override {
74 (void)comp;
75 if (callBack) {
76 callBack(workItems);
77 }
78 }
79
onTrippedCodecListener80 virtual void onTripped(
81 const std::weak_ptr<android::Codec2Client::Component> &comp,
82 const std::vector<std::shared_ptr<C2SettingResult>> &settingResults) override {
83 (void)comp;
84 (void)settingResults;
85 }
86
onErrorCodecListener87 virtual void onError(const std::weak_ptr<android::Codec2Client::Component> &comp,
88 uint32_t errorCode) override {
89 (void)comp;
90 (void)errorCode;
91 }
92
onDeathCodecListener93 virtual void onDeath(const std::weak_ptr<android::Codec2Client::Component> &comp) override {
94 (void)comp;
95 }
96
onInputBufferDoneCodecListener97 virtual void onInputBufferDone(uint64_t frameIndex, size_t arrayIndex) override {
98 (void)frameIndex;
99 (void)arrayIndex;
100 }
101
onFrameRenderedCodecListener102 virtual void onFrameRendered(uint64_t bufferQueueId, int32_t slotId,
103 int64_t timestampNs) override {
104 (void)bufferQueueId;
105 (void)slotId;
106 (void)timestampNs;
107 }
108 std::function<void(std::list<std::unique_ptr<C2Work>> &workItems)> callBack;
109 };
110
111 class Codec2VideoDecHidlTestBase {
112 public:
SetUp()113 bool SetUp() {
114 mClient = getClient();
115 if (!mClient) {
116 return false;
117 }
118 mListener.reset(new CodecListener([this](std::list<std::unique_ptr<C2Work>> &workItems) {
119 handleWorkDone(workItems);
120 }));
121 if (!mListener) {
122 return false;
123 }
124 if (android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener,
125 &mComponent, &mClient) != C2_OK) {
126 return false;
127 }
128 for (int i = 0; i < MAXIMUM_NUMBER_OF_INPUT_BUFFERS; ++i) {
129 mWorkQueue.emplace_back(new C2Work);
130 }
131 std::shared_ptr<C2AllocatorStore> store = android::GetCodec2PlatformAllocatorStore();
132 if (store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mLinearAllocator) != C2_OK) {
133 return false;
134 }
135 mLinearPool = std::make_shared<C2PooledBlockPool>(mLinearAllocator, ++mBlockPoolId);
136 if (!mLinearPool) {
137 return false;
138 }
139 mEos = false;
140 mHasVulnerability = false;
141 mTimestampUs = 0u;
142 mWorkResult = C2_OK;
143 mFramesReceived = 0;
144 return true;
145 }
146
~Codec2VideoDecHidlTestBase()147 ~Codec2VideoDecHidlTestBase() {
148 if (mComponent != nullptr) {
149 mComponent->release();
150 mComponent = nullptr;
151 }
152 }
153
getClient()154 std::shared_ptr<android::Codec2Client> getClient() {
155 auto instances = android::Codec2Client::GetServiceNames();
156 for (std::string instance : instances) {
157 std::shared_ptr<android::Codec2Client> client =
158 android::Codec2Client::CreateFromService(instance.c_str());
159 std::vector<C2Component::Traits> components = client->listComponents();
160 for (C2Component::Traits traits : components) {
161 if (instance.compare(traits.owner)) {
162 continue;
163 }
164 if (traits.domain == DOMAIN_VIDEO && traits.kind == KIND_DECODER &&
165 mComponentName.compare(traits.name)) {
166 return android::Codec2Client::
167 CreateFromService(instance.c_str(),
168 !bool(android::Codec2Client::
169 CreateFromService("default", true)));
170 }
171 }
172 }
173 return nullptr;
174 }
175
checkBufferOK(std::unique_ptr<C2Work> & work)176 void checkBufferOK(std::unique_ptr<C2Work> &work) {
177 const C2GraphicView output = work->worklets.front()
178 ->output.buffers[0]
179 ->data()
180 .graphicBlocks()
181 .front()
182 .map()
183 .get();
184 uint8_t *uPlane = const_cast<uint8_t *>(output.data()[C2PlanarLayout::PLANE_U]);
185 uint8_t *vPlane = const_cast<uint8_t *>(output.data()[C2PlanarLayout::PLANE_V]);
186 const uint8_t ul[] = {109, 109, 109, 109, 109, 109, 109};
187 const uint8_t vl[] = {121, 121, 121, 121, 121, 121, 121};
188 const uint8_t ur[] = {114, 114, 120, 120, 122, 127, 127};
189 const uint8_t vr[] = {126, 121, 123, 121, 123, 126, 126};
190 if (!memcmp(uPlane - 7, ul, 7) && !memcmp(vPlane - 7, vl, 7) &&
191 !memcmp(uPlane + 1, ur, 7) && !memcmp(vPlane + 1, vr, 7)) {
192 mHasVulnerability |= true;
193 }
194 }
195
196 // Config output pixel format
configPixelFormat(uint32_t format)197 bool configPixelFormat(uint32_t format) {
198 std::vector<std::unique_ptr<C2SettingResult>> failures;
199 C2StreamPixelFormatInfo::output pixelformat(0u, format);
200
201 std::vector<C2Param *> configParam{&pixelformat};
202 c2_status_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures);
203 if (status == C2_OK && failures.size() == 0u) {
204 return true;
205 }
206 return false;
207 }
208
209 // callback function to process onWorkDone received by Listener
handleWorkDone(std::list<std::unique_ptr<C2Work>> & workItems)210 void handleWorkDone(std::list<std::unique_ptr<C2Work>> &workItems) {
211 for (std::unique_ptr<C2Work> &work : workItems) {
212 if (!work->worklets.empty()) {
213 // For decoder components current timestamp always exceeds
214 // previous timestamp if output is in display order
215 mWorkResult |= work->result;
216 bool codecConfig = ((work->worklets.front()->output.flags &
217 C2FrameData::FLAG_CODEC_CONFIG) != 0);
218 if (!codecConfig && !work->worklets.front()->output.buffers.empty()) {
219 checkBufferOK(work);
220 }
221 bool mCsd = false;
222 workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, mWorkQueue,
223 mEos, mCsd, mFramesReceived);
224 (void)mCsd;
225 }
226 }
227 }
228
229 const std::string mComponentName = "c2.android.hevc.decoder";
230 bool mEos;
231 bool mHasVulnerability;
232 uint64_t mTimestampUs;
233 int32_t mWorkResult;
234 uint32_t mFramesReceived;
235 std::list<uint64_t> mFlushedIndices;
236
237 C2BlockPool::local_id_t mBlockPoolId;
238 std::shared_ptr<C2BlockPool> mLinearPool;
239 std::shared_ptr<C2Allocator> mLinearAllocator;
240
241 std::mutex mQueueLock;
242 std::condition_variable mQueueCondition;
243 std::list<std::unique_ptr<C2Work>> mWorkQueue;
244
245 std::shared_ptr<android::Codec2Client> mClient;
246 std::shared_ptr<android::Codec2Client::Listener> mListener;
247 std::shared_ptr<android::Codec2Client::Component> mComponent;
248 };
249
250 // process onWorkDone received by Listener
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)251 void workDone(const std::shared_ptr<android::Codec2Client::Component> &component,
252 std::unique_ptr<C2Work> &work, std::list<uint64_t> &flushedIndices,
253 std::mutex &queueLock, std::condition_variable &queueCondition,
254 std::list<std::unique_ptr<C2Work>> &workQueue, bool &eos, bool &csd,
255 uint32_t &framesReceived) {
256 // handle configuration changes in work done
257 if (work->worklets.front()->output.configUpdate.size() != 0) {
258 std::vector<std::unique_ptr<C2Param>> updates =
259 std::move(work->worklets.front()->output.configUpdate);
260 std::vector<C2Param *> configParam;
261 std::vector<std::unique_ptr<C2SettingResult>> failures;
262 for (size_t i = 0; i < updates.size(); ++i) {
263 C2Param *param = updates[i].get();
264 if (param->index() == C2StreamInitDataInfo::output::PARAM_TYPE) {
265 C2StreamInitDataInfo::output *csdBuffer = (C2StreamInitDataInfo::output *)(param);
266 size_t csdSize = csdBuffer->flexCount();
267 if (csdSize > 0) {
268 csd = true;
269 }
270 } else if ((param->index() == C2StreamSampleRateInfo::output::PARAM_TYPE) ||
271 (param->index() == C2StreamChannelCountInfo::output::PARAM_TYPE) ||
272 (param->index() == C2StreamPictureSizeInfo::output::PARAM_TYPE)) {
273 configParam.push_back(param);
274 }
275 }
276 component->config(configParam, C2_DONT_BLOCK, &failures);
277 assert(failures.size() == 0u);
278 }
279 if (work->worklets.front()->output.flags != C2FrameData::FLAG_INCOMPLETE) {
280 ++framesReceived;
281 eos = (work->worklets.front()->output.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
282 auto frameIndexIt = std::find(flushedIndices.begin(), flushedIndices.end(),
283 work->input.ordinal.frameIndex.peeku());
284 work->input.buffers.clear();
285 work->worklets.clear();
286 {
287 typedef std::unique_lock<std::mutex> ULock;
288 ULock l(queueLock);
289 workQueue.push_back(std::move(work));
290 if (!flushedIndices.empty() && (frameIndexIt != flushedIndices.end())) {
291 flushedIndices.erase(frameIndexIt);
292 }
293 queueCondition.notify_all();
294 }
295 }
296 }
297
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)298 bool decodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &component,
299 std::mutex &queueLock, std::condition_variable &queueCondition,
300 std::list<std::unique_ptr<C2Work>> &workQueue,
301 std::list<uint64_t> &flushedIndices, std::shared_ptr<C2BlockPool> &linearPool,
302 std::ifstream &ifStream, android::Vector<FrameInfo> *Info) {
303 typedef std::unique_lock<std::mutex> ULock;
304 int frameID = 0;
305 int retryCount = 0;
306 while (1) {
307 if (frameID == (int)Info->size()) {
308 break;
309 }
310 uint32_t flags = 0;
311 std::unique_ptr<C2Work> work;
312 // Prepare C2Work
313 while (!work && (retryCount < MAXIMUM_NUMBER_OF_RETRIES)) {
314 ULock l(queueLock);
315 if (!workQueue.empty()) {
316 work.swap(workQueue.front());
317 workQueue.pop_front();
318 } else {
319 queueCondition.wait_for(l, QUEUE_TIMEOUT);
320 ++retryCount;
321 }
322 }
323 if (!work && (retryCount >= MAXIMUM_NUMBER_OF_RETRIES)) {
324 return false; // "Wait for generating C2Work exceeded timeout"
325 }
326 int64_t timestamp = (*Info)[frameID].timestamp;
327 if ((*Info)[frameID].flags) {
328 flags = (1 << ((*Info)[frameID].flags - 1));
329 }
330 if (frameID == (int)Info->size() - 1) {
331 flags |= C2FrameData::FLAG_END_OF_STREAM;
332 }
333
334 work->input.flags = (C2FrameData::flags_t)flags;
335 work->input.ordinal.timestamp = timestamp;
336 work->input.ordinal.frameIndex = frameID;
337 {
338 ULock l(queueLock);
339 flushedIndices.emplace_back(frameID);
340 }
341
342 int size = (*Info)[frameID].bytesCount;
343 char *data = (char *)malloc(size);
344 if (!data) {
345 return false;
346 }
347
348 ifStream.read(data, size);
349 if (ifStream.gcount() != size) {
350 return false;
351 }
352
353 work->input.buffers.clear();
354 auto alignedSize = ALIGN(size, PAGE_SIZE);
355 if (size) {
356 std::shared_ptr<C2LinearBlock> block;
357 if (linearPool->fetchLinearBlock(alignedSize,
358 {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE},
359 &block) != C2_OK) {
360 return false;
361 }
362 if (!block) {
363 return false;
364 }
365
366 // Write View
367 C2WriteView view = block->map().get();
368 if (view.error() != C2_OK) {
369 return false;
370 }
371 if ((size_t)alignedSize != view.capacity()) {
372 return false;
373 }
374 if (0u != view.offset()) {
375 return false;
376 }
377 if ((size_t)alignedSize != view.size()) {
378 return false;
379 }
380
381 memcpy(view.base(), data, size);
382
383 work->input.buffers.emplace_back(new LinearBuffer(block, size));
384 free(data);
385 }
386 work->worklets.clear();
387 work->worklets.emplace_back(new C2Worklet);
388 std::list<std::unique_ptr<C2Work>> items;
389 items.push_back(std::move(work));
390
391 // DO THE DECODING
392 if (component->queue(&items) != C2_OK) {
393 return false;
394 }
395 ++frameID;
396 retryCount = 0;
397 }
398 return true;
399 }
400
401 // Wait for all the inputs to be consumed by the plugin.
waitOnInputConsumption(std::mutex & queueLock,std::condition_variable & queueCondition,std::list<std::unique_ptr<C2Work>> & workQueue,size_t bufferCount=MAXIMUM_NUMBER_OF_INPUT_BUFFERS)402 void waitOnInputConsumption(std::mutex &queueLock, std::condition_variable &queueCondition,
403 std::list<std::unique_ptr<C2Work>> &workQueue,
404 size_t bufferCount = MAXIMUM_NUMBER_OF_INPUT_BUFFERS) {
405 typedef std::unique_lock<std::mutex> ULock;
406 uint32_t queueSize;
407 uint32_t retryCount = 0;
408 {
409 ULock l(queueLock);
410 queueSize = workQueue.size();
411 }
412 while ((retryCount < MAXIMUM_NUMBER_OF_RETRIES) && (queueSize < bufferCount)) {
413 ULock l(queueLock);
414 if (queueSize != workQueue.size()) {
415 queueSize = workQueue.size();
416 retryCount = 0;
417 } else {
418 queueCondition.wait_for(l, QUEUE_TIMEOUT);
419 ++retryCount;
420 }
421 }
422 }
423
424 // Populate Info vector and return number of CSDs
populateInfoVector(std::string info,android::Vector<FrameInfo> * frameInfo)425 int32_t populateInfoVector(std::string info, android::Vector<FrameInfo> *frameInfo) {
426 std::ifstream eleInfo;
427 eleInfo.open(info);
428 if (!eleInfo.is_open()) {
429 return -1;
430 }
431 int32_t numCsds = 0;
432 int32_t bytesCount = 0;
433 uint32_t flags = 0;
434 uint32_t timestamp = 0;
435 while (1) {
436 if (!(eleInfo >> bytesCount)) {
437 break;
438 }
439 eleInfo >> flags;
440 eleInfo >> timestamp;
441 bool codecConfig = flags ? ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0;
442 if (codecConfig) {
443 ++numCsds;
444 }
445 frameInfo->push_back({bytesCount, flags, timestamp});
446 }
447 eleInfo.close();
448 return numCsds;
449 }
450
451 #define RETURN_FAILURE(condition) \
452 if ((condition)) { \
453 return EXIT_FAILURE; \
454 }
455
main(int argc,char ** argv)456 int main(int argc, char **argv) {
457 RETURN_FAILURE(argc != 3);
458
459 Codec2VideoDecHidlTestBase handle;
460 RETURN_FAILURE(!handle.SetUp());
461 RETURN_FAILURE(!handle.configPixelFormat(HAL_PIXEL_FORMAT_YCBCR_420_888));
462
463 std::string eleStreamInfo{argv[2]};
464 android::Vector<FrameInfo> Info;
465 RETURN_FAILURE(populateInfoVector(eleStreamInfo, &Info) < 0);
466 RETURN_FAILURE(handle.mComponent->start() != C2_OK);
467
468 std::string eleStream{argv[1]};
469 std::ifstream ifStream;
470 ifStream.open(eleStream, std::ifstream::binary);
471 RETURN_FAILURE(!ifStream.is_open());
472 RETURN_FAILURE(!decodeNFrames(handle.mComponent, handle.mQueueLock, handle.mQueueCondition,
473 handle.mWorkQueue, handle.mFlushedIndices, handle.mLinearPool,
474 ifStream, &Info));
475 // blocking call to ensures application to Wait till all the inputs are
476 // consumed
477 if (!handle.mEos) {
478 waitOnInputConsumption(handle.mQueueLock, handle.mQueueCondition, handle.mWorkQueue);
479 }
480 ifStream.close();
481 RETURN_FAILURE(handle.mFramesReceived != Info.size());
482 RETURN_FAILURE(handle.mComponent->stop() != C2_OK);
483 RETURN_FAILURE(handle.mWorkResult != C2_OK);
484 if (handle.mHasVulnerability) {
485 return EXIT_VULNERABLE;
486 }
487 return EXIT_SUCCESS;
488 }
489