This commit is contained in:
Isaac Connor 2017-10-27 20:36:49 -07:00
commit ef0379dd18
29 changed files with 1123 additions and 430 deletions

View File

@ -232,6 +232,7 @@ CREATE TABLE `Filters` (
`AutoExecute` tinyint(3) unsigned NOT NULL default '0',
`AutoExecuteCmd` tinytext,
`AutoDelete` tinyint(3) unsigned NOT NULL default '0',
`UpdateDiskSpace` tinyint(3) unsigned NOT NULL default '0',
`Background` tinyint(1) unsigned NOT NULL default '0',
`Concurrent` tinyint(1) unsigned NOT NULL default '0',
PRIMARY KEY (`Id`),
@ -289,6 +290,7 @@ CREATE TABLE `Logs` (
) ENGINE=@ZM_MYSQL_ENGINE@;
CREATE INDEX `Logs_TimeKey_idx` ON `Logs` (`TimeKey`);
CREATE INDEX `Logs_Level_idx` ON `Logs` (`Level`);
--
-- Table structure for table `Manufacturers`
--
@ -382,6 +384,8 @@ CREATE TABLE `Monitors` (
`Deinterlacing` int(10) unsigned NOT NULL default '0',
`SaveJPEGs` TINYINT NOT NULL DEFAULT '3' ,
`VideoWriter` TINYINT NOT NULL DEFAULT '0',
`OutputCodec` enum('h264','mjpeg'),
`OutputContainer` enum('mp4','mkv'),
`EncoderParameters` TEXT,
`RecordAudio` TINYINT NOT NULL DEFAULT '0',
`RTSPDescribe` tinyint(1) unsigned,

View File

@ -125,7 +125,7 @@ sub Execute {
push @results, $event;
}
$sth->finish();
Debug("Loaded " . @results . " events for filter $_[0]{Name} using query ($sql)");
Debug('Loaded ' . @results . " events for filter $_[0]{Name} using query ($sql)");
return @results;
}
@ -147,38 +147,59 @@ sub Sql {
foreach my $term ( @{$filter_expr->{terms}} ) {
if ( exists($term->{cnj}) ) {
$self->{Sql} .= " ".$term->{cnj}." ";
$self->{Sql} .= ' '.$term->{cnj}." ";
}
if ( exists($term->{obr}) ) {
$self->{Sql} .= " ".str_repeat( "(", $term->{obr} )." ";
$self->{Sql} .= ' '.str_repeat( "(", $term->{obr} )." ";
}
my $value = $term->{val};
my @value_list;
if ( $term->{attr} ) {
if ( $term->{attr} =~ /^Monitor/ ) {
my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/;
$self->{Sql} .= "M.".$temp_attr_name;
$self->{Sql} .= 'M.'.$temp_attr_name;
} elsif ( $term->{attr} =~ /^Server/ ) {
$self->{Sql} .= "M.".$term->{attr};
$self->{Sql} .= 'M.'.$term->{attr};
# StartTime options
} elsif ( $term->{attr} eq 'DateTime' ) {
$self->{Sql} .= "E.StartTime";
$self->{Sql} .= 'E.StartTime';
} elsif ( $term->{attr} eq 'StartDateTime' ) {
$self->{Sql} .= 'E.StartTime';
} elsif ( $term->{attr} eq 'Date' ) {
$self->{Sql} .= "to_days( E.StartTime )";
$self->{Sql} .= 'to_days( E.StartTime )';
} elsif ( $term->{attr} eq 'StartDate' ) {
$self->{Sql} .= 'to_days( E.StartTime )';
} elsif ( $term->{attr} eq 'Time' ) {
$self->{Sql} .= "extract( hour_second from E.StartTime )";
} elsif ( $term->{attr} eq 'Weekday' ) {
$self->{Sql} .= "weekday( E.StartTime )";
# EndTIme options
} elsif ( $term->{attr} eq 'EndDateTime' ) {
$self->{Sql} .= 'E.EndTime';
} elsif ( $term->{attr} eq 'EndDate' ) {
$self->{Sql} .= 'to_days( E.EndTime )';
} elsif ( $term->{attr} eq 'EndTime' ) {
$self->{Sql} .= "extract( hour_second from E.EndTime )";
} elsif ( $term->{attr} eq 'EndWeekday' ) {
$self->{Sql} .= "weekday( E.EndTime )";
#
} elsif ( $term->{attr} eq 'DiskSpace' ) {
$self->{Sql} .= 'E.DiskSpace';
$self->{HasDiskPercent} = !undef;
} elsif ( $term->{attr} eq 'DiskPercent' ) {
$self->{Sql} .= "zmDiskPercent";
$self->{Sql} .= 'zmDiskPercent';
$self->{HasDiskPercent} = !undef;
} elsif ( $term->{attr} eq 'DiskBlocks' ) {
$self->{Sql} .= "zmDiskBlocks";
$self->{Sql} .= 'zmDiskBlocks';
$self->{HasDiskBlocks} = !undef;
} elsif ( $term->{attr} eq 'SystemLoad' ) {
$self->{Sql} .= "zmSystemLoad";
$self->{Sql} .= 'zmSystemLoad';
$self->{HasSystemLoad} = !undef;
} else {
$self->{Sql} .= "E.".$term->{attr};
$self->{Sql} .= 'E.'.$term->{attr};
}
( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/;
@ -243,11 +264,11 @@ sub Sql {
} elsif ( $term->{op} eq '!~' ) {
$self->{Sql} .= " not in (".join( ",", @value_list ).")";
} else {
$self->{Sql} .= " ".$term->{op}." $value";
$self->{Sql} .= ' '.$term->{op}." $value";
}
} # end if has an operator
if ( exists($term->{cbr}) ) {
$self->{Sql} .= " ".str_repeat( ")", $term->{cbr} )." ";
$self->{Sql} .= ' '.str_repeat( ")", $term->{cbr} )." ";
}
} # end foreach term
} # end if terms
@ -284,7 +305,7 @@ sub Sql {
push @auto_terms, "E.Executed = 0";
}
if ( @auto_terms ) {
$sql .= " and ( ".join( " or ", @auto_terms )." )";
$sql .= " and ( ".join( ' or ', @auto_terms )." )";
}
if ( !$filter_expr->{sort_field} ) {
$filter_expr->{sort_field} = 'StartTime';
@ -292,30 +313,34 @@ sub Sql {
}
my $sort_column = '';
if ( $filter_expr->{sort_field} eq 'Id' ) {
$sort_column = "E.Id";
$sort_column = 'E.Id';
} elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) {
$sort_column = "M.Name";
$sort_column = 'M.Name';
} elsif ( $filter_expr->{sort_field} eq 'Name' ) {
$sort_column = "E.Name";
$sort_column = 'E.Name';
} elsif ( $filter_expr->{sort_field} eq 'StartTime' ) {
$sort_column = "E.StartTime";
$sort_column = 'E.StartTime';
} elsif ( $filter_expr->{sort_field} eq 'EndTime' ) {
$sort_column = 'E.EndTime';
} elsif ( $filter_expr->{sort_field} eq 'Secs' ) {
$sort_column = "E.Length";
$sort_column = 'E.Length';
} elsif ( $filter_expr->{sort_field} eq 'Frames' ) {
$sort_column = "E.Frames";
$sort_column = 'E.Frames';
} elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' ) {
$sort_column = "E.AlarmFrames";
$sort_column = 'E.AlarmFrames';
} elsif ( $filter_expr->{sort_field} eq 'TotScore' ) {
$sort_column = "E.TotScore";
$sort_column = 'E.TotScore';
} elsif ( $filter_expr->{sort_field} eq 'AvgScore' ) {
$sort_column = "E.AvgScore";
$sort_column = 'E.AvgScore';
} elsif ( $filter_expr->{sort_field} eq 'MaxScore' ) {
$sort_column = "E.MaxScore";
$sort_column = 'E.MaxScore';
} elsif ( $filter_expr->{sort_field} eq 'DiskSpace' ) {
$sort_column = 'E.DiskSpace';
} else {
$sort_column = "E.StartTime";
$sort_column = 'E.StartTime';
}
my $sort_order = $filter_expr->{sort_asc}?"asc":"desc";
$sql .= " order by ".$sort_column." ".$sort_order;
my $sort_order = $filter_expr->{sort_asc}?'asc':'desc';
$sql .= ' order by '.$sort_column." ".$sort_order;
if ( $filter_expr->{limit} ) {
$sql .= " limit 0,".$filter_expr->{limit};
}
@ -325,7 +350,7 @@ sub Sql {
} # end sub Sql
sub getDiskPercent {
my $command = "df " . ($_[0] ? $_[0] : '.');
my $command = 'df ' . ($_[0] ? $_[0] : '.');
my $df = qx( $command );
my $space = -1;
if ( $df =~ /\s(\d+)%/ms ) {
@ -335,7 +360,7 @@ sub getDiskPercent {
}
sub getDiskBlocks {
my $command = "df .";
my $command = 'df .';
my $df = qx( $command );
my $space = -1;
if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) {
@ -345,7 +370,7 @@ sub getDiskBlocks {
}
sub getLoad {
my $command = "uptime .";
my $command = 'uptime .';
my $uptime = qx( $command );
my $load = -1;
if ( $uptime =~ /load average:\s+([\d.]+)/ms ) {

View File

@ -678,6 +678,8 @@ sub Dump {
fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) );
}
sub debug { fetch()->logPrint( DEBUG, @_ ); }
sub Debug( @ ) {
fetch()->logPrint( DEBUG, @_ );
}
@ -685,14 +687,24 @@ sub Debug( @ ) {
sub Info( @ ) {
fetch()->logPrint( INFO, @_ );
}
sub info {
fetch()->logPrint( INFO, @_ );
}
sub Warning( @ ) {
fetch()->logPrint( WARNING, @_ );
}
sub warn {
fetch()->logPrint( WARNING, @_ );
}
sub Error( @ ) {
fetch()->logPrint( ERROR, @_ );
}
sub error {
fetch()->logPrint( ERROR, @_ );
}
sub Fatal( @ ) {
fetch()->logPrint( FATAL, @_ );

View File

@ -42,7 +42,13 @@ use ZoneMinder::Config qw(:all);
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Database qw(:all);
use vars qw/ $AUTOLOAD /;
use vars qw/ $AUTOLOAD $log $dbh/;
*log = \$ZoneMinder::Logger::logger;
*dbh = \$ZoneMinder::Database::dbh;
my $debug = 1;
use constant DEBUG_ALL=>0;
sub new {
my ( $parent, $id, $data ) = @_;
@ -110,7 +116,269 @@ sub AUTOLOAD {
return $_[0]{$name};
}
sub save {
my ( $self, $data, $force_insert ) = @_;
my $type = ref $self;
if ( ! $type ) {
my ( $caller, undef, $line ) = caller;
$log->error("No type in Object::save. self:$self from $caller:$line");
}
my $local_dbh = eval '$'.$type.'::dbh';
$local_dbh = $ZoneMinder::Database::dbh if ! $local_dbh;
$self->set( $data ? $data : {} );
if ( $debug or DEBUG_ALL ) {
if ( $data ) {
foreach my $k ( keys %$data ) {
$log->debug("Object::save after set $k => $$data{$k} $$self{$k}");
}
} else {
$log->debug("No data after set");
}
}
#$debug = 0;
my $table = eval '$'.$type.'::table';
my $fields = eval '\%'.$type.'::fields';
my $debug = eval '$'.$type.'::debug';
#$debug = DEBUG_ALL if ! $debug;
my %sql;
foreach my $k ( keys %$fields ) {
$sql{$$fields{$k}} = $$self{$k} if defined $$fields{$k};
} # end foreach
if ( ! $force_insert ) {
$sql{$$fields{updated_on}} = 'NOW()' if exists $$fields{updated_on};
} # end if
my $serial = eval '$'.$type.'::serial';
my @identified_by = eval '@'.$type.'::identified_by';
my $ac = sql::start_transaction( $local_dbh );
if ( ! $serial ) {
my $insert = $force_insert;
my %serial = eval '%'.$type.'::serial';
if ( ! %serial ) {
$log->debug("No serial") if $debug;
# No serial columns defined, which means that we will do saving by delete/insert instead of insert/update
if ( @identified_by ) {
my $where = join(' AND ', map { $$fields{$_}.'=?' } @identified_by );
if ( $debug ) {
$log->debug("DELETE FROM $table WHERE $where");
} # end if
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();
sql::end_transaction( $local_dbh, $ac );
return $local_dbh->errstr;
} elsif ( $debug ) {
$log->debug("SQL succesful DELETE FROM $table WHERE $where");
} # end if
} # end if
$insert = 1;
} else {
foreach my $id ( @identified_by ) {
if ( ! $serial{$id} ) {
my ( $caller, undef, $line ) = caller;
$log->error("$id nor in serial for $type from $caller:$line") if $debug;
next;
}
if ( ! $$self{$id} ) {
($$self{$id}) = ($sql{$$fields{$id}}) = $local_dbh->selectrow_array( q{SELECT nextval('} . $serial{$id} . q{')} );
$log->debug("SQL statement execution SELECT nextval('$serial{$id}') returned $$self{$id}") if $debug or DEBUG_ALL;
$insert = 1;
} # end if
} # end foreach
} # end if ! %serial
if ( $insert ) {
my @keys = keys %sql;
my $command = "INSERT INTO $table (" . join(',', @keys ) . ') VALUES (' . join(',', map { '?' } @sql{@keys} ) . ')';
if ( ! ( ( $_ = $local_dbh->prepare($command) ) and $_->execute( @sql{@keys} ) ) ) {
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();
sql::end_transaction( $local_dbh, $ac );
return $error;
} # end if
if ( $debug or DEBUG_ALL ) {
$command =~ s/\?/\%s/g;
$log->debug('SQL statement execution: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys} ) ).'):' );
} # end if
} else {
my @keys = keys %sql;
my $command = "UPDATE $table SET " . join(',', map { $_ . ' = ?' } @keys ) . ' WHERE ' . join(' AND ', map { $_ . ' = ?' } @$fields{@identified_by} );
if ( ! ( $_ = $local_dbh->prepare($command) and $_->execute( @sql{@keys,@$fields{@identified_by}} ) ) ) {
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();
sql::end_transaction( $local_dbh, $ac );
return $error;
} # end if
if ( $debug or DEBUG_ALL ) {
$command =~ s/\?/\%s/g;
$log->debug('SQL DEBUG: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys,@$fields{@identified_by}} ) ).'):' );
} # end if
} # end if
} else { # not identified_by
@identified_by = ('id') if ! @identified_by;
my $need_serial = ! ( @identified_by == map { $$self{$_} ? $_ : () } @identified_by );
if ( $force_insert or $need_serial ) {
if ( $need_serial ) {
if ( $serial ) {
@$self{@identified_by} = @sql{@$fields{@identified_by}} = $local_dbh->selectrow_array( q{SELECT nextval('} . $serial . q{')} );
if ( $local_dbh->errstr() ) {
$log->error("Error getting next id. " . $local_dbh->errstr() );
$log->error("SQL statement execution SELECT nextval('$serial') returned ".join(',',@$self{@identified_by}));
} elsif ( $debug or DEBUG_ALL ) {
$log->debug("SQL statement execution SELECT nextval('$serial') returned ".join(',',@$self{@identified_by}));
} # end if
} # end if
} # end if
my @keys = keys %sql;
my $command = "INSERT INTO $table (" . join(',', @keys ) . ') VALUES (' . join(',', map { '?' } @sql{@keys} ) . ')';
if ( ! ( $_ = $local_dbh->prepare($command) and $_->execute( @sql{@keys} ) ) ) {
$command =~ s/\?/\%s/g;
my $error = $local_dbh->errstr;
$log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $error);
$local_dbh->rollback();
sql::end_transaction( $local_dbh, $ac );
return $error;
} # end if
if ( $debug or DEBUG_ALL ) {
$command =~ s/\?/\%s/g;
$log->debug('SQL DEBUG: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys} ) ).'):' );
} # end if
} else {
delete $sql{created_on};
my @keys = keys %sql;
@keys = sets::exclude( [ @$fields{@identified_by} ], \@keys );
my $command = "UPDATE $table SET " . join(',', map { $_ . ' = ?' } @keys ) . ' WHERE ' . join(' AND ', map { $$fields{$_} .'= ?' } @identified_by );
if ( ! ( $_ = $local_dbh->prepare($command) and $_->execute( @sql{@keys}, @sql{@$fields{@identified_by}} ) ) ) {
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();
sql::end_transaction( $local_dbh, $ac );
return $error;
} # end if
if ( $debug or DEBUG_ALL ) {
$command =~ s/\?/\%s/g;
$log->debug('SQL DEBUG: ('.sprintf($command, map { defined $_ ? ( ref $_ eq 'ARRAY' ? join(',',@{$_}) : $_ ) : 'undef' } ( @sql{@keys}, @$self{@identified_by} ) ).'):' );
} # end if
} # end if
} # end if
sql::end_transaction( $local_dbh, $ac );
$self->load();
#if ( $$fields{id} ) {
#if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) {
#$ZoneMinder::Object::cache{$type}{$$self{id}} = $self;
#} # end if
#delete $ZoneMinder::Object::cache{$config{db_name}}{$type}{$$self{id}};
#} # end if
#$log->debug("after delete");
#eval 'if ( %'.$type.'::find_cache ) { %'.$type.'::find_cache = (); }';
#$log->debug("after clear cache");
return '';
} # end sub save
sub set {
my ( $self, $params ) = @_;
my @set_fields = ();
my $type = ref $self;
my %fields = eval ('%'.$type.'::fields');
if ( ! %fields ) {
$log->warn('ZoneMinder::Object::set called on an object with no fields');
} # end if
my %defaults = eval('%'.$type.'::defaults');
if ( ref $params ne 'HASH' ) {
my ( $caller, undef, $line ) = caller;
$openprint::log->error("$type -> set called with non-hash params from $caller $line");
}
foreach my $field ( keys %fields ) {
$log->debug("field: $field, param: ".$$params{$field}) if $debug;
if ( exists $$params{$field} ) {
$openprint::log->debug("field: $field, $$self{$field} =? param: ".$$params{$field}) if $debug;
if ( ( ! defined $$self{$field} ) or ($$self{$field} ne $params->{$field}) ) {
# Only make changes to fields that have changed
if ( defined $fields{$field} ) {
$$self{$field} = $$params{$field} if defined $fields{$field};
push @set_fields, $fields{$field}, $$params{$field}; #mark for sql updating
} # end if
$openprint::log->debug("Running $field with $$params{$field}") if $debug;
if ( my $func = $self->can( $field ) ) {
$func->( $self, $$params{$field} );
} # end if
} # end if
} # end if
if ( defined $fields{$field} ) {
if ( $$self{$field} ) {
$$self{$field} = transform( $type, $field, $$self{$field} );
} # end if $$self{field}
}
} # end foreach field
foreach my $field ( keys %defaults ) {
if ( ( ! exists $$self{$field} ) or (!defined $$self{$field}) or ( $$self{$field} eq '' ) ) {
$log->debug("Setting default ($field) ($$self{$field}) ($defaults{$field}) ") if $debug;
if ( defined $defaults{$field} ) {
$log->debug("Default $field is defined: $defaults{$field}") if $debug;
if ( $defaults{$field} eq 'NOW()' ) {
$$self{$field} = 'NOW()';
} else {
$$self{$field} = eval($defaults{$field});
$log->error( "Eval error of object default $field default ($defaults{$field}) Reason: " . $@ ) if $@;
} # end if
} else {
$$self{$field} = $defaults{$field};
} # end if
#$$self{$field} = ( defined $defaults{$field} ) ? eval($defaults{$field}) : $defaults{$field};
$log->debug("Setting default for ($field) using ($defaults{$field}) to ($$self{$field}) ") if $debug;
} # end if
} # end foreach default
return @set_fields;
} # end sub set
sub transform {
my $type = ref $_[0];
$type = $_[0] if ! $type;
my $fields = eval '\%'.$type.'::fields';
my $value = $_[2];
if ( defined $$fields{$_[1]} ) {
my @transforms = eval('@{$'.$type.'::transforms{$_[1]}}');
$openprint::log->debug("Transforms for $_[1] before $_[2]: @transforms") if $debug;
if ( @transforms ) {
foreach my $transform ( @transforms ) {
if ( $transform =~ /^s\// or $transform =~ /^tr\// ) {
eval '$value =~ ' . $transform;
} elsif ( $transform =~ /^<(\d+)/ ) {
if ( $value > $1 ) {
$value = undef;
} # end if
} else {
$openprint::log->debug("evalling $value ".$transform . " Now value is $value" );
eval '$value '.$transform;
$openprint::log->error("Eval error $@") if $@;
}
$openprint::log->debug("After $transform: $value") if $debug;
} # end foreach
} # end if
} else {
$openprint::log->error("Object::transform ($_[1]) not in fields for $type");
} # end if
return $value;
} # end sub transform
1;
__END__

View File

@ -214,6 +214,7 @@ sub getFilters {
or AutoMessage = 1
or AutoExecute = 1
or AutoDelete = 1
or UpdateDiskSpace = 1
) ORDER BY Name';
my $sth = $dbh->prepare_cached( $sql )
or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() );

View File

@ -168,7 +168,6 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string
snprintf( video_name, sizeof(video_name), "%d-%s", id, "video.mp4" );
snprintf( video_file, sizeof(video_file), staticConfig.video_file_format, path, video_name );
Debug(1,"Writing video file to %s", video_file );
#if 0
/* X264 MP4 video writer */
if ( monitor->GetOptVideoWriter() == Monitor::X264ENCODE ) {
@ -200,8 +199,8 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string
} else {
/* No video object */
videowriter = NULL;
}
#endif
}
} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent )
@ -239,7 +238,7 @@ Event::~Event() {
Error( "Can't update event: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
}
} // ~Event
void Event::createNotes( std::string &notes ) {
notes.clear();

View File

@ -113,22 +113,6 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri
mOpenStart = 0;
mReopenThread = 0;
#if HAVE_LIBSWSCALE
mConvertContext = NULL;
#endif
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
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;
imagePixFormat = AV_PIX_FMT_GRAY8;
} else {
Panic("Unexpected colours: %d",colours);
}
} // end FFmpegCamera::FFmpegCamera
FfmpegCamera::~FfmpegCamera() {
@ -218,94 +202,6 @@ ZMPacket * FfmpegCamera::Capture( Image &image ) {
}
Debug( 5, "Got packet from stream %d dts (%d) pts(%d)", packet.stream_index, packet.pts, packet.dts );
#if 0
// What about audio stream? Maybe someday we could do sound detection...
if ( ( packet.stream_index == mVideoStreamId ) && ( keyframe || have_video_keyframe ) ) {
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_send_packet( mVideoCodecContext, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
#if HAVE_AVUTIL_HWCONTEXT_H
if ( hwaccel ) {
ret = avcodec_receive_frame( mVideoCodecContext, hwFrame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
ret = av_hwframe_transfer_data(mRawFrame, hwFrame, 0);
if (ret < 0) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to transfer frame at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
} else {
#endif
ret = avcodec_receive_frame( mVideoCodecContext, mRawFrame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
#if HAVE_AVUTIL_HWCONTEXT_H
}
#endif
frameComplete = 1;
# else
ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to decode frame at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
#endif
Debug( 4, "Decoded video packet at frame %d", frameCount );
if ( frameComplete ) {
Debug( 4, "Got frame %d", frameCount );
uint8_t* directbuffer;
/* Request a writeable buffer of the target image */
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
if ( directbuffer == NULL ) {
Error("Failed requesting writeable buffer for the captured image.");
return (-1);
}
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
av_image_fill_arrays(mFrame->data, mFrame->linesize,
directbuffer, imagePixFormat, width, height, 1);
#else
avpicture_fill( (AVPicture *)mFrame, directbuffer,
imagePixFormat, width, height);
#endif
#if HAVE_LIBSWSCALE
if ( sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0 )
Fatal("Unable to convert raw format %u to target format %u at frame %d", mVideoCodecContext->pix_fmt, imagePixFormat, frameCount);
#else // HAVE_LIBSWSCALE
Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras");
#endif // HAVE_LIBSWSCALE
frameCount++;
} // end if frameComplete
} else {
Debug( 4, "Different stream_index %d", packet.stream_index );
} // end if packet.stream_index == mVideoStreamId
#endif
zm_packet = new ZMPacket( &packet );
zm_av_packet_unref( &packet );
return zm_packet;
@ -517,6 +413,18 @@ int FfmpegCamera::OpenFfmpeg() {
}
}
if ( mVideoCodecContext->codec_id != AV_CODEC_ID_H264 ) {
#ifdef AV_CODEC_ID_H265
if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H265 ) {
Debug( 1, "Input stream appears to be h265. The stored event file may not be viewable in browser." );
} else {
#endif
Warning( "Input stream is not h264. The stored event file may not be viewable in browser." );
#ifdef AV_CODEC_ID_H265
}
#endif
}
if (mVideoCodecContext->hwaccel != NULL) {
Debug(1, "HWACCEL in use");
} else {
@ -551,51 +459,11 @@ int FfmpegCamera::OpenFfmpeg() {
// Allocate space for the native video frame
mRawFrame = zm_av_frame_alloc();
// Allocate space for the converted video frame
mFrame = zm_av_frame_alloc();
if ( mRawFrame == NULL || mFrame == NULL )
if ( mRawFrame == NULL )
Fatal( "Unable to allocate frame for %s", mPath.c_str() );
Debug ( 1, "Allocated frames" );
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
int pSize = av_image_get_buffer_size( imagePixFormat, width, height,1 );
#else
int pSize = avpicture_get_size( imagePixFormat, width, height );
#endif
if ( (unsigned int)pSize != imagesize ) {
Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize);
}
Debug ( 1, "Validated imagesize" );
#if HAVE_LIBSWSCALE
Debug ( 1, "Calling sws_isSupportedInput" );
if ( !sws_isSupportedInput(mVideoCodecContext->pix_fmt) ) {
Fatal("swscale does not support the codec format: %c%c%c%c", (mVideoCodecContext->pix_fmt)&0xff, ((mVideoCodecContext->pix_fmt >> 8)&0xff), ((mVideoCodecContext->pix_fmt >> 16)&0xff), ((mVideoCodecContext->pix_fmt >> 24)&0xff));
}
if ( !sws_isSupportedOutput(imagePixFormat) ) {
Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff));
}
mConvertContext = sws_getContext(mVideoCodecContext->width,
mVideoCodecContext->height,
mVideoCodecContext->pix_fmt,
width, height,
imagePixFormat, SWS_BICUBIC, NULL,
NULL, NULL);
if ( mConvertContext == NULL )
Fatal( "Unable to create conversion context for %s", mPath.c_str() );
#else // HAVE_LIBSWSCALE
Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
#endif // HAVE_LIBSWSCALE
if ( (unsigned int)mVideoCodecContext->width != width || (unsigned int)mVideoCodecContext->height != height ) {
Warning( "Monitor dimensions are %dx%d but camera is sending %dx%d", width, height, mVideoCodecContext->width, mVideoCodecContext->height );
}
mCanCapture = true;
@ -636,12 +504,6 @@ int FfmpegCamera::CloseFfmpeg() {
mRawFrame = NULL;
}
#if HAVE_LIBSWSCALE
if ( mConvertContext ) {
sws_freeContext( mConvertContext );
mConvertContext = NULL;
}
#endif
if ( mVideoCodecContext ) {
avcodec_close(mVideoCodecContext);
@ -664,7 +526,7 @@ int FfmpegCamera::CloseFfmpeg() {
}
return 0;
}
} // end int FfmpegCamera::CloseFfmpeg()
int FfmpegCamera::FfmpegInterruptCallback(void *ctx) {
Debug(3,"FfmpegInteruptCallback");
@ -678,9 +540,9 @@ int FfmpegCamera::FfmpegInterruptCallback(void *ctx) {
}
return 0;
}
} // end int FfmpegCamera::FfmpegInterruptCallback(void *ctx)
void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){
void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx) {
Debug(3,"FfmpegReopenThreadtCallback");
if ( ctx == NULL ) return NULL;
@ -702,6 +564,47 @@ void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){
return NULL;
}
}
}
} // end void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx)
/*
Responsible for populating the decoded frame member of the zm_packet.
*/
Image * FfmpegCamera::decode( Image *i = NULL, _AVPIXELFORMAT imagePixFormat, struct SwsContext *mConvertContext ) {
if ( ! frame ) {
Error("Can't get image without frame.. maybe need to decode first");
return NULL;
}
if ( ! image ) {
if ( ! i ) {
Error("Need a pre-allocated image buffer");
return NULL;
}
image = i;
}
/* Request a writeable buffer of the target image */
uint8_t* directbuffer = image->WriteBuffer();
//uint8_t* directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
if ( directbuffer == NULL ) {
Error("Failed requesting writeable buffer for the captured image.");
image = NULL;
return NULL;
}
// mFrame is an intermediate.
AVFrame *mFrame = zm_av_frame_alloc();
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
av_image_fill_arrays(mFrame->data, mFrame->linesize, directbuffer, imagePixFormat, frame->width, frame->height, 1);
#else
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, frame->width, frame->height);
#endif
if (sws_scale(mConvertContext, frame->data, frame->linesize,
0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) {
Fatal("Unable to convert raw format %u to target format %u",
mVideoCodecContext->pix_fmt, imagePixFormat);
}
av_frame_free( &mFrame );
return image;
}
#endif // HAVE_LIBAVFORMAT

View File

@ -40,6 +40,7 @@ class FfmpegCamera : public Camera {
std::string mPath;
std::string mMethod;
std::string mOptions;
std::string encoder_options;
int frameCount;
@ -51,7 +52,6 @@ class FfmpegCamera : public Camera {
AVCodec *mAudioCodec;
AVFrame *mRawFrame;
AVFrame *mFrame;
_AVPIXELFORMAT imagePixFormat;
bool hwaccel;
#if HAVE_AVUTIL_HWCONTEXT_H
@ -82,9 +82,6 @@ class FfmpegCamera : public Camera {
#endif // HAVE_LIBAVFORMAT
#if HAVE_LIBSWSCALE
struct SwsContext *mConvertContext;
#endif
int64_t startTime;

180
src/zm_ffmpeg_output.cpp Normal file
View File

@ -0,0 +1,180 @@
#include "zm_ffmpeg_input.h"
#include "zm_logger.h"
#include "zm_ffmpeg.h"
FFmpeg_Output::FFmpeg_Output() {
input_format_context = NULL;
video_stream_id = -1;
audio_stream_id = -1;
av_register_all();
avcodec_register_all();
}
FFmpeg_Output::~FFmpeg_Output() {
}
int FFmpeg_Output::Open( const char *filepath ) {
int error;
/** Open the input file to read from it. */
if ( (error = avformat_open_input( &input_format_context, filepath, NULL, NULL)) < 0 ) {
Error("Could not open input file '%s' (error '%s')\n",
filepath, av_make_error_string(error).c_str() );
input_format_context = NULL;
return error;
}
/** Get information on the input file (number of streams etc.). */
if ( (error = avformat_find_stream_info(input_format_context, NULL)) < 0 ) {
Error( "Could not open find stream info (error '%s')\n",
av_make_error_string(error).c_str() );
avformat_close_input(&input_format_context);
return error;
}
for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) {
if ( is_video_stream( input_format_context->streams[i] ) ) {
zm_dump_stream_format(input_format_context, i, 0, 0);
if ( video_stream_id == -1 ) {
video_stream_id = i;
// if we break, then we won't find the audio stream
} else {
Warning( "Have another video stream." );
}
} else if ( is_audio_stream( input_format_context->streams[i] ) ) {
if ( audio_stream_id == -1 ) {
audio_stream_id = i;
} else {
Warning( "Have another audio stream." );
}
}
streams[i].frame_count = 0;
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
streams[i].context = avcodec_alloc_context3( NULL );
avcodec_parameters_to_context( streams[i].context, input_format_context->streams[i]->codecpar );
#else
streams[i].context = input_format_context->streams[i]->codec;
#endif
if ( !(streams[i].codec = avcodec_find_decoder(streams[i].context->codec_id)) ) {
Error( "Could not find input codec\n");
avformat_close_input(&input_format_context);
return AVERROR_EXIT;
} else {
Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i );
}
if ((error = avcodec_open2( streams[i].context, streams[i].codec, NULL)) < 0) {
Error( "Could not open input codec (error '%s')\n",
av_make_error_string(error).c_str() );
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
avcodec_free_context( &streams[i].context );
#endif
avformat_close_input(&input_format_context);
return error;
}
} // end foreach stream
if ( video_stream_id == -1 )
Error( "Unable to locate video stream in %s", filepath );
if ( audio_stream_id == -1 )
Debug( 3, "Unable to locate audio stream in %s", filepath );
return 0;
} // end int FFmpeg_Output::Open( const char * filepath )
AVFrame *FFmpeg_Output::get_frame( int stream_id ) {
Debug(1, "Getting frame from stream %d", stream_id );
int frameComplete = false;
AVPacket packet;
av_init_packet( &packet );
AVFrame *frame = zm_av_frame_alloc();
char errbuf[AV_ERROR_MAX_STRING_SIZE];
while ( !frameComplete ) {
int ret = av_read_frame( input_format_context, &packet );
if ( ret < 0 ) {
av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
if (
// Check if EOF.
(ret == AVERROR_EOF || (input_format_context->pb && input_format_context->pb->eof_reached)) ||
// Check for Connection failure.
(ret == -110)
) {
Info( "av_read_frame returned %s.", errbuf );
return NULL;
}
Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf );
return NULL;
}
if ( (stream_id < 0 ) || ( packet.stream_index == stream_id ) ) {
Debug(1,"Packet is for our stream (%d)", packet.stream_index );
AVCodecContext *context = streams[packet.stream_index].context;
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_send_packet( context, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
} else {
Debug(1, "Success getting a packet");
}
#if HAVE_AVUTIL_HWCONTEXT_H
if ( hwaccel ) {
ret = avcodec_receive_frame( context, hwFrame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to receive frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
}
ret = av_hwframe_transfer_data(frame, hwFrame, 0);
if (ret < 0) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to transfer frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
}
} else {
#endif
Debug(1,"Getting a frame?");
ret = avcodec_receive_frame( context, frame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
}
#if HAVE_AVUTIL_HWCONTEXT_H
}
#endif
frameComplete = 1;
# else
ret = zm_avcodec_decode_video( streams[packet.stream_index].context, frame, &frameComplete, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to decode frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
}
#endif
} // end if it's the right stream
zm_av_packet_unref( &packet );
} // end while ! frameComplete
return frame;
} // end AVFrame *FFmpeg_Output::get_frame

46
src/zm_ffmpeg_output.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef ZM_FFMPEG_INPUT_H
#define ZM_FFMPEG_INPUT_H
#ifdef __cplusplus
extern "C" {
#endif
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavcodec/avcodec.h"
#ifdef __cplusplus
}
#endif
class FFmpeg_Output {
public:
FFmpeg_Output();
~FFmpeg_Output();
int Open( const char *filename );
int Close();
AVFrame *put_frame( int stream_id=-1 );
AVFrame *put_packet( int stream_id=-1 );
int get_video_stream_id() {
return video_stream_id;
}
int get_audio_stream_id() {
return audio_stream_id;
}
private:
typedef struct {
AVCodecContext *context;
AVCodec *codec;
int frame_count;
} stream;
stream streams[2];
int video_stream_id;
int audio_stream_id;
AVFormatContext *input_format_context;
};
#endif

View File

@ -241,6 +241,7 @@ Monitor::Monitor(
int p_orientation,
unsigned int p_deinterlacing,
int p_savejpegs,
int p_colours,
VideoWriter p_videowriter,
std::string p_encoderparams,
bool p_record_audio,
@ -280,6 +281,7 @@ Monitor::Monitor(
orientation( (Orientation)p_orientation ),
deinterlacing( p_deinterlacing ),
savejpegspref( p_savejpegs ),
colours( p_colours ),
videowriter( p_videowriter ),
encoderparams( p_encoderparams ),
record_audio( p_record_audio ),
@ -336,6 +338,49 @@ Monitor::Monitor(
/* Parse encoder parameters */
ParseEncoderParameters(encoderparams.c_str(), &encoderparamsvec);
#if HAVE_LIBSWSCALE
mConvertContext = NULL;
#endif
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
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;
imagePixFormat = AV_PIX_FMT_GRAY8;
} else {
Panic("Unexpected colours: %d",colours);
}
#if HAVE_LIBSWSCALE
//FIXME, need to be able to query the camera input for what it is going to be getting, which needs to be called after the camera is open.
//Debug ( 1, "Calling sws_isSupportedInput" );
//if ( !sws_isSupportedInput(mVideoCodecContext->pix_fmt) ) {
//Fatal("swscale does not support the codec format: %c%c%c%c", (mVideoCodecContext->pix_fmt)&0xff, ((mVideoCodecContext->pix_fmt >> 8)&0xff), ((mVideoCodecContext->pix_fmt >> 16)&0xff), ((mVideoCodecContext->pix_fmt >> 24)&0xff));
//}
if ( !sws_isSupportedOutput(imagePixFormat) ) {
Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff));
}
// We don't know this yet, need to open the camera first.
//mConvertContext = sws_getContext(mVideoCodecContext->width,
//mVideoCodecContext->height,
//mVideoCodecContext->pix_fmt,
//width, height,
//imagePixFormat, SWS_BICUBIC, NULL,
//NULL, NULL);
//if ( mConvertContext == NULL )
//Fatal( "Unable to create conversion context for %s", mPath.c_str() );
#else // HAVE_LIBSWSCALE
//Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
#endif // HAVE_LIBSWSCALE
//if ( (unsigned int)mVideoCodecContext->width != width || (unsigned int)mVideoCodecContext->height != height ) {
//Warning( "Monitor dimensions are %dx%d but camera is sending %dx%d", width, height, mVideoCodecContext->width, mVideoCodecContext->height );
//}
fps = 0.0;
event_count = 0;
@ -568,6 +613,12 @@ Monitor::~Monitor() {
delete videoStore;
videoStore = NULL;
}
#if HAVE_LIBSWSCALE
if ( mConvertContext ) {
sws_freeContext( mConvertContext );
mConvertContext = NULL;
}
#endif
if ( timestamps ) {
delete[] timestamps;
timestamps = 0;
@ -1126,7 +1177,7 @@ bool Monitor::CheckSignal( const Image *image ) {
return true;
}
} else if(colours == ZM_COLOUR_RGB32) {
} else if ( colours == ZM_COLOUR_RGB32 ) {
if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR) {
if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) )
return true;
@ -1359,12 +1410,12 @@ Debug(3,"before DetectMotion");
if ( event ) {
//TODO: We shouldn't have to do this every time. Not sure why it clears itself if this isn't here??
//snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile());
Debug( 3, "Detected new event at (%d.%d)", timestamp->tv_sec,timestamp->tv_usec );
//Debug( 3, "Detected new event at (%d.%d)", timestamp->tv_sec,timestamp->tv_usec );
if ( section_length ) {
// TODO: Wouldn't this be clearer if we just did something like if now - event->start > section_length ?
int section_mod = timestamp->tv_sec % section_length;
Debug( 3, "Section length (%d) Last Section Mod(%d), new section mod(%d)", section_length, last_section_mod, section_mod );
Debug( 4, "Section length (%d) Last Section Mod(%d), new section mod(%d)", section_length, last_section_mod, section_mod );
if ( section_mod < last_section_mod ) {
//if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) {
//if ( state == TAPE ) {
@ -1976,6 +2027,7 @@ int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose
orientation,
deinterlacing,
savejpegs,
colours,
videowriter,
encoderparams,
record_audio,
@ -2160,6 +2212,7 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
orientation,
deinterlacing,
savejpegs,
colours,
videowriter,
encoderparams,
record_audio,
@ -2309,6 +2362,7 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu
orientation,
deinterlacing,
savejpegs,
colours,
videowriter,
encoderparams,
record_audio,
@ -2468,6 +2522,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
orientation,
deinterlacing,
savejpegs,
colours,
videowriter,
encoderparams,
record_audio,
@ -2795,6 +2850,7 @@ Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) {
orientation,
deinterlacing,
savejpegs,
colours,
videowriter,
encoderparams,
record_audio,

View File

@ -240,9 +240,15 @@ protected:
bool videoRecording;
int savejpegspref;
int colours;
VideoWriter videowriter;
std::string encoderparams;
std::vector<EncoderParameter_t> encoderparamsvec;
_AVPIXELFORMAT imagePixFormat;
unsigned int subpixelorder;
#if HAVE_LIBSWSCALE
struct SwsContext *mConvertContext;
#endif
bool record_audio; // Whether to store the audio that we receive
int brightness; // The statically saved brightness of the camera
@ -348,6 +354,7 @@ public:
int p_orientation,
unsigned int p_deinterlacing,
int p_savejpegs,
int p_colours,
VideoWriter p_videowriter,
std::string p_encoderparams,
bool p_record_audio,
@ -434,6 +441,7 @@ public:
int GetOptSaveJPEGs() const { return( savejpegspref ); }
VideoWriter GetOptVideoWriter() const { return( videowriter ); }
const std::vector<EncoderParameter_t>* GetOptEncoderParams() const { return( &encoderparamsvec ); }
const std::string &GetEncoderOptions() const { return( encoderparams ); }
uint32_t GetLastEventId() const { return shared_data->last_event_id; }
uint32_t GetVideoWriterEventId() const { return video_store_data->current_event; }
void SetVideoWriterEventId( uint32_t p_event_id ) { video_store_data->current_event = p_event_id; }

View File

@ -123,41 +123,4 @@ int ZMPacket::decode( AVCodecContext *ctx ) {
return 1;
} // end ZMPacket::decode
Image * ZMPacket::get_image( Image *i = NULL ) {
if ( ! frame ) {
Error("Can't get image without frame.. maybe need to decode first");
return NULL;
}
if ( ! image ) {
if ( ! i ) {
Error("Need a pre-allocated image buffer");
return NULL;
}
image = i;
}
/* Request a writeable buffer of the target image */
uint8_t* directbuffer = image->WriteBuffer();
//uint8_t* directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
if ( directbuffer == NULL ) {
Error("Failed requesting writeable buffer for the captured image.");
image = NULL;
return NULL;
}
AVFrame *mFrame = zm_av_frame_alloc();
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
av_image_fill_arrays(mFrame->data, mFrame->linesize, directbuffer, imagePixFormat, frame->width, frame->height, 1);
#else
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, frame->width, frame->height);
#endif
if (sws_scale(mConvertContext, frame->data, frame->linesize,
0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) {
Fatal("Unable to convert raw format %u to target format %u",
mVideoCodecContext->pix_fmt, imagePixFormat);
}
av_frame_free( &mFrame );
return image;
}

View File

@ -41,7 +41,7 @@ class ZMPacket {
public:
AVPacket *av_packet() { return &packet; }
AVFrame *av_frame() { return frame; }
Image *get_image( Image * );
Image *get_image( Image *, _AVPIXELFORMAT imagePixFormat, struct SwsContext *mConvertContext );
int is_keyframe() { return keyframe; };
int decode( AVCodecContext *ctx );
ZMPacket( AVPacket *packet, struct timeval *timestamp );

View File

@ -31,10 +31,12 @@ extern "C" {
#include "libavutil/time.h"
}
VideoStore::VideoStore(const char *filename_in, const char *format_in,
AVStream *p_video_in_stream,
AVStream *p_audio_in_stream,
Monitor *monitor) {
VideoStore::VideoStore(
const char *filename_in, const char *format_in,
AVStream *p_video_in_stream,
AVStream *p_audio_in_stream, int64_t nStartTime,
Monitor *monitor
) {
video_in_stream = p_video_in_stream;
audio_in_stream = p_audio_in_stream;
@ -50,9 +52,12 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
// store ins in variables local to class
filename = filename_in;
format = format_in;
packets_written = 0;
frame_count = 0;
Info("Opening video storage stream %s format: %s", filename, format);
#if 0
ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename);
if (ret < 0) {
Warning(
@ -65,6 +70,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
// Couldn't deduce format from filename, trying from format name
if (!oc) {
#endif
avformat_alloc_output_context2(&oc, NULL, format, filename);
if (!oc) {
Fatal(
@ -72,9 +78,11 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
" could not be assigned based on filename or format %s",
filename, format);
} else {
Debug(4, "Success alocateing out ctx");
Debug(4, "Success alocating out ctx");
}
#if 0
} // end if ! oc
#endif
AVDictionary *pmetadata = NULL;
int dsr =
@ -84,15 +92,14 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
oc->metadata = pmetadata;
out_format = oc->oformat;
in_frame = NULL;
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
// Since we are not re-encoding, all we have to do is copy the parameters
video_out_ctx = avcodec_alloc_context3(NULL);
// Copy params from instream to ctx
ret = avcodec_parameters_to_context(video_out_ctx,
video_in_stream->codecpar);
if (ret < 0) {
if ( ret < 0 ) {
Error("Could not initialize ctx parameteres");
return;
} else {
@ -106,7 +113,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
Debug(2, "Success creating video out stream");
}
if (!video_out_ctx->codec_tag) {
if ( !video_out_ctx->codec_tag ) {
video_out_ctx->codec_tag =
av_codec_get_tag(oc->oformat->codec_tag, video_in_ctx->codec_id);
Debug(2, "No codec_tag, setting to %d", video_out_ctx->codec_tag);
@ -124,22 +131,80 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
zm_dump_codecpar(video_out_stream->codecpar);
#else
video_out_stream =
avformat_new_stream(oc,(const AVCodec *)(video_in_ctx->codec));
if (!video_out_stream) {
if ( video_in_ctx->codec_id == AV_CODEC_ID_H264 ) {
// Same codec, just copy the packets, otherwise we have to decode/encode
video_out_codec = (AVCodec *)video_in_ctx->codec;
video_out_stream = avformat_new_stream(oc, video_out_codec);
if ( !video_out_stream ) {
Fatal("Unable to create video out stream\n");
} else {
Debug(2, "Success creating video out stream");
}
video_out_ctx = video_out_stream->codec;
// Just copy them from the in, no reason to choose different
video_out_ctx->time_base = video_in_ctx->time_base;
video_out_stream->time_base = video_in_stream->time_base;
} else {
/** Create a new frame to store the audio samples. */
if ( !(in_frame = zm_av_frame_alloc()) ) {
Error("Could not allocate in frame");
return;
}
video_out_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!video_out_codec) {
Fatal("Could not find codec for H264");
}
Debug(2, "Have video out codec");
}
video_out_stream = avformat_new_stream(oc, video_out_codec);
if ( !video_out_stream ) {
Fatal("Unable to create video out stream\n");
} else {
Debug(2, "Success creating video out stream");
}
video_out_ctx = video_out_stream->codec;
ret = avcodec_copy_context(video_out_ctx, video_in_ctx);
if (ret < 0) {
Fatal("Unable to copy in video ctx to out video ctx %s\n",
av_make_error_string(ret).c_str());
// * Copy over the useful; parameters
video_out_ctx->height = video_in_ctx->height;
video_out_ctx->width = video_in_ctx->width;
video_out_ctx->sample_aspect_ratio = video_in_ctx->sample_aspect_ratio;
/* take first format from list of supported formats */
video_out_ctx->pix_fmt = video_out_codec->pix_fmts[0];
/* video time_base can be set to whatever is handy and supported by encoder */
//video_out_ctx->time_base = video_in_ctx->time_base;
video_out_ctx->time_base = (AVRational){1, 1000}; // Milliseconds as base frame rate
video_out_ctx->framerate = (AVRational){0,1}; // Unknown framerate
AVDictionary *opts = 0;
std::string Options = monitor->GetEncoderOptions();
ret = av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0);
if ( ret < 0 ) {
Warning("Could not parse ffmpeg encoder options list '%s'\n", Options.c_str());
} else {
Debug(3, "Success copying ctx");
AVDictionaryEntry *e = NULL;
while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) {
Debug( 3, "Encoder Option %s=%s", e->key, e->value );
}
}
if (!video_out_ctx->codec_tag) {
ret = avcodec_open2(video_out_ctx, video_out_codec, &opts );
if (ret < 0) {
Error("Can't open video codec!");
return;
}
AVDictionaryEntry *e = NULL;
while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) {
Warning( "Encoder Option %s not recognized by ffmpeg codec", e->key);
}
av_dict_free(&opts);
//ret = avcodec_copy_context(video_out_ctx, video_in_ctx);
//if ( ret < 0 ) {
//Fatal("Unable to copy in video ctx to out video ctx %s\n",
//av_make_error_string(ret).c_str());
////} else {
//Debug(3, "Success copying ctx");
//}
#if 0
if ( !video_out_ctx->codec_tag ) {
Debug(2, "No codec_tag");
if (!oc->oformat->codec_tag ||
av_codec_get_id(oc->oformat->codec_tag,
@ -152,10 +217,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
}
}
#endif
// Just copy them from the in, no reason to choose different
video_out_ctx->time_base = video_in_ctx->time_base;
video_out_stream->time_base = video_in_stream->time_base;
#endif
Debug(3,
"Time bases: VIDEO in stream (%d/%d) in codec: (%d/%d) out "
@ -192,7 +254,6 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
audio_out_codec = NULL;
audio_in_ctx = NULL;
audio_out_stream = NULL;
in_frame = NULL;
out_frame = NULL;
#ifdef HAVE_LIBAVRESAMPLE
resample_ctx = NULL;
@ -329,17 +390,86 @@ bool VideoStore::open() {
return true;
}
void VideoStore::write_audio_packet( AVPacket &pkt ) {
Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts,
pkt.dts, pkt.duration);
pkt.pts = audio_next_pts;
pkt.dts = audio_next_dts;
if (pkt.duration > 0)
pkt.duration =
av_rescale_q(pkt.duration, audio_out_ctx->time_base,
audio_out_stream->time_base);
audio_next_pts += pkt.duration;
audio_next_dts += pkt.duration;
Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts,
pkt.dts, pkt.duration);
pkt.stream_index = audio_out_stream->index;
av_interleaved_write_frame(oc, &pkt);
}
VideoStore::~VideoStore() {
if ( video_out_ctx->codec_id != video_in_ctx->codec_id ) {
if (video_out_ctx->codec->capabilities & AV_CODEC_CAP_DELAY) {
// The codec queues data. We need to send a flush command and out
// whatever we get. Failures are not fatal.
AVPacket pkt;
av_init_packet(&pkt);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
// Put encoder into flushing mode
avcodec_send_frame(video_out_ctx, NULL);
while (1) {
ret = avcodec_receive_packet(video_out_ctx, &pkt);
if (ret < 0) {
if (AVERROR_EOF != ret) {
Error("ERror encoding audio while flushing (%d) (%s)", ret,
av_err2str(ret));
}
break;
}
#else
while (1) {
// WIthout these we seg fault I don't know why.
pkt.data = NULL;
pkt.size = 0;
av_init_packet(&pkt);
int got_packet = 0;
ret = avcodec_encode_video2(video_out_ctx, &pkt, NULL, &got_packet);
if ( ret < 0 ) {
Error("ERror encoding video while flushing (%d) (%s)", ret,
av_err2str(ret));
break;
}
if (!got_packet) {
break;
}
#endif
Debug(3, "(%d, %d)", video_next_dts, video_next_pts );
pkt.dts = video_next_dts;
pkt.pts = video_next_pts;
pkt.duration = video_last_duration;
write_video_packet(pkt);
zm_av_packet_unref(&pkt);
} // while have buffered frames
} // end if have delay capability
} // end if have buffered video
if (audio_out_codec) {
// The codec queues data. We need to send a flush command and out
// whatever we get. Failures are not fatal.
AVPacket pkt;
// WIthout these we seg fault I don't know why.
pkt.data = NULL;
pkt.size = 0;
av_init_packet(&pkt);
while (1) {
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
// Put encoder into flushing mode
avcodec_send_frame(audio_out_ctx, NULL);
// Put encoder into flushing mode
avcodec_send_frame(audio_out_ctx, NULL);
while (1) {
ret = avcodec_receive_packet(audio_out_ctx, &pkt);
if (ret < 0) {
if (AVERROR_EOF != ret) {
@ -349,6 +479,7 @@ VideoStore::~VideoStore() {
break;
}
#else
while (1) {
int got_packet = 0;
ret =
avcodec_encode_audio2(audio_out_ctx, &pkt, NULL, &got_packet);
@ -362,22 +493,7 @@ VideoStore::~VideoStore() {
break;
}
#endif
Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts,
pkt.dts, pkt.duration);
pkt.pts = audio_next_pts;
pkt.dts = audio_next_dts;
if (pkt.duration > 0)
pkt.duration =
av_rescale_q(pkt.duration, audio_out_ctx->time_base,
audio_out_stream->time_base);
audio_next_pts += pkt.duration;
audio_next_dts += pkt.duration;
Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts,
pkt.dts, pkt.duration);
pkt.stream_index = audio_out_stream->index;
av_interleaved_write_frame(oc, &pkt);
write_audio_packet(pkt);
zm_av_packet_unref(&pkt);
} // while have buffered frames
} // end if audio_out_codec
@ -402,6 +518,11 @@ VideoStore::~VideoStore() {
video_out_ctx = NULL;
Debug(4, "Success freeing video_out_ctx");
}
// Used by both audio and video conversions
if (in_frame) {
av_frame_free(&in_frame);
in_frame = NULL;
}
if (audio_out_stream) {
avcodec_close(audio_out_ctx);
audio_out_ctx = NULL;
@ -410,10 +531,6 @@ VideoStore::~VideoStore() {
avresample_close(resample_ctx);
avresample_free(&resample_ctx);
}
if (in_frame) {
av_frame_free(&in_frame);
in_frame = NULL;
}
if (out_frame) {
av_frame_free(&out_frame);
out_frame = NULL;
@ -547,10 +664,12 @@ bool VideoStore::setup_resampler() {
audio_out_ctx->channel_layout, audio_out_ctx->frame_size);
/** Create a new frame to store the audio samples. */
if ( ! in_frame ) {
if (!(in_frame = zm_av_frame_alloc())) {
Error("Could not allocate in frame");
return false;
}
}
/** Create a new frame to store the audio samples. */
if (!(out_frame = zm_av_frame_alloc())) {
@ -687,92 +806,117 @@ int VideoStore::writePacket( ZMPacket *ipkt ) {
int VideoStore::writeVideoFramePacket(AVPacket *ipkt) {
av_init_packet(&opkt);
frame_count += 1;
if ( video_out_ctx->codec_id != video_in_ctx->codec_id ) {
Debug(3, "Have encoding video frmae count (%d)", frame_count);
opkt.pts = video_next_pts;
opkt.dts = video_next_dts;
opkt.duration = 0;
int duration;
if (!video_last_pts) {
duration = 0;
} else {
duration =
av_rescale_q(ipkt->pts - video_last_pts, video_in_stream->time_base,
video_out_stream->time_base);
Debug(1, "duration calc: pts(%d) - last_pts(%d) = (%d)", ipkt->pts,
video_last_pts, duration);
if (duration < 0) {
duration = ipkt->duration;
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_send_packet(video_in_ctx, ipkt);
if (ret < 0) {
Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str());
return 0;
}
}
//#if ( 0 && video_last_pts && ( ipkt->duration == AV_NOPTS_VALUE || !
//ipkt->duration ) ) {
// Video packets don't really have a duration. Audio does.
// opkt.duration = av_rescale_q(duration, video_in_stream->time_base,
// video_out_stream->time_base);
// opkt.duration = 0;
//} else {
// duration = opkt.duration = av_rescale_q(ipkt->duration,
// video_in_stream->time_base, video_out_stream->time_base);
//}
video_last_pts = ipkt->pts;
video_last_dts = ipkt->dts;
ret = avcodec_receive_frame(video_in_ctx, in_frame);
if ( ret < 0 ) {
Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str());
return 0;
}
if ((ret = avcodec_send_frame(video_out_ctx, in_frame)) < 0) {
Error("Could not send frame (error '%s')",
av_make_error_string(ret).c_str());
zm_av_packet_unref(&opkt);
return 0;
}
#if 0
//Scale the PTS of the outgoing packet to be the correct time base
if ( ipkt->pts != AV_NOPTS_VALUE ) {
if ( ! video_last_pts ) {
// This is the first packet.
opkt.pts = 0;
Debug(2, "Starting video video_last_pts will become (%d)", ipkt->pts);
if ((ret = avcodec_receive_packet(video_out_ctx, &opkt)) < 0) {
if (AVERROR(EAGAIN) == ret) {
// THe codec may need more samples than it has, perfectly valid
Debug(3, "Could not recieve packet (error '%s')",
av_make_error_string(ret).c_str());
} else {
Error("Could not recieve packet (error %d = '%s')", ret,
av_make_error_string(ret).c_str());
}
zm_av_packet_unref(&opkt);
av_frame_unref(in_frame);
return 0;
}
#else
/**
* Decode the video frame stored in the packet.
* The in video stream decoder is used to do this.
* If we are at the end of the file, pass an empty packet to the decoder
* to flush it.
*/
if ((ret = avcodec_decode_video2(video_in_ctx, in_frame,
&data_present, ipkt)) < 0) {
Error("Could not decode frame (error '%s')\n",
av_make_error_string(ret).c_str());
dumpPacket(ipkt);
av_frame_free(&in_frame);
return 0;
} else {
if ( ipkt->pts < video_last_pts ) {
Debug(1, "Resetting video_last_pts from (%d) to (%d)", video_last_pts, ipkt->pts);
// wrap around, need to figure out the distance FIXME having this wrong should cause a jump, but then play ok?
opkt.pts = video_next_pts + av_rescale_q( ipkt->pts, video_in_stream->time_base, video_out_stream->time_base);
} else {
opkt.pts = video_next_pts + av_rescale_q( ipkt->pts - video_last_pts, video_in_stream->time_base, video_out_stream->time_base);
}
Debug(3, "Decoded frame data_present(%d)", data_present);
}
Debug(3, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, video_last_pts);
video_last_pts = ipkt->pts;
} else {
Debug(3, "opkt.pts = undef");
opkt.pts = AV_NOPTS_VALUE;
}
// Just because the in stream wraps, doesn't mean the out needs to. Really, if we are limiting ourselves to 10min segments I can't imagine every wrapping in the out. So need to handle in wrap, without causing out wrap.
if ( !video_last_dts ) {
// This is the first packet.
opkt.dts = 0;
Debug(1, "Starting video video_last_dts will become (%lu)", ipkt->dts);
video_last_dts = ipkt->dts;
} else {
// Scale the DTS of the outgoing packet to be the correct time base
if ( ipkt->dts == AV_NOPTS_VALUE ) {
// why are we using cur_dts instead of packet.dts? I think cur_dts is in AV_TIME_BASE_Q, but ipkt.dts is in video_in_stream->time_base
if ( video_in_stream->cur_dts < video_last_dts ) {
Debug(1, "Resetting video_last_dts from (%d) to (%d) p.dts was (%d)", video_last_dts, video_in_stream->cur_dts, ipkt->dts);
opkt.dts = video_next_dts + av_rescale_q(video_in_stream->cur_dts, AV_TIME_BASE_Q, video_out_stream->time_base);
} else {
opkt.dts = video_next_dts + av_rescale_q(video_in_stream->cur_dts - video_last_dts, AV_TIME_BASE_Q, video_out_stream->time_base);
}
Debug(3, "opkt.dts = %d from video_in_stream->cur_dts(%d) - previus_dts(%d)", opkt.dts, video_in_stream->cur_dts, video_last_dts);
video_last_dts = video_in_stream->cur_dts;
} else {
if ( ipkt->dts < video_last_dts ) {
Debug(1, "Resetting video_last_dts from (%d) to (%d)", video_last_dts, ipkt->dts);
opkt.dts = video_next_dts + av_rescale_q( ipkt->dts, video_in_stream->time_base, video_out_stream->time_base);
} else {
opkt.dts = video_next_dts + av_rescale_q( ipkt->dts - video_last_dts, video_in_stream->time_base, video_out_stream->time_base);
}
Debug(3, "opkt.dts = %d from ipkt.dts(%d) - previus_dts(%d)", opkt.dts, ipkt->dts, video_last_dts);
video_last_dts = ipkt->dts;
if ( !data_present ) {
Debug(2, "Not ready to transcode a frame yet.");
return 0;
}
if ((ret = avcodec_encode_video2(video_out_ctx, &opkt, in_frame,
&data_present)) < 0) {
Error("Could not encode frame (error '%s')",
av_make_error_string(ret).c_str());
zm_av_packet_unref(&opkt);
return 0;
}
if (!data_present) {
Debug(2, "Not ready to out a frame yet.");
zm_av_packet_unref(&opkt);
return 0;
}
}
#endif
} else {
Debug(3, "Doing passthrough, just copy packet");
// Just copy it because the codec is the same
opkt.data = ipkt->data;
opkt.size = ipkt->size;
opkt.flags = ipkt->flags;
}
opkt.dts = video_next_dts;
opkt.pts = video_next_pts;
int duration;
if ( !video_last_pts ) {
duration = 0;
} else {
duration = av_rescale_q(
ipkt->pts - video_last_pts,
video_in_stream->time_base,
video_out_stream->time_base
);
Debug(1, "duration calc: pts(%d) - last_pts(%d) = (%d)", ipkt->pts,
video_last_pts, duration);
if ( duration < 0 ) {
duration = ipkt->duration;
}
}
Debug(1, "ipkt.dts(%d) ipkt.pts(%d)", ipkt->dts, ipkt->pts);
video_last_pts = ipkt->pts;
video_last_dts = ipkt->dts;
video_last_duration = duration;
opkt.duration = duration;
write_video_packet( opkt );
zm_av_packet_unref(&opkt);
return 0;
} // end int VideoStore::writeVideoFramePacket( AVPacket *ipkt )
void VideoStore::write_video_packet( AVPacket &opkt ) {
if (opkt.dts > opkt.pts) {
Debug(1,
"opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen "
@ -781,36 +925,30 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) {
opkt.dts = opkt.pts;
}
opkt.flags = ipkt->flags;
int keyframe = opkt.flags & AV_PKT_FLAG_KEY;
opkt.pos = -1;
opkt.data = ipkt->data;
opkt.size = ipkt->size;
opkt.stream_index = video_out_stream->index;
video_next_dts += opkt.duration;
video_next_pts += opkt.duration;
AVPacket safepkt;
memcpy(&safepkt, &opkt, sizeof(AVPacket));
Debug(1,
"writing video packet keyframe(%d) pts(%d) dts(%d) duration(%d) "
"ipkt.duration(%d)",
keyframe, opkt.pts, opkt.dts, duration, ipkt->duration);
if ((opkt.data == NULL) || (opkt.size < 1)) {
"writing video packet keyframe(%d) pts(%d) dts(%d) duration(%d) packet_count(%d)",
keyframe, opkt.pts, opkt.dts, opkt.duration, packets_written );
if ( (opkt.data == NULL) || (opkt.size < 1) ) {
Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__);
dumpPacket(ipkt);
dumpPacket(&opkt);
} else if ((video_next_dts > 0) && (video_next_dts > opkt.dts)) {
Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame",
__FILE__, __LINE__, video_next_dts, opkt.dts);
video_next_dts = opkt.dts;
dumpPacket(&opkt);
//} else if ((video_next_dts > 0) && (video_next_dts > opkt.dts)) {
//Warning("%s:%d: DTS out of order: next:%lld \u226E opkt.dts %lld; discarding frame",
//__FILE__, __LINE__, video_next_dts, opkt.dts);
//video_next_dts = opkt.dts;
//dumpPacket(&opkt);
} else {
video_next_dts = opkt.dts + duration;
video_next_pts = opkt.pts + duration;
ret = av_interleaved_write_frame(oc, &opkt);
if (ret < 0) {
// There's nothing we can really do if the frame is rejected, just drop it
@ -824,13 +962,12 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) {
zm_dump_codecpar(video_in_stream->codecpar);
zm_dump_codecpar(video_out_stream->codecpar);
#endif
} else {
packets_written += 1;
}
}
zm_av_packet_unref(&opkt);
return 0;
} // end int VideoStore::writeVideoFramePacket( AVPacket *ipkt )
} // end void VideoStore::write_video_packet
int VideoStore::writeAudioFramePacket(AVPacket *ipkt) {
Debug(4, "writeAudioFrame");

View File

@ -18,11 +18,14 @@ extern "C" {
class VideoStore {
private:
unsigned int packets_written;
unsigned int frame_count;
AVOutputFormat *out_format;
AVFormatContext *oc;
AVStream *video_out_stream;
AVStream *audio_out_stream;
AVCodec *video_out_codec;
AVCodecContext *video_out_ctx;
AVStream *video_in_stream;
@ -31,6 +34,7 @@ private:
// Move this into the object so that we aren't constantly allocating/deallocating it on the stack
AVPacket opkt;
// we are transcoding
AVFrame *video_in_frame;
AVFrame *in_frame;
AVFrame *out_frame;
@ -58,12 +62,14 @@ AVAudioResampleContext* resample_ctx;
// These are for in
int64_t video_last_pts;
int64_t video_last_dts;
int64_t video_last_duration;
int64_t audio_last_pts;
int64_t audio_last_dts;
// These are for out, should start at zero. We assume they do not wrap because we just aren't going to save files that big.
int64_t video_next_pts;
int64_t video_next_dts;
;
int64_t audio_next_pts;
int64_t audio_next_dts;
@ -81,6 +87,8 @@ public:
bool open();
~VideoStore();
void write_video_packet( AVPacket &pkt );
void write_audio_packet( AVPacket &pkt );
int writeVideoFramePacket( AVPacket *pkt );
int writeAudioFramePacket( AVPacket *pkt );
int writePacket( ZMPacket *pkt );

View File

@ -198,14 +198,6 @@ class Event {
}
function createListThumbnail( $overwrite=false ) {
# Load the frame with the highest score to use as a thumbnail
if ( !($frame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND Score=? ORDER BY FrameId LIMIT 1', NULL, array( $this->{'Id'}, $this->{'MaxScore'} ) )) ) {
Error("Unable to find a Frame matching max score " . $this->{'MaxScore'} . ' for event ' . $this->{'Id'} );
// FIXME: What if somehow the db frame was lost or score was changed? Should probably try another search for any frame.
return( false );
}
$frameId = $frame['FrameId'];
if ( ZM_WEB_LIST_THUMB_WIDTH ) {
$thumbWidth = ZM_WEB_LIST_THUMB_WIDTH;
@ -219,12 +211,25 @@ class Event {
Fatal( "No thumbnail width or height specified, please check in Options->Web" );
}
$imageData = $this->getImageSrc( $frame, $scale, false, $overwrite );
if ( ! $imageData ) {
return ( false );
$eventPath = $this->Path();
if ( file_exists( $eventPath.'/snapshot.jpg' ) ) {
$frame = 'snapshot';
$humbData = array('Path'=>$eventPath.'/snapshot.jpg');
} else {
# Load the frame with the highest score to use as a thumbnail
if ( !($frame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND Score=? ORDER BY FrameId LIMIT 1', NULL, array( $this->{'Id'}, $this->{'MaxScore'} ) )) ) {
Error("Unable to find a Frame matching max score " . $this->{'MaxScore'} . ' for event ' . $this->{'Id'} );
// FIXME: What if somehow the db frame was lost or score was changed? Should probably try another search for any frame.
return( false );
}
$imageData = $this->getImageSrc( $frame, $scale, false, $overwrite );
if ( ! $imageData ) {
return ( false );
}
$thumbData = $frame;
$thumbData['Path'] = $imageData['thumbPath'];
}
$thumbData = $frame;
$thumbData['Path'] = $imageData['thumbPath'];
$thumbData['Width'] = (int)$thumbWidth;
$thumbData['Height'] = (int)$thumbHeight;

View File

@ -12,6 +12,7 @@ public $defaults = array(
'AutoArchive' => 0,
'AutoVideo' => 0,
'AutoMessage' => 0,
'UpdateDiskSpace' => 0,
'Background' => 0,
'Concurrent' => 0,
'limit' => 100,

View File

@ -148,6 +148,7 @@ Warning("Addterm");
$sql .= ', AutoExecute = '. ( !empty($_REQUEST['filter']['AutoExecute']) ? 1 : 0);
$sql .= ', AutoExecuteCmd = '.dbEscape($_REQUEST['filter']['AutoExecuteCmd']);
$sql .= ', AutoDelete = '. ( !empty($_REQUEST['filter']['AutoDelete']) ? 1 : 0);
$sql .= ', UpdateDiskSpace = '. ( !empty($_REQUEST['filter']['UpdateDiskSpace']) ? 1 : 0);
$sql .= ', Background = '. ( !empty($_REQUEST['filter']['Background']) ? 1 : 0);
$sql .= ', Concurrent = '. ( !empty($_REQUEST['filter']['Concurrent']) ? 1 : 0);

View File

@ -127,10 +127,10 @@ function dbQuery( $sql, $params=NULL ) {
} else {
$result = $dbConn->query( $sql );
}
//if ( $params )
//Warning("SQL: $sql" . implode(',',$params));
//else
//Warning("SQL: $sql" );
if ( $params )
Warning("SQL: $sql" . implode(',',$params));
else
Warning("SQL: $sql" );
} catch(PDOException $e) {
Error( "SQL-ERR '".$e->getMessage()."', statement was '".$sql."' params:" . implode(',',$params) );
}

View File

@ -1057,10 +1057,17 @@ function parseSort( $saveToSession=false, $querySep='&amp;' ) {
$sortColumn = 'E.Cause';
break;
case 'DateTime' :
$_REQUEST['sort_field'] = 'StartTime';
$sortColumn = 'E.StartTime';
break;
case 'DiskSpace' :
$sortColumn = 'E.DiskSpace';
break;
case 'StartTime' :
$sortColumn = 'E.StartTime';
break;
case 'EndTime' :
$sortColumn = 'E.EndTime';
break;
case 'Length' :
$sortColumn = 'E.Length';
break;
@ -1126,6 +1133,7 @@ function parseFilter( &$filter, $saveToSession=false, $querySep='&amp;' ) {
case 'ServerId':
$filter['sql'] .= 'M.ServerId';
break;
# Unspecified start or end, so assume start, this is to support legacy filters
case 'DateTime':
$filter['sql'] .= 'E.StartTime';
break;
@ -1138,8 +1146,35 @@ function parseFilter( &$filter, $saveToSession=false, $querySep='&amp;' ) {
case 'Weekday':
$filter['sql'] .= 'weekday( E.StartTime )';
break;
# Starting Time
case 'StartDateTime':
$filter['sql'] .= 'E.StartTime';
break;
case 'StartDate':
$filter['sql'] .= 'to_days( E.StartTime )';
break;
case 'StartTime':
$filter['sql'] .= 'extract( hour_second from E.StartTime )';
break;
case 'StartWeekday':
$filter['sql'] .= 'weekday( E.StartTime )';
break;
# Ending Time
case 'EndDateTime':
$filter['sql'] .= 'E.EndTime';
break;
case 'EndDate':
$filter['sql'] .= 'to_days( E.EndTime )';
break;
case 'EndTime':
$filter['sql'] .= 'extract( hour_second from E.EndTime )';
break;
case 'EndWeekday':
$filter['sql'] .= 'weekday( E.EndTime )';
break;
case 'Id':
case 'Name':
case 'DiskSpace':
case 'MonitorId':
case 'StorageId':
case 'Length':
@ -1853,7 +1888,8 @@ function logState() {
Logger::WARNING => array( ZM_LOG_ALERT_WAR_COUNT, ZM_LOG_ALARM_WAR_COUNT ),
);
$sql = "select Level, count(Level) as LevelCount from Logs where Level < ".Logger::INFO." and TimeKey > unix_timestamp(now() - interval ".ZM_LOG_CHECK_PERIOD." second) group by Level order by Level asc";
# This is an expensive request, as it has to hit every row of the Logs Table
$sql = 'SELECT Level, COUNT(Level) AS LevelCount FROM Logs WHERE Level < '.Logger::INFO.' AND TimeKey > unix_timestamp(now() - interval '.ZM_LOG_CHECK_PERIOD.' second) GROUP BY Level ORDER BY Level ASC';
$counts = dbFetchAll( $sql );
foreach ( $counts as $count ) {

View File

@ -116,8 +116,11 @@ $SLANG = array(
'AttrArchiveStatus' => 'Archive Status',
'AttrAvgScore' => 'Avg. Score',
'AttrCause' => 'Cause',
'AttrDate' => 'Date',
'AttrDateTime' => 'Date/Time',
'AttrStartDate' => 'Start Date',
'AttrEndDate' => 'End Date',
'AttrStartDateTime' => 'Start Date/Time',
'AttrEndDateTime' => 'End Date/Time',
'AttrDiskSpace' => 'Disk Space',
'AttrDiskBlocks' => 'Disk Blocks',
'AttrDiskPercent' => 'Disk Percent',
'AttrDuration' => 'Duration',
@ -132,9 +135,11 @@ $SLANG = array(
'AttrName' => 'Name',
'AttrNotes' => 'Notes',
'AttrSystemLoad' => 'System Load',
'AttrTime' => 'Time',
'AttrStartTime' => 'Start Time',
'AttrEndTime' => 'End Time',
'AttrTotalScore' => 'Total Score',
'AttrWeekday' => 'Weekday',
'AttrStartWeekday' => 'Start Weekday',
'AttrEndWeekday' => 'End Weekday',
'Auto' => 'Auto',
'AutoStopTimeout' => 'Auto Stop Timeout',
'Available' => 'Available',
@ -334,6 +339,7 @@ $SLANG = array(
'Ffmpeg' => 'Ffmpeg',
'File' => 'File',
'FilterArchiveEvents' => 'Archive all matches',
'FilterUpdateDiskSpace' => 'Update used disk space',
'FilterDeleteEvents' => 'Delete all matches',
'FilterEmailEvents' => 'Email details of all matches',
'FilterExecuteEvents' => 'Execute command on all matches',
@ -413,6 +419,7 @@ $SLANG = array(
'LimitResultsPre' => 'Limit to first', // This is used at the beginning of the phrase 'Limit to first N results only'
'LinkedMonitors' => 'Linked Monitors',
'List' => 'List',
'ListMatches' => 'List Matches',
'Load' => 'Load',
'Local' => 'Local',
'Log' => 'Log',

View File

@ -194,9 +194,19 @@ ZoneMinder requires Javascript. Please enable Javascript in your browser for thi
<li><a href="?view=console"><?php echo translate('Console') ?></a></li>
<?php if ( canView( 'System' ) ) { ?>
<li><a href="?view=options"><?php echo translate('Options') ?></a></li>
<li><?php if ( logToDatabase() > Logger::NOLOG ) { ?> <?php echo makePopupLink( '?view=log', 'zmLog', 'log', '<span class="'.logState().'">'.translate('Log').'</span>' ) ?><?php } ?></li>
<?php } ?>
<?php if ( ZM_OPT_X10 && canView( 'Devices' ) ) { ?>
<li>
<?php
if ( logToDatabase() > Logger::NOLOG ) {
if ( ! ZM_RUN_AUDIT ) {
# zmaudit can clean the logs, but if we aren't running it, then we should clecan them regularly
dbQuery("DELETE FROM Logs WHERE TimeKey < NOW()-to_days('".ZM_LOG_DATABASE_LIMIT."')");
}
echo makePopupLink( '?view=log', 'zmLog', 'log', '<span class="'.logState().'">'.translate('Log').'</span>' );
}
} // end if canview(System)
?></li>
<?php
if ( ZM_OPT_X10 && canView( 'Devices' ) ) { ?>
<li><a href="?view=devices">Devices</a></li>
<?php } ?>
<li><a href="?view=groups"<?php echo $view=='groups'?' class="selected"':''?>><?php echo translate('Groups') ?></a></li>

View File

@ -258,6 +258,17 @@ echo htmlSelect( 'StorageFilter', array(''=>'All')+$StorageById, (isset($_SESSIO
<?php
}
?>
<span class="StatusFilter"><label><?php echo translate('Status')?>:</label>
<?php
$status_options = array(
''=>'All',
'Unknown' => translate('Unknown'),
'NotRunning' => translate('NotRunning'),
'Running' => translate('Running'),
);
echo htmlSelect( 'StatusFilter', $status_options, ( isset($_SESSION['StatusFilter']) ? $_SESSION['StatusFilter'] : '' ), array('onchange'=>'changeFilter(this);') );
?>
</span>
</div>
<div class="container-fluid">

View File

@ -69,10 +69,14 @@ $attrTypes = array(
'Name' => translate('AttrName'),
'Cause' => translate('AttrCause'),
'Notes' => translate('AttrNotes'),
'DateTime' => translate('AttrDateTime'),
'Date' => translate('AttrDate'),
'Time' => translate('AttrTime'),
'Weekday' => translate('AttrWeekday'),
'StartDateTime' => translate('AttrStartDateTime'),
'StartDate' => translate('AttrStartDate'),
'StartTime' => translate('AttrStartTime'),
'StartWeekday' => translate('AttrStartWeekday'),
'EndDateTime' => translate('AttrEndDateTime'),
'EndDate' => translate('AttrEndDate'),
'EndTime' => translate('AttrEndTime'),
'EndWeekday' => translate('AttrEndWeekday'),
'Length' => translate('AttrDuration'),
'Frames' => translate('AttrFrames'),
'AlarmFrames' => translate('AttrAlarmFrames'),
@ -80,8 +84,9 @@ $attrTypes = array(
'AvgScore' => translate('AttrAvgScore'),
'MaxScore' => translate('AttrMaxScore'),
'Archived' => translate('AttrArchiveStatus'),
'DiskPercent' => translate('AttrDiskPercent'),
'DiskBlocks' => translate('AttrDiskBlocks'),
'DiskPercent' => translate('AttrDiskPercent'),
'DiskSpace' => translate('AttrDiskSpace'),
'SystemLoad' => translate('AttrSystemLoad'),
'StorageId' => translate('AttrStorageArea'),
'ServerId' => translate('AttrServer'),
@ -297,18 +302,19 @@ if ( count($terms) == 0 ) {
<label for="filter[Query][sort_field]"><?php echo translate('SortBy') ?></label>
<?php
$sort_fields = array(
'Id' => translate('AttrId'),
'Name' => translate('AttrName'),
'Cause' => translate('AttrCause'),
'Notes' => translate('AttrNotes'),
'MonitorName' => translate('AttrMonitorName'),
'DateTime' => translate('AttrDateTime'),
'Length' => translate('AttrDuration'),
'Frames' => translate('AttrFrames'),
'AlarmFrames' => translate('AttrAlarmFrames'),
'TotScore' => translate('AttrTotalScore'),
'AvgScore' => translate('AttrAvgScore'),
'MaxScore' => translate('AttrMaxScore'),
'Id' => translate('AttrId'),
'Name' => translate('AttrName'),
'Cause' => translate('AttrCause'),
'DiskSpace' => translate('AttrDiskSpace'),
'Notes' => translate('AttrNotes'),
'MonitorName' => translate('AttrMonitorName'),
'StartDateTime' => translate('AttrStartDateTime'),
'Length' => translate('AttrDuration'),
'Frames' => translate('AttrFrames'),
'AlarmFrames' => translate('AttrAlarmFrames'),
'TotScore' => translate('AttrTotalScore'),
'AvgScore' => translate('AttrAvgScore'),
'MaxScore' => translate('AttrMaxScore'),
);
echo htmlSelect( 'filter[Query][sort_field]', $sort_fields, $filter->sort_field() );
$sort_dirns = array(
@ -332,6 +338,9 @@ echo htmlSelect( 'filter[Query][sort_asc]', $sort_dirns, $filter->sort_asc() );
<label><?php echo translate('FilterArchiveEvents') ?></label>
<input type="checkbox" name="filter[AutoArchive]" value="1"<?php if ( !empty($filter->AutoArchive()) ) { ?> checked="checked"<?php } ?> onclick="updateButtons( this )"/>
</p>
<p><label><?php echo translate('FilterUpdateDiskSpace') ?></label>
<input type="checkbox" name="filter[UpdateDiskSpace]" value="1"<?php echo empty($filter->UpdateDiskSpace()) ? '' : ' checked="checked"' ?> onclick="updateButtons(this);"/>
</p>
<?php
if ( ZM_OPT_FFMPEG ) {
?>
@ -387,7 +396,7 @@ if ( ZM_OPT_MESSAGE ) {
</div>
<hr/>
<div id="contentButtons">
<input type="submit" value="<?php echo translate('Submit') ?>" onclick="submitToEvents( this );"/>
<input type="submit" value="<?php echo translate('ListMatches') ?>" onclick="submitToEvents( this );"/>
<input type="button" name="executeButton" id="executeButton" value="<?php echo translate('Execute') ?>" onclick="executeFilter( this );"/>
<?php
if ( canEdit( 'Events' ) ) {

View File

@ -19,6 +19,8 @@ function updateButtons( element ) {
canExecute = true;
else if ( form.elements['filter[AutoDelete]'].checked )
canExecute = true;
else if ( form.elements['filter[UpdateDiskSpace]'].checked )
canExecute = true;
form.elements['executeButton'].disabled = !canExecute;
}
}

View File

@ -55,7 +55,7 @@ function validateForm( form ) {
else if ( form.elements.mid.value == 0 && monitorNames[form.elements['newMonitor[Name]'].value] )
errors[errors.length] = "<?php echo translate('DuplicateMonitorName') ?>";
if ( form.elements['newMonitor[AnalysisFPS]'].value && !(parseFloat(form.elements['newMonitor[AnalysisFPS]'].value) > 0 ) )
if ( form.elements['newMonitor[AnalysisFPSLimit]'].value && !(parseFloat(form.elements['newMonitor[AnalysisFPSLimit]'].value) > 0 ) )
errors[errors.length] = "<?php echo translate('BadAnalysisFPS') ?>";
if ( form.elements['newMonitor[MaxFPS]'].value && !(parseFloat(form.elements['newMonitor[MaxFPS]'].value) > 0 ) )
errors[errors.length] = "<?php echo translate('BadMaxFPS') ?>";

View File

@ -84,7 +84,11 @@ function imagedone( obj, monId, success ) {
if ( ! success ) {
// if we had a failrue queue up the no-data image
//loadImage2Monitor(monId,"no data"); // leave the staged URL if there is one, just ignore it here.
loadNoData( monId );
if ( liveMode ) {
writeText( monId, "Camera Offline" );
} else {
writeText( monId, "No Data" );
}
} else {
if ( monitorLoadingStageURL[monId] == "" ) {
console.log("Not showing image for " + monId );

View File

@ -122,7 +122,7 @@ if ( ! $monitor ) {
'FrameSkip' => 0,
'MotionFrameSkip' => 0,
'EventPrefix' => 'Event-',
'AnalysisFPS' => '',
'AnalysisFPSLimit' => '',
'AnalysisUpdateDelay' => 0,
'MaxFPS' => '',
'AlarmMaxFPS' => '',
@ -170,8 +170,8 @@ if ( isset( $_REQUEST['newMonitor'] ) ) {
}
# What if it has less zeros? This is not robust code.
if ( $monitor->AnalysisFPS() == '0.00' )
$monitor->AnalysisFPS( '' );
if ( $monitor->AnalysisFPSLimit() == '0.00' )
$monitor->AnalysisFPSLimit( '' );
if ( $monitor->MaxFPS() == '0.00' )
$monitor->MaxFPS( '' );
if ( $monitor->AlarmMaxFPS() == '0.00' )
@ -538,7 +538,7 @@ if ( $tab != 'general' ) {
<input type="hidden" name="newMonitor[Enabled]" value="<?php echo validHtmlStr($monitor->Enabled()) ?>"/>
<input type="hidden" name="newMonitor[RefBlendPerc]" value="<?php echo validHtmlStr($monitor->RefBlendPerc()) ?>"/>
<input type="hidden" name="newMonitor[AlarmRefBlendPerc]" value="<?php echo validHtmlStr($monitor->AlarmRefBlendPerc()) ?>"/>
<input type="hidden" name="newMonitor[AnalysisFPS]" value="<?php echo validHtmlStr($monitor->AnalysisFPS()) ?>"/>
<input type="hidden" name="newMonitor[AnalysisFPSLimit]" value="<?php echo validHtmlStr($monitor->AnalysisFPSLimit()) ?>"/>
<input type="hidden" name="newMonitor[MaxFPS]" value="<?php echo validHtmlStr($monitor->MaxFPS()) ?>"/>
<input type="hidden" name="newMonitor[AlarmMaxFPS]" value="<?php echo validHtmlStr($monitor->AlarmMaxFPS()) ?>"/>
<?php
@ -727,7 +727,7 @@ switch ( $tab ) {
</select>
</td>
</tr>
<tr><td><?php echo translate('AnalysisFPS') ?></td><td><input type="text" name="newMonitor[AnalysisFPS]" value="<?php echo validHtmlStr($monitor->AnalysisFPS()) ?>" size="6"/></td></tr>
<tr><td><?php echo translate('AnalysisFPS') ?></td><td><input type="text" name="newMonitor[AnalysisFPSLimit]" value="<?php echo validHtmlStr($monitor->AnalysisFPSLimit()) ?>" size="6"/></td></tr>
<?php
if ( $monitor->Type() != 'Local' && $monitor->Type() != 'File' && $monitor->Type() != 'NVSocket' ) {
?>