chansrv: simple pulse audio support

This commit is contained in:
Jay Sorg 2012-05-25 12:36:55 -07:00
parent 17a65f90f7
commit 7fa4f936e4
4 changed files with 625 additions and 28 deletions

View File

@ -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])])

View File

@ -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)

View File

@ -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;
extern int g_rdpsnd_chan_id; /* in chansrv.c */
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));
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

View File

@ -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 <sys/socket.h>
#include <sys/un.h>
#if defined(XRDP_SIMPLESOUND)
#include <pulse/simple.h>
#include <pulse/error.h>
#endif
#include "arch.h"
#include "parse.h"
#include "os_calls.h"
#include "chansrv.h"
#include "trans.h"
#define _DEBUG_RDPSND
#ifdef DEBUG_RDPSND
#include <stdio.h>
#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