DDL UI Filter

This guide covers creating a simple ADTF UI Filter that visualizes the DDL content of a connected Sample Stream as a light version of Qt Media Description Display. After reading this guide, you will know how to:

How to create a UI Filter?

First, create a new Filter project for the data generator Filter using CMake.


cmake_minimum_required (VERSION 3.10)
project (DDLUIFilter)

set (DDL_UI_FILTER tutorial_filter_ui)

if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tutorial_filter_ui.h)
  file(WRITE tutorial_filter_ui.h)
endif()
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tutorial_filter_ui.cpp)
  file(WRITE tutorial_filter_ui.cpp)
endif()

find_package(ADTF COMPONENTS filtersdk ui)

# Adds the tutorial_filter_ui project to the Visual Studio solution, which when build
# creates a shared object called tutorial_filter_ui.adtfplugin
adtf_add_filter(${DDL_UI_FILTER} tutorial_filter_ui.h tutorial_filter_ui.cpp)

target_link_libraries(${DDL_UI_FILTER} PUBLIC adtf::ui Qt5::Widgets)

# Adds the INSTALL project to the Visual Studio solution, which when build
# copies our Filter to the subdirectory given as the second argument into ${CMAKE_INSTALL_PREFIX}
adtf_install_filter(${DDL_UI_FILTER} src/examples/bin)

# Generate a plugindescription for our Filter
adtf_create_plugindescription(
    TARGET ${DDL_UI_FILTER}
    PLUGIN_SUBDIR "src/examples/bin"
    VERSION "0.8.15"
    LICENSE "ADTF"
    SUPPORT_MAIL "support@mycompany.org"
    HOMEPAGE_URL "www.mycompany.org"
)

# Generate a documentation for our Filter
adtf_convert_plugindescription_to_dox(
    TARGET ${DDL_UI_FILTER}
    DIRECTORY ${CMAKE_BINARY_DIR}/src/doxygen/generated
)

    

The header file of the Filter

Just like before we continue by creating our tutorial_filter_ui.h header file.

            
/**
 *
 * @file
 * Copyright © Audi Electronics Venture GmbH. All rights reserved
 *
 */

/*
 * This file depends on Qt which is licensed under LGPLv3.
 * See ADTF_DIR/3rdparty/qt5 and doc/license for detailed information.
 */

#pragma once
#include <QtWidgets>

#include <adtffiltersdk/adtf_filtersdk.h>
#include <adtfmediadescription/codec_sample_streamer.h>
// always include filtersdk, systemsdk or streaming3 sdk BEFORE adtfui!!
#include <adtf_ui.h>

using namespace adtf::ucom;
using namespace adtf::streaming;
using namespace adtf::filter;
using namespace adtf::mediadescription;
using namespace adtf::ui;

// this is a helper that handles a single input that creates and updates
// the corresponding subtree
class cInput
{
public:
    cInput(decoding_sample_reader<cSingleSampleReader>* pReader);
    QTreeWidgetItem* GetRootItem();
    tVoid Update();

private:
    tVoid IndicateNoDescription();
    tResult TypeChanged(const iobject_ptr<const IStreamType>& pType);
    tVoid AddElement(const QString& strName, QTreeWidgetItem* pParent);
    tVoid ClearItems();

    decoding_sample_reader<cSingleSampleReader>* m_pReader = nullptr;
    object_ptr<const ISample> m_pLastSample;
    QTreeWidgetItem* m_pRootItem = nullptr;
    std::vector<QTreeWidgetItem*> m_oElementItems;
};

class cQtMediaDescFilter : public adtf::ui::cQtUIDynamicFilter
{
    public:
        ADTF_CLASS_ID_NAME(cQtMediaDescFilter,
                           "tutorial_filter_ui.ui_filter.adtf.cid",
                           "DDL UI Filter");

        ADTF_CLASS_DEPENDENCIES(REQUIRE_INTERFACE(adtf::ui::IQtXSystem));

    public:
        cQtMediaDescFilter();

        tResult RequestDynamicInputPin(const tChar* strName,
                                       const adtf::ucom::ant::iobject_ptr<const adtf::streaming::ant::IStreamType>& pType) override;

    protected: // Implement cBaseQtFilter
        QWidget* CreateView() override;
        tVoid ReleaseView() override;
        tResult OnTimer() override;

    private:
        std::vector<std::unique_ptr<cInput>> m_oInputs;
        QTreeWidget* m_pTree = nullptr;
};

            
          

The source file of the Filter

Now we need to write the tutorial_filter_ui.cpp source file.

            
/**
 *
 * @file
 * Copyright &copy; Audi Electronics Venture GmbH. All rights reserved
 *
 */

/*
 * This file depends on Qt which is licensed under LGPLv3.
 * See ADTF_DIR/3rdparty/qt5 and doc/license for detailed information.
 */

#include "tutorial_filter_ui.h"

ADTF_PLUGIN("DDL UI Filter Plugin", cQtMediaDescFilter)

cQtMediaDescFilter::cQtMediaDescFilter():
    adtf::ui::cQtUIDynamicFilter()
{
    // sets a short description for the component
    SetDescription("Use this filter to visualize the sampla data of a received input.");
}

// this is called for each dynamic input configured by the user
tResult cQtMediaDescFilter::RequestDynamicInputPin(const tChar* strName,
                                                   const iobject_ptr<const IStreamType>& pType)
{
    // note that we disable data in triggers, as we are going to read samples asynchronously from
    // within the UI thread during the OnTimer() call.
    auto pReader = CreateInputPin<decoding_sample_reader<cSingleSampleReader>>(strName, pType, tFalse);
    m_oInputs.push_back(std::make_unique<cInput>(pReader));
    RETURN_NOERROR;
}

QWidget* cQtMediaDescFilter::CreateView()
{
    // our view is quite simple and contains only the tree widget
    m_pTree = new QTreeWidget(nullptr);
    m_pTree->setObjectName("tutorial_filter_ui");
    m_pTree->setSelectionMode(QAbstractItemView::SingleSelection);
    m_pTree->setAlternatingRowColors(true);
    m_pTree->setHeaderHidden(true);
    m_pTree->setHeaderLabels(QStringList{"Name", "Value"});
    m_pTree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);

    for (auto& oInput: m_oInputs)
    {
        m_pTree->addTopLevelItem(oInput->GetRootItem());
    }

    return m_pTree;
}

tVoid cQtMediaDescFilter::ReleaseView()
{
    // our widget will be deleted automatically when the parent window is destroyed.
    // this method can be used to release some additional resources needed by the ui.
}

tResult cQtMediaDescFilter::OnTimer()
{
    RETURN_IF_FAILED(cQtUIDynamicFilter::OnTimer());

    m_pTree->setUpdatesEnabled(false);

    for (auto& oInput: m_oInputs)
    {
        oInput->Update();
    }

    m_pTree->setUpdatesEnabled(true);

    RETURN_NOERROR;
}


cInput::cInput(decoding_sample_reader<cSingleSampleReader>* pReader):
    m_pReader(pReader),
    m_pRootItem(new QTreeWidgetItem)
{
    QString strName;
    pReader->GetName(adtf_string_intf(strName));
    m_pRootItem->setText(0, strName);

    // we register a callback in order to recreate the tree nodes whenever the stream type changes
    m_pReader->SetAcceptTypeCallback(std::bind(&cInput::TypeChanged, this, std::placeholders::_1));
    IndicateNoDescription();
}

QTreeWidgetItem* cInput::GetRootItem()
{
    return m_pRootItem;
}

tVoid cInput::Update()
{
    // as an optimization we first check if there is a new sample
    object_ptr<const ISample> pCurrentSample;
    if (IS_OK(m_pReader->GetLastSample(pCurrentSample)))
    {
        if (pCurrentSample == m_pLastSample)
        {
            // nothing to do in this case, data stays the same
            return;
        }

        m_pLastSample = pCurrentSample;
    }

    // get a decoder for the last sample
    cSampleDecoder oDecoder;
    if (!m_pReader->GetLastDecoder(oDecoder))
    {
        return;
    }

    // and update all elements
    for (tSize nElementIndex = 0; nElementIndex < m_pReader->GetStaticElementCount(); ++nElementIndex)
    {
        m_oElementItems.at(nElementIndex)->setText(1, oDecoder.GetElementValueString(nElementIndex).GetPtr());
    }
}

tVoid cInput::IndicateNoDescription()
{
    m_oElementItems.clear();
    ClearItems();
    m_pRootItem->setText(1, "no description available (yet)");
}

// this is called during the GetLastSample() call in Update(), so we are fine to
// update the qt tree from within this method
tResult cInput::TypeChanged(const iobject_ptr<const IStreamType>& /*pType*/)
{
    m_oElementItems.clear();
    ClearItems();
    m_pRootItem->setText(1, "");

    // Currently we can only display the static elements of a structure
    for (tSize nElementIndex = 0; nElementIndex < m_pReader->GetStaticElementCount(); ++nElementIndex)
    {
        AddElement(m_pReader->GetStaticElement(nElementIndex).strName.GetPtr(), m_pRootItem);
    }

    RETURN_NOERROR;
}

tVoid cInput::ClearItems()
{
    while (m_pRootItem->childCount())
    {
        delete m_pRootItem->takeChild(0);
    }
}

tVoid cInput::AddElement(const QString& strName, QTreeWidgetItem* pParent)
{
    auto nDotPosition = strName.indexOf('.');
    if (nDotPosition == -1)
    {
        auto pLeaf = new QTreeWidgetItem(QStringList{strName});
        pParent->addChild(pLeaf);
        pParent->setExpanded(true);

        // update the mapping from elements to tree items
        m_oElementItems.push_back(pLeaf);
    }
    else
    {
        auto strIntermediateName = strName.left(nDotPosition);
        QTreeWidgetItem* pIntermediateNode = nullptr;

        // first check if the node already exists.
        // note that this introduces a complexity of O(n^2), but this is not a perfomance critical part.
        for (int nChildIndex = 0; nChildIndex < pParent->childCount(); ++nChildIndex)
        {
            auto pChild = pParent->child(nChildIndex);
            if (pChild->text(0) == strIntermediateName)
            {
                pIntermediateNode = pChild;
                break;
            }
        }

        // create a new one if it does not
        if (!pIntermediateNode)
        {
            pIntermediateNode = new QTreeWidgetItem(QStringList{strIntermediateName});
            pParent->addChild(pIntermediateNode);
            pParent->setExpanded(true);
        }

        // and continue the recursion
        AddElement(strName.mid(nDotPosition + 1), pIntermediateNode);
    }
}

            
          

Where to go next?

You want to extract data from an .adtfdat recording ? No problem - the ADTF File Library provides a dat processing library to create own adtffileplugins. Let's have a look at ADTF DAT Tool Processer how to extract images from a video stream.