Merge pull request #1514 from akallabeth/android_sound
Android sound support
This commit is contained in:
commit
d7a9add3b4
@ -213,7 +213,6 @@ endif()
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DWINPR_EXPORTS")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DFREERDP_EXPORTS")
|
||||
|
||||
# Include files
|
||||
if(NOT IOS)
|
||||
check_include_files(fcntl.h HAVE_FCNTL_H)
|
||||
@ -271,6 +270,11 @@ endif(APPLE)
|
||||
|
||||
# Android
|
||||
if(ANDROID)
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
add_definitions(-DNDK_DEBUG=1)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG}")
|
||||
endif()
|
||||
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -llog")
|
||||
if (NOT FREERDP_ANDROID_EXTERNAL_SSL_PATH)
|
||||
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external/openssl")
|
||||
set(FREERDP_ANDROID_EXTERNAL_SSL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/external/openssl")
|
||||
@ -290,7 +294,8 @@ if(ANDROID)
|
||||
endif()
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${FREERDP_ANDROID_EXTERNAL_SSL_PATH})
|
||||
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/client/Android/FreeRDPCore/libs/${ANDROID_ABI})
|
||||
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/scripts/regenerate_jni_headers.sh.cmake ${CMAKE_CURRENT_SOURCE_DIR}/scripts/regenerate_jni_headers.sh @ONLY)
|
||||
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/scripts/regenerate_jni_headers.sh.cmake
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/scripts/regenerate_jni_headers.sh @ONLY)
|
||||
endif()
|
||||
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
@ -323,6 +328,10 @@ set(OPENSSL_FEATURE_TYPE "REQUIRED")
|
||||
set(OPENSSL_FEATURE_PURPOSE "cryptography")
|
||||
set(OPENSSL_FEATURE_DESCRIPTION "encryption, certificate validation, hashing functions")
|
||||
|
||||
set(OPENSLES_FEATURE_TYPE "OPTIONAL")
|
||||
set(OPENSLES_FEATURE_PURPOSE "multimedia")
|
||||
set(OPENSLES_FEATURE_DESCRIPTION "OpenSLES audio / video")
|
||||
|
||||
set(ALSA_FEATURE_TYPE "RECOMMENDED")
|
||||
set(ALSA_FEATURE_PURPOSE "sound")
|
||||
set(ALSA_FEATURE_DESCRIPTION "audio input, audio output and multimedia redirection")
|
||||
@ -369,6 +378,7 @@ if(WIN32)
|
||||
set(PCSC_FEATURE_TYPE "DISABLED")
|
||||
set(FFMPEG_FEATURE_TYPE "DISABLED")
|
||||
set(GSTREAMER_FEATURE_TYPE "DISABLED")
|
||||
set(OPENSLES_FEATURE_TYPE "DISABLED")
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
@ -384,6 +394,7 @@ if(APPLE)
|
||||
set(PCSC_FEATURE_TYPE "DISABLED")
|
||||
set(GSTREAMER_FEATURE_TYPE "DISABLED")
|
||||
endif()
|
||||
set(OPENSLES_FEATURE_TYPE "DISABLED")
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
@ -395,6 +406,7 @@ if(ANDROID)
|
||||
set(PCSC_FEATURE_TYPE "DISABLED")
|
||||
set(FFMPEG_FEATURE_TYPE "DISABLED")
|
||||
set(GSTREAMER_FEATURE_TYPE "DISABLED")
|
||||
set(OPENSLES_FEATURE_TYPE "REQUIRED")
|
||||
endif()
|
||||
|
||||
|
||||
@ -408,6 +420,7 @@ endif()
|
||||
|
||||
find_feature(ZLIB ${ZLIB_FEATURE_TYPE} ${ZLIB_FEATURE_PURPOSE} ${ZLIB_FEATURE_DESCRIPTION})
|
||||
find_feature(OpenSSL ${OPENSSL_FEATURE_TYPE} ${OPENSSL_FEATURE_PURPOSE} ${OPENSSL_FEATURE_DESCRIPTION})
|
||||
find_feature(OpenSLES ${OPENSLES_FEATURE_TYPE} ${OPENSLES_FEATURE_PURPOSE} ${OPENSLES_FEATURE_DESCRIPTION})
|
||||
|
||||
find_feature(ALSA ${ALSA_FEATURE_TYPE} ${ALSA_FEATURE_PURPOSE} ${ALSA_FEATURE_DESCRIPTION})
|
||||
find_feature(Pulse ${PULSE_FEATURE_TYPE} ${PULSE_FEATURE_PURPOSE} ${PULSE_FEATURE_DESCRIPTION})
|
||||
|
@ -47,3 +47,7 @@ endif()
|
||||
if(WITH_PULSE)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "")
|
||||
endif()
|
||||
|
||||
if(WITH_OPENSLES)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "")
|
||||
endif()
|
||||
|
@ -165,7 +165,12 @@ static BOOL audin_alsa_thread_receive(AudinALSADevice* alsa, BYTE* src, int size
|
||||
if (WaitForSingleObject(alsa->stopEvent, 0) == WAIT_OBJECT_0)
|
||||
break;
|
||||
else
|
||||
{
|
||||
DEBUG_DVC("encoded %d [%d] to %d [%X]", alsa->buffer_frames,
|
||||
tbytes_per_frame, encoded_size,
|
||||
alsa->wformat);
|
||||
ret = alsa->receive(encoded_data, encoded_size, alsa->user_data);
|
||||
}
|
||||
|
||||
alsa->buffer_frames = 0;
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -554,8 +555,10 @@ int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
|
||||
ADDIN_ARGV* args;
|
||||
AUDIN_PLUGIN* audin;
|
||||
|
||||
audin = (AUDIN_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "audin");
|
||||
assert(pEntryPoints);
|
||||
assert(pEntryPoints->GetPlugin);
|
||||
|
||||
audin = (AUDIN_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "audin");
|
||||
if (audin == NULL)
|
||||
{
|
||||
audin = (AUDIN_PLUGIN*) malloc(sizeof(AUDIN_PLUGIN));
|
||||
@ -577,19 +580,32 @@ int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
|
||||
if (audin->subsystem)
|
||||
audin_load_device_plugin((IWTSPlugin*) audin, audin->subsystem, args);
|
||||
|
||||
#if defined(WITH_PULSE)
|
||||
if (!audin->device)
|
||||
{
|
||||
audin_set_subsystem(audin, "pulse");
|
||||
audin_set_device_name(audin, "");
|
||||
audin_load_device_plugin((IWTSPlugin*) audin, audin->subsystem, args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WITH_ALSA)
|
||||
if (!audin->device)
|
||||
{
|
||||
audin_set_subsystem(audin, "alsa");
|
||||
audin_set_device_name(audin, "default");
|
||||
audin_load_device_plugin((IWTSPlugin*) audin, audin->subsystem, args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WITH_OPENSLES)
|
||||
if (!audin->device)
|
||||
{
|
||||
audin_set_subsystem(audin, "opensles");
|
||||
audin_set_device_name(audin, "default");
|
||||
audin_load_device_plugin((IWTSPlugin*) audin, audin->subsystem, args);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (audin->device == NULL)
|
||||
{
|
||||
|
44
channels/audin/client/opensles/CMakeLists.txt
Normal file
44
channels/audin/client/opensles/CMakeLists.txt
Normal file
@ -0,0 +1,44 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||
# FreeRDP cmake build script
|
||||
#
|
||||
# Copyright 2013 Armin Novak <armin.novak@gmail.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
define_channel_client_subsystem("audin" "opensles" "")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
opensl_io.c
|
||||
audin_opensl_es.c)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(${OPENSLES_INCLUDE_DIRS})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
|
||||
set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE freerdp
|
||||
MODULES freerdp-codec freerdp-utils
|
||||
${OPENSLES_LIBRARIES}
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${OPENSLES_LIBRARIES})
|
||||
|
||||
target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
|
||||
|
||||
if(NOT STATIC_CHANNELS)
|
||||
install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_ADDIN_PATH})
|
||||
endif()
|
411
channels/audin/client/opensles/audin_opensl_es.c
Normal file
411
channels/audin/client/opensles/audin_opensl_es.c
Normal file
@ -0,0 +1,411 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Input Redirection Virtual Channel - OpenSL ES implementation
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/thread.h>
|
||||
#include <winpr/cmdline.h>
|
||||
|
||||
#include <freerdp/addin.h>
|
||||
#include <freerdp/codec/dsp.h>
|
||||
#include <freerdp/channels/rdpsnd.h>
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
|
||||
#include "audin_main.h"
|
||||
#include "opensl_io.h"
|
||||
|
||||
typedef struct _AudinOpenSLESDevice
|
||||
{
|
||||
IAudinDevice iface;
|
||||
|
||||
char* device_name;
|
||||
OPENSL_STREAM *stream;
|
||||
|
||||
UINT32 frames_per_packet;
|
||||
UINT32 rate;
|
||||
UINT32 channels;
|
||||
|
||||
UINT32 bytes_per_channel;
|
||||
|
||||
UINT32 format;
|
||||
UINT32 block_size;
|
||||
|
||||
FREERDP_DSP_CONTEXT* dsp_context;
|
||||
AudinReceive receive;
|
||||
|
||||
HANDLE thread;
|
||||
HANDLE stopEvent;
|
||||
|
||||
void* user_data;
|
||||
} AudinOpenSLESDevice;
|
||||
|
||||
static void* audin_opensles_thread_func(void* arg)
|
||||
{
|
||||
union
|
||||
{
|
||||
void *v;
|
||||
short* s;
|
||||
BYTE *b;
|
||||
} buffer;
|
||||
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) arg;
|
||||
const size_t raw_size = opensles->frames_per_packet * opensles->bytes_per_channel;
|
||||
|
||||
DEBUG_DVC("opensles=%p", opensles);
|
||||
|
||||
assert(opensles);
|
||||
assert(opensles->frames_per_packet > 0);
|
||||
assert(opensles->dsp_context);
|
||||
assert(opensles->stopEvent);
|
||||
assert(opensles->stream);
|
||||
|
||||
buffer.v = malloc(raw_size);
|
||||
ZeroMemory(buffer.v, raw_size);
|
||||
freerdp_dsp_context_reset_adpcm(opensles->dsp_context);
|
||||
|
||||
while (!(WaitForSingleObject(opensles->stopEvent, 0) == WAIT_OBJECT_0))
|
||||
{
|
||||
size_t encoded_size;
|
||||
void *encoded_data;
|
||||
|
||||
int rc = android_RecIn(opensles->stream, buffer.s, raw_size);
|
||||
if (rc < 0)
|
||||
{
|
||||
DEBUG_WARN("android_RecIn %d", rc);
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(rc == raw_size);
|
||||
if (opensles->format == WAVE_FORMAT_ADPCM)
|
||||
{
|
||||
opensles->dsp_context->encode_ms_adpcm(opensles->dsp_context,
|
||||
buffer.b, rc, opensles->channels, opensles->block_size);
|
||||
|
||||
encoded_data = opensles->dsp_context->adpcm_buffer;
|
||||
encoded_size = opensles->dsp_context->adpcm_size;
|
||||
}
|
||||
else if (opensles->format == WAVE_FORMAT_DVI_ADPCM)
|
||||
{
|
||||
opensles->dsp_context->encode_ima_adpcm(opensles->dsp_context,
|
||||
buffer.b, rc,
|
||||
opensles->channels, opensles->block_size);
|
||||
|
||||
encoded_data = opensles->dsp_context->adpcm_buffer;
|
||||
encoded_size = opensles->dsp_context->adpcm_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
encoded_data = buffer.v;
|
||||
encoded_size = rc;
|
||||
}
|
||||
|
||||
rc = opensles->receive(encoded_data, encoded_size, opensles->user_data);
|
||||
if (!rc)
|
||||
break;
|
||||
}
|
||||
|
||||
free(buffer.v);
|
||||
|
||||
DEBUG_DVC("thread shutdown.");
|
||||
|
||||
ExitThread(0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void audin_opensles_free(IAudinDevice* device)
|
||||
{
|
||||
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device;
|
||||
|
||||
DEBUG_DVC("device=%p", device);
|
||||
|
||||
/* The function may have been called out of order,
|
||||
* ignore duplicate requests. */
|
||||
if (!opensles)
|
||||
return;
|
||||
|
||||
assert(opensles);
|
||||
assert(opensles->dsp_context);
|
||||
assert(!opensles->stream);
|
||||
|
||||
freerdp_dsp_context_free(opensles->dsp_context);
|
||||
|
||||
if (opensles->device_name)
|
||||
free(opensles->device_name);
|
||||
|
||||
free(opensles);
|
||||
}
|
||||
|
||||
static BOOL audin_opensles_format_supported(IAudinDevice* device, audinFormat* format)
|
||||
{
|
||||
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device;
|
||||
|
||||
DEBUG_DVC("device=%p, format=%p", opensles, format);
|
||||
|
||||
assert(format);
|
||||
|
||||
switch (format->wFormatTag)
|
||||
{
|
||||
/* TODO: Deactivated, untested */
|
||||
#if 0
|
||||
case WAVE_FORMAT_PCM: /* PCM */
|
||||
if (format->cbSize == 0 &&
|
||||
(format->nSamplesPerSec <= 48000) &&
|
||||
(format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
|
||||
(format->nChannels >= 1 && format->nChannels <= 2))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
/* TODO: Deactivated format, does not work, find out why */
|
||||
// case WAVE_FORMAT_ADPCM: /* IMA ADPCM */
|
||||
case WAVE_FORMAT_DVI_ADPCM:
|
||||
if ((format->nSamplesPerSec <= 48000) &&
|
||||
(format->wBitsPerSample == 4) &&
|
||||
(format->nChannels == 1 || format->nChannels == 2))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DEBUG_DVC("Encoding '%s' [%08X] not supported",
|
||||
rdpsnd_get_audio_tag_string(format->wFormatTag),
|
||||
format->wFormatTag);
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void audin_opensles_set_format(IAudinDevice* device,
|
||||
audinFormat* format, UINT32 FramesPerPacket)
|
||||
{
|
||||
int bs;
|
||||
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device;
|
||||
|
||||
DEBUG_DVC("device=%p, format=%p, FramesPerPacket=%d",
|
||||
device, format, FramesPerPacket);
|
||||
|
||||
assert(format);
|
||||
|
||||
/* The function may have been called out of order, ignore
|
||||
* requests before the device is available. */
|
||||
if (!opensles)
|
||||
return;
|
||||
|
||||
switch (format->wFormatTag)
|
||||
{
|
||||
case WAVE_FORMAT_PCM:
|
||||
opensles->frames_per_packet = FramesPerPacket;
|
||||
switch (format->wBitsPerSample)
|
||||
{
|
||||
case 4:
|
||||
opensles->bytes_per_channel = 1;
|
||||
break;
|
||||
case 8:
|
||||
opensles->bytes_per_channel = 1;
|
||||
break;
|
||||
case 16:
|
||||
opensles->bytes_per_channel = 2;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case WAVE_FORMAT_ADPCM:
|
||||
case WAVE_FORMAT_DVI_ADPCM:
|
||||
opensles->bytes_per_channel = 2;
|
||||
bs = (format->nBlockAlign - 4 * format->nChannels) * 4;
|
||||
|
||||
opensles->frames_per_packet =
|
||||
(FramesPerPacket * format->nChannels * 2 /
|
||||
bs + 1) * bs / (format->nChannels * 2);
|
||||
break;
|
||||
case WAVE_FORMAT_ALAW:
|
||||
case WAVE_FORMAT_MULAW:
|
||||
opensles->frames_per_packet = FramesPerPacket;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_WARN("Encoding '%d' [%08X] not supported",
|
||||
(format->wFormatTag),
|
||||
format->wFormatTag);
|
||||
return;
|
||||
}
|
||||
|
||||
opensles->rate = format->nSamplesPerSec;
|
||||
opensles->channels = format->nChannels;
|
||||
|
||||
opensles->format = format->wFormatTag;
|
||||
opensles->block_size = format->nBlockAlign;
|
||||
|
||||
DEBUG_DVC("aligned frames_per_packet=%d, block_size=%d",
|
||||
opensles->frames_per_packet, opensles->block_size);
|
||||
}
|
||||
|
||||
static void audin_opensles_open(IAudinDevice* device, AudinReceive receive,
|
||||
void* user_data)
|
||||
{
|
||||
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device;
|
||||
|
||||
DEBUG_DVC("device=%p, receive=%d, user_data=%p", device, receive, user_data);
|
||||
|
||||
assert(opensles);
|
||||
|
||||
/* The function may have been called out of order,
|
||||
* ignore duplicate open requests. */
|
||||
if(opensles->stream)
|
||||
return;
|
||||
|
||||
opensles->stream = android_OpenRecDevice(
|
||||
opensles->device_name,
|
||||
opensles->rate,
|
||||
opensles->channels,
|
||||
opensles->frames_per_packet,
|
||||
opensles->bytes_per_channel * 8);
|
||||
assert(opensles->stream);
|
||||
|
||||
opensles->receive = receive;
|
||||
opensles->user_data = user_data;
|
||||
|
||||
opensles->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
opensles->thread = CreateThread(NULL, 0,
|
||||
(LPTHREAD_START_ROUTINE) audin_opensles_thread_func,
|
||||
opensles, 0, NULL);
|
||||
}
|
||||
|
||||
static void audin_opensles_close(IAudinDevice* device)
|
||||
{
|
||||
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device;
|
||||
|
||||
DEBUG_DVC("device=%p", device);
|
||||
|
||||
assert(opensles);
|
||||
|
||||
/* The function may have been called out of order,
|
||||
* ignore duplicate requests. */
|
||||
if (!opensles->stopEvent)
|
||||
{
|
||||
DEBUG_WARN("[ERROR] function called without matching open.");
|
||||
return;
|
||||
}
|
||||
|
||||
assert(opensles->stopEvent);
|
||||
assert(opensles->thread);
|
||||
assert(opensles->stream);
|
||||
|
||||
SetEvent(opensles->stopEvent);
|
||||
WaitForSingleObject(opensles->thread, INFINITE);
|
||||
CloseHandle(opensles->stopEvent);
|
||||
CloseHandle(opensles->thread);
|
||||
|
||||
android_CloseRecDevice(opensles->stream);
|
||||
|
||||
opensles->stopEvent = NULL;
|
||||
opensles->thread = NULL;
|
||||
opensles->receive = NULL;
|
||||
opensles->user_data = NULL;
|
||||
opensles->stream = NULL;
|
||||
}
|
||||
|
||||
static COMMAND_LINE_ARGUMENT_A audin_opensles_args[] =
|
||||
{
|
||||
{ "audio-dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
|
||||
NULL, NULL, -1, NULL, "audio device name" },
|
||||
{ NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
|
||||
};
|
||||
|
||||
static int audin_opensles_parse_addin_args(AudinOpenSLESDevice* device,
|
||||
ADDIN_ARGV* args)
|
||||
{
|
||||
int status;
|
||||
DWORD flags;
|
||||
COMMAND_LINE_ARGUMENT_A* arg;
|
||||
AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device;
|
||||
|
||||
DEBUG_DVC("device=%p, args=%p", device, args);
|
||||
|
||||
flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
|
||||
|
||||
status = CommandLineParseArgumentsA(args->argc, (const char**) args->argv,
|
||||
audin_opensles_args, flags, opensles, NULL, NULL);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
arg = audin_opensles_args;
|
||||
|
||||
do
|
||||
{
|
||||
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
|
||||
continue;
|
||||
|
||||
CommandLineSwitchStart(arg)
|
||||
|
||||
CommandLineSwitchCase(arg, "audio-dev")
|
||||
{
|
||||
opensles->device_name = _strdup(arg->Value);
|
||||
}
|
||||
|
||||
CommandLineSwitchEnd(arg)
|
||||
}
|
||||
while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifdef STATIC_CHANNELS
|
||||
#define freerdp_audin_client_subsystem_entry \
|
||||
opensles_freerdp_audin_client_subsystem_entry
|
||||
#endif
|
||||
|
||||
int freerdp_audin_client_subsystem_entry(
|
||||
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)
|
||||
{
|
||||
ADDIN_ARGV* args;
|
||||
AudinOpenSLESDevice* opensles;
|
||||
|
||||
DEBUG_DVC("pEntryPoints=%p", pEntryPoints);
|
||||
|
||||
opensles = (AudinOpenSLESDevice*) malloc(sizeof(AudinOpenSLESDevice));
|
||||
ZeroMemory(opensles, sizeof(AudinOpenSLESDevice));
|
||||
|
||||
opensles->iface.Open = audin_opensles_open;
|
||||
opensles->iface.FormatSupported = audin_opensles_format_supported;
|
||||
opensles->iface.SetFormat = audin_opensles_set_format;
|
||||
opensles->iface.Close = audin_opensles_close;
|
||||
opensles->iface.Free = audin_opensles_free;
|
||||
|
||||
args = pEntryPoints->args;
|
||||
|
||||
audin_opensles_parse_addin_args(opensles, args);
|
||||
|
||||
opensles->dsp_context = freerdp_dsp_context_new();
|
||||
|
||||
pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin,
|
||||
(IAudinDevice*) opensles);
|
||||
|
||||
return 0;
|
||||
}
|
379
channels/audin/client/opensles/opensl_io.c
Normal file
379
channels/audin/client/opensles/opensl_io.c
Normal file
@ -0,0 +1,379 @@
|
||||
/*
|
||||
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 <assert.h>
|
||||
|
||||
#include "audin_main.h"
|
||||
#include "opensl_io.h"
|
||||
#define CONV16BIT 32768
|
||||
#define CONVMYFLT (1./32768.)
|
||||
|
||||
static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context);
|
||||
|
||||
// creates the OpenSL ES audio engine
|
||||
static SLresult openSLCreateEngine(OPENSL_STREAM *p)
|
||||
{
|
||||
SLresult result;
|
||||
// create engine
|
||||
result = slCreateEngine(&(p->engineObject), 0, NULL, 0, NULL, NULL);
|
||||
DEBUG_DVC("engineObject=%p", p->engineObject);
|
||||
if(result != SL_RESULT_SUCCESS) goto engine_end;
|
||||
|
||||
// realize the engine
|
||||
result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE);
|
||||
DEBUG_DVC("Realize=%d", 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_DVC("engineEngine=%p", p->engineEngine);
|
||||
if(result != SL_RESULT_SUCCESS) goto engine_end;
|
||||
|
||||
// get the volume interface - important, this is optional!
|
||||
result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_DEVICEVOLUME, &(p->deviceVolume));
|
||||
DEBUG_DVC("deviceVolume=%p", p->deviceVolume);
|
||||
if(result != SL_RESULT_SUCCESS)
|
||||
{
|
||||
p->deviceVolume = NULL;
|
||||
result = SL_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
engine_end:
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Open the OpenSL ES device for input
|
||||
static SLresult openSLRecOpen(OPENSL_STREAM *p){
|
||||
|
||||
SLresult result;
|
||||
SLuint32 sr = p->sr;
|
||||
SLuint32 channels = p->inchannels;
|
||||
|
||||
assert(!p->recorderObject);
|
||||
|
||||
if(channels){
|
||||
|
||||
switch(sr){
|
||||
|
||||
case 8000:
|
||||
sr = SL_SAMPLINGRATE_8;
|
||||
break;
|
||||
case 11025:
|
||||
sr = SL_SAMPLINGRATE_11_025;
|
||||
break;
|
||||
case 16000:
|
||||
sr = SL_SAMPLINGRATE_16;
|
||||
break;
|
||||
case 22050:
|
||||
sr = SL_SAMPLINGRATE_22_05;
|
||||
break;
|
||||
case 24000:
|
||||
sr = SL_SAMPLINGRATE_24;
|
||||
break;
|
||||
case 32000:
|
||||
sr = SL_SAMPLINGRATE_32;
|
||||
break;
|
||||
case 44100:
|
||||
sr = SL_SAMPLINGRATE_44_1;
|
||||
break;
|
||||
case 48000:
|
||||
sr = SL_SAMPLINGRATE_48;
|
||||
break;
|
||||
case 64000:
|
||||
sr = SL_SAMPLINGRATE_64;
|
||||
break;
|
||||
case 88200:
|
||||
sr = SL_SAMPLINGRATE_88_2;
|
||||
break;
|
||||
case 96000:
|
||||
sr = SL_SAMPLINGRATE_96;
|
||||
break;
|
||||
case 192000:
|
||||
sr = SL_SAMPLINGRATE_192;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
// configure audio source
|
||||
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
|
||||
SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
|
||||
SLDataSource audioSrc = {&loc_dev, NULL};
|
||||
|
||||
// configure audio sink
|
||||
int speakers;
|
||||
if(channels > 1)
|
||||
speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
else
|
||||
speakers = SL_SPEAKER_FRONT_CENTER;
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
||||
SLDataFormat_PCM format_pcm;
|
||||
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = channels;
|
||||
format_pcm.samplesPerSec = sr;
|
||||
format_pcm.channelMask = speakers;
|
||||
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
|
||||
if (16 == p->bits_per_sample)
|
||||
{
|
||||
format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
|
||||
format_pcm.containerSize = 16;
|
||||
}
|
||||
else if (8 == p->bits_per_sample)
|
||||
{
|
||||
format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8;
|
||||
format_pcm.containerSize = 8;
|
||||
}
|
||||
else
|
||||
assert(0);
|
||||
|
||||
SLDataSink audioSnk = {&loc_bq, &format_pcm};
|
||||
|
||||
// create audio recorder
|
||||
// (requires the RECORD_AUDIO permission)
|
||||
const SLInterfaceID id[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
|
||||
const SLboolean req[] = {SL_BOOLEAN_TRUE};
|
||||
result = (*p->engineEngine)->CreateAudioRecorder(p->engineEngine,
|
||||
&(p->recorderObject), &audioSrc, &audioSnk, 1, id, req);
|
||||
DEBUG_DVC("p->recorderObject=%p", p->recorderObject);
|
||||
assert(!result);
|
||||
if (SL_RESULT_SUCCESS != result) goto end_recopen;
|
||||
|
||||
// realize the audio recorder
|
||||
result = (*p->recorderObject)->Realize(p->recorderObject, SL_BOOLEAN_FALSE);
|
||||
DEBUG_DVC("Realize=%d", result);
|
||||
assert(!result);
|
||||
if (SL_RESULT_SUCCESS != result) goto end_recopen;
|
||||
|
||||
// get the record interface
|
||||
result = (*p->recorderObject)->GetInterface(p->recorderObject,
|
||||
SL_IID_RECORD, &(p->recorderRecord));
|
||||
DEBUG_DVC("p->recorderRecord=%p", p->recorderRecord);
|
||||
assert(!result);
|
||||
if (SL_RESULT_SUCCESS != result) goto end_recopen;
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*p->recorderObject)->GetInterface(p->recorderObject,
|
||||
SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
&(p->recorderBufferQueue));
|
||||
DEBUG_DVC("p->recorderBufferQueue=%p", p->recorderBufferQueue);
|
||||
assert(!result);
|
||||
if (SL_RESULT_SUCCESS != result) goto end_recopen;
|
||||
|
||||
// register callback on the buffer queue
|
||||
result = (*p->recorderBufferQueue)->RegisterCallback(p->recorderBufferQueue,
|
||||
bqRecorderCallback, p);
|
||||
DEBUG_DVC("p->recorderBufferQueue=%p", p->recorderBufferQueue);
|
||||
assert(!result);
|
||||
if (SL_RESULT_SUCCESS != result)
|
||||
goto end_recopen;
|
||||
|
||||
end_recopen:
|
||||
return result;
|
||||
}
|
||||
else return SL_RESULT_SUCCESS;
|
||||
|
||||
|
||||
}
|
||||
|
||||
// close the OpenSL IO and destroy the audio engine
|
||||
static void openSLDestroyEngine(OPENSL_STREAM *p)
|
||||
{
|
||||
DEBUG_DVC("p=%p", p);
|
||||
|
||||
// destroy audio recorder object, and invalidate all associated interfaces
|
||||
if (p->recorderObject != NULL) {
|
||||
(*p->recorderObject)->Destroy(p->recorderObject);
|
||||
p->recorderObject = NULL;
|
||||
p->recorderRecord = NULL;
|
||||
p->recorderBufferQueue = NULL;
|
||||
}
|
||||
|
||||
// destroy engine object, and invalidate all associated interfaces
|
||||
if (p->engineObject != NULL) {
|
||||
(*p->engineObject)->Destroy(p->engineObject);
|
||||
p->engineObject = NULL;
|
||||
p->engineEngine = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// open the android audio device for input
|
||||
OPENSL_STREAM *android_OpenRecDevice(char *name, int sr, int inchannels,
|
||||
int bufferframes, int bits_per_sample)
|
||||
{
|
||||
|
||||
OPENSL_STREAM *p;
|
||||
p = (OPENSL_STREAM *) calloc(sizeof(OPENSL_STREAM),1);
|
||||
memset(p, 0, sizeof(OPENSL_STREAM));
|
||||
|
||||
p->inchannels = inchannels;
|
||||
p->sr = sr;
|
||||
p->queue = Queue_New(TRUE, -1, -1);
|
||||
p->buffersize = bufferframes;
|
||||
p->bits_per_sample = bits_per_sample;
|
||||
|
||||
if ((p->bits_per_sample != 8) && (p->bits_per_sample != 16))
|
||||
{
|
||||
android_CloseRecDevice(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(openSLCreateEngine(p) != SL_RESULT_SUCCESS)
|
||||
{
|
||||
android_CloseRecDevice(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(openSLRecOpen(p) != SL_RESULT_SUCCESS)
|
||||
{
|
||||
android_CloseRecDevice(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// close the android audio device
|
||||
void android_CloseRecDevice(OPENSL_STREAM *p)
|
||||
{
|
||||
DEBUG_DVC("p=%p", p);
|
||||
|
||||
if (p == NULL)
|
||||
return;
|
||||
|
||||
if (p->queue)
|
||||
{
|
||||
while (Queue_Count(p->queue) > 0)
|
||||
{
|
||||
queue_element *e = Queue_Dequeue(p->queue);
|
||||
free(e->data);
|
||||
free(e);
|
||||
}
|
||||
Queue_Free(p->queue);
|
||||
}
|
||||
|
||||
if (p->next)
|
||||
{
|
||||
free(p->next->data);
|
||||
free(p->next);
|
||||
}
|
||||
|
||||
if (p->prep)
|
||||
{
|
||||
free(p->prep->data);
|
||||
free(p->prep);
|
||||
}
|
||||
|
||||
openSLDestroyEngine(p);
|
||||
|
||||
free(p);
|
||||
}
|
||||
|
||||
// this callback handler is called every time a buffer finishes recording
|
||||
void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||
{
|
||||
queue_element *e;
|
||||
|
||||
OPENSL_STREAM *p = (OPENSL_STREAM *) context;
|
||||
|
||||
DEBUG_DVC("p=%p", p);
|
||||
|
||||
assert(p);
|
||||
assert(p->next);
|
||||
assert(p->prep);
|
||||
assert(p->queue);
|
||||
|
||||
e = calloc(1, sizeof(queue_element));
|
||||
e->data = calloc(p->buffersize, p->bits_per_sample / 8);
|
||||
e->size = p->buffersize * p->bits_per_sample / 8;
|
||||
|
||||
Queue_Enqueue(p->queue, p->next);
|
||||
p->next = p->prep;
|
||||
p->prep = e;
|
||||
|
||||
(*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue,
|
||||
e->data, e->size);
|
||||
|
||||
}
|
||||
|
||||
// gets a buffer of size samples from the device
|
||||
int android_RecIn(OPENSL_STREAM *p,short *buffer,int size)
|
||||
{
|
||||
queue_element *e;
|
||||
int rc;
|
||||
|
||||
assert(p);
|
||||
assert(buffer);
|
||||
assert(size > 0);
|
||||
|
||||
/* Initial trigger for the queue. */
|
||||
if (!p->prep)
|
||||
{
|
||||
p->prep = calloc(1, sizeof(queue_element));
|
||||
p->prep->data = calloc(p->buffersize, p->bits_per_sample / 8);
|
||||
p->prep->size = p->buffersize * p->bits_per_sample / 8;
|
||||
|
||||
p->next = calloc(1, sizeof(queue_element));
|
||||
p->next->data = calloc(p->buffersize, p->bits_per_sample / 8);
|
||||
p->next->size = p->buffersize * p->bits_per_sample / 8;
|
||||
|
||||
(*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue,
|
||||
p->next->data, p->next->size);
|
||||
(*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue,
|
||||
p->prep->data, p->prep->size);
|
||||
|
||||
(*p->recorderRecord)->SetRecordState(p->recorderRecord, SL_RECORDSTATE_RECORDING);
|
||||
}
|
||||
|
||||
/* Wait for queue to be filled... */
|
||||
if (!Queue_Count(p->queue))
|
||||
WaitForSingleObject(p->queue->event, INFINITE);
|
||||
|
||||
e = Queue_Dequeue(p->queue);
|
||||
if (!e)
|
||||
{
|
||||
DEBUG_WARN("[ERROR] got e=%p from queue", e);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = (e->size < size) ? e->size : size;
|
||||
assert(size == e->size);
|
||||
assert(p->buffersize * p->bits_per_sample / 8 == size);
|
||||
|
||||
memcpy(buffer, e->data, rc);
|
||||
free(e->data);
|
||||
free(e);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
92
channels/audin/client/opensles/opensl_io.h
Normal file
92
channels/audin/client/opensles/opensl_io.h
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
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 OPENSL_IO
|
||||
#define OPENSL_IO
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/collections.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
size_t size;
|
||||
void *data;
|
||||
} queue_element;
|
||||
|
||||
typedef struct opensl_stream {
|
||||
// engine interfaces
|
||||
SLObjectItf engineObject;
|
||||
SLEngineItf engineEngine;
|
||||
|
||||
// device interfaces
|
||||
SLDeviceVolumeItf deviceVolume;
|
||||
|
||||
// recorder interfaces
|
||||
SLObjectItf recorderObject;
|
||||
SLRecordItf recorderRecord;
|
||||
SLAndroidSimpleBufferQueueItf recorderBufferQueue;
|
||||
|
||||
unsigned int inchannels;
|
||||
unsigned int sr;
|
||||
unsigned int buffersize;
|
||||
unsigned int bits_per_sample;
|
||||
|
||||
wQueue *queue;
|
||||
queue_element *prep;
|
||||
queue_element *next;
|
||||
} OPENSL_STREAM;
|
||||
|
||||
/*
|
||||
Open the audio device with a given sampling rate (sr), input and output channels and IO buffer size
|
||||
in frames. Returns a handle to the OpenSL stream
|
||||
*/
|
||||
OPENSL_STREAM* android_OpenRecDevice(char *name, int sr, int inchannels,
|
||||
int bufferframes, int bits_per_sample);
|
||||
/*
|
||||
Close the audio device
|
||||
*/
|
||||
void android_CloseRecDevice(OPENSL_STREAM *p);
|
||||
/*
|
||||
Read a buffer from the OpenSL stream *p, of size samples. Returns the number of samples read.
|
||||
*/
|
||||
int android_RecIn(OPENSL_STREAM *p, short *buffer,int size);
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // #ifndef OPENSL_IO
|
@ -324,8 +324,6 @@ static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length
|
||||
pulse->sample_spec.channels, pulse->block_size);
|
||||
encoded_data = pulse->dsp_context->adpcm_buffer;
|
||||
encoded_size = pulse->dsp_context->adpcm_size;
|
||||
DEBUG_DVC("encoded %d to %d",
|
||||
pulse->buffer_frames * pulse->bytes_per_frame, encoded_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -333,6 +331,9 @@ static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length
|
||||
encoded_size = pulse->buffer_frames * pulse->bytes_per_frame;
|
||||
}
|
||||
|
||||
DEBUG_DVC("encoded %d [%d] to %d [%X]",
|
||||
pulse->buffer_frames, pulse->bytes_per_frame, encoded_size,
|
||||
pulse->format);
|
||||
ret = pulse->receive(encoded_data, encoded_size, pulse->user_data);
|
||||
pulse->buffer_frames = 0;
|
||||
if (!ret)
|
||||
@ -410,7 +411,7 @@ static void audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* u
|
||||
/* 500ms latency */
|
||||
buffer_attr.fragsize = pa_usec_to_bytes(500000, &pulse->sample_spec);
|
||||
if (pa_stream_connect_record(pulse->stream,
|
||||
pulse->device_name[0] ? pulse->device_name : NULL,
|
||||
pulse->device_name,
|
||||
&buffer_attr, PA_STREAM_ADJUST_LATENCY) < 0)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
@ -447,7 +448,7 @@ static void audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* u
|
||||
}
|
||||
}
|
||||
|
||||
COMMAND_LINE_ARGUMENT_A audin_pulse_args[] =
|
||||
static COMMAND_LINE_ARGUMENT_A audin_pulse_args[] =
|
||||
{
|
||||
{ "audio-dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL, "audio device name" },
|
||||
{ NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
|
||||
@ -505,9 +506,6 @@ int freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEnt
|
||||
|
||||
audin_pulse_parse_addin_args(pulse, args);
|
||||
|
||||
if (!pulse->device_name)
|
||||
pulse->device_name = _strdup("default");
|
||||
|
||||
pulse->dsp_context = freerdp_dsp_context_new();
|
||||
|
||||
pulse->mainloop = pa_threaded_mainloop_new();
|
||||
|
@ -58,3 +58,7 @@ 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()
|
||||
|
43
channels/rdpsnd/client/opensles/CMakeLists.txt
Normal file
43
channels/rdpsnd/client/opensles/CMakeLists.txt
Normal file
@ -0,0 +1,43 @@
|
||||
# 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" "")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
opensl_io.c
|
||||
rdpsnd_opensles.c)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(${OPENSLES_INCLUDE_DIRS})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
|
||||
set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE freerdp
|
||||
MODULES freerdp-codec freerdp-utils
|
||||
${OPENSLES_LIBRARIES})
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${OPENSLES_LIBRARIES})
|
||||
|
||||
target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
|
||||
|
||||
if(NOT STATIC_CHANNELS)
|
||||
install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_ADDIN_PATH})
|
||||
endif()
|
351
channels/rdpsnd/client/opensles/opensl_io.c
Normal file
351
channels/rdpsnd/client/opensles/opensl_io.c
Normal file
@ -0,0 +1,351 @@
|
||||
/*
|
||||
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 <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, NULL, 0, NULL, NULL);
|
||||
DEBUG_SND("engineObject=%p", 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=%d", 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", 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;
|
||||
|
||||
assert(p->engineObject);
|
||||
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", p->engineEngine);
|
||||
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=%d", result);
|
||||
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, NULL};
|
||||
|
||||
// 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", p->bqPlayerObject);
|
||||
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=%d", result);
|
||||
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", p->bqPlayerPlay);
|
||||
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", p->bqPlayerVolume);
|
||||
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", p->bqPlayerBufferQueue);
|
||||
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", p->bqPlayerCallback);
|
||||
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=%d", result);
|
||||
assert(!result);
|
||||
|
||||
end_openaudio:
|
||||
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 != NULL) {
|
||||
(*p->bqPlayerObject)->Destroy(p->bqPlayerObject);
|
||||
p->bqPlayerObject = NULL;
|
||||
p->bqPlayerVolume = NULL;
|
||||
p->bqPlayerPlay = NULL;
|
||||
p->bqPlayerBufferQueue = NULL;
|
||||
p->bqPlayerEffectSend = NULL;
|
||||
}
|
||||
|
||||
// destroy output mix object, and invalidate all associated interfaces
|
||||
if (p->outputMixObject != NULL) {
|
||||
(*p->outputMixObject)->Destroy(p->outputMixObject);
|
||||
p->outputMixObject = NULL;
|
||||
}
|
||||
|
||||
// destroy engine object, and invalidate all associated interfaces
|
||||
if (p->engineObject != NULL) {
|
||||
(*p->engineObject)->Destroy(p->engineObject);
|
||||
p->engineObject = NULL;
|
||||
p->engineEngine = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 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(sizeof(OPENSL_STREAM),1);
|
||||
memset(p, 0, sizeof(OPENSL_STREAM));
|
||||
|
||||
p->queuesize = bufferframes;
|
||||
p->outchannels = outchannels;
|
||||
p->sr = sr;
|
||||
|
||||
if(openSLCreateEngine(p) != SL_RESULT_SUCCESS) {
|
||||
android_CloseAudioDevice(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(openSLPlayOpen(p) != SL_RESULT_SUCCESS) {
|
||||
android_CloseAudioDevice(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p->queue = Queue_New(TRUE, -1, -1);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// close the android audio device
|
||||
void android_CloseAudioDevice(OPENSL_STREAM *p){
|
||||
|
||||
if (p == NULL)
|
||||
return;
|
||||
|
||||
openSLDestroyEngine(p);
|
||||
if (p->queue)
|
||||
Queue_Free(p->queue);
|
||||
|
||||
free(p);
|
||||
}
|
||||
|
||||
// this callback handler is called every time a buffer finishes playing
|
||||
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||
{
|
||||
OPENSL_STREAM *p = (OPENSL_STREAM *) context;
|
||||
|
||||
assert(p);
|
||||
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)
|
||||
{
|
||||
assert(p);
|
||||
assert(buffer);
|
||||
assert(size > 0);
|
||||
|
||||
/* Assure, that the queue is not full. */
|
||||
if (p->queuesize <= Queue_Count(p->queue))
|
||||
WaitForSingleObject(p->queue->event, INFINITE);
|
||||
|
||||
void *data = calloc(size, sizeof(short));
|
||||
memcpy(data, buffer, size * sizeof(short));
|
||||
(*p->bqPlayerBufferQueue)->Enqueue(p->bqPlayerBufferQueue,
|
||||
data, sizeof(short) * size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int android_GetOutputMute(OPENSL_STREAM *p) {
|
||||
SLboolean mute;
|
||||
|
||||
assert(p);
|
||||
assert(p->bqPlayerVolume);
|
||||
|
||||
SLresult rc = (*p->bqPlayerVolume)->GetMute(p->bqPlayerVolume, &mute);
|
||||
assert(SL_RESULT_SUCCESS == rc);
|
||||
|
||||
return mute;
|
||||
}
|
||||
|
||||
void android_SetOutputMute(OPENSL_STREAM *p, BOOL _mute) {
|
||||
SLboolean mute = _mute;
|
||||
|
||||
assert(p);
|
||||
assert(p->bqPlayerVolume);
|
||||
|
||||
SLresult rc = (*p->bqPlayerVolume)->SetMute(p->bqPlayerVolume, mute);
|
||||
assert(SL_RESULT_SUCCESS == rc);
|
||||
}
|
||||
|
||||
int android_GetOutputVolume(OPENSL_STREAM *p){
|
||||
SLmillibel level;
|
||||
|
||||
assert(p);
|
||||
assert(p->bqPlayerVolume);
|
||||
|
||||
SLresult rc = (*p->bqPlayerVolume)->GetVolumeLevel(p->bqPlayerVolume, &level);
|
||||
assert(SL_RESULT_SUCCESS == rc);
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
int android_GetOutputVolumeMax(OPENSL_STREAM *p){
|
||||
SLmillibel level;
|
||||
|
||||
assert(p);
|
||||
assert(p->bqPlayerVolume);
|
||||
|
||||
SLresult rc = (*p->bqPlayerVolume)->GetMaxVolumeLevel(p->bqPlayerVolume, &level);
|
||||
assert(SL_RESULT_SUCCESS == rc);
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
|
||||
void android_SetOutputVolume(OPENSL_STREAM *p, int level){
|
||||
SLresult rc = (*p->bqPlayerVolume)->SetVolumeLevel(p->bqPlayerVolume, level);
|
||||
assert(SL_RESULT_SUCCESS == rc);
|
||||
}
|
||||
|
106
channels/rdpsnd/client/opensles/opensl_io.h
Normal file
106
channels/rdpsnd/client/opensles/opensl_io.h
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
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 OPENSL_IO
|
||||
#define OPENSL_IO
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <stdlib.h>
|
||||
#include <winpr/synch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct opensl_stream {
|
||||
// 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
|
||||
*/
|
||||
OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, int bufferframes);
|
||||
/*
|
||||
Close the audio device
|
||||
*/
|
||||
void android_CloseAudioDevice(OPENSL_STREAM *p);
|
||||
/*
|
||||
Write a buffer to the OpenSL stream *p, of size samples. Returns the number of samples written.
|
||||
*/
|
||||
int android_AudioOut(OPENSL_STREAM *p, const short *buffer, int size);
|
||||
/*
|
||||
* Set the volume input level.
|
||||
*/
|
||||
void android_SetOutputVolume(OPENSL_STREAM *p, int level);
|
||||
/*
|
||||
* Get the current output mute setting.
|
||||
*/
|
||||
int android_GetOutputMute(OPENSL_STREAM *p);
|
||||
/*
|
||||
* Change the current output mute setting.
|
||||
*/
|
||||
void android_SetOutputMute(OPENSL_STREAM *p, BOOL mute);
|
||||
/*
|
||||
* Get the current output volume level.
|
||||
*/
|
||||
int android_GetOutputVolume(OPENSL_STREAM *p);
|
||||
/*
|
||||
* Get the maximum output volume level.
|
||||
*/
|
||||
int android_GetOutputVolumeMax(OPENSL_STREAM *p);
|
||||
|
||||
/*
|
||||
* Set the volume output level.
|
||||
*/
|
||||
void android_SetOutputVolume(OPENSL_STREAM *p, int level);
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // #ifndef OPENSL_IO
|
469
channels/rdpsnd/client/opensles/rdpsnd_opensles.c
Normal file
469
channels/rdpsnd/client/opensles/rdpsnd_opensles.c
Normal file
@ -0,0 +1,469 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Output Virtual Channel
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <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/codec/dsp.h>
|
||||
#include <freerdp/utils/debug.h>
|
||||
|
||||
#include "opensl_io.h"
|
||||
#include "rdpsnd_main.h"
|
||||
|
||||
typedef struct rdpsnd_opensles_plugin rdpsndopenslesPlugin;
|
||||
|
||||
struct rdpsnd_opensles_plugin
|
||||
{
|
||||
rdpsndDevicePlugin device;
|
||||
|
||||
int latency;
|
||||
int wformat;
|
||||
int block_size;
|
||||
char* device_name;
|
||||
|
||||
OPENSL_STREAM *stream;
|
||||
|
||||
UINT32 volume;
|
||||
|
||||
UINT32 rate;
|
||||
UINT32 channels;
|
||||
int format;
|
||||
FREERDP_DSP_CONTEXT* dsp_context;
|
||||
};
|
||||
|
||||
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=%d, 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->dsp_context)
|
||||
rc = false;
|
||||
if (!hdl->stream)
|
||||
rc = false;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device,
|
||||
UINT32 volume);
|
||||
|
||||
static int rdpsnd_opensles_set_params(rdpsndopenslesPlugin* opensles)
|
||||
{
|
||||
DEBUG_SND("opensles=%p", 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 void rdpsnd_opensles_set_format(rdpsndDevicePlugin* device,
|
||||
AUDIO_FORMAT* format, int latency)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
rdpsnd_opensles_check_handle(opensles);
|
||||
|
||||
DEBUG_SND("opensles=%p format=%p, latency=%d", opensles, format, latency);
|
||||
|
||||
if (format)
|
||||
{
|
||||
DEBUG_SND("format=%d, cbsize=%d, samples=%d, bits=%d, channels=%d, align=%d",
|
||||
format->wFormatTag, format->cbSize, format->nSamplesPerSec,
|
||||
format->wBitsPerSample, format->nChannels, format->nBlockAlign);
|
||||
|
||||
opensles->rate = format->nSamplesPerSec;
|
||||
opensles->channels = format->nChannels;
|
||||
|
||||
switch (format->wFormatTag)
|
||||
{
|
||||
case WAVE_FORMAT_PCM:
|
||||
switch (format->wBitsPerSample)
|
||||
{
|
||||
case 4:
|
||||
opensles->format = WAVE_FORMAT_ADPCM;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
opensles->format = WAVE_FORMAT_PCM;
|
||||
break;
|
||||
|
||||
case 16:
|
||||
opensles->format = WAVE_FORMAT_ADPCM;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case WAVE_FORMAT_ADPCM:
|
||||
case WAVE_FORMAT_DVI_ADPCM:
|
||||
opensles->format = format->wFormatTag;
|
||||
break;
|
||||
}
|
||||
|
||||
opensles->wformat = format->wFormatTag;
|
||||
opensles->block_size = format->nBlockAlign;
|
||||
}
|
||||
|
||||
opensles->latency = latency;
|
||||
|
||||
rdpsnd_opensles_set_params(opensles);
|
||||
}
|
||||
|
||||
static void rdpsnd_opensles_open(rdpsndDevicePlugin* device,
|
||||
AUDIO_FORMAT* format, int latency)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
|
||||
DEBUG_SND("opensles=%p format=%p, latency=%d, rate=%d",
|
||||
opensles, format, latency, opensles->rate);
|
||||
|
||||
if( rdpsnd_opensles_check_handle(opensles))
|
||||
return;
|
||||
|
||||
opensles->stream = android_OpenAudioDevice(
|
||||
opensles->rate, opensles->channels, 20);
|
||||
assert(opensles->stream);
|
||||
|
||||
if (!opensles->stream)
|
||||
DEBUG_WARN("android_OpenAudioDevice failed");
|
||||
else
|
||||
rdpsnd_opensles_set_volume(device, opensles->volume);
|
||||
|
||||
rdpsnd_opensles_set_format(device, format, latency);
|
||||
}
|
||||
|
||||
static void rdpsnd_opensles_close(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
|
||||
DEBUG_SND("opensles=%p", opensles);
|
||||
if( !rdpsnd_opensles_check_handle(opensles))
|
||||
return;
|
||||
|
||||
android_CloseAudioDevice(opensles->stream);
|
||||
opensles->stream = NULL;
|
||||
}
|
||||
|
||||
static void rdpsnd_opensles_free(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
|
||||
DEBUG_SND("opensles=%p", opensles);
|
||||
assert(opensles);
|
||||
|
||||
assert(opensles->device_name);
|
||||
free(opensles->device_name);
|
||||
|
||||
assert(opensles->dsp_context);
|
||||
freerdp_dsp_context_free(opensles->dsp_context);
|
||||
|
||||
free(opensles);
|
||||
}
|
||||
|
||||
static BOOL rdpsnd_opensles_format_supported(rdpsndDevicePlugin* device,
|
||||
AUDIO_FORMAT* format)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
|
||||
DEBUG_SND("format=%d, cbsize=%d, samples=%d, bits=%d, channels=%d, align=%d",
|
||||
format->wFormatTag, format->cbSize, format->nSamplesPerSec,
|
||||
format->wBitsPerSample, format->nChannels, format->nBlockAlign);
|
||||
|
||||
assert(opensles);
|
||||
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;
|
||||
|
||||
case WAVE_FORMAT_ADPCM:
|
||||
case WAVE_FORMAT_DVI_ADPCM:
|
||||
if (format->nSamplesPerSec <= 48000 &&
|
||||
format->wBitsPerSample == 4 &&
|
||||
(format->nChannels == 1 || format->nChannels == 2))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WAVE_FORMAT_ALAW:
|
||||
case WAVE_FORMAT_MULAW:
|
||||
case WAVE_FORMAT_GSM610:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static UINT32 rdpsnd_opensles_get_volume(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
|
||||
DEBUG_SND("opensles=%p", opensles);
|
||||
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 void rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device,
|
||||
UINT32 value)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
|
||||
DEBUG_SND("opensles=%p, value=%d", opensles, value);
|
||||
assert(opensles);
|
||||
|
||||
opensles->volume = value;
|
||||
|
||||
if (opensles->stream)
|
||||
{
|
||||
if (0 == opensles->volume)
|
||||
android_SetOutputMute(opensles->stream, true);
|
||||
else
|
||||
{
|
||||
const int max = android_GetOutputVolumeMax(opensles->stream);
|
||||
const int vol = rdpsnd_opensles_volume_to_millibel(value & 0xFFFF, max);
|
||||
|
||||
android_SetOutputMute(opensles->stream, false);
|
||||
android_SetOutputVolume(opensles->stream, vol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rdpsnd_opensles_play(rdpsndDevicePlugin* device,
|
||||
BYTE *data, int size)
|
||||
{
|
||||
union
|
||||
{
|
||||
BYTE *b;
|
||||
short *s;
|
||||
} src;
|
||||
int ret;
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
|
||||
DEBUG_SND("opensles=%p, data=%p, size=%d", opensles, data, size);
|
||||
if (!rdpsnd_opensles_check_handle(opensles))
|
||||
return;
|
||||
|
||||
if (opensles->format == WAVE_FORMAT_ADPCM)
|
||||
{
|
||||
DEBUG_SND("dsp_context=%p, channels=%d, block_size=%d",
|
||||
opensles->dsp_context, opensles->channels, opensles->block_size);
|
||||
|
||||
opensles->dsp_context->decode_ms_adpcm(opensles->dsp_context,
|
||||
data, size, opensles->channels, opensles->block_size);
|
||||
|
||||
size = opensles->dsp_context->adpcm_size;
|
||||
src.b = opensles->dsp_context->adpcm_buffer;
|
||||
}
|
||||
else if (opensles->format == WAVE_FORMAT_DVI_ADPCM)
|
||||
{
|
||||
DEBUG_SND("dsp_context=%p, channels=%d, block_size=%d",
|
||||
opensles->dsp_context, opensles->channels, opensles->block_size);
|
||||
|
||||
opensles->dsp_context->decode_ima_adpcm(opensles->dsp_context,
|
||||
data, size, opensles->channels, opensles->block_size);
|
||||
|
||||
size = opensles->dsp_context->adpcm_size;
|
||||
src.b = opensles->dsp_context->adpcm_buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
src.b = data;
|
||||
}
|
||||
|
||||
DEBUG_SND("size=%d, src=%p", size, src.b);
|
||||
assert(0 == size % 2);
|
||||
assert(size > 0);
|
||||
assert(src.b);
|
||||
|
||||
ret = android_AudioOut(opensles->stream, src.s, size / 2);
|
||||
if (ret < 0)
|
||||
DEBUG_WARN("android_AudioOut failed (%d)", ret);
|
||||
}
|
||||
|
||||
static void rdpsnd_opensles_start(rdpsndDevicePlugin* device)
|
||||
{
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
rdpsnd_opensles_check_handle(opensles);
|
||||
|
||||
DEBUG_SND("opensles=%p", opensles);
|
||||
}
|
||||
|
||||
static COMMAND_LINE_ARGUMENT_A rdpsnd_opensles_args[] =
|
||||
{
|
||||
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
|
||||
NULL, NULL, -1, NULL, "device" },
|
||||
{ NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
|
||||
};
|
||||
|
||||
static int rdpsnd_opensles_parse_addin_args(rdpsndDevicePlugin* device,
|
||||
ADDIN_ARGV* args)
|
||||
{
|
||||
int status;
|
||||
DWORD flags;
|
||||
COMMAND_LINE_ARGUMENT_A* arg;
|
||||
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device;
|
||||
|
||||
assert(opensles);
|
||||
assert(args);
|
||||
|
||||
DEBUG_SND("opensles=%p, args=%p", opensles, args);
|
||||
|
||||
flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
|
||||
|
||||
status = CommandLineParseArgumentsA(args->argc, (const char**) args->argv,
|
||||
rdpsnd_opensles_args, flags, opensles, NULL, NULL);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
arg = rdpsnd_opensles_args;
|
||||
|
||||
do
|
||||
{
|
||||
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
|
||||
continue;
|
||||
|
||||
CommandLineSwitchStart(arg)
|
||||
|
||||
CommandLineSwitchCase(arg, "dev")
|
||||
{
|
||||
opensles->device_name = _strdup(arg->Value);
|
||||
}
|
||||
|
||||
CommandLineSwitchEnd(arg)
|
||||
}
|
||||
while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifdef STATIC_CHANNELS
|
||||
#define freerdp_rdpsnd_client_subsystem_entry \
|
||||
opensles_freerdp_rdpsnd_client_subsystem_entry
|
||||
#endif
|
||||
|
||||
int freerdp_rdpsnd_client_subsystem_entry(
|
||||
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)
|
||||
{
|
||||
ADDIN_ARGV* args;
|
||||
rdpsndopenslesPlugin* opensles;
|
||||
|
||||
DEBUG_SND("pEntryPoints=%p", pEntryPoints);
|
||||
|
||||
opensles = (rdpsndopenslesPlugin*) malloc(sizeof(rdpsndopenslesPlugin));
|
||||
ZeroMemory(opensles, sizeof(rdpsndopenslesPlugin));
|
||||
|
||||
opensles->device.Open = rdpsnd_opensles_open;
|
||||
opensles->device.FormatSupported = rdpsnd_opensles_format_supported;
|
||||
opensles->device.SetFormat = rdpsnd_opensles_set_format;
|
||||
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;
|
||||
|
||||
args = pEntryPoints->args;
|
||||
rdpsnd_opensles_parse_addin_args((rdpsndDevicePlugin*) opensles, args);
|
||||
|
||||
if (!opensles->device_name)
|
||||
opensles->device_name = _strdup("default");
|
||||
|
||||
opensles->rate = 44100;
|
||||
opensles->channels = 2;
|
||||
opensles->format = WAVE_FORMAT_ADPCM;
|
||||
|
||||
opensles->dsp_context = freerdp_dsp_context_new();
|
||||
|
||||
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd,
|
||||
(rdpsndDevicePlugin*) opensles);
|
||||
|
||||
DEBUG_SND("success");
|
||||
return 0;
|
||||
}
|
@ -83,7 +83,7 @@ struct rdpsnd_plugin
|
||||
rdpsndDevicePlugin* device;
|
||||
};
|
||||
|
||||
void rdpsnd_send_wave_confirm_pdu(rdpsndPlugin* rdpsnd, UINT16 wTimeStamp, BYTE cConfirmedBlockNo);
|
||||
static void rdpsnd_send_wave_confirm_pdu(rdpsndPlugin* rdpsnd, UINT16 wTimeStamp, BYTE cConfirmedBlockNo);
|
||||
|
||||
static void* rdpsnd_schedule_thread(void* arg)
|
||||
{
|
||||
@ -117,6 +117,7 @@ static void* rdpsnd_schedule_thread(void* arg)
|
||||
|
||||
rdpsnd_send_wave_confirm_pdu(rdpsnd, wave->wTimeStampB, wave->cBlockNo);
|
||||
free(wave);
|
||||
message.wParam = NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -258,13 +259,14 @@ void rdpsnd_recv_server_audio_formats_pdu(rdpsndPlugin* rdpsnd, wStream* s)
|
||||
UINT16 wVersion;
|
||||
AUDIO_FORMAT* format;
|
||||
UINT16 wNumberOfFormats;
|
||||
UINT32 dwVolume;
|
||||
|
||||
rdpsnd_free_audio_formats(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats);
|
||||
rdpsnd->NumberOfServerFormats = 0;
|
||||
rdpsnd->ServerFormats = NULL;
|
||||
|
||||
Stream_Seek_UINT32(s); /* dwFlags */
|
||||
Stream_Seek_UINT32(s); /* dwVolume */
|
||||
Stream_Read_UINT32(s, dwVolume); /* dwVolume */
|
||||
Stream_Seek_UINT32(s); /* dwPitch */
|
||||
Stream_Seek_UINT16(s); /* wDGramPort */
|
||||
Stream_Read_UINT16(s, wNumberOfFormats);
|
||||
@ -297,6 +299,9 @@ void rdpsnd_recv_server_audio_formats_pdu(rdpsndPlugin* rdpsnd, wStream* s)
|
||||
|
||||
if (wVersion >= 6)
|
||||
rdpsnd_send_quality_mode_pdu(rdpsnd);
|
||||
|
||||
if (rdpsnd->device)
|
||||
IFCALL(rdpsnd->device->SetVolume, rdpsnd->device, dwVolume);
|
||||
}
|
||||
|
||||
void rdpsnd_send_training_confirm_pdu(rdpsndPlugin* rdpsnd, UINT16 wTimeStamp, UINT16 wPackSize)
|
||||
@ -379,7 +384,7 @@ void rdpsnd_send_wave_confirm_pdu(rdpsndPlugin* rdpsnd, UINT16 wTimeStamp, BYTE
|
||||
svc_plugin_send((rdpSvcPlugin*) rdpsnd, pdu);
|
||||
}
|
||||
|
||||
void rdpsnd_device_send_wave_confirm_pdu(rdpsndDevicePlugin* device, RDPSND_WAVE* wave)
|
||||
static void rdpsnd_device_send_wave_confirm_pdu(rdpsndDevicePlugin* device, RDPSND_WAVE* wave)
|
||||
{
|
||||
MessageQueue_Post(device->rdpsnd->queue, NULL, 0, (void*) wave, NULL);
|
||||
}
|
||||
@ -649,7 +654,9 @@ static void rdpsnd_process_connect(rdpSvcPlugin* plugin)
|
||||
|
||||
rdpsnd->latency = -1;
|
||||
rdpsnd->queue = MessageQueue_New();
|
||||
rdpsnd->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) rdpsnd_schedule_thread, (void*) plugin, 0, NULL);
|
||||
rdpsnd->thread = CreateThread(NULL, 0,
|
||||
(LPTHREAD_START_ROUTINE) rdpsnd_schedule_thread,
|
||||
(void*) plugin, 0, NULL);
|
||||
|
||||
args = (ADDIN_ARGV*) plugin->channel_entry_points.pExtendedData;
|
||||
|
||||
@ -664,33 +671,59 @@ static void rdpsnd_process_connect(rdpSvcPlugin* plugin)
|
||||
rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args);
|
||||
}
|
||||
|
||||
#if defined(WITH_IOSAUDIO)
|
||||
if (!rdpsnd->device)
|
||||
{
|
||||
rdpsnd_set_subsystem(rdpsnd, "ios");
|
||||
rdpsnd_set_device_name(rdpsnd, "");
|
||||
rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WITH_OPENSLES)
|
||||
if (!rdpsnd->device)
|
||||
{
|
||||
rdpsnd_set_subsystem(rdpsnd, "opensles");
|
||||
rdpsnd_set_device_name(rdpsnd, "");
|
||||
rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WITH_PULSE)
|
||||
if (!rdpsnd->device)
|
||||
{
|
||||
rdpsnd_set_subsystem(rdpsnd, "pulse");
|
||||
rdpsnd_set_device_name(rdpsnd, "");
|
||||
rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WITH_ALSA)
|
||||
if (!rdpsnd->device)
|
||||
{
|
||||
rdpsnd_set_subsystem(rdpsnd, "alsa");
|
||||
rdpsnd_set_device_name(rdpsnd, "default");
|
||||
rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WITH_MACAUDIO)
|
||||
if (!rdpsnd->device)
|
||||
{
|
||||
rdpsnd_set_subsystem(rdpsnd, "macaudio");
|
||||
rdpsnd_set_device_name(rdpsnd, "default");
|
||||
rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WITH_WINMM)
|
||||
if (!rdpsnd->device)
|
||||
{
|
||||
rdpsnd_set_subsystem(rdpsnd, "winmm");
|
||||
rdpsnd_set_device_name(rdpsnd, "");
|
||||
rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!rdpsnd->device)
|
||||
{
|
||||
|
@ -22,6 +22,12 @@
|
||||
|
||||
#include <freerdp/channels/rdpsnd.h>
|
||||
|
||||
#if defined(WITH_DEBUG_SND)
|
||||
#define DEBUG_SND(fmt, ...) DEBUG_CLASS("rdpsnd", fmt, ## __VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_SND(fmt, ...) do { } while (0)
|
||||
#endif
|
||||
|
||||
struct _RDPSND_WAVE
|
||||
{
|
||||
BYTE* data;
|
||||
|
@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set(ANDROID_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/aFreeRDP")
|
||||
|
||||
if(ANDROID_BUILD_JAVA)
|
||||
if (NOT ANDROID_SDK)
|
||||
message(FATAL_ERROR "ANDROID_SDK not set but required for building the java gui (ANDROID_BUILD_JAVA)")
|
||||
@ -36,5 +38,11 @@ if(ANDROID_BUILD_JAVA)
|
||||
endif()
|
||||
endif(ANDROID_BUILD_JAVA)
|
||||
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
set(ANDROID_DEBUG_ENABLE "true")
|
||||
else()
|
||||
set(ANDROID_DEBUG_ENABLE "false")
|
||||
endif()
|
||||
|
||||
add_subdirectory(FreeRDPCore)
|
||||
add_subdirectory(aFreeRDP)
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
@ -11,6 +11,8 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<supports-screens android:anyDensity="true" android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" />
|
||||
|
||||
<application>
|
||||
|
@ -16,8 +16,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set(ANDROID_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(ANDROID_PACKAGE_NAME "aFreeRDPCore")
|
||||
file(MAKE_DIRECTORY ${ANDROID_BINARY_DIR})
|
||||
|
||||
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/AndroidManifest.xml.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml @ONLY)
|
||||
@ -39,6 +39,7 @@ add_subdirectory(jni)
|
||||
|
||||
|
||||
if(ANDROID_BUILD_JAVA)
|
||||
file(MAKE_DIRECTORY "${ANDROID_BINARY_DIR}/bin")
|
||||
set(ANDROIDLIB "${ANDROID_BINARY_DIR}/bin/classes.jar")
|
||||
|
||||
# command to create the android package
|
||||
|
@ -72,3 +72,12 @@ set_target_properties(${MODULE_NAME}
|
||||
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${ANDROID_BINARY_DIR}/libs/${ANDROID_ABI}")
|
||||
|
||||
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Android")
|
||||
|
||||
get_property(LIB_ABSNAME TARGET ${MODULE_NAME} PROPERTY LOCATION)
|
||||
file(MAKE_DIRECTORY ${ANDROID_BINARY_DIR}/obj/local/${ANDROID_NDK_ABI_NAME})
|
||||
|
||||
add_custom_command(TARGET ${MODULE_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${LIB_ABSNAME}
|
||||
${ANDROID_BINARY_DIR}/obj/local/${ANDROID_NDK_ABI_NAME}/)
|
||||
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -266,6 +267,8 @@ int android_freerdp_run(freerdp* instance)
|
||||
int select_status;
|
||||
struct timeval timeout;
|
||||
|
||||
assert(instance);
|
||||
|
||||
memset(rfds, 0, sizeof(rfds));
|
||||
memset(wfds, 0, sizeof(wfds));
|
||||
|
||||
@ -333,6 +336,9 @@ int android_freerdp_run(freerdp* instance)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (freerdp_shall_disconnect(instance))
|
||||
break;
|
||||
|
||||
if (freerdp_check_fds(instance) != TRUE)
|
||||
{
|
||||
@ -369,6 +375,9 @@ void* android_thread_func(void* param)
|
||||
struct thread_data* data;
|
||||
data = (struct thread_data*) param;
|
||||
|
||||
assert(data);
|
||||
assert(data->instance);
|
||||
|
||||
freerdp* instance = data->instance;
|
||||
android_freerdp_run(instance);
|
||||
free(data);
|
||||
@ -421,6 +430,10 @@ JNIEXPORT jboolean JNICALL jni_freerdp_connect(JNIEnv *env, jclass cls, jint ins
|
||||
struct thread_data* data = (struct thread_data*) malloc(sizeof(struct thread_data));
|
||||
data->instance = inst;
|
||||
|
||||
assert(inst);
|
||||
assert(data);
|
||||
assert(inst->context);
|
||||
|
||||
androidContext* ctx = (androidContext*)inst->context;
|
||||
pthread_create(&ctx->thread, 0, android_thread_func, data);
|
||||
|
||||
@ -666,6 +679,52 @@ JNIEXPORT void JNICALL jni_freerdp_set_drive_redirection(JNIEnv *env, jclass cls
|
||||
(*env)->ReleaseStringUTFChars(env, jpath, path);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_sound_redirection(JNIEnv *env,
|
||||
jclass cls, jint instance, jint redirect)
|
||||
{
|
||||
char** p;
|
||||
int count = 1;
|
||||
freerdp* inst = (freerdp*)instance;
|
||||
rdpSettings * settings = inst->settings;
|
||||
|
||||
DEBUG_ANDROID("sound: %s",
|
||||
redirect ? ((redirect == 1) ? "Server" : "Redirect") : "None");
|
||||
|
||||
settings->AudioPlayback = (redirect == 2) ? TRUE : FALSE;
|
||||
settings->RemoteConsoleAudio = (redirect == 1) ? TRUE : FALSE;
|
||||
if (settings->AudioPlayback)
|
||||
{
|
||||
p = malloc(sizeof(char*));
|
||||
p[0] = "rdpsnd";
|
||||
|
||||
freerdp_client_add_static_channel(settings, count, p);
|
||||
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_microphone_redirection(JNIEnv *env,
|
||||
jclass cls, jint instance, jboolean enable)
|
||||
{
|
||||
char** p;
|
||||
int count = 1;
|
||||
freerdp* inst = (freerdp*)instance;
|
||||
rdpSettings * settings = inst->settings;
|
||||
|
||||
DEBUG_ANDROID("microphone redirect: %s", enable ? "TRUE" : "FALSE");
|
||||
|
||||
settings->AudioCapture = enable;
|
||||
if (enable)
|
||||
{
|
||||
p = malloc(sizeof(char*));
|
||||
p[0] = "audin";
|
||||
|
||||
freerdp_client_add_dynamic_channel(settings, count, p);
|
||||
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_clipboard_redirection(JNIEnv *env, jclass cls, jint instance, jboolean enable)
|
||||
{
|
||||
freerdp* inst = (freerdp*)instance;
|
||||
|
@ -45,6 +45,8 @@ JNIEXPORT void JNICALL jni_freerdp_set_performance_flags(JNIEnv *env, jclass cls
|
||||
jboolean disableMenuAnimations, jboolean disableTheming, jboolean enableFontSmoothing, jboolean enableDesktopComposition);
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_advanced_settings(JNIEnv *env, jclass cls, jint instance, jstring jRemoteProgram, jstring jWorkDir);
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_drive_redirection(JNIEnv *env, jclass cls, jint instance, jstring jpath);
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_sound_redirection(JNIEnv *env, jclass cls, jint instance, jint redirect);
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_microphone_redirection(JNIEnv *env, jclass cls, jint instance, jboolean enable);
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_clipboard_redirection(JNIEnv *env, jclass cls, jint instance, jboolean enable);
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_gateway_info(JNIEnv *env, jclass cls, jint instance, jstring jgatewayhostname, jint port, jstring jgatewayusername, jstring jgatewaypassword, jstring jgatewaydomain);
|
||||
JNIEXPORT void JNICALL jni_freerdp_set_data_directory(JNIEnv *env, jclass cls, jint instance, jstring jdirectory);
|
||||
|
@ -76,6 +76,18 @@ JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_
|
||||
jni_freerdp_set_clipboard_redirection(env, cls, inst, enable);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1sound_1redirection
|
||||
(JNIEnv *env, jclass cls, jint inst, jint redirect)
|
||||
{
|
||||
jni_freerdp_set_sound_redirection(env, cls, inst, redirect);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1microphone_1redirection
|
||||
(JNIEnv *env, jclass cls, jint inst, jboolean redirect)
|
||||
{
|
||||
jni_freerdp_set_microphone_redirection(env, cls, inst, redirect);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1drive_1redirection
|
||||
(JNIEnv *env, jclass cls, jint inst, jstring path)
|
||||
{
|
||||
|
@ -87,6 +87,22 @@ JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_
|
||||
JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1clipboard_1redirection
|
||||
(JNIEnv *, jclass, jint, jboolean);
|
||||
|
||||
/*
|
||||
* Class: com_freerdp_freerdpcore_services_LibFreeRDP
|
||||
* Method: freerdp_set_sound_redirection
|
||||
* Signature: (IZ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1sound_1redirection
|
||||
(JNIEnv *, jclass, jint, jint);
|
||||
|
||||
/*
|
||||
* Class: com_freerdp_freerdpcore_services_LibFreeRDP
|
||||
* Method: freerdp_set_microphone_redirection
|
||||
* Signature: (IZ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1microphone_1redirection
|
||||
(JNIEnv *, jclass, jint, jboolean);
|
||||
|
||||
/*
|
||||
* Class: com_freerdp_freerdpcore_services_LibFreeRDP
|
||||
* Method: freerdp_set_drive_redirection
|
||||
|
@ -115,6 +115,18 @@
|
||||
<string name="settings_enable_gateway_settings">Enable Gateway</string>
|
||||
<string name="settings_gateway_settings">Gateway Settings</string>
|
||||
<string name="settings_redirect_sdcard">Redirect SDCard</string>
|
||||
<string name="settings_redirect_sound">Redirect Sound</string>
|
||||
<string-array name="redirect_sound_array">
|
||||
<item>Do not play</item>
|
||||
<item>Play on Server</item>
|
||||
<item>Play on Device</item>
|
||||
</string-array>
|
||||
<string-array name="redirect_sound_values_array">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
<string name="settings_redirect_microphone">Redirect Microphone</string>
|
||||
<string name="settings_security">Seguridad</string>
|
||||
<string-array name="security_array">
|
||||
<item>Automatico</item>
|
||||
|
@ -114,6 +114,18 @@
|
||||
<string name="settings_enable_gateway_settings">Enable Gateway</string>
|
||||
<string name="settings_gateway_settings">Gateway Settings</string>
|
||||
<string name="settings_redirect_sdcard">"Redirect SDCard"</string>
|
||||
<string name="settings_redirect_sound">Redirect Sound</string>
|
||||
<string-array name="redirect_sound_array">
|
||||
<item>Do not play</item>
|
||||
<item>Play on Server</item>
|
||||
<item>Play on Device</item>
|
||||
</string-array>
|
||||
<string-array name="redirect_sound_values_array">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
<string name="settings_redirect_microphone">Redirect Microphone</string>
|
||||
<string name="settings_security">"Securité"</string>
|
||||
<string-array name="security_array">
|
||||
<item>"Automatique"</item>
|
||||
|
@ -115,6 +115,18 @@
|
||||
<string name="settings_enable_gateway_settings">Gateway inschakelen</string>
|
||||
<string name="settings_gateway_settings">Gateway instellingen</string>
|
||||
<string name="settings_redirect_sdcard">SDCard aankoppelen</string>
|
||||
<string name="settings_redirect_sound">Redirect Sound</string>
|
||||
<string-array name="redirect_sound_array">
|
||||
<item>Do not play</item>
|
||||
<item>Play on Server</item>
|
||||
<item>Play on Device</item>
|
||||
</string-array>
|
||||
<string-array name="redirect_sound_values_array">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
<string name="settings_redirect_microphone">Redirect Microphone</string>
|
||||
<string name="settings_security">Beveiliging</string>
|
||||
<string-array name="security_array">
|
||||
<item>Automatisch</item>
|
||||
|
@ -112,6 +112,18 @@
|
||||
<string name="settings_enable_gateway_settings">Enable Gateway</string>
|
||||
<string name="settings_gateway_settings">Gateway Settings</string>
|
||||
<string name="settings_redirect_sdcard">Redirect SDCard</string>
|
||||
<string name="settings_redirect_sound">Redirect Sound</string>
|
||||
<string-array name="redirect_sound_array">
|
||||
<item>Do not play</item>
|
||||
<item>Play on Server</item>
|
||||
<item>Play on Device</item>
|
||||
</string-array>
|
||||
<string-array name="redirect_sound_values_array">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
<string name="settings_redirect_microphone">Redirect Microphone</string>
|
||||
<string name="settings_security">Security</string>
|
||||
<string-array name="security_array">
|
||||
<item>Automatic</item>
|
||||
|
@ -36,6 +36,8 @@
|
||||
</PreferenceScreen>
|
||||
|
||||
<CheckBoxPreference android:key="bookmark.redirect_sdcard" android:title="@string/settings_redirect_sdcard"/>
|
||||
<com.freerdp.freerdpcore.utils.IntListPreference android:key="bookmark.redirect_sound" android:title="@string/settings_redirect_sound" android:entries="@array/redirect_sound_array" android:entryValues="@array/redirect_sound_values_array" />
|
||||
<CheckBoxPreference android:key="bookmark.redirect_microphone" android:title="@string/settings_redirect_microphone"/>
|
||||
<com.freerdp.freerdpcore.utils.IntListPreference android:key="bookmark.security" android:title="@string/settings_security" android:entries="@array/security_array" android:entryValues="@array/security_values_array" />
|
||||
<EditTextPreference android:key="bookmark.remote_program" android:title="@string/settings_remote_program" android:summary="notepad.exe"/>
|
||||
<EditTextPreference android:key="bookmark.work_dir" android:title="@string/settings_work_dir"/>
|
||||
|
@ -82,6 +82,7 @@ public class GlobalApp extends Application implements LibFreeRDP.EventListener
|
||||
super.onCreate();
|
||||
|
||||
bookmarkDB = new BookmarkDB(this);
|
||||
|
||||
manualBookmarkGateway = new ManualBookmarkGateway(bookmarkDB);
|
||||
|
||||
historyDB = new HistoryDB(this);
|
||||
|
@ -295,6 +295,8 @@ public class BookmarkBase implements Parcelable, Cloneable
|
||||
private ScreenSettings screen3G;
|
||||
private PerformanceFlags performance3G;
|
||||
private boolean redirectSDCard;
|
||||
private int redirectSound;
|
||||
private boolean redirectMicrophone;
|
||||
private int security;
|
||||
private boolean consoleMode;
|
||||
private String remoteProgram;
|
||||
@ -309,6 +311,8 @@ public class BookmarkBase implements Parcelable, Cloneable
|
||||
screen3G = parcel.readParcelable(ScreenSettings.class.getClassLoader());
|
||||
performance3G = parcel.readParcelable(PerformanceFlags.class.getClassLoader());
|
||||
redirectSDCard = (parcel.readInt() == 1) ? true : false;
|
||||
redirectSound = parcel.readInt();
|
||||
redirectMicrophone = (parcel.readInt() == 1) ? true : false;
|
||||
security = parcel.readInt();
|
||||
consoleMode = (parcel.readInt() == 1) ? true : false;
|
||||
remoteProgram = parcel.readString();
|
||||
@ -320,6 +324,8 @@ public class BookmarkBase implements Parcelable, Cloneable
|
||||
screen3G = new ScreenSettings();
|
||||
performance3G = new PerformanceFlags();
|
||||
redirectSDCard = false;
|
||||
redirectSound = 0;
|
||||
redirectMicrophone = false;
|
||||
security = 0;
|
||||
consoleMode = false;
|
||||
remoteProgram = "";
|
||||
@ -358,6 +364,22 @@ public class BookmarkBase implements Parcelable, Cloneable
|
||||
return redirectSDCard;
|
||||
}
|
||||
|
||||
public void setRedirectSound(int redirect) {
|
||||
this.redirectSound = redirect;
|
||||
}
|
||||
|
||||
public int getRedirectSound() {
|
||||
return redirectSound;
|
||||
}
|
||||
|
||||
public void setRedirectMicrophone(boolean redirect) {
|
||||
this.redirectMicrophone = redirect;
|
||||
}
|
||||
|
||||
public boolean getRedirectMicrophone() {
|
||||
return redirectMicrophone;
|
||||
}
|
||||
|
||||
public void setSecurity(int security) {
|
||||
this.security = security;
|
||||
}
|
||||
@ -418,6 +440,8 @@ public class BookmarkBase implements Parcelable, Cloneable
|
||||
out.writeParcelable(screen3G, flags);
|
||||
out.writeParcelable(performance3G, flags);
|
||||
out.writeInt(redirectSDCard ? 1 : 0);
|
||||
out.writeInt(redirectSound);
|
||||
out.writeInt(redirectMicrophone ? 1 : 0);
|
||||
out.writeInt(security);
|
||||
out.writeInt(consoleMode ? 1 : 0);
|
||||
out.writeString(remoteProgram);
|
||||
@ -620,6 +644,8 @@ public class BookmarkBase implements Parcelable, Cloneable
|
||||
editor.putBoolean("bookmark.perf_themes_3g", advancedSettings.getPerformance3G().getTheming());
|
||||
|
||||
editor.putBoolean("bookmark.redirect_sdcard", advancedSettings.getRedirectSDCard());
|
||||
editor.putInt("bookmark.redirect_sound", advancedSettings.getRedirectSound());
|
||||
editor.putBoolean("bookmark.redirect_microphone", advancedSettings.getRedirectMicrophone());
|
||||
editor.putInt("bookmark.security", advancedSettings.getSecurity());
|
||||
editor.putString("bookmark.remote_program", advancedSettings.getRemoteProgram());
|
||||
editor.putString("bookmark.work_dir", advancedSettings.getWorkDir());
|
||||
@ -662,6 +688,8 @@ public class BookmarkBase implements Parcelable, Cloneable
|
||||
advancedSettings.getPerformance3G().setTheming(sharedPrefs.getBoolean("bookmark.perf_themes_3g", false));
|
||||
|
||||
advancedSettings.setRedirectSDCard(sharedPrefs.getBoolean("bookmark.redirect_sdcard", false));
|
||||
advancedSettings.setRedirectSound(sharedPrefs.getInt("bookmark.redirect_sound", 0));
|
||||
advancedSettings.setRedirectMicrophone(sharedPrefs.getBoolean("bookmark.redirect_microphone", false));
|
||||
advancedSettings.setSecurity(sharedPrefs.getInt("bookmark.security", 0));
|
||||
advancedSettings.setRemoteProgram(sharedPrefs.getString("bookmark.remote_program", ""));
|
||||
advancedSettings.setWorkDir(sharedPrefs.getString("bookmark.work_dir", ""));
|
||||
|
@ -64,6 +64,8 @@ public abstract class BookmarkBaseGateway
|
||||
rowid = insertPerformanceFlags(db, bookmark.getAdvancedSettings().getPerformance3G());
|
||||
values.put("performance_3g", rowid);
|
||||
values.put("redirect_sdcard", bookmark.getAdvancedSettings().getRedirectSDCard());
|
||||
values.put("redirect_sound", bookmark.getAdvancedSettings().getRedirectSound());
|
||||
values.put("redirect_microphone", bookmark.getAdvancedSettings().getRedirectMicrophone());
|
||||
values.put("security", bookmark.getAdvancedSettings().getSecurity());
|
||||
values.put("console_mode", bookmark.getAdvancedSettings().getConsoleMode());
|
||||
values.put("remote_program", bookmark.getAdvancedSettings().getRemoteProgram());
|
||||
@ -100,6 +102,8 @@ public abstract class BookmarkBaseGateway
|
||||
updateScreenSettings3G(db, bookmark);
|
||||
updatePerformanceFlags3G(db, bookmark);
|
||||
values.put("redirect_sdcard", bookmark.getAdvancedSettings().getRedirectSDCard());
|
||||
values.put("redirect_sound", bookmark.getAdvancedSettings().getRedirectSound());
|
||||
values.put("redirect_microphone", bookmark.getAdvancedSettings().getRedirectMicrophone());
|
||||
values.put("security", bookmark.getAdvancedSettings().getSecurity());
|
||||
values.put("console_mode", bookmark.getAdvancedSettings().getConsoleMode());
|
||||
values.put("remote_program", bookmark.getAdvancedSettings().getRemoteProgram());
|
||||
@ -219,6 +223,8 @@ public abstract class BookmarkBaseGateway
|
||||
// advanced settings
|
||||
columns.add("enable_3g_settings");
|
||||
columns.add("redirect_sdcard");
|
||||
columns.add("redirect_sound");
|
||||
columns.add("redirect_microphone");
|
||||
columns.add("security");
|
||||
columns.add("console_mode");
|
||||
columns.add("remote_program");
|
||||
@ -277,6 +283,8 @@ public abstract class BookmarkBaseGateway
|
||||
readScreenSettings3G(bookmark, cursor);
|
||||
readPerformanceFlags3G(bookmark, cursor);
|
||||
bookmark.getAdvancedSettings().setRedirectSDCard(cursor.getInt(cursor.getColumnIndex("redirect_sdcard")) == 0 ? false : true);
|
||||
bookmark.getAdvancedSettings().setRedirectSound(cursor.getInt(cursor.getColumnIndex("redirect_sound")));
|
||||
bookmark.getAdvancedSettings().setRedirectMicrophone(cursor.getInt(cursor.getColumnIndex("redirect_microphone")) == 0 ? false : true);
|
||||
bookmark.getAdvancedSettings().setSecurity(cursor.getInt(cursor.getColumnIndex("security")));
|
||||
bookmark.getAdvancedSettings().setConsoleMode(cursor.getInt(cursor.getColumnIndex("console_mode")) == 0 ? false : true);
|
||||
bookmark.getAdvancedSettings().setRemoteProgram(cursor.getString(cursor.getColumnIndex("remote_program")));
|
||||
|
@ -22,14 +22,14 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
public class BookmarkDB extends SQLiteOpenHelper
|
||||
{
|
||||
private static final int DB_VERSION = 3;
|
||||
private static final int DB_VERSION = 4;
|
||||
private static final String DB_NAME = "bookmarks.db";
|
||||
|
||||
public static final String ID = BaseColumns._ID;
|
||||
|
||||
|
||||
public BookmarkDB(Context context)
|
||||
{
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,6 +101,8 @@ public class BookmarkDB extends SQLiteOpenHelper
|
||||
+ "screen_3g, "
|
||||
+ "performance_3g, "
|
||||
+ "redirect_sdcard, "
|
||||
+ "redirect_sound, "
|
||||
+ "redirect_microphone, "
|
||||
+ "security, "
|
||||
+ "remote_program, "
|
||||
+ "work_dir, "
|
||||
@ -112,7 +114,7 @@ public class BookmarkDB extends SQLiteOpenHelper
|
||||
+ "'', "
|
||||
+ "'', "
|
||||
+ "3389, "
|
||||
+ "1, 1, 2, 2, 0, 0, '', '', 0);";
|
||||
+ "1, 1, 2, 2, 0, 0, 0, 0, '', '', 0);";
|
||||
db.execSQL(sqlInsertDefaultSessionEntry);
|
||||
}
|
||||
|
||||
@ -140,7 +142,9 @@ public class BookmarkDB extends SQLiteOpenHelper
|
||||
+ "enable_3g_settings INTEGER DEFAULT 0, "
|
||||
+ "screen_3g INTEGER NOT NULL, "
|
||||
+ "performance_3g INTEGER NOT NULL, "
|
||||
+ "redirect_sdcard INTEGER, "
|
||||
+ "redirect_sdcard INTEGER DEFAULT 0, "
|
||||
+ "redirect_sound INTEGER DEFAULT 0, "
|
||||
+ "redirect_microphone INTEGER DEFAULT 0, "
|
||||
+ "security INTEGER, "
|
||||
+ "remote_program TEXT, "
|
||||
+ "work_dir TEXT, "
|
||||
|
@ -40,6 +40,8 @@ public class LibFreeRDP
|
||||
private static native void freerdp_set_data_directory(int inst, String directory);
|
||||
|
||||
private static native void freerdp_set_clipboard_redirection(int inst, boolean enable);
|
||||
private static native void freerdp_set_sound_redirection(int inst, int redirect);
|
||||
private static native void freerdp_set_microphone_redirection(int inst, boolean enable);
|
||||
private static native void freerdp_set_drive_redirection(int inst, String path);
|
||||
|
||||
private static native void freerdp_set_gateway_info(int inst, String gatewayhostname, int port,
|
||||
@ -165,7 +167,15 @@ public class LibFreeRDP
|
||||
freerdp_set_gateway_info(inst, gatewaySettings.getHostname(), gatewaySettings.getPort(),
|
||||
gatewaySettings.getUsername(), gatewaySettings.getPassword(), gatewaySettings.getDomain());
|
||||
}
|
||||
|
||||
|
||||
// Sound redirection
|
||||
freerdp_set_sound_redirection(inst,
|
||||
advancedSettings.getRedirectSound());
|
||||
|
||||
// Microphone redirection
|
||||
freerdp_set_microphone_redirection(inst,
|
||||
advancedSettings.getRedirectMicrophone());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
@ -3,6 +3,7 @@
|
||||
<name>aFreeRDP</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>FreeRDPCore</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
|
@ -16,7 +16,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set(ANDROID_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(ANDROID_PACKAGE_NAME "aFreeRDP")
|
||||
|
||||
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/AndroidManifest.xml.cmake
|
||||
@ -36,6 +35,32 @@ if (ANDROID_SDK)
|
||||
${CMAKE_CURRENT_BINARY_DIR}/local.properties @ONLY)
|
||||
endif()
|
||||
|
||||
if("${ANDROID_DEBUG_ENABLE}" STREQUAL "true")
|
||||
# 1. generate Android.mk
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/jni/Android.mk
|
||||
"APP_ABI := ${ANDROID_NDK_ABI_NAME}\n")
|
||||
|
||||
# 2. generate gdb.setup
|
||||
get_directory_property(INCLUDE_DIRECTORIES DIRECTORY . INCLUDE_DIRECTORIES)
|
||||
string(REGEX REPLACE ";" " " INCLUDE_DIRECTORIES "${INCLUDE_DIRECTORIES}")
|
||||
set(LIB_DIRECTORIES "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}")
|
||||
file(WRITE ${ANDROID_BINARY_DIR}/libs/${ANDROID_NDK_ABI_NAME}/gdb.setup
|
||||
"set solib-search-path ${CMAKE_CURRENT_BINARY_DIR}/obj/local/${ANDROID_NDK_ABI_NAME}\n")
|
||||
file(APPEND ${ANDROID_BINARY_DIR}/libs/${ANDROID_NDK_ABI_NAME}/gdb.setup
|
||||
"directory ${INCLUDE_DIRECTORIES} ${LIB_DIRECTORIES}\n")
|
||||
|
||||
# 3. copy gdbserver executable
|
||||
file(COPY ${ANDROID_NDK}/prebuilt/android-${ANDROID_ARCH_NAME}/gdbserver/gdbserver
|
||||
DESTINATION ${ANDROID_BINARY_DIR}/libs/${ANDROID_NDK_ABI_NAME}/)
|
||||
|
||||
# 4. Convenience target to launch debugger.
|
||||
add_custom_target(debug-ndk
|
||||
COMMAND adb install -r bin/aFreeRDP-debug.apk
|
||||
COMMAND ndk-gdb --start --verbose --force
|
||||
WORKING_DIRECTORY ${CURRENT_BINARY_DIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ANDROID_BUILD_JAVA)
|
||||
if(NOT ANDROID_BUILD_JAVA_DEBUG)
|
||||
set(APK "${ANDROID_BINARY_DIR}/bin/${ANDROID_PACKAGE_NAME}-release-unsigned.apk")
|
||||
|
@ -107,6 +107,7 @@ option(WITH_DEBUG_RDP "Print RDP debug messages" ${DEFAULT_DEBUG_OPTION})
|
||||
option(WITH_DEBUG_REDIR "Redirection debug messages" ${DEFAULT_DEBUG_OPTION})
|
||||
option(WITH_DEBUG_RFX "Print RemoteFX debug messages." ${DEFAULT_DEBUG_OPTION})
|
||||
option(WITH_DEBUG_SCARD "Print smartcard debug messages" ${DEFAULT_DEBUG_OPTION})
|
||||
option(WITH_DEBUG_SND "Print rdpsnd debug messages" ${DEFAULT_DEBUG_OPTION})
|
||||
option(WITH_DEBUG_SVC "Print static virtual channel debug messages." ${DEFAULT_DEBUG_OPTION})
|
||||
option(WITH_DEBUG_TRANSPORT "Print transport debug messages." ${DEFAULT_DEBUG_OPTION})
|
||||
option(WITH_DEBUG_TIMEZONE "Print timezone debug messages." ${DEFAULT_DEBUG_OPTION})
|
||||
|
@ -21,10 +21,11 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
endif()
|
||||
|
||||
option(WITH_DEBUG_ANDROID_JNI "Enable debug output for android jni bindings" ${DEFAULT_DEBUG_OPTION})
|
||||
option(WITH_OPENSLES "Enable sound and microphone redirection using OpenSLES" ON)
|
||||
option(ANDROID_BUILD_JAVA "Automatically android java code - build type depends on CMAKE_BUILD_TYPE" ON)
|
||||
option(ANDROID_BUILD_JAVA_DEBUG "Create a android debug package" ${JAVA_DEBUG_DEFAULT})
|
||||
|
||||
set(ANDROID_APP_TARGET_SDK 11 CACHE STRING "Application target android SDK")
|
||||
set(ANDROID_APP_MIN_SDK 8 CACHE STRING "Application minimum android SDK requirement")
|
||||
set(ANDROID_APP_MIN_SDK 9 CACHE STRING "Application minimum android SDK requirement")
|
||||
set(ANDROID_APP_GOOGLE_TARGET_SDK "16" CACHE STRING "Application target google SDK")
|
||||
|
||||
|
34
cmake/FindOpenSLES.cmake
Normal file
34
cmake/FindOpenSLES.cmake
Normal file
@ -0,0 +1,34 @@
|
||||
# - Find OpenSLES
|
||||
# Find the OpenSLES includes and libraries
|
||||
#
|
||||
# OPENSLES_INCLUDE_DIR - where to find dsound.h
|
||||
# OPENSLES_LIBRARIES - List of libraries when using dsound.
|
||||
# OPENSLES_FOUND - True if dsound found.
|
||||
|
||||
if(OPENSLES_INCLUDE_DIR)
|
||||
# Already in cache, be silent
|
||||
set(OPENSLES_FIND_QUIETLY TRUE)
|
||||
else()
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_OPENSLES QUIET OpenSLES)
|
||||
endif(OPENSLES_INCLUDE_DIR)
|
||||
|
||||
find_path(OPENSLES_INCLUDE_DIR SLES/OpenSLES.h
|
||||
HINTS ${PC_OPENSLES_INCLUDE_DIR})
|
||||
|
||||
find_library(OPENSLES_LIBRARY NAMES OpenSLES
|
||||
HINTS ${PC_OPENSLES_LIBDIR} ${PC_OPENSLES_LIBARRY_DIRS})
|
||||
|
||||
# Handle the QUIETLY and REQUIRED arguments and set OPENSL_FOUND to TRUE if
|
||||
# all listed variables are TRUE.
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(OPENSLES DEFAULT_MSG
|
||||
OPENSLES_INCLUDE_DIR OPENSLES_LIBRARY)
|
||||
|
||||
if(OPENSLES_FOUND)
|
||||
set(OPENSLES_LIBRARIES ${OPENSLES_LIBRARY})
|
||||
else(OPENSLES_FOUND)
|
||||
set(OPENSLES_LIBRARIES)
|
||||
endif(OPENSLES_FOUND)
|
||||
|
||||
mark_as_advanced(OPENSLES_INCLUDE_DIR OPENSLES_LIBRARY)
|
@ -46,6 +46,13 @@
|
||||
#cmakedefine WITH_WIN8
|
||||
#cmakedefine WITH_RDPSND_DSOUND
|
||||
|
||||
#cmakedefine WITH_WINMM
|
||||
#cmakedefine WITH_MACAUDIO
|
||||
#cmakedefine WITH_ALSA
|
||||
#cmakedefine WITH_PULSE
|
||||
#cmakedefine WITH_IOSAUDIO
|
||||
#cmakedefine WITH_OPENSLES
|
||||
|
||||
/* Plugins */
|
||||
#cmakedefine STATIC_CHANNELS
|
||||
#cmakedefine WITH_RDPDR
|
||||
@ -70,6 +77,7 @@
|
||||
#cmakedefine WITH_DEBUG_REDIR
|
||||
#cmakedefine WITH_DEBUG_RFX
|
||||
#cmakedefine WITH_DEBUG_SCARD
|
||||
#cmakedefine WITH_DEBUG_SND
|
||||
#cmakedefine WITH_DEBUG_SVC
|
||||
#cmakedefine WITH_DEBUG_RDPEI
|
||||
#cmakedefine WITH_DEBUG_TIMEZONE
|
||||
|
Loading…
Reference in New Issue
Block a user