Skip to content

Commit 00e4e77

Browse files
author
origin2000
committed
fix: start_time correction, external motion trigger (UDT), mp4_writer_close sequence
1 parent 54761f1 commit 00e4e77

9 files changed

Lines changed: 363 additions & 131 deletions

include/database/db_recordings.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ uint64_t add_recording_metadata(const recording_metadata_t *metadata);
5151
int update_recording_metadata(uint64_t id, time_t end_time,
5252
uint64_t size_bytes, bool is_complete);
5353

54+
/**
55+
* Correct the start_time of an existing recording.
56+
*
57+
* Called after the pre-buffer has been flushed into a detection recording so
58+
* that the database reflects the actual first-packet wall-clock time rather
59+
* than the time at which mp4_writer_create() was called.
60+
*
61+
* @param id Recording ID returned by add_recording_metadata()
62+
* @param start_time Corrected start time (epoch seconds)
63+
* @return 0 on success, non-zero on failure
64+
*/
65+
int update_recording_start_time(uint64_t id, time_t start_time);
66+
5467
/**
5568
* Get recording metadata from the database
5669
*

include/video/mp4_writer.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ struct mp4_writer {
5454
// Recording trigger type
5555
char trigger_type[16]; // 'scheduled', 'detection', 'motion', 'manual'
5656

57+
// Set to true once update_recording_start_time() has been called after the
58+
// pre-event buffer was flushed. Prevents a second correction on rotation.
59+
bool start_time_corrected;
60+
5761
// RTSP thread context
5862
mp4_writer_thread_t *thread_ctx; // Changed from void* to proper type
5963

@@ -99,6 +103,16 @@ int mp4_writer_add_audio_stream(mp4_writer_t *writer, const AVCodecParameters *c
99103
*/
100104
void mp4_writer_close(mp4_writer_t *writer);
101105

106+
/**
107+
* Return the actual encoded duration of an MP4 file in seconds by reading
108+
* its container metadata. Used after a file has been finalized to get a more
109+
* accurate duration than (end_time - start_time) wall-clock subtraction.
110+
*
111+
* @param path Full path to the closed MP4 file
112+
* @return Duration in seconds (>= 0), or -1 on error
113+
*/
114+
double get_mp4_file_duration_seconds(const char *path);
115+
102116
/**
103117
* Enable or disable audio recording
104118
*

include/video/unified_detection_thread.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ typedef struct {
9696
// Audio recording configuration
9797
bool record_audio; // Whether to include audio in recordings
9898

99+
// External motion trigger: set to 1 by unified_detection_notify_motion() when
100+
// an ONVIF-managed master stream propagates its motion event to this UDT-managed
101+
// slave stream (e.g. the PTZ lens on a TP-Link C545D that has no ONVIF profile).
102+
// The UDT processing path checks this flag when handling eligible video
103+
// keyframes and treats a rising edge as equivalent to a local detection
104+
// event (starts/extends recording).
105+
// Use atomic_store/atomic_load to avoid races between the event-processor thread
106+
// (writer) and the UDT thread (reader/resetter).
107+
atomic_int external_motion_trigger; // 0 = idle, 1 = motion active, 2 = motion ended
108+
99109
// Annotation-only mode: when true, detection runs but does NOT create separate MP4 files
100110
// Detections are stored in the database and linked to the continuous recording
101111
bool annotation_only;
@@ -235,5 +245,19 @@ int get_unified_detection_stats(const char *stream_name,
235245
uint64_t *detections,
236246
uint64_t *recordings);
237247

248+
/**
249+
* Notify a UDT-managed stream of an externally-detected motion event.
250+
*
251+
* Called by the ONVIF motion recording system when a master stream's ONVIF
252+
* event must be propagated to a slave stream that is managed by a UDT
253+
* (e.g. the PTZ lens on a dual-lens camera). The UDT thread polls
254+
* ctx->external_motion_trigger and reacts on the next packet boundary.
255+
*
256+
* @param stream_name Name of the slave stream (must be running as a UDT)
257+
* @param motion_active true = motion started / ongoing
258+
* false = motion ended
259+
*/
260+
void unified_detection_notify_motion(const char *stream_name, bool motion_active);
261+
238262
#endif /* UNIFIED_DETECTION_THREAD_H */
239263

src/database/db_recordings.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,57 @@ int update_recording_metadata(uint64_t id, time_t end_time,
204204
return 0;
205205
}
206206

207+
/**
208+
* Correct the start_time of an existing recording in the database.
209+
*
210+
* Used after flushing the pre-event circular buffer into a detection recording
211+
* so that the stored start_time matches the actual first packet timestamp
212+
* rather than the time mp4_writer_create() was called.
213+
*/
214+
int update_recording_start_time(uint64_t id, time_t start_time) {
215+
int rc;
216+
sqlite3_stmt *stmt;
217+
218+
sqlite3 *db = get_db_handle();
219+
pthread_mutex_t *db_mutex = get_db_mutex();
220+
221+
if (!db) {
222+
log_error("Database not initialized");
223+
return -1;
224+
}
225+
226+
pthread_mutex_lock(db_mutex);
227+
228+
const char *sql = "UPDATE recordings SET start_time = ? WHERE id = ?;";
229+
230+
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
231+
if (rc != SQLITE_OK) {
232+
log_error("Failed to prepare update_recording_start_time statement: %s",
233+
sqlite3_errmsg(db));
234+
pthread_mutex_unlock(db_mutex);
235+
return -1;
236+
}
237+
238+
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)start_time);
239+
sqlite3_bind_int64(stmt, 2, (sqlite3_int64)id);
240+
241+
rc = sqlite3_step(stmt);
242+
if (rc != SQLITE_DONE) {
243+
log_error("Failed to update recording start_time (id=%lu): %s",
244+
(unsigned long)id, sqlite3_errmsg(db));
245+
sqlite3_finalize(stmt);
246+
pthread_mutex_unlock(db_mutex);
247+
return -1;
248+
}
249+
250+
sqlite3_finalize(stmt);
251+
pthread_mutex_unlock(db_mutex);
252+
253+
log_debug("Corrected start_time for recording ID %lu to %ld",
254+
(unsigned long)id, (long)start_time);
255+
return 0;
256+
}
257+
207258
// Get recording metadata by ID
208259
int get_recording_metadata_by_id(uint64_t id, recording_metadata_t *metadata) {
209260
int rc;

src/video/mp4_segment_recorder.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,10 @@ int record_segment(const char *rtsp_url, const char *output_file, int duration,
967967

968968
log_info("Found first key frame, starting recording");
969969

970-
// Notify caller that segment has officially started (aligned to keyframe)
970+
// Notify caller that segment has officially started (aligned to keyframe).
971+
// The callback (on_segment_started_cb in mp4_writer_thread.c) creates the
972+
// database recording entry at this point so that start_time is anchored
973+
// to a decodable keyframe rather than the wall-clock time of avformat_open_input.
971974
if (!started_cb_called && started_cb) {
972975
started_cb(cb_ctx);
973976
started_cb_called = true;

0 commit comments

Comments
 (0)