/** \file matroska_read.h * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. * Kinect For Azure Record SDK. * Internal MKV Reading Helpers */ #ifndef RECORD_READ_H #define RECORD_READ_H #include #include #include #include #include namespace k4arecord { // The depth mode string for legacy recordings static const std::pair legacy_depth_modes[] = { { K4A_DEPTH_MODE_NFOV_2X2BINNED, "NFOV_2x2BINNED" }, { K4A_DEPTH_MODE_WFOV_2X2BINNED, "WFOV_2x2BINNED" } }; typedef struct _cluster_info_t { // The cluster size will be 0 until the actual cluster has been read from disk. // If cluster size is 0, the timestamp is not guaranteed to be the start of the cluster // populate_cluster_info() will update the cluster_size and timestamp to the real values. uint64_t timestamp_ns = 0; uint64_t file_offset = 0; uint64_t cluster_size = 0; std::weak_ptr cluster; bool next_known = false; struct _cluster_info_t *next = NULL; struct _cluster_info_t *previous = NULL; } cluster_info_t; // The cluster cache is a sparse linked-list index that may contain gaps until real data has been read from disk. // The list is initialized with metadata from the Cues block, which is used as a hint for seeking in the file. // Once it is known that no gap is present between indexed clusters, next_known is set to true. typedef std::unique_ptr> cluster_cache_t; // A pointer to a cluster that is still being loaded from disk. typedef std::shared_future> future_cluster_t; typedef struct _loaded_cluster_t { cluster_info_t *cluster_info = NULL; std::shared_ptr cluster; #if CLUSTER_READ_AHEAD_COUNT // Pointers to previous and next clusters to keep them preloaded in memory. future_cluster_t previous_clusters[CLUSTER_READ_AHEAD_COUNT]; future_cluster_t next_clusters[CLUSTER_READ_AHEAD_COUNT]; #endif } loaded_cluster_t; typedef struct _block_info_t { struct _track_reader_t *reader = NULL; std::shared_ptr cluster; libmatroska::KaxInternalBlock *block = NULL; uint64_t timestamp_ns = 0; // The timestamp of the block as written in the file. uint64_t sync_timestamp_ns = 0; // The timestamp of the block, including sychronization offsets. uint64_t block_duration_ns = 0; // If the block is a KaxBlockGroup, otherwise 0. int index = -1; // Index of the block element within the cluster. int sub_index = -1; // Index of the current buffer within the block. } block_info_t; typedef struct _track_reader_t { std::string track_name; uint64_t track_uid = 0; libmatroska::KaxTrackEntry *track = nullptr; std::string codec_id; std::vector codec_private; std::shared_ptr current_block; uint64_t frame_period_ns = 0; uint64_t sync_delay_ns = 0; track_type type = track_video; // Fields specific to video track uint32_t width = 0; uint32_t height = 0; uint32_t stride = 0; k4a_image_format_t format = K4A_IMAGE_FORMAT_CUSTOM; } track_reader_t; typedef struct _k4a_playback_context_t { const char *file_path; std::unique_ptr ebml_file; std::mutex io_lock; // Locks access to ebml_file bool file_closing; uint64_t timecode_scale; k4a_record_configuration_t record_config; k4a_image_format_t color_format_conversion; std::unique_ptr stream; std::unique_ptr segment; std::unique_ptr segment_info; std::unique_ptr tracks; std::unique_ptr cues; std::unique_ptr attachments; std::unique_ptr tags; libmatroska::KaxAttached *calibration_attachment; std::unique_ptr device_calibration; uint64_t sync_period_ns; uint64_t seek_timestamp_ns; std::shared_ptr seek_cluster; cluster_cache_t cluster_cache; std::recursive_mutex cache_lock; // Locks modification of cluster_cache track_reader_t *color_track = nullptr; track_reader_t *depth_track = nullptr; track_reader_t *ir_track = nullptr; track_reader_t *imu_track = nullptr; std::map track_map; uint64_t segment_info_offset; uint64_t first_cluster_offset; uint64_t tracks_offset; uint64_t cues_offset; uint64_t attachments_offset; uint64_t tags_offset; uint64_t last_file_timestamp_ns; // Relative to start of file. // Stats uint64_t seek_count, load_count, cache_hits; } k4a_playback_context_t; K4A_DECLARE_CONTEXT(k4a_playback_t, k4a_playback_context_t); typedef struct _k4a_playback_data_block_context_t { uint64_t device_timestamp_usec; std::vector data_block; } k4a_playback_data_block_context_t; K4A_DECLARE_CONTEXT(k4a_playback_data_block_t, k4a_playback_data_block_context_t); std::unique_ptr next_child(k4a_playback_context_t *context, EbmlElement *parent); k4a_result_t skip_element(k4a_playback_context_t *context, EbmlElement *element); void match_ebml_id(k4a_playback_context_t *context, EbmlId &id, uint64_t offset); bool seek_info_ready(k4a_playback_context_t *context); k4a_result_t parse_mkv(k4a_playback_context_t *context); k4a_result_t populate_cluster_cache(k4a_playback_context_t *context); k4a_result_t parse_recording_config(k4a_playback_context_t *context); k4a_result_t read_bitmap_info_header(track_reader_t *track); void reset_seek_pointers(k4a_playback_context_t *context, uint64_t seek_timestamp_ns); k4a_result_t parse_tracks(k4a_playback_context_t *context); track_reader_t *find_track(k4a_playback_context_t *context, const char *name, const char *tag_name); bool check_track_reader_is_builtin(k4a_playback_context_t *context, track_reader_t *track_reader); track_reader_t *get_track_reader_by_name(k4a_playback_context_t *context, std::string track_name); libmatroska::KaxTag *get_tag(k4a_playback_context_t *context, const char *name); std::string get_tag_string(libmatroska::KaxTag *tag); libmatroska::KaxAttached *get_attachment_by_name(k4a_playback_context_t *context, const char *file_name); libmatroska::KaxAttached *get_attachment_by_tag(k4a_playback_context_t *context, const char *tag_name); k4a_result_t seek_offset(k4a_playback_context_t *context, uint64_t offset); void populate_cluster_info(k4a_playback_context_t *context, std::shared_ptr &cluster, cluster_info_t *cluster_info); cluster_info_t *find_cluster(k4a_playback_context_t *context, uint64_t timestamp_ns); cluster_info_t *next_cluster(k4a_playback_context_t *context, cluster_info_t *current, bool next); std::shared_ptr load_cluster_internal(k4a_playback_context_t *context, cluster_info_t *cluster_info); std::shared_ptr load_cluster(k4a_playback_context_t *context, cluster_info_t *cluster_info); std::shared_ptr load_next_cluster(k4a_playback_context_t *context, loaded_cluster_t *current_cluster, bool next); uint64_t estimate_block_timestamp_ns(std::shared_ptr &block); std::shared_ptr find_block(k4a_playback_context_t *context, track_reader_t *reader, uint64_t timestamp_ns); std::shared_ptr next_block(k4a_playback_context_t *context, block_info_t *current, bool next); k4a_result_t convert_block_to_image(k4a_playback_context_t *context, block_info_t *in_block, k4a_image_t *image_out, k4a_image_format_t target_format); k4a_result_t new_capture(k4a_playback_context_t *context, block_info_t *block, k4a_capture_t *capture_handle); k4a_stream_result_t get_capture(k4a_playback_context_t *context, k4a_capture_t *capture_handle, bool next); k4a_stream_result_t get_imu_sample(k4a_playback_context_t *context, k4a_imu_sample_t *imu_sample, bool next); k4a_stream_result_t get_data_block(k4a_playback_context_t *context, track_reader_t *track_reader, k4a_playback_data_block_t *data_block_handle, bool next); // Template helper functions template T *read_element(k4a_playback_context_t *context, EbmlElement *element) { try { int upper_level = 0; EbmlElement *dummy = nullptr; T *typed_element = static_cast(element); typed_element->Read(*context->stream, T::ClassInfos.Context, upper_level, dummy, true); return typed_element; } catch (std::ios_base::failure &e) { LOG_ERROR("Failed to read element %s in recording '%s': %s", T::ClassInfos.GetName(), context->file_path, e.what()); return nullptr; } } /** * Find the next element of type T at the current file offset. * If \p search is true, this function will keep reading elements until an element of type T is found or EOF is reached. * If \p search is false, this function will only return an element if it exists at the current file offset. * * Example usage: find_next(context, true); */ template std::unique_ptr find_next(k4a_playback_context_t *context, bool search = false) { try { EbmlElement *element = nullptr; do { if (element) { if (!element->IsFiniteSize()) { LOG_ERROR("Failed to read recording: Element Id '%x' has unknown size", EbmlId(*element).GetValue()); delete element; return nullptr; } element->SkipData(*context->stream, element->Generic().Context); delete element; element = nullptr; } if (!element) { element = context->stream->FindNextID(T::ClassInfos, UINT64_MAX); } if (!search) { break; } } while (element && EbmlId(*element) != T::ClassInfos.GlobalId); if (!element) { if (!search) { LOG_ERROR("Failed to read recording: Element Id '%x' not found", T::ClassInfos.GlobalId.GetValue()); } return nullptr; } else if (EbmlId(*element) != T::ClassInfos.GlobalId) { LOG_ERROR("Failed to read recording: Expected element %s (id %x), found id '%x'", T::ClassInfos.GetName(), T::ClassInfos.GlobalId.GetValue(), EbmlId(*element).GetValue()); delete element; return nullptr; } return std::unique_ptr(static_cast(element)); } catch (std::ios_base::failure &e) { LOG_ERROR("Failed to find %s in recording '%s': %s", T::ClassInfos.GetName(), context->file_path, e.what()); return nullptr; } } template k4a_result_t read_offset(k4a_playback_context_t *context, std::unique_ptr &element_out, uint64_t offset) { RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, context == NULL); RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, offset == 0); RETURN_IF_ERROR(seek_offset(context, offset)); element_out = find_next(context); if (element_out) { if (read_element(context, element_out.get()) == NULL) { LOG_ERROR("Failed to read element: %s at offset %llu", typeid(T).name(), offset); return K4A_RESULT_FAILED; } return K4A_RESULT_SUCCEEDED; } else { LOG_ERROR("Element not found at offset: %s at offset %llu", typeid(T).name(), offset); return K4A_RESULT_FAILED; } } template bool check_element_type(EbmlElement *element, T **out) { if (EbmlId(*element) == T::ClassInfos.GlobalId) { *out = static_cast(element); return true; } *out = nullptr; return false; } } // namespace k4arecord #endif /* RECORD_READ_H */