From 74ba2b35151e0dbb4073cd9bcd3b76275c4fe770 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Tue, 20 Sep 2011 16:27:59 +0800 Subject: [PATCH] tsmf: add XVideo support in xfreerdp. --- client/X11/CMakeLists.txt | 9 + client/X11/xf_tsmf.c | 370 ++++++++++++++++++++++++++++++++++++++ client/X11/xf_tsmf.h | 29 +++ client/X11/xfreerdp.c | 47 +++-- client/X11/xfreerdp.h | 1 + cmake/ConfigOptions.cmake | 1 + cmake/FindXv.cmake | 49 +++++ config.h.in | 1 + 8 files changed, 497 insertions(+), 10 deletions(-) create mode 100644 client/X11/xf_tsmf.c create mode 100644 client/X11/xf_tsmf.h create mode 100644 cmake/FindXv.cmake diff --git a/client/X11/CMakeLists.txt b/client/X11/CMakeLists.txt index 7fb7bb0a8..835c089bf 100644 --- a/client/X11/CMakeLists.txt +++ b/client/X11/CMakeLists.txt @@ -25,6 +25,8 @@ add_executable(xfreerdp xf_gdi.h xf_rail.c xf_rail.h + xf_tsmf.c + xf_tsmf.h xf_event.c xf_event.h xf_keyboard.c @@ -72,6 +74,13 @@ if(Xcursor_FOUND) target_link_libraries(xfreerdp ${Xcursor_LIBRARIES}) endif() +find_suggested_package(Xv) +if(XV_FOUND) + add_definitions(-DWITH_XV) + include_directories(${XV_INCLUDE_DIRS}) + target_link_libraries(xfreerdp ${XV_LIBRARIES}) +endif() + target_link_libraries(xfreerdp freerdp-core) target_link_libraries(xfreerdp freerdp-gdi) target_link_libraries(xfreerdp freerdp-kbd) diff --git a/client/X11/xf_tsmf.c b/client/X11/xf_tsmf.c new file mode 100644 index 000000000..8f51ecf32 --- /dev/null +++ b/client/X11/xf_tsmf.c @@ -0,0 +1,370 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * X11 Video Redirection + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xf_tsmf.h" + +#ifdef WITH_XV + +#include +#include + +typedef struct xf_xv_context xfXvContext; + +struct xf_xv_context +{ + long xv_port; + Atom xv_colorkey_atom; + int xv_image_size; + int xv_shmid; + char* xv_shmaddr; + uint32* xv_pixfmts; +}; + +#ifdef WITH_DEBUG_XV +#define DEBUG_XV(fmt, ...) DEBUG_CLASS(XV, fmt, ## __VA_ARGS__) +#else +#define DEBUG_XV(fmt, ...) DEBUG_NULL(fmt, ## __VA_ARGS__) +#endif + +void xf_tsmf_init(xfInfo* xfi, long xv_port) +{ + int ret; + unsigned int i; + unsigned int version; + unsigned int release; + unsigned int event_base; + unsigned int error_base; + unsigned int request_base; + unsigned int num_adaptors; + xfXvContext* xv; + XvAdaptorInfo* ai; + XvAttribute* attr; + XvImageFormatValues* fo; + + xv = xnew(xfXvContext); + xfi->xv_context = xv; + + xv->xv_colorkey_atom = None; + xv->xv_image_size = 0; + xv->xv_port = xv_port; + + if (!XShmQueryExtension(xfi->display)) + { + DEBUG_XV("no shmem available."); + return; + } + + ret = XvQueryExtension(xfi->display, &version, &release, &request_base, &event_base, &error_base); + if (ret != Success) + { + DEBUG_XV("XvQueryExtension failed %d.", ret); + return; + } + DEBUG_XV("version %u release %u", version, release); + + ret = XvQueryAdaptors(xfi->display, DefaultRootWindow(xfi->display), + &num_adaptors, &ai); + if (ret != Success) + { + DEBUG_XV("XvQueryAdaptors failed %d.", ret); + return; + } + + for (i = 0; i < num_adaptors; i++) + { + DEBUG_XV("adapter port %ld-%ld (%s)", ai[i].base_id, + ai[i].base_id + ai[i].num_ports - 1, ai[i].name); + if (xv->xv_port == 0 && i == num_adaptors - 1) + xv->xv_port = ai[i].base_id; + } + + if (num_adaptors > 0) + XvFreeAdaptorInfo(ai); + + if (xv->xv_port == 0) + { + DEBUG_XV("no adapter selected, video frames will not be processed."); + return; + } + DEBUG_XV("selected %ld", xv->xv_port); + + attr = XvQueryPortAttributes(xfi->display, xv->xv_port, &ret); + for (i = 0; i < (unsigned int)ret; i++) + { + if (strcmp(attr[i].name, "XV_COLORKEY") == 0) + { + xv->xv_colorkey_atom = XInternAtom(xfi->display, "XV_COLORKEY", False); + XvSetPortAttribute(xfi->display, xv->xv_port, xv->xv_colorkey_atom, attr[i].min_value + 1); + break; + } + } + XFree(attr); + +#ifdef WITH_DEBUG_XV + printf("xf_tsmf_init: pixel format "); +#endif + fo = XvListImageFormats(xfi->display, xv->xv_port, &ret); + if (ret > 0) + { + xv->xv_pixfmts = (uint32*) xzalloc((ret + 1) * sizeof(uint32)); + for (i = 0; i < ret; i++) + { + xv->xv_pixfmts[i] = fo[i].id; +#ifdef WITH_DEBUG_XV + printf("%c%c%c%c ", ((char*)(xv->xv_pixfmts + i))[0], ((char*)(xv->xv_pixfmts + i))[1], + ((char*)(xv->xv_pixfmts + i))[2], ((char*)(xv->xv_pixfmts + i))[3]); +#endif + } + xv->xv_pixfmts[i] = 0; + } + XFree(fo); +#ifdef WITH_DEBUG_XV + printf("\n"); +#endif +} + +void xf_tsmf_uninit(xfInfo* xfi) +{ + xfXvContext* xv = (xfXvContext*) xfi->xv_context; + + if (xv) + { + if (xv->xv_image_size > 0) + { + shmdt(xv->xv_shmaddr); + shmctl(xv->xv_shmid, IPC_RMID, NULL); + } + if (xv->xv_pixfmts) + { + xfree(xv->xv_pixfmts); + xv->xv_pixfmts = NULL; + } + xfree(xv); + xfi->xv_context = NULL; + } +} + +static boolean +xf_tsmf_is_format_supported(xfXvContext* xv, uint32 pixfmt) +{ + int i; + + if (!xv->xv_pixfmts) + return False; + + for (i = 0; xv->xv_pixfmts[i]; i++) + { + if (xv->xv_pixfmts[i] == pixfmt) + return True; + } + + return False; +} + +static void xf_process_tsmf_video_frame_event(xfInfo* xfi, RDP_VIDEO_FRAME_EVENT* vevent) +{ + int i; + uint8* data1; + uint8* data2; + uint32 pixfmt; + XvImage * image; + int colorkey = 0; + XShmSegmentInfo shminfo; + xfXvContext* xv = (xfXvContext*) xfi->xv_context; + + if (xv->xv_port == 0) + return; + + /* In case the player is minimized */ + if (vevent->x < -2048 || vevent->y < -2048 || vevent->num_visible_rects <= 0) + return; + + if (xv->xv_colorkey_atom != None) + { + XvGetPortAttribute(xfi->display, xv->xv_port, xv->xv_colorkey_atom, &colorkey); + XSetFunction(xfi->display, xfi->gc, GXcopy); + XSetFillStyle(xfi->display, xfi->gc, FillSolid); + XSetForeground(xfi->display, xfi->gc, colorkey); + for (i = 0; i < vevent->num_visible_rects; i++) + { + XFillRectangle(xfi->display, xfi->window->handle, xfi->gc, + vevent->x + vevent->visible_rects[i].x, + vevent->y + vevent->visible_rects[i].y, + vevent->visible_rects[i].width, + vevent->visible_rects[i].height); + } + } + + pixfmt = vevent->frame_pixfmt; + image = XvShmCreateImage(xfi->display, xv->xv_port, + pixfmt, 0, vevent->frame_width, vevent->frame_height, &shminfo); + if (xv->xv_image_size != image->data_size) + { + if (xv->xv_image_size > 0) + { + shmdt(xv->xv_shmaddr); + shmctl(xv->xv_shmid, IPC_RMID, NULL); + } + xv->xv_image_size = image->data_size; + xv->xv_shmid = shmget(IPC_PRIVATE, image->data_size, IPC_CREAT | 0777); + xv->xv_shmaddr = shmat(xv->xv_shmid, 0, 0); + } + shminfo.shmid = xv->xv_shmid; + shminfo.shmaddr = image->data = xv->xv_shmaddr; + shminfo.readOnly = False; + + if (!XShmAttach(xfi->display, &shminfo)) + { + XFree(image); + DEBUG_XV("XShmAttach failed."); + return; + } + + /* The video driver may align each line to a different size + and we need to convert our original image data. */ + switch (pixfmt) + { + case RDP_PIXFMT_I420: + case RDP_PIXFMT_YV12: + if (!xf_tsmf_is_format_supported(xv, RDP_PIXFMT_I420) && + !xf_tsmf_is_format_supported(xv, RDP_PIXFMT_YV12)) + { + DEBUG_XV("pixel format 0x%X not supported by hardware.", pixfmt); + break; + } + /* Y */ + if (image->pitches[0] == vevent->frame_width) + { + memcpy(image->data + image->offsets[0], + vevent->frame_data, + vevent->frame_width * vevent->frame_height); + } + else + { + for (i = 0; i < vevent->frame_height; i++) + { + memcpy(image->data + image->offsets[0] + i * image->pitches[0], + vevent->frame_data + i * vevent->frame_width, + vevent->frame_width); + } + } + /* UV */ + /* Conversion between I420 and YV12 is to simply swap U and V */ + if (xf_tsmf_is_format_supported(xv, pixfmt)) + { + data1 = vevent->frame_data + vevent->frame_width * vevent->frame_height; + data2 = vevent->frame_data + vevent->frame_width * vevent->frame_height + + vevent->frame_width * vevent->frame_height / 4; + } + else + { + data2 = vevent->frame_data + vevent->frame_width * vevent->frame_height; + data1 = vevent->frame_data + vevent->frame_width * vevent->frame_height + + vevent->frame_width * vevent->frame_height / 4; + image->id = pixfmt == RDP_PIXFMT_I420 ? RDP_PIXFMT_YV12 : RDP_PIXFMT_I420; + } + if (image->pitches[1] * 2 == vevent->frame_width) + { + memcpy(image->data + image->offsets[1], + data1, + vevent->frame_width * vevent->frame_height / 4); + memcpy(image->data + image->offsets[2], + data2, + vevent->frame_width * vevent->frame_height / 4); + } + else + { + for (i = 0; i < vevent->frame_height / 2; i++) + { + memcpy(image->data + image->offsets[1] + i * image->pitches[1], + data1 + i * vevent->frame_width / 2, + vevent->frame_width / 2); + memcpy(image->data + image->offsets[2] + i * image->pitches[2], + data2 + i * vevent->frame_width / 2, + vevent->frame_width / 2); + } + } + break; + + default: + memcpy(image->data, vevent->frame_data, image->data_size <= vevent->frame_size ? + image->data_size : vevent->frame_size); + break; + } + + XvShmPutImage(xfi->display, xv->xv_port, xfi->window->handle, xfi->gc, image, + 0, 0, image->width, image->height, + vevent->x, vevent->y, vevent->width, vevent->height, False); + XSync(xfi->display, False); + + XShmDetach(xfi->display, &shminfo); + XFree(image); +} + +static void xf_process_tsmf_redraw_event(xfInfo* xfi, RDP_REDRAW_EVENT* revent) +{ + XSetFunction(xfi->display, xfi->gc, GXcopy); + XSetFillStyle(xfi->display, xfi->gc, FillSolid); + XCopyArea(xfi->display, xfi->primary, xfi->window->handle, xfi->gc, + revent->x, revent->y, revent->width, revent->height, revent->x, revent->y); +} + +void xf_process_tsmf_event(xfInfo* xfi, RDP_EVENT* event) +{ + switch (event->event_type) + { + case RDP_EVENT_TYPE_TSMF_VIDEO_FRAME: + xf_process_tsmf_video_frame_event(xfi, (RDP_VIDEO_FRAME_EVENT*) event); + break; + + case RDP_EVENT_TYPE_TSMF_REDRAW: + xf_process_tsmf_redraw_event(xfi, (RDP_REDRAW_EVENT*) event); + break; + + } +} + +#else /* WITH_XV */ + +void xf_tsmf_init(xfInfo* xfi, long xv_port) +{ +} + +void xf_tsmf_uninit(xfInfo* xfi) +{ +} + +void xf_process_tsmf_event(xfInfo* xfi, RDP_EVENT* event) +{ +} + +#endif /* WITH_XV */ diff --git a/client/X11/xf_tsmf.h b/client/X11/xf_tsmf.h new file mode 100644 index 000000000..09083e263 --- /dev/null +++ b/client/X11/xf_tsmf.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * X11 Video Redirection + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __XF_TSMF_H +#define __XF_TSMF_H + +#include "xfreerdp.h" + +void xf_tsmf_init(xfInfo* xfi, long xv_port); +void xf_tsmf_uninit(xfInfo* xfi); +void xf_process_tsmf_event(xfInfo* xfi, RDP_EVENT* event); + +#endif /* __XF_TSMF_H */ diff --git a/client/X11/xfreerdp.c b/client/X11/xfreerdp.c index a572b28f3..5e2e9ad7a 100644 --- a/client/X11/xfreerdp.c +++ b/client/X11/xfreerdp.c @@ -46,6 +46,7 @@ #include "xf_gdi.h" #include "xf_rail.h" +#include "xf_tsmf.h" #include "xf_event.h" #include "xf_monitor.h" #include "xf_keyboard.h" @@ -55,6 +56,8 @@ freerdp_sem g_sem; static int g_thread_count = 0; +static long xv_port = 0; + struct thread_data { freerdp* instance; @@ -463,11 +466,15 @@ boolean xf_post_connect(freerdp* instance) freerdp_chanman_post_connect(GET_CHANMAN(instance), instance); + xf_tsmf_init(xfi, xv_port); + return True; } int xf_process_ui_args(rdpSettings* settings, const char* opt, const char* val, void* user_data) { + int argc = 0; + if (strcmp("--kbd-list", opt) == 0) { int i; @@ -493,8 +500,13 @@ int xf_process_ui_args(rdpSettings* settings, const char* opt, const char* val, exit(0); } + else if (strcmp("--xv-port", opt) == 0) + { + xv_port = atoi(val); + argc = 2; + } - return 1; + return argc; } int xf_process_plugin_args(rdpSettings* settings, const char* name, RDP_PLUGIN_DATA* plugin_data, void* user_data) @@ -512,7 +524,7 @@ int xf_receive_channel_data(freerdp* instance, int channelId, uint8* data, int s return freerdp_chanman_data(instance, channelId, data, size, flags, total_size); } -void xf_process_cb_sync_event(rdpChanMan* chanman, freerdp* instance) +void xf_process_cb_sync_event(xfInfo* xfi, rdpChanMan* chanman) { RDP_EVENT* event; RDP_CB_FORMAT_LIST_EVENT* format_list_event; @@ -525,6 +537,19 @@ void xf_process_cb_sync_event(rdpChanMan* chanman, freerdp* instance) freerdp_chanman_send_event(chanman, event); } +void xf_process_cliprdr_event(xfInfo* xfi, rdpChanMan* chanman, RDP_EVENT* event) +{ + switch (event->event_type) + { + case RDP_EVENT_TYPE_CB_SYNC: + xf_process_cb_sync_event(xfi, chanman); + break; + + default: + break; + } +} + void xf_process_channel_event(rdpChanMan* chanman, freerdp* instance) { xfInfo* xfi; @@ -542,18 +567,18 @@ void xf_process_channel_event(rdpChanMan* chanman, freerdp* instance) xf_process_rail_event(xfi, chanman, event); break; + case RDP_EVENT_CLASS_TSMF: + xf_process_tsmf_event(xfi, event); + break; + + case RDP_EVENT_CLASS_CLIPRDR: + xf_process_cliprdr_event(xfi, chanman, event); + break; + default: break; } - switch (event->event_type) - { - case RDP_EVENT_TYPE_CB_SYNC: - xf_process_cb_sync_event(chanman, instance); - break; - default: - break; - } freerdp_event_free(event); } } @@ -590,6 +615,8 @@ void xf_window_free(xfInfo* xfi) xfree(xfi->clrconv); rail_free(xfi->rail); + + xf_tsmf_uninit(xfi); } void xf_free(xfInfo* xfi) diff --git a/client/X11/xfreerdp.h b/client/X11/xfreerdp.h index af09cca5f..089404b98 100644 --- a/client/X11/xfreerdp.h +++ b/client/X11/xfreerdp.h @@ -93,6 +93,7 @@ struct xf_info VIRTUAL_SCREEN vscreen; uint8* bmp_codec_none; void* rfx_context; + void* xv_context; Atom _NET_WM_ICON; Atom _MOTIF_WM_HINTS; diff --git a/cmake/ConfigOptions.cmake b/cmake/ConfigOptions.cmake index 9715771bc..285410876 100644 --- a/cmake/ConfigOptions.cmake +++ b/cmake/ConfigOptions.cmake @@ -11,6 +11,7 @@ option(WITH_DEBUG_GDI "Print graphics debug messages." OFF) option(WITH_DEBUG_RFX "Print RemoteFX debug messages." OFF) option(WITH_DEBUG_X11 "Print X11 Client debug messages" OFF) option(WITH_DEBUG_RAIL "Print RemoteApp debug messages" OFF) +option(WITH_DEBUG_XV "Print XVideo debug messages" OFF) option(WITH_MANPAGES "Generate manpages." ON) option(WITH_PROFILER "Compile profiler." OFF) option(WITH_SSE2 "Use SSE2 optimization." OFF) diff --git a/cmake/FindXv.cmake b/cmake/FindXv.cmake new file mode 100644 index 000000000..b95fb99f1 --- /dev/null +++ b/cmake/FindXv.cmake @@ -0,0 +1,49 @@ +# - Find Xv +# Find the Xv libraries +# +# This module defines the following variables: +# XV_FOUND - True if XV_INCLUDE_DIR & XV_LIBRARY are found +# XV_LIBRARIES - Set when XV_LIBRARY is found +# XV_INCLUDE_DIRS - Set when XV_INCLUDE_DIR is found +# +# XV_INCLUDE_DIR - where to find Xv.h, etc. +# XV_LIBRARY - the Xv library +# + +#============================================================================= +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +find_path(XV_INCLUDE_DIR NAMES Xv.h + PATH_SUFFIXES X11/extensions + DOC "The Xv include directory" +) + +find_library(XV_LIBRARY NAMES Xv + DOC "The Xv library" +) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(XV DEFAULT_MSG XV_LIBRARY XV_INCLUDE_DIR) + +if(XV_FOUND) + set( XV_LIBRARIES ${XV_LIBRARY} ) + set( XV_INCLUDE_DIRS ${XV_INCLUDE_DIR} ) +endif() + +mark_as_advanced(XV_INCLUDE_DIR XV_LIBRARY) + diff --git a/config.h.in b/config.h.in index 9eea02667..9b7c01f26 100644 --- a/config.h.in +++ b/config.h.in @@ -33,5 +33,6 @@ #cmakedefine WITH_SSE2 #cmakedefine WITH_DEBUG_X11 #cmakedefine WITH_DEBUG_RAIL +#cmakedefine WITH_DEBUG_XV #endif