Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,34 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2024 Oleg Turovski <oleg2104@hotmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
if(WITH_V4L)
define_channel_client_subsystem("rdpecam" "v4l" "")
find_package(libusb-1.0 REQUIRED)
freerdp_client_pc_add_requires_private("libusb-1.0")
include_directories(SYSTEM ${LIBUSB_1_INCLUDE_DIRS})
set(${MODULE_PREFIX}_SRCS camera_v4l.c uvc_h264.c)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${LIBUSB_1_LIBRARIES})
include_directories(..)
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
endif()

View File

@@ -0,0 +1,834 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* MS-RDPECAM Implementation, V4L Interface
*
* Copyright 2024 Oleg Turovski <oleg2104@hotmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
/* v4l includes */
#include <linux/videodev2.h>
#include "camera_v4l.h"
#include "uvc_h264.h"
#define TAG CHANNELS_TAG("rdpecam-v4l.client")
#define CAM_V4L2_BUFFERS_COUNT 4
#define CAM_V4L2_CAPTURE_THREAD_SLEEP_MS 1000
#define CAM_V4L2_FRAMERATE_NUMERATOR_DEFAULT 30
#define CAM_V4L2_FRAMERATE_DENOMINATOR_DEFAULT 1
typedef struct
{
ICamHal iHal;
wHashTable* streams; /* Index: deviceId, Value: CamV4lStream */
} CamV4lHal;
static CamV4lStream* cam_v4l_stream_create(const char* deviceId, size_t streamIndex);
static void cam_v4l_stream_free(void* obj);
static void cam_v4l_stream_close_device(CamV4lStream* stream);
static CAM_ERROR_CODE cam_v4l_stream_stop(CamV4lStream* stream);
/**
* Function description
*
* @return \0-terminated fourcc string
*/
static const char* cam_v4l_get_fourcc_str(unsigned int fourcc, char* buffer, size_t size)
{
if (size < 5)
return nullptr;
buffer[0] = (char)(fourcc & 0xFF);
buffer[1] = (char)((fourcc >> 8) & 0xFF);
buffer[2] = (char)((fourcc >> 16) & 0xFF);
buffer[3] = (char)((fourcc >> 24) & 0xFF);
buffer[4] = '\0';
return buffer;
}
/**
* Function description
*
* @return one of V4L2_PIX_FMT
*/
static UINT32 ecamToV4L2PixFormat(CAM_MEDIA_FORMAT ecamFormat)
{
switch (ecamFormat)
{
case CAM_MEDIA_FORMAT_H264:
return V4L2_PIX_FMT_H264;
case CAM_MEDIA_FORMAT_MJPG:
return V4L2_PIX_FMT_MJPEG;
case CAM_MEDIA_FORMAT_YUY2:
return V4L2_PIX_FMT_YUYV;
case CAM_MEDIA_FORMAT_NV12:
return V4L2_PIX_FMT_NV12;
case CAM_MEDIA_FORMAT_I420:
return V4L2_PIX_FMT_YUV420;
case CAM_MEDIA_FORMAT_RGB24:
return V4L2_PIX_FMT_RGB24;
case CAM_MEDIA_FORMAT_RGB32:
return V4L2_PIX_FMT_RGB32;
default:
WLog_ERR(TAG, "Unsupported CAM_MEDIA_FORMAT %u", ecamFormat);
return 0;
}
}
/**
* Function description
*
* @return TRUE or FALSE
*/
static BOOL cam_v4l_format_supported(int fd, UINT32 format)
{
struct v4l2_fmtdesc fmtdesc = WINPR_C_ARRAY_INIT;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for (fmtdesc.index = 0; ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0; fmtdesc.index++)
{
if (fmtdesc.pixelformat == format)
return TRUE;
}
return FALSE;
}
/**
* Function description
*
* @return file descriptor
*/
static int cam_v4l_open_device(const char* deviceId, int flags)
{
char device[20] = WINPR_C_ARRAY_INIT;
int fd = -1;
struct v4l2_capability cap = WINPR_C_ARRAY_INIT;
if (!deviceId)
return -1;
if (0 == strncmp(deviceId, "/dev/video", 10))
return open(deviceId, flags);
for (UINT n = 0; n < 64; n++)
{
(void)_snprintf(device, sizeof(device), "/dev/video%" PRIu32, n);
if ((fd = open(device, flags)) == -1)
continue;
/* query device capabilities and make sure this is a video capture device */
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE))
{
close(fd);
continue;
}
if (cap.bus_info[0] != 0 && 0 == strcmp((const char*)cap.bus_info, deviceId))
return fd;
close(fd);
}
return fd;
}
static BOOL cam_v4l_activate(ICamHal* ihal, const char* deviceId, CAM_ERROR_CODE* errorCode)
{
WINPR_UNUSED(ihal);
WINPR_UNUSED(deviceId);
*errorCode = CAM_ERROR_CODE_None;
return TRUE;
}
static BOOL cam_v4l_deactivate(ICamHal* ihal, const char* deviceId, CAM_ERROR_CODE* errorCode)
{
WINPR_UNUSED(ihal);
WINPR_UNUSED(deviceId);
*errorCode = CAM_ERROR_CODE_None;
return TRUE;
}
/**
* Function description
*
* @return -1 if error, otherwise index of supportedFormats array and mediaTypes/nMediaTypes filled
* in
*/
static INT16 cam_v4l_get_media_type_descriptions(ICamHal* ihal, const char* deviceId,
size_t streamIndex,
const CAM_MEDIA_FORMAT_INFO* supportedFormats,
size_t nSupportedFormats,
CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes,
size_t* nMediaTypes)
{
CamV4lHal* hal = (CamV4lHal*)ihal;
size_t maxMediaTypes = *nMediaTypes;
size_t nTypes = 0;
BOOL formatFound = FALSE;
CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, deviceId);
if (!stream)
{
stream = cam_v4l_stream_create(deviceId, streamIndex);
if (!stream)
return CAM_ERROR_CODE_OutOfMemory;
if (!HashTable_Insert(hal->streams, deviceId, stream))
{
cam_v4l_stream_free(stream);
return CAM_ERROR_CODE_UnexpectedError;
}
}
int fd = cam_v4l_open_device(deviceId, O_RDONLY);
if (fd == -1)
{
WLog_ERR(TAG, "Unable to open device %s", deviceId);
return -1;
}
size_t formatIndex = 0;
for (; formatIndex < nSupportedFormats; formatIndex++)
{
UINT32 pixelFormat = 0;
if (supportedFormats[formatIndex].inputFormat == CAM_MEDIA_FORMAT_MJPG_H264)
{
if (stream->h264UnitId > 0)
pixelFormat = V4L2_PIX_FMT_MJPEG;
else
continue; /* not supported */
}
else
{
pixelFormat = ecamToV4L2PixFormat(supportedFormats[formatIndex].inputFormat);
}
WINPR_ASSERT(pixelFormat != 0);
struct v4l2_frmsizeenum frmsize = WINPR_C_ARRAY_INIT;
if (!cam_v4l_format_supported(fd, pixelFormat))
continue;
frmsize.pixel_format = pixelFormat;
for (frmsize.index = 0; ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0; frmsize.index++)
{
struct v4l2_frmivalenum frmival = WINPR_C_ARRAY_INIT;
if (frmsize.type != V4L2_FRMSIZE_TYPE_DISCRETE)
break; /* don't support size types other than discrete */
formatFound = TRUE;
mediaTypes->Width = frmsize.discrete.width;
mediaTypes->Height = frmsize.discrete.height;
mediaTypes->Format = supportedFormats[formatIndex].inputFormat;
/* query frame rate (1st is highest fps supported) */
frmival.index = 0;
frmival.pixel_format = pixelFormat;
frmival.width = frmsize.discrete.width;
frmival.height = frmsize.discrete.height;
if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0 &&
frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE)
{
/* inverse of a fraction */
mediaTypes->FrameRateNumerator = frmival.discrete.denominator;
mediaTypes->FrameRateDenominator = frmival.discrete.numerator;
}
else
{
WLog_DBG(TAG, "VIDIOC_ENUM_FRAMEINTERVALS failed, using default framerate");
mediaTypes->FrameRateNumerator = CAM_V4L2_FRAMERATE_NUMERATOR_DEFAULT;
mediaTypes->FrameRateDenominator = CAM_V4L2_FRAMERATE_DENOMINATOR_DEFAULT;
}
mediaTypes->PixelAspectRatioNumerator = mediaTypes->PixelAspectRatioDenominator = 1;
char fourccstr[5] = WINPR_C_ARRAY_INIT;
WLog_DBG(TAG, "Camera format: %s, width: %u, height: %u, fps: %u/%u",
cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr)),
mediaTypes->Width, mediaTypes->Height, mediaTypes->FrameRateNumerator,
mediaTypes->FrameRateDenominator);
mediaTypes++;
nTypes++;
if (nTypes == maxMediaTypes)
{
WLog_ERR(TAG, "Media types reached buffer maximum %" PRIuz "", maxMediaTypes);
goto error;
}
}
if (formatFound)
{
/* we are interested in 1st supported format only, with all supported sizes */
break;
}
}
error:
*nMediaTypes = nTypes;
close(fd);
if (formatIndex > INT16_MAX)
return -1;
return (INT16)formatIndex;
}
/**
* Function description
*
* @return number of video capture devices
*/
static UINT cam_v4l_enumerate(WINPR_ATTR_UNUSED ICamHal* ihal, ICamHalEnumCallback callback,
CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel)
{
UINT count = 0;
for (UINT n = 0; n < 64; n++)
{
char device[20] = WINPR_C_ARRAY_INIT;
struct v4l2_capability cap = WINPR_C_ARRAY_INIT;
(void)_snprintf(device, sizeof(device), "/dev/video%" PRIu32, n);
int fd = open(device, O_RDONLY);
if (fd == -1)
continue;
/* query device capabilities and make sure this is a video capture device */
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE))
{
close(fd);
continue;
}
count++;
const char* deviceName = (char*)cap.card;
const char* deviceId = device;
if (cap.bus_info[0] != 0) /* may not be available in all drivers */
deviceId = (char*)cap.bus_info;
IFCALL(callback, ecam, hchannel, deviceId, deviceName);
close(fd);
}
return count;
}
static void cam_v4l_stream_free_buffers(CamV4lStream* stream)
{
if (!stream || !stream->buffers)
return;
/* unmap buffers */
for (size_t i = 0; i < stream->nBuffers; i++)
{
if (stream->buffers[i].length && stream->buffers[i].start != MAP_FAILED)
{
munmap(stream->buffers[i].start, stream->buffers[i].length);
}
}
free(stream->buffers);
stream->buffers = nullptr;
stream->nBuffers = 0;
}
/**
* Function description
*
* @return 0 on failure, otherwise allocated buffer size
*/
static size_t cam_v4l_stream_alloc_buffers(CamV4lStream* stream)
{
struct v4l2_requestbuffers rbuffer = WINPR_C_ARRAY_INIT;
rbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rbuffer.memory = V4L2_MEMORY_MMAP;
rbuffer.count = CAM_V4L2_BUFFERS_COUNT;
if (ioctl(stream->fd, VIDIOC_REQBUFS, &rbuffer) < 0 || rbuffer.count == 0)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "Failure in VIDIOC_REQBUFS, errno %s [%d], count %u",
winpr_strerror(errno, buffer, sizeof(buffer)), errno, rbuffer.count);
return 0;
}
stream->nBuffers = rbuffer.count;
/* Map the buffers */
stream->buffers = (CamV4lBuffer*)calloc(rbuffer.count, sizeof(CamV4lBuffer));
if (!stream->buffers)
{
WLog_ERR(TAG, "Failure in calloc");
return 0;
}
for (unsigned int i = 0; i < rbuffer.count; i++)
{
struct v4l2_buffer vbuffer = WINPR_C_ARRAY_INIT;
vbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vbuffer.memory = V4L2_MEMORY_MMAP;
vbuffer.index = i;
if (ioctl(stream->fd, VIDIOC_QUERYBUF, &vbuffer) < 0)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "Failure in VIDIOC_QUERYBUF, errno %s [%d]",
winpr_strerror(errno, buffer, sizeof(buffer)), errno);
cam_v4l_stream_free_buffers(stream);
return 0;
}
stream->buffers[i].start = mmap(nullptr, vbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
stream->fd, vbuffer.m.offset);
if (MAP_FAILED == stream->buffers[i].start)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "Failure in mmap, errno %s [%d]",
winpr_strerror(errno, buffer, sizeof(buffer)), errno);
cam_v4l_stream_free_buffers(stream);
return 0;
}
stream->buffers[i].length = vbuffer.length;
WLog_DBG(TAG, "Buffer %u mapped, size: %u", i, vbuffer.length);
if (ioctl(stream->fd, VIDIOC_QBUF, &vbuffer) < 0)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "Failure in VIDIOC_QBUF, errno %s [%d]",
winpr_strerror(errno, buffer, sizeof(buffer)), errno);
cam_v4l_stream_free_buffers(stream);
return 0;
}
}
return stream->buffers[0].length;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static DWORD WINAPI cam_v4l_stream_capture_thread(LPVOID param)
{
CamV4lStream* stream = (CamV4lStream*)param;
WINPR_ASSERT(stream);
int fd = stream->fd;
do
{
int retVal = 0;
struct pollfd pfd = WINPR_C_ARRAY_INIT;
pfd.fd = fd;
pfd.events = POLLIN;
retVal = poll(&pfd, 1, CAM_V4L2_CAPTURE_THREAD_SLEEP_MS);
if (retVal == 0)
{
/* poll timed out */
continue;
}
else if (retVal < 0)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_DBG(TAG, "Failure in poll, errno %s [%d]",
winpr_strerror(errno, buffer, sizeof(buffer)), errno);
Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS); /* trying to recover */
continue;
}
else if (!(pfd.revents & POLLIN))
{
WLog_DBG(TAG, "poll reported non-read event %d", pfd.revents);
Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS); /* also trying to recover */
continue;
}
EnterCriticalSection(&stream->lock);
if (stream->streaming)
{
struct v4l2_buffer buf = WINPR_C_ARRAY_INIT;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
/* dequeue buffers until empty */
while (ioctl(fd, VIDIOC_DQBUF, &buf) != -1)
{
const UINT error =
stream->sampleCallback(stream->dev, stream->streamIndex,
stream->buffers[buf.index].start, buf.bytesused);
if (error != CHANNEL_RC_OK)
WLog_ERR(TAG, "Failure in sampleCallback: %" PRIu32, error);
/* enqueue buffer back */
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "Failure in VIDIOC_QBUF, errno %s [%d]",
winpr_strerror(errno, buffer, sizeof(buffer)), errno);
}
}
}
LeaveCriticalSection(&stream->lock);
} while (stream->streaming);
return CHANNEL_RC_OK;
}
void cam_v4l_stream_close_device(CamV4lStream* stream)
{
if (stream->fd != -1)
{
close(stream->fd);
stream->fd = -1;
}
}
/**
* Function description
*
* @return Null on failure, otherwise pointer to new CamV4lStream
*/
WINPR_ATTR_MALLOC(cam_v4l_stream_free, 1)
CamV4lStream* cam_v4l_stream_create(const char* deviceId, size_t streamIndex)
{
CamV4lStream* stream = calloc(1, sizeof(CamV4lStream));
if (!stream)
{
WLog_ERR(TAG, "Failure in calloc");
return nullptr;
}
stream->streamIndex = streamIndex;
stream->fd = -1;
stream->h264UnitId = get_uvc_h624_unit_id(deviceId);
if (!InitializeCriticalSectionEx(&stream->lock, 0, 0))
{
WLog_ERR(TAG, "Failure in calloc");
free(stream);
return nullptr;
}
return stream;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
CAM_ERROR_CODE cam_v4l_stream_stop(CamV4lStream* stream)
{
if (!stream || !stream->streaming)
return CAM_ERROR_CODE_None;
stream->streaming = FALSE; /* this will terminate capture thread */
if (stream->captureThread)
{
(void)WaitForSingleObject(stream->captureThread, INFINITE);
(void)CloseHandle(stream->captureThread);
stream->captureThread = nullptr;
}
EnterCriticalSection(&stream->lock);
/* stop streaming */
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(stream->fd, VIDIOC_STREAMOFF, &type) < 0)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "Failure in VIDIOC_STREAMOFF, errno %s [%d]",
winpr_strerror(errno, buffer, sizeof(buffer)), errno);
}
cam_v4l_stream_free_buffers(stream);
cam_v4l_stream_close_device(stream);
LeaveCriticalSection(&stream->lock);
return CAM_ERROR_CODE_None;
}
static CAM_ERROR_CODE cam_v4l_stream_start(ICamHal* ihal, CameraDevice* dev, size_t streamIndex,
const CAM_MEDIA_TYPE_DESCRIPTION* mediaType,
ICamHalSampleCapturedCallback callback)
{
CamV4lHal* hal = (CamV4lHal*)ihal;
WINPR_ASSERT(hal);
CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, dev->deviceId);
if (!stream)
{
WLog_ERR(TAG, "Unable to find stream, device %s, streamIndex %" PRIuz, dev->deviceId,
streamIndex);
return CAM_ERROR_CODE_UnexpectedError;
}
if (stream->streaming)
{
WLog_ERR(TAG, "Streaming already in progress, device %s, streamIndex %" PRIuz,
dev->deviceId, streamIndex);
return CAM_ERROR_CODE_UnexpectedError;
}
stream->dev = dev;
stream->sampleCallback = callback;
if ((stream->fd = cam_v4l_open_device(dev->deviceId, O_RDWR | O_NONBLOCK)) == -1)
{
WLog_ERR(TAG, "Unable to open device %s", dev->deviceId);
return CAM_ERROR_CODE_UnexpectedError;
}
struct v4l2_format video_fmt = WINPR_C_ARRAY_INIT;
video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
UINT32 pixelFormat = 0;
if (mediaType->Format == CAM_MEDIA_FORMAT_MJPG_H264)
{
if (!set_h264_muxed_format(stream, mediaType))
{
WLog_ERR(TAG, "Failure to set H264 muxed format");
cam_v4l_stream_close_device(stream);
return CAM_ERROR_CODE_UnexpectedError;
}
/* setup container stream format */
pixelFormat = V4L2_PIX_FMT_MJPEG;
/* limit container stream resolution to save USB bandwidth - required */
video_fmt.fmt.pix.width = 640;
video_fmt.fmt.pix.height = 480;
}
else
{
pixelFormat = ecamToV4L2PixFormat(mediaType->Format);
video_fmt.fmt.pix.width = mediaType->Width;
video_fmt.fmt.pix.height = mediaType->Height;
}
if (pixelFormat == 0)
{
cam_v4l_stream_close_device(stream);
return CAM_ERROR_CODE_InvalidMediaType;
}
video_fmt.fmt.pix.pixelformat = pixelFormat;
/* set format and frame size */
if (ioctl(stream->fd, VIDIOC_S_FMT, &video_fmt) < 0)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "Failure in VIDIOC_S_FMT, errno %s [%d]",
winpr_strerror(errno, buffer, sizeof(buffer)), errno);
cam_v4l_stream_close_device(stream);
return CAM_ERROR_CODE_InvalidMediaType;
}
/* trying to set frame rate, if driver supports it */
struct v4l2_streamparm sp1 = WINPR_C_ARRAY_INIT;
struct v4l2_streamparm sp2 = WINPR_C_ARRAY_INIT;
sp1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(stream->fd, VIDIOC_G_PARM, &sp1) < 0 ||
!(sp1.parm.capture.capability & V4L2_CAP_TIMEPERFRAME))
{
WLog_INFO(TAG, "Driver doesn't support setting framerate");
}
else
{
sp2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* inverse of a fraction */
sp2.parm.capture.timeperframe.numerator = mediaType->FrameRateDenominator;
sp2.parm.capture.timeperframe.denominator = mediaType->FrameRateNumerator;
if (ioctl(stream->fd, VIDIOC_S_PARM, &sp2) < 0)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_INFO(TAG, "Failed to set the framerate, errno %s [%d]",
winpr_strerror(errno, buffer, sizeof(buffer)), errno);
}
}
size_t maxSample = cam_v4l_stream_alloc_buffers(stream);
if (maxSample == 0)
{
WLog_ERR(TAG, "Failure to allocate video buffers");
cam_v4l_stream_close_device(stream);
return CAM_ERROR_CODE_OutOfMemory;
}
stream->streaming = TRUE;
/* start streaming */
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(stream->fd, VIDIOC_STREAMON, &type) < 0)
{
char buffer[64] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "Failure in VIDIOC_STREAMON, errno %s [%d]",
winpr_strerror(errno, buffer, sizeof(buffer)), errno);
cam_v4l_stream_stop(stream);
return CAM_ERROR_CODE_UnexpectedError;
}
stream->captureThread =
CreateThread(nullptr, 0, cam_v4l_stream_capture_thread, stream, 0, nullptr);
if (!stream->captureThread)
{
WLog_ERR(TAG, "CreateThread failure");
cam_v4l_stream_stop(stream);
return CAM_ERROR_CODE_OutOfMemory;
}
char fourccstr[16] = WINPR_C_ARRAY_INIT;
if (mediaType->Format == CAM_MEDIA_FORMAT_MJPG_H264)
strncpy(fourccstr, "H264 muxed", ARRAYSIZE(fourccstr) - 1);
else
cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr));
WLog_INFO(TAG, "Camera format: %s, width: %u, height: %u, fps: %u/%u", fourccstr,
mediaType->Width, mediaType->Height, mediaType->FrameRateNumerator,
mediaType->FrameRateDenominator);
return CAM_ERROR_CODE_None;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static CAM_ERROR_CODE cam_v4l_stream_stop_by_device_id(ICamHal* ihal, const char* deviceId,
WINPR_ATTR_UNUSED size_t streamIndex)
{
CamV4lHal* hal = (CamV4lHal*)ihal;
CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, deviceId);
if (!stream)
return CAM_ERROR_CODE_NotInitialized;
return cam_v4l_stream_stop(stream);
}
/**
* Function description
*
* OBJECT_FREE_FN for streams hash table value
*
*/
void cam_v4l_stream_free(void* obj)
{
CamV4lStream* stream = (CamV4lStream*)obj;
if (!stream)
return;
cam_v4l_stream_stop(stream);
DeleteCriticalSection(&stream->lock);
free(stream);
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static CAM_ERROR_CODE cam_v4l_free(ICamHal* ihal)
{
CamV4lHal* hal = (CamV4lHal*)ihal;
if (hal == nullptr)
return CAM_ERROR_CODE_NotInitialized;
HashTable_Free(hal->streams);
free(hal);
return CAM_ERROR_CODE_None;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
FREERDP_ENTRY_POINT(UINT VCAPITYPE v4l_freerdp_rdpecam_client_subsystem_entry(
PFREERDP_CAMERA_HAL_ENTRY_POINTS pEntryPoints))
{
UINT ret = CHANNEL_RC_OK;
WINPR_ASSERT(pEntryPoints);
CamV4lHal* hal = (CamV4lHal*)calloc(1, sizeof(CamV4lHal));
if (hal == nullptr)
return CHANNEL_RC_NO_MEMORY;
hal->iHal.Enumerate = cam_v4l_enumerate;
hal->iHal.GetMediaTypeDescriptions = cam_v4l_get_media_type_descriptions;
hal->iHal.Activate = cam_v4l_activate;
hal->iHal.Deactivate = cam_v4l_deactivate;
hal->iHal.StartStream = cam_v4l_stream_start;
hal->iHal.StopStream = cam_v4l_stream_stop_by_device_id;
hal->iHal.Free = cam_v4l_free;
hal->streams = HashTable_New(FALSE);
if (!hal->streams)
{
ret = CHANNEL_RC_NO_MEMORY;
goto error;
}
HashTable_SetupForStringData(hal->streams, FALSE);
wObject* obj = HashTable_ValueObject(hal->streams);
WINPR_ASSERT(obj);
obj->fnObjectFree = cam_v4l_stream_free;
if ((ret = pEntryPoints->pRegisterCameraHal(pEntryPoints->plugin, &hal->iHal)))
{
WLog_ERR(TAG, "RegisterCameraHal failed with error %" PRIu32 "", ret);
goto error;
}
return ret;
error:
cam_v4l_free(&hal->iHal);
return ret;
}

View File

@@ -0,0 +1,54 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* MS-RDPECAM Implementation, V4L Interface
*
* Copyright 2025 Oleg Turovski <oleg2104@hotmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CAMERA_V4L_H
#define CAMERA_V4L_H
#include <winpr/synch.h>
#include <winpr/wtypes.h>
#include "../camera.h"
typedef struct
{
void* start;
size_t length;
} CamV4lBuffer;
typedef struct
{
CRITICAL_SECTION lock;
/* members used to call the callback */
CameraDevice* dev;
size_t streamIndex;
WINPR_ATTR_NODISCARD ICamHalSampleCapturedCallback sampleCallback;
BOOL streaming;
int fd;
uint8_t h264UnitId; /* UVC H264 UnitId, if 0 then UVC H264 is not supported */
size_t nBuffers;
CamV4lBuffer* buffers;
HANDLE captureThread;
} CamV4lStream;
#endif /* CAMERA_V4L_H */

View File

@@ -0,0 +1,489 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* MS-RDPECAM Implementation, UVC H264 support
*
* See USB_Video_Payload_H 264_1 0.pdf for more details
*
* Credits:
* guvcview http://guvcview.sourceforge.net
* Paulo Assis <pj.assis@gmail.com>
*
* Copyright 2025 Oleg Turovski <oleg2104@hotmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/ioctl.h>
#include <linux/uvcvideo.h>
#include <linux/videodev2.h>
#include <libusb.h>
#include "uvc_h264.h"
/* UVC H.264 extension unit GUID: {A29E7641-DE04-47E3-8B2B-F4341AFF003B} */
static uint8_t GUID_UVCX_H264_XU[16] = { 0x41, 0x76, 0x9E, 0xA2, 0x04, 0xDE, 0xE3, 0x47,
0x8B, 0x2B, 0xF4, 0x34, 0x1A, 0xFF, 0x00, 0x3B };
#define TAG CHANNELS_TAG("rdpecam-uvch264.client")
/*
* get length of xu control defined by unit id and selector
* args:
* stream - pointer to video device data
* unit - unit id of xu control
* selector - selector for control
*
* returns: length of xu control
*/
static uint16_t get_length_xu_control(CamV4lStream* stream, uint8_t unit, uint8_t selector)
{
WINPR_ASSERT(stream);
WINPR_ASSERT(stream->fd > 0);
uint16_t length = 0;
struct uvc_xu_control_query xu_ctrl_query = { .unit = unit,
.selector = selector,
.query = UVC_GET_LEN,
.size = sizeof(length),
.data = (uint8_t*)&length };
if (ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query) < 0)
{
char ebuffer[256] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "UVCIOC_CTRL_QUERY (GET_LEN) - Error: %s",
winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
return 0;
}
return length;
}
/*
* runs a query on xu control defined by unit id and selector
* args:
* stream - pointer to video device data
* unit - unit id of xu control
* selector - selector for control
* query - query type
* data - pointer to query data
*
* returns: 0 if query succeeded or error code on fail
*/
static int query_xu_control(CamV4lStream* stream, uint8_t unit, uint8_t selector, uint8_t query,
void* data)
{
int err = 0;
uint16_t len = get_length_xu_control(stream, unit, selector);
struct uvc_xu_control_query xu_ctrl_query = {
.unit = unit, .selector = selector, .query = query, .size = len, .data = (uint8_t*)data
};
/*get query data*/
if ((err = ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query)) < 0)
{
char ebuffer[256] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "UVCIOC_CTRL_QUERY (%" PRIu8 ") - Error: %s", query,
winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
}
return err;
}
/*
* resets the h264 encoder
* args:
* stream - pointer to video device data
*
* returns: 0 on success or error code on fail
*/
static int uvcx_video_encoder_reset(CamV4lStream* stream)
{
WINPR_ASSERT(stream);
uvcx_encoder_reset encoder_reset_req = WINPR_C_ARRAY_INIT;
int err = 0;
if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_ENCODER_RESET, UVC_SET_CUR,
&encoder_reset_req)) < 0)
{
char ebuffer[256] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "UVCX_ENCODER_RESET error: %s",
winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
}
return err;
}
/*
* probes the h264 encoder config
* args:
* stream - pointer to video device data
* query - probe query
* uvcx_video_config - pointer to probe/commit config data
*
* returns: 0 on success or error code on fail
*/
static int uvcx_video_probe(CamV4lStream* stream, uint8_t query,
uvcx_video_config_probe_commit_t* uvcx_video_config)
{
WINPR_ASSERT(stream);
int err = 0;
if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_PROBE, query,
uvcx_video_config)) < 0)
{
char ebuffer[256] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "UVCX_VIDEO_CONFIG_PROBE error: %s",
winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
}
return err;
}
/*
* commits the h264 encoder config
* args:
* stream - pointer to video device data
* uvcx_video_config - pointer to probe/commit config data
*
* returns: 0 on success or error code on fail
*/
static int uvcx_video_commit(CamV4lStream* stream,
uvcx_video_config_probe_commit_t* uvcx_video_config)
{
WINPR_ASSERT(stream);
int err = 0;
if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_COMMIT, UVC_SET_CUR,
uvcx_video_config)) < 0)
{
char ebuffer[256] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "UVCX_VIDEO_CONFIG_COMMIT error: %s",
winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
}
return err;
}
/*
* sets h264 muxed format (must not be called while streaming)
* args:
* stream - pointer to video device data
* mediaType
*
* returns: TRUE on success or FALSE on fail
*/
BOOL set_h264_muxed_format(CamV4lStream* stream, const CAM_MEDIA_TYPE_DESCRIPTION* mediaType)
{
WINPR_ASSERT(stream);
WINPR_ASSERT(mediaType);
uvcx_video_config_probe_commit_t config_probe_req = WINPR_C_ARRAY_INIT;
int err = 0;
/* reset the encoder */
err = uvcx_video_encoder_reset(stream);
if (err != 0)
return FALSE;
/* get default values */
err = uvcx_video_probe(stream, UVC_GET_DEF, &config_probe_req);
if (err != 0)
return FALSE;
/* set resolution */
config_probe_req.wWidth = WINPR_ASSERTING_INT_CAST(uint16_t, mediaType->Width);
config_probe_req.wHeight = WINPR_ASSERTING_INT_CAST(uint16_t, mediaType->Height);
/* set frame rate in 100ns units */
uint32_t frame_interval =
(mediaType->FrameRateDenominator * 1000000000LL / mediaType->FrameRateNumerator) / 100;
config_probe_req.dwFrameInterval = frame_interval;
/* quality settings */
config_probe_req.wProfile = PROFILE_HIGH;
config_probe_req.bUsageType = USAGETYPE_REALTIME;
config_probe_req.bRateControlMode = RATECONTROL_VBR;
config_probe_req.dwBitRate = h264_get_max_bitrate(mediaType->Height);
config_probe_req.bEntropyCABAC = ENTROPY_CABAC;
config_probe_req.wIFramePeriod = 1000; /* ms, 1 sec */
/* hints which parameters are configured */
config_probe_req.bmHints = BMHINTS_RESOLUTION | BMHINTS_FRAME_INTERVAL | BMHINTS_PROFILE |
BMHINTS_USAGE | BMHINTS_RATECONTROL | BMHINTS_BITRATE |
BMHINTS_ENTROPY | BMHINTS_IFRAMEPERIOD;
/* set the aux stream */
config_probe_req.bStreamMuxOption = STREAMMUX_H264;
/* probe the format */
err = uvcx_video_probe(stream, UVC_SET_CUR, &config_probe_req);
if (err != 0)
return FALSE;
err = uvcx_video_probe(stream, UVC_GET_CUR, &config_probe_req);
if (err != 0)
return FALSE;
if (config_probe_req.wWidth != mediaType->Width)
{
WLog_ERR(TAG, "Requested width %" PRIu16 " but got %" PRIu16, mediaType->Width,
config_probe_req.wWidth);
return FALSE;
}
if (config_probe_req.wHeight != mediaType->Height)
{
WLog_ERR(TAG, "Requested height %" PRIu16 " but got %" PRIu16, mediaType->Height,
config_probe_req.wHeight);
return FALSE;
}
if (config_probe_req.dwFrameInterval != frame_interval)
{
WLog_ERR(TAG, "Requested frame interval %" PRIu32 " but got %" PRIu32, frame_interval,
config_probe_req.dwFrameInterval);
return FALSE;
}
/* commit the format */
err = uvcx_video_commit(stream, &config_probe_req);
return (err == 0);
}
/*
* parses deviceId such as usb-0000:00:1a.0-1.2.2 to return devpath (1.2.2)
*
* deviceID format is: usb-<busname>-<devpath>
* see kernel's usb_make_path()
*
* args:
* deviceId
* path - buffer to return devpath
* size - buffer size
*
* returns: TRUE if success, FALSE otherwise
*/
static BOOL get_devpath_from_device_id(const char* deviceId, char* path, size_t size)
{
if (0 != strncmp(deviceId, "usb-", 4))
return FALSE;
/* find second `-` */
const char* p = strchr(deviceId + 4, '-');
if (!p)
return FALSE;
p++; // now points to nullptr terminated devpath
strncpy(path, p, size - 1);
return TRUE;
}
/*
* return devpath of a given libusb_device as text string such as: 1.2.2 or 2.3
*
* args:
* device
* path - buffer to return devpath
* size - buffer size
*
* returns: TRUE if success, FALSE otherwise
*/
static BOOL get_devpath_from_device(libusb_device* device, char* path, size_t size)
{
uint8_t ports[MAX_DEVPATH_DEPTH] = WINPR_C_ARRAY_INIT;
int nPorts = libusb_get_port_numbers(device, ports, sizeof(ports));
if (nPorts <= 0)
return FALSE;
for (int i = 0; i < nPorts; i++)
{
int nChars = snprintf(path, size, "%" PRIu8, ports[i]);
if ((nChars <= 0) || ((size_t)nChars >= size))
return FALSE;
size -= (size_t)nChars;
path += nChars;
if (i < nPorts - 1)
{
*path++ = '.';
size--;
}
}
return TRUE;
}
static uint8_t get_guid_unit_id_from_config_descriptor(struct libusb_config_descriptor* config,
const uint8_t* guid,
const struct libusb_device_descriptor* ddesc)
{
WINPR_ASSERT(config);
WINPR_ASSERT(guid);
WINPR_ASSERT(ddesc);
for (uint8_t j = 0; j < config->bNumInterfaces; j++)
{
const struct libusb_interface* cfg = &config->interface[j];
for (int k = 0; k < cfg->num_altsetting; k++)
{
const struct libusb_interface_descriptor* interface = &cfg->altsetting[k];
if (interface->bInterfaceClass != LIBUSB_CLASS_VIDEO ||
interface->bInterfaceSubClass != USB_VIDEO_CONTROL)
continue;
const uint8_t* ptr = interface->extra;
while (ptr < interface->extra + interface->extra_length)
{
const xu_descriptor* desc = (const xu_descriptor*)ptr;
if (desc->bDescriptorType == USB_VIDEO_CONTROL_INTERFACE &&
desc->bDescriptorSubType == USB_VIDEO_CONTROL_XU_TYPE &&
memcmp(desc->guidExtensionCode, guid, 16) == 0)
{
int8_t unit_id = desc->bUnitID;
WLog_DBG(TAG,
"For camera %04" PRIx16 ":%04" PRIx16
" found UVCX H264 UnitID %" PRId8,
ddesc->idVendor, ddesc->idProduct, unit_id);
if (unit_id < 0)
return 0;
return WINPR_CXX_COMPAT_CAST(uint8_t, unit_id);
}
ptr += desc->bLength;
}
}
}
return 0;
}
/*
* get GUID unit id from libusb_device, if any
*
* args:
* device
* guid - 16 byte xu GUID
*
* returns: unit id for the matching GUID or 0 if none
*/
static uint8_t get_guid_unit_id_from_device(libusb_device* device, const uint8_t* guid)
{
struct libusb_device_descriptor ddesc = WINPR_C_ARRAY_INIT;
if (libusb_get_device_descriptor(device, &ddesc) != 0)
{
WLog_ERR(TAG, "Couldn't get device descriptor");
return 0;
}
for (uint8_t i = 0; i < ddesc.bNumConfigurations; ++i)
{
uint8_t rc = 0;
struct libusb_config_descriptor* config = nullptr;
if (libusb_get_config_descriptor(device, i, &config) != 0)
{
WLog_ERR(TAG,
"Couldn't get config descriptor for "
"configuration %" PRIu8,
i);
}
else
rc = get_guid_unit_id_from_config_descriptor(config, guid, &ddesc);
libusb_free_config_descriptor(config);
if (rc != 0)
return rc;
}
/* no match found */
return 0;
}
/*
* get GUID unit id, if any
*
* args:
* deviceId - camera deviceId such as: usb-0000:00:1a.0-1.2.2
* guid - 16 byte xu GUID
*
* returns: unit id for the matching GUID or 0 if none
*/
static uint8_t get_guid_unit_id(const char* deviceId, const uint8_t* guid)
{
char cam_devpath[MAX_DEVPATH_STR_SIZE] = WINPR_C_ARRAY_INIT;
libusb_context* usb_ctx = nullptr;
libusb_device** device_list = nullptr;
uint8_t unit_id = 0;
if (!get_devpath_from_device_id(deviceId, cam_devpath, sizeof(cam_devpath)))
{
WLog_ERR(TAG, "Unable to get devpath from deviceId %s", deviceId);
return 0;
}
if (0 != libusb_init(&usb_ctx))
{
WLog_ERR(TAG, "Unable to initialize libusb");
return 0;
}
ssize_t cnt = libusb_get_device_list(usb_ctx, &device_list);
for (ssize_t i = 0; i < cnt; i++)
{
char path[MAX_DEVPATH_STR_SIZE] = WINPR_C_ARRAY_INIT;
libusb_device* device = device_list[i];
if (!device || !get_devpath_from_device(device, path, sizeof(path)))
continue;
if (0 != strcmp(cam_devpath, path))
continue;
/* found device with matching devpath, try to get guid unit id */
unit_id = get_guid_unit_id_from_device(device, guid);
if (unit_id > 0)
break; /* got it */
/* there's chance for another devpath match - continue */
}
libusb_free_device_list(device_list, TRUE);
libusb_exit(usb_ctx);
return unit_id;
}
/*
* gets the uvc h264 xu control unit id, if any
*
* args:
* deviceId - camera deviceId such as: usb-0000:00:1a.0-1.2.2
*
* returns: unit id or 0 if none
*/
uint8_t get_uvc_h624_unit_id(const char* deviceId)
{
WINPR_ASSERT(deviceId);
WLog_DBG(TAG, "Checking for UVCX H264 UnitID for %s", deviceId);
return get_guid_unit_id(deviceId, GUID_UVCX_H264_XU);
}

View File

@@ -0,0 +1,173 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* MS-RDPECAM Implementation, UVC H264 support
*
* See USB_Video_Payload_H 264_1 0.pdf for more details
*
* Credits:
* guvcview http://guvcview.sourceforge.net
* Paulo Assis <pj.assis@gmail.com>
*
* Copyright 2025 Oleg Turovski <oleg2104@hotmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UVC_H264_H
#define UVC_H264_H
#include <winpr/wtypes.h>
#include "../camera.h"
#include "camera_v4l.h"
/* UVC H.264 control selectors */
#define UVCX_VIDEO_CONFIG_PROBE 0x01
#define UVCX_VIDEO_CONFIG_COMMIT 0x02
#define UVCX_RATE_CONTROL_MODE 0x03
#define UVCX_TEMPORAL_SCALE_MODE 0x04
#define UVCX_SPATIAL_SCALE_MODE 0x05
#define UVCX_SNR_SCALE_MODE 0x06
#define UVCX_LTR_BUFFER_SIZE_CONTROL 0x07
#define UVCX_LTR_PICTURE_CONTROL 0x08
#define UVCX_PICTURE_TYPE_CONTROL 0x09
#define UVCX_VERSION 0x0A
#define UVCX_ENCODER_RESET 0x0B
#define UVCX_FRAMERATE_CONFIG 0x0C
#define UVCX_VIDEO_ADVANCE_CONFIG 0x0D
#define UVCX_BITRATE_LAYERS 0x0E
#define UVCX_QP_STEPS_LAYERS 0x0F
/* Video Class-Specific Request Codes */
#define UVC_RC_UNDEFINED 0x00
#define UVC_SET_CUR 0x01
#define UVC_GET_CUR 0x81
#define UVC_GET_MIN 0x82
#define UVC_GET_MAX 0x83
#define UVC_GET_RES 0x84
#define UVC_GET_LEN 0x85
#define UVC_GET_INFO 0x86
#define UVC_GET_DEF 0x87
/* bStreamMuxOption defines */
#define STREAMMUX_H264 (1 << 0) | (1 << 1)
/* wProfile defines */
#define PROFILE_BASELINE 0x4200
#define PROFILE_MAIN 0x4D00
#define PROFILE_HIGH 0x6400
/* bUsageType defines */
#define USAGETYPE_REALTIME 0x01
/* bRateControlMode defines */
#define RATECONTROL_CBR 0x01
#define RATECONTROL_VBR 0x02
#define RATECONTROL_CONST_QP 0x03
/* bEntropyCABAC defines */
#define ENTROPY_CABAC 0x01
/* bmHints defines */
#define BMHINTS_RESOLUTION 0x0001
#define BMHINTS_PROFILE 0x0002
#define BMHINTS_RATECONTROL 0x0004
#define BMHINTS_USAGE 0x0008
#define BMHINTS_SLICEMODE 0x0010
#define BMHINTS_SLICEUNITS 0x0020
#define BMHINTS_MVCVIEW 0x0040
#define BMHINTS_TEMPORAL 0x0080
#define BMHINTS_SNR 0x0100
#define BMHINTS_SPATIAL 0x0200
#define BMHINTS_SPATIAL_RATIO 0x0400
#define BMHINTS_FRAME_INTERVAL 0x0800
#define BMHINTS_LEAKY_BKT_SIZE 0x1000
#define BMHINTS_BITRATE 0x2000
#define BMHINTS_ENTROPY 0x4000
#define BMHINTS_IFRAMEPERIOD 0x8000
/* USB related defines */
#define USB_VIDEO_CONTROL 0x01
#define USB_VIDEO_CONTROL_INTERFACE 0x24
#define USB_VIDEO_CONTROL_XU_TYPE 0x06
#define MAX_DEVPATH_DEPTH 8
#define MAX_DEVPATH_STR_SIZE 32
#define WINPR_PACK_PUSH
#include <winpr/pack.h>
/* h264 probe commit struct (uvc 1.1) - packed */
typedef struct
{
uint32_t dwFrameInterval;
uint32_t dwBitRate;
uint16_t bmHints;
uint16_t wConfigurationIndex;
uint16_t wWidth;
uint16_t wHeight;
uint16_t wSliceUnits;
uint16_t wSliceMode;
uint16_t wProfile;
uint16_t wIFramePeriod;
uint16_t wEstimatedVideoDelay;
uint16_t wEstimatedMaxConfigDelay;
uint8_t bUsageType;
uint8_t bRateControlMode;
uint8_t bTemporalScaleMode;
uint8_t bSpatialScaleMode;
uint8_t bSNRScaleMode;
uint8_t bStreamMuxOption;
uint8_t bStreamFormat;
uint8_t bEntropyCABAC;
uint8_t bTimestamp;
uint8_t bNumOfReorderFrames;
uint8_t bPreviewFlipped;
uint8_t bView;
uint8_t bReserved1;
uint8_t bReserved2;
uint8_t bStreamID;
uint8_t bSpatialLayerRatio;
uint16_t wLeakyBucketSize;
} uvcx_video_config_probe_commit_t;
/* encoder reset struct - packed */
typedef struct
{
uint16_t wLayerID;
} uvcx_encoder_reset;
/* xu_descriptor struct - packed */
typedef struct
{
int8_t bLength;
int8_t bDescriptorType;
int8_t bDescriptorSubType;
int8_t bUnitID;
uint8_t guidExtensionCode[16];
} xu_descriptor;
#define WINPR_PACK_POP
#include <winpr/pack.h>
WINPR_ATTR_NODISCARD
FREERDP_LOCAL uint8_t get_uvc_h624_unit_id(const char* deviceId);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL BOOL set_h264_muxed_format(CamV4lStream* stream,
const CAM_MEDIA_TYPE_DESCRIPTION* mediaType);
#endif /* UVC_H264_H */