Files
grd/grd-rdp-dvc-camera-device.c
2026-02-13 13:06:50 +09:00

1784 lines
59 KiB
C

/*
* Copyright (C) 2025 Pascal Nowack
*
* 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "config.h"
#include "grd-rdp-dvc-camera-device.h"
#include "grd-pipewire-utils.h"
#include "grd-rdp-camera-stream.h"
#include "grd-sample-buffer.h"
#include "grd-utils.h"
#define SAMPLE_TIMEOUT_MS (2 * 1000)
enum
{
ERROR,
N_SIGNALS
};
static guint signals[N_SIGNALS];
typedef enum
{
DEVICE_STATE_FATAL_ERROR,
DEVICE_STATE_PENDING_ACTIVATION,
DEVICE_STATE_PENDING_ACTIVATION_RESPONSE,
DEVICE_STATE_PENDING_STREAM_LIST_RESPONSE,
DEVICE_STATE_PENDING_MEDIA_TYPE_LIST_RESPONSE,
DEVICE_STATE_PENDING_PROPERTY_LIST_RESPONSE,
DEVICE_STATE_PENDING_PROPERTY_VALUE_RESPONSE,
DEVICE_STATE_PENDING_STREAM_PREPARATION,
DEVICE_STATE_INITIALIZATION_DONE,
DEVICE_STATE_IN_SHUTDOWN,
} DeviceState;
typedef enum
{
DEVICE_EVENT_ACK_STARTED_STREAMS,
DEVICE_EVENT_STOP_STREAMS,
DEVICE_EVENT_ACK_STOPPED_STREAMS,
} DeviceEvent;
typedef enum
{
CLIENT_REQUEST_TYPE_NONE,
CLIENT_REQUEST_TYPE_START_STREAMS,
CLIENT_REQUEST_TYPE_STOP_STREAMS,
} ClientRequestType;
typedef struct
{
CAM_MEDIA_TYPE_DESCRIPTION *media_type_description;
uint32_t run_sequence;
} StreamRunContext;
typedef struct
{
GrdRdpDvcCameraDevice *device;
GList *media_type_descriptions;
GrdRdpCameraStream *camera_stream;
GAsyncQueue *pending_samples;
} StreamContext;
struct _GrdRdpDvcCameraDevice
{
GrdRdpDvc parent;
CameraDeviceServerContext *device_context;
gboolean channel_opened;
char *dvc_name;
char *device_name;
GThread *camera_thread;
GMainContext *camera_context;
gboolean in_shutdown;
GrdSyncPoint sync_point;
GSource *initialization_source;
GMutex state_mutex;
DeviceState state;
GQueue *pending_media_type_lists;
GQueue *pending_property_values;
GHashTable *stream_contexts;
GSource *pipewire_source;
struct pw_context *pipewire_context;
struct pw_core *pipewire_core;
struct spa_hook pipewire_core_listener;
GHashTable *queued_stream_starts;
GHashTable *pending_stream_starts;
GHashTable *running_streams;
GSource *event_source;
GMutex event_mutex;
GHashTable *pending_events;
GMutex client_request_mutex;
gboolean pending_client_request;
ClientRequestType pending_client_request_type;
GMutex sample_request_mutex;
GHashTable *pending_samples;
GSource *sample_timeout_source;
};
G_DEFINE_TYPE (GrdRdpDvcCameraDevice, grd_rdp_dvc_camera_device,
GRD_TYPE_RDP_DVC)
static StreamRunContext *
stream_run_context_new (CAM_MEDIA_TYPE_DESCRIPTION *media_type_description,
uint32_t run_sequence)
{
StreamRunContext *stream_run_context;
stream_run_context = g_new0 (StreamRunContext, 1);
stream_run_context->media_type_description = media_type_description;
stream_run_context->run_sequence = run_sequence;
return stream_run_context;
}
static void
stream_run_context_free (StreamRunContext *stream_run_context)
{
g_free (stream_run_context);
}
static StreamContext *
stream_context_new (GrdRdpDvcCameraDevice *device)
{
StreamContext *stream_context;
stream_context = g_new0 (StreamContext, 1);
stream_context->device = device;
stream_context->pending_samples = g_async_queue_new ();
return stream_context;
}
static void
discard_pending_sample_requests (StreamContext *stream_context)
{
GrdRdpDvcCameraDevice *device = stream_context->device;
g_autoptr (GMutexLocker) locker = NULL;
GrdSampleBuffer *sample_buffer;
locker = g_mutex_locker_new (&device->state_mutex);
while ((sample_buffer = g_async_queue_try_pop (stream_context->pending_samples)))
{
g_mutex_lock (&device->sample_request_mutex);
g_hash_table_remove (device->pending_samples, sample_buffer);
g_mutex_unlock (&device->sample_request_mutex);
grd_rdp_camera_stream_submit_sample (stream_context->camera_stream,
sample_buffer, FALSE);
}
}
static void
stream_context_free (StreamContext *stream_context)
{
if (stream_context->camera_stream)
{
GrdRdpDvcCameraDevice *device = stream_context->device;
uint8_t stream_index =
grd_rdp_camera_stream_get_stream_index (stream_context->camera_stream);
discard_pending_sample_requests (stream_context);
g_hash_table_remove (device->queued_stream_starts,
GUINT_TO_POINTER (stream_index));
g_hash_table_remove (device->pending_stream_starts,
GUINT_TO_POINTER (stream_index));
g_hash_table_remove (device->running_streams,
GUINT_TO_POINTER (stream_index));
}
g_clear_object (&stream_context->camera_stream);
g_clear_list (&stream_context->media_type_descriptions, g_free);
g_clear_pointer (&stream_context->pending_samples, g_async_queue_unref);
g_free (stream_context);
}
const char *
grd_rdp_dvc_camera_device_get_dvc_name (GrdRdpDvcCameraDevice *device)
{
return device->dvc_name;
}
void
grd_rdp_dvc_camera_device_start_stream (GrdRdpDvcCameraDevice *device,
GrdRdpCameraStream *camera_stream,
CAM_MEDIA_TYPE_DESCRIPTION *media_type_description,
uint32_t run_sequence)
{
uint8_t stream_index = grd_rdp_camera_stream_get_stream_index (camera_stream);
g_hash_table_insert (device->queued_stream_starts,
GUINT_TO_POINTER (stream_index),
stream_run_context_new (media_type_description,
run_sequence));
g_source_set_ready_time (device->event_source, 0);
}
static gboolean
queue_stream_restart (gpointer key,
gpointer value,
gpointer user_data)
{
GrdRdpDvcCameraDevice *device = user_data;
uint8_t stream_index = GPOINTER_TO_UINT (key);
StreamRunContext *stream_run_context = value;
StreamContext *stream_context = NULL;
if (!g_hash_table_lookup_extended (device->stream_contexts,
GUINT_TO_POINTER (stream_index),
NULL, (gpointer *) &stream_context))
g_assert_not_reached ();
grd_rdp_camera_stream_inhibit_camera_loop (stream_context->camera_stream);
if (!g_hash_table_contains (device->queued_stream_starts,
GUINT_TO_POINTER (stream_index)))
{
g_hash_table_insert (device->queued_stream_starts,
GUINT_TO_POINTER (stream_index),
g_memdup2 (stream_run_context,
sizeof (StreamRunContext)));
}
return TRUE;
}
void
grd_rdp_dvc_camera_device_stop_stream (GrdRdpDvcCameraDevice *device,
GrdRdpCameraStream *camera_stream)
{
uint8_t stream_index = grd_rdp_camera_stream_get_stream_index (camera_stream);
g_hash_table_remove (device->running_streams,
GUINT_TO_POINTER (stream_index));
g_hash_table_remove (device->queued_stream_starts,
GUINT_TO_POINTER (stream_index));
/*
* Stopping a stream can mean one of the two things:
* 1) The stream is stopped, because there is no consumer left
* 2) The stream format was changed
*
* For case 2, all streams need to be stopped, because the [MS-RDPECAM]
* protocol only supports stopping all streams and not stopping one individual
* stream.
*/
g_hash_table_foreach_remove (device->running_streams,
queue_stream_restart, device);
g_mutex_lock (&device->event_mutex);
g_hash_table_add (device->pending_events,
GUINT_TO_POINTER (DEVICE_EVENT_STOP_STREAMS));
g_mutex_unlock (&device->event_mutex);
g_source_set_ready_time (device->event_source, 0);
}
void
grd_rdp_dvc_camera_device_request_sample (GrdRdpDvcCameraDevice *device,
GrdRdpCameraStream *camera_stream,
GrdSampleBuffer *sample_buffer)
{
CameraDeviceServerContext *device_context = device->device_context;
uint8_t stream_index = grd_rdp_camera_stream_get_stream_index (camera_stream);
StreamContext *stream_context = NULL;
CAM_SAMPLE_REQUEST sample_request = {};
g_assert (device->pending_client_request_type !=
CLIENT_REQUEST_TYPE_STOP_STREAMS);
if (!g_hash_table_lookup_extended (device->stream_contexts,
GUINT_TO_POINTER (stream_index),
NULL, (gpointer *) &stream_context))
g_assert_not_reached ();
g_mutex_lock (&device->sample_request_mutex);
g_hash_table_add (device->pending_samples, sample_buffer);
g_mutex_unlock (&device->sample_request_mutex);
g_async_queue_push (stream_context->pending_samples, sample_buffer);
sample_request.StreamIndex = stream_index;
device_context->SampleRequest (device_context, &sample_request);
}
static void
dvc_creation_status_cb (gpointer user_data,
int32_t creation_status)
{
GrdRdpDvcCameraDevice *device = user_data;
if (creation_status < 0)
{
g_warning ("[RDP.CAM_DEVICE] Failed to open %s channel "
"(CreationStatus %i, device name: \"%s\"). "
"Terminating protocol",
device->dvc_name, creation_status, device->device_name);
g_signal_emit (device, signals[ERROR], 0);
return;
}
g_message ("[RDP.CAM_DEVICE] Successfully opened %s channel "
"(CreationStatus %i, device name: \"%s\"). ",
device->dvc_name, creation_status, device->device_name);
g_source_set_ready_time (device->initialization_source, 0);
}
static BOOL
device_channel_id_assigned (CameraDeviceServerContext *device_context,
uint32_t channel_id)
{
GrdRdpDvcCameraDevice *device = device_context->userdata;
GrdRdpDvc *dvc = GRD_RDP_DVC (device);
g_debug ("[RDP.CAM_DEVICE] DVC channel id for channel %s assigned to id %u",
device->dvc_name, channel_id);
grd_rdp_dvc_subscribe_creation_status (dvc, channel_id,
dvc_creation_status_cb,
device);
return TRUE;
}
static const char *
device_state_to_string (DeviceState state)
{
switch (state)
{
case DEVICE_STATE_FATAL_ERROR:
return "FATAL_ERROR";
case DEVICE_STATE_PENDING_ACTIVATION:
return "PENDING_ACTIVATION";
case DEVICE_STATE_PENDING_ACTIVATION_RESPONSE:
return "PENDING_ACTIVATION_RESPONSE";
case DEVICE_STATE_PENDING_STREAM_LIST_RESPONSE:
return "PENDING_STREAM_LIST_RESPONSE";
case DEVICE_STATE_PENDING_MEDIA_TYPE_LIST_RESPONSE:
return "PENDING_MEDIA_TYPE_LIST_RESPONSE";
case DEVICE_STATE_PENDING_PROPERTY_LIST_RESPONSE:
return "PENDING_PROPERTY_LIST_RESPONSE";
case DEVICE_STATE_PENDING_PROPERTY_VALUE_RESPONSE:
return "PENDING_PROPERTY_VALUE_RESPONSE";
case DEVICE_STATE_PENDING_STREAM_PREPARATION:
return "PENDING_STREAM_PREPARATION";
case DEVICE_STATE_INITIALIZATION_DONE:
return "INITIALIZATION_DONE";
case DEVICE_STATE_IN_SHUTDOWN:
return "IN_SHUTDOWN";
}
g_assert_not_reached ();
}
static void
transition_into_fatal_error_state (GrdRdpDvcCameraDevice *device)
{
device->state = DEVICE_STATE_FATAL_ERROR;
g_signal_emit (device, signals[ERROR], 0);
}
static void
request_stream_list (GrdRdpDvcCameraDevice *device)
{
CameraDeviceServerContext *device_context = device->device_context;
CAM_STREAM_LIST_REQUEST stream_list_request = {};
device->state = DEVICE_STATE_PENDING_STREAM_LIST_RESPONSE;
device_context->StreamListRequest (device_context, &stream_list_request);
}
static gboolean
try_handle_runtime_success_response (GrdRdpDvcCameraDevice *device,
GError **error)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->client_request_mutex);
if (!device->pending_client_request)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Protocol violation: "
"Received stray success response in state %s for device "
"\"%s\". There was no runtime request. Removing device...",
device_state_to_string (device->state), device->device_name);
return FALSE;
}
g_mutex_lock (&device->event_mutex);
switch (device->pending_client_request_type)
{
case CLIENT_REQUEST_TYPE_NONE:
g_assert_not_reached ();
break;
case CLIENT_REQUEST_TYPE_START_STREAMS:
g_hash_table_add (device->pending_events,
GUINT_TO_POINTER (DEVICE_EVENT_ACK_STARTED_STREAMS));
break;
case CLIENT_REQUEST_TYPE_STOP_STREAMS:
g_hash_table_add (device->pending_events,
GUINT_TO_POINTER (DEVICE_EVENT_ACK_STOPPED_STREAMS));
break;
}
g_mutex_unlock (&device->event_mutex);
g_source_set_ready_time (device->event_source, 0);
return TRUE;
}
/*
* Actions for success:
*
* - Activate Device Request
* - Deactivate Device Request
* - Start Streams Request
* - Stop Streams Request
* - Set Property Value Request
*/
static uint32_t
device_success_response (CameraDeviceServerContext *device_context,
const CAM_SUCCESS_RESPONSE *success_response)
{
GrdRdpDvcCameraDevice *device = device_context->userdata;
g_autoptr (GMutexLocker) locker = NULL;
g_autoptr (GError) error = NULL;
locker = g_mutex_locker_new (&device->state_mutex);
switch (device->state)
{
case DEVICE_STATE_FATAL_ERROR:
case DEVICE_STATE_IN_SHUTDOWN:
break;
case DEVICE_STATE_PENDING_ACTIVATION:
case DEVICE_STATE_PENDING_STREAM_LIST_RESPONSE:
case DEVICE_STATE_PENDING_MEDIA_TYPE_LIST_RESPONSE:
case DEVICE_STATE_PENDING_PROPERTY_LIST_RESPONSE:
case DEVICE_STATE_PENDING_PROPERTY_VALUE_RESPONSE:
case DEVICE_STATE_PENDING_STREAM_PREPARATION:
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received stray success "
"response in state %s for device \"%s\". Removing device...",
device_state_to_string (device->state), device->device_name);
transition_into_fatal_error_state (device);
break;
case DEVICE_STATE_PENDING_ACTIVATION_RESPONSE:
g_debug ("[RDP.CAM_DEVICE] Activated device \"%s\"", device->device_name);
request_stream_list (device);
break;
case DEVICE_STATE_INITIALIZATION_DONE:
if (!try_handle_runtime_success_response (device, &error))
{
g_warning ("[RDP.CAM_DEVICE] %s", error->message);
transition_into_fatal_error_state (device);
}
break;
}
return CHANNEL_RC_OK;
}
static const char *
error_code_to_string (CAM_ERROR_CODE error_code)
{
switch (error_code)
{
case CAM_ERROR_CODE_UnexpectedError:
return "Unexpected Error";
case CAM_ERROR_CODE_InvalidMessage:
return "Invalid Message";
case CAM_ERROR_CODE_NotInitialized:
return "Not Initialized";
case CAM_ERROR_CODE_InvalidRequest:
return "Invalid Request";
case CAM_ERROR_CODE_InvalidStreamNumber:
return "Invalid Stream Number";
case CAM_ERROR_CODE_InvalidMediaType:
return "Invalid MediaType";
case CAM_ERROR_CODE_OutOfMemory:
return "Out Of Memory";
case CAM_ERROR_CODE_ItemNotFound:
return "Item Not Found";
case CAM_ERROR_CODE_SetNotFound:
return "Set Not Found";
case CAM_ERROR_CODE_OperationNotSupported:
return "Operation Not Supported";
default:
return "Unknown Error";
}
g_assert_not_reached ();
}
static const char *
device_error_to_string (const CAM_ERROR_RESPONSE *error_response)
{
return error_code_to_string (error_response->ErrorCode);
}
static const char *
sample_error_to_string (const CAM_SAMPLE_ERROR_RESPONSE *sample_error_response)
{
return error_code_to_string (sample_error_response->ErrorCode);
}
static void
fetch_runtime_error_response (GrdRdpDvcCameraDevice *device,
const CAM_ERROR_RESPONSE *error_response,
GError **error)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->client_request_mutex);
if (!device->pending_client_request)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Protocol violation: "
"Received stray error response \"%s\" in state %s for "
"device \"%s\". There was no runtime request. Removing "
"device...", device_error_to_string (error_response),
device_state_to_string (device->state), device->device_name);
return;
}
switch (device->pending_client_request_type)
{
case CLIENT_REQUEST_TYPE_NONE:
g_assert_not_reached ();
break;
case CLIENT_REQUEST_TYPE_START_STREAMS:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to start "
"streams on device \"%s\": %s. Removing device...",
device->device_name, device_error_to_string (error_response));
break;
case CLIENT_REQUEST_TYPE_STOP_STREAMS:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to stop "
"streams on device \"%s\": %s. Removing device...",
device->device_name, device_error_to_string (error_response));
break;
}
}
/*
* Actions for failure:
*
* - Activate Device Request
* - Deactivate Device Request
* - Start Streams Request
* - Stop Streams Request
* - Set Property Value Request
*
* - Stream List Request
* - Media Type List Request
* - Current Media Type Request
* - Property List Request
* - Property Value Request
*/
static uint32_t
device_error_response (CameraDeviceServerContext *device_context,
const CAM_ERROR_RESPONSE *error_response)
{
GrdRdpDvcCameraDevice *device = device_context->userdata;
g_autoptr (GError) error = NULL;
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->state_mutex);
switch (device->state)
{
case DEVICE_STATE_FATAL_ERROR:
case DEVICE_STATE_IN_SHUTDOWN:
break;
case DEVICE_STATE_PENDING_ACTIVATION:
case DEVICE_STATE_PENDING_STREAM_PREPARATION:
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received stray error "
"response \"%s\" in state %s for device \"%s\". "
"Removing device...", device_error_to_string (error_response),
device_state_to_string (device->state), device->device_name);
break;
case DEVICE_STATE_PENDING_ACTIVATION_RESPONSE:
g_warning ("[RDP.CAM_DEVICE] Failed to activate device \"%s\": %s."
"Removing device...",
device->device_name, device_error_to_string (error_response));
break;
case DEVICE_STATE_PENDING_STREAM_LIST_RESPONSE:
g_warning ("[RDP.CAM_DEVICE] Failed to fetch stream list from device "
"\"%s\": %s. Removing device...",
device->device_name, device_error_to_string (error_response));
break;
case DEVICE_STATE_PENDING_MEDIA_TYPE_LIST_RESPONSE:
g_warning ("[RDP.CAM_DEVICE] Failed to fetch media type list from device "
"\"%s\": %s. Removing device...",
device->device_name, device_error_to_string (error_response));
break;
case DEVICE_STATE_PENDING_PROPERTY_LIST_RESPONSE:
g_warning ("[RDP.CAM_DEVICE] Failed to fetch property list from device "
"\"%s\": %s. Removing device...",
device->device_name, device_error_to_string (error_response));
break;
case DEVICE_STATE_PENDING_PROPERTY_VALUE_RESPONSE:
g_warning ("[RDP.CAM_DEVICE] Failed to fetch property value from device "
"\"%s\": %s. Removing device...",
device->device_name, device_error_to_string (error_response));
break;
case DEVICE_STATE_INITIALIZATION_DONE:
fetch_runtime_error_response (device, error_response, &error);
g_warning ("[RDP.CAM_DEVICE] %s", error->message);
break;
}
transition_into_fatal_error_state (device);
return CHANNEL_RC_OK;
}
static void
request_next_media_type_list (GrdRdpDvcCameraDevice *device)
{
CameraDeviceServerContext *device_context = device->device_context;
CAM_MEDIA_TYPE_LIST_REQUEST media_type_list_request = {};
g_assert (g_queue_get_length (device->pending_media_type_lists) > 0);
media_type_list_request.StreamIndex =
GPOINTER_TO_UINT (g_queue_peek_head (device->pending_media_type_lists));
device->state = DEVICE_STATE_PENDING_MEDIA_TYPE_LIST_RESPONSE;
device_context->MediaTypeListRequest (device_context,
&media_type_list_request);
}
static uint32_t
device_stream_list_response (CameraDeviceServerContext *device_context,
const CAM_STREAM_LIST_RESPONSE *stream_list_response)
{
GrdRdpDvcCameraDevice *device = device_context->userdata;
g_autoptr (GMutexLocker) locker = NULL;
uint16_t i;
locker = g_mutex_locker_new (&device->state_mutex);
if (device->state == DEVICE_STATE_FATAL_ERROR ||
device->state == DEVICE_STATE_IN_SHUTDOWN)
return CHANNEL_RC_OK;
if (device->state != DEVICE_STATE_PENDING_STREAM_LIST_RESPONSE)
{
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received stray stream "
"list response in state %s for device \"%s\". "
"Removing device...",
device_state_to_string (device->state), device->device_name);
transition_into_fatal_error_state (device);
return CHANNEL_RC_OK;
}
if (stream_list_response->N_Descriptions < 1)
{
g_warning ("[RDP.CAM_DEVICE] Device \"%s\": Received invalid stream list "
"response. Removing device...", device->device_name);
transition_into_fatal_error_state (device);
return CHANNEL_RC_OK;
}
for (i = 0; i < stream_list_response->N_Descriptions; ++i)
{
g_debug ("[RDP.CAM_DEVICE] Device \"%s\": Stream %u: FrameSourceTypes: %u, "
"StreamCategory, %u, Selected: %u, CanBeShared: %u",
device->device_name, i,
stream_list_response->StreamDescriptions[i].FrameSourceTypes,
stream_list_response->StreamDescriptions[i].StreamCategory,
stream_list_response->StreamDescriptions[i].Selected,
stream_list_response->StreamDescriptions[i].CanBeShared);
g_hash_table_insert (device->stream_contexts,
GUINT_TO_POINTER (i), stream_context_new (device));
g_queue_push_tail (device->pending_media_type_lists,
GUINT_TO_POINTER (i));
}
request_next_media_type_list (device);
return CHANNEL_RC_OK;
}
static void
request_property_list (GrdRdpDvcCameraDevice *device)
{
CameraDeviceServerContext *device_context = device->device_context;
CAM_PROPERTY_LIST_REQUEST property_list_request = {};
device->state = DEVICE_STATE_PENDING_PROPERTY_LIST_RESPONSE;
device_context->PropertyListRequest (device_context, &property_list_request);
}
static void
start_preparing_streams (GrdRdpDvcCameraDevice *device)
{
device->state = DEVICE_STATE_PENDING_STREAM_PREPARATION;
g_source_set_ready_time (device->initialization_source, 0);
}
static uint32_t
device_media_type_list_response (CameraDeviceServerContext *device_context,
const CAM_MEDIA_TYPE_LIST_RESPONSE *media_type_list_response)
{
GrdRdpDvcCameraDevice *device = device_context->userdata;
g_autoptr (GMutexLocker) locker = NULL;
StreamContext *stream_context = NULL;
uint8_t stream_index;
size_t i;
locker = g_mutex_locker_new (&device->state_mutex);
if (device->state == DEVICE_STATE_FATAL_ERROR ||
device->state == DEVICE_STATE_IN_SHUTDOWN)
return CHANNEL_RC_OK;
if (device->state != DEVICE_STATE_PENDING_MEDIA_TYPE_LIST_RESPONSE)
{
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received stray media "
"type list response in state %s for device \"%s\". "
"Removing device...",
device_state_to_string (device->state), device->device_name);
transition_into_fatal_error_state (device);
return CHANNEL_RC_OK;
}
if (media_type_list_response->N_Descriptions < 1)
{
g_warning ("[RDP.CAM_DEVICE] Device \"%s\": Received invalid media type "
"list response. Removing device...", device->device_name);
transition_into_fatal_error_state (device);
return CHANNEL_RC_OK;
}
stream_index =
GPOINTER_TO_UINT (g_queue_pop_head (device->pending_media_type_lists));
if (!g_hash_table_lookup_extended (device->stream_contexts,
GUINT_TO_POINTER (stream_index),
NULL, (gpointer *) &stream_context))
g_assert_not_reached ();
for (i = 0; i < media_type_list_response->N_Descriptions; ++i)
{
CAM_MEDIA_TYPE_DESCRIPTION *media_type_description =
&media_type_list_response->MediaTypeDescriptions[i];
g_debug ("[RDP.CAM_DEVICE] Device \"%s\": Stream %u: MediaTypeList %zu: "
"Format: %u, Width: %u, Height: %u, FrameRateNumerator: %u, "
"FrameRateDenominator: %u, PixelAspectRatioNumerator: %u, "
"PixelAspectRatioDenominator: %u, Flags: 0x%08X",
device->device_name, stream_index, i,
media_type_description->Format,
media_type_description->Width,
media_type_description->Height,
media_type_description->FrameRateNumerator,
media_type_description->FrameRateDenominator,
media_type_description->PixelAspectRatioNumerator,
media_type_description->PixelAspectRatioDenominator,
media_type_description->Flags);
stream_context->media_type_descriptions =
g_list_append (stream_context->media_type_descriptions,
g_memdup2 (media_type_description,
sizeof (CAM_MEDIA_TYPE_DESCRIPTION)));
}
if (g_queue_get_length (device->pending_media_type_lists) > 0)
request_next_media_type_list (device);
else if (device_context->protocolVersion >= 2)
request_property_list (device);
else
start_preparing_streams (device);
return CHANNEL_RC_OK;
}
static uint32_t
device_current_media_type_response (CameraDeviceServerContext *device_context,
const CAM_CURRENT_MEDIA_TYPE_RESPONSE *current_media_type_response)
{
return CHANNEL_RC_OK;
}
static gboolean
has_pending_event_unlocked (GrdRdpDvcCameraDevice *device,
DeviceEvent event)
{
return g_hash_table_contains (device->pending_events,
GUINT_TO_POINTER (event));
}
static gboolean
has_pending_event (GrdRdpDvcCameraDevice *device,
DeviceEvent event)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->event_mutex);
return has_pending_event_unlocked (device, event);
}
static uint32_t
device_sample_response (CameraDeviceServerContext *device_context,
const CAM_SAMPLE_RESPONSE *sample_response)
{
GrdRdpDvcCameraDevice *device = device_context->userdata;
uint8_t stream_index = sample_response->StreamIndex;
g_autoptr (GMutexLocker) locker = NULL;
StreamContext *stream_context = NULL;
g_autoptr (GError) error = NULL;
GrdSampleBuffer *sample_buffer;
uint32_t n_pending_samples;
gboolean success = FALSE;
locker = g_mutex_locker_new (&device->state_mutex);
if (device->state == DEVICE_STATE_FATAL_ERROR ||
device->state == DEVICE_STATE_IN_SHUTDOWN)
return CHANNEL_RC_OK;
if (!g_hash_table_lookup_extended (device->stream_contexts,
GUINT_TO_POINTER (stream_index),
NULL, (gpointer *) &stream_context))
{
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received sample "
"response for unknown stream in state %s for device \"%s\". "
"Removing device...",
device_state_to_string (device->state), device->device_name);
transition_into_fatal_error_state (device);
return CHANNEL_RC_OK;
}
sample_buffer = g_async_queue_try_pop (stream_context->pending_samples);
if (!sample_buffer)
{
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received stray sample "
"response for stream %u on device \"%s\". Ignoring...",
stream_index, device->device_name);
return CHANNEL_RC_OK;
}
g_mutex_lock (&device->sample_request_mutex);
g_hash_table_remove (device->pending_samples, sample_buffer);
n_pending_samples = g_hash_table_size (device->pending_samples);
g_mutex_unlock (&device->sample_request_mutex);
if (grd_rdp_camera_stream_announce_new_sample (stream_context->camera_stream,
sample_buffer))
{
success = grd_sample_buffer_load_sample (sample_buffer,
sample_response->Sample,
sample_response->SampleSize,
&error);
if (!success)
{
g_warning ("[RDP.CAM_DEVICE] Device \"%s\", stream %u: Failed to "
"load sample: %s ", device->device_name, stream_index,
error->message);
}
}
grd_rdp_camera_stream_submit_sample (stream_context->camera_stream,
sample_buffer, success);
if (n_pending_samples == 0 &&
has_pending_event (device, DEVICE_EVENT_STOP_STREAMS))
g_source_set_ready_time (device->event_source, 0);
return CHANNEL_RC_OK;
}
static uint32_t
device_sample_error_response (CameraDeviceServerContext *device_context,
const CAM_SAMPLE_ERROR_RESPONSE *sample_error_response)
{
GrdRdpDvcCameraDevice *device = device_context->userdata;
uint8_t stream_index = sample_error_response->StreamIndex;
g_autoptr (GMutexLocker) locker = NULL;
StreamContext *stream_context = NULL;
GrdSampleBuffer *sample_buffer;
uint32_t n_pending_samples;
locker = g_mutex_locker_new (&device->state_mutex);
if (device->state == DEVICE_STATE_FATAL_ERROR ||
device->state == DEVICE_STATE_IN_SHUTDOWN)
return CHANNEL_RC_OK;
if (!g_hash_table_lookup_extended (device->stream_contexts,
GUINT_TO_POINTER (stream_index),
NULL, (gpointer *) &stream_context))
{
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received sample error "
"response \"%s\" for unknown stream in state %s for device "
"\"%s\". Removing device...",
sample_error_to_string (sample_error_response),
device_state_to_string (device->state), device->device_name);
transition_into_fatal_error_state (device);
return CHANNEL_RC_OK;
}
sample_buffer = g_async_queue_try_pop (stream_context->pending_samples);
if (!sample_buffer)
{
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received stray sample "
"error response \"%s\" for stream %u on device \"%s\". "
"Ignoring...", sample_error_to_string (sample_error_response),
stream_index, device->device_name);
return CHANNEL_RC_OK;
}
g_mutex_lock (&device->sample_request_mutex);
g_hash_table_remove (device->pending_samples, sample_buffer);
n_pending_samples = g_hash_table_size (device->pending_samples);
g_mutex_unlock (&device->sample_request_mutex);
g_warning ("[RDP.CAM_DEVICE] Device \"%s\", stream %u: Failed to retrieve "
"sample from client: %s ", device->device_name, stream_index,
sample_error_to_string (sample_error_response));
grd_rdp_camera_stream_submit_sample (stream_context->camera_stream,
sample_buffer, FALSE);
if (n_pending_samples == 0 &&
has_pending_event (device, DEVICE_EVENT_STOP_STREAMS))
g_source_set_ready_time (device->event_source, 0);
return CHANNEL_RC_OK;
}
static void
request_next_property_value (GrdRdpDvcCameraDevice *device)
{
CameraDeviceServerContext *device_context = device->device_context;
CAM_PROPERTY_VALUE_REQUEST *property_value_request;
g_assert (g_queue_get_length (device->pending_property_values) > 0);
property_value_request = g_queue_peek_head (device->pending_property_values);
device->state = DEVICE_STATE_PENDING_PROPERTY_VALUE_RESPONSE;
device_context->PropertyValueRequest (device_context, property_value_request);
}
static uint32_t
device_property_list_response (CameraDeviceServerContext *device_context,
const CAM_PROPERTY_LIST_RESPONSE *property_list_response)
{
GrdRdpDvcCameraDevice *device = device_context->userdata;
g_autoptr (GMutexLocker) locker = NULL;
size_t i;
locker = g_mutex_locker_new (&device->state_mutex);
if (device->state == DEVICE_STATE_FATAL_ERROR ||
device->state == DEVICE_STATE_IN_SHUTDOWN)
return CHANNEL_RC_OK;
if (device->state != DEVICE_STATE_PENDING_PROPERTY_LIST_RESPONSE)
{
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received stray "
"property list response in state %s for device \"%s\". "
"Removing device...",
device_state_to_string (device->state), device->device_name);
transition_into_fatal_error_state (device);
return CHANNEL_RC_OK;
}
for (i = 0; i < property_list_response->N_Properties; ++i)
{
CAM_PROPERTY_DESCRIPTION *property =
&property_list_response->Properties[i];
CAM_PROPERTY_VALUE_REQUEST *property_value_request;
g_debug ("[RDP.CAM_DEVICE] Device \"%s\": Property %zu: "
"PropertySet: 0x%02X, PropertyId: 0x%02X, Capabilities: 0x%02X, "
"MinValue: %i, MaxValue: %i, Step: %i, DefaultValue: %i",
device->device_name, i,
property->PropertySet, property->PropertyId,
property->Capabilities, property->MinValue, property->MaxValue,
property->Step, property->DefaultValue);
property_value_request = g_new0 (CAM_PROPERTY_VALUE_REQUEST, 1);
property_value_request->PropertySet = property->PropertySet;
property_value_request->PropertyId = property->PropertyId;
g_queue_push_tail (device->pending_property_values,
property_value_request);
}
if (g_queue_get_length (device->pending_property_values) > 0)
request_next_property_value (device);
else
start_preparing_streams (device);
return CHANNEL_RC_OK;
}
static uint32_t
device_property_value_response (CameraDeviceServerContext *device_context,
const CAM_PROPERTY_VALUE_RESPONSE *property_value_response)
{
GrdRdpDvcCameraDevice *device = device_context->userdata;
const CAM_PROPERTY_VALUE *property_value =
&property_value_response->PropertyValue;
g_autoptr (GMutexLocker) locker = NULL;
g_autofree CAM_PROPERTY_VALUE_REQUEST *property_value_request = NULL;
locker = g_mutex_locker_new (&device->state_mutex);
if (device->state == DEVICE_STATE_FATAL_ERROR ||
device->state == DEVICE_STATE_IN_SHUTDOWN)
return CHANNEL_RC_OK;
if (device->state != DEVICE_STATE_PENDING_PROPERTY_VALUE_RESPONSE)
{
g_warning ("[RDP.CAM_DEVICE] Protocol violation: Received stray "
"property value response in state %s for device \"%s\". "
"Removing device...",
device_state_to_string (device->state), device->device_name);
transition_into_fatal_error_state (device);
return CHANNEL_RC_OK;
}
property_value_request = g_queue_pop_head (device->pending_property_values);
g_debug ("[RDP.CAM_DEVICE] Device \"%s\": PropertySet: 0x%02X, "
"PropertyId: 0x%02X, Mode: 0x%02X, Value: %i",
device->device_name, property_value_request->PropertySet,
property_value_request->PropertyId,
property_value->Mode, property_value->Value);
if (g_queue_get_length (device->pending_property_values) > 0)
request_next_property_value (device);
else
start_preparing_streams (device);
return CHANNEL_RC_OK;
}
GrdRdpDvcCameraDevice *
grd_rdp_dvc_camera_device_new (GrdRdpDvcHandler *dvc_handler,
HANDLE vcm,
rdpContext *rdp_context,
uint8_t protocol_version,
const char *dvc_name,
const char *device_name)
{
g_autoptr (GrdRdpDvcCameraDevice) device = NULL;
CameraDeviceServerContext *device_context;
device = g_object_new (GRD_TYPE_RDP_DVC_CAMERA_DEVICE, NULL);
device_context = camera_device_server_context_new (vcm);
if (!device_context)
g_error ("[RDP.CAM_DEVICE] Failed to allocate server context (OOM)");
device->device_context = device_context;
device->dvc_name = g_strdup (dvc_name);
device->device_name = g_strdup (device_name);
grd_rdp_dvc_initialize_base (GRD_RDP_DVC (device),
dvc_handler, NULL,
GRD_RDP_CHANNEL_CAMERA);
device_context->virtualChannelName = g_strdup (dvc_name);
device_context->protocolVersion = protocol_version;
device_context->ChannelIdAssigned = device_channel_id_assigned;
device_context->SuccessResponse = device_success_response;
device_context->ErrorResponse = device_error_response;
device_context->StreamListResponse = device_stream_list_response;
device_context->MediaTypeListResponse = device_media_type_list_response;
device_context->CurrentMediaTypeResponse = device_current_media_type_response;
device_context->SampleResponse = device_sample_response;
device_context->SampleErrorResponse = device_sample_error_response;
device_context->PropertyListResponse = device_property_list_response;
device_context->PropertyValueResponse = device_property_value_response;
device_context->rdpcontext = rdp_context;
device_context->userdata = device;
/* Check whether camera thread was able to successfully initialize PipeWire */
if (!grd_sync_point_wait_for_completion (&device->sync_point))
return NULL;
/*
* The DRDYNVC virtual channel is already initialized here,
* as the enumerator channel also uses it
*/
if (device_context->Open (device_context))
{
g_warning ("[RDP.CAM_DEVICE] Device \"%s\": Failed to open the %s "
"channel. Terminating protocol", device_name, dvc_name);
return NULL;
}
device->channel_opened = TRUE;
return g_steal_pointer (&device);
}
static gboolean
tear_down_streams (gpointer user_data)
{
GrdRdpDvcCameraDevice *device = user_data;
g_hash_table_remove_all (device->stream_contexts);
if (device->sample_timeout_source)
{
g_source_destroy (device->sample_timeout_source);
g_clear_pointer (&device->sample_timeout_source, g_source_unref);
}
grd_sync_point_complete (&device->sync_point, TRUE);
return G_SOURCE_REMOVE;
}
static gboolean
source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
g_source_set_ready_time (source, -1);
return callback (user_data);
}
static GSourceFuncs source_funcs =
{
.dispatch = source_dispatch,
};
static void
destroy_camera_streams_in_camera_thread (GrdRdpDvcCameraDevice *device)
{
GSource *stream_teardown_source;
g_mutex_lock (&device->state_mutex);
device->state = DEVICE_STATE_IN_SHUTDOWN;
g_mutex_unlock (&device->state_mutex);
grd_sync_point_reset (&device->sync_point);
stream_teardown_source = g_source_new (&source_funcs, sizeof (GSource));
g_source_set_callback (stream_teardown_source, tear_down_streams,
device, NULL);
g_source_set_ready_time (stream_teardown_source, 0);
g_source_attach (stream_teardown_source, device->camera_context);
g_source_unref (stream_teardown_source);
grd_sync_point_wait_for_completion (&device->sync_point);
}
static void
stop_camera_thread (GrdRdpDvcCameraDevice *device)
{
destroy_camera_streams_in_camera_thread (device);
device->in_shutdown = TRUE;
g_main_context_wakeup (device->camera_context);
g_clear_pointer (&device->camera_thread, g_thread_join);
}
static void
grd_rdp_dvc_camera_device_dispose (GObject *object)
{
GrdRdpDvcCameraDevice *device = GRD_RDP_DVC_CAMERA_DEVICE (object);
GrdRdpDvc *dvc = GRD_RDP_DVC (device);
if (device->camera_thread)
stop_camera_thread (device);
g_assert (g_hash_table_size (device->stream_contexts) == 0);
g_assert (!device->sample_timeout_source);
if (device->channel_opened)
{
device->device_context->Close (device->device_context);
device->channel_opened = FALSE;
}
grd_rdp_dvc_maybe_unsubscribe_creation_status (dvc);
g_assert (!device->pipewire_core);
g_assert (!device->pipewire_context);
g_assert (!device->pipewire_source);
if (device->event_source)
{
g_source_destroy (device->event_source);
g_clear_pointer (&device->event_source, g_source_unref);
}
if (device->initialization_source)
{
g_source_destroy (device->initialization_source);
g_clear_pointer (&device->initialization_source, g_source_unref);
}
g_clear_pointer (&device->camera_context, g_main_context_unref);
g_clear_pointer (&device->pending_samples, g_hash_table_unref);
g_clear_pointer (&device->pending_events, g_hash_table_unref);
g_clear_pointer (&device->running_streams, g_hash_table_unref);
g_clear_pointer (&device->pending_stream_starts, g_hash_table_unref);
g_clear_pointer (&device->queued_stream_starts, g_hash_table_unref);
if (device->pending_property_values)
{
g_queue_free_full (device->pending_property_values, g_free);
device->pending_property_values = NULL;
}
g_clear_pointer (&device->pending_media_type_lists, g_queue_free);
g_clear_pointer (&device->device_name, g_free);
g_clear_pointer (&device->dvc_name, g_free);
g_clear_pointer (&device->device_context, camera_device_server_context_free);
G_OBJECT_CLASS (grd_rdp_dvc_camera_device_parent_class)->dispose (object);
}
static void
grd_rdp_dvc_camera_device_finalize (GObject *object)
{
GrdRdpDvcCameraDevice *device = GRD_RDP_DVC_CAMERA_DEVICE (object);
grd_sync_point_clear (&device->sync_point);
g_mutex_clear (&device->sample_request_mutex);
g_mutex_clear (&device->client_request_mutex);
g_mutex_clear (&device->event_mutex);
g_mutex_clear (&device->state_mutex);
g_assert (g_hash_table_size (device->stream_contexts) == 0);
g_clear_pointer (&device->stream_contexts, g_hash_table_unref);
G_OBJECT_CLASS (grd_rdp_dvc_camera_device_parent_class)->finalize (object);
}
static void
pipewire_core_error (void *user_data,
uint32_t id,
int seq,
int res,
const char *message)
{
GrdRdpDvcCameraDevice *device = user_data;
g_warning ("[RDP.CAM_DEVICE] Device \"%s\": PipeWire core error: "
"id: %u, seq: %i, res: %i, %s",
device->device_name, id, seq, res, message);
if (id == PW_ID_CORE && res == -EPIPE)
g_signal_emit (device, signals[ERROR], 0);
}
static const struct pw_core_events pipewire_core_events =
{
.version = PW_VERSION_CORE_EVENTS,
.error = pipewire_core_error,
};
static gboolean
set_up_pipewire (GrdRdpDvcCameraDevice *device,
GError **error)
{
GrdPipeWireSource *pipewire_source;
pipewire_source = grd_pipewire_source_new ("RDP.CAM_DEVICE", error);
if (!pipewire_source)
return FALSE;
device->pipewire_source = (GSource *) pipewire_source;
device->pipewire_context =
pw_context_new (pipewire_source->pipewire_loop, NULL, 0);
if (!device->pipewire_context)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create PipeWire context");
return FALSE;
}
device->pipewire_core =
pw_context_connect (device->pipewire_context, NULL, 0);
if (!device->pipewire_core)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create PipeWire core");
return FALSE;
}
pw_core_add_listener (device->pipewire_core,
&device->pipewire_core_listener,
&pipewire_core_events, device);
g_source_attach (device->pipewire_source, device->camera_context);
return TRUE;
}
static void
stop_pipewire (GrdRdpDvcCameraDevice *device)
{
if (device->pipewire_core)
{
spa_hook_remove (&device->pipewire_core_listener);
g_clear_pointer (&device->pipewire_core, pw_core_disconnect);
}
g_clear_pointer (&device->pipewire_context, pw_context_destroy);
if (device->pipewire_source)
{
g_source_destroy (device->pipewire_source);
g_clear_pointer (&device->pipewire_source, g_source_unref);
}
}
static gpointer
camera_thread_func (gpointer data)
{
GrdRdpDvcCameraDevice *device = data;
g_autoptr (GError) error = NULL;
gboolean success;
pw_init (NULL, NULL);
success = set_up_pipewire (device, &error);
if (!success)
{
g_warning ("[RDP.CAM_DEVICE] Device \"%s\": Failed to set up PipeWire: "
"%s", device->device_name, error->message);
}
grd_sync_point_complete (&device->sync_point, success);
while (!device->in_shutdown)
g_main_context_iteration (device->camera_context, TRUE);
stop_pipewire (device);
pw_deinit ();
return NULL;
}
static void
activate_device (GrdRdpDvcCameraDevice *device)
{
CameraDeviceServerContext *device_context = device->device_context;
CAM_ACTIVATE_DEVICE_REQUEST activate_device_request = {};
device->state = DEVICE_STATE_PENDING_ACTIVATION_RESPONSE;
device_context->ActivateDeviceRequest (device_context,
&activate_device_request);
}
static gboolean
has_supported_media_type (GList *media_type_descriptions)
{
GList *l;
for (l = media_type_descriptions; l; l = l->next)
{
CAM_MEDIA_TYPE_DESCRIPTION *media_type_description = l->data;
/* Sanitize format */
if (media_type_description->FrameRateNumerator == 0 ||
media_type_description->FrameRateDenominator == 0 ||
media_type_description->PixelAspectRatioDenominator == 0)
continue;
if (media_type_description->Format == CAM_MEDIA_FORMAT_H264)
return TRUE;
}
return FALSE;
}
static void
create_streams (GrdRdpDvcCameraDevice *device)
{
StreamContext *stream_context = NULL;
gpointer key = NULL;
GHashTableIter iter;
g_hash_table_iter_init (&iter, device->stream_contexts);
while (g_hash_table_iter_next (&iter, &key, (gpointer *) &stream_context))
{
uint8_t stream_index = GPOINTER_TO_UINT (key);
g_autoptr (GError) error = NULL;
if (!has_supported_media_type (stream_context->media_type_descriptions))
continue;
stream_context->camera_stream =
grd_rdp_camera_stream_new (device,
device->camera_context,
device->pipewire_core,
device->device_name,
stream_index,
stream_context->media_type_descriptions,
&error);
if (!stream_context->camera_stream)
{
g_warning ("[RDP.CAM_DEVICE] Device \"%s\": Failed to create camera "
"stream for stream %u: %s ", device->device_name,
stream_index, error->message);
}
}
device->state = DEVICE_STATE_INITIALIZATION_DONE;
}
static gboolean
initialize_device (gpointer user_data)
{
GrdRdpDvcCameraDevice *device = user_data;
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->state_mutex);
switch (device->state)
{
case DEVICE_STATE_FATAL_ERROR:
case DEVICE_STATE_PENDING_ACTIVATION_RESPONSE:
case DEVICE_STATE_PENDING_STREAM_LIST_RESPONSE:
case DEVICE_STATE_PENDING_MEDIA_TYPE_LIST_RESPONSE:
case DEVICE_STATE_PENDING_PROPERTY_LIST_RESPONSE:
case DEVICE_STATE_PENDING_PROPERTY_VALUE_RESPONSE:
case DEVICE_STATE_INITIALIZATION_DONE:
case DEVICE_STATE_IN_SHUTDOWN:
break;
case DEVICE_STATE_PENDING_ACTIVATION:
activate_device (device);
break;
case DEVICE_STATE_PENDING_STREAM_PREPARATION:
create_streams (device);
break;
}
return G_SOURCE_CONTINUE;
}
static gboolean
has_pending_ack_event (GrdRdpDvcCameraDevice *device)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->event_mutex);
return has_pending_event_unlocked (device, DEVICE_EVENT_ACK_STARTED_STREAMS) ||
has_pending_event_unlocked (device, DEVICE_EVENT_ACK_STOPPED_STREAMS);
}
static gboolean
has_pending_client_success_response (GrdRdpDvcCameraDevice *device)
{
return device->pending_client_request && !has_pending_ack_event (device);
}
static void
set_pending_client_request (GrdRdpDvcCameraDevice *device,
ClientRequestType request_type)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->client_request_mutex);
g_assert (!device->pending_client_request);
g_assert (device->pending_client_request_type == CLIENT_REQUEST_TYPE_NONE);
device->pending_client_request_type = request_type;
device->pending_client_request = TRUE;
}
static void
reset_pending_client_request (GrdRdpDvcCameraDevice *device)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->client_request_mutex);
device->pending_client_request = FALSE;
device->pending_client_request_type = CLIENT_REQUEST_TYPE_NONE;
}
static gboolean
try_pop_event (GrdRdpDvcCameraDevice *device,
DeviceEvent event)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->event_mutex);
return g_hash_table_remove (device->pending_events, GUINT_TO_POINTER (event));
}
static void
ack_started_streams (GrdRdpDvcCameraDevice *device)
{
StreamRunContext *stream_run_context = NULL;
gpointer key = NULL;
GHashTableIter iter;
g_hash_table_iter_init (&iter, device->pending_stream_starts);
while (g_hash_table_iter_next (&iter, &key, (gpointer *) &stream_run_context))
{
uint8_t stream_index = GPOINTER_TO_UINT (key);
StreamContext *stream_context = NULL;
GrdRdpCameraStream *camera_stream;
uint32_t run_sequence;
if (!g_hash_table_lookup_extended (device->stream_contexts,
GUINT_TO_POINTER (stream_index),
NULL, (gpointer *) &stream_context))
g_assert_not_reached ();
camera_stream = stream_context->camera_stream;
run_sequence = stream_run_context->run_sequence;
if (grd_rdp_camera_stream_notify_stream_started (camera_stream,
run_sequence))
{
grd_rdp_camera_stream_uninhibit_camera_loop (camera_stream);
g_hash_table_insert (device->running_streams,
GUINT_TO_POINTER (stream_index),
stream_run_context);
g_hash_table_iter_steal (&iter);
}
else
{
g_hash_table_iter_remove (&iter);
}
}
g_debug ("[RDP.CAM_DEVICE] Device \"%s\": Started streams",
device->device_name);
}
static void
ack_stopped_streams (GrdRdpDvcCameraDevice *device)
{
g_debug ("[RDP.CAM_DEVICE] Device \"%s\": Stopped streams",
device->device_name);
}
static void
maybe_handle_client_success_response (GrdRdpDvcCameraDevice *device)
{
if (!has_pending_ack_event (device))
return;
reset_pending_client_request (device);
if (try_pop_event (device, DEVICE_EVENT_ACK_STARTED_STREAMS))
ack_started_streams (device);
if (try_pop_event (device, DEVICE_EVENT_ACK_STOPPED_STREAMS))
ack_stopped_streams (device);
}
static gboolean
has_pending_sample_requests (GrdRdpDvcCameraDevice *device)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&device->sample_request_mutex);
return g_hash_table_size (device->pending_samples) > 0;
}
static gboolean
discard_sample_requests (gpointer user_data)
{
GrdRdpDvcCameraDevice *device = user_data;
StreamContext *stream_context = NULL;
GHashTableIter iter;
g_warning ("[RDP.CAM_DEVICE] Device \"%s\": Protocol violation: Got no "
"sample response after timeout. Discarding requests...",
device->device_name);
g_clear_pointer (&device->sample_timeout_source, g_source_unref);
g_hash_table_iter_init (&iter, device->stream_contexts);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &stream_context))
discard_pending_sample_requests (stream_context);
g_source_set_ready_time (device->event_source, 0);
return G_SOURCE_REMOVE;
}
static void
maybe_set_sample_timeout (GrdRdpDvcCameraDevice *device)
{
if (device->sample_timeout_source)
return;
g_debug ("[RDP.CAM_DEVICE] Device \"%s\": Waiting for remaining samples "
"before sending stop-streams-request", device->device_name);
device->sample_timeout_source = g_timeout_source_new (SAMPLE_TIMEOUT_MS);
g_source_set_callback (device->sample_timeout_source, discard_sample_requests,
device, NULL);
g_source_attach (device->sample_timeout_source, device->camera_context);
}
static void
stop_camera_streams (GrdRdpDvcCameraDevice *device)
{
CameraDeviceServerContext *device_context = device->device_context;
CAM_STOP_STREAMS_REQUEST stop_streams_request = {};
if (device->sample_timeout_source)
{
g_source_destroy (device->sample_timeout_source);
g_clear_pointer (&device->sample_timeout_source, g_source_unref);
}
g_debug ("[RDP.CAM_DEVICE] Device \"%s\": Sending stop-streams-request",
device->device_name);
set_pending_client_request (device, CLIENT_REQUEST_TYPE_STOP_STREAMS);
device_context->StopStreamsRequest (device_context, &stop_streams_request);
}
static void
start_queued_camera_streams (GrdRdpDvcCameraDevice *device)
{
CameraDeviceServerContext *device_context = device->device_context;
CAM_START_STREAMS_REQUEST start_streams_request = {};
StreamRunContext *stream_run_context = NULL;
gpointer key = NULL;
GHashTableIter iter;
uint16_t i = 0;
g_assert (g_hash_table_size (device->pending_stream_starts) == 0);
g_assert (g_hash_table_size (device->queued_stream_starts) > 0);
start_streams_request.N_Infos =
g_hash_table_size (device->queued_stream_starts);
g_hash_table_iter_init (&iter, device->queued_stream_starts);
while (g_hash_table_iter_next (&iter, &key, (gpointer *) &stream_run_context))
{
CAM_START_STREAM_INFO *start_stream_info;
start_stream_info = &start_streams_request.StartStreamsInfo[i++];
start_stream_info->StreamIndex = GPOINTER_TO_UINT (key);
start_stream_info->MediaTypeDescription =
*stream_run_context->media_type_description;
g_hash_table_insert (device->pending_stream_starts,
key, stream_run_context);
g_hash_table_iter_steal (&iter);
}
g_assert (start_streams_request.N_Infos == i);
g_debug ("[RDP.CAM_DEVICE] Device \"%s\": Sending start-streams-request",
device->device_name);
set_pending_client_request (device, CLIENT_REQUEST_TYPE_START_STREAMS);
device_context->StartStreamsRequest (device_context, &start_streams_request);
}
static gboolean
handle_device_events (gpointer user_data)
{
GrdRdpDvcCameraDevice *device = user_data;
if (has_pending_client_success_response (device))
return G_SOURCE_CONTINUE;
maybe_handle_client_success_response (device);
if (has_pending_sample_requests (device) &&
has_pending_event (device, DEVICE_EVENT_STOP_STREAMS))
maybe_set_sample_timeout (device);
if (!has_pending_sample_requests (device) &&
try_pop_event (device, DEVICE_EVENT_STOP_STREAMS))
stop_camera_streams (device);
else if (g_hash_table_size (device->queued_stream_starts) > 0 &&
!has_pending_event (device, DEVICE_EVENT_STOP_STREAMS))
start_queued_camera_streams (device);
return G_SOURCE_CONTINUE;
}
static void
grd_rdp_dvc_camera_device_init (GrdRdpDvcCameraDevice *device)
{
GSource *initialization_source;
GSource *event_source;
device->state = DEVICE_STATE_PENDING_ACTIVATION;
device->stream_contexts =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) stream_context_free);
device->queued_stream_starts =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) stream_run_context_free);
device->pending_stream_starts =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) stream_run_context_free);
device->running_streams =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) stream_run_context_free);
device->pending_events = g_hash_table_new (NULL, NULL);
device->pending_samples = g_hash_table_new (NULL, NULL);
device->pending_media_type_lists = g_queue_new ();
device->pending_property_values = g_queue_new ();
g_mutex_init (&device->state_mutex);
g_mutex_init (&device->event_mutex);
g_mutex_init (&device->client_request_mutex);
g_mutex_init (&device->sample_request_mutex);
grd_sync_point_init (&device->sync_point);
device->camera_context = g_main_context_new ();
device->camera_thread = g_thread_new ("RDP camera device thread",
camera_thread_func,
device);
initialization_source = g_source_new (&source_funcs, sizeof (GSource));
g_source_set_callback (initialization_source, initialize_device,
device, NULL);
g_source_set_ready_time (initialization_source, -1);
g_source_attach (initialization_source, device->camera_context);
device->initialization_source = initialization_source;
event_source = g_source_new (&source_funcs, sizeof (GSource));
g_source_set_callback (event_source, handle_device_events, device, NULL);
g_source_set_ready_time (event_source, -1);
g_source_attach (event_source, device->camera_context);
device->event_source = event_source;
}
static void
grd_rdp_dvc_camera_device_class_init (GrdRdpDvcCameraDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_rdp_dvc_camera_device_dispose;
object_class->finalize = grd_rdp_dvc_camera_device_finalize;
signals[ERROR] = g_signal_new ("error",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}