mirror of
https://github.com/morgan9e/FreeRDP
synced 2026-04-15 00:44:19 +09:00
A helper function that does not require return checks, in contrast to Stream_SetPosition, which might fail.
927 lines
27 KiB
C
927 lines
27 KiB
C
/**
|
|
* 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 <winpr/cast.h>
|
|
#include <winpr/interlocked.h>
|
|
|
|
#include "camera.h"
|
|
#include "rdpecam-utils.h"
|
|
|
|
#define TAG CHANNELS_TAG("rdpecam-device.client")
|
|
|
|
/* supported formats in preference order:
|
|
* H264, MJPG, I420 (used as input for H264 encoder), other YUV based, RGB based
|
|
*/
|
|
static const CAM_MEDIA_FORMAT_INFO supportedFormats[] = {
|
|
/* inputFormat, outputFormat */
|
|
#if defined(WITH_INPUT_FORMAT_H264)
|
|
{ CAM_MEDIA_FORMAT_H264, CAM_MEDIA_FORMAT_H264 }, /* passthrough */
|
|
{ CAM_MEDIA_FORMAT_MJPG_H264, CAM_MEDIA_FORMAT_H264 },
|
|
#endif
|
|
#if defined(WITH_INPUT_FORMAT_MJPG)
|
|
{ CAM_MEDIA_FORMAT_MJPG, CAM_MEDIA_FORMAT_H264 },
|
|
{ CAM_MEDIA_FORMAT_MJPG, CAM_MEDIA_FORMAT_MJPG },
|
|
#endif
|
|
{ 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);
|
|
|
|
static void ecam_dev_write_media_type(wStream* s, CAM_MEDIA_TYPE_DESCRIPTION* mediaType)
|
|
{
|
|
WINPR_ASSERT(mediaType);
|
|
|
|
Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, 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, WINPR_ASSERTING_INT_CAST(uint8_t, mediaType->Flags));
|
|
}
|
|
|
|
static BOOL ecam_dev_read_media_type(wStream* s, CAM_MEDIA_TYPE_DESCRIPTION* mediaType)
|
|
{
|
|
WINPR_ASSERT(mediaType);
|
|
|
|
const uint8_t format = Stream_Get_UINT8(s);
|
|
if (!rdpecam_valid_CamMediaFormat(format))
|
|
return FALSE;
|
|
|
|
mediaType->Format = WINPR_ASSERTING_INT_CAST(CAM_MEDIA_FORMAT, 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);
|
|
|
|
const uint8_t flags = Stream_Get_UINT8(s);
|
|
if (!rdpecam_valid_MediaTypeDescriptionFlags(flags))
|
|
return FALSE;
|
|
mediaType->Flags = WINPR_ASSERTING_INT_CAST(CAM_MEDIA_TYPE_DESCRIPTION_FLAGS, flags);
|
|
return TRUE;
|
|
}
|
|
|
|
static void ecam_dev_print_media_type(CAM_MEDIA_TYPE_DESCRIPTION* mediaType)
|
|
{
|
|
WINPR_ASSERT(mediaType);
|
|
|
|
WLog_DBG(TAG, "Format: %u, width: %u, height: %u, fps: %u, flags: %u", 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_ResetPosition(stream->sampleRespBuffer);
|
|
|
|
Stream_Write_UINT8(stream->sampleRespBuffer,
|
|
WINPR_ASSERTING_INT_CAST(uint8_t, dev->ecam->version));
|
|
Stream_Write_UINT8(stream->sampleRespBuffer, WINPR_ASSERTING_INT_CAST(uint8_t, msg));
|
|
Stream_Write_UINT8(stream->sampleRespBuffer, WINPR_ASSERTING_INT_CAST(uint8_t, 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 */);
|
|
}
|
|
|
|
static BOOL mediaSupportDrops(CAM_MEDIA_FORMAT format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case CAM_MEDIA_FORMAT_H264:
|
|
return FALSE;
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static UINT ecam_dev_send_pending(CameraDevice* dev, size_t streamIndex, CameraDeviceStream* stream)
|
|
{
|
|
WINPR_ASSERT(dev);
|
|
WINPR_ASSERT(stream);
|
|
|
|
if (stream->samplesRequested <= 0)
|
|
{
|
|
WLog_VRB(TAG, "Frame delayed: No sample requested");
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
if (!stream->haveSample)
|
|
{
|
|
WLog_VRB(TAG, "Frame response delayed: No sample available");
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
BYTE* encodedSample = Stream_Buffer(stream->pendingSample);
|
|
size_t encodedSize = Stream_Length(stream->pendingSample);
|
|
if (streamInputFormat(stream) != streamOutputFormat(stream))
|
|
{
|
|
if (!ecam_encoder_compress(stream, encodedSample, encodedSize, &encodedSample,
|
|
&encodedSize))
|
|
{
|
|
WLog_DBG(TAG, "Frame dropped: error in ecam_encoder_compress");
|
|
stream->haveSample = FALSE;
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
if (!stream->streaming)
|
|
{
|
|
WLog_DBG(TAG, "Frame delayed/dropped: stream stopped");
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
}
|
|
|
|
stream->samplesRequested--;
|
|
stream->haveSample = FALSE;
|
|
|
|
return ecam_dev_send_sample_response(dev, streamIndex, encodedSample, encodedSize);
|
|
}
|
|
|
|
static UINT ecam_dev_sample_captured_callback(CameraDevice* dev, size_t streamIndex,
|
|
const BYTE* sample, size_t size)
|
|
{
|
|
WINPR_ASSERT(dev);
|
|
|
|
if (streamIndex >= ECAM_DEVICE_MAX_STREAMS)
|
|
return ERROR_INVALID_INDEX;
|
|
|
|
CameraDeviceStream* stream = &dev->streams[streamIndex];
|
|
|
|
if (!stream->streaming)
|
|
{
|
|
WLog_DBG(TAG, "Frame drop: stream not running");
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
EnterCriticalSection(&stream->lock);
|
|
UINT ret = CHANNEL_RC_NO_MEMORY;
|
|
|
|
/* If we already have a waiting sample, let's see if the input format support dropping
|
|
* frames so that we could just "refresh" the pending sample, otherwise we must wait until
|
|
* a frame request flushes it
|
|
*/
|
|
|
|
if (stream->haveSample && !mediaSupportDrops(stream->formats.inputFormat))
|
|
{
|
|
/* we can't drop samples, so we have to wait until the pending sample is
|
|
* sent, by a sample request.
|
|
*
|
|
* When we're here we already have a sample ready to be sent, the delay between 2 frames
|
|
* seems like a reasonable wait delay. For instance 60 FPS means a frame every 16ms.
|
|
* We also cap that wait delay to not spinloop and not get stuck for too long.
|
|
* */
|
|
DWORD waitDelay = (1000 * stream->currMediaType.FrameRateDenominator) /
|
|
stream->currMediaType.FrameRateNumerator;
|
|
if (waitDelay < 16)
|
|
waitDelay = 16;
|
|
if (waitDelay > 100)
|
|
waitDelay = 100;
|
|
|
|
while (stream->haveSample && stream->streaming)
|
|
{
|
|
LeaveCriticalSection(&stream->lock);
|
|
|
|
SleepEx(waitDelay, TRUE);
|
|
|
|
EnterCriticalSection(&stream->lock);
|
|
}
|
|
|
|
if (!stream->streaming)
|
|
{
|
|
WLog_DBG(TAG, "Frame drop: stream not running");
|
|
ret = CHANNEL_RC_OK;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
Stream_ResetPosition(stream->pendingSample);
|
|
if (!Stream_EnsureRemainingCapacity(stream->pendingSample, size))
|
|
goto out;
|
|
|
|
Stream_Write(stream->pendingSample, sample, size);
|
|
Stream_SealLength(stream->pendingSample);
|
|
stream->haveSample = TRUE;
|
|
|
|
ret = ecam_dev_send_pending(dev, streamIndex, stream);
|
|
|
|
out:
|
|
LeaveCriticalSection(&stream->lock);
|
|
return ret;
|
|
}
|
|
|
|
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);
|
|
|
|
DeleteCriticalSection(&stream->lock);
|
|
}
|
|
|
|
Stream_Free(stream->sampleRespBuffer, TRUE);
|
|
stream->sampleRespBuffer = nullptr;
|
|
|
|
Stream_Free(stream->pendingSample, TRUE);
|
|
stream->pendingSample = nullptr;
|
|
|
|
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 = WINPR_C_ARRAY_INIT;
|
|
|
|
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 %" PRIu8, 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(nullptr, 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->samplesRequested = 0;
|
|
stream->haveSample = FALSE;
|
|
|
|
if (!InitializeCriticalSectionEx(&stream->lock, 0, 0))
|
|
{
|
|
WLog_ERR(TAG, "InitializeCriticalSectionEx failed");
|
|
ecam_dev_stop_stream(dev, streamIndex);
|
|
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_OutOfMemory);
|
|
return ERROR_INVALID_DATA;
|
|
}
|
|
|
|
stream->pendingSample = Stream_New(nullptr, 4ull * mediaType.Width * mediaType.Height);
|
|
if (!stream->pendingSample)
|
|
{
|
|
WLog_ERR(TAG, "pending stream failed");
|
|
ecam_dev_stop_stream(dev, streamIndex);
|
|
ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_OutOfMemory);
|
|
return ERROR_INVALID_DATA;
|
|
}
|
|
|
|
const CAM_ERROR_CODE 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,
|
|
WINPR_ATTR_UNUSED 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(nullptr, 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, WINPR_ASSERTING_INT_CAST(uint8_t, dev->ecam->version));
|
|
Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, 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];
|
|
|
|
EnterCriticalSection(&stream->lock);
|
|
|
|
/* need to save channel because responses are asynchronous and coming from capture thread */
|
|
if (stream->hSampleReqChannel != hchannel)
|
|
stream->hSampleReqChannel = hchannel;
|
|
|
|
stream->samplesRequested++;
|
|
const UINT ret = ecam_dev_send_pending(dev, streamIndex, stream);
|
|
|
|
LeaveCriticalSection(&stream->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* 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(nullptr, 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, WINPR_ASSERTING_INT_CAST(uint8_t, dev->ecam->version));
|
|
Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, 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 = nullptr;
|
|
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 (size_t 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(nullptr, CAM_HEADER_SIZE + sizeof(CAM_STREAM_DESCRIPTION));
|
|
if (!s)
|
|
{
|
|
WLog_ERR(TAG, "Stream_New failed");
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, dev->ecam->version));
|
|
Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, 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,
|
|
WINPR_ATTR_UNUSED 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,
|
|
WINPR_ATTR_UNUSED wStream* s)
|
|
{
|
|
WINPR_ASSERT(dev);
|
|
CAM_ERROR_CODE errorCode = CAM_ERROR_CODE_None;
|
|
|
|
if (dev->ihal->Activate(dev->ihal, dev->deviceId, &errorCode))
|
|
return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse);
|
|
|
|
return ecam_channel_send_error_response(dev->ecam, hchannel, errorCode);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
CAM_ERROR_CODE errorCode = CAM_ERROR_CODE_None;
|
|
if (dev->ihal->Deactivate(dev->ihal, dev->deviceId, &errorCode))
|
|
return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse);
|
|
|
|
return ecam_channel_send_error_response(dev->ecam, hchannel, errorCode);
|
|
}
|
|
|
|
/**
|
|
* 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=%" PRIu32 ", 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(WINPR_ATTR_UNUSED IWTSVirtualChannelCallback* pChannelCallback)
|
|
{
|
|
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");
|
|
|
|
for (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++)
|
|
ecam_dev_stop_stream(dev, i);
|
|
|
|
/* 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 = nullptr;
|
|
|
|
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,
|
|
WINPR_ATTR_UNUSED BYTE* Data,
|
|
WINPR_ATTR_UNUSED 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 nullptr in case of error
|
|
*/
|
|
CameraDevice* ecam_dev_create(CameraPlugin* ecam, const char* deviceId,
|
|
WINPR_ATTR_UNUSED 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 nullptr;
|
|
}
|
|
|
|
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 nullptr;
|
|
}
|
|
|
|
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 nullptr;
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
|
|
/**
|
|
* Function description
|
|
*
|
|
* OBJECT_FREE_FN for devices hash table value
|
|
*
|
|
*/
|
|
void ecam_dev_destroy(CameraDevice* dev)
|
|
{
|
|
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 (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++)
|
|
ecam_dev_stop_stream(dev, i);
|
|
|
|
free(dev);
|
|
}
|