diff --git a/channels/client/CMakeLists.txt b/channels/client/CMakeLists.txt index d48428ec1..f1089d1e2 100644 --- a/channels/client/CMakeLists.txt +++ b/channels/client/CMakeLists.txt @@ -75,7 +75,7 @@ foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES}) string(TOUPPER "CLIENT_${STATIC_MODULE_CHANNEL}_SUBSYSTEM_TABLE" SUBSYSTEM_TABLE_NAME) set(SUBSYSTEM_TABLE "const STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[] =\n{") get_target_property(CHANNEL_SUBSYSTEMS ${STATIC_MODULE_NAME} SUBSYSTEMS) - if(CHANNEL_SUBSYSTEMS MATCHES "NOTFOUND") + if(CHANNEL_SUBSYSTEMS MATCHES "NOTFOUND" OR STATIC_MODULE MATCHES "CHANNEL_RDPSNDDYN") # do not generate subsystem table for dynamic rdpsnd channel set(CHANNEL_SUBSYSTEMS "") endif() foreach(STATIC_SUBSYSTEM ${CHANNEL_SUBSYSTEMS}) @@ -101,6 +101,10 @@ foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES}) set(SUBSYSTEM_TABLE "${SUBSYSTEM_TABLE}\n\t{ NULL, NULL, NULL }\n};") set(CLIENT_STATIC_SUBSYSTEM_TABLES "${CLIENT_STATIC_SUBSYSTEM_TABLES}\n${SUBSYSTEM_TABLE}") set(ENTRY_POINT_NAME "${STATIC_MODULE_CHANNEL}_${${STATIC_MODULE}_CLIENT_ENTRY}") + # Little hack for dynamic channels support in rdpsnd + if(STATIC_MODULE MATCHES "CHANNEL_RDPSNDDYN") + set(SUBSYSTEM_TABLE_NAME "CLIENT_RDPSND_SUBSYSTEM_TABLE") + endif() set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", (static_addin_fkt)${ENTRY_POINT_NAME}, ${SUBSYSTEM_TABLE_NAME} },") endforeach() set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ NULL, NULL, NULL }\n};") diff --git a/channels/rdpsnd/CMakeLists.txt b/channels/rdpsnd/CMakeLists.txt index 08b68365a..f77eeeaf6 100644 --- a/channels/rdpsnd/CMakeLists.txt +++ b/channels/rdpsnd/CMakeLists.txt @@ -20,6 +20,13 @@ define_channel("rdpsnd") include_directories(common) add_subdirectory(common) +# Define dynamic entry point of sound channel +set(CHANNEL_STATIC_CLIENT_MODULES ${CHANNEL_STATIC_CLIENT_MODULES} "CHANNEL_RDPSNDDYN") +set(CHANNEL_RDPSNDDYN_CLIENT_NAME "rdpsnd-client" PARENT_SCOPE) +set(CHANNEL_RDPSNDDYN_CLIENT_CHANNEL "rdpsnd_dyn" PARENT_SCOPE) +set(CHANNEL_RDPSNDDYN_CLIENT_ENTRY "DVCPluginEntry" PARENT_SCOPE) +set(CHANNEL_STATIC_CLIENT_ENTRIES ${CHANNEL_STATIC_CLIENT_ENTRIES} ${CHANNEL_RDPSNDDYN_CLIENT_ENTRY}) + if(WITH_CLIENT_CHANNELS) add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) endif() diff --git a/channels/rdpsnd/client/rdpsnd_main.c b/channels/rdpsnd/client/rdpsnd_main.c index 52b98179f..550c80534 100644 --- a/channels/rdpsnd/client/rdpsnd_main.c +++ b/channels/rdpsnd/client/rdpsnd_main.c @@ -50,8 +50,32 @@ #include "rdpsnd_common.h" #include "rdpsnd_main.h" +struct _RDPSND_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; +typedef struct _RDPSND_CHANNEL_CALLBACK RDPSND_CHANNEL_CALLBACK; + +struct _RDPSND_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + RDPSND_CHANNEL_CALLBACK* channel_callback; +}; +typedef struct _RDPSND_LISTENER_CALLBACK RDPSND_LISTENER_CALLBACK; + struct rdpsnd_plugin { + IWTSPlugin iface; + IWTSListener* listener; + RDPSND_LISTENER_CALLBACK* listener_callback; + CHANNEL_DEF channelDef; CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; @@ -539,6 +563,11 @@ static UINT rdpsnd_treat_wave(rdpsndPlugin* rdpsnd, wStream* s, size_t size) end = GetTickCount64(); diffMS = end - rdpsnd->wArrivalTime + latency; ts = (rdpsnd->wTimeStamp + diffMS) % UINT16_MAX; + + /* Don't send wave confirm PDU if not on static channel */ + if (rdpsnd->listener_callback) + return CHANNEL_RC_OK; + return rdpsnd_send_wave_confirm_pdu(rdpsnd, (UINT16)ts, rdpsnd->cBlockNo); } @@ -960,16 +989,25 @@ UINT rdpsnd_virtual_channel_write(rdpsndPlugin* rdpsnd, wStream* s) if (rdpsnd) { - status = rdpsnd->channelEntryPoints.pVirtualChannelWriteEx( - rdpsnd->InitHandle, rdpsnd->OpenHandle, Stream_Buffer(s), (UINT32)Stream_GetPosition(s), - s); - } + if (rdpsnd->listener_callback) + { + IWTSVirtualChannel* channel = rdpsnd->listener_callback->channel_callback->channel; + status = channel->Write(channel, (UINT32)Stream_Length(s), Stream_Buffer(s), NULL); + Stream_Free(s, TRUE); + } + else + { + status = rdpsnd->channelEntryPoints.pVirtualChannelWriteEx( + rdpsnd->InitHandle, rdpsnd->OpenHandle, Stream_Buffer(s), + (UINT32)Stream_GetPosition(s), s); - if (status != CHANNEL_RC_OK) - { - Stream_Free(s, TRUE); - WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]", - WTSErrorToString(status), status); + if (status != CHANNEL_RC_OK) + { + Stream_Free(s, TRUE); + WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + } } return status; @@ -1271,3 +1309,168 @@ BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints, PVOID p return TRUE; } + +static UINT rdpsnd_on_open(IWTSVirtualChannelCallback* pChannelCallback) +{ + RDPSND_CHANNEL_CALLBACK* callback = (RDPSND_CHANNEL_CALLBACK*)pChannelCallback; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)callback->plugin; + + rdpsnd->dsp_context = freerdp_dsp_context_new(FALSE); + if (!rdpsnd->dsp_context) + goto fail; + + rdpsnd->pool = StreamPool_New(TRUE, 4096); + if (!rdpsnd->pool) + goto fail; + + return rdpsnd_process_connect(rdpsnd); +fail: + freerdp_dsp_context_free(rdpsnd->dsp_context); + StreamPool_Free(rdpsnd->pool); + return CHANNEL_RC_NO_MEMORY; +} + +static UINT rdpsnd_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + RDPSND_CHANNEL_CALLBACK* callback = (RDPSND_CHANNEL_CALLBACK*)pChannelCallback; + return rdpsnd_recv_pdu((rdpsndPlugin*)callback->plugin, data); +} + +static UINT rdpsnd_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + RDPSND_CHANNEL_CALLBACK* callback = (RDPSND_CHANNEL_CALLBACK*)pChannelCallback; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)callback->plugin; + + IFCALL(rdpsnd->device->Close, rdpsnd->device); + freerdp_dsp_context_free(rdpsnd->dsp_context); + StreamPool_Return(rdpsnd->pool, rdpsnd->data_in); + StreamPool_Free(rdpsnd->pool); + + audio_formats_free(rdpsnd->ClientFormats, rdpsnd->NumberOfClientFormats); + rdpsnd->NumberOfClientFormats = 0; + rdpsnd->ClientFormats = NULL; + audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats); + rdpsnd->NumberOfServerFormats = 0; + rdpsnd->ServerFormats = NULL; + if (rdpsnd->device) + { + IFCALL(rdpsnd->device->Free, rdpsnd->device); + rdpsnd->device = NULL; + } + + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +static UINT rdpsnd_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + RDPSND_CHANNEL_CALLBACK* callback; + RDPSND_LISTENER_CALLBACK* listener_callback = (RDPSND_LISTENER_CALLBACK*)pListenerCallback; + callback = (RDPSND_CHANNEL_CALLBACK*)calloc(1, sizeof(RDPSND_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnOpen = rdpsnd_on_open; + callback->iface.OnDataReceived = rdpsnd_on_data_received; + callback->iface.OnClose = rdpsnd_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + *ppCallback = (IWTSVirtualChannelCallback*)callback; + return CHANNEL_RC_OK; +} + +static UINT rdpsnd_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pPlugin; + rdpsnd->listener_callback = + (RDPSND_LISTENER_CALLBACK*)calloc(1, sizeof(RDPSND_LISTENER_CALLBACK)); + + if (!rdpsnd->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpsnd->listener_callback->iface.OnNewChannelConnection = rdpsnd_on_new_channel_connection; + rdpsnd->listener_callback->plugin = pPlugin; + rdpsnd->listener_callback->channel_mgr = pChannelMgr; + status = pChannelMgr->CreateListener(pChannelMgr, RDPSND_DVC_CHANNEL_NAME, 0, + (IWTSListenerCallback*)rdpsnd->listener_callback, + &(rdpsnd->listener)); + rdpsnd->listener->pInterface = rdpsnd->iface.pInterface; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_plugin_terminated(IWTSPlugin* pPlugin) +{ + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pPlugin; + free(rdpsnd->listener_callback); + free(rdpsnd->iface.pInterface); + rdpsnd_virtual_channel_event_terminated(rdpsnd); + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry rdpsnd_dyn_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT error = CHANNEL_RC_OK; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pEntryPoints->GetPlugin(pEntryPoints, "rdpsnd_dyn"); + + if (!rdpsnd) + { + rdpsnd = (rdpsndPlugin*)calloc(1, sizeof(rdpsndPlugin)); + if (!rdpsnd) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpsnd->iface.Initialize = rdpsnd_plugin_initialize; + rdpsnd->iface.Connected = NULL; + rdpsnd->iface.Disconnected = NULL; + rdpsnd->iface.Terminated = rdpsnd_plugin_terminated; + rdpsnd->attached = TRUE; + rdpsnd->fixed_format = audio_format_new(); + if (!rdpsnd->fixed_format) + { + free(rdpsnd); + return FALSE; + } + rdpsnd->log = WLog_Get("com.freerdp.channels.rdpsnd.client"); + rdpsnd->channelEntryPoints.pExtendedData = pEntryPoints->GetPluginData(pEntryPoints); + + error = pEntryPoints->RegisterPlugin(pEntryPoints, "rdpsnd_dyn", (IWTSPlugin*)rdpsnd); + } + else + { + WLog_ERR(TAG, "could not get disp Plugin."); + return CHANNEL_RC_BAD_CHANNEL; + } + + return error; +} diff --git a/client/common/cmdline.c b/client/common/cmdline.c index 4783e056d..9a2df5d9f 100644 --- a/client/common/cmdline.c +++ b/client/common/cmdline.c @@ -924,6 +924,11 @@ static int freerdp_client_command_line_post_filter(void* context, COMMAND_LINE_A size_t count; p = CommandLineParseCommaSeparatedValuesEx("rdpsnd", arg->Value, &count); status = freerdp_client_add_static_channel(settings, count, p); + if (status) + { + p[0] = "rdpsnd_dyn"; + status = freerdp_client_add_dynamic_channel(settings, count, p); + } free(p); } CommandLineSwitchCase(arg, "microphone") @@ -2438,35 +2443,35 @@ int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, { const char* val = p[x]; #ifdef WITH_GFX_H264 - if (_strnicmp("AVC444", val, 7) == 0) - { - settings->GfxH264 = TRUE; - settings->GfxAVC444 = TRUE; - } - else if (_strnicmp("AVC420", val, 7) == 0) - { - settings->GfxH264 = TRUE; - settings->GfxAVC444 = FALSE; - } - else -#endif - if (_strnicmp("RFX", val, 4) == 0) - { - settings->GfxAVC444 = FALSE; - settings->GfxH264 = FALSE; - settings->RemoteFxCodec = TRUE; - } - else if (_strnicmp("mask:", val, 5) == 0) - { - ULONGLONG v; - const char* uv = &val[5]; - if (!value_to_uint(uv, &v, 0, UINT32_MAX)) - rc = COMMAND_LINE_ERROR; + if (_strnicmp("AVC444", val, 7) == 0) + { + settings->GfxH264 = TRUE; + settings->GfxAVC444 = TRUE; + } + else if (_strnicmp("AVC420", val, 7) == 0) + { + settings->GfxH264 = TRUE; + settings->GfxAVC444 = FALSE; + } else - settings->GfxCapsFilter = (UINT32)v; - } - else - rc = COMMAND_LINE_ERROR; +#endif + if (_strnicmp("RFX", val, 4) == 0) + { + settings->GfxAVC444 = FALSE; + settings->GfxH264 = FALSE; + settings->RemoteFxCodec = TRUE; + } + else if (_strnicmp("mask:", val, 5) == 0) + { + ULONGLONG v; + const char* uv = &val[5]; + if (!value_to_uint(uv, &v, 0, UINT32_MAX)) + rc = COMMAND_LINE_ERROR; + else + settings->GfxCapsFilter = (UINT32)v; + } + else + rc = COMMAND_LINE_ERROR; } } free(p); @@ -3264,6 +3269,15 @@ BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings) return FALSE; } + /* for audio playback also load the dynamic sound channel */ + if (settings->AudioPlayback) + { + char* p[] = { "rdpsnd_dyn" }; + + if (!freerdp_client_add_dynamic_channel(settings, ARRAYSIZE(p), p)) + return FALSE; + } + if (settings->AudioCapture) { char* p[] = { "audin" }; @@ -3272,7 +3286,8 @@ BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings) return FALSE; } - if ((freerdp_static_channel_collection_find(settings, "rdpsnd")) + if ((freerdp_static_channel_collection_find(settings, "rdpsnd")) || + (freerdp_dynamic_channel_collection_find(settings, "rdpsnd_dyn")) #if defined(CHANNEL_TSMF_CLIENT) || (freerdp_dynamic_channel_collection_find(settings, "tsmf")) #endif @@ -3397,7 +3412,8 @@ BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings) if (!freerdp_client_load_static_channel_addin(channels, settings, "rdpdr", settings)) return FALSE; - if (!freerdp_static_channel_collection_find(settings, "rdpsnd")) + if (!freerdp_static_channel_collection_find(settings, "rdpsnd") && + !freerdp_dynamic_channel_collection_find(settings, "rdpsnd_dyn")) { char* params[2]; params[0] = "rdpsnd"; diff --git a/include/freerdp/client/rdpsnd.h b/include/freerdp/client/rdpsnd.h index e78d9340a..8050aa399 100644 --- a/include/freerdp/client/rdpsnd.h +++ b/include/freerdp/client/rdpsnd.h @@ -23,6 +23,8 @@ #include +#define RDPSND_DVC_CHANNEL_NAME "AUDIO_PLAYBACK_DVC" + /** * Subsystem Interface */