From 1c7537e042223cb1797db7bcb2df93a5563754d1 Mon Sep 17 00:00:00 2001 From: Jens Koenen <koenen@vr.rwth-aachen.de> Date: Thu, 1 Dec 2022 15:17:26 +0100 Subject: [PATCH] When setting the command line flag --write-video, the raw video stream of an remote headset is stored on disk --- src/headset/remote_headset.cpp | 110 ++++++++++++++++++++++++--------- src/headset/remote_headset.hpp | 5 +- src/utility/command_parser.cpp | 14 +++++ src/utility/command_parser.hpp | 2 + src/vr_application.cpp | 2 +- 5 files changed, 102 insertions(+), 31 deletions(-) diff --git a/src/headset/remote_headset.cpp b/src/headset/remote_headset.cpp index 72456cbe..f7e4b94c 100644 --- a/src/headset/remote_headset.cpp +++ b/src/headset/remote_headset.cpp @@ -1,8 +1,9 @@ #include "remote_headset.hpp" #include "vr_application.hpp" -#include <imgui.h> -#include <glm/gtx/string_cast.hpp> +#include <imgui.h> +#include <filesystem> +#include <chrono> RemoteHeadset::RemoteHeadset() { @@ -119,33 +120,6 @@ bool RemoteHeadset::on_interface() ImGui::Separator(); - if(ImGui::Checkbox("Overlay", &this->overlay_enable)) - { - this->transport->send_overlay_config(this->overlay_enable); - } - - std::vector<std::string> graph_names; - std::vector<const char*> graph_name_pointers; - - std::unique_lock<std::mutex> lock(this->transport_mutex); - for (const Statistic::Ptr& statistic : this->statistic_list) - { - if (!statistic->is_log_only()) - { - graph_names.push_back(statistic->get_display_name()); - } - } - lock.unlock(); - - for (const std::string& graph_name : graph_names) - { - graph_name_pointers.push_back(graph_name.c_str()); - } - - ImGui::Combo("Overlay Graph", (int32_t*) &this->overlay_graph, graph_name_pointers.data(), graph_name_pointers.size()); - - ImGui::Separator(); - Encoder::Ptr encoder = this->encoders.front(); if (encoder->is_supported(ENCODER_SETTING_MODE_CONSTANT_BITRATE) && encoder->is_supported(ENCODER_SETTING_MODE_CONSTANT_QUALITY)) @@ -229,6 +203,33 @@ bool RemoteHeadset::on_interface() ImGui::Separator(); + if(ImGui::Checkbox("Overlay", &this->overlay_enable)) + { + this->transport->send_overlay_config(this->overlay_enable); + } + + std::vector<std::string> graph_names; + std::vector<const char*> graph_name_pointers; + + std::unique_lock<std::mutex> lock(this->transport_mutex); + for (const Statistic::Ptr& statistic : this->statistic_list) + { + if (!statistic->is_log_only()) + { + graph_names.push_back(statistic->get_display_name()); + } + } + lock.unlock(); + + for (const std::string& graph_name : graph_names) + { + graph_name_pointers.push_back(graph_name.c_str()); + } + + ImGui::Combo("Overlay Graph", (int32_t*) &this->overlay_graph, graph_name_pointers.data(), graph_name_pointers.size()); + + ImGui::Separator(); + lock.lock(); for (Statistic::Ptr statistic : this->statistic_list) { @@ -301,6 +302,11 @@ void RemoteHeadset::submit_frame(VkCommandBuffer command_buffer, VkImageLayout f { this->transport->send_frame_nal(frame_number, frame_id, transform_id, content); } + + if (!this->encoder_files.empty()) + { + this->encoder_files[frame_id].write((const char*)content.data(), content.size()); + } }); pass_timer->end_pass(command_buffer); @@ -498,11 +504,40 @@ bool RemoteHeadset::create_encoders() this->encoders[index] = encoder; } + if (this->get_application()->get_command_parser().should_write_video()) + { + if (!std::filesystem::exists(this->video_directory)) + { + std::filesystem::create_directory(this->video_directory); + } + + this->encoder_files.resize(this->frame_id_count); + + for (uint32_t index = 0; index < this->encoder_files.size(); index++) + { + std::string file_name = this->video_directory + "/" + build_file_name(index) + ".h264"; + + this->encoder_files[index].open(file_name, std::ios::out | std::ios::binary); + + if (!this->encoder_files[index].good()) + { + lava::log()->error("Can't create file '" + file_name + "' for video capture!"); + + return false; + } + } + } + return true; } void RemoteHeadset::destroy_encoders() { + for (uint32_t index = 0; index < this->encoder_files.size(); index++) + { + this->encoder_files[index].close(); + } + for (uint32_t index = 0; index < this->encoders.size(); index++) { if (this->encoders[index] != nullptr) @@ -719,6 +754,23 @@ void RemoteHeadset::on_encode_error() lava::log()->error("Remote Headset: Encode error detected!"); } +std::string RemoteHeadset::build_file_name(uint32_t frame_id) +{ + time_t system_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + tm local_time = *localtime(&system_time); + + std::string name = "video"; + name += std::to_string(frame_id); + name += "_" + std::to_string(local_time.tm_mday); + name += "-" + std::to_string(local_time.tm_mon + 1); + name += "-" + std::to_string(local_time.tm_year + 1900); + name += "_" + std::to_string(local_time.tm_hour); + name += "-" + std::to_string(local_time.tm_min); + name += "-" + std::to_string(local_time.tm_sec); + + return name; +} + bool RemoteHeadset::convert_strategy(StereoStrategyType strategy_type, RemoteStrategy& remote_strategy) { switch (strategy_type) diff --git a/src/headset/remote_headset.hpp b/src/headset/remote_headset.hpp index 4ba84800..02d97c42 100644 --- a/src/headset/remote_headset.hpp +++ b/src/headset/remote_headset.hpp @@ -1,11 +1,11 @@ #pragma once #include <liblava/lava.hpp> #include <glm/glm.hpp> +#include <fstream> #include <mutex> #include <vector> #include <array> #include <memory> -#include <chrono> #include "headset.hpp" #include "strategy/stereo_strategy.hpp" @@ -72,10 +72,12 @@ private: void on_transport_error(); void on_encode_error(); + static std::string build_file_name(uint32_t frame_id); static bool convert_strategy(StereoStrategyType strategy_type, RemoteStrategy& remote_strategy); private: const uint32_t server_port = 4000; + const std::string video_directory = "video_captures"; const float near_plane = 0.1f; const float far_plane = 1000.0f; @@ -110,6 +112,7 @@ private: std::vector<lava::image::ptr> framebuffers; std::vector<Encoder::Ptr> encoders; + std::vector<std::fstream> encoder_files; uint32_t encoder_mode = ENCODER_MODE_CONSTANT_QUALITY; uint32_t encoder_input_rate = 90; uint32_t encoder_key_rate = 90; diff --git a/src/utility/command_parser.cpp b/src/utility/command_parser.cpp index b1647bf8..77366016 100644 --- a/src/utility/command_parser.cpp +++ b/src/utility/command_parser.cpp @@ -20,6 +20,11 @@ bool CommandParser::parse_command(const argh::parser& cmd_line) this->write_frames = true; } + else if (flag == "write-video") + { + this->write_video = true; + } + else if (flag == "disable-indirect-lighting") { this->disable_indirect_lighting = true; @@ -315,6 +320,11 @@ bool CommandParser::should_write_frames() const return this->write_frames; } +bool CommandParser::should_write_video() const +{ + return this->write_video; +} + bool CommandParser::should_disable_indirect_lighting() const { return this->disable_indirect_lighting; @@ -340,8 +350,10 @@ void CommandParser::show_help() std::cout << " --stereo-strategy={strategy} The stereo strategy that should be used." << std::endl; std::cout << " Options: native-forward (default), native-deferred, multi-view, dpr" << std::endl; std::cout << " --transport={transport} The transport method that should be used." << std::endl; + std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; std::cout << " Options: udp (default)" << std::endl; std::cout << " --encoder={encoder} The encoder that should be used when a remote headset is used." << std::endl; + std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; std::cout << " Options: vulkan (default), nvidia" << std::endl; std::cout << " --benchmark Play animation once and close program after completion." << std::endl; std::cout << " If not set, the application runs indefinitely and the interface is enabled." << std::endl; @@ -359,6 +371,8 @@ void CommandParser::show_help() std::cout << " --show-companion-window Show companion window. If not set, writes to the companion window are ignored." << std::endl; std::cout << " --write-frames Write frames to files. If not set, frames will not be written to files." << std::endl; std::cout << " This parameter is only allowed during a benchmark." << std::endl; + std::cout << " --write-video Write video stream to file." << std::endl; + std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; std::cout << " --disable-indirect-lighting Disable indirect lighting and the allocation and computation of an indirect light cache." << std::endl; std::cout << " --disable-shadows Disable shadows and the allocation and computation of shadow maps." << std::endl; std::cout << " --help Show help information." << std::endl; diff --git a/src/utility/command_parser.hpp b/src/utility/command_parser.hpp index 77454760..6507df74 100644 --- a/src/utility/command_parser.hpp +++ b/src/utility/command_parser.hpp @@ -41,6 +41,7 @@ public: bool should_benchmark() const; bool should_show_companion_window() const; bool should_write_frames() const; + bool should_write_video() const; bool should_disable_indirect_lighting() const; bool should_disbale_shadows() const; @@ -74,6 +75,7 @@ private: bool benchmark = false; bool show_companion_window = false; bool write_frames = false; + bool write_video = false; bool disable_indirect_lighting = false; bool disable_shadows = false; }; \ No newline at end of file diff --git a/src/vr_application.cpp b/src/vr_application.cpp index ffb8fa3e..e387839c 100644 --- a/src/vr_application.cpp +++ b/src/vr_application.cpp @@ -416,7 +416,7 @@ bool VRApplication::on_create() return false; } - this->frame_capture = make_frame_capture("captures"); + this->frame_capture = make_frame_capture("frame_captures"); this->frame_capture->set_enabled(this->command_parser.should_write_frames()); if (!this->frame_capture->create(this->get_frame_count())) -- GitLab