mirror of
https://gitlab.freedesktop.org/monado/monado.git
synced 2025-02-03 12:28:07 +00:00
st/gui: Refactor out recording code into own window
This commit is contained in:
parent
9bfabdf706
commit
7a6412672d
src/xrt/state_trackers/gui
|
@ -18,6 +18,8 @@ set(GUI_SOURCE_FILES
|
|||
gui_scene_video.c
|
||||
gui_scene_tracking_overrides.c
|
||||
gui_stb.c
|
||||
gui_window_record.c
|
||||
gui_window_record.h
|
||||
../../../external/imgui/imgui/cimgui.cpp
|
||||
../../../external/imgui/imgui/cimgui.h
|
||||
../../../external/imgui/imgui/cimplot.cpp
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief Calibration gui scene.
|
||||
* @brief Recording scene gui.
|
||||
* @author Jakob Bornecrantz <jakob@collabora.com>
|
||||
* @ingroup gui
|
||||
*/
|
||||
|
@ -26,11 +26,6 @@
|
|||
#include "xrt/xrt_tracking.h"
|
||||
#include "xrt/xrt_frameserver.h"
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
#include "gstreamer/gst_sink.h"
|
||||
#include "gstreamer/gst_pipeline.h"
|
||||
#endif
|
||||
|
||||
#ifdef XRT_BUILD_DRIVER_VF
|
||||
#include "vf/vf_interface.h"
|
||||
#endif
|
||||
|
@ -39,33 +34,19 @@
|
|||
#include "depthai/depthai_interface.h"
|
||||
#endif
|
||||
|
||||
#include "gui_common.h"
|
||||
#include "gui_imgui.h"
|
||||
#include "gui_common.h"
|
||||
#include "gui_window_record.h"
|
||||
|
||||
#include "stb_image_write.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
enum bitrate
|
||||
{
|
||||
BITRATE_4096,
|
||||
BITRATE_2048,
|
||||
BITRATE_1024,
|
||||
};
|
||||
|
||||
enum pipeline
|
||||
struct camera_window
|
||||
{
|
||||
PIPELINE_SOFTWARE_FAST,
|
||||
PIPELINE_SOFTWARE_MEDIUM,
|
||||
PIPELINE_SOFTWARE_SLOW,
|
||||
PIPELINE_SOFTWARE_VERYSLOW,
|
||||
PIPELINE_VAAPI_H246,
|
||||
};
|
||||
|
||||
struct record_window
|
||||
{
|
||||
struct xrt_frame_sink sink;
|
||||
struct gui_record_window base;
|
||||
|
||||
struct
|
||||
{
|
||||
|
@ -92,334 +73,87 @@ struct record_window
|
|||
|
||||
char name[256];
|
||||
} camera;
|
||||
|
||||
struct
|
||||
{
|
||||
int scale;
|
||||
|
||||
struct xrt_frame_sink *sink;
|
||||
struct gui_ogl_texture *ogl;
|
||||
} texture;
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
struct
|
||||
{
|
||||
enum bitrate bitrate;
|
||||
|
||||
enum pipeline pipeline;
|
||||
|
||||
struct xrt_frame_context xfctx;
|
||||
|
||||
//! When not null we are recording.
|
||||
struct xrt_frame_sink *sink;
|
||||
|
||||
//! Protects sink
|
||||
struct os_mutex mutex;
|
||||
|
||||
//! App sink we are pushing frames into.
|
||||
struct gstreamer_sink *gs;
|
||||
|
||||
//! Recording pipeline.
|
||||
struct gstreamer_pipeline *gp;
|
||||
|
||||
char filename[512];
|
||||
} gst;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct record_scene
|
||||
{
|
||||
struct gui_scene base;
|
||||
|
||||
struct record_window *window;
|
||||
struct camera_window *window;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* GStreamer functions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
static void
|
||||
create_pipeline(struct record_window *rw)
|
||||
{
|
||||
const char *source_name = "source_name";
|
||||
const char *bitrate = NULL;
|
||||
const char *speed_preset = NULL;
|
||||
|
||||
char pipeline_string[2048];
|
||||
|
||||
switch (rw->gst.bitrate) {
|
||||
default:
|
||||
case BITRATE_4096: bitrate = "4096"; break;
|
||||
case BITRATE_2048: bitrate = "2048"; break;
|
||||
case BITRATE_1024: bitrate = "1024"; break;
|
||||
}
|
||||
|
||||
switch (rw->gst.pipeline) {
|
||||
case PIPELINE_SOFTWARE_FAST: speed_preset = "fast"; break;
|
||||
case PIPELINE_SOFTWARE_MEDIUM: speed_preset = "medium"; break;
|
||||
case PIPELINE_SOFTWARE_SLOW: speed_preset = "slow"; break;
|
||||
case PIPELINE_SOFTWARE_VERYSLOW: speed_preset = "veryslow"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (speed_preset != NULL) {
|
||||
snprintf(pipeline_string, //
|
||||
sizeof(pipeline_string), //
|
||||
"appsrc name=\"%s\" ! "
|
||||
"queue ! "
|
||||
"videoconvert ! "
|
||||
"queue ! "
|
||||
"x264enc bitrate=\"%s\" speed-preset=\"%s\" ! "
|
||||
"h264parse ! "
|
||||
"queue ! "
|
||||
"mp4mux ! "
|
||||
"filesink location=\"%s\"",
|
||||
source_name, bitrate, speed_preset, rw->gst.filename);
|
||||
} else {
|
||||
snprintf(pipeline_string, //
|
||||
sizeof(pipeline_string), //
|
||||
"appsrc name=\"%s\" ! "
|
||||
"queue ! "
|
||||
"videoconvert ! "
|
||||
"video/x-raw,format=NV12 ! "
|
||||
"queue ! "
|
||||
"vaapih264enc rate-control=cbr bitrate=\"%s\" tune=high-compression ! "
|
||||
"video/x-h264,profile=main ! "
|
||||
"h264parse ! "
|
||||
"queue ! "
|
||||
"mp4mux ! "
|
||||
"filesink location=\"%s\"",
|
||||
source_name, bitrate, rw->gst.filename);
|
||||
}
|
||||
|
||||
struct xrt_frame_sink *tmp = NULL;
|
||||
struct gstreamer_pipeline *gp = NULL;
|
||||
|
||||
gstreamer_pipeline_create_from_string(&rw->gst.xfctx, pipeline_string, &gp);
|
||||
|
||||
uint32_t width = rw->camera.mode.width;
|
||||
uint32_t height = rw->camera.mode.height;
|
||||
enum xrt_format format = rw->camera.mode.format;
|
||||
|
||||
bool do_convert = false;
|
||||
if (format == XRT_FORMAT_MJPEG) {
|
||||
format = XRT_FORMAT_R8G8B8;
|
||||
do_convert = true;
|
||||
}
|
||||
|
||||
struct gstreamer_sink *gs = NULL;
|
||||
gstreamer_sink_create_with_pipeline(gp, width, height, format, source_name, &gs, &tmp);
|
||||
if (do_convert) {
|
||||
u_sink_create_to_r8g8b8_or_l8(&rw->gst.xfctx, tmp, &tmp);
|
||||
}
|
||||
u_sink_queue_create(&rw->gst.xfctx, tmp, &tmp);
|
||||
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
rw->gst.gs = gs;
|
||||
rw->gst.sink = tmp;
|
||||
rw->gst.gp = gp;
|
||||
gstreamer_pipeline_play(rw->gst.gp);
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_pipeline(struct record_window *rw)
|
||||
{
|
||||
U_LOG_D("Called");
|
||||
|
||||
// Make sure we are not streaming any more frames into the pipeline.
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
rw->gst.gs = NULL;
|
||||
rw->gst.sink = NULL;
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
|
||||
// Stop the pipeline.
|
||||
gstreamer_pipeline_stop(rw->gst.gp);
|
||||
rw->gst.gp = NULL;
|
||||
|
||||
xrt_frame_context_destroy_nodes(&rw->gst.xfctx);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_gst(struct record_window *rw)
|
||||
{
|
||||
static ImVec2 button_dims = {0, 0};
|
||||
|
||||
if (!igCollapsingHeaderBoolPtr("Record", NULL, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
bool recording = rw->gst.gp != NULL;
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
|
||||
igComboStr("Pipeline", (int *)&rw->gst.pipeline, "SW Fast\0SW Medium\0SW Slow\0SW Veryslow\0VAAPI H264\0\0", 5);
|
||||
|
||||
igInputText("Filename", rw->gst.filename, sizeof(rw->gst.filename), 0, NULL, NULL);
|
||||
|
||||
if (!recording && igButton("Start", button_dims)) {
|
||||
create_pipeline(rw);
|
||||
}
|
||||
|
||||
if (recording && igButton("Stop", button_dims)) {
|
||||
destroy_pipeline(rw);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Record window functions.
|
||||
* Camera window functions.
|
||||
*
|
||||
*/
|
||||
|
||||
static void
|
||||
window_destroy(struct record_window *rw)
|
||||
window_set_camera_source(struct camera_window *cw, uint32_t width, uint32_t height, enum xrt_format format)
|
||||
{
|
||||
// Stop and remove the recording pipeline first.
|
||||
#ifdef XRT_HAVE_GST
|
||||
if (rw->gst.gp != NULL) {
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
rw->gst.gs = NULL;
|
||||
rw->gst.sink = NULL;
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
cw->base.source.width = width;
|
||||
cw->base.source.height = height;
|
||||
cw->base.source.format = format;
|
||||
|
||||
gstreamer_pipeline_stop(rw->gst.gp);
|
||||
rw->gst.gp = NULL;
|
||||
xrt_frame_context_destroy_nodes(&rw->gst.xfctx);
|
||||
// Touch up.
|
||||
if (cw->use.leap_motion) {
|
||||
cw->base.source.width = cw->base.source.width * 2;
|
||||
cw->base.source.format = XRT_FORMAT_L8;
|
||||
}
|
||||
#endif
|
||||
|
||||
// If it's a large source, scale to 50%
|
||||
if (cw->base.source.width > 640) {
|
||||
cw->base.texture.scale = 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
window_destroy(struct camera_window *cw)
|
||||
{
|
||||
// Stop the camera if we have one.
|
||||
xrt_frame_context_destroy_nodes(&rw->camera.xfctx);
|
||||
rw->camera.xfs = NULL;
|
||||
rw->texture.ogl = NULL;
|
||||
rw->texture.sink = NULL;
|
||||
xrt_frame_context_destroy_nodes(&cw->camera.xfctx);
|
||||
cw->camera.xfs = NULL;
|
||||
|
||||
free(rw);
|
||||
// Now it's safe to close the window.
|
||||
gui_window_record_close(&cw->base);
|
||||
|
||||
// And free.
|
||||
free(cw);
|
||||
}
|
||||
|
||||
static bool
|
||||
window_has_source(struct record_window *rw)
|
||||
window_has_source(struct camera_window *cw)
|
||||
{
|
||||
return rw->camera.xfs != NULL;
|
||||
return cw->camera.xfs != NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
window_draw_misc(struct record_window *rw)
|
||||
{
|
||||
if (!igCollapsingHeaderBoolPtr("Misc", NULL, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
static ImVec2 button_dims = {0, 0};
|
||||
bool plus = igButton("+", button_dims);
|
||||
igSameLine(0.0f, 4.0f);
|
||||
bool minus = igButton("-", button_dims);
|
||||
igSameLine(0.0f, 4.0f);
|
||||
|
||||
if (rw->texture.scale == 1) {
|
||||
igText("Scale 100%%");
|
||||
} else {
|
||||
igText("Scale 1/%i", rw->texture.scale);
|
||||
}
|
||||
|
||||
if (plus && rw->texture.scale > 1) {
|
||||
rw->texture.scale--;
|
||||
}
|
||||
if (minus && rw->texture.scale < 6) {
|
||||
rw->texture.scale++;
|
||||
}
|
||||
|
||||
igText("Sequence %u", (uint32_t)rw->texture.ogl->seq);
|
||||
}
|
||||
|
||||
static void
|
||||
window_render(struct record_window *rw, struct gui_program *p)
|
||||
{
|
||||
igBegin("Preview and Control", NULL, 0);
|
||||
|
||||
gui_ogl_sink_update(rw->texture.ogl);
|
||||
|
||||
struct gui_ogl_texture *tex = rw->texture.ogl;
|
||||
|
||||
int w = tex->w / rw->texture.scale;
|
||||
int h = tex->h / rw->texture.scale;
|
||||
|
||||
ImVec2 size = {(float)w, (float)h};
|
||||
ImVec2 uv0 = {0, 0};
|
||||
ImVec2 uv1 = {1, 1};
|
||||
ImVec4 white = {1, 1, 1, 1};
|
||||
ImTextureID id = (ImTextureID)(intptr_t)tex->id;
|
||||
igImage(id, size, uv0, uv1, white, white);
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
draw_gst(rw);
|
||||
#endif
|
||||
|
||||
window_draw_misc(rw);
|
||||
|
||||
igEnd();
|
||||
}
|
||||
|
||||
static void
|
||||
window_frame(struct xrt_frame_sink *xfs, struct xrt_frame *xf)
|
||||
{
|
||||
struct record_window *rw = container_of(xfs, struct record_window, sink);
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
if (rw->gst.sink != NULL) {
|
||||
xrt_sink_push_frame(rw->gst.sink, xf);
|
||||
}
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
#endif
|
||||
|
||||
xrt_sink_push_frame(rw->texture.sink, xf);
|
||||
}
|
||||
|
||||
static struct record_window *
|
||||
static struct camera_window *
|
||||
window_create(struct gui_program *p, const char *camera)
|
||||
{
|
||||
struct record_window *rw = U_TYPED_CALLOC(struct record_window);
|
||||
rw->sink.push_frame = window_frame;
|
||||
rw->use.index = camera == NULL ? false : strcmp(camera, "index") == 0;
|
||||
rw->use.leap_motion = camera == NULL ? false : strcmp(camera, "leap_motion") == 0;
|
||||
rw->use.depthai = camera == NULL ? false : strcmp(camera, "depthai") == 0;
|
||||
rw->use.elp = camera == NULL ? false : strcmp(camera, "elp") == 0;
|
||||
struct camera_window *cw = U_TYPED_CALLOC(struct camera_window);
|
||||
|
||||
if (!rw->use.index && !rw->use.leap_motion && !rw->use.depthai && !rw->use.elp) {
|
||||
// First init recording window.
|
||||
if (!gui_window_record_init(&cw->base)) {
|
||||
free(cw);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cw->use.index = camera == NULL ? false : strcmp(camera, "index") == 0;
|
||||
cw->use.leap_motion = camera == NULL ? false : strcmp(camera, "leap_motion") == 0;
|
||||
cw->use.depthai = camera == NULL ? false : strcmp(camera, "depthai") == 0;
|
||||
cw->use.elp = camera == NULL ? false : strcmp(camera, "elp") == 0;
|
||||
|
||||
if (!cw->use.index && !cw->use.leap_motion && !cw->use.depthai && !cw->use.elp) {
|
||||
U_LOG_W(
|
||||
"Can't recongnize camera name '%s', options are 'elp', depthai', index' & 'leap_motion'."
|
||||
"\n\tFalling back to 'index'.",
|
||||
camera);
|
||||
rw->use.index = true;
|
||||
cw->use.index = true;
|
||||
}
|
||||
|
||||
// Setup the preview texture.
|
||||
rw->texture.scale = 1;
|
||||
struct xrt_frame_sink *tmp = NULL;
|
||||
rw->texture.ogl = gui_ogl_sink_create("View", &rw->camera.xfctx, &tmp);
|
||||
u_sink_create_to_r8g8b8_or_l8(&rw->camera.xfctx, tmp, &tmp);
|
||||
u_sink_queue_create(&rw->camera.xfctx, tmp, &rw->texture.sink);
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
int ret = os_mutex_init(&rw->gst.mutex);
|
||||
if (ret < 0) {
|
||||
free(rw);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(rw->gst.filename, sizeof(rw->gst.filename), "/tmp/capture.mp4");
|
||||
#endif
|
||||
|
||||
return rw;
|
||||
return cw;
|
||||
}
|
||||
|
||||
|
||||
|
@ -431,14 +165,14 @@ window_create(struct gui_program *p, const char *camera)
|
|||
|
||||
#ifdef XRT_BUILD_DRIVER_DEPTHAI
|
||||
static void
|
||||
create_depthai(struct record_window *rw)
|
||||
create_depthai(struct camera_window *cw)
|
||||
{
|
||||
// Should we be using a DepthAI camera?
|
||||
if (!rw->use.depthai) {
|
||||
if (!cw->use.depthai) {
|
||||
return;
|
||||
}
|
||||
|
||||
rw->camera.xfs = depthai_fs_single_rgb(&rw->camera.xfctx);
|
||||
cw->camera.xfs = depthai_fs_single_rgb(&cw->camera.xfctx);
|
||||
|
||||
// Just after the camera create a quirk stream.
|
||||
struct u_sink_quirk_params qp;
|
||||
|
@ -447,28 +181,28 @@ create_depthai(struct record_window *rw)
|
|||
qp.ps4_cam = false;
|
||||
qp.leap_motion = false;
|
||||
|
||||
struct xrt_frame_sink *tmp = &rw->sink;
|
||||
u_sink_quirk_create(&rw->camera.xfctx, tmp, &qp, &tmp);
|
||||
struct xrt_frame_sink *tmp = &cw->base.sink;
|
||||
u_sink_quirk_create(&cw->camera.xfctx, tmp, &qp, &tmp);
|
||||
|
||||
struct xrt_fs_mode *modes = NULL;
|
||||
uint32_t mode_count = 0;
|
||||
xrt_fs_enumerate_modes(rw->camera.xfs, &modes, &mode_count);
|
||||
xrt_fs_enumerate_modes(cw->camera.xfs, &modes, &mode_count);
|
||||
assert(mode_count > 0);
|
||||
|
||||
// Just use the first one.
|
||||
uint32_t mode_index = 0;
|
||||
|
||||
rw->camera.mode = modes[mode_index];
|
||||
window_set_camera_source( //
|
||||
cw, //
|
||||
modes[mode_index].width, //
|
||||
modes[mode_index].height, //
|
||||
modes[mode_index].format); //
|
||||
|
||||
free(modes);
|
||||
modes = NULL;
|
||||
|
||||
// Now that we have setup a node graph, start it.
|
||||
xrt_fs_stream_start(rw->camera.xfs, tmp, XRT_FS_CAPTURE_TYPE_CALIBRATION, mode_index);
|
||||
|
||||
// If it's a large mode, scale to 50%
|
||||
if (rw->camera.mode.width > 640) {
|
||||
rw->texture.scale = 2;
|
||||
}
|
||||
xrt_fs_stream_start(cw->camera.xfs, tmp, XRT_FS_CAPTURE_TYPE_CALIBRATION, mode_index);
|
||||
}
|
||||
#endif /* XRT_BUILD_DRIVER_DEPTHAI */
|
||||
|
||||
|
@ -481,11 +215,11 @@ create_depthai(struct record_window *rw)
|
|||
|
||||
#ifdef XRT_BUILD_DRIVER_VF
|
||||
static void
|
||||
create_videotestsrc(struct record_window *rw)
|
||||
create_videotestsrc(struct camera_window *cw)
|
||||
{
|
||||
uint32_t width = 1920;
|
||||
uint32_t height = 960;
|
||||
rw->camera.xfs = vf_fs_videotestsource(&rw->camera.xfctx, width, height);
|
||||
cw->camera.xfs = vf_fs_videotestsource(&cw->camera.xfctx, width, height);
|
||||
|
||||
// Just after the camera create a quirk stream.
|
||||
struct u_sink_quirk_params qp;
|
||||
|
@ -495,19 +229,16 @@ create_videotestsrc(struct record_window *rw)
|
|||
qp.leap_motion = false;
|
||||
|
||||
struct xrt_frame_sink *tmp = NULL;
|
||||
u_sink_quirk_create(&rw->camera.xfctx, &rw->sink, &qp, &tmp);
|
||||
u_sink_quirk_create(&cw->camera.xfctx, &cw->base.sink, &qp, &tmp);
|
||||
|
||||
window_set_camera_source( //
|
||||
cw, //
|
||||
width, //
|
||||
height, //
|
||||
XRT_FORMAT_R8G8B8); //
|
||||
|
||||
// Now that we have setup a node graph, start it (mode index is hardcoded to 0).
|
||||
xrt_fs_stream_start(rw->camera.xfs, tmp, XRT_FS_CAPTURE_TYPE_CALIBRATION, 0);
|
||||
|
||||
rw->camera.mode.width = width;
|
||||
rw->camera.mode.height = height;
|
||||
rw->camera.mode.format = XRT_FORMAT_R8G8B8;
|
||||
|
||||
// If it's a large mode, scale to 50%
|
||||
if (rw->camera.mode.width > 640) {
|
||||
rw->texture.scale = 2;
|
||||
}
|
||||
xrt_fs_stream_start(cw->camera.xfs, tmp, XRT_FS_CAPTURE_TYPE_CALIBRATION, 0);
|
||||
}
|
||||
#endif /* XRT_BUILD_DRIVER_VF */
|
||||
|
||||
|
@ -545,7 +276,7 @@ on_video_device(struct xrt_prober *xp,
|
|||
const char *serial,
|
||||
void *ptr)
|
||||
{
|
||||
struct record_window *rw = (struct record_window *)ptr;
|
||||
struct camera_window *rw = (struct camera_window *)ptr;
|
||||
|
||||
if (rw->camera.xfs != NULL) {
|
||||
return;
|
||||
|
@ -571,7 +302,7 @@ on_video_device(struct xrt_prober *xp,
|
|||
|
||||
xrt_prober_open_video_device(xp, pdev, &rw->camera.xfctx, &rw->camera.xfs);
|
||||
|
||||
struct xrt_frame_sink *tmp = &rw->sink;
|
||||
struct xrt_frame_sink *tmp = &rw->base.sink;
|
||||
|
||||
if (rw->use.leap_motion) {
|
||||
// De-interleaving.
|
||||
|
@ -601,21 +332,15 @@ on_video_device(struct xrt_prober *xp,
|
|||
xrt_fs_enumerate_modes(rw->camera.xfs, &modes, &mode_count);
|
||||
assert(mode_count > 0);
|
||||
|
||||
rw->camera.mode = modes[mode_index];
|
||||
window_set_camera_source( //
|
||||
rw, //
|
||||
modes[mode_index].width, //
|
||||
modes[mode_index].height, //
|
||||
modes[mode_index].format); //
|
||||
|
||||
free(modes);
|
||||
modes = NULL;
|
||||
|
||||
// Touch up.
|
||||
if (rw->use.leap_motion) {
|
||||
rw->camera.mode.width = rw->camera.mode.width * 2;
|
||||
rw->camera.mode.format = XRT_FORMAT_L8;
|
||||
}
|
||||
|
||||
// If it's a large mode, scale to 50%
|
||||
if (rw->camera.mode.width > 640) {
|
||||
rw->texture.scale = 2;
|
||||
}
|
||||
|
||||
// Now that we have setup a node graph, start it.
|
||||
xrt_fs_stream_start(rw->camera.xfs, tmp, XRT_FS_CAPTURE_TYPE_CALIBRATION, mode_index);
|
||||
}
|
||||
|
@ -633,12 +358,17 @@ scene_render(struct gui_scene *scene, struct gui_program *p)
|
|||
static ImVec2 button_dims = {0, 0};
|
||||
struct record_scene *rs = (struct record_scene *)scene;
|
||||
|
||||
window_render(rs->window, p);
|
||||
|
||||
igBegin("Record-a-tron!", NULL, 0);
|
||||
|
||||
gui_window_record_render(&rs->window->base, p);
|
||||
|
||||
igSeparator();
|
||||
|
||||
if (igButton("Exit", button_dims)) {
|
||||
gui_scene_delete_me(p, &rs->base);
|
||||
}
|
||||
|
||||
igEnd();
|
||||
}
|
||||
|
||||
|
|
326
src/xrt/state_trackers/gui/gui_window_record.c
Normal file
326
src/xrt/state_trackers/gui/gui_window_record.c
Normal file
|
@ -0,0 +1,326 @@
|
|||
// Copyright 2019-2021, Collabora, Ltd.
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief Recording window gui.
|
||||
* @author Jakob Bornecrantz <jakob@collabora.com>
|
||||
* @ingroup gui
|
||||
*/
|
||||
|
||||
#include "xrt/xrt_config_have.h"
|
||||
#include "xrt/xrt_config_drivers.h"
|
||||
|
||||
#include "os/os_threading.h"
|
||||
|
||||
#include "util/u_var.h"
|
||||
#include "util/u_misc.h"
|
||||
#include "util/u_sink.h"
|
||||
#include "util/u_file.h"
|
||||
#include "util/u_json.h"
|
||||
#include "util/u_frame.h"
|
||||
#include "util/u_format.h"
|
||||
|
||||
#include "xrt/xrt_frame.h"
|
||||
#include "xrt/xrt_prober.h"
|
||||
#include "xrt/xrt_tracking.h"
|
||||
#include "xrt/xrt_frameserver.h"
|
||||
|
||||
#include "gui_window_record.h"
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
#include "gstreamer/gst_sink.h"
|
||||
#include "gstreamer/gst_pipeline.h"
|
||||
#endif
|
||||
|
||||
#include "gui_imgui.h"
|
||||
#include "gui_common.h"
|
||||
#include "gui_window_record.h"
|
||||
|
||||
#include "stb_image_write.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* GStreamer functions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
static void
|
||||
create_pipeline(struct gui_record_window *rw)
|
||||
{
|
||||
const char *source_name = "source_name";
|
||||
const char *bitrate = NULL;
|
||||
const char *speed_preset = NULL;
|
||||
|
||||
char pipeline_string[2048];
|
||||
|
||||
switch (rw->gst.bitrate) {
|
||||
default:
|
||||
case GUI_RECORD_BITRATE_4096: bitrate = "4096"; break;
|
||||
case GUI_RECORD_BITRATE_2048: bitrate = "2048"; break;
|
||||
case GUI_RECORD_BITRATE_1024: bitrate = "1024"; break;
|
||||
}
|
||||
|
||||
switch (rw->gst.pipeline) {
|
||||
case GUI_RECORD_PIPELINE_SOFTWARE_FAST: speed_preset = "fast"; break;
|
||||
case GUI_RECORD_PIPELINE_SOFTWARE_MEDIUM: speed_preset = "medium"; break;
|
||||
case GUI_RECORD_PIPELINE_SOFTWARE_SLOW: speed_preset = "slow"; break;
|
||||
case GUI_RECORD_PIPELINE_SOFTWARE_VERYSLOW: speed_preset = "veryslow"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (speed_preset != NULL) {
|
||||
snprintf(pipeline_string, //
|
||||
sizeof(pipeline_string), //
|
||||
"appsrc name=\"%s\" ! "
|
||||
"queue ! "
|
||||
"videoconvert ! "
|
||||
"queue ! "
|
||||
"x264enc bitrate=\"%s\" speed-preset=\"%s\" ! "
|
||||
"h264parse ! "
|
||||
"queue ! "
|
||||
"mp4mux ! "
|
||||
"filesink location=\"%s\"",
|
||||
source_name, bitrate, speed_preset, rw->gst.filename);
|
||||
} else {
|
||||
snprintf(pipeline_string, //
|
||||
sizeof(pipeline_string), //
|
||||
"appsrc name=\"%s\" ! "
|
||||
"queue ! "
|
||||
"videoconvert ! "
|
||||
"video/x-raw,format=NV12 ! "
|
||||
"queue ! "
|
||||
"vaapih264enc rate-control=cbr bitrate=\"%s\" tune=high-compression ! "
|
||||
"video/x-h264,profile=main ! "
|
||||
"h264parse ! "
|
||||
"queue ! "
|
||||
"mp4mux ! "
|
||||
"filesink location=\"%s\"",
|
||||
source_name, bitrate, rw->gst.filename);
|
||||
}
|
||||
|
||||
struct xrt_frame_sink *tmp = NULL;
|
||||
struct gstreamer_pipeline *gp = NULL;
|
||||
|
||||
gstreamer_pipeline_create_from_string(&rw->gst.xfctx, pipeline_string, &gp);
|
||||
|
||||
uint32_t width = rw->source.width;
|
||||
uint32_t height = rw->source.height;
|
||||
enum xrt_format format = rw->source.format;
|
||||
|
||||
bool do_convert = false;
|
||||
if (format == XRT_FORMAT_MJPEG) {
|
||||
format = XRT_FORMAT_R8G8B8;
|
||||
do_convert = true;
|
||||
}
|
||||
|
||||
struct gstreamer_sink *gs = NULL;
|
||||
gstreamer_sink_create_with_pipeline(gp, width, height, format, source_name, &gs, &tmp);
|
||||
if (do_convert) {
|
||||
u_sink_create_to_r8g8b8_or_l8(&rw->gst.xfctx, tmp, &tmp);
|
||||
}
|
||||
u_sink_queue_create(&rw->gst.xfctx, tmp, &tmp);
|
||||
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
rw->gst.gs = gs;
|
||||
rw->gst.sink = tmp;
|
||||
rw->gst.gp = gp;
|
||||
gstreamer_pipeline_play(rw->gst.gp);
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_pipeline(struct gui_record_window *rw)
|
||||
{
|
||||
U_LOG_D("Called");
|
||||
|
||||
// Make sure we are not streaming any more frames into the pipeline.
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
rw->gst.gs = NULL;
|
||||
rw->gst.sink = NULL;
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
|
||||
// Stop the pipeline.
|
||||
gstreamer_pipeline_stop(rw->gst.gp);
|
||||
rw->gst.gp = NULL;
|
||||
|
||||
xrt_frame_context_destroy_nodes(&rw->gst.xfctx);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_gst(struct gui_record_window *rw)
|
||||
{
|
||||
static ImVec2 button_dims = {0, 0};
|
||||
|
||||
if (!igCollapsingHeaderBoolPtr("Record", NULL, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
bool recording = rw->gst.gp != NULL;
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
|
||||
igComboStr("Pipeline", (int *)&rw->gst.pipeline, "SW Fast\0SW Medium\0SW Slow\0SW Veryslow\0VAAPI H264\0\0", 5);
|
||||
|
||||
igInputText("Filename", rw->gst.filename, sizeof(rw->gst.filename), 0, NULL, NULL);
|
||||
|
||||
if (!recording && igButton("Start", button_dims)) {
|
||||
create_pipeline(rw);
|
||||
}
|
||||
|
||||
if (recording && igButton("Stop", button_dims)) {
|
||||
destroy_pipeline(rw);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Misc helpers and interface functions.
|
||||
*
|
||||
*/
|
||||
|
||||
static void
|
||||
window_draw_misc(struct gui_record_window *rw)
|
||||
{
|
||||
if (!igCollapsingHeaderBoolPtr("Misc", NULL, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
static ImVec2 button_dims = {0, 0};
|
||||
bool plus = igButton("+", button_dims);
|
||||
igSameLine(0.0f, 4.0f);
|
||||
bool minus = igButton("-", button_dims);
|
||||
igSameLine(0.0f, 4.0f);
|
||||
|
||||
if (rw->texture.scale == 1) {
|
||||
igText("Scale 100%%");
|
||||
} else {
|
||||
igText("Scale 1/%i", rw->texture.scale);
|
||||
}
|
||||
|
||||
if (plus && rw->texture.scale > 1) {
|
||||
rw->texture.scale--;
|
||||
}
|
||||
if (minus && rw->texture.scale < 6) {
|
||||
rw->texture.scale++;
|
||||
}
|
||||
|
||||
igText("Sequence %u", (uint32_t)rw->texture.ogl->seq);
|
||||
}
|
||||
|
||||
static void
|
||||
window_frame(struct xrt_frame_sink *xfs, struct xrt_frame *xf)
|
||||
{
|
||||
struct gui_record_window *rw = container_of(xfs, struct gui_record_window, sink);
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
if (rw->gst.sink != NULL) {
|
||||
xrt_sink_push_frame(rw->gst.sink, xf);
|
||||
}
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
#endif
|
||||
|
||||
xrt_sink_push_frame(rw->texture.sink, xf);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* 'Exported' functions.
|
||||
*
|
||||
*/
|
||||
|
||||
bool
|
||||
gui_window_record_init(struct gui_record_window *rw)
|
||||
{
|
||||
// Basic init.
|
||||
rw->sink.push_frame = window_frame;
|
||||
|
||||
// Mutex first.
|
||||
#ifdef XRT_HAVE_GST
|
||||
int ret = os_mutex_init(&rw->gst.mutex);
|
||||
if (ret < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
snprintf(rw->gst.filename, sizeof(rw->gst.filename), "/tmp/capture.mp4");
|
||||
#endif
|
||||
|
||||
// Setup the preview texture.
|
||||
rw->texture.scale = 1;
|
||||
struct xrt_frame_sink *tmp = NULL;
|
||||
rw->texture.ogl = gui_ogl_sink_create("View", &rw->texture.xfctx, &tmp);
|
||||
u_sink_create_to_r8g8b8_or_l8(&rw->texture.xfctx, tmp, &tmp);
|
||||
u_sink_queue_create(&rw->texture.xfctx, tmp, &rw->texture.sink);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
gui_window_record_render(struct gui_record_window *rw, struct gui_program *p)
|
||||
{
|
||||
// Make all IDs unique.
|
||||
igPushIDPtr(rw);
|
||||
|
||||
gui_ogl_sink_update(rw->texture.ogl);
|
||||
|
||||
struct gui_ogl_texture *tex = rw->texture.ogl;
|
||||
|
||||
int w = tex->w / rw->texture.scale;
|
||||
int h = tex->h / rw->texture.scale;
|
||||
|
||||
ImVec2 size = {(float)w, (float)h};
|
||||
ImVec2 uv0 = {0, 0};
|
||||
ImVec2 uv1 = {1, 1};
|
||||
ImVec4 white = {1, 1, 1, 1};
|
||||
ImTextureID id = (ImTextureID)(intptr_t)tex->id;
|
||||
igImage(id, size, uv0, uv1, white, white);
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
draw_gst(rw);
|
||||
#endif
|
||||
|
||||
window_draw_misc(rw);
|
||||
|
||||
// Pop the ID making everything unique.
|
||||
igPopID();
|
||||
}
|
||||
|
||||
void
|
||||
gui_window_record_close(struct gui_record_window *rw)
|
||||
{
|
||||
// Stop and remove the recording pipeline first.
|
||||
#ifdef XRT_HAVE_GST
|
||||
if (rw->gst.gp != NULL) {
|
||||
os_mutex_lock(&rw->gst.mutex);
|
||||
rw->gst.gs = NULL;
|
||||
rw->gst.sink = NULL;
|
||||
os_mutex_unlock(&rw->gst.mutex);
|
||||
|
||||
gstreamer_pipeline_stop(rw->gst.gp);
|
||||
rw->gst.gp = NULL;
|
||||
xrt_frame_context_destroy_nodes(&rw->gst.xfctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
xrt_frame_context_destroy_nodes(&rw->texture.xfctx);
|
||||
|
||||
/*
|
||||
* This is safe to do, because we require that our sink 'window_frame'
|
||||
* function is not called when close is called.
|
||||
*/
|
||||
rw->texture.sink = NULL;
|
||||
rw->texture.ogl = NULL;
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
os_mutex_destroy(&rw->gst.mutex);
|
||||
#endif
|
||||
}
|
114
src/xrt/state_trackers/gui/gui_window_record.h
Normal file
114
src/xrt/state_trackers/gui/gui_window_record.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2019-2021, Collabora, Ltd.
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief Recording window gui.
|
||||
* @author Jakob Bornecrantz <jakob@collabora.com>
|
||||
* @ingroup gui
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "xrt/xrt_frame.h"
|
||||
#include "xrt/xrt_defines.h"
|
||||
#include "xrt/xrt_config_have.h"
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct xrt_frame_sink;
|
||||
struct gstreamer_sink;
|
||||
struct gstreamer_pipeline;
|
||||
struct gui_program;
|
||||
struct gui_ogl_texture;
|
||||
|
||||
|
||||
enum gui_record_bitrate
|
||||
{
|
||||
GUI_RECORD_BITRATE_4096,
|
||||
GUI_RECORD_BITRATE_2048,
|
||||
GUI_RECORD_BITRATE_1024,
|
||||
};
|
||||
|
||||
enum gui_record_pipeline
|
||||
{
|
||||
GUI_RECORD_PIPELINE_SOFTWARE_FAST,
|
||||
GUI_RECORD_PIPELINE_SOFTWARE_MEDIUM,
|
||||
GUI_RECORD_PIPELINE_SOFTWARE_SLOW,
|
||||
GUI_RECORD_PIPELINE_SOFTWARE_VERYSLOW,
|
||||
GUI_RECORD_PIPELINE_VAAPI_H246,
|
||||
};
|
||||
|
||||
struct gui_record_window
|
||||
{
|
||||
struct xrt_frame_sink sink;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t width, height;
|
||||
enum xrt_format format;
|
||||
} source;
|
||||
|
||||
struct
|
||||
{
|
||||
struct xrt_frame_context xfctx;
|
||||
|
||||
int scale;
|
||||
|
||||
struct xrt_frame_sink *sink;
|
||||
struct gui_ogl_texture *ogl;
|
||||
} texture;
|
||||
|
||||
#ifdef XRT_HAVE_GST
|
||||
struct
|
||||
{
|
||||
enum gui_record_bitrate bitrate;
|
||||
|
||||
enum gui_record_pipeline pipeline;
|
||||
|
||||
struct xrt_frame_context xfctx;
|
||||
|
||||
//! When not null we are recording.
|
||||
struct xrt_frame_sink *sink;
|
||||
|
||||
//! Protects sink
|
||||
struct os_mutex mutex;
|
||||
|
||||
//! App sink we are pushing frames into.
|
||||
struct gstreamer_sink *gs;
|
||||
|
||||
//! Recording pipeline.
|
||||
struct gstreamer_pipeline *gp;
|
||||
|
||||
char filename[512];
|
||||
} gst;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* Initialise a embeddable record window.
|
||||
*/
|
||||
bool
|
||||
gui_window_record_init(struct gui_record_window *rw);
|
||||
|
||||
/*!
|
||||
* Renders all controls of a record window.
|
||||
*/
|
||||
void
|
||||
gui_window_record_render(struct gui_record_window *rw, struct gui_program *p);
|
||||
|
||||
/*!
|
||||
* Frees all resources assocciated with a record window. Make sure to only call
|
||||
* this function on the main gui thread, and that nothing is pushing into the
|
||||
* record windows sink.
|
||||
*/
|
||||
void
|
||||
gui_window_record_close(struct gui_record_window *rw);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -15,6 +15,8 @@ gui_sources = [
|
|||
'gui_scene_video.c',
|
||||
'gui_scene_tracking_overrides.c',
|
||||
'gui_stb.c',
|
||||
'gui_window_record.c',
|
||||
'gui_window_record.h',
|
||||
'../../../external/imgui/imgui/cimgui.cpp',
|
||||
'../../../external/imgui/imgui/cimgui.h',
|
||||
'../../../external/imgui/imgui/cimplot.cpp',
|
||||
|
|
Loading…
Reference in a new issue