| /* |
| * Copyright 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. |
| */ |
| |
| #define LOG_TAG "Tuner_hidl_hal_test" |
| |
| #include <VtsHalHidlTargetTestBase.h> |
| #include <VtsHalHidlTargetTestEnvBase.h> |
| #include <android-base/logging.h> |
| #include <android/hardware/tv/tuner/1.0/IDemux.h> |
| #include <android/hardware/tv/tuner/1.0/IDescrambler.h> |
| #include <android/hardware/tv/tuner/1.0/IDvr.h> |
| #include <android/hardware/tv/tuner/1.0/IDvrCallback.h> |
| #include <android/hardware/tv/tuner/1.0/IFilter.h> |
| #include <android/hardware/tv/tuner/1.0/IFilterCallback.h> |
| #include <android/hardware/tv/tuner/1.0/IFrontend.h> |
| #include <android/hardware/tv/tuner/1.0/IFrontendCallback.h> |
| #include <android/hardware/tv/tuner/1.0/ITuner.h> |
| #include <android/hardware/tv/tuner/1.0/types.h> |
| #include <binder/MemoryDealer.h> |
| #include <fmq/MessageQueue.h> |
| #include <gtest/gtest.h> |
| #include <hidl/GtestPrinter.h> |
| #include <hidl/HidlSupport.h> |
| #include <hidl/HidlTransportSupport.h> |
| #include <hidl/ServiceManagement.h> |
| #include <hidl/Status.h> |
| #include <hidlmemory/FrameworkUtils.h> |
| #include <utils/Condition.h> |
| #include <utils/Mutex.h> |
| #include <fstream> |
| #include <iostream> |
| #include <map> |
| |
| #include "VtsHalTvTunerV1_0TestConfigurations.h" |
| |
| #define WAIT_TIMEOUT 3000000000 |
| #define WAIT_TIMEOUT_data_ready 3000000000 * 4 |
| |
| using android::Condition; |
| using android::IMemory; |
| using android::IMemoryHeap; |
| using android::MemoryDealer; |
| using android::Mutex; |
| using android::sp; |
| using android::hardware::EventFlag; |
| using android::hardware::fromHeap; |
| using android::hardware::hidl_string; |
| using android::hardware::hidl_vec; |
| using android::hardware::HidlMemory; |
| using android::hardware::kSynchronizedReadWrite; |
| using android::hardware::MessageQueue; |
| using android::hardware::MQDescriptorSync; |
| using android::hardware::Return; |
| using android::hardware::Void; |
| using android::hardware::tv::tuner::V1_0::DataFormat; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterEvent; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterMainType; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterPesEvent; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterSectionEvent; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterSettings; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterStatus; |
| using android::hardware::tv::tuner::V1_0::DemuxFilterType; |
| using android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits; |
| using android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings; |
| using android::hardware::tv::tuner::V1_0::DemuxTsFilterType; |
| using android::hardware::tv::tuner::V1_0::DvrSettings; |
| using android::hardware::tv::tuner::V1_0::DvrType; |
| using android::hardware::tv::tuner::V1_0::FrontendAtscModulation; |
| using android::hardware::tv::tuner::V1_0::FrontendAtscSettings; |
| using android::hardware::tv::tuner::V1_0::FrontendDvbtSettings; |
| using android::hardware::tv::tuner::V1_0::FrontendEventType; |
| using android::hardware::tv::tuner::V1_0::FrontendId; |
| using android::hardware::tv::tuner::V1_0::FrontendInfo; |
| using android::hardware::tv::tuner::V1_0::FrontendInnerFec; |
| using android::hardware::tv::tuner::V1_0::FrontendScanMessage; |
| using android::hardware::tv::tuner::V1_0::FrontendScanMessageType; |
| using android::hardware::tv::tuner::V1_0::FrontendScanType; |
| using android::hardware::tv::tuner::V1_0::FrontendSettings; |
| using android::hardware::tv::tuner::V1_0::IDemux; |
| using android::hardware::tv::tuner::V1_0::IDescrambler; |
| using android::hardware::tv::tuner::V1_0::IDvr; |
| using android::hardware::tv::tuner::V1_0::IDvrCallback; |
| using android::hardware::tv::tuner::V1_0::IFilter; |
| using android::hardware::tv::tuner::V1_0::IFilterCallback; |
| using android::hardware::tv::tuner::V1_0::IFrontend; |
| using android::hardware::tv::tuner::V1_0::IFrontendCallback; |
| using android::hardware::tv::tuner::V1_0::ITuner; |
| using android::hardware::tv::tuner::V1_0::PlaybackSettings; |
| using android::hardware::tv::tuner::V1_0::PlaybackStatus; |
| using android::hardware::tv::tuner::V1_0::RecordSettings; |
| using android::hardware::tv::tuner::V1_0::RecordStatus; |
| using android::hardware::tv::tuner::V1_0::Result; |
| |
| using ::testing::AssertionResult; |
| |
| namespace { |
| |
| using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>; |
| using MQDesc = MQDescriptorSync<uint8_t>; |
| |
| const std::vector<uint8_t> goldenDataOutputBuffer{ |
| 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0, 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1e, 0xdb, |
| 0x01, 0x40, 0x16, 0xec, 0x04, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x0f, 0x03, |
| 0xc5, 0x8b, 0xb8, 0x00, 0x00, 0x00, 0x01, 0x68, 0xca, 0x8c, 0xb2, 0x00, 0x00, 0x01, 0x06, |
| 0x05, 0xff, 0xff, 0x70, 0xdc, 0x45, 0xe9, 0xbd, 0xe6, 0xd9, 0x48, 0xb7, 0x96, 0x2c, 0xd8, |
| 0x20, 0xd9, 0x23, 0xee, 0xef, 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, 0x6f, 0x72, |
| 0x65, 0x20, 0x31, 0x34, 0x32, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34, 0x2f, 0x4d, |
| 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x63, |
| 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, |
| 0x33, 0x2d, 0x32, 0x30, 0x31, 0x34, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, |
| 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e, 0x2e, 0x6f, |
| 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x20, 0x2d, 0x20, |
| 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, |
| 0x30, 0x20, 0x72, 0x65, 0x66, 0x3d, 0x32, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, |
| 0x3d, 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x65, 0x3d, |
| 0x30, 0x78, 0x31, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x31, 0x20, 0x6d, 0x65, 0x3d, 0x68, 0x65, |
| 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 0x3d, 0x37, 0x20, 0x70, 0x73, 0x79, 0x3d, 0x31, |
| 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x3a, 0x30, 0x2e, |
| 0x30, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x66, 0x3d, 0x31, 0x20, |
| 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, |
| 0x6f, 0x6d, 0x61, 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69, |
| 0x73, 0x3d, 0x31, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x30, 0x20, 0x63, 0x71, |
| 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a, 0x6f, 0x6e, 0x65, 0x3d, 0x32, 0x31, |
| 0x2c, 0x31, 0x31, 0x20, 0x66, 0x61, 0x73, 0x74, 0x5f, 0x70, 0x73, 0x6b, 0x69, 0x70, 0x3d, |
| 0x31, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, |
| 0x73, 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, |
| 0x36, 0x30, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x5f, 0x74, 0x68, |
| 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x35, 0x20, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x64, 0x5f, |
| 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x30, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, |
| 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x69, 0x6e, 0x74, 0x65, |
| 0x72, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x6c, 0x75, 0x72, 0x61, 0x79, |
| 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, |
| 0x72, 0x61, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, |
| 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x30, 0x20, 0x77, 0x65, 0x69, 0x67, 0x68, |
| 0x74, 0x70, 0x3d, 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30, |
| 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d, 0x32, 0x35, 0x20, |
| 0x73, 0x63, 0x65, 0x6e, 0x65, |
| }; |
| |
| // const uint16_t FMQ_SIZE_4K = 0x1000; |
| // const uint32_t FMQ_SIZE_1M = 0x100000; |
| // const uint32_t FMQ_SIZE_16M = 0x1000000; |
| const uint8_t FRONTEND_EVENT_CALLBACK_WAIT_COUNT = 4; |
| |
| struct FilterConf { |
| DemuxFilterType type; |
| DemuxFilterSettings setting; |
| }; |
| |
| enum FilterEventType : uint8_t { |
| UNDEFINED, |
| SECTION, |
| MEDIA, |
| PES, |
| RECORD, |
| MMTPRECORD, |
| DOWNLOAD, |
| TEMI, |
| }; |
| |
| struct PlaybackConf { |
| string inputDataFile; |
| PlaybackSettings setting; |
| }; |
| |
| /******************************** Start FrontendCallback **********************************/ |
| class FrontendCallback : public IFrontendCallback { |
| public: |
| virtual Return<void> onEvent(FrontendEventType frontendEventType) override { |
| android::Mutex::Autolock autoLock(mMsgLock); |
| mEventReceived = true; |
| mEventType = frontendEventType; |
| mMsgCondition.signal(); |
| return Void(); |
| } |
| |
| virtual Return<void> onScanMessage(FrontendScanMessageType type, |
| const FrontendScanMessage& message) override { |
| android::Mutex::Autolock autoLock(mMsgLock); |
| ALOGD("[vts] scan message. Type: %d", mScanMessageType); |
| mScanMessageReceived = true; |
| mScanMessageType = type; |
| mScanLockMessageReceived = |
| mScanLockMessageReceived | (type == FrontendScanMessageType::LOCKED); |
| mScanMessage = message; |
| mMsgCondition.signal(); |
| return Void(); |
| }; |
| |
| void tuneTestOnEventReceive(sp<IFrontend>& frontend, FrontendSettings settings); |
| void tuneTestOnLock(sp<IFrontend>& frontend, FrontendSettings settings); |
| void scanTestOnMessageLock(sp<IFrontend>& frontend, FrontendSettings settings, |
| FrontendScanType type); |
| |
| private: |
| bool mEventReceived = false; |
| bool mScanMessageReceived = false; |
| bool mScanLockMessageReceived = false; |
| FrontendEventType mEventType; |
| FrontendScanMessageType mScanMessageType; |
| FrontendScanMessage mScanMessage; |
| hidl_vec<uint8_t> mEventMessage; |
| android::Mutex mMsgLock; |
| android::Condition mMsgCondition; |
| uint8_t mOnEvenRetry = 0; |
| }; |
| |
| void FrontendCallback::tuneTestOnEventReceive(sp<IFrontend>& frontend, FrontendSettings settings) { |
| Result result = frontend->tune(settings); |
| |
| EXPECT_TRUE(result == Result::SUCCESS); |
| |
| android::Mutex::Autolock autoLock(mMsgLock); |
| while (!mEventReceived) { |
| if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) { |
| EXPECT_TRUE(false) << "event not received within timeout"; |
| return; |
| } |
| } |
| mEventReceived = false; |
| } |
| |
| void FrontendCallback::tuneTestOnLock(sp<IFrontend>& frontend, FrontendSettings settings) { |
| Result result = frontend->tune(settings); |
| |
| EXPECT_TRUE(result == Result::SUCCESS); |
| |
| android::Mutex::Autolock autoLock(mMsgLock); |
| wait: |
| while (!mEventReceived) { |
| if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) { |
| EXPECT_TRUE(false) << "event not received within timeout"; |
| return; |
| } |
| } |
| if (mEventType != FrontendEventType::LOCKED) { |
| ALOGD("[vts] frontend callback event received. Type: %d", mEventType); |
| mEventReceived = false; |
| if (mOnEvenRetry++ < FRONTEND_EVENT_CALLBACK_WAIT_COUNT) { |
| goto wait; |
| } |
| } |
| EXPECT_TRUE(mEventType == FrontendEventType::LOCKED) << "LOCK event not received"; |
| mEventReceived = false; |
| mOnEvenRetry = 0; |
| } |
| |
| void FrontendCallback::scanTestOnMessageLock(sp<IFrontend>& frontend, FrontendSettings settings, |
| FrontendScanType type) { |
| Result result = frontend->scan(settings, type); |
| EXPECT_TRUE(result == Result::SUCCESS); |
| android::Mutex::Autolock autoLock(mMsgLock); |
| int messagesCount = 0; |
| |
| wait: |
| int count = 0; |
| while (!mScanMessageReceived) { |
| if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) { |
| ALOGD("[vts] waiting for scan message callback..."); |
| if (count++ > 10) { |
| EXPECT_TRUE(false) << "WAITING TOO LONG!!"; |
| return; |
| } |
| } |
| } |
| |
| if (mScanMessageType != FrontendScanMessageType::END) { |
| ALOGD("[vts] frontend scan message received. Type: %d", mScanMessageType); |
| mScanMessageReceived = false; |
| if (messagesCount++ > 3) { |
| EXPECT_TRUE(false) << "WAITING ON TOO MANY MSGS!!"; |
| return; |
| } |
| goto wait; |
| } |
| |
| EXPECT_TRUE(mScanLockMessageReceived) << "scan lock message not received before scan ended"; |
| mScanMessageReceived = false; |
| mScanLockMessageReceived = false; |
| } |
| /******************************** End FrontendCallback **********************************/ |
| |
| /******************************** Start FilterCallback **********************************/ |
| class FilterCallback : public IFilterCallback { |
| public: |
| virtual Return<void> onFilterEvent(const DemuxFilterEvent& filterEvent) override { |
| android::Mutex::Autolock autoLock(mMsgLock); |
| // Temprarily we treat the first coming back filter data on the matching pid a success |
| // once all of the MQ are cleared, means we got all the expected output |
| mFilterIdToEvent = filterEvent; |
| readFilterEventData(); |
| mPidFilterOutputCount++; |
| // mFilterIdToMQ.erase(filterEvent.filterId); |
| |
| // startFilterEventThread(filterEvent); |
| mMsgCondition.signal(); |
| return Void(); |
| } |
| |
| virtual Return<void> onFilterStatus(const DemuxFilterStatus /*status*/) override { |
| return Void(); |
| } |
| |
| void setFilterId(uint32_t filterId) { mFilterId = filterId; } |
| void setFilterEventType(FilterEventType type) { mFilterEventType = type; } |
| |
| void testFilterDataOutput(); |
| |
| void startFilterEventThread(DemuxFilterEvent event); |
| static void* __threadLoopFilter(void* threadArgs); |
| void filterThreadLoop(DemuxFilterEvent& event); |
| |
| void updateFilterMQ(MQDesc& filterMQDescriptor); |
| void updateGoldenOutputMap(string goldenOutputFile); |
| bool readFilterEventData(); |
| |
| private: |
| struct FilterThreadArgs { |
| FilterCallback* user; |
| DemuxFilterEvent event; |
| }; |
| uint16_t mDataLength = 0; |
| std::vector<uint8_t> mDataOutputBuffer; |
| |
| string mFilterIdToGoldenOutput; |
| |
| uint32_t mFilterId; |
| FilterEventType mFilterEventType; |
| std::unique_ptr<FilterMQ> mFilterIdToMQ; |
| EventFlag* mFilterIdToMQEventFlag; |
| DemuxFilterEvent mFilterIdToEvent; |
| |
| android::Mutex mMsgLock; |
| android::Mutex mFilterOutputLock; |
| android::Condition mMsgCondition; |
| android::Condition mFilterOutputCondition; |
| |
| pthread_t mFilterThread; |
| |
| int mPidFilterOutputCount = 0; |
| }; |
| |
| void FilterCallback::startFilterEventThread(DemuxFilterEvent event) { |
| struct FilterThreadArgs* threadArgs = |
| (struct FilterThreadArgs*)malloc(sizeof(struct FilterThreadArgs)); |
| threadArgs->user = this; |
| threadArgs->event = event; |
| |
| pthread_create(&mFilterThread, NULL, __threadLoopFilter, (void*)threadArgs); |
| pthread_setname_np(mFilterThread, "test_playback_input_loop"); |
| } |
| |
| void FilterCallback::testFilterDataOutput() { |
| android::Mutex::Autolock autoLock(mMsgLock); |
| while (mPidFilterOutputCount < 1) { |
| if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) { |
| EXPECT_TRUE(false) << "filter output matching pid does not output within timeout"; |
| return; |
| } |
| } |
| mPidFilterOutputCount = 0; |
| ALOGW("[vts] pass and stop"); |
| } |
| |
| void FilterCallback::updateFilterMQ(MQDesc& filterMQDescriptor) { |
| mFilterIdToMQ = std::make_unique<FilterMQ>(filterMQDescriptor, true /* resetPointers */); |
| EXPECT_TRUE(mFilterIdToMQ); |
| EXPECT_TRUE(EventFlag::createEventFlag(mFilterIdToMQ->getEventFlagWord(), |
| &mFilterIdToMQEventFlag) == android::OK); |
| } |
| |
| void FilterCallback::updateGoldenOutputMap(string goldenOutputFile) { |
| mFilterIdToGoldenOutput = goldenOutputFile; |
| } |
| |
| void* FilterCallback::__threadLoopFilter(void* threadArgs) { |
| FilterCallback* const self = |
| static_cast<FilterCallback*>(((struct FilterThreadArgs*)threadArgs)->user); |
| self->filterThreadLoop(((struct FilterThreadArgs*)threadArgs)->event); |
| return 0; |
| } |
| |
| void FilterCallback::filterThreadLoop(DemuxFilterEvent& /* event */) { |
| android::Mutex::Autolock autoLock(mFilterOutputLock); |
| // Read from mFilterIdToMQ[event.filterId] per event and filter type |
| |
| // Assemble to filterOutput[filterId] |
| |
| // check if filterOutput[filterId] matches goldenOutput[filterId] |
| |
| // If match, remove filterId entry from MQ map |
| |
| // end thread |
| } |
| |
| bool FilterCallback::readFilterEventData() { |
| bool result = false; |
| DemuxFilterEvent filterEvent = mFilterIdToEvent; |
| ALOGW("[vts] reading from filter FMQ %d", mFilterId); |
| // todo separate filter handlers |
| for (int i = 0; i < filterEvent.events.size(); i++) { |
| switch (mFilterEventType) { |
| case FilterEventType::SECTION: |
| mDataLength = filterEvent.events[i].section().dataLength; |
| break; |
| case FilterEventType::PES: |
| mDataLength = filterEvent.events[i].pes().dataLength; |
| break; |
| case FilterEventType::MEDIA: |
| break; |
| case FilterEventType::RECORD: |
| break; |
| case FilterEventType::MMTPRECORD: |
| break; |
| case FilterEventType::DOWNLOAD: |
| break; |
| default: |
| break; |
| } |
| // EXPECT_TRUE(mDataLength == goldenDataOutputBuffer.size()) << "buffer size does not |
| // match"; |
| |
| mDataOutputBuffer.resize(mDataLength); |
| result = mFilterIdToMQ->read(mDataOutputBuffer.data(), mDataLength); |
| EXPECT_TRUE(result) << "can't read from Filter MQ"; |
| |
| /*for (int i = 0; i < mDataLength; i++) { |
| EXPECT_TRUE(goldenDataOutputBuffer[i] == mDataOutputBuffer[i]) << "data does not match"; |
| }*/ |
| } |
| mFilterIdToMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED)); |
| return result; |
| } |
| /******************************** End FilterCallback **********************************/ |
| |
| /******************************** Start DvrCallback **********************************/ |
| class DvrCallback : public IDvrCallback { |
| public: |
| virtual Return<void> onRecordStatus(DemuxFilterStatus status) override { |
| ALOGW("[vts] record status %hhu", status); |
| switch (status) { |
| case DemuxFilterStatus::DATA_READY: |
| break; |
| case DemuxFilterStatus::LOW_WATER: |
| break; |
| case DemuxFilterStatus::HIGH_WATER: |
| case DemuxFilterStatus::OVERFLOW: |
| ALOGW("[vts] record overflow. Flushing"); |
| break; |
| } |
| return Void(); |
| } |
| |
| virtual Return<void> onPlaybackStatus(PlaybackStatus status) override { |
| // android::Mutex::Autolock autoLock(mMsgLock); |
| ALOGW("[vts] playback status %d", status); |
| switch (status) { |
| case PlaybackStatus::SPACE_EMPTY: |
| case PlaybackStatus::SPACE_ALMOST_EMPTY: |
| ALOGW("[vts] keep playback inputing %d", status); |
| mKeepWritingPlaybackFMQ = true; |
| break; |
| case PlaybackStatus::SPACE_ALMOST_FULL: |
| case PlaybackStatus::SPACE_FULL: |
| ALOGW("[vts] stop playback inputing %d", status); |
| mKeepWritingPlaybackFMQ = false; |
| break; |
| } |
| return Void(); |
| } |
| |
| void testFilterDataOutput(); |
| void stopPlaybackThread(); |
| void testRecordOutput(); |
| void stopRecordThread(); |
| |
| void startPlaybackInputThread(PlaybackConf playbackConf, MQDesc& playbackMQDescriptor); |
| void startRecordOutputThread(RecordSettings recordSetting, MQDesc& recordMQDescriptor); |
| static void* __threadLoopPlayback(void* threadArgs); |
| static void* __threadLoopRecord(void* threadArgs); |
| void playbackThreadLoop(PlaybackConf* playbackConf, bool* keepWritingPlaybackFMQ); |
| void recordThreadLoop(RecordSettings* recordSetting, bool* keepWritingPlaybackFMQ); |
| |
| bool readRecordFMQ(); |
| |
| private: |
| struct PlaybackThreadArgs { |
| DvrCallback* user; |
| PlaybackConf* playbackConf; |
| bool* keepWritingPlaybackFMQ; |
| }; |
| struct RecordThreadArgs { |
| DvrCallback* user; |
| RecordSettings* recordSetting; |
| bool* keepReadingRecordFMQ; |
| }; |
| uint16_t mDataLength = 0; |
| std::vector<uint8_t> mDataOutputBuffer; |
| |
| std::map<uint32_t, std::unique_ptr<FilterMQ>> mFilterIdToMQ; |
| std::unique_ptr<FilterMQ> mPlaybackMQ; |
| std::unique_ptr<FilterMQ> mRecordMQ; |
| std::map<uint32_t, EventFlag*> mFilterIdToMQEventFlag; |
| std::map<uint32_t, DemuxFilterEvent> mFilterIdToEvent; |
| |
| android::Mutex mMsgLock; |
| android::Mutex mPlaybackThreadLock; |
| android::Mutex mRecordThreadLock; |
| android::Condition mMsgCondition; |
| |
| bool mKeepWritingPlaybackFMQ = true; |
| bool mKeepReadingRecordFMQ = true; |
| bool mPlaybackThreadRunning; |
| bool mRecordThreadRunning; |
| pthread_t mPlaybackThread; |
| pthread_t mRecordThread; |
| |
| int mPidFilterOutputCount = 0; |
| }; |
| |
| void DvrCallback::startPlaybackInputThread(PlaybackConf playbackConf, |
| MQDesc& playbackMQDescriptor) { |
| mPlaybackMQ = std::make_unique<FilterMQ>(playbackMQDescriptor, true /* resetPointers */); |
| EXPECT_TRUE(mPlaybackMQ); |
| struct PlaybackThreadArgs* threadArgs = |
| (struct PlaybackThreadArgs*)malloc(sizeof(struct PlaybackThreadArgs)); |
| threadArgs->user = this; |
| threadArgs->playbackConf = &playbackConf; |
| threadArgs->keepWritingPlaybackFMQ = &mKeepWritingPlaybackFMQ; |
| |
| pthread_create(&mPlaybackThread, NULL, __threadLoopPlayback, (void*)threadArgs); |
| pthread_setname_np(mPlaybackThread, "test_playback_input_loop"); |
| } |
| |
| void DvrCallback::stopPlaybackThread() { |
| mPlaybackThreadRunning = false; |
| mKeepWritingPlaybackFMQ = false; |
| |
| android::Mutex::Autolock autoLock(mPlaybackThreadLock); |
| } |
| |
| void* DvrCallback::__threadLoopPlayback(void* threadArgs) { |
| DvrCallback* const self = |
| static_cast<DvrCallback*>(((struct PlaybackThreadArgs*)threadArgs)->user); |
| self->playbackThreadLoop(((struct PlaybackThreadArgs*)threadArgs)->playbackConf, |
| ((struct PlaybackThreadArgs*)threadArgs)->keepWritingPlaybackFMQ); |
| return 0; |
| } |
| |
| void DvrCallback::playbackThreadLoop(PlaybackConf* playbackConf, bool* keepWritingPlaybackFMQ) { |
| android::Mutex::Autolock autoLock(mPlaybackThreadLock); |
| mPlaybackThreadRunning = true; |
| |
| // Create the EventFlag that is used to signal the HAL impl that data have been |
| // written into the Playback FMQ |
| EventFlag* playbackMQEventFlag; |
| EXPECT_TRUE(EventFlag::createEventFlag(mPlaybackMQ->getEventFlagWord(), &playbackMQEventFlag) == |
| android::OK); |
| |
| // open the stream and get its length |
| std::ifstream inputData(playbackConf->inputDataFile, std::ifstream::binary); |
| int writeSize = playbackConf->setting.packetSize * 6; |
| char* buffer = new char[writeSize]; |
| ALOGW("[vts] playback thread loop start %s", playbackConf->inputDataFile.c_str()); |
| if (!inputData.is_open()) { |
| mPlaybackThreadRunning = false; |
| ALOGW("[vts] Error %s", strerror(errno)); |
| } |
| |
| while (mPlaybackThreadRunning) { |
| // move the stream pointer for packet size * 6 every read until the end |
| while (*keepWritingPlaybackFMQ) { |
| inputData.read(buffer, writeSize); |
| if (!inputData) { |
| int leftSize = inputData.gcount(); |
| if (leftSize == 0) { |
| mPlaybackThreadRunning = false; |
| break; |
| } |
| inputData.clear(); |
| inputData.read(buffer, leftSize); |
| // Write the left over of the input data and quit the thread |
| if (leftSize > 0) { |
| EXPECT_TRUE(mPlaybackMQ->write((unsigned char*)&buffer[0], leftSize)); |
| playbackMQEventFlag->wake( |
| static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY)); |
| } |
| mPlaybackThreadRunning = false; |
| break; |
| } |
| // Write input FMQ and notify the Tuner Implementation |
| EXPECT_TRUE(mPlaybackMQ->write((unsigned char*)&buffer[0], writeSize)); |
| playbackMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY)); |
| inputData.seekg(writeSize, inputData.cur); |
| sleep(1); |
| } |
| } |
| |
| ALOGW("[vts] Playback thread end."); |
| |
| delete[] buffer; |
| inputData.close(); |
| } |
| |
| void DvrCallback::testRecordOutput() { |
| android::Mutex::Autolock autoLock(mMsgLock); |
| while (mDataOutputBuffer.empty()) { |
| if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) { |
| EXPECT_TRUE(false) << "record output matching pid does not output within timeout"; |
| return; |
| } |
| } |
| stopRecordThread(); |
| ALOGW("[vts] record pass and stop"); |
| } |
| |
| void DvrCallback::startRecordOutputThread(RecordSettings recordSetting, |
| MQDesc& recordMQDescriptor) { |
| mRecordMQ = std::make_unique<FilterMQ>(recordMQDescriptor, true /* resetPointers */); |
| EXPECT_TRUE(mRecordMQ); |
| struct RecordThreadArgs* threadArgs = |
| (struct RecordThreadArgs*)malloc(sizeof(struct RecordThreadArgs)); |
| threadArgs->user = this; |
| threadArgs->recordSetting = &recordSetting; |
| threadArgs->keepReadingRecordFMQ = &mKeepReadingRecordFMQ; |
| |
| pthread_create(&mRecordThread, NULL, __threadLoopRecord, (void*)threadArgs); |
| pthread_setname_np(mRecordThread, "test_record_input_loop"); |
| } |
| |
| void* DvrCallback::__threadLoopRecord(void* threadArgs) { |
| DvrCallback* const self = |
| static_cast<DvrCallback*>(((struct RecordThreadArgs*)threadArgs)->user); |
| self->recordThreadLoop(((struct RecordThreadArgs*)threadArgs)->recordSetting, |
| ((struct RecordThreadArgs*)threadArgs)->keepReadingRecordFMQ); |
| return 0; |
| } |
| |
| void DvrCallback::recordThreadLoop(RecordSettings* /*recordSetting*/, bool* keepReadingRecordFMQ) { |
| ALOGD("[vts] DvrCallback record threadLoop start."); |
| android::Mutex::Autolock autoLock(mRecordThreadLock); |
| mRecordThreadRunning = true; |
| |
| // Create the EventFlag that is used to signal the HAL impl that data have been |
| // read from the Record FMQ |
| EventFlag* recordMQEventFlag; |
| EXPECT_TRUE(EventFlag::createEventFlag(mRecordMQ->getEventFlagWord(), &recordMQEventFlag) == |
| android::OK); |
| |
| while (mRecordThreadRunning) { |
| while (*keepReadingRecordFMQ) { |
| uint32_t efState = 0; |
| android::status_t status = recordMQEventFlag->wait( |
| static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT, |
| true /* retry on spurious wake */); |
| if (status != android::OK) { |
| ALOGD("[vts] wait for data ready on the record FMQ"); |
| continue; |
| } |
| // Our current implementation filter the data and write it into the filter FMQ |
| // immediately after the DATA_READY from the VTS/framework |
| if (!readRecordFMQ()) { |
| ALOGD("[vts] record data failed to be filtered. Ending thread"); |
| mRecordThreadRunning = false; |
| break; |
| } |
| } |
| } |
| |
| mRecordThreadRunning = false; |
| ALOGD("[vts] record thread ended."); |
| } |
| |
| bool DvrCallback::readRecordFMQ() { |
| android::Mutex::Autolock autoLock(mMsgLock); |
| bool result = false; |
| mDataOutputBuffer.clear(); |
| mDataOutputBuffer.resize(mRecordMQ->availableToRead()); |
| result = mRecordMQ->read(mDataOutputBuffer.data(), mRecordMQ->availableToRead()); |
| EXPECT_TRUE(result) << "can't read from Record MQ"; |
| mMsgCondition.signal(); |
| return result; |
| } |
| |
| void DvrCallback::stopRecordThread() { |
| mKeepReadingRecordFMQ = false; |
| mRecordThreadRunning = false; |
| android::Mutex::Autolock autoLock(mRecordThreadLock); |
| } |
| /******************************** End DvrCallback **********************************/ |
| |
| /******************************** Start Test Implementation**********************************/ |
| class TunerHidlTest : public testing::TestWithParam<std::string> { |
| public: |
| virtual void SetUp() override { |
| mService = ITuner::getService(GetParam()); |
| ASSERT_NE(mService, nullptr); |
| initFrontendConfig(); |
| initFrontendScanConfig(); |
| initFilterConfig(); |
| } |
| |
| sp<ITuner> mService; |
| |
| protected: |
| static AssertionResult failure() { return ::testing::AssertionFailure(); } |
| |
| static AssertionResult success() { return ::testing::AssertionSuccess(); } |
| |
| static void description(const std::string& description) { |
| RecordProperty("description", description); |
| } |
| |
| sp<IFrontend> mFrontend; |
| FrontendInfo mFrontendInfo; |
| sp<FrontendCallback> mFrontendCallback; |
| sp<IDescrambler> mDescrambler; |
| sp<IDemux> mDemux; |
| sp<IDvr> mDvr; |
| sp<IFilter> mFilter; |
| std::map<uint32_t, sp<IFilter>> mFilters; |
| std::map<uint32_t, sp<FilterCallback>> mFilterCallbacks; |
| sp<FilterCallback> mFilterCallback; |
| sp<DvrCallback> mDvrCallback; |
| MQDesc mFilterMQDescriptor; |
| MQDesc mPlaybackMQDescriptor; |
| MQDesc mRecordMQDescriptor; |
| vector<uint32_t> mUsedFilterIds; |
| hidl_vec<FrontendId> mFeIds; |
| |
| uint32_t mDemuxId; |
| uint32_t mFilterId; |
| |
| pthread_t mPlaybackshread; |
| bool mPlaybackThreadRunning; |
| |
| AssertionResult getFrontendIds(); |
| AssertionResult getFrontendInfo(uint32_t frontendId); |
| AssertionResult openFrontend(uint32_t frontendId); |
| AssertionResult setFrontendCallback(); |
| AssertionResult scanFrontend(FrontendConfig config, FrontendScanType type); |
| AssertionResult stopScanFrontend(); |
| AssertionResult tuneFrontend(FrontendConfig config); |
| AssertionResult stopTuneFrontend(); |
| AssertionResult closeFrontend(); |
| |
| ::testing::AssertionResult createDemux(); |
| ::testing::AssertionResult createDemuxWithFrontend(int32_t frontendId, |
| FrontendSettings settings); |
| ::testing::AssertionResult getPlaybackMQDescriptor(); |
| ::testing::AssertionResult addPlaybackToDemux(PlaybackSettings setting); |
| ::testing::AssertionResult getRecordMQDescriptor(); |
| ::testing::AssertionResult addRecordToDemux(RecordSettings setting); |
| ::testing::AssertionResult addFilterToDemux(DemuxFilterType type, DemuxFilterSettings setting); |
| ::testing::AssertionResult getFilterMQDescriptor(); |
| ::testing::AssertionResult closeDemux(); |
| ::testing::AssertionResult createDescrambler(); |
| ::testing::AssertionResult closeDescrambler(); |
| |
| ::testing::AssertionResult playbackDataFlowTest(vector<FilterConf> filterConf, |
| PlaybackConf playbackConf, |
| vector<string> goldenOutputFiles); |
| ::testing::AssertionResult recordDataFlowTest(vector<FilterConf> filterConf, |
| RecordSettings recordSetting, |
| vector<string> goldenOutputFiles); |
| ::testing::AssertionResult broadcastDataFlowTest(vector<FilterConf> filterConf, |
| vector<string> goldenOutputFiles); |
| }; |
| |
| /*============================Start Frontend APIs Tests Implementation============================*/ |
| AssertionResult TunerHidlTest::getFrontendIds() { |
| Result status; |
| mService->getFrontendIds([&](Result result, const hidl_vec<FrontendId>& frontendIds) { |
| status = result; |
| mFeIds = frontendIds; |
| }); |
| return AssertionResult(status == Result::SUCCESS); |
| } |
| |
| AssertionResult TunerHidlTest::getFrontendInfo(uint32_t frontendId) { |
| Result status; |
| mService->getFrontendInfo(frontendId, [&](Result result, const FrontendInfo& frontendInfo) { |
| mFrontendInfo = frontendInfo; |
| status = result; |
| }); |
| return AssertionResult(status == Result::SUCCESS); |
| } |
| |
| AssertionResult TunerHidlTest::openFrontend(uint32_t frontendId) { |
| Result status; |
| mService->openFrontendById(frontendId, [&](Result result, const sp<IFrontend>& frontend) { |
| mFrontend = frontend; |
| status = result; |
| }); |
| return AssertionResult(status == Result::SUCCESS); |
| } |
| |
| AssertionResult TunerHidlTest::setFrontendCallback() { |
| EXPECT_TRUE(mFrontend) << "Test with openFrontend first."; |
| mFrontendCallback = new FrontendCallback(); |
| auto callbackStatus = mFrontend->setCallback(mFrontendCallback); |
| return AssertionResult(callbackStatus.isOk()); |
| } |
| |
| AssertionResult TunerHidlTest::scanFrontend(FrontendConfig config, FrontendScanType type) { |
| EXPECT_TRUE(mFrontendCallback) |
| << "test with openFrontend/setFrontendCallback/getFrontendInfo first."; |
| |
| EXPECT_TRUE(mFrontendInfo.type == config.type) |
| << "FrontendConfig does not match the frontend info of the given id."; |
| |
| mFrontendCallback->scanTestOnMessageLock(mFrontend, config.settings, type); |
| return AssertionResult(true); |
| } |
| |
| AssertionResult TunerHidlTest::stopScanFrontend() { |
| EXPECT_TRUE(mFrontend) << "Test with openFrontend first."; |
| Result status; |
| status = mFrontend->stopScan(); |
| return AssertionResult(status == Result::SUCCESS); |
| } |
| |
| AssertionResult TunerHidlTest::tuneFrontend(FrontendConfig config) { |
| EXPECT_TRUE(mFrontendCallback) |
| << "test with openFrontend/setFrontendCallback/getFrontendInfo first."; |
| |
| EXPECT_TRUE(mFrontendInfo.type == config.type) |
| << "FrontendConfig does not match the frontend info of the given id."; |
| |
| mFrontendCallback->tuneTestOnLock(mFrontend, config.settings); |
| return AssertionResult(true); |
| } |
| |
| AssertionResult TunerHidlTest::stopTuneFrontend() { |
| EXPECT_TRUE(mFrontend) << "Test with openFrontend first."; |
| Result status; |
| status = mFrontend->stopTune(); |
| return AssertionResult(status == Result::SUCCESS); |
| } |
| |
| AssertionResult TunerHidlTest::closeFrontend() { |
| EXPECT_TRUE(mFrontend) << "Test with openFrontend first."; |
| Result status; |
| status = mFrontend->close(); |
| mFrontend = nullptr; |
| mFrontendCallback = nullptr; |
| return AssertionResult(status == Result::SUCCESS); |
| } |
| /*============================End Frontend APIs Tests Implementation============================*/ |
| |
| /*::testing::AssertionResult TunerHidlTest::createDemux() { |
| Result status; |
| |
| mService->openDemux([&](Result result, uint32_t demuxId, const sp<IDemux>& demux) { |
| mDemux = demux; |
| mDemuxId = demuxId; |
| status = result; |
| }); |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::createDemuxWithFrontend(int32_t frontendId, |
| FrontendSettings settings) { |
| Result status; |
| |
| if (!mDemux && createDemux() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| if (!mFrontend && createFrontend(frontendId) == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| mFrontendCallback->testOnEvent(mFrontend, settings); |
| |
| status = mDemux->setFrontendDataSource(frontendId); |
| |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::closeDemux() { |
| Result status; |
| if (!mDemux && createDemux() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| status = mDemux->close(); |
| mDemux = nullptr; |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::createDescrambler() { |
| Result status; |
| |
| mService->openDescrambler([&](Result result, const sp<IDescrambler>& descrambler) { |
| mDescrambler = descrambler; |
| status = result; |
| }); |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| if (!mDemux && createDemux() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| status = mDescrambler->setDemuxSource(mDemuxId); |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| // Test if demux source can be set more than once. |
| status = mDescrambler->setDemuxSource(mDemuxId); |
| return ::testing::AssertionResult(status == Result::INVALID_STATE); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::closeDescrambler() { |
| Result status; |
| if (!mDescrambler && createDescrambler() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| status = mDescrambler->close(); |
| mDescrambler = nullptr; |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::addPlaybackToDemux(PlaybackSettings setting) { |
| Result status; |
| |
| if (!mDemux && createDemux() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| // Create dvr callback |
| mDvrCallback = new DvrCallback(); |
| |
| // Add playback input to the local demux |
| mDemux->openDvr(DvrType::PLAYBACK, FMQ_SIZE_1M, mDvrCallback, |
| [&](Result result, const sp<IDvr>& dvr) { |
| mDvr = dvr; |
| status = result; |
| }); |
| |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| DvrSettings dvrSetting; |
| dvrSetting.playback(setting); |
| status = mDvr->configure(dvrSetting); |
| |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::getPlaybackMQDescriptor() { |
| Result status; |
| |
| if ((!mDemux && createDemux() == ::testing::AssertionFailure()) || !mDvr) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| mDvr->getQueueDesc([&](Result result, const MQDesc& dvrMQDesc) { |
| mPlaybackMQDescriptor = dvrMQDesc; |
| status = result; |
| }); |
| |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::addRecordToDemux(RecordSettings setting) { |
| Result status; |
| |
| if (!mDemux && createDemux() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| // Create dvr callback |
| mDvrCallback = new DvrCallback(); |
| |
| // Add playback input to the local demux |
| mDemux->openDvr(DvrType::RECORD, FMQ_SIZE_1M, mDvrCallback, |
| [&](Result result, const sp<IDvr>& dvr) { |
| mDvr = dvr; |
| status = result; |
| }); |
| |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| DvrSettings dvrSetting; |
| dvrSetting.record(setting); |
| status = mDvr->configure(dvrSetting); |
| |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::getRecordMQDescriptor() { |
| Result status; |
| |
| if ((!mDemux && createDemux() == ::testing::AssertionFailure()) || !mDvr) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| mDvr->getQueueDesc([&](Result result, const MQDesc& dvrMQDesc) { |
| mRecordMQDescriptor = dvrMQDesc; |
| status = result; |
| }); |
| |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::addFilterToDemux(DemuxFilterType type, |
| DemuxFilterSettings setting) { |
| Result status; |
| |
| if (!mDemux && createDemux() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| // Create demux callback |
| mFilterCallback = new FilterCallback(); |
| |
| // Add filter to the local demux |
| mDemux->openFilter(type, FMQ_SIZE_16M, mFilterCallback, |
| [&](Result result, const sp<IFilter>& filter) { |
| mFilter = filter; |
| status = result; |
| }); |
| |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| mFilter->getId([&](Result result, uint32_t filterId) { |
| mFilterId = filterId; |
| status = result; |
| }); |
| |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| mFilterCallback->setFilterId(mFilterId); |
| |
| FilterEventType eventType = FilterEventType::UNDEFINED; |
| switch (type.mainType) { |
| case DemuxFilterMainType::TS: |
| switch (type.subType.tsFilterType()) { |
| case DemuxTsFilterType::UNDEFINED: |
| break; |
| case DemuxTsFilterType::SECTION: |
| eventType = FilterEventType::SECTION; |
| break; |
| case DemuxTsFilterType::PES: |
| eventType = FilterEventType::PES; |
| break; |
| case DemuxTsFilterType::TS: |
| break; |
| case DemuxTsFilterType::AUDIO: |
| case DemuxTsFilterType::VIDEO: |
| eventType = FilterEventType::MEDIA; |
| break; |
| case DemuxTsFilterType::PCR: |
| break; |
| case DemuxTsFilterType::RECORD: |
| eventType = FilterEventType::RECORD; |
| break; |
| case DemuxTsFilterType::TEMI: |
| eventType = FilterEventType::TEMI; |
| break; |
| } |
| break; |
| case DemuxFilterMainType::MMTP: |
| \/\*mmtpSettings\*\/ |
| break; |
| case DemuxFilterMainType::IP: |
| \/\*ipSettings\*\/ |
| break; |
| case DemuxFilterMainType::TLV: |
| \/\*tlvSettings\*\/ |
| break; |
| case DemuxFilterMainType::ALP: |
| \/\*alpSettings\*\/ |
| break; |
| default: |
| break; |
| } |
| mFilterCallback->setFilterEventType(eventType); |
| |
| // Configure the filter |
| status = mFilter->configure(setting); |
| |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::getFilterMQDescriptor() { |
| Result status; |
| |
| if (!mDemux || !mFilter) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| mFilter->getQueueDesc([&](Result result, const MQDesc& filterMQDesc) { |
| mFilterMQDescriptor = filterMQDesc; |
| status = result; |
| }); |
| |
| return ::testing::AssertionResult(status == Result::SUCCESS); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::playbackDataFlowTest( |
| vector<FilterConf> filterConf, PlaybackConf playbackConf, |
| vector<string> \/\*goldenOutputFiles\*\/) { |
| Result status; |
| int filterIdsSize; |
| // Filter Configuration Module |
| for (int i = 0; i < filterConf.size(); i++) { |
| if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) == |
| ::testing::AssertionFailure() || |
| // TODO use a map to save the FMQs/EvenFlags and pass to callback |
| getFilterMQDescriptor() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| filterIdsSize = mUsedFilterIds.size(); |
| mUsedFilterIds.resize(filterIdsSize + 1); |
| mUsedFilterIds[filterIdsSize] = mFilterId; |
| mFilters[mFilterId] = mFilter; |
| mFilterCallbacks[mFilterId] = mFilterCallback; |
| mFilterCallback->updateFilterMQ(mFilterMQDescriptor); |
| // mDemuxCallback->updateGoldenOutputMap(goldenOutputFiles[i]); |
| status = mFilter->start(); |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| } |
| |
| // Playback Input Module |
| PlaybackSettings playbackSetting = playbackConf.setting; |
| if (addPlaybackToDemux(playbackSetting) == ::testing::AssertionFailure() || |
| getPlaybackMQDescriptor() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| for (int i = 0; i <= filterIdsSize; i++) { |
| if (mDvr->attachFilter(mFilters[mUsedFilterIds[i]]) != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| } |
| mDvrCallback->startPlaybackInputThread(playbackConf, mPlaybackMQDescriptor); |
| status = mDvr->start(); |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| // Data Verify Module |
| std::map<uint32_t, sp<FilterCallback>>::iterator it; |
| for (it = mFilterCallbacks.begin(); it != mFilterCallbacks.end(); it++) { |
| it->second->testFilterDataOutput(); |
| } |
| mDvrCallback->stopPlaybackThread(); |
| |
| // Clean Up Module |
| for (int i = 0; i <= filterIdsSize; i++) { |
| if (mFilters[mUsedFilterIds[i]]->stop() != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| } |
| if (mDvr->stop() != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| mUsedFilterIds.clear(); |
| mFilterCallbacks.clear(); |
| mFilters.clear(); |
| return closeDemux(); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::broadcastDataFlowTest( |
| vector<FilterConf> filterConf, vector<string> \/\*goldenOutputFiles\*\/) { |
| Result status; |
| hidl_vec<FrontendId> feIds; |
| |
| mService->getFrontendIds([&](Result result, const hidl_vec<FrontendId>& frontendIds) { |
| status = result; |
| feIds = frontendIds; |
| }); |
| |
| if (feIds.size() == 0) { |
| ALOGW("[ WARN ] Frontend isn't available"); |
| return ::testing::AssertionFailure(); |
| } |
| |
| FrontendDvbtSettings dvbt{ |
| .frequency = 1000, |
| }; |
| FrontendSettings settings; |
| settings.dvbt(dvbt); |
| |
| if (createDemuxWithFrontend(feIds[0], settings) != ::testing::AssertionSuccess()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| int filterIdsSize; |
| // Filter Configuration Module |
| for (int i = 0; i < filterConf.size(); i++) { |
| if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) == |
| ::testing::AssertionFailure() || |
| // TODO use a map to save the FMQs/EvenFlags and pass to callback |
| getFilterMQDescriptor() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| filterIdsSize = mUsedFilterIds.size(); |
| mUsedFilterIds.resize(filterIdsSize + 1); |
| mUsedFilterIds[filterIdsSize] = mFilterId; |
| mFilters[mFilterId] = mFilter; |
| mFilterCallbacks[mFilterId] = mFilterCallback; |
| mFilterCallback->updateFilterMQ(mFilterMQDescriptor); |
| status = mFilter->start(); |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| } |
| |
| // Data Verify Module |
| std::map<uint32_t, sp<FilterCallback>>::iterator it; |
| for (it = mFilterCallbacks.begin(); it != mFilterCallbacks.end(); it++) { |
| it->second->testFilterDataOutput(); |
| } |
| |
| // Clean Up Module |
| for (int i = 0; i <= filterIdsSize; i++) { |
| if (mFilters[mUsedFilterIds[i]]->stop() != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| } |
| if (mFrontend->stopTune() != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| mUsedFilterIds.clear(); |
| mFilterCallbacks.clear(); |
| mFilters.clear(); |
| return closeDemux(); |
| } |
| |
| ::testing::AssertionResult TunerHidlTest::recordDataFlowTest(vector<FilterConf> filterConf, |
| RecordSettings recordSetting, |
| vector<string> |
| \/\*goldenOutputFiles\*\/) { Result status; hidl_vec<FrontendId> feIds; |
| |
| mService->getFrontendIds([&](Result result, const hidl_vec<FrontendId>& frontendIds) { |
| status = result; |
| feIds = frontendIds; |
| }); |
| |
| if (feIds.size() == 0) { |
| ALOGW("[ WARN ] Frontend isn't available"); |
| return ::testing::AssertionFailure(); |
| } |
| |
| FrontendDvbtSettings dvbt{ |
| .frequency = 1000, |
| }; |
| FrontendSettings settings; |
| settings.dvbt(dvbt); |
| |
| int filterIdsSize; |
| // Filter Configuration Module |
| for (int i = 0; i < filterConf.size(); i++) { |
| if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) == |
| ::testing::AssertionFailure() || |
| // TODO use a map to save the FMQs/EvenFlags and pass to callback |
| getFilterMQDescriptor() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| filterIdsSize = mUsedFilterIds.size(); |
| mUsedFilterIds.resize(filterIdsSize + 1); |
| mUsedFilterIds[filterIdsSize] = mFilterId; |
| mFilters[mFilterId] = mFilter; |
| } |
| |
| // Record Config Module |
| if (addRecordToDemux(recordSetting) == ::testing::AssertionFailure() || |
| getRecordMQDescriptor() == ::testing::AssertionFailure()) { |
| return ::testing::AssertionFailure(); |
| } |
| for (int i = 0; i <= filterIdsSize; i++) { |
| if (mDvr->attachFilter(mFilters[mUsedFilterIds[i]]) != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| } |
| |
| mDvrCallback->startRecordOutputThread(recordSetting, mRecordMQDescriptor); |
| status = mDvr->start(); |
| if (status != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| if (createDemuxWithFrontend(feIds[0], settings) != ::testing::AssertionSuccess()) { |
| return ::testing::AssertionFailure(); |
| } |
| |
| // Data Verify Module |
| mDvrCallback->testRecordOutput(); |
| |
| // Clean Up Module |
| for (int i = 0; i <= filterIdsSize; i++) { |
| if (mFilters[mUsedFilterIds[i]]->stop() != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| } |
| if (mFrontend->stopTune() != Result::SUCCESS) { |
| return ::testing::AssertionFailure(); |
| } |
| mUsedFilterIds.clear(); |
| mFilterCallbacks.clear(); |
| mFilters.clear(); |
| return closeDemux(); |
| }*/ |
| /******************************** End Test Implementation**********************************/ |
| |
| /******************************** Start Test Entry**********************************/ |
| /*============================Start Frontend Tests============================*/ |
| TEST_P(TunerHidlTest, getFrontendIds) { |
| description("Get Frontend ids and verify frontends exist"); |
| ASSERT_TRUE(getFrontendIds()); |
| ASSERT_TRUE(mFeIds.size() > 0); |
| } |
| |
| TEST_P(TunerHidlTest, openFrontend) { |
| description("Open all the existing Frontends and close them"); |
| ASSERT_TRUE(getFrontendIds()); |
| ASSERT_TRUE(mFeIds.size() > 0); |
| |
| for (size_t i = 0; i < mFeIds.size(); i++) { |
| ASSERT_TRUE(openFrontend(mFeIds[i])); |
| ASSERT_TRUE(closeFrontend()); |
| } |
| } |
| |
| TEST_P(TunerHidlTest, TuneFrontend) { |
| description("Tune one Frontend with specific setting and check Lock event"); |
| ASSERT_TRUE(getFrontendIds()); |
| ASSERT_TRUE(mFeIds.size() > 0); |
| ALOGW("[vts] expected Frontend type is %d", frontendArray[0].type); |
| for (size_t i = 0; i < mFeIds.size(); i++) { |
| ASSERT_TRUE(getFrontendInfo(mFeIds[i])); |
| ALOGW("[vts] Frontend type is %d", mFrontendInfo.type); |
| if (mFrontendInfo.type != frontendArray[0].type) { |
| continue; |
| } |
| ASSERT_TRUE(openFrontend(mFeIds[i])); |
| ASSERT_TRUE(setFrontendCallback()); |
| ASSERT_TRUE(stopTuneFrontend()); |
| ASSERT_TRUE(tuneFrontend(frontendArray[0])); |
| ASSERT_TRUE(stopTuneFrontend()); |
| ASSERT_TRUE(closeFrontend()); |
| break; |
| } |
| } |
| |
| TEST_P(TunerHidlTest, AutoScanFrontend) { |
| description("Run an auto frontend scan with specific setting and check lock scanMessage"); |
| ASSERT_TRUE(getFrontendIds()); |
| ASSERT_TRUE(mFeIds.size() > 0); |
| |
| for (size_t i = 0; i < mFeIds.size(); i++) { |
| ASSERT_TRUE(getFrontendInfo(mFeIds[i])); |
| if (mFrontendInfo.type != frontendArray[0].type) { |
| continue; |
| } |
| ASSERT_TRUE(openFrontend(mFeIds[i])); |
| ASSERT_TRUE(setFrontendCallback()); |
| ASSERT_TRUE(stopScanFrontend()); |
| ASSERT_TRUE(scanFrontend(frontendScanArray[0], FrontendScanType::SCAN_AUTO)); |
| ASSERT_TRUE(stopScanFrontend()); |
| ASSERT_TRUE(closeFrontend()); |
| break; |
| } |
| } |
| /*============================End Frontend Tests============================*/ |
| |
| /*TEST_P(TunerHidlTest, CreateDemuxWithFrontend) { |
| Result status; |
| hidl_vec<FrontendId> feIds; |
| |
| description("Create Demux with Frontend"); |
| mService->getFrontendIds([&](Result result, const hidl_vec<FrontendId>& frontendIds) { |
| status = result; |
| feIds = frontendIds; |
| }); |
| |
| if (feIds.size() == 0) { |
| ALOGW("[ WARN ] Frontend isn't available"); |
| return; |
| } |
| |
| FrontendDvbtSettings dvbt{ |
| .frequency = 1000, |
| }; |
| FrontendSettings settings; |
| settings.dvbt(dvbt); |
| |
| for (size_t i = 0; i < feIds.size(); i++) { |
| ASSERT_TRUE(createDemuxWithFrontend(feIds[i], settings)); |
| mFrontend->stopTune(); |
| } |
| } |
| |
| TEST_P(TunerHidlTest, CreateDemux) { |
| description("Create Demux"); |
| ASSERT_TRUE(createDemux()); |
| } |
| |
| TEST_P(TunerHidlTest, CloseDemux) { |
| description("Close Demux"); |
| ASSERT_TRUE(closeDemux()); |
| } |
| |
| TEST_P(TunerHidlTest, CreateDescrambler) { |
| description("Create Descrambler"); |
| ASSERT_TRUE(createDescrambler()); |
| } |
| |
| TEST_P(TunerHidlTest, CloseDescrambler) { |
| description("Close Descrambler"); |
| ASSERT_TRUE(closeDescrambler()); |
| }*/ |
| |
| /* |
| * DATA FLOW TESTS |
| * |
| * TODO: re-enable the tests after finalizing the testing stream. |
| */ |
| /*TEST_P(TunerHidlTest, PlaybackDataFlowWithSectionFilterTest) { |
| description("Feed ts data from playback and configure pes filter to get output"); |
| |
| // todo modulize the filter conf parser |
| vector<FilterConf> filterConf; |
| filterConf.resize(1); |
| |
| DemuxFilterSettings filterSetting; |
| DemuxTsFilterSettings tsFilterSetting{ |
| .tpid = 18, |
| }; |
| DemuxFilterSectionSettings sectionFilterSetting; |
| tsFilterSetting.filterSettings.section(sectionFilterSetting); |
| filterSetting.ts(tsFilterSetting); |
| |
| DemuxFilterType type{ |
| .mainType = DemuxFilterMainType::TS, |
| }; |
| type.subType.tsFilterType(DemuxTsFilterType::SECTION); |
| FilterConf sectionFilterConf{ |
| .type = type, |
| .setting = filterSetting, |
| }; |
| filterConf[0] = sectionFilterConf; |
| |
| PlaybackSettings playbackSetting{ |
| .statusMask = 0xf, |
| .lowThreshold = 0x1000, |
| .highThreshold = 0x07fff, |
| .dataFormat = DataFormat::TS, |
| .packetSize = 188, |
| }; |
| |
| PlaybackConf playbackConf{ |
| .inputDataFile = "/vendor/etc/test1.ts", |
| .setting = playbackSetting, |
| }; |
| |
| vector<string> goldenOutputFiles; |
| |
| ASSERT_TRUE(playbackDataFlowTest(filterConf, playbackConf, goldenOutputFiles)); |
| } |
| |
| TEST_P(TunerHidlTest, BroadcastDataFlowWithPesFilterTest) { |
| description("Feed ts data from frontend and test with PES filter"); |
| |
| // todo modulize the filter conf parser |
| vector<FilterConf> filterConf; |
| filterConf.resize(1); |
| |
| DemuxFilterSettings filterSetting; |
| DemuxTsFilterSettings tsFilterSetting{ |
| .tpid = 119, |
| }; |
| DemuxFilterPesDataSettings pesFilterSetting; |
| tsFilterSetting.filterSettings.pesData(pesFilterSetting); |
| filterSetting.ts(tsFilterSetting); |
| |
| DemuxFilterType type{ |
| .mainType = DemuxFilterMainType::TS, |
| }; |
| type.subType.tsFilterType(DemuxTsFilterType::PES); |
| FilterConf pesFilterConf{ |
| .type = type, |
| .setting = filterSetting, |
| }; |
| filterConf[0] = pesFilterConf; |
| |
| vector<string> goldenOutputFiles; |
| |
| ASSERT_TRUE(broadcastDataFlowTest(filterConf, goldenOutputFiles)); |
| } |
| |
| TEST_P(TunerHidlTest, RecordDataFlowWithTsRecordFilterTest) { |
| description("Feed ts data from frontend to recording and test with ts record filter"); |
| |
| // todo modulize the filter conf parser |
| vector<FilterConf> filterConf; |
| filterConf.resize(1); |
| |
| DemuxFilterSettings filterSetting; |
| DemuxTsFilterSettings tsFilterSetting{ |
| .tpid = 119, |
| }; |
| DemuxFilterRecordSettings recordFilterSetting; |
| tsFilterSetting.filterSettings.record(recordFilterSetting); |
| filterSetting.ts(tsFilterSetting); |
| |
| DemuxFilterType type{ |
| .mainType = DemuxFilterMainType::TS, |
| }; |
| type.subType.tsFilterType(DemuxTsFilterType::RECORD); |
| FilterConf recordFilterConf{ |
| .type = type, |
| .setting = filterSetting, |
| }; |
| filterConf[0] = recordFilterConf; |
| |
| RecordSettings recordSetting{ |
| .statusMask = 0xf, |
| .lowThreshold = 0x1000, |
| .highThreshold = 0x07fff, |
| .dataFormat = DataFormat::TS, |
| .packetSize = 188, |
| }; |
| |
| vector<string> goldenOutputFiles; |
| |
| ASSERT_TRUE(recordDataFlowTest(filterConf, recordSetting, goldenOutputFiles)); |
| }*/ |
| /******************************** End Test Entry**********************************/ |
| } // namespace |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PerInstance, TunerHidlTest, |
| testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)), |
| android::hardware::PrintInstanceNameToString); |