Add VTS for FilterDelayHint
The VTS can be configured by adding timeDelayInMs and dataDelayInBytes
attributes to the VTS configuration's filter tags (as long as the filter
is not a media filter (media filters do not support FilterDelayHints)).
In order to circumvent a FilterDelayHint race condition around
configuring the hint (where the internal condition variable is notified
when the delay hint changes. If the scheduler thread has not been
scheduled to run before adjusting the delay hint, and callbacks are
added right after, they are always sent out immediately (as the cv predicate
still returns true when it is first evaluated after adjusting the delay)).
Test: atest VtsHalTvTunerTargetTest
Test: atest android.media.tv.tuner.cts
Bug: 183057734
CTS-Coverage-Bug: 209593343
Change-Id: I1b0893afce262b18ee385ec0f384e6ceebf58c7d
diff --git a/tv/tuner/aidl/vts/functional/FilterTests.cpp b/tv/tuner/aidl/vts/functional/FilterTests.cpp
index a5acdc1..53afef7 100644
--- a/tv/tuner/aidl/vts/functional/FilterTests.cpp
+++ b/tv/tuner/aidl/vts/functional/FilterTests.cpp
@@ -17,6 +17,7 @@
#include "FilterTests.h"
#include <inttypes.h>
+#include <algorithm>
#include <aidl/android/hardware/tv/tuner/DemuxFilterMonitorEventType.h>
#include <aidlcommonsupport/NativeHandle.h>
@@ -31,23 +32,24 @@
mPidFilterOutputCount++;
mMsgCondition.signal();
- // HACK: we need to cast the const away as DemuxFilterEvent contains a ScopedFileDescriptor
- // that cannot be copied.
- for (auto&& e : const_cast<std::vector<DemuxFilterEvent>&>(events)) {
- auto it = mFilterEventPromises.find(e.getTag());
- if (it != mFilterEventPromises.cend()) {
- it->second.set_value(std::move(e));
- mFilterEventPromises.erase(it);
+ for (auto it = mFilterCallbackVerifiers.begin(); it != mFilterCallbackVerifiers.end();) {
+ auto& [verifier, promise] = *it;
+ if (verifier(events)) {
+ promise.set_value();
+ it = mFilterCallbackVerifiers.erase(it);
+ } else {
+ ++it;
}
- }
+ };
return ::ndk::ScopedAStatus::ok();
}
-std::future<DemuxFilterEvent> FilterCallback::getNextFilterEventWithTag(DemuxFilterEvent::Tag tag) {
- // Note: this currently only supports one future per DemuxFilterEvent::Tag.
- mFilterEventPromises[tag] = std::promise<DemuxFilterEvent>();
- return mFilterEventPromises[tag].get_future();
+std::future<void> FilterCallback::verifyFilterCallback(FilterCallbackVerifier&& verifier) {
+ std::promise<void> promise;
+ auto future = promise.get_future();
+ mFilterCallbackVerifiers.emplace_back(std::move(verifier), std::move(promise));
+ return future;
}
void FilterCallback::testFilterDataOutput() {
diff --git a/tv/tuner/aidl/vts/functional/FilterTests.h b/tv/tuner/aidl/vts/functional/FilterTests.h
index 6258bac..f579441 100644
--- a/tv/tuner/aidl/vts/functional/FilterTests.h
+++ b/tv/tuner/aidl/vts/functional/FilterTests.h
@@ -60,9 +60,18 @@
class FilterCallback : public BnFilterCallback {
public:
+ /**
+ * A FilterCallbackVerifier is used to test and verify filter callbacks.
+ * The function should return true when a callback has been handled by this
+ * filter verifier. This will cause the associated future to be unblocked.
+ * If the function returns false, we continue to wait for future callbacks
+ * (the future remains blocked).
+ */
+ using FilterCallbackVerifier = std::function<bool(const std::vector<DemuxFilterEvent>&)>;
+
virtual ::ndk::ScopedAStatus onFilterEvent(const vector<DemuxFilterEvent>& events) override;
- std::future<DemuxFilterEvent> getNextFilterEventWithTag(DemuxFilterEvent::Tag tag);
+ std::future<void> verifyFilterCallback(FilterCallbackVerifier&& verifier);
virtual ::ndk::ScopedAStatus onFilterStatus(const DemuxFilterStatus /*status*/) override {
return ::ndk::ScopedAStatus::ok();
@@ -85,7 +94,7 @@
int32_t mFilterId;
std::shared_ptr<IFilter> mFilter;
- std::unordered_map<DemuxFilterEvent::Tag, std::promise<DemuxFilterEvent>> mFilterEventPromises;
+ std::vector<std::pair<FilterCallbackVerifier, std::promise<void>>> mFilterCallbackVerifiers;
native_handle_t* mAvSharedHandle = nullptr;
uint64_t mAvSharedMemSize = -1;
diff --git a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
index 88890e4..89e42df 100644
--- a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
+++ b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
@@ -640,10 +640,51 @@
testTimeFilter(timeFilterMap[timeFilter.timeFilterId]);
}
-// TODO: move boilerplate into text fixture
-TEST_P(TunerFilterAidlTest, FilterTimeDelayHintTest) {
- description("Test filter delay hint.");
+static bool isMediaFilter(const FilterConfig& filterConfig) {
+ switch (filterConfig.type.mainType) {
+ case DemuxFilterMainType::TS: {
+ // TS Audio and Video filters are media filters
+ auto tsFilterType =
+ filterConfig.type.subType.get<DemuxFilterSubType::Tag::tsFilterType>();
+ return (tsFilterType == DemuxTsFilterType::AUDIO ||
+ tsFilterType == DemuxTsFilterType::VIDEO);
+ }
+ case DemuxFilterMainType::MMTP: {
+ // MMTP Audio and Video filters are media filters
+ auto mmtpFilterType =
+ filterConfig.type.subType.get<DemuxFilterSubType::Tag::mmtpFilterType>();
+ return (mmtpFilterType == DemuxMmtpFilterType::AUDIO ||
+ mmtpFilterType == DemuxMmtpFilterType::VIDEO);
+ }
+ default:
+ return false;
+ }
+}
+static int getDemuxFilterEventDataLength(const DemuxFilterEvent& event) {
+ switch (event.getTag()) {
+ case DemuxFilterEvent::Tag::section:
+ return event.get<DemuxFilterEvent::Tag::section>().dataLength;
+ case DemuxFilterEvent::Tag::media:
+ return event.get<DemuxFilterEvent::Tag::media>().dataLength;
+ case DemuxFilterEvent::Tag::pes:
+ return event.get<DemuxFilterEvent::Tag::pes>().dataLength;
+ case DemuxFilterEvent::Tag::download:
+ return event.get<DemuxFilterEvent::Tag::download>().dataLength;
+ case DemuxFilterEvent::Tag::ipPayload:
+ return event.get<DemuxFilterEvent::Tag::ipPayload>().dataLength;
+
+ case DemuxFilterEvent::Tag::tsRecord:
+ case DemuxFilterEvent::Tag::mmtpRecord:
+ case DemuxFilterEvent::Tag::temi:
+ case DemuxFilterEvent::Tag::monitorEvent:
+ case DemuxFilterEvent::Tag::startId:
+ return 0;
+ }
+}
+
+// TODO: move boilerplate into text fixture
+void TunerFilterAidlTest::testDelayHint(const FilterConfig& filterConf) {
int32_t feId;
int32_t demuxId;
std::shared_ptr<IDemux> demux;
@@ -657,40 +698,87 @@
ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
mFilterTests.setDemux(demux);
- const auto& filterConf = filterMap[live.ipFilterId];
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId_64bit(filterId));
- ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.configIpFilterCid(filterConf.ipCid, filterId));
+ bool mediaFilter = isMediaFilter(filterConf);
auto filter = mFilterTests.getFilterById(filterId);
- auto delayValue = std::chrono::milliseconds(5000);
- FilterDelayHint delayHint;
- delayHint.hintType = FilterDelayHintType::TIME_DELAY_IN_MS;
- delayHint.hintValue = delayValue.count();
+ auto timeDelayInMs = std::chrono::milliseconds(filterConf.timeDelayInMs);
+ if (timeDelayInMs.count() > 0) {
+ FilterDelayHint delayHint;
+ delayHint.hintType = FilterDelayHintType::TIME_DELAY_IN_MS;
+ delayHint.hintValue = timeDelayInMs.count();
- auto status = filter->setDelayHint(delayHint);
- ASSERT_TRUE(status.isOk());
+ // setDelayHint should fail for media filters.
+ ASSERT_EQ(filter->setDelayHint(delayHint).isOk(), !mediaFilter);
+ }
- auto startTime = std::chrono::steady_clock::now();
- ASSERT_TRUE(mFilterTests.startFilter(filterId));
+ int dataDelayInBytes = filterConf.dataDelayInBytes;
+ if (dataDelayInBytes > 0) {
+ FilterDelayHint delayHint;
+ delayHint.hintType = FilterDelayHintType::DATA_SIZE_DELAY_IN_BYTES;
+ delayHint.hintValue = dataDelayInBytes;
- auto cb = mFilterTests.getFilterCallbacks().at(filterId);
- auto future = cb->getNextFilterEventWithTag(DemuxFilterEvent::Tag::ipPayload);
+ // setDelayHint should fail for media filters.
+ ASSERT_EQ(filter->setDelayHint(delayHint).isOk(), !mediaFilter);
+ }
- // block and wait for callback to be received.
- ASSERT_EQ(future.wait_for(std::chrono::seconds(10)), std::future_status::ready);
- auto duration = std::chrono::steady_clock::now() - startTime;
- ASSERT_GE(duration, delayValue);
+ // start and stop filter in order to circumvent callback scheduler race
+ // conditions after adjusting filter delays.
+ mFilterTests.startFilter(filterId);
+ mFilterTests.stopFilter(filterId);
- // cleanup
- ASSERT_TRUE(mFilterTests.stopFilter(filterId));
+ if (!mediaFilter) {
+ auto cb = mFilterTests.getFilterCallbacks().at(filterId);
+ int callbackSize = 0;
+ auto future = cb->verifyFilterCallback(
+ [&callbackSize](const std::vector<DemuxFilterEvent>& events) {
+ for (const auto& event : events) {
+ callbackSize += getDemuxFilterEventDataLength(event);
+ }
+ return true;
+ });
+
+ // The configure stage can also produce events, so we should set the delay
+ // hint beforehand.
+ ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
+
+ auto startTime = std::chrono::steady_clock::now();
+ ASSERT_TRUE(mFilterTests.startFilter(filterId));
+
+ // block and wait for callback to be received.
+ auto timeout = std::chrono::seconds(30);
+ ASSERT_EQ(future.wait_for(timeout), std::future_status::ready);
+ auto duration = std::chrono::steady_clock::now() - startTime;
+
+ bool delayHintTest = duration >= timeDelayInMs;
+ bool dataSizeTest = callbackSize >= dataDelayInBytes;
+
+ if (timeDelayInMs.count() > 0 && dataDelayInBytes > 0) {
+ ASSERT_TRUE(delayHintTest || dataSizeTest);
+ } else {
+ // if only one of time delay / data delay is configured, one of them
+ // holds true by default, so we want both assertions to be true.
+ ASSERT_TRUE(delayHintTest && dataSizeTest);
+ }
+
+ ASSERT_TRUE(mFilterTests.stopFilter(filterId));
+ }
+
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
ASSERT_TRUE(mDemuxTests.closeDemux());
ASSERT_TRUE(mFrontendTests.closeFrontend());
}
+TEST_P(TunerFilterAidlTest, FilterDelayHintTest) {
+ description("Test filter time delay hint.");
+
+ for (const auto& obj : filterMap) {
+ testDelayHint(obj.second);
+ }
+}
+
TEST_P(TunerPlaybackAidlTest, PlaybackDataFlowWithTsSectionFilterTest) {
description("Feed ts data from playback and configure Ts section filter to get output");
if (!playback.support || playback.sectionFilterId.compare(emptyHardwareId) == 0) {
diff --git a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.h b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.h
index 13c5a80..7f80d90 100644
--- a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.h
+++ b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.h
@@ -140,6 +140,7 @@
void reconfigSingleFilterInDemuxTest(FilterConfig filterConf, FilterConfig filterReconf,
FrontendConfig frontendConf);
void testTimeFilter(TimeFilterConfig filterConf);
+ void testDelayHint(const FilterConfig& filterConf);
DemuxFilterType getLinkageFilterType(int bit) {
DemuxFilterType type;