Include loading EndTime when loading Events data so that we can determine if an event is in-progress

Keep track of last_id which is the last frame id that we have info for. Use it instead of Frame Count to prevent
accessing non-existent frame records
close ffmpeg_input on event change
include duration in status messages because it may change when playing an in-progress event.
when we hit the end of our frame data if it is an in-progress event, reload the event data instead of moving to the next event.
Include X-Timestamp in http headers to match live stream behaviour
Only send time to next event when mode=ALL
Fix scale behaviour on event view
This commit is contained in:
Isaac Connor 2020-10-08 09:06:56 -04:00
parent 7cc023c1ec
commit c28ff1f2b4
5 changed files with 69 additions and 42 deletions

View File

@ -117,6 +117,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
snprintf(sql, sizeof(sql),
"SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, "
"unix_timestamp( `EndTime` ) AS EndTimestamp, "
"(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS Duration, "
"`DefaultVideo`, `Scheme`, `SaveJPEGs`, `Orientation`+0 FROM `Events` WHERE `Id` = %" PRIu64, event_id);
@ -150,9 +151,10 @@ bool EventStream::loadEventData(uint64_t event_id) {
event_data->storage_id = dbrow[1] ? atoi(dbrow[1]) : 0;
event_data->frame_count = dbrow[2] == NULL ? 0 : atoi(dbrow[2]);
event_data->start_time = atoi(dbrow[3]);
event_data->duration = dbrow[4] ? atof(dbrow[4]) : 0.0;
strncpy(event_data->video_file, dbrow[5], sizeof(event_data->video_file)-1);
std::string scheme_str = std::string(dbrow[6]);
event_data->end_time = dbrow[4] ? atoi(dbrow[4]) : 0;
event_data->duration = dbrow[5] ? atof(dbrow[5]) : 0.0;
strncpy(event_data->video_file, dbrow[6], sizeof(event_data->video_file)-1);
std::string scheme_str = std::string(dbrow[7]);
if ( scheme_str == "Deep" ) {
event_data->scheme = Storage::DEEP;
} else if ( scheme_str == "Medium" ) {
@ -160,8 +162,8 @@ bool EventStream::loadEventData(uint64_t event_id) {
} else {
event_data->scheme = Storage::SHALLOW;
}
event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]);
event_data->Orientation = (Monitor::Orientation)(dbrow[8] == NULL ? 0 : atoi(dbrow[8]));
event_data->SaveJPEGs = dbrow[8] == NULL ? 0 : atoi(dbrow[8]);
event_data->Orientation = (Monitor::Orientation)(dbrow[9] == NULL ? 0 : atoi(dbrow[9]));
mysql_free_result(result);
if ( !monitor ) {
@ -223,8 +225,6 @@ bool EventStream::loadEventData(uint64_t event_id) {
}
updateFrameRate((double)event_data->frame_count/event_data->duration);
Debug(3, "fps set by frame_count(%d)/duration(%f)",
event_data->frame_count, event_data->duration);
snprintf(sql, sizeof(sql), "SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` "
"FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id);
@ -276,7 +276,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
last_id = id;
last_delta = delta;
last_timestamp = event_data->frames[id-1].timestamp;
Debug(4, "Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)",
Debug(3, "Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)",
id,
event_data->frames[id-1].timestamp,
event_data->frames[id-1].offset,
@ -284,11 +284,13 @@ bool EventStream::loadEventData(uint64_t event_id) {
event_data->frames[id-1].in_db
);
}
// Incomplete events might not have any frame data
event_data->last_frame_id = last_id;
if ( mysql_errno(&dbconn) ) {
Error("Can't fetch row: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn));
}
mysql_free_result(result);
if ( event_data->video_file[0] || (monitor->GetOptVideoWriter() > 0) ) {
@ -296,9 +298,10 @@ bool EventStream::loadEventData(uint64_t event_id) {
snprintf(event_data->video_file, sizeof(event_data->video_file), "%" PRIu64 "-%s", event_data->event_id, "video.mp4");
}
std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file);
//char filepath[PATH_MAX];
//snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file);
Debug(1, "Loading video file from %s", filepath.c_str());
if ( ffmpeg_input )
delete ffmpeg_input;
ffmpeg_input = new FFmpeg_Input();
if ( 0 > ffmpeg_input->Open(filepath.c_str()) ) {
Warning("Unable to open ffmpeg_input %s", filepath.c_str());
@ -307,14 +310,15 @@ bool EventStream::loadEventData(uint64_t event_id) {
}
}
// Not sure about this
if ( forceEventChange || mode == MODE_ALL_GAPLESS ) {
if ( replay_rate > 0 )
curr_stream_time = event_data->frames[0].timestamp;
else
curr_stream_time = event_data->frames[event_data->frame_count-1].timestamp;
curr_stream_time = event_data->frames[event_data->last_frame_id-1].timestamp;
}
Debug(2, "Event:%" PRIu64 ", Frames:%ld, Duration: %.2f",
event_data->event_id, event_data->frame_count, event_data->duration);
Debug(2, "Event:%" PRIu64 ", Frames:%ld, Last Frame ID(%ld, Duration: %.2f",
event_data->event_id, event_data->frame_count, event_data->last_frame_id, event_data->duration);
return true;
} // bool EventStream::loadEventData( int event_id )
@ -341,12 +345,12 @@ void EventStream::processCommand(const CmdMsg *msg) {
if (
(mode == MODE_SINGLE || mode == MODE_NONE)
&&
((unsigned int)curr_frame_id == event_data->frame_count)
((unsigned int)curr_frame_id == event_data->last_frame_id)
) {
Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame");
curr_frame_id = 1;
} else {
Debug(1, "mode is %s, current frame is %d, frame count is %d",
Debug(1, "mode is %s, current frame is %ld, frame count is %ld, last frame id is %ld",
(mode == MODE_SINGLE ? "single" : "not single"),
curr_frame_id, event_data->frame_count );
}
@ -395,7 +399,7 @@ void EventStream::processCommand(const CmdMsg *msg) {
paused = true;
replay_rate = ZM_RATE_BASE;
step = 1;
if ( (unsigned int)curr_frame_id < event_data->frame_count )
if ( (unsigned int)curr_frame_id < event_data->last_frame_id )
curr_frame_id += 1;
break;
case CMD_SLOWREV :
@ -489,14 +493,14 @@ void EventStream::processCommand(const CmdMsg *msg) {
if ( replay_rate >= 0 )
curr_frame_id = 0;
else
curr_frame_id = event_data->frame_count+1;
curr_frame_id = event_data->last_frame_id+1;
paused = false;
forceEventChange = true;
break;
case CMD_NEXT :
Debug(1, "Got NEXT command");
if ( replay_rate >= 0 )
curr_frame_id = event_data->frame_count+1;
curr_frame_id = event_data->last_frame_id+1;
else
curr_frame_id = 0;
paused = false;
@ -533,6 +537,7 @@ void EventStream::processCommand(const CmdMsg *msg) {
}
struct {
uint64_t event_id;
double duration;
double progress;
int rate;
int zoom;
@ -540,12 +545,14 @@ void EventStream::processCommand(const CmdMsg *msg) {
} status_data;
status_data.event_id = event_data->event_id;
status_data.duration = event_data->duration;
status_data.progress = event_data->frames[curr_frame_id-1].offset;
status_data.rate = replay_rate;
status_data.zoom = zoom;
status_data.paused = paused;
Debug(2, "Event:%" PRIu64 ", Paused:%d, progress:%f Rate:%d, Zoom:%d",
Debug(2, "Event:%" PRIu64 ", Duration %f, Paused:%d, progress:%f Rate:%d, Zoom:%d",
status_data.event_id,
status_data.duration,
status_data.paused,
status_data.progress,
status_data.rate,
@ -577,7 +584,14 @@ bool EventStream::checkEventLoaded() {
snprintf(sql, sizeof(sql),
"SELECT `Id` FROM `Events` WHERE `MonitorId` = %ld AND `Id` < %" PRIu64 " ORDER BY `Id` DESC LIMIT 1",
event_data->monitor_id, event_data->event_id);
} else if ( (unsigned int)curr_frame_id > event_data->frame_count ) {
} else if ( (unsigned int)curr_frame_id > event_data->last_frame_id ) {
if ( !event_data->end_time ) {
// We are viewing an in-process event, so just reload it.
loadEventData(event_data->event_id);
if ( (unsigned int)curr_frame_id > event_data->last_frame_id )
curr_frame_id = event_data->last_frame_id;
return false;
}
snprintf(sql, sizeof(sql),
"SELECT `Id` FROM `Events` WHERE `MonitorId` = %ld AND `Id` > %" PRIu64 " ORDER BY `Id` ASC LIMIT 1",
event_data->monitor_id, event_data->event_id);
@ -590,6 +604,7 @@ bool EventStream::checkEventLoaded() {
// Event change required.
if ( forceEventChange || ( (mode != MODE_SINGLE) && (mode != MODE_NONE) ) ) {
Debug(1, "Checking for next event %s", sql);
if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn));
@ -600,6 +615,9 @@ bool EventStream::checkEventLoaded() {
Error("Can't use query result: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn));
}
if ( mysql_num_rows(result) != 1 ) {
Debug(1, "No rows returned for %s", sql);
}
MYSQL_ROW dbrow = mysql_fetch_row(result);
if ( mysql_errno(&dbconn)) {
@ -614,7 +632,7 @@ bool EventStream::checkEventLoaded() {
loadEventData(event_id);
if ( replay_rate < 0 ) // rewind
curr_frame_id = event_data->frame_count;
curr_frame_id = event_data->last_frame_id;
else
curr_frame_id = 1;
Debug(2, "New frame id = %d", curr_frame_id);
@ -635,7 +653,7 @@ bool EventStream::checkEventLoaded() {
if ( curr_frame_id <= 0 )
curr_frame_id = 1;
else
curr_frame_id = event_data->frame_count;
curr_frame_id = event_data->last_frame_id;
paused = true;
}
return false;
@ -846,15 +864,17 @@ Debug(1, "Loading image");
} else {
Debug(3, "Content length: %d", img_buffer_size);
if (
(0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size) )
( 0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %.06f\r\n\r\n",
img_buffer_size, event_data->frames[curr_frame_id-1].timestamp) )
||
( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) ) {
Error("Unable to send stream frame: %s", strerror(errno));
return false;
( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 )
) {
//Error("Unable to send stream frame: %s", strerror(errno));
//return false;
}
} // end if send_raw or not
fputs("\r\n\r\n", stdout);
fputs("\r\n", stdout);
fflush(stdout);
} // end if stream MPEG or other
last_frame_sent = TV_2_FLOAT(now);
@ -874,7 +894,6 @@ void EventStream::runStream() {
exit(0);
}
Debug(3, "frame rate is: (%f)", (double)event_data->frame_count/event_data->duration);
updateFrameRate((double)event_data->frame_count/event_data->duration);
gettimeofday(&start, NULL);
uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec;
@ -916,25 +935,28 @@ void EventStream::runStream() {
time_to_event = event_data->frames[0].timestamp - curr_stream_time;
if ( time_to_event > 0 )
in_event = false;
Debug(1, "replay rate(%d) in_event(%d) time_to_event(%f)=frames[0]->timestamp(%f)-curr_stream_time(%f)",
replay_rate, in_event, time_to_event,
event_data->frames[0].timestamp,
curr_stream_time
);
} else if ( replay_rate < 0 ) {
time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp;
if ( time_to_event > 0 )
in_event = false;
Debug(1, "replay rate(%d) in_event(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f",
replay_rate, in_event, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp);
}
Debug(1, "replay rate(%d) in_event(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f",
replay_rate, in_event, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp);
if ( !in_event ) {
double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent;
Debug(1, "Ctual delta time = %f = %f - %f", actual_delta_time , TV_2_FLOAT(now) , last_frame_sent);
if ( !in_event and (mode == MODE_ALL) ) {
double time_since_last_send = TV_2_FLOAT(now) - last_frame_sent;
Debug(1, "Actual delta time = %f = %f - %f", time_since_last_send, TV_2_FLOAT(now), last_frame_sent);
// > 1 second
if ( actual_delta_time > 1 ) {
if ( time_since_last_send > 1 ) {
Debug(1, "Sending time to next event frame");
static char frame_text[64];
snprintf(frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event);
if ( !sendTextFrame(frame_text) )
zm_terminate = true;
} else {
Debug(1, "Not Sending time to next event frame because actual delta time is %f", actual_delta_time);
}
//else
//{

View File

@ -54,11 +54,13 @@ class EventStream : public StreamBase {
uint64_t event_id;
unsigned long monitor_id;
unsigned long storage_id;
unsigned long frame_count;
unsigned long frame_count; // Value of Frames column in Event
unsigned long last_frame_id; // Highest frame id known about. Can be < frame_count in incomplete events
time_t start_time;
time_t end_time;
double duration;
char path[PATH_MAX];
int n_frames;
int n_frames; // # of frame rows returned from database
FrameData *frames;
char video_file[PATH_MAX];
Storage::Schemes scheme;

View File

@ -136,10 +136,10 @@ if ( sem_acquire($semaphore,1) !== false ) {
case MSG_DATA_EVENT :
if ( version_compare( phpversion(), '5.6.0', '<') ) {
ZM\Logger::Debug('Using old unpack methods to handle 64bit event id');
$data = unpack('ltype/ieventlow/ieventhigh/dprogress/irate/izoom/Cpaused', $msg);
$data = unpack('ltype/ieventlow/ieventhigh/dduration/dprogress/irate/izoom/Cpaused', $msg);
$data['event'] = $data['eventhigh'] << 32 | $data['eventlow'];
} else {
$data = unpack('ltype/Qevent/dprogress/irate/izoom/Cpaused', $msg);
$data = unpack('ltype/Qevent/dduration/dprogress/irate/izoom/Cpaused', $msg);
}
$data['rate'] /= RATE_BASE;
$data['zoom'] = round($data['zoom']/SCALE_BASE, 1);

View File

@ -52,7 +52,7 @@ if ( isset($_REQUEST['scale']) ) {
} else if ( isset($_COOKIE['zmEventScale'.$Event->MonitorId()]) ) {
$scale = $_COOKIE['zmEventScale'.$Event->MonitorId()];
} else {
$scale = reScale(SCALE_BASE, $Monitor->DefaultScale(), ZM_WEB_DEFAULT_SCALE);
$scale = $Monitor->DefaultScale();
}
$codec = 'auto';

View File

@ -243,6 +243,9 @@ function getCmdResponse( respObj, respText ) {
}
streamStatus = respObj.status;
if ( streamStatus.duration && ( streamStatus.duration != parseFloat(eventData.Length) ) ) {
eventData.Length = streamStatus.duration;
}
if ( streamStatus.progress > parseFloat(eventData.Length) ) {
console.log("Limiting progress to " + streamStatus.progress + ' >= ' + parseFloat(eventData.Length) );
streamStatus.progress = parseFloat(eventData.Length);