diff --git a/CMakeLists.txt b/CMakeLists.txt index 7df6aaa70..1a65f85eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -318,7 +318,7 @@ endif() # Do not check for cURL if ZM_NO_CURL is on if(NOT ZM_NO_CURL) # cURL - find_package(CURL) + find_package(CURL REQUIRED) if(CURL_FOUND) set(HAVE_LIBCURL 1) list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index fb2b8cf21..92dc7a706 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -471,6 +471,7 @@ CREATE TABLE `Monitors` ( `ONVIF_Password` VARCHAR(64) NOT NULL DEFAULT '', `ONVIF_Options` VARCHAR(64) NOT NULL DEFAULT '', `ONVIF_Event_Listener` BOOLEAN NOT NULL DEFAULT FALSE, + `use_Amcrest_API` BOOLEAN NOT NULL DEFAULT FALSE, `Device` tinytext NOT NULL default '', `Channel` tinyint(3) unsigned NOT NULL default '0', `Format` int(10) unsigned NOT NULL default '0', diff --git a/db/zm_update-1.37.11.sql b/db/zm_update-1.37.11.sql new file mode 100644 index 000000000..58a3d0cf1 --- /dev/null +++ b/db/zm_update-1.37.11.sql @@ -0,0 +1,18 @@ +-- +-- Update Monitors table to have use_Amcrest_API +-- + +SELECT 'Checking for use_Amcrest_API in Monitors'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'use_Amcrest_API' + ) > 0, +"SELECT 'Column use_Amcrest_API already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `use_Amcrest_API` BOOLEAN NOT NULL default false AFTER `ONVIF_Event_Listener`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/dep/RtspServer b/dep/RtspServer index eab328514..1b40f1661 160000 --- a/dep/RtspServer +++ b/dep/RtspServer @@ -1 +1 @@ -Subproject commit eab32851421ffe54fec0229c3efc44c642bc8d46 +Subproject commit 1b40f1661f93f50fd5805f239d1e466a3bcf888f diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index b95d80626..f15e60326 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -36,7 +36,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.37.10 +Version: 1.37.11 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index b92ff267a..a5162d185 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -35,7 +35,7 @@ Run the following commands. sudo apt install mariadb-server sudo apt install zoneminder -When mariadb is installed for the first time, it doesn't add a password to the root user. Therefore, for security, it is recommended to run ``mysql secure installation``. +By default MariaDB uses `unix socket authentication`_, so no root user password is required (root MariaDB user access only available to local root Linux user). If you wish, you can set a root MariaDB password (and apply other security tweaks) by running `mariadb-secure-installation`_. **Step 3:** Setup permissions for zm.conf @@ -337,3 +337,6 @@ Reload Apache to enable your changes and then start ZoneMinder. sudo systemctl start zoneminder You are now ready to go with ZoneMinder. Open a browser and type either ``localhost/zm`` one the local machine or ``{IP-OF-ZM-SERVER}/zm`` if you connect from a remote computer. + +.. _unix socket authentication: https://mariadb.com/kb/en/authentication-plugin-unix-socket/ +.. _mariadb-secure-installation: https://mariadb.com/kb/en/mysql_secure_installation/ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index 54ce25c87..10cfffe8b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -63,6 +63,7 @@ $serial = $primary_key = 'Id'; ONVIF_Password ONVIF_Options ONVIF_Event_Listener + use_Amcrest_API Device Channel Format diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 35026738e..0465ecd39 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -93,7 +93,7 @@ std::string load_monitor_sql = "`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`," "`RTSPServer`, `RTSPStreamName`," -"`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, " +"`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, `use_Amcrest_API`, " "`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`"; std::string CameraType_Strings[] = { @@ -434,9 +434,9 @@ Monitor::Monitor() privacy_bitmask(nullptr), n_linked_monitors(0), linked_monitors(nullptr), + ONVIF_Closes_Event(FALSE), #ifdef WITH_GSOAP soap(nullptr), - ONVIF_Closes_Event(FALSE), #endif red_val(0), green_val(0), @@ -460,7 +460,6 @@ Monitor::Monitor() } // Monitor::Monitor /* -<<<<<<< HEAD std::string load_monitor_sql = "SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Capturing`+0, `Analysing`+0, `AnalysisSource`, `Recording`+0, `RecordingSource`, `DecodingEnabled`, JanusEnabled, JanusAudioEnabled, " @@ -476,7 +475,7 @@ Monitor::Monitor() "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," "`RTSPServer`,`RTSPStreamName`, - "`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, " + "`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, `use_Amcrest_API`, " "SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */ @@ -656,6 +655,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { onvif_password = std::string(dbrow[col] ? dbrow[col] : ""); col++; onvif_options = std::string(dbrow[col] ? dbrow[col] : ""); col++; onvif_event_listener = (*dbrow[col] != '0'); col++; + use_Amcrest_API = (*dbrow[col] != '0'); col++; /*"SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */ signal_check_points = atoi(dbrow[col]); col++; @@ -1057,6 +1057,7 @@ bool Monitor::connect() { Debug(3, "Allocated %zu %zu image buffers", image_buffer.capacity(), image_buffer.size()); if (purpose == CAPTURE) { + curl_global_init(CURL_GLOBAL_DEFAULT); //May not be the appropriate place. Need to do this before any other curl calls, and any other threads start. memset(mem_ptr, 0, mem_size); shared_data->size = sizeof(SharedData); shared_data->analysing = analysing; @@ -1098,47 +1099,59 @@ bool Monitor::connect() { usedsubpixorder = camera->SubpixelOrder(); // Used in CheckSignal shared_data->valid = true; -#ifdef WITH_GSOAP - //ONVIF Setup + //ONVIF and Amcrest Setup + //since they serve the same function, handling them as two options of the same feature. ONVIF_Trigger_State = FALSE; - if (onvif_event_listener) { //Temporarily using this option to enable the feature + if (onvif_event_listener) { // Debug(1, "Starting ONVIF"); ONVIF_Healthy = FALSE; if (onvif_options.find("closes_event") != std::string::npos) { //Option to indicate that ONVIF will send a close event message ONVIF_Closes_Event = TRUE; } - tev__PullMessages.Timeout = "PT600S"; - tev__PullMessages.MessageLimit = 100; - soap = soap_new(); - soap->connect_timeout = 5; - soap->recv_timeout = 5; - soap->send_timeout = 5; - soap_register_plugin(soap, soap_wsse); - proxyEvent = PullPointSubscriptionBindingProxy(soap); - std::string full_url = onvif_url + "/Events"; - proxyEvent.soap_endpoint = full_url.c_str(); + if (use_Amcrest_API) { + curl_multi = curl_multi_init(); + start_Amcrest(); + //spin up curl_multi + //use the onvif_user and onvif_pass and onvif_url here. + //going to use the non-blocking curl api, and in the polling thread, block for 5 seconds waiting for input, just like onvif + //note that it's not possible for a single camera to use both. + } else { //using GSOAP +#ifdef WITH_GSOAP + tev__PullMessages.Timeout = "PT600S"; + tev__PullMessages.MessageLimit = 100; + soap = soap_new(); + soap->connect_timeout = 5; + soap->recv_timeout = 5; + soap->send_timeout = 5; + soap_register_plugin(soap, soap_wsse); + proxyEvent = PullPointSubscriptionBindingProxy(soap); + std::string full_url = onvif_url + "/Events"; + proxyEvent.soap_endpoint = full_url.c_str(); - set_credentials(soap); - Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); - if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { - Error("Couldn't create subscription! %s, %s", soap_fault_string(soap), soap_fault_detail(soap)); - } else { - //Empty the stored messages set_credentials(soap); - if ((proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) && - ( soap->error != SOAP_EOF)) { //SOAP_EOF could indicate no messages to pull. - Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap)); + Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); + if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { + Error("Couldn't create subscription! %s, %s", soap_fault_string(soap), soap_fault_detail(soap)); } else { - Debug(1, "Good Initial ONVIF Pull"); - ONVIF_Healthy = TRUE; + //Empty the stored messages + set_credentials(soap); + if ((proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) && + ( soap->error != SOAP_EOF)) { //SOAP_EOF could indicate no messages to pull. + Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap)); + } else { + Debug(1, "Good Initial ONVIF Pull"); + ONVIF_Healthy = TRUE; + } } +#else + Error("zmc not compiled with GSOAP. ONVIF support not built in!"); +#endif } } else { Debug(1, "Not Starting ONVIF"); } //End ONVIF Setup -#endif #if HAVE_LIBCURL //janus setup. Depends on libcurl. if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { @@ -1247,6 +1260,12 @@ Monitor::~Monitor() { sws_freeContext(convert_context); convert_context = nullptr; } + if (Amcrest_handle != nullptr) { + curl_multi_remove_handle(curl_multi, Amcrest_handle); + curl_easy_cleanup(Amcrest_handle); + } + if (curl_multi != nullptr) curl_multi_cleanup(curl_multi); + curl_global_cleanup(); } // end Monitor::~Monitor() void Monitor::AddPrivacyBitmask() { @@ -1818,36 +1837,25 @@ bool Monitor::Poll() { //We want to trigger every 5 seconds or so. so grab the time at the beginning of the loop, and sleep at the end. std::chrono::system_clock::time_point loop_start_time = std::chrono::system_clock::now(); -#ifdef WITH_GSOAP if (ONVIF_Healthy) { - set_credentials(soap); - int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse); - if (result != SOAP_OK) { - if (result != SOAP_EOF) { //Ignore the timeout error - Error("Failed to get ONVIF messages! %s", soap_fault_string(soap)); - ONVIF_Healthy = FALSE; - } - } else { - Debug(1, "Got Good Response! %i", result); - for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) { - if (msg->Topic->__any.text != NULL && - std::strstr(msg->Topic->__any.text, "MotionAlarm") && - msg->Message.__any.elts != NULL && - msg->Message.__any.elts->next != NULL && - msg->Message.__any.elts->next->elts != NULL && - msg->Message.__any.elts->next->elts->atts != NULL && - msg->Message.__any.elts->next->elts->atts->next != NULL && - msg->Message.__any.elts->next->elts->atts->next->text != NULL) { - Debug(1,"Got Motion Alarm!"); - if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) { + if(use_Amcrest_API) { + int open_handles; + int transfers; + curl_multi_perform(curl_multi, &open_handles); + if (open_handles == 0) { + start_Amcrest(); //http transfer ended, need to restart. + } else { + curl_multi_wait(curl_multi, NULL, 0, 5000, &transfers); //wait for max 5 seconds for event. + if (transfers > 0) { //have data to deal with + curl_multi_perform(curl_multi, &open_handles); //actually grabs the data, populates amcrest_response + if (amcrest_response.find("action=Start") != std::string::npos) { //Event Start - Debug(1,"Triggered on ONVIF"); + Debug(1,"Triggered on ONVIF"); if (!ONVIF_Trigger_State) { Debug(1,"Triggered Event"); ONVIF_Trigger_State = TRUE; - std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep } - } else { + } else if (amcrest_response.find("action=Stop") != std::string::npos){ Debug(1, "Triggered off ONVIF"); ONVIF_Trigger_State = FALSE; if (!ONVIF_Closes_Event) { //If we get a close event, then we know to expect them. @@ -1855,11 +1863,53 @@ bool Monitor::Poll() { Debug(1,"Setting ClosesEvent"); } } + amcrest_response.clear(); //We've dealt with the message, need to clear the queue } } + } else { + +#ifdef WITH_GSOAP + set_credentials(soap); + int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse); + if (result != SOAP_OK) { + if (result != SOAP_EOF) { //Ignore the timeout error + Error("Failed to get ONVIF messages! %s", soap_fault_string(soap)); + ONVIF_Healthy = FALSE; + } + } else { + Debug(1, "Got Good Response! %i", result); + for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) { + if (msg->Topic->__any.text != NULL && + std::strstr(msg->Topic->__any.text, "MotionAlarm") && + msg->Message.__any.elts != NULL && + msg->Message.__any.elts->next != NULL && + msg->Message.__any.elts->next->elts != NULL && + msg->Message.__any.elts->next->elts->atts != NULL && + msg->Message.__any.elts->next->elts->atts->next != NULL && + msg->Message.__any.elts->next->elts->atts->next->text != NULL) { + Debug(1,"Got Motion Alarm!"); + if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) { + //Event Start + Debug(1,"Triggered on ONVIF"); + if (!ONVIF_Trigger_State) { + Debug(1,"Triggered Event"); + ONVIF_Trigger_State = TRUE; + std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep + } + } else { + Debug(1, "Triggered off ONVIF"); + ONVIF_Trigger_State = FALSE; + if (!ONVIF_Closes_Event) { //If we get a close event, then we know to expect them. + ONVIF_Closes_Event = TRUE; + Debug(1,"Setting ClosesEvent"); + } + } + } + } + } +#endif } } -#endif if (janus_enabled) { if (janus_session.empty()) { get_janus_session(); @@ -1963,8 +2013,19 @@ bool Monitor::Analyse() { } else { event->addNote(SIGNAL_CAUSE, "Reacquired"); } - if (snap->image) + if (snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Debug(1, "assigning refimage from v-channel"); + Image v_image(snap->in_frame->width, + snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + ref_image.Assign(v_image); + } else if (snap->image) { + Debug(1, "assigning refimage from snap->image"); ref_image.Assign(*(snap->image)); + } } shared_data->state = state = IDLE; } // end if signal change @@ -2043,12 +2104,32 @@ bool Monitor::Analyse() { // decoder may not have been able to provide an image if (!ref_image.Buffer()) { Debug(1, "Assigning instead of Detecting"); - ref_image.Assign(*(snap->image)); + if (snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Debug(1, "assigning refimage from v-channel"); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + ref_image.Assign(v_image); + } else { + Debug(1, "assigning refimage from snap->image"); + ref_image.Assign(*(snap->image)); + } alarm_image.Assign(*(snap->image)); } else if (!(analysis_image_count % (motion_frame_skip+1))) { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); // Get new score. - snap->score = DetectMotion(*(snap->image), zoneSet); + if (snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + snap->score = DetectMotion(v_image, zoneSet); + } else { + snap->score = DetectMotion(*(snap->image), zoneSet); + } if (!snap->analysis_image) snap->analysis_image = new Image(*(snap->image)); @@ -2263,14 +2344,40 @@ bool Monitor::Analyse() { if ((analysing == ANALYSING_ALWAYS) and snap->image) { if (!ref_image.Buffer()) { - Debug(1, "Assigning"); - ref_image.Assign(*(snap->image)); + if (snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Debug(1, "Assigning from vchannel"); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + ref_image.Assign(v_image); + } else if (snap->image) { + Debug(1, "Assigning"); + ref_image.Assign(*(snap->image)); + } } else { - Debug(1, "Blending"); - ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); - Debug(1, "Done Blending"); - } - } + if (snap->in_frame && + ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) + ) { + Debug(1, "Blending from vchannel"); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + ref_image.Blend(v_image, ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + } else if (snap->image) { + Debug(1, "Blending because %p and format %d != %d, %d", snap->in_frame, + snap->in_frame->format, + AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUVJ420P + ); + ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + Debug(1, "Done Blending"); + } + } // end if have image + } // end if detecting last_signal = signal; } // end if videostream } // end if signal @@ -3232,11 +3339,15 @@ int Monitor::Close() { analysis_thread->Stop(); } -#ifdef WITH_GSOAP //ONVIF Teardown if (Poller) { Poller->Stop(); } + if (curl_multi != nullptr) { + curl_multi_cleanup(curl_multi); + curl_multi = nullptr; + } +#ifdef WITH_GSOAP if (onvif_event_listener && (soap != nullptr)) { Debug(1, "Tearing Down Onvif"); _wsnt__Unsubscribe wsnt__Unsubscribe; @@ -3249,9 +3360,9 @@ int Monitor::Close() { } //End ONVIF #endif #if HAVE_LIBCURL //Janus Teardown - if (janus_enabled && (purpose == CAPTURE)) { - remove_from_janus(); - } + if (janus_enabled && (purpose == CAPTURE)) { + remove_from_janus(); + } #endif packetqueue.clear(); @@ -3623,3 +3734,55 @@ int Monitor::get_janus_session() { curl_easy_cleanup(curl); return 1; } //get_janus_session + +int Monitor::start_Amcrest() { + //init the transfer and start it in multi-handle + int running_handles; + long response_code; + struct CURLMsg *m; + CURLMcode curl_error; + if (Amcrest_handle != nullptr) { //potentially clean up the old handle + curl_multi_remove_handle(curl_multi, Amcrest_handle); + curl_easy_cleanup(Amcrest_handle); + } + + std::string full_url = onvif_url; + if (full_url.back() != '/') full_url += '/'; + full_url += "eventManager.cgi?action=attach&codes=[VideoMotion]"; + Amcrest_handle = curl_easy_init(); + if (!Amcrest_handle){ + Warning("Handle is null!"); + return -1; + } + curl_easy_setopt(Amcrest_handle, CURLOPT_URL, full_url.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEDATA, &amcrest_response); + curl_easy_setopt(Amcrest_handle, CURLOPT_USERNAME, onvif_username.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_PASSWORD, onvif_password.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + curl_error = curl_multi_add_handle(curl_multi, Amcrest_handle); + Warning("error of %i", curl_error); + curl_error = curl_multi_perform(curl_multi, &running_handles); + if (curl_error == CURLM_OK) { + curl_easy_getinfo(Amcrest_handle, CURLINFO_RESPONSE_CODE, &response_code); + int msgq = 0; + m = curl_multi_info_read(curl_multi, &msgq); + if (m && (m->msg == CURLMSG_DONE)) { + Warning("Libcurl exited Early: %i", m->data.result); + } + + curl_multi_wait(curl_multi, NULL, 0, 300, NULL); + curl_error = curl_multi_perform(curl_multi, &running_handles); + } + + if ((curl_error == CURLM_OK) && (running_handles > 0)) { + ONVIF_Healthy = TRUE; + } else { + Warning("Response: %s", amcrest_response.c_str()); + Warning("Seeing %i streams, and error of %i, url: %s", running_handles, curl_error, full_url.c_str()); + curl_easy_getinfo(Amcrest_handle, CURLINFO_OS_ERRNO, &response_code); + Warning("Response code: %lu", response_code); + } + +return 0; +} diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 0042a9774..160cfa605 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -324,7 +324,9 @@ protected: std::string onvif_username; std::string onvif_password; std::string onvif_options; + std::string amcrest_response; bool onvif_event_listener; + bool use_Amcrest_API; std::string device; int palette; @@ -476,11 +478,12 @@ protected: std::string diag_path_delta; //ONVIF -#ifdef WITH_GSOAP - struct soap *soap; - bool ONVIF_Trigger_State; + bool ONVIF_Trigger_State; //Re-using some variables for Amcrest API support bool ONVIF_Healthy; bool ONVIF_Closes_Event; + +#ifdef WITH_GSOAP + struct soap *soap = nullptr; _tev__CreatePullPointSubscription request; _tev__CreatePullPointSubscriptionResponse response; _tev__PullMessages tev__PullMessages; @@ -489,8 +492,11 @@ protected: void set_credentials(struct soap *soap); #endif - //curl stuff for Janus - CURL *curl; + //curl stuff + CURL *curl = nullptr; + CURLM *curl_multi = nullptr; + CURL *Amcrest_handle = nullptr; + int start_Amcrest(); //helper class for CURL static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); int add_to_janus(); diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 2ea949078..9737c794a 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -118,7 +118,7 @@ commonprep () { fi fi - RTSPVER="cd7fd49becad6010a1b8466bfebbd93999a39878" + RTSPVER="eab32851421ffe54fec0229c3efc44c642bc8d46" if [ -e "build/RtspServer-${RTSPVER}.tar.gz" ]; then echo "Found existing RtspServer ${RTSPVER} tarball..." else diff --git a/version b/version index a9009bf2a..f951feb88 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.37.10 +1.37.11 diff --git a/web/ajax/events.php b/web/ajax/events.php index 4db6b99cc..c95b404c4 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -187,6 +187,9 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $col_str = 'E.*, M.Name AS Monitor'; $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.($sort?' ORDER BY '.$sort.' '.$order:''); + if ($filter->limit() and !count($filter->pre_sql_conditions()) and !count($filter->post_sql_conditions())) { + $sql .= ' LIMIT '.$filter->limit(); + } $storage_areas = ZM\Storage::find(); $StorageById = array(); @@ -213,6 +216,12 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $unfiltered_rows[] = $row; } # end foreach row + # Filter limits come before pagination limits. + if ($filter->limit() and ($filter->limit() > count($unfiltered_rows))) { + ZM\Debug("Filtering rows due to filter->limit " . count($unfiltered_rows)." limit: ".$filter->limit()); + $unfiltered_rows = array_slice($unfiltered_rows, 0, $filter->limit()); + } + ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.'); $filtered_rows = null; @@ -251,8 +260,10 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $filtered_rows = $unfiltered_rows; } # end if search_filter->terms() > 1 - if ($limit) + if ($limit) { + ZM\Debug("Filtering rows due to limit " . count($filtered_rows)." offset: $offset limit: $limit"); $filtered_rows = array_slice($filtered_rows, $offset, $limit); + } $returned_rows = array(); foreach ($filtered_rows as $row) { diff --git a/web/includes/Filter.php b/web/includes/Filter.php index 4fdd8e43f..2b599fe36 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -240,7 +240,7 @@ class Filter extends ZM_Object { } if ( isset( $this->Query()['limit'] ) ) return $this->{'Query'}['limit']; - return 100; + return 0; #return $this->defaults{'limit'}; } diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 26978cf2b..79cc0d9e5 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -110,7 +110,6 @@ class Monitor extends ZM_Object { 'ManufacturerId' => null, 'ModelId' => null, 'Type' => 'Ffmpeg', - 'Function' => 'Mocord', 'Capturing' => 'Always', 'Analysing' => 'Always', 'Recording' => 'Always', @@ -127,6 +126,7 @@ class Monitor extends ZM_Object { 'ONVIF_Password' => '', 'ONVIF_Options' => '', 'ONVIF_Event_Listener' => '0', + 'use_Amcrest_API' => '0', 'Device' => '', 'Channel' => 0, 'Format' => '0', @@ -414,7 +414,7 @@ class Monitor extends ZM_Object { if ($mode == 'restart') { daemonControl('stop', 'zmc', $zmcArgs); } - if ($this->{'Function'} != 'None') { + if ($this->{'Capturing'} != 'None') { daemonControl('start', 'zmc', $zmcArgs); } } @@ -789,7 +789,7 @@ class Monitor extends ZM_Object { fps fps '; - if ( $this->Function() == 'Modect' or $this->Function() == 'Mocord' ) { + if ($this->Analysing() != 'None') { $html .= ' fps '; } diff --git a/web/includes/actions/monitor.php b/web/includes/actions/monitor.php index b1f5dff2e..edc9ab1bd 100644 --- a/web/includes/actions/monitor.php +++ b/web/includes/actions/monitor.php @@ -316,7 +316,7 @@ if ($action == 'save') { } # end if ZM_OPT_X10 if ( $restart ) { - if ( $monitor->Function() != 'None' and $monitor->Type() != 'WebSite' ) { + if ( $monitor->Capturing() != 'None' and $monitor->Type() != 'WebSite' ) { $monitor->zmcControl('start'); if ( $monitor->Controllable() ) { diff --git a/web/skins/classic/views/js/monitor.js b/web/skins/classic/views/js/monitor.js index a6f53e652..02cbf8fc6 100644 --- a/web/skins/classic/views/js/monitor.js +++ b/web/skins/classic/views/js/monitor.js @@ -275,6 +275,23 @@ function initPage() { } }); + // Amcrest API controller + if (document.getElementsByName("newMonitor[ONVIF_Event_Listener]")[0].checked) { + document.getElementById("function_use_Amcrest_API").hidden = false; + } else { + document.getElementById("function_use_Amcrest_API").hidden = true; + } + document.getElementsByName("newMonitor[ONVIF_Event_Listener]")[0].addEventListener('change', function() { + if (this.checked) { + document.getElementById("function_use_Amcrest_API").hidden = false; + } + }); + document.getElementsByName("newMonitor[ONVIF_Event_Listener]")[1].addEventListener('change', function() { + if (this.checked) { + document.getElementById("function_use_Amcrest_API").hidden = true; + } + }); + if ( ZM_OPT_USE_GEOLOCATION ) { if ( window.L ) { var form = document.getElementById('contentForm'); diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index dd24ab55b..757a64d6a 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -754,6 +754,10 @@ if (count($available_monitor_ids)) { translate('Enabled'), '0'=>'Disabled'), $monitor->ONVIF_Event_Listener()); ?> + + + translate('Enabled'), '0'=>'Disabled'), $monitor->use_Amcrest_API()); ?> +