Milestone 5: deliver embedded RDP sessions and lifecycle hardening
This commit is contained in:
29
third_party/FreeRDP/channels/rdpsnd/CMakeLists.txt
vendored
Normal file
29
third_party/FreeRDP/channels/rdpsnd/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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("rdpsnd")
|
||||
|
||||
include_directories(common)
|
||||
add_subdirectory(common)
|
||||
|
||||
if(WITH_CLIENT_CHANNELS)
|
||||
add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
|
||||
endif()
|
||||
|
||||
if(WITH_SERVER_CHANNELS)
|
||||
add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
|
||||
endif()
|
||||
20
third_party/FreeRDP/channels/rdpsnd/ChannelOptions.cmake
vendored
Normal file
20
third_party/FreeRDP/channels/rdpsnd/ChannelOptions.cmake
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
set(OPTION_DEFAULT ON)
|
||||
set(OPTION_CLIENT_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
|
||||
define_channel_options(
|
||||
NAME
|
||||
"rdpsnd"
|
||||
TYPE
|
||||
"static;dynamic"
|
||||
DESCRIPTION
|
||||
"Audio Output Virtual Channel Extension"
|
||||
SPECIFICATIONS
|
||||
"[MS-RDPEA]"
|
||||
DEFAULT
|
||||
${OPTION_DEFAULT}
|
||||
CLIENT_DEFAULT
|
||||
${OPTION_CLIENT_DEFAULT}
|
||||
SERVER_DEFAULT
|
||||
${OPTION_SERVER_DEFAULT}
|
||||
)
|
||||
58
third_party/FreeRDP/channels/rdpsnd/client/CMakeLists.txt
vendored
Normal file
58
third_party/FreeRDP/channels/rdpsnd/client/CMakeLists.txt
vendored
Normal 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("rdpsnd")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_main.c rdpsnd_main.h)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS winpr freerdp ${CMAKE_THREAD_LIBS_INIT} rdpsnd-common)
|
||||
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx;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_IOSAUDIO)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ios" "")
|
||||
endif()
|
||||
|
||||
if(WITH_PULSE)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "")
|
||||
endif()
|
||||
|
||||
if(WITH_MACAUDIO)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "")
|
||||
endif()
|
||||
|
||||
if(WITH_WINMM)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "")
|
||||
endif()
|
||||
|
||||
if(WITH_OPENSLES)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "")
|
||||
endif()
|
||||
|
||||
if(WITH_SNDIO)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "sndio" "")
|
||||
endif()
|
||||
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "fake" "")
|
||||
30
third_party/FreeRDP/channels/rdpsnd/client/alsa/CMakeLists.txt
vendored
Normal file
30
third_party/FreeRDP/channels/rdpsnd/client/alsa/CMakeLists.txt
vendored
Normal 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("rdpsnd" "alsa" "")
|
||||
|
||||
find_package(ALSA REQUIRED)
|
||||
freerdp_client_pc_add_requires_private("alsa")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_alsa.c)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS winpr freerdp ${ALSA_LIBRARIES})
|
||||
|
||||
include_directories(..)
|
||||
include_directories(SYSTEM ${ALSA_INCLUDE_DIRS})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
571
third_party/FreeRDP/channels/rdpsnd/client/alsa/rdpsnd_alsa.c
vendored
Normal file
571
third_party/FreeRDP/channels/rdpsnd/client/alsa/rdpsnd_alsa.c
vendored
Normal file
@@ -0,0 +1,571 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* Copyright 2009-2011 Jay Sorg
|
||||
* 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/sysinfo.h>
|
||||
#include <winpr/collections.h>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <freerdp/types.h>
|
||||
#include <freerdp/codec/dsp.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
|
||||
UINT32 latency;
|
||||
AUDIO_FORMAT aformat;
|
||||
char* device_name;
|
||||
snd_pcm_t* pcm_handle;
|
||||
snd_mixer_t* mixer_handle;
|
||||
|
||||
UINT32 actual_rate;
|
||||
snd_pcm_format_t format;
|
||||
UINT32 actual_channels;
|
||||
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
snd_pcm_uframes_t period_size;
|
||||
} rdpsndAlsaPlugin;
|
||||
|
||||
#define SND_PCM_CHECK(_func, _status) \
|
||||
do \
|
||||
{ \
|
||||
if ((_status) < 0) \
|
||||
{ \
|
||||
WLog_ERR(TAG, "%s: %d\n", (_func), (_status)); \
|
||||
return -1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int rdpsnd_alsa_set_hw_params(rdpsndAlsaPlugin* alsa)
|
||||
{
|
||||
int status = 0;
|
||||
snd_pcm_hw_params_t* hw_params = nullptr;
|
||||
snd_pcm_uframes_t buffer_size_max = 0;
|
||||
status = snd_pcm_hw_params_malloc(&hw_params);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params_malloc", status);
|
||||
status = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params_any", status);
|
||||
/* Set interleaved read/write access */
|
||||
status =
|
||||
snd_pcm_hw_params_set_access(alsa->pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params_set_access", status);
|
||||
/* Set sample format */
|
||||
status = snd_pcm_hw_params_set_format(alsa->pcm_handle, hw_params, alsa->format);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params_set_format", status);
|
||||
/* Set sample rate */
|
||||
status =
|
||||
snd_pcm_hw_params_set_rate_near(alsa->pcm_handle, hw_params, &alsa->actual_rate, nullptr);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params_set_rate_near", status);
|
||||
/* Set number of channels */
|
||||
status = snd_pcm_hw_params_set_channels(alsa->pcm_handle, hw_params, alsa->actual_channels);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params_set_channels", status);
|
||||
/* Get maximum buffer size */
|
||||
status = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params_get_buffer_size_max", status);
|
||||
/**
|
||||
* ALSA Parameters
|
||||
*
|
||||
* http://www.alsa-project.org/main/index.php/FramesPeriods
|
||||
*
|
||||
* buffer_size = period_size * periods
|
||||
* period_bytes = period_size * bytes_per_frame
|
||||
* bytes_per_frame = channels * bytes_per_sample
|
||||
*
|
||||
* A frame is equivalent of one sample being played,
|
||||
* irrespective of the number of channels or the number of bits
|
||||
*
|
||||
* A period is the number of frames in between each hardware interrupt.
|
||||
*
|
||||
* The buffer size always has to be greater than one period size.
|
||||
* Commonly this is (2 * period_size), but some hardware can do 8 periods per buffer.
|
||||
* It is also possible for the buffer size to not be an integer multiple of the period size.
|
||||
*/
|
||||
const size_t interrupts_per_sec_near = 50;
|
||||
const size_t bytes_per_sec =
|
||||
(1ull * alsa->actual_rate * alsa->aformat.wBitsPerSample / 8 * alsa->actual_channels);
|
||||
alsa->buffer_size = buffer_size_max;
|
||||
alsa->period_size = (bytes_per_sec / interrupts_per_sec_near);
|
||||
|
||||
if (alsa->period_size > buffer_size_max)
|
||||
{
|
||||
WLog_ERR(TAG, "Warning: requested sound buffer size %lu, got %lu instead\n",
|
||||
alsa->buffer_size, buffer_size_max);
|
||||
alsa->period_size = (buffer_size_max / 8);
|
||||
}
|
||||
|
||||
/* Set buffer size */
|
||||
status =
|
||||
snd_pcm_hw_params_set_buffer_size_near(alsa->pcm_handle, hw_params, &alsa->buffer_size);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params_set_buffer_size_near", status);
|
||||
/* Set period size */
|
||||
status = snd_pcm_hw_params_set_period_size_near(alsa->pcm_handle, hw_params, &alsa->period_size,
|
||||
nullptr);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params_set_period_size_near", status);
|
||||
status = snd_pcm_hw_params(alsa->pcm_handle, hw_params);
|
||||
SND_PCM_CHECK("snd_pcm_hw_params", status);
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rdpsnd_alsa_set_sw_params(rdpsndAlsaPlugin* alsa)
|
||||
{
|
||||
int status = 0;
|
||||
snd_pcm_sw_params_t* sw_params = nullptr;
|
||||
status = snd_pcm_sw_params_malloc(&sw_params);
|
||||
SND_PCM_CHECK("snd_pcm_sw_params_malloc", status);
|
||||
status = snd_pcm_sw_params_current(alsa->pcm_handle, sw_params);
|
||||
SND_PCM_CHECK("snd_pcm_sw_params_current", status);
|
||||
status = snd_pcm_sw_params_set_avail_min(
|
||||
alsa->pcm_handle, sw_params, (1ULL * alsa->aformat.nChannels * alsa->actual_channels));
|
||||
SND_PCM_CHECK("snd_pcm_sw_params_set_avail_min", status);
|
||||
status = snd_pcm_sw_params_set_start_threshold(alsa->pcm_handle, sw_params,
|
||||
alsa->aformat.nBlockAlign);
|
||||
SND_PCM_CHECK("snd_pcm_sw_params_set_start_threshold", status);
|
||||
status = snd_pcm_sw_params(alsa->pcm_handle, sw_params);
|
||||
SND_PCM_CHECK("snd_pcm_sw_params", status);
|
||||
snd_pcm_sw_params_free(sw_params);
|
||||
status = snd_pcm_prepare(alsa->pcm_handle);
|
||||
SND_PCM_CHECK("snd_pcm_prepare", status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rdpsnd_alsa_validate_params(rdpsndAlsaPlugin* alsa)
|
||||
{
|
||||
int status = 0;
|
||||
snd_pcm_uframes_t buffer_size = 0;
|
||||
snd_pcm_uframes_t period_size = 0;
|
||||
status = snd_pcm_get_params(alsa->pcm_handle, &buffer_size, &period_size);
|
||||
SND_PCM_CHECK("snd_pcm_get_params", status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rdpsnd_alsa_set_params(rdpsndAlsaPlugin* alsa)
|
||||
{
|
||||
snd_pcm_drop(alsa->pcm_handle);
|
||||
|
||||
if (rdpsnd_alsa_set_hw_params(alsa) < 0)
|
||||
return -1;
|
||||
|
||||
if (rdpsnd_alsa_set_sw_params(alsa) < 0)
|
||||
return -1;
|
||||
|
||||
return rdpsnd_alsa_validate_params(alsa);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_alsa_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
|
||||
UINT32 latency)
|
||||
{
|
||||
rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
|
||||
|
||||
if (format)
|
||||
{
|
||||
alsa->aformat = *format;
|
||||
alsa->actual_rate = format->nSamplesPerSec;
|
||||
alsa->actual_channels = format->nChannels;
|
||||
|
||||
switch (format->wFormatTag)
|
||||
{
|
||||
case WAVE_FORMAT_PCM:
|
||||
switch (format->wBitsPerSample)
|
||||
{
|
||||
case 8:
|
||||
alsa->format = SND_PCM_FORMAT_S8;
|
||||
break;
|
||||
|
||||
case 16:
|
||||
alsa->format = SND_PCM_FORMAT_S16_LE;
|
||||
break;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
alsa->latency = latency;
|
||||
return (rdpsnd_alsa_set_params(alsa) == 0);
|
||||
}
|
||||
|
||||
static void rdpsnd_alsa_close_mixer(rdpsndAlsaPlugin* alsa)
|
||||
{
|
||||
if (alsa && alsa->mixer_handle)
|
||||
{
|
||||
snd_mixer_close(alsa->mixer_handle);
|
||||
alsa->mixer_handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_alsa_open_mixer(rdpsndAlsaPlugin* alsa)
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
if (alsa->mixer_handle)
|
||||
return TRUE;
|
||||
|
||||
status = snd_mixer_open(&alsa->mixer_handle, 0);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "snd_mixer_open failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
status = snd_mixer_attach(alsa->mixer_handle, alsa->device_name);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "snd_mixer_attach failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
status = snd_mixer_selem_register(alsa->mixer_handle, nullptr, nullptr);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "snd_mixer_selem_register failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
status = snd_mixer_load(alsa->mixer_handle);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "snd_mixer_load failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
fail:
|
||||
rdpsnd_alsa_close_mixer(alsa);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void rdpsnd_alsa_pcm_close(rdpsndAlsaPlugin* alsa)
|
||||
{
|
||||
if (alsa && alsa->pcm_handle)
|
||||
{
|
||||
snd_pcm_drain(alsa->pcm_handle);
|
||||
snd_pcm_close(alsa->pcm_handle);
|
||||
alsa->pcm_handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_alsa_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
|
||||
{
|
||||
int mode = 0;
|
||||
int status = 0;
|
||||
rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
|
||||
|
||||
if (alsa->pcm_handle)
|
||||
return TRUE;
|
||||
|
||||
mode = 0;
|
||||
/*mode |= SND_PCM_NONBLOCK;*/
|
||||
status = snd_pcm_open(&alsa->pcm_handle, alsa->device_name, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "snd_pcm_open failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return rdpsnd_alsa_set_format(device, format, latency) && rdpsnd_alsa_open_mixer(alsa);
|
||||
}
|
||||
|
||||
static void rdpsnd_alsa_close(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
|
||||
|
||||
if (!alsa)
|
||||
return;
|
||||
|
||||
rdpsnd_alsa_close_mixer(alsa);
|
||||
}
|
||||
|
||||
static void rdpsnd_alsa_free(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
|
||||
rdpsnd_alsa_pcm_close(alsa);
|
||||
rdpsnd_alsa_close_mixer(alsa);
|
||||
free(alsa->device_name);
|
||||
free(alsa);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_alsa_format_supported(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device,
|
||||
const AUDIO_FORMAT* format)
|
||||
{
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static UINT32 rdpsnd_alsa_get_volume(rdpsndDevicePlugin* device)
|
||||
{
|
||||
long volume_min = 0;
|
||||
long volume_max = 0;
|
||||
long volume_left = 0;
|
||||
long volume_right = 0;
|
||||
|
||||
rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
|
||||
UINT32 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
|
||||
UINT32 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
|
||||
|
||||
if (!rdpsnd_alsa_open_mixer(alsa))
|
||||
return 0;
|
||||
|
||||
for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem;
|
||||
elem = snd_mixer_elem_next(elem))
|
||||
{
|
||||
if (snd_mixer_selem_has_playback_volume(elem))
|
||||
{
|
||||
snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max);
|
||||
snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &volume_left);
|
||||
snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &volume_right);
|
||||
dwVolumeLeft =
|
||||
(UINT16)(((volume_left * 0xFFFF) - volume_min) / (volume_max - volume_min));
|
||||
dwVolumeRight =
|
||||
(UINT16)(((volume_right * 0xFFFF) - volume_min) / (volume_max - volume_min));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (dwVolumeLeft << 16) | dwVolumeRight;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_alsa_set_volume(rdpsndDevicePlugin* device, UINT32 value)
|
||||
{
|
||||
long left = 0;
|
||||
long right = 0;
|
||||
long volume_min = 0;
|
||||
long volume_max = 0;
|
||||
long volume_left = 0;
|
||||
long volume_right = 0;
|
||||
rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
|
||||
|
||||
if (!rdpsnd_alsa_open_mixer(alsa))
|
||||
return FALSE;
|
||||
|
||||
left = (value & 0xFFFF);
|
||||
right = ((value >> 16) & 0xFFFF);
|
||||
|
||||
for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem;
|
||||
elem = snd_mixer_elem_next(elem))
|
||||
{
|
||||
if (snd_mixer_selem_has_playback_volume(elem))
|
||||
{
|
||||
snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max);
|
||||
volume_left = volume_min + (left * (volume_max - volume_min)) / 0xFFFF;
|
||||
volume_right = volume_min + (right * (volume_max - volume_min)) / 0xFFFF;
|
||||
|
||||
if ((snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, volume_left) <
|
||||
0) ||
|
||||
(snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT,
|
||||
volume_right) < 0))
|
||||
{
|
||||
WLog_ERR(TAG, "error setting the volume\n");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static UINT rdpsnd_alsa_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
|
||||
{
|
||||
UINT latency = 0;
|
||||
size_t offset = 0;
|
||||
rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
|
||||
WINPR_ASSERT(alsa);
|
||||
WINPR_ASSERT(data || (size == 0));
|
||||
const size_t frame_size = 1ull * alsa->actual_channels * alsa->aformat.wBitsPerSample / 8;
|
||||
if (frame_size == 0)
|
||||
return 0;
|
||||
|
||||
while (offset < size)
|
||||
{
|
||||
snd_pcm_sframes_t status =
|
||||
snd_pcm_writei(alsa->pcm_handle, &data[offset], (size - offset) / frame_size);
|
||||
|
||||
if (status < 0)
|
||||
status = snd_pcm_recover(alsa->pcm_handle, (int)status, 0);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "status: %ld\n", status);
|
||||
rdpsnd_alsa_close(device);
|
||||
rdpsnd_alsa_open(device, nullptr, alsa->latency);
|
||||
break;
|
||||
}
|
||||
|
||||
offset += WINPR_ASSERTING_INT_CAST(size_t, status) * frame_size;
|
||||
}
|
||||
|
||||
{
|
||||
snd_pcm_sframes_t available = 0;
|
||||
snd_pcm_sframes_t delay = 0;
|
||||
int rc = snd_pcm_avail_delay(alsa->pcm_handle, &available, &delay);
|
||||
|
||||
if ((rc == 0) && (available == 0)) /* Get [ms] from number of samples */
|
||||
latency = (UINT32)MIN(UINT32_MAX, delay * 1000 / alsa->actual_rate);
|
||||
}
|
||||
|
||||
return latency + alsa->latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT rdpsnd_alsa_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
|
||||
{
|
||||
int status = 0;
|
||||
DWORD flags = 0;
|
||||
const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
|
||||
rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device;
|
||||
COMMAND_LINE_ARGUMENT_A rdpsnd_alsa_args[] = {
|
||||
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr, "device" },
|
||||
{ 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, rdpsnd_alsa_args, flags, alsa,
|
||||
nullptr, nullptr);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "CommandLineParseArgumentsA failed!");
|
||||
return CHANNEL_RC_INITIALIZATION_ERROR;
|
||||
}
|
||||
|
||||
arg = rdpsnd_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)
|
||||
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_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
const ADDIN_ARGV* args = nullptr;
|
||||
rdpsndAlsaPlugin* alsa = nullptr;
|
||||
UINT error = 0;
|
||||
alsa = (rdpsndAlsaPlugin*)calloc(1, sizeof(rdpsndAlsaPlugin));
|
||||
|
||||
if (!alsa)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
alsa->device.Open = rdpsnd_alsa_open;
|
||||
alsa->device.FormatSupported = rdpsnd_alsa_format_supported;
|
||||
alsa->device.GetVolume = rdpsnd_alsa_get_volume;
|
||||
alsa->device.SetVolume = rdpsnd_alsa_set_volume;
|
||||
alsa->device.Play = rdpsnd_alsa_play;
|
||||
alsa->device.Close = rdpsnd_alsa_close;
|
||||
alsa->device.Free = rdpsnd_alsa_free;
|
||||
args = pEntryPoints->args;
|
||||
|
||||
if (args->argc > 1)
|
||||
{
|
||||
if ((error = rdpsnd_alsa_parse_addin_args(&alsa->device, args)))
|
||||
{
|
||||
WLog_ERR(TAG, "rdpsnd_alsa_parse_addin_args failed with error %" PRIu32 "", error);
|
||||
goto error_parse_args;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alsa->device_name)
|
||||
{
|
||||
alsa->device_name = _strdup("default");
|
||||
|
||||
if (!alsa->device_name)
|
||||
{
|
||||
WLog_ERR(TAG, "_strdup failed!");
|
||||
error = CHANNEL_RC_NO_MEMORY;
|
||||
goto error_strdup;
|
||||
}
|
||||
}
|
||||
|
||||
alsa->pcm_handle = nullptr;
|
||||
alsa->actual_rate = 22050;
|
||||
alsa->format = SND_PCM_FORMAT_S16_LE;
|
||||
alsa->actual_channels = 2;
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)alsa);
|
||||
return CHANNEL_RC_OK;
|
||||
error_strdup:
|
||||
free(alsa->device_name);
|
||||
error_parse_args:
|
||||
free(alsa);
|
||||
return error;
|
||||
}
|
||||
27
third_party/FreeRDP/channels/rdpsnd/client/fake/CMakeLists.txt
vendored
Normal file
27
third_party/FreeRDP/channels/rdpsnd/client/fake/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||
# FreeRDP cmake build script
|
||||
#
|
||||
# Copyright 2019 Armin Novak <armin.novak@thincast.com>
|
||||
# Copyright 2019 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("rdpsnd" "fake" "")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_fake.c)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS winpr freerdp)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
153
third_party/FreeRDP/channels/rdpsnd/client/fake/rdpsnd_fake.c
vendored
Normal file
153
third_party/FreeRDP/channels/rdpsnd/client/fake/rdpsnd_fake.c
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* Copyright 2019 Armin Novak <armin.novak@thincast.com>
|
||||
* Copyright 2019 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/stream.h>
|
||||
#include <winpr/cmdline.h>
|
||||
|
||||
#include <freerdp/types.h>
|
||||
#include <freerdp/settings.h>
|
||||
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
} rdpsndFakePlugin;
|
||||
|
||||
static BOOL rdpsnd_fake_open(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device,
|
||||
WINPR_ATTR_UNUSED const AUDIO_FORMAT* format,
|
||||
WINPR_ATTR_UNUSED UINT32 latency)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void rdpsnd_fake_close(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device)
|
||||
{
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_fake_set_volume(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device,
|
||||
WINPR_ATTR_UNUSED UINT32 value)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void rdpsnd_fake_free(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndFakePlugin* fake = (rdpsndFakePlugin*)device;
|
||||
|
||||
if (!fake)
|
||||
return;
|
||||
|
||||
free(fake);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_fake_format_supported(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device,
|
||||
WINPR_ATTR_UNUSED const AUDIO_FORMAT* format)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static UINT rdpsnd_fake_play(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device,
|
||||
WINPR_ATTR_UNUSED const BYTE* data, WINPR_ATTR_UNUSED size_t size)
|
||||
{
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT rdpsnd_fake_parse_addin_args(rdpsndFakePlugin* fake, const ADDIN_ARGV* args)
|
||||
{
|
||||
int status = 0;
|
||||
DWORD flags = 0;
|
||||
const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
|
||||
COMMAND_LINE_ARGUMENT_A rdpsnd_fake_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, args->argv, rdpsnd_fake_args, flags, fake,
|
||||
nullptr, nullptr);
|
||||
|
||||
if (status < 0)
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
arg = rdpsnd_fake_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 fake_freerdp_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
const ADDIN_ARGV* args = nullptr;
|
||||
rdpsndFakePlugin* fake = nullptr;
|
||||
UINT ret = CHANNEL_RC_OK;
|
||||
fake = (rdpsndFakePlugin*)calloc(1, sizeof(rdpsndFakePlugin));
|
||||
|
||||
if (!fake)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
fake->device.Open = rdpsnd_fake_open;
|
||||
fake->device.FormatSupported = rdpsnd_fake_format_supported;
|
||||
fake->device.SetVolume = rdpsnd_fake_set_volume;
|
||||
fake->device.Play = rdpsnd_fake_play;
|
||||
fake->device.Close = rdpsnd_fake_close;
|
||||
fake->device.Free = rdpsnd_fake_free;
|
||||
args = pEntryPoints->args;
|
||||
|
||||
if (args->argc > 1)
|
||||
{
|
||||
ret = rdpsnd_fake_parse_addin_args(fake, args);
|
||||
|
||||
if (ret != CHANNEL_RC_OK)
|
||||
{
|
||||
WLog_ERR(TAG, "error parsing arguments");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &fake->device);
|
||||
return ret;
|
||||
error:
|
||||
rdpsnd_fake_free(&fake->device);
|
||||
return ret;
|
||||
}
|
||||
32
third_party/FreeRDP/channels/rdpsnd/client/ios/CMakeLists.txt
vendored
Normal file
32
third_party/FreeRDP/channels/rdpsnd/client/ios/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||
# FreeRDP cmake build script
|
||||
#
|
||||
# Copyright 2012 Laxmikant Rashinkar <LK.Rashinkar@gmail.com>
|
||||
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
# Copyright 2013 Corey Clayton <can.of.tuna@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("rdpsnd" "ios" "")
|
||||
|
||||
find_library(CORE_AUDIO CoreAudio)
|
||||
find_library(AUDIO_TOOL AudioToolbox)
|
||||
find_library(CORE_FOUNDATION CoreFoundation)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_ios.c TPCircularBuffer.c)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS winpr freerdp ${AUDIO_TOOL} ${CORE_AUDIO} ${CORE_FOUNDATION})
|
||||
|
||||
include_directories(..)
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
153
third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.c
vendored
Normal file
153
third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.c
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
//
|
||||
// TPCircularBuffer.c
|
||||
// Circular/Ring buffer implementation
|
||||
//
|
||||
// https://github.com/michaeltyson/TPCircularBuffer
|
||||
//
|
||||
// Created by Michael Tyson on 10/12/2011.
|
||||
//
|
||||
// Copyright (C) 2012-2013 A Tasty Pixel
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#include <winpr/wlog.h>
|
||||
|
||||
#include "TPCircularBuffer.h"
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
#include <mach/mach.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define reportResult(result, operation) (_reportResult((result), (operation), __FILE__, __LINE__))
|
||||
static inline bool _reportResult(kern_return_t result, const char* operation, const char* file,
|
||||
int line)
|
||||
{
|
||||
if (result != ERR_SUCCESS)
|
||||
{
|
||||
WLog_DBG(TAG, "%s:%d: %s: %s\n", file, line, operation, mach_error_string(result));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TPCircularBufferInit(TPCircularBuffer* buffer, int length)
|
||||
{
|
||||
|
||||
// Keep trying until we get our buffer, needed to handle race conditions
|
||||
int retries = 3;
|
||||
while (true)
|
||||
{
|
||||
|
||||
buffer->length = round_page(length); // We need whole page sizes
|
||||
|
||||
// Temporarily allocate twice the length, so we have the contiguous address space to
|
||||
// support a second instance of the buffer directly after
|
||||
vm_address_t bufferAddress;
|
||||
kern_return_t result = vm_allocate(mach_task_self(), &bufferAddress, buffer->length * 2,
|
||||
VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit
|
||||
if (result != ERR_SUCCESS)
|
||||
{
|
||||
if (retries-- == 0)
|
||||
{
|
||||
reportResult(result, "Buffer allocation");
|
||||
return false;
|
||||
}
|
||||
// Try again if we fail
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now replace the second half of the allocation with a virtual copy of the first half.
|
||||
// Deallocate the second half...
|
||||
result = vm_deallocate(mach_task_self(), bufferAddress + buffer->length, buffer->length);
|
||||
if (result != ERR_SUCCESS)
|
||||
{
|
||||
if (retries-- == 0)
|
||||
{
|
||||
reportResult(result, "Buffer deallocation");
|
||||
return false;
|
||||
}
|
||||
// If this fails somehow, deallocate the whole region and try again
|
||||
vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Re-map the buffer to the address space immediately after the buffer
|
||||
vm_address_t virtualAddress = bufferAddress + buffer->length;
|
||||
vm_prot_t cur_prot, max_prot;
|
||||
result = vm_remap(mach_task_self(),
|
||||
&virtualAddress, // mirror target
|
||||
buffer->length, // size of mirror
|
||||
0, // auto alignment
|
||||
0, // force remapping to virtualAddress
|
||||
mach_task_self(), // same task
|
||||
bufferAddress, // mirror source
|
||||
0, // MAP READ-WRITE, NOT COPY
|
||||
&cur_prot, // unused protection struct
|
||||
&max_prot, // unused protection struct
|
||||
VM_INHERIT_DEFAULT);
|
||||
if (result != ERR_SUCCESS)
|
||||
{
|
||||
if (retries-- == 0)
|
||||
{
|
||||
reportResult(result, "Remap buffer memory");
|
||||
return false;
|
||||
}
|
||||
// If this remap failed, we hit a race condition, so deallocate and try again
|
||||
vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (virtualAddress != bufferAddress + buffer->length)
|
||||
{
|
||||
// If the memory is not contiguous, clean up both allocated buffers and try again
|
||||
if (retries-- == 0)
|
||||
{
|
||||
WLog_DBG(TAG, "Couldn't map buffer memory to end of buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
vm_deallocate(mach_task_self(), virtualAddress, buffer->length);
|
||||
vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer->buffer = (void*)bufferAddress;
|
||||
buffer->fillCount = 0;
|
||||
buffer->head = buffer->tail = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TPCircularBufferCleanup(TPCircularBuffer* buffer)
|
||||
{
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2);
|
||||
memset(buffer, 0, sizeof(TPCircularBuffer));
|
||||
}
|
||||
|
||||
void TPCircularBufferClear(TPCircularBuffer* buffer)
|
||||
{
|
||||
int32_t fillCount;
|
||||
if (TPCircularBufferTail(buffer, &fillCount))
|
||||
{
|
||||
TPCircularBufferConsume(buffer, fillCount);
|
||||
}
|
||||
}
|
||||
217
third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.h
vendored
Normal file
217
third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.h
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
//
|
||||
// TPCircularBuffer.h
|
||||
// Circular/Ring buffer implementation
|
||||
//
|
||||
// https://github.com/michaeltyson/TPCircularBuffer
|
||||
//
|
||||
// Created by Michael Tyson on 10/12/2011.
|
||||
//
|
||||
//
|
||||
// This implementation makes use of a virtual memory mapping technique that inserts a virtual copy
|
||||
// of the buffer memory directly after the buffer's end, negating the need for any buffer
|
||||
// wrap-around logic. Clients can simply use the returned memory address as if it were contiguous
|
||||
// space.
|
||||
//
|
||||
// The implementation is thread-safe in the case of a single producer and single consumer.
|
||||
//
|
||||
// Virtual memory technique originally proposed by Philip Howard (http://vrb.slashusr.org/), and
|
||||
// adapted to Darwin by Kurt Revis (http://www.snoize.com,
|
||||
// http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz)
|
||||
//
|
||||
//
|
||||
// Copyright (C) 2012-2013 A Tasty Pixel
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#ifndef TPCircularBuffer_h
|
||||
#define TPCircularBuffer_h
|
||||
|
||||
#include <libkern/OSAtomic.h>
|
||||
#include <string.h>
|
||||
#include <winpr/assert.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void* buffer;
|
||||
int32_t length;
|
||||
int32_t tail;
|
||||
int32_t head;
|
||||
volatile int32_t fillCount;
|
||||
} TPCircularBuffer;
|
||||
|
||||
/*!
|
||||
* Initialise buffer
|
||||
*
|
||||
* Note that the length is advisory only: Because of the way the
|
||||
* memory mirroring technique works, the true buffer length will
|
||||
* be multiples of the device page size (e.g. 4096 bytes)
|
||||
*
|
||||
* @param buffer Circular buffer
|
||||
* @param length Length of buffer
|
||||
*/
|
||||
bool TPCircularBufferInit(TPCircularBuffer* buffer, int32_t length);
|
||||
|
||||
/*!
|
||||
* Cleanup buffer
|
||||
*
|
||||
* Releases buffer resources.
|
||||
*/
|
||||
void TPCircularBufferCleanup(TPCircularBuffer* buffer);
|
||||
|
||||
/*!
|
||||
* Clear buffer
|
||||
*
|
||||
* Resets buffer to original, empty state.
|
||||
*
|
||||
* This is safe for use by consumer while producer is accessing
|
||||
* buffer.
|
||||
*/
|
||||
void TPCircularBufferClear(TPCircularBuffer* buffer);
|
||||
|
||||
// Reading (consuming)
|
||||
|
||||
/*!
|
||||
* Access end of buffer
|
||||
*
|
||||
* This gives you a pointer to the end of the buffer, ready
|
||||
* for reading, and the number of available bytes to read.
|
||||
*
|
||||
* @param buffer Circular buffer
|
||||
* @param availableBytes On output, the number of bytes ready for reading
|
||||
* @return Pointer to the first bytes ready for reading, or nullptr if buffer is empty
|
||||
*/
|
||||
static inline __attribute__((always_inline)) void*
|
||||
TPCircularBufferTail(TPCircularBuffer* buffer, int32_t* availableBytes)
|
||||
{
|
||||
*availableBytes = buffer->fillCount;
|
||||
if (*availableBytes == 0)
|
||||
return nullptr;
|
||||
return (void*)((char*)buffer->buffer + buffer->tail);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Consume bytes in buffer
|
||||
*
|
||||
* This frees up the just-read bytes, ready for writing again.
|
||||
*
|
||||
* @param buffer Circular buffer
|
||||
* @param amount Number of bytes to consume
|
||||
*/
|
||||
static inline __attribute__((always_inline)) void
|
||||
TPCircularBufferConsume(TPCircularBuffer* buffer, int32_t amount)
|
||||
{
|
||||
buffer->tail = (buffer->tail + amount) % buffer->length;
|
||||
OSAtomicAdd32Barrier(-amount, &buffer->fillCount);
|
||||
WINPR_ASSERT(buffer->fillCount >= 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Version of TPCircularBufferConsume without the memory barrier, for more optimal use in
|
||||
* single-threaded contexts
|
||||
*/
|
||||
static inline __attribute__((always_inline)) void
|
||||
TPCircularBufferConsumeNoBarrier(TPCircularBuffer* buffer, int32_t amount)
|
||||
{
|
||||
buffer->tail = (buffer->tail + amount) % buffer->length;
|
||||
buffer->fillCount -= amount;
|
||||
WINPR_ASSERT(buffer->fillCount >= 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Access front of buffer
|
||||
*
|
||||
* This gives you a pointer to the front of the buffer, ready
|
||||
* for writing, and the number of available bytes to write.
|
||||
*
|
||||
* @param buffer Circular buffer
|
||||
* @param availableBytes On output, the number of bytes ready for writing
|
||||
* @return Pointer to the first bytes ready for writing, or nullptr if buffer is full
|
||||
*/
|
||||
static inline __attribute__((always_inline)) void*
|
||||
TPCircularBufferHead(TPCircularBuffer* buffer, int32_t* availableBytes)
|
||||
{
|
||||
*availableBytes = (buffer->length - buffer->fillCount);
|
||||
if (*availableBytes == 0)
|
||||
return nullptr;
|
||||
return (void*)((char*)buffer->buffer + buffer->head);
|
||||
}
|
||||
|
||||
// Writing (producing)
|
||||
|
||||
/*!
|
||||
* Produce bytes in buffer
|
||||
*
|
||||
* This marks the given section of the buffer ready for reading.
|
||||
*
|
||||
* @param buffer Circular buffer
|
||||
* @param amount Number of bytes to produce
|
||||
*/
|
||||
static inline __attribute__((always_inline)) void
|
||||
TPCircularBufferProduce(TPCircularBuffer* buffer, int amount)
|
||||
{
|
||||
buffer->head = (buffer->head + amount) % buffer->length;
|
||||
OSAtomicAdd32Barrier(amount, &buffer->fillCount);
|
||||
WINPR_ASSERT(buffer->fillCount <= buffer->length);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Version of TPCircularBufferProduce without the memory barrier, for more optimal use in
|
||||
* single-threaded contexts
|
||||
*/
|
||||
static inline __attribute__((always_inline)) void
|
||||
TPCircularBufferProduceNoBarrier(TPCircularBuffer* buffer, int amount)
|
||||
{
|
||||
buffer->head = (buffer->head + amount) % buffer->length;
|
||||
buffer->fillCount += amount;
|
||||
WINPR_ASSERT(buffer->fillCount <= buffer->length);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Helper routine to copy bytes to buffer
|
||||
*
|
||||
* This copies the given bytes to the buffer, and marks them ready for writing.
|
||||
*
|
||||
* @param buffer Circular buffer
|
||||
* @param src Source buffer
|
||||
* @param len Number of bytes in source buffer
|
||||
* @return true if bytes copied, false if there was insufficient space
|
||||
*/
|
||||
static inline __attribute__((always_inline)) bool
|
||||
TPCircularBufferProduceBytes(TPCircularBuffer* buffer, const void* src, int32_t len)
|
||||
{
|
||||
int32_t space;
|
||||
void* ptr = TPCircularBufferHead(buffer, &space);
|
||||
if (space < len)
|
||||
return false;
|
||||
memcpy(ptr, src, len);
|
||||
TPCircularBufferProduce(buffer, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
282
third_party/FreeRDP/channels/rdpsnd/client/ios/rdpsnd_ios.c
vendored
Normal file
282
third_party/FreeRDP/channels/rdpsnd/client/ios/rdpsnd_ios.c
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* Copyright 2013 Dell Software <Mike.McDonald@software.dell.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/wtypes.h>
|
||||
|
||||
#include <freerdp/types.h>
|
||||
#include <freerdp/codec/dsp.h>
|
||||
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
#include "rdpsnd_main.h"
|
||||
#include "TPCircularBuffer.h"
|
||||
|
||||
#define INPUT_BUFFER_SIZE 32768
|
||||
#define CIRCULAR_BUFFER_SIZE (INPUT_BUFFER_SIZE * 4)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
AudioComponentInstance audio_unit;
|
||||
TPCircularBuffer buffer;
|
||||
BOOL is_opened;
|
||||
BOOL is_playing;
|
||||
} rdpsndIOSPlugin;
|
||||
|
||||
#define THIS(__ptr) ((rdpsndIOSPlugin*)__ptr)
|
||||
|
||||
static OSStatus rdpsnd_ios_render_cb(void* inRefCon,
|
||||
AudioUnitRenderActionFlags __unused* ioActionFlags,
|
||||
const AudioTimeStamp __unused* inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 __unused inNumberFrames, AudioBufferList* ioData)
|
||||
{
|
||||
if (inBusNumber != 0)
|
||||
{
|
||||
return noErr;
|
||||
}
|
||||
|
||||
rdpsndIOSPlugin* p = THIS(inRefCon);
|
||||
|
||||
for (unsigned int i = 0; i < ioData->mNumberBuffers; i++)
|
||||
{
|
||||
AudioBuffer* target_buffer = &ioData->mBuffers[i];
|
||||
int32_t available_bytes = 0;
|
||||
const void* buffer = TPCircularBufferTail(&p->buffer, &available_bytes);
|
||||
|
||||
if (buffer != nullptr && available_bytes > 0)
|
||||
{
|
||||
const int bytes_to_copy = MIN((int32_t)target_buffer->mDataByteSize, available_bytes);
|
||||
memcpy(target_buffer->mData, buffer, bytes_to_copy);
|
||||
target_buffer->mDataByteSize = bytes_to_copy;
|
||||
TPCircularBufferConsume(&p->buffer, bytes_to_copy);
|
||||
}
|
||||
else
|
||||
{
|
||||
target_buffer->mDataByteSize = 0;
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
p->is_playing = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_ios_format_supported(rdpsndDevicePlugin* __unused device,
|
||||
const AUDIO_FORMAT* format)
|
||||
{
|
||||
if (format->wFormatTag == WAVE_FORMAT_PCM)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_ios_set_volume(rdpsndDevicePlugin* __unused device, UINT32 __unused value)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void rdpsnd_ios_start(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndIOSPlugin* p = THIS(device);
|
||||
|
||||
/* If this device is not playing... */
|
||||
if (!p->is_playing)
|
||||
{
|
||||
/* Start the device. */
|
||||
int32_t available_bytes = 0;
|
||||
TPCircularBufferTail(&p->buffer, &available_bytes);
|
||||
|
||||
if (available_bytes > 0)
|
||||
{
|
||||
p->is_playing = 1;
|
||||
AudioOutputUnitStart(p->audio_unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_ios_stop(rdpsndDevicePlugin* __unused device)
|
||||
{
|
||||
rdpsndIOSPlugin* p = THIS(device);
|
||||
|
||||
/* If the device is playing... */
|
||||
if (p->is_playing)
|
||||
{
|
||||
/* Stop the device. */
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
p->is_playing = 0;
|
||||
/* Free all buffers. */
|
||||
TPCircularBufferClear(&p->buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static UINT rdpsnd_ios_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
|
||||
{
|
||||
rdpsndIOSPlugin* p = THIS(device);
|
||||
const BOOL ok = TPCircularBufferProduceBytes(&p->buffer, data, size);
|
||||
|
||||
if (!ok)
|
||||
return 0;
|
||||
|
||||
rdpsnd_ios_start(device);
|
||||
return 10; /* TODO: Get real latencry in [ms] */
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_ios_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
|
||||
UINT32 __unused latency)
|
||||
{
|
||||
rdpsndIOSPlugin* p = THIS(device);
|
||||
|
||||
if (p->is_opened)
|
||||
return TRUE;
|
||||
|
||||
/* Find the output audio unit. */
|
||||
AudioComponentDescription desc;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
AudioComponent audioComponent = AudioComponentFindNext(nullptr, &desc);
|
||||
|
||||
if (audioComponent == nullptr)
|
||||
return FALSE;
|
||||
|
||||
/* Open the audio unit. */
|
||||
OSStatus status = AudioComponentInstanceNew(audioComponent, &p->audio_unit);
|
||||
|
||||
if (status != 0)
|
||||
return FALSE;
|
||||
|
||||
/* Set the format for the AudioUnit. */
|
||||
AudioStreamBasicDescription audioFormat = WINPR_C_ARRAY_INIT;
|
||||
audioFormat.mSampleRate = format->nSamplesPerSec;
|
||||
audioFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
|
||||
audioFormat.mFramesPerPacket = 1; /* imminent property of the Linear PCM */
|
||||
audioFormat.mChannelsPerFrame = format->nChannels;
|
||||
audioFormat.mBitsPerChannel = format->wBitsPerSample;
|
||||
audioFormat.mBytesPerFrame = (format->wBitsPerSample * format->nChannels) / 8;
|
||||
audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
|
||||
status = AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &audioFormat, sizeof(audioFormat));
|
||||
|
||||
if (status != 0)
|
||||
{
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
p->audio_unit = nullptr;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Set up the AudioUnit callback. */
|
||||
AURenderCallbackStruct callbackStruct = WINPR_C_ARRAY_INIT;
|
||||
callbackStruct.inputProc = rdpsnd_ios_render_cb;
|
||||
callbackStruct.inputProcRefCon = p;
|
||||
status =
|
||||
AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct));
|
||||
|
||||
if (status != 0)
|
||||
{
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
p->audio_unit = nullptr;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Initialize the AudioUnit. */
|
||||
status = AudioUnitInitialize(p->audio_unit);
|
||||
|
||||
if (status != 0)
|
||||
{
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
p->audio_unit = nullptr;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Allocate the circular buffer. */
|
||||
const BOOL ok = TPCircularBufferInit(&p->buffer, CIRCULAR_BUFFER_SIZE);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
p->audio_unit = nullptr;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
p->is_opened = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void rdpsnd_ios_close(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndIOSPlugin* p = THIS(device);
|
||||
/* Make sure the device is stopped. */
|
||||
rdpsnd_ios_stop(device);
|
||||
|
||||
/* If the device is open... */
|
||||
if (p->is_opened)
|
||||
{
|
||||
/* Close the device. */
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
p->audio_unit = nullptr;
|
||||
p->is_opened = 0;
|
||||
/* Destroy the circular buffer. */
|
||||
TPCircularBufferCleanup(&p->buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_ios_free(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndIOSPlugin* p = THIS(device);
|
||||
/* Ensure the device is closed. */
|
||||
rdpsnd_ios_close(device);
|
||||
/* Free memory associated with the device. */
|
||||
free(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE ios_freerdp_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
rdpsndIOSPlugin* p = (rdpsndIOSPlugin*)calloc(1, sizeof(rdpsndIOSPlugin));
|
||||
|
||||
if (!p)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
p->device.Open = rdpsnd_ios_open;
|
||||
p->device.FormatSupported = rdpsnd_ios_format_supported;
|
||||
p->device.SetVolume = rdpsnd_ios_set_volume;
|
||||
p->device.Play = rdpsnd_ios_play;
|
||||
p->device.Start = rdpsnd_ios_start;
|
||||
p->device.Close = rdpsnd_ios_close;
|
||||
p->device.Free = rdpsnd_ios_free;
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)p);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
43
third_party/FreeRDP/channels/rdpsnd/client/mac/CMakeLists.txt
vendored
Normal file
43
third_party/FreeRDP/channels/rdpsnd/client/mac/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||
# FreeRDP cmake build script
|
||||
#
|
||||
# Copyright 2012 Laxmikant Rashinkar <LK.Rashinkar@gmail.com>
|
||||
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
# Copyright 2013 Corey Clayton <can.of.tuna@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("rdpsnd" "mac" "")
|
||||
|
||||
find_library(COCOA_LIBRARY Cocoa REQUIRED)
|
||||
find_library(CORE_FOUNDATION CoreFoundation)
|
||||
find_library(CORE_AUDIO CoreAudio REQUIRED)
|
||||
find_library(AUDIO_TOOL AudioToolbox REQUIRED)
|
||||
find_library(AV_FOUNDATION AVFoundation REQUIRED)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_mac.m)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
${AUDIO_TOOL}
|
||||
${AV_FOUNDATION}
|
||||
${CORE_AUDIO}
|
||||
${COCOA_LIBRARY}
|
||||
${CORE_FOUNDATION}
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(SYSTEM ${MACAUDIO_INCLUDE_DIRS})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
402
third_party/FreeRDP/channels/rdpsnd/client/mac/rdpsnd_mac.m
vendored
Normal file
402
third_party/FreeRDP/channels/rdpsnd/client/mac/rdpsnd_mac.m
vendored
Normal file
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* Copyright 2012 Laxmikant Rashinkar <LK.Rashinkar@gmail.com>
|
||||
* Copyright 2015 Thincast Technologies GmbH
|
||||
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
|
||||
* Copyright 2016 Inuvika Inc.
|
||||
* Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.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/sysinfo.h>
|
||||
|
||||
#include <freerdp/types.h>
|
||||
|
||||
#include <AVFoundation/AVAudioBuffer.h>
|
||||
#include <AVFoundation/AVFoundation.h>
|
||||
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
|
||||
BOOL isOpen;
|
||||
BOOL isPlaying;
|
||||
|
||||
UINT32 latency;
|
||||
AUDIO_FORMAT format;
|
||||
|
||||
AVAudioEngine *engine;
|
||||
AVAudioPlayerNode *player;
|
||||
UINT64 diff;
|
||||
} rdpsndMacPlugin;
|
||||
|
||||
static BOOL rdpsnd_mac_set_format(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format,
|
||||
UINT32 latency)
|
||||
{
|
||||
rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
|
||||
if (!mac || !format)
|
||||
return FALSE;
|
||||
|
||||
mac->latency = latency;
|
||||
mac->format = *format;
|
||||
|
||||
audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static char *FormatError(OSStatus st)
|
||||
{
|
||||
switch (st)
|
||||
{
|
||||
case kAudioFileUnspecifiedError:
|
||||
return "kAudioFileUnspecifiedError";
|
||||
|
||||
case kAudioFileUnsupportedFileTypeError:
|
||||
return "kAudioFileUnsupportedFileTypeError";
|
||||
|
||||
case kAudioFileUnsupportedDataFormatError:
|
||||
return "kAudioFileUnsupportedDataFormatError";
|
||||
|
||||
case kAudioFileUnsupportedPropertyError:
|
||||
return "kAudioFileUnsupportedPropertyError";
|
||||
|
||||
case kAudioFileBadPropertySizeError:
|
||||
return "kAudioFileBadPropertySizeError";
|
||||
|
||||
case kAudioFilePermissionsError:
|
||||
return "kAudioFilePermissionsError";
|
||||
|
||||
case kAudioFileNotOptimizedError:
|
||||
return "kAudioFileNotOptimizedError";
|
||||
|
||||
case kAudioFileInvalidChunkError:
|
||||
return "kAudioFileInvalidChunkError";
|
||||
|
||||
case kAudioFileDoesNotAllow64BitDataSizeError:
|
||||
return "kAudioFileDoesNotAllow64BitDataSizeError";
|
||||
|
||||
case kAudioFileInvalidPacketOffsetError:
|
||||
return "kAudioFileInvalidPacketOffsetError";
|
||||
|
||||
case kAudioFileInvalidFileError:
|
||||
return "kAudioFileInvalidFileError";
|
||||
|
||||
case kAudioFileOperationNotSupportedError:
|
||||
return "kAudioFileOperationNotSupportedError";
|
||||
|
||||
case kAudioFileNotOpenError:
|
||||
return "kAudioFileNotOpenError";
|
||||
|
||||
case kAudioFileEndOfFileError:
|
||||
return "kAudioFileEndOfFileError";
|
||||
|
||||
case kAudioFilePositionError:
|
||||
return "kAudioFilePositionError";
|
||||
|
||||
case kAudioFileFileNotFoundError:
|
||||
return "kAudioFileFileNotFoundError";
|
||||
|
||||
default:
|
||||
return "unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_mac_release(rdpsndMacPlugin *mac)
|
||||
{
|
||||
if (mac->player)
|
||||
[mac->player release];
|
||||
mac->player = nullptr;
|
||||
|
||||
if (mac->engine)
|
||||
[mac->engine release];
|
||||
mac->engine = nullptr;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_mac_open(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format, UINT32 latency)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
AudioDeviceID outputDeviceID;
|
||||
UInt32 propertySize;
|
||||
OSStatus err;
|
||||
NSError *error;
|
||||
rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDefaultOutputDevice,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
#if defined(MAC_OS_VERSION_12_0)
|
||||
kAudioObjectPropertyElementMain
|
||||
#else
|
||||
kAudioObjectPropertyElementMaster
|
||||
#endif
|
||||
};
|
||||
|
||||
if (mac->isOpen)
|
||||
return TRUE;
|
||||
|
||||
if (!rdpsnd_mac_set_format(device, format, latency))
|
||||
return FALSE;
|
||||
|
||||
propertySize = sizeof(outputDeviceID);
|
||||
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
|
||||
&propertySize, &outputDeviceID);
|
||||
if (err)
|
||||
{
|
||||
WLog_ERR(TAG, "AudioHardwareGetProperty: %s", FormatError(err));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
mac->engine = [[AVAudioEngine alloc] init];
|
||||
if (!mac->engine)
|
||||
return FALSE;
|
||||
|
||||
err = AudioUnitSetProperty(mac->engine.outputNode.audioUnit,
|
||||
kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global,
|
||||
0, &outputDeviceID, sizeof(outputDeviceID));
|
||||
if (err)
|
||||
{
|
||||
rdpsnd_mac_release(mac);
|
||||
WLog_ERR(TAG, "AudioUnitSetProperty: %s", FormatError(err));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
mac->player = [[AVAudioPlayerNode alloc] init];
|
||||
if (!mac->player)
|
||||
{
|
||||
rdpsnd_mac_release(mac);
|
||||
WLog_ERR(TAG, "AVAudioPlayerNode::init() failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
[mac->engine attachNode:mac->player];
|
||||
|
||||
[mac->engine connect:mac->player to:mac->engine.mainMixerNode format:nil];
|
||||
|
||||
[mac->engine prepare];
|
||||
|
||||
if (![mac->engine startAndReturnError:&error])
|
||||
{
|
||||
device->Close(device);
|
||||
WLog_ERR(TAG, "Failed to start audio player %s",
|
||||
[error.localizedDescription UTF8String]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
mac->isOpen = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_mac_close(rdpsndDevicePlugin *device)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
|
||||
|
||||
if (mac->isPlaying)
|
||||
{
|
||||
[mac->player stop];
|
||||
mac->isPlaying = FALSE;
|
||||
}
|
||||
|
||||
if (mac->isOpen)
|
||||
{
|
||||
[mac->engine stop];
|
||||
mac->isOpen = FALSE;
|
||||
}
|
||||
|
||||
rdpsnd_mac_release(mac);
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_mac_free(rdpsndDevicePlugin *device)
|
||||
{
|
||||
rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
|
||||
device->Close(device);
|
||||
free(mac);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_mac_format_supported(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format)
|
||||
{
|
||||
WINPR_UNUSED(device);
|
||||
|
||||
switch (format->wFormatTag)
|
||||
{
|
||||
case WAVE_FORMAT_PCM:
|
||||
if (format->wBitsPerSample != 16)
|
||||
return FALSE;
|
||||
|
||||
if (format->nChannels != 2)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_mac_set_volume(rdpsndDevicePlugin *device, UINT32 value)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
Float32 fVolume;
|
||||
UINT16 volumeLeft;
|
||||
UINT16 volumeRight;
|
||||
rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
|
||||
|
||||
if (!mac->player)
|
||||
return FALSE;
|
||||
|
||||
volumeLeft = (value & 0xFFFF);
|
||||
volumeRight = ((value >> 16) & 0xFFFF);
|
||||
fVolume = ((float)volumeLeft) / 65535.0f;
|
||||
|
||||
mac->player.volume = fVolume;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_mac_start(rdpsndDevicePlugin *device)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
|
||||
|
||||
if (!mac->isPlaying)
|
||||
{
|
||||
if (!mac->engine.isRunning)
|
||||
{
|
||||
NSError *error;
|
||||
|
||||
if (![mac->engine startAndReturnError:&error])
|
||||
{
|
||||
device->Close(device);
|
||||
WLog_ERR(TAG, "Failed to start audio player %s",
|
||||
[error.localizedDescription UTF8String]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[mac->player play];
|
||||
|
||||
mac->isPlaying = TRUE;
|
||||
mac->diff = 100; /* Initial latency, corrected after first sample is played. */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static UINT rdpsnd_mac_play(rdpsndDevicePlugin *device, const BYTE *data, size_t size)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device;
|
||||
AVAudioPCMBuffer *buffer;
|
||||
AVAudioFormat *format;
|
||||
float *const *db;
|
||||
size_t step;
|
||||
AVAudioFrameCount count;
|
||||
UINT64 start = GetTickCount64();
|
||||
|
||||
if (!mac->isOpen)
|
||||
return 0;
|
||||
|
||||
step = 2 * mac->format.nChannels;
|
||||
|
||||
count = size / step;
|
||||
format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
|
||||
sampleRate:mac->format.nSamplesPerSec
|
||||
channels:mac->format.nChannels
|
||||
interleaved:NO];
|
||||
|
||||
if (!format)
|
||||
{
|
||||
WLog_WARN(TAG, "AVAudioFormat::init() failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity:count];
|
||||
[format release];
|
||||
|
||||
if (!buffer)
|
||||
{
|
||||
WLog_WARN(TAG, "AVAudioPCMBuffer::init() failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
buffer.frameLength = buffer.frameCapacity;
|
||||
db = buffer.floatChannelData;
|
||||
|
||||
for (size_t pos = 0; pos < count; pos++)
|
||||
{
|
||||
const BYTE *d = &data[pos * step];
|
||||
for (size_t x = 0; x < mac->format.nChannels; x++)
|
||||
{
|
||||
const float val = (int16_t)((uint16_t)d[0] | ((uint16_t)d[1] << 8)) / 32768.0f;
|
||||
db[x][pos] = val;
|
||||
d += sizeof(int16_t);
|
||||
}
|
||||
}
|
||||
|
||||
rdpsnd_mac_start(device);
|
||||
|
||||
[mac->player scheduleBuffer:buffer
|
||||
completionHandler:^{
|
||||
UINT64 stop = GetTickCount64();
|
||||
if (start > stop)
|
||||
mac->diff = 0;
|
||||
else
|
||||
mac->diff = stop - start;
|
||||
}];
|
||||
|
||||
[buffer release];
|
||||
|
||||
return mac->diff > UINT_MAX ? UINT_MAX : mac->diff;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
rdpsndMacPlugin *mac;
|
||||
mac = (rdpsndMacPlugin *)calloc(1, sizeof(rdpsndMacPlugin));
|
||||
|
||||
if (!mac)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
mac->device.Open = rdpsnd_mac_open;
|
||||
mac->device.FormatSupported = rdpsnd_mac_format_supported;
|
||||
mac->device.SetVolume = rdpsnd_mac_set_volume;
|
||||
mac->device.Play = rdpsnd_mac_play;
|
||||
mac->device.Close = rdpsnd_mac_close;
|
||||
mac->device.Free = rdpsnd_mac_free;
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin *)mac);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
29
third_party/FreeRDP/channels/rdpsnd/client/opensles/CMakeLists.txt
vendored
Normal file
29
third_party/FreeRDP/channels/rdpsnd/client/opensles/CMakeLists.txt
vendored
Normal 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("rdpsnd" "opensles" "")
|
||||
|
||||
find_package(OpenSLES REQUIRED)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS opensl_io.c rdpsnd_opensles.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 "")
|
||||
422
third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.c
vendored
Normal file
422
third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.c
vendored
Normal file
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
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 "rdpsnd_main.h"
|
||||
#include "opensl_io.h"
|
||||
#define CONV16BIT 32768
|
||||
#define CONVMYFLT (1. / 32768.)
|
||||
|
||||
static void bqPlayerCallback(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);
|
||||
DEBUG_SND("engineObject=%p", (void*)p->engineObject);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto engine_end;
|
||||
|
||||
// realize the engine
|
||||
result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE);
|
||||
DEBUG_SND("Realize=%" PRIu32 "", result);
|
||||
|
||||
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));
|
||||
DEBUG_SND("engineEngine=%p", (void*)p->engineEngine);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto engine_end;
|
||||
|
||||
engine_end:
|
||||
return result;
|
||||
}
|
||||
|
||||
// opens the OpenSL ES device for output
|
||||
static SLresult openSLPlayOpen(OPENSL_STREAM* p)
|
||||
{
|
||||
SLresult result;
|
||||
SLuint32 sr = p->sr;
|
||||
SLuint32 channels = p->outchannels;
|
||||
WINPR_ASSERT(p->engineObject);
|
||||
WINPR_ASSERT(p->engineEngine);
|
||||
|
||||
if (channels)
|
||||
{
|
||||
// configure audio source
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
p->queuesize };
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const SLInterfaceID ids[] = { SL_IID_VOLUME };
|
||||
const SLboolean req[] = { SL_BOOLEAN_FALSE };
|
||||
result = (*p->engineEngine)
|
||||
->CreateOutputMix(p->engineEngine, &(p->outputMixObject), 1, ids, req);
|
||||
DEBUG_SND("engineEngine=%p", (void*)p->engineEngine);
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto end_openaudio;
|
||||
|
||||
// realize the output mix
|
||||
result = (*p->outputMixObject)->Realize(p->outputMixObject, SL_BOOLEAN_FALSE);
|
||||
DEBUG_SND("Realize=%" PRIu32 "", result);
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto end_openaudio;
|
||||
|
||||
int speakers;
|
||||
|
||||
if (channels > 1)
|
||||
speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
else
|
||||
speakers = SL_SPEAKER_FRONT_CENTER;
|
||||
|
||||
SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM,
|
||||
channels,
|
||||
sr,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
speakers,
|
||||
SL_BYTEORDER_LITTLEENDIAN };
|
||||
SLDataSource audioSrc = { &loc_bufq, &format_pcm };
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, p->outputMixObject };
|
||||
SLDataSink audioSnk = { &loc_outmix, nullptr };
|
||||
// create audio player
|
||||
const SLInterfaceID ids1[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
|
||||
const SLboolean req1[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
|
||||
result = (*p->engineEngine)
|
||||
->CreateAudioPlayer(p->engineEngine, &(p->bqPlayerObject), &audioSrc,
|
||||
&audioSnk, 2, ids1, req1);
|
||||
DEBUG_SND("bqPlayerObject=%p", (void*)p->bqPlayerObject);
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto end_openaudio;
|
||||
|
||||
// realize the player
|
||||
result = (*p->bqPlayerObject)->Realize(p->bqPlayerObject, SL_BOOLEAN_FALSE);
|
||||
DEBUG_SND("Realize=%" PRIu32 "", result);
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto end_openaudio;
|
||||
|
||||
// get the play interface
|
||||
result =
|
||||
(*p->bqPlayerObject)->GetInterface(p->bqPlayerObject, SL_IID_PLAY, &(p->bqPlayerPlay));
|
||||
DEBUG_SND("bqPlayerPlay=%p", (void*)p->bqPlayerPlay);
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto end_openaudio;
|
||||
|
||||
// get the volume interface
|
||||
result = (*p->bqPlayerObject)
|
||||
->GetInterface(p->bqPlayerObject, SL_IID_VOLUME, &(p->bqPlayerVolume));
|
||||
DEBUG_SND("bqPlayerVolume=%p", (void*)p->bqPlayerVolume);
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto end_openaudio;
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*p->bqPlayerObject)
|
||||
->GetInterface(p->bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
&(p->bqPlayerBufferQueue));
|
||||
DEBUG_SND("bqPlayerBufferQueue=%p", (void*)p->bqPlayerBufferQueue);
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto end_openaudio;
|
||||
|
||||
// register callback on the buffer queue
|
||||
result = (*p->bqPlayerBufferQueue)
|
||||
->RegisterCallback(p->bqPlayerBufferQueue, bqPlayerCallback, p);
|
||||
DEBUG_SND("bqPlayerCallback=%p", (void*)p->bqPlayerCallback);
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS)
|
||||
goto end_openaudio;
|
||||
|
||||
// set the player's state to playing
|
||||
result = (*p->bqPlayerPlay)->SetPlayState(p->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
DEBUG_SND("SetPlayState=%" PRIu32 "", result);
|
||||
WINPR_ASSERT(!result);
|
||||
end_openaudio:
|
||||
WINPR_ASSERT(!result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return SL_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// close the OpenSL IO and destroy the audio engine
|
||||
static void openSLDestroyEngine(OPENSL_STREAM* p)
|
||||
{
|
||||
// destroy buffer queue audio player object, and invalidate all associated interfaces
|
||||
if (p->bqPlayerObject != nullptr)
|
||||
{
|
||||
(*p->bqPlayerObject)->Destroy(p->bqPlayerObject);
|
||||
p->bqPlayerObject = nullptr;
|
||||
p->bqPlayerVolume = nullptr;
|
||||
p->bqPlayerPlay = nullptr;
|
||||
p->bqPlayerBufferQueue = nullptr;
|
||||
p->bqPlayerEffectSend = nullptr;
|
||||
}
|
||||
|
||||
// destroy output mix object, and invalidate all associated interfaces
|
||||
if (p->outputMixObject != nullptr)
|
||||
{
|
||||
(*p->outputMixObject)->Destroy(p->outputMixObject);
|
||||
p->outputMixObject = nullptr;
|
||||
}
|
||||
|
||||
// destroy engine object, and invalidate all associated interfaces
|
||||
if (p->engineObject != nullptr)
|
||||
{
|
||||
(*p->engineObject)->Destroy(p->engineObject);
|
||||
p->engineObject = nullptr;
|
||||
p->engineEngine = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// open the android audio device for and/or output
|
||||
OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, int bufferframes)
|
||||
{
|
||||
OPENSL_STREAM* p;
|
||||
p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM));
|
||||
|
||||
if (!p)
|
||||
return nullptr;
|
||||
|
||||
p->queuesize = bufferframes;
|
||||
p->outchannels = outchannels;
|
||||
p->sr = sr;
|
||||
|
||||
if (openSLCreateEngine(p) != SL_RESULT_SUCCESS)
|
||||
{
|
||||
android_CloseAudioDevice(p);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (openSLPlayOpen(p) != SL_RESULT_SUCCESS)
|
||||
{
|
||||
android_CloseAudioDevice(p);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
p->queue = Queue_New(TRUE, -1, -1);
|
||||
|
||||
if (!p->queue)
|
||||
{
|
||||
android_CloseAudioDevice(p);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// close the android audio device
|
||||
void android_CloseAudioDevice(OPENSL_STREAM* p)
|
||||
{
|
||||
if (p == nullptr)
|
||||
return;
|
||||
|
||||
openSLDestroyEngine(p);
|
||||
|
||||
if (p->queue)
|
||||
Queue_Free(p->queue);
|
||||
|
||||
free(p);
|
||||
}
|
||||
|
||||
// this callback handler is called every time a buffer finishes playing
|
||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
||||
{
|
||||
OPENSL_STREAM* p = (OPENSL_STREAM*)context;
|
||||
WINPR_ASSERT(p);
|
||||
WINPR_ASSERT(p->queue);
|
||||
void* data = Queue_Dequeue(p->queue);
|
||||
free(data);
|
||||
}
|
||||
|
||||
// puts a buffer of size samples to the device
|
||||
int android_AudioOut(OPENSL_STREAM* p, const short* buffer, int size)
|
||||
{
|
||||
HANDLE ev;
|
||||
WINPR_ASSERT(p);
|
||||
WINPR_ASSERT(buffer);
|
||||
WINPR_ASSERT(size > 0);
|
||||
|
||||
ev = Queue_Event(p->queue);
|
||||
/* Assure, that the queue is not full. */
|
||||
if (p->queuesize <= Queue_Count(p->queue) && WaitForSingleObject(ev, INFINITE) == WAIT_FAILED)
|
||||
{
|
||||
DEBUG_SND("WaitForSingleObject failed!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
void* data = calloc(size, sizeof(short));
|
||||
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_SND("unable to allocate a buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(data, buffer, size * sizeof(short));
|
||||
Queue_Enqueue(p->queue, data);
|
||||
(*p->bqPlayerBufferQueue)->Enqueue(p->bqPlayerBufferQueue, data, sizeof(short) * size);
|
||||
return size;
|
||||
}
|
||||
|
||||
int android_GetOutputMute(OPENSL_STREAM* p)
|
||||
{
|
||||
SLboolean mute;
|
||||
WINPR_ASSERT(p);
|
||||
WINPR_ASSERT(p->bqPlayerVolume);
|
||||
SLresult rc = (*p->bqPlayerVolume)->GetMute(p->bqPlayerVolume, &mute);
|
||||
|
||||
if (SL_RESULT_SUCCESS != rc)
|
||||
return SL_BOOLEAN_FALSE;
|
||||
|
||||
return mute;
|
||||
}
|
||||
|
||||
BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL _mute)
|
||||
{
|
||||
SLboolean mute = _mute;
|
||||
WINPR_ASSERT(p);
|
||||
WINPR_ASSERT(p->bqPlayerVolume);
|
||||
SLresult rc = (*p->bqPlayerVolume)->SetMute(p->bqPlayerVolume, mute);
|
||||
|
||||
if (SL_RESULT_SUCCESS != rc)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int android_GetOutputVolume(OPENSL_STREAM* p)
|
||||
{
|
||||
SLmillibel level;
|
||||
WINPR_ASSERT(p);
|
||||
WINPR_ASSERT(p->bqPlayerVolume);
|
||||
SLresult rc = (*p->bqPlayerVolume)->GetVolumeLevel(p->bqPlayerVolume, &level);
|
||||
|
||||
if (SL_RESULT_SUCCESS != rc)
|
||||
return 0;
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
int android_GetOutputVolumeMax(OPENSL_STREAM* p)
|
||||
{
|
||||
SLmillibel level;
|
||||
WINPR_ASSERT(p);
|
||||
WINPR_ASSERT(p->bqPlayerVolume);
|
||||
SLresult rc = (*p->bqPlayerVolume)->GetMaxVolumeLevel(p->bqPlayerVolume, &level);
|
||||
|
||||
if (SL_RESULT_SUCCESS != rc)
|
||||
return 0;
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level)
|
||||
{
|
||||
SLresult rc = (*p->bqPlayerVolume)->SetVolumeLevel(p->bqPlayerVolume, level);
|
||||
|
||||
if (SL_RESULT_SUCCESS != rc)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
110
third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.h
vendored
Normal file
110
third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.h
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
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_RDPSND_CLIENT_OPENSL_IO_H
|
||||
#define FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <stdlib.h>
|
||||
#include <winpr/synch.h>
|
||||
|
||||
#include <freerdp/api.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// engine interfaces
|
||||
SLObjectItf engineObject;
|
||||
SLEngineItf engineEngine;
|
||||
|
||||
// output mix interfaces
|
||||
SLObjectItf outputMixObject;
|
||||
|
||||
// buffer queue player interfaces
|
||||
SLObjectItf bqPlayerObject;
|
||||
SLPlayItf bqPlayerPlay;
|
||||
SLVolumeItf bqPlayerVolume;
|
||||
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
|
||||
SLEffectSendItf bqPlayerEffectSend;
|
||||
|
||||
unsigned int outchannels;
|
||||
unsigned int sr;
|
||||
|
||||
unsigned int queuesize;
|
||||
wQueue* queue;
|
||||
} OPENSL_STREAM;
|
||||
|
||||
/*
|
||||
Open the audio device with a given sampling rate (sr), output channels and IO buffer size
|
||||
in frames. Returns a handle to the OpenSL stream
|
||||
*/
|
||||
FREERDP_LOCAL OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, int bufferframes);
|
||||
/*
|
||||
Close the audio device
|
||||
*/
|
||||
FREERDP_LOCAL void android_CloseAudioDevice(OPENSL_STREAM* p);
|
||||
/*
|
||||
Write a buffer to the OpenSL stream *p, of size samples. Returns the number of samples written.
|
||||
*/
|
||||
FREERDP_LOCAL int android_AudioOut(OPENSL_STREAM* p, const short* buffer, int size);
|
||||
/*
|
||||
* Set the volume input level.
|
||||
*/
|
||||
FREERDP_LOCAL void android_SetInputVolume(OPENSL_STREAM* p, int level);
|
||||
/*
|
||||
* Get the current output mute setting.
|
||||
*/
|
||||
FREERDP_LOCAL int android_GetOutputMute(OPENSL_STREAM* p);
|
||||
/*
|
||||
* Change the current output mute setting.
|
||||
*/
|
||||
FREERDP_LOCAL BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL mute);
|
||||
/*
|
||||
* Get the current output volume level.
|
||||
*/
|
||||
FREERDP_LOCAL int android_GetOutputVolume(OPENSL_STREAM* p);
|
||||
/*
|
||||
* Get the maximum output volume level.
|
||||
*/
|
||||
FREERDP_LOCAL int android_GetOutputVolumeMax(OPENSL_STREAM* p);
|
||||
|
||||
/*
|
||||
* Set the volume output level.
|
||||
*/
|
||||
FREERDP_LOCAL BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level);
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H */
|
||||
373
third_party/FreeRDP/channels/rdpsnd/client/opensles/rdpsnd_opensles.c
vendored
Normal file
373
third_party/FreeRDP/channels/rdpsnd/client/opensles/rdpsnd_opensles.c
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* 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 <stdbool.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/cmdline.h>
|
||||
#include <winpr/sysinfo.h>
|
||||
#include <winpr/collections.h>
|
||||
|
||||
#include <freerdp/types.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
#include "opensl_io.h"
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
|
||||
UINT32 latency;
|
||||
int wformat;
|
||||
int block_size;
|
||||
char* device_name;
|
||||
|
||||
OPENSL_STREAM* stream;
|
||||
|
||||
UINT32 volume;
|
||||
|
||||
UINT32 rate;
|
||||
UINT32 channels;
|
||||
int format;
|
||||
} rdpsndopenslesPlugin;
|
||||
|
||||
static int rdpsnd_opensles_volume_to_millibel(unsigned short level, int max)
|
||||
{
|
||||
const int min = SL_MILLIBEL_MIN;
|
||||
const int step = max - min;
|
||||
const int rc = (level * step / 0xFFFF) + min;
|
||||
DEBUG_SND("level=%hu, min=%d, max=%d, step=%d, result=%d", level, min, max, step, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static unsigned short rdpsnd_opensles_millibel_to_volume(int millibel, int max)
|
||||
{
|
||||
const int min = SL_MILLIBEL_MIN;
|
||||
const int range = max - min;
|
||||
const int rc = ((millibel - min) * 0xFFFF + range / 2 + 1) / range;
|
||||
DEBUG_SND("millibel=%d, min=%d, max=%d, range=%d, result=%d", millibel, min, max, range, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static bool rdpsnd_opensles_check_handle(const rdpsndopenslesPlugin* hdl)
|
||||
{
|
||||
bool rc = true;
|
||||
|
||||
if (!hdl)
|
||||
rc = false;
|
||||
else
|
||||
{
|
||||
if (!hdl->stream)
|
||||
rc = false;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, UINT32 volume);
|
||||
|
||||
static int rdpsnd_opensles_set_params(rdpsndopenslesPlugin* opensles)
|
||||
{
|
||||
DEBUG_SND("opensles=%p", (void*)opensles);
|
||||
|
||||
if (!rdpsnd_opensles_check_handle(opensles))
|
||||
return 0;
|
||||
|
||||
if (opensles->stream)
|
||||
android_CloseAudioDevice(opensles->stream);
|
||||
|
||||
opensles->stream = android_OpenAudioDevice(opensles->rate, opensles->channels, 20);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_opensles_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
|
||||
UINT32 latency)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
|
||||
rdpsnd_opensles_check_handle(opensles);
|
||||
DEBUG_SND("opensles=%p format=%p, latency=%" PRIu32, (void*)opensles, (void*)format, latency);
|
||||
|
||||
if (format)
|
||||
{
|
||||
DEBUG_SND("format=%" PRIu16 ", cbsize=%" PRIu16 ", samples=%" PRIu32 ", bits=%" PRIu16
|
||||
", channels=%" PRIu16 ", align=%" PRIu16 "",
|
||||
format->wFormatTag, format->cbSize, format->nSamplesPerSec,
|
||||
format->wBitsPerSample, format->nChannels, format->nBlockAlign);
|
||||
opensles->rate = format->nSamplesPerSec;
|
||||
opensles->channels = format->nChannels;
|
||||
opensles->format = format->wFormatTag;
|
||||
opensles->wformat = format->wFormatTag;
|
||||
opensles->block_size = format->nBlockAlign;
|
||||
}
|
||||
|
||||
opensles->latency = latency;
|
||||
return (rdpsnd_opensles_set_params(opensles) == 0);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_opensles_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
|
||||
UINT32 latency)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
|
||||
DEBUG_SND("opensles=%p format=%p, latency=%" PRIu32 ", rate=%" PRIu32 "", (void*)opensles,
|
||||
(void*)format, latency, opensles->rate);
|
||||
|
||||
if (rdpsnd_opensles_check_handle(opensles))
|
||||
return TRUE;
|
||||
|
||||
opensles->stream = android_OpenAudioDevice(opensles->rate, opensles->channels, 20);
|
||||
WINPR_ASSERT(opensles->stream);
|
||||
|
||||
if (!opensles->stream)
|
||||
WLog_ERR(TAG, "android_OpenAudioDevice failed");
|
||||
else
|
||||
rdpsnd_opensles_set_volume(device, opensles->volume);
|
||||
|
||||
return rdpsnd_opensles_set_format(device, format, latency);
|
||||
}
|
||||
|
||||
static void rdpsnd_opensles_close(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
|
||||
DEBUG_SND("opensles=%p", (void*)opensles);
|
||||
|
||||
if (!rdpsnd_opensles_check_handle(opensles))
|
||||
return;
|
||||
|
||||
android_CloseAudioDevice(opensles->stream);
|
||||
opensles->stream = nullptr;
|
||||
}
|
||||
|
||||
static void rdpsnd_opensles_free(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
|
||||
DEBUG_SND("opensles=%p", (void*)opensles);
|
||||
WINPR_ASSERT(opensles);
|
||||
WINPR_ASSERT(opensles->device_name);
|
||||
free(opensles->device_name);
|
||||
free(opensles);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_opensles_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
|
||||
{
|
||||
DEBUG_SND("format=%" PRIu16 ", cbsize=%" PRIu16 ", samples=%" PRIu32 ", bits=%" PRIu16
|
||||
", channels=%" PRIu16 ", align=%" PRIu16 "",
|
||||
format->wFormatTag, format->cbSize, format->nSamplesPerSec, format->wBitsPerSample,
|
||||
format->nChannels, format->nBlockAlign);
|
||||
WINPR_ASSERT(device);
|
||||
WINPR_ASSERT(format);
|
||||
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static UINT32 rdpsnd_opensles_get_volume(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
|
||||
DEBUG_SND("opensles=%p", (void*)opensles);
|
||||
WINPR_ASSERT(opensles);
|
||||
|
||||
if (opensles->stream)
|
||||
{
|
||||
const int max = android_GetOutputVolumeMax(opensles->stream);
|
||||
const int rc = android_GetOutputVolume(opensles->stream);
|
||||
|
||||
if (android_GetOutputMute(opensles->stream))
|
||||
opensles->volume = 0;
|
||||
else
|
||||
{
|
||||
const unsigned short vol = rdpsnd_opensles_millibel_to_volume(rc, max);
|
||||
opensles->volume = (vol << 16) | (vol & 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
return opensles->volume;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, UINT32 value)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
|
||||
DEBUG_SND("opensles=%p, value=%" PRIu32 "", (void*)opensles, value);
|
||||
WINPR_ASSERT(opensles);
|
||||
opensles->volume = value;
|
||||
|
||||
if (opensles->stream)
|
||||
{
|
||||
if (0 == opensles->volume)
|
||||
return android_SetOutputMute(opensles->stream, true);
|
||||
else
|
||||
{
|
||||
const int max = android_GetOutputVolumeMax(opensles->stream);
|
||||
const int vol = rdpsnd_opensles_volume_to_millibel(value & 0xFFFF, max);
|
||||
|
||||
if (!android_SetOutputMute(opensles->stream, false))
|
||||
return FALSE;
|
||||
|
||||
if (!android_SetOutputVolume(opensles->stream, vol))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static UINT rdpsnd_opensles_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
|
||||
{
|
||||
union
|
||||
{
|
||||
const BYTE* b;
|
||||
const short* s;
|
||||
} src;
|
||||
int ret;
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
|
||||
DEBUG_SND("opensles=%p, data=%p, size=%d", (void*)opensles, (void*)data, size);
|
||||
|
||||
if (!rdpsnd_opensles_check_handle(opensles))
|
||||
return 0;
|
||||
|
||||
src.b = data;
|
||||
DEBUG_SND("size=%d, src=%p", size, (void*)src.b);
|
||||
WINPR_ASSERT(0 == size % 2);
|
||||
WINPR_ASSERT(size > 0);
|
||||
WINPR_ASSERT(src.b);
|
||||
ret = android_AudioOut(opensles->stream, src.s, size / 2);
|
||||
|
||||
if (ret < 0)
|
||||
WLog_ERR(TAG, "android_AudioOut failed (%d)", ret);
|
||||
|
||||
return 10; /* TODO: Get real latencry in [ms] */
|
||||
}
|
||||
|
||||
static void rdpsnd_opensles_start(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
|
||||
rdpsnd_opensles_check_handle(opensles);
|
||||
DEBUG_SND("opensles=%p", (void*)opensles);
|
||||
}
|
||||
|
||||
static int rdpsnd_opensles_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
|
||||
COMMAND_LINE_ARGUMENT_A rdpsnd_opensles_args[] = {
|
||||
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr, "device" },
|
||||
{ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
|
||||
};
|
||||
|
||||
WINPR_ASSERT(opensles);
|
||||
WINPR_ASSERT(args);
|
||||
DEBUG_SND("opensles=%p, args=%p", (void*)opensles, (void*)args);
|
||||
const DWORD flags =
|
||||
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
|
||||
const int status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_opensles_args,
|
||||
flags, opensles, nullptr, nullptr);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
const COMMAND_LINE_ARGUMENT_A* arg = rdpsnd_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)
|
||||
return ERROR_OUTOFMEMORY;
|
||||
}
|
||||
CommandLineSwitchEnd(arg)
|
||||
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE opensles_freerdp_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
UINT error = ERROR_INTERNAL_ERROR;
|
||||
DEBUG_SND("pEntryPoints=%p", (void*)pEntryPoints);
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)calloc(1, sizeof(rdpsndopenslesPlugin));
|
||||
|
||||
if (!opensles)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
opensles->device.Open = rdpsnd_opensles_open;
|
||||
opensles->device.FormatSupported = rdpsnd_opensles_format_supported;
|
||||
opensles->device.GetVolume = rdpsnd_opensles_get_volume;
|
||||
opensles->device.SetVolume = rdpsnd_opensles_set_volume;
|
||||
opensles->device.Start = rdpsnd_opensles_start;
|
||||
opensles->device.Play = rdpsnd_opensles_play;
|
||||
opensles->device.Close = rdpsnd_opensles_close;
|
||||
opensles->device.Free = rdpsnd_opensles_free;
|
||||
const ADDIN_ARGV* args = pEntryPoints->args;
|
||||
rdpsnd_opensles_parse_addin_args((rdpsndDevicePlugin*)opensles, args);
|
||||
|
||||
if (!opensles->device_name)
|
||||
{
|
||||
opensles->device_name = _strdup("default");
|
||||
|
||||
if (!opensles->device_name)
|
||||
{
|
||||
error = CHANNEL_RC_NO_MEMORY;
|
||||
goto outstrdup;
|
||||
}
|
||||
}
|
||||
|
||||
opensles->rate = 44100;
|
||||
opensles->channels = 2;
|
||||
opensles->format = WAVE_FORMAT_ADPCM;
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)opensles);
|
||||
DEBUG_SND("success");
|
||||
return CHANNEL_RC_OK;
|
||||
outstrdup:
|
||||
free(opensles);
|
||||
return error;
|
||||
}
|
||||
31
third_party/FreeRDP/channels/rdpsnd/client/oss/CMakeLists.txt
vendored
Normal file
31
third_party/FreeRDP/channels/rdpsnd/client/oss/CMakeLists.txt
vendored
Normal 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("rdpsnd" "oss" "")
|
||||
|
||||
find_package(OSS REQUIRED)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_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 "")
|
||||
444
third_party/FreeRDP/channels/rdpsnd/client/oss/rdpsnd_oss.c
vendored
Normal file
444
third_party/FreeRDP/channels/rdpsnd/client/oss/rdpsnd_oss.c
vendored
Normal file
@@ -0,0 +1,444 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* 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/string.h>
|
||||
#include <winpr/cmdline.h>
|
||||
#include <winpr/sysinfo.h>
|
||||
#include <winpr/collections.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/types.h>
|
||||
#include <freerdp/settings.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
|
||||
int pcm_handle;
|
||||
int mixer_handle;
|
||||
int dev_unit;
|
||||
|
||||
int supported_formats;
|
||||
|
||||
UINT32 latency;
|
||||
AUDIO_FORMAT format;
|
||||
} rdpsndOssPlugin;
|
||||
|
||||
#define OSS_LOG_ERR(_text, _error) \
|
||||
do \
|
||||
{ \
|
||||
if ((_error) != 0) \
|
||||
{ \
|
||||
char ebuffer[256] = WINPR_C_ARRAY_INIT; \
|
||||
WLog_ERR(TAG, "%s: %i - %s", (_text), (_error), \
|
||||
winpr_strerror((_error), ebuffer, sizeof(ebuffer))); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int rdpsnd_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 rdpsnd_oss_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
|
||||
{
|
||||
int req_fmt = 0;
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
req_fmt = rdpsnd_oss_get_format(format);
|
||||
|
||||
/* Check really supported formats by dev. */
|
||||
if (oss->pcm_handle != -1)
|
||||
{
|
||||
if ((req_fmt & oss->supported_formats) == 0)
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (req_fmt == 0)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_oss_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
|
||||
UINT32 latency)
|
||||
{
|
||||
int tmp = 0;
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
|
||||
|
||||
if (device == nullptr || oss->pcm_handle == -1 || format == nullptr)
|
||||
return FALSE;
|
||||
|
||||
oss->latency = latency;
|
||||
CopyMemory(&(oss->format), format, sizeof(AUDIO_FORMAT));
|
||||
tmp = rdpsnd_oss_get_format(format);
|
||||
|
||||
if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1)
|
||||
{
|
||||
OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
tmp = format->nChannels;
|
||||
|
||||
if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
|
||||
{
|
||||
OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
tmp = (int)format->nSamplesPerSec;
|
||||
|
||||
if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
|
||||
{
|
||||
OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
tmp = format->nBlockAlign;
|
||||
|
||||
if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
|
||||
{
|
||||
OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void rdpsnd_oss_open_mixer(rdpsndOssPlugin* oss)
|
||||
{
|
||||
int devmask = 0;
|
||||
char mixer_name[PATH_MAX] = "/dev/mixer";
|
||||
|
||||
if (oss->mixer_handle != -1)
|
||||
return;
|
||||
|
||||
if (oss->dev_unit != -1)
|
||||
(void)sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit);
|
||||
|
||||
if ((oss->mixer_handle = open(mixer_name, O_RDWR)) < 0)
|
||||
{
|
||||
OSS_LOG_ERR("mixer open failed", errno);
|
||||
oss->mixer_handle = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ioctl(oss->mixer_handle, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
|
||||
{
|
||||
OSS_LOG_ERR("SOUND_MIXER_READ_DEVMASK failed", errno);
|
||||
close(oss->mixer_handle);
|
||||
oss->mixer_handle = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_oss_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
|
||||
{
|
||||
char dev_name[PATH_MAX] = "/dev/dsp";
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
|
||||
|
||||
if (device == nullptr || oss->pcm_handle != -1)
|
||||
return TRUE;
|
||||
|
||||
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 ((oss->pcm_handle = open(dev_name, O_WRONLY)) < 0)
|
||||
{
|
||||
OSS_LOG_ERR("sound dev open failed", errno);
|
||||
oss->pcm_handle = -1;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &oss->supported_formats) == -1)
|
||||
{
|
||||
OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno);
|
||||
close(oss->pcm_handle);
|
||||
oss->pcm_handle = -1;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
rdpsnd_oss_set_format(device, format, latency);
|
||||
rdpsnd_oss_open_mixer(oss);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void rdpsnd_oss_close(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
|
||||
|
||||
if (device == nullptr)
|
||||
return;
|
||||
|
||||
if (oss->pcm_handle != -1)
|
||||
{
|
||||
WLog_INFO(TAG, "close: dsp");
|
||||
close(oss->pcm_handle);
|
||||
oss->pcm_handle = -1;
|
||||
}
|
||||
|
||||
if (oss->mixer_handle != -1)
|
||||
{
|
||||
WLog_INFO(TAG, "close: mixer");
|
||||
close(oss->mixer_handle);
|
||||
oss->mixer_handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_oss_free(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
|
||||
|
||||
if (device == nullptr)
|
||||
return;
|
||||
|
||||
rdpsnd_oss_close(device);
|
||||
free(oss);
|
||||
}
|
||||
|
||||
static UINT32 rdpsnd_oss_get_volume(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
|
||||
WINPR_ASSERT(oss);
|
||||
int vol = 0;
|
||||
|
||||
/* On error return 50% volume. */
|
||||
UINT32 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
|
||||
UINT32 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
|
||||
UINT32 dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
|
||||
|
||||
if (device == nullptr || oss->mixer_handle == -1)
|
||||
return dwVolume;
|
||||
|
||||
if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1)
|
||||
{
|
||||
OSS_LOG_ERR("MIXER_READ", errno);
|
||||
return dwVolume;
|
||||
}
|
||||
|
||||
dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100);
|
||||
dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100);
|
||||
dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight);
|
||||
return dwVolume;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_oss_set_volume(rdpsndDevicePlugin* device, UINT32 value)
|
||||
{
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
|
||||
WINPR_ASSERT(oss);
|
||||
|
||||
if (device == nullptr || oss->mixer_handle == -1)
|
||||
return FALSE;
|
||||
|
||||
unsigned left = (((value & 0xFFFF) * 100) / 0xFFFF);
|
||||
unsigned right = ((((value >> 16) & 0xFFFF) * 100) / 0xFFFF);
|
||||
|
||||
left |= (right << 8);
|
||||
|
||||
if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1)
|
||||
{
|
||||
OSS_LOG_ERR("WRITE_MIXER", errno);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static UINT rdpsnd_oss_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
|
||||
{
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
|
||||
|
||||
if (device == nullptr || oss->mixer_handle == -1)
|
||||
return 0;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
ssize_t status = write(oss->pcm_handle, data, size);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
OSS_LOG_ERR("write fail", errno);
|
||||
rdpsnd_oss_close(device);
|
||||
rdpsnd_oss_open(device, nullptr, oss->latency);
|
||||
break;
|
||||
}
|
||||
|
||||
data += status;
|
||||
|
||||
if ((size_t)status <= size)
|
||||
size -= (size_t)status;
|
||||
else
|
||||
size = 0;
|
||||
}
|
||||
|
||||
return 10; /* TODO: Get real latency in [ms] */
|
||||
}
|
||||
|
||||
static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin* 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;
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device;
|
||||
COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = {
|
||||
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr, "device" },
|
||||
{ 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, rdpsnd_oss_args, flags, oss,
|
||||
nullptr, nullptr);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
arg = rdpsnd_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)
|
||||
return ERROR_OUTOFMEMORY;
|
||||
|
||||
{
|
||||
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 = (int)val;
|
||||
}
|
||||
|
||||
if (oss->dev_unit < 0 || *eptr != '\0')
|
||||
oss->dev_unit = -1;
|
||||
|
||||
free(str_num);
|
||||
}
|
||||
CommandLineSwitchEnd(arg)
|
||||
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
const ADDIN_ARGV* args = nullptr;
|
||||
rdpsndOssPlugin* oss = (rdpsndOssPlugin*)calloc(1, sizeof(rdpsndOssPlugin));
|
||||
|
||||
if (!oss)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
oss->device.Open = rdpsnd_oss_open;
|
||||
oss->device.FormatSupported = rdpsnd_oss_format_supported;
|
||||
oss->device.GetVolume = rdpsnd_oss_get_volume;
|
||||
oss->device.SetVolume = rdpsnd_oss_set_volume;
|
||||
oss->device.Play = rdpsnd_oss_play;
|
||||
oss->device.Close = rdpsnd_oss_close;
|
||||
oss->device.Free = rdpsnd_oss_free;
|
||||
oss->pcm_handle = -1;
|
||||
oss->mixer_handle = -1;
|
||||
oss->dev_unit = -1;
|
||||
args = pEntryPoints->args;
|
||||
if (rdpsnd_oss_parse_addin_args(&oss->device, args) < 0)
|
||||
{
|
||||
free(oss);
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
30
third_party/FreeRDP/channels/rdpsnd/client/pulse/CMakeLists.txt
vendored
Normal file
30
third_party/FreeRDP/channels/rdpsnd/client/pulse/CMakeLists.txt
vendored
Normal 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("rdpsnd" "pulse" "")
|
||||
|
||||
find_package(PulseAudio REQUIRED)
|
||||
freerdp_client_pc_add_requires_private("libpulse")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_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 "")
|
||||
789
third_party/FreeRDP/channels/rdpsnd/client/pulse/rdpsnd_pulse.c
vendored
Normal file
789
third_party/FreeRDP/channels/rdpsnd/client/pulse/rdpsnd_pulse.c
vendored
Normal file
@@ -0,0 +1,789 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* Copyright 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 <freerdp/utils/helpers.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/cast.h>
|
||||
#include <winpr/assert.h>
|
||||
#include <winpr/stream.h>
|
||||
#include <winpr/cmdline.h>
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include <freerdp/types.h>
|
||||
#include <freerdp/codec/dsp.h>
|
||||
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
|
||||
char* device_name;
|
||||
char* client_name;
|
||||
char* stream_name;
|
||||
pa_threaded_mainloop* mainloop;
|
||||
pa_context* context;
|
||||
pa_sample_spec sample_spec;
|
||||
pa_stream* stream;
|
||||
UINT32 latency;
|
||||
UINT32 volume;
|
||||
time_t reconnect_delay_seconds;
|
||||
time_t reconnect_time;
|
||||
} rdpsndPulsePlugin;
|
||||
|
||||
static BOOL rdpsnd_check_pulse(rdpsndPulsePlugin* pulse, BOOL haveStream)
|
||||
{
|
||||
BOOL rc = TRUE;
|
||||
WINPR_ASSERT(pulse);
|
||||
|
||||
if (!pulse->context)
|
||||
{
|
||||
WLog_WARN(TAG, "pulse->context=nullptr");
|
||||
rc = FALSE;
|
||||
}
|
||||
|
||||
if (haveStream)
|
||||
{
|
||||
if (!pulse->stream)
|
||||
{
|
||||
WLog_WARN(TAG, "pulse->stream=%p", WINPR_CXX_COMPAT_CAST(const void*, pulse->stream));
|
||||
rc = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pulse->mainloop)
|
||||
{
|
||||
WLog_WARN(TAG, "pulse->mainloop=%p", WINPR_CXX_COMPAT_CAST(const void*, pulse->mainloop));
|
||||
rc = FALSE;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format);
|
||||
|
||||
static void rdpsnd_pulse_get_sink_info(pa_context* c, const pa_sink_info* i,
|
||||
WINPR_ATTR_UNUSED int eol, void* userdata)
|
||||
{
|
||||
UINT16 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */
|
||||
UINT16 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
|
||||
|
||||
WINPR_ASSERT(c);
|
||||
if (!rdpsnd_check_pulse(pulse, FALSE) || !i)
|
||||
return;
|
||||
|
||||
for (uint8_t x = 0; x < i->volume.channels; x++)
|
||||
{
|
||||
pa_volume_t volume = i->volume.values[x];
|
||||
|
||||
if (volume >= PA_VOLUME_NORM)
|
||||
volume = PA_VOLUME_NORM - 1;
|
||||
|
||||
switch (x)
|
||||
{
|
||||
case 0:
|
||||
dwVolumeLeft = (UINT16)volume;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
dwVolumeRight = (UINT16)volume;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pulse->volume = ((UINT32)dwVolumeLeft << 16U) | dwVolumeRight;
|
||||
}
|
||||
|
||||
static void rdpsnd_pulse_context_state_callback(pa_context* context, void* userdata)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(pulse);
|
||||
|
||||
pa_context_state_t state = pa_context_get_state(context);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case PA_CONTEXT_READY:
|
||||
pa_threaded_mainloop_signal(pulse->mainloop, 0);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_FAILED:
|
||||
// Destroy context now, create new one for next connection attempt
|
||||
pa_context_unref(pulse->context);
|
||||
pulse->context = nullptr;
|
||||
if (pulse->reconnect_delay_seconds >= 0)
|
||||
pulse->reconnect_time = time(nullptr) + pulse->reconnect_delay_seconds;
|
||||
pa_threaded_mainloop_signal(pulse->mainloop, 0);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
pa_threaded_mainloop_signal(pulse->mainloop, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_pulse_connect(rdpsndDevicePlugin* device)
|
||||
{
|
||||
BOOL rc = 0;
|
||||
pa_operation* o = nullptr;
|
||||
pa_context_state_t state = PA_CONTEXT_FAILED;
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, FALSE))
|
||||
return FALSE;
|
||||
|
||||
pa_threaded_mainloop_lock(pulse->mainloop);
|
||||
|
||||
if (pa_context_connect(pulse->context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
state = pa_context_get_state(pulse->context);
|
||||
|
||||
if (state == PA_CONTEXT_READY)
|
||||
break;
|
||||
|
||||
if (!PA_CONTEXT_IS_GOOD(state))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_wait(pulse->mainloop);
|
||||
}
|
||||
|
||||
o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse);
|
||||
|
||||
if (o)
|
||||
pa_operation_unref(o);
|
||||
|
||||
if (state == PA_CONTEXT_READY)
|
||||
{
|
||||
rc = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pa_context_disconnect(pulse->context);
|
||||
rc = FALSE;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void rdpsnd_pulse_stream_success_callback(WINPR_ATTR_UNUSED pa_stream* stream,
|
||||
WINPR_ATTR_UNUSED int success, void* userdata)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, TRUE))
|
||||
return;
|
||||
|
||||
pa_threaded_mainloop_signal(pulse->mainloop, 0);
|
||||
}
|
||||
|
||||
static void rdpsnd_pulse_wait_for_operation(rdpsndPulsePlugin* pulse, pa_operation* operation)
|
||||
{
|
||||
if (!rdpsnd_check_pulse(pulse, TRUE))
|
||||
return;
|
||||
|
||||
if (!operation)
|
||||
return;
|
||||
|
||||
while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
|
||||
{
|
||||
pa_threaded_mainloop_wait(pulse->mainloop);
|
||||
}
|
||||
|
||||
pa_operation_unref(operation);
|
||||
}
|
||||
|
||||
static void rdpsnd_pulse_stream_state_callback(pa_stream* stream, void* userdata)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
|
||||
|
||||
WINPR_ASSERT(stream);
|
||||
if (!rdpsnd_check_pulse(pulse, TRUE))
|
||||
return;
|
||||
|
||||
pa_stream_state_t state = pa_stream_get_state(stream);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case PA_STREAM_READY:
|
||||
pa_threaded_mainloop_signal(pulse->mainloop, 0);
|
||||
break;
|
||||
|
||||
case PA_STREAM_FAILED:
|
||||
case PA_STREAM_TERMINATED:
|
||||
// Stream object is about to be destroyed, clean up our pointer
|
||||
pulse->stream = nullptr;
|
||||
pa_threaded_mainloop_signal(pulse->mainloop, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_pulse_stream_request_callback(pa_stream* stream, WINPR_ATTR_UNUSED size_t length,
|
||||
void* userdata)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata;
|
||||
|
||||
WINPR_ASSERT(stream);
|
||||
if (!rdpsnd_check_pulse(pulse, TRUE))
|
||||
return;
|
||||
|
||||
pa_threaded_mainloop_signal(pulse->mainloop, 0);
|
||||
}
|
||||
|
||||
static void rdpsnd_pulse_close(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
|
||||
WINPR_ASSERT(pulse);
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, FALSE))
|
||||
return;
|
||||
|
||||
pa_threaded_mainloop_lock(pulse->mainloop);
|
||||
if (pulse->stream)
|
||||
{
|
||||
rdpsnd_pulse_wait_for_operation(
|
||||
pulse, pa_stream_drain(pulse->stream, rdpsnd_pulse_stream_success_callback, pulse));
|
||||
pa_stream_disconnect(pulse->stream);
|
||||
pa_stream_unref(pulse->stream);
|
||||
pulse->stream = nullptr;
|
||||
}
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_pulse_set_format_spec(rdpsndPulsePlugin* pulse, const AUDIO_FORMAT* format)
|
||||
{
|
||||
WINPR_ASSERT(format);
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, FALSE))
|
||||
return FALSE;
|
||||
|
||||
if (!rdpsnd_pulse_format_supported(&pulse->device, format))
|
||||
return FALSE;
|
||||
|
||||
pa_sample_format_t sformat = PA_SAMPLE_INVALID;
|
||||
switch (format->wFormatTag)
|
||||
{
|
||||
case WAVE_FORMAT_PCM:
|
||||
switch (format->wBitsPerSample)
|
||||
{
|
||||
case 8:
|
||||
sformat = PA_SAMPLE_U8;
|
||||
break;
|
||||
|
||||
case 16:
|
||||
sformat = PA_SAMPLE_S16LE;
|
||||
break;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
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;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_pulse_context_connect(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
WINPR_ASSERT(pulse);
|
||||
|
||||
pulse->context =
|
||||
pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), pulse->client_name);
|
||||
|
||||
if (!pulse->context)
|
||||
return FALSE;
|
||||
|
||||
pa_context_set_state_callback(pulse->context, rdpsnd_pulse_context_state_callback, pulse);
|
||||
|
||||
return rdpsnd_pulse_connect(&pulse->device);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_pulse_open_stream(rdpsndDevicePlugin* device)
|
||||
{
|
||||
pa_stream_state_t state = PA_STREAM_FAILED;
|
||||
int flags = PA_STREAM_NOFLAGS;
|
||||
pa_buffer_attr buffer_attr = WINPR_C_ARRAY_INIT;
|
||||
char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = WINPR_C_ARRAY_INIT;
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
WINPR_ASSERT(pulse);
|
||||
|
||||
if (pa_sample_spec_valid(&pulse->sample_spec) == 0)
|
||||
{
|
||||
pa_sample_spec_snprint(ss, sizeof(ss), &pulse->sample_spec);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(pulse->mainloop);
|
||||
if (!pulse->context)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
if (pulse->reconnect_delay_seconds >= 0 && time(nullptr) - pulse->reconnect_time >= 0)
|
||||
rdpsnd_pulse_context_connect(device);
|
||||
pa_threaded_mainloop_lock(pulse->mainloop);
|
||||
}
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, FALSE))
|
||||
{
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pulse->stream = pa_stream_new(pulse->context, pulse->stream_name, &pulse->sample_spec, nullptr);
|
||||
|
||||
if (!pulse->stream)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* register essential callbacks */
|
||||
pa_stream_set_state_callback(pulse->stream, rdpsnd_pulse_stream_state_callback, pulse);
|
||||
pa_stream_set_write_callback(pulse->stream, rdpsnd_pulse_stream_request_callback, pulse);
|
||||
flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
|
||||
|
||||
if (pulse->latency > 0)
|
||||
{
|
||||
const size_t val = pa_usec_to_bytes(1000ULL * pulse->latency, &pulse->sample_spec);
|
||||
buffer_attr.maxlength = UINT32_MAX;
|
||||
buffer_attr.tlength = (val > UINT32_MAX) ? UINT32_MAX : (UINT32)val;
|
||||
buffer_attr.prebuf = UINT32_MAX;
|
||||
buffer_attr.minreq = UINT32_MAX;
|
||||
buffer_attr.fragsize = UINT32_MAX;
|
||||
flags |= PA_STREAM_ADJUST_LATENCY;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
|
||||
pa_stream_flags_t eflags = (pa_stream_flags_t)flags;
|
||||
if (pa_stream_connect_playback(pulse->stream, pulse->device_name,
|
||||
pulse->latency > 0 ? &buffer_attr : nullptr, eflags, nullptr,
|
||||
nullptr) < 0)
|
||||
{
|
||||
WLog_ERR(TAG, "error connecting playback stream");
|
||||
pa_stream_unref(pulse->stream);
|
||||
pulse->stream = nullptr;
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
while (pulse->stream)
|
||||
{
|
||||
state = pa_stream_get_state(pulse->stream);
|
||||
|
||||
if (state == PA_STREAM_READY)
|
||||
break;
|
||||
|
||||
if (!PA_STREAM_IS_GOOD(state))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_wait(pulse->mainloop);
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
|
||||
if (state == PA_STREAM_READY)
|
||||
return TRUE;
|
||||
|
||||
rdpsnd_pulse_close(device);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_pulse_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
|
||||
UINT32 latency)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
|
||||
WINPR_ASSERT(format);
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, FALSE))
|
||||
return TRUE;
|
||||
|
||||
if (!rdpsnd_pulse_set_format_spec(pulse, format))
|
||||
return FALSE;
|
||||
|
||||
pulse->latency = latency;
|
||||
|
||||
return rdpsnd_pulse_open_stream(device);
|
||||
}
|
||||
|
||||
static void rdpsnd_pulse_free(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
|
||||
if (!pulse)
|
||||
return;
|
||||
|
||||
rdpsnd_pulse_close(device);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_pulse_default_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* desired,
|
||||
AUDIO_FORMAT* defaultFormat)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
|
||||
if (!pulse || !defaultFormat)
|
||||
return FALSE;
|
||||
|
||||
*defaultFormat = *desired;
|
||||
defaultFormat->data = nullptr;
|
||||
defaultFormat->cbSize = 0;
|
||||
defaultFormat->wFormatTag = WAVE_FORMAT_PCM;
|
||||
if ((defaultFormat->nChannels < 1) || (defaultFormat->nChannels > PA_CHANNELS_MAX))
|
||||
defaultFormat->nChannels = 2;
|
||||
if ((defaultFormat->nSamplesPerSec < 1) || (defaultFormat->nSamplesPerSec > PA_RATE_MAX))
|
||||
defaultFormat->nSamplesPerSec = 44100;
|
||||
if ((defaultFormat->wBitsPerSample != 8) && (defaultFormat->wBitsPerSample != 16))
|
||||
defaultFormat->wBitsPerSample = 16;
|
||||
|
||||
defaultFormat->nBlockAlign = defaultFormat->nChannels * defaultFormat->wBitsPerSample / 8;
|
||||
defaultFormat->nAvgBytesPerSec = defaultFormat->nBlockAlign * defaultFormat->nSamplesPerSec;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
|
||||
{
|
||||
WINPR_ASSERT(device);
|
||||
WINPR_ASSERT(format);
|
||||
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static UINT32 rdpsnd_pulse_get_volume(rdpsndDevicePlugin* device)
|
||||
{
|
||||
pa_operation* o = nullptr;
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, FALSE))
|
||||
return 0;
|
||||
|
||||
pa_threaded_mainloop_lock(pulse->mainloop);
|
||||
o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse);
|
||||
if (o)
|
||||
pa_operation_unref(o);
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
return pulse->volume;
|
||||
}
|
||||
|
||||
static void rdpsnd_set_volume_success_cb(pa_context* c, int success, void* userdata)
|
||||
{
|
||||
rdpsndPulsePlugin* pulse = userdata;
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, TRUE))
|
||||
return;
|
||||
WINPR_ASSERT(c);
|
||||
|
||||
WLog_INFO(TAG, "%d", success);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_pulse_set_volume(rdpsndDevicePlugin* device, UINT32 value)
|
||||
{
|
||||
pa_cvolume cv = WINPR_C_ARRAY_INIT;
|
||||
pa_volume_t left = 0;
|
||||
pa_volume_t right = 0;
|
||||
pa_operation* operation = nullptr;
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, TRUE))
|
||||
{
|
||||
WLog_WARN(TAG, "called before pulse backend was initialized");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
left = (pa_volume_t)(value & 0xFFFF);
|
||||
right = (pa_volume_t)((value >> 16) & 0xFFFF);
|
||||
pa_cvolume_init(&cv);
|
||||
cv.channels = 2;
|
||||
cv.values[0] = PA_VOLUME_MUTED + (left * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM;
|
||||
cv.values[1] = PA_VOLUME_MUTED + (right * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM;
|
||||
pa_threaded_mainloop_lock(pulse->mainloop);
|
||||
operation = pa_context_set_sink_input_volume(pulse->context, pa_stream_get_index(pulse->stream),
|
||||
&cv, rdpsnd_set_volume_success_cb, pulse);
|
||||
|
||||
if (operation)
|
||||
pa_operation_unref(operation);
|
||||
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static UINT rdpsnd_pulse_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
|
||||
{
|
||||
size_t length = 0;
|
||||
void* pa_data = nullptr;
|
||||
int status = 0;
|
||||
pa_usec_t latency = 0;
|
||||
int negative = 0;
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device;
|
||||
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
pa_threaded_mainloop_lock(pulse->mainloop);
|
||||
|
||||
if (!rdpsnd_check_pulse(pulse, TRUE))
|
||||
{
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
// Discard this playback request and just attempt to reconnect the stream
|
||||
WLog_DBG(TAG, "reconnecting playback stream");
|
||||
rdpsnd_pulse_open_stream(device);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
length = size;
|
||||
|
||||
status = pa_stream_begin_write(pulse->stream, &pa_data, &length);
|
||||
|
||||
if (status < 0)
|
||||
break;
|
||||
|
||||
memcpy(pa_data, data, length);
|
||||
|
||||
status = pa_stream_write(pulse->stream, pa_data, length, nullptr, 0LL, PA_SEEK_RELATIVE);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
data += length;
|
||||
size -= length;
|
||||
}
|
||||
|
||||
if (pa_stream_get_latency(pulse->stream, &latency, &negative) != 0)
|
||||
latency = 0;
|
||||
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
|
||||
const pa_usec_t val = latency / 1000;
|
||||
if (val > UINT32_MAX)
|
||||
return UINT32_MAX;
|
||||
return (UINT32)val;
|
||||
}
|
||||
|
||||
static UINT rdpsnd_pulse_parse_addin_args(rdpsndPulsePlugin* pulse, const ADDIN_ARGV* args)
|
||||
{
|
||||
COMMAND_LINE_ARGUMENT_A rdpsnd_pulse_args[] = {
|
||||
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr, "device" },
|
||||
{ "reconnect_delay_seconds", COMMAND_LINE_VALUE_REQUIRED, "<reconnect_delay_seconds>",
|
||||
nullptr, nullptr, -1, nullptr, "reconnect_delay_seconds" },
|
||||
{ "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;
|
||||
|
||||
WINPR_ASSERT(pulse);
|
||||
WINPR_ASSERT(args);
|
||||
|
||||
const int status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_pulse_args, flags,
|
||||
pulse, nullptr, nullptr);
|
||||
|
||||
if (status < 0)
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
const COMMAND_LINE_ARGUMENT_A* arg = rdpsnd_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)
|
||||
return ERROR_OUTOFMEMORY;
|
||||
}
|
||||
CommandLineSwitchCase(arg, "reconnect_delay_seconds")
|
||||
{
|
||||
unsigned long val = strtoul(arg->Value, nullptr, 0);
|
||||
|
||||
if ((errno != 0) || (val > INT32_MAX))
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
pulse->reconnect_delay_seconds = (time_t)val;
|
||||
}
|
||||
CommandLineSwitchCase(arg, "client_name")
|
||||
{
|
||||
client_name = arg->Value;
|
||||
}
|
||||
CommandLineSwitchCase(arg, "stream_name")
|
||||
{
|
||||
stream_name = arg->Value;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
WINPR_ASSERT(pEntryPoints);
|
||||
|
||||
rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)calloc(1, sizeof(rdpsndPulsePlugin));
|
||||
|
||||
if (!pulse)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
pulse->device.Open = rdpsnd_pulse_open;
|
||||
pulse->device.FormatSupported = rdpsnd_pulse_format_supported;
|
||||
pulse->device.GetVolume = rdpsnd_pulse_get_volume;
|
||||
pulse->device.SetVolume = rdpsnd_pulse_set_volume;
|
||||
pulse->device.Play = rdpsnd_pulse_play;
|
||||
pulse->device.Close = rdpsnd_pulse_close;
|
||||
pulse->device.Free = rdpsnd_pulse_free;
|
||||
pulse->device.DefaultFormat = rdpsnd_pulse_default_format;
|
||||
|
||||
const ADDIN_ARGV* args = pEntryPoints->args;
|
||||
UINT ret = rdpsnd_pulse_parse_addin_args(pulse, args);
|
||||
|
||||
if (ret != CHANNEL_RC_OK)
|
||||
{
|
||||
WLog_ERR(TAG, "error parsing arguments");
|
||||
goto error;
|
||||
}
|
||||
|
||||
pulse->reconnect_delay_seconds = 5;
|
||||
pulse->reconnect_time = time(nullptr);
|
||||
|
||||
ret = CHANNEL_RC_NO_MEMORY;
|
||||
pulse->mainloop = pa_threaded_mainloop_new();
|
||||
|
||||
if (!pulse->mainloop)
|
||||
goto error;
|
||||
|
||||
pa_threaded_mainloop_lock(pulse->mainloop);
|
||||
|
||||
if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
goto error;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
|
||||
if (!rdpsnd_pulse_context_connect((rdpsndDevicePlugin*)pulse))
|
||||
goto error;
|
||||
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)pulse);
|
||||
return CHANNEL_RC_OK;
|
||||
error:
|
||||
rdpsnd_pulse_free((rdpsndDevicePlugin*)pulse);
|
||||
return ret;
|
||||
}
|
||||
1882
third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.c
vendored
Normal file
1882
third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
41
third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.h
vendored
Normal file
41
third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.h
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* Copyright 2010-2011 Vic Lee
|
||||
* Copyright 2012-2013 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.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H
|
||||
#define FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H
|
||||
|
||||
#include <freerdp/api.h>
|
||||
#include <freerdp/svc.h>
|
||||
#include <freerdp/addin.h>
|
||||
#include <freerdp/client/rdpsnd.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
#define TAG CHANNELS_TAG("rdpsnd.client")
|
||||
|
||||
#if defined(WITH_DEBUG_SND)
|
||||
#define DEBUG_SND(...) WLog_DBG(TAG, __VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_SND(...) \
|
||||
do \
|
||||
{ \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H */
|
||||
30
third_party/FreeRDP/channels/rdpsnd/client/sndio/CMakeLists.txt
vendored
Normal file
30
third_party/FreeRDP/channels/rdpsnd/client/sndio/CMakeLists.txt
vendored
Normal 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("rdpsnd" "sndio" "")
|
||||
|
||||
find_package(SNDIO REQUIRED)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_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 "")
|
||||
218
third_party/FreeRDP/channels/rdpsnd/client/sndio/rdpsnd_sndio.c
vendored
Normal file
218
third_party/FreeRDP/channels/rdpsnd/client/sndio/rdpsnd_sndio.c
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* Copyright 2019 Armin Novak <armin.novak@thincast.com>
|
||||
* Copyright 2019 Thincast Technologies GmbH
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/stream.h>
|
||||
#include <winpr/cmdline.h>
|
||||
|
||||
#include <freerdp/types.h>
|
||||
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
|
||||
struct sio_hdl* hdl;
|
||||
struct sio_par par;
|
||||
} rdpsndSndioPlugin;
|
||||
|
||||
static BOOL rdpsnd_sndio_open(rdpsndDevicePlugin* device, AUDIO_FORMAT* format, int latency)
|
||||
{
|
||||
rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
|
||||
|
||||
if (device == nullptr || format == nullptr)
|
||||
return FALSE;
|
||||
|
||||
if (sndio->hdl != nullptr)
|
||||
return TRUE;
|
||||
|
||||
sndio->hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
|
||||
if (sndio->hdl == nullptr)
|
||||
{
|
||||
WLog_ERR(TAG, "could not open audio device");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
sio_initpar(&sndio->par);
|
||||
sndio->par.bits = format->wBitsPerSample;
|
||||
sndio->par.pchan = format->nChannels;
|
||||
sndio->par.rate = format->nSamplesPerSec;
|
||||
if (!sio_setpar(sndio->hdl, &sndio->par))
|
||||
{
|
||||
WLog_ERR(TAG, "could not set audio parameters");
|
||||
return FALSE;
|
||||
}
|
||||
if (!sio_getpar(sndio->hdl, &sndio->par))
|
||||
{
|
||||
WLog_ERR(TAG, "could not get audio parameters");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!sio_start(sndio->hdl))
|
||||
{
|
||||
WLog_ERR(TAG, "could not start audio device");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void rdpsnd_sndio_close(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
|
||||
|
||||
if (device == nullptr)
|
||||
return;
|
||||
|
||||
if (sndio->hdl != nullptr)
|
||||
{
|
||||
sio_stop(sndio->hdl);
|
||||
sio_close(sndio->hdl);
|
||||
sndio->hdl = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_sndio_set_volume(rdpsndDevicePlugin* device, UINT32 value)
|
||||
{
|
||||
rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
|
||||
|
||||
if (device == nullptr || sndio->hdl == nullptr)
|
||||
return FALSE;
|
||||
|
||||
/*
|
||||
* Low-order word contains the left-channel volume setting.
|
||||
* We ignore the right-channel volume setting in the high-order word.
|
||||
*/
|
||||
return sio_setvol(sndio->hdl, ((value & 0xFFFF) * SIO_MAXVOL) / 0xFFFF);
|
||||
}
|
||||
|
||||
static void rdpsnd_sndio_free(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
|
||||
|
||||
if (device == nullptr)
|
||||
return;
|
||||
|
||||
rdpsnd_sndio_close(device);
|
||||
free(sndio);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_sndio_format_supported(rdpsndDevicePlugin* device, AUDIO_FORMAT* format)
|
||||
{
|
||||
if (format == nullptr)
|
||||
return FALSE;
|
||||
|
||||
return (format->wFormatTag == WAVE_FORMAT_PCM);
|
||||
}
|
||||
|
||||
static void rdpsnd_sndio_play(rdpsndDevicePlugin* device, BYTE* data, int size)
|
||||
{
|
||||
rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
|
||||
|
||||
if (device == nullptr || sndio->hdl == nullptr)
|
||||
return;
|
||||
|
||||
sio_write(sndio->hdl, data, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT rdpsnd_sndio_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args)
|
||||
{
|
||||
int status;
|
||||
DWORD flags;
|
||||
COMMAND_LINE_ARGUMENT_A* arg;
|
||||
rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device;
|
||||
COMMAND_LINE_ARGUMENT_A rdpsnd_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, rdpsnd_sndio_args,
|
||||
flags, sndio, nullptr, nullptr);
|
||||
|
||||
if (status < 0)
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
arg = rdpsnd_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_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
ADDIN_ARGV* args;
|
||||
rdpsndSndioPlugin* sndio;
|
||||
UINT ret = CHANNEL_RC_OK;
|
||||
sndio = (rdpsndSndioPlugin*)calloc(1, sizeof(rdpsndSndioPlugin));
|
||||
|
||||
if (sndio == nullptr)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
sndio->device.Open = rdpsnd_sndio_open;
|
||||
sndio->device.FormatSupported = rdpsnd_sndio_format_supported;
|
||||
sndio->device.SetVolume = rdpsnd_sndio_set_volume;
|
||||
sndio->device.Play = rdpsnd_sndio_play;
|
||||
sndio->device.Close = rdpsnd_sndio_close;
|
||||
sndio->device.Free = rdpsnd_sndio_free;
|
||||
args = pEntryPoints->args;
|
||||
|
||||
if (args->argc > 1)
|
||||
{
|
||||
ret = rdpsnd_sndio_parse_addin_args((rdpsndDevicePlugin*)sndio, args);
|
||||
|
||||
if (ret != CHANNEL_RC_OK)
|
||||
{
|
||||
WLog_ERR(TAG, "error parsing arguments");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &sndio->device);
|
||||
return ret;
|
||||
error:
|
||||
rdpsnd_sndio_free(&sndio->device);
|
||||
return ret;
|
||||
}
|
||||
26
third_party/FreeRDP/channels/rdpsnd/client/winmm/CMakeLists.txt
vendored
Normal file
26
third_party/FreeRDP/channels/rdpsnd/client/winmm/CMakeLists.txt
vendored
Normal 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("rdpsnd" "winmm" "")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_winmm.c)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS winpr freerdp winmm.lib)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
348
third_party/FreeRDP/channels/rdpsnd/client/winmm/rdpsnd_winmm.c
vendored
Normal file
348
third_party/FreeRDP/channels/rdpsnd/client/winmm/rdpsnd_winmm.c
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* Copyright 2009-2012 Jay Sorg
|
||||
* Copyright 2010-2012 Vic Lee
|
||||
* Copyright 2015 Thincast Technologies GmbH
|
||||
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
|
||||
* Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.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 <winpr/sysinfo.h>
|
||||
|
||||
#include <freerdp/types.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
|
||||
HWAVEOUT hWaveOut;
|
||||
WAVEFORMATEX format;
|
||||
UINT32 volume;
|
||||
wLog* log;
|
||||
UINT32 latency;
|
||||
HANDLE hThread;
|
||||
DWORD threadId;
|
||||
CRITICAL_SECTION cs;
|
||||
} rdpsndWinmmPlugin;
|
||||
|
||||
static BOOL rdpsnd_winmm_convert_format(const AUDIO_FORMAT* in, WAVEFORMATEX* out)
|
||||
{
|
||||
if (!in || !out)
|
||||
return FALSE;
|
||||
|
||||
ZeroMemory(out, sizeof(WAVEFORMATEX));
|
||||
out->wFormatTag = WAVE_FORMAT_PCM;
|
||||
out->nChannels = in->nChannels;
|
||||
out->nSamplesPerSec = in->nSamplesPerSec;
|
||||
|
||||
switch (in->wFormatTag)
|
||||
{
|
||||
case WAVE_FORMAT_PCM:
|
||||
out->wBitsPerSample = in->wBitsPerSample;
|
||||
break;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
out->nBlockAlign = out->nChannels * out->wBitsPerSample / 8;
|
||||
out->nAvgBytesPerSec = out->nSamplesPerSec * out->nBlockAlign;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_winmm_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
|
||||
UINT32 latency)
|
||||
{
|
||||
rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
|
||||
|
||||
winmm->latency = latency;
|
||||
if (!rdpsnd_winmm_convert_format(format, &winmm->format))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static DWORD WINAPI waveOutProc(LPVOID lpParameter)
|
||||
{
|
||||
MSG msg;
|
||||
rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)lpParameter;
|
||||
while (GetMessage(&msg, nullptr, 0, 0))
|
||||
{
|
||||
if (msg.message == MM_WOM_CLOSE)
|
||||
{
|
||||
/* device was closed - exit thread */
|
||||
break;
|
||||
}
|
||||
else if (msg.message == MM_WOM_DONE)
|
||||
{
|
||||
/* free buffer */
|
||||
LPWAVEHDR waveHdr = (LPWAVEHDR)msg.lParam;
|
||||
EnterCriticalSection(&winmm->cs);
|
||||
waveOutUnprepareHeader((HWAVEOUT)msg.wParam, waveHdr, sizeof(WAVEHDR));
|
||||
LeaveCriticalSection(&winmm->cs);
|
||||
free(waveHdr->lpData);
|
||||
free(waveHdr);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_winmm_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
|
||||
UINT32 latency)
|
||||
{
|
||||
MMRESULT mmResult;
|
||||
rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
|
||||
|
||||
if (winmm->hWaveOut)
|
||||
return TRUE;
|
||||
|
||||
if (!rdpsnd_winmm_set_format(device, format, latency))
|
||||
return FALSE;
|
||||
|
||||
winmm->hThread = CreateThread(nullptr, 0, waveOutProc, winmm, 0, &winmm->threadId);
|
||||
if (!winmm->hThread)
|
||||
{
|
||||
WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed: %" PRIu32 "", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
mmResult = waveOutOpen(&winmm->hWaveOut, WAVE_MAPPER, &winmm->format,
|
||||
(DWORD_PTR)winmm->threadId, 0, CALLBACK_THREAD);
|
||||
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
{
|
||||
WLog_Print(winmm->log, WLOG_ERROR, "waveOutOpen failed: %" PRIu32 "", mmResult);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
mmResult = waveOutSetVolume(winmm->hWaveOut, winmm->volume);
|
||||
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
{
|
||||
WLog_Print(winmm->log, WLOG_ERROR, "waveOutSetVolume failed: %" PRIu32 "", mmResult);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void rdpsnd_winmm_close(rdpsndDevicePlugin* device)
|
||||
{
|
||||
MMRESULT mmResult;
|
||||
rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
|
||||
|
||||
if (winmm->hWaveOut)
|
||||
{
|
||||
EnterCriticalSection(&winmm->cs);
|
||||
|
||||
mmResult = waveOutReset(winmm->hWaveOut);
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
WLog_Print(winmm->log, WLOG_ERROR, "waveOutReset failure: %" PRIu32 "", mmResult);
|
||||
|
||||
mmResult = waveOutClose(winmm->hWaveOut);
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
WLog_Print(winmm->log, WLOG_ERROR, "waveOutClose failure: %" PRIu32 "", mmResult);
|
||||
|
||||
LeaveCriticalSection(&winmm->cs);
|
||||
|
||||
winmm->hWaveOut = nullptr;
|
||||
}
|
||||
|
||||
if (winmm->hThread)
|
||||
{
|
||||
(void)WaitForSingleObject(winmm->hThread, INFINITE);
|
||||
(void)CloseHandle(winmm->hThread);
|
||||
winmm->hThread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_winmm_free(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
|
||||
|
||||
if (winmm)
|
||||
{
|
||||
rdpsnd_winmm_close(device);
|
||||
DeleteCriticalSection(&winmm->cs);
|
||||
free(winmm);
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_winmm_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
|
||||
{
|
||||
MMRESULT result;
|
||||
WAVEFORMATEX out;
|
||||
|
||||
WINPR_UNUSED(device);
|
||||
if (rdpsnd_winmm_convert_format(format, &out))
|
||||
{
|
||||
result = waveOutOpen(nullptr, WAVE_MAPPER, &out, 0, 0, WAVE_FORMAT_QUERY);
|
||||
|
||||
if (result == MMSYSERR_NOERROR)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static UINT32 rdpsnd_winmm_get_volume(rdpsndDevicePlugin* device)
|
||||
{
|
||||
MMRESULT mmResult;
|
||||
DWORD dwVolume = UINT32_MAX;
|
||||
rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
|
||||
|
||||
if (!winmm->hWaveOut)
|
||||
return dwVolume;
|
||||
|
||||
mmResult = waveOutGetVolume(winmm->hWaveOut, &dwVolume);
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
{
|
||||
WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
|
||||
dwVolume = UINT32_MAX;
|
||||
}
|
||||
return dwVolume;
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_winmm_set_volume(rdpsndDevicePlugin* device, UINT32 value)
|
||||
{
|
||||
MMRESULT mmResult;
|
||||
rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
|
||||
winmm->volume = value;
|
||||
|
||||
if (!winmm->hWaveOut)
|
||||
return TRUE;
|
||||
|
||||
mmResult = waveOutSetVolume(winmm->hWaveOut, value);
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
{
|
||||
WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static UINT rdpsnd_winmm_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
|
||||
{
|
||||
MMRESULT mmResult;
|
||||
LPWAVEHDR lpWaveHdr;
|
||||
rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device;
|
||||
|
||||
if (!winmm->hWaveOut)
|
||||
return 0;
|
||||
|
||||
if (size > UINT32_MAX)
|
||||
return 0;
|
||||
|
||||
lpWaveHdr = (LPWAVEHDR)calloc(1, sizeof(WAVEHDR));
|
||||
if (!lpWaveHdr)
|
||||
return 0;
|
||||
|
||||
lpWaveHdr->dwFlags = 0;
|
||||
lpWaveHdr->dwLoops = 0;
|
||||
lpWaveHdr->lpData = malloc(size);
|
||||
if (!lpWaveHdr->lpData)
|
||||
goto fail;
|
||||
memcpy(lpWaveHdr->lpData, data, size);
|
||||
lpWaveHdr->dwBufferLength = (DWORD)size;
|
||||
|
||||
EnterCriticalSection(&winmm->cs);
|
||||
|
||||
mmResult = waveOutPrepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
{
|
||||
WLog_Print(winmm->log, WLOG_ERROR, "waveOutPrepareHeader failure: %" PRIu32 "", mmResult);
|
||||
goto failCS;
|
||||
}
|
||||
|
||||
mmResult = waveOutWrite(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
{
|
||||
WLog_Print(winmm->log, WLOG_ERROR, "waveOutWrite failure: %" PRIu32 "", mmResult);
|
||||
waveOutUnprepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
|
||||
goto failCS;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&winmm->cs);
|
||||
return winmm->latency;
|
||||
failCS:
|
||||
LeaveCriticalSection(&winmm->cs);
|
||||
fail:
|
||||
if (lpWaveHdr)
|
||||
free(lpWaveHdr->lpData);
|
||||
free(lpWaveHdr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
|
||||
{
|
||||
WINPR_UNUSED(device);
|
||||
WINPR_UNUSED(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE winmm_freerdp_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
const ADDIN_ARGV* args;
|
||||
rdpsndWinmmPlugin* winmm;
|
||||
|
||||
if (waveOutGetNumDevs() == 0)
|
||||
{
|
||||
WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No sound playback device available!");
|
||||
return ERROR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
winmm = (rdpsndWinmmPlugin*)calloc(1, sizeof(rdpsndWinmmPlugin));
|
||||
if (!winmm)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
winmm->device.Open = rdpsnd_winmm_open;
|
||||
winmm->device.FormatSupported = rdpsnd_winmm_format_supported;
|
||||
winmm->device.GetVolume = rdpsnd_winmm_get_volume;
|
||||
winmm->device.SetVolume = rdpsnd_winmm_set_volume;
|
||||
winmm->device.Play = rdpsnd_winmm_play;
|
||||
winmm->device.Close = rdpsnd_winmm_close;
|
||||
winmm->device.Free = rdpsnd_winmm_free;
|
||||
winmm->log = WLog_Get(TAG);
|
||||
InitializeCriticalSection(&winmm->cs);
|
||||
|
||||
args = pEntryPoints->args;
|
||||
rdpsnd_winmm_parse_addin_args(&winmm->device, args);
|
||||
winmm->volume = 0xFFFFFFFF;
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)winmm);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
26
third_party/FreeRDP/channels/rdpsnd/common/CMakeLists.txt
vendored
Normal file
26
third_party/FreeRDP/channels/rdpsnd/common/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||
# FreeRDP cmake build script
|
||||
#
|
||||
# Copyright 2018 Armin Novak <armin.novak@thincast.com>
|
||||
# Copyright 2018 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.
|
||||
|
||||
set(SRCS rdpsnd_common.h rdpsnd_common.c)
|
||||
|
||||
# Library currently header only
|
||||
add_library(rdpsnd-common STATIC ${SRCS})
|
||||
set_property(TARGET rdpsnd-common PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Common")
|
||||
|
||||
freerdp_client_pc_add_library_private(rdpsnd-common)
|
||||
channel_install(rdpsnd-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets")
|
||||
21
third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.c
vendored
Normal file
21
third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.c
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Server Audio Virtual Channel
|
||||
*
|
||||
* Copyright 2018 Armin Novak <armin.novak@thincast.com>
|
||||
* Copyright 2018 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 "rdpsnd_common.h"
|
||||
43
third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.h
vendored
Normal file
43
third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.h
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Server Audio Virtual Channel
|
||||
*
|
||||
* Copyright 2018 Armin Novak <armin.novak@thincast.com>
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H
|
||||
#define FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/thread.h>
|
||||
|
||||
#include <freerdp/codec/dsp.h>
|
||||
#include <freerdp/channels/wtsvc.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
#include <freerdp/server/rdpsnd.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CHANNEL_VERSION_WIN_XP = 0x02,
|
||||
CHANNEL_VERSION_WIN_XP_SP1 = 0x05,
|
||||
CHANNEL_VERSION_WIN_VISTA = 0x05,
|
||||
CHANNEL_VERSION_WIN_7 = 0x06,
|
||||
CHANNEL_VERSION_WIN_8 = 0x08,
|
||||
CHANNEL_VERSION_WIN_MAX = CHANNEL_VERSION_WIN_8
|
||||
} RdpSndChannelVersion;
|
||||
|
||||
#endif /* FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H */
|
||||
24
third_party/FreeRDP/channels/rdpsnd/server/CMakeLists.txt
vendored
Normal file
24
third_party/FreeRDP/channels/rdpsnd/server/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# 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("rdpsnd")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS rdpsnd_main.c rdpsnd_main.h)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS rdpsnd-common)
|
||||
|
||||
add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
|
||||
1245
third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.c
vendored
Normal file
1245
third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
59
third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.h
vendored
Normal file
59
third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.h
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Server Audio Virtual Channel
|
||||
*
|
||||
* Copyright 2012 Vic Lee
|
||||
* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H
|
||||
#define FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/thread.h>
|
||||
|
||||
#include <freerdp/codec/dsp.h>
|
||||
#include <freerdp/channels/wtsvc.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
#include <freerdp/server/rdpsnd.h>
|
||||
|
||||
#define TAG CHANNELS_TAG("rdpsnd.server")
|
||||
|
||||
struct s_rdpsnd_server_private
|
||||
{
|
||||
BOOL ownThread;
|
||||
HANDLE Thread;
|
||||
HANDLE StopEvent;
|
||||
HANDLE channelEvent;
|
||||
void* ChannelHandle;
|
||||
DWORD SessionId;
|
||||
|
||||
BOOL waitingHeader;
|
||||
DWORD expectedBytes;
|
||||
BYTE msgType;
|
||||
wStream* input_stream;
|
||||
wStream* rdpsnd_pdu;
|
||||
BYTE* out_buffer;
|
||||
size_t out_buffer_size;
|
||||
size_t out_frames;
|
||||
size_t out_pending_frames;
|
||||
UINT32 src_bytes_per_sample;
|
||||
UINT32 src_bytes_per_frame;
|
||||
FREERDP_DSP_CONTEXT* dsp_context;
|
||||
CRITICAL_SECTION lock; /* Protect out_buffer and related parameters */
|
||||
};
|
||||
|
||||
#endif /* FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H */
|
||||
Reference in New Issue
Block a user