/* * 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); }