Coverage Report

Created: 2024-05-20 06:11

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