2006-04-17 00:11:04 +00:00
|
|
|
/***
|
2006-06-19 21:53:48 +00:00
|
|
|
This file is part of PulseAudio.
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
Copyright 2006 Lennart Poettering
|
2007-02-13 15:35:19 +00:00
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
PulseAudio is free software; you can redistribute it and/or modify
|
2006-04-17 00:11:04 +00:00
|
|
|
it under the terms of the GNU Lesser General Public License as published
|
2009-03-03 20:23:02 +00:00
|
|
|
by the Free Software Foundation; either version 2.1 of the License,
|
2006-04-17 00:11:04 +00:00
|
|
|
or (at your option) any later version.
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
PulseAudio is distributed in the hope that it will be useful, but
|
2006-04-17 00:11:04 +00:00
|
|
|
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.
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2006-04-17 00:11:04 +00:00
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
2014-11-26 14:14:51 +01:00
|
|
|
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
2006-04-17 00:11:04 +00:00
|
|
|
***/
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <stdio.h>
|
2006-04-17 00:11:04 +00:00
|
|
|
#include <errno.h>
|
2007-10-28 19:13:50 +00:00
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
2006-04-17 00:11:04 +00:00
|
|
|
|
|
|
|
|
#include <jack/jack.h>
|
2021-05-01 16:57:13 +02:00
|
|
|
#include <jack/metadata.h>
|
|
|
|
|
#include <jack/uuid.h>
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2018-04-21 09:45:26 +05:30
|
|
|
#include <pulse/util.h>
|
2006-06-19 21:53:48 +00:00
|
|
|
#include <pulse/xmalloc.h>
|
2006-05-17 16:34:18 +00:00
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
#include <pulsecore/sink.h>
|
|
|
|
|
#include <pulsecore/module.h>
|
|
|
|
|
#include <pulsecore/core-util.h>
|
|
|
|
|
#include <pulsecore/modargs.h>
|
2007-10-28 19:13:50 +00:00
|
|
|
#include <pulsecore/log.h>
|
|
|
|
|
#include <pulsecore/thread.h>
|
|
|
|
|
#include <pulsecore/thread-mq.h>
|
|
|
|
|
#include <pulsecore/rtpoll.h>
|
|
|
|
|
#include <pulsecore/sample-util.h>
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
/* General overview:
|
|
|
|
|
*
|
2011-08-24 18:24:46 +02:00
|
|
|
* Because JACK has a very inflexible event loop management which
|
2007-10-28 19:13:50 +00:00
|
|
|
* doesn't allow us to add our own event sources to the event thread
|
|
|
|
|
* we cannot use the JACK real-time thread for dispatching our PA
|
|
|
|
|
* work. Instead, we run an additional RT thread which does most of
|
|
|
|
|
* the PA handling, and have the JACK RT thread request data from it
|
|
|
|
|
* via pa_asyncmsgq. The cost is an additional context switch which
|
|
|
|
|
* should hopefully not be that expensive if RT scheduling is
|
|
|
|
|
* enabled. A better fix would only be possible with additional event
|
|
|
|
|
* source support in JACK.
|
|
|
|
|
*/
|
|
|
|
|
|
2007-11-09 18:25:40 +00:00
|
|
|
PA_MODULE_AUTHOR("Lennart Poettering");
|
|
|
|
|
PA_MODULE_DESCRIPTION("JACK Sink");
|
2014-08-28 11:04:53 +02:00
|
|
|
PA_MODULE_LOAD_ONCE(false);
|
2007-11-09 18:25:40 +00:00
|
|
|
PA_MODULE_VERSION(PACKAGE_VERSION);
|
2006-04-17 00:11:04 +00:00
|
|
|
PA_MODULE_USAGE(
|
2009-05-28 02:39:22 +02:00
|
|
|
"sink_name=<name for the sink> "
|
2011-03-12 19:45:02 +01:00
|
|
|
"sink_properties=<properties for the card> "
|
2006-04-18 13:20:50 +00:00
|
|
|
"server_name=<jack server name> "
|
|
|
|
|
"client_name=<jack client name> "
|
2006-04-17 00:11:04 +00:00
|
|
|
"channels=<number of channels> "
|
2009-05-28 02:39:22 +02:00
|
|
|
"channel_map=<channel map> "
|
|
|
|
|
"connect=<connect ports?>");
|
2006-04-17 00:11:04 +00:00
|
|
|
|
|
|
|
|
#define DEFAULT_SINK_NAME "jack_out"
|
2021-05-01 16:57:13 +02:00
|
|
|
#define METADATA_TYPE_INT "http://www.w3.org/2001/XMLSchema#int"
|
|
|
|
|
#define METADATA_KEY_ORDER "http://jackaudio.org/metadata/order"
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-08-20 06:22:21 +00:00
|
|
|
struct userdata {
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_core *core;
|
|
|
|
|
pa_module *module;
|
2006-04-17 00:11:04 +00:00
|
|
|
pa_sink *sink;
|
|
|
|
|
|
|
|
|
|
unsigned channels;
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
jack_port_t* port[PA_CHANNELS_MAX];
|
|
|
|
|
jack_client_t *client;
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
void *buffer[PA_CHANNELS_MAX];
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_thread_mq thread_mq;
|
|
|
|
|
pa_asyncmsgq *jack_msgq;
|
|
|
|
|
pa_rtpoll *rtpoll;
|
|
|
|
|
pa_rtpoll_item *rtpoll_item;
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_thread *thread;
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
jack_nframes_t frames_in_buffer;
|
|
|
|
|
jack_nframes_t saved_frame_time;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool saved_frame_time_valid;
|
2007-10-28 19:13:50 +00:00
|
|
|
};
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
static const char* const valid_modargs[] = {
|
|
|
|
|
"sink_name",
|
2009-05-28 02:39:22 +02:00
|
|
|
"sink_properties",
|
2007-10-28 19:13:50 +00:00
|
|
|
"server_name",
|
|
|
|
|
"client_name",
|
|
|
|
|
"channels",
|
|
|
|
|
"channel_map",
|
2009-05-28 02:39:22 +02:00
|
|
|
"connect",
|
2007-10-28 19:13:50 +00:00
|
|
|
NULL
|
|
|
|
|
};
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
enum {
|
|
|
|
|
SINK_MESSAGE_RENDER = PA_SINK_MESSAGE_MAX,
|
2009-03-25 00:40:12 +01:00
|
|
|
SINK_MESSAGE_BUFFER_SIZE,
|
2007-10-28 19:13:50 +00:00
|
|
|
SINK_MESSAGE_ON_SHUTDOWN
|
2007-08-20 06:22:21 +00:00
|
|
|
};
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *memchunk) {
|
|
|
|
|
struct userdata *u = PA_SINK(o)->userdata;
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
switch (code) {
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
case SINK_MESSAGE_RENDER:
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
/* Handle the request from the JACK thread */
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (u->sink->thread_info.state == PA_SINK_RUNNING) {
|
|
|
|
|
pa_memchunk chunk;
|
|
|
|
|
size_t nbytes;
|
|
|
|
|
void *p;
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_assert(offset > 0);
|
2008-08-19 22:39:54 +02:00
|
|
|
nbytes = (size_t) offset * pa_frame_size(&u->sink->sample_spec);
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_sink_render_full(u->sink, nbytes, &chunk);
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2012-08-17 18:09:34 +03:00
|
|
|
p = pa_memblock_acquire_chunk(&chunk);
|
2008-08-19 22:39:54 +02:00
|
|
|
pa_deinterleave(p, u->buffer, u->channels, sizeof(float), (unsigned) offset);
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_memblock_release(chunk.memblock);
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_memblock_unref(chunk.memblock);
|
|
|
|
|
} else {
|
|
|
|
|
unsigned c;
|
|
|
|
|
pa_sample_spec ss;
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
/* Humm, we're not RUNNING, hence let's write some silence */
|
2010-05-03 11:41:47 +02:00
|
|
|
/* This can happen if we're paused, or during shutdown (when we're unlinked but jack is still running). */
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
ss = u->sink->sample_spec;
|
|
|
|
|
ss.channels = 1;
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
for (c = 0; c < u->channels; c++)
|
2008-08-19 22:39:54 +02:00
|
|
|
pa_silence_memory(u->buffer[c], (size_t) offset * pa_sample_size(&ss), &ss);
|
2007-10-28 19:13:50 +00:00
|
|
|
}
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2008-08-19 22:39:54 +02:00
|
|
|
u->frames_in_buffer = (jack_nframes_t) offset;
|
2007-10-28 19:13:50 +00:00
|
|
|
u->saved_frame_time = * (jack_nframes_t*) data;
|
2013-06-27 19:28:09 +02:00
|
|
|
u->saved_frame_time_valid = true;
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
return 0;
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2009-03-25 00:40:12 +01:00
|
|
|
case SINK_MESSAGE_BUFFER_SIZE:
|
|
|
|
|
pa_sink_set_max_request_within_thread(u->sink, (size_t) offset * pa_frame_size(&u->sink->sample_spec));
|
|
|
|
|
return 0;
|
|
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
case SINK_MESSAGE_ON_SHUTDOWN:
|
|
|
|
|
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
|
|
|
|
return 0;
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
case PA_SINK_MESSAGE_GET_LATENCY: {
|
2017-05-02 16:44:29 +03:00
|
|
|
jack_nframes_t ft, d;
|
2012-03-26 23:12:24 +02:00
|
|
|
jack_latency_range_t r;
|
2007-10-28 19:13:50 +00:00
|
|
|
size_t n;
|
source/sink: Allow pa_{source, sink}_get_latency_within_thread() to return negative values
The reported latency of source or sink is based on measured initial conditions.
If the conditions contain an error, the estimated latency values may become negative.
This does not indicate that the latency is indeed negative but can be considered
merely an offset error. The current get_latency_in_thread() calls and the
implementations of the PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY messages truncate negative
latencies because they do not make sense from a physical point of view. In fact,
the values are truncated twice, once in the message handler and a second time in
the pa_{source,sink}_get_latency_within_thread() call itself.
This leads to two problems for the latency controller within module-loopback:
- Truncating leads to discontinuities in the latency reports which then trigger
unwanted end to end latency corrections.
- If a large negative port latency offsets is set, the reported latency is always 0,
making it impossible to control the end to end latency at all.
This patch is a pre-condition for solving these problems.
It adds a new flag to pa_{sink,source}_get_latency_within_thread() to allow
negative return values. Truncating is also removed in all implementations of the
PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY message handlers. The allow_negative flag
is set to false for all calls of pa_{sink,source}_get_latency_within_thread()
except when used within PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY. This means that the
original behavior is not altered in most cases. Only if a positive latency offset
is set and the message returns a negative value, the reported latency is smaller
because the values are not truncated twice.
Additionally let PA_SOURCE_MESSAGE_GET_LATENCY return -pa_sink_get_latency_within_thread()
for monitor sources because the source gets the data before it is played.
2017-04-17 19:50:10 +02:00
|
|
|
int32_t number_of_frames;
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
/* This is the "worst-case" latency */
|
2012-03-26 23:12:24 +02:00
|
|
|
jack_port_get_latency_range(u->port[0], JackPlaybackLatency, &r);
|
2017-05-02 16:44:29 +03:00
|
|
|
number_of_frames = r.max + u->frames_in_buffer;
|
2007-10-28 19:13:50 +00:00
|
|
|
|
|
|
|
|
if (u->saved_frame_time_valid) {
|
|
|
|
|
/* Adjust the worst case latency by the time that
|
|
|
|
|
* passed since we last handed data to JACK */
|
|
|
|
|
|
|
|
|
|
ft = jack_frame_time(u->client);
|
|
|
|
|
d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0;
|
2017-05-02 16:44:29 +03:00
|
|
|
number_of_frames -= d;
|
2007-10-28 19:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Convert it to usec */
|
source/sink: Allow pa_{source, sink}_get_latency_within_thread() to return negative values
The reported latency of source or sink is based on measured initial conditions.
If the conditions contain an error, the estimated latency values may become negative.
This does not indicate that the latency is indeed negative but can be considered
merely an offset error. The current get_latency_in_thread() calls and the
implementations of the PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY messages truncate negative
latencies because they do not make sense from a physical point of view. In fact,
the values are truncated twice, once in the message handler and a second time in
the pa_{source,sink}_get_latency_within_thread() call itself.
This leads to two problems for the latency controller within module-loopback:
- Truncating leads to discontinuities in the latency reports which then trigger
unwanted end to end latency corrections.
- If a large negative port latency offsets is set, the reported latency is always 0,
making it impossible to control the end to end latency at all.
This patch is a pre-condition for solving these problems.
It adds a new flag to pa_{sink,source}_get_latency_within_thread() to allow
negative return values. Truncating is also removed in all implementations of the
PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY message handlers. The allow_negative flag
is set to false for all calls of pa_{sink,source}_get_latency_within_thread()
except when used within PA_{SINK,SOURCE}_MESSAGE_GET_LATENCY. This means that the
original behavior is not altered in most cases. Only if a positive latency offset
is set and the message returns a negative value, the reported latency is smaller
because the values are not truncated twice.
Additionally let PA_SOURCE_MESSAGE_GET_LATENCY return -pa_sink_get_latency_within_thread()
for monitor sources because the source gets the data before it is played.
2017-04-17 19:50:10 +02:00
|
|
|
if (number_of_frames > 0) {
|
|
|
|
|
n = number_of_frames * pa_frame_size(&u->sink->sample_spec);
|
|
|
|
|
*((int64_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec);
|
|
|
|
|
} else {
|
|
|
|
|
n = - number_of_frames * pa_frame_size(&u->sink->sample_spec);
|
|
|
|
|
*((int64_t*) data) = - (int64_t)pa_bytes_to_usec(n, &u->sink->sample_spec);
|
|
|
|
|
}
|
2007-10-28 19:13:50 +00:00
|
|
|
|
|
|
|
|
return 0;
|
2006-04-17 00:11:04 +00:00
|
|
|
}
|
2009-03-25 00:40:12 +01:00
|
|
|
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
return pa_sink_process_msg(o, code, data, offset, memchunk);
|
|
|
|
|
}
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2010-05-03 13:28:15 +02:00
|
|
|
/* JACK Callback: This is called when JACK needs some data */
|
2007-10-28 19:13:50 +00:00
|
|
|
static int jack_process(jack_nframes_t nframes, void *arg) {
|
|
|
|
|
struct userdata *u = arg;
|
|
|
|
|
unsigned c;
|
|
|
|
|
jack_nframes_t frame_time;
|
|
|
|
|
pa_assert(u);
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
/* We just forward the request to our other RT thread */
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
for (c = 0; c < u->channels; c++)
|
|
|
|
|
pa_assert_se(u->buffer[c] = jack_port_get_buffer(u->port[c], nframes));
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
frame_time = jack_frame_time(u->client);
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_assert_se(pa_asyncmsgq_send(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_RENDER, &frame_time, nframes, NULL) == 0);
|
|
|
|
|
return 0;
|
2006-04-17 00:11:04 +00:00
|
|
|
}
|
|
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
static void thread_func(void *userdata) {
|
|
|
|
|
struct userdata *u = userdata;
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_assert(u);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_log_debug("Thread starting up");
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-11-01 02:58:26 +00:00
|
|
|
if (u->core->realtime_scheduling)
|
2018-04-21 09:45:26 +05:30
|
|
|
pa_thread_make_realtime(u->core->realtime_priority);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_thread_mq_install(&u->thread_mq);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
for (;;) {
|
|
|
|
|
int ret;
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2012-11-16 23:09:15 +05:30
|
|
|
if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
|
2012-08-30 16:50:13 +03:00
|
|
|
pa_sink_process_rewind(u->sink, 0);
|
2008-06-26 02:56:00 +02:00
|
|
|
|
2014-11-10 14:15:39 +01:00
|
|
|
if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
|
2007-08-20 06:22:21 +00:00
|
|
|
goto fail;
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (ret == 0)
|
|
|
|
|
goto finish;
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-08-20 06:22:21 +00:00
|
|
|
fail:
|
2007-10-28 19:13:50 +00:00
|
|
|
/* 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);
|
2006-04-18 13:20:50 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
finish:
|
|
|
|
|
pa_log_debug("Thread shutting down");
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2010-05-03 13:28:15 +02:00
|
|
|
/* JACK Callback: This is called when JACK triggers an error */
|
2007-10-28 19:13:50 +00:00
|
|
|
static void jack_error_func(const char*t) {
|
|
|
|
|
char *s;
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
s = pa_xstrndup(t, strcspn(t, "\n\r"));
|
|
|
|
|
pa_log_warn("JACK error >%s<", s);
|
|
|
|
|
pa_xfree(s);
|
2006-04-17 00:11:04 +00:00
|
|
|
}
|
|
|
|
|
|
2010-05-03 13:28:15 +02:00
|
|
|
/* JACK Callback: This is called when JACK is set up */
|
2007-10-28 19:13:50 +00:00
|
|
|
static void jack_init(void *arg) {
|
|
|
|
|
struct userdata *u = arg;
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_log_info("JACK thread starting up.");
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-11-01 02:58:26 +00:00
|
|
|
if (u->core->realtime_scheduling)
|
2018-04-21 09:45:26 +05:30
|
|
|
pa_thread_make_realtime(u->core->realtime_priority+4);
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2010-05-03 13:28:15 +02:00
|
|
|
/* JACK Callback: This is called when JACK kicks us */
|
2007-10-28 19:13:50 +00:00
|
|
|
static void jack_shutdown(void* arg) {
|
|
|
|
|
struct userdata *u = arg;
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2009-03-25 00:40:12 +01:00
|
|
|
pa_log_info("JACK thread shutting down.");
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL);
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2010-05-03 13:28:15 +02:00
|
|
|
/* JACK Callback: This is called when JACK changes the buffer size */
|
2009-03-25 00:40:12 +01:00
|
|
|
static int jack_buffer_size(jack_nframes_t nframes, void *arg) {
|
|
|
|
|
struct userdata *u = arg;
|
|
|
|
|
|
|
|
|
|
pa_log_info("JACK buffer size changed.");
|
|
|
|
|
pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_BUFFER_SIZE, NULL, nframes, NULL, NULL);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
int pa__init(pa_module*m) {
|
|
|
|
|
struct userdata *u = NULL;
|
|
|
|
|
pa_sample_spec ss;
|
|
|
|
|
pa_channel_map map;
|
|
|
|
|
pa_modargs *ma = NULL;
|
|
|
|
|
jack_status_t status;
|
|
|
|
|
const char *server_name, *client_name;
|
|
|
|
|
uint32_t channels = 0;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool do_connect = true;
|
2007-10-28 19:13:50 +00:00
|
|
|
unsigned i;
|
|
|
|
|
const char **ports = NULL, **p;
|
2008-05-15 23:34:41 +00:00
|
|
|
pa_sink_new_data data;
|
2012-03-26 23:12:24 +02:00
|
|
|
jack_latency_range_t r;
|
2021-05-01 16:57:13 +02:00
|
|
|
jack_uuid_t port_uuid;
|
|
|
|
|
char port_order[4];
|
2012-03-26 23:12:24 +02:00
|
|
|
size_t n;
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_assert(m);
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
jack_set_error_function(jack_error_func);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
|
|
|
|
pa_log("Failed to parse module arguments.");
|
2006-04-17 00:11:04 +00:00
|
|
|
goto fail;
|
|
|
|
|
}
|
2007-10-28 19:13:50 +00:00
|
|
|
|
|
|
|
|
if (pa_modargs_get_value_boolean(ma, "connect", &do_connect) < 0) {
|
|
|
|
|
pa_log("Failed to parse connect= argument.");
|
2006-04-17 00:11:04 +00:00
|
|
|
goto fail;
|
|
|
|
|
}
|
2006-04-18 13:20:50 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
server_name = pa_modargs_get_value(ma, "server_name", NULL);
|
|
|
|
|
client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Sink");
|
|
|
|
|
|
2009-03-25 00:40:12 +01:00
|
|
|
m->userdata = u = pa_xnew0(struct userdata, 1);
|
2007-10-28 19:13:50 +00:00
|
|
|
u->core = m->core;
|
|
|
|
|
u->module = m;
|
2013-06-27 19:28:09 +02:00
|
|
|
u->saved_frame_time_valid = false;
|
2007-10-28 19:13:50 +00:00
|
|
|
u->rtpoll = pa_rtpoll_new();
|
2016-09-13 18:43:38 +03:00
|
|
|
|
|
|
|
|
if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) {
|
|
|
|
|
pa_log("pa_thread_mq_init() failed.");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
2007-10-28 19:13:50 +00:00
|
|
|
|
|
|
|
|
/* The queue linking the JACK thread and our RT thread */
|
|
|
|
|
u->jack_msgq = pa_asyncmsgq_new(0);
|
2016-09-13 18:43:38 +03:00
|
|
|
if (!u->jack_msgq) {
|
|
|
|
|
pa_log("pa_asyncmsgq_new() failed.");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
2007-10-28 19:13:50 +00:00
|
|
|
|
|
|
|
|
/* The msgq from the JACK RT thread should have an even higher
|
|
|
|
|
* priority than the normal message queues, to match the guarantee
|
|
|
|
|
* all other drivers make: supplying the audio device with data is
|
|
|
|
|
* the top priority -- and as long as that is possible we don't do
|
|
|
|
|
* anything else */
|
2008-05-15 23:34:41 +00:00
|
|
|
u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq);
|
2007-10-28 19:13:50 +00:00
|
|
|
|
|
|
|
|
if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) {
|
|
|
|
|
pa_log("jack_client_open() failed.");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2009-11-05 05:18:10 +01:00
|
|
|
ports = jack_get_ports(u->client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsInput);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
channels = 0;
|
2011-03-28 15:16:12 +02:00
|
|
|
if (ports)
|
|
|
|
|
for (p = ports; *p; p++)
|
|
|
|
|
channels++;
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (!channels)
|
|
|
|
|
channels = m->core->default_sample_spec.channels;
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2009-02-21 16:32:42 +01:00
|
|
|
if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 ||
|
2013-12-04 09:50:11 +02:00
|
|
|
!pa_channels_valid(channels)) {
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_log("Failed to parse channels= argument.");
|
|
|
|
|
goto fail;
|
2006-04-17 00:11:04 +00:00
|
|
|
}
|
2007-10-28 19:13:50 +00:00
|
|
|
|
2009-02-21 16:32:42 +01:00
|
|
|
if (channels == m->core->default_channel_map.channels)
|
|
|
|
|
map = m->core->default_channel_map;
|
|
|
|
|
else
|
|
|
|
|
pa_channel_map_init_extend(&map, channels, PA_CHANNEL_MAP_ALSA);
|
|
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) {
|
|
|
|
|
pa_log("Failed to parse channel_map= argument.");
|
|
|
|
|
goto fail;
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client));
|
|
|
|
|
|
2008-08-19 22:39:54 +02:00
|
|
|
u->channels = ss.channels = (uint8_t) channels;
|
2007-10-28 19:13:50 +00:00
|
|
|
ss.rate = jack_get_sample_rate(u->client);
|
|
|
|
|
ss.format = PA_SAMPLE_FLOAT32NE;
|
2007-08-20 06:22:21 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_assert(pa_sample_spec_valid(&ss));
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < ss.channels; i++) {
|
|
|
|
|
if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput|JackPortIsTerminal, 0))) {
|
|
|
|
|
pa_log("jack_port_register() failed.");
|
|
|
|
|
goto fail;
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
2021-05-01 16:57:13 +02:00
|
|
|
|
|
|
|
|
/* Set order of ports as JACK metadata, if possible. */
|
|
|
|
|
/* See: https://jackaudio.org/api/group__Metadata.html */
|
|
|
|
|
port_uuid = jack_port_uuid(u->port[i]);
|
|
|
|
|
|
|
|
|
|
if (!jack_uuid_empty(port_uuid)) {
|
|
|
|
|
if (snprintf(port_order, 4, "%d", i+1) >= 4)
|
|
|
|
|
pa_log("Port order metadata value > 999 truncated.");
|
|
|
|
|
if (jack_set_property(u->client, port_uuid, METADATA_KEY_ORDER, port_order, METADATA_TYPE_INT) != 0)
|
|
|
|
|
pa_log("jack_set_property() failed.");
|
|
|
|
|
}
|
2006-04-17 00:11:04 +00:00
|
|
|
}
|
|
|
|
|
|
2008-05-15 23:34:41 +00:00
|
|
|
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_API, "jack");
|
|
|
|
|
if (server_name)
|
|
|
|
|
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name);
|
2020-12-20 11:35:30 +00:00
|
|
|
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "JACK sink (%s)", jack_get_client_name(u->client));
|
2008-05-15 23:34:41 +00:00
|
|
|
pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client));
|
|
|
|
|
|
2009-05-28 02:39:22 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2008-05-15 23:34:41 +00:00
|
|
|
u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY);
|
|
|
|
|
pa_sink_new_data_done(&data);
|
|
|
|
|
|
|
|
|
|
if (!u->sink) {
|
|
|
|
|
pa_log("Failed to create sink.");
|
2007-10-28 19:13:50 +00:00
|
|
|
goto fail;
|
|
|
|
|
}
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
u->sink->parent.process_msg = sink_process_msg;
|
|
|
|
|
u->sink->userdata = u;
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
|
|
|
|
|
pa_sink_set_rtpoll(u->sink, u->rtpoll);
|
2009-03-25 00:40:12 +01:00
|
|
|
pa_sink_set_max_request(u->sink, jack_get_buffer_size(u->client) * pa_frame_size(&u->sink->sample_spec));
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
jack_set_process_callback(u->client, jack_process, u);
|
|
|
|
|
jack_on_shutdown(u->client, jack_shutdown, u);
|
|
|
|
|
jack_set_thread_init_callback(u->client, jack_init, u);
|
2009-03-25 00:40:12 +01:00
|
|
|
jack_set_buffer_size_callback(u->client, jack_buffer_size, u);
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2010-05-03 13:28:15 +02:00
|
|
|
if (!(u->thread = pa_thread_new("jack-sink", thread_func, u))) {
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_log("Failed to create thread.");
|
|
|
|
|
goto fail;
|
2006-04-17 00:11:04 +00:00
|
|
|
}
|
|
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (jack_activate(u->client)) {
|
|
|
|
|
pa_log("jack_activate() failed");
|
|
|
|
|
goto fail;
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
2007-10-28 19:13:50 +00:00
|
|
|
|
|
|
|
|
if (do_connect) {
|
|
|
|
|
for (i = 0, p = ports; i < ss.channels; i++, p++) {
|
|
|
|
|
|
2011-03-28 15:16:12 +02:00
|
|
|
if (!p || !*p) {
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_log("Not enough physical output ports, leaving unconnected.");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_log_info("Connecting %s to %s", jack_port_name(u->port[i]), *p);
|
|
|
|
|
|
|
|
|
|
if (jack_connect(u->client, jack_port_name(u->port[i]), *p)) {
|
|
|
|
|
pa_log("Failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
|
|
|
|
}
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2012-03-26 23:12:24 +02:00
|
|
|
jack_port_get_latency_range(u->port[0], JackPlaybackLatency, &r);
|
|
|
|
|
n = r.max * pa_frame_size(&u->sink->sample_spec);
|
|
|
|
|
pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(n, &u->sink->sample_spec));
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_sink_put(u->sink);
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2011-03-28 15:16:12 +02:00
|
|
|
if (ports)
|
|
|
|
|
jack_free(ports);
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_modargs_free(ma);
|
2006-04-18 13:20:50 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
if (ma)
|
|
|
|
|
pa_modargs_free(ma);
|
|
|
|
|
|
2011-03-28 15:16:12 +02:00
|
|
|
if (ports)
|
|
|
|
|
jack_free(ports);
|
2007-01-04 13:43:45 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa__done(m);
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
return -1;
|
2006-04-17 00:11:04 +00:00
|
|
|
}
|
|
|
|
|
|
2009-01-15 20:49:12 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
void pa__done(pa_module*m) {
|
|
|
|
|
struct userdata *u;
|
|
|
|
|
|
|
|
|
|
pa_assert(m);
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (!(u = m->userdata))
|
2006-04-17 00:11:04 +00:00
|
|
|
return;
|
|
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (u->sink)
|
|
|
|
|
pa_sink_unlink(u->sink);
|
|
|
|
|
|
2010-05-03 11:41:47 +02:00
|
|
|
if (u->client)
|
|
|
|
|
jack_client_close(u->client);
|
|
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (u->thread) {
|
|
|
|
|
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
|
|
|
|
|
pa_thread_free(u->thread);
|
2007-08-20 06:22:21 +00:00
|
|
|
}
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
pa_thread_mq_done(&u->thread_mq);
|
2006-04-17 00:11:04 +00:00
|
|
|
|
2007-10-28 19:13:50 +00:00
|
|
|
if (u->sink)
|
2006-04-17 00:11:04 +00:00
|
|
|
pa_sink_unref(u->sink);
|
2007-10-28 19:13:50 +00:00
|
|
|
|
|
|
|
|
if (u->rtpoll_item)
|
|
|
|
|
pa_rtpoll_item_free(u->rtpoll_item);
|
|
|
|
|
|
|
|
|
|
if (u->jack_msgq)
|
|
|
|
|
pa_asyncmsgq_unref(u->jack_msgq);
|
|
|
|
|
|
|
|
|
|
if (u->rtpoll)
|
|
|
|
|
pa_rtpoll_free(u->rtpoll);
|
|
|
|
|
|
|
|
|
|
pa_xfree(u);
|
2006-04-17 00:11:04 +00:00
|
|
|
}
|