FreeRDP
Loading...
Searching...
No Matches
video_main.c
1
20#include <freerdp/config.h>
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25
26#include <winpr/crt.h>
27#include <winpr/assert.h>
28#include <winpr/cast.h>
29#include <winpr/synch.h>
30#include <winpr/print.h>
31#include <winpr/stream.h>
32#include <winpr/cmdline.h>
33#include <winpr/collections.h>
34#include <winpr/interlocked.h>
35#include <winpr/sysinfo.h>
36
37#include <freerdp/freerdp.h>
38#include <freerdp/addin.h>
39#include <freerdp/primitives.h>
40#include <freerdp/client/channels.h>
41#include <freerdp/client/geometry.h>
42#include <freerdp/client/video.h>
43#include <freerdp/channels/log.h>
44#include <freerdp/codec/h264.h>
45#include <freerdp/codec/yuv.h>
46#include <freerdp/timer.h>
47
48#define TAG CHANNELS_TAG("video.client")
49
50#include "video_main.h"
51
52typedef struct
53{
54 IWTSPlugin wtsPlugin;
55
56 IWTSListener* controlListener;
57 IWTSListener* dataListener;
58 GENERIC_LISTENER_CALLBACK* control_callback;
59 GENERIC_LISTENER_CALLBACK* data_callback;
60
61 VideoClientContext* context;
62 BOOL initialized;
63 rdpContext* rdpcontext;
64 BOOL connected;
65 BOOL dataConnected;
66} VIDEO_PLUGIN;
67
68#define XF_VIDEO_UNLIMITED_RATE 31
69
70static const BYTE MFVideoFormat_H264[] = { 'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00,
71 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 };
72
73typedef struct
74{
75 BYTE PresentationId;
76 UINT32 ScaledWidth;
77 UINT32 ScaledHeight;
78
79 UINT64 startTimeStamp;
80 UINT64 publishOffset;
81 wStream* currentSample;
82 UINT64 lastPublishTime;
83 UINT64 nextPublishTime;
84 volatile LONG refCounter;
85 H264_CONTEXT* h264;
86 VideoSurface* surface;
87 MAPPED_GEOMETRY* geometry;
88 VideoClientContext* video;
89} PresentationContext;
90
91typedef struct
92{
93 BYTE PresentationId;
94 UINT64 publishTime;
95 UINT64 hnsDuration;
96 MAPPED_GEOMETRY* geometry;
97 UINT32 w, h;
98 UINT32 scanline;
99 BYTE* surfaceData;
100} VideoFrame;
101
103struct s_VideoClientContextPriv
104{
105 VideoClientContext* video;
106 GeometryClientContext* geometry;
107 wQueue* frames;
108 CRITICAL_SECTION framesLock;
109 wBufferPool* surfacePool;
110 UINT32 publishedFrames;
111 UINT32 droppedFrames;
112 UINT32 lastSentRate;
113 UINT64 nextFeedbackTime;
114 PresentationContext* currentPresentation;
115 FreeRDP_TimerID timerID;
116};
117
118static void PresentationContext_unref(PresentationContext** presentation);
119static void VideoClientContextPriv_free(VideoClientContextPriv* priv);
120
121WINPR_ATTR_NODISCARD
122static const char* video_command_name(BYTE cmd)
123{
124 switch (cmd)
125 {
126 case TSMM_START_PRESENTATION:
127 return "start";
128 case TSMM_STOP_PRESENTATION:
129 return "stop";
130 default:
131 return "<unknown>";
132 }
133}
134
135static void video_client_context_set_geometry(VideoClientContext* video,
136 GeometryClientContext* geometry)
137{
138 WINPR_ASSERT(video);
139 WINPR_ASSERT(video->priv);
140
141 video->priv->geometry = geometry;
142}
143
144WINPR_ATTR_MALLOC(VideoClientContextPriv_free, 1)
145static VideoClientContextPriv* VideoClientContextPriv_new(VideoClientContext* video)
146{
147 WINPR_ASSERT(video);
148 VideoClientContextPriv* ret = calloc(1, sizeof(*ret));
149 if (!ret)
150 return nullptr;
151
152 ret->frames = Queue_New(TRUE, 10, 2);
153 if (!ret->frames)
154 {
155 WLog_ERR(TAG, "unable to allocate frames queue");
156 goto fail;
157 }
158
159 ret->surfacePool = BufferPool_New(FALSE, 0, 16);
160 if (!ret->surfacePool)
161 {
162 WLog_ERR(TAG, "unable to create surface pool");
163 goto fail;
164 }
165
166 if (!InitializeCriticalSectionAndSpinCount(&ret->framesLock, 4 * 1000))
167 {
168 WLog_ERR(TAG, "unable to initialize frames lock");
169 goto fail;
170 }
171
172 ret->video = video;
173
174 /* don't set to unlimited so that we have the chance to send a feedback in
175 * the first second (for servers that want feedback directly)
176 */
177 ret->lastSentRate = 30;
178 return ret;
179
180fail:
181 VideoClientContextPriv_free(ret);
182 return nullptr;
183}
184
185WINPR_ATTR_NODISCARD
186static BOOL PresentationContext_ref(PresentationContext* presentation)
187{
188 WINPR_ASSERT(presentation);
189
190 const LONG val = InterlockedIncrement(&presentation->refCounter);
191 return val > 0;
192}
193
194static void PresentationContext_free(PresentationContext* presentation)
195{
196 if (!presentation)
197 return;
198
199 MAPPED_GEOMETRY* geometry = presentation->geometry;
200 if (geometry)
201 {
202 geometry->MappedGeometryUpdate = nullptr;
203 geometry->MappedGeometryClear = nullptr;
204 geometry->custom = nullptr;
205 mappedGeometryUnref(geometry);
206 }
207
208 h264_context_free(presentation->h264);
209 Stream_Free(presentation->currentSample, TRUE);
210 presentation->video->deleteSurface(presentation->video, presentation->surface);
211 free(presentation);
212}
213
214WINPR_ATTR_MALLOC(PresentationContext_free, 1)
215static PresentationContext* PresentationContext_new(VideoClientContext* video, BYTE PresentationId,
216 UINT32 x, UINT32 y, UINT32 width, UINT32 height)
217{
218 if ((width == 0) || (height == 0))
219 {
220 WLog_ERR(TAG, "width==%" PRIu32 ", height=%" PRIu32, width, height);
221 return nullptr;
222 }
223 const size_t s = 4ULL * width * height;
224
225 WINPR_ASSERT(video);
226
227 if (s > INT32_MAX)
228 return nullptr;
229
230 PresentationContext* ret = calloc(1, sizeof(*ret));
231 if (!ret)
232 return nullptr;
233
234 ret->video = video;
235 ret->PresentationId = PresentationId;
236
237 ret->h264 = h264_context_new(FALSE);
238 if (!ret->h264)
239 {
240 WLog_ERR(TAG, "unable to create a h264 context");
241 goto fail;
242 }
243
244 VIDEO_PLUGIN* plugin = (VIDEO_PLUGIN*)video->handle;
245 WINPR_ASSERT(plugin);
246 WINPR_ASSERT(plugin->rdpcontext);
247 if (!h264_context_set_option(ret->h264, H264_CONTEXT_OPTION_HW_ACCEL,
248 (UINT32)freerdp_settings_get_bool(plugin->rdpcontext->settings,
249 FreeRDP_SoftwareGdi) == 0))
250 goto fail;
251 if (!h264_context_reset(ret->h264, width, height))
252 goto fail;
253
254 ret->currentSample = Stream_New(nullptr, 4096);
255 if (!ret->currentSample)
256 {
257 WLog_ERR(TAG, "unable to create current packet stream");
258 goto fail;
259 }
260
261 ret->surface = video->createSurface(video, x, y, width, height);
262 if (!ret->surface)
263 {
264 WLog_ERR(TAG, "unable to create surface");
265 goto fail;
266 }
267
268 if (!PresentationContext_ref(ret))
269 goto fail;
270
271 return ret;
272
273fail:
274 PresentationContext_free(ret);
275 return nullptr;
276}
277
278static void PresentationContext_unref(PresentationContext** ppresentation)
279{
280 WINPR_ASSERT(ppresentation);
281
282 PresentationContext* presentation = *ppresentation;
283 if (!presentation)
284 return;
285
286 if (InterlockedDecrement(&presentation->refCounter) > 0)
287 return;
288 *ppresentation = nullptr;
289
290 PresentationContext_free(presentation);
291}
292
293static void VideoFrame_free(VideoClientContextPriv* priv, VideoFrame* frame)
294{
295 WINPR_ASSERT(priv);
296 if (!frame)
297 return;
298
299 mappedGeometryUnref(frame->geometry);
300
301 BufferPool_Return(priv->surfacePool, frame->surfaceData);
302 free(frame);
303}
304
305WINPR_ATTR_MALLOC(VideoFrame_free, 1)
306static VideoFrame* VideoFrame_new(VideoClientContextPriv* priv, PresentationContext* presentation,
307 MAPPED_GEOMETRY* geom)
308{
309 WINPR_ASSERT(priv);
310 WINPR_ASSERT(presentation);
311 WINPR_ASSERT(geom);
312
313 const VideoSurface* surface = presentation->surface;
314 WINPR_ASSERT(surface);
315
316 VideoFrame* frame = calloc(1, sizeof(VideoFrame));
317 if (!frame)
318 goto fail;
319 frame->PresentationId = presentation->PresentationId;
320
321 mappedGeometryRef(geom);
322
323 frame->publishTime = presentation->lastPublishTime;
324 frame->geometry = geom;
325 frame->w = surface->alignedWidth;
326 frame->h = surface->alignedHeight;
327 frame->scanline = surface->scanline;
328
329 frame->surfaceData = BufferPool_Take(priv->surfacePool, 1ll * frame->scanline * frame->h);
330 if (!frame->surfaceData)
331 goto fail;
332
333 return frame;
334
335fail:
336 VideoFrame_free(priv, frame);
337 return nullptr;
338}
339
340void VideoClientContextPriv_free(VideoClientContextPriv* priv)
341{
342 if (!priv)
343 return;
344
345 EnterCriticalSection(&priv->framesLock);
346
347 if (priv->frames)
348 {
349 while (Queue_Count(priv->frames))
350 {
351 VideoFrame* frame = Queue_Dequeue(priv->frames);
352 if (frame)
353 VideoFrame_free(priv, frame);
354 }
355 }
356
357 Queue_Free(priv->frames);
358 LeaveCriticalSection(&priv->framesLock);
359
360 DeleteCriticalSection(&priv->framesLock);
361
362 if (priv->currentPresentation)
363 PresentationContext_unref(&priv->currentPresentation);
364
365 BufferPool_Free(priv->surfacePool);
366 free(priv);
367}
368
369WINPR_ATTR_NODISCARD
370static UINT video_channel_write(VIDEO_PLUGIN* video, const BYTE* data, UINT32 length)
371{
372 WINPR_ASSERT(video);
373
374 if (!video->control_callback || !video->control_callback->channel_callback)
375 return ERROR_BAD_CONFIGURATION;
376 IWTSVirtualChannel* channel = video->control_callback->channel_callback->channel;
377 if (!channel || !channel->Write)
378 return ERROR_BAD_CONFIGURATION;
379 return channel->Write(channel, length, data, nullptr);
380}
381
382WINPR_ATTR_NODISCARD
383static UINT video_control_send_presentation_response(VideoClientContext* context,
385{
386 BYTE buf[12] = WINPR_C_ARRAY_INIT;
387
388 WINPR_ASSERT(context);
389 WINPR_ASSERT(resp);
390
391 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)context->handle;
392 WINPR_ASSERT(video);
393
394 wStream* s = Stream_New(buf, 12);
395 if (!s)
396 return CHANNEL_RC_NO_MEMORY;
397
398 Stream_Write_UINT32(s, 12); /* cbSize */
399 Stream_Write_UINT32(s, TSMM_PACKET_TYPE_PRESENTATION_RESPONSE); /* PacketType */
400 Stream_Write_UINT8(s, resp->PresentationId);
401 Stream_Zero(s, 3);
402 Stream_SealLength(s);
403 Stream_Free(s, FALSE);
404
405 return video_channel_write(video, buf, sizeof(buf));
406}
407
408WINPR_ATTR_NODISCARD
409static BOOL video_onMappedGeometryUpdate(MAPPED_GEOMETRY* geometry)
410{
411 WINPR_ASSERT(geometry);
412
413 PresentationContext* presentation = (PresentationContext*)geometry->custom;
414 WINPR_ASSERT(presentation);
415
416 RDP_RECT* r = &geometry->geometry.boundingRect;
417 WLog_DBG(TAG,
418 "geometry updated topGeom=(%" PRId32 ",%" PRId32 "-%" PRId32 "x%" PRId32
419 ") geom=(%" PRId32 ",%" PRId32 "-%" PRId32 "x%" PRId32 ") rects=(%" PRId16 ",%" PRId16
420 "-%" PRId16 "x%" PRId16 ")",
421 geometry->topLevelLeft, geometry->topLevelTop,
422 geometry->topLevelRight - geometry->topLevelLeft,
423 geometry->topLevelBottom - geometry->topLevelTop,
424
425 geometry->left, geometry->top, geometry->right - geometry->left,
426 geometry->bottom - geometry->top,
427
428 r->x, r->y, r->width, r->height);
429
430 WINPR_ASSERT(presentation->surface);
431 if (geometry->topLevelLeft < 0)
432 {
433 WLog_ERR(TAG, "geometry->topLevelLeft=%d < 0", geometry->topLevelLeft);
434 return FALSE;
435 }
436 if (geometry->left < 0)
437 {
438 WLog_ERR(TAG, "geometry->left=%d < 0", geometry->left);
439 return FALSE;
440 }
441 presentation->surface->x = WINPR_ASSERTING_INT_CAST(uint32_t, geometry->topLevelLeft) +
442 WINPR_ASSERTING_INT_CAST(uint32_t, geometry->left);
443
444 if (geometry->topLevelTop < 0)
445 {
446 WLog_ERR(TAG, "geometry->topLevelTop=%d < 0", geometry->topLevelTop);
447 return FALSE;
448 }
449 if (geometry->top < 0)
450 {
451 WLog_ERR(TAG, "geometry->top=%d < 0", geometry->top);
452 return FALSE;
453 }
454 presentation->surface->y = WINPR_ASSERTING_INT_CAST(uint32_t, geometry->topLevelTop) +
455 WINPR_ASSERTING_INT_CAST(uint32_t, geometry->top);
456
457 return TRUE;
458}
459
460WINPR_ATTR_NODISCARD
461static BOOL video_onMappedGeometryClear(MAPPED_GEOMETRY* geometry)
462{
463 WINPR_ASSERT(geometry);
464
465 PresentationContext* presentation = (PresentationContext*)geometry->custom;
466 WINPR_ASSERT(presentation);
467
468 mappedGeometryUnref(presentation->geometry);
469 presentation->geometry = nullptr;
470 return TRUE;
471}
472
473WINPR_ATTR_NODISCARD
474static UINT video_PresentationRequest(VideoClientContext* video,
475 const TSMM_PRESENTATION_REQUEST* req)
476{
477 UINT ret = CHANNEL_RC_OK;
478
479 WINPR_ASSERT(video);
480 WINPR_ASSERT(req);
481
482 VideoClientContextPriv* priv = video->priv;
483 WINPR_ASSERT(priv);
484
485 EnterCriticalSection(&priv->framesLock);
486 if (req->Command == TSMM_START_PRESENTATION)
487 {
488 MAPPED_GEOMETRY* geom = nullptr;
489 TSMM_PRESENTATION_RESPONSE resp = WINPR_C_ARRAY_INIT;
490
491 if (memcmp(req->VideoSubtypeId, MFVideoFormat_H264, 16) != 0)
492 {
493 WLog_ERR(TAG, "not a H264 video, ignoring request");
494 goto fail;
495 }
496
497 if (priv->currentPresentation)
498 {
499 if (priv->currentPresentation->PresentationId == req->PresentationId)
500 {
501 WLog_ERR(TAG, "ignoring start request for existing presentation %" PRIu8,
502 req->PresentationId);
503 goto fail;
504 }
505
506 WLog_ERR(TAG, "releasing current presentation %" PRIu8, req->PresentationId);
507 PresentationContext_unref(&priv->currentPresentation);
508 }
509
510 if (!priv->geometry)
511 {
512 WLog_ERR(TAG, "geometry channel not ready, ignoring request");
513 goto fail;
514 }
515
516 geom = HashTable_GetItemValue(priv->geometry->geometries, &(req->GeometryMappingId));
517 if (!geom)
518 {
519 WLog_ERR(TAG, "geometry mapping 0x%" PRIx64 " not registered", req->GeometryMappingId);
520 goto fail;
521 }
522
523 WLog_DBG(TAG, "creating presentation 0x%x", req->PresentationId);
524 if ((geom->topLevelLeft < 0) || (geom->left < 0) || (geom->topLevelTop < 0) ||
525 (geom->top < 0))
526 {
527 WLog_ERR(TAG,
528 "geometry: topLevelLeft=%" PRId32 " < 0, left=%" PRId32
529 " < 0, topLevelTop=%" PRId32 " < 0, top=%" PRId32 " < 0",
530 geom->topLevelLeft, geom->left, geom->topLevelTop, geom->top);
531 goto fail;
532 }
533
534 priv->currentPresentation = PresentationContext_new(
535 video, req->PresentationId,
536 WINPR_ASSERTING_INT_CAST(uint32_t, geom->topLevelLeft + geom->left),
537 WINPR_ASSERTING_INT_CAST(uint32_t, geom->topLevelTop + geom->top), req->SourceWidth,
538 req->SourceHeight);
539 if (!priv->currentPresentation)
540 {
541 WLog_ERR(TAG, "unable to create presentation video");
542 ret = CHANNEL_RC_NO_MEMORY;
543 goto fail;
544 }
545
546 mappedGeometryRef(geom);
547 priv->currentPresentation->geometry = geom;
548
549 priv->currentPresentation->video = video;
550 priv->currentPresentation->ScaledWidth = req->ScaledWidth;
551 priv->currentPresentation->ScaledHeight = req->ScaledHeight;
552
553 geom->custom = priv->currentPresentation;
554 geom->MappedGeometryUpdate = video_onMappedGeometryUpdate;
555 geom->MappedGeometryClear = video_onMappedGeometryClear;
556
557 /* send back response */
558 resp.PresentationId = req->PresentationId;
559 ret = video_control_send_presentation_response(video, &resp);
560 }
561 else if (req->Command == TSMM_STOP_PRESENTATION)
562 {
563 WLog_DBG(TAG, "stopping presentation 0x%x", req->PresentationId);
564 if (!priv->currentPresentation)
565 {
566 WLog_ERR(TAG, "unknown presentation to stop %" PRIu8, req->PresentationId);
567 goto fail;
568 }
569
570 priv->droppedFrames = 0;
571 priv->publishedFrames = 0;
572 PresentationContext_unref(&priv->currentPresentation);
573 }
574
575fail:
576 LeaveCriticalSection(&priv->framesLock);
577 return ret;
578}
579
580WINPR_ATTR_NODISCARD
581static UINT video_read_tsmm_presentation_req(VideoClientContext* context, wStream* s)
582{
583 TSMM_PRESENTATION_REQUEST req = WINPR_C_ARRAY_INIT;
584
585 WINPR_ASSERT(context);
586 WINPR_ASSERT(s);
587
588 if (!Stream_CheckAndLogRequiredLength(TAG, s, 60))
589 return ERROR_INVALID_DATA;
590
591 Stream_Read_UINT8(s, req.PresentationId);
592 Stream_Read_UINT8(s, req.Version);
593 Stream_Read_UINT8(s, req.Command);
594 Stream_Read_UINT8(s, req.FrameRate); /* FrameRate - reserved and ignored */
595
596 Stream_Seek_UINT16(s); /* AverageBitrateKbps reserved and ignored */
597 Stream_Seek_UINT16(s); /* reserved */
598
599 Stream_Read_UINT32(s, req.SourceWidth);
600 Stream_Read_UINT32(s, req.SourceHeight);
601 Stream_Read_UINT32(s, req.ScaledWidth);
602 Stream_Read_UINT32(s, req.ScaledHeight);
603 if ((req.ScaledWidth == 0) || (req.SourceHeight == 0) || (req.ScaledWidth == 0) ||
604 (req.ScaledHeight == 0))
605 {
606 WLog_ERR(TAG,
607 "SourceWidth=%" PRIu32 ", SourceHeight=%" PRIu32 ", ScaledWidth=%" PRIu32
608 ", ScaledHeight=%" PRIu32,
609 req.SourceWidth, req.SourceHeight, req.ScaledWidth, req.ScaledHeight);
610 return ERROR_INVALID_DATA;
611 }
612 Stream_Read_UINT64(s, req.hnsTimestampOffset);
613 Stream_Read_UINT64(s, req.GeometryMappingId);
614 Stream_Read(s, req.VideoSubtypeId, 16);
615
616 Stream_Read_UINT32(s, req.cbExtra);
617
618 if (!Stream_CheckAndLogRequiredLength(TAG, s, req.cbExtra))
619 return ERROR_INVALID_DATA;
620
621 req.pExtraData = Stream_Pointer(s);
622
623 WLog_DBG(TAG,
624 "presentationReq: id:%" PRIu8 " version:%" PRIu8
625 " command:%s srcWidth/srcHeight=%" PRIu32 "x%" PRIu32 " scaled Width/Height=%" PRIu32
626 "x%" PRIu32 " timestamp=%" PRIu64 " mappingId=%" PRIx64 "",
627 req.PresentationId, req.Version, video_command_name(req.Command), req.SourceWidth,
628 req.SourceHeight, req.ScaledWidth, req.ScaledHeight, req.hnsTimestampOffset,
629 req.GeometryMappingId);
630
631 return video_PresentationRequest(context, &req);
632}
633
639WINPR_ATTR_NODISCARD
640static UINT video_control_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s)
641{
642 GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
643 UINT ret = CHANNEL_RC_OK;
644 UINT32 cbSize = 0;
645 UINT32 packetType = 0;
646
647 WINPR_ASSERT(callback);
648 WINPR_ASSERT(s);
649
650 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)callback->plugin;
651 WINPR_ASSERT(video);
652
653 VideoClientContext* context = (VideoClientContext*)video->wtsPlugin.pInterface;
654 WINPR_ASSERT(context);
655
656 if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
657 return ERROR_INVALID_DATA;
658
659 Stream_Read_UINT32(s, cbSize);
660 if (cbSize < 8)
661 {
662 WLog_ERR(TAG, "invalid cbSize %" PRIu32 ", expected 8", cbSize);
663 return ERROR_INVALID_DATA;
664 }
665 if (!Stream_CheckAndLogRequiredLength(TAG, s, cbSize - 4))
666 return ERROR_INVALID_DATA;
667
668 Stream_Read_UINT32(s, packetType);
669 switch (packetType)
670 {
671 case TSMM_PACKET_TYPE_PRESENTATION_REQUEST:
672 ret = video_read_tsmm_presentation_req(context, s);
673 break;
674 default:
675 WLog_ERR(TAG, "not expecting packet type %" PRIu32 "", packetType);
676 ret = ERROR_UNSUPPORTED_TYPE;
677 break;
678 }
679
680 return ret;
681}
682
683static UINT video_control_send_client_notification(VideoClientContext* context,
684 const TSMM_CLIENT_NOTIFICATION* notif)
685{
686 BYTE buf[100] = WINPR_C_ARRAY_INIT;
687
688 WINPR_ASSERT(context);
689 WINPR_ASSERT(notif);
690
691 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)context->handle;
692 WINPR_ASSERT(video);
693
694 wStream* s = Stream_New(buf, 32);
695 if (!s)
696 return CHANNEL_RC_NO_MEMORY;
697
698 UINT32 cbSize = 16;
699 Stream_Seek_UINT32(s); /* cbSize */
700 Stream_Write_UINT32(s, TSMM_PACKET_TYPE_CLIENT_NOTIFICATION); /* PacketType */
701 Stream_Write_UINT8(s, notif->PresentationId);
702 Stream_Write_UINT8(s, notif->NotificationType);
703 Stream_Zero(s, 2);
704 if (notif->NotificationType == TSMM_CLIENT_NOTIFICATION_TYPE_FRAMERATE_OVERRIDE)
705 {
706 Stream_Write_UINT32(s, 16); /* cbData */
707
708 /* TSMM_CLIENT_NOTIFICATION_FRAMERATE_OVERRIDE */
709 Stream_Write_UINT32(s, notif->FramerateOverride.Flags);
710 Stream_Write_UINT32(s, notif->FramerateOverride.DesiredFrameRate);
711 Stream_Zero(s, 4ULL * 2ULL);
712
713 cbSize += 4UL * 4UL;
714 }
715 else
716 {
717 Stream_Write_UINT32(s, 0); /* cbData */
718 }
719
720 Stream_SealLength(s);
721 Stream_ResetPosition(s);
722 Stream_Write_UINT32(s, cbSize);
723 Stream_Free(s, FALSE);
724
725 return video_channel_write(video, buf, cbSize);
726}
727
728static void video_timer(VideoClientContext* video, UINT64 now)
729{
730 VideoFrame* frame = nullptr;
731
732 WINPR_ASSERT(video);
733
734 VideoClientContextPriv* priv = video->priv;
735 WINPR_ASSERT(priv);
736
737 EnterCriticalSection(&priv->framesLock);
738 PresentationContext* presentation = video->priv->currentPresentation;
739 do
740 {
741 const VideoFrame* peekFrame = (VideoFrame*)Queue_Peek(priv->frames);
742 if (!peekFrame)
743 break;
744
745 if (peekFrame->publishTime > now)
746 break;
747
748 if (frame)
749 {
750 WLog_DBG(TAG, "dropping frame @%" PRIu64, frame->publishTime);
751 priv->droppedFrames++;
752 VideoFrame_free(priv, frame);
753 }
754 frame = Queue_Dequeue(priv->frames);
755 } while (1);
756
757 if (frame)
758 {
759 if (presentation && (presentation->PresentationId == frame->PresentationId))
760 {
761 VideoSurface* surface = presentation->surface;
762 const size_t frameSize = 1ull * frame->scanline * frame->h;
763 const size_t surfaceSize = 1ull * surface->scanline * surface->alignedHeight;
764
765 /* the presentation id is reused by the server across presentations of different
766 * sizes, so a frame queued for a previous, larger presentation can outlive it in
767 * the queue. copying it would write past the smaller surface->data buffer. */
768 if (frameSize > surfaceSize)
769 WLog_WARN(TAG, "dropping stale frame of %" PRIuz " bytes, surface holds %" PRIuz,
770 frameSize, surfaceSize);
771 else
772 {
773 priv->publishedFrames++;
774 memcpy(surface->data, frame->surfaceData, frameSize);
775
776 WINPR_ASSERT(video->showSurface);
777 if (!video->showSurface(video, surface, presentation->ScaledWidth,
778 presentation->ScaledHeight))
779 WLog_WARN(TAG, "showSurface failed");
780 }
781 }
782 VideoFrame_free(priv, frame);
783 }
784
785 if (priv->nextFeedbackTime < now)
786 {
787 /* we can compute some feedback only if we have some published frames and
788 * a current presentation
789 */
790 if (priv->publishedFrames && priv->currentPresentation)
791 {
792 UINT32 computedRate = 0;
793
794 if (!PresentationContext_ref(priv->currentPresentation))
795 WLog_WARN(TAG, "PresentationContext_ref(priv->currentPresentation) failed");
796
797 if (priv->droppedFrames)
798 {
804 if (priv->lastSentRate == XF_VIDEO_UNLIMITED_RATE)
805 computedRate = 24;
806 else
807 {
808 computedRate = priv->lastSentRate - 2;
809 if (!computedRate)
810 computedRate = 2;
811 }
812 }
813 else
814 {
819 if (priv->lastSentRate == XF_VIDEO_UNLIMITED_RATE)
820 computedRate = XF_VIDEO_UNLIMITED_RATE; /* stay unlimited */
821 else
822 {
823 computedRate = priv->lastSentRate + 2;
824 if (computedRate > XF_VIDEO_UNLIMITED_RATE)
825 computedRate = XF_VIDEO_UNLIMITED_RATE;
826 }
827 }
828
829 if (computedRate != priv->lastSentRate)
830 {
831 TSMM_CLIENT_NOTIFICATION notif = WINPR_C_ARRAY_INIT;
832
833 WINPR_ASSERT(priv->currentPresentation);
834 notif.PresentationId = priv->currentPresentation->PresentationId;
835 notif.NotificationType = TSMM_CLIENT_NOTIFICATION_TYPE_FRAMERATE_OVERRIDE;
836 if (computedRate == XF_VIDEO_UNLIMITED_RATE)
837 {
838 notif.FramerateOverride.Flags = 0x01;
839 notif.FramerateOverride.DesiredFrameRate = 0x00;
840 }
841 else
842 {
843 notif.FramerateOverride.Flags = 0x02;
844 notif.FramerateOverride.DesiredFrameRate = computedRate;
845 }
846
847 video_control_send_client_notification(video, &notif);
848 priv->lastSentRate = computedRate;
849
850 WLog_VRB(TAG,
851 "server notified with rate %" PRIu32 " published=%" PRIu32
852 " dropped=%" PRIu32,
853 priv->lastSentRate, priv->publishedFrames, priv->droppedFrames);
854 }
855
856 PresentationContext_unref(&priv->currentPresentation);
857 }
858
859 priv->droppedFrames = 0;
860 priv->publishedFrames = 0;
861 priv->nextFeedbackTime = now + 1000;
862 }
863 LeaveCriticalSection(&priv->framesLock);
864}
865
866WINPR_ATTR_NODISCARD
867static UINT video_VideoData(VideoClientContext* context, const TSMM_VIDEO_DATA* data)
868{
869 int status = 0;
870 UINT res = CHANNEL_RC_OK;
871
872 WINPR_ASSERT(context);
873 WINPR_ASSERT(data);
874
875 VideoClientContextPriv* priv = context->priv;
876 WINPR_ASSERT(priv);
877
878 EnterCriticalSection(&priv->framesLock);
879 PresentationContext* presentation = priv->currentPresentation;
880 if (!presentation)
881 {
882 WLog_ERR(TAG, "no current presentation");
883 LeaveCriticalSection(&priv->framesLock);
884 return CHANNEL_RC_OK;
885 }
886
887 if (!PresentationContext_ref(presentation))
888 {
889 LeaveCriticalSection(&priv->framesLock);
890 return ERROR_INTERNAL_ERROR;
891 }
892
893 if (presentation->PresentationId != data->PresentationId)
894 {
895 WLog_ERR(TAG, "current presentation id=%" PRIu8 " doesn't match data id=%" PRIu8,
896 presentation->PresentationId, data->PresentationId);
897 goto out;
898 }
899
900 if (!Stream_EnsureRemainingCapacity(presentation->currentSample, data->cbSample))
901 {
902 WLog_ERR(TAG, "unable to expand the current packet");
903 res = CHANNEL_RC_NO_MEMORY;
904 goto out;
905 }
906
907 Stream_Write(presentation->currentSample, data->pSample, data->cbSample);
908
909 if (data->CurrentPacketIndex == data->PacketsInSample)
910 {
911 VideoSurface* surface = presentation->surface;
912 H264_CONTEXT* h264 = presentation->h264;
913 const UINT64 startTime = winpr_GetTickCount64NS();
914 MAPPED_GEOMETRY* geom = presentation->geometry;
915 if (!geom)
916 goto out;
917
918 const RECTANGLE_16 rect = { 0, 0, WINPR_ASSERTING_INT_CAST(UINT16, surface->alignedWidth),
919 WINPR_ASSERTING_INT_CAST(UINT16, surface->alignedHeight) };
920 Stream_SealLength(presentation->currentSample);
921 Stream_ResetPosition(presentation->currentSample);
922
923 if (data->SampleNumber == 1)
924 {
925 presentation->lastPublishTime = startTime;
926 }
927
928 presentation->lastPublishTime += 100ull * data->hnsDuration;
929 const size_t len = Stream_Length(presentation->currentSample);
930 if (len > UINT32_MAX)
931 goto out;
932
933 BOOL enqueueResult = 0;
934 VideoFrame* frame = VideoFrame_new(priv, presentation, geom);
935 if (!frame)
936 {
937 WLog_ERR(TAG, "unable to create frame");
938 res = CHANNEL_RC_NO_MEMORY;
939 goto out;
940 }
941
942 status = avc420_decompress(h264, Stream_Pointer(presentation->currentSample), (UINT32)len,
943 frame->surfaceData, surface->format, surface->scanline,
944 surface->alignedWidth, surface->alignedHeight, &rect, 1);
945 if (status < 0)
946 {
947 VideoFrame_free(priv, frame);
948 goto out;
949 }
950
951 enqueueResult = Queue_Enqueue(priv->frames, frame);
952
953 if (!enqueueResult)
954 {
955 WLog_ERR(TAG, "unable to enqueue frame");
956 VideoFrame_free(priv, frame);
957 res = CHANNEL_RC_NO_MEMORY;
958 goto out;
959 }
960
961 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): Queue_Enqueue owns frame
962 WLog_DBG(TAG, "scheduling frame in %" PRIu64 " ns", (frame->publishTime - startTime));
963 }
964
965out:
966 PresentationContext_unref(&priv->currentPresentation);
967 LeaveCriticalSection(&priv->framesLock);
968
969 return res;
970}
971
972WINPR_ATTR_NODISCARD
973static UINT video_data_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s)
974{
975 GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
976 UINT32 cbSize = 0;
977 UINT32 packetType = 0;
978 TSMM_VIDEO_DATA data;
979
980 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)callback->plugin;
981 WINPR_ASSERT(video);
982
983 VideoClientContext* context = (VideoClientContext*)video->wtsPlugin.pInterface;
984
985 if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
986 return ERROR_INVALID_DATA;
987
988 Stream_Read_UINT32(s, cbSize);
989 if (cbSize < 8)
990 {
991 WLog_ERR(TAG, "invalid cbSize %" PRIu32 ", expected >= 8", cbSize);
992 return ERROR_INVALID_DATA;
993 }
994
995 if (!Stream_CheckAndLogRequiredLength(TAG, s, cbSize - 4))
996 return ERROR_INVALID_DATA;
997
998 Stream_Read_UINT32(s, packetType);
999 if (packetType != TSMM_PACKET_TYPE_VIDEO_DATA)
1000 {
1001 WLog_ERR(TAG, "only expecting VIDEO_DATA on the data channel");
1002 return ERROR_INVALID_DATA;
1003 }
1004
1005 if (!Stream_CheckAndLogRequiredLength(TAG, s, 32))
1006 return ERROR_INVALID_DATA;
1007
1008 Stream_Read_UINT8(s, data.PresentationId);
1009 Stream_Read_UINT8(s, data.Version);
1010 Stream_Read_UINT8(s, data.Flags);
1011 Stream_Seek_UINT8(s); /* reserved */
1012 Stream_Read_UINT64(s, data.hnsTimestamp);
1013 Stream_Read_UINT64(s, data.hnsDuration);
1014 Stream_Read_UINT16(s, data.CurrentPacketIndex);
1015 Stream_Read_UINT16(s, data.PacketsInSample);
1016 Stream_Read_UINT32(s, data.SampleNumber);
1017 Stream_Read_UINT32(s, data.cbSample);
1018 if (!Stream_CheckAndLogRequiredLength(TAG, s, data.cbSample))
1019 return ERROR_INVALID_DATA;
1020 data.pSample = Stream_Pointer(s);
1021
1022 /*
1023 WLog_DBG(TAG, "videoData: id:%"PRIu8" version:%"PRIu8" flags:0x%"PRIx8" timestamp=%"PRIu64"
1024 duration=%"PRIu64 " curPacketIndex:%"PRIu16" packetInSample:%"PRIu16" sampleNumber:%"PRIu32"
1025 cbSample:%"PRIu32"", data.PresentationId, data.Version, data.Flags, data.hnsTimestamp,
1026 data.hnsDuration, data.CurrentPacketIndex, data.PacketsInSample, data.SampleNumber,
1027 data.cbSample);
1028 */
1029
1030 return video_VideoData(context, &data);
1031}
1032
1038WINPR_ATTR_NODISCARD
1039static UINT video_control_on_close(IWTSVirtualChannelCallback* pChannelCallback)
1040{
1041 if (pChannelCallback)
1042 {
1043 GENERIC_CHANNEL_CALLBACK* listener_callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
1044 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)listener_callback->plugin;
1045 if (video && video->control_callback)
1046 {
1047 video->control_callback->channel_callback = nullptr;
1048 }
1049 if (video)
1050 video->connected = FALSE;
1051 }
1052 free(pChannelCallback);
1053 return CHANNEL_RC_OK;
1054}
1055
1056WINPR_ATTR_NODISCARD
1057static UINT video_data_on_close(IWTSVirtualChannelCallback* pChannelCallback)
1058{
1059 if (pChannelCallback)
1060 {
1061 GENERIC_CHANNEL_CALLBACK* listener_callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
1062 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)listener_callback->plugin;
1063 if (video && video->data_callback)
1064 {
1065 video->data_callback->channel_callback = nullptr;
1066 }
1067
1068 if (video)
1069 video->dataConnected = FALSE;
1070 }
1071 free(pChannelCallback);
1072 return CHANNEL_RC_OK;
1073}
1074
1080// NOLINTBEGIN(readability-non-const-parameter)
1081WINPR_ATTR_NODISCARD
1082static UINT video_control_on_new_channel_connection(IWTSListenerCallback* listenerCallback,
1083 IWTSVirtualChannel* channel,
1084 WINPR_ATTR_UNUSED BYTE* Data,
1085 WINPR_ATTR_UNUSED BOOL* pbAccept,
1086 IWTSVirtualChannelCallback** ppCallback)
1087// NOLINTEND(readability-non-const-parameter)
1088{
1089 GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)listenerCallback;
1090 WINPR_ASSERT(listener_callback);
1091
1092 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)listener_callback->plugin;
1093 WINPR_ASSERT(video);
1094
1095 if (video->connected)
1096 {
1097 WLog_ERR(TAG, "Channel already connected, terminating.");
1098 return ERROR_DEVICE_ALREADY_ATTACHED;
1099 }
1100
1101 GENERIC_CHANNEL_CALLBACK* callback =
1103 if (!callback)
1104 {
1105 WLog_ERR(TAG, "calloc failed!");
1106 return CHANNEL_RC_NO_MEMORY;
1107 }
1108
1109 callback->iface.OnDataReceived = video_control_on_data_received;
1110 callback->iface.OnClose = video_control_on_close;
1111 callback->plugin = listener_callback->plugin;
1112 callback->channel_mgr = listener_callback->channel_mgr;
1113 callback->channel = channel;
1114 listener_callback->channel_callback = callback;
1115
1116 *ppCallback = &callback->iface;
1117 video->connected = TRUE;
1118
1119 return CHANNEL_RC_OK;
1120}
1121
1122// NOLINTBEGIN(readability-non-const-parameter)
1123WINPR_ATTR_NODISCARD
1124static UINT video_data_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
1125 IWTSVirtualChannel* pChannel,
1126 WINPR_ATTR_UNUSED BYTE* Data,
1127 WINPR_ATTR_UNUSED BOOL* pbAccept,
1128 IWTSVirtualChannelCallback** ppCallback)
1129// NOLINTEND(readability-non-const-parameter)
1130{
1131 GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
1132 WINPR_ASSERT(listener_callback);
1133
1134 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)listener_callback->plugin;
1135 WINPR_ASSERT(video);
1136
1137 if (video->dataConnected)
1138 {
1139 WLog_ERR(TAG, "Channel already connected, terminating.");
1140 return ERROR_DEVICE_ALREADY_ATTACHED;
1141 }
1142
1143 GENERIC_CHANNEL_CALLBACK* callback =
1145 if (!callback)
1146 {
1147 WLog_ERR(TAG, "calloc failed!");
1148 return CHANNEL_RC_NO_MEMORY;
1149 }
1150
1151 callback->iface.OnDataReceived = video_data_on_data_received;
1152 callback->iface.OnClose = video_data_on_close;
1153 callback->plugin = listener_callback->plugin;
1154 callback->channel_mgr = listener_callback->channel_mgr;
1155 callback->channel = pChannel;
1156 listener_callback->channel_callback = callback;
1157
1158 *ppCallback = &callback->iface;
1159 video->dataConnected = TRUE;
1160
1161 return CHANNEL_RC_OK;
1162}
1163
1164WINPR_ATTR_NODISCARD
1165static uint64_t timer_cb(WINPR_ATTR_UNUSED rdpContext* context, void* userdata,
1166 WINPR_ATTR_UNUSED FreeRDP_TimerID timerID, uint64_t timestamp,
1167 uint64_t interval)
1168{
1169 VideoClientContext* video = userdata;
1170 if (!video)
1171 return 0;
1172 if (!video->timer)
1173 return 0;
1174
1175 video->timer(video, timestamp);
1176
1177 return interval;
1178}
1179
1185WINPR_ATTR_NODISCARD
1186static UINT video_plugin_initialize(IWTSPlugin* plugin, IWTSVirtualChannelManager* channelMgr)
1187{
1188 UINT status = 0;
1189 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)plugin;
1190
1191 if (video->initialized)
1192 {
1193 WLog_ERR(TAG, "[%s] channel initialized twice, aborting", VIDEO_CONTROL_DVC_CHANNEL_NAME);
1194 return ERROR_INVALID_DATA;
1195 }
1196
1197 {
1198 GENERIC_LISTENER_CALLBACK* callback =
1200 if (!callback)
1201 {
1202 WLog_ERR(TAG, "calloc for control callback failed!");
1203 return CHANNEL_RC_NO_MEMORY;
1204 }
1205
1206 callback->iface.OnNewChannelConnection = video_control_on_new_channel_connection;
1207 callback->plugin = plugin;
1208 callback->channel_mgr = channelMgr;
1209
1210 status = channelMgr->CreateListener(channelMgr, VIDEO_CONTROL_DVC_CHANNEL_NAME, 0,
1211 &callback->iface, &(video->controlListener));
1212 video->control_callback = callback;
1213 if (status != CHANNEL_RC_OK)
1214 return status;
1215 }
1216 video->controlListener->pInterface = video->wtsPlugin.pInterface;
1217
1218 {
1219 GENERIC_LISTENER_CALLBACK* callback =
1221 if (!callback)
1222 {
1223 WLog_ERR(TAG, "calloc for data callback failed!");
1224 return CHANNEL_RC_NO_MEMORY;
1225 }
1226
1227 callback->iface.OnNewChannelConnection = video_data_on_new_channel_connection;
1228 callback->plugin = plugin;
1229 callback->channel_mgr = channelMgr;
1230
1231 status = channelMgr->CreateListener(channelMgr, VIDEO_DATA_DVC_CHANNEL_NAME, 0,
1232 &callback->iface, &(video->dataListener));
1233 video->data_callback = callback;
1234 if (status == CHANNEL_RC_OK)
1235 video->dataListener->pInterface = video->wtsPlugin.pInterface;
1236 }
1237
1238 if (status == CHANNEL_RC_OK)
1239 video->context->priv->timerID =
1240 freerdp_timer_add(video->rdpcontext, 20000000, timer_cb, video->context, true);
1241 video->initialized = video->context->priv->timerID != 0;
1242 if (!video->initialized)
1243 status = ERROR_INTERNAL_ERROR;
1244 return status;
1245}
1246
1252WINPR_ATTR_NODISCARD
1253static UINT video_plugin_terminated(IWTSPlugin* pPlugin)
1254{
1255 VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)pPlugin;
1256 if (!video)
1257 return CHANNEL_RC_INVALID_INSTANCE;
1258
1259 if (video->context && video->context->priv)
1260 freerdp_timer_remove(video->rdpcontext, video->context->priv->timerID);
1261
1262 if (video->control_callback)
1263 {
1264 IWTSVirtualChannelManager* mgr = video->control_callback->channel_mgr;
1265 if (mgr)
1266 IFCALL(mgr->DestroyListener, mgr, video->controlListener);
1267 }
1268 if (video->data_callback)
1269 {
1270 IWTSVirtualChannelManager* mgr = video->data_callback->channel_mgr;
1271 if (mgr)
1272 IFCALL(mgr->DestroyListener, mgr, video->dataListener);
1273 }
1274
1275 if (video->context)
1276 VideoClientContextPriv_free(video->context->priv);
1277
1278 free(video->control_callback);
1279 free(video->data_callback);
1280 free(video->wtsPlugin.pInterface);
1281 free(pPlugin);
1282 return CHANNEL_RC_OK;
1283}
1284
1293WINPR_ATTR_NODISCARD
1294FREERDP_ENTRY_POINT(UINT VCAPITYPE video_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
1295{
1296 UINT error = ERROR_INTERNAL_ERROR;
1297
1298 VIDEO_PLUGIN* videoPlugin = (VIDEO_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "video");
1299 if (!videoPlugin)
1300 {
1301 videoPlugin = (VIDEO_PLUGIN*)calloc(1, sizeof(VIDEO_PLUGIN));
1302 if (!videoPlugin)
1303 {
1304 WLog_ERR(TAG, "calloc failed!");
1305 return CHANNEL_RC_NO_MEMORY;
1306 }
1307
1308 videoPlugin->wtsPlugin.Initialize = video_plugin_initialize;
1309 videoPlugin->wtsPlugin.Connected = nullptr;
1310 videoPlugin->wtsPlugin.Disconnected = nullptr;
1311 videoPlugin->wtsPlugin.Terminated = video_plugin_terminated;
1312
1313 VideoClientContext* videoContext =
1314 (VideoClientContext*)calloc(1, sizeof(VideoClientContext));
1315 if (!videoContext)
1316 {
1317 WLog_ERR(TAG, "calloc failed!");
1318 free(videoPlugin);
1319 return CHANNEL_RC_NO_MEMORY;
1320 }
1321
1322 VideoClientContextPriv* priv = VideoClientContextPriv_new(videoContext);
1323 if (!priv)
1324 {
1325 WLog_ERR(TAG, "VideoClientContextPriv_new failed!");
1326 free(videoContext);
1327 free(videoPlugin);
1328 return CHANNEL_RC_NO_MEMORY;
1329 }
1330
1331 videoContext->handle = (void*)videoPlugin;
1332 videoContext->priv = priv;
1333 videoContext->timer = video_timer;
1334 videoContext->setGeometry = video_client_context_set_geometry;
1335
1336 videoPlugin->wtsPlugin.pInterface = (void*)videoContext;
1337 videoPlugin->context = videoContext;
1338 videoPlugin->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
1339 if (videoPlugin->rdpcontext)
1340 error = pEntryPoints->RegisterPlugin(pEntryPoints, "video", &videoPlugin->wtsPlugin);
1341 }
1342 else
1343 {
1344 WLog_ERR(TAG, "could not get video Plugin.");
1345 return CHANNEL_RC_BAD_CHANNEL;
1346 }
1347
1348 return error;
1349}
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
a client to server notification struct
presentation request struct
response to a TSMM_PRESENTATION_REQUEST
a video data packet
an implementation of surface used by the video channel