The open source OpenXR runtime
at prediction-2 584 lines 12 kB view raw
1// Copyright 2020-2021, Collabora, Ltd. 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief Video file frameserver implementation 6 * @author Christoph Haag <christoph.haag@collabora.com> 7 * @author Pete Black <pblack@collabora.com> 8 * @author Jakob Bornecrantz <jakob@collabora.com> 9 * @ingroup drv_vf 10 */ 11 12#include "vf_interface.h" // IWYU pragma: associated 13 14#include "os/os_time.h" 15#include "os/os_threading.h" 16 17#include "util/u_trace_marker.h" 18#include "util/u_var.h" 19#include "util/u_misc.h" 20#include "util/u_debug.h" 21#include "util/u_format.h" 22#include "util/u_frame.h" 23#include "util/u_logging.h" 24#include "util/u_trace_marker.h" 25 26 27#include <stdio.h> 28#include <assert.h> 29 30#include <glib.h> 31#include <gst/gst.h> 32#include <gst/app/gstappsink.h> 33#include <gst/video/video-frame.h> 34 35 36/* 37 * 38 * Defines. 39 * 40 */ 41 42/* 43 * 44 * Printing functions. 45 * 46 */ 47 48#define VF_TRACE(d, ...) U_LOG_IFL_T(d->log_level, __VA_ARGS__) 49#define VF_DEBUG(d, ...) U_LOG_IFL_D(d->log_level, __VA_ARGS__) 50#define VF_INFO(d, ...) U_LOG_IFL_I(d->log_level, __VA_ARGS__) 51#define VF_WARN(d, ...) U_LOG_IFL_W(d->log_level, __VA_ARGS__) 52#define VF_ERROR(d, ...) U_LOG_IFL_E(d->log_level, __VA_ARGS__) 53 54DEBUG_GET_ONCE_LOG_OPTION(vf_log, "VF_LOG", U_LOGGING_WARN) 55 56/*! 57 * A frame server operating on a video file. 58 * 59 * @implements xrt_frame_node 60 * @implements xrt_fs 61 */ 62struct vf_fs 63{ 64 struct xrt_fs base; 65 66 struct os_thread_helper play_thread; 67 68 GMainLoop *loop; 69 GstElement *source; 70 GstElement *testsink; 71 bool got_sample; 72 int width; 73 int height; 74 enum xrt_format format; 75 enum xrt_stereo_format stereo_format; 76 77 struct xrt_frame_node node; 78 79 struct 80 { 81 bool extended_format; 82 bool timeperframe; 83 } has; 84 85 enum xrt_fs_capture_type capture_type; 86 struct xrt_frame_sink *sink; 87 88 uint32_t selected; 89 90 struct xrt_fs_capture_parameters capture_params; 91 92 bool is_configured; 93 bool is_running; 94 enum u_logging_level log_level; 95}; 96 97/*! 98 * Frame wrapping a GstSample/GstBuffer. 99 * 100 * @implements xrt_frame 101 */ 102struct vf_frame 103{ 104 struct xrt_frame base; 105 106 GstSample *sample; 107 108 GstVideoFrame frame; 109}; 110 111 112/* 113 * 114 * Cast helpers. 115 * 116 */ 117 118/*! 119 * Cast to derived type. 120 */ 121static inline struct vf_fs * 122vf_fs(struct xrt_fs *xfs) 123{ 124 return (struct vf_fs *)xfs; 125} 126 127/*! 128 * Cast to derived type. 129 */ 130static inline struct vf_frame * 131vf_frame(struct xrt_frame *xf) 132{ 133 return (struct vf_frame *)xf; 134} 135 136 137/* 138 * 139 * Frame methods. 140 * 141 */ 142 143static void 144vf_frame_destroy(struct xrt_frame *xf) 145{ 146 SINK_TRACE_MARKER(); 147 148 struct vf_frame *vff = vf_frame(xf); 149 150 gst_video_frame_unmap(&vff->frame); 151 152 if (vff->sample != NULL) { 153 gst_sample_unref(vff->sample); 154 vff->sample = NULL; 155 } 156 157 free(vff); 158} 159 160 161/* 162 * 163 * Misc helper functions 164 * 165 */ 166 167 168static void 169vf_fs_frame(struct vf_fs *vid, GstSample *sample) 170{ 171 SINK_TRACE_MARKER(); 172 173 // Noop. 174 if (!vid->sink) { 175 return; 176 } 177 178 GstVideoInfo info; 179 GstBuffer *buffer; 180 GstCaps *caps; 181 buffer = gst_sample_get_buffer(sample); 182 caps = gst_sample_get_caps(sample); 183 184 gst_video_info_init(&info); 185 gst_video_info_from_caps(&info, caps); 186 187 static int seq = 0; 188 struct vf_frame *vff = U_TYPED_CALLOC(struct vf_frame); 189 190 if (!gst_video_frame_map(&vff->frame, &info, buffer, GST_MAP_READ)) { 191 VF_ERROR(vid, "Failed to map frame %d", seq); 192 // Yes, we should do this here because we don't want the destroy function to run. 193 free(vff); 194 return; 195 } 196 197 // We now want to hold onto the sample for as long as the frame lives. 198 gst_sample_ref(sample); 199 vff->sample = sample; 200 201 // Hardcoded first plane. 202 int plane = 0; 203 204 struct xrt_frame *xf = &vff->base; 205 xf->reference.count = 1; 206 xf->destroy = vf_frame_destroy; 207 xf->width = vid->width; 208 xf->height = vid->height; 209 xf->format = vid->format; 210 xf->stride = info.stride[plane]; 211 xf->data = vff->frame.data[plane]; 212 xf->stereo_format = vid->stereo_format; 213 xf->size = info.size; 214 xf->source_id = vid->base.source_id; 215 216 //! @todo Proper sequence number and timestamp. 217 xf->source_sequence = seq; 218 xf->timestamp = os_monotonic_get_ns(); 219 220 xrt_sink_push_frame(vid->sink, &vff->base); 221 222 xrt_frame_reference(&xf, NULL); 223 vff = NULL; 224 225 seq++; 226} 227 228static GstFlowReturn 229on_new_sample_from_sink(GstElement *elt, struct vf_fs *vid) 230{ 231 SINK_TRACE_MARKER(); 232 GstSample *sample; 233 sample = gst_app_sink_pull_sample(GST_APP_SINK(elt)); 234 235 if (!vid->got_sample) { 236 gint width; 237 gint height; 238 239 GstCaps *caps = gst_sample_get_caps(sample); 240 GstStructure *structure = gst_caps_get_structure(caps, 0); 241 242 gst_structure_get_int(structure, "width", &width); 243 gst_structure_get_int(structure, "height", &height); 244 245 VF_DEBUG(vid, "video size is %dx%d", width, height); 246 vid->got_sample = true; 247 vid->width = width; 248 vid->height = height; 249 250 // first sample is only used for getting metadata 251 return GST_FLOW_OK; 252 } 253 254 // Takes ownership of the sample. 255 vf_fs_frame(vid, sample); 256 257 // Done with sample now. 258 gst_sample_unref(sample); 259 260 return GST_FLOW_OK; 261} 262 263static void 264print_gst_error(GstMessage *message) 265{ 266 GError *err = NULL; 267 gchar *dbg_info = NULL; 268 269 gst_message_parse_error(message, &err, &dbg_info); 270 U_LOG_E("ERROR from element %s: %s", GST_OBJECT_NAME(message->src), err->message); 271 U_LOG_E("Debugging info: %s", (dbg_info) ? dbg_info : "none"); 272 g_error_free(err); 273 g_free(dbg_info); 274} 275 276static gboolean 277on_source_message(GstBus *bus, GstMessage *message, struct vf_fs *vid) 278{ 279 /* nil */ 280 switch (GST_MESSAGE_TYPE(message)) { 281 case GST_MESSAGE_EOS: 282 VF_DEBUG(vid, "Finished playback."); 283 g_main_loop_quit(vid->loop); 284 break; 285 case GST_MESSAGE_ERROR: 286 VF_ERROR(vid, "Received error."); 287 print_gst_error(message); 288 g_main_loop_quit(vid->loop); 289 break; 290 default: break; 291 } 292 return TRUE; 293} 294 295static void * 296vf_fs_mainloop(void *ptr) 297{ 298 SINK_TRACE_MARKER(); 299 300 struct vf_fs *vid = (struct vf_fs *)ptr; 301 302 VF_DEBUG(vid, "Let's run!"); 303 g_main_loop_run(vid->loop); 304 VF_DEBUG(vid, "Going out!"); 305 306 gst_object_unref(vid->testsink); 307 gst_element_set_state(vid->source, GST_STATE_NULL); 308 309 310 gst_object_unref(vid->source); 311 g_main_loop_unref(vid->loop); 312 313 return NULL; 314} 315 316 317/* 318 * 319 * Frame server methods. 320 * 321 */ 322 323static bool 324vf_fs_enumerate_modes(struct xrt_fs *xfs, struct xrt_fs_mode **out_modes, uint32_t *out_count) 325{ 326 struct vf_fs *vid = vf_fs(xfs); 327 328 struct xrt_fs_mode *modes = U_TYPED_ARRAY_CALLOC(struct xrt_fs_mode, 1); 329 if (modes == NULL) { 330 return false; 331 } 332 333 modes[0].width = vid->width; 334 modes[0].height = vid->height; 335 modes[0].format = vid->format; 336 modes[0].stereo_format = vid->stereo_format; 337 338 *out_modes = modes; 339 *out_count = 1; 340 341 return true; 342} 343 344static bool 345vf_fs_configure_capture(struct xrt_fs *xfs, struct xrt_fs_capture_parameters *cp) 346{ 347 // struct vf_fs *vid = vf_fs(xfs); 348 //! @todo 349 return false; 350} 351 352static bool 353vf_fs_stream_start(struct xrt_fs *xfs, 354 struct xrt_frame_sink *xs, 355 enum xrt_fs_capture_type capture_type, 356 uint32_t descriptor_index) 357{ 358 struct vf_fs *vid = vf_fs(xfs); 359 360 vid->sink = xs; 361 vid->is_running = true; 362 vid->capture_type = capture_type; 363 vid->selected = descriptor_index; 364 365 gst_element_set_state(vid->source, GST_STATE_PLAYING); 366 367 VF_TRACE(vid, "info: Started!"); 368 369 // we're off to the races! 370 return true; 371} 372 373static bool 374vf_fs_stream_stop(struct xrt_fs *xfs) 375{ 376 struct vf_fs *vid = vf_fs(xfs); 377 378 if (!vid->is_running) { 379 return true; 380 } 381 382 vid->is_running = false; 383 gst_element_set_state(vid->source, GST_STATE_PAUSED); 384 385 return true; 386} 387 388static bool 389vf_fs_is_running(struct xrt_fs *xfs) 390{ 391 struct vf_fs *vid = vf_fs(xfs); 392 393 GstState current = GST_STATE_NULL; 394 GstState pending; 395 gst_element_get_state(vid->source, &current, &pending, 0); 396 397 return current == GST_STATE_PLAYING; 398} 399 400static void 401vf_fs_destroy(struct vf_fs *vid) 402{ 403 g_main_loop_quit(vid->loop); 404 405 // Destroy also stops the thread. 406 os_thread_helper_destroy(&vid->play_thread); 407 408 free(vid); 409} 410 411 412/* 413 * 414 * Node methods. 415 * 416 */ 417 418static void 419vf_fs_node_break_apart(struct xrt_frame_node *node) 420{ 421 struct vf_fs *vid = container_of(node, struct vf_fs, node); 422 vf_fs_stream_stop(&vid->base); 423} 424 425static void 426vf_fs_node_destroy(struct xrt_frame_node *node) 427{ 428 struct vf_fs *vid = container_of(node, struct vf_fs, node); 429 vf_fs_destroy(vid); 430} 431 432 433/* 434 * 435 * Exported create functions and helper. 436 * 437 */ 438 439static struct xrt_fs * 440alloc_and_init_common(struct xrt_frame_context *xfctx, // 441 enum xrt_format format, // 442 enum xrt_stereo_format stereo_format, // 443 gchar *pipeline_string) // 444{ 445 struct vf_fs *vid = U_TYPED_CALLOC(struct vf_fs); 446 vid->got_sample = false; 447 vid->format = format; 448 vid->stereo_format = stereo_format; 449 450 GstBus *bus = NULL; 451 452 int ret = os_thread_helper_init(&vid->play_thread); 453 if (ret < 0) { 454 VF_ERROR(vid, "Failed to init thread"); 455 g_free(pipeline_string); 456 free(vid); 457 return NULL; 458 } 459 460 vid->loop = g_main_loop_new(NULL, FALSE); 461 VF_DEBUG(vid, "Pipeline: %s", pipeline_string); 462 463 vid->source = gst_parse_launch(pipeline_string, NULL); 464 g_free(pipeline_string); 465 466 if (vid->source == NULL) { 467 VF_ERROR(vid, "Bad source"); 468 g_main_loop_unref(vid->loop); 469 free(vid); 470 return NULL; 471 } 472 473 vid->testsink = gst_bin_get_by_name(GST_BIN(vid->source), "testsink"); 474 g_object_set(G_OBJECT(vid->testsink), "emit-signals", TRUE, "sync", TRUE, NULL); 475 g_signal_connect(vid->testsink, "new-sample", G_CALLBACK(on_new_sample_from_sink), vid); 476 477 bus = gst_element_get_bus(vid->source); 478 gst_bus_add_watch(bus, (GstBusFunc)on_source_message, vid); 479 gst_object_unref(bus); 480 481 ret = os_thread_helper_start(&vid->play_thread, vf_fs_mainloop, vid); 482 if (ret != 0) { 483 VF_ERROR(vid, "Failed to start thread '%i'", ret); 484 g_main_loop_unref(vid->loop); 485 free(vid); 486 return NULL; 487 } 488 489 // We need one sample to determine frame size. 490 VF_DEBUG(vid, "Waiting for frame"); 491 gst_element_set_state(vid->source, GST_STATE_PLAYING); 492 while (!vid->got_sample) { 493 os_nanosleep(100 * 1000 * 1000); 494 } 495 VF_DEBUG(vid, "Got first sample"); 496 gst_element_set_state(vid->source, GST_STATE_PAUSED); 497 498 vid->base.enumerate_modes = vf_fs_enumerate_modes; 499 vid->base.configure_capture = vf_fs_configure_capture; 500 vid->base.stream_start = vf_fs_stream_start; 501 vid->base.stream_stop = vf_fs_stream_stop; 502 vid->base.is_running = vf_fs_is_running; 503 vid->node.break_apart = vf_fs_node_break_apart; 504 vid->node.destroy = vf_fs_node_destroy; 505 vid->log_level = debug_get_log_option_vf_log(); 506 507 // It's now safe to add it to the context. 508 xrt_frame_context_add(xfctx, &vid->node); 509 510 // Start the variable tracking after we know what device we have. 511 // clang-format off 512 u_var_add_root(vid, "Video File Frameserver", true); 513 u_var_add_ro_text(vid, vid->base.name, "Card"); 514 u_var_add_log_level(vid, &vid->log_level, "Log Level"); 515 // clang-format on 516 517 return &(vid->base); 518} 519 520struct xrt_fs * 521vf_fs_videotestsource(struct xrt_frame_context *xfctx, uint32_t width, uint32_t height) 522{ 523 gst_init(0, NULL); 524 525 enum xrt_format format = XRT_FORMAT_R8G8B8; 526 enum xrt_stereo_format stereo_format = XRT_STEREO_FORMAT_NONE; 527 528 gchar *pipeline_string = g_strdup_printf( 529 "videotestsrc name=source ! " 530 "clockoverlay ! " 531 "videoconvert ! " 532 "videoscale ! " 533 "video/x-raw,format=RGB,width=%u,height=%u ! " 534 "appsink name=testsink", 535 width, height); 536 537 return alloc_and_init_common(xfctx, format, stereo_format, pipeline_string); 538} 539 540struct xrt_fs * 541vf_fs_open_file(struct xrt_frame_context *xfctx, const char *path) 542{ 543 if (path == NULL) { 544 U_LOG_E("No path given"); 545 return NULL; 546 } 547 548 gst_init(0, NULL); 549 550 if (!g_file_test(path, G_FILE_TEST_EXISTS)) { 551 U_LOG_E("File %s does not exist", path); 552 return NULL; 553 } 554 555#if 0 556 const gchar *caps = "video/x-raw,format=RGB"; 557 enum xrt_format format = XRT_FORMAT_R8G8B8; 558 enum xrt_stereo_format stereo_format = XRT_STEREO_FORMAT_NONE; 559#endif 560 561#if 0 562 // For hand tracking 563 const gchar *caps = "video/x-raw,format=RGB"; 564 enum xrt_format format = XRT_FORMAT_R8G8B8; 565 enum xrt_stereo_format stereo_format = XRT_STEREO_FORMAT_SBS; 566#endif 567 568#if 1 569 const gchar *caps = "video/x-raw,format=YUY2"; 570 enum xrt_format format = XRT_FORMAT_YUYV422; 571 enum xrt_stereo_format stereo_format = XRT_STEREO_FORMAT_SBS; 572#endif 573 574 gchar *loop = "false"; 575 576 gchar *pipeline_string = g_strdup_printf( 577 "multifilesrc location=\"%s\" loop=%s ! " 578 "decodebin ! " 579 "videoconvert ! " 580 "appsink caps=\"%s\" name=testsink", 581 path, loop, caps); 582 583 return alloc_and_init_common(xfctx, format, stereo_format, pipeline_string); 584}