Merge branch 'smarter_packetqueue' into storageareas
This commit is contained in:
commit
7149576a7c
|
@ -781,6 +781,7 @@ INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,1
|
|||
INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
|
||||
INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0);
|
||||
INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
|
||||
INSERT INTO `Controls` VALUES (NULL,'PSIA','Remote','PSIA',0,0,0,1,0,0,1,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,-100,100,0,0,1,0,0,0,0,1,-100,100,0,0,0,0);
|
||||
INSERT INTO `Controls` VALUES (NULL,'Dahua','Remote','Dahua',0,0,0,1,0,0,1,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,1,8,0,0,1,0,0,0,0,1,1,8,0,0,0,0);
|
||||
|
||||
--
|
||||
|
|
|
@ -0,0 +1,365 @@
|
|||
package ZoneMinder::Control::PSIA;
|
||||
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require ZoneMinder::Base;
|
||||
require ZoneMinder::Control;
|
||||
|
||||
our @ISA = qw(ZoneMinder::Control);
|
||||
|
||||
our $REALM = 'TV-IP450PI';
|
||||
our $USERNAME = 'admin';
|
||||
our $PASSWORD = '';
|
||||
our $ADDRESS = '';
|
||||
our $PROTOCOL = 'http://';
|
||||
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Config qw(:all);
|
||||
use ZoneMinder::Database qw(zmDbConnect);
|
||||
|
||||
sub open
|
||||
{
|
||||
my $self = shift;
|
||||
$self->loadMonitor();
|
||||
|
||||
if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/ ) ) {
|
||||
$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';
|
||||
}
|
||||
|
||||
use LWP::UserAgent;
|
||||
$self->{ua} = LWP::UserAgent->new;
|
||||
$self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION );
|
||||
$self->{state} = 'closed';
|
||||
Debug( "sendCmd credentials control address:'".$ADDRESS
|
||||
."' realm:'" . $REALM
|
||||
. "' username:'" . $USERNAME
|
||||
. "' password:'".$PASSWORD
|
||||
."'"
|
||||
);
|
||||
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
|
||||
|
||||
# Detect REALM
|
||||
my $req = HTTP::Request->new(GET=>$PROTOCOL . $ADDRESS . "/PSIA/PTZ/channels");
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success) {
|
||||
$self->{state} = 'open';
|
||||
return;
|
||||
} elsif (! $res->is_success) {
|
||||
Debug("Need newer REALM");
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
my $headers = $res->headers();
|
||||
foreach my $k ( keys %$headers ) {
|
||||
Debug("Initial Header $k => $$headers{$k}");
|
||||
} # end foreach
|
||||
if ( $$headers{'www-authenticate'} ) {
|
||||
my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
|
||||
if ($tokens =~ /\w+="([^"]+)"/i) {
|
||||
$REALM = $1;
|
||||
Debug("Changing REALM to $REALM");
|
||||
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
|
||||
} # end if
|
||||
} else {
|
||||
Debug("No WWW-Authenticate header");
|
||||
} # end if www-authenticate header
|
||||
} # end if $res->status_line() eq '401 Unauthorized'
|
||||
} # end elsif ! $res->is_success
|
||||
}
|
||||
|
||||
sub close
|
||||
{
|
||||
my $self = shift;
|
||||
$self->{state} = 'closed';
|
||||
}
|
||||
|
||||
sub printMsg
|
||||
{
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $msg_len = length($msg);
|
||||
|
||||
Debug( $msg."[".$msg_len."]" );
|
||||
}
|
||||
|
||||
sub sendGetRequest {
|
||||
my $self = shift;
|
||||
my $url_path = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
my $url = $PROTOCOL . $ADDRESS . $url_path;
|
||||
my $req = HTTP::Request->new(GET=>$url);
|
||||
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
|
||||
Error("Content was " . $res->content() );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("Content was " . $res->content() );
|
||||
}
|
||||
}
|
||||
if ( ! $result ) {
|
||||
Error("Error check failed: '".$res->status_line());
|
||||
}
|
||||
}
|
||||
return($result);
|
||||
}
|
||||
sub sendPutRequest {
|
||||
my $self = shift;
|
||||
my $url_path = shift;
|
||||
my $content = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
my $url = $PROTOCOL . $ADDRESS . $url_path;
|
||||
my $req = HTTP::Request->new(PUT=>$url);
|
||||
if(defined($content)) {
|
||||
$req->content_type("application/x-www-form-urlencoded; charset=UTF-8");
|
||||
$req->content('<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $content);
|
||||
}
|
||||
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
|
||||
Error("Content was " . $res->content() );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("Content was " . $res->content() );
|
||||
}
|
||||
}
|
||||
if ( ! $result ) {
|
||||
Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
|
||||
}
|
||||
}
|
||||
return($result);
|
||||
}
|
||||
sub sendDeleteRequest {
|
||||
my $self = shift;
|
||||
my $url_path = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
my $url = $PROTOCOL . $ADDRESS . $url_path;
|
||||
my $req = HTTP::Request->new(DELETE=>$url);
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
|
||||
Error("Content was " . $res->content() );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("Content was " . $res->content() );
|
||||
}
|
||||
}
|
||||
if ( ! $result ) {
|
||||
Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
|
||||
}
|
||||
}
|
||||
return($result);
|
||||
}
|
||||
|
||||
sub move
|
||||
{
|
||||
my $self = shift;
|
||||
my $panPercentage = shift;
|
||||
my $tiltPercentage = shift;
|
||||
my $zoomPercentage = shift;
|
||||
|
||||
my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps";
|
||||
my $ptzdata = '<PTZData version="1.0" xmlns="urn:psialliance-org">';
|
||||
$ptzdata .= '<pan>' . $panPercentage . '</pan>';
|
||||
$ptzdata .= '<tilt>' . $tiltPercentage . '</tilt>';
|
||||
$ptzdata .= '<zoom>' . $zoomPercentage . '</zoom>';
|
||||
$ptzdata .= '<Momentary><duration>500</duration></Momentary>';
|
||||
$ptzdata .= '</PTZData>';
|
||||
$self->sendPutRequest("/PSIA/PTZ/channels/1/momentary", $ptzdata);
|
||||
}
|
||||
|
||||
sub moveRelUpLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up Left" );
|
||||
$self->move(-50, 50, 0);
|
||||
}
|
||||
|
||||
sub moveRelUp
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up" );
|
||||
$self->move(0, 50, 0);
|
||||
}
|
||||
|
||||
sub moveRelUpRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up Right" );
|
||||
$self->move(50, 50, 0);
|
||||
}
|
||||
|
||||
sub moveRelLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Left" );
|
||||
$self->move(-50, 0, 0);
|
||||
}
|
||||
|
||||
sub moveRelRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Right" );
|
||||
$self->move(50, 0, 0);
|
||||
}
|
||||
|
||||
sub moveRelDownLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down Left" );
|
||||
$self->move(-50, -50, 0);
|
||||
}
|
||||
|
||||
sub moveRelDown
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down" );
|
||||
$self->move(0, -50, 0);
|
||||
}
|
||||
|
||||
sub moveRelDownRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down Right" );
|
||||
$self->move(50, -50, 0);
|
||||
}
|
||||
|
||||
sub zoomRelTele
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Zoom Relative Tele");
|
||||
$self->move(0, 0, 50);
|
||||
}
|
||||
|
||||
sub zoomRelWide
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Zoom Relative Wide");
|
||||
$self->move(0, 0, -50);
|
||||
}
|
||||
|
||||
|
||||
sub presetClear
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
my $url_path = "/PSIA/PTZ/channels/1/presets/" . $preset_id;
|
||||
$self->sendDeleteRequest($url_path);
|
||||
}
|
||||
|
||||
|
||||
sub presetSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
|
||||
my $dbh = zmDbConnect(1);
|
||||
my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?';
|
||||
my $sth = $dbh->prepare($sql)
|
||||
or Fatal("Can't prepare sql '$sql': " . $dbh->errstr());
|
||||
my $res = $sth->execute($self->{Monitor}->{Id}, $preset_id)
|
||||
or Fatal("Can't execute sql '$sql': " . $sth->errstr());
|
||||
my $control_preset_row = $sth->fetchrow_hashref();
|
||||
my $new_label_name = $control_preset_row->{'Label'};
|
||||
|
||||
my $url_path = "/PSIA/PTZ/channels/1/presets/" . $preset_id;
|
||||
my $ptz_preset_data = '<PTZPreset>';
|
||||
$ptz_preset_data .= '<id>' . $preset_id . '</id>';
|
||||
$ptz_preset_data .= '<presetName>' . $new_label_name . '</presetName>';
|
||||
$ptz_preset_data .= '</PTZPreset>';
|
||||
$self->sendPutRequest($url_path, $ptz_preset_data);
|
||||
}
|
||||
|
||||
sub presetGoto
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
|
||||
my $url_path = '/PSIA/PTZ/channels/1/presets/' . $preset_id . '/goto';
|
||||
|
||||
$self->sendPutRequest($url_path);
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
ZoneMinder::Control::PSIA - Perl module for cameras implementing the PSIA
|
||||
(Physical Security Interoperability Alliance), IP Media Devices API
|
||||
specification
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use ZoneMinder::Control::PSIA;
|
||||
place this in /usr/share/perl5/ZoneMinder/Control
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This has so far been tested with:
|
||||
- Trendnet TV-IP450PI
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2018 ZoneMinder LLC
|
||||
|
||||
This library 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 library 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.
|
||||
|
||||
=cut
|
|
@ -162,7 +162,7 @@ my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL};
|
|||
my $event_id = 0;
|
||||
|
||||
if ( ! EVENT_PATH ) {
|
||||
Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}\n");
|
||||
Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}");
|
||||
die;
|
||||
}
|
||||
|
||||
|
@ -173,11 +173,11 @@ chdir( EVENT_PATH );
|
|||
my $dbh = zmDbConnect();
|
||||
|
||||
if ( $filter_name ) {
|
||||
Info("Scanning for events using filter '$filter_name'\n");
|
||||
Info("Scanning for events using filter '$filter_name'");
|
||||
} elsif ( $filter_id ) {
|
||||
Info("Scanning for events using filter id '$filter_id'\n");
|
||||
Info("Scanning for events using filter id '$filter_id'");
|
||||
} else {
|
||||
Info("Scanning for events using all filters\n");
|
||||
Info("Scanning for events using all filters");
|
||||
}
|
||||
|
||||
if ( ! ( $filter_name or $filter_id ) ) {
|
||||
|
@ -191,7 +191,7 @@ my $last_action = 0;
|
|||
while( !$zm_terminate ) {
|
||||
my $now = time;
|
||||
if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) {
|
||||
Debug("Reloading filters\n");
|
||||
Debug("Reloading filters");
|
||||
$last_action = $now;
|
||||
@filters = getFilters({ Name=>$filter_name, Id=>$filter_id });
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ while( !$zm_terminate ) {
|
|||
|
||||
last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate;
|
||||
|
||||
Debug("Sleeping for $delay seconds\n");
|
||||
Debug("Sleeping for $delay seconds");
|
||||
sleep($delay);
|
||||
}
|
||||
|
||||
|
@ -405,10 +405,10 @@ sub generateVideo {
|
|||
chomp($output);
|
||||
my $status = $? >> 8;
|
||||
if ( $status || logDebugging() ) {
|
||||
Debug("Output: $output\n");
|
||||
Debug("Output: $output");
|
||||
}
|
||||
if ( $status ) {
|
||||
Error("Video generation '$command' failed with status: $status\n");
|
||||
Error("Video generation '$command' failed with status: $status");
|
||||
if ( wantarray() ) {
|
||||
return( undef, undef );
|
||||
}
|
||||
|
@ -453,7 +453,7 @@ sub generateImage {
|
|||
chomp( $output );
|
||||
my $status = $? >> 8;
|
||||
if ( $status || logDebugging() ) {
|
||||
Debug("Output: $output\n");
|
||||
Debug("Output: $output");
|
||||
}
|
||||
if ( $status ) {
|
||||
Error("Failed $command status $status");
|
||||
|
@ -479,10 +479,9 @@ sub uploadArchFile {
|
|||
.'/'
|
||||
.(
|
||||
( $Config{ZM_UPLOAD_ARCH_ANALYSE} )
|
||||
? '{*analyse,*capture}'
|
||||
: '*capture'
|
||||
? '{*analyse.jpg,*capture.jpg,snapshot.jpg,*.mp4}'
|
||||
: '*capture.jpg,snapshot.jpg,*.mp4'
|
||||
)
|
||||
.'.jpg'
|
||||
;
|
||||
my @archImageFiles = glob($archImagePath);
|
||||
my $archLocPath;
|
||||
|
@ -492,11 +491,11 @@ sub uploadArchFile {
|
|||
$archFile .= '.zip';
|
||||
$archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
|
||||
my $zip = Archive::Zip->new();
|
||||
Info("Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n");
|
||||
Info("Creating upload file '$archLocPath', ".int(@archImageFiles).' files');
|
||||
|
||||
my $status = &AZ_OK;
|
||||
foreach my $imageFile ( @archImageFiles ) {
|
||||
Debug("Adding $imageFile\n");
|
||||
Debug("Adding $imageFile");
|
||||
my $member = $zip->addFile($imageFile);
|
||||
if ( !$member ) {
|
||||
Error("Unable toto add image file $imageFile to zip archive $archLocPath");
|
||||
|
@ -524,7 +523,7 @@ sub uploadArchFile {
|
|||
$archFile .= '.tar';
|
||||
}
|
||||
$archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
|
||||
Info("Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n");
|
||||
Info("Creating upload file '$archLocPath', ".int(@archImageFiles).' files');
|
||||
|
||||
if ( $archError = !Archive::Tar->create_archive(
|
||||
$archLocPath,
|
||||
|
|
|
@ -755,6 +755,12 @@ void EventStream::runStream() {
|
|||
// commands may set send_frame to true
|
||||
while(checkCommandQueue());
|
||||
|
||||
// Update modified time of the socket .lock file so that we can tell which ones are stale.
|
||||
if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
|
||||
touch(sock_path_lock);
|
||||
last_comm_update = now;
|
||||
}
|
||||
|
||||
if ( step != 0 )
|
||||
curr_frame_id += step;
|
||||
|
||||
|
|
|
@ -82,8 +82,35 @@ static AVPixelFormat get_format(AVCodecContext *avctx, const enum AVPixelFormat
|
|||
}
|
||||
#endif
|
||||
|
||||
FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
|
||||
Camera( p_id, FFMPEG_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 ),
|
||||
FfmpegCamera::FfmpegCamera(
|
||||
int p_id,
|
||||
const std::string &p_path,
|
||||
const std::string &p_method,
|
||||
const std::string &p_options,
|
||||
int p_width,
|
||||
int p_height,
|
||||
int p_colours,
|
||||
int p_brightness,
|
||||
int p_contrast,
|
||||
int p_hue,
|
||||
int p_colour,
|
||||
bool p_capture,
|
||||
bool p_record_audio
|
||||
) :
|
||||
Camera(
|
||||
p_id,
|
||||
FFMPEG_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 ),
|
||||
mMethod( p_method ),
|
||||
mOptions( p_options )
|
||||
|
@ -113,6 +140,7 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri
|
|||
videoStore = NULL;
|
||||
video_last_pts = 0;
|
||||
have_video_keyframe = false;
|
||||
packetqueue = NULL;
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
mConvertContext = NULL;
|
||||
|
@ -151,7 +179,7 @@ void FfmpegCamera::Terminate() {
|
|||
|
||||
int FfmpegCamera::PrimeCapture() {
|
||||
if ( mCanCapture ) {
|
||||
Info( "Priming capture from %s, CLosing", mPath.c_str() );
|
||||
Info("Priming capture from %s, Closing", mPath.c_str());
|
||||
Close();
|
||||
}
|
||||
mVideoStreamId = -1;
|
||||
|
@ -166,7 +194,7 @@ int FfmpegCamera::PreCapture() {
|
|||
if ( ! mCanCapture )
|
||||
return OpenFfmpeg();
|
||||
// Nothing to do here
|
||||
return( 0 );
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FfmpegCamera::Capture( Image &image ) {
|
||||
|
@ -433,6 +461,7 @@ int FfmpegCamera::OpenFfmpeg() {
|
|||
|
||||
Debug(3, "Found video stream at index %d", mVideoStreamId);
|
||||
Debug(3, "Found audio stream at index %d", mAudioStreamId);
|
||||
packetqueue = new zm_packetqueue( mVideoStreamId > mAudioStreamId ? mVideoStreamId : mAudioStreamId );
|
||||
|
||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||
mVideoCodecContext = avcodec_alloc_context3(NULL);
|
||||
|
@ -678,6 +707,10 @@ int FfmpegCamera::Close() {
|
|||
delete videoStore;
|
||||
videoStore = NULL;
|
||||
}
|
||||
if ( packetqueue ) {
|
||||
delete packetqueue;
|
||||
packetqueue = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} // end FfmpegCamera::Close
|
||||
|
@ -795,14 +828,14 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
|
|||
ZMPacket *queued_packet;
|
||||
|
||||
// Clear all packets that predate the moment when the recording began
|
||||
packetqueue.clear_unwanted_packets( &recording, mVideoStreamId );
|
||||
packetqueue->clear_unwanted_packets( &recording, mVideoStreamId );
|
||||
|
||||
while ( ( queued_packet = packetqueue.popPacket() ) ) {
|
||||
while ( ( queued_packet = packetqueue->popPacket() ) ) {
|
||||
AVPacket *avp = queued_packet->av_packet();
|
||||
|
||||
packet_count += 1;
|
||||
//Write the packet to our video store
|
||||
Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue.size() );
|
||||
Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue->size() );
|
||||
if ( avp->stream_index == mVideoStreamId ) {
|
||||
ret = videoStore->writeVideoFramePacket( avp );
|
||||
have_video_keyframe = true;
|
||||
|
@ -836,18 +869,18 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
|
|||
if ( packet.stream_index == mVideoStreamId ) {
|
||||
if ( keyframe ) {
|
||||
Debug(3, "Clearing queue");
|
||||
packetqueue.clearQueue(monitor->GetPreEventCount(), mVideoStreamId);
|
||||
packetqueue.queuePacket(&packet);
|
||||
} else if ( packetqueue.size() ) {
|
||||
packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId);
|
||||
packetqueue->queuePacket(&packet);
|
||||
} else if ( packetqueue->size() ) {
|
||||
// it's a keyframe or we already have something in the queue
|
||||
packetqueue.queuePacket(&packet);
|
||||
packetqueue->queuePacket(&packet);
|
||||
}
|
||||
} else if ( packet.stream_index == mAudioStreamId ) {
|
||||
// The following lines should ensure that the queue always begins with a video keyframe
|
||||
//Debug(2, "Have audio packet, reocrd_audio is (%d) and packetqueue.size is (%d)", record_audio, packetqueue.size() );
|
||||
if ( record_audio && packetqueue.size() ) {
|
||||
if ( record_audio && packetqueue->size() ) {
|
||||
// if it's audio, and we are doing audio, and there is already something in the queue
|
||||
packetqueue.queuePacket(&packet);
|
||||
packetqueue->queuePacket(&packet);
|
||||
}
|
||||
}
|
||||
} // end if recording or not
|
||||
|
|
|
@ -79,7 +79,7 @@ class FfmpegCamera : public Camera {
|
|||
#endif // HAVE_LIBAVFORMAT
|
||||
|
||||
VideoStore *videoStore;
|
||||
zm_packetqueue packetqueue;
|
||||
zm_packetqueue *packetqueue;
|
||||
bool have_video_keyframe;
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
|
|
|
@ -531,6 +531,12 @@ void MonitorStream::runStream() {
|
|||
Debug(2, "Have checking command Queue for connkey: %d", connkey );
|
||||
got_command = true;
|
||||
}
|
||||
// Update modified time of the socket .lock file so that we can tell which ones are stale.
|
||||
if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
|
||||
touch(sock_path_lock);
|
||||
last_comm_update = now;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( paused ) {
|
||||
|
|
|
@ -21,27 +21,31 @@
|
|||
#include "zm_ffmpeg.h"
|
||||
#include <sys/time.h>
|
||||
|
||||
#define VIDEO_QUEUESIZE 200
|
||||
#define AUDIO_QUEUESIZE 50
|
||||
|
||||
zm_packetqueue::zm_packetqueue(){
|
||||
|
||||
zm_packetqueue::zm_packetqueue( int p_max_stream_id ) {
|
||||
max_stream_id = p_max_stream_id;
|
||||
packet_counts = new int[max_stream_id+1];
|
||||
for ( int i=0; i <= max_stream_id; ++i )
|
||||
packet_counts = 0;
|
||||
}
|
||||
|
||||
zm_packetqueue::~zm_packetqueue() {
|
||||
clearQueue();
|
||||
delete[] packet_counts;
|
||||
packet_counts = NULL;
|
||||
}
|
||||
|
||||
bool zm_packetqueue::queuePacket(ZMPacket* zm_packet) {
|
||||
pktQueue.push_back(zm_packet);
|
||||
|
||||
packet_counts[zm_packet->packet.stream_index] += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool zm_packetqueue::queuePacket(AVPacket* av_packet) {
|
||||
|
||||
ZMPacket *zm_packet = new ZMPacket(av_packet);
|
||||
|
||||
pktQueue.push_back(zm_packet);
|
||||
packet_counts[zm_packet->packet.stream_index] += 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -53,6 +57,7 @@ ZMPacket* zm_packetqueue::popPacket( ) {
|
|||
|
||||
ZMPacket *packet = pktQueue.front();
|
||||
pktQueue.pop_front();
|
||||
packet_counts[packet->packet.stream_index] -= 1;
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
@ -74,7 +79,8 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream
|
|||
ZMPacket *zm_packet = *it;
|
||||
AVPacket *av_packet = &(zm_packet->packet);
|
||||
|
||||
Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
|
||||
Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)",
|
||||
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
|
||||
|
||||
// Want frames_to_keep video keyframes. Otherwise, we may not have enough
|
||||
if ( ( av_packet->stream_index == stream_id) ) {
|
||||
|
@ -88,11 +94,13 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream
|
|||
ZMPacket *zm_packet = *it;
|
||||
AVPacket *av_packet = &(zm_packet->packet);
|
||||
|
||||
Debug(5, "Looking for keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
|
||||
Debug(5, "Looking for keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)",
|
||||
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
|
||||
|
||||
// Want frames_to_keep video keyframes. Otherwise, we may not have enough
|
||||
if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) {
|
||||
Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
|
||||
Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)",
|
||||
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -109,11 +117,13 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream
|
|||
|
||||
packet = pktQueue.front();
|
||||
pktQueue.pop_front();
|
||||
packet_counts[packet->packet.stream_index] -= 1;
|
||||
delete packet;
|
||||
|
||||
delete_count += 1;
|
||||
}
|
||||
Debug(3, "Deleted (%d) packets", delete_count );
|
||||
packet = NULL; // tidy up for valgrind
|
||||
Debug(3, "Deleted %d packets, %d remaining", delete_count, pktQueue.size());
|
||||
return delete_count;
|
||||
} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id )
|
||||
|
||||
|
@ -121,6 +131,7 @@ void zm_packetqueue::clearQueue() {
|
|||
ZMPacket *packet = NULL;
|
||||
while (!pktQueue.empty()) {
|
||||
packet = pktQueue.front();
|
||||
packet_counts[packet->packet.stream_index] -= 1;
|
||||
pktQueue.pop_front();
|
||||
delete packet;
|
||||
}
|
||||
|
@ -130,12 +141,14 @@ unsigned int zm_packetqueue::size() {
|
|||
return pktQueue.size();
|
||||
}
|
||||
|
||||
int zm_packetqueue::packet_count( int stream_id ) {
|
||||
return packet_counts[stream_id];
|
||||
} // end int zm_packetqueue::packet_count( int stream_id )
|
||||
|
||||
void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) {
|
||||
// Need to find the keyframe <= recording_started. Can get rid of audio packets.
|
||||
if ( pktQueue.empty() ) {
|
||||
if ( pktQueue.empty() )
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1 - find keyframe < recording_started.
|
||||
// Step 2 - pop packets until we get to the packet in step 2
|
||||
|
@ -175,9 +188,11 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi
|
|||
//while ( pktQueue.rend() != it ) {
|
||||
packet = pktQueue.front();
|
||||
pktQueue.pop_front();
|
||||
packet_counts[packet->packet.stream_index] -= 1;
|
||||
delete packet;
|
||||
deleted_frames += 1;
|
||||
}
|
||||
packet = NULL; // tidy up for valgrind
|
||||
|
||||
zm_packet = pktQueue.front();
|
||||
av_packet = &(zm_packet->packet);
|
||||
|
@ -186,4 +201,4 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi
|
|||
} else {
|
||||
Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() );
|
||||
}
|
||||
}
|
||||
} // end void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId )
|
||||
|
|
|
@ -29,10 +29,9 @@
|
|||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
class zm_packetqueue {
|
||||
public:
|
||||
zm_packetqueue();
|
||||
zm_packetqueue(int max_stream_id);
|
||||
virtual ~zm_packetqueue();
|
||||
bool queuePacket(AVPacket* packet, struct timeval *timestamp);
|
||||
bool queuePacket(ZMPacket* packet);
|
||||
|
@ -44,8 +43,11 @@ public:
|
|||
void clearQueue();
|
||||
unsigned int size();
|
||||
void clear_unwanted_packets(timeval *recording, int mVideoStreamId);
|
||||
int packet_count(int stream_id);
|
||||
private:
|
||||
std::list<ZMPacket *> pktQueue;
|
||||
int max_stream_id;
|
||||
int *packet_counts; /* packet count for each stream_id, to keep track of how many video vs audio packets are in the queue */
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -332,6 +332,8 @@ void StreamBase::openComms() {
|
|||
snprintf(rem_sock_path, sizeof(rem_sock_path), "%s/zms-%06dw.sock", staticConfig.PATH_SOCKS.c_str(), connkey);
|
||||
strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path)-1);
|
||||
rem_addr.sun_family = AF_UNIX;
|
||||
|
||||
gettimeofday(&last_comm_update, NULL);
|
||||
} // end if connKey > 0
|
||||
Debug(2, "comms open");
|
||||
} // end void StreamBase::openComms()
|
||||
|
|
|
@ -85,6 +85,7 @@ protected:
|
|||
int step;
|
||||
|
||||
struct timeval now;
|
||||
struct timeval last_comm_update;
|
||||
|
||||
double base_fps;
|
||||
double effective_fps;
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <fcntl.h> /* Definition of AT_* constants */
|
||||
#include <sys/stat.h>
|
||||
#if defined(__arm__)
|
||||
#include <sys/auxv.h>
|
||||
#endif
|
||||
|
@ -414,3 +416,22 @@ Warning("ZM Compiled without LIBCURL. UriDecoding not implemented.");
|
|||
#endif
|
||||
}
|
||||
|
||||
void touch(const char *pathname) {
|
||||
int fd = open(pathname,
|
||||
O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK,
|
||||
0666);
|
||||
if ( fd < 0 ) {
|
||||
// Couldn't open that path.
|
||||
Error("Couldn't open() path \"%s in touch", pathname);
|
||||
return;
|
||||
}
|
||||
int rc = utimensat(AT_FDCWD,
|
||||
pathname,
|
||||
nullptr,
|
||||
0);
|
||||
if ( rc ) {
|
||||
Error("Couldn't utimensat() path %s in touch", pathname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,5 +63,5 @@ extern unsigned int neonversion;
|
|||
|
||||
char *timeval_to_string( struct timeval tv );
|
||||
std::string UriDecode( const std::string &encoded );
|
||||
|
||||
void touch( const char *pathname );
|
||||
#endif // ZM_UTILS_H
|
||||
|
|
|
@ -55,7 +55,7 @@ class Frame {
|
|||
#return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show.'&filename='.$this->Event()->MonitorId().'_'.$this->{'EventId'}.'_'.$this->{'FrameId'}.'.jpg';
|
||||
} // end function getImageSrc
|
||||
|
||||
public static function find( $parameters = array(), $limit = NULL ) {
|
||||
public static function find( $parameters = array(), $options = NULL ) {
|
||||
$sql = 'SELECT * FROM Frames';
|
||||
$values = array();
|
||||
if ( sizeof($parameters) ) {
|
||||
|
@ -65,17 +65,23 @@ class Frame {
|
|||
) );
|
||||
$values = array_values( $parameters );
|
||||
}
|
||||
if ( $limit ) {
|
||||
if ( is_integer( $limit ) or ctype_digit( $limit ) ) {
|
||||
$sql .= ' LIMIT ' . $limit;
|
||||
if ( $options ) {
|
||||
if ( isset($options['order']) ) {
|
||||
$sql .= ' ORDER BY ' . $options['order'];
|
||||
}
|
||||
if ( isset($options['limit']) ) {
|
||||
if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) {
|
||||
$sql .= ' LIMIT ' . $options['limit'];
|
||||
} else {
|
||||
$backTrace = debug_backtrace();
|
||||
$file = $backTrace[1]['file'];
|
||||
$line = $backTrace[1]['line'];
|
||||
Error("Invalid value for limit($limit) passed to Frame::find from $file:$line");
|
||||
Error("Invalid value for limit(".$options['limit'].") passed to Frame::find from $file:$line");
|
||||
return array();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$results = dbFetchAll($sql, NULL, $values);
|
||||
if ( $results ) {
|
||||
return array_map( function($id){ return new Frame($id); }, $results );
|
||||
|
@ -83,8 +89,9 @@ class Frame {
|
|||
return array();
|
||||
}
|
||||
|
||||
public static function find_one( $parameters = array() ) {
|
||||
$results = Frame::find( $parameters, 1 );
|
||||
public static function find_one( $parameters = array(), $options = null ) {
|
||||
$options['limit'] = 1;
|
||||
$results = Frame::find($parameters, $options);
|
||||
if ( ! sizeof($results) ) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -152,6 +152,7 @@ private $control_fields = array(
|
|||
}
|
||||
if ( $this->{'Controllable'} ) {
|
||||
$s = dbFetchOne('SELECT * FROM Controls WHERE Id=?', NULL, array($this->{'ControlId'}) );
|
||||
if ( $s ) {
|
||||
foreach ($s as $k => $v) {
|
||||
if ( $k == 'Id' ) {
|
||||
continue;
|
||||
|
@ -166,6 +167,9 @@ private $control_fields = array(
|
|||
$this->{$k} = $v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Warning('No Controls found for monitor '.$this->{'Id'} . ' ' . $this->{'Name'}.' althrough it is marked as controllable');
|
||||
}
|
||||
}
|
||||
global $monitor_cache;
|
||||
$monitor_cache[$row['Id']] = $this;
|
||||
|
|
|
@ -195,11 +195,12 @@ if ( currentView != 'none' && currentView != 'login' ) {
|
|||
$j.getJSON(thisUrl + '?view=request&request=status&entity=navBar')
|
||||
.done(setNavBar)
|
||||
.fail(function( jqxhr, textStatus, error ) {
|
||||
var err = textStatus + ", " + error;
|
||||
console.log( "Request Failed: " + err );
|
||||
console.log( "Request Failed: " + textStatus + ", " + error);
|
||||
if ( textStatus != "timeout" ) {
|
||||
// The idea is that this should only fail due to auth, so reload the page
|
||||
// which should go to login if it can't stay logged in.
|
||||
window.location.reload( true );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -253,7 +253,7 @@ for ( $i=0; $i < count($terms); $i++ ) {
|
|||
<td><?php echo htmlSelect( "filter[Query][terms][$i][op]", $opTypes, $term['op'] ); ?></td>
|
||||
<td>
|
||||
<input type="text" name="filter[Query][terms][<?php echo $i ?>][val]" id="filter[Query][terms][<?php echo $i ?>][val]" value="<?php echo isset($term['val'])?validHtmlStr(str_replace('T', ' ', $term['val'])):'' ?>"/>
|
||||
<script type="text/javascript">$j("[name$='\\[<?php echo $i ?>\\]\\[val\\]']").timepicker({timeFormat: "HH:mm:ss", constrainInput: falsepi}); </script>
|
||||
<script type="text/javascript">$j("[name$='\\[<?php echo $i ?>\\]\\[val\\]']").timepicker({timeFormat: "HH:mm:ss", constrainInput: false}); </script>
|
||||
</td>
|
||||
<?php
|
||||
} elseif ( $term['attr'] == 'StateId' ) {
|
||||
|
|
|
@ -76,6 +76,23 @@ if ( empty($_REQUEST['path']) ) {
|
|||
return;
|
||||
}
|
||||
|
||||
# if alarm, get the fid of the first alarmed frame if available and let the
|
||||
# fid= code continue processing it. Sort it to get the first alarmed frame
|
||||
if ( $_REQUEST['fid'] == 'alarm' ) {
|
||||
$Frame = Frame::find_one(array('EventId'=>$_REQUEST['eid'], 'Type'=>'Alarm'),
|
||||
array('order'=>'FrameId ASC'));
|
||||
if ( !$Frame ) # no alarms
|
||||
$Frame = Frame::find_one(array('EventId'=>$_REQUEST['eid'])); # first frame
|
||||
if ( !$Frame ) {
|
||||
Warning("No frame found for event " + $_REQUEST['eid']);
|
||||
$Frame = new Frame();
|
||||
$Frame->Delta(1);
|
||||
$Frame->FrameId('snapshot');
|
||||
}
|
||||
$_REQUEST['fid']=$Frame->FrameId();
|
||||
}
|
||||
|
||||
|
||||
if ( $_REQUEST['fid'] == 'snapshot' ) {
|
||||
$Frame = Frame::find_one(array('EventId'=>$_REQUEST['eid'], 'Score'=>$Event->MaxScore()));
|
||||
if ( !$Frame )
|
||||
|
|
Loading…
Reference in New Issue