From 6714d838106d727a3b4a3bf491a15f1b4fa32f2c Mon Sep 17 00:00:00 2001 From: Thegreatcodeholio Date: Sat, 4 Oct 2025 19:54:53 -0400 Subject: [PATCH 1/7] Add Millisecond Timestamp to call json. --- trunk-recorder/call_concluder/call_concluder.cc | 4 ++++ trunk-recorder/call_impl.cc | 15 +++++++++++++++ trunk-recorder/call_impl.h | 2 ++ trunk-recorder/global_structs.h | 4 ++++ trunk-recorder/gr_blocks/transmission_sink.cc | 15 +++++++++++++++ trunk-recorder/gr_blocks/transmission_sink.h | 2 ++ 6 files changed, 42 insertions(+) diff --git a/trunk-recorder/call_concluder/call_concluder.cc b/trunk-recorder/call_concluder/call_concluder.cc index 02287853c..cea6bb112 100644 --- a/trunk-recorder/call_concluder/call_concluder.cc +++ b/trunk-recorder/call_concluder/call_concluder.cc @@ -70,6 +70,8 @@ int create_call_json(Call_Data_t& call_info) { {"phase2_tdma", int(call_info.phase2_tdma)}, {"start_time", call_info.start_time}, {"stop_time", call_info.stop_time}, + {"start_time_ms", call_info.start_time_ms}, + {"stop_time_ms", call_info.stop_time_ms}, {"emergency", int(call_info.emergency)}, {"priority", call_info.priority}, {"mode", int(call_info.mode)}, @@ -402,10 +404,12 @@ Call_Data_t Call_Concluder::create_call_data(Call *call, System *sys, Config con if (it == call_info.transmission_list.begin()) { call_info.start_time = t.start_time; + call_info.start_time_ms = t.start_time_ms; } if (std::next(it) == call_info.transmission_list.end()) { call_info.stop_time = t.stop_time; + call_info.stop_time_ms = t.stop_time_ms; } Call_Source call_source = {t.source, t.start_time, total_length, false, "", tag}; diff --git a/trunk-recorder/call_impl.cc b/trunk-recorder/call_impl.cc index 553946361..c846058ab 100644 --- a/trunk-recorder/call_impl.cc +++ b/trunk-recorder/call_impl.cc @@ -8,6 +8,7 @@ #include #include #include +#include std::string Call_impl::get_capture_dir() { return this->config.capture_dir; @@ -41,6 +42,11 @@ Call_impl::Call_impl(long t, double f, System *s, Config c) { sys = s; start_time = time(NULL); stop_time = time(NULL); + start_time_ms = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + stop_time_ms = 0; last_update = time(NULL); state = MONITORING; monitoringState = UNSPECIFIED; @@ -74,6 +80,11 @@ Call_impl::Call_impl(TrunkMessage message, System *s, Config c) { sys = s; start_time = time(NULL); stop_time = time(NULL); + start_time_ms = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + stop_time_ms = 0; last_update = time(NULL); state = MONITORING; monitoringState = UNSPECIFIED; @@ -123,6 +134,10 @@ void Call_impl::conclude_call() { // BOOST_LOG_TRIVIAL(info) << "conclude_call()"; stop_time = time(NULL); + stop_time_ms = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); if (state == RECORDING || (state == MONITORING && monitoringState == SUPERSEDED)) { if (!recorder) { diff --git a/trunk-recorder/call_impl.h b/trunk-recorder/call_impl.h index 5b2dbe795..d2886c645 100644 --- a/trunk-recorder/call_impl.h +++ b/trunk-recorder/call_impl.h @@ -114,6 +114,8 @@ class Call_impl : public Call { int idle_count; time_t stop_time; time_t start_time; + long long start_time_ms; + long long stop_time_ms; bool debug_recording; bool sigmf_recording; bool was_update; diff --git a/trunk-recorder/global_structs.h b/trunk-recorder/global_structs.h index 5c4930225..d0712e70e 100644 --- a/trunk-recorder/global_structs.h +++ b/trunk-recorder/global_structs.h @@ -11,6 +11,8 @@ struct Transmission { long source; long start_time; long stop_time; + long long start_time_ms; + long long stop_time_ms; long sample_count; long spike_count; long error_count; @@ -105,6 +107,8 @@ struct Call_Data_t { double noise; long start_time; long stop_time; + long long start_time_ms; + long long stop_time_ms; long error_count; long spike_count; bool encrypted; diff --git a/trunk-recorder/gr_blocks/transmission_sink.cc b/trunk-recorder/gr_blocks/transmission_sink.cc index 931a9ad89..75ea76e17 100644 --- a/trunk-recorder/gr_blocks/transmission_sink.cc +++ b/trunk-recorder/gr_blocks/transmission_sink.cc @@ -228,11 +228,19 @@ void transmission_sink::end_transmission() { } else { BOOST_LOG_TRIVIAL(error) << "Ending transmission, sample_count is greater than 0 but d_fp is null" << std::endl; } + + auto now_ms = std::chrono::system_clock::now(); + d_stop_time_ms = std::chrono::duration_cast( + now_ms.time_since_epoch() + ).count(); + // if an Transmission has ended, send it to Call. Transmission transmission; transmission.source = curr_src_id; // Source ID for the Call transmission.start_time = d_start_time; // Start time of the Call transmission.stop_time = d_stop_time; // when the Call eneded + transmission.start_time_ms = d_start_time_ms; + transmission.stop_time_ms = d_stop_time_ms; transmission.sample_count = d_sample_count; transmission.spike_count = d_spike_count; transmission.error_count = d_error_count; @@ -506,6 +514,11 @@ int transmission_sink::dowork(int noutput_items, gr_vector_const_void_star &inpu d_start_time = current_time; } + auto now_ms = std::chrono::system_clock::now(); + d_start_time_ms = std::chrono::duration_cast( + now_ms.time_since_epoch() + ).count(); + // create a new filename, based on the current time and source. create_filename(); if (!open_internal(current_filename)) { @@ -551,6 +564,8 @@ int transmission_sink::dowork(int noutput_items, gr_vector_const_void_star &inpu d_stop_time = time(NULL); d_last_write_time = std::chrono::steady_clock::now(); + d_stop_time_ms = std::chrono::duration_cast(now_ms.time_since_epoch()).count(); + if (nwritten < noutput_items) { BOOST_LOG_TRIVIAL(error) << loghdr << "Failed to Write! Wrote: " << nwritten << " of " << noutput_items; } else { diff --git a/trunk-recorder/gr_blocks/transmission_sink.h b/trunk-recorder/gr_blocks/transmission_sink.h index ba2a91e55..bdf1efd70 100644 --- a/trunk-recorder/gr_blocks/transmission_sink.h +++ b/trunk-recorder/gr_blocks/transmission_sink.h @@ -52,6 +52,8 @@ class BLOCKS_API transmission_sink : virtual public sync_block { bool d_termination_flag; time_t d_start_time; time_t d_stop_time; + long long d_start_time_ms; + long long d_stop_time_ms; std::chrono::time_point d_last_write_time; long d_spike_count; long d_error_count; From e24e3e9a3b650611687a7a75363b379c3f62b843 Mon Sep 17 00:00:00 2001 From: Thegreatcodeholio Date: Sat, 4 Oct 2025 20:06:55 -0400 Subject: [PATCH 2/7] send MS time with call_stats, fix compile issue missing now_ms --- trunk-recorder/call_impl.cc | 2 ++ trunk-recorder/gr_blocks/transmission_sink.cc | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/trunk-recorder/call_impl.cc b/trunk-recorder/call_impl.cc index c846058ab..a126f6cc4 100644 --- a/trunk-recorder/call_impl.cc +++ b/trunk-recorder/call_impl.cc @@ -505,6 +505,8 @@ boost::property_tree::ptree Call_impl::get_stats() { call_node.put("duplex", this->get_duplex()); call_node.put("startTime", this->get_start_time()); call_node.put("stopTime", this->get_stop_time()); + call_node.put("startTimeMs", this->start_time_ms); + call_node.put("stopTimeMs", this->stop_time_ms); call_node.put("srcId", this->get_current_source_id()); Recorder *recorder = this->get_recorder(); diff --git a/trunk-recorder/gr_blocks/transmission_sink.cc b/trunk-recorder/gr_blocks/transmission_sink.cc index 75ea76e17..cd8ef6131 100644 --- a/trunk-recorder/gr_blocks/transmission_sink.cc +++ b/trunk-recorder/gr_blocks/transmission_sink.cc @@ -564,7 +564,10 @@ int transmission_sink::dowork(int noutput_items, gr_vector_const_void_star &inpu d_stop_time = time(NULL); d_last_write_time = std::chrono::steady_clock::now(); - d_stop_time_ms = std::chrono::duration_cast(now_ms.time_since_epoch()).count(); + auto now_sys = std::chrono::system_clock::now(); + d_stop_time_ms = std::chrono::duration_cast( + now_sys.time_since_epoch() + ).count(); if (nwritten < noutput_items) { BOOST_LOG_TRIVIAL(error) << loghdr << "Failed to Write! Wrote: " << nwritten << " of " << noutput_items; From 967016e3502cd139be10dfb1172fe6005480cbff Mon Sep 17 00:00:00 2001 From: Thegreatcodeholio Date: Sun, 5 Oct 2025 00:23:01 -0400 Subject: [PATCH 3/7] make the millisecond timestamps more accurate, reliable and efficient. Using sample rate and channels to derive the length of the file. 2038 proofed ms timestamps. change from long long to int64 --- trunk-recorder/call_conventional.cc | 20 ++++++-- trunk-recorder/call_impl.h | 4 +- trunk-recorder/global_structs.h | 8 +-- trunk-recorder/gr_blocks/transmission_sink.cc | 50 ++++++++----------- trunk-recorder/gr_blocks/transmission_sink.h | 5 +- trunk-recorder/monitor_systems.cc | 1 + 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/trunk-recorder/call_conventional.cc b/trunk-recorder/call_conventional.cc index 252cf6712..f1519d1c6 100644 --- a/trunk-recorder/call_conventional.cc +++ b/trunk-recorder/call_conventional.cc @@ -3,6 +3,7 @@ #include "formatter.h" #include "recorders/recorder.h" #include +#include Call_conventional::Call_conventional(long t, double f, System *s, Config c, double squelch_db, bool signal_detection) : Call_impl(t, f, s, c) { this->squelch_db = squelch_db; @@ -16,8 +17,13 @@ void Call_conventional::restart_call() { signal = DB_UNSET; noise = DB_UNSET; curr_src_id = -1; - start_time = time(NULL); - stop_time = time(NULL); + + auto now = std::chrono::system_clock::now(); + start_time = std::chrono::system_clock::to_time_t(now); + start_time_ms = std::chrono::duration_cast(now.time_since_epoch()).count(); + stop_time = start_time; + stop_time_ms = start_time_ms; + last_update = time(NULL); state = RECORDING; debug_recording = false; @@ -29,9 +35,13 @@ void Call_conventional::restart_call() { recorder->start(this); } +// derive start from stop − final_length: time_t Call_conventional::get_start_time() { // Fixes https://github.com/robotastic/trunk-recorder/issues/103#issuecomment-284825841 - return start_time = stop_time - final_length; + start_time = stop_time - final_length; + // keep ms in sync (rounded): + start_time_ms = stop_time_ms - static_cast(std::llround(final_length * 1000.0)); + return start_time; } void Call_conventional::set_recorder(Recorder *r) { @@ -40,7 +50,9 @@ void Call_conventional::set_recorder(Recorder *r) { } void Call_conventional::recording_started() { - start_time = time(NULL); + auto now = std::chrono::system_clock::now(); + start_time = std::chrono::system_clock::to_time_t(now); + start_time_ms = std::chrono::duration_cast(now.time_since_epoch()).count(); } double Call_conventional::get_squelch_db() { diff --git a/trunk-recorder/call_impl.h b/trunk-recorder/call_impl.h index d2886c645..42371e461 100644 --- a/trunk-recorder/call_impl.h +++ b/trunk-recorder/call_impl.h @@ -114,8 +114,8 @@ class Call_impl : public Call { int idle_count; time_t stop_time; time_t start_time; - long long start_time_ms; - long long stop_time_ms; + std::int64_t start_time_ms; + std::int64_t stop_time_ms; bool debug_recording; bool sigmf_recording; bool was_update; diff --git a/trunk-recorder/global_structs.h b/trunk-recorder/global_structs.h index d0712e70e..067b01883 100644 --- a/trunk-recorder/global_structs.h +++ b/trunk-recorder/global_structs.h @@ -11,8 +11,8 @@ struct Transmission { long source; long start_time; long stop_time; - long long start_time_ms; - long long stop_time_ms; + std::int64_t start_time_ms; + std::int64_t stop_time_ms; long sample_count; long spike_count; long error_count; @@ -107,8 +107,8 @@ struct Call_Data_t { double noise; long start_time; long stop_time; - long long start_time_ms; - long long stop_time_ms; + std::int64_t start_time_ms; + std::int64_t stop_time_ms; long error_count; long spike_count; bool encrypted; diff --git a/trunk-recorder/gr_blocks/transmission_sink.cc b/trunk-recorder/gr_blocks/transmission_sink.cc index cd8ef6131..4618c1f01 100644 --- a/trunk-recorder/gr_blocks/transmission_sink.cc +++ b/trunk-recorder/gr_blocks/transmission_sink.cc @@ -33,6 +33,7 @@ #include #include #include +#include // win32 (mingw/msvc) specific #ifdef HAVE_IO_H @@ -229,22 +230,23 @@ void transmission_sink::end_transmission() { BOOST_LOG_TRIVIAL(error) << "Ending transmission, sample_count is greater than 0 but d_fp is null" << std::endl; } - auto now_ms = std::chrono::system_clock::now(); - d_stop_time_ms = std::chrono::duration_cast( - now_ms.time_since_epoch() - ).count(); + const int64_t dur_ms = (int64_t)std::llround(1000.0 * (d_nchans > 0 ? (double)d_sample_count / (d_sample_rate * (double)d_nchans) : 0.0)); - // if an Transmission has ended, send it to Call. + // Assign canonical stop time from sample count + d_stop_time_ms = d_start_time_ms + dur_ms; + d_stop_time = static_cast(d_stop_time_ms / 1000); + + // Build Transmission using the canonical fields Transmission transmission; - transmission.source = curr_src_id; // Source ID for the Call - transmission.start_time = d_start_time; // Start time of the Call - transmission.stop_time = d_stop_time; // when the Call eneded - transmission.start_time_ms = d_start_time_ms; - transmission.stop_time_ms = d_stop_time_ms; - transmission.sample_count = d_sample_count; - transmission.spike_count = d_spike_count; - transmission.error_count = d_error_count; - transmission.length = length_in_seconds(); // length in seconds + transmission.source = curr_src_id; + transmission.start_time = d_start_time; + transmission.stop_time = d_stop_time; + transmission.start_time_ms = d_start_time_ms; + transmission.stop_time_ms = d_stop_time_ms; + transmission.sample_count = d_sample_count; + transmission.spike_count = d_spike_count; + transmission.error_count = d_error_count; + transmission.length = length_in_seconds(); d_prior_transmission_length = d_prior_transmission_length + transmission.length; strcpy(transmission.filename, current_filename); // Copy the filename this->add_transmission(transmission); @@ -507,17 +509,11 @@ int transmission_sink::dowork(int noutput_items, gr_vector_const_void_star &inpu close_wav(false); } - time_t current_time = time(NULL); - if (current_time == d_start_time) { - d_start_time = current_time + 1; - } else { - d_start_time = current_time; - } - - auto now_ms = std::chrono::system_clock::now(); + auto now_sys = std::chrono::system_clock::now(); d_start_time_ms = std::chrono::duration_cast( - now_ms.time_since_epoch() + now_sys.time_since_epoch() ).count(); + d_start_time = std::chrono::system_clock::to_time_t(now_sys); // create a new filename, based on the current time and source. create_filename(); @@ -561,14 +557,8 @@ int transmission_sink::dowork(int noutput_items, gr_vector_const_void_star &inpu } } - d_stop_time = time(NULL); d_last_write_time = std::chrono::steady_clock::now(); - auto now_sys = std::chrono::system_clock::now(); - d_stop_time_ms = std::chrono::duration_cast( - now_sys.time_since_epoch() - ).count(); - if (nwritten < noutput_items) { BOOST_LOG_TRIVIAL(error) << loghdr << "Failed to Write! Wrote: " << nwritten << " of " << noutput_items; } else { @@ -605,7 +595,7 @@ double transmission_sink::total_length_in_seconds() { } double transmission_sink::length_in_seconds() { - return (double)d_sample_count / (double)d_sample_rate; + return d_nchans > 0 ? (double)d_sample_count / ((double)d_sample_rate * (double)d_nchans) : 0.0; } void transmission_sink::do_update() {} diff --git a/trunk-recorder/gr_blocks/transmission_sink.h b/trunk-recorder/gr_blocks/transmission_sink.h index bdf1efd70..00c1c3524 100644 --- a/trunk-recorder/gr_blocks/transmission_sink.h +++ b/trunk-recorder/gr_blocks/transmission_sink.h @@ -32,6 +32,7 @@ #include #include #include +#include class Call; struct Transmission; @@ -52,8 +53,8 @@ class BLOCKS_API transmission_sink : virtual public sync_block { bool d_termination_flag; time_t d_start_time; time_t d_stop_time; - long long d_start_time_ms; - long long d_stop_time_ms; + std::int64_t d_start_time_ms; + std::int64_t d_stop_time_ms; std::chrono::time_point d_last_write_time; long d_spike_count; long d_error_count; diff --git a/trunk-recorder/monitor_systems.cc b/trunk-recorder/monitor_systems.cc index ff66daef2..5e9e17f13 100644 --- a/trunk-recorder/monitor_systems.cc +++ b/trunk-recorder/monitor_systems.cc @@ -1,4 +1,5 @@ #include "monitor_systems.h" +#include using namespace std; volatile sig_atomic_t exit_flag = 0; From 3af259c20ba91dbbe9f767dfc753dceaa61a200b Mon Sep 17 00:00:00 2001 From: Thegreatcodeholio Date: Sun, 5 Oct 2025 18:14:28 -0400 Subject: [PATCH 4/7] add call_length_ms for more accurate length --- .../call_concluder/call_concluder.cc | 194 +++++++++++------- trunk-recorder/global_structs.h | 2 + trunk-recorder/gr_blocks/transmission_sink.cc | 62 ++++-- 3 files changed, 171 insertions(+), 87 deletions(-) diff --git a/trunk-recorder/call_concluder/call_concluder.cc b/trunk-recorder/call_concluder/call_concluder.cc index cea6bb112..b029db089 100644 --- a/trunk-recorder/call_concluder/call_concluder.cc +++ b/trunk-recorder/call_concluder/call_concluder.cc @@ -2,6 +2,10 @@ #include "../plugin_manager/plugin_manager.h" #include #include +#include +#include +#include + namespace fs = std::filesystem; const int Call_Concluder::MAX_RETRY = 2; @@ -57,7 +61,8 @@ int create_call_json(Call_Data_t& call_info) { // Using nlohmann::ordered_json to preserve the previous order // Bools are stored as 0 or 1 as in previous versions // Call length is rounded up to the nearest second as in previous versions - // Time stored in fractional seconds will omit trailing zeroes per json spec (1.20 -> 1.2) + // Time stored in fractional seconds will omit trailing zeroes per json spec (1.20 -> 1.2) + nlohmann::ordered_json json_data = { {"freq", int(call_info.freq)}, @@ -78,6 +83,7 @@ int create_call_json(Call_Data_t& call_info) { {"duplex", int(call_info.duplex)}, {"encrypted",int(call_info.encrypted)}, {"call_length", int(std::round(call_info.length))}, + {"call_length_ms", call_info.call_length_ms}, {"talkgroup", call_info.talkgroup}, {"talkgroup_tag", call_info.talkgroup_alpha_tag}, {"talkgroup_description", call_info.talkgroup_description}, @@ -278,30 +284,47 @@ Call_Data_t upload_call_worker(Call_Data_t call_info) { // static int rec_counter=0; Call_Data_t Call_Concluder::create_base_filename(Call *call, Call_Data_t call_info) { - char base_filename[255]; time_t work_start_time = call->get_start_time(); - std::stringstream base_path_stream; tm *ltm = localtime(&work_start_time); - // Found some good advice on Streams and Strings here: https://blog.sensecodons.com/2013/04/dont-let-stdstringstreamstrcstr-happen.html - base_path_stream << call->get_capture_dir() << "/" << call->get_short_name() << "/" << 1900 + ltm->tm_year << "/" << 1 + ltm->tm_mon << "/" << ltm->tm_mday; - std::string base_path_string = base_path_stream.str(); - boost::filesystem::create_directories(base_path_string); - int nchars; + boost::filesystem::path base_path = + boost::filesystem::path(call->get_capture_dir()) / + call->get_short_name() / + boost::lexical_cast(1900 + ltm->tm_year) / + boost::lexical_cast(1 + ltm->tm_mon) / + boost::lexical_cast(ltm->tm_mday); + + boost::filesystem::create_directories(base_path); + // Seconds.milliseconds from call start_time_ms + const long long start_ms = static_cast(call->get_start_time_ms()); + const long long sec = start_ms / 1000; + const int milli = static_cast(start_ms % 1000); + + std::ostringstream ts; + ts << sec << '.' << std::setw(3) << std::setfill('0') << milli; + + char base_filename[255]; if (call->get_tdma_slot() == -1) { - nchars = snprintf(base_filename, 255, "%s/%ld-%ld_%.0f", base_path_string.c_str(), call->get_talkgroup(), work_start_time, call->get_freq()); + std::snprintf(base_filename, sizeof(base_filename), + "%s/%ld-%s_%.0f", + base_path.string().c_str(), + call->get_talkgroup(), + ts.str().c_str(), + call->get_freq()); } else { - // this is for the case when it is a P25P2 TDMA or DMR recorder and 2 wav files are created, the slot is needed to keep them separate. - nchars = snprintf(base_filename, 255, "%s/%ld-%ld_%.0f.%d", base_path_string.c_str(), call->get_talkgroup(), work_start_time, call->get_freq(), call->get_tdma_slot()); - } - if (nchars >= 255) { - BOOST_LOG_TRIVIAL(error) << "Call: Path longer than 255 charecters"; + std::snprintf(base_filename, sizeof(base_filename), + "%s/%ld-%s_%.0f.%d", + base_path.string().c_str(), + call->get_talkgroup(), + ts.str().c_str(), + call->get_freq(), + call->get_tdma_slot()); } - snprintf(call_info.filename, 300, "%s-call_%lu.wav", base_filename, call->get_call_num()); - snprintf(call_info.status_filename, 300, "%s-call_%lu.json", base_filename, call->get_call_num()); - snprintf(call_info.converted, 300, "%s-call_%lu.m4a", base_filename, call->get_call_num()); + std::snprintf(call_info.filename, sizeof(call_info.filename), "%s-call_%lu.wav", base_filename, call->get_call_num()); + std::snprintf(call_info.status_filename, sizeof(call_info.status_filename), "%s-call_%lu.json", base_filename, call->get_call_num()); + std::snprintf(call_info.converted, sizeof(call_info.converted), "%s-call_%lu.m4a", base_filename, call->get_call_num()); return call_info; } @@ -309,6 +332,8 @@ Call_Data_t Call_Concluder::create_base_filename(Call *call, Call_Data_t call_in Call_Data_t Call_Concluder::create_call_data(Call *call, System *sys, Config config) { Call_Data_t call_info; double total_length = 0; + std::int64_t audio_sum_ms = 0; + bool have_first = false; call_info = create_base_filename(call, call_info); @@ -368,63 +393,88 @@ Call_Data_t Call_Concluder::create_call_data(Call *call, System *sys, Config con // loop through the transmission list, pull in things to fill in totals for call_info // Using a for loop with iterator - for (std::vector::iterator it = call_info.transmission_list.begin(); it != call_info.transmission_list.end();) { - Transmission t = *it; - - if (t.length < sys->get_min_tx_duration()) { - if (!call_info.transmission_archive) { - std::string loghdr = log_header( call_info.short_name, call_info.call_num, call_info.talkgroup_display , call_info.freq); - BOOST_LOG_TRIVIAL(info) << loghdr << "Removing transmission less than " << sys->get_min_tx_duration() << " seconds. Actual length: " << t.length << "."; - call_info.min_transmissions_removed++; - - if (checkIfFile(t.filename)) { - remove(t.filename); - } - } - - it = call_info.transmission_list.erase(it); - continue; - } - - std::string tag = sys->find_unit_tag(t.source); - std::string display_tag = ""; - if (tag != "") { - display_tag = " (\033[0;34m" + tag + "\033[0m)"; - } - - std::stringstream transmission_info; - std::string loghdr = log_header( call_info.short_name, call_info.call_num, call_info.talkgroup_display , call_info.freq); - transmission_info << loghdr << "- Transmission src: " << t.source << display_tag << " pos: " << format_time(total_length) << " length: " << format_time(t.length); - - if (t.error_count < 1) { - BOOST_LOG_TRIVIAL(info) << transmission_info.str(); - } else { - BOOST_LOG_TRIVIAL(info) << transmission_info.str() << "\033[0;31m errors: " << t.error_count << " spikes: " << t.spike_count << "\033[0m"; - } - - if (it == call_info.transmission_list.begin()) { - call_info.start_time = t.start_time; - call_info.start_time_ms = t.start_time_ms; - } - - if (std::next(it) == call_info.transmission_list.end()) { - call_info.stop_time = t.stop_time; - call_info.stop_time_ms = t.stop_time_ms; - } - - Call_Source call_source = {t.source, t.start_time, total_length, false, "", tag}; - Call_Error call_error = {t.start_time, total_length, t.length, t.error_count, t.spike_count}; - call_info.error_count = call_info.error_count + t.error_count; - call_info.spike_count = call_info.spike_count + t.spike_count; - call_info.transmission_source_list.push_back(call_source); - call_info.transmission_error_list.push_back(call_error); - - total_length = total_length + t.length; - it++; + for (std::vector::iterator it = call_info.transmission_list.begin(); + it != call_info.transmission_list.end();) { + + Transmission t = *it; + + if (t.length < sys->get_min_tx_duration()) { + if (!call_info.transmission_archive) { + std::string loghdr = log_header(call_info.short_name, call_info.call_num, + call_info.talkgroup_display, call_info.freq); + BOOST_LOG_TRIVIAL(info) << loghdr << "Removing transmission less than " + << sys->get_min_tx_duration() + << " seconds. Actual length: " << t.length << "."; + call_info.min_transmissions_removed++; + + if (checkIfFile(t.filename)) { + remove(t.filename); + } + } + it = call_info.transmission_list.erase(it); + continue; + } + + // compute exact duration from ms stamps (matches playable audio) + const std::int64_t seg_ms = std::max(0, t.stop_time_ms - t.start_time_ms); + const double seg_len_s = seg_ms / 1000.0; + + std::string tag = sys->find_unit_tag(t.source); + std::string display_tag = tag.empty() ? "" : " (\033[0;34m" + tag + "\033[0m)"; + + std::stringstream transmission_info; + std::string loghdr = log_header(call_info.short_name, call_info.call_num, + call_info.talkgroup_display, call_info.freq); + transmission_info << loghdr << "- Transmission src: " << t.source << display_tag + << " pos: " << format_time(total_length) + << " length: " << format_time(t.length); + if (t.error_count < 1) { + BOOST_LOG_TRIVIAL(info) << transmission_info.str(); + } else { + BOOST_LOG_TRIVIAL(info) << transmission_info.str() + << "\033[0;31m errors: " << t.error_count + << " spikes: " << t.spike_count << "\033[0m"; + } + + if (it == call_info.transmission_list.begin()) { + call_info.start_time = t.start_time; + call_info.start_time_ms = t.start_time_ms; + have_first = true; + } + + if (std::next(it) == call_info.transmission_list.end()) { + call_info.stop_time = t.stop_time; + call_info.stop_time_ms = t.stop_time_ms; + } + + Call_Source call_source = { t.source, t.start_time, total_length, false, "", tag }; + Call_Error call_error = { t.start_time, total_length, seg_len_s, t.error_count, t.spike_count }; + call_info.transmission_source_list.push_back(call_source); + call_info.transmission_error_list.push_back(call_error); + + call_info.error_count += t.error_count; + call_info.spike_count += t.spike_count; + + total_length += seg_len_s; + audio_sum_ms += seg_ms; + + ++it; + } + + if (have_first) { + call_info.stop_time_ms = call_info.start_time_ms + audio_sum_ms; + call_info.stop_time = (time_t)(call_info.stop_time_ms / 1000); + call_info.length = audio_sum_ms / 1000.0; + call_info.call_length_ms = audio_sum_ms; + } else { + call_info.length = 0.0; + call_info.start_time_ms = 0; + call_info.stop_time_ms = 0; + call_info.start_time = 0; + call_info.stop_time = 0; + call_info.call_length_ms = 0; } - call_info.length = total_length; - return call_info; } diff --git a/trunk-recorder/global_structs.h b/trunk-recorder/global_structs.h index 067b01883..40b0a7279 100644 --- a/trunk-recorder/global_structs.h +++ b/trunk-recorder/global_structs.h @@ -1,5 +1,6 @@ #ifndef GLOBAL_STRUCTS_H #define GLOBAL_STRUCTS_H +#include #include #include #include @@ -132,6 +133,7 @@ struct Call_Data_t { int tdma_slot; double length; + std::int64_t call_length_ms; bool phase2_tdma; std::vector transmission_source_list; diff --git a/trunk-recorder/gr_blocks/transmission_sink.cc b/trunk-recorder/gr_blocks/transmission_sink.cc index 4618c1f01..c663834c1 100644 --- a/trunk-recorder/gr_blocks/transmission_sink.cc +++ b/trunk-recorder/gr_blocks/transmission_sink.cc @@ -79,24 +79,56 @@ transmission_sink::transmission_sink(int n_channels, unsigned int sample_rate, i } void transmission_sink::create_filename() { - time_t work_start_time = d_start_time; - std::stringstream temp_path_stream; - // Found some good advice on Streams and Strings here: https://blog.sensecodons.com/2013/04/dont-let-stdstringstreamstrcstr-happen.html - - temp_path_stream << d_current_call_temp_dir << "/" << d_current_call_short_name; - std::string temp_path_string = temp_path_stream.str(); - boost::filesystem::create_directories(temp_path_string); + using std::ostringstream; + using std::setw; + using std::setfill; + + // / + boost::filesystem::path dir = + boost::filesystem::path(d_current_call_temp_dir) / d_current_call_short_name; + + boost::system::error_code ec; + boost::filesystem::create_directories(dir, ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << "create_directories failed for " << dir.string() + << " : " << ec.message(); + } - int nchars; + // Seconds.milliseconds from d_start_time_ms + const long long start_ms = static_cast(d_start_time_ms); + const long long sec = start_ms / 1000; + const int milli = static_cast(start_ms % 1000); + + // Normalize frequency to integer + const long long freq_i = static_cast(std::llround(d_current_call_freq)); + + auto make_stem = [&](int suffix) { + ostringstream ts; + ts << sec << '.' << setw(3) << setfill('0') << milli; // e.g. 1718145678.042 + + ostringstream oss; + oss << d_current_call_talkgroup << "-" << ts.str() << "_" << freq_i; + if (d_slot != -1) oss << "." << d_slot; + if (suffix > 0) oss << "-" << suffix; // collision suffix + oss << ".wav"; + return oss.str(); + }; + + boost::filesystem::path candidate = dir / make_stem(0); + for (int i = 1; boost::filesystem::exists(candidate) && i <= 99; ++i) { + candidate = dir / make_stem(i); + } - if (d_slot == -1) { - nchars = snprintf(current_filename, 255, "%s/%ld-%ld_%.0f.wav", temp_path_string.c_str(), d_current_call_talkgroup, work_start_time, d_current_call_freq); + const std::string cand_str = candidate.string(); + const size_t cap = sizeof(current_filename); + if (cand_str.size() >= cap) { + BOOST_LOG_TRIVIAL(error) + << "Call: Path longer than buffer (" << cap + << ") — truncating filename: " << cand_str; + std::snprintf(current_filename, cap, "%.*s", + static_cast(cap - 1), cand_str.c_str()); } else { - // this is for the case when it is a P25P2 TDMA or DMR recorder and 2 wav files are created, the slot is needed to keep them separate. - nchars = snprintf(current_filename, 255, "%s/%ld-%ld_%.0f.%d.wav", temp_path_string.c_str(), d_current_call_talkgroup, work_start_time, d_current_call_freq, d_slot); - } - if (nchars >= 255) { - BOOST_LOG_TRIVIAL(error) << "Call: Path longer than 255 charecters"; + std::snprintf(current_filename, cap, "%s", cand_str.c_str()); } } From 92d1200e1d5035b2b08a6524357752ac0047216c Mon Sep 17 00:00:00 2001 From: Thegreatcodeholio Date: Sun, 5 Oct 2025 19:10:55 -0400 Subject: [PATCH 5/7] rewrite call_concluder.cc Call_Data_t Call_Concluder::create_call_data should now be more efficient and have more accurate lengths for ms and s both. add get_start_time_ms to call_impl so we can get the start_time_ms in call_concluder.cc filename is now seconds.ms --- trunk-recorder/call.h | 2 + .../call_concluder/call_concluder.cc | 267 +++++++++--------- trunk-recorder/call_impl.cc | 17 +- trunk-recorder/call_impl.h | 1 + trunk-recorder/gr_blocks/transmission_sink.cc | 10 +- trunk-recorder/gr_blocks/transmission_sink.h | 2 + 6 files changed, 168 insertions(+), 131 deletions(-) diff --git a/trunk-recorder/call.h b/trunk-recorder/call.h index 2b112e097..239fd0bb1 100644 --- a/trunk-recorder/call.h +++ b/trunk-recorder/call.h @@ -9,6 +9,7 @@ #include #include #include +#include class Recorder; class System; @@ -66,6 +67,7 @@ class Call { virtual void set_is_analog(bool a) = 0; virtual const char *get_xor_mask() = 0; virtual time_t get_start_time() = 0; + virtual std::int64_t get_start_time_ms() = 0; virtual bool is_conventional() = 0; virtual void set_encrypted(bool m) = 0; virtual bool get_encrypted() = 0; diff --git a/trunk-recorder/call_concluder/call_concluder.cc b/trunk-recorder/call_concluder/call_concluder.cc index b029db089..b088fe4c7 100644 --- a/trunk-recorder/call_concluder/call_concluder.cc +++ b/trunk-recorder/call_concluder/call_concluder.cc @@ -297,9 +297,8 @@ Call_Data_t Call_Concluder::create_base_filename(Call *call, Call_Data_t call_in boost::filesystem::create_directories(base_path); // Seconds.milliseconds from call start_time_ms - const long long start_ms = static_cast(call->get_start_time_ms()); - const long long sec = start_ms / 1000; - const int milli = static_cast(start_ms % 1000); + const long long sec = start_ms / 1000; + const int milli = static_cast(start_ms % 1000); std::ostringstream ts; ts << sec << '.' << std::setw(3) << std::setfill('0') << milli; @@ -331,55 +330,52 @@ Call_Data_t Call_Concluder::create_base_filename(Call *call, Call_Data_t call_in Call_Data_t Call_Concluder::create_call_data(Call *call, System *sys, Config config) { Call_Data_t call_info; - double total_length = 0; - std::int64_t audio_sum_ms = 0; - bool have_first = false; + // ---------- Static metadata ---------- call_info = create_base_filename(call, call_info); - call_info.status = INITIAL; - call_info.process_call_time = time(0); - call_info.retry_attempt = 0; - call_info.error_count = 0; - call_info.spike_count = 0; - call_info.freq = call->get_freq(); - call_info.freq_error = call->get_freq_error(); - call_info.signal = call->get_signal(); - call_info.noise = call->get_noise(); - call_info.recorder_num = call->get_recorder()->get_num(); - call_info.source_num = call->get_recorder()->get_source()->get_num(); - call_info.encrypted = call->get_encrypted(); - call_info.emergency = call->get_emergency(); - call_info.priority = call->get_priority(); - call_info.mode = call->get_mode(); - call_info.duplex = call->get_duplex(); - call_info.tdma_slot = call->get_tdma_slot(); - call_info.phase2_tdma = call->get_phase2_tdma(); - call_info.transmission_list = call->get_transmissions(); - call_info.sys_num = sys->get_sys_num(); - call_info.short_name = sys->get_short_name(); - call_info.upload_script = sys->get_upload_script(); - call_info.audio_archive = sys->get_audio_archive(); - call_info.transmission_archive = sys->get_transmission_archive(); - call_info.call_log = sys->get_call_log(); - call_info.call_num = call->get_call_num(); - call_info.compress_wav = sys->get_compress_wav(); - call_info.talkgroup = call->get_talkgroup(); - call_info.talkgroup_display = call->get_talkgroup_display(); - call_info.patched_talkgroups = sys->get_talkgroup_patch(call_info.talkgroup); + call_info.status = INITIAL; + call_info.process_call_time = time(0); + call_info.retry_attempt = 0; + call_info.error_count = 0; + call_info.spike_count = 0; + call_info.freq = call->get_freq(); + call_info.freq_error = call->get_freq_error(); + call_info.signal = call->get_signal(); + call_info.noise = call->get_noise(); + call_info.recorder_num = call->get_recorder()->get_num(); + call_info.source_num = call->get_recorder()->get_source()->get_num(); + call_info.encrypted = call->get_encrypted(); + call_info.emergency = call->get_emergency(); + call_info.priority = call->get_priority(); + call_info.mode = call->get_mode(); + call_info.duplex = call->get_duplex(); + call_info.tdma_slot = call->get_tdma_slot(); + call_info.phase2_tdma = call->get_phase2_tdma(); + call_info.transmission_list = call->get_transmissions(); + call_info.sys_num = sys->get_sys_num(); + call_info.short_name = sys->get_short_name(); + call_info.upload_script = sys->get_upload_script(); + call_info.audio_archive = sys->get_audio_archive(); + call_info.transmission_archive= sys->get_transmission_archive(); + call_info.call_log = sys->get_call_log(); + call_info.call_num = call->get_call_num(); + call_info.compress_wav = sys->get_compress_wav(); + call_info.talkgroup = call->get_talkgroup(); + call_info.talkgroup_display = call->get_talkgroup_display(); + call_info.patched_talkgroups = sys->get_talkgroup_patch(call_info.talkgroup); call_info.min_transmissions_removed = 0; - Talkgroup *tg = sys->find_talkgroup(call->get_talkgroup()); - if (tg != NULL) { - call_info.talkgroup_tag = tg->tag; - call_info.talkgroup_alpha_tag = tg->alpha_tag; - call_info.talkgroup_description = tg->description; - call_info.talkgroup_group = tg->group; + if (Talkgroup *tg = sys->find_talkgroup(call->get_talkgroup())) { + call_info.talkgroup_tag = tg->tag; + call_info.talkgroup_alpha_tag = tg->alpha_tag; + call_info.talkgroup_description = tg->description; + call_info.talkgroup_group = tg->group; } else { - call_info.talkgroup_tag = ""; - call_info.talkgroup_alpha_tag = ""; - call_info.talkgroup_description = ""; - call_info.talkgroup_group = ""; + call_info.talkgroup_tag.clear(); + call_info.talkgroup_alpha_tag.clear(); + call_info.talkgroup_description.clear(); + call_info.talkgroup_group.clear(); } if (call->get_is_analog()) { @@ -390,94 +386,113 @@ Call_Data_t Call_Concluder::create_call_data(Call *call, System *sys, Config con call_info.audio_type = "digital"; } + // ---------- Aggregate over transmissions (ms-accurate & efficient) ---------- + const double min_tx_s = sys->get_min_tx_duration(); // seconds + + // Reserve to avoid reallocs during push_back + call_info.transmission_source_list.reserve(call_info.transmission_list.size()); + call_info.transmission_error_list.reserve(call_info.transmission_list.size()); + + double playable_pos_s = 0.0; // "pos" field is playable timeline + std::int64_t audio_sum_ms = 0; // sum of segment durations (playable) + bool have_any = false; + std::int64_t min_start_ms = 0; + std::int64_t max_stop_ms = 0; + + for (auto it = call_info.transmission_list.begin(); + it != call_info.transmission_list.end(); /* manual inc */) { + + const Transmission &t = *it; + + // Canonical length from millisecond stamps + const std::int64_t seg_ms = std::max(0, t.stop_time_ms - t.start_time_ms); + const double seg_len_s = seg_ms / 1000.0; + + // Filter short segments using canonical length + if (seg_len_s < min_tx_s) { + if (!call_info.transmission_archive) { + std::string loghdr = log_header(call_info.short_name, call_info.call_num, + call_info.talkgroup_display, call_info.freq); + BOOST_LOG_TRIVIAL(info) << loghdr << "Removing transmission less than " + << min_tx_s + << " seconds. Actual length: " << seg_len_s << "."; + call_info.min_transmissions_removed++; + if (checkIfFile(t.filename)) { + remove(t.filename); + } + } + it = call_info.transmission_list.erase(it); + continue; + } - // loop through the transmission list, pull in things to fill in totals for call_info - // Using a for loop with iterator - for (std::vector::iterator it = call_info.transmission_list.begin(); - it != call_info.transmission_list.end();) { - - Transmission t = *it; - - if (t.length < sys->get_min_tx_duration()) { - if (!call_info.transmission_archive) { - std::string loghdr = log_header(call_info.short_name, call_info.call_num, - call_info.talkgroup_display, call_info.freq); - BOOST_LOG_TRIVIAL(info) << loghdr << "Removing transmission less than " - << sys->get_min_tx_duration() - << " seconds. Actual length: " << t.length << "."; - call_info.min_transmissions_removed++; - - if (checkIfFile(t.filename)) { - remove(t.filename); - } - } - it = call_info.transmission_list.erase(it); - continue; - } - - // compute exact duration from ms stamps (matches playable audio) - const std::int64_t seg_ms = std::max(0, t.stop_time_ms - t.start_time_ms); - const double seg_len_s = seg_ms / 1000.0; - - std::string tag = sys->find_unit_tag(t.source); - std::string display_tag = tag.empty() ? "" : " (\033[0;34m" + tag + "\033[0m)"; - - std::stringstream transmission_info; - std::string loghdr = log_header(call_info.short_name, call_info.call_num, - call_info.talkgroup_display, call_info.freq); - transmission_info << loghdr << "- Transmission src: " << t.source << display_tag - << " pos: " << format_time(total_length) - << " length: " << format_time(t.length); - if (t.error_count < 1) { - BOOST_LOG_TRIVIAL(info) << transmission_info.str(); - } else { - BOOST_LOG_TRIVIAL(info) << transmission_info.str() - << "\033[0;31m errors: " << t.error_count - << " spikes: " << t.spike_count << "\033[0m"; - } - - if (it == call_info.transmission_list.begin()) { - call_info.start_time = t.start_time; - call_info.start_time_ms = t.start_time_ms; - have_first = true; - } - - if (std::next(it) == call_info.transmission_list.end()) { - call_info.stop_time = t.stop_time; - call_info.stop_time_ms = t.stop_time_ms; - } - - Call_Source call_source = { t.source, t.start_time, total_length, false, "", tag }; - Call_Error call_error = { t.start_time, total_length, seg_len_s, t.error_count, t.spike_count }; - call_info.transmission_source_list.push_back(call_source); - call_info.transmission_error_list.push_back(call_error); - - call_info.error_count += t.error_count; - call_info.spike_count += t.spike_count; - - total_length += seg_len_s; - audio_sum_ms += seg_ms; - - ++it; - } - - if (have_first) { - call_info.stop_time_ms = call_info.start_time_ms + audio_sum_ms; - call_info.stop_time = (time_t)(call_info.stop_time_ms / 1000); - call_info.length = audio_sum_ms / 1000.0; - call_info.call_length_ms = audio_sum_ms; + // Track true wall-clock window [min start, max stop] + if (!have_any) { + have_any = true; + min_start_ms = t.start_time_ms; + max_stop_ms = t.stop_time_ms; + } else { + if (t.start_time_ms < min_start_ms) min_start_ms = t.start_time_ms; + if (t.stop_time_ms > max_stop_ms) max_stop_ms = t.stop_time_ms; + } + + // Unit tag (once per segment) + std::string tag = sys->find_unit_tag(t.source); + std::string display_tag = tag.empty() ? "" : " (\033[0;34m" + tag + "\033[0m)"; + + // Log with canonical length and playable position + { + std::stringstream transmission_info; + std::string loghdr = log_header(call_info.short_name, call_info.call_num, + call_info.talkgroup_display, call_info.freq); + transmission_info << loghdr << "- Transmission src: " << t.source << display_tag + << " pos: " << format_time(playable_pos_s) + << " length: " << format_time(seg_len_s); + if (t.error_count < 1) { + BOOST_LOG_TRIVIAL(info) << transmission_info.str(); + } else { + BOOST_LOG_TRIVIAL(info) << transmission_info.str() + << "\033[0;31m errors: " << t.error_count + << " spikes: " << t.spike_count << "\033[0m"; + } + } + + // Build src/error lists aligned to playable timeline + Call_Source call_source = { t.source, t.start_time, playable_pos_s, false, "", tag }; + Call_Error call_error = { t.start_time, playable_pos_s, seg_len_s, + t.error_count, t.spike_count }; + call_info.transmission_source_list.push_back(call_source); + call_info.transmission_error_list.push_back(call_error); + + call_info.error_count += t.error_count; + call_info.spike_count += t.spike_count; + + playable_pos_s += seg_len_s; + audio_sum_ms += seg_ms; + + ++it; + } + + // ---------- Finalize aggregate timing ---------- + if (have_any) { + call_info.start_time_ms = min_start_ms; // earliest start (ms) + call_info.stop_time_ms = max_stop_ms; // latest stop (ms) + call_info.start_time = (time_t)(min_start_ms / 1000); + call_info.stop_time = (time_t)(max_stop_ms / 1000); + call_info.call_length_ms = audio_sum_ms; // playable audio only + call_info.length = audio_sum_ms / 1000.0; // seconds } else { - call_info.length = 0.0; - call_info.start_time_ms = 0; - call_info.stop_time_ms = 0; - call_info.start_time = 0; - call_info.stop_time = 0; + call_info.length = 0.0; + call_info.start_time_ms = 0; + call_info.stop_time_ms = 0; + call_info.start_time = 0; + call_info.stop_time = 0; call_info.call_length_ms = 0; } return call_info; } + void Call_Concluder::conclude_call(Call *call, System *sys, Config config) { Call_Data_t call_info = create_call_data(call, sys, config); diff --git a/trunk-recorder/call_impl.cc b/trunk-recorder/call_impl.cc index a126f6cc4..e2a9ba3a5 100644 --- a/trunk-recorder/call_impl.cc +++ b/trunk-recorder/call_impl.cc @@ -228,6 +228,21 @@ double Call_impl::get_current_length() { } } +std::int64_t Call_impl::get_start_time_ms() { + // Prefer the earliest transmission start (true playable start) + if (!transmission_list.empty()) { + std::int64_t best = 0; + for (const auto& t : transmission_list) { + if (t.start_time_ms > 0 && (best == 0 || t.start_time_ms < best)) { + best = t.start_time_ms; + } + } + if (best > 0) return best; + } + // Fallback: call creation time in ms + return start_time_ms; +} + System *Call_impl::get_system() { return sys; } @@ -505,7 +520,7 @@ boost::property_tree::ptree Call_impl::get_stats() { call_node.put("duplex", this->get_duplex()); call_node.put("startTime", this->get_start_time()); call_node.put("stopTime", this->get_stop_time()); - call_node.put("startTimeMs", this->start_time_ms); + call_node.put("startTimeMs", this->get_start_time_ms()); call_node.put("stopTimeMs", this->stop_time_ms); call_node.put("srcId", this->get_current_source_id()); diff --git a/trunk-recorder/call_impl.h b/trunk-recorder/call_impl.h index 42371e461..fcf304bc9 100644 --- a/trunk-recorder/call_impl.h +++ b/trunk-recorder/call_impl.h @@ -68,6 +68,7 @@ class Call_impl : public Call { void set_is_analog(bool a); const char *get_xor_mask(); virtual time_t get_start_time() { return start_time; } + virtual std::int64_t get_start_time_ms(); bool is_conventional() { return false; } void set_encrypted(bool m); bool get_encrypted(); diff --git a/trunk-recorder/gr_blocks/transmission_sink.cc b/trunk-recorder/gr_blocks/transmission_sink.cc index c663834c1..44406c6b5 100644 --- a/trunk-recorder/gr_blocks/transmission_sink.cc +++ b/trunk-recorder/gr_blocks/transmission_sink.cc @@ -262,7 +262,10 @@ void transmission_sink::end_transmission() { BOOST_LOG_TRIVIAL(error) << "Ending transmission, sample_count is greater than 0 but d_fp is null" << std::endl; } - const int64_t dur_ms = (int64_t)std::llround(1000.0 * (d_nchans > 0 ? (double)d_sample_count / (d_sample_rate * (double)d_nchans) : 0.0)); + const std::int64_t dur_ms = (d_nchans > 0) + ? (std::int64_t)std::llround(1000.0 * + (double)d_sample_count / ((double)d_sample_rate * (double)d_nchans)) + : 0; // Assign canonical stop time from sample count d_stop_time_ms = d_start_time_ms + dur_ms; @@ -543,9 +546,8 @@ int transmission_sink::dowork(int noutput_items, gr_vector_const_void_star &inpu auto now_sys = std::chrono::system_clock::now(); d_start_time_ms = std::chrono::duration_cast( - now_sys.time_since_epoch() - ).count(); - d_start_time = std::chrono::system_clock::to_time_t(now_sys); + now_sys.time_since_epoch()).count(); + d_start_time = static_cast(d_start_time_ms / 1000); // create a new filename, based on the current time and source. create_filename(); diff --git a/trunk-recorder/gr_blocks/transmission_sink.h b/trunk-recorder/gr_blocks/transmission_sink.h index 00c1c3524..cdea27ea4 100644 --- a/trunk-recorder/gr_blocks/transmission_sink.h +++ b/trunk-recorder/gr_blocks/transmission_sink.h @@ -136,6 +136,8 @@ class BLOCKS_API transmission_sink : virtual public sync_block { unsigned int sample_rate(); double total_length_in_seconds(); double length_in_seconds(); + std::int64_t get_start_time_ms() const { return d_start_time_ms; } + std::int64_t get_stop_time_ms() const { return d_stop_time_ms; } Call_Source *get_source_list(); int get_source_count(); virtual int work(int noutput_items, From 571b137afd43b5570e2c4158e544224175dddfd5 Mon Sep 17 00:00:00 2001 From: Thegreatcodeholio Date: Sun, 5 Oct 2025 19:15:16 -0400 Subject: [PATCH 6/7] forgot a change, derive all seconds from the new ms stamps --- trunk-recorder/call_concluder/call_concluder.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/trunk-recorder/call_concluder/call_concluder.cc b/trunk-recorder/call_concluder/call_concluder.cc index b088fe4c7..5c8d7f3fa 100644 --- a/trunk-recorder/call_concluder/call_concluder.cc +++ b/trunk-recorder/call_concluder/call_concluder.cc @@ -284,7 +284,8 @@ Call_Data_t upload_call_worker(Call_Data_t call_info) { // static int rec_counter=0; Call_Data_t Call_Concluder::create_base_filename(Call *call, Call_Data_t call_info) { - time_t work_start_time = call->get_start_time(); + const std::int64_t start_ms = call->get_start_time_ms(); + time_t work_start_time = static_cast(start_ms / 1000); tm *ltm = localtime(&work_start_time); boost::filesystem::path base_path = From e9636de746c9edbd6c44a0a9d37aac2d2c4cd21b Mon Sep 17 00:00:00 2001 From: Thegreatcodeholio Date: Fri, 21 Nov 2025 16:10:15 -0500 Subject: [PATCH 7/7] fix retry backoff ^ is bitwise XOR, not exponentiation. --- trunk-recorder/call_concluder/call_concluder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trunk-recorder/call_concluder/call_concluder.cc b/trunk-recorder/call_concluder/call_concluder.cc index 5c8d7f3fa..d880658be 100644 --- a/trunk-recorder/call_concluder/call_concluder.cc +++ b/trunk-recorder/call_concluder/call_concluder.cc @@ -537,7 +537,7 @@ void Call_Concluder::manage_call_data_workers() { BOOST_LOG_TRIVIAL(error) << "[" << call_info.short_name << "]\t\033[0;34m" << call_info.call_num << "C\033[0m Failed to conclude call - TG: " << call_info.talkgroup_display << "\t" << std::put_time(std::localtime(&start_time), "%c %Z"); } else { long jitter = rand() % 10; - long backoff = (2 ^ call_info.retry_attempt * 60) + jitter; + long backoff = ((1 << call_info.retry_attempt) * 60) + jitter; call_info.process_call_time = time(0) + backoff; retry_call_list.push_back(call_info); BOOST_LOG_TRIVIAL(error) << loghdr << std::put_time(std::localtime(&start_time), "%c %Z") << " retry attempt " << call_info.retry_attempt << " in " << backoff << "s\t retry queue: " << retry_call_list.size() << " calls";