This guide covers creating a simple ADTF UI Filter that visualizes the DDL content of a connected Sample Stream as a light version of Qt5 Media Description Display. After reading this guide, you will know how to:
First, create a new Filter project for the data generator Filter using CMake.
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
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
)
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();
void Update();
private:
void IndicateNoDescription();
tResult TypeChanged(const iobject_ptr<const IStreamType>& pType);
void AddElement(const QString& strName, QTreeWidgetItem* pParent);
void 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 char* strName,
const adtf::ucom::ant::iobject_ptr<const adtf::streaming::ant::IStreamType>& pType) override;
protected: // Implement cBaseQtFilter
QWidget* CreateView() override;
void ReleaseView() override;
tResult OnTimer() override;
private:
std::vector<std::unique_ptr<cInput>> m_oInputs;
QTreeWidget* m_pTree = nullptr;
};
Now we need to write the tutorial_filter_ui.cpp source 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.
*/
#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("This filter shows the usage and visualization of DDL.");
}
// this is called for each dynamic input configured by the user
tResult cQtMediaDescFilter::RequestDynamicInputPin(const char* 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 GUI thread during the OnTimer() call.
auto pReader = CreateInputPin<decoding_sample_reader<cSingleSampleReader>>(strName, pType, false);
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;
}
void 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;
}
void 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
// leaf elements are non structured elements with POD type.
size_t nElementIndex = 0;
for_each_leaf_element(oDecoder.GetElements(),
[this, &nElementIndex](const auto& oElement) {
m_oElementItems.at(nElementIndex)->setText(1, oElement.getStringValue().c_str());
});
}
void 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_each_element(m_pReader->GetElements(),
[this](const auto& oElement) {
AddElement(oElement.getFullName().c_str(), m_pRootItem);
});
RETURN_NOERROR;
}
void cInput::ClearItems()
{
while (m_pRootItem->childCount())
{
delete m_pRootItem->takeChild(0);
}
}
void 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);
}
}
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.