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,
int streamIndex);
49static void cam_v4l_stream_free(
void* obj);
50static void cam_v4l_stream_close_device(
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 %d", ecamFormat);
105static BOOL cam_v4l_format_supported(
int fd, UINT32 format)
107 struct v4l2_fmtdesc fmtdesc = { 0 };
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] = { 0 };
127 struct v4l2_capability cap = { 0 };
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))
163static INT16 cam_v4l_get_media_type_descriptions(ICamHal* ihal,
const char* deviceId,
166 size_t nSupportedFormats,
170 CamV4lHal* hal = (CamV4lHal*)ihal;
171 size_t maxMediaTypes = *nMediaTypes;
173 BOOL formatFound = FALSE;
179 stream = cam_v4l_stream_create(deviceId, streamIndex);
181 return CAM_ERROR_CODE_OutOfMemory;
183 if (!HashTable_Insert(hal->streams, deviceId, stream))
185 cam_v4l_stream_free(stream);
186 return CAM_ERROR_CODE_UnexpectedError;
190 int fd = cam_v4l_open_device(deviceId, O_RDONLY);
193 WLog_ERR(TAG,
"Unable to open device %s", deviceId);
197 size_t formatIndex = 0;
198 for (; formatIndex < nSupportedFormats; formatIndex++)
200 UINT32 pixelFormat = 0;
201 if (supportedFormats[formatIndex].inputFormat == CAM_MEDIA_FORMAT_MJPG_H264)
203 if (stream->h264UnitId > 0)
204 pixelFormat = V4L2_PIX_FMT_MJPEG;
210 pixelFormat = ecamToV4L2PixFormat(supportedFormats[formatIndex].inputFormat);
213 WINPR_ASSERT(pixelFormat != 0);
214 struct v4l2_frmsizeenum frmsize = { 0 };
216 if (!cam_v4l_format_supported(fd, pixelFormat))
219 frmsize.pixel_format = pixelFormat;
220 for (frmsize.index = 0; ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0; frmsize.index++)
222 struct v4l2_frmivalenum frmival = { 0 };
224 if (frmsize.type != V4L2_FRMSIZE_TYPE_DISCRETE)
228 mediaTypes->Width = frmsize.discrete.width;
229 mediaTypes->Height = frmsize.discrete.height;
230 mediaTypes->Format = supportedFormats[formatIndex].inputFormat;
234 frmival.pixel_format = pixelFormat;
235 frmival.width = frmsize.discrete.width;
236 frmival.height = frmsize.discrete.height;
237 if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0 &&
238 frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE)
241 mediaTypes->FrameRateNumerator = frmival.discrete.denominator;
242 mediaTypes->FrameRateDenominator = frmival.discrete.numerator;
246 WLog_DBG(TAG,
"VIDIOC_ENUM_FRAMEINTERVALS failed, using default framerate");
247 mediaTypes->FrameRateNumerator = CAM_V4L2_FRAMERATE_NUMERATOR_DEFAULT;
248 mediaTypes->FrameRateDenominator = CAM_V4L2_FRAMERATE_DENOMINATOR_DEFAULT;
251 mediaTypes->PixelAspectRatioNumerator = mediaTypes->PixelAspectRatioDenominator = 1;
253 char fourccstr[5] = { 0 };
254 WLog_DBG(TAG,
"Camera format: %s, width: %u, height: %u, fps: %u/%u",
255 cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr)),
256 mediaTypes->Width, mediaTypes->Height, mediaTypes->FrameRateNumerator,
257 mediaTypes->FrameRateDenominator);
262 if (nTypes == maxMediaTypes)
264 WLog_ERR(TAG,
"Media types reached buffer maximum %" PRIu32
"", maxMediaTypes);
278 *nMediaTypes = nTypes;
280 if (formatIndex > INT16_MAX)
282 return (INT16)formatIndex;
290static UINT cam_v4l_enumerate(WINPR_ATTR_UNUSED ICamHal* ihal, ICamHalEnumCallback callback,
295 for (UINT n = 0; n < 64; n++)
297 char device[20] = { 0 };
298 struct v4l2_capability cap = { 0 };
299 (void)_snprintf(device,
sizeof(device),
"/dev/video%" PRIu32, n);
300 int fd = open(device, O_RDONLY);
305 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE))
312 const char* deviceName = (
char*)cap.card;
313 const char* deviceId = device;
314 if (cap.bus_info[0] != 0)
315 deviceId = (
char*)cap.bus_info;
317 IFCALL(callback, ecam, hchannel, deviceId, deviceName);
325static void cam_v4l_stream_free_buffers(
CamV4lStream* stream)
327 if (!stream || !stream->buffers)
331 for (
size_t i = 0; i < stream->nBuffers; i++)
333 if (stream->buffers[i].length && stream->buffers[i].start != MAP_FAILED)
335 munmap(stream->buffers[i].start, stream->buffers[i].length);
339 free(stream->buffers);
340 stream->buffers = NULL;
341 stream->nBuffers = 0;
349static size_t cam_v4l_stream_alloc_buffers(
CamV4lStream* stream)
351 struct v4l2_requestbuffers rbuffer = { 0 };
353 rbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
354 rbuffer.memory = V4L2_MEMORY_MMAP;
355 rbuffer.count = CAM_V4L2_BUFFERS_COUNT;
357 if (ioctl(stream->fd, VIDIOC_REQBUFS, &rbuffer) < 0 || rbuffer.count == 0)
359 char buffer[64] = { 0 };
360 WLog_ERR(TAG,
"Failure in VIDIOC_REQBUFS, errno %s [%d], count %d",
361 winpr_strerror(errno, buffer,
sizeof(buffer)), errno, rbuffer.count);
365 stream->nBuffers = rbuffer.count;
369 if (!stream->buffers)
371 WLog_ERR(TAG,
"Failure in calloc");
375 for (
unsigned int i = 0; i < rbuffer.count; i++)
377 struct v4l2_buffer vbuffer = { 0 };
378 vbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
379 vbuffer.memory = V4L2_MEMORY_MMAP;
382 if (ioctl(stream->fd, VIDIOC_QUERYBUF, &vbuffer) < 0)
384 char buffer[64] = { 0 };
385 WLog_ERR(TAG,
"Failure in VIDIOC_QUERYBUF, errno %s [%d]",
386 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
387 cam_v4l_stream_free_buffers(stream);
391 stream->buffers[i].start = mmap(NULL, vbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
392 stream->fd, vbuffer.m.offset);
394 if (MAP_FAILED == stream->buffers[i].start)
396 char buffer[64] = { 0 };
397 WLog_ERR(TAG,
"Failure in mmap, errno %s [%d]",
398 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
399 cam_v4l_stream_free_buffers(stream);
403 stream->buffers[i].length = vbuffer.length;
405 WLog_DBG(TAG,
"Buffer %d mapped, size: %d", i, vbuffer.length);
407 if (ioctl(stream->fd, VIDIOC_QBUF, &vbuffer) < 0)
409 char buffer[64] = { 0 };
410 WLog_ERR(TAG,
"Failure in VIDIOC_QBUF, errno %s [%d]",
411 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
412 cam_v4l_stream_free_buffers(stream);
417 return stream->buffers[0].length;
425static UINT cam_v4l_stream_capture_thread(
void* param)
434 struct pollfd pfd = { 0 };
439 retVal = poll(&pfd, 1, CAM_V4L2_CAPTURE_THREAD_SLEEP_MS);
448 char buffer[64] = { 0 };
449 WLog_DBG(TAG,
"Failure in poll, errno %s [%d]",
450 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
451 Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS);
454 else if (!(pfd.revents & POLLIN))
456 WLog_DBG(TAG,
"poll reported non-read event %d", pfd.revents);
457 Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS);
461 EnterCriticalSection(&stream->lock);
462 if (stream->streaming)
464 struct v4l2_buffer buf = { 0 };
465 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
466 buf.memory = V4L2_MEMORY_MMAP;
469 while (ioctl(fd, VIDIOC_DQBUF, &buf) != -1)
471 stream->sampleCallback(stream->dev, stream->streamIndex,
472 stream->buffers[buf.index].start, buf.bytesused);
475 if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
477 char buffer[64] = { 0 };
478 WLog_ERR(TAG,
"Failure in VIDIOC_QBUF, errno %s [%d]",
479 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
483 LeaveCriticalSection(&stream->lock);
485 }
while (stream->streaming);
487 return CHANNEL_RC_OK;
492 if (stream->fd != -1)
504static CamV4lStream* cam_v4l_stream_create(
const char* deviceId,
int streamIndex)
510 WLog_ERR(TAG,
"Failure in calloc");
513 stream->streamIndex = streamIndex;
515 stream->h264UnitId = get_uvc_h624_unit_id(deviceId);
517 if (!InitializeCriticalSectionEx(&stream->lock, 0, 0))
519 WLog_ERR(TAG,
"Failure in calloc");
534 if (!stream || !stream->streaming)
535 return CHANNEL_RC_OK;
537 stream->streaming = FALSE;
539 if (stream->captureThread)
541 (void)WaitForSingleObject(stream->captureThread, INFINITE);
542 (void)CloseHandle(stream->captureThread);
543 stream->captureThread = NULL;
546 EnterCriticalSection(&stream->lock);
549 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
550 if (ioctl(stream->fd, VIDIOC_STREAMOFF, &type) < 0)
552 char buffer[64] = { 0 };
553 WLog_ERR(TAG,
"Failure in VIDIOC_STREAMOFF, errno %s [%d]",
554 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
557 cam_v4l_stream_free_buffers(stream);
558 cam_v4l_stream_close_device(stream);
560 LeaveCriticalSection(&stream->lock);
562 return CHANNEL_RC_OK;
570static UINT cam_v4l_stream_start(ICamHal* ihal,
CameraDevice* dev,
int streamIndex,
572 ICamHalSampleCapturedCallback callback)
574 CamV4lHal* hal = (CamV4lHal*)ihal;
580 WLog_ERR(TAG,
"Unable to find stream, device %s, streamIndex %d", dev->deviceId,
582 return CAM_ERROR_CODE_UnexpectedError;
585 if (stream->streaming)
587 WLog_ERR(TAG,
"Streaming already in progress, device %s, streamIndex %d", dev->deviceId,
589 return CAM_ERROR_CODE_UnexpectedError;
593 stream->sampleCallback = callback;
595 if ((stream->fd = cam_v4l_open_device(dev->deviceId, O_RDWR | O_NONBLOCK)) == -1)
597 WLog_ERR(TAG,
"Unable to open device %s", dev->deviceId);
598 return CAM_ERROR_CODE_UnexpectedError;
601 struct v4l2_format video_fmt = { 0 };
602 video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
604 UINT32 pixelFormat = 0;
605 if (mediaType->Format == CAM_MEDIA_FORMAT_MJPG_H264)
607 if (!set_h264_muxed_format(stream, mediaType))
609 WLog_ERR(TAG,
"Failure to set H264 muxed format");
610 cam_v4l_stream_close_device(stream);
611 return CAM_ERROR_CODE_UnexpectedError;
614 pixelFormat = V4L2_PIX_FMT_MJPEG;
616 video_fmt.fmt.pix.width = 640;
617 video_fmt.fmt.pix.height = 480;
621 pixelFormat = ecamToV4L2PixFormat(mediaType->Format);
622 video_fmt.fmt.pix.width = mediaType->Width;
623 video_fmt.fmt.pix.height = mediaType->Height;
626 if (pixelFormat == 0)
628 cam_v4l_stream_close_device(stream);
629 return CAM_ERROR_CODE_InvalidMediaType;
632 video_fmt.fmt.pix.pixelformat = pixelFormat;
635 if (ioctl(stream->fd, VIDIOC_S_FMT, &video_fmt) < 0)
637 char buffer[64] = { 0 };
638 WLog_ERR(TAG,
"Failure in VIDIOC_S_FMT, errno %s [%d]",
639 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
640 cam_v4l_stream_close_device(stream);
641 return CAM_ERROR_CODE_InvalidMediaType;
645 struct v4l2_streamparm sp1 = { 0 };
646 struct v4l2_streamparm sp2 = { 0 };
647 sp1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
648 if (ioctl(stream->fd, VIDIOC_G_PARM, &sp1) < 0 ||
649 !(sp1.parm.capture.capability & V4L2_CAP_TIMEPERFRAME))
651 WLog_INFO(TAG,
"Driver doesn't support setting framerate");
655 sp2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
658 sp2.parm.capture.timeperframe.numerator = mediaType->FrameRateDenominator;
659 sp2.parm.capture.timeperframe.denominator = mediaType->FrameRateNumerator;
661 if (ioctl(stream->fd, VIDIOC_S_PARM, &sp2) < 0)
663 char buffer[64] = { 0 };
664 WLog_INFO(TAG,
"Failed to set the framerate, errno %s [%d]",
665 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
669 size_t maxSample = cam_v4l_stream_alloc_buffers(stream);
672 WLog_ERR(TAG,
"Failure to allocate video buffers");
673 cam_v4l_stream_close_device(stream);
674 return CAM_ERROR_CODE_OutOfMemory;
677 stream->streaming = TRUE;
680 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
681 if (ioctl(stream->fd, VIDIOC_STREAMON, &type) < 0)
683 char buffer[64] = { 0 };
684 WLog_ERR(TAG,
"Failure in VIDIOC_STREAMON, errno %s [%d]",
685 winpr_strerror(errno, buffer,
sizeof(buffer)), errno);
686 cam_v4l_stream_stop(stream);
687 return CAM_ERROR_CODE_UnexpectedError;
690 stream->captureThread = CreateThread(NULL, 0, cam_v4l_stream_capture_thread, stream, 0, NULL);
691 if (!stream->captureThread)
693 WLog_ERR(TAG,
"CreateThread failure");
694 cam_v4l_stream_stop(stream);
695 return CAM_ERROR_CODE_OutOfMemory;
698 char fourccstr[16] = { 0 };
699 if (mediaType->Format == CAM_MEDIA_FORMAT_MJPG_H264)
700 strncpy(fourccstr,
"H264 muxed", ARRAYSIZE(fourccstr) - 1);
702 cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr));
704 WLog_INFO(TAG,
"Camera format: %s, width: %u, height: %u, fps: %u/%u", fourccstr,
705 mediaType->Width, mediaType->Height, mediaType->FrameRateNumerator,
706 mediaType->FrameRateDenominator);
708 return CHANNEL_RC_OK;
716static UINT cam_v4l_stream_stop_by_device_id(ICamHal* ihal,
const char* deviceId,
717 WINPR_ATTR_UNUSED
int streamIndex)
719 CamV4lHal* hal = (CamV4lHal*)ihal;
724 return CHANNEL_RC_OK;
726 return cam_v4l_stream_stop(stream);
735void cam_v4l_stream_free(
void* obj)
741 cam_v4l_stream_stop(stream);
743 DeleteCriticalSection(&stream->lock);
752static UINT cam_v4l_free(ICamHal* ihal)
754 CamV4lHal* hal = (CamV4lHal*)ihal;
757 return ERROR_INVALID_PARAMETER;
759 HashTable_Free(hal->streams);
763 return CHANNEL_RC_OK;
771FREERDP_ENTRY_POINT(UINT VCAPITYPE v4l_freerdp_rdpecam_client_subsystem_entry(
774 UINT ret = CHANNEL_RC_OK;
775 WINPR_ASSERT(pEntryPoints);
777 CamV4lHal* hal = (CamV4lHal*)calloc(1,
sizeof(CamV4lHal));
780 return CHANNEL_RC_NO_MEMORY;
782 hal->iHal.Enumerate = cam_v4l_enumerate;
783 hal->iHal.GetMediaTypeDescriptions = cam_v4l_get_media_type_descriptions;
784 hal->iHal.StartStream = cam_v4l_stream_start;
785 hal->iHal.StopStream = cam_v4l_stream_stop_by_device_id;
786 hal->iHal.Free = cam_v4l_free;
788 hal->streams = HashTable_New(FALSE);
791 ret = CHANNEL_RC_NO_MEMORY;
795 HashTable_SetupForStringData(hal->streams, FALSE);
797 wObject* obj = HashTable_ValueObject(hal->streams);
799 obj->fnObjectFree = cam_v4l_stream_free;
801 if ((ret = pEntryPoints->pRegisterCameraHal(pEntryPoints->plugin, &hal->iHal)))
803 WLog_ERR(TAG,
"RegisterCameraHal failed with error %" PRIu32
"", ret);
810 cam_v4l_free(&hal->iHal);
This struct contains function pointer to initialize/free objects.