Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# 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("audin")
if(WITH_CLIENT_CHANNELS)
add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
endif()
if(WITH_SERVER_CHANNELS)
add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
endif()

View File

@@ -0,0 +1,24 @@
set(OPTION_DEFAULT ON)
set(OPTION_CLIENT_DEFAULT ON)
set(OPTION_SERVER_DEFAULT ON)
if(ANDROID)
set(OPTION_SERVER_DEFAULT OFF)
endif()
define_channel_options(
NAME
"audin"
TYPE
"dynamic"
DESCRIPTION
"Audio Input Redirection Virtual Channel Extension"
SPECIFICATIONS
"[MS-RDPEAI]"
DEFAULT
${OPTION_DEFAULT}
CLIENT_DEFAULT
${OPTION_CLIENT_DEFAULT}
SERVER_DEFAULT
${OPTION_SERVER_DEFAULT}
)

View File

@@ -0,0 +1,58 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# 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("audin")
set(${MODULE_PREFIX}_SRCS audin_main.c audin_main.h)
set(${MODULE_PREFIX}_LIBS freerdp winpr)
include_directories(..)
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
if(WITH_OSS)
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "")
endif()
if(WITH_ALSA)
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "")
endif()
if(WITH_PULSE)
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "")
endif()
if(WITH_OPENSLES)
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "")
endif()
if(WITH_WINMM)
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "")
endif()
if(WITH_MACAUDIO)
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "")
endif()
if(WITH_SNDIO)
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "sndio" "")
endif()
if(WITH_IOSAUDIO)
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ios" "")
endif()

View File

@@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# 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("audin" "alsa" "")
find_package(ALSA REQUIRED)
set(${MODULE_PREFIX}_SRCS audin_alsa.c)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${ALSA_LIBRARIES})
freerdp_client_pc_add_requires_private("alsa")
include_directories(..)
include_directories(SYSTEM ${ALSA_INCLUDE_DIRS})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,466 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - ALSA implementation
*
* Copyright 2010-2011 Vic Lee
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* 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.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/cmdline.h>
#include <winpr/wlog.h>
#include <alsa/asoundlib.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#include "audin_main.h"
typedef struct
{
IAudinDevice iface;
char* device_name;
UINT32 frames_per_packet;
AUDIO_FORMAT aformat;
HANDLE thread;
HANDLE stopEvent;
AudinReceive receive;
void* user_data;
rdpContext* rdpcontext;
wLog* log;
size_t bytes_per_frame;
} AudinALSADevice;
static snd_pcm_format_t audin_alsa_format(UINT32 wFormatTag, UINT32 bitPerChannel)
{
switch (wFormatTag)
{
case WAVE_FORMAT_PCM:
switch (bitPerChannel)
{
case 16:
return SND_PCM_FORMAT_S16_LE;
case 8:
return SND_PCM_FORMAT_S8;
default:
return SND_PCM_FORMAT_UNKNOWN;
}
default:
return SND_PCM_FORMAT_UNKNOWN;
}
}
static BOOL audin_alsa_set_params(AudinALSADevice* alsa, snd_pcm_t* capture_handle)
{
int error = 0;
SSIZE_T s = 0;
UINT32 channels = alsa->aformat.nChannels;
snd_pcm_hw_params_t* hw_params = nullptr;
snd_pcm_format_t format =
audin_alsa_format(alsa->aformat.wFormatTag, alsa->aformat.wBitsPerSample);
if ((error = snd_pcm_hw_params_malloc(&hw_params)) < 0)
{
WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_hw_params_malloc (%s)", snd_strerror(error));
return FALSE;
}
snd_pcm_hw_params_any(capture_handle, hw_params);
snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(capture_handle, hw_params, format);
snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &alsa->aformat.nSamplesPerSec,
nullptr);
snd_pcm_hw_params_set_channels_near(capture_handle, hw_params, &channels);
snd_pcm_hw_params(capture_handle, hw_params);
snd_pcm_hw_params_free(hw_params);
snd_pcm_prepare(capture_handle);
if (channels > UINT16_MAX)
return FALSE;
s = snd_pcm_format_size(format, 1);
if ((s < 0) || (s > UINT16_MAX))
return FALSE;
alsa->aformat.nChannels = (UINT16)channels;
alsa->bytes_per_frame = (size_t)s * channels;
return TRUE;
}
static DWORD WINAPI audin_alsa_thread_func(LPVOID arg)
{
DWORD error = CHANNEL_RC_OK;
BYTE* buffer = nullptr;
AudinALSADevice* alsa = (AudinALSADevice*)arg;
WINPR_ASSERT(alsa);
WLog_Print(alsa->log, WLOG_DEBUG, "in");
snd_pcm_t* capture_handle = nullptr;
const int rc = snd_pcm_open(&capture_handle, alsa->device_name, SND_PCM_STREAM_CAPTURE, 0);
if (rc < 0)
{
WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_open (%s)", snd_strerror(rc));
error = CHANNEL_RC_INITIALIZATION_ERROR;
goto out;
}
if (!audin_alsa_set_params(alsa, capture_handle))
{
WLog_Print(alsa->log, WLOG_ERROR, "audin_alsa_set_params failed");
goto out;
}
buffer =
(BYTE*)calloc(alsa->frames_per_packet + alsa->aformat.nBlockAlign, alsa->bytes_per_frame);
if (!buffer)
{
WLog_Print(alsa->log, WLOG_ERROR, "calloc failed!");
error = CHANNEL_RC_NO_MEMORY;
goto out;
}
while (1)
{
size_t frames = alsa->frames_per_packet;
const DWORD status = WaitForSingleObject(alsa->stopEvent, 0);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!",
error);
break;
}
if (status == WAIT_OBJECT_0)
{
WLog_Print(alsa->log, WLOG_DEBUG, "alsa->stopEvent requests termination");
break;
}
snd_pcm_sframes_t framesRead = snd_pcm_readi(capture_handle, buffer, frames);
if (framesRead == 0)
continue;
if (framesRead == -EPIPE)
{
const int res = snd_pcm_recover(capture_handle, (int)framesRead, 0);
if (res < 0)
WLog_Print(alsa->log, WLOG_WARN, "snd_pcm_recover (%s)", snd_strerror(res));
continue;
}
else if (framesRead < 0)
{
WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_readi (%s)", snd_strerror((int)framesRead));
error = ERROR_INTERNAL_ERROR;
break;
}
error = alsa->receive(&alsa->aformat, buffer,
WINPR_ASSERTING_INT_CAST(size_t, framesRead) * alsa->bytes_per_frame,
alsa->user_data);
if (error)
{
WLog_Print(alsa->log, WLOG_ERROR,
"audin_alsa_thread_receive failed with error %" PRIu32, error);
break;
}
}
free(buffer);
if (capture_handle)
{
const int res = snd_pcm_close(capture_handle);
if (res < 0)
WLog_Print(alsa->log, WLOG_WARN, "snd_pcm_close (%s)", snd_strerror(res));
}
out:
WLog_Print(alsa->log, WLOG_DEBUG, "out");
if (error && alsa->rdpcontext)
setChannelError(alsa->rdpcontext, error, "audin_alsa_thread_func reported an error");
ExitThread(error);
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_alsa_free(IAudinDevice* device)
{
AudinALSADevice* alsa = (AudinALSADevice*)device;
if (alsa)
free(alsa->device_name);
free(alsa);
return CHANNEL_RC_OK;
}
static BOOL audin_alsa_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
{
if (!device || !format)
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 TRUE;
}
break;
default:
return FALSE;
}
return FALSE;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_alsa_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
UINT32 FramesPerPacket)
{
AudinALSADevice* alsa = (AudinALSADevice*)device;
if (!alsa || !format)
return ERROR_INVALID_PARAMETER;
alsa->aformat = *format;
alsa->frames_per_packet = FramesPerPacket;
if (audin_alsa_format(format->wFormatTag, format->wBitsPerSample) == SND_PCM_FORMAT_UNKNOWN)
return ERROR_INTERNAL_ERROR;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_alsa_open(IAudinDevice* device, AudinReceive receive, void* user_data)
{
AudinALSADevice* alsa = (AudinALSADevice*)device;
if (!device || !receive || !user_data)
return ERROR_INVALID_PARAMETER;
alsa->receive = receive;
alsa->user_data = user_data;
if (!(alsa->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
{
WLog_Print(alsa->log, WLOG_ERROR, "CreateEvent failed!");
goto error_out;
}
if (!(alsa->thread = CreateThread(nullptr, 0, audin_alsa_thread_func, alsa, 0, nullptr)))
{
WLog_Print(alsa->log, WLOG_ERROR, "CreateThread failed!");
goto error_out;
}
return CHANNEL_RC_OK;
error_out:
(void)CloseHandle(alsa->stopEvent);
alsa->stopEvent = nullptr;
return ERROR_INTERNAL_ERROR;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_alsa_close(IAudinDevice* device)
{
UINT error = CHANNEL_RC_OK;
AudinALSADevice* alsa = (AudinALSADevice*)device;
if (!alsa)
return ERROR_INVALID_PARAMETER;
if (alsa->stopEvent)
{
(void)SetEvent(alsa->stopEvent);
if (WaitForSingleObject(alsa->thread, INFINITE) == WAIT_FAILED)
{
error = GetLastError();
WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "",
error);
return error;
}
(void)CloseHandle(alsa->stopEvent);
alsa->stopEvent = nullptr;
(void)CloseHandle(alsa->thread);
alsa->thread = nullptr;
}
alsa->receive = nullptr;
alsa->user_data = nullptr;
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_alsa_parse_addin_args(AudinALSADevice* device, const ADDIN_ARGV* args)
{
int status = 0;
DWORD flags = 0;
const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
AudinALSADevice* alsa = device;
COMMAND_LINE_ARGUMENT_A audin_alsa_args[] = {
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
"audio device name" },
{ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
};
flags =
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
status = CommandLineParseArgumentsA(args->argc, args->argv, audin_alsa_args, flags, alsa,
nullptr, nullptr);
if (status < 0)
return ERROR_INVALID_PARAMETER;
arg = audin_alsa_args;
do
{
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
continue;
CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
{
alsa->device_name = _strdup(arg->Value);
if (!alsa->device_name)
{
WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!");
return CHANNEL_RC_NO_MEMORY;
}
}
CommandLineSwitchEnd(arg)
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
FREERDP_ENTRY_POINT(UINT VCAPITYPE alsa_freerdp_audin_client_subsystem_entry(
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
{
const ADDIN_ARGV* args = nullptr;
AudinALSADevice* alsa = nullptr;
UINT error = 0;
alsa = (AudinALSADevice*)calloc(1, sizeof(AudinALSADevice));
if (!alsa)
{
WLog_ERR(TAG, "calloc failed!");
return CHANNEL_RC_NO_MEMORY;
}
alsa->log = WLog_Get(TAG);
alsa->iface.Open = audin_alsa_open;
alsa->iface.FormatSupported = audin_alsa_format_supported;
alsa->iface.SetFormat = audin_alsa_set_format;
alsa->iface.Close = audin_alsa_close;
alsa->iface.Free = audin_alsa_free;
alsa->rdpcontext = pEntryPoints->rdpcontext;
args = pEntryPoints->args;
if ((error = audin_alsa_parse_addin_args(alsa, args)))
{
WLog_Print(alsa->log, WLOG_ERROR,
"audin_alsa_parse_addin_args failed with errorcode %" PRIu32 "!", error);
goto error_out;
}
if (!alsa->device_name)
{
alsa->device_name = _strdup("default");
if (!alsa->device_name)
{
WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!");
error = CHANNEL_RC_NO_MEMORY;
goto error_out;
}
}
alsa->frames_per_packet = 128;
alsa->aformat.nChannels = 2;
alsa->aformat.wBitsPerSample = 16;
alsa->aformat.wFormatTag = WAVE_FORMAT_PCM;
alsa->aformat.nSamplesPerSec = 44100;
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)alsa)))
{
WLog_Print(alsa->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
error);
goto error_out;
}
return CHANNEL_RC_OK;
error_out:
free(alsa->device_name);
free(alsa);
return error;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel
*
* Copyright 2010-2011 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.
*/
#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H
#define FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H
#include <freerdp/config.h>
#include <freerdp/dvc.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#include <freerdp/client/audin.h>
#define TAG CHANNELS_TAG("audin.client")
#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H */

View File

@@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
# Copyright (c) 2015 Thincast Technologies GmbH
#
# 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("audin" "ios" "")
find_library(CORE_AUDIO CoreAudio)
find_library(AVFOUNDATION AVFoundation)
find_library(AUDIO_TOOL AudioToolbox)
set(${MODULE_PREFIX}_SRCS audin_ios.m)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${AVFOUNDATION} ${CORE_AUDIO} ${AUDIO_TOOL})
include_directories(..)
include_directories(SYSTEM ${MAC_INCLUDE_DIRS})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,335 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - iOS implementation
*
* Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
* Copyright 2015 Thincast Technologies GmbH
*
* 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.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/string.h>
#include <winpr/thread.h>
#include <winpr/debug.h>
#include <winpr/cmdline.h>
#import <AVFoundation/AVFoundation.h>
#define __COREFOUNDATION_CFPLUGINCOM__ 1
#define IUNKNOWN_C_GUTS \
void *_reserved; \
void *QueryInterface; \
void *AddRef; \
void *Release
#include <CoreAudio/CoreAudioTypes.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AudioToolbox/AudioQueue.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#include "audin_main.h"
#define IOS_AUDIO_QUEUE_NUM_BUFFERS 100
typedef struct
{
IAudinDevice iface;
AUDIO_FORMAT format;
UINT32 FramesPerPacket;
int dev_unit;
AudinReceive receive;
void *user_data;
rdpContext *rdpcontext;
bool isOpen;
AudioQueueRef audioQueue;
AudioStreamBasicDescription audioFormat;
AudioQueueBufferRef audioBuffers[IOS_AUDIO_QUEUE_NUM_BUFFERS];
} AudinIosDevice;
static AudioFormatID audin_ios_get_format(const AUDIO_FORMAT *format)
{
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM:
return kAudioFormatLinearPCM;
default:
return 0;
}
}
static AudioFormatFlags audin_ios_get_flags_for_format(const AUDIO_FORMAT *format)
{
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM:
return kAudioFormatFlagIsSignedInteger;
default:
return 0;
}
}
static BOOL audin_ios_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format)
{
AudinIosDevice *ios = (AudinIosDevice *)device;
AudioFormatID req_fmt = 0;
if (device == nullptr || format == nullptr)
return FALSE;
req_fmt = audin_ios_get_format(format);
if (req_fmt == 0)
return FALSE;
return TRUE;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_ios_set_format(IAudinDevice *device, const AUDIO_FORMAT *format,
UINT32 FramesPerPacket)
{
AudinIosDevice *ios = (AudinIosDevice *)device;
if (device == nullptr || format == nullptr)
return ERROR_INVALID_PARAMETER;
ios->FramesPerPacket = FramesPerPacket;
ios->format = *format;
WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]",
audio_format_get_tag_string(format->wFormatTag), format->nChannels,
format->nSamplesPerSec, format->wBitsPerSample);
ios->audioFormat.mBitsPerChannel = format->wBitsPerSample;
if (format->wBitsPerSample == 0)
ios->audioFormat.mBitsPerChannel = 16;
ios->audioFormat.mChannelsPerFrame = ios->format.nChannels;
ios->audioFormat.mFramesPerPacket = 1;
ios->audioFormat.mBytesPerFrame =
ios->audioFormat.mChannelsPerFrame * (ios->audioFormat.mBitsPerChannel / 8);
ios->audioFormat.mBytesPerPacket =
ios->audioFormat.mBytesPerFrame * ios->audioFormat.mFramesPerPacket;
ios->audioFormat.mFormatFlags = audin_ios_get_flags_for_format(format);
ios->audioFormat.mFormatID = audin_ios_get_format(format);
ios->audioFormat.mReserved = 0;
ios->audioFormat.mSampleRate = ios->format.nSamplesPerSec;
return CHANNEL_RC_OK;
}
static void ios_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime, UInt32 inNumPackets,
const AudioStreamPacketDescription *inPacketDesc)
{
AudinIosDevice *ios = (AudinIosDevice *)aqData;
UINT error = CHANNEL_RC_OK;
const BYTE *buffer = inBuffer->mAudioData;
int buffer_size = inBuffer->mAudioDataByteSize;
(void)inAQ;
(void)inStartTime;
(void)inNumPackets;
(void)inPacketDesc;
if (buffer_size > 0)
error = ios->receive(&ios->format, buffer, buffer_size, ios->user_data);
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nullptr);
if (error)
{
WLog_ERR(TAG, "ios->receive failed with error %" PRIu32 "", error);
SetLastError(ERROR_INTERNAL_ERROR);
}
}
static UINT audin_ios_close(IAudinDevice *device)
{
UINT errCode = CHANNEL_RC_OK;
char errString[1024];
OSStatus devStat;
AudinIosDevice *ios = (AudinIosDevice *)device;
if (device == nullptr)
return ERROR_INVALID_PARAMETER;
if (ios->isOpen)
{
devStat = AudioQueueStop(ios->audioQueue, true);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
}
ios->isOpen = false;
}
if (ios->audioQueue)
{
devStat = AudioQueueDispose(ios->audioQueue, true);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
}
ios->audioQueue = nullptr;
}
ios->receive = nullptr;
ios->user_data = nullptr;
return errCode;
}
static UINT audin_ios_open(IAudinDevice *device, AudinReceive receive, void *user_data)
{
AudinIosDevice *ios = (AudinIosDevice *)device;
DWORD errCode;
char errString[1024];
OSStatus devStat;
ios->receive = receive;
ios->user_data = user_data;
devStat = AudioQueueNewInput(&(ios->audioFormat), ios_audio_queue_input_cb, ios, nullptr,
kCFRunLoopCommonModes, 0, &(ios->audioQueue));
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
goto err_out;
}
for (size_t index = 0; index < IOS_AUDIO_QUEUE_NUM_BUFFERS; index++)
{
devStat = AudioQueueAllocateBuffer(ios->audioQueue,
ios->FramesPerPacket * 2 * ios->format.nChannels,
&ios->audioBuffers[index]);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
goto err_out;
}
devStat = AudioQueueEnqueueBuffer(ios->audioQueue, ios->audioBuffers[index], 0, nullptr);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
goto err_out;
}
}
devStat = AudioQueueStart(ios->audioQueue, nullptr);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
goto err_out;
}
ios->isOpen = true;
return CHANNEL_RC_OK;
err_out:
audin_ios_close(device);
return CHANNEL_RC_INITIALIZATION_ERROR;
}
static UINT audin_ios_free(IAudinDevice *device)
{
AudinIosDevice *ios = (AudinIosDevice *)device;
int error;
if (device == nullptr)
return ERROR_INVALID_PARAMETER;
if ((error = audin_ios_close(device)))
{
WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error);
}
free(ios);
return CHANNEL_RC_OK;
}
FREERDP_ENTRY_POINT(UINT VCAPITYPE ios_freerdp_audin_client_subsystem_entry(
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
{
DWORD errCode;
char errString[1024];
const ADDIN_ARGV *args;
AudinIosDevice *ios;
UINT error;
ios = (AudinIosDevice *)calloc(1, sizeof(AudinIosDevice));
if (!ios)
{
errCode = GetLastError();
WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
return CHANNEL_RC_NO_MEMORY;
}
ios->iface.Open = audin_ios_open;
ios->iface.FormatSupported = audin_ios_format_supported;
ios->iface.SetFormat = audin_ios_set_format;
ios->iface.Close = audin_ios_close;
ios->iface.Free = audin_ios_free;
ios->rdpcontext = pEntryPoints->rdpcontext;
ios->dev_unit = -1;
args = pEntryPoints->args;
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)ios)))
{
WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
goto error_out;
}
return CHANNEL_RC_OK;
error_out:
free(ios);
return error;
}

View File

@@ -0,0 +1,32 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
# Copyright (c) 2015 Thincast Technologies GmbH
#
# 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("audin" "mac" "")
find_library(CORE_AUDIO CoreAudio)
find_library(AVFOUNDATION AVFoundation)
find_library(AUDIO_TOOL AudioToolbox)
find_library(APP_SERVICES ApplicationServices)
set(${MODULE_PREFIX}_SRCS audin_mac.m)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${AVFOUNDATION} ${CORE_AUDIO} ${AUDIO_TOOL} ${APP_SERVICES})
include_directories(..)
include_directories(SYSTEM ${MAC_INCLUDE_DIRS})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,465 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - Mac OS X implementation
*
* Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
* Copyright 2015 Thincast Technologies GmbH
*
* 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.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/string.h>
#include <winpr/thread.h>
#include <winpr/debug.h>
#include <winpr/cmdline.h>
#import <AVFoundation/AVFoundation.h>
#define __COREFOUNDATION_CFPLUGINCOM__ 1
#define IUNKNOWN_C_GUTS \
void *_reserved; \
void *QueryInterface; \
void *AddRef; \
void *Release
#include <CoreAudio/CoreAudioTypes.h>
#include <CoreAudio/CoreAudio.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AudioToolbox/AudioQueue.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#include "audin_main.h"
#define MAC_AUDIO_QUEUE_NUM_BUFFERS 100
/* Fix for #4462: Provide type alias if not declared (Mac OS < 10.10)
* https://developer.apple.com/documentation/coreaudio/audioformatid
*/
#ifndef AudioFormatID
typedef UInt32 AudioFormatID;
#endif
#ifndef AudioFormatFlags
typedef UInt32 AudioFormatFlags;
#endif
typedef struct
{
IAudinDevice iface;
AUDIO_FORMAT format;
UINT32 FramesPerPacket;
int dev_unit;
AudinReceive receive;
void *user_data;
rdpContext *rdpcontext;
bool isAuthorized;
bool isOpen;
AudioQueueRef audioQueue;
AudioStreamBasicDescription audioFormat;
AudioQueueBufferRef audioBuffers[MAC_AUDIO_QUEUE_NUM_BUFFERS];
} AudinMacDevice;
static AudioFormatID audin_mac_get_format(const AUDIO_FORMAT *format)
{
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM:
return kAudioFormatLinearPCM;
default:
return 0;
}
}
static AudioFormatFlags audin_mac_get_flags_for_format(const AUDIO_FORMAT *format)
{
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM:
return kAudioFormatFlagIsSignedInteger;
default:
return 0;
}
}
static BOOL audin_mac_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format)
{
AudinMacDevice *mac = (AudinMacDevice *)device;
AudioFormatID req_fmt = 0;
if (!mac->isAuthorized)
return FALSE;
if (device == nullptr || format == nullptr)
return FALSE;
if (format->nChannels != 2)
return FALSE;
req_fmt = audin_mac_get_format(format);
if (req_fmt == 0)
return FALSE;
return TRUE;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_mac_set_format(IAudinDevice *device, const AUDIO_FORMAT *format,
UINT32 FramesPerPacket)
{
AudinMacDevice *mac = (AudinMacDevice *)device;
if (!mac->isAuthorized)
return ERROR_INTERNAL_ERROR;
if (device == nullptr || format == nullptr)
return ERROR_INVALID_PARAMETER;
mac->FramesPerPacket = FramesPerPacket;
mac->format = *format;
WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]",
audio_format_get_tag_string(format->wFormatTag), format->nChannels,
format->nSamplesPerSec, format->wBitsPerSample);
mac->audioFormat.mBitsPerChannel = format->wBitsPerSample;
if (format->wBitsPerSample == 0)
mac->audioFormat.mBitsPerChannel = 16;
mac->audioFormat.mChannelsPerFrame = mac->format.nChannels;
mac->audioFormat.mFramesPerPacket = 1;
mac->audioFormat.mBytesPerFrame =
mac->audioFormat.mChannelsPerFrame * (mac->audioFormat.mBitsPerChannel / 8);
mac->audioFormat.mBytesPerPacket =
mac->audioFormat.mBytesPerFrame * mac->audioFormat.mFramesPerPacket;
mac->audioFormat.mFormatFlags = audin_mac_get_flags_for_format(format);
mac->audioFormat.mFormatID = audin_mac_get_format(format);
mac->audioFormat.mReserved = 0;
mac->audioFormat.mSampleRate = mac->format.nSamplesPerSec;
return CHANNEL_RC_OK;
}
static void mac_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime, UInt32 inNumPackets,
const AudioStreamPacketDescription *inPacketDesc)
{
AudinMacDevice *mac = (AudinMacDevice *)aqData;
UINT error = CHANNEL_RC_OK;
const BYTE *buffer = inBuffer->mAudioData;
int buffer_size = inBuffer->mAudioDataByteSize;
(void)inAQ;
(void)inStartTime;
(void)inNumPackets;
(void)inPacketDesc;
if (buffer_size > 0)
error = mac->receive(&mac->format, buffer, buffer_size, mac->user_data);
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nullptr);
if (error)
{
WLog_ERR(TAG, "mac->receive failed with error %" PRIu32 "", error);
SetLastError(ERROR_INTERNAL_ERROR);
}
}
static UINT audin_mac_close(IAudinDevice *device)
{
UINT errCode = CHANNEL_RC_OK;
char errString[1024];
OSStatus devStat;
AudinMacDevice *mac = (AudinMacDevice *)device;
if (!mac->isAuthorized)
return ERROR_INTERNAL_ERROR;
if (device == nullptr)
return ERROR_INVALID_PARAMETER;
if (mac->isOpen)
{
devStat = AudioQueueStop(mac->audioQueue, true);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
}
mac->isOpen = false;
}
if (mac->audioQueue)
{
devStat = AudioQueueDispose(mac->audioQueue, true);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
}
mac->audioQueue = nullptr;
}
mac->receive = nullptr;
mac->user_data = nullptr;
return errCode;
}
static UINT audin_mac_open(IAudinDevice *device, AudinReceive receive, void *user_data)
{
AudinMacDevice *mac = (AudinMacDevice *)device;
DWORD errCode;
char errString[1024];
OSStatus devStat;
if (!mac->isAuthorized)
return ERROR_INTERNAL_ERROR;
mac->receive = receive;
mac->user_data = user_data;
devStat = AudioQueueNewInput(&(mac->audioFormat), mac_audio_queue_input_cb, mac, nullptr,
kCFRunLoopCommonModes, 0, &(mac->audioQueue));
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
goto err_out;
}
for (size_t index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++)
{
devStat = AudioQueueAllocateBuffer(mac->audioQueue,
mac->FramesPerPacket * 2 * mac->format.nChannels,
&mac->audioBuffers[index]);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
goto err_out;
}
devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0, nullptr);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
goto err_out;
}
}
devStat = AudioQueueStart(mac->audioQueue, nullptr);
if (devStat != 0)
{
errCode = GetLastError();
WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
goto err_out;
}
mac->isOpen = true;
return CHANNEL_RC_OK;
err_out:
audin_mac_close(device);
return CHANNEL_RC_INITIALIZATION_ERROR;
}
static UINT audin_mac_free(IAudinDevice *device)
{
AudinMacDevice *mac = (AudinMacDevice *)device;
int error;
if (device == nullptr)
return ERROR_INVALID_PARAMETER;
if ((error = audin_mac_close(device)))
{
WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error);
}
free(mac);
return CHANNEL_RC_OK;
}
static UINT audin_mac_parse_addin_args(AudinMacDevice *device, const ADDIN_ARGV *args)
{
DWORD errCode;
char errString[1024];
int status;
char *str_num, *eptr;
DWORD flags;
const COMMAND_LINE_ARGUMENT_A *arg;
COMMAND_LINE_ARGUMENT_A audin_mac_args[] = {
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
"audio device name" },
{ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
};
AudinMacDevice *mac = (AudinMacDevice *)device;
if (args->argc == 1)
return CHANNEL_RC_OK;
flags =
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
status = CommandLineParseArgumentsA(args->argc, args->argv, audin_mac_args, flags, mac, nullptr,
nullptr);
if (status < 0)
return ERROR_INVALID_PARAMETER;
arg = audin_mac_args;
do
{
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
continue;
CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
{
str_num = _strdup(arg->Value);
if (!str_num)
{
errCode = GetLastError();
WLog_ERR(TAG, "_strdup failed with %s [%d]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
return CHANNEL_RC_NO_MEMORY;
}
mac->dev_unit = strtol(str_num, &eptr, 10);
if (mac->dev_unit < 0 || *eptr != '\0')
mac->dev_unit = -1;
free(str_num);
}
CommandLineSwitchEnd(arg)
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
return CHANNEL_RC_OK;
}
FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_audin_client_subsystem_entry(
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
{
DWORD errCode;
char errString[1024];
const ADDIN_ARGV *args;
AudinMacDevice *mac;
UINT error;
mac = (AudinMacDevice *)calloc(1, sizeof(AudinMacDevice));
if (!mac)
{
errCode = GetLastError();
WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
return CHANNEL_RC_NO_MEMORY;
}
mac->iface.Open = audin_mac_open;
mac->iface.FormatSupported = audin_mac_format_supported;
mac->iface.SetFormat = audin_mac_set_format;
mac->iface.Close = audin_mac_close;
mac->iface.Free = audin_mac_free;
mac->rdpcontext = pEntryPoints->rdpcontext;
mac->dev_unit = -1;
args = pEntryPoints->args;
if ((error = audin_mac_parse_addin_args(mac, args)))
{
WLog_ERR(TAG, "audin_mac_parse_addin_args failed with %" PRIu32 "!", error);
goto error_out;
}
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac)))
{
WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
goto error_out;
}
#if defined(MAC_OS_X_VERSION_10_14)
if (@available(macOS 10.14, *))
{
@autoreleasepool
{
AVAuthorizationStatus status =
[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
switch (status)
{
case AVAuthorizationStatusAuthorized:
mac->isAuthorized = TRUE;
break;
case AVAuthorizationStatusNotDetermined:
[AVCaptureDevice
requestAccessForMediaType:AVMediaTypeAudio
completionHandler:^(BOOL granted) {
if (granted == YES)
{
mac->isAuthorized = TRUE;
}
else
WLog_WARN(TAG, "Microphone access denied by user");
}];
break;
case AVAuthorizationStatusRestricted:
WLog_WARN(TAG, "Microphone access restricted by policy");
break;
case AVAuthorizationStatusDenied:
WLog_WARN(TAG, "Microphone access denied by policy");
break;
default:
break;
}
}
}
#endif
return CHANNEL_RC_OK;
error_out:
free(mac);
return error;
}

View File

@@ -0,0 +1,29 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2013 Armin Novak <armin.novak@gmail.com>
#
# 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("audin" "opensles" "")
find_package(OpenSLES REQUIRED)
set(${MODULE_PREFIX}_SRCS opensl_io.c audin_opensl_es.c)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${OpenSLES_LIBRARIES})
include_directories(..)
include_directories(SYSTEM ${OpenSLES_INCLUDE_DIRS})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,334 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - OpenSL ES implementation
*
* Copyright 2013 Armin Novak <armin.novak@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* 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.
*/
#include <freerdp/config.h>
#include <winpr/assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/cmdline.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#include <SLES/OpenSLES.h>
#include <freerdp/client/audin.h>
#include "audin_main.h"
#include "opensl_io.h"
typedef struct
{
IAudinDevice iface;
char* device_name;
OPENSL_STREAM* stream;
AUDIO_FORMAT format;
UINT32 frames_per_packet;
UINT32 bytes_per_channel;
AudinReceive receive;
void* user_data;
rdpContext* rdpcontext;
wLog* log;
} AudinOpenSLESDevice;
static UINT audin_opensles_close(IAudinDevice* device);
static void audin_receive(void* context, const void* data, size_t size)
{
UINT error;
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)context;
if (!opensles || !data)
{
WLog_ERR(TAG, "Invalid arguments context=%p, data=%p", opensles, data);
return;
}
error = opensles->receive(&opensles->format, data, size, opensles->user_data);
if (error && opensles->rdpcontext)
setChannelError(opensles->rdpcontext, error, "audin_receive reported an error");
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_opensles_free(IAudinDevice* device)
{
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
if (!opensles)
return ERROR_INVALID_PARAMETER;
WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*)device);
free(opensles->device_name);
free(opensles);
return CHANNEL_RC_OK;
}
static BOOL audin_opensles_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
{
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
if (!opensles || !format)
return FALSE;
WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p", (void*)opensles, (void*)format);
WINPR_ASSERT(format);
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM: /* PCM */
if (format->cbSize == 0 && (format->nSamplesPerSec <= 48000) &&
(format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
(format->nChannels >= 1 && format->nChannels <= 2))
{
return TRUE;
}
break;
default:
WLog_Print(opensles->log, WLOG_DEBUG, "Encoding '%s' [0x%04" PRIX16 "] not supported",
audio_format_get_tag_string(format->wFormatTag), format->wFormatTag);
break;
}
return FALSE;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_opensles_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
UINT32 FramesPerPacket)
{
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
if (!opensles || !format)
return ERROR_INVALID_PARAMETER;
WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p, FramesPerPacket=%" PRIu32 "",
(void*)device, (void*)format, FramesPerPacket);
WINPR_ASSERT(format);
opensles->format = *format;
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM:
opensles->frames_per_packet = FramesPerPacket;
switch (format->wBitsPerSample)
{
case 4:
opensles->bytes_per_channel = 1;
break;
case 8:
opensles->bytes_per_channel = 1;
break;
case 16:
opensles->bytes_per_channel = 2;
break;
default:
return ERROR_UNSUPPORTED_TYPE;
}
break;
default:
WLog_Print(opensles->log, WLOG_ERROR,
"Encoding '%" PRIu16 "' [%04" PRIX16 "] not supported", format->wFormatTag,
format->wFormatTag);
return ERROR_UNSUPPORTED_TYPE;
}
WLog_Print(opensles->log, WLOG_DEBUG, "frames_per_packet=%" PRIu32,
opensles->frames_per_packet);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_opensles_open(IAudinDevice* device, AudinReceive receive, void* user_data)
{
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
if (!opensles || !receive || !user_data)
return ERROR_INVALID_PARAMETER;
WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, receive=%p, user_data=%p", (void*)device,
(void*)receive, (void*)user_data);
if (opensles->stream)
goto error_out;
if (!(opensles->stream = android_OpenRecDevice(
opensles, audin_receive, opensles->format.nSamplesPerSec, opensles->format.nChannels,
opensles->frames_per_packet, opensles->format.wBitsPerSample)))
{
WLog_Print(opensles->log, WLOG_ERROR, "android_OpenRecDevice failed!");
goto error_out;
}
opensles->receive = receive;
opensles->user_data = user_data;
return CHANNEL_RC_OK;
error_out:
audin_opensles_close(device);
return ERROR_INTERNAL_ERROR;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
UINT audin_opensles_close(IAudinDevice* device)
{
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
if (!opensles)
return ERROR_INVALID_PARAMETER;
WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*)device);
android_CloseRecDevice(opensles->stream);
opensles->receive = nullptr;
opensles->user_data = nullptr;
opensles->stream = nullptr;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_opensles_parse_addin_args(AudinOpenSLESDevice* device, const ADDIN_ARGV* args)
{
UINT status;
DWORD flags;
const COMMAND_LINE_ARGUMENT_A* arg;
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device;
COMMAND_LINE_ARGUMENT_A audin_opensles_args[] = {
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
"audio device name" },
{ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
};
WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, args=%p", (void*)device, (void*)args);
flags =
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
status = CommandLineParseArgumentsA(args->argc, args->argv, audin_opensles_args, flags,
opensles, nullptr, nullptr);
if (status < 0)
return status;
arg = audin_opensles_args;
do
{
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
continue;
CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
{
opensles->device_name = _strdup(arg->Value);
if (!opensles->device_name)
{
WLog_Print(opensles->log, WLOG_ERROR, "_strdup failed!");
return CHANNEL_RC_NO_MEMORY;
}
}
CommandLineSwitchEnd(arg)
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
FREERDP_ENTRY_POINT(UINT VCAPITYPE opensles_freerdp_audin_client_subsystem_entry(
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
{
UINT error = ERROR_INTERNAL_ERROR;
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)calloc(1, sizeof(AudinOpenSLESDevice));
if (!opensles)
{
WLog_ERR(TAG, "calloc failed!");
return CHANNEL_RC_NO_MEMORY;
}
opensles->log = WLog_Get(TAG);
opensles->iface.Open = audin_opensles_open;
opensles->iface.FormatSupported = audin_opensles_format_supported;
opensles->iface.SetFormat = audin_opensles_set_format;
opensles->iface.Close = audin_opensles_close;
opensles->iface.Free = audin_opensles_free;
opensles->rdpcontext = pEntryPoints->rdpcontext;
const ADDIN_ARGV* args = pEntryPoints->args;
if ((error = audin_opensles_parse_addin_args(opensles, args)))
{
WLog_Print(opensles->log, WLOG_ERROR,
"audin_opensles_parse_addin_args failed with errorcode %" PRIu32 "!", error);
goto error_out;
}
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)opensles)))
{
WLog_Print(opensles->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
error);
goto error_out;
}
return CHANNEL_RC_OK;
error_out:
free(opensles);
return error;
}

View File

@@ -0,0 +1,388 @@
/*
opensl_io.c:
Android OpenSL input/output module
Copyright (c) 2012, Victor Lazzarini
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <winpr/assert.h>
#include "audin_main.h"
#include "opensl_io.h"
#define CONV16BIT 32768
#define CONVMYFLT (1. / 32768.)
typedef struct
{
size_t size;
void* data;
} queue_element;
struct opensl_stream
{
// engine interfaces
SLObjectItf engineObject;
SLEngineItf engineEngine;
// device interfaces
SLDeviceVolumeItf deviceVolume;
// recorder interfaces
SLObjectItf recorderObject;
SLRecordItf recorderRecord;
SLAndroidSimpleBufferQueueItf recorderBufferQueue;
unsigned int inchannels;
unsigned int sr;
unsigned int buffersize;
unsigned int bits_per_sample;
queue_element* prep;
queue_element* next;
void* context;
opensl_receive_t receive;
};
static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
// creates the OpenSL ES audio engine
static SLresult openSLCreateEngine(OPENSL_STREAM* p)
{
SLresult result;
// create engine
result = slCreateEngine(&(p->engineObject), 0, nullptr, 0, nullptr, nullptr);
if (result != SL_RESULT_SUCCESS)
goto engine_end;
// realize the engine
result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS)
goto engine_end;
// get the engine interface, which is needed in order to create other objects
result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_ENGINE, &(p->engineEngine));
if (result != SL_RESULT_SUCCESS)
goto engine_end;
// get the volume interface - important, this is optional!
result =
(*p->engineObject)->GetInterface(p->engineObject, SL_IID_DEVICEVOLUME, &(p->deviceVolume));
if (result != SL_RESULT_SUCCESS)
{
p->deviceVolume = nullptr;
result = SL_RESULT_SUCCESS;
}
engine_end:
WINPR_ASSERT(SL_RESULT_SUCCESS == result);
return result;
}
// Open the OpenSL ES device for input
static SLresult openSLRecOpen(OPENSL_STREAM* p)
{
SLresult result;
SLuint32 sr = p->sr;
SLuint32 channels = p->inchannels;
WINPR_ASSERT(!p->recorderObject);
if (channels)
{
switch (sr)
{
case 8000:
sr = SL_SAMPLINGRATE_8;
break;
case 11025:
sr = SL_SAMPLINGRATE_11_025;
break;
case 16000:
sr = SL_SAMPLINGRATE_16;
break;
case 22050:
sr = SL_SAMPLINGRATE_22_05;
break;
case 24000:
sr = SL_SAMPLINGRATE_24;
break;
case 32000:
sr = SL_SAMPLINGRATE_32;
break;
case 44100:
sr = SL_SAMPLINGRATE_44_1;
break;
case 48000:
sr = SL_SAMPLINGRATE_48;
break;
case 64000:
sr = SL_SAMPLINGRATE_64;
break;
case 88200:
sr = SL_SAMPLINGRATE_88_2;
break;
case 96000:
sr = SL_SAMPLINGRATE_96;
break;
case 192000:
sr = SL_SAMPLINGRATE_192;
break;
default:
return -1;
}
// configure audio source
SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr };
SLDataSource audioSrc = { &loc_dev, nullptr };
// configure audio sink
int speakers;
if (channels > 1)
speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
else
speakers = SL_SPEAKER_FRONT_CENTER;
SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2 };
SLDataFormat_PCM format_pcm;
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = channels;
format_pcm.samplesPerSec = sr;
format_pcm.channelMask = speakers;
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
if (16 == p->bits_per_sample)
{
format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
format_pcm.containerSize = 16;
}
else if (8 == p->bits_per_sample)
{
format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8;
format_pcm.containerSize = 8;
}
else
WINPR_ASSERT(0);
SLDataSink audioSnk = { &loc_bq, &format_pcm };
// create audio recorder
// (requires the RECORD_AUDIO permission)
const SLInterfaceID id[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
const SLboolean req[] = { SL_BOOLEAN_TRUE };
result = (*p->engineEngine)
->CreateAudioRecorder(p->engineEngine, &(p->recorderObject), &audioSrc,
&audioSnk, 1, id, req);
WINPR_ASSERT(!result);
if (SL_RESULT_SUCCESS != result)
goto end_recopen;
// realize the audio recorder
result = (*p->recorderObject)->Realize(p->recorderObject, SL_BOOLEAN_FALSE);
WINPR_ASSERT(!result);
if (SL_RESULT_SUCCESS != result)
goto end_recopen;
// get the record interface
result = (*p->recorderObject)
->GetInterface(p->recorderObject, SL_IID_RECORD, &(p->recorderRecord));
WINPR_ASSERT(!result);
if (SL_RESULT_SUCCESS != result)
goto end_recopen;
// get the buffer queue interface
result = (*p->recorderObject)
->GetInterface(p->recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&(p->recorderBufferQueue));
WINPR_ASSERT(!result);
if (SL_RESULT_SUCCESS != result)
goto end_recopen;
// register callback on the buffer queue
result = (*p->recorderBufferQueue)
->RegisterCallback(p->recorderBufferQueue, bqRecorderCallback, p);
WINPR_ASSERT(!result);
if (SL_RESULT_SUCCESS != result)
goto end_recopen;
end_recopen:
return result;
}
else
return SL_RESULT_SUCCESS;
}
// close the OpenSL IO and destroy the audio engine
static void openSLDestroyEngine(OPENSL_STREAM* p)
{
// destroy audio recorder object, and invalidate all associated interfaces
if (p->recorderObject != nullptr)
{
(*p->recorderObject)->Destroy(p->recorderObject);
p->recorderObject = nullptr;
p->recorderRecord = nullptr;
p->recorderBufferQueue = nullptr;
}
// destroy engine object, and invalidate all associated interfaces
if (p->engineObject != nullptr)
{
(*p->engineObject)->Destroy(p->engineObject);
p->engineObject = nullptr;
p->engineEngine = nullptr;
}
}
static queue_element* opensles_queue_element_new(size_t size)
{
queue_element* q = calloc(1, sizeof(queue_element));
if (!q)
goto fail;
q->size = size;
q->data = malloc(size);
if (!q->data)
goto fail;
return q;
fail:
free(q);
return nullptr;
}
static void opensles_queue_element_free(void* obj)
{
queue_element* e = (queue_element*)obj;
if (e)
free(e->data);
free(e);
}
// open the android audio device for input
OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive, int sr,
int inchannels, int bufferframes, int bits_per_sample)
{
OPENSL_STREAM* p;
if (!context || !receive)
return nullptr;
p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM));
if (!p)
return nullptr;
p->context = context;
p->receive = receive;
p->inchannels = inchannels;
p->sr = sr;
p->buffersize = bufferframes;
p->bits_per_sample = bits_per_sample;
if ((p->bits_per_sample != 8) && (p->bits_per_sample != 16))
goto fail;
if (openSLCreateEngine(p) != SL_RESULT_SUCCESS)
goto fail;
if (openSLRecOpen(p) != SL_RESULT_SUCCESS)
goto fail;
/* Create receive buffers, prepare them and start recording */
p->prep = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8);
p->next = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8);
if (!p->prep || !p->next)
goto fail;
(*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, p->next->data, p->next->size);
(*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, p->prep->data, p->prep->size);
(*p->recorderRecord)->SetRecordState(p->recorderRecord, SL_RECORDSTATE_RECORDING);
return p;
fail:
android_CloseRecDevice(p);
return nullptr;
}
// close the android audio device
void android_CloseRecDevice(OPENSL_STREAM* p)
{
if (p == nullptr)
return;
opensles_queue_element_free(p->next);
opensles_queue_element_free(p->prep);
openSLDestroyEngine(p);
free(p);
}
// this callback handler is called every time a buffer finishes recording
static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
{
OPENSL_STREAM* p = (OPENSL_STREAM*)context;
queue_element* e;
if (!p)
return;
e = p->next;
if (!e)
return;
if (!p->context || !p->receive)
WLog_WARN(TAG, "Missing receive callback=%p, context=%p", p->receive, p->context);
else
p->receive(p->context, e->data, e->size);
p->next = p->prep;
p->prep = e;
(*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, e->data, e->size);
}

View File

@@ -0,0 +1,67 @@
/*
opensl_io.c:
Android OpenSL input/output module header
Copyright (c) 2012, Victor Lazzarini
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H
#define FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <freerdp/api.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct opensl_stream OPENSL_STREAM;
typedef void (*opensl_receive_t)(void* context, const void* data, size_t size);
/*
Close the audio device
*/
FREERDP_LOCAL void android_CloseRecDevice(OPENSL_STREAM* p);
/*
Open the audio device with a given sampling rate (sr), input and output channels and IO buffer
size in frames. Returns a handle to the OpenSL stream
*/
WINPR_ATTR_MALLOC(android_CloseRecDevice, 1)
WINPR_ATTR_NODISCARD
FREERDP_LOCAL OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive,
int sr, int inchannels, int bufferframes,
int bits_per_sample);
#ifdef __cplusplus
};
#endif
#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H */

View File

@@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
#
# 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("audin" "oss" "")
find_package(OSS REQUIRED)
set(${MODULE_PREFIX}_SRCS audin_oss.c)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${OSS_LIBRARIES})
include_directories(..)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(SYSTEM ${OSS_INCLUDE_DIRS})
cleaning_configure_file(${CMAKE_SOURCE_DIR}/cmake/oss-includes.h.in ${CMAKE_CURRENT_BINARY_DIR}/oss-includes.h @ONLY)
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,449 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - OSS implementation
*
* Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* 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.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/string.h>
#include <winpr/thread.h>
#include <winpr/cmdline.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <unistd.h>
#include <oss-includes.h>
#include <sys/ioctl.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#include "audin_main.h"
typedef struct
{
IAudinDevice iface;
HANDLE thread;
HANDLE stopEvent;
AUDIO_FORMAT format;
UINT32 FramesPerPacket;
int dev_unit;
AudinReceive receive;
void* user_data;
rdpContext* rdpcontext;
} AudinOSSDevice;
static void OSS_LOG_ERR(const char* _text, int _error)
{
if ((_error) != 0)
{
char buffer[256] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "%s: %i - %s\n", (_text), (_error),
winpr_strerror((_error), buffer, sizeof(buffer)));
}
}
static UINT32 audin_oss_get_format(const AUDIO_FORMAT* format)
{
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM:
switch (format->wBitsPerSample)
{
case 8:
return AFMT_S8;
case 16:
return AFMT_S16_LE;
default:
break;
}
break;
default:
break;
}
return 0;
}
static BOOL audin_oss_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
{
if (device == nullptr || format == nullptr)
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;
default:
return FALSE;
}
return TRUE;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_oss_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
UINT32 FramesPerPacket)
{
AudinOSSDevice* oss = (AudinOSSDevice*)device;
if (device == nullptr || format == nullptr)
return ERROR_INVALID_PARAMETER;
oss->FramesPerPacket = FramesPerPacket;
oss->format = *format;
return CHANNEL_RC_OK;
}
static DWORD WINAPI audin_oss_thread_func(LPVOID arg)
{
char dev_name[PATH_MAX] = "/dev/dsp";
int pcm_handle = -1;
BYTE* buffer = nullptr;
unsigned long tmp = 0;
size_t buffer_size = 0;
AudinOSSDevice* oss = (AudinOSSDevice*)arg;
UINT error = 0;
DWORD status = 0;
if (oss == nullptr)
{
error = ERROR_INVALID_PARAMETER;
goto err_out;
}
if (oss->dev_unit != -1)
(void)sprintf_s(dev_name, (PATH_MAX - 1), "/dev/dsp%i", oss->dev_unit);
WLog_INFO(TAG, "open: %s", dev_name);
if ((pcm_handle = open(dev_name, O_RDONLY)) < 0)
{
OSS_LOG_ERR("sound dev open failed", errno);
error = ERROR_INTERNAL_ERROR;
goto err_out;
}
/* Set format. */
tmp = audin_oss_get_format(&oss->format);
if (ioctl(pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
tmp = oss->format.nChannels;
if (ioctl(pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
tmp = oss->format.nSamplesPerSec;
if (ioctl(pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
tmp = oss->format.nBlockAlign;
if (ioctl(pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
buffer_size =
(1ull * oss->FramesPerPacket * oss->format.nChannels * (oss->format.wBitsPerSample / 8ull));
buffer = (BYTE*)calloc((buffer_size + sizeof(void*)), sizeof(BYTE));
if (nullptr == buffer)
{
OSS_LOG_ERR("malloc() fail", errno);
error = ERROR_NOT_ENOUGH_MEMORY;
goto err_out;
}
while (1)
{
SSIZE_T stmp = -1;
status = WaitForSingleObject(oss->stopEvent, 0);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
goto err_out;
}
if (status == WAIT_OBJECT_0)
break;
stmp = read(pcm_handle, buffer, buffer_size);
/* Error happen. */
if (stmp < 0)
{
OSS_LOG_ERR("read() error", errno);
continue;
}
if ((size_t)stmp < buffer_size) /* Not enough data. */
continue;
if ((error = oss->receive(&oss->format, buffer, buffer_size, oss->user_data)))
{
WLog_ERR(TAG, "oss->receive failed with error %" PRIu32 "", error);
break;
}
}
err_out:
if (error && oss && oss->rdpcontext)
setChannelError(oss->rdpcontext, error, "audin_oss_thread_func reported an error");
if (pcm_handle != -1)
{
WLog_INFO(TAG, "close: %s", dev_name);
close(pcm_handle);
}
free(buffer);
ExitThread(error);
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_oss_open(IAudinDevice* device, AudinReceive receive, void* user_data)
{
AudinOSSDevice* oss = (AudinOSSDevice*)device;
oss->receive = receive;
oss->user_data = user_data;
if (!(oss->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
{
WLog_ERR(TAG, "CreateEvent failed!");
return ERROR_INTERNAL_ERROR;
}
if (!(oss->thread = CreateThread(nullptr, 0, audin_oss_thread_func, oss, 0, nullptr)))
{
WLog_ERR(TAG, "CreateThread failed!");
(void)CloseHandle(oss->stopEvent);
oss->stopEvent = nullptr;
return ERROR_INTERNAL_ERROR;
}
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_oss_close(IAudinDevice* device)
{
UINT error = 0;
AudinOSSDevice* oss = (AudinOSSDevice*)device;
if (device == nullptr)
return ERROR_INVALID_PARAMETER;
if (oss->stopEvent != nullptr)
{
(void)SetEvent(oss->stopEvent);
if (WaitForSingleObject(oss->thread, INFINITE) == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
return error;
}
(void)CloseHandle(oss->stopEvent);
oss->stopEvent = nullptr;
(void)CloseHandle(oss->thread);
oss->thread = nullptr;
}
oss->receive = nullptr;
oss->user_data = nullptr;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_oss_free(IAudinDevice* device)
{
AudinOSSDevice* oss = (AudinOSSDevice*)device;
UINT error = 0;
if (device == nullptr)
return ERROR_INVALID_PARAMETER;
if ((error = audin_oss_close(device)))
{
WLog_ERR(TAG, "audin_oss_close failed with error code %" PRIu32 "!", error);
}
free(oss);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_oss_parse_addin_args(AudinOSSDevice* device, const ADDIN_ARGV* args)
{
int status = 0;
char* str_num = nullptr;
char* eptr = nullptr;
DWORD flags = 0;
const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
AudinOSSDevice* oss = device;
COMMAND_LINE_ARGUMENT_A audin_oss_args[] = {
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
"audio device name" },
{ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
};
flags =
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
status = CommandLineParseArgumentsA(args->argc, args->argv, audin_oss_args, flags, oss, nullptr,
nullptr);
if (status < 0)
return ERROR_INVALID_PARAMETER;
arg = audin_oss_args;
errno = 0;
do
{
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
continue;
CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
{
str_num = _strdup(arg->Value);
if (!str_num)
{
WLog_ERR(TAG, "_strdup failed!");
return CHANNEL_RC_NO_MEMORY;
}
{
long val = strtol(str_num, &eptr, 10);
if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX))
{
free(str_num);
return CHANNEL_RC_NULL_DATA;
}
oss->dev_unit = (INT32)val;
}
if (oss->dev_unit < 0 || *eptr != '\0')
oss->dev_unit = -1;
free(str_num);
}
CommandLineSwitchEnd(arg)
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_audin_client_subsystem_entry(
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
{
const ADDIN_ARGV* args = nullptr;
AudinOSSDevice* oss = nullptr;
UINT error = 0;
oss = (AudinOSSDevice*)calloc(1, sizeof(AudinOSSDevice));
if (!oss)
{
WLog_ERR(TAG, "calloc failed!");
return CHANNEL_RC_NO_MEMORY;
}
oss->iface.Open = audin_oss_open;
oss->iface.FormatSupported = audin_oss_format_supported;
oss->iface.SetFormat = audin_oss_set_format;
oss->iface.Close = audin_oss_close;
oss->iface.Free = audin_oss_free;
oss->rdpcontext = pEntryPoints->rdpcontext;
oss->dev_unit = -1;
args = pEntryPoints->args;
if ((error = audin_oss_parse_addin_args(oss, args)))
{
WLog_ERR(TAG, "audin_oss_parse_addin_args failed with errorcode %" PRIu32 "!", error);
goto error_out;
}
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)oss)))
{
WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
goto error_out;
}
return CHANNEL_RC_OK;
error_out:
free(oss);
return error;
}

View File

@@ -0,0 +1,30 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# 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("audin" "pulse" "")
find_package(PulseAudio REQUIRED)
freerdp_client_pc_add_requires_private("libpulse")
set(${MODULE_PREFIX}_SRCS audin_pulse.c)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY})
include_directories(..)
include_directories(SYSTEM ${PULSEAUDIO_INCLUDE_DIR})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,584 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - PulseAudio implementation
*
* Copyright 2010-2011 Vic Lee
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* 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.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/cmdline.h>
#include <winpr/wlog.h>
#include <winpr/cast.h>
#include <pulse/pulseaudio.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/freerdp.h>
#include <freerdp/codec/audio.h>
#include <freerdp/client/audin.h>
#include <freerdp/utils/helpers.h>
#include "audin_main.h"
typedef struct
{
IAudinDevice iface;
char* device_name;
char* client_name;
char* stream_name;
UINT32 frames_per_packet;
pa_threaded_mainloop* mainloop;
pa_context* context;
pa_sample_spec sample_spec;
pa_stream* stream;
AUDIO_FORMAT format;
size_t bytes_per_frame;
size_t buffer_frames;
AudinReceive receive;
void* user_data;
rdpContext* rdpcontext;
wLog* log;
} AudinPulseDevice;
static const char* pulse_context_state_string(pa_context_state_t state)
{
switch (state)
{
case PA_CONTEXT_UNCONNECTED:
return "PA_CONTEXT_UNCONNECTED";
case PA_CONTEXT_CONNECTING:
return "PA_CONTEXT_CONNECTING";
case PA_CONTEXT_AUTHORIZING:
return "PA_CONTEXT_AUTHORIZING";
case PA_CONTEXT_SETTING_NAME:
return "PA_CONTEXT_SETTING_NAME";
case PA_CONTEXT_READY:
return "PA_CONTEXT_READY";
case PA_CONTEXT_FAILED:
return "PA_CONTEXT_FAILED";
case PA_CONTEXT_TERMINATED:
return "PA_CONTEXT_TERMINATED";
default:
return "UNKNOWN";
}
}
static const char* pulse_stream_state_string(pa_stream_state_t state)
{
switch (state)
{
case PA_STREAM_UNCONNECTED:
return "PA_STREAM_UNCONNECTED";
case PA_STREAM_CREATING:
return "PA_STREAM_CREATING";
case PA_STREAM_READY:
return "PA_STREAM_READY";
case PA_STREAM_FAILED:
return "PA_STREAM_FAILED";
case PA_STREAM_TERMINATED:
return "PA_STREAM_TERMINATED";
default:
return "UNKNOWN";
}
}
static void audin_pulse_context_state_callback(pa_context* context, void* userdata)
{
AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
pa_context_state_t state = pa_context_get_state(context);
WLog_Print(pulse->log, WLOG_DEBUG, "context state %s", pulse_context_state_string(state));
switch (state)
{
case PA_CONTEXT_READY:
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
pa_threaded_mainloop_signal(pulse->mainloop, 0);
break;
default:
break;
}
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_pulse_connect(IAudinDevice* device)
{
pa_context_state_t state = PA_CONTEXT_FAILED;
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
if (!pulse->context)
return ERROR_INVALID_PARAMETER;
if (pa_context_connect(pulse->context, nullptr, PA_CONTEXT_NOFLAGS, nullptr))
{
WLog_Print(pulse->log, WLOG_ERROR, "pa_context_connect failed (%d)",
pa_context_errno(pulse->context));
return ERROR_INTERNAL_ERROR;
}
pa_threaded_mainloop_lock(pulse->mainloop);
if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
{
pa_threaded_mainloop_unlock(pulse->mainloop);
WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_start failed (%d)",
pa_context_errno(pulse->context));
return ERROR_INTERNAL_ERROR;
}
for (;;)
{
state = pa_context_get_state(pulse->context);
if (state == PA_CONTEXT_READY)
break;
if (!PA_CONTEXT_IS_GOOD(state))
{
WLog_Print(pulse->log, WLOG_ERROR, "bad context state (%s: %d)",
pulse_context_state_string(state), pa_context_errno(pulse->context));
pa_context_disconnect(pulse->context);
return ERROR_INVALID_STATE;
}
pa_threaded_mainloop_wait(pulse->mainloop);
}
pa_threaded_mainloop_unlock(pulse->mainloop);
WLog_Print(pulse->log, WLOG_DEBUG, "connected");
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_pulse_free(IAudinDevice* device)
{
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
if (!pulse)
return ERROR_INVALID_PARAMETER;
if (pulse->mainloop)
{
pa_threaded_mainloop_stop(pulse->mainloop);
}
if (pulse->context)
{
pa_context_disconnect(pulse->context);
pa_context_unref(pulse->context);
pulse->context = nullptr;
}
if (pulse->mainloop)
{
pa_threaded_mainloop_free(pulse->mainloop);
pulse->mainloop = nullptr;
}
free(pulse->device_name);
free(pulse->client_name);
free(pulse->stream_name);
free(pulse);
return CHANNEL_RC_OK;
}
static BOOL audin_pulse_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
{
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
if (!pulse || !format)
return FALSE;
if (!pulse->context)
return 0;
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM:
if (format->cbSize == 0 && (format->nSamplesPerSec <= PA_RATE_MAX) &&
(format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
(format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX))
{
return TRUE;
}
break;
default:
return FALSE;
}
return FALSE;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_pulse_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
UINT32 FramesPerPacket)
{
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
if (!pulse || !format)
return ERROR_INVALID_PARAMETER;
if (!pulse->context)
return ERROR_INVALID_PARAMETER;
if (FramesPerPacket > 0)
pulse->frames_per_packet = FramesPerPacket;
pa_sample_format_t sformat = PA_SAMPLE_INVALID;
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM: /* PCM */
switch (format->wBitsPerSample)
{
case 8:
sformat = PA_SAMPLE_U8;
break;
case 16:
sformat = PA_SAMPLE_S16LE;
break;
default:
return ERROR_INTERNAL_ERROR;
}
break;
default:
return ERROR_INTERNAL_ERROR;
}
const pa_sample_spec sample_spec = {
.format = sformat,
.rate = format->nSamplesPerSec,
.channels = WINPR_ASSERTING_INT_CAST(uint8_t, format->nChannels),
};
pulse->sample_spec = sample_spec;
pulse->format = *format;
return CHANNEL_RC_OK;
}
static void audin_pulse_stream_state_callback(pa_stream* stream, void* userdata)
{
AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
WINPR_ASSERT(pulse);
pa_stream_state_t state = pa_stream_get_state(stream);
WLog_Print(pulse->log, WLOG_DEBUG, "stream state %s", pulse_stream_state_string(state));
switch (state)
{
case PA_STREAM_READY:
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
pa_threaded_mainloop_signal(pulse->mainloop, 0);
break;
case PA_STREAM_UNCONNECTED:
case PA_STREAM_CREATING:
default:
break;
}
}
static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata)
{
const void* data = nullptr;
AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
UINT error = CHANNEL_RC_OK;
pa_stream_peek(stream, &data, &length);
error =
IFCALLRESULT(CHANNEL_RC_OK, pulse->receive, &pulse->format, data, length, pulse->user_data);
pa_stream_drop(stream);
if (error && pulse->rdpcontext)
setChannelError(pulse->rdpcontext, error, "audin_pulse_thread_func reported an error");
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_pulse_close(IAudinDevice* device)
{
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
if (!pulse)
return ERROR_INVALID_PARAMETER;
if (pulse->stream)
{
pa_threaded_mainloop_lock(pulse->mainloop);
pa_stream_disconnect(pulse->stream);
pa_stream_unref(pulse->stream);
pulse->stream = nullptr;
pa_threaded_mainloop_unlock(pulse->mainloop);
}
pulse->receive = nullptr;
pulse->user_data = nullptr;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* user_data)
{
pa_stream_state_t state = PA_STREAM_FAILED;
pa_buffer_attr buffer_attr = WINPR_C_ARRAY_INIT;
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
if (!pulse || !receive || !user_data)
return ERROR_INVALID_PARAMETER;
if (!pulse->context)
return ERROR_INVALID_PARAMETER;
if (!pulse->sample_spec.rate || pulse->stream)
return ERROR_INVALID_PARAMETER;
pulse->receive = receive;
pulse->user_data = user_data;
pa_threaded_mainloop_lock(pulse->mainloop);
pulse->stream = pa_stream_new(pulse->context, pulse->stream_name, &pulse->sample_spec, nullptr);
if (!pulse->stream)
{
pa_threaded_mainloop_unlock(pulse->mainloop);
WLog_Print(pulse->log, WLOG_DEBUG, "pa_stream_new failed (%d)",
pa_context_errno(pulse->context));
const int rc = pa_context_errno(pulse->context);
return (UINT)rc;
}
pulse->bytes_per_frame = pa_frame_size(&pulse->sample_spec);
pa_stream_set_state_callback(pulse->stream, audin_pulse_stream_state_callback, pulse);
pa_stream_set_read_callback(pulse->stream, audin_pulse_stream_request_callback, pulse);
buffer_attr.maxlength = (UINT32)-1;
buffer_attr.tlength = (UINT32)-1;
buffer_attr.prebuf = (UINT32)-1;
buffer_attr.minreq = (UINT32)-1;
/* 500ms latency */
const size_t frag = pulse->bytes_per_frame * pulse->frames_per_packet;
WINPR_ASSERT(frag <= UINT32_MAX);
buffer_attr.fragsize = (uint32_t)frag;
if (buffer_attr.fragsize % pulse->format.nBlockAlign)
buffer_attr.fragsize +=
pulse->format.nBlockAlign - buffer_attr.fragsize % pulse->format.nBlockAlign;
if (pa_stream_connect_record(pulse->stream, pulse->device_name, &buffer_attr,
PA_STREAM_ADJUST_LATENCY) < 0)
{
pa_threaded_mainloop_unlock(pulse->mainloop);
WLog_Print(pulse->log, WLOG_ERROR, "pa_stream_connect_playback failed (%d)",
pa_context_errno(pulse->context));
const int rc = pa_context_errno(pulse->context);
return (UINT)rc;
}
while (pulse->stream)
{
state = pa_stream_get_state(pulse->stream);
if (state == PA_STREAM_READY)
break;
if (!PA_STREAM_IS_GOOD(state))
{
audin_pulse_close(device);
WLog_Print(pulse->log, WLOG_ERROR, "bad stream state (%s: %d)",
pulse_stream_state_string(state), pa_context_errno(pulse->context));
pa_threaded_mainloop_unlock(pulse->mainloop);
const int rc = pa_context_errno(pulse->context);
return (UINT)rc;
}
pa_threaded_mainloop_wait(pulse->mainloop);
}
pa_threaded_mainloop_unlock(pulse->mainloop);
pulse->buffer_frames = 0;
WLog_Print(pulse->log, WLOG_DEBUG, "connected");
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_pulse_parse_addin_args(AudinPulseDevice* pulse, const ADDIN_ARGV* args)
{
COMMAND_LINE_ARGUMENT_A audin_pulse_args[] = {
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
"audio device name" },
{ "client_name", COMMAND_LINE_VALUE_REQUIRED, "<client_name>", nullptr, nullptr, -1,
nullptr, "name of pulse client" },
{ "stream_name", COMMAND_LINE_VALUE_REQUIRED, "<stream_name>", nullptr, nullptr, -1,
nullptr, "name of pulse stream" },
{ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
};
const DWORD flags =
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
const int status = CommandLineParseArgumentsA(args->argc, args->argv, audin_pulse_args, flags,
pulse, nullptr, nullptr);
if (status < 0)
return ERROR_INVALID_PARAMETER;
const COMMAND_LINE_ARGUMENT_A* arg = audin_pulse_args;
const char* client_name = nullptr;
const char* stream_name = nullptr;
do
{
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
continue;
CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
{
pulse->device_name = _strdup(arg->Value);
if (!pulse->device_name)
{
WLog_Print(pulse->log, WLOG_ERROR, "_strdup failed!");
return CHANNEL_RC_NO_MEMORY;
}
}
CommandLineSwitchEnd(arg)
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
if (!client_name)
client_name = freerdp_getApplicationDetailsString();
if (!stream_name)
stream_name = freerdp_getApplicationDetailsString();
pulse->client_name = _strdup(client_name);
pulse->stream_name = _strdup(stream_name);
if (!pulse->client_name || !pulse->stream_name)
return ERROR_OUTOFMEMORY;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_audin_client_subsystem_entry(
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
{
const ADDIN_ARGV* args = nullptr;
AudinPulseDevice* pulse = nullptr;
UINT error = 0;
pulse = (AudinPulseDevice*)calloc(1, sizeof(AudinPulseDevice));
if (!pulse)
{
WLog_ERR(TAG, "calloc failed!");
return CHANNEL_RC_NO_MEMORY;
}
pulse->log = WLog_Get(TAG);
pulse->iface.Open = audin_pulse_open;
pulse->iface.FormatSupported = audin_pulse_format_supported;
pulse->iface.SetFormat = audin_pulse_set_format;
pulse->iface.Close = audin_pulse_close;
pulse->iface.Free = audin_pulse_free;
pulse->rdpcontext = pEntryPoints->rdpcontext;
args = pEntryPoints->args;
if ((error = audin_pulse_parse_addin_args(pulse, args)))
{
WLog_Print(pulse->log, WLOG_ERROR,
"audin_pulse_parse_addin_args failed with error %" PRIu32 "!", error);
goto error_out;
}
pulse->mainloop = pa_threaded_mainloop_new();
if (!pulse->mainloop)
{
WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_new failed");
error = CHANNEL_RC_NO_MEMORY;
goto error_out;
}
pulse->context =
pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), pulse->client_name);
if (!pulse->context)
{
WLog_Print(pulse->log, WLOG_ERROR, "pa_context_new failed");
error = CHANNEL_RC_NO_MEMORY;
goto error_out;
}
pa_context_set_state_callback(pulse->context, audin_pulse_context_state_callback, pulse);
if ((error = audin_pulse_connect(&pulse->iface)))
{
WLog_Print(pulse->log, WLOG_ERROR, "audin_pulse_connect failed");
goto error_out;
}
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &pulse->iface)))
{
WLog_Print(pulse->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
error);
goto error_out;
}
return CHANNEL_RC_OK;
error_out:
audin_pulse_free(&pulse->iface);
return error;
}

View File

@@ -0,0 +1,30 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
# Copyright (c) 2020 Ingo Feinerer <feinerer@logic.at>
#
# 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("audin" "sndio" "")
find_package(SNDIO REQUIRED)
set(${MODULE_PREFIX}_SRCS audin_sndio.c)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${SNDIO_LIBRARIES})
include_directories(..)
include_directories(SYSTEM ${SNDIO_INCLUDE_DIRS})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,353 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - sndio implementation
*
* Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2020 Ingo Feinerer <feinerer@logic.at>
*
* 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.
*/
#include <freerdp/config.h>
#include <sndio.h>
#include <winpr/cmdline.h>
#include <freerdp/freerdp.h>
#include <freerdp/channels/rdpsnd.h>
#include "audin_main.h"
typedef struct
{
IAudinDevice device;
HANDLE thread;
HANDLE stopEvent;
AUDIO_FORMAT format;
UINT32 FramesPerPacket;
AudinReceive receive;
void* user_data;
rdpContext* rdpcontext;
} AudinSndioDevice;
static BOOL audin_sndio_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
{
if (device == nullptr || format == nullptr)
return FALSE;
return (format->wFormatTag == WAVE_FORMAT_PCM);
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_sndio_set_format(IAudinDevice* device, AUDIO_FORMAT* format,
UINT32 FramesPerPacket)
{
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
if (device == nullptr || format == nullptr)
return ERROR_INVALID_PARAMETER;
if (format->wFormatTag != WAVE_FORMAT_PCM)
return ERROR_INTERNAL_ERROR;
sndio->format = *format;
sndio->FramesPerPacket = FramesPerPacket;
return CHANNEL_RC_OK;
}
static void* audin_sndio_thread_func(void* arg)
{
struct sio_hdl* hdl;
struct sio_par par;
BYTE* buffer = nullptr;
size_t n, nbytes;
AudinSndioDevice* sndio = (AudinSndioDevice*)arg;
UINT error = 0;
DWORD status;
if (arg == nullptr)
{
error = ERROR_INVALID_PARAMETER;
goto err_out;
}
hdl = sio_open(SIO_DEVANY, SIO_REC, 0);
if (hdl == nullptr)
{
WLog_ERR(TAG, "could not open audio device");
error = ERROR_INTERNAL_ERROR;
goto err_out;
}
sio_initpar(&par);
par.bits = sndio->format.wBitsPerSample;
par.rchan = sndio->format.nChannels;
par.rate = sndio->format.nSamplesPerSec;
if (!sio_setpar(hdl, &par))
{
WLog_ERR(TAG, "could not set audio parameters");
error = ERROR_INTERNAL_ERROR;
goto err_out;
}
if (!sio_getpar(hdl, &par))
{
WLog_ERR(TAG, "could not get audio parameters");
error = ERROR_INTERNAL_ERROR;
goto err_out;
}
if (!sio_start(hdl))
{
WLog_ERR(TAG, "could not start audio device");
error = ERROR_INTERNAL_ERROR;
goto err_out;
}
nbytes =
(sndio->FramesPerPacket * sndio->format.nChannels * (sndio->format.wBitsPerSample / 8));
buffer = (BYTE*)calloc((nbytes + sizeof(void*)), sizeof(BYTE));
if (buffer == nullptr)
{
error = ERROR_NOT_ENOUGH_MEMORY;
goto err_out;
}
while (1)
{
status = WaitForSingleObject(sndio->stopEvent, 0);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
goto err_out;
}
if (status == WAIT_OBJECT_0)
break;
n = sio_read(hdl, buffer, nbytes);
if (n == 0)
{
WLog_ERR(TAG, "could not read");
continue;
}
if (n < nbytes)
continue;
if ((error = sndio->receive(&sndio->format, buffer, nbytes, sndio->user_data)))
{
WLog_ERR(TAG, "sndio->receive failed with error %" PRIu32 "", error);
break;
}
}
err_out:
if (error && sndio && sndio->rdpcontext)
setChannelError(sndio->rdpcontext, error, "audin_sndio_thread_func reported an error");
if (hdl != nullptr)
{
WLog_INFO(TAG, "sio_close");
sio_stop(hdl);
sio_close(hdl);
}
free(buffer);
ExitThread(0);
return nullptr;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_sndio_open(IAudinDevice* device, AudinReceive receive, void* user_data)
{
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
sndio->receive = receive;
sndio->user_data = user_data;
if (!(sndio->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
{
WLog_ERR(TAG, "CreateEvent failed");
return ERROR_INTERNAL_ERROR;
}
if (!(sndio->thread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)audin_sndio_thread_func,
sndio, 0, nullptr)))
{
WLog_ERR(TAG, "CreateThread failed");
(void)CloseHandle(sndio->stopEvent);
sndio->stopEvent = nullptr;
return ERROR_INTERNAL_ERROR;
}
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_sndio_close(IAudinDevice* device)
{
UINT error;
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
if (device == nullptr)
return ERROR_INVALID_PARAMETER;
if (sndio->stopEvent != nullptr)
{
(void)SetEvent(sndio->stopEvent);
if (WaitForSingleObject(sndio->thread, INFINITE) == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
return error;
}
(void)CloseHandle(sndio->stopEvent);
sndio->stopEvent = nullptr;
(void)CloseHandle(sndio->thread);
sndio->thread = nullptr;
}
sndio->receive = nullptr;
sndio->user_data = nullptr;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_sndio_free(IAudinDevice* device)
{
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
int error;
if (device == nullptr)
return ERROR_INVALID_PARAMETER;
if ((error = audin_sndio_close(device)))
{
WLog_ERR(TAG, "audin_sndio_close failed with error code %d", error);
}
free(sndio);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_sndio_parse_addin_args(AudinSndioDevice* device, ADDIN_ARGV* args)
{
int status;
DWORD flags;
COMMAND_LINE_ARGUMENT_A* arg;
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
COMMAND_LINE_ARGUMENT_A audin_sndio_args[] = { { nullptr, 0, nullptr, nullptr, nullptr, -1,
nullptr, nullptr } };
flags =
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
status = CommandLineParseArgumentsA(args->argc, (const char**)args->argv, audin_sndio_args,
flags, sndio, nullptr, nullptr);
if (status < 0)
return ERROR_INVALID_PARAMETER;
arg = audin_sndio_args;
do
{
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
continue;
CommandLineSwitchStart(arg) CommandLineSwitchEnd(arg)
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
FREERDP_ENTRY_POINT(UINT VCAPITYPE sndio_freerdp_audin_client_subsystem_entry(
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
{
ADDIN_ARGV* args;
AudinSndioDevice* sndio;
UINT ret = CHANNEL_RC_OK;
sndio = (AudinSndioDevice*)calloc(1, sizeof(AudinSndioDevice));
if (sndio == nullptr)
return CHANNEL_RC_NO_MEMORY;
sndio->device.Open = audin_sndio_open;
sndio->device.FormatSupported = audin_sndio_format_supported;
sndio->device.SetFormat = audin_sndio_set_format;
sndio->device.Close = audin_sndio_close;
sndio->device.Free = audin_sndio_free;
sndio->rdpcontext = pEntryPoints->rdpcontext;
args = pEntryPoints->args;
if (args->argc > 1)
{
ret = audin_sndio_parse_addin_args(sndio, args);
if (ret != CHANNEL_RC_OK)
{
WLog_ERR(TAG, "error parsing arguments");
goto error;
}
}
if ((ret = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)sndio)))
{
WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "", ret);
goto error;
}
return ret;
error:
audin_sndio_free(&sndio->device);
return ret;
}

View File

@@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# 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("audin" "winmm" "")
set(${MODULE_PREFIX}_SRCS audin_winmm.c)
set(${MODULE_PREFIX}_LIBS winpr freerdp winmm.lib)
include_directories(..)
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,568 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - WinMM implementation
*
* Copyright 2013 Zhang Zhaolong <zhangzl2013@126.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* 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.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <mmsystem.h>
#include <winpr/crt.h>
#include <winpr/wtsapi.h>
#include <winpr/cmdline.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/client/audin.h>
#include "audin_main.h"
/* fix missing definitions in mingw */
#ifndef WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE
#define WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE 0x0010
#endif
typedef struct
{
IAudinDevice iface;
char* device_name;
AudinReceive receive;
void* user_data;
HANDLE thread;
HANDLE stopEvent;
HWAVEIN hWaveIn;
PWAVEFORMATEX* ppwfx;
PWAVEFORMATEX pwfx_cur;
UINT32 ppwfx_size;
UINT32 cFormats;
UINT32 frames_per_packet;
rdpContext* rdpcontext;
wLog* log;
} AudinWinmmDevice;
static void CALLBACK waveInProc(HWAVEIN hWaveIn, UINT uMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
AudinWinmmDevice* winmm = (AudinWinmmDevice*)dwInstance;
PWAVEHDR pWaveHdr;
UINT error = CHANNEL_RC_OK;
MMRESULT mmResult;
switch (uMsg)
{
case WIM_CLOSE:
break;
case WIM_DATA:
pWaveHdr = (WAVEHDR*)dwParam1;
if (WHDR_DONE == (WHDR_DONE & pWaveHdr->dwFlags))
{
if (pWaveHdr->dwBytesRecorded &&
!(WaitForSingleObject(winmm->stopEvent, 0) == WAIT_OBJECT_0))
{
AUDIO_FORMAT format;
format.cbSize = winmm->pwfx_cur->cbSize;
format.nBlockAlign = winmm->pwfx_cur->nBlockAlign;
format.nAvgBytesPerSec = winmm->pwfx_cur->nAvgBytesPerSec;
format.nChannels = winmm->pwfx_cur->nChannels;
format.nSamplesPerSec = winmm->pwfx_cur->nSamplesPerSec;
format.wBitsPerSample = winmm->pwfx_cur->wBitsPerSample;
format.wFormatTag = winmm->pwfx_cur->wFormatTag;
if ((error = winmm->receive(&format, pWaveHdr->lpData,
pWaveHdr->dwBytesRecorded, winmm->user_data)))
break;
mmResult = waveInAddBuffer(hWaveIn, pWaveHdr, sizeof(WAVEHDR));
if (mmResult != MMSYSERR_NOERROR)
error = ERROR_INTERNAL_ERROR;
}
}
break;
case WIM_OPEN:
break;
default:
break;
}
if (error && winmm->rdpcontext)
setChannelError(winmm->rdpcontext, error, "waveInProc reported an error");
}
static BOOL log_mmresult(AudinWinmmDevice* winmm, const char* what, MMRESULT result)
{
if (result != MMSYSERR_NOERROR)
{
CHAR buffer[8192] = WINPR_C_ARRAY_INIT;
CHAR msg[8192] = WINPR_C_ARRAY_INIT;
CHAR cmsg[8192] = WINPR_C_ARRAY_INIT;
waveInGetErrorTextA(result, buffer, sizeof(buffer));
_snprintf(msg, sizeof(msg) - 1, "%s failed. %" PRIu32 " [%s]", what, result, buffer);
_snprintf(cmsg, sizeof(cmsg) - 1, "audin_winmm_thread_func reported an error '%s'", msg);
WLog_Print(winmm->log, WLOG_DEBUG, "%s", msg);
if (winmm->rdpcontext)
setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, cmsg);
return FALSE;
}
return TRUE;
}
static BOOL test_format_supported(const PWAVEFORMATEX pwfx)
{
MMRESULT rc;
WAVEINCAPSA caps = WINPR_C_ARRAY_INIT;
rc = waveInGetDevCapsA(WAVE_MAPPER, &caps, sizeof(caps));
if (rc != MMSYSERR_NOERROR)
return FALSE;
switch (pwfx->nChannels)
{
case 1:
if ((caps.dwFormats &
(WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08 |
WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_96M16)) == 0)
return FALSE;
break;
case 2:
if ((caps.dwFormats &
(WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08 |
WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_96S16)) == 0)
return FALSE;
break;
default:
return FALSE;
}
rc = waveInOpen(nullptr, WAVE_MAPPER, pwfx, 0, 0,
WAVE_FORMAT_QUERY | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
return (rc == MMSYSERR_NOERROR);
}
static DWORD WINAPI audin_winmm_thread_func(LPVOID arg)
{
AudinWinmmDevice* winmm = (AudinWinmmDevice*)arg;
char* buffer = nullptr;
int size = 0;
WAVEHDR waveHdr[4] = WINPR_C_ARRAY_INIT;
DWORD status = 0;
MMRESULT rc = 0;
if (!winmm->hWaveIn)
{
rc = waveInOpen(&winmm->hWaveIn, WAVE_MAPPER, winmm->pwfx_cur, (DWORD_PTR)waveInProc,
(DWORD_PTR)winmm,
CALLBACK_FUNCTION | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE);
if (!log_mmresult(winmm, "waveInOpen", rc))
return ERROR_INTERNAL_ERROR;
}
size =
(winmm->pwfx_cur->wBitsPerSample * winmm->pwfx_cur->nChannels * winmm->frames_per_packet +
7) /
8;
for (int i = 0; i < 4; i++)
{
buffer = (char*)malloc(size);
if (!buffer)
return CHANNEL_RC_NO_MEMORY;
waveHdr[i].dwBufferLength = size;
waveHdr[i].dwFlags = 0;
waveHdr[i].lpData = buffer;
rc = waveInPrepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
if (!log_mmresult(winmm, "waveInPrepareHeader", rc))
{
}
rc = waveInAddBuffer(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
if (!log_mmresult(winmm, "waveInAddBuffer", rc))
{
}
}
rc = waveInStart(winmm->hWaveIn);
if (!log_mmresult(winmm, "waveInStart", rc))
{
}
status = WaitForSingleObject(winmm->stopEvent, INFINITE);
if (status == WAIT_FAILED)
{
WLog_Print(winmm->log, WLOG_DEBUG, "WaitForSingleObject failed.");
if (winmm->rdpcontext)
setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR,
"audin_winmm_thread_func reported an error");
}
rc = waveInReset(winmm->hWaveIn);
if (!log_mmresult(winmm, "waveInReset", rc))
{
}
for (int i = 0; i < 4; i++)
{
rc = waveInUnprepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i]));
if (!log_mmresult(winmm, "waveInUnprepareHeader", rc))
{
}
free(waveHdr[i].lpData);
}
rc = waveInClose(winmm->hWaveIn);
if (!log_mmresult(winmm, "waveInClose", rc))
{
}
winmm->hWaveIn = nullptr;
return 0;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_winmm_free(IAudinDevice* device)
{
AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
if (!winmm)
return ERROR_INVALID_PARAMETER;
for (UINT32 i = 0; i < winmm->cFormats; i++)
{
free(winmm->ppwfx[i]);
}
free(winmm->ppwfx);
free(winmm->device_name);
free(winmm);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_winmm_close(IAudinDevice* device)
{
DWORD status;
UINT error = CHANNEL_RC_OK;
AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
if (!winmm)
return ERROR_INVALID_PARAMETER;
(void)SetEvent(winmm->stopEvent);
status = WaitForSingleObject(winmm->thread, INFINITE);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_Print(winmm->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!",
error);
return error;
}
(void)CloseHandle(winmm->thread);
(void)CloseHandle(winmm->stopEvent);
winmm->thread = nullptr;
winmm->stopEvent = nullptr;
winmm->receive = nullptr;
winmm->user_data = nullptr;
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_winmm_set_format(IAudinDevice* device, const AUDIO_FORMAT* format,
UINT32 FramesPerPacket)
{
AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
if (!winmm || !format)
return ERROR_INVALID_PARAMETER;
winmm->frames_per_packet = FramesPerPacket;
for (UINT32 i = 0; i < winmm->cFormats; i++)
{
const PWAVEFORMATEX ppwfx = winmm->ppwfx[i];
if ((ppwfx->wFormatTag == format->wFormatTag) && (ppwfx->nChannels == format->nChannels) &&
(ppwfx->wBitsPerSample == format->wBitsPerSample) &&
(ppwfx->nSamplesPerSec == format->nSamplesPerSec))
{
/* BUG: Many devices report to support stereo recording but fail here.
* Ensure we always use mono. */
if (ppwfx->nChannels > 1)
{
ppwfx->nChannels = 1;
}
if (ppwfx->nBlockAlign != 2)
{
ppwfx->nBlockAlign = 2;
ppwfx->nAvgBytesPerSec = ppwfx->nSamplesPerSec * ppwfx->nBlockAlign;
}
if (!test_format_supported(ppwfx))
return ERROR_INVALID_PARAMETER;
winmm->pwfx_cur = ppwfx;
return CHANNEL_RC_OK;
}
}
return ERROR_INVALID_PARAMETER;
}
static BOOL audin_winmm_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
{
AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
PWAVEFORMATEX pwfx;
BYTE* data;
if (!winmm || !format)
return FALSE;
if (format->wFormatTag != WAVE_FORMAT_PCM)
return FALSE;
if (format->nChannels != 1)
return FALSE;
pwfx = (PWAVEFORMATEX)malloc(sizeof(WAVEFORMATEX) + format->cbSize);
if (!pwfx)
return FALSE;
pwfx->cbSize = format->cbSize;
pwfx->wFormatTag = format->wFormatTag;
pwfx->nChannels = format->nChannels;
pwfx->nSamplesPerSec = format->nSamplesPerSec;
pwfx->nBlockAlign = format->nBlockAlign;
pwfx->wBitsPerSample = format->wBitsPerSample;
data = (BYTE*)pwfx + sizeof(WAVEFORMATEX);
memcpy(data, format->data, format->cbSize);
pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign;
if (!test_format_supported(pwfx))
goto fail;
if (winmm->cFormats >= winmm->ppwfx_size)
{
PWAVEFORMATEX* tmp_ppwfx;
tmp_ppwfx = realloc(winmm->ppwfx, sizeof(PWAVEFORMATEX) * winmm->ppwfx_size * 2);
if (!tmp_ppwfx)
goto fail;
winmm->ppwfx_size *= 2;
winmm->ppwfx = tmp_ppwfx;
}
winmm->ppwfx[winmm->cFormats++] = pwfx;
return TRUE;
fail:
free(pwfx);
return FALSE;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_winmm_open(IAudinDevice* device, AudinReceive receive, void* user_data)
{
AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
if (!winmm || !receive || !user_data)
return ERROR_INVALID_PARAMETER;
winmm->receive = receive;
winmm->user_data = user_data;
if (!(winmm->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
{
WLog_Print(winmm->log, WLOG_ERROR, "CreateEvent failed!");
return ERROR_INTERNAL_ERROR;
}
if (!(winmm->thread = CreateThread(nullptr, 0, audin_winmm_thread_func, winmm, 0, nullptr)))
{
WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed!");
(void)CloseHandle(winmm->stopEvent);
winmm->stopEvent = nullptr;
return ERROR_INTERNAL_ERROR;
}
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT audin_winmm_parse_addin_args(AudinWinmmDevice* device, const ADDIN_ARGV* args)
{
int status;
DWORD flags;
const COMMAND_LINE_ARGUMENT_A* arg;
AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
COMMAND_LINE_ARGUMENT_A audin_winmm_args[] = {
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr,
"audio device name" },
{ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
};
flags =
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
status = CommandLineParseArgumentsA(args->argc, args->argv, audin_winmm_args, flags, winmm,
nullptr, nullptr);
arg = audin_winmm_args;
do
{
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
continue;
CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
{
winmm->device_name = _strdup(arg->Value);
if (!winmm->device_name)
{
WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
return CHANNEL_RC_NO_MEMORY;
}
}
CommandLineSwitchEnd(arg)
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
FREERDP_ENTRY_POINT(UINT VCAPITYPE winmm_freerdp_audin_client_subsystem_entry(
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
{
const ADDIN_ARGV* args;
AudinWinmmDevice* winmm;
UINT error;
if (waveInGetNumDevs() == 0)
{
WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No microphone available!");
return ERROR_DEVICE_NOT_AVAILABLE;
}
winmm = (AudinWinmmDevice*)calloc(1, sizeof(AudinWinmmDevice));
if (!winmm)
{
WLog_ERR(TAG, "calloc failed!");
return CHANNEL_RC_NO_MEMORY;
}
winmm->log = WLog_Get(TAG);
winmm->iface.Open = audin_winmm_open;
winmm->iface.FormatSupported = audin_winmm_format_supported;
winmm->iface.SetFormat = audin_winmm_set_format;
winmm->iface.Close = audin_winmm_close;
winmm->iface.Free = audin_winmm_free;
winmm->rdpcontext = pEntryPoints->rdpcontext;
args = pEntryPoints->args;
if ((error = audin_winmm_parse_addin_args(winmm, args)))
{
WLog_Print(winmm->log, WLOG_ERROR,
"audin_winmm_parse_addin_args failed with error %" PRIu32 "!", error);
goto error_out;
}
if (!winmm->device_name)
{
winmm->device_name = _strdup("default");
if (!winmm->device_name)
{
WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!");
error = CHANNEL_RC_NO_MEMORY;
goto error_out;
}
}
winmm->ppwfx_size = 10;
winmm->ppwfx = calloc(winmm->ppwfx_size, sizeof(PWAVEFORMATEX));
if (!winmm->ppwfx)
{
WLog_Print(winmm->log, WLOG_ERROR, "malloc failed!");
error = CHANNEL_RC_NO_MEMORY;
goto error_out;
}
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &winmm->iface)))
{
WLog_Print(winmm->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
error);
goto error_out;
}
return CHANNEL_RC_OK;
error_out:
free(winmm->ppwfx);
free(winmm->device_name);
free(winmm);
return error;
}

View File

@@ -0,0 +1,23 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# 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_server("audin")
set(${MODULE_PREFIX}_SRCS audin.c)
set(${MODULE_PREFIX}_LIBS freerdp)
add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")

View File

@@ -0,0 +1,927 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Server Audio Input Virtual Channel
*
* Copyright 2012 Vic Lee
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
*
* 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.
*/
#include <freerdp/config.h>
#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <freerdp/freerdp.h>
#include <freerdp/server/server-common.h>
#include <freerdp/server/audin.h>
#include <freerdp/channels/log.h>
#define AUDIN_TAG CHANNELS_TAG("audin.server")
#define SNDIN_HEADER_SIZE 1
typedef enum
{
MSG_SNDIN_VERSION = 0x01,
MSG_SNDIN_FORMATS = 0x02,
MSG_SNDIN_OPEN = 0x03,
MSG_SNDIN_OPEN_REPLY = 0x04,
MSG_SNDIN_DATA_INCOMING = 0x05,
MSG_SNDIN_DATA = 0x06,
MSG_SNDIN_FORMATCHANGE = 0x07,
} MSG_SNDIN;
typedef struct
{
audin_server_context context;
HANDLE stopEvent;
HANDLE thread;
void* audin_channel;
DWORD SessionId;
AUDIO_FORMAT* audin_server_formats;
UINT32 audin_n_server_formats;
AUDIO_FORMAT* audin_negotiated_format;
UINT32 audin_client_format_idx;
wLog* log;
} audin_server;
static UINT audin_server_recv_version(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
audin_server* audin = (audin_server*)context;
SNDIN_VERSION pdu = WINPR_C_ARRAY_INIT;
UINT error = CHANNEL_RC_OK;
WINPR_ASSERT(context);
WINPR_ASSERT(header);
pdu.Header = *header;
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
return ERROR_NO_DATA;
{
const UINT32 version = Stream_Get_UINT32(s);
switch (version)
{
case SNDIN_VERSION_Version_1:
pdu.Version = SNDIN_VERSION_Version_1;
break;
case SNDIN_VERSION_Version_2:
pdu.Version = SNDIN_VERSION_Version_2;
break;
default:
pdu.Version = SNDIN_VERSION_Version_2;
WLog_Print(audin->log, WLOG_WARN,
"Received unsupported channel version %" PRIu32
", using highest supported version %u",
version, pdu.Version);
break;
}
}
IFCALLRET(context->ReceiveVersion, error, context, &pdu);
if (error)
WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveVersion failed with error %" PRIu32 "",
error);
return error;
}
static UINT audin_server_recv_formats(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
audin_server* audin = (audin_server*)context;
SNDIN_FORMATS pdu = WINPR_C_ARRAY_INIT;
UINT error = CHANNEL_RC_OK;
WINPR_ASSERT(context);
WINPR_ASSERT(header);
pdu.Header = *header;
/* Implementations MUST, at a minimum, support WAVE_FORMAT_PCM (0x0001) */
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4 + 4 + 18))
return ERROR_NO_DATA;
Stream_Read_UINT32(s, pdu.NumFormats);
Stream_Read_UINT32(s, pdu.cbSizeFormatsPacket);
if (pdu.NumFormats == 0)
{
WLog_Print(audin->log, WLOG_ERROR, "Sound Formats PDU contains no formats");
return ERROR_INVALID_DATA;
}
pdu.SoundFormats = audio_formats_new(pdu.NumFormats);
if (!pdu.SoundFormats)
{
WLog_Print(audin->log, WLOG_ERROR, "Failed to allocate %u SoundFormats", pdu.NumFormats);
return ERROR_NOT_ENOUGH_MEMORY;
}
for (UINT32 i = 0; i < pdu.NumFormats; ++i)
{
AUDIO_FORMAT* format = &pdu.SoundFormats[i];
if (!audio_format_read(s, format))
goto fail;
audio_format_print(audin->log, WLOG_DEBUG, format);
}
if (pdu.cbSizeFormatsPacket != Stream_GetPosition(s))
{
WLog_Print(audin->log, WLOG_WARN,
"cbSizeFormatsPacket is invalid! Expected: %u Got: %zu. Fixing size",
pdu.cbSizeFormatsPacket, Stream_GetPosition(s));
const size_t pos = Stream_GetPosition(s);
if (pos > UINT32_MAX)
{
WLog_Print(audin->log, WLOG_ERROR, "Stream too long, %" PRIuz " exceeds UINT32_MAX",
pos);
error = ERROR_INVALID_PARAMETER;
goto fail;
}
pdu.cbSizeFormatsPacket = (UINT32)pos;
}
pdu.ExtraDataSize = Stream_GetRemainingLength(s);
IFCALLRET(context->ReceiveFormats, error, context, &pdu);
if (error)
WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveFormats failed with error %" PRIu32 "",
error);
fail:
audio_formats_free(pdu.SoundFormats, pdu.NumFormats);
return error;
}
static UINT audin_server_recv_open_reply(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
audin_server* audin = (audin_server*)context;
SNDIN_OPEN_REPLY pdu = WINPR_C_ARRAY_INIT;
UINT error = CHANNEL_RC_OK;
WINPR_ASSERT(context);
WINPR_ASSERT(header);
pdu.Header = *header;
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
return ERROR_NO_DATA;
Stream_Read_UINT32(s, pdu.Result);
IFCALLRET(context->OpenReply, error, context, &pdu);
if (error)
WLog_Print(audin->log, WLOG_ERROR, "context->OpenReply failed with error %" PRIu32 "",
error);
return error;
}
static UINT audin_server_recv_data_incoming(audin_server_context* context,
WINPR_ATTR_UNUSED wStream* s, const SNDIN_PDU* header)
{
audin_server* audin = (audin_server*)context;
SNDIN_DATA_INCOMING pdu = WINPR_C_ARRAY_INIT;
UINT error = CHANNEL_RC_OK;
WINPR_ASSERT(context);
WINPR_ASSERT(header);
pdu.Header = *header;
IFCALLRET(context->IncomingData, error, context, &pdu);
if (error)
WLog_Print(audin->log, WLOG_ERROR, "context->IncomingData failed with error %" PRIu32 "",
error);
return error;
}
static UINT audin_server_recv_data(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
audin_server* audin = (audin_server*)context;
SNDIN_DATA pdu = WINPR_C_ARRAY_INIT;
wStream dataBuffer = WINPR_C_ARRAY_INIT;
UINT error = CHANNEL_RC_OK;
WINPR_ASSERT(context);
WINPR_ASSERT(header);
pdu.Header = *header;
pdu.Data = Stream_StaticInit(&dataBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s));
IFCALLRET(context->Data, error, context, &pdu);
if (error)
WLog_Print(audin->log, WLOG_ERROR, "context->Data failed with error %" PRIu32 "", error);
return error;
}
static UINT audin_server_recv_format_change(audin_server_context* context, wStream* s,
const SNDIN_PDU* header)
{
audin_server* audin = (audin_server*)context;
SNDIN_FORMATCHANGE pdu = WINPR_C_ARRAY_INIT;
UINT error = CHANNEL_RC_OK;
WINPR_ASSERT(context);
WINPR_ASSERT(header);
pdu.Header = *header;
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
return ERROR_NO_DATA;
Stream_Read_UINT32(s, pdu.NewFormat);
IFCALLRET(context->ReceiveFormatChange, error, context, &pdu);
if (error)
WLog_Print(audin->log, WLOG_ERROR,
"context->ReceiveFormatChange failed with error %" PRIu32 "", error);
return error;
}
static DWORD WINAPI audin_server_thread_func(LPVOID arg)
{
wStream* s = nullptr;
void* buffer = nullptr;
DWORD nCount = 0;
HANDLE events[8] = WINPR_C_ARRAY_INIT;
BOOL ready = FALSE;
HANDLE ChannelEvent = nullptr;
DWORD BytesReturned = 0;
audin_server* audin = (audin_server*)arg;
UINT error = CHANNEL_RC_OK;
DWORD status = ERROR_INTERNAL_ERROR;
WINPR_ASSERT(audin);
if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualEventHandle, &buffer,
&BytesReturned) == TRUE)
{
if (BytesReturned == sizeof(HANDLE))
ChannelEvent = *(HANDLE*)buffer;
WTSFreeMemory(buffer);
}
else
{
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed");
error = ERROR_INTERNAL_ERROR;
goto out;
}
nCount = 0;
events[nCount++] = audin->stopEvent;
events[nCount++] = ChannelEvent;
/* Wait for the client to confirm that the Audio Input dynamic channel is ready */
while (1)
{
status = WaitForMultipleObjects(nCount, events, FALSE, 100);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_Print(audin->log, WLOG_ERROR,
"WaitForMultipleObjects failed with error %" PRIu32 "", error);
goto out;
}
if (status == WAIT_OBJECT_0)
goto out;
if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualChannelReady, &buffer,
&BytesReturned) == FALSE)
{
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed");
error = ERROR_INTERNAL_ERROR;
goto out;
}
ready = *((BOOL*)buffer);
WTSFreeMemory(buffer);
if (ready)
break;
}
s = Stream_New(nullptr, 4096);
if (!s)
{
WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
error = CHANNEL_RC_NO_MEMORY;
goto out;
}
if (ready)
{
SNDIN_VERSION version = WINPR_C_ARRAY_INIT;
version.Version = audin->context.serverVersion;
if ((error = audin->context.SendVersion(&audin->context, &version)))
{
WLog_Print(audin->log, WLOG_ERROR, "SendVersion failed with error %" PRIu32 "!", error);
goto out_capacity;
}
}
while (ready)
{
SNDIN_PDU header = WINPR_C_ARRAY_INIT;
if ((status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE)) == WAIT_OBJECT_0)
break;
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_Print(audin->log, WLOG_ERROR,
"WaitForMultipleObjects failed with error %" PRIu32 "", error);
break;
}
if (status == WAIT_OBJECT_0)
break;
Stream_ResetPosition(s);
if (!WTSVirtualChannelRead(audin->audin_channel, 0, nullptr, 0, &BytesReturned))
{
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
error = ERROR_INTERNAL_ERROR;
break;
}
if (BytesReturned < 1)
continue;
if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
break;
WINPR_ASSERT(Stream_Capacity(s) <= UINT32_MAX);
if (WTSVirtualChannelRead(audin->audin_channel, 0, Stream_BufferAs(s, char),
(ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
{
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
error = ERROR_INTERNAL_ERROR;
break;
}
Stream_SetLength(s, BytesReturned);
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, SNDIN_HEADER_SIZE))
{
error = ERROR_INTERNAL_ERROR;
break;
}
Stream_Read_UINT8(s, header.MessageId);
switch (header.MessageId)
{
case MSG_SNDIN_VERSION:
error = audin_server_recv_version(&audin->context, s, &header);
break;
case MSG_SNDIN_FORMATS:
error = audin_server_recv_formats(&audin->context, s, &header);
break;
case MSG_SNDIN_OPEN_REPLY:
error = audin_server_recv_open_reply(&audin->context, s, &header);
break;
case MSG_SNDIN_DATA_INCOMING:
error = audin_server_recv_data_incoming(&audin->context, s, &header);
break;
case MSG_SNDIN_DATA:
error = audin_server_recv_data(&audin->context, s, &header);
break;
case MSG_SNDIN_FORMATCHANGE:
error = audin_server_recv_format_change(&audin->context, s, &header);
break;
default:
WLog_Print(audin->log, WLOG_ERROR,
"audin_server_thread_func: unknown or invalid MessageId %" PRIu8 "",
header.MessageId);
error = ERROR_INVALID_DATA;
break;
}
if (error)
break;
}
out_capacity:
Stream_Free(s, TRUE);
out:
(void)WTSVirtualChannelClose(audin->audin_channel);
audin->audin_channel = nullptr;
if (error && audin->context.rdpcontext)
setChannelError(audin->context.rdpcontext, error,
"audin_server_thread_func reported an error");
ExitThread(error);
return error;
}
static BOOL audin_server_open(audin_server_context* context)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
if (!audin->thread)
{
PULONG pSessionId = nullptr;
DWORD BytesReturned = 0;
audin->SessionId = WTS_CURRENT_SESSION;
UINT32 channelId = 0;
BOOL status = TRUE;
if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
(LPSTR*)&pSessionId, &BytesReturned))
{
audin->SessionId = (DWORD)*pSessionId;
WTSFreeMemory(pSessionId);
}
audin->audin_channel = WTSVirtualChannelOpenEx(audin->SessionId, AUDIN_DVC_CHANNEL_NAME,
WTS_CHANNEL_OPTION_DYNAMIC);
if (!audin->audin_channel)
{
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelOpenEx failed!");
return FALSE;
}
channelId = WTSChannelGetIdByHandle(audin->audin_channel);
IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
if (!status)
{
WLog_Print(audin->log, WLOG_ERROR, "context->ChannelIdAssigned failed!");
return FALSE;
}
if (!(audin->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
{
WLog_Print(audin->log, WLOG_ERROR, "CreateEvent failed!");
return FALSE;
}
if (!(audin->thread =
CreateThread(nullptr, 0, audin_server_thread_func, (void*)audin, 0, nullptr)))
{
WLog_Print(audin->log, WLOG_ERROR, "CreateThread failed!");
(void)CloseHandle(audin->stopEvent);
audin->stopEvent = nullptr;
return FALSE;
}
return TRUE;
}
WLog_Print(audin->log, WLOG_ERROR, "thread already running!");
return FALSE;
}
static BOOL audin_server_is_open(audin_server_context* context)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
return audin->thread != nullptr;
}
static BOOL audin_server_close(audin_server_context* context)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
if (audin->thread)
{
(void)SetEvent(audin->stopEvent);
if (WaitForSingleObject(audin->thread, INFINITE) == WAIT_FAILED)
{
WLog_Print(audin->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "",
GetLastError());
return FALSE;
}
(void)CloseHandle(audin->thread);
(void)CloseHandle(audin->stopEvent);
audin->thread = nullptr;
audin->stopEvent = nullptr;
}
if (audin->audin_channel)
{
(void)WTSVirtualChannelClose(audin->audin_channel);
audin->audin_channel = nullptr;
}
audin->audin_negotiated_format = nullptr;
return TRUE;
}
static wStream* audin_server_packet_new(wLog* log, size_t size, BYTE MessageId)
{
WINPR_ASSERT(log);
/* Allocate what we need plus header bytes */
wStream* s = Stream_New(nullptr, size + SNDIN_HEADER_SIZE);
if (!s)
{
WLog_Print(log, WLOG_ERROR, "Stream_New failed!");
return nullptr;
}
Stream_Write_UINT8(s, MessageId);
return s;
}
static UINT audin_server_packet_send(audin_server_context* context, wStream* s)
{
audin_server* audin = (audin_server*)context;
UINT error = CHANNEL_RC_OK;
ULONG written = 0;
WINPR_ASSERT(context);
WINPR_ASSERT(s);
const size_t pos = Stream_GetPosition(s);
WINPR_ASSERT(pos <= UINT32_MAX);
if (!WTSVirtualChannelWrite(audin->audin_channel, Stream_BufferAs(s, char), (UINT32)pos,
&written))
{
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelWrite failed!");
error = ERROR_INTERNAL_ERROR;
goto out;
}
if (written < Stream_GetPosition(s))
{
WLog_Print(audin->log, WLOG_WARN, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "",
written, Stream_GetPosition(s));
}
out:
Stream_Free(s, TRUE);
return error;
}
static UINT audin_server_send_version(audin_server_context* context, const SNDIN_VERSION* version)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(context);
WINPR_ASSERT(version);
wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_VERSION);
if (!s)
return ERROR_NOT_ENOUGH_MEMORY;
Stream_Write_UINT32(s, version->Version);
return audin_server_packet_send(context, s);
}
static UINT audin_server_send_formats(audin_server_context* context, const SNDIN_FORMATS* formats)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
WINPR_ASSERT(formats);
wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18, MSG_SNDIN_FORMATS);
if (!s)
return ERROR_NOT_ENOUGH_MEMORY;
Stream_Write_UINT32(s, formats->NumFormats);
Stream_Write_UINT32(s, formats->cbSizeFormatsPacket);
for (UINT32 i = 0; i < formats->NumFormats; ++i)
{
AUDIO_FORMAT* format = &formats->SoundFormats[i];
if (!audio_format_write(s, format))
{
WLog_Print(audin->log, WLOG_ERROR, "Failed to write audio format");
Stream_Free(s, TRUE);
return CHANNEL_RC_NO_MEMORY;
}
}
return audin_server_packet_send(context, s);
}
static UINT audin_server_send_open(audin_server_context* context, const SNDIN_OPEN* open)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
WINPR_ASSERT(open);
wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18 + 22, MSG_SNDIN_OPEN);
if (!s)
return ERROR_NOT_ENOUGH_MEMORY;
Stream_Write_UINT32(s, open->FramesPerPacket);
Stream_Write_UINT32(s, open->initialFormat);
Stream_Write_UINT16(s, open->captureFormat.wFormatTag);
Stream_Write_UINT16(s, open->captureFormat.nChannels);
Stream_Write_UINT32(s, open->captureFormat.nSamplesPerSec);
Stream_Write_UINT32(s, open->captureFormat.nAvgBytesPerSec);
Stream_Write_UINT16(s, open->captureFormat.nBlockAlign);
Stream_Write_UINT16(s, open->captureFormat.wBitsPerSample);
if (open->ExtraFormatData)
{
Stream_Write_UINT16(s, 22); /* cbSize */
Stream_Write_UINT16(s, open->ExtraFormatData->Samples.wReserved);
Stream_Write_UINT32(s, open->ExtraFormatData->dwChannelMask);
Stream_Write_UINT32(s, open->ExtraFormatData->SubFormat.Data1);
Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data2);
Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data3);
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[0]);
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[1]);
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[2]);
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[3]);
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[4]);
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[5]);
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[6]);
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[7]);
}
else
{
WINPR_ASSERT(open->captureFormat.wFormatTag != WAVE_FORMAT_EXTENSIBLE);
Stream_Write_UINT16(s, 0); /* cbSize */
}
return audin_server_packet_send(context, s);
}
static UINT audin_server_send_format_change(audin_server_context* context,
const SNDIN_FORMATCHANGE* format_change)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(context);
WINPR_ASSERT(format_change);
wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_FORMATCHANGE);
if (!s)
return ERROR_NOT_ENOUGH_MEMORY;
Stream_Write_UINT32(s, format_change->NewFormat);
return audin_server_packet_send(context, s);
}
static UINT audin_server_receive_version_default(audin_server_context* audin_ctx,
const SNDIN_VERSION* version)
{
audin_server* audin = (audin_server*)audin_ctx;
SNDIN_FORMATS formats = WINPR_C_ARRAY_INIT;
WINPR_ASSERT(audin);
WINPR_ASSERT(version);
if (version->Version == 0)
{
WLog_Print(audin->log, WLOG_ERROR, "Received invalid AUDIO_INPUT version from client");
return ERROR_INVALID_DATA;
}
WLog_Print(audin->log, WLOG_DEBUG, "AUDIO_INPUT version of client: %u", version->Version);
formats.NumFormats = audin->audin_n_server_formats;
formats.SoundFormats = audin->audin_server_formats;
return audin->context.SendFormats(&audin->context, &formats);
}
static UINT send_open(audin_server* audin)
{
SNDIN_OPEN open = WINPR_C_ARRAY_INIT;
WINPR_ASSERT(audin);
open.FramesPerPacket = 441;
open.initialFormat = audin->audin_client_format_idx;
open.captureFormat.wFormatTag = WAVE_FORMAT_PCM;
open.captureFormat.nChannels = 2;
open.captureFormat.nSamplesPerSec = 44100;
open.captureFormat.nAvgBytesPerSec = 44100 * 2 * 2;
open.captureFormat.nBlockAlign = 4;
open.captureFormat.wBitsPerSample = 16;
WINPR_ASSERT(audin->context.SendOpen);
return audin->context.SendOpen(&audin->context, &open);
}
static UINT audin_server_receive_formats_default(audin_server_context* context,
const SNDIN_FORMATS* formats)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
WINPR_ASSERT(formats);
if (audin->audin_negotiated_format)
{
WLog_Print(audin->log, WLOG_ERROR,
"Received client formats, but negotiation was already done");
return ERROR_INVALID_DATA;
}
for (UINT32 i = 0; i < audin->audin_n_server_formats; ++i)
{
for (UINT32 j = 0; j < formats->NumFormats; ++j)
{
if (audio_format_compatible(&audin->audin_server_formats[i], &formats->SoundFormats[j]))
{
audin->audin_negotiated_format = &audin->audin_server_formats[i];
audin->audin_client_format_idx = i;
return send_open(audin);
}
}
}
WLog_Print(audin->log, WLOG_ERROR, "Could not agree on a audio format with the server");
return ERROR_INVALID_DATA;
}
static UINT audin_server_receive_format_change_default(audin_server_context* context,
const SNDIN_FORMATCHANGE* format_change)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
WINPR_ASSERT(format_change);
if (format_change->NewFormat != audin->audin_client_format_idx)
{
WLog_Print(audin->log, WLOG_ERROR,
"NewFormat in FormatChange differs from requested format");
return ERROR_INVALID_DATA;
}
WLog_Print(audin->log, WLOG_DEBUG, "Received Format Change PDU: %u", format_change->NewFormat);
return CHANNEL_RC_OK;
}
static UINT
audin_server_incoming_data_default(audin_server_context* context,
WINPR_ATTR_UNUSED const SNDIN_DATA_INCOMING* data_incoming)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
WINPR_ASSERT(data_incoming);
/* TODO: Implement bandwidth measure of clients uplink */
WLog_Print(audin->log, WLOG_DEBUG, "Received Incoming Data PDU");
return CHANNEL_RC_OK;
}
static UINT audin_server_open_reply_default(audin_server_context* context,
const SNDIN_OPEN_REPLY* open_reply)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
WINPR_ASSERT(open_reply);
/* TODO: Implement failure handling */
WLog_Print(audin->log, WLOG_DEBUG, "Open Reply PDU: Result: %" PRIu32, open_reply->Result);
return CHANNEL_RC_OK;
}
audin_server_context* audin_server_context_new(HANDLE vcm)
{
audin_server* audin = (audin_server*)calloc(1, sizeof(audin_server));
if (!audin)
{
WLog_ERR(AUDIN_TAG, "calloc failed!");
return nullptr;
}
audin->log = WLog_Get(AUDIN_TAG);
audin->context.vcm = vcm;
audin->context.Open = audin_server_open;
audin->context.IsOpen = audin_server_is_open;
audin->context.Close = audin_server_close;
audin->context.SendVersion = audin_server_send_version;
audin->context.SendFormats = audin_server_send_formats;
audin->context.SendOpen = audin_server_send_open;
audin->context.SendFormatChange = audin_server_send_format_change;
/* Default values */
audin->context.serverVersion = SNDIN_VERSION_Version_2;
audin->context.ReceiveVersion = audin_server_receive_version_default;
audin->context.ReceiveFormats = audin_server_receive_formats_default;
audin->context.ReceiveFormatChange = audin_server_receive_format_change_default;
audin->context.IncomingData = audin_server_incoming_data_default;
audin->context.OpenReply = audin_server_open_reply_default;
return &audin->context;
}
void audin_server_context_free(audin_server_context* context)
{
audin_server* audin = (audin_server*)context;
if (!audin)
return;
audin_server_close(context);
audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats);
audin->audin_server_formats = nullptr;
free(audin);
}
BOOL audin_server_set_formats(audin_server_context* context, SSIZE_T count,
const AUDIO_FORMAT* formats)
{
audin_server* audin = (audin_server*)context;
WINPR_ASSERT(audin);
audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats);
audin->audin_n_server_formats = 0;
audin->audin_server_formats = nullptr;
audin->audin_negotiated_format = nullptr;
if (count < 0)
{
const size_t audin_n_server_formats =
server_audin_get_formats(&audin->audin_server_formats);
WINPR_ASSERT(audin_n_server_formats <= UINT32_MAX);
audin->audin_n_server_formats = (UINT32)audin_n_server_formats;
}
else
{
const size_t scount = (size_t)count;
AUDIO_FORMAT* audin_server_formats = audio_formats_new(scount);
if (!audin_server_formats)
return count == 0;
for (SSIZE_T x = 0; x < count; x++)
{
if (!audio_format_copy(&formats[x], &audin_server_formats[x]))
{
audio_formats_free(audin_server_formats, scount);
return FALSE;
}
}
WINPR_ASSERT(count <= UINT32_MAX);
audin->audin_server_formats = audin_server_formats;
audin->audin_n_server_formats = (UINT32)count;
}
return audin->audin_n_server_formats > 0;
}
const AUDIO_FORMAT* audin_server_get_negotiated_format(const audin_server_context* context)
{
const audin_server* audin = (const audin_server*)context;
WINPR_ASSERT(audin);
return audin->audin_negotiated_format;
}