Merge branch 'video' into feature-h264-videostorage

Conflicts:
	src/CMakeLists.txt
	src/Makefile.am
	src/zm_event.cpp
	web/skins/classic/views/js/event.js
pull/618/head
SteveGilvarry 2014-12-02 20:58:35 +11:00
commit bf33eab17c
19 changed files with 1231 additions and 173 deletions

View File

@ -264,6 +264,59 @@ else(MYSQLCLIENT_LIBRARIES)
message(FATAL_ERROR "zm requires mysqlclient but it was not found on your system")
endif(MYSQLCLIENT_LIBRARIES)
# x264 (using find_library and find_path)
find_library(X264_LIBRARIES x264)
if(X264_LIBRARIES)
set(HAVE_LIBX264 1)
list(APPEND ZM_BIN_LIBS "${X264_LIBRARIES}")
find_path(X264_INCLUDE_DIR x264.h)
if(X264_INCLUDE_DIR)
include_directories("${X264_INCLUDE_DIR}")
set(CMAKE_REQUIRED_INCLUDES "${X264_INCLUDE_DIR}")
endif(X264_INCLUDE_DIR)
mark_as_advanced(FORCE X264_LIBRARIES X264_INCLUDE_DIR)
check_include_files("stdint.h;x264.h" HAVE_X264_H)
set(optlibsfound "${optlibsfound} x264")
else(X264_LIBRARIES)
set(optlibsnotfound "${optlibsnotfound} x264")
endif(X264_LIBRARIES)
# mp4v2 (using find_library and find_path)
find_library(MP4V2_LIBRARIES mp4v2)
if(MP4V2_LIBRARIES)
set(HAVE_LIBMP4V2 1)
list(APPEND ZM_BIN_LIBS "${MP4V2_LIBRARIES}")
# mp4v2/mp4v2.h
find_path(MP4V2_INCLUDE_DIR mp4v2/mp4v2.h)
if(MP4V2_INCLUDE_DIR)
include_directories("${MP4V2_INCLUDE_DIR}")
set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}")
endif(MP4V2_INCLUDE_DIR)
check_include_file("mp4v2/mp4v2.h" HAVE_MP4V2_MP4V2_H)
# mp4v2.h
find_path(MP4V2_INCLUDE_DIR mp4v2.h)
if(MP4V2_INCLUDE_DIR)
include_directories("${MP4V2_INCLUDE_DIR}")
set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}")
endif(MP4V2_INCLUDE_DIR)
check_include_file("mp4v2.h" HAVE_MP4V2_H)
# mp4.h
find_path(MP4V2_INCLUDE_DIR mp4.h)
if(MP4V2_INCLUDE_DIR)
include_directories("${MP4V2_INCLUDE_DIR}")
set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}")
endif(MP4V2_INCLUDE_DIR)
check_include_file("mp4.h" HAVE_MP4_H)
mark_as_advanced(FORCE MP4V2_LIBRARIES MP4V2_INCLUDE_DIR)
set(optlibsfound "${optlibsfound} mp4v2")
else(MP4V2_LIBRARIES)
set(optlibsnotfound "${optlibsnotfound} mp4v2")
endif(MP4V2_LIBRARIES)
set(PATH_FFMPEG "")
set(OPT_FFMPEG "no")
# Do not check for ffmpeg if ZM_NO_FFMPEG is on

View File

@ -326,6 +326,7 @@ fi
AC_CHECK_LIB(pcre,pcre_compile,,AC_MSG_WARN(libpcre.a may be required for remote/network camera support))
AC_CHECK_LIB(z,zlibVersion)
AC_CHECK_LIB(x264,x264_predict_16x16_init)
AC_CHECK_LIB(mp4v2,MP4AddH264VideoTrack)
AC_CHECK_LIB(avutil,av_malloc,,AC_MSG_WARN(libavutil.a may be required for MPEG streaming))
# Don't bother to warn about this one
AC_CHECK_LIB(avcore,av_image_copy,,)
@ -375,6 +376,8 @@ AC_CHECK_HEADERS(sys/ipc.h,,,)
AC_CHECK_HEADERS(sys/shm.h,,,)
fi
AC_CHECK_HEADERS(zlib.h,,,)
AC_CHECK_HEADERS(x264.h,,,)
AC_CHECK_HEADERS([mp4v2/mp4v2.h mp4v2.h mp4.h],,,)
AC_CHECK_HEADERS(vlc/vlc.h,,,)
AC_CHECK_HEADERS(curl/curl.h,,,)

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_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_videostore.cpp zm_zone.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_ffmpeg.cpp zm_mpeg.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)
# A fix for cmake recompiling the source files for every target.
add_library(zm STATIC ${ZM_BIN_SRC_FILES})

View File

@ -60,6 +60,7 @@ zm_SOURCES = \
zm_timer.cpp \
zm_user.cpp \
zm_utils.cpp \
zm_video.cpp \
zm_videostore.cpp \
zm_zone.cpp
@ -117,6 +118,7 @@ noinst_HEADERS = \
zm_timer.h \
zm_user.h \
zm_utils.h \
zm_video.h \
zm_videostore.h \
zm_zone.h

View File

@ -48,6 +48,7 @@ bool Event::initialised = false;
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];
int Event::pre_alarm_count = 0;
Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = { { 0 } };
@ -162,6 +163,48 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string
Fatal( "Can't fopen %s: %s", id_file, strerror(errno));
}
last_db_frame = 0;
video_name[0] = 0;
/* Save as video */
if ( monitor->GetOptVideoWriter() != 0 ) {
int nRet;
snprintf( video_name, sizeof(video_name), "%d-%s", id, "video.mp4" );
snprintf( video_file, sizeof(video_file), video_file_format, path, video_name );
snprintf( timecodes_name, sizeof(timecodes_name), "%d-%s", id, "video.timecodes" );
snprintf( timecodes_file, sizeof(timecodes_file), video_file_format, path, timecodes_name );
/* X264 MP4 video writer */
if(monitor->GetOptVideoWriter() == 1) {
#if ZM_HAVE_VIDEOWRITER_X264MP4
videowriter = new X264MP4Writer(video_file, monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder(), monitor->GetOptEncoderParams());
#else
videowriter = NULL;
Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)");
#endif
}
if(videowriter != NULL) {
/* Open the video stream */
nRet = videowriter->Open();
if(nRet != 0) {
Error("Failed opening video stream");
delete videowriter;
videowriter = NULL;
}
/* Create timecodes file */
timecodes_fd = fopen(timecodes_file, "wb");
if(timecodes_fd == NULL) {
Error("Failed creating timecodes file");
}
}
} else {
/* No video object */
videowriter = NULL;
}
}
Event::~Event()
@ -181,12 +224,28 @@ Event::~Event()
}
}
/* Close the video file */
if ( videowriter != NULL ) {
int nRet;
nRet = videowriter->Close();
if(nRet != 0) {
Error("Failed closing video stream");
}
delete videowriter;
videowriter = NULL;
/* Close the timecodes file */
fclose(timecodes_fd);
timecodes_fd = NULL;
}
static char sql[ZM_SQL_MED_BUFSIZ];
struct DeltaTimeval delta_time;
DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 );
snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, 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 );
snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, video_name, id );
if ( mysql_query( &dbconn, sql ) )
{
Error( "Can't update event: %s", mysql_error( &dbconn ) );
@ -369,6 +428,40 @@ bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char
return( true );
}
bool Event::WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow )
{
const Image* frameimg = image;
Image ts_image;
/* Checking for invalid parameters */
if ( videow == NULL ) {
Error("NULL Video object");
return false;
}
/* If the image does not contain a timestamp, add the timestamp */
if (!config.timestamp_on_capture) {
ts_image = *image;
monitor->TimestampImage( &ts_image, &timestamp );
frameimg = &ts_image;
}
/* Calculate delta time */
struct DeltaTimeval delta_time3;
DELTA_TIMEVAL( delta_time3, timestamp, start_time, DT_PREC_3 );
unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec;
/* Encode and write the frame */
if(videowriter->Encode(image, timeMS) != 0) {
Error("Failed encoding video frame");
}
/* Add the frame to the timecodes file */
fprintf(timecodes_fd, "%u\n", timeMS);
return( true );
}
void Event::updateNotes( const StringSetMap &newNoteSetMap )
{
bool update = false;
@ -510,23 +603,27 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st
}
frames++;
static char event_file[PATH_MAX];
snprintf( event_file, sizeof(event_file), capture_file_format, path, frames );
if(videoEvent){
/* FIXME Comeback and fix either videoEvent or videowriter not both */
if(videoEvent){
//If this is the first frame, we should add a thumbnail to the event directory
if(frames == 10){
char snapshot_file[PATH_MAX];
snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path );
WriteFrameImage( images[i], *(timestamps[i]), snapshot_file );
}
}else{
Debug( 1, "Writing pre-capture frame %d", frames );
WriteFrameImage( images[i], *(timestamps[i]), event_file );
Debug( 1, "Writing pre-capture frame %d", frames );
if ( monitor->GetOptSaveJPEGs() & 1) {
WriteFrameImage( images[i], *(timestamps[i]), event_file );
}
if ( videowriter != NULL ) {
WriteFrameVideo( images[i], *(timestamps[i]), videowriter );
}
struct DeltaTimeval delta_time;
DELTA_TIMEVAL( delta_time, *(timestamps[i]), start_time, DT_PREC_2 );
@ -562,8 +659,7 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
}
frames++;
static char event_file[PATH_MAX];
snprintf( event_file, sizeof(event_file), capture_file_format, path, frames );
@ -577,9 +673,14 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
}else{
Debug( 1, "Writing capture frame %d", frames );
if( monitor->GetOptSaveJPEGs() & 1) {
WriteFrameImage( image, timestamp, event_file );
}
if ( videowriter != NULL ) {
WriteFrameVideo( image, timestamp, videowriter );
}
struct DeltaTimeval delta_time;
DELTA_TIMEVAL( delta_time, timestamp, start_time, DT_PREC_2 );
@ -626,7 +727,9 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
snprintf( event_file, sizeof(event_file), analyse_file_format, path, frames );
Debug( 1, "Writing analysis frame %d", frames );
WriteFrameImage( alarm_image, timestamp, event_file, true );
if ( monitor->GetOptSaveJPEGs() & 2) {
WriteFrameImage( alarm_image, timestamp, event_file, true );
}
}
}
@ -674,7 +777,6 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
*/
}
bool EventStream::loadInitialEventData( int monitor_id, time_t event_time )
{
static char sql[ZM_SQL_SML_BUFSIZ];

View File

@ -37,6 +37,7 @@
#include "zm.h"
#include "zm_image.h"
#include "zm_stream.h"
#include "zm_video.h"
class Zone;
class Monitor;
@ -55,6 +56,7 @@ protected:
static char capture_file_format[PATH_MAX];
static char analyse_file_format[PATH_MAX];
static char general_file_format[PATH_MAX];
static char video_file_format[PATH_MAX];
protected:
static int sd;
@ -90,6 +92,12 @@ protected:
unsigned int tot_score;
unsigned int max_score;
char path[PATH_MAX];
VideoWriter* videowriter;
FILE* timecodes_fd;
char video_name[PATH_MAX];
char video_file[PATH_MAX];
char timecodes_name[PATH_MAX];
char timecodes_file[PATH_MAX];
protected:
int last_db_frame;
@ -103,6 +111,7 @@ protected:
snprintf( capture_file_format, sizeof(capture_file_format), "%%s/%%0%dd-capture.jpg", config.event_image_digits );
snprintf( analyse_file_format, sizeof(analyse_file_format), "%%s/%%0%dd-analyse.jpg", config.event_image_digits );
snprintf( general_file_format, sizeof(general_file_format), "%%s/%%0%dd-%%s", config.event_image_digits );
snprintf( video_file_format, sizeof(video_file_format), "%%s/%%s");
initialised = true;
}
@ -128,6 +137,7 @@ public:
bool SendFrameImage( const Image *image, bool alarm_frame=false );
bool WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame=false );
bool WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow );
void updateNotes( const StringSetMap &stringSetMap );

View File

@ -23,6 +23,16 @@
#if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE
void FFMPEGInit() {
static bool bInit = false;
if(!bInit) {
av_register_all();
av_log_set_level(AV_LOG_DEBUG);
bInit = true;
}
}
#if HAVE_LIBAVUTIL
enum PixelFormat GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder) {
enum PixelFormat pf;
@ -124,10 +134,7 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint
Error("NULL Input or output buffer");
return -1;
}
if(in_pf == 0 || out_pf == 0) {
Error("Invalid input or output pixel formats");
return -2;
}
if(!width || !height) {
Error("Invalid width or height");
return -3;
@ -156,7 +163,7 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint
}
/* Get the context */
swscale_ctx = sws_getCachedContext( NULL, width, height, in_pf, width, height, out_pf, 0, NULL, NULL, NULL );
swscale_ctx = sws_getCachedContext( swscale_ctx, width, height, in_pf, width, height, out_pf, SWS_FAST_BILINEAR, NULL, NULL, NULL );
if(swscale_ctx == NULL) {
Error("Failed getting swscale context");
return -6;

View File

@ -99,6 +99,8 @@ extern "C" {
#define SWS_CPU_CAPS_SSE2 0x02000000
#endif
/* A single function to initialize ffmpeg, to avoid multiple initializations */
void FFMPEGInit();
#if HAVE_LIBAVUTIL
enum PixelFormat GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder);

View File

@ -28,6 +28,7 @@
#include "zm_mpeg.h"
#include "zm_signal.h"
#include "zm_monitor.h"
#include "zm_video.h"
#if ZM_HAS_V4L
#include "zm_local_camera.h"
#endif // ZM_HAS_V4L
@ -265,6 +266,9 @@ Monitor::Monitor(
Camera *p_camera,
int p_orientation,
unsigned int p_deinterlacing,
int p_savejpegs,
int p_videowriter,
std::string p_encoderparams,
const char *p_event_prefix,
const char *p_label_format,
const Coord &p_label_coord,
@ -294,6 +298,9 @@ Monitor::Monitor(
height( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Width():p_camera->Height() ),
orientation( (Orientation)p_orientation ),
deinterlacing( p_deinterlacing ),
savejpegspref( p_savejpegs ),
videowriterpref( p_videowriter ),
encoderparams( p_encoderparams ),
label_coord( p_label_coord ),
image_buffer_count( p_image_buffer_count ),
warmup_count( p_warmup_count ),
@ -344,6 +351,9 @@ Monitor::Monitor(
}
}
/* Parse encoder parameters */
ParseEncoderParameters(encoderparams.c_str(), &encoderparamsvec);
fps = 0.0;
event_count = 0;
image_count = 0;
@ -1870,11 +1880,11 @@ int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose
static char sql[ZM_SQL_MED_BUFSIZ];
if ( !device[0] )
{
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Function != 'None' and Type = 'Local' order by Device, Channel", sizeof(sql) );
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Function != 'None' and Type = 'Local' order by Device, Channel", sizeof(sql) );
}
else
{
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Function != 'None' and Type = 'Local' and Device = '%s' order by Channel", device );
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Function != 'None' and Type = 'Local' and Device = '%s' order by Channel", device );
}
if ( mysql_query( &dbconn, sql ) )
{
@ -1933,6 +1943,11 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
int palette = atoi(dbrow[col]); col++;
Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
unsigned int deinterlacing = atoi(dbrow[col]); col++;
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
int hue = atoi(dbrow[col]); col++;
@ -2001,6 +2016,9 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
camera,
orientation,
deinterlacing,
savejpegs,
videowriter,
encoderparams,
event_prefix,
label_format,
Coord( label_x, label_y ),
@ -2046,11 +2064,11 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
static char sql[ZM_SQL_MED_BUFSIZ];
if ( !protocol )
{
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Remote'", sizeof(sql) );
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Remote'", sizeof(sql) );
}
else
{
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Remote' and Protocol = '%s' and Host = '%s' and Port = '%s' and Path = '%s'", protocol, host, port, path );
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Remote' and Protocol = '%s' and Host = '%s' and Port = '%s' and Path = '%s'", protocol, host, port, path );
}
if ( mysql_query( &dbconn, sql ) )
{
@ -2089,7 +2107,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
int colours = atoi(dbrow[col]); col++;
/* int palette = atoi(dbrow[col]); */ col++;
Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
unsigned int deinterlacing = atoi(dbrow[col]); col++;
unsigned int deinterlacing = atoi(dbrow[col]); col++;
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
int hue = atoi(dbrow[col]); col++;
@ -2174,6 +2197,9 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
camera,
orientation,
deinterlacing,
savejpegs,
videowriter,
encoderparams,
event_prefix.c_str(),
label_format.c_str(),
Coord( label_x, label_y ),
@ -2219,11 +2245,11 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu
static char sql[ZM_SQL_MED_BUFSIZ];
if ( !file[0] )
{
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'File'", sizeof(sql) );
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'File'", sizeof(sql) );
}
else
{
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'File' and Path = '%s'", file );
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'File' and Path = '%s'", file );
}
if ( mysql_query( &dbconn, sql ) )
{
@ -2259,6 +2285,11 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu
/* int palette = atoi(dbrow[col]); */ col++;
Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
unsigned int deinterlacing = atoi(dbrow[col]); col++;
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
int hue = atoi(dbrow[col]); col++;
@ -2311,6 +2342,9 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu
camera,
orientation,
deinterlacing,
savejpegs,
videowriter,
encoderparams,
event_prefix,
label_format,
Coord( label_x, label_y ),
@ -2356,11 +2390,11 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
static char sql[ZM_SQL_MED_BUFSIZ];
if ( !file[0] )
{
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Ffmpeg'", sizeof(sql) );
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Ffmpeg'", sizeof(sql) );
}
else
{
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Ffmpeg' and Path = '%s'", file );
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Ffmpeg' and Path = '%s'", file );
}
if ( mysql_query( &dbconn, sql ) )
{
@ -2398,6 +2432,11 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
/* int palette = atoi(dbrow[col]); */ col++;
Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
unsigned int deinterlacing = atoi(dbrow[col]); col++;
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
int hue = atoi(dbrow[col]); col++;
@ -2452,6 +2491,9 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
camera,
orientation,
deinterlacing,
savejpegs,
videowriter,
encoderparams,
event_prefix,
label_format,
Coord( label_x, label_y ),
@ -2495,7 +2537,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
Monitor *Monitor::Load( int id, bool load_zones, Purpose purpose )
{
static char sql[ZM_SQL_MED_BUFSIZ];
snprintf( sql, sizeof(sql), "select Id, Name, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = %d", id );
snprintf( sql, sizeof(sql), "select Id, Name, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = %d", id );
if ( mysql_query( &dbconn, sql ) )
{
Error( "Can't run query: %s", mysql_error( &dbconn ) );
@ -2562,6 +2604,11 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
int palette = atoi(dbrow[col]); col++;
Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
unsigned int deinterlacing = atoi(dbrow[col]); col++;
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
int hue = atoi(dbrow[col]); col++;
@ -2765,6 +2812,9 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
camera,
orientation,
deinterlacing,
savejpegs,
videowriter,
encoderparams,
event_prefix.c_str(),
label_format.c_str(),
Coord( label_x, label_y ),

View File

@ -234,6 +234,12 @@ protected:
unsigned int v4l_captures_per_frame;
Orientation orientation; // Whether the image has to be rotated at all
unsigned int deinterlacing;
int savejpegspref;
int videowriterpref;
std::string encoderparams;
std::vector<EncoderParameter_t> encoderparamsvec;
int brightness; // The statically saved brightness of the camera
int contrast; // The statically saved contrast of the camera
int hue; // The statically saved hue of the camera
@ -312,7 +318,7 @@ protected:
public:
// OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info.
//bool OurCheckAlarms( Zone *zone, const Image *pImage );
Monitor( int p_id, const char *p_name, int p_function, bool p_enabled, const char *p_linked_monitors, Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, const char *p_event_prefix, const char *p_label_format, const Coord &p_label_coord, int p_image_buffer_count, int p_warmup_count, int p_pre_event_count, int p_post_event_count, int p_stream_replay_buffer, int p_alarm_frame_count, int p_section_length, int p_frame_skip, int p_motion_frame_skip, int p_capture_delay, int p_alarm_capture_delay, int p_fps_report_interval, int p_ref_blend_perc, int p_alarm_ref_blend_perc, bool p_track_motion, Rgb p_signal_check_colour, Purpose p_purpose, int p_n_zones=0, Zone *p_zones[]=0 );
Monitor( int p_id, const char *p_name, int p_function, bool p_enabled, const char *p_linked_monitors, Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, int p_savejpegs, int p_videowriter, std::string p_encoderparams, const char *p_event_prefix, const char *p_label_format, const Coord &p_label_coord, int p_image_buffer_count, int p_warmup_count, int p_pre_event_count, int p_post_event_count, int p_stream_replay_buffer, int p_alarm_frame_count, int p_section_length, int p_frame_skip, int p_motion_frame_skip, int p_capture_delay, int p_alarm_capture_delay, int p_fps_report_interval, int p_ref_blend_perc, int p_alarm_ref_blend_perc, bool p_track_motion, Rgb p_signal_check_colour, Purpose p_purpose, int p_n_zones=0, Zone *p_zones[]=0 );
~Monitor();
void AddZones( int p_n_zones, Zone *p_zones[] );
@ -362,7 +368,10 @@ public:
unsigned int Height() const { return( height ); }
unsigned int Colours() const { return( camera->Colours() ); }
unsigned int SubpixelOrder() const { return( camera->SubpixelOrder() ); }
int GetOptSaveJPEGs() const { return( savejpegspref ); }
int GetOptVideoWriter() const { return( videowriterpref ); }
const std::vector<EncoderParameter_t>* GetOptEncoderParams() const { return( &encoderparamsvec ); }
State GetState() const;
int GetImage( int index=-1, int scale=100 ) const;

518
src/zm_video.cpp Normal file
View File

@ -0,0 +1,518 @@
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "zm.h"
#include "zm_video.h"
#include "zm_image.h"
#include "zm_utils.h"
#include "zm_rgb.h"
#include <sstream>
VideoWriter::VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder) :
container(p_container), codec(p_codec), path(p_path), width(p_width), height(p_height), colours(p_colours), subpixelorder(p_subpixelorder), frame_count(0) {
Debug(7,"Video object created");
/* Parameter checking */
if(path.empty()) {
Error("Invalid file path");
}
if(!width || !height) {
Error("Invalid width or height");
}
}
VideoWriter::~VideoWriter() {
Debug(7,"Video object destroyed");
}
int VideoWriter::Reset(const char* new_path) {
/* Common variables reset */
/* If there is a new path, use it */
if(new_path != NULL) {
path = new_path;
}
/* Reset frame counter */
frame_count = 0;
return 0;
}
#if ZM_HAVE_VIDEOWRITER_X264MP4
X264MP4Writer::X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector<EncoderParameter_t>* p_user_params) : VideoWriter("mp4", "h264", p_path, p_width, p_height, p_colours, p_subpixelorder), bOpen(false), bGotH264AVCInfo(false), bFirstFrame(true) {
/* Initialize ffmpeg if it hasn't been initialized yet */
FFMPEGInit();
/* Initialize swscale */
zm_pf = GetFFMPEGPixelFormat(colours,subpixelorder);
if(zm_pf == 0) {
Error("Unable to match ffmpeg pixelformat");
}
codec_pf = PIX_FMT_YUV420P;
swscaleobj.SetDefaults(zm_pf, codec_pf, width, height);
/* Calculate the image sizes. We will need this for parameter checking */
zm_imgsize = colours * width * height;
codec_imgsize = avpicture_get_size( codec_pf, width, height);
if(!codec_imgsize) {
Error("Failed calculating codec pixel format image size");
}
/* If supplied with user parameters to the encoder, copy them */
if(p_user_params != NULL) {
user_params = *p_user_params;
}
/* Setup x264 parameters */
if(x264config() < 0) {
Error("Failed setting x264 parameters");
}
/* Allocate x264 input picture */
x264_picture_alloc(&x264picin, X264_CSP_I420, x264params.i_width, x264params.i_height);
}
X264MP4Writer::~X264MP4Writer() {
/* Free x264 input picture */
x264_picture_clean(&x264picin);
if(bOpen)
Close();
}
int X264MP4Writer::Open() {
/* Open the encoder */
x264enc = x264_encoder_open(&x264params);
if(x264enc == NULL) {
Error("Failed opening x264 encoder");
return -1;
}
// Debug(4,"x264 maximum delayed frames: %d",x264_encoder_maximum_delayed_frames(x264enc));
x264_nal_t* nals;
int i_nals;
if(!x264_encoder_headers(x264enc,&nals,&i_nals)) {
Error("Failed getting encoder headers");
return -2;
}
/* Search SPS NAL for AVC information */
for(int i=0;i<i_nals;i++) {
if(nals[i].i_type == NAL_SPS) {
x264_profleindication = nals[i].p_payload[5];
x264_profilecompat = nals[i].p_payload[6];
x264_levelindication = nals[i].p_payload[7];
bGotH264AVCInfo = true;
break;
}
}
if(!bGotH264AVCInfo) {
Warning("Missing AVC information");
}
/* Create the file */
mp4h = MP4Create((path + ".incomplete").c_str());
if(mp4h == MP4_INVALID_FILE_HANDLE) {
Error("Failed creating mp4 file: %s",path.c_str());
return -10;
}
/* Set the global timescale */
if(!MP4SetTimeScale(mp4h, 1000)) {
Error("Failed setting timescale");
return -11;
}
/* Set the global video profile */
/* I am a bit confused about this one.
I couldn't find what the value should be
Some use 0x15 while others use 0x7f. */
MP4SetVideoProfileLevel(mp4h, 0x7f);
/* Add H264 video track */
mp4vtid = MP4AddH264VideoTrack(mp4h,1000,MP4_INVALID_DURATION,width,height,x264_profleindication,x264_profilecompat,x264_levelindication,3);
if(mp4vtid == MP4_INVALID_TRACK_ID) {
Error("Failed adding H264 video track");
return -12;
}
bOpen = true;
return 0;
}
int X264MP4Writer::Close() {
/* Flush all pending frames */
for(int i = (x264_encoder_delayed_frames(x264enc) + 1); i > 0; i-- ) {
x264encodeloop(true);
}
/* Close the encoder */
x264_encoder_close(x264enc);
/* Close MP4 handle */
MP4Close(mp4h);
/* Required for proper HTTP streaming */
MP4Optimize((path + ".incomplete").c_str(), path.c_str());
/* Delete the temporary file */
unlink((path + ".incomplete").c_str());
bOpen = false;
Debug(7, "Video closed. Total frames: %d", frame_count);
return 0;
}
int X264MP4Writer::Reset(const char* new_path) {
/* Close the encoder and file */
if(bOpen)
Close();
/* Reset common variables */
VideoWriter::Reset(new_path);
/* Reset local variables */
bFirstFrame = true;
bGotH264AVCInfo = false;
prevnals.clear();
prevpayload.clear();
/* Reset x264 parameters */
x264config();
/* Open the encoder */
Open();
return 0;
}
int X264MP4Writer::Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) {
/* Parameter checking */
if(data == NULL) {
Error("NULL buffer");
return -1;
}
if(data_size != zm_imgsize) {
Error("The data buffer size does not match the expected size. Expected: %d Current: %d", zm_imgsize, data_size);
return -2;
}
if(!bOpen) {
Warning("The encoder was not initialized, initializing now");
Open();
}
/* Convert the image into the x264 input picture */
if(swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0) {
Error("Image conversion failed");
return -3;
}
/* Set PTS */
x264picin.i_pts = frame_time;
/* Do the encoding */
x264encodeloop();
/* Increment frame counter */
frame_count++;
return 0;
}
int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) {
if(img->Width() != width) {
Error("Source image width differs. Source: %d Output: %d",img->Width(), width);
return -12;
}
if(img->Height() != height) {
Error("Source image height differs. Source: %d Output: %d",img->Height(), height);
return -13;
}
return Encode(img->Buffer(),img->Size(),frame_time);
}
int X264MP4Writer::x264config() {
/* Sets up the encoder configuration */
int x264ret;
/* Defaults */
const char* preset = "veryfast";
const char* tune = "stillimage";
const char* profile = "main";
/* Search the user parameters for preset, tune and profile */
for(unsigned int i=0; i < user_params.size(); i++) {
if(strcmp(user_params[i].pname, "preset") == 0) {
/* Got preset */
preset = user_params[i].pvalue;
} else if(strcmp(user_params[i].pname, "tune") == 0) {
/* Got tune */
tune = user_params[i].pvalue;
} else if(strcmp(user_params[i].pname, "profile") == 0) {
/* Got profile */
profile = user_params[i].pvalue;
}
}
/* Set the defaults and preset and tune */
x264ret = x264_param_default_preset(&x264params, preset, tune);
if(x264ret != 0) {
Error("Failed setting x264 preset %s and tune %s : %d",preset,tune,x264ret);
}
/* Set the profile */
x264ret = x264_param_apply_profile(&x264params, profile);
if(x264ret != 0) {
Error("Failed setting x264 profile %s : %d",profile,x264ret);
}
/* Input format */
x264params.i_width = width;
x264params.i_height = height;
x264params.i_csp = X264_CSP_I420;
/* Quality control */
x264params.rc.i_rc_method = X264_RC_CRF;
x264params.rc.f_rf_constant = 23.0;
/* Enable b-frames */
x264params.i_bframe = 16;
x264params.i_bframe_adaptive = 1;
/* Timebase */
x264params.i_timebase_num = 1;
x264params.i_timebase_den = 1000;
/* Enable variable frame rate */
x264params.b_vfr_input = 1;
/* Disable annex-b (start codes) */
x264params.b_annexb = 0;
/* TODO: Setup error handler */
//x264params.i_log_level = X264_LOG_DEBUG;
/* Process user parameters (excluding preset, tune and profile) */
for(unsigned int i=0; i < user_params.size(); i++) {
/* Skip preset, tune and profile */
if( (strcmp(user_params[i].pname, "preset") == 0) || (strcmp(user_params[i].pname, "tune") == 0) || (strcmp(user_params[i].pname, "profile") == 0) ) {
continue;
}
/* Pass the name and value to x264 */
x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue);
/* Error checking */
if(x264ret != 0) {
if(x264ret == X264_PARAM_BAD_NAME) {
Error("Failed processing x264 user parameter %s=%s : Bad name", user_params[i].pname, user_params[i].pvalue);
} else if(x264ret == X264_PARAM_BAD_VALUE) {
Error("Failed processing x264 user parameter %s=%s : Bad value", user_params[i].pname, user_params[i].pvalue);
} else {
Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)", user_params[i].pname, user_params[i].pvalue, x264ret);
}
}
}
return 0;
}
void X264MP4Writer::x264encodeloop(bool bFlush) {
x264_nal_t* nals;
int i_nals;
int frame_size;
if(bFlush) {
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout);
} else {
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout);
}
if (frame_size > 0 || bFlush) {
Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",frame_count, x264picout.i_pts, x264picout.i_dts, frame_size);
/* Handle the previous frame */
if(!bFirstFrame) {
buffer.clear();
/* Process the NALs for the previous frame */
for(unsigned int i=0; i < prevnals.size(); i++) {
Debug(9,"Processing NAL: Type %d Size %d",prevnals[i].i_type,prevnals[i].i_payload);
switch(prevnals[i].i_type) {
case NAL_PPS:
/* PPS NAL */
MP4AddH264PictureParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
break;
case NAL_SPS:
/* SPS NAL */
MP4AddH264SequenceParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
break;
default:
/* Anything else, hopefully frames, so copy it into the sample */
buffer.append(prevnals[i].p_payload, prevnals[i].i_payload);
}
}
/* Calculate frame duration and offset */
int duration = x264picout.i_dts - prevDTS;
int offset = prevPTS - prevDTS;
/* Write the sample */
if(!buffer.empty()) {
if(!MP4WriteSample(mp4h, mp4vtid, buffer.extract(buffer.size()), buffer.size(), duration, offset, prevKeyframe)) {
Error("Failed writing sample");
}
}
/* Cleanup */
prevnals.clear();
prevpayload.clear();
}
/* Got a frame. Copy this new frame into the previous frame */
if(frame_size > 0) {
/* Copy the NALs and the payloads */
for(int i=0;i<i_nals;i++) {
prevnals.push_back(nals[i]);
prevpayload.append(nals[i].p_payload, nals[i].i_payload);
}
/* Update the payload pointers */
/* This is done in a separate loop because the previous loop might reallocate memory when appending,
making the pointers invalid */
unsigned int payload_offset = 0;
for(unsigned int i=0;i<prevnals.size();i++) {
prevnals[i].p_payload = prevpayload.head() + payload_offset;
payload_offset += nals[i].i_payload;
}
/* We need this for the next frame */
prevPTS = x264picout.i_pts;
prevDTS = x264picout.i_dts;
prevKeyframe = x264picout.b_keyframe;
bFirstFrame = false;
}
} else if(frame_size == 0) {
Debug(7,"x264 encode returned zero. Delayed frames: %d",x264_encoder_delayed_frames(x264enc));
} else {
Error("x264 encode failed: %d",frame_size);
}
}
#endif // ZM_VIDEOWRITER_X264MP4
int ParseEncoderParameters(const char* str, std::vector<EncoderParameter_t>* vec) {
if(vec == NULL) {
Error("NULL Encoder parameters vector pointer");
return -1;
}
if(str == NULL) {
Error("NULL Encoder parameters string");
return -2;
}
vec->clear();
if(str[0] == 0) {
/* Empty */
return 0;
}
std::string line;
std::stringstream ss(str);
size_t valueoffset;
size_t valuelen;
unsigned int lineno = 0;
EncoderParameter_t param;
while(std::getline(ss, line) ) {
lineno++;
/* Remove CR if exists */
if(line.length() >= 1 && line[line.length()-1] == '\r') {
line.erase(line.length()-1);
}
/* Skip comments and empty lines */
if(line.empty() || line[0] == '#') {
continue;
}
valueoffset = line.find('=');
if(valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0) {
Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno);
continue;
}
if(valueoffset > (sizeof(param.pname)-1) ) {
Warning("Failed parsing encoder parameters line %d: Name too long", lineno);
continue;
}
valuelen = line.length() - (valueoffset+1);
if( valuelen > (sizeof(param.pvalue)-1) ) {
Warning("Failed parsing encoder parameters line %d: Value too long", lineno);
continue;
}
/* Copy and NULL terminate */
line.copy(param.pname, valueoffset, 0);
line.copy(param.pvalue, valuelen, valueoffset+1);
param.pname[valueoffset] = 0;
param.pvalue[valuelen] = 0;
/* Push to the vector */
vec->push_back(param);
Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue);
}
Debug(7, "Parsed %d lines", lineno);
return 0;
}

173
src/zm_video.h Normal file
View File

@ -0,0 +1,173 @@
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef ZM_VIDEO_H
#define ZM_VIDEO_H
#include "zm.h"
#include "zm_rgb.h"
#include "zm_utils.h"
#include "zm_ffmpeg.h"
#include "zm_buffer.h"
/*
#define HAVE_LIBX264 1
#define HAVE_LIBMP4V2 1
#define HAVE_X264_H 1
#define HAVE_MP4_H 1
*/
#if HAVE_MP4V2_MP4V2_H
#include <mp4v2/mp4v2.h>
#endif
#if HAVE_MP4V2_H
#include <mp4v2.h>
#endif
#if HAVE_MP4_H
#include <mp4.h>
#endif
#if HAVE_X264_H
#ifdef __cplusplus
extern "C" {
#endif
#include <x264.h>
#ifdef __cplusplus
}
#endif
#endif
/* Structure for user parameters to the encoder */
struct EncoderParameter_t {
char pname[48];
char pvalue[48];
};
int ParseEncoderParameters(const char* str, std::vector<EncoderParameter_t>* vec);
/* VideoWriter is a generic interface that ZM uses to save events as videos */
/* It is relatively simple and the functions are pure virtual, so they must be implemented by the deriving class */
class VideoWriter {
protected:
std::string container;
std::string codec;
std::string path;
unsigned int width;
unsigned int height;
unsigned int colours;
unsigned int subpixelorder;
unsigned int frame_count;
public:
VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder);
virtual ~VideoWriter();
virtual int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) = 0;
virtual int Encode(const Image* img, const unsigned int frame_time) = 0;
virtual int Open() = 0;
virtual int Close() = 0;
virtual int Reset(const char* new_path = NULL);
const char* GetContainer() const {
return container.c_str();
}
const char* GetCodec() const {
return codec.c_str();
}
const char* GetPath() const {
return path.c_str();
}
unsigned int GetWidth() const {
return width;
}
unsigned int GetHeight() const {
return height;
}
unsigned int GetColours() const {
return colours;
}
unsigned int GetSubpixelorder () const {
return subpixelorder;
}
unsigned int GetFrameCount() const {
return frame_count;
}
};
#if HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE
#define ZM_HAVE_VIDEOWRITER_X264MP4 1
class X264MP4Writer : public VideoWriter {
protected:
bool bOpen;
bool bGotH264AVCInfo;
bool bFirstFrame;
/* SWScale */
SWScale swscaleobj;
enum PixelFormat zm_pf;
enum PixelFormat codec_pf;
size_t codec_imgsize;
size_t zm_imgsize;
/* User parameters */
std::vector<EncoderParameter_t> user_params;
/* AVC Information */
uint8_t x264_profleindication;
uint8_t x264_profilecompat;
uint8_t x264_levelindication;
/* NALs */
Buffer buffer;
/* Previous frame */
int prevPTS;
int prevDTS;
bool prevKeyframe;
Buffer prevpayload;
std::vector<x264_nal_t> prevnals;
/* Internal functions */
int x264config();
void x264encodeloop(bool bFlush = false);
/* x264 objects */
x264_t* x264enc;
x264_param_t x264params;
x264_picture_t x264picin;
x264_picture_t x264picout;
/* MP4v2 objects */
MP4FileHandle mp4h;
MP4TrackId mp4vtid;
public:
X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector<EncoderParameter_t>* p_user_params = NULL);
~X264MP4Writer();
int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time);
int Encode(const Image* img, const unsigned int frame_time);
int Open();
int Close();
int Reset(const char* new_path = NULL);
};
#endif // HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE
#endif // ZM_VIDEO_H

View File

@ -384,31 +384,33 @@ function getNearEvents()
else
$midSql = '';
$sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc');
$sql = "select E.* as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc');
$result = dbQuery( $sql );
while ( $id = dbFetchNext( $result, 'Id' ) )
{
if ( $id == $eventId )
{
$prevId = dbFetchNext( $result, 'Id' );
$prevEvent = dbFetchNext( $result );
break;
}
}
$sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder";
$sql = "select E.* as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder";
$result = dbQuery( $sql );
while ( $id = dbFetchNext( $result, 'Id' ) )
{
if ( $id == $eventId )
{
$nextId = dbFetchNext( $result, 'Id' );
$nextEvent = dbFetchNext( $result );
break;
}
}
$result = array( 'EventId'=>$eventId );
$result['PrevEventId'] = empty($prevId)?0:$prevId;
$result['NextEventId'] = empty($nextId)?0:$nextId;
$result['PrevEventId'] = empty($prevEvent)?0:$prevEvent['Id'];
$result['NextEventId'] = empty($nextEvent)?0:$nextEvent['Id'];
$result['PrevEventDefVideoPath'] = empty($prevEvent)?0:(getEventDefaultVideoPath($prevEvent));
$result['NextEventDefVideoPath'] = empty($nextEvent)?0:(getEventDefaultVideoPath($nextEvent));
return( $result );
}

View File

@ -494,6 +494,9 @@ function getEventPath( $event )
return( $eventPath );
}
function getEventDefaultVideoPath( $event ) {
return ZM_DIR_EVENTS . "/" . getEventPath($event) . "/" . $event['DefaultVideo'];
}
function deletePath( $path )
{

View File

@ -57,6 +57,23 @@
text-align: right;
}
#videoBar1 div {
text-align: center;
float: center;
}
#videoBar1 #prevEvent {
float: left;
}
#videoBar1 #dlEvent {
float: center;
}
#videoBar1 #nextEvent {
float: right;
}
#imageFeed {
text-align: center;
}

View File

@ -10,55 +10,55 @@
//
// 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
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
if ( !canView( 'Events' ) )
{
$view = "error";
return;
$view = "error";
return;
}
$eid = validInt( $_REQUEST['eid'] );
$fid = !empty($_REQUEST['fid'])?validInt($_REQUEST['fid']):1;
if ( $user['MonitorIds'] )
$midSql = " and MonitorId in (".join( ",", preg_split( '/["\'\s]*,["\'\s]*/', dbEscape($user['MonitorIds']) ) ).")";
$midSql = " and MonitorId in (".join( ",", preg_split( '/["\'\s]*,["\'\s]*/', dbEscape($user['MonitorIds']) ) ).")";
else
$midSql = '';
$midSql = '';
$sql = 'SELECT E.*,M.Name AS MonitorName,M.DefaultRate,M.DefaultScale FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE E.Id = ?'.$midSql;
$event = dbFetchOne( $sql, NULL, array($eid) );
if ( isset( $_REQUEST['rate'] ) )
$rate = validInt($_REQUEST['rate']);
$rate = validInt($_REQUEST['rate']);
else
$rate = reScale( RATE_BASE, $event['DefaultRate'], ZM_WEB_DEFAULT_RATE );
$rate = reScale( RATE_BASE, $event['DefaultRate'], ZM_WEB_DEFAULT_RATE );
if ( isset( $_REQUEST['scale'] ) )
$scale = validInt($_REQUEST['scale']);
$scale = validInt($_REQUEST['scale']);
else
$scale = reScale( SCALE_BASE, $event['DefaultScale'], ZM_WEB_DEFAULT_SCALE );
$scale = reScale( SCALE_BASE, $event['DefaultScale'], ZM_WEB_DEFAULT_SCALE );
$replayModes = array(
'single' => $SLANG['ReplaySingle'],
'all' => $SLANG['ReplayAll'],
'gapless' => $SLANG['ReplayGapless'],
'single' => $SLANG['ReplaySingle'],
'all' => $SLANG['ReplayAll'],
'gapless' => $SLANG['ReplayGapless'],
);
if ( isset( $_REQUEST['streamMode'] ) )
$streamMode = validHtmlStr($_REQUEST['streamMode']);
$streamMode = validHtmlStr($_REQUEST['streamMode']);
else
$streamMode = canStream()?'stream':'stills';
$streamMode = canStream()?'stream':'stills';
if ( isset( $_REQUEST['replayMode'] ) )
$replayMode = validHtmlStr($_REQUEST['replayMode']);
$replayMode = validHtmlStr($_REQUEST['replayMode']);
if ( isset( $_COOKIE['replayMode']) && preg_match('#^[a-z]+$#', $_COOKIE['replayMode']) )
$replayMode = validHtmlStr($_COOKIE['replayMode']);
$replayMode = validHtmlStr($_COOKIE['replayMode']);
else {
$keys = array_keys( $replayModes );
$replayMode = array_shift( $keys );
@ -79,63 +79,64 @@ $focusWindow = true;
xhtmlHeaders(__FILE__, $SLANG['Event'] );
?>
<body>
<div id="page">
<div id="content">
<div id="dataBar">
<table id="dataTable" class="major" cellspacing="0">
<tr>
<td><span id="dataId" title="<?= $SLANG['Id'] ?>"><?= $event['Id'] ?></span></td>
<td><span id="dataCause" title="<?= $event['Notes']?validHtmlStr($event['Notes']):$SLANG['AttrCause'] ?>"><?= validHtmlStr($event['Cause']) ?></span></td>
<td><span id="dataTime" title="<?= $SLANG['Time'] ?>"><?= strftime( STRF_FMT_DATETIME_SHORT, strtotime($event['StartTime'] ) ) ?></span></td>
<td><span id="dataDuration" title="<?= $SLANG['Duration'] ?>"><?= $event['Length'] ?></span>s</td>
<td><span id="dataFrames" title="<?= $SLANG['AttrFrames']."/".$SLANG['AttrAlarmFrames'] ?>"><?= $event['Frames'] ?>/<?= $event['AlarmFrames'] ?></span></td>
<td><span id="dataScore" title="<?= $SLANG['AttrTotalScore']."/".$SLANG['AttrAvgScore']."/".$SLANG['AttrMaxScore'] ?>"><?= $event['TotScore'] ?>/<?= $event['AvgScore'] ?>/<?= $event['MaxScore'] ?></span></td>
</tr>
</table>
</div>
<div id="menuBar1">
<div id="scaleControl"><label for="scale"><?= $SLANG['Scale'] ?></label><?= buildSelect( "scale", $scales, "changeScale();" ); ?></div>
<div id="replayControl"><label for="replayMode"><?= $SLANG['Replay'] ?></label><?= buildSelect( "replayMode", $replayModes, "changeReplayMode();" ); ?></div>
<div id="nameControl"><input type="text" id="eventName" name="eventName" value="<?= validHtmlStr($event['Name']) ?>" size="16"/><input type="button" value="<?= $SLANG['Rename'] ?>" onclick="renameEvent()"<?php if ( !canEdit( 'Events' ) ) { ?> disabled="disabled"<?php } ?>/></div>
</div>
<div id="menuBar2">
<div id="closeWindow"><a href="#" onclick="closeWindow();"><?= $SLANG['Close'] ?></a></div>
<div id="page">
<div id="content">
<div id="dataBar">
<table id="dataTable" class="major" cellspacing="0">
<tr>
<td><span id="dataId" title="<?= $SLANG['Id'] ?>"><?= $event['Id'] ?></span></td>
<td><span id="dataCause" title="<?= $event['Notes']?validHtmlStr($event['Notes']):$SLANG['AttrCause'] ?>"><?= validHtmlStr($event['Cause']) ?></span></td>
<td><span id="dataTime" title="<?= $SLANG['Time'] ?>"><?= strftime( STRF_FMT_DATETIME_SHORT, strtotime($event['StartTime'] ) ) ?></span></td>
<td><span id="dataDuration" title="<?= $SLANG['Duration'] ?>"><?= $event['Length'] ?></span>s</td>
<td><span id="dataFrames" title="<?= $SLANG['AttrFrames']."/".$SLANG['AttrAlarmFrames'] ?>"><?= $event['Frames'] ?>/<?= $event['AlarmFrames'] ?></span></td>
<td><span id="dataScore" title="<?= $SLANG['AttrTotalScore']."/".$SLANG['AttrAvgScore']."/".$SLANG['AttrMaxScore'] ?>"><?= $event['TotScore'] ?>/<?= $event['AvgScore'] ?>/<?= $event['MaxScore'] ?></span></td>
</tr>
</table>
</div>
<div id="menuBar1">
<div id="scaleControl"><label for="scale"><?= $SLANG['Scale'] ?></label><?= buildSelect( "scale", $scales, "changeScale();" ); ?></div>
<div id="replayControl"><label for="replayMode"><?= $SLANG['Replay'] ?></label><?= buildSelect( "replayMode", $replayModes, "changeReplayMode();" ); ?></div>
<div id="nameControl"><input type="text" id="eventName" name="eventName" value="<?= validHtmlStr($event['Name']) ?>" size="16"/><input type="button" value="<?= $SLANG['Rename'] ?>" onclick="renameEvent()"<?php if ( !canEdit( 'Events' ) ) { ?> disabled="disabled"<?php } ?>/></div>
</div>
<div id="menuBar2">
<div id="closeWindow"><a href="#" onclick="closeWindow();"><?= $SLANG['Close'] ?></a></div>
<?php
if ( canEdit( 'Events' ) )
{
?>
<div id="deleteEvent"><a href="#" onclick="deleteEvent()"><?= $SLANG['Delete'] ?></a></div>
<div id="editEvent"><a href="#" onclick="editEvent()"><?= $SLANG['Edit'] ?></a></div>
<div id="deleteEvent"><a href="#" onclick="deleteEvent()"><?= $SLANG['Delete'] ?></a></div>
<div id="editEvent"><a href="#" onclick="editEvent()"><?= $SLANG['Edit'] ?></a></div>
<?php
}
if ( canView( 'Events' ) )
{
?>
<div id="exportEvent"><a href="#" onclick="exportEvent()"><?= $SLANG['Export'] ?></a></div>
<div id="exportEvent"><a href="#" onclick="exportEvent()"><?= $SLANG['Export'] ?></a></div>
<?php
}
if ( canEdit( 'Events' ) )
{
?>
<div id="archiveEvent" class="hidden"><a href="#" onclick="archiveEvent()"><?= $SLANG['Archive'] ?></a></div>
<div id="unarchiveEvent" class="hidden"><a href="#" onclick="unarchiveEvent()"><?= $SLANG['Unarchive'] ?></a></div>
<div id="archiveEvent" class="hidden"><a href="#" onclick="archiveEvent()"><?= $SLANG['Archive'] ?></a></div>
<div id="unarchiveEvent" class="hidden"><a href="#" onclick="unarchiveEvent()"><?= $SLANG['Unarchive'] ?></a></div>
<?php
}
?>
<div id="framesEvent"><a href="#" onclick="showEventFrames()"><?= $SLANG['Frames'] ?></a></div>
<div id="streamEvent"<?php if ( $streamMode == 'stream' ) { ?> class="hidden"<?php } ?>><a href="#" onclick="showStream()"><?= $SLANG['Stream'] ?></a></div>
<div id="stillsEvent"<?php if ( $streamMode == 'still' ) { ?> class="hidden"<?php } ?>><a href="#" onclick="showStills()"><?= $SLANG['Stills'] ?></a></div>
<div id="framesEvent"><a href="#" onclick="showEventFrames()"><?= $SLANG['Frames'] ?></a></div>
<div id="streamEvent"<?php if ( $streamMode == 'stream' ) { ?> class="hidden"<?php } ?>><a href="#" onclick="showStream()"><?= $SLANG['Stream'] ?></a></div>
<div id="stillsEvent"<?php if ( $streamMode == 'still' ) { ?> class="hidden"<?php } ?>><a href="#" onclick="showStills()"><?= $SLANG['Stills'] ?></a></div>
<div id="videoEvent"<?php if ( $streamMode == 'video' ) { ?> class="hidden"<?php } ?>><a href="#" onclick="showVideo()">HTML5Video</a></div>
<?php
if ( ZM_OPT_FFMPEG )
{
?>
<div id="videoEvent"><a href="#" onclick="videoEvent()"><?= $SLANG['Video'] ?></a></div>
<div id="videoEvent"><a href="#" onclick="videoEvent()"><?= $SLANG['Video'] ?></a></div>
<?php
}
?>
</div>
<div id="eventStream">
<div id="imageFeed">
</div>
<div id="eventStream">
<div id="imageFeed">
<?php
if(file_exists(ZM_PATH_WEB."/events/".getEventPath($event)."/event.mp4")){
?>
@ -153,87 +154,102 @@ Your browser does not support the video tag.
}else{
if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT )
{
$streamSrc = getStreamSrc( array( "source=event", "mode=mpeg", "event=".$eid, "frame=".$fid, "scale=".$scale, "rate=".$rate, "bitrate=".ZM_WEB_VIDEO_BITRATE, "maxfps=".ZM_WEB_VIDEO_MAXFPS, "format=".ZM_MPEG_REPLAY_FORMAT, "replay=".$replayMode ) );
outputVideoStream( "evtStream", $streamSrc, reScale( $event['Width'], $scale ), reScale( $event['Height'], $scale ), ZM_MPEG_LIVE_FORMAT );
$streamSrc = getStreamSrc( array( "source=event", "mode=mpeg", "event=".$eid, "frame=".$fid, "scale=".$scale, "rate=".$rate, "bitrate=".ZM_WEB_VIDEO_BITRATE, "maxfps=".ZM_WEB_VIDEO_MAXFPS, "format=".ZM_MPEG_REPLAY_FORMAT, "replay=".$replayMode ) );
outputVideoStream( "evtStream", $streamSrc, reScale( $event['Width'], $scale ), reScale( $event['Height'], $scale ), ZM_MPEG_LIVE_FORMAT );
}
else
{
$streamSrc = getStreamSrc( array( "source=event", "mode=jpeg", "event=".$eid, "frame=".$fid, "scale=".$scale, "rate=".$rate, "maxfps=".ZM_WEB_VIDEO_MAXFPS, "replay=".$replayMode) );
if ( canStreamNative() )
{
outputImageStream( "evtStream", $streamSrc, reScale( $event['Width'], $scale ), reScale( $event['Height'], $scale ), validHtmlStr($event['Name']) );
}
else
{
outputHelperStream( "evtStream", $streamSrc, reScale( $event['Width'], $scale ), reScale( $event['Height'], $scale ) );
}
$streamSrc = getStreamSrc( array( "source=event", "mode=jpeg", "event=".$eid, "frame=".$fid, "scale=".$scale, "rate=".$rate, "maxfps=".ZM_WEB_VIDEO_MAXFPS, "replay=".$replayMode) );
if ( canStreamNative() )
{
outputImageStream( "evtStream", $streamSrc, reScale( $event['Width'], $scale ), reScale( $event['Height'], $scale ), validHtmlStr($event['Name']) );
}
else
{
outputHelperStream( "evtStream", $streamSrc, reScale( $event['Width'], $scale ), reScale( $event['Height'], $scale ) );
}
}
}
?>
</div>
<p id="dvrControls">
<input type="button" value="&lt;+" id="prevBtn" title="<?= $SLANG['Prev'] ?>" class="inactive" onclick="streamPrev( true )"/>
<input type="button" value="&lt;&lt;" id="fastRevBtn" title="<?= $SLANG['Rewind'] ?>" class="inactive" disabled="disabled" onclick="streamFastRev( true )"/>
<input type="button" value="&lt;" id="slowRevBtn" title="<?= $SLANG['StepBack'] ?>" class="unavail" disabled="disabled" onclick="streamSlowRev( true )"/>
<input type="button" value="||" id="pauseBtn" title="<?= $SLANG['Pause'] ?>" class="inactive" onclick="streamPause( true )"/>
<input type="button" value="|>" id="playBtn" title="<?= $SLANG['Play'] ?>" class="active" disabled="disabled" onclick="streamPlay( true )"/>
<input type="button" value="&gt;" id="slowFwdBtn" title="<?= $SLANG['StepForward'] ?>" class="unavail" disabled="disabled" onclick="streamSlowFwd( true )"/>
<input type="button" value="&gt;&gt;" id="fastFwdBtn" title="<?= $SLANG['FastForward'] ?>" class="inactive" disabled="disabled" onclick="streamFastFwd( true )"/>
<input type="button" value="&ndash;" id="zoomOutBtn" title="<?= $SLANG['ZoomOut'] ?>" class="avail" onclick="streamZoomOut()"/>
<input type="button" value="+&gt;" id="nextBtn" title="<?= $SLANG['Next'] ?>" class="inactive" onclick="streamNext( true )"/>
</p>
<div id="replayStatus">
<span id="mode">Mode: <span id="modeValue">&nbsp;</span></span>
<span id="rate">Rate: <span id="rateValue"></span>x</span>
<span id="progress">Progress: <span id="progressValue"></span>s</span>
<span id="zoom">Zoom: <span id="zoomValue"></span>x</span>
</div>
<div id="progressBar" class="invisible">
</div>
<p id="dvrControls">
<input type="button" value="&lt;+" id="prevBtn" title="<?= $SLANG['Prev'] ?>" class="inactive" onclick="streamPrev( true )"/>
<input type="button" value="&lt;&lt;" id="fastRevBtn" title="<?= $SLANG['Rewind'] ?>" class="inactive" disabled="disabled" onclick="streamFastRev( true )"/>
<input type="button" value="&lt;" id="slowRevBtn" title="<?= $SLANG['StepBack'] ?>" class="unavail" disabled="disabled" onclick="streamSlowRev( true )"/>
<input type="button" value="||" id="pauseBtn" title="<?= $SLANG['Pause'] ?>" class="inactive" onclick="streamPause( true )"/>
<input type="button" value="|>" id="playBtn" title="<?= $SLANG['Play'] ?>" class="active" disabled="disabled" onclick="streamPlay( true )"/>
<input type="button" value="&gt;" id="slowFwdBtn" title="<?= $SLANG['StepForward'] ?>" class="unavail" disabled="disabled" onclick="streamSlowFwd( true )"/>
<input type="button" value="&gt;&gt;" id="fastFwdBtn" title="<?= $SLANG['FastForward'] ?>" class="inactive" disabled="disabled" onclick="streamFastFwd( true )"/>
<input type="button" value="&ndash;" id="zoomOutBtn" title="<?= $SLANG['ZoomOut'] ?>" class="avail" onclick="streamZoomOut()"/>
<input type="button" value="+&gt;" id="nextBtn" title="<?= $SLANG['Next'] ?>" class="inactive" onclick="streamNext( true )"/>
</p>
<div id="replayStatus">
<span id="mode">Mode: <span id="modeValue">&nbsp;</span></span>
<span id="rate">Rate: <span id="rateValue"></span>x</span>
<span id="progress">Progress: <span id="progressValue"></span>s</span>
<span id="zoom">Zoom: <span id="zoomValue"></span>x</span>
</div>
<div id="progressBar" class="invisible">
<?php
for ( $i = 0; $i < $panelSections; $i++ )
{
for ( $i = 0; $i < $panelSections; $i++ )
{
?>
<div class="progressBox" id="progressBox<?= $i ?>" title=""></div>
<div class="progressBox" id="progressBox<?= $i ?>" title=""></div>
<?php
}
}
?>
</div>
</div>
<div id="eventStills" class="hidden">
<div id="eventThumbsPanel">
<div id="eventThumbs">
</div>
</div>
<div id="eventImagePanel" class="hidden">
<div id="eventImageFrame">
<img id="eventImage" src="graphics/transparent.gif" alt=""/>
<div id="eventImageBar">
<div id="eventImageClose"><input type="button" value="<?= $SLANG['Close'] ?>" onclick="hideEventImage()"/></div>
<div id="eventImageStats" class="hidden"><input type="button" value="<?= $SLANG['Stats'] ?>" onclick="showFrameStats()"/></div>
<div id="eventImageData">Frame <span id="eventImageNo"></span></div>
</div>
</div>
</div>
<div id="eventImageNav">
<div id="eventImageButtons">
<div id="prevButtonsPanel">
<input id="prevEventBtn" type="button" value="&lt;E" onclick="prevEvent()" disabled="disabled"/>
<input id="prevThumbsBtn" type="button" value="&lt;&lt;" onclick="prevThumbs()" disabled="disabled"/>
<input id="prevImageBtn" type="button" value="&lt;" onclick="prevImage()" disabled="disabled"/>
<input id="nextImageBtn" type="button" value="&gt;" onclick="nextImage()" disabled="disabled"/>
<input id="nextThumbsBtn" type="button" value="&gt;&gt;" onclick="nextThumbs()" disabled="disabled"/>
<input id="nextEventBtn" type="button" value="E&gt;" onclick="nextEvent()" disabled="disabled"/>
</div>
</div>
<div id="thumbsSliderPanel">
<div id="thumbsSlider">
<div id="thumbsKnob">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php if ( $Monitor[VideoWriter} ) { ?>
<div id="eventVideo" class="hidden">
<div id="videoFeed">
<video id="videoobj" width="<?= $event['Width'] ?>" height="<?= $event['Height'] ?>" controls autoplay>
<source src="<?= getEventDefaultVideoPath($event) ?>" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
<div id="videoBar1">
<div id="prevEvent"><a href="#" onclick="prevEvent()">Previous Event</a></div>
<div id="dlEvent"><a id="downloadlink" href="<?= getEventDefaultVideoPath($event) ?>" download>Download Video</a></div>
<div id="nextEvent"><a href="#" onclick="nextEvent()">Next Event</a></div>
</div>
</div>
<div id="eventStills" class="hidden">
<div id="eventThumbsPanel">
<div id="eventThumbs">
</div>
</div>
<div id="eventImagePanel" class="hidden">
<div id="eventImageFrame">
<img id="eventImage" src="graphics/transparent.gif" alt=""/>
<div id="eventImageBar">
<div id="eventImageClose"><input type="button" value="<?= $SLANG['Close'] ?>" onclick="hideEventImage()"/></div>
<div id="eventImageStats" class="hidden"><input type="button" value="<?= $SLANG['Stats'] ?>" onclick="showFrameStats()"/></div>
<div id="eventImageData">Frame <span id="eventImageNo"></span></div>
</div>
</div>
</div>
<div id="eventImageNav">
<div id="eventImageButtons">
<div id="prevButtonsPanel">
<input id="prevEventBtn" type="button" value="&lt;E" onclick="prevEvent()" disabled="disabled"/>
<input id="prevThumbsBtn" type="button" value="&lt;&lt;" onclick="prevThumbs()" disabled="disabled"/>
<input id="prevImageBtn" type="button" value="&lt;" onclick="prevImage()" disabled="disabled"/>
<input id="nextImageBtn" type="button" value="&gt;" onclick="nextImage()" disabled="disabled"/>
<input id="nextThumbsBtn" type="button" value="&gt;&gt;" onclick="nextThumbs()" disabled="disabled"/>
<input id="nextEventBtn" type="button" value="E&gt;" onclick="nextEvent()" disabled="disabled"/>
</div>
</div>
<div id="thumbsSliderPanel">
<div id="thumbsSlider">
<div id="thumbsKnob">
</div>
</div>
</div>
</div>
</div>
<?php } ) ?>
</div>
</div>
</body>
</html>

View File

@ -12,7 +12,13 @@ function changeScale()
var newWidth = ( baseWidth * scale ) / SCALE_BASE;
var newHeight = ( baseHeight * scale ) / SCALE_BASE;
streamScale( scale );
if(streamMode == 'video') {
var videoid = document.getElementById('videoobj');
videoid.style.width = newWidth + "px";
videoid.style.height = newHeight + "px";
} else {
streamScale( scale );
/*Stream could be an applet so can't use moo tools*/
@ -217,15 +223,35 @@ function streamFastRev( action )
function streamPrev( action )
{
streamPlay( false );
if ( action )
streamReq.send( streamParms+"&command="+CMD_PREV );
if(streamMode == 'video') {
var videoid = document.getElementById('videoobj');
videoobj.src = PrevEventDefVideoPath;
videoobj.load();
updatedownloadlink();
} else {
if ( action )
streamReq.send( streamParms+"&command="+CMD_PREV );
}
}
function streamNext( action )
{
streamPlay( false );
if ( action )
streamReq.send( streamParms+"&command="+CMD_NEXT );
if(streamMode == 'video') {
var videoid = document.getElementById('videoobj');
videoobj.src = NextEventDefVideoPath;
videoobj.load();
updatedownloadlink();
} else {
if ( action )
streamReq.send( streamParms+"&command="+CMD_NEXT );
}
}
function updatedownloadlink() {
var videoid = document.getElementById('videoobj');
var link = document.getElementById('downloadlink');
link.href = videoid.currentSrc;
}
function streamZoomIn( x, y )
@ -316,6 +342,8 @@ function eventQuery( eventId )
var prevEventId = 0;
var nextEventId = 0;
var PrevEventDefVideoPath = "";
var NextEventDefVideoPath = "";
function getNearEventsResponse( respObj, respText )
{
@ -323,6 +351,8 @@ function getNearEventsResponse( respObj, respText )
return;
prevEventId = respObj.nearevents.PrevEventId;
nextEventId = respObj.nearevents.NextEventId;
PrevEventDefVideoPath = respObj.nearevents.PrevEventDefVideoPath;
NextEventDefVideoPath = respObj.nearevents.NextEventDefVideoPath;
$('prevEventBtn').disabled = !prevEventId;
$('nextEventBtn').disabled = !nextEventId;
@ -691,18 +721,42 @@ function showStream()
{
$('eventStills').addClass( 'hidden' );
$('eventStream').removeClass( 'hidden' );
$('eventVideo').addClass( 'hidden' );
$('streamEvent').addClass( 'hidden' );
$('stillsEvent').removeClass( 'hidden' );
$('videoEvent').removeClass( 'hidden' );
streamMode = 'stream';
}
//$(window).removeEvent( 'resize', updateStillsSizes );
function showVideo()
{
$('eventStills').addClass( 'hidden' );
$('eventStream').addClass( 'hidden' );
$('eventVideo').removeClass( 'hidden' );
$('streamEvent').removeClass( 'hidden' );
$('stillsEvent').removeClass( 'hidden' );
$('videoEvent').addClass( 'hidden' );
streamMode = 'video';
var videoid = document.getElementById('videoobj');
}
function showStills()
{
$('eventStream').addClass( 'hidden' );
$('eventStills').removeClass( 'hidden' );
$('stillsEvent').addClass( 'hidden' );
$('eventStream').addClass( 'hidden' );
$('eventVideo').addClass( 'hidden' );
$('streamEvent').removeClass( 'hidden' );
$('stillsEvent').addClass( 'hidden' );
$('videoEvent').removeClass( 'hidden' );
streamMode = 'stills';
streamPause( true );
if ( !scroll )
{
@ -819,13 +873,12 @@ function initPage()
streamCmdTimer = streamQuery.delay( 250 );
eventQuery.pass( eventData.Id ).delay( 500 );
if ( canStreamNative )
{
var streamImg = $('imageFeed').getElement('img');
if ( !streamImg )
streamImg = $('imageFeed').getElement('object');
$(streamImg).addEvent( 'click', function( event ) { handleClick( event ); } );
}
if ( canStreamNative )
{
var streamImg = $('imageFeed').getElement('img');
if ( !streamImg )
streamImg = $('imageFeed').getElement('object');
$(streamImg).addEvent( 'click', function( event ) { handleClick( event ); } );
}
}

View File

@ -27,6 +27,7 @@ if ( !canView( 'Monitors' ) )
$tabs = array();
$tabs["general"] = $SLANG['General'];
$tabs["source"] = $SLANG['Source'];
$tabs["storage"] = "Storage";
$tabs["timestamp"] = $SLANG['Timestamp'];
$tabs["buffers"] = $SLANG['Buffers'];
if ( ZM_OPT_CONTROL && canView( 'Control' ) )
@ -70,6 +71,9 @@ if ( ! empty($_REQUEST['mid']) ) {
'Height' => "240",
'Orientation' => "0",
'Deinterlacing' => 0,
'SaveJPEGs' => "3",
'VideoWriter' => "0",
'EncoderParameters' => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n",
'LabelFormat' => '%N - %d/%m/%y %H:%M:%S',
'LabelX' => 0,
'LabelY' => 0,
@ -415,6 +419,19 @@ $fastblendopts_alarm = array(
"50% (Alarm lasts a moment)" => 50
);
$savejpegopts = array(
"Disabled" => 0,
"Frames only" => 1,
"Analysis images only (if available)" => 2,
"Frames + Analysis images (if available)" => 3
);
$videowriteropts = array(
"Disabled" => 0,
"X264 : MP4V2" => 1
// "H264 Passthrough : MP4V2 (not implemented)" => 2
);
xhtmlHeaders(__FILE__, $SLANG['Monitor']." - ".validHtmlStr($monitor['Name']) );
?>
<body>
@ -533,6 +550,14 @@ if ( $tab != 'source' )
<input type="hidden" name="newMonitor[Deinterlacing]" value="<?= validHtmlStr($newMonitor['Deinterlacing']) ?>"/>
<?php
}
if ( $tab != 'storage' )
{
?>
<input type="hidden" name="newMonitor[SaveJPEGs]" value="<?= validHtmlStr($newMonitor['SaveJPEGs']) ?>"/>
<input type="hidden" name="newMonitor[VideoWriter]" value="<?= validHtmlStr($newMonitor['VideoWriter']) ?>"/>
<input type="hidden" name="newMonitor[EncoderParameters]" value="<?= validHtmlStr($newMonitor['EncoderParameters']) ?>"/>
<?php
}
if ( $tab != 'timestamp' )
{
?>
@ -785,6 +810,13 @@ switch ( $tab )
<?php
break;
}
case 'storage' :
?>
<tr><td>Save as JPEGs</td><td><select name="newMonitor[SaveJPEGs]"><?php foreach ( $savejpegopts as $name => $value ) { ?><option value="<?= $value ?>"<?php if ( $value == $newMonitor['SaveJPEGs'] ) { ?> selected="selected"<?php } ?>><?= $name ?></option><?php } ?></select></td></tr>
<tr><td>Video writer</td><td><select name="newMonitor[VideoWriter]"><?php foreach ( $videowriteropts as $name => $value ) { ?><option value="<?= $value ?>"<?php if ( $value == $newMonitor['VideoWriter'] ) { ?> selected="selected"<?php } ?>><?= $name ?></option><?php } ?></select></td></tr>
<tr><td>Optional encoder parameters</td><td><textarea name="newMonitor[EncoderParameters]" rows="4" cols="36"><?= validHtmlStr($newMonitor['EncoderParameters']) ?></textarea></td></tr>
<?php
break;
case 'timestamp' :
{
?>

View File

@ -39,6 +39,12 @@
#cmakedefine HAVE_GNUTLS_GNUTLS_H 1
#cmakedefine HAVE_LIBMYSQLCLIENT 1
#cmakedefine HAVE_MYSQL_H 1
#cmakedefine HAVE_LIBX264 1
#cmakedefine HAVE_X264_H 1
#cmakedefine HAVE_LIBMP4V2 1
#cmakedefine HAVE_MP4V2_MP4V2_H 1
#cmakedefine HAVE_MP4V2_H 1
#cmakedefine HAVE_MP4_H 1
#cmakedefine HAVE_LIBAVFORMAT 1
#cmakedefine HAVE_LIBAVFORMAT_AVFORMAT_H 1
#cmakedefine HAVE_LIBAVCODEC 1