27#include <linux/videodev2.h>
29#include "camera_v4l.h"
32#define TAG CHANNELS_TAG("rdpecam-v4l.client")
34#define CAM_V4L2_BUFFERS_COUNT 4
35#define CAM_V4L2_CAPTURE_THREAD_SLEEP_MS 1000
37#define CAM_V4L2_FRAMERATE_NUMERATOR_DEFAULT 30
38#define CAM_V4L2_FRAMERATE_DENOMINATOR_DEFAULT 1
48static CamV4lStream* cam_v4l_stream_create(
const char* deviceId,
size_t streamIndex);
49static void cam_v4l_stream_free(
void* obj);
50static void cam_v4l_stream_close_device(
CamV4lStream* stream);
51static CAM_ERROR_CODE cam_v4l_stream_stop(
CamV4lStream* stream);
58static const char* cam_v4l_get_fourcc_str(
unsigned int fourcc,
char* buffer,
size_t size)
63 buffer[0] = (char)(fourcc & 0xFF);
64 buffer[1] = (char)((fourcc >> 8) & 0xFF);
65 buffer[2] = (char)((fourcc >> 16) & 0xFF);
66 buffer[3] = (char)((fourcc >> 24) & 0xFF);
76static UINT32 ecamToV4L2PixFormat(CAM_MEDIA_FORMAT ecamFormat)
80 case CAM_MEDIA_FORMAT_H264:
81 return V4L2_PIX_FMT_H264;
82 case CAM_MEDIA_FORMAT_MJPG:
83 return V4L2_PIX_FMT_MJPEG;
84 case CAM_MEDIA_FORMAT_YUY2:
85 return V4L2_PIX_FMT_YUYV;
86 case CAM_MEDIA_FORMAT_NV12:
87 return V4L2_PIX_FMT_NV12;
88 case CAM_MEDIA_FORMAT_I420:
89 return V4L2_PIX_FMT_YUV420;
90 case CAM_MEDIA_FORMAT_RGB24:
91 return V4L2_PIX_FMT_RGB24;
92 case CAM_MEDIA_FORMAT_RGB32:
93 return V4L2_PIX_FMT_RGB32;
95 WLog_ERR(TAG,
"Unsupported CAM_MEDIA_FORMAT %u", ecamFormat);
105static BOOL cam_v4l_format_supported(
int fd, UINT32 format)
107 struct v4l2_fmtdesc fmtdesc = WINPR_C_ARRAY_INIT;
108 fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
110 for (fmtdesc.index = 0; ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0; fmtdesc.index++)
112 if (fmtdesc.pixelformat == format)
123static int cam_v4l_open_device(
const char* deviceId,
int flags)
125 char device[20] = WINPR_C_ARRAY_INIT;
127 struct v4l2_capability cap = WINPR_C_ARRAY_INIT;
132 if (0 == strncmp(deviceId,
"/dev/video", 10))
133 return open(deviceId, flags);
135 for (UINT n = 0; n < 64; n++)
137 (void)_snprintf(device,
sizeof(device),
"/dev/video%" PRIu32, n);
138 if ((fd = open(device, flags)) == -1)
142 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE))
148 if (cap.bus_info[0] != 0 && 0 == strcmp((
const char*)cap.bus_info, deviceId))
157static BOOL cam_v4l_activate(ICamHal* ihal,
const char* deviceId, CAM_ERROR_CODE* errorCode)
160 WINPR_UNUSED(deviceId);
162 *errorCode = CAM_ERROR_CODE_None;
166static BOOL cam_v4l_deactivate(ICamHal* ihal,
const char* deviceId, CAM_ERROR_CODE* errorCode)
169 WINPR_UNUSED(deviceId);
171 *errorCode = CAM_ERROR_CODE_None;
181static INT16 cam_v4l_get_media_type_descriptions(ICamHal* ihal,
const char* deviceId,
184 size_t nSupportedFormats,
188 CamV4lHal* hal = (CamV4lHal*)ihal;
189 size_t maxMediaTypes = *nMediaTypes;
191 BOOL formatFound = FALSE;
197 stream = cam_v4l_stream_create(deviceId, streamIndex);
199 return CAM_ERROR_CODE_OutOfMemory;
201 if (!HashTable_Insert(hal->streams, deviceId, stream))
203 cam_v4l_stream_free(stream);
204 return CAM_ERROR_CODE_UnexpectedError;
208 int fd = cam_v4l_open_device(deviceId, O_RDONLY);
211 WLog_ERR(TAG,
"Unable to open device %s", deviceId);
215 size_t formatIndex = 0;
216 for (; formatIndex < nSupportedFormats; formatIndex++)
218 const UINT32 pixelFormat = ecamToV4L2PixFormat(supportedFormats[formatIndex].inputFormat);
220 WINPR_ASSERT(pixelFormat != 0);
221 struct v4l2_frmsizeenum frmsize = WINPR_C_ARRAY_INIT;
223 if (!cam_v4l_format_supported(fd, pixelFormat))
226 frmsize.pixel_format = pixelFormat;
227 for (frmsize.index = 0; ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0; frmsize.index++)
229 struct v4l2_frmivalenum frmival = WINPR_C_ARRAY_INIT;
231 if (frmsize.type != V4L2_FRMSIZE_TYPE_DISCRETE)
235 mediaTypes->Width = frmsize.discrete.width;
236 mediaTypes->Height = frmsize.discrete.height;
237 mediaTypes->Format = supportedFormats[formatIndex].inputFormat;
241 frmival.pixel_format = pixelFormat;
242 frmival.width = frmsize.discrete.width;
243 frmival.height = frmsize.discrete.height;
244 if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0 &&
245 frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE)
248 mediaTypes->FrameRateNumerator = frmival.discrete.denominator;
249 mediaTypes->FrameRateDenominator = frmival.discrete.numerator;
253 WLog_DBG(TAG,
"VIDIOC_ENUM_FRAMEINTERVALS failed, using default framerate");
254 mediaTypes->FrameRateNumerator = CAM_V4L2_FRAMERATE_NUMERATOR_DEFAULT;
255 mediaTypes->FrameRateDenominator = CAM_V4L2_FRAMERATE_DENOMINATOR_DEFAULT;
258 mediaTypes->PixelAspectRatioNumerator = mediaTypes->PixelAspectRatioDenominator = 1;
260 char fourccstr[5] = WINPR_C_ARRAY_INIT;
261 WLog_DBG(TAG,
"Camera format: %s, width: %u, height: %u, fps: %u/%u",
262 cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr)),
263 mediaTypes->Width, mediaTypes->Height, mediaTypes->FrameRateNumerator,
264 mediaTypes->FrameRateDenominator);
269 if (nTypes == maxMediaTypes)
271 WLog_ERR(TAG,
"Media types reached buffer maximum %" PRIuz
"", maxMediaTypes);
285 *nMediaTypes = nTypes;
287 if (formatIndex > INT16_MAX)
289 return (INT16)formatIndex;
292static bool cam_v4l_formats_supported(
int fd,
const struct v4l2_capability* cap,
const char* device)
295 WINPR_ASSERT(device);
299 struct v4l2_fmtdesc fmtdesc = WINPR_C_ARRAY_INIT;
300 fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
303 char* formats =
nullptr;
304 bool supported =
false;
305 for (fmtdesc.index = 0; ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0; fmtdesc.index++)
307 char fmt[32] = WINPR_C_ARRAY_INIT;
308 memcpy(fmt, &fmtdesc.pixelformat,
sizeof(fmtdesc.pixelformat));
313 winpr_asprintf(&tmp, &tlen,
"%s, %s [%s]", formats, fmtdesc.description, fmt);
319 winpr_asprintf(&formats, &flen,
"%s [%s]", fmtdesc.description, fmt);
321 switch (fmtdesc.pixelformat)
323 case V4L2_PIX_FMT_H264:
324 case V4L2_PIX_FMT_MJPEG:
325 case V4L2_PIX_FMT_YUYV:
326 case V4L2_PIX_FMT_NV12:
327 case V4L2_PIX_FMT_YUV420:
328 case V4L2_PIX_FMT_RGB24:
329 case V4L2_PIX_FMT_RGB32:
338 WLog_DBG(TAG,
"Device %s (%s) selected, formats supported: {%s}", cap->card, device,
341 WLog_DBG(TAG,
"Device %s (%s) filtered, no formats supported: {%s}", cap->card, device,
352static UINT cam_v4l_enumerate(WINPR_ATTR_UNUSED ICamHal* ihal, ICamHalEnumCallback callback,
357 for (UINT n = 0; n < 64; n++)
359 char device[20] = WINPR_C_ARRAY_INIT;
360 struct v4l2_capability cap = WINPR_C_ARRAY_INIT;
361 (void)_snprintf(device,
sizeof(device),
"/dev/video%" PRIu32, n);
362 int fd = open(device, O_RDONLY);
367 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE) ||
368 !cam_v4l_formats_supported(fd, &cap, device))
375 const char* deviceName = (
char*)cap.card;
376 const char* deviceId = device;
377 if (cap.bus_info[0] != 0)
378 deviceId = (
char*)cap.bus_info;
380 IFCALL(callback, ecam, hchannel, deviceId, deviceName);
388static void cam_v4l_stream_free_buffers(
CamV4lStream* stream)
390 if (!stream || !stream->buffers)
394 for (
size_t i = 0; i < stream->nBuffers; i++)
396 if (stream->buffers[i].length && stream->buffers[i].start != MAP_FAILED)
398 munmap(stream->buffers[i].start, stream->buffers[i].length);
402 free(stream->buffers);
403 stream->buffers =
nullptr;
404 stream->nBuffers = 0;
412static size_t cam_v4l_stream_alloc_buffers(
CamV4lStream* stream)
414 struct v4l2_requestbuffers rbuffer = WINPR_C_ARRAY_INIT;
416 rbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
417 rbuffer.memory = V4L2_MEMORY_MMAP;
418 rbuffer.count = CAM_V4L2_BUFFERS_COUNT;
420 if (ioctl(stream->fd, VIDIOC_REQBUFS, &rbuffer) < 0 || rbuffer.count == 0)
422 char buffer[64] = WINPR_C_ARRAY_INIT;
423 WLog_ERR(TAG,
"Failure in VIDIOC_REQBUFS, errno %s [%d], count %u",
424 winpr_strerror(errno, buffer,
sizeof(buffer)), errno, rbuffer.count);
428 stream->nBuffers = rbuffer.count;
432 if (!stream->buffers)
434 WLog_ERR(TAG,
"Failure in calloc");
438 for (
unsigned int i = 0; i < rbuffer.count; i++)
440 struct v4l2_buffer vbuffer = WINPR_C_ARRAY_INIT;
441 vbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
442 vbuffer.memory = V4L2_MEMORY_MMAP;
445 if (ioctl(stream->fd, VIDIOC_QUERYBUF, &vbuffer) < 0)
447 char buffer[64] = WINPR_C_ARRAY_INIT;
448 WLog_ERR(TAG,
"Failure in VIDIOC_QUERYBUF, errno %s [%d]",
449 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
450 cam_v4l_stream_free_buffers(stream);
454 stream->buffers[i].start = mmap(
nullptr, vbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
455 stream->fd, vbuffer.m.offset);
457 if (MAP_FAILED == stream->buffers[i].start)
459 char buffer[64] = WINPR_C_ARRAY_INIT;
460 WLog_ERR(TAG,
"Failure in mmap, errno %s [%d]",
461 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
462 cam_v4l_stream_free_buffers(stream);
466 stream->buffers[i].length = vbuffer.length;
468 WLog_DBG(TAG,
"Buffer %u mapped, size: %u", i, vbuffer.length);
470 if (ioctl(stream->fd, VIDIOC_QBUF, &vbuffer) < 0)
472 char buffer[64] = WINPR_C_ARRAY_INIT;
473 WLog_ERR(TAG,
"Failure in VIDIOC_QBUF, errno %s [%d]",
474 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
475 cam_v4l_stream_free_buffers(stream);
480 return stream->buffers[0].length;
488static DWORD WINAPI cam_v4l_stream_capture_thread(LPVOID param)
491 WINPR_ASSERT(stream);
494 BOOL releaseDevice = FALSE;
496 BOOL streaming = TRUE;
500 struct pollfd pfd = WINPR_C_ARRAY_INIT;
505 retVal = poll(&pfd, 1, CAM_V4L2_CAPTURE_THREAD_SLEEP_MS);
514 char buffer[64] = WINPR_C_ARRAY_INIT;
515 WLog_DBG(TAG,
"Failure in poll, errno %s [%d]",
516 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
517 Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS);
520 else if (!(pfd.revents & POLLIN))
522 WLog_DBG(TAG,
"poll reported non-read event %d", pfd.revents);
523 Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS);
527 EnterCriticalSection(&stream->lock);
528 streaming = stream->streaming;
531 struct v4l2_buffer buf = WINPR_C_ARRAY_INIT;
532 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
533 buf.memory = V4L2_MEMORY_MMAP;
536 while (streaming && ioctl(fd, VIDIOC_DQBUF, &buf) != -1)
539 stream->sampleCallback(stream->dev, stream->streamIndex,
540 stream->buffers[buf.index].start, buf.bytesused);
541 if (error == ECAM_SAMPLE_CAPTURE_DRAINED)
543 streaming = stream->streaming = FALSE;
544 releaseDevice = TRUE;
547 else if (error != CHANNEL_RC_OK)
549 WLog_ERR(TAG,
"Failure in sampleCallback: %" PRIu32, error);
553 if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
555 char buffer[64] = WINPR_C_ARRAY_INIT;
556 WLog_ERR(TAG,
"Failure in VIDIOC_QBUF, errno %s [%d]",
557 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
561 LeaveCriticalSection(&stream->lock);
567 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
568 if (ioctl(stream->fd, VIDIOC_STREAMOFF, &type) < 0)
570 char buffer[64] = WINPR_C_ARRAY_INIT;
571 WLog_ERR(TAG,
"Failure in VIDIOC_STREAMOFF, errno %s [%d]",
572 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
575 cam_v4l_stream_free_buffers(stream);
576 cam_v4l_stream_close_device(stream);
579 return CHANNEL_RC_OK;
584 if (stream->fd != -1)
596WINPR_ATTR_MALLOC(cam_v4l_stream_free, 1)
597CamV4lStream* cam_v4l_stream_create(const
char* deviceId,
size_t streamIndex)
603 WLog_ERR(TAG,
"Failure in calloc");
606 stream->streamIndex = streamIndex;
608 stream->h264UnitId = get_uvc_h624_unit_id(deviceId);
610 if (!InitializeCriticalSectionEx(&stream->lock, 0, 0))
612 WLog_ERR(TAG,
"Failure in calloc");
628 return CAM_ERROR_CODE_None;
630 EnterCriticalSection(&stream->lock);
631 stream->streaming = FALSE;
632 LeaveCriticalSection(&stream->lock);
634 if (stream->captureThread)
636 (void)WaitForSingleObject(stream->captureThread, INFINITE);
637 (void)CloseHandle(stream->captureThread);
638 stream->captureThread =
nullptr;
641 EnterCriticalSection(&stream->lock);
644 if (stream->fd != -1)
646 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
647 if (ioctl(stream->fd, VIDIOC_STREAMOFF, &type) < 0)
649 char buffer[64] = WINPR_C_ARRAY_INIT;
650 WLog_ERR(TAG,
"Failure in VIDIOC_STREAMOFF, errno %s [%d]",
651 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
655 cam_v4l_stream_free_buffers(stream);
656 cam_v4l_stream_close_device(stream);
658 LeaveCriticalSection(&stream->lock);
660 return CAM_ERROR_CODE_None;
663static CAM_ERROR_CODE cam_v4l_stream_start(ICamHal* ihal,
CameraDevice* dev,
size_t streamIndex,
665 ICamHalSampleCapturedCallback callback)
667 CamV4lHal* hal = (CamV4lHal*)ihal;
674 WLog_ERR(TAG,
"Unable to find stream, device %s, streamIndex %" PRIuz, dev->deviceId,
676 return CAM_ERROR_CODE_UnexpectedError;
679 EnterCriticalSection(&stream->lock);
680 const BOOL streaming = stream->streaming;
681 LeaveCriticalSection(&stream->lock);
685 WLog_ERR(TAG,
"Streaming already in progress, device %s, streamIndex %" PRIuz,
686 dev->deviceId, streamIndex);
687 return CAM_ERROR_CODE_UnexpectedError;
690 if (stream->captureThread)
692 (void)WaitForSingleObject(stream->captureThread, INFINITE);
693 (void)CloseHandle(stream->captureThread);
694 stream->captureThread =
nullptr;
698 stream->sampleCallback = callback;
700 if ((stream->fd = cam_v4l_open_device(dev->deviceId, O_RDWR | O_NONBLOCK)) == -1)
702 WLog_ERR(TAG,
"Unable to open device %s", dev->deviceId);
703 return CAM_ERROR_CODE_UnexpectedError;
706 struct v4l2_format video_fmt = WINPR_C_ARRAY_INIT;
707 video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
709 const UINT32 pixelFormat = ecamToV4L2PixFormat(mediaType->Format);
710 video_fmt.fmt.pix.width = mediaType->Width;
711 video_fmt.fmt.pix.height = mediaType->Height;
713 if (pixelFormat == 0)
715 cam_v4l_stream_close_device(stream);
716 return CAM_ERROR_CODE_InvalidMediaType;
719 video_fmt.fmt.pix.pixelformat = pixelFormat;
722 if (ioctl(stream->fd, VIDIOC_S_FMT, &video_fmt) < 0)
724 char buffer[64] = WINPR_C_ARRAY_INIT;
725 WLog_ERR(TAG,
"Failure in VIDIOC_S_FMT, errno %s [%d]",
726 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
727 cam_v4l_stream_close_device(stream);
728 return CAM_ERROR_CODE_InvalidMediaType;
732 struct v4l2_streamparm sp1 = WINPR_C_ARRAY_INIT;
733 struct v4l2_streamparm sp2 = WINPR_C_ARRAY_INIT;
734 sp1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
735 if (ioctl(stream->fd, VIDIOC_G_PARM, &sp1) < 0 ||
736 !(sp1.parm.capture.capability & V4L2_CAP_TIMEPERFRAME))
738 WLog_INFO(TAG,
"Driver doesn't support setting framerate");
742 sp2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
745 sp2.parm.capture.timeperframe.numerator = mediaType->FrameRateDenominator;
746 sp2.parm.capture.timeperframe.denominator = mediaType->FrameRateNumerator;
748 if (ioctl(stream->fd, VIDIOC_S_PARM, &sp2) < 0)
750 char buffer[64] = WINPR_C_ARRAY_INIT;
751 WLog_INFO(TAG,
"Failed to set the framerate, errno %s [%d]",
752 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
756 size_t maxSample = cam_v4l_stream_alloc_buffers(stream);
759 WLog_ERR(TAG,
"Failure to allocate video buffers");
760 cam_v4l_stream_close_device(stream);
761 return CAM_ERROR_CODE_OutOfMemory;
764 EnterCriticalSection(&stream->lock);
765 stream->streaming = TRUE;
766 LeaveCriticalSection(&stream->lock);
769 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
770 if (ioctl(stream->fd, VIDIOC_STREAMON, &type) < 0)
772 char buffer[64] = WINPR_C_ARRAY_INIT;
773 WLog_ERR(TAG,
"Failure in VIDIOC_STREAMON, errno %s [%d]",
774 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
775 cam_v4l_stream_stop(stream);
776 return CAM_ERROR_CODE_UnexpectedError;
779 stream->captureThread =
780 CreateThread(
nullptr, 0, cam_v4l_stream_capture_thread, stream, 0,
nullptr);
781 if (!stream->captureThread)
783 WLog_ERR(TAG,
"CreateThread failure");
784 cam_v4l_stream_stop(stream);
785 return CAM_ERROR_CODE_OutOfMemory;
788 char fourccstr[16] = WINPR_C_ARRAY_INIT;
789 cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr));
791 WLog_INFO(TAG,
"Camera format: %s, width: %u, height: %u, fps: %u/%u", fourccstr,
792 mediaType->Width, mediaType->Height, mediaType->FrameRateNumerator,
793 mediaType->FrameRateDenominator);
795 return CAM_ERROR_CODE_None;
803static CAM_ERROR_CODE cam_v4l_stream_stop_by_device_id(ICamHal* ihal,
const char* deviceId,
804 WINPR_ATTR_UNUSED
size_t streamIndex)
806 CamV4lHal* hal = (CamV4lHal*)ihal;
811 return CAM_ERROR_CODE_NotInitialized;
813 return cam_v4l_stream_stop(stream);
822void cam_v4l_stream_free(
void* obj)
828 cam_v4l_stream_stop(stream);
830 DeleteCriticalSection(&stream->lock);
839static CAM_ERROR_CODE cam_v4l_free(ICamHal* ihal)
841 CamV4lHal* hal = (CamV4lHal*)ihal;
844 return CAM_ERROR_CODE_NotInitialized;
846 HashTable_Free(hal->streams);
850 return CAM_ERROR_CODE_None;
858FREERDP_ENTRY_POINT(UINT VCAPITYPE v4l_freerdp_rdpecam_client_subsystem_entry(
861 UINT ret = ERROR_INTERNAL_ERROR;
862 WINPR_ASSERT(pEntryPoints);
864 CamV4lHal* hal = (CamV4lHal*)calloc(1,
sizeof(CamV4lHal));
867 return CHANNEL_RC_NO_MEMORY;
869 hal->iHal.Enumerate = cam_v4l_enumerate;
870 hal->iHal.RequestDrivenCapture = TRUE;
871 hal->iHal.GetMediaTypeDescriptions = cam_v4l_get_media_type_descriptions;
872 hal->iHal.Activate = cam_v4l_activate;
873 hal->iHal.Deactivate = cam_v4l_deactivate;
874 hal->iHal.StartStream = cam_v4l_stream_start;
875 hal->iHal.StopStream = cam_v4l_stream_stop_by_device_id;
876 hal->iHal.Free = cam_v4l_free;
878 hal->streams = HashTable_New(FALSE);
882 if (!HashTable_SetupForStringData(hal->streams, FALSE))
885 wObject* obj = HashTable_ValueObject(hal->streams);
889 ret = pEntryPoints->pRegisterCameraHal(pEntryPoints->plugin, &hal->iHal);
890 if (ret != CHANNEL_RC_OK)
892 WLog_ERR(TAG,
"RegisterCameraHal failed with error %" PRIu32
"", ret);
899 cam_v4l_free(&hal->iHal);
This struct contains function pointer to initialize/free objects.
OBJECT_FREE_FN fnObjectFree