Adds RTSP2Web support for live view

This commit is contained in:
Jonathan Bennett committed 2023-07-29 23:46:31 -05:00
1 parent 9295c390ca
commit 5c9588c012
20 files changed
+530 -30

No files matched your search

+1
View File
@@ -36,6 +36,7 @@ set(ZM_BIN_SRC_FILES
zm_local_camera.cpp
zm_monitor.cpp
zm_monitor_monitorlink.cpp
zm_monitor_rtsp2web.cpp
zm_monitor_janus.cpp
zm_monitor_amcrest.cpp
zm_monitorlink_expression.cpp
+30 -3
View File
@@ -82,7 +82,7 @@ struct Namespace namespaces[] =
// It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended
std::string load_monitor_sql =
"SELECT `Id`, `Name`, `Deleted`, `ServerId`, `StorageId`, `Type`, `Capturing`+0, `Analysing`+0, `AnalysisSource`+0, `AnalysisImage`+0,"
"`Recording`+0, `RecordingSource`+0, `Decoding`+0, "
"`Recording`+0, `RecordingSource`+0, `Decoding`+0, `RTSP2WebEnabled`, `RTSP2WebType`,"
"`JanusEnabled`, `JanusAudioEnabled`, `Janus_Profile_Override`, `Janus_Use_RTSP_Restream`, `Janus_RTSP_User`, `Janus_RTSP_Session_Timeout`, "
"`LinkedMonitors`, `EventStartCommand`, `EventEndCommand`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`,"
"`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings
@@ -155,6 +155,12 @@ std::string Decoding_Strings[] = {
"Always"
};
std::string RTSP2Web_Strings[] = {
"HLS",
"MSE",
"WebRTC"
};
std::string TriggerState_Strings[] = {
"Cancel", "On", "Off"
};
@@ -169,6 +175,8 @@ Monitor::Monitor()
analysing(ANALYSING_ALWAYS),
recording(RECORDING_ALWAYS),
decoding(DECODING_ALWAYS),
RTSP2Web_enabled(false),
RTSP2Web_type(WEBRTC),
janus_enabled(false),
janus_audio_enabled(false),
janus_profile_override(""),
@@ -301,6 +309,7 @@ Monitor::Monitor()
Poll_Trigger_State(false),
Event_Poller_Healthy(false),
Event_Poller_Closes_Event(false),
RTSP2Web_Manager(nullptr),
Janus_Manager(nullptr),
Amcrest_Manager(nullptr),
#ifdef WITH_GSOAP
@@ -330,7 +339,7 @@ Monitor::Monitor()
/*
std::string load_monitor_sql =
"SELECT `Id`, `Name`, `Deleted`, `ServerId`, `StorageId`, `Type`, `Capturing`+0, `Analysing`+0, `AnalysisSource`+0, `AnalysisImage`+0,"
"`Recording`+0, `RecordingSource`+0, `Decoding`+0, JanusEnabled, JanusAudioEnabled, Janus_Profile_Override, Janus_Use_RTSP_Restream, Janus_RTSP_User, Janus_RTSP_Session_Timeout, "
"`Recording`+0, `RecordingSource`+0, `Decoding`+0, RTSP2WebEnabled, RTSP2WebType, JanusEnabled, JanusAudioEnabled, Janus_Profile_Override, Janus_Use_RTSP_Restream, Janus_RTSP_User, Janus_RTSP_Session_Timeout, "
"LinkedMonitors, `EventStartCommand`, `EventEndCommand`, "
"AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS,"
"Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings
@@ -390,6 +399,8 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
decoding = (DecodingOption)atoi(dbrow[col]); col++;
// See below after save_jpegs for a recalculation of decoding_enabled
RTSP2Web_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
RTSP2Web_type = (RTSP2WebOption)atoi(dbrow[col]); col++;
janus_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
janus_audio_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
janus_profile_override = std::string(dbrow[col] ? dbrow[col] : ""); col++;
@@ -1015,6 +1026,9 @@ bool Monitor::connect() {
ReloadLinkedMonitors();
if (RTSP2Web_enabled) {
RTSP2Web_Manager = new RTSP2WebManager(this);
}
if (janus_enabled) {
Janus_Manager = new JanusManager(this);
}
@@ -1887,6 +1901,13 @@ bool Monitor::Poll() {
#endif
} // end if Amcrest or not
} // end if Healthy
Debug(1, "Trying to check RTSP2Web in Poller");
if (RTSP2Web_enabled and RTSP2Web_Manager) {
Debug(1, "Trying to add stream to RTSP2Web");
if (RTSP2Web_Manager->check_RTSP2Web() == 0) {
RTSP2Web_Manager->add_to_RTSP2Web();
}
}
if (janus_enabled and Janus_Manager) {
if (Janus_Manager->check_janus() == 0) {
Janus_Manager->add_to_janus();
@@ -3331,7 +3352,7 @@ int Monitor::PrimeCapture() {
} // end if rtsp_server
//Poller Thread
if (onvif_event_listener || janus_enabled || use_Amcrest_API) {
if (onvif_event_listener || janus_enabled || RTSP2Web_enabled ||use_Amcrest_API) {
if (!Poller) {
Debug(1, "Creating unique poller thread");
Poller = zm::make_unique<PollThread>(this);
@@ -3411,6 +3432,12 @@ int Monitor::Close() {
soap = nullptr;
} //End ONVIF
#endif
//RTSP2Web teardoen
if (RTSP2Web_enabled and (purpose == CAPTURE) and RTSP2Web_Manager) {
delete RTSP2Web_Manager;
RTSP2Web_Manager = nullptr;
}
//Janus Teardown
if (janus_enabled and (purpose == CAPTURE) and Janus_Manager) {
delete Janus_Manager;
+31
View File
@@ -111,6 +111,12 @@ public:
DECODING_ALWAYS
} DecodingOption;
typedef enum {
HLS,
MSE,
WEBRTC
} RTSP2WebOption;
typedef enum {
LOCAL=1,
REMOTE,
@@ -332,6 +338,28 @@ protected:
int start_Amcrest();
};
class RTSP2WebManager {
protected:
Monitor *parent;
CURL *curl = nullptr;
//helper class for CURL
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
bool RTSP2Web_Healthy;
bool Use_RTSP_Restream;
std::string RTSP2Web_endpoint;
std::string rtsp_username;
std::string rtsp_password;
std::string rtsp_path;
public:
explicit RTSP2WebManager(Monitor *parent_);
~RTSP2WebManager();
void load_from_monitor();
int add_to_RTSP2Web();
int check_RTSP2Web();
int remove_from_RTSP2Web();
};
class JanusManager {
protected:
Monitor *parent;
@@ -379,6 +407,8 @@ protected:
RecordingSourceOption recording_source; // Primary, Secondary, Both
DecodingOption decoding; // Whether the monitor will decode h264/h265 packets
bool RTSP2Web_enabled; // Whether we set the h264/h265 stream up on RTSP2Web
int RTSP2Web_type; // Whether we set the h264/h265 stream up on RTSP2Web
bool janus_enabled; // Whether we set the h264/h265 stream up on janus
bool janus_audio_enabled; // Whether we tell Janus to try to include audio.
std::string janus_profile_override; // The Profile-ID to force the stream to use.
@@ -575,6 +605,7 @@ protected:
bool Event_Poller_Healthy;
bool Event_Poller_Closes_Event;
RTSP2WebManager *RTSP2Web_Manager;
JanusManager *Janus_Manager;
AmcrestAPI *Amcrest_Manager;
+185
View File
@@ -0,0 +1,185 @@
//
// ZoneMinder Monitor::RTSP2WebManager Class Implementation, $Date$, $Revision$
// Copyright (C) 2022 Jonathan Bennett
//
// 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.
//
#include "zm_crypt.h"
#include "zm_monitor.h"
#include "zm_server.h"
#include "zm_time.h"
#include <regex>
std::string remove_newlines(std::string input);
std::string escape_json_string(std::string input);
Monitor::RTSP2WebManager::RTSP2WebManager(Monitor *parent_) :
parent(parent_),
RTSP2Web_Healthy(false)
{
Use_RTSP_Restream = false;
if ((config.rtsp2web_path != nullptr) && (config.rtsp2web_path[0] != '\0')) {
RTSP2Web_endpoint = config.rtsp2web_path;
//remove the trailing slash if present
if (RTSP2Web_endpoint.back() == '/') RTSP2Web_endpoint.pop_back();
} else {
RTSP2Web_endpoint = "demo:demo@127.0.0.1:8083";
}
rtsp_path = parent->path;
if (!parent->user.empty()) {
rtsp_username = escape_json_string(parent->user);
rtsp_password = escape_json_string(parent->pass);
if (rtsp_path.find("rtsp://") == 0) {
rtsp_path = "rtsp://" + rtsp_username + ":" + rtsp_password + "@" + rtsp_path.substr(7, std::string::npos);
} else {
rtsp_path = "rtsp://" + rtsp_username + ":" + rtsp_password + "@" + rtsp_path;
}
}
Debug(1, "Monitor %u rtsp url is %s", parent->id, rtsp_path.c_str());
}
Monitor::RTSP2WebManager::~RTSP2WebManager() {
remove_from_RTSP2Web();
}
int Monitor::RTSP2WebManager::check_RTSP2Web() {
curl = curl_easy_init();
if (!curl) return -1;
//Assemble our actual request
std::string response;
std::string endpoint = RTSP2Web_endpoint+"/stream/"+std::to_string(parent->id)+"/info";
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
Warning("Attempted to send to %s and got %s", endpoint.c_str(), curl_easy_strerror(res));
return -1;
}
Debug(1, "Queried for stream status: %s", remove_newlines(response).c_str());
if (response.find("\"status\": 0") != std::string::npos) {
if (response.find("stream not found") != std::string::npos) {
Warning("Mountpoint Missing");
return 0;
} else {
Warning("unknown RTSP2Web error");
return 0;
}
}
return 1;
}
int Monitor::RTSP2WebManager::add_to_RTSP2Web() {
Debug(1, "Adding stream to RTSP2Web");
curl = curl_easy_init();
if (!curl) {
Error("Failed to init curl");
return -1;
}
std::string endpoint = RTSP2Web_endpoint+"/stream/"+std::to_string(parent->id)+"/add";
//Assemble our actual request
std::string postData = "{\"name\" : \"";
postData += std::string(parent->Name());
postData += "\", \"channels\" : { \"0\" : {";
postData += "\"name\" : \"ch1\", \"url\" : \"";
postData += rtsp_path;
postData += "\", \"on_demand\": true, \"debug\": false, \"status\": 0}}}";
Debug(1, "Sending %s to %s", postData.c_str(), endpoint.c_str());
CURLcode res;
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
Error("Failed to curl_easy_perform adding rtsp stream");
return -1;
}
Debug(1, "Adding stream response: %s", remove_newlines(response).c_str());
//scan for missing session or handle id "No such session" "no such handle"
if (response.find("\"status\": 1") != std::string::npos) {
Warning("RTSP2Web failed adding stream");
return -2;
}
Debug(1,"Added stream to RTSP2Web: %s", response.c_str());
return 0;
}
int Monitor::RTSP2WebManager::remove_from_RTSP2Web() {
curl = curl_easy_init();
if (!curl) return -1;
std::string endpoint = RTSP2Web_endpoint+"/stream/"+std::to_string(parent->id)+"/delete";
std::string response;
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
} else {
Debug(1, "Removed stream from RTSP2Web: %s", remove_newlines(response).c_str());
}
curl_easy_cleanup(curl);
return 0;
}
size_t Monitor::RTSP2WebManager::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}
std::string remove_newlines( std::string str ) {
while (!str.empty() && str.find("\n") != std::string::npos)
str.erase(std::remove(str.begin(), str.end(), '\n'), str.cend());
return str;
}
/*
std::string escape_json_string( std::string input ) {
std::string tmp;
tmp = regex_replace(input, std::regex("\n"), "\\n");
tmp = regex_replace(tmp, std::regex("\b"), "\\b");
tmp = regex_replace(tmp, std::regex("\f"), "\\f");
tmp = regex_replace(tmp, std::regex("\r"), "\\r");
tmp = regex_replace(tmp, std::regex("\t"), "\\t");
tmp = regex_replace(tmp, std::regex("\""), "\\\"");
tmp = regex_replace(tmp, std::regex("[\\\\]"), "\\\\");
return tmp;
}
*/