Milestone 5: deliver embedded RDP sessions and lifecycle hardening

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

View File

@@ -0,0 +1,29 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2013 Armin Novak <armin.novak@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
define_channel_client_subsystem("rdpsnd" "opensles" "")
find_package(OpenSLES REQUIRED)
set(${MODULE_PREFIX}_SRCS opensl_io.c rdpsnd_opensles.c)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${OpenSLES_LIBRARIES})
include_directories(..)
include_directories(SYSTEM ${OpenSLES_INCLUDE_DIRS})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,422 @@
/*
opensl_io.c:
Android OpenSL input/output module
Copyright (c) 2012, Victor Lazzarini
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <winpr/assert.h>
#include "rdpsnd_main.h"
#include "opensl_io.h"
#define CONV16BIT 32768
#define CONVMYFLT (1. / 32768.)
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
// creates the OpenSL ES audio engine
static SLresult openSLCreateEngine(OPENSL_STREAM* p)
{
SLresult result;
// create engine
result = slCreateEngine(&(p->engineObject), 0, nullptr, 0, nullptr, nullptr);
DEBUG_SND("engineObject=%p", (void*)p->engineObject);
if (result != SL_RESULT_SUCCESS)
goto engine_end;
// realize the engine
result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE);
DEBUG_SND("Realize=%" PRIu32 "", result);
if (result != SL_RESULT_SUCCESS)
goto engine_end;
// get the engine interface, which is needed in order to create other objects
result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_ENGINE, &(p->engineEngine));
DEBUG_SND("engineEngine=%p", (void*)p->engineEngine);
if (result != SL_RESULT_SUCCESS)
goto engine_end;
engine_end:
return result;
}
// opens the OpenSL ES device for output
static SLresult openSLPlayOpen(OPENSL_STREAM* p)
{
SLresult result;
SLuint32 sr = p->sr;
SLuint32 channels = p->outchannels;
WINPR_ASSERT(p->engineObject);
WINPR_ASSERT(p->engineEngine);
if (channels)
{
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
p->queuesize };
switch (sr)
{
case 8000:
sr = SL_SAMPLINGRATE_8;
break;
case 11025:
sr = SL_SAMPLINGRATE_11_025;
break;
case 16000:
sr = SL_SAMPLINGRATE_16;
break;
case 22050:
sr = SL_SAMPLINGRATE_22_05;
break;
case 24000:
sr = SL_SAMPLINGRATE_24;
break;
case 32000:
sr = SL_SAMPLINGRATE_32;
break;
case 44100:
sr = SL_SAMPLINGRATE_44_1;
break;
case 48000:
sr = SL_SAMPLINGRATE_48;
break;
case 64000:
sr = SL_SAMPLINGRATE_64;
break;
case 88200:
sr = SL_SAMPLINGRATE_88_2;
break;
case 96000:
sr = SL_SAMPLINGRATE_96;
break;
case 192000:
sr = SL_SAMPLINGRATE_192;
break;
default:
return -1;
}
const SLInterfaceID ids[] = { SL_IID_VOLUME };
const SLboolean req[] = { SL_BOOLEAN_FALSE };
result = (*p->engineEngine)
->CreateOutputMix(p->engineEngine, &(p->outputMixObject), 1, ids, req);
DEBUG_SND("engineEngine=%p", (void*)p->engineEngine);
WINPR_ASSERT(!result);
if (result != SL_RESULT_SUCCESS)
goto end_openaudio;
// realize the output mix
result = (*p->outputMixObject)->Realize(p->outputMixObject, SL_BOOLEAN_FALSE);
DEBUG_SND("Realize=%" PRIu32 "", result);
WINPR_ASSERT(!result);
if (result != SL_RESULT_SUCCESS)
goto end_openaudio;
int speakers;
if (channels > 1)
speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
else
speakers = SL_SPEAKER_FRONT_CENTER;
SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM,
channels,
sr,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
speakers,
SL_BYTEORDER_LITTLEENDIAN };
SLDataSource audioSrc = { &loc_bufq, &format_pcm };
// configure audio sink
SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, p->outputMixObject };
SLDataSink audioSnk = { &loc_outmix, nullptr };
// create audio player
const SLInterfaceID ids1[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
const SLboolean req1[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
result = (*p->engineEngine)
->CreateAudioPlayer(p->engineEngine, &(p->bqPlayerObject), &audioSrc,
&audioSnk, 2, ids1, req1);
DEBUG_SND("bqPlayerObject=%p", (void*)p->bqPlayerObject);
WINPR_ASSERT(!result);
if (result != SL_RESULT_SUCCESS)
goto end_openaudio;
// realize the player
result = (*p->bqPlayerObject)->Realize(p->bqPlayerObject, SL_BOOLEAN_FALSE);
DEBUG_SND("Realize=%" PRIu32 "", result);
WINPR_ASSERT(!result);
if (result != SL_RESULT_SUCCESS)
goto end_openaudio;
// get the play interface
result =
(*p->bqPlayerObject)->GetInterface(p->bqPlayerObject, SL_IID_PLAY, &(p->bqPlayerPlay));
DEBUG_SND("bqPlayerPlay=%p", (void*)p->bqPlayerPlay);
WINPR_ASSERT(!result);
if (result != SL_RESULT_SUCCESS)
goto end_openaudio;
// get the volume interface
result = (*p->bqPlayerObject)
->GetInterface(p->bqPlayerObject, SL_IID_VOLUME, &(p->bqPlayerVolume));
DEBUG_SND("bqPlayerVolume=%p", (void*)p->bqPlayerVolume);
WINPR_ASSERT(!result);
if (result != SL_RESULT_SUCCESS)
goto end_openaudio;
// get the buffer queue interface
result = (*p->bqPlayerObject)
->GetInterface(p->bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&(p->bqPlayerBufferQueue));
DEBUG_SND("bqPlayerBufferQueue=%p", (void*)p->bqPlayerBufferQueue);
WINPR_ASSERT(!result);
if (result != SL_RESULT_SUCCESS)
goto end_openaudio;
// register callback on the buffer queue
result = (*p->bqPlayerBufferQueue)
->RegisterCallback(p->bqPlayerBufferQueue, bqPlayerCallback, p);
DEBUG_SND("bqPlayerCallback=%p", (void*)p->bqPlayerCallback);
WINPR_ASSERT(!result);
if (result != SL_RESULT_SUCCESS)
goto end_openaudio;
// set the player's state to playing
result = (*p->bqPlayerPlay)->SetPlayState(p->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
DEBUG_SND("SetPlayState=%" PRIu32 "", result);
WINPR_ASSERT(!result);
end_openaudio:
WINPR_ASSERT(!result);
return result;
}
return SL_RESULT_SUCCESS;
}
// close the OpenSL IO and destroy the audio engine
static void openSLDestroyEngine(OPENSL_STREAM* p)
{
// destroy buffer queue audio player object, and invalidate all associated interfaces
if (p->bqPlayerObject != nullptr)
{
(*p->bqPlayerObject)->Destroy(p->bqPlayerObject);
p->bqPlayerObject = nullptr;
p->bqPlayerVolume = nullptr;
p->bqPlayerPlay = nullptr;
p->bqPlayerBufferQueue = nullptr;
p->bqPlayerEffectSend = nullptr;
}
// destroy output mix object, and invalidate all associated interfaces
if (p->outputMixObject != nullptr)
{
(*p->outputMixObject)->Destroy(p->outputMixObject);
p->outputMixObject = nullptr;
}
// destroy engine object, and invalidate all associated interfaces
if (p->engineObject != nullptr)
{
(*p->engineObject)->Destroy(p->engineObject);
p->engineObject = nullptr;
p->engineEngine = nullptr;
}
}
// open the android audio device for and/or output
OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, int bufferframes)
{
OPENSL_STREAM* p;
p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM));
if (!p)
return nullptr;
p->queuesize = bufferframes;
p->outchannels = outchannels;
p->sr = sr;
if (openSLCreateEngine(p) != SL_RESULT_SUCCESS)
{
android_CloseAudioDevice(p);
return nullptr;
}
if (openSLPlayOpen(p) != SL_RESULT_SUCCESS)
{
android_CloseAudioDevice(p);
return nullptr;
}
p->queue = Queue_New(TRUE, -1, -1);
if (!p->queue)
{
android_CloseAudioDevice(p);
return nullptr;
}
return p;
}
// close the android audio device
void android_CloseAudioDevice(OPENSL_STREAM* p)
{
if (p == nullptr)
return;
openSLDestroyEngine(p);
if (p->queue)
Queue_Free(p->queue);
free(p);
}
// this callback handler is called every time a buffer finishes playing
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
{
OPENSL_STREAM* p = (OPENSL_STREAM*)context;
WINPR_ASSERT(p);
WINPR_ASSERT(p->queue);
void* data = Queue_Dequeue(p->queue);
free(data);
}
// puts a buffer of size samples to the device
int android_AudioOut(OPENSL_STREAM* p, const short* buffer, int size)
{
HANDLE ev;
WINPR_ASSERT(p);
WINPR_ASSERT(buffer);
WINPR_ASSERT(size > 0);
ev = Queue_Event(p->queue);
/* Assure, that the queue is not full. */
if (p->queuesize <= Queue_Count(p->queue) && WaitForSingleObject(ev, INFINITE) == WAIT_FAILED)
{
DEBUG_SND("WaitForSingleObject failed!");
return -1;
}
void* data = calloc(size, sizeof(short));
if (!data)
{
DEBUG_SND("unable to allocate a buffer");
return -1;
}
memcpy(data, buffer, size * sizeof(short));
Queue_Enqueue(p->queue, data);
(*p->bqPlayerBufferQueue)->Enqueue(p->bqPlayerBufferQueue, data, sizeof(short) * size);
return size;
}
int android_GetOutputMute(OPENSL_STREAM* p)
{
SLboolean mute;
WINPR_ASSERT(p);
WINPR_ASSERT(p->bqPlayerVolume);
SLresult rc = (*p->bqPlayerVolume)->GetMute(p->bqPlayerVolume, &mute);
if (SL_RESULT_SUCCESS != rc)
return SL_BOOLEAN_FALSE;
return mute;
}
BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL _mute)
{
SLboolean mute = _mute;
WINPR_ASSERT(p);
WINPR_ASSERT(p->bqPlayerVolume);
SLresult rc = (*p->bqPlayerVolume)->SetMute(p->bqPlayerVolume, mute);
if (SL_RESULT_SUCCESS != rc)
return FALSE;
return TRUE;
}
int android_GetOutputVolume(OPENSL_STREAM* p)
{
SLmillibel level;
WINPR_ASSERT(p);
WINPR_ASSERT(p->bqPlayerVolume);
SLresult rc = (*p->bqPlayerVolume)->GetVolumeLevel(p->bqPlayerVolume, &level);
if (SL_RESULT_SUCCESS != rc)
return 0;
return level;
}
int android_GetOutputVolumeMax(OPENSL_STREAM* p)
{
SLmillibel level;
WINPR_ASSERT(p);
WINPR_ASSERT(p->bqPlayerVolume);
SLresult rc = (*p->bqPlayerVolume)->GetMaxVolumeLevel(p->bqPlayerVolume, &level);
if (SL_RESULT_SUCCESS != rc)
return 0;
return level;
}
BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level)
{
SLresult rc = (*p->bqPlayerVolume)->SetVolumeLevel(p->bqPlayerVolume, level);
if (SL_RESULT_SUCCESS != rc)
return FALSE;
return TRUE;
}

View File

@@ -0,0 +1,110 @@
/*
opensl_io.c:
Android OpenSL input/output module header
Copyright (c) 2012, Victor Lazzarini
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H
#define FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <stdlib.h>
#include <winpr/synch.h>
#include <freerdp/api.h>
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct
{
// engine interfaces
SLObjectItf engineObject;
SLEngineItf engineEngine;
// output mix interfaces
SLObjectItf outputMixObject;
// buffer queue player interfaces
SLObjectItf bqPlayerObject;
SLPlayItf bqPlayerPlay;
SLVolumeItf bqPlayerVolume;
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
SLEffectSendItf bqPlayerEffectSend;
unsigned int outchannels;
unsigned int sr;
unsigned int queuesize;
wQueue* queue;
} OPENSL_STREAM;
/*
Open the audio device with a given sampling rate (sr), output channels and IO buffer size
in frames. Returns a handle to the OpenSL stream
*/
FREERDP_LOCAL OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, int bufferframes);
/*
Close the audio device
*/
FREERDP_LOCAL void android_CloseAudioDevice(OPENSL_STREAM* p);
/*
Write a buffer to the OpenSL stream *p, of size samples. Returns the number of samples written.
*/
FREERDP_LOCAL int android_AudioOut(OPENSL_STREAM* p, const short* buffer, int size);
/*
* Set the volume input level.
*/
FREERDP_LOCAL void android_SetInputVolume(OPENSL_STREAM* p, int level);
/*
* Get the current output mute setting.
*/
FREERDP_LOCAL int android_GetOutputMute(OPENSL_STREAM* p);
/*
* Change the current output mute setting.
*/
FREERDP_LOCAL BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL mute);
/*
* Get the current output volume level.
*/
FREERDP_LOCAL int android_GetOutputVolume(OPENSL_STREAM* p);
/*
* Get the maximum output volume level.
*/
FREERDP_LOCAL int android_GetOutputVolumeMax(OPENSL_STREAM* p);
/*
* Set the volume output level.
*/
FREERDP_LOCAL BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level);
#ifdef __cplusplus
};
#endif
#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H */

View File

@@ -0,0 +1,373 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Output Virtual Channel
*
* Copyright 2013 Armin Novak <armin.novak@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <winpr/assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <winpr/crt.h>
#include <winpr/cmdline.h>
#include <winpr/sysinfo.h>
#include <winpr/collections.h>
#include <freerdp/types.h>
#include <freerdp/channels/log.h>
#include "opensl_io.h"
#include "rdpsnd_main.h"
typedef struct
{
rdpsndDevicePlugin device;
UINT32 latency;
int wformat;
int block_size;
char* device_name;
OPENSL_STREAM* stream;
UINT32 volume;
UINT32 rate;
UINT32 channels;
int format;
} rdpsndopenslesPlugin;
static int rdpsnd_opensles_volume_to_millibel(unsigned short level, int max)
{
const int min = SL_MILLIBEL_MIN;
const int step = max - min;
const int rc = (level * step / 0xFFFF) + min;
DEBUG_SND("level=%hu, min=%d, max=%d, step=%d, result=%d", level, min, max, step, rc);
return rc;
}
static unsigned short rdpsnd_opensles_millibel_to_volume(int millibel, int max)
{
const int min = SL_MILLIBEL_MIN;
const int range = max - min;
const int rc = ((millibel - min) * 0xFFFF + range / 2 + 1) / range;
DEBUG_SND("millibel=%d, min=%d, max=%d, range=%d, result=%d", millibel, min, max, range, rc);
return rc;
}
static bool rdpsnd_opensles_check_handle(const rdpsndopenslesPlugin* hdl)
{
bool rc = true;
if (!hdl)
rc = false;
else
{
if (!hdl->stream)
rc = false;
}
return rc;
}
static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, UINT32 volume);
static int rdpsnd_opensles_set_params(rdpsndopenslesPlugin* opensles)
{
DEBUG_SND("opensles=%p", (void*)opensles);
if (!rdpsnd_opensles_check_handle(opensles))
return 0;
if (opensles->stream)
android_CloseAudioDevice(opensles->stream);
opensles->stream = android_OpenAudioDevice(opensles->rate, opensles->channels, 20);
return 0;
}
static BOOL rdpsnd_opensles_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
UINT32 latency)
{
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
rdpsnd_opensles_check_handle(opensles);
DEBUG_SND("opensles=%p format=%p, latency=%" PRIu32, (void*)opensles, (void*)format, latency);
if (format)
{
DEBUG_SND("format=%" PRIu16 ", cbsize=%" PRIu16 ", samples=%" PRIu32 ", bits=%" PRIu16
", channels=%" PRIu16 ", align=%" PRIu16 "",
format->wFormatTag, format->cbSize, format->nSamplesPerSec,
format->wBitsPerSample, format->nChannels, format->nBlockAlign);
opensles->rate = format->nSamplesPerSec;
opensles->channels = format->nChannels;
opensles->format = format->wFormatTag;
opensles->wformat = format->wFormatTag;
opensles->block_size = format->nBlockAlign;
}
opensles->latency = latency;
return (rdpsnd_opensles_set_params(opensles) == 0);
}
static BOOL rdpsnd_opensles_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format,
UINT32 latency)
{
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
DEBUG_SND("opensles=%p format=%p, latency=%" PRIu32 ", rate=%" PRIu32 "", (void*)opensles,
(void*)format, latency, opensles->rate);
if (rdpsnd_opensles_check_handle(opensles))
return TRUE;
opensles->stream = android_OpenAudioDevice(opensles->rate, opensles->channels, 20);
WINPR_ASSERT(opensles->stream);
if (!opensles->stream)
WLog_ERR(TAG, "android_OpenAudioDevice failed");
else
rdpsnd_opensles_set_volume(device, opensles->volume);
return rdpsnd_opensles_set_format(device, format, latency);
}
static void rdpsnd_opensles_close(rdpsndDevicePlugin* device)
{
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
DEBUG_SND("opensles=%p", (void*)opensles);
if (!rdpsnd_opensles_check_handle(opensles))
return;
android_CloseAudioDevice(opensles->stream);
opensles->stream = nullptr;
}
static void rdpsnd_opensles_free(rdpsndDevicePlugin* device)
{
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
DEBUG_SND("opensles=%p", (void*)opensles);
WINPR_ASSERT(opensles);
WINPR_ASSERT(opensles->device_name);
free(opensles->device_name);
free(opensles);
}
static BOOL rdpsnd_opensles_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format)
{
DEBUG_SND("format=%" PRIu16 ", cbsize=%" PRIu16 ", samples=%" PRIu32 ", bits=%" PRIu16
", channels=%" PRIu16 ", align=%" PRIu16 "",
format->wFormatTag, format->cbSize, format->nSamplesPerSec, format->wBitsPerSample,
format->nChannels, format->nBlockAlign);
WINPR_ASSERT(device);
WINPR_ASSERT(format);
switch (format->wFormatTag)
{
case WAVE_FORMAT_PCM:
if (format->cbSize == 0 && format->nSamplesPerSec <= 48000 &&
(format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
(format->nChannels == 1 || format->nChannels == 2))
{
return TRUE;
}
break;
default:
break;
}
return FALSE;
}
static UINT32 rdpsnd_opensles_get_volume(rdpsndDevicePlugin* device)
{
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
DEBUG_SND("opensles=%p", (void*)opensles);
WINPR_ASSERT(opensles);
if (opensles->stream)
{
const int max = android_GetOutputVolumeMax(opensles->stream);
const int rc = android_GetOutputVolume(opensles->stream);
if (android_GetOutputMute(opensles->stream))
opensles->volume = 0;
else
{
const unsigned short vol = rdpsnd_opensles_millibel_to_volume(rc, max);
opensles->volume = (vol << 16) | (vol & 0xFFFF);
}
}
return opensles->volume;
}
static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, UINT32 value)
{
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
DEBUG_SND("opensles=%p, value=%" PRIu32 "", (void*)opensles, value);
WINPR_ASSERT(opensles);
opensles->volume = value;
if (opensles->stream)
{
if (0 == opensles->volume)
return android_SetOutputMute(opensles->stream, true);
else
{
const int max = android_GetOutputVolumeMax(opensles->stream);
const int vol = rdpsnd_opensles_volume_to_millibel(value & 0xFFFF, max);
if (!android_SetOutputMute(opensles->stream, false))
return FALSE;
if (!android_SetOutputVolume(opensles->stream, vol))
return FALSE;
}
}
return TRUE;
}
static UINT rdpsnd_opensles_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size)
{
union
{
const BYTE* b;
const short* s;
} src;
int ret;
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
DEBUG_SND("opensles=%p, data=%p, size=%d", (void*)opensles, (void*)data, size);
if (!rdpsnd_opensles_check_handle(opensles))
return 0;
src.b = data;
DEBUG_SND("size=%d, src=%p", size, (void*)src.b);
WINPR_ASSERT(0 == size % 2);
WINPR_ASSERT(size > 0);
WINPR_ASSERT(src.b);
ret = android_AudioOut(opensles->stream, src.s, size / 2);
if (ret < 0)
WLog_ERR(TAG, "android_AudioOut failed (%d)", ret);
return 10; /* TODO: Get real latencry in [ms] */
}
static void rdpsnd_opensles_start(rdpsndDevicePlugin* device)
{
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
rdpsnd_opensles_check_handle(opensles);
DEBUG_SND("opensles=%p", (void*)opensles);
}
static int rdpsnd_opensles_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args)
{
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device;
COMMAND_LINE_ARGUMENT_A rdpsnd_opensles_args[] = {
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", nullptr, nullptr, -1, nullptr, "device" },
{ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
};
WINPR_ASSERT(opensles);
WINPR_ASSERT(args);
DEBUG_SND("opensles=%p, args=%p", (void*)opensles, (void*)args);
const DWORD flags =
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
const int status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_opensles_args,
flags, opensles, nullptr, nullptr);
if (status < 0)
return status;
const COMMAND_LINE_ARGUMENT_A* arg = rdpsnd_opensles_args;
do
{
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
continue;
CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
{
opensles->device_name = _strdup(arg->Value);
if (!opensles->device_name)
return ERROR_OUTOFMEMORY;
}
CommandLineSwitchEnd(arg)
} while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
return status;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
FREERDP_ENTRY_POINT(UINT VCAPITYPE opensles_freerdp_rdpsnd_client_subsystem_entry(
PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints))
{
UINT error = ERROR_INTERNAL_ERROR;
DEBUG_SND("pEntryPoints=%p", (void*)pEntryPoints);
rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)calloc(1, sizeof(rdpsndopenslesPlugin));
if (!opensles)
return CHANNEL_RC_NO_MEMORY;
opensles->device.Open = rdpsnd_opensles_open;
opensles->device.FormatSupported = rdpsnd_opensles_format_supported;
opensles->device.GetVolume = rdpsnd_opensles_get_volume;
opensles->device.SetVolume = rdpsnd_opensles_set_volume;
opensles->device.Start = rdpsnd_opensles_start;
opensles->device.Play = rdpsnd_opensles_play;
opensles->device.Close = rdpsnd_opensles_close;
opensles->device.Free = rdpsnd_opensles_free;
const ADDIN_ARGV* args = pEntryPoints->args;
rdpsnd_opensles_parse_addin_args((rdpsndDevicePlugin*)opensles, args);
if (!opensles->device_name)
{
opensles->device_name = _strdup("default");
if (!opensles->device_name)
{
error = CHANNEL_RC_NO_MEMORY;
goto outstrdup;
}
}
opensles->rate = 44100;
opensles->channels = 2;
opensles->format = WAVE_FORMAT_ADPCM;
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)opensles);
DEBUG_SND("success");
return CHANNEL_RC_OK;
outstrdup:
free(opensles);
return error;
}