History

add build system and refactor into logical units

Sander van Grieken committed 6 years ago

Commit a5a93661 parent 2d098f58 files · +548 −0Browse files

Unified · Split

8 files
  1. Receivers/linux/CMakeLists.txt +26 −0
  2. Receivers/linux/config.h.in +2 −0
  3. Receivers/linux/network.c +40 −0
  4. Receivers/linux/network.h +26 −0
  5. Receivers/linux/scream.c +329 −0
  6. Receivers/linux/scream.h +22 −0
  7. Receivers/linux/shmem.c +68 −0
  8. Receivers/linux/shmem.h +35 −0
Receivers/linux/CMakeLists.txt+26−0
@@ -0,0 +1,26 @@
1+cmake_minimum_required(VERSION 3.7)
2+
3+project(scream)
4+
5+add_executable(${PROJECT_NAME} scream.c network.c shmem.c)
6+
7+# find pulseaudio
8+find_package(PulseAudio)
9+if (PULSEAUDIO_FOUND)
10+ include_directories(${PULSEAUDIO_INCLUDE_DIRS})
11+ target_link_libraries(${PROJECT_NAME} pulse pulse-simple)
12+ option(PULSEAUDIO_ENABLE "Enable PulseAudio" ON)
13+endif (PULSEAUDIO_FOUND)
14+
15+# find ALSA
16+find_package(PkgConfig)
17+pkg_check_modules(PC_ALSA alsa)
18+
19+if (PC_ALSA_FOUND)
20+ include_directories(${PC_ALSA_INCLUDEDIR})
21+ target_link_libraries(${PROJECT_NAME} ${PC_ALSA_LIBRARIES})
22+ option(ALSA_ENABLE "Enable ALSA" ON)
23+endif (PC_ALSA_FOUND)
24+
25+configure_file(config.h.in config.h)
26+target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_BINARY_DIR}")
Receivers/linux/config.h.in+2−0
@@ -0,0 +1,2 @@
1+#cmakedefine PULSEAUDIO_ENABLE
2+#cmakedefine ALSA_ENABLE
Receivers/linux/network.c+40−0
@@ -0,0 +1,40 @@
1+#include "network.h"
2+
3+void* init_network(enum receiver_type receiver_mode, in_addr_t interface, int port, char* multicast_group)
4+{
5+ rctx_network_t* ctx = malloc(sizeof(rctx_network_t));
6+ ctx->sockfd = socket(AF_INET,SOCK_DGRAM,0);
7+
8+ memset((void *)&(ctx->servaddr), 0, sizeof(ctx->servaddr));
9+ ctx->servaddr.sin_family = AF_INET;
10+ ctx->servaddr.sin_addr.s_addr = (receiver_mode == Unicast) ? interface : htonl(INADDR_ANY);
11+ ctx->servaddr.sin_port = htons(port);
12+ bind(ctx->sockfd, (struct sockaddr *)&ctx->servaddr, sizeof(ctx->servaddr));
13+
14+ if (receiver_mode == Multicast) {
15+ ctx->imreq.imr_multiaddr.s_addr = inet_addr(multicast_group ? multicast_group : DEFAULT_MULTICAST_GROUP);
16+ ctx->imreq.imr_interface.s_addr = interface;
17+
18+ setsockopt(ctx->sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
19+ (const void *)&ctx->imreq, sizeof(struct ip_mreq));
20+ }
21+
22+ return ctx;
23+}
24+
25+void rcv_network(void* network_ctx, receiver_data_t* receiver_data)
26+{
27+ rctx_network_t* ctx = (rctx_network_t*)network_ctx;
28+ ssize_t n = 0;
29+
30+ while (n < HEADER_SIZE) {
31+ n = recvfrom(ctx->sockfd, ctx->buf, MAX_SO_PACKETSIZE, 0, NULL, 0);
32+ }
33+ receiver_data->format.sample_rate = ctx->buf[0];
34+ receiver_data->format.sample_size = ctx->buf[1];
35+ receiver_data->format.channels = ctx->buf[2];
36+ receiver_data->format.channel_map = (ctx->buf[3] << 8) | ctx->buf[4];
37+ receiver_data->audio_size = n - HEADER_SIZE;
38+ receiver_data->audio = &ctx->buf[5];
39+}
40+
Receivers/linux/network.h+26−0
@@ -0,0 +1,26 @@
1+#ifndef NETWORK_H
2+#define NETWORK_H
3+
4+#include <arpa/inet.h>
5+#include <stdlib.h>
6+#include <string.h>
7+
8+#include "scream.h"
9+
10+#define DEFAULT_MULTICAST_GROUP "239.255.77.77"
11+#define DEFAULT_PORT 4010
12+
13+#define HEADER_SIZE 5
14+#define MAX_SO_PACKETSIZE 1152+HEADER_SIZE
15+
16+typedef struct rctx_network {
17+ int sockfd;
18+ struct sockaddr_in servaddr;
19+ struct ip_mreq imreq;
20+ unsigned char buf[MAX_SO_PACKETSIZE];
21+} rctx_network_t;
22+
23+void* init_network(enum receiver_type receiver_mode, in_addr_t interface, int port, char* multicast_group);
24+void rcv_network(void* network_ctx, receiver_data_t* receiver_data);
25+
26+#endif
Receivers/linux/scream.c+329−0
@@ -0,0 +1,329 @@
1+#include "config.h"
2+
3+#include <stdio.h>
4+#include <stdlib.h>
5+#include <string.h>
6+
7+#ifdef PULSEAUDIO_ENABLE
8+#include <pulse/simple.h>
9+#include <pulse/error.h>
10+#endif
11+
12+#include <unistd.h>
13+#include <fcntl.h>
14+
15+#include <sys/ioctl.h>
16+#include <sys/stat.h>
17+#include <sys/mman.h>
18+#include <sys/resource.h>
19+#include <net/if.h>
20+
21+#include "scream.h"
22+#include "network.h"
23+#include "shmem.h"
24+
25+static void show_usage(const char *arg0)
26+{
27+ fprintf(stderr, "\n");
28+ fprintf(stderr, "Usage: %s [-u] [-p <port>] [-i <iface>] [-g <group>]\n", arg0);
29+ fprintf(stderr, "\n");
30+ fprintf(stderr, " All command line options are optional. Default is to use\n");
31+ fprintf(stderr, " multicast with group address 239.255.77.77, port 4010.\n");
32+ fprintf(stderr, "\n");
33+ fprintf(stderr, " -u : Use unicast instead of multicast.\n");
34+ fprintf(stderr, " -p <port> : Use <port> instead of default port 4010.\n");
35+ fprintf(stderr, " Applies to both multicast and unicast.\n");
36+ fprintf(stderr, " -i <iface> : Use local interface <iface>. Either the IP\n");
37+ fprintf(stderr, " or the interface name can be specified. In\n");
38+ fprintf(stderr, " multicast mode, uses this interface for IGMP.\n");
39+ fprintf(stderr, " In unicast, binds to this interface only.\n");
40+ fprintf(stderr, " -g <group> : Multicast group address. Multicast mode only.\n");
41+ fprintf(stderr, " -m <ivshmem device path> : Use shared memory device.\n");
42+ fprintf(stderr, " -t <latency> : Target latency in milliseconds. Defaults to 50ms.\n");
43+ fprintf(stderr, "\n");
44+ exit(1);
45+}
46+
47+
48+static in_addr_t get_interface(const char *name)
49+{
50+ int sockfd = socket(AF_INET,SOCK_DGRAM,0);
51+ struct ifreq ifr;
52+ in_addr_t addr = inet_addr(name);
53+ struct if_nameindex *ni;
54+ int i;
55+
56+ if (addr != INADDR_NONE) {
57+ return addr;
58+ }
59+
60+ if (strlen(name) >= sizeof(ifr.ifr_name)) {
61+ fprintf(stderr, "Too long interface name: %s\n\n", name);
62+ goto error_exit;
63+ }
64+ strcpy(ifr.ifr_name, name);
65+
66+ sockfd = socket(AF_INET,SOCK_DGRAM,0);
67+ if (ioctl(sockfd, SIOCGIFADDR, &ifr) != 0) {
68+ fprintf(stderr, "Invalid interface: %s\n\n", name);
69+ goto error_exit;
70+ }
71+ close(sockfd);
72+ return ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr;
73+
74+error_exit:
75+ ni = if_nameindex();
76+ fprintf(stderr, "Available interfaces:\n");
77+ for (i = 0; ni[i].if_name != NULL; i++) {
78+ strcpy(ifr.ifr_name, ni[i].if_name);
79+ if (ioctl(sockfd, SIOCGIFADDR, &ifr) == 0) {
80+ fprintf(stderr, " %-10s (%s)\n", ni[i].if_name, inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));
81+ }
82+ }
83+ exit(1);
84+}
85+
86+
87+int main(int argc, char*argv[]) {
88+ int error;
89+
90+ // function pointer definition for receiver
91+ void (*receiver_rcv_fn)(void* ctx, receiver_data_t* receiver_data);
92+ void *receiver_ctx;
93+ receiver_data_t receiver_data;
94+
95+ pa_simple *s;
96+ pa_sample_spec ss;
97+ pa_channel_map channel_map;
98+ pa_buffer_attr buffer_attr;
99+
100+ int opt;
101+ unsigned char cur_sample_rate = 0;
102+ unsigned char cur_sample_size = 0;
103+ unsigned char cur_channels = 2;
104+ unsigned char cur_channel_map_lsb = 0x03; // stereo
105+ unsigned char cur_channel_map_msb = 0;
106+ uint16_t cur_channel_map = (cur_channel_map_msb << 8) | cur_channel_map_lsb;
107+
108+ // Command line options
109+ enum receiver_type receiver_mode = Multicast;
110+
111+ char *multicast_group = NULL;
112+ char *ivshmem_device = NULL;
113+ int target_latency_ms = 50;
114+ in_addr_t interface = INADDR_ANY;
115+ uint16_t port = DEFAULT_PORT;
116+
117+ while ((opt = getopt(argc, argv, "i:g:p:m:t:uh")) != -1) {
118+ switch (opt) {
119+ case 'i':
120+ interface = get_interface(optarg);
121+ break;
122+ case 'p':
123+ port = atoi(optarg);
124+ if (!port) show_usage(argv[0]);
125+ break;
126+ case 'u':
127+ receiver_mode = Unicast;
128+ break;
129+ case 'g':
130+ multicast_group = strdup(optarg);
131+ break;
132+ case 'm':
133+ receiver_mode = SharedMem;
134+ ivshmem_device = strdup(optarg);
135+ break;
136+ case 't':
137+ target_latency_ms = atoi(optarg);
138+ if (target_latency_ms < 0) show_usage(argv[0]);
139+ break;
140+ default:
141+ show_usage(argv[0]);
142+ }
143+ }
144+ if (optind < argc) {
145+ fprintf(stderr, "Expected argument after options\n");
146+ show_usage(argv[0]);
147+ }
148+
149+ // Opportunistic call to renice us, so we can keep up under
150+ // higher load conditions. This may fail when run as non-root.
151+ setpriority(PRIO_PROCESS, 0, -11);
152+
153+ // set application icon
154+ setenv("PULSE_PROP_application.icon_name", "audio-card", 0);
155+
156+ // map to stereo, it's the default number of channels
157+ pa_channel_map_init_stereo(&channel_map);
158+
159+ // Start with base default format, will switch to actual format later
160+ ss.format = PA_SAMPLE_S16LE;
161+ ss.rate = 44100;
162+ ss.channels = cur_channels;
163+
164+ // set buffer size for requested latency
165+ buffer_attr.maxlength = (uint32_t)-1;
166+ buffer_attr.tlength = pa_usec_to_bytes((pa_usec_t)target_latency_ms * 1000u, &ss);
167+ buffer_attr.prebuf = (uint32_t)-1;
168+ buffer_attr.minreq = (uint32_t)-1;
169+ buffer_attr.fragsize = (uint32_t)-1;
170+
171+ s = pa_simple_new(NULL,
172+ "Scream",
173+ PA_STREAM_PLAYBACK,
174+ NULL,
175+ "Audio",
176+ &ss,
177+ &channel_map,
178+ &buffer_attr,
179+ &error
180+ );
181+ if (!s) {
182+ fprintf(stderr, "Unable to connect to PulseAudio. %s\n", pa_strerror(error));
183+ goto BAIL;
184+ }
185+
186+ // initialize receiver
187+ switch (receiver_mode) {
188+ case SharedMem:
189+ printf("Starting IVSHMEM receiver\n");
190+ receiver_ctx = init_shmem(ivshmem_device);
191+ receiver_rcv_fn = rcv_shmem;
192+ break;
193+ case Unicast:
194+ case Multicast:
195+ default:
196+ printf("Starting %s receiver\n", receiver_mode == Unicast ? "unicast" : "multicast");
197+ receiver_ctx = init_network(receiver_mode, interface, port, multicast_group);
198+ receiver_rcv_fn = rcv_network;
199+ break;
200+ }
201+
202+
203+ for (;;) {
204+ receiver_rcv_fn(receiver_ctx, &receiver_data);
205+
206+ if (cur_sample_rate != receiver_data.format.sample_rate ||
207+ cur_sample_size != receiver_data.format.sample_size ||
208+ cur_channels != receiver_data.format.channels ||
209+ cur_channel_map != receiver_data.format.channel_map) {
210+ cur_sample_rate = receiver_data.format.sample_rate;
211+ cur_sample_size = receiver_data.format.sample_size;
212+ cur_channels = receiver_data.format.channels;
213+ cur_channel_map = receiver_data.format.channel_map;
214+
215+ ss.channels = cur_channels;
216+
217+ ss.rate = ((cur_sample_rate >= 128) ? 44100 : 48000) * (cur_sample_rate % 128);
218+ switch (cur_sample_size) {
219+ case 16: ss.format = PA_SAMPLE_S16LE; break;
220+ case 24: ss.format = PA_SAMPLE_S24LE; break;
221+ case 32: ss.format = PA_SAMPLE_S32LE; break;
222+ default:
223+ printf("Unsupported sample size %hhu, not playing until next format switch.\n", cur_sample_size);
224+ ss.rate = 0;
225+ }
226+
227+ if (cur_channels == 1) {
228+ pa_channel_map_init_mono(&channel_map);
229+ }
230+ else if (cur_channels == 2) {
231+ pa_channel_map_init_stereo(&channel_map);
232+ }
233+ else {
234+ pa_channel_map_init(&channel_map);
235+ channel_map.channels = cur_channels;
236+ // k is the key to map a windows SPEAKER_* position to a PA_CHANNEL_POSITION_*
237+ // it goes from 0 (SPEAKER_FRONT_LEFT) up to 10 (SPEAKER_SIDE_RIGHT) following the order in ksmedia.h
238+ // the SPEAKER_TOP_* values are not used
239+ int k = -1;
240+ for (int i=0; i<cur_channels; i++) {
241+ for (int j = k+1; j<=10; j++) {// check the channel map bit by bit from lsb to msb, starting from were we left on the previous step
242+ if ((cur_channel_map >> j) & 0x01) {// if the bit in j position is set then we have the key for this channel
243+ k = j;
244+ break;
245+ }
246+ }
247+ // map the key value to a pulseaudio channel position
248+ switch (k) {
249+ case 0: channel_map.map[i] = PA_CHANNEL_POSITION_LEFT; break;
250+ case 1: channel_map.map[i] = PA_CHANNEL_POSITION_RIGHT; break;
251+ case 2: channel_map.map[i] = PA_CHANNEL_POSITION_CENTER; break;
252+ case 3: channel_map.map[i] = PA_CHANNEL_POSITION_LFE; break;
253+ case 4: channel_map.map[i] = PA_CHANNEL_POSITION_REAR_LEFT; break;
254+ case 5: channel_map.map[i] = PA_CHANNEL_POSITION_REAR_RIGHT; break;
255+ case 6: channel_map.map[i] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; break;
256+ case 7: channel_map.map[i] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; break;
257+ case 8: channel_map.map[i] = PA_CHANNEL_POSITION_REAR_CENTER; break;
258+ case 9: channel_map.map[i] = PA_CHANNEL_POSITION_SIDE_LEFT; break;
259+ case 10: channel_map.map[i] = PA_CHANNEL_POSITION_SIDE_RIGHT; break;
260+ default:
261+ // center is a safe default, at least it's balanced. This shouldn't happen, but it's better to have a fallback
262+ printf("Channel %i coult not be mapped. Falling back to 'center'.\n", i);
263+ channel_map.map[i] = PA_CHANNEL_POSITION_CENTER;
264+ }
265+ const char *channel_name;
266+ switch (k) {
267+ case 0: channel_name = "Front Left"; break;
268+ case 1: channel_name = "Front Right"; break;
269+ case 2: channel_name = "Front Center"; break;
270+ case 3: channel_name = "LFE / Subwoofer"; break;
271+ case 4: channel_name = "Rear Left"; break;
272+ case 5: channel_name = "Rear Right"; break;
273+ case 6: channel_name = "Front-Left Center"; break;
274+ case 7: channel_name = "Front-Right Center"; break;
275+ case 8: channel_name = "Rear Center"; break;
276+ case 9: channel_name = "Side Left"; break;
277+ case 10: channel_name = "Side Right"; break;
278+ default:
279+ channel_name = "Unknown. Setted to Center.";
280+ }
281+ printf("Channel %i mapped to %s\n", i, channel_name);
282+ }
283+ }
284+ // this is for extra safety
285+ if (!pa_channel_map_valid(&channel_map)) {
286+ printf("Invalid channel mapping, falling back to CHANNEL_MAP_WAVEEX.\n");
287+ pa_channel_map_init_extend(&channel_map, cur_channels, PA_CHANNEL_MAP_WAVEEX);
288+ }
289+ if (!pa_channel_map_compatible(&channel_map, &ss)){
290+ printf("Incompatible channel mapping.\n");
291+ ss.rate = 0;
292+ }
293+
294+ if (ss.rate > 0) {
295+ // sample spec has changed, so the playback buffer size for the requested latency must be recalculated as well
296+ buffer_attr.tlength = pa_usec_to_bytes((pa_usec_t)target_latency_ms * 1000, &ss);
297+
298+ if (s) pa_simple_free(s);
299+ s = pa_simple_new(NULL,
300+ "Scream",
301+ PA_STREAM_PLAYBACK,
302+ NULL,
303+ "Audio",
304+ &ss,
305+ &channel_map,
306+ &buffer_attr,
307+ NULL
308+ );
309+ if (s) {
310+ printf("Switched format to sample rate %u, sample size %hhu and %u channels.\n", ss.rate, cur_sample_size, cur_channels);
311+ }
312+ else {
313+ printf("Unable to open PulseAudio with sample rate %u, sample size %hhu and %u channels, not playing until next format switch.\n", ss.rate, cur_sample_size, cur_channels);
314+ ss.rate = 0;
315+ }
316+ }
317+ }
318+
319+ if (!ss.rate) continue;
320+ if (pa_simple_write(s, receiver_data.audio, receiver_data.audio_size, &error) < 0) {
321+ fprintf(stderr, "pa_simple_write() failed: %s\n", pa_strerror(error));
322+ goto BAIL;
323+ }
324+ }
325+
326+ BAIL:
327+ if (s) pa_simple_free(s);
328+ return 1;
329+};
Receivers/linux/scream.h+22−0
@@ -0,0 +1,22 @@
1+#ifndef SCREAM_H
2+#define SCREAM_H
3+
4+enum receiver_type {
5+ Unicast, Multicast, SharedMem
6+};
7+
8+typedef struct receiver_format {
9+ unsigned char sample_rate;
10+ unsigned char sample_size;
11+ unsigned char channels;
12+ uint16_t channel_map;
13+} receiver_format_t;
14+
15+typedef struct receiver_data {
16+ receiver_format_t format;
17+ unsigned int audio_size;
18+ unsigned char* audio;
19+} receiver_data_t;
20+
21+
22+#endif
Receivers/linux/shmem.c+68−0
@@ -0,0 +1,68 @@
1+#include "shmem.h"
2+
3+void* init_shmem(char* shmem_device_file)
4+{
5+ rctx_shmem_t* ctx = malloc(sizeof(rctx_shmem_t));
6+
7+ struct stat st;
8+ if (stat(shmem_device_file, &st) < 0) {
9+ fprintf(stderr, "Failed to stat the shared memory file: %s\n", shmem_device_file);
10+ exit(2);
11+ }
12+
13+ int shmFD = open(shmem_device_file, O_RDONLY);
14+ if (shmFD < 0) {
15+ fprintf(stderr, "Failed to open the shared memory file: %s\n", shmem_device_file);
16+ exit(3);
17+ }
18+
19+ ctx->mmap = mmap(0, st.st_size, PROT_READ, MAP_SHARED, shmFD, 0);
20+ if (ctx->mmap == MAP_FAILED) {
21+ fprintf(stderr, "Failed to map the shared memory file: %s\n", shmem_device_file);
22+ close(shmFD);
23+ exit(4);
24+ }
25+
26+ struct shmheader *header = (struct shmheader*)ctx->mmap;
27+ ctx->read_idx = header->write_idx;
28+
29+ return ctx;
30+}
31+
32+void rcv_shmem(void* shmem_ctx, receiver_data_t* receiver_data)
33+{
34+ rctx_shmem_t* ctx = (rctx_shmem_t*)shmem_ctx;
35+ struct shmheader *header = (struct shmheader*)ctx->mmap;
36+
37+ int valid = 0;
38+ do {
39+ if (header->magic != 0x11112014) {
40+ while (header->magic != 0x11112014) {
41+ usleep(10000);//10ms
42+ }
43+ ctx->read_idx = header->write_idx;
44+ continue;
45+ }
46+ if (ctx->read_idx == header->write_idx) {
47+ usleep(10000);//10ms
48+ continue;
49+ }
50+ if (header->channels == 0 || header->channel_map == 0)
51+ continue;
52+
53+ valid = 1;
54+ } while (!valid);
55+
56+ if (++ctx->read_idx == header->max_chunks) {
57+ ctx->read_idx = 0;
58+ }
59+
60+ receiver_data->format.sample_rate = header->sample_rate;
61+ receiver_data->format.sample_size = header->sample_size;
62+ receiver_data->format.channels = header->channels;
63+ receiver_data->format.channel_map = header->channel_map;
64+
65+ receiver_data->audio_size = header->chunk_size;
66+ receiver_data->audio = &ctx->mmap[header->offset+header->chunk_size*ctx->read_idx];
67+}
68+
Receivers/linux/shmem.h+35−0
@@ -0,0 +1,35 @@
1+#ifndef SHMEM_H
2+#define SHMEM_H
3+
4+#include <stdio.h>
5+#include <stdlib.h>
6+#include <stdint.h>
7+#include <unistd.h>
8+#include <fcntl.h>
9+
10+#include <sys/stat.h>
11+#include <sys/mman.h>
12+
13+#include "scream.h"
14+
15+struct shmheader {
16+ uint32_t magic;
17+ uint16_t write_idx;
18+ uint8_t offset;
19+ uint16_t max_chunks;
20+ uint32_t chunk_size;
21+ uint8_t sample_rate;
22+ uint8_t sample_size;
23+ uint8_t channels;
24+ uint16_t channel_map;
25+};
26+
27+typedef struct rctx_shmem {
28+ unsigned char* mmap;
29+ uint16_t read_idx;
30+} rctx_shmem_t;
31+
32+void* init_shmem(char* shmem_device_file);
33+void rcv_shmem(void* shmem_ctx, receiver_data_t* receiver_data);
34+
35+#endif

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