360 lines
12 KiB
C++
360 lines
12 KiB
C++
//
|
|
// ZoneMinder RTSP Daemon
|
|
// Copyright (C) 2021 Isaac Connor
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
//
|
|
|
|
/*
|
|
|
|
=head1 NAME
|
|
|
|
zm_rtsp_server - The ZoneMinder Server
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
zmc -m <monitor_id>
|
|
zmc --monitor <monitor_id>
|
|
zmc -h
|
|
zmc --help
|
|
zmc -v
|
|
zmc --version
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This binary's job is to connect to fifo's provided by local zmc processes
|
|
and provide that stream over rtsp
|
|
|
|
=head1 OPTIONS
|
|
|
|
-m, --monitor_id - ID of a monitor to stream
|
|
-h, --help - Display usage information
|
|
-v, --version - Print the installed version of ZoneMinder
|
|
|
|
=cut
|
|
|
|
*/
|
|
|
|
#include "zm.h"
|
|
#include "zm_db.h"
|
|
#include "zm_define.h"
|
|
#include "zm_monitor.h"
|
|
#include "zm_rtsp_server_authenticator.h"
|
|
#include "zm_rtsp_server_fifo_h264_source.h"
|
|
#include "zm_rtsp_server_fifo_adts_source.h"
|
|
#include "zm_signal.h"
|
|
#include "zm_time.h"
|
|
#include "zm_utils.h"
|
|
|
|
#include <getopt.h>
|
|
#include <iostream>
|
|
#include <vector>
|
|
|
|
|
|
#include "xop/RtspServer.h"
|
|
|
|
void Usage() {
|
|
fprintf(stderr, "zm_rtsp_server -m <monitor_id>\n");
|
|
|
|
fprintf(stderr, "Options:\n");
|
|
fprintf(stderr, " -m, --monitor <monitor_id> : We default to all monitors use this to specify just one\n");
|
|
fprintf(stderr, " -h, --help : This screen\n");
|
|
fprintf(stderr, " -v, --version : Report the installed version of ZoneMinder\n");
|
|
exit(0);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
self = argv[0];
|
|
|
|
srand(getpid() * time(nullptr));
|
|
|
|
int monitor_id = -1;
|
|
|
|
static struct option long_options[] = {
|
|
{"monitor", 1, nullptr, 'm'},
|
|
{"help", 0, nullptr, 'h'},
|
|
{"version", 0, nullptr, 'v'},
|
|
{nullptr, 0, nullptr, 0}
|
|
};
|
|
|
|
while (1) {
|
|
int option_index = 0;
|
|
int c = getopt_long(argc, argv, "m:h:v", long_options, &option_index);
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c) {
|
|
case 'm':
|
|
monitor_id = atoi(optarg);
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
Usage();
|
|
break;
|
|
case 'v':
|
|
std::cout << ZM_VERSION << "\n";
|
|
exit(0);
|
|
default:
|
|
// fprintf(stderr, "?? getopt returned character code 0%o ??\n", c);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (optind < argc) {
|
|
fprintf(stderr, "Extraneous options, ");
|
|
while (optind < argc)
|
|
printf("%s ", argv[optind++]);
|
|
printf("\n");
|
|
Usage();
|
|
}
|
|
|
|
const char *log_id_string = "zm_rtsp_server";
|
|
///std::string log_id_string = std::string("zm_rtsp_server");
|
|
///if ( monitor_id > 0 ) log_id_string += stringtf("_m%d", monitor_id);
|
|
|
|
logInit(log_id_string);
|
|
zmLoadStaticConfig();
|
|
zmDbConnect();
|
|
zmLoadDBConfig();
|
|
logInit(log_id_string);
|
|
if (!config.min_rtsp_port) {
|
|
Debug(1, "Not starting RTSP server because min_rtsp_port not set");
|
|
exit(-1);
|
|
}
|
|
|
|
HwCapsDetect();
|
|
|
|
std::string where = "`Deleted` = 0 AND `Capturing` != 'None' AND `RTSPServer` != false";
|
|
if (staticConfig.SERVER_ID)
|
|
where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID);
|
|
if (monitor_id > 0)
|
|
where += stringtf(" AND `Id`=%d", monitor_id);
|
|
|
|
Info("Starting RTSP Server version %s", ZM_VERSION);
|
|
zmSetDefaultHupHandler();
|
|
zmSetDefaultTermHandler();
|
|
zmSetDefaultDieHandler();
|
|
|
|
std::shared_ptr<xop::EventLoop> eventLoop(new xop::EventLoop());
|
|
std::shared_ptr<xop::RtspServer> rtspServer = xop::RtspServer::Create(eventLoop.get());
|
|
rtspServer->SetVersion("ZoneMinder RTSP Server");
|
|
|
|
if (config.opt_use_auth) {
|
|
std::shared_ptr<ZM_RtspServer_Authenticator> authenticator(new ZM_RtspServer_Authenticator());
|
|
rtspServer->SetAuthenticator(authenticator);
|
|
}
|
|
|
|
if (!rtspServer->Start("0.0.0.0", config.min_rtsp_port)) {
|
|
Debug(1, "Failed starting RTSP server on port %d", config.min_rtsp_port);
|
|
exit(-1);
|
|
}
|
|
|
|
std::unordered_map<unsigned int, xop::MediaSession *> sessions;
|
|
std::unordered_map<unsigned int, ZoneMinderFifoSource *> video_sources;
|
|
std::unordered_map<unsigned int, ZoneMinderFifoSource *> audio_sources;
|
|
std::unordered_map<unsigned int, std::shared_ptr<Monitor>> monitors;
|
|
|
|
while (!zm_terminate) {
|
|
std::unordered_map<unsigned int, std::shared_ptr<Monitor>> old_monitors = monitors;
|
|
|
|
std::vector<std::shared_ptr<Monitor>> new_monitors = Monitor::LoadMonitors(where, Monitor::QUERY);
|
|
for (const auto &monitor : new_monitors) {
|
|
auto old_monitor_it = old_monitors.find(monitor->Id());
|
|
if (old_monitor_it != old_monitors.end()
|
|
and
|
|
(old_monitor_it->second->GetRTSPStreamName() == monitor->GetRTSPStreamName())
|
|
) {
|
|
Debug(1, "Found monitor in oldmonitors, clearing it");
|
|
old_monitors.erase(old_monitor_it);
|
|
} else {
|
|
Debug(1, "Adding monitor %d to monitors", monitor->Id());
|
|
monitors[monitor->Id()] = monitor;
|
|
}
|
|
}
|
|
// Remove monitors that are no longer doing rtsp
|
|
for (auto it = old_monitors.begin(); it != old_monitors.end(); ++it) {
|
|
auto mid = it->first;
|
|
auto &monitor = it->second;
|
|
Debug(1, "Removing %d %s from monitors", monitor->Id(), monitor->Name());
|
|
monitors.erase(mid);
|
|
if (sessions.find(mid) != sessions.end()) {
|
|
if (video_sources.find(monitor->Id()) != video_sources.end()) {
|
|
delete video_sources[monitor->Id()];
|
|
video_sources.erase(monitor->Id());
|
|
}
|
|
if (audio_sources.find(monitor->Id()) != audio_sources.end()) {
|
|
delete audio_sources[monitor->Id()];
|
|
audio_sources.erase(monitor->Id());
|
|
}
|
|
rtspServer->RemoveSession(sessions[mid]->GetMediaSessionId());
|
|
sessions.erase(mid);
|
|
}
|
|
}
|
|
|
|
for (auto it = monitors.begin(); it != monitors.end(); ++it) {
|
|
auto &monitor = it->second;
|
|
|
|
if (!monitor->ShmValid()) {
|
|
Debug(1, "!ShmValid");
|
|
monitor->disconnect();
|
|
if (!monitor->connect()) {
|
|
Warning("Couldn't connect to monitor %d", monitor->Id());
|
|
if (sessions.find(monitor->Id()) != sessions.end()) {
|
|
if (video_sources.find(monitor->Id()) != video_sources.end()) {
|
|
video_sources.erase(monitor->Id());
|
|
}
|
|
if (audio_sources.find(monitor->Id()) != audio_sources.end()) {
|
|
audio_sources.erase(monitor->Id());
|
|
}
|
|
rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId());
|
|
sessions.erase(monitor->Id());
|
|
}
|
|
monitor->Reload(); // This is to pickup change of colours, width, height, etc
|
|
continue;
|
|
} // end if failed to connect
|
|
} // end if !ShmValid
|
|
|
|
if (sessions.end() == sessions.find(monitor->Id())) {
|
|
Debug(1, "Monitor not found in sessions, opening it");
|
|
std::string videoFifoPath = monitor->GetVideoFifoPath();
|
|
if (videoFifoPath.empty()) {
|
|
Debug(1, "video fifo is empty. Skipping.");
|
|
continue;
|
|
}
|
|
|
|
std::string streamname = monitor->GetRTSPStreamName();
|
|
xop::MediaSession *session = sessions[monitor->Id()] = xop::MediaSession::CreateNew(streamname);
|
|
if (!session) {
|
|
Error("Unable to create session for %s", streamname.c_str());
|
|
continue;
|
|
}
|
|
session->AddNotifyConnectedCallback([] (xop::MediaSessionId sessionId, const std::string &peer_ip, uint16_t peer_port){
|
|
Debug(1, "RTSP client connect, ip=%s, port=%hu", peer_ip.c_str(), peer_port);
|
|
});
|
|
|
|
session->AddNotifyDisconnectedCallback([](xop::MediaSessionId sessionId, const std::string &peer_ip, uint16_t peer_port) {
|
|
Debug(1, "RTSP client disconnect, ip=%s, port=%hu", peer_ip.c_str(), peer_port);
|
|
});
|
|
|
|
rtspServer->AddSession(session);
|
|
//char *url = rtspServer->rtspURL(session);
|
|
//Debug(1, "url is %s for stream %s", url, streamname.c_str());
|
|
//delete[] url;
|
|
monitors[monitor->Id()] = monitor;
|
|
|
|
Debug(1, "Adding video fifo %s", videoFifoPath.c_str());
|
|
ZoneMinderFifoVideoSource *videoSource = nullptr;
|
|
|
|
if (std::string::npos != videoFifoPath.find("h264")) {
|
|
session->AddSource(xop::channel_0, xop::H264Source::CreateNew());
|
|
videoSource = new H264_ZoneMinderFifoSource(rtspServer, session->GetMediaSessionId(), xop::channel_0, videoFifoPath);
|
|
} else if (
|
|
std::string::npos != videoFifoPath.find("hevc")
|
|
or
|
|
std::string::npos != videoFifoPath.find("h265")) {
|
|
session->AddSource(xop::channel_0, xop::H265Source::CreateNew());
|
|
videoSource = new H265_ZoneMinderFifoSource(rtspServer, session->GetMediaSessionId(), xop::channel_0, videoFifoPath);
|
|
} else {
|
|
Warning("Unknown format in %s", videoFifoPath.c_str());
|
|
}
|
|
if (videoSource == nullptr) {
|
|
Error("Unable to create source for %s", videoFifoPath.c_str());
|
|
rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId());
|
|
sessions.erase(monitor->Id());
|
|
continue;
|
|
}
|
|
video_sources[monitor->Id()] = videoSource;
|
|
videoSource->setWidth(monitor->Width());
|
|
videoSource->setHeight(monitor->Height());
|
|
|
|
std::string audioFifoPath = monitor->GetAudioFifoPath();
|
|
if (audioFifoPath.empty()) {
|
|
Debug(1, "audio fifo is empty. Skipping.");
|
|
continue;
|
|
}
|
|
Debug(1, "Adding audio fifo %s", audioFifoPath.c_str());
|
|
|
|
ZoneMinderFifoAudioSource *audioSource = nullptr;
|
|
|
|
if (std::string::npos != audioFifoPath.find("aac")) {
|
|
Debug(1, "Adding aac source at %dHz %d channels",
|
|
monitor->GetAudioFrequency(), monitor->GetAudioChannels());
|
|
session->AddSource(xop::channel_1, xop::AACSource::CreateNew(
|
|
monitor->GetAudioFrequency(),
|
|
monitor->GetAudioChannels(),
|
|
false /* has_adts */));
|
|
audioSource = new ADTS_ZoneMinderFifoSource(rtspServer,
|
|
session->GetMediaSessionId(), xop::channel_1, audioFifoPath);
|
|
audioSource->setFrequency(monitor->GetAudioFrequency());
|
|
audioSource->setChannels(monitor->GetAudioChannels());
|
|
} else if (std::string::npos != audioFifoPath.find("pcm_alaw")) {
|
|
Debug(1, "Adding G711A source at %dHz %d channels",
|
|
monitor->GetAudioFrequency(), monitor->GetAudioChannels());
|
|
session->AddSource(xop::channel_1, xop::G711ASource::CreateNew());
|
|
audioSource = new ADTS_ZoneMinderFifoSource(rtspServer,
|
|
session->GetMediaSessionId(), xop::channel_1, audioFifoPath);
|
|
audioSource->setFrequency(monitor->GetAudioFrequency());
|
|
audioSource->setChannels(monitor->GetAudioChannels());
|
|
} else {
|
|
Warning("Unknown format in %s", audioFifoPath.c_str());
|
|
}
|
|
if (audioSource == nullptr) {
|
|
Error("Unable to create source");
|
|
}
|
|
audio_sources[monitor->Id()] = audioSource;
|
|
} // end if ! sessions[monitor->Id()]
|
|
if (sessions[monitor->Id()]->GetNumClient() > 0) {
|
|
SystemTimePoint now = std::chrono::system_clock::now();
|
|
monitor->setLastViewed(now);
|
|
}
|
|
} // end foreach monitor
|
|
|
|
sleep(10);
|
|
|
|
if (zm_reload) {
|
|
logTerm();
|
|
logInit(log_id_string);
|
|
zm_reload = false;
|
|
} // end if zm_reload
|
|
} // end while !zm_terminate
|
|
|
|
Info("RTSP Server shutting down");
|
|
|
|
for (const std::pair<const unsigned int, std::shared_ptr<Monitor>> &mon_pair : monitors) {
|
|
unsigned int i = mon_pair.first;
|
|
if (video_sources.find(i) != video_sources.end()) {
|
|
delete video_sources[i];
|
|
}
|
|
if (audio_sources.find(i) != audio_sources.end()) {
|
|
delete audio_sources[i];
|
|
}
|
|
if (sessions.find(i) != sessions.end()) {
|
|
Debug(1, "Removing session for %s", mon_pair.second->Name());
|
|
rtspServer->RemoveSession(sessions[i]->GetMediaSessionId());
|
|
sessions.erase(i);
|
|
}
|
|
} // end foreach monitor
|
|
|
|
rtspServer->Stop();
|
|
|
|
sessions.clear();
|
|
|
|
Image::Deinitialise();
|
|
logTerm();
|
|
zmDbClose();
|
|
|
|
return 0;
|
|
}
|