#include <wayland-tbm-server.h>
#include <pixman.h>
+#define ENABLE_VIDEO_CAPTURE
+
+#ifdef ENABLE_VIDEO_CAPTURE
+#include "e_util_video.h" /* For converting colorspace */
+#endif
+
#define CAPINF(f, ec, str, obj, x...) \
do \
{ \
typedef struct
{
+#ifdef ENABLE_VIDEO_CAPTURE
+ E_Comp_Wl_Buffer_Ref buffer_ref;
+
+ struct
+ {
+ Eina_Rectangle buffer;
+ Eina_Rectangle output;
+ } viewport;
+#endif
+
void *shm_buffer_ptr;
int shm_buffer_stride;
int shm_buffer_h;
E_Capture_Client_Save_End_Cb func_end;
void *data;
+
+#ifdef ENABLE_VIDEO_CAPTURE
+ Capture_Data *video_capture_data;
+#endif
} Thread_Data;
+#ifdef ENABLE_VIDEO_CAPTURE
+static pixman_format_code_t _e_capture_image_data_pixman_format_get_from_tbm_surface(tbm_format format);
+static pixman_format_code_t _e_capture_image_data_pixman_format_get_from_shm_buffer(uint32_t format);
+
+static void
+_e_capture_client_image_viewport_apply(pixman_image_t *image, Eina_Rectangle *src, Eina_Rectangle *dst, uint32_t transform)
+{
+ struct pixman_f_transform ft;
+ pixman_transform_t t;
+ double scale_x, scale_y;
+ int c, s;
+ int tx, ty;
+
+ tx = src->x;
+ ty = src->y;
+
+ switch (transform)
+ {
+ case WL_OUTPUT_TRANSFORM_90:
+ c = 0, s = 1;
+ ty = -(src->x + src->w);
+ break;
+ case WL_OUTPUT_TRANSFORM_180:
+ c = -1, s = 0;
+ tx = -(src->y + src->h);
+ ty = -(src->x + src->w);
+ break;
+ case WL_OUTPUT_TRANSFORM_270:
+ c = 0, s = -1;
+ tx = -(src->y + src->h);
+ break;
+ default:
+ c = 1, s = 0;
+ break;
+ }
+
+ switch (transform)
+ {
+ default:
+ case WL_OUTPUT_TRANSFORM_NORMAL:
+ case WL_OUTPUT_TRANSFORM_180:
+ scale_x = (double)dst->w / (double)src->w;
+ scale_y = (double)dst->h / (double)src->h;
+ break;
+ case WL_OUTPUT_TRANSFORM_90:
+ case WL_OUTPUT_TRANSFORM_270:
+ scale_x = (double)dst->w / (double)src->h;
+ scale_y = (double)dst->h / (double)src->w;
+ break;
+ }
+
+ pixman_f_transform_init_identity(&ft);
+ pixman_f_transform_translate(&ft, NULL, tx, ty);
+ pixman_f_transform_rotate(&ft, NULL, c, s);
+ pixman_f_transform_scale(NULL, &ft, scale_x, scale_y);
+
+ pixman_transform_from_pixman_f_transform(&t, &ft);
+ pixman_image_set_transform(image, &t);
+}
+
+static pixman_image_t *
+_pixman_image_create_with_transform(unsigned char *ptr, int bw, int bh, int stride, Eina_Rectangle *src, Eina_Rectangle *dst, uint32_t transform)
+{
+ pixman_image_t *image;
+ int bpp;
+
+ EINA_SAFETY_ON_FALSE_RETURN_VAL((src->x >= 0), NULL);
+ EINA_SAFETY_ON_FALSE_RETURN_VAL((src->y >= 0), NULL);
+ EINA_SAFETY_ON_FALSE_RETURN_VAL((src->w > 0), NULL);
+ EINA_SAFETY_ON_FALSE_RETURN_VAL((src->h > 0), NULL);
+ EINA_SAFETY_ON_FALSE_RETURN_VAL(((src->x + src->w) <= bw), NULL);
+ EINA_SAFETY_ON_FALSE_RETURN_VAL(((src->y + src->h) <= bh), NULL);
+
+ if ((src->x > 0) || (src->y > 0))
+ {
+ bpp = stride / bw;
+ ptr += (src->y * stride) + (src->x * bpp);
+ }
+
+ image = pixman_image_create_bits(PIXMAN_a8r8g8b8,
+ src->w, src->h,
+ (uint32_t *)ptr,
+ stride);
+ if (!image)
+ {
+ ERR("Failed to create pixman_image_t");
+ return NULL;
+ }
+
+ _e_capture_client_image_viewport_apply(image, src, dst, transform);
+
+ return image;
+}
+
+static void
+_e_capture_client_video_composite(Thread_Data *td, pixman_image_t *target_image)
+{
+ Capture_Data *video_capture_data;
+ tbm_surface_h video_surface;
+ tbm_surface_info_s info;
+ tbm_format format;
+ pixman_image_t *video_image;
+ Eina_Bool need_destroy = EINA_FALSE;
+ int res;
+
+ video_capture_data = td->video_capture_data;
+ video_surface = video_capture_data->tbm_surface;
+
+ format = tbm_surface_get_format(video_surface);
+ switch (format)
+ {
+ case TBM_FORMAT_ARGB8888:
+ case TBM_FORMAT_XRGB8888:
+ break;
+ case TBM_FORMAT_NV12:
+ video_surface = e_util_video_tbm_surface_convert_to_rgb(video_surface);
+ need_destroy = EINA_TRUE;
+ break;
+ default:
+ ERR("Unsupported format(%c%c%c%c)", FOURCC_STR(format));
+ return;
+ }
+
+ res = tbm_surface_map(video_surface, TBM_SURF_OPTION_READ, &info);
+ if (res != TBM_SURFACE_ERROR_NONE)
+ {
+ ERR("Failed to map tbm_surface");
+ goto end;
+ }
+
+ video_image =
+ _pixman_image_create_with_transform(info.planes[0].ptr,
+ tbm_surface_get_width(video_surface),
+ tbm_surface_get_height(video_surface),
+ info.planes[0].stride,
+ &video_capture_data->viewport.buffer,
+ &video_capture_data->viewport.output,
+ video_capture_data->transform);
+ if (!video_image)
+ {
+ ERR("Failed to create pixman_image for video buffer");
+ goto end;
+ }
+
+ pixman_image_composite(PIXMAN_OP_OVER, video_image, NULL, target_image,
+ 0, 0,
+ 0, 0,
+ video_capture_data->viewport.output.x,
+ video_capture_data->viewport.output.y,
+ video_capture_data->viewport.output.w,
+ video_capture_data->viewport.output.h);
+
+ pixman_image_unref(video_image);
+
+end:
+ if (need_destroy)
+ tbm_surface_destroy(video_surface);
+}
+
+static void
+_e_capture_client_main_composite(Thread_Data *td, pixman_image_t *target_image)
+{
+ // for base
+ tbm_surface_info_s info;
+ int tw = 0, th = 0;
+ pixman_image_t *src_img = NULL;
+ pixman_format_code_t src_format;
+ pixman_transform_t t;
+ struct pixman_f_transform ft;
+ unsigned char *src_ptr = NULL;
+ int c = 0, s = 0, tx = 0, ty = 0;
+ int w, h;
+
+ // for child
+ tbm_surface_h c_dst_surface = NULL;
+ tbm_surface_info_s c_info;
+ pixman_image_t *c_src_img = NULL, *c_dst_img = NULL;
+ pixman_format_code_t c_src_format, c_dst_format;
+ pixman_transform_t c_t;
+ struct pixman_f_transform c_ft;
+ unsigned char *c_src_ptr = NULL, *c_dst_ptr = NULL;
+ int c_x, c_y, c_w, c_h;
+ int c_tx, c_ty, c_tw, c_th;
+
+ EINA_SAFETY_ON_NULL_RETURN(td);
+
+ if (td->transform > WL_OUTPUT_TRANSFORM_270) return;
+
+ if (td->tbm_surface)
+ {
+ src_format = _e_capture_image_data_pixman_format_get_from_tbm_surface(tbm_surface_get_format(td->tbm_surface));
+
+ tbm_surface_map(td->tbm_surface, TBM_SURF_OPTION_READ, &info);
+ src_ptr = info.planes[0].ptr;
+
+ w = tbm_surface_get_width(td->tbm_surface);
+ h = tbm_surface_get_height(td->tbm_surface);
+ src_img = pixman_image_create_bits(src_format, w, h, (uint32_t*)src_ptr, info.planes[0].stride);
+ EINA_SAFETY_ON_NULL_GOTO(src_img, clean_up);
+ }
+ else if (td->shm_buffer_ptr)
+ {
+ src_format = _e_capture_image_data_pixman_format_get_from_shm_buffer(td->shm_buffer_format);
+
+ src_ptr = td->shm_buffer_ptr;
+ w = td->shm_buffer_stride;
+ h = td->shm_buffer_h;
+ src_img = pixman_image_create_bits(src_format, w, h, (uint32_t*)src_ptr, w * 4);
+ EINA_SAFETY_ON_NULL_GOTO(src_img, clean_up);
+ }
+ else
+ {
+ ERR("invalid buffer");
+ return;
+ }
+
+ if (td->transform == WL_OUTPUT_TRANSFORM_90)
+ {
+ c = 0, s = -1, tx = -h;
+ tw = h, th = w;
+ }
+ else if (td->transform == WL_OUTPUT_TRANSFORM_180)
+ {
+ c = -1, s = 0, tx = -w, ty = -h;
+ tw = w, th = h;
+ }
+ else if (td->transform == WL_OUTPUT_TRANSFORM_270)
+ {
+ c = 0, s = 1, ty = -w;
+ tw = h, th = w;
+ }
+ else
+ {
+ c = 1, s = 0;
+ tw = w, th = h;
+ }
+
+ pixman_f_transform_init_identity(&ft);
+ pixman_f_transform_translate(&ft, NULL, tx, ty);
+ pixman_f_transform_rotate(&ft, NULL, c, s);
+
+ pixman_transform_from_pixman_f_transform(&t, &ft);
+ pixman_image_set_transform(src_img, &t);
+
+ pixman_image_composite(PIXMAN_OP_OVER, src_img, NULL, target_image, 0, 0, 0, 0, 0, 0, tw, th);
+
+ // for child data
+ if (td->child_data)
+ {
+ c_x = td->child_data->x;
+ c_y = td->child_data->y;
+ c_w = td->child_data->w;
+ c_h = td->child_data->h;
+
+ c_tx = 0;
+ c_ty = 0;
+ c_tw = c_w;
+ c_th = c_h;
+
+ if (td->child_data->tbm_surface)
+ {
+ c_src_format = _e_capture_image_data_pixman_format_get_from_tbm_surface(tbm_surface_get_format(td->child_data->tbm_surface));
+ c_dst_format = c_src_format;
+
+ tbm_surface_map(td->child_data->tbm_surface, TBM_SURF_OPTION_READ, &c_info);
+ c_src_ptr = c_info.planes[0].ptr;
+
+ c_src_img = pixman_image_create_bits(c_src_format, c_w, c_h, (uint32_t*)c_src_ptr, c_info.planes[0].stride);
+ EINA_SAFETY_ON_NULL_GOTO(c_src_img, clean_up);
+ }
+ else if (td->child_data->shm_buffer_ptr)
+ {
+ c_src_format = _e_capture_image_data_pixman_format_get_from_shm_buffer(td->child_data->shm_buffer_format);
+ c_dst_format = c_src_format;
+
+ c_src_ptr = td->child_data->shm_buffer_ptr;
+ c_src_img = pixman_image_create_bits(c_src_format, c_w, c_h, (uint32_t*)c_src_ptr, c_w * 4);
+ EINA_SAFETY_ON_NULL_GOTO(c_src_img, clean_up);
+ }
+ else
+ {
+ ERR("invalid buffer");
+ goto clean_up;
+ }
+
+ if (td->child_data->transform == WL_OUTPUT_TRANSFORM_90)
+ {
+ c = 0, s = -1, c_tx = -c_h;
+ c_tw = c_h, c_th = c_w;
+ }
+ else if (td->child_data->transform == WL_OUTPUT_TRANSFORM_180)
+ {
+ c = -1, s = 0, c_tx = -c_w, c_ty = -c_h;
+ c_tw = c_w, c_th = c_h;
+ }
+ else if (td->child_data->transform == WL_OUTPUT_TRANSFORM_270)
+ {
+ c = 0, s = 1, c_ty = -c_w;
+ c_tw = c_h, c_th = c_w;
+ }
+ else
+ {
+ c = 1, s = 0;
+ c_tw = c_w, c_th = c_h;
+ }
+
+ c_dst_surface = tbm_surface_create(c_tw, c_th, tbm_surface_get_format(td->child_data->tbm_surface));
+ EINA_SAFETY_ON_NULL_GOTO(c_dst_surface, clean_up);
+
+ tbm_surface_map(c_dst_surface, TBM_SURF_OPTION_WRITE, &c_info);
+ c_dst_ptr = c_info.planes[0].ptr;
+
+ c_dst_img = pixman_image_create_bits(c_dst_format, c_tw, c_th, (uint32_t*)c_dst_ptr, c_info.planes[0].stride);
+ EINA_SAFETY_ON_NULL_GOTO(c_dst_img, clean_up);
+
+ pixman_f_transform_init_identity(&c_ft);
+ pixman_f_transform_translate(&c_ft, NULL, c_tx, c_ty);
+ pixman_f_transform_rotate(&c_ft, NULL, c, s);
+
+ pixman_transform_from_pixman_f_transform(&c_t, &c_ft);
+ pixman_image_set_transform(c_src_img, &c_t);
+
+ pixman_image_composite(PIXMAN_OP_SRC, c_src_img, NULL, c_dst_img, 0, 0, 0, 0, 0, 0, c_tw, c_th);
+
+ CAPDBG("image composite with child. child(win:%zx, ec:%p)",
+ td->ec, "ECC", NULL,
+ e_client_util_win_get(td->child_data->ec), td->child_data->ec);
+
+ pixman_image_composite(PIXMAN_OP_OVER, c_dst_img, NULL, target_image, 0, 0, 0, 0, c_x, c_y, tw, th);
+ }
+
+clean_up:
+ if (td->child_data)
+ {
+ if (c_src_ptr && td->child_data->tbm_surface) tbm_surface_unmap(td->child_data->tbm_surface);
+ if (c_dst_ptr) tbm_surface_unmap(c_dst_surface);
+
+ if (c_dst_surface)
+ {
+ tbm_surface_destroy(c_dst_surface);
+ c_dst_surface = NULL;
+ }
+
+ if (c_src_img) pixman_image_unref(c_src_img);
+ if (c_dst_img) pixman_image_unref(c_dst_img);
+ }
+
+ if (src_ptr && td->tbm_surface) tbm_surface_unmap(td->tbm_surface);
+ if (src_img) pixman_image_unref(src_img);
+}
+
+static void
+_pixman_image_fill_black(pixman_image_t *image)
+{
+ pixman_image_t *black_image;
+ pixman_color_t color;
+ int w, h;
+
+ w = pixman_image_get_width(image);
+ h = pixman_image_get_height(image);
+
+ color.red = 0;
+ color.green = 0;
+ color.blue = 0;
+ color.alpha = 0xffff;
+
+ black_image = pixman_image_create_solid_fill(&color);
+
+ pixman_image_composite(PIXMAN_OP_SRC,
+ black_image, /* src */
+ NULL, /* mask */
+ image, /* dst */
+ 0, 0, /* src x,y */
+ 0, 0, /* mask x,y */
+ 0, 0, /* dst x,y */
+ w, h);
+ pixman_image_unref(black_image);
+}
+
+static Eina_Bool
+_e_capture_client_capture_with_video(Thread_Data *td)
+{
+ tbm_surface_h main_surface, capture_surface;
+ tbm_surface_info_s info;
+ pixman_image_t *capture_image;
+ unsigned char *capture_data;
+ int w, h;
+ int res;
+ Eina_Bool ret = EINA_TRUE;
+
+ main_surface = td->tbm_surface;
+ w = tbm_surface_get_width(main_surface);
+ h = tbm_surface_get_height(main_surface);
+ if ((w <= 0) || (h <= 0))
+ {
+ ERR("Abnormal buffer size of main surface %dx%d", w, h);
+ return EINA_FALSE;
+ }
+
+ capture_surface = tbm_surface_create(w, h, TBM_FORMAT_ARGB8888);
+ if (!capture_surface)
+ {
+ ERR("Failed to create tbm_surface_h");
+ return EINA_FALSE;
+ }
+
+ res = tbm_surface_map(capture_surface, TBM_SURF_OPTION_READ | TBM_SURF_OPTION_WRITE, &info);
+ if (res != TBM_SURFACE_ERROR_NONE)
+ {
+ ERR("Failed to map tbm_surface_h");
+ ret = EINA_FALSE;
+ goto err_map;
+ }
+
+ capture_data = info.planes[0].ptr;
+ capture_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, w, h,
+ (uint32_t *)capture_data,
+ info.planes[0].stride);
+ if (!capture_image)
+ {
+ ERR("Failed to create pixman_image for capture");
+ ret = EINA_FALSE;
+ goto err_image;
+ }
+
+ _pixman_image_fill_black(capture_image);
+
+ _e_capture_client_video_composite(td, capture_image);
+ _e_capture_client_main_composite(td, capture_image);
+
+ res = tbm_surface_internal_capture_buffer(capture_surface, td->image_dir, td->image_name, "png");
+ if (res != TBM_SURFACE_ERROR_NONE)
+ {
+ ERR("Failed to capture buffer");
+ ret = EINA_FALSE;
+ }
+
+ pixman_image_unref(capture_image);
+err_image:
+ tbm_surface_unmap(capture_surface);
+err_map:
+ tbm_surface_destroy(capture_surface);
+
+ return ret;
+}
+
+static Capture_Data *
+_e_capture_client_video_data_create(E_Client *ec)
+{
+ Capture_Data *capture_data;
+ E_Comp_Wl_Buffer *buffer = NULL;
+ Eina_Bool res;
+
+ buffer = e_pixmap_resource_get(ec->pixmap);
+ if (!buffer)
+ {
+ ERR("Failed to get resource of E_Pixmap");
+ return NULL;
+ }
+
+ capture_data = E_NEW(Capture_Data, 1);
+ if (!capture_data)
+ return NULL;
+
+ CAPDBG("Create video capture data", ec, "Capture_Data", capture_data);
+
+ /* Is referencing E_Client necessary here?
+ * Is it to prevent E_Client from being destoyed?
+ * It seems not necessary E_Client anywhere during capture process. */
+ e_object_ref(E_OBJECT(ec));
+ capture_data->ec = ec;
+
+ /* Referencing buffer here to prevent compositor from signaling release
+ * event for wl_buffer. */
+ e_comp_wl_buffer_reference(&capture_data->buffer_ref, buffer);
+ switch (buffer->type)
+ {
+ case E_COMP_WL_BUFFER_TYPE_VIDEO:
+ /* capture_data->buffer_ref.buffer can indicate NULL pointer
+ * if wl_buffer is destroyed later. */
+ capture_data->tbm_surface = buffer->tbm_surface;
+ if (!capture_data->tbm_surface) goto err;
+
+ /* Referencing tbm_surface_h to prevent it from being freed. */
+ tbm_surface_internal_ref(capture_data->tbm_surface);
+ break;
+ default:
+ ERR("Unsupported buffer type(%d) win(0x%08zx) name(%s)",
+ buffer->type, e_client_util_win_get(ec), ec->icccm.name?:"");
+ goto err;
+ }
+
+ res = e_comp_wl_surface_viewport_get(ec,
+ &capture_data->viewport.buffer,
+ &capture_data->viewport.output,
+ &capture_data->transform);
+ if (!res)
+ {
+ ERR("Failed to get viewport");
+ goto err_vp;
+ }
+
+ CAPDBG("Buffer Size(%dx%d) Viewport(buffer %d,%d %dx%d output %d,%d %dx%d transform %d)",
+ ec, "Capture_Data", capture_data,
+ buffer->w, buffer->h,
+ EINA_RECTANGLE_ARGS(&capture_data->viewport.buffer),
+ EINA_RECTANGLE_ARGS(&capture_data->viewport.output),
+ capture_data->transform);
+
+ return capture_data;
+err_vp:
+ tbm_surface_internal_unref(capture_data->tbm_surface);
+err:
+ e_object_unref(E_OBJECT(ec));
+ e_comp_wl_buffer_reference(&capture_data->buffer_ref, NULL);
+ free(capture_data);
+ return NULL;
+}
+
+static void
+_e_capture_client_video_data_destroy(Capture_Data *capture_data)
+{
+ CAPDBG("Destroy video capture data", capture_data->ec,
+ "Capture_Data", capture_data);
+
+ if (capture_data->shm_pool)
+ wl_shm_pool_unref(capture_data->shm_pool);
+
+ if (capture_data->tbm_surface)
+ tbm_surface_internal_unref(capture_data->tbm_surface);
+
+ e_comp_wl_buffer_reference(&capture_data->buffer_ref, NULL);
+
+ e_object_unref(E_OBJECT(capture_data->ec));
+
+ free(capture_data);
+}
+
+static Eina_Bool
+_e_capture_client_surface_tree_foreach_cb(void *data, E_Client *ec)
+{
+ Thread_Data *td;
+
+ td = data;
+
+ if (e_object_is_del(E_OBJECT(ec)))
+ return EINA_TRUE;
+
+ /* NOTE: Here only handle video client for minimum modification for now.
+ * The other surfaces won't be handled here. However, it must be
+ * handled later. */
+ if (!e_client_is_video(ec))
+ return EINA_TRUE;
+
+ td->video_capture_data = _e_capture_client_video_data_create(ec);
+ if (!td->video_capture_data)
+ ERR("Failed to create video capture data w:0x%08zx ec:%8p",
+ e_client_util_win_get(ec), ec);
+
+ /* NOTE: Returning EINA_FALSE here means that it doesn't want to iterate
+ * surface tree anymore. Because capturing the video surface is supported
+ * for only one surface now.*/
+ return EINA_FALSE;
+}
+#endif
+
static pixman_format_code_t
_e_capture_image_data_pixman_format_get_from_tbm_surface(tbm_format format)
{
shm_buffer_ptr = td->shm_buffer_ptr;
tbm_surface = td->tbm_surface;
+#ifdef ENABLE_VIDEO_CAPTURE
+ if (td->video_capture_data)
+ {
+ ret = _e_capture_client_capture_with_video(td);
+ if (!ret)
+ {
+ CAPDBG("IMG save fail: %s/%s.png",
+ td->ec, "ECC", NULL, td->image_dir, td->image_name);
+ return EINA_FALSE;
+ }
+ }
+ else if (shm_buffer_ptr)
+#else
if (shm_buffer_ptr)
+#endif
{
stride = td->shm_buffer_stride;
w = stride / 4;
void
_e_capture_client_save_thread_data_free(Thread_Data *td)
{
+#ifdef ENABLE_VIDEO_CAPTURE
+ if (td->video_capture_data)
+ _e_capture_client_video_data_destroy(td->video_capture_data);
+#endif
+
if (td->child_data)
{
_e_capture_client_child_data_release(td);
goto end;
}
+#ifdef ENABLE_VIDEO_CAPTURE
+ /* Don't iterate surface tree if it's video client. */
+ if (!e_client_is_video(td->ec))
+ e_client_surface_tree_foreach(ec,
+ _e_capture_client_surface_tree_foreach_cb,
+ td);
+#endif
+
if (!skip_child)
_e_capture_client_child_data_check(td);