Merge branch 'rtsp_server'

This commit is contained in:
Isaac Connor 2021-01-26 14:22:10 -05:00
commit 4dcce4ac95
110 changed files with 7268 additions and 3714 deletions

View File

@ -59,7 +59,7 @@ if(NOT HOST_OS)
endif(NOT HOST_OS) endif(NOT HOST_OS)
set (CMAKE_CXX_STANDARD 11) set (CMAKE_CXX_STANDARD 11)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
# Default CLFAGS and CXXFLAGS: # Default CLFAGS and CXXFLAGS:
set(CMAKE_C_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2") set(CMAKE_C_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2")
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2") set(CMAKE_CXX_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2")
@ -69,6 +69,8 @@ set(CMAKE_C_FLAGS_OPTIMISED "-Wall -D__STDC_CONSTANT_MACROS -O3")
set(CMAKE_CXX_FLAGS_OPTIMISED "-Wall -D__STDC_CONSTANT_MACROS -O3") set(CMAKE_CXX_FLAGS_OPTIMISED "-Wall -D__STDC_CONSTANT_MACROS -O3")
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
set (CMAKE_CXX_STANDARD 11)
# GCC below 6.0 doesn't support __target__("fpu=neon") attribute, required for compiling ARM Neon code, otherwise compilation fails. # GCC below 6.0 doesn't support __target__("fpu=neon") attribute, required for compiling ARM Neon code, otherwise compilation fails.
# Must use -mfpu=neon compiler flag instead, but only do that for processors that support neon, otherwise strip the neon code alltogether, # Must use -mfpu=neon compiler flag instead, but only do that for processors that support neon, otherwise strip the neon code alltogether,
@ -201,6 +203,8 @@ set(ZM_NO_X10 "OFF" CACHE BOOL
set(ZM_ONVIF "ON" CACHE BOOL set(ZM_ONVIF "ON" CACHE BOOL
"Set to ON to enable basic ONVIF support. This is EXPERIMENTAL and may not "Set to ON to enable basic ONVIF support. This is EXPERIMENTAL and may not
work with all cameras claiming to be ONVIF compliant. default: ON") work with all cameras claiming to be ONVIF compliant. default: ON")
set(ZM_NO_RTSPSERVER "OFF" CACHE BOOL
"Set to ON to skip live555 checks and force building ZM without rtsp server support. default: OFF")
set(ZM_PERL_MM_PARMS INSTALLDIRS=vendor NO_PACKLIST=1 NO_PERLLOCAL=1 CACHE STRING set(ZM_PERL_MM_PARMS INSTALLDIRS=vendor NO_PACKLIST=1 NO_PERLLOCAL=1 CACHE STRING
"By default, ZoneMinder's Perl modules are installed into the Vendor folders, "By default, ZoneMinder's Perl modules are installed into the Vendor folders,
as defined by your installation of Perl. You can change that here. Consult Perl's as defined by your installation of Perl. You can change that here. Consult Perl's
@ -708,6 +712,21 @@ endif(NOT ZM_NO_LIBVNC)
##set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}") ##set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}")
#list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}") #list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}")
#endif() #endif()
if(NOT ZM_NO_RTSPSERVER)
find_package(Live555)
if(Live555_FOUND)
include_directories(${Live555_INCLUDE_DIRS})
set(CMAKE_REQUIRED_INCLUDES "${Live555_INCLUDE_DIRS}")
list(APPEND ZM_BIN_LIBS "${Live555_LIBRARIES}")
set(HAVE_RTSP_SERVER 1)
else(Live555_FOUND)
set(HAVE_RTSP_SERVER 0)
endif(Live555_FOUND)
else(NOT ZM_NO_RTSPSERVER)
set(HAVE_RTSP_SERVER 0)
endif(NOT ZM_NO_RTSPSERVER)
# #
# *** END OF LIBRARY CHECKS *** # *** END OF LIBRARY CHECKS ***

View File

@ -0,0 +1,75 @@
# Try to find Live555 libraries
# Once done this will define
# Live555_FOUND
# Live555_INCLUDE_DIRS
# Live555_LIBRARIES
if (NOT Live555_FOUND)
set(_Live555_FOUND ON)
foreach (library liveMedia BasicUsageEnvironment Groupsock UsageEnvironment)
string(TOLOWER ${library} lowercase_library)
find_path(Live555_${library}_INCLUDE_DIR
NAMES
${library}.hh
${lowercase_library}.hh
PATHS
${Live555_ROOT}/${library}/include
${Live555_ROOT}/live/${library}/include
/usr/include/${library}
/usr/local/include/${library}
/usr/include/${lowercase_library}
/usr/local/include/${lowercase_library}
)
if (Live555_${library}_INCLUDE_DIR)
list(APPEND _Live555_INCLUDE_DIRS ${Live555_${library}_INCLUDE_DIR})
else()
set(_Live555_FOUND OFF)
endif ()
foreach (mode DEBUG RELEASE)
find_library(Live555_${library}_LIBRARY_${mode}
NAMES
${library}
${lowercase_library}
PATHS
${Live555_ROOT}/lib/${mode}
${Live555_ROOT}/${library}
)
if (Live555_${library}_LIBRARY_${mode})
if (${mode} STREQUAL RELEASE)
list(APPEND _Live555_LIBRARIES optimized ${Live555_${library}_LIBRARY_${mode}})
elseif (${mode} STREQUAL DEBUG)
list(APPEND _Live555_LIBRARIES debug ${Live555_${library}_LIBRARY_${mode}})
else ()
MESSAGE(STATUS no)
list(APPEND _Live555_LIBRARIES ${Live555_${library}_LIBRARY_${mode}})
endif()
else()
set(_Live555_FOUND OFF)
endif ()
endforeach ()
endforeach ()
if (_Live555_FOUND)
set(Live555_INCLUDE_DIRS ${_Live555_INCLUDE_DIRS} CACHE INTERNAL "")
set(Live555_LIBRARIES ${_Live555_LIBRARIES} CACHE INTERNAL "")
set(Live555_FOUND ${_Live555_FOUND} CACHE BOOL "" FORCE)
endif()
include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LOGGING_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(Live555 DEFAULT_MSG Live555_INCLUDE_DIRS Live555_LIBRARIES Live555_FOUND)
# Tell cmake GUIs to ignore the "local" variables.
mark_as_advanced(Live555_INCLUDE_DIRS Live555_LIBRARIES Live555_FOUND)
endif (NOT Live555_FOUND)
if (Live555_FOUND)
message(STATUS "Found live555")
endif()

View File

@ -481,7 +481,8 @@ CREATE TABLE `Monitors` (
`DecoderHWAccelDevice` varchar(255), `DecoderHWAccelDevice` varchar(255),
`SaveJPEGs` TINYINT NOT NULL DEFAULT '3' , `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' ,
`VideoWriter` TINYINT NOT NULL DEFAULT '0', `VideoWriter` TINYINT NOT NULL DEFAULT '0',
`OutputCodec` enum('h264','mjpeg','mpeg1','mpeg2'), `OutputCodec` int(10) unsigned NOT NULL default 0,
`Encoder` enum('auto','h264','libx264','h264_omx','h264_vaapi','mjpeg','mpeg1','mpeg2'),
`OutputContainer` enum('auto','mp4','mkv'), `OutputContainer` enum('auto','mp4','mkv'),
`EncoderParameters` TEXT, `EncoderParameters` TEXT,
`RecordAudio` TINYINT NOT NULL DEFAULT '0', `RecordAudio` TINYINT NOT NULL DEFAULT '0',

View File

@ -176,8 +176,10 @@ BEGIN
WHERE Id=OLD.MonitorId; WHERE Id=OLD.MonitorId;
END IF; END IF;
END IF; END IF;
ELSEIF ( NEW.Archived AND diff ) THEN ELSE
UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; IF ( NEW.Archived AND diff ) THEN
UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
END IF;
END IF; END IF;
IF ( diff ) THEN IF ( diff ) THEN
@ -185,7 +187,6 @@ BEGIN
END IF; END IF;
END; END;
// //
delimiter ; delimiter ;

View File

@ -10,3 +10,18 @@ SET @s = (SELECT IF(
PREPARE stmt FROM @s; PREPARE stmt FROM @s;
EXECUTE stmt; EXECUTE stmt;
ALTER TABLE `Monitors` MODIFY `OutputCodec` INT UNSIGNED default 0;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'Encoder'
) > 0,
"SELECT 'Column Encoder already exists in Monitors'",
"ALTER TABLE `Monitors` ADD `Encoder` enum('auto','h264','h264_omx','mjpeg','mpeg1','mpeg2') AFTER `OutputCodec`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

1
db/zm_update-1.35.17.sql Normal file
View File

@ -0,0 +1 @@
ALTER TABLE `Monitors` MODIFY `Encoder` enum('auto','h264','libx264', 'h264_omx', 'h264_vaapi', 'mjpeg','mpeg1','mpeg2');

View File

@ -28,7 +28,7 @@
%global _hardened_build 1 %global _hardened_build 1
Name: zoneminder Name: zoneminder
Version: 1.35.16 Version: 1.35.17
Release: 1%{?dist} Release: 1%{?dist}
Summary: A camera monitoring and analysis tool Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons Group: System Environment/Daemons

View File

@ -84,6 +84,8 @@ if [ "$1" = "configure" ]; then
else else
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)." echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)."
fi fi
echo "Done Updating; starting ZoneMinder."
deb-systemd-invoke restart zoneminder.service deb-systemd-invoke restart zoneminder.service
fi fi

View File

@ -947,6 +947,19 @@ our @options = (
type => $types{integer}, type => $types{integer},
category => 'network', category => 'network',
}, },
{
name => 'ZM_MIN_RTSP_PORT',
default => '',
description => 'Start of port range to contact for RTSP streaming video.',
help => q`
The beginng of a port range that will be used to offer
RTSP streaming of live captured video.
Each monitor will use this value plus the Monitor Id to stream
content. So a value of 2000 here will cause a stream for Monitor 1 to
hit port 2001.`,
type => $types{integer},
category => 'network',
},
{ {
name => 'ZM_MIN_RTP_PORT', name => 'ZM_MIN_RTP_PORT',
default => '40200', default => '40200',

View File

@ -147,6 +147,8 @@ our $mem_data = {
last_write_index => { type=>'uint32', seq=>$mem_seq++ }, last_write_index => { type=>'uint32', seq=>$mem_seq++ },
last_read_index => { type=>'uint32', seq=>$mem_seq++ }, last_read_index => { type=>'uint32', seq=>$mem_seq++ },
state => { type=>'uint32', seq=>$mem_seq++ }, state => { type=>'uint32', seq=>$mem_seq++ },
capture_fps => { type=>'double', seq=>$mem_seq++ },
analysis_fps => { type=>'double', seq=>$mem_seq++ },
last_event => { type=>'uint64', seq=>$mem_seq++ }, last_event => { type=>'uint64', seq=>$mem_seq++ },
action => { type=>'uint32', seq=>$mem_seq++ }, action => { type=>'uint32', seq=>$mem_seq++ },
brightness => { type=>'int32', seq=>$mem_seq++ }, brightness => { type=>'int32', seq=>$mem_seq++ },
@ -214,7 +216,7 @@ sub zmMemInit {
|| $member_data->{type} eq 'bool4' || $member_data->{type} eq 'bool4'
) { ) {
$member_data->{size} = $member_data->{align} = 4; $member_data->{size} = $member_data->{align} = 4;
} elsif ($member_data->{type} eq 'int16' } elsif ( $member_data->{type} eq 'int16'
|| $member_data->{type} eq 'uint16' || $member_data->{type} eq 'uint16'
) { ) {
$member_data->{size} = $member_data->{align} = 2; $member_data->{size} = $member_data->{align} = 2;
@ -223,6 +225,8 @@ sub zmMemInit {
|| $member_data->{type} eq 'bool1' || $member_data->{type} eq 'bool1'
) { ) {
$member_data->{size} = $member_data->{align} = 1; $member_data->{size} = $member_data->{align} = 1;
} elsif ( $member_data->{type} eq 'double' ) {
$member_data->{size} = $member_data->{align} = 8;
} elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) { } elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) {
$member_data->{size} = $1; $member_data->{size} = $1;
$member_data->{align} = 1; $member_data->{align} = 1;
@ -236,7 +240,7 @@ sub zmMemInit {
$offset += ($member_data->{align} - ($offset%$member_data->{align})); $offset += ($member_data->{align} - ($offset%$member_data->{align}));
} }
$member_data->{offset} = $offset; $member_data->{offset} = $offset;
$offset += $member_data->{size} $offset += $member_data->{size};
} }
$section_data->{size} = $offset - $section_data->{offset}; $section_data->{size} = $offset - $section_data->{offset};
} }
@ -322,7 +326,7 @@ sub zmMemRead {
my $size = $mem_data->{$section}->{contents}->{$element}->{size}; my $size = $mem_data->{$section}->{contents}->{$element}->{size};
if (!defined $offset || !defined $type || !defined $size) { if (!defined $offset || !defined $type || !defined $size) {
Error('Invalid field:'.$field.' setting to undef and exiting zmMemRead'); Error('Invalid field:'.$field.' setting to undef and exiting zmMemRead offset:'.$offset.' type:'.$type.' size:'.$size);
zmMemInvalidate($monitor); zmMemInvalidate($monitor);
return undef; return undef;
} }
@ -355,7 +359,9 @@ sub zmMemRead {
} elsif ( $type eq 'int8' ) { } elsif ( $type eq 'int8' ) {
( $value ) = unpack('c', $data); ( $value ) = unpack('c', $data);
} elsif ( $type eq 'uint8' || $type eq 'bool1' ) { } elsif ( $type eq 'uint8' || $type eq 'bool1' ) {
( $value ) = unpack( 'C', $data ); ( $value ) = unpack('C', $data);
} elsif ( $type eq 'double' ) {
( $value ) = unpack('d', $data);
} elsif ( $type =~ /^int8\[\d+\]$/ ) { } elsif ( $type =~ /^int8\[\d+\]$/ ) {
( $value ) = unpack('Z'.$size, $data); ( $value ) = unpack('Z'.$size, $data);
} elsif ( $type =~ /^uint8\[\d+\]$/ ) { } elsif ( $type =~ /^uint8\[\d+\]$/ ) {

View File

@ -92,7 +92,7 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
my @daemons = ( my @daemons = (
'zmc', 'zmc',
'zma', #'zma',
'zmfilter.pl', 'zmfilter.pl',
'zmaudit.pl', 'zmaudit.pl',
'zmtrigger.pl', 'zmtrigger.pl',
@ -239,7 +239,7 @@ use Sys::MemInfo qw(totalmem freemem totalswap freeswap);
use ZoneMinder::Server qw(CpuLoad); use ZoneMinder::Server qw(CpuLoad);
#use Data::Dumper; #use Data::Dumper;
use constant KILL_DELAY => 60; # seconds to wait between sending TERM and sending KILL use constant KILL_DELAY => 10; # seconds to wait between sending TERM and sending KILL
our %cmd_hash; our %cmd_hash;
our %pid_hash; our %pid_hash;

View File

@ -216,9 +216,6 @@ if ( $command =~ /^(?:start|restart)$/ ) {
} else { } else {
runCommand("zmdc.pl start zmc -m $monitor->{Id}"); runCommand("zmdc.pl start zmc -m $monitor->{Id}");
} }
if ( $monitor->{Function} ne 'Monitor' ) {
runCommand("zmdc.pl start zma -m $monitor->{Id}");
}
if ( $Config{ZM_OPT_CONTROL} ) { if ( $Config{ZM_OPT_CONTROL} ) {
if ( $monitor->{Controllable} ) { if ( $monitor->{Controllable} ) {
runCommand("zmdc.pl start zmcontrol.pl --id $monitor->{Id}"); runCommand("zmdc.pl start zmcontrol.pl --id $monitor->{Id}");

View File

@ -123,7 +123,7 @@ while( 1 ) {
) ? (3/$monitor->{MaxFPS}) ) ? (3/$monitor->{MaxFPS})
: $Config{ZM_WATCH_MAX_DELAY} : $Config{ZM_WATCH_MAX_DELAY}
; ;
my $image_delay = $now-$capture_time; my $image_delay = $now - $capture_time;
Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay"); Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay");
if ( $image_delay > $max_image_delay ) { if ( $image_delay > $max_image_delay ) {
Warning("Restarting capture daemon for " Warning("Restarting capture daemon for "
@ -138,9 +138,6 @@ while( 1 ) {
} }
if ( $restart ) { if ( $restart ) {
# Because zma depends on zmc, and zma can hold the shm in place, preventing zmc from using the space in /dev/shm,
# we need to stop zma before restarting zmc.
runCommand("zmdc.pl stop zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor';
my $command; my $command;
if ( $monitor->{Type} eq 'Local' ) { if ( $monitor->{Type} eq 'Local' ) {
$command = "zmdc.pl restart zmc -d $monitor->{Device}"; $command = "zmdc.pl restart zmc -d $monitor->{Device}";
@ -148,7 +145,6 @@ while( 1 ) {
$command = "zmdc.pl restart zmc -m $monitor->{Id}"; $command = "zmdc.pl restart zmc -m $monitor->{Id}";
} }
runCommand($command); runCommand($command);
runCommand("zmdc.pl start zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor';
} elsif ( $monitor->{Function} ne 'Monitor' ) { } elsif ( $monitor->{Function} ne 'Monitor' ) {
# Now check analysis daemon # Now check analysis daemon
$restart = 0; $restart = 0;
@ -160,7 +156,7 @@ while( 1 ) {
Error("Error reading shared data for $$monitor{Id} $$monitor{Name}"); Error("Error reading shared data for $$monitor{Id} $$monitor{Name}");
} elsif ( !$image_time ) { } elsif ( !$image_time ) {
# We can't get the last capture time so can't be sure it's died. # We can't get the last capture time so can't be sure it's died.
$restart = 1; #$restart = 1;
Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero.");
} else { } else {
@ -181,8 +177,13 @@ while( 1 ) {
} }
if ( $restart ) { if ( $restart ) {
Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}"); Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}\n");
my $command = 'zmdc.pl restart zma -m '.$monitor->{Id}; my $command;
if ( $monitor->{Type} eq 'Local' ) {
$command = "zmdc.pl restart zmc -d $monitor->{Device}";
} else {
$command = "zmdc.pl restart zmc -m $monitor->{Id}";
}
runCommand($command); runCommand($command);
} # end if restart } # end if restart
} # end if check analysis daemon } # end if check analysis daemon

View File

@ -3,16 +3,78 @@
# Create files from the .in files # Create files from the .in files
configure_file(zm_config_data.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config_data.h" @ONLY) configure_file(zm_config_data.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config_data.h" @ONLY)
# Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc) # Group together all the source files that are used by all the binaries (zmc, zmu, zms etc)
set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_frame.cpp zm_eventstream.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_input.cpp zm_ffmpeg_camera.cpp zm_group.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_libvnc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_font.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_nvsocket.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_swscale.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp zm_fifo.cpp zm_crypt.cpp) set(ZM_BIN_SRC_FILES
zm_analysis_thread.cpp
zm_box.cpp
zm_buffer.cpp
zm_camera.cpp
zm_comms.cpp
zm_config.cpp
zm_coord.cpp
zm_curl_camera.cpp
zm_crypt.cpp
zm.cpp
zm_db.cpp
zm_logger.cpp
zm_event.cpp
zm_eventstream.cpp
zm_exception.cpp
zm_fifo.cpp
zm_file_camera.cpp
zm_font.cpp
zm_frame.cpp
zm_group.cpp
zm_image.cpp
zm_jpeg.cpp
zm_libvlc_camera.cpp
zm_libvnc_camera.cpp
zm_local_camera.cpp
zm_monitor.cpp
zm_monitorstream.cpp
zm_ffmpeg.cpp
zm_ffmpeg_camera.cpp
zm_ffmpeg_input.cpp
zm_mpeg.cpp
zm_packet.cpp
zm_packetqueue.cpp
zm_poly.cpp
zm_regexp.cpp
zm_remote_camera.cpp
zm_remote_camera_http.cpp
zm_remote_camera_nvsocket.cpp
zm_remote_camera_rtsp.cpp
zm_rtp.cpp
zm_rtp_ctrl.cpp
zm_rtp_data.cpp
zm_rtp_source.cpp
zm_rtsp.cpp
zm_rtsp_auth.cpp
zm_rtsp_server_thread.cpp
zm_rtsp_server_adts_source.cpp
zm_rtsp_server_h264_device_source.cpp
zm_rtsp_server_device_source.cpp
zm_rtsp_server_server_media_subsession.cpp
zm_rtsp_server_unicast_server_media_subsession.cpp
zm_sdp.cpp
zm_signal.cpp
zm_stream.cpp
zm_swscale.cpp
zm_thread.cpp
zm_time.cpp
zm_timer.cpp
zm_user.cpp
zm_utils.cpp
zm_video.cpp
zm_videostore.cpp
zm_zone.cpp
zm_storage.cpp)
# A fix for cmake recompiling the source files for every target. # A fix for cmake recompiling the source files for every target.
add_library(zm STATIC ${ZM_BIN_SRC_FILES}) add_library(zm STATIC ${ZM_BIN_SRC_FILES})
link_directories(libbcrypt) link_directories(libbcrypt)
add_executable(zmc zmc.cpp) add_executable(zmc zmc.cpp)
add_executable(zma zma.cpp)
add_executable(zmu zmu.cpp) add_executable(zmu zmu.cpp)
add_executable(zms zms.cpp) add_executable(zms zms.cpp)
@ -21,16 +83,15 @@ include_directories(libbcrypt/include/bcrypt)
include_directories(jwt-cpp/include/jwt-cpp) include_directories(jwt-cpp/include/jwt-cpp)
target_link_libraries(zmc zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS}) target_link_libraries(zmc zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS})
target_link_libraries(zma zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS})
target_link_libraries(zmu zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt) target_link_libraries(zmu zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt)
target_link_libraries(zms zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt) target_link_libraries(zms zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt)
# Generate man files for the binaries destined for the bin folder # Generate man files for the binaries destined for the bin folder
FOREACH(CBINARY zma zmc zmu) FOREACH(CBINARY zmc zmu)
POD2MAN(${CMAKE_CURRENT_SOURCE_DIR}/${CBINARY}.cpp ${CBINARY} 8 ${ZM_MANPAGE_DEST_PREFIX}) POD2MAN(${CMAKE_CURRENT_SOURCE_DIR}/${CBINARY}.cpp ${CBINARY} 8 ${ZM_MANPAGE_DEST_PREFIX})
ENDFOREACH(CBINARY zma zmc zmu) ENDFOREACH(CBINARY zmc zmu)
install(TARGETS zmc zma zmu RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(TARGETS zmc zmu RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(TARGETS zms RUNTIME DESTINATION "${ZM_CGIDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(TARGETS zms RUNTIME DESTINATION "${ZM_CGIDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(CODE "execute_process(COMMAND ln -sf zms nph-zms WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})" ) install(CODE "execute_process(COMMAND ln -sf zms nph-zms WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})" )
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nph-zms DESTINATION "${ZM_CGIDIR}") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nph-zms DESTINATION "${ZM_CGIDIR}")

View File

@ -0,0 +1,47 @@
#include "zm_analysis_thread.h"
AnalysisThread::AnalysisThread(Monitor *p_monitor) {
monitor = p_monitor;
terminate = false;
//sigemptyset(&block_set);
}
AnalysisThread::~AnalysisThread() {
Debug(2, "THREAD: deleteing analysis thread");
}
int AnalysisThread::run() {
Debug(2, "AnalysisThread::run()");
useconds_t analysis_rate = monitor->GetAnalysisRate();
unsigned int analysis_update_delay = monitor->GetAnalysisUpdateDelay();
time_t last_analysis_update_time, cur_time;
monitor->UpdateAdaptiveSkip();
last_analysis_update_time = time(0);
while ( !(terminate or zm_terminate) ) {
// Some periodic updates are required for variable capturing framerate
if ( analysis_update_delay ) {
cur_time = time(0);
if ( (unsigned int)( cur_time - last_analysis_update_time ) > analysis_update_delay ) {
analysis_rate = monitor->GetAnalysisRate();
monitor->UpdateAdaptiveSkip();
last_analysis_update_time = cur_time;
}
}
Debug(2, "Analyzing");
if ( !monitor->Analyse() ) {
Debug(2, "uSleeping for %d", (monitor->Active()?ZM_SAMPLE_RATE:ZM_SUSPENDED_RATE));
usleep(monitor->Active() ? ZM_SAMPLE_RATE : ZM_SUSPENDED_RATE);
} else if ( analysis_rate ) {
Debug(2, "uSleeping for %d", analysis_rate);
usleep(analysis_rate);
} else {
Debug(2, "Not Sleeping");
}
} // end while ! terminate
return 0;
} // end in AnalysisThread::run()

29
src/zm_analysis_thread.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef ZM_ANALYSIS_THREAD_H
#define ZM_ANALYSIS_THREAD_H
#include "zm_thread.h"
#include <signal.h>
#include "zm_monitor.h"
class AnalysisThread : public Thread {
private:
bool terminate;
sigset_t block_set;
Monitor *monitor;
public:
explicit AnalysisThread(Monitor *);
~AnalysisThread();
int run();
void stop() {
terminate = true;
}
bool stopped() const {
return terminate;
}
};
#endif

View File

@ -35,17 +35,24 @@ Camera::Camera(
bool p_record_audio bool p_record_audio
) : ) :
monitor_id(p_monitor_id), monitor_id(p_monitor_id),
monitor(nullptr),
type(p_type), type(p_type),
width(p_width), width(p_width),
height(p_height), height(p_height),
colours(p_colours), colours(p_colours),
subpixelorder(p_subpixelorder), subpixelorder(p_subpixelorder),
brightness(p_brightness), brightness(p_brightness),
hue(p_hue), hue(p_hue),
colour(p_colour), colour(p_colour),
contrast(p_contrast), contrast(p_contrast),
capture(p_capture), capture(p_capture),
record_audio(p_record_audio), record_audio(p_record_audio),
mVideoStreamId(-1),
mAudioStreamId(-1),
mVideoCodecContext(nullptr),
mAudioCodecContext(nullptr),
video_stream(nullptr),
oc(nullptr),
bytes(0) bytes(0)
{ {
linesize = width * colours; linesize = width * colours;
@ -54,8 +61,6 @@ Camera::Camera(
Debug(2, "New camera id: %d width: %d line size: %d height: %d colours: %d subpixelorder: %d capture: %d", Debug(2, "New camera id: %d width: %d line size: %d height: %d colours: %d subpixelorder: %d capture: %d",
monitor_id, width, linesize, height, colours, subpixelorder, capture); monitor_id, width, linesize, height, colours, subpixelorder, capture);
monitor = nullptr;
} }
Camera::~Camera() { Camera::~Camera() {

View File

@ -24,6 +24,7 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include "zm_image.h" #include "zm_image.h"
#include "zm_packet.h"
class Camera; class Camera;
@ -53,10 +54,29 @@ protected:
int contrast; int contrast;
bool capture; bool capture;
bool record_audio; bool record_audio;
int mVideoStreamId;
int mAudioStreamId;
AVCodecContext *mVideoCodecContext;
AVCodecContext *mAudioCodecContext;
AVStream *video_stream;
AVFormatContext *oc;
unsigned int bytes; unsigned int bytes;
public: public:
Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); Camera(
unsigned int p_monitor_id,
SourceType p_type,
unsigned int p_width,
unsigned int p_height,
int p_colours,
int p_subpixelorder,
int p_brightness,
int p_contrast,
int p_hue,
int p_colour,
bool p_capture,
bool p_record_audio
);
virtual ~Camera(); virtual ~Camera();
unsigned int getId() const { return monitor_id; } unsigned int getId() const { return monitor_id; }
@ -91,11 +111,17 @@ public:
//return (type == FFMPEG_SRC )||(type == REMOTE_SRC); //return (type == FFMPEG_SRC )||(type == REMOTE_SRC);
} }
virtual AVStream *get_VideoStream() { return nullptr; };
virtual AVStream *get_AudioStream() { return nullptr; };
virtual AVCodecContext *get_VideoCodecContext() { return nullptr; };
virtual AVCodecContext *get_AudioCodecContext() { return nullptr; };
int get_VideoStreamId() { return mVideoStreamId; };
int get_AudioStreamId() { return mAudioStreamId; };
virtual int PrimeCapture() { return 0; } virtual int PrimeCapture() { return 0; }
virtual int PreCapture() = 0; virtual int PreCapture() = 0;
virtual int Capture(Image &image) = 0; virtual int Capture(ZMPacket &p) = 0;
virtual int PostCapture() = 0; virtual int PostCapture() = 0;
virtual int CaptureAndRecord(Image &image, timeval recording, char* event_directory) = 0;
virtual int Close() = 0; virtual int Close() = 0;
}; };

View File

@ -28,6 +28,7 @@
#include <glob.h> #include <glob.h>
#include "zm_utils.h" #include "zm_utils.h"
#include "zm_config.h"
// Note that Error and Debug calls won't actually go anywhere unless you // Note that Error and Debug calls won't actually go anywhere unless you
// set the relevant ENV vars because the logger gets it's setting from the // set the relevant ENV vars because the logger gets it's setting from the

View File

@ -163,7 +163,7 @@ int cURLCamera::PreCapture() {
return( 0 ); return( 0 );
} }
int cURLCamera::Capture( Image &image ) { int cURLCamera::Capture( ZMPacket &zm_packet ) {
bool frameComplete = false; bool frameComplete = false;
/* MODE_STREAM specific variables */ /* MODE_STREAM specific variables */
@ -192,7 +192,7 @@ int cURLCamera::Capture( Image &image ) {
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
if ( nRet != 0 ) { if ( nRet != 0 ) {
Error("Failed waiting for available data condition variable: %s",strerror(nRet)); Error("Failed waiting for available data condition variable: %s",strerror(nRet));
return -20; return -1;
} }
} }
@ -295,7 +295,7 @@ int cURLCamera::Capture( Image &image ) {
need_more_data = true; need_more_data = true;
} else { } else {
/* All good. decode the image */ /* All good. decode the image */
image.DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); zm_packet.image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder);
frameComplete = true; frameComplete = true;
} }
} }
@ -305,7 +305,7 @@ int cURLCamera::Capture( Image &image ) {
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
if(nRet != 0) { if(nRet != 0) {
Error("Failed waiting for available data condition variable: %s",strerror(nRet)); Error("Failed waiting for available data condition variable: %s",strerror(nRet));
return -18; return -1;
} }
need_more_data = false; need_more_data = false;
} }
@ -315,7 +315,7 @@ int cURLCamera::Capture( Image &image ) {
if (!single_offsets.empty()) { if (!single_offsets.empty()) {
if( (single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front()) ) { if( (single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front()) ) {
/* Extract frame */ /* Extract frame */
image.DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); zm_packet.image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder);
single_offsets.pop_front(); single_offsets.pop_front();
frameComplete = true; frameComplete = true;
} else { } else {
@ -329,7 +329,7 @@ int cURLCamera::Capture( Image &image ) {
nRet = pthread_cond_wait(&request_complete_cond,&shareddata_mutex); nRet = pthread_cond_wait(&request_complete_cond,&shareddata_mutex);
if(nRet != 0) { if(nRet != 0) {
Error("Failed waiting for request complete condition variable: %s",strerror(nRet)); Error("Failed waiting for request complete condition variable: %s",strerror(nRet));
return -19; return -1;
} }
} }
} else { } else {
@ -344,7 +344,7 @@ int cURLCamera::Capture( Image &image ) {
unlock(); unlock();
if(!frameComplete) if(!frameComplete)
return -1; return 0;
return 1; return 1;
} }
@ -354,12 +354,6 @@ int cURLCamera::PostCapture() {
return( 0 ); return( 0 );
} }
int cURLCamera::CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ) {
Error("Capture and Record not implemented for the cURL camera type");
// Nothing to do here
return( 0 );
}
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) {
lock(); lock();

View File

@ -77,9 +77,8 @@ public:
int PrimeCapture(); int PrimeCapture();
int PreCapture(); int PreCapture();
int Capture( Image &image ); int Capture( ZMPacket &p );
int PostCapture(); int PostCapture();
int CaptureAndRecord( Image &image, struct timeval recording, char* event_directory );
size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata);
size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata); size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata);

View File

@ -50,31 +50,54 @@ Event::Event(
Monitor *p_monitor, Monitor *p_monitor,
struct timeval p_start_time, struct timeval p_start_time,
const std::string &p_cause, const std::string &p_cause,
const StringSetMap &p_noteSetMap, const StringSetMap &p_noteSetMap
bool p_videoEvent ) : ) :
id(0), id(0),
monitor(p_monitor), monitor(p_monitor),
start_time(p_start_time), start_time(p_start_time),
end_time({0,0}),
cause(p_cause), cause(p_cause),
noteSetMap(p_noteSetMap), noteSetMap(p_noteSetMap),
videoEvent(p_videoEvent), frames(0),
videowriter(nullptr) alarm_frames(0),
alarm_frame_written(false),
tot_score(0),
max_score(0),
//path(""),
//snapshit_file(),
//alarm_file(""),
videoStore(nullptr),
//video_name(""),
//video_file(""),
last_db_frame(0),
have_video_keyframe(false),
//scheme
save_jpegs(0)
{ {
std::string notes; std::string notes;
createNotes(notes); createNotes(notes);
struct timeval now; struct timeval now;
gettimeofday(&now, 0); gettimeofday(&now, 0);
bool untimedEvent = false;
if ( !start_time.tv_sec ) { if ( !start_time.tv_sec ) {
untimedEvent = true; Warning("Event has zero time, setting to now");
start_time = now; start_time = now;
} else if ( start_time.tv_sec > now.tv_sec ) { } else if ( start_time.tv_sec > now.tv_sec ) {
char buffer[26];
char buffer_now[26];
struct tm* tm_info;
tm_info = localtime(&start_time.tv_sec);
strftime(buffer, 26, "%Y:%m:%d %H:%M:%S", tm_info);
tm_info = localtime(&now.tv_sec);
strftime(buffer_now, 26, "%Y:%m:%d %H:%M:%S", tm_info);
Error( Error(
"StartDateTime in the future %u.%u > %u.%u", "StartDateTime in the future starttime %u.%u >? now %u.%u difference %d\n%s\n%s",
start_time.tv_sec, start_time.tv_usec, now.tv_sec, now.tv_usec start_time.tv_sec, start_time.tv_usec, now.tv_sec, now.tv_usec,
(now.tv_sec-start_time.tv_sec),
buffer, buffer_now
); );
start_time = now; start_time = now;
} }
@ -85,12 +108,16 @@ Event::Event(
state_id = atoi(dbrow[0]); state_id = atoi(dbrow[0]);
} }
// Copy it in case opening the mp4 doesn't work we can set it to another value
save_jpegs = monitor->GetOptSaveJPEGs();
Storage * storage = monitor->getStorage(); Storage * storage = monitor->getStorage();
char sql[ZM_SQL_MED_BUFSIZ]; char sql[ZM_SQL_MED_BUFSIZ];
snprintf(sql, sizeof(sql), "INSERT INTO Events " snprintf(sql, sizeof(sql),
"( MonitorId, StorageId, Name, StartDateTime, Width, Height, Cause, Notes, StateId, Orientation, Videoed, DefaultVideo, SaveJPEGs, Scheme )" "INSERT INTO `Events` "
" VALUES ( %u, %u, 'New Event', from_unixtime( %ld ), %u, %u, '%s', '%s', %u, %d, %d, '%s', %d, '%s' )", "( `MonitorId`, `StorageId`, `Name`, `StartDateTime`, `Width`, `Height`, `Cause`, `Notes`, `StateId`, `Orientation`, `Videoed`, `DefaultVideo`, `SaveJPEGs`, `Scheme` )"
" VALUES "
"( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '%s', %d, '%s' )",
monitor->Id(), monitor->Id(),
storage->Id(), storage->Id(),
start_time.tv_sec, start_time.tv_sec,
@ -100,11 +127,12 @@ Event::Event(
notes.c_str(), notes.c_str(),
state_id, state_id,
monitor->getOrientation(), monitor->getOrientation(),
videoEvent, ( monitor->GetOptVideoWriter() != 0 ? 1 : 0 ),
( monitor->GetOptVideoWriter() != 0 ? "video.mp4" : "" ), ( monitor->GetOptVideoWriter() != 0 ? "video.mp4" : "" ),
monitor->GetOptSaveJPEGs(), monitor->GetOptSaveJPEGs(),
storage->SchemeString().c_str() storage->SchemeString().c_str()
); );
db_mutex.lock(); db_mutex.lock();
while ( mysql_query(&dbconn, sql) ) { while ( mysql_query(&dbconn, sql) ) {
db_mutex.unlock(); db_mutex.unlock();
@ -171,16 +199,6 @@ Event::Event(
Debug(1, "Using storage area at %s", path.c_str()); Debug(1, "Using storage area at %s", path.c_str());
db_mutex.unlock(); db_mutex.unlock();
if ( untimedEvent ) {
Warning("Event %" PRIu64 " has zero time, setting to current", id);
}
end_time.tv_sec = 0;
frames = 0;
alarm_frames = 0;
tot_score = 0;
max_score = 0;
alarm_frame_written = false;
last_db_frame = 0;
video_name = ""; video_name = "";
snapshot_file = path + "/snapshot.jpg"; snapshot_file = path + "/snapshot.jpg";
@ -189,7 +207,16 @@ Event::Event(
/* Save as video */ /* Save as video */
if ( monitor->GetOptVideoWriter() != 0 ) { if ( monitor->GetOptVideoWriter() != 0 ) {
video_name = stringtf("%" PRIu64 "-%s", id, "video.mp4"); std::string container = monitor->OutputContainer();
if ( container == "auto" || container == "" ) {
if ( monitor->OutputCodec() == AV_CODEC_ID_H264 ) {
container = "mp4";
} else {
container = "mkv";
}
}
video_name = stringtf("%" PRIu64 "-%s.%s", id, "video", container.c_str());
snprintf(sql, sizeof(sql), "UPDATE Events SET DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id); snprintf(sql, sizeof(sql), "UPDATE Events SET DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id);
db_mutex.lock(); db_mutex.lock();
if ( mysql_query(&dbconn, sql) ) { if ( mysql_query(&dbconn, sql) ) {
@ -199,48 +226,33 @@ Event::Event(
} }
db_mutex.unlock(); db_mutex.unlock();
video_file = path + "/" + video_name; video_file = path + "/" + video_name;
Debug(1, "Writing video file to %s", video_file.c_str()); Debug(1, "Writing video file to %s", video_file.c_str());
Camera * camera = monitor->getCamera();
videoStore = new VideoStore(
video_file.c_str(),
container.c_str(),
camera->get_VideoStream(),
camera->get_VideoCodecContext(),
( monitor->RecordAudio() ? camera->get_AudioStream() : nullptr ),
( monitor->RecordAudio() ? camera->get_AudioCodecContext() : nullptr ),
monitor );
/* X264 MP4 video writer */ if ( !videoStore->open() ) {
if ( monitor->GetOptVideoWriter() == Monitor::X264ENCODE ) { delete videoStore;
#if ZM_HAVE_VIDEOWRITER_X264MP4 videoStore = nullptr;
videowriter = new X264MP4Writer(video_file.c_str(), save_jpegs |= 1; // Turn on jpeg storage
monitor->Width(), }
monitor->Height(), } // end if GetOptVideoWriter
monitor->Colours(),
monitor->SubpixelOrder(),
monitor->GetOptEncoderParamsVec());
#else
Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)");
#endif
if ( videowriter != nullptr ) {
/* Open the video stream */
int nRet = videowriter->Open();
if ( nRet != 0 ) {
Error("Failed opening video stream");
delete videowriter;
videowriter = nullptr;
}
}
}
} else {
/* No video object */
videowriter = nullptr;
}
} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) } // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent )
Event::~Event() { Event::~Event() {
// We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet.
/* Close the video file */ /* Close the video file */
if ( videowriter != nullptr ) { if ( videoStore != nullptr ) {
int nRet = videowriter->Close(); Debug(2, "Deleting video store");
if ( nRet != 0 ) { delete videoStore;
Error("Failed closing video stream"); videoStore = nullptr;
}
delete videowriter;
videowriter = nullptr;
} }
// endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp. // endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp.
@ -311,16 +323,20 @@ Event::~Event() {
} // Event::~Event() } // Event::~Event()
void Event::createNotes(std::string &notes) { void Event::createNotes(std::string &notes) {
notes.clear(); if ( !notes.empty() ) {
for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); ++mapIter ) { notes.clear();
notes += mapIter->first; for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); ++mapIter ) {
notes += ": "; notes += mapIter->first;
const StringSet &stringSet = mapIter->second; notes += ": ";
for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); ++setIter ) { const StringSet &stringSet = mapIter->second;
if ( setIter != stringSet.begin() ) for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); ++setIter ) {
notes += ", "; if ( setIter != stringSet.begin() )
notes += *setIter; notes += ", ";
notes += *setIter;
}
} }
} else {
notes = "";
} }
} // void Event::createNotes(std::string &notes) } // void Event::createNotes(std::string &notes)
@ -350,38 +366,12 @@ bool Event::WriteFrameImage(
} }
return rc; return rc;
} } // end Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame )
bool Event::WriteFrameVideo( bool Event::WritePacket(ZMPacket &packet) {
const Image *image,
const struct timeval timestamp, if ( videoStore->writePacket(&packet) < 0 )
VideoWriter* videow) const {
const Image* frameimg = image;
Image ts_image;
/* Checking for invalid parameters */
if ( videow == nullptr ) {
Error("NULL Video object");
return false; return false;
}
/* If the image does not contain a timestamp, add the timestamp */
if ( !config.timestamp_on_capture ) {
ts_image = *image;
monitor->TimestampImage(&ts_image, &timestamp);
frameimg = &ts_image;
}
/* Calculate delta time */
struct DeltaTimeval delta_time3;
DELTA_TIMEVAL(delta_time3, timestamp, start_time, DT_PREC_3);
unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec;
/* Encode and write the frame */
if ( videowriter->Encode(frameimg, timeMS) != 0 ) {
Error("Failed encoding video frame");
}
return true; return true;
} // bool Event::WriteFrameVideo } // bool Event::WriteFrameVideo
@ -469,7 +459,7 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) {
if ( mysql_stmt_bind_param(stmt, bind) ) { if ( mysql_stmt_bind_param(stmt, bind) ) {
Error("Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt)); Error("Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt));
} }
} } // end if ! stmt
strncpy(notesStr, notes.c_str(), sizeof(notesStr)); strncpy(notesStr, notes.c_str(), sizeof(notesStr));
@ -506,11 +496,16 @@ void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, str
if ( timestamps[i]->tv_sec <= 0 ) { if ( timestamps[i]->tv_sec <= 0 ) {
Debug(1, "Not adding pre-capture frame %d, zero or less than 0 timestamp", i); Debug(1, "Not adding pre-capture frame %d, zero or less than 0 timestamp", i);
continue; continue;
} else if ( timestamps[i]->tv_sec < 0 ) {
Warning( "Not adding pre-capture frame %d, negative timestamp", i );
continue;
} else {
Debug( 3, "Adding pre-capture frame %d, timestamp = (%d), start_time=(%d)", i, timestamps[i]->tv_sec, start_time.tv_sec );
} }
frames++; frames++;
if ( monitor->GetOptSaveJPEGs() & 1 ) { if ( save_jpegs & 1 ) {
std::string event_file = stringtf(staticConfig.capture_file_format, path.c_str(), frames); std::string event_file = stringtf(staticConfig.capture_file_format, path.c_str(), frames);
Debug(1, "Writing pre-capture frame %d", frames); Debug(1, "Writing pre-capture frame %d", frames);
WriteFrameImage(images[i], *(timestamps[i]), event_file.c_str()); WriteFrameImage(images[i], *(timestamps[i]), event_file.c_str());
@ -523,10 +518,6 @@ void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, str
WriteFrameImage(images[i], *(timestamps[i]), snapshot_file.c_str()); WriteFrameImage(images[i], *(timestamps[i]), snapshot_file.c_str());
} }
if ( videowriter != nullptr ) {
WriteFrameVideo(images[i], *(timestamps[i]), videowriter);
}
struct DeltaTimeval delta_time; struct DeltaTimeval delta_time;
DELTA_TIMEVAL(delta_time, *(timestamps[i]), start_time, DT_PREC_2); DELTA_TIMEVAL(delta_time, *(timestamps[i]), start_time, DT_PREC_2);
// Delta is Decimal(8,2) so 6 integer digits and 2 decimal digits // Delta is Decimal(8,2) so 6 integer digits and 2 decimal digits
@ -564,6 +555,26 @@ void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, str
end_time = *timestamps[n_frames-1]; end_time = *timestamps[n_frames-1];
} // void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) } // void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps)
void Event::AddPacket(ZMPacket *packet) {
have_video_keyframe = have_video_keyframe || ( ( packet->codec_type == AVMEDIA_TYPE_VIDEO ) && packet->keyframe );
Debug(2, "have_video_keyframe %d codec_type %d == video? %d packet keyframe %d",
have_video_keyframe, packet->codec_type, (packet->codec_type == AVMEDIA_TYPE_VIDEO), packet->keyframe);
dumpPacket(&packet->packet, "Adding to event");
if ( videoStore ) {
if ( have_video_keyframe ) {
videoStore->writePacket(packet);
} else {
Debug(2, "No video keyframe yet, not writing");
}
//FIXME if it fails, we should write a jpeg
}
if ( packet->codec_type == AVMEDIA_TYPE_VIDEO )
AddFrame(packet->image, *(packet->timestamp), packet->score, packet->analysis_image);
end_time = *packet->timestamp;
return;
}
void Event::WriteDbFrames() { void Event::WriteDbFrames() {
char *frame_insert_values_ptr = (char *)&frame_insert_sql + 90; // 90 == strlen(frame_insert_sql); char *frame_insert_values_ptr = (char *)&frame_insert_sql + 90; // 90 == strlen(frame_insert_sql);
@ -625,72 +636,67 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
} }
frames++; frames++;
bool write_to_db = false; bool write_to_db = false;
FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL); FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL);
// < 0 means no motion detection is being done. // < 0 means no motion detection is being done.
if ( score < 0 ) if ( score < 0 )
score = 0; score = 0;
tot_score += score;
if ( score > (int)max_score )
max_score = score;
if ( monitor->GetOptSaveJPEGs() & 1 ) { if ( image ) {
std::string event_file = stringtf(staticConfig.capture_file_format, path.c_str(), frames); if ( save_jpegs & 1 ) {
Debug(1, "Writing capture frame %d to %s", frames, event_file.c_str()); std::string event_file = stringtf(staticConfig.capture_file_format, path.c_str(), frames);
if ( !WriteFrameImage(image, timestamp, event_file.c_str()) ) { Debug(1, "Writing capture frame %d to %s", frames, event_file.c_str());
Error("Failed to write frame image"); if ( !WriteFrameImage(image, timestamp, event_file.c_str()) ) {
Error("Failed to write frame image");
}
} // end if save_jpegs
// If this is the first frame, we should add a thumbnail to the event directory
if ( (frames == 1) || (score > (int)max_score) ) {
write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it.
WriteFrameImage(image, timestamp, snapshot_file.c_str());
} }
}
// If this is the first frame, we should add a thumbnail to the event directory // We are writing an Alarm frame
if ( (frames == 1) || (score > (int)max_score) ) { if ( frame_type == ALARM ) {
write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. // The first frame with a score will be the frame that alarmed the event
WriteFrameImage(image, timestamp, snapshot_file.c_str()); if ( !alarm_frame_written ) {
} write_to_db = true; // OD processing will need it, so the db needs to know about it
alarm_frame_written = true;
WriteFrameImage(image, timestamp, alarm_file.c_str());
}
alarm_frames++;
// We are writing an Alarm frame if ( alarm_image and ( save_jpegs & 2 ) ) {
if ( frame_type == ALARM ) {
// The first frame with a score will be the frame that alarmed the event
if ( !alarm_frame_written ) {
write_to_db = true; // OD processing will need it, so the db needs to know about it
alarm_frame_written = true;
WriteFrameImage(image, timestamp, alarm_file.c_str());
}
alarm_frames++;
tot_score += score;
if ( score > (int)max_score )
max_score = score;
if ( alarm_image ) {
if ( monitor->GetOptSaveJPEGs() & 2 ) {
std::string event_file = stringtf(staticConfig.analyse_file_format, path.c_str(), frames); std::string event_file = stringtf(staticConfig.analyse_file_format, path.c_str(), frames);
Debug(1, "Writing analysis frame %d", frames); Debug(1, "Writing analysis frame %d", frames);
if ( ! WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true) ) { if ( ! WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true) ) {
Error("Failed to write analysis frame image"); Error("Failed to write analysis frame image");
} }
} }
} } // end if is an alarm frame
} // end if frame_type == ALARM } // end if has image
if ( videowriter != nullptr ) {
WriteFrameVideo(image, timestamp, videowriter);
}
struct DeltaTimeval delta_time;
DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2);
Debug(1, "Frame delta is %d.%d - %d.%d = %d.%d",
start_time.tv_sec, start_time.tv_usec, timestamp.tv_sec, timestamp.tv_usec, delta_time.sec, delta_time.fsec);
double fps = monitor->get_fps();
bool db_frame = ( frame_type != BULK ) || (frames==1) || ((frames%config.bulk_frame_interval)==0) ; bool db_frame = ( frame_type != BULK ) || (frames==1) || ((frames%config.bulk_frame_interval)==0) ;
if ( db_frame ) { if ( db_frame ) {
struct DeltaTimeval delta_time;
DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2);
Debug(1, "Frame delta is %d.%d - %d.%d = %d.%d",
start_time.tv_sec, start_time.tv_usec, timestamp.tv_sec, timestamp.tv_usec, delta_time.sec, delta_time.fsec);
// The idea is to write out 1/sec // The idea is to write out 1/sec
frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score)); frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score));
double fps = monitor->get_capture_fps();
if ( write_to_db if ( write_to_db
or or
(frame_data.size() >= MAX_DB_FRAMES) (frame_data.size() >= MAX_DB_FRAMES)
or or
(frame_type==BULK) (frame_type == BULK)
or or
( fps and (frame_data.size() > fps) ) ( fps and (frame_data.size() > fps) )
) { ) {

View File

@ -34,13 +34,15 @@
#include <set> #include <set>
#include <map> #include <map>
#include <queue> #include <queue>
#include <string>
#include "zm.h" #include "zm.h"
#include "zm_image.h" #include "zm_image.h"
#include "zm_stream.h" #include "zm_stream.h"
#include "zm_video.h" #include "zm_packet.h"
#include "zm_storage.h" #include "zm_storage.h"
class VideoStore;
class Zone; class Zone;
class Monitor; class Monitor;
class EventStream; class EventStream;
@ -82,25 +84,22 @@ class Event {
struct timeval end_time; struct timeval end_time;
std::string cause; std::string cause;
StringSetMap noteSetMap; StringSetMap noteSetMap;
bool videoEvent;
int frames; int frames;
int alarm_frames; int alarm_frames;
bool alarm_frame_written; bool alarm_frame_written;
unsigned int tot_score; unsigned int tot_score;
unsigned int max_score; unsigned int max_score;
std::string path; std::string path;
std::string snapshot_file; std::string snapshot_file;
std::string alarm_file; std::string alarm_file;
VideoStore *videoStore;
VideoWriter* videowriter;
FILE* timecodes_fd;
std::string video_name; std::string video_name;
std::string video_file; std::string video_file;
std::string timecodes_name;
std::string timecodes_file;
int last_db_frame; int last_db_frame;
bool have_video_keyframe; // a flag to tell us if we have had a video keyframe when writing an mp4. The first frame SHOULD be a video keyframe.
Storage::Schemes scheme; Storage::Schemes scheme;
int save_jpegs;
void createNotes(std::string &notes); void createNotes(std::string &notes);
@ -112,8 +111,8 @@ class Event {
Monitor *p_monitor, Monitor *p_monitor,
struct timeval p_start_time, struct timeval p_start_time,
const std::string &p_cause, const std::string &p_cause,
const StringSetMap &p_noteSetMap, const StringSetMap &p_noteSetMap
bool p_videoEvent=false); );
~Event(); ~Event();
uint64_t Id() const { return id; } uint64_t Id() const { return id; }
@ -124,6 +123,8 @@ class Event {
const struct timeval &StartTime() const { return start_time; } const struct timeval &StartTime() const { return start_time; }
const struct timeval &EndTime() const { return end_time; } const struct timeval &EndTime() const { return end_time; }
void AddPacket(ZMPacket *p);
bool WritePacket(ZMPacket &p);
bool SendFrameImage(const Image *image, bool alarm_frame=false); bool SendFrameImage(const Image *image, bool alarm_frame=false);
bool WriteFrameImage( bool WriteFrameImage(
Image *image, Image *image,
@ -131,11 +132,6 @@ class Event {
const char *event_file, const char *event_file,
bool alarm_frame=false bool alarm_frame=false
) const; ) const;
bool WriteFrameVideo(
const Image *image,
const struct timeval timestamp,
VideoWriter* videow
) const;
void updateNotes(const StringSetMap &stringSetMap); void updateNotes(const StringSetMap &stringSetMap);
@ -176,6 +172,7 @@ class Event {
return pre_alarm_count; return pre_alarm_count;
} }
static void EmptyPreAlarmFrames() { static void EmptyPreAlarmFrames() {
#if 0
while ( pre_alarm_count > 0 ) { while ( pre_alarm_count > 0 ) {
int i = pre_alarm_count - 1; int i = pre_alarm_count - 1;
delete pre_alarm_data[i].image; delete pre_alarm_data[i].image;
@ -186,6 +183,7 @@ class Event {
} }
pre_alarm_count--; pre_alarm_count--;
} }
#endif
pre_alarm_count = 0; pre_alarm_count = 0;
} }
static void AddPreAlarmFrame( static void AddPreAlarmFrame(
@ -194,15 +192,18 @@ class Event {
int score=0, int score=0,
Image *alarm_frame=nullptr Image *alarm_frame=nullptr
) { ) {
#if 0
pre_alarm_data[pre_alarm_count].image = new Image(*image); pre_alarm_data[pre_alarm_count].image = new Image(*image);
pre_alarm_data[pre_alarm_count].timestamp = timestamp; pre_alarm_data[pre_alarm_count].timestamp = timestamp;
pre_alarm_data[pre_alarm_count].score = score; pre_alarm_data[pre_alarm_count].score = score;
if ( alarm_frame ) { if ( alarm_frame ) {
pre_alarm_data[pre_alarm_count].alarm_frame = new Image(*alarm_frame); pre_alarm_data[pre_alarm_count].alarm_frame = new Image(*alarm_frame);
} }
#endif
pre_alarm_count++; pre_alarm_count++;
} }
void SavePreAlarmFrames() { void SavePreAlarmFrames() {
#if 0
for ( int i = 0; i < pre_alarm_count; i++ ) { for ( int i = 0; i < pre_alarm_count; i++ ) {
AddFrame( AddFrame(
pre_alarm_data[i].image, pre_alarm_data[i].image,
@ -210,6 +211,7 @@ class Event {
pre_alarm_data[i].score, pre_alarm_data[i].score,
pre_alarm_data[i].alarm_frame); pre_alarm_data[i].alarm_frame);
} }
#endif
EmptyPreAlarmFrames(); EmptyPreAlarmFrames();
} }
}; };

View File

@ -30,6 +30,13 @@
#include "zm_sendfile.h" #include "zm_sendfile.h"
const std::string EventStream::StreamMode_Strings[4] = {
"None",
"Single",
"All",
"Gapless"
};
bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) { bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) {
static char sql[ZM_SQL_SML_BUFSIZ]; static char sql[ZM_SQL_SML_BUFSIZ];
@ -82,7 +89,10 @@ bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) {
return true; return true;
} // bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) } // bool EventStream::loadInitialEventData( int monitor_id, time_t event_time )
bool EventStream::loadInitialEventData(uint64_t init_event_id, unsigned int init_frame_id) { bool EventStream::loadInitialEventData(
uint64_t init_event_id,
unsigned int init_frame_id
) {
loadEventData(init_event_id); loadEventData(init_event_id);
if ( init_frame_id ) { if ( init_frame_id ) {
@ -213,7 +223,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
event_data->event_id); event_data->event_id);
} }
updateFrameRate((double)event_data->frame_count/event_data->duration); updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1);
snprintf(sql, sizeof(sql), "SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` " snprintf(sql, sizeof(sql), "SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` "
"FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id); "FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id);
@ -340,8 +350,7 @@ void EventStream::processCommand(const CmdMsg *msg) {
curr_frame_id = 1; curr_frame_id = 1;
} else { } else {
Debug(1, "mode is %s, current frame is %ld, frame count is %ld, last frame id is %ld", Debug(1, "mode is %s, current frame is %ld, frame count is %ld, last frame id is %ld",
(mode == MODE_SINGLE ? "single" : "not single"), StreamMode_Strings[(int)mode], curr_frame_id, event_data->frame_count );
curr_frame_id, event_data->frame_count );
} }
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
@ -588,7 +597,7 @@ void EventStream::processCommand(const CmdMsg *msg) {
if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT )
exit(0); exit(0);
updateFrameRate((double)event_data->frame_count/event_data->duration); updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1);
} // void EventStream::processCommand(const CmdMsg *msg) } // void EventStream::processCommand(const CmdMsg *msg)
bool EventStream::checkEventLoaded() { bool EventStream::checkEventLoaded() {
@ -663,7 +672,7 @@ bool EventStream::checkEventLoaded() {
mysql_free_result(result); mysql_free_result(result);
forceEventChange = false; forceEventChange = false;
} else { } else {
Debug(2, "Pausing because mode is %d", mode); Debug(2, "Pausing because mode is %s", StreamMode_Strings[mode].c_str());
if ( curr_frame_id <= 0 ) if ( curr_frame_id <= 0 )
curr_frame_id = 1; curr_frame_id = 1;
else else
@ -756,7 +765,7 @@ bool EventStream::sendFrame(int delta_us) {
// when stored as an mp4, we just have the rotation as a flag in the headers // when stored as an mp4, we just have the rotation as a flag in the headers
// so we need to rotate it before outputting // so we need to rotate it before outputting
if ( if (
(monitor->GetOptVideoWriter() == Monitor::H264PASSTHROUGH) (monitor->GetOptVideoWriter() == Monitor::PASSTHROUGH)
and and
(event_data->Orientation != Monitor::ROTATE_0) (event_data->Orientation != Monitor::ROTATE_0)
) { ) {
@ -836,7 +845,7 @@ void EventStream::runStream() {
exit(0); exit(0);
} }
updateFrameRate((double)event_data->frame_count/event_data->duration); updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1);
gettimeofday(&start, nullptr); gettimeofday(&start, nullptr);
uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec; uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec;
uint64_t last_frame_offset = 0; uint64_t last_frame_offset = 0;
@ -997,10 +1006,10 @@ void EventStream::runStream() {
delta_us = ((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*(replay_rate?abs(replay_rate*2):2))); delta_us = ((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*(replay_rate?abs(replay_rate*2):2)));
Debug(2, "Sleeping %d because 1000000 * ZM_RATE_BASE(%d) / ( base_fps (%f), replay_rate(%d)", Debug(2, "Sleeping %d because 1000000 * ZM_RATE_BASE(%d) / ( base_fps (%f), replay_rate(%d)",
(unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))), delta_us,
ZM_RATE_BASE, ZM_RATE_BASE,
(base_fps?base_fps:1), (base_fps ? base_fps : 1),
(replay_rate?abs(replay_rate*2):0) (replay_rate ? abs(replay_rate*2) : 0)
); );
if ( delta_us > 0 ) { if ( delta_us > 0 ) {
if ( delta_us > MAX_SLEEP_USEC ) { if ( delta_us > MAX_SLEEP_USEC ) {

View File

@ -37,9 +37,11 @@ extern "C" {
} }
#endif #endif
class EventStream : public StreamBase { class EventStream : public StreamBase {
public: public:
typedef enum { MODE_NONE, MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode; typedef enum { MODE_NONE, MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
static const std::string StreamMode_Strings[4];
protected: protected:
struct FrameData { struct FrameData {
@ -84,13 +86,13 @@ class EventStream : public StreamBase {
EventData *event_data; EventData *event_data;
protected: protected:
bool loadEventData( uint64_t event_id ); bool loadEventData(uint64_t event_id);
bool loadInitialEventData( uint64_t init_event_id, unsigned int init_frame_id ); bool loadInitialEventData(uint64_t init_event_id, unsigned int init_frame_id);
bool loadInitialEventData( int monitor_id, time_t event_time ); bool loadInitialEventData(int monitor_id, time_t event_time);
bool checkEventLoaded(); bool checkEventLoaded();
void processCommand( const CmdMsg *msg ); void processCommand(const CmdMsg *msg);
bool sendFrame( int delta_us ); bool sendFrame(int delta_us);
public: public:
EventStream() : EventStream() :
@ -128,11 +130,9 @@ class EventStream : public StreamBase {
ffmpeg_input = nullptr; ffmpeg_input = nullptr;
} }
} }
void setStreamStart( uint64_t init_event_id, unsigned int init_frame_id ); void setStreamStart(uint64_t init_event_id, unsigned int init_frame_id);
void setStreamStart( int monitor_id, time_t event_time ); void setStreamStart(int monitor_id, time_t event_time);
void setStreamMode( StreamMode p_mode ) { void setStreamMode(StreamMode p_mode) { mode = p_mode; }
mode = p_mode;
}
void runStream() override; void runStream() override;
Image *getImage(); Image *getImage();
private: private:

View File

@ -290,7 +290,7 @@ static void zm_log_fps(double d, const char *postfix) {
} }
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
void zm_dump_codecpar ( const AVCodecParameters *par ) { void zm_dump_codecpar(const AVCodecParameters *par) {
Debug(1, "Dumping codecpar codec_type(%d %s) codec_id(%d %s) codec_tag(%" PRIu32 ") width(%d) height(%d) bit_rate(%" PRIu64 ") format(%d %s)", Debug(1, "Dumping codecpar codec_type(%d %s) codec_id(%d %s) codec_tag(%" PRIu32 ") width(%d) height(%d) bit_rate(%" PRIu64 ") format(%d %s)",
par->codec_type, par->codec_type,
av_get_media_type_string(par->codec_type), av_get_media_type_string(par->codec_type),
@ -385,6 +385,9 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output)
zm_log_fps(av_q2d(st->avg_frame_rate), "fps"); zm_log_fps(av_q2d(st->avg_frame_rate), "fps");
if (tbn) if (tbn)
zm_log_fps(1 / av_q2d(st->time_base), "stream tb numerator"); zm_log_fps(1 / av_q2d(st->time_base), "stream tb numerator");
} else if ( codec->codec_type == AVMEDIA_TYPE_AUDIO ) {
Debug(1, "profile %d channels %d sample_rate %d",
codec->profile, codec->channels, codec->sample_rate);
} }
if (st->disposition & AV_DISPOSITION_DEFAULT) if (st->disposition & AV_DISPOSITION_DEFAULT)
@ -425,6 +428,26 @@ int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) {
return 0; return 0;
} }
void fix_deprecated_pix_fmt(AVCodecContext *ctx) {
// Fix deprecated formats
switch ( ctx->pix_fmt ) {
case AV_PIX_FMT_YUVJ422P :
ctx->pix_fmt = AV_PIX_FMT_YUV422P;
break;
case AV_PIX_FMT_YUVJ444P :
ctx->pix_fmt = AV_PIX_FMT_YUV444P;
break;
case AV_PIX_FMT_YUVJ440P :
ctx->pix_fmt = AV_PIX_FMT_YUV440P;
break;
case AV_PIX_FMT_NONE :
case AV_PIX_FMT_YUVJ420P :
default:
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
break;
}
}
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
#else #else
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) { unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) {
@ -528,7 +551,7 @@ int zm_receive_packet(AVCodecContext *context, AVPacket &packet) {
Error("Error encoding (%d) (%s)", ret, Error("Error encoding (%d) (%s)", ret,
av_err2str(ret)); av_err2str(ret));
} }
return 0; return ret;
} }
return 1; return 1;
#else #else
@ -536,8 +559,9 @@ int zm_receive_packet(AVCodecContext *context, AVPacket &packet) {
int ret = avcodec_encode_audio2(context, &packet, nullptr, &got_packet); int ret = avcodec_encode_audio2(context, &packet, nullptr, &got_packet);
if ( ret < 0 ) { if ( ret < 0 ) {
Error("Error encoding (%d) (%s)", ret, av_err2str(ret)); Error("Error encoding (%d) (%s)", ret, av_err2str(ret));
return ret;
} }
return got_packet; return got_packet; // 1
#endif #endif
} // end int zm_receive_packet(AVCodecContext *context, AVPacket &packet) } // end int zm_receive_packet(AVCodecContext *context, AVPacket &packet)
@ -636,7 +660,7 @@ void dumpPacket(AVStream *stream, AVPacket *pkt, const char *text) {
", size: %d, stream_index: %d, flags: %04x, keyframe(%d) pos: %" PRId64 ", size: %d, stream_index: %d, flags: %04x, keyframe(%d) pos: %" PRId64
", duration: %" ", duration: %"
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
PRId64 PRIu64
#else #else
"d" "d"
#endif #endif
@ -653,6 +677,17 @@ void dumpPacket(AVStream *stream, AVPacket *pkt, const char *text) {
Debug(2, "%s:%d:%s: %s", __FILE__, __LINE__, text, b); Debug(2, "%s:%d:%s: %s", __FILE__, __LINE__, text, b);
} }
void zm_free_codec( AVCodecContext **ctx ) {
if ( *ctx ) {
avcodec_close(*ctx);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
// We allocate and copy in newer ffmpeg, so need to free it
avcodec_free_context(ctx);
#endif
*ctx = NULL;
} // end if
}
void dumpPacket(AVPacket *pkt, const char *text) { void dumpPacket(AVPacket *pkt, const char *text) {
char b[10240]; char b[10240];

View File

@ -323,14 +323,15 @@ void zm_dump_codecpar(const AVCodecParameters *par);
); );
#if LIBAVUTIL_VERSION_CHECK(54, 4, 0, 74, 100) #if LIBAVUTIL_VERSION_CHECK(54, 4, 0, 74, 100)
#define zm_dump_video_frame(frame,text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64, \ #define zm_dump_video_frame(frame, text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64 " keyframe: %d", \
text, \ text, \
frame->format, \ frame->format, \
av_get_pix_fmt_name((AVPixelFormat)frame->format), \ av_get_pix_fmt_name((AVPixelFormat)frame->format), \
frame->width, \ frame->width, \
frame->height, \ frame->height, \
frame->linesize[0], frame->linesize[1], \ frame->linesize[0], frame->linesize[1], \
frame->pts \ frame->pts, \
frame->key_frame \
); );
#else #else
@ -346,8 +347,8 @@ void zm_dump_codecpar(const AVCodecParameters *par);
#endif #endif
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
#define zm_av_packet_unref( packet ) av_packet_unref( packet ) #define zm_av_packet_unref(packet) av_packet_unref(packet)
#define zm_av_packet_ref( dst, src ) av_packet_ref( dst, src ) #define zm_av_packet_ref(dst, src) av_packet_ref(dst, src)
#else #else
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ); unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src );
#define zm_av_packet_unref( packet ) av_free_packet( packet ) #define zm_av_packet_unref( packet ) av_free_packet( packet )
@ -355,11 +356,17 @@ void zm_dump_codecpar(const AVCodecParameters *par);
void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb); void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb);
#endif #endif
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
#define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) \
avcodec_send_packet( context, packet ); \
avcodec_receive_frame( context, rawFrame );
#else
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) #if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
#define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet ) #define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet )
#else #else
#define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet ) avcodec_decode_video( context, rawFrame, frameComplete, packet->data, packet->size) #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet ) avcodec_decode_video( context, rawFrame, frameComplete, packet->data, packet->size)
#endif #endif
#endif
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
#define zm_av_frame_alloc() av_frame_alloc() #define zm_av_frame_alloc() av_frame_alloc()
@ -372,6 +379,7 @@ void zm_dump_codecpar(const AVCodecParameters *par);
#endif #endif
int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt); int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt);
void fix_deprecated_pix_fmt(AVCodecContext *);
bool is_video_stream(const AVStream *); bool is_video_stream(const AVStream *);
bool is_audio_stream(const AVStream *); bool is_audio_stream(const AVStream *);
@ -416,4 +424,5 @@ int zm_add_samples_to_fifo(AVAudioFifo *fifo, AVFrame *frame);
int zm_get_samples_from_fifo(AVAudioFifo *fifo, AVFrame *frame); int zm_get_samples_from_fifo(AVAudioFifo *fifo, AVFrame *frame);
#endif // ZM_FFMPEG_H #endif // ZM_FFMPEG_H

View File

@ -30,14 +30,12 @@ extern "C" {
#if HAVE_LIBAVUTIL_HWCONTEXT_H #if HAVE_LIBAVUTIL_HWCONTEXT_H
#include "libavutil/hwcontext.h" #include "libavutil/hwcontext.h"
#endif #endif
#include "libavutil/pixdesc.h" #include "libavutil/pixdesc.h"
} }
#ifndef AV_ERROR_MAX_STRING_SIZE
#define AV_ERROR_MAX_STRING_SIZE 64
#endif
#include <string> #include <string>
#include <locale>
#if HAVE_LIBAVUTIL_HWCONTEXT_H #if HAVE_LIBAVUTIL_HWCONTEXT_H
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
@ -130,7 +128,7 @@ FfmpegCamera::FfmpegCamera(
hwaccel_device(p_hwaccel_device) hwaccel_device(p_hwaccel_device)
{ {
if ( capture ) { if ( capture ) {
Initialise(); FFMPEGInit();
} }
mFormatContext = nullptr; mFormatContext = nullptr;
@ -144,9 +142,6 @@ FfmpegCamera::FfmpegCamera(
mFrame = nullptr; mFrame = nullptr;
frameCount = 0; frameCount = 0;
mCanCapture = false; mCanCapture = false;
videoStore = nullptr;
have_video_keyframe = false;
packetqueue = nullptr;
error_count = 0; error_count = 0;
use_hwaccel = true; use_hwaccel = true;
#if HAVE_LIBAVUTIL_HWCONTEXT_H #if HAVE_LIBAVUTIL_HWCONTEXT_H
@ -199,19 +194,9 @@ FfmpegCamera::FfmpegCamera(
FfmpegCamera::~FfmpegCamera() { FfmpegCamera::~FfmpegCamera() {
Close(); Close();
if ( capture ) {
Terminate();
}
FFMPEGDeInit(); FFMPEGDeInit();
} }
void FfmpegCamera::Initialise() {
FFMPEGInit();
}
void FfmpegCamera::Terminate() {
}
int FfmpegCamera::PrimeCapture() { int FfmpegCamera::PrimeCapture() {
if ( mCanCapture ) { if ( mCanCapture ) {
Debug(1, "Priming capture from %s, Closing", mPath.c_str()); Debug(1, "Priming capture from %s, Closing", mPath.c_str());
@ -225,118 +210,42 @@ int FfmpegCamera::PrimeCapture() {
} }
int FfmpegCamera::PreCapture() { int FfmpegCamera::PreCapture() {
// If Reopen was called, then ffmpeg is closed and we need to reopen it.
if ( !mCanCapture )
return OpenFfmpeg();
// Nothing to do here
return 0; return 0;
} }
int FfmpegCamera::Capture(Image &image) { int FfmpegCamera::Capture(ZMPacket &zm_packet) {
if ( !mCanCapture ) { if ( !mCanCapture )
return -1; return -1;
}
int ret; int ret;
// If the reopen thread has a value, but mCanCapture != 0, then we have just
// reopened the connection to the device, and we can clean up the thread.
int frameComplete = false; if ( (ret = av_read_frame(mFormatContext, &packet)) < 0 ) {
while ( !frameComplete && !zm_terminate ) {
ret = av_read_frame(mFormatContext, &packet);
if ( ret < 0 ) {
if (
// Check if EOF.
(
ret == AVERROR_EOF
||
(mFormatContext->pb && mFormatContext->pb->eof_reached)
) ||
// Check for Connection failure.
(ret == -110)
) {
Info("Unable to read packet from stream %d: error %d \"%s\".",
packet.stream_index, ret, av_make_error_string(ret).c_str());
} else {
Error("Unable to read packet from stream %d: error %d \"%s\".",
packet.stream_index, ret, av_make_error_string(ret).c_str());
}
return -1;
}
bytes += packet.size;
int keyframe = packet.flags & AV_PKT_FLAG_KEY;
if ( keyframe )
have_video_keyframe = true;
Debug(5, "Got packet from stream %d dts (%d) pts(%d)",
packet.stream_index, packet.pts, packet.dts);
// What about audio stream? Maybe someday we could do sound detection...
if ( if (
(packet.stream_index == mVideoStreamId) // Check if EOF.
&& (ret == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) ||
(keyframe || have_video_keyframe) // Check for Connection failure.
) { (ret == -110)
ret = zm_send_packet_receive_frame(mVideoCodecContext, mRawFrame, packet); ) {
if ( ret < 0 ) { Info("Unable to read packet from stream %d: error %d \"%s\".",
if ( AVERROR(EAGAIN) != ret ) { packet.stream_index, ret, av_make_error_string(ret).c_str());
Warning("Unable to receive frame %d: code %d %s. error count is %d",
frameCount, ret, av_make_error_string(ret).c_str(), error_count);
error_count += 1;
if ( error_count > 100 ) {
Error("Error count over 100, going to close and re-open stream");
return -1;
}
}
zm_av_packet_unref(&packet);
continue;
}
frameComplete = 1;
zm_dump_video_frame(mRawFrame, "raw frame from decoder");
#if HAVE_LIBAVUTIL_HWCONTEXT_H
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
if (
(hw_pix_fmt != AV_PIX_FMT_NONE)
&&
(mRawFrame->format == hw_pix_fmt)
) {
/* retrieve data from GPU to CPU */
ret = av_hwframe_transfer_data(hwFrame, mRawFrame, 0);
if ( ret < 0 ) {
Error("Unable to transfer frame at frame %d: %s, continuing",
frameCount, av_make_error_string(ret).c_str());
zm_av_packet_unref(&packet);
continue;
}
zm_dump_video_frame(hwFrame, "After hwtransfer");
hwFrame->pts = mRawFrame->pts;
input_frame = hwFrame;
} else {
#endif
#endif
input_frame = mRawFrame;
#if HAVE_LIBAVUTIL_HWCONTEXT_H
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
}
#endif
#endif
if ( transfer_to_image(image, mFrame, input_frame) < 0 ) {
zm_av_packet_unref(&packet);
return -1;
}
frameCount++;
} else { } else {
Debug(4, "Different stream_index %d", packet.stream_index); Error("Unable to read packet from stream %d: error %d \"%s\".",
} // end if packet.stream_index == mVideoStreamId packet.stream_index, ret, av_make_error_string(ret).c_str());
zm_av_packet_unref(&packet); }
} // end while ! frameComplete return -1;
return frameComplete ? 1 : 0; }
} // FfmpegCamera::Capture dumpPacket(mFormatContext->streams[packet.stream_index], &packet, "ffmpeg_camera in");
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
zm_packet.codec_type = mFormatContext->streams[packet.stream_index]->codecpar->codec_type;
#else
zm_packet.codec_type = mFormatContext->streams[packet.stream_index]->codec->codec_type;
#endif
bytes += packet.size;
zm_packet.set_packet(&packet);
zm_av_packet_unref(&packet);
return 1;
} // FfmpegCamera::Capture
int FfmpegCamera::PostCapture() { int FfmpegCamera::PostCapture() {
// Nothing to do here // Nothing to do here
@ -346,7 +255,6 @@ int FfmpegCamera::PostCapture() {
int FfmpegCamera::OpenFfmpeg() { int FfmpegCamera::OpenFfmpeg() {
int ret; int ret;
have_video_keyframe = false;
error_count = 0; error_count = 0;
// Open the input, not necessarily a file // Open the input, not necessarily a file
@ -416,6 +324,7 @@ int FfmpegCamera::OpenFfmpeg() {
} }
av_dict_free(&opts); av_dict_free(&opts);
Debug(1, "Finding stream info");
#if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0) #if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0)
ret = av_find_stream_info(mFormatContext); ret = av_find_stream_info(mFormatContext);
#else #else
@ -449,6 +358,7 @@ int FfmpegCamera::OpenFfmpeg() {
} }
} }
} // end foreach stream } // end foreach stream
if ( mVideoStreamId == -1 ) { if ( mVideoStreamId == -1 ) {
Error("Unable to locate video stream in %s", mPath.c_str()); Error("Unable to locate video stream in %s", mPath.c_str());
return -1; return -1;
@ -456,19 +366,17 @@ int FfmpegCamera::OpenFfmpeg() {
Debug(3, "Found video stream at index %d, audio stream at index %d", Debug(3, "Found video stream at index %d, audio stream at index %d",
mVideoStreamId, mAudioStreamId); mVideoStreamId, mAudioStreamId);
packetqueue = new zm_packetqueue(
(mVideoStreamId > mAudioStreamId) ? mVideoStreamId : mAudioStreamId);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
// mVideoCodecContext = avcodec_alloc_context3(NULL); mVideoCodecContext = avcodec_alloc_context3(nullptr);
// avcodec_parameters_to_context(mVideoCodecContext, avcodec_parameters_to_context(mVideoCodecContext,
// mFormatContext->streams[mVideoStreamId]->codecpar); mFormatContext->streams[mVideoStreamId]->codecpar);
// this isn't copied. // this isn't copied.
// mVideoCodecContext->time_base = mVideoCodecContext->time_base =
// mFormatContext->streams[mVideoStreamId]->codec->time_base; mFormatContext->streams[mVideoStreamId]->codec->time_base;
#else #else
#endif #endif
mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; //mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
#ifdef CODEC_FLAG2_FAST #ifdef CODEC_FLAG2_FAST
mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY; mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY;
#endif #endif
@ -495,8 +403,8 @@ int FfmpegCamera::OpenFfmpeg() {
if ( use_hwaccel && (hwaccel_name != "") ) { if ( use_hwaccel && (hwaccel_name != "") ) {
#if HAVE_LIBAVUTIL_HWCONTEXT_H #if HAVE_LIBAVUTIL_HWCONTEXT_H
// 3.2 doesn't seem to have all the bits in place, so let's require 3.3 and up // 3.2 doesn't seem to have all the bits in place, so let's require 3.3 and up
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
// Print out available types // Print out available types
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE; enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
while ( (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE ) while ( (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE )
Debug(1, "%s", av_hwdevice_get_type_name(type)); Debug(1, "%s", av_hwdevice_get_type_name(type));
@ -509,22 +417,24 @@ int FfmpegCamera::OpenFfmpeg() {
Debug(1, "Found hwdevice %s", av_hwdevice_get_type_name(type)); Debug(1, "Found hwdevice %s", av_hwdevice_get_type_name(type));
} }
#if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) #if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0)
// Get hw_pix_fmt // Get hw_pix_fmt
for ( int i = 0;; i++ ) { for ( int i = 0;; i++ ) {
const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i); const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i);
if ( !config ) { if ( !config ) {
Debug(1, "Decoder %s does not support device type %s.", Debug(1, "Decoder %s does not support config %d.",
mVideoCodec->name, av_hwdevice_get_type_name(type)); mVideoCodec->name, i);
break; break;
} }
if ( (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) if ( (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
&& (config->device_type == type) && (config->device_type == type)
) { ) {
hw_pix_fmt = config->pix_fmt; hw_pix_fmt = config->pix_fmt;
break; Debug(1, "Decoder %s does support our type %s.",
mVideoCodec->name, av_hwdevice_get_type_name(type));
//break;
} else { } else {
Debug(1, "decoder %s hwConfig doesn't match our type: %s != %s, pix_fmt %s.", Debug(1, "Decoder %s hwConfig doesn't match our type: %s != %s, pix_fmt %s.",
mVideoCodec->name, mVideoCodec->name,
av_hwdevice_get_type_name(type), av_hwdevice_get_type_name(type),
av_hwdevice_get_type_name(config->device_type), av_hwdevice_get_type_name(config->device_type),
@ -532,36 +442,43 @@ int FfmpegCamera::OpenFfmpeg() {
); );
} }
} // end foreach hwconfig } // end foreach hwconfig
#else #else
hw_pix_fmt = find_fmt_by_hw_type(type); hw_pix_fmt = find_fmt_by_hw_type(type);
#endif #endif
if ( hw_pix_fmt != AV_PIX_FMT_NONE ) { if ( hw_pix_fmt != AV_PIX_FMT_NONE ) {
Debug(1, "Selected hw_pix_fmt %d %s", Debug(1, "Selected hw_pix_fmt %d %s",
hw_pix_fmt, av_get_pix_fmt_name(hw_pix_fmt)); hw_pix_fmt, av_get_pix_fmt_name(hw_pix_fmt));
mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
//if (!lavc_param->check_hw_profile)
mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH;
ret = av_hwdevice_ctx_create(&hw_device_ctx, type, ret = av_hwdevice_ctx_create(&hw_device_ctx, type,
(hwaccel_device != "" ? hwaccel_device.c_str(): nullptr), nullptr, 0); (hwaccel_device != "" ? hwaccel_device.c_str() : nullptr), nullptr, 0);
if ( ret < 0 and hwaccel_device != "" ) {
ret = av_hwdevice_ctx_create(&hw_device_ctx, type, nullptr, nullptr, 0);
}
if ( ret < 0 ) { if ( ret < 0 ) {
Error("Failed to create hwaccel device. %s",av_make_error_string(ret).c_str()); Error("Failed to create hwaccel device. %s", av_make_error_string(ret).c_str());
hw_pix_fmt = AV_PIX_FMT_NONE; hw_pix_fmt = AV_PIX_FMT_NONE;
} else { } else {
Debug(1, "Created hwdevice for %s", hwaccel_device.c_str()); Debug(1, "Created hwdevice for %s", hwaccel_device.c_str());
mVideoCodecContext->get_format = get_hw_format; mVideoCodecContext->get_format = get_hw_format;
mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx); mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx);
hwFrame = zm_av_frame_alloc(); hwFrame = zm_av_frame_alloc();
} }
} else { } else {
Debug(1, "Failed to find suitable hw_pix_fmt."); Debug(1, "Failed to find suitable hw_pix_fmt.");
} }
#else #else
Debug(1, "AVCodec not new enough for hwaccel"); Debug(1, "AVCodec not new enough for hwaccel");
#endif #endif
#else #else
Warning("HWAccel support not compiled in."); Warning("HWAccel support not compiled in.");
#endif #endif
} // end if hwaccel_name } // end if hwaccel_name
// Open the codec
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
ret = avcodec_open(mVideoCodecContext, mVideoCodec); ret = avcodec_open(mVideoCodecContext, mVideoCodec);
#else #else
@ -604,50 +521,16 @@ int FfmpegCamera::OpenFfmpeg() {
zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0); zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0);
// Open the codec // Open the codec
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
if ( avcodec_open(mAudioCodecContext, mAudioCodec) < 0 ) { if ( avcodec_open(mAudioCodecContext, mAudioCodec) < 0 )
#else #else
if ( avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr) < 0 ) { if ( avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr) < 0 )
#endif #endif
{
Error("Unable to open codec for audio stream from %s", mPath.c_str()); Error("Unable to open codec for audio stream from %s", mPath.c_str());
return -1; return -1;
} } // end if opened
zm_dump_codec(mAudioCodecContext); } // end if found decoder
} // end if find decoder } // end if have audio stream
} // end if have audio_context
// 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 == nullptr || mFrame == nullptr ) {
Error("Unable to allocate frame for %s", mPath.c_str());
return -1;
}
mFrame->width = width;
mFrame->height = height;
#if HAVE_LIBSWSCALE
if ( !sws_isSupportedInput(mVideoCodecContext->pix_fmt) ) {
Error("swscale does not support the codec format for input: %s",
av_get_pix_fmt_name(mVideoCodecContext->pix_fmt)
);
return -1;
}
if ( !sws_isSupportedOutput(imagePixFormat) ) {
Error("swscale does not support the target format: %s",
av_get_pix_fmt_name(imagePixFormat)
);
return -1;
}
#else // HAVE_LIBSWSCALE
Fatal("You must compile ffmpeg with the --enable-swscale "
"option to use ffmpeg cameras");
#endif // HAVE_LIBSWSCALE
if ( if (
((unsigned int)mVideoCodecContext->width != width) ((unsigned int)mVideoCodecContext->width != width)
@ -660,11 +543,10 @@ int FfmpegCamera::OpenFfmpeg() {
mCanCapture = true; mCanCapture = true;
return 0; return 1;
} // int FfmpegCamera::OpenFfmpeg() } // int FfmpegCamera::OpenFfmpeg()
int FfmpegCamera::Close() { int FfmpegCamera::Close() {
mCanCapture = false; mCanCapture = false;
if ( mFrame ) { if ( mFrame ) {
@ -682,22 +564,10 @@ int FfmpegCamera::Close() {
} }
#endif #endif
#if HAVE_LIBSWSCALE
if ( mConvertContext ) {
sws_freeContext(mConvertContext);
mConvertContext = nullptr;
}
#endif
if ( videoStore ) {
delete videoStore;
videoStore = nullptr;
}
if ( mVideoCodecContext ) { if ( mVideoCodecContext ) {
avcodec_close(mVideoCodecContext); avcodec_close(mVideoCodecContext);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
// avcodec_free_context(&mVideoCodecContext); avcodec_free_context(&mVideoCodecContext);
#endif #endif
mVideoCodecContext = nullptr; // Freed by av_close_input_file mVideoCodecContext = nullptr; // Freed by av_close_input_file
} }
@ -724,345 +594,9 @@ int FfmpegCamera::Close() {
mFormatContext = nullptr; mFormatContext = nullptr;
} }
if ( packetqueue ) {
delete packetqueue;
packetqueue = nullptr;
}
return 0; return 0;
} // end FfmpegCamera::Close } // end FfmpegCamera::Close
// Function to handle capture and store
int FfmpegCamera::CaptureAndRecord(
Image &image,
timeval recording,
char* event_file
) {
if ( !mCanCapture ) {
return -1;
}
int ret;
struct timeval video_buffer_duration = monitor->GetVideoBufferDuration();
int frameComplete = false;
while ( !frameComplete ) {
av_init_packet(&packet);
ret = av_read_frame(mFormatContext, &packet);
if ( ret < 0 ) {
if (
// Check if EOF.
(
(ret == AVERROR_EOF) ||
(mFormatContext->pb && mFormatContext->pb->eof_reached)
) ||
// Check for Connection failure.
(ret == -110)
) {
Info("Unable to read packet from stream %d: error %d \"%s\".",
packet.stream_index, ret, av_make_error_string(ret).c_str());
} else {
Error("Unable to read packet from stream %d: error %d \"%s\".",
packet.stream_index, ret, av_make_error_string(ret).c_str());
}
return -1;
}
int keyframe = packet.flags & AV_PKT_FLAG_KEY;
bytes += packet.size;
dumpPacket(
mFormatContext->streams[packet.stream_index],
&packet,
"Captured Packet");
if ( packet.dts == AV_NOPTS_VALUE ) {
packet.dts = packet.pts;
}
// Video recording
if ( recording.tv_sec ) {
uint32_t last_event_id = monitor->GetLastEventId();
uint32_t video_writer_event_id = monitor->GetVideoWriterEventId();
if ( last_event_id != video_writer_event_id ) {
Debug(2, "Have change of event. last_event(%d), our current (%d)",
last_event_id, video_writer_event_id);
if ( videoStore ) {
Info("Re-starting video storage module");
// I don't know if this is important or not... but I figure we might
// as well write this last packet out to the store before closing it.
// Also don't know how much it matters for audio.
if ( packet.stream_index == mVideoStreamId ) {
// Write the packet to our video store
int ret = videoStore->writeVideoFramePacket(&packet);
if ( ret < 0 ) { // Less than zero and we skipped a frame
Warning("Error writing last packet to videostore.");
}
} // end if video
delete videoStore;
videoStore = nullptr;
have_video_keyframe = false;
monitor->SetVideoWriterEventId(0);
} // end if videoStore
} // end if end of recording
if ( last_event_id && !videoStore ) {
// Instantiate the video storage module
packetqueue->dumpQueue();
if ( record_audio ) {
if ( mAudioStreamId == -1 ) {
Debug(3, "Record Audio on but no audio stream found");
videoStore = new VideoStore((const char *) event_file, "mp4",
mFormatContext->streams[mVideoStreamId],
nullptr,
this->getMonitor());
} else {
Debug(3, "Video module initiated with audio stream");
videoStore = new VideoStore((const char *) event_file, "mp4",
mFormatContext->streams[mVideoStreamId],
mFormatContext->streams[mAudioStreamId],
this->getMonitor());
}
} else {
if ( mAudioStreamId >= 0 ) {
Debug(3, "Record_audio is false so exclude audio stream");
}
videoStore = new VideoStore((const char *) event_file, "mp4",
mFormatContext->streams[mVideoStreamId],
nullptr,
this->getMonitor());
} // end if record_audio
if ( !videoStore->open() ) {
delete videoStore;
videoStore = nullptr;
} else {
monitor->SetVideoWriterEventId(last_event_id);
// Need to write out all the frames from the last keyframe?
// No... need to write out all frames from when the event began.
// Due to PreEventFrames, this could be more than
// since the last keyframe.
unsigned int packet_count = 0;
ZMPacket *queued_packet;
struct timeval video_offset = {0};
// Clear all packets that predate the moment when the recording began
packetqueue->clear_unwanted_packets(
&recording, 0, mVideoStreamId);
while ( (queued_packet = packetqueue->popPacket()) ) {
AVPacket *avp = queued_packet->av_packet();
// compute time offset between event start and first frame in video
if ( packet_count == 0 ) {
monitor->SetVideoWriterStartTime(queued_packet->timestamp);
timersub(&queued_packet->timestamp, &recording, &video_offset);
Info("Event video offset is %.3f sec (<0 means video starts early)",
video_offset.tv_sec + video_offset.tv_usec*1e-6);
}
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());
if ( avp->stream_index == mVideoStreamId ) {
ret = videoStore->writeVideoFramePacket(avp);
have_video_keyframe = true;
} else if ( avp->stream_index == mAudioStreamId ) {
ret = videoStore->writeAudioFramePacket(avp);
} else {
Warning("Unknown stream id in queued packet (%d)",
avp->stream_index);
ret = -1;
}
if ( ret < 0 ) {
// Less than zero and we skipped a frame
}
delete queued_packet;
} // end while packets in the packetqueue
Debug(2, "Wrote %d queued packets", packet_count);
} // end if videostore is open or not
} // end if ! was recording
} else {
// Not recording
if ( videoStore ) {
Debug(1, "Deleting videoStore instance");
delete videoStore;
videoStore = nullptr;
have_video_keyframe = false;
monitor->SetVideoWriterEventId(0);
}
} // end if recording or not
// Buffer video packets, since we are not recording.
// All audio packets are keyframes, so only if it's a video keyframe
if ( packet.stream_index == mVideoStreamId ) {
if ( keyframe ) {
Debug(3, "Clearing queue");
if (video_buffer_duration.tv_sec > 0 || video_buffer_duration.tv_usec > 0) {
packetqueue->clearQueue(&video_buffer_duration, mVideoStreamId);
}
else {
packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId);
}
if (
packetqueue->packet_count(mVideoStreamId)
>=
monitor->GetImageBufferCount()
) {
Warning(
"ImageBufferCount %d is too small. "
"Needs to be at least %d. "
"Either increase it or decrease time between keyframes",
monitor->GetImageBufferCount(),
packetqueue->packet_count(mVideoStreamId)+1);
}
packetqueue->queuePacket(&packet);
} else if ( packetqueue->size() ) {
// it's a keyframe or we already have something in the queue
packetqueue->queuePacket(&packet);
}
} else if ( packet.stream_index == mAudioStreamId ) {
// Ensure that the queue always begins with a video keyframe
if ( record_audio && packetqueue->size() ) {
packetqueue->queuePacket(&packet);
}
} // end if packet type
if ( packet.stream_index == mVideoStreamId ) {
if ( (have_video_keyframe || keyframe) && videoStore ) {
int ret = videoStore->writeVideoFramePacket(&packet);
if ( ret < 0 ) {
// Less than zero and we skipped a frame
Error("Unable to write video packet code: %d, framecount %d: %s",
ret, frameCount, av_make_error_string(ret).c_str());
} else {
have_video_keyframe = true;
}
} // end if keyframe or have_video_keyframe
if ( monitor->DecodingEnabled() ) {
ret = zm_send_packet_receive_frame(mVideoCodecContext, mRawFrame, packet);
if ( ret < 0 ) {
if ( AVERROR(EAGAIN) != ret ) {
Warning("Unable to receive frame %d: code %d %s. error count is %d",
frameCount, ret, av_make_error_string(ret).c_str(), error_count);
error_count += 1;
if ( error_count > 100 ) {
Error("Error count over 100, going to close and re-open stream");
return -1;
}
#if HAVE_LIBAVUTIL_HWCONTEXT_H
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
if ( (ret == AVERROR_INVALIDDATA ) && (hw_pix_fmt != AV_PIX_FMT_NONE) ) {
use_hwaccel = false;
return -1;
}
#endif
#endif
}
zm_av_packet_unref(&packet);
continue;
}
if ( error_count > 0 ) error_count--;
zm_dump_video_frame(mRawFrame, "raw frame from decoder");
#if HAVE_LIBAVUTIL_HWCONTEXT_H
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
if (
(hw_pix_fmt != AV_PIX_FMT_NONE)
&&
(mRawFrame->format == hw_pix_fmt)
) {
/* retrieve data from GPU to CPU */
ret = av_hwframe_transfer_data(hwFrame, mRawFrame, 0);
if ( ret < 0 ) {
Error("Unable to transfer frame at frame %d: %s, continuing",
frameCount, av_make_error_string(ret).c_str());
zm_av_packet_unref(&packet);
continue;
}
zm_dump_video_frame(hwFrame, "After hwtransfer");
hwFrame->pts = mRawFrame->pts;
input_frame = hwFrame;
} else {
#endif
#endif
input_frame = mRawFrame;
#if HAVE_LIBAVUTIL_HWCONTEXT_H
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
}
#endif
#endif
if ( transfer_to_image(image, mFrame, input_frame) < 0 ) {
Error("Failed to transfer from frame to image");
zm_av_packet_unref(&packet);
return -1;
}
} // end if don't need to do decode
frameComplete = 1;
frameCount++;
} else if ( packet.stream_index == mAudioStreamId ) {
// FIXME best way to copy all other streams
frameComplete = 1;
if ( videoStore ) {
if ( record_audio ) {
if ( have_video_keyframe ) {
// Write the packet to our video store
// FIXME no relevance of last key frame
int ret = videoStore->writeAudioFramePacket(&packet);
if ( ret < 0 ) {
// Less than zero and we skipped a frame
Warning("Failure to write audio packet.");
zm_av_packet_unref(&packet);
return 0;
}
} else {
Debug(3, "Not recording audio because no video keyframe");
}
} else {
Debug(4, "Not doing recording of audio packet");
}
} else {
Debug(4, "Have audio packet, but not recording atm");
}
zm_av_packet_unref(&packet);
return 0;
} else {
#if LIBAVUTIL_VERSION_CHECK(56, 23, 0, 23, 0)
Debug(3, "Some other stream index %d, %s",
packet.stream_index,
av_get_media_type_string(
mFormatContext->streams[packet.stream_index]->codecpar->codec_type)
);
#else
Debug(3, "Some other stream index %d", packet.stream_index);
#endif
} // end if is video or audio or something else
// the packet contents are ref counted... when queuing, we allocate another
// packet and reference it with that one, so we should always need to unref
// here, which should not affect the queued version.
zm_av_packet_unref(&packet);
} // end while ! frameComplete
return frameCount;
} // end FfmpegCamera::CaptureAndRecord
int FfmpegCamera::transfer_to_image( int FfmpegCamera::transfer_to_image(
Image &image, Image &image,
AVFrame *output_frame, AVFrame *output_frame,

View File

@ -25,7 +25,6 @@
#include "zm_buffer.h" #include "zm_buffer.h"
#include "zm_ffmpeg.h" #include "zm_ffmpeg.h"
#include "zm_videostore.h" #include "zm_videostore.h"
#include "zm_packetqueue.h"
#if HAVE_LIBAVUTIL_HWCONTEXT_H #if HAVE_LIBAVUTIL_HWCONTEXT_H
typedef struct DecodeContext { typedef struct DecodeContext {
@ -41,6 +40,8 @@ class FfmpegCamera : public Camera {
std::string mPath; std::string mPath;
std::string mMethod; std::string mMethod;
std::string mOptions; std::string mOptions;
std::string encoder_options;
std::string hwaccel_name; std::string hwaccel_name;
std::string hwaccel_device; std::string hwaccel_device;
@ -50,15 +51,12 @@ class FfmpegCamera : public Camera {
#if HAVE_LIBAVFORMAT #if HAVE_LIBAVFORMAT
AVFormatContext *mFormatContext; AVFormatContext *mFormatContext;
int mVideoStreamId;
int mAudioStreamId;
AVCodecContext *mVideoCodecContext;
AVCodecContext *mAudioCodecContext;
AVCodec *mVideoCodec; AVCodec *mVideoCodec;
AVCodec *mAudioCodec; AVCodec *mAudioCodec;
AVFrame *mRawFrame; AVFrame *mRawFrame;
AVFrame *mFrame; AVFrame *mFrame;
_AVPIXELFORMAT imagePixFormat; _AVPIXELFORMAT imagePixFormat;
AVFrame *input_frame; // Use to point to mRawFrame or hwFrame; AVFrame *input_frame; // Use to point to mRawFrame or hwFrame;
AVFrame *hwFrame; // Will also be used to indicate if hwaccel is in use AVFrame *hwFrame; // Will also be used to indicate if hwaccel is in use
@ -77,10 +75,6 @@ class FfmpegCamera : public Camera {
bool mCanCapture; bool mCanCapture;
#endif // HAVE_LIBAVFORMAT #endif // HAVE_LIBAVFORMAT
VideoStore *videoStore;
zm_packetqueue *packetqueue;
bool have_video_keyframe;
#if HAVE_LIBSWSCALE #if HAVE_LIBSWSCALE
struct SwsContext *mConvertContext; struct SwsContext *mConvertContext;
#endif #endif
@ -112,14 +106,22 @@ class FfmpegCamera : public Camera {
const std::string &Options() const { return mOptions; } const std::string &Options() const { return mOptions; }
const std::string &Method() const { return mMethod; } const std::string &Method() const { return mMethod; }
void Initialise();
void Terminate();
int PrimeCapture(); int PrimeCapture();
int PreCapture(); int PreCapture();
int Capture( Image &image ); int Capture(ZMPacket &p);
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
int PostCapture(); int PostCapture();
AVStream *get_VideoStream() {
if ( mVideoStreamId != -1 )
return mFormatContext->streams[mVideoStreamId];
return nullptr;
}
AVStream *get_AudioStream() {
if ( mAudioStreamId != -1 )
return mFormatContext->streams[mAudioStreamId];
return nullptr;
}
AVCodecContext *get_VideoCodecContext() { return mVideoCodecContext; };
AVCodecContext *get_AudioCodecContext() { return mAudioCodecContext; };
private: private:
static int FfmpegInterruptCallback(void*ctx); static int FfmpegInterruptCallback(void*ctx);
int transfer_to_image(Image &i, AVFrame *output_frame, AVFrame *input_frame); int transfer_to_image(Image &i, AVFrame *output_frame, AVFrame *input_frame);

View File

@ -14,28 +14,29 @@ FFmpeg_Input::FFmpeg_Input() {
} }
FFmpeg_Input::~FFmpeg_Input() { FFmpeg_Input::~FFmpeg_Input() {
if ( streams ) { if ( input_format_context ) {
for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { Close();
avcodec_close(streams[i].context);
avcodec_free_context(&streams[i].context);
}
delete[] streams;
streams = nullptr;
} }
if ( frame ) { if ( frame ) {
av_frame_free(&frame); av_frame_free(&frame);
frame = nullptr; frame = nullptr;
} }
if ( input_format_context ) {
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
av_close_input_file(input_format_context);
#else
avformat_close_input(&input_format_context);
#endif
input_format_context = nullptr;
}
} // end ~FFmpeg_Input() } // end ~FFmpeg_Input()
int FFmpeg_Input::Open(
const AVStream * video_in_stream,
const AVStream * audio_in_stream
) {
video_stream_id = video_in_stream->index;
int max_stream_index = video_in_stream->index;
if ( audio_in_stream ) {
max_stream_index = video_in_stream->index > audio_in_stream->index ? video_in_stream->index : audio_in_stream->index;
audio_stream_id = audio_in_stream->index;
}
streams = new stream[max_stream_index];
}
int FFmpeg_Input::Open(const char *filepath) { int FFmpeg_Input::Open(const char *filepath) {
int error; int error;
@ -43,8 +44,8 @@ int FFmpeg_Input::Open(const char *filepath) {
/** Open the input file to read from it. */ /** Open the input file to read from it. */
error = avformat_open_input(&input_format_context, filepath, nullptr, nullptr); error = avformat_open_input(&input_format_context, filepath, nullptr, nullptr);
if ( error < 0 ) { if ( error < 0 ) {
Error("Could not open input file '%s' (error '%s')\n", Error("Could not open input file '%s' (error '%s')",
filepath, av_make_error_string(error).c_str() ); filepath, av_make_error_string(error).c_str());
input_format_context = nullptr; input_format_context = nullptr;
return error; return error;
} }
@ -119,6 +120,30 @@ int FFmpeg_Input::Open(const char *filepath) {
return 0; return 0;
} // end int FFmpeg_Input::Open( const char * filepath ) } // end int FFmpeg_Input::Open( const char * filepath )
int FFmpeg_Input::Close( ) {
if ( streams ) {
for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) {
avcodec_close(streams[i].context);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
avcodec_free_context(&streams[i].context);
streams[i].context = nullptr;
#endif
}
delete[] streams;
streams = nullptr;
}
if ( input_format_context ) {
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
av_close_input_file(input_format_context);
#else
avformat_close_input(&input_format_context);
#endif
input_format_context = nullptr;
}
return 1;
} // end int FFmpeg_Input::Close()
AVFrame *FFmpeg_Input::get_frame(int stream_id) { AVFrame *FFmpeg_Input::get_frame(int stream_id) {
int frameComplete = false; int frameComplete = false;
AVPacket packet; AVPacket packet;
@ -142,40 +167,41 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id) {
} }
dumpPacket(input_format_context->streams[packet.stream_index], &packet, "Received packet"); dumpPacket(input_format_context->streams[packet.stream_index], &packet, "Received packet");
if ( (stream_id < 0) || (packet.stream_index == stream_id) ) { if ( (stream_id >= 0) && (packet.stream_index != stream_id) ) {
Debug(3, "Packet is for our stream (%d)", packet.stream_index); Debug(1,"Packet is not for our stream (%d)", packet.stream_index );
continue;
}
AVCodecContext *context = streams[packet.stream_index].context; AVCodecContext *context = streams[packet.stream_index].context;
if ( frame ) { if ( frame ) {
av_frame_free(&frame); av_frame_free(&frame);
frame = zm_av_frame_alloc(); frame = zm_av_frame_alloc();
} else {
frame = zm_av_frame_alloc();
}
ret = zm_send_packet_receive_frame(context, frame, packet);
if ( ret < 0 ) {
Error("Unable to decode frame at frame %d: %d %s, continuing",
streams[packet.stream_index].frame_count, ret, av_make_error_string(ret).c_str());
zm_av_packet_unref(&packet);
av_frame_free(&frame);
continue;
} else {
if ( is_video_stream(input_format_context->streams[packet.stream_index]) ) {
zm_dump_video_frame(frame, "resulting video frame");
} else { } else {
frame = zm_av_frame_alloc(); zm_dump_frame(frame, "resulting frame");
}
ret = zm_send_packet_receive_frame(context, frame, packet);
if ( ret < 0 ) {
Error("Unable to decode frame at frame %d: %d %s, continuing",
streams[packet.stream_index].frame_count, ret, av_make_error_string(ret).c_str());
zm_av_packet_unref(&packet);
av_frame_free(&frame);
continue;
} else {
if ( is_video_stream(input_format_context->streams[packet.stream_index]) ) {
zm_dump_video_frame(frame, "resulting video frame");
} else {
zm_dump_frame(frame, "resulting frame");
}
} }
}
frameComplete = 1; frameComplete = 1;
} // end if it's the right stream
zm_av_packet_unref(&packet); zm_av_packet_unref(&packet);
} // end while ! frameComplete } // end while !frameComplete
return frame; return frame;
} // end AVFrame *FFmpeg_Input::get_frame } // end AVFrame *FFmpeg_Input::get_frame
AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) {
Debug(1, "Getting frame from stream %d at %f", stream_id, at); Debug(1, "Getting frame from stream %d at %f", stream_id, at);
@ -220,7 +246,11 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) {
} }
// Have to grab a frame to update our current frame to know where we are // Have to grab a frame to update our current frame to know where we are
get_frame(stream_id); get_frame(stream_id);
zm_dump_frame(frame, "frame->pts > seek_target, got"); if ( is_video_stream(input_format_context->streams[stream_id]) ) {
zm_dump_video_frame(frame, "frame->pts > seek_target, got");
} else {
zm_dump_frame(frame, "frame->pts > seek_target, got");
}
} else if ( last_seek_request == seek_target ) { } else if ( last_seek_request == seek_target ) {
// paused case, sending keepalives // paused case, sending keepalives
return frame; return frame;
@ -230,7 +260,11 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) {
// Seeking seems to typically seek to a keyframe, so then we have to decode until we get the frame we want. // Seeking seems to typically seek to a keyframe, so then we have to decode until we get the frame we want.
if ( frame->pts <= seek_target ) { if ( frame->pts <= seek_target ) {
zm_dump_frame(frame, "pts <= seek_target"); if ( is_video_stream(input_format_context->streams[stream_id]) ) {
zm_dump_video_frame(frame, "pts <= seek_target");
} else {
zm_dump_frame(frame, "pts <= seek_target");
}
while ( frame && (frame->pts < seek_target) ) { while ( frame && (frame->pts < seek_target) ) {
if ( !get_frame(stream_id) ) { if ( !get_frame(stream_id) ) {
Warning("Got no frame. returning nothing"); Warning("Got no frame. returning nothing");

View File

@ -20,6 +20,7 @@ class FFmpeg_Input {
~FFmpeg_Input(); ~FFmpeg_Input();
int Open( const char *filename ); int Open( const char *filename );
int Open( const AVStream *, const AVStream * );
int Close(); int Close();
AVFrame *get_frame( int stream_id=-1 ); AVFrame *get_frame( int stream_id=-1 );
AVFrame *get_frame( int stream_id, double at ); AVFrame *get_frame( int stream_id, double at );

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

@ -94,8 +94,8 @@ int FileCamera::PreCapture() {
return 0; return 0;
} }
int FileCamera::Capture(Image &image) { int FileCamera::Capture( ZMPacket &zm_packet ) {
return image.ReadJpeg(path, colours, subpixelorder)?1:-1; return zm_packet.image->ReadJpeg(path, colours, subpixelorder) ? 1 : -1;
} }
int FileCamera::PostCapture() { int FileCamera::PostCapture() {

View File

@ -23,7 +23,7 @@
#include "zm_camera.h" #include "zm_camera.h"
#include "zm_buffer.h" #include "zm_buffer.h"
#include "zm_regexp.h" #include "zm_regexp.h"
#include "zm_packetqueue.h" #include "zm_packet.h"
#include <sys/param.h> #include <sys/param.h>
@ -36,7 +36,19 @@ protected:
char path[PATH_MAX]; char path[PATH_MAX];
public: public:
FileCamera( int p_id, const char *p_path, 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 ); FileCamera(
int p_id,
const char *p_path,
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
);
~FileCamera(); ~FileCamera();
const char *Path() const { return( path ); } const char *Path() const { return( path ); }
@ -44,10 +56,9 @@ public:
void Initialise(); void Initialise();
void Terminate(); void Terminate();
int PreCapture(); int PreCapture();
int Capture( Image &image ); int Capture( ZMPacket &p );
int PostCapture(); int PostCapture();
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; int Close() { return 0; };
int Close() { return 0; };
}; };
#endif // ZM_FILE_CAMERA_H #endif // ZM_FILE_CAMERA_H

View File

@ -166,6 +166,7 @@ Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint
AllocImgBuffer(size); AllocImgBuffer(size);
} }
text[0] = '\0'; text[0] = '\0';
imagePixFormat = AVPixFormat();
update_function_pointers(); update_function_pointers();
} }
@ -193,59 +194,107 @@ Image::Image(int p_width, int p_linesize, int p_height, int p_colours, int p_sub
AllocImgBuffer(size); AllocImgBuffer(size);
} }
text[0] = '\0'; text[0] = '\0';
imagePixFormat = AVPixFormat();
update_function_pointers(); update_function_pointers();
} }
Image::Image(const AVFrame *frame) { Image::Image(const AVFrame *frame) {
AVFrame *dest_frame = zm_av_frame_alloc();
text[0] = '\0'; text[0] = '\0';
width = frame->width; width = frame->width;
height = frame->height; height = frame->height;
pixels = width*height; pixels = width*height;
zm_dump_video_frame(frame, "Image.Assign(frame)");
// FIXME
colours = ZM_COLOUR_RGB32; colours = ZM_COLOUR_RGB32;
subpixelorder = ZM_SUBPIX_ORDER_RGBA; subpixelorder = ZM_SUBPIX_ORDER_RGBA;
imagePixFormat = AV_PIX_FMT_RGBA;
//(AVPixelFormat)frame->format;
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, width, height, 32); size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, width, height, 32);
// av_image_get_linesize isn't aligned, so we have to do that. // av_image_get_linesize isn't aligned, so we have to do that.
linesize = FFALIGN(av_image_get_linesize(AV_PIX_FMT_RGBA, width, 0), 32); linesize = FFALIGN(av_image_get_linesize(AV_PIX_FMT_RGBA, width, 0), 32);
#else #else
linesize = FFALIGN(av_image_get_linesize(AV_PIX_FMT_RGBA, width, 0), 1); linesize = FFALIGN(av_image_get_linesize(AV_PIX_FMT_RGBA, width, 0), 1);
size = avpicture_get_size(AV_PIX_FMT_RGBA, width, height); size = avpicture_get_size(AV_PIX_FMT_RGB0, width, height);
#endif #endif
buffer = nullptr; buffer = nullptr;
holdbuffer = 0; holdbuffer = 0;
AllocImgBuffer(size); AllocImgBuffer(size);
this->Assign(frame);
}
static void dont_free(void *opaque, uint8_t *data) {
}
int Image::PopulateFrame(AVFrame *frame) {
Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d %s",
width, height, linesize, colours, size,
av_get_pix_fmt_name(imagePixFormat)
);
AVBufferRef *ref = av_buffer_create(buffer, size,
dont_free, /* Free callback */
nullptr, /* opaque */
0 /* flags */
);
if ( !ref ) {
Warning("Failed to create av_buffer ");
}
frame->buf[0] = ref;
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
av_image_fill_arrays(dest_frame->data, dest_frame->linesize, // From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too.
buffer, AV_PIX_FMT_RGBA, width, height, 32); int size = av_image_fill_arrays(
frame->data, frame->linesize,
buffer, imagePixFormat, width, height,
32 //alignment
);
if ( size < 0 ) {
Error("Problem setting up data pointers into image %s",
av_make_error_string(size).c_str());
return size;
}
#else #else
avpicture_fill( (AVPicture *)dest_frame, buffer, avpicture_fill((AVPicture *)frame, buffer,
AV_PIX_FMT_RGBA, width, height); imagePixFormat, width, height);
#endif #endif
frame->width = width;
frame->height = height;
frame->format = imagePixFormat;
Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d", width, height, linesize, colours, size);
zm_dump_video_frame(frame, "Image.Populate(frame)");
return 1;
}
void Image::Assign(const AVFrame *frame) {
/* Assume the dimensions etc are correct. FIXME */
// Desired format
AVPixelFormat format = (AVPixelFormat)AVPixFormat();
AVFrame *dest_frame = zm_av_frame_alloc();
PopulateFrame(dest_frame);
zm_dump_video_frame(frame, "source frame");
zm_dump_video_frame(dest_frame, "dest frame before convert");
#if HAVE_LIBSWSCALE #if HAVE_LIBSWSCALE
sws_convert_context = sws_getCachedContext( sws_convert_context = sws_getCachedContext(
sws_convert_context, sws_convert_context,
width, width, height, (AVPixelFormat)frame->format,
height, width, height, format,
(AVPixelFormat)frame->format, SWS_BICUBIC, nullptr, nullptr, nullptr);
width, height,
AV_PIX_FMT_RGBA, SWS_BICUBIC, nullptr,
nullptr, nullptr);
if ( sws_convert_context == nullptr ) if ( sws_convert_context == nullptr )
Fatal("Unable to create conversion context"); Fatal("Unable to create conversion context");
if ( sws_scale(sws_convert_context, frame->data, frame->linesize, 0, frame->height, if ( sws_scale(sws_convert_context,
frame->data, frame->linesize, 0, frame->height,
dest_frame->data, dest_frame->linesize) < 0 ) dest_frame->data, dest_frame->linesize) < 0 )
Fatal("Unable to convert raw format %u to target format %u", frame->format, AV_PIX_FMT_RGBA); Fatal("Unable to convert raw format %u to target format %u", frame->format, AV_PIX_FMT_RGBA);
#else // HAVE_LIBSWSCALE #else // HAVE_LIBSWSCALE
Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras"); Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras");
#endif // HAVE_LIBSWSCALE #endif // HAVE_LIBSWSCALE
zm_dump_video_frame(dest_frame, "dest frame after convert");
av_frame_free(&dest_frame); av_frame_free(&dest_frame);
update_function_pointers(); update_function_pointers();
} // end Image::Image(const AVFrame *frame) } // end Image::Image(const AVFrame *frame)
@ -265,6 +314,7 @@ Image::Image(const Image &p_image) {
AllocImgBuffer(size); AllocImgBuffer(size);
(*fptr_imgbufcpy)(buffer, p_image.buffer, size); (*fptr_imgbufcpy)(buffer, p_image.buffer, size);
strncpy(text, p_image.text, sizeof(text)); strncpy(text, p_image.text, sizeof(text));
imagePixFormat = p_image.imagePixFormat;
update_function_pointers(); update_function_pointers();
} }
@ -679,6 +729,7 @@ void Image::Assign(
if ( new_buffer != buffer ) if ( new_buffer != buffer )
(*fptr_imgbufcpy)(buffer, new_buffer, size); (*fptr_imgbufcpy)(buffer, new_buffer, size);
update_function_pointers();
} }
void Image::Assign(const Image &image) { void Image::Assign(const Image &image) {
@ -710,7 +761,7 @@ void Image::Assign(const Image &image) {
return; return;
} }
} else { } else {
if ( new_size > allocation || !buffer) { if ( new_size > allocation || !buffer ) {
// DumpImgBuffer(); This is also done in AllocImgBuffer // DumpImgBuffer(); This is also done in AllocImgBuffer
AllocImgBuffer(new_size); AllocImgBuffer(new_size);
} }
@ -1796,8 +1847,8 @@ Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb thres
return result; return result;
} }
/* New function to allow buffer re-using instead of allocating memory for the delta image every time */ /* New function to allow buffer re-using instead of allocationg memory for the delta image every time */
void Image::Delta( const Image &image, Image* targetimage) const { void Image::Delta(const Image &image, Image* targetimage) const {
#ifdef ZM_IMAGE_PROFILING #ifdef ZM_IMAGE_PROFILING
struct timespec start,end,diff; struct timespec start,end,diff;
unsigned long long executetime; unsigned long long executetime;
@ -1940,8 +1991,7 @@ void Image::Annotate(
const Coord &coord, const Coord &coord,
const unsigned int size, const unsigned int size,
const Rgb fg_colour, const Rgb fg_colour,
const Rgb bg_colour const Rgb bg_colour) {
) {
strncpy(text, p_text, sizeof(text)-1); strncpy(text, p_text, sizeof(text)-1);
unsigned int index = 0; unsigned int index = 0;
@ -1967,7 +2017,7 @@ void Image::Annotate(
const uint16_t char_width = font.GetCharWidth(); const uint16_t char_width = font.GetCharWidth();
const uint16_t char_height = font.GetCharHeight(); const uint16_t char_height = font.GetCharHeight();
const uint64_t *font_bitmap = font.GetBitmapData(); const uint64_t *font_bitmap = font.GetBitmapData();
Debug(1, "Font size %d, char_width %d char_height %d", size, char_width, char_height); Debug(4, "Font size %d, char_width %d char_height %d", size, char_width, char_height);
while ( (index < text_len) && (line_len = strcspn(line, "\n")) ) { while ( (index < text_len) && (line_len = strcspn(line, "\n")) ) {
unsigned int line_width = line_len * char_width; unsigned int line_width = line_len * char_width;
@ -5234,3 +5284,5 @@ __attribute__((noinline)) void std_deinterlace_4field_abgr(uint8_t* col1, uint8_
pncurrent += 4; pncurrent += 4;
} }
} }

View File

@ -108,6 +108,7 @@ protected:
double _1_m; double _1_m;
static int CompareYX( const void *p1, const void *p2 ) { static int CompareYX( const void *p1, const void *p2 ) {
// This is because these functions are passed to qsort
const Edge *e1 = reinterpret_cast<const Edge *>(p1), *e2 = reinterpret_cast<const Edge *>(p2); const Edge *e1 = reinterpret_cast<const Edge *>(p1), *e2 = reinterpret_cast<const Edge *>(p2);
if ( e1->min_y == e2->min_y ) if ( e1->min_y == e2->min_y )
return( int(e1->min_x - e2->min_x) ); return( int(e1->min_x - e2->min_x) );
@ -161,6 +162,7 @@ protected:
unsigned int size; unsigned int size;
unsigned int subpixelorder; unsigned int subpixelorder;
unsigned long allocation; unsigned long allocation;
_AVPIXELFORMAT imagePixFormat;
uint8_t *buffer; uint8_t *buffer;
int buffertype; /* 0=not ours, no need to call free(), 1=malloc() buffer, 2=new buffer */ int buffertype; /* 0=not ours, no need to call free(), 1=malloc() buffer, 2=new buffer */
int holdbuffer; /* Hold the buffer instead of replacing it with new one */ int holdbuffer; /* Hold the buffer instead of replacing it with new one */
@ -171,8 +173,8 @@ public:
explicit Image(const char *filename); explicit Image(const char *filename);
Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer=0, unsigned int padding=0); Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer=0, unsigned int padding=0);
Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer=0, unsigned int padding=0); Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer=0, unsigned int padding=0);
explicit Image( const Image &p_image ); explicit Image(const Image &p_image);
explicit Image( const AVFrame *frame ); explicit Image(const AVFrame *frame);
~Image(); ~Image();
static void Initialise(); static void Initialise();
@ -185,12 +187,31 @@ public:
inline unsigned int Colours() const { return colours; } inline unsigned int Colours() const { return colours; }
inline unsigned int SubpixelOrder() const { return subpixelorder; } inline unsigned int SubpixelOrder() const { return subpixelorder; }
inline unsigned int Size() const { return size; } inline unsigned int Size() const { return size; }
inline AVPixelFormat AVPixFormat() {
if ( colours == ZM_COLOUR_RGB32 ) {
return AV_PIX_FMT_RGBA;
} else if ( colours == ZM_COLOUR_RGB24 ) {
if ( subpixelorder == ZM_SUBPIX_ORDER_BGR){
return AV_PIX_FMT_BGR24;
} else {
return AV_PIX_FMT_RGB24;
}
} else if ( colours == ZM_COLOUR_GRAY8 ) {
return AV_PIX_FMT_GRAY8;
} else {
Error("Unknown colours (%d)",colours);
return AV_PIX_FMT_RGBA;
}
}
/* Internal buffer should not be modified from functions outside of this class */ /* Internal buffer should not be modified from functions outside of this class */
inline const uint8_t* Buffer() const { return buffer; } inline const uint8_t* Buffer() const { return buffer; }
inline const uint8_t* Buffer( unsigned int x, unsigned int y= 0 ) const { return &buffer[(y*linesize)+x]; } inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize)+x]; }
/* Request writeable buffer */ /* Request writeable buffer */
uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder);
// Is only acceptable on a pre-allocated buffer
uint8_t* WriteBuffer() { return holdbuffer ? buffer : nullptr; };
inline int IsBufferHeld() const { return holdbuffer; } inline int IsBufferHeld() const { return holdbuffer; }
inline void HoldBuffer(int tohold) { holdbuffer = tohold; } inline void HoldBuffer(int tohold) { holdbuffer = tohold; }
@ -210,6 +231,7 @@ public:
const uint8_t* new_buffer, const uint8_t* new_buffer,
const size_t buffer_size); const size_t buffer_size);
void Assign(const Image &image); void Assign(const Image &image);
void Assign(const AVFrame *frame);
void AssignDirect( void AssignDirect(
const unsigned int p_width, const unsigned int p_width,
const unsigned int p_height, const unsigned int p_height,
@ -219,6 +241,8 @@ public:
const size_t buffer_size, const size_t buffer_size,
const int p_buffertype); const int p_buffertype);
int PopulateFrame(AVFrame *frame);
inline void CopyBuffer(const Image &image) { inline void CopyBuffer(const Image &image) {
Assign(image); Assign(image);
} }
@ -231,40 +255,39 @@ public:
return *this; return *this;
} }
bool ReadRaw( const char *filename ); bool ReadRaw(const char *filename);
bool WriteRaw( const char *filename ) const; bool WriteRaw(const char *filename) const;
bool ReadJpeg( const char *filename, unsigned int p_colours, unsigned int p_subpixelorder); bool ReadJpeg(const char *filename, unsigned int p_colours, unsigned int p_subpixelorder);
bool WriteJpeg ( const char *filename) const; bool WriteJpeg(const char *filename) const;
bool WriteJpeg ( const char *filename, bool on_blocking_abort) const; bool WriteJpeg(const char *filename, bool on_blocking_abort) const;
bool WriteJpeg ( const char *filename, int quality_override ) const; bool WriteJpeg(const char *filename, int quality_override) const;
bool WriteJpeg ( const char *filename, struct timeval timestamp ) const; bool WriteJpeg(const char *filename, struct timeval timestamp) const;
bool WriteJpeg ( const char *filename, int quality_override, struct timeval timestamp ) const; bool WriteJpeg(const char *filename, int quality_override, struct timeval timestamp) const;
bool WriteJpeg ( const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort ) const; bool WriteJpeg(const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort) const;
bool DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder);
bool DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder); bool EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_override=0) const;
bool EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size, int quality_override=0 ) const;
#if HAVE_ZLIB_H #if HAVE_ZLIB_H
bool Unzip( const Bytef *inbuffer, unsigned long inbuffer_size ); bool Unzip(const Bytef *inbuffer, unsigned long inbuffer_size);
bool Zip( Bytef *outbuffer, unsigned long *outbuffer_size, int compression_level=Z_BEST_SPEED ) const; bool Zip(Bytef *outbuffer, unsigned long *outbuffer_size, int compression_level=Z_BEST_SPEED) const;
#endif // HAVE_ZLIB_H #endif // HAVE_ZLIB_H
bool Crop( unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsigned int hi_y ); bool Crop(unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsigned int hi_y);
bool Crop( const Box &limits ); bool Crop(const Box &limits);
void Overlay( const Image &image ); void Overlay(const Image &image);
void Overlay( const Image &image, unsigned int x, unsigned int y ); void Overlay(const Image &image, unsigned int x, unsigned int y);
void Blend( const Image &image, int transparency=12 ); void Blend(const Image &image, int transparency=12);
static Image *Merge( unsigned int n_images, Image *images[] ); static Image *Merge( unsigned int n_images, Image *images[] );
static Image *Merge( unsigned int n_images, Image *images[], double weight ); static Image *Merge( unsigned int n_images, Image *images[], double weight );
static Image *Highlight( unsigned int n_images, Image *images[], const Rgb threshold=RGB_BLACK, const Rgb ref_colour=RGB_RED ); static Image *Highlight( unsigned int n_images, Image *images[], const Rgb threshold=RGB_BLACK, const Rgb ref_colour=RGB_RED );
//Image *Delta( const Image &image ) const; //Image *Delta( const Image &image ) const;
void Delta( const Image &image, Image* targetimage) const; void Delta( const Image &image, Image* targetimage) const;
const Coord centreCoord( const char *text, const int size ) const; const Coord centreCoord(const char *text, const int size) const;
void MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour=0x00222222 ); void MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour=0x00222222 );
void Annotate( const char *p_text, const Coord &coord, const unsigned int size=1, const Rgb fg_colour=RGB_WHITE, const Rgb bg_colour=RGB_BLACK ); void Annotate( const char *p_text, const Coord &coord, const unsigned int size=1, const Rgb fg_colour=RGB_WHITE, const Rgb bg_colour=RGB_BLACK );
Image *HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p_subpixelorder, const Box *limits=0 ); Image *HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p_subpixelorder, const Box *limits=0 );

View File

@ -263,8 +263,7 @@ int LibvlcCamera::PreCapture() {
} }
// Should not return -1 as cancels capture. Always wait for image if available. // Should not return -1 as cancels capture. Always wait for image if available.
int LibvlcCamera::Capture(Image &image) { int LibvlcCamera::Capture( ZMPacket &zm_packet ) {
// newImage is a mutex/condition based flag to tell us when there is an image available // newImage is a mutex/condition based flag to tell us when there is an image available
while( !mLibvlcData.newImage.getValueImmediate() ) { while( !mLibvlcData.newImage.getValueImmediate() ) {
if (zm_terminate) if (zm_terminate)
@ -273,17 +272,13 @@ int LibvlcCamera::Capture(Image &image) {
} }
mLibvlcData.mutex.lock(); mLibvlcData.mutex.lock();
image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); zm_packet.image->Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp);
mLibvlcData.newImage.setValueImmediate(false); mLibvlcData.newImage.setValueImmediate(false);
mLibvlcData.mutex.unlock(); mLibvlcData.mutex.unlock();
return 1; return 1;
} }
int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) {
return 0;
}
int LibvlcCamera::PostCapture() { int LibvlcCamera::PostCapture() {
return 0; return 0;
} }

View File

@ -31,8 +31,7 @@
#endif #endif
// Used by libvlc callbacks // Used by libvlc callbacks
struct LibvlcPrivateData struct LibvlcPrivateData {
{
uint8_t* buffer; uint8_t* buffer;
uint8_t* prevBuffer; uint8_t* prevBuffer;
time_t prevTime; time_t prevTime;
@ -70,8 +69,7 @@ public:
int PrimeCapture(); int PrimeCapture();
int PreCapture(); int PreCapture();
int Capture( Image &image ); int Capture( ZMPacket &p );
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
int PostCapture(); int PostCapture();
int Close() { return 0; }; int Close() { return 0; };
}; };

View File

@ -162,7 +162,7 @@ int VncCamera::PrimeCapture() {
mRfb = nullptr; mRfb = nullptr;
return -1; return -1;
} }
if ( (mRfb->width != width) or (mRfb->height != height) ) { if ( ((unsigned int)mRfb->width != width) or ((unsigned int)mRfb->height != height) ) {
Warning("Specified dimensions do not match screen size monitor: (%dx%d) != vnc: (%dx%d)", Warning("Specified dimensions do not match screen size monitor: (%dx%d) != vnc: (%dx%d)",
width, height, mRfb->width, mRfb->height); width, height, mRfb->width, mRfb->height);
} }
@ -182,11 +182,11 @@ int VncCamera::PreCapture() {
return res == TRUE ? 1 : -1; return res == TRUE ? 1 : -1;
} }
int VncCamera::Capture(Image &image) { int VncCamera::Capture(ZMPacket &zm_packet) {
if ( ! mVncData.buffer ) { if ( ! mVncData.buffer ) {
return 0; return 0;
} }
uint8_t *directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); uint8_t *directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder);
int rc = scale.Convert( int rc = scale.Convert(
mVncData.buffer, mVncData.buffer,
mRfb->si.framebufferHeight * mRfb->si.framebufferWidth * 4, mRfb->si.framebufferHeight * mRfb->si.framebufferWidth * 4,
@ -205,10 +205,6 @@ int VncCamera::PostCapture() {
return 0; return 0;
} }
int VncCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) {
return 0;
}
int VncCamera::Close() { int VncCamera::Close() {
return 0; return 0;
} }

View File

@ -49,9 +49,8 @@ public:
int PreCapture(); int PreCapture();
int PrimeCapture(); int PrimeCapture();
int Capture( Image &image ); int Capture(ZMPacket &packet);
int PostCapture(); int PostCapture();
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
int Close(); int Close();
}; };

View File

@ -285,7 +285,6 @@ static const uint32_t prefered_gray8_formats[] = {
}; };
#endif #endif
int LocalCamera::camera_count = 0; int LocalCamera::camera_count = 0;
int LocalCamera::channel_count = 0; int LocalCamera::channel_count = 0;
int LocalCamera::channels[VIDEO_MAX_FRAME]; int LocalCamera::channels[VIDEO_MAX_FRAME];
@ -428,10 +427,6 @@ LocalCamera::LocalCamera(
/* RGB24 palette and 24bit target colourspace */ /* RGB24 palette and 24bit target colourspace */
} else if ( palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24 ) { } else if ( palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24 ) {
conversion_type = 0; conversion_type = 0;
subpixelorder = ZM_SUBPIX_ORDER_RGB;
/* BGR24 palette and 24bit target colourspace */
} else if ( palette == V4L2_PIX_FMT_BGR24 && colours == ZM_COLOUR_RGB24 ) {
conversion_type = 0; conversion_type = 0;
subpixelorder = ZM_SUBPIX_ORDER_BGR; subpixelorder = ZM_SUBPIX_ORDER_BGR;
@ -452,7 +447,7 @@ LocalCamera::LocalCamera(
#if HAVE_LIBSWSCALE #if HAVE_LIBSWSCALE
/* Try using swscale for the conversion */ /* Try using swscale for the conversion */
conversion_type = 1; conversion_type = 1;
Debug(2,"Using swscale for image conversion"); Debug(2, "Using swscale for image conversion");
if ( colours == ZM_COLOUR_RGB32 ) { if ( colours == ZM_COLOUR_RGB32 ) {
subpixelorder = ZM_SUBPIX_ORDER_RGBA; subpixelorder = ZM_SUBPIX_ORDER_RGBA;
imagePixFormat = AV_PIX_FMT_RGBA; imagePixFormat = AV_PIX_FMT_RGBA;
@ -595,15 +590,15 @@ LocalCamera::LocalCamera(
conversion_type = 2; /* Try ZM format conversions */ conversion_type = 2; /* Try ZM format conversions */
} }
} }
#else
/* Don't have swscale, see what we can do */
conversion_type = 2;
#endif
/* Our YUYV->Grayscale conversion is a lot faster than swscale's */ /* Our YUYV->Grayscale conversion is a lot faster than swscale's */
if ( colours == ZM_COLOUR_GRAY8 && (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) ) { if ( colours == ZM_COLOUR_GRAY8 && (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) ) {
conversion_type = 2; conversion_type = 2;
} }
#else
/* Don't have swscale, see what we can do */
conversion_type = 2;
#endif
if ( conversion_type == 2 ) { if ( conversion_type == 2 ) {
Debug(2,"Using ZM for image conversion"); Debug(2,"Using ZM for image conversion");
if ( palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_GRAY8 ) { if ( palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_GRAY8 ) {
@ -645,17 +640,17 @@ LocalCamera::LocalCamera(
} else { } else {
Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace."); Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace.");
} }
} } // end if conversion_type == 2
} }
} }
#endif // ZM_HAS_V4L1 #endif // ZM_HAS_V4L1
last_camera = this; last_camera = this;
Debug(3,"Selected subpixelorder: %u",subpixelorder); Debug(3, "Selected subpixelorder: %u", subpixelorder);
#if HAVE_LIBSWSCALE #if HAVE_LIBSWSCALE
/* Initialize swscale stuff */ /* Initialize swscale stuff */
if ( capture && conversion_type == 1 ) { if ( capture and (conversion_type == 1) ) {
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
tmpPicture = av_frame_alloc(); tmpPicture = av_frame_alloc();
#else #else
@ -673,7 +668,10 @@ LocalCamera::LocalCamera(
Fatal("Image size mismatch. Required: %d Available: %u", pSize, imagesize); Fatal("Image size mismatch. Required: %d Available: %u", pSize, imagesize);
} }
imgConversionContext = sws_getContext(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, nullptr, nullptr, nullptr); imgConversionContext = sws_getContext(
width, height, capturePixFormat,
width, height, imagePixFormat, SWS_BICUBIC,
nullptr, nullptr, nullptr);
if ( !imgConversionContext ) { if ( !imgConversionContext ) {
Fatal("Unable to initialise image scaling context"); Fatal("Unable to initialise image scaling context");
@ -681,8 +679,11 @@ LocalCamera::LocalCamera(
} else { } else {
tmpPicture = nullptr; tmpPicture = nullptr;
imgConversionContext = nullptr; imgConversionContext = nullptr;
} } // end if capture and conversion_tye == swscale
#endif #endif
mVideoStreamId = 0;
mAudioStreamId = -1;
video_stream = nullptr;
} // end LocalCamera::LocalCamera } // end LocalCamera::LocalCamera
LocalCamera::~LocalCamera() { LocalCamera::~LocalCamera() {
@ -691,23 +692,22 @@ LocalCamera::~LocalCamera() {
#if HAVE_LIBSWSCALE #if HAVE_LIBSWSCALE
/* Clean up swscale stuff */ /* Clean up swscale stuff */
if ( capture && conversion_type == 1 ) { if ( capture && (conversion_type == 1) ) {
sws_freeContext(imgConversionContext); sws_freeContext(imgConversionContext);
imgConversionContext = nullptr; imgConversionContext = nullptr;
av_frame_free(&tmpPicture); av_frame_free(&tmpPicture);
} }
#endif #endif
} if ( video_stream ) {
// Should also free streams
avformat_free_context(oc);
video_stream = nullptr;
}
} // end LocalCamera::~LocalCamera
void LocalCamera::Initialise() { void LocalCamera::Initialise() {
#if HAVE_LIBSWSCALE
if ( logDebugging() )
av_log_set_level(AV_LOG_DEBUG);
else
av_log_set_level(AV_LOG_QUIET);
#endif // HAVE_LIBSWSCALE
Debug(3, "Opening video device %s", device.c_str()); Debug(3, "Opening video device %s", device.c_str());
//if ( (vid_fd = open( device.c_str(), O_RDWR|O_NONBLOCK, 0 )) < 0 ) //if ( (vid_fd = open( device.c_str(), O_RDWR|O_NONBLOCK, 0 )) < 0 )
if ( (vid_fd = open(device.c_str(), O_RDWR, 0)) < 0 ) if ( (vid_fd = open(device.c_str(), O_RDWR, 0)) < 0 )
@ -781,7 +781,7 @@ void LocalCamera::Initialise() {
} }
} else { } else {
if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) {
Fatal("Failed to set video format: %s", strerror(errno)); Error("Failed to set video format: %s", strerror(errno));
} }
} }
@ -807,6 +807,13 @@ void LocalCamera::Initialise() {
, v4l2_data.fmt.fmt.pix.priv , v4l2_data.fmt.fmt.pix.priv
); );
if ( v4l2_data.fmt.fmt.pix.width != width ) {
Warning("Failed to set requested width");
}
if ( v4l2_data.fmt.fmt.pix.height != height ) {
Warning("Failed to set requested height");
}
/* Buggy driver paranoia. */ /* Buggy driver paranoia. */
unsigned int min; unsigned int min;
min = v4l2_data.fmt.fmt.pix.width * 2; min = v4l2_data.fmt.fmt.pix.width * 2;
@ -840,8 +847,8 @@ void LocalCamera::Initialise() {
if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) { if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) {
Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno)); Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno));
} else { } else {
Debug(4, "JPEG quality: %d",jpeg_comp.quality); Debug(4, "JPEG quality: %d, markers: %#x",
Debug(4, "JPEG markers: %#x",jpeg_comp.jpeg_markers); jpeg_comp.quality, jpeg_comp.jpeg_markers);
} }
} }
} }
@ -924,7 +931,8 @@ void LocalCamera::Initialise() {
#else #else
avpicture_fill( avpicture_fill(
(AVPicture *)capturePictures[i], (AVPicture *)capturePictures[i],
(uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, (uint8_t*)v4l2_data.buffers[i].start,
capturePixFormat,
v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.width,
v4l2_data.fmt.fmt.pix.height v4l2_data.fmt.fmt.pix.height
); );
@ -1172,11 +1180,11 @@ void LocalCamera::Terminate() {
Error("Failed to munmap buffers: %s", strerror(errno)); Error("Failed to munmap buffers: %s", strerror(errno));
delete[] v4l1_data.buffers; delete[] v4l1_data.buffers;
} } // end if using v4l1
#endif // ZM_HAS_V4L1 #endif // ZM_HAS_V4L1
close(vid_fd); close(vid_fd);
} // end Terminate } // end LocalCamera::Terminate
uint32_t LocalCamera::AutoSelectFormat(int p_colours) { uint32_t LocalCamera::AutoSelectFormat(int p_colours) {
/* Automatic format selection */ /* Automatic format selection */
@ -1268,13 +1276,16 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) {
#endif /* ZM_HAS_V4L2 */ #endif /* ZM_HAS_V4L2 */
return selected_palette; return selected_palette;
} } //uint32_t LocalCamera::AutoSelectFormat(int p_colours)
#define capString(test,prefix,yesString,noString,capability) \ #define capString(test,prefix,yesString,noString,capability) \
(test) ? (prefix yesString " " capability "\n") : (prefix noString " " capability "\n") (test) ? (prefix yesString " " capability "\n") : (prefix noString " " capability "\n")
bool LocalCamera::GetCurrentSettings(const char *device, char *output, int version, bool verbose) { bool LocalCamera::GetCurrentSettings(
const char *device,
char *output,
int version,
bool verbose) {
output[0] = 0; output[0] = 0;
char *output_ptr = output; char *output_ptr = output;
@ -1766,7 +1777,7 @@ bool LocalCamera::GetCurrentSettings(const char *device, char *output, int versi
return true; return true;
} }
int LocalCamera::Brightness( int p_brightness ) { int LocalCamera::Brightness(int p_brightness) {
#if ZM_HAS_V4L2 #if ZM_HAS_V4L2
if ( v4l_version == 2 ) { if ( v4l_version == 2 ) {
struct v4l2_control vid_control; struct v4l2_control vid_control;
@ -1820,7 +1831,7 @@ int LocalCamera::Brightness( int p_brightness ) {
return -1; return -1;
} }
int LocalCamera::Hue( int p_hue ) { int LocalCamera::Hue(int p_hue) {
#if ZM_HAS_V4L2 #if ZM_HAS_V4L2
if ( v4l_version == 2 ) { if ( v4l_version == 2 ) {
struct v4l2_control vid_control; struct v4l2_control vid_control;
@ -1978,11 +1989,14 @@ int LocalCamera::PrimeCapture() {
Debug(2, "Priming capture"); Debug(2, "Priming capture");
#if ZM_HAS_V4L2 #if ZM_HAS_V4L2
if ( v4l_version == 2 ) { if ( v4l_version == 2 ) {
Debug(3, "Queueing buffers"); Debug(3, "Queueing (%d) buffers", v4l2_data.reqbufs.count);
for ( unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++ ) { for ( unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++ ) {
struct v4l2_buffer vid_buf; struct v4l2_buffer vid_buf;
memset(&vid_buf, 0, sizeof(vid_buf)); memset(&vid_buf, 0, sizeof(vid_buf));
if ( v4l2_data.fmt.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ) {
Warning("Unknown type: (%d)", v4l2_data.fmt.type);
}
vid_buf.type = v4l2_data.fmt.type; vid_buf.type = v4l2_data.fmt.type;
vid_buf.memory = v4l2_data.reqbufs.memory; vid_buf.memory = v4l2_data.reqbufs.memory;
@ -2012,17 +2026,18 @@ int LocalCamera::PrimeCapture() {
} }
} }
#endif // ZM_HAS_V4L1 #endif // ZM_HAS_V4L1
mVideoStreamId = 0;
return 0; return 1;
} // end LocalCamera::PrimeCapture } // end LocalCamera::PrimeCapture
int LocalCamera::PreCapture() { int LocalCamera::PreCapture() {
//Debug(5, "Pre-capturing"); //Debug(5, "Pre-capturing");
return 0; return 1;
} }
int LocalCamera::Capture(Image &image) { int LocalCamera::Capture(ZMPacket &zm_packet) {
Debug(3, "Capturing"); // We assume that the avpacket is allocated, and just needs to be filled
static uint8_t* buffer = nullptr; static uint8_t* buffer = nullptr;
int buffer_bytesused = 0; int buffer_bytesused = 0;
int capture_frame = -1; int capture_frame = -1;
@ -2057,6 +2072,7 @@ int LocalCamera::Capture(Image &image) {
} }
return -1; return -1;
} }
Debug(5, "Captured a frame");
v4l2_data.bufptr = &vid_buf; v4l2_data.bufptr = &vid_buf;
capture_frame = v4l2_data.bufptr->index; capture_frame = v4l2_data.bufptr->index;
@ -2078,9 +2094,12 @@ int LocalCamera::Capture(Image &image) {
if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) { if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) {
Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d", Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",
v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height);
} }
} // end if v4l2 } // end if v4l2
#if ZM_HAS_V4L1
else
#endif // ZM_HAS_V4L1
#endif // ZM_HAS_V4L2 #endif // ZM_HAS_V4L2
#if ZM_HAS_V4L1 #if ZM_HAS_V4L1
if ( v4l_version == 1 ) { if ( v4l_version == 1 ) {
@ -2108,21 +2127,25 @@ int LocalCamera::Capture(Image &image) {
buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_frame]; buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_frame];
} }
#endif // ZM_HAS_V4L1 #endif // ZM_HAS_V4L1
} /* prime capture */ } /* prime capture */
if ( conversion_type != 0 ) { if ( !zm_packet.image ) {
Debug(1, "Allocating image");
zm_packet.image = new Image(width, height, colours, subpixelorder);
}
Debug(3, "Performing format conversion"); if ( conversion_type != 0 ) {
Debug(3, "Performing format conversion %d", conversion_type);
/* Request a writeable buffer of the target image */ /* Request a writeable buffer of the target image */
uint8_t* directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); uint8_t *directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder);
if ( directbuffer == nullptr ) { if ( directbuffer == nullptr ) {
Error("Failed requesting writeable buffer for the captured image."); Error("Failed requesting writeable buffer for the captured image.");
return -1; return -1;
} }
#if HAVE_LIBSWSCALE #if HAVE_LIBSWSCALE
if ( conversion_type == 1 ) { if ( conversion_type == 1 ) {
Debug(9, "Calling sws_scale to perform the conversion"); Debug(9, "Calling sws_scale to perform the conversion");
/* Use swscale to convert the image directly into the shared memory */ /* Use swscale to convert the image directly into the shared memory */
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
@ -2133,32 +2156,37 @@ int LocalCamera::Capture(Image &image) {
avpicture_fill( (AVPicture *)tmpPicture, directbuffer, avpicture_fill( (AVPicture *)tmpPicture, directbuffer,
imagePixFormat, width, height ); imagePixFormat, width, height );
#endif #endif
sws_scale( imgConversionContext, sws_scale(
imgConversionContext,
capturePictures[capture_frame]->data, capturePictures[capture_frame]->data,
capturePictures[capture_frame]->linesize, capturePictures[capture_frame]->linesize,
0, 0,
height, height,
tmpPicture->data, tmpPicture->data,
tmpPicture->linesize ); tmpPicture->linesize
} );
} else
#endif #endif
if ( conversion_type == 2 ) { if ( conversion_type == 2 ) {
Debug(9, "Calling the conversion function"); Debug(9, "Calling the conversion function");
/* Call the image conversion function and convert directly into the shared memory */ /* Call the image conversion function and convert directly into the shared memory */
(*conversion_fptr)(buffer, directbuffer, pixels); (*conversion_fptr)(buffer, directbuffer, pixels);
} else if ( conversion_type == 3 ) { } else if ( conversion_type == 3 ) {
// Need to store the jpeg data too
Debug(9, "Decoding the JPEG image"); Debug(9, "Decoding the JPEG image");
/* JPEG decoding */ /* JPEG decoding */
image.DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); zm_packet.image->DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder);
} }
} else { } else {
Debug(3, "No format conversion performed. Assigning the image"); Debug(3, "No format conversion performed. Assigning the image");
/* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */ /* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */
image.Assign( width, height, colours, subpixelorder, buffer, imagesize); zm_packet.image->Assign(width, height, colours, subpixelorder, buffer, imagesize);
} } // end if doing conversion or not
zm_packet.codec_type = AVMEDIA_TYPE_VIDEO;
zm_packet.keyframe = 1;
return 1; return 1;
} // end int LocalCamera::Capture() } // end int LocalCamera::Capture()
@ -2177,7 +2205,7 @@ int LocalCamera::PostCapture() {
} }
v4l2_std_id stdId = standards[next_channel]; v4l2_std_id stdId = standards[next_channel];
if ( vidioctl( vid_fd, VIDIOC_S_STD, &stdId ) < 0 ) { if ( vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0 ) {
Error("Failed to set video format %d: %s", standards[next_channel], strerror(errno)); Error("Failed to set video format %d: %s", standards[next_channel], strerror(errno));
return -1; return -1;
} }
@ -2192,6 +2220,9 @@ int LocalCamera::PostCapture() {
Error("Unable to requeue buffer due to not v4l2_data") Error("Unable to requeue buffer due to not v4l2_data")
} }
} }
#if ZM_HAS_V4L1
else
#endif // ZM_HAS_V4L1
#endif // ZM_HAS_V4L2 #endif // ZM_HAS_V4L2
#if ZM_HAS_V4L1 #if ZM_HAS_V4L1
if ( v4l_version == 1 ) { if ( v4l_version == 1 ) {
@ -2227,4 +2258,32 @@ int LocalCamera::PostCapture() {
return 0; return 0;
} }
AVStream *LocalCamera::get_VideoStream() {
if ( ! video_stream ) {
oc = avformat_alloc_context();
Debug(1, "Allocating avstream");
video_stream = avformat_new_stream(oc, nullptr);
if ( video_stream ) {
video_stream->time_base = (AVRational){1, 1000000}; // microseconds as base frame rate
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
video_stream->codecpar->width = width;
video_stream->codecpar->height = height;
video_stream->codecpar->format = GetFFMPEGPixelFormat(colours, subpixelorder);
video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
video_stream->codecpar->codec_id = AV_CODEC_ID_NONE;
Debug(1, "Allocating avstream %p %p %d", video_stream, video_stream->codecpar, video_stream->codecpar->codec_id);
#else
video_stream->codec->width = width;
video_stream->codec->height = height;
video_stream->codec->pix_fmt = GetFFMPEGPixelFormat(colours, subpixelorder);
video_stream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
video_stream->codec->codec_id = AV_CODEC_ID_NONE;
#endif
} else {
Error("Can't create video stream");
}
}
return video_stream;
}
#endif // ZM_HAS_V4L #endif // ZM_HAS_V4L

View File

@ -23,7 +23,7 @@
#include "zm.h" #include "zm.h"
#include "zm_camera.h" #include "zm_camera.h"
#include "zm_image.h" #include "zm_image.h"
#include "zm_packetqueue.h" #include "zm_packet.h"
#if ZM_HAS_V4L #if ZM_HAS_V4L
@ -49,18 +49,15 @@
// directly connect to the host machine and which are accessed // directly connect to the host machine and which are accessed
// via a video interface. // via a video interface.
// //
class LocalCamera : public Camera class LocalCamera : public Camera {
{
protected: protected:
#if ZM_HAS_V4L2 #if ZM_HAS_V4L2
struct V4L2MappedBuffer struct V4L2MappedBuffer {
{
void *start; void *start;
size_t length; size_t length;
}; };
struct V4L2Data struct V4L2Data {
{
v4l2_cropcap cropcap; v4l2_cropcap cropcap;
v4l2_crop crop; v4l2_crop crop;
v4l2_format fmt; v4l2_format fmt;
@ -71,8 +68,7 @@ protected:
#endif // ZM_HAS_V4L2 #endif // ZM_HAS_V4L2
#if ZM_HAS_V4L1 #if ZM_HAS_V4L1
struct V4L1Data struct V4L1Data {
{
int active_frame; int active_frame;
video_mbuf frames; video_mbuf frames;
video_mmap *buffers; video_mmap *buffers;
@ -160,12 +156,12 @@ public:
int PrimeCapture()override ; int PrimeCapture()override ;
int PreCapture()override ; int PreCapture()override ;
int Capture( Image &image )override ; int Capture(ZMPacket &p) override;
int PostCapture()override ; int PostCapture()override ;
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) override {return(0);};
int Close() override { return 0; }; int Close() override { return 0; };
static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose ); static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose );
AVStream* get_VideoStream();
}; };
#endif // ZM_HAS_V4L #endif // ZM_HAS_V4L

View File

@ -36,6 +36,7 @@
#ifdef __FreeBSD__ #ifdef __FreeBSD__
#include <sys/thr.h> #include <sys/thr.h>
#endif #endif
#include <cstdarg>
bool Logger::smInitialised = false; bool Logger::smInitialised = false;
Logger *Logger::smInstance = nullptr; Logger *Logger::smInstance = nullptr;
@ -271,9 +272,7 @@ std::string Logger::strEnv(const std::string &name, const std::string &defaultVa
} }
char *Logger::getTargettedEnv(const std::string &name) { char *Logger::getTargettedEnv(const std::string &name) {
std::string envName; std::string envName = name+"_"+mId;
envName = name+"_"+mId;
char *envPtr = getenv(envName.c_str()); char *envPtr = getenv(envName.c_str());
if ( !envPtr && mId != mIdRoot ) { if ( !envPtr && mId != mIdRoot ) {
envName = name+"_"+mIdRoot; envName = name+"_"+mIdRoot;
@ -523,8 +522,10 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con
puts(logString); puts(logString);
fflush(stdout); fflush(stdout);
} }
if ( level <= mFileLevel ) { if ( level <= mFileLevel ) {
if ( !mLogFileFP ) { if ( !mLogFileFP ) {
// We do this here so that we only create the file if we ever write to it.
log_mutex.unlock(); log_mutex.unlock();
openFile(); openFile();
log_mutex.lock(); log_mutex.lock();
@ -536,20 +537,14 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con
} else { } else {
puts("Logging to file, but failed to open it\n"); puts("Logging to file, but failed to open it\n");
} }
#if 0 } // end if level <= mFileLevel
} else {
printf("Not writing to log file because level %d %s <= mFileLevel %d %s\nstring: %s\n",
level, smCodes[level].c_str(), mFileLevel, smCodes[mFileLevel].c_str(), logString);
#endif
}
*syslogEnd = '\0';
if ( level <= mDatabaseLevel ) {
char sql[ZM_SQL_MED_BUFSIZ];
char escapedString[(strlen(syslogStart)*2)+1];
if ( level <= mDatabaseLevel ) {
if ( !db_mutex.trylock() ) { if ( !db_mutex.trylock() ) {
char escapedString[(strlen(syslogStart)*2)+1];
mysql_real_escape_string(&dbconn, escapedString, syslogStart, strlen(syslogStart)); mysql_real_escape_string(&dbconn, escapedString, syslogStart, strlen(syslogStart));
char sql[ZM_SQL_MED_BUFSIZ];
snprintf(sql, sizeof(sql), snprintf(sql, sizeof(sql),
"INSERT INTO `Logs` " "INSERT INTO `Logs` "
"( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` )" "( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` )"
@ -567,26 +562,26 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con
} else { } else {
Level tempDatabaseLevel = mDatabaseLevel; Level tempDatabaseLevel = mDatabaseLevel;
databaseLevel(NOLOG); databaseLevel(NOLOG);
Error("Can't insert log entry: sql(%s) error(db is locked)", logString); Error("Can't insert log entry: sql(%s) error(%s)", syslogStart, mysql_error(&dbconn));
databaseLevel(tempDatabaseLevel); databaseLevel(tempDatabaseLevel);
} }
} db_mutex.unlock();
} // end if level <= mDatabaseLevel
if ( level <= mSyslogLevel ) { if ( level <= mSyslogLevel ) {
int priority = smSyslogPriorities[level]; *syslogEnd = '\0';
//priority |= LOG_DAEMON; syslog(smSyslogPriorities[level], "%s [%s] [%s]", classString, mId.c_str(), syslogStart);
syslog(priority, "%s [%s] [%s]", classString, mId.c_str(), syslogStart);
} }
free(filecopy); free(filecopy);
log_mutex.unlock();
if ( level <= FATAL ) { if ( level <= FATAL ) {
log_mutex.unlock();
logTerm(); logTerm();
zmDbClose(); zmDbClose();
if ( level <= PANIC ) if ( level <= PANIC )
abort(); abort();
exit(-1); exit(-1);
} }
log_mutex.unlock();
} // end logPrint } // end logPrint
void logInit(const char *name, const Logger::Options &options) { void logInit(const char *name, const Logger::Options &options) {

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,21 @@
// //
// ZoneMinder Monitor Class Interfaces, $Date$, $Revision$ // ZoneMinder Monitor Class Interfaces, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes // Copyright (C) 2001-2008 Philip Coombes
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License // modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2 // as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version. // of the License, or (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// //
#ifndef ZM_MONITOR_H #ifndef ZM_MONITOR_H
#define ZM_MONITOR_H #define ZM_MONITOR_H
@ -30,6 +30,11 @@
#include "zm_rgb.h" #include "zm_rgb.h"
#include "zm_zone.h" #include "zm_zone.h"
#include "zm_event.h" #include "zm_event.h"
#include "zm_video.h"
#include "zm_videostore.h"
#include "zm_packetqueue.h"
#include "zm_thread.h"
class Monitor; class Monitor;
#include "zm_group.h" #include "zm_group.h"
#include "zm_camera.h" #include "zm_camera.h"
@ -75,10 +80,11 @@ public:
FFMPEG, FFMPEG,
LIBVLC, LIBVLC,
CURL, CURL,
NVSOCKET,
VNC, VNC,
} CameraType; } CameraType;
typedef enum { typedef enum {
ROTATE_0=1, ROTATE_0=1,
ROTATE_90, ROTATE_90,
ROTATE_180, ROTATE_180,
@ -98,8 +104,8 @@ public:
typedef enum { typedef enum {
DISABLED, DISABLED,
X264ENCODE, ENCODE,
H264PASSTHROUGH, PASSTHROUGH,
} VideoWriter; } VideoWriter;
protected: protected:
@ -109,13 +115,15 @@ protected:
typedef enum { CLOSE_TIME, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode; typedef enum { CLOSE_TIME, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode;
/* sizeof(SharedData) expected to be 340 bytes on 32bit and 64bit */ /* sizeof(SharedData) expected to be 344 bytes on 32bit and 64bit */
typedef struct { typedef struct {
uint32_t size; /* +0 */ uint32_t size; /* +0 */
uint32_t last_write_index; /* +4 */ uint32_t last_write_index; /* +4 */
uint32_t last_read_index; /* +8 */ uint32_t last_read_index; /* +8 */
uint32_t state; /* +12 */ uint32_t state; /* +12 */
uint64_t last_event; /* +16 */ double capture_fps; // Current capturing fps
double analysis_fps; // Current analysis fps
uint64_t last_event_id; /* +16 */
uint32_t action; /* +24 */ uint32_t action; /* +24 */
int32_t brightness; /* +28 */ int32_t brightness; /* +28 */
int32_t hue; /* +32 */ int32_t hue; /* +32 */
@ -175,26 +183,15 @@ protected:
char trigger_showtext[256]; char trigger_showtext[256];
} TriggerData; } TriggerData;
/* sizeof(Snapshot) expected to be 16 bytes on 32bit and 32 bytes on 64bit */
struct Snapshot {
struct timeval *timestamp;
Image *image;
void* padding;
};
//TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat //TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat
#if 1
//sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit //sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit
typedef struct { typedef struct {
uint32_t size; uint32_t size;
uint64_t current_event; uint64_t current_event;
char event_file[4096]; char event_file[4096];
timeval recording; // used as both bool and a pointer to the timestamp when recording should begin timeval recording; // used as both bool and a pointer to the timestamp when recording should begin
//uint32_t frameNumber;
} VideoStoreData; } VideoStoreData;
#endif // HAVE_LIBAVFORMAT
class MonitorLink { class MonitorLink {
protected: protected:
unsigned int id; unsigned int id;
@ -217,25 +214,17 @@ protected:
volatile VideoStoreData *video_store_data; volatile VideoStoreData *video_store_data;
int last_state; int last_state;
uint64_t last_event; uint64_t last_event_id;
public: public:
MonitorLink(unsigned int p_id, const char *p_name); MonitorLink(unsigned int p_id, const char *p_name);
~MonitorLink(); ~MonitorLink();
inline unsigned int Id() const { inline unsigned int Id() const { return id; }
return id; inline const char *Name() const { return name; }
}
inline const char *Name() const {
return name;
}
inline bool isConnected() const { inline bool isConnected() const { return connected && shared_data->valid; }
return connected && shared_data->valid; inline time_t getLastConnectTime() const { return last_connect_time; }
}
inline time_t getLastConnectTime() const {
return last_connect_time;
}
inline uint32_t lastFrameScore() { inline uint32_t lastFrameScore() {
return shared_data->last_frame_score; return shared_data->last_frame_score;
@ -259,81 +248,103 @@ protected:
Function function; // What the monitor is doing Function function; // What the monitor is doing
bool enabled; // Whether the monitor is enabled or asleep bool enabled; // Whether the monitor is enabled or asleep
bool decoding_enabled; // Whether the monitor will decode h264/h265 packets bool decoding_enabled; // Whether the monitor will decode h264/h265 packets
std::string protocol;
std::string method;
std::string options;
std::string host;
std::string port;
std::string user;
std::string pass;
std::string path;
char device[64];
int palette;
int channel;
int format;
unsigned int camera_width;
unsigned int camera_height;
unsigned int width; // Normally the same as the camera, but not if partly rotated unsigned int width; // Normally the same as the camera, but not if partly rotated
unsigned int height; // Normally the same as the camera, but not if partly rotated unsigned int height; // Normally the same as the camera, but not if partly rotated
bool v4l_multi_buffer; bool v4l_multi_buffer;
unsigned int v4l_captures_per_frame; unsigned int v4l_captures_per_frame;
Orientation orientation; // Whether the image has to be rotated at all Orientation orientation; // Whether the image has to be rotated at all
unsigned int deinterlacing; unsigned int deinterlacing;
bool videoRecording; unsigned int deinterlacing_value;
std::string decoder_hwaccel_name; std::string decoder_hwaccel_name;
std::string decoder_hwaccel_device; std::string decoder_hwaccel_device;
bool videoRecording;
bool rtsp_describe;
int savejpegs; int savejpegs;
VideoWriter videowriter; int colours;
std::string encoderparams; VideoWriter videowriter;
std::string encoderparams;
int output_codec;
std::string encoder;
std::string output_container;
std::vector<EncoderParameter_t> encoderparamsvec; std::vector<EncoderParameter_t> encoderparamsvec;
bool record_audio; // Whether to store the audio that we receive _AVPIXELFORMAT imagePixFormat;
unsigned int subpixelorder;
bool record_audio; // Whether to store the audio that we receive
int brightness; // The statically saved brightness of the camera
int contrast; // The statically saved contrast of the camera int brightness; // The statically saved brightness of the camera
int hue; // The statically saved hue of the camera int contrast; // The statically saved contrast of the camera
int colour; // The statically saved colour of the camera int hue; // The statically saved hue of the camera
char event_prefix[64]; // The prefix applied to event names as they are created int colour; // The statically saved colour of the camera
char label_format[64]; // The format of the timestamp on the images
Coord label_coord; // The coordinates of the timestamp on the images char event_prefix[64]; // The prefix applied to event names as they are created
int label_size; // Size of the timestamp on the images char label_format[64]; // The format of the timestamp on the images
int image_buffer_count; // Size of circular image buffer, at least twice the size of the pre_event_count Coord label_coord; // The coordinates of the timestamp on the images
int pre_event_buffer_count; // Size of dedicated circular pre event buffer used when analysis is not performed at capturing framerate, int label_size; // Size of the timestamp on the images
int image_buffer_count; // Size of circular image buffer, at least twice the size of the pre_event_count
int pre_event_buffer_count; // Size of dedicated circular pre event buffer used when analysis is not performed at capturing framerate,
// value is pre_event_count + alarm_frame_count - 1 // value is pre_event_count + alarm_frame_count - 1
int warmup_count; // How many images to process before looking for events int warmup_count; // How many images to process before looking for events
int pre_event_count; // How many images to hold and prepend to an alarm event int pre_event_count; // How many images to hold and prepend to an alarm event
int post_event_count; // How many unalarmed images must occur before the alarm state is reset int post_event_count; // How many unalarmed images must occur before the alarm state is reset
int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now
int section_length; // How long events should last in continuous modes
int min_section_length; // Minimum event length when using event_close_mode == ALARM
bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor
int frame_skip; // How many frames to skip in continuous modes
int motion_frame_skip; // How many frames to skip in motion detection
double analysis_fps_limit; // Target framerate for video analysis
struct timeval video_buffer_duration; // How long a video segment to keep in buffer (set only if analysis fps != 0 ) struct timeval video_buffer_duration; // How long a video segment to keep in buffer (set only if analysis fps != 0 )
int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now
int section_length; // How long events should last in continuous modes
int min_section_length; // Minimum event length when using event_close_mode == ALARM
bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor
int frame_skip; // How many frames to skip in continuous modes
int motion_frame_skip; // How many frames to skip in motion detection
double capture_max_fps; // Target Capture FPS
double analysis_fps; // Target framerate for video analysis
unsigned int analysis_update_delay; // How long we wait before updating analysis parameters unsigned int analysis_update_delay; // How long we wait before updating analysis parameters
int capture_delay; // How long we wait between capture frames int capture_delay; // How long we wait between capture frames
int alarm_capture_delay; // How long we wait between capture frames when in alarm state int alarm_capture_delay; // How long we wait between capture frames when in alarm state
int alarm_frame_count; // How many alarm frames are required before an event is triggered int alarm_frame_count; // How many alarm frames are required before an event is triggered
int fps_report_interval; // How many images should be captured/processed between reporting the current FPS int fps_report_interval; // How many images should be captured/processed between reporting the current FPS
int ref_blend_perc; // Percentage of new image going into reference image. int ref_blend_perc; // Percentage of new image going into reference image.
int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm. int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm.
bool track_motion; // Whether this monitor tries to track detected motion bool track_motion; // Whether this monitor tries to track detected motion
int signal_check_points; // Number of points in the image to check for signal int signal_check_points; // Number of points in the image to check for signal
Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected
bool embed_exif; // Whether to embed Exif data into each image frame or not bool embed_exif; // Whether to embed Exif data into each image frame or not
bool last_signal; int capture_max_fps;
double fps;
unsigned int last_camera_bytes;
Image delta_image;
Image ref_image;
Image alarm_image; // Used in creating analysis images, will be initialized in Analysis
Image write_image; // Used when creating snapshot images
std::string diag_path_ref;
std::string diag_path_delta;
Purpose purpose; // What this monitor has been created to do Purpose purpose; // What this monitor has been created to do
int event_count; unsigned int last_camera_bytes;
int image_count;
int ready_count; int event_count;
int first_alarm_count; int image_count;
int last_alarm_count; int analysis_image_count; // How many frames have been processed by analysis thread.
int buffer_count; int motion_frame_count; // How many frames have had motion detection performed on them.
int prealarm_count; int ready_count;
State state; int first_alarm_count;
time_t start_time; int last_alarm_count;
time_t last_fps_time; bool last_signal;
time_t auto_resume_time; int last_section_mod;
int buffer_count;
State state;
time_t start_time;
double last_fps_time;
double last_analysis_fps_time;
time_t auto_resume_time;
unsigned int last_motion_score; unsigned int last_motion_score;
EventCloseMode event_close_mode; EventCloseMode event_close_mode;
@ -350,14 +361,23 @@ protected:
TriggerData *trigger_data; TriggerData *trigger_data;
VideoStoreData *video_store_data; VideoStoreData *video_store_data;
Snapshot *image_buffer; struct timeval *shared_timestamps;
Snapshot next_buffer; /* Used by four field deinterlacing */ unsigned char *shared_images;
Snapshot *pre_event_buffer; ZMPacket *image_buffer;
ZMPacket next_buffer; /* Used by four field deinterlacing */
int video_stream_id; // will be filled in PrimeCapture
int audio_stream_id; // will be filled in PrimeCapture
Camera *camera; Camera *camera;
Event *event; Event *event;
Storage *storage; Storage *storage;
VideoStore *videoStore;
zm_packetqueue *packetqueue;
packetqueue_iterator *analysis_it;
Mutex mutex;
int n_zones; int n_zones;
Zone **zones; Zone **zones;
@ -372,59 +392,17 @@ protected:
std::vector<Group *> groups; std::vector<Group *> groups;
Image delta_image;
Image ref_image;
Image alarm_image; // Used in creating analysis images, will be initialized in Analysis
Image write_image; // Used when creating snapshot images
std::string diag_path_ref;
std::string diag_path_delta;
public: public:
explicit Monitor();
explicit Monitor(unsigned int p_id); explicit Monitor(unsigned int p_id);
// OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info.
//bool OurCheckAlarms( Zone *zone, const Image *pImage );
Monitor(
unsigned int p_id,
const char *p_name,
unsigned int p_server_id,
unsigned int p_storage_id,
int p_function,
bool p_enabled,
bool p_decoding_enabled,
const char *p_linked_monitors,
Camera *p_camera,
int p_orientation,
unsigned int p_deinterlacing,
const std::string &p_decoder_hwaccel_name,
const std::string &p_decoder_hwaccel_device,
int p_savejpegs,
VideoWriter p_videowriter,
std::string p_encoderparams,
bool p_record_audio,
const char *p_event_prefix,
const char *p_label_format,
const Coord &p_label_coord,
int label_size,
int p_image_buffer_count,
int p_warmup_count,
int p_pre_event_count,
int p_post_event_count,
int p_stream_replay_buffer,
int p_alarm_frame_count,
int p_section_length,
int p_min_section_length,
int p_frame_skip,
int p_motion_frame_skip,
double p_capture_max_fps,
double p_analysis_fps,
unsigned int p_analysis_update_delay,
int p_capture_delay,
int p_alarm_capture_delay,
int p_fps_report_interval,
int p_ref_blend_perc,
int p_alarm_ref_blend_perc,
bool p_track_motion,
int p_signal_check_points,
Rgb p_signal_check_colour,
bool p_embed_exif,
Purpose p_purpose,
int p_n_zones=0,
Zone *p_zones[]=0
);
~Monitor(); ~Monitor();
void AddZones( int p_n_zones, Zone *p_zones[] ); void AddZones( int p_n_zones, Zone *p_zones[] );
@ -436,17 +414,19 @@ public:
inline int ShmValid() const { inline int ShmValid() const {
return shared_data && shared_data->valid; return shared_data && shared_data->valid;
} }
Camera *getCamera();
inline unsigned int Id() const { return id; } inline unsigned int Id() const { return id; }
inline const char *Name() const { return name; } inline const char *Name() const { return name; }
inline unsigned int ServerId() { return server_id; } inline unsigned int ServerId() { return server_id; }
inline Storage *getStorage() { inline Storage *getStorage() {
if ( ! storage ) { if ( ! storage ) {
storage = new Storage( storage_id ); storage = new Storage(storage_id);
} }
return storage; return storage;
} }
inline Function GetFunction() const { return function; } inline Function GetFunction() const { return function; }
inline zm_packetqueue * GetPacketQueue() const { return packetqueue; }
inline bool Enabled() const { inline bool Enabled() const {
if ( function <= MONITOR ) if ( function <= MONITOR )
return false; return false;
@ -457,9 +437,15 @@ public:
} }
inline const char *EventPrefix() const { return event_prefix; } inline const char *EventPrefix() const { return event_prefix; }
inline bool Ready() const { inline bool Ready() const {
if ( function <= MONITOR ) if ( function <= MONITOR ) {
Error("Should not be calling Ready if the function doesn't include motion detection");
return false; return false;
return( image_count > ready_count ); }
if ( image_count >= ready_count ) {
return true;
}
Debug(2, "Not ready because image_count(%d) <= ready_count(%d)", image_count, ready_count);
return false;
} }
inline bool Active() const { inline bool Active() const {
if ( function <= MONITOR ) if ( function <= MONITOR )
@ -467,19 +453,31 @@ public:
return( enabled && shared_data->active ); return( enabled && shared_data->active );
} }
inline bool Exif() const { return embed_exif; } inline bool Exif() const { return embed_exif; }
inline bool RecordAudio() { return record_audio; }
/*
inline Purpose Purpose() { return purpose };
inline Purpose Purpose( Purpose p ) { purpose = p; };
*/
Orientation getOrientation() const; Orientation getOrientation() const;
unsigned int Width() const { return width; } unsigned int Width() const { return width; }
unsigned int Height() const { return height; } unsigned int Height() const { return height; }
unsigned int Colours() const; unsigned int Colours() const;
unsigned int SubpixelOrder() const; unsigned int SubpixelOrder() const;
int GetOptSaveJPEGs() const { return savejpegs; } int GetOptSaveJPEGs() const { return savejpegs; }
VideoWriter GetOptVideoWriter() const { return videowriter; } VideoWriter GetOptVideoWriter() const { return videowriter; }
const std::vector<EncoderParameter_t>* GetOptEncoderParamsVec() const { return &encoderparamsvec; } //const std::vector<EncoderParameter_t>* GetEncoderParams() const { return &encoderparamsvec; }
const std::string GetOptEncoderParams() const { return encoderparams; } const std::string &GetEncoderOptions() const { return encoderparams; }
const int OutputCodec() const { return output_codec; }
const std::string &Encoder() const { return encoder; }
const std::string &OutputContainer() const { return output_container; }
uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; } uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; }
void SetVideoWriterEventId( unsigned long long p_event_id ) { video_store_data->current_event = p_event_id; } void SetVideoWriterEventId( uint64_t p_event_id ) { video_store_data->current_event = p_event_id; }
struct timeval GetVideoWriterStartTime() const { return video_store_data->recording; } struct timeval GetVideoWriterStartTime() const { return video_store_data->recording; }
void SetVideoWriterStartTime(const struct timeval &t) { video_store_data->recording = t; } void SetVideoWriterStartTime(const struct timeval &t) { video_store_data->recording = t; }
@ -488,7 +486,7 @@ public:
int GetImageBufferCount() const { return image_buffer_count; }; int GetImageBufferCount() const { return image_buffer_count; };
State GetState() const; State GetState() const;
int GetImage( int index=-1, int scale=100 ); int GetImage( int index=-1, int scale=100 );
Snapshot *getSnapshot() const; ZMPacket *getSnapshot( int index=-1 ) const;
struct timeval GetTimestamp( int index=-1 ) const; struct timeval GetTimestamp( int index=-1 ) const;
void UpdateAdaptiveSkip(); void UpdateAdaptiveSkip();
useconds_t GetAnalysisRate(); useconds_t GetAnalysisRate();
@ -500,12 +498,15 @@ public:
unsigned int GetLastWriteIndex() const; unsigned int GetLastWriteIndex() const;
uint64_t GetLastEventId() const; uint64_t GetLastEventId() const;
double GetFPS() const; double GetFPS() const;
void UpdateAnalysisFPS();
void UpdateCaptureFPS();
void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" ); void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" );
void ForceAlarmOff(); void ForceAlarmOff();
void CancelForced(); void CancelForced();
TriggerState GetTriggerState() const { return (TriggerState)(trigger_data?trigger_data->trigger_state:TRIGGER_CANCEL); } TriggerState GetTriggerState() const { return (TriggerState)(trigger_data?trigger_data->trigger_state:TRIGGER_CANCEL); }
inline time_t getStartupTime() const { return shared_data->startup_time; } inline time_t getStartupTime() const { return shared_data->startup_time; }
inline void setStartupTime( time_t p_time ) { shared_data->startup_time = p_time; } inline void setStartupTime( time_t p_time ) { shared_data->startup_time = p_time; }
void get_ref_image();
int LabelSize() const { return label_size; } int LabelSize() const { return label_size; }
@ -520,12 +521,14 @@ public:
int actionColour( int p_colour=-1 ); int actionColour( int p_colour=-1 );
int actionContrast( int p_contrast=-1 ); int actionContrast( int p_contrast=-1 );
int PrimeCapture() const; int PrimeCapture();
int PreCapture() const; int PreCapture() const;
int Capture(); int Capture();
int PostCapture() const; int PostCapture() const;
int Close(); int Close();
void CheckAction();
unsigned int DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ); unsigned int DetectMotion( const Image &comp_image, Event::StringSet &zoneSet );
// DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info. // DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info.
//unsigned int DetectBlack( const Image &comp_image, Event::StringSet &zoneSet ); //unsigned int DetectBlack( const Image &comp_image, Event::StringSet &zoneSet );
@ -554,7 +557,7 @@ public:
static int LoadFfmpegMonitors(const char *file, Monitor **&monitors, Purpose purpose); static int LoadFfmpegMonitors(const char *file, Monitor **&monitors, Purpose purpose);
#endif // HAVE_LIBAVFORMAT #endif // HAVE_LIBAVFORMAT
static Monitor *Load(unsigned int id, bool load_zones, Purpose purpose); static Monitor *Load(unsigned int id, bool load_zones, Purpose purpose);
static Monitor *Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose); void Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose);
//void writeStreamImage( Image *image, struct timeval *timestamp, int scale, int mag, int x, int y ); //void writeStreamImage( Image *image, struct timeval *timestamp, int scale, int mag, int x, int y );
//void StreamImages( int scale=100, int maxfps=10, time_t ttl=0, int msq_id=0 ); //void StreamImages( int scale=100, int maxfps=10, time_t ttl=0, int msq_id=0 );
//void StreamImagesRaw( int scale=100, int maxfps=10, time_t ttl=0 ); //void StreamImagesRaw( int scale=100, int maxfps=10, time_t ttl=0 );
@ -562,8 +565,11 @@ public:
#if HAVE_LIBAVCODEC #if HAVE_LIBAVCODEC
//void StreamMpeg( const char *format, int scale=100, int maxfps=10, int bitrate=100000 ); //void StreamMpeg( const char *format, int scale=100, int maxfps=10, int bitrate=100000 );
#endif // HAVE_LIBAVCODEC #endif // HAVE_LIBAVCODEC
double get_fps( ) const { double get_capture_fps( ) const {
return fps; return shared_data ? shared_data->capture_fps : 0.0;
}
double get_analysis_fps( ) const {
return shared_data ? shared_data->analysis_fps : 0.0;
} }
}; };

View File

@ -27,13 +27,12 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <glob.h> #include <glob.h>
const int MAX_SLEEP_USEC=1000000; // 1 sec const int MAX_SLEEP_USEC = 1000000; // 1 sec
bool MonitorStream::checkSwapPath(const char *path, bool create_path) { bool MonitorStream::checkSwapPath(const char *path, bool create_path) {
struct stat stat_buf; struct stat stat_buf;
if ( stat(path, &stat_buf) < 0 ) { if ( stat(path, &stat_buf) < 0 ) {
if ( create_path && errno == ENOENT ) { if ( create_path and (errno == ENOENT) ) {
Debug(3, "Swap path '%s' missing, creating", path); Debug(3, "Swap path '%s' missing, creating", path);
if ( mkdir(path, 0755) ) { if ( mkdir(path, 0755) ) {
Error("Can't mkdir %s: %s", path, strerror(errno)); Error("Can't mkdir %s: %s", path, strerror(errno));
@ -73,7 +72,7 @@ bool MonitorStream::checkSwapPath(const char *path, bool create_path) {
return false; return false;
} }
return true; return true;
} // end bool MonitorStream::checkSwapPath( const char *path, bool create_path ) } // end bool MonitorStream::checkSwapPath(const char *path, bool create_path)
void MonitorStream::processCommand(const CmdMsg *msg) { void MonitorStream::processCommand(const CmdMsg *msg) {
Debug(2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0]); Debug(2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0]);
@ -196,7 +195,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
} }
break; break;
case CMD_ZOOMOUT : case CMD_ZOOMOUT :
Debug( 1, "Got ZOOM OUT command" ); Debug(1, "Got ZOOM OUT command");
switch ( zoom ) { switch ( zoom ) {
case 500: case 500:
zoom = 400; zoom = 400;
@ -240,6 +239,8 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
int id; int id;
int state; int state;
double fps; double fps;
double capture_fps;
double analysis_fps;
int buffer_level; int buffer_level;
int rate; int rate;
double delay; double delay;
@ -253,6 +254,8 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
status_data.id = monitor->Id(); status_data.id = monitor->Id();
if ( ! monitor->ShmValid() ) { if ( ! monitor->ShmValid() ) {
status_data.fps = 0.0; status_data.fps = 0.0;
status_data.capture_fps = 0.0;
status_data.analysis_fps = 0.0;
status_data.state = Monitor::UNKNOWN; status_data.state = Monitor::UNKNOWN;
//status_data.enabled = monitor->shared_data->active; //status_data.enabled = monitor->shared_data->active;
status_data.enabled = false; status_data.enabled = false;
@ -260,6 +263,8 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
status_data.buffer_level = 0; status_data.buffer_level = 0;
} else { } else {
status_data.fps = monitor->GetFPS(); status_data.fps = monitor->GetFPS();
status_data.capture_fps = monitor->get_capture_fps();
status_data.analysis_fps = monitor->get_analysis_fps();
status_data.state = monitor->shared_data->state; status_data.state = monitor->shared_data->state;
//status_data.enabled = monitor->shared_data->active; //status_data.enabled = monitor->shared_data->active;
status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF; status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF;
@ -274,16 +279,19 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
status_data.rate = replay_rate; status_data.rate = replay_rate;
status_data.delay = TV_2_FLOAT(now) - TV_2_FLOAT(last_frame_timestamp); status_data.delay = TV_2_FLOAT(now) - TV_2_FLOAT(last_frame_timestamp);
status_data.zoom = zoom; status_data.zoom = zoom;
Debug(2, "Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d", Debug(2, "fps: %.2f capture_fps: %.2f analysis_fps: %.2f Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d",
status_data.buffer_level, status_data.fps,
status_data.delayed, status_data.capture_fps,
status_data.paused, status_data.analysis_fps,
status_data.rate, status_data.buffer_level,
status_data.delay, status_data.delayed,
status_data.zoom, status_data.paused,
status_data.enabled, status_data.rate,
status_data.forced status_data.delay,
); status_data.zoom,
status_data.enabled,
status_data.forced
);
DataMsg status_msg; DataMsg status_msg;
status_msg.msg_type = MSG_DATA_WATCH; status_msg.msg_type = MSG_DATA_WATCH;
@ -299,7 +307,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes); Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes);
// quit after sending a status, if this was a quit request // quit after sending a status, if this was a quit request
if ( (MsgCommand)msg->msg_data[0]==CMD_QUIT ) { if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) {
zm_terminate = true; zm_terminate = true;
Debug(2, "Quitting"); Debug(2, "Quitting");
return; return;
@ -307,19 +315,20 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
//Debug(2,"Updating framerate"); //Debug(2,"Updating framerate");
//updateFrameRate(monitor->GetFPS()); //updateFrameRate(monitor->GetFPS());
} // end void MonitorStream::processCommand(const CmdMsg *msg) } // end void MonitorStream::processCommand(const CmdMsg *msg)
bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) { bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) {
bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE));
if ( type != STREAM_JPEG ) if (
send_raw = false; ( type != STREAM_JPEG )
if ( !config.timestamp_on_capture && timestamp ) ||
( (!config.timestamp_on_capture) && timestamp )
)
send_raw = false; send_raw = false;
if ( !send_raw ) { if ( !send_raw ) {
Image temp_image(filepath); Image temp_image(filepath);
return sendFrame(&temp_image, timestamp); return sendFrame(&temp_image, timestamp);
} else { } else {
int img_buffer_size = 0; int img_buffer_size = 0;
@ -679,20 +688,25 @@ void MonitorStream::runStream() {
if ( last_read_index != monitor->shared_data->last_write_index ) { if ( last_read_index != monitor->shared_data->last_write_index ) {
// have a new image to send // have a new image to send
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary
if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) {
if ( !paused && !delayed ) { if ( !paused && !delayed ) {
last_read_index = monitor->shared_data->last_write_index; last_read_index = monitor->shared_data->last_write_index;
Debug(2, "Sending frame index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", Debug(2, "Sending frame index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)",
index, frame_mod, frame_count, paused, delayed); index, frame_mod, frame_count, paused, delayed);
// Send the next frame // Send the next frame
Monitor::Snapshot *snap = &monitor->image_buffer[index]; //
ZMPacket *snap = &monitor->image_buffer[index];
if ( !sendFrame(snap->image, snap->timestamp) ) { if ( !sendFrame(snap->image, snap->timestamp) ) {
Debug(2, "sendFrame failed, quiting."); Debug(2, "sendFrame failed, quiting.");
zm_terminate = true; zm_terminate = true;
break; break;
} }
// Perhaps we should use NOW instead.
last_frame_timestamp = *(snap->timestamp);
//frame_sent = true;
//
if ( frame_count == 0 ) { if ( frame_count == 0 ) {
// Chrome will not display the first frame until it receives another. // Chrome will not display the first frame until it receives another.
// Firefox is fine. So just send the first frame twice. // Firefox is fine. So just send the first frame twice.
@ -702,16 +716,6 @@ void MonitorStream::runStream() {
break; break;
} }
} }
// Perhaps we should use NOW instead.
last_frame_timestamp = *snap->timestamp;
/*
memcpy(
&last_frame_timestamp,
snap->timestamp,
sizeof(last_frame_timestamp)
);
*/
// frame_sent = true;
temp_read_index = temp_write_index; temp_read_index = temp_write_index;
} else { } else {
@ -738,14 +742,14 @@ void MonitorStream::runStream() {
} else { } else {
Debug(2, "Would have sent keepalive frame, but had no paused_image"); Debug(2, "Would have sent keepalive frame, but had no paused_image");
} }
} // end if actual_delta_time > 5 } // end if actual_delta_time > 5
} // end if change in zoom } // end if change in zoom
} // end if paused or not } // end if paused or not
} // end if should send frame } // end if should send frame
if ( buffered_playback && !paused ) { if ( buffered_playback && !paused ) {
if ( monitor->shared_data->valid ) { if ( monitor->shared_data->valid ) {
if ( monitor->image_buffer[index].timestamp->tv_sec ) { if ( monitor->shared_timestamps[index].tv_sec ) {
int temp_index = temp_write_index%temp_image_buffer_count; int temp_index = temp_write_index%temp_image_buffer_count;
Debug(2, "Storing frame %d", temp_index); Debug(2, "Storing frame %d", temp_index);
if ( !temp_image_buffer[temp_index].valid ) { if ( !temp_image_buffer[temp_index].valid ) {
@ -757,8 +761,11 @@ void MonitorStream::runStream() {
temp_index); temp_index);
temp_image_buffer[temp_index].valid = true; temp_image_buffer[temp_index].valid = true;
} }
memcpy(&(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp)); temp_image_buffer[temp_index].timestamp = monitor->shared_timestamps[index];
monitor->image_buffer[index].image->WriteJpeg(temp_image_buffer[temp_index].file_name, config.jpeg_file_quality); monitor->image_buffer[index].image->WriteJpeg(
temp_image_buffer[temp_index].file_name,
config.jpeg_file_quality
);
temp_write_index = MOD_ADD(temp_write_index, 1, temp_image_buffer_count); temp_write_index = MOD_ADD(temp_write_index, 1, temp_image_buffer_count);
if ( temp_write_index == temp_read_index ) { if ( temp_write_index == temp_read_index ) {
// Go back to live viewing // Go back to live viewing
@ -844,7 +851,7 @@ void MonitorStream::SingleImage(int scale) {
int img_buffer_size = 0; int img_buffer_size = 0;
static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE];
Image scaled_image; Image scaled_image;
Monitor::Snapshot *snap = monitor->getSnapshot(); ZMPacket *snap = &(monitor->image_buffer[monitor->shared_data->last_write_index]);
Image *snap_image = snap->image; Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) { if ( scale != ZM_SCALE_BASE ) {
@ -862,11 +869,11 @@ void MonitorStream::SingleImage(int scale) {
"Content-Type: image/jpeg\r\n\r\n", "Content-Type: image/jpeg\r\n\r\n",
img_buffer_size); img_buffer_size);
fwrite(img_buffer, img_buffer_size, 1, stdout); fwrite(img_buffer, img_buffer_size, 1, stdout);
} } // end void MonitorStream::SingleImage(int scale)
void MonitorStream::SingleImageRaw(int scale) { void MonitorStream::SingleImageRaw(int scale) {
Image scaled_image; Image scaled_image;
Monitor::Snapshot *snap = monitor->getSnapshot(); ZMPacket *snap = monitor->getSnapshot();
Image *snap_image = snap->image; Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) { if ( scale != ZM_SCALE_BASE ) {
@ -883,7 +890,7 @@ void MonitorStream::SingleImageRaw(int scale) {
"Content-Type: image/x-rgb\r\n\r\n", "Content-Type: image/x-rgb\r\n\r\n",
snap_image->Size()); snap_image->Size());
fwrite(snap_image->Buffer(), snap_image->Size(), 1, stdout); fwrite(snap_image->Buffer(), snap_image->Size(), 1, stdout);
} } // end void MonitorStream::SingleImageRaw(int scale)
#ifdef HAVE_ZLIB_H #ifdef HAVE_ZLIB_H
void MonitorStream::SingleImageZip(int scale) { void MonitorStream::SingleImageZip(int scale) {
@ -891,7 +898,7 @@ void MonitorStream::SingleImageZip(int scale) {
static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; static Bytef img_buffer[ZM_MAX_IMAGE_SIZE];
Image scaled_image; Image scaled_image;
Monitor::Snapshot *snap = monitor->getSnapshot(); ZMPacket *snap = monitor->getSnapshot();
Image *snap_image = snap->image; Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) { if ( scale != ZM_SCALE_BASE ) {
@ -909,5 +916,5 @@ void MonitorStream::SingleImageZip(int scale) {
"Content-Type: image/x-rgbz\r\n\r\n", "Content-Type: image/x-rgbz\r\n\r\n",
img_buffer_size); img_buffer_size);
fwrite(img_buffer, img_buffer_size, 1, stdout); fwrite(img_buffer, img_buffer_size, 1, stdout);
} } // end void MonitorStream::SingleImageZip(int scale)
#endif // HAVE_ZLIB_H #endif // HAVE_ZLIB_H

View File

@ -59,8 +59,14 @@ class MonitorStream : public StreamBase {
public: public:
MonitorStream() : MonitorStream() :
temp_image_buffer(nullptr), temp_image_buffer_count(0), temp_read_index(0), temp_write_index(0), temp_image_buffer(nullptr),
ttl(0), playback_buffer(0), delayed(false), frame_count(0) { temp_image_buffer_count(0),
temp_read_index(0),
temp_write_index(0),
ttl(0),
playback_buffer(0),
delayed(false),
frame_count(0) {
} }
void setStreamBuffer(int p_playback_buffer) { void setStreamBuffer(int p_playback_buffer) {
playback_buffer = p_playback_buffer; playback_buffer = p_playback_buffer;

View File

@ -23,28 +23,285 @@
#include <sys/time.h> #include <sys/time.h>
using namespace std; using namespace std;
AVPixelFormat target_format = AV_PIX_FMT_NONE;
ZMPacket::ZMPacket( AVPacket *p ) { ZMPacket::ZMPacket() :
frame = nullptr; keyframe(0),
image = nullptr; in_frame(nullptr),
av_init_packet( &packet ); out_frame(nullptr),
if ( zm_av_packet_ref( &packet, p ) < 0 ) { timestamp(nullptr),
Error("error refing packet"); buffer(nullptr),
} image(nullptr),
gettimeofday( &timestamp, nullptr ); analysis_image(nullptr),
score(-1),
codec_type(AVMEDIA_TYPE_UNKNOWN),
image_index(-1),
codec_imgsize(0)
{
av_init_packet(&packet);
packet.size = 0; // So we can detect whether it has been filled.
} }
ZMPacket::ZMPacket( AVPacket *p, struct timeval *t ) { ZMPacket::ZMPacket(ZMPacket &p) :
frame = nullptr; keyframe(0),
image = nullptr; in_frame(nullptr),
av_init_packet( &packet ); out_frame(nullptr),
if ( zm_av_packet_ref( &packet, p ) < 0 ) { timestamp(nullptr),
buffer(nullptr),
image(nullptr),
analysis_image(nullptr),
score(-1),
codec_type(AVMEDIA_TYPE_UNKNOWN),
image_index(-1),
codec_imgsize(0)
{
av_init_packet(&packet);
if ( zm_av_packet_ref(&packet, &p.packet) < 0 ) {
Error("error refing packet"); Error("error refing packet");
} }
timestamp = *t; timestamp = new struct timeval;
*timestamp = *p.timestamp;
} }
ZMPacket::~ZMPacket() { ZMPacket::~ZMPacket() {
zm_av_packet_unref( &packet ); zm_av_packet_unref(&packet);
if ( in_frame ) {
av_frame_free(&in_frame);
}
if ( out_frame ) {
av_frame_free(&out_frame);
}
if ( buffer ) {
av_freep(&buffer);
}
if ( analysis_image ) {
delete analysis_image;
analysis_image = nullptr;
}
if ( image ) {
delete image;
image = nullptr;
}
if ( timestamp ) {
delete timestamp;
timestamp = nullptr;
}
#if 0
if ( image ) {
if ( image->IsBufferHeld() ) {
// Don't free the mmap'd image
} else {
delete image;
image = nullptr;
delete timestamp;
timestamp = nullptr;
}
} else {
if ( timestamp ) {
delete timestamp;
timestamp = nullptr;
}
}
#endif
} }
// deprecated
void ZMPacket::reset() {
zm_av_packet_unref(&packet);
if ( in_frame ) {
av_frame_free(&in_frame);
}
if ( out_frame ) {
av_frame_free(&out_frame);
}
if ( buffer ) {
av_freep(&buffer);
}
if ( analysis_image ) {
delete analysis_image;
analysis_image = nullptr;
}
#if 0
if ( (! image) && timestamp ) {
delete timestamp;
timestamp = NULL;
}
#endif
score = -1;
keyframe = 0;
}
int ZMPacket::decode(AVCodecContext *ctx) {
Debug(4, "about to decode video, image_index is (%d)", image_index);
if ( in_frame ) {
Error("Already have a frame?");
} else {
in_frame = zm_av_frame_alloc();
}
int ret = zm_send_packet_receive_frame(ctx, in_frame, packet);
if ( ret < 0 ) {
if ( AVERROR(EAGAIN) != ret ) {
Warning("Unable to receive frame : code %d %s.",
ret, av_make_error_string(ret).c_str());
}
av_frame_free(&in_frame);
return 0;
}
#if HAVE_LIBAVUTIL_HWCONTEXT_H
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
if ( (ctx->sw_pix_fmt != in_frame->format) ) {
Debug(1, "Have different format %s != %s.",
av_get_pix_fmt_name(ctx->pix_fmt),
av_get_pix_fmt_name(ctx->sw_pix_fmt)
);
if ( target_format == AV_PIX_FMT_NONE and ctx->hw_frames_ctx and (image->Colours() == 4) ) {
// Look for rgb0 in list of supported formats
enum AVPixelFormat *formats;
if ( 0 <= av_hwframe_transfer_get_formats(
ctx->hw_frames_ctx,
AV_HWFRAME_TRANSFER_DIRECTION_FROM,
&formats,
0
) ) {
for (int i = 0; formats[i] != AV_PIX_FMT_NONE; i++) {
Debug(1, "Available dest formats %d %s",
formats[i],
av_get_pix_fmt_name(formats[i])
);
if ( formats[i] == AV_PIX_FMT_RGB0 ) {
target_format = formats[i];
break;
} // endif RGB0
} // end foreach support format
av_freep(&formats);
} // endif success getting list of formats
} // end if target_format not set
AVFrame *new_frame = zm_av_frame_alloc();
if ( target_format != AV_PIX_FMT_NONE ) {
if ( 1 and image ) {
if ( 0 > image->PopulateFrame(new_frame) ) {
delete new_frame;
new_frame = zm_av_frame_alloc();
delete image;
image = nullptr;
}
} else {
delete image;
image = nullptr;
}
new_frame->format = target_format;
}
/* retrieve data from GPU to CPU */
zm_dump_video_frame(in_frame, "Before hwtransfer");
ret = av_hwframe_transfer_data(new_frame, in_frame, 0);
if ( ret < 0 ) {
Error("Unable to transfer frame: %s, continuing",
av_make_error_string(ret).c_str());
av_frame_free(&in_frame);
av_frame_free(&new_frame);
return 0;
}
new_frame->pts = in_frame->pts;
zm_dump_video_frame(new_frame, "After hwtransfer");
if ( new_frame->format == AV_PIX_FMT_RGB0 ) {
new_frame->format = AV_PIX_FMT_RGBA;
zm_dump_video_frame(new_frame, "After hwtransfer setting to rgba");
}
av_frame_free(&in_frame);
in_frame = new_frame;
} else {
Debug(2, "Same pix format %s so not hwtransferring. sw_pix_fmt is %s",
av_get_pix_fmt_name(ctx->pix_fmt),
av_get_pix_fmt_name(ctx->sw_pix_fmt)
);
if ( image ) {
image->Assign(in_frame);
}
}
#endif
#endif
return 1;
} // end ZMPacket::decode
Image *ZMPacket::get_image(Image *i) {
if ( !in_frame ) {
Error("Can't get image without frame.. maybe need to decode first");
return nullptr;
}
if ( !image ) {
if ( !i ) {
Error("Need a pre-allocated image buffer");
return nullptr;
}
image = i;
}
image->Assign(in_frame);
return image;
}
Image *ZMPacket::set_image(Image *i) {
image = i;
return image;
}
AVPacket *ZMPacket::set_packet(AVPacket *p) {
if ( zm_av_packet_ref(&packet, p) < 0 ) {
Error("error refing packet");
}
//dumpPacket(&packet, "zmpacket:");
gettimeofday(timestamp, nullptr);
keyframe = p->flags & AV_PKT_FLAG_KEY;
return &packet;
}
AVFrame *ZMPacket::get_out_frame(const AVCodecContext *ctx) {
if ( !out_frame ) {
out_frame = zm_av_frame_alloc();
if ( !out_frame ) {
Error("Unable to allocate a frame");
return nullptr;
}
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
codec_imgsize = av_image_get_buffer_size(
ctx->pix_fmt,
ctx->width,
ctx->height, 32);
buffer = (uint8_t *)av_malloc(codec_imgsize);
av_image_fill_arrays(
out_frame->data,
out_frame->linesize,
buffer,
ctx->pix_fmt,
ctx->width,
ctx->height,
32);
#else
codec_imgsize = avpicture_get_size(
ctx->pix_fmt,
ctx->width,
ctx->height);
buffer = (uint8_t *)av_malloc(codec_imgsize);
avpicture_fill(
(AVPicture *)out_frame,
buffer,
ctx->pix_fmt,
ctx->width,
ctx->height
);
#endif
out_frame->width = ctx->width;
out_frame->height = ctx->height;
out_frame->format = ctx->pix_fmt;
}
return out_frame;
} // end AVFrame *ZMPacket::get_out_frame( AVCodecContext *ctx );

View File

@ -27,20 +27,57 @@ extern "C" {
#ifdef __FreeBSD__ #ifdef __FreeBSD__
#include <sys/time.h> #include <sys/time.h>
#endif // __FreeBSD__ #endif // __FreeBSD__
#include "zm_image.h" #include "zm_image.h"
#include "zm_thread.h"
#include <mutex>
class ZMPacket { class ZMPacket {
public: public:
AVPacket packet; // Input packet, undecoded std::recursive_mutex mutex;
AVFrame *frame; // Input image, decoded int keyframe;
Image *image; // Our internal image oject representing this frame AVPacket packet; // Input packet, undecoded
struct timeval timestamp; AVFrame *in_frame; // Input image, decoded Theoretically only filled if needed.
AVFrame *out_frame; // output image, Only filled if needed.
struct timeval *timestamp;
uint8_t *buffer; // buffer used in image
Image *image;
Image *analysis_image;
int score;
AVMediaType codec_type;
int image_index;
int codec_imgsize;
public: public:
AVPacket *av_packet() { return &packet; } AVPacket *av_packet() { return &packet; }
ZMPacket( AVPacket *packet, struct timeval *timestamp ); AVPacket *set_packet(AVPacket *p) ;
explicit ZMPacket( AVPacket *packet ); AVFrame *av_frame() { return out_frame; }
Image *get_image(Image *i=nullptr);
Image *set_image(Image *);
int is_keyframe() { return keyframe; };
int decode( AVCodecContext *ctx );
void reset();
explicit ZMPacket(Image *image);
explicit ZMPacket(ZMPacket &packet);
ZMPacket();
~ZMPacket(); ~ZMPacket();
void lock() {
Debug(4,"Locking packet %d", this->image_index);
mutex.lock();
Debug(4,"packet %d locked", this->image_index);
};
bool trylock() {
Debug(4,"TryLocking packet %d", this->image_index);
return mutex.try_lock();
};
void unlock() {
Debug(4,"packet %d unlocked", this->image_index);
mutex.unlock();
};
AVFrame *get_out_frame( const AVCodecContext *ctx );
int get_codec_imgsize() { return codec_imgsize; };
}; };
#endif /* ZM_PACKET_H */ #endif /* ZM_PACKET_H */

View File

@ -17,223 +17,352 @@
//along with ZoneMinder. If not, see <http://www.gnu.org/licenses/>. //along with ZoneMinder. If not, see <http://www.gnu.org/licenses/>.
// PacketQueue must know about all iterators and manage them
#include "zm_packetqueue.h" #include "zm_packetqueue.h"
#include "zm_ffmpeg.h" #include "zm_ffmpeg.h"
#include "zm_signal.h"
#include <sys/time.h> #include <sys/time.h>
#include "zm_time.h" #include "zm_time.h"
zm_packetqueue::zm_packetqueue( int p_max_stream_id ) { zm_packetqueue::zm_packetqueue(
max_stream_id = p_max_stream_id; int video_image_count,
int p_video_stream_id,
int p_audio_stream_id
):
video_stream_id(p_video_stream_id),
max_video_packet_count(video_image_count),
deleting(false)
{
max_stream_id = p_video_stream_id > p_audio_stream_id ? p_video_stream_id : p_audio_stream_id;
packet_counts = new int[max_stream_id+1]; packet_counts = new int[max_stream_id+1];
for ( int i=0; i <= max_stream_id; ++i ) for ( int i=0; i <= max_stream_id; ++i )
packet_counts[i] = 0; packet_counts[i] = 0;
} }
zm_packetqueue::~zm_packetqueue() { zm_packetqueue::~zm_packetqueue() {
clearQueue(); deleting = true;
// Anyone waiting should immediately check deleting
condition.notify_all();
/* zma might be waiting. Must have exclusive access */
while ( !mutex.try_lock() ) {
Debug(4, "Waiting for exclusive access");
condition.notify_all();
}
while ( !pktQueue.empty() ) {
ZMPacket *packet = pktQueue.front();
pktQueue.pop_front();
delete packet;
}
delete[] packet_counts; delete[] packet_counts;
Debug(4, "Done in destructor");
packet_counts = nullptr; packet_counts = nullptr;
mutex.unlock();
condition.notify_all();
} }
bool zm_packetqueue::queuePacket(ZMPacket* zm_packet) { /* Enqueues the given packet. Will maintain the it pointer and image packet counts.
* If we have reached our max image packet count, it will pop off as many packets as are needed.
* Thus it will ensure that the same packet never gets queued twice.
*/
if ( bool zm_packetqueue::queuePacket(ZMPacket* add_packet) {
( zm_packet->packet.dts == AV_NOPTS_VALUE ) Debug(4, "packetqueue queuepacket %p %d", add_packet, add_packet->image_index);
|| mutex.lock();
( packet_counts[zm_packet->packet.stream_index] <= 0 )
) { pktQueue.push_back(add_packet);
Debug(2,"Inserting packet with dts %" PRId64 " because queue %d is empty (queue size: %d) or invalid dts", packet_counts[add_packet->packet.stream_index] += 1;
zm_packet->packet.dts, zm_packet->packet.stream_index, packet_counts[zm_packet->packet.stream_index] Debug(1, "packet counts for %d is %d",
add_packet->packet.stream_index,
packet_counts[add_packet->packet.stream_index]);
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *iterator_it = *iterators_it;
if ( *iterator_it == pktQueue.end() ) {
Debug(4, "pointing it %p to back", iterator_it);
--(*iterator_it);
}
} // end foreach iterator
// Only do queueCleaning if we are adding a video keyframe, so that we guarantee that there is one.
// No good. Have to satisfy two conditions:
// 1. packetqueue starts with a video keyframe
// 2. Have minimum # of video packets
// 3. No packets can be locked
// 4. No iterator can point to one of the packets
//
// So start at the beginning, counting video packets until the next keyframe.
// Then if deleting those packets doesn't break 1 and 2, then go ahead and delete them.
if ( add_packet->packet.stream_index == video_stream_id
and
add_packet->keyframe
and
(packet_counts[video_stream_id] > max_video_packet_count)
) {
packetqueue_iterator it = pktQueue.begin();
int video_stream_packets = 0;
// Since we have many packets in the queue, we should NOT be pointing at end so don't need to test for that
do {
it++;
ZMPacket *zm_packet = *it;
Debug(1, "Checking packet to see if we can delete them");
if ( zm_packet->packet.stream_index == video_stream_id ) {
if ( zm_packet->keyframe ) {
Debug(1, "Have a video keyframe so breaking out");
if ( !zm_packet->trylock() ) {
Debug(1, "Have locked packet %d", zm_packet->image_index);
video_stream_packets = max_video_packet_count;
}
zm_packet->unlock();
break;
}
video_stream_packets ++;
}
if ( !zm_packet->trylock() ) {
Debug(1, "Have locked packet %d", zm_packet->image_index);
video_stream_packets = max_video_packet_count;
break;
}
zm_packet->unlock();
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *iterator_it = *iterators_it;
// Have to check each iterator and make sure it doesn't point to the packet we are about to delete
if ( *(*iterator_it) == zm_packet ) {
Debug(4, "Found IT at beginning of queue. Threads not keeping up");
video_stream_packets = max_video_packet_count;
}
} // end foreach iterator
} while ( *it != add_packet );
Debug(1, "Resulting video_stream_packets count %d, %d > %d, pointing at latest packet? %d",
video_stream_packets,
packet_counts[video_stream_id] - video_stream_packets, max_video_packet_count,
( *it == add_packet )
); );
// No dts value, can't so much with it if (
pktQueue.push_back(zm_packet); packet_counts[video_stream_id] - video_stream_packets > max_video_packet_count
packet_counts[zm_packet->packet.stream_index] += 1; and
return true; ( *it != add_packet )
} ) {
Debug(1, "Deleting packets");
// It is enough to delete the packets tested above. A subsequent queuePacket can clear a second set
while ( pktQueue.begin() != it ) {
ZMPacket *zm_packet = *pktQueue.begin();
if ( !zm_packet ) {
Error("NULL zm_packet in queue");
continue;
}
#if 0 Debug(1, "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%d",
std::list<ZMPacket *>::reverse_iterator it = pktQueue.rbegin(); zm_packet->packet.stream_index, zm_packet->image_index, zm_packet->keyframe, packet_counts[video_stream_id], max_video_packet_count, pktQueue.size());
pktQueue.pop_front();
// Scan through the queue looking for a packet for our stream with a dts <= ours. packet_counts[zm_packet->packet.stream_index] -= 1;
while ( it != pktQueue.rend() ) { delete zm_packet;
AVPacket *av_packet = &((*it)->packet);
Debug(2, "Looking at packet with stream index (%d) with dts %" PRId64,
av_packet->stream_index, av_packet->dts);
if ( av_packet->stream_index == zm_packet->packet.stream_index ) {
if (
( av_packet->dts != AV_NOPTS_VALUE )
&&
( av_packet->dts <= zm_packet->packet.dts)
) {
Debug(2, "break packet with stream index (%d) with dts %" PRId64,
(*it)->packet.stream_index, (*it)->packet.dts);
break;
} }
} else { // Not same stream, compare timestamps } // end if have at least max_video_packet_count video packets remaining
if ( tvDiffUsec(((*it)->timestamp, zm_packet->timestamp) ) <= 0 ) { } // end if this is a video keyframe
Debug(2, "break packet with stream index (%d) with dts %" PRId64,
(*it)->packet.stream_index, (*it)->packet.dts);
break;
}
}
it++;
} // end while not the end of the queue
if ( it != pktQueue.rend() ) { mutex.unlock();
Debug(2, "Found packet with stream index (%d) with dts %" PRId64 " <= %" PRId64, // We signal on every packet because someday we may analyze sound
(*it)->packet.stream_index, (*it)->packet.dts, zm_packet->packet.dts); Debug(4, "packetqueue queuepacket, unlocked signalling");
if ( it == pktQueue.rbegin() ) { condition.notify_all();
Debug(2,"Inserting packet with dts %" PRId64 " at end", zm_packet->packet.dts);
// No dts value, can't so much with it
pktQueue.push_back(zm_packet);
packet_counts[zm_packet->packet.stream_index] += 1;
return true;
}
// Convert to a forward iterator so that we can insert at end
std::list<ZMPacket *>::iterator f_it = it.base();
Debug(2, "Insert packet before packet with stream index (%d) with dts %" PRId64 " for dts %" PRId64, return true;
(*f_it)->packet.stream_index, (*f_it)->packet.dts, zm_packet->packet.dts);
pktQueue.insert(f_it, zm_packet);
packet_counts[zm_packet->packet.stream_index] += 1;
return true;
}
Debug(1,"Unable to insert packet for stream %d with dts %" PRId64 " into queue.",
zm_packet->packet.stream_index, zm_packet->packet.dts);
#endif
pktQueue.push_back(zm_packet);
packet_counts[zm_packet->packet.stream_index] += 1;
return true;
} // end bool zm_packetqueue::queuePacket(ZMPacket* zm_packet) } // end bool zm_packetqueue::queuePacket(ZMPacket* zm_packet)
bool zm_packetqueue::queuePacket(AVPacket* av_packet) {
ZMPacket *zm_packet = new ZMPacket(av_packet);
return queuePacket(zm_packet);
}
ZMPacket* zm_packetqueue::popPacket( ) { ZMPacket* zm_packetqueue::popPacket( ) {
Debug(4, "pktQueue size %d", pktQueue.size());
if ( pktQueue.empty() ) { if ( pktQueue.empty() ) {
return nullptr; return nullptr;
} }
Debug(4, "poPacket Mutex locking");
mutex.lock();
ZMPacket *zm_packet = pktQueue.front();
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *iterator_it = *iterators_it;
// Have to check each iterator and make sure it doesn't point to the packet we are about to delete
if ( *(*iterator_it) == zm_packet ) {
Debug(4, "Bumping it because it is at the front that we are deleting");
++(*iterators_it);
}
} // end foreach iterator
zm_packet->lock();
ZMPacket *packet = pktQueue.front();
pktQueue.pop_front(); pktQueue.pop_front();
packet_counts[packet->packet.stream_index] -= 1; packet_counts[zm_packet->packet.stream_index] -= 1;
return packet; mutex.unlock();
}
return zm_packet;
} // popPacket
/* Keeps frames_to_keep frames of the provided stream, which theoretically is the video stream
* Basically it starts at the end, moving backwards until it finds the minimum video frame.
* Then it should probably move forward to find a keyframe. The first video frame must always be a keyframe.
* So really frames_to_keep is a maximum which isn't so awesome.. maybe we should go back farther to find the keyframe in which case
* frames_to_keep in a minimum
*/
unsigned int zm_packetqueue::clearQueue(unsigned int frames_to_keep, int stream_id) { unsigned int zm_packetqueue::clearQueue(unsigned int frames_to_keep, int stream_id) {
Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size()); Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size());
frames_to_keep += 1;
if ( pktQueue.empty() ) { if ( pktQueue.empty() ) {
Debug(3, "Queue is empty");
return 0; return 0;
} }
std::list<ZMPacket *>::reverse_iterator it; // If size is <= frames_to_keep since it could contain audio, we can't possibly do anything
ZMPacket *packet = nullptr; if ( pktQueue.size() <= frames_to_keep ) {
return 0;
}
Debug(5, "Locking in clearQueue");
mutex.lock();
for ( it = pktQueue.rbegin(); it != pktQueue.rend() && frames_to_keep; ++it ) { packetqueue_iterator it = pktQueue.end()--; // point to last element instead of end
ZMPacket *zm_packet = *it; ZMPacket *zm_packet = nullptr;
while ( (it != pktQueue.begin()) and frames_to_keep ) {
zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet); AVPacket *av_packet = &(zm_packet->packet);
Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", Debug(3, "Looking at packet with stream index (%d) with keyframe(%d), Image_index(%d) frames_to_keep is (%d)",
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); av_packet->stream_index, zm_packet->keyframe, zm_packet->image_index, frames_to_keep );
// Want frames_to_keep video keyframes. Otherwise, we may not have enough // Want frames_to_keep video keyframes. Otherwise, we may not have enough
if ( ( av_packet->stream_index == stream_id) ) { if ( av_packet->stream_index == stream_id ) {
//&& ( av_packet->flags & AV_PKT_FLAG_KEY ) ) {
frames_to_keep --; frames_to_keep --;
} }
it --;
} }
// Make sure we start on a keyframe // Either at beginning or frames_to_keep == 0
for ( ; it != pktQueue.rend(); ++it ) {
ZMPacket *zm_packet = *it; if ( it == pktQueue.begin() ) {
AVPacket *av_packet = &(zm_packet->packet); if ( frames_to_keep ) {
Warning("Couldn't remove any packets, needed %d", 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);
break;
} }
mutex.unlock();
return 0;
} }
if ( frames_to_keep ) {
Debug(3, "Hit end of queue, still need (%d) video frames", frames_to_keep);
}
if ( it != pktQueue.rend() ) {
// We want to keep this packet, so advance to the next
++it;
}
unsigned int delete_count = 0;
while ( it != pktQueue.rend() ) {
Debug(4, "Deleting a packet from the front, count is (%d)", delete_count);
packet = pktQueue.front(); int delete_count = 0;
// Else not at beginning, are pointing at packet before the last video packet
while ( pktQueue.begin() != it ) {
Debug(4, "Deleting a packet from the front, count is (%d), queue size is %d",
delete_count, pktQueue.size());
zm_packet = pktQueue.front();
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *iterator_it = *iterators_it;
// Have to check each iterator and make sure it doesn't point to the packet we are about to delete
if ( *(*iterator_it) == zm_packet ) {
Debug(4, "Bumping it because it is at the front that we are deleting");
++(*iterators_it);
}
} // end foreach iterator
packet_counts[zm_packet->packet.stream_index] --;
pktQueue.pop_front(); pktQueue.pop_front();
packet_counts[packet->packet.stream_index] -= 1; //if ( zm_packet->image_index == -1 )
delete packet; delete zm_packet;
delete_count += 1; delete_count += 1;
} } // while our iterator is not the first packet
packet = nullptr; // tidy up for valgrind zm_packet = nullptr; // tidy up for valgrind
Debug(3, "Deleted %d packets, %d remaining", delete_count, pktQueue.size()); Debug(3, "Deleted %d packets, %d remaining", delete_count, pktQueue.size());
mutex.unlock();
return delete_count;
Debug(3, "Deleted packets, resulting size is %d", pktQueue.size());
mutex.unlock();
return delete_count; return delete_count;
} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) } // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id )
void zm_packetqueue::clearQueue() { void zm_packetqueue::clearQueue() {
Debug(4, "Clocking in clearQueue");
mutex.lock();
ZMPacket *packet = nullptr; ZMPacket *packet = nullptr;
int delete_count = 0; int delete_count = 0;
while ( !pktQueue.empty() ) { while ( !pktQueue.empty() ) {
packet = pktQueue.front(); packet = pktQueue.front();
packet_counts[packet->packet.stream_index] -= 1; packet_counts[packet->packet.stream_index] -= 1;
pktQueue.pop_front(); pktQueue.pop_front();
delete packet; //if ( packet->image_index == -1 )
delete packet;
delete_count += 1; delete_count += 1;
} }
Debug(3, "Deleted (%d) packets", delete_count ); Debug(3, "Deleted (%d) packets", delete_count );
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *iterator_it = *iterators_it;
*iterator_it = pktQueue.begin();
} // end foreach iterator
mutex.unlock();
} }
// clear queue keeping only specified duration of video -- return number of pkts removed // clear queue keeping only specified duration of video -- return number of pkts removed
unsigned int zm_packetqueue::clearQueue(struct timeval *duration, int streamId) { unsigned int zm_packetqueue::clearQueue(struct timeval *duration, int streamId) {
if (pktQueue.empty()) { if ( pktQueue.empty() ) {
return 0; return 0;
} }
struct timeval keep_from; Debug(4, "Locking in clearQueue");
std::list<ZMPacket *>::reverse_iterator it; mutex.lock();
it = pktQueue.rbegin();
timersub(&(*it)->timestamp, duration, &keep_from); struct timeval keep_from;
std::list<ZMPacket *>::reverse_iterator it = pktQueue.rbegin();
struct timeval *t = (*it)->timestamp;
timersub(t, duration, &keep_from);
++it; ++it;
Debug(3, "Looking for frame before queue keep time with stream id (%d), queue has %d packets", Debug(3, "Looking for frame before queue keep time with stream id (%d), queue has %d packets",
streamId, pktQueue.size()); streamId, pktQueue.size());
for ( ; it != pktQueue.rend(); ++it) { for ( ; it != pktQueue.rend(); ++it) {
ZMPacket *zm_packet = *it; ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet); AVPacket *av_packet = &(zm_packet->packet);
if (av_packet->stream_index == streamId if (
&& timercmp( &zm_packet->timestamp, &keep_from, <= )) { (av_packet->stream_index == streamId)
and
timercmp(zm_packet->timestamp, &keep_from, <=)
) {
Debug(3, "Found frame before keep time with stream index %d at %d.%d", Debug(3, "Found frame before keep time with stream index %d at %d.%d",
av_packet->stream_index, av_packet->stream_index,
zm_packet->timestamp.tv_sec, zm_packet->timestamp->tv_sec,
zm_packet->timestamp.tv_usec); zm_packet->timestamp->tv_usec);
break; break;
} }
} }
if (it == pktQueue.rend()) { if ( it == pktQueue.rend() ) {
Debug(1, "Didn't find a frame before queue preserve time. keeping all"); Debug(1, "Didn't find a frame before queue preserve time. keeping all");
mutex.unlock();
return 0; return 0;
} }
@ -241,31 +370,49 @@ unsigned int zm_packetqueue::clearQueue(struct timeval *duration, int streamId)
for ( ; it != pktQueue.rend(); ++it) { for ( ; it != pktQueue.rend(); ++it) {
ZMPacket *zm_packet = *it; ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet); AVPacket *av_packet = &(zm_packet->packet);
if (av_packet->flags & AV_PKT_FLAG_KEY if (
&& av_packet->stream_index == streamId) { (av_packet->flags & AV_PKT_FLAG_KEY)
and
(av_packet->stream_index == streamId)
) {
Debug(3, "Found keyframe before start with stream index %d at %d.%d", Debug(3, "Found keyframe before start with stream index %d at %d.%d",
av_packet->stream_index, av_packet->stream_index,
zm_packet->timestamp.tv_sec, zm_packet->timestamp->tv_sec,
zm_packet->timestamp.tv_usec ); zm_packet->timestamp->tv_usec );
break; break;
} }
} }
if ( it == pktQueue.rend() ) { if ( it == pktQueue.rend() ) {
Debug(1, "Didn't find a keyframe before event starttime. keeping all" ); Debug(1, "Didn't find a keyframe before event starttime. keeping all" );
mutex.unlock();
return 0; return 0;
} }
unsigned int deleted_frames = 0; unsigned int deleted_frames = 0;
ZMPacket *zm_packet = nullptr; ZMPacket *zm_packet = nullptr;
while (distance(it, pktQueue.rend()) > 1) { while ( distance(it, pktQueue.rend()) > 1 ) {
zm_packet = pktQueue.front(); zm_packet = pktQueue.front();
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *iterator_it = *iterators_it;
// Have to check each iterator and make sure it doesn't point to the packet we are about to delete
if ( *(*iterator_it) == zm_packet ) {
Debug(4, "Bumping it because it is at the front that we are deleting");
++(*iterators_it);
}
} // end foreach iterator
pktQueue.pop_front(); pktQueue.pop_front();
packet_counts[zm_packet->packet.stream_index] -= 1; packet_counts[zm_packet->packet.stream_index] -= 1;
delete zm_packet; //if ( zm_packet->image_index == -1 )
delete zm_packet;
deleted_frames += 1; deleted_frames += 1;
} }
zm_packet = nullptr; zm_packet = nullptr;
Debug(3, "Deleted %d frames", deleted_frames); Debug(3, "Deleted %d frames", deleted_frames);
mutex.unlock();
return deleted_frames; return deleted_frames;
} }
@ -274,119 +421,161 @@ unsigned int zm_packetqueue::size() {
return pktQueue.size(); return pktQueue.size();
} }
int zm_packetqueue::packet_count( int stream_id ) { int zm_packetqueue::packet_count(int stream_id) {
return packet_counts[stream_id]; return packet_counts[stream_id];
} // end int zm_packetqueue::packet_count( int stream_id ) } // end int zm_packetqueue::packet_count(int stream_id)
// Clear packets before the given timestamp.
// Must also take into account pre_event_count frames
void zm_packetqueue::clear_unwanted_packets(
timeval *recording_started,
int pre_event_count,
int mVideoStreamId) {
// Need to find the keyframe <= recording_started. Can get rid of audio packets.
if ( pktQueue.empty() )
return;
// Step 1 - find frame <= recording_started. // Returns a packet. Packet will be locked
// Step 2 - go back pre_event_count ZMPacket *zm_packetqueue::get_packet(packetqueue_iterator *it) {
// Step 3 - find a keyframe if ( deleting or zm_terminate )
// Step 4 - pop packets until we get to the packet in step 3 return nullptr;
std::list<ZMPacket *>::reverse_iterator it;
// Step 1 - find frame <= recording_started. Debug(4, "Locking in get_packet using it %p queue end? %d, packet %p",
Debug(3, "Looking for frame before start (%d.%d) recording stream id (%d), queue has %d packets", *it, (*it == pktQueue.end()), *(*it));
recording_started->tv_sec, recording_started->tv_usec, mVideoStreamId, pktQueue.size()); std::unique_lock<std::mutex> lck(mutex);
for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { Debug(4, "Have Lock in get_packet");
ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet); while ( (!pktQueue.size()) or (*it == pktQueue.end()) ) {
if ( if ( deleting or zm_terminate )
( av_packet->stream_index == mVideoStreamId ) return nullptr;
&& Debug(2, "waiting. Queue size %d it == end? %d", pktQueue.size(), (*it == pktQueue.end()));
timercmp( &(zm_packet->timestamp), recording_started, <= ) condition.wait(lck);
) { }
Debug(3, "Found frame before start with stream index %d at %d.%d", if ( deleting or zm_terminate )
av_packet->stream_index, return nullptr;
zm_packet->timestamp.tv_sec,
zm_packet->timestamp.tv_usec); ZMPacket *p = *(*it);
break; if ( !p ) {
Error("Null p?!");
return nullptr;
}
Debug(3, "get_packet %p image_index: %d, about to lock packet", p, p->image_index);
while ( !(zm_terminate or deleting) and !p->trylock() ) {
Debug(3, "waiting. Queue size %d it == end? %d", pktQueue.size(), ( *it == pktQueue.end() ) );
condition.wait(lck);
}
Debug(2, "Locked packet, unlocking packetqueue mutex");
return p;
} // end ZMPacket *zm_packetqueue::get_packet(it)
bool zm_packetqueue::increment_it(packetqueue_iterator *it) {
Debug(2, "Incrementing %p, queue size %d, end? %d", it, pktQueue.size(), ((*it) == pktQueue.end()));
if ( (*it) == pktQueue.end() ) {
return false;
}
++(*it);
if ( *it != pktQueue.end() ) {
Debug(2, "Incrementing %p, %p still not at end %p, so returning true", it, *it, pktQueue.end());
return true;
}
return false;
} // end bool zm_packetqueue::increment_it(packetqueue_iterator *it)
// Increment it only considering packets for a given stream
bool zm_packetqueue::increment_it(packetqueue_iterator *it, int stream_id) {
Debug(2, "Incrementing %p, queue size %d, end? %d", it, pktQueue.size(), (*it == pktQueue.end()));
if ( *it == pktQueue.end() ) {
return false;
}
do {
++(*it);
} while ( (*it != pktQueue.end()) and ( (*(*it))->packet.stream_index != stream_id) );
if ( *it != pktQueue.end() ) {
Debug(2, "Incrementing %p, still not at end, so incrementing", it);
return true;
}
return false;
} // end bool zm_packetqueue::increment_it(packetqueue_iterator *it)
std::list<ZMPacket *>::iterator zm_packetqueue::get_event_start_packet_it(
std::list<ZMPacket *>::iterator snapshot_it,
unsigned int pre_event_count
) {
std::list<ZMPacket *>::iterator it = snapshot_it;
dumpPacket(&((*it)->packet));
// Step one count back pre_event_count frames as the minimum
// Do not assume that snapshot_it is video
// snapshot it might already point to the beginning
while ( ( it != pktQueue.begin() ) and pre_event_count ) {
Debug(1, "Previous packet pre_event_count %d stream_index %d keyframe %d", pre_event_count, (*it)->packet.stream_index, (*it)->keyframe);
dumpPacket(&((*it)->packet));
if ( (*it)->packet.stream_index == video_stream_id ) {
pre_event_count --;
if ( ! pre_event_count )
break;
} }
Debug(3, "Not Found frame before start with stream index %d at %d.%d", it--;
av_packet->stream_index,
zm_packet->timestamp.tv_sec,
zm_packet->timestamp.tv_usec);
} }
// it either points to beginning or we have seen pre_event_count video packets.
if ( it == pktQueue.rend() ) {
Info("Didn't find a frame before event starttime. keeping all"); if ( it == pktQueue.begin() ) {
return; Debug(1, "Hit begin");
} // hit end, the first packet in the queue should ALWAYS be a video keyframe.
// So we should be able to return it.
Debug(1, "Seeking back %d frames", pre_event_count); if ( pre_event_count ) {
for ( ; pre_event_count && (it != pktQueue.rend()); ++ it ) { if ( (*it)->image_index < (int)pre_event_count ) {
ZMPacket *zm_packet = *it; // probably just starting up
AVPacket *av_packet = &(zm_packet->packet); Debug(1, "Hit end of packetqueue before satisfying pre_event_count. Needed %d more video frames", pre_event_count);
if ( av_packet->stream_index == mVideoStreamId ) { } else {
--pre_event_count; Warning("Hit end of packetqueue before satisfying pre_event_count. Needed %d more video frames", pre_event_count);
}
dumpPacket(&((*it)->packet));
} }
return it;
} }
if ( it == pktQueue.rend() ) { // Not at beginning, so must be pointing at a video keyframe or maybe pre_event_count == 0
Debug(1, "ran out of pre_event frames before event starttime. keeping all"); if ( (*it)->keyframe ) {
return; dumpPacket(&((*it)->packet), "Found video keyframe, Returning");
return it;
} }
Debug(3, "Looking for keyframe"); while ( it-- != pktQueue.begin() ) {
for ( ; it != pktQueue.rend(); ++ it ) { dumpPacket(&((*it)->packet), "No keyframe");
ZMPacket *zm_packet = *it; if ( (*it)->packet.stream_index == video_stream_id and (*it)->keyframe )
AVPacket *av_packet = &(zm_packet->packet); return it; // Success
if ( }
( av_packet->flags & AV_PKT_FLAG_KEY ) if ( !(*it)->keyframe ) {
&& Warning("Hit end of packetqueue before satisfying pre_event_count. Needed %d more video frames", pre_event_count);
( av_packet->stream_index == mVideoStreamId ) }
) { return it;
Debug(3, "Found keyframe before start with stream index %d at %d.%d",
av_packet->stream_index, #if 0
zm_packet->timestamp.tv_sec, std::list<ZMPacket *>::iterator it = snapshot_it.base();
zm_packet->timestamp.tv_usec ); // Step one count back pre_event_count frames as the minimum
break; // Do not assume that snapshot_it is video
while ( ( it++ != pktQueue.rend() ) and pre_event_count ) {
// Is video, maybe should compare stream_id instead
if ( *it->image_index != -1 ) {
pre_event_count --;
} }
} }
if ( it == pktQueue.rend() ) { if ( it == pktQueue.rend() ) {
Debug(1, "Didn't find a keyframe before event starttime. keeping all" ); // hit end, the first packet in the queue should ALWAYS be a video keyframe.
return; // So we should be able to return it.
if ( pre_event_count )
Warning("Hit end of packetqueue before satisfying pre_event_count. Needed %d more video frames", pre_event_count);
return it.base();
}
if ( *it->keyframe ) {
return (it++).base();
} }
ZMPacket *zm_packet = *it; while ( ( it++ != pktQueue.rend() ) and ! (*it)->keyframe ) { }
AVPacket *av_packet = &(zm_packet->packet); if ( it == pktQueue.rend() ) {
Debug(3, "Found packet before start with stream index (%d) with keyframe (%d), distance(%d), size(%d)", // hit end, the first packet in the queue should ALWAYS be a video keyframe.
av_packet->stream_index, // So we should be able to return it.
( av_packet->flags & AV_PKT_FLAG_KEY ), if ( pre_event_count )
distance( it, pktQueue.rend() ), Warning("Hit end of packetqueue before satisfying pre_event_count. Needed %d more video frames", pre_event_count);
pktQueue.size() ); return it.base();
unsigned int deleted_frames = 0;
ZMPacket *packet = nullptr;
while ( distance(it, pktQueue.rend()) > 1 ) {
//while ( pktQueue.rend() != it ) {
packet = pktQueue.front();
pktQueue.pop_front();
packet_counts[packet->packet.stream_index] -= 1;
delete packet;
deleted_frames += 1;
} }
packet = nullptr; // tidy up for valgrind return (it++).base();
#endif
zm_packet = pktQueue.front(); }
av_packet = &(zm_packet->packet);
if ( ( ! ( av_packet->flags & AV_PKT_FLAG_KEY ) ) || ( av_packet->stream_index != mVideoStreamId ) ) {
Error( "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() );
} 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 )
void zm_packetqueue::dumpQueue() { void zm_packetqueue::dumpQueue() {
std::list<ZMPacket *>::reverse_iterator it; std::list<ZMPacket *>::reverse_iterator it;
@ -396,3 +585,43 @@ void zm_packetqueue::dumpQueue() {
dumpPacket(av_packet); dumpPacket(av_packet);
} }
} }
/* Returns an iterator to the first video keyframe in the queue.
* nullptr if no keyframe video packet exists.
*/
packetqueue_iterator * zm_packetqueue::get_video_it(bool wait) {
packetqueue_iterator *it = new packetqueue_iterator;
iterators.push_back(it);
std::unique_lock<std::mutex> lck(mutex);
*it = pktQueue.begin();
if ( wait ) {
while ( ((! pktQueue.size()) or (*it == pktQueue.end())) and !zm_terminate and !deleting ) {
Debug(2, "waiting. Queue size %d it == end? %d", pktQueue.size(), ( *it == pktQueue.end() ) );
condition.wait(lck);
*it = pktQueue.begin();
}
if ( deleting or zm_terminate ) {
delete it;
return nullptr;
}
}
while ( *it != pktQueue.end() ) {
ZMPacket *zm_packet = *(*it);
if ( !zm_packet ) {
Error("Null zmpacket in queue!?");
return nullptr;
}
Debug(1, "Packet keyframe %d for stream %d, so returning the it to it",
zm_packet->keyframe, zm_packet->packet.stream_index);
if ( zm_packet->keyframe and ( zm_packet->packet.stream_index == video_stream_id ) ) {
Debug(1, "Found a keyframe for stream %d, so returning the it to it", video_stream_id);
return it;
}
++(*it);
}
Debug(1, "DIdn't Found a keyframe for stream %d, so returning the it to it", video_stream_id);
return it;
}

View File

@ -25,17 +25,37 @@
//#include <boost/interprocess/allocators/allocator.hpp> //#include <boost/interprocess/allocators/allocator.hpp>
#include <list> #include <list>
#include "zm_packet.h" #include "zm_packet.h"
#include "zm_thread.h"
#include <mutex>
#include <condition_variable>
extern "C" { extern "C" {
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
} }
typedef std::list<ZMPacket *>::iterator packetqueue_iterator;
class zm_packetqueue { class zm_packetqueue {
public: public: // For now just to ease development
explicit zm_packetqueue(int max_stream_id); std::list<ZMPacket *> pktQueue;
std::list<ZMPacket *>::iterator analysis_it;
int video_stream_id;
int max_video_packet_count; // allow a negative value to someday mean unlimited
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 */
bool deleting;
std::list<packetqueue_iterator *> iterators;
std::mutex mutex;
std::condition_variable condition;
public:
zm_packetqueue(int p_max_video_packet_count, int p_video_stream_id, int p_audio_stream_id);
virtual ~zm_packetqueue(); virtual ~zm_packetqueue();
bool queuePacket(AVPacket* packet, struct timeval *timestamp); std::list<ZMPacket *>::const_iterator end() const { return pktQueue.end(); }
std::list<ZMPacket *>::const_iterator begin() const { return pktQueue.begin(); }
bool queuePacket(ZMPacket* packet); bool queuePacket(ZMPacket* packet);
bool queuePacket(AVPacket* packet);
ZMPacket * popPacket(); ZMPacket * popPacket();
bool popVideoPacket(ZMPacket* packet); bool popVideoPacket(ZMPacket* packet);
bool popAudioPacket(ZMPacket* packet); bool popAudioPacket(ZMPacket* packet);
@ -44,13 +64,21 @@ public:
void clearQueue(); void clearQueue();
void dumpQueue(); void dumpQueue();
unsigned int size(); unsigned int size();
unsigned int get_packet_count(int stream_id) const { return packet_counts[stream_id]; };
void clear_unwanted_packets(timeval *recording, int pre_event_count, int mVideoStreamId); void clear_unwanted_packets(timeval *recording, int pre_event_count, int mVideoStreamId);
int packet_count(int stream_id); 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 */
bool increment_it(packetqueue_iterator *it);
bool increment_it(packetqueue_iterator *it, int stream_id);
ZMPacket *get_packet(packetqueue_iterator *);
packetqueue_iterator *get_video_it(bool wait);
packetqueue_iterator *get_stream_it(int stream_id);
std::list<ZMPacket *>::iterator get_event_start_packet_it(
packetqueue_iterator snapshot_it,
unsigned int pre_event_count
);
}; };
#endif /* ZM_PACKETQUEUE_H */ #endif /* ZM_PACKETQUEUE_H */

View File

@ -88,9 +88,8 @@ public:
virtual int Disconnect() = 0; virtual int Disconnect() = 0;
virtual int PreCapture() { return 0; }; virtual int PreCapture() { return 0; };
virtual int PrimeCapture() { return 0; }; virtual int PrimeCapture() { return 0; };
virtual int Capture( Image &image ) = 0; virtual int Capture( ZMPacket &p ) = 0;
virtual int PostCapture() = 0; virtual int PostCapture() = 0;
virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory )=0;
int Read( int fd, char*buf, int size ); int Read( int fd, char*buf, int size );
}; };

View File

@ -87,6 +87,7 @@ RemoteCameraHttp::RemoteCameraHttp(
if ( capture ) { if ( capture ) {
Initialise(); Initialise();
} }
video_stream = NULL;
} }
RemoteCameraHttp::~RemoteCameraHttp() { RemoteCameraHttp::~RemoteCameraHttp() {
@ -261,12 +262,12 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) {
// There can be lots of bytes available. I've seen 4MB or more. This will vastly inflate our buffer size unnecessarily. // There can be lots of bytes available. I've seen 4MB or more. This will vastly inflate our buffer size unnecessarily.
if ( total_bytes_to_read > ZM_NETWORK_BUFSIZ ) { if ( total_bytes_to_read > ZM_NETWORK_BUFSIZ ) {
total_bytes_to_read = ZM_NETWORK_BUFSIZ; total_bytes_to_read = ZM_NETWORK_BUFSIZ;
Debug(3, "Just getting 32K" ); Debug(4, "Just getting 32K" );
} else { } else {
Debug(3, "Just getting %d", total_bytes_to_read ); Debug(4, "Just getting %d", total_bytes_to_read );
} }
} // end if bytes_expected or not } // end if bytes_expected or not
Debug( 3, "Expecting %d bytes", total_bytes_to_read ); Debug( 4, "Expecting %d bytes", total_bytes_to_read );
int total_bytes_read = 0; int total_bytes_read = 0;
do { do {
@ -337,8 +338,7 @@ int RemoteCameraHttp::GetResponse() {
header_len = header_expr->MatchLength( 1 ); header_len = header_expr->MatchLength( 1 );
Debug(4, "Captured header (%d bytes):\n'%s'", header_len, header); Debug(4, "Captured header (%d bytes):\n'%s'", header_len, header);
if ( status_expr->Match( header, header_len ) < 4 ) if ( status_expr->Match( header, header_len ) < 4 ) {
{
Error( "Unable to extract HTTP status from header" ); Error( "Unable to extract HTTP status from header" );
return( -1 ); return( -1 );
} }
@ -375,55 +375,43 @@ int RemoteCameraHttp::GetResponse() {
} }
Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version );
if ( connection_expr->Match( header, header_len ) == 2 ) if ( connection_expr->Match( header, header_len ) == 2 ) {
{
connection_type = connection_expr->MatchString( 1 ); connection_type = connection_expr->MatchString( 1 );
Debug( 3, "Got connection '%s'", connection_type ); Debug( 3, "Got connection '%s'", connection_type );
} }
if ( content_length_expr->Match( header, header_len ) == 2 ) if ( content_length_expr->Match( header, header_len ) == 2 ) {
{
content_length = atoi( content_length_expr->MatchString( 1 ) ); content_length = atoi( content_length_expr->MatchString( 1 ) );
Debug( 3, "Got content length '%d'", content_length ); Debug( 3, "Got content length '%d'", content_length );
} }
if ( content_type_expr->Match( header, header_len ) >= 2 ) if ( content_type_expr->Match( header, header_len ) >= 2 ) {
{
content_type = content_type_expr->MatchString( 1 ); content_type = content_type_expr->MatchString( 1 );
Debug( 3, "Got content type '%s'\n", content_type ); Debug( 3, "Got content type '%s'\n", content_type );
if ( content_type_expr->MatchCount() > 2 ) if ( content_type_expr->MatchCount() > 2 ) {
{
content_boundary = content_type_expr->MatchString( 2 ); content_boundary = content_type_expr->MatchString( 2 );
Debug( 3, "Got content boundary '%s'", content_boundary ); Debug( 3, "Got content boundary '%s'", content_boundary );
} }
} }
if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) {
{
// Single image // Single image
mode = SINGLE_IMAGE; mode = SINGLE_IMAGE;
format = JPEG; format = JPEG;
state = CONTENT; state = CONTENT;
} } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) {
else if ( !strcasecmp( content_type, "image/x-rgb" ) )
{
// Single image // Single image
mode = SINGLE_IMAGE; mode = SINGLE_IMAGE;
format = X_RGB; format = X_RGB;
state = CONTENT; state = CONTENT;
} } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) {
else if ( !strcasecmp( content_type, "image/x-rgbz" ) )
{
// Single image // Single image
mode = SINGLE_IMAGE; mode = SINGLE_IMAGE;
format = X_RGBZ; format = X_RGBZ;
state = CONTENT; state = CONTENT;
} } else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) {
else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) )
{
// Image stream, so start processing // Image stream, so start processing
if ( !content_boundary[0] ) if ( !content_boundary[0] ) {
{
Error( "No content boundary found in header '%s'", header ); Error( "No content boundary found in header '%s'", header );
return( -1 ); return( -1 );
} }
@ -434,15 +422,12 @@ int RemoteCameraHttp::GetResponse() {
//{ //{
//// MPEG stream, coming soon! //// MPEG stream, coming soon!
//} //}
else else {
{
Error( "Unrecognised content type '%s'", content_type ); Error( "Unrecognised content type '%s'", content_type );
return( -1 ); return( -1 );
} }
buffer.consume( header_len ); buffer.consume( header_len );
} } else {
else
{
Debug( 3, "Unable to extract header from stream, retrying" ); Debug( 3, "Unable to extract header from stream, retrying" );
//return( -1 ); //return( -1 );
} }
@ -454,39 +439,33 @@ int RemoteCameraHttp::GetResponse() {
static RegExpr *subcontent_length_expr = nullptr; static RegExpr *subcontent_length_expr = nullptr;
static RegExpr *subcontent_type_expr = nullptr; static RegExpr *subcontent_type_expr = nullptr;
if ( !subheader_expr ) if ( !subheader_expr ) {
{
char subheader_pattern[256] = ""; char subheader_pattern[256] = "";
snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary );
subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL );
} }
if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) {
{
subheader = subheader_expr->MatchString( 1 ); subheader = subheader_expr->MatchString( 1 );
subheader_len = subheader_expr->MatchLength( 1 ); subheader_len = subheader_expr->MatchLength( 1 );
Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader );
if ( !subcontent_length_expr ) if ( !subcontent_length_expr )
subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS );
if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) {
{
content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); content_length = atoi( subcontent_length_expr->MatchString( 1 ) );
Debug( 3, "Got subcontent length '%d'", content_length ); Debug( 3, "Got subcontent length '%d'", content_length );
} }
if ( !subcontent_type_expr ) if ( !subcontent_type_expr )
subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS );
if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) {
{
content_type = subcontent_type_expr->MatchString( 1 ); content_type = subcontent_type_expr->MatchString( 1 );
Debug( 3, "Got subcontent type '%s'", content_type ); Debug( 3, "Got subcontent type '%s'", content_type );
} }
buffer.consume( subheader_len ); buffer.consume( subheader_len );
state = CONTENT; state = CONTENT;
} } else {
else
{
Debug( 3, "Unable to extract subheader from stream, retrying" ); Debug( 3, "Unable to extract subheader from stream, retrying" );
buffer_len = GetData(); buffer_len = GetData();
if ( buffer_len < 0 ) { if ( buffer_len < 0 ) {
@ -506,28 +485,19 @@ int RemoteCameraHttp::GetResponse() {
*semicolon = '\0'; *semicolon = '\0';
} }
if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) {
{
format = JPEG; format = JPEG;
} } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) {
else if ( !strcasecmp( content_type, "image/x-rgb" ) )
{
format = X_RGB; format = X_RGB;
} } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) {
else if ( !strcasecmp( content_type, "image/x-rgbz" ) )
{
format = X_RGBZ; format = X_RGBZ;
} } else {
else
{
Error( "Found unsupported content type '%s'", content_type ); Error( "Found unsupported content type '%s'", content_type );
return( -1 ); return( -1 );
} }
if ( content_length ) if ( content_length ) {
{ while ( ((long)buffer.size() < content_length ) && ! zm_terminate ) {
while ( ((long)buffer.size() < content_length ) && ! zm_terminate )
{
Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length );
int bytes_read = GetData(); int bytes_read = GetData();
@ -538,11 +508,8 @@ int RemoteCameraHttp::GetResponse() {
bytes += bytes_read; bytes += bytes_read;
} }
Debug( 3, "Got end of image by length, content-length = %d", content_length ); Debug( 3, "Got end of image by length, content-length = %d", content_length );
} } else {
else while ( !content_length ) {
{
while ( !content_length )
{
buffer_len = GetData(); buffer_len = GetData();
if ( buffer_len < 0 ) { if ( buffer_len < 0 ) {
Error( "Unable to read content" ); Error( "Unable to read content" );
@ -550,16 +517,13 @@ int RemoteCameraHttp::GetResponse() {
} }
bytes += buffer_len; bytes += buffer_len;
static RegExpr *content_expr = 0; static RegExpr *content_expr = 0;
if ( mode == MULTI_IMAGE ) if ( mode == MULTI_IMAGE ) {
{ if ( !content_expr ) {
if ( !content_expr )
{
char content_pattern[256] = ""; char content_pattern[256] = "";
snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary );
content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); content_expr = new RegExpr( content_pattern, PCRE_DOTALL );
} }
if ( content_expr->Match( buffer, buffer.size() ) == 2 ) if ( content_expr->Match( buffer, buffer.size() ) == 2 ) {
{
content_length = content_expr->MatchLength( 1 ); content_length = content_expr->MatchLength( 1 );
Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); Debug( 3, "Got end of image by pattern, content-length = %d", content_length );
} }
@ -1067,7 +1031,7 @@ int RemoteCameraHttp::GetResponse() {
} }
} }
return( 0 ); return( 0 );
} } // end RemoteCameraHttp::GetResponse
int RemoteCameraHttp::PrimeCapture() { int RemoteCameraHttp::PrimeCapture() {
if ( sd < 0 ) { if ( sd < 0 ) {
@ -1098,9 +1062,9 @@ int RemoteCameraHttp::PreCapture() {
} }
} }
return 0; return 0;
} } // end int RemoteCameraHttp::PreCapture()
int RemoteCameraHttp::Capture( Image &image ) { int RemoteCameraHttp::Capture( ZMPacket &packet ) {
int content_length = GetResponse(); int content_length = GetResponse();
if ( content_length == 0 ) { if ( content_length == 0 ) {
Warning( "Unable to capture image, retrying" ); Warning( "Unable to capture image, retrying" );
@ -1111,46 +1075,54 @@ int RemoteCameraHttp::Capture( Image &image ) {
Disconnect(); Disconnect();
return -1; return -1;
} }
Image *image = packet.image;
switch( format ) { switch( format ) {
case JPEG : case JPEG :
{ if ( !image->DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) {
if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) { Error( "Unable to decode jpeg" );
Error( "Unable to decode jpeg" );
Disconnect();
return -1;
}
break;
}
case X_RGB :
{
if ( content_length != (long)image.Size() ) {
Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length );
Disconnect();
return -1;
}
image.Assign( width, height, colours, subpixelorder, buffer, imagesize );
break;
}
case X_RGBZ :
{
if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) {
Error( "Unable to unzip RGB image" );
Disconnect();
return -1;
}
image.Assign( width, height, colours, subpixelorder, buffer, imagesize );
break;
}
default :
{
Error( "Unexpected image format encountered" );
Disconnect(); Disconnect();
return -1; return -1;
} }
break;
case X_RGB :
if ( content_length != (long)image->Size() ) {
Error( "Image length mismatch, expected %d bytes, content length was %d", image->Size(), content_length );
Disconnect();
return -1;
}
image->Assign( width, height, colours, subpixelorder, buffer, imagesize );
break;
case X_RGBZ :
if ( !image->Unzip( buffer.extract( content_length ), content_length ) ) {
Error( "Unable to unzip RGB image" );
Disconnect();
return -1;
}
image->Assign( width, height, colours, subpixelorder, buffer, imagesize );
break;
default :
Error( "Unexpected image format encountered" );
Disconnect();
return -1;
} }
return 1; return 1;
} } // end ZmPacket *RmoteCameraHttp::Capture( &image );
int RemoteCameraHttp::PostCapture() { int RemoteCameraHttp::PostCapture() {
return 0; return 0;
} }
AVStream *RemoteCameraHttp::get_VideoStream() {
if ( video_stream ) {
AVFormatContext *oc = avformat_alloc_context();
video_stream = avformat_new_stream( oc, NULL );
if ( video_stream ) {
video_stream->codec->width = width;
video_stream->codec->height = height;
video_stream->codec->pix_fmt = GetFFMPEGPixelFormat(colours,subpixelorder);
}
}
return video_stream;
}

View File

@ -44,7 +44,22 @@ protected:
enum { SIMPLE, REGEXP } method; enum { SIMPLE, REGEXP } method;
public: public:
RemoteCameraHttp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, 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 ); RemoteCameraHttp(
unsigned int p_monitor_id,
const std::string &method,
const std::string &host,
const std::string &port,
const std::string &path,
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
);
~RemoteCameraHttp(); ~RemoteCameraHttp();
void Initialise(); void Initialise();
@ -57,9 +72,9 @@ public:
int GetResponse(); int GetResponse();
int PrimeCapture(); int PrimeCapture();
int PreCapture(); int PreCapture();
int Capture( Image &image ); int Capture( ZMPacket &p );
int PostCapture(); int PostCapture();
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return 0;}; AVStream* get_VideoStream();
int Close() { Disconnect(); return 0; }; int Close() { Disconnect(); return 0; };
}; };

View File

@ -68,6 +68,7 @@ RemoteCameraNVSocket::RemoteCameraNVSocket(
timeout.tv_sec = 0; timeout.tv_sec = 0;
timeout.tv_usec = 0; timeout.tv_usec = 0;
subpixelorder = ZM_SUBPIX_ORDER_BGR; subpixelorder = ZM_SUBPIX_ORDER_BGR;
video_stream = NULL;
if ( capture ) { if ( capture ) {
Initialise(); Initialise();
@ -137,13 +138,13 @@ int RemoteCameraNVSocket::Disconnect() {
} }
int RemoteCameraNVSocket::SendRequest( std::string request ) { int RemoteCameraNVSocket::SendRequest( std::string request ) {
Debug( 4, "Sending request: %s", request.c_str() ); //Debug( 4, "Sending request: %s", request.c_str() );
if ( write( sd, request.data(), request.length() ) < 0 ) { if ( write( sd, request.data(), request.length() ) < 0 ) {
Error( "Can't write: %s", strerror(errno) ); Error( "Can't write: %s", strerror(errno) );
Disconnect(); Disconnect();
return( -1 ); return( -1 );
} }
Debug( 4, "Request sent" ); //Debug( 4, "Request sent" );
return( 0 ); return( 0 );
} }
@ -178,11 +179,12 @@ int RemoteCameraNVSocket::PrimeCapture() {
Disconnect(); Disconnect();
return -1; return -1;
} }
mVideoStreamId=0;
return 0; return 0;
} }
int RemoteCameraNVSocket::Capture( Image &image ) { int RemoteCameraNVSocket::Capture( ZMPacket &zm_packet ) {
if ( SendRequest("GetNextImage\n") < 0 ) { if ( SendRequest("GetNextImage\n") < 0 ) {
Warning( "Unable to capture image, retrying" ); Warning( "Unable to capture image, retrying" );
return 0; return 0;
@ -202,10 +204,35 @@ int RemoteCameraNVSocket::Capture( Image &image ) {
return 0; return 0;
} }
image.Assign(width, height, colours, subpixelorder, buffer, imagesize); zm_packet.image->Assign(width, height, colours, subpixelorder, buffer, imagesize);
zm_packet.keyframe = 1;
return 1; return 1;
} }
int RemoteCameraNVSocket::PostCapture() { int RemoteCameraNVSocket::PostCapture() {
return( 0 ); return( 0 );
} }
AVStream *RemoteCameraNVSocket::get_VideoStream() {
if ( ! video_stream ) {
AVFormatContext *oc = avformat_alloc_context();
video_stream = avformat_new_stream( oc, NULL );
if ( video_stream ) {
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
video_stream->codecpar->width = width;
video_stream->codecpar->height = height;
video_stream->codecpar->format = GetFFMPEGPixelFormat(colours,subpixelorder);
video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
#else
video_stream->codec->width = width;
video_stream->codec->height = height;
video_stream->codec->pix_fmt = GetFFMPEGPixelFormat(colours,subpixelorder);
video_stream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
#endif
} else {
Error("Can't create video stream");
}
} else {
Debug(5,"Have videostream");
}
return video_stream;
}

View File

@ -61,12 +61,11 @@ public:
int Connect(); int Connect();
int Disconnect(); int Disconnect();
int SendRequest( std::string ); int SendRequest( std::string );
int ReadData( Buffer &buffer, unsigned int bytes_expected=0 );
int GetResponse(); int GetResponse();
int PrimeCapture(); int PrimeCapture();
int Capture( Image &image ); int Capture( ZMPacket &p );
int PostCapture(); int PostCapture();
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; AVStream* get_VideoStream();
int Close() { return 0; }; int Close() { return 0; };
}; };

View File

@ -69,14 +69,8 @@ RemoteCameraRtsp::RemoteCameraRtsp(
mAudioStreamId = -1; mAudioStreamId = -1;
mCodecContext = nullptr; mCodecContext = nullptr;
mCodec = nullptr; mCodec = nullptr;
mRawFrame = nullptr;
mFrame = nullptr;
frameCount = 0; frameCount = 0;
startTime=0;
#if HAVE_LIBSWSCALE
mConvertContext = nullptr;
#endif
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */ /* 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 ) { if ( colours == ZM_COLOUR_RGB32 ) {
subpixelorder = ZM_SUBPIX_ORDER_RGBA; subpixelorder = ZM_SUBPIX_ORDER_RGBA;
@ -93,15 +87,6 @@ RemoteCameraRtsp::RemoteCameraRtsp(
} // end RemoteCameraRtsp::RemoteCameraRtsp(...) } // end RemoteCameraRtsp::RemoteCameraRtsp(...)
RemoteCameraRtsp::~RemoteCameraRtsp() { RemoteCameraRtsp::~RemoteCameraRtsp() {
av_frame_free(&mFrame);
av_frame_free(&mRawFrame);
#if HAVE_LIBSWSCALE
if ( mConvertContext ) {
sws_freeContext(mConvertContext);
mConvertContext = nullptr;
}
#endif
if ( mCodecContext ) { if ( mCodecContext ) {
avcodec_close(mCodecContext); avcodec_close(mCodecContext);
@ -166,7 +151,7 @@ int RemoteCameraRtsp::PrimeCapture() {
// Find first video stream present // Find first video stream present
mVideoStreamId = -1; mVideoStreamId = -1;
mAudioStreamId = -1; mAudioStreamId = -1;
// Find the first video stream. // Find the first video stream.
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) {
if ( is_video_stream(mFormatContext->streams[i]) ) { if ( is_video_stream(mFormatContext->streams[i]) ) {
@ -193,7 +178,12 @@ int RemoteCameraRtsp::PrimeCapture() {
Debug(3, "Unable to locate audio stream"); Debug(3, "Unable to locate audio stream");
// Get a pointer to the codec context for the video stream // Get a pointer to the codec context for the video stream
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
mCodecContext = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(mCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar);
#else
mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; mCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
#endif
// Find the decoder for the video stream // Find the decoder for the video stream
mCodec = avcodec_find_decoder(mCodecContext->codec_id); mCodec = avcodec_find_decoder(mCodecContext->codec_id);
@ -208,15 +198,6 @@ int RemoteCameraRtsp::PrimeCapture() {
#endif #endif
Panic("Can't open codec"); Panic("Can't open codec");
// 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 == nullptr || mFrame == nullptr )
Fatal("Unable to allocate frame(s)");
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1);
#else #else
@ -226,23 +207,9 @@ int RemoteCameraRtsp::PrimeCapture() {
if ( (unsigned int)pSize != imagesize ) { if ( (unsigned int)pSize != imagesize ) {
Fatal("Image size mismatch. Required: %d Available: %d", pSize, imagesize); Fatal("Image size mismatch. Required: %d Available: %d", pSize, imagesize);
} }
/*
#if HAVE_LIBSWSCALE
if(!sws_isSupportedInput(mCodecContext->pix_fmt)) {
Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff));
}
if(!sws_isSupportedOutput(imagePixFormat)) { return 1;
Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff)); } // end PrimeCapture
}
#else // HAVE_LIBSWSCALE
Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" );
#endif // HAVE_LIBSWSCALE
*/
return 0;
}
int RemoteCameraRtsp::PreCapture() { int RemoteCameraRtsp::PreCapture() {
if ( !rtspThread->isRunning() ) if ( !rtspThread->isRunning() )
@ -251,22 +218,14 @@ int RemoteCameraRtsp::PreCapture() {
Error("Cannot precapture, no RTP sources"); Error("Cannot precapture, no RTP sources");
return -1; return -1;
} }
return 0; return 1;
} }
int RemoteCameraRtsp::Capture( Image &image ) { int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) {
AVPacket packet;
uint8_t* directbuffer;
int frameComplete = false; int frameComplete = false;
AVPacket *packet = &zm_packet.packet;
/* Request a writeable buffer of the target image */ while ( !frameComplete ) {
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
if ( directbuffer == nullptr ) {
Error("Failed requesting writeable buffer for the captured image.");
return -1;
}
while ( true ) {
buffer.clear(); buffer.clear();
if ( !rtspThread->isRunning() ) if ( !rtspThread->isRunning() )
return -1; return -1;
@ -283,7 +242,8 @@ int RemoteCameraRtsp::Capture( Image &image ) {
int nalType = (buffer.head()[3] & 0x1f); int nalType = (buffer.head()[3] & 0x1f);
// SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures // SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures
if ( nalType == 7 ) { if ( nalType == 1 ) {
} else if ( nalType == 7 ) {
lastSps = buffer; lastSps = buffer;
continue; continue;
} else if ( nalType == 8 ) { } else if ( nalType == 8 ) {
@ -291,6 +251,8 @@ int RemoteCameraRtsp::Capture( Image &image ) {
lastPps = buffer; lastPps = buffer;
continue; continue;
} else if ( nalType == 5 ) { } else if ( nalType == 5 ) {
packet->flags |= AV_PKT_FLAG_KEY;
zm_packet.keyframe = 1;
// IDR // IDR
buffer += lastSps; buffer += lastSps;
buffer += lastPps; buffer += lastPps;
@ -301,92 +263,31 @@ int RemoteCameraRtsp::Capture( Image &image ) {
Debug(3, "Not an h264 packet"); Debug(3, "Not an h264 packet");
} }
av_init_packet(&packet);
while ( (!frameComplete) && (buffer.size() > 0) ) { while ( (!frameComplete) && (buffer.size() > 0) ) {
packet.data = buffer.head(); packet->data = buffer.head();
packet.size = buffer.size(); packet->size = buffer.size();
bytes += packet.size; bytes += packet->size;
// So I think this is the magic decode step. Result is a raw image? if ( 1 != zm_packet.decode(mCodecContext) ) {
int len = zm_send_packet_receive_frame(mCodecContext, mRawFrame, packet);
if ( len < 0 ) {
Error("Error while decoding frame %d", frameCount); Error("Error while decoding frame %d", frameCount);
Hexdump(Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size()); Hexdump(Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size());
buffer.clear(); buffer.clear();
continue; break;
} }
int len = packet->size;
zm_packet.codec_type = mCodecContext->codec_type;
frameComplete = true; frameComplete = true;
Debug(2, "Frame: %d - %d/%d", frameCount, len, buffer.size()); Debug(2, "Frame: %d - %d/%d", frameCount, len, buffer.size());
//if ( buffer.size() < 400 )
//Hexdump( 0, buffer.head(), buffer.size() );
buffer -= len; buffer -= len;
} }
// At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer?
if ( frameComplete ) {
Debug(3, "Got frame %d", frameCount);
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
// From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too.
int size = av_image_fill_arrays(
mFrame->data, mFrame->linesize,
directbuffer, imagePixFormat, width, height,
(AV_PIX_FMT_RGBA == imagePixFormat ? 32 : 1)
);
if ( size < 0 ) {
Error("Problem setting up data pointers into image %s",
av_make_error_string(size).c_str());
}
#else
avpicture_fill((AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
#endif
#if HAVE_LIBSWSCALE
if ( mConvertContext == nullptr ) {
mConvertContext = sws_getContext(
mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt,
width, height, imagePixFormat, SWS_BICUBIC, nullptr, nullptr, nullptr);
if ( mConvertContext == nullptr )
Fatal("Unable to create conversion context");
if (
((unsigned int)mRawFrame->width != width)
||
((unsigned int)mRawFrame->height != height)
) {
Warning("Monitor dimensions are %dx%d but camera is sending %dx%d",
width, height, mRawFrame->width, mRawFrame->height);
}
}
if ( sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize) < 0 )
Fatal("Unable to convert raw format %u to target format %u at frame %d",
mCodecContext->pix_fmt, imagePixFormat, frameCount );
#else // HAVE_LIBSWSCALE
Fatal("You must compile ffmpeg with the --enable-swscale option to use RTSP cameras");
#endif // HAVE_LIBSWSCALE
frameCount++;
} /* frame complete */
zm_av_packet_unref(&packet);
} /* getFrame() */ } /* getFrame() */
if ( frameComplete )
return 1;
} // end while true } // end while true
// can never get here. return 1;
return 0; } // end int RemoteCameraRtsp::Capture(ZMPacket &packet)
}
//Function to handle capture and store
int RemoteCameraRtsp::PostCapture() { int RemoteCameraRtsp::PostCapture() {
return 0; return 1;
} }
#endif // HAVE_LIBAVFORMAT #endif // HAVE_LIBAVFORMAT

View File

@ -55,22 +55,10 @@ protected:
#if HAVE_LIBAVFORMAT #if HAVE_LIBAVFORMAT
AVFormatContext *mFormatContext; AVFormatContext *mFormatContext;
int mVideoStreamId;
int mAudioStreamId;
AVCodecContext *mCodecContext; AVCodecContext *mCodecContext;
AVCodec *mCodec; AVCodec *mCodec;
AVFrame *mRawFrame;
AVFrame *mFrame;
_AVPIXELFORMAT imagePixFormat; _AVPIXELFORMAT imagePixFormat;
#endif // HAVE_LIBAVFORMAT #endif // HAVE_LIBAVFORMAT
bool wasRecording;
VideoStore *videoStore;
char oldDirectory[4096];
int64_t startTime;
#if HAVE_LIBSWSCALE
struct SwsContext *mConvertContext;
#endif
public: public:
RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
@ -83,10 +71,21 @@ public:
int PrimeCapture(); int PrimeCapture();
int PreCapture(); int PreCapture();
int Capture( Image &image ); int Capture( ZMPacket &p );
int PostCapture(); int PostCapture();
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return 0;};
int Close() { return 0; }; int Close() { return 0; };
AVStream *get_VideoStream() {
if ( mVideoStreamId != -1 )
return mFormatContext->streams[mVideoStreamId];
return nullptr;
}
AVStream *get_AudioStream() {
if ( mAudioStreamId != -1 )
return mFormatContext->streams[mAudioStreamId];
return nullptr;
}
AVCodecContext *get_VideoCodecContext() { return mVideoCodecContext; };
AVCodecContext *get_AudioCodecContext() { return mAudioCodecContext; };
}; };
#endif // ZM_REMOTE_CAMERA_RTSP_H #endif // ZM_REMOTE_CAMERA_RTSP_H

View File

@ -318,7 +318,7 @@ bool RtpSource::handlePacket(const unsigned char *packet, size_t packetLen) {
// What is the point of this for loop? Is it just me, or will it call getUpdatedValue once or twice? Could it not be better written as // What is the point of this for loop? Is it just me, or will it call getUpdatedValue once or twice? Could it not be better written as
// if ( ! mFrameProcessed.getUpdatedValue( 1 ) && mFrameProcessed.getUpdatedValue( 1 ) ) return false; // if ( ! mFrameProcessed.getUpdatedValue( 1 ) && mFrameProcessed.getUpdatedValue( 1 ) ) return false;
for ( int count = 0; !mFrameProcessed.getUpdatedValue( 1 ); count++ ) for ( int count = 0; !mFrameProcessed.getUpdatedValue(1); count++ )
if ( count > 1 ) if ( count > 1 )
return false; return false;
} }
@ -352,9 +352,10 @@ bool RtpSource::getFrame(Buffer &buffer) {
if ( !mFrameReady.getValueImmediate() ) { if ( !mFrameReady.getValueImmediate() ) {
Debug(3, "Getting frame but not ready"); Debug(3, "Getting frame but not ready");
// Allow for a couple of spurious returns // Allow for a couple of spurious returns
for ( int count = 0; !mFrameReady.getUpdatedValue(1); count++ ) for ( int count = 0; !mFrameReady.getUpdatedValue(1); count++ ) {
if ( count > 1 ) if ( count > 1 )
return false; return false;
}
} }
buffer = mFrame; buffer = mFrame;
mFrameReady.setValueImmediate(false); mFrameReady.setValueImmediate(false);

View File

@ -376,8 +376,8 @@ int RtspThread::run() {
try { try {
mSessDesc = new SessionDescriptor( mUrl, sdp ); mSessDesc = new SessionDescriptor( mUrl, sdp );
mFormatContext = mSessDesc->generateFormatContext(); mFormatContext = mSessDesc->generateFormatContext();
} catch( const Exception &e ) { } catch ( const Exception &e ) {
Error( e.getMessage().c_str() ); Error(e.getMessage().c_str());
return -1; return -1;
} }
@ -411,7 +411,7 @@ int RtspThread::run() {
{ {
// Check if control Url is absolute or relative // Check if control Url is absolute or relative
controlUrl = mediaDesc->getControlUrl(); controlUrl = mediaDesc->getControlUrl();
if (std::equal(trackUrl.begin(), trackUrl.end(), controlUrl.begin())) { if ( std::equal(trackUrl.begin(), trackUrl.end(), controlUrl.begin()) ) {
trackUrl = controlUrl; trackUrl = controlUrl;
} else { } else {
if ( *trackUrl.rbegin() != '/') { if ( *trackUrl.rbegin() != '/') {
@ -422,46 +422,36 @@ int RtspThread::run() {
} }
rtpClock = mediaDesc->getClock(); rtpClock = mediaDesc->getClock();
codecId = mFormatContext->streams[i]->codec->codec_id; codecId = mFormatContext->streams[i]->codec->codec_id;
// Hackery pokery
//rtpClock = mFormatContext->streams[i]->codec->sample_rate;
break; break;
} }
} }
} }
switch( mMethod ) { switch ( mMethod ) {
case RTP_UNICAST : case RTP_UNICAST :
{
localPorts[0] = requestPorts(); localPorts[0] = requestPorts();
localPorts[1] = localPorts[0]+1; localPorts[1] = localPorts[0]+1;
message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port="+stringtf( "%d", localPorts[0] )+"-"+stringtf( "%d", localPorts[1] )+"\r\n"; message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port="+stringtf( "%d", localPorts[0] )+"-"+stringtf( "%d", localPorts[1] )+"\r\n";
break; break;
}
case RTP_MULTICAST : case RTP_MULTICAST :
{
message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;multicast\r\n"; message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;multicast\r\n";
break; break;
}
case RTP_RTSP : case RTP_RTSP :
case RTP_RTSP_HTTP : case RTP_RTSP_HTTP :
{
message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP/TCP;unicast\r\n"; message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP/TCP;unicast\r\n";
break; break;
}
default: default:
{
Panic( "Got unexpected method %d", mMethod ); Panic( "Got unexpected method %d", mMethod );
break; break;
}
} }
if ( !sendCommand( message ) ) if ( !sendCommand(message) )
return( -1 ); return -1;
if ( !recvResponse( response ) ) if ( !recvResponse(response) )
return( -1 ); return -1;
lines = split( response, "\r\n" ); lines = split(response, "\r\n");
std::string session; std::string session;
int timeout = 0; int timeout = 0;
char transport[256] = ""; char transport[256] = "";
@ -473,18 +463,18 @@ int RtspThread::run() {
if ( sessionLine.size() == 2 ) if ( sessionLine.size() == 2 )
sscanf( trimSpaces( sessionLine[1] ).c_str(), "timeout=%d", &timeout ); sscanf( trimSpaces( sessionLine[1] ).c_str(), "timeout=%d", &timeout );
} }
sscanf( lines[i].c_str(), "Transport: %s", transport ); sscanf(lines[i].c_str(), "Transport: %s", transport);
} }
if ( session.empty() ) if ( session.empty() )
Fatal( "Unable to get session identifier from response '%s'", response.c_str() ); Fatal("Unable to get session identifier from response '%s'", response.c_str());
Debug( 2, "Got RTSP session %s, timeout %d secs", session.c_str(), timeout ); Debug(2, "Got RTSP session %s, timeout %d secs", session.c_str(), timeout);
if ( !transport[0] ) if ( !transport[0] )
Fatal( "Unable to get transport details from response '%s'", response.c_str() ); Fatal("Unable to get transport details from response '%s'", response.c_str());
Debug( 2, "Got RTSP transport %s", transport ); Debug(2, "Got RTSP transport %s", transport);
std::string method = ""; std::string method = "";
int remotePorts[2] = { 0, 0 }; int remotePorts[2] = { 0, 0 };
@ -531,23 +521,23 @@ int RtspThread::run() {
Debug( 2, "RTSP Remote Channels are %d/%d", remoteChannels[0], remoteChannels[1] ); Debug( 2, "RTSP Remote Channels are %d/%d", remoteChannels[0], remoteChannels[1] );
message = "PLAY "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\nRange: npt=0.000-\r\n"; message = "PLAY "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\nRange: npt=0.000-\r\n";
if ( !sendCommand( message ) ) if ( !sendCommand(message) )
return( -1 ); return -1;
if ( !recvResponse( response ) ) if ( !recvResponse(response) )
return( -1 ); return -1;
lines = split( response, "\r\n" ); lines = split(response, "\r\n");
std::string rtpInfo; std::string rtpInfo;
for ( size_t i = 0; i < lines.size(); i++ ) { for ( size_t i = 0; i < lines.size(); i++ ) {
if ( ( lines[i].size() > 9 ) && ( lines[i].substr( 0, 9 ) == "RTP-Info:" ) ) if ( ( lines[i].size() > 9 ) && ( lines[i].substr(0, 9) == "RTP-Info:" ) )
rtpInfo = trimSpaces( lines[i].substr( 9 ) ); rtpInfo = trimSpaces(lines[i].substr(9));
// Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent // Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent
if ( ( lines[i].size() > 8 ) && ( lines[i].substr( 0, 8 ) == "Session:" ) && ( timeout == 0 ) ) { if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) && ( timeout == 0 ) ) {
StringVector sessionLine = split( lines[i].substr(9), ";" ); StringVector sessionLine = split(lines[i].substr(9), ";");
if ( sessionLine.size() == 2 ) if ( sessionLine.size() == 2 )
sscanf( trimSpaces( sessionLine[1] ).c_str(), "timeout=%d", &timeout ); sscanf(trimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout);
if ( timeout > 0 ) if ( timeout > 0 )
Debug( 2, "Got timeout %d secs from PLAY command response", timeout ); Debug(2, "Got timeout %d secs from PLAY command response", timeout);
} }
} }

View File

@ -0,0 +1,46 @@
/* ---------------------------------------------------------------------------
**
** ADTS_DeviceSource.cpp
**
** ADTS Live555 source
**
** -------------------------------------------------------------------------*/
#include <sstream>
#include <iomanip>
// live555
#include <Base64.hh>
#include "zm_rtsp_server_adts_source.h"
static unsigned const samplingFrequencyTable[16] = {
96000, 88200, 64000, 48000,
44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000,
7350, 0, 0, 0
};
// ---------------------------------
// ADTS ZoneMinder FramedSource
// ---------------------------------
//
ADTS_ZoneMinderDeviceSource::ADTS_ZoneMinderDeviceSource(
UsageEnvironment& env,
Monitor *monitor,
AVStream *stream,
unsigned int queueSize
)
:
ZoneMinderDeviceSource(env, monitor, stream, queueSize),
samplingFrequencyIndex(0),
channels(stream->codecpar->channels)
{
std::ostringstream os;
os <<
"profile-level-id=1;"
"mode=AAC-hbr;sizelength=13;indexlength=3;"
"indexdeltalength=3"
//<< extradata2psets(nullptr, m_stream)
<< "\r\n";
m_auxLine.assign(os.str());
}

View File

@ -0,0 +1,67 @@
/* ---------------------------------------------------------------------------
** This software is in the public domain, furnished "as is", without technical
** support, and with no warranty, express or implied, as to its usefulness for
** any purpose.
**
** ADTS_ZoneMinderDeviceSource.h
**
** ADTS ZoneMinder live555 source
**
** -------------------------------------------------------------------------*/
#ifndef ADTS_ZoneMinder_DEVICE_SOURCE
#define ADTS_ZoneMinder_DEVICE_SOURCE
// project
#include "zm_rtsp_server_device_source.h"
#include "zm_rtsp_server_frame.h"
// ---------------------------------
// ADTS(AAC) ZoneMinder FramedSource
// ---------------------------------
class ADTS_ZoneMinderDeviceSource : public ZoneMinderDeviceSource {
public:
static ADTS_ZoneMinderDeviceSource* createNew(
UsageEnvironment& env,
Monitor* monitor,
AVStream * stream,
unsigned int queueSize
) {
Debug(1, "m_stream %p codecpar %p channels %d",
stream, stream->codecpar, stream->codecpar->channels);
return new ADTS_ZoneMinderDeviceSource(env, monitor, stream, queueSize);
};
protected:
ADTS_ZoneMinderDeviceSource(
UsageEnvironment& env,
Monitor *monitor,
AVStream *stream,
unsigned int queueSize
);
virtual ~ADTS_ZoneMinderDeviceSource() {}
/*
virtual unsigned char* extractFrame(unsigned char* frame, size_t& size, size_t& outsize);
virtual unsigned char* findMarker(unsigned char *frame, size_t size, size_t &length);
*/
public:
int samplingFrequency() { return m_stream->codecpar->sample_rate; };
const char *configStr() { return config.c_str(); };
int numChannels() {
Debug(1, "this %p m_stream %p channels %d",
this, m_stream, channels);
Debug(1, "m_stream %p codecpar %p channels %d => %d",
m_stream, m_stream->codecpar, m_stream->codecpar->channels, channels);
return channels;
return m_stream->codecpar->channels;
}
protected:
std::string config;
int samplingFrequencyIndex;
int channels;
};
#endif

View File

@ -0,0 +1,207 @@
/* ---------------------------------------------------------------------------
** This software is in the public domain, furnished "as is", without technical
** support, and with no warranty, express or implied, as to its usefulness for
** any purpose.
**
**
** ZoneMinder Live555 source
**
** -------------------------------------------------------------------------*/
#include <utility>
#include "zm_rtsp_server_device_source.h"
#include "zm_rtsp_server_frame.h"
#include "zm_logger.h"
ZoneMinderDeviceSource::ZoneMinderDeviceSource(
UsageEnvironment& env,
Monitor* monitor,
AVStream *stream,
unsigned int queueSize
) :
FramedSource(env),
m_stream(stream),
m_monitor(monitor),
m_packetqueue(nullptr),
m_packetqueue_it(nullptr),
m_queueSize(queueSize)
{
m_eventTriggerId = envir().taskScheduler().createEventTrigger(ZoneMinderDeviceSource::deliverFrameStub);
memset(&m_thid, 0, sizeof(m_thid));
memset(&m_mutex, 0, sizeof(m_mutex));
if ( m_monitor ) {
m_packetqueue = m_monitor->GetPacketQueue();
if ( !m_packetqueue ) {
Fatal("No packetqueue");
}
pthread_mutex_init(&m_mutex, nullptr);
pthread_create(&m_thid, nullptr, threadStub, this);
} else {
Error("No monitor in ZoneMinderDeviceSource");
}
}
ZoneMinderDeviceSource::~ZoneMinderDeviceSource() {
stop = 1;
envir().taskScheduler().deleteEventTrigger(m_eventTriggerId);
pthread_join(m_thid, nullptr);
pthread_mutex_destroy(&m_mutex);
}
// thread mainloop
void* ZoneMinderDeviceSource::thread() {
stop = 0;
while ( !stop ) {
getNextFrame();
}
return nullptr;
}
// getting FrameSource callback
void ZoneMinderDeviceSource::doGetNextFrame() {
deliverFrame();
}
// stopping FrameSource callback
void ZoneMinderDeviceSource::doStopGettingFrames() {
Debug(1, "ZoneMinderDeviceSource::doStopGettingFrames");
FramedSource::doStopGettingFrames();
}
// deliver frame to the sink
void ZoneMinderDeviceSource::deliverFrame() {
if ( !isCurrentlyAwaitingData() ) {
Debug(4, "not awaiting data");
return;
}
pthread_mutex_lock(&m_mutex);
if ( m_captureQueue.empty() ) {
Debug(4, "Queue is empty");
pthread_mutex_unlock(&m_mutex);
return;
}
NAL_Frame *frame = m_captureQueue.front();
m_captureQueue.pop_front();
pthread_mutex_unlock(&m_mutex);
fDurationInMicroseconds = 0;
fFrameSize = 0;
unsigned int nal_size = frame->size();
if ( nal_size > fMaxSize ) {
fFrameSize = fMaxSize;
fNumTruncatedBytes = nal_size - fMaxSize;
} else {
fFrameSize = nal_size;
}
Debug(2, "deliverFrame stream: %d timestamp: %ld.%06ld size: %d queuesize: %d",
m_stream->index,
frame->m_timestamp.tv_sec, frame->m_timestamp.tv_usec,
fFrameSize,
m_captureQueue.size()
);
fPresentationTime = frame->m_timestamp;
memcpy(fTo, frame->buffer(), fFrameSize);
if ( fFrameSize > 0 ) {
// send Frame to the consumer
FramedSource::afterGetting(this);
}
delete frame;
} // end void ZoneMinderDeviceSource::deliverFrame()
// FrameSource callback on read event
void ZoneMinderDeviceSource::incomingPacketHandler() {
if ( this->getNextFrame() <= 0 ) {
handleClosure(this);
}
}
// read from monitor
int ZoneMinderDeviceSource::getNextFrame() {
if ( zm_terminate )
return -1;
if ( !m_packetqueue_it ) {
m_packetqueue_it = m_packetqueue->get_video_it(true);
}
ZMPacket *zm_packet = m_packetqueue->get_packet(m_packetqueue_it);
while ( zm_packet and (zm_packet->packet.stream_index != m_stream->index) ) {
zm_packet->unlock();
// We want our stream to start at the same it as the video
// but if this is an audio stream we need to increment past that first packet
Debug(4, "Have audio packet, skipping");
m_packetqueue->increment_it(m_packetqueue_it, m_stream->index);
zm_packet = m_packetqueue->get_packet(m_packetqueue_it);
}
if ( !zm_packet ) {
Debug(1, "null zm_packet %p", zm_packet);
return -1;
}
// packet is locked
AVPacket *pkt = &zm_packet->packet;
m_packetqueue->increment_it(m_packetqueue_it, m_stream->index);
// Convert pts to timeval
int64_t pts = av_rescale_q(pkt->dts, m_stream->time_base, AV_TIME_BASE_Q);
timeval tv = { pts/1000000, pts%1000000 };
dumpPacket(m_stream, pkt, "rtspServer");
Debug(2, "pts %" PRId64 " pkt.pts %" PRId64 " tv %d.%d", pts, pkt->pts, tv.tv_sec, tv.tv_usec);
std::list< std::pair<unsigned char*, size_t> > framesList = this->splitFrames(pkt->data, pkt->size);
zm_packet->unlock();
zm_packet = nullptr;// we no longer have the lock so shouldn't be accessing it
while ( framesList.size() ) {
std::pair<unsigned char*, size_t> nal = framesList.front();
framesList.pop_front();
NAL_Frame *frame = new NAL_Frame(nal.first, nal.second, tv);
pthread_mutex_lock(&m_mutex);
if ( m_captureQueue.size() ) {
NAL_Frame * f = m_captureQueue.front();
while ( (f->m_timestamp.tv_sec - tv.tv_sec) > 10 ) {
m_captureQueue.pop_front();
delete f;
f = m_captureQueue.front();
}
}
#if 0
while ( m_captureQueue.size() >= m_queueSize ) {
Debug(2, "Queue full dropping frame %d", m_captureQueue.size());
NAL_Frame * f = m_captureQueue.front();
m_captureQueue.pop_front();
delete f;
}
#endif
m_captureQueue.push_back(frame);
pthread_mutex_unlock(&m_mutex);
// post an event to ask to deliver the frame
envir().taskScheduler().triggerEvent(m_eventTriggerId, this);
} // end while we get frame from data
return 1;
}
// split packet in frames
std::list< std::pair<unsigned char*,size_t> > ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) {
std::list< std::pair<unsigned char*,size_t> > frameList;
if ( frame != nullptr ) {
frameList.push_back(std::pair<unsigned char*,size_t>(frame, frameSize));
}
return frameList;
}
// extract a frame
unsigned char* ZoneMinderDeviceSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) {
outsize = size;
size = 0;
return frame;
}

View File

@ -0,0 +1,78 @@
/* ---------------------------------------------------------------------------
**
** DeviceSource.h
**
** live555 source
**
** -------------------------------------------------------------------------*/
#ifndef DEVICE_SOURCE
#define DEVICE_SOURCE
#include <string>
#include <list>
#include <iostream>
#include <liveMedia.hh>
#include "zm_monitor.h"
#include "zm_rtsp_server_frame.h"
#include "zm_packetqueue.h"
#include <linux/types.h>
class ZoneMinderDeviceSource: public FramedSource {
public:
static ZoneMinderDeviceSource* createNew(
UsageEnvironment& env,
Monitor* monitor,
AVStream * stream,
unsigned int queueSize
) {
return new ZoneMinderDeviceSource(env, monitor, stream, queueSize);
};
std::string getAuxLine() { return m_auxLine; };
int getWidth() { return m_monitor->Width(); };
int getHeight() { return m_monitor->Height(); };
protected:
ZoneMinderDeviceSource(UsageEnvironment& env, Monitor* monitor, AVStream * stream, unsigned int queueSize);
virtual ~ZoneMinderDeviceSource();
protected:
static void* threadStub(void* clientData) { return ((ZoneMinderDeviceSource*) clientData)->thread();};
void* thread();
static void deliverFrameStub(void* clientData) {((ZoneMinderDeviceSource*) clientData)->deliverFrame();};
void deliverFrame();
static void incomingPacketHandlerStub(void* clientData, int mask) { ((ZoneMinderDeviceSource*) clientData)->incomingPacketHandler(); };
void incomingPacketHandler();
int getNextFrame();
void processFrame(char * frame, int frameSize, const timeval &ref);
void queueFrame(char * frame, int frameSize, const timeval &tv);
// split packet in frames
virtual std::list< std::pair<unsigned char*, size_t> > splitFrames(unsigned char* frame, unsigned frameSize);
// overide FramedSource
virtual void doGetNextFrame();
virtual void doStopGettingFrames();
virtual unsigned char *extractFrame(unsigned char *data, size_t& size, size_t& outsize);
protected:
std::list<NAL_Frame*> m_captureQueue;
EventTriggerId m_eventTriggerId;
AVStream *m_stream;
Monitor* m_monitor;
zm_packetqueue *m_packetqueue;
std::list<ZMPacket *>::iterator *m_packetqueue_it;
unsigned int m_queueSize;
pthread_t m_thid;
pthread_mutex_t m_mutex;
std::string m_auxLine;
int stop;
};
#endif

View File

@ -0,0 +1,58 @@
#pragma once
#include "zm_logger.h"
#include <sys/time.h>
// ---------------------------------
// Captured frame
// ---------------------------------
const char H264marker[] = {0,0,0,1};
const char H264shortmarker[] = {0,0,1};
class NAL_Frame {
public:
NAL_Frame(unsigned char * buffer, size_t size, timeval timestamp) :
m_buffer(nullptr),
m_size(size),
m_timestamp(timestamp),
m_ref_count(1) {
m_buffer = new unsigned char[m_size];
memcpy(m_buffer, buffer, m_size);
};
NAL_Frame(unsigned char* buffer, size_t size) : m_buffer(buffer), m_size(size) {
gettimeofday(&m_timestamp, NULL);
};
NAL_Frame& operator=(const NAL_Frame&);
~NAL_Frame() {
delete[] m_buffer;
m_buffer = nullptr;
};
unsigned char *buffer() const { return m_buffer; };
// The buffer has a 32bit nal size value at the front, so if we want the nal, it's
// the address of the buffer plus 4 bytes.
unsigned char *nal() const { return m_buffer+4; };
size_t size() const { return m_size; };
size_t nal_size() const { return m_size-4; };
bool check() const {
// Look for marker at beginning
unsigned char *marker = (unsigned char*)memmem(m_buffer, sizeof(H264marker), H264marker, sizeof(H264marker));
if ( marker ) {
Debug(1, "marker found at beginning");
return true;
} else {
marker = (unsigned char*)memmem(m_buffer, m_size, H264marker, sizeof(H264marker));
if ( marker ) {
Debug(1, "marker not found at beginning");
return false;
}
}
return false;
}
private:
unsigned char* m_buffer;
size_t m_size;
public:
timeval m_timestamp;
private:
int m_ref_count;
};

View File

@ -0,0 +1,224 @@
/* ---------------------------------------------------------------------------
**
** H264_DeviceSource.cpp
**
** H264 Live555 source
**
** -------------------------------------------------------------------------*/
#include <sstream>
#include <iomanip>
// live555
#include <Base64.hh>
#include "zm_rtsp_server_h264_device_source.h"
// ---------------------------------
// H264 ZoneMinder FramedSource
// ---------------------------------
//
H264_ZoneMinderDeviceSource::H264_ZoneMinderDeviceSource(
UsageEnvironment& env,
Monitor *monitor,
AVStream *stream,
unsigned int queueSize,
bool repeatConfig,
bool keepMarker)
: H26X_ZoneMinderDeviceSource(env, monitor, stream, queueSize, repeatConfig, keepMarker)
{
// extradata appears to simply be the SPS and PPS NAL's
this->splitFrames(m_stream->codecpar->extradata, m_stream->codecpar->extradata_size);
}
// split packet into frames
std::list< std::pair<unsigned char*, size_t> > H264_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) {
std::list< std::pair<unsigned char*, size_t> > frameList;
size_t bufSize = frameSize;
size_t size = 0;
unsigned char* buffer = this->extractFrame(frame, bufSize, size);
while ( buffer != nullptr ) {
switch ( m_frameType & 0x1F ) {
case 7:
Debug(1, "SPS_Size: %d bufSize %d", size, bufSize);
m_sps.assign((char*)buffer, size);
break;
case 8:
Debug(1, "PPS_Size: %d bufSize %d", size, bufSize);
m_pps.assign((char*)buffer,size);
break;
case 5:
Debug(1, "IDR_Size: %d bufSize %d", size, bufSize);
if ( m_repeatConfig && !m_sps.empty() && !m_pps.empty() ) {
frameList.push_back(std::pair<unsigned char*,size_t>((unsigned char*)m_sps.c_str(), m_sps.size()));
frameList.push_back(std::pair<unsigned char*,size_t>((unsigned char*)m_pps.c_str(), m_pps.size()));
}
break;
default:
Debug(1, "Unknown frametype!? %d %d", m_frameType, m_frameType & 0x1F);
break;
}
if ( !m_sps.empty() && !m_pps.empty() ) {
u_int32_t profile_level_id = 0;
if ( m_sps.size() >= 4 ) profile_level_id = (m_sps[1]<<16)|(m_sps[2]<<8)|m_sps[3];
char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size());
char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size());
std::ostringstream os;
os << "profile-level-id=" << std::hex << std::setw(6) << std::setfill('0') << profile_level_id;
os << ";sprop-parameter-sets=" << sps_base64 << "," << pps_base64;
m_auxLine.assign(os.str());
Debug(1, "auxLine: %s", m_auxLine.c_str());
delete [] sps_base64;
delete [] pps_base64;
}
frameList.push_back(std::pair<unsigned char*,size_t>(buffer, size));
buffer = this->extractFrame(&buffer[size], bufSize, size);
} // end while buffer
return frameList;
}
H265_ZoneMinderDeviceSource::H265_ZoneMinderDeviceSource(
UsageEnvironment& env,
Monitor *monitor,
AVStream *stream,
unsigned int queueSize,
bool repeatConfig,
bool keepMarker)
: H26X_ZoneMinderDeviceSource(env, monitor, stream, queueSize, repeatConfig, keepMarker)
{
// extradata appears to simply be the SPS and PPS NAL's
this->splitFrames(m_stream->codecpar->extradata, m_stream->codecpar->extradata_size);
}
// split packet in frames
std::list< std::pair<unsigned char*,size_t> >
H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) {
std::list< std::pair<unsigned char*,size_t> > frameList;
size_t bufSize = frameSize;
size_t size = 0;
unsigned char* buffer = this->extractFrame(frame, bufSize, size);
while ( buffer != nullptr ) {
switch ((m_frameType&0x7E)>>1) {
case 32:
Debug(1, "VPS_Size: %d bufSize %d", size, bufSize);
m_vps.assign((char*)buffer,size);
break;
case 33:
Debug(1, "SPS_Size: %d bufSize %d", size, bufSize);
m_sps.assign((char*)buffer,size);
break;
case 34:
Debug(1, "PPS_Size: %d bufSize %d", size, bufSize);
m_pps.assign((char*)buffer,size);
break;
case 19:
case 20:
Debug(1, "IDR_Size: %d bufSize %d", size, bufSize);
if ( m_repeatConfig && !m_vps.empty() && !m_sps.empty() && !m_pps.empty() ) {
frameList.push_back(std::pair<unsigned char*,size_t>((unsigned char*)m_vps.c_str(), m_vps.size()));
frameList.push_back(std::pair<unsigned char*,size_t>((unsigned char*)m_sps.c_str(), m_sps.size()));
frameList.push_back(std::pair<unsigned char*,size_t>((unsigned char*)m_pps.c_str(), m_pps.size()));
}
break;
default:
Debug(1, "Unknown frametype!? %d %d", m_frameType, ((m_frameType & 0x7E) >> 1));
break;
}
if ( !m_vps.empty() && !m_sps.empty() && !m_pps.empty() ) {
char* vps_base64 = base64Encode(m_vps.c_str(), m_vps.size());
char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size());
char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size());
std::ostringstream os;
os << "sprop-vps=" << vps_base64;
os << ";sprop-sps=" << sps_base64;
os << ";sprop-pps=" << pps_base64;
m_auxLine.assign(os.str());
Debug(1, "Assigned auxLine to %s", m_auxLine.c_str());
delete [] vps_base64;
delete [] sps_base64;
delete [] pps_base64;
}
frameList.push_back(std::pair<unsigned char*,size_t>(buffer, size));
buffer = this->extractFrame(&buffer[size], bufSize, size);
} // end while buffer
if ( bufSize ) {
Debug(1, "%d bytes remaining", bufSize);
}
return frameList;
} // end H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize)
unsigned char * H26X_ZoneMinderDeviceSource::findMarker(
unsigned char *frame, size_t size, size_t &length
) {
//Debug(1, "findMarker %p %d", frame, size);
unsigned char *start = nullptr;
for ( size_t i = 0; i < size-2; i += 1 ) {
//Debug(1, "%d: %d %d %d", i, frame[i], frame[i+1], frame[i+2]);
if ( (frame[i] == 0) and (frame[i+1]) == 0 and (frame[i+2] == 1) ) {
if ( i and (frame[i-1] == 0) ) {
start = frame + i - 1;
length = sizeof(H264marker);
} else {
start = frame + i;
length = sizeof(H264shortmarker);
}
break;
}
}
return start;
}
// extract a frame
unsigned char* H26X_ZoneMinderDeviceSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) {
unsigned char *outFrame = nullptr;
Debug(1, "ExtractFrame: %p %d", frame, size);
outsize = 0;
size_t markerLength = 0;
size_t endMarkerLength = 0;
m_frameType = 0;
unsigned char *startFrame = nullptr;
if ( size >= 3 )
startFrame = this->findMarker(frame, size, markerLength);
if ( startFrame != nullptr ) {
Debug(1, "startFrame: %p marker Length %d", startFrame, markerLength);
m_frameType = startFrame[markerLength];
int remainingSize = size-(startFrame-frame+markerLength);
unsigned char *endFrame = nullptr;
if ( remainingSize > 3 ) {
endFrame = this->findMarker(startFrame+markerLength, remainingSize, endMarkerLength);
}
Debug(1, "endFrame: %p marker Length %d, remaining size %d", endFrame, endMarkerLength, remainingSize);
if ( m_keepMarker ) {
size -= startFrame-frame;
outFrame = startFrame;
} else {
size -= startFrame-frame+markerLength;
outFrame = &startFrame[markerLength];
}
if ( endFrame != nullptr ) {
outsize = endFrame - outFrame;
} else {
outsize = size;
}
size -= outsize;
Debug(1, "Have frame type: %d size %d, keepmarker %d", m_frameType, outsize, m_keepMarker);
} else if ( size >= sizeof(H264shortmarker) ) {
Info("No marker found");
}
return outFrame;
}

View File

@ -0,0 +1,103 @@
/* ---------------------------------------------------------------------------
** This software is in the public domain, furnished "as is", without technical
** support, and with no warranty, express or implied, as to its usefulness for
** any purpose.
**
** H264_ZoneMinderDeviceSource.h
**
** H264 ZoneMinder live555 source
**
** -------------------------------------------------------------------------*/
#ifndef H264_ZoneMinder_DEVICE_SOURCE
#define H264_ZoneMinder_DEVICE_SOURCE
#include "zm_rtsp_server_device_source.h"
#include "zm_rtsp_server_frame.h"
// ---------------------------------
// H264 ZoneMinder FramedSource
// ---------------------------------
class H26X_ZoneMinderDeviceSource : public ZoneMinderDeviceSource {
protected:
H26X_ZoneMinderDeviceSource(
UsageEnvironment& env,
Monitor *monitor,
AVStream *stream,
unsigned int queueSize,
bool repeatConfig,
bool keepMarker)
:
ZoneMinderDeviceSource(env, monitor, stream, queueSize),
m_repeatConfig(repeatConfig),
m_keepMarker(keepMarker),
m_frameType(0) { }
virtual ~H26X_ZoneMinderDeviceSource() {}
virtual unsigned char* extractFrame(unsigned char* frame, size_t& size, size_t& outsize);
virtual unsigned char* findMarker(unsigned char *frame, size_t size, size_t &length);
protected:
std::string m_sps;
std::string m_pps;
bool m_repeatConfig;
bool m_keepMarker;
int m_frameType;
};
class H264_ZoneMinderDeviceSource : public H26X_ZoneMinderDeviceSource {
public:
static H264_ZoneMinderDeviceSource* createNew(
UsageEnvironment& env,
Monitor *monitor,
AVStream *stream,
unsigned int queueSize,
bool repeatConfig,
bool keepMarker) {
return new H264_ZoneMinderDeviceSource(env, monitor, stream, queueSize, repeatConfig, keepMarker);
}
protected:
H264_ZoneMinderDeviceSource(
UsageEnvironment& env,
Monitor *monitor,
AVStream *stream,
unsigned int queueSize,
bool repeatConfig,
bool keepMarker);
// overide ZoneMinderDeviceSource
virtual std::list< std::pair<unsigned char*,size_t> > splitFrames(unsigned char* frame, unsigned frameSize);
};
class H265_ZoneMinderDeviceSource : public H26X_ZoneMinderDeviceSource {
public:
static H265_ZoneMinderDeviceSource* createNew(
UsageEnvironment& env,
Monitor *monitor,
AVStream *stream,
unsigned int queueSize,
bool repeatConfig,
bool keepMarker) {
return new H265_ZoneMinderDeviceSource(env, monitor, stream, queueSize, repeatConfig, keepMarker);
}
protected:
H265_ZoneMinderDeviceSource(
UsageEnvironment& env,
Monitor *monitor,
AVStream *stream,
unsigned int queueSize,
bool repeatConfig,
bool keepMarker);
// overide ZoneMinderDeviceSource
virtual std::list< std::pair<unsigned char*,size_t> > splitFrames(unsigned char* frame, unsigned frameSize);
protected:
std::string m_vps;
};
#endif

View File

@ -0,0 +1,106 @@
/* ---------------------------------------------------------------------------
**
** ServerMediaSubsession.cpp
**
** -------------------------------------------------------------------------*/
#include <sstream>
#include "zm_rtsp_server_server_media_subsession.h"
#include "zm_rtsp_server_device_source.h"
#include "zm_rtsp_server_adts_source.h"
// ---------------------------------
// BaseServerMediaSubsession
// ---------------------------------
FramedSource* BaseServerMediaSubsession::createSource(
UsageEnvironment& env, FramedSource* inputSource, const std::string& format)
{
FramedSource* source = nullptr;
if ( format == "video/MP2T" ) {
source = MPEG2TransportStreamFramer::createNew(env, inputSource);
} else if ( format == "video/H264" ) {
source = H264VideoStreamDiscreteFramer::createNew(env, inputSource);
}
#if LIVEMEDIA_LIBRARY_VERSION_INT > 1414454400
else if ( format == "video/H265" ) {
source = H265VideoStreamDiscreteFramer::createNew(env, inputSource);
}
#endif
#if 0
else if (format == "video/JPEG") {
source = MJPEGVideoSource::createNew(env, inputSource);
}
#endif
else {
source = inputSource;
}
return source;
}
/* source is generally a replica */
RTPSink* BaseServerMediaSubsession::createSink(
UsageEnvironment& env,
Groupsock* rtpGroupsock,
unsigned char rtpPayloadTypeIfDynamic,
const std::string& format,
FramedSource *source
) {
RTPSink* sink = nullptr;
if ( format == "video/MP2T" ) {
sink = SimpleRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic, 90000, "video", "MP2T", 1, true, false);
} else if ( format == "video/H264" ) {
sink = H264VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic);
} else if ( format == "video/VP8" ) {
sink = VP8VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic);
}
#if LIVEMEDIA_LIBRARY_VERSION_INT > 1414454400
else if ( format == "video/VP9" ) {
sink = VP9VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic);
} else if ( format == "video/H265" ) {
sink = H265VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic);
#endif
} else if ( format == "audio/AAC" ) {
ADTS_ZoneMinderDeviceSource *adts_source = (ADTS_ZoneMinderDeviceSource *)(m_replicator->inputSource());
sink = MPEG4GenericRTPSink::createNew(env, rtpGroupsock,
rtpPayloadTypeIfDynamic,
adts_source->samplingFrequency(),
"audio", "AAC-hbr",
adts_source->configStr(),
adts_source->numChannels()
);
} else {
Error("unknown format");
}
#if 0
else if (format == "video/JPEG") {
sink = JPEGVideoRTPSink::createNew (env, rtpGroupsock);
}
#endif
return sink;
}
char const* BaseServerMediaSubsession::getAuxLine(
ZoneMinderDeviceSource* source,
unsigned char rtpPayloadType
) {
const char* auxLine = nullptr;
if ( source ) {
std::ostringstream os;
os << "a=fmtp:" << int(rtpPayloadType) << " ";
os << source->getAuxLine();
os << "\r\n";
int width = source->getWidth();
int height = source->getHeight();
if ( (width > 0) && (height>0) ) {
os << "a=x-dimensions:" << width << "," << height << "\r\n";
}
auxLine = strdup(os.str().c_str());
Debug(1, "auxLine: %s", auxLine);
} else {
Error("No source auxLine: ");
}
return auxLine;
}

View File

@ -0,0 +1,46 @@
/* ---------------------------------------------------------------------------
** This software is in the public domain, furnished "as is", without technical
** support, and with no warranty, express or implied, as to its usefulness for
** any purpose.
**
** ServerMediaSubsession.h
**
** -------------------------------------------------------------------------*/
#pragma once
#include <sys/stat.h>
#include <string>
#include <iomanip>
#include <iostream>
#include <fstream>
#include <liveMedia.hh>
class ZoneMinderDeviceSource;
class BaseServerMediaSubsession {
public:
BaseServerMediaSubsession(StreamReplicator* replicator):
m_replicator(replicator) {};
FramedSource* createSource(
UsageEnvironment& env,
FramedSource * videoES,
const std::string& format);
RTPSink * createSink(
UsageEnvironment& env,
Groupsock * rtpGroupsock,
unsigned char rtpPayloadTypeIfDynamic,
const std::string& format,
FramedSource *source);
char const* getAuxLine(
ZoneMinderDeviceSource* source,
unsigned char rtpPayloadType);
protected:
StreamReplicator* m_replicator;
};

View File

@ -0,0 +1,157 @@
#include "zm_rtsp_server_thread.h"
#include "zm_rtsp_server_device_source.h"
#include "zm_rtsp_server_h264_device_source.h"
#include "zm_rtsp_server_adts_source.h"
#include "zm_rtsp_server_unicast_server_media_subsession.h"
#include <StreamReplicator.hh>
#include "zm.h"
#if HAVE_RTSP_SERVER
RTSPServerThread::RTSPServerThread(Monitor *p_monitor) :
monitor(p_monitor),
terminate(0)
{
//unsigned short rtsp_over_http_port = 0;
//const char *realm = "ZoneMinder";
//unsigned int timeout = 65;
OutPacketBuffer::maxSize = 2000000;
scheduler = BasicTaskScheduler::createNew();
env = BasicUsageEnvironment::createNew(*scheduler);
authDB = nullptr;
//authDB = new UserAuthenticationDatabase("ZoneMinder");
//authDB->addUserRecord("username1", "password1"); // replace these with real strings
portNumBits rtspServerPortNum = config.min_rtsp_port + monitor->Id();
rtspServer = RTSPServer::createNew(*env, rtspServerPortNum, authDB);
if ( rtspServer == nullptr ) {
Fatal("Failed to create rtspServer at port %d", rtspServerPortNum);
return;
}
const char *prefix = rtspServer->rtspURLPrefix();
Debug(1, "RTSP prefix is %s", prefix);
delete[] prefix;
} // end RTSPServerThread::RTSPServerThread
RTSPServerThread::~RTSPServerThread() {
if ( rtspServer ) {
Medium::close(rtspServer);
} // end if rtsp_server
while ( sources.size() ) {
FramedSource *source = sources.front();
sources.pop_front();
Medium::close(source);
}
env->reclaim();
delete scheduler;
}
int RTSPServerThread::run() {
Debug(1, "RTSPServerThread::run()");
if ( rtspServer )
env->taskScheduler().doEventLoop(&terminate); // does not return
Debug(1, "RTSPServerThread::done()");
return 0;
} // end in RTSPServerThread::run()
void RTSPServerThread::stop() {
Debug(1, "RTSPServerThread::stop()");
terminate = 1;
for ( std::list<FramedSource *>::iterator it = sources.begin(); it != sources.end(); ++it ) {
(*it)->stopGettingFrames();
}
} // end RTSPServerThread::stop()
bool RTSPServerThread::stopped() const {
return terminate ? true : false;
} // end RTSPServerThread::stopped()
void RTSPServerThread::addStream(AVStream *stream, AVStream *audio_stream) {
if ( !rtspServer )
return;
AVCodecID codec_id = stream->codecpar->codec_id;
std::string rtpFormat = getRtpFormat(codec_id, false);
Debug(1, "RTSP: format %s", rtpFormat.c_str());
if ( rtpFormat.empty() ) {
Error("No streaming format");
return;
}
int queueSize = 30;
bool repeatConfig = true;
bool muxTS = false;
ServerMediaSession *sms = nullptr;
if ( stream ) {
StreamReplicator* videoReplicator = nullptr;
FramedSource *source = nullptr;
if ( rtpFormat == "video/H264" ) {
source = H264_ZoneMinderDeviceSource::createNew(*env, monitor, stream, queueSize, repeatConfig, muxTS);
} else if ( rtpFormat == "video/H265" ) {
source = H265_ZoneMinderDeviceSource::createNew(*env, monitor, stream, queueSize, repeatConfig, muxTS);
}
if ( source == nullptr ) {
Error("Unable to create source");
} else {
videoReplicator = StreamReplicator::createNew(*env, source, false);
}
sources.push_back(source);
// Create Unicast Session
if ( videoReplicator ) {
if ( !sms )
sms = ServerMediaSession::createNew(*env, "streamname");
sms->addSubsession(UnicastServerMediaSubsession::createNew(*env, videoReplicator, rtpFormat));
}
}
if ( audio_stream ) {
StreamReplicator* replicator = nullptr;
FramedSource *source = nullptr;
rtpFormat = getRtpFormat(audio_stream->codecpar->codec_id, false);
if ( rtpFormat == "audio/AAC" ) {
source = ADTS_ZoneMinderDeviceSource::createNew(*env, monitor, audio_stream, queueSize);
Debug(1, "ADTS source %p", source);
}
if ( source ) {
replicator = StreamReplicator::createNew(*env, source, false /* deleteWhenLastReplicaDies */);
sources.push_back(source);
}
if ( replicator ) {
if ( !sms )
sms = ServerMediaSession::createNew(*env, "streamname");
sms->addSubsession(UnicastServerMediaSubsession::createNew(*env, replicator, rtpFormat));
}
} else {
Debug(1, "Not Adding auto stream");
}
rtspServer->addServerMediaSession(sms);
char *url = rtspServer->rtspURL(sms);
Debug(1, "url is %s", url);
delete[] url;
} // end void addStream
// -----------------------------------------
// convert V4L2 pix format to RTP mime
// -----------------------------------------
const std::string RTSPServerThread::getRtpFormat(AVCodecID codec_id, bool muxTS) {
if ( muxTS ) {
return "video/MP2T";
} else {
switch ( codec_id ) {
case AV_CODEC_ID_H265 : return "video/H265";
case AV_CODEC_ID_H264 : return "video/H264";
//case PIX_FMT_MJPEG: rtpFormat = "video/JPEG"; break;
//case PIX_FMT_JPEG : rtpFormat = "video/JPEG"; break;
//case AV_PIX_FMT_VP8 : rtpFormat = "video/VP8" ; break;
//case AV_PIX_FMT_VP9 : rtpFormat = "video/VP9" ; break;
case AV_CODEC_ID_AAC : return "audio/AAC";
default: break;
}
}
return "";
}
#endif

View File

@ -0,0 +1,45 @@
#include "zm.h"
#if HAVE_RTSP_SERVER
#ifndef ZM_RTSP_SERVER_THREAD_H
#define ZM_RTSP_SERVER_THREAD_H
#include "zm_thread.h"
#include <signal.h>
#include "zm_monitor.h"
#include <BasicUsageEnvironment.hh>
#include <RTSPServer.hh>
#include <libavcodec/codec_id.h>
#include <libavformat/avformat.h>
class RTSPServerThread : public Thread {
private:
Monitor *monitor;
char terminate;
TaskScheduler* scheduler;
UsageEnvironment* env;
UserAuthenticationDatabase* authDB;
RTSPServer* rtspServer;
std::list<FramedSource *> sources;
public:
explicit RTSPServerThread(Monitor *);
~RTSPServerThread();
void addStream(AVStream *, AVStream *);
int run();
void stop();
bool stopped() const;
private:
const std::string getRtpFormat(AVCodecID codec, bool muxTS);
int addSession(
const std::string & sessionName,
const std::list<ServerMediaSubsession*> & subSession
);
};
#endif
#endif

View File

@ -0,0 +1,47 @@
/* ---------------------------------------------------------------------------
** This software is in the public domain, furnished "as is", without technical
** support, and with no warranty, express or implied, as to its usefulness for
** any purpose.
**
** ServerMediaSubsession.cpp
**
** -------------------------------------------------------------------------*/
#include "zm_rtsp_server_unicast_server_media_subsession.h"
#include "zm_rtsp_server_device_source.h"
// -----------------------------------------
// ServerMediaSubsession for Unicast
// -----------------------------------------
UnicastServerMediaSubsession* UnicastServerMediaSubsession::createNew(
UsageEnvironment& env,
StreamReplicator* replicator,
//FramedSource *source,
const std::string& format
) {
return new UnicastServerMediaSubsession(env, replicator, format);
//return new UnicastServerMediaSubsession(env, replicator, source, format);
}
FramedSource* UnicastServerMediaSubsession::createNewStreamSource(
unsigned clientSessionId,
unsigned& estBitrate
) {
FramedSource* replica = m_replicator->createStreamReplica();
return createSource(envir(), replica, m_format);
}
RTPSink* UnicastServerMediaSubsession::createNewRTPSink(
Groupsock* rtpGroupsock,
unsigned char rtpPayloadTypeIfDynamic,
FramedSource* inputSource
) {
return createSink(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, m_format, inputSource);
}
char const* UnicastServerMediaSubsession::getAuxSDPLine(
RTPSink* rtpSink, FramedSource* inputSource
) {
return this->getAuxLine(dynamic_cast<ZoneMinderDeviceSource*>(m_replicator->inputSource()), rtpSink->rtpPayloadType());
}

View File

@ -0,0 +1,45 @@
/* ---------------------------------------------------------------------------
** This software is in the public domain, furnished "as is", without technical
** support, and with no warranty, express or implied, as to its usefulness for
** any purpose.
**
** ServerMediaSubsession.h
**
** -------------------------------------------------------------------------*/
#pragma once
#include "zm_rtsp_server_server_media_subsession.h"
// -----------------------------------------
// ServerMediaSubsession for Unicast
// -----------------------------------------
class UnicastServerMediaSubsession :
public OnDemandServerMediaSubsession,
public BaseServerMediaSubsession
{
public:
static UnicastServerMediaSubsession* createNew(
UsageEnvironment& env,
StreamReplicator* replicator,
const std::string& format);
protected:
UnicastServerMediaSubsession(
UsageEnvironment& env,
StreamReplicator* replicator,
const std::string& format)
:
OnDemandServerMediaSubsession(env, true
/* Boolean reuseFirstSource, portNumBits initialPortNum=6970, Boolean multiplexRTCPWithRTP=False */
),
BaseServerMediaSubsession(replicator),
m_format(format) {};
virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate);
virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
virtual char const* getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource);
protected:
const std::string m_format;
};

View File

@ -350,7 +350,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const {
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
AVCodecContext *codec_context = avcodec_alloc_context3(nullptr); AVCodecContext *codec_context = avcodec_alloc_context3(nullptr);
avcodec_parameters_to_context(codec_context, stream->codecpar); //avcodec_parameters_to_context(codec_context, stream->codecpar);
stream->codec = codec_context; stream->codec = codec_context;
#else #else
AVCodecContext *codec_context = stream->codec; AVCodecContext *codec_context = stream->codec;
@ -376,9 +376,6 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const {
#endif #endif
else else
Warning("Unknown media_type %s", type.c_str()); Warning("Unknown media_type %s", type.c_str());
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
stream->codecpar->codec_type = codec_context->codec_type;
#endif
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) #if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
std::string codec_name; std::string codec_name;
@ -417,6 +414,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const {
} }
} /// end if static or dynamic } /// end if static or dynamic
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) #if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
if ( codec_name.empty() ) if ( codec_name.empty() )
#else #else
@ -425,7 +423,6 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const {
{ {
Warning( "Can't find payload details for %s payload type %d, name %s", Warning( "Can't find payload details for %s payload type %d, name %s",
mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() ); mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() );
//return( 0 );
} }
if ( mediaDesc->getWidth() ) if ( mediaDesc->getWidth() )
codec_context->width = mediaDesc->getWidth(); codec_context->width = mediaDesc->getWidth();
@ -439,7 +436,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const {
strcpy(pvalue, mediaDesc->getSprops().c_str()); strcpy(pvalue, mediaDesc->getSprops().c_str());
while (*value) { while ( *value ) {
char base64packet[1024]; char base64packet[1024];
uint8_t decoded_packet[1024]; uint8_t decoded_packet[1024];
uint32_t packet_size; uint32_t packet_size;
@ -454,9 +451,9 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const {
if ( *value == ',' ) if ( *value == ',' )
value++; value++;
packet_size= av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet)); packet_size = av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet));
Hexdump(4, (char *)decoded_packet, packet_size); Hexdump(4, (char *)decoded_packet, packet_size);
if (packet_size) { if ( packet_size ) {
uint8_t *dest = uint8_t *dest =
(uint8_t *)av_malloc(packet_size + sizeof(start_sequence) + (uint8_t *)av_malloc(packet_size + sizeof(start_sequence) +
codec_context->extradata_size + codec_context->extradata_size +
@ -493,7 +490,10 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const {
} }
} }
} }
} #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
avcodec_parameters_from_context(stream->codecpar, codec_context);
#endif
} // end foreach mediaList
return formatContext; return formatContext;
} }

View File

@ -33,7 +33,13 @@ class Monitor;
class StreamBase { class StreamBase {
public: public:
typedef enum { STREAM_JPEG, STREAM_RAW, STREAM_ZIP, STREAM_SINGLE, STREAM_MPEG } StreamType; typedef enum {
STREAM_JPEG,
STREAM_RAW,
STREAM_ZIP,
STREAM_SINGLE,
STREAM_MPEG
} StreamType;
protected: protected:
static const int MAX_STREAM_DELAY = 5; // Seconds static const int MAX_STREAM_DELAY = 5; // Seconds
@ -57,8 +63,33 @@ protected:
char msg_data[256]; char msg_data[256];
} DataMsg; } DataMsg;
typedef enum { MSG_CMD=1, MSG_DATA_WATCH, MSG_DATA_EVENT } MsgType; typedef enum {
typedef enum { CMD_NONE=0, CMD_PAUSE, CMD_PLAY, CMD_STOP, CMD_FASTFWD, CMD_SLOWFWD, CMD_SLOWREV, CMD_FASTREV, CMD_ZOOMIN, CMD_ZOOMOUT, CMD_PAN, CMD_SCALE, CMD_PREV, CMD_NEXT, CMD_SEEK, CMD_VARPLAY, CMD_GET_IMAGE, CMD_QUIT, CMD_QUERY=99 } MsgCommand; MSG_CMD=1,
MSG_DATA_WATCH,
MSG_DATA_EVENT
} MsgType;
typedef enum {
CMD_NONE=0,
CMD_PAUSE,
CMD_PLAY,
CMD_STOP,
CMD_FASTFWD,
CMD_SLOWFWD,
CMD_SLOWREV,
CMD_FASTREV,
CMD_ZOOMIN,
CMD_ZOOMOUT,
CMD_PAN,
CMD_SCALE,
CMD_PREV,
CMD_NEXT,
CMD_SEEK,
CMD_VARPLAY,
CMD_GET_IMAGE,
CMD_QUIT,
CMD_QUERY=99
} MsgCommand;
protected: protected:
int monitor_id; int monitor_id;
@ -109,7 +140,6 @@ protected:
bool checkInitialised(); bool checkInitialised();
void updateFrameRate(double fps); void updateFrameRate(double fps);
Image *prepareImage(Image *image); Image *prepareImage(Image *image);
bool sendTextFrame(const char *text);
bool checkCommandQueue(); bool checkCommandQueue();
virtual void processCommand(const CmdMsg *msg)=0; virtual void processCommand(const CmdMsg *msg)=0;
@ -137,7 +167,7 @@ public:
lock_fd(0), lock_fd(0),
paused(false), paused(false),
step(0) step(0)
{ {
memset(&loc_sock_path, 0, sizeof(loc_sock_path)); memset(&loc_sock_path, 0, sizeof(loc_sock_path));
memset(&loc_addr, 0, sizeof(loc_addr)); memset(&loc_addr, 0, sizeof(loc_addr));
memset(&rem_sock_path, 0, sizeof(rem_sock_path)); memset(&rem_sock_path, 0, sizeof(rem_sock_path));
@ -188,6 +218,7 @@ public:
void setStreamQueue(int p_connkey) { void setStreamQueue(int p_connkey) {
connkey = p_connkey; connkey = p_connkey;
} }
bool sendTextFrame(const char *text);
virtual void openComms(); virtual void openComms();
virtual void closeComms(); virtual void closeComms();
virtual void runStream()=0; virtual void runStream()=0;

View File

@ -25,8 +25,7 @@
#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL #if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL
SWScale::SWScale() : gotdefaults(false), swscale_ctx(nullptr), input_avframe(nullptr), output_avframe(nullptr) { SWScale::SWScale() : gotdefaults(false), swscale_ctx(nullptr), input_avframe(nullptr), output_avframe(nullptr) {
Debug(4,"SWScale object created"); Debug(4, "SWScale object created");
} }
bool SWScale::init() { bool SWScale::init() {
@ -68,10 +67,14 @@ SWScale::~SWScale() {
swscale_ctx = nullptr; swscale_ctx = nullptr;
} }
Debug(4,"SWScale object destroyed"); Debug(4, "SWScale object destroyed");
} }
int SWScale::SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) { int SWScale::SetDefaults(
enum _AVPIXELFORMAT in_pf,
enum _AVPIXELFORMAT out_pf,
unsigned int width,
unsigned int height) {
/* Assign the defaults */ /* Assign the defaults */
default_input_pf = in_pf; default_input_pf = in_pf;
@ -84,6 +87,48 @@ int SWScale::SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf,
return 0; return 0;
} }
int SWScale::Convert(
AVFrame *in_frame,
AVFrame *out_frame
) {
// THe J formats are deprecated, so we need to convert
AVPixelFormat format;
switch ( in_frame->format ) {
case AV_PIX_FMT_YUVJ420P :
format = AV_PIX_FMT_YUV420P;
break;
case AV_PIX_FMT_YUVJ422P :
format = AV_PIX_FMT_YUV422P;
break;
case AV_PIX_FMT_YUVJ444P :
format = AV_PIX_FMT_YUV444P;
break;
case AV_PIX_FMT_YUVJ440P :
format = AV_PIX_FMT_YUV440P;
break;
default:
format = (AVPixelFormat)in_frame->format;
break;
}
/* Get the context */
swscale_ctx = sws_getCachedContext(swscale_ctx,
in_frame->width, in_frame->height, format,
out_frame->width, out_frame->height, (AVPixelFormat)out_frame->format,
SWS_FAST_BILINEAR, NULL, NULL, NULL);
if ( swscale_ctx == NULL ) {
Error("Failed getting swscale context");
return -6;
}
/* Do the conversion */
if ( !sws_scale(swscale_ctx, in_frame->data, in_frame->linesize, 0, in_frame->height, out_frame->data, out_frame->linesize ) ) {
Error("swscale conversion failed");
return -10;
}
return 0;
}
int SWScale::Convert( int SWScale::Convert(
const uint8_t* in_buffer, const uint8_t* in_buffer,
const size_t in_buffer_size, const size_t in_buffer_size,
@ -96,6 +141,8 @@ int SWScale::Convert(
unsigned int new_width, unsigned int new_width,
unsigned int new_height unsigned int new_height
) { ) {
Debug(1, "Convert: in_buffer %p in_buffer_size %d out_buffer %p size %d width %d height %d width %d height %d",
in_buffer, in_buffer_size, out_buffer, out_buffer_size, width, height, new_width, new_height);
/* Parameter checking */ /* Parameter checking */
if ( in_buffer == nullptr ) { if ( in_buffer == nullptr ) {
Error("NULL Input buffer"); Error("NULL Input buffer");
@ -109,11 +156,29 @@ int SWScale::Convert(
// Error("Invalid input or output pixel formats"); // Error("Invalid input or output pixel formats");
// return -2; // return -2;
// } // }
if (!width || !height || !new_height || !new_width) { if ( !width || !height || !new_height || !new_width ) {
Error("Invalid width or height"); Error("Invalid width or height");
return -3; return -3;
} }
// THe J formats are deprecated, so we need to convert
switch ( in_pf ) {
case AV_PIX_FMT_YUVJ420P :
in_pf = AV_PIX_FMT_YUV420P;
break;
case AV_PIX_FMT_YUVJ422P :
in_pf = AV_PIX_FMT_YUV422P;
break;
case AV_PIX_FMT_YUVJ444P :
in_pf = AV_PIX_FMT_YUV444P;
break;
case AV_PIX_FMT_YUVJ440P :
in_pf = AV_PIX_FMT_YUV440P;
break;
default:
break;
}
#if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0) #if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0)
/* Warn if the input or output pixelformat is not supported */ /* Warn if the input or output pixelformat is not supported */
if ( !sws_isSupportedInput(in_pf) ) { if ( !sws_isSupportedInput(in_pf) ) {
@ -128,7 +193,7 @@ int SWScale::Convert(
/* Check the buffer sizes */ /* Check the buffer sizes */
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
size_t insize = av_image_get_buffer_size(in_pf, width, height, 1); size_t insize = av_image_get_buffer_size(in_pf, width, height, 32);
#else #else
size_t insize = avpicture_get_size(in_pf, width, height); size_t insize = avpicture_get_size(in_pf, width, height);
#endif #endif
@ -137,7 +202,7 @@ int SWScale::Convert(
return -4; return -4;
} }
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
size_t outsize = av_image_get_buffer_size(out_pf, new_width, new_height, 1); size_t outsize = av_image_get_buffer_size(out_pf, new_width, new_height, 32);
#else #else
size_t outsize = avpicture_get_size(out_pf, new_width, new_height); size_t outsize = avpicture_get_size(out_pf, new_width, new_height);
#endif #endif
@ -148,7 +213,9 @@ int SWScale::Convert(
} }
/* Get the context */ /* Get the context */
swscale_ctx = sws_getCachedContext( swscale_ctx, width, height, in_pf, new_width, new_height, out_pf, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr ); swscale_ctx = sws_getCachedContext(
swscale_ctx, width, height, in_pf, new_width, new_height,
out_pf, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
if ( swscale_ctx == nullptr ) { if ( swscale_ctx == nullptr ) {
Error("Failed getting swscale context"); Error("Failed getting swscale context");
return -6; return -6;
@ -156,7 +223,7 @@ int SWScale::Convert(
/* Fill in the buffers */ /* Fill in the buffers */
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
if (av_image_fill_arrays(input_avframe->data, input_avframe->linesize, if ( av_image_fill_arrays(input_avframe->data, input_avframe->linesize,
(uint8_t*) in_buffer, in_pf, width, height, 1) <= 0) { (uint8_t*) in_buffer, in_pf, width, height, 1) <= 0) {
#else #else
if (avpicture_fill((AVPicture*) input_avframe, (uint8_t*) in_buffer, if (avpicture_fill((AVPicture*) input_avframe, (uint8_t*) in_buffer,
@ -166,10 +233,10 @@ int SWScale::Convert(
return -7; return -7;
} }
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
if (av_image_fill_arrays(output_avframe->data, output_avframe->linesize, if ( av_image_fill_arrays(output_avframe->data, output_avframe->linesize,
out_buffer, out_pf, new_width, new_height, 1) <= 0) { out_buffer, out_pf, new_width, new_height, 1) <= 0) {
#else #else
if (avpicture_fill((AVPicture*) output_avframe, out_buffer, out_pf, new_width, if ( avpicture_fill((AVPicture*) output_avframe, out_buffer, out_pf, new_width,
new_height) <= 0) { new_height) <= 0) {
#endif #endif
Error("Failed filling output frame with output buffer"); Error("Failed filling output frame with output buffer");
@ -177,7 +244,9 @@ int SWScale::Convert(
} }
/* Do the conversion */ /* Do the conversion */
if(!sws_scale(swscale_ctx, input_avframe->data, input_avframe->linesize, 0, height, output_avframe->data, output_avframe->linesize ) ) { if ( !sws_scale(swscale_ctx,
input_avframe->data, input_avframe->linesize,
0, height, output_avframe->data, output_avframe->linesize ) ) {
Error("swscale conversion failed"); Error("swscale conversion failed");
return -10; return -10;
} }
@ -185,27 +254,42 @@ int SWScale::Convert(
return 0; return 0;
} }
int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) { int SWScale::Convert(
const uint8_t* in_buffer,
const size_t in_buffer_size,
uint8_t* out_buffer,
const size_t out_buffer_size,
enum _AVPIXELFORMAT in_pf,
enum _AVPIXELFORMAT out_pf,
unsigned int width,
unsigned int height) {
return Convert(in_buffer, in_buffer_size, out_buffer, out_buffer_size, in_pf, out_pf, width, height, width, height); return Convert(in_buffer, in_buffer_size, out_buffer, out_buffer_size, in_pf, out_pf, width, height, width, height);
} }
int SWScale::Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) { int SWScale::Convert(
if(img->Width() != width) { const Image* img,
Error("Source image width differs. Source: %d Output: %d",img->Width(), width); uint8_t* out_buffer,
const size_t out_buffer_size,
enum _AVPIXELFORMAT in_pf,
enum _AVPIXELFORMAT out_pf,
unsigned int width,
unsigned int height) {
if ( img->Width() != width ) {
Error("Source image width differs. Source: %d Output: %d", img->Width(), width);
return -12; return -12;
} }
if(img->Height() != height) { if ( img->Height() != height ) {
Error("Source image height differs. Source: %d Output: %d",img->Height(), height); Error("Source image height differs. Source: %d Output: %d", img->Height(), height);
return -13; return -13;
} }
return Convert(img->Buffer(),img->Size(),out_buffer,out_buffer_size,in_pf,out_pf,width,height); return Convert(img->Buffer(), img->Size(), out_buffer, out_buffer_size, in_pf, out_pf, width, height);
} }
int SWScale::ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size) { int SWScale::ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size) {
if(!gotdefaults) { if ( !gotdefaults ) {
Error("Defaults are not set"); Error("Defaults are not set");
return -24; return -24;
} }
@ -215,7 +299,7 @@ int SWScale::ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t
int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size) { int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size) {
if(!gotdefaults) { if ( !gotdefaults ) {
Error("Defaults are not set"); Error("Defaults are not set");
return -24; return -24;
} }

View File

@ -14,6 +14,7 @@ class SWScale {
int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size); int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size);
int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size); int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size);
int Convert( AVFrame *in_frame, AVFrame *out_frame );
int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height, unsigned int new_width, unsigned int new_height); int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height, unsigned int new_width, unsigned int new_height);

View File

@ -66,33 +66,35 @@ int Mutex::trylock() {
} }
void Mutex::lock() { void Mutex::lock() {
if ( pthread_mutex_lock(&mMutex) < 0 ) if ( pthread_mutex_lock(&mMutex) < 0 )
throw ThreadException( stringtf( "Unable to lock pthread mutex: %s", strerror(errno) ) ); throw ThreadException(stringtf("Unable to lock pthread mutex: %s", strerror(errno)));
//Debug(3, "Lock");
} }
void Mutex::lock( int secs ) { void Mutex::lock( int secs ) {
struct timespec timeout = getTimeout( secs ); struct timespec timeout = getTimeout(secs);
if ( pthread_mutex_timedlock( &mMutex, &timeout ) < 0 ) if ( pthread_mutex_timedlock(&mMutex, &timeout) < 0 )
throw ThreadException( stringtf( "Unable to timedlock pthread mutex: %s", strerror(errno) ) ); throw ThreadException(stringtf("Unable to timedlock pthread mutex: %s", strerror(errno)));
} }
void Mutex::lock( double secs ) { void Mutex::lock( double secs ) {
struct timespec timeout = getTimeout( secs ); struct timespec timeout = getTimeout(secs);
if ( pthread_mutex_timedlock( &mMutex, &timeout ) < 0 ) if ( pthread_mutex_timedlock(&mMutex, &timeout) < 0 )
throw ThreadException( stringtf( "Unable to timedlock pthread mutex: %s", strerror(errno) ) ); throw ThreadException(stringtf("Unable to timedlock pthread mutex: %s", strerror(errno)));
} }
void Mutex::unlock() { void Mutex::unlock() {
if ( pthread_mutex_unlock( &mMutex ) < 0 ) if ( pthread_mutex_unlock(&mMutex) < 0 )
throw ThreadException( stringtf( "Unable to unlock pthread mutex: %s", strerror(errno) ) ); throw ThreadException(stringtf("Unable to unlock pthread mutex: %s", strerror(errno)));
//Debug(3, "unLock");
} }
bool Mutex::locked() { bool Mutex::locked() {
int state = pthread_mutex_trylock( &mMutex ); int state = pthread_mutex_trylock(&mMutex);
if ( state != 0 && state != EBUSY ) if ( (state != 0) && (state != EBUSY) )
throw ThreadException( stringtf( "Unable to trylock pthread mutex: %s", strerror(errno) ) ); throw ThreadException(stringtf("Unable to trylock pthread mutex: %s", strerror(errno)));
if ( state != EBUSY ) if ( state != EBUSY )
unlock(); unlock();
return( state == EBUSY ); return (state == EBUSY);
} }
RecursiveMutex::RecursiveMutex() { RecursiveMutex::RecursiveMutex() {
@ -105,8 +107,8 @@ RecursiveMutex::RecursiveMutex() {
} }
Condition::Condition( Mutex &mutex ) : mMutex( mutex ) { Condition::Condition( Mutex &mutex ) : mMutex( mutex ) {
if ( pthread_cond_init( &mCondition, nullptr ) < 0 ) if ( pthread_cond_init(&mCondition, nullptr) < 0 )
throw ThreadException( stringtf( "Unable to create pthread condition: %s", strerror(errno) ) ); throw ThreadException(stringtf("Unable to create pthread condition: %s", strerror(errno)));
} }
Condition::~Condition() { Condition::~Condition() {
@ -236,8 +238,10 @@ Thread::Thread() :
Thread::~Thread() { Thread::~Thread() {
Debug( 1, "Destroying thread %d", mPid ); Debug( 1, "Destroying thread %d", mPid );
if ( mStarted ) if ( mStarted ) {
Warning("You should really join the thread before destroying it");
join(); join();
}
} }
void *Thread::mThreadFunc( void *arg ) { void *Thread::mThreadFunc( void *arg ) {
@ -251,6 +255,7 @@ void *Thread::mThreadFunc( void *arg ) {
thisPtr->mThreadCondition.signal(); thisPtr->mThreadCondition.signal();
thisPtr->mThreadMutex.unlock(); thisPtr->mThreadMutex.unlock();
thisPtr->mRunning = true; thisPtr->mRunning = true;
Debug(2,"Runnning");
thisPtr->status = thisPtr->run(); thisPtr->status = thisPtr->run();
thisPtr->mRunning = false; thisPtr->mRunning = false;
Debug( 2, "Exiting thread, status %p", (void *)&(thisPtr->status) ); Debug( 2, "Exiting thread, status %p", (void *)&(thisPtr->status) );
@ -264,9 +269,9 @@ void *Thread::mThreadFunc( void *arg ) {
} }
void Thread::start() { void Thread::start() {
Debug( 1, "Starting thread" ); Debug(4, "Starting thread" );
if ( isThread() ) if ( isThread() )
throw ThreadException( "Can't self start thread" ); throw ThreadException("Can't self start thread");
mThreadMutex.lock(); mThreadMutex.lock();
if ( !mStarted ) { if ( !mStarted ) {
pthread_attr_t threadAttrs; pthread_attr_t threadAttrs;
@ -282,11 +287,11 @@ void Thread::start() {
} }
mThreadCondition.wait(); mThreadCondition.wait();
mThreadMutex.unlock(); mThreadMutex.unlock();
Debug( 1, "Started thread %d", mPid ); Debug(4, "Started thread %d", mPid);
} }
void Thread::join() { void Thread::join() {
Debug( 1, "Joining thread %d", mPid ); Debug(1, "Joining thread %d", mPid);
if ( isThread() ) if ( isThread() )
throw ThreadException( "Can't self join thread" ); throw ThreadException( "Can't self join thread" );
mThreadMutex.lock(); mThreadMutex.lock();

View File

@ -233,7 +233,7 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) {
} }
} }
Debug(1, "Attempting to authenticate user from auth string '%s'", auth); Debug(1, "Attempting to authenticate user from auth string '%s', remote addr(%s)", auth, remote_addr);
char sql[ZM_SQL_SML_BUFSIZ] = ""; char sql[ZM_SQL_SML_BUFSIZ] = "";
snprintf(sql, sizeof(sql), snprintf(sql, sizeof(sql),
"SELECT `Id`, `Username`, `Password`, `Enabled`," "SELECT `Id`, `Username`, `Password`, `Enabled`,"
@ -257,13 +257,14 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) {
return nullptr; return nullptr;
} }
// getting the time is expensive, so only do it once.
time_t now = time(nullptr); time_t now = time(nullptr);
unsigned int hours = config.auth_hash_ttl; unsigned int hours = config.auth_hash_ttl;
if ( ! hours ) { if ( !hours ) {
Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2.");
hours = 2; hours = 2;
} else { } else {
Debug(1, "AUTH_HASH_TTL is %d", hours); Debug(1, "AUTH_HASH_TTL is %d, time is %d", hours, now);
} }
char auth_key[512] = ""; char auth_key[512] = "";
char auth_md5[32+1] = ""; char auth_md5[32+1] = "";

View File

@ -96,6 +96,16 @@ bool startsWith(const std::string &haystack, const std::string &needle) {
return ( haystack.substr(0, needle.length()) == needle ); return ( haystack.substr(0, needle.length()) == needle );
} }
std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> elems;
std::stringstream ss(s);
std::string item;
while(std::getline(ss, item, delim)) {
elems.push_back(trimSpaces(item));
}
return elems;
}
StringVector split(const std::string &string, const std::string &chars, int limit) { StringVector split(const std::string &string, const std::string &chars, int limit) {
StringVector stringVector; StringVector stringVector;
std::string tempString = string; std::string tempString = string;

View File

@ -553,8 +553,8 @@ int ParseEncoderParameters(
} }
valueoffset = line.find('='); valueoffset = line.find('=');
if ( valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0 ) { if ( valueoffset == std::string::npos || (valueoffset+1 >= line.length()) || (valueoffset == 0) ) {
Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno); Warning("Failed parsing encoder parameters line %d %s: Invalid pair", lineno, line.c_str());
continue; continue;
} }

File diff suppressed because it is too large Load Diff

View File

@ -15,36 +15,55 @@ extern "C" {
#if HAVE_LIBAVCODEC #if HAVE_LIBAVCODEC
class VideoStore;
#include "zm_monitor.h" #include "zm_monitor.h"
#include "zm_packet.h"
#include "zm_packetqueue.h"
#include "zm_swscale.h"
class VideoStore { class VideoStore {
private: private:
AVOutputFormat *out_format;
AVFormatContext *oc; struct CodecData {
const AVCodecID codec_id;
const char *codec_codec;
const char *codec_name;
const enum AVPixelFormat pix_fmt;
};
static struct CodecData codec_data[];
Monitor *monitor;
AVOutputFormat *out_format;
AVFormatContext *oc;
AVStream *video_out_stream;
AVStream *audio_out_stream;
int video_in_stream_index;
int audio_in_stream_index;
AVCodec *video_out_codec; AVCodec *video_out_codec;
AVCodecContext *video_in_ctx;
AVCodecContext *video_out_ctx; AVCodecContext *video_out_ctx;
AVStream *video_out_stream;
AVStream *video_in_stream; AVStream *video_in_stream;
AVStream *audio_in_stream; AVStream *audio_in_stream;
Monitor *monitor;
const AVCodec *audio_in_codec;
AVCodecContext *audio_in_ctx;
// The following are used when encoding the audio stream to AAC
AVCodec *audio_out_codec;
AVCodecContext *audio_out_ctx;
// Move this into the object so that we aren't constantly allocating/deallocating it on the stack // Move this into the object so that we aren't constantly allocating/deallocating it on the stack
AVPacket opkt; AVPacket opkt;
// we are transcoding // we are transcoding
AVFrame *video_in_frame;
AVFrame *in_frame; AVFrame *in_frame;
AVFrame *out_frame; AVFrame *out_frame;
AVCodecContext *video_in_ctx; SWScale swscale;
const AVCodec *audio_in_codec; unsigned int packets_written;
AVCodecContext *audio_in_ctx; unsigned int frame_count;
// The following are used when encoding the audio stream to AAC
AVStream *audio_out_stream;
AVCodec *audio_out_codec;
AVCodecContext *audio_out_ctx;
#ifdef HAVE_LIBSWRESAMPLE #ifdef HAVE_LIBSWRESAMPLE
SwrContext *resample_ctx; SwrContext *resample_ctx;
#else #else
@ -59,6 +78,8 @@ private:
const char *format; const char *format;
// These are for in // These are for in
int64_t video_start_pts;
int64_t video_last_pts; int64_t video_last_pts;
int64_t video_last_dts; int64_t video_last_dts;
int64_t audio_last_pts; int64_t audio_last_pts;
@ -83,13 +104,20 @@ public:
const char *filename_in, const char *filename_in,
const char *format_in, const char *format_in,
AVStream *video_in_stream, AVStream *video_in_stream,
AVCodecContext *video_in_ctx,
AVStream *audio_in_stream, AVStream *audio_in_stream,
AVCodecContext *audio_in_ctx,
Monitor * p_monitor); Monitor * p_monitor);
bool open();
~VideoStore(); ~VideoStore();
bool open();
int writeVideoFramePacket( AVPacket *pkt ); void write_video_packet( AVPacket &pkt );
int writeAudioFramePacket( AVPacket *pkt ); void write_audio_packet( AVPacket &pkt );
int writeVideoFramePacket( ZMPacket *pkt );
int writeAudioFramePacket( ZMPacket *pkt );
int writePacket( ZMPacket *pkt );
int write_packets( zm_packetqueue &queue );
void flush_codecs();
}; };
#endif //havelibav #endif //havelibav

View File

@ -844,12 +844,11 @@ int Zone::Load(Monitor *monitor, Zone **&zones) {
} }
MYSQL_RES *result = mysql_store_result(&dbconn); MYSQL_RES *result = mysql_store_result(&dbconn);
db_mutex.unlock();
if ( !result ) { if ( !result ) {
Error("Can't use query result: %s", mysql_error(&dbconn)); Error("Can't use query result: %s", mysql_error(&dbconn));
db_mutex.unlock();
return 0; return 0;
} }
db_mutex.unlock();
int n_zones = mysql_num_rows(result); int n_zones = mysql_num_rows(result);
Debug(1, "Got %d zones for monitor %s", n_zones, monitor->Name()); Debug(1, "Got %d zones for monitor %s", n_zones, monitor->Name());
delete[] zones; delete[] zones;

View File

@ -70,6 +70,8 @@ possible, this should run at more or less constant speed.
#include "zm_time.h" #include "zm_time.h"
#include "zm_signal.h" #include "zm_signal.h"
#include "zm_monitor.h" #include "zm_monitor.h"
#include "zm_analysis_thread.h"
#include "zm_rtsp_server_thread.h"
void Usage() { void Usage() {
fprintf(stderr, "zmc -d <device_path> or -r <proto> -H <host> -P <port> -p <path> or -f <file_path> or -m <monitor_id>\n"); fprintf(stderr, "zmc -d <device_path> or -r <proto> -H <host> -P <port> -p <path> or -f <file_path> or -m <monitor_id>\n");
@ -240,11 +242,15 @@ int main(int argc, char *argv[]) {
result = 0; result = 0;
static char sql[ZM_SQL_SML_BUFSIZ]; static char sql[ZM_SQL_SML_BUFSIZ];
for ( int i = 0; i < n_monitors; i++ ) { for ( int i = 0; i < n_monitors; i++ ) {
if ( ! monitors[i]->getCamera() ) {
}
if ( ! monitors[i]->connect() ) {
}
time_t now = (time_t)time(nullptr); time_t now = (time_t)time(nullptr);
monitors[i]->setStartupTime(now); monitors[i]->setStartupTime(now);
snprintf(sql, sizeof(sql), snprintf(sql, sizeof(sql),
"INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%d, 'Running') ON DUPLICATE KEY UPDATE Status='Running'", "INSERT INTO Monitor_Status (MonitorId,Status,CaptureFPS,AnalysisFPS) VALUES (%d, 'Running',0,0) ON DUPLICATE KEY UPDATE Status='Running',CaptureFPS=0,AnalysisFPS=0",
monitors[i]->Id()); monitors[i]->Id());
if ( mysql_query(&dbconn, sql) ) { if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn)); Error("Can't run query: %s", mysql_error(&dbconn));
@ -252,95 +258,129 @@ int main(int argc, char *argv[]) {
} // end foreach monitor } // end foreach monitor
// Outer primary loop, handles connection to camera // Outer primary loop, handles connection to camera
if ( monitors[0]->PrimeCapture() < 0 ) { if ( monitors[0]->PrimeCapture() <= 0 ) {
if ( prime_capture_log_count % 60 ) { if ( prime_capture_log_count % 60 ) {
Error("Failed to prime capture of initial monitor"); Error("Failed to prime capture of initial monitor");
} else { } else {
Debug(1, "Failed to prime capture of initial monitor"); Debug(1, "Failed to prime capture of initial monitor");
} }
prime_capture_log_count ++; prime_capture_log_count ++;
if ( !zm_terminate ) monitors[0]->disconnect();
sleep(10); if ( !zm_terminate ) {
Debug(1, "Sleeping");
sleep(5);
}
continue; continue;
} }
int *capture_delays = new int[n_monitors];
int *alarm_capture_delays = new int[n_monitors];
int *next_delays = new int[n_monitors];
struct timeval * last_capture_times = new struct timeval[n_monitors];
for ( int i = 0; i < n_monitors; i++ ) { for ( int i = 0; i < n_monitors; i++ ) {
last_capture_times[i].tv_sec = last_capture_times[i].tv_usec = 0;
capture_delays[i] = monitors[i]->GetCaptureDelay();
alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay();
snprintf(sql, sizeof(sql), snprintf(sql, sizeof(sql),
"INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%d, 'Connected') ON DUPLICATE KEY UPDATE Status='Connected'", "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%d, 'Connected') ON DUPLICATE KEY UPDATE Status='Connected'",
monitors[i]->Id()); monitors[i]->Id());
if ( mysql_query(&dbconn, sql) ) { if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn)); Error("Can't run query: %s", mysql_error(&dbconn));
} }
}
#if HAVE_RTSP_SERVER
RTSPServerThread ** rtsp_server_threads = nullptr;
if ( config.min_rtsp_port ) {
rtsp_server_threads = new RTSPServerThread *[n_monitors];
Debug(1, "Starting RTSP server because min_rtsp_port is set");
} else {
Debug(1, "Not starting RTSP server because min_rtsp_port not set");
}
#endif
AnalysisThread **analysis_threads = new AnalysisThread *[n_monitors];
int *capture_delays = new int[n_monitors];
int *alarm_capture_delays = new int[n_monitors];
struct timeval * last_capture_times = new struct timeval[n_monitors];
for ( int i = 0; i < n_monitors; i++ ) {
last_capture_times[i].tv_sec = last_capture_times[i].tv_usec = 0;
capture_delays[i] = monitors[i]->GetCaptureDelay();
alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay();
Debug(2, "capture delay(%u mSecs 1000/capture_fps) alarm delay(%u)",
capture_delays[i], alarm_capture_delays[i]);
Monitor::Function function = monitors[0]->GetFunction();
if ( function != Monitor::MONITOR ) {
Debug(1, "Starting an analysis thread for monitor (%d)", monitors[i]->Id());
analysis_threads[i] = new AnalysisThread(monitors[i]);
analysis_threads[i]->start();
} else {
analysis_threads[i] = NULL;
}
#if HAVE_RTSP_SERVER
if ( rtsp_server_threads ) {
for ( int i = 0; i < n_monitors; i++ ) {
rtsp_server_threads[i] = new RTSPServerThread(monitors[i]);
Camera *camera = monitors[i]->getCamera();
rtsp_server_threads[i]->addStream(camera->get_VideoStream(), camera->get_AudioStream());
rtsp_server_threads[i]->start();
}
}
#endif
} // end foreach monitor } // end foreach monitor
struct timeval now; struct timeval now;
struct DeltaTimeval delta_time; struct DeltaTimeval delta_time;
while ( !zm_terminate ) { while ( !zm_terminate ) {
//sigprocmask(SIG_BLOCK, &block_set, 0); //sigprocmask(SIG_BLOCK, &block_set, 0);
for ( int i = 0; i < n_monitors; i++ ) { for ( int i = 0; i < n_monitors; i++ ) {
long min_delay = MAXINT;
monitors[i]->CheckAction();
if ( monitors[i]->PreCapture() < 0 ) {
Error("Failed to pre-capture monitor %d %d (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
result = -1;
break;
}
if ( monitors[i]->Capture() < 0 ) {
Error("Failed to capture image from monitor %d %s (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
result = -1;
break;
}
if ( monitors[i]->PostCapture() < 0 ) {
Error("Failed to post-capture monitor %d %s (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
result = -1;
break;
}
gettimeofday(&now, nullptr); gettimeofday(&now, nullptr);
for ( int j = 0; j < n_monitors; j++ ) { // capture_delay is the amount of time we should sleep to achieve the desired framerate.
if ( last_capture_times[j].tv_sec ) { int delay = monitors[i]->GetState() == Monitor::ALARM ? alarm_capture_delays[i] : capture_delays[i];
DELTA_TIMEVAL(delta_time, now, last_capture_times[j], DT_PREC_3); if ( delay && last_capture_times[i].tv_sec ) {
if ( monitors[i]->GetState() == Monitor::ALARM ) int sleep_time;
next_delays[j] = alarm_capture_delays[j]-delta_time.delta; DELTA_TIMEVAL(delta_time, now, last_capture_times[i], DT_PREC_3);
else
next_delays[j] = capture_delays[j]-delta_time.delta;
if ( next_delays[j] < 0 )
next_delays[j] = 0;
} else {
next_delays[j] = 0;
}
if ( next_delays[j] <= min_delay ) {
min_delay = next_delays[j];
}
} // end foreach monitor
if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) { sleep_time = delay - delta_time.delta;
if ( monitors[i]->PreCapture() < 0 ) { Debug(3, "Sleep time is %d from now:%d.%d last:%d.%d delay: %d",
Error("Failed to pre-capture monitor %d %s (%d/%d)", sleep_time,
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); now.tv_sec, now.tv_usec,
monitors[i]->Close(); last_capture_times[i].tv_sec, last_capture_times[i].tv_usec,
result = -1; delay
break; );
}
if ( monitors[i]->Capture() < 0 ) { if ( sleep_time < 0 )
Info("Failed to capture image from monitor %d %s (%d/%d)", sleep_time = 0;
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
monitors[i]->Close();
result = -1;
break;
}
if ( monitors[i]->PostCapture() < 0 ) {
Error("Failed to post-capture monitor %d %s (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
monitors[i]->Close();
result = -1;
break;
}
if ( next_delays[i] > 0 ) { if ( sleep_time > 0 ) {
gettimeofday(&now, nullptr); Debug(2,"usleeping (%d)", sleep_time*(DT_MAXGRAN/DT_PREC_3) );
DELTA_TIMEVAL(delta_time, now, last_capture_times[i], DT_PREC_3); usleep(sleep_time*(DT_MAXGRAN/DT_PREC_3));
long sleep_time = next_delays[i]-delta_time.delta;
if ( sleep_time > 0 ) {
usleep(sleep_time*(DT_MAXGRAN/DT_PREC_3));
}
} }
gettimeofday(&(last_capture_times[i]), nullptr); } // end if has a last_capture time
} // end if next_delay <= min_delay || next_delays[i] <= 0 ) last_capture_times[i] = now;
} // end foreach n_monitors
if ( result < 0 ) {
// Failure, try reconnecting
break;
}
} // end foreach n_monitors
//sigprocmask(SIG_UNBLOCK, &block_set, 0);
if ( zm_reload ) { if ( zm_reload ) {
for ( int i = 0; i < n_monitors; i++ ) { for ( int i = 0; i < n_monitors; i++ ) {
monitors[i]->Reload(); monitors[i]->Reload();
@ -348,19 +388,57 @@ int main(int argc, char *argv[]) {
logTerm(); logTerm();
logInit(log_id_string); logInit(log_id_string);
zm_reload = false; zm_reload = false;
} // end if zm_reload
} // end while ! zm_terminate and connected
// Killoff the analysis threads. Don't need them spinning while we try to reconnect
for ( int i = 0; i < n_monitors; i++ ) {
if ( analysis_threads[i] ) {
analysis_threads[i]->stop();
} }
if ( result < 0 ) { #if HAVE_RTSP_SERVER
// Failure, try reconnecting if ( rtsp_server_threads ) {
sleep(5); rtsp_server_threads[i]->stop();
break;
} }
} // end while ! zm_terminate #endif
}
for ( int i = 0; i < n_monitors; i++ ) {
monitors[i]->Close();
}
// Killoff the analysis threads. Don't need them spinning while we try to reconnect
for ( int i = 0; i < n_monitors; i++ ) {
if ( analysis_threads[i] ) {
analysis_threads[i]->join();
delete analysis_threads[i];
analysis_threads[i] = nullptr;
}
} // end foreach monitor
delete [] analysis_threads;
#if HAVE_RTSP_SERVER
if ( rtsp_server_threads ) {
for ( int i = 0; i < n_monitors; i++ ) {
rtsp_server_threads[i]->join();;
delete rtsp_server_threads[i];
rtsp_server_threads[i] = nullptr;
}
delete[] rtsp_server_threads;
rtsp_server_threads = nullptr;
}
#endif
delete [] alarm_capture_delays; delete [] alarm_capture_delays;
delete [] capture_delays; delete [] capture_delays;
delete [] next_delays;
delete [] last_capture_times; delete [] last_capture_times;
if ( result < 0 ) {
// Failure, try reconnecting
Debug(1, "Sleeping for 5");
sleep(5);
}
} // end while ! zm_terminate outer connection loop } // end while ! zm_terminate outer connection loop
Debug(1,"Updating Monitor status");
for ( int i = 0; i < n_monitors; i++ ) { for ( int i = 0; i < n_monitors; i++ ) {
static char sql[ZM_SQL_SML_BUFSIZ]; static char sql[ZM_SQL_SML_BUFSIZ];
snprintf(sql, sizeof(sql), snprintf(sql, sizeof(sql),
@ -374,6 +452,7 @@ int main(int argc, char *argv[]) {
delete [] monitors; delete [] monitors;
Image::Deinitialise(); Image::Deinitialise();
Debug(1,"terminating");
logTerm(); logTerm();
zmDbClose(); zmDbClose();

View File

@ -262,7 +262,13 @@ int main(int argc, const char *argv[], char **envp) {
stream.setStreamTTL(ttl); stream.setStreamTTL(ttl);
stream.setStreamQueue(connkey); stream.setStreamQueue(connkey);
stream.setStreamBuffer(playback_buffer); stream.setStreamBuffer(playback_buffer);
stream.setStreamStart(monitor_id); if ( !stream.setStreamStart(monitor_id) ) {
Error("Unable set start stream for monitor %d", monitor_id);
stream.sendTextFrame("Unable to connect to monitor");
logTerm();
zmDbClose();
return -1;
}
if ( mode == ZMS_JPEG ) { if ( mode == ZMS_JPEG ) {
stream.setStreamType(MonitorStream::STREAM_JPEG); stream.setStreamType(MonitorStream::STREAM_JPEG);

View File

@ -548,10 +548,11 @@ int main(int argc, char *argv[]) {
} }
if ( function & ZMU_FPS ) { if ( function & ZMU_FPS ) {
if ( verbose ) { if ( verbose ) {
printf("Current capture rate: %.2f frames per second\n", monitor->GetFPS()); printf("Current capture rate: %.2f frames per second, analysis rate: %.2f frames per second\n",
monitor->get_capture_fps(), monitor->get_analysis_fps());
} else { } else {
if ( have_output ) fputc(separator, stdout); if ( have_output ) fputc(separator, stdout);
printf("%.2f", monitor->GetFPS()); printf("capture: %.2f, analysis: %.2f", monitor->get_capture_fps(), monitor->get_analysis_fps());
have_output = true; have_output = true;
} }
} }

View File

@ -48,18 +48,18 @@ while (my $line = <F>) {
$in_head-- if $line =~ /^$/ and $in_head; $in_head-- if $line =~ /^$/ and $in_head;
next while $in_head; next while $in_head;
unless ($line =~ /^\s+(0x..), \/\* (........)/) { unless ($line =~ /^\s+(0x..), \/\* (........)/) {
$line =~ s/static unsigned char fontdata/static unsigned int bigfontdata/; #$line =~ s/static unsigned char fontdata/static unsigned int bigfontdata/;
print $line; print $line;
next; next;
} }
my $code = $1; my $code = $1;
my $bincode = $2; my $bincode = $2;
$bincode = "$1$1$2$2$3$3$4$4$5$5$6$6$7$7$8$8" if $bincode =~ /(.)(.)(.)(.)(.)(.)(.)(.)$/; $bincode = "$1$1$2$2$3$3$4$4$5$5$6$6$7$7$8$8" if $bincode =~ /(.)(.)(.)(.)(.)(.)(.)(.)$/;
$bincode =~ s/ /1/g; #$bincode =~ s/ /1/g;
my $intcode = unpack("N", pack("B32", substr("0" x 32 . $bincode, -32))); #my $intcode = unpack("N", pack("B32", substr("0" x 32 . $bincode, -32)));
my $hexcode = sprintf("%#x", $intcode); #my $hexcode = sprintf("%#x", $intcode);
$hexcode =~ s/^0$/0x0/; #$hexcode =~ s/^0$/0x0/;
$bincode =~ s/1/ /g; #$bincode =~ s/1/ /g;
print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode); print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode);
print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode); print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode);
} }

View File

@ -1 +1 @@
1.35.16 1.35.17

View File

@ -1,14 +1,22 @@
<?php <?php
error_reporting(0);
$defaultMonitor = new ZM\Monitor(); $defaultMonitor = new ZM\Monitor();
$defaultMonitor->set(array( $defaultMonitor->set(array(
'StorageId' => 1, 'StorageId' => 1,
'ServerId' => 'auto', 'ServerId' => 'auto',
'Function' => 'Record', 'Function' => 'Mocord',
'Type' => 'Ffmpeg', 'Type' => 'Ffmpeg',
'Enabled' => '1', 'Enabled' => '1',
'Colour' => '4', // 32bit 'Colour' => '4', // 32bit
'PreEventCount' => 0, 'ImageBufferCount' => '20',
'WarmupCount' => '0',
'PreEventCount' => '0',
'StreamReplayBuffer' => '0',
'SaveJPEGs' => '4',
'VideoWriter' => '1',
'MaxFPS' => '20',
'AlarmMaxFPS' => '20',
) ); ) );
function probe( &$url_bits ) { function probe( &$url_bits ) {

View File

@ -117,8 +117,10 @@ if ( sem_acquire($semaphore,1) !== false ) {
$data = unpack('ltype', $msg); $data = unpack('ltype', $msg);
switch ( $data['type'] ) { switch ( $data['type'] ) {
case MSG_DATA_WATCH : case MSG_DATA_WATCH :
$data = unpack('ltype/imonitor/istate/dfps/ilevel/irate/ddelay/izoom/Cdelayed/Cpaused/Cenabled/Cforced', $msg); $data = unpack('ltype/imonitor/istate/dfps/dcapturefps/danalysisfps/ilevel/irate/ddelay/izoom/Cdelayed/Cpaused/Cenabled/Cforced', $msg);
$data['fps'] = round( $data['fps'], 2 ); $data['fps'] = round( $data['fps'], 2 );
$data['capturefps'] = round( $data['capturefps'], 2 );
$data['analysisfps'] = round( $data['analysisfps'], 2 );
$data['rate'] /= RATE_BASE; $data['rate'] /= RATE_BASE;
$data['delay'] = round( $data['delay'], 2 ); $data['delay'] = round( $data['delay'], 2 );
$data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); $data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 );

View File

@ -63,7 +63,7 @@ class Event extends ZM_Object {
$this->{'Storage'} = Storage::find_one(array('Id'=>$this->{'StorageId'})); $this->{'Storage'} = Storage::find_one(array('Id'=>$this->{'StorageId'}));
if ( ! ( property_exists($this, 'Storage') and $this->{'Storage'} ) ) { if ( ! ( property_exists($this, 'Storage') and $this->{'Storage'} ) ) {
$this->{'Storage'} = new Storage(NULL); $this->{'Storage'} = new Storage(NULL);
$this->{'Storage'}->Scheme($this->{'Scheme'}); $this->{'Storage'}->Scheme($this->Scheme());
} }
} }
return $this->{'Storage'}; return $this->{'Storage'};

View File

@ -68,6 +68,7 @@ class Monitor extends ZM_Object {
'SaveJPEGs' => 3, 'SaveJPEGs' => 3,
'VideoWriter' => '0', 'VideoWriter' => '0',
'OutputCodec' => null, 'OutputCodec' => null,
'Encoder' => 'auto',
'OutputContainer' => null, 'OutputContainer' => null,
'EncoderParameters' => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n", 'EncoderParameters' => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n",
'RecordAudio' => array('type'=>'boolean', 'default'=>0), 'RecordAudio' => array('type'=>'boolean', 'default'=>0),

969
web/includes/actions.php Normal file
View File

@ -0,0 +1,969 @@
<?php
//
// ZoneMinder web action file, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
function getAffectedIds( $name ) {
$names = $name.'s';
$ids = array();
if ( isset($_REQUEST[$names]) ) {
if ( is_array($_REQUEST[$names]) ) {
$ids = $_REQUEST[$names];
} else {
$ids = array($_REQUEST[$names]);
}
} else if ( isset($_REQUEST[$name]) ) {
if ( is_array($_REQUEST[$name]) ) {
$ids = $_REQUEST[$name];
} else {
$ids = array($_REQUEST[$name]);
}
}
return $ids;
}
if ( empty($action) ) {
return;
}
if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) {
$refreshParent = true;
// User login is automatically performed in includes/auth.php So we don't need to perform a login here,
// just handle redirects. This is the action that comes from the login view, so the logical thing to
// do on successful auth is redirect to console, otherwise loop back to login.
if ( !$user ) {
$view = 'login';
} else {
$view = 'console';
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console';
}
} else if ( $action == 'logout' ) {
userLogout();
$refreshParent = true;
$view = 'none';
} else if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) {
$_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']);
setcookie('zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10);
$refreshParent = true;
}
// Event scope actions, view permissions only required
if ( canView('Events') ) {
if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) {
if ( $action == 'addterm' ) {
$_REQUEST['filter'] = addFilterTerm($_REQUEST['filter'], $_REQUEST['line']);
} elseif ( $action == 'delterm' ) {
$_REQUEST['filter'] = delFilterTerm($_REQUEST['filter'], $_REQUEST['line']);
} else if ( canEdit('Events') ) {
if ( $action == 'delete' ) {
if ( ! empty($_REQUEST['Id']) ) {
dbQuery('DELETE FROM Filters WHERE Id=?', array($_REQUEST['Id']));
}
} else if ( ( $action == 'Save' ) or ( $action == 'SaveAs' ) or ( $action == 'execute' ) ) {
# or ( $action == 'submit' ) ) {
$sql = '';
$_REQUEST['filter']['Query']['sort_field'] = validStr($_REQUEST['filter']['Query']['sort_field']);
$_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']);
$_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']);
if ( $action == 'execute' ) {
$tempFilterName = '_TempFilter'.time();
$sql .= ' Name = \''.$tempFilterName.'\'';
} else {
$sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']);
}
$sql .= ', Query = '.dbEscape(jsonEncode($_REQUEST['filter']['Query']));
$sql .= ', AutoArchive = '.(!empty($_REQUEST['filter']['AutoArchive']) ? 1 : 0);
$sql .= ', AutoVideo = '. ( !empty($_REQUEST['filter']['AutoVideo']) ? 1 : 0);
$sql .= ', AutoUpload = '. ( !empty($_REQUEST['filter']['AutoUpload']) ? 1 : 0);
$sql .= ', AutoEmail = '. ( !empty($_REQUEST['filter']['AutoEmail']) ? 1 : 0);
$sql .= ', AutoMessage = '. ( !empty($_REQUEST['filter']['AutoMessage']) ? 1 : 0);
$sql .= ', AutoExecute = '. ( !empty($_REQUEST['filter']['AutoExecute']) ? 1 : 0);
$sql .= ', AutoExecuteCmd = '.dbEscape($_REQUEST['filter']['AutoExecuteCmd']);
$sql .= ', AutoDelete = '. ( !empty($_REQUEST['filter']['AutoDelete']) ? 1 : 0);
if ( !empty($_REQUEST['filter']['AutoMove']) ? 1 : 0) {
$sql .= ', AutoMove = 1, AutoMoveTo='. validInt($_REQUEST['filter']['AutoMoveTo']);
} else {
$sql .= ', AutoMove = 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);
if ( $_REQUEST['Id'] and ( $action == 'Save' ) ) {
dbQuery('UPDATE Filters SET ' . $sql. ' WHERE Id=?', array($_REQUEST['Id']));
} else {
dbQuery('INSERT INTO Filters SET' . $sql);
$_REQUEST['Id'] = dbInsertId();
}
if ( $action == 'execute' ) {
executeFilter( $tempFilterName );
}
} // end if save or execute
} // end if canEdit(Events)
return;
} // end if object == filter
else {
// Event scope actions, edit permissions required
if ( canEdit('Events') ) {
if ( ($action == 'rename') && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) {
dbQuery('UPDATE Events SET Name=? WHERE Id=?', array($_REQUEST['eventName'], $_REQUEST['eid']));
} else if ( $action == 'eventdetail' ) {
if ( !empty($_REQUEST['eid']) ) {
dbQuery('UPDATE Events SET Cause=?, Notes=? WHERE Id=?',
array($_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid']) );
} else {
$dbConn->beginTransaction();
foreach( getAffectedIds('markEid') as $markEid ) {
dbQuery('UPDATE Events SET Cause=?, Notes=? WHERE Id=?',
array($_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid) );
}
$dbConn->commit();
}
$refreshParent = true;
$closePopup = true;
} elseif ( $action == 'archive' || $action == 'unarchive' ) {
$archiveVal = ($action == 'archive')?1:0;
if ( !empty($_REQUEST['eid']) ) {
dbQuery('UPDATE Events SET Archived=? WHERE Id=?', array($archiveVal, $_REQUEST['eid']));
} else {
$dbConn->beginTransaction();
foreach( getAffectedIds('markEid') as $markEid ) {
dbQuery('UPDATE Events SET Archived=? WHERE Id=?', array($archiveVal, $markEid));
}
$dbConn->commit();
$refreshParent = true;
}
} elseif ( $action == 'delete' ) {
$dbConn->beginTransaction();
foreach( getAffectedIds('eids') as $markEid ) {
deleteEvent($markEid);
}
$dbConn->commit();
$refreshParent = true;
}
} // end if canEdit(Events)
} // end if filter or something else
} // end canView(Events)
// Monitor control actions, require a monitor id and control view permissions for that monitor
if ( !empty($_REQUEST['mid']) && canView('Control', $_REQUEST['mid']) ) {
require_once('control_functions.php');
require_once('Monitor.php');
$mid = validInt($_REQUEST['mid']);
if ( $action == 'control' ) {
$monitor = new Monitor($mid);
$ctrlCommand = buildControlCommand($monitor);
sendControlCommand($monitor->Id(), $ctrlCommand);
} else if ( $action == 'settings' ) {
$args = ' -m ' . escapeshellarg($mid);
$args .= ' -B' . escapeshellarg($_REQUEST['newBrightness']);
$args .= ' -C' . escapeshellarg($_REQUEST['newContrast']);
$args .= ' -H' . escapeshellarg($_REQUEST['newHue']);
$args .= ' -O' . escapeshellarg($_REQUEST['newColour']);
$zmuCommand = getZmuCommand($args);
$zmuOutput = exec($zmuCommand);
list($brightness, $contrast, $hue, $colour) = explode(' ', $zmuOutput);
dbQuery(
'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?',
array($brightness, $contrast, $hue, $colour, $mid));
}
}
// Control capability actions, require control edit permissions
if ( canEdit('Control') ) {
if ( $action == 'controlcap' ) {
require_once('Control.php');
$Control = new Control( !empty($_REQUEST['cid']) ? $_REQUEST['cid'] : null );
//$changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns );
$Control->save($_REQUEST['newControl']);
$refreshParent = true;
$view = 'none';
} elseif ( $action == 'delete' ) {
if ( isset($_REQUEST['markCids']) ) {
foreach( $_REQUEST['markCids'] as $markCid ) {
dbQuery('DELETE FROM Controls WHERE Id = ?', array($markCid));
dbQuery('UPDATE Monitors SET Controllable = 0, ControlId = 0 WHERE ControlId = ?', array($markCid));
$refreshParent = true;
}
}
} // end if action
} // end if canEdit Controls
if ( isset($_REQUEST['object']) and $_REQUEST['object'] == 'Monitor' ) {
if ( $action == 'save' ) {
foreach ( $_REQUEST['mids'] as $mid ) {
$mid = ValidInt($mid);
if ( ! canEdit('Monitors', $mid) ) {
Warning("Cannot edit monitor $mid");
continue;
}
$Monitor = new Monitor($mid);
if ( $Monitor->Type() != 'WebSite' ) {
$Monitor->zmaControl('stop');
$Monitor->zmcControl('stop');
}
$Monitor->save($_REQUEST['newMonitor']);
if ( $Monitor->Function() != 'None' && $Monitor->Type() != 'WebSite' ) {
$Monitor->zmcControl('start');
if ( $Monitor->Enabled() ) {
$Monitor->zmaControl('start');
}
}
} // end foreach mid
$refreshParent = true;
} // end if action == save
} // end if object is Monitor
// Monitor edit actions, require a monitor id and edit permissions for that monitor
if ( !empty($_REQUEST['mid']) && canEdit('Monitors', $_REQUEST['mid']) ) {
$mid = validInt($_REQUEST['mid']);
if ( $action == 'function' ) {
$monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id=?', NULL, array($mid));
$newFunction = validStr($_REQUEST['newFunction']);
# Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0
$newEnabled = ( !isset($_REQUEST['newEnabled']) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1';
$oldFunction = $monitor['Function'];
$oldEnabled = $monitor['Enabled'];
if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) {
dbQuery('UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?',
array($newFunction, $newEnabled, $mid));
$monitor['Function'] = $newFunction;
$monitor['Enabled'] = $newEnabled;
if ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) {
$restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled);
zmaControl($monitor, 'stop');
zmcControl($monitor, $restart?'restart':'');
zmaControl($monitor, 'start');
}
$refreshParent = true;
}
} else if ( $action == 'zone' && isset($_REQUEST['zid']) ) {
$zid = validInt($_REQUEST['zid']);
$monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id=?', NULL, array($mid));
if ( !empty($zid) ) {
$zone = dbFetchOne('SELECT * FROM Zones WHERE MonitorId=? AND Id=?', NULL, array($mid, $zid));
} else {
$zone = array();
}
if ( $_REQUEST['newZone']['Units'] == 'Percent' ) {
$_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100);
$_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100);
if ( isset($_REQUEST['newZone']['MinFilterPixels']) )
$_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100);
if ( isset($_REQUEST['newZone']['MaxFilterPixels']) )
$_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100);
if ( isset($_REQUEST['newZone']['MinBlobPixels']) )
$_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100);
if ( isset($_REQUEST['newZone']['MaxBlobPixels']) )
$_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100);
}
unset( $_REQUEST['newZone']['Points'] );
$types = array();
$changes = getFormChanges($zone, $_REQUEST['newZone'], $types);
if ( count($changes) ) {
if ( $zid > 0 ) {
dbQuery('UPDATE Zones SET '.implode(', ', $changes).' WHERE MonitorId=? AND Id=?', array($mid, $zid));
} else {
dbQuery('INSERT INTO Zones SET MonitorId=?, '.implode(', ', $changes), array($mid));
}
if ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) {
if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) {
zmaControl($monitor, 'stop');
zmcControl($monitor, 'restart');
zmaControl($monitor, 'start');
} else {
zmaControl($monitor, 'restart');
}
}
if ( ($_REQUEST['newZone']['Type'] == 'Privacy') && $monitor['Controllable'] ) {
require_once('control_functions.php');
sendControlCommand($mid, 'quit');
}
$refreshParent = true;
}
$view = 'none';
} elseif ( $action == 'plugin' && isset($_REQUEST['pl']) ) {
$sql = 'SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?';
$pconfs=dbFetchAll($sql, NULL, array($mid, $_REQUEST['zid'], $_REQUEST['pl']));
$changes = 0;
foreach ( $pconfs as $pconf ) {
$value = $_REQUEST['pluginOpt'][$pconf['Name']];
if ( array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value'] != $value) ) {
dbQuery('UPDATE PluginsConfig SET Value=? WHERE id=?', array($value, $pconf['Id']));
$changes++;
}
}
if ( $changes > 0 ) {
if ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) {
zmaControl($mid, 'restart');
}
$refreshParent = true;
}
$view = 'none';
} elseif ( ($action == 'sequence') && isset($_REQUEST['smid']) ) {
$smid = validInt($_REQUEST['smid']);
$monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid));
$smonitor = dbFetchOne('SELECT * FROM Monitors WHERE Id = ?', NULL, array($smid));
dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($smonitor['Sequence'], $monitor['Id']));
dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($monitor['Sequence'], $smonitor['Id']));
$refreshParent = true;
fixSequences();
} elseif ( $action == 'delete' ) {
if ( isset($_REQUEST['markZids']) ) {
$deletedZid = 0;
foreach ( $_REQUEST['markZids'] as $markZid ) {
$zone = dbFetchOne('SELECT * FROM Zones WHERE Id=?', NULL, array($markZid));
dbQuery('DELETE FROM Zones WHERE MonitorId=? AND Id=?', array($mid, $markZid));
$deletedZid = 1;
}
if ( $deletedZid ) {
if ( daemonCheck() && $monitor['Type'] != 'WebSite' ) {
if ( $zone['Type'] == 'Privacy' ) {
zmaControl($mid, 'stop');
zmcControl($mid, 'restart');
zmaControl($mid, 'start');
} else {
zmaControl($mid, 'restart');
}
} // end if daemonCheck()
$refreshParent = true;
} // end if deletedzid
} // end if isset($_REQUEST['markZids'])
} // end if action
} // end if $mid and canEdit($mid)
// Monitor edit actions, monitor id derived, require edit permissions for that monitor
if ( canEdit('Monitors') ) {
if ( $action == 'monitor' ) {
$mid = 0;
if ( !empty($_REQUEST['mid']) ) {
$mid = validInt($_REQUEST['mid']);
$monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id=?', NULL, array($mid));
if ( ZM_OPT_X10 ) {
$x10Monitor = dbFetchOne('SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid));
if ( !$x10Monitor )
$x10Monitor = array();
}
} else {
$monitor = array();
if ( ZM_OPT_X10 ) {
$x10Monitor = array();
}
}
$Monitor = new Monitor($monitor);
// Define a field type for anything that's not simple text equivalent
$types = array(
'Triggers' => 'set',
'Controllable' => 'toggle',
'TrackMotion' => 'toggle',
'Enabled' => 'toggle',
'DoNativeMotDet' => 'toggle',
'Exif' => 'toggle',
'RTSPDescribe' => 'toggle',
'RecordAudio' => 'toggle',
'Method' => 'raw',
);
if ( $_REQUEST['newMonitor']['ServerId'] == 'auto' ) {
$_REQUEST['newMonitor']['ServerId'] = dbFetchOne(
'SELECT Id FROM Servers WHERE Status=\'Running\' ORDER BY FreeMem DESC, CpuLoad ASC LIMIT 1', 'Id');
Logger::Debug('Auto selecting server: Got ' . $_REQUEST['newMonitor']['ServerId'] );
if ( ( ! $_REQUEST['newMonitor'] ) and defined('ZM_SERVER_ID') ) {
$_REQUEST['newMonitor']['ServerId'] = ZM_SERVER_ID;
Logger::Debug('Auto selecting server to ' . ZM_SERVER_ID);
}
}
$columns = getTableColumns('Monitors');
$changes = getFormChanges($monitor, $_REQUEST['newMonitor'], $types, $columns);
if ( count($changes) ) {
if ( $mid ) {
# If we change anything that changes the shared mem size, zma can complain. So let's stop first.
if ( $monitor['Type'] != 'WebSite' ) {
zmaControl($monitor, 'stop');
zmcControl($monitor, 'stop');
}
dbQuery('UPDATE Monitors SET '.implode(', ', $changes).' WHERE Id=?', array($mid));
// Groups will be added below
if ( isset($changes['Name']) or isset($changes['StorageId']) ) {
$OldStorage = new Storage($monitor['StorageId']);
$saferOldName = basename($monitor['Name']);
if ( file_exists($OldStorage->Path().'/'.$saferOldName) )
unlink($OldStorage->Path().'/'.$saferOldName);
$NewStorage = new Storage($_REQUEST['newMonitor']['StorageId']);
if ( ! file_exists($NewStorage->Path().'/'.$mid) )
mkdir($NewStorage->Path().'/'.$mid, 0755);
$saferNewName = basename($_REQUEST['newMonitor']['Name']);
symlink($mid, $NewStorage->Path().'/'.$saferNewName);
}
if ( isset($changes['Width']) || isset($changes['Height']) ) {
$newW = $_REQUEST['newMonitor']['Width'];
$newH = $_REQUEST['newMonitor']['Height'];
$newA = $newW * $newH;
$oldW = $monitor['Width'];
$oldH = $monitor['Height'];
$oldA = $oldW * $oldH;
$zones = dbFetchAll('SELECT * FROM Zones WHERE MonitorId=?', NULL, array($mid));
foreach ( $zones as $zone ) {
$newZone = $zone;
$points = coordsToPoints($zone['Coords']);
for ( $i = 0; $i < count($points); $i++ ) {
$points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1));
$points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1));
}
$newZone['Coords'] = pointsToCoords($points);
$newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA));
$newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA));
$newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA));
$newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA));
$newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA));
$newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA));
$newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA));
$changes = getFormChanges($zone, $newZone, $types);
if ( count($changes) ) {
dbQuery('UPDATE Zones SET '.implode(', ', $changes).' WHERE MonitorId=? AND Id=?',
array($mid, $zone['Id']));
}
} // end foreach zone
} // end if width and height
$restart = true;
} else if ( ! $user['MonitorIds'] ) {
// Can only create new monitors if we are not restricted to specific monitors
# FIXME This is actually a race condition. Should lock the table.
$maxSeq = dbFetchOne('SELECT MAX(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence');
$changes[] = 'Sequence = '.($maxSeq+1);
$sql = 'INSERT INTO Monitors SET '.implode(', ', $changes);
if ( dbQuery($sql) ) {
$mid = dbInsertId();
$zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height'];
dbQuery("INSERT INTO Zones SET MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) );
//$view = 'none';
$Storage = new Storage($_REQUEST['newMonitor']['StorageId']);
mkdir($Storage->Path().'/'.$mid, 0755);
$saferName = basename($_REQUEST['newMonitor']['Name']);
symlink($mid, $Storage->Path().'/'.$saferName);
} else {
Error('Error saving new Monitor.');
$error_message = dbError($sql);
return;
}
} else {
Error('Users with Monitors restrictions cannot create new monitors.');
return;
}
$restart = true;
} else {
Logger::Debug('No action due to no changes to Monitor');
} # end if count(changes)
if (
( !isset($_POST['newMonitor']['GroupIds']) )
or
( count($_POST['newMonitor']['GroupIds']) != count($Monitor->GroupIds()) )
or
array_diff($_POST['newMonitor']['GroupIds'], $Monitor->GroupIds())
) {
if ( $Monitor->Id() )
dbQuery('DELETE FROM Groups_Monitors WHERE MonitorId=?', array($mid));
if ( isset($_POST['newMonitor']['GroupIds']) ) {
foreach ( $_POST['newMonitor']['GroupIds'] as $group_id ) {
dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid));
}
}
} // end if there has been a change of groups
if ( ZM_OPT_X10 ) {
$x10Changes = getFormChanges($x10Monitor, $_REQUEST['newX10Monitor']);
if ( count($x10Changes) ) {
if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) {
dbQuery('UPDATE TriggersX10 SET '.implode(', ', $x10Changes).' WHERE MonitorId=?', array($mid));
} elseif ( !$user['MonitorIds'] ) {
if ( !$x10Monitor ) {
dbQuery('INSERT INTO TriggersX10 SET MonitorId = ?, '.implode(', ', $x10Changes), array($mid));
} else {
dbQuery('DELETE FROM TriggersX10 WHERE MonitorId = ?', array($mid));
}
}
$restart = true;
} # end if has x10Changes
} # end if ZM_OPT_X10
if ( $restart ) {
$new_monitor = new Monitor($mid);
//fixDevices();
if ( $new_monitor->Type() != 'WebSite' ) {
$new_monitor->zmcControl('start');
$new_monitor->zmaControl('start');
}
if ( $new_monitor->Controllable() ) {
require_once('control_functions.php');
sendControlCommand($mid, 'quit');
}
// really should thump zmwatch and maybe zmtrigger too.
//daemonControl( 'restart', 'zmwatch.pl' );
$refreshParent = true;
} // end if restart
$view = 'none';
} elseif ( $action == 'delete' ) {
if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) {
require_once('Monitor.php');
foreach ( $_REQUEST['markMids'] as $markMid ) {
if ( canEdit('Monitors', $markMid) ) {
// This could be faster as a select all
if ( $monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id = ?', NULL, array($markMid)) ) {
$Monitor = new Monitor($monitor);
$Monitor->delete();
} // end if monitor found in db
} // end if canedit this monitor
} // end foreach monitor in MarkMid
} // markMids is set and we aren't limited to specific monitors
} // end if action == Delete
}
// Device view actions
if ( canEdit('Devices') ) {
if ( $action == 'device' ) {
if ( !empty($_REQUEST['command']) ) {
setDeviceStatusX10($_REQUEST['key'], $_REQUEST['command']);
} else if ( isset($_REQUEST['newDevice']) ) {
if ( isset($_REQUEST['did']) ) {
dbQuery('UPDATE Devices SET Name=?, KeyString=? WHERE Id=?',
array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) );
} else {
dbQuery('INSERT INTO Devices SET Name=?, KeyString=?',
array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString']) );
}
$refreshParent = true;
$view = 'none';
}
} elseif ( $action == 'delete' ) {
if ( isset($_REQUEST['markDids']) ) {
foreach( $_REQUEST['markDids'] as $markDid ) {
dbQuery('DELETE FROM Devices WHERE Id=?', array($markDid));
$refreshParent = true;
}
}
} // end if action
} // end if canedit devices
// Group view actions
if ( canView('Groups') && ($action == 'setgroup') ) {
if ( !empty($_REQUEST['gid']) ) {
setcookie('zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10);
} else {
setcookie('zmGroup', '', time()-3600*24*2);
}
$refreshParent = true;
}
// Group edit actions
# Should probably verify that each monitor id is a valid monitor, that we have access to.
# However at the moment, you have to have System permissions to do this
if ( canEdit('Groups') ) {
if ( $action == 'group' ) {
$monitors = empty($_POST['newGroup']['MonitorIds']) ? '' : implode(',', $_POST['newGroup']['MonitorIds']);
$group_id = null;
if ( !empty($_POST['gid']) ) {
$group_id = $_POST['gid'];
dbQuery(
'UPDATE Groups SET Name=?, ParentId=? WHERE Id=?',
array(
$_POST['newGroup']['Name'],
( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ),
$group_id,
)
);
dbQuery('DELETE FROM Groups_Monitors WHERE GroupId=?', array($group_id));
} else {
dbQuery(
'INSERT INTO Groups (Name,ParentId) VALUES (?,?)',
array(
$_POST['newGroup']['Name'],
( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ),
)
);
$group_id = dbInsertId();
}
if ( $group_id ) {
foreach ( $_POST['newGroup']['MonitorIds'] as $mid ) {
dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid));
}
}
$view = 'none';
$refreshParent = true;
} else if ( $action == 'delete' ) {
if ( !empty($_REQUEST['gid']) ) {
foreach ( Group::find(array('Id'=>$_REQUEST['gid'])) as $Group ) {
$Group->delete();
}
}
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=groups';
$refreshParent = true;
} # end if action
} // end if can edit groups
// System edit actions
if ( canEdit('System') ) {
if ( isset($_REQUEST['object']) ) {
if ( $_REQUEST['object'] == 'MontageLayout' ) {
require_once('MontageLayout.php');
if ( $action == 'Save' ) {
$Layout = null;
if ( $_REQUEST['Name'] != '' ) {
$Layout = new MontageLayout();
$Layout->Name($_REQUEST['Name']);
} else {
$Layout = new MontageLayout($_REQUEST['zmMontageLayout']);
}
$Layout->Positions($_REQUEST['Positions']);
$Layout->save();
session_start();
$_SESSION['zmMontageLayout'] = $Layout->Id();
setcookie('zmMontageLayout', $Layout->Id(), 1);
session_write_close();
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=montage';
} // end if save
} else if ( $_REQUEST['object'] == 'server' ) {
if ( $action == 'Save' ) {
if ( !empty($_REQUEST['id']) ) {
$dbServer = dbFetchOne(
'SELECT * FROM Servers WHERE Id=?',
NULL,
array($_REQUEST['id']) );
} else {
$dbServer = array();
}
$types = array();
$changes = getFormChanges($dbServer, $_REQUEST['newServer'], $types);
if ( count($changes) ) {
if ( !empty($_REQUEST['id']) ) {
dbQuery('UPDATE Servers SET '.implode(', ', $changes).' WHERE Id = ?',
array($_REQUEST['id']) );
} else {
dbQuery('INSERT INTO Servers SET '.implode(', ', $changes));
}
$refreshParent = true;
}
$view = 'none';
} else if ( $action == 'delete' ) {
if ( !empty($_REQUEST['markIds']) ) {
foreach( $_REQUEST['markIds'] as $Id )
dbQuery('DELETE FROM Servers WHERE Id=?', array($Id));
}
$refreshParent = true;
} else {
Error("Unknown action $action in saving Server");
}
} else if ( $_REQUEST['object'] == 'storage' ) {
if ( $action == 'Save' ) {
if ( !empty($_REQUEST['id']) )
$dbStorage = dbFetchOne('SELECT * FROM Storage WHERE Id=?', NULL, array($_REQUEST['id']));
else
$dbStorage = array();
$types = array();
$changes = getFormChanges($dbStorage, $_REQUEST['newStorage'], $types);
if ( count($changes) ) {
if ( !empty($_REQUEST['id']) ) {
dbQuery('UPDATE Storage SET '.implode(', ', $changes).' WHERE Id = ?', array($_REQUEST['id']));
} else {
dbQuery('INSERT INTO Storage set '.implode(', ', $changes));
}
$refreshParent = true;
}
$view = 'none';
} else if ( $action == 'delete' ) {
if ( !empty($_REQUEST['markIds']) ) {
foreach( $_REQUEST['markIds'] as $Id )
dbQuery('DELETE FROM Storage WHERE Id=?', array($Id));
}
$refreshParent = true;
} else {
Error("Unknown action $action in saving Storage");
}
} # end if isset($_REQUEST['object'] )
} else if ( $action == 'version' && isset($_REQUEST['option']) ) {
$option = $_REQUEST['option'];
switch( $option ) {
case 'go' :
{
// Ignore this, the caller will open the page itself
break;
}
case 'ignore' :
{
dbQuery("UPDATE Config SET Value = '".ZM_DYN_LAST_VERSION."' WHERE Name = 'ZM_DYN_CURR_VERSION'");
break;
}
case 'hour' :
case 'day' :
case 'week' :
{
$nextReminder = time();
if ( $option == 'hour' ) {
$nextReminder += 60*60;
} elseif ( $option == 'day' ) {
$nextReminder += 24*60*60;
} elseif ( $option == 'week' ) {
$nextReminder += 7*24*60*60;
}
dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_NEXT_REMINDER'");
break;
}
case 'never' :
{
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_CHECK_FOR_UPDATES'");
break;
}
}
}
if ( $action == 'donate' && isset($_REQUEST['option']) ) {
$option = $_REQUEST['option'];
switch( $option ) {
case 'go' :
{
// Ignore this, the caller will open the page itself
break;
}
case 'hour' :
case 'day' :
case 'week' :
case 'month' :
{
$nextReminder = time();
if ( $option == 'hour' ) {
$nextReminder += 60*60;
} elseif ( $option == 'day' ) {
$nextReminder += 24*60*60;
} elseif ( $option == 'week' ) {
$nextReminder += 7*24*60*60;
} elseif ( $option == 'month' ) {
$nextReminder += 30*24*60*60;
}
dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_DONATE_REMINDER_TIME'");
break;
}
case 'never' :
case 'already' :
{
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_DYN_SHOW_DONATE_REMINDER'");
break;
}
} // end switch option
}
if ( ($action == 'privacy') && isset($_REQUEST['option']) ) {
switch( $_REQUEST['option'] ) {
case 'decline' :
{
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'");
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_TELEMETRY_DATA'");
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console';
break;
}
case 'accept' :
{
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'");
dbQuery("UPDATE Config SET Value = '1' WHERE Name = 'ZM_TELEMETRY_DATA'");
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console';
break;
}
default: # Enable the privacy statement if we somehow submit something other than accept or decline
dbQuery("UPDATE Config SET Value = '1' WHERE Name = 'ZM_SHOW_PRIVACY'");
} // end switch option
return;
}
if ( $action == 'options' && isset($_REQUEST['tab']) ) {
$config = array();
$configCat = array();
$configCats = array();
$result = $dbConn->query('SELECT * FROM Config ORDER BY Id ASC');
if ( !$result )
echo mysql_error();
while( $row = dbFetchNext($result) ) {
$config[$row['Name']] = $row;
if ( !($configCat = &$configCats[$row['Category']]) ) {
$configCats[$row['Category']] = array();
$configCat = &$configCats[$row['Category']];
}
$configCat[$row['Name']] = $row;
}
$configCat = $configCats[$_REQUEST['tab']];
$changed = false;
foreach ( $configCat as $name=>$value ) {
unset($newValue);
if ( $value['Type'] == 'boolean' && empty($_REQUEST['newConfig'][$name]) ) {
$newValue = 0;
} else if ( isset($_REQUEST['newConfig'][$name]) ) {
$newValue = preg_replace("/\r\n/", "\n", stripslashes($_REQUEST['newConfig'][$name]));
}
if ( isset($newValue) && ($newValue != $value['Value']) ) {
dbQuery('UPDATE Config SET Value=? WHERE Name=?', array($newValue, $name));
$changed = true;
}
} // end foreach config entry
if ( $changed ) {
switch( $_REQUEST['tab'] ) {
case 'system' :
case 'config' :
$restartWarning = true;
break;
case 'web' :
case 'tools' :
break;
case 'logging' :
case 'network' :
case 'mail' :
case 'upload' :
$restartWarning = true;
break;
case 'highband' :
case 'medband' :
case 'lowband' :
break;
}
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=options&tab='.$_REQUEST['tab'];
}
loadConfig(false);
return;
} elseif ( $action == 'user' ) {
if ( !empty($_REQUEST['uid']) )
$dbUser = dbFetchOne('SELECT * FROM Users WHERE Id=?', NULL, array($_REQUEST['uid']));
else
$dbUser = array();
$types = array();
$changes = getFormChanges($dbUser, $_REQUEST['newUser'], $types);
if ( $_REQUEST['newUser']['Password'] )
$changes['Password'] = 'Password = password('.dbEscape($_REQUEST['newUser']['Password']).')';
else
unset($changes['Password']);
if ( count($changes) ) {
if ( !empty($_REQUEST['uid']) ) {
dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id = ?', array($_REQUEST['uid']));
# If we are updating the logged in user, then update our session user data.
if ( $user and ( $dbUser['Username'] == $user['Username'] ) )
userLogin($dbUser['Username'], $dbUser['Password']);
} else {
dbQuery('INSERT INTO Users SET '.implode(', ', $changes));
}
$refreshParent = true;
}
$view = 'none';
} elseif ( $action == 'state' ) {
if ( !empty($_REQUEST['runState']) ) {
//if ( $cookies ) session_write_close();
packageControl($_REQUEST['runState']);
$refreshParent = true;
}
} elseif ( $action == 'save' ) {
if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) {
$sql = 'SELECT Id,Function,Enabled FROM Monitors ORDER BY Id';
$definitions = array();
foreach( dbFetchAll($sql) as $monitor ) {
$definitions[] = $monitor['Id'].':'.$monitor['Function'].':'.$monitor['Enabled'];
}
$definition = join(',', $definitions);
if ( $_REQUEST['newState'] )
$_REQUEST['runState'] = $_REQUEST['newState'];
dbQuery('REPLACE INTO States SET Name=?, Definition=?', array($_REQUEST['runState'],$definition));
}
} elseif ( $action == 'delete' ) {
if ( isset($_REQUEST['runState']) )
dbQuery('DELETE FROM States WHERE Name=?', array($_REQUEST['runState']));
if ( isset($_REQUEST['markUids']) ) {
foreach( $_REQUEST['markUids'] as $markUid )
dbQuery('DELETE FROM Users WHERE Id = ?', array($markUid));
if ( $markUid == $user['Id'] )
userLogout();
}
}
} else {
if ( ZM_USER_SELF_EDIT && $action == 'user' ) {
$uid = $user['Id'];
$dbUser = dbFetchOne('SELECT Id, Password, Language FROM Users WHERE Id = ?', NULL, array($uid));
$types = array();
$changes = getFormChanges($dbUser, $_REQUEST['newUser'], $types);
if ( !empty($_REQUEST['newUser']['Password']) )
$changes['Password'] = 'Password = password('.dbEscape($_REQUEST['newUser']['Password']).')';
else
unset($changes['Password']);
if ( count($changes) ) {
dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id=?', array($uid));
$refreshParent = true;
}
$view = 'none';
}
}
if ( $action == 'reset' ) {
session_start();
$_SESSION['zmEventResetTime'] = strftime(STRF_FMT_DATETIME_DB);
setcookie('zmEventResetTime', $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10);
session_write_close();
}
?>

View File

@ -202,6 +202,9 @@ if ( $action == 'save' ) {
} // end foreach zone } // end foreach zone
} // end if rotation or just size change } // end if rotation or just size change
} // end if changes in width or height } // end if changes in width or height
} else {
global $error_message;
$error_message = dbError();
} // end if successful save } // end if successful save
$restart = true; $restart = true;
} else { // new monitor } else { // new monitor

View File

@ -112,10 +112,10 @@ function dbLog($sql, $update=false) {
function dbError($sql) { function dbError($sql) {
global $dbConn; global $dbConn;
$error = $dbConn->errorInfo(); $error = $dbConn->errorInfo();
if ( ! $error[0] ) if ( !$error[0] )
return ''; return '';
$message = "SQL-ERR '".implode("\n",$dbConn->errorInfo())."', statement was '".$sql."'"; $message = "SQL-ERR '".implode("\n", $dbConn->errorInfo())."', statement was '".$sql."'";
ZM\Error($message); ZM\Error($message);
return $message; return $message;
} }

View File

@ -230,6 +230,7 @@ if (
( $request != 'control' ) && ( $request != 'control' ) &&
//( $view != 'frames' ) && // big html can overflow ob //( $view != 'frames' ) && // big html can overflow ob
( $view != 'archive' ) // returns data ( $view != 'archive' ) // returns data
&& ( (!isset($_SERVER['CONTENT_TYPE']) or ($_SERVER['CONTENT_TYPE'] != 'application/csp-report')) )
) { ) {
require_once('includes/csrf/csrf-magic.php'); require_once('includes/csrf/csrf-magic.php');
#ZM\Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); #ZM\Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\"");

View File

@ -295,50 +295,50 @@ function getCollapsedNavBarHTML($running, $user, $bandwidth_options, $view, $ski
?> ?>
<div class="fixed-top container-fluid p-0"> <div class="fixed-top container-fluid p-0">
<nav class="navbar navbar-dark bg-dark px-1 flex-nowrap"> <nav class="navbar navbar-dark bg-dark px-1 flex-nowrap">
<div class="navbar-brand align-self-start px-0"> <div class="navbar-brand align-self-start px-0">
<?php echo getNavBrandHTML() ?> <?php echo getNavBrandHTML() ?>
</div> </div>
<nav class="navbar navbar-expand-md align-self-start px-0"> <nav class="navbar navbar-expand-md align-self-start px-0">
<?php <?php
// *** Build the statistics shown on the navigation bar *** // *** Build the statistics shown on the navigation bar ***
if ( (!ZM_OPT_USE_AUTH) or $user ) { if ( (!ZM_OPT_USE_AUTH) or $user ) {
?> ?>
<div id="reload" class="collapse navbar-collapse px-0"> <div id="reload" class="collapse navbar-collapse px-0">
<ul id="Version" class="pr-2 navbar-nav"> <ul id="Version" class="pr-2 navbar-nav">
<?php echo getZMVersionHTML() ?> <?php echo getZMVersionHTML() ?>
</ul> </ul>
<ul id="Bandwidth" class="px-2 navbar-nav"> <ul id="Bandwidth" class="px-2 navbar-nav">
<?php echo getBandwidthHTML($bandwidth_options, $user) ?> <?php echo getBandwidthHTML($bandwidth_options, $user) ?>
</ul> </ul>
<ul class="nav navbar-nav list-group px-0"> <ul class="nav navbar-nav list-group px-0">
<?php <?php
echo getSysLoadHTML(); echo getSysLoadHTML();
echo getDbConHTML(); echo getDbConHTML();
echo getStorageHTML(); echo getStorageHTML();
echo getShmHTML(); echo getShmHTML();
echo getLogIconHTML(); echo getLogIconHTML();
?> ?>
</ul> </ul>
</div> </div>
<?php <?php
} // end if (!ZM_OPT_USE_AUTH) or $user ) } // end if (!ZM_OPT_USE_AUTH) or $user )
?> ?>
</nav> </nav>
<ul class="list-group list-group-horizontal ml-auto"> <ul class="list-group list-group-horizontal ml-auto">
<?php <?php
echo getAccountCircleHTML($skin, $user); echo getAccountCircleHTML($skin, $user);
echo getStatusBtnHTML($status); echo getStatusBtnHTML($status);
?> ?>
</ul> </ul>
<!-- the Navigation Bar Hamburger Button --> <!-- the Navigation Bar Hamburger Button -->
<?php if ( (!ZM_OPT_USE_AUTH) or $user ) { ?> <?php if ( (!ZM_OPT_USE_AUTH) or $user ) { ?>
@ -348,30 +348,30 @@ function getCollapsedNavBarHTML($running, $user, $bandwidth_options, $view, $ski
</button> </button>
<?php } ?> <?php } ?>
<div style="background-color:#485460" class="dropdown-menu dropdown-menu-right px-3" id="main-header-nav"> <div style="background-color:#485460" class="dropdown-menu dropdown-menu-right px-3" id="main-header-nav">
<?php <?php
if ( $user and $user['Username'] ) { if ( $user and $user['Username'] ) {
echo '<ul class="navbar-nav">'; echo '<ul class="navbar-nav">';
echo getConsoleHTML(); echo getConsoleHTML();
echo getOptionsHTML(); echo getOptionsHTML();
echo getLogHTML(); echo getLogHTML();
echo getDevicesHTML(); echo getDevicesHTML();
echo getGroupsHTML($view); echo getGroupsHTML($view);
echo getFilterHTML($view); echo getFilterHTML($view);
echo getCycleHTML($view); echo getCycleHTML($view);
echo getMontageHTML($view); echo getMontageHTML($view);
echo getMontageReviewHTML($view); echo getMontageReviewHTML($view);
echo getRprtEvntAuditHTML($view); echo getRprtEvntAuditHTML($view);
echo '</ul>'; echo '</ul>';
} }
?> ?>
</div> </div>
</nav><!-- End First Navbar --> </nav><!-- End First Navbar -->
<nav class="navbar navbar-expand-md bg-dark justify-content-center p-0"> <nav class="navbar navbar-expand-md bg-dark justify-content-center p-0">
<?php echo getConsoleBannerHTML() ?> <?php echo getConsoleBannerHTML() ?>
</nav><!-- End Second Navbar --> </nav><!-- End Second Navbar -->
</div> </div>
<?php <?php

View File

@ -913,3 +913,7 @@ function initThumbAnimation() {
}); });
} }
} }
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

Some files were not shown because too many files have changed in this diff Show More