Merge pull request #10258 from oleg0421/rdpecam_client
Implement MS-RDPECAM client, closes #4971
This commit is contained in:
commit
fa2813e9f9
1
.github/workflows/abi-checker.yml
vendored
1
.github/workflows/abi-checker.yml
vendored
@ -72,6 +72,7 @@ jobs:
|
||||
libopus-dev \
|
||||
libwebp-dev \
|
||||
libpng-dev \
|
||||
libv4l-dev \
|
||||
libjpeg-dev \
|
||||
liburiparser-dev \
|
||||
cmake \
|
||||
|
1
.github/workflows/alt-architectures.yml
vendored
1
.github/workflows/alt-architectures.yml
vendored
@ -86,6 +86,7 @@ jobs:
|
||||
liburiparser-dev \
|
||||
libssl-dev \
|
||||
opensc-pkcs11 \
|
||||
libv4l-dev \
|
||||
cmake \
|
||||
clang
|
||||
run: |
|
||||
|
2
.github/workflows/clang-tidy.yml
vendored
2
.github/workflows/clang-tidy.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
split_workflow: true
|
||||
clang_tidy_checks: ''
|
||||
# List of packages to install
|
||||
apt_packages: libkrb5-dev,libxkbcommon-dev,libxkbfile-dev,libx11-dev,libwayland-dev,libxrandr-dev,libxi-dev,libxrender-dev,libxext-dev,libxinerama-dev,libxfixes-dev,libxcursor-dev,libxv-dev,libxdamage-dev,libxtst-dev,libcups2-dev,libcairo2-dev,libpcsclite-dev,libasound2-dev,libswscale-dev,libpulse-dev,libavcodec-dev,libavutil-dev,libfuse3-dev,libswresample-dev,libusb-1.0-0-dev,libudev-dev,libdbus-glib-1-dev,libpam0g-dev,uuid-dev,libxml2-dev,libcjson-dev,libsdl2-2.0-0,libsdl2-dev,libsdl2-ttf-dev,libsdl2-image-dev,libsystemd-dev,liburiparser-dev,libopus-dev,libwebp-dev,libjpeg-dev,libpng-dev,xsltproc,docbook-xsl,libgsm1-dev,libfaac-dev,libfaad-dev,libsoxr-dev,opencl-c-headers,opencl-headers,ocl-icd-opencl-dev,libssl-dev
|
||||
apt_packages: libkrb5-dev,libxkbcommon-dev,libxkbfile-dev,libx11-dev,libwayland-dev,libxrandr-dev,libxi-dev,libxrender-dev,libxext-dev,libxinerama-dev,libxfixes-dev,libxcursor-dev,libxv-dev,libxdamage-dev,libxtst-dev,libcups2-dev,libcairo2-dev,libpcsclite-dev,libasound2-dev,libswscale-dev,libpulse-dev,libavcodec-dev,libavutil-dev,libfuse3-dev,libswresample-dev,libusb-1.0-0-dev,libudev-dev,libdbus-glib-1-dev,libpam0g-dev,uuid-dev,libxml2-dev,libcjson-dev,libsdl2-2.0-0,libsdl2-dev,libsdl2-ttf-dev,libsdl2-image-dev,libsystemd-dev,liburiparser-dev,libopus-dev,libwebp-dev,libjpeg-dev,libpng-dev,xsltproc,docbook-xsl,libgsm1-dev,libfaac-dev,libfaad-dev,libsoxr-dev,opencl-c-headers,opencl-headers,ocl-icd-opencl-dev,libssl-dev,libv4l-dev
|
||||
|
||||
# CMake command to run in order to generate compile_commands.json
|
||||
build_dir: tidy
|
||||
|
1
.github/workflows/codeql-analysis.yml
vendored
1
.github/workflows/codeql-analysis.yml
vendored
@ -89,6 +89,7 @@ jobs:
|
||||
libavutil-dev \
|
||||
libavcodec-dev \
|
||||
libcups2-dev \
|
||||
libv4l-dev \
|
||||
libpulse-dev \
|
||||
libasound2-dev \
|
||||
libpcsclite-dev \
|
||||
|
1
.github/workflows/coverity.yml
vendored
1
.github/workflows/coverity.yml
vendored
@ -31,6 +31,7 @@ jobs:
|
||||
libpulse-dev \
|
||||
libasound2-dev \
|
||||
libpcsclite-dev \
|
||||
libv4l-dev \
|
||||
xsltproc \
|
||||
libxcb-cursor-dev \
|
||||
libxcursor-dev \
|
||||
|
@ -119,25 +119,29 @@ macro(define_channel_server_subsystem _channel_name _subsystem _type)
|
||||
endmacro(define_channel_server_subsystem)
|
||||
|
||||
macro(add_channel_client _channel_prefix _channel_name)
|
||||
add_subdirectory(client)
|
||||
if(${${_channel_prefix}_CLIENT_STATIC})
|
||||
set(CHANNEL_STATIC_CLIENT_MODULES ${CHANNEL_STATIC_CLIENT_MODULES} ${_channel_prefix} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_CLIENT_NAME ${${_channel_prefix}_CLIENT_NAME} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_CLIENT_CHANNEL ${${_channel_prefix}_CLIENT_CHANNEL} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_CLIENT_ENTRY ${${_channel_prefix}_CLIENT_ENTRY} PARENT_SCOPE)
|
||||
set(CHANNEL_STATIC_CLIENT_ENTRIES ${CHANNEL_STATIC_CLIENT_ENTRIES} ${${_channel_prefix}_CLIENT_ENTRY} PARENT_SCOPE)
|
||||
endif()
|
||||
if (${_channel_prefix}_CLIENT)
|
||||
add_subdirectory(client)
|
||||
if(${${_channel_prefix}_CLIENT_STATIC})
|
||||
set(CHANNEL_STATIC_CLIENT_MODULES ${CHANNEL_STATIC_CLIENT_MODULES} ${_channel_prefix} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_CLIENT_NAME ${${_channel_prefix}_CLIENT_NAME} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_CLIENT_CHANNEL ${${_channel_prefix}_CLIENT_CHANNEL} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_CLIENT_ENTRY ${${_channel_prefix}_CLIENT_ENTRY} PARENT_SCOPE)
|
||||
set(CHANNEL_STATIC_CLIENT_ENTRIES ${CHANNEL_STATIC_CLIENT_ENTRIES} ${${_channel_prefix}_CLIENT_ENTRY} PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
endmacro(add_channel_client)
|
||||
|
||||
macro(add_channel_server _channel_prefix _channel_name)
|
||||
add_subdirectory(server)
|
||||
if(${${_channel_prefix}_SERVER_STATIC})
|
||||
set(CHANNEL_STATIC_SERVER_MODULES ${CHANNEL_STATIC_SERVER_MODULES} ${_channel_prefix} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_SERVER_NAME ${${_channel_prefix}_SERVER_NAME} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_SERVER_CHANNEL ${${_channel_prefix}_SERVER_CHANNEL} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_SERVER_ENTRY ${${_channel_prefix}_SERVER_ENTRY} PARENT_SCOPE)
|
||||
set(CHANNEL_STATIC_SERVER_ENTRIES ${CHANNEL_STATIC_SERVER_ENTRIES} ${${_channel_prefix}_SERVER_ENTRY} PARENT_SCOPE)
|
||||
endif()
|
||||
if (${_channel_prefix}_SERVER)
|
||||
add_subdirectory(server)
|
||||
if(${${_channel_prefix}_SERVER_STATIC})
|
||||
set(CHANNEL_STATIC_SERVER_MODULES ${CHANNEL_STATIC_SERVER_MODULES} ${_channel_prefix} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_SERVER_NAME ${${_channel_prefix}_SERVER_NAME} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_SERVER_CHANNEL ${${_channel_prefix}_SERVER_CHANNEL} PARENT_SCOPE)
|
||||
set(${_channel_prefix}_SERVER_ENTRY ${${_channel_prefix}_SERVER_ENTRY} PARENT_SCOPE)
|
||||
set(CHANNEL_STATIC_SERVER_ENTRIES ${CHANNEL_STATIC_SERVER_ENTRIES} ${${_channel_prefix}_SERVER_ENTRY} PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
endmacro(add_channel_server)
|
||||
|
||||
macro(add_channel_client_subsystem _channel_prefix _channel_name _subsystem _type)
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
set(OPTION_DEFAULT OFF)
|
||||
set(OPTION_CLIENT_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT OFF)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
|
||||
define_channel_options(NAME "disp" TYPE "dynamic"
|
||||
DESCRIPTION "Display Update Virtual Channel Extension"
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
set(OPTION_DEFAULT OFF)
|
||||
set(OPTION_CLIENT_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT OFF)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
|
||||
define_channel_options(NAME "rail" TYPE "static"
|
||||
DESCRIPTION "Remote Programs Virtual Channel Extension"
|
||||
|
@ -1,8 +1,12 @@
|
||||
|
||||
set(OPTION_DEFAULT ON)
|
||||
set(OPTION_CLIENT_DEFAULT OFF)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
|
||||
set(OPTION_CLIENT_DEFAULT OFF)
|
||||
if (NOT IOS AND NOT ANDROID AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(OPTION_CLIENT_DEFAULT ON)
|
||||
endif()
|
||||
|
||||
define_channel_options(NAME "rdpecam" TYPE "dynamic"
|
||||
DESCRIPTION "Video Capture Virtual Channel Extension"
|
||||
SPECIFICATIONS "[MS-RDPECAM]"
|
||||
|
@ -0,0 +1,51 @@
|
||||
# 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.
|
||||
|
||||
define_channel_client("rdpecam")
|
||||
|
||||
if(NOT WITH_SWSCALE OR NOT WITH_FFMPEG)
|
||||
message(FATAL_ERROR "WITH_FFMPEG and WITH_SWSCALE required for CHANNEL_RDPECAM_CLIENT")
|
||||
endif()
|
||||
|
||||
# currently camera redirect client supported for platforms with Video4Linux only
|
||||
find_package(SWScale REQUIRED)
|
||||
find_package(V4L)
|
||||
if(V4L_FOUND)
|
||||
set(WITH_V4L ON)
|
||||
add_definitions("-DWITH_V4L")
|
||||
else()
|
||||
message(FATAL_ERROR "libv4l-dev required for CHANNEL_RDPECAM_CLIENT")
|
||||
endif()
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
camera_device_enum_main.c
|
||||
camera_device_main.c
|
||||
encoding.c
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
freerdp
|
||||
winpr
|
||||
${SWScale_LIBRARY}
|
||||
${FFMPEG_LIBRARIES}
|
||||
)
|
||||
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
|
||||
|
||||
if(V4L_FOUND)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "v4l" "")
|
||||
endif()
|
188
channels/rdpecam/client/camera.h
Normal file
188
channels/rdpecam/client/camera.h
Normal file
@ -0,0 +1,188 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* MS-RDPECAM Implementation, main header file
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_CLIENT_CAMERA_H
|
||||
#define FREERDP_CLIENT_CAMERA_H
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
|
||||
#include <winpr/wlog.h>
|
||||
|
||||
#include <freerdp/api.h>
|
||||
#include <freerdp/types.h>
|
||||
|
||||
#include <freerdp/client/channels.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
#include <freerdp/channels/rdpecam.h>
|
||||
#include <freerdp/codecs.h>
|
||||
#include <freerdp/primitives.h>
|
||||
|
||||
#define ECAM_PROTO_VERSION 0x02
|
||||
/* currently supporting 1 stream per device */
|
||||
#define ECAM_DEVICE_MAX_STREAMS 1
|
||||
#define ECAM_MAX_MEDIA_TYPE_DESCRIPTORS 256
|
||||
|
||||
/* Allow to send up to that many unsolicited samples.
|
||||
* For example, to support 30 fps with 250 ms round trip
|
||||
* ECAM_MAX_SAMPLE_CREDITS has to be at least 8.
|
||||
*/
|
||||
#define ECAM_MAX_SAMPLE_CREDITS 8
|
||||
|
||||
/* Having this hardcoded allows to preallocate and reuse buffer
|
||||
* for sample responses. Excessive size is to make sure any sample
|
||||
* will fit in, even with highest resolution.
|
||||
*/
|
||||
#define ECAM_SAMPLE_RESPONSE_BUFFER_SIZE (1024 * 4050)
|
||||
|
||||
/* 4 Mbps max encoded bitrate seems to produce reasonably
|
||||
* good quality with H264_RATECONTROL_VBR.
|
||||
*/
|
||||
#define ECAM_H264_ENCODED_BITRATE 4000000
|
||||
|
||||
typedef struct s_ICamHal ICamHal;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IWTSPlugin iface;
|
||||
IWTSListener* listener;
|
||||
GENERIC_LISTENER_CALLBACK* hlistener;
|
||||
|
||||
/* HAL interface */
|
||||
ICamHal* ihal;
|
||||
char* subsystem;
|
||||
|
||||
BOOL initialized;
|
||||
BOOL attached;
|
||||
|
||||
UINT32 version;
|
||||
wHashTable* devices;
|
||||
|
||||
} CameraPlugin;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
CAM_MEDIA_FORMAT inputFormat; /* camera side */
|
||||
CAM_MEDIA_FORMAT outputFormat; /* network side */
|
||||
|
||||
} CAM_MEDIA_FORMAT_INFO;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
BOOL streaming;
|
||||
CAM_MEDIA_FORMAT_INFO formats;
|
||||
CAM_MEDIA_TYPE_DESCRIPTION currMediaType;
|
||||
|
||||
GENERIC_CHANNEL_CALLBACK* hSampleReqChannel;
|
||||
INT nSampleCredits;
|
||||
wStream* sampleRespBuffer;
|
||||
|
||||
H264_CONTEXT* h264;
|
||||
struct SwsContext* sws;
|
||||
int srcLineSizes[4];
|
||||
|
||||
} CameraDeviceStream;
|
||||
|
||||
static INLINE CAM_MEDIA_FORMAT streamInputFormat(CameraDeviceStream* stream)
|
||||
{
|
||||
return stream->formats.inputFormat;
|
||||
}
|
||||
static INLINE CAM_MEDIA_FORMAT streamOutputFormat(CameraDeviceStream* stream)
|
||||
{
|
||||
return stream->formats.outputFormat;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IWTSListener* listener;
|
||||
GENERIC_LISTENER_CALLBACK* hlistener;
|
||||
CameraPlugin* ecam;
|
||||
ICamHal* ihal; /* HAL interface, same as used by CameraPlugin */
|
||||
char deviceId[32];
|
||||
CameraDeviceStream streams[ECAM_DEVICE_MAX_STREAMS];
|
||||
|
||||
} CameraDevice;
|
||||
|
||||
/**
|
||||
* Subsystem (Hardware Abstraction Layer, HAL) Interface
|
||||
*/
|
||||
|
||||
typedef UINT (*ICamHalEnumCallback)(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
const char* deviceId, const char* deviceName);
|
||||
|
||||
/* may run in context of different thread */
|
||||
typedef UINT (*ICamHalSampleCapturedCallback)(CameraDevice* dev, int streamIndex,
|
||||
const BYTE* sample, size_t size);
|
||||
|
||||
struct s_ICamHal
|
||||
{
|
||||
UINT(*Enumerate)
|
||||
(ICamHal* ihal, ICamHalEnumCallback callback, CameraPlugin* ecam,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel);
|
||||
INT16(*GetMediaTypeDescriptions)
|
||||
(ICamHal* ihal, const char* deviceId, int streamIndex,
|
||||
const CAM_MEDIA_FORMAT_INFO* supportedFormats, size_t nSupportedFormats,
|
||||
CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes, size_t* nMediaTypes);
|
||||
UINT(*StartStream)
|
||||
(ICamHal* ihal, CameraDevice* dev, int streamIndex, const CAM_MEDIA_TYPE_DESCRIPTION* mediaType,
|
||||
ICamHalSampleCapturedCallback callback);
|
||||
UINT (*StopStream)(ICamHal* ihal, const char* deviceId, int streamIndex);
|
||||
UINT (*Free)(ICamHal* hal);
|
||||
};
|
||||
|
||||
typedef UINT (*PREGISTERCAMERAHAL)(IWTSPlugin* plugin, ICamHal* hal);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IWTSPlugin* plugin;
|
||||
PREGISTERCAMERAHAL pRegisterCameraHal;
|
||||
CameraPlugin* ecam;
|
||||
const ADDIN_ARGV* args;
|
||||
|
||||
} FREERDP_CAMERA_HAL_ENTRY_POINTS;
|
||||
|
||||
typedef FREERDP_CAMERA_HAL_ENTRY_POINTS* PFREERDP_CAMERA_HAL_ENTRY_POINTS;
|
||||
|
||||
/* entry point called by addin manager */
|
||||
typedef UINT (*PFREERDP_CAMERA_HAL_ENTRY)(PFREERDP_CAMERA_HAL_ENTRY_POINTS pEntryPoints);
|
||||
|
||||
/* common functions */
|
||||
UINT ecam_channel_send_generic_msg(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
CAM_MSG_ID msg);
|
||||
UINT ecam_channel_send_error_response(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
CAM_ERROR_CODE code);
|
||||
UINT ecam_channel_write(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel, CAM_MSG_ID msg,
|
||||
wStream* out, BOOL freeStream);
|
||||
|
||||
/* ecam device interface */
|
||||
CameraDevice* ecam_dev_create(CameraPlugin* ecam, const char* deviceId, const char* deviceName);
|
||||
void ecam_dev_destroy(void* dev);
|
||||
|
||||
/* video encoding interface */
|
||||
BOOL ecam_encoder_context_init(CameraDeviceStream* stream);
|
||||
BOOL ecam_encoder_context_free(CameraDeviceStream* stream);
|
||||
BOOL ecam_encoder_compress(CameraDeviceStream* stream, const BYTE* srcData, size_t srcSize,
|
||||
BYTE** ppDstData, size_t* pDstSize);
|
||||
|
||||
#endif /* FREERDP_CLIENT_CAMERA_H */
|
551
channels/rdpecam/client/camera_device_enum_main.c
Normal file
551
channels/rdpecam/client/camera_device_enum_main.c
Normal file
@ -0,0 +1,551 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* MS-RDPECAM Implementation, Device Enumeration Channel
|
||||
*
|
||||
* 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 <winpr/assert.h>
|
||||
|
||||
#include "camera.h"
|
||||
|
||||
#define TAG CHANNELS_TAG("rdpecam-enum.client")
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT ecam_channel_send_error_response(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
CAM_ERROR_CODE code)
|
||||
{
|
||||
CAM_MSG_ID msg = CAM_MSG_ID_ErrorResponse;
|
||||
|
||||
WINPR_ASSERT(ecam);
|
||||
|
||||
wStream* s = Stream_New(NULL, CAM_HEADER_SIZE + 4);
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed!");
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
Stream_Write_UINT8(s, ecam->version);
|
||||
Stream_Write_UINT8(s, msg);
|
||||
Stream_Write_UINT32(s, code);
|
||||
|
||||
return ecam_channel_write(ecam, hchannel, msg, s, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT ecam_channel_send_generic_msg(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
CAM_MSG_ID msg)
|
||||
{
|
||||
WINPR_ASSERT(ecam);
|
||||
|
||||
wStream* s = Stream_New(NULL, CAM_HEADER_SIZE);
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed!");
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
Stream_Write_UINT8(s, ecam->version);
|
||||
Stream_Write_UINT8(s, msg);
|
||||
|
||||
return ecam_channel_write(ecam, hchannel, msg, s, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT ecam_channel_write(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel, CAM_MSG_ID msg,
|
||||
wStream* out, BOOL freeStream)
|
||||
{
|
||||
if (!hchannel || !out)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
Stream_SealLength(out);
|
||||
WINPR_ASSERT(Stream_Length(out) <= ULONG_MAX);
|
||||
|
||||
WLog_DBG(TAG, "ChannelId=%d, MessageId=0x%02" PRIx8 ", Length=%d",
|
||||
hchannel->channel_mgr->GetChannelId(hchannel->channel), msg, Stream_Length(out));
|
||||
|
||||
const UINT error = hchannel->channel->Write(hchannel->channel, (ULONG)Stream_Length(out),
|
||||
Stream_Buffer(out), NULL);
|
||||
|
||||
if (freeStream)
|
||||
Stream_Free(out, TRUE);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_send_device_added_notification(CameraPlugin* ecam,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
const char* deviceName, const char* channelName)
|
||||
{
|
||||
CAM_MSG_ID msg = CAM_MSG_ID_DeviceAddedNotification;
|
||||
|
||||
WINPR_ASSERT(ecam);
|
||||
|
||||
wStream* s = Stream_New(NULL, 256);
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed!");
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
Stream_Write_UINT8(s, ecam->version);
|
||||
Stream_Write_UINT8(s, msg);
|
||||
|
||||
size_t devNameLen = strlen(deviceName);
|
||||
if (Stream_Write_UTF16_String_From_UTF8(s, devNameLen + 1, deviceName, devNameLen, TRUE) < 0)
|
||||
{
|
||||
Stream_Free(s, TRUE);
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
Stream_Write(s, channelName, strlen(channelName) + 1);
|
||||
|
||||
return ecam_channel_write(ecam, hchannel, msg, s, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_ihal_device_added_callback(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
const char* deviceId, const char* deviceName)
|
||||
{
|
||||
WLog_DBG(TAG, "deviceId=%s, deviceName=%s", deviceId, deviceName);
|
||||
|
||||
if (!HashTable_ContainsKey(ecam->devices, deviceId))
|
||||
{
|
||||
CameraDevice* dev = ecam_dev_create(ecam, deviceId, deviceName);
|
||||
HashTable_Insert(ecam->devices, deviceId, dev);
|
||||
}
|
||||
else
|
||||
{
|
||||
WLog_DBG(TAG, "Device %s already exists", deviceId);
|
||||
}
|
||||
|
||||
ecam_send_device_added_notification(ecam, hchannel, deviceName, deviceId /*channelName*/);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_enumerate_devices(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel)
|
||||
{
|
||||
ecam->ihal->Enumerate(ecam->ihal, ecam_ihal_device_added_callback, ecam, hchannel);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_process_select_version_response(CameraPlugin* ecam,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s,
|
||||
BYTE serverVersion)
|
||||
{
|
||||
const BYTE clientVersion = ECAM_PROTO_VERSION;
|
||||
|
||||
/* check remaining s capacity */
|
||||
|
||||
WLog_DBG(TAG, "ServerVersion=%" PRIu8 ", ClientVersion=%" PRIu8, serverVersion, clientVersion);
|
||||
|
||||
if (serverVersion > clientVersion)
|
||||
{
|
||||
WLog_ERR(TAG,
|
||||
"Incompatible protocol version server=%" PRIu8 ", client supports version=%" PRIu8,
|
||||
serverVersion, clientVersion);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
ecam->version = serverVersion;
|
||||
|
||||
if (ecam->ihal)
|
||||
ecam_enumerate_devices(ecam, hchannel);
|
||||
else
|
||||
WLog_ERR(TAG, "No HAL registered");
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
|
||||
{
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
BYTE version = 0;
|
||||
BYTE messageId = 0;
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
|
||||
|
||||
if (!hchannel || !data)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
CameraPlugin* ecam = (CameraPlugin*)hchannel->plugin;
|
||||
|
||||
if (!ecam)
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
|
||||
if (!Stream_CheckAndLogRequiredCapacity(TAG, data, CAM_HEADER_SIZE))
|
||||
return ERROR_NO_DATA;
|
||||
|
||||
Stream_Read_UINT8(data, version);
|
||||
Stream_Read_UINT8(data, messageId);
|
||||
WLog_DBG(TAG, "ChannelId=%d, MessageId=0x%02" PRIx8 ", Version=%d",
|
||||
hchannel->channel_mgr->GetChannelId(hchannel->channel), messageId, version);
|
||||
|
||||
switch (messageId)
|
||||
{
|
||||
case CAM_MSG_ID_SelectVersionResponse:
|
||||
error = ecam_process_select_version_response(ecam, hchannel, data, version);
|
||||
break;
|
||||
|
||||
default:
|
||||
WLog_WARN(TAG, "unknown MessageId=0x%02" PRIx8 "", messageId);
|
||||
error = ERROR_INVALID_DATA;
|
||||
ecam_channel_send_error_response(ecam, hchannel, CAM_ERROR_CODE_OperationNotSupported);
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_on_open(IWTSVirtualChannelCallback* pChannelCallback)
|
||||
{
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
|
||||
WINPR_ASSERT(hchannel);
|
||||
|
||||
CameraPlugin* ecam = (CameraPlugin*)hchannel->plugin;
|
||||
WINPR_ASSERT(ecam);
|
||||
|
||||
WLog_DBG(TAG, "entered");
|
||||
return ecam_channel_send_generic_msg(ecam, hchannel, CAM_MSG_ID_SelectVersionRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_on_close(IWTSVirtualChannelCallback* pChannelCallback)
|
||||
{
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
|
||||
WINPR_ASSERT(hchannel);
|
||||
|
||||
WLog_DBG(TAG, "entered");
|
||||
|
||||
free(hchannel);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
|
||||
IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept,
|
||||
IWTSVirtualChannelCallback** ppCallback)
|
||||
{
|
||||
GENERIC_LISTENER_CALLBACK* hlistener = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
|
||||
|
||||
if (!hlistener || !hlistener->plugin)
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
|
||||
WLog_DBG(TAG, "entered");
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel =
|
||||
(GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK));
|
||||
|
||||
if (!hchannel)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
hchannel->iface.OnDataReceived = ecam_on_data_received;
|
||||
hchannel->iface.OnOpen = ecam_on_open;
|
||||
hchannel->iface.OnClose = ecam_on_close;
|
||||
hchannel->plugin = hlistener->plugin;
|
||||
hchannel->channel_mgr = hlistener->channel_mgr;
|
||||
hchannel->channel = pChannel;
|
||||
*ppCallback = (IWTSVirtualChannelCallback*)hchannel;
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
|
||||
{
|
||||
CameraPlugin* ecam = (CameraPlugin*)pPlugin;
|
||||
|
||||
WLog_DBG(TAG, "entered");
|
||||
|
||||
if (!ecam || !pChannelMgr)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (ecam->initialized)
|
||||
{
|
||||
WLog_ERR(TAG, "[%s] plugin initialized twice, aborting", RDPECAM_CONTROL_DVC_CHANNEL_NAME);
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
ecam->version = ECAM_PROTO_VERSION;
|
||||
|
||||
ecam->devices = HashTable_New(FALSE);
|
||||
if (!ecam->devices)
|
||||
{
|
||||
WLog_ERR(TAG, "HashTable_New failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
HashTable_SetupForStringData(ecam->devices, FALSE);
|
||||
|
||||
wObject* obj = HashTable_ValueObject(ecam->devices);
|
||||
WINPR_ASSERT(obj);
|
||||
obj->fnObjectFree = ecam_dev_destroy;
|
||||
|
||||
ecam->hlistener = (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
|
||||
|
||||
if (!ecam->hlistener)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
ecam->hlistener->iface.OnNewChannelConnection = ecam_on_new_channel_connection;
|
||||
ecam->hlistener->plugin = pPlugin;
|
||||
ecam->hlistener->channel_mgr = pChannelMgr;
|
||||
const UINT rc = pChannelMgr->CreateListener(pChannelMgr, RDPECAM_CONTROL_DVC_CHANNEL_NAME, 0,
|
||||
&ecam->hlistener->iface, &ecam->listener);
|
||||
|
||||
ecam->initialized = (rc == CHANNEL_RC_OK);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_plugin_terminated(IWTSPlugin* pPlugin)
|
||||
{
|
||||
CameraPlugin* ecam = (CameraPlugin*)pPlugin;
|
||||
|
||||
if (!ecam)
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
WLog_DBG(TAG, "entered");
|
||||
|
||||
if (ecam->hlistener)
|
||||
{
|
||||
IWTSVirtualChannelManager* mgr = ecam->hlistener->channel_mgr;
|
||||
if (mgr)
|
||||
IFCALL(mgr->DestroyListener, mgr, ecam->listener);
|
||||
}
|
||||
|
||||
free(ecam->hlistener);
|
||||
|
||||
HashTable_Free(ecam->devices);
|
||||
|
||||
if (ecam->ihal)
|
||||
ecam->ihal->Free(ecam->ihal);
|
||||
|
||||
free(ecam);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_plugin_attached(IWTSPlugin* pPlugin)
|
||||
{
|
||||
CameraPlugin* ecam = (CameraPlugin*)pPlugin;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
if (!ecam)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
ecam->attached = TRUE;
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_plugin_detached(IWTSPlugin* pPlugin)
|
||||
{
|
||||
CameraPlugin* ecam = (CameraPlugin*)pPlugin;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
if (!ecam)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
ecam->attached = FALSE;
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_register_hal_plugin(IWTSPlugin* pPlugin, ICamHal* ihal)
|
||||
{
|
||||
CameraPlugin* ecam = (CameraPlugin*)pPlugin;
|
||||
|
||||
WINPR_ASSERT(ecam);
|
||||
|
||||
if (ecam->ihal)
|
||||
{
|
||||
WLog_DBG(TAG, "already registered");
|
||||
return ERROR_ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
WLog_DBG(TAG, "HAL registered");
|
||||
ecam->ihal = ihal;
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_load_hal_plugin(CameraPlugin* ecam, const char* name, const ADDIN_ARGV* args)
|
||||
{
|
||||
WINPR_ASSERT(ecam);
|
||||
|
||||
FREERDP_CAMERA_HAL_ENTRY_POINTS entryPoints = { 0 };
|
||||
UINT error = ERROR_INTERNAL_ERROR;
|
||||
const PFREERDP_CAMERA_HAL_ENTRY entry =
|
||||
(const PFREERDP_CAMERA_HAL_ENTRY)freerdp_load_channel_addin_entry(RDPECAM_CHANNEL_NAME,
|
||||
name, NULL, 0);
|
||||
|
||||
if (entry == NULL)
|
||||
{
|
||||
WLog_ERR(TAG,
|
||||
"freerdp_load_channel_addin_entry did not return any function pointers for %s ",
|
||||
name);
|
||||
return ERROR_INVALID_FUNCTION;
|
||||
}
|
||||
|
||||
entryPoints.plugin = &ecam->iface;
|
||||
entryPoints.pRegisterCameraHal = ecam_register_hal_plugin;
|
||||
entryPoints.args = args;
|
||||
entryPoints.ecam = ecam;
|
||||
|
||||
if ((error = entry(&entryPoints)))
|
||||
{
|
||||
WLog_ERR(TAG, "%s entry returned error %" PRIu32 ".", name, error);
|
||||
return error;
|
||||
}
|
||||
|
||||
WLog_INFO(TAG, "Loaded %s HAL for ecam", name);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT rdpecam_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
|
||||
{
|
||||
UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
|
||||
|
||||
WINPR_ASSERT(pEntryPoints);
|
||||
WINPR_ASSERT(pEntryPoints->GetPlugin);
|
||||
CameraPlugin* ecam = (CameraPlugin*)pEntryPoints->GetPlugin(pEntryPoints, RDPECAM_CHANNEL_NAME);
|
||||
|
||||
if (ecam != NULL)
|
||||
return CHANNEL_RC_ALREADY_INITIALIZED;
|
||||
|
||||
ecam = (CameraPlugin*)calloc(1, sizeof(CameraPlugin));
|
||||
|
||||
if (!ecam)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
ecam->attached = TRUE;
|
||||
ecam->iface.Initialize = ecam_plugin_initialize;
|
||||
ecam->iface.Connected = NULL; /* server connects to client */
|
||||
ecam->iface.Disconnected = NULL;
|
||||
ecam->iface.Terminated = ecam_plugin_terminated;
|
||||
ecam->iface.Attached = ecam_plugin_attached;
|
||||
ecam->iface.Detached = ecam_plugin_detached;
|
||||
|
||||
/* TODO: camera redirect only supported for platforms with Video4Linux */
|
||||
#if defined(WITH_V4L)
|
||||
ecam->subsystem = "v4l";
|
||||
#else
|
||||
ecam->subsystem = NULL;
|
||||
#endif
|
||||
|
||||
if (ecam->subsystem)
|
||||
{
|
||||
if ((error = ecam_load_hal_plugin(ecam, ecam->subsystem, NULL /*args*/)))
|
||||
{
|
||||
WLog_ERR(TAG,
|
||||
"Unable to load camera redirection subsystem %s because of error %" PRIu32 "",
|
||||
ecam->subsystem, error);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
error = pEntryPoints->RegisterPlugin(pEntryPoints, RDPECAM_CHANNEL_NAME, &ecam->iface);
|
||||
if (error == CHANNEL_RC_OK)
|
||||
return error;
|
||||
|
||||
out:
|
||||
ecam_plugin_terminated(&ecam->iface);
|
||||
return error;
|
||||
}
|
821
channels/rdpecam/client/camera_device_main.c
Normal file
821
channels/rdpecam/client/camera_device_main.c
Normal file
@ -0,0 +1,821 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* MS-RDPECAM Implementation, Device Channels
|
||||
*
|
||||
* 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 <winpr/assert.h>
|
||||
|
||||
#include "camera.h"
|
||||
|
||||
#define TAG CHANNELS_TAG("rdpecam-device.client")
|
||||
|
||||
/* supported formats in preference order:
|
||||
* passthrough, I420 (used as input for H264 encoder), other YUV based, RGB based
|
||||
*/
|
||||
static const CAM_MEDIA_FORMAT_INFO supportedFormats[] = {
|
||||
/* inputFormat, outputFormat */
|
||||
{ CAM_MEDIA_FORMAT_H264, CAM_MEDIA_FORMAT_H264 }, /* passthrough: comment out to disable */
|
||||
{ CAM_MEDIA_FORMAT_I420, CAM_MEDIA_FORMAT_H264 },
|
||||
{ CAM_MEDIA_FORMAT_YUY2, CAM_MEDIA_FORMAT_H264 },
|
||||
{ CAM_MEDIA_FORMAT_NV12, CAM_MEDIA_FORMAT_H264 },
|
||||
{ CAM_MEDIA_FORMAT_RGB24, CAM_MEDIA_FORMAT_H264 },
|
||||
{ CAM_MEDIA_FORMAT_RGB32, CAM_MEDIA_FORMAT_H264 },
|
||||
};
|
||||
static const size_t nSupportedFormats = ARRAYSIZE(supportedFormats);
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
static void ecam_dev_write_media_type(wStream* s, CAM_MEDIA_TYPE_DESCRIPTION* mediaType)
|
||||
{
|
||||
WINPR_ASSERT(mediaType);
|
||||
|
||||
Stream_Write_UINT8(s, mediaType->Format);
|
||||
Stream_Write_UINT32(s, mediaType->Width);
|
||||
Stream_Write_UINT32(s, mediaType->Height);
|
||||
Stream_Write_UINT32(s, mediaType->FrameRateNumerator);
|
||||
Stream_Write_UINT32(s, mediaType->FrameRateDenominator);
|
||||
Stream_Write_UINT32(s, mediaType->PixelAspectRatioNumerator);
|
||||
Stream_Write_UINT32(s, mediaType->PixelAspectRatioDenominator);
|
||||
Stream_Write_UINT8(s, mediaType->Flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return TRUE if success
|
||||
*/
|
||||
static BOOL ecam_dev_read_media_type(wStream* s, CAM_MEDIA_TYPE_DESCRIPTION* mediaType)
|
||||
{
|
||||
WINPR_ASSERT(mediaType);
|
||||
|
||||
Stream_Read_UINT8(s, mediaType->Format);
|
||||
Stream_Read_UINT32(s, mediaType->Width);
|
||||
Stream_Read_UINT32(s, mediaType->Height);
|
||||
Stream_Read_UINT32(s, mediaType->FrameRateNumerator);
|
||||
Stream_Read_UINT32(s, mediaType->FrameRateDenominator);
|
||||
Stream_Read_UINT32(s, mediaType->PixelAspectRatioNumerator);
|
||||
Stream_Read_UINT32(s, mediaType->PixelAspectRatioDenominator);
|
||||
Stream_Read_UINT8(s, mediaType->Flags);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
static void ecam_dev_print_media_type(CAM_MEDIA_TYPE_DESCRIPTION* mediaType)
|
||||
{
|
||||
WINPR_ASSERT(mediaType);
|
||||
|
||||
WLog_DBG(TAG, "Format: %d, width: %d, height: %d, fps: %d, flags: %d", mediaType->Format,
|
||||
mediaType->Width, mediaType->Height, mediaType->FrameRateNumerator, mediaType->Flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_send_sample_response(CameraDevice* dev, size_t streamIndex, const BYTE* sample,
|
||||
size_t size)
|
||||
{
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
CameraDeviceStream* stream = &dev->streams[streamIndex];
|
||||
CAM_MSG_ID msg = CAM_MSG_ID_SampleResponse;
|
||||
|
||||
Stream_SetPosition(stream->sampleRespBuffer, 0);
|
||||
|
||||
Stream_Write_UINT8(stream->sampleRespBuffer, dev->ecam->version);
|
||||
Stream_Write_UINT8(stream->sampleRespBuffer, msg);
|
||||
Stream_Write_UINT8(stream->sampleRespBuffer, streamIndex);
|
||||
|
||||
Stream_Write(stream->sampleRespBuffer, sample, size);
|
||||
|
||||
/* channel write is protected by critical section in dvcman_write_channel */
|
||||
return ecam_channel_write(dev->ecam, stream->hSampleReqChannel, msg, stream->sampleRespBuffer,
|
||||
FALSE /* don't free stream */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_sample_captured_callback(CameraDevice* dev, int streamIndex,
|
||||
const BYTE* sample, size_t size)
|
||||
{
|
||||
BYTE* encodedSample = NULL;
|
||||
size_t encodedSize = 0;
|
||||
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
if (streamIndex >= ECAM_DEVICE_MAX_STREAMS)
|
||||
return ERROR_INVALID_INDEX;
|
||||
|
||||
CameraDeviceStream* stream = &dev->streams[streamIndex];
|
||||
|
||||
if (!stream->streaming)
|
||||
return CHANNEL_RC_OK;
|
||||
|
||||
if (streamInputFormat(stream) != streamOutputFormat(stream))
|
||||
{
|
||||
if (!ecam_encoder_compress(stream, sample, size, &encodedSample, &encodedSize))
|
||||
{
|
||||
WLog_DBG(TAG, "Frame drop or error in ecam_encoder_compress");
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
if (!stream->streaming)
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
else /* passthrough */
|
||||
{
|
||||
encodedSample = (BYTE*)sample;
|
||||
encodedSize = size;
|
||||
}
|
||||
|
||||
if (stream->nSampleCredits == 0)
|
||||
{
|
||||
WLog_DBG(TAG, "Skip sample: no credits left");
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
stream->nSampleCredits--;
|
||||
|
||||
return ecam_dev_send_sample_response(dev, streamIndex, encodedSample, encodedSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
static void ecam_dev_stop_stream(CameraDevice* dev, size_t streamIndex)
|
||||
{
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
if (streamIndex >= ECAM_DEVICE_MAX_STREAMS)
|
||||
return;
|
||||
|
||||
CameraDeviceStream* stream = &dev->streams[streamIndex];
|
||||
|
||||
if (stream->streaming)
|
||||
{
|
||||
stream->streaming = FALSE;
|
||||
dev->ihal->StopStream(dev->ihal, dev->deviceId, 0);
|
||||
}
|
||||
|
||||
if (stream->sampleRespBuffer)
|
||||
{
|
||||
Stream_Free(stream->sampleRespBuffer, TRUE);
|
||||
stream->sampleRespBuffer = NULL;
|
||||
}
|
||||
|
||||
ecam_encoder_context_free(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_process_stop_streams_request(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s)
|
||||
{
|
||||
WINPR_ASSERT(dev);
|
||||
WINPR_UNUSED(s);
|
||||
|
||||
for (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++)
|
||||
ecam_dev_stop_stream(dev, i);
|
||||
|
||||
return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_process_start_streams_request(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s)
|
||||
{
|
||||
BYTE streamIndex = 0;
|
||||
CAM_MEDIA_TYPE_DESCRIPTION mediaType = { 0 };
|
||||
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 1 + 26))
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
Stream_Read_UINT8(s, streamIndex);
|
||||
|
||||
if (streamIndex >= ECAM_DEVICE_MAX_STREAMS)
|
||||
{
|
||||
WLog_ERR(TAG, "Incorrect streamIndex %" PRIuz, streamIndex);
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidStreamNumber);
|
||||
return ERROR_INVALID_INDEX;
|
||||
}
|
||||
|
||||
if (!ecam_dev_read_media_type(s, &mediaType))
|
||||
{
|
||||
WLog_ERR(TAG, "Unable to read MEDIA_TYPE_DESCRIPTION");
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidMessage);
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
ecam_dev_print_media_type(&mediaType);
|
||||
|
||||
CameraDeviceStream* stream = &dev->streams[streamIndex];
|
||||
|
||||
if (stream->streaming)
|
||||
{
|
||||
WLog_ERR(TAG, "Streaming already in progress, device %s, streamIndex %d", dev->deviceId,
|
||||
streamIndex);
|
||||
return CAM_ERROR_CODE_UnexpectedError;
|
||||
}
|
||||
|
||||
/* saving media type description for CurrentMediaTypeRequest,
|
||||
* to be done before calling ecam_encoder_context_init
|
||||
*/
|
||||
stream->currMediaType = mediaType;
|
||||
|
||||
/* initialize encoder, if input and output formats differ */
|
||||
if (streamInputFormat(stream) != streamOutputFormat(stream) &&
|
||||
!ecam_encoder_context_init(stream))
|
||||
{
|
||||
WLog_ERR(TAG, "stream_ecam_encoder_init failed");
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_UnexpectedError);
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
stream->sampleRespBuffer = Stream_New(NULL, ECAM_SAMPLE_RESPONSE_BUFFER_SIZE);
|
||||
if (!stream->sampleRespBuffer)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed");
|
||||
ecam_dev_stop_stream(dev, streamIndex);
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_OutOfMemory);
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
/* replacing outputFormat with inputFormat in mediaType before starting stream */
|
||||
mediaType.Format = streamInputFormat(stream);
|
||||
|
||||
stream->nSampleCredits = 0;
|
||||
|
||||
UINT error = dev->ihal->StartStream(dev->ihal, dev, streamIndex, &mediaType,
|
||||
ecam_dev_sample_captured_callback);
|
||||
if (error)
|
||||
{
|
||||
WLog_ERR(TAG, "StartStream failure");
|
||||
ecam_dev_stop_stream(dev, streamIndex);
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, error);
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
stream->streaming = TRUE;
|
||||
return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_process_property_list_request(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s)
|
||||
{
|
||||
WINPR_ASSERT(dev);
|
||||
// TODO: supported properties implementation
|
||||
|
||||
return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_PropertyListResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_send_current_media_type_response(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
CAM_MEDIA_TYPE_DESCRIPTION* mediaType)
|
||||
{
|
||||
CAM_MSG_ID msg = CAM_MSG_ID_CurrentMediaTypeResponse;
|
||||
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
wStream* s = Stream_New(NULL, CAM_HEADER_SIZE + sizeof(CAM_MEDIA_TYPE_DESCRIPTION));
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed");
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
Stream_Write_UINT8(s, dev->ecam->version);
|
||||
Stream_Write_UINT8(s, msg);
|
||||
|
||||
ecam_dev_write_media_type(s, mediaType);
|
||||
|
||||
return ecam_channel_write(dev->ecam, hchannel, msg, s, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_process_sample_request(CameraDevice* dev, GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
wStream* s)
|
||||
{
|
||||
BYTE streamIndex = 0;
|
||||
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
Stream_Read_UINT8(s, streamIndex);
|
||||
|
||||
if (streamIndex >= ECAM_DEVICE_MAX_STREAMS)
|
||||
{
|
||||
WLog_ERR(TAG, "Incorrect streamIndex %d", streamIndex);
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidStreamNumber);
|
||||
return ERROR_INVALID_INDEX;
|
||||
}
|
||||
|
||||
CameraDeviceStream* stream = &dev->streams[streamIndex];
|
||||
|
||||
/* need to save channel because responses are asynchronous and coming from capture thread */
|
||||
if (stream->hSampleReqChannel != hchannel)
|
||||
stream->hSampleReqChannel = hchannel;
|
||||
|
||||
/* allow to send that many unsolicited samples */
|
||||
stream->nSampleCredits = ECAM_MAX_SAMPLE_CREDITS;
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_process_current_media_type_request(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
wStream* s)
|
||||
{
|
||||
BYTE streamIndex = 0;
|
||||
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
Stream_Read_UINT8(s, streamIndex);
|
||||
|
||||
if (streamIndex >= ECAM_DEVICE_MAX_STREAMS)
|
||||
{
|
||||
WLog_ERR(TAG, "Incorrect streamIndex %d", streamIndex);
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidStreamNumber);
|
||||
return ERROR_INVALID_INDEX;
|
||||
}
|
||||
|
||||
CameraDeviceStream* stream = &dev->streams[streamIndex];
|
||||
|
||||
if (stream->currMediaType.Format == 0)
|
||||
{
|
||||
WLog_ERR(TAG, "Current media type unknown for streamIndex %d", streamIndex);
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_NotInitialized);
|
||||
return ERROR_DEVICE_REINITIALIZATION_NEEDED;
|
||||
}
|
||||
|
||||
return ecam_dev_send_current_media_type_response(dev, hchannel, &stream->currMediaType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_send_media_type_list_response(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes,
|
||||
size_t nMediaTypes)
|
||||
{
|
||||
CAM_MSG_ID msg = CAM_MSG_ID_MediaTypeListResponse;
|
||||
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
wStream* s = Stream_New(NULL, CAM_HEADER_SIZE + ECAM_MAX_MEDIA_TYPE_DESCRIPTORS *
|
||||
sizeof(CAM_MEDIA_TYPE_DESCRIPTION));
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed");
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
Stream_Write_UINT8(s, dev->ecam->version);
|
||||
Stream_Write_UINT8(s, msg);
|
||||
|
||||
for (size_t i = 0; i < nMediaTypes; i++, mediaTypes++)
|
||||
{
|
||||
ecam_dev_write_media_type(s, mediaTypes);
|
||||
}
|
||||
|
||||
return ecam_channel_write(dev->ecam, hchannel, msg, s, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_process_media_type_list_request(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s)
|
||||
{
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
BYTE streamIndex = 0;
|
||||
CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes = NULL;
|
||||
size_t nMediaTypes = ECAM_MAX_MEDIA_TYPE_DESCRIPTORS;
|
||||
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
Stream_Read_UINT8(s, streamIndex);
|
||||
|
||||
if (streamIndex >= ECAM_DEVICE_MAX_STREAMS)
|
||||
{
|
||||
WLog_ERR(TAG, "Incorrect streamIndex %d", streamIndex);
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidStreamNumber);
|
||||
return ERROR_INVALID_INDEX;
|
||||
}
|
||||
CameraDeviceStream* stream = &dev->streams[streamIndex];
|
||||
|
||||
mediaTypes =
|
||||
(CAM_MEDIA_TYPE_DESCRIPTION*)calloc(nMediaTypes, sizeof(CAM_MEDIA_TYPE_DESCRIPTION));
|
||||
if (!mediaTypes)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed");
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_OutOfMemory);
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
INT16 formatIndex =
|
||||
dev->ihal->GetMediaTypeDescriptions(dev->ihal, dev->deviceId, streamIndex, supportedFormats,
|
||||
nSupportedFormats, mediaTypes, &nMediaTypes);
|
||||
if (formatIndex == -1 || nMediaTypes == 0)
|
||||
{
|
||||
WLog_ERR(TAG, "Camera doesn't support any compatible video formats");
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_ItemNotFound);
|
||||
error = ERROR_DEVICE_FEATURE_NOT_SUPPORTED;
|
||||
goto error;
|
||||
}
|
||||
|
||||
stream->formats = supportedFormats[formatIndex];
|
||||
|
||||
/* replacing inputFormat with outputFormat in mediaTypes before sending response */
|
||||
for (int i = 0; i < nMediaTypes; i++)
|
||||
{
|
||||
mediaTypes[i].Format = streamOutputFormat(stream);
|
||||
mediaTypes[i].Flags = CAM_MEDIA_TYPE_DESCRIPTION_FLAG_DecodingRequired;
|
||||
}
|
||||
|
||||
if (stream->currMediaType.Format == 0)
|
||||
{
|
||||
/* saving 1st media type description for CurrentMediaTypeRequest */
|
||||
stream->currMediaType = mediaTypes[0];
|
||||
}
|
||||
|
||||
error = ecam_dev_send_media_type_list_response(dev, hchannel, mediaTypes, nMediaTypes);
|
||||
|
||||
error:
|
||||
free(mediaTypes);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_send_stream_list_response(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel)
|
||||
{
|
||||
CAM_MSG_ID msg = CAM_MSG_ID_StreamListResponse;
|
||||
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
wStream* s = Stream_New(NULL, CAM_HEADER_SIZE + sizeof(CAM_STREAM_DESCRIPTION));
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed");
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
Stream_Write_UINT8(s, dev->ecam->version);
|
||||
Stream_Write_UINT8(s, msg);
|
||||
|
||||
/* single stream description */
|
||||
Stream_Write_UINT16(s, CAM_STREAM_FRAME_SOURCE_TYPE_Color);
|
||||
Stream_Write_UINT8(s, CAM_STREAM_CATEGORY_Capture);
|
||||
Stream_Write_UINT8(s, TRUE /* Selected */);
|
||||
Stream_Write_UINT8(s, FALSE /* CanBeShared */);
|
||||
|
||||
return ecam_channel_write(dev->ecam, hchannel, msg, s, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_process_stream_list_request(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s)
|
||||
{
|
||||
return ecam_dev_send_stream_list_response(dev, hchannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_process_activate_device_request(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s)
|
||||
{
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
/* TODO: TBD if this is required */
|
||||
return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_process_deactivate_device_request(CameraDevice* dev,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel,
|
||||
wStream* s)
|
||||
{
|
||||
WINPR_ASSERT(dev);
|
||||
WINPR_UNUSED(s);
|
||||
|
||||
for (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++)
|
||||
ecam_dev_stop_stream(dev, i);
|
||||
|
||||
return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
|
||||
{
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
BYTE version = 0;
|
||||
BYTE messageId = 0;
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
|
||||
|
||||
if (!hchannel || !data)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
CameraDevice* dev = (CameraDevice*)hchannel->plugin;
|
||||
|
||||
if (!dev)
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
|
||||
if (!Stream_CheckAndLogRequiredCapacity(TAG, data, CAM_HEADER_SIZE))
|
||||
return ERROR_NO_DATA;
|
||||
|
||||
Stream_Read_UINT8(data, version);
|
||||
Stream_Read_UINT8(data, messageId);
|
||||
WLog_DBG(TAG, "ChannelId=%d, MessageId=0x%02" PRIx8 ", Version=%d",
|
||||
hchannel->channel_mgr->GetChannelId(hchannel->channel), messageId, version);
|
||||
|
||||
switch (messageId)
|
||||
{
|
||||
case CAM_MSG_ID_ActivateDeviceRequest:
|
||||
error = ecam_dev_process_activate_device_request(dev, hchannel, data);
|
||||
break;
|
||||
|
||||
case CAM_MSG_ID_DeactivateDeviceRequest:
|
||||
error = ecam_dev_process_deactivate_device_request(dev, hchannel, data);
|
||||
break;
|
||||
|
||||
case CAM_MSG_ID_StreamListRequest:
|
||||
error = ecam_dev_process_stream_list_request(dev, hchannel, data);
|
||||
break;
|
||||
|
||||
case CAM_MSG_ID_MediaTypeListRequest:
|
||||
error = ecam_dev_process_media_type_list_request(dev, hchannel, data);
|
||||
break;
|
||||
|
||||
case CAM_MSG_ID_CurrentMediaTypeRequest:
|
||||
error = ecam_dev_process_current_media_type_request(dev, hchannel, data);
|
||||
break;
|
||||
|
||||
case CAM_MSG_ID_PropertyListRequest:
|
||||
error = ecam_dev_process_property_list_request(dev, hchannel, data);
|
||||
break;
|
||||
|
||||
case CAM_MSG_ID_StartStreamsRequest:
|
||||
error = ecam_dev_process_start_streams_request(dev, hchannel, data);
|
||||
break;
|
||||
|
||||
case CAM_MSG_ID_StopStreamsRequest:
|
||||
error = ecam_dev_process_stop_streams_request(dev, hchannel, data);
|
||||
break;
|
||||
|
||||
case CAM_MSG_ID_SampleRequest:
|
||||
error = ecam_dev_process_sample_request(dev, hchannel, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
WLog_WARN(TAG, "unknown MessageId=0x%02" PRIx8 "", messageId);
|
||||
error = ERROR_INVALID_DATA;
|
||||
ecam_channel_send_error_response(dev->ecam, hchannel,
|
||||
CAM_ERROR_CODE_OperationNotSupported);
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_on_open(IWTSVirtualChannelCallback* pChannelCallback)
|
||||
{
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
|
||||
WINPR_ASSERT(hchannel);
|
||||
|
||||
CameraDevice* dev = (CameraDevice*)hchannel->plugin;
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
WLog_DBG(TAG, "entered");
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_on_close(IWTSVirtualChannelCallback* pChannelCallback)
|
||||
{
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
|
||||
WINPR_ASSERT(hchannel);
|
||||
|
||||
CameraDevice* dev = (CameraDevice*)hchannel->plugin;
|
||||
WINPR_ASSERT(dev);
|
||||
|
||||
WLog_DBG(TAG, "entered");
|
||||
|
||||
/* make sure this channel is not used for sample responses */
|
||||
for (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++)
|
||||
if (dev->streams[i].hSampleReqChannel == hchannel)
|
||||
dev->streams[i].hSampleReqChannel = NULL;
|
||||
|
||||
free(hchannel);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ecam_dev_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
|
||||
IWTSVirtualChannel* pChannel, BYTE* Data,
|
||||
BOOL* pbAccept,
|
||||
IWTSVirtualChannelCallback** ppCallback)
|
||||
{
|
||||
GENERIC_LISTENER_CALLBACK* hlistener = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
|
||||
|
||||
if (!hlistener || !hlistener->plugin)
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
|
||||
WLog_DBG(TAG, "entered");
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel =
|
||||
(GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK));
|
||||
|
||||
if (!hchannel)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
hchannel->iface.OnDataReceived = ecam_dev_on_data_received;
|
||||
hchannel->iface.OnOpen = ecam_dev_on_open;
|
||||
hchannel->iface.OnClose = ecam_dev_on_close;
|
||||
hchannel->plugin = hlistener->plugin;
|
||||
hchannel->channel_mgr = hlistener->channel_mgr;
|
||||
hchannel->channel = pChannel;
|
||||
*ppCallback = (IWTSVirtualChannelCallback*)hchannel;
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return CameraDevice pointer or NULL in case of error
|
||||
*/
|
||||
CameraDevice* ecam_dev_create(CameraPlugin* ecam, const char* deviceId, const char* deviceName)
|
||||
{
|
||||
WINPR_ASSERT(ecam);
|
||||
WINPR_ASSERT(ecam->hlistener);
|
||||
|
||||
IWTSVirtualChannelManager* pChannelMgr = ecam->hlistener->channel_mgr;
|
||||
WINPR_ASSERT(pChannelMgr);
|
||||
|
||||
WLog_DBG(TAG, "entered for %s", deviceId);
|
||||
|
||||
CameraDevice* dev = (CameraDevice*)calloc(1, sizeof(CameraDevice));
|
||||
|
||||
if (!dev)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dev->ecam = ecam;
|
||||
dev->ihal = ecam->ihal;
|
||||
strncpy(dev->deviceId, deviceId, sizeof(dev->deviceId) - 1);
|
||||
dev->hlistener = (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
|
||||
|
||||
if (!dev->hlistener)
|
||||
{
|
||||
free(dev);
|
||||
WLog_ERR(TAG, "calloc failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dev->hlistener->iface.OnNewChannelConnection = ecam_dev_on_new_channel_connection;
|
||||
dev->hlistener->plugin = (IWTSPlugin*)dev;
|
||||
dev->hlistener->channel_mgr = pChannelMgr;
|
||||
if (CHANNEL_RC_OK != pChannelMgr->CreateListener(pChannelMgr, deviceId, 0,
|
||||
&dev->hlistener->iface, &dev->listener))
|
||||
{
|
||||
free(dev->hlistener);
|
||||
free(dev);
|
||||
WLog_ERR(TAG, "CreateListener failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* OBJECT_FREE_FN for devices hash table value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ecam_dev_destroy(void* obj)
|
||||
{
|
||||
CameraDevice* dev = (CameraDevice*)obj;
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
WLog_DBG(TAG, "entered for %s", dev->deviceId);
|
||||
|
||||
if (dev->hlistener)
|
||||
{
|
||||
IWTSVirtualChannelManager* mgr = dev->hlistener->channel_mgr;
|
||||
if (mgr)
|
||||
IFCALL(mgr->DestroyListener, mgr, dev->listener);
|
||||
}
|
||||
|
||||
free(dev->hlistener);
|
||||
|
||||
for (int i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++)
|
||||
ecam_dev_stop_stream(dev, i);
|
||||
|
||||
free(dev);
|
||||
return;
|
||||
}
|
242
channels/rdpecam/client/encoding.c
Normal file
242
channels/rdpecam/client/encoding.c
Normal file
@ -0,0 +1,242 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* MS-RDPECAM Implementation, Video Encoding
|
||||
*
|
||||
* 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 <winpr/assert.h>
|
||||
|
||||
#include "camera.h"
|
||||
|
||||
#define TAG CHANNELS_TAG("rdpecam-video.client")
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return enum AVPixelFormat value
|
||||
*/
|
||||
static enum AVPixelFormat ecamToAVPixFormat(CAM_MEDIA_FORMAT ecamFormat)
|
||||
{
|
||||
switch (ecamFormat)
|
||||
{
|
||||
case CAM_MEDIA_FORMAT_YUY2:
|
||||
return AV_PIX_FMT_YUYV422;
|
||||
case CAM_MEDIA_FORMAT_NV12:
|
||||
return AV_PIX_FMT_NV12;
|
||||
case CAM_MEDIA_FORMAT_I420:
|
||||
return AV_PIX_FMT_YUV420P;
|
||||
case CAM_MEDIA_FORMAT_RGB24:
|
||||
return AV_PIX_FMT_RGB24;
|
||||
case CAM_MEDIA_FORMAT_RGB32:
|
||||
return AV_PIX_FMT_RGB32;
|
||||
default:
|
||||
WLog_ERR(TAG, "Unsupported ecamFormat %d", ecamFormat);
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return success/failure
|
||||
*/
|
||||
static BOOL ecam_encoder_compress_h264(CameraDeviceStream* stream, const BYTE* srcData,
|
||||
size_t srcSize, BYTE** ppDstData, size_t* pDstSize)
|
||||
{
|
||||
UINT32 dstSize = 0;
|
||||
BYTE* srcSlice[4] = { 0 };
|
||||
BYTE* yuv420pData[3] = { 0 };
|
||||
UINT32 yuv420pStride[3] = { 0 };
|
||||
prim_size_t size = { 0 };
|
||||
size.width = stream->currMediaType.Width;
|
||||
size.height = stream->currMediaType.Height;
|
||||
CAM_MEDIA_FORMAT inputFormat = streamInputFormat(stream);
|
||||
enum AVPixelFormat pixFormat = ecamToAVPixFormat(inputFormat);
|
||||
|
||||
/* get buffers for YUV420P */
|
||||
if (h264_get_yuv_buffer(stream->h264, stream->srcLineSizes[0], size.width, size.height,
|
||||
yuv420pData, yuv420pStride) < 0)
|
||||
return FALSE;
|
||||
|
||||
/* convert from source format to YUV420P */
|
||||
if (av_image_fill_pointers(srcSlice, pixFormat, (int)size.height, (BYTE*)srcData,
|
||||
stream->srcLineSizes) < 0)
|
||||
return FALSE;
|
||||
|
||||
const BYTE* cSrcSlice[4] = { srcSlice[0], srcSlice[1], srcSlice[2], srcSlice[3] };
|
||||
if (sws_scale(stream->sws, cSrcSlice, stream->srcLineSizes, 0, (int)size.height, yuv420pData,
|
||||
(int*)yuv420pStride) <= 0)
|
||||
return FALSE;
|
||||
|
||||
/* encode from YUV420P to H264 */
|
||||
if (h264_compress(stream->h264, ppDstData, &dstSize) < 0)
|
||||
return FALSE;
|
||||
|
||||
*pDstSize = dstSize;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
*/
|
||||
static void ecam_encoder_context_free_h264(CameraDeviceStream* stream)
|
||||
{
|
||||
WINPR_ASSERT(stream);
|
||||
if (stream->sws)
|
||||
{
|
||||
sws_freeContext(stream->sws);
|
||||
stream->sws = NULL;
|
||||
}
|
||||
|
||||
if (stream->h264)
|
||||
{
|
||||
h264_context_free(stream->h264);
|
||||
stream->h264 = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return success/failure
|
||||
*/
|
||||
static BOOL ecam_encoder_context_init_h264(CameraDeviceStream* stream)
|
||||
{
|
||||
WINPR_ASSERT(stream);
|
||||
if (!stream->h264)
|
||||
stream->h264 = h264_context_new(TRUE);
|
||||
|
||||
if (!stream->h264)
|
||||
{
|
||||
WLog_ERR(TAG, "h264_context_new failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!h264_context_reset(stream->h264, stream->currMediaType.Width,
|
||||
stream->currMediaType.Height))
|
||||
goto fail;
|
||||
|
||||
if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_USAGETYPE,
|
||||
H264_CAMERA_VIDEO_REAL_TIME))
|
||||
goto fail;
|
||||
|
||||
if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_FRAMERATE,
|
||||
stream->currMediaType.FrameRateNumerator))
|
||||
goto fail;
|
||||
|
||||
if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_BITRATE,
|
||||
ECAM_H264_ENCODED_BITRATE))
|
||||
goto fail;
|
||||
|
||||
if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_RATECONTROL,
|
||||
H264_RATECONTROL_VBR))
|
||||
goto fail;
|
||||
|
||||
if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_QP, 0))
|
||||
goto fail;
|
||||
|
||||
/* initialize libswscale */
|
||||
{
|
||||
const int width = (int)stream->currMediaType.Width;
|
||||
const int height = (int)stream->currMediaType.Height;
|
||||
CAM_MEDIA_FORMAT inputFormat = streamInputFormat(stream);
|
||||
enum AVPixelFormat pixFormat = ecamToAVPixFormat(inputFormat);
|
||||
|
||||
if (av_image_fill_linesizes(stream->srcLineSizes, pixFormat, width) < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "av_image_fill_linesizes failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!stream->sws)
|
||||
{
|
||||
stream->sws = sws_getContext(width, height, pixFormat, width, height,
|
||||
AV_PIX_FMT_YUV420P, 0, NULL, NULL, NULL);
|
||||
}
|
||||
if (!stream->sws)
|
||||
{
|
||||
WLog_ERR(TAG, "sws_getContext failed");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
||||
fail:
|
||||
ecam_encoder_context_free_h264(stream);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return success/failure
|
||||
*/
|
||||
BOOL ecam_encoder_context_init(CameraDeviceStream* stream)
|
||||
{
|
||||
CAM_MEDIA_FORMAT format = streamOutputFormat(stream);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case CAM_MEDIA_FORMAT_H264:
|
||||
return ecam_encoder_context_init_h264(stream);
|
||||
|
||||
default:
|
||||
WLog_ERR(TAG, "Unsupported output format %d", format);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return success/failure
|
||||
*/
|
||||
BOOL ecam_encoder_context_free(CameraDeviceStream* stream)
|
||||
{
|
||||
CAM_MEDIA_FORMAT format = streamOutputFormat(stream);
|
||||
switch (format)
|
||||
{
|
||||
case CAM_MEDIA_FORMAT_H264:
|
||||
ecam_encoder_context_free_h264(stream);
|
||||
break;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return success/failure
|
||||
*/
|
||||
BOOL ecam_encoder_compress(CameraDeviceStream* stream, const BYTE* srcData, size_t srcSize,
|
||||
BYTE** ppDstData, size_t* pDstSize)
|
||||
{
|
||||
CAM_MEDIA_FORMAT format = streamOutputFormat(stream);
|
||||
switch (format)
|
||||
{
|
||||
case CAM_MEDIA_FORMAT_H264:
|
||||
return ecam_encoder_compress_h264(stream, srcData, srcSize, ppDstData, pDstSize);
|
||||
default:
|
||||
WLog_ERR(TAG, "Unsupported output format %d", format);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
36
channels/rdpecam/client/v4l/CMakeLists.txt
Normal file
36
channels/rdpecam/client/v4l/CMakeLists.txt
Normal file
@ -0,0 +1,36 @@
|
||||
# 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" "")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
camera_v4l.c
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
${V4L_TARGETS}
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
|
||||
endif()
|
734
channels/rdpecam/client/v4l/camera_v4l.c
Normal file
734
channels/rdpecam/client/v4l/camera_v4l.c
Normal file
@ -0,0 +1,734 @@
|
||||
/**
|
||||
* 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.h"
|
||||
|
||||
#define TAG CHANNELS_TAG("rdpecam-v4l.client")
|
||||
|
||||
#define CAM_V4L2_BUFFERS_COUNT 4
|
||||
#define CAM_V4L2_CAPTURE_THREAD_SLEEP_MS 1000
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void* start;
|
||||
size_t length;
|
||||
|
||||
} CamV4lBuffer;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
CRITICAL_SECTION lock;
|
||||
|
||||
/* members used to call the callback */
|
||||
CameraDevice* dev;
|
||||
int streamIndex;
|
||||
ICamHalSampleCapturedCallback sampleCallback;
|
||||
|
||||
BOOL streaming;
|
||||
int fd;
|
||||
size_t nBuffers;
|
||||
CamV4lBuffer* buffers;
|
||||
|
||||
} CamV4lStream;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ICamHal iHal;
|
||||
|
||||
wHashTable* streams; /* Index: deviceId, Value: CamV4lStream */
|
||||
|
||||
} CamV4lHal;
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return NULL-terminated fourcc string
|
||||
*/
|
||||
static const char* cam_v4l_get_fourcc_str(unsigned int fourcc)
|
||||
{
|
||||
static char buf[5];
|
||||
buf[0] = (fourcc & 0xFF);
|
||||
buf[1] = (fourcc >> 8) & 0xFF;
|
||||
buf[2] = (fourcc >> 16) & 0xFF;
|
||||
buf[3] = (fourcc >> 24) & 0xFF;
|
||||
buf[4] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 %d", ecamFormat);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return file descriptor
|
||||
*/
|
||||
static int cam_v4l_open_device(const char* deviceId, int flags)
|
||||
{
|
||||
UINT n;
|
||||
char device[20];
|
||||
int fd = -1;
|
||||
struct v4l2_capability cap;
|
||||
|
||||
if (!deviceId)
|
||||
return -1;
|
||||
|
||||
if (0 == strncmp(deviceId, "/dev/video", 10))
|
||||
return open(deviceId, flags);
|
||||
|
||||
for (n = 0; n < 64; n++)
|
||||
{
|
||||
snprintf(device, sizeof(device), "/dev/video%d", 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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* hal, const char* deviceId,
|
||||
int streamIndex,
|
||||
const CAM_MEDIA_FORMAT_INFO* supportedFormats,
|
||||
size_t nSupportedFormats,
|
||||
CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes,
|
||||
size_t* nMediaTypes)
|
||||
{
|
||||
int fd;
|
||||
size_t maxMediaTypes = *nMediaTypes;
|
||||
size_t nTypes = 0;
|
||||
int formatIndex;
|
||||
BOOL formatFound = FALSE;
|
||||
struct v4l2_format video_fmt = { 0 };
|
||||
|
||||
video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
video_fmt.fmt.pix.sizeimage = 0;
|
||||
|
||||
unsigned int videoSizes[][2] = { { 160, 90 }, { 160, 120 }, { 320, 180 }, { 320, 240 },
|
||||
{ 432, 240 }, { 352, 288 }, { 640, 360 }, { 800, 448 },
|
||||
{ 640, 480 }, { 848, 480 }, { 864, 480 }, { 960, 540 },
|
||||
{ 1024, 576 }, { 800, 600 }, { 960, 720 }, { 1280, 720 },
|
||||
{ 1024, 768 }, { 1600, 896 }, { 1440, 1080 }, { 1920, 1080 } };
|
||||
const int totalSizes = sizeof(videoSizes) / sizeof(unsigned int[2]);
|
||||
|
||||
if ((fd = cam_v4l_open_device(deviceId, O_RDONLY)) == -1)
|
||||
{
|
||||
WLog_ERR(TAG, "Unable to open device %s", deviceId);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (formatIndex = 0; formatIndex < nSupportedFormats; formatIndex++)
|
||||
{
|
||||
UINT32 pixelFormat = ecamToV4L2PixFormat(supportedFormats[formatIndex].inputFormat);
|
||||
WINPR_ASSERT(pixelFormat != 0);
|
||||
|
||||
for (int i = 0; i < totalSizes; i++)
|
||||
{
|
||||
video_fmt.fmt.pix.pixelformat = pixelFormat;
|
||||
video_fmt.fmt.pix.width = videoSizes[i][0];
|
||||
video_fmt.fmt.pix.height = videoSizes[i][1];
|
||||
|
||||
if (ioctl(fd, VIDIOC_TRY_FMT, &video_fmt) < 0 ||
|
||||
video_fmt.fmt.pix.pixelformat != pixelFormat ||
|
||||
video_fmt.fmt.pix.width != videoSizes[i][0] ||
|
||||
video_fmt.fmt.pix.height != videoSizes[i][1])
|
||||
continue;
|
||||
|
||||
formatFound = TRUE;
|
||||
mediaTypes->Width = video_fmt.fmt.pix.width;
|
||||
mediaTypes->Height = video_fmt.fmt.pix.height;
|
||||
mediaTypes->Format = supportedFormats[formatIndex].inputFormat;
|
||||
/* V4l2 does not have a stable method of knowing fps so we use 30 */
|
||||
mediaTypes->FrameRateNumerator = 30;
|
||||
mediaTypes->FrameRateDenominator = 1;
|
||||
mediaTypes->PixelAspectRatioNumerator = mediaTypes->PixelAspectRatioDenominator = 1;
|
||||
|
||||
WLog_DBG(
|
||||
TAG, "Camera capability %d: width: %d, height: %d, fourcc: %s, type: %d, fps: %d",
|
||||
nTypes, mediaTypes->Width, mediaTypes->Height, cam_v4l_get_fourcc_str(pixelFormat),
|
||||
mediaTypes->Format, mediaTypes->FrameRateNumerator);
|
||||
|
||||
mediaTypes++;
|
||||
nTypes++;
|
||||
|
||||
if (nTypes == maxMediaTypes)
|
||||
{
|
||||
WLog_ERR(TAG, "Media types reached buffer maximum %" PRIu32 "", maxMediaTypes);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (formatFound)
|
||||
{
|
||||
/* we are interested in 1st supported format only, with all supported sizes */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
error:
|
||||
|
||||
*nMediaTypes = nTypes;
|
||||
close(fd);
|
||||
return formatIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return number of video capture devices
|
||||
*/
|
||||
static UINT cam_v4l_enumerate(ICamHal* ihal, ICamHalEnumCallback callback, CameraPlugin* ecam,
|
||||
GENERIC_CHANNEL_CALLBACK* hchannel)
|
||||
{
|
||||
UINT n, count = 0;
|
||||
char device[20];
|
||||
int fd = -1;
|
||||
struct v4l2_capability cap;
|
||||
char *deviceName, *deviceId;
|
||||
|
||||
for (n = 0; n < 64; n++)
|
||||
{
|
||||
snprintf(device, sizeof(device), "/dev/video%d", n);
|
||||
if ((fd = open(device, O_RDONLY)) == -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++;
|
||||
|
||||
deviceName = (char*)cap.card;
|
||||
|
||||
if (cap.bus_info[0] != 0) /* may not be available in all drivers */
|
||||
deviceId = (char*)cap.bus_info;
|
||||
else
|
||||
deviceId = device;
|
||||
|
||||
IFCALL(callback, ecam, hchannel, deviceId, deviceName);
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
static void cam_v4l_stream_free_buffers(CamV4lStream* stream)
|
||||
{
|
||||
if (!stream || !stream->buffers)
|
||||
return;
|
||||
|
||||
/* unmap buffers */
|
||||
for (int 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 = NULL;
|
||||
stream->nBuffers = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on failure, otherwise allocated buffer size
|
||||
*/
|
||||
static size_t cam_v4l_stream_alloc_buffers(CamV4lStream* stream)
|
||||
{
|
||||
struct v4l2_requestbuffers rbuffer = { 0 };
|
||||
|
||||
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)
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in VIDIOC_REQBUFS, errno %d, count %d", 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 buffer = { 0 };
|
||||
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buffer.memory = V4L2_MEMORY_MMAP;
|
||||
buffer.index = i;
|
||||
|
||||
if (ioctl(stream->fd, VIDIOC_QUERYBUF, &buffer) < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in VIDIOC_QUERYBUF, errno %d", errno);
|
||||
cam_v4l_stream_free_buffers(stream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream->buffers[i].start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
stream->fd, buffer.m.offset);
|
||||
|
||||
if (MAP_FAILED == stream->buffers[i].start)
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in mmap, errno %d", errno);
|
||||
cam_v4l_stream_free_buffers(stream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream->buffers[i].length = buffer.length;
|
||||
|
||||
WLog_DBG(TAG, "Buffer %d mapped, size: %d", i, buffer.length);
|
||||
|
||||
if (ioctl(stream->fd, VIDIOC_QBUF, &buffer) < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in VIDIOC_QBUF, errno %d", 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 UINT cam_v4l_stream_capture_thread(void* param)
|
||||
{
|
||||
CamV4lStream* stream = (CamV4lStream*)param;
|
||||
|
||||
int fd = stream->fd;
|
||||
|
||||
do
|
||||
{
|
||||
int retVal;
|
||||
struct pollfd pfd = { 0 };
|
||||
|
||||
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)
|
||||
{
|
||||
WLog_DBG(TAG, "Failure in poll, errno %d", 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 = { 0 };
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
/* dequeue buffers until empty */
|
||||
while (ioctl(fd, VIDIOC_DQBUF, &buf) != -1)
|
||||
{
|
||||
stream->sampleCallback(stream->dev, stream->streamIndex,
|
||||
stream->buffers[buf.index].start, buf.bytesused);
|
||||
|
||||
/* enqueue buffer back */
|
||||
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in VIDIOC_QBUF, errno %d", errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
LeaveCriticalSection(&stream->lock);
|
||||
|
||||
} while (stream->streaming);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
static 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
|
||||
*/
|
||||
static CamV4lStream* cam_v4l_stream_create(CameraDevice* dev, int streamIndex,
|
||||
ICamHalSampleCapturedCallback callback)
|
||||
{
|
||||
CamV4lStream* stream = calloc(1, sizeof(CamV4lStream));
|
||||
|
||||
if (!stream)
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in calloc");
|
||||
return NULL;
|
||||
}
|
||||
stream->dev = dev;
|
||||
stream->streamIndex = streamIndex;
|
||||
stream->sampleCallback = callback;
|
||||
|
||||
stream->fd = -1;
|
||||
|
||||
if (!InitializeCriticalSectionEx(&stream->lock, 0, 0))
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in calloc");
|
||||
free(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT cam_v4l_stream_stop(CamV4lStream* stream)
|
||||
{
|
||||
if (!stream || !stream->streaming)
|
||||
return CHANNEL_RC_OK;
|
||||
|
||||
stream->streaming = FALSE; /* this will terminate capture thread */
|
||||
|
||||
EnterCriticalSection(&stream->lock);
|
||||
|
||||
/* stop streaming */
|
||||
enum v4l2_buf_type type;
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (ioctl(stream->fd, VIDIOC_STREAMOFF, &type) < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in VIDIOC_STREAMOFF, errno %d", errno);
|
||||
}
|
||||
|
||||
cam_v4l_stream_free_buffers(stream);
|
||||
cam_v4l_stream_close_device(stream);
|
||||
|
||||
LeaveCriticalSection(&stream->lock);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise CAM_ERROR_CODE
|
||||
*/
|
||||
static UINT cam_v4l_stream_start(ICamHal* ihal, CameraDevice* dev, int streamIndex,
|
||||
const CAM_MEDIA_TYPE_DESCRIPTION* mediaType,
|
||||
ICamHalSampleCapturedCallback callback)
|
||||
{
|
||||
CamV4lHal* hal = (CamV4lHal*)ihal;
|
||||
|
||||
CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, dev->deviceId);
|
||||
|
||||
if (!stream)
|
||||
{
|
||||
stream = cam_v4l_stream_create(dev, streamIndex, callback);
|
||||
if (!stream)
|
||||
return CAM_ERROR_CODE_OutOfMemory;
|
||||
|
||||
HashTable_Insert(hal->streams, dev->deviceId, stream);
|
||||
}
|
||||
|
||||
if (stream->streaming)
|
||||
{
|
||||
WLog_ERR(TAG, "Streaming already in progress, device %s, streamIndex %d", dev->deviceId,
|
||||
streamIndex);
|
||||
return CAM_ERROR_CODE_UnexpectedError;
|
||||
}
|
||||
|
||||
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 = { 0 };
|
||||
video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
video_fmt.fmt.pix.sizeimage = 0;
|
||||
video_fmt.fmt.pix.width = mediaType->Width;
|
||||
video_fmt.fmt.pix.height = mediaType->Height;
|
||||
UINT32 pixelFormat = ecamToV4L2PixFormat(mediaType->Format);
|
||||
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)
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in VIDIOC_S_FMT, errno %d", 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 = { 0 }, sp2 = { 0 };
|
||||
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)
|
||||
{
|
||||
WLog_INFO(TAG, "Failed to set the framerate, errno %d", 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;
|
||||
}
|
||||
|
||||
HANDLE captureThread = CreateThread(NULL, 0, cam_v4l_stream_capture_thread, stream, 0, NULL);
|
||||
if (!captureThread)
|
||||
{
|
||||
WLog_ERR(TAG, "CreateThread failure");
|
||||
cam_v4l_stream_free_buffers(stream);
|
||||
cam_v4l_stream_close_device(stream);
|
||||
return CAM_ERROR_CODE_OutOfMemory;
|
||||
}
|
||||
|
||||
stream->streaming = TRUE;
|
||||
|
||||
/* start streaming */
|
||||
enum v4l2_buf_type type;
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (ioctl(stream->fd, VIDIOC_STREAMON, &type) < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "Failure in VIDIOC_STREAMON, errno %d", errno);
|
||||
cam_v4l_stream_stop(stream);
|
||||
return CAM_ERROR_CODE_UnexpectedError;
|
||||
}
|
||||
|
||||
WLog_INFO(TAG, "Camera format: %s, width: %d, height: %d, fps: %d",
|
||||
cam_v4l_get_fourcc_str(pixelFormat), mediaType->Width, mediaType->Height,
|
||||
mediaType->FrameRateNumerator);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT cam_v4l_stream_stop_by_device_id(ICamHal* ihal, const char* deviceId, int streamIndex)
|
||||
{
|
||||
CamV4lHal* hal = (CamV4lHal*)ihal;
|
||||
|
||||
CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, deviceId);
|
||||
|
||||
if (!stream)
|
||||
return CHANNEL_RC_OK;
|
||||
|
||||
return cam_v4l_stream_stop(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* OBJECT_FREE_FN for streams hash table value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
static void cam_v4l_stream_free(void* obj)
|
||||
{
|
||||
CamV4lStream* stream = (CamV4lStream*)obj;
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
cam_v4l_stream_stop(stream);
|
||||
|
||||
DeleteCriticalSection(&stream->lock);
|
||||
free(stream);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT cam_v4l_free(ICamHal* ihal)
|
||||
{
|
||||
CamV4lHal* hal = (CamV4lHal*)ihal;
|
||||
|
||||
if (hal == NULL)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
HashTable_Free(hal->streams);
|
||||
|
||||
free(hal);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(
|
||||
UINT 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 == NULL)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
hal->iHal.Enumerate = cam_v4l_enumerate;
|
||||
hal->iHal.GetMediaTypeDescriptions = cam_v4l_get_media_type_descriptions;
|
||||
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)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
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;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
|
||||
set(OPTION_DEFAULT OFF)
|
||||
set(OPTION_CLIENT_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT OFF)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
|
||||
define_channel_options(NAME "rdpei" TYPE "dynamic"
|
||||
DESCRIPTION "Input Virtual Channel Extension"
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
set(OPTION_DEFAULT OFF)
|
||||
set(OPTION_CLIENT_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT OFF)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
|
||||
define_channel_options(NAME "rdpgfx" TYPE "dynamic"
|
||||
DESCRIPTION "Graphics Pipeline Extension"
|
||||
|
9
checker/abi-suppr.txt
Normal file
9
checker/abi-suppr.txt
Normal file
@ -0,0 +1,9 @@
|
||||
# settings are opaque, ignore all changes
|
||||
[suppress_type]
|
||||
type_kind = struct
|
||||
name = rdp_settings
|
||||
|
||||
# allow insertions at end of structs
|
||||
[suppress_type]
|
||||
type_kind = struct
|
||||
has_data_members_inserted_at = end
|
282
checker/check-abi
Executable file
282
checker/check-abi
Executable file
@ -0,0 +1,282 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Copyright 2019 Mathieu Bridon
|
||||
# Copyright 2021 Bastien Nocera
|
||||
|
||||
"""check-abi"""
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
VERSION = '0.1'
|
||||
|
||||
def format_title(title):
|
||||
"""Put title in a box"""
|
||||
box = {
|
||||
'tl': '╔', 'tr': '╗', 'bl': '╚', 'br': '╝', 'h': '═', 'v': '║',
|
||||
}
|
||||
hline = box['h'] * (len(title) + 2)
|
||||
|
||||
return '\n'.join([
|
||||
f"{box['tl']}{hline}{box['tr']}",
|
||||
f"{box['v']} {title} {box['v']}",
|
||||
f"{box['bl']}{hline}{box['br']}",
|
||||
])
|
||||
|
||||
|
||||
def rm_rf(path):
|
||||
"""rm -rf"""
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def sanitize_path(name):
|
||||
"""Replace slashes with dashes"""
|
||||
return name.replace('/', '-')
|
||||
|
||||
|
||||
def get_current_revision():
|
||||
"""gets the current git revision"""
|
||||
revision = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||
encoding='utf-8').strip()
|
||||
|
||||
if revision == 'HEAD':
|
||||
# This is a detached HEAD, get the commit hash
|
||||
revision = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8')
|
||||
|
||||
return revision
|
||||
|
||||
|
||||
def get_meson_version():
|
||||
"""Retrieves the version of Meson as a tuple"""
|
||||
res = subprocess.check_output(['meson', '--version']).strip().decode('utf-8')
|
||||
return [int(x) for x in res.split('.')]
|
||||
|
||||
|
||||
def build_meson(build_dir, parameters, env):
|
||||
"""Builds using Meson"""
|
||||
_setup_args = ['meson', 'setup', build_dir, '--prefix=/usr', '--libdir=lib']
|
||||
meson_version = get_meson_version()
|
||||
if meson_version[0] > 0 or (meson_version[0] == 0 and meson_version[1] >= 54):
|
||||
_compile_args = ['meson', 'compile', '-v', '-C', build_dir]
|
||||
else:
|
||||
_compile_args = ['ninja', '-v', '-C', build_dir]
|
||||
if meson_version[0] > 0 or (meson_version[0] == 0 and meson_version[1] >= 47):
|
||||
_install_args = ['meson', 'install', '-C', build_dir]
|
||||
else:
|
||||
_install_args = ['ninja', '-v', '-C', build_dir, 'install']
|
||||
if parameters is not None:
|
||||
_setup_args += parameters.split(' ')
|
||||
subprocess.check_call(_setup_args)
|
||||
subprocess.check_call(_compile_args)
|
||||
subprocess.check_call(_install_args, env=env)
|
||||
|
||||
|
||||
def get_cmake_version():
|
||||
"""Retrieves the version of CMake as a tuple"""
|
||||
res = subprocess.check_output(['cmake', '--version']).strip().decode('utf-8')
|
||||
lines = res.split('\n')
|
||||
ver = lines[0].replace('cmake version ', '')
|
||||
return [int(x) for x in ver.split('.')]
|
||||
|
||||
|
||||
def build_cmake(build_dir, parameters, env):
|
||||
"""Builds using CMake"""
|
||||
_setup_args = [
|
||||
"cmake",
|
||||
"-GNinja",
|
||||
"-B",
|
||||
build_dir,
|
||||
"-S",
|
||||
".",
|
||||
"-DCMAKE_INSTALL_PREFIX=/usr",
|
||||
"-DCMAKE_INSTALL_LIBDIR=lib",
|
||||
]
|
||||
_compile_args = ['cmake', '--build', build_dir]
|
||||
_install_args = ['cmake', '--install', build_dir]
|
||||
if parameters is not None:
|
||||
_setup_args += parameters.split(' ')
|
||||
subprocess.check_call(_setup_args)
|
||||
subprocess.check_call(_compile_args)
|
||||
subprocess.check_call(_install_args, env=env)
|
||||
|
||||
|
||||
def build_autotools(revision, src_dir_copy, build_dir, parameters, env):
|
||||
"""Builds using Autotools"""
|
||||
src_dir = os.getcwd()
|
||||
|
||||
# Setup src dir copy
|
||||
os.mkdir(src_dir_copy)
|
||||
os.chdir(src_dir_copy)
|
||||
subprocess.check_call(['git', 'clone', src_dir, '.'])
|
||||
subprocess.check_call(['git', 'checkout', '-q', revision])
|
||||
subprocess.check_call(['sed',
|
||||
'-i',
|
||||
's/AM_GNU_GETTEXT_VERSION.*/AM_GNU_GETTEXT_VERSION([0.21])/',
|
||||
'configure.ac'])
|
||||
os.chdir('..')
|
||||
|
||||
os.mkdir(build_dir)
|
||||
os.chdir(build_dir)
|
||||
autogen_path = src_dir_copy + '/autogen.sh'
|
||||
_args = [autogen_path, '--prefix=/usr', '--libdir=/usr/lib']
|
||||
if parameters is not None:
|
||||
_args += parameters.split(' ')
|
||||
subprocess.check_call(_args)
|
||||
subprocess.check_call(['make'])
|
||||
subprocess.check_call(['make','install'], env=env)
|
||||
os.chdir(src_dir)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def checkout_git_revision(revision):
|
||||
"""Checkout a git revision before reverting to the current one"""
|
||||
current_revision = get_current_revision()
|
||||
subprocess.check_call(['git', 'checkout', '-q', revision])
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
subprocess.check_call(['git', 'checkout', '-q', current_revision])
|
||||
|
||||
|
||||
def build_install(revision, build_system, parameters):
|
||||
"""Build git revision with the passed build system"""
|
||||
build_dir = '_check_abi_build'
|
||||
dest_dir = os.path.abspath(sanitize_path(revision))
|
||||
src_dir_copy = os.getcwd() + '/_src_copy'
|
||||
if not args.quiet:
|
||||
print(format_title(f'# Building and installing {revision} in {dest_dir}'),
|
||||
end='\n\n', flush=True)
|
||||
|
||||
with checkout_git_revision(revision):
|
||||
rm_rf(build_dir)
|
||||
rm_rf(revision)
|
||||
rm_rf(src_dir_copy)
|
||||
|
||||
env = os.environ.copy()
|
||||
env['DESTDIR'] = dest_dir
|
||||
env['V'] = '1'
|
||||
|
||||
if not build_system or build_system == 'autodetect':
|
||||
if os.path.exists('meson.build'):
|
||||
build_system = 'meson'
|
||||
elif os.path.exists('CMakeLists.txt'):
|
||||
build_system = 'cmake'
|
||||
elif os.path.exists('configure.ac') and os.path.exists('autogen.sh'):
|
||||
build_system = 'autotools'
|
||||
else:
|
||||
print('Could not detect build system', file=sys.stderr)
|
||||
assert()
|
||||
|
||||
if build_system == 'meson':
|
||||
build_meson(build_dir, parameters, env)
|
||||
elif build_system == 'cmake':
|
||||
build_cmake(build_dir, parameters, env)
|
||||
elif build_system == 'autotools':
|
||||
build_autotools(revision, src_dir_copy, build_dir, parameters, env)
|
||||
else:
|
||||
print(f'Unsupported build system "f{build_system}"', file=sys.stderr)
|
||||
assert()
|
||||
|
||||
return dest_dir
|
||||
|
||||
|
||||
def compare(_old_tree, _new_tree):
|
||||
"""Compare the ABIs between 2 build trees"""
|
||||
if not args.quiet:
|
||||
print(format_title('# Comparing the two ABIs'), end='\n\n', flush=True)
|
||||
|
||||
if not args.library_name:
|
||||
paths = glob.glob(os.path.join(_old_tree, 'usr', 'lib') + '/*.so')
|
||||
libs_name = []
|
||||
for path in paths:
|
||||
libs_name.append(os.path.basename(path))
|
||||
else:
|
||||
libs_name = [ args.library_name ]
|
||||
|
||||
for lib_name in libs_name:
|
||||
|
||||
old_headers = os.path.join(_old_tree, 'usr', 'include')
|
||||
old_lib = os.path.join(_old_tree, 'usr', 'lib', lib_name)
|
||||
|
||||
new_headers = os.path.join(_new_tree, 'usr', 'include')
|
||||
new_lib = os.path.join(_new_tree, 'usr', 'lib', lib_name)
|
||||
|
||||
cmd = [
|
||||
'abidiff', '--headers-dir1', old_headers, '--headers-dir2', new_headers,
|
||||
'--drop-private-types', '--fail-no-debug-info', '--no-added-syms'
|
||||
]
|
||||
if args.suppr:
|
||||
cmd.append ('--suppr')
|
||||
cmd.append (args.suppr)
|
||||
|
||||
cmd.append (old_lib)
|
||||
cmd.append (new_lib)
|
||||
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('old', help='the previous git revision, considered the reference')
|
||||
parser.add_argument('new', help='the new git revision, to compare to the reference')
|
||||
parser.add_argument('-q', '--quiet', action='store_true', help='increase output verbosity')
|
||||
parser.add_argument('-s', '--suppr', help='Pass a suppression file to abidiff')
|
||||
parser.add_argument('--old-build-system',
|
||||
help='build system to use for the reference git revision (default: autodetect)',
|
||||
type=str, choices=['autodetect', 'meson', 'cmake', 'autotools'])
|
||||
parser.add_argument('--new-build-system',
|
||||
help='build system to use for the new git revision (default: autodetect)',
|
||||
type=str, choices=['autodetect', 'meson', 'cmake', 'autotools'])
|
||||
parser.add_argument('--old-parameters',
|
||||
help='additional arguments to pass to the reference git revisionʼs build system',
|
||||
type=str)
|
||||
parser.add_argument('--new-parameters',
|
||||
help='additional arguments to pass to the new git revisionʼs build system',
|
||||
type=str)
|
||||
parser.add_argument('--parameters',
|
||||
help="additional arguments to pass to both the old and the"
|
||||
" new git revisionʼs build systems",
|
||||
type=str)
|
||||
parser.add_argument('-l', '--library-name',
|
||||
help='the name of the shared library to compare (default: \'*.so\')',
|
||||
type=str)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.parameters:
|
||||
if args.old_parameters or args.new_parameters:
|
||||
if not args.quiet:
|
||||
print("Can't pass --parameters with either --old-parameters"
|
||||
" or --new-parameters")
|
||||
sys.exit(1)
|
||||
args.old_parameters = args.parameters
|
||||
args.new_parameters = args.parameters
|
||||
|
||||
if args.old == args.new and args.old_parameters == args.new_parameters:
|
||||
if not args.quiet:
|
||||
print("Let's not waste time comparing something to itself")
|
||||
sys.exit(0)
|
||||
|
||||
old_tree = build_install(args.old, args.old_build_system, args.old_parameters)
|
||||
new_tree = build_install(args.new, args.new_build_system, args.new_parameters)
|
||||
|
||||
try:
|
||||
compare(old_tree, new_tree)
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
sys.exit(1)
|
||||
|
||||
if not args.quiet:
|
||||
print(f'Hurray! {args.old} and {args.new} are ABI-compatible!')
|
122
cmake/FindV4L.cmake
Normal file
122
cmake/FindV4L.cmake
Normal file
@ -0,0 +1,122 @@
|
||||
############################################################################
|
||||
# FindV4L.cmake
|
||||
# Copyright (C) 2014-2023 Belledonne Communications, Grenoble France
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# Find the v4l library.
|
||||
#
|
||||
# Targets
|
||||
# ^^^^^^^
|
||||
#
|
||||
# The following targets may be defined:
|
||||
#
|
||||
# v4l1 - If the v4l1 library has been found
|
||||
# v4l2 - If the v4l2 library has been found
|
||||
# v4lconvert - If the v4lconvert library has been found
|
||||
#
|
||||
#
|
||||
# Result variables
|
||||
# ^^^^^^^^^^^^^^^^
|
||||
#
|
||||
# This module will set the following variables in your project:
|
||||
#
|
||||
# V4L_FOUND - The v4l library has been found
|
||||
# V4L_TARGETS - The list of the names of the CMake targets for the v4l libraries
|
||||
# V4L_TARGET - The name of the CMake target for the v4l library
|
||||
# V4L_convert_TARGET - The name of the CMake target for the v4lconvert library
|
||||
# HAVE_LINUX_VIDEODEV_H - If the v4l library provides the linux/videodev.h header file
|
||||
# HAVE_LINUX_VIDEODEV2_H - If the v4l library provides the linux/videodev2.h header file
|
||||
|
||||
|
||||
set(_V4L_REQUIRED_VARS V4L_TARGETS V4L_TARGET V4L_convert_TARGET)
|
||||
set(_V4L_CACHE_VARS ${_V4L_REQUIRED_VARS} HAVE_LINUX_VIDEODEV_H HAVE_LINUX_VIDEODEV2_H)
|
||||
|
||||
set(_V4L_ROOT_PATHS ${CMAKE_INSTALL_PREFIX})
|
||||
|
||||
find_path(_V4L1_INCLUDE_DIRS
|
||||
NAMES linux/videodev.h
|
||||
HINTS _V4L_ROOT_PATHS
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
if(_V4L1_INCLUDE_DIRS)
|
||||
set(HAVE_LINUX_VIDEODEV_H 1)
|
||||
endif()
|
||||
find_path(_V4L2_INCLUDE_DIRS
|
||||
NAMES linux/videodev2.h
|
||||
HINTS _V4L_ROOT_PATHS
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
if(_V4L2_INCLUDE_DIRS)
|
||||
set(HAVE_LINUX_VIDEODEV2_H 1)
|
||||
endif()
|
||||
|
||||
if(_V4L1_INCLUDE_DIRS OR _V4L2_INCLUDE_DIRS)
|
||||
find_library(_V4L1_LIBRARY
|
||||
NAMES v4l1
|
||||
HINTS _V4L_ROOT_PATHS
|
||||
PATH_SUFFIXES bin lib
|
||||
)
|
||||
find_library(_V4L2_LIBRARY
|
||||
NAMES v4l2
|
||||
HINTS _V4L_ROOT_PATHS
|
||||
PATH_SUFFIXES bin lib
|
||||
)
|
||||
find_library(_V4LCONVERT_LIBRARY
|
||||
NAMES v4lconvert
|
||||
HINTS _V4L_ROOT_PATHS
|
||||
PATH_SUFFIXES bin lib
|
||||
)
|
||||
endif()
|
||||
|
||||
if(_V4L1_LIBRARY OR _V4L2_LIBRARY OR _V4LCONVERT_LIBRARY)
|
||||
set(V4L_TARGETS )
|
||||
if(_V4L2_LIBRARY)
|
||||
add_library(v4l2 UNKNOWN IMPORTED)
|
||||
set_target_properties(v4l2 PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${_V4L2_INCLUDE_DIRS}"
|
||||
IMPORTED_LOCATION "${_V4L2_LIBRARY}"
|
||||
)
|
||||
set(V4L_TARGET v4l2)
|
||||
list(APPEND V4L_TARGETS v4l2)
|
||||
set(_V4L_INCLUDE_DIRS "${_V4L2_INCLUDE_DIRS}")
|
||||
elseif(_V4L1_LIBRARY)
|
||||
add_library(v4l1 UNKNOWN IMPORTED)
|
||||
set_target_properties(v4l1 PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${_V4L1_INCLUDE_DIRS}"
|
||||
IMPORTED_LOCATION "${_V4L1_LIBRARY}"
|
||||
)
|
||||
set(V4L_TARGET v4l1)
|
||||
list(APPEND V4L_TARGETS v4l1)
|
||||
set(_V4L_INCLUDE_DIRS "${_V4L1_INCLUDE_DIRS}")
|
||||
endif()
|
||||
if(_V4LCONVERT_LIBRARY)
|
||||
add_library(v4lconvert UNKNOWN IMPORTED)
|
||||
set_target_properties(v4lconvert PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${_V4L_INCLUDE_DIRS}"
|
||||
IMPORTED_LOCATION "${_V4LCONVERT_LIBRARY}"
|
||||
)
|
||||
set(V4L_convert_TARGET v4lconvert)
|
||||
list(APPEND V4L_TARGETS v4lconvert)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(V4L REQUIRED_VARS ${_V4L_REQUIRED_VARS})
|
||||
mark_as_advanced(${_V4L_CACHE_VARS})
|
@ -41,12 +41,22 @@ extern "C"
|
||||
H264_RATECONTROL_CQP
|
||||
} H264_RATECONTROL_MODE;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
H264_SCREEN_CONTENT_REAL_TIME = 0,
|
||||
H264_SCREEN_CONTENT_NON_REAL_TIME,
|
||||
H264_CAMERA_VIDEO_REAL_TIME,
|
||||
H264_CAMERA_VIDEO_NON_REAL_TIME,
|
||||
|
||||
} H264_USAGETYPE;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
H264_CONTEXT_OPTION_RATECONTROL,
|
||||
H264_CONTEXT_OPTION_BITRATE,
|
||||
H264_CONTEXT_OPTION_FRAMERATE,
|
||||
H264_CONTEXT_OPTION_QP
|
||||
H264_CONTEXT_OPTION_QP,
|
||||
H264_CONTEXT_OPTION_USAGETYPE,
|
||||
} H264_CONTEXT_OPTION;
|
||||
|
||||
FREERDP_API void free_h264_metablock(RDPGFX_H264_METABLOCK* meta);
|
||||
@ -60,6 +70,11 @@ extern "C"
|
||||
const RECTANGLE_16* regionRect, BYTE** ppDstData,
|
||||
UINT32* pDstSize, RDPGFX_H264_METABLOCK* meta);
|
||||
|
||||
/* API for user to fill YUV I420 buffer before encoding */
|
||||
FREERDP_API INT32 h264_get_yuv_buffer(H264_CONTEXT* h264, UINT32 nSrcStride, UINT32 nSrcWidth,
|
||||
UINT32 nSrcHeight, BYTE* YUVData[3], UINT32 stride[3]);
|
||||
FREERDP_API INT32 h264_compress(H264_CONTEXT* h264, BYTE** ppDstData, UINT32* pDstSize);
|
||||
|
||||
FREERDP_API INT32 avc420_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize,
|
||||
BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep,
|
||||
UINT32 nDstWidth, UINT32 nDstHeight,
|
||||
|
@ -232,6 +232,44 @@ static BOOL detect_changes(BOOL firstFrameDone, const UINT32 QP, const RECTANGLE
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
INT32 h264_get_yuv_buffer(H264_CONTEXT* h264, UINT32 nSrcStride, UINT32 nSrcWidth,
|
||||
UINT32 nSrcHeight, BYTE* YUVData[3], UINT32 stride[3])
|
||||
{
|
||||
if (!h264 || !h264->Compressor || !h264->subsystem || !h264->subsystem->Compress)
|
||||
return -1;
|
||||
|
||||
if (!avc420_ensure_buffer(h264, nSrcStride, nSrcWidth, nSrcHeight))
|
||||
return -1;
|
||||
|
||||
for (size_t x = 0; x < 3; x++)
|
||||
{
|
||||
YUVData[x] = h264->pYUVData[x];
|
||||
stride[x] = h264->iStride[x];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
INT32 h264_compress(H264_CONTEXT* h264, BYTE** ppDstData, UINT32* pDstSize)
|
||||
{
|
||||
if (!h264 || !h264->Compressor || !h264->subsystem || !h264->subsystem->Compress)
|
||||
return -1;
|
||||
|
||||
const BYTE* pcYUVData[3] = { h264->pYUVData[0], h264->pYUVData[1], h264->pYUVData[2] };
|
||||
|
||||
return h264->subsystem->Compress(h264, pcYUVData, h264->iStride, ppDstData, pDstSize);
|
||||
}
|
||||
|
||||
INT32 avc420_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
|
||||
UINT32 nSrcWidth, UINT32 nSrcHeight, const RECTANGLE_16* regionRect,
|
||||
BYTE** ppDstData, UINT32* pDstSize, RDPGFX_H264_METABLOCK* meta)
|
||||
@ -749,6 +787,9 @@ BOOL h264_context_set_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option, UIN
|
||||
case H264_CONTEXT_OPTION_QP:
|
||||
h264->QP = value;
|
||||
return TRUE;
|
||||
case H264_CONTEXT_OPTION_USAGETYPE:
|
||||
h264->UsageType = value;
|
||||
return TRUE;
|
||||
default:
|
||||
WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]",
|
||||
option);
|
||||
@ -769,6 +810,8 @@ UINT32 h264_context_get_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option)
|
||||
return h264->RateControlMode;
|
||||
case H264_CONTEXT_OPTION_QP:
|
||||
return h264->QP;
|
||||
case H264_CONTEXT_OPTION_USAGETYPE:
|
||||
return h264->UsageType;
|
||||
default:
|
||||
WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]",
|
||||
option);
|
||||
|
@ -61,6 +61,7 @@ extern "C"
|
||||
UINT32 BitRate;
|
||||
UINT32 FrameRate;
|
||||
UINT32 QP;
|
||||
UINT32 UsageType;
|
||||
UINT32 NumberOfThreads;
|
||||
|
||||
UINT32 iStride[3];
|
||||
|
@ -228,14 +228,32 @@ static int openh264_compress(H264_CONTEXT* WINPR_RESTRICT h264,
|
||||
return status;
|
||||
}
|
||||
|
||||
sys->EncParamExt.iUsageType = SCREEN_CONTENT_REAL_TIME;
|
||||
EUsageType usageType;
|
||||
|
||||
switch (h264->UsageType)
|
||||
{
|
||||
case H264_CAMERA_VIDEO_NON_REAL_TIME:
|
||||
usageType = CAMERA_VIDEO_NON_REAL_TIME;
|
||||
break;
|
||||
case H264_CAMERA_VIDEO_REAL_TIME:
|
||||
usageType = CAMERA_VIDEO_REAL_TIME;
|
||||
break;
|
||||
case H264_SCREEN_CONTENT_NON_REAL_TIME:
|
||||
usageType = SCREEN_CONTENT_NON_REAL_TIME;
|
||||
break;
|
||||
case H264_SCREEN_CONTENT_REAL_TIME:
|
||||
default:
|
||||
usageType = SCREEN_CONTENT_REAL_TIME;
|
||||
break;
|
||||
}
|
||||
|
||||
sys->EncParamExt.iUsageType = usageType;
|
||||
sys->EncParamExt.iPicWidth = (int)h264->width;
|
||||
sys->EncParamExt.iPicHeight = (int)h264->height;
|
||||
sys->EncParamExt.fMaxFrameRate = (int)h264->FrameRate;
|
||||
sys->EncParamExt.iMaxBitrate = UNSPECIFIED_BIT_RATE;
|
||||
sys->EncParamExt.bEnableDenoise = 0;
|
||||
sys->EncParamExt.bEnableLongTermReference = 0;
|
||||
sys->EncParamExt.bEnableFrameSkip = 0;
|
||||
sys->EncParamExt.iSpatialLayerNum = 1;
|
||||
sys->EncParamExt.iMultipleThreadIdc = (int)h264->NumberOfThreads;
|
||||
sys->EncParamExt.sSpatialLayers[0].fFrameRate = h264->FrameRate;
|
||||
@ -250,11 +268,13 @@ static int openh264_compress(H264_CONTEXT* WINPR_RESTRICT h264,
|
||||
sys->EncParamExt.iTargetBitrate = (int)h264->BitRate;
|
||||
sys->EncParamExt.sSpatialLayers[0].iSpatialBitrate =
|
||||
sys->EncParamExt.iTargetBitrate;
|
||||
sys->EncParamExt.bEnableFrameSkip = 1;
|
||||
break;
|
||||
|
||||
case H264_RATECONTROL_CQP:
|
||||
sys->EncParamExt.iRCMode = RC_OFF_MODE;
|
||||
sys->EncParamExt.sSpatialLayers[0].iDLayerQp = (int)h264->QP;
|
||||
sys->EncParamExt.bEnableFrameSkip = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,7 @@ Build-Depends:
|
||||
libpng-dev,
|
||||
libjpeg-dev,
|
||||
opensc-pkcs11,
|
||||
libv4l-dev,
|
||||
libasan5 | libasan6 | libasan8
|
||||
Standards-Version: 3.9.5
|
||||
Homepage: http://www.freerdp.com/
|
||||
|
@ -51,6 +51,7 @@ BuildRequires: libwebp-devel
|
||||
BuildRequires: fuse3-devel
|
||||
BuildRequires: pam-devel
|
||||
BuildRequires: libicu-devel
|
||||
BuildRequires: libv4l-devel
|
||||
|
||||
# (Open)Suse
|
||||
%if %{defined suse_version}
|
||||
|
Loading…
Reference in New Issue
Block a user