Merge tag 'upstream/1.8' into develop
Tag upstream version 1.8
Chris Boot
8 years ago
102 | 102 | |
103 | 103 | Minor changes |
104 | 104 | - improve synchronisation feedback accuracy |
105 | ||
106 | Version 1.6.5 21/11/14 | |
107 | ====================== | |
108 | ||
109 | Minor changes | |
110 | - fix problem opening ALSA device if 44100 is not supported | |
111 | - trap setting of hw player mac address | |
112 | ||
113 | Version 1.7 1/1/15 | |
114 | ================== | |
115 | ||
116 | Minor changes | |
117 | - allow player modelname to be set at compile or run time | |
118 | - workaround alsa drivers reporting very large number of available frames | |
119 | - fix clicks on localfile playback of AIFF files | |
120 | - add -P option to store process id in a file | |
121 | - improve error messages for command line parsing | |
122 | ||
123 | Version 1.7.1 10/1/15 | |
124 | ===================== | |
125 | ||
126 | Minor changes | |
127 | - fix crash which could occur when resampling | |
128 | ||
129 | Version 1.8 1/2/15 | |
130 | ================== | |
131 | ||
132 | Features | |
133 | - support for closing output device when idle with -C option | |
134 | - support for basic IR input using LIRC on Linux | |
135 | - support for volume adjustment or unmuting of alsa mixer | |
136 | - support for inverting output polarity via LMS setting (requires recent 7.9 server) |
0 | 0 | Squeezelite - lightweight headless squeezebox emulator |
1 | 1 | |
2 | (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
2 | (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
3 | 3 | |
4 | 4 | Released under GPLv3 license: |
5 | 5 | |
16 | 16 | You should have received a copy of the GNU General Public License |
17 | 17 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 | |
19 | ||
20 | --------------------------------------------------------------------- | |
21 | ||
22 | If built with DSD support, this software also includes code subject to the following license: | |
23 | ||
24 | Copyright 2009, 2011 Sebastian Gesemann. All rights reserved. | |
25 | ||
26 | Redistribution and use in source and binary forms, with or without modification, are | |
27 | permitted provided that the following conditions are met: | |
28 | ||
29 | 1. Redistributions of source code must retain the above copyright notice, this list of | |
30 | conditions and the following disclaimer. | |
31 | ||
32 | 2. Redistributions in binary form must reproduce the above copyright notice, this list | |
33 | of conditions and the following disclaimer in the documentation and/or other materials | |
34 | provided with the distribution. | |
35 | ||
36 | THIS SOFTWARE IS PROVIDED BY SEBASTIAN GESEMANN ''AS IS'' AND ANY EXPRESS OR IMPLIED | |
37 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
38 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEBASTIAN GESEMANN OR | |
39 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
40 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
41 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
42 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
43 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
44 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
45 | ||
46 | The views and conclusions contained in the software and documentation are those of the | |
47 | authors and should not be interpreted as representing official policies, either expressed | |
48 | or implied, of Sebastian Gesemann. |
8 | 8 | OPT_LINKALL = -DLINKALL |
9 | 9 | OPT_RESAMPLE= -DRESAMPLE |
10 | 10 | OPT_VIS = -DVISEXPORT |
11 | OPT_IR = -DIR | |
11 | 12 | |
12 | 13 | SOURCES = \ |
13 | 14 | main.c slimproto.c buffer.c stream.c utils.c \ |
18 | 19 | SOURCES_FF = ffmpeg.c |
19 | 20 | SOURCES_RESAMPLE = process.c resample.c |
20 | 21 | SOURCES_VIS = output_vis.c |
22 | SOURCES_IR = ir.c | |
21 | 23 | |
22 | 24 | LINK_LINUX = -ldl |
23 | 25 | |
24 | 26 | LINKALL = -lFLAC -lmad -lvorbisfile -lfaad -lmpg123 |
25 | 27 | LINKALL_FF = -lavcodec -lavformat -lavutil |
26 | 28 | LINKALL_RESAMPLE = -lsoxr |
29 | LINKALL_IR = -llirc_client | |
27 | 30 | |
28 | 31 | DEPS = squeezelite.h slimproto.h |
29 | 32 | |
42 | 45 | ifneq (,$(findstring $(OPT_VIS), $(CFLAGS))) |
43 | 46 | SOURCES += $(SOURCES_VIS) |
44 | 47 | endif |
48 | ifneq (,$(findstring $(OPT_IR), $(CFLAGS))) | |
49 | SOURCES += $(SOURCES_IR) | |
50 | endif | |
45 | 51 | |
46 | 52 | # add optional link options |
47 | 53 | ifneq (,$(findstring $(OPT_LINKALL), $(CFLAGS))) |
51 | 57 | endif |
52 | 58 | ifneq (,$(findstring $(OPT_RESAMPLE), $(CFLAGS))) |
53 | 59 | LDFLAGS += $(LINKALL_RESAMPLE) |
60 | endif | |
61 | ifneq (,$(findstring $(OPT_IR), $(CFLAGS))) | |
62 | LDFLAGS += $(LINKALL_IR) | |
54 | 63 | endif |
55 | 64 | else |
56 | 65 | # if not LINKALL and linux add LINK_LINUX |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
239 | 239 | |
240 | 240 | codec->open(sample_size, sample_rate, channels, endianness); |
241 | 241 | |
242 | decode.state = DECODE_READY; | |
243 | ||
242 | 244 | UNLOCK_D; |
243 | 245 | return; |
244 | 246 | } |
36 | 36 | u32_t next = 0; |
37 | 37 | |
38 | 38 | while (frames--) { |
39 | if (((*lptr & 0x00FF0000) == 0x00050000 && (*rptr & 0x00FF0000) == 0x00050000) || | |
39 | if (((*lptr & 0x00FF0000) == 0x00050000 && (*rptr & 0x00FF0000) == 0x00050000) || | |
40 | 40 | ((*lptr & 0x00FF0000) == 0x00FA0000 && (*rptr & 0x00FF0000) == 0x00FA0000)) { |
41 | 41 | if (*lptr >> 24 == next) { |
42 | 42 | matched++; |
57 | 57 | return false; |
58 | 58 | } |
59 | 59 | |
60 | // update the dop marker for frames in the output buffer | |
60 | // update the dop marker and potentially invert polarity for frames in the output buffer | |
61 | 61 | // performaned on all output including silence to maintain marker phase consitency |
62 | void update_dop_marker(u32_t *ptr, frames_t frames) { | |
62 | void update_dop(u32_t *ptr, frames_t frames, bool invert) { | |
63 | 63 | static u32_t marker = 0x05; |
64 | while (frames--) { | |
65 | u32_t scaled_marker = marker << 24; | |
66 | *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; | |
67 | ++ptr; | |
68 | *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; | |
69 | ++ptr; | |
70 | marker = ( 0x05 + 0xFA ) - marker; | |
64 | if (!invert) { | |
65 | while (frames--) { | |
66 | u32_t scaled_marker = marker << 24; | |
67 | *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; | |
68 | ++ptr; | |
69 | *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; | |
70 | ++ptr; | |
71 | marker = ( 0x05 + 0xFA ) - marker; | |
72 | } | |
73 | } else { | |
74 | while (frames--) { | |
75 | u32_t scaled_marker = marker << 24; | |
76 | *ptr = ((~(*ptr)) & 0x00FFFFFF) | scaled_marker; | |
77 | ++ptr; | |
78 | *ptr = ((~(*ptr)) & 0x00FFFFFF) | scaled_marker; | |
79 | ++ptr; | |
80 | marker = ( 0x05 + 0xFA ) - marker; | |
81 | } | |
71 | 82 | } |
72 | 83 | } |
73 | 84 |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
227 | 227 | |
228 | 228 | // found media data, advance to start of first chunk and return |
229 | 229 | if (!strcmp(type, "mdat")) { |
230 | _buf_inc_readp(streambuf, 8); | |
230 | _buf_inc_readp(streambuf, 8); | |
231 | 231 | a->pos += 8; |
232 | 232 | bytes -= 8; |
233 | 233 | if (play) { |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
609 | 609 | ff->avcodec_alloc_frame = dlsym(handle_codec, "avcodec_alloc_frame"); |
610 | 610 | ff->avcodec_free_frame = dlsym(handle_codec, "avcodec_free_frame"); |
611 | 611 | ff->avcodec_decode_audio4 = dlsym(handle_codec, "avcodec_decode_audio4"); |
612 | ff->av_init_packet = dlsym(handle_codec, "av_init_packet"); | |
613 | ff->av_free_packet = dlsym(handle_codec, "av_free_packet"); | |
612 | ff->av_init_packet = dlsym(handle_codec, "av_init_packet"); | |
613 | ff->av_free_packet = dlsym(handle_codec, "av_free_packet"); | |
614 | 614 | |
615 | 615 | if ((err = dlerror()) != NULL) { |
616 | 616 | LOG_INFO("dlerror: %s", err); |
619 | 619 | |
620 | 620 | LOG_INFO("loaded "LIBAVCODEC" (%u.%u.%u)", LIBAVCODEC_VERSION_MAJOR, ff->avcodec_version() >> 16, (ff->avcodec_version() >> 8) & 0xff, ff->avcodec_version() & 0xff); |
621 | 621 | |
622 | ff->avformat_version = dlsym(handle_format, "avformat_version"); | |
623 | ff->avformat_alloc_context = dlsym(handle_format, "avformat_alloc_context"); | |
624 | ff->avformat_free_context = dlsym(handle_format, "avformat_free_context"); | |
625 | ff->avformat_open_input = dlsym(handle_format, "avformat_open_input"); | |
626 | ff->avformat_find_stream_info = dlsym(handle_format, "avformat_find_stream_info"); | |
627 | ff->avio_alloc_context = dlsym(handle_format, "avio_alloc_context"); | |
628 | ff->av_read_frame = dlsym(handle_format, "av_read_frame"); | |
629 | ff->av_find_input_format= dlsym(handle_format, "av_find_input_format"); | |
622 | ff->avformat_version = dlsym(handle_format, "avformat_version"); | |
623 | ff->avformat_alloc_context = dlsym(handle_format, "avformat_alloc_context"); | |
624 | ff->avformat_free_context = dlsym(handle_format, "avformat_free_context"); | |
625 | ff->avformat_open_input = dlsym(handle_format, "avformat_open_input"); | |
626 | ff->avformat_find_stream_info = dlsym(handle_format, "avformat_find_stream_info"); | |
627 | ff->avio_alloc_context = dlsym(handle_format, "avio_alloc_context"); | |
628 | ff->av_read_frame = dlsym(handle_format, "av_read_frame"); | |
629 | ff->av_find_input_format= dlsym(handle_format, "av_find_input_format"); | |
630 | 630 | ff->av_register_all = dlsym(handle_format, "av_register_all"); |
631 | 631 | |
632 | 632 | if ((err = dlerror()) != NULL) { |
0 | /* | |
1 | * Squeezelite - lightweight headless squeezebox emulator | |
2 | * | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | */ | |
19 | ||
20 | // ir thread - linux only | |
21 | ||
22 | #include "squeezelite.h" | |
23 | ||
24 | #if IR | |
25 | ||
26 | #include <lirc/lirc_client.h> | |
27 | ||
28 | #define LIRC_CLIENT_ID "squeezelite" | |
29 | ||
30 | static log_level loglevel; | |
31 | ||
32 | struct irstate ir; | |
33 | ||
34 | static struct lirc_config *config = NULL; | |
35 | static sockfd fd = -1; | |
36 | ||
37 | static thread_type thread; | |
38 | ||
39 | #define LOCK_I mutex_lock(ir.mutex) | |
40 | #define UNLOCK_I mutex_unlock(ir.mutex) | |
41 | ||
42 | #if !LINKALL | |
43 | struct lirc { | |
44 | // LIRC symbols to be dynamically loaded | |
45 | int (* lirc_init)(char *prog, int verbose); | |
46 | int (* lirc_deinit)(void); | |
47 | int (* lirc_readconfig)(char *file, struct lirc_config **config, int (check) (char *s)); | |
48 | void (* lirc_freeconfig)(struct lirc_config *config); | |
49 | int (* lirc_nextcode)(char **code); | |
50 | int (* lirc_code2char)(struct lirc_config *config, char *code, char **string); | |
51 | }; | |
52 | ||
53 | static struct lirc *i; | |
54 | #endif | |
55 | ||
56 | #if LINKALL | |
57 | #define LIRC(h, fn, ...) (lirc_ ## fn)(__VA_ARGS__) | |
58 | #else | |
59 | #define LIRC(h, fn, ...) (h)->lirc_##fn(__VA_ARGS__) | |
60 | #endif | |
61 | ||
62 | // cmds based on entires in Slim_Device_Remote.ir | |
63 | // these may appear as config entries in .lircrc files | |
64 | static struct { | |
65 | char *cmd; | |
66 | u32_t code; | |
67 | } cmdmap[] = { | |
68 | { "voldown", 0x768900ff }, | |
69 | { "volup", 0x7689807f }, | |
70 | { "rew", 0x7689c03f }, | |
71 | { "fwd", 0x7689a05f }, | |
72 | { "pause", 0x768920df }, | |
73 | { "play", 0x768910ef }, | |
74 | { "power", 0x768940bf }, | |
75 | { "muting", 0x7689c43b }, | |
76 | { "power_on", 0x76898f70 }, | |
77 | { "power_off",0x76898778 }, | |
78 | { NULL, 0 }, | |
79 | }; | |
80 | ||
81 | // selected lirc namespace button names as defaults - some support repeat | |
82 | static struct { | |
83 | char *lirc; | |
84 | u32_t code; | |
85 | bool repeat; | |
86 | } keymap[] = { | |
87 | { "KEY_VOLUMEDOWN", 0x768900ff, true }, | |
88 | { "KEY_VOLUMEUP", 0x7689807f, true }, | |
89 | { "KEY_PREVIOUS", 0x7689c03f, false }, | |
90 | { "KEY_REWIND", 0x7689c03f, false }, | |
91 | { "KEY_NEXT", 0x7689a05f, false }, | |
92 | { "KEY_FORWARD", 0x7689a05f, false }, | |
93 | { "KEY_PAUSE", 0x768920df, true }, | |
94 | { "KEY_PLAY", 0x768910ef, false }, | |
95 | { "KEY_POWER", 0x768940bf, false }, | |
96 | { "KEY_MUTE", 0x7689c43b, false }, | |
97 | { NULL, 0 , false }, | |
98 | }; | |
99 | ||
100 | static u32_t ir_cmd_map(const char *c) { | |
101 | int i; | |
102 | for (i = 0; cmdmap[i].cmd; i++) { | |
103 | if (!strcmp(c, cmdmap[i].cmd)) { | |
104 | return cmdmap[i].code; | |
105 | } | |
106 | } | |
107 | return 0; | |
108 | } | |
109 | ||
110 | static u32_t ir_key_map(const char *c, const char *r) { | |
111 | int i; | |
112 | for (i = 0; keymap[i].lirc; i++) { | |
113 | if (!strcmp(c, keymap[i].lirc)) { | |
114 | if (keymap[i].repeat || !strcmp(r, "00")) { | |
115 | return keymap[i].code; | |
116 | } | |
117 | LOG_DEBUG("repeat suppressed"); | |
118 | break; | |
119 | } | |
120 | } | |
121 | return 0; | |
122 | } | |
123 | ||
124 | static void *ir_thread() { | |
125 | char *code; | |
126 | ||
127 | while (fd > 0 && LIRC(i, nextcode, &code) == 0) { | |
128 | ||
129 | u32_t now = gettime_ms(); | |
130 | u32_t ir_code = 0; | |
131 | ||
132 | if (code == NULL) continue; | |
133 | ||
134 | if (config) { | |
135 | // allow lirc_client to decode then lookup cmd in our table | |
136 | // we can only send one IR event to slimproto so break after first one | |
137 | char *c; | |
138 | while (LIRC(i, code2char, config, code, &c) == 0 && c != NULL) { | |
139 | ir_code = ir_cmd_map(c); | |
140 | if (ir_code) { | |
141 | LOG_DEBUG("ir cmd: %s -> %x", c, ir_code); | |
142 | } | |
143 | } | |
144 | } | |
145 | ||
146 | if (!ir_code) { | |
147 | // try to match on lirc button name if it is from the standard namespace | |
148 | // this allows use of non slim remotes without a specific entry in .lircrc | |
149 | char *b, *r; | |
150 | strtok(code, " \n"); // discard | |
151 | r = strtok(NULL, " \n"); // repeat count | |
152 | b = strtok(NULL, " \n"); // key name | |
153 | if (r && b) { | |
154 | ir_code = ir_key_map(b, r); | |
155 | LOG_DEBUG("ir lirc: %s [%s] -> %x", b, r, ir_code); | |
156 | } | |
157 | } | |
158 | ||
159 | if (ir_code) { | |
160 | LOCK_I; | |
161 | if (ir.code) { | |
162 | LOG_DEBUG("code dropped"); | |
163 | } | |
164 | ir.code = ir_code; | |
165 | ir.ts = now; | |
166 | UNLOCK_I; | |
167 | wake_controller(); | |
168 | } | |
169 | ||
170 | free(code); | |
171 | } | |
172 | ||
173 | return 0; | |
174 | } | |
175 | ||
176 | #if !LINKALL | |
177 | static bool load_lirc() { | |
178 | void *handle = dlopen(LIBLIRC, RTLD_NOW); | |
179 | char *err; | |
180 | ||
181 | if (!handle) { | |
182 | LOG_INFO("dlerror: %s", dlerror()); | |
183 | return false; | |
184 | } | |
185 | ||
186 | i->lirc_init = dlsym(handle, "lirc_init"); | |
187 | i->lirc_deinit = dlsym(handle, "lirc_deinit"); | |
188 | i->lirc_readconfig = dlsym(handle, "lirc_readconfig"); | |
189 | i->lirc_freeconfig = dlsym(handle, "lirc_freeconfig"); | |
190 | i->lirc_nextcode = dlsym(handle, "lirc_nextcode"); | |
191 | i->lirc_code2char = dlsym(handle, "lirc_code2char"); | |
192 | ||
193 | if ((err = dlerror()) != NULL) { | |
194 | LOG_INFO("dlerror: %s", err); | |
195 | return false; | |
196 | } | |
197 | ||
198 | LOG_INFO("loaded "LIBLIRC); | |
199 | return true; | |
200 | } | |
201 | #endif | |
202 | ||
203 | void ir_init(log_level level, char *lircrc) { | |
204 | loglevel = level; | |
205 | ||
206 | #if !LINKALL | |
207 | i = malloc(sizeof(struct lirc)); | |
208 | if (!i || !load_lirc()) { | |
209 | return; | |
210 | } | |
211 | #endif | |
212 | ||
213 | fd = LIRC(i, init, LIRC_CLIENT_ID, 0); | |
214 | ||
215 | if (fd > 0) { | |
216 | if (LIRC(i, readconfig,lircrc, &config, NULL) != 0) { | |
217 | LOG_WARN("error reading config: %s", lircrc); | |
218 | } | |
219 | ||
220 | mutex_create(ir.mutex); | |
221 | ||
222 | pthread_attr_t attr; | |
223 | pthread_attr_init(&attr); | |
224 | pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + IR_THREAD_STACK_SIZE); | |
225 | pthread_create(&thread, &attr, ir_thread, NULL); | |
226 | pthread_attr_destroy(&attr); | |
227 | ||
228 | } else { | |
229 | LOG_WARN("failed to connect to lircd - ir processing disabled"); | |
230 | } | |
231 | } | |
232 | ||
233 | void ir_close(void) { | |
234 | if (fd > 0) { | |
235 | fd = -1; | |
236 | if (config) { | |
237 | LIRC(i, freeconfig, config); | |
238 | } | |
239 | LIRC(i, deinit); | |
240 | ||
241 | pthread_cancel(thread); | |
242 | pthread_join(thread, NULL); | |
243 | mutex_destroy(ir.mutex); | |
244 | } | |
245 | } | |
246 | ||
247 | #endif //#if IR |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
21 | 21 | |
22 | 22 | #include <signal.h> |
23 | 23 | |
24 | #define TITLE "Squeezelite " VERSION ", Copyright 2012-2014 Adrian Smith." | |
24 | #define TITLE "Squeezelite " VERSION ", Copyright 2012-2015 Adrian Smith." | |
25 | 25 | |
26 | 26 | #define CODECS_BASE "flac,pcm,mp3,ogg,aac" |
27 | 27 | #if FFMPEG |
57 | 57 | " -a <f>\t\tSpecify sample format (16|24|32) of output file when using -o - to output samples to stdout (interleaved little endian only)\n" |
58 | 58 | " -b <stream>:<output>\tSpecify internal Stream and Output buffer sizes in Kbytes\n" |
59 | 59 | " -c <codec1>,<codec2>\tRestrict codecs to those specified, otherwise load all available codecs; known codecs: " CODECS "\n" |
60 | " -C <timeout>\t\tClose output device when idle after timeout seconds, default is to keep it open while player is 'on'\n" | |
61 | #if !IR | |
60 | 62 | " -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug\n" |
63 | #else | |
64 | " -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output|ir, level: info|debug|sdebug\n" | |
65 | #endif | |
61 | 66 | " -e <codec1>,<codec2>\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n" |
62 | 67 | " -f <logfile>\t\tWrite debug to logfile\n" |
68 | #if IR | |
69 | " -i [<filename>]\tEnable lirc remote control support (lirc config file ~/.lircrc used if filename not specified)\n" | |
70 | #endif | |
63 | 71 | " -m <mac addr>\t\tSet mac address, format: ab:cd:ef:12:34:56\n" |
72 | " -M <modelname>\tSet the squeezelite player model name sent to the server (default: " MODEL_NAME_STRING ")\n" | |
64 | 73 | " -n <name>\t\tSet the player name\n" |
65 | 74 | " -N <filename>\t\tStore player name in filename to allow server defined name changes to be shared between servers (not supported with -n)\n" |
66 | 75 | #if ALSA |
67 | 76 | " -p <priority>\t\tSet real time priority of output thread (1-99)\n" |
77 | #endif | |
78 | #if LINUX || FREEBSD | |
79 | " -P <filename>\t\tStore the process id (PID) in filename\n" | |
68 | 80 | #endif |
69 | 81 | " -r <rates>[:<delay>]\tSample rates supported, allows output to be off when squeezelite is started; rates = <maxrate>|<minrate>-<maxrate>|<rate1>,<rate2>,<rate3>; delay = optional delay switching rates in ms\n" |
70 | 82 | #if RESAMPLE |
83 | 95 | #if VISEXPORT |
84 | 96 | " -v \t\t\tVisualiser support\n" |
85 | 97 | #endif |
98 | # if ALSA | |
99 | " -L \t\t\tList volume controls for output device\n" | |
100 | " -U <control>\t\tUnmute ALSA control and set to full volume (not supported with -V)\n" | |
101 | " -V <control>\t\tUse ALSA control for volume adjustment, otherwise use software volume adjustment\n" | |
102 | #endif | |
86 | 103 | #if LINUX || FREEBSD |
87 | 104 | " -z \t\t\tDaemonize\n" |
88 | 105 | #endif |
89 | 106 | " -t \t\t\tLicense terms\n" |
107 | " -? \t\t\tDisplay this help text\n" | |
90 | 108 | "\n" |
91 | 109 | "Build options:" |
92 | 110 | #if LINUX |
128 | 146 | #endif |
129 | 147 | #if VISEXPORT |
130 | 148 | " VISEXPORT" |
149 | #endif | |
150 | #if IR | |
151 | " IR" | |
131 | 152 | #endif |
132 | 153 | #if DSD |
133 | 154 | " DSD" |
172 | 193 | char *exclude_codecs = ""; |
173 | 194 | char *name = NULL; |
174 | 195 | char *namefile = NULL; |
196 | char *modelname = NULL; | |
175 | 197 | char *logfile = NULL; |
176 | 198 | u8_t mac[6]; |
177 | 199 | unsigned stream_buf_size = STREAMBUF_SIZE; |
180 | 202 | unsigned rate_delay = 0; |
181 | 203 | char *resample = NULL; |
182 | 204 | char *output_params = NULL; |
205 | unsigned idle = 0; | |
183 | 206 | #if LINUX || FREEBSD |
184 | 207 | bool daemonize = false; |
208 | char *pidfile = NULL; | |
209 | FILE *pidfp = NULL; | |
185 | 210 | #endif |
186 | 211 | #if ALSA |
187 | 212 | unsigned rt_priority = OUTPUT_RT_PRIORITY; |
213 | char *output_mixer = NULL; | |
214 | bool output_mixer_unmute = false; | |
188 | 215 | #endif |
189 | 216 | #if DSD |
190 | 217 | bool dop = false; |
192 | 219 | #endif |
193 | 220 | #if VISEXPORT |
194 | 221 | bool visexport = false; |
222 | #endif | |
223 | #if IR | |
224 | char *lircrc = NULL; | |
195 | 225 | #endif |
196 | 226 | |
197 | 227 | log_level log_output = lWARN; |
198 | 228 | log_level log_stream = lWARN; |
199 | 229 | log_level log_decode = lWARN; |
200 | 230 | log_level log_slimproto = lWARN; |
231 | #if IR | |
232 | log_level log_ir = lWARN; | |
233 | #endif | |
201 | 234 | |
202 | 235 | char *optarg = NULL; |
203 | 236 | int optind = 1; |
204 | 237 | int i; |
205 | 238 | |
206 | #define MAXCMDLINE 256 | |
239 | #define MAXCMDLINE 512 | |
207 | 240 | char cmdline[MAXCMDLINE] = ""; |
208 | 241 | |
209 | 242 | get_mac(mac); |
215 | 248 | |
216 | 249 | while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') { |
217 | 250 | char *opt = argv[optind] + 1; |
218 | if (strstr("oabcdefmnNprs", opt) && optind < argc - 1) { | |
251 | if (strstr("oabcCdefmMnNpPrs" | |
252 | #if ALSA | |
253 | "UV" | |
254 | #endif | |
255 | , opt) && optind < argc - 1) { | |
219 | 256 | optarg = argv[optind + 1]; |
220 | 257 | optind += 2; |
221 | } else if (strstr("ltz" | |
258 | } else if (strstr("ltz?" | |
259 | #if ALSA | |
260 | "L" | |
261 | #endif | |
222 | 262 | #if RESAMPLE |
223 | 263 | "uR" |
224 | 264 | #endif |
228 | 268 | #if VISEXPORT |
229 | 269 | "v" |
230 | 270 | #endif |
271 | #if IR | |
272 | "i" | |
273 | #endif | |
274 | ||
231 | 275 | , opt)) { |
232 | 276 | optarg = NULL; |
233 | 277 | optind += 1; |
234 | 278 | } else { |
279 | fprintf(stderr, "\nOption error: -%s\n\n", opt); | |
235 | 280 | usage(argv[0]); |
236 | exit(0); | |
281 | exit(1); | |
237 | 282 | } |
238 | 283 | |
239 | 284 | switch (opt[0]) { |
240 | case 'o': | |
241 | output_device = optarg; | |
242 | break; | |
285 | case 'o': | |
286 | output_device = optarg; | |
287 | break; | |
243 | 288 | case 'a': |
244 | 289 | output_params = optarg; |
245 | 290 | break; |
254 | 299 | case 'c': |
255 | 300 | include_codecs = optarg; |
256 | 301 | break; |
302 | case 'C': | |
303 | if (atoi(optarg) > 0) { | |
304 | idle = atoi(optarg) * 1000; | |
305 | } | |
306 | break; | |
257 | 307 | case 'e': |
258 | 308 | exclude_codecs = optarg; |
259 | 309 | break; |
260 | case 'd': | |
310 | case 'd': | |
261 | 311 | { |
262 | 312 | char *l = strtok(optarg, "="); |
263 | 313 | char *v = strtok(NULL, "="); |
270 | 320 | if (!strcmp(l, "all") || !strcmp(l, "stream")) log_stream = new; |
271 | 321 | if (!strcmp(l, "all") || !strcmp(l, "decode")) log_decode = new; |
272 | 322 | if (!strcmp(l, "all") || !strcmp(l, "output")) log_output = new; |
323 | #if IR | |
324 | if (!strcmp(l, "all") || !strcmp(l, "ir")) log_ir = new; | |
325 | #endif | |
273 | 326 | } else { |
327 | fprintf(stderr, "\nDebug settings error: -d %s\n\n", optarg); | |
274 | 328 | usage(argv[0]); |
275 | exit(0); | |
329 | exit(1); | |
276 | 330 | } |
277 | 331 | } |
278 | break; | |
332 | break; | |
279 | 333 | case 'f': |
280 | 334 | logfile = optarg; |
281 | 335 | break; |
283 | 337 | { |
284 | 338 | int byte = 0; |
285 | 339 | char *tmp; |
286 | char *t = strtok(optarg, ":"); | |
287 | while (t && byte < 6) { | |
288 | mac[byte++] = (u8_t)strtoul(t, &tmp, 16); | |
289 | t = strtok(NULL, ":"); | |
340 | if (!strncmp(optarg, "00:04:20", 8)) { | |
341 | LOG_ERROR("ignoring mac address from hardware player range 00:04:20:**:**:**"); | |
342 | } else { | |
343 | char *t = strtok(optarg, ":"); | |
344 | while (t && byte < 6) { | |
345 | mac[byte++] = (u8_t)strtoul(t, &tmp, 16); | |
346 | t = strtok(NULL, ":"); | |
347 | } | |
290 | 348 | } |
291 | 349 | } |
350 | break; | |
351 | case 'M': | |
352 | modelname = optarg; | |
292 | 353 | break; |
293 | 354 | case 'r': |
294 | 355 | { |
348 | 409 | case 'p': |
349 | 410 | rt_priority = atoi(optarg); |
350 | 411 | if (rt_priority > 99 || rt_priority < 1) { |
412 | fprintf(stderr, "\nError: invalid priority: %s\n\n", optarg); | |
351 | 413 | usage(argv[0]); |
352 | exit(0); | |
353 | } | |
414 | exit(1); | |
415 | } | |
416 | break; | |
417 | #endif | |
418 | #if LINUX || FREEBSD | |
419 | case 'P': | |
420 | pidfile = optarg; | |
354 | 421 | break; |
355 | 422 | #endif |
356 | 423 | case 'l': |
357 | 424 | list_devices(); |
358 | 425 | exit(0); |
359 | 426 | break; |
427 | #if ALSA | |
428 | case 'L': | |
429 | list_mixers(output_device); | |
430 | exit(0); | |
431 | break; | |
432 | #endif | |
360 | 433 | #if RESAMPLE |
361 | 434 | case 'u': |
362 | 435 | case 'R': |
380 | 453 | visexport = true; |
381 | 454 | break; |
382 | 455 | #endif |
456 | #if ALSA | |
457 | case 'U': | |
458 | output_mixer_unmute = true; | |
459 | case 'V': | |
460 | if (output_mixer) { | |
461 | fprintf(stderr, "-U and -V option should not be used at same time\n"); | |
462 | exit(1); | |
463 | } | |
464 | output_mixer = optarg; | |
465 | break; | |
466 | #endif | |
467 | #if IR | |
468 | case 'i': | |
469 | if (optind < argc && argv[optind] && argv[optind][0] != '-') { | |
470 | lircrc = argv[optind++]; | |
471 | } else { | |
472 | lircrc = "~/.lircrc"; // liblirc_client will expand ~/ | |
473 | } | |
474 | break; | |
475 | #endif | |
383 | 476 | #if LINUX || FREEBSD |
384 | 477 | case 'z': |
385 | 478 | daemonize = true; |
388 | 481 | case 't': |
389 | 482 | license(); |
390 | 483 | exit(0); |
391 | default: | |
392 | break; | |
393 | } | |
394 | } | |
484 | case '?': | |
485 | usage(argv[0]); | |
486 | exit(0); | |
487 | default: | |
488 | fprintf(stderr, "Arg error: %s\n", argv[optind]); | |
489 | break; | |
490 | } | |
491 | } | |
395 | 492 | |
396 | 493 | // warn if command line includes something which isn't parsed |
397 | 494 | if (optind < argc) { |
495 | fprintf(stderr, "\nError: command line argument error\n\n"); | |
398 | 496 | usage(argv[0]); |
399 | exit(0); | |
497 | exit(1); | |
400 | 498 | } |
401 | 499 | |
402 | 500 | signal(SIGINT, sighandler); |
433 | 531 | } |
434 | 532 | |
435 | 533 | #if LINUX || FREEBSD |
534 | if (pidfile) { | |
535 | if (!(pidfp = fopen(pidfile, "w")) ) { | |
536 | fprintf(stderr, "Error opening pidfile %s: %s\n", pidfile, strerror(errno)); | |
537 | exit(1); | |
538 | } | |
539 | pidfile = realpath(pidfile, NULL); // daemonize will change cwd | |
540 | } | |
541 | ||
436 | 542 | if (daemonize) { |
437 | 543 | if (daemon(0, logfile ? 1 : 0)) { |
438 | 544 | fprintf(stderr, "error daemonizing: %s\n", strerror(errno)); |
439 | 545 | } |
440 | 546 | } |
547 | ||
548 | if (pidfp) { | |
549 | fprintf(pidfp, "%d\n", getpid()); | |
550 | fclose(pidfp); | |
551 | } | |
441 | 552 | #endif |
442 | 553 | |
443 | 554 | #if WIN |
450 | 561 | output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay); |
451 | 562 | } else { |
452 | 563 | #if ALSA |
453 | output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority); | |
564 | output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle, output_mixer, | |
565 | output_mixer_unmute); | |
454 | 566 | #endif |
455 | 567 | #if PORTAUDIO |
456 | output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay); | |
568 | output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle); | |
457 | 569 | #endif |
458 | 570 | } |
459 | 571 | |
475 | 587 | } |
476 | 588 | #endif |
477 | 589 | |
590 | #if IR | |
591 | if (lircrc) { | |
592 | ir_init(log_ir, lircrc); | |
593 | } | |
594 | #endif | |
595 | ||
478 | 596 | if (name && namefile) { |
479 | printf("-n and -N option should not be used at same time\n"); | |
480 | exit(0); | |
481 | } | |
482 | ||
483 | slimproto(log_slimproto, server, mac, name, namefile); | |
484 | ||
597 | fprintf(stderr, "-n and -N option should not be used at same time\n"); | |
598 | exit(1); | |
599 | } | |
600 | ||
601 | slimproto(log_slimproto, server, mac, name, namefile, modelname); | |
602 | ||
485 | 603 | decode_close(); |
486 | 604 | stream_close(); |
487 | 605 | |
496 | 614 | #endif |
497 | 615 | } |
498 | 616 | |
617 | #if IR | |
618 | ir_close(); | |
619 | #endif | |
620 | ||
499 | 621 | #if WIN |
500 | 622 | winsock_close(); |
501 | 623 | #endif |
502 | 624 | |
625 | #if LINUX || FREEBSD | |
626 | if (pidfile) { | |
627 | unlink(pidfile); | |
628 | free(pidfile); | |
629 | } | |
630 | #endif | |
631 | ||
503 | 632 | exit(0); |
504 | 633 | } |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
48 | 48 | |
49 | 49 | s32_t gainL = output.current_replay_gain ? gain(output.gainL, output.current_replay_gain) : output.gainL; |
50 | 50 | s32_t gainR = output.current_replay_gain ? gain(output.gainR, output.current_replay_gain) : output.gainR; |
51 | ||
51 | ||
52 | if (output.invert) { gainL = -gainL; gainR = -gainR; } | |
53 | ||
52 | 54 | frames = _buf_used(outputbuf) / BYTES_PER_FRAME; |
53 | 55 | silence = false; |
54 | 56 | |
219 | 221 | fade_gain = to_gain((float)cur_f / (float)dur_f); |
220 | 222 | gainL = gain(gainL, fade_gain); |
221 | 223 | gainR = gain(gainR, fade_gain); |
224 | if (output.invert) { gainL = -gainL; gainR = -gainR; } | |
222 | 225 | } |
223 | 226 | if (output.fade_dir == FADE_CROSS) { |
224 | 227 | // cross fade requires special treatment - performed later based on these values |
234 | 237 | } |
235 | 238 | gainL = output.gainL; |
236 | 239 | gainR = output.gainR; |
240 | if (output.invert) { gainL = -gainL; gainR = -gainR; } | |
237 | 241 | cross_ptr = (s32_t *)(output.fade_end + cur_f * BYTES_PER_FRAME); |
238 | 242 | } else { |
239 | 243 | LOG_INFO("unable to continue crossfade - too few samples"); |
332 | 336 | } |
333 | 337 | } |
334 | 338 | |
335 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[]) { | |
339 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle) { | |
336 | 340 | unsigned i; |
337 | 341 | |
338 | 342 | loglevel = level; |
362 | 366 | dop_silence_frames((u32_t *)silencebuf_dop, MAX_SILENCE_FRAMES); |
363 | 367 | ) |
364 | 368 | |
365 | output.state = OUTPUT_STOPPED; | |
369 | LOG_DEBUG("idle timeout: %u", idle); | |
370 | ||
371 | output.state = idle ? OUTPUT_OFF: OUTPUT_STOPPED; | |
366 | 372 | output.device = device; |
367 | 373 | output.fade = FADE_INACTIVE; |
374 | output.invert = false; | |
368 | 375 | output.error_opening = false; |
376 | output.idle_to = (u32_t) idle; | |
369 | 377 | |
370 | 378 | if (!rates[0]) { |
371 | 379 | if (!test_open(output.device, output.supported_rates)) { |
386 | 394 | } |
387 | 395 | } |
388 | 396 | if (!output.default_sample_rate) { |
389 | output.default_sample_rate = rates[0]; | |
397 | output.default_sample_rate = output.supported_rates[0]; | |
390 | 398 | } |
391 | 399 | |
392 | 400 | output.current_sample_rate = output.default_sample_rate; |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
26 | 26 | #include <alsa/asoundlib.h> |
27 | 27 | #include <sys/mman.h> |
28 | 28 | #include <malloc.h> |
29 | #include <math.h> | |
29 | 30 | |
30 | 31 | #define MAX_DEVICE_LEN 128 |
31 | 32 | |
48 | 49 | bool mmap; |
49 | 50 | bool reopen; |
50 | 51 | u8_t *write_buf; |
52 | const char *volume_mixer_name; | |
53 | int volume_mixer_index; | |
51 | 54 | } alsa; |
52 | 55 | |
53 | 56 | static snd_pcm_t *pcmp = NULL; |
92 | 95 | printf("\n"); |
93 | 96 | } |
94 | 97 | |
98 | void list_mixers(const char *output_device) { | |
99 | int err; | |
100 | snd_mixer_t *handle; | |
101 | snd_mixer_selem_id_t *sid; | |
102 | snd_mixer_elem_t *elem; | |
103 | snd_mixer_selem_id_alloca(&sid); | |
104 | ||
105 | LOG_INFO("listing mixers for: %s", output_device); | |
106 | ||
107 | if ((err = snd_mixer_open(&handle, 0)) < 0) { | |
108 | LOG_ERROR("open error: %s", snd_strerror(err)); | |
109 | return; | |
110 | } | |
111 | if ((err = snd_mixer_attach(handle, output_device)) < 0) { | |
112 | LOG_ERROR("attach error: %s", snd_strerror(err)); | |
113 | snd_mixer_close(handle); | |
114 | return; | |
115 | } | |
116 | if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { | |
117 | LOG_ERROR("register error: %s", snd_strerror(err)); | |
118 | snd_mixer_close(handle); | |
119 | return; | |
120 | } | |
121 | if ((err = snd_mixer_load(handle)) < 0) { | |
122 | LOG_ERROR("load error: %s", snd_strerror(err)); | |
123 | snd_mixer_close(handle); | |
124 | return; | |
125 | } | |
126 | ||
127 | printf("Volume controls for %s\n", output_device); | |
128 | for (elem = snd_mixer_first_elem(handle); elem; elem = snd_mixer_elem_next(elem)) { | |
129 | if (snd_mixer_selem_has_playback_volume(elem)) { | |
130 | snd_mixer_selem_get_id(elem, sid); | |
131 | printf(" %s", snd_mixer_selem_id_get_name(sid)); | |
132 | if (snd_mixer_selem_id_get_index(sid)) { | |
133 | printf(",%d", snd_mixer_selem_id_get_index(sid)); | |
134 | } | |
135 | printf("\n"); | |
136 | } | |
137 | } | |
138 | printf("\n"); | |
139 | ||
140 | snd_mixer_close(handle); | |
141 | } | |
142 | ||
143 | #define MINVOL_DB 72 // LMS volume map for SqueezePlay sends values in range ~ -72..0 dB | |
144 | ||
145 | static void set_mixer(const char *device, const char *mixer, int mixer_index, bool setmax, float ldB, float rdB) { | |
146 | int err; | |
147 | long nleft, nright; | |
148 | long min, max; | |
149 | snd_mixer_t *handle; | |
150 | snd_mixer_selem_id_t *sid; | |
151 | snd_mixer_elem_t* elem; | |
152 | ||
153 | if ((err = snd_mixer_open(&handle, 0)) < 0) { | |
154 | LOG_ERROR("open error: %s", snd_strerror(err)); | |
155 | return; | |
156 | } | |
157 | if ((err = snd_mixer_attach(handle, device)) < 0) { | |
158 | LOG_ERROR("attach error: %s", snd_strerror(err)); | |
159 | snd_mixer_close(handle); | |
160 | return; | |
161 | } | |
162 | if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { | |
163 | LOG_ERROR("register error: %s", snd_strerror(err)); | |
164 | snd_mixer_close(handle); | |
165 | return; | |
166 | } | |
167 | if ((err = snd_mixer_load(handle)) < 0) { | |
168 | LOG_ERROR("load error: %s", snd_strerror(err)); | |
169 | snd_mixer_close(handle); | |
170 | return; | |
171 | } | |
172 | ||
173 | snd_mixer_selem_id_alloca(&sid); | |
174 | ||
175 | snd_mixer_selem_id_set_index(sid, mixer_index); | |
176 | snd_mixer_selem_id_set_name(sid, mixer); | |
177 | ||
178 | if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) { | |
179 | LOG_ERROR("error find selem %s", mixer); | |
180 | snd_mixer_close(handle); | |
181 | return; | |
182 | } | |
183 | ||
184 | if (snd_mixer_selem_has_playback_switch(elem)) { | |
185 | snd_mixer_selem_set_playback_switch_all(elem, 1); // unmute | |
186 | } | |
187 | ||
188 | err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max); | |
189 | ||
190 | if (err < 0 || max - min < 1000) { | |
191 | // unable to get db range or range is less than 10dB - ignore and set using raw values | |
192 | if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0) { | |
193 | LOG_ERROR("unable to get volume raw range"); | |
194 | } else { | |
195 | long lraw, rraw; | |
196 | if (setmax) { | |
197 | lraw = rraw = max; | |
198 | } else { | |
199 | lraw = ((ldB > -MINVOL_DB ? MINVOL_DB + floor(ldB) : 0) / MINVOL_DB * (max-min)) + min; | |
200 | rraw = ((rdB > -MINVOL_DB ? MINVOL_DB + floor(rdB) : 0) / MINVOL_DB * (max-min)) + min; | |
201 | } | |
202 | LOG_DEBUG("setting vol raw [%ld..%ld]", min, max); | |
203 | if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, lraw)) < 0) { | |
204 | LOG_ERROR("error setting left volume: %s", snd_strerror(err)); | |
205 | } | |
206 | if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, rraw)) < 0) { | |
207 | LOG_ERROR("error setting right volume: %s", snd_strerror(err)); | |
208 | } | |
209 | } | |
210 | } else { | |
211 | // set db directly | |
212 | LOG_DEBUG("setting vol dB [%ld..%ld]", min, max); | |
213 | if (setmax) { | |
214 | // set to 0dB if available as this should be max volume for music recored at max pcm values | |
215 | if (max >= 0 && min <= 0) { | |
216 | ldB = rdB = 0; | |
217 | } else { | |
218 | ldB = rdB = max; | |
219 | } | |
220 | } | |
221 | if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_LEFT, 100 * ldB, 1)) < 0) { | |
222 | LOG_ERROR("error setting left volume: %s", snd_strerror(err)); | |
223 | } | |
224 | if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_RIGHT, 100 * rdB, 1)) < 0) { | |
225 | LOG_ERROR("error setting right volume: %s", snd_strerror(err)); | |
226 | } | |
227 | } | |
228 | ||
229 | if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &nleft)) < 0) { | |
230 | LOG_ERROR("error getting left vol: %s", snd_strerror(err)); | |
231 | } | |
232 | if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &nright)) < 0) { | |
233 | LOG_ERROR("error getting right vol: %s", snd_strerror(err)); | |
234 | } | |
235 | ||
236 | LOG_DEBUG("%s left: %3.1fdB -> %ld right: %3.1fdB -> %ld", mixer, ldB, nleft, rdB, nright); | |
237 | ||
238 | snd_mixer_close(handle); | |
239 | } | |
240 | ||
241 | void set_volume(unsigned left, unsigned right) { | |
242 | float ldB, rdB; | |
243 | ||
244 | if (!alsa.volume_mixer_name) { | |
245 | LOG_DEBUG("setting internal gain left: %u right: %u", left, right); | |
246 | LOCK; | |
247 | output.gainL = left; | |
248 | output.gainR = right; | |
249 | UNLOCK; | |
250 | return; | |
251 | } else { | |
252 | LOCK; | |
253 | output.gainL = FIXED_ONE; | |
254 | output.gainR = FIXED_ONE; | |
255 | UNLOCK; | |
256 | } | |
257 | ||
258 | // convert 16.16 fixed point to dB | |
259 | ldB = 20 * log10( left / 65536.0F ); | |
260 | rdB = 20 * log10( right / 65536.0F ); | |
261 | ||
262 | set_mixer(output.device, alsa.volume_mixer_name, alsa.volume_mixer_index, false, ldB, rdB); | |
263 | } | |
264 | ||
95 | 265 | static void *alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) { |
96 | 266 | va_list args; |
97 | 267 | if ((loglevel >= lINFO && err == 0) || loglevel >= lDEBUG) { |
180 | 350 | LOG_ERROR("device name too long: %s", device); |
181 | 351 | return -1; |
182 | 352 | } |
353 | ||
354 | LOG_INFO("opening device at: %u", sample_rate); | |
183 | 355 | |
184 | 356 | bool retry; |
185 | 357 | do { |
379 | 551 | if (silence) { |
380 | 552 | inputptr = (s32_t *) silencebuf_dop; |
381 | 553 | } |
382 | update_dop_marker((u32_t *) inputptr, out_frames); | |
554 | update_dop((u32_t *) inputptr, out_frames, output.invert && !silence); | |
383 | 555 | } |
384 | 556 | ) |
385 | 557 | |
437 | 609 | |
438 | 610 | static void *output_thread(void *arg) { |
439 | 611 | bool start = true; |
440 | bool output_off = false, probe_device = (arg != NULL); | |
612 | bool output_off = (output.state == OUTPUT_OFF); | |
613 | bool probe_device = (arg != NULL); | |
441 | 614 | int err; |
442 | 615 | |
443 | 616 | while (running) { |
547 | 720 | continue; |
548 | 721 | } |
549 | 722 | |
550 | // restrict avail in writei mode as write_buf is restricted to period_size | |
551 | if (!alsa.mmap) { | |
723 | // restrict avail to within sensible limits as alsa drivers can return erroneous large values | |
724 | // in writei mode restrict to period_size due to size of write_buf | |
725 | if (alsa.mmap) { | |
726 | avail = min(avail, alsa.buffer_size); | |
727 | } else { | |
552 | 728 | avail = min(avail, alsa.period_size); |
553 | 729 | } |
554 | 730 | |
612 | 788 | static pthread_t thread; |
613 | 789 | |
614 | 790 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], |
615 | unsigned rate_delay, unsigned rt_priority) { | |
791 | unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute) { | |
616 | 792 | |
617 | 793 | unsigned alsa_buffer = ALSA_BUFFER_TIME; |
618 | 794 | unsigned alsa_period = ALSA_PERIOD_COUNT; |
620 | 796 | bool alsa_mmap = true; |
621 | 797 | bool alsa_reopen = false; |
622 | 798 | |
799 | char *volume_mixer_name = next_param(volume_mixer, ','); | |
800 | char *volume_mixer_index = next_param(NULL, ','); | |
801 | ||
623 | 802 | char *t = next_param(params, ':'); |
624 | 803 | char *c = next_param(NULL, ':'); |
625 | 804 | char *s = next_param(NULL, ':'); |
642 | 821 | alsa.write_buf = NULL; |
643 | 822 | alsa.format = 0; |
644 | 823 | alsa.reopen = alsa_reopen; |
824 | ||
825 | if (!mixer_unmute) { | |
826 | alsa.volume_mixer_name = volume_mixer_name; | |
827 | alsa.volume_mixer_index = volume_mixer_index ? atoi(volume_mixer_index) : 0; | |
828 | } | |
829 | ||
645 | 830 | output.format = 0; |
646 | 831 | output.buffer = alsa_buffer; |
647 | 832 | output.period = alsa_period; |
661 | 846 | |
662 | 847 | snd_lib_error_set_handler((snd_lib_error_handler_t)alsa_error_handler); |
663 | 848 | |
664 | output_init_common(level, device, output_buf_size, rates); | |
849 | output_init_common(level, device, output_buf_size, rates, idle); | |
850 | ||
851 | if (mixer_unmute && volume_mixer_name) { | |
852 | set_mixer(output.device, volume_mixer_name, volume_mixer_index ? atoi(volume_mixer_index) : 0, true, 0, 0); | |
853 | } | |
665 | 854 | |
666 | 855 | #if LINUX |
667 | 856 | // RT linux - aim to avoid pagefaults by locking memory: |
672 | 861 | LOG_INFO("memory locked"); |
673 | 862 | } |
674 | 863 | |
675 | mallopt(M_TRIM_THRESHOLD, -1); | |
676 | mallopt(M_MMAP_MAX, 0); | |
864 | mallopt(M_TRIM_THRESHOLD, -1); | |
865 | mallopt(M_MMAP_MAX, 0); | |
677 | 866 | |
678 | 867 | touch_memory(silencebuf, MAX_SILENCE_FRAMES * BYTES_PER_FRAME); |
679 | 868 | touch_memory(outputbuf->buf, outputbuf->size); |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
50 | 50 | #endif |
51 | 51 | |
52 | 52 | void list_devices(void) { |
53 | PaError err; | |
53 | PaError err; | |
54 | 54 | int i; |
55 | 55 | |
56 | 56 | if ((err = Pa_Initialize()) != paNoError) { |
66 | 66 | } |
67 | 67 | printf("\n"); |
68 | 68 | |
69 | if ((err = Pa_Terminate()) != paNoError) { | |
69 | if ((err = Pa_Terminate()) != paNoError) { | |
70 | 70 | LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); |
71 | 71 | } |
72 | } | |
73 | ||
74 | void set_volume(unsigned left, unsigned right) { | |
75 | LOG_DEBUG("setting internal gain left: %u right: %u", left, right); | |
76 | LOCK; | |
77 | output.gainL = left; | |
78 | output.gainR = right; | |
79 | UNLOCK; | |
72 | 80 | } |
73 | 81 | |
74 | 82 | static int pa_device_id(const char *device) { |
305 | 313 | |
306 | 314 | IF_DSD( |
307 | 315 | if (output.dop) { |
308 | update_dop_marker((u32_t *) outputbuf->readp, out_frames); | |
316 | update_dop((u32_t *) outputbuf->readp, out_frames, output.invert); | |
309 | 317 | } |
310 | 318 | ) |
311 | 319 | |
318 | 326 | IF_DSD( |
319 | 327 | if (output.dop) { |
320 | 328 | buf = silencebuf_dop; |
321 | update_dop_marker((u32_t *) buf, out_frames); | |
329 | update_dop((u32_t *) buf, out_frames, false); // don't invert silence | |
322 | 330 | } |
323 | 331 | ) |
324 | 332 | |
376 | 384 | return ret; |
377 | 385 | } |
378 | 386 | |
379 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay) { | |
387 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, | |
388 | unsigned idle) { | |
380 | 389 | PaError err; |
381 | 390 | unsigned latency = 0; |
382 | 391 | int osx_playnice = -1; |
403 | 412 | |
404 | 413 | LOG_INFO("requested latency: %u", output.latency); |
405 | 414 | |
406 | if ((err = Pa_Initialize()) != paNoError) { | |
415 | if ((err = Pa_Initialize()) != paNoError) { | |
407 | 416 | LOG_WARN("error initialising port audio: %s", Pa_GetErrorText(err)); |
408 | 417 | exit(0); |
409 | 418 | } |
410 | 419 | |
411 | output_init_common(level, device, output_buf_size, rates); | |
420 | output_init_common(level, device, output_buf_size, rates, idle); | |
412 | 421 | |
413 | 422 | LOCK; |
414 | 423 | |
433 | 442 | } |
434 | 443 | } |
435 | 444 | |
436 | if ((err = Pa_Terminate()) != paNoError) { | |
445 | if ((err = Pa_Terminate()) != paNoError) { | |
437 | 446 | LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); |
438 | 447 | } |
439 | 448 |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
66 | 66 | if (silence) { |
67 | 67 | obuf = silencebuf_dop; |
68 | 68 | } |
69 | update_dop_marker((u32_t *)obuf, out_frames); | |
69 | update_dop((u32_t *)obuf, out_frames, output.invert && !silence); | |
70 | 70 | } |
71 | 71 | ) |
72 | 72 | |
149 | 149 | rates[0] = 44100; |
150 | 150 | } |
151 | 151 | |
152 | output_init_common(level, "-", output_buf_size, rates); | |
152 | output_init_common(level, "-", output_buf_size, rates, 0); | |
153 | 153 | |
154 | 154 | #if LINUX || OSX || FREEBSD |
155 | 155 | pthread_attr_t attr; |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
103 | 103 | ptr += 8; |
104 | 104 | _buf_inc_readp(streambuf, ptr - streambuf->readp); |
105 | 105 | audio_left = len; |
106 | LOG_INFO("audio size: %u", audio_left); | |
106 | 107 | limit = true; |
107 | 108 | return; |
108 | 109 | } |
112 | 113 | // following 4 bytes is blocksize - ignored |
113 | 114 | ptr += 8 + 8; |
114 | 115 | _buf_inc_readp(streambuf, ptr + offset - streambuf->readp); |
115 | audio_left = len; | |
116 | audio_left = len - 8 - offset; | |
117 | LOG_INFO("audio size: %u", audio_left); | |
116 | 118 | limit = true; |
117 | 119 | return; |
118 | 120 | } |
188 | 190 | if (decode.new_stream) { |
189 | 191 | LOG_INFO("setting track_start"); |
190 | 192 | LOCK_O_not_direct; |
191 | output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates); | |
193 | output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates); | |
192 | 194 | output.track_start = outputbuf->writep; |
193 | 195 | IF_DSD( output.next_dop = false; ) |
194 | 196 | if (output.fade_mode) _checkfade(true); |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
73 | 73 | SOXR(r, process, r->resampler, process->inbuf, process->in_frames, &idone, process->outbuf, process->max_out_frames, &odone); |
74 | 74 | if (error) { |
75 | 75 | LOG_INFO("soxr_process error: %s", soxr_strerror(error)); |
76 | return; | |
76 | 77 | } |
77 | 78 | |
78 | 79 | if (idone != process->in_frames) { |
99 | 100 | soxr_error_t error = SOXR(r, process, r->resampler, NULL, 0, NULL, process->outbuf, process->max_out_frames, &odone); |
100 | 101 | if (error) { |
101 | 102 | LOG_INFO("soxr_process error: %s", soxr_strerror(error)); |
103 | return true; | |
102 | 104 | } |
103 | 105 | |
104 | 106 | process->out_frames = odone; |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
45 | 45 | extern struct decodestate decode; |
46 | 46 | |
47 | 47 | extern struct codec *codecs[]; |
48 | #if IR | |
49 | extern struct irstate ir; | |
50 | #endif | |
48 | 51 | |
49 | 52 | event_event wake_e; |
50 | 53 | |
54 | 57 | #define UNLOCK_O mutex_unlock(outputbuf->mutex) |
55 | 58 | #define LOCK_D mutex_lock(decode.mutex) |
56 | 59 | #define UNLOCK_D mutex_unlock(decode.mutex) |
60 | #if IR | |
61 | #define LOCK_I mutex_lock(ir.mutex) | |
62 | #define UNLOCK_I mutex_unlock(ir.mutex) | |
63 | #endif | |
57 | 64 | |
58 | 65 | static struct { |
59 | 66 | u32_t updated; |
100 | 107 | } |
101 | 108 | |
102 | 109 | static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap, u8_t mac[6]) { |
103 | const char *base_cap = "Model=squeezelite,ModelName=SqueezeLite,AccuratePlayPoints=1,HasDigitalOut=1"; | |
110 | const char *base_cap = "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION; | |
104 | 111 | struct HELO_packet pkt; |
105 | 112 | |
106 | 113 | memset(&pkt, 0, sizeof(pkt)); |
225 | 232 | send_packet((u8_t *)name, strlen(name) + 1); |
226 | 233 | } |
227 | 234 | |
235 | #if IR | |
236 | void sendIR(u32_t code, u32_t ts) { | |
237 | struct IR_packet pkt; | |
238 | ||
239 | memset(&pkt, 0, sizeof(pkt)); | |
240 | memcpy(&pkt.opcode, "IR ", 4); | |
241 | pkt.length = htonl(sizeof(pkt) - 8); | |
242 | ||
243 | packN(&pkt.jiffies, ts); | |
244 | pkt.ir_code = htonl(code); | |
245 | ||
246 | LOG_DEBUG("IR: ir code: 0x%x ts: %u", code, ts); | |
247 | ||
248 | send_packet((u8_t *)&pkt, sizeof(pkt)); | |
249 | } | |
250 | #endif | |
251 | ||
228 | 252 | static void process_strm(u8_t *pkt, int len) { |
229 | 253 | struct strm_packet *strm = (struct strm_packet *)pkt; |
230 | 254 | |
256 | 280 | unsigned interval = unpackN(&strm->replay_gain); |
257 | 281 | LOCK_O; |
258 | 282 | output.pause_frames = interval * status.current_sample_rate / 1000; |
259 | output.state = interval ? OUTPUT_PAUSE_FRAMES : OUTPUT_STOPPED; | |
283 | if (interval) { | |
284 | output.state = OUTPUT_PAUSE_FRAMES; | |
285 | } else { | |
286 | output.state = OUTPUT_STOPPED; | |
287 | output.stop_time = gettime_ms(); | |
288 | } | |
260 | 289 | UNLOCK_O; |
261 | 290 | if (!interval) sendSTAT("STMp", 0); |
262 | 291 | LOG_DEBUG("pause interval: %u", interval); |
279 | 308 | output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING; |
280 | 309 | output.start_at = jiffies; |
281 | 310 | UNLOCK_O; |
282 | LOCK_D; | |
283 | decode.state = DECODE_RUNNING; | |
284 | UNLOCK_D; | |
285 | 311 | LOG_DEBUG("unpause at: %u now: %u", jiffies, gettime_ms()); |
286 | 312 | sendSTAT("STMr", 0); |
287 | 313 | } |
326 | 352 | output.next_replay_gain = unpackN(&strm->replay_gain); |
327 | 353 | output.fade_mode = strm->transition_type - '0'; |
328 | 354 | output.fade_secs = strm->transition_period; |
355 | output.invert = (strm->flags & 0x03) == 0x03; | |
329 | 356 | LOG_DEBUG("set fade mode: %u", output.fade_mode); |
330 | 357 | UNLOCK_O; |
331 | 358 | } |
370 | 397 | if (!aude->enable_spdif && output.state != OUTPUT_OFF) { |
371 | 398 | output.state = OUTPUT_OFF; |
372 | 399 | } |
373 | if (aude->enable_spdif && output.state == OUTPUT_OFF) { | |
400 | if (aude->enable_spdif && output.state == OUTPUT_OFF && !output.idle_to) { | |
374 | 401 | output.state = OUTPUT_STOPPED; |
402 | output.stop_time = gettime_ms(); | |
375 | 403 | } |
376 | 404 | UNLOCK_O; |
377 | 405 | } |
383 | 411 | |
384 | 412 | LOG_DEBUG("audg gainL: %u gainR: %u adjust: %u", audg->gainL, audg->gainR, audg->adjust); |
385 | 413 | |
386 | LOCK_O; | |
387 | output.gainL = audg->adjust ? audg->gainL : FIXED_ONE; | |
388 | output.gainR = audg->adjust ? audg->gainR : FIXED_ONE; | |
389 | UNLOCK_O; | |
414 | set_volume(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE); | |
390 | 415 | } |
391 | 416 | |
392 | 417 | static void process_setd(u8_t *pkt, int len) { |
563 | 588 | bool _sendSTMo = false; |
564 | 589 | bool _sendSTMn = false; |
565 | 590 | bool _stream_disconnect = false; |
591 | bool _start_output = false; | |
592 | decode_state _decode_state; | |
566 | 593 | disconnect_code disconnect_code; |
567 | 594 | static char header[MAX_HEADER]; |
568 | 595 | size_t header_len = 0; |
596 | #if IR | |
597 | bool _sendIR = false; | |
598 | u32_t ir_code, ir_ts; | |
599 | #endif | |
569 | 600 | last = now; |
570 | 601 | |
571 | 602 | LOCK_S; |
593 | 624 | stream.meta_send = false; |
594 | 625 | } |
595 | 626 | UNLOCK_S; |
627 | ||
628 | LOCK_D; | |
629 | if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) && !sentSTMl | |
630 | && decode.state == DECODE_READY) { | |
631 | if (autostart == 0) { | |
632 | decode.state = DECODE_RUNNING; | |
633 | _sendSTMl = true; | |
634 | sentSTMl = true; | |
635 | } else if (autostart == 1) { | |
636 | decode.state = DECODE_RUNNING; | |
637 | _start_output = true; | |
638 | } | |
639 | // autostart 2 and 3 require cont to be received first | |
640 | } | |
641 | if (decode.state == DECODE_COMPLETE || decode.state == DECODE_ERROR) { | |
642 | if (decode.state == DECODE_COMPLETE) _sendSTMd = true; | |
643 | if (decode.state == DECODE_ERROR) _sendSTMn = true; | |
644 | decode.state = DECODE_STOPPED; | |
645 | if (status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) { | |
646 | _stream_disconnect = true; | |
647 | } | |
648 | } | |
649 | _decode_state = decode.state; | |
650 | UNLOCK_D; | |
596 | 651 | |
597 | 652 | LOCK_O; |
598 | 653 | status.output_full = _buf_used(outputbuf); |
613 | 668 | output.pa_reopen = false; |
614 | 669 | } |
615 | 670 | #endif |
616 | if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT) { | |
671 | if (_start_output && (output.state == OUTPUT_STOPPED || OUTPUT_OFF)) { | |
672 | output.state = OUTPUT_BUFFER; | |
673 | } | |
674 | if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT && | |
675 | _decode_state == DECODE_STOPPED) { | |
617 | 676 | _sendSTMu = true; |
618 | 677 | sentSTMu = true; |
678 | LOG_DEBUG("output underrun"); | |
679 | output.state = OUTPUT_STOPPED; | |
680 | output.stop_time = now; | |
619 | 681 | } |
620 | 682 | if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) { |
621 | 683 | _sendSTMo = true; |
622 | 684 | sentSTMo = true; |
623 | 685 | } |
624 | UNLOCK_O; | |
625 | ||
626 | LOCK_D; | |
627 | if (decode.state == DECODE_RUNNING && now - status.last > 1000) { | |
686 | if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) { | |
687 | output.state = OUTPUT_OFF; | |
688 | LOG_DEBUG("output timeout"); | |
689 | } | |
690 | if (output.state == OUTPUT_RUNNING && now - status.last > 1000) { | |
628 | 691 | _sendSTMt = true; |
629 | 692 | status.last = now; |
630 | 693 | } |
631 | if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) && !sentSTMl | |
632 | && decode.state == DECODE_STOPPED) { | |
633 | if (autostart == 0) { | |
634 | _sendSTMl = true; | |
635 | sentSTMl = true; | |
636 | } else if (autostart == 1) { | |
637 | decode.state = DECODE_RUNNING; | |
638 | LOCK_O; | |
639 | if (output.state == OUTPUT_STOPPED) { | |
640 | output.state = OUTPUT_BUFFER; | |
641 | } | |
642 | UNLOCK_O; | |
643 | } | |
644 | // autostart 2 and 3 require cont to be received first | |
645 | } | |
646 | if (decode.state == DECODE_COMPLETE || decode.state == DECODE_ERROR) { | |
647 | if (decode.state == DECODE_COMPLETE) _sendSTMd = true; | |
648 | if (decode.state == DECODE_ERROR) _sendSTMn = true; | |
649 | decode.state = DECODE_STOPPED; | |
650 | if (status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) { | |
651 | _stream_disconnect = true; | |
652 | } | |
653 | } | |
654 | UNLOCK_D; | |
655 | ||
694 | UNLOCK_O; | |
695 | ||
696 | #if IR | |
697 | LOCK_I; | |
698 | if (ir.code) { | |
699 | _sendIR = true; | |
700 | ir_code = ir.code; | |
701 | ir_ts = ir.ts; | |
702 | ir.code = 0; | |
703 | } | |
704 | UNLOCK_I; | |
705 | #endif | |
706 | ||
656 | 707 | if (_stream_disconnect) stream_disconnect(); |
657 | 708 | |
658 | 709 | // send packets once locks released as packet sending can block |
666 | 717 | if (_sendSTMn) sendSTAT("STMn", 0); |
667 | 718 | if (_sendRESP) sendRESP(header, header_len); |
668 | 719 | if (_sendMETA) sendMETA(header, header_len); |
720 | #if IR | |
721 | if (_sendIR) sendIR(ir_code, ir_ts); | |
722 | #endif | |
669 | 723 | } |
670 | 724 | } |
671 | 725 | } |
676 | 730 | } |
677 | 731 | |
678 | 732 | in_addr_t discover_server(void) { |
679 | struct sockaddr_in d; | |
680 | struct sockaddr_in s; | |
733 | struct sockaddr_in d; | |
734 | struct sockaddr_in s; | |
681 | 735 | char *buf; |
682 | 736 | struct pollfd pollinfo; |
683 | 737 | |
689 | 743 | buf = "e"; |
690 | 744 | |
691 | 745 | memset(&d, 0, sizeof(d)); |
692 | d.sin_family = AF_INET; | |
746 | d.sin_family = AF_INET; | |
693 | 747 | d.sin_port = htons(PORT); |
694 | d.sin_addr.s_addr = htonl(INADDR_BROADCAST); | |
748 | d.sin_addr.s_addr = htonl(INADDR_BROADCAST); | |
695 | 749 | |
696 | 750 | pollinfo.fd = disc_sock; |
697 | 751 | pollinfo.events = POLLIN; |
719 | 773 | return s.sin_addr.s_addr; |
720 | 774 | } |
721 | 775 | |
722 | void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile) { | |
723 | struct sockaddr_in serv_addr; | |
724 | static char fixed_cap[128], var_cap[128] = ""; | |
776 | #define FIXED_CAP_LEN 256 | |
777 | #define VAR_CAP_LEN 128 | |
778 | ||
779 | void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname) { | |
780 | struct sockaddr_in serv_addr; | |
781 | static char fixed_cap[FIXED_CAP_LEN], var_cap[VAR_CAP_LEN] = ""; | |
725 | 782 | bool reconnect = false; |
726 | 783 | unsigned failed_connect = 0; |
727 | 784 | unsigned slimproto_port = 0; |
771 | 828 | if (!running) return; |
772 | 829 | |
773 | 830 | LOCK_O; |
774 | sprintf(fixed_cap, ",MaxSampleRate=%u", output.supported_rates[0]); | |
831 | snprintf(fixed_cap, FIXED_CAP_LEN, ",ModelName=%s,MaxSampleRate=%u", modelname ? modelname : MODEL_NAME_STRING, | |
832 | output.supported_rates[0]); | |
775 | 833 | |
776 | 834 | for (i = 0; i < MAX_CODECS; i++) { |
777 | if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < 128 - 10) { | |
835 | if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < FIXED_CAP_LEN - 10) { | |
778 | 836 | strcat(fixed_cap, ","); |
779 | 837 | strcat(fixed_cap, codecs[i]->types); |
780 | 838 | } |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
87 | 87 | // data |
88 | 88 | }; |
89 | 89 | |
90 | #if IR | |
91 | struct IR_packet { | |
92 | char opcode[4]; | |
93 | u32_t length; | |
94 | u32_t jiffies; | |
95 | u8_t format; // ignored by server | |
96 | u8_t bits; // ignored by server | |
97 | u32_t ir_code; | |
98 | }; | |
99 | #endif | |
100 | ||
90 | 101 | // from S:P:Squeezebox stream_s |
91 | 102 | struct strm_packet { |
92 | 103 | char opcode[4]; |
154 | 165 | // codec open - this is an extension to slimproto to allow the server to read the header and then return decode params |
155 | 166 | struct codc_packet { |
156 | 167 | char opcode[4]; |
157 | u8_t format; | |
168 | u8_t format; | |
158 | 169 | u8_t pcm_sample_size; |
159 | 170 | u8_t pcm_sample_rate; |
160 | 171 | u8_t pcm_channels; |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
17 | 17 | * |
18 | 18 | */ |
19 | 19 | |
20 | // make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, DSD, LINKALL to influence build | |
21 | ||
22 | #define VERSION "v1.6.4" | |
20 | // make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, IR, DSD, LINKALL to influence build | |
21 | ||
22 | #define VERSION "v1.8" | |
23 | ||
24 | #if !defined(MODEL_NAME) | |
25 | #define MODEL_NAME SqueezeLite | |
26 | #endif | |
27 | ||
28 | #define QUOTE(name) #name | |
29 | #define STR(macro) QUOTE(macro) | |
30 | #define MODEL_NAME_STRING STR(MODEL_NAME) | |
23 | 31 | |
24 | 32 | // build detection |
25 | 33 | #if defined(linux) |
99 | 107 | #define VISEXPORT 0 |
100 | 108 | #endif |
101 | 109 | |
110 | #if LINUX && defined(IR) | |
111 | #undef IR | |
112 | #define IR 1 | |
113 | #else | |
114 | #define IR 0 | |
115 | #endif | |
116 | ||
102 | 117 | #if defined(DSD) |
103 | 118 | #undef DSD |
104 | 119 | #define DSD 1 |
132 | 147 | #define LIBAVCODEC "libavcodec.so.%d" |
133 | 148 | #define LIBAVFORMAT "libavformat.so.%d" |
134 | 149 | #define LIBSOXR "libsoxr.so.0" |
150 | #define LIBLIRC "liblirc_client.so.0" | |
135 | 151 | #endif |
136 | 152 | |
137 | 153 | #if OSX |
212 | 228 | #define STREAM_THREAD_STACK_SIZE 64 * 1024 |
213 | 229 | #define DECODE_THREAD_STACK_SIZE 128 * 1024 |
214 | 230 | #define OUTPUT_THREAD_STACK_SIZE 64 * 1024 |
231 | #define IR_THREAD_STACK_SIZE 64 * 1024 | |
215 | 232 | #define thread_t pthread_t; |
216 | 233 | #define closesocket(s) close(s) |
217 | 234 | #define last_error() errno |
408 | 425 | void buf_destroy(struct buffer *buf); |
409 | 426 | |
410 | 427 | // slimproto.c |
411 | void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile); | |
428 | void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname); | |
412 | 429 | void slimproto_stop(void); |
413 | 430 | void wake_controller(void); |
414 | 431 | |
439 | 456 | bool stream_disconnect(void); |
440 | 457 | |
441 | 458 | // decode.c |
442 | typedef enum { DECODE_STOPPED = 0, DECODE_RUNNING, DECODE_COMPLETE, DECODE_ERROR } decode_state; | |
459 | typedef enum { DECODE_STOPPED = 0, DECODE_READY, DECODE_RUNNING, DECODE_COMPLETE, DECODE_ERROR } decode_state; | |
443 | 460 | |
444 | 461 | struct decodestate { |
445 | 462 | decode_state state; |
543 | 560 | u8_t *track_start; // set in decode thread |
544 | 561 | u32_t gainL; // set by slimproto |
545 | 562 | u32_t gainR; // set by slimproto |
563 | bool invert; // set by slimproto | |
546 | 564 | u32_t next_replay_gain; // set by slimproto |
547 | 565 | unsigned threshold; // set by slimproto |
548 | 566 | fade_state fade; |
553 | 571 | unsigned fade_secs; // set by slimproto |
554 | 572 | unsigned rate_delay; |
555 | 573 | bool delay_active; |
574 | u32_t stop_time; | |
575 | u32_t idle_to; | |
556 | 576 | #if DSD |
557 | 577 | bool next_dop; // set in decode thread |
558 | 578 | bool dop; |
561 | 581 | #endif |
562 | 582 | }; |
563 | 583 | |
564 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[]); | |
584 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle); | |
565 | 585 | void output_close_common(void); |
566 | 586 | void output_flush(void); |
567 | 587 | // _* called with mutex locked |
571 | 591 | // output_alsa.c |
572 | 592 | #if ALSA |
573 | 593 | void list_devices(void); |
594 | void list_mixers(const char *output_device); | |
595 | void set_volume(unsigned left, unsigned right); | |
574 | 596 | bool test_open(const char *device, unsigned rates[]); |
575 | 597 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], |
576 | unsigned rate_delay, unsigned rt_priority); | |
598 | unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute); | |
577 | 599 | void output_close_alsa(void); |
578 | 600 | #endif |
579 | 601 | |
580 | 602 | // output_pa.c |
581 | 603 | #if PORTAUDIO |
582 | 604 | void list_devices(void); |
605 | void set_volume(unsigned left, unsigned right); | |
583 | 606 | bool test_open(const char *device, unsigned rates[]); |
584 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay); | |
607 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle); | |
585 | 608 | void output_close_pa(void); |
586 | 609 | void _pa_open(void); |
587 | 610 | #endif |
610 | 633 | // dop.c |
611 | 634 | #if DSD |
612 | 635 | bool is_flac_dop(u32_t *lptr, u32_t *rptr, frames_t frames); |
613 | void update_dop_marker(u32_t *ptr, frames_t frames); | |
636 | void update_dop(u32_t *ptr, frames_t frames, bool invert); | |
614 | 637 | void dop_silence_frames(u32_t *ptr, frames_t frames); |
615 | 638 | void dop_init(bool enable, unsigned delay); |
616 | 639 | #endif |
626 | 649 | struct codec *register_faad(void); |
627 | 650 | struct codec *register_dsd(void); |
628 | 651 | struct codec *register_ff(const char *codec); |
652 | ||
653 | // ir.c | |
654 | #if IR | |
655 | struct irstate { | |
656 | mutex_type mutex; | |
657 | u32_t code; | |
658 | u32_t ts; | |
659 | }; | |
660 | ||
661 | void ir_init(log_level level, char *lircrc); | |
662 | void ir_close(void); | |
663 | #endif |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
366 | 366 | } |
367 | 367 | |
368 | 368 | void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) { |
369 | struct sockaddr_in addr; | |
369 | struct sockaddr_in addr; | |
370 | 370 | |
371 | 371 | int sock = socket(AF_INET, SOCK_STREAM, 0); |
372 | 372 | |
385 | 385 | set_nonblock(sock); |
386 | 386 | set_nosigpipe(sock); |
387 | 387 | |
388 | if (connect_timeout(sock, (struct sockaddr *) &addr, sizeof(addr), 10) < 0) { | |
388 | if (connect_timeout(sock, (struct sockaddr *) &addr, sizeof(addr), 10) < 0) { | |
389 | 389 | LOG_INFO("unable to connect to server"); |
390 | 390 | LOCK; |
391 | 391 | stream.state = DISCONNECT; |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |
69 | 69 | static char *str = NULL; |
70 | 70 | char *ptr, *ret; |
71 | 71 | if (src) str = src; |
72 | if (str && (ptr = strchr(str, c))) { | |
72 | if (str && (ptr = strchr(str, c))) { | |
73 | 73 | ret = str; |
74 | 74 | *ptr = '\0'; |
75 | 75 | str = ptr + 1; |
102 | 102 | #if LINUX |
103 | 103 | // search first 4 interfaces returned by IFCONF |
104 | 104 | void get_mac(u8_t mac[]) { |
105 | struct ifconf ifc; | |
106 | struct ifreq *ifr, *ifend; | |
107 | struct ifreq ifreq; | |
108 | struct ifreq ifs[4]; | |
105 | struct ifconf ifc; | |
106 | struct ifreq *ifr, *ifend; | |
107 | struct ifreq ifreq; | |
108 | struct ifreq ifs[4]; | |
109 | 109 | |
110 | 110 | mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0; |
111 | 111 | |
112 | int s = socket(AF_INET, SOCK_DGRAM, 0); | |
113 | ||
114 | ifc.ifc_len = sizeof(ifs); | |
115 | ifc.ifc_req = ifs; | |
116 | ||
117 | if (ioctl(s, SIOCGIFCONF, &ifc) == 0) { | |
112 | int s = socket(AF_INET, SOCK_DGRAM, 0); | |
113 | ||
114 | ifc.ifc_len = sizeof(ifs); | |
115 | ifc.ifc_req = ifs; | |
116 | ||
117 | if (ioctl(s, SIOCGIFCONF, &ifc) == 0) { | |
118 | 118 | ifend = ifs + (ifc.ifc_len / sizeof(struct ifreq)); |
119 | 119 | |
120 | 120 | for (ifr = ifc.ifc_req; ifr < ifend; ifr++) { |
162 | 162 | #if WIN |
163 | 163 | #pragma comment(lib, "IPHLPAPI.lib") |
164 | 164 | void get_mac(u8_t mac[]) { |
165 | IP_ADAPTER_INFO AdapterInfo[16]; | |
166 | DWORD dwBufLen = sizeof(AdapterInfo); | |
167 | DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen); | |
165 | IP_ADAPTER_INFO AdapterInfo[16]; | |
166 | DWORD dwBufLen = sizeof(AdapterInfo); | |
167 | DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen); | |
168 | 168 | |
169 | 169 | mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0; |
170 | 170 | |
315 | 315 | |
316 | 316 | #if WIN |
317 | 317 | void winsock_init(void) { |
318 | WSADATA wsaData; | |
318 | WSADATA wsaData; | |
319 | 319 | WORD wVersionRequested = MAKEWORD(2, 2); |
320 | int WSerr = WSAStartup(wVersionRequested, &wsaData); | |
321 | if (WSerr != 0) { | |
322 | LOG_ERROR("Bad winsock version"); | |
323 | exit(1); | |
324 | } | |
320 | int WSerr = WSAStartup(wVersionRequested, &wsaData); | |
321 | if (WSerr != 0) { | |
322 | LOG_ERROR("Bad winsock version"); | |
323 | exit(1); | |
324 | } | |
325 | 325 | } |
326 | 326 | |
327 | 327 | void winsock_close(void) { |
365 | 365 | tv.tv_usec = 1000 * (timeout % 1000); |
366 | 366 | |
367 | 367 | ret = select(fds[0].fd + 1, &r, &w, NULL, &tv); |
368 | ||
368 | ||
369 | 369 | if (ret < 0) return ret; |
370 | 370 | |
371 | 371 | fds[0].revents = 0; |
382 | 382 | u8_t *ptr; |
383 | 383 | for (ptr = buf; ptr < buf + size; ptr += sysconf(_SC_PAGESIZE)) { |
384 | 384 | *ptr = 0; |
385 | } | |
386 | } | |
387 | #endif | |
385 | } | |
386 | } | |
387 | #endif |
0 | 0 | /* |
1 | 1 | * Squeezelite - lightweight headless squeezebox emulator |
2 | 2 | * |
3 | * (c) Adrian Smith 2012-2014, triode1@btinternet.com | |
3 | * (c) Adrian Smith 2012-2015, triode1@btinternet.com | |
4 | 4 | * |
5 | 5 | * This program is free software: you can redistribute it and/or modify |
6 | 6 | * it under the terms of the GNU General Public License as published by |