Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1586d21f authored by Patrick Rohr's avatar Patrick Rohr
Browse files

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
parent 5d29ada4
Loading
Loading
Loading
Loading
+14 −12
Original line number Diff line number Diff line
@@ -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 @@ using ::aidl::android::hardware::common::NativeHandle;
    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() {
+11 −2
Original line number Diff line number Diff line
@@ -60,9 +60,18 @@ using MQDesc = MQDescriptor<int8_t, SynchronizedReadWrite>;

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 @@ class FilterCallback : public BnFilterCallback {
    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;

+113 −25
Original line number Diff line number Diff line
@@ -640,10 +640,51 @@ TEST_P(TunerFilterAidlTest, testTimeFilter) {
    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 @@ TEST_P(TunerFilterAidlTest, FilterTimeDelayHintTest) {
    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);
    auto timeDelayInMs = std::chrono::milliseconds(filterConf.timeDelayInMs);
    if (timeDelayInMs.count() > 0) {
        FilterDelayHint delayHint;
        delayHint.hintType = FilterDelayHintType::TIME_DELAY_IN_MS;
    delayHint.hintValue = delayValue.count();
        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;

        // setDelayHint should fail for media filters.
        ASSERT_EQ(filter->setDelayHint(delayHint).isOk(), !mediaFilter);
    }

    // start and stop filter in order to circumvent callback scheduler race
    // conditions after adjusting filter delays.
    mFilterTests.startFilter(filterId);
    mFilterTests.stopFilter(filterId);

    if (!mediaFilter) {
        auto cb = mFilterTests.getFilterCallbacks().at(filterId);
    auto future = cb->getNextFilterEventWithTag(DemuxFilterEvent::Tag::ipPayload);
        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.
    ASSERT_EQ(future.wait_for(std::chrono::seconds(10)), std::future_status::ready);
        auto timeout = std::chrono::seconds(30);
        ASSERT_EQ(future.wait_for(timeout), std::future_status::ready);
        auto duration = std::chrono::steady_clock::now() - startTime;
    ASSERT_GE(duration, delayValue);

    // cleanup
        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) {
+1 −0
Original line number Diff line number Diff line
@@ -140,6 +140,7 @@ class TunerFilterAidlTest : public testing::TestWithParam<std::string> {
    void reconfigSingleFilterInDemuxTest(FilterConfig filterConf, FilterConfig filterReconf,
                                         FrontendConfig frontendConf);
    void testTimeFilter(TimeFilterConfig filterConf);
    void testDelayHint(const FilterConfig& filterConf);

    DemuxFilterType getLinkageFilterType(int bit) {
        DemuxFilterType type;
+8 −0
Original line number Diff line number Diff line
@@ -96,6 +96,8 @@ struct FilterConfig {
    AvStreamType streamType;
    int32_t ipCid;
    int32_t monitorEventTypes;
    int timeDelayInMs = 0;
    int dataDelayInBytes = 0;

    bool operator<(const FilterConfig& /*c*/) const { return false; }
};
@@ -336,6 +338,12 @@ struct TunerTestingConfigAidlReader1_0 {
                if (filterConfig.hasMonitorEventTypes()) {
                    filterMap[id].monitorEventTypes = (int32_t)filterConfig.getMonitorEventTypes();
                }
                if (filterConfig.hasTimeDelayInMs()) {
                    filterMap[id].timeDelayInMs = filterConfig.getTimeDelayInMs();
                }
                if (filterConfig.hasDataDelayInBytes()) {
                    filterMap[id].dataDelayInBytes = filterConfig.getDataDelayInBytes();
                }
                if (filterConfig.hasAvFilterSettings_optional()) {
                    auto av = filterConfig.getFirstAvFilterSettings_optional();
                    if (av->hasAudioStreamType_optional()) {
Loading