Merge pull request #1186 from speidy/pulse-remove

chansrv: remove pulseaudio modules from xrdp source tree
master
metalefty 6 years ago committed by GitHub
commit 1e08bd041c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,7 +1,6 @@
EXTRA_DIST = \ EXTRA_DIST = \
clipboard-notes.txt \ clipboard-notes.txt \
pcsc \ pcsc \
pulse \
wave-format-server.txt wave-format-server.txt
AM_CPPFLAGS = \ AM_CPPFLAGS = \

@ -1 +0,0 @@
!Makefile

@ -1,18 +0,0 @@
#
# build xrdp pulseaudio modules
#
# change this to your pulseaudio source directory
PULSE_DIR = /tmp/pulseaudio-10.0
CFLAGS = -Wall -O2 -I $(PULSE_DIR) -I $(PULSE_DIR)/src -DHAVE_CONFIG_H -fPIC
all: module-xrdp-sink.so module-xrdp-source.so
module-xrdp-sink.so: module-xrdp-sink.o
$(CC) $(LDFLAGS) -shared -o module-xrdp-sink.so module-xrdp-sink.o
module-xrdp-source.so: module-xrdp-source.o
$(CC) $(LDFLAGS) -shared -o module-xrdp-source.so module-xrdp-source.o
clean:
rm -f module-xrdp-sink.o module-xrdp-sink.so module-xrdp-source.o module-xrdp-source.so

@ -1,117 +0,0 @@
The latest version of this document can be found at wiki.
* https://github.com/neutrinolabs/xrdp/wiki/How-to-set-up-audio-redirection
# Overview
xrdp supports audio redirection using PulseAudio, which is a sound system for
POSIX operating systems. Server to client redirection is compliant to Remote
Desktop Procol standard [[MS-RDPEA]](https://msdn.microsoft.com/en-us/library/cc240933.aspx)
but client to server redirection implementation is proprietary. Accordingly,
server to client redirection is available with many of RDP clients including
Microsoft client but client to server redirection requires NeutrinoRDP client,
not available with other clients.
Here is how to build pulseaudio modules for your distro, so you can have audio
support through xrdp.
# Prerequisites
Prepare xrdp source in your home directory. Of course, you can choose another
directory.
cd ~
git clone https://github.com/neutrinolabs/xrdp.git
In this instruction, pulseaudio version is **10.0**. Replace the version number
in this instruction if your environment has different versions. You can find
out your pulseaudio version executing the following command:
pulseaudio --version
# How to build
## Debian 9 / Ubuntu
This instruction also should be applicable to Ubuntu family.
### Prerequisites
Some build tools and package development tools are required. Make sure install
the tools.
apt install build-essential dpkg-dev
### Prepare & build
Install pulseaudio and requisite packages to build pulseaudio.
apt install pulseaudio
apt build-dep pulseaudio
Fetch the pulseaudio source . You'll see `pulseaudio-10.0` directory in your
current directory.
apt source pulseaudio
Enter into the directory and build the pulseaudio package.
cd pulseaudio-10.0
./configure
Finally, let's make. You'll have two .so files `module-xrdp-sink.so` and
`module-xrdp-source.so`.
cd ~/xrdp/sesman/chansrv/pulse
make PULSE_DIR="~/pulseaudio-10.0"
## Other distro
First off, find out your pulseaudio version using `pulseaudio --version`
command. Download the tarball of the pulseaudio version that you have.
* https://freedesktop.org/software/pulseaudio/releases/
After downloading the tarball, extact the tarball and `cd` into the source
directory, then run `./configure`.
wget https://freedesktop.org/software/pulseaudio/releases/pulseaudio-10.0.tar.xz
tar xf pulseaudio-10.0.tar.gz
cd pulseaudio-10.0
./configure
If additional packages are required to run `./configure`, install requisite
packages depending on your environment.
Finally, let's make. You'll have two .so files `module-xrdp-sink.so` and
`module-xrdp-source.so`.
cd ~/xrdp/sesman/chansrv/pulse
make PULSE_DIR="~/pulseaudio-10.0"
# Install
Install process is not distro specific except for install destination. Install
built two .so files into the pulseaudio modules directory. Typically,
`/usr/lib/pulse-10.0/modules` for Debian, `/usr/lib64/pulse-10.0/modules` for
CentOS 7. Other distro might have different path. Find out the right path for
your distro.
Look into the directory with `ls` command. You'll see lots of `module-*.so`
files. There's the place!
cd ~/xrdp/sesman/chansrv/pulse
for f in *.so; do install -s -m 644 $f /usr/lib/pulse-10.0/modules; done
This command is equivalent to following:
install -s -m 644 module-xrdp-sink.so /usr/lib/pulse-10.0/modules
install -s -m 644 module-xrdp-source.so /usr/lib/pulse-10.0/modules
Well done! Pulseaudio modules should be properly built and installed.
# See if it works
To see if it works, run `pavumeter` in the xrdp session. Playback any YouTube
video in Firefox. You'll see "Showing signal levels of **xrdp sink**" and
volume meter moving.

@ -1,29 +0,0 @@
#ifndef MODULE_XRDP_SINK_SYMDEF_H
#define MODULE_XRDP_SINK_SYMDEF_H
#include <pulsecore/core.h>
#include <pulsecore/module.h>
#include <pulsecore/macro.h>
#define pa__init module_xrdp_sink_LTX_pa__init
#define pa__done module_xrdp_sink_LTX_pa__done
#define pa__get_author module_xrdp_sink_LTX_pa__get_author
#define pa__get_description module_xrdp_sink_LTX_pa__get_description
#define pa__get_usage module_xrdp_sink_LTX_pa__get_usage
#define pa__get_version module_xrdp_sink_LTX_pa__get_version
#define pa__get_deprecated module_xrdp_sink_LTX_pa__get_deprecated
#define pa__load_once module_xrdp_sink_LTX_pa__load_once
#define pa__get_n_used module_xrdp_sink_LTX_pa__get_n_used
int pa__init(pa_module*m);
void pa__done(pa_module*m);
int pa__get_n_used(pa_module*m);
const char* pa__get_author(void);
const char* pa__get_description(void);
const char* pa__get_usage(void);
const char* pa__get_version(void);
const char* pa__get_deprecated(void);
pa_bool_t pa__load_once(void);
#endif

@ -1,613 +0,0 @@
/**
* xrdp: A Remote Desktop Protocol server.
* pulse sink
*
* Copyright (C) Jay Sorg 2013
*
* 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.
*/
/*
* see pulse-notes.txt
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/xmalloc.h>
#include <pulsecore/core-error.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h>
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
#include <pulsecore/thread.h>
#include <pulsecore/thread-mq.h>
#include <pulsecore/rtpoll.h>
/* defined in pulse/version.h */
#if PA_PROTOCOL_VERSION > 28
/* these used to be defined in pulsecore/macro.h */
typedef bool pa_bool_t;
#define FALSE ((pa_bool_t) 0)
#define TRUE (!FALSE)
#else
#endif
#include "module-xrdp-sink-symdef.h"
#include "../../../common/xrdp_sockets.h"
PA_MODULE_AUTHOR("Jay Sorg");
PA_MODULE_DESCRIPTION("xrdp sink");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(FALSE);
PA_MODULE_USAGE(
"sink_name=<name for the sink> "
"sink_properties=<properties for the sink> "
"format=<sample format> "
"rate=<sample rate> "
"channels=<number of channels> "
"channel_map=<channel map>");
#define DEFAULT_SINK_NAME "xrdp-sink"
#define BLOCK_USEC 30000
//#define BLOCK_USEC (PA_USEC_PER_SEC * 2)
struct userdata {
pa_core *core;
pa_module *module;
pa_sink *sink;
pa_thread *thread;
pa_thread_mq thread_mq;
pa_rtpoll *rtpoll;
pa_usec_t block_usec;
pa_usec_t timestamp;
pa_usec_t failed_connect_time;
pa_usec_t last_send_time;
int fd; /* unix domain socket connection to xrdp chansrv */
int display_num;
int skip_bytes;
int got_max_latency;
};
static const char* const valid_modargs[] = {
"sink_name",
"sink_properties",
"format",
"rate",
"channels",
"channel_map",
NULL
};
static int close_send(struct userdata *u);
static int sink_process_msg(pa_msgobject *o, int code, void *data,
int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
pa_usec_t now;
long lat;
pa_log_debug("sink_process_msg: code %d", code);
switch (code) {
case PA_SINK_MESSAGE_SET_VOLUME: /* 3 */
break;
case PA_SINK_MESSAGE_SET_MUTE: /* 6 */
break;
case PA_SINK_MESSAGE_GET_LATENCY: /* 7 */
now = pa_rtclock_now();
lat = u->timestamp > now ? u->timestamp - now : 0ULL;
pa_log_debug("sink_process_msg: lat %ld", lat);
*((pa_usec_t*) data) = lat;
return 0;
case PA_SINK_MESSAGE_GET_REQUESTED_LATENCY: /* 8 */
break;
case PA_SINK_MESSAGE_SET_STATE: /* 9 */
if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING) /* 0 */ {
pa_log("sink_process_msg: running");
u->timestamp = pa_rtclock_now();
} else {
pa_log("sink_process_msg: not running");
close_send(u);
}
break;
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
size_t nbytes;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
u->block_usec = BLOCK_USEC;
//u->block_usec = pa_sink_get_requested_latency_within_thread(s);
pa_log("1 block_usec %llu", (unsigned long long) u->block_usec);
u->got_max_latency = 0;
if (u->block_usec == (pa_usec_t) -1) {
u->block_usec = s->thread_info.max_latency;
pa_log_debug("2 block_usec %llu", (unsigned long long) u->block_usec);
u->got_max_latency = 1;
}
nbytes = pa_usec_to_bytes(u->block_usec, &s->sample_spec);
pa_sink_set_max_rewind_within_thread(s, nbytes);
pa_sink_set_max_request_within_thread(s, nbytes);
}
static void process_rewind(struct userdata *u, pa_usec_t now) {
size_t rewind_nbytes, in_buffer;
pa_usec_t delay;
pa_assert(u);
/* Figure out how much we shall rewind and reset the counter */
rewind_nbytes = u->sink->thread_info.rewind_nbytes;
u->sink->thread_info.rewind_nbytes = 0;
pa_assert(rewind_nbytes > 0);
pa_log_debug("Requested to rewind %lu bytes.",
(unsigned long) rewind_nbytes);
if (u->timestamp <= now)
goto do_nothing;
delay = u->timestamp - now;
in_buffer = pa_usec_to_bytes(delay, &u->sink->sample_spec);
if (in_buffer <= 0)
goto do_nothing;
if (rewind_nbytes > in_buffer)
rewind_nbytes = in_buffer;
pa_sink_process_rewind(u->sink, rewind_nbytes);
u->timestamp -= pa_bytes_to_usec(rewind_nbytes, &u->sink->sample_spec);
u->skip_bytes += rewind_nbytes;
pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes);
return;
do_nothing:
pa_sink_process_rewind(u->sink, 0);
}
struct header {
int code;
int bytes;
};
static int get_display_num_from_display(char *display_text) {
int index;
int mode;
int host_index;
int disp_index;
int scre_index;
int display_num;
char host[256];
char disp[256];
char scre[256];
if (display_text == NULL) {
return 0;
}
memset(host, 0, 256);
memset(disp, 0, 256);
memset(scre, 0, 256);
index = 0;
host_index = 0;
disp_index = 0;
scre_index = 0;
mode = 0;
while (display_text[index] != 0) {
if (display_text[index] == ':') {
mode = 1;
} else if (display_text[index] == '.') {
mode = 2;
} else if (mode == 0) {
host[host_index] = display_text[index];
host_index++;
} else if (mode == 1) {
disp[disp_index] = display_text[index];
disp_index++;
} else if (mode == 2) {
scre[scre_index] = display_text[index];
scre_index++;
}
index++;
}
host[host_index] = 0;
disp[disp_index] = 0;
scre[scre_index] = 0;
display_num = atoi(disp);
return display_num;
}
static int lsend(int fd, char *data, int bytes) {
int sent = 0;
int error;
while (sent < bytes) {
error = send(fd, data + sent, bytes - sent, 0);
if (error < 1) {
return error;
}
sent += error;
}
return sent;
}
static int data_send(struct userdata *u, pa_memchunk *chunk) {
char *data;
char *socket_dir;
int bytes;
int sent;
int fd;
struct header h;
struct sockaddr_un s;
if (u->fd == 0) {
if (u->failed_connect_time != 0) {
if (pa_rtclock_now() - u->failed_connect_time < 1000000) {
return 0;
}
}
fd = socket(PF_LOCAL, SOCK_STREAM, 0);
memset(&s, 0, sizeof(s));
s.sun_family = AF_UNIX;
bytes = sizeof(s.sun_path) - 1;
socket_dir = getenv("XRDP_SOCKET_PATH");
if (socket_dir == NULL || socket_dir[0] == '\0')
{
socket_dir = "/tmp/.xrdp";
}
snprintf(s.sun_path, bytes, "%s/" CHANSRV_PORT_OUT_BASE_STR,
socket_dir, u->display_num);
pa_log_debug("trying to connect to %s", s.sun_path);
if (connect(fd, (struct sockaddr *)&s,
sizeof(struct sockaddr_un)) != 0) {
u->failed_connect_time = pa_rtclock_now();
pa_log_debug("Connected failed");
close(fd);
return 0;
}
u->failed_connect_time = 0;
pa_log("Connected ok fd %d", fd);
u->fd = fd;
}
bytes = chunk->length;
pa_log_debug("bytes %d", bytes);
/* from rewind */
if (u->skip_bytes > 0) {
if (bytes > u->skip_bytes) {
bytes -= u->skip_bytes;
u->skip_bytes = 0;
} else {
u->skip_bytes -= bytes;
return bytes;
}
}
h.code = 0;
h.bytes = bytes + 8;
if (lsend(u->fd, (char*)(&h), 8) != 8) {
pa_log("data_send: send failed");
close(u->fd);
u->fd = 0;
return 0;
} else {
pa_log_debug("data_send: sent header ok bytes %d", bytes);
}
data = (char*)pa_memblock_acquire(chunk->memblock);
data += chunk->index;
sent = lsend(u->fd, data, bytes);
pa_memblock_release(chunk->memblock);
if (sent != bytes) {
pa_log("data_send: send failed sent %d bytes %d", sent, bytes);
close(u->fd);
u->fd = 0;
return 0;
}
return sent;
}
static int close_send(struct userdata *u) {
struct header h;
pa_log("close_send:");
if (u->fd == 0) {
return 0;
}
h.code = 1;
h.bytes = 8;
if (lsend(u->fd, (char*)(&h), 8) != 8) {
pa_log("close_send: send failed");
close(u->fd);
u->fd = 0;
return 0;
} else {
pa_log_debug("close_send: sent header ok");
}
return 8;
}
static void process_render(struct userdata *u, pa_usec_t now) {
pa_memchunk chunk;
int request_bytes;
pa_assert(u);
if (u->got_max_latency) {
return;
}
pa_log_debug("process_render: u->block_usec %llu", (unsigned long long) u->block_usec);
while (u->timestamp < now + u->block_usec) {
request_bytes = u->sink->thread_info.max_request;
request_bytes = MIN(request_bytes, 16 * 1024);
pa_sink_render(u->sink, request_bytes, &chunk);
data_send(u, &chunk);
pa_memblock_unref(chunk.memblock);
u->timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec);
}
}
static void thread_func(void *userdata) {
struct userdata *u = userdata;
int ret;
pa_usec_t now;
pa_assert(u);
pa_log_debug("Thread starting up");
pa_thread_mq_install(&u->thread_mq);
u->timestamp = pa_rtclock_now();
for (;;) {
if (u->sink->thread_info.state == PA_SINK_RUNNING) {
now = pa_rtclock_now();
if (u->sink->thread_info.rewind_requested) {
if (u->sink->thread_info.rewind_nbytes > 0) {
process_rewind(u, now);
} else {
pa_sink_process_rewind(u->sink, 0);
}
}
if (u->timestamp <= now) {
pa_log_debug("thread_func: calling process_render");
process_render(u, now);
}
pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp);
} else {
pa_rtpoll_set_timer_disabled(u->rtpoll);
}
#if defined(PA_CHECK_VERSION) && PA_CHECK_VERSION(6, 0, 0)
if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) {
#else
if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) {
#endif
goto fail;
}
if (ret == 0) {
goto finish;
}
}
fail:
/* If this was no regular exit from the loop we have to continue
* processing messages until we received PA_MESSAGE_SHUTDOWN */
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core),
PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0,
NULL, NULL);
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
finish:
pa_log_debug("Thread shutting down");
}
int pa__init(pa_module*m) {
struct userdata *u = NULL;
pa_sample_spec ss;
pa_channel_map map;
pa_modargs *ma = NULL;
pa_sink_new_data data;
size_t nbytes;
pa_assert(m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments.");
goto fail;
}
ss = m->core->default_sample_spec;
map = m->core->default_channel_map;
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map,
PA_CHANNEL_MAP_DEFAULT) < 0) {
pa_log("Invalid sample format specification or channel map");
goto fail;
}
m->userdata = u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->module = m;
u->rtpoll = pa_rtpoll_new();
pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
pa_sink_new_data_init(&data);
data.driver = __FILE__;
data.module = m;
pa_sink_new_data_set_name(&data,
pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME));
pa_sink_new_data_set_sample_spec(&data, &ss);
pa_sink_new_data_set_channel_map(&data, &map);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "xrdp sink");
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract");
if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist,
PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
pa_sink_new_data_done(&data);
goto fail;
}
u->sink = pa_sink_new(m->core, &data,
PA_SINK_LATENCY | PA_SINK_DYNAMIC_LATENCY);
pa_sink_new_data_done(&data);
if (!u->sink) {
pa_log("Failed to create sink object.");
goto fail;
}
u->sink->parent.process_msg = sink_process_msg;
u->sink->update_requested_latency = sink_update_requested_latency_cb;
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
pa_sink_set_rtpoll(u->sink, u->rtpoll);
u->block_usec = BLOCK_USEC;
pa_log_debug("3 block_usec %llu", (unsigned long long) u->block_usec);
nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec);
pa_sink_set_max_rewind(u->sink, nbytes);
pa_sink_set_max_request(u->sink, nbytes);
u->display_num = get_display_num_from_display(getenv("DISPLAY"));
#if defined(PA_CHECK_VERSION)
#if PA_CHECK_VERSION(0, 9, 22)
if (!(u->thread = pa_thread_new("xrdp-sink", thread_func, u))) {
#else
if (!(u->thread = pa_thread_new(thread_func, u))) {
#endif
#else
if (!(u->thread = pa_thread_new(thread_func, u))) {
#endif
pa_log("Failed to create thread.");
goto fail;
}
pa_sink_put(u->sink);
pa_modargs_free(ma);
return 0;
fail:
if (ma) {
pa_modargs_free(ma);
}
pa__done(m);
return -1;
}
int pa__get_n_used(pa_module *m) {
struct userdata *u;
pa_assert(m);
pa_assert_se(u = m->userdata);
return pa_sink_linked_by(u->sink);
}
void pa__done(pa_module*m) {
struct userdata *u;
pa_assert(m);
if (!(u = m->userdata)) {
return;
}
if (u->sink) {
pa_sink_unlink(u->sink);
}
if (u->thread) {
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN,
NULL, 0, NULL);
pa_thread_free(u->thread);
}
pa_thread_mq_done(&u->thread_mq);
if (u->sink) {
pa_sink_unref(u->sink);
}
if (u->rtpoll) {
pa_rtpoll_free(u->rtpoll);
}
pa_xfree(u);
}

@ -1,29 +0,0 @@
#ifndef MODULE_XRDP_SOURCE_SYMDEF_H
#define MODULE_XRDP_SOURCE_SYMDEF_H
#include <pulsecore/core.h>
#include <pulsecore/module.h>
#include <pulsecore/macro.h>
#define pa__init module_xrdp_source_LTX_pa__init
#define pa__done module_xrdp_source_LTX_pa__done
#define pa__get_author module_xrdp_source_LTX_pa__get_author
#define pa__get_description module_xrdp_source_LTX_pa__get_description
#define pa__get_usage module_xrdp_source_LTX_pa__get_usage
#define pa__get_version module_xrdp_source_LTX_pa__get_version
#define pa__get_deprecated module_xrdp_source_LTX_pa__get_deprecated
#define pa__load_once module_xrdp_source_LTX_pa__load_once
#define pa__get_n_used module_xrdp_source_LTX_pa__get_n_used
int pa__init(pa_module*m);
void pa__done(pa_module*m);
int pa__get_n_used(pa_module*m);
const char* pa__get_author(void);
const char* pa__get_description(void);
const char* pa__get_usage(void);
const char* pa__get_version(void);
const char* pa__get_deprecated(void);
pa_bool_t pa__load_once(void);
#endif

@ -1,547 +0,0 @@
/***
This file is part of PulseAudio.
Copyright 2004-2008 Lennart Poettering
Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
PulseAudio 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 Lesser General Public License
along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/xmalloc.h>
#include <pulsecore/core-util.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/modargs.h>
#include <pulsecore/module.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/source.h>
#include <pulsecore/thread-mq.h>
#include <pulsecore/thread.h>
/* defined in pulse/version.h */
#if PA_PROTOCOL_VERSION > 28
/* these used to be defined in pulsecore/macro.h */
typedef bool pa_bool_t;
#define FALSE ((pa_bool_t) 0)
#define TRUE (!FALSE)
#else
#endif
#include "module-xrdp-source-symdef.h"
#include "../../../common/xrdp_sockets.h"
PA_MODULE_AUTHOR("Laxmikant Rashinkar");
PA_MODULE_DESCRIPTION("xrdp source");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(FALSE);
PA_MODULE_USAGE(
"format=<sample format> "
"channels=<number of channels> "
"rate=<sample rate> "
"source_name=<name of source> "
"channel_map=<channel map> "
"description=<description for the source> "
"latency_time=<latency time in ms>");
#define DEFAULT_SOURCE_NAME "xrdp-source"
#define DEFAULT_LATENCY_TIME 10
#define MAX_LATENCY_USEC 1000
struct userdata {
pa_core *core;
pa_module *module;
pa_source *source;
pa_thread *thread;
pa_thread_mq thread_mq;
pa_rtpoll *rtpoll;
size_t block_size;
pa_usec_t block_usec;
pa_usec_t timestamp;
pa_usec_t latency_time;
/* xrdp stuff */
int fd; /* UDS connection to xrdp chansrv */
int display_num; /* X display number */
int want_src_data;
};
static const char* const valid_modargs[] = {
"rate",
"format",
"channels",
"source_name",
"channel_map",
"description",
"latency_time",
NULL
};
static int get_display_num_from_display(char *display_text) ;
static int source_process_msg(pa_msgobject *o, int code, void *data,
int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SOURCE(o)->userdata;
switch (code) {
case PA_SOURCE_MESSAGE_SET_STATE:
if (PA_PTR_TO_UINT(data) == PA_SOURCE_RUNNING)
u->timestamp = pa_rtclock_now();
break;
case PA_SOURCE_MESSAGE_GET_LATENCY: {
pa_usec_t now;
now = pa_rtclock_now();
*((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0;
return 0;
}
}
return pa_source_process_msg(o, code, data, offset, chunk);
}
static void source_update_requested_latency_cb(pa_source *s) {
struct userdata *u;
pa_source_assert_ref(s);
u = s->userdata;
pa_assert(u);
u->block_usec = pa_source_get_requested_latency_within_thread(s);
}
static int lsend(int fd, char *data, int bytes) {
int sent = 0;
int error;
while (sent < bytes) {
error = send(fd, data + sent, bytes - sent, 0);
if (error < 1) {
return error;
}
sent += error;
}
return sent;
}
static int lrecv(int fd, char *data, int bytes) {
int recved = 0;
int error;
while (recved < bytes) {
error = recv(fd, data + recved, bytes - recved, 0);
if (error < 1) {
return error;
}
recved += error;
}
return recved;
}
static int data_get(struct userdata *u, pa_memchunk *chunk) {
int fd;
int bytes;
int read_bytes;
struct sockaddr_un s;
char *data;
char *socket_dir;
char buf[11];
unsigned char ubuf[10];
if (u->fd == 0) {
/* connect to xrdp unix domain socket */
fd = socket(PF_LOCAL, SOCK_STREAM, 0);
memset(&s, 0, sizeof(s));
s.sun_family = AF_UNIX;
bytes = sizeof(s.sun_path) - 1;
socket_dir = getenv("XRDP_SOCKET_PATH");
if (socket_dir == NULL || socket_dir[0] == '\0')
{
socket_dir = "/tmp/.xrdp";
}
snprintf(s.sun_path, bytes, "%s/" CHANSRV_PORT_IN_BASE_STR,
socket_dir, u->display_num);
pa_log_debug("Trying to connect to %s", s.sun_path);
if (connect(fd, (struct sockaddr *) &s, sizeof(struct sockaddr_un)) != 0) {
pa_log_debug("Connect failed");
close(fd);
return -1;
}
pa_log("Connected ok, fd=%d", fd);
pa_log_debug("###### connected to xrdp audio_in socket");
u->fd = fd;
}
data = (char *) pa_memblock_acquire(chunk->memblock);
if (!u->want_src_data) {
char buf[12];
buf[0] = 0;
buf[1] = 0;
buf[2] = 0;
buf[3] = 0;
buf[4] = 11;
buf[5] = 0;
buf[6] = 0;
buf[7] = 0;
buf[8] = 1;
buf[9] = 0;
buf[10] = 0;
if (lsend(u->fd, buf, 11) != 11) {
close(u->fd);
u->fd = 0;
pa_memblock_release(chunk->memblock);
return -1;
}
u->want_src_data = 1;
pa_log_debug("###### started recording");
}
/* ask for more data */
buf[0] = 0;
buf[1] = 0;
buf[2] = 0;
buf[3] = 0;
buf[4] = 11;
buf[5] = 0;
buf[6] = 0;
buf[7] = 0;
buf[8] = 3;
buf[9] = (unsigned char) chunk->length;
buf[10] = (unsigned char) ((chunk->length >> 8) & 0xff);
if (lsend(u->fd, buf, 11) != 11) {
close(u->fd);
u->fd = 0;
pa_memblock_release(chunk->memblock);
u->want_src_data = 0;
return -1;
}
/* read length of data available */
if (lrecv(u->fd, (char *) ubuf, 2) != 2) {
close(u->fd);
u->fd = 0;
pa_memblock_release(chunk->memblock);
u->want_src_data = 0;
return -1;
}
bytes = ((ubuf[1] << 8) & 0xff00) | (ubuf[0] & 0xff);
if (bytes == 0) {
pa_memblock_release(chunk->memblock);
return 0;
}
/* get data */
read_bytes = lrecv(u->fd, data, bytes);
if (read_bytes != bytes) {
close(u->fd);
u->fd = 0;
pa_memblock_release(chunk->memblock);
u->want_src_data = 0;
return -1;
}
pa_memblock_release(chunk->memblock);
return read_bytes;
}
static void thread_func(void *userdata) {
struct userdata *u = userdata;
int bytes;
pa_assert(u);
pa_thread_mq_install(&u->thread_mq);
u->timestamp = pa_rtclock_now();
for (;;) {
int ret;
/* Generate some null data */
if (u->source->thread_info.state == PA_SOURCE_RUNNING) {
pa_usec_t now;
pa_memchunk chunk;
now = pa_rtclock_now();
if ((chunk.length = pa_usec_to_bytes(now - u->timestamp, &u->source->sample_spec)) > 0) {
chunk.length *= 4;
chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length);
chunk.index = 0;
bytes = data_get(u, &chunk);
if (bytes > 0)
{
chunk.length = bytes;
pa_source_post(u->source, &chunk);
}
pa_memblock_unref(chunk.memblock);
u->timestamp = now;
}
pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp + u->latency_time * PA_USEC_PER_MSEC);
} else {
if (u->want_src_data)
{
/* we don't want source data anymore */
char buf[12];
buf[0] = 0;
buf[1] = 0;
buf[2] = 0;
buf[3] = 0;
buf[4] = 11;
buf[5] = 0;
buf[6] = 0;
buf[7] = 0;
buf[8] = 2;
buf[9] = 0;
buf[10] = 0;
if (lsend(u->fd, buf, 11) != 11) {
close(u->fd);
u->fd = 0;
}
u->want_src_data = 0;
pa_log_debug("###### stopped recording");
}
pa_rtpoll_set_timer_disabled(u->rtpoll);
}
/* Hmm, nothing to do. Let's sleep */
#if defined(PA_CHECK_VERSION) && PA_CHECK_VERSION(6, 0, 0)
if ((ret = pa_rtpoll_run(u->rtpoll)) < 0) {
#else
if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) {
#endif
goto fail;
}
if (ret == 0)
goto finish;
}
fail:
/* If this was no regular exit from the loop we have to continue
* processing messages until we received PA_MESSAGE_SHUTDOWN */
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
finish:
pa_log_debug("###### thread shutting down");
}
int pa__init(pa_module *m) {
struct userdata *u = NULL;
pa_sample_spec ss;
pa_channel_map map;
pa_modargs *ma = NULL;
pa_source_new_data data;
uint32_t latency_time = DEFAULT_LATENCY_TIME;
pa_assert(m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments.");
goto fail;
}
#if 1
ss = m->core->default_sample_spec;
#else
ss.format = PA_SAMPLE_S16LE;
ss.rate = 22050;
ss.channels = 2;
#endif
map = m->core->default_channel_map;
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
pa_log("Invalid sample format specification or channel map");
goto fail;
}
m->userdata = u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->module = m;
u->rtpoll = pa_rtpoll_new();
pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
pa_source_new_data_init(&data);
data.driver = __FILE__;
data.module = m;
pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME));
pa_source_new_data_set_sample_spec(&data, &ss);
pa_source_new_data_set_channel_map(&data, &map);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "xrdp source"));
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract");
u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY | PA_SOURCE_DYNAMIC_LATENCY);
pa_source_new_data_done(&data);
if (!u->source) {
pa_log("Failed to create source object.");
goto fail;
}
u->latency_time = DEFAULT_LATENCY_TIME;
if (pa_modargs_get_value_u32(ma, "latency_time", &latency_time) < 0) {
pa_log("Failed to parse latency_time value.");
goto fail;
}
u->latency_time = latency_time;
u->source->parent.process_msg = source_process_msg;
u->source->update_requested_latency = source_update_requested_latency_cb;
u->source->userdata = u;
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
pa_source_set_rtpoll(u->source, u->rtpoll);
pa_source_set_latency_range(u->source, 0, MAX_LATENCY_USEC);
u->block_usec = u->source->thread_info.max_latency;
u->source->thread_info.max_rewind =
pa_usec_to_bytes(u->block_usec, &u->source->sample_spec);
#if defined(PA_CHECK_VERSION)
#if PA_CHECK_VERSION(0, 9, 22)
if (!(u->thread = pa_thread_new("xrdp-source", thread_func, u))) {
#else
if (!(u->thread = pa_thread_new(thread_func, u))) {
#endif
#else
if (!(u->thread = pa_thread_new(thread_func, u)))
#endif
pa_log("Failed to create thread.");
goto fail;
}
pa_source_put(u->source);
pa_modargs_free(ma);
u->display_num = get_display_num_from_display(getenv("DISPLAY"));
return 0;
fail:
if (ma)
pa_modargs_free(ma);
pa__done(m);
return -1;
}
void pa__done(pa_module*m) {
struct userdata *u;
pa_assert(m);
if (!(u = m->userdata))
return;
if (u->source)
pa_source_unlink(u->source);
if (u->thread) {
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
pa_thread_free(u->thread);
}
pa_thread_mq_done(&u->thread_mq);
if (u->source)
pa_source_unref(u->source);
if (u->rtpoll)
pa_rtpoll_free(u->rtpoll);
pa_xfree(u);
}
static int get_display_num_from_display(char *display_text) {
int index;
int mode;
int host_index;
int disp_index;
int scre_index;
int display_num;
char host[256];
char disp[256];
char scre[256];
if (display_text == NULL) {
return 0;
}
memset(host, 0, 256);
memset(disp, 0, 256);
memset(scre, 0, 256);
index = 0;
host_index = 0;
disp_index = 0;
scre_index = 0;
mode = 0;
while (display_text[index] != 0) {
if (display_text[index] == ':') {
mode = 1;
} else if (display_text[index] == '.') {
mode = 2;
} else if (mode == 0) {
host[host_index] = display_text[index];
host_index++;
} else if (mode == 1) {
disp[disp_index] = display_text[index];
disp_index++;
} else if (mode == 2) {
scre[scre_index] = display_text[index];
scre_index++;
}
index++;
}
host[host_index] = 0;
disp[disp_index] = 0;
scre[scre_index] = 0;
display_num = atoi(disp);
return display_num;
}
Loading…
Cancel
Save