Merge branch 'storageareas' of github.com:ConnorTechnology/ZoneMinder into storageareas

pull/1857/head
Isaac Connor 2017-04-12 16:20:09 -04:00
commit 9027c0ee06
48 changed files with 2413 additions and 2227 deletions

View File

@ -2,11 +2,12 @@
# Created by mastertheknife (Kfir Itzhak)
# For more information and installation, see the INSTALL file
#
cmake_minimum_required (VERSION 2.8.7)
cmake_minimum_required (VERSION 3.1.0)
project (zoneminder)
file (STRINGS "version" zoneminder_VERSION)
# make API version a minor of ZM version
set(zoneminder_API_VERSION "${zoneminder_VERSION}.1")
set (CMAKE_CXX_STANDARD 11)
# Make sure the submodules are there
if( NOT EXISTS "${CMAKE_SOURCE_DIR}/web/api/app/Plugin/Crud/Lib/CrudControllerTrait.php" )
@ -564,21 +565,21 @@ if(NOT ZM_NO_FFMPEG)
endif(SWSCALE_LIBRARIES)
# rescale (using find_library and find_path)
find_library(SWRESAMPLE_LIBRARIES swresample)
if(SWRESAMPLE_LIBRARIES)
set(HAVE_LIBSWRESAMPLE 1)
list(APPEND ZM_BIN_LIBS "${SWRESAMPLE_LIBRARIES}")
find_path(SWRESAMPLE_INCLUDE_DIR "libswresample/swresample.h" /usr/include/ffmpeg)
if(SWRESAMPLE_INCLUDE_DIR)
include_directories("${SWRESAMPLE_INCLUDE_DIR}")
set(CMAKE_REQUIRED_INCLUDES "${SWRESAMPLE_INCLUDE_DIR}")
endif(SWRESAMPLE_INCLUDE_DIR)
mark_as_advanced(FORCE SWRESAMPLE_LIBRARIES SWRESAMPLE_INCLUDE_DIR)
check_include_file("libswresample/swresample.h" HAVE_LIBSWRESAMPLE_SWRESAMPLE_H)
set(optlibsfound "${optlibsfound} SWResample")
else(SWRESAMPLE_LIBRARIES)
set(optlibsnotfound "${optlibsnotfound} SWResample")
endif(SWRESAMPLE_LIBRARIES)
find_library(AVRESAMPLE_LIBRARIES avresample)
if(AVRESAMPLE_LIBRARIES)
set(HAVE_LIBAVRESAMPLE 1)
list(APPEND ZM_BIN_LIBS "${AVRESAMPLE_LIBRARIES}")
find_path(AVRESAMPLE_INCLUDE_DIR "libavresample/avresample.h" /usr/include/ffmpeg)
if(AVRESAMPLE_INCLUDE_DIR)
include_directories("${AVRESAMPLE_INCLUDE_DIR}")
set(CMAKE_REQUIRED_INCLUDES "${AVRESAMPLE_INCLUDE_DIR}")
endif(AVRESAMPLE_INCLUDE_DIR)
mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR)
check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H)
set(optlibsfound "${optlibsfound} AVResample")
else(AVRESAMPLE_LIBRARIES)
set(optlibsnotfound "${optlibsnotfound} AVResample")
endif(AVRESAMPLE_LIBRARIES)
# Find the path to the ffmpeg executable
find_program(FFMPEG_EXECUTABLE

View File

@ -43,7 +43,7 @@ ADD utils/docker/start.sh /tmp/start.sh
RUN chown -R www-data:www-data /usr/local/share/zoneminder/
# Adding apache virtual hosts file
ADD utils/docker/apache-vhost /etc/apache2/sites-available/000-default.conf
RUN cp misc/apache.conf /etc/apache2/sites-available/000-default.conf
ADD utils/docker/phpdate.ini /etc/php5/apache2/conf.d/25-phpdate.ini
# Expose http ports

View File

@ -46,13 +46,16 @@ else("${unzip_jsc}" STREQUAL "")
endif("${unzip_jsc}" STREQUAL "")
# Create several empty folders
file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp)
file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp logs cache models persistent views)
# Install the empty folders
install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY zoneminder DESTINATION /var/run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY logs cache DESTINATION /var/lib/zoneminder/temp DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY models persistent views DESTINATION /var/lib/zoneminder/temp/cache DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
# Create symlinks
install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/events \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/events\")")

View File

@ -132,8 +132,8 @@ too much degradation of performance.
%prep
%autosetup -n ZoneMinder-%{version}
%autosetup -a 1 -n ZoneMinder-%{version}
rmdir ./web/api/app/Plugin/Crud
mv -f crud-%{crud_version} ./web/api/app/Plugin/Crud
%{__rm} -rf ./web/api/app/Plugin/Crud
%{__mv} -f crud-%{crud_version} ./web/api/app/Plugin/Crud
# Change the following default values
./utils/zmeditconfigdata.sh ZM_PATH_ZMS /cgi-bin-zm/nph-zms
@ -333,7 +333,14 @@ rm -rf %{_docdir}/%{name}-%{version}
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %ghost %{_localstatedir}/run/zoneminder
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/run/zoneminder
# cakephp requires its cache folders to pre-exist
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/logs
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/cache
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/cache/views
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/cache/models
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp/cache/persistent
%changelog
* Thu Mar 30 2017 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.30.2-2

View File

@ -1,2 +1,6 @@
d /var/run/zm 0755 www-data www-data
d /tmp/zm 0755 www-data www-data
d /var/tmp/zm 0755 www-data www-data
d /var/tmp/cache 0755 www-data www-data
d /var/tmp/cache/models 0755 www-data www-data
d /var/tmp/cache/persistent 0755 www-data www-data

View File

@ -13,12 +13,12 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa
,libgcrypt-dev
,libcurl4-gnutls-dev
,libgnutls-openssl-dev
,libjpeg-dev
, libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev
,libmysqlclient-dev
,libpcre3-dev
,libpolkit-gobject-1-dev
,libv4l-dev (>= 0.8.3) [!hurd-any]
,libvlc-dev
,libvlc-dev,
,libdate-manip-perl
,libdbd-mysql-perl
,libphp-serialization-perl
@ -65,6 +65,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,policykit-1
,rsyslog | system-log-daemon
,zip
,libprce3
Recommends: ${misc:Recommends}
,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm
,mysql-server | virtual-mysql-server

View File

@ -1 +1 @@
1.0
3.0 (native)

View File

@ -169,8 +169,6 @@ Now clone the ZoneMinder git repository:
cd
git clone https://github.com/ZoneMinder/ZoneMinder
cd ZoneMinder
git submodule init
git submodule update
This will create a sub-folder called ZoneMinder, which will contain the latest development.
@ -180,21 +178,22 @@ We want to turn this into a tarball, but first we need to figure out what to nam
ls ~/rpmbuild/SOURCES
The tarball from the previsouly installed SRPM should be there. This is the name we will use. For this example, the name is ZoneMinder-1.28.1.tar.gz. From one folder above the local ZoneMinder git repository, execute the following:
The tarball from the previsouly installed SRPM should be there. This is the name we will use. For this example, the name is ZoneMinder-1.28.1.tar.gz. From the local ZoneMinder git repository, execute the following:
::
mv ZoneMinder ZoneMinder-1.28.1
tar -cvzf ~/rpmbuild/SOURCES/ZoneMinder-1.28.1.tar.gz ZoneMinder-1.28.1/*
git archive --prefix=ZoneMinder-1.28.1/ -o zoneminder-1.28.1.tar.gz HEAD
mv zoneminder-1.28.1.tar.gz ~/rpmbuld/SOURCES/
The trailing "/\*" leaves off the hidden dot "." file and folders from the git repo, which is what we want.
Note that we are overwriting the original tarball. If you wish to keep the original tarball then create a copy prior to creating the new tarball.
Now build a new src.rpm:
From the root of the local ZoneMinder git repo, execute the following:
::
rpmbuild -bs --nodeps ~/rpmbuild/SPECS/zoneminder.el7.spec
rpmbuild -bs --nodeps distros/redhat/zoneminder.spec
Notice we used the rpm specfile that is part of the latest master branch you just downloaded, rather than the one that may be in your ~/rpmbbuild/SOURCES folder.
This step will overwrite the SRPM you originally downloaded, so you may want to back it up prior to completing this step. Note that the name of the specfile will vary slightly depending on the target distro.

View File

@ -5,13 +5,22 @@
# Some values may need to manually adjusted to suit your setup
#
<VirtualHost *:80>
ServerName @WEB_HOST@
ServerAdmin webmaster@localhost
DocumentRoot "@WEB_PREFIX@"
Alias /zm/ "@WEB_PREFIX@/"
<Directory "@WEB_PREFIX@">
Options -Indexes +FollowSymLinks
AllowOverride All
<IfModule mod_authz_core.c>
# Apache 2.4
Require all granted
</IfModule>
<IfModule !mod_authz_core.c>
# Apache 2.2
Order deny,allow
Allow from all
</IfModule>
</Directory>
ScriptAlias /cgi-bin "@CGI_PREFIX@"

View File

@ -104,8 +104,10 @@ sub getCmdFormat {
my $suffix = "";
my $command = $prefix.$null_command.$suffix;
Debug( "Testing \"$command\"\n" );
my $output = qx($command);
my $output = qx($command 2>&1);
my $status = $? >> 8;
$output //= $!;
if ( !$status ) {
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" );
return( $prefix, $suffix );
@ -117,8 +119,10 @@ sub getCmdFormat {
$suffix = "'";
$command = $prefix.$null_command.$suffix;
Debug( "Testing \"$command\"\n" );
my $output = qx($command);
my $output = qx($command 2>&1);
my $status = $? >> 8;
$output //= $!;
if ( !$status ) {
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" );
return( $prefix, $suffix );
@ -130,8 +134,10 @@ sub getCmdFormat {
$suffix = "'";
$command = $prefix.$null_command.$suffix;
Debug( "Testing \"$command\"\n" );
$output = qx($command);
$output = qx($command 2>&1);
$status = $? >> 8;
$output //= $!;
if ( !$status ) {
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" );
return( $prefix, $suffix );

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY)
# Group together all the source files that are used by all the binaries (zmc, zma, 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_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.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_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)
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_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_monitorstream.cpp zm_ffmpeg.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_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_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.
add_library(zm STATIC ${ZM_BIN_SRC_FILES})

View File

@ -20,7 +20,7 @@
#include "zm.h"
#include "zm_camera.h"
Camera::Camera( unsigned int p_monitor_id, SourceType p_type, int p_width, 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::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 ) :
monitor_id( p_monitor_id ),
type( p_type ),
width( p_width),

View File

@ -55,7 +55,7 @@ protected:
bool record_audio;
public:
Camera( unsigned int p_monitor_id, SourceType p_type, int p_width, 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();
unsigned int getId() const { return( monitor_id ); }
@ -88,7 +88,7 @@ public:
virtual int PreCapture()=0;
virtual int Capture( Image &image )=0;
virtual int PostCapture()=0;
virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory)=0;
virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) = 0;
};
#endif // ZM_CAMERA_H

View File

@ -33,7 +33,7 @@ const char* content_type_match = "Content-Type:";
size_t content_length_match_len;
size_t content_type_match_len;
cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, 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 ) :
cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET )
{
@ -305,7 +305,7 @@ int cURLCamera::PostCapture() {
return( 0 );
}
int cURLCamera::CaptureAndRecord( Image &image, bool recording, char* event_directory ) {
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 );

View File

@ -64,7 +64,7 @@ protected:
pthread_cond_t request_complete_cond;
public:
cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, 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 );
cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
~cURLCamera();
const std::string &Path() const { return( mPath ); }
@ -78,7 +78,7 @@ public:
int PreCapture();
int Capture( Image &image );
int PostCapture();
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
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 header_callback(void *buffer, size_t size, size_t nmemb, void *userdata);

View File

@ -55,6 +55,7 @@ char Event::capture_file_format[PATH_MAX];
char Event::analyse_file_format[PATH_MAX];
char Event::general_file_format[PATH_MAX];
char Event::video_file_format[PATH_MAX];
constexpr const char * Event::frame_type_names[3];
int Event::pre_alarm_count = 0;
@ -629,16 +630,16 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
struct DeltaTimeval delta_time;
DELTA_TIMEVAL( delta_time, timestamp, start_time, DT_PREC_2 );
const char *frame_type = score>0?"Alarm":(score<0?"Bulk":"Normal");
FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL);
if ( score < 0 )
score = 0;
bool db_frame = (strcmp(frame_type,"Bulk") != 0) || ((frames%config.bulk_frame_interval)==0) || !frames;
bool db_frame = ( frame_type != BULK ) || ((frames%config.bulk_frame_interval)==0) || !frames;
if ( db_frame ) {
Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, frame_type );
Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, Event::frame_type_names[frame_type] );
static char sql[ZM_SQL_MED_BUFSIZ];
snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) values ( %d, %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", id, frames, frame_type, timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score );
snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) values ( %d, %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", id, frames, frame_type_names[frame_type], timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score );
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't insert frame: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
@ -646,7 +647,7 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
last_db_frame = frames;
// We are writing a Bulk frame
if ( !strcmp( frame_type,"Bulk") ) {
if ( frame_type == BULK ) {
snprintf( sql, sizeof(sql), "update Events set Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id );
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't update event: %s", mysql_error( &dbconn ) );
@ -658,7 +659,7 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
end_time = timestamp;
// We are writing an Alarm frame
if ( !strcmp( frame_type,"Alarm") ) {
if ( frame_type == ALARM ) {
alarm_frames++;
tot_score += score;

View File

@ -65,7 +65,8 @@ class Event {
typedef std::map<std::string,StringSet> StringSetMap;
protected:
typedef enum { NORMAL, BULK, ALARM } FrameType;
typedef enum { NORMAL=0, BULK, ALARM } FrameType;
static constexpr const char * frame_type_names[3] = { "Normal", "Bulk", "Alarm" };
struct PreAlarmData {
Image *image;
@ -130,6 +131,7 @@ class Event {
const struct timeval &StartTime() const { return( start_time ); }
const struct timeval &EndTime() const { return( end_time ); }
struct timeval &StartTime() { return( start_time ); }
struct timeval &EndTime() { return( end_time ); }
bool SendFrameImage( const Image *image, bool alarm_frame=false );

View File

@ -62,6 +62,7 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri
mOpenStart = 0;
mReopenThread = 0;
videoStore = NULL;
video_last_pts = 0;
#if HAVE_LIBSWSCALE
mConvertContext = NULL;
@ -223,8 +224,7 @@ int FfmpegCamera::Capture( Image &image )
return (0);
} // FfmpegCamera::Capture
int FfmpegCamera::PostCapture()
{
int FfmpegCamera::PostCapture() {
// Nothing to do here
return( 0 );
}
@ -300,7 +300,7 @@ int FfmpegCamera::OpenFfmpeg() {
#endif
Fatal( "Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno) );
startTime=av_gettime();//FIXME here or after find_Stream_info
startTime = av_gettime();//FIXME here or after find_Stream_info
Debug ( 1, "Got stream info" );
// Find first video stream present
@ -332,7 +332,7 @@ int FfmpegCamera::OpenFfmpeg() {
Debug(2, "Have another audio stream." );
}
}
}
} // end foreach stream
if ( mVideoStreamId == -1 )
Fatal( "Unable to locate video stream in %s", mPath.c_str() );
if ( mAudioStreamId == -1 )
@ -430,7 +430,7 @@ int FfmpegCamera::OpenFfmpeg() {
Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
#endif // HAVE_LIBSWSCALE
if ( mVideoCodecContext->width != width || mVideoCodecContext->height != height ) {
if ( (unsigned int)mVideoCodecContext->width != width || (unsigned int)mVideoCodecContext->height != height ) {
Warning( "Monitor dimensions are %dx%d but camera is sending %dx%d", width, height, mVideoCodecContext->width, mVideoCodecContext->height );
}
@ -533,7 +533,7 @@ void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){
}
//Function to handle capture and store
int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_file ) {
int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event_file ) {
if (!mCanCapture){
return -1;
}
@ -553,19 +553,15 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi
mReopenThread = 0;
}
if (mVideoCodecContext->codec_id != AV_CODEC_ID_H264) {
Error( "Input stream is not h264. The stored event file may not be viewable in browser." );
}
int frameComplete = false;
while ( !frameComplete ) {
Debug(5, "Before av_init_packe");
while ( ! frameComplete ) {
av_init_packet( &packet );
Debug(5, "Before av_read_frame");
ret = av_read_frame( mFormatContext, &packet );
Debug(5, "After av_read_frame (%d)", ret );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
if (
@ -590,7 +586,7 @@ Debug(5, "After av_read_frame (%d)", ret );
);
//Video recording
if ( recording ) {
if ( recording.tv_sec ) {
// The directory we are recording to is no longer tied to the current event.
// Need to re-init the videostore with the correct directory and start recording again
// for efficiency's sake, we should test for keyframe before we test for directory change...
@ -611,9 +607,9 @@ Debug(5, "After av_read_frame (%d)", ret );
delete videoStore;
videoStore = NULL;
}
} // end if end of recording
if ( ( ! videoStore )&& key_frame && ( packet.stream_index == mVideoStreamId ) ) {
if ( ( ! videoStore ) && key_frame && ( packet.stream_index == mVideoStreamId ) ) {
//Instantiate the video storage module
if (record_audio) {
@ -644,25 +640,30 @@ Debug(5, "After av_read_frame (%d)", ret );
strcpy(oldDirectory, event_file);
// 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;
AVPacket *queued_packet;
ZMPacket *queued_packet;
packetqueue.clear_unwanted_packets( &recording, mVideoStreamId );
while ( ( queued_packet = packetqueue.popPacket() ) ) {
AVPacket *avp = queued_packet->av_packet();
packet_count += 1;
//Write the packet to our video store
Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", queued_packet->stream_index, queued_packet->flags & AV_PKT_FLAG_KEY, packetqueue.size() );
if ( queued_packet->stream_index == mVideoStreamId ) {
ret = videoStore->writeVideoFramePacket( queued_packet );
} else if ( queued_packet->stream_index == mAudioStreamId ) {
ret = videoStore->writeAudioFramePacket( queued_packet );
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 );
} else if ( avp->stream_index == mAudioStreamId ) {
ret = videoStore->writeAudioFramePacket( avp );
} else {
Warning("Unknown stream id in queued packet (%d)", queued_packet->stream_index );
Warning("Unknown stream id in queued packet (%d)", avp->stream_index );
ret = -1;
}
if ( ret < 0 ) {
//Less than zero and we skipped a frame
}
zm_av_packet_unref( queued_packet );
av_free( queued_packet );
delete queued_packet;
} // end while packets in the packetqueue
Debug(2, "Wrote %d queued packets", packet_count );
} // end if ! wasRecording
@ -680,7 +681,7 @@ Debug(5, "After av_read_frame (%d)", ret );
if ( packet.stream_index == mVideoStreamId) {
if ( key_frame ) {
Debug(3, "Clearing queue");
packetqueue.clearQueue();
packetqueue.clearQueue( monitor->GetPreEventCount(), mVideoStreamId );
}
#if 0
// Not sure this is valid. While a camera will PROBABLY always have an increasing pts... it doesn't have to.
@ -712,6 +713,24 @@ else if ( packet.pts && video_last_pts > packet.pts ) {
}
}
Debug(4, "about to decode video" );
#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0)
ret = avcodec_send_packet( mVideoCodecContext, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
ret = avcodec_receive_frame( mVideoCodecContext, mRawFrame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
frameComplete = 1;
# else
ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
@ -719,6 +738,7 @@ else if ( packet.pts && video_last_pts > packet.pts ) {
zm_av_packet_unref( &packet );
continue;
}
#endif
Debug( 4, "Decoded video packet at frame %d", frameCount );
@ -734,7 +754,9 @@ else if ( packet.pts && video_last_pts > packet.pts ) {
zm_av_packet_unref( &packet );
return (-1);
}
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
// avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
av_image_fill_arrays(mFrame->data, mFrame->linesize, directbuffer, imagePixFormat, width, height, 1);
if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize,
0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) {
@ -763,8 +785,8 @@ else if ( packet.pts && video_last_pts > packet.pts ) {
}
}
} else {
#if LIBAVUTIL_VERSION_CHECK(54, 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]->codec->codec_type) );
#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

View File

@ -101,7 +101,7 @@ class FfmpegCamera : public Camera
int PrimeCapture();
int PreCapture();
int Capture( Image &image );
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
int PostCapture();
};

View File

@ -47,7 +47,7 @@ public:
int PreCapture();
int Capture( Image &image );
int PostCapture();
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);};
};
#endif // ZM_FILE_CAMERA_H

View File

@ -212,7 +212,7 @@ int LibvlcCamera::Capture( Image &image )
}
// Should not return -1 as cancels capture. Always wait for image if available.
int LibvlcCamera::CaptureAndRecord(Image &image, bool recording, char* event_directory)
int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory)
{
while(!mLibvlcData.newImage.getValueImmediate())
mLibvlcData.newImage.getUpdatedValue(1);

View File

@ -70,7 +70,7 @@ public:
int PrimeCapture();
int PreCapture();
int Capture( Image &image );
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
int PostCapture();
};

View File

@ -366,7 +366,7 @@ LocalCamera::LocalCamera(
palette = V4L2_PIX_FMT_YUYV;
} else {
if(capture) {
Info("Selected capture palette: %s (%c%c%c%c)", palette_desc, palette&0xff, (palette>>8)&0xff, (palette>>16)&0xff, (palette>>24)&0xff);
Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", palette_desc, (palette>>24)&0xff, (palette>>16)&0xff, (palette>>8)&0xff, (palette)&0xff);
}
}
}
@ -427,7 +427,8 @@ LocalCamera::LocalCamera(
} else {
if( capture )
#if HAVE_LIBSWSCALE
Info("No direct match for the selected palette (%c%c%c%c) and target colorspace (%d). Format conversion is required, performance penalty expected", (capturePixFormat)&0xff,((capturePixFormat>>8)&0xff),((capturePixFormat>>16)&0xff),((capturePixFormat>>24)&0xff), colours );
Info("No direct match for the selected palette (0x%02hhx%02hhx%02hhx%02hhx) and target colorspace (%02u). Format conversion is required, performance penalty expected",
(capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff), colours);
#else
Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected");
#endif
@ -445,16 +446,18 @@ LocalCamera::LocalCamera(
subpixelorder = ZM_SUBPIX_ORDER_NONE;
imagePixFormat = AV_PIX_FMT_GRAY8;
} else {
Panic("Unexpected colours: %d",colours);
Panic("Unexpected colours: %u",colours);
}
if( capture ) {
#if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0)
if(!sws_isSupportedInput(capturePixFormat)) {
Error("swscale does not support the used capture format: %c%c%c%c",(capturePixFormat)&0xff,((capturePixFormat>>8)&0xff),((capturePixFormat>>16)&0xff),((capturePixFormat>>24)&0xff));
Error("swscale does not support the used capture format: 0x%02hhx%02hhx%02hhx%02hhx",
(capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff));
conversion_type = 2; /* Try ZM format conversions */
}
if(!sws_isSupportedOutput(imagePixFormat)) {
Error("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff));
Error("swscale does not support the target format: 0x%02hhx%02hhx%02hhx%02hhx",
(imagePixFormat>>24)&0xff,((imagePixFormat>>16)&0xff),((imagePixFormat>>8)&0xff),((imagePixFormat)&0xff));
conversion_type = 2; /* Try ZM format conversions */
}
#endif
@ -563,7 +566,7 @@ LocalCamera::LocalCamera(
subpixelorder = ZM_SUBPIX_ORDER_NONE;
imagePixFormat = AV_PIX_FMT_GRAY8;
} else {
Panic("Unexpected colours: %d",colours);
Panic("Unexpected colours: %u",colours);
}
if( capture ) {
if(!sws_isSupportedInput(capturePixFormat)) {
@ -631,7 +634,7 @@ LocalCamera::LocalCamera(
#endif // ZM_HAS_V4L1
last_camera = this;
Debug(3,"Selected subpixelorder: %d",subpixelorder);
Debug(3,"Selected subpixelorder: %u",subpixelorder);
#if HAVE_LIBSWSCALE
/* Initialize swscale stuff */
@ -650,7 +653,7 @@ LocalCamera::LocalCamera(
int pSize = avpicture_get_size( imagePixFormat, width, height );
#endif
if( (unsigned int)pSize != imagesize) {
Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize);
Fatal("Image size mismatch. Required: %d Available: %u",pSize,imagesize);
}
imgConversionContext = sws_getContext(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
@ -871,7 +874,7 @@ void LocalCamera::Initialise()
v4l2_data.buffers[i].start = mmap( NULL, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset );
if ( v4l2_data.buffers[i].start == MAP_FAILED )
Fatal( "Can't map video buffer %d (%d bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno );
Fatal( "Can't map video buffer %u (%u bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno );
#if HAVE_LIBSWSCALE
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
@ -1194,9 +1197,10 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) {
/* Got a format. Copy it to the array */
strcpy(fmt_desc[nIndex], (const char*)(fmtinfo.description));
fmt_fcc[nIndex] = fmtinfo.pixelformat;
Debug(6, "Got format: %s (%c%c%c%c) at index %d",fmt_desc[nIndex],fmt_fcc[nIndex]&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>24)&0xff ,nIndex);
Debug(6, "Got format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %d",
fmt_desc[nIndex], (fmt_fcc[nIndex]>>24)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex])&0xff ,nIndex);
/* Proceed to the next index */
memset(&fmtinfo, 0, sizeof(fmtinfo));
fmtinfo.index = ++nIndex;
@ -1223,12 +1227,14 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) {
for( unsigned int i=0; i < n_preferedformats && nIndexUsed < 0; i++ ) {
for( unsigned int j=0; j < nIndex; j++ ) {
if( preferedformats[i] == fmt_fcc[j] ) {
Debug(6, "Choosing format: %s (%c%c%c%c) at index %d",fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j);
Debug(6, "Choosing format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u",
fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j);
/* Found a format! */
nIndexUsed = j;
break;
} else {
Debug(6, "No match for format: %s (%c%c%c%c) at index %d",fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j);
Debug(6, "No match for format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u",
fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j);
}
}
}
@ -1403,9 +1409,11 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers
}
}
if ( verbose )
sprintf( output+strlen(output), " %s (%c%c%c%c)\n", format.description, format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff );
sprintf( output+strlen(output), " %s (0x%02hhx%02hhx%02hhx%02hhx)\n",
format.description, (format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, format.pixelformat&0xff);
else
sprintf( output+strlen(output), "%c%c%c%c/", format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff );
sprintf( output+strlen(output), "0x%02hhx%02hhx%02hhx%02hhx/",
(format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat)&0xff);
}
while ( formatIndex++ >= 0 );
if ( !verbose )

View File

@ -162,7 +162,7 @@ public:
int PreCapture();
int Capture( Image &image );
int PostCapture();
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);};
static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose );
};

View File

@ -425,7 +425,7 @@ Monitor::Monitor(
trigger_data->trigger_text[0] = 0;
trigger_data->trigger_showtext[0] = 0;
shared_data->valid = true;
video_store_data->recording = false;
video_store_data->recording = (struct timeval){0};
snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "nothing");
video_store_data->size = sizeof(VideoStoreData);
//video_store_data->frameNumber = 0;
@ -1358,6 +1358,7 @@ bool Monitor::Analyse() {
if ( event ) {
//TODO: We shouldn't have to do this every time. Not sure why it clears itself if this isn't here??
snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile());
Debug( 3, "Detected new event at (%d.%d)", timestamp->tv_sec,timestamp->tv_usec );
if ( section_length ) {
int section_mod = timestamp->tv_sec%section_length;
@ -1388,8 +1389,8 @@ bool Monitor::Analyse() {
shared_data->last_event = event->Id();
//set up video store data
snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile());
video_store_data->recording = true;
video_store_data->recording = event->StartTime();
Info( "%s: %03d - Opening new event %d, section start", name, image_count, event->Id() );
/* To prevent cancelling out an existing alert\prealarm\alarm state */
@ -1491,7 +1492,7 @@ bool Monitor::Analyse() {
shared_data->last_event = event->Id();
//set up video store data
snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile());
video_store_data->recording = true;
video_store_data->recording = event->StartTime();
Info( "%s: %03d - Opening new event %d, alarm start", name, image_count, event->Id() );
@ -1600,9 +1601,11 @@ bool Monitor::Analyse() {
event->updateNotes( noteSetMap );
} else if ( state == TAPE ) {
//Video Storage: activate only for supported cameras. Event::AddFrame knows whether or not we are recording video and saves frames accordingly
if((GetOptVideoWriter() == 2) && camera->SupportsNativeVideo()) {
video_store_data->recording = true;
}
//if((GetOptVideoWriter() == 2) && camera->SupportsNativeVideo()) {
// I don't think this is required, and causes problems, as the event file hasn't been setup yet.
//Warning("In state TAPE,
//video_store_data->recording = event->StartTime();
//}
if ( !(image_count%(frame_skip+1)) ) {
if ( config.bulk_frame_interval > 1 ) {
event->AddFrame( snap_image, *timestamp, (event->Frames()<pre_event_count?0:-1) );
@ -3009,7 +3012,7 @@ bool Monitor::closeEvent() {
gettimeofday( &(event->EndTime()), NULL );
}
delete event;
video_store_data->recording = false;
video_store_data->recording = (struct timeval){0};
event = 0;
return( true );
}
@ -3032,7 +3035,7 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z
ref_image.WriteJpeg( diag_path );
}
ref_image.Delta( comp_image, &delta_image);
ref_image.Delta( comp_image, &delta_image );
if ( config.record_diag_images ) {
static char diag_path[PATH_MAX] = "";
@ -3120,6 +3123,7 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z
if ( alarm ) {
for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) {
Zone *zone = zones[n_zone];
// Wasn't this zone already checked above?
if ( !zone->IsInclusive() ) {
continue;
}
@ -3154,7 +3158,7 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z
zoneSet.insert( zone->Label() );
}
}
} // end if alaram or not
} // end if alarm or not
}
if ( top_score > 0 ) {
@ -3236,782 +3240,6 @@ bool Monitor::DumpSettings( char *output, bool verbose ) {
return( true );
} // bool Monitor::DumpSettings( char *output, bool verbose )
bool MonitorStream::checkSwapPath( const char *path, bool create_path ) {
uid_t uid = getuid();
gid_t gid = getgid();
struct stat stat_buf;
if ( stat( path, &stat_buf ) < 0 ) {
if ( create_path && errno == ENOENT ) {
Debug( 3, "Swap path '%s' missing, creating", path );
if ( mkdir( path, 0755 ) ) {
Error( "Can't mkdir %s: %s", path, strerror(errno));
return( false );
}
if ( stat( path, &stat_buf ) < 0 ) {
Error( "Can't stat '%s': %s", path, strerror(errno) );
return( false );
}
} else {
Error( "Can't stat '%s': %s", path, strerror(errno) );
return( false );
}
}
if ( !S_ISDIR(stat_buf.st_mode) ) {
Error( "Swap image path '%s' is not a directory", path );
return( false );
}
mode_t mask = 0;
if ( uid == stat_buf.st_uid ) {
// If we are the owner
mask = 00700;
} else if ( gid == stat_buf.st_gid ) {
// If we are in the owner group
mask = 00070;
} else {
// We are neither the owner nor in the group
mask = 00007;
}
if ( (stat_buf.st_mode & mask) != mask ) {
Error( "Insufficient permissions on swap image path '%s'", path );
return( false );
}
return( true );
}
void MonitorStream::processCommand( const CmdMsg *msg ) {
Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] );
// Check for incoming command
switch( (MsgCommand)msg->msg_data[0] ) {
case CMD_PAUSE :
{
Debug( 1, "Got PAUSE command" );
// Set paused flag
paused = true;
// Set delayed flag
delayed = true;
last_frame_sent = TV_2_FLOAT( now );
break;
}
case CMD_PLAY :
{
Debug( 1, "Got PLAY command" );
if ( paused ) {
// Clear paused flag
paused = false;
// Set delayed_play flag
delayed = true;
}
replay_rate = ZM_RATE_BASE;
break;
}
case CMD_VARPLAY :
{
Debug( 1, "Got VARPLAY command" );
if ( paused ) {
// Clear paused flag
paused = false;
// Set delayed_play flag
delayed = true;
}
replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768;
break;
}
case CMD_STOP :
{
Debug( 1, "Got STOP command" );
// Clear paused flag
paused = false;
// Clear delayed_play flag
delayed = false;
break;
}
case CMD_FASTFWD :
{
Debug( 1, "Got FAST FWD command" );
if ( paused ) {
// Clear paused flag
paused = false;
// Set delayed_play flag
delayed = true;
}
// Set play rate
switch ( replay_rate )
{
case 2 * ZM_RATE_BASE :
replay_rate = 5 * ZM_RATE_BASE;
break;
case 5 * ZM_RATE_BASE :
replay_rate = 10 * ZM_RATE_BASE;
break;
case 10 * ZM_RATE_BASE :
replay_rate = 25 * ZM_RATE_BASE;
break;
case 25 * ZM_RATE_BASE :
case 50 * ZM_RATE_BASE :
replay_rate = 50 * ZM_RATE_BASE;
break;
default :
replay_rate = 2 * ZM_RATE_BASE;
break;
}
break;
}
case CMD_SLOWFWD :
{
Debug( 1, "Got SLOW FWD command" );
// Set paused flag
paused = true;
// Set delayed flag
delayed = true;
// Set play rate
replay_rate = ZM_RATE_BASE;
// Set step
step = 1;
break;
}
case CMD_SLOWREV :
{
Debug( 1, "Got SLOW REV command" );
// Set paused flag
paused = true;
// Set delayed flag
delayed = true;
// Set play rate
replay_rate = ZM_RATE_BASE;
// Set step
step = -1;
break;
}
case CMD_FASTREV :
{
Debug( 1, "Got FAST REV command" );
if ( paused ) {
// Clear paused flag
paused = false;
// Set delayed_play flag
delayed = true;
}
// Set play rate
switch ( replay_rate ) {
case -2 * ZM_RATE_BASE :
replay_rate = -5 * ZM_RATE_BASE;
break;
case -5 * ZM_RATE_BASE :
replay_rate = -10 * ZM_RATE_BASE;
break;
case -10 * ZM_RATE_BASE :
replay_rate = -25 * ZM_RATE_BASE;
break;
case -25 * ZM_RATE_BASE :
case -50 * ZM_RATE_BASE :
replay_rate = -50 * ZM_RATE_BASE;
break;
default :
replay_rate = -2 * ZM_RATE_BASE;
break;
}
break;
}
case CMD_ZOOMIN :
{
x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
Debug( 1, "Got ZOOM IN command, to %d,%d", x, y );
switch ( zoom ) {
case 100:
zoom = 150;
break;
case 150:
zoom = 200;
break;
case 200:
zoom = 300;
break;
case 300:
zoom = 400;
break;
case 400:
default :
zoom = 500;
break;
}
break;
}
case CMD_ZOOMOUT :
{
Debug( 1, "Got ZOOM OUT command" );
switch ( zoom ) {
case 500:
zoom = 400;
break;
case 400:
zoom = 300;
break;
case 300:
zoom = 200;
break;
case 200:
zoom = 150;
break;
case 150:
default :
zoom = 100;
break;
}
break;
}
case CMD_PAN :
{
x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
Debug( 1, "Got PAN command, to %d,%d", x, y );
break;
}
case CMD_SCALE :
{
scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
Debug( 1, "Got SCALE command, to %d", scale );
break;
}
case CMD_QUIT :
{
Info ("User initiated exit - CMD_QUIT");
break;
}
case CMD_QUERY :
{
Debug( 1, "Got QUERY command, sending STATUS" );
break;
}
default :
{
Error( "Got unexpected command %d", msg->msg_data[0] );
break;
}
}
struct {
int id;
int state;
double fps;
int buffer_level;
int rate;
double delay;
int zoom;
bool delayed;
bool paused;
bool enabled;
bool forced;
} status_data;
status_data.id = monitor->Id();
status_data.fps = monitor->GetFPS();
status_data.state = monitor->shared_data->state;
if ( playback_buffer > 0 )
status_data.buffer_level = (MOD_ADD( (temp_write_index-temp_read_index), 0, temp_image_buffer_count )*100)/temp_image_buffer_count;
else
status_data.buffer_level = 0;
status_data.delayed = delayed;
status_data.paused = paused;
status_data.rate = replay_rate;
status_data.delay = TV_2_FLOAT( now ) - TV_2_FLOAT( last_frame_timestamp );
status_data.zoom = zoom;
//status_data.enabled = monitor->shared_data->active;
status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF;
status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON;
Debug( 2, "L:%d, D:%d, P:%d, R:%d, d:%.3f, Z:%d, E:%d F:%d",
status_data.buffer_level,
status_data.delayed,
status_data.paused,
status_data.rate,
status_data.delay,
status_data.zoom,
status_data.enabled,
status_data.forced
);
DataMsg status_msg;
status_msg.msg_type = MSG_DATA_WATCH;
memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) );
int nbytes = 0;
if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) {
//if ( errno != EAGAIN )
{
Error( "Can't sendto on sd %d: %s", sd, strerror(errno) );
//exit( -1 );
}
}
// quit after sending a status, if this was a quit request
if ((MsgCommand)msg->msg_data[0]==CMD_QUIT)
exit(0);
updateFrameRate( monitor->GetFPS() );
}
bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) {
bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE));
if ( type != STREAM_JPEG )
send_raw = false;
if ( !config.timestamp_on_capture && timestamp )
send_raw = false;
if ( !send_raw ) {
Image temp_image( filepath );
return( sendFrame( &temp_image, timestamp ) );
} else {
int img_buffer_size = 0;
static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE];
FILE *fdj = NULL;
if ( (fdj = fopen( filepath, "r" )) ) {
img_buffer_size = fread( img_buffer, 1, sizeof(img_buffer), fdj );
fclose( fdj );
} else {
Error( "Can't open %s: %s", filepath, strerror(errno) );
return( false );
}
// Calculate how long it takes to actually send the frame
struct timeval frameStartTime;
gettimeofday( &frameStartTime, NULL );
fprintf( stdout, "--ZoneMinderFrame\r\n" );
fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size );
fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" );
if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) {
if ( ! zm_terminate )
Error( "Unable to send stream frame: %s", strerror(errno) );
return( false );
}
fprintf( stdout, "\r\n\r\n" );
fflush( stdout );
struct timeval frameEndTime;
gettimeofday( &frameEndTime, NULL );
int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime );
if ( frameSendTime > 1000/maxfps ) {
maxfps /= 2;
Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps );
}
last_frame_sent = TV_2_FLOAT( now );
return( true );
}
return( false );
}
bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) {
Image *send_image = prepareImage( image );
if ( !config.timestamp_on_capture && timestamp )
monitor->TimestampImage( send_image, timestamp );
#if HAVE_LIBAVCODEC
if ( type == STREAM_MPEG ) {
if ( !vid_stream ) {
vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() );
fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() );
vid_stream->OpenStream();
}
static struct timeval base_time;
struct DeltaTimeval delta_time;
if ( !frame_count )
base_time = *timestamp;
DELTA_TIMEVAL( delta_time, *timestamp, base_time, DT_PREC_3 );
/* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta );
} else
#endif // HAVE_LIBAVCODEC
{
static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];
int img_buffer_size = 0;
unsigned char *img_buffer = temp_img_buffer;
// Calculate how long it takes to actually send the frame
struct timeval frameStartTime;
gettimeofday( &frameStartTime, NULL );
fprintf( stdout, "--ZoneMinderFrame\r\n" );
switch( type ) {
case STREAM_JPEG :
send_image->EncodeJpeg( img_buffer, &img_buffer_size );
fprintf( stdout, "Content-Type: image/jpeg\r\n" );
break;
case STREAM_RAW :
fprintf( stdout, "Content-Type: image/x-rgb\r\n" );
img_buffer = (uint8_t*)send_image->Buffer();
img_buffer_size = send_image->Size();
break;
case STREAM_ZIP :
fprintf( stdout, "Content-Type: image/x-rgbz\r\n" );
unsigned long zip_buffer_size;
send_image->Zip( img_buffer, &zip_buffer_size );
img_buffer_size = zip_buffer_size;
break;
default :
Fatal( "Unexpected frame type %d", type );
break;
}
fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size );
if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) {
if ( !zm_terminate )
Error( "Unable to send stream frame: %s", strerror(errno) );
return( false );
}
fprintf( stdout, "\r\n\r\n" );
fflush( stdout );
struct timeval frameEndTime;
gettimeofday( &frameEndTime, NULL );
int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime );
if ( frameSendTime > 1000/maxfps ) {
maxfps /= 1.5;
Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps );
}
}
last_frame_sent = TV_2_FLOAT( now );
return( true );
} // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp )
void MonitorStream::runStream() {
if ( type == STREAM_SINGLE ) {
// Not yet migrated over to stream class
monitor->SingleImage( scale );
return;
}
openComms();
checkInitialised();
updateFrameRate( monitor->GetFPS() );
if ( type == STREAM_JPEG )
fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" );
int last_read_index = monitor->image_buffer_count;
time_t stream_start_time;
time( &stream_start_time );
frame_count = 0;
temp_image_buffer = 0;
temp_image_buffer_count = playback_buffer;
temp_read_index = temp_image_buffer_count;
temp_write_index = temp_image_buffer_count;
char *swap_path = 0;
bool buffered_playback = false;
// 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id
const int max_swap_len_suffix = 15;
int swap_path_length = strlen(config.path_swap) + 1; // +1 for NULL terminator
int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id() ) + 1;
int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey ) + 1;
int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length;
if ( connkey && playback_buffer > 0 ) {
if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) {
Error( "Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX );
} else {
swap_path = (char *)malloc( total_swap_path_length+max_swap_len_suffix );
strncpy( swap_path, config.path_swap, swap_path_length );
Debug( 3, "Checking swap path folder: %s", swap_path );
if ( checkSwapPath( swap_path, false ) ) {
// Append the subfolder name /zmswap-m{monitor-id} to the end of swap_path
int ndx = swap_path_length - 1; // Array index of the NULL terminator
snprintf( &(swap_path[ndx]), subfolder1_length, "/zmswap-m%d", monitor->Id() );
Debug( 4, "Checking swap path subfolder: %s", swap_path );
if ( checkSwapPath( swap_path, true ) ) {
// Append the subfolder name /zmswap-q{connection key} to the end of swap_path
ndx = swap_path_length+subfolder1_length - 2; // Array index of the NULL terminator
snprintf( &(swap_path[ndx]), subfolder2_length, "/zmswap-q%06d", connkey );
Debug( 4, "Checking swap path subfolder: %s", swap_path );
if ( checkSwapPath( swap_path, true ) ) {
buffered_playback = true;
}
}
}
if ( !buffered_playback ) {
Error( "Unable to validate swap image path, disabling buffered playback" );
} else {
Debug( 2, "Assigning temporary buffer" );
temp_image_buffer = new SwapImage[temp_image_buffer_count];
memset( temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count );
Debug( 2, "Assigned temporary buffer" );
}
}
}
float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs)
while ( !zm_terminate ) {
bool got_command = false;
if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) {
break;
}
gettimeofday( &now, NULL );
if ( connkey ) {
while(checkCommandQueue()) {
got_command = true;
}
}
//bool frame_sent = false;
if ( buffered_playback && delayed ) {
if ( temp_read_index == temp_write_index ) {
// Go back to live viewing
Debug( 1, "Exceeded temporary streaming buffer" );
// Clear paused flag
paused = false;
// Clear delayed_play flag
delayed = false;
replay_rate = ZM_RATE_BASE;
} else {
if ( !paused ) {
int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count );
//Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index );
SwapImage *swap_image = &temp_image_buffer[temp_index];
if ( !swap_image->valid ) {
paused = true;
delayed = true;
temp_read_index = MOD_ADD( temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count );
} else {
//Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) );
double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate;
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
//Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) );
// If the next frame is due
if ( actual_delta_time > expected_delta_time ) {
//Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time );
if ( temp_index%frame_mod == 0 ) {
Debug( 2, "Sending delayed frame %d", temp_index );
// Send the next frame
if ( ! sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) )
zm_terminate = true;
memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) );
//frame_sent = true;
}
temp_read_index = MOD_ADD( temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count );
}
}
} else if ( step != 0 ) {
temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count );
SwapImage *swap_image = &temp_image_buffer[temp_read_index];
// Send the next frame
if ( !sendFrame( temp_image_buffer[temp_read_index].file_name, &temp_image_buffer[temp_read_index].timestamp ) )
zm_terminate = true;
memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) );
//frame_sent = true;
step = 0;
} else {
int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count );
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
if ( got_command || actual_delta_time > 5 ) {
// Send keepalive
Debug( 2, "Sending keepalive frame %d", temp_index );
// Send the next frame
if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) )
zm_terminate = true;
//frame_sent = true;
}
}
}
if ( temp_read_index == temp_write_index ) {
// Go back to live viewing
Warning( "Rewound over write index, resuming live play" );
// Clear paused flag
paused = false;
// Clear delayed_play flag
delayed = false;
replay_rate = ZM_RATE_BASE;
}
}
if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) {
int index = monitor->shared_data->last_write_index%monitor->image_buffer_count;
last_read_index = monitor->shared_data->last_write_index;
//Debug( 1, "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer );
if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) {
if ( !paused && !delayed ) {
// Send the next frame
Monitor::Snapshot *snap = &monitor->image_buffer[index];
if ( !sendFrame( snap->image, snap->timestamp ) )
zm_terminate = true;
memcpy( &last_frame_timestamp, snap->timestamp, sizeof(last_frame_timestamp) );
//frame_sent = true;
temp_read_index = temp_write_index;
}
}
if ( buffered_playback ) {
if ( monitor->shared_data->valid ) {
if ( monitor->image_buffer[index].timestamp->tv_sec ) {
int temp_index = temp_write_index%temp_image_buffer_count;
Debug( 2, "Storing frame %d", temp_index );
if ( !temp_image_buffer[temp_index].valid ) {
snprintf( temp_image_buffer[temp_index].file_name, sizeof(temp_image_buffer[0].file_name), "%s/zmswap-i%05d.jpg", swap_path, temp_index );
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) );
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 );
if ( temp_write_index == temp_read_index ) {
// Go back to live viewing
Warning( "Exceeded temporary buffer, resuming live play" );
// Clear paused flag
paused = false;
// Clear delayed_play flag
delayed = false;
replay_rate = ZM_RATE_BASE;
}
} else {
Warning( "Unable to store frame as timestamp invalid" );
}
} else {
Warning( "Unable to store frame as shared memory invalid" );
}
}
frame_count++;
}
usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) );
if ( ttl ) {
if ( (now.tv_sec - stream_start_time) > ttl ) {
break;
}
}
if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) {
Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame );
break;
}
}
if ( buffered_playback ) {
Debug( 1, "Cleaning swap files from %s", swap_path );
struct stat stat_buf;
if ( stat( swap_path, &stat_buf ) < 0 ) {
if ( errno != ENOENT ) {
Error( "Can't stat '%s': %s", swap_path, strerror(errno) );
}
} else if ( !S_ISDIR(stat_buf.st_mode) ) {
Error( "Swap image path '%s' is not a directory", swap_path );
} else {
char glob_pattern[PATH_MAX] = "";
snprintf( glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path );
glob_t pglob;
int glob_status = glob( glob_pattern, 0, 0, &pglob );
if ( glob_status != 0 ) {
if ( glob_status < 0 ) {
Error( "Can't glob '%s': %s", glob_pattern, strerror(errno) );
} else {
Debug( 1, "Can't glob '%s': %d", glob_pattern, glob_status );
}
} else {
for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) {
if ( unlink( pglob.gl_pathv[i] ) < 0 ) {
Error( "Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno) );
}
}
}
globfree( &pglob );
if ( rmdir( swap_path ) < 0 ) {
Error( "Can't rmdir '%s': %s", swap_path, strerror(errno) );
}
}
}
if ( swap_path ) free( swap_path );
closeComms();
}
void Monitor::SingleImage( int scale) {
int img_buffer_size = 0;
static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE];
Image scaled_image;
int index = shared_data->last_write_index%image_buffer_count;
Snapshot *snap = &image_buffer[index];
Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign( *snap_image );
scaled_image.Scale( scale );
snap_image = &scaled_image;
}
if ( !config.timestamp_on_capture ) {
TimestampImage( snap_image, snap->timestamp );
}
snap_image->EncodeJpeg( img_buffer, &img_buffer_size );
fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size );
fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" );
fwrite( img_buffer, img_buffer_size, 1, stdout );
}
void Monitor::SingleImageRaw( int scale) {
Image scaled_image;
int index = shared_data->last_write_index%image_buffer_count;
Snapshot *snap = &image_buffer[index];
Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign( *snap_image );
scaled_image.Scale( scale );
snap_image = &scaled_image;
}
if ( !config.timestamp_on_capture ) {
TimestampImage( snap_image, snap->timestamp );
}
fprintf( stdout, "Content-Length: %d\r\n", snap_image->Size() );
fprintf( stdout, "Content-Type: image/x-rgb\r\n\r\n" );
fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout );
}
void Monitor::SingleImageZip( int scale) {
unsigned long img_buffer_size = 0;
static Bytef img_buffer[ZM_MAX_IMAGE_SIZE];
Image scaled_image;
int index = shared_data->last_write_index%image_buffer_count;
Snapshot *snap = &image_buffer[index];
Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign( *snap_image );
scaled_image.Scale( scale );
snap_image = &scaled_image;
}
if ( !config.timestamp_on_capture ) {
TimestampImage( snap_image, snap->timestamp );
}
snap_image->Zip( img_buffer, &img_buffer_size );
fprintf( stdout, "Content-Length: %ld\r\n", img_buffer_size );
fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" );
fwrite( img_buffer, img_buffer_size, 1, stdout );
}
unsigned int Monitor::Colours() const { return( camera->Colours() ); }
unsigned int Monitor::SubpixelOrder() const { return( camera->SubpixelOrder() ); }
int Monitor::PrimeCapture() {
@ -4024,3 +3252,7 @@ int Monitor::PostCapture() {
return( camera->PostCapture() );
}
Monitor::Orientation Monitor::getOrientation() const { return orientation; }
Monitor::Snapshot *Monitor::getSnapshot() {
return &image_buffer[ shared_data->last_write_index%image_buffer_count ];
}

View File

@ -172,7 +172,7 @@ class Monitor
{
uint32_t size;
char event_file[4096];
uint32_t recording; //bool arch dependent so use uint32 instead
timeval recording; // used as both bool and a pointer to the timestamp when recording should begin
//uint32_t frameNumber;
} VideoStoreData;
@ -441,8 +441,10 @@ public:
VideoWriter GetOptVideoWriter() const { return( videowriter ); }
const std::vector<EncoderParameter_t>* GetOptEncoderParams() const { return( &encoderparamsvec ); }
unsigned int GetPreEventCount() const { return pre_event_count; };
State GetState() const;
int GetImage( int index=-1, int scale=100 );
Snapshot *getSnapshot();
struct timeval GetTimestamp( int index=-1 ) const;
void UpdateAdaptiveSkip();
useconds_t GetAnalysisRate();
@ -509,9 +511,6 @@ public:
//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 StreamImagesZip( int scale=100, int maxfps=10, time_t ttl=0 );
void SingleImage( int scale=100 );
void SingleImageRaw( int scale=100 );
void SingleImageZip( int scale=100 );
#if HAVE_LIBAVCODEC
//void StreamMpeg( const char *format, int scale=100, int maxfps=10, int bitrate=100000 );
#endif // HAVE_LIBAVCODEC
@ -519,49 +518,4 @@ public:
#define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit))
class MonitorStream : public StreamBase {
protected:
typedef struct SwapImage {
bool valid;
struct timeval timestamp;
char file_name[PATH_MAX];
} SwapImage;
private:
SwapImage *temp_image_buffer;
int temp_image_buffer_count;
int temp_read_index;
int temp_write_index;
protected:
time_t ttl;
protected:
int playback_buffer;
bool delayed;
int frame_count;
protected:
bool checkSwapPath( const char *path, bool create_path );
bool sendFrame( const char *filepath, struct timeval *timestamp );
bool sendFrame( Image *image, struct timeval *timestamp );
void processCommand( const CmdMsg *msg );
public:
MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) {
}
void setStreamBuffer( int p_playback_buffer ) {
playback_buffer = p_playback_buffer;
}
void setStreamTTL( time_t p_ttl ) {
ttl = p_ttl;
}
bool setStreamStart( int monitor_id ) {
return loadMonitor( monitor_id );
}
void runStream();
};
#endif // ZM_MONITOR_H

803
src/zm_monitorstream.cpp Normal file
View File

@ -0,0 +1,803 @@
//
// ZoneMinder Monitor Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm.h"
#include "zm_db.h"
#include "zm_time.h"
#include "zm_mpeg.h"
#include "zm_signal.h"
#include "zm_monitor.h"
#include "zm_monitorstream.h"
#include <arpa/inet.h>
#include <glob.h>
bool MonitorStream::checkSwapPath( const char *path, bool create_path ) {
struct stat stat_buf;
if ( stat( path, &stat_buf ) < 0 ) {
if ( create_path && errno == ENOENT ) {
Debug( 3, "Swap path '%s' missing, creating", path );
if ( mkdir( path, 0755 ) ) {
Error( "Can't mkdir %s: %s", path, strerror(errno));
return( false );
}
if ( stat( path, &stat_buf ) < 0 ) {
Error( "Can't stat '%s': %s", path, strerror(errno) );
return( false );
}
} else {
Error( "Can't stat '%s': %s", path, strerror(errno) );
return( false );
}
}
if ( !S_ISDIR(stat_buf.st_mode) ) {
Error( "Swap image path '%s' is not a directory", path );
return( false );
}
uid_t uid = getuid();
gid_t gid = getgid();
mode_t mask = 0;
if ( uid == stat_buf.st_uid ) {
// If we are the owner
mask = 00700;
} else if ( gid == stat_buf.st_gid ) {
// If we are in the owner group
mask = 00070;
} else {
// We are neither the owner nor in the group
mask = 00007;
}
if ( (stat_buf.st_mode & mask) != mask ) {
Error( "Insufficient permissions on swap image path '%s'", path );
return( false );
}
return( true );
} // end bool MonitorStream::checkSwapPath( const char *path, bool create_path )
void MonitorStream::processCommand( const CmdMsg *msg ) {
Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] );
// Check for incoming command
switch( (MsgCommand)msg->msg_data[0] ) {
case CMD_PAUSE :
{
Debug( 1, "Got PAUSE command" );
// Set paused flag
paused = true;
// Set delayed flag
delayed = true;
last_frame_sent = TV_2_FLOAT( now );
break;
}
case CMD_PLAY :
{
Debug( 1, "Got PLAY command" );
if ( paused ) {
// Clear paused flag
paused = false;
// Set delayed_play flag
delayed = true;
}
replay_rate = ZM_RATE_BASE;
break;
}
case CMD_VARPLAY :
{
Debug( 1, "Got VARPLAY command" );
if ( paused ) {
// Clear paused flag
paused = false;
// Set delayed_play flag
delayed = true;
}
replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768;
break;
}
case CMD_STOP :
{
Debug( 1, "Got STOP command" );
// Clear paused flag
paused = false;
// Clear delayed_play flag
delayed = false;
break;
}
case CMD_FASTFWD :
{
Debug( 1, "Got FAST FWD command" );
if ( paused ) {
// Clear paused flag
paused = false;
// Set delayed_play flag
delayed = true;
}
// Set play rate
switch ( replay_rate )
{
case 2 * ZM_RATE_BASE :
replay_rate = 5 * ZM_RATE_BASE;
break;
case 5 * ZM_RATE_BASE :
replay_rate = 10 * ZM_RATE_BASE;
break;
case 10 * ZM_RATE_BASE :
replay_rate = 25 * ZM_RATE_BASE;
break;
case 25 * ZM_RATE_BASE :
case 50 * ZM_RATE_BASE :
replay_rate = 50 * ZM_RATE_BASE;
break;
default :
replay_rate = 2 * ZM_RATE_BASE;
break;
}
break;
}
case CMD_SLOWFWD :
{
Debug( 1, "Got SLOW FWD command" );
// Set paused flag
paused = true;
// Set delayed flag
delayed = true;
// Set play rate
replay_rate = ZM_RATE_BASE;
// Set step
step = 1;
break;
}
case CMD_SLOWREV :
{
Debug( 1, "Got SLOW REV command" );
// Set paused flag
paused = true;
// Set delayed flag
delayed = true;
// Set play rate
replay_rate = ZM_RATE_BASE;
// Set step
step = -1;
break;
}
case CMD_FASTREV :
{
Debug( 1, "Got FAST REV command" );
if ( paused ) {
// Clear paused flag
paused = false;
// Set delayed_play flag
delayed = true;
}
// Set play rate
switch ( replay_rate ) {
case -2 * ZM_RATE_BASE :
replay_rate = -5 * ZM_RATE_BASE;
break;
case -5 * ZM_RATE_BASE :
replay_rate = -10 * ZM_RATE_BASE;
break;
case -10 * ZM_RATE_BASE :
replay_rate = -25 * ZM_RATE_BASE;
break;
case -25 * ZM_RATE_BASE :
case -50 * ZM_RATE_BASE :
replay_rate = -50 * ZM_RATE_BASE;
break;
default :
replay_rate = -2 * ZM_RATE_BASE;
break;
}
break;
}
case CMD_ZOOMIN :
{
x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
Debug( 1, "Got ZOOM IN command, to %d,%d", x, y );
switch ( zoom ) {
case 100:
zoom = 150;
break;
case 150:
zoom = 200;
break;
case 200:
zoom = 300;
break;
case 300:
zoom = 400;
break;
case 400:
default :
zoom = 500;
break;
}
break;
}
case CMD_ZOOMOUT :
{
Debug( 1, "Got ZOOM OUT command" );
switch ( zoom ) {
case 500:
zoom = 400;
break;
case 400:
zoom = 300;
break;
case 300:
zoom = 200;
break;
case 200:
zoom = 150;
break;
case 150:
default :
zoom = 100;
break;
}
break;
}
case CMD_PAN :
{
x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
Debug( 1, "Got PAN command, to %d,%d", x, y );
break;
}
case CMD_SCALE :
{
scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
Debug( 1, "Got SCALE command, to %d", scale );
break;
}
case CMD_QUIT :
{
Info ("User initiated exit - CMD_QUIT");
break;
}
case CMD_QUERY :
{
Debug( 1, "Got QUERY command, sending STATUS" );
break;
}
default :
{
Error( "Got unexpected command %d", msg->msg_data[0] );
break;
}
}
struct {
int id;
int state;
double fps;
int buffer_level;
int rate;
double delay;
int zoom;
bool delayed;
bool paused;
bool enabled;
bool forced;
} status_data;
status_data.id = monitor->Id();
status_data.fps = monitor->GetFPS();
status_data.state = monitor->shared_data->state;
if ( playback_buffer > 0 )
status_data.buffer_level = (MOD_ADD( (temp_write_index-temp_read_index), 0, temp_image_buffer_count )*100)/temp_image_buffer_count;
else
status_data.buffer_level = 0;
status_data.delayed = delayed;
status_data.paused = paused;
status_data.rate = replay_rate;
status_data.delay = TV_2_FLOAT( now ) - TV_2_FLOAT( last_frame_timestamp );
status_data.zoom = zoom;
//status_data.enabled = monitor->shared_data->active;
status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF;
status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON;
Debug( 2, "L:%d, D:%d, P:%d, R:%d, d:%.3f, Z:%d, E:%d F:%d",
status_data.buffer_level,
status_data.delayed,
status_data.paused,
status_data.rate,
status_data.delay,
status_data.zoom,
status_data.enabled,
status_data.forced
);
DataMsg status_msg;
status_msg.msg_type = MSG_DATA_WATCH;
memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) );
int nbytes = 0;
if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) {
//if ( errno != EAGAIN )
{
Error( "Can't sendto on sd %d: %s", sd, strerror(errno) );
//exit( -1 );
}
}
// quit after sending a status, if this was a quit request
if ((MsgCommand)msg->msg_data[0]==CMD_QUIT)
exit(0);
updateFrameRate( monitor->GetFPS() );
} // end void MonitorStream::processCommand( const CmdMsg *msg )
bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) {
bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE));
if ( type != STREAM_JPEG )
send_raw = false;
if ( !config.timestamp_on_capture && timestamp )
send_raw = false;
if ( !send_raw ) {
Image temp_image( filepath );
return( sendFrame( &temp_image, timestamp ) );
} else {
int img_buffer_size = 0;
static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE];
FILE *fdj = NULL;
if ( (fdj = fopen( filepath, "r" )) ) {
img_buffer_size = fread( img_buffer, 1, sizeof(img_buffer), fdj );
fclose( fdj );
} else {
Error( "Can't open %s: %s", filepath, strerror(errno) );
return( false );
}
// Calculate how long it takes to actually send the frame
struct timeval frameStartTime;
gettimeofday( &frameStartTime, NULL );
fprintf( stdout, "--ZoneMinderFrame\r\n" );
fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size );
fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" );
if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) {
if ( ! zm_terminate )
Error( "Unable to send stream frame: %s", strerror(errno) );
return( false );
}
fprintf( stdout, "\r\n\r\n" );
fflush( stdout );
struct timeval frameEndTime;
gettimeofday( &frameEndTime, NULL );
int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime );
if ( frameSendTime > 1000/maxfps ) {
maxfps /= 2;
Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps );
}
last_frame_sent = TV_2_FLOAT( now );
return( true );
}
return( false );
}
bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) {
Image *send_image = prepareImage( image );
if ( !config.timestamp_on_capture && timestamp )
monitor->TimestampImage( send_image, timestamp );
#if HAVE_LIBAVCODEC
if ( type == STREAM_MPEG ) {
if ( !vid_stream ) {
vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() );
fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() );
vid_stream->OpenStream();
}
static struct timeval base_time;
struct DeltaTimeval delta_time;
if ( !frame_count )
base_time = *timestamp;
DELTA_TIMEVAL( delta_time, *timestamp, base_time, DT_PREC_3 );
/* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta );
} else
#endif // HAVE_LIBAVCODEC
{
static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];
int img_buffer_size = 0;
unsigned char *img_buffer = temp_img_buffer;
// Calculate how long it takes to actually send the frame
struct timeval frameStartTime;
gettimeofday( &frameStartTime, NULL );
fprintf( stdout, "--ZoneMinderFrame\r\n" );
switch( type ) {
case STREAM_JPEG :
send_image->EncodeJpeg( img_buffer, &img_buffer_size );
fprintf( stdout, "Content-Type: image/jpeg\r\n" );
break;
case STREAM_RAW :
fprintf( stdout, "Content-Type: image/x-rgb\r\n" );
img_buffer = (uint8_t*)send_image->Buffer();
img_buffer_size = send_image->Size();
break;
case STREAM_ZIP :
fprintf( stdout, "Content-Type: image/x-rgbz\r\n" );
unsigned long zip_buffer_size;
send_image->Zip( img_buffer, &zip_buffer_size );
img_buffer_size = zip_buffer_size;
break;
default :
Fatal( "Unexpected frame type %d", type );
break;
}
fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size );
if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) {
if ( !zm_terminate )
Error( "Unable to send stream frame: %s", strerror(errno) );
return( false );
}
fprintf( stdout, "\r\n\r\n" );
fflush( stdout );
struct timeval frameEndTime;
gettimeofday( &frameEndTime, NULL );
int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime );
if ( frameSendTime > 1000/maxfps ) {
maxfps /= 1.5;
Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps );
}
}
last_frame_sent = TV_2_FLOAT( now );
return( true );
} // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp )
void MonitorStream::runStream() {
if ( type == STREAM_SINGLE ) {
// Not yet migrated over to stream class
SingleImage( scale );
return;
}
openComms();
checkInitialised();
updateFrameRate( monitor->GetFPS() );
if ( type == STREAM_JPEG )
fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" );
int last_read_index = monitor->image_buffer_count;
time_t stream_start_time;
time( &stream_start_time );
frame_count = 0;
temp_image_buffer = 0;
temp_image_buffer_count = playback_buffer;
temp_read_index = temp_image_buffer_count;
temp_write_index = temp_image_buffer_count;
char *swap_path = 0;
bool buffered_playback = false;
// 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id
const int max_swap_len_suffix = 15;
int swap_path_length = strlen(config.path_swap) + 1; // +1 for NULL terminator
int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id() ) + 1;
int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey ) + 1;
int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length;
if ( connkey && playback_buffer > 0 ) {
if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) {
Error( "Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX );
} else {
swap_path = (char *)malloc( total_swap_path_length+max_swap_len_suffix );
strncpy( swap_path, config.path_swap, swap_path_length );
Debug( 3, "Checking swap path folder: %s", swap_path );
if ( checkSwapPath( swap_path, false ) ) {
// Append the subfolder name /zmswap-m{monitor-id} to the end of swap_path
int ndx = swap_path_length - 1; // Array index of the NULL terminator
snprintf( &(swap_path[ndx]), subfolder1_length, "/zmswap-m%d", monitor->Id() );
Debug( 4, "Checking swap path subfolder: %s", swap_path );
if ( checkSwapPath( swap_path, true ) ) {
// Append the subfolder name /zmswap-q{connection key} to the end of swap_path
ndx = swap_path_length+subfolder1_length - 2; // Array index of the NULL terminator
snprintf( &(swap_path[ndx]), subfolder2_length, "/zmswap-q%06d", connkey );
Debug( 4, "Checking swap path subfolder: %s", swap_path );
if ( checkSwapPath( swap_path, true ) ) {
buffered_playback = true;
}
}
}
if ( !buffered_playback ) {
Error( "Unable to validate swap image path, disabling buffered playback" );
} else {
Debug( 2, "Assigning temporary buffer" );
temp_image_buffer = new SwapImage[temp_image_buffer_count];
memset( temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count );
Debug( 2, "Assigned temporary buffer" );
}
}
}
float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs)
while ( !zm_terminate ) {
bool got_command = false;
if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) {
break;
}
gettimeofday( &now, NULL );
if ( connkey ) {
while(checkCommandQueue()) {
got_command = true;
}
}
//bool frame_sent = false;
if ( buffered_playback && delayed ) {
if ( temp_read_index == temp_write_index ) {
// Go back to live viewing
Debug( 1, "Exceeded temporary streaming buffer" );
// Clear paused flag
paused = false;
// Clear delayed_play flag
delayed = false;
replay_rate = ZM_RATE_BASE;
} else {
if ( !paused ) {
int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count );
//Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index );
SwapImage *swap_image = &temp_image_buffer[temp_index];
if ( !swap_image->valid ) {
paused = true;
delayed = true;
temp_read_index = MOD_ADD( temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count );
} else {
//Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) );
double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate;
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
//Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) );
// If the next frame is due
if ( actual_delta_time > expected_delta_time ) {
//Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time );
if ( temp_index%frame_mod == 0 ) {
Debug( 2, "Sending delayed frame %d", temp_index );
// Send the next frame
if ( ! sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) )
zm_terminate = true;
memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) );
//frame_sent = true;
}
temp_read_index = MOD_ADD( temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count );
}
}
} else if ( step != 0 ) {
temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count );
SwapImage *swap_image = &temp_image_buffer[temp_read_index];
// Send the next frame
if ( !sendFrame( temp_image_buffer[temp_read_index].file_name, &temp_image_buffer[temp_read_index].timestamp ) )
zm_terminate = true;
memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) );
//frame_sent = true;
step = 0;
} else {
int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count );
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
if ( got_command || actual_delta_time > 5 ) {
// Send keepalive
Debug( 2, "Sending keepalive frame %d", temp_index );
// Send the next frame
if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) )
zm_terminate = true;
//frame_sent = true;
}
}
}
if ( temp_read_index == temp_write_index ) {
// Go back to live viewing
Warning( "Rewound over write index, resuming live play" );
// Clear paused flag
paused = false;
// Clear delayed_play flag
delayed = false;
replay_rate = ZM_RATE_BASE;
}
}
if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) {
int index = monitor->shared_data->last_write_index%monitor->image_buffer_count;
last_read_index = monitor->shared_data->last_write_index;
//Debug( 1, "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer );
if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) {
if ( !paused && !delayed ) {
// Send the next frame
Monitor::Snapshot *snap = &monitor->image_buffer[index];
if ( !sendFrame( snap->image, snap->timestamp ) )
zm_terminate = true;
memcpy( &last_frame_timestamp, snap->timestamp, sizeof(last_frame_timestamp) );
//frame_sent = true;
temp_read_index = temp_write_index;
}
}
if ( buffered_playback ) {
if ( monitor->shared_data->valid ) {
if ( monitor->image_buffer[index].timestamp->tv_sec ) {
int temp_index = temp_write_index%temp_image_buffer_count;
Debug( 2, "Storing frame %d", temp_index );
if ( !temp_image_buffer[temp_index].valid ) {
snprintf( temp_image_buffer[temp_index].file_name, sizeof(temp_image_buffer[0].file_name), "%s/zmswap-i%05d.jpg", swap_path, temp_index );
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) );
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 );
if ( temp_write_index == temp_read_index ) {
// Go back to live viewing
Warning( "Exceeded temporary buffer, resuming live play" );
// Clear paused flag
paused = false;
// Clear delayed_play flag
delayed = false;
replay_rate = ZM_RATE_BASE;
}
} else {
Warning( "Unable to store frame as timestamp invalid" );
}
} else {
Warning( "Unable to store frame as shared memory invalid" );
}
}
frame_count++;
}
usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) );
if ( ttl ) {
if ( (now.tv_sec - stream_start_time) > ttl ) {
break;
}
}
if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) {
Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame );
break;
}
}
if ( buffered_playback ) {
Debug( 1, "Cleaning swap files from %s", swap_path );
struct stat stat_buf;
if ( stat( swap_path, &stat_buf ) < 0 ) {
if ( errno != ENOENT ) {
Error( "Can't stat '%s': %s", swap_path, strerror(errno) );
}
} else if ( !S_ISDIR(stat_buf.st_mode) ) {
Error( "Swap image path '%s' is not a directory", swap_path );
} else {
char glob_pattern[PATH_MAX] = "";
snprintf( glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path );
glob_t pglob;
int glob_status = glob( glob_pattern, 0, 0, &pglob );
if ( glob_status != 0 ) {
if ( glob_status < 0 ) {
Error( "Can't glob '%s': %s", glob_pattern, strerror(errno) );
} else {
Debug( 1, "Can't glob '%s': %d", glob_pattern, glob_status );
}
} else {
for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) {
if ( unlink( pglob.gl_pathv[i] ) < 0 ) {
Error( "Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno) );
}
}
}
globfree( &pglob );
if ( rmdir( swap_path ) < 0 ) {
Error( "Can't rmdir '%s': %s", swap_path, strerror(errno) );
}
}
}
if ( swap_path ) free( swap_path );
closeComms();
}
void MonitorStream::SingleImage( int scale ) {
int img_buffer_size = 0;
static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE];
Image scaled_image;
Monitor::Snapshot *snap = monitor->getSnapshot();
Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign( *snap_image );
scaled_image.Scale( scale );
snap_image = &scaled_image;
}
if ( !config.timestamp_on_capture ) {
monitor->TimestampImage( snap_image, snap->timestamp );
}
snap_image->EncodeJpeg( img_buffer, &img_buffer_size );
fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size );
fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" );
fwrite( img_buffer, img_buffer_size, 1, stdout );
}
void MonitorStream::SingleImageRaw( int scale ) {
Image scaled_image;
Monitor::Snapshot *snap = monitor->getSnapshot();
Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign( *snap_image );
scaled_image.Scale( scale );
snap_image = &scaled_image;
}
if ( !config.timestamp_on_capture ) {
monitor->TimestampImage( snap_image, snap->timestamp );
}
fprintf( stdout, "Content-Length: %d\r\n", snap_image->Size() );
fprintf( stdout, "Content-Type: image/x-rgb\r\n\r\n" );
fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout );
}
void MonitorStream::SingleImageZip( int scale ) {
unsigned long img_buffer_size = 0;
static Bytef img_buffer[ZM_MAX_IMAGE_SIZE];
Image scaled_image;
Monitor::Snapshot *snap = monitor->getSnapshot();
Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign( *snap_image );
scaled_image.Scale( scale );
snap_image = &scaled_image;
}
if ( !config.timestamp_on_capture ) {
monitor->TimestampImage( snap_image, snap->timestamp );
}
snap_image->Zip( img_buffer, &img_buffer_size );
fprintf( stdout, "Content-Length: %ld\r\n", img_buffer_size );
fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" );
fwrite( img_buffer, img_buffer_size, 1, stdout );
}

77
src/zm_monitorstream.h Normal file
View File

@ -0,0 +1,77 @@
//
// ZoneMinder MonitorStream Class Interfaces, $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.
//
#ifndef ZM_MONITORSTREAM_H
#define ZM_MONITORSTREAM_H
#include "zm.h"
#include "zm_coord.h"
#include "zm_image.h"
#include "zm_utils.h"
#include "zm_monitor.h"
class MonitorStream : public StreamBase {
protected:
typedef struct SwapImage {
bool valid;
struct timeval timestamp;
char file_name[PATH_MAX];
} SwapImage;
private:
SwapImage *temp_image_buffer;
int temp_image_buffer_count;
int temp_read_index;
int temp_write_index;
protected:
time_t ttl;
protected:
int playback_buffer;
bool delayed;
int frame_count;
protected:
bool checkSwapPath( const char *path, bool create_path );
bool sendFrame( const char *filepath, struct timeval *timestamp );
bool sendFrame( Image *image, struct timeval *timestamp );
void processCommand( const CmdMsg *msg );
void SingleImage( int scale=100 );
void SingleImageRaw( int scale=100 );
void SingleImageZip( int scale=100 );
public:
MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) {
}
void setStreamBuffer( int p_playback_buffer ) {
playback_buffer = p_playback_buffer;
}
void setStreamTTL( time_t p_ttl ) {
ttl = p_ttl;
}
bool setStreamStart( int monitor_id ) {
return loadMonitor( monitor_id );
}
void runStream();
};
#endif // ZM_MONITORSTREAM_H

44
src/zm_packet.cpp Normal file
View File

@ -0,0 +1,44 @@
//ZoneMinder Packet Implementation Class
//Copyright 2017 ZoneMinder LLC
//
//This file is part of ZoneMinder.
//
//ZoneMinder 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 3 of the License, or
//(at your option) any later version.
//
//ZoneMinder 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 ZoneMinder. If not, see <http://www.gnu.org/licenses/>.
#include "zm_packet.h"
#include "zm_ffmpeg.h"
using namespace std;
ZMPacket::ZMPacket( AVPacket *p ) {
av_init_packet( &packet );
if ( zm_av_packet_ref( &packet, p ) < 0 ) {
Error("error refing packet");
}
gettimeofday( &timestamp, NULL );
}
ZMPacket::ZMPacket( AVPacket *p, struct timeval *t ) {
av_init_packet( &packet );
if ( zm_av_packet_ref( &packet, p ) < 0 ) {
Error("error refing packet");
}
timestamp = *t;
}
ZMPacket::~ZMPacket() {
zm_av_packet_unref( &packet );
}

39
src/zm_packet.h Normal file
View File

@ -0,0 +1,39 @@
//ZoneMinder Packet Wrapper Class
//Copyright 2017 ZoneMinder LLC
//
//This file is part of ZoneMinder.
//
//ZoneMinder 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 3 of the License, or
//(at your option) any later version.
//
//ZoneMinder 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 ZoneMinder. If not, see <http://www.gnu.org/licenses/>.
#ifndef ZM_PACKET_H
#define ZM_PACKET_H
extern "C" {
#include <libavformat/avformat.h>
}
class ZMPacket {
public:
AVPacket packet;
struct timeval timestamp;
public:
AVPacket *av_packet() { return &packet; }
ZMPacket( AVPacket *packet, struct timeval *timestamp );
ZMPacket( AVPacket *packet );
~ZMPacket();
};
#endif /* ZM_PACKET_H */

View File

@ -33,44 +33,120 @@ zm_packetqueue::~zm_packetqueue() {
}
bool zm_packetqueue::queuePacket( AVPacket* packet ) {
bool zm_packetqueue::queuePacket( ZMPacket* zm_packet ) {
pktQueue.push_back( zm_packet );
return true;
}
bool zm_packetqueue::queuePacket( AVPacket* av_packet ) {
AVPacket *input_ref = (AVPacket *)av_malloc(sizeof(AVPacket));
av_init_packet( input_ref );
if ( zm_av_packet_ref( input_ref, packet ) < 0 ) {
Error("error refing packet");
av_free(input_ref);
return false;
}
ZMPacket *zm_packet = new ZMPacket( av_packet );
pktQueue.push( input_ref );
pktQueue.push_back( zm_packet );
return true;
}
AVPacket* zm_packetqueue::popPacket( ) {
ZMPacket* zm_packetqueue::popPacket( ) {
if ( pktQueue.empty() ) {
return NULL;
}
AVPacket *packet = pktQueue.front();
pktQueue.pop();
ZMPacket *packet = pktQueue.front();
pktQueue.pop_front();
return packet;
}
void zm_packetqueue::clearQueue() {
AVPacket *packet = NULL;
while(!pktQueue.empty()) {
unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) {
Debug(3, "Clearing all but %d frames", frames_to_keep );
frames_to_keep += 1;
if ( pktQueue.empty() ) {
Debug(3, "Queue is empty");
return 0;
} else {
Debug(3, "Queue has (%d)", pktQueue.size() );
}
list<ZMPacket *>::reverse_iterator it;
ZMPacket *packet = NULL;
for ( it = pktQueue.rbegin(); it != pktQueue.rend() && frames_to_keep; ++it ) {
ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet);
Debug(3, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
// Want frames_to_keep video keyframes. Otherwise, we may not have enough
if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) {
if (!frames_to_keep)
break;
frames_to_keep --;
}
}
unsigned int delete_count = 0;
while ( it != pktQueue.rend() ) {
Debug(3, "Deleting a packet from the front, count is (%d)", delete_count );
packet = pktQueue.front();
pktQueue.pop();
// If we clear it, then no freeing gets done, whereas when we pop off, we assume that the packet was freed somewhere else.
zm_av_packet_unref( packet );
av_free( packet );
pktQueue.pop_front();
delete packet;
delete_count += 1;
}
return delete_count;
} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id )
void zm_packetqueue::clearQueue() {
ZMPacket *packet = NULL;
while(!pktQueue.empty()) {
packet = pktQueue.front();
pktQueue.pop_front();
delete packet;
}
}
unsigned int zm_packetqueue::size() {
return pktQueue.size();
}
void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) {
// Need to find the keyframe <= recording_started. Can get rid of audio packets.
if ( pktQueue.empty() ) {
return;
}
// Step 1 - find keyframe < recording_started.
// Step 2 - pop packets until we get to the packet in step 2
list<ZMPacket *>::reverse_iterator it;
for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) {
ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet);
Debug(1, "Looking for keyframe after start" );
if (
( av_packet->flags & AV_PKT_FLAG_KEY )
&&
( av_packet->stream_index == mVideoStreamId )
&&
timercmp( &(zm_packet->timestamp), recording_started, < )
) {
Debug(1, "Found keyframe before start" );
break;
}
}
if ( it == pktQueue.rend() ) {
Debug(1, "Didn't find a keyframe packet keeping all" );
return;
}
ZMPacket *packet = NULL;
while ( pktQueue.rend() != it ) {
packet = pktQueue.front();
pktQueue.pop_front();
delete packet;
}
}

View File

@ -23,7 +23,8 @@
//#include <boost/interprocess/managed_shared_memory.hpp>
//#include <boost/interprocess/containers/map.hpp>
//#include <boost/interprocess/allocators/allocator.hpp>
#include <queue>
#include <list>
#include "zm_packet.h"
extern "C" {
#include <libavformat/avformat.h>
@ -33,18 +34,19 @@ class zm_packetqueue {
public:
zm_packetqueue();
virtual ~zm_packetqueue();
bool queuePacket( AVPacket* packet, struct timeval *timestamp );
bool queuePacket( ZMPacket* packet );
bool queuePacket( AVPacket* packet );
AVPacket * popPacket( );
bool popVideoPacket(AVPacket* packet);
bool popAudioPacket(AVPacket* packet);
ZMPacket * popPacket( );
bool popVideoPacket(ZMPacket* packet);
bool popAudioPacket(ZMPacket* packet);
unsigned int clearQueue( unsigned int video_frames_to_keep, int stream_id );
void clearQueue( );
unsigned int size();
void clear_unwanted_packets( timeval *recording, int mVideoStreamId );
private:
std::queue<AVPacket *> pktQueue;
std::list<ZMPacket *> pktQueue;
};
#endif /* ZM_PACKETQUEUE_H */

View File

@ -90,7 +90,7 @@ public:
virtual int PreCapture() = 0;
virtual int Capture( Image &image ) = 0;
virtual int PostCapture() = 0;
virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory )=0;
virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory )=0;
};
#endif // ZM_REMOTE_CAMERA_H

View File

@ -59,7 +59,7 @@ public:
int PreCapture();
int Capture( Image &image );
int PostCapture();
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);};
};
#endif // ZM_REMOTE_CAMERA_HTTP_H

View File

@ -392,7 +392,7 @@ int RemoteCameraRtsp::Capture( Image &image ) {
//Function to handle capture and store
int RemoteCameraRtsp::CaptureAndRecord(Image &image, bool recording, char* event_file ) {
int RemoteCameraRtsp::CaptureAndRecord(Image &image, timeval recording, char* event_file ) {
AVPacket packet;
uint8_t* directbuffer;
int frameComplete = false;
@ -407,7 +407,7 @@ int RemoteCameraRtsp::CaptureAndRecord(Image &image, bool recording, char* event
return (-1);
//Video recording
if ( recording ) {
if ( recording.tv_sec ) {
// The directory we are recording to is no longer tied to the current event.
// Need to re-init the videostore with the correct directory and start recording again
// Not sure why we are only doing this on keyframe, al

View File

@ -86,7 +86,7 @@ public:
int PreCapture();
int Capture( Image &image );
int PostCapture();
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
};
#endif // ZM_REMOTE_CAMERA_RTSP_H

View File

@ -390,6 +390,18 @@ void timespec_diff(struct timespec *start, struct timespec *end, struct timespec
}
}
char *timeval_to_string( struct timeval tv ) {
time_t nowtime;
struct tm *nowtm;
char tmbuf[64], buf[64];
nowtime = tv.tv_sec;
nowtm = localtime(&nowtime);
strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm);
snprintf(buf, sizeof buf, "%s.%06ld", tmbuf, tv.tv_usec);
return buf;
}
std::string UriDecode( const std::string &encoded ) {
#ifdef HAVE_LIBCURL
CURL *curl = curl_easy_init();

View File

@ -61,6 +61,7 @@ void hwcaps_detect();
extern unsigned int sseversion;
extern unsigned int neonversion;
char *timeval_to_string( struct timeval tv );
std::string UriDecode( const std::string &encoded );
#endif // ZM_UTILS_H

View File

@ -1,4 +1,3 @@
//
// ZoneMinder Video Storage Implementation
// Written by Chris Wiggins
// http://chriswiggins.co.nz
@ -129,50 +128,6 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
video_output_context->time_base.den
);
#if 0
if ( video_input_context->sample_aspect_ratio.den && ( video_output_stream->sample_aspect_ratio.den != video_input_context->sample_aspect_ratio.den ) ) {
Warning("Fixing sample_aspect_ratio.den from (%d) to (%d)", video_output_stream->sample_aspect_ratio.den, video_input_context->sample_aspect_ratio.den );
video_output_stream->sample_aspect_ratio.den = video_input_context->sample_aspect_ratio.den;
} else {
Debug(3, "aspect ratio denominator is (%d)", video_output_stream->sample_aspect_ratio.den );
}
if ( video_input_context->sample_aspect_ratio.num && ( video_output_stream->sample_aspect_ratio.num != video_input_context->sample_aspect_ratio.num ) ) {
Warning("Fixing sample_aspect_ratio.num from video_output_stream(%d) to video_input_stream(%d)", video_output_stream->sample_aspect_ratio.num, video_input_context->sample_aspect_ratio.num );
video_output_stream->sample_aspect_ratio.num = video_input_context->sample_aspect_ratio.num;
} else {
Debug(3, "aspect ratio numerator is (%d)", video_output_stream->sample_aspect_ratio.num );
}
if ( video_output_context->codec_id != video_input_context->codec_id ) {
Warning("Fixing video_output_context->codec_id");
video_output_context->codec_id = video_input_context->codec_id;
}
if ( ! video_output_context->time_base.num ) {
Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_context->time_base.num, video_output_context->time_base.den);
Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_stream->time_base.num, video_output_stream->time_base.den);
video_output_context->time_base.num = video_output_stream->time_base.num;
video_output_context->time_base.den = video_output_stream->time_base.den;
}
if ( video_output_stream->sample_aspect_ratio.den != video_output_context->sample_aspect_ratio.den ) {
Warning("Fixingample_aspect_ratio.den");
video_output_stream->sample_aspect_ratio.den = video_output_context->sample_aspect_ratio.den;
}
if ( video_output_stream->sample_aspect_ratio.num != video_input_context->sample_aspect_ratio.num ) {
Warning("Fixingample_aspect_ratio.num");
video_output_stream->sample_aspect_ratio.num = video_input_context->sample_aspect_ratio.num;
}
if ( video_output_context->codec_id != video_input_context->codec_id ) {
Warning("Fixing video_output_context->codec_id");
video_output_context->codec_id = video_input_context->codec_id;
}
if ( ! video_output_context->time_base.num ) {
Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_context->time_base.num, video_output_context->time_base.den);
Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_stream->time_base.num, video_output_stream->time_base.den);
video_output_context->time_base.num = video_output_stream->time_base.num;
video_output_context->time_base.den = video_output_stream->time_base.den;
}
#endif
// WHY?
//video_output_context->codec_tag = 0;
if (!video_output_context->codec_tag) {
@ -209,180 +164,19 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
audio_output_codec = NULL;
audio_input_context = NULL;
audio_output_stream = NULL;
resample_context = NULL;
if (audio_input_stream) {
audio_input_context = audio_input_stream->codec;
if ( audio_input_context->codec_id != AV_CODEC_ID_AAC ) {
#ifdef HAVE_LIBSWRESAMPLE
resample_context = NULL;
char error_buffer[256];
static char error_buffer[256];
avcodec_string(error_buffer, sizeof(error_buffer), audio_input_context, 0 );
Debug(3, "Got something other than AAC (%s)", error_buffer );
audio_output_stream = NULL;
audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if ( audio_output_codec ) {
Debug(2, "Have audio output codec");
audio_output_stream = avformat_new_stream( oc, audio_output_codec );
audio_output_context = audio_output_stream->codec;
if ( audio_output_context ) {
Debug(2, "Have audio_output_context");
AVDictionary *opts = NULL;
av_dict_set(&opts, "strict", "experimental", 0);
/* put sample parameters */
audio_output_context->bit_rate = audio_input_context->bit_rate;
audio_output_context->sample_rate = audio_input_context->sample_rate;
audio_output_context->channels = audio_input_context->channels;
audio_output_context->channel_layout = audio_input_context->channel_layout;
audio_output_context->sample_fmt = audio_input_context->sample_fmt;
//audio_output_context->refcounted_frames = 1;
if (audio_output_codec->supported_samplerates) {
int found = 0;
for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) {
if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) {
found = 1;
break;
}
}
if ( found ) {
Debug(3, "Sample rate is good");
} else {
audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0];
Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] );
}
}
/* check that the encoder supports s16 pcm input */
if (!check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt)) {
Debug( 3, "Encoder does not support sample format %s, setting to FLTP",
av_get_sample_fmt_name( audio_output_context->sample_fmt));
audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP;
}
//audio_output_stream->time_base = audio_input_stream->time_base;
audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate };
Debug(3, "Audio Time bases input stream (%d/%d) input codec: (%d/%d) output_stream (%d/%d) output codec (%d/%d)",
audio_input_stream->time_base.num,
audio_input_stream->time_base.den,
audio_input_context->time_base.num,
audio_input_context->time_base.den,
audio_output_stream->time_base.num,
audio_output_stream->time_base.den,
audio_output_context->time_base.num,
audio_output_context->time_base.den
);
ret = avcodec_open2(audio_output_context, audio_output_codec, &opts );
if ( ret < 0 ) {
av_strerror(ret, error_buffer, sizeof(error_buffer));
Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer );
} else {
Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d), refcounted_frames(%d)",
audio_output_context->bit_rate,
audio_output_context->sample_rate,
audio_output_context->channels,
audio_output_context->sample_fmt,
audio_output_context->channel_layout,
audio_output_context->frame_size,
audio_output_context->refcounted_frames
);
#if 1
/** Create the FIFO buffer based on the specified output sample format. */
if (!(fifo = av_audio_fifo_alloc(audio_output_context->sample_fmt,
audio_output_context->channels, 1))) {
Error("Could not allocate FIFO\n");
return;
}
#endif
output_frame_size = audio_output_context->frame_size;
/** Create a new frame to store the audio samples. */
if (!(input_frame = zm_av_frame_alloc())) {
Error("Could not allocate input frame");
return;
}
/** Create a new frame to store the audio samples. */
if (!(output_frame = zm_av_frame_alloc())) {
Error("Could not allocate output frame");
av_frame_free(&input_frame);
return;
}
/**
* Create a resampler context for the conversion.
* Set the conversion parameters.
* Default channel layouts based on the number of channels
* are assumed for simplicity (they are sometimes not detected
* properly by the demuxer and/or decoder).
*/
resample_context = swr_alloc_set_opts(NULL,
av_get_default_channel_layout(audio_output_context->channels),
audio_output_context->sample_fmt,
audio_output_context->sample_rate,
av_get_default_channel_layout( audio_input_context->channels),
audio_input_context->sample_fmt,
audio_input_context->sample_rate,
0, NULL);
if (!resample_context) {
Error( "Could not allocate resample context\n");
return;
}
/**
* Perform a sanity check so that the number of converted samples is
* not greater than the number of samples to be converted.
* If the sample rates differ, this case has to be handled differently
*/
av_assert0(audio_output_context->sample_rate == audio_input_context->sample_rate);
/** Open the resampler with the specified parameters. */
if ((ret = swr_init(resample_context)) < 0) {
Error( "Could not open resample context\n");
swr_free(&resample_context);
return;
}
/**
* Allocate as many pointers as there are audio channels.
* Each pointer will later point to the audio samples of the corresponding
* channels (although it may be NULL for interleaved formats).
*/
if (!( converted_input_samples = (uint8_t *)calloc( audio_output_context->channels, sizeof(*converted_input_samples))) ) {
Error( "Could not allocate converted input sample pointers\n");
return;
}
/**
* Allocate memory for the samples of all channels in one consecutive
* block for convenience.
*/
if ((ret = av_samples_alloc( &converted_input_samples, NULL,
audio_output_context->channels,
audio_output_context->frame_size,
audio_output_context->sample_fmt, 0)) < 0) {
Error( "Could not allocate converted input samples (error '%s')\n",
av_make_error_string(ret).c_str() );
av_freep(converted_input_samples);
free(converted_input_samples);
return;
}
Debug(2, "Success opening AAC codec");
}
av_dict_free(&opts);
} else {
Error( "could not allocate codec context for AAC\n");
}
} else {
Error( "could not find codec for AAC\n");
if ( ! setup_resampler() ) {
return;
}
#else
Error("Not built with libswresample library. Cannot do audio conversion to AAC");
audio_output_stream = NULL;
#endif
} else {
Debug(3, "Got AAC" );
@ -390,32 +184,32 @@ Debug(2, "Have audio_output_context");
if ( ! audio_output_stream ) {
Error("Unable to create audio out stream\n");
audio_output_stream = NULL;
}
audio_output_context = audio_output_stream->codec;
ret = avcodec_copy_context(audio_output_context, audio_input_context);
if (ret < 0) {
Fatal("Unable to copy audio context %s\n", av_make_error_string(ret).c_str());
}
audio_output_context->codec_tag = 0;
if ( audio_output_context->channels > 1 ) {
Warning("Audio isn't mono, changing it.");
audio_output_context->channels = 1;
} else {
Debug(3, "Audio is mono");
}
audio_output_context = audio_output_stream->codec;
ret = avcodec_copy_context(audio_output_context, audio_input_context);
if (ret < 0) {
Error("Unable to copy audio context %s\n", av_make_error_string(ret).c_str());
audio_output_stream = NULL;
} else {
audio_output_context->codec_tag = 0;
if ( audio_output_context->channels > 1 ) {
Warning("Audio isn't mono, changing it.");
audio_output_context->channels = 1;
} else {
Debug(3, "Audio is mono");
}
}
} // end if audio_output_stream
} // end if is AAC
if ( audio_output_stream ) {
if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
audio_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
if ( audio_output_stream ) {
if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
audio_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
}
} else {
Debug(3, "No Audio output stream");
audio_output_stream = NULL;
}
} // end if audio_input_stream
/* open the output file, if needed */
if (!(output_format->flags & AVFMT_NOFILE)) {
@ -451,17 +245,11 @@ if ( audio_output_stream ) {
previous_pts = 0;
previous_dts = 0;
filter_in_rescale_delta_last = AV_NOPTS_VALUE;
// now - when streaming started
//startTime=av_gettime()-nStartTime;//oc->start_time;
//Info("VideoStore startTime=%d\n",startTime);
} // VideoStore::VideoStore
VideoStore::~VideoStore(){
if ( audio_output_codec ) {
Debug(1, "Have audio encoder, need to flush it's output" );
// Do we need to flush the outputs? I have no idea.
AVPacket pkt;
int got_packet;
@ -471,7 +259,11 @@ Debug(1, "Have audio encoder, need to flush it's output" );
int64_t size;
while(1) {
#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0)
ret = avcodec_receive_packet( audio_output_context, &pkt );
#else
ret = avcodec_encode_audio2( audio_output_context, &pkt, NULL, &got_packet );
#endif
if (ret < 0) {
Error("ERror encoding audio while flushing");
break;
@ -513,6 +305,10 @@ Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts
}
if (audio_output_stream) {
avcodec_close(audio_output_context);
if ( resample_context ) {
avresample_close( resample_context );
avresample_free( &resample_context );
}
}
// WHen will be not using a file ?
@ -527,10 +323,198 @@ Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts
/* free the stream */
avformat_free_context(oc);
}
#ifdef HAVE_LIBSWRESAMPLE
if ( resample_context )
swr_free( &resample_context );
bool VideoStore::setup_resampler() {
#ifdef HAVE_LIBAVRESAMPLE
static char error_buffer[256];
audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if ( ! audio_output_codec ) {
Error("Could not find codec for AAC");
return false;
}
Debug(2, "Have audio output codec");
audio_output_stream = avformat_new_stream( oc, audio_output_codec );
audio_output_context = audio_output_stream->codec;
if ( ! audio_output_context ) {
Error( "could not allocate codec context for AAC\n");
audio_output_stream = NULL;
return false;
}
Debug(2, "Have audio_output_context");
AVDictionary *opts = NULL;
av_dict_set(&opts, "strict", "experimental", 0);
/* put sample parameters */
audio_output_context->bit_rate = audio_input_context->bit_rate;
audio_output_context->sample_rate = audio_input_context->sample_rate;
audio_output_context->channels = audio_input_context->channels;
audio_output_context->channel_layout = audio_input_context->channel_layout;
audio_output_context->sample_fmt = audio_input_context->sample_fmt;
//audio_output_context->refcounted_frames = 1;
if (audio_output_codec->supported_samplerates) {
int found = 0;
for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) {
if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) {
found = 1;
break;
}
}
if ( found ) {
Debug(3, "Sample rate is good");
} else {
audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0];
Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] );
}
}
/* check that the encoder supports s16 pcm input */
if (!check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt)) {
Debug( 3, "Encoder does not support sample format %s, setting to FLTP",
av_get_sample_fmt_name( audio_output_context->sample_fmt));
audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP;
}
//audio_output_stream->time_base = audio_input_stream->time_base;
audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate };
Debug(3, "Audio Time bases input stream (%d/%d) input codec: (%d/%d) output_stream (%d/%d) output codec (%d/%d)",
audio_input_stream->time_base.num,
audio_input_stream->time_base.den,
audio_input_context->time_base.num,
audio_input_context->time_base.den,
audio_output_stream->time_base.num,
audio_output_stream->time_base.den,
audio_output_context->time_base.num,
audio_output_context->time_base.den
);
ret = avcodec_open2(audio_output_context, audio_output_codec, &opts );
av_dict_free(&opts);
if ( ret < 0 ) {
av_strerror(ret, error_buffer, sizeof(error_buffer));
Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer );
audio_output_codec = NULL;
audio_output_context = NULL;
audio_output_stream = NULL;
return false;
}
Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d), refcounted_frames(%d)",
audio_output_context->bit_rate,
audio_output_context->sample_rate,
audio_output_context->channels,
audio_output_context->sample_fmt,
audio_output_context->channel_layout,
audio_output_context->frame_size,
audio_output_context->refcounted_frames
);
output_frame_size = audio_output_context->frame_size;
/** Create a new frame to store the audio samples. */
if (!(input_frame = zm_av_frame_alloc())) {
Error("Could not allocate input frame");
return false;
}
/** Create a new frame to store the audio samples. */
if (!(output_frame = zm_av_frame_alloc())) {
Error("Could not allocate output frame");
av_frame_free(&input_frame);
return false;
}
// Setup the audio resampler
resample_context = avresample_alloc_context();
if ( ! resample_context ) {
Error( "Could not allocate resample context\n");
return false;
}
// Some formats (i.e. WAV) do not produce the proper channel layout
if ( audio_input_context->channel_layout == 0 ) {
Error( "Bad channel layout. Need to set it to mono.\n");
av_opt_set_int( resample_context, "in_channel_layout", av_get_channel_layout( "mono" ), 0 );
} else {
av_opt_set_int( resample_context, "in_channel_layout", audio_input_context->channel_layout, 0 );
}
av_opt_set_int( resample_context, "in_sample_fmt", audio_input_context->sample_fmt, 0);
av_opt_set_int( resample_context, "in_sample_rate", audio_input_context->sample_rate, 0);
av_opt_set_int( resample_context, "in_channels", audio_input_context->channels,0);
//av_opt_set_int( resample_context, "out_channel_layout", audio_output_context->channel_layout, 0);
av_opt_set_int( resample_context, "out_channel_layout", av_get_channel_layout( "mono" ), 0 );
av_opt_set_int( resample_context, "out_sample_fmt", audio_output_context->sample_fmt, 0);
av_opt_set_int( resample_context, "out_sample_rate", audio_output_context->sample_rate, 0);
av_opt_set_int( resample_context, "out_channels", audio_output_context->channels, 0);
ret = avresample_open( resample_context );
if ( ret < 0 ) {
Error( "Could not open resample context\n");
return false;
}
#if 0
/**
* Allocate as many pointers as there are audio channels.
* Each pointer will later point to the audio samples of the corresponding
* channels (although it may be NULL for interleaved formats).
*/
if (!( converted_input_samples = (uint8_t *)calloc( audio_output_context->channels, sizeof(*converted_input_samples))) ) {
Error( "Could not allocate converted input sample pointers\n");
return;
}
/**
* Allocate memory for the samples of all channels in one consecutive
* block for convenience.
*/
if ((ret = av_samples_alloc( &converted_input_samples, NULL,
audio_output_context->channels,
audio_output_context->frame_size,
audio_output_context->sample_fmt, 0)) < 0) {
Error( "Could not allocate converted input samples (error '%s')\n",
av_make_error_string(ret).c_str() );
av_freep(converted_input_samples);
free(converted_input_samples);
return;
}
#endif
output_frame->nb_samples = audio_output_context->frame_size;
output_frame->format = audio_output_context->sample_fmt;
output_frame->channel_layout = audio_output_context->channel_layout;
// The codec gives us the frame size, in samples, we calculate the size of the samples buffer in bytes
unsigned int audioSampleBuffer_size = av_samples_get_buffer_size( NULL, audio_output_context->channels, audio_output_context->frame_size, audio_output_context->sample_fmt, 0 );
converted_input_samples = (uint8_t*) av_malloc( audioSampleBuffer_size );
if ( !converted_input_samples ) {
Error( "Could not allocate converted input sample pointers\n");
return false;
}
// Setup the data pointers in the AVFrame
if ( avcodec_fill_audio_frame(
output_frame,
audio_output_context->channels,
audio_output_context->sample_fmt,
(const uint8_t*) converted_input_samples,
audioSampleBuffer_size, 0 ) < 0 ) {
Error( "Could not allocate converted input sample pointers\n");
return false;
}
return true;
#else
Error("Not built with libavresample library. Cannot do audio conversion to AAC");
return false;
#endif
}
@ -554,6 +538,8 @@ void VideoStore::dumpPacket( AVPacket *pkt ){
int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
av_init_packet(&opkt);
int duration;
//Scale the PTS of the outgoing packet to be the correct time base
if (ipkt->pts != AV_NOPTS_VALUE) {
@ -571,6 +557,7 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
}
}
Debug(3, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, video_last_pts );
duration = ipkt->pts - video_last_pts;
video_last_pts = ipkt->pts;
} else {
Debug(3, "opkt.pts = undef");
@ -583,7 +570,8 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
if ( ! video_last_dts ) {
// This is the first packet.
opkt.dts = 0;
Debug(1, "Starting video video_last_pts will become (%d)", ipkt->dts );
Debug(1, "Starting video video_last_dts will become (%d)", ipkt->dts );
video_last_dts = ipkt->dts;
} else {
if ( ipkt->dts == AV_NOPTS_VALUE ) {
// why are we using cur_dts instead of packet.dts? I think cur_dts is in AV_TIME_BASE_Q, but ipkt.dts is in video_input_stream->time_base
@ -615,7 +603,11 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
opkt.dts = opkt.pts;
}
opkt.duration = av_rescale_q(ipkt->duration, video_input_stream->time_base, video_output_stream->time_base);
if ( ipkt->duration == AV_NOPTS_VALUE ) {
opkt.duration = av_rescale_q( duration, video_input_stream->time_base, video_output_stream->time_base);
} else {
opkt.duration = av_rescale_q(ipkt->duration, video_input_stream->time_base, video_output_stream->time_base);
}
opkt.flags = ipkt->flags;
opkt.pos=-1;
@ -630,26 +622,10 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
opkt.stream_index = ipkt->stream_index;
}
/*opkt.flags |= AV_PKT_FLAG_KEY;*/
#if 0
if (video_output_context->codec_type == AVMEDIA_TYPE_VIDEO && (output_format->flags & AVFMT_RAWPICTURE)) {
AVPicture pict;
Debug(3, "video and RAWPICTURE");
/* store AVPicture in AVPacket, as expected by the output format */
avpicture_fill(&pict, opkt.data, video_output_context->pix_fmt, video_output_context->width, video_output_context->height, 0);
av_image_fill_arrays(
opkt.data = (uint8_t *)&pict;
opkt.size = sizeof(AVPicture);
opkt.flags |= AV_PKT_FLAG_KEY;
} else {
Debug(4, "Not video and RAWPICTURE");
}
#endif
AVPacket safepkt;
memcpy(&safepkt, &opkt, sizeof(AVPacket));
Debug(1, "writing video packet pts(%d) dts(%d) duration(%d)", opkt.pts, opkt.dts, opkt.duration );
if ((opkt.data == NULL)||(opkt.size < 1)) {
Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ );
dumpPacket( ipkt);
@ -685,15 +661,138 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) {
Debug(1, "Called writeAudioFramePacket when no audio_output_stream");
return 0;//FIXME -ve return codes do not free packet in ffmpeg_camera at the moment
}
/*if(!keyframeMessage)
return -1;*/
//zm_dump_stream_format( oc, ipkt->stream_index, 0, 1 );
av_init_packet(&opkt);
Debug(5, "after init packet" );
#if 1
//Scale the PTS of the outgoing packet to be the correct time base
if ( audio_output_codec ) {
#if 0
ret = avcodec_send_packet( audio_input_context, ipkt );
if ( ret < 0 ) {
Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str());
return 0;
}
ret = avcodec_receive_frame( audio_input_context, input_frame );
if ( ret < 0 ) {
Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str());
return 0;
}
Debug(2, "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d) refd(%d)",
input_frame->nb_samples,
input_frame->format,
input_frame->sample_rate,
input_frame->channel_layout,
audio_output_context->refcounted_frames
);
ret = avcodec_send_frame( audio_output_context, input_frame );
if ( ret < 0 ) {
av_frame_unref( input_frame );
Error("avcodec_send_frame fail(%d), %s codec is open(%d) is_encoder(%d)", ret, av_make_error_string(ret).c_str(),
avcodec_is_open( audio_output_context ),
av_codec_is_encoder( audio_output_context->codec)
);
return 0;
}
ret = avcodec_receive_packet( audio_output_context, &opkt );
if ( ret < 0 ) {
av_frame_unref( input_frame );
Error("avcodec_receive_packet fail %s", av_make_error_string(ret).c_str());
return 0;
}
av_frame_unref( input_frame );
#else
/**
* Decode the audio frame stored in the packet.
* The input audio stream decoder is used to do this.
* If we are at the end of the file, pass an empty packet to the decoder
* to flush it.
*/
if ((ret = avcodec_decode_audio4(audio_input_context, input_frame,
&data_present, ipkt)) < 0) {
Error( "Could not decode frame (error '%s')\n",
av_make_error_string(ret).c_str());
dumpPacket( ipkt );
av_frame_free(&input_frame);
zm_av_packet_unref(&opkt);
return 0;
}
if ( ! data_present ) {
Debug(2, "Not ready to transcode a frame yet.");
zm_av_packet_unref(&opkt);
return 0;
}
int frame_size = input_frame->nb_samples;
Debug(4, "Frame size: %d", frame_size );
// Resample the input into the audioSampleBuffer until we proceed the whole decoded data
if ( (ret = avresample_convert( resample_context,
NULL,
0,
0,
input_frame->data,
0,
input_frame->nb_samples )) < 0 ) {
Error( "Could not resample frame (error '%s')\n",
av_make_error_string(ret).c_str());
return 0;
}
if ( avresample_available( resample_context ) < output_frame->nb_samples ) {
Debug(1, "No enough samples yet");
return 0;
}
// Read a frame audio data from the resample fifo
if ( avresample_read( resample_context, output_frame->data, output_frame->nb_samples ) != output_frame->nb_samples ) {
Warning( "Error reading resampled audio: " );
return 0;
}
av_init_packet(&opkt);
Debug(5, "after init packet" );
/** Set a timestamp based on the sample rate for the container. */
//output_frame->pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base );
// convert the packet to the codec timebase from the stream timebase
//Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts,
//av_frame_get_best_effort_timestamp(output_frame)
//);
/**
* Encode the audio frame and store it in the temporary packet.
* The output audio stream encoder is used to do this.
*/
#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0)
if (( ret = avcodec_receive_packet( audio_output_context, &opkt )) < 0 ) {
#else
if (( ret = avcodec_encode_audio2( audio_output_context, &opkt, output_frame, &data_present )) < 0) {
#endif
Error( "Could not encode frame (error '%s')",
av_make_error_string(ret).c_str());
zm_av_packet_unref(&opkt);
return 0;
}
if ( ! data_present ) {
Debug(2, "Not ready to output a frame yet.");
zm_av_packet_unref(&opkt);
return 0;
}
#endif
} else {
av_init_packet(&opkt);
Debug(5, "after init packet" );
opkt.data = ipkt->data;
opkt.size = ipkt->size;
}
// PTS is difficult, because of the buffering of the audio packets in the resampler. So we have to do it once we actually have a packet...
//Scale the PTS of the outgoing packet to be the correct time base
if ( ipkt->pts != AV_NOPTS_VALUE ) {
if ( !audio_last_pts ) {
opkt.pts = 0;
@ -739,193 +838,15 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) {
Debug(1,"opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts );
opkt.dts = opkt.pts;
}
//opkt.pts = AV_NOPTS_VALUE;
//opkt.dts = AV_NOPTS_VALUE;
// I wonder if we could just use duration instead of all the hoop jumping above?
// I wonder if we could just use duration instead of all the hoop jumping above?
opkt.duration = av_rescale_q(ipkt->duration, audio_input_stream->time_base, audio_output_stream->time_base);
#else
#endif
// pkt.pos: byte position in stream, -1 if unknown
opkt.pos = -1;
opkt.flags = ipkt->flags;
opkt.stream_index = ipkt->stream_index;
Debug(2, "Stream index is %d", opkt.stream_index );
if ( audio_output_codec ) {
#ifdef HAVE_LIBSWRESAMPLE
// Need to re-encode
#if 0
ret = avcodec_send_packet( audio_input_context, ipkt );
if ( ret < 0 ) {
Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str());
return 0;
}
ret = avcodec_receive_frame( audio_input_context, input_frame );
if ( ret < 0 ) {
Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str());
return 0;
}
Debug(2, "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d) refd(%d)",
input_frame->nb_samples,
input_frame->format,
input_frame->sample_rate,
input_frame->channel_layout,
audio_output_context->refcounted_frames
);
ret = avcodec_send_frame( audio_output_context, input_frame );
if ( ret < 0 ) {
av_frame_unref( input_frame );
Error("avcodec_send_frame fail(%d), %s codec is open(%d) is_encoder(%d)", ret, av_make_error_string(ret).c_str(),
avcodec_is_open( audio_output_context ),
av_codec_is_encoder( audio_output_context->codec)
);
return 0;
}
ret = avcodec_receive_packet( audio_output_context, &opkt );
if ( ret < 0 ) {
av_frame_unref( input_frame );
Error("avcodec_receive_packet fail %s", av_make_error_string(ret).c_str());
return 0;
}
av_frame_unref( input_frame );
#else
/**
* Decode the audio frame stored in the packet.
* The input audio stream decoder is used to do this.
* If we are at the end of the file, pass an empty packet to the decoder
* to flush it.
*/
if ((ret = avcodec_decode_audio4(audio_input_context, input_frame,
&data_present, ipkt)) < 0) {
Error( "Could not decode frame (error '%s')\n",
av_make_error_string(ret).c_str());
dumpPacket( ipkt );
av_frame_free(&input_frame);
zm_av_packet_unref(&opkt);
return 0;
}
if ( ! data_present ) {
Debug(2, "Not ready to transcode a frame yet.");
zm_av_packet_unref(&opkt);
return 0;
}
int frame_size = input_frame->nb_samples;
Debug(4, "Frame size: %d", frame_size );
Debug(4, "About to convert");
/** Convert the samples using the resampler. */
if ((ret = swr_convert(resample_context,
&converted_input_samples, frame_size,
(const uint8_t **)input_frame->extended_data , frame_size)) < 0) {
Error( "Could not convert input samples (error '%s')\n",
av_make_error_string(ret).c_str()
);
return 0;
}
Debug(4, "About to realloc");
if ((ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + frame_size)) < 0) {
Error( "Could not reallocate FIFO to %d\n", av_audio_fifo_size(fifo) + frame_size );
return 0;
}
/** Store the new samples in the FIFO buffer. */
Debug(4, "About to write");
if (av_audio_fifo_write(fifo, (void **)&converted_input_samples, frame_size) < frame_size) {
Error( "Could not write data to FIFO\n");
return 0;
}
/**
* Set the frame's parameters, especially its size and format.
* av_frame_get_buffer needs this to allocate memory for the
* audio samples of the frame.
* Default channel layouts based on the number of channels
* are assumed for simplicity.
*/
output_frame->nb_samples = audio_output_context->frame_size;
output_frame->channel_layout = audio_output_context->channel_layout;
output_frame->channels = audio_output_context->channels;
output_frame->format = audio_output_context->sample_fmt;
output_frame->sample_rate = audio_output_context->sample_rate;
/**
* Allocate the samples of the created frame. This call will make
* sure that the audio frame can hold as many samples as specified.
*/
Debug(4, "getting buffer");
if (( ret = av_frame_get_buffer( output_frame, 0)) < 0) {
Error( "Couldnt allocate output frame buffer samples (error '%s')",
av_make_error_string(ret).c_str() );
Error("Frame: samples(%d) layout (%d) format(%d) rate(%d)", output_frame->nb_samples,
output_frame->channel_layout, output_frame->format , output_frame->sample_rate
);
zm_av_packet_unref(&opkt);
return 0;
}
Debug(4, "About to read");
if (av_audio_fifo_read(fifo, (void **)output_frame->data, frame_size) < frame_size) {
Error( "Could not read data from FIFO\n");
return 0;
}
/** Set a timestamp based on the sample rate for the container. */
output_frame->pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base );
// convert the packet to the codec timebase from the stream timebase
Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts,
av_frame_get_best_effort_timestamp(output_frame)
);
/**
* Encode the audio frame and store it in the temporary packet.
* The output audio stream encoder is used to do this.
*/
if (( ret = avcodec_encode_audio2( audio_output_context, &opkt,
output_frame, &data_present )) < 0) {
Error( "Could not encode frame (error '%s')",
av_make_error_string(ret).c_str());
zm_av_packet_unref(&opkt);
return 0;
}
if ( ! data_present ) {
Debug(2, "Not ready to output a frame yet.");
zm_av_packet_unref(&opkt);
return 0;
}
Debug(2, "opkt dts (%d) pts(%d) duration:(%d)", opkt.dts, opkt.pts, opkt.duration );
// Convert tb from code back to stream
//av_packet_rescale_ts(&opkt, audio_output_context->time_base, audio_output_stream->time_base);
if (opkt.pts != AV_NOPTS_VALUE) {
opkt.pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base);
}
if ( opkt.dts != AV_NOPTS_VALUE)
opkt.dts = av_rescale_q( opkt.dts, audio_output_context->time_base, audio_output_stream->time_base);
if ( opkt.duration > 0)
opkt.duration = av_rescale_q( opkt.duration, audio_output_context->time_base, audio_output_stream->time_base);
Debug(2, "opkt dts (%d) pts(%d) duration:(%d) pos(%d) ", opkt.dts, opkt.pts, opkt.duration, opkt.pos );
//opkt.dts = AV_NOPTS_VALUE;
#endif
#endif
} else {
opkt.data = ipkt->data;
opkt.size = ipkt->size;
}
Debug(2, "Stream index is %d", opkt.stream_index );
AVPacket safepkt;
memcpy(&safepkt, &opkt, sizeof(AVPacket));
@ -938,4 +859,5 @@ Debug(2, "opkt dts (%d) pts(%d) duration:(%d) pos(%d) ", opkt.dts, opkt.pts, opk
}
zm_av_packet_unref(&opkt);
return 0;
}
} // end int VideoStore::writeAudioFramePacket( AVPacket *ipkt )

View File

@ -8,6 +8,9 @@ extern "C" {
#ifdef HAVE_LIBSWRESAMPLE
#include "libswresample/swresample.h"
#endif
#ifdef HAVE_LIBAVRESAMPLE
#include "libavresample/avresample.h"
#endif
}
#if HAVE_LIBAVCODEC
@ -44,7 +47,10 @@ private:
AVAudioFifo *fifo;
int output_frame_size;
#ifdef HAVE_LIBSWRESAMPLE
SwrContext *resample_context = NULL;
//SwrContext *resample_context = NULL;
#endif
#ifdef HAVE_LIBAVRESAMPLE
AVAudioResampleContext* resample_context;
#endif
uint8_t *converted_input_samples = NULL;
@ -66,6 +72,8 @@ private:
int64_t filter_in_rescale_delta_last;
bool setup_resampler();
public:
VideoStore(const char *filename_in, const char *format_in, AVStream *video_input_stream, AVStream *audio_input_stream, int64_t nStartTime, Monitor * p_monitor );
~VideoStore();

View File

@ -25,6 +25,7 @@
#include "zm_user.h"
#include "zm_signal.h"
#include "zm_monitor.h"
#include "zm_monitorstream.h"
bool ValidateAccess( User *user, int mon_id ) {
bool allowed = true;

View File

@ -1,14 +0,0 @@
<VirtualHost *:80>
DocumentRoot /usr/local/share/zoneminder/www
DirectoryIndex index.php
ScriptAlias /cgi-bin/ /usr/local/libexec/zoneminder/cgi-bin/
<Directory />
Require all granted
</Directory>
<Directory "/usr/local/libexec/zoneminder/cgi-bin">
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Require all granted
</Directory>
</VirtualHost>

View File

@ -27,6 +27,9 @@ mysql -u root < db/zm_create.sql
# Add the ZoneMinder DB user
mysql -u root -e "grant insert,select,update,delete,lock tables,alter on zm.* to 'zmuser'@'localhost' identified by 'zmpass';"
# Make ZM_LOGDIR
mkdir /var/log/zm
# Activate CGI
a2enmod cgi

View File

@ -72,14 +72,11 @@ if ( count($frames) ) {
<td class="colTimeStamp"><?php echo strftime( STRF_FMT_TIME, $frame['UnixTimeStamp'] ) ?></td>
<td class="colTimeDelta"><?php echo number_format( $frame['Delta'], 2 ) ?></td>
<?php
if ( ZM_RECORD_EVENT_STATS && ($frame['Type'] == 'Alarm') )
{
if ( ZM_RECORD_EVENT_STATS && ($frame['Type'] == 'Alarm') ) {
?>
<td class="colScore"><?php echo makePopupLink( '?view=stats&amp;eid='.$Event->Id().'&amp;fid='.$frame['FrameId'], 'zmStats', 'stats', $frame['Score'] ) ?></td>
<?php
}
else
{
} else {
?>
<td class="colScore"><?php echo $frame['Score'] ?></td>
<?php
@ -90,16 +87,14 @@ if ( count($frames) ) {
(ZM_WEB_LIST_THUMB_WIDTH?'width='.ZM_WEB_LIST_THUMB_WIDTH.'&amp;':'').
(ZM_WEB_LIST_THUMB_HEIGHT?'height='.ZM_WEB_LIST_THUMB_HEIGHT.'&amp;':'').'filename='.$Event->MonitorId().'_'.$frame['EventId'].'_'.$frame['FrameId'].'.jpg" '.
(ZM_WEB_LIST_THUMB_WIDTH?'width="'.ZM_WEB_LIST_THUMB_WIDTH.'" ':'').
(ZM_WEB_LIST_THUMB_HEIGHT?'height="'.ZM_WEB_LIST_THUMB_HEIGHT.'" ':'').'alt="'.$frame['FrameId'].'"/>' ) ?></td>
(ZM_WEB_LIST_THUMB_HEIGHT?'height="'.ZM_WEB_LIST_THUMB_HEIGHT.'" ':'').' alt="'.$frame['FrameId'].'"/>' ) ?></td>
<?php
}
}
?>
</tr>
<?php
}
}
else
{
} // end foreach frame
} else {
?>
<tr>
<td colspan="5"><?php echo translate('NoFramesRecorded') ?></td>

View File

@ -143,9 +143,7 @@ $frameSql = '
// This program only calls itself with the time range involved -- it does all monitors (the user can see, in the called group) all the time
if ( ! empty( $user['MonitorIds'] ) ) {
$monFilterSql = ' AND M.Id IN ('.$user['MonitorIds'].')';
$eventsSql .= $monFilterSql;
$eventsSql .= ' AND M.Id IN ('.$user['MonitorIds'].')';
$monitorsSql .= ' AND Id IN ('.$user['MonitorIds'].')';
$frameSql .= ' AND E.MonitorId IN ('.$user['MonitorIds'].')';
}

View File

@ -85,7 +85,8 @@ if ( empty($_REQUEST['path']) ) {
Debug( "$path does not exist");
# Generate the frame JPG
if ( $show == 'capture' and $Event->DefaultVideo() ) {
$command ='ffmpeg -i '.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path;
$command ='ffmpeg -ss '. $Frame->Delta() .' -i '.$Event->Path().'/'.$Event->DefaultVideo().' -frames:v 1 '.$path;
#$command ='ffmpeg -ss '. $Frame->Delta() .' -i '.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path;
#$command ='ffmpeg -v 0 -i '.$Storage->Path().'/'.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path;
Debug( "Running $command" );
$output = array();

View File

@ -53,8 +53,8 @@
#cmakedefine HAVE_LIBAVUTIL_MATHEMATICS_H 1
#cmakedefine HAVE_LIBSWSCALE 1
#cmakedefine HAVE_LIBSWSCALE_SWSCALE_H 1
#cmakedefine HAVE_LIBSWRESAMPLE 1
#cmakedefine HAVE_LIBSWRESAMPLE_SWRESAMPLE_H 1
#cmakedefine HAVE_LIBAVRESAMPLE 1
#cmakedefine HAVE_LIBAVRESAMPLE_AVRESAMPLE_H 1
#cmakedefine HAVE_LIBVLC 1
#cmakedefine HAVE_VLC_VLC_H 1
#cmakedefine HAVE_LIBX264 1