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
This commit is contained in:
stan 2004-03-11 11:18:14 +00:00
parent b9dbe6f381
commit e6ef3bbcd7
10 changed files with 112 additions and 102 deletions

View File

@ -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 ));
}

View File

@ -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
};

View File

@ -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

View File

@ -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
};

View File

@ -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" );

View File

@ -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

View File

@ -265,7 +265,7 @@ if ( $mode == "stream" )
<?php
if ( ZM_WEB_VIDEO_STREAM_METHOD == 'mpeg' )
{
$stream_src = ZM_PATH_ZMS."?mode=mpeg&event=$eid&rate=$rate&scale=$scale&bitrate=".VIDEO_BITRATE;
$stream_src = ZM_PATH_ZMS."?mode=mpeg&event=$eid&rate=$rate&scale=$scale&bitrate=".VIDEO_BITRATE."&maxfps=".VIDEO_MAXFPS
if ( isWindows() )
{
if ( isInternetExplorer() )

View File

@ -104,7 +104,7 @@ if ( $mode == "stream" )
{
if ( ZM_WEB_VIDEO_STREAM_METHOD == 'mpeg' )
{
$stream_src = ZM_PATH_ZMS."?mode=mpeg&monitor=".$monitor['Id']."&scale=$scale&bitrate=".VIDEO_BITRATE."&buffer=0";
$stream_src = ZM_PATH_ZMS."?mode=mpeg&monitor=".$monitor['Id']."&scale=$scale&bitrate=".VIDEO_BITRATE."&maxfps=".VIDEO_MAXFPS."&buffer=0";
if ( isWindows() )
{
if ( isInternetExplorer() )

View File

@ -101,7 +101,7 @@ if ( $mode == "stream" )
{
if ( ZM_WEB_VIDEO_STREAM_METHOD == 'mpeg' )
{
$stream_src = ZM_PATH_ZMS."?mode=mpeg&monitor=".$monitor['Id']."&scale=$scale&bitrate=".VIDEO_BITRATE."&buffer=0";
$stream_src = ZM_PATH_ZMS."?mode=mpeg&monitor=".$monitor['Id']."&scale=$scale&bitrate=".VIDEO_BITRATE."&maxfps=".VIDEO_MAXFPS."&buffer=0";
if ( isWindows() )
{
if ( isInternetExplorer() )

View File

@ -460,6 +460,15 @@ my @options =
type => { 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",