FreeRDP
camera_v4l.c
1 
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <poll.h>
23 #include <sys/ioctl.h>
24 #include <sys/mman.h>
25 
26 /* v4l includes */
27 #include <linux/videodev2.h>
28 
29 #include "camera.h"
30 #include <winpr/handle.h>
31 
32 #define TAG CHANNELS_TAG("rdpecam-v4l.client")
33 
34 #define CAM_V4L2_BUFFERS_COUNT 4
35 #define CAM_V4L2_CAPTURE_THREAD_SLEEP_MS 1000
36 
37 #define CAM_V4L2_FRAMERATE_NUMERATOR_DEFAULT 30
38 #define CAM_V4L2_FRAMERATE_DENOMINATOR_DEFAULT 1
39 
40 typedef struct
41 {
42  void* start;
43  size_t length;
44 
45 } CamV4lBuffer;
46 
47 typedef struct
48 {
49  CRITICAL_SECTION lock;
50 
51  /* members used to call the callback */
52  CameraDevice* dev;
53  int streamIndex;
54  ICamHalSampleCapturedCallback sampleCallback;
55 
56  BOOL streaming;
57  int fd;
58  size_t nBuffers;
59  CamV4lBuffer* buffers;
60  HANDLE captureThread;
61 
62 } CamV4lStream;
63 
64 typedef struct
65 {
66  ICamHal iHal;
67 
68  wHashTable* streams; /* Index: deviceId, Value: CamV4lStream */
69 
70 } CamV4lHal;
71 
72 static void cam_v4l_stream_free(void* obj);
73 static void cam_v4l_stream_close_device(CamV4lStream* stream);
74 static UINT cam_v4l_stream_stop(CamV4lStream* stream);
75 
81 static const char* cam_v4l_get_fourcc_str(unsigned int fourcc, char* buffer, size_t size)
82 {
83  if (size < 5)
84  return NULL;
85 
86  buffer[0] = (char)(fourcc & 0xFF);
87  buffer[1] = (char)((fourcc >> 8) & 0xFF);
88  buffer[2] = (char)((fourcc >> 16) & 0xFF);
89  buffer[3] = (char)((fourcc >> 24) & 0xFF);
90  buffer[4] = '\0';
91  return buffer;
92 }
93 
99 static UINT32 ecamToV4L2PixFormat(CAM_MEDIA_FORMAT ecamFormat)
100 {
101  switch (ecamFormat)
102  {
103  case CAM_MEDIA_FORMAT_H264:
104  return V4L2_PIX_FMT_H264;
105  case CAM_MEDIA_FORMAT_MJPG:
106  return V4L2_PIX_FMT_MJPEG;
107  case CAM_MEDIA_FORMAT_YUY2:
108  return V4L2_PIX_FMT_YUYV;
109  case CAM_MEDIA_FORMAT_NV12:
110  return V4L2_PIX_FMT_NV12;
111  case CAM_MEDIA_FORMAT_I420:
112  return V4L2_PIX_FMT_YUV420;
113  case CAM_MEDIA_FORMAT_RGB24:
114  return V4L2_PIX_FMT_RGB24;
115  case CAM_MEDIA_FORMAT_RGB32:
116  return V4L2_PIX_FMT_RGB32;
117  default:
118  WLog_ERR(TAG, "Unsupported CAM_MEDIA_FORMAT %d", ecamFormat);
119  return 0;
120  }
121 }
122 
128 static BOOL cam_v4l_format_supported(int fd, UINT32 format)
129 {
130  struct v4l2_fmtdesc fmtdesc = { 0 };
131  fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
132 
133  for (fmtdesc.index = 0; ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0; fmtdesc.index++)
134  {
135  if (fmtdesc.pixelformat == format)
136  return TRUE;
137  }
138  return FALSE;
139 }
140 
146 static int cam_v4l_open_device(const char* deviceId, int flags)
147 {
148  char device[20] = { 0 };
149  int fd = -1;
150  struct v4l2_capability cap = { 0 };
151 
152  if (!deviceId)
153  return -1;
154 
155  if (0 == strncmp(deviceId, "/dev/video", 10))
156  return open(deviceId, flags);
157 
158  for (UINT n = 0; n < 64; n++)
159  {
160  (void)_snprintf(device, sizeof(device), "/dev/video%" PRIu32, n);
161  if ((fd = open(device, flags)) == -1)
162  continue;
163 
164  /* query device capabilities and make sure this is a video capture device */
165  if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE))
166  {
167  close(fd);
168  continue;
169  }
170 
171  if (cap.bus_info[0] != 0 && 0 == strcmp((const char*)cap.bus_info, deviceId))
172  return fd;
173 
174  close(fd);
175  }
176 
177  return fd;
178 }
179 
186 static INT16 cam_v4l_get_media_type_descriptions(ICamHal* hal, const char* deviceId,
187  int streamIndex,
188  const CAM_MEDIA_FORMAT_INFO* supportedFormats,
189  size_t nSupportedFormats,
190  CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes,
191  size_t* nMediaTypes)
192 {
193  size_t maxMediaTypes = *nMediaTypes;
194  size_t nTypes = 0;
195  BOOL formatFound = FALSE;
196 
197  int fd = cam_v4l_open_device(deviceId, O_RDONLY);
198  if (fd == -1)
199  {
200  WLog_ERR(TAG, "Unable to open device %s", deviceId);
201  return -1;
202  }
203 
204  size_t formatIndex = 0;
205  for (; formatIndex < nSupportedFormats; formatIndex++)
206  {
207  UINT32 pixelFormat = ecamToV4L2PixFormat(supportedFormats[formatIndex].inputFormat);
208  WINPR_ASSERT(pixelFormat != 0);
209  struct v4l2_frmsizeenum frmsize = { 0 };
210 
211  if (!cam_v4l_format_supported(fd, pixelFormat))
212  continue;
213 
214  frmsize.pixel_format = pixelFormat;
215  for (frmsize.index = 0; ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0; frmsize.index++)
216  {
217  struct v4l2_frmivalenum frmival = { 0 };
218 
219  if (frmsize.type != V4L2_FRMSIZE_TYPE_DISCRETE)
220  break; /* don't support size types other than discrete */
221 
222  formatFound = TRUE;
223  mediaTypes->Width = frmsize.discrete.width;
224  mediaTypes->Height = frmsize.discrete.height;
225  mediaTypes->Format = supportedFormats[formatIndex].inputFormat;
226 
227  /* query frame rate (1st is highest fps supported) */
228  frmival.index = 0;
229  frmival.pixel_format = pixelFormat;
230  frmival.width = frmsize.discrete.width;
231  frmival.height = frmsize.discrete.height;
232  if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0 &&
233  frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE)
234  {
235  /* inverse of a fraction */
236  mediaTypes->FrameRateNumerator = frmival.discrete.denominator;
237  mediaTypes->FrameRateDenominator = frmival.discrete.numerator;
238  }
239  else
240  {
241  WLog_DBG(TAG, "VIDIOC_ENUM_FRAMEINTERVALS failed, using default framerate");
242  mediaTypes->FrameRateNumerator = CAM_V4L2_FRAMERATE_NUMERATOR_DEFAULT;
243  mediaTypes->FrameRateDenominator = CAM_V4L2_FRAMERATE_DENOMINATOR_DEFAULT;
244  }
245 
246  mediaTypes->PixelAspectRatioNumerator = mediaTypes->PixelAspectRatioDenominator = 1;
247 
248  char fourccstr[5] = { 0 };
249  WLog_DBG(TAG, "Camera format: %s, width: %u, height: %u, fps: %u/%u",
250  cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr)),
251  mediaTypes->Width, mediaTypes->Height, mediaTypes->FrameRateNumerator,
252  mediaTypes->FrameRateDenominator);
253 
254  mediaTypes++;
255  nTypes++;
256 
257  if (nTypes == maxMediaTypes)
258  {
259  WLog_ERR(TAG, "Media types reached buffer maximum %" PRIu32 "", maxMediaTypes);
260  goto error;
261  }
262  }
263 
264  if (formatFound)
265  {
266  /* we are interested in 1st supported format only, with all supported sizes */
267  break;
268  }
269  }
270 
271 error:
272 
273  *nMediaTypes = nTypes;
274  close(fd);
275  if (formatIndex > INT16_MAX)
276  return -1;
277  return (INT16)formatIndex;
278 }
279 
285 static UINT cam_v4l_enumerate(ICamHal* ihal, ICamHalEnumCallback callback, CameraPlugin* ecam,
286  GENERIC_CHANNEL_CALLBACK* hchannel)
287 {
288  UINT count = 0;
289 
290  for (UINT n = 0; n < 64; n++)
291  {
292  char device[20] = { 0 };
293  struct v4l2_capability cap = { 0 };
294  (void)_snprintf(device, sizeof(device), "/dev/video%" PRIu32, n);
295  int fd = open(device, O_RDONLY);
296  if (fd == -1)
297  continue;
298 
299  /* query device capabilities and make sure this is a video capture device */
300  if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE))
301  {
302  close(fd);
303  continue;
304  }
305  count++;
306 
307  const char* deviceName = (char*)cap.card;
308  const char* deviceId = device;
309  if (cap.bus_info[0] != 0) /* may not be available in all drivers */
310  deviceId = (char*)cap.bus_info;
311 
312  IFCALL(callback, ecam, hchannel, deviceId, deviceName);
313 
314  close(fd);
315  }
316 
317  return count;
318 }
319 
320 static void cam_v4l_stream_free_buffers(CamV4lStream* stream)
321 {
322  if (!stream || !stream->buffers)
323  return;
324 
325  /* unmap buffers */
326  for (size_t i = 0; i < stream->nBuffers; i++)
327  {
328  if (stream->buffers[i].length && stream->buffers[i].start != MAP_FAILED)
329  {
330  munmap(stream->buffers[i].start, stream->buffers[i].length);
331  }
332  }
333 
334  free(stream->buffers);
335  stream->buffers = NULL;
336  stream->nBuffers = 0;
337 }
338 
344 static size_t cam_v4l_stream_alloc_buffers(CamV4lStream* stream)
345 {
346  struct v4l2_requestbuffers rbuffer = { 0 };
347 
348  rbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
349  rbuffer.memory = V4L2_MEMORY_MMAP;
350  rbuffer.count = CAM_V4L2_BUFFERS_COUNT;
351 
352  if (ioctl(stream->fd, VIDIOC_REQBUFS, &rbuffer) < 0 || rbuffer.count == 0)
353  {
354  WLog_ERR(TAG, "Failure in VIDIOC_REQBUFS, errno %d, count %d", errno, rbuffer.count);
355  return 0;
356  }
357 
358  stream->nBuffers = rbuffer.count;
359 
360  /* Map the buffers */
361  stream->buffers = (CamV4lBuffer*)calloc(rbuffer.count, sizeof(CamV4lBuffer));
362  if (!stream->buffers)
363  {
364  WLog_ERR(TAG, "Failure in calloc");
365  return 0;
366  }
367 
368  for (unsigned int i = 0; i < rbuffer.count; i++)
369  {
370  struct v4l2_buffer buffer = { 0 };
371  buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
372  buffer.memory = V4L2_MEMORY_MMAP;
373  buffer.index = i;
374 
375  if (ioctl(stream->fd, VIDIOC_QUERYBUF, &buffer) < 0)
376  {
377  WLog_ERR(TAG, "Failure in VIDIOC_QUERYBUF, errno %d", errno);
378  cam_v4l_stream_free_buffers(stream);
379  return 0;
380  }
381 
382  stream->buffers[i].start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
383  stream->fd, buffer.m.offset);
384 
385  if (MAP_FAILED == stream->buffers[i].start)
386  {
387  WLog_ERR(TAG, "Failure in mmap, errno %d", errno);
388  cam_v4l_stream_free_buffers(stream);
389  return 0;
390  }
391 
392  stream->buffers[i].length = buffer.length;
393 
394  WLog_DBG(TAG, "Buffer %d mapped, size: %d", i, buffer.length);
395 
396  if (ioctl(stream->fd, VIDIOC_QBUF, &buffer) < 0)
397  {
398  WLog_ERR(TAG, "Failure in VIDIOC_QBUF, errno %d", errno);
399  cam_v4l_stream_free_buffers(stream);
400  return 0;
401  }
402  }
403 
404  return stream->buffers[0].length;
405 }
406 
412 static UINT cam_v4l_stream_capture_thread(void* param)
413 {
414  CamV4lStream* stream = (CamV4lStream*)param;
415 
416  int fd = stream->fd;
417 
418  do
419  {
420  int retVal = 0;
421  struct pollfd pfd = { 0 };
422 
423  pfd.fd = fd;
424  pfd.events = POLLIN;
425 
426  retVal = poll(&pfd, 1, CAM_V4L2_CAPTURE_THREAD_SLEEP_MS);
427 
428  if (retVal == 0)
429  {
430  /* poll timed out */
431  continue;
432  }
433  else if (retVal < 0)
434  {
435  WLog_DBG(TAG, "Failure in poll, errno %d", errno);
436  Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS); /* trying to recover */
437  continue;
438  }
439  else if (!(pfd.revents & POLLIN))
440  {
441  WLog_DBG(TAG, "poll reported non-read event %d", pfd.revents);
442  Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS); /* also trying to recover */
443  continue;
444  }
445 
446  EnterCriticalSection(&stream->lock);
447  if (stream->streaming)
448  {
449  struct v4l2_buffer buf = { 0 };
450  buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
451  buf.memory = V4L2_MEMORY_MMAP;
452 
453  /* dequeue buffers until empty */
454  while (ioctl(fd, VIDIOC_DQBUF, &buf) != -1)
455  {
456  stream->sampleCallback(stream->dev, stream->streamIndex,
457  stream->buffers[buf.index].start, buf.bytesused);
458 
459  /* enqueue buffer back */
460  if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
461  {
462  WLog_ERR(TAG, "Failure in VIDIOC_QBUF, errno %d", errno);
463  }
464  }
465  }
466  LeaveCriticalSection(&stream->lock);
467 
468  } while (stream->streaming);
469 
470  return CHANNEL_RC_OK;
471 }
472 
473 void cam_v4l_stream_close_device(CamV4lStream* stream)
474 {
475  if (stream->fd != -1)
476  {
477  close(stream->fd);
478  stream->fd = -1;
479  }
480 }
481 
487 static CamV4lStream* cam_v4l_stream_create(CameraDevice* dev, int streamIndex,
488  ICamHalSampleCapturedCallback callback)
489 {
490  CamV4lStream* stream = calloc(1, sizeof(CamV4lStream));
491 
492  if (!stream)
493  {
494  WLog_ERR(TAG, "Failure in calloc");
495  return NULL;
496  }
497  stream->dev = dev;
498  stream->streamIndex = streamIndex;
499  stream->sampleCallback = callback;
500 
501  stream->fd = -1;
502 
503  if (!InitializeCriticalSectionEx(&stream->lock, 0, 0))
504  {
505  WLog_ERR(TAG, "Failure in calloc");
506  free(stream);
507  return NULL;
508  }
509 
510  return stream;
511 }
512 
518 UINT cam_v4l_stream_stop(CamV4lStream* stream)
519 {
520  if (!stream || !stream->streaming)
521  return CHANNEL_RC_OK;
522 
523  stream->streaming = FALSE; /* this will terminate capture thread */
524 
525  if (stream->captureThread)
526  {
527  (void)WaitForSingleObject(stream->captureThread, INFINITE);
528  (void)CloseHandle(stream->captureThread);
529  stream->captureThread = NULL;
530  }
531 
532  EnterCriticalSection(&stream->lock);
533 
534  /* stop streaming */
535  enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
536  if (ioctl(stream->fd, VIDIOC_STREAMOFF, &type) < 0)
537  {
538  WLog_ERR(TAG, "Failure in VIDIOC_STREAMOFF, errno %d", errno);
539  }
540 
541  cam_v4l_stream_free_buffers(stream);
542  cam_v4l_stream_close_device(stream);
543 
544  LeaveCriticalSection(&stream->lock);
545 
546  return CHANNEL_RC_OK;
547 }
548 
554 static UINT cam_v4l_stream_start(ICamHal* ihal, CameraDevice* dev, int streamIndex,
555  const CAM_MEDIA_TYPE_DESCRIPTION* mediaType,
556  ICamHalSampleCapturedCallback callback)
557 {
558  CamV4lHal* hal = (CamV4lHal*)ihal;
559 
560  CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, dev->deviceId);
561 
562  if (!stream)
563  {
564  stream = cam_v4l_stream_create(dev, streamIndex, callback);
565  if (!stream)
566  return CAM_ERROR_CODE_OutOfMemory;
567 
568  if (!HashTable_Insert(hal->streams, dev->deviceId, stream))
569  {
570  cam_v4l_stream_free(stream);
571  return CAM_ERROR_CODE_UnexpectedError;
572  }
573  }
574 
575  if (stream->streaming)
576  {
577  WLog_ERR(TAG, "Streaming already in progress, device %s, streamIndex %d", dev->deviceId,
578  streamIndex);
579  return CAM_ERROR_CODE_UnexpectedError;
580  }
581 
582  if ((stream->fd = cam_v4l_open_device(dev->deviceId, O_RDWR | O_NONBLOCK)) == -1)
583  {
584  WLog_ERR(TAG, "Unable to open device %s", dev->deviceId);
585  return CAM_ERROR_CODE_UnexpectedError;
586  }
587 
588  struct v4l2_format video_fmt = { 0 };
589  video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
590  video_fmt.fmt.pix.sizeimage = 0;
591  video_fmt.fmt.pix.width = mediaType->Width;
592  video_fmt.fmt.pix.height = mediaType->Height;
593  UINT32 pixelFormat = ecamToV4L2PixFormat(mediaType->Format);
594  if (pixelFormat == 0)
595  {
596  cam_v4l_stream_close_device(stream);
597  return CAM_ERROR_CODE_InvalidMediaType;
598  }
599  video_fmt.fmt.pix.pixelformat = pixelFormat;
600 
601  /* set format and frame size */
602  if (ioctl(stream->fd, VIDIOC_S_FMT, &video_fmt) < 0)
603  {
604  WLog_ERR(TAG, "Failure in VIDIOC_S_FMT, errno %d", errno);
605  cam_v4l_stream_close_device(stream);
606  return CAM_ERROR_CODE_InvalidMediaType;
607  }
608 
609  /* trying to set frame rate, if driver supports it */
610  struct v4l2_streamparm sp1 = { 0 };
611  struct v4l2_streamparm sp2 = { 0 };
612  sp1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
613  if (ioctl(stream->fd, VIDIOC_G_PARM, &sp1) < 0 ||
614  !(sp1.parm.capture.capability & V4L2_CAP_TIMEPERFRAME))
615  {
616  WLog_INFO(TAG, "Driver doesn't support setting framerate");
617  }
618  else
619  {
620  sp2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
621 
622  /* inverse of a fraction */
623  sp2.parm.capture.timeperframe.numerator = mediaType->FrameRateDenominator;
624  sp2.parm.capture.timeperframe.denominator = mediaType->FrameRateNumerator;
625 
626  if (ioctl(stream->fd, VIDIOC_S_PARM, &sp2) < 0)
627  {
628  WLog_INFO(TAG, "Failed to set the framerate, errno %d", errno);
629  }
630  }
631 
632  size_t maxSample = cam_v4l_stream_alloc_buffers(stream);
633  if (maxSample == 0)
634  {
635  WLog_ERR(TAG, "Failure to allocate video buffers");
636  cam_v4l_stream_close_device(stream);
637  return CAM_ERROR_CODE_OutOfMemory;
638  }
639 
640  stream->streaming = TRUE;
641 
642  /* start streaming */
643  enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
644  if (ioctl(stream->fd, VIDIOC_STREAMON, &type) < 0)
645  {
646  WLog_ERR(TAG, "Failure in VIDIOC_STREAMON, errno %d", errno);
647  cam_v4l_stream_stop(stream);
648  return CAM_ERROR_CODE_UnexpectedError;
649  }
650 
651  stream->captureThread = CreateThread(NULL, 0, cam_v4l_stream_capture_thread, stream, 0, NULL);
652  if (!stream->captureThread)
653  {
654  WLog_ERR(TAG, "CreateThread failure");
655  cam_v4l_stream_stop(stream);
656  return CAM_ERROR_CODE_OutOfMemory;
657  }
658 
659  char fourccstr[5] = { 0 };
660  WLog_INFO(TAG, "Camera format: %s, width: %u, height: %u, fps: %u/%u",
661  cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr)),
662  mediaType->Width, mediaType->Height, mediaType->FrameRateNumerator,
663  mediaType->FrameRateDenominator);
664 
665  return CHANNEL_RC_OK;
666 }
667 
673 static UINT cam_v4l_stream_stop_by_device_id(ICamHal* ihal, const char* deviceId, int streamIndex)
674 {
675  CamV4lHal* hal = (CamV4lHal*)ihal;
676 
677  CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, deviceId);
678 
679  if (!stream)
680  return CHANNEL_RC_OK;
681 
682  return cam_v4l_stream_stop(stream);
683 }
684 
691 void cam_v4l_stream_free(void* obj)
692 {
693  CamV4lStream* stream = (CamV4lStream*)obj;
694  if (!stream)
695  return;
696 
697  cam_v4l_stream_stop(stream);
698 
699  DeleteCriticalSection(&stream->lock);
700  free(stream);
701 }
702 
708 static UINT cam_v4l_free(ICamHal* ihal)
709 {
710  CamV4lHal* hal = (CamV4lHal*)ihal;
711 
712  if (hal == NULL)
713  return ERROR_INVALID_PARAMETER;
714 
715  HashTable_Free(hal->streams);
716 
717  free(hal);
718 
719  return CHANNEL_RC_OK;
720 }
721 
727 FREERDP_ENTRY_POINT(UINT VCAPITYPE v4l_freerdp_rdpecam_client_subsystem_entry(
728  PFREERDP_CAMERA_HAL_ENTRY_POINTS pEntryPoints))
729 {
730  UINT ret = CHANNEL_RC_OK;
731  WINPR_ASSERT(pEntryPoints);
732 
733  CamV4lHal* hal = (CamV4lHal*)calloc(1, sizeof(CamV4lHal));
734 
735  if (hal == NULL)
736  return CHANNEL_RC_NO_MEMORY;
737 
738  hal->iHal.Enumerate = cam_v4l_enumerate;
739  hal->iHal.GetMediaTypeDescriptions = cam_v4l_get_media_type_descriptions;
740  hal->iHal.StartStream = cam_v4l_stream_start;
741  hal->iHal.StopStream = cam_v4l_stream_stop_by_device_id;
742  hal->iHal.Free = cam_v4l_free;
743 
744  hal->streams = HashTable_New(FALSE);
745  if (!hal->streams)
746  {
747  ret = CHANNEL_RC_NO_MEMORY;
748  goto error;
749  }
750 
751  HashTable_SetupForStringData(hal->streams, FALSE);
752 
753  wObject* obj = HashTable_ValueObject(hal->streams);
754  WINPR_ASSERT(obj);
755  obj->fnObjectFree = cam_v4l_stream_free;
756 
757  if ((ret = pEntryPoints->pRegisterCameraHal(pEntryPoints->plugin, &hal->iHal)))
758  {
759  WLog_ERR(TAG, "RegisterCameraHal failed with error %" PRIu32 "", ret);
760  goto error;
761  }
762 
763  return ret;
764 
765 error:
766  cam_v4l_free(&hal->iHal);
767  return ret;
768 }
Definition: camera.h:163
This struct contains function pointer to initialize/free objects.
Definition: collections.h:57