Files
qt5-legatus-legacy/Lts/Services/AudioVideoManagement/UnitTests/audio_video_handler_impl_test.cpp
T
2026-08-13 18:33:10 +02:00

603 lines
21 KiB
C++
Executable File

/**
* @RDE_HEADER@
* AudioVideoHandlerImplTest
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Fröbel, NLSE, 2018-01-09
*/
#include "audio_video_handler_impl_test.h"
#include <AudioVideoManagement/audio_video_handler_impl.h>
#include <AudioVideoManagement/asset.h>
#include <AudioVideoManagement/mounting_point.h>
#include <AudioVideoManagement/exercise.h>
#include <AudioVideoManagement/avm.hpp>
#include <InfrastructureManagement/ism.hpp>
#include <LtsUtilsCore/json_object.h>
#include <QtGlobal>
#include <boost/optional.hpp>
#include <map>
#include <string>
#include <vector>
#include <utility>
#include <memory>
using namespace AudioVideoManagement;
using namespace InfrastructureManagement;
using LtsUtils::JsonObject;
namespace
{
int streamCount(const Streams &streams)
{
if (streams.second)
return 2;
return 1;
}
class DbMock : public AudioVideoHandlerImpl::IDbProxy
{
public:
const Streams &findStreams(AssetId id) const override
{
auto found = m_cache.find(id);
Q_ASSERT(found != m_cache.end());
return found->second;
}
const Streams &findStreams(Asset *asset) override
{
Q_ASSERT(asset);
return findStreams(asset->data().id());
}
bool isEmpty() const { return m_cache.empty(); }
int deviceCount() const { return m_cache.size(); }
int streamCount() const
{
int count = 0;
for (const auto &streams: m_cache)
count += ::streamCount(streams.second);
return count;
}
void insert(Streams &streams) override
{
streams.first.id = ++m_nextId;
if (streams.second)
streams.second->id = ++m_nextId;
link(streams);
auto insertResult = m_cache.insert(std::make_pair(streams.assetId(), streams));
Q_ASSERT(insertResult.second);
}
/**
* We simply maintain the ids or give a new id to a possibly new entry.
*/
void update(Streams &newStreams) override
{
auto found = m_cache.find(newStreams.assetId());
Q_ASSERT(found != m_cache.end());
const Streams oldStreams = found->second;
newStreams.first.id = oldStreams.first.id;
if (newStreams.second)
{
if (oldStreams.second)
newStreams.second->id = oldStreams.second->id;
else
newStreams.second->id = ++m_nextId;
}
link(newStreams);
found->second = newStreams; // update cache
}
void remove(Asset *asset) override
{
Q_ASSERT(asset);
auto count = m_cache.erase(asset->data().id());
Q_ASSERT(count == 1);
}
private:
void link(Streams& streams)
{
if (!streams.second)
{
streams.first.link = 0;
return;
}
streams.first.link = streams.second->id;
streams.second->link = streams.first.id;
}
int m_nextId{0};
std::map<AssetId, Streams> m_cache;
};
class DtsSpy : public AudioVideoHandlerImpl::IDtsProxy
{
public:
void publish(const Stream &stream) override
{
m_published.push_back(stream);
}
void remove(uint64_t streamId) override
{
m_removed.push_back(streamId);
}
bool isEmpty() const { return m_published.empty() && m_removed.empty(); }
void reset()
{
m_published.clear();
m_removed.clear();
}
std::vector<Stream> m_published;
std::vector<uint64_t> m_removed;
};
Asset *createAsset(int id, MountingPointType mpType, const std::string &properties)
{
InstrumentationAsset data;
data.id(id);
data.name(QStringLiteral("ASSET_000%1").arg(id).toUtf8().constData());
data.hostname(QStringLiteral("host_000%1").arg(id).toUtf8().constData());
data.mountingPointTypeId(mpType);
data.properties(properties);
return new Asset(data);
}
MountingPoint *createMountingPoint(int id, MountingPointType mpType)
{
StructureElement data;
data.id(id);
data.name(QStringLiteral("MP_000%1").arg(id).toUtf8().constData());
data.displayName(QStringLiteral("display name %1").arg(id).toUtf8().constData());
data.structureElementTypeId(StructureElementType::MountingPoint);
data.mountingPointTypeId(mpType);
data.properties("{}");
return new MountingPoint(data);
}
void setProperties(Asset *asset, const std::string &newProperties)
{
Q_ASSERT(asset);
InstrumentationAsset data = asset->data();
data.properties(newProperties);
asset->update(data);
}
void setDisplayName(MountingPoint *mp, const std::string &newDisplayName)
{
Q_ASSERT(mp);
StructureElement data = mp->data();
data.displayName(newDisplayName);
mp->update(data);
}
void setRecording(MountingPoint *mp, bool on)
{
Q_ASSERT(mp);
StructureElement data = mp->data();
if (on)
data.properties(R"({"record": true})");
else
data.properties(R"({"record": false})");
mp->update(data);
}
void mount(Asset *asset, MountingPoint *mp)
{
Q_ASSERT(asset);
InstrumentationAsset data = asset->data();
data.assetStateId(InstrumentationAssetState::Mounted);
asset->update(data);
asset->mount(mp);
}
Database::AvmDeviceConfig dcEntry(int64_t id, const std::string &context, const std::string &type, const std::string &name, const std::string &controller, const std::string &definition, int32_t link)
{
Database::AvmDeviceConfig dc;
dc.id = id;
dc.context = QString::fromStdString(context);
dc.type = QString::fromStdString(type);
dc.name = QString::fromStdString(name);
dc.controller = QString::fromStdString(controller);
dc.definition = QString::fromStdString(definition);
dc.link = link;
return dc;
}
bool operator==(const Database::AvmDeviceConfig &lhs, const Database::AvmDeviceConfig &rhs)
{
if (lhs.id != rhs.id)
return false;
if (lhs.context != rhs.context)
return false;
if (lhs.type != rhs.type)
return false;
if (lhs.name != rhs.name)
return false;
if (lhs.controller != rhs.controller)
return false;
if (lhs.definition != rhs.definition)
return false;
if (lhs.link != rhs.link)
return false;
return true;
}
} // anonymous namespace
namespace AudioVideoManagement
{
AudioVideoHandlerImplTest::AudioVideoHandlerImplTest() = default;
AudioVideoHandlerImplTest::~AudioVideoHandlerImplTest() = default;
/**
* Create Assets and MountingPoints.
* They will be mounted already but left in the pool exercise.
*/
void AudioVideoHandlerImplTest::init()
{
m_nextAssetId = 0;
m_nextMpId = 0;
m_pool.reset(new Exercise(ExerciseContext::defaultContext()));
m_exercise.reset(new Exercise(ExerciseContext(42)));
m_indoorCam = createAsset(++m_nextAssetId, MountingPointType::IndoorCamera, R"({"type": "vapix"})");
m_outdoorCam = createAsset(++m_nextAssetId, MountingPointType::OutdoorCamera, R"({"type": "vapix"})");
m_announcementUnit = createAsset(++m_nextAssetId, MountingPointType::AnnouncementUnit, R"({"type": "vapix"})");
m_mpIndoor = createMountingPoint(++m_nextMpId, MountingPointType::IndoorCamera);
m_mpOutdoor = createMountingPoint(++m_nextMpId, MountingPointType::OutdoorCamera);
m_mpAnnouncementUnit = createMountingPoint(++m_nextMpId, MountingPointType::AnnouncementUnit);
mount(m_indoorCam, m_mpIndoor);
mount(m_outdoorCam, m_mpOutdoor);
mount(m_announcementUnit, m_mpAnnouncementUnit);
m_pool->add(std::unique_ptr<Asset>(m_indoorCam));
m_pool->add(std::unique_ptr<Asset>(m_outdoorCam));
m_pool->add(std::unique_ptr<Asset>(m_announcementUnit));
m_pool->add(std::unique_ptr<MountingPoint>(m_mpIndoor));
m_pool->add(std::unique_ptr<MountingPoint>(m_mpOutdoor));
m_pool->add(std::unique_ptr<MountingPoint>(m_mpAnnouncementUnit));
}
void AudioVideoHandlerImplTest::cleanup()
{
m_pool.reset();
m_exercise.reset();
}
/**
* Assign a camera to an Exercise that neither has microphone nor speaker.
*/
void AudioVideoHandlerImplTest::testAssignCameraVideo()
{
DbMock dbMock;
DtsSpy dtsSpy;
AudioVideoHandlerImpl handler(m_exercise.get(), &dbMock, &dtsSpy);
handler.onContextActivated();
QVERIFY(dbMock.isEmpty());
QVERIFY(dtsSpy.isEmpty());
m_pool->moveMountedTo(m_indoorCam, m_exercise.get(), boost::none);
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 1);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 1);
QVERIFY(dtsSpy.m_removed.empty());
// verify DB
const Streams &streams = dbMock.findStreams(m_indoorCam->data().id());
QVERIFY(!streams.second);
QVERIFY(streams.first == dcEntry(1, "ex_42", "AXIS_VIDEO_CHANNEL", "av_0001", "EXCAVS01", "url=rtsp://root:root@host_0001:554;mode=1;", 0));
// verify DDS
QCOMPARE(dtsSpy.m_published[0], Stream(1, "av_0001", StreamType::Video, m_mpIndoor->data().id()));
}
/**
* Assign a Camera to an Exercise that has a microphone.
*/
void AudioVideoHandlerImplTest::testAssignCameraVideoAudioIn()
{
testAssignCameraVideoAudio(R"({"type": "vapix", "hasMicrophone": true})", StreamType::AudioIn);
}
/**
* Assign a Camera to an Exercise that has a speaker.
*/
void AudioVideoHandlerImplTest::testAssignCameraVideoAudioOut()
{
testAssignCameraVideoAudio(R"({"type": "vapix", "hasSpeaker": true})", StreamType::AudioOut);
}
/**
* Assign a Camera to an Exercise that has both, microphone and speaker.
*/
void AudioVideoHandlerImplTest::testAssignCameraVideoAudioInOut()
{
testAssignCameraVideoAudio(R"({"type": "vapix", "hasMicrophone": true, "hasSpeaker": true})", StreamType::AudioInOut);
}
void AudioVideoHandlerImplTest::testAssignCameraVideoAudio(const std::string &camProperties, StreamType expectedStreamType)
{
DbMock dbMock;
DtsSpy dtsSpy;
AudioVideoHandlerImpl handler(m_exercise.get(), &dbMock, &dtsSpy);
// Change the Asset's properties before activating the context.
setProperties(m_outdoorCam, camProperties);
handler.onContextActivated();
m_pool->moveMountedTo(m_outdoorCam, m_exercise.get(), boost::none);
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 2);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 2);
QVERIFY(dtsSpy.m_removed.empty());
// verfiy DB
const Streams &streams = dbMock.findStreams(m_outdoorCam->data().id());
QVERIFY(streams.second);
QVERIFY(streams.first == dcEntry(1, "ex_42", "AXIS_VIDEO_CHANNEL", "av_0002", "EXCAVS01", "url=rtsp://root:root@host_0002:554;mode=1;", 2));
QVERIFY(*streams.second == dcEntry(2, "ex_42", "AXIS_AUDIO_CHANNEL", "av_0002", "EXCAVS01", "url=http://root:root@host_0002:80;mode=1;", 1));
// verify DDS
const Stream &videoStream = dtsSpy.m_published[0];
QCOMPARE(videoStream, Stream(1, "av_0002", StreamType::Video, m_mpOutdoor->data().id()));
const Stream &audioStream = dtsSpy.m_published[1];
QCOMPARE(audioStream, Stream(2, "av_0002", expectedStreamType, m_mpOutdoor->data().id()));
}
/**
* Assign an AnnouncementUnit to an exercise.
*/
void AudioVideoHandlerImplTest::testAssignAnnouncementUnit()
{
DbMock dbMock;
DtsSpy dtsSpy;
AudioVideoHandlerImpl handler(m_exercise.get(), &dbMock, &dtsSpy);
handler.onContextActivated();
m_pool->moveMountedTo(m_announcementUnit, m_exercise.get(), boost::none);
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 1);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 1);
QVERIFY(dtsSpy.m_removed.empty());
// verfiy DB
const Streams &streams = dbMock.findStreams(m_announcementUnit->data().id());
QVERIFY(!streams.second);
QVERIFY(streams.first == dcEntry(1, "ex_42", "AXIS_AUDIO_CHANNEL", "av_0003", "EXCAVS01", "url=http://root:root@host_0003:80;codec=aac;mode=1;", 0));
// verify DDS
QCOMPARE(dtsSpy.m_published[0], Stream(1, "av_0003", StreamType::AudioOut, m_mpAnnouncementUnit->data().id()));
}
/**
* In this test we assign Assets to the Exercise *before* the Exercise gets activated.
*/
void AudioVideoHandlerImplTest::testAssignBeforeActivation()
{
DbMock dbMock;
DtsSpy dtsSpy;
AudioVideoHandlerImpl handler(m_exercise.get(), &dbMock, &dtsSpy);
m_pool->moveMountedTo(m_indoorCam, m_exercise.get(), boost::none);
m_pool->moveMountedTo(m_outdoorCam, m_exercise.get(), boost::none);
m_pool->moveMountedTo(m_announcementUnit, m_exercise.get(), boost::none);
QVERIFY(dbMock.isEmpty());
QVERIFY(dtsSpy.isEmpty());
handler.onContextActivated();
QCOMPARE(dbMock.deviceCount(), 3);
QCOMPARE(dbMock.streamCount(), 3);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 3);
}
void AudioVideoHandlerImplTest::testUnassignCameraVideo()
{
DbMock dbMock;
DtsSpy dtsSpy;
AudioVideoHandlerImpl handler(m_exercise.get(), &dbMock, &dtsSpy);
m_pool->moveMountedTo(m_indoorCam, m_exercise.get(), boost::none);
handler.onContextActivated();
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 1);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 1);
QVERIFY(dtsSpy.m_removed.empty());
dtsSpy.reset();
m_exercise->moveMountedTo(m_indoorCam, m_pool.get(), boost::none);
QVERIFY(dbMock.isEmpty());
QVERIFY(dtsSpy.m_published.empty());
QCOMPARE(static_cast<int>(dtsSpy.m_removed.size()), 1);
QCOMPARE(static_cast<int>(dtsSpy.m_removed[0]), 1);
}
/**
* Update a Camera while it is assigned.
*/
void AudioVideoHandlerImplTest::testUpdateRemoveAssignedAsset()
{
DbMock dbMock;
DtsSpy dtsSpy;
AudioVideoHandlerImpl handler(m_exercise.get(), &dbMock, &dtsSpy);
m_pool->moveMountedTo(m_indoorCam, m_exercise.get(), boost::none);
handler.onContextActivated();
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 1);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 1);
QVERIFY(dtsSpy.m_removed.empty());
// add microphone to Camera => one additional audio stream
dtsSpy.reset();
setProperties(m_indoorCam, R"({"type": "vapix", "hasMicrophone": true})");
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 2);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 2); // one updated, one new
QVERIFY(dtsSpy.m_removed.empty());
// verify DB
Streams streams = dbMock.findStreams(m_indoorCam->data().id());
QVERIFY(streams.second);
QVERIFY(streams.first == dcEntry(1, "ex_42", "AXIS_VIDEO_CHANNEL", "av_0001", "EXCAVS01", "url=rtsp://root:root@host_0001:554;mode=1;", 2));
QVERIFY(*streams.second == dcEntry(2, "ex_42", "AXIS_AUDIO_CHANNEL", "av_0001", "EXCAVS01", "url=http://root:root@host_0001:80;mode=1;", 1));
// verify DDS
QCOMPARE(dtsSpy.m_published[0], Stream(1, "av_0001", StreamType::Video, m_mpIndoor->data().id()));
QCOMPARE(dtsSpy.m_published[1], Stream(2, "av_0001", StreamType::AudioIn, m_mpIndoor->data().id()));
// remove microphone again => removal of audio stream
dtsSpy.reset();
setProperties(m_indoorCam, R"({"type": "vapix", "hasMicrophone": false})");
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 1);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 1); // one updated
QCOMPARE(static_cast<int>(dtsSpy.m_removed.size()), 1);
// verify DB
streams = dbMock.findStreams(m_indoorCam->data().id());
QVERIFY(!streams.second);
QVERIFY(streams.first == dcEntry(1, "ex_42", "AXIS_VIDEO_CHANNEL", "av_0001", "EXCAVS01", "url=rtsp://root:root@host_0001:554;mode=1;", 0));
// verify DDS
QCOMPARE(dtsSpy.m_published[0], Stream(1, "av_0001", StreamType::Video, m_mpIndoor->data().id()));
QCOMPARE(static_cast<int>(dtsSpy.m_removed[0]), 2);
// unassign the Camera
dtsSpy.reset();
m_exercise->moveMountedTo(m_indoorCam, m_pool.get(), boost::none);
QCOMPARE(dbMock.deviceCount(), 0);
QVERIFY(dtsSpy.m_published.empty());
QCOMPARE(static_cast<int>(dtsSpy.m_removed.size()), 1);
// verify DDS
QCOMPARE(static_cast<int>(dtsSpy.m_removed[0]), 1);
}
/**
* Update the MountingPoint of an assigned Asset.
*/
void AudioVideoHandlerImplTest::testUpdateAssignedMountingPoint()
{
DbMock dbMock;
DtsSpy dtsSpy;
AudioVideoHandlerImpl handler(m_exercise.get(), &dbMock, &dtsSpy);
m_pool->moveMountedTo(m_indoorCam, m_exercise.get(), boost::none);
handler.onContextActivated();
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 1);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 1);
QVERIFY(dtsSpy.m_removed.empty());
// Change the displayName of the MountingPoint.
// We now distinguish between internal stream name and display name.
// Updating the displayName property of a MountingPoint does not change the internal stream name.
dtsSpy.reset();
setDisplayName(m_mpIndoor, "updated display name");
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 1);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 1);
QVERIFY(dtsSpy.m_removed.empty());
// verify DB
Streams streams = dbMock.findStreams(m_indoorCam->data().id());
QVERIFY(!streams.second);
QVERIFY(streams.first == dcEntry(1, "ex_42", "AXIS_VIDEO_CHANNEL", "av_0001", "EXCAVS01", "url=rtsp://root:root@host_0001:554;mode=1;", 0));
// verify DDS
QCOMPARE(dtsSpy.m_published[0], Stream(1, "av_0001", StreamType::Video, m_mpIndoor->data().id()));
// set recording-flag on MountingPoint
dtsSpy.reset();
setRecording(m_mpIndoor, true);
QCOMPARE(dbMock.deviceCount(), 1);
QCOMPARE(dbMock.streamCount(), 1);
QCOMPARE(static_cast<int>(dtsSpy.m_published.size()), 1);
QVERIFY(dtsSpy.m_removed.empty());
// verify DB
streams = dbMock.findStreams(m_indoorCam->data().id());
QVERIFY(!streams.second);
QVERIFY(streams.first == dcEntry(1, "ex_42", "AXIS_VIDEO_CHANNEL", "av_0001", "EXCAVS01", "url=rtsp://root:root@host_0001:554;mode=1;recording=ECRS@EXCAVS01;recordcontrol=prealarm;", 0));
// verify DDS
QCOMPARE(dtsSpy.m_published[0], Stream(1, "av_0001", StreamType::Video, m_mpIndoor->data().id())); // no change here
}
void AudioVideoHandlerImplTest::testDeviceDefinitionCameraVideo()
{
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionCameraVideo("host", JsonObject::fromJson(R"({"type": "vapix"})"), JsonObject::fromJson(R"({})")),
QStringLiteral("url=rtsp://root:root@host:554;mode=1;"));
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionCameraVideo("host", JsonObject::fromJson(R"({"type": "vapix", "username": "user", "password": "1234"})"), JsonObject::fromJson(R"({})")),
QStringLiteral("url=rtsp://user:1234@host:554;mode=1;"));
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionCameraVideo("host", JsonObject::fromJson(R"({"type": "vapix", "port": 123})"), JsonObject::fromJson(R"({})")),
QStringLiteral("url=rtsp://root:root@host:123;mode=1;"));
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionCameraVideo("host", JsonObject::fromJson(R"({"type": "vapix"})"), JsonObject::fromJson(R"({"record": true})")),
QStringLiteral("url=rtsp://root:root@host:554;mode=1;recording=ECRS@EXCAVS01;recordcontrol=prealarm;"));
}
void AudioVideoHandlerImplTest::testDeviceDefinitionCameraAudio()
{
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionCameraAudio("host", JsonObject::fromJson(R"({"type": "vapix"})"), JsonObject::fromJson(R"({})")),
QStringLiteral("url=http://root:root@host:80;mode=1;"));
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionCameraAudio("host", JsonObject::fromJson(R"({"type": "vapix", "username": "user", "password": "1234"})"), JsonObject::fromJson(R"({})")),
QStringLiteral("url=http://user:1234@host:80;mode=1;"));
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionCameraAudio("host", JsonObject::fromJson(R"({"type": "vapix", "audioPort": 88})"), JsonObject::fromJson(R"({})")),
QStringLiteral("url=http://root:root@host:88;mode=1;"));
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionCameraAudio("host", JsonObject::fromJson(R"({"type": "vapix"})"), JsonObject::fromJson(R"({"record": true})")),
QStringLiteral("url=http://root:root@host:80;mode=1;recording=ECRS@EXCAVS01;recordcontrol=prealarm;"));
}
void AudioVideoHandlerImplTest::testDeviceDefinitionAnnouncementUnit()
{
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionAnnouncementUnit("host", JsonObject::fromJson(R"({"type": "vapix"})"), JsonObject::fromJson(R"({})")),
QStringLiteral("url=http://root:root@host:80;codec=aac;mode=1;"));
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionAnnouncementUnit("host", JsonObject::fromJson(R"({"type": "vapix", "username": "user", "password": "1234"})"), JsonObject::fromJson(R"({})")),
QStringLiteral("url=http://user:1234@host:80;codec=aac;mode=1;"));
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionAnnouncementUnit("host", JsonObject::fromJson(R"({"type": "vapix", "audioPort": 88})"), JsonObject::fromJson(R"({})")),
QStringLiteral("url=http://root:root@host:88;codec=aac;mode=1;"));
QCOMPARE(AudioVideoHandlerImpl::deviceDefinitionAnnouncementUnit("host", JsonObject::fromJson(R"({"type": "vapix"})"), JsonObject::fromJson(R"({"record": true})")),
QStringLiteral("url=http://root:root@host:80;codec=aac;mode=1;recording=ECRS@EXCAVS01;"));
}
} // namespace AudioVideoManagement