mirror of https://github.com/FreeRDP/FreeRDP
Merged back audiotrack support from felix lang.
This commit is contained in:
parent
403a9fff62
commit
4748d0e4dd
|
@ -0,0 +1,51 @@
|
||||||
|
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||||
|
# FreeRDP cmake build script
|
||||||
|
#
|
||||||
|
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||||
|
# 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" "audiotrack" "")
|
||||||
|
|
||||||
|
set(${MODULE_PREFIX}_SRCS
|
||||||
|
audiotrack.cpp
|
||||||
|
rdpsnd_audiotrack.c)
|
||||||
|
|
||||||
|
include_directories(.)
|
||||||
|
include_directories(..)
|
||||||
|
include_directories(${AUDIOTRACK_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)
|
||||||
|
|
||||||
|
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||||
|
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||||
|
MODULE winpr
|
||||||
|
MODULES winpr-utils)
|
||||||
|
|
||||||
|
set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${AUDIOTRACK_LIBRARIES})
|
||||||
|
|
||||||
|
target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
|
||||||
|
|
||||||
|
if(NOT STATIC_CHANNELS)
|
||||||
|
install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_ADDIN_PATH})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/AudioTrack")
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2007 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ANDROID_ERRORS_H
|
||||||
|
#define ANDROID_ERRORS_H
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
namespace android {
|
||||||
|
|
||||||
|
// use this type to return error codes
|
||||||
|
#ifdef HAVE_MS_C_RUNTIME
|
||||||
|
typedef int status_t;
|
||||||
|
#else
|
||||||
|
typedef int32_t status_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* the MS C runtime lacks a few error codes */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Error codes.
|
||||||
|
* All error codes are negative values.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Win32 #defines NO_ERROR as well. It has the same value, so there's no
|
||||||
|
// real conflict, though it's a bit awkward.
|
||||||
|
#ifdef _WIN32
|
||||||
|
# undef NO_ERROR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum {
|
||||||
|
OK = 0, // Everything's swell.
|
||||||
|
NO_ERROR = 0, // No errors.
|
||||||
|
|
||||||
|
UNKNOWN_ERROR = 0x80000000,
|
||||||
|
|
||||||
|
NO_MEMORY = -ENOMEM,
|
||||||
|
INVALID_OPERATION = -ENOSYS,
|
||||||
|
BAD_VALUE = -EINVAL,
|
||||||
|
BAD_TYPE = 0x80000001,
|
||||||
|
NAME_NOT_FOUND = -ENOENT,
|
||||||
|
PERMISSION_DENIED = -EPERM,
|
||||||
|
NO_INIT = -ENODEV,
|
||||||
|
ALREADY_EXISTS = -EEXIST,
|
||||||
|
DEAD_OBJECT = -EPIPE,
|
||||||
|
FAILED_TRANSACTION = 0x80000002,
|
||||||
|
JPARKS_BROKE_IT = -EPIPE,
|
||||||
|
#if !defined(HAVE_MS_C_RUNTIME)
|
||||||
|
BAD_INDEX = -EOVERFLOW,
|
||||||
|
NOT_ENOUGH_DATA = -ENODATA,
|
||||||
|
WOULD_BLOCK = -EWOULDBLOCK,
|
||||||
|
TIMED_OUT = -ETIME,
|
||||||
|
UNKNOWN_TRANSACTION = -EBADMSG,
|
||||||
|
#else
|
||||||
|
BAD_INDEX = -E2BIG,
|
||||||
|
NOT_ENOUGH_DATA = 0x80000003,
|
||||||
|
WOULD_BLOCK = 0x80000004,
|
||||||
|
TIMED_OUT = 0x80000005,
|
||||||
|
UNKNOWN_TRANSACTION = 0x80000006,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Restore define; enumeration is in "android" namespace, so the value defined
|
||||||
|
// there won't work for Win32 code in a different namespace.
|
||||||
|
#ifdef _WIN32
|
||||||
|
# define NO_ERROR 0L
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}; // namespace android
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#endif // ANDROID_ERRORS_H
|
||||||
|
|
|
@ -0,0 +1,327 @@
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
#define TAG "freerdp_android_audiotrack"
|
||||||
|
#define LOGI(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
|
||||||
|
|
||||||
|
#include "audiotrack.h"
|
||||||
|
|
||||||
|
#define SIZE_OF_AUDIOTRACK 256
|
||||||
|
|
||||||
|
// _ZN7android11AudioSystem19getOutputFrameCountEPii
|
||||||
|
typedef int (*AudioSystem_getOutputFrameCount)(int *, int);
|
||||||
|
// _ZN7android11AudioSystem16getOutputLatencyEPji
|
||||||
|
typedef int (*AudioSystem_getOutputLatency)(unsigned int *, int);
|
||||||
|
// _ZN7android11AudioSystem21getOutputSamplingRateEPii
|
||||||
|
typedef int (*AudioSystem_getOutputSamplingRate)(int *, int);
|
||||||
|
|
||||||
|
// _ZN7android10AudioTrack16getMinFrameCountEPiij
|
||||||
|
typedef int (*AudioTrack_getMinFrameCount)(int *, int, unsigned int);
|
||||||
|
|
||||||
|
// _ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii
|
||||||
|
typedef void (*AudioTrack_ctor)(void *, int, unsigned int, int, int, int, unsigned int, void (*)(int, void *, void *), void *, int, int);
|
||||||
|
// _ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_i
|
||||||
|
typedef void (*AudioTrack_ctor_legacy)(void *, int, unsigned int, int, int, int, unsigned int, void (*)(int, void *, void *), void *, int);
|
||||||
|
// _ZN7android10AudioTrackD1Ev
|
||||||
|
typedef void (*AudioTrack_dtor)(void *);
|
||||||
|
// _ZNK7android10AudioTrack9initCheckEv
|
||||||
|
typedef int (*AudioTrack_initCheck)(void *);
|
||||||
|
typedef uint32_t (*AudioTrack_latency)(void *);
|
||||||
|
// _ZN7android10AudioTrack5startEv
|
||||||
|
typedef void (*AudioTrack_start)(void *);
|
||||||
|
// _ZN7android10AudioTrack4stopEv
|
||||||
|
typedef void (*AudioTrack_stop)(void *);
|
||||||
|
// _ZN7android10AudioTrack5writeEPKvj
|
||||||
|
typedef int (*AudioTrack_write)(void *, void const*, unsigned int);
|
||||||
|
// _ZN7android10AudioTrack5flushEv
|
||||||
|
typedef void (*AudioTrack_flush)(void *);
|
||||||
|
|
||||||
|
static void *libmedia;
|
||||||
|
static AudioSystem_getOutputFrameCount as_getOutputFrameCount;
|
||||||
|
static AudioSystem_getOutputLatency as_getOutputLatency;
|
||||||
|
static AudioSystem_getOutputSamplingRate as_getOutputSamplingRate;
|
||||||
|
|
||||||
|
static AudioTrack_getMinFrameCount at_getMinFrameCount;
|
||||||
|
static AudioTrack_ctor at_ctor;
|
||||||
|
static AudioTrack_ctor_legacy at_ctor_legacy;
|
||||||
|
static AudioTrack_dtor at_dtor;
|
||||||
|
static AudioTrack_initCheck at_initCheck;
|
||||||
|
static AudioTrack_latency at_latency;
|
||||||
|
static AudioTrack_start at_start;
|
||||||
|
static AudioTrack_stop at_stop;
|
||||||
|
static AudioTrack_write at_write;
|
||||||
|
static AudioTrack_flush at_flush;
|
||||||
|
|
||||||
|
class AndroidAudioTrack
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
void* mAudioTrack;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AndroidAudioTrack()
|
||||||
|
: mAudioTrack(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~AndroidAudioTrack()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void close()
|
||||||
|
{
|
||||||
|
if (mAudioTrack) {
|
||||||
|
if (at_stop)
|
||||||
|
at_stop(mAudioTrack);
|
||||||
|
if (at_flush)
|
||||||
|
at_flush(mAudioTrack);
|
||||||
|
if (at_dtor)
|
||||||
|
at_dtor(mAudioTrack);
|
||||||
|
free(mAudioTrack);
|
||||||
|
mAudioTrack = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int set(int streamType, uint32_t sampleRate, int format, int channels)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
int minFrameCount = 0;
|
||||||
|
int size = 0;
|
||||||
|
|
||||||
|
LOGI("streamTyp = %d, sampleRate = %d, format = %d, channels = %d\n", streamType, sampleRate, format, channels);
|
||||||
|
close();
|
||||||
|
|
||||||
|
if (at_getMinFrameCount) {
|
||||||
|
status = at_getMinFrameCount(&minFrameCount, streamType, sampleRate);
|
||||||
|
LOGI("at_getMinFrameCount %d, %d\n", minFrameCount, status);
|
||||||
|
}
|
||||||
|
//size = minFrameCount * (channels == CHANNEL_OUT_STEREO ? 2 : 1) * 4;
|
||||||
|
|
||||||
|
mAudioTrack = malloc(SIZE_OF_AUDIOTRACK);
|
||||||
|
*((uint32_t *) ((uint32_t)mAudioTrack + SIZE_OF_AUDIOTRACK - 4)) = 0xbaadbaad;
|
||||||
|
if (at_ctor) {
|
||||||
|
at_ctor(mAudioTrack, streamType, sampleRate, format, channels, size, 0, NULL, NULL, 0, 0);
|
||||||
|
} else if (at_ctor_legacy) {
|
||||||
|
at_ctor_legacy(mAudioTrack, streamType, sampleRate, format, channels, size, 0, NULL, NULL, 0);
|
||||||
|
} else {
|
||||||
|
LOGI("Cannot create AudioTrack!");
|
||||||
|
free(mAudioTrack);
|
||||||
|
mAudioTrack = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
assert( (*((uint32_t *) ((uint32_t)mAudioTrack + SIZE_OF_AUDIOTRACK - 4)) == 0xbaadbaad) );
|
||||||
|
|
||||||
|
/* And Init */
|
||||||
|
status = at_initCheck(mAudioTrack);
|
||||||
|
LOGI("at_initCheck = %d\n", status);
|
||||||
|
|
||||||
|
/* android 1.6 uses channel count instead of stream_type */
|
||||||
|
if (status != 0 && at_ctor_legacy) {
|
||||||
|
channels = (channels == CHANNEL_OUT_STEREO) ? 2 : 1;
|
||||||
|
at_ctor_legacy(mAudioTrack, streamType, sampleRate, format, channels, size, 0, NULL, NULL, 0);
|
||||||
|
status = at_initCheck(mAudioTrack);
|
||||||
|
LOGI("at_initCheck2 = %d\n", status);
|
||||||
|
}
|
||||||
|
if (status != 0) {
|
||||||
|
LOGI("Cannot create AudioTrack!");
|
||||||
|
free(mAudioTrack);
|
||||||
|
mAudioTrack = NULL;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
uint32_t latency()
|
||||||
|
{
|
||||||
|
if (mAudioTrack && at_latency) {
|
||||||
|
return at_latency(mAudioTrack);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int start()
|
||||||
|
{
|
||||||
|
if (mAudioTrack && at_start) {
|
||||||
|
at_start(mAudioTrack);
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
}
|
||||||
|
int write(void* buffer, int size)
|
||||||
|
{
|
||||||
|
if (mAudioTrack && at_write) {
|
||||||
|
return at_write(mAudioTrack, buffer, size);
|
||||||
|
}
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
}
|
||||||
|
int flush()
|
||||||
|
{
|
||||||
|
if (mAudioTrack && at_flush) {
|
||||||
|
at_flush(mAudioTrack);
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
}
|
||||||
|
int stop()
|
||||||
|
{
|
||||||
|
if (mAudioTrack && at_stop) {
|
||||||
|
at_stop(mAudioTrack);
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
}
|
||||||
|
int reload()
|
||||||
|
{
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static void* InitLibrary()
|
||||||
|
{
|
||||||
|
/* DL Open libmedia */
|
||||||
|
void *p_library;
|
||||||
|
p_library = dlopen("libmedia.so", RTLD_NOW);
|
||||||
|
if (!p_library)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Register symbols */
|
||||||
|
as_getOutputFrameCount = (AudioSystem_getOutputFrameCount)(dlsym(p_library, "_ZN7android11AudioSystem19getOutputFrameCountEPii"));
|
||||||
|
as_getOutputLatency = (AudioSystem_getOutputLatency)(dlsym(p_library, "_ZN7android11AudioSystem16getOutputLatencyEPji"));
|
||||||
|
as_getOutputSamplingRate = (AudioSystem_getOutputSamplingRate)(dlsym(p_library, "_ZN7android11AudioSystem21getOutputSamplingRateEPii"));
|
||||||
|
at_getMinFrameCount = (AudioTrack_getMinFrameCount)(dlsym(p_library, "_ZN7android10AudioTrack16getMinFrameCountEPiij"));
|
||||||
|
at_ctor = (AudioTrack_ctor)(dlsym(p_library, "_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii"));
|
||||||
|
at_ctor_legacy = (AudioTrack_ctor_legacy)(dlsym(p_library, "_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_i"));
|
||||||
|
at_dtor = (AudioTrack_dtor)(dlsym(p_library, "_ZN7android10AudioTrackD1Ev"));
|
||||||
|
at_initCheck = (AudioTrack_initCheck)(dlsym(p_library, "_ZNK7android10AudioTrack9initCheckEv"));
|
||||||
|
at_latency = (AudioTrack_latency)(dlsym(p_library, "_ZNK7android10AudioTrack7latencyEv"));
|
||||||
|
at_start = (AudioTrack_start)(dlsym(p_library, "_ZN7android10AudioTrack5startEv"));
|
||||||
|
at_stop = (AudioTrack_stop)(dlsym(p_library, "_ZN7android10AudioTrack4stopEv"));
|
||||||
|
at_write = (AudioTrack_write)(dlsym(p_library, "_ZN7android10AudioTrack5writeEPKvj"));
|
||||||
|
at_flush = (AudioTrack_flush)(dlsym(p_library, "_ZN7android10AudioTrack5flushEv"));
|
||||||
|
|
||||||
|
LOGI("p_library : %p\n", p_library);
|
||||||
|
LOGI("as_getOutputFrameCount : %p\n", as_getOutputFrameCount);
|
||||||
|
LOGI("as_getOutputLatency : %p\n", as_getOutputLatency);
|
||||||
|
LOGI("as_getOutputSamplingRate : %p\n", as_getOutputSamplingRate);
|
||||||
|
LOGI("at_getMinFrameCount : %p\n", at_getMinFrameCount);
|
||||||
|
LOGI("at_ctor : %p\n", at_ctor);
|
||||||
|
LOGI("at_ctor_legacy : %p\n", at_ctor_legacy);
|
||||||
|
LOGI("at_dtor : %p\n", at_dtor);
|
||||||
|
LOGI("at_initCheck : %p\n", at_initCheck);
|
||||||
|
LOGI("at_latency : %p\n", at_latency);
|
||||||
|
LOGI("at_start : %p\n", at_start);
|
||||||
|
LOGI("at_stop : %p\n", at_stop);
|
||||||
|
LOGI("at_write : %p\n", at_write);
|
||||||
|
LOGI("at_flush : %p\n", at_flush);
|
||||||
|
|
||||||
|
/* We need the first 3 or the last 1 */
|
||||||
|
#if 0
|
||||||
|
if (!((as_getOutputFrameCount && as_getOutputLatency && as_getOutputSamplingRate)
|
||||||
|
|| at_getMinFrameCount)) {
|
||||||
|
dlclose(p_library);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// We need all the other Symbols
|
||||||
|
if (!((at_ctor || at_ctor_legacy) && at_dtor && at_initCheck &&
|
||||||
|
at_start && at_stop && at_write && at_flush)) {
|
||||||
|
dlclose(p_library);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return p_library;
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_open(AUDIO_DRIVER_HANDLE* outHandle)
|
||||||
|
{
|
||||||
|
int ret = ANDROID_AUDIOTRACK_RESULT_SUCCESS;
|
||||||
|
AndroidAudioTrack* audioTrack = new AndroidAudioTrack();
|
||||||
|
|
||||||
|
*outHandle = audioTrack;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_close(AUDIO_DRIVER_HANDLE handle)
|
||||||
|
{
|
||||||
|
AndroidAudioTrack* audioTrack = (AndroidAudioTrack*)handle;
|
||||||
|
if (!audioTrack)
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
delete audioTrack;
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_set(AUDIO_DRIVER_HANDLE handle, int streamType, uint32_t sampleRate, int format, int channels)
|
||||||
|
{
|
||||||
|
AndroidAudioTrack* audioTrack = (AndroidAudioTrack*)handle;
|
||||||
|
if (!audioTrack)
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
return audioTrack->set(streamType, sampleRate, format, channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_set_volume(AUDIO_DRIVER_HANDLE handle, float left, float right)
|
||||||
|
{
|
||||||
|
AndroidAudioTrack* audioTrack = (AndroidAudioTrack*)handle;
|
||||||
|
if (!audioTrack)
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t freerdp_android_at_latency(AUDIO_DRIVER_HANDLE handle)
|
||||||
|
{
|
||||||
|
AndroidAudioTrack* audioTrack = (AndroidAudioTrack*)handle;
|
||||||
|
if (!audioTrack)
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
return audioTrack->latency();
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_start(AUDIO_DRIVER_HANDLE handle)
|
||||||
|
{
|
||||||
|
AndroidAudioTrack* audioTrack = (AndroidAudioTrack*)handle;
|
||||||
|
if (!audioTrack)
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
return audioTrack->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_write(AUDIO_DRIVER_HANDLE handle, void *buffer, int buffer_size)
|
||||||
|
{
|
||||||
|
AndroidAudioTrack* audioTrack = (AndroidAudioTrack*)handle;
|
||||||
|
if (!audioTrack)
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
return audioTrack->write(buffer, buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_flush(AUDIO_DRIVER_HANDLE handle)
|
||||||
|
{
|
||||||
|
AndroidAudioTrack* audioTrack = (AndroidAudioTrack*)handle;
|
||||||
|
if (!audioTrack)
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
return audioTrack->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_stop(AUDIO_DRIVER_HANDLE handle)
|
||||||
|
{
|
||||||
|
AndroidAudioTrack* audioTrack = (AndroidAudioTrack*)handle;
|
||||||
|
if (!audioTrack)
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
return audioTrack->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_reload(AUDIO_DRIVER_HANDLE handle)
|
||||||
|
{
|
||||||
|
AndroidAudioTrack* audioTrack = (AndroidAudioTrack*)handle;
|
||||||
|
if (!audioTrack)
|
||||||
|
return ANDROID_AUDIOTRACK_RESULT_ERRNO;
|
||||||
|
return audioTrack->reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_android_at_init_library()
|
||||||
|
{
|
||||||
|
if (libmedia == NULL)
|
||||||
|
{
|
||||||
|
libmedia = InitLibrary();
|
||||||
|
LOGI("loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ANDROID_AUDIOTRACK_WRAPPER_H
|
||||||
|
#define ANDROID_AUDIOTRACK_WRAPPER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
#define ANDROID_AUDIOTRACK_RESULT_SUCCESS 0
|
||||||
|
#define ANDROID_AUDIOTRACK_RESULT_BAD_PARAMETER -1
|
||||||
|
#define ANDROID_AUDIOTRACK_RESULT_JNI_EXCEPTION -2
|
||||||
|
#define ANDROID_AUDIOTRACK_RESULT_ALLOCATION_FAILED -3
|
||||||
|
#define ANDROID_AUDIOTRACK_RESULT_ERRNO -4
|
||||||
|
|
||||||
|
enum stream_type {
|
||||||
|
DEFAULT =-1,
|
||||||
|
VOICE_CALL = 0,
|
||||||
|
SYSTEM = 1,
|
||||||
|
RING = 2,
|
||||||
|
MUSIC = 3,
|
||||||
|
ALARM = 4,
|
||||||
|
NOTIFICATION = 5,
|
||||||
|
BLUETOOTH_SCO = 6,
|
||||||
|
ENFORCED_AUDIBLE = 7, // Sounds that cannot be muted by user and must be routed to speaker
|
||||||
|
DTMF = 8,
|
||||||
|
TTS = 9,
|
||||||
|
NUM_STREAM_TYPES
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NO_MORE_BUFFERS = 0x80000001,
|
||||||
|
STOPPED = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Audio sub formats (see AudioSystem::audio_format).
|
||||||
|
enum pcm_sub_format {
|
||||||
|
PCM_SUB_16_BIT = 0x1, // must be 1 for backward compatibility
|
||||||
|
PCM_SUB_8_BIT = 0x2, // must be 2 for backward compatibility
|
||||||
|
};
|
||||||
|
|
||||||
|
// Audio format consists in a main format field (upper 8 bits) and a sub format field (lower 24 bits).
|
||||||
|
// The main format indicates the main codec type. The sub format field indicates options and parameters
|
||||||
|
// for each format. The sub format is mainly used for record to indicate for instance the requested bitrate
|
||||||
|
// or profile. It can also be used for certain formats to give informations not present in the encoded
|
||||||
|
// audio stream (e.g. octet alignement for AMR).
|
||||||
|
enum audio_format {
|
||||||
|
INVALID_FORMAT = -1,
|
||||||
|
FORMAT_DEFAULT = 0,
|
||||||
|
PCM = 0x00000000, // must be 0 for backward compatibility
|
||||||
|
MP3 = 0x01000000,
|
||||||
|
AMR_NB = 0x02000000,
|
||||||
|
AMR_WB = 0x03000000,
|
||||||
|
AAC = 0x04000000,
|
||||||
|
HE_AAC_V1 = 0x05000000,
|
||||||
|
HE_AAC_V2 = 0x06000000,
|
||||||
|
VORBIS = 0x07000000,
|
||||||
|
MAIN_FORMAT_MASK = 0xFF000000,
|
||||||
|
SUB_FORMAT_MASK = 0x00FFFFFF,
|
||||||
|
// Aliases
|
||||||
|
PCM_16_BIT = (PCM|PCM_SUB_16_BIT),
|
||||||
|
PCM_8_BIT = (PCM|PCM_SUB_8_BIT)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Channel mask definitions must be kept in sync with JAVA values in /media/java/android/media/AudioFormat.java
|
||||||
|
enum audio_channels {
|
||||||
|
// output channels
|
||||||
|
CHANNEL_OUT_FRONT_LEFT = 0x4,
|
||||||
|
CHANNEL_OUT_FRONT_RIGHT = 0x8,
|
||||||
|
CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT,
|
||||||
|
CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT)
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void* AUDIO_DRIVER_HANDLE;
|
||||||
|
|
||||||
|
int freerdp_android_at_init_library();
|
||||||
|
int freerdp_android_at_open(AUDIO_DRIVER_HANDLE* outHandle);
|
||||||
|
int freerdp_android_at_set(AUDIO_DRIVER_HANDLE handle, int streamType, uint32_t sampleRate, int format, int channels);
|
||||||
|
int freerdp_android_at_set_volume(AUDIO_DRIVER_HANDLE handle, float left, float right);
|
||||||
|
int freerdp_android_at_close(AUDIO_DRIVER_HANDLE handle);
|
||||||
|
uint32_t freerdp_android_at_latency(AUDIO_DRIVER_HANDLE handle);
|
||||||
|
int freerdp_android_at_start(AUDIO_DRIVER_HANDLE handle);
|
||||||
|
int freerdp_android_at_write(AUDIO_DRIVER_HANDLE handle, void *buffer, int buffer_size);
|
||||||
|
int freerdp_android_at_flush(AUDIO_DRIVER_HANDLE handle);
|
||||||
|
int freerdp_android_at_stop(AUDIO_DRIVER_HANDLE handle);
|
||||||
|
int freerdp_android_at_reload(AUDIO_DRIVER_HANDLE handle);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,308 @@
|
||||||
|
/**
|
||||||
|
* FreeRDP: A Remote Desktop Protocol client.
|
||||||
|
* Audio Output Virtual Channel
|
||||||
|
*
|
||||||
|
* Copyright 2009-2011 Jay Sorg
|
||||||
|
* Copyright 2010-2011 Vic Lee
|
||||||
|
* Copyright 2012 Felix Long
|
||||||
|
* Copyright 2013 Armin Novak
|
||||||
|
*
|
||||||
|
* 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <audiotrack.h>
|
||||||
|
|
||||||
|
#include <freerdp/types.h>
|
||||||
|
#include <freerdp/utils/svc_plugin.h>
|
||||||
|
#include <freerdp/codec/dsp.h>
|
||||||
|
|
||||||
|
#include "rdpsnd_main.h"
|
||||||
|
|
||||||
|
typedef struct rdpsnd_audiotrack_plugin rdpsndAudioTrackPlugin;
|
||||||
|
struct rdpsnd_audiotrack_plugin
|
||||||
|
{
|
||||||
|
rdpsndDevicePlugin device;
|
||||||
|
|
||||||
|
AUDIO_DRIVER_HANDLE out_handle;
|
||||||
|
|
||||||
|
UINT32 source_rate;
|
||||||
|
UINT32 actual_rate;
|
||||||
|
int format;
|
||||||
|
UINT32 source_channels;
|
||||||
|
UINT32 actual_channels;
|
||||||
|
int bytes_per_channel;
|
||||||
|
int wformat;
|
||||||
|
int block_size;
|
||||||
|
int latency;
|
||||||
|
|
||||||
|
FREERDP_DSP_CONTEXT* dsp_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void rdpsnd_audiotrack_set_format(rdpsndDevicePlugin* device, AUDIO_FORMAT* format, int latency)
|
||||||
|
{
|
||||||
|
rdpsndAudioTrackPlugin* audiotrack = (rdpsndAudioTrackPlugin*)device;
|
||||||
|
|
||||||
|
if (format != NULL)
|
||||||
|
{
|
||||||
|
audiotrack->source_rate = format->nSamplesPerSec;
|
||||||
|
audiotrack->actual_rate = format->nSamplesPerSec;
|
||||||
|
audiotrack->source_channels = format->nChannels;
|
||||||
|
audiotrack->actual_channels = format->nChannels;
|
||||||
|
switch (format->wFormatTag)
|
||||||
|
{
|
||||||
|
case 1: /* PCM */
|
||||||
|
switch (format->wBitsPerSample)
|
||||||
|
{
|
||||||
|
case 8:
|
||||||
|
audiotrack->format = PCM_8_BIT;
|
||||||
|
audiotrack->bytes_per_channel = 1;
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
audiotrack->format = PCM_16_BIT;
|
||||||
|
audiotrack->bytes_per_channel = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: /* MS ADPCM */
|
||||||
|
case 0x11: /* IMA ADPCM */
|
||||||
|
audiotrack->format = PCM_16_BIT;
|
||||||
|
audiotrack->bytes_per_channel = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
audiotrack->wformat = format->wFormatTag;
|
||||||
|
audiotrack->block_size = format->nBlockAlign;
|
||||||
|
}
|
||||||
|
audiotrack->latency = latency;
|
||||||
|
|
||||||
|
freerdp_android_at_set(audiotrack->out_handle,
|
||||||
|
MUSIC,
|
||||||
|
audiotrack->actual_rate,
|
||||||
|
audiotrack->format,
|
||||||
|
audiotrack->actual_channels == 2 ? CHANNEL_OUT_STEREO : CHANNEL_OUT_MONO);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rdpsnd_audiotrack_open(rdpsndDevicePlugin* device, AUDIO_FORMAT* format, int latency)
|
||||||
|
{
|
||||||
|
rdpsndAudioTrackPlugin* audiotrack = (rdpsndAudioTrackPlugin*)device;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (audiotrack->out_handle != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DEBUG_SVC("opening");
|
||||||
|
|
||||||
|
error = freerdp_android_at_open(&audiotrack->out_handle);
|
||||||
|
|
||||||
|
if (error < 0)
|
||||||
|
{
|
||||||
|
DEBUG_WARN("freerdp_android_at_open failed");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
freerdp_dsp_context_reset_adpcm(audiotrack->dsp_context);
|
||||||
|
rdpsnd_audiotrack_set_format(device, format, latency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rdpsnd_audiotrack_close(rdpsndDevicePlugin* device)
|
||||||
|
{
|
||||||
|
rdpsndAudioTrackPlugin* audiotrack = (rdpsndAudioTrackPlugin*)device;
|
||||||
|
|
||||||
|
if (audiotrack->out_handle != 0)
|
||||||
|
{
|
||||||
|
DEBUG_SVC("close");
|
||||||
|
freerdp_android_at_close(audiotrack->out_handle);
|
||||||
|
audiotrack->out_handle = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rdpsnd_audiotrack_free(rdpsndDevicePlugin* device)
|
||||||
|
{
|
||||||
|
rdpsndAudioTrackPlugin* audiotrack = (rdpsndAudioTrackPlugin*)device;
|
||||||
|
|
||||||
|
rdpsnd_audiotrack_close(device);
|
||||||
|
freerdp_dsp_context_free(audiotrack->dsp_context);
|
||||||
|
free(audiotrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL rdpsnd_audiotrack_format_supported(rdpsndDevicePlugin* device, AUDIO_FORMAT* format)
|
||||||
|
{
|
||||||
|
switch (format->wFormatTag)
|
||||||
|
{
|
||||||
|
case 1: /* PCM */
|
||||||
|
if (format->cbSize == 0 &&
|
||||||
|
format->nSamplesPerSec <= 48000 &&
|
||||||
|
(format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
|
||||||
|
(format->nChannels == 1 || format->nChannels == 2))
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: /* MS ADPCM */
|
||||||
|
case 0x11: /* IMA ADPCM */
|
||||||
|
if (format->nSamplesPerSec <= 48000 &&
|
||||||
|
format->wBitsPerSample == 4 &&
|
||||||
|
(format->nChannels == 1 || format->nChannels == 2))
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rdpsnd_audiotrack_set_volume(rdpsndDevicePlugin* device, UINT32 value)
|
||||||
|
{
|
||||||
|
rdpsndAudioTrackPlugin* audiotrack = (rdpsndAudioTrackPlugin*)device;
|
||||||
|
float left;
|
||||||
|
float right;
|
||||||
|
|
||||||
|
if (audiotrack->out_handle == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
left = ((value & 0xFFFF) * 1.0) / 0xFFFF;
|
||||||
|
right = (((value >> 16) & 0xFFFF) * 1.0) / 0xFFFF;
|
||||||
|
freerdp_android_at_set_volume(audiotrack->out_handle, left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rdpsnd_audiotrack_play(rdpsndDevicePlugin* device, BYTE* data, int size)
|
||||||
|
{
|
||||||
|
rdpsndAudioTrackPlugin* audiotrack = (rdpsndAudioTrackPlugin*)device;
|
||||||
|
BYTE* src;
|
||||||
|
int len;
|
||||||
|
int error;
|
||||||
|
int frames;
|
||||||
|
int rbytes_per_frame;
|
||||||
|
int sbytes_per_frame;
|
||||||
|
BYTE* pindex;
|
||||||
|
BYTE* end;
|
||||||
|
|
||||||
|
if (audiotrack->out_handle == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (audiotrack->wformat == 2)
|
||||||
|
{
|
||||||
|
audiotrack->dsp_context->decode_ms_adpcm(audiotrack->dsp_context,
|
||||||
|
data, size, audiotrack->source_channels, audiotrack->block_size);
|
||||||
|
size = audiotrack->dsp_context->adpcm_size;
|
||||||
|
src = audiotrack->dsp_context->adpcm_buffer;
|
||||||
|
}
|
||||||
|
else if (audiotrack->wformat == 0x11)
|
||||||
|
{
|
||||||
|
audiotrack->dsp_context->decode_ima_adpcm(audiotrack->dsp_context,
|
||||||
|
data, size, audiotrack->source_channels, audiotrack->block_size);
|
||||||
|
size = audiotrack->dsp_context->adpcm_size;
|
||||||
|
src = audiotrack->dsp_context->adpcm_buffer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
src = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
sbytes_per_frame = audiotrack->source_channels * audiotrack->bytes_per_channel;
|
||||||
|
rbytes_per_frame = audiotrack->actual_channels * audiotrack->bytes_per_channel;
|
||||||
|
if ((size % sbytes_per_frame) != 0)
|
||||||
|
{
|
||||||
|
DEBUG_WARN("error len mod");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((audiotrack->source_rate == audiotrack->actual_rate) &&
|
||||||
|
(audiotrack->source_channels == audiotrack->actual_channels))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
audiotrack->dsp_context->resample(audiotrack->dsp_context, src, audiotrack->bytes_per_channel,
|
||||||
|
audiotrack->source_channels, audiotrack->source_rate, size / sbytes_per_frame,
|
||||||
|
audiotrack->actual_channels, audiotrack->actual_rate);
|
||||||
|
frames = audiotrack->dsp_context->resampled_frames;
|
||||||
|
DEBUG_SVC("resampled %d frames at %d to %d frames at %d",
|
||||||
|
size / sbytes_per_frame, audiotrack->source_rate, frames, audiotrack->actual_rate);
|
||||||
|
size = frames * rbytes_per_frame;
|
||||||
|
src = audiotrack->dsp_context->resampled_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
pindex = src;
|
||||||
|
end = pindex + size;
|
||||||
|
while (pindex < end)
|
||||||
|
{
|
||||||
|
len = end - pindex;
|
||||||
|
|
||||||
|
error = freerdp_android_at_write(audiotrack->out_handle, pindex, len);
|
||||||
|
if (error < 0)
|
||||||
|
{
|
||||||
|
DEBUG_WARN("error %d", error);
|
||||||
|
freerdp_android_at_close(audiotrack->out_handle);
|
||||||
|
audiotrack->out_handle = 0;
|
||||||
|
rdpsnd_audiotrack_open(device, NULL, audiotrack->latency);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pindex += error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rdpsnd_audiotrack_start(rdpsndDevicePlugin* device)
|
||||||
|
{
|
||||||
|
rdpsndAudioTrackPlugin* audiotrack = (rdpsndAudioTrackPlugin*)device;
|
||||||
|
|
||||||
|
if (audiotrack->out_handle == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
freerdp_android_at_start(audiotrack->out_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef STATIC_CHANNELS
|
||||||
|
#define freerdp_rdpsnd_client_subsystem_entry audiotrack_freerdp_rdpsnd_client_subsystem_entry
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)
|
||||||
|
{
|
||||||
|
rdpsndAudioTrackPlugin* audiotrack;
|
||||||
|
|
||||||
|
freerdp_android_at_init_library();
|
||||||
|
audiotrack = malloc(sizeof(rdpsndAudioTrackPlugin));
|
||||||
|
|
||||||
|
audiotrack->device.Open = rdpsnd_audiotrack_open;
|
||||||
|
audiotrack->device.FormatSupported = rdpsnd_audiotrack_format_supported;
|
||||||
|
audiotrack->device.SetFormat = rdpsnd_audiotrack_set_format;
|
||||||
|
audiotrack->device.SetVolume = rdpsnd_audiotrack_set_volume;
|
||||||
|
audiotrack->device.Play = rdpsnd_audiotrack_play;
|
||||||
|
audiotrack->device.Start = rdpsnd_audiotrack_start;
|
||||||
|
audiotrack->device.Close = rdpsnd_audiotrack_close;
|
||||||
|
audiotrack->device.Free = rdpsnd_audiotrack_free;
|
||||||
|
|
||||||
|
audiotrack->out_handle = 0;
|
||||||
|
audiotrack->source_rate = 22050;
|
||||||
|
audiotrack->actual_rate = 22050;
|
||||||
|
audiotrack->format = PCM_16_BIT;
|
||||||
|
audiotrack->source_channels = 2;
|
||||||
|
audiotrack->actual_channels = 2;
|
||||||
|
audiotrack->bytes_per_channel = 2;
|
||||||
|
|
||||||
|
audiotrack->dsp_context = freerdp_dsp_context_new();
|
||||||
|
|
||||||
|
pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)audiotrack);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue