From c5170a00e0266694c629e9d208bd68dd5c9668eb Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Thu, 27 Dec 2012 19:19:52 +0800 Subject: [PATCH] channels/rdpsnd: support wfreerdp using Windows Multimedia API. --- channels/rdpsnd/client/CMakeLists.txt | 4 + channels/rdpsnd/client/rdpsnd_main.c | 7 + channels/rdpsnd/client/winmm/CMakeLists.txt | 45 +++ channels/rdpsnd/client/winmm/rdpsnd_winmm.c | 290 ++++++++++++++++++++ cmake/ConfigOptions.cmake | 1 + 5 files changed, 347 insertions(+) create mode 100644 channels/rdpsnd/client/winmm/CMakeLists.txt create mode 100644 channels/rdpsnd/client/winmm/rdpsnd_winmm.c diff --git a/channels/rdpsnd/client/CMakeLists.txt b/channels/rdpsnd/client/CMakeLists.txt index 7e318d984..bdf84756a 100644 --- a/channels/rdpsnd/client/CMakeLists.txt +++ b/channels/rdpsnd/client/CMakeLists.txt @@ -50,3 +50,7 @@ endif() if(WITH_MACAUDIO) add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "") endif() + +if(WITH_WINMM) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "") +endif() diff --git a/channels/rdpsnd/client/rdpsnd_main.c b/channels/rdpsnd/client/rdpsnd_main.c index 451fc5023..431cb1b35 100644 --- a/channels/rdpsnd/client/rdpsnd_main.c +++ b/channels/rdpsnd/client/rdpsnd_main.c @@ -624,6 +624,13 @@ static void rdpsnd_process_connect(rdpSvcPlugin* plugin) rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args); } + if (!rdpsnd->device) + { + rdpsnd_set_subsystem(rdpsnd, "winmm"); + rdpsnd_set_device_name(rdpsnd, ""); + rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args); + } + if (rdpsnd->device == NULL) { DEBUG_WARN("no sound device."); diff --git a/channels/rdpsnd/client/winmm/CMakeLists.txt b/channels/rdpsnd/client/winmm/CMakeLists.txt new file mode 100644 index 000000000..4eca824d7 --- /dev/null +++ b/channels/rdpsnd/client/winmm/CMakeLists.txt @@ -0,0 +1,45 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "winmm" "") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_winmm.c) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + +set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "") + +set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS + MONOLITHIC ${MONOLITHIC_BUILD} + MODULE freerdp + MODULES freerdp-utils) + +set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS + MONOLITHIC ${MONOLITHIC_BUILD} + MODULE winpr + MODULES winpr-utils) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winmm.lib) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +if(NOT STATIC_CHANNELS) + install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_ADDIN_PATH}) +endif() diff --git a/channels/rdpsnd/client/winmm/rdpsnd_winmm.c b/channels/rdpsnd/client/winmm/rdpsnd_winmm.c new file mode 100644 index 000000000..dc4bd7f14 --- /dev/null +++ b/channels/rdpsnd/client/winmm/rdpsnd_winmm.c @@ -0,0 +1,290 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2009-2012 Jay Sorg + * Copyright 2010-2012 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_winmm_datablock rdpsndWinmmDatablock; + +struct rdpsnd_winmm_datablock +{ + WAVEHDR header; + rdpsndWinmmDatablock* next; +}; + +typedef struct rdpsnd_winmm_plugin rdpsndWinmmPlugin; + +struct rdpsnd_winmm_plugin +{ + rdpsndDevicePlugin device; + + HWAVEOUT out_handle; + WAVEFORMATEX format; + int wformat; + int block_size; + int latency; + HANDLE event; + rdpsndWinmmDatablock* datablock_head; + + FREERDP_DSP_CONTEXT* dsp_context; +}; + +static BOOL rdpsnd_winmm_convert_format(const rdpsndFormat* in, WAVEFORMATEX* out) +{ + BOOL result = FALSE; + + ZeroMemory(out, sizeof(WAVEFORMATEX)); + out->wFormatTag = WAVE_FORMAT_PCM; + out->nChannels = in->nChannels; + out->nSamplesPerSec = in->nSamplesPerSec; + switch (in->wFormatTag) + { + case WAVE_FORMAT_PCM: + out->wBitsPerSample = in->wBitsPerSample; + result = TRUE; + break; + + case 2: /* MS ADPCM */ + case 0x11: /* IMA ADPCM */ + out->wBitsPerSample = 16; + result = TRUE; + break; + } + out->nBlockAlign = out->nChannels * out->wBitsPerSample / 8; + out->nAvgBytesPerSec = out->nSamplesPerSec * out->nBlockAlign; + + return result; +} + +static void rdpsnd_winmm_clear_datablocks(rdpsndWinmmPlugin* winmm, BOOL drain) +{ + rdpsndWinmmDatablock* datablock; + + while ((datablock = winmm->datablock_head) != NULL) + { + if (!drain && (datablock->header.dwFlags & WHDR_DONE) == 0) + break; + while ((datablock->header.dwFlags & WHDR_DONE) == 0) + WaitForSingleObject(winmm->event, INFINITE); + winmm->datablock_head = datablock->next; + free(datablock->header.lpData); + free(datablock); + } +} + +static void rdpsnd_winmm_set_format(rdpsndDevicePlugin* device, rdpsndFormat* format, int latency) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (format != NULL) + { + rdpsnd_winmm_convert_format(format, &winmm->format); + + winmm->wformat = format->wFormatTag; + winmm->block_size = format->nBlockAlign; + } + + winmm->latency = latency; +} + +static void rdpsnd_winmm_open(rdpsndDevicePlugin* device, rdpsndFormat* format, int latency) +{ + MMRESULT result; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + + if (winmm->out_handle != NULL) + return; + + DEBUG_SVC("opening"); + + rdpsnd_winmm_set_format(device, format, latency); + freerdp_dsp_context_reset_adpcm(winmm->dsp_context); + + result = waveOutOpen(&winmm->out_handle, WAVE_MAPPER, &winmm->format, (DWORD_PTR) winmm->event, 0, CALLBACK_EVENT); + if (result != MMSYSERR_NOERROR) + { + DEBUG_WARN("waveOutOpen failed: %d", result); + } +} + +static void rdpsnd_winmm_close(rdpsndDevicePlugin* device) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (winmm->out_handle != NULL) + { + DEBUG_SVC("close"); + rdpsnd_winmm_clear_datablocks(winmm, TRUE); + if (waveOutClose(winmm->out_handle) != MMSYSERR_NOERROR) + { + DEBUG_WARN("waveOutClose error"); + } + winmm->out_handle = NULL; + } +} + +static void rdpsnd_winmm_free(rdpsndDevicePlugin* device) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + rdpsnd_winmm_close(device); + freerdp_dsp_context_free(winmm->dsp_context); + CloseHandle(winmm->event); + free(winmm); +} + +static BOOL rdpsnd_winmm_format_supported(rdpsndDevicePlugin* device, rdpsndFormat* format) +{ + MMRESULT result; + WAVEFORMATEX out; + + if (rdpsnd_winmm_convert_format(format, &out)) + { + result = waveOutOpen(NULL, WAVE_MAPPER, &out, 0, 0, WAVE_FORMAT_QUERY); + if (result == MMSYSERR_NOERROR) + return TRUE; + } + + return FALSE; +} + +static void rdpsnd_winmm_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + + if (winmm->out_handle == NULL) + return; + + waveOutSetVolume(winmm->out_handle, value); +} + +static void rdpsnd_winmm_play(rdpsndDevicePlugin* device, BYTE* data, int size) +{ + BYTE* src; + MMRESULT result; + rdpsndWinmmDatablock* last; + rdpsndWinmmDatablock* datablock; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + + if (winmm->out_handle == NULL) + return; + + if (winmm->wformat == 2) + { + winmm->dsp_context->decode_ms_adpcm(winmm->dsp_context, + data, size, winmm->format.nChannels, winmm->block_size); + size = winmm->dsp_context->adpcm_size; + src = winmm->dsp_context->adpcm_buffer; + } + else if (winmm->wformat == 0x11) + { + winmm->dsp_context->decode_ima_adpcm(winmm->dsp_context, + data, size, winmm->format.nChannels, winmm->block_size); + size = winmm->dsp_context->adpcm_size; + src = winmm->dsp_context->adpcm_buffer; + } + else + { + src = data; + } + + rdpsnd_winmm_clear_datablocks(winmm, FALSE); + for (last = winmm->datablock_head; last && last->next; last = last->next) + { + } + datablock = (rdpsndWinmmDatablock*) malloc(sizeof(rdpsndWinmmDatablock)); + ZeroMemory(datablock, sizeof(rdpsndWinmmDatablock)); + if (last) + last->next = datablock; + else + winmm->datablock_head = datablock; + datablock->header.dwBufferLength = size; + datablock->header.lpData = (LPSTR) malloc(size); + CopyMemory(datablock->header.lpData, src, size); + + result = waveOutPrepareHeader(winmm->out_handle, &datablock->header, sizeof(datablock->header)); + if (result != MMSYSERR_NOERROR) + { + DEBUG_WARN("waveOutPrepareHeader: %d", result); + return; + } + ResetEvent(winmm->event); + waveOutWrite(winmm->out_handle, &datablock->header, sizeof(datablock->header)); +} + +static void rdpsnd_winmm_start(rdpsndDevicePlugin* device) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + + rdpsnd_winmm_clear_datablocks(winmm, FALSE); +} + +static void rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args) +{ +} + +#ifdef STATIC_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry winmm_freerdp_rdpsnd_client_subsystem_entry +#endif + +int freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + rdpsndWinmmPlugin* winmm; + + winmm = (rdpsndWinmmPlugin*) malloc(sizeof(rdpsndWinmmPlugin)); + ZeroMemory(winmm, sizeof(rdpsndWinmmPlugin)); + + winmm->device.Open = rdpsnd_winmm_open; + winmm->device.FormatSupported = rdpsnd_winmm_format_supported; + winmm->device.SetFormat = rdpsnd_winmm_set_format; + winmm->device.SetVolume = rdpsnd_winmm_set_volume; + winmm->device.Play = rdpsnd_winmm_play; + winmm->device.Start = rdpsnd_winmm_start; + winmm->device.Close = rdpsnd_winmm_close; + winmm->device.Free = rdpsnd_winmm_free; + + args = pEntryPoints->args; + rdpsnd_winmm_parse_addin_args((rdpsndDevicePlugin*) winmm, args); + + winmm->dsp_context = freerdp_dsp_context_new(); + winmm->event = CreateEvent(NULL, TRUE, FALSE, NULL); + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*) winmm); + + return 0; +} diff --git a/cmake/ConfigOptions.cmake b/cmake/ConfigOptions.cmake index ebe145f2c..7f7c09be9 100644 --- a/cmake/ConfigOptions.cmake +++ b/cmake/ConfigOptions.cmake @@ -30,6 +30,7 @@ endif() if(MSVC) option(WITH_NATIVE_SSPI "Use native SSPI modules" ON) + option(WITH_WINMM "Use Windows Multimedia" ON) option(WITH_WIN8 "Use Windows 8 libraries" OFF) endif()