From ead1631fce7e823fc1929cfe7a36cbdfe5d6e855 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 2 Dec 2016 13:24:59 -0500 Subject: [PATCH 01/39] copy some filter code into the object --- src/zm_videostore.cpp | 162 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 1 deletion(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 152bb121c..959933dca 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -453,6 +453,10 @@ if ( audio_output_stream ) { // now - when streaming started //startTime=av_gettime()-nStartTime;//oc->start_time; //Info("VideoStore startTime=%d\n",startTime); + + if ( init_filters() < 0 ) { + Fatal("Could not init fileters"); + } } // VideoStore::VideoStore @@ -733,7 +737,7 @@ Debug(2, "Stream index is %d", opkt.stream_index ); #ifdef HAVE_LIBSWRESAMPLE // Need to re-encode -#if 0 +#if 1 ret = avcodec_send_packet( audio_input_context, ipkt ); if ( ret < 0 ) { Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); @@ -915,3 +919,159 @@ Debug(2, "opkt dts (%d) pts(%d) duration:(%d) pos(%d) ", opkt.dts, opkt.pts, opk zm_av_packet_unref(&opkt); return 0; } + +static int init_filter(FilteringContext* fctx, AVCodecContext *dec_ctx, + AVCodecContext *enc_ctx, const char *filter_spec) +{ + char args[512]; + int ret = 0; + AVFilter *buffersrc = NULL; + AVFilter *buffersink = NULL; + AVFilterContext *buffersrc_ctx = NULL; + AVFilterContext *buffersink_ctx = NULL; + AVFilterInOut *outputs = avfilter_inout_alloc(); + AVFilterInOut *inputs = avfilter_inout_alloc(); + AVFilterGraph *filter_graph = avfilter_graph_alloc(); + if (!outputs || !inputs || !filter_graph) { + ret = AVERROR(ENOMEM); + goto end; + } + if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { + buffersrc = avfilter_get_by_name("buffer"); + buffersink = avfilter_get_by_name("buffersink"); + if (!buffersrc || !buffersink) { + av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n"); + ret = AVERROR_UNKNOWN; + goto end; + } + snprintf(args, sizeof(args), + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, + dec_ctx->time_base.num, dec_ctx->time_base.den, + dec_ctx->sample_aspect_ratio.num, + dec_ctx->sample_aspect_ratio.den); + ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", + args, NULL, filter_graph); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n"); + goto end; + } + ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", + NULL, NULL, filter_graph); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n"); + goto end; + } + ret = av_opt_set_bin(buffersink_ctx, "pix_fmts", + (uint8_t*)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt), + AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n"); + goto end; + } + } else if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) { + buffersrc = avfilter_get_by_name("abuffer"); + buffersink = avfilter_get_by_name("abuffersink"); + if (!buffersrc || !buffersink) { + av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n"); + ret = AVERROR_UNKNOWN; + goto end; + } + if (!dec_ctx->channel_layout) + dec_ctx->channel_layout = + av_get_default_channel_layout(dec_ctx->channels); + snprintf(args, sizeof(args), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, + dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate, + av_get_sample_fmt_name(dec_ctx->sample_fmt), + dec_ctx->channel_layout); + ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", + args, NULL, filter_graph); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n"); + goto end; + } + ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", + NULL, NULL, filter_graph); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n"); + goto end; + } + ret = av_opt_set_bin(buffersink_ctx, "sample_fmts", + (uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt), + AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n"); + goto end; + } + ret = av_opt_set_bin(buffersink_ctx, "channel_layouts", + (uint8_t*)&enc_ctx->channel_layout, + sizeof(enc_ctx->channel_layout), AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n"); + goto end; + } + ret = av_opt_set_bin(buffersink_ctx, "sample_rates", + (uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate), + AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n"); + goto end; + } + } else { + ret = AVERROR_UNKNOWN; + goto end; + } + /* Endpoints for the filter graph. */ + outputs->name = av_strdup("in"); + outputs->filter_ctx = buffersrc_ctx; + outputs->pad_idx = 0; + outputs->next = NULL; + inputs->name = av_strdup("out"); + inputs->filter_ctx = buffersink_ctx; + inputs->pad_idx = 0; + inputs->next = NULL; + if (!outputs->name || !inputs->name) { + ret = AVERROR(ENOMEM); + goto end; + } + if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_spec, + &inputs, &outputs, NULL)) < 0) + goto end; + if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) + goto end; + /* Fill FilteringContext */ + fctx->buffersrc_ctx = buffersrc_ctx; + fctx->buffersink_ctx = buffersink_ctx; + fctx->filter_graph = filter_graph; +end: + avfilter_inout_free(&inputs); + avfilter_inout_free(&outputs); + return ret; +} +static int init_filters(void) +{ + const char *filter_spec; + unsigned int i; + int ret; + filter_ctx = av_malloc_array(ifmt_ctx->nb_streams, sizeof(*filter_ctx)); + if (!filter_ctx) + return AVERROR(ENOMEM); + for (i = 0; i < ifmt_ctx->nb_streams; i++) { + filter_ctx[i].buffersrc_ctx = NULL; + filter_ctx[i].buffersink_ctx = NULL; + filter_ctx[i].filter_graph = NULL; + if (!(ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO + || ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)) + continue; + if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) + filter_spec = "null"; /* passthrough (dummy) filter for video */ + else + filter_spec = "anull"; /* passthrough (dummy) filter for audio */ + ret = init_filter(&filter_ctx[i], ifmt_ctx->streams[i]->codec, + ofmt_ctx->streams[i]->codec, filter_spec); + if (ret) + return ret; + } + return 0; +} From 9748b9346e8d34e7057c16fc25f59534badb9aa6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 8 Jan 2017 16:52:16 -0500 Subject: [PATCH 02/39] bump required version to 3.1 so that we can use a simple statement to turn on c++11 features --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index baba4218d..a70ebe8e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,12 @@ # Created by mastertheknife (Kfir Itzhak) # For more information and installation, see the INSTALL file # -cmake_minimum_required (VERSION 2.8.7) +cmake_minimum_required (VERSION 3.1.0) project (zoneminder) file (STRINGS "version" zoneminder_VERSION) # make API version a minor of ZM version set(zoneminder_API_VERSION "${zoneminder_VERSION}.1") +set (CMAKE_CXX_STANDARD 11) # Make sure the submodules are there if( NOT EXISTS "${CMAKE_SOURCE_DIR}/web/api/app/Plugin/Crud/Lib/CrudControllerTrait.php" ) From 8f3e40d7af19c78676f72c304cb4b8e7366a7e9a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 8 Jan 2017 16:53:15 -0500 Subject: [PATCH 03/39] add a const array of char * strings for the frame types so that we can make better use of the FrameType enum for more efficient code --- src/zm_event.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zm_event.h b/src/zm_event.h index 901e5dc52..37cb69c64 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -64,7 +64,8 @@ public: typedef std::map StringSetMap; protected: - typedef enum { NORMAL, BULK, ALARM } FrameType; + typedef enum { NORMAL=0, BULK, ALARM } FrameType; + static const constexpr char * const frame_type_names[] = { "Normal", "Bulk", "Alarm" }; struct PreAlarmData { From 8ae02b9ac000dbf99948706c2d699224a4f516ee Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 8 Jan 2017 16:53:29 -0500 Subject: [PATCH 04/39] use a FrameType enum instead of string comparisons --- src/zm_event.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 9b6202966..7902f5036 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -566,17 +566,17 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * struct DeltaTimeval delta_time; DELTA_TIMEVAL( delta_time, timestamp, start_time, DT_PREC_2 ); - const char *frame_type = score>0?"Alarm":(score<0?"Bulk":"Normal"); + FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL); if ( score < 0 ) score = 0; - bool db_frame = (strcmp(frame_type,"Bulk") != 0) || ((frames%config.bulk_frame_interval)==0) || !frames; + bool db_frame = ( frame_type != BULK ) || ((frames%config.bulk_frame_interval)==0) || !frames; if ( db_frame ) { - Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, frame_type ); + Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, frame_type_names[frame_type] ); static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) values ( %d, %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", id, frames, frame_type, timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score ); + snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) values ( %d, %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", id, frames, frame_type_names[frame_type], timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score ); if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frame: %s", mysql_error( &dbconn ) ); @@ -585,7 +585,7 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * last_db_frame = frames; // We are writing a Bulk frame - if ( !strcmp( frame_type,"Bulk") ) + if ( frame_type == BULK ) { snprintf( sql, sizeof(sql), "update Events set Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id ); if ( mysql_query( &dbconn, sql ) ) @@ -598,8 +598,8 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * end_time = timestamp; - // We are writing an Alarm frame - if ( !strcmp( frame_type,"Alarm") ) + // We are writing an Alarm frame + if ( frame_type == ALARM ) { alarm_frames++; From f19b3d5505fed5f659872c5f6afc68f25b1e661f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 18 Feb 2017 15:22:56 -0500 Subject: [PATCH 05/39] create zm_packet --- src/CMakeLists.txt | 2 +- src/zm_ffmpeg_camera.cpp | 46 +++++++++++++++++++++++++++++----------- src/zm_monitor.cpp | 6 ++++-- src/zm_packet.cpp | 44 ++++++++++++++++++++++++++++++++++++++ src/zm_packet.h | 39 ++++++++++++++++++++++++++++++++++ src/zm_packetqueue.cpp | 27 +++++++++++------------ src/zm_packetqueue.h | 14 ++++++------ src/zm_videostore.cpp | 11 ++++++++-- 8 files changed, 150 insertions(+), 39 deletions(-) create mode 100644 src/zm_packet.cpp create mode 100644 src/zm_packet.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e33f0f0bb..a2e9de9cd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY) # Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc) -set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp) +set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp) # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 799d59b70..07efff42f 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -63,6 +63,7 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri mOpenStart = 0; mReopenThread = 0; videoStore = NULL; + video_last_pts = 0; #if HAVE_LIBSWSCALE mConvertContext = NULL; @@ -645,24 +646,24 @@ Debug(5, "After av_read_frame (%d)", ret ); // Need to write out all the frames from the last keyframe? unsigned int packet_count = 0; - AVPacket *queued_packet; + ZMPacket *queued_packet; while ( ( queued_packet = packetqueue.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)", queued_packet->stream_index, queued_packet->flags & AV_PKT_FLAG_KEY, packetqueue.size() ); - if ( queued_packet->stream_index == mVideoStreamId ) { - ret = videoStore->writeVideoFramePacket( queued_packet ); - } else if ( queued_packet->stream_index == mAudioStreamId ) { - ret = videoStore->writeAudioFramePacket( queued_packet ); + Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue.size() ); + if ( avp->stream_index == mVideoStreamId ) { + ret = videoStore->writeVideoFramePacket( avp ); + } else if ( avp->stream_index == mAudioStreamId ) { + ret = videoStore->writeAudioFramePacket( avp ); } else { - Warning("Unknown stream id in queued packet (%d)", queued_packet->stream_index ); + Warning("Unknown stream id in queued packet (%d)", avp->stream_index ); ret = -1; } if ( ret < 0 ) { //Less than zero and we skipped a frame } - zm_av_packet_unref( queued_packet ); - av_free( queued_packet ); + delete queued_packet; } // end while packets in the packetqueue Debug(2, "Wrote %d queued packets", packet_count ); } // end if ! wasRecording @@ -682,7 +683,7 @@ Debug(5, "After av_read_frame (%d)", ret ); packetqueue.clearQueue(); } if ( packet.pts && video_last_pts > packet.pts ) { - Warning( "Clearing queue due to out of order pts"); + Warning( "Clearing queue due to out of order pts packet.pts=%d video_last_pts=%d", packet.pts, video_last_pts ); packetqueue.clearQueue(); } } @@ -706,6 +707,24 @@ Debug(5, "After av_read_frame (%d)", ret ); } } Debug(4, "about to decode video" ); + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + ret = avcodec_send_packet( mVideoCodecContext, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + ret = avcodec_receive_frame( mVideoCodecContext, mRawFrame ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + frameComplete = 1; +# else ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet ); if ( ret < 0 ) { av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); @@ -713,6 +732,7 @@ Debug(5, "After av_read_frame (%d)", ret ); zm_av_packet_unref( &packet ); continue; } +#endif Debug( 4, "Decoded video packet at frame %d", frameCount ); @@ -728,7 +748,9 @@ Debug(5, "After av_read_frame (%d)", ret ); zm_av_packet_unref( &packet ); return (-1); } - avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height); +// avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height); + av_image_fill_arrays(mFrame->data, mFrame->linesize, directbuffer, imagePixFormat, width, height, 1); + if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) { @@ -758,7 +780,7 @@ Debug(5, "After av_read_frame (%d)", ret ); } } else { #if LIBAVUTIL_VERSION_CHECK(54, 23, 0, 23, 0) - Debug( 3, "Some other stream index %d, %s", packet.stream_index, av_get_media_type_string( mFormatContext->streams[packet.stream_index]->codec->codec_type) ); + Debug( 3, "Some other stream index %d, %s", packet.stream_index, av_get_media_type_string( mFormatContext->streams[packet.stream_index]->codecpar->codec_type) ); #else Debug( 3, "Some other stream index %d", packet.stream_index ); #endif diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 3decdf69f..39efb712d 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1357,6 +1357,7 @@ bool Monitor::Analyse() { if ( event ) { //TODO: We shouldn't have to do this every time. Not sure why it clears itself if this isn't here?? snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + Debug( 3, "Detected new event at (%d.%d)", timestamp->tv_sec,timestamp->tv_usec ); if ( section_length ) { int section_mod = timestamp->tv_sec%section_length; @@ -3029,7 +3030,7 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z ref_image.WriteJpeg( diag_path ); } - ref_image.Delta( comp_image, &delta_image); + ref_image.Delta( comp_image, &delta_image ); if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; @@ -3117,6 +3118,7 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z if ( alarm ) { for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; + // Wasn't this zone already checked above? if ( !zone->IsInclusive() ) { continue; } @@ -3151,7 +3153,7 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z zoneSet.insert( zone->Label() ); } } - } // end if alaram or not + } // end if alarm or not } if ( top_score > 0 ) { diff --git a/src/zm_packet.cpp b/src/zm_packet.cpp new file mode 100644 index 000000000..8fbb65cb8 --- /dev/null +++ b/src/zm_packet.cpp @@ -0,0 +1,44 @@ +//ZoneMinder Packet Implementation Class +//Copyright 2017 ZoneMinder LLC +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#include "zm_packet.h" +#include "zm_ffmpeg.h" + +using namespace std; + +ZMPacket::ZMPacket( AVPacket *p ) { + av_init_packet( &packet ); + if ( zm_av_packet_ref( &packet, p ) < 0 ) { + Error("error refing packet"); + } + gettimeofday( ×tamp, NULL ); +} + +ZMPacket::ZMPacket( AVPacket *p, struct timeval *t ) { + av_init_packet( &packet ); + if ( zm_av_packet_ref( &packet, p ) < 0 ) { + Error("error refing packet"); + } + timestamp = *t; +} + +ZMPacket::~ZMPacket() { + zm_av_packet_unref( &packet ); +} + diff --git a/src/zm_packet.h b/src/zm_packet.h new file mode 100644 index 000000000..9fd7ed8ee --- /dev/null +++ b/src/zm_packet.h @@ -0,0 +1,39 @@ +//ZoneMinder Packet Wrapper Class +//Copyright 2017 ZoneMinder LLC +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#ifndef ZM_PACKET_H +#define ZM_PACKET_H + +extern "C" { +#include +} + +class ZMPacket { + public: + + AVPacket packet; + struct timeval timestamp; + public: + AVPacket *av_packet() { return &packet; } + ZMPacket( AVPacket *packet, struct timeval *timestamp ); + ZMPacket( AVPacket *packet ); + ~ZMPacket(); +}; + +#endif /* ZM_PACKET_H */ diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 207053e6e..c38f74c52 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -33,41 +33,38 @@ zm_packetqueue::~zm_packetqueue() { } -bool zm_packetqueue::queuePacket( AVPacket* packet ) { +bool zm_packetqueue::queuePacket( ZMPacket* zm_packet ) { + pktQueue.push( zm_packet ); + + return true; +} +bool zm_packetqueue::queuePacket( AVPacket* av_packet ) { - AVPacket *input_ref = (AVPacket *)av_malloc(sizeof(AVPacket)); - av_init_packet( input_ref ); - if ( zm_av_packet_ref( input_ref, packet ) < 0 ) { - Error("error refing packet"); - av_free(input_ref); - return false; - } + ZMPacket *zm_packet = new ZMPacket( av_packet ); - pktQueue.push( input_ref ); + pktQueue.push( zm_packet ); return true; } -AVPacket* zm_packetqueue::popPacket( ) { +ZMPacket* zm_packetqueue::popPacket( ) { if ( pktQueue.empty() ) { return NULL; } - AVPacket *packet = pktQueue.front(); + ZMPacket *packet = pktQueue.front(); pktQueue.pop(); return packet; } void zm_packetqueue::clearQueue() { - AVPacket *packet = NULL; + ZMPacket *packet = NULL; while(!pktQueue.empty()) { packet = pktQueue.front(); pktQueue.pop(); - // If we clear it, then no freeing gets done, whereas when we pop off, we assume that the packet was freed somewhere else. - zm_av_packet_unref( packet ); - av_free( packet ); + delete packet; } } diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index 60a9620a4..730487312 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -24,6 +24,7 @@ //#include //#include #include +#include "zm_packet.h" extern "C" { #include @@ -33,18 +34,17 @@ class zm_packetqueue { public: zm_packetqueue(); virtual ~zm_packetqueue(); + bool queuePacket( AVPacket* packet, struct timeval *timestamp ); + bool queuePacket( ZMPacket* packet ); bool queuePacket( AVPacket* packet ); - AVPacket * popPacket( ); - bool popVideoPacket(AVPacket* packet); - bool popAudioPacket(AVPacket* packet); + ZMPacket * popPacket( ); + bool popVideoPacket(ZMPacket* packet); + bool popAudioPacket(ZMPacket* packet); void clearQueue( ); unsigned int size(); private: - std::queue pktQueue; + std::queue pktQueue; }; - - #endif /* ZM_PACKETQUEUE_H */ - diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 586b9165a..b33900c76 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -470,7 +470,11 @@ Debug(1, "Have audio encoder, need to flush it's output" ); int64_t size; while(1) { +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + ret = avcodec_receive_packet( audio_output_context, &pkt ); +#else ret = avcodec_encode_audio2( audio_output_context, &pkt, NULL, &got_packet ); +#endif if (ret < 0) { Error("ERror encoding audio while flushing"); break; @@ -871,8 +875,11 @@ av_frame_get_best_effort_timestamp(output_frame) * Encode the audio frame and store it in the temporary packet. * The output audio stream encoder is used to do this. */ - if (( ret = avcodec_encode_audio2( audio_output_context, &opkt, - output_frame, &data_present )) < 0) { +#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0) + if (( ret = avcodec_receive_packet( audio_output_context, &opkt )) < 0 ) { +#else + if (( ret = avcodec_encode_audio2( audio_output_context, &opkt, output_frame, &data_present )) < 0) { +#endif Error( "Could not encode frame (error '%s')", av_make_error_string(ret).c_str()); zm_av_packet_unref(&opkt); From 6ee254977731c6019d56dc42a3a3cfb330fe2f51 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 16 Mar 2017 10:50:31 -0400 Subject: [PATCH 06/39] Fix zmc crashing when zones are no good --- src/zm_zone.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index 5d5ae41cd..d781270ce 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -995,12 +995,14 @@ int Zone::Load( Monitor *monitor, Zone **&zones ) Polygon polygon; if ( !ParsePolygonString( Coords, polygon ) ) { Error( "Unable to parse polygon string '%s' for zone %d/%s for monitor %s, ignoring", Coords, Id, Name, monitor->Name() ); + n_zones -= 1; continue; } if ( polygon.LoX() < 0 || polygon.HiX() >= (int)monitor->Width() || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { Error( "Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), ignoring", Id, Name, monitor->Name(), polygon.LoX(), polygon.LoY(), polygon.HiX(), polygon.HiY() ); + n_zones -= 1; continue; } From b1d5fbef0bcf98f6a38447b194a7cb83f94916e6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 20 Mar 2017 13:19:50 -0400 Subject: [PATCH 07/39] revert an unintended change --- distros/ubuntu1604/source/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/ubuntu1604/source/format b/distros/ubuntu1604/source/format index d3827e75a..89ae9db8f 100644 --- a/distros/ubuntu1604/source/format +++ b/distros/ubuntu1604/source/format @@ -1 +1 @@ -1.0 +3.0 (native) From c2461f93cd6e4173656444fbd35e40bf0947b1e8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 20 Mar 2017 16:47:58 -0400 Subject: [PATCH 08/39] google code style and a better debug message --- scripts/zmfilter.pl.in | 1531 ++++++++++++++++++++-------------------- 1 file changed, 750 insertions(+), 781 deletions(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index cbff891ee..245faf23c 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -27,7 +27,7 @@ zmfilter.pl - ZoneMinder tool to filter events =head1 SYNOPSIS - zmfilter.pl [-f ,--filter=] [--filter_id=] | -v, --version +zmfilter.pl [-f ,--filter=] [--filter_id=] | -v, --version =head1 DESCRIPTION @@ -37,10 +37,10 @@ matching events. =head1 OPTIONS - - -f{filter name}, --filter={filter name} - The name of a specific filter to run - --filter_id={filter id} - The id of a specific filter to run - -v, --version - Print ZoneMinder version + +-f{filter name}, --filter={filter name} - The name of a specific filter to run +--filter_id={filter id} - The id of a specific filter to run +-v, --version - Print ZoneMinder version =cut use strict; @@ -78,71 +78,56 @@ GetOptions( 'filter=s' =>\$filter_name, 'filter_id=s' =>\$filter_id, 'version' =>\$version -) or pod2usage(-exitstatus => -1); + ) or pod2usage(-exitstatus => -1); if ( $version ) { - print ZoneMinder::Base::ZM_VERSION . "\n"; - exit(0); + print ZoneMinder::Base::ZM_VERSION . "\n"; + exit(0); } require ZoneMinder::Event; require ZoneMinder::Filter; use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) - ? $Config{ZM_DIR_EVENTS} - : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) -; + ? $Config{ZM_DIR_EVENTS} + : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) + ; -logInit(); -logSetSignal(); + logInit(); + logSetSignal(); -if ( $Config{ZM_OPT_UPLOAD} ) -{ - # Comment these out if you don't have them and don't want to upload - # or don't want to use that format - if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) - { - require Archive::Zip; - import Archive::Zip qw( :ERROR_CODES :CONSTANTS ); - } - else - { - require Archive::Tar; - } - if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) - { - require Net::FTP; - } - else - { - require Net::SFTP::Foreign; - } +if ( $Config{ZM_OPT_UPLOAD} ) { +# Comment these out if you don't have them and don't want to upload +# or don't want to use that format + if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) { + require Archive::Zip; + import Archive::Zip qw( :ERROR_CODES :CONSTANTS ); + } else { + require Archive::Tar; + } + if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) { + require Net::FTP; + } else { + require Net::SFTP::Foreign; + } } -if ( $Config{ZM_OPT_EMAIL} ) -{ - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - require MIME::Lite; - require Net::SMTP; - } - else - { - require MIME::Entity; - } +if ( $Config{ZM_OPT_EMAIL} ) { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { + require MIME::Lite; + require Net::SMTP; + } else { + require MIME::Entity; + } } -if ( $Config{ZM_OPT_MESSAGE} ) -{ - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - require MIME::Lite; - require Net::SMTP; - } - else - { - require MIME::Entity; - } +if ( $Config{ZM_OPT_MESSAGE} ) { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { + require MIME::Lite; + require Net::SMTP; + } else { + require MIME::Entity; + } } $| = 1; @@ -155,8 +140,8 @@ my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; if ( ! EVENT_PATH ) { - Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); - die; + Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); + die; } chdir( EVENT_PATH ); @@ -164,830 +149,814 @@ chdir( EVENT_PATH ); my $dbh = zmDbConnect(); if ( $filter_name ) { - Info( "Scanning for events using filter '$filter_name'\n" ); + Info( "Scanning for events using filter '$filter_name'\n" ); } elsif ( $filter_id ) { - Info( "Scanning for events using filter id '$filter_id'\n" ); + Info( "Scanning for events using filter id '$filter_id'\n" ); } else { - Info( "Scanning for events\n" ); + Info( "Scanning for events using all filters\n" ); } if ( ! ( $filter_name or $filter_id ) ) { - sleep( START_DELAY ); + sleep( START_DELAY ); } my $filters; my $last_action = 0; while( 1 ) { - my $now = time; - if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { - Debug( "Reloading filters\n" ); - $last_action = $now; - $filters = getFilters( { Name=>$filter_name, Id=>$filter_id } ); + my $now = time; + if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { + Debug( "Reloading filters\n" ); + $last_action = $now; + $filters = getFilters( { Name=>$filter_name, Id=>$filter_id } ); + } + + foreach my $filter ( @$filters ) { + if ( $$filter{Concurrent} and ! ( $filter_id or $filter_name ) ) { + my ( $proc ) = $0 =~ /(\S+)/; + my ( $id ) = $$filter{Id} =~ /(\d+)/; + Debug("Running concurrent filter process $proc --filter_id $$filter{Id} => $id for $$filter{Name}"); + + system( qq`$proc --filter "$$filter{Name}" &` ); + } else { + checkFilter( $filter ); } + } - foreach my $filter ( @$filters ) { - if ( $$filter{Concurrent} and ! ( $filter_id or $filter_name ) ) { - my ( $proc ) = $0 =~ /(\S+)/; - my ( $id ) = $$filter{Id} =~ /(\d+)/; - Debug("Running concurrent filter process $proc --filter_id $$filter{Id} => $id for $$filter{Name}"); + last if ( $filter_name or $filter_id ); - system( qq`$proc --filter "$$filter{Name}" &` ); - } else { - checkFilter( $filter ); - } - } - - last if ( $filter_name or $filter_id ); - - Debug( "Sleeping for $delay seconds\n" ); - sleep( $delay ); + Debug( "Sleeping for $delay seconds\n" ); + sleep( $delay ); } -sub getFilters -{ - my $sql_filters = @_ ? shift : {}; - my @sql_values; +sub getFilters { + my $sql_filters = @_ ? shift : {}; + my @sql_values; - my @filters; - my $sql = "SELECT * FROM Filters WHERE"; - if ( $$sql_filters{Name} ) { - $sql .= " Name = ? and"; - push @sql_values, $$sql_filters{Name}; - } elsif ( $$sql_filters{Id} ) { - $sql .= " Id = ? and"; - push @sql_values, $$sql_filters{Id}; - } else { - $sql .= " Background = 1 and"; + my @filters; + my $sql = "SELECT * FROM Filters WHERE"; + if ( $$sql_filters{Name} ) { + $sql .= " Name = ? and"; + push @sql_values, $$sql_filters{Name}; + } elsif ( $$sql_filters{Id} ) { + $sql .= " Id = ? and"; + push @sql_values, $$sql_filters{Id}; + } else { + $sql .= " Background = 1 and"; + } + $sql .= "( AutoArchive = 1 + or AutoVideo = 1 + or AutoUpload = 1 + or AutoEmail = 1 + or AutoMessage = 1 + or AutoExecute = 1 + or AutoDelete = 1 + ) ORDER BY Name"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( @sql_values ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); +FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); + Debug( "Found filter '$db_filter->{Name}'\n" ); + my $sql = $filter->Sql(); + + if ( ! $sql ) { + Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); + next FILTER; } - $sql .= "( AutoArchive = 1 - or AutoVideo = 1 - or AutoUpload = 1 - or AutoEmail = 1 - or AutoMessage = 1 - or AutoExecute = 1 - or AutoDelete = 1 - ) ORDER BY Name"; - my $sth = $dbh->prepare_cached( $sql ) + push( @filters, $filter ); + } + $sth->finish(); + if ( ! @filters ) { + Warning("No filter found for $sql with values(@sql_values)"); + } else { + Debug( "Got " . @filters . " filters" ); + } + + return( \@filters ); +} + +sub checkFilter { + my $filter = shift; + + my @Events = $filter->Execute(); + Info( join( "Checking filter '$filter->{Name}'", + ($filter->{AutoDelete}?", delete":""), + ($filter->{AutoArchive}?", archive":""), + ($filter->{AutoVideo}?", video":""), + ($filter->{AutoUpload}?", upload":""), + ($filter->{AutoEmail}?", email":""), + ($filter->{AutoMessage}?", message":""), + ($filter->{AutoExecute}?", execute":""), + " returned " , scalar @Events , ' events', + "\n", + ) ); + + foreach my $event ( @Events ) { + Debug( "Checking event $event->{Id}\n" ); + my $delete_ok = !undef; + $dbh->ping(); + if ( $filter->{AutoArchive} ) { + Info( "Archiving event $event->{Id}\n" ); +# Do it individually to avoid locking up the table for new events + my $sql = "update Events set Archived = 1 where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( @sql_values ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) - { - my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); - Debug( "Found filter '$db_filter->{Name}'\n" ); - my $sql = $filter->Sql(); - - if ( ! $sql ) { - Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); - next FILTER; - } - push( @filters, $filter ); + my $res = $sth->execute( $event->{Id} ) + or Error( "Can't execute '$sql': ".$sth->errstr() ); } - $sth->finish(); - if ( ! @filters ) { - Warning("No filter found for $sql with values(@sql_values)"); - } else { - Debug( "Got " . @filters . " filters" ); + if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { + if ( !$event->{Videoed} ) { + $delete_ok = undef if ( !generateVideo( $filter, $event ) ); + } } - - return( \@filters ); -} - -sub checkFilter -{ - my $filter = shift; - - Debug( "Checking filter '$filter->{Name}'". - ($filter->{AutoDelete}?", delete":""). - ($filter->{AutoArchive}?", archive":""). - ($filter->{AutoVideo}?", video":""). - ($filter->{AutoUpload}?", upload":""). - ($filter->{AutoEmail}?", email":""). - ($filter->{AutoMessage}?", message":""). - ($filter->{AutoExecute}?", execute":""). - "\n" - ); - - foreach my $event ( $filter->Execute() ) { - Debug( "Checking event $event->{Id}\n" ); - my $delete_ok = !undef; -$dbh->ping(); - if ( $filter->{AutoArchive} ) - { - Info( "Archiving event $event->{Id}\n" ); - # Do it individually to avoid locking up the table for new events - my $sql = "update Events set Archived = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Error( "Can't execute '$sql': ".$sth->errstr() ); - } - if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) - { - if ( !$event->{Videoed} ) - { - $delete_ok = undef if ( !generateVideo( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) - { - if ( !$event->{Emailed} ) - { - $delete_ok = undef if ( !sendEmail( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) - { - if ( !$event->{Messaged} ) - { - $delete_ok = undef if ( !sendMessage( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) - { - if ( !$event->{Uploaded} ) - { - $delete_ok = undef if ( !uploadArchFile( $filter, $event ) ); - } - } - if ( $filter->{AutoExecute} ) - { - if ( !$event->{Executed} ) - { - $delete_ok = undef if ( !executeCommand( $filter, $event ) ); - } - } - if ( $filter->{AutoDelete} ) - { - if ( $delete_ok ) - { - my $Event = new ZoneMinder::Event( $$event{Id}, $event ); - $Event->delete(); - } - else - { - Error( "Unable to delete event $event->{Id} as previous operations failed\n" ); - } - } + if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) { + if ( !$event->{Emailed} ) { + $delete_ok = undef if ( !sendEmail( $filter, $event ) ); + } } + if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) { + if ( !$event->{Messaged} ) { + $delete_ok = undef if ( !sendMessage( $filter, $event ) ); + } + } + if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) { + if ( !$event->{Uploaded} ) { + $delete_ok = undef if ( !uploadArchFile( $filter, $event ) ); + } + } + if ( $filter->{AutoExecute} ) { + if ( !$event->{Executed} ) { + $delete_ok = undef if ( !executeCommand( $filter, $event ) ); + } + } + if ( $filter->{AutoDelete} ) { + if ( $delete_ok ) { + my $Event = new ZoneMinder::Event( $$event{Id}, $event ); + $Event->delete(); + } else { + Error( "Unable to delete event $event->{Id} as previous operations failed\n" ); + } + } # end if AutoDelete + } # end foreach event } sub generateVideo { - my $filter = shift; - my $event = shift; - my $phone = shift; + my $filter = shift; + my $event = shift; + my $phone = shift; - my $rate = $event->{DefaultRate}/100; - my $scale = $event->{DefaultScale}/100; - my $format; + my $rate = $event->{DefaultRate}/100; + my $scale = $event->{DefaultScale}/100; + my $format; - my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); - my $default_video_format; - my $default_phone_format; - foreach my $ffmpeg_format( @ffmpeg_formats ) + my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); + my $default_video_format; + my $default_phone_format; + foreach my $ffmpeg_format( @ffmpeg_formats ) + { + if ( $ffmpeg_format =~ /^(.+)\*\*$/ ) { - if ( $ffmpeg_format =~ /^(.+)\*\*$/ ) - { - $default_phone_format = $1; - } - elsif ( $ffmpeg_format =~ /^(.+)\*$/ ) - { - $default_video_format = $1; - } + $default_phone_format = $1; } + elsif ( $ffmpeg_format =~ /^(.+)\*$/ ) + { + $default_video_format = $1; + } + } - if ( $phone && $default_phone_format ) - { - $format = $default_phone_format; - } - elsif ( $default_video_format ) - { - $format = $default_video_format; - } - else - { - $format = $ffmpeg_formats[0]; - } + if ( $phone && $default_phone_format ) + { + $format = $default_phone_format; + } + elsif ( $default_video_format ) + { + $format = $default_video_format; + } + else + { + $format = $ffmpeg_formats[0]; + } - my $command = $Config{ZM_PATH_BIN}."/zmvideo.pl -e " - .$event->{Id}." -r ".$rate." -s ".$scale." -f ".$format; - my $output = qx($command); - chomp( $output ); - my $status = $? >> 8; - if ( $status || logDebugging() ) + my $command = $Config{ZM_PATH_BIN}."/zmvideo.pl -e " + .$event->{Id}." -r ".$rate." -s ".$scale." -f ".$format; + my $output = qx($command); + chomp( $output ); + my $status = $? >> 8; + if ( $status || logDebugging() ) + { + Debug( "Output: $output\n" ); + } + if ( $status ) + { + Error( "Video generation '$command' failed with status: $status\n" ); + if ( wantarray() ) { - Debug( "Output: $output\n" ); + return( undef, undef ); } - if ( $status ) + return( 0 ); + } + else + { + my $sql = "update Events set Videoed = 1 where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + if ( wantarray() ) { - Error( "Video generation '$command' failed with status: $status\n" ); - if ( wantarray() ) - { - return( undef, undef ); - } - return( 0 ); + return( $format, $output ); } - else - { - my $sql = "update Events set Videoed = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - if ( wantarray() ) - { - return( $format, $output ); - } - } - return( 1 ); + } + return( 1 ); } sub uploadArchFile { - my $filter = shift; - my $event = shift; + my $filter = shift; + my $event = shift; - if ( ! $Config{ZM_UPLOAD_HOST} ) - { - Error( "Cannot upload archive as no upload host defined" ); - return( 0 ); - } + if ( ! $Config{ZM_UPLOAD_HOST} ) + { + Error( "Cannot upload archive as no upload host defined" ); + return( 0 ); + } - my $archFile = $event->{MonitorName}.'-'.$event->{Id}; - my $archImagePath = getEventPath( $event ) - ."/" - .( - ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) - ? '{*analyse,*capture}' - : '*capture' - ) - .".jpg" + my $archFile = $event->{MonitorName}.'-'.$event->{Id}; + my $archImagePath = getEventPath( $event ) + ."/" + .( + ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) + ? '{*analyse,*capture}' + : '*capture' + ) + .".jpg" ; - my @archImageFiles = glob($archImagePath); - my $archLocPath; + my @archImageFiles = glob($archImagePath); + my $archLocPath; - my $archError = 0; - if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) + my $archError = 0; + if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) + { + $archFile .= '.zip'; + $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; + my $zip = Archive::Zip->new(); + Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + + my $status = &AZ_OK; + foreach my $imageFile ( @archImageFiles ) { - $archFile .= '.zip'; - $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; - my $zip = Archive::Zip->new(); - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); - - my $status = &AZ_OK; - foreach my $imageFile ( @archImageFiles ) - { - Debug( "Adding $imageFile\n" ); - my $member = $zip->addFile( $imageFile ); - if ( !$member ) - { - Error( "Unable to add image file $imageFile to zip archive $archLocPath" ); - $archError = 1; - last; - } - $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS} - ? &COMPRESSION_DEFLATED - : &COMPRESSION_STORED - ); - } - if ( !$archError ) - { - $status = $zip->writeToFileNamed( $archLocPath ); - - if ( $archError = ($status != &AZ_OK) ) - { - Error( "Zip error: $status\n " ); - } - } - else - { - Error( "Error adding images to zip archive $archLocPath, not writing" ); - } + Debug( "Adding $imageFile\n" ); + my $member = $zip->addFile( $imageFile ); + if ( !$member ) + { + Error( "Unable to add image file $imageFile to zip archive $archLocPath" ); + $archError = 1; + last; + } + $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS} + ? &COMPRESSION_DEFLATED + : &COMPRESSION_STORED + ); } - elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "tar" ) + if ( !$archError ) { - if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) - { - $archFile .= '.tar.gz'; - } - else - { - $archFile .= '.tar'; - } - $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + $status = $zip->writeToFileNamed( $archLocPath ); - if ( $archError = !Archive::Tar->create_archive( - $archLocPath, - $Config{ZM_UPLOAD_ARCH_COMPRESS}, - @archImageFiles - ) - ) - { - Error( "Tar error: ".Archive::Tar->error()."\n " ); - } - } - - if ( $archError ) - { - return( 0 ); + if ( $archError = ($status != &AZ_OK) ) + { + Error( "Zip error: $status\n " ); + } } else { - if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) - { - Info( "Uploading to ".$Config{ZM_UPLOAD_HOST}." using FTP\n" ); - my $ftp = Net::FTP->new( - $Config{ZM_UPLOAD_HOST}, - Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, - Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE}, - Debug=>$Config{ZM_UPLOAD_DEBUG} - ); - if ( !$ftp ) - { - Error( "Can't create FTP connection: $@" ); - return( 0 ); - } - $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} ) - or Error( "FTP - Can't login" ); - $ftp->binary() - or Error( "FTP - Can't go binary" ); - $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "FTP - Can't cwd" ) - if ( $Config{ZM_UPLOAD_REM_DIR} ); - $ftp->put( $archLocPath ) - or Error( "FTP - Can't upload '$archLocPath'" ); - $ftp->quit() - or Error( "FTP - Can't quit" ); - } - else - { - my $host = $Config{ZM_UPLOAD_HOST}; - $host .= ":".$Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - Info( "Uploading to ".$host." using SFTP\n" ); - my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} ); - $sftpOptions{password} = $Config{ZM_UPLOAD_PASS} - if $Config{ZM_UPLOAD_PASS}; - $sftpOptions{port} = $Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT} - if $Config{ZM_UPLOAD_TIMEOUT}; - my @more_ssh_args; - push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no' - if ! $Config{ZM_UPLOAD_STRICT}; - push @more_ssh_args, '-v' - if $Config{ZM_UPLOAD_DEBUG}; - $sftpOptions{more} = [@more_ssh_args]; - my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions ); - if ( $sftp->error ) - { - Error( "Can't create SFTP connection: ".$sftp->error ); - return( 0 ); - } - $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "SFTP - Can't setcwd: ".$sftp->error ) - if $Config{ZM_UPLOAD_REM_DIR}; - $sftp->put( $archLocPath, $archFile ) - or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error ); - } - unlink( $archLocPath ); - my $sql = "update Events set Uploaded = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + Error( "Error adding images to zip archive $archLocPath, not writing" ); } - return( 1 ); + } + elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "tar" ) + { + if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) + { + $archFile .= '.tar.gz'; + } + else + { + $archFile .= '.tar'; + } + $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; + Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + + if ( $archError = !Archive::Tar->create_archive( + $archLocPath, + $Config{ZM_UPLOAD_ARCH_COMPRESS}, + @archImageFiles + ) + ) + { + Error( "Tar error: ".Archive::Tar->error()."\n " ); + } + } + + if ( $archError ) + { + return( 0 ); + } + else + { + if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) + { + Info( "Uploading to ".$Config{ZM_UPLOAD_HOST}." using FTP\n" ); + my $ftp = Net::FTP->new( + $Config{ZM_UPLOAD_HOST}, + Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, + Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE}, + Debug=>$Config{ZM_UPLOAD_DEBUG} + ); + if ( !$ftp ) + { + Error( "Can't create FTP connection: $@" ); + return( 0 ); + } + $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} ) + or Error( "FTP - Can't login" ); + $ftp->binary() + or Error( "FTP - Can't go binary" ); + $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} ) + or Error( "FTP - Can't cwd" ) + if ( $Config{ZM_UPLOAD_REM_DIR} ); + $ftp->put( $archLocPath ) + or Error( "FTP - Can't upload '$archLocPath'" ); + $ftp->quit() + or Error( "FTP - Can't quit" ); + } + else + { + my $host = $Config{ZM_UPLOAD_HOST}; + $host .= ":".$Config{ZM_UPLOAD_PORT} + if $Config{ZM_UPLOAD_PORT}; + Info( "Uploading to ".$host." using SFTP\n" ); + my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} ); + $sftpOptions{password} = $Config{ZM_UPLOAD_PASS} + if $Config{ZM_UPLOAD_PASS}; + $sftpOptions{port} = $Config{ZM_UPLOAD_PORT} + if $Config{ZM_UPLOAD_PORT}; + $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT} + if $Config{ZM_UPLOAD_TIMEOUT}; + my @more_ssh_args; + push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no' + if ! $Config{ZM_UPLOAD_STRICT}; + push @more_ssh_args, '-v' + if $Config{ZM_UPLOAD_DEBUG}; + $sftpOptions{more} = [@more_ssh_args]; + my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions ); + if ( $sftp->error ) + { + Error( "Can't create SFTP connection: ".$sftp->error ); + return( 0 ); + } + $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} ) + or Error( "SFTP - Can't setcwd: ".$sftp->error ) + if $Config{ZM_UPLOAD_REM_DIR}; + $sftp->put( $archLocPath, $archFile ) + or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error ); + } + unlink( $archLocPath ); + my $sql = "update Events set Uploaded = 1 where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } + return( 1 ); } sub substituteTags { - my $text = shift; - my $filter = shift; - my $event = shift; - my $attachments_ref = shift; + my $text = shift; + my $filter = shift; + my $event = shift; + my $attachments_ref = shift; - # First we'd better check what we need to get - # We have a filter and an event, do we need any more - # monitor information? - my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; +# First we'd better check what we need to get +# We have a filter and an event, do we need any more +# monitor information? + my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; - my $monitor = {}; - if ( $need_monitor ) - { - my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() ); - my $sql = "SELECT - M.Id, - count(E.Id) as EventCount, - count(if(E.Archived,1,NULL)) - as ArchEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL)) - as HourEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL)) - as DayEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL)) - as WeekEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL)) - as MonthEventCount - FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id - WHERE MonitorId = ? - GROUP BY E.MonitorId - ORDER BY Id" + my $monitor = {}; + if ( $need_monitor ) + { + my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() ); + my $sql = "SELECT + M.Id, + count(E.Id) as EventCount, + count(if(E.Archived,1,NULL)) + as ArchEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL)) + as HourEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL)) + as DayEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL)) + as WeekEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL)) + as MonthEventCount + FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id + WHERE MonitorId = ? + GROUP BY E.MonitorId + ORDER BY Id" ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{MonitorId} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - $monitor = $sth->fetchrow_hashref(); - $sth->finish(); - return() if ( !$monitor ); - } + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{MonitorId} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $monitor = $sth->fetchrow_hashref(); + $sth->finish(); + return() if ( !$monitor ); + } - # Do we need the image information too? - my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM)%/; - my $first_alarm_frame; - my $max_alarm_frame; - my $max_alarm_score = 0; - if ( $need_images ) +# Do we need the image information too? + my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM)%/; + my $first_alarm_frame; + my $max_alarm_frame; + my $max_alarm_score = 0; + if ( $need_images ) + { + my $sql = "SELECT * FROM Frames + WHERE EventId = ? AND Type = 'Alarm' + ORDER BY FrameId" + ; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + while( my $frame = $sth->fetchrow_hashref() ) { - my $sql = "SELECT * FROM Frames - WHERE EventId = ? AND Type = 'Alarm' - ORDER BY FrameId" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - while( my $frame = $sth->fetchrow_hashref() ) - { - if ( !$first_alarm_frame ) - { - $first_alarm_frame = $frame; - } - if ( $frame->{Score} > $max_alarm_score ) - { - $max_alarm_frame = $frame; - $max_alarm_score = $frame->{Score}; - } - } - $sth->finish(); + if ( !$first_alarm_frame ) + { + $first_alarm_frame = $frame; + } + if ( $frame->{Score} > $max_alarm_score ) + { + $max_alarm_frame = $frame; + $max_alarm_score = $frame->{Score}; + } } + $sth->finish(); + } - my $url = $Config{ZM_URL}; - $text =~ s/%ZP%/$url/g; - $text =~ s/%MN%/$event->{MonitorName}/g; - $text =~ s/%MET%/$monitor->{EventCount}/g; - $text =~ s/%MEH%/$monitor->{HourEventCount}/g; - $text =~ s/%MED%/$monitor->{DayEventCount}/g; - $text =~ s/%MEW%/$monitor->{WeekEventCount}/g; - $text =~ s/%MEM%/$monitor->{MonthEventCount}/g; - $text =~ s/%MEA%/$monitor->{ArchEventCount}/g; - $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g; - $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g; - $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g; - $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EI%/$event->{Id}/g; - $text =~ s/%EN%/$event->{Name}/g; - $text =~ s/%EC%/$event->{Cause}/g; - $text =~ s/%ED%/$event->{Notes}/g; - $text =~ s/%ET%/$event->{StartTime}/g; - $text =~ s/%EL%/$event->{Length}/g; - $text =~ s/%EF%/$event->{Frames}/g; - $text =~ s/%EFA%/$event->{AlarmFrames}/g; - $text =~ s/%EST%/$event->{TotScore}/g; - $text =~ s/%ESA%/$event->{AvgScore}/g; - $text =~ s/%ESM%/$event->{MaxScore}/g; - if ( $first_alarm_frame ) + my $url = $Config{ZM_URL}; + $text =~ s/%ZP%/$url/g; + $text =~ s/%MN%/$event->{MonitorName}/g; + $text =~ s/%MET%/$monitor->{EventCount}/g; + $text =~ s/%MEH%/$monitor->{HourEventCount}/g; + $text =~ s/%MED%/$monitor->{DayEventCount}/g; + $text =~ s/%MEW%/$monitor->{WeekEventCount}/g; + $text =~ s/%MEM%/$monitor->{MonthEventCount}/g; + $text =~ s/%MEA%/$monitor->{ArchEventCount}/g; + $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g; + $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g; + $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g; + $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EI%/$event->{Id}/g; + $text =~ s/%EN%/$event->{Name}/g; + $text =~ s/%EC%/$event->{Cause}/g; + $text =~ s/%ED%/$event->{Notes}/g; + $text =~ s/%ET%/$event->{StartTime}/g; + $text =~ s/%EL%/$event->{Length}/g; + $text =~ s/%EF%/$event->{Frames}/g; + $text =~ s/%EFA%/$event->{AlarmFrames}/g; + $text =~ s/%EST%/$event->{TotScore}/g; + $text =~ s/%ESA%/$event->{AvgScore}/g; + $text =~ s/%ESM%/$event->{MaxScore}/g; + if ( $first_alarm_frame ) + { + $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g; + $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g; + if ( $attachments_ref && $text =~ s/%EI1%//g ) { - $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g; - $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g; - if ( $attachments_ref && $text =~ s/%EI1%//g ) - { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg", - getEventPath( $event ), - $first_alarm_frame->{FrameId} - ) - } + push( @$attachments_ref, + { + type=>"image/jpeg", + path=>sprintf( + "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg", + getEventPath( $event ), + $first_alarm_frame->{FrameId} + ) + } + ); + } + if ( $attachments_ref && $text =~ s/%EIM%//g ) + { +# Don't attach the same image twice + if ( !@$attachments_ref + || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) + ) + { + push( @$attachments_ref, + { + type=>"image/jpeg", + path=>sprintf( + "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg", + getEventPath( $event ), + $max_alarm_frame->{FrameId} + ) + } ); - } - if ( $attachments_ref && $text =~ s/%EIM%//g ) - { - # Don't attach the same image twice - if ( !@$attachments_ref - || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) - ) - { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg", - getEventPath( $event ), - $max_alarm_frame->{FrameId} - ) - } - ); - } - } + } } - if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) + } + if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) + { + if ( $text =~ s/%EV%//g ) { - if ( $text =~ s/%EV%//g ) - { - my ( $format, $path ) = generateVideo( $filter, $event ); - if ( !$format ) - { - return( undef ); - } - push( @$attachments_ref, { type=>"video/$format", path=>$path } ); - } - if ( $text =~ s/%EVM%//g ) - { - my ( $format, $path ) = generateVideo( $filter, $event, 1 ); - if ( !$format ) - { - return( undef ); - } - push( @$attachments_ref, { type=>"video/$format", path=>$path } ); - } + my ( $format, $path ) = generateVideo( $filter, $event ); + if ( !$format ) + { + return( undef ); + } + push( @$attachments_ref, { type=>"video/$format", path=>$path } ); } - $text =~ s/%FN%/$filter->{Name}/g; - ( my $filter_name = $filter->{Name} ) =~ s/ /+/g; - $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g; + if ( $text =~ s/%EVM%//g ) + { + my ( $format, $path ) = generateVideo( $filter, $event, 1 ); + if ( !$format ) + { + return( undef ); + } + push( @$attachments_ref, { type=>"video/$format", path=>$path } ); + } + } + $text =~ s/%FN%/$filter->{Name}/g; + ( my $filter_name = $filter->{Name} ) =~ s/ /+/g; + $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g; - return( $text ); + return( $text ); } sub sendEmail { - my $filter = shift; - my $event = shift; + my $filter = shift; + my $event = shift; - if ( ! $Config{ZM_FROM_EMAIL} ) + if ( ! $Config{ZM_FROM_EMAIL} ) + { + Error( "No 'from' email address defined, not sending email" ); + return( 0 ); + } + if ( ! $Config{ZM_EMAIL_ADDRESS} ) + { + Error( "No email address defined, not sending email" ); + return( 0 ); + } + + Info( "Creating notification email\n" ); + + my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event ); + return( 0 ) if ( !$subject ); + my @attachments; + my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments ); + return( 0 ) if ( !$body ); + + Info( "Sending notification email '$subject'\n" ); + + eval + { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { - Error( "No 'from' email address defined, not sending email" ); - return( 0 ); - } - if ( ! $Config{ZM_EMAIL_ADDRESS} ) - { - Error( "No email address defined, not sending email" ); - return( 0 ); - } - - Info( "Creating notification email\n" ); - - my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event ); - return( 0 ) if ( !$subject ); - my @attachments; - my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments ); - return( 0 ) if ( !$body ); - - Info( "Sending notification email '$subject'\n" ); - - eval - { - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - ### Create the multipart container - my $mail = MIME::Lite->new ( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, - Subject => $subject, - Type => "multipart/mixed" +### Create the multipart container + my $mail = MIME::Lite->new ( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_EMAIL_ADDRESS}, + Subject => $subject, + Type => "multipart/mixed" + ); +### Add the text message part + $mail->attach ( + Type => "TEXT", + Data => $body + ); +### Add the attachments + foreach my $attachment ( @attachments ) + { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Disposition => "attachment" ); - ### Add the text message part - $mail->attach ( - Type => "TEXT", - Data => $body - ); - ### Add the attachments - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Disposition => "attachment" - ); - } - ### Send the Message - if ( $Config{ZM_SSMTP_MAIL} ) { - my $ssmtp_location = $Config{ZM_SSMTP_PATH}; - if ( !$ssmtp_location ) { - if ( logDebugging() ) { - Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); - } - $ssmtp_location = qx('which ssmtp'); - } - if ( !$ssmtp_location ) { - Debug( "Can't find ssmtp, trying MIME::Lite->send" ); - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } else { - ### Send using SSMTP - $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} ); - } - } else { - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } + } +### Send the Message + if ( $Config{ZM_SSMTP_MAIL} ) { + my $ssmtp_location = $Config{ZM_SSMTP_PATH}; + if ( !$ssmtp_location ) { + if ( logDebugging() ) { + Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + } + $ssmtp_location = qx('which ssmtp'); } - else - { - my $mail = MIME::Entity->build( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, - Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), - Data => $body - ); - - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Encoding => "base64" - ); - } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); + if ( !$ssmtp_location ) { + Debug( "Can't find ssmtp, trying MIME::Lite->send" ); + MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } else { +### Send using SSMTP + $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} ); } - }; - if ( $@ ) - { - Error( "Can't send email: $@" ); - return( 0 ); + } else { + MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } } else { - Info( "Notification email sent\n" ); - } - my $sql = "update Events set Emailed = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $mail = MIME::Entity->build( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_EMAIL_ADDRESS}, + Subject => $subject, + Type => (($body=~//)?'text/html':'text/plain'), + Data => $body + ); - return( 1 ); + foreach my $attachment ( @attachments ) + { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Encoding => "base64" + ); + } + $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); + } + }; + if ( $@ ) + { + Error( "Can't send email: $@" ); + return( 0 ); + } + else + { + Info( "Notification email sent\n" ); + } + my $sql = "update Events set Emailed = 1 where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + return( 1 ); } sub sendMessage { - my $filter = shift; - my $event = shift; + my $filter = shift; + my $event = shift; - if ( ! $Config{ZM_FROM_EMAIL} ) + if ( ! $Config{ZM_FROM_EMAIL} ) + { + Error( "No 'from' email address defined, not sending message" ); + return( 0 ); + } + if ( ! $Config{ZM_MESSAGE_ADDRESS} ) + { + Error( "No message address defined, not sending message" ); + return( 0 ); + } + + Info( "Creating notification message\n" ); + + my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event ); + return( 0 ) if ( !$subject ); + my @attachments; + my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments ); + return( 0 ) if ( !$body ); + + Info( "Sending notification message '$subject'\n" ); + + eval + { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { - Error( "No 'from' email address defined, not sending message" ); - return( 0 ); - } - if ( ! $Config{ZM_MESSAGE_ADDRESS} ) - { - Error( "No message address defined, not sending message" ); - return( 0 ); - } - - Info( "Creating notification message\n" ); - - my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event ); - return( 0 ) if ( !$subject ); - my @attachments; - my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments ); - return( 0 ) if ( !$body ); - - Info( "Sending notification message '$subject'\n" ); - - eval - { - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - ### Create the multipart container - my $mail = MIME::Lite->new ( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_MESSAGE_ADDRESS}, - Subject => $subject, - Type => "multipart/mixed" +### Create the multipart container + my $mail = MIME::Lite->new ( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_MESSAGE_ADDRESS}, + Subject => $subject, + Type => "multipart/mixed" + ); +### Add the text message part + $mail->attach ( + Type => "TEXT", + Data => $body + ); +### Add the attachments + foreach my $attachment ( @attachments ) + { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Disposition => "attachment" ); - ### Add the text message part - $mail->attach ( - Type => "TEXT", - Data => $body - ); - ### Add the attachments - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Disposition => "attachment" - ); - } - ### Send the Message - if ( $Config{ZM_SSMTP_MAIL} ) { - my $ssmtp_location = $Config{ZM_SSMTP_PATH}; - if ( !$ssmtp_location ) { - if ( logDebugging() ) { - Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); - } - $ssmtp_location = qx('which ssmtp'); - } - if ( !$ssmtp_location ) { - Debug( "Can't find ssmtp, trying MIME::Lite->send" ); - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } else { - ### Send using SSMTP - $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS} ); - } - } else { - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } + } +### Send the Message + if ( $Config{ZM_SSMTP_MAIL} ) { + my $ssmtp_location = $Config{ZM_SSMTP_PATH}; + if ( !$ssmtp_location ) { + if ( logDebugging() ) { + Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + } + $ssmtp_location = qx('which ssmtp'); } - else - { - my $mail = MIME::Entity->build( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_MESSAGE_ADDRESS}, - Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), - Data => $body - ); - - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Encoding => "base64" - ); - } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, - MailFrom => $Config{ZM_FROM_EMAIL} - ); + if ( !$ssmtp_location ) { + Debug( "Can't find ssmtp, trying MIME::Lite->send" ); + MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } else { +### Send using SSMTP + $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS} ); } - }; - if ( $@ ) - { - Error( "Can't send email: $@" ); - return( 0 ); + } else { + MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } } else { - Info( "Notification message sent\n" ); - } - my $sql = "update Events set Messaged = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $mail = MIME::Entity->build( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_MESSAGE_ADDRESS}, + Subject => $subject, + Type => (($body=~//)?'text/html':'text/plain'), + Data => $body + ); - return( 1 ); + foreach my $attachment ( @attachments ) + { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Encoding => "base64" + ); + } + $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, + MailFrom => $Config{ZM_FROM_EMAIL} + ); + } + }; + if ( $@ ) + { + Error( "Can't send email: $@" ); + return( 0 ); + } + else + { + Info( "Notification message sent\n" ); + } + my $sql = "update Events set Messaged = 1 where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + return( 1 ); } sub executeCommand { - my $filter = shift; - my $event = shift; + my $filter = shift; + my $event = shift; - my $event_path = getEventPath( $event ); + my $event_path = getEventPath( $event ); - my $command = $filter->{AutoExecuteCmd}; - $command .= " $event_path"; + my $command = $filter->{AutoExecuteCmd}; + $command .= " $event_path"; - Info( "Executing '$command'\n" ); - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - Debug( "Output: $output\n" ); - } - if ( $status ) - { - Error( "Command '$command' exited with status: $status\n" ); - return( 0 ); - } - else - { - my $sql = "update Events set Executed = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - } - return( 1 ); + Info( "Executing '$command'\n" ); + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) + { + chomp( $output ); + Debug( "Output: $output\n" ); + } + if ( $status ) + { + Error( "Command '$command' exited with status: $status\n" ); + return( 0 ); + } + else + { + my $sql = "update Events set Executed = 1 where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } + return( 1 ); } From 184eb507a7390191e160ff8e6ccbfd68d547b703 Mon Sep 17 00:00:00 2001 From: Michael Schupikov Date: Wed, 29 Mar 2017 12:29:29 +0200 Subject: [PATCH 09/39] zm_local_camera.cpp: Improve format strings This avoids printing '0x0' as char and hence terminating the output too early. --- src/zm_local_camera.cpp | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index e82115e70..0d300eb92 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -348,7 +348,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, palette = V4L2_PIX_FMT_YUYV; } else { if(capture) { - Info("Selected capture palette: %s (%c%c%c%c)", palette_desc, palette&0xff, (palette>>8)&0xff, (palette>>16)&0xff, (palette>>24)&0xff); + Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", palette_desc, (palette>>24)&0xff, (palette>>16)&0xff, (palette>>8)&0xff, (palette)&0xff); } } } @@ -409,7 +409,8 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } else { if( capture ) #if HAVE_LIBSWSCALE - Info("No direct match for the selected palette (%c%c%c%c) and target colorspace (%d). Format conversion is required, performance penalty expected", (capturePixFormat)&0xff,((capturePixFormat>>8)&0xff),((capturePixFormat>>16)&0xff),((capturePixFormat>>24)&0xff), colours ); + Info("No direct match for the selected palette (0x%02hhx%02hhx%02hhx%02hhx) and target colorspace (%02u). Format conversion is required, performance penalty expected", + (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff), colours); #else Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); #endif @@ -427,16 +428,18 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, subpixelorder = ZM_SUBPIX_ORDER_NONE; imagePixFormat = AV_PIX_FMT_GRAY8; } else { - Panic("Unexpected colours: %d",colours); + Panic("Unexpected colours: %u",colours); } if( capture ) { #if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0) if(!sws_isSupportedInput(capturePixFormat)) { - Error("swscale does not support the used capture format: %c%c%c%c",(capturePixFormat)&0xff,((capturePixFormat>>8)&0xff),((capturePixFormat>>16)&0xff),((capturePixFormat>>24)&0xff)); + Error("swscale does not support the used capture format: 0x%02hhx%02hhx%02hhx%02hhx", + (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff)); conversion_type = 2; /* Try ZM format conversions */ } if(!sws_isSupportedOutput(imagePixFormat)) { - Error("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff)); + Error("swscale does not support the target format: 0x%02hhx%02hhx%02hhx%02hhx", + (imagePixFormat>>24)&0xff,((imagePixFormat>>16)&0xff),((imagePixFormat>>8)&0xff),((imagePixFormat)&0xff)); conversion_type = 2; /* Try ZM format conversions */ } #endif @@ -545,7 +548,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, subpixelorder = ZM_SUBPIX_ORDER_NONE; imagePixFormat = AV_PIX_FMT_GRAY8; } else { - Panic("Unexpected colours: %d",colours); + Panic("Unexpected colours: %u",colours); } if( capture ) { if(!sws_isSupportedInput(capturePixFormat)) { @@ -613,7 +616,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, #endif // ZM_HAS_V4L1 last_camera = this; - Debug(3,"Selected subpixelorder: %d",subpixelorder); + Debug(3,"Selected subpixelorder: %u",subpixelorder); #if HAVE_LIBSWSCALE /* Initialize swscale stuff */ @@ -632,7 +635,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, int pSize = avpicture_get_size( imagePixFormat, width, height ); #endif if( (unsigned int)pSize != imagesize) { - Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize); + Fatal("Image size mismatch. Required: %d Available: %u",pSize,imagesize); } imgConversionContext = sws_getContext(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); @@ -789,7 +792,7 @@ void LocalCamera::Initialise() Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno) ); } else { Debug(4, "JPEG quality: %d",jpeg_comp.quality); - Debug(4, "JPEG markers: %#x",jpeg_comp.jpeg_markers); + Debug(4, "JPEG markers: 0x%x",jpeg_comp.jpeg_markers); } } } @@ -853,7 +856,7 @@ void LocalCamera::Initialise() v4l2_data.buffers[i].start = mmap( NULL, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset ); if ( v4l2_data.buffers[i].start == MAP_FAILED ) - Fatal( "Can't map video buffer %d (%d bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno ); + Fatal( "Can't map video buffer %u (%u bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno ); #if HAVE_LIBSWSCALE #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) @@ -1177,7 +1180,8 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { strcpy(fmt_desc[nIndex], (const char*)(fmtinfo.description)); fmt_fcc[nIndex] = fmtinfo.pixelformat; - Debug(6, "Got format: %s (%c%c%c%c) at index %d",fmt_desc[nIndex],fmt_fcc[nIndex]&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>24)&0xff ,nIndex); + Debug(6, "Got format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %d", + fmt_desc[nIndex], (fmt_fcc[nIndex]>>24)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex])&0xff ,nIndex); /* Proceed to the next index */ memset(&fmtinfo, 0, sizeof(fmtinfo)); @@ -1205,12 +1209,14 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { for( unsigned int i=0; i < n_preferedformats && nIndexUsed < 0; i++ ) { for( unsigned int j=0; j < nIndex; j++ ) { if( preferedformats[i] == fmt_fcc[j] ) { - Debug(6, "Choosing format: %s (%c%c%c%c) at index %d",fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + Debug(6, "Choosing format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", + fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); /* Found a format! */ nIndexUsed = j; break; } else { - Debug(6, "No match for format: %s (%c%c%c%c) at index %d",fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + Debug(6, "No match for format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", + fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); } } } @@ -1385,9 +1391,11 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } } if ( verbose ) - sprintf( output+strlen(output), " %s (%c%c%c%c)\n", format.description, format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff ); + sprintf( output+strlen(output), " %s (0x%02hhx%02hhx%02hhx%02hhx)\n", + format.description, (format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, format.pixelformat&0xff); else - sprintf( output+strlen(output), "%c%c%c%c/", format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff ); + sprintf( output+strlen(output), "0x%02hhx%02hhx%02hhx%02hhx/", + (format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat)&0xff); } while ( formatIndex++ >= 0 ); if ( !verbose ) From d3f6ab3d2947554297a32065724290c7b4d406bd Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 30 Mar 2017 13:06:54 -0400 Subject: [PATCH 10/39] fix Monitors filtering SQL --- web/skins/classic/views/montagereview.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/web/skins/classic/views/montagereview.php b/web/skins/classic/views/montagereview.php index 4c1195190..b85ca9fda 100644 --- a/web/skins/classic/views/montagereview.php +++ b/web/skins/classic/views/montagereview.php @@ -145,11 +145,9 @@ $frameSql = " if ( !empty($user['MonitorIds']) ) { - $monFilterSql = ' AND M.Id IN ('.$user['MonitorIds'].')'; - - $eventsSql .= $monFilterSql; - $monitorsSQL .= $monFilterSql; - $frameSql .= $monFilterSql; + $eventsSql .= ' AND M.Id IN ('.$user['MonitorIds'].')'; + $monitorsSql .= ' AND Id IN ('.$user['MonitorIds'].')'; + $frameSql .= ' AND E.MonitorId IN ('.$user['MonitorIds'].')'; } // Parse input parameters -- note for future, validate/clean up better in case we don't get called from self. From 716a25fc1f56d5972eaf59b87f803d20bfb2ee00 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Fri, 31 Mar 2017 20:17:05 -0500 Subject: [PATCH 11/39] rpmspecfile - ensure cache folders are created during install --- distros/redhat/CMakeLists.txt | 5 ++++- distros/redhat/zoneminder.spec | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/distros/redhat/CMakeLists.txt b/distros/redhat/CMakeLists.txt index f8acef552..69ed05ff3 100644 --- a/distros/redhat/CMakeLists.txt +++ b/distros/redhat/CMakeLists.txt @@ -46,13 +46,16 @@ else("${unzip_jsc}" STREQUAL "") endif("${unzip_jsc}" STREQUAL "") # Create several empty folders -file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp) +file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp logs cache models persistent views) # Install the empty folders install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(DIRECTORY zoneminder DESTINATION /var/run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(DIRECTORY logs cache DESTINATION /var/lib/zoneminder/temp DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(DIRECTORY models persistent views DESTINATION /var/lib/zoneminder/temp/cache DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) # Create symlinks install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/events \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/events\")") diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 0ed6323f0..3e1a1fd52 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -333,7 +333,14 @@ rm -rf %{_docdir}/%{name}-%{version} %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %ghost %{_localstatedir}/run/zoneminder +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/run/zoneminder + +# cakephp requires its cache folders to pre-exist +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/logs +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/cache +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/cache/views +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/cache/models +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/cache/persistent %changelog * Thu Mar 30 2017 Andrew Bauer - 1.30.2-2 From cd1b09d97751d3a49558c4e6dd382708fea4088d Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Fri, 31 Mar 2017 20:35:24 -0500 Subject: [PATCH 12/39] Update zoneminder.spec use rpm macros --- distros/redhat/zoneminder.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 3e1a1fd52..6fe589949 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -132,8 +132,8 @@ too much degradation of performance. %prep %autosetup -n ZoneMinder-%{version} %autosetup -a 1 -n ZoneMinder-%{version} -rmdir ./web/api/app/Plugin/Crud -mv -f crud-%{crud_version} ./web/api/app/Plugin/Crud +%{__rm} -rf ./web/api/app/Plugin/Crud +%{__mv} -f crud-%{crud_version} ./web/api/app/Plugin/Crud # Change the following default values ./utils/zmeditconfigdata.sh ZM_PATH_ZMS /cgi-bin-zm/nph-zms From a40de3b588a2c34d0e50c55b1a23d01ab4d65b30 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sat, 1 Apr 2017 08:20:34 -0500 Subject: [PATCH 13/39] Update redhat.rst better way to create a tarball from a local git repo --- docs/installationguide/redhat.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/installationguide/redhat.rst b/docs/installationguide/redhat.rst index e6b506ac5..10ac7a17f 100644 --- a/docs/installationguide/redhat.rst +++ b/docs/installationguide/redhat.rst @@ -169,8 +169,6 @@ Now clone the ZoneMinder git repository: cd git clone https://github.com/ZoneMinder/ZoneMinder cd ZoneMinder - git submodule init - git submodule update This will create a sub-folder called ZoneMinder, which will contain the latest development. @@ -180,14 +178,13 @@ We want to turn this into a tarball, but first we need to figure out what to nam ls ~/rpmbuild/SOURCES -The tarball from the previsouly installed SRPM should be there. This is the name we will use. For this example, the name is ZoneMinder-1.28.1.tar.gz. From one folder above the local ZoneMinder git repository, execute the following: +The tarball from the previsouly installed SRPM should be there. This is the name we will use. For this example, the name is ZoneMinder-1.28.1.tar.gz. From the local ZoneMinder git repository, execute the following: :: - mv ZoneMinder ZoneMinder-1.28.1 - tar -cvzf ~/rpmbuild/SOURCES/ZoneMinder-1.28.1.tar.gz ZoneMinder-1.28.1/* + git archive --prefix=ZoneMinder-1.28.1/ -o zoneminder-1.28.1.tar.gz HEAD + mv zoneminder-1.28.1.tar.gz ~/rpmbuld/SOURCES/ -The trailing "/\*" leaves off the hidden dot "." file and folders from the git repo, which is what we want. Note that we are overwriting the original tarball. If you wish to keep the original tarball then create a copy prior to creating the new tarball. Now build a new src.rpm: From 8555b07f95895899605dbd8c86b0239497e90a33 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sat, 1 Apr 2017 08:26:27 -0500 Subject: [PATCH 14/39] Update redhat.rst update rpm building instructions to reflect latest changes --- docs/installationguide/redhat.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/installationguide/redhat.rst b/docs/installationguide/redhat.rst index 10ac7a17f..ff68bb685 100644 --- a/docs/installationguide/redhat.rst +++ b/docs/installationguide/redhat.rst @@ -187,11 +187,13 @@ The tarball from the previsouly installed SRPM should be there. This is the name Note that we are overwriting the original tarball. If you wish to keep the original tarball then create a copy prior to creating the new tarball. -Now build a new src.rpm: +From the root of the local ZoneMinder git repo, execute the following: :: - rpmbuild -bs --nodeps ~/rpmbuild/SPECS/zoneminder.el7.spec + rpmbuild -bs --nodeps distros/redhat/zoneminder.spec + +Notice we used the rpm specfile that is part of the latest master branch you just downloaded, rather than the one that may be in your ~/rpmbbuild/SOURCES folder. This step will overwrite the SRPM you originally downloaded, so you may want to back it up prior to completing this step. Note that the name of the specfile will vary slightly depending on the target distro. From a1f0ec3ecf2e14a9324bbff977f143e49e282ded Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 2 Apr 2017 19:42:48 -0700 Subject: [PATCH 15/39] Use misc/apache.conf instead of utils/docker/apache-vhost in docker This avoids duplication and fixes a few things that were missing in the Docker version for modern Apache: * AllowOverride All * Require all granted Deleted `ServerName @WEB_HOST@` since `WEB_HOST` was never defined in ZM code so it would give an Apache error. --- Dockerfile | 2 +- misc/apache.conf.in | 11 ++++++++++- utils/docker/apache-vhost | 14 -------------- utils/docker/setup.sh | 3 +++ 4 files changed, 14 insertions(+), 16 deletions(-) delete mode 100644 utils/docker/apache-vhost diff --git a/Dockerfile b/Dockerfile index 2c72f0f38..40f759a5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,7 @@ ADD utils/docker/start.sh /tmp/start.sh RUN chown -R www-data:www-data /usr/local/share/zoneminder/ # Adding apache virtual hosts file -ADD utils/docker/apache-vhost /etc/apache2/sites-available/000-default.conf +RUN cp misc/apache.conf /etc/apache2/sites-available/000-default.conf ADD utils/docker/phpdate.ini /etc/php5/apache2/conf.d/25-phpdate.ini # Expose http ports diff --git a/misc/apache.conf.in b/misc/apache.conf.in index 5ad45338b..d1c6d70db 100644 --- a/misc/apache.conf.in +++ b/misc/apache.conf.in @@ -5,13 +5,22 @@ # Some values may need to manually adjusted to suit your setup # - ServerName @WEB_HOST@ ServerAdmin webmaster@localhost DocumentRoot "@WEB_PREFIX@" + Alias /zm/ "@WEB_PREFIX@/" Options -Indexes +FollowSymLinks AllowOverride All + + # Apache 2.4 + Require all granted + + + # Apache 2.2 + Order deny,allow + Allow from all + ScriptAlias /cgi-bin "@CGI_PREFIX@" diff --git a/utils/docker/apache-vhost b/utils/docker/apache-vhost deleted file mode 100644 index e1821932e..000000000 --- a/utils/docker/apache-vhost +++ /dev/null @@ -1,14 +0,0 @@ - - DocumentRoot /usr/local/share/zoneminder/www - DirectoryIndex index.php - - ScriptAlias /cgi-bin/ /usr/local/libexec/zoneminder/cgi-bin/ - - Require all granted - - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - Require all granted - - diff --git a/utils/docker/setup.sh b/utils/docker/setup.sh index fbd3592f0..c7b8b121b 100755 --- a/utils/docker/setup.sh +++ b/utils/docker/setup.sh @@ -27,6 +27,9 @@ mysql -u root < db/zm_create.sql # Add the ZoneMinder DB user mysql -u root -e "grant insert,select,update,delete,lock tables,alter on zm.* to 'zmuser'@'localhost' identified by 'zmpass';" +# Make ZM_LOGDIR +mkdir /var/log/zm + # Activate CGI a2enmod cgi From c4ec2d7fabeefedc4a9823ec5090056ca99122d1 Mon Sep 17 00:00:00 2001 From: Eliot Kent Woodrich Date: Wed, 5 Apr 2017 17:26:19 -0700 Subject: [PATCH 16/39] Suppress debug messages on ZM service start/restart The tests in the script General.pm that determine the format for superuser calls on the target environment may leak debug messages to STDOUT. This can be replicated on a Docker installation of Zoneminder lacking sudo. Starting the Zoneminder process will display an intimidating series of error messages that can safely be ignored by the user: e.g. ''' root@2bfdd23cc27a:~# service zoneminder start Starting ZoneMinder: Can't exec "sudo": No such file or directory at /usr/share/perl5/ZoneMinder/General.pm line 1 10. Use of uninitialized value $output in scalar chomp at /usr/share/perl5/ZoneMinder/General.pm line 119. Use of uninitialized value $output in concatenation (.) or string at /usr/share/perl5/ZoneMinder/General.pm line 1 20. success ''' This patch redirects STDERR from the qx(...) calls to the debug message., And if the output of qx(...) is undefined, replaces it with any error in $! resulting from the qx(...) call. --- scripts/ZoneMinder/lib/ZoneMinder/General.pm | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index bb03fe70c..3d004725b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -103,8 +103,10 @@ sub getCmdFormat { my $suffix = ""; my $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); - my $output = qx($command); + my $output = qx($command 2>&1); my $status = $? >> 8; + $output //= $!; + if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); @@ -116,8 +118,10 @@ sub getCmdFormat { $suffix = "'"; $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); - my $output = qx($command); + my $output = qx($command 2>&1); my $status = $? >> 8; + $output //= $!; + if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); @@ -129,8 +133,10 @@ sub getCmdFormat { $suffix = "'"; $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); - $output = qx($command); + $output = qx($command 2>&1); $status = $? >> 8; + $output //= $!; + if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); From 57bf1bb4e00798e8555e86e567441a7b686f4910 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 10 Apr 2017 16:56:59 -0400 Subject: [PATCH 17/39] add needed quotes --- utils/do_debian_package.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index de7b31178..84bd7acf0 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -139,10 +139,10 @@ cd "$DIRECTORY.orig"; git submodule init git submodule update --init --recursive -if [ $DISTRO == "trusty" ] || [ $DISTRO == "precise" ]; then +if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then ln -sf distros/ubuntu1204 debian else - if [ $DISTRO == "wheezy" ]; then + if [ "$DISTRO" == "wheezy" ]; then ln -sf distros/debian debian else ln -sf distros/ubuntu1604 debian From 766925d9a79cae0ed0cdf5b038b846e5ffe30c3f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 10 Apr 2017 21:51:18 -0400 Subject: [PATCH 18/39] Split MonitorStream into it's own files. --- src/zm_monitor.cpp | 780 +-------------------------------------------- src/zm_monitor.h | 49 +-- 2 files changed, 5 insertions(+), 824 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index eebad7291..6db50acb9 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -3236,782 +3236,6 @@ bool Monitor::DumpSettings( char *output, bool verbose ) { return( true ); } // bool Monitor::DumpSettings( char *output, bool verbose ) -bool MonitorStream::checkSwapPath( const char *path, bool create_path ) { - uid_t uid = getuid(); - gid_t gid = getgid(); - - struct stat stat_buf; - if ( stat( path, &stat_buf ) < 0 ) { - if ( create_path && errno == ENOENT ) { - Debug( 3, "Swap path '%s' missing, creating", path ); - if ( mkdir( path, 0755 ) ) { - Error( "Can't mkdir %s: %s", path, strerror(errno)); - return( false ); - } - if ( stat( path, &stat_buf ) < 0 ) { - Error( "Can't stat '%s': %s", path, strerror(errno) ); - return( false ); - } - } else { - Error( "Can't stat '%s': %s", path, strerror(errno) ); - return( false ); - } - } - if ( !S_ISDIR(stat_buf.st_mode) ) { - Error( "Swap image path '%s' is not a directory", path ); - return( false ); - } - - mode_t mask = 0; - if ( uid == stat_buf.st_uid ) { - // If we are the owner - mask = 00700; - } else if ( gid == stat_buf.st_gid ) { - // If we are in the owner group - mask = 00070; - } else { - // We are neither the owner nor in the group - mask = 00007; - } - - if ( (stat_buf.st_mode & mask) != mask ) { - Error( "Insufficient permissions on swap image path '%s'", path ); - return( false ); - } - return( true ); -} - -void MonitorStream::processCommand( const CmdMsg *msg ) { - Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ); - // Check for incoming command - switch( (MsgCommand)msg->msg_data[0] ) { - case CMD_PAUSE : - { - Debug( 1, "Got PAUSE command" ); - - // Set paused flag - paused = true; - // Set delayed flag - delayed = true; - last_frame_sent = TV_2_FLOAT( now ); - break; - } - case CMD_PLAY : - { - Debug( 1, "Got PLAY command" ); - if ( paused ) { - // Clear paused flag - paused = false; - // Set delayed_play flag - delayed = true; - } - replay_rate = ZM_RATE_BASE; - break; - } - case CMD_VARPLAY : - { - Debug( 1, "Got VARPLAY command" ); - if ( paused ) { - // Clear paused flag - paused = false; - // Set delayed_play flag - delayed = true; - } - replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768; - break; - } - case CMD_STOP : - { - Debug( 1, "Got STOP command" ); - - // Clear paused flag - paused = false; - // Clear delayed_play flag - delayed = false; - break; - } - case CMD_FASTFWD : - { - Debug( 1, "Got FAST FWD command" ); - if ( paused ) { - // Clear paused flag - paused = false; - // Set delayed_play flag - delayed = true; - } - // Set play rate - switch ( replay_rate ) - { - case 2 * ZM_RATE_BASE : - replay_rate = 5 * ZM_RATE_BASE; - break; - case 5 * ZM_RATE_BASE : - replay_rate = 10 * ZM_RATE_BASE; - break; - case 10 * ZM_RATE_BASE : - replay_rate = 25 * ZM_RATE_BASE; - break; - case 25 * ZM_RATE_BASE : - case 50 * ZM_RATE_BASE : - replay_rate = 50 * ZM_RATE_BASE; - break; - default : - replay_rate = 2 * ZM_RATE_BASE; - break; - } - break; - } - case CMD_SLOWFWD : - { - Debug( 1, "Got SLOW FWD command" ); - // Set paused flag - paused = true; - // Set delayed flag - delayed = true; - // Set play rate - replay_rate = ZM_RATE_BASE; - // Set step - step = 1; - break; - } - case CMD_SLOWREV : - { - Debug( 1, "Got SLOW REV command" ); - // Set paused flag - paused = true; - // Set delayed flag - delayed = true; - // Set play rate - replay_rate = ZM_RATE_BASE; - // Set step - step = -1; - break; - } - case CMD_FASTREV : - { - Debug( 1, "Got FAST REV command" ); - if ( paused ) { - // Clear paused flag - paused = false; - // Set delayed_play flag - delayed = true; - } - // Set play rate - switch ( replay_rate ) { - case -2 * ZM_RATE_BASE : - replay_rate = -5 * ZM_RATE_BASE; - break; - case -5 * ZM_RATE_BASE : - replay_rate = -10 * ZM_RATE_BASE; - break; - case -10 * ZM_RATE_BASE : - replay_rate = -25 * ZM_RATE_BASE; - break; - case -25 * ZM_RATE_BASE : - case -50 * ZM_RATE_BASE : - replay_rate = -50 * ZM_RATE_BASE; - break; - default : - replay_rate = -2 * ZM_RATE_BASE; - break; - } - break; - } - case CMD_ZOOMIN : - { - x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; - y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - Debug( 1, "Got ZOOM IN command, to %d,%d", x, y ); - switch ( zoom ) { - case 100: - zoom = 150; - break; - case 150: - zoom = 200; - break; - case 200: - zoom = 300; - break; - case 300: - zoom = 400; - break; - case 400: - default : - zoom = 500; - break; - } - break; - } - case CMD_ZOOMOUT : - { - Debug( 1, "Got ZOOM OUT command" ); - switch ( zoom ) { - case 500: - zoom = 400; - break; - case 400: - zoom = 300; - break; - case 300: - zoom = 200; - break; - case 200: - zoom = 150; - break; - case 150: - default : - zoom = 100; - break; - } - break; - } - case CMD_PAN : - { - x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; - y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - Debug( 1, "Got PAN command, to %d,%d", x, y ); - break; - } - case CMD_SCALE : - { - scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; - Debug( 1, "Got SCALE command, to %d", scale ); - break; - } - case CMD_QUIT : - { - Info ("User initiated exit - CMD_QUIT"); - break; - } - case CMD_QUERY : - { - Debug( 1, "Got QUERY command, sending STATUS" ); - break; - } - default : - { - Error( "Got unexpected command %d", msg->msg_data[0] ); - break; - } - } - - struct { - int id; - int state; - double fps; - int buffer_level; - int rate; - double delay; - int zoom; - bool delayed; - bool paused; - bool enabled; - bool forced; - } status_data; - - status_data.id = monitor->Id(); - status_data.fps = monitor->GetFPS(); - status_data.state = monitor->shared_data->state; - if ( playback_buffer > 0 ) - status_data.buffer_level = (MOD_ADD( (temp_write_index-temp_read_index), 0, temp_image_buffer_count )*100)/temp_image_buffer_count; - else - status_data.buffer_level = 0; - status_data.delayed = delayed; - status_data.paused = paused; - status_data.rate = replay_rate; - status_data.delay = TV_2_FLOAT( now ) - TV_2_FLOAT( last_frame_timestamp ); - status_data.zoom = zoom; - //status_data.enabled = monitor->shared_data->active; - status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF; - status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON; - Debug( 2, "L:%d, D:%d, P:%d, R:%d, d:%.3f, Z:%d, E:%d F:%d", - status_data.buffer_level, - status_data.delayed, - status_data.paused, - status_data.rate, - status_data.delay, - status_data.zoom, - status_data.enabled, - status_data.forced - ); - - DataMsg status_msg; - status_msg.msg_type = MSG_DATA_WATCH; - memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) ); - int nbytes = 0; - if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) { - //if ( errno != EAGAIN ) - { - Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); - //exit( -1 ); - } - } - - // quit after sending a status, if this was a quit request - if ((MsgCommand)msg->msg_data[0]==CMD_QUIT) - exit(0); - - updateFrameRate( monitor->GetFPS() ); -} - -bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) { - bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); - - if ( type != STREAM_JPEG ) - send_raw = false; - if ( !config.timestamp_on_capture && timestamp ) - send_raw = false; - - if ( !send_raw ) { - Image temp_image( filepath ); - - return( sendFrame( &temp_image, timestamp ) ); - } else { - int img_buffer_size = 0; - static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE]; - - FILE *fdj = NULL; - if ( (fdj = fopen( filepath, "r" )) ) { - img_buffer_size = fread( img_buffer, 1, sizeof(img_buffer), fdj ); - fclose( fdj ); - } else { - Error( "Can't open %s: %s", filepath, strerror(errno) ); - return( false ); - } - - // Calculate how long it takes to actually send the frame - struct timeval frameStartTime; - gettimeofday( &frameStartTime, NULL ); - - fprintf( stdout, "--ZoneMinderFrame\r\n" ); - fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size ); - fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { - if ( ! zm_terminate ) - Error( "Unable to send stream frame: %s", strerror(errno) ); - return( false ); - } - fprintf( stdout, "\r\n\r\n" ); - fflush( stdout ); - - struct timeval frameEndTime; - gettimeofday( &frameEndTime, NULL ); - - int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); - if ( frameSendTime > 1000/maxfps ) { - maxfps /= 2; - Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); - } - - last_frame_sent = TV_2_FLOAT( now ); - - return( true ); - } - return( false ); -} - -bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) { - Image *send_image = prepareImage( image ); - if ( !config.timestamp_on_capture && timestamp ) - monitor->TimestampImage( send_image, timestamp ); - -#if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) { - if ( !vid_stream ) { - vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() ); - fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() ); - vid_stream->OpenStream(); - } - static struct timeval base_time; - struct DeltaTimeval delta_time; - if ( !frame_count ) - base_time = *timestamp; - DELTA_TIMEVAL( delta_time, *timestamp, base_time, DT_PREC_3 ); - /* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta ); - } else -#endif // HAVE_LIBAVCODEC - { - static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; - - int img_buffer_size = 0; - unsigned char *img_buffer = temp_img_buffer; - - // Calculate how long it takes to actually send the frame - struct timeval frameStartTime; - gettimeofday( &frameStartTime, NULL ); - - fprintf( stdout, "--ZoneMinderFrame\r\n" ); - switch( type ) { - case STREAM_JPEG : - send_image->EncodeJpeg( img_buffer, &img_buffer_size ); - fprintf( stdout, "Content-Type: image/jpeg\r\n" ); - break; - case STREAM_RAW : - fprintf( stdout, "Content-Type: image/x-rgb\r\n" ); - img_buffer = (uint8_t*)send_image->Buffer(); - img_buffer_size = send_image->Size(); - break; - case STREAM_ZIP : - fprintf( stdout, "Content-Type: image/x-rgbz\r\n" ); - unsigned long zip_buffer_size; - send_image->Zip( img_buffer, &zip_buffer_size ); - img_buffer_size = zip_buffer_size; - break; - default : - Fatal( "Unexpected frame type %d", type ); - break; - } - fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { - if ( !zm_terminate ) - Error( "Unable to send stream frame: %s", strerror(errno) ); - return( false ); - } - fprintf( stdout, "\r\n\r\n" ); - fflush( stdout ); - - struct timeval frameEndTime; - gettimeofday( &frameEndTime, NULL ); - - int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); - if ( frameSendTime > 1000/maxfps ) { - maxfps /= 1.5; - Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); - } - } - last_frame_sent = TV_2_FLOAT( now ); - return( true ); -} // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) - -void MonitorStream::runStream() { - if ( type == STREAM_SINGLE ) { - // Not yet migrated over to stream class - monitor->SingleImage( scale ); - return; - } - - openComms(); - - checkInitialised(); - - updateFrameRate( monitor->GetFPS() ); - - if ( type == STREAM_JPEG ) - fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" ); - - int last_read_index = monitor->image_buffer_count; - - time_t stream_start_time; - time( &stream_start_time ); - - frame_count = 0; - - temp_image_buffer = 0; - temp_image_buffer_count = playback_buffer; - temp_read_index = temp_image_buffer_count; - temp_write_index = temp_image_buffer_count; - - char *swap_path = 0; - bool buffered_playback = false; - - // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id - const int max_swap_len_suffix = 15; - - int swap_path_length = strlen(config.path_swap) + 1; // +1 for NULL terminator - int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id() ) + 1; - int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey ) + 1; - int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; - - if ( connkey && playback_buffer > 0 ) { - - if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) { - Error( "Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX ); - } else { - swap_path = (char *)malloc( total_swap_path_length+max_swap_len_suffix ); - strncpy( swap_path, config.path_swap, swap_path_length ); - - Debug( 3, "Checking swap path folder: %s", swap_path ); - if ( checkSwapPath( swap_path, false ) ) { - // Append the subfolder name /zmswap-m{monitor-id} to the end of swap_path - int ndx = swap_path_length - 1; // Array index of the NULL terminator - snprintf( &(swap_path[ndx]), subfolder1_length, "/zmswap-m%d", monitor->Id() ); - - Debug( 4, "Checking swap path subfolder: %s", swap_path ); - if ( checkSwapPath( swap_path, true ) ) { - // Append the subfolder name /zmswap-q{connection key} to the end of swap_path - ndx = swap_path_length+subfolder1_length - 2; // Array index of the NULL terminator - snprintf( &(swap_path[ndx]), subfolder2_length, "/zmswap-q%06d", connkey ); - - Debug( 4, "Checking swap path subfolder: %s", swap_path ); - if ( checkSwapPath( swap_path, true ) ) { - buffered_playback = true; - } - } - } - - if ( !buffered_playback ) { - Error( "Unable to validate swap image path, disabling buffered playback" ); - } else { - Debug( 2, "Assigning temporary buffer" ); - temp_image_buffer = new SwapImage[temp_image_buffer_count]; - memset( temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count ); - Debug( 2, "Assigned temporary buffer" ); - } - } - } - - float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs) - while ( !zm_terminate ) { - bool got_command = false; - if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) { - break; - } - - gettimeofday( &now, NULL ); - - if ( connkey ) { - while(checkCommandQueue()) { - got_command = true; - } - } - - //bool frame_sent = false; - if ( buffered_playback && delayed ) { - if ( temp_read_index == temp_write_index ) { - // Go back to live viewing - Debug( 1, "Exceeded temporary streaming buffer" ); - // Clear paused flag - paused = false; - // Clear delayed_play flag - delayed = false; - replay_rate = ZM_RATE_BASE; - } else { - if ( !paused ) { - int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count ); - //Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index ); - SwapImage *swap_image = &temp_image_buffer[temp_index]; - - if ( !swap_image->valid ) { - paused = true; - delayed = true; - temp_read_index = MOD_ADD( temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count ); - } else { - //Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); - double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate; - double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - - //Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); - // If the next frame is due - if ( actual_delta_time > expected_delta_time ) { - //Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); - if ( temp_index%frame_mod == 0 ) { - Debug( 2, "Sending delayed frame %d", temp_index ); - // Send the next frame - if ( ! sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) - zm_terminate = true; - memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); - //frame_sent = true; - } - temp_read_index = MOD_ADD( temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count ); - } - } - } else if ( step != 0 ) { - temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count ); - - SwapImage *swap_image = &temp_image_buffer[temp_read_index]; - - // Send the next frame - if ( !sendFrame( temp_image_buffer[temp_read_index].file_name, &temp_image_buffer[temp_read_index].timestamp ) ) - zm_terminate = true; - memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); - //frame_sent = true; - step = 0; - } else { - int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count ); - - double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( got_command || actual_delta_time > 5 ) { - // Send keepalive - Debug( 2, "Sending keepalive frame %d", temp_index ); - // Send the next frame - if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) - zm_terminate = true; - //frame_sent = true; - } - } - } - if ( temp_read_index == temp_write_index ) { - // Go back to live viewing - Warning( "Rewound over write index, resuming live play" ); - // Clear paused flag - paused = false; - // Clear delayed_play flag - delayed = false; - replay_rate = ZM_RATE_BASE; - } - } - if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) { - int index = monitor->shared_data->last_write_index%monitor->image_buffer_count; - last_read_index = monitor->shared_data->last_write_index; - //Debug( 1, "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer ); - if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { - if ( !paused && !delayed ) { - // Send the next frame - Monitor::Snapshot *snap = &monitor->image_buffer[index]; - - if ( !sendFrame( snap->image, snap->timestamp ) ) - zm_terminate = true; - memcpy( &last_frame_timestamp, snap->timestamp, sizeof(last_frame_timestamp) ); - //frame_sent = true; - - temp_read_index = temp_write_index; - } - } - if ( buffered_playback ) { - if ( monitor->shared_data->valid ) { - if ( monitor->image_buffer[index].timestamp->tv_sec ) { - int temp_index = temp_write_index%temp_image_buffer_count; - Debug( 2, "Storing frame %d", temp_index ); - if ( !temp_image_buffer[temp_index].valid ) { - snprintf( temp_image_buffer[temp_index].file_name, sizeof(temp_image_buffer[0].file_name), "%s/zmswap-i%05d.jpg", swap_path, temp_index ); - temp_image_buffer[temp_index].valid = true; - } - memcpy( &(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp) ); - monitor->image_buffer[index].image->WriteJpeg( temp_image_buffer[temp_index].file_name, config.jpeg_file_quality ); - temp_write_index = MOD_ADD( temp_write_index, 1, temp_image_buffer_count ); - if ( temp_write_index == temp_read_index ) { - // Go back to live viewing - Warning( "Exceeded temporary buffer, resuming live play" ); - // Clear paused flag - paused = false; - // Clear delayed_play flag - delayed = false; - replay_rate = ZM_RATE_BASE; - } - } else { - Warning( "Unable to store frame as timestamp invalid" ); - } - } else { - Warning( "Unable to store frame as shared memory invalid" ); - } - } - frame_count++; - } - usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); - if ( ttl ) { - if ( (now.tv_sec - stream_start_time) > ttl ) { - break; - } - } - if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) { - Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame ); - break; - } - } - if ( buffered_playback ) { - Debug( 1, "Cleaning swap files from %s", swap_path ); - struct stat stat_buf; - if ( stat( swap_path, &stat_buf ) < 0 ) { - if ( errno != ENOENT ) { - Error( "Can't stat '%s': %s", swap_path, strerror(errno) ); - } - } else if ( !S_ISDIR(stat_buf.st_mode) ) { - Error( "Swap image path '%s' is not a directory", swap_path ); - } else { - char glob_pattern[PATH_MAX] = ""; - - snprintf( glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path ); - glob_t pglob; - int glob_status = glob( glob_pattern, 0, 0, &pglob ); - if ( glob_status != 0 ) { - if ( glob_status < 0 ) { - Error( "Can't glob '%s': %s", glob_pattern, strerror(errno) ); - } else { - Debug( 1, "Can't glob '%s': %d", glob_pattern, glob_status ); - } - } else { - for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { - if ( unlink( pglob.gl_pathv[i] ) < 0 ) { - Error( "Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno) ); - } - } - } - globfree( &pglob ); - if ( rmdir( swap_path ) < 0 ) { - Error( "Can't rmdir '%s': %s", swap_path, strerror(errno) ); - } - } - } - if ( swap_path ) free( swap_path ); - closeComms(); -} - -void Monitor::SingleImage( int scale) { - int img_buffer_size = 0; - static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; - Image scaled_image; - int index = shared_data->last_write_index%image_buffer_count; - Snapshot *snap = &image_buffer[index]; - Image *snap_image = snap->image; - - if ( scale != ZM_SCALE_BASE ) { - scaled_image.Assign( *snap_image ); - scaled_image.Scale( scale ); - snap_image = &scaled_image; - } - if ( !config.timestamp_on_capture ) { - TimestampImage( snap_image, snap->timestamp ); - } - snap_image->EncodeJpeg( img_buffer, &img_buffer_size ); - - fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size ); - fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" ); - fwrite( img_buffer, img_buffer_size, 1, stdout ); -} - -void Monitor::SingleImageRaw( int scale) { - Image scaled_image; - int index = shared_data->last_write_index%image_buffer_count; - Snapshot *snap = &image_buffer[index]; - Image *snap_image = snap->image; - - if ( scale != ZM_SCALE_BASE ) { - scaled_image.Assign( *snap_image ); - scaled_image.Scale( scale ); - snap_image = &scaled_image; - } - if ( !config.timestamp_on_capture ) { - TimestampImage( snap_image, snap->timestamp ); - } - - fprintf( stdout, "Content-Length: %d\r\n", snap_image->Size() ); - fprintf( stdout, "Content-Type: image/x-rgb\r\n\r\n" ); - fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout ); -} - -void Monitor::SingleImageZip( int scale) { - unsigned long img_buffer_size = 0; - static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; - Image scaled_image; - int index = shared_data->last_write_index%image_buffer_count; - Snapshot *snap = &image_buffer[index]; - Image *snap_image = snap->image; - - if ( scale != ZM_SCALE_BASE ) { - scaled_image.Assign( *snap_image ); - scaled_image.Scale( scale ); - snap_image = &scaled_image; - } - if ( !config.timestamp_on_capture ) { - TimestampImage( snap_image, snap->timestamp ); - } - snap_image->Zip( img_buffer, &img_buffer_size ); - - fprintf( stdout, "Content-Length: %ld\r\n", img_buffer_size ); - fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" ); - fwrite( img_buffer, img_buffer_size, 1, stdout ); -} - unsigned int Monitor::Colours() const { return( camera->Colours() ); } unsigned int Monitor::SubpixelOrder() const { return( camera->SubpixelOrder() ); } int Monitor::PrimeCapture() { @@ -4024,3 +3248,7 @@ int Monitor::PostCapture() { return( camera->PostCapture() ); } Monitor::Orientation Monitor::getOrientation() const { return orientation; } + +Monitor::Snapshot *Monitor::getSnapshot() { + return &image_buffer[ shared_data->last_write_index%image_buffer_count ]; +} diff --git a/src/zm_monitor.h b/src/zm_monitor.h index d17da0477..91b464f88 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -443,6 +443,7 @@ public: State GetState() const; int GetImage( int index=-1, int scale=100 ); +Snapshot *getSnapshot(); struct timeval GetTimestamp( int index=-1 ) const; void UpdateAdaptiveSkip(); useconds_t GetAnalysisRate(); @@ -509,9 +510,6 @@ public: //void StreamImages( int scale=100, int maxfps=10, time_t ttl=0, int msq_id=0 ); //void StreamImagesRaw( int scale=100, int maxfps=10, time_t ttl=0 ); //void StreamImagesZip( int scale=100, int maxfps=10, time_t ttl=0 ); - void SingleImage( int scale=100 ); - void SingleImageRaw( int scale=100 ); - void SingleImageZip( int scale=100 ); #if HAVE_LIBAVCODEC //void StreamMpeg( const char *format, int scale=100, int maxfps=10, int bitrate=100000 ); #endif // HAVE_LIBAVCODEC @@ -519,49 +517,4 @@ public: #define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit)) -class MonitorStream : public StreamBase { - protected: - typedef struct SwapImage { - bool valid; - struct timeval timestamp; - char file_name[PATH_MAX]; - } SwapImage; - - private: - SwapImage *temp_image_buffer; - int temp_image_buffer_count; - int temp_read_index; - int temp_write_index; - - protected: - time_t ttl; - - protected: - int playback_buffer; - bool delayed; - - int frame_count; - - protected: - bool checkSwapPath( const char *path, bool create_path ); - - bool sendFrame( const char *filepath, struct timeval *timestamp ); - bool sendFrame( Image *image, struct timeval *timestamp ); - void processCommand( const CmdMsg *msg ); - - public: - MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) { - } - void setStreamBuffer( int p_playback_buffer ) { - playback_buffer = p_playback_buffer; - } - void setStreamTTL( time_t p_ttl ) { - ttl = p_ttl; - } - bool setStreamStart( int monitor_id ) { - return loadMonitor( monitor_id ); - } - void runStream(); -}; - #endif // ZM_MONITOR_H From 2220b04203395d465f976612552f70b39a83dccf Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 10 Apr 2017 21:52:29 -0400 Subject: [PATCH 19/39] need to include zm_monitorstream.h now --- src/zms.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zms.cpp b/src/zms.cpp index 4deacb03b..f37b63ca1 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -25,6 +25,7 @@ #include "zm_user.h" #include "zm_signal.h" #include "zm_monitor.h" +#include "zm_monitorstream.h" bool ValidateAccess( User *user, int mon_id ) { bool allowed = true; From 620797ac18193905ad0d1ac3f82c3d7570c28eae Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 10 Apr 2017 21:54:23 -0400 Subject: [PATCH 20/39] replace swresample with libavresample --- CMakeLists.txt | 30 +- src/CMakeLists.txt | 2 +- src/zm_videostore.cpp | 659 +++++++++++++++++++++++----------------- src/zm_videostore.h | 10 +- zoneminder-config.cmake | 4 +- 5 files changed, 406 insertions(+), 299 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 559ad2bdc..1971c268f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -564,21 +564,21 @@ if(NOT ZM_NO_FFMPEG) endif(SWSCALE_LIBRARIES) # rescale (using find_library and find_path) - find_library(SWRESAMPLE_LIBRARIES swresample) - if(SWRESAMPLE_LIBRARIES) - set(HAVE_LIBSWRESAMPLE 1) - list(APPEND ZM_BIN_LIBS "${SWRESAMPLE_LIBRARIES}") - find_path(SWRESAMPLE_INCLUDE_DIR "libswresample/swresample.h" /usr/include/ffmpeg) - if(SWRESAMPLE_INCLUDE_DIR) - include_directories("${SWRESAMPLE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${SWRESAMPLE_INCLUDE_DIR}") - endif(SWRESAMPLE_INCLUDE_DIR) - mark_as_advanced(FORCE SWRESAMPLE_LIBRARIES SWRESAMPLE_INCLUDE_DIR) - check_include_file("libswresample/swresample.h" HAVE_LIBSWRESAMPLE_SWRESAMPLE_H) - set(optlibsfound "${optlibsfound} SWResample") - else(SWRESAMPLE_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} SWResample") - endif(SWRESAMPLE_LIBRARIES) + find_library(AVRESAMPLE_LIBRARIES avresample) + if(AVRESAMPLE_LIBRARIES) + set(HAVE_LIBAVRESAMPLE 1) + list(APPEND ZM_BIN_LIBS "${AVRESAMPLE_LIBRARIES}") + find_path(AVRESAMPLE_INCLUDE_DIR "libavresample/avresample.h" /usr/include/ffmpeg) + if(AVRESAMPLE_INCLUDE_DIR) + include_directories("${AVRESAMPLE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${AVRESAMPLE_INCLUDE_DIR}") + endif(AVRESAMPLE_INCLUDE_DIR) + mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR) + check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H) + set(optlibsfound "${optlibsfound} AVResample") + else(AVRESAMPLE_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} AVResample") + endif(AVRESAMPLE_LIBRARIES) # Find the path to the ffmpeg executable find_program(FFMPEG_EXECUTABLE diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e33f0f0bb..6a68a8acd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY) # Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc) -set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp) +set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp) # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index b4d2ef741..7048746d9 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -1,4 +1,3 @@ -// // ZoneMinder Video Storage Implementation // Written by Chris Wiggins // http://chriswiggins.co.nz @@ -214,175 +213,14 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, audio_input_context = audio_input_stream->codec; if ( audio_input_context->codec_id != AV_CODEC_ID_AAC ) { -#ifdef HAVE_LIBSWRESAMPLE resample_context = NULL; - char error_buffer[256]; + static char error_buffer[256]; avcodec_string(error_buffer, sizeof(error_buffer), audio_input_context, 0 ); Debug(3, "Got something other than AAC (%s)", error_buffer ); audio_output_stream = NULL; - - audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); - if ( audio_output_codec ) { -Debug(2, "Have audio output codec"); - audio_output_stream = avformat_new_stream( oc, audio_output_codec ); - - audio_output_context = audio_output_stream->codec; - - if ( audio_output_context ) { - -Debug(2, "Have audio_output_context"); - AVDictionary *opts = NULL; - av_dict_set(&opts, "strict", "experimental", 0); - - /* put sample parameters */ - audio_output_context->bit_rate = audio_input_context->bit_rate; - audio_output_context->sample_rate = audio_input_context->sample_rate; - audio_output_context->channels = audio_input_context->channels; - audio_output_context->channel_layout = audio_input_context->channel_layout; - audio_output_context->sample_fmt = audio_input_context->sample_fmt; - //audio_output_context->refcounted_frames = 1; - - if (audio_output_codec->supported_samplerates) { - int found = 0; - for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) { - if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) { - found = 1; - break; - } - } - if ( found ) { - Debug(3, "Sample rate is good"); - } else { - audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0]; - Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] ); - } - } - - /* check that the encoder supports s16 pcm input */ - if (!check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt)) { - Debug( 3, "Encoder does not support sample format %s, setting to FLTP", - av_get_sample_fmt_name( audio_output_context->sample_fmt)); - audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP; - } - - //audio_output_stream->time_base = audio_input_stream->time_base; - audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate }; - - Debug(3, "Audio Time bases input stream (%d/%d) input codec: (%d/%d) output_stream (%d/%d) output codec (%d/%d)", - audio_input_stream->time_base.num, - audio_input_stream->time_base.den, - audio_input_context->time_base.num, - audio_input_context->time_base.den, - audio_output_stream->time_base.num, - audio_output_stream->time_base.den, - audio_output_context->time_base.num, - audio_output_context->time_base.den - ); - - ret = avcodec_open2(audio_output_context, audio_output_codec, &opts ); - if ( ret < 0 ) { - av_strerror(ret, error_buffer, sizeof(error_buffer)); - Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer ); - } else { - - Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d), refcounted_frames(%d)", - audio_output_context->bit_rate, - audio_output_context->sample_rate, - audio_output_context->channels, - audio_output_context->sample_fmt, - audio_output_context->channel_layout, - audio_output_context->frame_size, - audio_output_context->refcounted_frames - ); -#if 1 - /** Create the FIFO buffer based on the specified output sample format. */ - if (!(fifo = av_audio_fifo_alloc(audio_output_context->sample_fmt, - audio_output_context->channels, 1))) { - Error("Could not allocate FIFO\n"); - return; - } -#endif - output_frame_size = audio_output_context->frame_size; - /** Create a new frame to store the audio samples. */ - if (!(input_frame = zm_av_frame_alloc())) { - Error("Could not allocate input frame"); - return; - } - - /** Create a new frame to store the audio samples. */ - if (!(output_frame = zm_av_frame_alloc())) { - Error("Could not allocate output frame"); - av_frame_free(&input_frame); - return; - } - /** - * Create a resampler context for the conversion. - * Set the conversion parameters. - * Default channel layouts based on the number of channels - * are assumed for simplicity (they are sometimes not detected - * properly by the demuxer and/or decoder). - */ - resample_context = swr_alloc_set_opts(NULL, - av_get_default_channel_layout(audio_output_context->channels), - audio_output_context->sample_fmt, - audio_output_context->sample_rate, - av_get_default_channel_layout( audio_input_context->channels), - audio_input_context->sample_fmt, - audio_input_context->sample_rate, - 0, NULL); - if (!resample_context) { - Error( "Could not allocate resample context\n"); - return; - } - /** - * Perform a sanity check so that the number of converted samples is - * not greater than the number of samples to be converted. - * If the sample rates differ, this case has to be handled differently - */ - av_assert0(audio_output_context->sample_rate == audio_input_context->sample_rate); - /** Open the resampler with the specified parameters. */ - if ((ret = swr_init(resample_context)) < 0) { - Error( "Could not open resample context\n"); - swr_free(&resample_context); - return; - } - /** - * Allocate as many pointers as there are audio channels. - * Each pointer will later point to the audio samples of the corresponding - * channels (although it may be NULL for interleaved formats). - */ - if (!( converted_input_samples = (uint8_t *)calloc( audio_output_context->channels, sizeof(*converted_input_samples))) ) { - Error( "Could not allocate converted input sample pointers\n"); - return; - } - /** - * Allocate memory for the samples of all channels in one consecutive - * block for convenience. - */ - if ((ret = av_samples_alloc( &converted_input_samples, NULL, - audio_output_context->channels, - audio_output_context->frame_size, - audio_output_context->sample_fmt, 0)) < 0) { - Error( "Could not allocate converted input samples (error '%s')\n", - av_make_error_string(ret).c_str() ); - - av_freep(converted_input_samples); - free(converted_input_samples); - return; - } - Debug(2, "Success opening AAC codec"); - } - av_dict_free(&opts); - } else { - Error( "could not allocate codec context for AAC\n"); - } - } else { - Error( "could not find codec for AAC\n"); + if ( ! setup_resampler() ) { + return; } -#else - Error("Not built with libswresample library. Cannot do audio conversion to AAC"); - audio_output_stream = NULL; -#endif } else { Debug(3, "Got AAC" ); @@ -390,32 +228,32 @@ Debug(2, "Have audio_output_context"); if ( ! audio_output_stream ) { Error("Unable to create audio out stream\n"); audio_output_stream = NULL; - } - audio_output_context = audio_output_stream->codec; - - ret = avcodec_copy_context(audio_output_context, audio_input_context); - if (ret < 0) { - Fatal("Unable to copy audio context %s\n", av_make_error_string(ret).c_str()); - } - audio_output_context->codec_tag = 0; - if ( audio_output_context->channels > 1 ) { - Warning("Audio isn't mono, changing it."); - audio_output_context->channels = 1; } else { - Debug(3, "Audio is mono"); - } + audio_output_context = audio_output_stream->codec; + + ret = avcodec_copy_context(audio_output_context, audio_input_context); + if (ret < 0) { + Error("Unable to copy audio context %s\n", av_make_error_string(ret).c_str()); + audio_output_stream = NULL; + } else { + audio_output_context->codec_tag = 0; + if ( audio_output_context->channels > 1 ) { + Warning("Audio isn't mono, changing it."); + audio_output_context->channels = 1; + } else { + Debug(3, "Audio is mono"); + } + } + } // end if audio_output_stream } // end if is AAC -if ( audio_output_stream ) { - if (oc->oformat->flags & AVFMT_GLOBALHEADER) { - audio_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER; - } + if ( audio_output_stream ) { + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + audio_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER; + } } - } else { - Debug(3, "No Audio output stream"); - audio_output_stream = NULL; - } + } // end if audio_input_stream /* open the output file, if needed */ if (!(output_format->flags & AVFMT_NOFILE)) { @@ -529,8 +367,242 @@ Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts avformat_free_context(oc); #ifdef HAVE_LIBSWRESAMPLE - if ( resample_context ) - swr_free( &resample_context ); + //if ( resample_context ) + //swr_free( &resample_context ); +#endif +} + +bool VideoStore::setup_resampler() { +#ifdef HAVE_LIBSWRESAMPLE + static char error_buffer[256]; + + audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); + if ( ! audio_output_codec ) { + Error("Could not find codec for AAC"); + return false; + } + Debug(2, "Have audio output codec"); + + audio_output_stream = avformat_new_stream( oc, audio_output_codec ); + audio_output_context = audio_output_stream->codec; + + if ( ! audio_output_context ) { + Error( "could not allocate codec context for AAC\n"); + audio_output_stream = NULL; + return false; + } + + Debug(2, "Have audio_output_context"); + + AVDictionary *opts = NULL; + av_dict_set(&opts, "strict", "experimental", 0); + + /* put sample parameters */ + audio_output_context->bit_rate = audio_input_context->bit_rate; + audio_output_context->sample_rate = audio_input_context->sample_rate; + audio_output_context->channels = audio_input_context->channels; + audio_output_context->channel_layout = audio_input_context->channel_layout; + audio_output_context->sample_fmt = audio_input_context->sample_fmt; + //audio_output_context->refcounted_frames = 1; + + if (audio_output_codec->supported_samplerates) { + int found = 0; + for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) { + if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) { + found = 1; + break; + } + } + if ( found ) { + Debug(3, "Sample rate is good"); + } else { + audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0]; + Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] ); + } + } + + /* check that the encoder supports s16 pcm input */ + if (!check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt)) { + Debug( 3, "Encoder does not support sample format %s, setting to FLTP", + av_get_sample_fmt_name( audio_output_context->sample_fmt)); + audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP; + } + + //audio_output_stream->time_base = audio_input_stream->time_base; + audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate }; + + Debug(3, "Audio Time bases input stream (%d/%d) input codec: (%d/%d) output_stream (%d/%d) output codec (%d/%d)", + audio_input_stream->time_base.num, + audio_input_stream->time_base.den, + audio_input_context->time_base.num, + audio_input_context->time_base.den, + audio_output_stream->time_base.num, + audio_output_stream->time_base.den, + audio_output_context->time_base.num, + audio_output_context->time_base.den + ); + + ret = avcodec_open2(audio_output_context, audio_output_codec, &opts ); + av_dict_free(&opts); + if ( ret < 0 ) { + av_strerror(ret, error_buffer, sizeof(error_buffer)); + Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer ); + audio_output_codec = NULL; + audio_output_context = NULL; + audio_output_stream = NULL; + return false; + } + + Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d), refcounted_frames(%d)", + audio_output_context->bit_rate, + audio_output_context->sample_rate, + audio_output_context->channels, + audio_output_context->sample_fmt, + audio_output_context->channel_layout, + audio_output_context->frame_size, + audio_output_context->refcounted_frames + ); +#if 1 + /** Create the FIFO buffer based on the specified output sample format. */ + if (!(fifo = av_audio_fifo_alloc(audio_output_context->sample_fmt, + audio_output_context->channels, 1))) { + Error("Could not allocate FIFO\n"); + return false; + } +#endif + output_frame_size = audio_output_context->frame_size; + /** Create a new frame to store the audio samples. */ + if (!(input_frame = zm_av_frame_alloc())) { + Error("Could not allocate input frame"); + return false; + } + + /** Create a new frame to store the audio samples. */ + if (!(output_frame = zm_av_frame_alloc())) { + Error("Could not allocate output frame"); + av_frame_free(&input_frame); + return false; + } + +#if 0 + /** + * Create a resampler context for the conversion. + * Set the conversion parameters. + * Default channel layouts based on the number of channels + * are assumed for simplicity (they are sometimes not detected + * properly by the demuxer and/or decoder). + */ + resample_context = swr_alloc_set_opts(NULL, + av_get_default_channel_layout(audio_output_context->channels), + audio_output_context->sample_fmt, + audio_output_context->sample_rate, + av_get_default_channel_layout( audio_input_context->channels), + audio_input_context->sample_fmt, + audio_input_context->sample_rate, + 0, NULL); + + if (!resample_context) { + Error( "Could not allocate resample context\n"); + return; + } + /** + * Perform a sanity check so that the number of converted samples is + * not greater than the number of samples to be converted. + * If the sample rates differ, this case has to be handled differently + */ + av_assert0(audio_output_context->sample_rate == audio_input_context->sample_rate); + /** Open the resampler with the specified parameters. */ + if ((ret = swr_init(resample_context)) < 0) { + Error( "Could not open resample context\n"); + swr_free(&resample_context); + return; + } +#else + // Setup the audio resampler + resample_context = avresample_alloc_context(); + if (!resample_context) { + Error( "Could not allocate resample context\n"); + return false; + } + + // Some formats (i.e. WAV) do not produce the proper channel layout + if ( audio_input_context->channel_layout == 0 ) { + Error( "Could not allocate resample context channgel_layout\n"); + //av_opt_set_int( resample_context, "in_channel_layout", av_get_channel_layout( m_profile->channels == 1 ? "mono" : "stereo" ), 0 ); + } else { + av_opt_set_int( resample_context, "in_channel_layout", audio_input_context->channel_layout, 0 ); + } + + av_opt_set_int( resample_context, "in_sample_fmt", audio_input_context->sample_fmt, 0); + av_opt_set_int( resample_context, "in_sample_rate", audio_input_context->sample_rate, 0); + av_opt_set_int( resample_context, "in_channels", audio_input_context->channels,0); + av_opt_set_int( resample_context, "out_channel_layout", audio_output_context->channel_layout, 0); + av_opt_set_int( resample_context, "out_sample_fmt", audio_output_context->sample_fmt, 0); + av_opt_set_int( resample_context, "out_sample_rate", audio_output_context->sample_rate, 0); + av_opt_set_int( resample_context, "out_channels", audio_output_context->channels, 0); + + ret = avresample_open( resample_context ); + if ( ret < 0 ) { + Error( "Could not open resample context\n"); + return false; + } + +#if 0 + /** + * Allocate as many pointers as there are audio channels. + * Each pointer will later point to the audio samples of the corresponding + * channels (although it may be NULL for interleaved formats). + */ + if (!( converted_input_samples = (uint8_t *)calloc( audio_output_context->channels, sizeof(*converted_input_samples))) ) { + Error( "Could not allocate converted input sample pointers\n"); + return; + } + /** + * Allocate memory for the samples of all channels in one consecutive + * block for convenience. + */ + if ((ret = av_samples_alloc( &converted_input_samples, NULL, + audio_output_context->channels, + audio_output_context->frame_size, + audio_output_context->sample_fmt, 0)) < 0) { + Error( "Could not allocate converted input samples (error '%s')\n", + av_make_error_string(ret).c_str() ); + + av_freep(converted_input_samples); + free(converted_input_samples); + return; + } +#endif + + output_frame->nb_samples = audio_output_context->frame_size; + output_frame->format = audio_output_context->sample_fmt; + output_frame->channel_layout = audio_output_context->channel_layout; + + // The codec gives us the frame size, in samples, we calculate the size of the samples buffer in bytes + unsigned int audioSampleBuffer_size = av_samples_get_buffer_size( NULL, audio_output_context->channels, audio_output_context->frame_size, audio_output_context->sample_fmt, 0 ); + converted_input_samples = (uint8_t*) av_malloc( audioSampleBuffer_size ); + + if ( !converted_input_samples ) { + Error( "Could not allocate converted input sample pointers\n"); + return false; + } + + // Setup the data pointers in the AVFrame + if ( avcodec_fill_audio_frame( + output_frame, + audio_output_context->channels, + audio_output_context->sample_fmt, + (const uint8_t*) converted_input_samples, + audioSampleBuffer_size, 0 ) < 0 ) { + Error( "Could not allocate converted input sample pointers\n"); + return false; + } + +#endif +return true; +#else +Error("Not built with libswresample library. Cannot do audio conversion to AAC"); +return false; #endif } @@ -634,47 +706,47 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) { #if 0 if (video_output_context->codec_type == AVMEDIA_TYPE_VIDEO && (output_format->flags & AVFMT_RAWPICTURE)) { - AVPicture pict; -Debug(3, "video and RAWPICTURE"); + AVPicture pict; + Debug(3, "video and RAWPICTURE"); /* store AVPicture in AVPacket, as expected by the output format */ avpicture_fill(&pict, opkt.data, video_output_context->pix_fmt, video_output_context->width, video_output_context->height, 0); - av_image_fill_arrays( - opkt.data = (uint8_t *)&pict; - opkt.size = sizeof(AVPicture); - opkt.flags |= AV_PKT_FLAG_KEY; - } else { -Debug(4, "Not video and RAWPICTURE"); - } + av_image_fill_arrays( + opkt.data = (uint8_t *)&pict; + opkt.size = sizeof(AVPicture); + opkt.flags |= AV_PKT_FLAG_KEY; + } else { + Debug(4, "Not video and RAWPICTURE"); + } #endif - AVPacket safepkt; - memcpy(&safepkt, &opkt, sizeof(AVPacket)); + AVPacket safepkt; + memcpy(&safepkt, &opkt, sizeof(AVPacket)); - if ((opkt.data == NULL)||(opkt.size < 1)) { - Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ ); - dumpPacket( ipkt); - dumpPacket(&opkt); + if ((opkt.data == NULL)||(opkt.size < 1)) { + Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ ); + dumpPacket( ipkt); + dumpPacket(&opkt); - } else if ((previous_dts > 0) && (previous_dts > opkt.dts)) { - Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, previous_dts, opkt.dts); - previous_dts = opkt.dts; - dumpPacket(&opkt); + } else if ((previous_dts > 0) && (previous_dts > opkt.dts)) { + Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, previous_dts, opkt.dts); + previous_dts = opkt.dts; + dumpPacket(&opkt); - } else { + } else { - previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance - previous_pts = opkt.pts; - ret = av_interleaved_write_frame(oc, &opkt); - if(ret<0){ - // There's nothing we can really do if the frame is rejected, just drop it and get on with the next - Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret)); - dumpPacket(&safepkt); - } - } + previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance + previous_pts = opkt.pts; + ret = av_interleaved_write_frame(oc, &opkt); + if(ret<0){ + // There's nothing we can really do if the frame is rejected, just drop it and get on with the next + Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret)); + dumpPacket(&safepkt); + } + } - zm_av_packet_unref(&opkt); + zm_av_packet_unref(&opkt); - return 0; + return 0; } @@ -693,7 +765,7 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { Debug(5, "after init packet" ); #if 1 - //Scale the PTS of the outgoing packet to be the correct time base + //Scale the PTS of the outgoing packet to be the correct time base if ( ipkt->pts != AV_NOPTS_VALUE ) { if ( !audio_last_pts ) { opkt.pts = 0; @@ -739,10 +811,10 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { Debug(1,"opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts ); opkt.dts = opkt.pts; } - //opkt.pts = AV_NOPTS_VALUE; - //opkt.dts = AV_NOPTS_VALUE; + //opkt.pts = AV_NOPTS_VALUE; + //opkt.dts = AV_NOPTS_VALUE; - // I wonder if we could just use duration instead of all the hoop jumping above? + // I wonder if we could just use duration instead of all the hoop jumping above? opkt.duration = av_rescale_q(ipkt->duration, audio_input_stream->time_base, audio_output_stream->time_base); #else #endif @@ -751,48 +823,48 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { opkt.pos = -1; opkt.flags = ipkt->flags; opkt.stream_index = ipkt->stream_index; -Debug(2, "Stream index is %d", opkt.stream_index ); + Debug(2, "Stream index is %d", opkt.stream_index ); if ( audio_output_codec ) { #ifdef HAVE_LIBSWRESAMPLE - // Need to re-encode + // Need to re-encode #if 0 - ret = avcodec_send_packet( audio_input_context, ipkt ); - if ( ret < 0 ) { - Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); - return 0; - } + ret = avcodec_send_packet( audio_input_context, ipkt ); + if ( ret < 0 ) { + Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); + return 0; + } - ret = avcodec_receive_frame( audio_input_context, input_frame ); - if ( ret < 0 ) { - Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str()); - return 0; - } -Debug(2, "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d) refd(%d)", -input_frame->nb_samples, -input_frame->format, -input_frame->sample_rate, -input_frame->channel_layout, -audio_output_context->refcounted_frames -); + ret = avcodec_receive_frame( audio_input_context, input_frame ); + if ( ret < 0 ) { + Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str()); + return 0; + } + Debug(2, "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d) refd(%d)", + input_frame->nb_samples, + input_frame->format, + input_frame->sample_rate, + input_frame->channel_layout, + audio_output_context->refcounted_frames + ); - ret = avcodec_send_frame( audio_output_context, input_frame ); - if ( ret < 0 ) { + ret = avcodec_send_frame( audio_output_context, input_frame ); + if ( ret < 0 ) { + av_frame_unref( input_frame ); + Error("avcodec_send_frame fail(%d), %s codec is open(%d) is_encoder(%d)", ret, av_make_error_string(ret).c_str(), + avcodec_is_open( audio_output_context ), + av_codec_is_encoder( audio_output_context->codec) + ); + return 0; + } + ret = avcodec_receive_packet( audio_output_context, &opkt ); + if ( ret < 0 ) { + av_frame_unref( input_frame ); + Error("avcodec_receive_packet fail %s", av_make_error_string(ret).c_str()); + return 0; + } av_frame_unref( input_frame ); - Error("avcodec_send_frame fail(%d), %s codec is open(%d) is_encoder(%d)", ret, av_make_error_string(ret).c_str(), -avcodec_is_open( audio_output_context ), -av_codec_is_encoder( audio_output_context->codec) -); - return 0; - } - ret = avcodec_receive_packet( audio_output_context, &opkt ); - if ( ret < 0 ) { - av_frame_unref( input_frame ); - Error("avcodec_receive_packet fail %s", av_make_error_string(ret).c_str()); - return 0; - } - av_frame_unref( input_frame ); #else @@ -803,13 +875,13 @@ av_codec_is_encoder( audio_output_context->codec) * to flush it. */ if ((ret = avcodec_decode_audio4(audio_input_context, input_frame, - &data_present, ipkt)) < 0) { - Error( "Could not decode frame (error '%s')\n", - av_make_error_string(ret).c_str()); - dumpPacket( ipkt ); - av_frame_free(&input_frame); - zm_av_packet_unref(&opkt); - return 0; + &data_present, ipkt)) < 0) { + Error( "Could not decode frame (error '%s')\n", + av_make_error_string(ret).c_str()); + dumpPacket( ipkt ); + av_frame_free(&input_frame); + zm_av_packet_unref(&opkt); + return 0; } if ( ! data_present ) { Debug(2, "Not ready to transcode a frame yet."); @@ -820,7 +892,33 @@ av_codec_is_encoder( audio_output_context->codec) int frame_size = input_frame->nb_samples; Debug(4, "Frame size: %d", frame_size ); +#if 1 +// Resample the input into the audioSampleBuffer until we proceed the whole decoded data + if ( (ret = avresample_convert( resample_context, + NULL, + 0, + 0, + input_frame->data, + 0, + input_frame->nb_samples )) < 0 ) + { + Error( "Could not resample frame (error '%s')\n", + av_make_error_string(ret).c_str()); + return 0; + } + if ( avresample_available( resample_context ) < output_frame->nb_samples ) { + Debug(1, "No enough samples yet"); + return 0; + } +// Read a frame audio data from the resample fifo + if ( avresample_read( resample_context, output_frame->data, output_frame->nb_samples ) != output_frame->nb_samples ) + { + Warning( "Error reading resampled audio: " ); + return 0; + } + +#else Debug(4, "About to convert"); /** Convert the samples using the resampler. */ @@ -877,14 +975,15 @@ av_codec_is_encoder( audio_output_context->codec) Error( "Could not read data from FIFO\n"); return 0; } +#endif /** Set a timestamp based on the sample rate for the container. */ output_frame->pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base ); - // convert the packet to the codec timebase from the stream timebase -Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts, -av_frame_get_best_effort_timestamp(output_frame) - ); + // convert the packet to the codec timebase from the stream timebase + Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts, + av_frame_get_best_effort_timestamp(output_frame) + ); /** * Encode the audio frame and store it in the temporary packet. * The output audio stream encoder is used to do this. @@ -901,24 +1000,24 @@ av_frame_get_best_effort_timestamp(output_frame) zm_av_packet_unref(&opkt); return 0; } - -Debug(2, "opkt dts (%d) pts(%d) duration:(%d)", opkt.dts, opkt.pts, opkt.duration ); + + Debug(2, "opkt dts (%d) pts(%d) duration:(%d)", opkt.dts, opkt.pts, opkt.duration ); // Convert tb from code back to stream //av_packet_rescale_ts(&opkt, audio_output_context->time_base, audio_output_stream->time_base); -if (opkt.pts != AV_NOPTS_VALUE) { - opkt.pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base); -} - if ( opkt.dts != AV_NOPTS_VALUE) - opkt.dts = av_rescale_q( opkt.dts, audio_output_context->time_base, audio_output_stream->time_base); - if ( opkt.duration > 0) - opkt.duration = av_rescale_q( opkt.duration, audio_output_context->time_base, audio_output_stream->time_base); -Debug(2, "opkt dts (%d) pts(%d) duration:(%d) pos(%d) ", opkt.dts, opkt.pts, opkt.duration, opkt.pos ); + if (opkt.pts != AV_NOPTS_VALUE) { + opkt.pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base); + } + if ( opkt.dts != AV_NOPTS_VALUE) + opkt.dts = av_rescale_q( opkt.dts, audio_output_context->time_base, audio_output_stream->time_base); + if ( opkt.duration > 0) + opkt.duration = av_rescale_q( opkt.duration, audio_output_context->time_base, audio_output_stream->time_base); + Debug(2, "opkt dts (%d) pts(%d) duration:(%d) pos(%d) ", opkt.dts, opkt.pts, opkt.duration, opkt.pos ); -//opkt.dts = AV_NOPTS_VALUE; - + //opkt.dts = AV_NOPTS_VALUE; + #endif #endif diff --git a/src/zm_videostore.h b/src/zm_videostore.h index b9c68bd4e..43f508a6b 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -8,6 +8,9 @@ extern "C" { #ifdef HAVE_LIBSWRESAMPLE #include "libswresample/swresample.h" #endif +#ifdef HAVE_LIBAVRESAMPLE +#include "libavresample/avresample.h" +#endif } #if HAVE_LIBAVCODEC @@ -44,7 +47,10 @@ private: AVAudioFifo *fifo; int output_frame_size; #ifdef HAVE_LIBSWRESAMPLE - SwrContext *resample_context = NULL; + //SwrContext *resample_context = NULL; +#endif +#ifdef HAVE_LIBAVRESAMPLE +AVAudioResampleContext* resample_context; #endif uint8_t *converted_input_samples = NULL; @@ -66,6 +72,8 @@ private: int64_t filter_in_rescale_delta_last; + bool setup_resampler(); + public: VideoStore(const char *filename_in, const char *format_in, AVStream *video_input_stream, AVStream *audio_input_stream, int64_t nStartTime, Monitor * p_monitor ); ~VideoStore(); diff --git a/zoneminder-config.cmake b/zoneminder-config.cmake index 05da91ff0..3b35684b5 100644 --- a/zoneminder-config.cmake +++ b/zoneminder-config.cmake @@ -53,8 +53,8 @@ #cmakedefine HAVE_LIBAVUTIL_MATHEMATICS_H 1 #cmakedefine HAVE_LIBSWSCALE 1 #cmakedefine HAVE_LIBSWSCALE_SWSCALE_H 1 -#cmakedefine HAVE_LIBSWRESAMPLE 1 -#cmakedefine HAVE_LIBSWRESAMPLE_SWRESAMPLE_H 1 +#cmakedefine HAVE_LIBAVRESAMPLE 1 +#cmakedefine HAVE_LIBAVRESAMPLE_AVRESAMPLE_H 1 #cmakedefine HAVE_LIBVLC 1 #cmakedefine HAVE_VLC_VLC_H 1 #cmakedefine HAVE_LIBX264 1 From e4a20caf24a2530ce572070fc97a1997f0513614 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 10 Apr 2017 21:55:11 -0400 Subject: [PATCH 21/39] put quotes around --- utils/do_debian_package.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index de7b31178..84bd7acf0 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -139,10 +139,10 @@ cd "$DIRECTORY.orig"; git submodule init git submodule update --init --recursive -if [ $DISTRO == "trusty" ] || [ $DISTRO == "precise" ]; then +if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then ln -sf distros/ubuntu1204 debian else - if [ $DISTRO == "wheezy" ]; then + if [ "$DISTRO" == "wheezy" ]; then ln -sf distros/debian debian else ln -sf distros/ubuntu1604 debian From a58259c60e4143656e444eccbb8dc89b6eff5644 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 10 Apr 2017 21:55:52 -0400 Subject: [PATCH 22/39] include other libjpeg alternatives. Include a libpcre dependency --- distros/ubuntu1604/control | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index 2c4fd3b95..31074f2f4 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -13,12 +13,12 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libgcrypt-dev ,libcurl4-gnutls-dev ,libgnutls-openssl-dev - ,libjpeg-dev + , libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev ,libmysqlclient-dev ,libpcre3-dev ,libpolkit-gobject-1-dev ,libv4l-dev (>= 0.8.3) [!hurd-any] - ,libvlc-dev + ,libvlc-dev, ,libdate-manip-perl ,libdbd-mysql-perl ,libphp-serialization-perl @@ -65,6 +65,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,policykit-1 ,rsyslog | system-log-daemon ,zip + ,libprce3 Recommends: ${misc:Recommends} ,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm ,mysql-server | virtual-mysql-server From 42e94d1a7c5e488dbdbc2026408ad5e6ff180e7f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 10 Apr 2017 21:57:31 -0400 Subject: [PATCH 23/39] contains the MonitorStream class --- src/zm_monitorstream.cpp | 803 +++++++++++++++++++++++++++++++++++++++ src/zm_monitorstream.h | 77 ++++ 2 files changed, 880 insertions(+) create mode 100644 src/zm_monitorstream.cpp create mode 100644 src/zm_monitorstream.h diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp new file mode 100644 index 000000000..07da05811 --- /dev/null +++ b/src/zm_monitorstream.cpp @@ -0,0 +1,803 @@ +// +// ZoneMinder Monitor Class Implementation, $Date$, $Revision$ +// Copyright (C) 2001-2008 Philip Coombes +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#include "zm.h" +#include "zm_db.h" +#include "zm_time.h" +#include "zm_mpeg.h" +#include "zm_signal.h" +#include "zm_monitor.h" +#include "zm_monitorstream.h" +#include +#include + +bool MonitorStream::checkSwapPath( const char *path, bool create_path ) { + + struct stat stat_buf; + if ( stat( path, &stat_buf ) < 0 ) { + if ( create_path && errno == ENOENT ) { + Debug( 3, "Swap path '%s' missing, creating", path ); + if ( mkdir( path, 0755 ) ) { + Error( "Can't mkdir %s: %s", path, strerror(errno)); + return( false ); + } + if ( stat( path, &stat_buf ) < 0 ) { + Error( "Can't stat '%s': %s", path, strerror(errno) ); + return( false ); + } + } else { + Error( "Can't stat '%s': %s", path, strerror(errno) ); + return( false ); + } + } + if ( !S_ISDIR(stat_buf.st_mode) ) { + Error( "Swap image path '%s' is not a directory", path ); + return( false ); + } + + uid_t uid = getuid(); + gid_t gid = getgid(); + + mode_t mask = 0; + if ( uid == stat_buf.st_uid ) { + // If we are the owner + mask = 00700; + } else if ( gid == stat_buf.st_gid ) { + // If we are in the owner group + mask = 00070; + } else { + // We are neither the owner nor in the group + mask = 00007; + } + + if ( (stat_buf.st_mode & mask) != mask ) { + Error( "Insufficient permissions on swap image path '%s'", path ); + return( false ); + } + return( true ); +} // end bool MonitorStream::checkSwapPath( const char *path, bool create_path ) + +void MonitorStream::processCommand( const CmdMsg *msg ) { + Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ); + // Check for incoming command + switch( (MsgCommand)msg->msg_data[0] ) { + case CMD_PAUSE : + { + Debug( 1, "Got PAUSE command" ); + + // Set paused flag + paused = true; + // Set delayed flag + delayed = true; + last_frame_sent = TV_2_FLOAT( now ); + break; + } + case CMD_PLAY : + { + Debug( 1, "Got PLAY command" ); + if ( paused ) { + // Clear paused flag + paused = false; + // Set delayed_play flag + delayed = true; + } + replay_rate = ZM_RATE_BASE; + break; + } + case CMD_VARPLAY : + { + Debug( 1, "Got VARPLAY command" ); + if ( paused ) { + // Clear paused flag + paused = false; + // Set delayed_play flag + delayed = true; + } + replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768; + break; + } + case CMD_STOP : + { + Debug( 1, "Got STOP command" ); + + // Clear paused flag + paused = false; + // Clear delayed_play flag + delayed = false; + break; + } + case CMD_FASTFWD : + { + Debug( 1, "Got FAST FWD command" ); + if ( paused ) { + // Clear paused flag + paused = false; + // Set delayed_play flag + delayed = true; + } + // Set play rate + switch ( replay_rate ) + { + case 2 * ZM_RATE_BASE : + replay_rate = 5 * ZM_RATE_BASE; + break; + case 5 * ZM_RATE_BASE : + replay_rate = 10 * ZM_RATE_BASE; + break; + case 10 * ZM_RATE_BASE : + replay_rate = 25 * ZM_RATE_BASE; + break; + case 25 * ZM_RATE_BASE : + case 50 * ZM_RATE_BASE : + replay_rate = 50 * ZM_RATE_BASE; + break; + default : + replay_rate = 2 * ZM_RATE_BASE; + break; + } + break; + } + case CMD_SLOWFWD : + { + Debug( 1, "Got SLOW FWD command" ); + // Set paused flag + paused = true; + // Set delayed flag + delayed = true; + // Set play rate + replay_rate = ZM_RATE_BASE; + // Set step + step = 1; + break; + } + case CMD_SLOWREV : + { + Debug( 1, "Got SLOW REV command" ); + // Set paused flag + paused = true; + // Set delayed flag + delayed = true; + // Set play rate + replay_rate = ZM_RATE_BASE; + // Set step + step = -1; + break; + } + case CMD_FASTREV : + { + Debug( 1, "Got FAST REV command" ); + if ( paused ) { + // Clear paused flag + paused = false; + // Set delayed_play flag + delayed = true; + } + // Set play rate + switch ( replay_rate ) { + case -2 * ZM_RATE_BASE : + replay_rate = -5 * ZM_RATE_BASE; + break; + case -5 * ZM_RATE_BASE : + replay_rate = -10 * ZM_RATE_BASE; + break; + case -10 * ZM_RATE_BASE : + replay_rate = -25 * ZM_RATE_BASE; + break; + case -25 * ZM_RATE_BASE : + case -50 * ZM_RATE_BASE : + replay_rate = -50 * ZM_RATE_BASE; + break; + default : + replay_rate = -2 * ZM_RATE_BASE; + break; + } + break; + } + case CMD_ZOOMIN : + { + x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; + y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; + Debug( 1, "Got ZOOM IN command, to %d,%d", x, y ); + switch ( zoom ) { + case 100: + zoom = 150; + break; + case 150: + zoom = 200; + break; + case 200: + zoom = 300; + break; + case 300: + zoom = 400; + break; + case 400: + default : + zoom = 500; + break; + } + break; + } + case CMD_ZOOMOUT : + { + Debug( 1, "Got ZOOM OUT command" ); + switch ( zoom ) { + case 500: + zoom = 400; + break; + case 400: + zoom = 300; + break; + case 300: + zoom = 200; + break; + case 200: + zoom = 150; + break; + case 150: + default : + zoom = 100; + break; + } + break; + } + case CMD_PAN : + { + x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; + y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; + Debug( 1, "Got PAN command, to %d,%d", x, y ); + break; + } + case CMD_SCALE : + { + scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; + Debug( 1, "Got SCALE command, to %d", scale ); + break; + } + case CMD_QUIT : + { + Info ("User initiated exit - CMD_QUIT"); + break; + } + case CMD_QUERY : + { + Debug( 1, "Got QUERY command, sending STATUS" ); + break; + } + default : + { + Error( "Got unexpected command %d", msg->msg_data[0] ); + break; + } + } + + struct { + int id; + int state; + double fps; + int buffer_level; + int rate; + double delay; + int zoom; + bool delayed; + bool paused; + bool enabled; + bool forced; + } status_data; + + status_data.id = monitor->Id(); + status_data.fps = monitor->GetFPS(); + status_data.state = monitor->shared_data->state; + if ( playback_buffer > 0 ) + status_data.buffer_level = (MOD_ADD( (temp_write_index-temp_read_index), 0, temp_image_buffer_count )*100)/temp_image_buffer_count; + else + status_data.buffer_level = 0; + status_data.delayed = delayed; + status_data.paused = paused; + status_data.rate = replay_rate; + status_data.delay = TV_2_FLOAT( now ) - TV_2_FLOAT( last_frame_timestamp ); + status_data.zoom = zoom; + //status_data.enabled = monitor->shared_data->active; + status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF; + status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON; + Debug( 2, "L:%d, D:%d, P:%d, R:%d, d:%.3f, Z:%d, E:%d F:%d", + status_data.buffer_level, + status_data.delayed, + status_data.paused, + status_data.rate, + status_data.delay, + status_data.zoom, + status_data.enabled, + status_data.forced + ); + + DataMsg status_msg; + status_msg.msg_type = MSG_DATA_WATCH; + memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) ); + int nbytes = 0; + if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) { + //if ( errno != EAGAIN ) + { + Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); + //exit( -1 ); + } + } + + // quit after sending a status, if this was a quit request + if ((MsgCommand)msg->msg_data[0]==CMD_QUIT) + exit(0); + + updateFrameRate( monitor->GetFPS() ); +} // end void MonitorStream::processCommand( const CmdMsg *msg ) + +bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) { + bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); + + if ( type != STREAM_JPEG ) + send_raw = false; + if ( !config.timestamp_on_capture && timestamp ) + send_raw = false; + + if ( !send_raw ) { + Image temp_image( filepath ); + + return( sendFrame( &temp_image, timestamp ) ); + } else { + int img_buffer_size = 0; + static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE]; + + FILE *fdj = NULL; + if ( (fdj = fopen( filepath, "r" )) ) { + img_buffer_size = fread( img_buffer, 1, sizeof(img_buffer), fdj ); + fclose( fdj ); + } else { + Error( "Can't open %s: %s", filepath, strerror(errno) ); + return( false ); + } + + // Calculate how long it takes to actually send the frame + struct timeval frameStartTime; + gettimeofday( &frameStartTime, NULL ); + + fprintf( stdout, "--ZoneMinderFrame\r\n" ); + fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size ); + fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" ); + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { + if ( ! zm_terminate ) + Error( "Unable to send stream frame: %s", strerror(errno) ); + return( false ); + } + fprintf( stdout, "\r\n\r\n" ); + fflush( stdout ); + + struct timeval frameEndTime; + gettimeofday( &frameEndTime, NULL ); + + int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); + if ( frameSendTime > 1000/maxfps ) { + maxfps /= 2; + Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); + } + + last_frame_sent = TV_2_FLOAT( now ); + + return( true ); + } + return( false ); +} + +bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) { + Image *send_image = prepareImage( image ); + if ( !config.timestamp_on_capture && timestamp ) + monitor->TimestampImage( send_image, timestamp ); + +#if HAVE_LIBAVCODEC + if ( type == STREAM_MPEG ) { + if ( !vid_stream ) { + vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() ); + fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() ); + vid_stream->OpenStream(); + } + static struct timeval base_time; + struct DeltaTimeval delta_time; + if ( !frame_count ) + base_time = *timestamp; + DELTA_TIMEVAL( delta_time, *timestamp, base_time, DT_PREC_3 ); + /* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta ); + } else +#endif // HAVE_LIBAVCODEC + { + static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; + + int img_buffer_size = 0; + unsigned char *img_buffer = temp_img_buffer; + + // Calculate how long it takes to actually send the frame + struct timeval frameStartTime; + gettimeofday( &frameStartTime, NULL ); + + fprintf( stdout, "--ZoneMinderFrame\r\n" ); + switch( type ) { + case STREAM_JPEG : + send_image->EncodeJpeg( img_buffer, &img_buffer_size ); + fprintf( stdout, "Content-Type: image/jpeg\r\n" ); + break; + case STREAM_RAW : + fprintf( stdout, "Content-Type: image/x-rgb\r\n" ); + img_buffer = (uint8_t*)send_image->Buffer(); + img_buffer_size = send_image->Size(); + break; + case STREAM_ZIP : + fprintf( stdout, "Content-Type: image/x-rgbz\r\n" ); + unsigned long zip_buffer_size; + send_image->Zip( img_buffer, &zip_buffer_size ); + img_buffer_size = zip_buffer_size; + break; + default : + Fatal( "Unexpected frame type %d", type ); + break; + } + fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { + if ( !zm_terminate ) + Error( "Unable to send stream frame: %s", strerror(errno) ); + return( false ); + } + fprintf( stdout, "\r\n\r\n" ); + fflush( stdout ); + + struct timeval frameEndTime; + gettimeofday( &frameEndTime, NULL ); + + int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); + if ( frameSendTime > 1000/maxfps ) { + maxfps /= 1.5; + Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); + } + } + last_frame_sent = TV_2_FLOAT( now ); + return( true ); +} // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) + +void MonitorStream::runStream() { + if ( type == STREAM_SINGLE ) { + // Not yet migrated over to stream class + SingleImage( scale ); + return; + } + + openComms(); + + checkInitialised(); + + updateFrameRate( monitor->GetFPS() ); + + if ( type == STREAM_JPEG ) + fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" ); + + int last_read_index = monitor->image_buffer_count; + + time_t stream_start_time; + time( &stream_start_time ); + + frame_count = 0; + + temp_image_buffer = 0; + temp_image_buffer_count = playback_buffer; + temp_read_index = temp_image_buffer_count; + temp_write_index = temp_image_buffer_count; + + char *swap_path = 0; + bool buffered_playback = false; + + // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id + const int max_swap_len_suffix = 15; + + int swap_path_length = strlen(config.path_swap) + 1; // +1 for NULL terminator + int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id() ) + 1; + int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey ) + 1; + int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; + + if ( connkey && playback_buffer > 0 ) { + + if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) { + Error( "Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX ); + } else { + swap_path = (char *)malloc( total_swap_path_length+max_swap_len_suffix ); + strncpy( swap_path, config.path_swap, swap_path_length ); + + Debug( 3, "Checking swap path folder: %s", swap_path ); + if ( checkSwapPath( swap_path, false ) ) { + // Append the subfolder name /zmswap-m{monitor-id} to the end of swap_path + int ndx = swap_path_length - 1; // Array index of the NULL terminator + snprintf( &(swap_path[ndx]), subfolder1_length, "/zmswap-m%d", monitor->Id() ); + + Debug( 4, "Checking swap path subfolder: %s", swap_path ); + if ( checkSwapPath( swap_path, true ) ) { + // Append the subfolder name /zmswap-q{connection key} to the end of swap_path + ndx = swap_path_length+subfolder1_length - 2; // Array index of the NULL terminator + snprintf( &(swap_path[ndx]), subfolder2_length, "/zmswap-q%06d", connkey ); + + Debug( 4, "Checking swap path subfolder: %s", swap_path ); + if ( checkSwapPath( swap_path, true ) ) { + buffered_playback = true; + } + } + } + + if ( !buffered_playback ) { + Error( "Unable to validate swap image path, disabling buffered playback" ); + } else { + Debug( 2, "Assigning temporary buffer" ); + temp_image_buffer = new SwapImage[temp_image_buffer_count]; + memset( temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count ); + Debug( 2, "Assigned temporary buffer" ); + } + } + } + + float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs) + while ( !zm_terminate ) { + bool got_command = false; + if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) { + break; + } + + gettimeofday( &now, NULL ); + + if ( connkey ) { + while(checkCommandQueue()) { + got_command = true; + } + } + + //bool frame_sent = false; + if ( buffered_playback && delayed ) { + if ( temp_read_index == temp_write_index ) { + // Go back to live viewing + Debug( 1, "Exceeded temporary streaming buffer" ); + // Clear paused flag + paused = false; + // Clear delayed_play flag + delayed = false; + replay_rate = ZM_RATE_BASE; + } else { + if ( !paused ) { + int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count ); + //Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index ); + SwapImage *swap_image = &temp_image_buffer[temp_index]; + + if ( !swap_image->valid ) { + paused = true; + delayed = true; + temp_read_index = MOD_ADD( temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count ); + } else { + //Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); + double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate; + double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; + + //Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); + // If the next frame is due + if ( actual_delta_time > expected_delta_time ) { + //Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); + if ( temp_index%frame_mod == 0 ) { + Debug( 2, "Sending delayed frame %d", temp_index ); + // Send the next frame + if ( ! sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) + zm_terminate = true; + memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); + //frame_sent = true; + } + temp_read_index = MOD_ADD( temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count ); + } + } + } else if ( step != 0 ) { + temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count ); + + SwapImage *swap_image = &temp_image_buffer[temp_read_index]; + + // Send the next frame + if ( !sendFrame( temp_image_buffer[temp_read_index].file_name, &temp_image_buffer[temp_read_index].timestamp ) ) + zm_terminate = true; + memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); + //frame_sent = true; + step = 0; + } else { + int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count ); + + double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; + if ( got_command || actual_delta_time > 5 ) { + // Send keepalive + Debug( 2, "Sending keepalive frame %d", temp_index ); + // Send the next frame + if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) + zm_terminate = true; + //frame_sent = true; + } + } + } + if ( temp_read_index == temp_write_index ) { + // Go back to live viewing + Warning( "Rewound over write index, resuming live play" ); + // Clear paused flag + paused = false; + // Clear delayed_play flag + delayed = false; + replay_rate = ZM_RATE_BASE; + } + } + if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) { + int index = monitor->shared_data->last_write_index%monitor->image_buffer_count; + last_read_index = monitor->shared_data->last_write_index; + //Debug( 1, "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer ); + if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { + if ( !paused && !delayed ) { + // Send the next frame + Monitor::Snapshot *snap = &monitor->image_buffer[index]; + + if ( !sendFrame( snap->image, snap->timestamp ) ) + zm_terminate = true; + memcpy( &last_frame_timestamp, snap->timestamp, sizeof(last_frame_timestamp) ); + //frame_sent = true; + + temp_read_index = temp_write_index; + } + } + if ( buffered_playback ) { + if ( monitor->shared_data->valid ) { + if ( monitor->image_buffer[index].timestamp->tv_sec ) { + int temp_index = temp_write_index%temp_image_buffer_count; + Debug( 2, "Storing frame %d", temp_index ); + if ( !temp_image_buffer[temp_index].valid ) { + snprintf( temp_image_buffer[temp_index].file_name, sizeof(temp_image_buffer[0].file_name), "%s/zmswap-i%05d.jpg", swap_path, temp_index ); + temp_image_buffer[temp_index].valid = true; + } + memcpy( &(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp) ); + monitor->image_buffer[index].image->WriteJpeg( temp_image_buffer[temp_index].file_name, config.jpeg_file_quality ); + temp_write_index = MOD_ADD( temp_write_index, 1, temp_image_buffer_count ); + if ( temp_write_index == temp_read_index ) { + // Go back to live viewing + Warning( "Exceeded temporary buffer, resuming live play" ); + // Clear paused flag + paused = false; + // Clear delayed_play flag + delayed = false; + replay_rate = ZM_RATE_BASE; + } + } else { + Warning( "Unable to store frame as timestamp invalid" ); + } + } else { + Warning( "Unable to store frame as shared memory invalid" ); + } + } + frame_count++; + } + usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); + if ( ttl ) { + if ( (now.tv_sec - stream_start_time) > ttl ) { + break; + } + } + if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) { + Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame ); + break; + } + } + if ( buffered_playback ) { + Debug( 1, "Cleaning swap files from %s", swap_path ); + struct stat stat_buf; + if ( stat( swap_path, &stat_buf ) < 0 ) { + if ( errno != ENOENT ) { + Error( "Can't stat '%s': %s", swap_path, strerror(errno) ); + } + } else if ( !S_ISDIR(stat_buf.st_mode) ) { + Error( "Swap image path '%s' is not a directory", swap_path ); + } else { + char glob_pattern[PATH_MAX] = ""; + + snprintf( glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path ); + glob_t pglob; + int glob_status = glob( glob_pattern, 0, 0, &pglob ); + if ( glob_status != 0 ) { + if ( glob_status < 0 ) { + Error( "Can't glob '%s': %s", glob_pattern, strerror(errno) ); + } else { + Debug( 1, "Can't glob '%s': %d", glob_pattern, glob_status ); + } + } else { + for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { + if ( unlink( pglob.gl_pathv[i] ) < 0 ) { + Error( "Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno) ); + } + } + } + globfree( &pglob ); + if ( rmdir( swap_path ) < 0 ) { + Error( "Can't rmdir '%s': %s", swap_path, strerror(errno) ); + } + } + } + if ( swap_path ) free( swap_path ); + closeComms(); +} + +void MonitorStream::SingleImage( int scale ) { + int img_buffer_size = 0; + static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; + Image scaled_image; + Monitor::Snapshot *snap = monitor->getSnapshot(); + Image *snap_image = snap->image; + + if ( scale != ZM_SCALE_BASE ) { + scaled_image.Assign( *snap_image ); + scaled_image.Scale( scale ); + snap_image = &scaled_image; + } + if ( !config.timestamp_on_capture ) { + monitor->TimestampImage( snap_image, snap->timestamp ); + } + snap_image->EncodeJpeg( img_buffer, &img_buffer_size ); + + fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size ); + fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" ); + fwrite( img_buffer, img_buffer_size, 1, stdout ); +} + +void MonitorStream::SingleImageRaw( int scale ) { + Image scaled_image; + Monitor::Snapshot *snap = monitor->getSnapshot(); + Image *snap_image = snap->image; + + if ( scale != ZM_SCALE_BASE ) { + scaled_image.Assign( *snap_image ); + scaled_image.Scale( scale ); + snap_image = &scaled_image; + } + if ( !config.timestamp_on_capture ) { + monitor->TimestampImage( snap_image, snap->timestamp ); + } + + fprintf( stdout, "Content-Length: %d\r\n", snap_image->Size() ); + fprintf( stdout, "Content-Type: image/x-rgb\r\n\r\n" ); + fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout ); +} + +void MonitorStream::SingleImageZip( int scale ) { + unsigned long img_buffer_size = 0; + static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; + Image scaled_image; + + Monitor::Snapshot *snap = monitor->getSnapshot(); + Image *snap_image = snap->image; + + if ( scale != ZM_SCALE_BASE ) { + scaled_image.Assign( *snap_image ); + scaled_image.Scale( scale ); + snap_image = &scaled_image; + } + if ( !config.timestamp_on_capture ) { + monitor->TimestampImage( snap_image, snap->timestamp ); + } + snap_image->Zip( img_buffer, &img_buffer_size ); + + fprintf( stdout, "Content-Length: %ld\r\n", img_buffer_size ); + fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" ); + fwrite( img_buffer, img_buffer_size, 1, stdout ); +} diff --git a/src/zm_monitorstream.h b/src/zm_monitorstream.h new file mode 100644 index 000000000..fb8e22a13 --- /dev/null +++ b/src/zm_monitorstream.h @@ -0,0 +1,77 @@ +// +// ZoneMinder MonitorStream Class Interfaces, $Date$, $Revision$ +// Copyright (C) 2001-2008 Philip Coombes +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#ifndef ZM_MONITORSTREAM_H +#define ZM_MONITORSTREAM_H + +#include "zm.h" +#include "zm_coord.h" +#include "zm_image.h" +#include "zm_utils.h" +#include "zm_monitor.h" + +class MonitorStream : public StreamBase { + protected: + typedef struct SwapImage { + bool valid; + struct timeval timestamp; + char file_name[PATH_MAX]; + } SwapImage; + + private: + SwapImage *temp_image_buffer; + int temp_image_buffer_count; + int temp_read_index; + int temp_write_index; + + protected: + time_t ttl; + + protected: + int playback_buffer; + bool delayed; + + int frame_count; + + protected: + bool checkSwapPath( const char *path, bool create_path ); + + bool sendFrame( const char *filepath, struct timeval *timestamp ); + bool sendFrame( Image *image, struct timeval *timestamp ); + void processCommand( const CmdMsg *msg ); + void SingleImage( int scale=100 ); + void SingleImageRaw( int scale=100 ); + void SingleImageZip( int scale=100 ); + + public: + MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) { + } + void setStreamBuffer( int p_playback_buffer ) { + playback_buffer = p_playback_buffer; + } + void setStreamTTL( time_t p_ttl ) { + ttl = p_ttl; + } + bool setStreamStart( int monitor_id ) { + return loadMonitor( monitor_id ); + } + void runStream(); +}; + +#endif // ZM_MONITORSTREAM_H From 161aaac3f2dbbfe8942a860d0916d877ab418d91 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 11 Apr 2017 10:39:43 -0400 Subject: [PATCH 24/39] cleanup swsresample stuff. resampling now works --- src/zm_videostore.cpp | 594 +++++++++++++++--------------------------- 1 file changed, 209 insertions(+), 385 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 7048746d9..b3da56865 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -128,50 +128,6 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, video_output_context->time_base.den ); -#if 0 - if ( video_input_context->sample_aspect_ratio.den && ( video_output_stream->sample_aspect_ratio.den != video_input_context->sample_aspect_ratio.den ) ) { - Warning("Fixing sample_aspect_ratio.den from (%d) to (%d)", video_output_stream->sample_aspect_ratio.den, video_input_context->sample_aspect_ratio.den ); - video_output_stream->sample_aspect_ratio.den = video_input_context->sample_aspect_ratio.den; - } else { - Debug(3, "aspect ratio denominator is (%d)", video_output_stream->sample_aspect_ratio.den ); - } - if ( video_input_context->sample_aspect_ratio.num && ( video_output_stream->sample_aspect_ratio.num != video_input_context->sample_aspect_ratio.num ) ) { - Warning("Fixing sample_aspect_ratio.num from video_output_stream(%d) to video_input_stream(%d)", video_output_stream->sample_aspect_ratio.num, video_input_context->sample_aspect_ratio.num ); - video_output_stream->sample_aspect_ratio.num = video_input_context->sample_aspect_ratio.num; - } else { - Debug(3, "aspect ratio numerator is (%d)", video_output_stream->sample_aspect_ratio.num ); - } - if ( video_output_context->codec_id != video_input_context->codec_id ) { - Warning("Fixing video_output_context->codec_id"); - video_output_context->codec_id = video_input_context->codec_id; - } - if ( ! video_output_context->time_base.num ) { - Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_context->time_base.num, video_output_context->time_base.den); - Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_stream->time_base.num, video_output_stream->time_base.den); - video_output_context->time_base.num = video_output_stream->time_base.num; - video_output_context->time_base.den = video_output_stream->time_base.den; - } - - if ( video_output_stream->sample_aspect_ratio.den != video_output_context->sample_aspect_ratio.den ) { - Warning("Fixingample_aspect_ratio.den"); - video_output_stream->sample_aspect_ratio.den = video_output_context->sample_aspect_ratio.den; - } - if ( video_output_stream->sample_aspect_ratio.num != video_input_context->sample_aspect_ratio.num ) { - Warning("Fixingample_aspect_ratio.num"); - video_output_stream->sample_aspect_ratio.num = video_input_context->sample_aspect_ratio.num; - } - if ( video_output_context->codec_id != video_input_context->codec_id ) { - Warning("Fixing video_output_context->codec_id"); - video_output_context->codec_id = video_input_context->codec_id; - } - if ( ! video_output_context->time_base.num ) { - Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_context->time_base.num, video_output_context->time_base.den); - Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_stream->time_base.num, video_output_stream->time_base.den); - video_output_context->time_base.num = video_output_stream->time_base.num; - video_output_context->time_base.den = video_output_stream->time_base.den; - } -#endif - // WHY? //video_output_context->codec_tag = 0; if (!video_output_context->codec_tag) { @@ -208,16 +164,16 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, audio_output_codec = NULL; audio_input_context = NULL; + audio_output_stream = NULL; + resample_context = NULL; if (audio_input_stream) { audio_input_context = audio_input_stream->codec; if ( audio_input_context->codec_id != AV_CODEC_ID_AAC ) { - resample_context = NULL; static char error_buffer[256]; avcodec_string(error_buffer, sizeof(error_buffer), audio_input_context, 0 ); Debug(3, "Got something other than AAC (%s)", error_buffer ); - audio_output_stream = NULL; if ( ! setup_resampler() ) { return; } @@ -290,16 +246,11 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, previous_dts = 0; filter_in_rescale_delta_last = AV_NOPTS_VALUE; - - // now - when streaming started - //startTime=av_gettime()-nStartTime;//oc->start_time; - //Info("VideoStore startTime=%d\n",startTime); } // VideoStore::VideoStore VideoStore::~VideoStore(){ if ( audio_output_codec ) { -Debug(1, "Have audio encoder, need to flush it's output" ); // Do we need to flush the outputs? I have no idea. AVPacket pkt; int got_packet; @@ -351,6 +302,10 @@ Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts } if (audio_output_stream) { avcodec_close(audio_output_context); + if ( resample_context ) { + avresample_close( resample_context ); + avresample_free( &resample_context ); + } } // WHen will be not using a file ? @@ -365,187 +320,142 @@ Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts /* free the stream */ avformat_free_context(oc); - -#ifdef HAVE_LIBSWRESAMPLE - //if ( resample_context ) - //swr_free( &resample_context ); -#endif } bool VideoStore::setup_resampler() { -#ifdef HAVE_LIBSWRESAMPLE - static char error_buffer[256]; +#ifdef HAVE_LIBAVRESAMPLE + static char error_buffer[256]; - audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); - if ( ! audio_output_codec ) { - Error("Could not find codec for AAC"); - return false; - } - Debug(2, "Have audio output codec"); + audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); + if ( ! audio_output_codec ) { + Error("Could not find codec for AAC"); + return false; + } + Debug(2, "Have audio output codec"); - audio_output_stream = avformat_new_stream( oc, audio_output_codec ); - audio_output_context = audio_output_stream->codec; + audio_output_stream = avformat_new_stream( oc, audio_output_codec ); + audio_output_context = audio_output_stream->codec; - if ( ! audio_output_context ) { - Error( "could not allocate codec context for AAC\n"); - audio_output_stream = NULL; - return false; - } + if ( ! audio_output_context ) { + Error( "could not allocate codec context for AAC\n"); + audio_output_stream = NULL; + return false; + } - Debug(2, "Have audio_output_context"); + Debug(2, "Have audio_output_context"); - AVDictionary *opts = NULL; - av_dict_set(&opts, "strict", "experimental", 0); + AVDictionary *opts = NULL; + av_dict_set(&opts, "strict", "experimental", 0); - /* put sample parameters */ - audio_output_context->bit_rate = audio_input_context->bit_rate; - audio_output_context->sample_rate = audio_input_context->sample_rate; - audio_output_context->channels = audio_input_context->channels; - audio_output_context->channel_layout = audio_input_context->channel_layout; - audio_output_context->sample_fmt = audio_input_context->sample_fmt; - //audio_output_context->refcounted_frames = 1; + /* put sample parameters */ + audio_output_context->bit_rate = audio_input_context->bit_rate; + audio_output_context->sample_rate = audio_input_context->sample_rate; + audio_output_context->channels = audio_input_context->channels; + audio_output_context->channel_layout = audio_input_context->channel_layout; + audio_output_context->sample_fmt = audio_input_context->sample_fmt; + //audio_output_context->refcounted_frames = 1; - if (audio_output_codec->supported_samplerates) { - int found = 0; - for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) { - if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) { - found = 1; - break; - } - } - if ( found ) { - Debug(3, "Sample rate is good"); - } else { - audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0]; - Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] ); + if (audio_output_codec->supported_samplerates) { + int found = 0; + for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) { + if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) { + found = 1; + break; } } - - /* check that the encoder supports s16 pcm input */ - if (!check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt)) { - Debug( 3, "Encoder does not support sample format %s, setting to FLTP", - av_get_sample_fmt_name( audio_output_context->sample_fmt)); - audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP; - } - - //audio_output_stream->time_base = audio_input_stream->time_base; - audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate }; - - Debug(3, "Audio Time bases input stream (%d/%d) input codec: (%d/%d) output_stream (%d/%d) output codec (%d/%d)", - audio_input_stream->time_base.num, - audio_input_stream->time_base.den, - audio_input_context->time_base.num, - audio_input_context->time_base.den, - audio_output_stream->time_base.num, - audio_output_stream->time_base.den, - audio_output_context->time_base.num, - audio_output_context->time_base.den - ); - - ret = avcodec_open2(audio_output_context, audio_output_codec, &opts ); - av_dict_free(&opts); - if ( ret < 0 ) { - av_strerror(ret, error_buffer, sizeof(error_buffer)); - Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer ); - audio_output_codec = NULL; - audio_output_context = NULL; - audio_output_stream = NULL; - return false; - } - - Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d), refcounted_frames(%d)", - audio_output_context->bit_rate, - audio_output_context->sample_rate, - audio_output_context->channels, - audio_output_context->sample_fmt, - audio_output_context->channel_layout, - audio_output_context->frame_size, - audio_output_context->refcounted_frames - ); -#if 1 - /** Create the FIFO buffer based on the specified output sample format. */ - if (!(fifo = av_audio_fifo_alloc(audio_output_context->sample_fmt, - audio_output_context->channels, 1))) { - Error("Could not allocate FIFO\n"); - return false; - } -#endif - output_frame_size = audio_output_context->frame_size; - /** Create a new frame to store the audio samples. */ - if (!(input_frame = zm_av_frame_alloc())) { - Error("Could not allocate input frame"); - return false; - } - - /** Create a new frame to store the audio samples. */ - if (!(output_frame = zm_av_frame_alloc())) { - Error("Could not allocate output frame"); - av_frame_free(&input_frame); - return false; - } - -#if 0 - /** - * Create a resampler context for the conversion. - * Set the conversion parameters. - * Default channel layouts based on the number of channels - * are assumed for simplicity (they are sometimes not detected - * properly by the demuxer and/or decoder). - */ - resample_context = swr_alloc_set_opts(NULL, - av_get_default_channel_layout(audio_output_context->channels), - audio_output_context->sample_fmt, - audio_output_context->sample_rate, - av_get_default_channel_layout( audio_input_context->channels), - audio_input_context->sample_fmt, - audio_input_context->sample_rate, - 0, NULL); - - if (!resample_context) { - Error( "Could not allocate resample context\n"); - return; - } - /** - * Perform a sanity check so that the number of converted samples is - * not greater than the number of samples to be converted. - * If the sample rates differ, this case has to be handled differently - */ - av_assert0(audio_output_context->sample_rate == audio_input_context->sample_rate); - /** Open the resampler with the specified parameters. */ - if ((ret = swr_init(resample_context)) < 0) { - Error( "Could not open resample context\n"); - swr_free(&resample_context); - return; - } -#else - // Setup the audio resampler - resample_context = avresample_alloc_context(); - if (!resample_context) { - Error( "Could not allocate resample context\n"); - return false; - } - - // Some formats (i.e. WAV) do not produce the proper channel layout - if ( audio_input_context->channel_layout == 0 ) { - Error( "Could not allocate resample context channgel_layout\n"); - //av_opt_set_int( resample_context, "in_channel_layout", av_get_channel_layout( m_profile->channels == 1 ? "mono" : "stereo" ), 0 ); + if ( found ) { + Debug(3, "Sample rate is good"); } else { - av_opt_set_int( resample_context, "in_channel_layout", audio_input_context->channel_layout, 0 ); + audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0]; + Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] ); } + } - av_opt_set_int( resample_context, "in_sample_fmt", audio_input_context->sample_fmt, 0); - av_opt_set_int( resample_context, "in_sample_rate", audio_input_context->sample_rate, 0); - av_opt_set_int( resample_context, "in_channels", audio_input_context->channels,0); - av_opt_set_int( resample_context, "out_channel_layout", audio_output_context->channel_layout, 0); - av_opt_set_int( resample_context, "out_sample_fmt", audio_output_context->sample_fmt, 0); - av_opt_set_int( resample_context, "out_sample_rate", audio_output_context->sample_rate, 0); - av_opt_set_int( resample_context, "out_channels", audio_output_context->channels, 0); + /* check that the encoder supports s16 pcm input */ + if (!check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt)) { + Debug( 3, "Encoder does not support sample format %s, setting to FLTP", + av_get_sample_fmt_name( audio_output_context->sample_fmt)); + audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP; + } - ret = avresample_open( resample_context ); - if ( ret < 0 ) { - Error( "Could not open resample context\n"); - return false; - } + //audio_output_stream->time_base = audio_input_stream->time_base; + audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate }; + + Debug(3, "Audio Time bases input stream (%d/%d) input codec: (%d/%d) output_stream (%d/%d) output codec (%d/%d)", + audio_input_stream->time_base.num, + audio_input_stream->time_base.den, + audio_input_context->time_base.num, + audio_input_context->time_base.den, + audio_output_stream->time_base.num, + audio_output_stream->time_base.den, + audio_output_context->time_base.num, + audio_output_context->time_base.den + ); + + ret = avcodec_open2(audio_output_context, audio_output_codec, &opts ); + av_dict_free(&opts); + if ( ret < 0 ) { + av_strerror(ret, error_buffer, sizeof(error_buffer)); + Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer ); + audio_output_codec = NULL; + audio_output_context = NULL; + audio_output_stream = NULL; + return false; + } + + Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d), refcounted_frames(%d)", + audio_output_context->bit_rate, + audio_output_context->sample_rate, + audio_output_context->channels, + audio_output_context->sample_fmt, + audio_output_context->channel_layout, + audio_output_context->frame_size, + audio_output_context->refcounted_frames + ); + + output_frame_size = audio_output_context->frame_size; + /** Create a new frame to store the audio samples. */ + if (!(input_frame = zm_av_frame_alloc())) { + Error("Could not allocate input frame"); + return false; + } + + /** Create a new frame to store the audio samples. */ + if (!(output_frame = zm_av_frame_alloc())) { + Error("Could not allocate output frame"); + av_frame_free(&input_frame); + return false; + } + + // Setup the audio resampler + resample_context = avresample_alloc_context(); + if ( ! resample_context ) { + Error( "Could not allocate resample context\n"); + return false; + } + + // Some formats (i.e. WAV) do not produce the proper channel layout + if ( audio_input_context->channel_layout == 0 ) { + Error( "Bad channel layout. Need to set it to mono.\n"); + av_opt_set_int( resample_context, "in_channel_layout", av_get_channel_layout( "mono" ), 0 ); + } else { + av_opt_set_int( resample_context, "in_channel_layout", audio_input_context->channel_layout, 0 ); + } + + av_opt_set_int( resample_context, "in_sample_fmt", audio_input_context->sample_fmt, 0); + av_opt_set_int( resample_context, "in_sample_rate", audio_input_context->sample_rate, 0); + av_opt_set_int( resample_context, "in_channels", audio_input_context->channels,0); + //av_opt_set_int( resample_context, "out_channel_layout", audio_output_context->channel_layout, 0); + av_opt_set_int( resample_context, "out_channel_layout", av_get_channel_layout( "mono" ), 0 ); + av_opt_set_int( resample_context, "out_sample_fmt", audio_output_context->sample_fmt, 0); + av_opt_set_int( resample_context, "out_sample_rate", audio_output_context->sample_rate, 0); + av_opt_set_int( resample_context, "out_channels", audio_output_context->channels, 0); + + ret = avresample_open( resample_context ); + if ( ret < 0 ) { + Error( "Could not open resample context\n"); + return false; + } #if 0 /** @@ -598,11 +508,10 @@ bool VideoStore::setup_resampler() { return false; } -#endif -return true; + return true; #else -Error("Not built with libswresample library. Cannot do audio conversion to AAC"); -return false; + Error("Not built with libavresample library. Cannot do audio conversion to AAC"); + return false; #endif } @@ -757,78 +666,10 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { Debug(1, "Called writeAudioFramePacket when no audio_output_stream"); return 0;//FIXME -ve return codes do not free packet in ffmpeg_camera at the moment } - /*if(!keyframeMessage) - return -1;*/ - //zm_dump_stream_format( oc, ipkt->stream_index, 0, 1 ); - av_init_packet(&opkt); - Debug(5, "after init packet" ); - -#if 1 - //Scale the PTS of the outgoing packet to be the correct time base - if ( ipkt->pts != AV_NOPTS_VALUE ) { - if ( !audio_last_pts ) { - opkt.pts = 0; - } else { - if ( audio_last_pts > ipkt->pts ) { - Debug(1, "Resetting audeo_start_pts from (%d) to (%d)", audio_last_pts, ipkt->pts ); - } - opkt.pts = previous_pts + av_rescale_q(ipkt->pts - audio_last_pts, audio_input_stream->time_base, audio_output_stream->time_base); - Debug(2, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, audio_last_pts ); - } - audio_last_pts = ipkt->pts; - } else { - Debug(2, "opkt.pts = undef"); - opkt.pts = AV_NOPTS_VALUE; - } - - //Scale the DTS of the outgoing packet to be the correct time base - if ( ! audio_last_dts ) { - opkt.dts = 0; - } else { - if( ipkt->dts == AV_NOPTS_VALUE ) { - // So if the input has no dts assigned... still need an output dts... so we use cur_dts? - - if ( audio_last_dts > audio_input_stream->cur_dts ) { - Debug(1, "Resetting audio_last_pts from (%d) to cur_dts (%d)", audio_last_dts, audio_input_stream->cur_dts ); - opkt.dts = previous_dts + av_rescale_q( audio_input_stream->cur_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); - } else { - opkt.dts = previous_dts + av_rescale_q( audio_input_stream->cur_dts - audio_last_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); - } - audio_last_dts = audio_input_stream->cur_dts; - Debug(2, "opkt.dts = %d from video_input_stream->cur_dts(%d) - last_dts(%d)", opkt.dts, audio_input_stream->cur_dts, audio_last_dts ); - } else { - if ( audio_last_dts > ipkt->dts ) { - Debug(1, "Resetting audio_last_dts from (%d) to (%d)", audio_last_dts, ipkt->dts ); - opkt.dts = previous_dts + av_rescale_q(ipkt->dts, audio_input_stream->time_base, audio_output_stream->time_base); - } else { - opkt.dts = previous_dts + av_rescale_q(ipkt->dts - audio_last_dts, audio_input_stream->time_base, audio_output_stream->time_base); - } - Debug(2, "opkt.dts = %d from ipkt->dts(%d) - last_dts(%d)", opkt.dts, ipkt->dts, audio_last_dts ); - } - } - if ( opkt.dts > opkt.pts ) { - Debug(1,"opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts ); - opkt.dts = opkt.pts; - } - //opkt.pts = AV_NOPTS_VALUE; - //opkt.dts = AV_NOPTS_VALUE; - - // I wonder if we could just use duration instead of all the hoop jumping above? - opkt.duration = av_rescale_q(ipkt->duration, audio_input_stream->time_base, audio_output_stream->time_base); -#else -#endif - - // pkt.pos: byte position in stream, -1 if unknown - opkt.pos = -1; - opkt.flags = ipkt->flags; - opkt.stream_index = ipkt->stream_index; - Debug(2, "Stream index is %d", opkt.stream_index ); if ( audio_output_codec ) { -#ifdef HAVE_LIBSWRESAMPLE - // Need to re-encode #if 0 ret = avcodec_send_packet( audio_input_context, ipkt ); if ( ret < 0 ) { @@ -892,98 +733,40 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { int frame_size = input_frame->nb_samples; Debug(4, "Frame size: %d", frame_size ); -#if 1 -// Resample the input into the audioSampleBuffer until we proceed the whole decoded data - if ( (ret = avresample_convert( resample_context, - NULL, - 0, - 0, - input_frame->data, - 0, - input_frame->nb_samples )) < 0 ) - { + // Resample the input into the audioSampleBuffer until we proceed the whole decoded data + if ( (ret = avresample_convert( resample_context, + NULL, + 0, + 0, + input_frame->data, + 0, + input_frame->nb_samples )) < 0 ) { Error( "Could not resample frame (error '%s')\n", av_make_error_string(ret).c_str()); return 0; - } + } - if ( avresample_available( resample_context ) < output_frame->nb_samples ) { - Debug(1, "No enough samples yet"); - return 0; - } -// Read a frame audio data from the resample fifo - if ( avresample_read( resample_context, output_frame->data, output_frame->nb_samples ) != output_frame->nb_samples ) - { - Warning( "Error reading resampled audio: " ); - return 0; - } - -#else - Debug(4, "About to convert"); - - /** Convert the samples using the resampler. */ - if ((ret = swr_convert(resample_context, - &converted_input_samples, frame_size, - (const uint8_t **)input_frame->extended_data , frame_size)) < 0) { - Error( "Could not convert input samples (error '%s')\n", - av_make_error_string(ret).c_str() - ); + if ( avresample_available( resample_context ) < output_frame->nb_samples ) { + Debug(1, "No enough samples yet"); return 0; } - Debug(4, "About to realloc"); - if ((ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + frame_size)) < 0) { - Error( "Could not reallocate FIFO to %d\n", av_audio_fifo_size(fifo) + frame_size ); - return 0; - } - /** Store the new samples in the FIFO buffer. */ - Debug(4, "About to write"); - if (av_audio_fifo_write(fifo, (void **)&converted_input_samples, frame_size) < frame_size) { - Error( "Could not write data to FIFO\n"); + // Read a frame audio data from the resample fifo + if ( avresample_read( resample_context, output_frame->data, output_frame->nb_samples ) != output_frame->nb_samples ) { + Warning( "Error reading resampled audio: " ); return 0; } - /** - * Set the frame's parameters, especially its size and format. - * av_frame_get_buffer needs this to allocate memory for the - * audio samples of the frame. - * Default channel layouts based on the number of channels - * are assumed for simplicity. - */ - output_frame->nb_samples = audio_output_context->frame_size; - output_frame->channel_layout = audio_output_context->channel_layout; - output_frame->channels = audio_output_context->channels; - output_frame->format = audio_output_context->sample_fmt; - output_frame->sample_rate = audio_output_context->sample_rate; - /** - * Allocate the samples of the created frame. This call will make - * sure that the audio frame can hold as many samples as specified. - */ - Debug(4, "getting buffer"); - if (( ret = av_frame_get_buffer( output_frame, 0)) < 0) { - Error( "Couldnt allocate output frame buffer samples (error '%s')", - av_make_error_string(ret).c_str() ); - Error("Frame: samples(%d) layout (%d) format(%d) rate(%d)", output_frame->nb_samples, - output_frame->channel_layout, output_frame->format , output_frame->sample_rate - ); - zm_av_packet_unref(&opkt); - return 0; - } - - Debug(4, "About to read"); - if (av_audio_fifo_read(fifo, (void **)output_frame->data, frame_size) < frame_size) { - Error( "Could not read data from FIFO\n"); - return 0; - } -#endif + av_init_packet(&opkt); + Debug(5, "after init packet" ); /** Set a timestamp based on the sample rate for the container. */ - output_frame->pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base ); + //output_frame->pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base ); // convert the packet to the codec timebase from the stream timebase - Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts, - av_frame_get_best_effort_timestamp(output_frame) - ); + //Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts, + //av_frame_get_best_effort_timestamp(output_frame) + //); /** * Encode the audio frame and store it in the temporary packet. * The output audio stream encoder is used to do this. @@ -1001,31 +784,72 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { return 0; } - - Debug(2, "opkt dts (%d) pts(%d) duration:(%d)", opkt.dts, opkt.pts, opkt.duration ); - - // Convert tb from code back to stream - //av_packet_rescale_ts(&opkt, audio_output_context->time_base, audio_output_stream->time_base); - if (opkt.pts != AV_NOPTS_VALUE) { - opkt.pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base); - } - if ( opkt.dts != AV_NOPTS_VALUE) - opkt.dts = av_rescale_q( opkt.dts, audio_output_context->time_base, audio_output_stream->time_base); - if ( opkt.duration > 0) - opkt.duration = av_rescale_q( opkt.duration, audio_output_context->time_base, audio_output_stream->time_base); - Debug(2, "opkt dts (%d) pts(%d) duration:(%d) pos(%d) ", opkt.dts, opkt.pts, opkt.duration, opkt.pos ); - - - //opkt.dts = AV_NOPTS_VALUE; - - -#endif #endif } else { + av_init_packet(&opkt); + Debug(5, "after init packet" ); opkt.data = ipkt->data; opkt.size = ipkt->size; } + // PTS is difficult, because of the buffering of the audio packets in the resampler. + + //Scale the PTS of the outgoing packet to be the correct time base + if ( ipkt->pts != AV_NOPTS_VALUE ) { + if ( !audio_last_pts ) { + opkt.pts = 0; + } else { + if ( audio_last_pts > ipkt->pts ) { + Debug(1, "Resetting audeo_start_pts from (%d) to (%d)", audio_last_pts, ipkt->pts ); + } + opkt.pts = previous_pts + av_rescale_q(ipkt->pts - audio_last_pts, audio_input_stream->time_base, audio_output_stream->time_base); + Debug(2, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, audio_last_pts ); + } + audio_last_pts = ipkt->pts; + } else { + Debug(2, "opkt.pts = undef"); + opkt.pts = AV_NOPTS_VALUE; + } + + //Scale the DTS of the outgoing packet to be the correct time base + if ( ! audio_last_dts ) { + opkt.dts = 0; + } else { + if( ipkt->dts == AV_NOPTS_VALUE ) { + // So if the input has no dts assigned... still need an output dts... so we use cur_dts? + + if ( audio_last_dts > audio_input_stream->cur_dts ) { + Debug(1, "Resetting audio_last_pts from (%d) to cur_dts (%d)", audio_last_dts, audio_input_stream->cur_dts ); + opkt.dts = previous_dts + av_rescale_q( audio_input_stream->cur_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); + } else { + opkt.dts = previous_dts + av_rescale_q( audio_input_stream->cur_dts - audio_last_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); + } + audio_last_dts = audio_input_stream->cur_dts; + Debug(2, "opkt.dts = %d from video_input_stream->cur_dts(%d) - last_dts(%d)", opkt.dts, audio_input_stream->cur_dts, audio_last_dts ); + } else { + if ( audio_last_dts > ipkt->dts ) { + Debug(1, "Resetting audio_last_dts from (%d) to (%d)", audio_last_dts, ipkt->dts ); + opkt.dts = previous_dts + av_rescale_q(ipkt->dts, audio_input_stream->time_base, audio_output_stream->time_base); + } else { + opkt.dts = previous_dts + av_rescale_q(ipkt->dts - audio_last_dts, audio_input_stream->time_base, audio_output_stream->time_base); + } + Debug(2, "opkt.dts = %d from ipkt->dts(%d) - last_dts(%d)", opkt.dts, ipkt->dts, audio_last_dts ); + } + } + if ( opkt.dts > opkt.pts ) { + Debug(1,"opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts ); + opkt.dts = opkt.pts; + } + + // I wonder if we could just use duration instead of all the hoop jumping above? + opkt.duration = av_rescale_q(ipkt->duration, audio_input_stream->time_base, audio_output_stream->time_base); + + // pkt.pos: byte position in stream, -1 if unknown + opkt.pos = -1; + opkt.flags = ipkt->flags; + opkt.stream_index = ipkt->stream_index; + Debug(2, "Stream index is %d", opkt.stream_index ); + AVPacket safepkt; memcpy(&safepkt, &opkt, sizeof(AVPacket)); ret = av_interleaved_write_frame(oc, &opkt); From bdb6dd63a3b096666a1f161a0757d1b4d84cd4be Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:32:22 -0400 Subject: [PATCH 25/39] change type of width and height to unsigned int and recording from bool to timeval --- src/zm_camera.cpp | 2 +- src/zm_camera.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zm_camera.cpp b/src/zm_camera.cpp index 30918e502..7abafee52 100644 --- a/src/zm_camera.cpp +++ b/src/zm_camera.cpp @@ -20,7 +20,7 @@ #include "zm.h" #include "zm_camera.h" -Camera::Camera( unsigned int p_monitor_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : +Camera::Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : monitor_id( p_monitor_id ), type( p_type ), width( p_width), diff --git a/src/zm_camera.h b/src/zm_camera.h index ed9647c54..4d991d495 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -55,7 +55,7 @@ protected: bool record_audio; public: - Camera( unsigned int p_monitor_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); virtual ~Camera(); unsigned int getId() const { return( monitor_id ); } @@ -88,7 +88,7 @@ public: virtual int PreCapture()=0; virtual int Capture( Image &image )=0; virtual int PostCapture()=0; - virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory)=0; + virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) = 0; }; #endif // ZM_CAMERA_H From 669a23c408d30e14fae866337a29003deb167bae Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:35:07 -0400 Subject: [PATCH 26/39] change type of width and height to unsigned int and recording from bool to timeval --- src/zm_curl_camera.cpp | 4 ++-- src/zm_curl_camera.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index b0f57115b..a5717d9ca 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -33,7 +33,7 @@ const char* content_type_match = "Content-Type:"; size_t content_length_match_len; size_t content_type_match_len; -cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : +cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET ) { @@ -305,7 +305,7 @@ int cURLCamera::PostCapture() { return( 0 ); } -int cURLCamera::CaptureAndRecord( Image &image, bool recording, char* event_directory ) { +int cURLCamera::CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ) { Error("Capture and Record not implemented for the cURL camera type"); // Nothing to do here return( 0 ); diff --git a/src/zm_curl_camera.h b/src/zm_curl_camera.h index a8cdc1f16..c9dc2e935 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -64,7 +64,7 @@ protected: pthread_cond_t request_complete_cond; public: - cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~cURLCamera(); const std::string &Path() const { return( mPath ); } @@ -78,7 +78,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); - int CaptureAndRecord( Image &image, bool recording, char* event_directory ); + int CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ); size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata); From 7e920eb5d39d1ef1e4b2b99e759d842f133a31d5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:36:39 -0400 Subject: [PATCH 27/39] add a non-const version of StartTime() which we need in order to assign to video_data->recording --- src/zm_monitor.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 516d11a0c..35c19927d 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -425,7 +425,7 @@ Monitor::Monitor( trigger_data->trigger_text[0] = 0; trigger_data->trigger_showtext[0] = 0; shared_data->valid = true; - video_store_data->recording = false; + video_store_data->recording = (struct timeval){0}; snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "nothing"); video_store_data->size = sizeof(VideoStoreData); //video_store_data->frameNumber = 0; @@ -1389,8 +1389,8 @@ bool Monitor::Analyse() { shared_data->last_event = 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 = true; - + video_store_data->recording = event->StartTime(); + Info( "%s: %03d - Opening new event %d, section start", name, image_count, event->Id() ); /* To prevent cancelling out an existing alert\prealarm\alarm state */ @@ -1492,7 +1492,7 @@ bool Monitor::Analyse() { shared_data->last_event = 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 = true; + video_store_data->recording = event->StartTime(); Info( "%s: %03d - Opening new event %d, alarm start", name, image_count, event->Id() ); @@ -1601,9 +1601,11 @@ bool Monitor::Analyse() { event->updateNotes( noteSetMap ); } else if ( state == TAPE ) { //Video Storage: activate only for supported cameras. Event::AddFrame knows whether or not we are recording video and saves frames accordingly - if((GetOptVideoWriter() == 2) && camera->SupportsNativeVideo()) { - video_store_data->recording = true; - } + //if((GetOptVideoWriter() == 2) && camera->SupportsNativeVideo()) { + // I don't think this is required, and causes problems, as the event file hasn't been setup yet. + //Warning("In state TAPE, + //video_store_data->recording = event->StartTime(); + //} if ( !(image_count%(frame_skip+1)) ) { if ( config.bulk_frame_interval > 1 ) { event->AddFrame( snap_image, *timestamp, (event->Frames()EndTime()), NULL ); } delete event; - video_store_data->recording = false; + video_store_data->recording = (struct timeval){0}; event = 0; return( true ); } From 954c863ad82280e3dabc4e3c0b591165f57d2c1d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:36:49 -0400 Subject: [PATCH 28/39] add a non-const version of StartTime() which we need in order to assign to video_data->recording --- src/zm_event.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_event.h b/src/zm_event.h index 4ded4ed36..3b78318fb 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -131,6 +131,7 @@ class Event { const struct timeval &StartTime() const { return( start_time ); } const struct timeval &EndTime() const { return( end_time ); } + struct timeval &StartTime() { return( start_time ); } struct timeval &EndTime() { return( end_time ); } bool SendFrameImage( const Image *image, bool alarm_frame=false ); From 8efeb41f7d1208e85b27ac03a4a6d5aa11af54b9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:38:12 -0400 Subject: [PATCH 29/39] use new clear_unwanted_packets and a clearQueue version that keeps the approprate # of frames --- src/zm_ffmpeg_camera.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 1eba7ec06..692657892 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -224,8 +224,7 @@ int FfmpegCamera::Capture( Image &image ) return (0); } // FfmpegCamera::Capture -int FfmpegCamera::PostCapture() -{ +int FfmpegCamera::PostCapture() { // Nothing to do here return( 0 ); } @@ -301,7 +300,7 @@ int FfmpegCamera::OpenFfmpeg() { #endif Fatal( "Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno) ); - startTime=av_gettime();//FIXME here or after find_Stream_info + startTime = av_gettime();//FIXME here or after find_Stream_info Debug ( 1, "Got stream info" ); // Find first video stream present @@ -333,7 +332,7 @@ int FfmpegCamera::OpenFfmpeg() { Debug(2, "Have another audio stream." ); } } - } + } // end foreach stream if ( mVideoStreamId == -1 ) Fatal( "Unable to locate video stream in %s", mPath.c_str() ); if ( mAudioStreamId == -1 ) @@ -431,7 +430,7 @@ int FfmpegCamera::OpenFfmpeg() { Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); #endif // HAVE_LIBSWSCALE - if ( mVideoCodecContext->width != width || mVideoCodecContext->height != height ) { + if ( (unsigned int)mVideoCodecContext->width != width || (unsigned int)mVideoCodecContext->height != height ) { Warning( "Monitor dimensions are %dx%d but camera is sending %dx%d", width, height, mVideoCodecContext->width, mVideoCodecContext->height ); } @@ -534,7 +533,7 @@ void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){ } //Function to handle capture and store -int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_file ) { +int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event_file ) { if (!mCanCapture){ return -1; } @@ -554,19 +553,15 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi mReopenThread = 0; } - if (mVideoCodecContext->codec_id != AV_CODEC_ID_H264) { Error( "Input stream is not h264. The stored event file may not be viewable in browser." ); } int frameComplete = false; - while ( !frameComplete ) { -Debug(5, "Before av_init_packe"); + while ( ! frameComplete ) { av_init_packet( &packet ); -Debug(5, "Before av_read_frame"); ret = av_read_frame( mFormatContext, &packet ); -Debug(5, "After av_read_frame (%d)", ret ); if ( ret < 0 ) { av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); if ( @@ -591,7 +586,7 @@ Debug(5, "After av_read_frame (%d)", ret ); ); //Video recording - if ( recording ) { + if ( recording.tv_sec ) { // The directory we are recording to is no longer tied to the current event. // Need to re-init the videostore with the correct directory and start recording again // for efficiency's sake, we should test for keyframe before we test for directory change... @@ -612,9 +607,9 @@ Debug(5, "After av_read_frame (%d)", ret ); delete videoStore; videoStore = NULL; - } + } // end if end of recording - if ( ( ! videoStore )&& key_frame && ( packet.stream_index == mVideoStreamId ) ) { + if ( ( ! videoStore ) && key_frame && ( packet.stream_index == mVideoStreamId ) ) { //Instantiate the video storage module if (record_audio) { @@ -645,10 +640,15 @@ Debug(5, "After av_read_frame (%d)", ret ); strcpy(oldDirectory, event_file); // 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; + + packetqueue.clear_unwanted_packets( &recording, mVideoStreamId ); + while ( ( queued_packet = packetqueue.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, packetqueue.size() ); @@ -681,7 +681,7 @@ Debug(5, "After av_read_frame (%d)", ret ); if ( packet.stream_index == mVideoStreamId) { if ( key_frame ) { Debug(3, "Clearing queue"); - packetqueue.clearQueue(); + packetqueue.clearQueue( monitor->GetPreEventCount(), mVideoStreamId ); } #if 0 // Not sure this is valid. While a camera will PROBABLY always have an increasing pts... it doesn't have to. From 59c85c1fbbb4f502d16ce9cf122ba86bc8e6bb6d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:38:39 -0400 Subject: [PATCH 30/39] change bool recording to a timeval. --- src/zm_ffmpeg_camera.h | 2 +- src/zm_file_camera.h | 2 +- src/zm_libvlc_camera.cpp | 2 +- src/zm_libvlc_camera.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index e7b39fde8..45e67f2a4 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -101,7 +101,7 @@ class FfmpegCamera : public Camera int PrimeCapture(); int PreCapture(); int Capture( Image &image ); - int CaptureAndRecord( Image &image, bool recording, char* event_directory ); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); int PostCapture(); }; diff --git a/src/zm_file_camera.h b/src/zm_file_camera.h index 71f9452f7..6ad911755 100644 --- a/src/zm_file_camera.h +++ b/src/zm_file_camera.h @@ -47,7 +47,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); - int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);}; + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; }; #endif // ZM_FILE_CAMERA_H diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index 5d4b497ce..dbe69750f 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -212,7 +212,7 @@ int LibvlcCamera::Capture( Image &image ) } // Should not return -1 as cancels capture. Always wait for image if available. -int LibvlcCamera::CaptureAndRecord(Image &image, bool recording, char* event_directory) +int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) { while(!mLibvlcData.newImage.getValueImmediate()) mLibvlcData.newImage.getUpdatedValue(1); diff --git a/src/zm_libvlc_camera.h b/src/zm_libvlc_camera.h index d1df60f3b..4221bd0b7 100644 --- a/src/zm_libvlc_camera.h +++ b/src/zm_libvlc_camera.h @@ -70,7 +70,7 @@ public: int PrimeCapture(); int PreCapture(); int Capture( Image &image ); - int CaptureAndRecord( Image &image, bool recording, char* event_directory ); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); int PostCapture(); }; From e3d3fc341183b509bfaa3b15d7750106f9c6939e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:38:56 -0400 Subject: [PATCH 31/39] change bool recording to a timeval. --- src/zm_local_camera.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index 3f4d8fb70..dae5f830e 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -162,7 +162,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); - int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);}; + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose ); }; From a0b57cedeb09d3ba5f57516135c87af0947fbc75 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:39:12 -0400 Subject: [PATCH 32/39] change bool recording to a timeval. and add GetPreEventCount --- src/zm_monitor.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 91b464f88..c00576ca3 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -172,7 +172,7 @@ class Monitor { uint32_t size; char event_file[4096]; - uint32_t recording; //bool arch dependent so use uint32 instead + timeval recording; // used as both bool and a pointer to the timestamp when recording should begin //uint32_t frameNumber; } VideoStoreData; @@ -441,9 +441,10 @@ public: VideoWriter GetOptVideoWriter() const { return( videowriter ); } const std::vector* GetOptEncoderParams() const { return( &encoderparamsvec ); } + unsigned int GetPreEventCount() const { return pre_event_count; }; State GetState() const; int GetImage( int index=-1, int scale=100 ); -Snapshot *getSnapshot(); + Snapshot *getSnapshot(); struct timeval GetTimestamp( int index=-1 ) const; void UpdateAdaptiveSkip(); useconds_t GetAnalysisRate(); From 575b3138de49a8750f9a7babaa747fd10bf46492 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:39:47 -0400 Subject: [PATCH 33/39] change it from a queue to a list. Implement a clearQueue that keeps some frames, and a function clear out unwanted frames --- src/zm_packetqueue.cpp | 89 +++++++++++++++++++++++++++++++++++++++--- src/zm_packetqueue.h | 6 ++- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index c38f74c52..f60b11df5 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -34,7 +34,7 @@ zm_packetqueue::~zm_packetqueue() { } bool zm_packetqueue::queuePacket( ZMPacket* zm_packet ) { - pktQueue.push( zm_packet ); + pktQueue.push_back( zm_packet ); return true; } @@ -42,7 +42,7 @@ bool zm_packetqueue::queuePacket( AVPacket* av_packet ) { ZMPacket *zm_packet = new ZMPacket( av_packet ); - pktQueue.push( zm_packet ); + pktQueue.push_back( zm_packet ); return true; } @@ -53,17 +53,57 @@ ZMPacket* zm_packetqueue::popPacket( ) { } ZMPacket *packet = pktQueue.front(); - pktQueue.pop(); + pktQueue.pop_front(); return packet; } +unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) { + + Debug(3, "Clearing all but %d frames", frames_to_keep ); + frames_to_keep += 1; + + if ( pktQueue.empty() ) { + Debug(3, "Queue is empty"); + return 0; + } else { + Debug(3, "Queue has (%d)", pktQueue.size() ); + } + + list::reverse_iterator it; + ZMPacket *packet = NULL; + + for ( it = pktQueue.rbegin(); it != pktQueue.rend() && frames_to_keep; ++it ) { + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + + Debug(3, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); + + // Want frames_to_keep video keyframes. Otherwise, we may not have enough + if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) { + if (!frames_to_keep) + break; + frames_to_keep --; + } + } + unsigned int delete_count = 0; + while ( it != pktQueue.rend() ) { + Debug(3, "Deleting a packet from the front, count is (%d)", delete_count ); + + packet = pktQueue.front(); + pktQueue.pop_front(); + delete packet; + + delete_count += 1; + } + return delete_count; +} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) + void zm_packetqueue::clearQueue() { ZMPacket *packet = NULL; while(!pktQueue.empty()) { - packet = pktQueue.front(); - pktQueue.pop(); + pktQueue.pop_front(); delete packet; } } @@ -71,3 +111,42 @@ void zm_packetqueue::clearQueue() { unsigned int zm_packetqueue::size() { return pktQueue.size(); } + + +void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) { + // Need to find the keyframe <= recording_started. Can get rid of audio packets. + if ( pktQueue.empty() ) { + return; + } + + // Step 1 - find keyframe < recording_started. + // Step 2 - pop packets until we get to the packet in step 2 + list::reverse_iterator it; + + for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); +Debug(1, "Looking for keyframe after start" ); + if ( + ( av_packet->flags & AV_PKT_FLAG_KEY ) + && + ( av_packet->stream_index == mVideoStreamId ) + && + timercmp( &(zm_packet->timestamp), recording_started, < ) + ) { +Debug(1, "Found keyframe before start" ); + break; + } + } + if ( it == pktQueue.rend() ) { + Debug(1, "Didn't find a keyframe packet keeping all" ); + return; + } + + ZMPacket *packet = NULL; + while ( pktQueue.rend() != it ) { + packet = pktQueue.front(); + pktQueue.pop_front(); + delete packet; + } +} diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index 730487312..39160ddfd 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -23,7 +23,7 @@ //#include //#include //#include -#include +#include #include "zm_packet.h" extern "C" { @@ -40,10 +40,12 @@ public: ZMPacket * popPacket( ); bool popVideoPacket(ZMPacket* packet); bool popAudioPacket(ZMPacket* packet); + unsigned int clearQueue( unsigned int video_frames_to_keep, int stream_id ); void clearQueue( ); unsigned int size(); + void clear_unwanted_packets( timeval *recording, int mVideoStreamId ); private: - std::queue pktQueue; + std::list pktQueue; }; From ecb57f8f04aae8cf52eab98e6273a015a2a0eb14 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:40:17 -0400 Subject: [PATCH 34/39] change bool recording to a timeval. --- src/zm_remote_camera.h | 2 +- src/zm_remote_camera_http.h | 2 +- src/zm_remote_camera_rtsp.cpp | 4 ++-- src/zm_remote_camera_rtsp.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zm_remote_camera.h b/src/zm_remote_camera.h index f25f3f4b4..d15b0127c 100644 --- a/src/zm_remote_camera.h +++ b/src/zm_remote_camera.h @@ -90,7 +90,7 @@ public: virtual int PreCapture() = 0; virtual int Capture( Image &image ) = 0; virtual int PostCapture() = 0; - virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory )=0; + virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory )=0; }; #endif // ZM_REMOTE_CAMERA_H diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index db76ef3e6..a22efa27c 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -59,7 +59,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); - int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);}; + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; }; #endif // ZM_REMOTE_CAMERA_HTTP_H diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index fbb1b9b9d..096b69ccb 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -392,7 +392,7 @@ int RemoteCameraRtsp::Capture( Image &image ) { //Function to handle capture and store -int RemoteCameraRtsp::CaptureAndRecord(Image &image, bool recording, char* event_file ) { +int RemoteCameraRtsp::CaptureAndRecord(Image &image, timeval recording, char* event_file ) { AVPacket packet; uint8_t* directbuffer; int frameComplete = false; @@ -407,7 +407,7 @@ int RemoteCameraRtsp::CaptureAndRecord(Image &image, bool recording, char* event return (-1); //Video recording - if ( recording ) { + if ( recording.tv_sec ) { // The directory we are recording to is no longer tied to the current event. // Need to re-init the videostore with the correct directory and start recording again // Not sure why we are only doing this on keyframe, al diff --git a/src/zm_remote_camera_rtsp.h b/src/zm_remote_camera_rtsp.h index 79f656ac5..8ed8b713c 100644 --- a/src/zm_remote_camera_rtsp.h +++ b/src/zm_remote_camera_rtsp.h @@ -86,7 +86,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); - int CaptureAndRecord( Image &image, bool recording, char* event_directory ); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); }; #endif // ZM_REMOTE_CAMERA_RTSP_H From 51d5bfc8d58bd9d731ec57f89f5de4f3d27b12ed Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:40:38 -0400 Subject: [PATCH 35/39] introduce a helper function to print out a timeval --- src/zm_utils.cpp | 12 ++++++++++++ src/zm_utils.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index 988a5c9ea..efe3fe34c 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -390,6 +390,18 @@ void timespec_diff(struct timespec *start, struct timespec *end, struct timespec } } +char *timeval_to_string( struct timeval tv ) { + time_t nowtime; + struct tm *nowtm; + char tmbuf[64], buf[64]; + + nowtime = tv.tv_sec; + nowtm = localtime(&nowtime); + strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm); + snprintf(buf, sizeof buf, "%s.%06ld", tmbuf, tv.tv_usec); + return buf; +} + std::string UriDecode( const std::string &encoded ) { #ifdef HAVE_LIBCURL CURL *curl = curl_easy_init(); diff --git a/src/zm_utils.h b/src/zm_utils.h index 7235bb15f..961389611 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -61,6 +61,7 @@ void hwcaps_detect(); extern unsigned int sseversion; extern unsigned int neonversion; +char *timeval_to_string( struct timeval tv ); std::string UriDecode( const std::string &encoded ); #endif // ZM_UTILS_H From 6e7bd9311d4f76fde1a992b8ca0d5abe3ff515c4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 13:41:02 -0400 Subject: [PATCH 36/39] add the api tmp cache folders to the tmpfiles --- distros/ubuntu1204/zoneminder.tmpfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/distros/ubuntu1204/zoneminder.tmpfile b/distros/ubuntu1204/zoneminder.tmpfile index d307c6640..c4be55632 100644 --- a/distros/ubuntu1204/zoneminder.tmpfile +++ b/distros/ubuntu1204/zoneminder.tmpfile @@ -1,2 +1,6 @@ d /var/run/zm 0755 www-data www-data d /tmp/zm 0755 www-data www-data +d /var/tmp/zm 0755 www-data www-data +d /var/tmp/cache 0755 www-data www-data +d /var/tmp/cache/models 0755 www-data www-data +d /var/tmp/cache/persistent 0755 www-data www-data From d1f04a60f9e342875bbda6ab1e96dc8cf9f33c1b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 16:16:37 -0400 Subject: [PATCH 37/39] fix errors calculating dts --- src/zm_videostore.cpp | 74 +++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 7112b78f5..c990e7422 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -538,6 +538,8 @@ void VideoStore::dumpPacket( AVPacket *pkt ){ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) { av_init_packet(&opkt); + int duration; + //Scale the PTS of the outgoing packet to be the correct time base if (ipkt->pts != AV_NOPTS_VALUE) { @@ -555,6 +557,7 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) { } } Debug(3, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, video_last_pts ); + duration = ipkt->pts - video_last_pts; video_last_pts = ipkt->pts; } else { Debug(3, "opkt.pts = undef"); @@ -567,7 +570,8 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) { if ( ! video_last_dts ) { // This is the first packet. opkt.dts = 0; - Debug(1, "Starting video video_last_pts will become (%d)", ipkt->dts ); + Debug(1, "Starting video video_last_dts will become (%d)", ipkt->dts ); + video_last_dts = ipkt->dts; } else { if ( ipkt->dts == AV_NOPTS_VALUE ) { // why are we using cur_dts instead of packet.dts? I think cur_dts is in AV_TIME_BASE_Q, but ipkt.dts is in video_input_stream->time_base @@ -599,7 +603,11 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) { opkt.dts = opkt.pts; } - opkt.duration = av_rescale_q(ipkt->duration, video_input_stream->time_base, video_output_stream->time_base); + if ( ipkt->duration == AV_NOPTS_VALUE ) { + opkt.duration = av_rescale_q( duration, video_input_stream->time_base, video_output_stream->time_base); + } else { + opkt.duration = av_rescale_q(ipkt->duration, video_input_stream->time_base, video_output_stream->time_base); + } opkt.flags = ipkt->flags; opkt.pos=-1; @@ -614,51 +622,35 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) { opkt.stream_index = ipkt->stream_index; } - /*opkt.flags |= AV_PKT_FLAG_KEY;*/ + AVPacket safepkt; + memcpy(&safepkt, &opkt, sizeof(AVPacket)); -#if 0 - if (video_output_context->codec_type == AVMEDIA_TYPE_VIDEO && (output_format->flags & AVFMT_RAWPICTURE)) { - AVPicture pict; - Debug(3, "video and RAWPICTURE"); - /* store AVPicture in AVPacket, as expected by the output format */ - avpicture_fill(&pict, opkt.data, video_output_context->pix_fmt, video_output_context->width, video_output_context->height, 0); - av_image_fill_arrays( - opkt.data = (uint8_t *)&pict; - opkt.size = sizeof(AVPicture); - opkt.flags |= AV_PKT_FLAG_KEY; - } else { - Debug(4, "Not video and RAWPICTURE"); - } -#endif +Debug(1, "writing video packet pts(%d) dts(%d) duration(%d)", opkt.pts, opkt.dts, opkt.duration ); + if ((opkt.data == NULL)||(opkt.size < 1)) { + Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ ); + dumpPacket( ipkt); + dumpPacket(&opkt); - AVPacket safepkt; - memcpy(&safepkt, &opkt, sizeof(AVPacket)); + } else if ((previous_dts > 0) && (previous_dts > opkt.dts)) { + Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, previous_dts, opkt.dts); + previous_dts = opkt.dts; + dumpPacket(&opkt); - if ((opkt.data == NULL)||(opkt.size < 1)) { - Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ ); - dumpPacket( ipkt); - dumpPacket(&opkt); + } else { - } else if ((previous_dts > 0) && (previous_dts > opkt.dts)) { - Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, previous_dts, opkt.dts); - previous_dts = opkt.dts; - dumpPacket(&opkt); + previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance + previous_pts = opkt.pts; + ret = av_interleaved_write_frame(oc, &opkt); + if(ret<0){ + // There's nothing we can really do if the frame is rejected, just drop it and get on with the next + Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret)); + dumpPacket(&safepkt); + } + } - } else { + zm_av_packet_unref(&opkt); - previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance - previous_pts = opkt.pts; - ret = av_interleaved_write_frame(oc, &opkt); - if(ret<0){ - // There's nothing we can really do if the frame is rejected, just drop it and get on with the next - Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret)); - dumpPacket(&safepkt); - } - } - - zm_av_packet_unref(&opkt); - - return 0; + return 0; } From f97e0743930b4052d17cd45702e16fa51bffa84b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 16:16:56 -0400 Subject: [PATCH 38/39] braces fixes --- web/skins/classic/views/frames.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/web/skins/classic/views/frames.php b/web/skins/classic/views/frames.php index 165e8db04..9b5e57e6e 100644 --- a/web/skins/classic/views/frames.php +++ b/web/skins/classic/views/frames.php @@ -72,14 +72,11 @@ if ( count($frames) ) { Id().'&fid='.$frame['FrameId'], 'zmStats', 'stats', $frame['Score'] ) ?> MonitorId().'_'.$frame['EventId'].'_'.$frame['FrameId'].'.jpg" '. (ZM_WEB_LIST_THUMB_WIDTH?'width="'.ZM_WEB_LIST_THUMB_WIDTH.'" ':''). -(ZM_WEB_LIST_THUMB_HEIGHT?'height="'.ZM_WEB_LIST_THUMB_HEIGHT.'" ':'').'alt="'.$frame['FrameId'].'"/>' ) ?> +(ZM_WEB_LIST_THUMB_HEIGHT?'height="'.ZM_WEB_LIST_THUMB_HEIGHT.'" ':'').' alt="'.$frame['FrameId'].'"/>' ) ?> From 7d6505f0156babc2f4c14d446abdf82305569740 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Apr 2017 16:17:19 -0400 Subject: [PATCH 39/39] use Delta to grab frame from video, using -ss for speed --- web/views/image.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/views/image.php b/web/views/image.php index 1a8009aad..7bc40a1c1 100644 --- a/web/views/image.php +++ b/web/views/image.php @@ -85,7 +85,8 @@ if ( empty($_REQUEST['path']) ) { Debug( "$path does not exist"); # Generate the frame JPG if ( $show == 'capture' and $Event->DefaultVideo() ) { - $command ='ffmpeg -i '.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path; + $command ='ffmpeg -ss '. $Frame->Delta() .' -i '.$Event->Path().'/'.$Event->DefaultVideo().' -frames:v 1 '.$path; + #$command ='ffmpeg -ss '. $Frame->Delta() .' -i '.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path; #$command ='ffmpeg -v 0 -i '.$Storage->Path().'/'.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path; Debug( "Running $command" ); $output = array();