// // 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 zmc --monitor 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 #include #include #include "xop/RtspServer.h" void Usage() { fprintf(stderr, "zm_rtsp_server -m \n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " -m, --monitor : 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> 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 eventLoop(new xop::EventLoop()); std::shared_ptr rtspServer = xop::RtspServer::Create(eventLoop.get()); if (config.opt_use_auth) { std::shared_ptr 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()]; std::vector video_sources; video_sources.reserve(monitors.size()); std::vector audio_sources; audio_sources.reserve(monitors.size()); for (size_t i = 0; i < monitors.size(); i++) { sessions[i] = nullptr; video_sources[i] = nullptr; audio_sources[i] = nullptr; } while (!zm_terminate) { for (size_t i = 0; i < monitors.size(); i++) { std::shared_ptr 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; } if (video_sources[i]) { delete video_sources[i]; video_sources[i] = 0; } if (audio_sources[i]) { delete audio_sources[i]; audio_sources[i] = 0; } 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, const 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, const 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"); } video_sources[i] = 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"); } audio_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; } if (video_sources[i]) delete video_sources[i]; if (audio_sources[i]) delete audio_sources[i]; } // end foreach monitor rtspServer->Stop(); delete[] sessions; sessions = nullptr; Image::Deinitialise(); logTerm(); zmDbClose(); return 0; }