diff options
-rw-r--r-- | TODO | 7 | ||||
-rw-r--r-- | extra/gpu-screen-recorder.service | 3 | ||||
-rw-r--r-- | include/capture/kms_vaapi.h | 3 | ||||
-rw-r--r-- | include/capture/xcomposite_vaapi.h | 2 | ||||
-rw-r--r-- | include/color_conversion.h | 10 | ||||
-rw-r--r-- | kms/kms_shared.h | 7 | ||||
-rw-r--r-- | kms/server/kms_server.c | 68 | ||||
-rw-r--r-- | src/capture/kms_vaapi.c | 111 | ||||
-rw-r--r-- | src/capture/xcomposite_vaapi.c | 6 | ||||
-rw-r--r-- | src/color_conversion.c | 89 | ||||
-rw-r--r-- | src/main.cpp | 505 | ||||
-rw-r--r-- | study/color_space_transform_matrix.png | bin | 0 -> 7166 bytes | |||
-rwxr-xr-x | study/create_matrix.py | 42 |
13 files changed, 563 insertions, 290 deletions
@@ -111,4 +111,9 @@ Mesa doesn't support global headers (AV_CODEC_FLAG_GLOBAL_HEADER) with h264... w Drop frames if live streaming cant keep up with target fps, or dynamically change resolution/quality. -Support low power option (does it even work with vaapi in ffmpeg??). Would be very useful for steam deck.
\ No newline at end of file +Support low power option (does it even work with vaapi in ffmpeg??). Would be very useful for steam deck. + +Instead of sending a big list of drm data back to kms client, send the monitor we want to record to kms server and the server should respond with only the matching monitor, and cursor. + +Tonemap hdr to sdr when hdr is enabled and when hevc_hdr/av1_hdr is not used. +Support hdr capture with kms cuda.
\ No newline at end of file diff --git a/extra/gpu-screen-recorder.service b/extra/gpu-screen-recorder.service index 61009be..2a223d6 100644 --- a/extra/gpu-screen-recorder.service +++ b/extra/gpu-screen-recorder.service @@ -13,7 +13,8 @@ Environment=FRAMERATE=60 Environment=REPLAYDURATION=60 Environment=OUTPUTDIR=%h/Videos Environment=MAKEFOLDERS=no -ExecStart=/bin/sh -c 'AUDIO="${AUDIO_DEVICE:-$(pactl get-default-sink).monitor}"; gpu-screen-recorder -v no -w $WINDOW -c $CONTAINER -q $QUALITY -k $CODEC -ac $AUDIO_CODEC -a "$AUDIO" -f $FRAMERATE -r $REPLAYDURATION -o "$OUTPUTDIR" -mf $MAKEFOLDERS $ADDITIONAL_ARGS' +Environment=COLOR_RANGE=limited +ExecStart=/bin/sh -c 'AUDIO="${AUDIO_DEVICE:-$(pactl get-default-sink).monitor}"; gpu-screen-recorder -v no -w $WINDOW -c $CONTAINER -q $QUALITY -k $CODEC -ac $AUDIO_CODEC -a "$AUDIO" -f $FRAMERATE -r $REPLAYDURATION -o "$OUTPUTDIR" -mf $MAKEFOLDERS $ADDITIONAL_ARGS -cr $COLOR_RANGE' KillSignal=SIGINT Restart=on-failure RestartSec=5s diff --git a/include/capture/kms_vaapi.h b/include/capture/kms_vaapi.h index 26cda2c..b7fbb81 100644 --- a/include/capture/kms_vaapi.h +++ b/include/capture/kms_vaapi.h @@ -3,6 +3,7 @@ #include "../vec2.h" #include "../utils.h" +#include "../color_conversion.h" #include "capture.h" typedef struct _XDisplay Display; @@ -13,6 +14,8 @@ typedef struct { gsr_gpu_info gpu_inf; const char *card_path; /* reference */ bool wayland; + bool hdr; + gsr_color_range color_range; } gsr_capture_kms_vaapi_params; gsr_capture* gsr_capture_kms_vaapi_create(const gsr_capture_kms_vaapi_params *params); diff --git a/include/capture/xcomposite_vaapi.h b/include/capture/xcomposite_vaapi.h index e80c60a..ba234ec 100644 --- a/include/capture/xcomposite_vaapi.h +++ b/include/capture/xcomposite_vaapi.h @@ -4,6 +4,7 @@ #include "capture.h" #include "../egl.h" #include "../vec2.h" +#include "../color_conversion.h" #include <X11/X.h> typedef struct _XDisplay Display; @@ -15,6 +16,7 @@ typedef struct { bool follow_focused; /* If this is set then |window| is ignored */ vec2i region_size; /* This is currently only used with |follow_focused| */ const char *card_path; /* reference */ + gsr_color_range color_range; } gsr_capture_xcomposite_vaapi_params; gsr_capture* gsr_capture_xcomposite_vaapi_create(const gsr_capture_xcomposite_vaapi_params *params); diff --git a/include/color_conversion.h b/include/color_conversion.h index d84d548..d8e660e 100644 --- a/include/color_conversion.h +++ b/include/color_conversion.h @@ -5,12 +5,18 @@ #include "vec2.h" typedef enum { + GSR_COLOR_RANGE_LIMITED, + GSR_COLOR_RANGE_FULL +} gsr_color_range; + +typedef enum { GSR_SOURCE_COLOR_RGB } gsr_source_color; typedef enum { GSR_DESTINATION_COLOR_BGR, - GSR_DESTINATION_COLOR_NV12 /* YUV420, BT709, limited */ + GSR_DESTINATION_COLOR_NV12, /* YUV420, BT709, 8-bit */ + GSR_DESTINATION_COLOR_P010 /* YUV420, BT2020, 10-bit */ } gsr_destination_color; typedef struct { @@ -21,6 +27,8 @@ typedef struct { unsigned int destination_textures[2]; int num_destination_textures; + + gsr_color_range color_range; } gsr_color_conversion_params; typedef struct { diff --git a/kms/kms_shared.h b/kms/kms_shared.h index b72d75d..f146441 100644 --- a/kms/kms_shared.h +++ b/kms/kms_shared.h @@ -3,9 +3,10 @@ #include <stdint.h> #include <stdbool.h> +#include <drm_mode.h> -#define GSR_KMS_PROTOCOL_VERSION 1 -#define GSR_KMS_MAX_PLANES 32 +#define GSR_KMS_PROTOCOL_VERSION 2 +#define GSR_KMS_MAX_PLANES 10 typedef enum { KMS_REQUEST_TYPE_REPLACE_CONNECTION, @@ -37,10 +38,12 @@ typedef struct { uint32_t connector_id; /* 0 if unknown */ bool is_combined_plane; bool is_cursor; + bool has_hdr_metadata; int x; int y; int src_w; int src_h; + struct hdr_output_metadata hdr_metadata; } gsr_kms_response_fd; typedef struct { diff --git a/kms/server/kms_server.c b/kms/server/kms_server.c index 6062074..8bc5d8c 100644 --- a/kms/server/kms_server.c +++ b/kms/server/kms_server.c @@ -25,6 +25,7 @@ typedef struct { typedef struct { uint32_t connector_id; uint64_t crtc_id; + uint64_t hdr_metadata_blob_id; } connector_crtc_pair; typedef struct { @@ -188,12 +189,12 @@ static uint32_t plane_get_properties(int drmfd, uint32_t plane_id, bool *is_curs } /* Returns 0 if not found */ -static uint32_t get_connector_by_crtc_id(const connector_to_crtc_map *c2crtc_map, uint32_t crtc_id) { +static const connector_crtc_pair* get_connector_pair_by_crtc_id(const connector_to_crtc_map *c2crtc_map, uint32_t crtc_id) { for(int i = 0; i < c2crtc_map->num_maps; ++i) { if(c2crtc_map->maps[i].crtc_id == crtc_id) - return c2crtc_map->maps[i].connector_id; + return &c2crtc_map->maps[i]; } - return 0; + return NULL; } static void map_crtc_to_connector_ids(gsr_drm *drm, connector_to_crtc_map *c2crtc_map) { @@ -210,8 +211,12 @@ static void map_crtc_to_connector_ids(gsr_drm *drm, connector_to_crtc_map *c2crt uint64_t crtc_id = 0; connector_get_property_by_name(drm->drmfd, connector, "CRTC_ID", &crtc_id); + uint64_t hdr_output_metadata_blob_id = 0; + connector_get_property_by_name(drm->drmfd, connector, "HDR_OUTPUT_METADATA", &hdr_output_metadata_blob_id); + c2crtc_map->maps[c2crtc_map->num_maps].connector_id = connector->connector_id; c2crtc_map->maps[c2crtc_map->num_maps].crtc_id = crtc_id; + c2crtc_map->maps[c2crtc_map->num_maps].hdr_metadata_blob_id = hdr_output_metadata_blob_id; ++c2crtc_map->num_maps; drmModeFreeConnector(connector); @@ -239,6 +244,18 @@ static void drm_mode_cleanup_handles(int drmfd, drmModeFB2Ptr drmfb) { } } +static bool get_hdr_metadata(int drm_fd, uint64_t hdr_metadata_blob_id, struct hdr_output_metadata *hdr_metadata) { + drmModePropertyBlobPtr hdr_metadata_blob = drmModeGetPropertyBlob(drm_fd, hdr_metadata_blob_id); + if(!hdr_metadata_blob) + return false; + + if(hdr_metadata_blob->length >= sizeof(struct hdr_output_metadata)) + *hdr_metadata = *(struct hdr_output_metadata*)hdr_metadata_blob->data; + + drmModeFreePropertyBlob(hdr_metadata_blob); + return true; +} + static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response, connector_to_crtc_map *c2crtc_map) { int result = -1; @@ -289,30 +306,39 @@ static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response, connector_to_crt goto cleanup_handles; } + const int fd_index = response->num_fds; + bool is_cursor = false; int x = 0, y = 0, src_x = 0, src_y = 0, src_w = 0, src_h = 0; plane_get_properties(drm->drmfd, plane->plane_id, &is_cursor, &x, &y, &src_x, &src_y, &src_w, &src_h); - response->fds[response->num_fds].fd = fb_fd; - response->fds[response->num_fds].width = drmfb->width; - response->fds[response->num_fds].height = drmfb->height; - response->fds[response->num_fds].pitch = drmfb->pitches[0]; - response->fds[response->num_fds].offset = drmfb->offsets[0]; - response->fds[response->num_fds].pixel_format = drmfb->pixel_format; - response->fds[response->num_fds].modifier = drmfb->modifier; - response->fds[response->num_fds].connector_id = get_connector_by_crtc_id(c2crtc_map, plane->crtc_id); - response->fds[response->num_fds].is_cursor = is_cursor; - response->fds[response->num_fds].is_combined_plane = false; + const connector_crtc_pair *crtc_pair = get_connector_pair_by_crtc_id(c2crtc_map, plane->crtc_id); + if(crtc_pair && crtc_pair->hdr_metadata_blob_id) { + response->fds[fd_index].has_hdr_metadata = get_hdr_metadata(drm->drmfd, crtc_pair->hdr_metadata_blob_id, &response->fds[fd_index].hdr_metadata); + } else { + response->fds[fd_index].has_hdr_metadata = false; + } + + response->fds[fd_index].fd = fb_fd; + response->fds[fd_index].width = drmfb->width; + response->fds[fd_index].height = drmfb->height; + response->fds[fd_index].pitch = drmfb->pitches[0]; + response->fds[fd_index].offset = drmfb->offsets[0]; + response->fds[fd_index].pixel_format = drmfb->pixel_format; + response->fds[fd_index].modifier = drmfb->modifier; + response->fds[fd_index].connector_id = crtc_pair ? crtc_pair->connector_id : 0; + response->fds[fd_index].is_cursor = is_cursor; + response->fds[fd_index].is_combined_plane = false; if(is_cursor) { - response->fds[response->num_fds].x = x; - response->fds[response->num_fds].y = y; - response->fds[response->num_fds].src_w = 0; - response->fds[response->num_fds].src_h = 0; + response->fds[fd_index].x = x; + response->fds[fd_index].y = y; + response->fds[fd_index].src_w = 0; + response->fds[fd_index].src_h = 0; } else { - response->fds[response->num_fds].x = src_x; - response->fds[response->num_fds].y = src_y; - response->fds[response->num_fds].src_w = src_w; - response->fds[response->num_fds].src_h = src_h; + response->fds[fd_index].x = src_x; + response->fds[fd_index].y = src_y; + response->fds[fd_index].src_w = src_w; + response->fds[fd_index].src_h = src_h; } ++response->num_fds; diff --git a/src/capture/kms_vaapi.c b/src/capture/kms_vaapi.c index 0569c2d..bfd6ea8 100644 --- a/src/capture/kms_vaapi.c +++ b/src/capture/kms_vaapi.c @@ -1,7 +1,6 @@ #include "../../include/capture/kms_vaapi.h" #include "../../kms/client/kms_client.h" #include "../../include/utils.h" -#include "../../include/color_conversion.h" #include <stdlib.h> #include <stdio.h> #include <unistd.h> @@ -9,6 +8,7 @@ #include <libavutil/hwcontext.h> #include <libavutil/hwcontext_vaapi.h> #include <libavutil/frame.h> +#include <libavutil/mastering_display_metadata.h> #include <libavcodec/avcodec.h> #include <va/va.h> #include <va/va_drmcommon.h> @@ -44,6 +44,10 @@ typedef struct { unsigned int cursor_texture; gsr_color_conversion color_conversion; + + AVCodecContext *video_codec_context; + AVMasteringDisplayMetadata *mastering_display_metadata; + AVContentLightMetadata *light_metadata; } gsr_capture_kms_vaapi; static int max_int(int a, int b) { @@ -76,7 +80,7 @@ static bool drm_create_codec_context(gsr_capture_kms_vaapi *cap_kms, AVCodecCont (AVHWFramesContext *)frame_context->data; hw_frame_context->width = video_codec_context->width; hw_frame_context->height = video_codec_context->height; - hw_frame_context->sw_format = AV_PIX_FMT_NV12;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_YUV420P;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_NV12; + hw_frame_context->sw_format = cap_kms->params.hdr ? AV_PIX_FMT_P010LE : AV_PIX_FMT_NV12; hw_frame_context->format = video_codec_context->pix_fmt; hw_frame_context->device_ref = device_ctx; hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; @@ -99,7 +103,7 @@ static bool drm_create_codec_context(gsr_capture_kms_vaapi *cap_kms, AVCodecCont return true; } -#define DRM_FORMAT_MOD_INVALID 72057594037927935 +#define DRM_FORMAT_MOD_INVALID 0xffffffffffffffULL // TODO: On monitor reconfiguration, find monitor x, y, width and height again. Do the same for nvfbc. @@ -130,6 +134,8 @@ static void monitor_callback(const gsr_monitor *monitor, void *userdata) { static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context) { gsr_capture_kms_vaapi *cap_kms = cap->priv; + cap_kms->video_codec_context = video_codec_context; + gsr_monitor monitor; cap_kms->monitor_id.num_connector_ids = 0; if(gsr_egl_start_capture(cap_kms->params.egl, cap_kms->params.display_to_capture)) { @@ -182,6 +188,7 @@ static uint32_t fourcc(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { } #define FOURCC_NV12 842094158 +#define FOURCC_P010 808530000 static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) { gsr_capture_kms_vaapi *cap_kms = cap->priv; @@ -219,7 +226,7 @@ static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_c VASurfaceID target_surface_id = (uintptr_t)(*frame)->data[3]; - VAStatus va_status = vaExportSurfaceHandle(cap_kms->va_dpy, target_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_READ_WRITE | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &cap_kms->prime); + VAStatus va_status = vaExportSurfaceHandle(cap_kms->va_dpy, target_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_WRITE_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &cap_kms->prime); if(va_status != VA_STATUS_SUCCESS) { fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: vaExportSurfaceHandle failed, error: %d\n", va_status); cap_kms->should_stop = true; @@ -244,10 +251,14 @@ static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_c cap_kms->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); cap_kms->params.egl->glBindTexture(GL_TEXTURE_2D, 0); - if(cap_kms->prime.fourcc == FOURCC_NV12) { + const uint32_t formats_nv12[2] = { fourcc('R', '8', ' ', ' '), fourcc('G', 'R', '8', '8') }; + const uint32_t formats_p010[2] = { fourcc('R', '1', '6', ' '), fourcc('G', 'R', '3', '2') }; + + if(cap_kms->prime.fourcc == FOURCC_NV12 || cap_kms->prime.fourcc == FOURCC_P010) { + const uint32_t *formats = cap_kms->prime.fourcc == FOURCC_NV12 ? formats_nv12 : formats_p010; + cap_kms->params.egl->glGenTextures(2, cap_kms->target_textures); for(int i = 0; i < 2; ++i) { - const uint32_t formats[2] = { fourcc('R', '8', ' ', ' '), fourcc('G', 'R', '8', '8') }; const int layer = i; const int plane = 0; @@ -300,9 +311,13 @@ static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_c } gsr_color_conversion_params color_conversion_params = {0}; + color_conversion_params.color_range = cap_kms->params.color_range; color_conversion_params.egl = cap_kms->params.egl; color_conversion_params.source_color = GSR_SOURCE_COLOR_RGB; - color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12; + if(cap_kms->prime.fourcc == FOURCC_NV12) + color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12; + else + color_conversion_params.destination_color = GSR_DESTINATION_COLOR_P010; color_conversion_params.destination_textures[0] = cap_kms->target_textures[0]; color_conversion_params.destination_textures[1] = cap_kms->target_textures[1]; @@ -315,7 +330,7 @@ static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_c return; } } else { - fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: unexpected fourcc %u for output drm fd, expected nv12\n", cap_kms->prime.fourcc); + fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: unexpected fourcc %u for output drm fd, expected nv12 or p010\n", cap_kms->prime.fourcc); cap_kms->should_stop = true; cap_kms->stop_is_error = true; return; @@ -386,8 +401,65 @@ static gsr_kms_response_fd* find_cursor_drm(gsr_kms_response *kms_response) { return NULL; } +static void copy_wayland_surface_data_to_drm_buffer(gsr_capture_kms_vaapi *cap_kms) { + cap_kms->wayland_kms_data.fd = cap_kms->params.egl->fd; + cap_kms->wayland_kms_data.width = cap_kms->params.egl->width; + cap_kms->wayland_kms_data.height = cap_kms->params.egl->height; + cap_kms->wayland_kms_data.pitch = cap_kms->params.egl->pitch; + cap_kms->wayland_kms_data.offset = cap_kms->params.egl->offset; + cap_kms->wayland_kms_data.pixel_format = cap_kms->params.egl->pixel_format; + cap_kms->wayland_kms_data.modifier = cap_kms->params.egl->modifier; + cap_kms->wayland_kms_data.connector_id = 0; + cap_kms->wayland_kms_data.is_combined_plane = false; + cap_kms->wayland_kms_data.is_cursor = false; + cap_kms->wayland_kms_data.x = cap_kms->wayland_kms_data.x; // TODO: Use these + cap_kms->wayland_kms_data.y = cap_kms->wayland_kms_data.y; + cap_kms->wayland_kms_data.src_w = cap_kms->wayland_kms_data.width; + cap_kms->wayland_kms_data.src_h = cap_kms->wayland_kms_data.height; + + cap_kms->capture_pos.x = cap_kms->wayland_kms_data.x; + cap_kms->capture_pos.y = cap_kms->wayland_kms_data.y; +} + +#define HDMI_STATIC_METADATA_TYPE1 0 +#define HDMI_EOTF_SMPTE_ST2084 2 + +static bool hdr_metadata_is_supported_format(const struct hdr_output_metadata *hdr_metadata) { + return hdr_metadata->metadata_type == HDMI_STATIC_METADATA_TYPE1 && + hdr_metadata->hdmi_metadata_type1.metadata_type == HDMI_STATIC_METADATA_TYPE1 && + hdr_metadata->hdmi_metadata_type1.eotf == HDMI_EOTF_SMPTE_ST2084; +} + +static void gsr_capture_kms_vaapi_set_hdr_metadata(gsr_capture_kms_vaapi *cap_kms, AVFrame *frame, gsr_kms_response_fd *drm_fd) { + if(!cap_kms->mastering_display_metadata) + cap_kms->mastering_display_metadata = av_mastering_display_metadata_create_side_data(frame); + + if(!cap_kms->light_metadata) + cap_kms->light_metadata = av_content_light_metadata_create_side_data(frame); + + if(cap_kms->mastering_display_metadata) { + for(int i = 0; i < 3; ++i) { + cap_kms->mastering_display_metadata->display_primaries[i][0] = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.display_primaries[i].x, 50000); + cap_kms->mastering_display_metadata->display_primaries[i][1] = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.display_primaries[i].y, 50000); + } + + cap_kms->mastering_display_metadata->white_point[0] = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.white_point.x, 50000); + cap_kms->mastering_display_metadata->white_point[1] = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.white_point.y, 50000); + + cap_kms->mastering_display_metadata->min_luminance = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.min_display_mastering_luminance, 10000); + cap_kms->mastering_display_metadata->max_luminance = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.max_display_mastering_luminance, 1); + + cap_kms->mastering_display_metadata->has_primaries = cap_kms->mastering_display_metadata->display_primaries[0][0].num > 0; + cap_kms->mastering_display_metadata->has_luminance = cap_kms->mastering_display_metadata->max_luminance.num > 0; + } + + if(cap_kms->light_metadata) { + cap_kms->light_metadata->MaxCLL = drm_fd->hdr_metadata.hdmi_metadata_type1.max_cll; + cap_kms->light_metadata->MaxFALL = drm_fd->hdr_metadata.hdmi_metadata_type1.max_fall; + } +} + static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) { - (void)frame; gsr_capture_kms_vaapi *cap_kms = cap->priv; for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) { @@ -402,23 +474,7 @@ static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) { bool capture_is_combined_plane = false; if(cap_kms->using_wayland_capture) { gsr_egl_update(cap_kms->params.egl); - cap_kms->wayland_kms_data.fd = cap_kms->params.egl->fd; - cap_kms->wayland_kms_data.width = cap_kms->params.egl->width; - cap_kms->wayland_kms_data.height = cap_kms->params.egl->height; - cap_kms->wayland_kms_data.pitch = cap_kms->params.egl->pitch; - cap_kms->wayland_kms_data.offset = cap_kms->params.egl->offset; - cap_kms->wayland_kms_data.pixel_format = cap_kms->params.egl->pixel_format; - cap_kms->wayland_kms_data.modifier = cap_kms->params.egl->modifier; - cap_kms->wayland_kms_data.connector_id = 0; - cap_kms->wayland_kms_data.is_combined_plane = false; - cap_kms->wayland_kms_data.is_cursor = false; - cap_kms->wayland_kms_data.x = cap_kms->wayland_kms_data.x; // TODO: Use these - cap_kms->wayland_kms_data.y = cap_kms->wayland_kms_data.y; - cap_kms->wayland_kms_data.src_w = cap_kms->wayland_kms_data.width; - cap_kms->wayland_kms_data.src_h = cap_kms->wayland_kms_data.height; - - cap_kms->capture_pos.x = cap_kms->wayland_kms_data.x; - cap_kms->capture_pos.y = cap_kms->wayland_kms_data.y; + copy_wayland_surface_data_to_drm_buffer(cap_kms); if(cap_kms->wayland_kms_data.fd <= 0) return -1; @@ -462,6 +518,9 @@ static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) { if(!capture_is_combined_plane && cursor_drm_fd && cursor_drm_fd->connector_id != drm_fd->connector_id) cursor_drm_fd = NULL; + if(drm_fd->has_hdr_metadata && cap_kms->params.hdr && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata)) + gsr_capture_kms_vaapi_set_hdr_metadata(cap_kms, frame, drm_fd); + // TODO: This causes a crash sometimes on steam deck, why? is it a driver bug? a vaapi pure version doesn't cause a crash. // Even ffmpeg kmsgrab causes this crash. The error is: // amdgpu: Failed to allocate a buffer: diff --git a/src/capture/xcomposite_vaapi.c b/src/capture/xcomposite_vaapi.c index f138d13..687eb2d 100644 --- a/src/capture/xcomposite_vaapi.c +++ b/src/capture/xcomposite_vaapi.c @@ -1,7 +1,6 @@ #include "../../include/capture/xcomposite_vaapi.h" #include "../../include/window_texture.h" #include "../../include/utils.h" -#include "../../include/color_conversion.h" #include <stdlib.h> #include <stdio.h> #include <unistd.h> @@ -112,7 +111,7 @@ static bool drm_create_codec_context(gsr_capture_xcomposite_vaapi *cap_xcomp, AV return true; } -#define DRM_FORMAT_MOD_INVALID 72057594037927935 +#define DRM_FORMAT_MOD_INVALID 0xffffffffffffffULL static int gsr_capture_xcomposite_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context) { gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv; @@ -332,7 +331,7 @@ static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext * VASurfaceID target_surface_id = (uintptr_t)(*frame)->data[3]; - VAStatus va_status = vaExportSurfaceHandle(cap_xcomp->va_dpy, target_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_READ_WRITE | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &cap_xcomp->prime); + VAStatus va_status = vaExportSurfaceHandle(cap_xcomp->va_dpy, target_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_WRITE_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &cap_xcomp->prime); if(va_status != VA_STATUS_SUCCESS) { fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaExportSurfaceHandle failed, error: %d\n", va_status); cap_xcomp->should_stop = true; @@ -397,6 +396,7 @@ static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext * } gsr_color_conversion_params color_conversion_params = {0}; + color_conversion_params.color_range = cap_xcomp->params.color_range; color_conversion_params.egl = cap_xcomp->params.egl; color_conversion_params.source_color = GSR_SOURCE_COLOR_RGB; color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12; diff --git a/src/color_conversion.c b/src/color_conversion.c index ebf802b..2bfb9c2 100644 --- a/src/color_conversion.c +++ b/src/color_conversion.c @@ -20,11 +20,59 @@ static float abs_f(float v) { " 0.0, 0.0, 0.0, 1.0);\n" \ "}\n" -/* BT709 limited */ -#define RGB_TO_YUV "const mat4 RGBtoYUV = mat4(0.1826, -0.1006, 0.4392, 0.0,\n" \ - " 0.6142, -0.3386, -0.3989, 0.0,\n" \ - " 0.0620, 0.4392, -0.0403, 0.0,\n" \ - " 0.0625, 0.5000, 0.5000, 1.0);" +/* https://en.wikipedia.org/wiki/YCbCr, see study/color_space_transform_matrix.png */ + +/* ITU-R BT2020, full */ +/* https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-2-201510-I!!PDF-E.pdf */ +#define RGB_TO_P010_FULL "const mat4 RGBtoYUV = mat4(0.262700, -0.139630, 0.500000, 0.000000,\n" \ + " 0.678000, -0.360370, -0.459786, 0.000000,\n" \ + " 0.059300, 0.500000, -0.040214, 0.000000,\n" \ + " 0.000000, 0.500000, 0.500000, 1.000000);" + +/* ITU-R BT2020, limited (full multiplied by (235-16)/255, adding 16/255 to luma) */ +#define RGB_TO_P010_LIMITED "const mat4 RGBtoYUV = mat4(0.225613, -0.119918, 0.429412, 0.000000,\n" \ + " 0.582282, -0.309494, -0.394875, 0.000000,\n" \ + " 0.050928, 0.429412, -0.034537, 0.000000,\n" \ + " 0.062745, 0.500000, 0.500000, 1.000000)"; + +/* ITU-R BT709, full */ +/* https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf */ +#define RGB_TO_NV12_FULL "const mat4 RGBtoYUV = mat4(0.212600, -0.114572, 0.500000, 0.000000,\n" \ + " 0.715200, -0.385428, -0.454153, 0.000000,\n" \ + " 0.072200, 0.500000, -0.045847, 0.000000,\n" \ + " 0.000000, 0.500000, 0.500000, 1.000000);" + +/* ITU-R BT709, limited (full multiplied by (235-16)/255, adding 16/255 to luma) */ +#define RGB_TO_NV12_LIMITED "const mat4 RGBtoYUV = mat4(0.182586, -0.098397, 0.429412, 0.000000,\n" \ + " 0.614231, -0.331015, -0.390037, 0.000000,\n" \ + " 0.062007, 0.429412, -0.039375, 0.000000,\n" \ + " 0.062745, 0.500000, 0.500000, 1.000000);" + +static const char* color_format_range_get_transform_matrix(gsr_destination_color color_format, gsr_color_range color_range) { + switch(color_format) { + case GSR_DESTINATION_COLOR_NV12: { + switch(color_range) { + case GSR_COLOR_RANGE_LIMITED: + return RGB_TO_NV12_LIMITED; + case GSR_COLOR_RANGE_FULL: + return RGB_TO_NV12_FULL; + } + break; + } + case GSR_DESTINATION_COLOR_P010: { + switch(color_range) { + case GSR_COLOR_RANGE_LIMITED: + return RGB_TO_P010_LIMITED; + case GSR_COLOR_RANGE_FULL: + return RGB_TO_P010_FULL; + } + break; + } + default: + return NULL; + } + return NULL; +} static int load_shader_bgr(gsr_shader *shader, gsr_egl *egl, int *rotation_uniform) { char vertex_shader[2048]; @@ -98,7 +146,9 @@ static int load_shader_bgr_external_texture(gsr_shader *shader, gsr_egl *egl, in return 0; } -static int load_shader_y(gsr_shader *shader, gsr_egl *egl, int *rotation_uniform) { +static int load_shader_y(gsr_shader *shader, gsr_egl *egl, int *rotation_uniform, gsr_destination_color color_format, gsr_color_range color_range) { + const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range); + char vertex_shader[2048]; snprintf(vertex_shader, sizeof(vertex_shader), "#version 300 es \n" @@ -113,19 +163,20 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, int *rotation_uniform " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0) * rotate_z(rotation); \n" "} \n"); - char fragment_shader[] = + char fragment_shader[2048]; + snprintf(fragment_shader, sizeof(fragment_shader), "#version 300 es \n" "precision mediump float; \n" "in vec2 texcoords_out; \n" "uniform sampler2D tex1; \n" "out vec4 FragColor; \n" - RGB_TO_YUV + "%s" "void main() \n" "{ \n" " vec4 pixel = texture(tex1, texcoords_out); \n" " FragColor.x = (RGBtoYUV * vec4(pixel.rgb, 1.0)).x; \n" " FragColor.w = pixel.a; \n" - "} \n"; + "} \n", color_transform_matrix); if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0) return -1; @@ -136,7 +187,9 @@ static int load_shader_y(gsr_shader *shader, gsr_egl *egl, int *rotation_uniform return 0; } -static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, int *rotation_uniform) { +static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, int *rotation_uniform, gsr_destination_color color_format, gsr_color_range color_range) { + const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range); + char vertex_shader[2048]; snprintf(vertex_shader, sizeof(vertex_shader), "#version 300 es \n" @@ -151,19 +204,20 @@ static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, int *rotati " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0) * rotate_z(rotation) * vec4(0.5, 0.5, 1.0, 1.0) - vec4(0.5, 0.5, 0.0, 0.0); \n" "} \n"); - char fragment_shader[] = + char fragment_shader[2048]; + snprintf(fragment_shader, sizeof(fragment_shader), "#version 300 es \n" "precision mediump float; \n" "in vec2 texcoords_out; \n" "uniform sampler2D tex1; \n" "out vec4 FragColor; \n" - RGB_TO_YUV + "%s" "void main() \n" "{ \n" " vec4 pixel = texture(tex1, texcoords_out); \n" " FragColor.xy = (RGBtoYUV * vec4(pixel.rgb, 1.0)).yz; \n" " FragColor.w = pixel.a; \n" - "} \n"; + "} \n", color_transform_matrix); if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0) return -1; @@ -248,18 +302,19 @@ int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conver } break; } - case GSR_DESTINATION_COLOR_NV12: { + case GSR_DESTINATION_COLOR_NV12: + case GSR_DESTINATION_COLOR_P010: { if(self->params.num_destination_textures != 2) { - fprintf(stderr, "gsr error: gsr_color_conversion_init: expected 2 destination textures for destination color NV12, got %d destination texture(s)\n", self->params.num_destination_textures); + fprintf(stderr, "gsr error: gsr_color_conversion_init: expected 2 destination textures for destination color NV12/P010, got %d destination texture(s)\n", self->params.num_destination_textures); return -1; } - if(load_shader_y(&self->shaders[0], self->params.egl, &self->rotation_uniforms[0]) != 0) { + if(load_shader_y(&self->shaders[0], self->params.egl, &self->rotation_uniforms[0], params->destination_color, params->color_range) != 0) { fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y shader\n"); goto err; } - if(load_shader_uv(&self->shaders[1], self->params.egl, &self->rotation_uniforms[1]) != 0) { + if(load_shader_uv(&self->shaders[1], self->params.egl, &self->rotation_uniforms[1], params->destination_color, params->color_range) != 0) { fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV shader\n"); goto err; } diff --git a/src/main.cpp b/src/main.cpp index 1e0757a..b9fbc99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ extern "C" { #include "../include/capture/kms_cuda.h" #include "../include/egl.h" #include "../include/utils.h" +#include "../include/color_conversion.h" } #include <assert.h> @@ -33,6 +34,7 @@ extern "C" { #include <libswresample/swresample.h> #include <libavutil/avutil.h> #include <libavutil/time.h> +#include <libavutil/mastering_display_metadata.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> @@ -80,7 +82,9 @@ enum class VideoQuality { enum class VideoCodec { H264, HEVC, - AV1 + HEVC_HDR, + AV1, + AV1_HDR }; enum class AudioCodec { @@ -107,6 +111,16 @@ static int x11_io_error_handler(Display*) { return 0; } +static bool video_codec_is_hdr(VideoCodec video_codec) { + switch(video_codec) { + case VideoCodec::HEVC_HDR: + case VideoCodec::AV1_HDR: + return true; + default: + return false; + } +} + struct PacketData { PacketData() {} PacketData(const PacketData&) = delete; @@ -311,7 +325,8 @@ static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_code static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, VideoQuality video_quality, - int fps, const AVCodec *codec, bool is_livestream, gsr_gpu_vendor vendor, FramerateMode framerate_mode) { + int fps, const AVCodec *codec, bool is_livestream, gsr_gpu_vendor vendor, FramerateMode framerate_mode, + bool hdr, gsr_color_range color_range) { AVCodecContext *codec_context = avcodec_alloc_context3(codec); @@ -341,10 +356,16 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, } codec_context->max_b_frames = 0; codec_context->pix_fmt = pix_fmt; - //codec_context->color_range = AVCOL_RANGE_JPEG; // TODO: Amd/nvidia? - //codec_context->color_primaries = AVCOL_PRI_BT709; - //codec_context->color_trc = AVCOL_TRC_BT709; - //codec_context->colorspace = AVCOL_SPC_BT709; + codec_context->color_range = color_range == GSR_COLOR_RANGE_LIMITED ? AVCOL_RANGE_MPEG : AVCOL_RANGE_JPEG; + if(hdr) { + codec_context->color_primaries = AVCOL_PRI_BT2020; + codec_context->color_trc = AVCOL_TRC_SMPTE2084; + codec_context->colorspace = AVCOL_SPC_BT2020_NCL; + } else { + //codec_context->color_primaries = AVCOL_PRI_BT709; + //codec_context->color_trc = AVCOL_TRC_BT709; + //codec_context->colorspace = AVCOL_SPC_BT709; + } //codec_context->chroma_sample_location = AVCHROMA_LOC_CENTER; if(codec->id == AV_CODEC_ID_HEVC) codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); @@ -421,6 +442,8 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, //codec_context->compression_level = 2; } + av_opt_set(codec_context->priv_data, "bsf", "hevc_metadata=colour_primaries=9:transfer_characteristics=16:matrix_coefficients=9", 0); + //codec_context->rc_max_rate = codec_context->bit_rate; //codec_context->rc_min_rate = codec_context->bit_rate; //codec_context->rc_buffer_size = codec_context->bit_rate / 10; @@ -478,7 +501,7 @@ static bool vaapi_create_codec_context(AVCodecContext *video_codec_context, cons static bool check_if_codec_valid_for_hardware(const AVCodec *codec, gsr_gpu_vendor vendor, const char *card_path) { // Do not use AV_PIX_FMT_CUDA because we dont want to do full check with hardware context - AVCodecContext *codec_context = create_video_codec_context(vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_VAAPI, VideoQuality::VERY_HIGH, 60, codec, false, vendor, FramerateMode::CONSTANT); + AVCodecContext *codec_context = create_video_codec_context(vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_VAAPI, VideoQuality::VERY_HIGH, 60, codec, false, vendor, FramerateMode::CONSTANT, false, GSR_COLOR_RANGE_LIMITED); if(!codec_context) return false; @@ -594,7 +617,7 @@ static AVFrame* create_audio_frame(AVCodecContext *audio_codec_context) { return frame; } -static void open_video(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format) { +static void open_video(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format, bool hdr) { AVDictionary *options = nullptr; if(vendor == GSR_GPU_VENDOR_NVIDIA) { #if 0 @@ -754,7 +777,12 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality av_dict_set(&options, "profile", "main", 0); // TODO: use professional instead? av_dict_set(&options, "tier", "main", 0); } else { - av_dict_set(&options, "profile", "main", 0); + if(hdr) { + av_dict_set(&options, "profile", "main10", 0); + av_dict_set(&options, "sei", "hdr", 0); + } else { + av_dict_set(&options, "profile", "main", 0); + } } } @@ -772,7 +800,7 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality } static void usage_header() { - fprintf(stderr, "usage: gpu-screen-recorder -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|h265|av1] [-ac aac|opus|flac] [-oc yes|no] [-fm cfr|vfr] [-v yes|no] [-h|--help] [-o <output_file>] [-mf yes|no] [-sc <script_path>]\n"); + fprintf(stderr, "usage: gpu-screen-recorder -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|hevc|hevc_hdr|av1|av1_hdr] [-ac aac|opus|flac] [-oc yes|no] [-fm cfr|vfr] [-cr limited|full] [-v yes|no] [-h|--help] [-o <output_file>] [-mf yes|no] [-sc <script_path>]\n"); } static void usage_full() { @@ -808,9 +836,9 @@ static void usage_full() { fprintf(stderr, " and the video will only be saved when the gpu-screen-recorder is closed. This feature is similar to Nvidia's instant replay feature.\n"); fprintf(stderr, " This option has be between 5 and 1200. Note that the replay buffer size will not always be precise, because of keyframes. Optional, disabled by default.\n"); fprintf(stderr, "\n"); - fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'h265' or 'av1'. Defaults to 'auto' which defaults to 'h265' on AMD/Nvidia and 'h264' on intel.\n"); + fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'hevc_hdr', 'av1' or 'av1_hdr'. Defaults to 'auto' which defaults to 'hevc' on AMD/Nvidia and 'h264' on intel.\n"); fprintf(stderr, " Forcefully set to 'h264' if the file container type is 'flv'.\n"); - fprintf(stderr, " Forcefully set to 'h265' on AMD/intel if video codec is 'h264' and if the file container type is 'mkv'.\n"); + fprintf(stderr, " Forcefully set to 'hevc' on AMD/intel if video codec is 'h264' and if the file container type is 'mkv'.\n"); fprintf(stderr, "\n"); fprintf(stderr, " -ac Audio codec to use. Should be either 'aac', 'opus' or 'flac'. Defaults to 'opus' for .mp4/.mkv files, otherwise defaults to 'aac'.\n"); fprintf(stderr, " 'opus' and 'flac' is only supported by .mp4/.mkv files. 'opus' is recommended for best performance and smallest audio size.\n"); @@ -821,6 +849,9 @@ static void usage_full() { fprintf(stderr, "\n"); fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' or 'vfr'. Defaults to 'vfr'.\n"); fprintf(stderr, "\n"); + fprintf(stderr, " -cr Color range. Should be either 'limited' (aka mpeg) or 'full' (aka jpeg). Defaults to 'limited'.\n"); + fprintf(stderr, " Limited color range means that colors are in range 16-235 while full color range means that colors are in range 0-255 (when not recording with hdr).\n"); + fprintf(stderr, "\n"); fprintf(stderr, " -v Prints per second, fps updates. Optional, set to 'yes' by default.\n"); fprintf(stderr, "\n"); fprintf(stderr, " -h, --help\n"); @@ -831,7 +862,7 @@ static void usage_full() { fprintf(stderr, " -sc Run a script on the saved video file (non-blocking). The first argument to the script is the filepath to the saved video file and the second argument is the recording type (either \"regular\" or \"replay\"). Not applicable for live streams.\n"); fprintf(stderr, "\n"); fprintf(stderr, " --list-supported-video-codecs\n"); - fprintf(stderr, " List supported video codecs and exits. Prints h264, hevc and av1 (if supported).\n"); + fprintf(stderr, " List supported video codecs and exits. Prints h264, hevc, hevc_hdr, av1 and av1_hdr (if supported).\n"); fprintf(stderr, "\n"); //fprintf(stderr, " -pixfmt The pixel format to use for the output video. yuv420 is the most common format and is best supported, but the color is compressed, so colors can look washed out and certain colors of text can look bad. Use yuv444 for no color compression, but the video may not work everywhere and it may not work with hardware video decoding. Optional, defaults to yuv420\n"); fprintf(stderr, " -o The output file path. If omitted then the encoded data is sent to stdout. Required in replay mode (when using -r).\n"); @@ -1362,6 +1393,7 @@ static void list_supported_video_codecs() { av_log_set_level(AV_LOG_FATAL); + // TODO: Output hdr if(find_h264_encoder(gpu_inf.vendor, card_path)) puts("h264"); if(find_h265_encoder(gpu_inf.vendor, card_path)) @@ -1372,6 +1404,198 @@ static void list_supported_video_codecs() { fflush(stdout); } +static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_gpu_info gpu_inf, gsr_egl &egl, char *card_path, Display *dpy, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range) { + vec2i region_size = { 0, 0 }; + Window src_window_id = None; + bool follow_focused = false; + + gsr_capture *capture = nullptr; + if(strcmp(window_str, "focused") == 0) { + if(wayland) { + fprintf(stderr, "Error: GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland\n"); + _exit(2); + } + + if(!screen_region) { + fprintf(stderr, "Error: option -s is required when using -w focused\n"); + usage(); + } + + if(sscanf(screen_region, "%dx%d", ®ion_size.x, ®ion_size.y) != 2) { + fprintf(stderr, "Error: invalid value for option -s '%s', expected a value in format WxH\n", screen_region); + usage(); + } + + if(region_size.x <= 0 || region_size.y <= 0) { + fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater than 0\n", screen_region); + usage(); + } + + follow_focused = true; + } else if(contains_non_hex_number(window_str)) { + if(wayland || gpu_inf.vendor != GSR_GPU_VENDOR_NVIDIA) { + if(strcmp(window_str, "screen") == 0) { + FirstOutputCallback first_output; + first_output.output_name = NULL; + if(gsr_egl_supports_wayland_capture(&egl)) { + for_each_active_monitor_output(&egl, GSR_CONNECTION_WAYLAND, get_first_output, &first_output); + } else { + for_each_active_monitor_output(card_path, GSR_CONNECTION_DRM, get_first_output, &first_output); + } + + if(first_output.output_name) { + window_str = first_output.output_name; + } else { + fprintf(stderr, "Error: no available output found\n"); + } + } + + if(gsr_egl_supports_wayland_capture(&egl)) { + gsr_monitor gmon; + if(!get_monitor_by_name(&egl, GSR_CONNECTION_WAYLAND, window_str, &gmon)) { + fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); + fprintf(stderr, " \"screen\"\n"); + for_each_active_monitor_output(&egl, GSR_CONNECTION_WAYLAND, monitor_output_callback_print, NULL); + _exit(1); + } + } else { + gsr_monitor gmon; + if(!get_monitor_by_name(card_path, GSR_CONNECTION_DRM, window_str, &gmon)) { + fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); + fprintf(stderr, " \"screen\"\n"); + for_each_active_monitor_output(card_path, GSR_CONNECTION_DRM, monitor_output_callback_print, NULL); + _exit(1); + } + } + } else { + if(strcmp(window_str, "screen") != 0 && strcmp(window_str, "screen-direct") != 0 && strcmp(window_str, "screen-direct-force") != 0) { + gsr_monitor gmon; + if(!get_monitor_by_name(dpy, GSR_CONNECTION_X11, window_str, &gmon)) { + fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); + fprintf(stderr, " \"screen\" (%dx%d+%d+%d)\n", XWidthOfScreen(DefaultScreenOfDisplay(dpy)), XHeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0); + fprintf(stderr, " \"screen-direct\" (%dx%d+%d+%d)\n", XWidthOfScreen(DefaultScreenOfDisplay(dpy)), XHeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0); + fprintf(stderr, " \"screen-direct-force\" (%dx%d+%d+%d)\n", XWidthOfScreen(DefaultScreenOfDisplay(dpy)), XHeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0); + for_each_active_monitor_output(dpy, GSR_CONNECTION_X11, monitor_output_callback_print, NULL); + _exit(1); + } + } + } + + if(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA) { + if(wayland) { + gsr_capture_kms_cuda_params kms_params; + kms_params.egl = &egl; + kms_params.display_to_capture = window_str; + kms_params.gpu_inf = gpu_inf; + kms_params.card_path = card_path; + capture = gsr_capture_kms_cuda_create(&kms_params); + if(!capture) + _exit(1); + } else { + const char *capture_target = window_str; + bool direct_capture = strcmp(window_str, "screen-direct") == 0; + if(direct_capture) { + capture_target = "screen"; + // TODO: Temporary disable direct capture because push model causes stuttering when it's direct capturing. This might be a nvfbc bug. This does not happen when using a compositor. + direct_capture = false; + fprintf(stderr, "Warning: screen-direct has temporary been disabled as it causes stuttering. This is likely a NvFBC bug. Falling back to \"screen\".\n"); + } + + if(strcmp(window_str, "screen-direct-force") == 0) { + direct_capture = true; + capture_target = "screen"; + } + + gsr_egl_unload(&egl); + + gsr_capture_nvfbc_params nvfbc_params; + nvfbc_params.dpy = dpy; + nvfbc_params.display_to_capture = capture_target; + nvfbc_params.fps = fps; + nvfbc_params.pos = { 0, 0 }; + nvfbc_params.size = { 0, 0 }; + nvfbc_params.direct_capture = direct_capture; + nvfbc_params.overclock = overclock; + capture = gsr_capture_nvfbc_create(&nvfbc_params); + if(!capture) + _exit(1); + } + } else { + gsr_capture_kms_vaapi_params kms_params; + kms_params.egl = &egl; + kms_params.display_to_capture = window_str; + kms_params.gpu_inf = gpu_inf; + kms_params.card_path = card_path; + kms_params.wayland = wayland; + kms_params.hdr = video_codec_is_hdr(video_codec); + kms_params.color_range = color_range; + capture = gsr_capture_kms_vaapi_create(&kms_params); + if(!capture) + _exit(1); + } + } else { + if(wayland) { + fprintf(stderr, "Error: GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland\n"); + _exit(2); + } + + errno = 0; + src_window_id = strtol(window_str, nullptr, 0); + if(src_window_id == None || errno == EINVAL) { + fprintf(stderr, "Invalid window number %s\n", window_str); + usage(); + } + } + + if(!capture) { + switch(gpu_inf.vendor) { + case GSR_GPU_VENDOR_AMD: { + gsr_capture_xcomposite_vaapi_params xcomposite_params; + xcomposite_params.egl = &egl; + xcomposite_params.dpy = dpy; + xcomposite_params.window = src_window_id; + xcomposite_params.follow_focused = follow_focused; + xcomposite_params.region_size = region_size; + xcomposite_params.card_path = card_path; + xcomposite_params.color_range = color_range; + capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params); + if(!capture) + _exit(1); + break; + } + case GSR_GPU_VENDOR_INTEL: { + gsr_capture_xcomposite_vaapi_params xcomposite_params; + xcomposite_params.egl = &egl; + xcomposite_params.dpy = dpy; + xcomposite_params.window = src_window_id; + xcomposite_params.follow_focused = follow_focused; + xcomposite_params.region_size = region_size; + xcomposite_params.card_path = card_path; + xcomposite_params.color_range = color_range; + capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params); + if(!capture) + _exit(1); + break; + } + case GSR_GPU_VENDOR_NVIDIA: { + gsr_capture_xcomposite_cuda_params xcomposite_params; + xcomposite_params.egl = &egl; + xcomposite_params.dpy = dpy; + xcomposite_params.window = src_window_id; + xcomposite_params.follow_focused = follow_focused; + xcomposite_params.region_size = region_size; + xcomposite_params.overclock = overclock; + capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params); + if(!capture) + _exit(1); + break; + } + } + } + + return capture; +} + struct Arg { std::vector<const char*> values; bool optional = false; @@ -1422,6 +1646,7 @@ int main(int argc, char **argv) { { "-v", Arg { {}, true, false } }, { "-mf", Arg { {}, true, false } }, { "-sc", Arg { {}, true, false } }, + { "-cr", Arg { {}, true, false } }, }; for(int i = 1; i < argc; i += 2) { @@ -1460,10 +1685,14 @@ int main(int argc, char **argv) { video_codec = VideoCodec::H264; } else if(strcmp(video_codec_to_use, "h265") == 0 || strcmp(video_codec_to_use, "hevc") == 0) { video_codec = VideoCodec::HEVC; + } else if(strcmp(video_codec_to_use, "hevc_hdr") == 0) { + video_codec = VideoCodec::HEVC_HDR; } else if(strcmp(video_codec_to_use, "av1") == 0) { video_codec = VideoCodec::AV1; + } else if(strcmp(video_codec_to_use, "av1_hdr") == 0) { + video_codec = VideoCodec::AV1_HDR; } else if(strcmp(video_codec_to_use, "auto") != 0) { - fprintf(stderr, "Error: -k should either be either 'auto', 'h264', 'h265', 'hevc' or 'av1', got: '%s'\n", video_codec_to_use); + fprintf(stderr, "Error: -k should either be either 'auto', 'h264', 'hevc', 'hevc_hdr', 'av1' or 'av1_hdr', got: '%s'\n", video_codec_to_use); usage(); } @@ -1693,6 +1922,20 @@ int main(int argc, char **argv) { usage(); } + gsr_color_range color_range; + const char *color_range_str = args["-cr"].value(); + if(!color_range_str) + color_range_str = "limited"; + + if(strcmp(color_range_str, "limited") == 0) { + color_range = GSR_COLOR_RANGE_LIMITED; + } else if(strcmp(color_range_str, "full") == 0) { + color_range = GSR_COLOR_RANGE_FULL; + } else { + fprintf(stderr, "Error: -cr should either be either 'limited' or 'full', got: '%s'\n", color_range_str); + usage(); + } + const char *screen_region = args["-s"].value(); const char *window_str = strdup(args["-w"].value()); @@ -1701,190 +1944,6 @@ int main(int argc, char **argv) { usage(); } - vec2i region_size = { 0, 0 }; - Window src_window_id = None; - bool follow_focused = false; - - gsr_capture *capture = nullptr; - if(strcmp(window_str, "focused") == 0) { - if(wayland) { - fprintf(stderr, "Error: GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland\n"); - _exit(2); - } - - if(!screen_region) { - fprintf(stderr, "Error: option -s is required when using -w focused\n"); - usage(); - } - - if(sscanf(screen_region, "%dx%d", ®ion_size.x, ®ion_size.y) != 2) { - fprintf(stderr, "Error: invalid value for option -s '%s', expected a value in format WxH\n", screen_region); - usage(); - } - - if(region_size.x <= 0 || region_size.y <= 0) { - fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater than 0\n", screen_region); - usage(); - } - - follow_focused = true; - } else if(contains_non_hex_number(window_str)) { - if(wayland || gpu_inf.vendor != GSR_GPU_VENDOR_NVIDIA) { - if(strcmp(window_str, "screen") == 0) { - FirstOutputCallback first_output; - first_output.output_name = NULL; - if(gsr_egl_supports_wayland_capture(&egl)) { - for_each_active_monitor_output(&egl, GSR_CONNECTION_WAYLAND, get_first_output, &first_output); - } else { - for_each_active_monitor_output(card_path, GSR_CONNECTION_DRM, get_first_output, &first_output); - } - - if(first_output.output_name) { - window_str = first_output.output_name; - } else { - fprintf(stderr, "Error: no available output found\n"); - } - } - - if(gsr_egl_supports_wayland_capture(&egl)) { - gsr_monitor gmon; - if(!get_monitor_by_name(&egl, GSR_CONNECTION_WAYLAND, window_str, &gmon)) { - fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); - fprintf(stderr, " \"screen\"\n"); - for_each_active_monitor_output(&egl, GSR_CONNECTION_WAYLAND, monitor_output_callback_print, NULL); - _exit(1); - } - } else { - gsr_monitor gmon; - if(!get_monitor_by_name(card_path, GSR_CONNECTION_DRM, window_str, &gmon)) { - fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); - fprintf(stderr, " \"screen\"\n"); - for_each_active_monitor_output(card_path, GSR_CONNECTION_DRM, monitor_output_callback_print, NULL); - _exit(1); - } - } - } else { - if(strcmp(window_str, "screen") != 0 && strcmp(window_str, "screen-direct") != 0 && strcmp(window_str, "screen-direct-force") != 0) { - gsr_monitor gmon; - if(!get_monitor_by_name(dpy, GSR_CONNECTION_X11, window_str, &gmon)) { - fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); - fprintf(stderr, " \"screen\" (%dx%d+%d+%d)\n", XWidthOfScreen(DefaultScreenOfDisplay(dpy)), XHeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0); - fprintf(stderr, " \"screen-direct\" (%dx%d+%d+%d)\n", XWidthOfScreen(DefaultScreenOfDisplay(dpy)), XHeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0); - fprintf(stderr, " \"screen-direct-force\" (%dx%d+%d+%d)\n", XWidthOfScreen(DefaultScreenOfDisplay(dpy)), XHeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0); - for_each_active_monitor_output(dpy, GSR_CONNECTION_X11, monitor_output_callback_print, NULL); - _exit(1); - } - } - } - - if(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA) { - if(wayland) { - gsr_capture_kms_cuda_params kms_params; - kms_params.egl = &egl; - kms_params.display_to_capture = window_str; - kms_params.gpu_inf = gpu_inf; - kms_params.card_path = card_path; - capture = gsr_capture_kms_cuda_create(&kms_params); - if(!capture) - _exit(1); - } else { - const char *capture_target = window_str; - bool direct_capture = strcmp(window_str, "screen-direct") == 0; - if(direct_capture) { - capture_target = "screen"; - // TODO: Temporary disable direct capture because push model causes stuttering when it's direct capturing. This might be a nvfbc bug. This does not happen when using a compositor. - direct_capture = false; - fprintf(stderr, "Warning: screen-direct has temporary been disabled as it causes stuttering. This is likely a NvFBC bug. Falling back to \"screen\".\n"); - } - - if(strcmp(window_str, "screen-direct-force") == 0) { - direct_capture = true; - capture_target = "screen"; - } - - gsr_egl_unload(&egl); - - gsr_capture_nvfbc_params nvfbc_params; - nvfbc_params.dpy = dpy; - nvfbc_params.display_to_capture = capture_target; - nvfbc_params.fps = fps; - nvfbc_params.pos = { 0, 0 }; - nvfbc_params.size = { 0, 0 }; - nvfbc_params.direct_capture = direct_capture; - nvfbc_params.overclock = overclock; - capture = gsr_capture_nvfbc_create(&nvfbc_params); - if(!capture) - _exit(1); - } - } else { - gsr_capture_kms_vaapi_params kms_params; - kms_params.egl = &egl; - kms_params.display_to_capture = window_str; - kms_params.gpu_inf = gpu_inf; - kms_params.card_path = card_path; - kms_params.wayland = wayland; - capture = gsr_capture_kms_vaapi_create(&kms_params); - if(!capture) - _exit(1); - } - } else { - if(wayland) { - fprintf(stderr, "Error: GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland\n"); - _exit(2); - } - - errno = 0; - src_window_id = strtol(window_str, nullptr, 0); - if(src_window_id == None || errno == EINVAL) { - fprintf(stderr, "Invalid window number %s\n", window_str); - usage(); - } - } - - if(!capture) { - switch(gpu_inf.vendor) { - case GSR_GPU_VENDOR_AMD: { - gsr_capture_xcomposite_vaapi_params xcomposite_params; - xcomposite_params.egl = &egl; - xcomposite_params.dpy = dpy; - xcomposite_params.window = src_window_id; - xcomposite_params.follow_focused = follow_focused; - xcomposite_params.region_size = region_size; - xcomposite_params.card_path = card_path; - capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params); - if(!capture) - _exit(1); - break; - } - case GSR_GPU_VENDOR_INTEL: { - gsr_capture_xcomposite_vaapi_params xcomposite_params; - xcomposite_params.egl = &egl; - xcomposite_params.dpy = dpy; - xcomposite_params.window = src_window_id; - xcomposite_params.follow_focused = follow_focused; - xcomposite_params.region_size = region_size; - xcomposite_params.card_path = card_path; - capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params); - if(!capture) - _exit(1); - break; - } - case GSR_GPU_VENDOR_NVIDIA: { - gsr_capture_xcomposite_cuda_params xcomposite_params; - xcomposite_params.egl = &egl; - xcomposite_params.dpy = dpy; - xcomposite_params.window = src_window_id; - xcomposite_params.follow_focused = follow_focused; - xcomposite_params.region_size = region_size; - xcomposite_params.overclock = overclock; - capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params); - if(!capture) - _exit(1); - break; - } - } - } - const char *filename = args["-o"].value(); if(filename) { if(replay_buffer_size_secs == -1) { @@ -1944,9 +2003,9 @@ int main(int argc, char **argv) { } if(gpu_inf.vendor != GSR_GPU_VENDOR_NVIDIA && file_extension == "mkv" && strcmp(video_codec_to_use, "h264") == 0) { - video_codec_to_use = "h265"; + video_codec_to_use = "hevc"; video_codec = VideoCodec::HEVC; - fprintf(stderr, "Warning: video codec was forcefully set to h265 because mkv container is used and mesa (AMD and Intel driver) does not support h264 in mkv files\n"); + fprintf(stderr, "Warning: video codec was forcefully set to hevc because mkv container is used and mesa (AMD and Intel driver) does not support h264 in mkv files\n"); } switch(audio_codec) { @@ -1984,8 +2043,8 @@ int main(int argc, char **argv) { if(gpu_inf.vendor == GSR_GPU_VENDOR_INTEL) { const AVCodec *h264_codec = find_h264_encoder(gpu_inf.vendor, card_path); if(!h264_codec) { - fprintf(stderr, "Info: using h265 encoder because a codec was not specified and your gpu does not support h264\n"); - video_codec_to_use = "h265"; + fprintf(stderr, "Info: using hevc encoder because a codec was not specified and your gpu does not support h264\n"); + video_codec_to_use = "hevc"; video_codec = VideoCodec::HEVC; } else { fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n"); @@ -1996,19 +2055,19 @@ int main(int argc, char **argv) { const AVCodec *h265_codec = find_h265_encoder(gpu_inf.vendor, card_path); if(h265_codec && fps > 60) { - fprintf(stderr, "Warning: recording at higher fps than 60 with h265 might result in recording at a very low fps. If this happens, switch to h264\n"); + fprintf(stderr, "Warning: recording at higher fps than 60 with hevc might result in recording at a very low fps. If this happens, switch to h264\n"); } - // h265 generally allows recording at a higher resolution than h264 on nvidia cards. On a gtx 1080 4k is the max resolution for h264 but for h265 it's 8k. - // Another important info is that when recording at a higher fps than.. 60? h265 has very bad performance. For example when recording at 144 fps the fps drops to 1 + // hevc generally allows recording at a higher resolution than h264 on nvidia cards. On a gtx 1080 4k is the max resolution for h264 but for hevc it's 8k. + // Another important info is that when recording at a higher fps than.. 60? hevc has very bad performance. For example when recording at 144 fps the fps drops to 1 // while with h264 the fps doesn't drop. if(!h265_codec) { - fprintf(stderr, "Info: using h264 encoder because a codec was not specified and your gpu does not support h265\n"); + fprintf(stderr, "Info: using h264 encoder because a codec was not specified and your gpu does not support hevc\n"); video_codec_to_use = "h264"; video_codec = VideoCodec::H264; } else { - fprintf(stderr, "Info: using h265 encoder because a codec was not specified\n"); - video_codec_to_use = "h265"; + fprintf(stderr, "Info: using hevc encoder because a codec was not specified\n"); + video_codec_to_use = "hevc"; video_codec = VideoCodec::HEVC; } } @@ -2019,7 +2078,7 @@ int main(int argc, char **argv) { if(video_codec != VideoCodec::H264 && is_flv) { video_codec_to_use = "h264"; video_codec = VideoCodec::H264; - fprintf(stderr, "Warning: h265 is not compatible with flv, falling back to h264 instead.\n"); + fprintf(stderr, "Warning: hevc/av1 is not compatible with flv, falling back to h264 instead.\n"); } const AVCodec *video_codec_f = nullptr; @@ -2028,9 +2087,11 @@ int main(int argc, char **argv) { video_codec_f = find_h264_encoder(gpu_inf.vendor, card_path); break; case VideoCodec::HEVC: + case VideoCodec::HEVC_HDR: video_codec_f = find_h265_encoder(gpu_inf.vendor, card_path); break; case VideoCodec::AV1: + case VideoCodec::AV1_HDR: video_codec_f = find_av1_encoder(gpu_inf.vendor, card_path); break; } @@ -2038,20 +2099,22 @@ int main(int argc, char **argv) { if(!video_codec_auto && !video_codec_f && !is_flv) { switch(video_codec) { case VideoCodec::H264: { - fprintf(stderr, "Warning: selected video codec h264 is not supported, trying h265 instead\n"); - video_codec_to_use = "h265"; + fprintf(stderr, "Warning: selected video codec h264 is not supported, trying hevc instead\n"); + video_codec_to_use = "hevc"; video_codec = VideoCodec::HEVC; video_codec_f = find_h265_encoder(gpu_inf.vendor, card_path); break; } - case VideoCodec::HEVC: { - fprintf(stderr, "Warning: selected video codec h265 is not supported, trying h264 instead\n"); + case VideoCodec::HEVC: + case VideoCodec::HEVC_HDR: { + fprintf(stderr, "Warning: selected video codec hevc is not supported, trying h264 instead\n"); video_codec_to_use = "h264"; video_codec = VideoCodec::H264; video_codec_f = find_h264_encoder(gpu_inf.vendor, card_path); break; } - case VideoCodec::AV1: { + case VideoCodec::AV1: + case VideoCodec::AV1_HDR: { fprintf(stderr, "Warning: selected video codec av1 is not supported, trying h264 instead\n"); video_codec_to_use = "h264"; video_codec = VideoCodec::H264; @@ -2068,11 +2131,13 @@ int main(int argc, char **argv) { video_codec_name = "h265"; break; } - case VideoCodec::HEVC: { + case VideoCodec::HEVC: + case VideoCodec::HEVC_HDR: { video_codec_name = "h265"; break; } - case VideoCodec::AV1: { + case VideoCodec::AV1: + case VideoCodec::AV1_HDR: { video_codec_name = "av1"; break; } @@ -2088,6 +2153,8 @@ int main(int argc, char **argv) { _exit(2); } + gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, gpu_inf, egl, card_path, dpy, fps, overclock, video_codec, color_range); + const bool is_livestream = is_livestream_path(filename); // (Some?) livestreaming services require at least one audio track to work. // If not audio is provided then create one silent audio track. @@ -2111,8 +2178,9 @@ int main(int argc, char **argv) { AVStream *video_stream = nullptr; std::vector<AudioTrack> audio_tracks; + const bool hdr = video_codec_is_hdr(video_codec); - AVCodecContext *video_codec_context = create_video_codec_context(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream, gpu_inf.vendor, framerate_mode); + AVCodecContext *video_codec_context = create_video_codec_context(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream, gpu_inf.vendor, framerate_mode, hdr, color_range); if(replay_buffer_size_secs == -1) video_stream = create_stream(av_format_context, video_codec_context); @@ -2122,7 +2190,7 @@ int main(int argc, char **argv) { _exit(capture_result); } - open_video(video_codec_context, quality, very_old_gpu, gpu_inf.vendor, pixel_format); + open_video(video_codec_context, quality, very_old_gpu, gpu_inf.vendor, pixel_format, hdr); if(video_stream) avcodec_parameters_from_context(video_stream->codecpar, video_codec_context); @@ -2232,6 +2300,7 @@ int main(int argc, char **argv) { double paused_time_offset = 0.0; double paused_time_start = 0.0; + // TODO: Remove? AVFrame *frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Error: Failed to allocate frame\n"); diff --git a/study/color_space_transform_matrix.png b/study/color_space_transform_matrix.png Binary files differnew file mode 100644 index 0000000..2b7729e5 --- /dev/null +++ b/study/color_space_transform_matrix.png diff --git a/study/create_matrix.py b/study/create_matrix.py new file mode 100755 index 0000000..96bfcd4 --- /dev/null +++ b/study/create_matrix.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +import sys + +def usage(): + print("usage: Kr Kg Kb full|limited") + print("examples:") + print(" create_matrix.py 0.2126 0.7152 0.0722 full") + print(" create_matrix.py 0.2126 0.7152 0.0722 limited") + exit(1) + +def main(argv): + if len(argv) != 5: + usage() + + Kr = float(sys.argv[1]) + Kg = float(sys.argv[2]) + Kb = float(sys.argv[3]) + color_range = sys.argv[4] + luma_offset = 0.0 + transform_range = 1.0 + + if color_range == "full": + pass + elif color_range == "limited": + transform_range = (235.0 - 16.0) / 255.0 + luma_offset = 16.0 / 255.0 + + matrix = [ + [Kr, Kg, Kb], + [-0.5 * (Kr / (1.0 - Kb)), -0.5 * (Kg / (1.0 - Kb)), 0.5], + [0.5, -0.5 * (Kg / (1.0 - Kr)), -0.5 * (Kb / (1.0 -Kr))], + [0.0, 0.5, 0.5] + ] + + # Transform from row major to column major for glsl + print("%f, %f, %f, %f" % (matrix[0][0] * transform_range, matrix[1][0] * transform_range, matrix[2][0] * transform_range, 0.0)) + print("%f, %f, %f, %f" % (matrix[0][1] * transform_range, matrix[1][1] * transform_range, matrix[2][1] * transform_range, 0.0)) + print("%f, %f, %f, %f" % (matrix[0][2] * transform_range, matrix[1][2] * transform_range, matrix[2][2] * transform_range, 0.0)) + print("%f, %f, %f, %f" % (matrix[3][0] + luma_offset, matrix[3][1], matrix[3][2], 1.0)) + +main(sys.argv) |