From 7fa4f936e43577c3529b10610ccf7ddcb2bd9fbe Mon Sep 17 00:00:00 2001 From: Jay Sorg Date: Fri, 25 May 2012 12:36:55 -0700 Subject: [PATCH] chansrv: simple pulse audio support --- configure.ac | 11 + sesman/chansrv/Makefile.am | 22 +- sesman/chansrv/sound.c | 529 +++++++++++++++++++++++++++++++++++-- sesman/chansrv/sound.h | 91 ++++++- 4 files changed, 625 insertions(+), 28 deletions(-) diff --git a/configure.ac b/configure.ac index dddd5a07..49070f58 100644 --- a/configure.ac +++ b/configure.ac @@ -31,6 +31,10 @@ AC_ARG_ENABLE(jpeg, AS_HELP_STRING([--enable-jpeg], [Build jpeg module (default: no)]), [jpeg=true], [jpeg=false]) AM_CONDITIONAL(XRDP_JPEG, [test x$jpeg = xtrue]) +AC_ARG_ENABLE(simplesound, AS_HELP_STRING([--enable-simplesound], + [Build simple pulse audio interface (default: no)]), + [simplesound=true], [simplesound=false]) +AM_CONDITIONAL(XRDP_SIMPLESOUND, [test x$simplesound = xtrue]) AM_CONDITIONAL(GOT_PREFIX, test "x${prefix}" != "xNONE"]) @@ -58,6 +62,13 @@ then [AC_MSG_ERROR([please install libjpeg-dev or libjpeg-devel])]) fi +# checking for libpulse libpulse-simple +if ! test -z "$enable_simplesound" +then + AC_CHECK_HEADER([pulse/simple.h], [], + [AC_MSG_ERROR([please install libpulse-dev or libpulse-devel])]) +fi + # checking for Xlib, Xfixes AC_CHECK_HEADER([X11/Xlib.h], [], [AC_MSG_ERROR([please install libx11-dev or libX11-devel])]) diff --git a/sesman/chansrv/Makefile.am b/sesman/chansrv/Makefile.am index 7cd1fa6c..fd8c260c 100644 --- a/sesman/chansrv/Makefile.am +++ b/sesman/chansrv/Makefile.am @@ -1,13 +1,25 @@ EXTRA_DIST = chansrv.h clipboard.h devredir.h sound.h +EXTRA_DEFINES = +EXTRA_INCLUDES = +EXTRA_LIBS = +EXTRA_FLAGS = + +if XRDP_SIMPLESOUND +EXTRA_DEFINES += -DXRDP_SIMPLESOUND +EXTRA_LIBS += -lpthread -lpulse -lpulse-simple +endif + AM_CFLAGS = \ -DXRDP_CFG_PATH=\"${sysconfdir}/xrdp\" \ -DXRDP_SBIN_PATH=\"${sbindir}\" \ -DXRDP_SHARE_PATH=\"${datadir}/xrdp\" \ - -DXRDP_PID_PATH=\"${localstatedir}/run\" + -DXRDP_PID_PATH=\"${localstatedir}/run\" \ + $(EXTRA_DEFINES) INCLUDES = \ - -I$(top_srcdir)/common + -I$(top_srcdir)/common \ + $(EXTRA_INCLUDES) sbin_PROGRAMS = \ xrdp-chansrv @@ -18,7 +30,11 @@ xrdp_chansrv_SOURCES = \ clipboard.c \ devredir.c +xrdp_chansrv_LDFLAGS = \ + $(EXTRA_FLAGS) + xrdp_chansrv_LDADD = \ -L/usr/X11R6/lib \ $(top_srcdir)/common/libcommon.la \ - -lX11 -lXfixes + -lX11 -lXfixes \ + $(EXTRA_LIBS) diff --git a/sesman/chansrv/sound.c b/sesman/chansrv/sound.c index 8cca641d..3d68faff 100644 --- a/sesman/chansrv/sound.c +++ b/sesman/chansrv/sound.c @@ -1,32 +1,343 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2009-2012 + * + * 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 "sound.h" + +extern int g_rdpsnd_chan_id; /* in chansrv.c */ +extern int g_display_num; /* in chansrv.c */ + +static struct trans *g_audio_l_trans = 0; // listener +static struct trans *g_audio_c_trans = 0; // connection +static int g_training_sent_time = 0; +static int g_cBlockNo = 0; + +/*****************************************************************************/ +static int APP_CC +sound_send_server_formats(void) +{ + struct stream* s; + int bytes; + char* size_ptr; + + print_got_here(); + + make_stream(s); + init_stream(s, 8182); + out_uint16_le(s, SNDC_FORMATS); + size_ptr = s->p; + out_uint16_le(s, 0); /* size, set later */ + out_uint32_le(s, 0); /* dwFlags */ + out_uint32_le(s, 0); /* dwVolume */ + out_uint32_le(s, 0); /* dwPitch */ + out_uint16_le(s, 0); /* wDGramPort */ + out_uint16_le(s, 1); /* wNumberOfFormats */ + out_uint8(s, g_cBlockNo); /* cLastBlockConfirmed */ + out_uint16_le(s, 2); /* wVersion */ + out_uint8(s, 0); /* bPad */ + + /* sndFormats */ + /* + wFormatTag 2 byte offset 0 + nChannels 2 byte offset 2 + nSamplesPerSec 4 byte offset 4 + nAvgBytesPerSec 4 byte offset 8 + nBlockAlign 2 byte offset 12 + wBitsPerSample 2 byte offset 14 + cbSize 2 byte offset 16 + data variable offset 18 + */ + + /* examples + 01 00 02 00 44 ac 00 00 10 b1 02 00 04 00 10 00 ....D........... + 00 00 + 01 00 02 00 22 56 00 00 88 58 01 00 04 00 10 00 ...."V...X...... + 00 00 + */ + + out_uint16_le(s, 1); // wFormatTag - WAVE_FORMAT_PCM + out_uint16_le(s, 2); // num of channels + out_uint32_le(s, 44100); // samples per sec + out_uint32_le(s, 176400); // avg bytes per sec + out_uint16_le(s, 4); // block align + out_uint16_le(s, 16); // bits per sample + out_uint16_le(s, 0); // size + + s_mark_end(s); + bytes = (int)((s->end - s->data) - 4); + size_ptr[0] = bytes; + size_ptr[1] = bytes >> 8; + bytes = (int)(s->end - s->data); + send_channel_data(g_rdpsnd_chan_id, s->data, bytes); + free_stream(s); + return 0; +} + +/*****************************************************************************/ +static int +sound_send_training(void) +{ + struct stream* s; + int bytes; + int time; + char* size_ptr; + + print_got_here(); + + make_stream(s); + init_stream(s, 8182); + out_uint16_le(s, SNDC_TRAINING); + size_ptr = s->p; + out_uint16_le(s, 0); /* size, set later */ + time = g_time2(); + g_training_sent_time = time; + out_uint16_le(s, time); + out_uint16_le(s, 1024); + out_uint8s(s, (1024 - 4)); + s_mark_end(s); + bytes = (int)((s->end - s->data) - 4); + size_ptr[0] = bytes; + size_ptr[1] = bytes >> 8; + bytes = (int)(s->end - s->data); + send_channel_data(g_rdpsnd_chan_id, s->data, bytes); + free_stream(s); + return 0; +} + +/*****************************************************************************/ /* - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - - xrdp: A Remote Desktop Protocol server. - Copyright (C) Jay Sorg 2009-2010 + 0000 07 02 26 00 03 00 80 00 ff ff ff ff 00 00 00 00 ..&............. + 0010 00 00 01 00 00 02 00 00 01 00 02 00 44 ac 00 00 ............D... + 0020 10 b1 02 00 04 00 10 00 00 00 */ -#include "arch.h" -#include "parse.h" -#include "os_calls.h" +static int APP_CC +sound_process_formats(struct stream* s, int size) +{ + int num_formats; + + print_got_here(); + + LOG(0, ("sound_process_formats:")); + if (size < 16) + { + return 1; + } + in_uint8s(s, 14); + in_uint16_le(s, num_formats); + if (num_formats > 0) + { + sound_send_training(); + } + return 0; +} + +/*****************************************************************************/ +static int +sound_send_wave_data(char* data, int data_bytes) +{ + struct stream* s; + int bytes; + int time; + char* size_ptr; + + print_got_here(); + + if ((data_bytes < 4) || (data_bytes > 32 * 1024)) + { + LOG(0, ("sound_send_wave_data: bad data_bytes %d", data_bytes)); + } + + /* part one of 2 PDU wave info */ + + LOG(3, ("sound_send_wave_data: sending %d bytes", data_bytes)); + + make_stream(s); + init_stream(s, data_bytes); + out_uint16_le(s, SNDC_WAVE); + size_ptr = s->p; + out_uint16_le(s, 0); /* size, set later */ + time = g_time2(); + out_uint16_le(s, time); + out_uint16_le(s, 0); /* wFormatNo */ + g_cBlockNo++; + out_uint8(s, g_cBlockNo); + + LOG(3, ("sound_send_wave_data: sending time %d, g_cBlockNo %d", + time & 0xffff, g_cBlockNo & 0xff)); + + out_uint8s(s, 3); + out_uint8a(s, data, 4); + s_mark_end(s); + bytes = (int)((s->end - s->data) - 4); + bytes += data_bytes; + bytes -= 4; + size_ptr[0] = bytes; + size_ptr[1] = bytes >> 8; + bytes = (int)(s->end - s->data); + send_channel_data(g_rdpsnd_chan_id, s->data, bytes); + + /* part two of 2 PDU wave info */ + init_stream(s, data_bytes); + out_uint32_le(s, 0); + out_uint8a(s, data + 4, data_bytes - 4); + s_mark_end(s); + bytes = (int)(s->end - s->data); + send_channel_data(g_rdpsnd_chan_id, s->data, bytes); + free_stream(s); + return 0; +} + +/*****************************************************************************/ +static int APP_CC +sound_process_training(struct stream* s, int size) +{ + int time_diff; + char buf[256]; + + print_got_here(); + + time_diff = g_time2() - g_training_sent_time; + LOG(0, ("sound_process_training: round trip time %u", time_diff)); + return 0; +} + +/*****************************************************************************/ +static int APP_CC +sound_process_wave_confirm(struct stream* s, int size) +{ + int wTimeStamp; + int cConfirmedBlockNo; + + print_got_here(); + + in_uint16_le(s, wTimeStamp); + in_uint8(s, cConfirmedBlockNo); + + LOG(3, ("sound_process_wave_confirm: wTimeStamp %d, cConfirmedBlockNo %d", + wTimeStamp, cConfirmedBlockNo)); -extern int g_rdpsnd_chan_id; /* in chansrv.c */ + return 0; +} + +/*****************************************************************************/ +static int APP_CC +process_pcm_message(int id, int size, struct stream* s) +{ + print_got_here(); + + sound_send_wave_data(s->p, size); + return 0; +} + +/*****************************************************************************/ +/* data coming in from audio source, eg pulse, alsa */ +static int DEFAULT_CC +sound_trans_audio_data_in(struct trans* trans) +{ + struct stream *s; + int id; + int size; + int error; + + if (trans == 0) + { + return 0; + } + if (trans != g_audio_c_trans) + { + return 1; + } + s = trans_get_in_s(trans); + in_uint32_le(s, id); + in_uint32_le(s, size); + if ((id != 0) || (size > 32 * 1024 + 8) || (size < 1)) + { + LOG(0, ("sound_trans_audio_data_in: bad message id %d size %d", id, size)); + return 1; + } + error = trans_force_read(trans, size - 8); + if (error == 0) + { + /* here, the entire message block is read in, process it */ + error = process_pcm_message(id, size - 8, s); + } + return error; +} + +/*****************************************************************************/ +static int DEFAULT_CC +sound_trans_audio_conn_in(struct trans *trans, struct trans *new_trans) +{ + print_got_here(); + + if (trans == 0) + { + return 1; + } + if (trans != g_audio_l_trans) + { + return 1; + } + if (g_audio_c_trans != 0) /* if already set, error */ + { + return 1; + } + if (new_trans == 0) + { + return 1; + } + g_audio_c_trans = new_trans; + g_audio_c_trans->trans_data_in = sound_trans_audio_data_in; + g_audio_c_trans->header_size = 8; + trans_delete(g_audio_l_trans); + g_audio_l_trans = 0; + return 0; +} /*****************************************************************************/ int APP_CC sound_init(void) { + char port[256]; + int error; + pthread_t thread; + + print_got_here(); + LOG(0, ("sound_init:")); + + sound_send_server_formats(); + g_audio_l_trans = trans_create(2, 33 * 1024, 8192); + g_snprintf(port, 255, "/tmp/xrdp_chansrv_audio_socket_%d", g_display_num); + g_audio_l_trans->trans_conn_in = sound_trans_audio_conn_in; + error = trans_listen(g_audio_l_trans, port); + if (error != 0) + { + LOG(0, ("sound_init: trans_listen failed")); + } + +#if defined(XRDP_SIMPLESOUND) + + // start thread to read raw audio data from pulseaudio device + pthread_create(&thread, 0, read_raw_audio_data, NULL); + pthread_detach(thread); + +#endif + return 0; } @@ -34,14 +345,54 @@ sound_init(void) int APP_CC sound_deinit(void) { + print_got_here(); + + if (g_audio_l_trans != 0) + { + trans_delete(g_audio_l_trans); + g_audio_l_trans = 0; + } + if (g_audio_c_trans != 0) + { + trans_delete(g_audio_c_trans); + g_audio_l_trans = 0; + } + return 0; } /*****************************************************************************/ +/* data in from client ( clinet -> xrdp -> chansrv ) */ int APP_CC sound_data_in(struct stream* s, int chan_id, int chan_flags, int length, int total_length) { + int code; + int size; + + print_got_here(); + + in_uint8(s, code); + in_uint8s(s, 1); + in_uint16_le(s, size); + switch (code) + { + case SNDC_WAVECONFIRM: + sound_process_wave_confirm(s, size); + break; + + case SNDC_TRAINING: + sound_process_training(s, size); + break; + + case SNDC_FORMATS: + sound_process_formats(s, size); + break; + + default: + LOG(0, ("sound_data_in: unknown code %d size %d", code, size)); + break; + } return 0; } @@ -49,6 +400,20 @@ sound_data_in(struct stream* s, int chan_id, int chan_flags, int length, int APP_CC sound_get_wait_objs(tbus* objs, int* count, int* timeout) { + int lcount; + + lcount = *count; + if (g_audio_l_trans != 0) + { + objs[lcount] = g_audio_l_trans->sck; + lcount++; + } + if (g_audio_c_trans != 0) + { + objs[lcount] = g_audio_c_trans->sck; + lcount++; + } + *count = lcount; return 0; } @@ -56,5 +421,129 @@ sound_get_wait_objs(tbus* objs, int* count, int* timeout) int APP_CC sound_check_wait_objs(void) { + if (g_audio_l_trans != 0) + { + trans_check_wait_objs(g_audio_l_trans); + } + + if (g_audio_c_trans != 0) + { + trans_check_wait_objs(g_audio_c_trans); + } + return 0; } + +#if defined(XRDP_SIMPLESOUND) + +/** + * read raw audio data from pulseaudio device and write it + * to a unix domain socket on which trans server is listening + */ + +static void * +read_raw_audio_data(void* arg) +{ + struct sockaddr_un serv_addr; + pa_sample_spec samp_spec; + pa_simple* simple = NULL; + + uint32_t bytes_read; + uint8_t audio_buf[AUDIO_BUF_SIZE + 8]; + char* cptr; + int i; + int error; + int skt_fd; + + // create client socket + if ((skt_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + { + LOG(0, ("read_raw_audio_data: error creating unix domain socket\n")); + return NULL; + } + + // setup server address and bind to it + memset(&serv_addr, 0, sizeof(struct sockaddr_un)); + serv_addr.sun_family = AF_UNIX; + g_snprintf(serv_addr.sun_path, 255, "/tmp/xrdp_chansrv_audio_socket_%d", g_display_num); + + if (connect(skt_fd, (struct sockaddr *) &serv_addr, sizeof(struct sockaddr_un)) < 0) + { + LOG(0, ("read_raw_audio_data: error connecting to server\n")); + close(skt_fd); + return NULL; + } + + // setup audio format + samp_spec.format = PA_SAMPLE_S16LE; + samp_spec.rate = 44100; + samp_spec.channels = 2; + + // if we are root, then for first 8 seconds connection to pulseaudo server + // fails; if we are non-root, then connection succeeds on first attempt; + // for now we have changed code to be non-root, but this may change in the + // future - so pretend we are root and try connecting to pulseaudio server + // for upto one minute + for (i = 0; i < 60; i++) + { + simple = pa_simple_new(NULL, "xrdp", PA_STREAM_RECORD, NULL, + "record", &samp_spec, NULL, NULL, &error); + if (simple) + { + // connected to pulseaudio server + LOG(0, ("read_raw_audio_data: connected to pulseaudio server\n")); + break; + } + LOG(0, ("read_raw_audio_data: ERROR creating PulseAudio async interface\n")); + LOG(0, ("read_raw_audio_data: %s\n", pa_strerror(error))); + sleep(1); + } + + if (i == 60) + { + // failed to connect to audio server + close(skt_fd); + return NULL; + } + + // insert header just once + cptr = audio_buf; + ins_uint32_le(cptr, 0); + ins_uint32_le(cptr, AUDIO_BUF_SIZE + 8); + + while (1) + { + // read a block of raw audio data... + if ((bytes_read = pa_simple_read(simple, cptr, AUDIO_BUF_SIZE, &error)) < 0) + { + LOG(0, ("read_raw_audio_data: ERROR reading from pulseaudio stream\n")); + LOG(0, ("read_raw_audio_data: %s\n", pa_strerror(error))); + break; + } + + // bug workaround: + // even when there is no audio data, pulseaudio is returning without + // errors but the data itself is zero; we use this zero data to + // determine that there is no audio data present + if (*cptr == 0 && *(cptr + 1) == 0 && *(cptr + 2) == 0 && *(cptr + 3) == 0) + { + usleep(10000); + continue; + } + + // ... and write it to a unix domain socket + if (write(skt_fd, audio_buf, AUDIO_BUF_SIZE + 8) < 0) + { + LOG(0, ("read_raw_audio_data: ERROR writing audio data to server\n")); + break; + } + } + +done: + + pa_simple_free(simple); + close(skt_fd); + return NULL; +} + +#endif diff --git a/sesman/chansrv/sound.h b/sesman/chansrv/sound.h index 45448a5a..a2d1704a 100644 --- a/sesman/chansrv/sound.h +++ b/sesman/chansrv/sound.h @@ -1,20 +1,101 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2009-2012 + * + * 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. + */ -#if !defined(SOUND_H) -#define SOUND_H +#ifndef _SOUND_H_ +#define _SOUND_H_ + +#include +#include + +#if defined(XRDP_SIMPLESOUND) +#include +#include +#endif #include "arch.h" #include "parse.h" +#include "os_calls.h" +#include "chansrv.h" +#include "trans.h" + +#define _DEBUG_RDPSND + +#ifdef DEBUG_RDPSND +#include +#define print_got_here() printf("****** got here: %s : %s : %d\n", \ + __FILE__, __func__, __LINE__); +#else +#define print_got_here() +#endif + +/** + * insert a uint32_t value into specified byte array + * + * @param p pointer to byte array + * @param v value to insert into byte array + */ + +#if defined(B_ENDIAN) || defined(NEED_ALIGN) +#define ins_uint32_le(p, v) \ +{ \ + *p++ = (unsigned char) v; \ + *p++ = (unsigned char) (v >> 8); \ + *p++ = (unsigned char) (v >> 16); \ + *p++ = (unsigned char) (v >> 24); \ +} +#else +#define ins_uint32_le(p, v) \ +{ \ + *((uint32_t *) p) = v; \ + p += 4; \ +} +#endif + +#define AUDIO_BUF_SIZE 2048 + +#define SNDC_CLOSE 0x01 +#define SNDC_WAVE 0x02 +#define SNDC_SETVOLUME 0x03 +#define SNDC_SETPITCH 0x04 +#define SNDC_WAVECONFIRM 0x05 +#define SNDC_TRAINING 0x06 +#define SNDC_FORMATS 0x07 +#define SNDC_CRYPTKEY 0x08 +#define SNDC_WAVEENCRYPT 0x09 +#define SNDC_UDPWAVE 0x0A +#define SNDC_UDPWAVELAST 0x0B +#define SNDC_QUALITYMODE 0x0C int APP_CC sound_init(void); int APP_CC sound_deinit(void); int APP_CC -sound_data_in(struct stream* s, int chan_id, int chan_flags, int length, - int total_length); -int APP_CC sound_get_wait_objs(tbus* objs, int* count, int* timeout); int APP_CC sound_check_wait_objs(void); +int APP_CC +sound_data_in(struct stream* s, int chan_id, int chan_flags, + int length, int total_length); + +#if defined(XRDP_SIMPLESOUND) +static void* +read_raw_audio_data(void* arg); +#endif #endif