245 lines
6.7 KiB
C
245 lines
6.7 KiB
C
/*
|
|
* Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
|
|
* Licensed under the MIT license. See LICENSE file in the project root for details.
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
/*************************************************************************************************/
|
|
/* Minimal DRP2 recording player */
|
|
/*************************************************************************************************/
|
|
|
|
|
|
|
|
/*************************************************************************************************/
|
|
/* Includes */
|
|
/*************************************************************************************************/
|
|
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "_compat.h"
|
|
#include "_time_utils.h"
|
|
#include "datoviz/drp2.h"
|
|
|
|
|
|
|
|
/*************************************************************************************************/
|
|
/* Functions */
|
|
/*************************************************************************************************/
|
|
|
|
/**
|
|
* Print command-line usage.
|
|
*
|
|
* @param argv0 executable path
|
|
*/
|
|
static void _usage(const char* argv0)
|
|
{
|
|
dvz_fprintf(
|
|
stderr,
|
|
"usage: %s [--fast] recording.dvzr\n"
|
|
"\n"
|
|
"Replays a DRP2 recording through the semantic runtime.\n"
|
|
"Default playback is paced by recorded presentation timestamps.\n"
|
|
"Use --fast to execute frames without timestamp waits.\n",
|
|
argv0 != NULL ? argv0 : "dvz_drp2_player");
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Sleep until the recorded presentation timestamp unless fast mode is enabled.
|
|
*
|
|
* @param clock playback clock
|
|
* @param frame recorded frame metadata
|
|
* @param fast whether playback is unpaced
|
|
*/
|
|
static void _pace_frame(DvzClock* clock, const DvzDrp2RecordedFrame* frame, bool fast)
|
|
{
|
|
if (clock == NULL || frame == NULL || fast || frame->t_present <= 0)
|
|
return;
|
|
|
|
double now = dvz_clock_get(clock);
|
|
double delay = frame->t_present - now;
|
|
if (delay <= 0)
|
|
return;
|
|
|
|
double delay_us = delay * 1000000.0;
|
|
int sleep_us = delay_us > (double)INT_MAX ? INT_MAX : (int)delay_us;
|
|
dvz_sleep_us(sleep_us);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Warn when a recording contains ABI-local raw fallback command blobs.
|
|
*
|
|
* @param path recording directory path
|
|
*/
|
|
static void _warn_raw_fallbacks(const char* path)
|
|
{
|
|
if (path == NULL)
|
|
return;
|
|
|
|
char stream_path[4096] = {0};
|
|
int written = snprintf(stream_path, sizeof(stream_path), "%s/stream.jsonl", path);
|
|
if (written <= 0 || (size_t)written >= sizeof(stream_path))
|
|
return;
|
|
|
|
FILE* fp = fopen(stream_path, "rb");
|
|
if (fp == NULL)
|
|
return;
|
|
|
|
uint32_t count = 0;
|
|
uint32_t first_index = 0;
|
|
int first_type = 0;
|
|
char line[4096] = {0};
|
|
while (fgets(line, sizeof(line), fp) != NULL)
|
|
{
|
|
if (strstr(line, "\"command_blob\":\"") == NULL)
|
|
continue;
|
|
if (count == 0)
|
|
{
|
|
(void)sscanf(
|
|
line, "{\"type\":\"command\",\"index\":%" SCNu32 ",\"cmd_type\":%d",
|
|
&first_index, &first_type);
|
|
}
|
|
count++;
|
|
}
|
|
fclose(fp);
|
|
|
|
if (count == 0)
|
|
return;
|
|
|
|
dvz_fprintf(
|
|
stderr,
|
|
"dvz_drp2_player: warning: recording uses %" PRIu32
|
|
" ABI-local raw fallback command(s); portability is not guaranteed\n",
|
|
count);
|
|
dvz_fprintf(
|
|
stderr, "dvz_drp2_player: first raw fallback at command %" PRIu32 " type %d\n",
|
|
first_index, first_type);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Play a recording frame by frame through the semantic DRP2 runtime.
|
|
*
|
|
* @param recording loaded recording
|
|
* @param path recording directory path
|
|
* @param fast whether playback is unpaced
|
|
* @return process exit code
|
|
*/
|
|
static int _play_recording(const DvzDrp2Recording* recording, const char* path, bool fast)
|
|
{
|
|
if (recording == NULL)
|
|
return 1;
|
|
|
|
_warn_raw_fallbacks(path);
|
|
|
|
#if defined(DVZ_DRP2_HAS_VKLITE) && DVZ_DRP2_HAS_VKLITE
|
|
DvzDrp2RuntimeConfig cfg = dvz_drp2_runtime_vklite_config(NULL, NULL);
|
|
cfg.semantic_only = true;
|
|
DvzDrp2Runtime* runtime = dvz_drp2_runtime_vklite(&cfg);
|
|
if (runtime == NULL)
|
|
{
|
|
dvz_fprintf(stderr, "dvz_drp2_player: failed to create semantic runtime\n");
|
|
return 1;
|
|
}
|
|
|
|
uint32_t frame_count = dvz_drp2_recording_frame_count(recording);
|
|
DvzClock clock = dvz_clock();
|
|
for (uint32_t i = 0; i < frame_count; i++)
|
|
{
|
|
const DvzDrp2RecordedFrame* frame = dvz_drp2_recording_frame(recording, i);
|
|
if (frame == NULL)
|
|
{
|
|
dvz_fprintf(stderr, "dvz_drp2_player: missing frame %" PRIu32 "\n", i);
|
|
dvz_drp2_runtime_destroy(runtime);
|
|
return 1;
|
|
}
|
|
|
|
_pace_frame(&clock, frame, fast);
|
|
DvzDrp2ValidationResult result =
|
|
dvz_drp2_recording_execute_frame(recording, runtime, i);
|
|
if (!result.ok)
|
|
{
|
|
dvz_fprintf(
|
|
stderr,
|
|
"dvz_drp2_player: frame %" PRIu32 " failed at command %" PRIu32
|
|
" with code %d\n",
|
|
i, result.command_index, (int)result.code);
|
|
dvz_drp2_runtime_destroy(runtime);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
dvz_fprintf(
|
|
stdout, "dvz_drp2_player: replayed %" PRIu32 " frame(s) (%s)\n", frame_count,
|
|
fast ? "fast" : "paced");
|
|
dvz_drp2_runtime_destroy(runtime);
|
|
return 0;
|
|
#else
|
|
(void)fast;
|
|
dvz_fprintf(stderr, "dvz_drp2_player: DRP2 vklite runtime is not available\n");
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Program entry point.
|
|
*
|
|
* @param argc argument count
|
|
* @param argv argument vector
|
|
* @return process exit code
|
|
*/
|
|
int main(int argc, char** argv)
|
|
{
|
|
bool fast = false;
|
|
const char* path = NULL;
|
|
|
|
for (int i = 1; i < argc; i++)
|
|
{
|
|
if (strcmp(argv[i], "--fast") == 0)
|
|
{
|
|
fast = true;
|
|
continue;
|
|
}
|
|
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
|
|
{
|
|
_usage(argv[0]);
|
|
return 0;
|
|
}
|
|
if (path == NULL)
|
|
{
|
|
path = argv[i];
|
|
continue;
|
|
}
|
|
_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
if (path == NULL)
|
|
{
|
|
_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
DvzDrp2Recording* recording = dvz_drp2_recording_open(path);
|
|
if (recording == NULL)
|
|
{
|
|
dvz_fprintf(stderr, "dvz_drp2_player: failed to open %s\n", path);
|
|
return 1;
|
|
}
|
|
|
|
int rc = _play_recording(recording, path, fast);
|
|
dvz_drp2_recording_close(recording);
|
|
return rc;
|
|
}
|