mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-27 16:58:31 -04:00
Adds RTSP2Web support for live view
This commit is contained in:
1 parent
9295c390ca
commit
5c9588c012
20 files changed
+530
-30
No files matched your search
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
*/
|
||||
Reference in new issue
Block a user