FreeRDP
Loading...
Searching...
No Matches
camera_gst.c
1
20/* A pseudo camera whose frames come from a GStreamer pipeline, selected with
21 * /dvc:rdpecam,sys:gst,pipeline:<pipeline>|pipeline-file:<path>|file:<path>
22 * [,name:<camera name>][,loop:<on|off>]
23 *
24 * The pipeline is given in gst-launch syntax and must produce video, the backend appends the
25 * conversion to I420 and the sink. The only media type announced is what the pipeline
26 * negotiates (cropped to even dimensions). A pipeline reaching its end is played again unless
27 * loop:off is given, live sources (pipewiresrc, v4l2src, ...) never end. */
28
29#include <winpr/assert.h>
30#include <winpr/cast.h>
31#include <winpr/cmdline.h>
32#include <winpr/file.h>
33#include <winpr/string.h>
34#include <winpr/synch.h>
35#include <winpr/thread.h>
36
37#include <gst/gst.h>
38#include <gst/app/gstappsink.h>
39#include <gst/video/video.h>
40
41#include "camera.h"
42
43#define TAG CHANNELS_TAG("rdpecam-gst.client")
44
45/* the channel name of the device, what a Windows client would use for its first camera */
46#define CAM_GST_DEVICE_ID "RDCamera_Device_0"
47
48#define CAM_GST_SINK_NAME "freerdpcamsink"
49
50#define CAM_GST_FRAMERATE_NUMERATOR_DEFAULT 30
51#define CAM_GST_FRAMERATE_DENOMINATOR_DEFAULT 1
52
53/* how long the pipeline may take to start, to preroll or to produce its first frame */
54#define CAM_GST_START_TIMEOUT (10 * GST_SECOND)
55
56/* longest wait for a frame, so that a stop request is seen quickly */
57#define CAM_GST_PULL_TIMEOUT (100 * GST_MSECOND)
58
59typedef struct
60{
61 ICamHal iHal;
62
63 char* description; /* the complete pipeline, sink included */
64 char* name;
65 BOOL loop;
66 BOOL live;
67
68 /* characteristics negotiated by the pipeline, the only media type announced */
69 UINT32 width;
70 UINT32 height;
71 UINT32 frameRateNumerator;
72 UINT32 frameRateDenominator;
73
74 GstElement* pipeline;
75 GstAppSink* sink;
76
78 BOOL streaming;
79 HANDLE thread;
80
81 CameraDevice* dev;
82 size_t streamIndex;
83 ICamHalSampleCapturedCallback sampleCallback;
84
85 BYTE* sample;
86 size_t sampleSize;
87} CamGstHal;
88
90static BOOL cam_gst_log_errors(CamGstHal* hal)
91{
92 WINPR_ASSERT(hal);
93
94 BOOL found = FALSE;
95 GstBus* bus = gst_element_get_bus(hal->pipeline);
96 if (!bus)
97 return FALSE;
98
99 GstMessage* msg = nullptr;
100 while ((msg = gst_bus_pop_filtered(bus, GST_MESSAGE_ERROR)) != nullptr)
101 {
102 GError* error = nullptr;
103 gchar* debug = nullptr;
104 gst_message_parse_error(msg, &error, &debug);
105 WLog_ERR(TAG, "Pipeline error from %s: %s (%s)", GST_OBJECT_NAME(GST_MESSAGE_SRC(msg)),
106 error ? error->message : "unknown", debug ? debug : "no details");
107 g_clear_error(&error);
108 g_free(debug);
109 gst_message_unref(msg);
110 found = TRUE;
111 }
112 gst_object_unref(bus);
113 return found;
114}
115
116static void cam_gst_pipeline_destroy(CamGstHal* hal)
117{
118 WINPR_ASSERT(hal);
119
120 if (hal->sink)
121 {
122 gst_object_unref(hal->sink);
123 hal->sink = nullptr;
124 }
125 if (hal->pipeline)
126 {
127 (void)gst_element_set_state(hal->pipeline, GST_STATE_NULL);
128 gst_object_unref(hal->pipeline);
129 hal->pipeline = nullptr;
130 }
131}
132
135WINPR_ATTR_NODISCARD
136static BOOL cam_gst_pipeline_create(CamGstHal* hal)
137{
138 WINPR_ASSERT(hal);
139 WINPR_ASSERT(!hal->pipeline);
140
141 GError* error = nullptr;
142 hal->pipeline =
143 gst_parse_launch_full(hal->description, nullptr, GST_PARSE_FLAG_FATAL_ERRORS, &error);
144 if (!hal->pipeline || error)
145 {
146 WLog_ERR(TAG, "Invalid pipeline \"%s\": %s", hal->description,
147 error ? error->message : "unknown error");
148 g_clear_error(&error);
149 cam_gst_pipeline_destroy(hal);
150 return FALSE;
151 }
152
153 GstElement* sink = GST_IS_BIN(hal->pipeline)
154 ? gst_bin_get_by_name(GST_BIN(hal->pipeline), CAM_GST_SINK_NAME)
155 : nullptr;
156 if (!sink)
157 {
158 WLog_ERR(TAG, "The pipeline has no %s element", CAM_GST_SINK_NAME);
159 cam_gst_pipeline_destroy(hal);
160 return FALSE;
161 }
162 hal->sink = GST_APP_SINK(sink);
163 return TRUE;
164}
165
167WINPR_ATTR_NODISCARD
168static BOOL cam_gst_set_state(CamGstHal* hal, GstState state, GstStateChangeReturn* pResult)
169{
170 WINPR_ASSERT(hal);
171
172 GstStateChangeReturn rc = gst_element_set_state(hal->pipeline, state);
173 if (rc == GST_STATE_CHANGE_ASYNC)
174 rc = gst_element_get_state(hal->pipeline, nullptr, nullptr, CAM_GST_START_TIMEOUT);
175
176 if (pResult)
177 *pResult = rc;
178
179 switch (rc)
180 {
181 case GST_STATE_CHANGE_SUCCESS:
182 case GST_STATE_CHANGE_NO_PREROLL:
183 return TRUE;
184 case GST_STATE_CHANGE_ASYNC:
185 WLog_ERR(TAG, "Pipeline did not reach %s in time", gst_element_state_get_name(state));
186 return FALSE;
187 case GST_STATE_CHANGE_FAILURE:
188 default:
189 if (!cam_gst_log_errors(hal))
190 WLog_ERR(TAG, "Pipeline failed to go to %s", gst_element_state_get_name(state));
191 return FALSE;
192 }
193}
194
196WINPR_ATTR_NODISCARD
197static BOOL cam_gst_convert_sample(CamGstHal* hal, GstSample* sample)
198{
199 WINPR_ASSERT(hal);
200 WINPR_ASSERT(sample);
201
202 GstVideoInfo info;
203 GstCaps* caps = gst_sample_get_caps(sample);
204 GstBuffer* buffer = gst_sample_get_buffer(sample);
205 if (!caps || !buffer || !gst_video_info_from_caps(&info, caps))
206 {
207 WLog_WARN(TAG, "Skipping a sample without video caps");
208 return FALSE;
209 }
210
211 if ((GST_VIDEO_INFO_FORMAT(&info) != GST_VIDEO_FORMAT_I420) ||
212 (GST_VIDEO_INFO_WIDTH(&info) < 0) || (GST_VIDEO_INFO_HEIGHT(&info) < 0) ||
213 ((UINT32)GST_VIDEO_INFO_WIDTH(&info) < hal->width) ||
214 ((UINT32)GST_VIDEO_INFO_HEIGHT(&info) < hal->height))
215 {
216 WLog_WARN(
217 TAG, "Skipping a %s %dx%d frame, the announced format is I420 %" PRIu32 "x%" PRIu32,
218 gst_video_format_to_string(GST_VIDEO_INFO_FORMAT(&info)), GST_VIDEO_INFO_WIDTH(&info),
219 GST_VIDEO_INFO_HEIGHT(&info), hal->width, hal->height);
220 return FALSE;
221 }
222
223 GstVideoFrame frame;
224 if (!gst_video_frame_map(&frame, &info, buffer, GST_MAP_READ))
225 {
226 WLog_WARN(TAG, "Unable to map a frame");
227 return FALSE;
228 }
229
230 /* planes may be padded, and larger than announced when cropping an odd size */
231 BYTE* dst = hal->sample;
232 for (guint plane = 0; plane < 3; plane++)
233 {
234 const size_t rowSize = (plane == 0) ? hal->width : hal->width / 2;
235 const size_t rows = (plane == 0) ? hal->height : hal->height / 2;
236 const BYTE* src = GST_VIDEO_FRAME_PLANE_DATA(&frame, plane);
237 const size_t stride = (size_t)GST_VIDEO_FRAME_PLANE_STRIDE(&frame, plane);
238
239 for (size_t y = 0; y < rows; y++)
240 {
241 memcpy(dst, &src[y * stride], rowSize);
242 dst += rowSize;
243 }
244 }
245
246 gst_video_frame_unmap(&frame);
247 return TRUE;
248}
249
252static void cam_gst_release(CamGstHal* hal)
253{
254 WINPR_ASSERT(hal);
255
256 if (hal->live || !cam_gst_set_state(hal, GST_STATE_PAUSED, nullptr))
257 cam_gst_pipeline_destroy(hal);
258}
259
260static DWORD WINAPI cam_gst_capture_thread(LPVOID param)
261{
262 CamGstHal* hal = (CamGstHal*)param;
263 WINPR_ASSERT(hal);
264
265 BOOL endLogged = FALSE;
266 for (;;)
267 {
268 EnterCriticalSection(&hal->lock);
269 const BOOL streaming = hal->streaming;
270 LeaveCriticalSection(&hal->lock);
271 if (!streaming)
272 break;
273
274 GstSample* sample = gst_app_sink_try_pull_sample(hal->sink, CAM_GST_PULL_TIMEOUT);
275 if (!sample)
276 {
277 if (cam_gst_log_errors(hal))
278 break;
279
280 if (!gst_app_sink_is_eos(hal->sink))
281 continue;
282
283 if (hal->loop && !hal->live)
284 {
285 WLog_DBG(TAG, "End of stream, playing it again");
286 if (!gst_element_seek_simple(hal->pipeline, GST_FORMAT_TIME,
287 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 0))
288 {
289 WLog_ERR(TAG, "Unable to rewind the pipeline");
290 break;
291 }
292 }
293 else if (!endLogged)
294 {
295 /* the camera keeps its last frame until the stream is stopped */
296 WLog_INFO(TAG, "End of stream");
297 endLogged = TRUE;
298 }
299 continue;
300 }
301
302 const BOOL converted = cam_gst_convert_sample(hal, sample);
303 gst_sample_unref(sample);
304 if (!converted)
305 continue;
306
307 const UINT error =
308 hal->sampleCallback(hal->dev, hal->streamIndex, hal->sample, hal->sampleSize);
309 if (error == ECAM_SAMPLE_CAPTURE_DRAINED)
310 {
311 WLog_DBG(TAG, "No more sample requests, releasing the pipeline");
312 cam_gst_release(hal);
313 break;
314 }
315 else if (error != CHANNEL_RC_OK)
316 WLog_ERR(TAG, "Failure in sampleCallback: %" PRIu32, error);
317 }
318
319 EnterCriticalSection(&hal->lock);
320 hal->streaming = FALSE;
321 LeaveCriticalSection(&hal->lock);
322 return CHANNEL_RC_OK;
323}
324
325static void cam_gst_join(CamGstHal* hal)
326{
327 WINPR_ASSERT(hal);
328
329 EnterCriticalSection(&hal->lock);
330 hal->streaming = FALSE;
331 LeaveCriticalSection(&hal->lock);
332
333 if (hal->thread)
334 {
335 (void)WaitForSingleObject(hal->thread, INFINITE);
336 (void)CloseHandle(hal->thread);
337 hal->thread = nullptr;
338 }
339}
340
341static BOOL cam_gst_activate(WINPR_ATTR_UNUSED ICamHal* ihal,
342 WINPR_ATTR_UNUSED const char* deviceId, CAM_ERROR_CODE* errorCode)
343{
344 *errorCode = CAM_ERROR_CODE_None;
345 return TRUE;
346}
347
348static BOOL cam_gst_deactivate(WINPR_ATTR_UNUSED ICamHal* ihal,
349 WINPR_ATTR_UNUSED const char* deviceId, CAM_ERROR_CODE* errorCode)
350{
351 *errorCode = CAM_ERROR_CODE_None;
352 return TRUE;
353}
354
355static UINT cam_gst_enumerate(ICamHal* ihal, ICamHalEnumCallback callback, CameraPlugin* ecam,
356 GENERIC_CHANNEL_CALLBACK* hchannel)
357{
358 CamGstHal* hal = (CamGstHal*)ihal;
359 WINPR_ASSERT(hal);
360
361 IFCALL(callback, ecam, hchannel, CAM_GST_DEVICE_ID, hal->name);
362 return 1;
363}
364
371static INT16 cam_gst_get_media_type_descriptions(
372 ICamHal* ihal, WINPR_ATTR_UNUSED const char* deviceId, WINPR_ATTR_UNUSED size_t streamIndex,
373 const CAM_MEDIA_FORMAT_INFO* supportedFormats, size_t nSupportedFormats,
374 CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes, size_t* nMediaTypes)
375{
376 CamGstHal* hal = (CamGstHal*)ihal;
377 WINPR_ASSERT(hal);
378 WINPR_ASSERT(nMediaTypes);
379
380 const size_t maxMediaTypes = *nMediaTypes;
381 *nMediaTypes = 0;
382 if (maxMediaTypes == 0)
383 return -1;
384
385 /* the pipeline always ends with I420, take the preferred encoding of it */
386 for (size_t formatIndex = 0; formatIndex < nSupportedFormats; formatIndex++)
387 {
388 if (supportedFormats[formatIndex].inputFormat != CAM_MEDIA_FORMAT_I420)
389 continue;
390 if (formatIndex > INT16_MAX)
391 break;
392
393 *mediaTypes = (CAM_MEDIA_TYPE_DESCRIPTION){
394 .Format = CAM_MEDIA_FORMAT_I420,
395 .Width = hal->width,
396 .Height = hal->height,
397 .FrameRateNumerator = hal->frameRateNumerator,
398 .FrameRateDenominator = hal->frameRateDenominator,
399 .PixelAspectRatioNumerator = 1,
400 .PixelAspectRatioDenominator = 1,
401 };
402 *nMediaTypes = 1;
403 return (INT16)formatIndex;
404 }
405
406 WLog_ERR(TAG, "I420 is not a supported input format");
407 return -1;
408}
409
410static CAM_ERROR_CODE cam_gst_stream_start(ICamHal* ihal, CameraDevice* dev, size_t streamIndex,
411 const CAM_MEDIA_TYPE_DESCRIPTION* mediaType,
412 ICamHalSampleCapturedCallback callback)
413{
414 CamGstHal* hal = (CamGstHal*)ihal;
415 WINPR_ASSERT(hal);
416 WINPR_ASSERT(dev);
417 WINPR_ASSERT(mediaType);
418
419 if ((mediaType->Format != CAM_MEDIA_FORMAT_I420) || (mediaType->Width != hal->width) ||
420 (mediaType->Height != hal->height))
421 {
422 WLog_ERR(TAG, "Unexpected media type %u %" PRIu32 "x%" PRIu32, mediaType->Format,
423 mediaType->Width, mediaType->Height);
424 return CAM_ERROR_CODE_InvalidMediaType;
425 }
426
427 EnterCriticalSection(&hal->lock);
428 const BOOL streaming = hal->streaming;
429 LeaveCriticalSection(&hal->lock);
430
431 if (streaming)
432 {
433 WLog_ERR(TAG, "Streaming already in progress, device %s, streamIndex %" PRIuz,
434 dev->deviceId, streamIndex);
435 return CAM_ERROR_CODE_UnexpectedError;
436 }
437
438 /* the thread of a capture released for lack of sample requests */
439 cam_gst_join(hal);
440
441 hal->dev = dev;
442 hal->streamIndex = streamIndex;
443 hal->sampleCallback = callback;
444
445 if (!hal->pipeline && !cam_gst_pipeline_create(hal))
446 return CAM_ERROR_CODE_UnexpectedError;
447
448 if (!cam_gst_set_state(hal, GST_STATE_PLAYING, nullptr))
449 {
450 cam_gst_pipeline_destroy(hal);
451 return CAM_ERROR_CODE_UnexpectedError;
452 }
453
454 EnterCriticalSection(&hal->lock);
455 hal->streaming = TRUE;
456 LeaveCriticalSection(&hal->lock);
457
458 hal->thread = CreateThread(nullptr, 0, cam_gst_capture_thread, hal, 0, nullptr);
459 if (!hal->thread)
460 {
461 WLog_ERR(TAG, "CreateThread failure");
462 EnterCriticalSection(&hal->lock);
463 hal->streaming = FALSE;
464 LeaveCriticalSection(&hal->lock);
465 cam_gst_pipeline_destroy(hal);
466 return CAM_ERROR_CODE_OutOfMemory;
467 }
468
469 WLog_INFO(TAG, "Pipeline playing as camera: %" PRIu32 "x%" PRIu32 ", fps: %" PRIu32 "/%" PRIu32,
470 hal->width, hal->height, hal->frameRateNumerator, hal->frameRateDenominator);
471 return CAM_ERROR_CODE_None;
472}
473
474static CAM_ERROR_CODE cam_gst_stream_stop(ICamHal* ihal, WINPR_ATTR_UNUSED const char* deviceId,
475 WINPR_ATTR_UNUSED size_t streamIndex)
476{
477 CamGstHal* hal = (CamGstHal*)ihal;
478 WINPR_ASSERT(hal);
479
480 cam_gst_join(hal);
481
482 /* closes the source, the next stream starts from the beginning */
483 cam_gst_pipeline_destroy(hal);
484 return CAM_ERROR_CODE_None;
485}
486
487static CAM_ERROR_CODE cam_gst_free(ICamHal* ihal)
488{
489 CamGstHal* hal = (CamGstHal*)ihal;
490
491 if (!hal)
492 return CAM_ERROR_CODE_NotInitialized;
493
494 cam_gst_join(hal);
495 cam_gst_pipeline_destroy(hal);
496 DeleteCriticalSection(&hal->lock);
497
498 free(hal->sample);
499 free(hal->description);
500 free(hal->name);
501 free(hal);
502 return CAM_ERROR_CODE_None;
503}
504
507WINPR_ATTR_NODISCARD
508static BOOL cam_gst_probe(CamGstHal* hal)
509{
510 WINPR_ASSERT(hal);
511
512 WLog_DBG(TAG, "Pipeline: %s", hal->description);
513
514 if (!cam_gst_pipeline_create(hal))
515 return FALSE;
516
517 /* a live source does not preroll, it has to play to show its format */
518 BOOL rc = FALSE;
519 GstSample* sample = nullptr;
520 GstStateChangeReturn state = GST_STATE_CHANGE_FAILURE;
521 if (!cam_gst_set_state(hal, GST_STATE_PAUSED, &state))
522 goto out;
523
524 hal->live = (state == GST_STATE_CHANGE_NO_PREROLL);
525 if (hal->live)
526 {
527 if (!cam_gst_set_state(hal, GST_STATE_PLAYING, nullptr))
528 goto out;
529 sample = gst_app_sink_try_pull_sample(hal->sink, CAM_GST_START_TIMEOUT);
530 }
531 else
532 sample = gst_app_sink_try_pull_preroll(hal->sink, CAM_GST_START_TIMEOUT);
533
534 if (!sample)
535 {
536 if (!cam_gst_log_errors(hal))
537 WLog_ERR(TAG, "The pipeline did not produce any frame");
538 goto out;
539 }
540
541 GstVideoInfo info;
542 GstCaps* caps = gst_sample_get_caps(sample);
543 if (!caps || !gst_video_info_from_caps(&info, caps))
544 {
545 WLog_ERR(TAG, "The pipeline does not produce video");
546 goto out;
547 }
548
549 /* I420 needs even dimensions: crop the last column or row if needed */
550 const int width = GST_VIDEO_INFO_WIDTH(&info);
551 const int height = GST_VIDEO_INFO_HEIGHT(&info);
552 if ((width < 2) || (height < 2))
553 {
554 WLog_ERR(TAG, "Invalid video size %dx%d", width, height);
555 goto out;
556 }
557 hal->width = (UINT32)width & ~1u;
558 hal->height = (UINT32)height & ~1u;
559
560 /* 0/1 is a variable frame rate */
561 if ((GST_VIDEO_INFO_FPS_N(&info) > 0) && (GST_VIDEO_INFO_FPS_D(&info) > 0))
562 {
563 hal->frameRateNumerator = (UINT32)GST_VIDEO_INFO_FPS_N(&info);
564 hal->frameRateDenominator = (UINT32)GST_VIDEO_INFO_FPS_D(&info);
565 }
566 else
567 {
568 WLog_WARN(TAG, "Variable frame rate, announcing %d fps",
569 CAM_GST_FRAMERATE_NUMERATOR_DEFAULT);
570 hal->frameRateNumerator = CAM_GST_FRAMERATE_NUMERATOR_DEFAULT;
571 hal->frameRateDenominator = CAM_GST_FRAMERATE_DENOMINATOR_DEFAULT;
572 }
573
574 hal->sampleSize = 3ull * hal->width * hal->height / 2;
575 hal->sample = calloc(1, hal->sampleSize);
576 if (!hal->sample)
577 goto out;
578
579 WLog_INFO(TAG,
580 "%s pipeline: %dx%d, announced as %" PRIu32 "x%" PRIu32 " at %" PRIu32 "/%" PRIu32
581 " fps",
582 hal->live ? "Live" : "Non live", width, height, hal->width, hal->height,
583 hal->frameRateNumerator, hal->frameRateDenominator);
584 rc = TRUE;
585
586out:
587 if (sample)
588 gst_sample_unref(sample);
589
590 /* nothing runs until the server starts the camera */
591 cam_gst_pipeline_destroy(hal);
592 return rc;
593}
594
595static const char* cam_gst_basename(const char* path)
596{
597 const char* name = path;
598 for (const char* cur = path; *cur; cur++)
599 {
600 if ((*cur == '/') || (*cur == '\\'))
601 name = cur + 1;
602 }
603 return name;
604}
605
606WINPR_ATTR_MALLOC(free, 1)
607static char* cam_gst_read_file(const char* path)
608{
609 WINPR_ASSERT(path);
610
611 char* content = nullptr;
612 FILE* fp = winpr_fopen(path, "rb");
613 if (!fp)
614 {
615 WLog_ERR(TAG, "Unable to open pipeline file %s", path);
616 return nullptr;
617 }
618
619 if (_fseeki64(fp, 0, SEEK_END) != 0)
620 goto out;
621 const INT64 size = _ftelli64(fp);
622 if ((size <= 0) || (size > 1024 * 1024) || (_fseeki64(fp, 0, SEEK_SET) != 0))
623 goto out;
624
625 content = calloc(1, (size_t)size + 1);
626 if (!content)
627 goto out;
628
629 if (fread(content, 1, (size_t)size, fp) != (size_t)size)
630 {
631 free(content);
632 content = nullptr;
633 }
634
635out:
636 if (!content)
637 WLog_ERR(TAG, "Unable to read pipeline file %s", path);
638 (void)fclose(fp);
639 return content;
640}
641
642WINPR_ATTR_NODISCARD
643static BOOL cam_gst_parse_bool(const char* value, BOOL* result)
644{
645 WINPR_ASSERT(value);
646 WINPR_ASSERT(result);
647
648 if ((_stricmp(value, "on") == 0) || (_stricmp(value, "true") == 0) || (strcmp(value, "1") == 0))
649 *result = TRUE;
650 else if ((_stricmp(value, "off") == 0) || (_stricmp(value, "false") == 0) ||
651 (strcmp(value, "0") == 0))
652 *result = FALSE;
653 else
654 return FALSE;
655 return TRUE;
656}
657
658WINPR_ATTR_NODISCARD
659static BOOL cam_gst_parse_args(CamGstHal* hal, const ADDIN_ARGV* args)
660{
661 WINPR_ASSERT(hal);
662
663 COMMAND_LINE_ARGUMENT_A gst_args[] = {
664 { "pipeline", COMMAND_LINE_VALUE_REQUIRED, "<pipeline>", nullptr, nullptr, -1, nullptr,
665 "pipeline producing video, in gst-launch syntax" },
666 { "pipeline-file", COMMAND_LINE_VALUE_REQUIRED, "<path>", nullptr, nullptr, -1, nullptr,
667 "file containing the pipeline" },
668 { "file", COMMAND_LINE_VALUE_REQUIRED, "<path>", nullptr, nullptr, -1, nullptr,
669 "media file to play" },
670 { "name", COMMAND_LINE_VALUE_REQUIRED, "<name>", nullptr, nullptr, -1, nullptr,
671 "camera name" },
672 { "loop", COMMAND_LINE_VALUE_REQUIRED, "<on|off>", nullptr, nullptr, -1, nullptr,
673 "play the pipeline again when it ends" },
674 { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
675 };
676
677 char* source = nullptr;
678 char* defaultName = nullptr;
679 size_t sources = 0;
680 size_t len = 0;
681 BOOL rc = FALSE;
682
683 if (args && (args->argc > 1))
684 {
685 const DWORD flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON |
686 COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
687 const int status = CommandLineParseArgumentsA(args->argc, args->argv, gst_args, flags, hal,
688 nullptr, nullptr);
689 if (status != 0)
690 return FALSE;
691
692 const COMMAND_LINE_ARGUMENT_A* arg = gst_args;
693 do
694 {
695 if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
696 continue;
697
698 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "pipeline")
699 {
700 sources++;
701 free(source);
702 source = _strdup(arg->Value);
703 if (!source)
704 goto out;
705 }
706 CommandLineSwitchCase(arg, "pipeline-file")
707 {
708 sources++;
709 free(source);
710 source = cam_gst_read_file(arg->Value);
711 if (!source)
712 goto out;
713 }
714 CommandLineSwitchCase(arg, "file")
715 {
716 sources++;
717 free(source);
718 source = nullptr;
719
720 GError* error = nullptr;
721 gchar* uri = gst_filename_to_uri(arg->Value, &error);
722 if (!uri)
723 {
724 WLog_ERR(TAG, "Invalid file %s: %s", arg->Value,
725 error ? error->message : "unknown error");
726 g_clear_error(&error);
727 goto out;
728 }
729 /* only decode the video streams, the other ones are exposed undecoded and stay
730 * unlinked. Don't add expose-all-streams=false: combined with these caps,
731 * decodebin stops finding decoders at all ("Missing decoder: H.264") */
732 winpr_asprintf(&source, &len, "uridecodebin uri=\"%s\" caps=video/x-raw", uri);
733 g_free(uri);
734 if (!source)
735 goto out;
736
737 free(defaultName);
738 defaultName = nullptr;
739 winpr_asprintf(&defaultName, &len, "Movie: %s", cam_gst_basename(arg->Value));
740 if (!defaultName)
741 goto out;
742 }
743 CommandLineSwitchCase(arg, "name")
744 {
745 free(hal->name);
746 hal->name = _strdup(arg->Value);
747 if (!hal->name)
748 goto out;
749 }
750 CommandLineSwitchCase(arg, "loop")
751 {
752 if (!cam_gst_parse_bool(arg->Value, &hal->loop))
753 {
754 WLog_ERR(TAG, "Invalid loop value %s, expecting on or off", arg->Value);
755 goto out;
756 }
757 }
758 CommandLineSwitchDefault(arg)
759 {
760 }
761 CommandLineSwitchEnd(arg)
762 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
763 }
764
765 if (sources != 1)
766 {
767 WLog_ERR(TAG, "The gst backend needs one of pipeline:<pipeline>, pipeline-file:<path> or "
768 "file:<path>");
769 goto out;
770 }
771
772 winpr_asprintf(&hal->description, &len,
773 "%s ! videoconvert ! video/x-raw,format=I420 ! appsink name=" CAM_GST_SINK_NAME
774 " sync=true max-buffers=1 drop=true",
775 source);
776 if (!hal->description)
777 goto out;
778
779 if (!hal->name)
780 {
781 hal->name = defaultName ? defaultName : _strdup("GStreamer camera");
782 defaultName = nullptr;
783 if (!hal->name)
784 goto out;
785 }
786 rc = TRUE;
787
788out:
789 free(source);
790 free(defaultName);
791 return rc;
792}
793
799FREERDP_ENTRY_POINT(UINT VCAPITYPE gst_freerdp_rdpecam_client_subsystem_entry(
801{
802 UINT ret = ERROR_INVALID_PARAMETER;
803 WINPR_ASSERT(pEntryPoints);
804
805 GError* error = nullptr;
806 if (!gst_init_check(nullptr, nullptr, &error))
807 {
808 WLog_ERR(TAG, "Unable to initialize GStreamer: %s",
809 error ? error->message : "unknown error");
810 g_clear_error(&error);
811 return ERROR_INTERNAL_ERROR;
812 }
813
814 CamGstHal* hal = (CamGstHal*)calloc(1, sizeof(CamGstHal));
815 if (!hal)
816 return CHANNEL_RC_NO_MEMORY;
817
818 hal->iHal.Enumerate = cam_gst_enumerate;
819 hal->iHal.RequestDrivenCapture = TRUE;
820 hal->iHal.GetMediaTypeDescriptions = cam_gst_get_media_type_descriptions;
821 hal->iHal.Activate = cam_gst_activate;
822 hal->iHal.Deactivate = cam_gst_deactivate;
823 hal->iHal.StartStream = cam_gst_stream_start;
824 hal->iHal.StopStream = cam_gst_stream_stop;
825 hal->iHal.Free = cam_gst_free;
826 hal->loop = TRUE;
827
828 if (!InitializeCriticalSectionEx(&hal->lock, 0, 0))
829 {
830 free(hal);
831 return CHANNEL_RC_NO_MEMORY;
832 }
833
834 if (!cam_gst_parse_args(hal, pEntryPoints->args))
835 goto error;
836
837 /* fail now rather than when the server opens the camera */
838 if (!cam_gst_probe(hal))
839 goto error;
840
841 ret = pEntryPoints->pRegisterCameraHal(pEntryPoints->plugin, &hal->iHal);
842 if (ret != CHANNEL_RC_OK)
843 {
844 WLog_ERR(TAG, "RegisterCameraHal failed with error %" PRIu32 "", ret);
845 goto error;
846 }
847
848 return ret;
849
850error:
851 cam_gst_free(&hal->iHal);
852 return ret;
853}
Definition camera.h:237