History

JACK receiver (#126)

JACK receiver by @roovio

Oleksandr Dunaievskyi committed 5 years ago

Commit cdb0c741 parent 24efc0e6 files · +474 −7Browse files

Unified · Split

6 files
  1. Receivers/unix/CMakeLists.txt +16 −0
  2. Receivers/unix/config.h.in +1 −0
  3. Receivers/unix/jack.c +419 −0
  4. Receivers/unix/jack.h +9 −0
  5. Receivers/unix/scream.c +28 −6
  6. Receivers/unix/scream.h +1 −1
Receivers/unix/CMakeLists.txt+16−0
@@ -33,6 +33,22 @@ if (ALSA_ENABLE) · 32 unmodified lines
33 endif ()33 endif ()
34endif ()34endif ()
35 35
36# find JACK
37option(JACK_ENABLE "Enable JACK" ON)
38if (JACK_ENABLE)
39 find_package(PkgConfig)
40 pkg_check_modules(PC_JACK jack)
41 pkg_check_modules(PC_LIBSOXR soxr)
42 if(PC_JACK_FOUND AND PC_LIBSOXR_FOUND)
43 include_directories(${PC_JACK_INCLUDEDIR} ${PC_LIBSOXR_INCLUDEDIR})
44 target_link_libraries(${PROJECT_NAME} ${PC_JACK_LIBRARIES} ${PC_LIBSOXR_LIBRARIES})
45 target_sources(${PROJECT_NAME} PRIVATE jack.c)
46 else ()
47 set(JACK_ENABLE OFF)
48 endif ()
49endif ()
50
51
36# find libpcap52# find libpcap
37option(PCAP_ENABLE "Enable PCAP" ON)53option(PCAP_ENABLE "Enable PCAP" ON)
38if (PCAP_ENABLE)54if (PCAP_ENABLE)
Receivers/unix/config.h.in+1−0
@@ -1,3 +1,4 @@
1#cmakedefine01 PULSEAUDIO_ENABLE1#cmakedefine01 PULSEAUDIO_ENABLE
2#cmakedefine01 ALSA_ENABLE2#cmakedefine01 ALSA_ENABLE
3#cmakedefine01 JACK_ENABLE
3#cmakedefine01 PCAP_ENABLE4#cmakedefine01 PCAP_ENABLE
Receivers/unix/jack.c+419−0
@@ -0,0 +1,419 @@
1//
2// A Scream receiver JACK client
3//
4// (c) 2020 Oleksandr Dunayevskyy <oleksandr.dunayevskyy@gmail.com>
5//
6
7#include <jack/jack.h>
8#include <soxr.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13#include "jack.h"
14
15
16
17typedef jack_default_audio_sample_t ringbuffer_element_t;
18#define RINGBUFFER_SIZE 8192
19struct ringbuffer_t
20{
21 ringbuffer_element_t elements[RINGBUFFER_SIZE];
22 uint32_t read_pos;
23 uint32_t write_pos;
24};
25
26
27uint32_t ringbuffer_mask(uint32_t val)
28{
29 return val & (RINGBUFFER_SIZE - 1u);
30}
31
32void ringbuffer_init(struct ringbuffer_t *rb)
33{
34 rb->read_pos = 0u;
35 rb->write_pos = 0u;
36}
37
38uint32_t ringbuffer_size(struct ringbuffer_t *rb)
39{
40 return rb->write_pos - rb->read_pos;
41}
42
43int ringbuffer_empty(struct ringbuffer_t *rb)
44{
45 return ringbuffer_size(rb) == 0u;
46}
47
48int ringbuffer_full(struct ringbuffer_t *rb)
49{
50 return ringbuffer_size(rb) == RINGBUFFER_SIZE;
51}
52
53void ringbuffer_push(struct ringbuffer_t *rb, ringbuffer_element_t val)
54{
55 if (!ringbuffer_full(rb))
56 {
57 rb->elements[ringbuffer_mask(rb->write_pos++)] = val;
58 }
59 else
60 {
61 fprintf(stderr, "ringbuffer overflow\n");
62 }
63
64}
65
66ringbuffer_element_t ringbuffer_pop(struct ringbuffer_t *rb)
67{
68 if (!ringbuffer_empty(rb))
69 {
70 return rb->elements[ringbuffer_mask(rb->read_pos++)];
71 }
72 else
73 {
74 fprintf(stderr, "ringbuffer underflow\n");
75 return (ringbuffer_element_t)0;
76 }
77
78}
79
80
81static struct jack_output_data
82{
83 jack_client_t *client;
84 jack_port_t **output_ports; ///< number of ports actually used == rf->channels
85 jack_default_audio_sample_t **buffers;
86 receiver_format_t receiver_format;
87 uint32_t sample_rate; ///< source sample rate
88 soxr_t soxr;
89 jack_default_audio_sample_t *resample_buffer;
90 size_t resample_buffer_size;
91 struct ringbuffer_t rb;
92}
93jo_data;
94
95
96
97static int init_resampler();
98static int init_channels();
99static int connect_ports();
100static int process_source_data();
101
102// JACK realtime process callback
103int jack_process(jack_nframes_t nframes, void *arg);
104
105
106
107int jack_output_init(char *stream_name)
108{
109 jack_status_t status;
110
111 // init receiver format to track changes
112 jo_data.receiver_format.sample_rate = 0;
113 jo_data.receiver_format.sample_size = 0;
114 jo_data.receiver_format.channels = 2;
115 jo_data.receiver_format.channel_map = 0x0003;
116
117 jo_data.soxr = NULL;
118 jo_data.resample_buffer = NULL;
119 jo_data.resample_buffer_size = 0;
120
121 ringbuffer_init(&jo_data.rb);
122
123
124 jo_data.client = jack_client_open(stream_name, JackNullOption, &status, NULL);
125 if (jo_data.client == NULL)
126 {
127 fprintf(stderr, "jack_client_open() failed, status = 0x%2.0x\n", status);
128 if (status & JackServerFailed)
129 {
130 fprintf(stderr, "Unable to connect to JACK server\n");
131 }
132 return 1;
133 }
134
135 if (status & JackServerStarted)
136 {
137 fprintf(stderr, "JACK server started\n");
138 }
139
140 if (status & JackNameNotUnique)
141 {
142 const char *client_name = jack_get_client_name(jo_data.client);
143 fprintf(stderr, "unique name `%s' assigned\n", client_name);
144 }
145
146 jack_set_process_callback(jo_data.client, jack_process, 0);
147
148 return 0;
149}
150
151
152
153
154int jack_output_send(receiver_data_t *data)
155{
156 receiver_format_t *rf = &data->format;
157
158 if (memcmp(&jo_data.receiver_format, rf, sizeof(receiver_format_t)))
159 {
160 // audio format changed, reconfigure
161 memcpy(&jo_data.receiver_format, rf, sizeof(receiver_format_t));
162
163 jo_data.sample_rate = ((rf->sample_rate >= 128) ? 44100 : 48000) * (rf->sample_rate % 128);
164
165 printf(
166 "Switched sample rate %"PRIu32", sample size %u and %u channels\n",
167 jo_data.sample_rate,
168 jo_data.receiver_format.sample_size,
169 rf->channels);
170
171 printf("JACK sample rate %" PRIu32 "\n", jack_get_sample_rate(jo_data.client));
172
173
174 if (init_resampler())
175 {
176 return 1;
177 }
178
179
180
181 if (jack_deactivate(jo_data.client))
182 {
183 fprintf(stderr, "cannot deactivate client");
184 return 1;
185 }
186
187 if (init_channels())
188 {
189 return 1;
190 }
191
192 // activating JJACK client - jack_process() callback will start running now
193 if (jack_activate(jo_data.client))
194 {
195 fprintf(stderr, "cannot activate client");
196 return 1;
197 }
198
199 if (connect_ports())
200 {
201 return 1;
202 }
203 }
204
205
206 if (process_source_data(data))
207 {
208 return 1;
209 }
210
211 return 0;
212}
213
214
215
216
217static int init_resampler()
218{
219 soxr_io_spec_t io_spec;
220 soxr_datatype_t in_datatype;
221 switch(jo_data.receiver_format.sample_size)
222 {
223 case 16: in_datatype = SOXR_INT16_I; break;
224 case 32: in_datatype = SOXR_INT32_I; break;
225 default:
226 return 1;
227 }
228
229 io_spec = soxr_io_spec(in_datatype, SOXR_FLOAT32_I);
230 if (jo_data.soxr)
231 {
232 soxr_delete(jo_data.soxr);
233 }
234 jo_data.soxr = soxr_create(
235 jo_data.sample_rate,
236 jack_get_sample_rate(jo_data.client),
237 jo_data.receiver_format.channels,
238 NULL, &io_spec, NULL, NULL );
239 if (!jo_data.soxr )
240 {
241 fprintf(stderr, "failed to initialize resampler");
242 return 1;
243 }
244 return 0;
245}
246
247
248static const char *channel_index_to_name(uint8_t channel)
249{
250 const char *channel_name;
251 switch (channel) {
252 case 0: channel_name = "Front Left"; break;
253 case 1: channel_name = "Front Right"; break;
254 case 2: channel_name = "Front Center"; break;
255 case 3: channel_name = "LFE / Subwoofer"; break;
256 case 4: channel_name = "Rear Left"; break;
257 case 5: channel_name = "Rear Right"; break;
258 case 6: channel_name = "Front-Left Center"; break;
259 case 7: channel_name = "Front-Right Center"; break;
260 case 8: channel_name = "Rear Center"; break;
261 case 9: channel_name = "Side Left"; break;
262 case 10: channel_name = "Side Right"; break;
263 default: channel_name = "unknown_channel";
264 }
265
266 return channel_name;
267}
268
269
270static int init_channels()
271{
272 free(jo_data.output_ports);
273 jo_data.output_ports = malloc(sizeof(jack_port_t*) * jo_data.receiver_format.channels);
274 free(jo_data.buffers);
275 jo_data.buffers = malloc(sizeof(jack_default_audio_sample_t*) * jo_data.receiver_format.channels);
276 for (int i = 0 ; i < jo_data.receiver_format.channels; ++i)
277 {
278 if ( (jo_data.receiver_format.channel_map >> i) & 1 )
279 {
280 const char *port_name = channel_index_to_name(i);
281 jo_data.output_ports[i] = jack_port_register(jo_data.client,
282 port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
283 printf("registered jack port '%s' for channel %u\n", port_name, i);
284
285 if (jo_data.output_ports[i] == NULL)
286 {
287 fprintf(stderr, "no more JACK ports available\n");
288 return 1;
289 }
290 }
291 }
292 return 0;
293}
294
295
296static int connect_ports()
297{
298 int num_output_ports = 0;
299
300 const char **ports = jack_get_ports(jo_data.client, NULL, NULL, JackPortIsPhysical|JackPortIsInput);
301 if (ports == NULL)
302 {
303 fprintf(stderr, "no physical playback ports\n");
304 return 1;
305 }
306
307 // connect the receiver if there is enough output ports
308 for ( ; ports[num_output_ports] && (!strstr(ports[num_output_ports],"midi")); ++num_output_ports)
309 {
310 ;
311 }
312
313
314 if (num_output_ports >= jo_data.receiver_format.channels)
315 {
316 for (int port = 0; port < jo_data.receiver_format.channels && ports[port] ; ++port)
317 {
318 if (jack_connect(jo_data.client, jack_port_name(jo_data.output_ports[port]), ports[port]))
319 {
320 fprintf(stderr, "cannot connect to output port %s\n", ports[port]);
321 }
322 else
323 {
324 printf("channel %u connected to output port %s\n", port, ports[port]);
325 }
326
327 }
328 }
329
330 jack_free(ports);
331}
332
333
334static int process_source_data(receiver_data_t *data)
335{
336 // per channel!
337 size_t src_nsamples;
338 size_t dst_nsamples;
339 size_t idone;
340 size_t odone;
341
342 size_t resample_ideal_buf_size;
343 soxr_error_t soxr_error;
344
345 // (re)allocate a resampling buffer if needed
346 src_nsamples = data->audio_size / (jo_data.receiver_format.sample_size >> 3) / jo_data.receiver_format.channels;
347 dst_nsamples = src_nsamples * jack_get_sample_rate(jo_data.client) / jo_data.sample_rate + 1;
348 resample_ideal_buf_size = dst_nsamples*jo_data.receiver_format.channels;
349 if (jo_data.resample_buffer_size < resample_ideal_buf_size)
350 {
351 free(jo_data.resample_buffer);
352 jo_data.resample_buffer = malloc(resample_ideal_buf_size* sizeof(jack_default_audio_sample_t));
353 jo_data.resample_buffer_size = resample_ideal_buf_size;
354 }
355
356 soxr_error = soxr_process(jo_data.soxr, data->audio, src_nsamples, &idone, jo_data.resample_buffer, dst_nsamples, &odone);
357 if (soxr_error != NULL)
358 {
359 fprintf(stderr,"soxr error : %s\n", soxr_strerror(soxr_error));
360 return 1;
361 }
362
363 for (int i = 0; i < odone*jo_data.receiver_format.channels; ++i)
364 {
365 ringbuffer_push(&jo_data.rb, jo_data.resample_buffer[i]);
366 }
367
368 return 0;
369}
370
371
372
373
374
375int jack_process(jack_nframes_t nframes, void *arg)
376{
377 uint8_t channels = jo_data.receiver_format.channels;
378 uint32_t total_nframes = nframes * channels;
379 uint32_t read_frames = ringbuffer_size(&jo_data.rb);
380
381 // wait until rb fills up a bit to prevent early underruns
382 if (read_frames < (total_nframes + 200))
383 {
384 return 0;
385 }
386
387 if (total_nframes <= read_frames)
388 {
389 read_frames = total_nframes;
390 }
391 else
392 {
393 // JACK wants more than we can fullfill
394 fprintf(stderr, "jack_process: underflow!! read_frames=%u nframes=%u\n", read_frames, total_nframes);
395 }
396
397
398 if (read_frames > 0)
399 {
400 for (int port = 0; port < channels; ++port)
401 {
402 jo_data.buffers[port] = jack_port_get_buffer(jo_data.output_ports[port], nframes);
403 }
404
405 // transfer samples from ringbuffer to JACK port buffers
406 for (uint32_t sample = 0; sample < read_frames /channels; ++sample)
407 {
408 for (int port = 0; port < channels; ++port)
409 {
410 *jo_data.buffers[port] = ringbuffer_pop(&jo_data.rb);
411 jo_data.buffers[port]++;
412
413 }
414 }
415 }
416
417 return 0;
418}
419
Receivers/unix/jack.h+9−0
@@ -0,0 +1,9 @@
1#ifndef JACK_H
2#define JACK_H
3
4#include "scream.h"
5
6int jack_output_init(char *stream_name);
7int jack_output_send(receiver_data_t *data);
8
9#endif
Receivers/unix/scream.c+28−6
@@ -32,6 +32,12 @@ · 31 unmodified lines
32#include "pcap.h"32#include "pcap.h"
33#endif33#endif
34 34
35
36#if JACK_ENABLE
37#include "jack.h"
38#endif
39
40
35static void show_usage(const char *arg0)41static void show_usage(const char *arg0)
36{42{
37 fprintf(stderr, "\n");43 fprintf(stderr, "\n");
@@ -51,10 +57,11 @@ static void show_usage(const char *arg0) · 13 unmodified lines
51 fprintf(stderr, " -m <ivshmem device path> : Use shared memory device.\n");57 fprintf(stderr, " -m <ivshmem device path> : Use shared memory device.\n");
52 fprintf(stderr, " -P : Use libpcap to sniff the packets.\n");58 fprintf(stderr, " -P : Use libpcap to sniff the packets.\n");
53 fprintf(stderr, "\n");59 fprintf(stderr, "\n");
54 fprintf(stderr, " -o pulse|alsa|raw : Send audio to PulseAudio, ALSA, or stdout.\n");60 fprintf(stderr, " -o pulse|alsa|jack|raw : Send audio to PulseAudio, ALSA, Jack or stdout.\n");
55 fprintf(stderr, " -d <device> : ALSA device name. 'default' if not specified.\n");61 fprintf(stderr, " -d <device> : ALSA device name. 'default' if not specified.\n");
56 fprintf(stderr, " -s <sink name> : Pulseaudio sink name.\n");62 fprintf(stderr, " -s <sink name> : Pulseaudio sink name.\n");
57 fprintf(stderr, " -n <stream name> : Pulseaudio stream name/description.\n");63 fprintf(stderr, " -n <stream name> : Pulseaudio stream name/description.\n");
64 fprintf(stderr, " -n <client name> : JACK client name.\n");
58 fprintf(stderr, " -t <latency> : Target latency in milliseconds. Defaults to 50ms.\n");65 fprintf(stderr, " -t <latency> : Target latency in milliseconds. Defaults to 50ms.\n");
59 fprintf(stderr, " Only relevant for PulseAudio and ALSA output.\n");66 fprintf(stderr, " Only relevant for PulseAudio and ALSA output.\n");
60 fprintf(stderr, "\n");67 fprintf(stderr, "\n");
@@ -130,8 +137,9 @@ int main(int argc, char*argv[]) { · 69 unmodified lines
130 char *output = NULL;137 char *output = NULL;
131 const char* interface_name = NULL;138 const char* interface_name = NULL;
132 char *alsa_device = "default";139 char *alsa_device = "default";
133 char *sink = NULL;140 char *pa_sink = NULL;
134 char *stream_name = "Audio";141 char *pa_stream_name = "Audio";
142 char *jack_client_name = "scream";
135 int target_latency_ms = 50;143 int target_latency_ms = 50;
136 in_addr_t interface = INADDR_ANY;144 in_addr_t interface = INADDR_ANY;
137 uint16_t port = DEFAULT_PORT;145 uint16_t port = DEFAULT_PORT;
@@ -162,16 +170,18 @@ int main(int argc, char*argv[]) { · 24 unmodified lines
162 output = strdup(optarg);170 output = strdup(optarg);
163 if (strcmp(output,"pulse") == 0) output_mode = Pulseaudio;171 if (strcmp(output,"pulse") == 0) output_mode = Pulseaudio;
164 else if (strcmp(output,"alsa") == 0) output_mode = Alsa;172 else if (strcmp(output,"alsa") == 0) output_mode = Alsa;
173 else if (strcmp(output,"jack") == 0) output_mode = Jack;
165 else if (strcmp(output,"raw") == 0) output_mode = Raw;174 else if (strcmp(output,"raw") == 0) output_mode = Raw;
166 break;175 break;
167 case 'd':176 case 'd':
168 alsa_device = strdup(optarg);177 alsa_device = strdup(optarg);
169 break;178 break;
170 case 's':179 case 's':
171 sink = strdup(optarg);180 pa_sink = strdup(optarg);
172 break;181 break;
173 case 'n':182 case 'n':
174 stream_name = strdup(optarg);183 pa_stream_name = strdup(optarg);
184 jack_client_name = pa_stream_name;
175 break;185 break;
176 case 't':186 case 't':
177 target_latency_ms = atoi(optarg);187 target_latency_ms = atoi(optarg);
@@ -203,7 +213,7 @@ int main(int argc, char*argv[]) { · 25 unmodified lines
203 case Pulseaudio:213 case Pulseaudio:
204#if PULSEAUDIO_ENABLE214#if PULSEAUDIO_ENABLE
205 if (verbosity) fprintf(stderr, "Using Pulseaudio output\n");215 if (verbosity) fprintf(stderr, "Using Pulseaudio output\n");
206 if (pulse_output_init(target_latency_ms, sink, stream_name) != 0) {216 if (pulse_output_init(target_latency_ms, pa_sink, pa_stream_name) != 0) {
207 return 1;217 return 1;
208 }218 }
209 output_send_fn = pulse_output_send;219 output_send_fn = pulse_output_send;
@@ -222,6 +232,18 @@ int main(int argc, char*argv[]) { · 12 unmodified lines
222#else232#else
223 fprintf(stderr, "%s compiled without ALSA support. Aborting\n", argv[0]);233 fprintf(stderr, "%s compiled without ALSA support. Aborting\n", argv[0]);
224 return 1;234 return 1;
235#endif
236 break;
237 case Jack:
238#if JACK_ENABLE
239 if (verbosity) fprintf(stderr, "Using JACK output\n");
240 if (jack_output_init(jack_client_name) != 0) {
241 return 1;
242 }
243 output_send_fn = jack_output_send;
244#else
245 fprintf(stderr, "%s compiled without JACK support. Aborting\n", argv[0]);
246 return 1;
225#endif247#endif
226 break;248 break;
227 case Raw:249 case Raw:
Receivers/unix/scream.h+1−1
@@ -8,7 +8,7 @@ enum receiver_type { · 7 unmodified lines
8};8};
9 9
10enum output_type {10enum output_type {
11 Raw, Alsa, Pulseaudio11 Raw, Alsa, Pulseaudio, Jack
12};12};
13 13
14typedef struct receiver_format {14typedef struct receiver_format {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t