diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 93af9039d..ecc2d477d 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -541,7 +541,7 @@ CREATE TABLE `Monitors` ( `Longitude` DECIMAL(11,8), `RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE, `RTSPStreamName` varchar(255) NOT NULL default '', - `Importance` enum('Not','Less','Normal'), + `Importance` enum('Normal','Less','Not') NOT NULL default 'Normal', PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; diff --git a/db/zm_update-1.35.14.sql b/db/zm_update-1.35.14.sql index daa8239ff..f3c8bf779 100644 --- a/db/zm_update-1.35.14.sql +++ b/db/zm_update-1.35.14.sql @@ -28,8 +28,8 @@ SET @s = (SELECT IF( AND table_name = 'Monitors' AND column_name = 'TotalEvents' ) > 0, -"SELECT 'Column TotalEvents is already removed from Monitors'", -"ALTER TABLE `Monitors` DROP `TotalEvents`" +"ALTER TABLE `Monitors` DROP `TotalEvents`", +"SELECT 'Column TotalEvents is already removed from Monitors'" )); PREPARE stmt FROM @s; EXECUTE stmt; @@ -50,8 +50,8 @@ SET @s = (SELECT IF( AND table_name = 'Monitors' AND column_name = 'TotalEventDiskSpace' ) > 0, -"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'", -"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`" +"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`", +"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'" )); PREPARE stmt FROM @s; EXECUTE stmt; diff --git a/db/zm_update-1.37.2.sql b/db/zm_update-1.37.2.sql index 0002f12ae..d06fe204f 100644 --- a/db/zm_update-1.37.2.sql +++ b/db/zm_update-1.37.2.sql @@ -2,3 +2,6 @@ UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left","left":"0px UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='2 Wide'; UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"25%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='4 Wide'; UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"20%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='5 Wide'; + +UPDATE Monitors set Importance = 'Normal' where Importance IS NULL; +ALTER TABLE `Monitors` MODIFY `Importance` enum('Normal','Less','Not') NOT NULL default 'Normal'; diff --git a/distros/ubuntu2004/zoneminder.postinst b/distros/ubuntu2004/zoneminder.postinst index 4be136a71..6f26bca24 100644 --- a/distros/ubuntu2004/zoneminder.postinst +++ b/distros/ubuntu2004/zoneminder.postinst @@ -5,6 +5,12 @@ set +e create_db () { echo "Checking for db" mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload + if [ $? -ne 0 ]; then + echo "Cannot talk to database. You will have to create the db manually with something like:"; + echo "cat /usr/share/zoneminder/db/zm_create.sql | mysql -u root"; + return; + fi + # test if database if already present... if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then echo "Creating zm db" diff --git a/docs/faq.rst b/docs/faq.rst index dd0273667..f23fa2fc1 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -225,7 +225,7 @@ change the 3 to a 1 I can't see more than 6 monitors in montage on my browser --------------------------------------------------------- -Browsers such a Chrome and Safari only support upto 6 streams from the same domain. To work around that, take a look at the multi-port configuration discussed in the ``MIN_STREAMING_PORT`` configuration in :doc:`/userguide/options/options_network` +Browsers such a Chrome and Safari only support up to 6 streams from the same domain. To work around that, take a look at the multi-port configuration discussed in the ``MIN_STREAMING_PORT`` configuration in :doc:`/userguide/options/options_network` Why is ZoneMinder using so much CPU? --------------------------------------- diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index c6e0cebcf..f7325fe1f 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -3,6 +3,50 @@ Debian .. contents:: +Easy Way: Debian 11 (Bullseye) +------------------------ + +This procedure will guide you through the installation of ZoneMinder on Debian 11 (Bullseye). + +**Step 1:** Setup Sudo (optional but recommended) + +By default Debian does not come with sudo, so you have to install it and configure it manually. +This step is optional but recommended and the following instructions assume that you have setup sudo. +If you prefer to setup ZoneMinder as root, do it at your own risk and adapt the following instructions accordingly. + +:: + + apt install sudo + usermod -a -G sudo + exit + +Now your terminal session is back under your normal user. You can check that +you are now part of the sudo group with the command ``groups``, "sudo" should +appear in the list. If not, run ``newgrp sudo`` and check again with ``groups``. + +**Step 2:** Update system and install zoneminder + +Run the following commands. + +:: + + sudo apt update + sudo apt upgrade + sudo apt install mariadb-server + sudo apt install zoneminder + +When mariadb is installed for the first time, it doesn't add a password to the root user. Therefore, for security, it is recommended to run ``mysql secure installation``. + +**Step 3:** Setup permissions for zm.conf + +To make sure zoneminder can read the configuration file, run the following command. + +:: + + sudo chgrp -c www-data /etc/zm/zm.conf + +Congratulations! You should now be able to access zoneminder at ``http://yourhostname/zm`` + Easy Way: Debian Buster ------------------------ diff --git a/docs/installationguide/easydocker.rst b/docs/installationguide/easydocker.rst index 899ce0e5d..a7d37a9e5 100644 --- a/docs/installationguide/easydocker.rst +++ b/docs/installationguide/easydocker.rst @@ -2,7 +2,7 @@ An Easy To Use Docker Image =========================== If you are interested in trying out ZoneMinder quickly, user Dan Landon maintains an easy to use docker image for ZoneMinder. With a few simple configuration changes, it also provides complete Event Notification Server and Machine Learning hook support. Please follow instructions in his repostory. He maintains two repositories: -* If you want to run the latest stable release, please use his `zoneminder repository `__. +* If you want to run the latest stable release, please use his `zoneminder machine learning repository `__. * If you want to run the latest zoneminder master, please use his `zoneminder master repository `__. In both cases, instructions are provided in the repo README files. diff --git a/docs/userguide/definemonitor.rst b/docs/userguide/definemonitor.rst index a5b622872..ab58b656c 100644 --- a/docs/userguide/definemonitor.rst +++ b/docs/userguide/definemonitor.rst @@ -86,7 +86,7 @@ Source Path Use this field to enter the full URL of the stream or file your camera supports. This is usually an RTSP url. There are several methods to learn this: * Check the documentation that came with your camera - * Look for your camera in the hardware compatibilty list in the `hardware compatibility wiki `__ + * Look for your camera in the hardware compatibility list in the `hardware compatibility wiki `__ * Try ZoneMinder's new ONVIF probe feature * Download and install the `ONVIF Device Manager `__ onto a Windows machine * Use Google to find third party sites, such as ispy, which document this information @@ -179,12 +179,12 @@ Storage Tab The storage section allows for each monitor to configure if and how video and audio are recorded. Save JPEGs - Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows to view an event anytime while it is being recorded. + Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows one to view an event anytime while it is being recorded. * Disabled – video is not recorded as JPEG frames. If this setting is selected, then "Video Writer" should be enabled otherwise there is no video recording at all. * Frames only – video is recorded in individual JPEG frames. - * Analysis images only (if available) – video is recorded in invidual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames. - * Frames + Analysis images (if available) – video is recorded twice, once as normal individual JPEG frames and once in invidual JPEG frames with analysis information overlaid. + * Analysis images only (if available) – video is recorded in individual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames. + * Frames + Analysis images (if available) – video is recorded twice, once as normal individual JPEG frames and once in individual JPEG frames with analysis information overlaid. Video Writer Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored. diff --git a/docs/userguide/filterevents.rst b/docs/userguide/filterevents.rst index b30a3185d..77f811fba 100644 --- a/docs/userguide/filterevents.rst +++ b/docs/userguide/filterevents.rst @@ -34,7 +34,7 @@ Here is what the filter window looks like * Update used disk space: calculates how much disk space is currently taken by the event and updates the db record. * Create video for all matches: creates a video file of all the events that match * Create video for all matches: ffmpeg will be used to create a video file (mp4) out of all the stored jpgs if using jpeg storage. - * Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceeded by replacement arguents. eg: /usr/bin/script.sh %MN% will excecute as /usr/bin/script.sh MonitorName /path/to/event. Please note that urls may contain characters like & that need quoting. So you may need to put quotes around them like /usr/bin/scrupt.sh "%MN%". + * Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceded by replacement arguents. eg: /usr/bin/script.sh %MN% will execute as /usr/bin/script.sh MonitorName /path/to/event. Please note that urls may contain characters like & that need quoting. So you may need to put quotes around them like /usr/bin/scrupt.sh "%MN%". * Delete all matches: Deletes all the matched events. * Email details of all matches: Sends an email to the configured address with details about the event. * Copy all matches: copies the event files to another location, specified in the Copy To dropdown. The other location must be setup in the Storage Tab under options. diff --git a/docs/userguide/gettingstarted.rst b/docs/userguide/gettingstarted.rst index 99f2ea4ba..61d4cb523 100644 --- a/docs/userguide/gettingstarted.rst +++ b/docs/userguide/gettingstarted.rst @@ -53,7 +53,7 @@ This screen is called the "console" screen in ZoneMinder and shows a summary of * **B**: This brings up a color coded log window that shows various system and component level logs. This window is useful if you are trying to diagnose issues. Refer to :doc:`logging`. * **C**: ZoneMinder allows you to group monitors for logical separation. This option lets you create new groups, associate monitors to them and edit/delete existing groups. * **D**: Filters are a powerful mechanism to perform actions when certain conditions are met. ZoneMinder comes with some preset filters that keep a tab of disk space and others. Many users create their own filters for more advanced actions like sending emails when certain events occur and more. Refer to :doc:`filterevents`. -* **E**: The Cycle option allows you to rotate between live views of each cofigured monitor. +* **E**: The Cycle option allows you to rotate between live views of each configured monitor. * **F**: The Montage option shows a collage of your monitors. You can customize them including moving them around. * **G**: Montage Review allows you to simultaneously view past events for different monitors. Note that this is a very resource intensive page and its performance will vary based on your system capabilities. * **H**: Audit Events Report is more of a power user feature. This option looks for recording gaps in events and recording issues in mp4 files. diff --git a/docs/userguide/mobile.rst b/docs/userguide/mobile.rst index c6da91642..7f000bea3 100644 --- a/docs/userguide/mobile.rst +++ b/docs/userguide/mobile.rst @@ -6,7 +6,7 @@ Here are some options for using ZoneMinder on Mobile devices: Third party mobile clients ^^^^^^^^^^^^^^^^^^^^^^^^^^^ * zmNinja (`source code `__, needs APIs to be installed to work) - * Available in App Store, Play Store and for Desktops - `website `__ + * Available in App Store, Play Store and for Desktops - `website `__ Using the existing web console ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,4 +17,4 @@ Discontinued clients The following are a list of clients that do not work and have not been updated: * eyeZM -* zmView \ No newline at end of file +* zmView diff --git a/docs/userguide/options/options_users.rst b/docs/userguide/options/options_users.rst index def8744d7..4258c4ed0 100644 --- a/docs/userguide/options/options_users.rst +++ b/docs/userguide/options/options_users.rst @@ -30,7 +30,7 @@ This screen allows you to configure various permissions on a per user basis. The .. note:: if you are using zmNinja, users are required to have 'View' access to system because multi-server information is only available as part of this permission - Bandwidth - - Specifies the maximum bandwith that this user can configure (Low, Medium or High) + - Specifies the maximum bandwidth that this user can configure (Low, Medium or High) - API enabled - Specifies if the ZoneMinder API is enabled for this user (needs to be on, if you are using a mobile app such as zmNinja) @@ -42,4 +42,4 @@ Here is an example of a restricted user, for example: .. image:: images/Options_Users_Example.png -This user "home" is enabled, can view live streams and events, but only from "DoorBell" and "DeckCamera". This user also cannot control PTZ. \ No newline at end of file +This user "home" is enabled, can view live streams and events, but only from "DoorBell" and "DeckCamera". This user also cannot control PTZ. diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/MotionInCells.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/MotionInCells.pm index 89de61e18..b6a5a487a 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/MotionInCells.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/MotionInCells.pm @@ -147,7 +147,7 @@ This attribute is of type L object. @@ -3093,7 +3093,7 @@ Returns a L object. @@ -3288,7 +3288,7 @@ Returns a L object. @@ -3298,7 +3298,7 @@ Returns a L object. diff --git a/onvif/proxy/lib/ONVIF/Device/Types/MotionInCells.pm b/onvif/proxy/lib/ONVIF/Device/Types/MotionInCells.pm index a4bb4696b..87c17a350 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/MotionInCells.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/MotionInCells.pm @@ -147,7 +147,7 @@ This attribute is of type L object. diff --git a/onvif/proxy/lib/ONVIF/Media/Types/FaultCodesOpenEnumType.pm b/onvif/proxy/lib/ONVIF/Media/Types/FaultCodesOpenEnumType.pm index 12e921a67..f6bdde62a 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/FaultCodesOpenEnumType.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/FaultCodesOpenEnumType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/ONVIF/Media/Types/MotionInCells.pm b/onvif/proxy/lib/ONVIF/Media/Types/MotionInCells.pm index 687131675..e88c29862 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/MotionInCells.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/MotionInCells.pm @@ -147,7 +147,7 @@ This attribute is of type L or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm b/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm index d1c5faa29..950c26344 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm @@ -987,7 +987,7 @@ Returns a L object. diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/FaultCodesOpenEnumType.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/FaultCodesOpenEnumType.pm index e69cc37ad..e827b659b 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/FaultCodesOpenEnumType.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/FaultCodesOpenEnumType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/MotionInCells.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/MotionInCells.pm index 8f68d1ef5..825f45ffa 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/MotionInCells.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/MotionInCells.pm @@ -147,7 +147,7 @@ This attribute is of type L or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery10/Types/FaultCodeOpenType.pm b/onvif/proxy/lib/WSDiscovery10/Types/FaultCodeOpenType.pm index cd183e240..d89688025 100644 --- a/onvif/proxy/lib/WSDiscovery10/Types/FaultCodeOpenType.pm +++ b/onvif/proxy/lib/WSDiscovery10/Types/FaultCodeOpenType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery10/Types/OpenRelationshipType.pm b/onvif/proxy/lib/WSDiscovery10/Types/OpenRelationshipType.pm index 45aab20e5..84f5985f8 100644 --- a/onvif/proxy/lib/WSDiscovery10/Types/OpenRelationshipType.pm +++ b/onvif/proxy/lib/WSDiscovery10/Types/OpenRelationshipType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm b/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm index a7dcd3532..0aa2a393c 100644 --- a/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm +++ b/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm @@ -100,7 +100,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. diff --git a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm index 52f5d2c8f..42fe97b83 100644 --- a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm +++ b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm index 89d8704e8..072948d96 100644 --- a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm +++ b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm b/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm index dc3e004c2..31c9df482 100644 --- a/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm +++ b/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSNotification/Types/AbsoluteOrRelativeTimeType.pm b/onvif/proxy/lib/WSNotification/Types/AbsoluteOrRelativeTimeType.pm index ac8dc805d..c80f98fe0 100644 --- a/onvif/proxy/lib/WSNotification/Types/AbsoluteOrRelativeTimeType.pm +++ b/onvif/proxy/lib/WSNotification/Types/AbsoluteOrRelativeTimeType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSNotification/Types/FaultCodesOpenEnumType.pm b/onvif/proxy/lib/WSNotification/Types/FaultCodesOpenEnumType.pm index 06e4df192..fae356a95 100644 --- a/onvif/proxy/lib/WSNotification/Types/FaultCodesOpenEnumType.pm +++ b/onvif/proxy/lib/WSNotification/Types/FaultCodesOpenEnumType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSNotification/Types/RelationshipTypeOpenEnum.pm b/onvif/proxy/lib/WSNotification/Types/RelationshipTypeOpenEnum.pm index e598093cb..0125e612c 100644 --- a/onvif/proxy/lib/WSNotification/Types/RelationshipTypeOpenEnum.pm +++ b/onvif/proxy/lib/WSNotification/Types/RelationshipTypeOpenEnum.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index ff802dd15..48d494614 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -1064,7 +1064,7 @@ our @options = ( }, { name => 'ZM_FFMPEG_FORMATS', - default => 'mpg mpeg wmv asf avi* mov swf 3gp**', + default => 'mp4* mpg mpeg wmv asf avi mov swf 3gp**', description => 'Formats to allow for ffmpeg video generation', help => q` Ffmpeg can generate video in many different formats. This diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm index feb12a0ca..e4d052700 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm @@ -29,6 +29,7 @@ use strict; use warnings; require ZoneMinder::Base; +require ZoneMinder::Object; require ZoneMinder::Monitor; our $VERSION = $ZoneMinder::Base::VERSION; @@ -42,24 +43,116 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; +$table = 'Controls'; +$serial = $primary_key = 'Id'; +%fields = map { $_ => $_ } qw( + Id + Name + Type + Protocol + CanWake + CanSleep + CanReset + CanReboot + CanZoom + CanAutoZoom + CanZoomAbs + CanZoomRel + CanZoomCon + MinZoomRange + MaxZoomRange + MinZoomStep + MaxZoomStep + HasZoomSpeed + MinZoomSpeed + MaxZoomSpeed + CanFocus + CanAutoFocus + CanFocusAbs + CanFocusRel + CanFocusCon + MinFocusRange + MaxFocusRange + MinFocusStep + MaxFocusStep + HasFocusSpeed + MinFocusSpeed + MaxFocusSpeed + CanIris + CanAutoIris + CanIrisAbs + CanIrisRel + CanIrisCon + MinIrisRange + MaxIrisRange + MinIrisStep + MaxIrisStep + HasIrisSpeed + MinIrisSpeed + MaxIrisSpeed + CanGain + CanAutoGain + CanGainAbs + CanGainRel + CanGainCon + MinGainRange + MaxGainRange + MinGainStep + MaxGainStep + HasGainSpeed + MinGainSpeed + MaxGainSpeed + CanWhite + CanAutoWhite + CanWhiteAbs + CanWhiteRel + CanWhiteCon + MinWhiteRange + MaxWhiteRange + MinWhiteStep + MaxWhiteStep + HasWhiteSpeed + MinWhiteSpeed + MaxWhiteSpeed + HasPresets + NumPresets + HasHomePreset + CanSetPresets + CanMove + CanMoveDiag + CanMoveMap + CanMoveAbs + CanMoveRel + CanMoveCon + CanPan + MinPanRange + MaxPanRange + MinPanStep + MaxPanStep + HasPanSpeed + MinPanSpeed + MaxPanSpeed + HasTurboPan + TurboPanSpeed + CanTilt + MinTiltRange + MaxTiltRange + MinTiltStep + MaxTiltStep + HasTiltSpeed + MinTiltSpeed + MaxTiltSpeed + HasTurboTilt + TurboTiltSpeed + CanAutoScan + NumScanPaths + ); + our $AUTOLOAD; -sub new { - my $class = shift; - my $id = shift; - if ( !defined($id) ) { - Fatal('No monitor defined when invoking protocol '.$class); - } - my $self = {}; - $self->{name} = $class; - $self->{id} = $id; - bless($self, $class); - return $self; -} - -sub DESTROY { -} - sub AUTOLOAD { my $self = shift; my $class = ref($self); @@ -79,24 +172,24 @@ sub AUTOLOAD { sub getKey { my $self = shift; - return $self->{id}; + return $self->{Id}; } sub open { my $self = shift; - Fatal('No open method defined for protocol '.$self->{name}); + Fatal('No open method defined for protocol '.$self->{Protocol}); } sub close { my $self = shift; $self->{state} = 'closed'; - Debug('No close method defined for protocol '.$self->{name}); + Debug('No close method defined for protocol '.$self->{Protocol}); } sub loadMonitor { my $self = shift; if ( !$self->{Monitor} ) { - if ( !($self->{Monitor} = ZoneMinder::Monitor->find_one(Id=>$self->{id})) ) { + if ( !($self->{Monitor} = ZoneMinder::Monitor->find_one(Id=>$self->{MonitorId})) ) { Fatal('Monitor id '.$self->{id}.' not found'); } if ( defined($self->{Monitor}->{AutoStopTimeout}) ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm index 521409e11..4f05cd3da 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm @@ -283,7 +283,7 @@ sub presetSet my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=0"; + my $cmd = 'form/presetSet?flag=3&existFlag=1&language=cn&presetNum='.$preset; $self->sendCmd( $cmd ); } @@ -294,7 +294,7 @@ sub presetGoto my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=1"; + my $cmd = 'form/presetSet?flag=4&existFlag=1&language=cn&presetNum='.$preset; $self->sendCmd( $cmd ); } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm index 58ebe4c63..bcf1905c5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm @@ -41,120 +41,133 @@ our @ISA = qw(ZoneMinder::Control); use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); +use ZoneMinder::General qw(:all); use Time::HiRes qw( usleep ); +use URI::Encode qw(uri_encode); -sub open -{ - my $self = shift; +our $REALM = ''; +our $PROTOCOL = 'http://'; +our $USERNAME = 'admin'; +our $PASSWORD = ''; +our $ADDRESS = ''; +our $BASE_URL = ''; - $self->loadMonitor(); - Debug( "Camera open" ); - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); +sub open { + my $self = shift; + $self->loadMonitor(); - $self->{state} = 'open'; + if (($self->{Monitor}->{ControlAddress} =~ /^(?https?:\/\/)?(?[^:@]+)?:?(?[^\/@]+)?@?(?
.*)$/)) { + $PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL}; + $USERNAME = $+{USERNAME} if $+{USERNAME}; + $PASSWORD = $+{PASSWORD} if $+{PASSWORD}; + $ADDRESS = $+{ADDRESS} if $+{ADDRESS}; + } else { + Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress}); + $ADDRESS = $self->{Monitor}->{ControlAddress}; + } + if ( !($ADDRESS =~ /:/) ) { + Error('You generally need to also specify the port. I will append :80'); + $ADDRESS .= ':80'; + } + $BASE_URL = $PROTOCOL.($USERNAME?$USERNAME.':'.$PASSWORD.'@':'').$ADDRESS; + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( 'ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION ); + $self->{state} = 'open'; } -sub close -{ - my $self = shift; - $self->{state} = 'closed'; +sub close { + my $self = shift; + $self->{state} = 'closed'; } -sub printMsg -{ - my $msg = shift; - my $msg_len = length($msg); +sub sendCmd { + my ($self, $cmd, $speedcmd) = @_; - Debug( $msg."[".$msg_len."]" ); + $self->printMsg( $speedcmd, 'Tx' ); + $self->printMsg( $cmd, 'Tx' ); + + my $req = HTTP::Request->new( GET => $BASE_URL."/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd"); + my $res = $self->{ua}->request($req); + + if (!$res->is_success) { + Error('Request failed: '.$res->status_line().' (URI: '.$req->as_string().')'); + } + return $res->is_success; } -sub sendCmd -{ - my ($self, $cmd, $speedcmd) = @_; - - my $result = undef; - - printMsg( $speedcmd, "Tx" ); - printMsg( $cmd, "Tx" ); - - my $req = HTTP::Request->new( GET => "http://" . $self->{Monitor}->{ControlAddress} . "/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd" ); - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) - { - $result = !undef; - } - else - { - Error( "Request failed: '" . $res->status_line() . "' (URI: '" . $req->as_string() . "')" ); - } - - return( $result ); +sub moveConUp { + my ($self, $params) = @_; + my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); + $self->sendCmd( 'move=up', $speed ); } -sub moveConUp -{ - my ($self, $params) = @_; - my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); - Debug( "Move Up" ); - $self->sendCmd( 'move=up', $speed ); +sub moveConDown { + my ($self, $params) = @_; + my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); + $self->sendCmd( 'move=down', $speed ); } -sub moveConDown -{ - my ($self, $params) = @_; - my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); - Debug( "Move Down" ); - $self->sendCmd( 'move=down', $speed ); +sub moveConLeft { + my ($self, $params) = @_; + my $speed = 'speedpan=-' . $params->{panspeed}; + $self->sendCmd( 'move=left', $speed ); } -sub moveConLeft -{ - my ($self, $params) = @_; - my $speed = 'speedpan=-' . $params->{panspeed}; - Debug( "Move Left" ); - $self->sendCmd( 'move=left', $speed ); +sub moveConRight { + my ($self, $params) = @_; + my $speed = 'speedpan=' . ($params->{panspeed} - 6); + $self->sendCmd( 'move=right', $speed ); } -sub moveConRight -{ - my ($self, $params) = @_; - my $speed = 'speedpan=' . ($params->{panspeed} - 6); - Debug( "Move Right" ); - $self->sendCmd( 'move=right', $speed ); +sub moveStop { + my $self = shift; + Debug( "Move Stop: not implemented" ); + # not implemented } -sub moveStop -{ - my $self = shift; - Debug( "Move Stop" ); - # not implemented +sub zoomConTele { + my ($self, $params) = @_; + my $speed = 'speedzoom=' . ($params->{speed} - 6); + $self->sendCmd( 'zoom=tele', $speed ); } -sub zoomConTele -{ - my ($self, $params) = @_; - my $speed = 'speedzoom=' . ($params->{speed} - 6); - Debug( "Zoom In" ); - $self->sendCmd( 'zoom=tele', $speed ); +sub zoomConWide { + my ($self, $params) = @_; + my $speed = 'speedzoom=' . ($params->{speed} - 6); + $self->sendCmd( 'zoom=wide', $speed ); } -sub zoomConWide -{ - my ($self, $params) = @_; - my $speed = 'speedzoom=' . ($params->{speed} - 6); - Debug( "Zoom Out" ); - $self->sendCmd( 'zoom=wide', $speed ); +sub reset { + my $self = shift; + $self->sendCmd( 'move=home' ); } -sub reset -{ - my $self = shift; - Debug( "Camera Reset" ); - $self->sendCmd( 'move=home' ); +sub get_config { + my $self = shift; + + my $url = $BASE_URL.'/cgi-bin/admin/lsctrl.cgi?cmd=queryStatus&retType=javascript'; + my $req = new HTTP::Request(GET => $url); + my $response = $self->{ua}->request($req); + if ( $response->is_success() ) { + my $resp = $response->decoded_content; + return ZoneMinder::General::parseNameEqualsValueToHash($resp); + } + Warn("Failed to get config from $url: " . $response->status_line()); + return; +} # end sub get_config + +sub set_config { + my $self = shift; + my $diff = shift; + + my $url = $BASE_URL.'/cgi-bin/'.$USERNAME.'/setparam.cgi?'. + join('&', map { $_.'='.uri_encode($$diff{$_}) } keys %$diff); + my $response = $self->{ua}->get($url); + Debug($response->content); + return $response->is_success(); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 48544a911..0276af097 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -584,6 +584,7 @@ sub DiskSpace { return $_[0]{DiskSpace}; } +# Icon: I removed the locking from this. So we now have an assumption that the Event object is up to date. sub CopyTo { my ( $self, $NewStorage ) = @_; @@ -614,16 +615,12 @@ sub CopyTo { Debug("$NewPath is good"); } - $ZoneMinder::Database::dbh->begin_work(); - $self->lock_and_load(); # data is reloaded, so need to check that the move hasn't already happened. if ( $$self{StorageId} == $$NewStorage{Id} ) { - $ZoneMinder::Database::dbh->commit(); return 'Event has already been moved by someone else.'; } if ( $$OldStorage{Id} != $$self{StorageId} ) { - $ZoneMinder::Database::dbh->commit(); return 'Old Storage path changed, Event has moved somewhere else.'; } @@ -661,39 +658,21 @@ sub CopyTo { } my $event_path = $subpath.$self->RelativePath(); - if ( 0 ) { # Not neccessary - Debug("Making directory $event_path/"); - if ( !$bucket->add_key($event_path.'/', '') ) { - Warning("Unable to add key for $event_path/ :". $s3->err . ': '. $s3->errstr()); - } - } my @files = glob("$OldPath/*"); Debug("Files to move @files"); - foreach my $file ( @files ) { + foreach my $file (@files) { next if $file =~ /^\./; - ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint + ($file) = ($file =~ /^(.*)$/); # De-taint my $starttime = [gettimeofday]; Debug("Moving file $file to $NewPath"); my $size = -s $file; - if ( ! $size ) { + if (!$size) { Info('Not moving file with 0 size'); } - if ( 0 ) { - my $file_contents = File::Slurp::read_file($file); - if ( ! $file_contents ) { - die 'Loaded empty file, but it had a size. Giving up'; - } - - my $filename = $event_path.'/'.File::Basename::basename($file); - if ( ! $bucket->add_key($filename, $file_contents) ) { - die "Unable to add key for $filename : ".$s3->err . ': ' . $s3->errstr; - } - } else { - my $filename = $event_path.'/'.File::Basename::basename($file); - if ( ! $bucket->add_key_filename($filename, $file) ) { - die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr; - } + my $filename = $event_path.'/'.File::Basename::basename($file); + if (!$bucket->add_key_filename($filename, $file)) { + die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr; } my $duration = tv_interval($starttime); @@ -704,16 +683,15 @@ sub CopyTo { }; Error($@) if $@; } else { - Error("Unable to parse S3 Url into it's component parts."); + Error('Unable to parse S3 Url into it\'s component parts.'); } - #die $@ if $@; } # end if Url } # end if s3 my $error = ''; - if ( !$moved ) { + if (!$moved) { File::Path::make_path($NewPath, {error => \my $err}); - if ( @$err ) { + if (@$err) { for my $diag (@$err) { my ($file, $message) = %$diag; next if $message eq 'File exists'; @@ -724,23 +702,16 @@ sub CopyTo { } } } - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); - return $error; - } + return $error if $error; my @files = glob("$OldPath/*"); - if ( ! @files ) { - $ZoneMinder::Database::dbh->commit(); - return 'No files to move.'; - } + return 'No files to move.' if !@files; for my $file (@files) { next if $file =~ /^\./; - ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint + ($file) = ($file =~ /^(.*)$/); # De-taint my $starttime = [gettimeofday]; - Debug("Moving file $file to $NewPath"); my $size = -s $file; - if ( ! File::Copy::copy( $file, $NewPath ) ) { + if (!File::Copy::copy($file, $NewPath)) { $error .= "Copy failed: for $file to $NewPath: $!"; last; } @@ -749,20 +720,21 @@ sub CopyTo { } # end foreach file. } # end if ! moved - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); - return $error; - } + return $error; } # end sub CopyTo sub MoveTo { - my ( $self, $NewStorage ) = @_; + my ($self, $NewStorage) = @_; - if ( !$self->canEdit() ) { + if (!$self->canEdit()) { Warning('No permission to move event.'); return 'No permission to move event.'; } + my $was_in_transaction = !$ZoneMinder::Database::dbh->{AutoCommit}; + $ZoneMinder::Database::dbh->begin_work() if !$was_in_transaction; + $self->lock_and_load(); # The fact that we are in a transaction might not imply locking + my $OldStorage = $self->Storage(undef); my $error = $self->CopyTo($NewStorage); @@ -772,11 +744,11 @@ sub MoveTo { $$self{StorageId} = $$NewStorage{Id}; $self->Storage($NewStorage); $error .= $self->save(); - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); - return $error; - } - $ZoneMinder::Database::dbh->commit(); + + # Going to leave it to upper layer as to whether we rollback or not + $ZoneMinder::Database::dbh->commit() if !$was_in_transaction; + return $error if $error; + $self->delete_files($OldStorage); return $error; } # end sub MoveTo diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm new file mode 100644 index 000000000..0086b5bac --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm @@ -0,0 +1,99 @@ +# ========================================================================== +# +# ZoneMinder Event_Summary Module +# Copyright (C) 2020 ZoneMinder +# +# 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. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Event_Summary; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; +$table = 'Event_Summaries'; +$serial = $primary_key = 'MonitorId'; +%fields = map { $_ => $_ } qw( + MonitorId + TotalEvents + TotalEventDiskSpace + HourEvents + HourEventDiskSpace + DayEvents + DayEventDiskSpace + WeekEvents + WeekEventDiskSpace + MonthEvents + MonthEventDiskSpace + ArchivedEvents + ArchivedEventDiskSpace + ); + +%defaults = ( + TotalEvents => undef, + TotalEventDiskSpace => undef, + HourEvents => undef, + HourEventDiskSpace => undef, + DayEvents => undef, + DayEventDiskSpace => undef, + WeekEvents => undef, + WeekEventDiskSpace => undef, + MonthEvents => undef, + MonthEventDiskSpace => undef, + ArchivedEvents => undef, + ArchivedEventDiskSpace => undef, + ); + +sub Monitor { + return new ZoneMinder::Monitor( $_[0]{MonitorId} ); +} # end sub Monitor + +1; +__END__ + +=head1 NAME + +ZoneMinder::Event_Summary - Perl Class for Event Summaries + +=head1 SYNOPSIS + +use ZoneMinder::Event_Summary; + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index d68967fa9..b14b08aae 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -31,6 +31,8 @@ our %EXPORT_TAGS = ( systemStatus packageControl daemonControl + parseNameEqualsValueToHash + hash_diff ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; @@ -534,6 +536,42 @@ sub jsonDecode { return $result; } +sub parseNameEqualsValueToHash { + my %settings; + foreach my $line ( split ( /\r?\n/, $_[0] ) ) { + next if ! $line; + next if ! ( $line =~ /=/ ); + my ($name, $value ) = split('=', $line); + $value =~ s/^'//; + $value =~ s/'$//; + $settings{$name} = defined $value ? $value : ''; + } + return %settings; +} + +sub hash_diff { + # assumes keys of second hash are all in the first hash + my ( $settings, $defaults ) = @_; + my %updates; + + foreach my $setting ( keys %{$settings} ) { + next if ! exists $$defaults{$setting}; + if ( + ($$settings{$setting} and ! $$defaults{$setting}) + or + (!$$settings{$setting} and $$defaults{$setting}) + or + ( + ($$settings{$setting} and $$defaults{$setting} and ( + $$settings{$setting} ne $$defaults{$setting})) + ) + ) { + $updates{$setting} = $$defaults{$setting}; + } + } # end foreach setting + return %updates; +} + sub packageControl { my $command = shift; my $string = $Config{ZM_PATH_BIN}.'/zmpkg.pl '.$command; @@ -598,6 +636,8 @@ of the ZoneMinder scripts packageControl daemonControl systemStatus + parseNameEqualsValueToHash + hash_diff ) ] diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index d01ef3455..c5e09c137 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -35,7 +35,9 @@ require ZoneMinder::Storage; require ZoneMinder::Server; require ZoneMinder::Memory; require ZoneMinder::Monitor_Status; +require ZoneMinder::Event_Summary; require ZoneMinder::Zone; +use ZoneMinder::Logger qw(:all); #our @ISA = qw(Exporter ZoneMinder::Base); use parent qw(ZoneMinder::Object); @@ -266,6 +268,15 @@ sub Status { return $$self{Status}; } +sub Event_Summary { + my $self = shift; + $$self{Event_Summary} = shift if @_; + if ( ! $$self{Event_Summary} ) { + $$self{Event_Summary} = ZoneMinder::Event_Summary->find_one(MonitorId=>$$self{Id}); + } + return $$self{Event_Summary}; +} + sub connect { my $self = shift; return ZoneMinder::Memory::zmMemVerify($self); @@ -313,6 +324,25 @@ sub resumeMotionDetection { return 1; } +sub Control { + my $self = shift; + if ( ! exists $$self{Control}) { + require ZoneMinder::Control; + my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId}); + if ($Control) { + require Module::Load::Conditional; + if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$$Control{Protocol} => undef})) { + Error("Can't load ZoneMinder::Control::$$Control{Protocol}\n$Module::Load::Conditional::ERROR"); + return undef; + } + bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol}; + $$Control{MonitorId} = $$self{Id}; + $$self{Control} = $Control; + } + } + return $$self{Control}; +} + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm index 9a9077653..1fafd3b0b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm @@ -43,18 +43,6 @@ $serial = $primary_key = 'MonitorId'; CaptureFPS AnalysisFPS CaptureBandwidth - TotalEvents - TotalEventDiskSpace - HourEvents - HourEventDiskSpace - DayEvents - DayEventDiskSpace - WeekEvents - WeekEventDiskSpace - MonthEvents - MonthEventDiskSpace - ArchivedEvents - ArchivedEventDiskSpace ); %defaults = ( @@ -62,18 +50,6 @@ $serial = $primary_key = 'MonitorId'; CaptureFPS => undef, AnalysisFPS => undef, CaptureBandwidth => undef, - TotalEvents => undef, - TotalEventDiskSpace => undef, - HourEvents => undef, - HourEventDiskSpace => undef, - DayEvents => undef, - DayEventDiskSpace => undef, - WeekEvents => undef, - WeekEventDiskSpace => undef, - MonthEvents => undef, - MonthEventDiskSpace => undef, - ArchivedEvents => undef, - ArchivedEventDiskSpace => undef, ); sub Monitor { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index f3d750338..e54bb15ce 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -218,7 +218,7 @@ sub save { my $serial = eval '$'.$type.'::serial'; my @identified_by = eval '@'.$type.'::identified_by'; - my $ac = ZoneMinder::Database::start_transaction( $local_dbh ); + my $ac = ZoneMinder::Database::start_transaction( $local_dbh ) if $local_dbh->{AutoCommit}; if ( ! $serial ) { my $insert = $force_insert; my %serial = eval '%'.$type.'::serial'; @@ -234,8 +234,8 @@ $log->debug("No serial") if $debug; if ( ! ( ( $_ = $local_dbh->prepare("DELETE FROM `$table` WHERE $where") ) and $_->execute( @$self{@identified_by} ) ) ) { $where =~ s/\?/\%s/g; $log->error("Error deleting: DELETE FROM $table WHERE " . sprintf($where, map { defined $_ ? $_ : 'undef' } ( @$self{@identified_by}) ).'):' . $local_dbh->errstr); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $local_dbh->errstr; } elsif ( $debug ) { $log->debug("SQL succesful DELETE FROM $table WHERE $where"); @@ -267,8 +267,8 @@ $log->debug("No serial") if $debug; my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL statement execution failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $local_dbh->errstr); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -282,8 +282,8 @@ $log->debug("No serial") if $debug; my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys, @$fields{@identified_by}}) ).'):' . $local_dbh->errstr); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -321,8 +321,8 @@ $log->debug("No serial") if $debug; $command =~ s/\?/\%s/g; my $error = $local_dbh->errstr; $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $error); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -340,8 +340,8 @@ $log->debug("No serial") if $debug; my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}, @sql{@$fields{@identified_by}} ) ).'):' . $error) if $log; - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -350,7 +350,7 @@ $log->debug("No serial") if $debug; } # end if } # end if } # end if - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; #$self->load(); #if ( $$fields{id} ) { #if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) { diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index 8a93a19a4..a9f26ac80 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -30,7 +30,6 @@ use autouse 'Pod::Usage'=>qw(pod2usage); use POSIX qw/strftime EPIPE EINTR/; use Socket; use Data::Dumper; -use Module::Load::Conditional qw{can_load}; use constant MAX_CONNECT_DELAY => 15; use constant MAX_COMMAND_WAIT => 1800; @@ -102,40 +101,21 @@ if ($options{command}) { } } else { # The server isn't there - my $monitor = zmDbGetMonitorAndControl($id); + require ZoneMinder::Monitor; + + my $monitor = ZoneMinder::Monitor->find_one(Id=>$id); Fatal("Unable to load control data for monitor $id") if !$monitor; - my $protocol = $monitor->{Protocol}; + my $control = $monitor->Control(); + + my $protocol = $control->{Protocol}; if (!$protocol) { Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field'); } - if (-x $protocol) { - # Protocol is actually a script! - # Holdover from previous versions - my $command .= $protocol.' '.$arg_string; - Debug($command); - - my $output = qx($command); - my $status = $? >> 8; - if ($status || logDebugging()) { - chomp($output); - Debug("Output: $output"); - } - if ($status) { - Error("Command '$command' exited with status: $status"); - exit($status); - } - exit(0); - } - Info("Starting control server $id/$protocol"); close(CLIENT); - if (!can_load(modules => {'ZoneMinder::Control::'.$protocol => undef})) { - Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); - } - my $zm_terminate = 0; sub TermHandler { Info('Received TERM, exiting'); @@ -150,7 +130,6 @@ if ($options{command}) { $0 = $0.' --id '.$id; - my $control = ('ZoneMinder::Control::'.$protocol)->new($id); my $control_key = $control->getKey(); $control->loadMonitor(); diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 1e62cb229..61922771b 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -665,10 +665,10 @@ sub substituteTags { # We have a filter and an event, do we need any more # monitor information? my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/; - my $need_status = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; + my $need_summary = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; my $Monitor = $Event->Monitor() if $need_monitor; - my $Status = $Monitor->Status() if $need_status; + my $Summary = $Monitor->Event_Summary() if $need_summary; # Do we need the image information too? my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD|EIMODG)%/; @@ -692,19 +692,19 @@ sub substituteTags { } $rows ++; } - Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alaarm_frame: $max_alarm_frame, score: $max_alarm_score"); + Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alarm_frame: $max_alarm_frame, score: $max_alarm_score"); $sth->finish(); } my $url = $Config{ZM_URL}; $text =~ s/%ZP%/$url/g; $text =~ s/%MN%/$Monitor->{Name}/g; - $text =~ s/%MET%/$Status->{TotalEvents}/g; - $text =~ s/%MEH%/$Status->{HourEvents}/g; - $text =~ s/%MED%/$Status->{DayEvents}/g; - $text =~ s/%MEW%/$Status->{WeekEvents}/g; - $text =~ s/%MEM%/$Status->{MonthEvents}/g; - $text =~ s/%MEA%/$Status->{ArchivedEvents}/g; + $text =~ s/%MET%/$Summary->{TotalEvents}/g; + $text =~ s/%MEH%/$Summary->{HourEvents}/g; + $text =~ s/%MED%/$Summary->{DayEvents}/g; + $text =~ s/%MEW%/$Summary->{WeekEvents}/g; + $text =~ s/%MEM%/$Summary->{MonthEvents}/g; + $text =~ s/%MEA%/$Summary->{ArchivedEvents}/g; $text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g; $text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g; $text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f37ff0ab..80e09e9c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -107,6 +107,7 @@ endif() add_executable(zmc zmc.cpp) add_executable(zms zms.cpp) add_executable(zmu zmu.cpp) +add_executable(zmbenchmark zmbenchmark.cpp) target_link_libraries(zmc PRIVATE @@ -129,6 +130,13 @@ target_link_libraries(zmu ${ZM_EXTRA_LIBS} ${CMAKE_DL_LIBS}) +target_link_libraries(zmbenchmark + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${CMAKE_DL_LIBS}) + # Generate man files for the binaries destined for the bin folder if(BUILD_MAN) foreach(CBINARY zmc zmu) diff --git a/src/zm_analysis_thread.cpp b/src/zm_analysis_thread.cpp index 7d803c16b..325d8de59 100644 --- a/src/zm_analysis_thread.cpp +++ b/src/zm_analysis_thread.cpp @@ -23,14 +23,6 @@ void AnalysisThread::Start() { void AnalysisThread::Run() { while (!(terminate_ or zm_terminate)) { - // Some periodic updates are required for variable capturing framerate - Debug(2, "Analyzing"); - if (!monitor_->Analyse()) { - if (!(terminate_ or zm_terminate)) { - Microseconds sleep_for = monitor_->Active() ? Microseconds(ZM_SAMPLE_RATE) : Microseconds(ZM_SUSPENDED_RATE); - Debug(2, "Sleeping for %" PRId64 "us", int64(sleep_for.count())); - std::this_thread::sleep_for(sleep_for); - } - } + monitor_->Analyse(); } } diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index 4c9f7c737..27a213ed1 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -1,17 +1,17 @@ // // ZoneMinder cURL Camera Class Implementation, $Date: 2009-01-16 12:18:50 +0000 (Fri, 16 Jan 2009) $, $Revision: 2713 $ // 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. @@ -73,19 +73,35 @@ void bind_libcurl_symbols() { *(void**) (&curl_easy_cleanup_f) = dlsym(curl_lib, "curl_easy_cleanup"); } -cURLCamera::cURLCamera( const Monitor* monitor, 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( monitor, 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 ) +cURLCamera::cURLCamera( + const Monitor* monitor, + 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(monitor, 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) { - - if ( capture ) { + if (capture) { Initialise(); } } cURLCamera::~cURLCamera() { - if ( capture ) { - + if (capture) { Terminate(); } } @@ -95,40 +111,40 @@ void cURLCamera::Initialise() { content_type_match_len = strlen(content_type_match); databuffer.expand(CURL_BUFFER_INITIAL_SIZE); - + bind_libcurl_symbols(); /* cURL initialization */ CURLcode cRet = (*curl_global_init_f)(CURL_GLOBAL_ALL); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("libcurl initialization failed: %s", (*curl_easy_strerror_f)(cRet)); dlclose(curl_lib); return; } - Debug(2,"libcurl version: %s", (*curl_version_f)()); + Debug(2, "libcurl version: %s", (*curl_version_f)()); /* Create the shared data mutex */ int nRet = pthread_mutex_init(&shareddata_mutex, nullptr); - if(nRet != 0) { + if (nRet != 0) { Error("Shared data mutex creation failed: %s",strerror(nRet)); return; } /* Create the data available condition variable */ nRet = pthread_cond_init(&data_available_cond, nullptr); - if(nRet != 0) { + if (nRet != 0) { Error("Data available condition variable creation failed: %s",strerror(nRet)); return; } /* Create the request complete condition variable */ nRet = pthread_cond_init(&request_complete_cond, nullptr); - if(nRet != 0) { + if (nRet != 0) { Error("Request complete condition variable creation failed: %s",strerror(nRet)); return; } /* Create the thread */ nRet = pthread_create(&thread, nullptr, thread_func_dispatcher, this); - if(nRet != 0) { + if (nRet != 0) { Error("Thread creation failed: %s",strerror(nRet)); return; } @@ -151,18 +167,19 @@ void cURLCamera::Terminate() { /* cURL cleanup */ (*curl_global_cleanup_f)(); - if(curl_lib) + if (curl_lib) dlclose(curl_lib); } int cURLCamera::PrimeCapture() { + getVideoStream(); //Info( "Priming capture from %s", mPath.c_str() ); - return 0; + return 1; } int cURLCamera::PreCapture() { - // Nothing to do here - return( 0 ); + // Nothing to do here + return 1; } int cURLCamera::Capture(std::shared_ptr &zm_packet) { @@ -178,10 +195,9 @@ int cURLCamera::Capture(std::shared_ptr &zm_packet) { /* Grab the mutex to ensure exclusive access to the shared data */ lock(); - while ( !frameComplete ) { - + while (!frameComplete) { /* If the work thread did a reset, reset our local variables */ - if ( bReset ) { + if (bReset) { SubHeadersParsingComplete = false; frame_content_length = 0; frame_content_type.clear(); @@ -189,40 +205,40 @@ int cURLCamera::Capture(std::shared_ptr &zm_packet) { bReset = false; } - if ( mode == MODE_UNSET ) { + if (mode == MODE_UNSET) { /* Don't have a mode yet. Sleep while waiting for data */ nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); - if ( nRet != 0 ) { + if (nRet != 0) { Error("Failed waiting for available data condition variable: %s",strerror(nRet)); return -1; } } - if ( mode == MODE_STREAM ) { + if (mode == MODE_STREAM) { /* Subheader parsing */ - while( !SubHeadersParsingComplete && !need_more_data ) { + while (!SubHeadersParsingComplete && !need_more_data) { size_t crlf_start, crlf_end, crlf_size; std::string subheader; /* Check if the buffer contains something */ - if ( databuffer.empty() ) { + if (databuffer.empty()) { /* Empty buffer, wait for data */ need_more_data = true; break; } - + /* Find crlf start */ crlf_start = memcspn(reinterpret_cast(databuffer.head()),"\r\n",databuffer.size()); - if ( crlf_start == databuffer.size() ) { + if (crlf_start == databuffer.size()) { /* Not found, wait for more data */ need_more_data = true; break; } /* See if we have enough data for determining crlf length */ - if ( databuffer.size() < crlf_start+5 ) { + if (databuffer.size() < crlf_start+5) { /* Need more data */ need_more_data = true; break; @@ -233,18 +249,22 @@ int cURLCamera::Capture(std::shared_ptr &zm_packet) { crlf_size = (crlf_start + crlf_end) - crlf_start; /* Is this the end of a previous stream? (This is just before the boundary) */ - if ( crlf_start == 0 ) { + if (crlf_start == 0) { databuffer.consume(crlf_size); - continue; + continue; } /* Check for invalid CRLF size */ - if ( crlf_size > 4 ) { + if (crlf_size > 4) { Error("Invalid CRLF length"); } /* Check if the crlf is \n\n or \r\n\r\n (marks end of headers, this is the last header) */ - if( (crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0) || (crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0) ) { + if ( + (crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0) + || + (crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0) + ) { /* This is the last header */ SubHeadersParsingComplete = true; } @@ -255,48 +275,56 @@ int cURLCamera::Capture(std::shared_ptr &zm_packet) { /* Advance the buffer past this one */ databuffer.consume(crlf_start+crlf_size); - Debug(7,"Got subheader: %s",subheader.c_str()); + Debug(7, "Got subheader: %s",subheader.c_str()); /* Find where the data in this header starts */ size_t subheader_data_start = subheader.rfind(' '); - if ( subheader_data_start == std::string::npos ) { + if (subheader_data_start == std::string::npos) { subheader_data_start = subheader.find(':'); } /* Extract the data into a string */ std::string subheader_data = subheader.substr(subheader_data_start+1, std::string::npos); - Debug(8,"Got subheader data: %s",subheader_data.c_str()); + Debug(8, "Got subheader data: %s", subheader_data.c_str()); /* Check the header */ - if(strncasecmp(subheader.c_str(),content_length_match,content_length_match_len) == 0) { + if (strncasecmp(subheader.c_str(), content_length_match, content_length_match_len) == 0) { /* Found the content-length header */ frame_content_length = atoi(subheader_data.c_str()); Debug(6,"Got content-length subheader: %d",frame_content_length); - } else if(strncasecmp(subheader.c_str(),content_type_match,content_type_match_len) == 0) { + } else if (strncasecmp(subheader.c_str(), content_type_match, content_type_match_len) == 0) { /* Found the content-type header */ frame_content_type = subheader_data; - Debug(6,"Got content-type subheader: %s",frame_content_type.c_str()); + Debug(6,"Got content-type subheader: %s", frame_content_type.c_str()); } } /* Attempt to extract the frame */ - if(!need_more_data) { - if(!SubHeadersParsingComplete) { + if (!need_more_data) { + if (!SubHeadersParsingComplete) { /* We haven't parsed all headers yet */ need_more_data = true; - } else if ( ! frame_content_length ) { + } else if (!frame_content_length) { /* Invalid frame */ Error("Invalid frame: invalid content length"); - } else if ( frame_content_type != "image/jpeg" ) { + } else if (frame_content_type != "image/jpeg") { /* Unsupported frame type */ - Error("Unsupported frame: %s",frame_content_type.c_str()); - } else if(frame_content_length > databuffer.size()) { + Error("Unsupported frame: %s", frame_content_type.c_str()); + } else if (frame_content_length > databuffer.size()) { /* Incomplete frame, wait for more data */ need_more_data = true; } else { /* All good. decode the image */ + if (!zm_packet->image) { + Debug(4, "Allocating image"); + zm_packet->image = new Image(width, height, colours, subpixelorder); + } + zm_packet->keyframe = 1; + zm_packet->codec_type = AVMEDIA_TYPE_VIDEO; + zm_packet->packet.stream_index = mVideoStreamId; + zm_packet->stream = mVideoStream; zm_packet->image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); frameComplete = true; } @@ -312,11 +340,19 @@ int cURLCamera::Capture(std::shared_ptr &zm_packet) { need_more_data = false; } - } else if (mode == MODE_SINGLE) { + } else if (mode == MODE_SINGLE) { /* Check if we have anything */ if (!single_offsets.empty()) { if ((single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front())) { /* Extract frame */ + if (!zm_packet->image) { + Debug(4, "Allocating image"); + zm_packet->image = new Image(width, height, colours, subpixelorder); + } + zm_packet->keyframe = 1; + zm_packet->codec_type = AVMEDIA_TYPE_VIDEO; + zm_packet->packet.stream_index = mVideoStreamId; + zm_packet->stream = mVideoStream; zm_packet->image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); single_offsets.pop_front(); frameComplete = true; @@ -345,15 +381,15 @@ int cURLCamera::Capture(std::shared_ptr &zm_packet) { /* Release the mutex */ unlock(); - if(!frameComplete) + if (!frameComplete) return 0; return 1; } int cURLCamera::PostCapture() { - // Nothing to do here - return( 0 ); + // Nothing to do here + return 1; } size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { @@ -364,7 +400,7 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void * /* Signal data available */ int nRet = pthread_cond_signal(&data_available_cond); - if ( nRet != 0 ) { + if (nRet != 0) { Error("Failed signaling data available condition variable: %s",strerror(nRet)); unlock(); return -16; @@ -379,18 +415,18 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void * size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) { std::string header; header.assign((const char*)buffer, size*nmemb); - - Debug(4,"Got header: %s",header.c_str()); - /* Check Content-Type header */ - if(strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) { + Debug(4, "Got header: %s", header.c_str()); + + /* Check Content-Type header */ + if (strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) { size_t pos = header.find(';'); - if(pos != std::string::npos) { + if (pos != std::string::npos) { header.erase(pos, std::string::npos); } pos = header.rfind(' '); - if(pos == std::string::npos) { + if (pos == std::string::npos) { pos = header.find(':'); } @@ -401,17 +437,17 @@ size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, voi const char* multipart_match = "multipart/x-mixed-replace"; const char* image_jpeg_match = "image/jpeg"; - if(strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) { - Debug(7,"Content type matched as multipart/x-mixed-replace"); + if (strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) { + Debug(7, "Content type matched as multipart/x-mixed-replace"); mode = MODE_STREAM; - } else if(strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) { - Debug(7,"Content type matched as image/jpeg"); + } else if (strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) { + Debug(7, "Content type matched as image/jpeg"); mode = MODE_SINGLE; } unlock(); } - + /* Return bytes processed */ return size*nmemb; } @@ -421,7 +457,7 @@ void* cURLCamera::thread_func() { double dSize; c = (*curl_easy_init_f)(); - if(c == nullptr) { + if (c == nullptr) { dlclose(curl_lib); Error("Failed getting easy handle from libcurl"); tRet = -51; @@ -431,99 +467,99 @@ void* cURLCamera::thread_func() { CURLcode cRet; /* Set URL */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_URL, mPath.c_str()); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl URL: %s", (*curl_easy_strerror_f)(cRet)); tRet = -52; return (void*)tRet; } - + /* Header callback */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl header callback function: %s", (*curl_easy_strerror_f)(cRet)); tRet = -53; return (void*)tRet; } - + cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERDATA, this); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl header callback object: %s", (*curl_easy_strerror_f)(cRet)); tRet = -54; return (void*)tRet; } /* Data callback */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl data callback function: %s", (*curl_easy_strerror_f)(cRet)); tRet = -55; return (void*)tRet; } cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEDATA, this); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl data callback object: %s", (*curl_easy_strerror_f)(cRet)); tRet = -56; return (void*)tRet; } /* Progress callback */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_NOPROGRESS, 0); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed enabling libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); tRet = -57; return (void*)tRet; } - + cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); tRet = -58; return (void*)tRet; } - + cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSDATA, this); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl progress callback object: %s", (*curl_easy_strerror_f)(cRet)); tRet = -59; return (void*)tRet; } /* Set username and password */ - if(!mUser.empty()) { + if (!mUser.empty()) { cRet = (*curl_easy_setopt_f)(c, CURLOPT_USERNAME, mUser.c_str()); - if(cRet != CURLE_OK) + if (cRet != CURLE_OK) Error("Failed setting username: %s", (*curl_easy_strerror_f)(cRet)); } - if(!mPass.empty()) { + if (!mPass.empty()) { cRet = (*curl_easy_setopt_f)(c, CURLOPT_PASSWORD, mPass.c_str()); - if(cRet != CURLE_OK) + if (cRet != CURLE_OK) Error("Failed setting password: %s", (*curl_easy_strerror_f)(cRet)); } /* Authenication preference */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - if(cRet != CURLE_OK) + if (cRet != CURLE_OK) Warning("Failed setting libcurl acceptable http authenication methods: %s", (*curl_easy_strerror_f)(cRet)); /* Work loop */ - for(int attempt=1;attempt<=CURL_MAXRETRY;attempt++) { + for (int attempt=1;attempt<=CURL_MAXRETRY;attempt++) { tRet = 0; - while(!bTerminate) { + while (!bTerminate) { /* Do the work */ cRet = (*curl_easy_perform_f)(c); - if(mode == MODE_SINGLE) { - if(cRet != CURLE_OK) { + if (mode == MODE_SINGLE) { + if (cRet != CURLE_OK) { break; } /* Attempt to get the size of the file */ cRet = (*curl_easy_getinfo_f)(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { break; } /* We need to lock for the offsets array and the condition variable */ lock(); /* Push the size into our offsets array */ - if(dSize > 0) { + if (dSize > 0) { single_offsets.push_back(dSize); } else { Error("Unable to get the size of the image"); @@ -532,7 +568,7 @@ void* cURLCamera::thread_func() { } /* Signal the request complete condition variable */ tRet = pthread_cond_signal(&request_complete_cond); - if(tRet != 0) { + if (tRet != 0) { Error("Failed signaling request completed condition variable: %s",strerror(tRet)); tRet = -61; return (void*)tRet; @@ -546,13 +582,13 @@ void* cURLCamera::thread_func() { } /* Return value checking */ - if(cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) { + if (cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) { /* Aborted */ break; } else if (cRet != CURLE_OK) { /* Some error */ Error("cURL Request failed: %s",(*curl_easy_strerror_f)(cRet)); - if(attempt < CURL_MAXRETRY) { + if (attempt < CURL_MAXRETRY) { Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY); /* Do a reset */ lock(); @@ -565,11 +601,11 @@ void* cURLCamera::thread_func() { tRet = -50; } } - + /* Cleanup */ (*curl_easy_cleanup_f)(c); c = nullptr; - + return (void*)tRet; } @@ -597,9 +633,9 @@ int cURLCamera::unlock() { int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) { /* Signal the curl thread to terminate */ - if(bTerminate) + if (bTerminate) return -10; - + return 0; } diff --git a/src/zm_db.cpp b/src/zm_db.cpp index c6cc452b9..e3b737c07 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -251,6 +251,13 @@ void zmDbQueue::process() { mCondition.wait(lock); } while (!mQueue.empty()) { + if (mQueue.size() > 10) { + Logger *log = Logger::fetch(); + Logger::Level db_level = log->databaseLevel(); + log->databaseLevel(Logger::NOLOG); + Warning("db queue size has grown larger %zu than 10 entries", mQueue.size()); + log->databaseLevel(db_level); + } std::string sql = mQueue.front(); mQueue.pop(); // My idea for leaving the locking around each sql statement is to allow diff --git a/src/zm_event.cpp b/src/zm_event.cpp index adda18655..442033c50 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -60,8 +60,8 @@ Event::Event( //snapshit_file(), //alarm_file(""), videoStore(nullptr), - //video_name(""), //video_file(""), + //video_path(""), last_db_frame(0), have_video_keyframe(false), //scheme @@ -104,6 +104,13 @@ Event::Event( // Copy it in case opening the mp4 doesn't work we can set it to another value save_jpegs = monitor->GetOptSaveJPEGs(); Storage * storage = monitor->getStorage(); + if (monitor->GetOptVideoWriter() != 0) { + container = monitor->OutputContainer(); + if ( container == "auto" || container == "" ) { + container = "mp4"; + } + video_incomplete_file = "incomplete."+container; + } std::string sql = stringtf( "INSERT INTO `Events` " @@ -120,7 +127,7 @@ Event::Event( state_id, monitor->getOrientation(), 0, - "", + video_incomplete_file.c_str(), save_jpegs, storage->SchemeString().c_str() ); @@ -178,24 +185,16 @@ Event::Event( } // end if ! setPath(Storage) Debug(1, "Using storage area at %s", path.c_str()); - video_name = ""; - snapshot_file = path + "/snapshot.jpg"; alarm_file = path + "/alarm.jpg"; - /* Save as video */ + video_incomplete_path = path + "/" + video_incomplete_file; - if ( monitor->GetOptVideoWriter() != 0 ) { - std::string container = monitor->OutputContainer(); - if ( container == "auto" || container == "" ) { - container = "mp4"; - } + if (monitor->GetOptVideoWriter() != 0) { + /* Save as video */ - video_name = stringtf("%" PRIu64 "-%s.%s", id, "video", container.c_str()); - video_file = path + "/" + video_name; - Debug(1, "Writing video file to %s", video_file.c_str()); videoStore = new VideoStore( - video_file.c_str(), + video_incomplete_path.c_str(), container.c_str(), monitor->GetVideoStream(), monitor->GetVideoCodecContext(), @@ -213,8 +212,10 @@ Event::Event( zmDbDo(sql); } } else { - sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id); - zmDbDo(sql); + std::string codec = videoStore->get_codec(); + video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str()); + video_path = path + "/" + video_file; + Debug(1, "Video file is %s", video_file.c_str()); } } // end if GetOptVideoWriter } @@ -223,10 +224,18 @@ Event::~Event() { // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. /* Close the video file */ - if ( videoStore != nullptr ) { + if (videoStore != nullptr) { Debug(4, "Deleting video store"); delete videoStore; videoStore = nullptr; + int result = rename(video_incomplete_path.c_str(), video_path.c_str()); + if (result == 0) { + Debug(1, "File successfully renamed"); + } else { + Error("Failed renaming %s to %s", video_incomplete_path.c_str(), video_path.c_str()); + // So that we don't update the event record + video_file = video_incomplete_file; + } } // endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp. @@ -245,21 +254,23 @@ Event::~Event() { } std::string sql = stringtf( - "UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64 " AND Name='New Event'", + "UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64 " AND Name='New Event'", monitor->EventPrefix(), id, std::chrono::system_clock::to_time_t(end_time), delta_time.count(), frames, alarm_frames, tot_score, static_cast(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, + video_file.c_str(), // defaults to "" id); if (!zmDbDoUpdate(sql)) { // Name might have been changed during recording, so just do the update without changing the name. sql = stringtf( - "UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, + "UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64, std::chrono::system_clock::to_time_t(end_time), delta_time.count(), frames, alarm_frames, tot_score, static_cast(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, + video_file.c_str(), // defaults to "" id); zmDbDoUpdate(sql); } // end if no changed rows due to Name change during recording @@ -479,7 +490,7 @@ void Event::AddFrame(Image *image, Debug(1, "Writing snapshot"); WriteFrameImage(image, timestamp, snapshot_file.c_str()); } else { - Debug(1, "Not Writing snapshot"); + Debug(1, "Not Writing snapshot because score %d > max %d", score, max_score); } // We are writing an Alarm frame @@ -491,7 +502,7 @@ void Event::AddFrame(Image *image, Debug(1, "Writing alarm image"); WriteFrameImage(image, timestamp, alarm_file.c_str()); } else { - Debug(1, "Not Writing alarm image"); + Debug(3, "Not Writing alarm image because alarm frame already written"); } if (alarm_image and (save_jpegs & 2)) { diff --git a/src/zm_event.h b/src/zm_event.h index 8b8e49322..611b2f716 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -84,8 +84,13 @@ class Event { std::string alarm_file; VideoStore *videoStore; - std::string video_name; + std::string container; + std::string codec; std::string video_file; + std::string video_path; + std::string video_incomplete_file; + std::string video_incomplete_path; + int last_db_frame; bool have_video_keyframe; // a flag to tell us if we have had a video keyframe when writing an mp4. The first frame SHOULD be a video keyframe. Storage::Schemes scheme; diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 4bad2d61a..5e1ad605d 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -663,6 +663,7 @@ bool EventStream::checkEventLoaded() { else curr_frame_id = 1; Debug(2, "New frame id = %ld", curr_frame_id); + start = std::chrono::system_clock::now(); return true; } else { Debug(2, "No next event loaded using %s. Pausing", sql.c_str()); @@ -810,7 +811,7 @@ bool EventStream::sendFrame(Microseconds delta_us) { fputs("Content-Type: image/x-rgbz\r\n", stdout); break; case STREAM_RAW : - img_buffer = (uint8_t*)(send_image->Buffer()); + img_buffer = send_image->Buffer(); img_buffer_size = send_image->Size(); fputs("Content-Type: image/x-rgb\r\n", stdout); break; @@ -957,18 +958,20 @@ void EventStream::runStream() { static_cast(std::chrono::duration_cast(delta).count())); // if effective > base we should speed up frame delivery - delta = std::chrono::duration_cast((delta * base_fps) / effective_fps); - Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f)", + if (base_fps < effective_fps) { + delta = std::chrono::duration_cast((delta * base_fps) / effective_fps); + Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f)", static_cast(std::chrono::duration_cast(delta).count()), base_fps, effective_fps); - // but must not exceed maxfps - delta = std::max(delta, Microseconds(lround(Microseconds::period::den / maxfps))); - Debug(3, "delta %" PRIi64 " us = base_fps (%f) /effective_fps (%f) from 30fps", + // but must not exceed maxfps + delta = std::max(delta, Microseconds(lround(Microseconds::period::den / maxfps))); + Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f) from 30fps", static_cast(std::chrono::duration_cast(delta).count()), base_fps, effective_fps); + } // +/- 1? What if we are skipping frames? curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod; diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 2b7bc69e2..295426254 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -270,7 +270,6 @@ int Image::PopulateFrame(AVFrame *frame) { frame->width = width; frame->height = height; frame->format = imagePixFormat; - Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d", width, height, linesize, colours, size); zm_dump_video_frame(frame, "Image.Populate(frame)"); return 1; } // int Image::PopulateFrame(AVFrame *frame) diff --git a/src/zm_image.h b/src/zm_image.h index 24626d789..74e5931eb 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -179,9 +179,11 @@ class Image { } } - /* Internal buffer should not be modified from functions outside of this class */ + inline uint8_t* Buffer() { return buffer; } inline const uint8_t* Buffer() const { return buffer; } + inline uint8_t* Buffer(unsigned int x, unsigned int y=0) { return &buffer[(y*linesize) + x*colours]; } inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize) + x*colours]; } + /* Request writeable buffer */ uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); // Is only acceptable on a pre-allocated buffer diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index 8f38b4d99..68465fa21 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -49,130 +49,128 @@ static int vidioctl(int fd, int request, void *arg) { static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) { _AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE; - if ( v4l_version == 2 ) { - switch ( palette ) { + switch (palette) { #if defined(V4L2_PIX_FMT_RGB444) && defined(AV_PIX_FMT_RGB444) - case V4L2_PIX_FMT_RGB444 : - pixFormat = AV_PIX_FMT_RGB444; - break; + case V4L2_PIX_FMT_RGB444 : + pixFormat = AV_PIX_FMT_RGB444; + break; #endif // V4L2_PIX_FMT_RGB444 - case V4L2_PIX_FMT_RGB555 : - pixFormat = AV_PIX_FMT_RGB555; - break; - case V4L2_PIX_FMT_RGB565 : - pixFormat = AV_PIX_FMT_RGB565; - break; - case V4L2_PIX_FMT_BGR24 : - pixFormat = AV_PIX_FMT_BGR24; - break; - case V4L2_PIX_FMT_RGB24 : - pixFormat = AV_PIX_FMT_RGB24; - break; - case V4L2_PIX_FMT_BGR32 : - pixFormat = AV_PIX_FMT_BGRA; - break; - case V4L2_PIX_FMT_RGB32 : - pixFormat = AV_PIX_FMT_ARGB; - break; - case V4L2_PIX_FMT_GREY : - pixFormat = AV_PIX_FMT_GRAY8; - break; - case V4L2_PIX_FMT_YUYV : - pixFormat = AV_PIX_FMT_YUYV422; - break; - case V4L2_PIX_FMT_YUV422P : - pixFormat = AV_PIX_FMT_YUV422P; - break; - case V4L2_PIX_FMT_YUV411P : - pixFormat = AV_PIX_FMT_YUV411P; - break; + case V4L2_PIX_FMT_RGB555 : + pixFormat = AV_PIX_FMT_RGB555; + break; + case V4L2_PIX_FMT_RGB565 : + pixFormat = AV_PIX_FMT_RGB565; + break; + case V4L2_PIX_FMT_BGR24 : + pixFormat = AV_PIX_FMT_BGR24; + break; + case V4L2_PIX_FMT_RGB24 : + pixFormat = AV_PIX_FMT_RGB24; + break; + case V4L2_PIX_FMT_BGR32 : + pixFormat = AV_PIX_FMT_BGRA; + break; + case V4L2_PIX_FMT_RGB32 : + pixFormat = AV_PIX_FMT_ARGB; + break; + case V4L2_PIX_FMT_GREY : + pixFormat = AV_PIX_FMT_GRAY8; + break; + case V4L2_PIX_FMT_YUYV : + pixFormat = AV_PIX_FMT_YUYV422; + break; + case V4L2_PIX_FMT_YUV422P : + pixFormat = AV_PIX_FMT_YUV422P; + break; + case V4L2_PIX_FMT_YUV411P : + pixFormat = AV_PIX_FMT_YUV411P; + break; #ifdef V4L2_PIX_FMT_YUV444 - case V4L2_PIX_FMT_YUV444 : - pixFormat = AV_PIX_FMT_YUV444P; - break; + case V4L2_PIX_FMT_YUV444 : + pixFormat = AV_PIX_FMT_YUV444P; + break; #endif // V4L2_PIX_FMT_YUV444 - case V4L2_PIX_FMT_YUV410 : - pixFormat = AV_PIX_FMT_YUV410P; + case V4L2_PIX_FMT_YUV410 : + pixFormat = AV_PIX_FMT_YUV410P; + break; + case V4L2_PIX_FMT_YUV420 : + pixFormat = AV_PIX_FMT_YUV420P; + break; + case V4L2_PIX_FMT_JPEG : + case V4L2_PIX_FMT_MJPEG : + pixFormat = AV_PIX_FMT_YUVJ444P; + break; + case V4L2_PIX_FMT_UYVY : + pixFormat = AV_PIX_FMT_UYVY422; + break; + // These don't seem to have ffmpeg equivalents + // See if you can match any of the ones in the default clause below!? + case V4L2_PIX_FMT_RGB332 : + case V4L2_PIX_FMT_RGB555X : + case V4L2_PIX_FMT_RGB565X : + //case V4L2_PIX_FMT_Y16 : + //case V4L2_PIX_FMT_PAL8 : + case V4L2_PIX_FMT_YVU410 : + case V4L2_PIX_FMT_YVU420 : + case V4L2_PIX_FMT_Y41P : + //case V4L2_PIX_FMT_YUV555 : + //case V4L2_PIX_FMT_YUV565 : + //case V4L2_PIX_FMT_YUV32 : + case V4L2_PIX_FMT_NV12 : + case V4L2_PIX_FMT_NV21 : + case V4L2_PIX_FMT_YYUV : + case V4L2_PIX_FMT_HI240 : + case V4L2_PIX_FMT_HM12 : + //case V4L2_PIX_FMT_SBGGR8 : + //case V4L2_PIX_FMT_SGBRG8 : + //case V4L2_PIX_FMT_SBGGR16 : + case V4L2_PIX_FMT_DV : + case V4L2_PIX_FMT_MPEG : + case V4L2_PIX_FMT_WNVA : + case V4L2_PIX_FMT_SN9C10X : + case V4L2_PIX_FMT_PWC1 : + case V4L2_PIX_FMT_PWC2 : + case V4L2_PIX_FMT_ET61X251 : + //case V4L2_PIX_FMT_SPCA501 : + //case V4L2_PIX_FMT_SPCA505 : + //case V4L2_PIX_FMT_SPCA508 : + //case V4L2_PIX_FMT_SPCA561 : + //case V4L2_PIX_FMT_PAC207 : + //case V4L2_PIX_FMT_PJPG : + //case V4L2_PIX_FMT_YVYU : + default : + { + Fatal("Can't find swscale format for palette %d", palette); break; - case V4L2_PIX_FMT_YUV420 : - pixFormat = AV_PIX_FMT_YUV420P; - break; - case V4L2_PIX_FMT_JPEG : - case V4L2_PIX_FMT_MJPEG : - pixFormat = AV_PIX_FMT_YUVJ444P; - break; - case V4L2_PIX_FMT_UYVY : - pixFormat = AV_PIX_FMT_UYVY422; - break; - // These don't seem to have ffmpeg equivalents - // See if you can match any of the ones in the default clause below!? - case V4L2_PIX_FMT_RGB332 : - case V4L2_PIX_FMT_RGB555X : - case V4L2_PIX_FMT_RGB565X : - //case V4L2_PIX_FMT_Y16 : - //case V4L2_PIX_FMT_PAL8 : - case V4L2_PIX_FMT_YVU410 : - case V4L2_PIX_FMT_YVU420 : - case V4L2_PIX_FMT_Y41P : - //case V4L2_PIX_FMT_YUV555 : - //case V4L2_PIX_FMT_YUV565 : - //case V4L2_PIX_FMT_YUV32 : - case V4L2_PIX_FMT_NV12 : - case V4L2_PIX_FMT_NV21 : - case V4L2_PIX_FMT_YYUV : - case V4L2_PIX_FMT_HI240 : - case V4L2_PIX_FMT_HM12 : - //case V4L2_PIX_FMT_SBGGR8 : - //case V4L2_PIX_FMT_SGBRG8 : - //case V4L2_PIX_FMT_SBGGR16 : - case V4L2_PIX_FMT_DV : - case V4L2_PIX_FMT_MPEG : - case V4L2_PIX_FMT_WNVA : - case V4L2_PIX_FMT_SN9C10X : - case V4L2_PIX_FMT_PWC1 : - case V4L2_PIX_FMT_PWC2 : - case V4L2_PIX_FMT_ET61X251 : - //case V4L2_PIX_FMT_SPCA501 : - //case V4L2_PIX_FMT_SPCA505 : - //case V4L2_PIX_FMT_SPCA508 : - //case V4L2_PIX_FMT_SPCA561 : - //case V4L2_PIX_FMT_PAC207 : - //case V4L2_PIX_FMT_PJPG : - //case V4L2_PIX_FMT_YVYU : - default : - { - Fatal("Can't find swscale format for palette %d", palette); - break; #if 0 - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; + // These are all spare and may match some of the above + pixFormat = AV_PIX_FMT_YUVJ420P; + pixFormat = AV_PIX_FMT_YUVJ422P; + pixFormat = AV_PIX_FMT_UYVY422; + pixFormat = AV_PIX_FMT_UYYVYY411; + pixFormat = AV_PIX_FMT_BGR565; + pixFormat = AV_PIX_FMT_BGR555; + pixFormat = AV_PIX_FMT_BGR8; + pixFormat = AV_PIX_FMT_BGR4; + pixFormat = AV_PIX_FMT_BGR4_BYTE; + pixFormat = AV_PIX_FMT_RGB8; + pixFormat = AV_PIX_FMT_RGB4; + pixFormat = AV_PIX_FMT_RGB4_BYTE; + pixFormat = AV_PIX_FMT_NV12; + pixFormat = AV_PIX_FMT_NV21; + pixFormat = AV_PIX_FMT_RGB32_1; + pixFormat = AV_PIX_FMT_BGR32_1; + pixFormat = AV_PIX_FMT_GRAY16BE; + pixFormat = AV_PIX_FMT_GRAY16LE; + pixFormat = AV_PIX_FMT_YUV440P; + pixFormat = AV_PIX_FMT_YUVJ440P; + pixFormat = AV_PIX_FMT_YUVA420P; + //pixFormat = AV_PIX_FMT_VDPAU_H264; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; #endif - } - } // end switch palette - } // end if v4l2 + } + } // end switch palette return pixFormat; } // end getFfPixFormatFromV4lPalette @@ -259,12 +257,12 @@ LocalCamera::LocalCamera( v4l_multi_buffer = p_v4l_multi_buffer; v4l_captures_per_frame = p_v4l_captures_per_frame; - if ( capture ) { - if ( device_prime ) { + if (capture) { + if (device_prime) { Debug(2, "V4L support enabled, using V4L%d api", v4l_version); } - if ( (!last_camera) || (channel != last_camera->channel) ) { + if ((!last_camera) || (channel != last_camera->channel)) { // We are the first, or only, input that uses this channel channel_prime = true; channel_index = channel_count++; @@ -278,10 +276,10 @@ LocalCamera::LocalCamera( /* The V4L1 API doesn't care about endianness, we need to check the endianness of the machine */ uint32_t checkval = 0xAABBCCDD; - if ( *(unsigned char*)&checkval == 0xDD ) { + if (*(unsigned char*)&checkval == 0xDD) { BigEndian = 0; Debug(2, "little-endian processor detected"); - } else if ( *(unsigned char*)&checkval == 0xAA ) { + } else if (*(unsigned char*)&checkval == 0xAA) { BigEndian = 1; Debug(2, "Big-endian processor detected"); } else { @@ -289,15 +287,15 @@ LocalCamera::LocalCamera( BigEndian = 0; } - if ( v4l_version == 2 && palette == 0 ) { + if (palette == 0) { /* Use automatic format selection */ Debug(2,"Using automatic format selection"); palette = AutoSelectFormat(colours); - if ( palette == 0 ) { + if (palette == 0) { Error("Automatic format selection failed. Falling back to YUYV"); palette = V4L2_PIX_FMT_YUYV; } else { - if ( capture ) { + if (capture) { Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", palette_desc, static_cast((palette >> 24) & 0xff), @@ -310,9 +308,6 @@ LocalCamera::LocalCamera( if (capture) { if (last_camera) { - if ((p_method == "v4l2" && v4l_version != 2) || (p_method == "v4l1" && v4l_version != 1)) - Fatal("Different Video For Linux version used for monitors sharing same device"); - if (standard != last_camera->standard) Warning("Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong"); @@ -324,134 +319,131 @@ LocalCamera::LocalCamera( } /* Get ffmpeg pixel format based on capture palette and endianness */ - capturePixFormat = getFfPixFormatFromV4lPalette( v4l_version, palette ); + capturePixFormat = getFfPixFormatFromV4lPalette(v4l_version, palette); imagePixFormat = AV_PIX_FMT_NONE; } - /* V4L2 format matching */ - if ( v4l_version == 2 ) { - /* Try to find a match for the selected palette and target colourspace */ + /* Try to find a match for the selected palette and target colourspace */ - /* RGB32 palette and 32bit target colourspace */ - if ( palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_RGB32 ) { - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_ARGB; + /* RGB32 palette and 32bit target colourspace */ + if (palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_RGB32) { + conversion_type = 0; + subpixelorder = ZM_SUBPIX_ORDER_ARGB; - /* BGR32 palette and 32bit target colourspace */ - } else if ( palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_RGB32 ) { - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_BGRA; + /* BGR32 palette and 32bit target colourspace */ + } else if (palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_RGB32) { + conversion_type = 0; + subpixelorder = ZM_SUBPIX_ORDER_BGRA; - /* RGB24 palette and 24bit target colourspace */ - } else if ( palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24 ) { - conversion_type = 0; - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_BGR; + /* RGB24 palette and 24bit target colourspace */ + } else if (palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24) { + conversion_type = 0; + conversion_type = 0; + subpixelorder = ZM_SUBPIX_ORDER_BGR; - /* Grayscale palette and grayscale target colourspace */ - } else if ( palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8 ) { - conversion_type = 0; + /* Grayscale palette and grayscale target colourspace */ + } else if (palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8) { + conversion_type = 0; + subpixelorder = ZM_SUBPIX_ORDER_NONE; + /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ + } else { + if (capture) { + Info( + "No direct match for the selected palette (%d) and target colorspace (%02u). Format conversion is required, performance penalty expected", + capturePixFormat, + colours); + } + /* Try using swscale for the conversion */ + conversion_type = 1; + Debug(2, "Using swscale for image conversion"); + if (colours == ZM_COLOUR_RGB32) { + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + imagePixFormat = AV_PIX_FMT_RGBA; + } else if (colours == ZM_COLOUR_RGB24) { + subpixelorder = ZM_SUBPIX_ORDER_RGB; + imagePixFormat = AV_PIX_FMT_RGB24; + } else if (colours == ZM_COLOUR_GRAY8) { subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ + imagePixFormat = AV_PIX_FMT_GRAY8; } else { - if ( capture ) { - Info( - "No direct match for the selected palette (%d) and target colorspace (%02u). Format conversion is required, performance penalty expected", - capturePixFormat, - colours); + Panic("Unexpected colours: %u",colours); + } + if (capture) { + if (!sws_isSupportedInput(capturePixFormat)) { + Error("swscale does not support the used capture format: %d", capturePixFormat); + conversion_type = 2; /* Try ZM format conversions */ } - /* Try using swscale for the conversion */ - conversion_type = 1; - Debug(2, "Using swscale for image conversion"); - if ( colours == ZM_COLOUR_RGB32 ) { - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - imagePixFormat = AV_PIX_FMT_RGBA; - } else if ( colours == ZM_COLOUR_RGB24 ) { - subpixelorder = ZM_SUBPIX_ORDER_RGB; - imagePixFormat = AV_PIX_FMT_RGB24; - } else if ( colours == ZM_COLOUR_GRAY8 ) { + if (!sws_isSupportedOutput(imagePixFormat)) { + Error("swscale does not support the target format: 0x%d", imagePixFormat); + conversion_type = 2; /* Try ZM format conversions */ + } + } + /* Our YUYV->Grayscale conversion is a lot faster than swscale's */ + if (colours == ZM_COLOUR_GRAY8 && palette == V4L2_PIX_FMT_YUYV) { + conversion_type = 2; + } + + /* JPEG */ + if (palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { + Debug(2,"Using JPEG image decoding"); + conversion_type = 3; + } + + if (conversion_type == 2) { + Debug(2,"Using ZM for image conversion"); + if ( palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_GRAY8 ) { + conversion_fptr = &std_convert_argb_gray8; subpixelorder = ZM_SUBPIX_ORDER_NONE; - imagePixFormat = AV_PIX_FMT_GRAY8; - } else { - Panic("Unexpected colours: %u",colours); - } - if ( capture ) { - if ( !sws_isSupportedInput(capturePixFormat) ) { - Error("swscale does not support the used capture format: %d", capturePixFormat); - conversion_type = 2; /* Try ZM format conversions */ - } - if ( !sws_isSupportedOutput(imagePixFormat) ) { - Error("swscale does not support the target format: 0x%d", imagePixFormat); - conversion_type = 2; /* Try ZM format conversions */ - } - } - /* Our YUYV->Grayscale conversion is a lot faster than swscale's */ - if ( colours == ZM_COLOUR_GRAY8 && palette == V4L2_PIX_FMT_YUYV ) { - conversion_type = 2; - } - - /* JPEG */ - if ( palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG ) { - Debug(2,"Using JPEG image decoding"); - conversion_type = 3; - } - - if ( conversion_type == 2 ) { - Debug(2,"Using ZM for image conversion"); - if ( palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_GRAY8 ) { - conversion_fptr = &std_convert_argb_gray8; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else if ( palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_GRAY8 ) { - conversion_fptr = &std_convert_bgra_gray8; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else if ( palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_GRAY8 ) { - /* Fast YUYV->Grayscale conversion by extracting the Y channel */ - if ( config.cpu_extensions && sse_version >= 35 ) { - conversion_fptr = &ssse3_convert_yuyv_gray8; - Debug(2,"Using SSSE3 YUYV->grayscale fast conversion"); - } else { - conversion_fptr = &std_convert_yuyv_gray8; - Debug(2,"Using standard YUYV->grayscale fast conversion"); - } - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else if ( palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_yuyv_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_yuyv_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_rgb555_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_rgb555_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_rgb565_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_rgb565_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } else if (palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_GRAY8) { + conversion_fptr = &std_convert_bgra_gray8; + subpixelorder = ZM_SUBPIX_ORDER_NONE; + } else if (palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_GRAY8) { + /* Fast YUYV->Grayscale conversion by extracting the Y channel */ + if (config.cpu_extensions && sse_version >= 35) { + conversion_fptr = &ssse3_convert_yuyv_gray8; + Debug(2,"Using SSSE3 YUYV->grayscale fast conversion"); } else { - Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace."); + conversion_fptr = &std_convert_yuyv_gray8; + Debug(2,"Using standard YUYV->grayscale fast conversion"); } - } // end if conversion_type == 2 - } // end if needs conversion - } // end if v4l2 + subpixelorder = ZM_SUBPIX_ORDER_NONE; + } else if (palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB24) { + conversion_fptr = &zm_convert_yuyv_rgb; + subpixelorder = ZM_SUBPIX_ORDER_RGB; + } else if (palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB32) { + conversion_fptr = &zm_convert_yuyv_rgba; + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } else if (palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB24) { + conversion_fptr = &zm_convert_rgb555_rgb; + subpixelorder = ZM_SUBPIX_ORDER_RGB; + } else if (palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB32) { + conversion_fptr = &zm_convert_rgb555_rgba; + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } else if (palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB24) { + conversion_fptr = &zm_convert_rgb565_rgb; + subpixelorder = ZM_SUBPIX_ORDER_RGB; + } else if (palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB32) { + conversion_fptr = &zm_convert_rgb565_rgba; + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } else { + Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace."); + } + } // end if conversion_type == 2 + } // end if needs conversion last_camera = this; Debug(3, "Selected subpixelorder: %u", subpixelorder); /* Initialize swscale stuff */ - if ( capture and (conversion_type == 1) ) { + if (capture and (conversion_type == 1)) { tmpPicture = av_frame_alloc(); - if ( !tmpPicture ) + if (!tmpPicture) Fatal("Could not allocate temporary picture"); unsigned int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); - if ( pSize != imagesize ) { + if (pSize != imagesize) { Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); } @@ -460,23 +452,23 @@ LocalCamera::LocalCamera( width, height, imagePixFormat, SWS_BICUBIC, nullptr, nullptr, nullptr); - if ( !imgConversionContext ) { + if (!imgConversionContext) { Fatal("Unable to initialise image scaling context"); } } else { tmpPicture = nullptr; imgConversionContext = nullptr; } // end if capture and conversion_tye == swscale - if ( capture and device_prime ) + if (capture and device_prime) Initialise(); } // end LocalCamera::LocalCamera LocalCamera::~LocalCamera() { - if ( device_prime && capture ) + if (device_prime && capture) Terminate(); /* Clean up swscale stuff */ - if ( capture && (conversion_type == 1) ) { + if (capture && (conversion_type == 1)) { sws_freeContext(imgConversionContext); imgConversionContext = nullptr; @@ -492,251 +484,247 @@ int LocalCamera::Close() { void LocalCamera::Initialise() { Debug(3, "Opening video device %s", device.c_str()); - //if ( (vid_fd = open( device.c_str(), O_RDWR|O_NONBLOCK, 0 )) < 0 ) - if ( (vid_fd = open(device.c_str(), O_RDWR, 0)) < 0 ) + if ((vid_fd = open(device.c_str(), O_RDWR, 0)) < 0) Fatal("Failed to open video device %s: %s", device.c_str(), strerror(errno)); struct stat st; - if ( stat(device.c_str(), &st) < 0 ) + if (stat(device.c_str(), &st) < 0) Fatal("Failed to stat video device %s: %s", device.c_str(), strerror(errno)); - if ( !S_ISCHR(st.st_mode) ) + if (!S_ISCHR(st.st_mode)) Fatal("File %s is not device file: %s", device.c_str(), strerror(errno)); - Debug(2, "V4L2 support enabled, using V4L%d api", v4l_version); - if ( v4l_version == 2 ) { - struct v4l2_capability vid_cap; + struct v4l2_capability vid_cap; - Debug(3, "Checking video device capabilities"); - if ( vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0 ) - Fatal("Failed to query video device: %s", strerror(errno)); + Debug(3, "Checking video device capabilities"); + if ( vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0 ) + Fatal("Failed to query video device: %s", strerror(errno)); - if ( !(vid_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ) - Fatal("Video device is not video capture device"); + if ( !(vid_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ) + Fatal("Video device is not video capture device"); - if ( !(vid_cap.capabilities & V4L2_CAP_STREAMING) ) - Fatal("Video device does not support streaming i/o"); + if ( !(vid_cap.capabilities & V4L2_CAP_STREAMING) ) + Fatal("Video device does not support streaming i/o"); - Debug(3, "Setting up video format"); + Debug(3, "Setting up video format"); - memset(&v4l2_data.fmt, 0, sizeof(v4l2_data.fmt)); - v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + memset(&v4l2_data.fmt, 0, sizeof(v4l2_data.fmt)); + v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if ( vidioctl( vid_fd, VIDIOC_G_FMT, &v4l2_data.fmt ) < 0 ) - Fatal("Failed to get video format: %s", strerror(errno)); + if ( vidioctl( vid_fd, VIDIOC_G_FMT, &v4l2_data.fmt ) < 0 ) + Fatal("Failed to get video format: %s", strerror(errno)); - Debug(4, - " v4l2_data.fmt.type = %08x\n" - " v4l2_data.fmt.fmt.pix.width = %d\n" - " v4l2_data.fmt.fmt.pix.height = %d\n" - " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" - " v4l2_data.fmt.fmt.pix.field = %08x\n" - " v4l2_data.fmt.fmt.pix.bytesperline = %d\n" - " v4l2_data.fmt.fmt.pix.sizeimage = %d\n" - " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" - " v4l2_data.fmt.fmt.pix.priv = %08x\n" - , v4l2_data.fmt.type - , v4l2_data.fmt.fmt.pix.width - , v4l2_data.fmt.fmt.pix.height - , v4l2_data.fmt.fmt.pix.pixelformat - , v4l2_data.fmt.fmt.pix.field - , v4l2_data.fmt.fmt.pix.bytesperline - , v4l2_data.fmt.fmt.pix.sizeimage - , v4l2_data.fmt.fmt.pix.colorspace - , v4l2_data.fmt.fmt.pix.priv - ); + Debug(4, + " v4l2_data.fmt.type = %08x\n" + " v4l2_data.fmt.fmt.pix.width = %d\n" + " v4l2_data.fmt.fmt.pix.height = %d\n" + " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" + " v4l2_data.fmt.fmt.pix.field = %08x\n" + " v4l2_data.fmt.fmt.pix.bytesperline = %d\n" + " v4l2_data.fmt.fmt.pix.sizeimage = %d\n" + " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" + " v4l2_data.fmt.fmt.pix.priv = %08x\n" + , v4l2_data.fmt.type + , v4l2_data.fmt.fmt.pix.width + , v4l2_data.fmt.fmt.pix.height + , v4l2_data.fmt.fmt.pix.pixelformat + , v4l2_data.fmt.fmt.pix.field + , v4l2_data.fmt.fmt.pix.bytesperline + , v4l2_data.fmt.fmt.pix.sizeimage + , v4l2_data.fmt.fmt.pix.colorspace + , v4l2_data.fmt.fmt.pix.priv + ); - v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - v4l2_data.fmt.fmt.pix.width = width; - v4l2_data.fmt.fmt.pix.height = height; - v4l2_data.fmt.fmt.pix.pixelformat = palette; + v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_data.fmt.fmt.pix.width = width; + v4l2_data.fmt.fmt.pix.height = height; + v4l2_data.fmt.fmt.pix.pixelformat = palette; - if ( (extras & 0xff) != 0 ) { - v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); + if ((extras & 0xff) != 0) { + v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); - if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { - Warning("Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff)); - v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; - if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { - Fatal("Failed to set video format: %s", strerror(errno)); - } - } - } else { - if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { - Error("Failed to set video format: %s", strerror(errno)); + if (vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0) { + Warning("Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff)); + v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0) { + Fatal("Failed to set video format: %s", strerror(errno)); } } - - /* Note VIDIOC_S_FMT may change width and height. */ - Debug(4, - " v4l2_data.fmt.type = %08x\n" - " v4l2_data.fmt.fmt.pix.width = %d\n" - " v4l2_data.fmt.fmt.pix.height = %d\n" - " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" - " v4l2_data.fmt.fmt.pix.field = %08x\n" - " v4l2_data.fmt.fmt.pix.bytesperline = %d\n" - " v4l2_data.fmt.fmt.pix.sizeimage = %d\n" - " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" - " v4l2_data.fmt.fmt.pix.priv = %08x\n" - , v4l2_data.fmt.type - , v4l2_data.fmt.fmt.pix.width - , v4l2_data.fmt.fmt.pix.height - , v4l2_data.fmt.fmt.pix.pixelformat - , v4l2_data.fmt.fmt.pix.field - , v4l2_data.fmt.fmt.pix.bytesperline - , v4l2_data.fmt.fmt.pix.sizeimage - , v4l2_data.fmt.fmt.pix.colorspace - , v4l2_data.fmt.fmt.pix.priv - ); - - if ( v4l2_data.fmt.fmt.pix.width != width ) { - Warning("Failed to set requested width"); - } - if ( v4l2_data.fmt.fmt.pix.height != height ) { - Warning("Failed to set requested height"); + } else { + if (vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0) { + Error("Failed to set video format: %s", strerror(errno)); } + } - /* Buggy driver paranoia. */ - unsigned int min; - min = v4l2_data.fmt.fmt.pix.width * 2; - if ( v4l2_data.fmt.fmt.pix.bytesperline < min ) - v4l2_data.fmt.fmt.pix.bytesperline = min; - min = v4l2_data.fmt.fmt.pix.bytesperline * v4l2_data.fmt.fmt.pix.height; - if ( v4l2_data.fmt.fmt.pix.sizeimage < min ) - v4l2_data.fmt.fmt.pix.sizeimage = min; + /* Note VIDIOC_S_FMT may change width and height. */ + Debug(4, + " v4l2_data.fmt.type = %08x\n" + " v4l2_data.fmt.fmt.pix.width = %d\n" + " v4l2_data.fmt.fmt.pix.height = %d\n" + " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" + " v4l2_data.fmt.fmt.pix.field = %08x\n" + " v4l2_data.fmt.fmt.pix.bytesperline = %d\n" + " v4l2_data.fmt.fmt.pix.sizeimage = %d\n" + " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" + " v4l2_data.fmt.fmt.pix.priv = %08x\n" + , v4l2_data.fmt.type + , v4l2_data.fmt.fmt.pix.width + , v4l2_data.fmt.fmt.pix.height + , v4l2_data.fmt.fmt.pix.pixelformat + , v4l2_data.fmt.fmt.pix.field + , v4l2_data.fmt.fmt.pix.bytesperline + , v4l2_data.fmt.fmt.pix.sizeimage + , v4l2_data.fmt.fmt.pix.colorspace + , v4l2_data.fmt.fmt.pix.priv + ); - if ( palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG ) { - v4l2_jpegcompression jpeg_comp; - if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) { - if ( errno == EINVAL ) { - Debug(2, "JPEG compression options are not available"); - } else { - Warning("Failed to get JPEG compression options: %s", strerror(errno)); - } + if (v4l2_data.fmt.fmt.pix.width != width) { + Warning("Failed to set requested width"); + } + if (v4l2_data.fmt.fmt.pix.height != height) { + Warning("Failed to set requested height"); + } + + /* Buggy driver paranoia. */ + unsigned int min; + min = v4l2_data.fmt.fmt.pix.width * 2; + if (v4l2_data.fmt.fmt.pix.bytesperline < min) + v4l2_data.fmt.fmt.pix.bytesperline = min; + min = v4l2_data.fmt.fmt.pix.bytesperline * v4l2_data.fmt.fmt.pix.height; + if (v4l2_data.fmt.fmt.pix.sizeimage < min) + v4l2_data.fmt.fmt.pix.sizeimage = min; + + if (palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { + v4l2_jpegcompression jpeg_comp; + if (vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0) { + if (errno == EINVAL) { + Debug(2, "JPEG compression options are not available"); } else { - /* Set flags and quality. MJPEG should not have the huffman tables defined */ - if ( palette == V4L2_PIX_FMT_MJPEG ) { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; - } else { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; - } - jpeg_comp.quality = 85; - - /* Update the JPEG options */ - if ( vidioctl(vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp) < 0 ) { - Warning("Failed to set JPEG compression options: %s", strerror(errno)); - } else { - if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) { - Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno)); - } else { - Debug(4, "JPEG quality: %d, markers: %#x", - jpeg_comp.quality, jpeg_comp.jpeg_markers); - } - } - } - } // end if JPEG/MJPEG - - Debug(3, "Setting up request buffers"); - - memset(&v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs)); - if ( channel_count > 1 ) { - Debug(3, "Channel count is %d", channel_count); - if ( v4l_multi_buffer ){ - v4l2_data.reqbufs.count = 2*channel_count; - } else { - v4l2_data.reqbufs.count = 1; + Warning("Failed to get JPEG compression options: %s", strerror(errno)); } } else { - v4l2_data.reqbufs.count = 8; - } - Debug(3, "Request buffers count is %d", v4l2_data.reqbufs.count); - - v4l2_data.reqbufs.type = v4l2_data.fmt.type; - v4l2_data.reqbufs.memory = V4L2_MEMORY_MMAP; - - if ( vidioctl(vid_fd, VIDIOC_REQBUFS, &v4l2_data.reqbufs) < 0 ) { - if ( errno == EINVAL ) { - Fatal("Unable to initialise memory mapping, unsupported in device"); + /* Set flags and quality. MJPEG should not have the huffman tables defined */ + if (palette == V4L2_PIX_FMT_MJPEG) { + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; } else { - Fatal("Unable to initialise memory mapping: %s", strerror(errno)); + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; + } + jpeg_comp.quality = 85; + + /* Update the JPEG options */ + if (vidioctl(vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp) < 0) { + Warning("Failed to set JPEG compression options: %s", strerror(errno)); + } else { + if (vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0) { + Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno)); + } else { + Debug(4, "JPEG quality: %d, markers: %#x", + jpeg_comp.quality, jpeg_comp.jpeg_markers); + } } } + } // end if JPEG/MJPEG - if ( v4l2_data.reqbufs.count < (v4l_multi_buffer?2:1) ) - Fatal("Insufficient buffer memory %d on video device", v4l2_data.reqbufs.count); + Debug(3, "Setting up request buffers"); - Debug(3, "Setting up data buffers: Channels %d MultiBuffer %d Buffers: %d", - channel_count, v4l_multi_buffer, v4l2_data.reqbufs.count); - - v4l2_data.buffers = new V4L2MappedBuffer[v4l2_data.reqbufs.count]; - capturePictures = new AVFrame *[v4l2_data.reqbufs.count]; - - for ( unsigned int i = 0; i < v4l2_data.reqbufs.count; i++ ) { - struct v4l2_buffer vid_buf; - - memset(&vid_buf, 0, sizeof(vid_buf)); - - //vid_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - vid_buf.type = v4l2_data.fmt.type; - //vid_buf.memory = V4L2_MEMORY_MMAP; - vid_buf.memory = v4l2_data.reqbufs.memory; - vid_buf.index = i; - - if ( vidioctl(vid_fd, VIDIOC_QUERYBUF, &vid_buf) < 0 ) - Fatal("Unable to query video buffer: %s", strerror(errno)); - - v4l2_data.buffers[i].length = vid_buf.length; - v4l2_data.buffers[i].start = mmap(nullptr, 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 %u (%u bytes) to memory: %s(%d)", - i, vid_buf.length, strerror(errno), errno); - - capturePictures[i] = av_frame_alloc(); - - if ( !capturePictures[i] ) - Fatal("Could not allocate picture"); - - av_image_fill_arrays( - capturePictures[i]->data, - capturePictures[i]->linesize, - (uint8_t*)v4l2_data.buffers[i].start, - capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height, - 1); - } // end foreach request buf - - Debug(3, "Configuring video source"); - - if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &channel) < 0 ) { - Fatal("Failed to set camera source %d: %s", channel, strerror(errno)); + memset(&v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs)); + if (channel_count > 1) { + Debug(3, "Channel count is %d", channel_count); + if (v4l_multi_buffer){ + v4l2_data.reqbufs.count = 2*channel_count; + } else { + v4l2_data.reqbufs.count = 1; } - - struct v4l2_input input; - v4l2_std_id stdId; - - memset(&input, 0, sizeof(input)); - input.index = channel; - - if ( vidioctl(vid_fd, VIDIOC_ENUMINPUT, &input) < 0 ) { - Fatal("Failed to enumerate input %d: %s", channel, strerror(errno)); - } - - if ( (input.std != V4L2_STD_UNKNOWN) && ((input.std & standard) == V4L2_STD_UNKNOWN) ) { - Error("Device does not support video standard %d", standard); - } - - stdId = standard; - if ((vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0)) { - Error("Failed to set video standard %d: %d %s", standard, errno, strerror(errno)); - } - - Contrast(contrast); - Brightness(brightness); - Hue(hue); - Colour(colour); + } else { + v4l2_data.reqbufs.count = 8; } + Debug(3, "Request buffers count is %d", v4l2_data.reqbufs.count); + + v4l2_data.reqbufs.type = v4l2_data.fmt.type; + v4l2_data.reqbufs.memory = V4L2_MEMORY_MMAP; + + if (vidioctl(vid_fd, VIDIOC_REQBUFS, &v4l2_data.reqbufs) < 0) { + if (errno == EINVAL) { + Fatal("Unable to initialise memory mapping, unsupported in device"); + } else { + Fatal("Unable to initialise memory mapping: %s", strerror(errno)); + } + } + + if (v4l2_data.reqbufs.count < (v4l_multi_buffer?2:1)) + Fatal("Insufficient buffer memory %d on video device", v4l2_data.reqbufs.count); + + Debug(3, "Setting up data buffers: Channels %d MultiBuffer %d Buffers: %d", + channel_count, v4l_multi_buffer, v4l2_data.reqbufs.count); + + v4l2_data.buffers = new V4L2MappedBuffer[v4l2_data.reqbufs.count]; + capturePictures = new AVFrame *[v4l2_data.reqbufs.count]; + + for (unsigned int i = 0; i < v4l2_data.reqbufs.count; i++) { + struct v4l2_buffer vid_buf; + + memset(&vid_buf, 0, sizeof(vid_buf)); + + //vid_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vid_buf.type = v4l2_data.fmt.type; + //vid_buf.memory = V4L2_MEMORY_MMAP; + vid_buf.memory = v4l2_data.reqbufs.memory; + vid_buf.index = i; + + if (vidioctl(vid_fd, VIDIOC_QUERYBUF, &vid_buf) < 0) + Fatal("Unable to query video buffer: %s", strerror(errno)); + + v4l2_data.buffers[i].length = vid_buf.length; + v4l2_data.buffers[i].start = mmap(nullptr, 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 %u (%u bytes) to memory: %s(%d)", + i, vid_buf.length, strerror(errno), errno); + + capturePictures[i] = av_frame_alloc(); + + if (!capturePictures[i]) + Fatal("Could not allocate picture"); + + av_image_fill_arrays( + capturePictures[i]->data, + capturePictures[i]->linesize, + (uint8_t*)v4l2_data.buffers[i].start, + capturePixFormat, + v4l2_data.fmt.fmt.pix.width, + v4l2_data.fmt.fmt.pix.height, + 1); + } // end foreach request buf + + Debug(3, "Configuring video source"); + + if (vidioctl(vid_fd, VIDIOC_S_INPUT, &channel) < 0) { + Fatal("Failed to set camera source %d: %s", channel, strerror(errno)); + } + + struct v4l2_input input; + v4l2_std_id stdId; + + memset(&input, 0, sizeof(input)); + input.index = channel; + + if (vidioctl(vid_fd, VIDIOC_ENUMINPUT, &input) < 0) { + Fatal("Failed to enumerate input %d: %s", channel, strerror(errno)); + } + + if ((input.std != V4L2_STD_UNKNOWN) && ((input.std & standard) == V4L2_STD_UNKNOWN)) { + Error("Device does not support video standard %d", standard); + } + + stdId = standard; + if ((vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0)) { + Error("Failed to set video standard %d: %d %s", standard, errno, strerror(errno)); + } + + Contrast(contrast); + Brightness(brightness); + Hue(hue); + Colour(colour); } // end LocalCamera::Initialize void LocalCamera::Terminate() { @@ -1170,170 +1158,87 @@ bool LocalCamera::GetCurrentSettings( return true; } -int LocalCamera::Brightness(int p_brightness) { - if ( v4l_version == 2 ) { - struct v4l2_control vid_control; +int LocalCamera::Control(int vid_id, int newvalue) { + struct v4l2_control vid_control; - memset(&vid_control, 0, sizeof(vid_control)); - vid_control.id = V4L2_CID_BRIGHTNESS; + memset(&vid_control, 0, sizeof(vid_control)); + vid_control.id = vid_id; - if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { - if ( errno != EINVAL ) { - Error("Unable to query brightness: %s", strerror(errno)); - } else { - Warning("Brightness control is not supported"); - } - //Info( "Brightness 1 %d", vid_control.value ); - } else if ( p_brightness >= 0 ) { - vid_control.value = p_brightness; - - //Info( "Brightness 2 %d", vid_control.value ); - /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) { - if ( errno != ERANGE ) { - Error("Unable to set brightness: %s", strerror(errno)); - } else { - Warning("Given brightness value (%d) may be out-of-range", p_brightness); - } - } - //Info( "Brightness 3 %d", vid_control.value ); + if (vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0) { + if (errno != EINVAL) { + Error("Unable to query control: %s", strerror(errno)); + } else { + Warning("Control is not supported"); + } + } else if (newvalue >= 0) { + vid_control.value = newvalue; + + /* The driver may clamp the value or return ERANGE, ignored here */ + if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) { + if (errno != ERANGE) { + Error("Unable to set control: %s", strerror(errno)); + } else { + Warning("Given control value (%d) may be out-of-range", newvalue); + } } - return vid_control.value; } - return -1; + return vid_control.value; +} + +int LocalCamera::Brightness(int p_brightness) { + return Control(V4L2_CID_BRIGHTNESS, p_brightness); } int LocalCamera::Hue(int p_hue) { - if ( v4l_version == 2 ) { - struct v4l2_control vid_control; - - memset( &vid_control, 0, sizeof(vid_control) ); - vid_control.id = V4L2_CID_HUE; - - if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { - if ( errno != EINVAL ) - Error("Unable to query hue: %s", strerror(errno)); - else - Warning("Hue control is not supported"); - } else if ( p_hue >= 0 ) { - vid_control.value = p_hue; - - /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) < 0 ) { - if ( errno != ERANGE ) { - Error("Unable to set hue: %s", strerror(errno)); - } else { - Warning("Given hue value (%d) may be out-of-range", p_hue); - } - } - } - return vid_control.value; - } - return -1; + return Control(V4L2_CID_HUE, p_hue); } int LocalCamera::Colour( int p_colour ) { - if ( v4l_version == 2 ) { - struct v4l2_control vid_control; - - memset(&vid_control, 0, sizeof(vid_control)); - vid_control.id = V4L2_CID_SATURATION; - - if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { - if ( errno != EINVAL ) { - Error("Unable to query saturation: %s", strerror(errno)); - } else { - Warning("Saturation control is not supported"); - } - } else if ( p_colour >= 0 ) { - vid_control.value = p_colour; - - /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) < 0 ) { - if ( errno != ERANGE ) { - Error("Unable to set saturation: %s", strerror(errno)); - } else { - Warning("Given saturation value (%d) may be out-of-range", p_colour); - } - } - } - return vid_control.value; - } - return -1; + return Control(V4L2_CID_SATURATION, p_colour); } -int LocalCamera::Contrast( int p_contrast ) { - if ( v4l_version == 2 ) { - struct v4l2_control vid_control; - - memset(&vid_control, 0, sizeof(vid_control)); - vid_control.id = V4L2_CID_CONTRAST; - - if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { - if ( errno != EINVAL ) { - Error("Unable to query contrast: %s", strerror(errno)); - } else { - Warning("Contrast control is not supported"); - } - } else if ( p_contrast >= 0 ) { - vid_control.value = p_contrast; - - /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) { - if ( errno != ERANGE ) { - Error("Unable to set contrast: %s", strerror(errno)); - } else { - Warning("Given contrast value (%d) may be out-of-range", p_contrast); - } - } - } - return vid_control.value; - } - return -1; +int LocalCamera::Contrast(int p_contrast) { + return Control(V4L2_CID_CONTRAST, p_contrast); } int LocalCamera::PrimeCapture() { getVideoStream(); - if ( !device_prime ) + if (!device_prime) return 1; - Debug(2, "Priming capture"); - if ( v4l_version == 2 ) { - Debug(3, "Queueing (%d) buffers", v4l2_data.reqbufs.count); - for ( unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++ ) { - struct v4l2_buffer vid_buf; + Debug(3, "Queueing (%d) buffers", v4l2_data.reqbufs.count); + for (unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++) { + struct v4l2_buffer vid_buf; - memset(&vid_buf, 0, sizeof(vid_buf)); - if ( v4l2_data.fmt.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ) { - Warning("Unknown type: (%d)", v4l2_data.fmt.type); - } - - vid_buf.type = v4l2_data.fmt.type; - vid_buf.memory = v4l2_data.reqbufs.memory; - vid_buf.index = frame; - - if (vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0) { - Error("Failed to queue buffer %d: %s", frame, strerror(errno)); - return 0; - } + memset(&vid_buf, 0, sizeof(vid_buf)); + if (v4l2_data.fmt.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + Warning("Unknown type: (%d)", v4l2_data.fmt.type); } - v4l2_data.bufptr = nullptr; - Debug(3, "Starting video stream"); - //enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - //enum v4l2_buf_type type = v4l2_data.fmt.type; - enum v4l2_buf_type type = (v4l2_buf_type)v4l2_data.fmt.type; - if (vidioctl(vid_fd, VIDIOC_STREAMON, &type) < 0) { - Error("Failed to start capture stream: %s", strerror(errno)); - return -1; + vid_buf.type = v4l2_data.fmt.type; + vid_buf.memory = v4l2_data.reqbufs.memory; + vid_buf.index = frame; + + if (vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0) { + Error("Failed to queue buffer %d: %s", frame, strerror(errno)); + return 0; } - } // end if v4l_version == 2 + } + v4l2_data.bufptr = nullptr; + + Debug(3, "Starting video stream"); + //enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + //enum v4l2_buf_type type = v4l2_data.fmt.type; + enum v4l2_buf_type type = (v4l2_buf_type)v4l2_data.fmt.type; + if (vidioctl(vid_fd, VIDIOC_STREAMON, &type) < 0) { + Error("Failed to start capture stream: %s", strerror(errno)); + return -1; + } return 1; } // end LocalCamera::PrimeCapture int LocalCamera::PreCapture() { - //Debug(5, "Pre-capturing"); return 1; } @@ -1352,78 +1257,74 @@ int LocalCamera::Capture(std::shared_ptr &zm_packet) { } // Do the capture, unless we are the second or subsequent camera on a channel, in which case just reuse the buffer - if ( channel_prime ) { - if ( v4l_version == 2 ) { - static struct v4l2_buffer vid_buf; + if (channel_prime) { + static struct v4l2_buffer vid_buf; - memset(&vid_buf, 0, sizeof(vid_buf)); + memset(&vid_buf, 0, sizeof(vid_buf)); - vid_buf.type = v4l2_data.fmt.type; - vid_buf.memory = v4l2_data.reqbufs.memory; + vid_buf.type = v4l2_data.fmt.type; + vid_buf.memory = v4l2_data.reqbufs.memory; - Debug(3, "Capturing %d frames", captures_per_frame); - while ( captures_per_frame ) { - if ( vidioctl(vid_fd, VIDIOC_DQBUF, &vid_buf) < 0 ) { - if ( errno == EIO ) { - Warning("Capture failure, possible signal loss?: %s", strerror(errno)); - } else { - Error("Unable to capture frame %d: %s", vid_buf.index, strerror(errno)); - } + Debug(3, "Capturing %d frames", captures_per_frame); + while (captures_per_frame) { + if (vidioctl(vid_fd, VIDIOC_DQBUF, &vid_buf) < 0) { + if (errno == EIO) { + Warning("Capture failure, possible signal loss?: %s", strerror(errno)); + } else { + Error("Unable to capture frame %d: %s", vid_buf.index, strerror(errno)); + } + return -1; + } + Debug(5, "Captured a frame"); + + v4l2_data.bufptr = &vid_buf; + capture_frame = v4l2_data.bufptr->index; + bytes += vid_buf.bytesused; + + if (--captures_per_frame) { + if (vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0) { + Error("Unable to requeue buffer %d: %s", vid_buf.index, strerror(errno)); return -1; } - Debug(5, "Captured a frame"); - - v4l2_data.bufptr = &vid_buf; - capture_frame = v4l2_data.bufptr->index; - bytes += vid_buf.bytesused; - - if ( --captures_per_frame ) { - if ( vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0 ) { - Error("Unable to requeue buffer %d: %s", vid_buf.index, strerror(errno)); - return -1; - } - } - } // while captures_per_frame - - Debug(3, "Captured frame %d/%d from channel %d", capture_frame, v4l2_data.bufptr->sequence, channel); - - buffer = (unsigned char *)v4l2_data.buffers[v4l2_data.bufptr->index].start; - buffer_bytesused = v4l2_data.bufptr->bytesused; - bytes += buffer_bytesused; - - if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) > (width * height) ) { - Fatal("Captured image dimensions larger than image buffer: V4L2: %dx%d monitor: %dx%d", - v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); - } else if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) { - Error("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d", - v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); } - } // end if v4l2 + } // while captures_per_frame - if ( v4l_version == 2 ) { - if ( channel_count > 1 ) { - int next_channel = (channel_index+1)%channel_count; - Debug(3, "Switching video source to %d", channels[next_channel]); - if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &channels[next_channel]) < 0 ) { - Error("Failed to set camera source %d: %s", channels[next_channel], strerror(errno)); - return -1; - } + Debug(3, "Captured frame %d/%d from channel %d", capture_frame, v4l2_data.bufptr->sequence, channel); - v4l2_std_id stdId = standards[next_channel]; - if ( vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0 ) { - Error("Failed to set video format %d: %s", standards[next_channel], strerror(errno)); - } + buffer = (unsigned char *)v4l2_data.buffers[v4l2_data.bufptr->index].start; + buffer_bytesused = v4l2_data.bufptr->bytesused; + bytes += buffer_bytesused; + + if ((v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) > (width * height)) { + Fatal("Captured image dimensions larger than image buffer: V4L2: %dx%d monitor: %dx%d", + v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); + } else if ((v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height)) { + Error("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d", + v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); + } + + if (channel_count > 1) { + int next_channel = (channel_index+1)%channel_count; + Debug(3, "Switching video source to %d", channels[next_channel]); + if (vidioctl(vid_fd, VIDIOC_S_INPUT, &channels[next_channel]) < 0) { + Error("Failed to set camera source %d: %s", channels[next_channel], strerror(errno)); + return -1; } - if ( v4l2_data.bufptr ) { - Debug(3, "Requeueing buffer %d", v4l2_data.bufptr->index); - if ( vidioctl(vid_fd, VIDIOC_QBUF, v4l2_data.bufptr) < 0 ) { - Error("Unable to requeue buffer %d: %s", v4l2_data.bufptr->index, strerror(errno)); - return -1; - } - } else { - Error("Unable to requeue buffer due to not v4l2_data"); + + v4l2_std_id stdId = standards[next_channel]; + if (vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0) { + Error("Failed to set video format %d: %s", standards[next_channel], strerror(errno)); } } + if (v4l2_data.bufptr) { + Debug(3, "Requeueing buffer %d", v4l2_data.bufptr->index); + if (vidioctl(vid_fd, VIDIOC_QBUF, v4l2_data.bufptr) < 0) { + Error("Unable to requeue buffer %d: %s", v4l2_data.bufptr->index, strerror(errno)); + return -1; + } + } else { + Error("Unable to requeue buffer due to not v4l2_data"); + } } /* prime capture */ if (!zm_packet->image) { diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index f6991c5de..f702b816f 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -113,6 +113,7 @@ public: int Palette() const { return palette; } int Extras() const { return extras; } + int Control(int vid_id, int newvalue=-1 ); int Brightness( int p_brightness=-1 ) override; int Hue( int p_hue=-1 ) override; int Colour( int p_colour=-1 ) override; diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 723d4f7b5..679ea27b7 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -83,9 +83,10 @@ std::string load_monitor_sql = "`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`," "`RTSPServer`, `RTSPStreamName`," -"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-2 FROM `Monitors`"; +"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`"; std::string CameraType_Strings[] = { + "Unknown", "Local", "Remote", "File", @@ -93,10 +94,21 @@ std::string CameraType_Strings[] = { "LibVLC", "NVSOCKET", "CURL", - "VNC", + "VNC" +}; + +std::string Function_Strings[] = { + "Unknown", + "None", + "Monitor", + "Modect", + "Record", + "Mocord", + "Nodect" }; std::string State_Strings[] = { + "Unknown", "IDLE", "PREALARM", "ALARM", @@ -435,7 +447,7 @@ Monitor::Monitor() "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," "`RTSPServer`,`RTSPStreamName`, - "SignalCheckPoints, SignalCheckColour, Importance-2 FROM Monitors"; + "SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { @@ -474,6 +486,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { function = (Function)atoi(dbrow[col]); col++; enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; + // See below after save_jpegs for a recalculation of decoding_enabled ReloadLinkedMonitors(dbrow[col]); col++; @@ -542,6 +555,17 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { videowriter = (VideoWriter)atoi(dbrow[col]); col++; encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + decoding_enabled = !( + ( function == RECORD or function == NODECT ) + and + ( savejpegs == 0 ) + and + ( videowriter == PASSTHROUGH ) + and + !decoding_enabled + ); + Debug(3, "Decoding enabled: %d function %d %s savejpegs %d videowriter %d", decoding_enabled, function, Function_Strings[function].c_str(), savejpegs, videowriter); + /*"`OutputCodec`, `Encoder`, `OutputContainer`, " */ output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++; encoder = dbrow[col] ? dbrow[col] : ""; col++; @@ -591,7 +615,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { rtsp_server = (*dbrow[col] != '0'); col++; rtsp_streamname = dbrow[col]; col++; - /*"SignalCheckPoints, SignalCheckColour, Importance-2 FROM Monitors"; */ + /*"SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */ signal_check_points = atoi(dbrow[col]); col++; signal_check_colour = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); col++; @@ -603,6 +627,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { grayscale_val = signal_check_colour & 0xff; /* Clear all bytes but lowest byte */ importance = dbrow[col] ? atoi(dbrow[col]) : 0;// col++; + if (importance < 0) importance = 0; // Should only be >= 0 // How many frames we need to have before we start analysing ready_count = std::max(warmup_count, pre_event_count); @@ -650,18 +675,6 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { Error("Can't mkdir %s: %s", monitor_dir.c_str(), strerror(errno)); } - // Do this here to save a few cycles with all the comparisons - decoding_enabled = !( - ( function == RECORD or function == NODECT ) - and - ( savejpegs == 0 ) - and - ( videowriter == PASSTHROUGH ) - and - !decoding_enabled - ); - Debug(1, "Decoding enabled: %d", decoding_enabled); - if ( config.record_diag_images ) { if ( config.record_diag_images_fifo ) { diag_path_ref = stringtf("%s/diagpipe-r-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id); @@ -1304,127 +1317,153 @@ void Monitor::actionResume() { } int Monitor::actionBrightness(int p_brightness) { - if (purpose != CAPTURE) { - if (p_brightness >= 0) { - shared_data->brightness = p_brightness; - shared_data->action |= SET_SETTINGS; - int wait_loops = 10; - while (shared_data->action & SET_SETTINGS) { - if (wait_loops--) { - std::this_thread::sleep_for(Milliseconds(100)); - } else { - Warning("Timed out waiting to set brightness"); - return -1; - } - } - } else { - shared_data->action |= GET_SETTINGS; - int wait_loops = 10; - while (shared_data->action & GET_SETTINGS) { - if (wait_loops--) { - std::this_thread::sleep_for(Milliseconds(100)); - } else { - Warning("Timed out waiting to get brightness"); - return -1; - } - } - } - return shared_data->brightness; + if (purpose == CAPTURE) { + // We are the capture process, so take the action + return camera->Brightness(p_brightness); } - return camera->Brightness(p_brightness); -} // end int Monitor::actionBrightness(int p_brightness) + + // If we are an outside actor, sending the command + shared_data->brightness = p_brightness; + shared_data->action |= SET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to set brightness"); + return -1; + } + } + return shared_data->brightness; +} + +int Monitor::actionBrightness() { + if (purpose == CAPTURE) { + // We are the capture process, so take the action + return camera->Brightness(); + } + + // If we are an outside actor, sending the command + shared_data->action |= GET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to get brightness"); + return -1; + } + } + return shared_data->brightness; +} // end int Monitor::actionBrightness() int Monitor::actionContrast(int p_contrast) { - if (purpose != CAPTURE) { - if (p_contrast >= 0) { - shared_data->contrast = p_contrast; - shared_data->action |= SET_SETTINGS; - int wait_loops = 10; - while (shared_data->action & SET_SETTINGS) { - if (wait_loops--) { - std::this_thread::sleep_for(Milliseconds(100)); - } else { - Warning("Timed out waiting to set contrast"); - return -1; - } - } - } else { - shared_data->action |= GET_SETTINGS; - int wait_loops = 10; - while (shared_data->action & GET_SETTINGS) { - if (wait_loops--) { - std::this_thread::sleep_for(Milliseconds(100)); - } else { - Warning("Timed out waiting to get contrast"); - return -1; - } - } - } - return shared_data->contrast; + if (purpose == CAPTURE) { + return camera->Contrast(p_contrast); } - return camera->Contrast(p_contrast); -} // end int Monitor::actionContrast(int p_contrast) + + shared_data->contrast = p_contrast; + shared_data->action |= SET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to set contrast"); + return -1; + } + } + return shared_data->contrast; +} + +int Monitor::actionContrast() { + if (purpose == CAPTURE) { + // We are the capture process, so take the action + return camera->Contrast(); + } + + shared_data->action |= GET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to get contrast"); + return -1; + } + } + return shared_data->contrast; +} // end int Monitor::actionContrast() int Monitor::actionHue(int p_hue) { - if (purpose != CAPTURE) { - if (p_hue >= 0) { - shared_data->hue = p_hue; - shared_data->action |= SET_SETTINGS; - int wait_loops = 10; - while (shared_data->action & SET_SETTINGS) { - if (wait_loops--) { - std::this_thread::sleep_for(Milliseconds(100)); - } else { - Warning("Timed out waiting to set hue"); - return -1; - } - } - } else { - shared_data->action |= GET_SETTINGS; - int wait_loops = 10; - while (shared_data->action & GET_SETTINGS) { - if (wait_loops--) { - std::this_thread::sleep_for(Milliseconds(100)); - } else { - Warning("Timed out waiting to get hue"); - return -1; - } - } - } - return shared_data->hue; + if (purpose == CAPTURE) { + return camera->Hue(p_hue); } - return camera->Hue(p_hue); + + shared_data->hue = p_hue; + shared_data->action |= SET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to set hue"); + return -1; + } + } + return shared_data->hue; +} + +int Monitor::actionHue() { + if (purpose == CAPTURE) { + return camera->Hue(); + } + shared_data->action |= GET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to get hue"); + return -1; + } + } + return shared_data->hue; } // end int Monitor::actionHue(int p_hue) int Monitor::actionColour(int p_colour) { - if (purpose != CAPTURE) { - if (p_colour >= 0) { - shared_data->colour = p_colour; - shared_data->action |= SET_SETTINGS; - int wait_loops = 10; - while (shared_data->action & SET_SETTINGS) { - if (wait_loops--) { - std::this_thread::sleep_for(Milliseconds(100)); - } else { - Warning("Timed out waiting to set colour"); - return -1; - } - } - } else { - shared_data->action |= GET_SETTINGS; - int wait_loops = 10; - while (shared_data->action & GET_SETTINGS) { - if (wait_loops--) { - std::this_thread::sleep_for(Milliseconds(100)); - } else { - Warning("Timed out waiting to get colour"); - return -1; - } - } - } - return shared_data->colour; + if (purpose == CAPTURE) { + return camera->Colour(p_colour); } - return camera->Colour(p_colour); + shared_data->colour = p_colour; + shared_data->action |= SET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to set colour"); + return -1; + } + } + return shared_data->colour; +} + +int Monitor::actionColour() { + if (purpose == CAPTURE) { + return camera->Colour(); + } + shared_data->action |= GET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to get colour"); + return -1; + } + } + return shared_data->colour; } // end int Monitor::actionColour(int p_colour) void Monitor::DumpZoneImage(const char *zone_string) { @@ -1617,7 +1656,7 @@ void Monitor::CheckAction() { } } -void Monitor::UpdateCaptureFPS() { +void Monitor::UpdateFPS() { if ( fps_report_interval and ( !(image_count%fps_report_interval) @@ -1636,82 +1675,35 @@ void Monitor::UpdateCaptureFPS() { uint32 new_camera_bytes = camera->Bytes(); uint32 new_capture_bandwidth = static_cast((new_camera_bytes - last_camera_bytes) / elapsed.count()); - last_camera_bytes = new_camera_bytes; + double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count(); - Debug(4, "%s: %d - last %d = %d now:%lf, last %lf, elapsed %lf = %lffps", - "Capturing", + Debug(4, "FPS: capture count %d - last capture count %d = %d now:%lf, last %lf, elapsed %lf = capture: %lf fps analysis: %lf fps", image_count, last_capture_image_count, image_count - last_capture_image_count, FPSeconds(now.time_since_epoch()).count(), - FPSeconds(last_analysis_fps_time.time_since_epoch()).count(), + FPSeconds(last_fps_time.time_since_epoch()).count(), elapsed.count(), - new_capture_fps); + new_capture_fps, + new_analysis_fps); - Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", - name.c_str(), image_count, new_capture_fps, new_capture_bandwidth); + Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec Analysing at %.2lf fps", + name.c_str(), image_count, new_capture_fps, new_capture_bandwidth, new_analysis_fps); shared_data->capture_fps = new_capture_fps; last_fps_time = now; last_capture_image_count = image_count; + shared_data->analysis_fps = new_analysis_fps; + last_motion_frame_count = motion_frame_count; + last_camera_bytes = new_camera_bytes; std::string sql = stringtf( - "UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u WHERE MonitorId=%u", - new_capture_fps, new_capture_bandwidth, id); + "UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u, AnalysisFPS = %.2lf WHERE MonitorId=%u", + new_capture_fps, new_capture_bandwidth, new_analysis_fps, id); dbQueue.push(std::move(sql)); } // now != last_fps_time } // end if report fps -} // void Monitor::UpdateCaptureFPS() - -void Monitor::UpdateAnalysisFPS() { - Debug(1, "analysis_image_count(%d) motion_count(%d) fps_report_interval(%d) mod%d", - analysis_image_count, motion_frame_count, fps_report_interval, - ((analysis_image_count && fps_report_interval) ? !(analysis_image_count%fps_report_interval) : -1 ) ); - - if ( - ( analysis_image_count and fps_report_interval and !(analysis_image_count%fps_report_interval) ) - or - // In startup do faster updates - ( (analysis_image_count < fps_report_interval) and !(analysis_image_count%10) ) - ) { - SystemTimePoint now = std::chrono::system_clock::now(); - - FPSeconds elapsed = now - last_analysis_fps_time; - Debug(4, "%s: %d - now: %.2f, last %lf, diff %lf", - name.c_str(), - analysis_image_count, - FPSeconds(now.time_since_epoch()).count(), - FPSeconds(last_analysis_fps_time.time_since_epoch()).count(), - elapsed.count()); - - if (elapsed > Seconds(1)) { - double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count(); - Info("%s: %d - Analysing at %.2lf fps from %d - %d=%d / %lf - %lf = %lf", - name.c_str(), - analysis_image_count, - new_analysis_fps, - motion_frame_count, - last_motion_frame_count, - (motion_frame_count - last_motion_frame_count), - FPSeconds(now.time_since_epoch()).count(), - FPSeconds(last_analysis_fps_time.time_since_epoch()).count(), - elapsed.count()); - - if (new_analysis_fps != shared_data->analysis_fps) { - shared_data->analysis_fps = new_analysis_fps; - - std::string sql = stringtf("UPDATE LOW_PRIORITY Monitor_Status SET AnalysisFPS = %.2lf WHERE MonitorId=%u", - new_analysis_fps, id); - dbQueue.push(std::move(sql)); - last_analysis_fps_time = now; - last_motion_frame_count = motion_frame_count; - } else { - Debug(4, "No change in fps"); - } // end if change in fps - } // end if at least 1 second has passed since last update - - } // end if time to do an update -} // end void Monitor::UpdateAnalysisFPS +} // void Monitor::UpdateFPS() // Would be nice if this JUST did analysis // This idea is that we should be analysing as close to the capture frame as possible. @@ -2015,8 +2007,7 @@ bool Monitor::Analyse() { } // end if ! event } // end if RECORDING - if (score) { - + if (score and (function == MODECT or function == NODECT)) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() @@ -2261,8 +2252,6 @@ bool Monitor::Analyse() { // Only do these if it's a video packet. shared_data->last_read_index = snap->image_index; analysis_image_count++; - if (function == MODECT or function == MOCORD) - UpdateAnalysisFPS(); } packetqueue.increment_it(analysis_it); packetqueue.unlock(packet_lock); @@ -2511,7 +2500,7 @@ int Monitor::Capture() { if (packet->codec_type == AVMEDIA_TYPE_VIDEO) { packet->packet.stream_index = video_stream_id; // Convert to packetQueue's index if (video_fifo) { - if ( packet->keyframe ) { + if (packet->keyframe) { // avcodec strips out important nals that describe the stream and // stick them in extradata. Need to send them along with keyframes AVStream *stream = camera->getVideoStream(); @@ -2547,7 +2536,6 @@ int Monitor::Capture() { // Will only be queued if there are iterators allocated in the queue. packetqueue.queuePacket(packet); - UpdateCaptureFPS(); } else { // result == 0 // Question is, do we update last_write_index etc? return 0; @@ -2587,7 +2575,7 @@ bool Monitor::Decode() { // //capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); int ret = packet->decode(camera->getVideoCodecContext()); - if (ret > 0) { + if (ret > 0 and !zm_terminate) { if (packet->in_frame and !packet->image) { packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder()); AVFrame *input_frame = packet->in_frame; diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 974ab87cc..56785405f 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -64,7 +64,7 @@ public: } Function; typedef enum { - LOCAL, + LOCAL=1, REMOTE, FILE, FFMPEG, @@ -546,8 +546,7 @@ public: unsigned int GetLastWriteIndex() const; uint64_t GetLastEventId() const; double GetFPS() const; - void UpdateAnalysisFPS(); - void UpdateCaptureFPS(); + void UpdateFPS(); void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" ); void ForceAlarmOff(); void CancelForced(); @@ -567,10 +566,14 @@ public: void actionSuspend(); void actionResume(); - int actionBrightness( int p_brightness=-1 ); - int actionHue( int p_hue=-1 ); - int actionColour( int p_colour=-1 ); - int actionContrast( int p_contrast=-1 ); + int actionBrightness(int p_brightness); + int actionBrightness(); + int actionHue(int p_hue); + int actionHue(); + int actionColour(int p_colour); + int actionColour(); + int actionContrast(int p_contrast); + int actionContrast(); int PrimeCapture(); int PreCapture() const; diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 067946eb7..5c32b8e05 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -422,7 +422,7 @@ bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) { break; case STREAM_RAW : fputs("Content-Type: image/x-rgb\r\n", stdout); - img_buffer = (uint8_t*)send_image->Buffer(); + img_buffer = send_image->Buffer(); img_buffer_size = send_image->Size(); break; case STREAM_ZIP : diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index ad9bb3ef1..ceef421db 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -86,61 +86,6 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { { std::unique_lock lck(mutex); - if (add_packet->packet.stream_index == video_stream_id) { - if ((max_video_packet_count > 0) and (packet_counts[video_stream_id] > max_video_packet_count)) { - Warning("You have set the max video packets in the queue to %u." - " The queue is full. Either Analysis is not keeping up or" - " your camera's keyframe interval is larger than this setting." - " We are dropping packets.", max_video_packet_count); - if (add_packet->keyframe) { - // Have a new keyframe, so delete everything - while ((*pktQueue.begin() != add_packet) and (packet_counts[video_stream_id] > max_video_packet_count)) { - std::shared_ptr zm_packet = *pktQueue.begin(); - ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); - if (!lp->trylock()) { - Debug(1, "Found locked packet when trying to free up video packets. Can't continue"); - delete lp; - break; - } - delete lp; - - for ( - std::list::iterator iterators_it = iterators.begin(); - iterators_it != iterators.end(); - ++iterators_it - ) { - packetqueue_iterator *iterator_it = *iterators_it; - // Have to check each iterator and make sure it doesn't point to the packet we are about to delete - if ( *(*iterator_it) == zm_packet ) { - Debug(1, "Bumping IT because it is at the front that we are deleting"); - ++(*iterators_it); - } - } // end foreach iterator - - pktQueue.pop_front(); - packet_counts[zm_packet->packet.stream_index] -= 1; - Debug(1, - "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu", - zm_packet->packet.stream_index, - zm_packet->image_index, - zm_packet->keyframe, - packet_counts[video_stream_id], - max_video_packet_count, - pktQueue.size()); - } // end while - } - } // end if too many video packets - if (max_video_packet_count > 0) { - while (packet_counts[video_stream_id] > max_video_packet_count) { - Error("Unable to free up older packets. Waiting."); - condition.notify_all(); - condition.wait(lck); - if (deleting or zm_terminate) - return false; - } - } - } // end if this packet is a video packet - pktQueue.push_back(add_packet); packet_counts[add_packet->packet.stream_index] += 1; Debug(2, "packet counts for %d is %d", @@ -148,25 +93,79 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { packet_counts[add_packet->packet.stream_index]); for ( - std::list::iterator iterators_it = iterators.begin(); + auto iterators_it = iterators.begin(); iterators_it != iterators.end(); ++iterators_it ) { packetqueue_iterator *iterator_it = *iterators_it; if (*iterator_it == pktQueue.end()) { - Debug(4, "pointing it %p to back", iterator_it); --(*iterator_it); - } else { - Debug(4, "it %p not at end", iterator_it); } } // end foreach iterator + + if ( + (add_packet->packet.stream_index == video_stream_id) + and + (max_video_packet_count > 0) + and + (packet_counts[video_stream_id] > max_video_packet_count) + ) { + Warning("You have set the max video packets in the queue to %u." + " The queue is full. Either Analysis is not keeping up or" + " your camera's keyframe interval is larger than this setting." + , max_video_packet_count); + + for ( + auto it = ++pktQueue.begin(); + it != pktQueue.end() and *it != add_packet; + ) { + std::shared_ptr zm_packet = *it; + + ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); + if (!lp->trylock()) { + Debug(1, "Found locked packet when trying to free up video packets. Skipping to next one"); + delete lp; + ++it; + continue; + } + + for ( + auto iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + auto iterator_it = *iterators_it; + // Have to check each iterator and make sure it doesn't point to the packet we are about to delete + if ((*iterator_it!=pktQueue.end()) and (*(*iterator_it) == zm_packet)) { + Debug(1, "Bumping IT because it is at the front that we are deleting"); + ++(*iterator_it); + } + } // end foreach iterator + + it = pktQueue.erase(it); + packet_counts[zm_packet->packet.stream_index] -= 1; + Debug(1, + "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu", + zm_packet->packet.stream_index, + zm_packet->image_index, + zm_packet->keyframe, + packet_counts[video_stream_id], + max_video_packet_count, + pktQueue.size()); + + delete lp; + + if (zm_packet->packet.stream_index == video_stream_id) + break; + } // end while + } // end if not able catch up } // end lock scope // We signal on every packet because someday we may analyze sound Debug(4, "packetqueue queuepacket, unlocked signalling"); condition.notify_all(); return true; -} // end bool PacketQueue::queuePacket(ZMPacket* zm_packet) +} // end bool PacketQueue::queuePacket(ZMPacket* zm_packet) void PacketQueue::clearPackets(const std::shared_ptr &add_packet) { // Only do queueCleaning if we are adding a video keyframe, so that we guarantee that there is one. @@ -195,7 +194,6 @@ void PacketQueue::clearPackets(const std::shared_ptr &add_packet) { add_packet->packet.stream_index, video_stream_id, add_packet->keyframe, keep_keyframes, packet_counts[video_stream_id], pre_event_video_packet_count, ( *(pktQueue.begin()) != add_packet ) ); - Warning("Keyframe interval may be larger than MaxImageBufferCount and PreEventCount. Please increase MaxImageBufferCount"); return; } std::unique_lock lck(mutex); @@ -241,8 +239,8 @@ void PacketQueue::clearPackets(const std::shared_ptr &add_packet) { return; } - packetqueue_iterator it = pktQueue.begin(); - packetqueue_iterator next_front = pktQueue.begin(); + auto it = pktQueue.begin(); + auto next_front = pktQueue.begin(); // First packet is special because we know it is a video keyframe and only need to check for lock std::shared_ptr zm_packet = *it; @@ -250,32 +248,32 @@ void PacketQueue::clearPackets(const std::shared_ptr &add_packet) { return; } - Debug(1, "trying lock on first packet"); ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); if (lp->trylock()) { int video_packets_to_delete = 0; // This is a count of how many packets we will delete so we know when to stop looking - Debug(1, "Have lock on first packet"); + Debug(4, "Have lock on first packet"); ++it; delete lp; - if (it == pktQueue.end()) { - Debug(1, "Hit end already"); - it = pktQueue.begin(); - } else { // Since we have many packets in the queue, we should NOT be pointing at end so don't need to test for that while (*it != add_packet) { zm_packet = *it; lp = new ZMLockedPacket(zm_packet); if (!lp->trylock()) { + Debug(3, "Failed locking packet %d", zm_packet->image_index); delete lp; break; } delete lp; - if (is_there_an_iterator_pointing_to_packet(zm_packet) and (pktQueue.begin() == next_front)) { - Warning("Found iterator at beginning of queue. Some thread isn't keeping up"); +#if 0 + // There are no threads that follow analysis thread. So there cannot be an it pointing here + if (is_there_an_iterator_pointing_to_packet(zm_packet)) { + if (pktQueue.begin() == next_front) + Warning("Found iterator at beginning of queue. Some thread isn't keeping up"); break; } +#endif if (zm_packet->packet.stream_index == video_stream_id) { if (zm_packet->keyframe) { @@ -283,17 +281,16 @@ void PacketQueue::clearPackets(const std::shared_ptr &add_packet) { next_front = it; } ++video_packets_to_delete; - Debug(4, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d", - video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete, tail_count); + Debug(3, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d", + video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete, tail_count); if (packet_counts[video_stream_id] - video_packets_to_delete <= pre_event_video_packet_count + tail_count) { break; } } ++it; } // end while - } } // end if first packet not locked - Debug(1, "Resulting pointing at latest packet? %d, next front points to begin? %d", + Debug(1, "Resulting it pointing at latest packet? %d, next front points to begin? %d", ( *it == add_packet ), ( next_front == pktQueue.begin() ) ); diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index 9cb948352..3f45578ec 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -49,45 +49,43 @@ RemoteCamera::RemoteCamera( mNeedAuth(false), mAuthenticator(nullptr) { - if ( path[0] != '/' ) + if (path[0] != '/') path = '/'+path; } RemoteCamera::~RemoteCamera() { - if ( hp != nullptr ) { + if (hp != nullptr) { freeaddrinfo(hp); hp = nullptr; } - if ( mAuthenticator ) { + if (mAuthenticator) { delete mAuthenticator; mAuthenticator = nullptr; } } void RemoteCamera::Initialise() { - if( protocol.empty() ) - Fatal( "No protocol specified for remote camera" ); + if (protocol.empty()) + Fatal("No protocol specified for remote camera"); - if( host.empty() ) - Fatal( "No host specified for remote camera" ); + if (host.empty()) + Fatal("No host specified for remote camera"); - if ( port.empty() ) + if (port.empty()) Fatal("No port specified for remote camera"); - //if( path.empty() ) - //Fatal( "No path specified for remote camera" ); // Cache as much as we can to speed things up - std::string::size_type authIndex = host.rfind( '@' ); + std::string::size_type authIndex = host.rfind('@'); - if ( authIndex != std::string::npos ) { - auth = host.substr( 0, authIndex ); - host.erase( 0, authIndex+1 ); + if (authIndex != std::string::npos) { + auth = host.substr(0, authIndex); + host.erase(0, authIndex+1); auth64 = Base64Encode(auth); - authIndex = auth.rfind( ':' ); + authIndex = auth.rfind(':'); username = auth.substr(0,authIndex); - password = auth.substr( authIndex+1, auth.length() ); + password = auth.substr(authIndex+1, auth.length()); } mNeedAuth = false; @@ -99,13 +97,13 @@ void RemoteCamera::Initialise() { hints.ai_socktype = SOCK_STREAM; int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); - if ( ret != 0 ) { - Error( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); + if (ret != 0) { + Error("Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret)); return; } struct addrinfo *p = nullptr; int addr_count = 0; - for ( p = hp; p != nullptr; p = p->ai_next ) { + for (p = hp; p != nullptr; p = p->ai_next) { addr_count++; } Debug(1, "%d addresses returned", addr_count); diff --git a/src/zm_signal.cpp b/src/zm_signal.cpp index 9a92b5eac..4107e96c8 100644 --- a/src/zm_signal.cpp +++ b/src/zm_signal.cpp @@ -46,6 +46,7 @@ RETSIGTYPE zm_die_handler(int signal, siginfo_t * info, void *context) RETSIGTYPE zm_die_handler(int signal) #endif { + zm_terminate = true; Error("Got signal %d (%s), crashing", signal, strsignal(signal)); #if (defined(__i386__) || defined(__x86_64__)) // Get more information if available diff --git a/src/zm_time.h b/src/zm_time.h index f7574bbf7..d3d5b95c5 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -83,4 +83,41 @@ Duration duration_cast(timeval const &tv) { } } +// +// This can be used for benchmarking. It will measure the time in between +// its constructor and destructor (or when you call Finish()) and add that +// duration to a microseconds clock. +// +class TimeSegmentAdder { + public: + TimeSegmentAdder(Microseconds &in_target) : + target_(in_target), + start_time_(std::chrono::steady_clock::now()), + finished_(false) { + } + + ~TimeSegmentAdder() { + Finish(); + } + + // Call this to stop the timer and add the timed duration to `target`. + void Finish() { + if (!finished_) { + const TimePoint end_time = std::chrono::steady_clock::now(); + target_ += (std::chrono::duration_cast(end_time - start_time_)); + } + finished_ = true; + } + + private: + // This is where we will add our duration to. + Microseconds &target_; + + // The time we started. + const TimePoint start_time_; + + // True when it has finished timing. + bool finished_; +}; + #endif // ZM_TIME_H diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 1bd0724d5..ad401b22c 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -151,6 +151,7 @@ bool VideoStore::open() { Debug(3, "Encoder Option %s=%s", e->key, e->value); } } + av_dict_free(&opts); if (video_in_stream) { zm_dump_codecpar(video_in_stream->codecpar); @@ -184,6 +185,7 @@ bool VideoStore::open() { } } // end if orientation + av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); if (av_dict_get(opts, "new_extradata", nullptr, AV_DICT_MATCH_CASE)) { av_dict_set(&opts, "new_extradata", nullptr, 0); // Special flag to tell us to open a codec to get new extraflags to fix weird h265 @@ -230,8 +232,8 @@ bool VideoStore::open() { if (ret < 0) { Error("Could not initialize stream parameteres"); } + av_dict_free(&opts); } // end if extradata_entry - av_dict_free(&opts); } else if (monitor->GetOptVideoWriter() == Monitor::ENCODE) { int wanted_codec = monitor->OutputCodec(); if (!wanted_codec) { @@ -485,6 +487,7 @@ bool VideoStore::open() { zm_dump_stream_format(oc, 0, 0, 1); if (audio_out_stream) zm_dump_stream_format(oc, 1, 0, 1); + av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); const AVDictionaryEntry *movflags_entry = av_dict_get(opts, "movflags", nullptr, AV_DICT_MATCH_CASE); if (!movflags_entry) { Debug(1, "setting movflags to frag_keyframe+empty_moov"); @@ -616,7 +619,8 @@ VideoStore::~VideoStore() { Debug(1, "Writing trailer"); /* Write the trailer before close */ - if (int rc = av_write_trailer(oc)) { + int rc; + if ((rc = av_write_trailer(oc)) < 0) { Error("Error writing trailer %s", av_err2str(rc)); } else { Debug(3, "Success Writing trailer"); @@ -626,7 +630,7 @@ VideoStore::~VideoStore() { if (!(out_format->flags & AVFMT_NOFILE)) { /* Close the out file. */ Debug(4, "Closing"); - if (int rc = avio_close(oc->pb)) { + if ((rc = avio_close(oc->pb)) < 0) { Error("Error closing avio %s", av_err2str(rc)); } } else { diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 682fc147b..c73ed19db 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -111,6 +111,13 @@ class VideoStore { int writePacket(const std::shared_ptr &pkt); int write_packets(PacketQueue &queue); void flush_codecs(); + const char *get_codec() { + if (chosen_codec_data) + return chosen_codec_data->codec_codec; + if (video_out_stream) + return avcodec_get_name(video_out_stream->codecpar->codec_id); + return ""; + } }; #endif // ZM_VIDEOSTORE_H diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index bc7086614..f0c09ec78 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -206,7 +206,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { // Get the difference image Image *diff_image = image = new Image(*delta_image); int diff_width = diff_image->Width(); - uint8_t* diff_buff = (uint8_t*)diff_image->Buffer(); + uint8_t* diff_buff = diff_image->Buffer(); uint8_t* pdiff; unsigned int pixel_diff_count = 0; @@ -283,7 +283,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; - pdiff = (uint8_t*)diff_image->Buffer(lo_x, y); + pdiff = diff_image->Buffer(lo_x, y); for (int x = lo_x; x <= hi_x; x++, pdiff++) { if (*pdiff == kWhite) { @@ -366,7 +366,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; - pdiff = (uint8_t*)diff_image->Buffer(lo_x, y); + pdiff = diff_image->Buffer(lo_x, y); for (int x = lo_x; x <= hi_x; x++, pdiff++) { if (*pdiff == kWhite) { Debug(9, "Got white pixel at %d,%d (%p)", x, y, pdiff); @@ -980,7 +980,7 @@ void Zone::std_alarmedpixels( unsigned int hi_x = ranges[y].hi_x; Debug(7, "Checking line %d from %d -> %d", y, lo_x, hi_x); - uint8_t *pdiff = (uint8_t*)pdiff_image->Buffer(lo_x, y); + uint8_t *pdiff = pdiff_image->Buffer(lo_x, y); const uint8_t *ppoly = ppoly_image->Buffer(lo_x, y); for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) { diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp new file mode 100644 index 000000000..5e6b93e42 --- /dev/null +++ b/src/zmbenchmark.cpp @@ -0,0 +1,326 @@ +// +// ZoneMinder Benchmark, $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 +#include +#include +#include +#include +#include + +#include "zm_config.h" +#include "zm_image.h" +#include "zm_monitor.h" +#include "zm_time.h" +#include "zm_utils.h" +#include "zm_zone.h" + +static std::mt19937 mt_rand(111); + +// +// This allows you to feed in a set of columns and timing rows, and print it +// out in a nice-looking table. +// +class TimingsTable { + public: + explicit TimingsTable(std::vector in_columns) : columns_(std::move(in_columns)) {} + + // + // Add a row to the end of the table. + // + // Args: + // label: The name of the row (printed in the first column). + // timings: The values for all the other columns in this row. + void AddRow(const std::string &label, const std::vector &timings) { + assert(timings.size() == columns_.size()); + Row row; + row.label = label; + row.timings = timings; + rows_.push_back(row); + } + + // + // Print out the table. + // + // Args: + // columnPad: # characters between table columns + // + void Print(const int column_pad = 5) { + // Figure out column widths. + std::vector widths(columns_.size() + 1); + + // The first width is the max of the row labels. + auto result = std::max_element(rows_.begin(), + rows_.end(), + [](const Row &a, const Row &b) -> bool { + return a.label.length() < b.label.length(); + }); + widths[0] = result->label.length() + column_pad; + + // Calculate the rest of the column widths. + for (size_t i = 0 ; i < columns_.size() ; i++) + widths[i + 1] = columns_[i].length() + column_pad; + + auto PrintColStr = [&](size_t icol, const std::string &str) { + printf("%s", str.c_str()); + PrintPadding(widths[icol] - str.length()); + }; + + // Print the header. + PrintColStr(0, ""); + for (size_t i = 0 ; i < columns_.size() ; i++) { + PrintColStr(i + 1, columns_[i]); + } + printf("\n"); + + // Print the timings rows. + for (const Row &row : rows_) { + PrintColStr(0, row.label); + + for (size_t i = 0 ; i < row.timings.size() ; i++) { + std::string num = stringtf("%.2f", std::chrono::duration_cast(row.timings[i]).count()); + PrintColStr(i + 1, num); + } + + printf("\n"); + } + } + + private: + static void PrintPadding(size_t count) { + std::string str(count, ' '); + printf("%s", str.c_str()); + } + + struct Row { + std::string label; + std::vector timings; + }; + + std::vector columns_; + std::vector rows_; +}; + +// +// Generate a greyscale image that simulates a delta that can be fed to +// Zone::CheckAlarms. This first creates a black image, and then it fills +// a box of a certain size inside the image with random data. This is to simulate +// a typical scene where most of the scene doesn't change except a specific region. +// +// Args: +// changeBoxPercent: 0-100 value telling how large the box with random data should be. +// Set to 0 to leave the whole thing black. +// width: The width of the new image. +// height: The height of the new image. +// +// Return: +// An image with all pixels initialized to values in the [minVal,maxVal] range. +// +std::shared_ptr GenerateRandomImage( + const int change_box_percent, + const int width = 3840, + const int height = 2160) { + // Create the image. + Image *image = new Image(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); + + // Set it to black initially. + memset(image->Buffer(0, 0), 0, (size_t) image->LineSize() * (size_t) image->Height()); + + // Now randomize the pixels inside a box. + const int box_width = (width * change_box_percent) / 100; + const int box_height = (height * change_box_percent) / 100; + const int box_x = (int) ((uint64_t) mt_rand() * (width - box_width) / RAND_MAX); + const int box_y = (int) ((uint64_t) mt_rand() * (height - box_height) / RAND_MAX); + + for (int y = 0 ; y < box_height ; y++) { + uint8_t *row = image->Buffer(box_x, box_y + y); + for (int x = 0 ; x < box_width ; x++) { + row[x] = (uint8_t) mt_rand(); + } + } + + return std::shared_ptr(image); +} + +// +// This is used to help rig up Monitor benchmarks. +// +class TestMonitor : public Monitor { + public: + TestMonitor(int width, int height) : cur_zone_id(111) { + this->width = width; + this->height = height; + + // Create a dummy ref_image. + std::shared_ptr tempImage = GenerateRandomImage(0, width, height); + ref_image = *tempImage; + + shared_data = &temp_shared_data; + } + + // + // Add a new zone to this monitor. + // + // Args: + // checkMethod: This controls how this zone will actually do motion detection. + // + // p_filter_box: The size of the filter to use. + // + void AddZone(Zone::CheckMethod checkMethod, const Vector2 &p_filter_box = Vector2(5, 5)) { + const int p_min_pixel_threshold = 50; + const int p_max_pixel_threshold = 255; + const int p_min_alarm_pixels = 1000; + const int p_max_alarm_pixels = 10000000; + + const int zone_id = cur_zone_id++; + const std::string zone_label = std::string("zone_") + std::to_string(zone_id); + const Zone::ZoneType zone_type = Zone::ZoneType::ACTIVE; + const Polygon poly({Vector2(0, 0), + Vector2(width - 1, 0), + Vector2(width - 1, height - 1), + Vector2(0, height - 1)}); + + Zone zone(this, + zone_id, + zone_label.c_str(), + zone_type, + poly, + kRGBGreen, + Zone::CheckMethod::FILTERED_PIXELS, + p_min_pixel_threshold, + p_max_pixel_threshold, + p_min_alarm_pixels, + p_max_alarm_pixels, + p_filter_box); + zones.push_back(zone); + } + + void SetRefImage(const Image *image) { + ref_image = *image; + } + + private: + SharedData temp_shared_data; + int cur_zone_id; +}; + +// +// Run zone benchmarks on the given image. +// +// Args: +// label: A label to be printed before the output. +// +// image: The image to run the tests on. +// +// p_filter_box: The size of the filter to use for alarm detection. +// +// Return: +// The average time taken for each DetectMotion call. +// +Microseconds RunDetectMotionBenchmark(const std::string &label, + const std::shared_ptr& image, + const Vector2 &p_filter_box) { + // Create a monitor to use for the benchmark. Give it 1 zone that uses + // a 5x5 filter. + TestMonitor testMonitor(image->Width(), image->Height()); + testMonitor.AddZone(Zone::CheckMethod::FILTERED_PIXELS, p_filter_box); + + // Generate a black image to use as the reference image. + std::shared_ptr blackImage = GenerateRandomImage( + 0, image->Width(), image->Height()); + testMonitor.SetRefImage(blackImage.get()); + + Microseconds totalTimeTaken(0); + + // Run a series of passes over DetectMotion. + const int numPasses = 10; + for (int i = 0 ; i < numPasses ; i++) { + printf("\r%s - pass %2d / %2d ", label.c_str(), i + 1, numPasses); + fflush(stdout); + + TimeSegmentAdder adder(totalTimeTaken); + + Event::StringSet zoneSet; + testMonitor.DetectMotion(*image, zoneSet); + } + printf("\n"); + + return totalTimeTaken / numPasses; +} + +// +// This runs a set of Monitor::DetectMotion benchmarks, one for each of the +// "delta box percents" that are passed in. This adds one row to the +// TimingsTable specified. +// +// Args: +// table: The table to add timings into. +// +// deltaBoxPercents: Each of these defines a box size in the delta images +// passed to DetectMotion (larger boxes make it slower, sometimes significantly so). +// +// p_filter_box: Defines the filter size used in DetectMotion. +// +void RunDetectMotionBenchmarks( + TimingsTable &table, + const std::vector &delta_box_percents, + const Vector2 &p_filter_box) { + std::vector timings; + + for (int percent : delta_box_percents) { + Microseconds timing = RunDetectMotionBenchmark( + std::string("DetectMotion: ") + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + + " box, " + std::to_string(percent) + "% delta", + GenerateRandomImage(percent), + p_filter_box); + timings.push_back(timing); + } + + table.AddRow( + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " filter", + timings); +} + +int main(int argc, char *argv[]) { + // Init global stuff that we need. + config.font_file_location = "../fonts/default.zmfnt"; + config.event_close_mode = "time"; + config.cpu_extensions = true; + + // Detect SSE version. + HwCapsDetect(); + + // Setup the column titles for the TimingsTable we'll generate. + // Each column represents how large the box in the image is with delta pixels. + // Each row represents a different filter size. + const std::vector percents = {0, 10, 50, 100}; + std::vector columns(percents.size()); + std::transform(percents.begin(), percents.end(), columns.begin(), + [](const int percent) { return std::to_string(percent) + "% delta (ms)"; }); + TimingsTable table(columns); + + std::vector filterSizes = {Vector2(3, 3), Vector2(5, 5), Vector2(13, 13)}; + for (const auto filterSize : filterSizes) { + RunDetectMotionBenchmarks(table, percents, filterSize); + } + + table.Print(); + return 0; +} + diff --git a/src/zmc.cpp b/src/zmc.cpp index d974d1bec..68c6227af 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -268,7 +268,7 @@ int main(int argc, char *argv[]) { std::this_thread::sleep_for(sleep_time); } - if (zm_terminate){ + if (zm_terminate) { break; } @@ -308,6 +308,7 @@ int main(int argc, char *argv[]) { result = -1; break; } + monitors[i]->UpdateFPS(); // capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate. Microseconds delay = (monitors[i]->GetState() == Monitor::ALARM) ? monitors[i]->GetAlarmCaptureDelay() diff --git a/src/zmu.cpp b/src/zmu.cpp index ddf66a3dd..ec4ae7b86 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -257,9 +257,16 @@ int main(int argc, char *argv[]) { int image_idx = -1; int scale = -1; int brightness = -1; + bool have_brightness = false; + int contrast = -1; + bool have_contrast = false; + int hue = -1; + bool have_hue = false; int colour = -1; + bool have_colour = false; + char *zoneString = nullptr; char *username = nullptr; char *password = nullptr; @@ -272,13 +279,13 @@ int main(int argc, char *argv[]) { int option_index = 0; int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::RWU:P:A:V:T:", long_options, &option_index); - if ( c == -1 ) { + if (c == -1) { break; } switch (c) { case 'd': - if ( optarg ) + if (optarg) device = optarg; break; case 'm': @@ -292,7 +299,7 @@ int main(int argc, char *argv[]) { break; case 'i': function |= ZMU_IMAGE; - if ( optarg ) + if (optarg) image_idx = atoi(optarg); break; case 'S': @@ -300,7 +307,7 @@ int main(int argc, char *argv[]) { break; case 't': function |= ZMU_TIME; - if ( optarg ) + if (optarg) image_idx = atoi(optarg); break; case 'R': @@ -317,7 +324,7 @@ int main(int argc, char *argv[]) { break; case 'z': function |= ZMU_ZONES; - if ( optarg ) + if (optarg) zoneString = optarg; break; case 'a': @@ -349,23 +356,31 @@ int main(int argc, char *argv[]) { break; case 'B': function |= ZMU_BRIGHTNESS; - if ( optarg ) + if (optarg) { + have_brightness = true; brightness = atoi(optarg); + } break; case 'C': function |= ZMU_CONTRAST; - if ( optarg ) + if (optarg) { + have_contrast = true; contrast = atoi(optarg); + } break; case 'H': function |= ZMU_HUE; - if ( optarg ) + if (optarg) { + have_hue = true; hue = atoi(optarg); + } break; case 'O': function |= ZMU_COLOUR; - if ( optarg ) + if (optarg) { + have_colour = true; colour = atoi(optarg); + } break; case 'U': username = optarg; @@ -650,60 +665,60 @@ int main(int argc, char *argv[]) { monitor->DumpSettings(monString, verbose); printf("%s\n", monString); } - if ( function & ZMU_BRIGHTNESS ) { - if ( verbose ) { - if ( brightness >= 0 ) + if (function & ZMU_BRIGHTNESS) { + if (verbose) { + if (have_brightness) printf("New brightness: %d\n", monitor->actionBrightness(brightness)); else printf("Current brightness: %d\n", monitor->actionBrightness()); } else { - if ( have_output ) fputc(separator, stdout); - if ( brightness >= 0 ) + if (have_output) fputc(separator, stdout); + if (have_brightness) printf("%d", monitor->actionBrightness(brightness)); else printf("%d", monitor->actionBrightness()); have_output = true; } } - if ( function & ZMU_CONTRAST ) { - if ( verbose ) { - if ( contrast >= 0 ) - printf("New brightness: %d\n", monitor->actionContrast(contrast)); + if (function & ZMU_CONTRAST) { + if (verbose) { + if (have_contrast) + printf("New contrast: %d\n", monitor->actionContrast(contrast)); else printf("Current contrast: %d\n", monitor->actionContrast()); } else { - if ( have_output ) fputc(separator, stdout); - if ( contrast >= 0 ) + if (have_output) fputc(separator, stdout); + if (have_contrast) printf("%d", monitor->actionContrast(contrast)); else printf("%d", monitor->actionContrast()); have_output = true; } } - if ( function & ZMU_HUE ) { - if ( verbose ) { - if ( hue >= 0 ) + if (function & ZMU_HUE) { + if (verbose) { + if (have_hue) printf("New hue: %d\n", monitor->actionHue(hue)); else printf("Current hue: %d\n", monitor->actionHue()); } else { - if ( have_output ) fputc(separator, stdout); - if ( hue >= 0 ) + if (have_output) fputc(separator, stdout); + if (have_hue) printf("%d", monitor->actionHue(hue)); else printf("%d", monitor->actionHue()); have_output = true; } } - if ( function & ZMU_COLOUR ) { - if ( verbose ) { - if ( colour >= 0 ) + if (function & ZMU_COLOUR) { + if (verbose) { + if (have_colour) printf("New colour: %d\n", monitor->actionColour(colour)); else printf("Current colour: %d\n", monitor->actionColour()); } else { - if ( have_output ) fputc(separator, stdout); - if ( colour >= 0 ) + if (have_output) fputc(separator, stdout); + if (have_colour) printf("%d", monitor->actionColour(colour)); else printf("%d", monitor->actionColour()); @@ -711,7 +726,7 @@ int main(int argc, char *argv[]) { } } - if ( have_output ) { + if (have_output) { printf("\n"); } if ( !function ) { diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 691fb530d..2a4dd6989 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -230,9 +230,13 @@ rm .gitignore cd ../ -if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then +if [ !-e "$DIRECTORY.orig.tar.gz" ]; then +read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]" + if [[ $REPLY == [yY] ]]; then + tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig fi; +fi; IFS=',' ;for DISTRO in `echo "$DISTROS"`; do echo "Generating package for $DISTRO"; @@ -358,7 +362,7 @@ EOF dput="Y"; if [ "$INTERACTIVE" != "no" ]; then read -p "Ready to dput $SC to $PPA ? Y/n..."; - if [[ "$REPLY" == [yY] ]]; then + if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then dput $PPA $SC fi; else diff --git a/web/ajax/events.php b/web/ajax/events.php index 562cce9c2..4e364242a 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -6,28 +6,30 @@ $data = array(); // INITIALIZE AND CHECK SANITY // -if ( !canView('Events') ) $message = 'Insufficient permissions for user '.$user['Username']; +if (!canView('Events')) + $message = 'Insufficient permissions for user '.$user['Username'].'
'; -if ( empty($_REQUEST['task']) ) { - $message = 'Must specify a task'; +if (empty($_REQUEST['task'])) { + $message = 'Must specify a task
'; } else { $task = $_REQUEST['task']; } -if ( empty($_REQUEST['eids']) ) { - if ( isset($_REQUEST['task']) && $_REQUEST['task'] != 'query' ) $message = 'No event id(s) supplied'; +if (empty($_REQUEST['eids'])) { + if (isset($_REQUEST['task']) && $_REQUEST['task'] != 'query') + $message = 'No event id(s) supplied
'; } else { $eids = $_REQUEST['eids']; } -if ( $message ) { +if ($message) { ajaxError($message); return; } require_once('includes/Filter.php'); $filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter(); -if ( $user['MonitorIds'] ) { +if ($user['MonitorIds']) { $filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds'])); } @@ -39,10 +41,19 @@ $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : ''; $advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); // Order specifies the sort direction, either asc or desc -$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; +$order = $filter->sort_asc() ? 'ASC' : 'DESC'; +if (isset($_REQUEST['order'])) { + if (strtolower($_REQUEST['order']) == 'asc') { + $order = 'ASC'; + } else if (strtolower($_REQUEST['order']) == 'desc') { + $order = 'DESC'; + } else { + Warning("Invalid value for order " . $_REQUEST['order']); + } +} // Sort specifies the name of the column to sort on -$sort = 'StartDateTime'; +$sort = $filter->sort_field(); if (isset($_REQUEST['sort'])) { $sort = $_REQUEST['sort']; if ($sort == 'EndDateTime') { @@ -228,15 +239,17 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim } # end if search $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order; - ZM\Debug('Calling the following sql query: ' .$sql); $filtered_rows = dbFetchAll($sql); - ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter.'); + ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter: '.$sql); } else { $filtered_rows = $unfiltered_rows; } # end if search_filter->terms() > 1 + if ($limit) + $filtered_rows = array_slice($filtered_rows, $offset, $limit); + $returned_rows = array(); - foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) { + foreach ($filtered_rows as $row) { $event = new ZM\Event($row); $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); diff --git a/web/ajax/modals/settings.php b/web/ajax/modals/settings.php index 59d19ddbb..de42d6c87 100644 --- a/web/ajax/modals/settings.php +++ b/web/ajax/modals/settings.php @@ -1,11 +1,11 @@ $_REQUEST['mid'])); $zmuCommand = getZmuCommand(' -m '.escapeshellarg($_REQUEST['mid']).' -B -C -H -O'); $zmuOutput = exec( $zmuCommand ); -if ( $zmuOutput ) { +if ($zmuOutput) { list($brightness, $contrast, $hue, $colour) = explode(' ', $zmuOutput); $monitor->Brightness($brightness); @@ -13,7 +13,6 @@ if ( $zmuOutput ) { $monitor->Hue($hue); $monitor->Colour($colour); } - ?> - +

All

'; echo '

Monitor: '.$monitorNames[$monitor_id].'

'; - foreach ( $events as $event ) { - if ( $event->MonitorId() == $monitor_id ) { + foreach ($events as $event) { + if ($event->MonitorId() == $monitor_id) { echo eventlist_html($event, $exportDetail, $exportFrames, $exportStructure); } # end if its the right monitor } # end foreach event @@ -665,7 +659,7 @@ function exportEventImagesMaster($eids, $exportDetail, $exportFrames, $exportStr } # end foreach monitor ?> -
+