// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. //************************ Includes ***************************** #ifdef _WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #endif #define LLD(val) ((int64_t)(val)) #define STS_TO_MS(ts) (LLD((ts) / 1000000)) // System TS convertion to milliseconds static bool g_skip_delay_off_color_validation = false; static int32_t g_depth_delay_off_color_usec = 0; static uint8_t g_device_index = K4A_DEVICE_DEFAULT; static k4a_wired_sync_mode_t g_wired_sync_mode = K4A_WIRED_SYNC_MODE_STANDALONE; static int g_capture_count = 50; static bool g_synchronized_images_only = false; static bool g_no_startup_flush = false; static uint32_t g_subordinate_delay_off_master_usec = 0; static bool g_manual_exposure = true; static uint32_t g_exposure_setting = 31000; // will round up to nearest value static bool g_power_line_50_hz = false; using ::testing::ValuesIn; typedef struct _sys_pts_time_t { uint64_t pts; uint64_t system; } sys_pts_time_t; static std::mutex g_lock_mutex; static std::deque g_time_c; // Color image copy of data static std::deque g_time_i; // Ir image copy of data struct latency_parameters { int test_number; const char *test_name; k4a_fps_t fps; k4a_image_format_t color_format; k4a_color_resolution_t color_resolution; k4a_depth_mode_t depth_mode; friend std::ostream &operator<<(std::ostream &os, const latency_parameters &obj) { return os << "test index: (" << obj.test_name << ") " << (int)obj.test_number; } }; struct thread_data { volatile bool save_samples; volatile bool exit; volatile uint32_t imu_samples; k4a_device_t device; }; class latency_perf : public ::testing::Test, public ::testing::WithParamInterface { public: virtual void SetUp() { ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_open(g_device_index, &m_device)) << "Couldn't open device\n"; ASSERT_NE(m_device, nullptr); EXPECT_NE((FILE *)NULL, (m_file_handle = fopen("latency_testResults.csv", "a"))); } virtual void TearDown() { if (m_device != nullptr) { k4a_device_close(m_device); m_device = nullptr; } if (m_file_handle) { fclose(m_file_handle); } } void print_and_log(const char *message, const char *mode, int64_t ave, int64_t min, int64_t max); void process_image(k4a_capture_t capture, uint64_t current_system_ts, bool process_color, bool *image_first_pass, std::deque *system_latency, std::deque *system_latency_from_pts, uint64_t *system_ts_last, uint64_t *system_ts_from_pts_last); k4a_device_t m_device = nullptr; FILE *m_file_handle; }; static const char *get_string_from_color_format(k4a_image_format_t format) { switch (format) { case K4A_IMAGE_FORMAT_COLOR_NV12: return "K4A_IMAGE_FORMAT_COLOR_NV12"; break; case K4A_IMAGE_FORMAT_COLOR_YUY2: return "K4A_IMAGE_FORMAT_COLOR_YUY2"; break; case K4A_IMAGE_FORMAT_COLOR_MJPG: return "K4A_IMAGE_FORMAT_COLOR_MJPG"; break; case K4A_IMAGE_FORMAT_COLOR_BGRA32: return "K4A_IMAGE_FORMAT_COLOR_BGRA32"; break; case K4A_IMAGE_FORMAT_DEPTH16: return "K4A_IMAGE_FORMAT_DEPTH16"; break; case K4A_IMAGE_FORMAT_IR16: return "K4A_IMAGE_FORMAT_IR16"; break; case K4A_IMAGE_FORMAT_CUSTOM8: return "K4A_IMAGE_FORMAT_CUSTOM8"; break; case K4A_IMAGE_FORMAT_CUSTOM16: return "K4A_IMAGE_FORMAT_CUSTOM16"; break; case K4A_IMAGE_FORMAT_CUSTOM: return "K4A_IMAGE_FORMAT_CUSTOM"; break; } assert(0); return "K4A_IMAGE_FORMAT_UNKNOWN"; } static const char *get_string_from_color_resolution(k4a_color_resolution_t resolution) { switch (resolution) { case K4A_COLOR_RESOLUTION_OFF: return "OFF"; break; case K4A_COLOR_RESOLUTION_720P: return "1280 * 720 16:9"; break; case K4A_COLOR_RESOLUTION_1080P: return "1920 * 1080 16:9"; break; case K4A_COLOR_RESOLUTION_1440P: return "2560 * 1440 16:9"; break; case K4A_COLOR_RESOLUTION_1536P: return "2048 * 1536 4:3"; break; case K4A_COLOR_RESOLUTION_2160P: return "3840 * 2160 16:9"; break; case K4A_COLOR_RESOLUTION_3072P: return "4096 * 3072 4:3"; break; } assert(0); return "Unknown resolution"; } static const char *get_string_from_depth_mode(k4a_depth_mode_t mode) { switch (mode) { case K4A_DEPTH_MODE_OFF: return "K4A_DEPTH_MODE_OFF"; break; case K4A_DEPTH_MODE_NFOV_2X2BINNED: return "K4A_DEPTH_MODE_NFOV_2X2BINNED"; break; case K4A_DEPTH_MODE_NFOV_UNBINNED: return "K4A_DEPTH_MODE_NFOV_UNBINNED"; break; case K4A_DEPTH_MODE_WFOV_2X2BINNED: return "K4A_DEPTH_MODE_WFOV_2X2BINNED"; break; case K4A_DEPTH_MODE_WFOV_UNBINNED: return "K4A_DEPTH_MODE_WFOV_UNBINNED"; break; case K4A_DEPTH_MODE_PASSIVE_IR: return "K4A_DEPTH_MODE_PASSIVE_IR"; break; } assert(0); return "Unknown Depth"; } static bool get_system_time(uint64_t *time_nsec) { k4a_result_t result = K4A_RESULT_SUCCEEDED; #ifdef _WIN32 LARGE_INTEGER qpc = { 0 }; static LARGE_INTEGER freq = { 0 }; result = K4A_RESULT_FROM_BOOL(QueryPerformanceCounter(&qpc) != 0); if (K4A_FAILED(result)) { return false; } if (freq.QuadPart == 0) { result = K4A_RESULT_FROM_BOOL(QueryPerformanceFrequency(&freq) != 0); if (K4A_FAILED(result)) { return false; } } // Calculate seconds in such a way we minimize overflow. // Rollover happens, for a 1MHz Freq, when qpc.QuadPart > 0x003F FFFF FFFF FFFF; ~571 Years after boot. *time_nsec = qpc.QuadPart / freq.QuadPart * 1000000000; *time_nsec += qpc.QuadPart % freq.QuadPart * 1000000000 / freq.QuadPart; #else struct timespec ts_time; result = K4A_RESULT_FROM_BOOL(clock_gettime(CLOCK_MONOTONIC, &ts_time) == 0); if (K4A_FAILED(result)) { return false; } // Rollover happens about ~136 years after boot. *time_nsec = (uint64_t)ts_time.tv_sec * 1000000000 + (uint64_t)ts_time.tv_nsec; #endif return true; } static int _latency_imu_thread(void *param) { struct thread_data *data = (struct thread_data *)param; k4a_result_t result; k4a_imu_sample_t imu; result = k4a_device_start_imu(data->device); if (K4A_FAILED(result)) { printf("Failed to start imu\n"); return result; } g_time_c.clear(); g_time_i.clear(); while (data->exit == false) { k4a_wait_result_t wresult = k4a_device_get_imu_sample(data->device, &imu, 10); if (wresult == K4A_WAIT_RESULT_FAILED) { printf("k4a_device_get_imu_sample failed\n"); result = K4A_RESULT_FAILED; break; } else if ((wresult == K4A_WAIT_RESULT_SUCCEEDED) && (data->save_samples)) { sys_pts_time_t time; time.pts = imu.acc_timestamp_usec; if (get_system_time(&time.system) == 0) { result = K4A_RESULT_FAILED; break; } // Save data to each of the queues g_lock_mutex.lock(); g_time_c.push_back(time); g_time_i.push_back(time); g_lock_mutex.unlock(); } }; k4a_device_stop_imu(data->device); return result; } // Drop the lock and sleep for Xms. This is to allow the queue to fill again. Return if we yield too long. #define YIELD_THREAD(lock_var, count, message) \ lock_var.unlock(); \ printf("Lock dropped while %s\n", message); \ ThreadAPI_Sleep(2); \ if (++count > 15) \ { \ EXPECT_LT(count, 15); \ return 0; \ } \ lock_var.lock(); static uint64_t lookup_system_ts(uint64_t pts_ts, bool color) { sys_pts_time_t last_time; uint64_t start_time_nsec; uint64_t current_time_nsec; int count = 0; bool found = false; std::deque *time_queue = &g_time_i; if (color) { time_queue = &g_time_c; } g_lock_mutex.lock(); // Record start time if (get_system_time(&start_time_nsec) == 0) { printf("ERROR getting system time\n"); EXPECT_TRUE(0); g_lock_mutex.unlock(); return 0; } int delay_count = 0; while (time_queue->empty()) { // Drop lock, wait, retake lock - Exit if taking too long YIELD_THREAD(g_lock_mutex, delay_count, "Initializing") } last_time = time_queue->front(); time_queue->pop_front(); while (!found) { int x; for (x = 0; !time_queue->empty(); x++) { last_time = time_queue->front(); if (pts_ts > last_time.pts) { // Hold onto last_time for 1 more loop last_time = time_queue->front(); time_queue->pop_front(); } else { // We just found the first system time that is beyond the one we are looking for. if ((pts_ts - last_time.pts) < (time_queue->front().pts - pts_ts)) { g_lock_mutex.unlock(); found = true; return last_time.system; } uint64_t ret_time = time_queue->front().system; g_lock_mutex.unlock(); found = true; return ret_time; } if (get_system_time(¤t_time_nsec) == 0) { printf("ERROR getting system time\n"); EXPECT_TRUE(0); g_lock_mutex.unlock(); return 0; } if (STS_TO_MS(current_time_nsec - start_time_nsec) > 1000) { printf("Count for break is %d\n", count); break; // Don't hold lock too long, run YIELD_THREAD below } } // Queue is drained or we held the lock too long. We need to let the IMU thread catch up. Drop lock, wait, // retake lock - Exit if taking too long YIELD_THREAD(g_lock_mutex, delay_count, "walking list."); // Update start time after the thread yield if (get_system_time(&start_time_nsec) == 0) { printf("ERROR getting system time\n"); EXPECT_TRUE(0); g_lock_mutex.unlock(); return 0; } } // Should not happen EXPECT_FALSE(1); g_lock_mutex.unlock(); return 0; } void latency_perf::print_and_log(const char *message, const char *mode, int64_t ave, int64_t min, int64_t max) { printf(" %30s %30s: Ave=%" PRId64 " min=%" PRId64 " max=%" PRId64 "\n", message, mode, ave, min, max); if (m_file_handle) { char buffer[1024]; snprintf(buffer, sizeof(buffer), "%s, %s (min ave max),%" PRId64 ",%" PRId64 ",%" PRId64 ",", mode, message, min, ave, max); fputs(buffer, m_file_handle); } } void latency_perf::process_image(k4a_capture_t capture, uint64_t current_system_ts, bool process_color, bool *image_first_pass, std::deque *system_latency, std::deque *system_latency_from_pts, uint64_t *system_ts_last, uint64_t *system_ts_from_pts_last) { k4a_image_t image; if (process_color) { image = k4a_capture_get_color_image(capture); } else { image = k4a_capture_get_ir_image(capture); } if (image) { uint64_t system_ts = k4a_image_get_system_timestamp_nsec(image); uint64_t system_ts_from_pts = lookup_system_ts(k4a_image_get_device_timestamp_usec(image), process_color); // Time from center of exposure until given to us from the SDK; based on Host system time. uint64_t system_ts_latency = current_system_ts - system_ts; // Time from center of exposure PTS time (converted to system time based on low latency IMU data) until we // read the frame; based on Host system time. uint64_t system_ts_latency_from_pts = current_system_ts - system_ts_from_pts; if (system_ts_from_pts > current_system_ts) { printf("Calculated %s pts system time %" PRId64 " is after our arrival system time %" PRId64 " a diff of %" PRId64 "\n", process_color ? "color" : "IR", STS_TO_MS(system_ts_from_pts), STS_TO_MS(current_system_ts), STS_TO_MS(system_ts_from_pts - current_system_ts)); // Update values anyway *system_ts_last = system_ts; *system_ts_from_pts_last = system_ts_from_pts; } else { if (!*image_first_pass) { system_latency->push_back(current_system_ts - system_ts); system_latency_from_pts->push_back(system_ts_latency_from_pts); printf("| %9" PRId64 " [%5" PRId64 "] [%5" PRId64 "] ", STS_TO_MS(system_ts), STS_TO_MS(system_ts_latency), STS_TO_MS(system_ts_latency_from_pts)); // TS should increase EXPECT_GT(system_ts, *system_ts_last); EXPECT_GT(system_ts_from_pts, *system_ts_from_pts_last); } *system_ts_last = system_ts; *system_ts_from_pts_last = system_ts_from_pts; *image_first_pass = false; } k4a_image_release(image); } else { printf("| "); } } TEST_P(latency_perf, testTest) { auto as = GetParam(); const int32_t TIMEOUT_IN_MS = 1000; k4a_capture_t capture = NULL; int capture_count = g_capture_count; bool failed = false; k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; thread_data thread = { 0 }; THREAD_HANDLE th1 = NULL; std::deque color_system_latency; std::deque color_system_latency_from_pts; std::deque ir_system_latency; std::deque ir_system_latency_from_pts; uint64_t current_system_ts = 0; uint64_t color_system_ts_last = 0, color_system_ts_from_pts_last = 0; uint64_t ir_system_ts_last = 0, ir_system_ts_from_pts_last = 0; int32_t read_exposure = 0; printf("Capturing %d frames for test: %s\n", g_capture_count, as.test_name); { int32_t power_line_setting = g_power_line_50_hz ? 1 : 2; ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_set_color_control(m_device, K4A_COLOR_CONTROL_POWERLINE_FREQUENCY, K4A_COLOR_CONTROL_MODE_MANUAL, power_line_setting)); printf("Power line mode set to manual and %s.\n", power_line_setting == 1 ? "50Hz" : "60Hz"); } if (g_manual_exposure) { k4a_color_control_mode_t read_mode; ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_set_color_control(m_device, K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, K4A_COLOR_CONTROL_MODE_MANUAL, (int32_t)g_exposure_setting)); ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_get_color_control(m_device, K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, &read_mode, &read_exposure)); printf( "Setting exposure to manual mode, exposure target is: %d. Actual mode is: %s. Actual value is: %d.\n", g_exposure_setting, read_mode == K4A_COLOR_CONTROL_MODE_AUTO ? "auto" : "manual", read_exposure); read_exposure = 0; // Clear this so we read it again after sensor is started. } else { ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_set_color_control(m_device, K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, K4A_COLOR_CONTROL_MODE_AUTO, 0)); printf("Auto Exposure\n"); read_exposure = 0; } config.color_format = as.color_format; config.color_resolution = as.color_resolution; config.depth_mode = as.depth_mode; config.camera_fps = as.fps; config.depth_delay_off_color_usec = g_depth_delay_off_color_usec; config.wired_sync_mode = g_wired_sync_mode; config.synchronized_images_only = g_synchronized_images_only; config.subordinate_delay_off_master_usec = g_subordinate_delay_off_master_usec; printf("Config being used is:\n"); printf(" color_format:%d\n", config.color_format); printf(" color_resolution:%d\n", config.color_resolution); printf(" depth_mode:%d\n", config.depth_mode); printf(" camera_fps:%d\n", config.camera_fps); printf(" synchronized_images_only:%d\n", config.synchronized_images_only); printf(" depth_delay_off_color_usec:%d\n", config.depth_delay_off_color_usec); printf(" wired_sync_mode:%d\n", config.wired_sync_mode); printf(" subordinate_delay_off_master_usec:%d\n", config.subordinate_delay_off_master_usec); printf(" disable_streaming_indicator:%d\n", config.disable_streaming_indicator); printf("\n"); ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(m_device, &config)); thread.device = m_device; ASSERT_EQ(THREADAPI_OK, ThreadAPI_Create(&th1, _latency_imu_thread, &thread)); if (!g_no_startup_flush) { // // Wait for streams to start and then purge the data collected // if (as.fps == K4A_FRAMES_PER_SECOND_30) { printf("Flushing first 2s of data\n"); ThreadAPI_Sleep(2000); } else if (as.fps == K4A_FRAMES_PER_SECOND_15) { printf("Flushing first 3s of data\n"); ThreadAPI_Sleep(3000); } else { printf("Flushing first 4s of data\n"); ThreadAPI_Sleep(4000); } while (K4A_WAIT_RESULT_SUCCEEDED == k4a_device_get_capture(m_device, &capture, 0)) { // Drain the queue k4a_capture_release(capture); }; } else { printf("Flushing no start of stream data\n"); } // For consistent IMU timing, block entering the while loop until we get 1 sample if (K4A_WAIT_RESULT_SUCCEEDED == k4a_device_get_capture(m_device, &capture, 1000)) { k4a_capture_release(capture); capture = NULL; } printf("Sys lat: is this difference in the system time recorded on the image and the system time when the image " "was presented to the caller.\n"); printf( "PTS lat: Similar to Sys lat, but instead of using the system time assigned to the image (which is recorded by " "the Host PC), the image PTS (which is center of exposure in single camera mode) is used to " "calculate a more accurate system time from when the same PTS arrived from the least latent sensor source, " "IMU. The IMU data received is turned into a list of PTS values and associated system ts's for when each " "sample arrived on system.\n"); printf("+---------------------------+---------------------------+\n"); printf("| Color Info (ms) | IR 16 Info (ms) |\n"); printf("| system [ sys ] [ PTS ] | system [ sys ] [ PTS ] |\n"); printf("| ts [ lat ] [ lat ] | ts [ lat ] [ lat ] |\n"); printf("+---------------------------+---------------------------+\n"); thread.save_samples = true; // start saving IMU samples bool color_first_pass = true; bool ir_first_pass = true; capture_count++; // to account for dropping the first sample while (capture_count-- > 0) { if (capture) { k4a_capture_release(capture); } // Get a depth frame k4a_wait_result_t wresult = k4a_device_get_capture(m_device, &capture, TIMEOUT_IN_MS); if (wresult != K4A_WAIT_RESULT_SUCCEEDED) { if (wresult == K4A_WAIT_RESULT_TIMEOUT) { printf("Timed out waiting for a capture\n"); } else // wresult == K4A_WAIT_RESULT_FAILED: { printf("Failed to read a capture\n"); capture_count = 0; } failed = true; continue; } if (get_system_time(¤t_system_ts) == 0) { printf("Timed out waiting for a capture\n"); failed = true; continue; } if (read_exposure == 0) { k4a_image_t image = k4a_capture_get_color_image(capture); if (image) { read_exposure = (int32_t)k4a_image_get_exposure_usec(image); k4a_image_release(image); } } process_image(capture, current_system_ts, true, // Color Image &color_first_pass, &color_system_latency, &color_system_latency_from_pts, &color_system_ts_last, &color_system_ts_from_pts_last); process_image(capture, current_system_ts, false, // IR Image &ir_first_pass, &ir_system_latency, &ir_system_latency_from_pts, &ir_system_ts_last, &ir_system_ts_from_pts_last); printf("|\n"); // End of line } // End capture loop thread.exit = true; // shut down IMU thread k4a_device_stop_cameras(m_device); if (capture) { k4a_capture_release(capture); } int thread_result; ASSERT_EQ(THREADAPI_OK, ThreadAPI_Join(th1, &thread_result)); ASSERT_EQ(thread_result, (int)K4A_RESULT_SUCCEEDED); printf("\nLatency Results:\n"); { // init CSV line if (m_file_handle != 0) { std::time_t date_time = std::time(NULL); char buffer_date_time[100]; std::strftime(buffer_date_time, sizeof(buffer_date_time), "%c", localtime(&date_time)); const char *computer_name = environment_get_variable("COMPUTERNAME"); const char *disable_synchronization = environment_get_variable("K4A_DISABLE_SYNCHRONIZATION"); char buffer[1024]; snprintf(buffer, sizeof(buffer), "%s, %s, %s, %s,%s, %s, fps, %d, %s, captures, %d, %d, %d,", buffer_date_time, computer_name ? computer_name : "computer name not set", as.test_name, disable_synchronization ? disable_synchronization : "0", get_string_from_color_format(as.color_format), get_string_from_color_resolution(as.color_resolution), k4a_convert_fps_to_uint(as.fps), get_string_from_depth_mode(as.depth_mode), g_capture_count, g_manual_exposure, read_exposure); fputs(buffer, m_file_handle); } } { uint64_t color_system_latency_ave = 0; uint64_t min = (uint64_t)-1; uint64_t max = 0; for (size_t x = 0; x < color_system_latency.size(); x++) { color_system_latency_ave += color_system_latency[x]; if (color_system_latency[x] < min) { min = color_system_latency[x]; } if (color_system_latency[x] > max) { max = color_system_latency[x]; } } color_system_latency_ave = color_system_latency_ave / color_system_latency.size(); print_and_log("Color System Time Latency", get_string_from_color_format(config.color_format), STS_TO_MS(color_system_latency_ave), STS_TO_MS(min), STS_TO_MS(max)); } { uint64_t color_system_latency_from_pts_ave = 0; uint64_t min = (uint64_t)-1; uint64_t max = 0; for (size_t x = 0; x < color_system_latency_from_pts.size(); x++) { color_system_latency_from_pts_ave += color_system_latency_from_pts[x]; if (color_system_latency_from_pts[x] < min) { min = color_system_latency_from_pts[x]; } if (color_system_latency_from_pts[x] > max) { max = color_system_latency_from_pts[x]; } } color_system_latency_from_pts_ave = color_system_latency_from_pts_ave / color_system_latency_from_pts.size(); print_and_log("Color System Time PTS Latency", get_string_from_color_format(config.color_format), STS_TO_MS(color_system_latency_from_pts_ave), STS_TO_MS(min), STS_TO_MS(max)); } { uint64_t ir_system_latency_ave = 0; uint64_t min = (uint64_t)-1; uint64_t max = 0; for (size_t x = 0; x < ir_system_latency.size(); x++) { ir_system_latency_ave += ir_system_latency[x]; if (ir_system_latency[x] < min) { min = ir_system_latency[x]; } if (ir_system_latency[x] > max) { max = ir_system_latency[x]; } } ir_system_latency_ave = ir_system_latency_ave / ir_system_latency.size(); print_and_log(" IR System Time Latency", get_string_from_depth_mode(config.depth_mode), STS_TO_MS(ir_system_latency_ave), STS_TO_MS(min), STS_TO_MS(max)); } { uint64_t ir_system_latency_from_pts_ave = 0; uint64_t min = (uint64_t)-1; uint64_t max = 0; for (size_t x = 0; x < ir_system_latency_from_pts.size(); x++) { ir_system_latency_from_pts_ave += ir_system_latency_from_pts[x]; if (ir_system_latency_from_pts[x] < min) { min = ir_system_latency_from_pts[x]; } if (ir_system_latency_from_pts[x] > max) { max = ir_system_latency_from_pts[x]; } } ir_system_latency_from_pts_ave = ir_system_latency_from_pts_ave / ir_system_latency_from_pts.size(); print_and_log(" IR System Time PTS", get_string_from_depth_mode(config.depth_mode), STS_TO_MS(ir_system_latency_from_pts_ave), STS_TO_MS(min), STS_TO_MS(max)); } printf("\n"); if (m_file_handle != 0) { // Terminate line fputs("\n", m_file_handle); } ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_set_color_control(m_device, K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, K4A_COLOR_CONTROL_MODE_AUTO, 0)); ASSERT_EQ(failed, false); return; } // K4A_DEPTH_MODE_WFOV_UNBINNED is the most demanding depth mode, only runs at 15FPS or less // clang-format off // PASSIVE_IR is fastest Depth Mode - YUY2 is fastest Color mode static struct latency_parameters tests_30fps[] = { // All Color modes with fast Depth { 0, "FPS_30_MJPEG_2160P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_2160P, K4A_DEPTH_MODE_PASSIVE_IR}, { 1, "FPS_30_MJPEG_1536P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_1536P, K4A_DEPTH_MODE_PASSIVE_IR}, { 2, "FPS_30_MJPEG_1440P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_1440P, K4A_DEPTH_MODE_PASSIVE_IR}, { 3, "FPS_30_MJPEG_1080P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_1080P, K4A_DEPTH_MODE_PASSIVE_IR}, { 4, "FPS_30_MJPEG_0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, { 5, "FPS_30_NV12__0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_NV12, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, { 6, "FPS_30_YUY2__0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, { 7, "FPS_30_BGRA32_2160P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_2160P, K4A_DEPTH_MODE_PASSIVE_IR}, { 8, "FPS_30_BGRA32_1536P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_1536P, K4A_DEPTH_MODE_PASSIVE_IR}, { 9, "FPS_30_BGRA32_1440P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_1440P, K4A_DEPTH_MODE_PASSIVE_IR}, { 10, "FPS_30_BGRA32_1080P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_1080P, K4A_DEPTH_MODE_PASSIVE_IR}, { 11, "FPS_30_BGRA32_0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, // All Depth Modes with fastest Color { 12, "FPS_30_YUY2__0720P_NFOV_2X2BINNED", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_NFOV_2X2BINNED}, { 13, "FPS_30_YUY2__0720P_NFOV_UNBINNED", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_NFOV_UNBINNED}, { 14, "FPS_30_YUY2__0720P_WFOV_2X2BINNED", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_WFOV_2X2BINNED}, { 15, "FPS_30_YUY2__0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, }; INSTANTIATE_TEST_CASE_P(30FPS_TESTS, latency_perf, ValuesIn(tests_30fps)); static struct latency_parameters tests_15fps[] = { // All Color modes with fast Depth { 0, "FPS_15_MJPEG_3072P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_15, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_3072P, K4A_DEPTH_MODE_PASSIVE_IR}, { 1, "FPS_15_BGRA32_3072P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_15, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_3072P, K4A_DEPTH_MODE_PASSIVE_IR}, // All Depth Modes with fastest Color { 2, "FPS_15_YUY2__0720P_WFOV_UNBINNED", K4A_FRAMES_PER_SECOND_15, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_WFOV_UNBINNED}, }; INSTANTIATE_TEST_CASE_P(15FPS_TESTS, latency_perf, ValuesIn(tests_15fps)); // clang-format on int main(int argc, char **argv) { bool error = false; k4a_unittest_init(); ::testing::InitGoogleTest(&argc, argv); for (int i = 1; i < argc; ++i) { char *argument = argv[i]; for (int j = 0; argument[j]; j++) { argument[j] = (char)tolower(argument[j]); } if (strcmp(argument, "--depth_delay_off_color") == 0) { if (i + 1 <= argc) { g_depth_delay_off_color_usec = (int32_t)strtol(argv[i + 1], NULL, 10); printf("Setting g_depth_delay_off_color_usec = %d\n", g_depth_delay_off_color_usec); i++; } else { printf("Error: depth_delay_off_color parameter missing\n"); error = true; } } else if (strcmp(argument, "--skip_delay_off_color_validation") == 0) { g_skip_delay_off_color_validation = true; } else if (strcmp(argument, "--master") == 0) { g_wired_sync_mode = K4A_WIRED_SYNC_MODE_MASTER; printf("Setting g_wired_sync_mode = K4A_WIRED_SYNC_MODE_MASTER\n"); } else if (strcmp(argument, "--subordinate") == 0) { g_wired_sync_mode = K4A_WIRED_SYNC_MODE_SUBORDINATE; printf("Setting g_wired_sync_mode = K4A_WIRED_SYNC_MODE_SUBORDINATE\n"); } else if (strcmp(argument, "--synchronized_images_only") == 0) { g_synchronized_images_only = true; printf("g_synchronized_images_only = true\n"); } else if (strcmp(argument, "--no_startup_flush") == 0) { g_no_startup_flush = true; printf("g_no_startup_flush = true\n"); } else if (strcmp(argument, "--60hz") == 0) { g_power_line_50_hz = false; printf("g_power_line_50_hz = false\n"); } else if (strcmp(argument, "--50hz") == 0) { g_power_line_50_hz = true; printf("g_power_line_50_hz = true\n"); } else if (strcmp(argument, "--index") == 0) { if (i + 1 <= argc) { g_device_index = (uint8_t)strtol(argv[i + 1], NULL, 10); printf("setting g_device_index = %d\n", g_device_index); i++; } else { printf("Error: index parameter missing\n"); error = true; } } else if (strcmp(argument, "--subordinate_delay_off_master_usec") == 0) { if (i + 1 <= argc) { g_subordinate_delay_off_master_usec = (uint32_t)strtol(argv[i + 1], NULL, 10); printf("g_subordinate_delay_off_master_usec = %d\n", g_subordinate_delay_off_master_usec); i++; } else { printf("Error: index parameter missing\n"); error = true; } } else if (strcmp(argument, "--capture_count") == 0) { if (i + 1 <= argc) { g_capture_count = (int)strtol(argv[i + 1], NULL, 10); printf("g_capture_count g_device_index = %d\n", g_capture_count); i++; } else { printf("Error: index parameter missing\n"); error = true; } } else if (strcmp(argument, "--exposure") == 0) { if (i + 1 <= argc) { g_exposure_setting = (uint32_t)strtol(argv[i + 1], NULL, 10); printf("g_exposure_setting = %d\n", g_exposure_setting); g_manual_exposure = true; i++; } else { printf("Error: index parameter missing\n"); error = true; } } else if (strcmp(argument, "--auto") == 0) { g_manual_exposure = false; printf("Auto Exposure Enabled\n"); } if ((strcmp(argument, "-h") == 0) || (strcmp(argument, "/h") == 0) || (strcmp(argument, "-?") == 0) || (strcmp(argument, "/?") == 0)) { error = true; } } if (error) { printf("\n\nOptional Custom Test Settings:\n"); printf(" --depth_delay_off_color <+/- microseconds>\n"); printf(" This is the time delay the depth image capture is delayed off the color.\n"); printf(" valid ranges for this are -1 frame time to +1 frame time. The percentage\n"); printf(" needs to be multiplied by 100 to achieve correct behavior; 10000 is \n"); printf(" 100.00%%, 100 is 1.00%%.\n"); printf(" --skip_delay_off_color_validation\n"); printf(" Set this when don't want the results of color to depth timestamp \n" " measurements to allow your test run to fail. They will still be logged\n" " to output and the CSV file.\n"); printf(" --master\n"); printf(" Run device in master mode\n"); printf(" --subordinate\n"); printf(" Run device in subordinate mode\n"); printf(" --index\n"); printf(" The device index to target when calling k4a_device_open()\n"); printf(" --capture_count\n"); printf(" The number of captures the test should read; default is 100\n"); printf(" --synchronized_images_only\n"); printf(" By default this setting is false, enabling this will for the test to wait for\n"); printf(" both and depth images to be available.\n"); printf(" --subordinate_delay_off_master_usec <+ microseconds>\n"); printf(" This is the time delay the device captures off the master devices capture sync\n"); printf(" pulse. This value needs to be less than one image sample period, i.e for 30FPS \n"); printf(" this needs to be less than 33333us.\n"); printf(" --no_startup_flush\n"); printf(" By default the test will wait for streams to run for X seconds to stabilize. This\n"); printf(" disables that.\n"); printf(" --exposure \n"); printf(" Deault is manual exposure with an exposure of 33,333us. This will test with the manual exposure " "setting\n"); printf(" that is passed in.\n"); printf(" --auto\n"); printf(" By default the test uses manual exposure. This will test with auto exposure.\n"); printf(" --60hz\n"); printf(" Sets the power line compensation frequency to 60Hz\n"); printf(" --50hz\n"); printf(" Sets the power line compensation frequency to 50Hz\n"); return 1; // Indicates an error or warning } int results = RUN_ALL_TESTS(); k4a_unittest_deinit(); return results; }