From e6ef3bbcd799eac170a60fb651b84332f476fa9b Mon Sep 17 00:00:00 2001 From: stan Date: Thu, 11 Mar 2004 11:18:14 +0000 Subject: [PATCH] Added maximum fps and timed option to video streaming. git-svn-id: http://svn.zoneminder.com/svn/zm/trunk@942 e3e1d417-86f3-4887-817a-d78f3d33393f --- src/zm_event.cpp | 15 ++-- src/zm_event.h | 2 +- src/zm_monitor.cpp | 140 ++++++++++++------------------- src/zm_monitor.h | 2 +- src/zms.cpp | 7 +- web/zm_config.php.z | 9 +- web/zm_html_view_event.php | 2 +- web/zm_html_view_montagefeed.php | 2 +- web/zm_html_view_watchfeed.php | 2 +- zmconfig.pl.in | 33 ++++++++ 10 files changed, 112 insertions(+), 102 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 1cdcfafe3..298b47795 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -404,7 +404,6 @@ void Event::StreamEvent( int event_id, int rate, int scale ) } fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" ); - fprintf( stdout, "--ZoneMinderFrame\n" ); //int n_frames = mysql_num_rows( result ); //Info(( "Got %d frames, at rate %d, scale %d", n_frames, rate, scale )); @@ -442,7 +441,6 @@ void Event::StreamEvent( int event_id, int rate, int scale ) static char filepath[PATH_MAX]; sprintf( filepath, "%s/%03d-capture.jpg", eventpath, atoi(dbrow[0]) ); - fprintf( stdout, "Content-type: image/jpeg\n\n" ); if ( scale == 100 ) { if ( (fdj = fopen( filepath, "r" )) ) @@ -468,7 +466,9 @@ void Event::StreamEvent( int event_id, int rate, int scale ) write( fileno(stdout), buffer, n_bytes ); } - fprintf( stdout, "\n--ZoneMinderFrame\n" ); + fprintf( stdout, "\r\n--ZoneMinderFrame\r\n" ); + fprintf( stdout, "Content-length: %d\r\n", n_bytes ); + fprintf( stdout, "Content-type: image/jpeg\r\n\r\n" ); fflush( stdout ); } if ( mysql_errno( &dbconn ) ) @@ -482,12 +482,13 @@ void Event::StreamEvent( int event_id, int rate, int scale ) #if HAVE_LIBAVCODEC -void Event::StreamMpeg( int event_id, const char *format, int bitrate, int rate, int scale ) +void Event::StreamMpeg( int event_id, const char *format, int bitrate, int maxfps, int rate, int scale ) { static char sql[BUFSIZ]; static char eventpath[PATH_MAX]; - //sprintf( sql, "select M.Id, M.Name,max(F.Delta)-min(F.Delta) as Duration, count(F.Id) as Frames from Events as E inner join Monitors as M on E.MonitorId = M.Id inner join Frames as F on F.EventId = E.Id where E.Id = %d group by F.EventId", event_id ); + bool timed_frames = (bool)config.Item( ZM_WEB_VIDEO_TIMED_FRAMES ); + sprintf( sql, "select M.Id, M.Name, E.Length, E.Frames from Events as E inner join Monitors as M on E.MonitorId = M.Id where E.Id = %d", event_id ); if ( mysql_query( &dbconn, sql ) ) { @@ -514,7 +515,7 @@ void Event::StreamMpeg( int event_id, const char *format, int bitrate, int rate, int frames = atoi(dbrow[3]); int min_fps = 1; - int max_fps = 30; + int max_fps = maxfps; int base_fps = frames/duration; int effective_fps = (base_fps*rate)/ZM_RATE_SCALE; @@ -582,7 +583,7 @@ void Event::StreamMpeg( int event_id, const char *format, int bitrate, int rate, delta_ms = (unsigned int)((last_delta+temp_delta)*1000); if ( rate != ZM_RATE_SCALE ) delta_ms = (delta_ms*ZM_RATE_SCALE)/rate; - double pts = vid_stream->EncodeFrame( image.Buffer(), image.Size(), true, delta_ms ); + double pts = vid_stream->EncodeFrame( image.Buffer(), image.Size(), timed_frames, delta_ms ); //Info(( "I:%d, DI:%d, LI:%d, DD:%lf, LD:%lf, TD:%lf, DM:%d, PTS:%lf", id, db_id, last_id, db_delta, last_delta, temp_delta, delta_ms, pts )); } diff --git a/src/zm_event.h b/src/zm_event.h index 20895b71f..05aa64f31 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -93,7 +93,7 @@ public: static void StreamEvent( int event_id, int rate=100, int scale=100 ); #if HAVE_LIBAVCODEC - static void StreamMpeg( int event_id, const char *format, int bitrate=100000, int rate=100, int scale=100 ); + static void StreamMpeg( int event_id, const char *format, int bitrate=100000, int maxfps=10, int rate=100, int scale=100 ); #endif // HAVE_LIBAVCODEC }; diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index f8da504ef..86952752c 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1078,7 +1078,6 @@ Monitor *Monitor::Load( int id, bool load_zones, Purpose purpose ) void Monitor::StreamImages( unsigned long idle, unsigned long refresh, time_t ttl, int scale ) { fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" ); - fprintf( stdout, "--ZoneMinderFrame\n" ); int last_read_index = image_buffer_count; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; @@ -1126,9 +1125,10 @@ void Monitor::StreamImages( unsigned long idle, unsigned long refresh, time_t tt scaled_image.EncodeJpeg( img_buffer, &img_buffer_size ); } - fprintf( stdout, "Content-type: image/jpeg\n\n" ); + fprintf( stdout, "\r\n--ZoneMinderFrame\r\n" ); + fprintf( stdout, "Content-type: image/jpeg\r\n" ); + fprintf( stdout, "Content-length: %d\r\n\r\n", img_buffer_size ); fwrite( img_buffer, img_buffer_size, 1, stdout ); - fprintf( stdout, "\n--ZoneMinderFrame\n" ); } usleep( refresh*1000 ); for ( int i = 0; shared_data->state == IDLE && i < loop_count; i++ ) @@ -1150,14 +1150,31 @@ void Monitor::StreamImages( unsigned long idle, unsigned long refresh, time_t tt #if HAVE_LIBAVCODEC -void Monitor::StreamMpeg( const char *format, int bitrate, int scale, int buffer ) +void Monitor::StreamMpeg( const char *format, int bitrate, int maxfps, int scale, int buffer ) { fprintf( stdout, "Content-type: video/x-ms-asf\r\n\r\n"); + bool timed_frames = (bool)config.Item( ZM_WEB_VIDEO_TIMED_FRAMES ); + int fps = int(GetFPS()); if ( !fps ) fps = 5; + int min_fps = 1; + int max_fps = maxfps; + int base_fps = int(GetFPS()); + int effective_fps = base_fps; + + int frame_mod = 1; + // Min frame repeat? + while( effective_fps > max_fps ) + { + effective_fps /= 2; + frame_mod *= 2; + } + + Info(( "BFPS:%d, EFPS:%d, FM:%d", base_fps, effective_fps, frame_mod )); + VideoStream vid_stream( "pipe:", format, bitrate, fps, camera->Colours(), (width*scale)/ZM_SCALE_SCALE, (height*scale)/ZM_SCALE_SCALE ); int last_read_index = image_buffer_count; @@ -1165,60 +1182,10 @@ void Monitor::StreamMpeg( const char *format, int bitrate, int scale, int buffer time_t stream_start_time; time( &stream_start_time ); - Image scaled_image; - - // Do any catching up - if ( buffer ) - { - int index = shared_data->last_write_index; - int offset = buffer*fps; - if ( offset > image_buffer_count ) - { - last_read_index = (index+1)%image_buffer_count; - } - else - { - last_read_index = (index-offset+image_buffer_count)%image_buffer_count; - } - Info(( "LWI:%d", shared_data->last_write_index )); - Info(( "LRI:%d", last_read_index )); - - while ( last_read_index != shared_data->last_write_index ) - { - Info(( "LRI+:%d", last_read_index )); - - Snapshot *snap = &image_buffer[last_read_index]; - Image *snap_image = snap->image; - - if ( scale == 100 ) - { - if ( !timestamp_on_capture ) - { - TimestampImage( snap_image, snap->timestamp->tv_sec ); - } - } - else - { - scaled_image.Assign( *snap_image ); - - scaled_image.Scale( scale ); - - if ( !timestamp_on_capture ) - { - TimestampImage( &scaled_image, snap->timestamp->tv_sec ); - } - snap_image = &scaled_image; - } - - double pts = vid_stream.EncodeFrame( snap_image->Buffer(), snap_image->Size() ); - - last_read_index = (last_read_index+1)%image_buffer_count; - } - } - int frame_count = 0; struct timeval base_time; struct DeltaTimeval delta_time; + Image scaled_image; while ( true ) { if ( feof( stdout ) || ferror( stdout ) ) @@ -1227,43 +1194,46 @@ void Monitor::StreamMpeg( const char *format, int bitrate, int scale, int buffer } if ( last_read_index != shared_data->last_write_index ) { - // Send the next frame - last_read_index = shared_data->last_write_index; - int index = shared_data->last_write_index%image_buffer_count; - //Info(( "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer )); - Snapshot *snap = &image_buffer[index]; - Image *snap_image = snap->image; - - if ( scale == 100 ) + if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { - if ( !timestamp_on_capture ) + // Send the next frame + last_read_index = shared_data->last_write_index; + int index = shared_data->last_write_index%image_buffer_count; + //Info(( "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer )); + Snapshot *snap = &image_buffer[index]; + Image *snap_image = snap->image; + + if ( scale == 100 ) { - TimestampImage( snap_image, snap->timestamp->tv_sec ); + if ( !timestamp_on_capture ) + { + TimestampImage( snap_image, snap->timestamp->tv_sec ); + } } - } - else - { - scaled_image.Assign( *snap_image ); - - scaled_image.Scale( scale ); - - if ( !timestamp_on_capture ) + else { - TimestampImage( &scaled_image, snap->timestamp->tv_sec ); - } - snap_image = &scaled_image; - } + scaled_image.Assign( *snap_image ); - if ( !frame_count ) - { - base_time = *(snap->timestamp); + scaled_image.Scale( scale ); + + if ( !timestamp_on_capture ) + { + TimestampImage( &scaled_image, snap->timestamp->tv_sec ); + } + snap_image = &scaled_image; + } + + if ( !frame_count ) + { + base_time = *(snap->timestamp); + } + DELTA_TIMEVAL( delta_time, *(snap->timestamp), base_time, DT_PREC_3 ); + double pts = vid_stream.EncodeFrame( snap_image->Buffer(), snap_image->Size(), timed_frames, delta_time.delta ); + //Info(( "DTD:%d, PTS:%lf", delta_time.delta, pts )); } - DELTA_TIMEVAL( delta_time, *(snap->timestamp), base_time, DT_PREC_3 ); - double pts = vid_stream.EncodeFrame( snap_image->Buffer(), snap_image->Size(), true, delta_time.delta ); - //Info(( "DTD:%d, PTS:%lf", delta_time.delta, pts )); + frame_count++; } - frame_count++; - usleep( 10000 ); + usleep( ZM_SAMPLE_RATE ); } } #endif // HAVE_LIBAVCODEC diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 50222f2d9..01ca5ecb4 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -289,7 +289,7 @@ public: static Monitor *Load( int id, bool load_zones=false, Purpose purpose=QUERY ); void StreamImages( unsigned long idle=5000, unsigned long refresh=50, time_t ttl=0, int scale=100 ); #if HAVE_LIBAVCODEC - void StreamMpeg( const char *format, int bitrate=100000, int scale=100, int buffer=0 ); + void StreamMpeg( const char *format, int bitrate=100000, int maxfps=10, int scale=100, int buffer=0 ); #endif // HAVE_LIBAVCODEC }; diff --git a/src/zms.cpp b/src/zms.cpp index 4c92440d7..8f8efbcb2 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -28,6 +28,7 @@ int main(void ) int id = 1; int event = 0; unsigned int bitrate = 100000; + unsigned int maxfps = 100000; unsigned int rate = 100; unsigned int scale = 100; unsigned int buffer = 0; @@ -69,6 +70,8 @@ int main(void ) strncpy( format, value, sizeof(format) ); else if ( !strcmp( name, "bitrate" ) ) bitrate = atoi( value ); + else if ( !strcmp( name, "maxfps" ) ) + maxfps = atoi( value ); else if ( !strcmp( name, "rate" ) ) rate = atoi( value ); else if ( !strcmp( name, "scale" ) ) @@ -112,7 +115,7 @@ int main(void ) else { #if HAVE_LIBAVCODEC - monitor->StreamMpeg( format, bitrate, scale, buffer ); + monitor->StreamMpeg( format, bitrate, maxfps, scale, buffer ); #else // HAVE_LIBAVCODEC Error(( "MPEG streaming of '%s' attempted while disabled", query )); fprintf( stderr, "MPEG streaming is disabled.\nYou should configure with the --with-ffmpeg option and rebuild to use this functionality.\n" ); @@ -130,7 +133,7 @@ int main(void ) else { #if HAVE_LIBAVCODEC - Event::StreamMpeg( event, format, bitrate, rate, scale ); + Event::StreamMpeg( event, format, bitrate, maxfps, rate, scale ); #else // HAVE_LIBAVCODEC Error(( "MPEG streaming of '%s' attempted while disabled", query )); fprintf( stderr, "MPEG streaming is disabled.\nYou should configure with the --with-ffmpeg option and rebuild to use this functionality.\n" ); diff --git a/web/zm_config.php.z b/web/zm_config.php.z index f18a88f54..6f3b4c9a4 100644 --- a/web/zm_config.php.z +++ b/web/zm_config.php.z @@ -57,7 +57,8 @@ switch ( $bandwidth ) define( "REFRESH_IMAGE", ZM_WEB_H_REFRESH_IMAGE ); // How often the watched image is refreshed (if not streaming) define( "REFRESH_STATUS", ZM_WEB_H_REFRESH_STATUS ); // How often the little status frame refreshes itself in the watch window define( "REFRESH_EVENTS", ZM_WEB_H_REFRESH_EVENTS ); // How often the event listing is refreshed in the watch window, only for recent events - define( "VIDEO_BITRATE", ZM_WEB_H_VIDEO_BITRATE ); // What the bitrate of any generated video should be + define( "VIDEO_BITRATE", ZM_WEB_H_VIDEO_BITRATE ); // What the bitrate of any streamed video should be + define( "VIDEO_MAXFPS", ZM_WEB_H_VIDEO_MAXFPS ); // What the maximum frame rate of any streamed video should be define( "STREAM_IDLE_DELAY", ZM_WEB_H_STREAM_IDLE_DELAY ); // How long (in milliseconds) between streamed frames in the watch window define( "STREAM_FRAME_DELAY", ZM_WEB_H_STREAM_FRAME_DELAY ); // How long (in milliseconds) to wait before looking for the next streamed frame define( "IMAGE_SCALING", ZM_WEB_H_IMAGE_SCALING ); // Image scaling for thumbnails, bandwidth versus cpu in rescaling @@ -70,7 +71,8 @@ switch ( $bandwidth ) define( "REFRESH_IMAGE", ZM_WEB_M_REFRESH_IMAGE ); // How often the watched image is refreshed (if not streaming) define( "REFRESH_STATUS", ZM_WEB_M_REFRESH_STATUS ); // How often the little status frame refreshes itself in the watch window define( "REFRESH_EVENTS", ZM_WEB_M_REFRESH_EVENTS ); // How often the event listing is refreshed in the watch window, only for recent events - define( "VIDEO_BITRATE", ZM_WEB_M_VIDEO_BITRATE ); // What the bitrate of any generated video should be + define( "VIDEO_BITRATE", ZM_WEB_M_VIDEO_BITRATE ); // What the bitrate of any streamed video should be + define( "VIDEO_MAXFPS", ZM_WEB_M_VIDEO_MAXFPS ); // What the maximum frame rate of any streamed video should be define( "STREAM_IDLE_DELAY", ZM_WEB_M_STREAM_IDLE_DELAY ); // How long (in milliseconds) between streamed frames in the watch window define( "STREAM_FRAME_DELAY", ZM_WEB_M_STREAM_FRAME_DELAY ); // How long (in milliseconds) to wait before looking for the next streamed frame define( "IMAGE_SCALING", ZM_WEB_M_IMAGE_SCALING ); // Image scaling for thumbnails, bandwidth versus cpu in rescaling @@ -83,7 +85,8 @@ switch ( $bandwidth ) define( "REFRESH_IMAGE", ZM_WEB_L_REFRESH_IMAGE ); // How often the watched image is refreshed (if not streaming) define( "REFRESH_STATUS", ZM_WEB_L_REFRESH_STATUS ); // How often the little status frame refreshes itself in the watch window define( "REFRESH_EVENTS", ZM_WEB_L_REFRESH_EVENTS ); // How often the event listing is refreshed in the watch window, only for recent events - define( "VIDEO_BITRATE", ZM_WEB_L_VIDEO_BITRATE ); // What the bitrate of any generated video should be + define( "VIDEO_BITRATE", ZM_WEB_L_VIDEO_BITRATE ); // What the bitrate of any streamed video should be + define( "VIDEO_MAXFPS", ZM_WEB_L_VIDEO_MAXFPS ); // What the maximum frame rate of any streamed video should be define( "STREAM_IDLE_DELAY", ZM_WEB_L_STREAM_IDLE_DELAY ); // How long (in milliseconds) between streamed frames in the watch window define( "STREAM_FRAME_DELAY", ZM_WEB_L_STREAM_FRAME_DELAY ); // How long (in milliseconds) to wait before looking for the next streamed frame define( "IMAGE_SCALING", ZM_WEB_L_IMAGE_SCALING ); // Image scaling for thumbnails, bandwidth versus cpu in rescaling diff --git a/web/zm_html_view_event.php b/web/zm_html_view_event.php index 4df0dd79e..cae4d375c 100644 --- a/web/zm_html_view_event.php +++ b/web/zm_html_view_event.php @@ -265,7 +265,7 @@ if ( $mode == "stream" ) { db_type=>'string', hint=>'mpeg|jpeg', pattern=>qr|^([mj])|i, format=>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, category => 'web', }, + { + name => "ZM_WEB_VIDEO_TIMED_FRAMES", + default => "yes", + description => "Whether video frames are sent tagged with a timestamp for more realistic streaming", + help => "When using streamed MPEG based video, either for live monitor streams or events, ZoneMinder can send the streams in two ways. If this option is selected then the timestamp for each frame, taken from it's capture time, is included in the stream. This means that where the frame rate varies, for instance around an alarm, the stream will still maintain it's 'real' timing. If this option is not selected then an approximate frame rate is calculated and that is used to schedule frames instead. This option should be selected unless you encounter problems with your preferred streaming method.", + requires => [ { name=>"ZM_WEB_VIDEO_STREAM_METHOD", value=>"mpeg" } ], + type => $types{boolean}, + category => 'web', + }, { name => "ZM_WEB_POPUP_ON_ALARM", default => "yes", @@ -942,6 +951,14 @@ my @options = type => $types{integer}, category => 'highband', }, + { + name => "ZM_WEB_H_VIDEO_MAXFPS", + default => "10", + description => "What the maximum frame rate for streamed video should be", + help => "When using streamed video the main control is the bitrate which determines how much data can be transmitted. However a lower bitrate at high frame rates results in a lower quality image. This option allows you to limit the maximum frame rate to ensure that video quality is maintained. An additional advantage is that encoding video at high frame rates is a processor intensive task when for the most part a very high frame rate offers little perceptible improvement over one that has a more manageable resource requirement. Note, this option is implemented as a cap beyond which binary reduction takes place. So if you have a device capturing at 15fps and set this option to 10fps then the video is not produced at 10fps, but rather at 7.5fps (15 divided by 2) as the final frame rate must be the original divided by a power of 2.", + type => $types{integer}, + category => 'highband', + }, { name => "ZM_WEB_H_STREAM_IDLE_DELAY", default => "250", @@ -1016,6 +1033,14 @@ my @options = type => $types{integer}, category => 'medband', }, + { + name => "ZM_WEB_M_VIDEO_MAXFPS", + default => "10", + description => "What the maximum frame rate for streamed video should be", + help => "When using streamed video the main control is the bitrate which determines how much data can be transmitted. However a lower bitrate at high frame rates results in a lower quality image. This option allows you to limit the maximum frame rate to ensure that video quality is maintained. An additional advantage is that encoding video at high frame rates is a processor intensive task when for the most part a very high frame rate offers little perceptible improvement over one that has a more manageable resource requirement. Note, this option is implemented as a cap beyond which binary reduction takes place. So if you have a device capturing at 15fps and set this option to 10fps then the video is not produced at 10fps, but rather at 7.5fps (15 divided by 2) as the final frame rate must be the original divided by a power of 2.", + type => $types{integer}, + category => 'medband', + }, { name => "ZM_WEB_M_STREAM_IDLE_DELAY", default => "2500", @@ -1090,6 +1115,14 @@ my @options = type => $types{integer}, category => 'lowband', }, + { + name => "ZM_WEB_L_VIDEO_MAXFPS", + default => "5", + description => "What the maximum frame rate for streamed video should be", + help => "When using streamed video the main control is the bitrate which determines how much data can be transmitted. However a lower bitrate at high frame rates results in a lower quality image. This option allows you to limit the maximum frame rate to ensure that video quality is maintained. An additional advantage is that encoding video at high frame rates is a processor intensive task when for the most part a very high frame rate offers little perceptible improvement over one that has a more manageable resource requirement. Note, this option is implemented as a cap beyond which binary reduction takes place. So if you have a device capturing at 15fps and set this option to 10fps then the video is not produced at 10fps, but rather at 7.5fps (15 divided by 2) as the final frame rate must be the original divided by a power of 2.", + type => $types{integer}, + category => 'lowband', + }, { name => "ZM_WEB_L_STREAM_IDLE_DELAY", default => "10000",