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 @@
1cmake_minimum_required(VERSION 3.7)
2
3project(scream)
4
5add_executable(${PROJECT_NAME} scream.c network.c shmem.c)
6
7# find pulseaudio
8find_package(PulseAudio)
9if (PULSEAUDIO_FOUND)
10 include_directories(${PULSEAUDIO_INCLUDE_DIRS})
11 target_link_libraries(${PROJECT_NAME} pulse pulse-simple)
12 option(PULSEAUDIO_ENABLE "Enable PulseAudio" ON)
13endif (PULSEAUDIO_FOUND)
14
15# find ALSA
16find_package(PkgConfig)
17pkg_check_modules(PC_ALSA alsa)
18
19if (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)
23endif (PC_ALSA_FOUND)
24
25configure_file(config.h.in config.h)
26target_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
3void* 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
25void 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
16typedef 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
23void* init_network(enum receiver_type receiver_mode, in_addr_t interface, int port, char* multicast_group);
24void 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
25static 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
48static 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
74error_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
87int 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
4enum receiver_type {
5 Unicast, Multicast, SharedMem
6};
7
8typedef 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
15typedef 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
3void* 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
32void 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
15struct 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
27typedef struct rctx_shmem {
28 unsigned char* mmap;
29 uint16_t read_idx;
30} rctx_shmem_t;
31
32void* init_shmem(char* shmem_device_file);
33void 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