diff --git a/channels/rdpsnd/client/oss/CMakeLists.txt b/channels/rdpsnd/client/oss/CMakeLists.txt new file mode 100644 index 000000000000..c5f9618fca40 --- /dev/null +++ b/channels/rdpsnd/client/oss/CMakeLists.txt @@ -0,0 +1,38 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan +# +# 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" "oss" "") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_oss.c) + +include_directories(..) +include_directories(${OSS_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${OSS_LIBRARIES}) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_ADDIN_PATH} EXPORT FreeRDPTargets) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/OSS") diff --git a/channels/rdpsnd/client/oss/rdpsnd_oss.c b/channels/rdpsnd/client/oss/rdpsnd_oss.c new file mode 100644 index 000000000000..540ac5a8dcc0 --- /dev/null +++ b/channels/rdpsnd/client/oss/rdpsnd_oss.c @@ -0,0 +1,431 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright (c) 2015 Rozhuk Ivan + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_oss_plugin rdpsndOssPlugin; + +struct rdpsnd_oss_plugin { + rdpsndDevicePlugin device; + + int pcm_handle; + int mixer_handle; + int dev_unit; + + int supported_formats; + + int latency; + AUDIO_FORMAT format; + + FREERDP_DSP_CONTEXT *dsp_context; +}; + +#define OSS_LOG_ERR(_text, _error) \ + if (_error != 0) \ + WLog_ERR(TAG, "%s: %i - %s\n", _text, _error, strerror(_error)); + + +static int rdpsnd_oss_get_format(AUDIO_FORMAT *format) { + + switch (format->wFormatTag) { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) { + case 8: + return AFMT_S8; + case 16: + return AFMT_S16_LE; + } + break; + case WAVE_FORMAT_ALAW: + return AFMT_A_LAW; +#if 0 /* This does not work on my desktop. */ + case WAVE_FORMAT_MULAW: + return AFMT_MU_LAW; +#endif + case WAVE_FORMAT_ADPCM: + case WAVE_FORMAT_DVI_ADPCM: + return AFMT_S16_LE; + } + + return 0; +} + +static BOOL rdpsnd_oss_format_supported(rdpsndDevicePlugin *device, AUDIO_FORMAT *format) { + int req_fmt = 0; + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + if (device == NULL || format == NULL) + return FALSE; + + switch (format->wFormatTag) { + case WAVE_FORMAT_PCM: + if (format->cbSize != 0 || + format->nSamplesPerSec > 48000 || + (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) || + (format->nChannels != 1 && format->nChannels != 2)) + return FALSE; + break; + case WAVE_FORMAT_ADPCM: + case WAVE_FORMAT_DVI_ADPCM: + if (format->nSamplesPerSec > 48000 || + format->wBitsPerSample != 4 || + (format->nChannels != 1 && format->nChannels != 2)) + return FALSE; + break; + } + + req_fmt = rdpsnd_oss_get_format(format); + if (oss->pcm_handle != -1) { /* Check really supported formats by dev. */ + if ((req_fmt & oss->supported_formats) == 0) + return FALSE; + if ((AFMT_STEREO & oss->supported_formats) == 0 && format->nChannels == 2) + return FALSE; + } else { + if (req_fmt == 0) + return FALSE; + } + + return TRUE; +} + +static void rdpsnd_oss_set_format(rdpsndDevicePlugin *device, AUDIO_FORMAT *format, int latency) { + int tmp; + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + if (device == NULL || oss->pcm_handle == -1 || format == NULL) + return; + + oss->latency = latency; + CopyMemory(&(oss->format), format, sizeof(AUDIO_FORMAT)); + + tmp = rdpsnd_oss_get_format(format); + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed.", errno); + tmp = format->nChannels; + if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed.", errno); + tmp = format->nSamplesPerSec; + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SPEED failed.", errno); + tmp = format->nBlockAlign; + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed.", errno); +} + +static void rdpsnd_oss_open_mixer(rdpsndOssPlugin *oss) { + int devmask = 0; + char mixer[PATH_MAX] = "/dev/mixer"; + + if (oss->mixer_handle != -1) + return; + + if (oss->dev_unit != -1) + snprintf(mixer, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit); + if ((oss->mixer_handle = open(mixer, O_RDWR)) < 0) { + OSS_LOG_ERR("mixer open failed", errno); + oss->mixer_handle = -1; + return; + } + if (ioctl(oss->mixer_handle, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) { + OSS_LOG_ERR("SOUND_MIXER_READ_DEVMASK failed", errno); + close(oss->mixer_handle); + oss->mixer_handle = -1; + return; + } +} + +static void rdpsnd_oss_open(rdpsndDevicePlugin *device, AUDIO_FORMAT *format, int latency) { + int mask = 0; + char dev_name[PATH_MAX] = "/dev/dsp"; + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + if (device == NULL || oss->pcm_handle != -1) + return; + + if (oss->dev_unit != -1) + snprintf(dev_name, PATH_MAX - 1, "/dev/dsp%i", oss->dev_unit); + if ((oss->pcm_handle = open(dev_name, O_WRONLY)) < 0) { + OSS_LOG_ERR("sound dev open failed", errno); + oss->pcm_handle = -1; + return; + } +#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_OUTPUT flag. */ + if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETCAPS, &mask) == -1) { + OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory.", errno); + } else if ((mask & PCM_CAP_OUTPUT) == 0) { + OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return; + } +#endif + if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &oss->supported_formats) == -1) { + OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed.", errno); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return; + } + freerdp_dsp_context_reset_adpcm(oss->dsp_context); + rdpsnd_oss_set_format(device, format, latency); + rdpsnd_oss_open_mixer(oss); +} + +static void rdpsnd_oss_close(rdpsndDevicePlugin *device) { + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + if (device == NULL) + return; + + if (oss->pcm_handle != -1) { + close(oss->pcm_handle); + oss->pcm_handle = -1; + } + + if (oss->mixer_handle != -1) { + close(oss->mixer_handle); + oss->mixer_handle = -1; + } +} + +static void rdpsnd_oss_free(rdpsndDevicePlugin *device) { + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + if (device == NULL) + return; + + rdpsnd_oss_close(device); + freerdp_dsp_context_free(oss->dsp_context); + + free(oss); +} + +static UINT32 rdpsnd_oss_get_volume(rdpsndDevicePlugin *device) { + int vol; + UINT32 dwVolume; + UINT16 dwVolumeLeft, dwVolumeRight; + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + /* On error return 50% volume. */ + dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */ + dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */ + dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight); + + if (device == NULL || oss->mixer_handle == -1) + return dwVolume; + + if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1) { + OSS_LOG_ERR("MIXER_READ", errno); + return dwVolume; + } + + dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100); + dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100); + dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight); + + return dwVolume; +} + +static void rdpsnd_oss_set_volume(rdpsndDevicePlugin *device, UINT32 value) { + int left, right; + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + if (device == NULL || oss->mixer_handle == -1) + return; + + left = (value & 0xFFFF); + right = ((value >> 16) & 0xFFFF); + + if (left < 0) + left = 0; + else if (left > 100) + left = 100; + if (right < 0) + right = 0; + else if (right > 100) + right = 100; + + left |= (right << 8); + if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1) + OSS_LOG_ERR("WRITE_MIXER", errno); +} + +static void rdpsnd_oss_wave_decode(rdpsndDevicePlugin *device, RDPSND_WAVE *wave) { + int size; + BYTE *data; + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + if (device == NULL || wave == NULL) + return; + + switch (oss->format.wFormatTag) { + case WAVE_FORMAT_ADPCM: + oss->dsp_context->decode_ms_adpcm(oss->dsp_context, + wave->data, wave->length, oss->format.nChannels, oss->format.nBlockAlign); + size = oss->dsp_context->adpcm_size; + data = oss->dsp_context->adpcm_buffer; + break; + case WAVE_FORMAT_DVI_ADPCM: + oss->dsp_context->decode_ima_adpcm(oss->dsp_context, + wave->data, wave->length, oss->format.nChannels, oss->format.nBlockAlign); + size = oss->dsp_context->adpcm_size; + data = oss->dsp_context->adpcm_buffer; + break; + default: + size = wave->length; + data = wave->data; + } + + wave->data = (BYTE*)malloc(size); + CopyMemory(wave->data, data, size); + if (wave->length != size) + WLog_ERR(TAG, "wave->length = %i, size = %i!!!\n", wave->length, size); + wave->length = size; +} + +static void rdpsnd_oss_wave_play(rdpsndDevicePlugin *device, RDPSND_WAVE *wave) { + BYTE *data; + int offset, size, status; + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + if (device == NULL || wave == NULL) + return; + + offset = 0; + data = wave->data; + size = wave->length; + + while (offset < size) { + status = write(oss->pcm_handle, &data[offset], (size - offset)); + if (status < 0) { + OSS_LOG_ERR("write fail", errno); + rdpsnd_oss_close(device); + rdpsnd_oss_open(device, NULL, 0); + break; + } + offset += status; + } + /* From rdpsnd_main.c */ + wave->wTimeStampB = wave->wTimeStampA + wave->wAudioLength + 65; + wave->wLocalTimeB = wave->wLocalTimeA + wave->wAudioLength + 65; + + free(data); +} + + +static COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "device" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin *device, ADDIN_ARGV *args) { + int status; + char *str_num, *eptr; + DWORD flags; + COMMAND_LINE_ARGUMENT_A *arg; + rdpsndOssPlugin *oss = (rdpsndOssPlugin*)device; + + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON; + + status = CommandLineParseArgumentsA(args->argc, (const char**)args->argv, rdpsnd_oss_args, flags, oss, NULL, NULL); + if (status < 0) + return status; + + arg = rdpsnd_oss_args; + + do { + WLog_ERR(TAG, "arg: %s\n", arg->Name); + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + + CommandLineSwitchCase(arg, "dev") { + str_num = _strdup(arg->Value); + oss->dev_unit = strtol(str_num, &eptr, 10); + if (oss->dev_unit < 0 || *eptr != '\0') + oss->dev_unit = -1; + free(str_num); + } + + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return status; +} + +#ifdef STATIC_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry oss_freerdp_rdpsnd_client_subsystem_entry +#endif + +int freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) { + ADDIN_ARGV *args; + rdpsndOssPlugin *oss; + + oss = (rdpsndOssPlugin*)malloc(sizeof(rdpsndOssPlugin)); + ZeroMemory(oss, sizeof(rdpsndOssPlugin)); + + oss->device.Open = rdpsnd_oss_open; + oss->device.FormatSupported = rdpsnd_oss_format_supported; + oss->device.SetFormat = rdpsnd_oss_set_format; + oss->device.GetVolume = rdpsnd_oss_get_volume; + oss->device.SetVolume = rdpsnd_oss_set_volume; + oss->device.WaveDecode = rdpsnd_oss_wave_decode; + oss->device.WavePlay = rdpsnd_oss_wave_play; + oss->device.Close = rdpsnd_oss_close; + oss->device.Free = rdpsnd_oss_free; + + oss->pcm_handle = -1; + oss->mixer_handle = -1; + oss->dev_unit = -1; + + args = pEntryPoints->args; + rdpsnd_oss_parse_addin_args((rdpsndDevicePlugin*)oss, args); + + oss->dsp_context = freerdp_dsp_context_new(); + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss); + + return 0; +} diff --git a/cmake/FindOSS.cmake b/cmake/FindOSS.cmake new file mode 100644 index 000000000000..fa8f9f16f3ba --- /dev/null +++ b/cmake/FindOSS.cmake @@ -0,0 +1,40 @@ +# +# Find OSS include header for Unix platforms. +# used by FQTerm to detect the availability of OSS. + +IF(UNIX) + IF(CMAKE_SYSTEM_NAME MATCHES "Linux") + SET(OSS_HDR_NAME "linux/soundcard.h") + ELSE(CMAKE_SYSTEM_NAME MATCHES "Linux") + IF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + SET(OSS_HDR_NAME "sys/soundcard.h") + ELSE(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + SET(OSS_HDR_NAME "machine/soundcard.h") + ENDIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + ENDIF(CMAKE_SYSTEM_NAME MATCHES "Linux") +ENDIF(UNIX) + +FIND_PATH(OSS_INCLUDE_DIR "${OSS_HDR_NAME}" + "/usr/include" "/usr/local/include" +) + +IF(OSS_INCLUDE_DIR) + SET(OSS_FOUND TRUE) +ELSE(OSS_INCLUDE_DIR) + SET(OSS_FOUND) +ENDIF(OSS_INCLUDE_DIR) + +IF(OSS_FOUND) + MESSAGE(STATUS "Found OSS Audio") +ELSE(OSS_FOUND) + IF(OSS_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "FAILED to found Audio - REQUIRED") + ELSE(OSS_FIND_REQUIRED) + MESSAGE(STATUS "Audio Disabled") + ENDIF(OSS_FIND_REQUIRED) +ENDIF(OSS_FOUND) + +MARK_AS_ADVANCED ( + OSS_FOUND + OSS_INCLUDE_DIR +)