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
3333 endif ()
3434 endif ()
3535
36+# find JACK
37+option(JACK_ENABLE "Enable JACK" ON)
38+if (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 ()
49+endif ()
50+
51+
3652 # find libpcap
3753 option(PCAP_ENABLE "Enable PCAP" ON)
3854 if (PCAP_ENABLE)
Receivers/unix/config.h.in+1−0
@@ -1,3 +1,4 @@
11 #cmakedefine01 PULSEAUDIO_ENABLE
22 #cmakedefine01 ALSA_ENABLE
3+#cmakedefine01 JACK_ENABLE
34 #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+
17+typedef jack_default_audio_sample_t ringbuffer_element_t;
18+#define RINGBUFFER_SIZE 8192
19+struct ringbuffer_t
20+{
21+ ringbuffer_element_t elements[RINGBUFFER_SIZE];
22+ uint32_t read_pos;
23+ uint32_t write_pos;
24+};
25+
26+
27+uint32_t ringbuffer_mask(uint32_t val)
28+{
29+ return val & (RINGBUFFER_SIZE - 1u);
30+}
31+
32+void ringbuffer_init(struct ringbuffer_t *rb)
33+{
34+ rb->read_pos = 0u;
35+ rb->write_pos = 0u;
36+}
37+
38+uint32_t ringbuffer_size(struct ringbuffer_t *rb)
39+{
40+ return rb->write_pos - rb->read_pos;
41+}
42+
43+int ringbuffer_empty(struct ringbuffer_t *rb)
44+{
45+ return ringbuffer_size(rb) == 0u;
46+}
47+
48+int ringbuffer_full(struct ringbuffer_t *rb)
49+{
50+ return ringbuffer_size(rb) == RINGBUFFER_SIZE;
51+}
52+
53+void 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+
66+ringbuffer_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+
81+static 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+}
93+jo_data;
94+
95+
96+
97+static int init_resampler();
98+static int init_channels();
99+static int connect_ports();
100+static int process_source_data();
101+
102+// JACK realtime process callback
103+int jack_process(jack_nframes_t nframes, void *arg);
104+
105+
106+
107+int 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+
154+int 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+
217+static 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+
248+static 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+
270+static 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+
296+static 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+
334+static 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+
375+int 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+
6+int jack_output_init(char *stream_name);
7+int jack_output_send(receiver_data_t *data);
8+
9+#endif
Receivers/unix/scream.c+28−6
@@ -32,6 +32,12 @@ · 31 unmodified lines
3232 #include "pcap.h"
3333 #endif
3434
35+
36+#if JACK_ENABLE
37+#include "jack.h"
38+#endif
39+
40+
3541 static void show_usage(const char *arg0)
3642 {
3743 fprintf(stderr, "\n");
@@ -51,10 +57,11 @@ static void show_usage(const char *arg0) · 13 unmodified lines
5157 fprintf(stderr, " -m <ivshmem device path> : Use shared memory device.\n");
5258 fprintf(stderr, " -P : Use libpcap to sniff the packets.\n");
5359 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");
5561 fprintf(stderr, " -d <device> : ALSA device name. 'default' if not specified.\n");
5662 fprintf(stderr, " -s <sink name> : Pulseaudio sink name.\n");
5763 fprintf(stderr, " -n <stream name> : Pulseaudio stream name/description.\n");
64+ fprintf(stderr, " -n <client name> : JACK client name.\n");
5865 fprintf(stderr, " -t <latency> : Target latency in milliseconds. Defaults to 50ms.\n");
5966 fprintf(stderr, " Only relevant for PulseAudio and ALSA output.\n");
6067 fprintf(stderr, "\n");
@@ -130,8 +137,9 @@ int main(int argc, char*argv[]) { · 69 unmodified lines
130137 char *output = NULL;
131138 const char* interface_name = NULL;
132139 char *alsa_device = "default";
133− char *sink = NULL;
134− char *stream_name = "Audio";
140+ char *pa_sink = NULL;
141+ char *pa_stream_name = "Audio";
142+ char *jack_client_name = "scream";
135143 int target_latency_ms = 50;
136144 in_addr_t interface = INADDR_ANY;
137145 uint16_t port = DEFAULT_PORT;
@@ -162,16 +170,18 @@ int main(int argc, char*argv[]) { · 24 unmodified lines
162170 output = strdup(optarg);
163171 if (strcmp(output,"pulse") == 0) output_mode = Pulseaudio;
164172 else if (strcmp(output,"alsa") == 0) output_mode = Alsa;
173+ else if (strcmp(output,"jack") == 0) output_mode = Jack;
165174 else if (strcmp(output,"raw") == 0) output_mode = Raw;
166175 break;
167176 case 'd':
168177 alsa_device = strdup(optarg);
169178 break;
170179 case 's':
171− sink = strdup(optarg);
180+ pa_sink = strdup(optarg);
172181 break;
173182 case 'n':
174− stream_name = strdup(optarg);
183+ pa_stream_name = strdup(optarg);
184+ jack_client_name = pa_stream_name;
175185 break;
176186 case 't':
177187 target_latency_ms = atoi(optarg);
@@ -203,7 +213,7 @@ int main(int argc, char*argv[]) { · 25 unmodified lines
203213 case Pulseaudio:
204214 #if PULSEAUDIO_ENABLE
205215 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) {
207217 return 1;
208218 }
209219 output_send_fn = pulse_output_send;
@@ -222,6 +232,18 @@ int main(int argc, char*argv[]) { · 12 unmodified lines
222232 #else
223233 fprintf(stderr, "%s compiled without ALSA support. Aborting\n", argv[0]);
224234 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;
225247 #endif
226248 break;
227249 case Raw:
Receivers/unix/scream.h+1−1
@@ -8,7 +8,7 @@ enum receiver_type { · 7 unmodified lines
88 };
99
1010 enum output_type {
11− Raw, Alsa, Pulseaudio
11+ Raw, Alsa, Pulseaudio, Jack
1212 };
1313
1414 typedef 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