diff --git a/channels/rdpsnd/client/audiotrack/CMakeLists.txt b/channels/rdpsnd/client/audiotrack/CMakeLists.txt new file mode 100644 index 000000000..e663df087 --- /dev/null +++ b/channels/rdpsnd/client/audiotrack/CMakeLists.txt @@ -0,0 +1,51 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# 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. + +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") diff --git a/channels/rdpsnd/client/audiotrack/Errors.h b/channels/rdpsnd/client/audiotrack/Errors.h new file mode 100644 index 000000000..84147d702 --- /dev/null +++ b/channels/rdpsnd/client/audiotrack/Errors.h @@ -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 +#include + +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 + diff --git a/channels/rdpsnd/client/audiotrack/audiotrack.cpp b/channels/rdpsnd/client/audiotrack/audiotrack.cpp new file mode 100644 index 000000000..3206ef17e --- /dev/null +++ b/channels/rdpsnd/client/audiotrack/audiotrack.cpp @@ -0,0 +1,327 @@ +#include +#include +#include + +#include +#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; +} diff --git a/channels/rdpsnd/client/audiotrack/audiotrack.h b/channels/rdpsnd/client/audiotrack/audiotrack.h new file mode 100644 index 000000000..f332c12cd --- /dev/null +++ b/channels/rdpsnd/client/audiotrack/audiotrack.h @@ -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 +#include + +#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 diff --git a/channels/rdpsnd/client/audiotrack/rdpsnd_audiotrack.c b/channels/rdpsnd/client/audiotrack/rdpsnd_audiotrack.c new file mode 100644 index 000000000..117f4d68f --- /dev/null +++ b/channels/rdpsnd/client/audiotrack/rdpsnd_audiotrack.c @@ -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 +#include +#include + +#include + +#include +#include +#include + +#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; +} +