zoneminder/src/zm_rtsp_server.cpp

312 lines
9.5 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 "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 = "`Function` != '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);
std::vector<std::shared_ptr<Monitor>> monitors = Monitor::LoadMonitors(where, Monitor::QUERY);
if (monitors.empty()) {
Error("No monitors found");
exit(-1);
} else {
Debug(2, "%d monitors loaded", monitors.size());
}
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());
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);
}
xop::MediaSession **sessions = new xop::MediaSession *[monitors.size()];
for (size_t i = 0; i < monitors.size(); i++) sessions[i] = nullptr;
std::list<ZoneMinderFifoSource *> sources;
while (!zm_terminate) {
for (size_t i = 0; i < monitors.size(); i++) {
std::shared_ptr<Monitor> monitor = monitors[i];
if (!monitor->ShmValid()) {
monitor->disconnect();
if (!monitor->connect()) {
Warning("Couldn't connect to monitor %d", monitor->Id());
if (sessions[i]) {
rtspServer->RemoveSession(sessions[i]->GetMediaSessionId());
sessions[i] = nullptr;
}
continue;
}
}
if (!sessions[i]) {
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[i] = xop::MediaSession::CreateNew(streamname);
if (session) {
session->AddNotifyConnectedCallback([] (xop::MediaSessionId sessionId, std::string peer_ip, uint16_t peer_port){
Debug(1, "RTSP client connect, ip=%s, port=%hu \n", peer_ip.c_str(), peer_port);
});
session->AddNotifyDisconnectedCallback([](xop::MediaSessionId sessionId, std::string peer_ip, uint16_t peer_port) {
Debug(1, "RTSP client disconnect, ip=%s, port=%hu \n", 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;
}
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");
}
sources.push_back(videoSource);
if (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 {
Warning("Unknown format in %s", audioFifoPath.c_str());
}
if (audioSource == nullptr) {
Error("Unable to create source");
}
sources.push_back(audioSource);
} // end if ! sessions[i]
} // end foreach monitor
sleep(1);
if (zm_reload) {
for (size_t i = 0; i < monitors.size(); i++) {
monitors[i]->Reload();
}
logTerm();
logInit(log_id_string);
zm_reload = false;
} // end if zm_reload
} // end while !zm_terminate
Info("RTSP Server shutting down");
for (size_t i = 0; i < monitors.size(); i++) {
if (sessions[i]) {
Debug(1, "Removing session for %s", monitors[i]->Name());
rtspServer->RemoveSession(sessions[i]->GetMediaSessionId());
sessions[i] = nullptr;
}
} // end foreach monitor
for ( std::list<ZoneMinderFifoSource *>::iterator it = sources.begin(); it != sources.end(); ++it ) {
Debug(1, "RTSPServerThread::stopping source");
(*it)->Stop();
}
while (sources.size()) {
Debug(1, "RTSPServerThread::stop closing source");
ZoneMinderFifoSource *source = sources.front();
sources.pop_front();
delete source;
}
rtspServer->Stop();
delete[] sessions;
sessions = nullptr;
Image::Deinitialise();
logTerm();
zmDbClose();
return 0;
}