0.9beta7
add support for forcing alsa sample size to 32, 24, 24_3 or 16 bits
extend -a alsa option to combine all alsa options including mmap and bitdepth
fix big endian support
try to recover better from writei errors
ensure squeezelite quits on ^C during server discovery
Adrian Smith
11 years ago
30 | 30 | " -o <output device>\tSpecify output device, default \"default\"\n" |
31 | 31 | " -l \t\t\tList output devices\n" |
32 | 32 | #if ALSA |
33 | " -a <time>:<count>\tSpecify ALSA buffer_time (ms) and period_count, default 20:4\n" | |
33 | " -a <b>:<c>:<f>:<m>\tSpecify ALSA params to open output device, b = buffer time in ms, c = period count, f sample format (16|24|24_3|32), m = use mmap (0|1)\n" | |
34 | 34 | #endif |
35 | 35 | #if PORTAUDIO |
36 | 36 | " -a <latency>\t\tSpecify output target latency in ms\n" |
37 | 37 | #endif |
38 | 38 | " -b <stream>:<output>\tSpecify internal Stream and Output buffer sizes in Kbytes\n" |
39 | " -c <codec1>,<codec2>\tRestrict codecs those specified, otherwise loads all available codecs; known codecs: flac,pcm,mp3,ogg,aac\n" | |
39 | " -c <codec1>,<codec2>\tRestrict codecs those specified, otherwise loads all available codecs; known codecs: flac,pcm,mp3,ogg,aac (mad,mpg for specific mp3 codec)\n" | |
40 | 40 | " -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug\n" |
41 | 41 | " -f <logfile>\t\tWrite debug to logfile\n" |
42 | 42 | " -m <mac addr>\t\tSet mac address, format: ab:cd:ef:12:34:56\n" |
43 | 43 | " -n <name>\t\tSet the player name\n" |
44 | 44 | " -r <rate>\t\tMax sample rate for output device, enables output device to be off when squeezelite is started\n" |
45 | #if ALSA | |
46 | " -w \t\t\tDisable ASLA mmap output\n" | |
47 | #endif | |
48 | 45 | #if LINUX |
49 | 46 | " -z \t\t\tDaemonize\n" |
50 | 47 | #endif |
68 | 65 | ); |
69 | 66 | } |
70 | 67 | |
71 | void sighandler(int signum) { | |
68 | static void sighandler(int signum) { | |
72 | 69 | slimproto_stop(); |
70 | } | |
71 | ||
72 | static char *next_param(char *src, char c) { | |
73 | static char *str = NULL; | |
74 | char *ptr, *ret; | |
75 | if (src) str = src; | |
76 | if (str && (ptr = strchr(str, c))) { | |
77 | ret = str; | |
78 | *ptr = '\0'; | |
79 | str = ptr + 1; | |
80 | return ret[0] ? ret : NULL; | |
81 | } else { | |
82 | ret = str; | |
83 | str = NULL; | |
84 | return ret; | |
85 | } | |
73 | 86 | } |
74 | 87 | |
75 | 88 | int main(int argc, char **argv) { |
88 | 101 | #if ALSA |
89 | 102 | unsigned alsa_buffer_time = ALSA_BUFFER_TIME; |
90 | 103 | unsigned alsa_period_count = ALSA_PERIOD_COUNT; |
91 | unsigned alsa_mmap = true; | |
104 | char *alsa_sample_fmt = NULL; | |
105 | bool alsa_mmap = true; | |
92 | 106 | #endif |
93 | 107 | #if PORTAUDIO |
94 | 108 | unsigned pa_latency = 0; |
124 | 138 | case 'a': |
125 | 139 | { |
126 | 140 | #if ALSA |
127 | char *t = strtok(optarg, ":"); | |
128 | char *c = strtok(NULL, ":"); | |
141 | char *t = next_param(optarg, ':'); | |
142 | char *c = next_param(NULL, ':'); | |
143 | char *s = next_param(NULL, ':'); | |
144 | char *m = next_param(NULL, ':'); | |
129 | 145 | if (t) alsa_buffer_time = atoi(t) * 1000; |
130 | 146 | if (c) alsa_period_count = atoi(c); |
147 | if (s) alsa_sample_fmt = s; | |
148 | if (m) alsa_mmap = atoi(m); | |
131 | 149 | #endif |
132 | 150 | #if PORTAUDIO |
133 | 151 | pa_latency = atoi(optarg); |
136 | 154 | break; |
137 | 155 | case 'b': |
138 | 156 | { |
139 | char *s = strtok(optarg, ":"); | |
140 | char *o = strtok(NULL, ":"); | |
157 | char *s = next_param(optarg, ':'); | |
158 | char *o = next_param(NULL, ':'); | |
141 | 159 | if (s) stream_buf_size = atoi(s) * 1024; |
142 | 160 | if (o) output_buf_size = atoi(o) * 1024; |
143 | 161 | } |
188 | 206 | list_devices(); |
189 | 207 | exit(0); |
190 | 208 | break; |
191 | #if ALSA | |
192 | case 'w': | |
193 | alsa_mmap = false; | |
194 | break; | |
195 | #endif | |
196 | 209 | #if LINUX |
197 | 210 | case 'z': |
198 | 211 | daemonize = true; |
239 | 252 | #endif |
240 | 253 | |
241 | 254 | #if ALSA |
242 | output_init(log_output, output_device, output_buf_size, alsa_buffer_time, alsa_period_count, alsa_mmap, max_rate); | |
255 | output_init(log_output, output_device, output_buf_size, alsa_buffer_time, alsa_period_count, alsa_sample_fmt, alsa_mmap, max_rate); | |
243 | 256 | #endif |
244 | 257 | #if PORTAUDIO |
245 | 258 | output_init(log_output, output_device, output_buf_size, pa_latency, max_rate); |
22 | 22 | // - PortAudio is the output output supported on other platforms and also builds on linux for test purposes |
23 | 23 | |
24 | 24 | #include "squeezelite.h" |
25 | ||
26 | 25 | #if ALSA |
27 | ||
28 | 26 | #include <alsa/asoundlib.h> |
27 | #endif | |
28 | #if PORTAUDIO | |
29 | #include <portaudio.h> | |
30 | #if OSX | |
31 | #include <pa_mac_core.h> | |
32 | #endif | |
33 | #endif | |
34 | ||
35 | #if defined LITTLE_ENDIAN | |
36 | #undef LITTLE_ENDIAN | |
37 | #endif | |
38 | #define LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) | |
39 | ||
40 | #if ALSA | |
29 | 41 | |
30 | 42 | #define MAX_SILENCE_FRAMES 1024 |
31 | 43 | |
32 | 44 | static snd_pcm_format_t fmts[] = { SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S16_LE, |
33 | 45 | SND_PCM_FORMAT_UNKNOWN }; |
46 | #if LITTLE_ENDIAN | |
47 | #define NATIVE_FORMAT SND_PCM_FORMAT_S32_LE | |
48 | #else | |
49 | #define NATIVE_FORMAT SND_PCM_FORMAT_S32_BE | |
50 | #endif | |
34 | 51 | |
35 | 52 | // ouput device |
36 | 53 | static struct { |
47 | 64 | #endif // ALSA |
48 | 65 | |
49 | 66 | #if PORTAUDIO |
50 | ||
51 | #include <portaudio.h> | |
52 | #if OSX | |
53 | #include <pa_mac_core.h> | |
54 | #endif | |
55 | 67 | |
56 | 68 | #define MAX_SILENCE_FRAMES 102400 // silencebuf not used in pa case so set large |
57 | 69 | |
189 | 201 | alsa.device = NULL; |
190 | 202 | alsa.rate = 0; |
191 | 203 | alsa.period_size = 0; |
192 | alsa.format = SND_PCM_FORMAT_UNKNOWN; | |
193 | 204 | alsa.device = malloc(strlen(device) + 1 + 4); // extra space for changing to plug |
194 | 205 | strcpy(alsa.device, device); |
195 | 206 | |
239 | 250 | } |
240 | 251 | |
241 | 252 | // set the sample format |
242 | snd_pcm_format_t *fmt = (snd_pcm_format_t *)fmts; | |
243 | while (*fmt != SND_PCM_FORMAT_UNKNOWN) { | |
253 | snd_pcm_format_t *fmt = alsa.format ? &alsa.format : (snd_pcm_format_t *)fmts; | |
254 | do { | |
244 | 255 | if (snd_pcm_hw_params_set_format(*pcmp, hw_params, *fmt) >= 0) { |
245 | 256 | LOG_INFO("opened device %s using format: %s sample rate: %u mmap: %u", alsa.device, snd_pcm_format_name(*fmt), sample_rate, alsa.mmap); |
246 | 257 | alsa.format = *fmt; |
247 | 258 | break; |
248 | 259 | } |
260 | if (alsa.format) { | |
261 | LOG_ERROR("unable to open audio device requested format: %s", snd_pcm_format_name(alsa.format)); | |
262 | return -1; | |
263 | } | |
249 | 264 | ++fmt; |
250 | 265 | if (*fmt == SND_PCM_FORMAT_UNKNOWN) { |
251 | 266 | LOG_ERROR("unable to open audio device with any supported format"); |
252 | 267 | return -1; |
253 | 268 | } |
254 | } | |
269 | } while (*fmt != SND_PCM_FORMAT_UNKNOWN); | |
255 | 270 | |
256 | 271 | // set channels |
257 | 272 | if ((err = snd_pcm_hw_params_set_channels (*pcmp, hw_params, 2)) < 0) { |
288 | 303 | |
289 | 304 | LOG_INFO("buffer time: %u period count: %u buffer size: %u period size: %u", time, count, buffer_size, alsa.period_size); |
290 | 305 | |
291 | // create an intermediate buffer for non mmap case for all but S32_LE | |
306 | // create an intermediate buffer for non mmap case for all but NATIVE_FORMAT | |
292 | 307 | // this is used to pack samples into the output format before calling writei |
293 | if (!alsa.mmap && !alsa.write_buf && alsa.format != SND_PCM_FORMAT_S32_LE) { | |
308 | if (!alsa.mmap && !alsa.write_buf && alsa.format != NATIVE_FORMAT) { | |
294 | 309 | alsa.write_buf = malloc(alsa.period_size * BYTES_PER_FRAME); |
295 | 310 | if (!alsa.write_buf) { |
296 | 311 | LOG_ERROR("unable to malloc write_buf"); |
830 | 845 | out_frames = !silence ? min(size, cont_frames) : size; |
831 | 846 | |
832 | 847 | #if ALSA |
833 | if (alsa.mmap || alsa.format != SND_PCM_FORMAT_S32_LE) { | |
834 | ||
835 | // in all alsa cases except SND_PCM_FORMAT_S32_LE non mmap we take this path: | |
848 | if (alsa.mmap || alsa.format != NATIVE_FORMAT) { | |
849 | ||
850 | // in all alsa cases except NATIVE_FORMAT non mmap we take this path: | |
836 | 851 | // - mmap: scale and pack to output format, write direct into mmap region |
837 | 852 | // - non mmap: scale and pack into alsa.write_buf, which is the used with writei to send to alsa |
838 | 853 | const snd_pcm_channel_area_t *areas; |
1071 | 1086 | } else { |
1072 | 1087 | snd_pcm_sframes_t w = snd_pcm_writei(pcmp, alsa.write_buf, out_frames); |
1073 | 1088 | if (w < 0) { |
1074 | LOG_WARN("writei error: %d", w); | |
1089 | if (w != -EAGAIN && (err = snd_pcm_recover(pcmp, w, 1)) < 0) { | |
1090 | LOG_WARN("recover failed: %s", snd_strerror(err)); | |
1091 | } | |
1075 | 1092 | break; |
1076 | 1093 | } else { |
1077 | 1094 | if (w != out_frames) { |
1115 | 1132 | } |
1116 | 1133 | } |
1117 | 1134 | #if ALSA |
1118 | // only used in S32_LE non mmap case, write the 32 samples straight with writei, no need for intermediate buffer | |
1135 | // only used in S32_LE non mmap LE case, write the 32 samples straight with writei, no need for intermediate buffer | |
1119 | 1136 | snd_pcm_sframes_t w = snd_pcm_writei(pcmp, silence ? silencebuf : outputbuf->readp, out_frames); |
1120 | 1137 | if (w < 0) { |
1121 | LOG_WARN("writei error: %d", w); | |
1138 | if (w != -EAGAIN && (err = snd_pcm_recover(pcmp, w, 1)) < 0) { | |
1139 | LOG_WARN("recover failed: %s", snd_strerror(err)); | |
1140 | } | |
1122 | 1141 | break; |
1123 | 1142 | } else { |
1124 | 1143 | if (w != out_frames) { |
1233 | 1252 | |
1234 | 1253 | #if ALSA |
1235 | 1254 | static pthread_t thread; |
1236 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned buffer_time, unsigned period_count, bool mmap, unsigned max_rate) { | |
1255 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned buffer_time, unsigned period_count, | |
1256 | const char *alsa_sample_fmt, bool mmap, unsigned max_rate) { | |
1237 | 1257 | #endif |
1238 | 1258 | #if PORTAUDIO |
1239 | 1259 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned latency, unsigned max_rate) { |
1262 | 1282 | #if ALSA |
1263 | 1283 | alsa.mmap = mmap; |
1264 | 1284 | alsa.write_buf = NULL; |
1285 | alsa.format = 0; | |
1265 | 1286 | output.buffer_time = buffer_time; |
1266 | 1287 | output.period_count = period_count; |
1267 | 1288 | |
1268 | LOG_INFO("requested buffer_time: %u period_count: %u mmap: %u", output.buffer_time, output.period_count, alsa.mmap); | |
1289 | if (alsa_sample_fmt) { | |
1290 | if (!strcmp(alsa_sample_fmt, "32")) alsa.format = SND_PCM_FORMAT_S32_LE; | |
1291 | if (!strcmp(alsa_sample_fmt, "24")) alsa.format = SND_PCM_FORMAT_S24_LE; | |
1292 | if (!strcmp(alsa_sample_fmt, "24_3")) alsa.format = SND_PCM_FORMAT_S24_3LE; | |
1293 | if (!strcmp(alsa_sample_fmt, "16")) alsa.format = SND_PCM_FORMAT_S16_LE; | |
1294 | } | |
1295 | ||
1296 | LOG_INFO("requested buffer_time: %u period_count: %u format: %s mmap: %u", output.buffer_time, output.period_count, | |
1297 | alsa_sample_fmt ? alsa_sample_fmt : "any", alsa.mmap); | |
1269 | 1298 | #endif |
1270 | 1299 | |
1271 | 1300 | #if PORTAUDIO |
610 | 610 | LOG_INFO("error sending disovery"); |
611 | 611 | } |
612 | 612 | |
613 | if (poll(&pollinfo, 1, 5000)) { | |
613 | if (poll(&pollinfo, 1, 5000) && running) { | |
614 | 614 | char readbuf[10]; |
615 | 615 | socklen_t slen = sizeof(s); |
616 | 616 | recvfrom(disc_sock, readbuf, 10, 0, (struct sockaddr *)&s, &slen); |
617 | 617 | LOG_INFO("got response from: %s:%d", inet_ntoa(s.sin_addr), ntohs(s.sin_port)); |
618 | 618 | } |
619 | 619 | |
620 | } while (s.sin_addr.s_addr == 0); | |
620 | } while (s.sin_addr.s_addr == 0 && running); | |
621 | 621 | |
622 | 622 | closesocket(disc_sock); |
623 | 623 | |
633 | 633 | wake_create(wake_e); |
634 | 634 | |
635 | 635 | loglevel = level; |
636 | running = true; | |
637 | ||
636 | 638 | slimproto_ip = addr ? inet_addr(addr) : discover_server(); |
639 | ||
640 | if (!running) return; | |
637 | 641 | |
638 | 642 | LOCK_O; |
639 | 643 | sprintf(fixed_cap, ",MaxSampleRate=%u", output.max_sample_rate); |
653 | 657 | |
654 | 658 | LOG_INFO("connecting to %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port)); |
655 | 659 | |
656 | running = true; | |
657 | 660 | new_server = 0; |
658 | 661 | |
659 | 662 | while (running) { |
721 | 724 | } |
722 | 725 | |
723 | 726 | void slimproto_stop(void) { |
727 | LOG_INFO("slimproto stop"); | |
724 | 728 | running = false; |
725 | 729 | } |
17 | 17 | * |
18 | 18 | */ |
19 | 19 | |
20 | #define VERSION "v0.9beta6" | |
20 | #define VERSION "v0.9beta7" | |
21 | 21 | |
22 | 22 | // build detection |
23 | 23 | #if defined(linux) |
401 | 401 | |
402 | 402 | void list_devices(void); |
403 | 403 | #if ALSA |
404 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned buffer_time, unsigned period_count, bool mmap, unsigned max_rate); | |
404 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned buffer_time, unsigned period_count, const char *alsa_sample_fmt, bool mmap, unsigned max_rate); | |
405 | 405 | #endif |
406 | 406 | #if PORTAUDIO |
407 | 407 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned latency, unsigned max_rate); |