diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 06f1d37b4..7d2a66ce0 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -213,9 +213,11 @@ Event::Event( if ( monitor->GetOptVideoWriter() == Monitor::X264ENCODE ) { #if ZM_HAVE_VIDEOWRITER_X264MP4 videowriter = new X264MP4Writer(video_file.c_str(), - monitor->Width(), monitor->Height(), - monitor->Colours(), monitor->SubpixelOrder(), - monitor->GetOptEncoderParams()); + monitor->Width(), + monitor->Height(), + monitor->Colours(), + monitor->SubpixelOrder(), + monitor->GetOptEncoderParamsVec()); #else Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)"); #endif diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index bbf31d311..7a99f324f 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -383,6 +383,7 @@ Monitor::Monitor( strncpy(event_prefix, p_event_prefix, sizeof(event_prefix)-1); strncpy(label_format, p_label_format, sizeof(label_format)-1); + Debug(1, "encoder params %s", encoderparams.c_str()); // Change \n to actual line feeds char *token_ptr = label_format; @@ -632,7 +633,7 @@ bool Monitor::connect() { image_buffer = new Snapshot[image_buffer_count]; for ( int i = 0; i < image_buffer_count; i++ ) { image_buffer[i].timestamp = &(shared_timestamps[i]); - image_buffer[i].image = new Image(width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()]) ); + image_buffer[i].image = new Image(width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()])); image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ } if ( (deinterlacing & 0xff) == 4 ) { diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 6ff3fb5c0..345a85862 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -473,7 +473,8 @@ public: int GetOptSaveJPEGs() const { return savejpegs; } VideoWriter GetOptVideoWriter() const { return videowriter; } - const std::vector* GetOptEncoderParams() const { return &encoderparamsvec; } + const std::vector* GetOptEncoderParamsVec() const { return &encoderparamsvec; } + const std::string GetOptEncoderParams() const { return encoderparams; } uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; } void SetVideoWriterEventId( unsigned long long p_event_id ) { video_store_data->current_event = p_event_id; } struct timeval GetVideoWriterStartTime() const { return video_store_data->recording; } diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 1cd22b701..bff7d83c1 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -624,8 +624,8 @@ int RemoteCameraHttp::GetResponse() { static char *content_type_header; static char *boundary_header; static char *authenticate_header; - static char subcontent_length_header[32]; - static char subcontent_type_header[64]; + static char subcontent_length_header[33]; + static char subcontent_type_header[65]; static char http_version[16]; static char status_code[16]; @@ -896,25 +896,37 @@ int RemoteCameraHttp::GetResponse() { } } - Debug( 6, "%d: %s", subheader_len, subheader_ptr ); + Debug(6, "%d: %s", subheader_len, subheader_ptr); - if ( (crlf = mempbrk( subheader_ptr, "\r\n", subheader_len )) ) { + if ( (crlf = mempbrk(subheader_ptr, "\r\n", subheader_len)) ) { //subheaders[n_subheaders++] = subheader_ptr; n_subheaders++; - if ( !boundary_header && (strncasecmp( subheader_ptr, content_boundary, content_boundary_len ) == 0) ) { + if ( !boundary_header && (strncasecmp(subheader_ptr, content_boundary, content_boundary_len) == 0) ) { boundary_header = subheader_ptr; - Debug( 4, "Got boundary subheader '%s'", subheader_ptr ); - } else if ( !subcontent_length_header[0] && (strncasecmp( subheader_ptr, content_length_match, content_length_match_len) == 0) ) { - strncpy( subcontent_length_header, subheader_ptr+content_length_match_len, sizeof(subcontent_length_header) ); - *(subcontent_length_header+strcspn( subcontent_length_header, "\r\n" )) = '\0'; - Debug( 4, "Got content length subheader '%s'", subcontent_length_header ); + Debug(4, "Got boundary subheader '%s'", subheader_ptr); + } else if ( + !subcontent_length_header[0] + && + (strncasecmp(subheader_ptr, content_length_match, content_length_match_len) == 0) + ) { + strncpy( + subcontent_length_header, + subheader_ptr+content_length_match_len, + sizeof(subcontent_length_header)-1 + ); + *(subcontent_length_header+strcspn(subcontent_length_header, "\r\n")) = '\0'; + Debug(4, "Got content length subheader '%s'", subcontent_length_header); } else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) { - strncpy( subcontent_type_header, subheader_ptr+content_type_match_len, sizeof(subcontent_type_header) ); - *(subcontent_type_header+strcspn( subcontent_type_header, "\r\n" )) = '\0'; - Debug( 4, "Got content type subheader '%s'", subcontent_type_header ); + strncpy( + subcontent_type_header, + subheader_ptr+content_type_match_len, + sizeof(subcontent_type_header)-1 + ); + *(subcontent_type_header+strcspn(subcontent_type_header, "\r\n")) = '\0'; + Debug(4, "Got content type subheader '%s'", subcontent_type_header); } else { - Debug( 6, "Got ignored subheader '%s' found", subheader_ptr ); + Debug(6, "Got ignored subheader '%s' found", subheader_ptr); } subheader_ptr = crlf; subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 6acee0cfd..19f1251e2 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -37,11 +37,12 @@ VideoStore::VideoStore( const char *format_in, AVStream *p_video_in_stream, AVStream *p_audio_in_stream, - Monitor *monitor + Monitor *p_monitor ) { video_in_stream = p_video_in_stream; audio_in_stream = p_audio_in_stream; + monitor = p_monitor; #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) //video_in_ctx = avcodec_alloc_context3(NULL); @@ -213,11 +214,18 @@ VideoStore::VideoStore( } #if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) +#if 0 +# This is commented out because we are only doing passthrough right now /* I'm not entirely sure that this is a good idea. We may have to do it someday but really only when transcoding * * think what I was trying to achieve here was to have zm_dump_codecpar output nice info * */ -#if 0 + AVDictionary *opts = 0; + ret = av_dict_parse_string(&opts, monitor->GetOptEncoderParams().c_str(), "=", ",", 0); + if ( ret < 0 ) { + Warning("Could not parse ffmpeg encoder options '%s'", monitor->GetOptEncoderParams().c_str()); + } + if ( (ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0 ) { Warning("Can't open video codec (%s) %s", video_out_codec->name, @@ -404,21 +412,39 @@ bool VideoStore::open() { } zm_dump_stream_format(oc, 0, 0, 1); - if (audio_out_stream) zm_dump_stream_format(oc, 1, 0, 1); + if ( audio_out_stream ) zm_dump_stream_format(oc, 1, 0, 1); AVDictionary *opts = NULL; - // av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); - // Shiboleth reports that this may break seeking in mp4 before it downloads - av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0); - // av_dict_set(&opts, "movflags", - // "frag_keyframe+empty_moov+default_base_moof", 0); + + std::string option_string = monitor->GetOptEncoderParams(); + ret = av_dict_parse_string(&opts, option_string.c_str(), "=", ",\n", 0); + if ( ret < 0 ) { + Warning("Could not parse ffmpeg output options '%s'", option_string.c_str()); + } + + const AVDictionaryEntry *movflags_entry = av_dict_get(opts, "movflags", NULL, AV_DICT_MATCH_CASE); + if ( !movflags_entry ) { + Debug(1, "setting movflags to frag_keyframe+empty_moov"); + // av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); + // Shiboleth reports that this may break seeking in mp4 before it downloads + av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0); + // av_dict_set(&opts, "movflags", + // "frag_keyframe+empty_moov+default_base_moof", 0); + } else { + Debug(1, "using movflags %s", movflags_entry->value); + } if ( (ret = avformat_write_header(oc, &opts)) < 0 ) { - // if ((ret = avformat_write_header(oc, &opts)) < 0) { - Warning("Unable to set movflags to frag_custom+dash+delay_moov"); - /* Write the stream header, if any. */ + Warning("Unable to set movflags trying with defaults."); ret = avformat_write_header(oc, NULL); - } else if (av_dict_count(opts) != 0) { - Warning("some options not set"); + } else if ( av_dict_count(opts) != 0 ) { + Info("some options not used, turn on debugging for a list."); + AVDictionaryEntry *e = NULL; + while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { + Debug(1, "Encoder Option %s=>%s", e->key, e->value); + if ( !e->value ) { + av_dict_set(&opts, e->key, NULL, 0); + } + } } if ( opts ) av_dict_free(&opts); if ( ret < 0 ) { diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 7c465ea4b..64c2296ba 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -19,7 +19,6 @@ extern "C" { class VideoStore { private: - AVOutputFormat *out_format; AVFormatContext *oc; @@ -30,6 +29,7 @@ private: AVStream *video_in_stream; AVStream *audio_in_stream; + Monitor *monitor; // Move this into the object so that we aren't constantly allocating/deallocating it on the stack AVPacket opkt; diff --git a/web/ajax/status.php b/web/ajax/status.php index a4bf08d2e..a80559998 100644 --- a/web/ajax/status.php +++ b/web/ajax/status.php @@ -254,7 +254,7 @@ function collectData() { if ( isset($elementData['sql']) ) $fieldSql[] = $elementData['sql'].' as '.$element; else - $fieldSql[] = $element; + $fieldSql[] = '`'.$element.'`'; if ( isset($elementData['table']) && isset($elementData['join']) ) { $joinSql[] = 'left join '.$elementData['table'].' on '.$elementData['join']; } @@ -294,7 +294,7 @@ function collectData() { preg_match('/^`?(\w+)`?\s*(ASC|DESC)?( NULLS FIRST)?$/i', $sort_field, $matches); if ( count($matches) ) { - if ( in_array($matches[1], $fieldSql) ) { + if ( in_array($matches[1], $fieldSql) or in_array('`'.$matches[1].'`', $fieldSql) ) { $sql .= $matches[1]; } else { ZM\Error('Sort field '.$matches[1].' from ' .$sort_field.' not in SQL Fields: '.join(',', $sort_field)); diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 962409efc..b3de1729d 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -993,6 +993,18 @@ $OLANG = array( "loglevel=debug" Set verbosity of FFmpeg (quiet, panic, fatal, error, warning, info, verbose, debug) ' ), + 'OPTIONS_ENCODER_PARAMETERS' => array( + 'Help' => ' + Parameters passed to the encoding codec. name=value separated by either , or newline.~~ + For example to changing quality, use the crf option. 1 is best, 51 is worst 23 is default.~~ +~~ + crf=23~~ + ~~ + You might want to alter the movflags value to support different behaviours. Some people have troubles viewing videos due to the frag_keyframe option, but that option is supposed to allow viewing of incomplete events. See + [https://ffmpeg.org/ffmpeg-formats.html](https://ffmpeg.org/ffmpeg-formats.html) + for more information. ZoneMinder\'s default is frag_keyframe,empty_moov~~ + ', + ), 'OPTIONS_DECODERHWACCELNAME' => array( 'Help' => ' This is equivalent to the ffmpeg -hwaccel command line option. With intel graphics support, use "vaapi". For NVIDIA cuda support use "cuda". To check for support, run ffmpeg -hwaccels on the command line.' diff --git a/web/skins/classic/css/base/views/monitor.css b/web/skins/classic/css/base/views/monitor.css index f4d0770e1..2cdc0c400 100644 --- a/web/skins/classic/css/base/views/monitor.css +++ b/web/skins/classic/css/base/views/monitor.css @@ -11,6 +11,7 @@ textarea, input[name="newMonitor[Name]"], +input[name="newMonitor[LabelFormat]"], input[name="newMonitor[ControlDevice]"], input[name="newMonitor[ControlAddress]"] { width: 100%; diff --git a/web/skins/classic/views/events.php b/web/skins/classic/views/events.php index db9f11d4a..c8e426a15 100644 --- a/web/skins/classic/views/events.php +++ b/web/skins/classic/views/events.php @@ -68,9 +68,9 @@ if ( !empty($page) ) { $limitLeft = $limit - $limitStart; $limitAmount = ($limitLeft>ZM_WEB_EVENTS_PER_PAGE)?ZM_WEB_EVENTS_PER_PAGE:$limitLeft; } - $eventsSql .= " limit $limitStart, $limitAmount"; + $eventsSql .= " LIMIT $limitStart, $limitAmount"; } elseif ( !empty($limit) ) { - $eventsSql .= ' limit 0, '.$limit; + $eventsSql .= ' LIMIT 0, '.$limit; } $maxShortcuts = 5; diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index a6d74bbd9..0679b8781 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -297,8 +297,8 @@ function changeSize() { console.log("Error finding frame for " + monitor.id); continue; } - monitor_frame.css('width', ( width ? width+'px' : 'auto') ); - monitor_frame.css('height', ( height ? height+'px' : 'auto') ); + monitor_frame.css('width', ( width ? width+'px' : 'auto')); + monitor_frame.css('height', ( height ? height+'px' : 'auto')); /*Stream could be an applet so can't use moo tools*/ var streamImg = $('liveStream'+monitor.id); diff --git a/web/skins/classic/views/js/zones.js.php b/web/skins/classic/views/js/zones.js.php index 6bd9c402c..81248d94c 100644 --- a/web/skins/classic/views/js/zones.js.php +++ b/web/skins/classic/views/js/zones.js.php @@ -1,3 +1,7 @@ + var connKey = ''; var monitorUrl = 'UrlToIndex() ) ?>'; var CMD_QUIT = ; diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 3a1591104..a935afa89 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -1060,8 +1060,11 @@ include('_monitor_source_nvsocket.php'); ?> - - +  () + + + + Type() == 'Ffmpeg' ) { ?> RecordAudio() ) { ?> checked="checked"/>