Skip to content

Commit d817799

Browse files
useruser
authored andcommitted
refactor(mp4ctl): rewrite recording loop for robust serial processing
The previous loop_worker implementation attempted to maintain several mathematically incompatible invariants simultaneously: 1. Exact video duration (e.g., 60s). 2. Filenames with "round" timestamps (e.g., :00 seconds). 3. Zero processing time between files. 4. Keyframes arriving exactly at minute boundaries. 5. System clock is monotonic and never jumps (e.g. via NTP). In practice, this caused issues because: - Processing overhead (open/close) takes time, causing natural drift. - Wall clock adjustments (NTP) can shift time unexpectedly. - Keyframes/metadata arrive at unpredictable intervals, often requiring 1-2s delays. - The "catch-up" logic intended to align timestamps caused drift, dropped segments, and errors. This rewrite abandons the attempt to "beautify" timestamps in favor of reliability: - Independent Iterations: Each recording loop iteration is now independent. - Natural Drift: We accept that filenames will reflect the actual start time (e.g., 12-34-56.mp4) rather than a forced round number. - Current Time: Timestamps are derived from now() immediately before file creation, ensuring accuracy over cosmetic alignment. - Simplified Logic: Removed round_up_to_minute and sleep_until_time helpers.
1 parent aa0c794 commit d817799

1 file changed

Lines changed: 9 additions & 68 deletions

File tree

src/MP4ControlSocket.cpp

Lines changed: 9 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -99,32 +99,7 @@ std::array<std::shared_ptr<LoopState>, NUM_VIDEO_CHANNELS> loop_states;
9999
std::array<std::thread, NUM_VIDEO_CHANNELS> loop_threads;
100100
std::mutex loop_state_mutex;
101101

102-
std::chrono::system_clock::time_point round_up_to_minute(std::chrono::system_clock::time_point tp) {
103-
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(tp);
104-
auto epoch_seconds = seconds.time_since_epoch();
105-
auto remainder = epoch_seconds.count() % 60;
106-
if (remainder == 0) {
107-
return seconds;
108-
}
109-
return seconds + std::chrono::seconds(60 - remainder);
110-
}
111102

112-
bool sleep_until_time(std::chrono::system_clock::time_point target, std::atomic<bool> *stop_flag = nullptr) {
113-
while (true) {
114-
if (stop_flag && stop_flag->load(std::memory_order_relaxed)) {
115-
return false;
116-
}
117-
auto now = std::chrono::system_clock::now();
118-
if (now >= target) {
119-
return true;
120-
}
121-
auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(target - now);
122-
if (remaining > std::chrono::milliseconds(250)) {
123-
remaining = std::chrono::milliseconds(250);
124-
}
125-
std::this_thread::sleep_for(remaining);
126-
}
127-
}
128103

129104
struct MountEntry {
130105
fs::path mountPoint;
@@ -860,63 +835,29 @@ void loop_worker(int channel, std::shared_ptr<LoopState> state) {
860835
return;
861836
}
862837

863-
constexpr auto kMinute = std::chrono::seconds(60);
864-
constexpr auto kShortClipThreshold = std::chrono::seconds(15);
865-
866-
bool first_segment_pending = true;
867-
auto next_start = std::chrono::system_clock::now();
838+
constexpr int kShortClipSeconds = 15;
868839

869840
while (!state->stopRequested.load(std::memory_order_relaxed)) {
870-
int segment_duration_seconds = state->params.durationSeconds;
871-
std::chrono::system_clock::time_point segment_start;
872-
873-
if (first_segment_pending) {
874-
segment_start = std::chrono::system_clock::now();
875-
auto boundary = round_up_to_minute(segment_start);
876-
if (boundary <= segment_start) {
877-
boundary += kMinute;
878-
}
879-
auto span = std::chrono::duration_cast<std::chrono::seconds>(boundary - segment_start);
880-
if (span < kShortClipThreshold) {
881-
boundary += kMinute;
882-
span = std::chrono::duration_cast<std::chrono::seconds>(boundary - segment_start);
883-
}
884-
segment_duration_seconds = static_cast<int>(span.count());
885-
if (segment_duration_seconds <= 0) {
886-
segment_duration_seconds = state->params.durationSeconds;
887-
}
888-
next_start = boundary;
889-
} else {
890-
auto scheduled_start = next_start;
891-
auto now = std::chrono::system_clock::now();
892-
if (scheduled_start > now) {
893-
if (!sleep_until_time(scheduled_start, &state->stopRequested)) {
894-
break;
895-
}
896-
}
897-
segment_start = scheduled_start;
898-
segment_duration_seconds = state->params.durationSeconds;
899-
}
900-
901841
if (!wait_until_channel_idle(channel, &state->stopRequested)) {
902842
break;
903843
}
904844

905-
auto target = build_loop_target_path(state->params, segment_start);
845+
int segment_duration_seconds = state->params.durationSeconds;
846+
if (segment_duration_seconds < kShortClipSeconds) {
847+
segment_duration_seconds = kShortClipSeconds;
848+
}
849+
850+
auto now = std::chrono::system_clock::now();
851+
auto target = build_loop_target_path(state->params, now);
906852
if (target.empty()) {
907853
std::this_thread::sleep_for(std::chrono::milliseconds(250));
908854
continue;
909855
}
856+
910857
if (!begin_segment(target, channel, segment_duration_seconds)) {
911858
std::this_thread::sleep_for(std::chrono::milliseconds(500));
912859
continue;
913860
}
914-
if (!first_segment_pending) {
915-
// Advance next_start only after segment successfully started
916-
next_start = segment_start + std::chrono::seconds(segment_duration_seconds);
917-
next_start = round_up_to_minute(next_start);
918-
}
919-
first_segment_pending = false;
920861

921862
if (!wait_for_recording_completion(channel, &state->stopRequested)) {
922863
break;

0 commit comments

Comments
 (0)