Coverage Report

Created: 2026-10-15 06:27

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