diff --git a/distros/debian/postinst b/distros/debian/postinst index d06f9c641..8bb4d23eb 100644 --- a/distros/debian/postinst +++ b/distros/debian/postinst @@ -3,51 +3,51 @@ set -e if [ "$1" = "configure" ]; then - if [ -e "/etc/init.d/mysql" ]; then - # - # Get mysql started if it isn't - # - if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then - invoke-rc.d mysql start - fi - if $(/etc/init.d/mysql status >/dev/null 2>&1); then - mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload - # test if database if already present... - if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then - cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf - echo 'grant lock tables, alter,select,insert,update,delete on zm.* to 'zmuser'@localhost identified by "zmpass";' | mysql --defaults-file=/etc/mysql/debian.cnf mysql - fi - invoke-rc.d zoneminder stop || true - zmupdate.pl --nointeractive + . /etc/zm/zm.conf - else - echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.' - fi - else - echo 'mysql not found, assuming remote server.' - fi - chown www-data:www-data /var/log/zm - chown www-data:www-data /var/lib/zm/ + # The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group. + chown www-data:root /var/log/zm + chown www-data:www-data /var/lib/zm if [ -z "$2" ]; then - chown www-data:www-data -R /var/cache/zoneminder + chown www-data:www-data -R /var/cache/zoneminder fi -fi -# Ensure zoneminder is stopped... -if [ -x "/etc/init.d/zoneminder" ]; then - if invoke-rc.d zoneminder status ; then - invoke-rc.d zoneminder stop || exit $? + + # Do this every time the package is installed or upgraded + + if [ "$ZM_DB_HOST" = "localhost" ]; then + if [ -e "/etc/init.d/mysql" ]; then + # + # Get mysql started if it isn't + # + if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then + invoke-rc.d mysql start + fi + if $(/etc/init.d/mysql status >/dev/null 2>&1); then + mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload + # test if database if already present... + if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then + cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf + # This creates the user. + echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + else + echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + fi + + # Ensure zoneminder is stopped + invoke-rc.d zoneminder stop || true + zmupdate.pl --nointeractive + zmupdate.pl --nointeractive -f + invoke-rc.d zoneminder start || true + else + echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.' + fi + else + echo 'mysql not found, assuming remote server.' fi -fi - -if [ "$1" = "configure" ]; then - if [ -z "$2" ]; then - chown www-data:www-data /var/log/zm - chown www-data:www-data /var/lib/zm/ - chown www-data:www-data -R /var/cache/zoneminder else - chown www-data:www-data /var/log/zm - zmupdate.pl - fi + echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)" + fi + fi #DEBHELPER# diff --git a/distros/ubuntu1204/zoneminder.postinst b/distros/ubuntu1204/zoneminder.postinst index e7810e468..1bfd41d42 100644 --- a/distros/ubuntu1204/zoneminder.postinst +++ b/distros/ubuntu1204/zoneminder.postinst @@ -3,11 +3,51 @@ set -e if [ "$1" = "configure" ]; then - chown www-data:root /var/log/zm - chown www-data:www-data /var/lib/zm - if [ -z "$2" ]; then - chown www-data:www-data -R /var/cache/zoneminder - fi + + . /etc/zm/zm.conf + + # The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group + chown www-data:root /var/log/zm + chown www-data:www-data /var/lib/zm + if [ -z "$2" ]; then + chown www-data:www-data -R /var/cache/zoneminder + fi + + # Do this every time the package is installed or upgraded + + if [ "$ZM_DB_HOST" = "localhost" ]; then + if [ -e "/etc/init.d/mysql" ]; then + # + # Get mysql started if it isn't + # + if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then + invoke-rc.d mysql start + fi + if $(/etc/init.d/mysql status >/dev/null 2>&1); then + mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload + # test if database if already present... + if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then + cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf + # This creates the user. + echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + else + echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + fi + + # Ensure zoneminder is stopped + invoke-rc.d zoneminder stop || true + zmupdate.pl --nointeractive + zmupdate.pl --nointeractive -f + invoke-rc.d zoneminder start || true + else + echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.' + fi + else + echo 'mysql not found, assuming remote server.' + fi + else + echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)" + fi fi #DEBHELPER# diff --git a/distros/ubuntu1504/control b/distros/ubuntu1504/control index 50dee74c6..c1b7d958f 100644 --- a/distros/ubuntu1504/control +++ b/distros/ubuntu1504/control @@ -57,7 +57,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libsys-cpu-perl, libsys-meminfo-perl ,mysql-client | virtual-mysql-client ,perl-modules - ,php5-mysql + ,php5-mysql, php5-gd ,policykit-1 ,rsyslog | system-log-daemon ,zip diff --git a/distros/ubuntu1504/zoneminder.postinst b/distros/ubuntu1504/zoneminder.postinst index 82b256e6b..c45474e1d 100644 --- a/distros/ubuntu1504/zoneminder.postinst +++ b/distros/ubuntu1504/zoneminder.postinst @@ -2,28 +2,57 @@ set -e -. /etc/zm/zm.conf - if [ "$1" = "configure" ]; then - chown www-data:root /var/log/zm - chown www-data:www-data /var/lib/zm - if [ -z "$2" ]; then - chown www-data:www-data -R /var/cache/zoneminder - fi + + . /etc/zm/zm.conf + + # The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group + chown www-data:root /var/log/zm + chown www-data:www-data /var/lib/zm + if [ -z "$2" ]; then + chown www-data:www-data -R /var/cache/zoneminder + fi # Do this every time the package is installed or upgraded - # Test for database presence to avoid failure of zmupdate.pl - - # Ensure zoneminder is stopped - deb-systemd-invoke stop zoneminder.service || exit $? if [ "$ZM_DB_HOST" = "localhost" ]; then - echo 'grant lock tables, create, index, alter on zm.* to 'zmuser'@localhost identified by "zmpass";' | mysql --defaults-file=/etc/mysql/debian.cnf mysql - # Run the ZoneMinder update tool - zmupdate.pl --nointeractive - else + + if [ -e "/etc/init.d/mysql" ]; then + + # + # Get mysql started if it isn't + # + if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then + deb-systemd-invoke start mysql.service || exit $? + fi + + if $(/etc/init.d/mysql status >/dev/null 2>&1); then + mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload + # test if database if already present... + if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then + cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf + # This creates the user. + echo "grant lock tables,alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + else + echo "grant lock tables,alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + fi + + # Ensure zoneminder is stopped + deb-systemd-invoke stop zoneminder.service || exit $? + zmupdate.pl --nointeractive + zmupdate.pl --nointeractive -f + deb-systemd-invoke start zoneminder.service || exit $? + + else + echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.' + fi + else + echo 'mysql not found, assuming remote server.' + fi + + else echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)" - fi; + fi fi diff --git a/docs/faq.rst b/docs/faq.rst index 5aa736ee8..1c5c36d09 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -514,6 +514,8 @@ You do not need to rebuild ZM for X10 support. You will need to install the perl Extending Zoneminder ------------------------ +.. _runstate_cron_example: + How can I get ZM to do different things at different times of day or week? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/userguide/gettingstarted.rst b/docs/userguide/gettingstarted.rst index 73f6e2783..b2e92f63f 100644 --- a/docs/userguide/gettingstarted.rst +++ b/docs/userguide/gettingstarted.rst @@ -67,8 +67,14 @@ own empty screen. * **D**: This is the core of ZoneMinder - recording events. It gives you a count of how many events were recorded over the hour, day, week, month. * **E**: These are the "Zones". Zones are areas within the camera that you mark as 'hotspots' for motion detection. Simply put, when you first configure your monitors (cameras), by default Zoneminder uses the entire field of view of the camera to detect motion. You may not want this. You may want to create "zones" specifically for detecting motion and ignore others. For example, lets consider a room with a fan that spins. You surely don't want to consider the fan moving continuously a reason for triggering a record? Probably not - in that case, you'd leave the fan out while making your zones. * **F**: This is the "source" column that tells you the type of the camera - if its an IP camera, a USB camera or more. In this example, they are all IP cameras. Note the color red on item F ? Well that means there is something wrong with that camera. No wonder the log also shows red. Good indication for you to tap on logs and investigate -* **G**: This defines how Zoneminder will record events. There are various modes. In brief Modect == record if a motion is detected,Record = always record 24x7, Mocord = always record PLUS detect motion, Monitor = just provide a live view but don't record anytime, Nodect = Don't record till an externa entity via zmtrigger tells Zoneminder to (this is advanced usage). +* **G**: This defines how Zoneminder will record events. There are various modes. In brief Modect == record if a motion is detected,Record = always record 24x7, Mocord = always record PLUS detect motion, Monitor = just provide a live view but don't record anytime, Nodect = Don't record till an external entity via zmtrigger tells Zoneminder to (this is advanced usage). * **H**: If you click on these links you can view a "Montage" of all your configured monitors or cycle through each one +* **I**: One of the most often missed features is the ability of ZoneMinder to maintain "run states". If you click on the "Running" text, ZoneMinder brings up a popup that allows you to define additional "states" (referred to as runstates). A runstate is essentially a snapshot that records the state of each monitor and you can switch between states easily. For example, you might have a run state defined that switches all monitors to "monitor" mode in which they are not recording anything while another state that sets some of the monitors to "modect". Why would you want this? A great example is to disable recording when you are at home and enable when you are away, based on time of day or other triggers. You can switch states by selecting an appropriate state manually, or do it automatically via cron jobs, for example. An example of using cron to automatically switch is provided in the :ref:`FAQ `. More esoteric examples of switching run states based on phone location can be found `here `__. + +Here is an example of multiple run states that I've defined. Each one of these runstates changes the mode of specific monitors depending on time of day and other conditions. Use your imagination to decide which conditions require state changes. + +.. image:: images/runstates.png + Adding Monitors diff --git a/docs/userguide/images/getting-started-understand-console.png b/docs/userguide/images/getting-started-understand-console.png index 805917f23..f332b4977 100644 Binary files a/docs/userguide/images/getting-started-understand-console.png and b/docs/userguide/images/getting-started-understand-console.png differ diff --git a/docs/userguide/images/runstates.png b/docs/userguide/images/runstates.png new file mode 100644 index 000000000..ced74cb55 Binary files /dev/null and b/docs/userguide/images/runstates.png differ diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index c95f65441..a8cebde89 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -155,10 +155,12 @@ MAIN: while( $loop ) { Fatal("ZM_AUDIT_MIN_AGE is not set in config."); } + my %Monitors; my $db_monitors; - my $monitorSelectSql = "select Id from Monitors order by Id"; + my $monitorSelectSql = "select * from Monitors order by Id"; my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); + my $eventSelectSql = "SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age FROM Events WHERE MonitorId = ? ORDER BY Id"; my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) @@ -169,6 +171,8 @@ MAIN: while( $loop ) { or Fatal( "Can't execute: ".$monitorSelectSth->errstr() ); while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) { + $Monitors{$$monitor{Id}} = $monitor; + Debug( "Found database monitor '$monitor->{Id}'" ); my $db_events = $db_monitors->{$monitor->{Id}} = {}; my $res = $eventSelectSth->execute( $monitor->{Id} ) @@ -467,20 +471,30 @@ MAIN: while( $loop ) { # New audit to close any events that were left open for longer than MIN_AGE seconds my $selectUnclosedEventsSql = - "SELECT E.Id, - max(F.TimeStamp) as EndTime, - unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, - max(F.FrameId) as Frames, + #"SELECT E.Id, ANY_VALUE(E.MonitorId), +# + #max(F.TimeStamp) as EndTime, + #unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, + #max(F.FrameId) as Frames, + #count(if(F.Score>0,1,NULL)) as AlarmFrames, + #sum(F.Score) as TotScore, + #max(F.Score) as MaxScore + #FROM Events as E + #INNER JOIN Frames as F on E.Id = F.EventId + #WHERE isnull(E.Frames) or isnull(E.EndTime) + #GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)" + #; + "SELECT *, unix_timestamp(StartTime) AS TimeStamp FROM Events WHERE EndTime IS NULL AND StartTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)"; + + my $selectFrameDataSql = "SELECT max(TimeStamp) as EndTime, unix_timestamp(max(TimeStamp)) AS EndTimeStamp, max(FrameId) as Frames, count(if(F.Score>0,1,NULL)) as AlarmFrames, sum(F.Score) as TotScore, - max(F.Score) as MaxScore, - M.EventPrefix as Prefix - FROM Events as E - LEFT JOIN Monitors as M on E.MonitorId = M.Id - INNER JOIN Frames as F on E.Id = F.EventId - WHERE isnull(E.Frames) or isnull(E.EndTime) - GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)" - ; + max(F.Score) as MaxScore + FROM Frames WHERE EventId=?"; + my $selectFrameDataSth = $dbh->prepare_cached($selectFrameDataSql) + or Fatal( "Can't prepare '$selectFrameDataSql': ".$dbh->errstr() ); + + my $selectUnclosedEventsSth = $dbh->prepare_cached( $selectUnclosedEventsSql ) or Fatal( "Can't prepare '$selectUnclosedEventsSql': ".$dbh->errstr() ); my $updateUnclosedEventsSql = @@ -505,26 +519,32 @@ MAIN: while( $loop ) { aud_print( "Found open event '$event->{Id}'" ); if ( confirm( 'close', 'closing' ) ) { - $res = $updateUnclosedEventsSth->execute - ( - sprintf("%s%d%s", - $event->{Prefix}, - $event->{Id}, - RECOVER_TAG - ), - $event->{EndTime}, - $event->{Length}, - $event->{Frames}, - $event->{AlarmFrames}, - $event->{TotScore}, - $event->{AlarmFrames} - ? int($event->{TotScore} / $event->{AlarmFrames}) - : 0 - , - $event->{MaxScore}, - RECOVER_TEXT, - $event->{Id} - ) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); + $res = $selectFrameDataSth->execute( $event->{Id} ); + my $frame = $selectFrameDataSth->fetchrow_hashref(); + if ( $frame ) { + $res = $updateUnclosedEventsSth->execute + ( + sprintf("%s%d%s", + $Monitors{$event->{MonitorId}}->{EventPrefix}, + $event->{Id}, + RECOVER_TAG + ), + $frame->{EndTime}, + $frame->{EndTimeStamp} - $event->{TimeStamp}, + $frame->{Frames}, + $frame->{AlarmFrames}, + $frame->{TotScore}, + $frame->{AlarmFrames} + ? int($frame->{TotScore} / $frame->{AlarmFrames}) + : 0 + , + $frame->{MaxScore}, + RECOVER_TEXT, + $event->{Id} + ) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); + } else { + Error("SHOULD DELETE"); + } # end if has frame data } } diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index b068b9a49..36d5e922a 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -862,7 +862,7 @@ sub killAll my $killall; if ( '@HOST_OS@' eq 'BSD' ) { - $killall = 'killall -'; + $killall = 'killall -q -'; } elsif ( '@HOST_OS@' eq 'solaris' ) { $killall = 'pkill -'; } else { diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index a93fbf15c..7e1813896 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -61,6 +61,7 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri mOpenStart = 0; mReopenThread = 0; wasRecording = false; + videoStore = NULL; #if HAVE_LIBSWSCALE mConvertContext = NULL; @@ -99,6 +100,7 @@ void FfmpegCamera::Initialise() av_log_set_level( AV_LOG_QUIET ); av_register_all(); + avformat_network_init(); } void FfmpegCamera::Terminate() @@ -356,7 +358,8 @@ int FfmpegCamera::OpenFfmpeg() { if ( mAudioStreamId == -1 ) Debug( 2, "Unable to locate audio stream in %s", mPath.c_str() ); - Debug ( 1, "Found video stream" ); + Debug ( 3, "Found video stream at index %d", mVideoStreamId ); + Debug ( 3, "Found audio stream at index %d", mAudioStreamId ); mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; @@ -408,7 +411,7 @@ int FfmpegCamera::OpenFfmpeg() { Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize); } - Debug ( 1, "Validated imagesize" ); + Debug ( 1, "Validated imagesize %d", pSize ); #if HAVE_LIBSWSCALE Debug ( 1, "Calling sws_isSupportedInput" ); @@ -590,9 +593,11 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height); //Keep the last keyframe so we can establish immediate video - /*if(packet.flags & AV_PKT_FLAG_KEY) - av_copy_packet(&lastKeyframePkt, &packet);*/ - //TODO I think we need to store the key frame location for seeking as part of the event + if(packet.flags & AV_PKT_FLAG_KEY) { + Debug(4, "Have keyframe"); + av_copy_packet(&lastKeyframePkt, &packet); + //TODO I think we need to store the key frame location for seeking as part of the event + } //Video recording if ( recording && !wasRecording ) { @@ -602,6 +607,9 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi videoStore = new VideoStore((const char *)event_file, "mp4", mFormatContext->streams[mVideoStreamId],mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId],startTime); wasRecording = true; strcpy(oldDirectory, event_file); + + + // Need to write out all the frames from the last keyframe? } else if ( ( ! recording ) && wasRecording && videoStore ) { Info("Deleting videoStore instance"); @@ -660,9 +668,10 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi } if ( videoStore && recording ) { if ( record_audio ) { - Debug(3, "Recording audio packet" ); + Debug(3, "Recording audio packet streamindex(%d) packetstreamindex(%d)", mAudioStreamId, packet.stream_index ); //Write the packet to our video store - int ret = videoStore->writeAudioFramePacket(&packet, mFormatContext->streams[packet.stream_index]); //FIXME no relevance of last key frame + //FIXME no relevance of last key frame + int ret = videoStore->writeAudioFramePacket( &packet, mFormatContext->streams[packet.stream_index] ); if ( ret < 0 ) {//Less than zero and we skipped a frame av_free_packet( &packet ); return 0; diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index bc7ec2ecb..314ce6fa1 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -64,7 +64,9 @@ protected: bool wasRecording; VideoStore *videoStore; char oldDirectory[4096]; - //AVPacket lastKeyframePkt; + + // Last Key frame + AVPacket lastKeyframePkt; #if HAVE_LIBSWSCALE struct SwsContext *mConvertContext; diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 96afa88cc..4175fce32 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -33,7 +33,8 @@ extern "C"{ } VideoStore::VideoStore(const char *filename_in, const char *format_in, - AVStream *input_st, AVStream *inpaud_st, + AVStream *input_st, + AVStream *inpaud_st, int64_t nStartTime) { AVDictionary *pmetadata = NULL; @@ -46,8 +47,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, keyframeMessage = false; keyframeSkipNumber = 0; - - Info("Opening video storage stream %s\n", filename); + Info("Opening video storage stream %s format: %d\n", filename, format); //Init everything we need int ret; @@ -96,12 +96,13 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, } if (inpaud_st) { + audio_st = avformat_new_stream(oc, inpaud_st->codec->codec); if (!audio_st) { Error("Unable to create audio out stream\n"); audio_st = NULL; } else { - ret=avcodec_copy_context(audio_st->codec, inpaud_st->codec); + ret = avcodec_copy_context(audio_st->codec, inpaud_st->codec); if (ret < 0) { Fatal("Unable to copy audio context %s\n", av_make_error_string(ret).c_str()); } @@ -111,6 +112,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, } } } else { + Debug(3, "No Audio output stream"); audio_st = NULL; } @@ -146,7 +148,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, startTime=av_gettime()-nStartTime;//oc->start_time; Info("VideoStore startTime=%d\n",startTime); -} +} // VideoStore::VideoStore VideoStore::~VideoStore(){ @@ -249,7 +251,7 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt, AVStream *input_st){//, AV } else if ((prevDts > 0) && (prevDts >= opkt.dts)) { Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, prevDts, opkt.dts); prevDts = opkt.dts; - dumpPacket(&opkt); + dumpPacket(&opkt); } else { int ret; @@ -272,34 +274,47 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt, AVStream *input_st){//, AV int VideoStore::writeAudioFramePacket(AVPacket *ipkt, AVStream *input_st){ - if(!audio_st) + if(!audio_st) { + Error("Called writeAudioFramePacket when no audio_st"); return -1;//FIXME -ve return codes do not free packet in ffmpeg_camera at the moment + } /*if(!keyframeMessage) return -1;*/ - - int64_t ost_tb_start_time = av_rescale_q(startTime, AV_TIME_BASE_Q, video_st->time_base); + //zm_dump_stream_format( oc, ipkt->stream_index, 0, 1 ); + + // What is this doing? Getting the time of the start of this video chunk? Does that actually make sense? + int64_t ost_tb_start_time = av_rescale_q(startTime, AV_TIME_BASE_Q, audio_st->time_base); AVPacket opkt; av_init_packet(&opkt); + Debug(3, "after init packet" ); //Scale the PTS of the outgoing packet to be the correct time base - if (ipkt->pts != AV_NOPTS_VALUE) + if (ipkt->pts != AV_NOPTS_VALUE) { + Debug(3, "Rescaling output pts"); opkt.pts = av_rescale_q(ipkt->pts-startPts, input_st->time_base, audio_st->time_base) - ost_tb_start_time; - else + } else { + Debug(3, "Setting output pts to AV_NOPTS_VALUE"); opkt.pts = AV_NOPTS_VALUE; + } //Scale the DTS of the outgoing packet to be the correct time base if(ipkt->dts == AV_NOPTS_VALUE) { +Debug(4, "ipkt->dts == AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts ); opkt.dts = av_rescale_q(input_st->cur_dts-startDts, AV_TIME_BASE_Q, audio_st->time_base); -Debug(3, "ipkt->dts == AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts ); - } else +Debug(4, "ipkt->dts == AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts ); + } else { +Debug(4, "ipkt->dts != AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts ); opkt.dts = av_rescale_q(ipkt->dts-startDts, input_st->time_base, audio_st->time_base); +Debug(4, "ipkt->dts != AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts ); + } opkt.dts -= ost_tb_start_time; + // Seems like it would be really weird for the codec type to NOT be audiu if (audio_st->codec->codec_type == AVMEDIA_TYPE_AUDIO && ipkt->dts != AV_NOPTS_VALUE) { - Debug( 3, "code is audio, dts != AV_NOPTS_VALUE " ); + Debug( 4, "code is audio, dts != AV_NOPTS_VALUE " ); int duration = av_get_audio_frame_duration(input_st->codec, ipkt->size); if(!duration) duration = input_st->codec->frame_size; @@ -324,7 +339,7 @@ Debug(3, "ipkt->dts == AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts ); if(ret!=0){ Fatal("Error encoding audio frame packet: %s\n", av_make_error_string(ret).c_str()); } - + Debug(4,"Success writing audio frame" ); av_free_packet(&opkt); return 0; } diff --git a/web/includes/actions.php b/web/includes/actions.php index 1ceda8231..74689c95c 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -527,6 +527,7 @@ if ( !empty($action) ) 'DoNativeMotDet' => 'toggle', 'Exif' => 'toggle', 'RTSPDescribe' => 'toggle', + 'RecordAudio' => 'toggle', ); $columns = getTableColumns( 'Monitors' ); diff --git a/web/skins/classic/css/dark/views/event.css b/web/skins/classic/css/dark/views/event.css index 995a06c1d..13ac0cc0b 100644 --- a/web/skins/classic/css/dark/views/event.css +++ b/web/skins/classic/css/dark/views/event.css @@ -15,7 +15,6 @@ #menuBar1 { width: 100%; - height: 1.5em; padding: 3px 0; text-align: center; clear: both; @@ -41,7 +40,6 @@ #menuBar2 { width: 100%; - height: 1.2em; padding: 3px 0; margin-bottom: 4px; } @@ -57,6 +55,16 @@ text-align: right; } +#menuBar1:after, +#menuBar2:after { + content: "."; + display: block; + height: 0; + font-size: 0; + clear: both; + visibility: hidden; +} + #imageFeed { text-align: center; } diff --git a/web/skins/classic/css/flat/views/event.css b/web/skins/classic/css/flat/views/event.css index 620b1ce11..9b085189c 100644 --- a/web/skins/classic/css/flat/views/event.css +++ b/web/skins/classic/css/flat/views/event.css @@ -15,12 +15,12 @@ #menuBar1 { width: 100%; - height: 1.5em; padding: 3px 0; text-align: center; clear: both; } + #menuBar1 #nameControl { float: left; } @@ -41,7 +41,6 @@ #menuBar2 { width: 100%; - height: 1.2em; padding: 3px 0; margin-bottom: 4px; } @@ -56,6 +55,15 @@ float: right; text-align: right; } +#menuBar1:after, +#menuBar2:after { + content: "."; + display: block; + height: 0; + font-size: 0; + clear: both; + visibility: hidden; +} #imageFeed { text-align: center; diff --git a/web/skins/classic/views/js/console.js b/web/skins/classic/views/js/console.js index 5a7eb9f68..5182a5fd5 100644 --- a/web/skins/classic/views/js/console.js +++ b/web/skins/classic/views/js/console.js @@ -11,19 +11,8 @@ function setButtonStates( element ) if ( checked++ > 1 ) break; } - } - else if ( form.elements[i].length ) - { - for( var j = 0, j_length = form.elements[i].length; j < j_length; j += 1 ) { - if ( form.elements[j].type == "checkbox" ) { - if ( form.elements[j].checked ) { - if ( checked++ > 1 ) - break; - } - } - } // end foreach element in array - } - } // end foreach element in array + } + } $(element).getParent( 'tr' ).toggleClass( 'highlight' ); form.editBtn.disabled = (checked!=1); form.deleteBtn.disabled = (checked==0); diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index fe4559201..1367bc8b1 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -35,6 +35,7 @@ function Monitor( index, id, connKey ) if ( this.streamCmdTimer ) this.streamCmdTimer = clearTimeout( this.streamCmdTimer ); + var stream = document.getElementById( "liveStream"+this.id ); if ( respObj.result == 'Ok' ) { this.status = respObj.status; @@ -57,7 +58,6 @@ function Monitor( index, id, connKey ) this.setStateClass( $('monitor'+this.index), stateClass ); /*Stream could be an applet so can't use moo tools*/ - var stream = document.getElementById( "liveStream"+this.id ); stream.className = stateClass; var isAlarmed = ( this.alarmState == STATE_ALARM || this.alarmState == STATE_ALERT ); @@ -90,6 +90,10 @@ function Monitor( index, id, connKey ) else { console.error( respObj.message ); + // Try to reload the image stream. + if ( stream ) + stream.src = stream.src.replace(/rand=\d+/i,'rand='+Math.floor((Math.random() * 1000000) )); + } var streamCmdTimeout = statusRefreshTimeout; if ( this.alarmState == STATE_ALARM || this.alarmState == STATE_ALERT ) diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php index 160234cf7..541ea5a13 100644 --- a/web/skins/classic/views/watch.php +++ b/web/skins/classic/views/watch.php @@ -115,7 +115,10 @@ if ( canEdit( 'Monitors' ) ) if ( canEdit( 'Monitors' ) ) { ?> -
+
+ + +