diff --git a/cmake/compiler/gcc/settings.cmake b/cmake/compiler/gcc/settings.cmake index 87ff6e939..497db5a17 100644 --- a/cmake/compiler/gcc/settings.cmake +++ b/cmake/compiler/gcc/settings.cmake @@ -1,6 +1,7 @@ target_compile_options(zm-warning-interface INTERFACE -Wall + -Wconditionally-supported -Wextra -Wformat-security -Wno-cast-function-type diff --git a/dep/RtspServer b/dep/RtspServer index 20846b25f..c81ec629f 160000 --- a/dep/RtspServer +++ b/dep/RtspServer @@ -1 +1 @@ -Subproject commit 20846b25ffc0c9a1de1b6701ca99425ef39e9f3f +Subproject commit c81ec629f091b77e2948b1e6113064de7e9bc9e3 diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index 474148df3..d1f50cf43 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -138,6 +138,14 @@ if ( $options{command} ) { Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); } + my $zm_terminate = 0; + sub TermHandler { + Info('Received TERM, exiting'); + $zm_terminate = 1; + } + $SIG{TERM} = \&TermHandler; + $SIG{INT} = \&TermHandler; + Info("Control server $id/$protocol starting at " .strftime('%y/%m/%d %H:%M:%S', localtime()) ); @@ -166,7 +174,7 @@ if ( $options{command} ) { my $win = $rin; my $ein = $win; my $timeout = MAX_COMMAND_WAIT; - while ( 1 ) { + while (!$zm_terminate) { my $nfound = select(my $rout = $rin, undef, undef, $timeout); if ( $nfound > 0 ) { if ( vec($rout, fileno(SERVER), 1) ) { @@ -202,7 +210,7 @@ if ( $options{command} ) { } else { Debug('Select timed out'); } - } # end while forever + } # end while !$zm_terminate Info("Control server $id/$protocol exiting"); unlink($sock_file); $control->close(); diff --git a/scripts/zmstats.pl.in b/scripts/zmstats.pl.in index 81fdd3129..7cdf93b5a 100644 --- a/scripts/zmstats.pl.in +++ b/scripts/zmstats.pl.in @@ -28,13 +28,20 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); +my $zm_terminate = 0; +sub TermHandler { + Info('Received TERM, exiting'); + $zm_terminate = 1; +} +$SIG{TERM} = \&TermHandler; +$SIG{INT} = \&TermHandler; Info('Stats Daemon starting in '.START_DELAY.' seconds'); sleep(START_DELAY); my $dbh = zmDbConnect(); -while( 1 ) { +while (!$zm_terminate) { while ( ! ( $dbh and $dbh->ping() ) ) { Info('Reconnecting to db'); if ( !($dbh = zmDbConnect()) ) { @@ -95,7 +102,7 @@ while( 1 ) { zmDbDo('DELETE FROM Sessions WHERE access < ? LIMIT 100', time - (60*60*24*7)); sleep($Config{ZM_STATS_UPDATE_INTERVAL}); -} # end while (1) +} # end while (!$zm_terminate) Info('Stats Daemon exiting'); exit(); diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index 5fc632165..c409bde33 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -87,6 +87,13 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); +my $zm_terminate = 0; +sub TermHandler { + Info('Received TERM, exiting'); + $zm_terminate = 1; +} +$SIG{TERM} = \&TermHandler; +$SIG{INT} = \&TermHandler; Info('Trigger daemon starting'); @@ -118,7 +125,7 @@ my $win = $rin; my $ein = $win; my $timeout = SELECT_TIMEOUT; my %actions; -while (1) { +while (!$zm_terminate) { $rin = $base_rin; # Add the file descriptors of any spawned connections foreach my $fileno ( keys %spawned_connections ) { @@ -312,7 +319,7 @@ while (1) { # zmDbConnect will ping and reconnect if neccessary $dbh = zmDbConnect(); -} # end while ( 1 ) +} # end while (!$zm_terminate) Info('Trigger daemon exiting'); exit; diff --git a/scripts/zmwatch.pl.in b/scripts/zmwatch.pl.in index d82c74b99..f1fbd16b4 100644 --- a/scripts/zmwatch.pl.in +++ b/scripts/zmwatch.pl.in @@ -68,6 +68,13 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); +my $zm_terminate = 0; +sub TermHandler { + Info('Received TERM, exiting'); + $zm_terminate = 1; +} +$SIG{TERM} = \&TermHandler; +$SIG{INT} = \&TermHandler; Info('Watchdog starting, pausing for '.START_DELAY.' seconds'); sleep(START_DELAY); @@ -77,7 +84,7 @@ my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'S my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); -while( 1 ) { +while (!$zm_terminate) { while ( ! ( $dbh and $dbh->ping() ) ) { if ( ! ( $dbh = zmDbConnect() ) ) { sleep($Config{ZM_WATCH_CHECK_INTERVAL}); @@ -192,7 +199,7 @@ while( 1 ) { } # end foreach monitor sleep($Config{ZM_WATCH_CHECK_INTERVAL}); -} # end while (1) +} # end while (!$zm_terminate) Info("Watchdog exiting"); exit(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c6a9c04c7..37b08ad58 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,7 @@ set(ZM_BIN_SRC_FILES zm_crypt.cpp zm.cpp zm_db.cpp + zm_decoder_thread.cpp zm_logger.cpp zm_event.cpp zm_eventstream.cpp diff --git a/src/zm_analysis_thread.cpp b/src/zm_analysis_thread.cpp index 3e66940a0..7a6b01bbb 100644 --- a/src/zm_analysis_thread.cpp +++ b/src/zm_analysis_thread.cpp @@ -1,10 +1,13 @@ #include "zm_analysis_thread.h" +#include "zm_monitor.h" #include "zm_signal.h" #include "zm_utils.h" -AnalysisThread::AnalysisThread(std::shared_ptr monitor) : - monitor_(std::move(monitor)), terminate_(false) { +//AnalysisThread::AnalysisThread(std::shared_ptr monitor) : +AnalysisThread::AnalysisThread(Monitor* monitor) : + monitor_(monitor), terminate_(false) { + //monitor_(std::move(monitor)), terminate_(false) { thread_ = std::thread(&AnalysisThread::Run, this); } diff --git a/src/zm_analysis_thread.h b/src/zm_analysis_thread.h index 8cf389e93..7fc0fd8c9 100644 --- a/src/zm_analysis_thread.h +++ b/src/zm_analysis_thread.h @@ -1,14 +1,15 @@ #ifndef ZM_ANALYSIS_THREAD_H #define ZM_ANALYSIS_THREAD_H -#include "zm_monitor.h" +class Monitor; #include #include #include class AnalysisThread { public: - explicit AnalysisThread(std::shared_ptr monitor); + explicit AnalysisThread(Monitor* monitor); + //explicit AnalysisThread(std::shared_ptr monitor); ~AnalysisThread(); AnalysisThread(AnalysisThread &rhs) = delete; AnalysisThread(AnalysisThread &&rhs) = delete; @@ -18,7 +19,8 @@ class AnalysisThread { private: void Run(); - std::shared_ptr monitor_; + Monitor* monitor_; + //std::shared_ptr monitor_; std::atomic terminate_; std::thread thread_; }; diff --git a/src/zm_decoder_thread.cpp b/src/zm_decoder_thread.cpp new file mode 100644 index 000000000..3d522830d --- /dev/null +++ b/src/zm_decoder_thread.cpp @@ -0,0 +1,53 @@ +#include "zm_decoder_thread.h" + +#include "zm_monitor.h" +#include "zm_signal.h" + +//DecoderThread::DecoderThread(std::shared_ptr monitor) : +DecoderThread::DecoderThread(Monitor * monitor) : + monitor_(monitor), terminate_(false) { + //monitor_(std::move(monitor)), terminate_(false) { + thread_ = std::thread(&DecoderThread::Run, this); +} + +DecoderThread::~DecoderThread() { + Stop(); + if (thread_.joinable()) + thread_.join(); +} + +void DecoderThread::Run() { + Debug(2, "DecoderThread::Run() for %d", monitor_->Id()); + + //Microseconds decoder_rate = Microseconds(monitor_->GetDecoderRate()); + //Seconds decoder_update_delay = Seconds(monitor_->GetDecoderUpdateDelay()); + //Debug(2, "DecoderThread::Run() have update delay %d", decoder_update_delay); + + //TimePoint last_decoder_update_time = std::chrono::steady_clock::now(); + //TimePoint cur_time; + + while (!(terminate_ or zm_terminate)) { + // Some periodic updates are required for variable capturing framerate + //if (decoder_update_delay != Seconds::zero()) { + //cur_time = std::chrono::steady_clock::now(); + //Debug(2, "Updating adaptive skip"); + //if ((cur_time - last_decoder_update_time) > decoder_update_delay) { + //decoder_rate = Microseconds(monitor_->GetDecoderRate()); + //last_decoder_update_time = cur_time; + //} + //} + + if (!monitor_->Decode()) { + //if ( !(terminate_ or zm_terminate) ) { + //Microseconds sleep_for = monitor_->Active() ? Microseconds(ZM_SAMPLE_RATE) : Microseconds(ZM_SUSPENDED_RATE); + //Debug(2, "Sleeping for %" PRId64 "us", int64(sleep_for.count())); + //std::this_thread::sleep_for(sleep_for); + //} + //} else if (decoder_rate != Microseconds::zero()) { + //Debug(2, "Sleeping for %" PRId64 " us", int64(decoder_rate.count())); + //std::this_thread::sleep_for(decoder_rate); + //} else { + //Debug(2, "Not sleeping"); + } + } +} diff --git a/src/zm_decoder_thread.h b/src/zm_decoder_thread.h new file mode 100644 index 000000000..703a7e1e1 --- /dev/null +++ b/src/zm_decoder_thread.h @@ -0,0 +1,29 @@ +#ifndef ZM_DECODER_THREAD_H +#define ZM_DECODER_THREAD_H + +#include +#include +#include + +class Monitor; + +class DecoderThread { + public: + explicit DecoderThread(Monitor* monitor); + //explicit DecoderThread(std::shared_ptr monitor); + ~DecoderThread(); + DecoderThread(DecoderThread &rhs) = delete; + DecoderThread(DecoderThread &&rhs) = delete; + + void Stop() { terminate_ = true; } + + private: + void Run(); + + Monitor* monitor_; + //std::shared_ptr monitor_; + std::atomic terminate_; + std::thread thread_; +}; + +#endif diff --git a/src/zm_event.cpp b/src/zm_event.cpp index f0b2da694..9ab13fafc 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -71,8 +71,8 @@ Event::Event( std::string notes; createNotes(notes); - struct timeval now; - gettimeofday(&now, 0); + timeval now = {}; + gettimeofday(&now, nullptr); if ( !start_time.tv_sec ) { Warning("Event has zero time, setting to now"); @@ -80,12 +80,12 @@ Event::Event( } else if ( start_time.tv_sec > now.tv_sec ) { char buffer[26]; char buffer_now[26]; - struct tm* tm_info; + tm tm_info = {}; - tm_info = localtime(&start_time.tv_sec); - strftime(buffer, 26, "%Y:%m:%d %H:%M:%S", tm_info); - tm_info = localtime(&now.tv_sec); - strftime(buffer_now, 26, "%Y:%m:%d %H:%M:%S", tm_info); + localtime_r(&start_time.tv_sec, &tm_info); + strftime(buffer, 26, "%Y:%m:%d %H:%M:%S", &tm_info); + localtime_r(&now.tv_sec, &tm_info); + strftime(buffer_now, 26, "%Y:%m:%d %H:%M:%S", &tm_info); Error( "StartDateTime in the future starttime %u.%u >? now %u.%u difference %d\n%s\n%s", @@ -661,16 +661,17 @@ bool Event::SetPath(Storage *storage) { return false; } - struct tm *stime = localtime(&start_time.tv_sec); + tm stime = {}; + localtime_r(&start_time.tv_sec, &stime); if ( scheme == Storage::DEEP ) { int dt_parts[6]; - dt_parts[0] = stime->tm_year-100; - dt_parts[1] = stime->tm_mon+1; - dt_parts[2] = stime->tm_mday; - dt_parts[3] = stime->tm_hour; - dt_parts[4] = stime->tm_min; - dt_parts[5] = stime->tm_sec; + dt_parts[0] = stime.tm_year-100; + dt_parts[1] = stime.tm_mon+1; + dt_parts[2] = stime.tm_mday; + dt_parts[3] = stime.tm_hour; + dt_parts[4] = stime.tm_min; + dt_parts[5] = stime.tm_sec; std::string date_path; std::string time_path; @@ -685,7 +686,7 @@ bool Event::SetPath(Storage *storage) { if ( i == 2 ) date_path = path; } - time_path = stringtf("%02d/%02d/%02d", stime->tm_hour, stime->tm_min, stime->tm_sec); + time_path = stringtf("%02d/%02d/%02d", stime.tm_hour, stime.tm_min, stime.tm_sec); // Create event id symlink std::string id_file = stringtf("%s/.%" PRIu64, date_path.c_str(), id); @@ -695,7 +696,7 @@ bool Event::SetPath(Storage *storage) { } } else if ( scheme == Storage::MEDIUM ) { path += stringtf("/%04d-%02d-%02d", - stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday + stime.tm_year+1900, stime.tm_mon+1, stime.tm_mday ); if ( mkdir(path.c_str(), 0755) and ( errno != EEXIST ) ) { Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); diff --git a/src/zm_event.h b/src/zm_event.h index 1eb3f2a7f..5e972eca2 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -138,18 +138,20 @@ class Event { bool SetPath(Storage *storage); public: - static const char *getSubPath(struct tm *time) { + static const char *getSubPath(tm time) { static char subpath[PATH_MAX] = ""; snprintf(subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", - time->tm_year-100, time->tm_mon+1, time->tm_mday, - time->tm_hour, time->tm_min, time->tm_sec); + time.tm_year-100, time.tm_mon+1, time.tm_mday, + time.tm_hour, time.tm_min, time.tm_sec); return subpath; } static const char *getSubPath(time_t *time) { - return Event::getSubPath(localtime(time)); + tm time_tm = {}; + localtime_r(time, &time_tm); + return Event::getSubPath(time_tm); } - const char* getEventFile(void) const { + const char* getEventFile() const { return video_file.c_str(); } diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 5ed424b7f..8610f2f04 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -171,33 +171,35 @@ bool EventStream::loadEventData(uint64_t event_id) { const char *storage_path = storage->Path(); if ( event_data->scheme == Storage::DEEP ) { - struct tm *event_time = localtime(&event_data->start_time); + tm event_time = {}; + localtime_r(&event_data->start_time, &event_time); if ( storage_path[0] == '/' ) snprintf(event_data->path, sizeof(event_data->path), "%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", storage_path, event_data->monitor_id, - event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, - event_time->tm_hour, event_time->tm_min, event_time->tm_sec); + event_time.tm_year-100, event_time.tm_mon+1, event_time.tm_mday, + event_time.tm_hour, event_time.tm_min, event_time.tm_sec); else snprintf(event_data->path, sizeof(event_data->path), "%s/%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, - event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, - event_time->tm_hour, event_time->tm_min, event_time->tm_sec); + event_time.tm_year-100, event_time.tm_mon+1, event_time.tm_mday, + event_time.tm_hour, event_time.tm_min, event_time.tm_sec); } else if ( event_data->scheme == Storage::MEDIUM ) { - struct tm *event_time = localtime(&event_data->start_time); + tm event_time = {}; + localtime_r(&event_data->start_time, &event_time); if ( storage_path[0] == '/' ) snprintf(event_data->path, sizeof(event_data->path), "%s/%u/%04d-%02d-%02d/%" PRIu64, storage_path, event_data->monitor_id, - event_time->tm_year+1900, event_time->tm_mon+1, event_time->tm_mday, + event_time.tm_year+1900, event_time.tm_mon+1, event_time.tm_mday, event_data->event_id); else snprintf(event_data->path, sizeof(event_data->path), "%s/%s/%u/%04d-%02d-%02d/%" PRIu64, staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, - event_time->tm_year+1900, event_time->tm_mon+1, event_time->tm_mday, + event_time.tm_year+1900, event_time.tm_mon+1, event_time.tm_mday, event_data->event_id); } else { diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index a1d8fb418..2f89197cb 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -200,10 +200,10 @@ int FfmpegCamera::Capture(ZMPacket &zm_packet) { ) ) { // if audio stream is behind video stream, then read from audio, otherwise video mFormatContextPtr = mSecondFormatContext; - Debug(1, "Using audio input"); + Debug(2, "Using audio input"); } else { mFormatContextPtr = mFormatContext; - Debug(1, "Using video input because %" PRId64 " >= %" PRId64, + Debug(2, "Using video input because %" PRId64 " >= %" PRId64, (mAudioStream?av_rescale_q(mLastAudioPTS, mAudioStream->time_base, AV_TIME_BASE_Q):0), av_rescale_q(mLastVideoPTS, mVideoStream->time_base, AV_TIME_BASE_Q) ); diff --git a/src/zm_fifo.cpp b/src/zm_fifo.cpp index e7ff145af..10a8cc365 100644 --- a/src/zm_fifo.cpp +++ b/src/zm_fifo.cpp @@ -86,7 +86,7 @@ bool Fifo::open() { } long pipe_size = (long)fcntl(raw_fd, F_GETPIPE_SZ); if (pipe_size == -1) { - perror("get pipe size failed."); + Error("get pipe size failed."); } Debug(1, "default pipe size: %ld\n", pipe_size); #endif @@ -104,9 +104,9 @@ bool Fifo::close() { bool Fifo::writePacket(ZMPacket &packet) { if (!(outfile or open())) return false; - Debug(1, "Writing header ZM %u %" PRId64, packet.packet.size, packet.pts); + Debug(2, "Writing header ZM %u %" PRId64, packet.packet.size, packet.pts); // Going to write a brief header - if ( fprintf(outfile, "ZM %u %" PRId64 "\n", packet.packet.size, packet.pts) < 0 ) { + if (fprintf(outfile, "ZM %u %" PRId64 "\n", packet.packet.size, packet.pts) < 0) { if (errno != EAGAIN) { Error("Problem during writing: %s", strerror(errno)); } else { @@ -121,6 +121,7 @@ bool Fifo::writePacket(ZMPacket &packet) { } return true; } + bool Fifo::writePacket(std::string filename, ZMPacket &packet) { bool on_blocking_abort = true; FILE *outfile = nullptr; diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 014ae783c..2e0c82670 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -1186,7 +1186,8 @@ cinfo->out_color_space = JCS_RGB; // This is a lot of stuff to allocate on the stack. Recommend char *timebuf[64]; char timebuf[64], msbuf[64]; - strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime(&(timestamp.tv_sec))); + tm timestamp_tm = {}; + strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime_r(×tamp.tv_sec, ×tamp_tm)); snprintf(msbuf, sizeof msbuf, "%06d",(int)(timestamp.tv_usec)); // we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it unsigned char exiftimes[82] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, @@ -2132,7 +2133,8 @@ void Image::Annotate( void Image::Timestamp( const char *label, const time_t when, const Coord &coord, const int size ) { char time_text[64]; - strftime(time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime(&when)); + tm when_tm = {}; + strftime(time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime_r(&when, &when_tm)); if ( label ) { // Assume label is max 64, + ' - ' + 64 chars of time_text char text[132]; diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index b5c0ab665..b464c4d96 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -798,10 +798,10 @@ void LocalCamera::Initialise() { ); if ( v4l2_data.fmt.fmt.pix.width != width ) { - Warning("Failed to set requested width"); + Warning("Failed to set requested width"); } if ( v4l2_data.fmt.fmt.pix.height != height ) { - Warning("Failed to set requested height"); + Warning("Failed to set requested height"); } /* Buggy driver paranoia. */ @@ -2087,8 +2087,11 @@ int LocalCamera::Capture(ZMPacket &zm_packet) { buffer_bytesused = v4l2_data.bufptr->bytesused; bytes += buffer_bytesused; - if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) { - Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d", + if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) > (width * height) ) { + Fatal("Captured image dimensions larger than image buffer: V4L2: %dx%d monitor: %dx%d", + v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); + } else if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) { + Error("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d", v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); } } // end if v4l2 diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index 75d4d4865..d425764ae 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -447,7 +447,8 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con } else { #endif char *timePtr = timeString; - timePtr += strftime(timePtr, sizeof(timeString), "%x %H:%M:%S", localtime(&timeVal.tv_sec)); + tm now_tm = {}; + timePtr += strftime(timePtr, sizeof(timeString), "%x %H:%M:%S", localtime_r(&timeVal.tv_sec, &now_tm)); snprintf(timePtr, sizeof(timeString)-(timePtr-timeString), ".%06ld", timeVal.tv_usec); #if 0 } @@ -524,8 +525,8 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con if (mLogFileFP) { fputs(logString, mLogFileFP); if (mFlush) fflush(mLogFileFP); - } else { - puts("Logging to file, but failed to open it\n"); + } else if (mTerminalLevel != NOLOG) { + puts("Logging to file but failed to open it\n"); } } // end if level <= mFileLevel diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 88028ca32..83c041ed4 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -395,6 +395,9 @@ Monitor::Monitor() storage(nullptr), videoStore(nullptr), analysis_it(nullptr), + analysis_thread(nullptr), + decoder_it(nullptr), + decoder(nullptr), n_zones(0), zones(nullptr), privacy_bitmask(nullptr), @@ -444,8 +447,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; storage_id = atoi(dbrow[col]); col++; - if ( storage ) - delete storage; + if (storage) delete storage; storage = new Storage(storage_id); if ( ! strcmp(dbrow[col], "Local") ) { @@ -580,6 +582,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { warmup_count = atoi(dbrow[col]); col++; pre_event_count = atoi(dbrow[col]); col++; packetqueue.setMaxVideoPackets(pre_event_count); + packetqueue.setKeepKeyframes(videowriter == PASSTHROUGH); post_event_count = atoi(dbrow[col]); col++; stream_replay_buffer = atoi(dbrow[col]); col++; alarm_frame_count = atoi(dbrow[col]); col++; @@ -634,16 +637,16 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { image_buffer_count, image_size, (image_buffer_count*image_size), mem_size); - Zone **zones = 0; - int n_zones = Zone::Load(this, zones); - this->AddZones(n_zones, zones); - this->AddPrivacyBitmask(zones); - // Should maybe store this for later use std::string monitor_dir = stringtf("%s/%d", storage->Path(), id); LoadCamera(); if ( purpose != QUERY ) { + Zone **zones = 0; + int n_zones = Zone::Load(this, zones); + this->AddZones(n_zones, zones); + this->AddPrivacyBitmask(zones); + if ( mkdir(monitor_dir.c_str(), 0755) && ( errno != EEXIST ) ) { Error("Can't mkdir %s: %s", monitor_dir.c_str(), strerror(errno)); } @@ -1125,12 +1128,11 @@ Monitor::~Monitor() { disconnect(); } // end if mem_ptr - if (analysis_it) { - packetqueue.free_it(analysis_it); - analysis_it = nullptr; - } + // Will be free by packetqueue destructor + analysis_it = nullptr; + decoder_it = nullptr; - for ( int i = 0; i < n_zones; i++ ) { + for (int i = 0; i < n_zones; i++) { delete zones[i]; } delete[] zones; @@ -1733,16 +1735,10 @@ void Monitor::UpdateCaptureFPS() { last_fps_time = now_double; last_capture_image_count = image_count; - static char sql[ZM_SQL_SML_BUFSIZ]; - // The reason we update the Status as well is because if mysql restarts, the Monitor_Status table is lost, - // and nothing else will update the status until zmc restarts. Since we are successfully capturing we can - // assume that we are connected - snprintf(sql, sizeof(sql), - "INSERT INTO Monitor_Status (MonitorId,CaptureFPS,CaptureBandwidth,Status) " - "VALUES (%d, %.2lf, %u, 'Connected') ON DUPLICATE KEY UPDATE " - "CaptureFPS = %.2lf, CaptureBandwidth=%u, Status='Connected'", - id, new_capture_fps, new_capture_bandwidth, new_capture_fps, new_capture_bandwidth); - dbQueue.push(sql); + std::string sql = stringtf( + "UPDATE Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u WHERE MonitorId=%u", + new_capture_fps, new_capture_bandwidth, id); + dbQueue.push(sql.c_str()); } // now != last_fps_time } // end if report fps } // void Monitor::UpdateCaptureFPS() @@ -1802,22 +1798,17 @@ void Monitor::UpdateAnalysisFPS() { // If there isn't then we keep pre-event + alarm frames. = pre_event_count bool Monitor::Analyse() { - if ( !Enabled() ) { - Warning("Shouldn't be doing Analyse when not Enabled"); - return false; - } - if ( !analysis_it ) - analysis_it = packetqueue.get_video_it(true); // if have event, send frames until we find a video packet, at which point do analysis. Adaptive skip should only affect which frames we do analysis on. // get_analysis_packet will lock the packet and may wait if analysis_it is at the end - ZMPacket *snap = packetqueue.get_packet(analysis_it); - if ( !snap ) return false; + ZMLockedPacket *packet_lock = packetqueue.get_packet(analysis_it); + if (!packet_lock) return false; + ZMPacket *snap = packet_lock->packet_; // Is it possible for snap->score to be ! -1 ? Not if everything is working correctly - if ( snap->score != -1 ) { - snap->unlock(); + if (snap->score != -1) { + delete packet_lock; packetqueue.increment_it(analysis_it); Error("skipping because score was %d", snap->score); return false; @@ -1837,25 +1828,24 @@ bool Monitor::Analyse() { std::lock_guard lck(event_mutex); // if we have been told to be OFF, then we are off and don't do any processing. - if ( trigger_data->trigger_state != TriggerState::TRIGGER_OFF ) { + if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); int score = 0; // Ready means that we have captured the warmup # of frames - if ( !Ready() ) { + if (!Ready()) { Debug(3, "Not ready?"); - snap->unlock(); + delete packet_lock; return false; } - Debug(4, "Ready"); std::string cause; Event::StringSetMap noteSetMap; // Specifically told to be on. Setting the score here will trigger the alarm. - if ( trigger_data->trigger_state == TriggerState::TRIGGER_ON ) { + if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { score += trigger_data->trigger_score; Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); - if ( !event ) { + if (!event) { cause += trigger_data->trigger_cause; } Event::StringSet noteSet; @@ -1863,12 +1853,13 @@ bool Monitor::Analyse() { noteSetMap[trigger_data->trigger_cause] = noteSet; } // end if trigger_on - if ( signal_change ) { + // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. + if (signal_change) { Debug(2, "Signal change, new signal is %d", signal); const char *signalText = "Unknown"; - if ( !signal ) { + if (!signal) { signalText = "Lost"; - if ( event ) { + if (event) { Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name, analysis_image_count, event->Id()); closeEvent(); last_section_mod = 0; @@ -1877,9 +1868,8 @@ bool Monitor::Analyse() { signalText = "Reacquired"; score += 100; } - if ( !event ) { - if ( cause.length() ) - cause += ", "; + if (!event) { + if (cause.length()) cause += ", "; cause += SIGNAL_CAUSE; } Event::StringSet noteSet; @@ -1887,64 +1877,25 @@ bool Monitor::Analyse() { noteSetMap[SIGNAL_CAUSE] = noteSet; shared_data->state = state = IDLE; shared_data->active = signal; - if ( (function == MODECT or function == MOCORD) and snap->image ) + if ((function == MODECT or function == MOCORD) and snap->image) ref_image.Assign(*(snap->image)); }// else if (signal) { - if (snap->image or (snap->codec_type == AVMEDIA_TYPE_VIDEO)) { - struct timeval *timestamp = snap->timestamp; - - if ( Active() and (function == MODECT or function == MOCORD) and snap->image ) { - Debug(3, "signal and active and modect"); - Event::StringSet zoneSet; - - int motion_score = last_motion_score; - - if ( analysis_fps_limit ) { - double capture_fps = get_capture_fps(); - motion_frame_skip = capture_fps / analysis_fps_limit; - Debug(1, "Recalculating motion_frame_skip (%d) = capture_fps(%f) / analysis_fps(%f)", - motion_frame_skip, capture_fps, analysis_fps_limit); - } - - if ( !(analysis_image_count % (motion_frame_skip+1)) ) { - if ( snap->image ) { - // Get new score. - motion_score = DetectMotion(*(snap->image), zoneSet); - - Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", - score, last_motion_score, motion_score); - } else { - Warning("No image in snap"); - } - // Why are we updating the last_motion_score too? - last_motion_score = motion_score; - motion_frame_count += 1; - } else { - Debug(1, "Skipped motion detection"); - } - if (motion_score) { - score += motion_score; - if (cause.length()) cause += ", "; - cause += MOTION_CAUSE; - noteSetMap[MOTION_CAUSE] = zoneSet; - } // end if motion_score - } // end if active and doing motion detection - + if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { // Check to see if linked monitors are triggering. if (n_linked_monitors > 0) { - Debug(4, "Checking linked monitors"); + Debug(1, "Checking linked monitors"); // FIXME improve logic here bool first_link = true; Event::StringSet noteSet; for ( int i = 0; i < n_linked_monitors; i++ ) { // TODO: Shouldn't we try to connect? if ( linked_monitors[i]->isConnected() ) { - Debug(4, "Linked monitor %d %s is connected", + Debug(1, "Linked monitor %d %s is connected", linked_monitors[i]->Id(), linked_monitors[i]->Name()); if ( linked_monitors[i]->hasAlarmed() ) { - Debug(4, "Linked monitor %d %s is alarmed", + Debug(1, "Linked monitor %d %s is alarmed", linked_monitors[i]->Id(), linked_monitors[i]->Name()); if ( !event ) { if ( first_link ) { @@ -1957,7 +1908,7 @@ bool Monitor::Analyse() { noteSet.insert(linked_monitors[i]->Name()); score += linked_monitors[i]->lastFrameScore(); // 50; } else { - Debug(4, "Linked monitor %d %s is not alarmed", + Debug(1, "Linked monitor %d %s is not alarmed", linked_monitors[i]->Id(), linked_monitors[i]->Name()); } } else { @@ -1969,16 +1920,72 @@ bool Monitor::Analyse() { noteSetMap[LINKED_CAUSE] = noteSet; } // end if linked_monitors + if ( decoding_enabled ) { + while (!snap->image and !snap->decoded and !zm_terminate) { + // Need to wait for the decoder thread. + Debug(1, "Waiting for decode"); + packet_lock->wait(); + if (!snap->image and snap->decoded) { + Debug(1, "No image but was decoded, giving up"); + delete packet_lock; + return false; + } + } // end while ! decoded + } + + struct timeval *timestamp = snap->timestamp; + + if (Active() and (function == MODECT or function == MOCORD)) { + Debug(3, "signal and active and modect"); + Event::StringSet zoneSet; + + int motion_score = last_motion_score; + + if ( analysis_fps_limit ) { + double capture_fps = get_capture_fps(); + motion_frame_skip = capture_fps / analysis_fps_limit; + Debug(1, "Recalculating motion_frame_skip (%d) = capture_fps(%f) / analysis_fps(%f)", + motion_frame_skip, capture_fps, analysis_fps_limit); + } + + if (!(analysis_image_count % (motion_frame_skip+1))) { + if (snap->image) { + // Get new score. + motion_score = DetectMotion(*(snap->image), zoneSet); + + Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", + score, last_motion_score, motion_score); + motion_frame_count += 1; + } else { + Debug(1, "No image in snap, codec likely not ready"); + } + // Why are we updating the last_motion_score too? + last_motion_score = motion_score; + } else { + Debug(1, "Skipped motion detection"); + } + if (motion_score) { + score += motion_score; + if (cause.length()) cause += ", "; + cause += MOTION_CAUSE; + noteSetMap[MOTION_CAUSE] = zoneSet; + } // end if motion_score + } // end if active and doing motion detection + if (function == RECORD or function == MOCORD) { // If doing record, check to see if we need to close the event or not. - if (event) { Debug(2, "Have event %" PRIu64 " in mocord", event->Id()); - if (section_length - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) - && ( (function == MOCORD && (event_close_mode != CLOSE_TIME)) || ! ( timestamp->tv_sec % section_length ) ) - ) { + if (section_length && + ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) + && ( + ( (function == MOCORD) && (event_close_mode != CLOSE_TIME) ) + || + ( (function == RECORD) && (event_close_mode == CLOSE_TIME) ) + || ! ( timestamp->tv_sec % section_length ) + ) + ) { Info("%s: %03d - Closing event %" PRIu64 ", section end forced %d - %d = %d >= %d", name, image_count, event->Id(), timestamp->tv_sec, video_store_data->recording.tv_sec, @@ -1999,22 +2006,33 @@ bool Monitor::Analyse() { ); // This gets a lock on the starting packet - ZMPacket *starting_packet = packetqueue.get_packet(start_it); + + ZMLockedPacket *starting_packet_lock = nullptr; + ZMPacket *starting_packet = nullptr; + if ( *start_it != snap_it ) { + starting_packet_lock = packetqueue.get_packet(start_it); + if (!starting_packet_lock) return false; + starting_packet = starting_packet_lock->packet_; + } else { + starting_packet = snap; + } event = new Event(this, *(starting_packet->timestamp), "Continuous", noteSetMap); // Write out starting packets, do not modify packetqueue it will garbage collect itself - while ( starting_packet and (*start_it) != snap_it ) { + while ( starting_packet and ((*start_it) != snap_it) ) { event->AddPacket(starting_packet); // Have added the packet, don't want to unlock it until we have locked the next packetqueue.increment_it(start_it); if ( (*start_it) == snap_it ) { - starting_packet->unlock(); + if (starting_packet_lock) delete starting_packet_lock; break; } - ZMPacket *p = packetqueue.get_packet(start_it); - starting_packet->unlock(); - starting_packet = p; + ZMLockedPacket *lp = packetqueue.get_packet(start_it); + delete starting_packet_lock; + if (!lp) return false; + starting_packet_lock = lp; + starting_packet = lp->packet_; } packetqueue.free_it(start_it); delete start_it; @@ -2030,9 +2048,7 @@ bool Monitor::Analyse() { for ( int i=0; i < n_zones; i++ ) { if ( zones[i]->Alarmed() ) { alarm_cause += std::string(zones[i]->Label()); - if ( i < n_zones-1 ) { - alarm_cause += ","; - } + if (i < n_zones-1) alarm_cause += ","; } } alarm_cause = cause+" "+alarm_cause; @@ -2085,7 +2101,16 @@ bool Monitor::Analyse() { snap_it, (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count) ); - ZMPacket *starting_packet = *(*start_it); + + ZMLockedPacket *starting_packet_lock = nullptr; + ZMPacket *starting_packet = nullptr; + if ( *start_it != snap_it ) { + starting_packet_lock = packetqueue.get_packet(start_it); + if (!starting_packet_lock) return false; + starting_packet = starting_packet_lock->packet_; + } else { + starting_packet = snap; + } event = new Event(this, *(starting_packet->timestamp), cause, noteSetMap); shared_data->last_event_id = event->Id(); @@ -2099,12 +2124,18 @@ bool Monitor::Analyse() { packetqueue.increment_it(start_it); if ( (*start_it) == snap_it ) { - starting_packet->unlock(); + if (starting_packet_lock) delete starting_packet_lock; break; } - ZMPacket *p = packetqueue.get_packet(start_it); - starting_packet->unlock(); - starting_packet = p; + ZMLockedPacket *lp = packetqueue.get_packet(start_it); + delete starting_packet_lock; + if (!lp) { + // Shutting down event will be closed by ~Monitor() + // Perhaps we shouldn't do this. + return false; + } + starting_packet_lock = lp; + starting_packet = lp->packet_; } packetqueue.free_it(start_it); delete start_it; @@ -2138,7 +2169,7 @@ bool Monitor::Analyse() { Debug(1, "Staying in %s", State_Strings[state].c_str()); } - if ( state == ALARM ) { + if (state == ALARM) { last_alarm_count = analysis_image_count; } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT } else { // no score? @@ -2146,7 +2177,7 @@ bool Monitor::Analyse() { if (state == ALARM) { Info("%s: %03d - Gone into alert state", name, analysis_image_count); shared_data->state = state = ALERT; - } else if ( state == ALERT ) { + } else if (state == ALERT) { if ( ( analysis_image_count-last_alarm_count > post_event_count ) && @@ -2164,7 +2195,7 @@ bool Monitor::Analyse() { shared_data->state = state = TAPE; } } - } else if ( state == PREALARM ) { + } else if (state == PREALARM) { // Back to IDLE shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); } else { @@ -2172,19 +2203,19 @@ bool Monitor::Analyse() { State_Strings[state].c_str(), analysis_image_count, last_alarm_count, post_event_count, timestamp->tv_sec, video_store_data->recording.tv_sec, min_section_length); } - if ( Event::PreAlarmCount() ) + if (Event::PreAlarmCount()) Event::EmptyPreAlarmFrames(); } // end if score or not snap->score = score; - if ( state == PREALARM ) { + if (state == PREALARM) { // Generate analysis images if necessary - if ( (savejpegs > 1) and snap->image ) { - for ( int i = 0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) { - if ( zones[i]->AlarmImage() ) { - if ( ! snap->analysis_image ) + if ((savejpegs > 1) and snap->image) { + for (int i = 0; i < n_zones; i++) { + if (zones[i]->Alarmed()) { + if (zones[i]->AlarmImage()) { + if (!snap->analysis_image) snap->analysis_image = new Image(*(snap->image)); snap->analysis_image->Overlay(*(zones[i]->AlarmImage())); } @@ -2195,38 +2226,42 @@ bool Monitor::Analyse() { // incremement pre alarm image count //have_pre_alarmed_frames ++; Event::AddPreAlarmFrame(snap->image, *timestamp, score, nullptr); - } else if ( state == ALARM ) { - if ( ( savejpegs > 1 ) and snap->image ) { - for ( int i = 0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) { - if ( zones[i]->AlarmImage() ) { - if ( ! snap->analysis_image ) + } else if (state == ALARM) { + if ((savejpegs > 1 ) and snap->image) { + for (int i = 0; i < n_zones; i++) { + if (zones[i]->Alarmed()) { + if (zones[i]->AlarmImage()) { + if (!snap->analysis_image) snap->analysis_image = new Image(*(snap->image)); snap->analysis_image->Overlay(*(zones[i]->AlarmImage())); } - if ( config.record_event_stats ) + if (config.record_event_stats) zones[i]->RecordStats(event); } // end if zone is alarmed } // end foreach zone - } - if ( noteSetMap.size() > 0 ) - event->updateNotes(noteSetMap); - if ( section_length - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) - && ! (image_count % fps_report_interval) - ) { - Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %d - %d = %d >= %d", - name, image_count, event->Id(), - timestamp->tv_sec, video_store_data->recording.tv_sec, - timestamp->tv_sec - video_store_data->recording.tv_sec, - section_length - ); - closeEvent(); - event = new Event(this, *timestamp, cause, noteSetMap); - shared_data->last_event_id = event->Id(); - //set up video store data - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - video_store_data->recording = event->StartTime(); + } + if (event) { + if (noteSetMap.size() > 0) + event->updateNotes(noteSetMap); + if ( section_length + && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) + && ! (image_count % fps_report_interval) + ) { + Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %d - %d = %d >= %d", + name, image_count, event->Id(), + timestamp->tv_sec, video_store_data->recording.tv_sec, + timestamp->tv_sec - video_store_data->recording.tv_sec, + section_length + ); + closeEvent(); + event = new Event(this, *timestamp, cause, noteSetMap); + shared_data->last_event_id = event->Id(); + //set up video store data + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + video_store_data->recording = event->StartTime(); + } + } else { + Error("ALARM but no event"); } } else if ( state == ALERT ) { @@ -2255,27 +2290,18 @@ bool Monitor::Analyse() { } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) if (event) event->AddPacket(snap); -#if 0 - if (snap->packet.stream_index == video_stream_id) { - if (video_fifo) { - if ( snap->keyframe ) { - // avcodec strips out important nals that describe the stream and - // stick them in extradata. Need to send them along with keyframes - AVStream *stream = camera->getVideoStream(); - video_fifo->write( - static_cast(stream->codecpar->extradata), - stream->codecpar->extradata_size); - } - video_fifo->writePacket(*snap); - } - } else if (snap->packet.stream_index == audio_stream_id) { - if (audio_fifo) - audio_fifo->writePacket(*snap); + + // In the case where people have pre-alarm frames, the web ui will generate the frame images + // from the mp4. So no one will notice anyways. + if (snap->image and (videowriter == PASSTHROUGH) and !savejpegs) { + Debug(1, "Deleting image data for %d", snap->image_index); + // Don't need raw images anymore + delete snap->image; + snap->image = nullptr; } -#endif // popPacket will have placed a second lock on snap, so release it here. - snap->unlock(); + delete packet_lock; if ( snap->image_index > 0 ) { // Only do these if it's a video packet. @@ -2507,7 +2533,6 @@ int Monitor::Capture() { gettimeofday(packet->timestamp, nullptr); shared_data->zmc_heartbeat_time = packet->timestamp->tv_sec; - Image* capture_image = image_buffer[index].image; int captureResult = 0; if ( deinterlacing_value == 4 ) { @@ -2526,7 +2551,7 @@ int Monitor::Capture() { } } else { captureResult = camera->Capture(*packet); - Debug(4, "Back from capture result=%d image %d", captureResult, image_count); + Debug(4, "Back from capture result=%d image count %d", captureResult, image_count); if ( captureResult < 0 ) { Debug(2, "failed capture"); @@ -2536,7 +2561,7 @@ int Monitor::Capture() { Rgb signalcolor; /* HTML colour code is actually BGR in memory, we want RGB */ signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); - capture_image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); + Image *capture_image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); capture_image->Fill(signalcolor); shared_data->signal = false; shared_data->last_write_index = index; @@ -2550,6 +2575,12 @@ int Monitor::Capture() { // Don't want to do analysis on it, but we won't due to signal return -1; } else if ( captureResult > 0 ) { + // If we captured, let's assume signal, Decode will detect further + if (!decoding_enabled) { + shared_data->last_write_index = index; + shared_data->last_write_time = packet->timestamp->tv_sec; + shared_data->signal = true; + } Debug(2, "Have packet stream_index:%d ?= videostream_id:(%d) q.vpktcount(%d) event?(%d) ", packet->packet.stream_index, video_stream_id, packetqueue.packet_count(video_stream_id), ( event ? 1 : 0 ) ); @@ -2592,6 +2623,7 @@ int Monitor::Capture() { return 1; } // end if audio +#if 0 if ( !packet->image ) { if ( packet->packet.size and !packet->in_frame ) { if ( !decoding_enabled ) { @@ -2678,6 +2710,7 @@ int Monitor::Capture() { shared_data->signal = ( capture_image and signal_check_points ) ? CheckSignal(capture_image) : true; shared_data->last_write_index = index; shared_data->last_write_time = packet->timestamp->tv_sec; +#endif image_count++; // Will only be queued if there are iterators allocated in the queue. @@ -2710,13 +2743,108 @@ int Monitor::Capture() { return captureResult; } // end Monitor::Capture +bool Monitor::Decode() { + + ZMLockedPacket *packet_lock = packetqueue.get_packet(decoder_it); + if (!packet_lock) return false; + ZMPacket *packet = packet_lock->packet_; + packetqueue.increment_it(decoder_it); + if (packet->codec_type != AVMEDIA_TYPE_VIDEO) { + delete packet_lock; + return true; // Don't need decode + } + + int ret = 0; + if ((!packet->image) and packet->packet.size and !packet->in_frame) { + // Allocate the image first so that it can be used by hwaccel + // We don't actually care about camera colours, pixel order etc. We care about the desired settings + // + //capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); + ret = packet->decode(camera->getVideoCodecContext()); + if (ret > 0) { + if (packet->in_frame and !packet->image) { + packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder()); + packet->get_image(); + } + } else { + Debug(1, "No packet.size(%d) or packet->in_frame(%p). Not decoding", packet->packet.size, packet->in_frame); + } + } + Image* capture_image = nullptr; + unsigned int index = image_count % image_buffer_count; + + if (packet->image) { + capture_image = packet->image; + + /* Deinterlacing */ + if ( deinterlacing_value ) { + if ( deinterlacing_value == 1 ) { + capture_image->Deinterlace_Discard(); + } else if ( deinterlacing_value == 2 ) { + capture_image->Deinterlace_Linear(); + } else if ( deinterlacing_value == 3 ) { + capture_image->Deinterlace_Blend(); + } else if ( deinterlacing_value == 4 ) { + capture_image->Deinterlace_4Field(next_buffer.image, (deinterlacing>>8)&0xff); + } else if ( deinterlacing_value == 5 ) { + capture_image->Deinterlace_Blend_CustomRatio((deinterlacing>>8)&0xff); + } + } + + if ( orientation != ROTATE_0 ) { + Debug(2, "Doing rotation"); + switch ( orientation ) { + case ROTATE_0 : + // No action required + break; + case ROTATE_90 : + case ROTATE_180 : + case ROTATE_270 : + capture_image->Rotate((orientation-1)*90); + break; + case FLIP_HORI : + case FLIP_VERT : + capture_image->Flip(orientation==FLIP_HORI); + break; + } + } // end if have rotation + + if (privacy_bitmask) { + Debug(1, "Applying privacy"); + capture_image->MaskPrivacy(privacy_bitmask); + } + + if (config.timestamp_on_capture) { + Debug(1, "Timestampprivacy"); + TimestampImage(packet->image, packet->timestamp); + } + + if (!ref_image.Buffer()) { + // First image, so assign it to ref image + Debug(1, "Assigning ref image %dx%d size: %d", width, height, camera->ImageSize()); + ref_image.Assign(width, height, camera->Colours(), camera->SubpixelOrder(), + packet->image->Buffer(), camera->ImageSize()); + } + image_buffer[index].image->Assign(*(packet->image)); + *(image_buffer[index].timestamp) = *(packet->timestamp); + } // end if have image + packet->decoded = true; + + shared_data->signal = ( capture_image and signal_check_points ) ? CheckSignal(capture_image) : true; + shared_data->last_write_index = index; + shared_data->last_write_time = packet->timestamp->tv_sec; + delete packet_lock; + return true; +} // end bool Monitor::Decode() + void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) const { if ( !label_format[0] ) return; // Expand the strftime macros first char label_time_text[256]; - strftime(label_time_text, sizeof(label_time_text), label_format, localtime(&ts_time->tv_sec)); + tm ts_tm = {}; + strftime(label_time_text, sizeof(label_time_text), label_format, localtime_r(&ts_time->tv_sec, &ts_tm)); char label_text[1024]; const char *s_ptr = label_time_text; char *d_ptr = label_text; @@ -3026,6 +3154,18 @@ int Monitor::PrimeCapture() { audio_fifo = new Fifo(shared_data->audio_fifo_path, true); } } // end if rtsp_server + + if (decoding_enabled) { + if (!decoder_it) decoder_it = packetqueue.get_video_it(false); + if (!decoder) decoder = new DecoderThread(this); + } + + if (!analysis_it) analysis_it = packetqueue.get_video_it(false); + if (!analysis_thread) { + Debug(1, "Starting an analysis thread for monitor (%d)", id); + analysis_thread = new AnalysisThread(this); + } + } else { Debug(2, "Failed to prime %d", ret); } @@ -3035,13 +3175,24 @@ int Monitor::PrimeCapture() { int Monitor::PreCapture() const { return camera->PreCapture(); } int Monitor::PostCapture() const { return camera->PostCapture(); } int Monitor::Close() { + // Because the stream indexes may change we have to clear out the packetqueue + if (decoder) decoder->Stop(); + if (analysis_thread) analysis_thread->Stop(); + packetqueue.clear(); + if (decoder) { + delete decoder; + decoder = nullptr; + } + if (analysis_thread) { + delete analysis_thread; + analysis_thread = nullptr; + } std::lock_guard lck(event_mutex); if (event) { Info("%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name, image_count, event->Id()); closeEvent(); } if (camera) camera->Close(); - packetqueue.clear(); return 1; } @@ -3051,25 +3202,25 @@ Monitor::Orientation Monitor::getOrientation() const { return orientation; } // So this should be done as the first task in the analysis thread startup. // This function is deprecated. void Monitor::get_ref_image() { - ZMPacket *snap = nullptr; + ZMLockedPacket *snap_lock = nullptr; if ( !analysis_it ) analysis_it = packetqueue.get_video_it(true); while ( ( - !( snap = packetqueue.get_packet(analysis_it)) + !( snap_lock = packetqueue.get_packet(analysis_it)) or - ( snap->codec_type != AVMEDIA_TYPE_VIDEO ) + ( snap_lock->packet_->codec_type != AVMEDIA_TYPE_VIDEO ) or - ! snap->image + ! snap_lock->packet_->image ) and !zm_terminate) { Debug(1, "Waiting for capture daemon lastwriteindex(%d) lastwritetime(%d)", shared_data->last_write_index, shared_data->last_write_time); - if ( snap and ! snap->image ) { - snap->unlock(); + if ( snap_lock and ! snap_lock->packet_->image ) { + delete snap_lock; // can't analyse it anyways, incremement packetqueue.increment_it(analysis_it); } @@ -3078,6 +3229,7 @@ void Monitor::get_ref_image() { if ( zm_terminate ) return; + ZMPacket *snap = snap_lock->packet_; Debug(1, "get_ref_image: packet.stream %d ?= video_stream %d, packet image id %d packet image %p", snap->packet.stream_index, video_stream_id, snap->image_index, snap->image ); // Might not have been decoded yet FIXME @@ -3088,7 +3240,7 @@ void Monitor::get_ref_image() { } else { Debug(2, "Have no ref image about to unlock"); } - snap->unlock(); + delete snap_lock; } std::vector Monitor::Groups() { diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 20a59d79c..f12910597 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -22,6 +22,8 @@ #include "zm_define.h" #include "zm_camera.h" +#include "zm_analysis_thread.h" +#include "zm_decoder_thread.h" #include "zm_event.h" #include "zm_fifo.h" #include "zm_image.h" @@ -375,7 +377,9 @@ protected: VideoStore *videoStore; PacketQueue packetqueue; packetqueue_iterator *analysis_it; - + AnalysisThread *analysis_thread; + packetqueue_iterator *decoder_it; + DecoderThread *decoder; int n_zones; Zone **zones; @@ -411,6 +415,8 @@ public: if ( shared_data && shared_data->valid ) { struct timeval now; gettimeofday(&now, nullptr); + Debug(3, "Shared data is valid, checking heartbeat %u - %u = %d < %f", + now.tv_sec, shared_data->zmc_heartbeat_time, (now.tv_sec - shared_data->zmc_heartbeat_time), config.watch_max_delay); if ((now.tv_sec - shared_data->zmc_heartbeat_time) < config.watch_max_delay) return true; } @@ -426,6 +432,7 @@ public: } return storage; } + inline CameraType GetType() const { return type; } inline Function GetFunction() const { return function; } inline PacketQueue * GetPacketQueue() { return &packetqueue; } inline bool Enabled() const { @@ -438,10 +445,6 @@ public: } inline const char *EventPrefix() const { return event_prefix; } inline bool Ready() const { - if ( function <= MONITOR ) { - Error("Should not be calling Ready if the function doesn't include motion detection"); - return false; - } if ( image_count >= ready_count ) { return true; } @@ -549,6 +552,7 @@ public: //unsigned int DetectBlack( const Image &comp_image, Event::StringSet &zoneSet ); bool CheckSignal( const Image *image ); bool Analyse(); + bool Decode(); void DumpImage( Image *dump_image ) const; void TimestampImage( Image *ts_image, const struct timeval *ts_time ) const; bool closeEvent(); diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index fd618800e..491019a3a 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -467,28 +467,35 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { } // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) void MonitorStream::runStream() { - if ( type == STREAM_SINGLE ) { + if (type == STREAM_SINGLE) { // Not yet migrated over to stream class - SingleImage(scale); + if (checkInitialised()) + SingleImage(scale); + else + sendTextFrame("Unable to send image"); + return; } openComms(); - if ( type == STREAM_JPEG ) + if (type == STREAM_JPEG) fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout); - if ( !checkInitialised() ) { - Error("Not initialized"); - while ( !(loadMonitor(monitor_id) || zm_terminate) ) { - sendTextFrame("Not connected"); - if ( connkey ) - checkCommandQueue(); - sleep(1); - } - if ( zm_terminate ) - return; + while (!(loadMonitor(monitor_id) || zm_terminate)) { + sendTextFrame("Not connected"); + if (connkey) + checkCommandQueue(); + sleep(1); } + if (zm_terminate) return; + while (!checkInitialised() and !zm_terminate) { + sendTextFrame("Unable to stream"); + if (connkey) + checkCommandQueue(); + sleep(1); + } + if (zm_terminate) return; updateFrameRate(monitor->GetFPS()); @@ -562,7 +569,7 @@ void MonitorStream::runStream() { capture_fps = capture_max_fps; } - while ( !zm_terminate ) { + while (!zm_terminate) { bool got_command = false; if ( feof(stdout) ) { Debug(2, "feof stdout"); @@ -570,7 +577,7 @@ void MonitorStream::runStream() { } else if ( ferror(stdout) ) { Debug(2, "ferror stdout"); break; - } else if ( !monitor->ShmValid() ) { + } else if (!monitor->ShmValid()) { Debug(2, "monitor not valid.... maybe we should wait until it comes back."); break; } @@ -856,7 +863,7 @@ void MonitorStream::SingleImage(int scale) { int img_buffer_size = 0; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; - while ( monitor->shared_data->last_write_index >= monitor->image_buffer_count ) { + while ((monitor->shared_data->last_write_index >= monitor->image_buffer_count) and !zm_terminate) { Debug(1, "Waiting for capture to begin"); usleep(100000); } diff --git a/src/zm_packet.cpp b/src/zm_packet.cpp index 58c2bda8c..cb37558b3 100644 --- a/src/zm_packet.cpp +++ b/src/zm_packet.cpp @@ -37,7 +37,8 @@ ZMPacket::ZMPacket() : score(-1), codec_type(AVMEDIA_TYPE_UNKNOWN), image_index(-1), - codec_imgsize(0) + codec_imgsize(0), + decoded(0) { av_init_packet(&packet); packet.size = 0; // So we can detect whether it has been filled. diff --git a/src/zm_packet.h b/src/zm_packet.h index 118239860..507547f99 100644 --- a/src/zm_packet.h +++ b/src/zm_packet.h @@ -21,6 +21,7 @@ #define ZM_PACKET_H #include "zm_logger.h" +#include #include extern "C" { @@ -36,7 +37,9 @@ class Image; class ZMPacket { public: - std::recursive_mutex mutex; + std::mutex mutex_; + std::condition_variable condition_; + int keyframe; AVStream *stream; // Input stream AVPacket packet; // Input packet, undecoded @@ -51,6 +54,7 @@ class ZMPacket { int image_index; int codec_imgsize; int64_t pts; // pts in the packet can be in another time base. This MUST be in AV_TIME_BASE_Q + bool decoded; public: AVPacket *av_packet() { return &packet; } @@ -65,21 +69,45 @@ class ZMPacket { explicit ZMPacket(ZMPacket &packet); ZMPacket(); ~ZMPacket(); - void lock() { - Debug(4,"Locking packet %d", this->image_index); - mutex.lock(); - Debug(4,"packet %d locked", this->image_index); - }; - bool trylock() { - Debug(4,"TryLocking packet %d", this->image_index); - return mutex.try_lock(); - }; - void unlock() { - Debug(4,"packet %d unlocked", this->image_index); - mutex.unlock(); - }; - AVFrame *get_out_frame( const AVCodecContext *ctx ); + + AVFrame *get_out_frame(const AVCodecContext *ctx); int get_codec_imgsize() { return codec_imgsize; }; }; +class ZMLockedPacket { + public: + ZMPacket *packet_; + std::unique_lock lck_; + + ZMLockedPacket(ZMPacket *p) : + packet_(p), + lck_(packet_->mutex_, std::defer_lock) { + } + ~ZMLockedPacket() { + unlock(); + } + + void lock() { + Debug(4, "locking packet %d", packet_->image_index); + lck_.lock(); + Debug(4, "packet %d locked", packet_->image_index); + }; + bool trylock() { + Debug(4, "TryLocking packet %d", packet_->image_index); + return lck_.try_lock(); + }; + void unlock() { + Debug(4, "packet %d unlocked", packet_->image_index); + lck_.unlock(); + packet_->condition_.notify_all(); + }; + + void wait() { + Debug(4, "packet %d waiting", packet_->image_index); + // We already have a lock, but it's a recursive mutex.. so this may be ok + packet_->condition_.wait(lck_); + } + +}; + #endif /* ZM_PACKET_H */ diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 2ba04e71b..152f51972 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -31,7 +31,8 @@ PacketQueue::PacketQueue(): max_video_packet_count(-1), max_stream_id(-1), packet_counts(nullptr), - deleting(false) + deleting(false), + keep_keyframes(false) { } @@ -56,11 +57,11 @@ int PacketQueue::addStream() { PacketQueue::~PacketQueue() { clear(); - if ( packet_counts ) { + if (packet_counts) { delete[] packet_counts; packet_counts = nullptr; } - while ( !iterators.empty() ) { + while (!iterators.empty()) { packetqueue_iterator *it = iterators.front(); iterators.pop_front(); delete it; @@ -87,7 +88,7 @@ bool PacketQueue::queuePacket(ZMPacket* add_packet) { pktQueue.push_back(add_packet); packet_counts[add_packet->packet.stream_index] += 1; - Debug(1, "packet counts for %d is %d", + Debug(2, "packet counts for %d is %d", add_packet->packet.stream_index, packet_counts[add_packet->packet.stream_index]); @@ -120,56 +121,87 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { // // So start at the beginning, counting video packets until the next keyframe. // Then if deleting those packets doesn't break 1 and 2, then go ahead and delete them. - if ( ! ( + if (keep_keyframes and ! ( add_packet->packet.stream_index == video_stream_id - and - add_packet->keyframe - and - (packet_counts[video_stream_id] > max_video_packet_count) - and - *(pktQueue.begin()) != add_packet - ) + and + add_packet->keyframe + and + (packet_counts[video_stream_id] > max_video_packet_count) + and + *(pktQueue.begin()) != add_packet + ) ) { - Debug(3, "stream index %d ?= video_stream_id %d, keyframe %d, counts %d > max %d at begin %d", - add_packet->packet.stream_index, video_stream_id, add_packet->keyframe, packet_counts[video_stream_id], max_video_packet_count, + Debug(3, "stream index %d ?= video_stream_id %d, keyframe %d, keep_keyframes %d, counts %d > max %d at begin %d", + add_packet->packet.stream_index, video_stream_id, add_packet->keyframe, keep_keyframes, packet_counts[video_stream_id], max_video_packet_count, ( *(pktQueue.begin()) != add_packet ) ); return; } std::unique_lock lck(mutex); + if (!keep_keyframes) { + // If not doing passthrough, we don't care about starting with a keyframe so logic is simpler + while ((*pktQueue.begin() != add_packet) and (packet_counts[video_stream_id] > max_video_packet_count)) { + ZMPacket *zm_packet = *pktQueue.begin(); + ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); + if (!lp->trylock()) break; + delete lp; + + pktQueue.pop_front(); + packet_counts[zm_packet->packet.stream_index] -= 1; + Debug(1, "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%d", + zm_packet->packet.stream_index, zm_packet->image_index, zm_packet->keyframe, packet_counts[video_stream_id], max_video_packet_count, pktQueue.size()); + delete zm_packet; + } // end while + return; + } + + // If ananlysis_it isn't at the end, we need to keep that many additional packets + int tail_count = 0; + if (pktQueue.back() != add_packet) { + Debug(1, "Ours is not the back"); + packetqueue_iterator it = pktQueue.end(); + --it; + while (*it != add_packet) { + if ((*it)->packet.stream_index == video_stream_id) + ++tail_count; + --it; + } + } + Debug(1, "Tail count is %d", tail_count); packetqueue_iterator it = pktQueue.begin(); packetqueue_iterator next_front = pktQueue.begin(); int video_packets_to_delete = 0; // This is a count of how many packets we will delete so we know when to stop looking + // First packet is special because we know it is a video keyframe and only need to check for lock ZMPacket *zm_packet = *it; - if ( zm_packet->trylock() ) { + ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); + if (lp->trylock()) { ++it; - zm_packet->unlock(); + delete lp; // Since we have many packets in the queue, we should NOT be pointing at end so don't need to test for that - while ( *it != add_packet ) { + while (*it != add_packet) { zm_packet = *it; - if ( !zm_packet->trylock() ) { - break; - } - zm_packet->unlock(); + lp = new ZMLockedPacket(zm_packet); + if (!lp->trylock()) break; + delete lp; - if ( is_there_an_iterator_pointing_to_packet(zm_packet) ) { + if (is_there_an_iterator_pointing_to_packet(zm_packet)) { Warning("Found iterator at beginning of queue. Some thread isn't keeping up"); break; } - if ( zm_packet->packet.stream_index == video_stream_id ) { - if ( zm_packet->keyframe ) { + if (zm_packet->packet.stream_index == video_stream_id) { + if (zm_packet->keyframe) { Debug(3, "Have a video keyframe so setting next front to it"); next_front = it; } ++video_packets_to_delete; - Debug(4, "Counted %d video packets. Which would leave %d in packetqueue", - video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete); - if (packet_counts[video_stream_id] - video_packets_to_delete <= max_video_packet_count) { + Debug(4, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d", + video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete, tail_count); + if (packet_counts[video_stream_id] - video_packets_to_delete <= max_video_packet_count + tail_count) { break; } } @@ -200,7 +232,7 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { return; } // end voidPacketQueue::clearPackets(ZMPacket* zm_packet) -ZMPacket* PacketQueue::popPacket( ) { +ZMLockedPacket* PacketQueue::popPacket( ) { Debug(4, "pktQueue size %d", pktQueue.size()); if ( pktQueue.empty() ) { return nullptr; @@ -222,14 +254,15 @@ ZMPacket* PacketQueue::popPacket( ) { } } // end foreach iterator - zm_packet->lock(); + ZMLockedPacket *lp = new ZMLockedPacket (zm_packet); + lp->lock(); pktQueue.pop_front(); packet_counts[zm_packet->packet.stream_index] -= 1; mutex.unlock(); - return zm_packet; + return lp; } // popPacket @@ -319,15 +352,17 @@ unsigned int PacketQueue::clear(unsigned int frames_to_keep, int stream_id) { void PacketQueue::clear() { deleting = true; + condition.notify_all(); std::unique_lock lck(mutex); while (!pktQueue.empty()) { ZMPacket *packet = pktQueue.front(); // Someone might have this packet, but not for very long and since we have locked the queue they won't be able to get another one - packet->lock(); + ZMLockedPacket *lp = new ZMLockedPacket(packet); + lp->lock(); pktQueue.pop_front(); - packet->unlock(); + delete lp; delete packet; } @@ -452,8 +487,8 @@ int PacketQueue::packet_count(int stream_id) { // Returns a packet. Packet will be locked -ZMPacket *PacketQueue::get_packet(packetqueue_iterator *it) { - if ( deleting or zm_terminate ) +ZMLockedPacket *PacketQueue::get_packet(packetqueue_iterator *it) { + if (deleting or zm_terminate) return nullptr; Debug(4, "Locking in get_packet using it %p queue end? %d, packet %p", @@ -467,7 +502,7 @@ ZMPacket *PacketQueue::get_packet(packetqueue_iterator *it) { Debug(2, "waiting. Queue size %d it == end? %d", pktQueue.size(), (*it == pktQueue.end())); condition.wait(lck); } - if ( deleting or zm_terminate ) + if (deleting or zm_terminate) return nullptr; Debug(4, "get_packet using it %p queue end? %d, packet %p", @@ -477,14 +512,21 @@ ZMPacket *PacketQueue::get_packet(packetqueue_iterator *it) { Error("Null p?!"); return nullptr; } + ZMLockedPacket *lp = new ZMLockedPacket(p); Debug(3, "get_packet %p image_index: %d, about to lock packet", p, p->image_index); - while ( !(zm_terminate or deleting) and !p->trylock() ) { - Debug(3, "waiting. Queue size %d it == end? %d", pktQueue.size(), ( *it == pktQueue.end() ) ); + while (!(zm_terminate or deleting) and !lp->trylock()) { + Debug(3, "waiting on index %d. Queue size %d it == end? %d", + p->image_index, pktQueue.size(), ( *it == pktQueue.end() ) ); + ZM_DUMP_PACKET(p->packet, ""); condition.wait(lck); } + if (deleting or zm_terminate) { + // packet may have been deleted so we can't delete the lp FIXME + return nullptr; + } Debug(2, "Locked packet, unlocking packetqueue mutex"); - return p; -} // end ZMPacket *PacketQueue::get_packet(it) + return lp; +} // end ZMLockedPacket *PacketQueue::get_packet(it) bool PacketQueue::increment_it(packetqueue_iterator *it) { Debug(2, "Incrementing %p, queue size %d, end? %d", it, pktQueue.size(), ((*it) == pktQueue.end())); @@ -599,7 +641,7 @@ packetqueue_iterator * PacketQueue::get_video_it(bool wait) { if ( wait ) { while ( ((! pktQueue.size()) or (*it == pktQueue.end())) and !zm_terminate and !deleting ) { - Debug(2, "waiting. Queue size %d it == end? %d", pktQueue.size(), ( *it == pktQueue.end() ) ); + Debug(2, "waiting for packets in queue. Queue size %d it == end? %d", pktQueue.size(), ( *it == pktQueue.end() ) ); condition.wait(lck); *it = pktQueue.begin(); } @@ -612,14 +654,14 @@ packetqueue_iterator * PacketQueue::get_video_it(bool wait) { while ( *it != pktQueue.end() ) { ZMPacket *zm_packet = *(*it); - if ( !zm_packet ) { + if (!zm_packet) { Error("Null zmpacket in queue!?"); free_it(it); return nullptr; } Debug(1, "Packet keyframe %d for stream %d, so returning the it to it", zm_packet->keyframe, zm_packet->packet.stream_index); - if ( zm_packet->keyframe and ( zm_packet->packet.stream_index == video_stream_id ) ) { + if (zm_packet->keyframe and ( zm_packet->packet.stream_index == video_stream_id )) { Debug(1, "Found a keyframe for stream %d, so returning the it to it", video_stream_id); return it; } @@ -627,7 +669,7 @@ packetqueue_iterator * PacketQueue::get_video_it(bool wait) { } Debug(1, "DIdn't Found a keyframe for stream %d, so returning the it to it", video_stream_id); return it; -} +} // get video_it void PacketQueue::free_it(packetqueue_iterator *it) { for ( diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index c0ba045b8..8caa0a527 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -24,6 +24,7 @@ #include class ZMPacket; +class ZMLockedPacket; typedef std::list::iterator packetqueue_iterator; @@ -37,6 +38,7 @@ class PacketQueue { int max_stream_id; int *packet_counts; /* packet count for each stream_id, to keep track of how many video vs audio packets are in the queue */ bool deleting; + bool keep_keyframes; std::list iterators; std::mutex mutex; @@ -50,9 +52,10 @@ class PacketQueue { int addStream(); void setMaxVideoPackets(int p); + void setKeepKeyframes(bool k) { keep_keyframes = k; }; bool queuePacket(ZMPacket* packet); - ZMPacket * popPacket(); + ZMLockedPacket * popPacket(); bool popVideoPacket(ZMPacket* packet); bool popAudioPacket(ZMPacket* packet); unsigned int clear(unsigned int video_frames_to_keep, int stream_id); @@ -68,7 +71,7 @@ class PacketQueue { bool increment_it(packetqueue_iterator *it); bool increment_it(packetqueue_iterator *it, int stream_id); - ZMPacket *get_packet(packetqueue_iterator *); + ZMLockedPacket *get_packet(packetqueue_iterator *); packetqueue_iterator *get_video_it(bool wait); packetqueue_iterator *get_stream_it(int stream_id); void free_it(packetqueue_iterator *); diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index d3ba95df0..010fc4fbc 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -202,7 +202,7 @@ int RemoteCameraHttp::SendRequest() { * > 0 is the # of bytes read. */ -int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { +int RemoteCameraHttp::ReadData(Buffer &buffer, unsigned int bytes_expected) { fd_set rfds; FD_ZERO(&rfds); FD_SET(sd, &rfds); @@ -210,44 +210,44 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { struct timeval temp_timeout = timeout; int n_found = select(sd+1, &rfds, nullptr, nullptr, &temp_timeout); - if( n_found == 0 ) { - Debug( 1, "Select timed out timeout was %d secs %d usecs", temp_timeout.tv_sec, temp_timeout.tv_usec ); + if (n_found == 0) { + Debug(1, "Select timed out timeout was %d secs %d usecs", temp_timeout.tv_sec, temp_timeout.tv_usec); int error = 0; - socklen_t len = sizeof (error); - int retval = getsockopt (sd, SOL_SOCKET, SO_ERROR, &error, &len); - if(retval != 0 ) { - Debug( 1, "error getting socket error code %s", strerror(retval) ); + socklen_t len = sizeof(error); + int retval = getsockopt(sd, SOL_SOCKET, SO_ERROR, &error, &len); + if (retval != 0) { + Debug(1, "error getting socket error code %s", strerror(retval)); } - if (error != 0 ) { + if (error != 0) { return -1; } // Why are we disconnecting? It's just a timeout, meaning that data wasn't available. //Disconnect(); return 0; - } else if ( n_found < 0) { + } else if (n_found < 0) { Error("Select error: %s", strerror(errno)); return -1; } unsigned int total_bytes_to_read = 0; - if ( bytes_expected ) { + if (bytes_expected) { total_bytes_to_read = bytes_expected; } else { - if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) { - Error( "Can't ioctl(): %s", strerror(errno) ); + if (ioctl(sd, FIONREAD, &total_bytes_to_read) < 0) { + Error("Can't ioctl(): %s", strerror(errno) ); return -1; } - if ( total_bytes_to_read == 0 ) { - if ( mode == SINGLE_IMAGE ) { + if (total_bytes_to_read == 0) { + if (mode == SINGLE_IMAGE) { int error = 0; - socklen_t len = sizeof (error); - int retval = getsockopt( sd, SOL_SOCKET, SO_ERROR, &error, &len ); - if ( retval != 0 ) { - Debug( 1, "error getting socket error code %s", strerror(retval) ); + socklen_t len = sizeof(error); + int retval = getsockopt(sd, SOL_SOCKET, SO_ERROR, &error, &len); + if (retval != 0) { + Debug(1, "error getting socket error code %s", strerror(retval)); } - if ( error != 0 ) { + if (error != 0) { return -1; } // Case where we are grabbing a single jpg, but no content-length was given, so the expectation is that we read until close. @@ -261,38 +261,38 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { } // There can be lots of bytes available. I've seen 4MB or more. This will vastly inflate our buffer size unnecessarily. - if ( total_bytes_to_read > ZM_NETWORK_BUFSIZ ) { + if (total_bytes_to_read > ZM_NETWORK_BUFSIZ) { total_bytes_to_read = ZM_NETWORK_BUFSIZ; - Debug(4, "Just getting 32K" ); + Debug(4, "Just getting 32K"); } else { - Debug(4, "Just getting %d", total_bytes_to_read ); + Debug(4, "Just getting %d", total_bytes_to_read); } } // end if bytes_expected or not - Debug( 4, "Expecting %d bytes", total_bytes_to_read ); + Debug(4, "Expecting %d bytes", total_bytes_to_read); int total_bytes_read = 0; do { - int bytes_read = buffer.read_into( sd, total_bytes_to_read ); - if ( bytes_read < 0 ) { - Error( "Read error: %s", strerror(errno) ); - return( -1 ); - } else if ( bytes_read == 0 ) { - Debug( 2, "Socket closed" ); + int bytes_read = buffer.read_into(sd, total_bytes_to_read); + if (bytes_read < 0) { + Error("Read error: %s", strerror(errno)); + return -1; + } else if (bytes_read == 0) { + Debug(2, "Socket closed"); //Disconnect(); // Disconnect is done outside of ReadData now. - return( -1 ); - } else if ( (unsigned int)bytes_read < total_bytes_to_read ) { - Error( "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read ); - return( -1 ); + return -1; + } else if ((unsigned int)bytes_read < total_bytes_to_read) { + Debug(1, "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read); + } else { + Debug(3, "Read %d bytes", bytes_read); } - Debug( 3, "Read %d bytes", bytes_read ); total_bytes_read += bytes_read; total_bytes_to_read -= bytes_read; - } while ( total_bytes_to_read ); + } while (total_bytes_to_read); - Debug(4, buffer); + Debug(4, "buffer size: %d", static_cast(buffer)); return total_bytes_read; -} +} // end readData int RemoteCameraHttp::GetData() { time_t start_time = time(nullptr); @@ -497,36 +497,36 @@ int RemoteCameraHttp::GetResponse() { return( -1 ); } - if ( content_length ) { - while ( ((long)buffer.size() < content_length ) && ! zm_terminate ) { - Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); - int bytes_read = GetData(); + if (content_length) { + while (((long)buffer.size() < content_length ) and !zm_terminate) { + Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length); + int bytes_read = ReadData(buffer, content_length - buffer.size()); - if ( bytes_read < 0 ) { - Error( "Unable to read content" ); - return( -1 ); + if (bytes_read < 0) { + Error("Unable to read content"); + return -1; } bytes += bytes_read; } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); + Debug(3, "Got end of image by length, content-length = %d", content_length); } else { - while ( !content_length ) { + while (!content_length) { buffer_len = GetData(); - if ( buffer_len < 0 ) { - Error( "Unable to read content" ); - return( -1 ); + if (buffer_len < 0) { + Error("Unable to read content"); + return -1; } bytes += buffer_len; static RegExpr *content_expr = 0; - if ( mode == MULTI_IMAGE ) { - if ( !content_expr ) { + if (mode == MULTI_IMAGE) { + if (!content_expr) { char content_pattern[256] = ""; - snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); - content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); + snprintf(content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary); + content_expr = new RegExpr(content_pattern, PCRE_DOTALL); } - if ( content_expr->Match( buffer, buffer.size() ) == 2 ) { + if (content_expr->Match( buffer, buffer.size()) == 2) { content_length = content_expr->MatchLength( 1 ); - Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); + Debug(3, "Got end of image by pattern, content-length = %d", content_length); } } } @@ -623,7 +623,7 @@ int RemoteCameraHttp::GetResponse() { case HEADERCONT : { buffer_len = GetData(); - if ( buffer_len < 0 ) { + if (buffer_len < 0) { Error("Unable to read header"); return -1; } @@ -634,13 +634,13 @@ int RemoteCameraHttp::GetResponse() { int header_len = buffer.size(); bool all_headers = false; - while ( true ) { + while (true and !zm_terminate) { int crlf_len = memspn(header_ptr, "\r\n", header_len); - if ( n_headers ) { + if (n_headers) { if ( - (crlf_len == 2 && !strncmp(header_ptr, "\n\n", crlf_len )) + (crlf_len == 2 && !strncmp(header_ptr, "\n\n", crlf_len)) || - (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) + (crlf_len == 4 && !strncmp(header_ptr, "\r\n\r\n", crlf_len)) ) { Debug(3, "Have double linefeed, done headers"); *header_ptr = '\0'; @@ -650,8 +650,8 @@ int RemoteCameraHttp::GetResponse() { break; } } - if ( crlf_len ) { - if ( header_len == crlf_len ) { + if (crlf_len) { + if (header_len == crlf_len) { break; } else { *header_ptr = '\0'; @@ -661,22 +661,22 @@ int RemoteCameraHttp::GetResponse() { } Debug(6, "%s", header_ptr); - if ( (crlf = mempbrk(header_ptr, "\r\n", header_len)) ) { + if ((crlf = mempbrk(header_ptr, "\r\n", header_len))) { //headers[n_headers++] = header_ptr; n_headers++; - if ( !http_header && (strncasecmp(header_ptr, http_match, http_match_len) == 0) ) { + if (!http_header && (strncasecmp(header_ptr, http_match, http_match_len) == 0)) { http_header = header_ptr+http_match_len; - Debug( 6, "Got http header '%s'", header_ptr ); + Debug(6, "Got http header '%s'", header_ptr); } else if ( !connection_header && (strncasecmp(header_ptr, connection_match, connection_match_len) == 0) ) { connection_header = header_ptr+connection_match_len; - Debug( 6, "Got connection header '%s'", header_ptr ); + Debug(6, "Got connection header '%s'", header_ptr); } else if ( !content_length_header && (strncasecmp(header_ptr, content_length_match, content_length_match_len) == 0) ) { content_length_header = header_ptr+content_length_match_len; - Debug( 6, "Got content length header '%s'", header_ptr ); + Debug(6, "Got content length header '%s'", header_ptr); } else if ( !authenticate_header && (strncasecmp(header_ptr, authenticate_match, authenticate_match_len) == 0) ) { authenticate_header = header_ptr; - Debug( 6, "Got authenticate header '%s'", header_ptr ); + Debug(6, "Got authenticate header '%s'", header_ptr); } else if ( !content_type_header && (strncasecmp(header_ptr, content_type_match, content_type_match_len) == 0) ) { content_type_header = header_ptr+content_type_match_len; Debug(6, "Got content type header '%s'", header_ptr); @@ -691,10 +691,10 @@ int RemoteCameraHttp::GetResponse() { } } // end while search for headers - if ( all_headers ) { + if (all_headers) { char *start_ptr, *end_ptr; - if ( !http_header ) { + if (!http_header) { Error("Unable to extract HTTP status from header"); return -1; } @@ -703,7 +703,7 @@ int RemoteCameraHttp::GetResponse() { end_ptr = start_ptr+strspn(start_ptr, "10."); // FIXME Why are we memsetting every time? Can we not do it once? - memset(http_version, 0, sizeof(http_version)); + //memset(http_version, 0, sizeof(http_version)); strncpy(http_version, start_ptr, end_ptr-start_ptr); start_ptr = end_ptr; @@ -718,12 +718,12 @@ int RemoteCameraHttp::GetResponse() { start_ptr += strspn(start_ptr, " "); strcpy(status_mesg, start_ptr); - if ( status == 401 ) { - if ( mNeedAuth ) { + if (status == 401) { + if (mNeedAuth) { Error("Failed authentication"); return -1; } - if ( !authenticate_header ) { + if (!authenticate_header) { Error("Failed authentication, but don't have an authentication header."); return -1; } @@ -732,7 +732,7 @@ int RemoteCameraHttp::GetResponse() { Debug(2, "Checking for digest auth in %s", authenticate_header); mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { + if (mAuthenticator->auth_method() == zm::AUTH_DIGEST) { Debug(2, "Need Digest Authentication"); request = stringtf("GET %s HTTP/%s\r\n", path.c_str(), config.http_version); request += stringtf("User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION); @@ -747,26 +747,26 @@ int RemoteCameraHttp::GetResponse() { } else { Debug(2, "Need some other kind of Authentication"); } - } else if ( status < 200 || status > 299 ) { + } else if (status < 200 || status > 299) { Error("Invalid response status %s: %s", status_code, status_mesg); return -1; } Debug(3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version); - if ( connection_header ) { + if (connection_header) { memset(connection_type, 0, sizeof(connection_type)); start_ptr = connection_header + strspn(connection_header, " "); // FIXME Should we not use strncpy? strcpy(connection_type, start_ptr); Debug(3, "Got connection '%s'", connection_type); } - if ( content_length_header ) { + if (content_length_header) { start_ptr = content_length_header + strspn(content_length_header, " "); - content_length = atoi( start_ptr ); + content_length = atoi(start_ptr); Debug(3, "Got content length '%d'", content_length); } - if ( content_type_header ) { - memset(content_type, 0, sizeof(content_type)); + if (content_type_header) { + //memset(content_type, 0, sizeof(content_type)); start_ptr = content_type_header + strspn(content_type_header, " "); if ( (end_ptr = strchr(start_ptr, ';')) ) { strncpy(content_type, start_ptr, end_ptr-start_ptr); @@ -774,7 +774,7 @@ int RemoteCameraHttp::GetResponse() { start_ptr = end_ptr + strspn(end_ptr, "; "); - if ( strncasecmp(start_ptr, boundary_match, boundary_match_len) == 0 ) { + if (strncasecmp(start_ptr, boundary_match, boundary_match_len) == 0) { start_ptr += boundary_match_len; start_ptr += strspn(start_ptr, "-"); content_boundary_len = sprintf(content_boundary, "--%s", start_ptr); @@ -788,24 +788,24 @@ int RemoteCameraHttp::GetResponse() { } } // end if content_type_header - if ( !strcasecmp(content_type, "image/jpeg") || !strcasecmp(content_type, "image/jpg") ) { + if (!strcasecmp(content_type, "image/jpeg") || !strcasecmp(content_type, "image/jpg")) { // Single image mode = SINGLE_IMAGE; format = JPEG; state = CONTENT; - } else if ( !strcasecmp(content_type, "image/x-rgb") ) { + } else if (!strcasecmp(content_type, "image/x-rgb")) { // Single image mode = SINGLE_IMAGE; format = X_RGB; state = CONTENT; - } else if ( !strcasecmp(content_type, "image/x-rgbz") ) { + } else if (!strcasecmp(content_type, "image/x-rgbz")) { // Single image mode = SINGLE_IMAGE; format = X_RGBZ; state = CONTENT; - } else if ( !strcasecmp(content_type, "multipart/x-mixed-replace") ) { + } else if (!strcasecmp(content_type, "multipart/x-mixed-replace")) { // Image stream, so start processing - if ( !content_boundary[0] ) { + if (!content_boundary[0]) { Error("No content boundary found in header '%s'", content_type_header); return -1; } @@ -817,8 +817,8 @@ int RemoteCameraHttp::GetResponse() { //// MPEG stream, coming soon! //} else { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); + Error("Unrecognised content type '%s'", content_type); + return -1; } } else { Debug(3, "Unable to extract entire header from stream, continuing"); @@ -844,24 +844,24 @@ int RemoteCameraHttp::GetResponse() { int subheader_len = buffer.size(); bool all_headers = false; - while( true ) { - int crlf_len = memspn( subheader_ptr, "\r\n", subheader_len ); - if ( n_subheaders ) { - if ( (crlf_len == 2 && !strncmp( subheader_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) { + while (true and !zm_terminate) { + int crlf_len = memspn(subheader_ptr, "\r\n", subheader_len); + if (n_subheaders) { + if ( (crlf_len == 2 && !strncmp(subheader_ptr, "\n\n", crlf_len)) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) { *subheader_ptr = '\0'; subheader_ptr += crlf_len; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + subheader_len -= buffer.consume(subheader_ptr-(char *)buffer); all_headers = true; break; } } - if ( crlf_len ) { - if ( subheader_len == crlf_len ) { + if (crlf_len) { + if (subheader_len == crlf_len) { break; } else { *subheader_ptr = '\0'; subheader_ptr += crlf_len; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + subheader_len -= buffer.consume(subheader_ptr-(char *)buffer); } } @@ -905,29 +905,29 @@ int RemoteCameraHttp::GetResponse() { } } - if ( all_headers && boundary_header ) { + if (all_headers && boundary_header) { char *start_ptr/*, *end_ptr*/; - Debug( 3, "Got boundary '%s'", boundary_header ); + Debug(3, "Got boundary '%s'", boundary_header); - if ( subcontent_length_header[0] ) { - start_ptr = subcontent_length_header + strspn( subcontent_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got subcontent length '%d'", content_length ); + if (subcontent_length_header[0]) { + start_ptr = subcontent_length_header + strspn(subcontent_length_header, " "); + content_length = atoi(start_ptr); + Debug(3, "Got subcontent length '%d'", content_length); } - if ( subcontent_type_header[0] ) { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); - strcpy( content_type, start_ptr ); - Debug( 3, "Got subcontent type '%s'", content_type ); + if (subcontent_type_header[0]) { + //memset(content_type, 0, sizeof(content_type)); + start_ptr = subcontent_type_header + strspn(subcontent_type_header, " "); + strcpy(content_type, start_ptr); + Debug(3, "Got subcontent type '%s'", content_type); } state = CONTENT; } else { - Debug( 3, "Unable to extract subheader from stream, retrying" ); + Debug(3, "Unable to extract subheader from stream, retrying"); buffer_len = GetData(); - if ( buffer_len < 0 ) { - Error( "Unable to read subheader" ); - return( -1 ); + if (buffer_len < 0) { + Error("Unable to read subheader"); + return -1; } bytes += buffer_len; state = SUBHEADERCONT; @@ -942,90 +942,90 @@ int RemoteCameraHttp::GetResponse() { *semicolon = '\0'; } - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { + if (!strcasecmp(content_type, "image/jpeg") || !strcasecmp(content_type, "image/jpg")) { format = JPEG; - } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { + } else if (!strcasecmp(content_type, "image/x-rgb")) { format = X_RGB; - } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { + } else if (!strcasecmp(content_type, "image/x-rgbz")) { format = X_RGBZ; } else { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); + Error("Found unsupported content type '%s'", content_type); + return -1; } // This is an early test for jpeg content, so we can bail early - if ( format == JPEG && buffer.size() >= 2 ) { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); - return( -1 ); + if (format == JPEG and buffer.size() >= 2) { + if (buffer[0] != 0xff or buffer[1] != 0xd8) { + Error("Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1]); + return -1; } } - if ( content_length ) { - while ( ( (long)buffer.size() < content_length ) && ! zm_terminate ) { + if (content_length) { + while (((long)buffer.size() < content_length) and !zm_terminate) { Debug(4, "getting more data"); - int bytes_read = GetData(); - if ( bytes_read < 0 ) { + int bytes_read = ReadData(buffer, content_length-buffer.size()); + if (bytes_read < 0) { Error("Unable to read content"); return -1; } bytes += bytes_read; } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); + Debug(3, "Got end of image by length, content-length = %d", content_length); } else { // Read until we find the end of image or the stream closes. - while ( !content_length && !zm_terminate ) { + while (!content_length && !zm_terminate) { Debug(4, "!content_length, ReadData"); - buffer_len = ReadData( buffer ); - if ( buffer_len < 0 ) { - Error( "Unable to read content" ); - return( -1 ); + buffer_len = ReadData(buffer); + if (buffer_len < 0) { + Error("Unable to read content"); + return -1; } bytes += buffer_len; int buffer_size = buffer.size(); - if ( buffer_len ) { + if (buffer_len) { // Got some data - if ( mode == MULTI_IMAGE ) { + if (mode == MULTI_IMAGE) { // Look for the boundary marker, determine content length using it's position - if ( char *start_ptr = (char *)memstr( (char *)buffer, "\r\n--", buffer_size ) ) { + if (char *start_ptr = (char *)memstr( (char *)buffer, "\r\n--", buffer_size)) { content_length = start_ptr - (char *)buffer; - Debug( 2, "Got end of image by pattern (crlf--), content-length = %d", content_length ); + Debug(2, "Got end of image by pattern (crlf--), content-length = %d", content_length); } else { - Debug( 2, "Did not find end of image by patten (crlf--) yet, content-length = %d", content_length ); + Debug(2, "Did not find end of image by patten (crlf--) yet, content-length = %d", content_length); } } // end if MULTI_IMAGE } else { content_length = buffer_size; - Debug( 2, "Got end of image by closure, content-length = %d", content_length ); - if ( mode == SINGLE_IMAGE ) { + Debug(2, "Got end of image by closure, content-length = %d", content_length); + if (mode == SINGLE_IMAGE) { char *end_ptr = (char *)buffer+buffer_size; // strip off any last line feeds - while( *end_ptr == '\r' || *end_ptr == '\n' ) { + while (*end_ptr == '\r' || *end_ptr == '\n') { content_length--; end_ptr--; } - if ( end_ptr != ((char *)buffer+buffer_size) ) { - Debug( 2, "Trimmed end of image, new content-length = %d", content_length ); + if (end_ptr != ((char *)buffer+buffer_size)) { + Debug(2, "Trimmed end of image, new content-length = %d", content_length); } } // end if SINGLE_IMAGE } // end if read some data } // end while ! content_length } // end if content_length - if ( mode == SINGLE_IMAGE ) { + if (mode == SINGLE_IMAGE) { state = HEADER; Disconnect(); } else { state = SUBHEADER; } - if ( format == JPEG && buffer.size() >= 2 ) { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); - return( -1 ); + if (format == JPEG && buffer.size() >= 2) { + if (buffer[0] != 0xff || buffer[1] != 0xd8) { + Error("Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1]); + return -1; } } diff --git a/src/zm_rtsp_server.cpp b/src/zm_rtsp_server.cpp index 0fb5a8d15..7fe8e916a 100644 --- a/src/zm_rtsp_server.cpp +++ b/src/zm_rtsp_server.cpp @@ -187,14 +187,16 @@ int main(int argc, char *argv[]) { for (size_t i = 0; i < monitors.size(); i++) { std::shared_ptr monitor = monitors[i]; - if (!(monitor->ShmValid() or monitor->connect())) { - Warning("Couldn't connect to monitor %d", monitor->Id()); - if (sessions[i]) { - rtspServer->RemoveSession(sessions[i]->GetMediaSessionId()); - sessions[i] = nullptr; - } - monitor->disconnect(); - continue; + 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]) { diff --git a/src/zm_rtsp_server_authenticator.h b/src/zm_rtsp_server_authenticator.h index 55a7784e7..9908327d3 100644 --- a/src/zm_rtsp_server_authenticator.h +++ b/src/zm_rtsp_server_authenticator.h @@ -46,6 +46,9 @@ class ZM_RtspServer_Authenticator : public xop::Authenticator { if (query.has("jwt_token")) { const QueryParameter *jwt_token = query.get("jwt_token"); user = zmLoadTokenUser(jwt_token->firstValue().c_str(), false); + } else if (query.has("token")) { + const QueryParameter *jwt_token = query.get("token"); + user = zmLoadTokenUser(jwt_token->firstValue().c_str(), false); } else if (strcmp(config.auth_relay, "none") == 0) { if (query.has("username")) { std::string username = query.get("username")->firstValue(); diff --git a/src/zm_rtsp_server_fifo_h264_source.cpp b/src/zm_rtsp_server_fifo_h264_source.cpp index 6d7dda1b1..3d32f1809 100644 --- a/src/zm_rtsp_server_fifo_h264_source.cpp +++ b/src/zm_rtsp_server_fifo_h264_source.cpp @@ -83,6 +83,7 @@ std::list< std::pair > H264_ZoneMinderFifoSource::splitF } #endif frameList.push_back(std::pair(buffer, size)); + if (!bufSize) break; buffer = this->extractFrame(&buffer[size], bufSize, size); } // end while buffer diff --git a/src/zm_rtsp_server_fifo_source.cpp b/src/zm_rtsp_server_fifo_source.cpp index cc3d443b1..1b3b4f73d 100644 --- a/src/zm_rtsp_server_fifo_source.cpp +++ b/src/zm_rtsp_server_fifo_source.cpp @@ -84,7 +84,7 @@ int ZoneMinderFifoSource::getNextFrame() { return -1; } - Debug(4, "%s bytes read %d bytes, buffer size %u", m_fifo.c_str(), bytes_read, m_buffer.size()); + Debug(1, "%s bytes read %d bytes, buffer size %u", m_fifo.c_str(), bytes_read, m_buffer.size()); while (m_buffer.size()) { unsigned int data_size = 0; int64_t pts; @@ -96,14 +96,14 @@ int ZoneMinderFifoSource::getNextFrame() { header_end = (unsigned char *)memchr(header_start, '\n', m_buffer.tail()-header_start); if (!header_end) { // Must not have enough data. So... keep all. - Debug(1, "Didn't find newline"); + Debug(1, "Didn't find newline buffer size is %d", m_buffer.size()); return 0; } unsigned int header_size = header_end-header_start; char *header = new char[header_size+1]; + strncpy(header, reinterpret_cast(header_start), header_size); header[header_size] = '\0'; - strncpy(header, reinterpret_cast(header_start), header_end-header_start); char *content_length_ptr = strchr(header, ' '); if (!content_length_ptr) { @@ -125,12 +125,12 @@ int ZoneMinderFifoSource::getNextFrame() { pts_ptr ++; data_size = atoi(content_length_ptr); pts = strtoll(pts_ptr, nullptr, 10); + Debug(4, "ZM Packet %s header_size %d packet size %u pts %" PRId64, header, header_size, data_size, pts); delete[] header; } else { - Debug(1, "ZM header not found %s.",m_buffer.head()); + Debug(1, "ZM header not found in %d of buffer:%s.", m_buffer.size(), m_buffer.head()); return 0; } - Debug(4, "ZM Packet size %u pts %" PRId64, data_size, pts); if (header_start != m_buffer) { Debug(4, "ZM Packet didn't start at beginning of buffer %u. %c%c", header_start-m_buffer.head(), m_buffer[0], m_buffer[1]); @@ -142,21 +142,21 @@ int ZoneMinderFifoSource::getNextFrame() { int bytes_needed = data_size - (m_buffer.size() - header_size); if (bytes_needed > 0) { Debug(4, "Need another %d bytes. Trying to read them", bytes_needed); - int bytes_read = m_buffer.read_into(m_fd, bytes_needed, {1,0}); + int bytes_read = m_buffer.read_into(m_fd, bytes_needed); if ( bytes_read != bytes_needed ) { - Debug(4, "Failed to read another %d bytes.", bytes_needed); + Debug(1, "Failed to read another %d bytes, got %d.", bytes_needed, bytes_read); return -1; } } - unsigned char *packet_start = m_buffer.head() + header_size; + m_buffer.consume(header_size); + unsigned char *packet_start = m_buffer.head(); size_t bytes_remaining = data_size; std::list< std::pair > framesList = this->splitFrames(packet_start, bytes_remaining); Debug(3, "Got %d frames, consuming %d bytes, remaining %d", framesList.size(), header_size + data_size, bytes_remaining); - m_buffer.consume(header_size + data_size); + m_buffer.consume(data_size); while (framesList.size()) { std::pair nal = framesList.front(); framesList.pop_front(); - PushFrame(nal.first, nal.second, pts); } } // end while m_buffer.size() diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 1f141ec65..869341114 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -40,7 +40,7 @@ StreamBase::~StreamBase() { bool StreamBase::loadMonitor(int p_monitor_id) { monitor_id = p_monitor_id; - if ( !(monitor = Monitor::Load(monitor_id, false, Monitor::QUERY)) ) { + if ( !(monitor or (monitor = Monitor::Load(monitor_id, false, Monitor::QUERY))) ) { Error("Unable to load monitor id %d for streaming", monitor_id); return false; } @@ -52,6 +52,7 @@ bool StreamBase::loadMonitor(int p_monitor_id) { if ( !monitor->connect() ) { Error("Unable to connect to monitor id %d for streaming", monitor_id); + monitor->disconnect(); return false; } @@ -71,6 +72,10 @@ bool StreamBase::checkInitialised() { Error("Monitor shm is not connected"); return false; } + if ((monitor->GetType() == Monitor::FFMPEG) and !monitor->DecodingEnabled() ) { + Error("Monitor is not decoding."); + return false; + } return true; } diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 512718ccb..13cf7c1ac 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -245,18 +245,19 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { const char *pass = dbrow[2]; time_t our_now = now; + tm now_tm = {}; for ( unsigned int i = 0; i < hours; i++, our_now -= 3600 ) { - struct tm *now_tm = localtime(&our_now); + localtime_r(&our_now, &now_tm); snprintf(auth_key, sizeof(auth_key)-1, "%s%s%s%s%d%d%d%d", config.auth_hash_secret, user, pass, remote_addr, - now_tm->tm_hour, - now_tm->tm_mday, - now_tm->tm_mon, - now_tm->tm_year); + now_tm.tm_hour, + now_tm.tm_mday, + now_tm.tm_mon, + now_tm.tm_year); #if HAVE_DECL_MD5 MD5((unsigned char *)auth_key, strlen(auth_key), md5sum); diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index 3fce11d1c..71f99ef9e 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -22,7 +22,7 @@ #include "zm_config.h" #include "zm_logger.h" #include -#include +#include #include #include /* Definition of AT_* constants */ #include @@ -43,7 +43,7 @@ std::string trimSet(std::string str, std::string trimset) { // Trim Both leading and trailing sets size_t startpos = str.find_first_not_of(trimset); // Find the first character position after excluding leading blank spaces size_t endpos = str.find_last_not_of(trimset); // Find the first character position from reverse af - + // if all spaces or empty return an empty string if ( ( std::string::npos == startpos ) || ( std::string::npos == endpos ) ) return std::string(""); @@ -148,7 +148,7 @@ const std::string base64Encode(const std::string &inString) { selection = remainder | (*inPtr >> 4); remainder = (*inPtr++ & 0x0f) << 2; outString += base64_table[selection]; - + if ( *inPtr ) { selection = remainder | (*inPtr >> 6); outString += base64_table[selection]; @@ -175,7 +175,7 @@ int split(const char* string, const char delim, std::vector& items) return -2; std::string str(string); - + while ( true ) { size_t pos = str.find(delim); items.push_back(str.substr(0, pos)); @@ -242,7 +242,7 @@ void hwcaps_detect() { } else { sse_version = 0; Debug(1, "Detected a x86\\x86-64 processor"); - } + } #elif defined(__arm__) // ARM processor in 32bit mode // To see if it supports NEON, we need to get that information from the kernel @@ -279,7 +279,7 @@ void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes) { "sse2_copy_iter:\n\t" "movdqa (%0),%%xmm0\n\t" "movdqa 0x10(%0),%%xmm1\n\t" - "movdqa 0x20(%0),%%xmm2\n\t" + "movdqa 0x20(%0),%%xmm2\n\t" "movdqa 0x30(%0),%%xmm3\n\t" "movdqa 0x40(%0),%%xmm4\n\t" "movdqa 0x50(%0),%%xmm5\n\t" @@ -328,16 +328,17 @@ void timespec_diff(struct timespec *start, struct timespec *end, struct timespec } } -char *timeval_to_string( struct timeval tv ) { - time_t nowtime; - struct tm *nowtm; - static char tmbuf[20], buf[28]; +std::string TimevalToString(timeval tv) { + tm now = {}; + std::array tm_buf = {}; - nowtime = tv.tv_sec; - nowtm = localtime(&nowtime); - strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm); - snprintf(buf, sizeof buf-1, "%s.%06ld", tmbuf, tv.tv_usec); - return buf; + localtime_r(&tv.tv_sec, &now); + size_t tm_buf_len = strftime(tm_buf.data(), tm_buf.size(), "%Y-%m-%d %H:%M:%S", &now); + if (tm_buf_len == 0) { + return ""; + } + + return stringtf("%s.%06ld", tm_buf.data(), tv.tv_usec); } std::string UriDecode( const std::string &encoded ) { diff --git a/src/zm_utils.h b/src/zm_utils.h index 4eba32b6a..41b6789fe 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -63,7 +63,7 @@ void hwcaps_detect(); extern unsigned int sse_version; extern unsigned int neonversion; -char *timeval_to_string( struct timeval tv ); +std::string TimevalToString(timeval tv); std::string UriDecode( const std::string &encoded ); void touch( const char *pathname ); diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 4d4bf7e6d..7ea0f3e1a 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -984,7 +984,7 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { ); } else if ( !zm_packet->in_frame ) { Debug(4, "Have no in_frame"); - if ( zm_packet->packet.size ) { + if (zm_packet->packet.size and !zm_packet->decoded) { Debug(4, "Decoding"); if ( !zm_packet->decode(video_in_ctx) ) { Debug(2, "unable to decode yet."); @@ -1216,29 +1216,6 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { return 0; } // end int VideoStore::writeAudioFramePacket(AVPacket *ipkt) -int VideoStore::write_packets(PacketQueue &queue) { - // Need to write out all the frames from the last keyframe? - // No... need to write out all frames from when the event began. Due to PreEventFrames, this could be more than since the last keyframe. - unsigned int packet_count = 0; - ZMPacket *queued_packet; - - while ( ( queued_packet = queue.popPacket() ) ) { - AVPacket *avp = queued_packet->av_packet(); - - packet_count += 1; - //Write the packet to our video store - Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", - avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, queue.size() ); - int ret = this->writePacket( queued_packet ); - if ( ret < 0 ) { - //Less than zero and we skipped a frame - } - delete queued_packet; - } // end while packets in the packetqueue - Debug(2, "Wrote %d queued packets", packet_count ); - return packet_count; -} // end int VideoStore::write_packets( PacketQueue &queue ) { - int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) { pkt->pos = -1; pkt->stream_index = stream->index; diff --git a/src/zmc.cpp b/src/zmc.cpp index 19c8891c7..6a9a37c14 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -54,7 +54,6 @@ possible, this should run at more or less constant speed. */ #include "zm.h" -#include "zm_analysis_thread.h" #include "zm_camera.h" #include "zm_db.h" #include "zm_define.h" @@ -272,8 +271,6 @@ int main(int argc, char *argv[]) { } // end foreach monitor if (zm_terminate) break; - std::vector> analysis_threads = std::vector>(); - int *capture_delays = new int[monitors.size()]; int *alarm_capture_delays = new int[monitors.size()]; struct timeval * last_capture_times = new struct timeval[monitors.size()]; @@ -284,12 +281,6 @@ int main(int argc, char *argv[]) { alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay(); Debug(2, "capture delay(%u mSecs 1000/capture_fps) alarm delay(%u)", capture_delays[i], alarm_capture_delays[i]); - - Monitor::Function function = monitors[0]->GetFunction(); - if (function != Monitor::MONITOR) { - Debug(1, "Starting an analysis thread for monitor (%d)", monitors[i]->Id()); - analysis_threads.emplace_back(ZM::make_unique(monitors[i])); - } } struct timeval now; @@ -320,29 +311,31 @@ int main(int argc, char *argv[]) { break; } - gettimeofday(&now, nullptr); // capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate. int delay = (monitors[i]->GetState() == Monitor::ALARM) ? alarm_capture_delays[i] : capture_delays[i]; - if (delay && last_capture_times[i].tv_sec) { - // DT_PREC_3 means that the value will be in thousands of a second - DELTA_TIMEVAL(delta_time, now, last_capture_times[i], DT_PREC_6); + if (delay) { + gettimeofday(&now, nullptr); + if (last_capture_times[i].tv_sec) { + // DT_PREC_3 means that the value will be in thousands of a second + DELTA_TIMEVAL(delta_time, now, last_capture_times[i], DT_PREC_6); - // You have to add back in the previous sleep time - sleep_time = delay - (delta_time.delta - sleep_time); - Debug(4, "Sleep time is %d from now:%d.%d last:%d.%d delta %d delay: %d", - sleep_time, - now.tv_sec, now.tv_usec, - last_capture_times[i].tv_sec, last_capture_times[i].tv_usec, - delta_time.delta, - delay - ); - - if (sleep_time > 0) { - Debug(4, "usleeping (%d)", sleep_time); - usleep(sleep_time); - } - } // end if has a last_capture time - last_capture_times[i] = now; + // You have to add back in the previous sleep time + sleep_time = delay - (delta_time.delta - sleep_time); + Debug(4, "Sleep time is %d from now:%d.%d last:%d.%d delta %d delay: %d", + sleep_time, + now.tv_sec, now.tv_usec, + last_capture_times[i].tv_sec, last_capture_times[i].tv_usec, + delta_time.delta, + delay + ); + + if (sleep_time > 0) { + Debug(4, "usleeping (%d)", sleep_time); + usleep(sleep_time); + } + } // end if has a last_capture time + last_capture_times[i] = now; + } // end if delay } // end foreach n_monitors if ((result < 0) or zm_reload) { @@ -351,16 +344,10 @@ int main(int argc, char *argv[]) { } } // end while ! zm_terminate and connected - for (std::unique_ptr &analysis_thread: analysis_threads) - analysis_thread->Stop(); - for (size_t i = 0; i < monitors.size(); i++) { monitors[i]->Close(); } - // Killoff the analysis threads. Don't need them spinning while we try to reconnect - analysis_threads.clear(); - delete [] alarm_capture_delays; delete [] capture_delays; delete [] last_capture_times; @@ -385,7 +372,7 @@ int main(int argc, char *argv[]) { for (std::shared_ptr &monitor : monitors) { static char sql[ZM_SQL_SML_BUFSIZ]; snprintf(sql, sizeof(sql), - "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%d, 'Connected') ON DUPLICATE KEY UPDATE Status='NotRunning'", + "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%d, 'NotRunning') ON DUPLICATE KEY UPDATE Status='NotRunning'", monitor->Id()); zmDbDo(sql); } diff --git a/src/zms.cpp b/src/zms.cpp index 89b20e30c..70ec38fdc 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -241,8 +241,9 @@ int main(int argc, const char *argv[], char **envp) { time_t now = time(nullptr); char date_string[64]; + tm now_tm = {}; strftime(date_string, sizeof(date_string)-1, - "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); + "%a, %d %b %Y %H:%M:%S GMT", gmtime_r(&now, &now_tm)); fputs("Last-Modified: ", stdout); fputs(date_string, stdout); diff --git a/src/zmu.cpp b/src/zmu.cpp index 0bfe90b3b..384af99a3 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -506,8 +506,10 @@ int main(int argc, char *argv[]) { struct timeval timestamp = monitor->GetTimestamp(image_idx); if ( verbose ) { char timestamp_str[64] = "None"; - if ( timestamp.tv_sec ) - strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime(×tamp.tv_sec)); + if ( timestamp.tv_sec ) { + tm tm_info = {}; + strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime_r(×tamp.tv_sec, &tm_info)); + } if ( image_idx == -1 ) printf("Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000); else diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index d464089d3..0ba1d2d07 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -117,6 +117,17 @@ commonprep () { exit 1 fi fi + + if [ -e "build/RtspServer" ]; then + echo "Found existing RtspServer..." + else + echo "Cloning RtspServer ..." + git clone https://github.com/ZoneMinder/RtspServer build/RtspServer + if [ $? -ne 0 ]; then + echo "ERROR: RtspServer clone failed..." + exit 1 + fi + fi } # Uncompress the submodule tarballs and move them into place @@ -137,6 +148,13 @@ movecrud () { rmdir web/api/app/Plugin/CakePHP-Enum-Behavior mv -f CakePHP-Enum-Behavior-${CEBVER} web/api/app/Plugin/CakePHP-Enum-Behavior fi + if [ -e "dep/RtspServer/CMakeLists.txt" ]; then + echo "RtspServer already installed..." + else + echo "Copying RtspServer..." + rm -r dep/RtspServer + cp -Rpd build/RtspServer dep/RtspServer + fi } # previsouly part of installzm.sh @@ -347,7 +365,7 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia setdebpkgname movecrud - if [ "${DIST}" == "focal" ] || [ "${DIST}" == "buster" ]; then + if [ "${DIST}" == "focal" ] || [ "${DIST}" == "groovy" ] || [ "${DIST}" == "hirsuit" ] || [ "${DIST}" == "buster" ]; then ln -sfT distros/ubuntu2004 debian elif [ "${DIST}" == "beowulf" ]; then ln -sfT distros/beowulf debian diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 2a6374222..184e17bb3 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -344,7 +344,7 @@ class MonitorsController extends AppController { public function daemonControl($id, $command, $daemon=null) { // Need to see if it is local or remote $monitor = $this->Monitor->find('first', array( - 'fields' => array('Type', 'Function', 'Device', 'ServerId'), + 'fields' => array('Id', 'Type', 'Function', 'Device', 'ServerId'), 'conditions' => array('Id' => $id) )); $monitor = $monitor['Monitor']; diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 9470c68f3..eb9efed29 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -83,7 +83,7 @@ class Monitor extends ZM_Object { 'LabelX' => 0, 'LabelY' => 0, 'LabelSize' => 1, - 'ImageBufferCount' => 20, + 'ImageBufferCount' => 3, 'WarmupCount' => 0, 'PreEventCount' => 5, 'PostEventCount' => 5, diff --git a/web/includes/Object.php b/web/includes/Object.php index 9c9a3c095..337136836 100644 --- a/web/includes/Object.php +++ b/web/includes/Object.php @@ -5,6 +5,7 @@ require_once('database.php'); $object_cache = array(); class ZM_Object { + protected $_last_error; public function __construct($IdOrRow = NULL) { $class = get_class($this); @@ -38,6 +39,7 @@ class ZM_Object { public function __call($fn, array $args){ $type = (array_key_exists($fn, $this->defaults) && is_array($this->defaults[$fn])) ? $this->defaults[$fn]['type'] : 'scalar'; + if ( count($args) ) { if ( $type == 'set' and is_array($args[0]) ) { $this->{$fn} = implode(',', $args[0]); @@ -50,7 +52,15 @@ class ZM_Object { $this->{$fn} = preg_replace($this->defaults[$fn]['filter_regexp'], '', $args[0]); } } else { - $this->{$fn} = $args[0]; + if ( $args[0] == '' and array_key_exists($fn, $this->defaults) ) { + if ( is_array($this->defaults[$fn]) ) { + $this->{$fn} = $this->defaults[$fn]['default']; + } else { + $this->{$fn} = $this->defaults[$fn]; + } + } else { + $this->{$fn} = $args[0]; + } } } @@ -174,7 +184,7 @@ class ZM_Object { $this->$field($value); } else { if ( is_array($value) ) { -# perhaps should turn into a comma-separated string + # perhaps should turn into a comma-separated string $this->{$field} = implode(',', $value); } else if ( is_string($value) ) { if ( array_key_exists($field, $this->defaults) && is_array($this->defaults[$field]) && isset($this->defaults[$field]['filter_regexp']) ) { @@ -185,8 +195,14 @@ class ZM_Object { } else { $this->{$field} = preg_replace($this->defaults[$field]['filter_regexp'], '', trim($value)); } + } else if ( $value == '' and array_key_exists($field, $this->defaults) ) { + if ( is_array($this->defaults[$field]) ) { + $this->{$field} = $this->defaults[$field]['default']; + } else { + $this->{$field} = $this->defaults[$field]; + } } else { - $this->{$field} = trim($value); + $this->{$field} = $value; } } else if ( is_integer($value) ) { $this->{$field} = $value; @@ -207,8 +223,8 @@ class ZM_Object { public function changes($new_values, $defaults=null) { $changes = array(); - if ( $defaults ) { - foreach ( $defaults as $field => $type ) { + if ($defaults) { + foreach ($defaults as $field => $type) { if ( isset($new_values[$field]) ) { # Will have already been handled above continue; @@ -247,47 +263,40 @@ class ZM_Object { } else if ( $this->$field() != $value ) { $changes[$field] = $value; } - } else if ( property_exists($this, $field) ) { + } else if (property_exists($this, $field)) { $type = (array_key_exists($field, $this->defaults) && is_array($this->defaults[$field])) ? $this->defaults[$field]['type'] : 'scalar'; - if ( $type == 'set' ) { + if ($type == 'set') { $old_value = is_array($this->$field) ? $this->$field : ($this->$field ? explode(',', $this->$field) : array()); $new_value = is_array($value) ? $value : ($value ? explode(',', $value) : array()); $diff = array_recursive_diff($old_value, $new_value); - if ( count($diff) ) { - $changes[$field] = $new_value; - } + if (count($diff)) $changes[$field] = $new_value; # Input might be a command separated string, or an array } else { - if ( array_key_exists($field, $this->defaults) && is_array($this->defaults[$field]) && isset($this->defaults[$field]['filter_regexp']) ) { - if ( is_array($this->defaults[$field]['filter_regexp']) ) { - foreach ( $this->defaults[$field]['filter_regexp'] as $regexp ) { + if (array_key_exists($field, $this->defaults) && is_array($this->defaults[$field]) && isset($this->defaults[$field]['filter_regexp'])) { + if (is_array($this->defaults[$field]['filter_regexp'])) { + foreach ($this->defaults[$field]['filter_regexp'] as $regexp) { $value = preg_replace($regexp, '', trim($value)); } } else { $value = preg_replace($this->defaults[$field]['filter_regexp'], '', trim($value)); } } - if ( $this->{$field} != $value ) { - $changes[$field] = $value; - } + if ($this->{$field} != $value) $changes[$field] = $value; } - } else if ( array_key_exists($field, $this->defaults) ) { - if ( is_array($this->defaults[$field]) and isset($this->defaults[$field]['default']) ) { + } else if (array_key_exists($field, $this->defaults)) { + if (is_array($this->defaults[$field]) and isset($this->defaults[$field]['default'])) { $default = $this->defaults[$field]['default']; } else { $default = $this->defaults[$field]; } - if ( $default != $value ) { - $changes[$field] = $value; - } + if ($default != $value) $changes[$field] = $value; } } # end foreach newvalue - return $changes; } # end public function changes @@ -302,14 +311,20 @@ class ZM_Object { # Set defaults. Note that we only replace "" with null, not other values # because for example if we want to clear TimestampFormat, we clear it, but the default is a string value foreach ( $this->defaults as $field => $default ) { - if ( (!property_exists($this, $field)) or ($this->{$field} === '') ) { - if ( is_array($default) ) { + if (!property_exists($this, $field)) { + if (is_array($default)) { $this->{$field} = $default['default']; - } else if ( $default == null ) { + } else { + $this->{$field} = $default; + } + } else if ($this->{$field} === '') { + if (is_array($default)) { + $this->{$field} = $default['default']; + } else if ($default == null) { $this->{$field} = $default; } } - } + } # end foreach default $fields = array_filter( $this->defaults, @@ -329,8 +344,7 @@ class ZM_Object { $sql = 'UPDATE `'.$table.'` SET '.implode(', ', array_map(function($field) {return '`'.$field.'`=?';}, $fields)).' WHERE Id=?'; $values = array_map(function($field){ return $this->{$field};}, $fields); $values[] = $this->{'Id'}; - if ( dbQuery($sql, $values) ) - return true; + if (dbQuery($sql, $values)) return true; } else { unset($fields['Id']); @@ -339,15 +353,16 @@ class ZM_Object { ') VALUES ('. implode(', ', array_map(function($field){return $this->$field() == 'NOW()' ? 'NOW()' : '?';}, $fields)).')'; - $values = array_values(array_map( - function($field){return $this->$field();}, - array_filter($fields, function($field){ return $this->$field() != 'NOW()';}) - )); - if ( dbQuery($sql, $values) ) { + # For some reason comparing 0 to 'NOW()' returns false; So we do this. + $filtered = array_filter($fields, function($field){ return ( (!$this->$field()) or ($this->$field() != 'NOW()'));}); + $mapped = array_map(function($field){return $this->$field();}, $filtered); + $values = array_values($mapped); + if (dbQuery($sql, $values)) { $this->{'Id'} = dbInsertId(); return true; } } + $this->_last_error = dbError($sql); return false; } // end function save @@ -421,5 +436,8 @@ class ZM_Object { public function remove_from_cache() { return ZM_Object::_remove_from_cache(get_class(), $this); } + public function get_last_error() { + return $this->_last_error; + } } # end class Object ?> diff --git a/web/includes/actions/function.php b/web/includes/actions/function.php index 338361883..fc7f7703b 100644 --- a/web/includes/actions/function.php +++ b/web/includes/actions/function.php @@ -40,10 +40,12 @@ if ( $action == 'function' ) { $newFunction = validStr($_REQUEST['newFunction']); # Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0 $newEnabled = ( !isset($_REQUEST['newEnabled']) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1'; + $newDecodingEnabled = ( !isset($_REQUEST['newDecodingEnabled']) or $_REQUEST['newDecodingEnabled'] != '1' ) ? '0' : '1'; $oldFunction = $monitor->Function(); $oldEnabled = $monitor->Enabled(); - if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) { - $monitor->save(array('Function'=>$newFunction, 'Enabled'=>$newEnabled)); + $oldDecodingEnabled = $monitor->DecodingEnabled(); + if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled || $newDecodingEnabled != $oldDecodingEnabled ) { + $monitor->save(array('Function'=>$newFunction, 'Enabled'=>$newEnabled, 'DecodingEnabled'=>$newDecodingEnabled)); if ( daemonCheck() && ($monitor->Type() != 'WebSite') ) { $monitor->zmcControl(($newFunction != 'None') ? 'restart' : 'stop'); diff --git a/web/includes/actions/user.php b/web/includes/actions/user.php index 372108355..b13471231 100644 --- a/web/includes/actions/user.php +++ b/web/includes/actions/user.php @@ -18,77 +18,72 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -if ( $action == 'Save' ) { - if ( canEdit('System') ) { - if ( !empty($_REQUEST['uid']) ) { - $dbUser = dbFetchOne('SELECT * FROM Users WHERE Id=?', NULL, array($_REQUEST['uid'])); - } else { - $dbUser = array(); - } +global $error_message; - $types = array(); - if ( isset($_REQUEST['newUser']['MonitorIds']) and is_array($_REQUEST['newUser']['MonitorIds']) ) +if ($action == 'Save') { + require_once('includes/User.php'); + $uid = isset($_REQUEST['uid']) ? validInt($_REQUEST['uid']) : 0; + $dbUser = new ZM\User($uid); + + if (canEdit('System')) { + # Need to check for uniqueness of Username + $user_with_my_username = ZM\User::find_one(array('Username'=>$_REQUEST['newUser']['Username'])); + if ($user_with_my_username and + ( ( $uid and ($user_with_my_username->Id() != $uid) ) or !$uid) + ) { + $error_message = 'There already exists a user with this Username
'; + unset($_REQUEST['redirect']); + return; + } + # What other tests should we do? + + if (isset($_REQUEST['newUser']['MonitorIds']) and is_array($_REQUEST['newUser']['MonitorIds'])) $_REQUEST['newUser']['MonitorIds'] = implode(',', $_REQUEST['newUser']['MonitorIds']); - if ( !$_REQUEST['newUser']['Password'] ) + if (!empty($_REQUEST['newUser']['Password'])) { + $_REQUEST['newUser']['Password'] = password_hash($_REQUEST['newUser']['Password'], PASSWORD_BCRYPT); + } else { unset($_REQUEST['newUser']['Password']); - - $changes = getFormChanges($dbUser, $_REQUEST['newUser'], $types); - - if ( isset($_REQUEST['newUser']['Password']) ) { - if ( function_exists('password_hash') ) { - $pass_hash = '"'.password_hash($_REQUEST['newUser']['Password'], PASSWORD_BCRYPT).'"'; - } else { - $pass_hash = ' PASSWORD('.dbEscape($_REQUEST['newUser']['Password']).') '; - ZM\Info('Cannot use bcrypt as you are using PHP < 5.3'); - } - - if ( $_REQUEST['newUser']['Password'] ) { - $changes['Password'] = 'Password = '.$pass_hash; - } else { - unset($changes['Password']); - } } + $changes = $dbUser->changes($_REQUEST['newUser']); + ZM\Debug("Changes: " . print_r($changes, true)); - if ( count($changes) ) { - if ( !empty($_REQUEST['uid']) ) { - dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id = ?', array($_REQUEST['uid'])); - # If we are updating the logged in user, then update our session user data. - if ( $user and ( $dbUser['Username'] == $user['Username'] ) ) { + if (count($changes)) { + if (!$dbUser->save($changes)) { + $error_message = $dbUser->get_last_error(); + unset($_REQUEST['redirect']); + return; + } + + if ($uid) { + if ($user and ($dbUser->Username() == $user['Username'])) { # We are the logged in user, need to update the $user object and generate a new auth_hash $sql = 'SELECT * FROM Users WHERE Enabled=1 AND Id=?'; - $user = dbFetchOne($sql, NULL, array($_REQUEST['uid'])); + $user = dbFetchOne($sql, NULL, array($uid)); # Have to update auth hash in session zm_session_start(); generateAuthHash(ZM_AUTH_HASH_IPS, true); session_write_close(); } - } else { - dbQuery('INSERT INTO Users SET '.implode(', ', $changes)); } } # end if changes - } else if ( ZM_USER_SELF_EDIT and ( $_REQUEST['uid'] == $user['Id'] ) ) { - $uid = $user['Id']; - - $dbUser = dbFetchOne('SELECT Id, Password, Language FROM Users WHERE Id = ?', NULL, array($uid)); - - $types = array(); - $changes = getFormChanges($dbUser, $_REQUEST['newUser'], $types); - - if ( function_exists('password_hash') ) { - $pass_hash = '"'.password_hash($_REQUEST['newUser']['Password'], PASSWORD_BCRYPT).'"'; + } else if (ZM_USER_SELF_EDIT and ($uid == $user['Id'])) { + if (!empty($_REQUEST['newUser']['Password'])) { + $_REQUEST['newUser']['Password'] = password_hash($_REQUEST['newUser']['Password'], PASSWORD_BCRYPT); } else { - $pass_hash = ' PASSWORD('.dbEscape($_REQUEST['newUser']['Password']).') '; - ZM\Info ('Cannot use bcrypt as you are using PHP < 5.3'); + unset($_REQUEST['newUser']['Password']); } + $fields = array('Password'=>'', 'Language'=>'', 'HomeView'=>''); + ZM\Debug("changes: ".print_r(array_intersect_key($_REQUEST['newUser'], $fields),true)); + $changes = $dbUser->changes(array_intersect_key($_REQUEST['newUser'], $fields)); + ZM\Debug("changes: ".print_r($changes, true)); - if ( !empty($_REQUEST['newUser']['Password']) ) { - $changes['Password'] = 'Password = '.$pass_hash; - } else { - unset($changes['Password']); - } - if ( count($changes) ) { - dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id=?', array($uid)); + if (count($changes)) { + if (!$dbUser->save($changes)) { + $error_message = $dbUser->get_last_error(); + unset($_REQUEST['redirect']); + return; + } # We are the logged in user, need to update the $user object and generate a new auth_hash $sql = 'SELECT * FROM Users WHERE Enabled=1 AND Id=?'; diff --git a/web/index.php b/web/index.php index bf71b059c..9683e5b31 100644 --- a/web/index.php +++ b/web/index.php @@ -98,7 +98,7 @@ if ( isset($_GET['skin']) ) { $skin = 'classic'; } -if ( ! is_dir("skins/$skin") ) { +if (!is_dir('skins/'.$skin) ) { $skins = array_map('basename', glob('skins/*', GLOB_ONLYDIR)); if ( !in_array($skin, $skins) ) { @@ -117,10 +117,10 @@ if ( isset($_GET['css']) ) { $css = 'classic'; } -if ( !is_dir("skins/$skin/css/$css") ) { +if (!is_dir("skins/$skin/css/$css")) { $css_skins = array_map('basename', glob('skins/'.$skin.'/css/*', GLOB_ONLYDIR)); - if ( count($css_skins) ) { - if ( !in_array($css, $css_skins) ) { + if (count($css_skins)) { + if (!in_array($css, $css_skins)) { ZM\Error("Invalid skin css '$css' setting to " . $css_skins[0]); $css = $css_skins[0]; } else { @@ -137,7 +137,7 @@ define('ZM_SKIN_PATH', "skins/$skin"); define('ZM_SKIN_NAME', $skin); $skinBase = array(); // To allow for inheritance of skins -if ( !file_exists(ZM_SKIN_PATH) ) +if (!file_exists(ZM_SKIN_PATH)) ZM\Fatal("Invalid skin '$skin'"); $skinBase[] = $skin; @@ -183,9 +183,6 @@ $user = null; if ( isset($_REQUEST['view']) ) $view = detaintPath($_REQUEST['view']); -if ( isset($_REQUEST['redirect']) ) - $redirect = '?view='.detaintPath($_REQUEST['redirect']); - # Add CSP Headers $cspNonce = bin2hex(zm_random_bytes(16)); @@ -265,6 +262,8 @@ if ( ZM_OPT_USE_AUTH and (!isset($user)) and ($view != 'login') and ($view != 'n $request = null; } +if ( isset($_REQUEST['redirect']) ) + $redirect = '?view='.detaintPath($_REQUEST['redirect']); if ( $redirect ) { ZM\Debug("Redirecting to $redirect"); diff --git a/web/skins/classic/css/base/views/log.css b/web/skins/classic/css/base/views/log.css index 918c1097c..0c6f618c8 100644 --- a/web/skins/classic/css/base/views/log.css +++ b/web/skins/classic/css/base/views/log.css @@ -16,3 +16,14 @@ tr.log-dbg td { font-style: italic; } +th[data-field="DateTime"], +th[data-field="Component"], +th[data-field="Code"], +th[data-field="Message"], +th[data-field="File"], +th[data-field="Line"] { + text-align: left; +} +th[data-field="DateTime"] { + min-width: 135px; +} diff --git a/web/skins/classic/views/js/events.js b/web/skins/classic/views/js/events.js index 40adaba20..9f6d34f75 100644 --- a/web/skins/classic/views/js/events.js +++ b/web/skins/classic/views/js/events.js @@ -58,8 +58,8 @@ function processRows(rows) { var emailed = row.Emailed == yesString ? emailedString : ''; row.Id = '' + eid + ''; - row.Name = '' + row.Name + '' - + '
' + archived + emailed + '
'; + row.Name = '' + row.Name + '' + + '
' + archived + emailed + '
'; if ( canEdit.Monitors ) row.Monitor = '' + row.Monitor + ''; if ( canEdit.Events ) row.Cause = '' + row.Cause + ''; if ( row.Notes.indexOf('detected:') >= 0 ) { diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index c59e755ff..61c01a2f2 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -40,10 +40,8 @@ function validateForm(form) { var have_endtime_term = false; for ( var i = 0; i < rows.length; i++ ) { if ( - ( form.elements['filter[Query][terms][' + i + '][attr]'].value == 'EndDateTime' ) - || - ( form.elements['filter[Query][terms][' + i + '][attr]'].value == 'EndTime' ) - || + ( form.elements['filter[Query][terms][' + i + '][attr]'].value == 'EndDateTime' ) || + ( form.elements['filter[Query][terms][' + i + '][attr]'].value == 'EndTime' ) || ( form.elements['filter[Query][terms][' + i + '][attr]'].value == 'EndDate' ) ) { have_endtime_term = true; diff --git a/web/skins/classic/views/js/monitor.js b/web/skins/classic/views/js/monitor.js index 1603df783..854ba627b 100644 --- a/web/skins/classic/views/js/monitor.js +++ b/web/skins/classic/views/js/monitor.js @@ -48,6 +48,7 @@ function updateMonitorDimensions(element) { form.elements['newMonitor[Height]'].value = dimensions[1]; } } + update_estimated_ram_use(); return false; } diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 1a3c901d4..a1260effc 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -271,7 +271,9 @@ function reloadWebSite(ndx) { } function takeSnapshot() { - monitor_ids = monitorData.map( monitor=> { return 'monitor_ids[]='+monitor.id; }); + monitor_ids = monitorData.map((monitor)=>{ + return 'monitor_ids[]='+monitor.id; + }); window.location = '?view=snapshot&action=create&'+monitor_ids.join('&'); } diff --git a/web/skins/classic/views/js/snapshot.js b/web/skins/classic/views/js/snapshot.js index 9db096277..9ddefe0f7 100644 --- a/web/skins/classic/views/js/snapshot.js +++ b/web/skins/classic/views/js/snapshot.js @@ -27,7 +27,6 @@ function manageDelConfirmModalBtns() { } function initPage() { - // enable or disable buttons based on current selection and user rights /* renameBtn.prop('disabled', !canEdit.Events); diff --git a/web/skins/classic/views/js/snapshots.js b/web/skins/classic/views/js/snapshots.js index dff843374..8df31d5c3 100644 --- a/web/skins/classic/views/js/snapshots.js +++ b/web/skins/classic/views/js/snapshots.js @@ -48,7 +48,6 @@ function ajaxRequest(params) { function processRows(rows) { $j.each(rows, function(ndx, row) { - var id = row.Id; row.Id = '' + id + ''; row.Name = '' + row.Name + ''; @@ -192,7 +191,7 @@ function initPage() { window.location.reload(true); }); -/* + /* // Manage the ARCHIVE button document.getElementById("archiveBtn").addEventListener("click", function onArchiveClick(evt) { var selections = getIdSelections(); @@ -268,7 +267,7 @@ function initPage() { }) .fail(logAjaxFail); }); -*/ + */ // Manage the DELETE button document.getElementById("deleteBtn").addEventListener("click", function onDeleteClick(evt) { if ( ! canEdit.Events ) { diff --git a/web/skins/classic/views/js/zone.js b/web/skins/classic/views/js/zone.js index 67ff28d1d..6282cbf8a 100644 --- a/web/skins/classic/views/js/zone.js +++ b/web/skins/classic/views/js/zone.js @@ -202,7 +202,7 @@ function toPercent(field, maxValue) { field.value = 100; } } - field.setAttribute('step', 0.01); + field.setAttribute('step', 'any'); field.setAttribute('max', 100); } diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index a2402b8bc..fb7e2bde8 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -37,16 +37,14 @@ $mid = null; $monitor = null; if ( !empty($_REQUEST['mid']) ) { $mid = validInt($_REQUEST['mid']); - $monitor = new ZM\Monitor($mid); if ( $monitor and ZM_OPT_X10 ) $x10Monitor = dbFetchOne('SELECT * FROM TriggersX10 WHERE MonitorId = ?', NULL, array($mid)); } if ( !$monitor ) { - $nextId = getTableAutoInc('Monitors'); $monitor = new ZM\Monitor(); - $monitor->Name(translate('Monitor').'-'.$nextId); + $monitor->Name(translate('Monitor').'-'.getTableAutoInc('Monitors')); $monitor->WebColour(random_colour()); } # end if $_REQUEST['mid'] diff --git a/web/skins/classic/views/options.php b/web/skins/classic/views/options.php index 2be9e9b52..2745979b7 100644 --- a/web/skins/classic/views/options.php +++ b/web/skins/classic/views/options.php @@ -156,7 +156,6 @@ foreach ( array_map('basename', glob('skins/'.$skin.'/css/*', GLOB_ONLYDIR)) as $userMonitors[] = $monitors[$monitorId]['Name']; } } - ZM\Debug("monitors: ".$user_row['Username'] . ' ' . $user_row['MonitorIds']. ' :' . print_r($userMonitors, true)); ?> diff --git a/web/skins/classic/views/user.php b/web/skins/classic/views/user.php index 981b205e2..e14c2e1f2 100644 --- a/web/skins/classic/views/user.php +++ b/web/skins/classic/views/user.php @@ -25,7 +25,7 @@ if ( !canEdit('System') && !$selfEdit ) { return; } -require('includes/User.php'); +require_once('includes/User.php'); if ( $_REQUEST['uid'] ) { if ( !($newUser = new ZM\User($_REQUEST['uid'])) ) { @@ -54,9 +54,9 @@ foreach ( dbFetchAll($sql) as $monitor ) { $focusWindow = true; xhtmlHeaders(__FILE__, translate('User').' - '.$newUser->Username()); +echo getBodyTopHTML(); +echo getNavBarHTML(); ?> - -
diff --git a/web/skins/classic/views/zone.php b/web/skins/classic/views/zone.php index 2c41a885c..7a4761a50 100644 --- a/web/skins/classic/views/zone.php +++ b/web/skins/classic/views/zone.php @@ -206,7 +206,7 @@ if ( count($other_zones) ) { array('data-on-change'=>'applyZoneUnits', 'id'=>'newZone[Units]') ); # Used later for number inputs - $step = $newZone['Units'] == 'Percent' ? ' step="0.01" max="100"' : ''; + $step = $newZone['Units'] == 'Percent' ? ' step="any" max="100"' : ''; ?>