| @@ -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 | }; |