From 3da340492a5ecf84e134a72415b9405d7640b8e4 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Sun, 14 Aug 2011 21:46:02 +0800 Subject: [PATCH 1/8] libfreerdp-utils: migrate dsp module. --- include/freerdp/utils/dsp.h | 40 ++++ libfreerdp-utils/CMakeLists.txt | 1 + libfreerdp-utils/dsp.c | 336 ++++++++++++++++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 include/freerdp/utils/dsp.h create mode 100644 libfreerdp-utils/dsp.c diff --git a/include/freerdp/utils/dsp.h b/include/freerdp/utils/dsp.h new file mode 100644 index 000000000..4087308a7 --- /dev/null +++ b/include/freerdp/utils/dsp.h @@ -0,0 +1,40 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Digital Sound Processing + * + * Copyright 2010-2011 Vic Lee + * + * 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 __DSP_UTILS_H +#define __DSP_UTILS_H + +struct _ADPCM +{ + sint16 last_sample[2]; + sint16 last_step[2]; +}; +typedef struct _ADPCM ADPCM; + +uint8* dsp_resample(uint8* src, int bytes_per_sample, + uint32 schan, uint32 srate, int sframes, + uint32 rchan, uint32 rrate, int * prframes); + +uint8* dsp_decode_ima_adpcm(ADPCM* adpcm, + uint8* src, int size, int channels, int block_size, int* out_size); +uint8* dsp_encode_ima_adpcm(ADPCM* adpcm, + uint8* src, int size, int channels, int block_size, int* out_size); + +#endif /* __DSP_UTILS_H */ + diff --git a/libfreerdp-utils/CMakeLists.txt b/libfreerdp-utils/CMakeLists.txt index be1cd3f1e..bde53bb6d 100644 --- a/libfreerdp-utils/CMakeLists.txt +++ b/libfreerdp-utils/CMakeLists.txt @@ -23,6 +23,7 @@ find_package(Threads REQUIRED) set(FREERDP_UTILS_SRCS args.c blob.c + dsp.c event.c hexdump.c list.c diff --git a/libfreerdp-utils/dsp.c b/libfreerdp-utils/dsp.c new file mode 100644 index 000000000..8dd7c4d64 --- /dev/null +++ b/libfreerdp-utils/dsp.c @@ -0,0 +1,336 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Digital Sound Processing + * + * Copyright 2010-2011 Vic Lee + * + * 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 +#include +#include +#include +#include +#include + +uint8* dsp_resample(uint8* src, int bytes_per_sample, + uint32 schan, uint32 srate, int sframes, + uint32 rchan, uint32 rrate, int * prframes) +{ + uint8* dst; + uint8* p; + int rframes; + int rsize; + int i, j; + int n1, n2; + int sbytes, rbytes; + + sbytes = bytes_per_sample * schan; + rbytes = bytes_per_sample * rchan; + rframes = sframes * rrate / srate; + *prframes = rframes; + rsize = rbytes * rframes; + dst = (uint8*) xzalloc(rsize); + + p = dst; + for (i = 0; i < rframes; i++) + { + n1 = i * srate / rrate; + if (n1 >= sframes) + n1 = sframes - 1; + n2 = (n1 * rrate == i * srate || n1 == sframes - 1 ? n1 : n1 + 1); + for (j = 0; j < rbytes; j++) + { + /* Nearest Interpolation, probably the easiest, but works */ + *p++ = (i * srate - n1 * rrate > n2 * rrate - i * srate ? + src[n2 * sbytes + (j % sbytes)] : + src[n1 * sbytes + (j % sbytes)]); + } + } + + return dst; +} + +/** + * Microsoft IMA ADPCM specification: + * + * http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM + * http://wiki.multimedia.cx/index.php?title=IMA_ADPCM + */ + +static const sint16 ima_step_index_table[] = +{ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 +}; + +static const sint16 ima_step_size_table[] = +{ + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + +static uint16 dsp_decode_ima_adpcm_sample(ADPCM* adpcm, + int channel, uint8 sample) +{ + sint32 ss; + sint32 d; + + ss = ima_step_size_table[adpcm->last_step[channel]]; + d = (ss >> 3); + if (sample & 1) + d += (ss >> 2); + if (sample & 2) + d += (ss >> 1); + if (sample & 4) + d += ss; + if (sample & 8) + d = -d; + d += adpcm->last_sample[channel]; + + if (d < -32768) + d = -32768; + else if (d > 32767) + d = 32767; + + adpcm->last_sample[channel] = (sint16) d; + + adpcm->last_step[channel] += ima_step_index_table[sample]; + if (adpcm->last_step[channel] < 0) + adpcm->last_step[channel] = 0; + else if (adpcm->last_step[channel] > 88) + adpcm->last_step[channel] = 88; + + return (uint16) d; +} + +uint8* dsp_decode_ima_adpcm(ADPCM* adpcm, + uint8* src, int size, int channels, int block_size, int* out_size) +{ + uint8* out; + uint8* dst; + uint8 sample; + uint16 decoded; + int channel; + int i; + + *out_size = size * 4; + out = (uint8 *) xzalloc(*out_size); + dst = out; + while (size > 0) + { + if (size % block_size == 0) + { + adpcm->last_sample[0] = (sint16) (((uint16)(*src)) | (((uint16)(*(src + 1))) << 8)); + adpcm->last_step[0] = (sint16) (*(src + 2)); + src += 4; + size -= 4; + *out_size -= 16; + if (channels > 1) + { + adpcm->last_sample[1] = (sint16) (((uint16)(*src)) | (((uint16)(*(src + 1))) << 8)); + adpcm->last_step[1] = (sint16) (*(src + 2)); + src += 4; + size -= 4; + *out_size -= 16; + } + } + + if (channels > 1) + { + for (i = 0; i < 8; i++) + { + channel = (i < 4 ? 0 : 1); + sample = ((*src) & 0x0f); + decoded = dsp_decode_ima_adpcm_sample(adpcm, channel, sample); + dst[((i & 3) << 3) + (channel << 1)] = (decoded & 0xff); + dst[((i & 3) << 3) + (channel << 1) + 1] = (decoded >> 8); + sample = ((*src) >> 4); + decoded = dsp_decode_ima_adpcm_sample(adpcm, channel, sample); + dst[((i & 3) << 3) + (channel << 1) + 4] = (decoded & 0xff); + dst[((i & 3) << 3) + (channel << 1) + 5] = (decoded >> 8); + src++; + } + dst += 32; + size -= 8; + } + else + { + sample = ((*src) & 0x0f); + decoded = dsp_decode_ima_adpcm_sample(adpcm, 0, sample); + *dst++ = (decoded & 0xff); + *dst++ = (decoded >> 8); + sample = ((*src) >> 4); + decoded = dsp_decode_ima_adpcm_sample(adpcm, 0, sample); + *dst++ = (decoded & 0xff); + *dst++ = (decoded >> 8); + src++; + size--; + } + } + return out; +} + +/** + * 0 1 2 3 + * 2 0 6 4 10 8 14 12 + * + * 4 5 6 7 + * 3 1 7 5 11 9 15 13 + */ +static const struct +{ + uint8 byte_num; + uint8 byte_shift; +} ima_stereo_encode_map[] = +{ + { 0, 0 }, + { 4, 0 }, + { 0, 4 }, + { 4, 4 }, + { 1, 0 }, + { 5, 0 }, + { 1, 4 }, + { 5, 4 }, + { 2, 0 }, + { 6, 0 }, + { 2, 4 }, + { 6, 4 }, + { 3, 0 }, + { 7, 0 }, + { 3, 4 }, + { 7, 4 } +}; + +static uint8 dsp_encode_ima_adpcm_sample(ADPCM* adpcm, + int channel, sint16 sample) +{ + sint32 e; + sint32 d; + sint32 ss; + uint8 enc; + sint32 diff; + + ss = ima_step_size_table[adpcm->last_step[channel]]; + d = e = sample - adpcm->last_sample[channel]; + diff = ss >> 3; + enc = 0; + if (e < 0) + { + enc = 8; + e = -e; + } + if (e >= ss) + { + enc |= 4; + e -= ss; + } + ss >>= 1; + if (e >= ss) + { + enc |= 2; + e -= ss; + } + ss >>= 1; + if (e >= ss) + { + enc |= 1; + e -= ss; + } + + if (d < 0) + diff = d + e - diff; + else + diff = d - e + diff; + + diff += adpcm->last_sample[channel]; + if (diff < -32768) + diff = -32768; + else if (diff > 32767) + diff = 32767; + adpcm->last_sample[channel] = (sint16) diff; + + adpcm->last_step[channel] += ima_step_index_table[enc]; + if (adpcm->last_step[channel] < 0) + adpcm->last_step[channel] = 0; + else if (adpcm->last_step[channel] > 88) + adpcm->last_step[channel] = 88; + + return enc; +} + +uint8* dsp_encode_ima_adpcm(ADPCM* adpcm, + uint8* src, int size, int channels, int block_size, int* out_size) +{ + uint8* out; + uint8* dst; + sint16 sample; + uint8 encoded; + int i; + + out = (uint8*) xzalloc(size / 2); + dst = out; + while (size > 0) + { + if ((dst - out) % block_size == 0) + { + *dst++ = adpcm->last_sample[0] & 0xff; + *dst++ = (adpcm->last_sample[0] >> 8) & 0xff; + *dst++ = adpcm->last_step[0]; + *dst++ = 0; + if (channels > 1) + { + *dst++ = adpcm->last_sample[1] & 0xff; + *dst++ = (adpcm->last_sample[1] >> 8) & 0xff; + *dst++ = adpcm->last_step[1]; + *dst++ = 0; + } + } + + if (channels > 1) + { + memset(dst, 0, 8); + for (i = 0; i < 16; i++) + { + sample = (sint16) (((uint16)(*src)) | (((uint16)(*(src + 1))) << 8)); + src += 2; + encoded = dsp_encode_ima_adpcm_sample(adpcm, i % 2, sample); + dst[ima_stereo_encode_map[i].byte_num] |= encoded << ima_stereo_encode_map[i].byte_shift; + } + dst += 8; + size -= 32; + } + else + { + sample = (sint16) (((uint16)(*src)) | (((uint16)(*(src + 1))) << 8)); + src += 2; + encoded = dsp_encode_ima_adpcm_sample(adpcm, 0, sample); + sample = (sint16) (((uint16)(*src)) | (((uint16)(*(src + 1))) << 8)); + src += 2; + encoded |= dsp_encode_ima_adpcm_sample(adpcm, 0, sample) << 4; + *dst++ = encoded; + size -= 4; + } + } + *out_size = dst - out; + return out; +} + From c3d75fd5b4fbc1b2f29a48ca49313f41b194b6f2 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Sun, 14 Aug 2011 22:32:12 +0800 Subject: [PATCH 2/8] libfreerdp-utils/svc_plugin: add interval feature. --- include/freerdp/utils/svc_plugin.h | 3 +++ include/freerdp/utils/thread.h | 1 + libfreerdp-utils/svc_plugin.c | 16 +++++++++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/include/freerdp/utils/svc_plugin.h b/include/freerdp/utils/svc_plugin.h index 4de2f892d..2f18ecf86 100644 --- a/include/freerdp/utils/svc_plugin.h +++ b/include/freerdp/utils/svc_plugin.h @@ -35,9 +35,12 @@ struct rdp_svc_plugin CHANNEL_ENTRY_POINTS_EX channel_entry_points; CHANNEL_DEF channel_def; + int interval_ms; + void (*connect_callback)(rdpSvcPlugin* plugin); void (*receive_callback)(rdpSvcPlugin* plugin, STREAM* data_in); void (*event_callback)(rdpSvcPlugin* plugin, FRDP_EVENT* event); + void (*interval_callback)(rdpSvcPlugin* plugin); void (*terminate_callback)(rdpSvcPlugin* plugin); rdpSvcPluginPrivate* priv; diff --git a/include/freerdp/utils/thread.h b/include/freerdp/utils/thread.h index 8dab3bd86..cb4c4006d 100644 --- a/include/freerdp/utils/thread.h +++ b/include/freerdp/utils/thread.h @@ -43,6 +43,7 @@ void freerdp_thread_start(freerdp_thread* thread, void* func, void* arg); void freerdp_thread_stop(freerdp_thread* thread); #define freerdp_thread_wait(_t) wait_obj_select(_t->signals, _t->num_signals, -1) +#define freerdp_thread_wait_timeout(_t, _timeout) wait_obj_select(_t->signals, _t->num_signals, _timeout) #define freerdp_thread_is_stopped(_t) wait_obj_is_set(_t->signals[0]) #define freerdp_thread_quit(_t) _t->status = -1 #define freerdp_thread_signal(_t) wait_obj_set(_t->signals[1]) diff --git a/libfreerdp-utils/svc_plugin.c b/libfreerdp-utils/svc_plugin.c index aecf84e80..fbd7d1557 100644 --- a/libfreerdp-utils/svc_plugin.c +++ b/libfreerdp-utils/svc_plugin.c @@ -237,9 +237,9 @@ static void svc_plugin_process_data_in(rdpSvcPlugin* plugin) { /* the ownership of the data is passed to the callback */ if (item->data_in) - plugin->receive_callback(plugin, item->data_in); + IFCALL(plugin->receive_callback, plugin, item->data_in); if (item->event_in) - plugin->event_callback(plugin, item->event_in); + IFCALL(plugin->event_callback, plugin, item->event_in); xfree(item); } else @@ -253,17 +253,23 @@ static void* svc_plugin_thread_func(void* arg) DEBUG_SVC("in"); - plugin->connect_callback(plugin); + IFCALL(plugin->connect_callback, plugin); while (1) { - freerdp_thread_wait(plugin->priv->thread); + if (plugin->interval_ms > 0) + freerdp_thread_wait_timeout(plugin->priv->thread, plugin->interval_ms); + else + freerdp_thread_wait(plugin->priv->thread); if (freerdp_thread_is_stopped(plugin->priv->thread)) break; freerdp_thread_reset(plugin->priv->thread); svc_plugin_process_data_in(plugin); + + if (plugin->interval_ms > 0) + IFCALL(plugin->interval_callback, plugin); } freerdp_thread_quit(plugin->priv->thread); @@ -314,7 +320,7 @@ static void svc_plugin_process_terminated(rdpSvcPlugin* plugin) xfree(plugin->priv); plugin->priv = NULL; - plugin->terminate_callback(plugin); + IFCALL(plugin->terminate_callback, plugin); } static void svc_plugin_init_event(void* pInitHandle, uint32 event, void* pData, uint32 dataLength) From bfe7f83f030686b985c632c509dc8222d6b82f9b Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 15 Aug 2011 15:08:23 +0800 Subject: [PATCH 3/8] rdpsnd: add main rdpsnd module. --- channels/CMakeLists.txt | 1 + channels/rdpsnd/CMakeLists.txt | 30 ++ channels/rdpsnd/rdpsnd_main.c | 531 +++++++++++++++++++++++++++++++++ channels/rdpsnd/rdpsnd_main.h | 74 +++++ 4 files changed, 636 insertions(+) create mode 100644 channels/rdpsnd/CMakeLists.txt create mode 100644 channels/rdpsnd/rdpsnd_main.c create mode 100644 channels/rdpsnd/rdpsnd_main.h diff --git a/channels/CMakeLists.txt b/channels/CMakeLists.txt index a5a55738d..648e2a819 100644 --- a/channels/CMakeLists.txt +++ b/channels/CMakeLists.txt @@ -22,4 +22,5 @@ add_subdirectory(drdynvc) add_subdirectory(rdpdbg) add_subdirectory(rdpdr) add_subdirectory(rail) +add_subdirectory(rdpsnd) diff --git a/channels/rdpsnd/CMakeLists.txt b/channels/rdpsnd/CMakeLists.txt new file mode 100644 index 000000000..08615fcf7 --- /dev/null +++ b/channels/rdpsnd/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(RDPSND_SRCS + rdpsnd_main.c + rdpsnd_main.h +) + +add_library(rdpsnd SHARED ${RDPSND_SRCS}) +set_target_properties(rdpsnd PROPERTIES PREFIX "") + +target_link_libraries(rdpsnd freerdp-utils) + +install(TARGETS rdpsnd DESTINATION ${FREERDP_PLUGIN_PATH}) diff --git a/channels/rdpsnd/rdpsnd_main.c b/channels/rdpsnd/rdpsnd_main.c new file mode 100644 index 000000000..61c7ef161 --- /dev/null +++ b/channels/rdpsnd/rdpsnd_main.c @@ -0,0 +1,531 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Audio Output Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rdpsnd_main.h" + +#define SNDC_CLOSE 1 +#define SNDC_WAVE 2 +#define SNDC_SETVOLUME 3 +#define SNDC_SETPITCH 4 +#define SNDC_WAVECONFIRM 5 +#define SNDC_TRAINING 6 +#define SNDC_FORMATS 7 +#define SNDC_CRYPTKEY 8 +#define SNDC_WAVEENCRYPT 9 +#define SNDC_UDPWAVE 10 +#define SNDC_UDPWAVELAST 11 +#define SNDC_QUALITYMODE 12 + +#define TSSNDCAPS_ALIVE 1 +#define TSSNDCAPS_VOLUME 2 +#define TSSNDCAPS_PITCH 4 + +#define DYNAMIC_QUALITY 0x0000 +#define MEDIUM_QUALITY 0x0001 +#define HIGH_QUALITY 0x0002 + +struct rdpsnd_plugin +{ + rdpSvcPlugin plugin; + + LIST* data_out_list; + + uint8 cBlockNo; + rdpsndFormat* supported_formats; + int n_supported_formats; + int current_format; + + boolean expectingWave; + uint8 waveData[4]; + uint16 waveDataSize; + uint32 wTimeStamp; /* server timestamp */ + uint32 wave_timestamp; /* client timestamp */ + + boolean is_open; + uint32 close_timestamp; + + uint16 fixed_format; + uint16 fixed_channel; + uint32 fixed_rate; + + /* Device plugin */ + rdpsndDevicePlugin* device; +}; + +struct data_out_item +{ + STREAM* data_out; + uint32 out_timestamp; +}; + +/* get time in milliseconds */ +static uint32 get_mstime(void) +{ + struct timeval tp; + + gettimeofday(&tp, 0); + return (tp.tv_sec * 1000) + (tp.tv_usec / 1000); +} + +/* process the linked list of data that has queued to be sent */ +static void rdpsnd_process_interval(rdpSvcPlugin* plugin) +{ + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)plugin; + struct data_out_item* item; + uint32 cur_time; + + while (rdpsnd->data_out_list->head) + { + item = (struct data_out_item*)rdpsnd->data_out_list->head->data; + cur_time = get_mstime(); + if (cur_time <= item->out_timestamp) + break; + + item = (struct data_out_item*)list_dequeue(rdpsnd->data_out_list); + svc_plugin_send(plugin, item->data_out); + xfree(item); + + DEBUG_SVC("processed data_out"); + } + + if (rdpsnd->is_open && rdpsnd->close_timestamp > 0) + { + cur_time = get_mstime(); + if (cur_time > rdpsnd->close_timestamp) + { + if (rdpsnd->device) + IFCALL(rdpsnd->device->Close, rdpsnd->device); + rdpsnd->is_open = False; + rdpsnd->close_timestamp = 0; + + DEBUG_SVC("processed close"); + } + } + + if (rdpsnd->data_out_list->head == NULL && !rdpsnd->is_open) + { + rdpsnd->plugin.interval_ms = 0; + } +} + +static void rdpsnd_free_supported_formats(rdpsndPlugin* rdpsnd) +{ + uint16 i; + + for (i = 0; i < rdpsnd->n_supported_formats; i++) + xfree(rdpsnd->supported_formats[i].data); + xfree(rdpsnd->supported_formats); + + rdpsnd->supported_formats = NULL; + rdpsnd->n_supported_formats = 0; +} + +/* receives a list of server supported formats and returns a list + of client supported formats */ +static void rdpsnd_process_message_formats(rdpsndPlugin* rdpsnd, STREAM* data_in) +{ + uint16 wNumberOfFormats; + uint16 nFormat; + uint16 wVersion; + STREAM* data_out; + rdpsndFormat* out_formats; + uint16 n_out_formats; + rdpsndFormat* format; + uint8* format_mark; + uint8* data_mark; + int pos; + + rdpsnd_free_supported_formats(rdpsnd); + + stream_seek_uint32(data_in); /* dwFlags */ + stream_seek_uint32(data_in); /* dwVolume */ + stream_seek_uint32(data_in); /* dwPitch */ + stream_seek_uint16(data_in); /* wDGramPort */ + stream_read_uint16(data_in, wNumberOfFormats); + stream_read_uint8(data_in, rdpsnd->cBlockNo); /* cLastBlockConfirmed */ + stream_read_uint16(data_in, wVersion); + stream_seek_uint8(data_in); /* bPad */ + + DEBUG_SVC("wNumberOfFormats %d wVersion %d", wNumberOfFormats, wVersion); + if (wNumberOfFormats < 1) + { + DEBUG_WARN("wNumberOfFormats is 0"); + return; + } + + out_formats = (rdpsndFormat*)xzalloc(wNumberOfFormats * sizeof(rdpsndFormat)); + n_out_formats = 0; + + data_out = stream_new(24); + stream_write_uint8(data_out, SNDC_FORMATS); /* msgType */ + stream_write_uint8(data_out, 0); /* bPad */ + stream_seek_uint16(data_out); /* BodySize */ + stream_write_uint32(data_out, TSSNDCAPS_ALIVE); /* dwFlags */ + stream_write_uint32(data_out, 0); /* dwVolume */ + stream_write_uint32(data_out, 0); /* dwPitch */ + stream_write_uint16_be(data_out, 0); /* wDGramPort */ + stream_seek_uint16(data_out); /* wNumberOfFormats */ + stream_write_uint8(data_out, 0); /* cLastBlockConfirmed */ + stream_write_uint16(data_out, 6); /* wVersion */ + stream_write_uint8(data_out, 0); /* bPad */ + + for (nFormat = 0; nFormat < wNumberOfFormats; nFormat++) + { + stream_get_mark(data_in, format_mark); + format = &out_formats[n_out_formats]; + stream_read_uint16(data_in, format->wFormatTag); + stream_read_uint16(data_in, format->nChannels); + stream_read_uint32(data_in, format->nSamplesPerSec); + stream_seek_uint32(data_in); /* nAvgBytesPerSec */ + stream_read_uint16(data_in, format->nBlockAlign); + stream_read_uint16(data_in, format->wBitsPerSample); + stream_read_uint16(data_in, format->cbSize); + stream_get_mark(data_in, data_mark); + stream_seek(data_in, format->cbSize); + format->data = NULL; + + if (rdpsnd->fixed_format > 0 && rdpsnd->fixed_format != format->wFormatTag) + continue; + if (rdpsnd->fixed_channel > 0 && rdpsnd->fixed_channel != format->nChannels) + continue; + if (rdpsnd->fixed_rate > 0 && rdpsnd->fixed_rate != format->nSamplesPerSec) + continue; + if (rdpsnd->device && rdpsnd->device->FormatSupported(rdpsnd->device, format)) + { + stream_check_size(data_out, 18 + format->cbSize); + stream_write(data_out, format_mark, 18 + format->cbSize); + if (format->cbSize > 0) + { + format->data = xmalloc(format->cbSize); + memcpy(format->data, data_mark, format->cbSize); + } + n_out_formats++; + } + } + + rdpsnd->n_supported_formats = n_out_formats; + if (n_out_formats > 0) + { + rdpsnd->supported_formats = out_formats; + } + else + { + xfree(out_formats); + DEBUG_WARN("no formats supported"); + } + + pos = stream_get_pos(data_out); + stream_set_pos(data_out, 2); + stream_write_uint16(data_out, pos - 4); + stream_set_pos(data_out, 18); + stream_write_uint16(data_out, n_out_formats); + stream_set_pos(data_out, pos); + + svc_plugin_send((rdpSvcPlugin*)rdpsnd, data_out); + + if (wVersion >= 6) + { + data_out = stream_new(8); + stream_write_uint8(data_out, SNDC_QUALITYMODE); /* msgType */ + stream_write_uint8(data_out, 0); /* bPad */ + stream_write_uint16(data_out, 4); /* BodySize */ + stream_write_uint16(data_out, HIGH_QUALITY); /* wQualityMode */ + stream_write_uint16(data_out, 0); /* Reserved */ + + svc_plugin_send((rdpSvcPlugin*)rdpsnd, data_out); + } +} + +/* server is getting a feel of the round trip time */ +static void rdpsnd_process_message_training(rdpsndPlugin* rdpsnd, STREAM* data_in) +{ + uint16 wTimeStamp; + uint16 wPackSize; + STREAM* data_out; + + stream_read_uint16(data_in, wTimeStamp); + stream_read_uint16(data_in, wPackSize); + + data_out = stream_new(8); + stream_write_uint8(data_out, SNDC_TRAINING); /* msgType */ + stream_write_uint8(data_out, 0); /* bPad */ + stream_write_uint16(data_out, 4); /* BodySize */ + stream_write_uint16(data_out, wTimeStamp); + stream_write_uint16(data_out, wPackSize); + + svc_plugin_send((rdpSvcPlugin*)rdpsnd, data_out); +} + +static void rdpsnd_process_message_wave_info(rdpsndPlugin* rdpsnd, STREAM* data_in, uint16 BodySize) +{ + uint16 wFormatNo; + + stream_read_uint16(data_in, rdpsnd->wTimeStamp); + stream_read_uint16(data_in, wFormatNo); + stream_read_uint8(data_in, rdpsnd->cBlockNo); + stream_seek(data_in, 3); /* bPad */ + stream_read(data_in, rdpsnd->waveData, 4); + rdpsnd->waveDataSize = BodySize - 8; + rdpsnd->wave_timestamp = get_mstime(); + rdpsnd->expectingWave = True; + + DEBUG_SVC("waveDataSize %d wFormatNo %d", rdpsnd->waveDataSize, wFormatNo); + + if (!rdpsnd->is_open) + { + rdpsnd->current_format = wFormatNo; + rdpsnd->is_open = True; + if (rdpsnd->device) + IFCALL(rdpsnd->device->Open, rdpsnd->device, &rdpsnd->supported_formats[wFormatNo]); + } + else if (wFormatNo != rdpsnd->current_format) + { + rdpsnd->current_format = wFormatNo; + if (rdpsnd->device) + IFCALL(rdpsnd->device->SetFormat, rdpsnd->device, &rdpsnd->supported_formats[wFormatNo]); + } +} + +/* header is not removed from data in this function */ +static void rdpsnd_process_message_wave(rdpsndPlugin* rdpsnd, STREAM* data_in) +{ + uint16 wTimeStamp; + uint32 delay_ms; + uint32 process_ms; + struct data_out_item* item; + + rdpsnd->expectingWave = 0; + memcpy(stream_get_head(data_in), rdpsnd->waveData, 4); + if (stream_get_size(data_in) != rdpsnd->waveDataSize) + { + DEBUG_WARN("size error"); + return; + } + if (rdpsnd->device) + IFCALL(rdpsnd->device->Play, rdpsnd->device, stream_get_head(data_in), stream_get_size(data_in)); + + process_ms = get_mstime() - rdpsnd->wave_timestamp; + delay_ms = 250; + wTimeStamp = rdpsnd->wTimeStamp + delay_ms; + + DEBUG_SVC("data_size %d delay_ms %u process_ms %u", + stream_get_size(data_in), delay_ms, process_ms); + + item = xnew(struct data_out_item); + item->data_out = stream_new(8); + stream_write_uint8(item->data_out, SNDC_WAVECONFIRM); + stream_write_uint8(item->data_out, 0); + stream_write_uint16(item->data_out, 4); + stream_write_uint16(item->data_out, wTimeStamp); + stream_write_uint8(item->data_out, rdpsnd->cBlockNo); /* cConfirmedBlockNo */ + stream_write_uint8(item->data_out, 0); /* bPad */ + item->out_timestamp = rdpsnd->wave_timestamp + delay_ms; + + list_enqueue(rdpsnd->data_out_list, item); + rdpsnd->plugin.interval_ms = 10; +} + +static void rdpsnd_process_message_close(rdpsndPlugin* rdpsnd) +{ + DEBUG_SVC("server closes."); + rdpsnd->close_timestamp = get_mstime() + 2000; + rdpsnd->plugin.interval_ms = 10; +} + +static void rdpsnd_process_message_setvolume(rdpsndPlugin* rdpsnd, STREAM* data_in) +{ + uint32 dwVolume; + + stream_read_uint32(data_in, dwVolume); + DEBUG_SVC("dwVolume 0x%X", dwVolume); + if (rdpsnd->device) + IFCALL(rdpsnd->device->SetVolume, rdpsnd->device, dwVolume); +} + +static void rdpsnd_process_receive(rdpSvcPlugin* plugin, STREAM* data_in) +{ + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)plugin; + uint8 msgType; + uint16 BodySize; + + if (rdpsnd->expectingWave) + { + rdpsnd_process_message_wave(rdpsnd, data_in); + return; + } + + stream_read_uint8(data_in, msgType); /* msgType */ + stream_seek_uint8(data_in); /* bPad */ + stream_read_uint16(data_in, BodySize); + + DEBUG_SVC("msgType %d BodySize %d", msgType, BodySize); + + switch (msgType) + { + case SNDC_FORMATS: + rdpsnd_process_message_formats(rdpsnd, data_in); + break; + case SNDC_TRAINING: + rdpsnd_process_message_training(rdpsnd, data_in); + break; + case SNDC_WAVE: + rdpsnd_process_message_wave_info(rdpsnd, data_in, BodySize); + break; + case SNDC_CLOSE: + rdpsnd_process_message_close(rdpsnd); + break; + case SNDC_SETVOLUME: + rdpsnd_process_message_setvolume(rdpsnd, data_in); + break; + default: + DEBUG_WARN("unknown msgType %d", msgType); + break; + } +} + +static void rdpsnd_register_device_plugin(rdpsndPlugin* rdpsnd, rdpsndDevicePlugin* device) +{ + if (rdpsnd->device) + { + DEBUG_WARN("existing device, abort."); + return; + } + rdpsnd->device = device; +} + +static boolean rdpsnd_load_device_plugin(rdpsndPlugin* rdpsnd, const char* name, FRDP_PLUGIN_DATA* data) +{ + FREERDP_RDPSND_DEVICE_ENTRY_POINTS entryPoints; + PFREERDP_RDPSND_DEVICE_ENTRY entry; + + entry = (PFREERDP_RDPSND_DEVICE_ENTRY)freerdp_load_plugin(name, RDPSND_DEVICE_EXPORT_FUNC_NAME); + if (entry == NULL) + { + return False; + } + + entryPoints.rdpsnd = rdpsnd; + entryPoints.pRegisterRdpsndDevice = rdpsnd_register_device_plugin; + entryPoints.plugin_data = data; + if (entry(&entryPoints) != 0) + { + DEBUG_WARN("%s entry returns error.", name); + return False; + } + return True; +} + +static void rdpsnd_process_plugin_data(rdpsndPlugin* rdpsnd, FRDP_PLUGIN_DATA* data) +{ + if (strcmp((char*)data->data[0], "format") == 0) + { + rdpsnd->fixed_format = atoi(data->data[1]); + } + else if (strcmp((char*)data->data[0], "rate") == 0) + { + rdpsnd->fixed_rate = atoi(data->data[1]); + } + else if (strcmp((char*)data->data[0], "channel") == 0) + { + rdpsnd->fixed_channel = atoi(data->data[1]); + } + else + { + rdpsnd_load_device_plugin(rdpsnd, (char*)data->data[0], data); + } +} + +static void rdpsnd_process_connect(rdpSvcPlugin* plugin) +{ + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)plugin; + FRDP_PLUGIN_DATA* data; + FRDP_PLUGIN_DATA default_data[2] = { { 0 }, { 0 } }; + + DEBUG_SVC("connecting"); + + plugin->interval_callback = rdpsnd_process_interval; + + rdpsnd->data_out_list = list_new(); + + data = (FRDP_PLUGIN_DATA*)plugin->channel_entry_points.pExtendedData; + while (data && data->size > 0) + { + rdpsnd_process_plugin_data(rdpsnd, data); + data = (FRDP_PLUGIN_DATA*) (((void*) data) + data->size); + } + + if (rdpsnd->device == NULL) + { + default_data[0].size = sizeof(FRDP_PLUGIN_DATA); + default_data[0].data[0] = "pulse"; + default_data[0].data[1] = ""; + if (!rdpsnd_load_device_plugin(rdpsnd, "pulse", default_data)) + { + default_data[0].data[0] = "alsa"; + default_data[0].data[1] = "default"; + rdpsnd_load_device_plugin(rdpsnd, "alsa", default_data); + } + } + if (rdpsnd->device == NULL) + { + DEBUG_WARN("no sound device."); + } +} + +static void rdpsnd_process_event(rdpSvcPlugin* plugin, FRDP_EVENT* event) +{ + freerdp_event_free(event); +} + +static void rdpsnd_process_terminate(rdpSvcPlugin* plugin) +{ + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)plugin; + struct data_out_item* item; + + if (rdpsnd->device) + IFCALL(rdpsnd->device->Free, rdpsnd->device); + + while ((item = list_dequeue(rdpsnd->data_out_list)) != NULL) + { + stream_free(item->data_out); + xfree(item); + } + list_free(rdpsnd->data_out_list); + + rdpsnd_free_supported_formats(rdpsnd); + + xfree(plugin); +} + +DEFINE_SVC_PLUGIN(rdpsnd, "rdpsnd", + CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP) + diff --git a/channels/rdpsnd/rdpsnd_main.h b/channels/rdpsnd/rdpsnd_main.h new file mode 100644 index 000000000..7313804ba --- /dev/null +++ b/channels/rdpsnd/rdpsnd_main.h @@ -0,0 +1,74 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Audio Output Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * + * 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 __RDPSND_MAIN_H +#define __RDPSND_MAIN_H + +typedef struct rdpsnd_plugin rdpsndPlugin; + +typedef struct rdpsnd_format rdpsndFormat; +struct rdpsnd_format +{ + uint16 wFormatTag; + uint16 nChannels; + uint32 nSamplesPerSec; + uint16 nBlockAlign; + uint16 wBitsPerSample; + uint16 cbSize; + uint8* data; +}; + +typedef struct rdpsnd_device_plugin rdpsndDevicePlugin; + +typedef boolean (*pcFormatSupported) (rdpsndDevicePlugin* devplugin, rdpsndFormat* format); +typedef void (*pcOpen) (rdpsndDevicePlugin* devplugin, rdpsndFormat* format); +typedef void (*pcSetFormat) (rdpsndDevicePlugin* devplugin, rdpsndFormat* format); +typedef void (*pcSetVolume) (rdpsndDevicePlugin* devplugin, uint32 value); +typedef void (*pcPlay) (rdpsndDevicePlugin* devplugin, uint8* data, int size); +typedef void (*pcClose) (rdpsndDevicePlugin* devplugin); +typedef void (*pcFree) (rdpsndDevicePlugin* devplugin); + +struct rdpsnd_device_plugin +{ + pcFormatSupported FormatSupported; + pcOpen Open; + pcSetFormat SetFormat; + pcSetVolume SetVolume; + pcPlay Play; + pcClose Close; + pcFree Free; +}; + +#define RDPSND_DEVICE_EXPORT_FUNC_NAME "FreeRDPRdpsndDeviceEntry" + +typedef void (*PREGISTERRDPSNDDEVICE)(rdpsndPlugin* rdpsnd, rdpsndDevicePlugin* device); + +struct _FREERDP_RDPSND_DEVICE_ENTRY_POINTS +{ + rdpsndPlugin* rdpsnd; + PREGISTERRDPSNDDEVICE pRegisterRdpsndDevice; + FRDP_PLUGIN_DATA* plugin_data; +}; +typedef struct _FREERDP_RDPSND_DEVICE_ENTRY_POINTS FREERDP_RDPSND_DEVICE_ENTRY_POINTS; +typedef FREERDP_RDPSND_DEVICE_ENTRY_POINTS* PFREERDP_RDPSND_DEVICE_ENTRY_POINTS; + +typedef int (*PFREERDP_RDPSND_DEVICE_ENTRY)(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints); + +#endif /* __RDPSND_MAIN_H */ + From a2a9fdb630fa6ce6241d0d65efb2b844178d6601 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 15 Aug 2011 15:21:56 +0800 Subject: [PATCH 4/8] rdpsnd: add device sub-plugin prefix. --- channels/rdpsnd/rdpsnd_main.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/channels/rdpsnd/rdpsnd_main.c b/channels/rdpsnd/rdpsnd_main.c index 61c7ef161..43c89843f 100644 --- a/channels/rdpsnd/rdpsnd_main.c +++ b/channels/rdpsnd/rdpsnd_main.c @@ -426,8 +426,18 @@ static boolean rdpsnd_load_device_plugin(rdpsndPlugin* rdpsnd, const char* name, { FREERDP_RDPSND_DEVICE_ENTRY_POINTS entryPoints; PFREERDP_RDPSND_DEVICE_ENTRY entry; + char* fullname; - entry = (PFREERDP_RDPSND_DEVICE_ENTRY)freerdp_load_plugin(name, RDPSND_DEVICE_EXPORT_FUNC_NAME); + if (strrchr(name, '.') != NULL) + entry = (PFREERDP_RDPSND_DEVICE_ENTRY)freerdp_load_plugin(name, RDPSND_DEVICE_EXPORT_FUNC_NAME); + else + { + fullname = xzalloc(strlen(name) + 8); + strcpy(fullname, "rdpsnd_"); + strcat(fullname, name); + entry = (PFREERDP_RDPSND_DEVICE_ENTRY)freerdp_load_plugin(fullname, RDPSND_DEVICE_EXPORT_FUNC_NAME); + xfree(fullname); + } if (entry == NULL) { return False; From a122006b0e2a8cd5eda9e027b9d710344756819c Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 15 Aug 2011 16:28:52 +0800 Subject: [PATCH 5/8] rdpsnd: add ALSA sub-plugin. --- CMakeLists.txt | 1 + channels/rdpsnd/CMakeLists.txt | 5 + channels/rdpsnd/alsa/CMakeLists.txt | 33 +++ channels/rdpsnd/alsa/rdpsnd_alsa.c | 337 ++++++++++++++++++++++++++++ channels/rdpsnd/rdpsnd_main.c | 6 + channels/rdpsnd/rdpsnd_main.h | 14 +- 6 files changed, 389 insertions(+), 7 deletions(-) create mode 100644 channels/rdpsnd/alsa/CMakeLists.txt create mode 100644 channels/rdpsnd/alsa/rdpsnd_alsa.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b6a5a6f0..4db64e14a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,7 @@ check_include_files(unistd.h HAVE_UNISTD_H) # Libraries that we have a hard dependency on find_package(OpenSSL REQUIRED) find_package(ZLIB REQUIRED) +find_package(ALSA) # Endian test_big_endian(BIG_ENDIAN) diff --git a/channels/rdpsnd/CMakeLists.txt b/channels/rdpsnd/CMakeLists.txt index 08615fcf7..db8f6832a 100644 --- a/channels/rdpsnd/CMakeLists.txt +++ b/channels/rdpsnd/CMakeLists.txt @@ -28,3 +28,8 @@ set_target_properties(rdpsnd PROPERTIES PREFIX "") target_link_libraries(rdpsnd freerdp-utils) install(TARGETS rdpsnd DESTINATION ${FREERDP_PLUGIN_PATH}) + +if(ALSA_FOUND) + add_subdirectory(alsa) +endif() + diff --git a/channels/rdpsnd/alsa/CMakeLists.txt b/channels/rdpsnd/alsa/CMakeLists.txt new file mode 100644 index 000000000..73a9f4e07 --- /dev/null +++ b/channels/rdpsnd/alsa/CMakeLists.txt @@ -0,0 +1,33 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(RDPSND_ALSA_SRCS + rdpsnd_alsa.c +) + +include_directories(..) +include_directories(${ALSA_INCLUDE_DIRS}) + +add_library(rdpsnd_alsa SHARED ${RDPSND_ALSA_SRCS}) +set_target_properties(rdpsnd_alsa PROPERTIES PREFIX "") + +target_link_libraries(rdpsnd_alsa freerdp-utils) +target_link_libraries(rdpsnd_alsa ${ALSA_LIBRARIES}) + +install(TARGETS rdpsnd_alsa DESTINATION ${FREERDP_PLUGIN_PATH}) diff --git a/channels/rdpsnd/alsa/rdpsnd_alsa.c b/channels/rdpsnd/alsa/rdpsnd_alsa.c new file mode 100644 index 000000000..e88e85a93 --- /dev/null +++ b/channels/rdpsnd/alsa/rdpsnd_alsa.c @@ -0,0 +1,337 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Audio Output Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_alsa_plugin rdpsndAlsaPlugin; +struct rdpsnd_alsa_plugin +{ + rdpsndDevicePlugin device; + + char* device_name; + snd_pcm_t* out_handle; + uint32 source_rate; + uint32 actual_rate; + snd_pcm_format_t format; + uint32 source_channels; + uint32 actual_channels; + int bytes_per_channel; + int wformat; + int block_size; + ADPCM adpcm; +}; + +static void rdpsnd_alsa_set_params(rdpsndAlsaPlugin* alsa) +{ + snd_pcm_hw_params_t* hw_params; + snd_pcm_sw_params_t* sw_params; + int error; + snd_pcm_uframes_t frames; + + snd_pcm_drop(alsa->out_handle); + + error = snd_pcm_hw_params_malloc(&hw_params); + if (error < 0) + { + DEBUG_WARN("snd_pcm_hw_params_malloc failed"); + return; + } + snd_pcm_hw_params_any(alsa->out_handle, hw_params); + snd_pcm_hw_params_set_access(alsa->out_handle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(alsa->out_handle, hw_params, + alsa->format); + snd_pcm_hw_params_set_rate_near(alsa->out_handle, hw_params, + &alsa->actual_rate, NULL); + snd_pcm_hw_params_set_channels_near(alsa->out_handle, hw_params, + &alsa->actual_channels); + frames = alsa->actual_rate * 4; + snd_pcm_hw_params_set_buffer_size_near(alsa->out_handle, hw_params, + &frames); + snd_pcm_hw_params(alsa->out_handle, hw_params); + snd_pcm_hw_params_free(hw_params); + + error = snd_pcm_sw_params_malloc(&sw_params); + if (error < 0) + { + DEBUG_WARN("snd_pcm_sw_params_malloc failed"); + return; + } + snd_pcm_sw_params_current(alsa->out_handle, sw_params); + snd_pcm_sw_params_set_start_threshold(alsa->out_handle, sw_params, + frames / 2); + snd_pcm_sw_params(alsa->out_handle, sw_params); + snd_pcm_sw_params_free(sw_params); + + snd_pcm_prepare(alsa->out_handle); + + DEBUG_SVC("hardware buffer %d frames, playback buffer %.2g seconds", + (int)frames, (double)frames / 2.0 / (double)alsa->actual_rate); + if ((alsa->actual_rate != alsa->source_rate) || + (alsa->actual_channels != alsa->source_channels)) + { + DEBUG_SVC("actual rate %d / channel %d is different from source rate %d / channel %d, resampling required.", + alsa->actual_rate, alsa->actual_channels, alsa->source_rate, alsa->source_channels); + } +} + +static void rdpsnd_alsa_set_format(rdpsndDevicePlugin* device, rdpsndFormat* format) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + + if (format != NULL) + { + alsa->source_rate = format->nSamplesPerSec; + alsa->actual_rate = format->nSamplesPerSec; + alsa->source_channels = format->nChannels; + alsa->actual_channels = format->nChannels; + switch (format->wFormatTag) + { + case 1: /* PCM */ + switch (format->wBitsPerSample) + { + case 8: + alsa->format = SND_PCM_FORMAT_S8; + alsa->bytes_per_channel = 1; + break; + case 16: + alsa->format = SND_PCM_FORMAT_S16_LE; + alsa->bytes_per_channel = 2; + break; + } + break; + + case 0x11: /* IMA ADPCM */ + alsa->format = SND_PCM_FORMAT_S16_LE; + alsa->bytes_per_channel = 2; + break; + } + alsa->wformat = format->wFormatTag; + alsa->block_size = format->nBlockAlign; + } + + rdpsnd_alsa_set_params(alsa); +} + +static void rdpsnd_alsa_open(rdpsndDevicePlugin* device, rdpsndFormat* format) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + int error; + + if (alsa->out_handle != 0) + return; + + DEBUG_SVC("opening"); + + error = snd_pcm_open(&alsa->out_handle, alsa->device_name, + SND_PCM_STREAM_PLAYBACK, 0); + if (error < 0) + { + DEBUG_WARN("snd_pcm_open failed"); + } + else + { + memset(&alsa->adpcm, 0, sizeof(ADPCM)); + rdpsnd_alsa_set_format(device, format); + } +} + +static void rdpsnd_alsa_close(rdpsndDevicePlugin* device) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + + if (alsa->out_handle != 0) + { + DEBUG_SVC("close"); + snd_pcm_drain(alsa->out_handle); + snd_pcm_close(alsa->out_handle); + alsa->out_handle = 0; + } +} + +static void rdpsnd_alsa_free(rdpsndDevicePlugin* device) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + + rdpsnd_alsa_close(device); + xfree(alsa->device_name); + xfree(alsa); +} + +static boolean rdpsnd_alsa_format_supported(rdpsndDevicePlugin* device, rdpsndFormat* 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 0x11: /* IMA ADPCM */ + if (format->nSamplesPerSec <= 48000 && + format->wBitsPerSample == 4 && + (format->nChannels == 1 || format->nChannels == 2)) + { + return True; + } + break; + } + return False; +} + +static void rdpsnd_alsa_set_volume(rdpsndDevicePlugin* device, uint32 value) +{ +} + +static void rdpsnd_alsa_play(rdpsndDevicePlugin* device, uint8* data, int size) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + uint8* decoded_data; + int decoded_size; + uint8* src; + uint8* resampled_data; + int len; + int error; + int frames; + int rbytes_per_frame; + int sbytes_per_frame; + uint8* pindex; + uint8* end; + + if (alsa->out_handle == 0) + return; + + if (alsa->wformat == 0x11) + { + decoded_data = dsp_decode_ima_adpcm(&alsa->adpcm, + data, size, alsa->source_channels, alsa->block_size, &decoded_size); + size = decoded_size; + src = decoded_data; + } + else + { + decoded_data = NULL; + src = data; + } + + sbytes_per_frame = alsa->source_channels * alsa->bytes_per_channel; + rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_channel; + if ((size % sbytes_per_frame) != 0) + { + DEBUG_WARN("error len mod"); + return; + } + + if ((alsa->source_rate == alsa->actual_rate) && + (alsa->source_channels == alsa->actual_channels)) + { + resampled_data = NULL; + } + else + { + resampled_data = dsp_resample(src, alsa->bytes_per_channel, + alsa->source_channels, alsa->source_rate, size / sbytes_per_frame, + alsa->actual_channels, alsa->actual_rate, &frames); + DEBUG_SVC("resampled %d frames at %d to %d frames at %d", + size / sbytes_per_frame, alsa->source_rate, frames, alsa->actual_rate); + size = frames * rbytes_per_frame; + src = resampled_data; + } + + pindex = src; + end = pindex + size; + while (pindex < end) + { + len = end - pindex; + frames = len / rbytes_per_frame; + error = snd_pcm_writei(alsa->out_handle, pindex, frames); + if (error == -EPIPE) + { + snd_pcm_recover(alsa->out_handle, error, 0); + error = 0; + } + else if (error < 0) + { + DEBUG_WARN("error %d", error); + snd_pcm_close(alsa->out_handle); + alsa->out_handle = 0; + rdpsnd_alsa_open(device, NULL); + break; + } + pindex += error * rbytes_per_frame; + } + + if (resampled_data) + free(resampled_data); + if (decoded_data) + free(decoded_data); +} + +int FreeRDPRdpsndDeviceEntry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + rdpsndAlsaPlugin* alsa; + FRDP_PLUGIN_DATA* data; + + alsa = xnew(rdpsndAlsaPlugin); + + alsa->device.Open = rdpsnd_alsa_open; + alsa->device.FormatSupported = rdpsnd_alsa_format_supported; + alsa->device.SetFormat = rdpsnd_alsa_set_format; + alsa->device.SetVolume = rdpsnd_alsa_set_volume; + alsa->device.Play = rdpsnd_alsa_play; + alsa->device.Close = rdpsnd_alsa_close; + alsa->device.Free = rdpsnd_alsa_free; + + data = pEntryPoints->plugin_data; + if (data && strcmp((char*)data->data[0], "alsa") == 0) + { + alsa->device_name = xstrdup((char*)data->data[1]); + } + if (alsa->device_name == NULL) + { + alsa->device_name = xstrdup("default"); + } + alsa->out_handle = 0; + alsa->source_rate = 22050; + alsa->actual_rate = 22050; + alsa->format = SND_PCM_FORMAT_S16_LE; + alsa->source_channels = 2; + alsa->actual_channels = 2; + alsa->bytes_per_channel = 2; + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)alsa); + + return 0; +} + diff --git a/channels/rdpsnd/rdpsnd_main.c b/channels/rdpsnd/rdpsnd_main.c index 43c89843f..1f17e8adf 100644 --- a/channels/rdpsnd/rdpsnd_main.c +++ b/channels/rdpsnd/rdpsnd_main.c @@ -213,6 +213,10 @@ static void rdpsnd_process_message_formats(rdpsndPlugin* rdpsnd, STREAM* data_in stream_seek(data_in, format->cbSize); format->data = NULL; + DEBUG_SVC("wFormatTag=%d nChannels=%d nSamplesPerSec=%d nBlockAlign=%d wBitsPerSample=%d", + format->wFormatTag, format->nChannels, format->nSamplesPerSec, + format->nBlockAlign, format->wBitsPerSample); + if (rdpsnd->fixed_format > 0 && rdpsnd->fixed_format != format->wFormatTag) continue; if (rdpsnd->fixed_channel > 0 && rdpsnd->fixed_channel != format->nChannels) @@ -221,6 +225,8 @@ static void rdpsnd_process_message_formats(rdpsndPlugin* rdpsnd, STREAM* data_in continue; if (rdpsnd->device && rdpsnd->device->FormatSupported(rdpsnd->device, format)) { + DEBUG_SVC("format supported."); + stream_check_size(data_out, 18 + format->cbSize); stream_write(data_out, format_mark, 18 + format->cbSize); if (format->cbSize > 0) diff --git a/channels/rdpsnd/rdpsnd_main.h b/channels/rdpsnd/rdpsnd_main.h index 7313804ba..ba391a09e 100644 --- a/channels/rdpsnd/rdpsnd_main.h +++ b/channels/rdpsnd/rdpsnd_main.h @@ -36,13 +36,13 @@ struct rdpsnd_format typedef struct rdpsnd_device_plugin rdpsndDevicePlugin; -typedef boolean (*pcFormatSupported) (rdpsndDevicePlugin* devplugin, rdpsndFormat* format); -typedef void (*pcOpen) (rdpsndDevicePlugin* devplugin, rdpsndFormat* format); -typedef void (*pcSetFormat) (rdpsndDevicePlugin* devplugin, rdpsndFormat* format); -typedef void (*pcSetVolume) (rdpsndDevicePlugin* devplugin, uint32 value); -typedef void (*pcPlay) (rdpsndDevicePlugin* devplugin, uint8* data, int size); -typedef void (*pcClose) (rdpsndDevicePlugin* devplugin); -typedef void (*pcFree) (rdpsndDevicePlugin* devplugin); +typedef boolean (*pcFormatSupported) (rdpsndDevicePlugin* device, rdpsndFormat* format); +typedef void (*pcOpen) (rdpsndDevicePlugin* device, rdpsndFormat* format); +typedef void (*pcSetFormat) (rdpsndDevicePlugin* device, rdpsndFormat* format); +typedef void (*pcSetVolume) (rdpsndDevicePlugin* device, uint32 value); +typedef void (*pcPlay) (rdpsndDevicePlugin* device, uint8* data, int size); +typedef void (*pcClose) (rdpsndDevicePlugin* device); +typedef void (*pcFree) (rdpsndDevicePlugin* device); struct rdpsnd_device_plugin { From a8fde1144bf6423284cc70c9abaa57c443466a9e Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 15 Aug 2011 16:58:07 +0800 Subject: [PATCH 6/8] rdpsnd: start playing immediately when a close signal received. --- channels/rdpsnd/alsa/rdpsnd_alsa.c | 11 +++++++++++ channels/rdpsnd/rdpsnd_main.c | 2 ++ channels/rdpsnd/rdpsnd_main.h | 2 ++ 3 files changed, 15 insertions(+) diff --git a/channels/rdpsnd/alsa/rdpsnd_alsa.c b/channels/rdpsnd/alsa/rdpsnd_alsa.c index e88e85a93..c9cb27cd0 100644 --- a/channels/rdpsnd/alsa/rdpsnd_alsa.c +++ b/channels/rdpsnd/alsa/rdpsnd_alsa.c @@ -298,6 +298,16 @@ static void rdpsnd_alsa_play(rdpsndDevicePlugin* device, uint8* data, int size) free(decoded_data); } +static void rdpsnd_alsa_start(rdpsndDevicePlugin* device) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + + if (alsa->out_handle == 0) + return; + + snd_pcm_start(alsa->out_handle); +} + int FreeRDPRdpsndDeviceEntry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) { rdpsndAlsaPlugin* alsa; @@ -310,6 +320,7 @@ int FreeRDPRdpsndDeviceEntry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) alsa->device.SetFormat = rdpsnd_alsa_set_format; alsa->device.SetVolume = rdpsnd_alsa_set_volume; alsa->device.Play = rdpsnd_alsa_play; + alsa->device.Start = rdpsnd_alsa_start; alsa->device.Close = rdpsnd_alsa_close; alsa->device.Free = rdpsnd_alsa_free; diff --git a/channels/rdpsnd/rdpsnd_main.c b/channels/rdpsnd/rdpsnd_main.c index 1f17e8adf..4f2910aac 100644 --- a/channels/rdpsnd/rdpsnd_main.c +++ b/channels/rdpsnd/rdpsnd_main.c @@ -363,6 +363,8 @@ static void rdpsnd_process_message_wave(rdpsndPlugin* rdpsnd, STREAM* data_in) static void rdpsnd_process_message_close(rdpsndPlugin* rdpsnd) { DEBUG_SVC("server closes."); + if (rdpsnd->device) + IFCALL(rdpsnd->device->Start, rdpsnd->device); rdpsnd->close_timestamp = get_mstime() + 2000; rdpsnd->plugin.interval_ms = 10; } diff --git a/channels/rdpsnd/rdpsnd_main.h b/channels/rdpsnd/rdpsnd_main.h index ba391a09e..90e87e082 100644 --- a/channels/rdpsnd/rdpsnd_main.h +++ b/channels/rdpsnd/rdpsnd_main.h @@ -41,6 +41,7 @@ typedef void (*pcOpen) (rdpsndDevicePlugin* device, rdpsndFormat* format); typedef void (*pcSetFormat) (rdpsndDevicePlugin* device, rdpsndFormat* format); typedef void (*pcSetVolume) (rdpsndDevicePlugin* device, uint32 value); typedef void (*pcPlay) (rdpsndDevicePlugin* device, uint8* data, int size); +typedef void (*pcStart) (rdpsndDevicePlugin* device); typedef void (*pcClose) (rdpsndDevicePlugin* device); typedef void (*pcFree) (rdpsndDevicePlugin* device); @@ -51,6 +52,7 @@ struct rdpsnd_device_plugin pcSetFormat SetFormat; pcSetVolume SetVolume; pcPlay Play; + pcStart Start; pcClose Close; pcFree Free; }; From c02a568ed8134e830f1ba15eb4302d52474c6748 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 15 Aug 2011 18:21:11 +0800 Subject: [PATCH 7/8] rdpsnd: use xfree. --- channels/rdpsnd/alsa/rdpsnd_alsa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/channels/rdpsnd/alsa/rdpsnd_alsa.c b/channels/rdpsnd/alsa/rdpsnd_alsa.c index c9cb27cd0..abe9e5168 100644 --- a/channels/rdpsnd/alsa/rdpsnd_alsa.c +++ b/channels/rdpsnd/alsa/rdpsnd_alsa.c @@ -293,9 +293,9 @@ static void rdpsnd_alsa_play(rdpsndDevicePlugin* device, uint8* data, int size) } if (resampled_data) - free(resampled_data); + xfree(resampled_data); if (decoded_data) - free(decoded_data); + xfree(decoded_data); } static void rdpsnd_alsa_start(rdpsndDevicePlugin* device) From 15e553f8f17710c60f582dcc3d59e51bb8d18fe6 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 15 Aug 2011 18:21:58 +0800 Subject: [PATCH 8/8] rdpsnd: add PulseAudio sub-plugin. --- CMakeLists.txt | 2 + channels/rdpsnd/CMakeLists.txt | 4 + channels/rdpsnd/pulse/CMakeLists.txt | 33 ++ channels/rdpsnd/pulse/rdpsnd_pulse.c | 482 +++++++++++++++++++++++++++ cmake/FindPulseAudio.cmake | 1 + 5 files changed, 522 insertions(+) create mode 100644 channels/rdpsnd/pulse/CMakeLists.txt create mode 100644 channels/rdpsnd/pulse/rdpsnd_pulse.c create mode 100644 cmake/FindPulseAudio.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 4db64e14a..a4bf0ae1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ set(CMAKE_COLOR_MAKEFILE ON) # Include cmake modules include(CheckIncludeFiles) include(CheckLibraryExists) +include(FindPkgConfig) include(TestBigEndian) # Include our extra modules @@ -68,6 +69,7 @@ check_include_files(unistd.h HAVE_UNISTD_H) find_package(OpenSSL REQUIRED) find_package(ZLIB REQUIRED) find_package(ALSA) +find_package(PulseAudio) # Endian test_big_endian(BIG_ENDIAN) diff --git a/channels/rdpsnd/CMakeLists.txt b/channels/rdpsnd/CMakeLists.txt index db8f6832a..c1b0862ed 100644 --- a/channels/rdpsnd/CMakeLists.txt +++ b/channels/rdpsnd/CMakeLists.txt @@ -33,3 +33,7 @@ if(ALSA_FOUND) add_subdirectory(alsa) endif() +if(PULSE_FOUND) + add_subdirectory(pulse) +endif() + diff --git a/channels/rdpsnd/pulse/CMakeLists.txt b/channels/rdpsnd/pulse/CMakeLists.txt new file mode 100644 index 000000000..0c6c200cf --- /dev/null +++ b/channels/rdpsnd/pulse/CMakeLists.txt @@ -0,0 +1,33 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(RDPSND_PULSE_SRCS + rdpsnd_pulse.c +) + +include_directories(..) +include_directories(${PULSE_INCLUDE_DIRS}) + +add_library(rdpsnd_pulse SHARED ${RDPSND_PULSE_SRCS}) +set_target_properties(rdpsnd_pulse PROPERTIES PREFIX "") + +target_link_libraries(rdpsnd_pulse freerdp-utils) +target_link_libraries(rdpsnd_pulse ${PULSE_LIBRARIES}) + +install(TARGETS rdpsnd_pulse DESTINATION ${FREERDP_PLUGIN_PATH}) diff --git a/channels/rdpsnd/pulse/rdpsnd_pulse.c b/channels/rdpsnd/pulse/rdpsnd_pulse.c new file mode 100644 index 000000000..198be155a --- /dev/null +++ b/channels/rdpsnd/pulse/rdpsnd_pulse.c @@ -0,0 +1,482 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Audio Output Virtual Channel + * + * Copyright 2011 Vic Lee + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_pulse_plugin rdpsndPulsePlugin; +struct rdpsnd_pulse_plugin +{ + rdpsndDevicePlugin device; + + char* device_name; + pa_threaded_mainloop *mainloop; + pa_context *context; + pa_sample_spec sample_spec; + pa_stream *stream; + int format; + int block_size; + ADPCM adpcm; +}; + +static void rdpsnd_pulse_context_state_callback(pa_context* context, void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata; + pa_context_state_t state; + + state = pa_context_get_state(context); + switch (state) + { + case PA_CONTEXT_READY: + DEBUG_SVC("PA_CONTEXT_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + DEBUG_SVC("state %d", (int)state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_SVC("state %d", (int)state); + break; + } +} + +static boolean rdpsnd_pulse_connect(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + pa_context_state_t state; + + if (!pulse->context) + return False; + + if (pa_context_connect(pulse->context, NULL, 0, NULL)) + { + DEBUG_WARN("pa_context_connect failed (%d)", pa_context_errno(pulse->context)); + return False; + } + pa_threaded_mainloop_lock(pulse->mainloop); + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("pa_threaded_mainloop_start failed (%d)", pa_context_errno(pulse->context)); + return False; + } + for (;;) + { + state = pa_context_get_state(pulse->context); + if (state == PA_CONTEXT_READY) + break; + if (!PA_CONTEXT_IS_GOOD(state)) + { + DEBUG_WARN("bad context state (%d)", pa_context_errno(pulse->context)); + break; + } + pa_threaded_mainloop_wait(pulse->mainloop); + } + pa_threaded_mainloop_unlock(pulse->mainloop); + if (state == PA_CONTEXT_READY) + { + DEBUG_SVC("connected"); + return True; + } + else + { + pa_context_disconnect(pulse->context); + return False; + } +} + +static void rdpsnd_pulse_stream_success_callback(pa_stream* stream, int success, void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata; + + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void rdpsnd_pulse_wait_for_operation(rdpsndPulsePlugin* pulse, pa_operation* operation) +{ + while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait(pulse->mainloop); + } + pa_operation_unref(operation); +} + +static void rdpsnd_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata; + pa_stream_state_t state; + + state = pa_stream_get_state(stream); + switch (state) + { + case PA_STREAM_READY: + DEBUG_SVC("PA_STREAM_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + DEBUG_SVC("state %d", (int)state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_SVC("state %d", (int)state); + break; + } +} + +static void rdpsnd_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata; + + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void rdpsnd_pulse_close(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!pulse->context || !pulse->stream) + return; + + pa_threaded_mainloop_lock(pulse->mainloop); + rdpsnd_pulse_wait_for_operation(pulse, + pa_stream_drain(pulse->stream, rdpsnd_pulse_stream_success_callback, pulse)); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = NULL; + pa_threaded_mainloop_unlock(pulse->mainloop); +} + +static void rdpsnd_pulse_set_format_spec(rdpsndPulsePlugin* pulse, rdpsndFormat* format) +{ + pa_sample_spec sample_spec = { 0 }; + + if (!pulse->context) + return; + + sample_spec.rate = format->nSamplesPerSec; + sample_spec.channels = format->nChannels; + switch (format->wFormatTag) + { + case 1: /* PCM */ + switch (format->wBitsPerSample) + { + case 8: + sample_spec.format = PA_SAMPLE_U8; + break; + case 16: + sample_spec.format = PA_SAMPLE_S16LE; + break; + } + break; + + case 6: /* A-LAW */ + sample_spec.format = PA_SAMPLE_ALAW; + break; + + case 7: /* U-LAW */ + sample_spec.format = PA_SAMPLE_ULAW; + break; + + case 0x11: /* IMA ADPCM */ + sample_spec.format = PA_SAMPLE_S16LE; + break; + } + + pulse->sample_spec = sample_spec; + pulse->format = format->wFormatTag; + pulse->block_size = format->nBlockAlign; +} + +static void rdpsnd_pulse_open(rdpsndDevicePlugin* device, rdpsndFormat* format) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + pa_stream_state_t state; + pa_buffer_attr buffer_attr = { 0 }; + + if (!pulse->context || pulse->stream) + return; + + rdpsnd_pulse_set_format_spec(pulse, format); + + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, "freerdp", + &pulse->sample_spec, NULL); + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("pa_stream_new failed (%d)", + pa_context_errno(pulse->context)); + return; + } + pa_stream_set_state_callback(pulse->stream, + rdpsnd_pulse_stream_state_callback, pulse); + pa_stream_set_write_callback(pulse->stream, + rdpsnd_pulse_stream_request_callback, pulse); + buffer_attr.maxlength = (uint32_t) -1; + buffer_attr.tlength = (uint32_t) -1; /* pa_usec_to_bytes(2000000, &pulse->sample_spec); */ + buffer_attr.prebuf = (uint32_t) -1; + buffer_attr.minreq = (uint32_t) -1; + buffer_attr.fragsize = (uint32_t) -1; + if (pa_stream_connect_playback(pulse->stream, + pulse->device_name, &buffer_attr, 0, NULL, NULL) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("pa_stream_connect_playback failed (%d)", + pa_context_errno(pulse->context)); + return; + } + + for (;;) + { + state = pa_stream_get_state(pulse->stream); + if (state == PA_STREAM_READY) + break; + if (!PA_STREAM_IS_GOOD(state)) + { + DEBUG_WARN("bad stream state (%d)", + pa_context_errno(pulse->context)); + break; + } + pa_threaded_mainloop_wait(pulse->mainloop); + } + pa_threaded_mainloop_unlock(pulse->mainloop); + if (state == PA_STREAM_READY) + { + memset(&pulse->adpcm, 0, sizeof(ADPCM)); + DEBUG_SVC("connected"); + } + else + { + rdpsnd_pulse_close(device); + } +} + +static void rdpsnd_pulse_free(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!pulse) + return; + rdpsnd_pulse_close(device); + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = NULL; + } + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = NULL; + } + xfree(pulse->device_name); + xfree(pulse); +} + +static boolean rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, rdpsndFormat* format) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!pulse->context) + return False; + + switch (format->wFormatTag) + { + case 1: /* PCM */ + if (format->cbSize == 0 && + (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return True; + } + break; + + case 6: /* A-LAW */ + case 7: /* U-LAW */ + if (format->cbSize == 0 && + (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return True; + } + break; + + case 0x11: /* IMA ADPCM */ + if ((format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 4) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return True; + } + break; + } + return False; +} + +static void rdpsnd_pulse_set_format(rdpsndDevicePlugin* device, rdpsndFormat* format) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (pulse->stream) + { + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = NULL; + pa_threaded_mainloop_unlock(pulse->mainloop); + } + rdpsnd_pulse_open(device, format); +} + +static void rdpsnd_pulse_set_volume(rdpsndDevicePlugin* device, uint32 value) +{ +} + +static void rdpsnd_pulse_play(rdpsndDevicePlugin* device, uint8* data, int size) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + int len; + int ret; + uint8* decoded_data; + uint8* src; + int decoded_size; + + if (!pulse->stream) + return; + + if (pulse->format == 0x11) + { + decoded_data = dsp_decode_ima_adpcm(&pulse->adpcm, + data, size, pulse->sample_spec.channels, pulse->block_size, &decoded_size); + size = decoded_size; + src = decoded_data; + } + else + { + decoded_data = NULL; + src = data; + } + + pa_threaded_mainloop_lock(pulse->mainloop); + while (size > 0) + { + while ((len = pa_stream_writable_size(pulse->stream)) == 0) + { + pa_threaded_mainloop_wait(pulse->mainloop); + } + if (len < 0) + break; + if (len > size) + len = size; + ret = pa_stream_write(pulse->stream, src, len, NULL, 0LL, PA_SEEK_RELATIVE); + if (ret < 0) + { + DEBUG_WARN("pa_stream_write failed (%d)", + pa_context_errno(pulse->context)); + break; + } + src += len; + size -= len; + } + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (decoded_data) + xfree(decoded_data); +} + +static void rdpsnd_pulse_start(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!pulse->stream) + return; + + pa_stream_trigger(pulse->stream, NULL, NULL); +} + +int FreeRDPRdpsndDeviceEntry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + rdpsndPulsePlugin* pulse; + FRDP_PLUGIN_DATA* data; + + pulse = xnew(rdpsndPulsePlugin); + + pulse->device.Open = rdpsnd_pulse_open; + pulse->device.FormatSupported = rdpsnd_pulse_format_supported; + pulse->device.SetFormat = rdpsnd_pulse_set_format; + pulse->device.SetVolume = rdpsnd_pulse_set_volume; + pulse->device.Play = rdpsnd_pulse_play; + pulse->device.Start = rdpsnd_pulse_start; + pulse->device.Close = rdpsnd_pulse_close; + pulse->device.Free = rdpsnd_pulse_free; + + data = pEntryPoints->plugin_data; + if (data && strcmp((char*)data->data[0], "pulse") == 0) + { + pulse->device_name = xstrdup((char*)data->data[1]); + } + + pulse->mainloop = pa_threaded_mainloop_new(); + if (!pulse->mainloop) + { + DEBUG_WARN("pa_threaded_mainloop_new failed"); + rdpsnd_pulse_free((rdpsndDevicePlugin*)pulse); + return 1; + } + pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp"); + if (!pulse->context) + { + DEBUG_WARN("pa_context_new failed"); + rdpsnd_pulse_free((rdpsndDevicePlugin*)pulse); + return 1; + } + pa_context_set_state_callback(pulse->context, rdpsnd_pulse_context_state_callback, pulse); + if (!rdpsnd_pulse_connect((rdpsndDevicePlugin*)pulse)) + { + DEBUG_WARN("rdpsnd_pulse_connect failed"); + rdpsnd_pulse_free((rdpsndDevicePlugin*)pulse); + return 1; + } + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)pulse); + + return 0; +} + diff --git a/cmake/FindPulseAudio.cmake b/cmake/FindPulseAudio.cmake new file mode 100644 index 000000000..e30c6459b --- /dev/null +++ b/cmake/FindPulseAudio.cmake @@ -0,0 +1 @@ +pkg_check_modules(PULSE libpulse)