Convert all playback timestamps to use device time (#592)

* Convert all playback timestamps to use device time

* Add some extra documentation comments around seek_timestamp

* Rename last_timestamp_ns field to last_file_timestamp_ns
This commit is contained in:
Jacob Wirth 2019-08-05 15:45:17 -07:00 коммит произвёл GitHub
Родитель 0f82028001
Коммит 3b299f14bd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 454 добавлений и 64 удалений

Просмотреть файл

@ -51,8 +51,7 @@ static void print_capture_info(recording_t *file)
{
if (images[i] != NULL)
{
uint64_t timestamp = k4a_image_get_device_timestamp_usec(images[i]) +
(uint64_t)file->record_config.start_timestamp_offset_usec;
uint64_t timestamp = k4a_image_get_device_timestamp_usec(images[i]);
printf(" %7ju usec", timestamp);
k4a_image_release(images[i]);
images[i] = NULL;
@ -162,11 +161,7 @@ int main(int argc, char **argv)
{
if (files[i].capture != NULL)
{
// All recording files start at timestamp 0, however the first timestamp off the camera is usually
// non-zero. We need to add the recording "start offset" back to the recording timestamp to recover
// the original timestamp from the device, and synchronize the files.
uint64_t timestamp = first_capture_timestamp(files[i].capture) +
files[i].record_config.start_timestamp_offset_usec;
uint64_t timestamp = first_capture_timestamp(files[i].capture);
if (timestamp < min_timestamp)
{
min_timestamp = timestamp;

Просмотреть файл

@ -273,7 +273,7 @@ static int playback(char *input_path, int timestamp = 1000, std::string output_f
}
printf("Seeking to timestamp: %d/%d (ms)\n",
timestamp,
(int)(k4a_playback_get_last_timestamp_usec(playback) / 1000));
(int)(k4a_playback_get_recording_length_usec(playback) / 1000));
stream_result = k4a_playback_get_next_capture(playback, &capture);
if (stream_result != K4A_STREAM_RESULT_SUCCEEDED || capture == NULL)

Просмотреть файл

@ -121,7 +121,7 @@ typedef struct _k4a_playback_context_t
uint64_t attachments_offset;
uint64_t tags_offset;
uint64_t last_timestamp_ns;
uint64_t last_file_timestamp_ns; // Relative to start of file.
// Stats
uint64_t seek_count, load_count, cache_hits;

Просмотреть файл

@ -376,10 +376,11 @@ K4ARECORD_EXPORT k4a_stream_result_t k4a_playback_get_previous_imu_sample(k4a_pl
* Handle obtained by k4a_playback_open().
*
* \param offset_usec
* The timestamp offset to seek to relative to \p origin
* The timestamp offset to seek to, relative to \p origin
*
* \param origin
* Specifies if the seek operation should be done relative to the beginning or end of the recording.
* Specifies how the given timestamp should be interpreted. Seek can be done relative to the beginning or end of the
* recording, or using an absolute device timestamp.
*
* \returns
* ::K4A_RESULT_SUCCEEDED if the seek operation was successful, or ::K4A_RESULT_FAILED if an error occured. The current
@ -388,6 +389,10 @@ K4ARECORD_EXPORT k4a_stream_result_t k4a_playback_get_previous_imu_sample(k4a_pl
* \relates k4a_playback_t
*
* \remarks
* The first device timestamp in a recording is usually non-zero. The recording file starts at the device timestamp
* defined by start_timestamp_offset_usec, which is accessible via k4a_playback_get_record_configuration().
*
* \remarks
* The first call to k4a_playback_get_next_capture() after k4a_playback_seek_timestamp() will return the first capture
* containing an image timestamp greater than or equal to the seek time.
*
@ -415,18 +420,19 @@ K4ARECORD_EXPORT k4a_result_t k4a_playback_seek_timestamp(k4a_playback_t playbac
int64_t offset_usec,
k4a_playback_seek_origin_t origin);
/** Gets the last timestamp in a recording.
/** Returns the length of the recording in microseconds.
*
* \param playback_handle
* Handle obtained by k4a_playback_open().
*
* \returns
* The timestamp of the last capture image or IMU sample in microseconds.
* The recording length, calculated as the difference between the first and last timestamp in the file.
*
* \relates k4a_playback_t
*
* \remarks
* Recordings start at timestamp 0, and end at the timestamp returned by k4a_playback_get_last_timestamp_usec().
* The recording length may be longer than an individual track if, for example, the IMU continues to run after the last
* color image is recorded.
*
* \xmlonly
* <requirements>
@ -436,7 +442,34 @@ K4ARECORD_EXPORT k4a_result_t k4a_playback_seek_timestamp(k4a_playback_t playbac
* </requirements>
* \endxmlonly
*/
K4ARECORD_EXPORT uint64_t k4a_playback_get_last_timestamp_usec(k4a_playback_t playback_handle);
K4ARECORD_EXPORT uint64_t k4a_playback_get_recording_length_usec(k4a_playback_t playback_handle);
/** Gets the last timestamp in a recording, relative to the start of the recording.
*
* \param playback_handle
* Handle obtained by k4a_playback_open().
*
* \returns
* The file timestamp of the last capture image or IMU sample in microseconds.
*
* \relates k4a_playback_t
*
* \remarks
* This function returns a file timestamp, not an absolute device timestamp, meaning it is relative to the start of the
* recording. This function is equivalent to the length of the recording.
*
* \deprecated
* Deprecated starting in 1.2.0. Please use k4a_playback_get_recording_length_usec().
*
* \xmlonly
* <requirements>
* <requirement name="Header">playback.h (include k4arecord/playback.h)</requirement>
* <requirement name="Library">k4arecord.lib</requirement>
* <requirement name="DLL">k4arecord.dll</requirement>
* </requirements>
* \endxmlonly
*/
K4ARECORD_DEPRECATED_EXPORT uint64_t k4a_playback_get_last_timestamp_usec(k4a_playback_t playback_handle);
/** Closes a recording playback handle.
*

Просмотреть файл

@ -288,11 +288,11 @@ public:
/** Get the last valid timestamp in the recording
*
* \sa k4a_playback_get_last_timestamp_usec
* \sa k4a_playback_get_recording_length_usec
*/
std::chrono::microseconds get_last_timestamp() const noexcept
std::chrono::microseconds get_recording_length() const noexcept
{
return std::chrono::microseconds(k4a_playback_get_last_timestamp_usec(m_handle));
return std::chrono::microseconds(k4a_playback_get_recording_length_usec(m_handle));
}
/** Set the image format that color captures will be converted to. By default the conversion format will be the same

Просмотреть файл

@ -80,8 +80,9 @@ typedef enum
*/
typedef enum
{
K4A_PLAYBACK_SEEK_BEGIN, /**< Seek relative to the beginning of a recording. */
K4A_PLAYBACK_SEEK_END /**< Seek relative to the end of a recording. */
K4A_PLAYBACK_SEEK_BEGIN, /**< Seek relative to the beginning of a recording. */
K4A_PLAYBACK_SEEK_END, /**< Seek relative to the end of a recording. */
K4A_PLAYBACK_SEEK_DEVICE_TIME /**< Seek to an absolute device timestamp. */
} k4a_playback_seek_origin_t;
/**

Просмотреть файл

@ -229,7 +229,7 @@ k4a_result_t parse_mkv(k4a_playback_context_t *context)
RETURN_IF_ERROR(populate_cluster_cache(context));
// Find the last timestamp in the file
context->last_timestamp_ns = 0;
context->last_file_timestamp_ns = 0;
cluster_info_t *cluster_info = find_cluster(context, UINT64_MAX);
if (cluster_info == NULL)
{
@ -251,9 +251,9 @@ k4a_result_t parse_mkv(k4a_playback_context_t *context)
{
simple_block->SetParent(*last_cluster);
uint64_t block_timestamp_ns = simple_block->GlobalTimecode();
if (block_timestamp_ns > context->last_timestamp_ns)
if (block_timestamp_ns > context->last_file_timestamp_ns)
{
context->last_timestamp_ns = block_timestamp_ns;
context->last_file_timestamp_ns = block_timestamp_ns;
}
}
else if (check_element_type(e, &block_group))
@ -281,13 +281,13 @@ k4a_result_t parse_mkv(k4a_playback_context_t *context)
block_timestamp_ns += block_duration_ns - 1;
}
}
if (block_timestamp_ns > context->last_timestamp_ns)
if (block_timestamp_ns > context->last_file_timestamp_ns)
{
context->last_timestamp_ns = block_timestamp_ns;
context->last_file_timestamp_ns = block_timestamp_ns;
}
}
}
LOG_TRACE("Found last timestamp: %llu", context->last_timestamp_ns);
LOG_TRACE("Found last file timestamp: %llu", context->last_file_timestamp_ns);
return K4A_RESULT_SUCCEEDED;
}
@ -1750,7 +1750,9 @@ k4a_result_t convert_block_to_image(k4a_playback_context_t *context,
&free_vector_buffer,
buffer,
image_out));
k4a_image_set_device_timestamp_usec(*image_out, in_block->timestamp_ns / 1000);
uint64_t device_timestamp_usec = in_block->timestamp_ns / 1000 +
(uint64_t)context->record_config.start_timestamp_offset_usec;
k4a_image_set_device_timestamp_usec(*image_out, device_timestamp_usec);
}
if (K4A_FAILED(result) && buffer != NULL)
@ -2041,6 +2043,11 @@ k4a_stream_result_t get_imu_sample(k4a_playback_context_t *context, k4a_imu_samp
else
{
// The timestamp we're looking for is within the found block.
// IMU timestamps within the sample buffer are device timestamps, not relative to start of file.
// The seek timestamp needs to be converted to a device timestamp when comparing.
uint64_t seek_device_timestamp_ns = context->seek_timestamp_ns +
((uint64_t)context->record_config.start_timestamp_offset_usec *
1000);
context->imu_sample_index = -1;
for (size_t i = 0; i < sample_count; i++)
{
@ -2051,7 +2058,7 @@ k4a_stream_result_t get_imu_sample(k4a_playback_context_t *context, k4a_imu_samp
*imu_sample = { 0 };
return K4A_STREAM_RESULT_FAILED;
}
else if (sample->acc_timestamp_ns >= context->seek_timestamp_ns)
else if (sample->acc_timestamp_ns >= seek_device_timestamp_ns)
{
context->imu_sample_index = next ? (int)i : (int)i - 1;
break;

Просмотреть файл

@ -297,7 +297,16 @@ k4a_result_t k4a_playback_seek_timestamp(k4a_playback_t playback_handle,
k4a_playback_context_t *context = k4a_playback_t_get_context(playback_handle);
RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, context == NULL);
RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, context->segment == nullptr);
RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, origin != K4A_PLAYBACK_SEEK_BEGIN && origin != K4A_PLAYBACK_SEEK_END);
RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED,
origin != K4A_PLAYBACK_SEEK_BEGIN && origin != K4A_PLAYBACK_SEEK_END &&
origin != K4A_PLAYBACK_SEEK_DEVICE_TIME);
// If seeking to a device timestamp, calculate the offset relative to the start of file.
if (origin == K4A_PLAYBACK_SEEK_DEVICE_TIME)
{
origin = K4A_PLAYBACK_SEEK_BEGIN;
offset_usec -= (int64_t)context->record_config.start_timestamp_offset_usec;
}
// Clamp the offset timestamp so the seek direction is correct reletive to the specified origin.
if (origin == K4A_PLAYBACK_SEEK_BEGIN && offset_usec < 0)
@ -313,14 +322,14 @@ k4a_result_t k4a_playback_seek_timestamp(k4a_playback_t playback_handle,
if (origin == K4A_PLAYBACK_SEEK_END)
{
uint64_t offset_ns = (uint64_t)(-offset_usec * 1000);
if (offset_ns > context->last_timestamp_ns)
if (offset_ns > context->last_file_timestamp_ns)
{
// If the target timestamp is negative, clamp to 0 so we don't underflow.
target_time_ns = 0;
}
else
{
target_time_ns = context->last_timestamp_ns + 1 - offset_ns;
target_time_ns = context->last_file_timestamp_ns + 1 - offset_ns;
}
}
else
@ -348,13 +357,22 @@ k4a_result_t k4a_playback_seek_timestamp(k4a_playback_t playback_handle,
return K4A_RESULT_SUCCEEDED;
}
uint64_t k4a_playback_get_recording_length_usec(k4a_playback_t playback_handle)
{
RETURN_VALUE_IF_HANDLE_INVALID(0, k4a_playback_t, playback_handle);
k4a_playback_context_t *context = k4a_playback_t_get_context(playback_handle);
RETURN_VALUE_IF_ARG(0, context == NULL);
return context->last_file_timestamp_ns / 1000;
}
uint64_t k4a_playback_get_last_timestamp_usec(k4a_playback_t playback_handle)
{
RETURN_VALUE_IF_HANDLE_INVALID(0, k4a_playback_t, playback_handle);
k4a_playback_context_t *context = k4a_playback_t_get_context(playback_handle);
RETURN_VALUE_IF_ARG(0, context == NULL);
return context->last_timestamp_ns / 1000;
return context->last_file_timestamp_ns / 1000;
}
void k4a_playback_close(const k4a_playback_t playback_handle)

Просмотреть файл

@ -543,6 +543,13 @@ k4a_result_t k4a_record_write_imu_sample(const k4a_record_t recording_handle, k4
k4a_record_context_t *context = k4a_record_t_get_context(recording_handle);
RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, context == NULL);
if (!context->imu_track)
{
LOG_ERROR("The IMU track needs to be added with k4a_record_add_imu_track() before IMU samples can be written.",
0);
return K4A_RESULT_FAILED;
}
if (!context->header_written)
{
LOG_ERROR("The recording header needs to be written before any imu samples.", 0);

Просмотреть файл

@ -110,6 +110,28 @@ TEST_F(playback_perf, test_open)
}
}
if (config.imu_track_enabled)
{
k4a_imu_sample_t imu_sample = { 0 };
k4a_stream_result_t playback_result = k4a_playback_get_next_imu_sample(handle, &imu_sample);
ASSERT_NE(playback_result, K4A_STREAM_RESULT_FAILED);
if (playback_result == K4A_STREAM_RESULT_EOF)
{
std::cout << "No IMU data in recording." << std::endl;
}
else
{
std::cout << std::endl;
std::cout << "First IMU sample:" << std::endl;
std::cout << " Accel Timestamp: " << imu_sample.acc_timestamp_usec << " usec" << std::endl;
std::cout << " Accel Data: (" << imu_sample.acc_sample.xyz.x << ", " << imu_sample.acc_sample.xyz.y
<< ", " << imu_sample.acc_sample.xyz.z << ")" << std::endl;
std::cout << " Gyro Timestamp: " << imu_sample.gyro_timestamp_usec << " usec" << std::endl;
std::cout << " Gyro Data: (" << imu_sample.gyro_sample.xyz.x << ", " << imu_sample.gyro_sample.xyz.y
<< ", " << imu_sample.gyro_sample.xyz.z << ")" << std::endl;
}
}
k4a_playback_close(handle);
}

Просмотреть файл

@ -93,7 +93,7 @@ TEST_F(playback_ut, open_large_file)
k4a_stream_result_t stream_result = K4A_STREAM_RESULT_FAILED;
uint64_t timestamps[3] = { 0, 1000, 1000 };
uint64_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(config.camera_fps);
int i = 0;
size_t i = 0;
for (; i < 50; i++)
{
stream_result = k4a_playback_get_next_capture(handle, &capture);
@ -130,7 +130,7 @@ TEST_F(playback_ut, open_large_file)
timestamps[1] += timestamp_delta;
timestamps[2] += timestamp_delta;
}
for (; i < 100; i++)
for (; i < test_frame_count; i++)
{
stream_result = k4a_playback_get_next_capture(handle, &capture);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
@ -180,7 +180,7 @@ TEST_F(playback_ut, open_delay_offset_file)
uint64_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(config.camera_fps);
// Read forward
for (int i = 0; i < 100; i++)
for (size_t i = 0; i < test_frame_count; i++)
{
stream_result = k4a_playback_get_next_capture(handle, &capture);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
@ -199,7 +199,7 @@ TEST_F(playback_ut, open_delay_offset_file)
ASSERT_EQ(capture, (k4a_capture_t)NULL);
// Read backward
for (int i = 0; i < 100; i++)
for (size_t i = 0; i < test_frame_count; i++)
{
timestamps[0] -= timestamp_delta;
timestamps[1] -= timestamp_delta;
@ -241,6 +241,16 @@ TEST_F(playback_ut, open_subordinate_delay_file)
ASSERT_EQ(config.depth_delay_off_color_usec, 0);
ASSERT_EQ(config.wired_sync_mode, K4A_WIRED_SYNC_MODE_SUBORDINATE);
ASSERT_EQ(config.subordinate_delay_off_master_usec, (uint32_t)10000);
ASSERT_EQ(config.start_timestamp_offset_usec, (uint32_t)10000);
uint64_t timestamps[3] = { 10000, 10000, 10000 };
k4a_capture_t capture = NULL;
k4a_stream_result_t stream_result = k4a_playback_get_next_capture(handle, &capture);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(
validate_test_capture(capture, timestamps, config.color_format, config.color_resolution, config.depth_mode));
k4a_capture_release(capture);
k4a_playback_close(handle);
}
@ -295,7 +305,7 @@ TEST_F(playback_ut, playback_seek_test)
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(validate_imu_sample(imu_sample, imu_timestamp));
int64_t recording_length = (int64_t)k4a_playback_get_last_timestamp_usec(handle) + 1;
int64_t recording_length = (int64_t)k4a_playback_get_recording_length_usec(handle) + 1;
std::pair<int64_t, k4a_playback_seek_origin_t> start_seek_combinations[] = { // Beginning
{ 0, K4A_PLAYBACK_SEEK_BEGIN },
{ -recording_length,
@ -535,9 +545,7 @@ TEST_F(playback_ut, open_skipped_frames_file)
k4a_capture_t capture = NULL;
k4a_stream_result_t stream_result = K4A_STREAM_RESULT_FAILED;
uint64_t timestamps[3] = { 1000000 - config.start_timestamp_offset_usec,
1001000 - config.start_timestamp_offset_usec,
1001000 - config.start_timestamp_offset_usec };
uint64_t timestamps[3] = { 1000000, 1001000, 1001000 };
uint64_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(config.camera_fps);
// Test initial state
@ -570,7 +578,7 @@ TEST_F(playback_ut, open_skipped_frames_file)
// Test seek past beginning
result = k4a_playback_seek_timestamp(handle,
-(int64_t)k4a_playback_get_last_timestamp_usec(handle) - 10,
-(int64_t)k4a_playback_get_recording_length_usec(handle) - 10,
K4A_PLAYBACK_SEEK_END);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
@ -601,7 +609,7 @@ TEST_F(playback_ut, open_skipped_frames_file)
// Test seek to end, relative to start
result = k4a_playback_seek_timestamp(handle,
(int64_t)k4a_playback_get_last_timestamp_usec(handle) + 1,
(int64_t)k4a_playback_get_recording_length_usec(handle) + 1,
K4A_PLAYBACK_SEEK_BEGIN);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
@ -620,7 +628,7 @@ TEST_F(playback_ut, open_skipped_frames_file)
timestamps[0] -= timestamp_delta * 50;
timestamps[1] -= timestamp_delta * 50;
timestamps[2] -= timestamp_delta * 50;
result = k4a_playback_seek_timestamp(handle, (int64_t)timestamps[0], K4A_PLAYBACK_SEEK_BEGIN);
result = k4a_playback_seek_timestamp(handle, (int64_t)timestamps[0], K4A_PLAYBACK_SEEK_DEVICE_TIME);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
stream_result = k4a_playback_get_next_capture(handle, &capture);
@ -631,7 +639,7 @@ TEST_F(playback_ut, open_skipped_frames_file)
k4a_capture_release(capture);
// Test seek to middle of the recording, then read backward
result = k4a_playback_seek_timestamp(handle, (int64_t)timestamps[0], K4A_PLAYBACK_SEEK_BEGIN);
result = k4a_playback_seek_timestamp(handle, (int64_t)timestamps[0], K4A_PLAYBACK_SEEK_DEVICE_TIME);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
timestamps[0] -= timestamp_delta;
@ -646,7 +654,7 @@ TEST_F(playback_ut, open_skipped_frames_file)
k4a_capture_release(capture);
// Read the rest of the file
for (int i = 49; i < 100; i++)
for (size_t i = 49; i < test_frame_count; i++)
{
timestamps[0] += timestamp_delta;
timestamps[1] += timestamp_delta;
@ -721,11 +729,11 @@ TEST_F(playback_ut, open_imu_playback_file)
k4a_imu_sample_t imu_sample = { 0 };
k4a_stream_result_t stream_result = K4A_STREAM_RESULT_FAILED;
uint64_t imu_timestamp = 1150;
uint64_t last_timestamp = k4a_playback_get_last_timestamp_usec(handle);
ASSERT_EQ(last_timestamp, 3333150);
uint64_t recording_length = k4a_playback_get_recording_length_usec(handle);
ASSERT_EQ(recording_length, 3333150);
// Read forward
while (imu_timestamp <= last_timestamp)
while (imu_timestamp <= recording_length)
{
stream_result = k4a_playback_get_next_imu_sample(handle, &imu_sample);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
@ -749,7 +757,7 @@ TEST_F(playback_ut, open_imu_playback_file)
ASSERT_TRUE(validate_null_imu_sample(imu_sample));
// Test seeking to first 100 samples (covers edge cases around block boundaries)
for (int i = 0; i < 100; i++)
for (size_t i = 0; i < test_frame_count; i++)
{
// Seek to before sample
result = k4a_playback_seek_timestamp(handle, (int64_t)imu_timestamp - 100, K4A_PLAYBACK_SEEK_BEGIN);
@ -781,6 +789,204 @@ TEST_F(playback_ut, open_imu_playback_file)
k4a_playback_close(handle);
}
TEST_F(playback_ut, open_start_offset_file)
{
k4a_playback_t handle = NULL;
k4a_result_t result = k4a_playback_open("record_test_offset.mkv", &handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
// Read recording configuration
k4a_record_configuration_t config;
result = k4a_playback_get_record_configuration(handle, &config);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
ASSERT_EQ(config.color_format, K4A_IMAGE_FORMAT_COLOR_MJPG);
ASSERT_EQ(config.color_resolution, K4A_COLOR_RESOLUTION_1080P);
ASSERT_EQ(config.depth_mode, K4A_DEPTH_MODE_NFOV_UNBINNED);
ASSERT_EQ(config.camera_fps, K4A_FRAMES_PER_SECOND_30);
ASSERT_TRUE(config.color_track_enabled);
ASSERT_TRUE(config.depth_track_enabled);
ASSERT_TRUE(config.ir_track_enabled);
ASSERT_TRUE(config.imu_track_enabled);
ASSERT_EQ(config.depth_delay_off_color_usec, 0);
ASSERT_EQ(config.wired_sync_mode, K4A_WIRED_SYNC_MODE_STANDALONE);
ASSERT_EQ(config.subordinate_delay_off_master_usec, (uint32_t)0);
ASSERT_EQ(config.start_timestamp_offset_usec, (uint32_t)1000000);
k4a_capture_t capture = NULL;
k4a_imu_sample_t imu_sample = { 0 };
k4a_stream_result_t stream_result = K4A_STREAM_RESULT_FAILED;
uint64_t timestamps[3] = { 1000000, 1000000, 1000000 };
uint64_t imu_timestamp = 1001150;
uint64_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(config.camera_fps);
uint64_t last_timestamp = k4a_playback_get_recording_length_usec(handle) +
(uint64_t)config.start_timestamp_offset_usec;
ASSERT_EQ(last_timestamp, (uint64_t)config.start_timestamp_offset_usec + 3333150);
// Read capture forward
for (size_t i = 0; i < test_frame_count; i++)
{
stream_result = k4a_playback_get_next_capture(handle, &capture);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(validate_test_capture(capture,
timestamps,
config.color_format,
config.color_resolution,
config.depth_mode));
k4a_capture_release(capture);
timestamps[0] += timestamp_delta;
timestamps[1] += timestamp_delta;
timestamps[2] += timestamp_delta;
}
stream_result = k4a_playback_get_next_capture(handle, &capture);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_EOF);
ASSERT_EQ(capture, (k4a_capture_t)NULL);
// Read capture backward
for (size_t i = 0; i < test_frame_count; i++)
{
timestamps[0] -= timestamp_delta;
timestamps[1] -= timestamp_delta;
timestamps[2] -= timestamp_delta;
stream_result = k4a_playback_get_previous_capture(handle, &capture);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(validate_test_capture(capture,
timestamps,
config.color_format,
config.color_resolution,
config.depth_mode));
k4a_capture_release(capture);
}
stream_result = k4a_playback_get_previous_capture(handle, &capture);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_EOF);
ASSERT_EQ(capture, (k4a_capture_t)NULL);
// Read IMU forward
while (imu_timestamp <= last_timestamp)
{
stream_result = k4a_playback_get_next_imu_sample(handle, &imu_sample);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(validate_imu_sample(imu_sample, imu_timestamp));
imu_timestamp += 1000;
}
stream_result = k4a_playback_get_next_imu_sample(handle, &imu_sample);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_EOF);
ASSERT_TRUE(validate_null_imu_sample(imu_sample));
// Read IMU backward
while (imu_timestamp > 1001150)
{
imu_timestamp -= 1000;
stream_result = k4a_playback_get_previous_imu_sample(handle, &imu_sample);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(validate_imu_sample(imu_sample, imu_timestamp));
}
stream_result = k4a_playback_get_previous_imu_sample(handle, &imu_sample);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_EOF);
ASSERT_TRUE(validate_null_imu_sample(imu_sample));
// Test seeking to first 100 samples (covers edge cases around block boundaries)
for (size_t i = 0; i < test_frame_count; i++)
{
// Seek to before sample
result = k4a_playback_seek_timestamp(handle, (int64_t)imu_timestamp - 100, K4A_PLAYBACK_SEEK_DEVICE_TIME);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
stream_result = k4a_playback_get_next_imu_sample(handle, &imu_sample);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(validate_imu_sample(imu_sample, imu_timestamp));
// Seek exactly to sample
result = k4a_playback_seek_timestamp(handle, (int64_t)imu_timestamp, K4A_PLAYBACK_SEEK_DEVICE_TIME);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
stream_result = k4a_playback_get_next_imu_sample(handle, &imu_sample);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(validate_imu_sample(imu_sample, imu_timestamp));
// Seek to after sample
result = k4a_playback_seek_timestamp(handle, (int64_t)imu_timestamp + 100, K4A_PLAYBACK_SEEK_DEVICE_TIME);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
stream_result = k4a_playback_get_previous_imu_sample(handle, &imu_sample);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(validate_imu_sample(imu_sample, imu_timestamp));
imu_timestamp += 1000;
}
k4a_playback_close(handle);
}
TEST_F(playback_ut, open_color_only_file)
{
k4a_playback_t handle = NULL;
k4a_result_t result = k4a_playback_open("record_test_color_only.mkv", &handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
// Read recording configuration
k4a_record_configuration_t config;
result = k4a_playback_get_record_configuration(handle, &config);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
ASSERT_EQ(config.color_format, K4A_IMAGE_FORMAT_COLOR_MJPG);
ASSERT_EQ(config.color_resolution, K4A_COLOR_RESOLUTION_1080P);
ASSERT_EQ(config.depth_mode, K4A_DEPTH_MODE_OFF);
ASSERT_EQ(config.camera_fps, K4A_FRAMES_PER_SECOND_30);
ASSERT_TRUE(config.color_track_enabled);
ASSERT_FALSE(config.depth_track_enabled);
ASSERT_FALSE(config.ir_track_enabled);
ASSERT_FALSE(config.imu_track_enabled);
ASSERT_EQ(config.depth_delay_off_color_usec, 0);
ASSERT_EQ(config.wired_sync_mode, K4A_WIRED_SYNC_MODE_STANDALONE);
ASSERT_EQ(config.subordinate_delay_off_master_usec, (uint32_t)0);
ASSERT_EQ(config.start_timestamp_offset_usec, (uint32_t)0);
uint64_t timestamps[3] = { 0, 0, 0 };
k4a_capture_t capture = NULL;
k4a_stream_result_t stream_result = k4a_playback_get_next_capture(handle, &capture);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(
validate_test_capture(capture, timestamps, config.color_format, config.color_resolution, config.depth_mode));
k4a_capture_release(capture);
k4a_playback_close(handle);
}
TEST_F(playback_ut, open_depth_only_file)
{
k4a_playback_t handle = NULL;
k4a_result_t result = k4a_playback_open("record_test_depth_only.mkv", &handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
// Read recording configuration
k4a_record_configuration_t config;
result = k4a_playback_get_record_configuration(handle, &config);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
ASSERT_EQ(config.color_format, K4A_IMAGE_FORMAT_CUSTOM);
ASSERT_EQ(config.color_resolution, K4A_COLOR_RESOLUTION_OFF);
ASSERT_EQ(config.depth_mode, K4A_DEPTH_MODE_NFOV_UNBINNED);
ASSERT_EQ(config.camera_fps, K4A_FRAMES_PER_SECOND_30);
ASSERT_FALSE(config.color_track_enabled);
ASSERT_TRUE(config.depth_track_enabled);
ASSERT_TRUE(config.ir_track_enabled);
ASSERT_FALSE(config.imu_track_enabled);
ASSERT_EQ(config.depth_delay_off_color_usec, 0);
ASSERT_EQ(config.wired_sync_mode, K4A_WIRED_SYNC_MODE_STANDALONE);
ASSERT_EQ(config.subordinate_delay_off_master_usec, (uint32_t)0);
ASSERT_EQ(config.start_timestamp_offset_usec, (uint32_t)0);
uint64_t timestamps[3] = { 0, 0, 0 };
k4a_capture_t capture = NULL;
k4a_stream_result_t stream_result = k4a_playback_get_next_capture(handle, &capture);
ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED);
ASSERT_TRUE(
validate_test_capture(capture, timestamps, config.color_format, config.color_resolution, config.depth_mode));
k4a_capture_release(capture);
k4a_playback_close(handle);
}
int main(int argc, char **argv)
{
k4a_unittest_init();

Просмотреть файл

@ -29,6 +29,12 @@ void SampleRecordings::SetUp()
record_config_sub.wired_sync_mode = K4A_WIRED_SYNC_MODE_SUBORDINATE;
record_config_sub.subordinate_delay_off_master_usec = 10000; // 10ms
k4a_device_configuration_t record_config_color_only = record_config_full;
record_config_color_only.depth_mode = K4A_DEPTH_MODE_OFF;
k4a_device_configuration_t record_config_depth_only = record_config_full;
record_config_depth_only.color_resolution = K4A_COLOR_RESOLUTION_OFF;
{
k4a_record_t handle = NULL;
k4a_result_t result = k4a_record_create("record_test_empty.mkv", NULL, record_config_empty, &handle);
@ -57,7 +63,7 @@ void SampleRecordings::SetUp()
uint64_t imu_timestamp = 1150;
uint32_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(record_config_full.camera_fps);
k4a_capture_t capture = NULL;
for (int i = 0; i < 100; i++)
for (size_t i = 0; i < test_frame_count; i++)
{
capture = create_test_capture(timestamps,
record_config_full.color_format,
@ -100,7 +106,7 @@ void SampleRecordings::SetUp()
(uint64_t)record_config_delay.depth_delay_off_color_usec };
uint32_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(record_config_delay.camera_fps);
k4a_capture_t capture = NULL;
for (int i = 0; i < 100; i++)
for (size_t i = 0; i < test_frame_count; i++)
{
capture = create_test_capture(timestamps,
record_config_delay.color_format,
@ -120,7 +126,7 @@ void SampleRecordings::SetUp()
k4a_record_close(handle);
}
{
{ // Create a recording file with a subordinate delay off master
k4a_record_t handle = NULL;
k4a_result_t result = k4a_record_create("record_test_sub.mkv", NULL, record_config_sub, &handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
@ -128,7 +134,9 @@ void SampleRecordings::SetUp()
result = k4a_record_write_header(handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
uint64_t timestamps[3] = { 0, 0, 0 };
uint64_t timestamps[3] = { record_config_sub.subordinate_delay_off_master_usec,
record_config_sub.subordinate_delay_off_master_usec,
record_config_sub.subordinate_delay_off_master_usec };
k4a_capture_t capture = create_test_capture(timestamps,
record_config_sub.color_format,
record_config_sub.color_resolution,
@ -158,7 +166,7 @@ void SampleRecordings::SetUp()
uint64_t timestamps[3] = { 1000000, 1001000, 1001000 }; // Start recording at 1s
uint32_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(record_config_full.camera_fps);
for (int i = 0; i < 100; i++)
for (size_t i = 0; i < test_frame_count; i++)
{
// Create a known pattern of dropped / missing frames that can be tested against
// The pattern is repeated every 4 captures until the end of the file.
@ -208,6 +216,95 @@ void SampleRecordings::SetUp()
result = k4a_record_flush(handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
k4a_record_close(handle);
}
{ // Create a recording file with a start offset and all tracks enabled
k4a_record_t handle = NULL;
k4a_result_t result = k4a_record_create("record_test_offset.mkv", NULL, record_config_full, &handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
result = k4a_record_add_imu_track(handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
result = k4a_record_write_header(handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
uint64_t timestamps[3] = { 1000000, 1000000, 1000000 };
uint64_t imu_timestamp = 1001150;
uint32_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(record_config_delay.camera_fps);
k4a_capture_t capture = NULL;
for (size_t i = 0; i < test_frame_count; i++)
{
capture = create_test_capture(timestamps,
record_config_delay.color_format,
record_config_delay.color_resolution,
record_config_delay.depth_mode);
result = k4a_record_write_capture(handle, capture);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
k4a_capture_release(capture);
timestamps[0] += timestamp_delta;
timestamps[1] += timestamp_delta;
timestamps[2] += timestamp_delta;
while (imu_timestamp < timestamps[0])
{
k4a_imu_sample_t imu_sample = create_test_imu_sample(imu_timestamp);
result = k4a_record_write_imu_sample(handle, imu_sample);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
// Write IMU samples at ~1000 samples per second (this is an arbitrary rate for testing)
imu_timestamp += 1000; // 1ms
}
}
result = k4a_record_flush(handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
k4a_record_close(handle);
}
{ // Create a recording file with only the color camera enabled
k4a_record_t handle = NULL;
k4a_result_t result = k4a_record_create("record_test_color_only.mkv", NULL, record_config_color_only, &handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
result = k4a_record_write_header(handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
uint64_t timestamps[3] = { 0, 0, 0 };
k4a_capture_t capture = create_test_capture(timestamps,
record_config_color_only.color_format,
record_config_color_only.color_resolution,
record_config_color_only.depth_mode);
result = k4a_record_write_capture(handle, capture);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
k4a_capture_release(capture);
result = k4a_record_flush(handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
k4a_record_close(handle);
}
{ // Create a recording file with only the depth camera enabled
k4a_record_t handle = NULL;
k4a_result_t result = k4a_record_create("record_test_depth_only.mkv", NULL, record_config_depth_only, &handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
result = k4a_record_write_header(handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
uint64_t timestamps[3] = { 0, 0, 0 };
k4a_capture_t capture = create_test_capture(timestamps,
record_config_depth_only.color_format,
record_config_depth_only.color_resolution,
record_config_depth_only.depth_mode);
result = k4a_record_write_capture(handle, capture);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
k4a_capture_release(capture);
result = k4a_record_flush(handle);
ASSERT_EQ(result, K4A_RESULT_SUCCEEDED);
k4a_record_close(handle);
}
}
@ -219,4 +316,7 @@ void SampleRecordings::TearDown()
ASSERT_EQ(std::remove("record_test_delay.mkv"), 0);
ASSERT_EQ(std::remove("record_test_skips.mkv"), 0);
ASSERT_EQ(std::remove("record_test_sub.mkv"), 0);
ASSERT_EQ(std::remove("record_test_offset.mkv"), 0);
ASSERT_EQ(std::remove("record_test_color_only.mkv"), 0);
ASSERT_EQ(std::remove("record_test_depth_only.mkv"), 0);
}

Просмотреть файл

@ -24,6 +24,13 @@ static const char *const fps_names[] = { "K4A_FRAMES_PER_SECOND_5",
"K4A_FRAMES_PER_SECOND_15",
"K4A_FRAMES_PER_SECOND_30" };
// Testing values
static const uint32_t test_depth_width = 640;
static const uint32_t test_depth_height = 576;
static const uint32_t test_camera_fps = 30;
static const uint32_t test_timestamp_delta_usec = 33333;
static const size_t test_frame_count = 100;
k4a_capture_t create_test_capture(uint64_t timestamp_us[3],
k4a_image_format_t color_format,
k4a_color_resolution_t resolution,
@ -33,6 +40,7 @@ bool validate_test_capture(k4a_capture_t capture,
k4a_image_format_t color_format,
k4a_color_resolution_t resolution,
k4a_depth_mode_t mode);
k4a_image_t
create_test_image(uint64_t timestamp_us, k4a_image_format_t format, uint32_t width, uint32_t height, uint32_t stride);
bool validate_test_image(k4a_image_t image,
@ -41,6 +49,7 @@ bool validate_test_image(k4a_image_t image,
uint32_t width,
uint32_t height,
uint32_t stride);
k4a_imu_sample_t create_test_imu_sample(uint64_t timestamp_us);
bool validate_imu_sample(k4a_imu_sample_t &imu_sample, uint64_t timestamp_us);
bool validate_null_imu_sample(k4a_imu_sample_t &imu_sample);

Просмотреть файл

@ -112,8 +112,7 @@ K4ARecordingDockControl::K4ARecordingDockControl(std::string &&path, k4a::playba
m_subordinateDelayOffMasterUsec = m_recordConfiguration.subordinate_delay_off_master_usec;
m_startTimestampOffsetUsec = m_recordConfiguration.start_timestamp_offset_usec;
m_playbackThreadState.TimestampOffset = std::chrono::microseconds(m_startTimestampOffsetUsec);
m_recordingLengthUsec = static_cast<uint64_t>(recording.get_last_timestamp().count());
m_recordingLengthUsec = static_cast<uint64_t>(recording.get_recording_length().count());
// Device info
//
@ -361,12 +360,6 @@ bool K4ARecordingDockControl::PlaybackThreadFn(PlaybackThreadState *state)
break;
}
// Update the timestamps on the IMU samples using the timing data embedded in the recording
// so we show comparable timestamps when playing back synchronized recordings
//
nextImuSample.acc_timestamp_usec += static_cast<uint64_t>(state->TimestampOffset.count());
nextImuSample.gyro_timestamp_usec += static_cast<uint64_t>(state->TimestampOffset.count());
state->ImuDataSource.NotifyObservers(nextImuSample);
}
}
@ -392,7 +385,7 @@ bool K4ARecordingDockControl::PlaybackThreadFn(PlaybackThreadState *state)
{
if (image)
{
image.set_timestamp(image.get_device_timestamp() + state->TimestampOffset);
image.set_timestamp(image.get_device_timestamp());
}
}

Просмотреть файл

@ -54,7 +54,6 @@ private:
// Constant state (expected to be set once, accessible without synchronization)
//
std::chrono::microseconds TimePerFrame;
std::chrono::microseconds TimestampOffset = std::chrono::microseconds(0);
// Recording state
//