468 lines
15 KiB
C++
Executable File
468 lines
15 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* AudioVideoHandlerImpl
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
* Created by Christian Fröbel, NLSE, 2018-01-09
|
|
*/
|
|
|
|
#include "audio_video_handler_impl.h"
|
|
#include "asset.h"
|
|
#include "mounting_point.h"
|
|
#include "exercise.h"
|
|
#include "stream_name_mapping_publisher.h"
|
|
#include <AudioVideoManagement/avm.hpp>
|
|
#include <InfrastructureManagement/ism.hpp>
|
|
#include <LtsUtilsCore/json_object.h>
|
|
#include <QString>
|
|
#include <QtGlobal>
|
|
#include <string>
|
|
|
|
using LtsUtils::JsonObject;
|
|
using namespace AudioVideoManagement;
|
|
|
|
|
|
namespace
|
|
{
|
|
|
|
QString VAPIX() { return QStringLiteral("vapix"); }
|
|
QString ONVIF() { return QStringLiteral("onvif"); }
|
|
QString RTP_RECEIVER() { return QStringLiteral("rtp_receiver"); } // e.g. EGCU
|
|
QString AXIS_AUDIO_CHANNEL() { return QStringLiteral("AXIS_AUDIO_CHANNEL"); }
|
|
QString AXIS_VIDEO_CHANNEL() { return QStringLiteral("AXIS_VIDEO_CHANNEL"); }
|
|
QString ONVIF_AUDIO_CHANNEL() { return QStringLiteral("ONVIF_AUDIO_CHANNEL"); }
|
|
QString ONVIF_VIDEO_CHANNEL() { return QStringLiteral("ONVIF_VIDEO_CHANNEL"); }
|
|
QString RTP_ANNOUNCEMENT_CHANNEL() { return QStringLiteral("RTP_ANNOUNCEMENT_CHANNEL"); }
|
|
|
|
JsonObject jsonFromString(const std::string &assetProperties)
|
|
{
|
|
JsonObject obj = JsonObject::fromJson(assetProperties);
|
|
|
|
// type is mandatory and must be either "onvif", "vapix" or "rtc_receiver"
|
|
QString protocolType = obj.value<QString>("type");
|
|
Q_ASSERT(protocolType == ONVIF() || protocolType == VAPIX() || protocolType == RTP_RECEIVER());
|
|
|
|
return obj;
|
|
}
|
|
|
|
bool isOnvif(const JsonObject &properties)
|
|
{
|
|
const QString protocolType = properties.value<QString>("type").toLower();
|
|
return protocolType == ONVIF();
|
|
}
|
|
|
|
bool isVapix(const JsonObject &properties)
|
|
{
|
|
const QString protocolType = properties.value<QString>("type").toLower();
|
|
return protocolType == VAPIX();
|
|
}
|
|
|
|
bool isRtpReceiver(const JsonObject &properties)
|
|
{
|
|
const QString protocolType = properties.value<QString>("type").toLower();
|
|
return protocolType == RTP_RECEIVER();
|
|
}
|
|
|
|
bool cameraHasMicrophone(const JsonObject &properties)
|
|
{
|
|
return properties.valueOr<bool>("hasMicrophone", false);
|
|
}
|
|
|
|
bool cameraHasSpeaker(const JsonObject &properties)
|
|
{
|
|
return properties.valueOr<bool>("hasSpeaker", false);
|
|
}
|
|
|
|
QString avTypeVideoChannel(const JsonObject &properties)
|
|
{
|
|
if (isVapix(properties))
|
|
return AXIS_VIDEO_CHANNEL();
|
|
if (isOnvif(properties))
|
|
return ONVIF_VIDEO_CHANNEL();
|
|
|
|
Q_ASSERT(false);
|
|
return AXIS_VIDEO_CHANNEL(); // default
|
|
}
|
|
|
|
QString avTypeAudioChannel(const JsonObject &properties)
|
|
{
|
|
if (isVapix(properties))
|
|
return AXIS_AUDIO_CHANNEL();
|
|
if (isOnvif(properties))
|
|
return ONVIF_AUDIO_CHANNEL();
|
|
if (isRtpReceiver(properties))
|
|
return RTP_ANNOUNCEMENT_CHANNEL();
|
|
|
|
Q_ASSERT(false);
|
|
return AXIS_AUDIO_CHANNEL(); // default
|
|
}
|
|
|
|
QString getUrl(const std::string &hostname, const JsonObject &properties, const QString &portString, quint16 defaultPort, bool isAudio)
|
|
{
|
|
const QString scheme = isAudio || isOnvif(properties) ? QStringLiteral("http") : QStringLiteral("rtsp");
|
|
const QString user = properties.valueOr<QString>("username", QStringLiteral("root"));
|
|
const QString pass = properties.valueOr<QString>("password", QStringLiteral("root"));
|
|
const int port = properties.valueOr<int>(portString, defaultPort);
|
|
|
|
return QStringLiteral("url=%1://%2:%3@%4:%5;").arg(scheme, user, pass, QString::fromStdString(hostname)).arg(port);
|
|
}
|
|
|
|
QString getAudioUrl(const std::string &hostname, const JsonObject &properties)
|
|
{
|
|
const bool isAudio = true;
|
|
const quint16 defaultPort = 80;
|
|
return getUrl(hostname, properties, QStringLiteral("audioPort"), defaultPort, isAudio);
|
|
}
|
|
|
|
QString getVideoUrl(const std::string &hostname, const JsonObject &properties)
|
|
{
|
|
const bool isVideo = false;
|
|
const quint16 defaultPort = isVapix(properties) ? 554 : 80;
|
|
return getUrl(hostname, properties, QStringLiteral("port"), defaultPort, isVideo);
|
|
}
|
|
|
|
/**
|
|
* For streams of type RTP_ANNOUNCEMENT_CHANNEL.
|
|
*/
|
|
QString getRtpAccess(const std::string &hostname, const JsonObject &properties)
|
|
{
|
|
int port = properties.valueOr<int>("port", 5000);
|
|
int sampleRate = properties.valueOr<int>("sampleRate", 8000);
|
|
Q_ASSERT(sampleRate == 8000 || sampleRate == 16000);
|
|
|
|
QString sampleRateFlag;
|
|
if (sampleRate == 16000)
|
|
sampleRateFlag = QStringLiteral("g16kHz=1;");
|
|
|
|
return QStringLiteral("rtpoutaddress=%1:%2;").arg(QString::fromStdString(hostname)).arg(port) + sampleRateFlag;
|
|
}
|
|
|
|
QString avRootName() { return QStringLiteral("ECRS"); }
|
|
QString avController() { return QStringLiteral("EXCAVS01"); }
|
|
|
|
QString recordingString(bool hasRecordingOn_, bool usePrealarm)
|
|
{
|
|
QString result = QLatin1String("mode=1;");
|
|
|
|
if (hasRecordingOn_)
|
|
{
|
|
result.append(QStringLiteral("recording=%1@%2;").arg(avRootName(), avController()));
|
|
if (usePrealarm)
|
|
result.append(QStringLiteral("recordcontrol=prealarm;"));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool isMountingPointOfInterest(const InfrastructureManagement::InstrumentationAsset &asset)
|
|
{
|
|
return (asset.mountingPointTypeId() == InfrastructureManagement::MountingPointType::IndoorCamera ||
|
|
asset.mountingPointTypeId() == InfrastructureManagement::MountingPointType::OutdoorCamera ||
|
|
asset.mountingPointTypeId() == InfrastructureManagement::MountingPointType::AnnouncementUnit);
|
|
}
|
|
|
|
bool hasRecordingOn(const JsonObject &mountingPointProperties)
|
|
{
|
|
return mountingPointProperties.valueOr<bool>("record", false);
|
|
}
|
|
|
|
Stream toDds(const AvmDeviceConfigEx &dc, int32_t mountintPointId)
|
|
{
|
|
Stream stream;
|
|
stream.id(dc.id);
|
|
stream.name(dc.name.toStdString());
|
|
stream.type(dc.streamType);
|
|
stream.mountingPointId(mountintPointId);
|
|
return stream;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
namespace AudioVideoManagement
|
|
{
|
|
|
|
AvmDeviceConfigEx::AvmDeviceConfigEx(const AvmDeviceConfig &dc, StreamType streamType_)
|
|
: AvmDeviceConfig(dc)
|
|
, streamType(streamType_)
|
|
{}
|
|
|
|
|
|
Streams::Streams(Asset *asset_, const AvmDeviceConfigEx &first_)
|
|
: asset(asset_)
|
|
, first(first_)
|
|
{
|
|
Q_ASSERT(asset_);
|
|
}
|
|
|
|
Streams::Streams(Asset *asset_, const AvmDeviceConfigEx &first_, const AvmDeviceConfigEx &second_)
|
|
: asset(asset_)
|
|
, first(first_)
|
|
, second(second_)
|
|
{
|
|
Q_ASSERT(asset_);
|
|
Q_ASSERT(first_.name == second_.name);
|
|
Q_ASSERT(first.streamType == StreamType::Video);
|
|
}
|
|
|
|
int32_t Streams::assetId() const
|
|
{
|
|
return asset->data().id();
|
|
}
|
|
|
|
int32_t Streams::mountingPointId() const
|
|
{
|
|
return asset->mountingPoint()->data().id();
|
|
}
|
|
|
|
|
|
AudioVideoHandlerImpl::AudioVideoHandlerImpl(Exercise *exercise, IDbProxy *dbProxy, IDtsProxy *dtsProxy)
|
|
: m_exContext(exercise->context())
|
|
, m_exercise(exercise)
|
|
, m_dbProxy(dbProxy)
|
|
, m_dtsProxy(dtsProxy)
|
|
, m_logger(getLogger("AudioVideoHandler"))
|
|
{
|
|
Q_ASSERT(exercise);
|
|
Q_ASSERT(dbProxy);
|
|
Q_ASSERT(dtsProxy);
|
|
|
|
connect(m_exercise, &Exercise::assetAssigned, this, &AudioVideoHandlerImpl::onAssetAssigned);
|
|
connect(m_exercise, &Exercise::assetUnassigned, this, &AudioVideoHandlerImpl::onAssetUnassigned);
|
|
}
|
|
|
|
/**
|
|
* Create one or two AvmDeviceConfigEx structs depending on the type of device:
|
|
* - For AnnouncementUnits create only one for the audio stream.
|
|
* - For IndoorCamera/OutdoorCamera create one for the video stream and
|
|
* another one for the audio stream if the camera has microphone or speaker.
|
|
* The id and link properties are not set (set to 0).
|
|
*/
|
|
Streams AudioVideoHandlerImpl::createStreams(Asset *asset_)
|
|
{
|
|
Q_ASSERT(asset_);
|
|
|
|
Database::AvmDeviceConfig dc;
|
|
dc.context = m_exContext.toString();
|
|
dc.controller = avController();
|
|
dc.name = internalStreamName(asset_->mountingPoint()->data().id());
|
|
|
|
const InfrastructureManagement::InstrumentationAsset &asset = asset_->data();
|
|
const JsonObject assetProperties = jsonFromString(asset.properties());
|
|
const JsonObject mpProperties = JsonObject::fromJson(asset_->mountingPoint()->data().properties());
|
|
|
|
if (asset.mountingPointTypeId() == InfrastructureManagement::MountingPointType::AnnouncementUnit)
|
|
{
|
|
dc.definition = deviceDefinitionAnnouncementUnit(asset.hostname(), assetProperties, mpProperties);
|
|
dc.type = avTypeAudioChannel(assetProperties);
|
|
return {asset_, {dc, StreamType::AudioOut}};
|
|
}
|
|
|
|
Q_ASSERT(asset.mountingPointTypeId() == InfrastructureManagement::MountingPointType::IndoorCamera ||
|
|
asset.mountingPointTypeId() == InfrastructureManagement::MountingPointType::OutdoorCamera);
|
|
|
|
dc.definition = deviceDefinitionCameraVideo(asset.hostname(), assetProperties, mpProperties);
|
|
dc.type = avTypeVideoChannel(assetProperties);
|
|
|
|
if (!cameraHasMicrophone(assetProperties) && !cameraHasSpeaker(assetProperties))
|
|
return {asset_, {dc, StreamType::Video}};
|
|
|
|
// additional entry for Microphone or Speaker (audio)
|
|
Database::AvmDeviceConfig dcAudio = dc;
|
|
|
|
StreamType audioStreamType;
|
|
if (cameraHasMicrophone(assetProperties) && cameraHasSpeaker(assetProperties))
|
|
audioStreamType = StreamType::AudioInOut;
|
|
else if (cameraHasMicrophone(assetProperties))
|
|
audioStreamType = StreamType::AudioIn;
|
|
else
|
|
audioStreamType = StreamType::AudioOut;
|
|
|
|
dcAudio.definition = deviceDefinitionCameraAudio(asset.hostname(), assetProperties, mpProperties);
|
|
dcAudio.type = avTypeAudioChannel(assetProperties);
|
|
return {asset_, {dc, StreamType::Video}, {dcAudio, audioStreamType}};
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::onAssetAssigned(Asset *asset)
|
|
{
|
|
Q_ASSERT(asset);
|
|
Q_ASSERT(asset->isMounted());
|
|
|
|
if (!m_isActive)
|
|
return;
|
|
|
|
if (!isMountingPointOfInterest(asset->data()))
|
|
return;
|
|
|
|
connect(asset, &Asset::changed, this, &AudioVideoHandlerImpl::onAssetChanged);
|
|
connect(asset->mountingPoint(), &MountingPoint::changed, this, &AudioVideoHandlerImpl::onMountingPointChanged);
|
|
addDeviceConfigToDb(asset);
|
|
}
|
|
|
|
/**
|
|
* @attention asset will be deleted after the method returns!
|
|
* This slot must not be connected using QueuedConnection.
|
|
*/
|
|
void AudioVideoHandlerImpl::onAssetUnassigned(Asset *asset)
|
|
{
|
|
Q_ASSERT(asset);
|
|
Q_ASSERT(asset->isMounted());
|
|
|
|
if (!m_isActive)
|
|
return;
|
|
|
|
if (!isMountingPointOfInterest(asset->data()))
|
|
return;
|
|
|
|
removeDeviceConfigFromDb(asset);
|
|
asset->disconnect(this);
|
|
asset->mountingPoint()->disconnect(this);
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::onAssetChanged(Asset *asset)
|
|
{
|
|
Q_ASSERT(m_isActive);
|
|
Q_ASSERT(asset);
|
|
DEBUG_LOG(m_logger) << m_exContext << ": Asset changed: " << asset->data().name() << "(" << asset->data().id() << ")";
|
|
|
|
updateDeviceConfig(asset);
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::onMountingPointChanged(MountingPoint *mountingPoint)
|
|
{
|
|
Q_ASSERT(m_isActive);
|
|
Q_ASSERT(mountingPoint);
|
|
DEBUG_LOG(m_logger) << m_exContext << ": MountingPoint changed: " << mountingPoint->data().name() << "(" << mountingPoint->data().id() << ")";
|
|
|
|
updateDeviceConfig(mountingPoint->asset());
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::onContextActivated()
|
|
{
|
|
m_isActive = true;
|
|
|
|
for (auto asset: m_exercise->assets())
|
|
onAssetAssigned(asset);
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::onContextDeactivated()
|
|
{
|
|
for (auto asset: m_exercise->assets())
|
|
onAssetUnassigned(asset);
|
|
|
|
m_isActive = false;
|
|
}
|
|
|
|
QString AudioVideoHandlerImpl::internalStreamName(int32_t mountingPointId)
|
|
{
|
|
return QStringLiteral("av_%1").arg(QString::number(mountingPointId), 4, QLatin1Char('0'));
|
|
}
|
|
|
|
QString AudioVideoHandlerImpl::deviceDefinitionAnnouncementUnit(const std::string &hostname, const JsonObject &assetProperties, const JsonObject &mountingPointProperties)
|
|
{
|
|
// Shall Announcement Units also be run in prealarm mode?
|
|
const bool noPrealarm = false;
|
|
const QString recording = recordingString(hasRecordingOn(mountingPointProperties), noPrealarm);
|
|
|
|
if (isRtpReceiver(assetProperties))
|
|
return getRtpAccess(hostname, assetProperties) + recording;
|
|
|
|
// AXIS or ONVIF
|
|
return getAudioUrl(hostname, assetProperties) + QStringLiteral("codec=aac;") + recording;
|
|
}
|
|
|
|
QString AudioVideoHandlerImpl::deviceDefinitionCameraVideo(const std::string &hostname, const JsonObject &assetProperties, const JsonObject &mountingPointProperties)
|
|
{
|
|
const bool usePrealarm = true;
|
|
QString definition = getVideoUrl(hostname, assetProperties) + recordingString(hasRecordingOn(mountingPointProperties), usePrealarm);
|
|
|
|
// Special settings needed for ONVIF-camera used in FAT.
|
|
// Different ONVIF-devices wound need different settings here.
|
|
if (isOnvif(assetProperties))
|
|
definition.append(QStringLiteral("eventtype=1;eventrelaymapping=AlarmOut_1:AlarmOut_0;"));
|
|
return definition;
|
|
}
|
|
|
|
QString AudioVideoHandlerImpl::deviceDefinitionCameraAudio(const std::string &hostname, const JsonObject &assetProperties, const JsonObject &mountingPointProperties)
|
|
{
|
|
const bool usePrealarm = true;
|
|
QString definition = getAudioUrl(hostname, assetProperties) + recordingString(hasRecordingOn(mountingPointProperties), usePrealarm);
|
|
|
|
// Special settings needed for ONVIF-camera used in FAT.
|
|
// Different ONVIF-devices would need different settings here.
|
|
if (isOnvif(assetProperties))
|
|
definition.append(QStringLiteral("eventtype=0;backchannel=2;"));
|
|
return definition;
|
|
}
|
|
|
|
|
|
|
|
void AudioVideoHandlerImpl::addDeviceConfigToDb(Asset *asset_)
|
|
{
|
|
Q_ASSERT(asset_);
|
|
|
|
Streams entries = createStreams(asset_);
|
|
m_dbProxy->insert(entries);
|
|
ddsPublish(entries);
|
|
emit avDbReloadNeeded();
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::updateDeviceConfig(Asset *asset)
|
|
{
|
|
Q_ASSERT(asset);
|
|
|
|
Streams oldStreams = m_dbProxy->findStreams(asset);
|
|
Streams newStreams = createStreams(asset);
|
|
m_dbProxy->update(newStreams);
|
|
ddsUpdate(oldStreams, newStreams);
|
|
emit avDbReloadNeeded();
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::removeDeviceConfigFromDb(Asset *asset)
|
|
{
|
|
Streams oldStreams = m_dbProxy->findStreams(asset);
|
|
m_dbProxy->remove(asset);
|
|
ddsRemove(oldStreams);
|
|
emit avDbReloadNeeded();
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::ddsPublish(const Streams &streams)
|
|
{
|
|
int32_t mountingPointId = streams.mountingPointId();
|
|
m_dtsProxy->publish(toDds(streams.first, mountingPointId));
|
|
if (streams.second)
|
|
m_dtsProxy->publish(toDds(*streams.second, mountingPointId));
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::ddsUpdate(const Streams &oldStreams, const Streams &newStreams)
|
|
{
|
|
Q_ASSERT(newStreams.asset == oldStreams.asset);
|
|
Q_ASSERT(newStreams.first.id == oldStreams.first.id);
|
|
int32_t mountingPointId = newStreams.mountingPointId();
|
|
|
|
m_dtsProxy->publish(toDds(newStreams.first, mountingPointId));
|
|
|
|
if (newStreams.second)
|
|
{
|
|
if (oldStreams.second)
|
|
Q_ASSERT(newStreams.second->id == oldStreams.second->id);
|
|
|
|
m_dtsProxy->publish(toDds(*newStreams.second, mountingPointId));
|
|
}
|
|
else
|
|
{
|
|
if (oldStreams.second)
|
|
m_dtsProxy->remove(oldStreams.second->id);
|
|
}
|
|
}
|
|
|
|
void AudioVideoHandlerImpl::ddsRemove(const Streams &streams)
|
|
{
|
|
m_dtsProxy->remove(streams.first.id);
|
|
if (streams.second)
|
|
m_dtsProxy->remove(streams.second->id);
|
|
}
|
|
|
|
} // namespace AudioVideoManagement
|