fruncm server sql 无法生成 线程_FreeRDP(4/5):Server处理图像

在windows,编译freerdp会生成两个server:wfreerdp-server.exe和freerdp-shadow-cli.exe。

  • wfreerdp-server.exe。抓屏支持dxgi和Mirage Driver,Mirage Driver是较早技术,win10已不再支持,因而实际可用的是dxgi。编码图像固定使用RemoteFX,不支持使用自定义h264压缩,像openh264。由于RemoteFX有非常好性能,client连接它时画面流畅,即使server桌面是2560x1600这种高分辨率。
  • freerdp-shadow-cli.exe。抓屏支持dxgi和wds(Windows Desktop Sharing)。如不改源码,默认使用wds。压缩图像可使用h264,传输编码使码gfx([MS-RDPEGFX])。由于压缩效率不如RemoteFX,server高分辨率时,client显示会有较大延时,感觉是个实验室产品。

从流畅角度,windows自然推荐使用wfreerdp-server.exe,但考虑到有人学FreeRDP是为在其它平台上写rdp server,像Android,那些平台很难使用RemoteFX,为此本文介绍能使用H264压缩的freerdp-shadow-cli.exe。有说到该exe抓屏支持dxgi或wds,本文不深入抓屏技术,但考虑到wds要比dxgi复杂很多,如果只是想搞清抓屏后面逻辑、又不在乎抓到图像是否正确,做以下修改可让工作在dxgi。

1、<FreeRDP>/server/shadow/Win/win_dxgi.h,去掉注释WITH_DXGI_1_2宏。
2、<FreeRDP>/server/shadow/Win/win_dxgi.c。
textureDesc.Width = subsystem->width;
textureDesc.Height = subsystem->height;
改为
textureDesc.Width = subsystem->base.monitors[0].right - subsystem->base.monitors[0].left;
textureDesc.Height = subsystem->base.monitors[0].bottom - subsystem->base.monitors[0].top;
注:估计freerdp已不再用subsystem->width、subsystem->height,它们总是0。monitors[0]存放着屏幕尺寸。width、height不对时,接下的CreateTexture2D会返回失败。
3、不编译<FreeRDP>/server/shadow/Win/win_rdp.c、<FreeRDP>/server/shadow/Win/win_wds.c
4、<FreeRDP>/server/shadow/Win/win_wds.h。注释宏WITH_WDS_API。

或许,我们很在乎怎么在rdp中使用h264。这里先说rdp协议是怎么确定此次会话能使用h264,主要关心client发来的两个pdu。

Client MCS Connect Initial PDU with GCC Conference Create Request。这是在连接阶段client发来的一个pdu。包含Client Core Data(TS_UD_CS_CORE),该数据中有个2字节的earlyCapabilityFlags,如果设了RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL标记,表示client支持gfx(SupportGraphicsPipeline)。一旦发现这标记,server可考虑使用h264压缩了。至于h264要压缩哪些数据,像需要一个还是两个h264流,这取决于第二个字段。

RDPGFX_CAPS_ADVERTISE_PDU。[MS-RDPEGFX]定义的pdu,也是client发出,内中包含若干个指示客户端能力的RDPGFX_CAPSET。server检查逻辑是这样的:每个RDPGFX_CAPSET有版本字段,检查从高版本到低版本,一旦某个RDPGFX_CAPSET可以用了,就以它为准,不再检查更低版本。假设要使用的版本是RDPGFX_CAPVERSION_105,RDPGFX_CAPSET有个字段叫flags,flags支持的标记中RDPGFX_CAPS_FLAG_AVC_DISABLED,一旦设置该标记,表示client不支持MPEG-4 AVC/H.264解码,针对该标记,freerdp server会执行以下赋值。

settings->GfxSmallCache = (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE);
settings->GfxAVC444v2 = settings->GfxAVC444 = settings->GfxH264 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);

下文认为client没有设置RDPGFX_CAPS_FLAG_AVC_DISABLED标志,即GfxAVC444v2、GfxAVC444、GfxH264都是true。

注:为强制使用openh264,config.h不要定义WITH_MEDIA_FOUNDATION宏,因为MF的优先级要比openh264高。

接下分析server如何处理图像,把过程分三个阶段:抓屏、编码、发送。

一、抓屏

8f52ad8f24ff3fbfb7ddff1844b3b4e7.png
图1 抓屏

图1显示的抓拍使用wds技术。dxgi不存左侧的抓拍线程,没有RdpUpdateEnterEvent、RdpUpdateLeaveEvent这两个同步变量,子系统线程抓到一帧后立即调用win_shadow_surface_copy。也就是说,无论哪种技术都会调用win_shadow_surface_copy。

总的来说,子系统线程抓到一帧,图像放在server->surface->data,设置event->event有信号,死等event->doneEvent。因为event->event有信号了,所有会话线程去处理新帧,shadow_client_send_surface_update是处理新帧的函数。所有会话线程处理完这帧后,最后一个处理完的会话线程负责设置event->doneEvent有信号,触发子系统线程去抓下一帧。当中存在两个同步,1)子系统线程必须等到所有会话线程处理完后才能去抓下一帧;2)所有client要等到其它client都处理完才会执行下一个操作。

rdp_shadow_multiclient_event是抓帧同步中一个重要结构,当中光事件就有三个,event、barrierEvent和doneEvent,为什么会这么复杂,——因为会存在多个client。各线程如何使用这三个事件分两种场景,一种是当前只有一个client,一种是当前至少有两个client。

只一个client。会话线程发现event->event有信号,调用shadow_client_send_surface_update执行处理。处理完后,因为只一个,它自然是最后一个处理屏幕帧的client,于是把doneEvent置为有信号。doneEvent有信号触发子线程去抓下一帧。整个过程不涉及到barrierEvent。

至少有两个client。这时要用到barrierEvent、waiting。如果它不是处理完帧的最后一个client,那不去设置event->doneEvent有信号,而是依次执行把waiting+1、等待barrierEvent有信号。当最后一个client处理完成后,发现waiting不是0,改为设置barrierEvent有信号。之前那些处理好的、正等barrierEvent的client会被唤醒,唤醒后waiting-1,-1后waiting为0表示已没有client在等了,于是置event->doneEvent有信号。

waiting表示正等待barrierEvent有信号的client个数。什么样的client会去等待barrierEvent有信号?那些处理完新帧、但又不是最后一个处理完新帧的client。consuming指的是系统中还没处理新帧的client数。

二、编码

574517ed8d139592943645671a54d516.png
图2 [MS-RDPEGFX] RFX_AVC444V2_BITMAP_STREAM中LC

surface->data存着图像数据,格式是RGB,进入h264压缩前须要转成YUV420P。和惯常h264压缩不一样,rdp有可能进行两次h264压缩(图2中LC=0时),当然,两次压缩用的是两份不同YUV420P数据。一次是惯常认为的,生成的YUV420P数据放在h264->pYUV444Data,[MS-RDPEGFX]称为Main View,LC(图2)中称YUV420 frame,经h264压缩后放在avc444.bistream[0],gfx编码时放在avc420EncodedBitstream1。第二次用另一种方法生成,生成的YUV420P数据放在h264->pYUVData,[MS-RDPEGFX]称为Auxillary View,LC(图2)中称Chroma420 frame,经h264压缩后放在avc444.bitstream[1],gfx编码时放在avc420EncodedBitstream2。

2.1 RGB转成YUV420P、h264压缩

02a899098a4a36ee3ff72a049bc055f2.png
图3 RGB转成YUV420P

RGB转成YUV420P、h264压缩都是在avc444_compress执行的,以下是参数。

  • pSrcData[IN]。来自surface->data的RGB格式数据。
  • SrcFormat[IN]。pSrcData的RGB格式,BGRA32或BGRX32。
  • nSrcStep[IN]。行距。不论BGRA32还是BGRX32,每像素4字节,nSrcStep都是4*nSrcWidth。
  • nSrcWidth[IN]、nSrcHeight[IN]。屏幕尺寸,像2560x1600。
  • version[IN]。“version = settings->GfxAVC444v2? 2: 1”,本文GfxAVC444v2是ture,因而值是2。
  • op[OUT]。就是图2中的LC。因为总支持两条h264流,值会固定等于0。
  • ppDstData[OUT]、pDstSize[OUT]。指示Main View经h264压缩后数据的存放地址,即avc444.bistream[0]。pDstSize是对应的字节数。
  • ppAuxDstData[OUT]、pAuxDstSize[OUT]。指示Auxillary View经h264压缩后数据的存放地址,即avc444.bistream[1]。pAuxDstSize是对应的字节数。
INT32 avc444_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
                      UINT32 nSrcWidth, UINT32 nSrcHeight, BYTE version, BYTE* op, BYTE** ppDstData,
                      UINT32* pDstSize, BYTE** ppAuxDstData, UINT32* pAuxDstSize)
{
	prim_size_t roi;
	primitives_t* prims = primitives_get();
	BYTE* coded;
	UINT32 codedSize;

	if (!h264)
		return -1;

	if (!h264->subsystem->Compress)
		return -1;

	if (!avc420_ensure_buffer(h264, nSrcStep, nSrcWidth, nSrcHeight))
		return -1;

设置h264->iStride[0:2]、h264->width、h264->heght,分配h264->pYUVData[0:2]的三块内存,每内存块字节数是对应iStride[i]*height。iStride[0:2]指示YUV三分量行距,分别是nSrcStep、nSrcStep/2、nSrcStep/2。这会让人疑惑,对YUV420P,Y分量只占1字节

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值