1 /*
2 * Copyright (C) 2018 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 // #define LOG_NDEBUG 0
18 #define LOG_TAG "codec2_hidl_hal_audio_enc_test"
19
20 #include <android-base/logging.h>
21 #include <gtest/gtest.h>
22 #include <stdio.h>
23 #include <fstream>
24 #include <algorithm>
25
26 #include <codec2/hidl/client.h>
27 #include <C2AllocatorIon.h>
28 #include <C2Config.h>
29 #include <C2Debug.h>
30 #include <C2Buffer.h>
31 #include <C2BufferPriv.h>
32
33 using android::C2AllocatorIon;
34
35 #include <VtsHalHidlTargetTestBase.h>
36 #include "media_c2_audio_hidl_test_common.h"
37 #include "media_c2_hidl_test_common.h"
38
39 class LinearBuffer : public C2Buffer {
40 public:
LinearBuffer(const std::shared_ptr<C2LinearBlock> & block)41 explicit LinearBuffer(const std::shared_ptr<C2LinearBlock>& block)
42 : C2Buffer(
43 {block->share(block->offset(), block->size(), ::C2Fence())}) {}
44 };
45
46 static ComponentTestEnvironment* gEnv = nullptr;
47
48 namespace {
49
50 class Codec2AudioEncHidlTest : public ::testing::VtsHalHidlTargetTestBase {
51 private:
52 typedef ::testing::VtsHalHidlTargetTestBase Super;
53
54 public:
getTestCaseInfo() const55 ::std::string getTestCaseInfo() const override {
56 return ::std::string() +
57 "Component: " + gEnv->getComponent().c_str() + " | " +
58 "Instance: " + gEnv->getInstance().c_str() + " | " +
59 "Res: " + gEnv->getRes().c_str();
60 }
61
62 // google.codec2 Audio test setup
SetUp()63 virtual void SetUp() override {
64 Super::SetUp();
65 mDisableTest = false;
66 ALOGV("Codec2AudioEncHidlTest SetUp");
67 mClient = android::Codec2Client::CreateFromService(
68 gEnv->getInstance().c_str());
69 ASSERT_NE(mClient, nullptr);
70 mListener.reset(new CodecListener(
71 [this](std::list<std::unique_ptr<C2Work>>& workItems) {
72 handleWorkDone(workItems);
73 }));
74 ASSERT_NE(mListener, nullptr);
75 for (int i = 0; i < MAX_INPUT_BUFFERS; ++i) {
76 mWorkQueue.emplace_back(new C2Work);
77 }
78 mClient->createComponent(gEnv->getComponent().c_str(), mListener,
79 &mComponent);
80 ASSERT_NE(mComponent, nullptr);
81
82 std::shared_ptr<C2AllocatorStore> store =
83 android::GetCodec2PlatformAllocatorStore();
84 CHECK_EQ(store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR,
85 &mLinearAllocator),
86 C2_OK);
87 mLinearPool = std::make_shared<C2PooledBlockPool>(mLinearAllocator,
88 mBlockPoolId++);
89 ASSERT_NE(mLinearPool, nullptr);
90
91 mCompName = unknown_comp;
92 struct StringToName {
93 const char* Name;
94 standardComp CompName;
95 };
96 const StringToName kStringToName[] = {
97 {"aac", aac},
98 {"flac", flac},
99 {"opus", opus},
100 {"amrnb", amrnb},
101 {"amrwb", amrwb},
102 };
103 const size_t kNumStringToName =
104 sizeof(kStringToName) / sizeof(kStringToName[0]);
105
106 std::string substring;
107 std::string comp;
108 substring = std::string(gEnv->getComponent());
109 /* TODO: better approach to find the component */
110 /* "c2.android." => 11th position */
111 size_t pos = 11;
112 size_t len = substring.find(".encoder", pos);
113 comp = substring.substr(pos, len - pos);
114
115 for (size_t i = 0; i < kNumStringToName; ++i) {
116 if (!strcasecmp(comp.c_str(), kStringToName[i].Name)) {
117 mCompName = kStringToName[i].CompName;
118 break;
119 }
120 }
121 mEos = false;
122 mCsd = false;
123 mFramesReceived = 0;
124 if (mCompName == unknown_comp) mDisableTest = true;
125 if (mDisableTest) std::cout << "[ WARN ] Test Disabled \n";
126 }
127
TearDown()128 virtual void TearDown() override {
129 if (mComponent != nullptr) {
130 if (::testing::Test::HasFatalFailure()) return;
131 mComponent->release();
132 mComponent = nullptr;
133 }
134 Super::TearDown();
135 }
136 // callback function to process onWorkDone received by Listener
handleWorkDone(std::list<std::unique_ptr<C2Work>> & workItems)137 void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) {
138 for (std::unique_ptr<C2Work>& work : workItems) {
139 if (!work->worklets.empty()) {
140 workDone(mComponent, work, mFlushedIndices, mQueueLock,
141 mQueueCondition, mWorkQueue, mEos, mCsd,
142 mFramesReceived);
143 }
144 }
145 }
146 enum standardComp {
147 aac,
148 flac,
149 opus,
150 amrnb,
151 amrwb,
152 unknown_comp,
153 };
154
155 bool mEos;
156 bool mCsd;
157 bool mDisableTest;
158 standardComp mCompName;
159 uint32_t mFramesReceived;
160 std::list<uint64_t> mFlushedIndices;
161
162 C2BlockPool::local_id_t mBlockPoolId;
163 std::shared_ptr<C2BlockPool> mLinearPool;
164 std::shared_ptr<C2Allocator> mLinearAllocator;
165
166 std::mutex mQueueLock;
167 std::condition_variable mQueueCondition;
168 std::list<std::unique_ptr<C2Work>> mWorkQueue;
169
170 std::shared_ptr<android::Codec2Client> mClient;
171 std::shared_ptr<android::Codec2Client::Listener> mListener;
172 std::shared_ptr<android::Codec2Client::Component> mComponent;
173
174 protected:
description(const std::string & description)175 static void description(const std::string& description) {
176 RecordProperty("description", description);
177 }
178 };
179
validateComponent(const std::shared_ptr<android::Codec2Client::Component> & component,Codec2AudioEncHidlTest::standardComp compName,bool & disableTest)180 void validateComponent(
181 const std::shared_ptr<android::Codec2Client::Component>& component,
182 Codec2AudioEncHidlTest::standardComp compName, bool& disableTest) {
183 // Validate its a C2 Component
184 if (component->getName().find("c2") == std::string::npos) {
185 ALOGE("Not a c2 component");
186 disableTest = true;
187 return;
188 }
189
190 // Validate its not an encoder and the component to be tested is audio
191 if (component->getName().find("decoder") != std::string::npos) {
192 ALOGE("Expected Encoder, given Decoder");
193 disableTest = true;
194 return;
195 }
196 std::vector<std::unique_ptr<C2Param>> queried;
197 c2_status_t c2err =
198 component->query({}, {C2PortMediaTypeSetting::input::PARAM_TYPE},
199 C2_DONT_BLOCK, &queried);
200 if (c2err != C2_OK && queried.size() == 0) {
201 ALOGE("Query media type failed => %d", c2err);
202 } else {
203 std::string inputDomain =
204 ((C2StreamMediaTypeSetting::input*)queried[0].get())->m.value;
205 if (inputDomain.find("audio/") == std::string::npos) {
206 ALOGE("Expected Audio Component");
207 disableTest = true;
208 return;
209 }
210 }
211
212 // Validates component name
213 if (compName == Codec2AudioEncHidlTest::unknown_comp) {
214 ALOGE("Component InValid");
215 disableTest = true;
216 return;
217 }
218 ALOGV("Component Valid");
219 }
220
221 // Set Default config param.
setupConfigParam(const std::shared_ptr<android::Codec2Client::Component> & component,int32_t nChannels,int32_t nSampleRate)222 void setupConfigParam(
223 const std::shared_ptr<android::Codec2Client::Component>& component,
224 int32_t nChannels, int32_t nSampleRate) {
225 std::vector<std::unique_ptr<C2SettingResult>> failures;
226 C2StreamSampleRateInfo::input sampleRateInfo(0u, nSampleRate);
227 C2StreamChannelCountInfo::input channelCountInfo(0u, nChannels);
228
229 std::vector<C2Param*> configParam{&sampleRateInfo, &channelCountInfo};
230 c2_status_t status =
231 component->config(configParam, C2_DONT_BLOCK, &failures);
232 ASSERT_EQ(failures.size(), 0u);
233 ASSERT_EQ(status, C2_OK);
234 }
235
236 // LookUpTable of clips and metadata for component testing
GetURLForComponent(Codec2AudioEncHidlTest::standardComp comp,char * mURL)237 void GetURLForComponent(Codec2AudioEncHidlTest::standardComp comp, char* mURL) {
238 struct CompToURL {
239 Codec2AudioEncHidlTest::standardComp comp;
240 const char* mURL;
241 };
242 static const CompToURL kCompToURL[] = {
243 {Codec2AudioEncHidlTest::standardComp::aac,
244 "bbb_raw_2ch_48khz_s16le.raw"},
245 {Codec2AudioEncHidlTest::standardComp::amrnb,
246 "bbb_raw_1ch_8khz_s16le.raw"},
247 {Codec2AudioEncHidlTest::standardComp::amrwb,
248 "bbb_raw_1ch_16khz_s16le.raw"},
249 {Codec2AudioEncHidlTest::standardComp::flac,
250 "bbb_raw_2ch_48khz_s16le.raw"},
251 {Codec2AudioEncHidlTest::standardComp::opus,
252 "bbb_raw_2ch_48khz_s16le.raw"},
253 };
254
255 for (size_t i = 0; i < sizeof(kCompToURL) / sizeof(kCompToURL[0]); ++i) {
256 if (kCompToURL[i].comp == comp) {
257 strcat(mURL, kCompToURL[i].mURL);
258 return;
259 }
260 }
261 }
262
encodeNFrames(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 & eleStream,uint32_t nFrames,int32_t samplesPerFrame,int32_t nChannels,int32_t nSampleRate,bool flushed=false,bool signalEOS=true)263 void encodeNFrames(const std::shared_ptr<android::Codec2Client::Component>& component,
264 std::mutex &queueLock, std::condition_variable& queueCondition,
265 std::list<std::unique_ptr<C2Work>>& workQueue,
266 std::list<uint64_t>& flushedIndices,
267 std::shared_ptr<C2BlockPool>& linearPool,
268 std::ifstream& eleStream, uint32_t nFrames,
269 int32_t samplesPerFrame, int32_t nChannels,
270 int32_t nSampleRate, bool flushed = false,
271 bool signalEOS = true) {
272 typedef std::unique_lock<std::mutex> ULock;
273
274 uint32_t frameID = 0;
275 uint32_t maxRetry = 0;
276 int bytesCount = samplesPerFrame * nChannels * 2;
277 int32_t timestampIncr =
278 (int)(((float)samplesPerFrame / nSampleRate) * 1000000);
279 uint64_t timestamp = 0;
280 while (1) {
281 if (nFrames == 0) break;
282 uint32_t flags = 0;
283 std::unique_ptr<C2Work> work;
284 // Prepare C2Work
285 while (!work && (maxRetry < MAX_RETRY)) {
286 ULock l(queueLock);
287 if (!workQueue.empty()) {
288 work.swap(workQueue.front());
289 workQueue.pop_front();
290 } else {
291 queueCondition.wait_for(l, TIME_OUT);
292 maxRetry++;
293 }
294 }
295 if (!work && (maxRetry >= MAX_RETRY)) {
296 ASSERT_TRUE(false) << "Wait for generating C2Work exceeded timeout";
297 }
298 if (signalEOS && (nFrames == 1))
299 flags |= C2FrameData::FLAG_END_OF_STREAM;
300 if (flushed) {
301 flags |= SYNC_FRAME;
302 flushed = false;
303 }
304 work->input.flags = (C2FrameData::flags_t)flags;
305 work->input.ordinal.timestamp = timestamp;
306 work->input.ordinal.frameIndex = frameID;
307 {
308 ULock l(queueLock);
309 flushedIndices.emplace_back(frameID);
310 }
311 char* data = (char*)malloc(bytesCount);
312 ASSERT_NE(data, nullptr);
313 eleStream.read(data, bytesCount);
314 ASSERT_EQ(eleStream.gcount(), bytesCount);
315 std::shared_ptr<C2LinearBlock> block;
316 ASSERT_EQ(C2_OK, linearPool->fetchLinearBlock(
317 bytesCount, {C2MemoryUsage::CPU_READ,
318 C2MemoryUsage::CPU_WRITE},
319 &block));
320 ASSERT_TRUE(block);
321 // Write View
322 C2WriteView view = block->map().get();
323 if (view.error() != C2_OK) {
324 fprintf(stderr, "C2LinearBlock::map() failed : %d", view.error());
325 break;
326 }
327 ASSERT_EQ((size_t)bytesCount, view.capacity());
328 ASSERT_EQ(0u, view.offset());
329 ASSERT_EQ((size_t)bytesCount, view.size());
330
331 memcpy(view.base(), data, bytesCount);
332 work->input.buffers.clear();
333 work->input.buffers.emplace_back(new LinearBuffer(block));
334 work->worklets.clear();
335 work->worklets.emplace_back(new C2Worklet);
336 free(data);
337
338 std::list<std::unique_ptr<C2Work>> items;
339 items.push_back(std::move(work));
340
341 // DO THE DECODING
342 ASSERT_EQ(component->queue(&items), C2_OK);
343 ALOGV("Frame #%d size = %d queued", frameID, bytesCount);
344 nFrames--;
345 timestamp += timestampIncr;
346 frameID++;
347 maxRetry = 0;
348 }
349 }
350
TEST_F(Codec2AudioEncHidlTest,validateCompName)351 TEST_F(Codec2AudioEncHidlTest, validateCompName) {
352 if (mDisableTest) return;
353 ALOGV("Checks if the given component is a valid audio component");
354 validateComponent(mComponent, mCompName, mDisableTest);
355 ASSERT_EQ(mDisableTest, false);
356 }
357
TEST_F(Codec2AudioEncHidlTest,EncodeTest)358 TEST_F(Codec2AudioEncHidlTest, EncodeTest) {
359 ALOGV("EncodeTest");
360 if (mDisableTest) return;
361 ASSERT_EQ(mComponent->start(), C2_OK);
362 char mURL[512];
363 strcpy(mURL, gEnv->getRes().c_str());
364 GetURLForComponent(mCompName, mURL);
365
366 // Setting default configuration
367 int32_t nChannels = 2;
368 int32_t nSampleRate = 44100;
369 int32_t samplesPerFrame = 1024;
370 switch (mCompName) {
371 case aac:
372 nChannels = 2;
373 nSampleRate = 48000;
374 samplesPerFrame = 1024;
375 break;
376 case flac:
377 nChannels = 2;
378 nSampleRate = 48000;
379 samplesPerFrame = 1152;
380 break;
381 case opus:
382 nChannels = 2;
383 nSampleRate = 48000;
384 samplesPerFrame = 960;
385 break;
386 case amrnb:
387 nChannels = 1;
388 nSampleRate = 8000;
389 samplesPerFrame = 160;
390 break;
391 case amrwb:
392 nChannels = 1;
393 nSampleRate = 16000;
394 samplesPerFrame = 160;
395 break;
396 default:
397 ASSERT_TRUE(false);
398 }
399 setupConfigParam(mComponent, nChannels, nSampleRate);
400 std::ifstream eleStream;
401 uint32_t numFrames = 128;
402 eleStream.open(mURL, std::ifstream::binary);
403 ASSERT_EQ(eleStream.is_open(), true);
404 ALOGV("mURL : %s", mURL);
405 ASSERT_NO_FATAL_FAILURE(
406 encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
407 mFlushedIndices, mLinearPool, eleStream, numFrames,
408 samplesPerFrame, nChannels, nSampleRate));
409 // blocking call to ensures application to Wait till all the inputs are
410 // consumed
411 ASSERT_NO_FATAL_FAILURE(
412 waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
413 eleStream.close();
414 if (mFramesReceived != numFrames) {
415 ALOGE("Input buffer count and Output buffer count mismatch");
416 ALOGE("framesReceived : %d inputFrames : %u", mFramesReceived, numFrames);
417 ASSERT_TRUE(false);
418 }
419 if ((mCompName == flac || mCompName == opus || mCompName == aac)) {
420 if (!mCsd) {
421 ALOGE("CSD buffer missing");
422 ASSERT_TRUE(false);
423 }
424 }
425 ASSERT_EQ(mEos, true);
426 ASSERT_EQ(mComponent->stop(), C2_OK);
427 }
428
TEST_F(Codec2AudioEncHidlTest,EOSTest)429 TEST_F(Codec2AudioEncHidlTest, EOSTest) {
430 description("Test empty input buffer with EOS flag");
431 if (mDisableTest) return;
432 ASSERT_EQ(mComponent->start(), C2_OK);
433
434 typedef std::unique_lock<std::mutex> ULock;
435 std::unique_ptr<C2Work> work;
436 {
437 ULock l(mQueueLock);
438 if (!mWorkQueue.empty()) {
439 work.swap(mWorkQueue.front());
440 mWorkQueue.pop_front();
441 } else {
442 ALOGE("mWorkQueue Empty is not expected at the start of the test");
443 ASSERT_TRUE(false);
444 }
445 }
446 ASSERT_NE(work, nullptr);
447 work->input.flags = (C2FrameData::flags_t)C2FrameData::FLAG_END_OF_STREAM;
448 work->input.ordinal.timestamp = 0;
449 work->input.ordinal.frameIndex = 0;
450 work->input.buffers.clear();
451 work->worklets.clear();
452 work->worklets.emplace_back(new C2Worklet);
453
454 std::list<std::unique_ptr<C2Work>> items;
455 items.push_back(std::move(work));
456 ASSERT_EQ(mComponent->queue(&items), C2_OK);
457 uint32_t queueSize;
458 {
459 ULock l(mQueueLock);
460 queueSize = mWorkQueue.size();
461 if (queueSize < MAX_INPUT_BUFFERS) {
462 mQueueCondition.wait_for(l, TIME_OUT);
463 }
464 }
465 ASSERT_EQ(mEos, true);
466 ASSERT_EQ(mComponent->stop(), C2_OK);
467 }
468
TEST_F(Codec2AudioEncHidlTest,FlushTest)469 TEST_F(Codec2AudioEncHidlTest, FlushTest) {
470 description("Test Request for flush");
471 if (mDisableTest) return;
472 ASSERT_EQ(mComponent->start(), C2_OK);
473
474 typedef std::unique_lock<std::mutex> ULock;
475 char mURL[512];
476 strcpy(mURL, gEnv->getRes().c_str());
477 GetURLForComponent(mCompName, mURL);
478
479 // Setting default configuration
480 mFlushedIndices.clear();
481 int32_t nChannels = 2;
482 int32_t nSampleRate = 44100;
483 int32_t samplesPerFrame = 1024;
484 switch (mCompName) {
485 case aac:
486 nChannels = 2;
487 nSampleRate = 48000;
488 samplesPerFrame = 1024;
489 break;
490 case flac:
491 nChannels = 2;
492 nSampleRate = 48000;
493 samplesPerFrame = 1152;
494 break;
495 case opus:
496 nChannels = 2;
497 nSampleRate = 48000;
498 samplesPerFrame = 960;
499 break;
500 case amrnb:
501 nChannels = 1;
502 nSampleRate = 8000;
503 samplesPerFrame = 160;
504 break;
505 case amrwb:
506 nChannels = 1;
507 nSampleRate = 16000;
508 samplesPerFrame = 160;
509 break;
510 default:
511 ASSERT_TRUE(false);
512 }
513 setupConfigParam(mComponent, nChannels, nSampleRate);
514 std::ifstream eleStream;
515 uint32_t numFramesFlushed = 30;
516 uint32_t numFrames = 128;
517 eleStream.open(mURL, std::ifstream::binary);
518 ASSERT_EQ(eleStream.is_open(), true);
519 ALOGV("mURL : %s", mURL);
520 ASSERT_NO_FATAL_FAILURE(
521 encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
522 mFlushedIndices, mLinearPool, eleStream, numFramesFlushed,
523 samplesPerFrame, nChannels, nSampleRate));
524 std::list<std::unique_ptr<C2Work>> flushedWork;
525 c2_status_t err =
526 mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
527 ASSERT_EQ(err, C2_OK);
528 ASSERT_NO_FATAL_FAILURE(
529 waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
530 (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
531 uint64_t frameIndex;
532 {
533 //Update mFlushedIndices based on the index received from flush()
534 ULock l(mQueueLock);
535 for (std::unique_ptr<C2Work>& work : flushedWork) {
536 ASSERT_NE(work, nullptr);
537 frameIndex = work->input.ordinal.frameIndex.peeku();
538 std::list<uint64_t>::iterator frameIndexIt =
539 std::find(mFlushedIndices.begin(), mFlushedIndices.end(),
540 frameIndex);
541 if (!mFlushedIndices.empty() &&
542 (frameIndexIt != mFlushedIndices.end())) {
543 mFlushedIndices.erase(frameIndexIt);
544 work->input.buffers.clear();
545 work->worklets.clear();
546 mWorkQueue.push_back(std::move(work));
547 }
548 }
549 }
550 mFlushedIndices.clear();
551 ASSERT_NO_FATAL_FAILURE(
552 encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
553 mFlushedIndices, mLinearPool, eleStream,
554 numFrames - numFramesFlushed, samplesPerFrame,
555 nChannels, nSampleRate, true));
556 eleStream.close();
557 err =
558 mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
559 ASSERT_EQ(err, C2_OK);
560 ASSERT_NO_FATAL_FAILURE(
561 waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
562 (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
563 {
564 //Update mFlushedIndices based on the index received from flush()
565 ULock l(mQueueLock);
566 for (std::unique_ptr<C2Work>& work : flushedWork) {
567 ASSERT_NE(work, nullptr);
568 frameIndex = work->input.ordinal.frameIndex.peeku();
569 std::list<uint64_t>::iterator frameIndexIt =
570 std::find(mFlushedIndices.begin(), mFlushedIndices.end(),
571 frameIndex);
572 if (!mFlushedIndices.empty() &&
573 (frameIndexIt != mFlushedIndices.end())) {
574 mFlushedIndices.erase(frameIndexIt);
575 work->input.buffers.clear();
576 work->worklets.clear();
577 mWorkQueue.push_back(std::move(work));
578 }
579 }
580 }
581 ASSERT_EQ(mFlushedIndices.empty(), true);
582 ASSERT_EQ(mComponent->stop(), C2_OK);
583 }
584
585 } // anonymous namespace
586
main(int argc,char ** argv)587 int main(int argc, char** argv) {
588 gEnv = new ComponentTestEnvironment();
589 ::testing::AddGlobalTestEnvironment(gEnv);
590 ::testing::InitGoogleTest(&argc, argv);
591 gEnv->init(&argc, argv);
592 int status = gEnv->initFromOptions(argc, argv);
593 if (status == 0) {
594 int status = RUN_ALL_TESTS();
595 LOG(INFO) << "C2 Test result = " << status;
596 }
597 return status;
598 }
599