add support for lirc on Linux
Adrian Smith
9 years ago
131 | 131 | |
132 | 132 | Features |
133 | 133 | - support for closing output device when idle with -C option |
134 | - add support for basic IR input using LIRC on Linux |
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 | /* | |
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 |
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 | 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 | |
61 | 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 | |
62 | 66 | " -e <codec1>,<codec2>\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n" |
63 | 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 | |
64 | 71 | " -m <mac addr>\t\tSet mac address, format: ab:cd:ef:12:34:56\n" |
65 | 72 | " -M <modelname>\tSet the squeezelite player model name sent to the server (default: " MODEL_NAME_STRING ")\n" |
66 | 73 | " -n <name>\t\tSet the player name\n" |
134 | 141 | #endif |
135 | 142 | #if VISEXPORT |
136 | 143 | " VISEXPORT" |
144 | #endif | |
145 | #if IR | |
146 | " IR" | |
137 | 147 | #endif |
138 | 148 | #if DSD |
139 | 149 | " DSD" |
203 | 213 | #if VISEXPORT |
204 | 214 | bool visexport = false; |
205 | 215 | #endif |
216 | #if IR | |
217 | char *lircrc = NULL; | |
218 | #endif | |
206 | 219 | |
207 | 220 | log_level log_output = lWARN; |
208 | 221 | log_level log_stream = lWARN; |
209 | 222 | log_level log_decode = lWARN; |
210 | 223 | log_level log_slimproto = lWARN; |
224 | #if IR | |
225 | log_level log_ir = lWARN; | |
226 | #endif | |
211 | 227 | |
212 | 228 | char *optarg = NULL; |
213 | 229 | int optind = 1; |
238 | 254 | #if VISEXPORT |
239 | 255 | "v" |
240 | 256 | #endif |
257 | #if IR | |
258 | "i" | |
259 | #endif | |
260 | ||
241 | 261 | , opt)) { |
242 | 262 | optarg = NULL; |
243 | 263 | optind += 1; |
286 | 306 | if (!strcmp(l, "all") || !strcmp(l, "stream")) log_stream = new; |
287 | 307 | if (!strcmp(l, "all") || !strcmp(l, "decode")) log_decode = new; |
288 | 308 | if (!strcmp(l, "all") || !strcmp(l, "output")) log_output = new; |
309 | #if IR | |
310 | if (!strcmp(l, "all") || !strcmp(l, "ir")) log_ir = new; | |
311 | #endif | |
289 | 312 | } else { |
290 | 313 | fprintf(stderr, "\nDebug settings error: -d %s\n\n", optarg); |
291 | 314 | usage(argv[0]); |
410 | 433 | visexport = true; |
411 | 434 | break; |
412 | 435 | #endif |
436 | #if IR | |
437 | case 'i': | |
438 | if (optind < argc && argv[optind] && argv[optind][0] != '-') { | |
439 | lircrc = argv[optind++]; | |
440 | } else { | |
441 | lircrc = "~/.lircrc"; // liblirc_client will expand ~/ | |
442 | } | |
443 | break; | |
444 | #endif | |
413 | 445 | #if LINUX || FREEBSD |
414 | 446 | case 'z': |
415 | 447 | daemonize = true; |
523 | 555 | } |
524 | 556 | #endif |
525 | 557 | |
558 | #if IR | |
559 | if (lircrc) { | |
560 | ir_init(log_ir, lircrc); | |
561 | } | |
562 | #endif | |
563 | ||
526 | 564 | if (name && namefile) { |
527 | 565 | fprintf(stderr, "-n and -N option should not be used at same time\n"); |
528 | 566 | exit(1); |
529 | 567 | } |
530 | 568 | |
531 | 569 | slimproto(log_slimproto, server, mac, name, namefile, modelname); |
532 | ||
570 | ||
533 | 571 | decode_close(); |
534 | 572 | stream_close(); |
535 | 573 | |
544 | 582 | #endif |
545 | 583 | } |
546 | 584 | |
585 | #if IR | |
586 | ir_close(); | |
587 | #endif | |
588 | ||
547 | 589 | #if WIN |
548 | 590 | winsock_close(); |
549 | 591 | #endif |
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; |
224 | 231 | send_packet((u8_t *)&pkt_header, sizeof(pkt_header)); |
225 | 232 | send_packet((u8_t *)name, strlen(name) + 1); |
226 | 233 | } |
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 | |
227 | 251 | |
228 | 252 | static void process_strm(u8_t *pkt, int len) { |
229 | 253 | struct strm_packet *strm = (struct strm_packet *)pkt; |
571 | 595 | disconnect_code disconnect_code; |
572 | 596 | static char header[MAX_HEADER]; |
573 | 597 | size_t header_len = 0; |
598 | #if IR | |
599 | bool _sendIR = false; | |
600 | u32_t ir_code, ir_ts; | |
601 | #endif | |
574 | 602 | last = now; |
575 | 603 | |
576 | 604 | LOCK_S; |
667 | 695 | } |
668 | 696 | UNLOCK_O; |
669 | 697 | |
698 | #if IR | |
699 | LOCK_I; | |
700 | if (ir.code) { | |
701 | _sendIR = true; | |
702 | ir_code = ir.code; | |
703 | ir_ts = ir.ts; | |
704 | ir.code = 0; | |
705 | } | |
706 | UNLOCK_I; | |
707 | #endif | |
708 | ||
670 | 709 | if (_stream_disconnect) stream_disconnect(); |
671 | 710 | |
672 | 711 | // send packets once locks released as packet sending can block |
680 | 719 | if (_sendSTMn) sendSTAT("STMn", 0); |
681 | 720 | if (_sendRESP) sendRESP(header, header_len); |
682 | 721 | if (_sendMETA) sendMETA(header, header_len); |
722 | #if IR | |
723 | if (_sendIR) sendIR(ir_code, ir_ts); | |
724 | #endif | |
683 | 725 | } |
684 | 726 | } |
685 | 727 | } |
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]; |
17 | 17 | * |
18 | 18 | */ |
19 | 19 | |
20 | // make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, DSD, LINKALL to influence build | |
20 | // make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, IR, DSD, LINKALL to influence build | |
21 | 21 | |
22 | 22 | #define VERSION "v1.8-dev" |
23 | 23 | |
107 | 107 | #define VISEXPORT 0 |
108 | 108 | #endif |
109 | 109 | |
110 | #if LINUX && defined(IR) | |
111 | #undef IR | |
112 | #define IR 1 | |
113 | #else | |
114 | #define IR 0 | |
115 | #endif | |
116 | ||
110 | 117 | #if defined(DSD) |
111 | 118 | #undef DSD |
112 | 119 | #define DSD 1 |
140 | 147 | #define LIBAVCODEC "libavcodec.so.%d" |
141 | 148 | #define LIBAVFORMAT "libavformat.so.%d" |
142 | 149 | #define LIBSOXR "libsoxr.so.0" |
150 | #define LIBLIRC "liblirc_client.so.0" | |
143 | 151 | #endif |
144 | 152 | |
145 | 153 | #if OSX |
220 | 228 | #define STREAM_THREAD_STACK_SIZE 64 * 1024 |
221 | 229 | #define DECODE_THREAD_STACK_SIZE 128 * 1024 |
222 | 230 | #define OUTPUT_THREAD_STACK_SIZE 64 * 1024 |
231 | #define IR_THREAD_STACK_SIZE 64 * 1024 | |
223 | 232 | #define thread_t pthread_t; |
224 | 233 | #define closesocket(s) close(s) |
225 | 234 | #define last_error() errno |
636 | 645 | struct codec *register_faad(void); |
637 | 646 | struct codec *register_dsd(void); |
638 | 647 | struct codec *register_ff(const char *codec); |
648 | ||
649 | // ir.c | |
650 | #if IR | |
651 | struct irstate { | |
652 | mutex_type mutex; | |
653 | u32_t code; | |
654 | u32_t ts; | |
655 | }; | |
656 | ||
657 | void ir_init(log_level level, char *lircrc); | |
658 | void ir_close(void); | |
659 | #endif |