Merge pull request #1514 from akallabeth/android_sound

Android sound support
This commit is contained in:
Bernhard Miklautz 2013-10-01 02:20:44 -07:00
commit d7a9add3b4
42 changed files with 2283 additions and 31 deletions

View File

@ -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})

View File

@ -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()

View File

@ -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;

View File

@ -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)
{

View 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()

View 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;
}

View 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;
}

View 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

View File

@ -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();

View File

@ -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()

View 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()

View 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);
}

View 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

View 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;
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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}/)

View File

@ -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;

View File

@ -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);

View File

@ -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)
{

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"/>

View File

@ -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);

View File

@ -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", ""));

View File

@ -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")));

View File

@ -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, "

View File

@ -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;
}

View File

@ -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>

View File

@ -3,6 +3,7 @@
<name>aFreeRDP</name>
<comment></comment>
<projects>
<project>FreeRDPCore</project>
</projects>
<buildSpec>
<buildCommand>

View File

@ -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")

View File

@ -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})

View File

@ -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
View 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)

View File

@ -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