Codebase list squeezelite / d5411b1
add support for lirc on Linux Adrian Smith 9 years ago
7 changed file(s) with 376 addition(s) and 2 deletion(s). Raw diff Collapse all Expand all
131131
132132 Features
133133 - support for closing output device when idle with -C option
134 - add support for basic IR input using LIRC on Linux
88 OPT_LINKALL = -DLINKALL
99 OPT_RESAMPLE= -DRESAMPLE
1010 OPT_VIS = -DVISEXPORT
11 OPT_IR = -DIR
1112
1213 SOURCES = \
1314 main.c slimproto.c buffer.c stream.c utils.c \
1819 SOURCES_FF = ffmpeg.c
1920 SOURCES_RESAMPLE = process.c resample.c
2021 SOURCES_VIS = output_vis.c
22 SOURCES_IR = ir.c
2123
2224 LINK_LINUX = -ldl
2325
2426 LINKALL = -lFLAC -lmad -lvorbisfile -lfaad -lmpg123
2527 LINKALL_FF = -lavcodec -lavformat -lavutil
2628 LINKALL_RESAMPLE = -lsoxr
29 LINKALL_IR = -llirc_client
2730
2831 DEPS = squeezelite.h slimproto.h
2932
4245 ifneq (,$(findstring $(OPT_VIS), $(CFLAGS)))
4346 SOURCES += $(SOURCES_VIS)
4447 endif
48 ifneq (,$(findstring $(OPT_IR), $(CFLAGS)))
49 SOURCES += $(SOURCES_IR)
50 endif
4551
4652 # add optional link options
4753 ifneq (,$(findstring $(OPT_LINKALL), $(CFLAGS)))
5157 endif
5258 ifneq (,$(findstring $(OPT_RESAMPLE), $(CFLAGS)))
5359 LDFLAGS += $(LINKALL_RESAMPLE)
60 endif
61 ifneq (,$(findstring $(OPT_IR), $(CFLAGS)))
62 LDFLAGS += $(LINKALL_IR)
5463 endif
5564 else
5665 # if not LINKALL and linux add LINK_LINUX
+248
-0
ir.c less more
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
5858 " -b <stream>:<output>\tSpecify internal Stream and Output buffer sizes in Kbytes\n"
5959 " -c <codec1>,<codec2>\tRestrict codecs to those specified, otherwise load all available codecs; known codecs: " CODECS "\n"
6060 " -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
6162 " -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
6266 " -e <codec1>,<codec2>\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n"
6367 " -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
6471 " -m <mac addr>\t\tSet mac address, format: ab:cd:ef:12:34:56\n"
6572 " -M <modelname>\tSet the squeezelite player model name sent to the server (default: " MODEL_NAME_STRING ")\n"
6673 " -n <name>\t\tSet the player name\n"
134141 #endif
135142 #if VISEXPORT
136143 " VISEXPORT"
144 #endif
145 #if IR
146 " IR"
137147 #endif
138148 #if DSD
139149 " DSD"
203213 #if VISEXPORT
204214 bool visexport = false;
205215 #endif
216 #if IR
217 char *lircrc = NULL;
218 #endif
206219
207220 log_level log_output = lWARN;
208221 log_level log_stream = lWARN;
209222 log_level log_decode = lWARN;
210223 log_level log_slimproto = lWARN;
224 #if IR
225 log_level log_ir = lWARN;
226 #endif
211227
212228 char *optarg = NULL;
213229 int optind = 1;
238254 #if VISEXPORT
239255 "v"
240256 #endif
257 #if IR
258 "i"
259 #endif
260
241261 , opt)) {
242262 optarg = NULL;
243263 optind += 1;
286306 if (!strcmp(l, "all") || !strcmp(l, "stream")) log_stream = new;
287307 if (!strcmp(l, "all") || !strcmp(l, "decode")) log_decode = new;
288308 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
289312 } else {
290313 fprintf(stderr, "\nDebug settings error: -d %s\n\n", optarg);
291314 usage(argv[0]);
410433 visexport = true;
411434 break;
412435 #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
413445 #if LINUX || FREEBSD
414446 case 'z':
415447 daemonize = true;
523555 }
524556 #endif
525557
558 #if IR
559 if (lircrc) {
560 ir_init(log_ir, lircrc);
561 }
562 #endif
563
526564 if (name && namefile) {
527565 fprintf(stderr, "-n and -N option should not be used at same time\n");
528566 exit(1);
529567 }
530568
531569 slimproto(log_slimproto, server, mac, name, namefile, modelname);
532
570
533571 decode_close();
534572 stream_close();
535573
544582 #endif
545583 }
546584
585 #if IR
586 ir_close();
587 #endif
588
547589 #if WIN
548590 winsock_close();
549591 #endif
4545 extern struct decodestate decode;
4646
4747 extern struct codec *codecs[];
48 #if IR
49 extern struct irstate ir;
50 #endif
4851
4952 event_event wake_e;
5053
5457 #define UNLOCK_O mutex_unlock(outputbuf->mutex)
5558 #define LOCK_D mutex_lock(decode.mutex)
5659 #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
5764
5865 static struct {
5966 u32_t updated;
224231 send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
225232 send_packet((u8_t *)name, strlen(name) + 1);
226233 }
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
227251
228252 static void process_strm(u8_t *pkt, int len) {
229253 struct strm_packet *strm = (struct strm_packet *)pkt;
571595 disconnect_code disconnect_code;
572596 static char header[MAX_HEADER];
573597 size_t header_len = 0;
598 #if IR
599 bool _sendIR = false;
600 u32_t ir_code, ir_ts;
601 #endif
574602 last = now;
575603
576604 LOCK_S;
667695 }
668696 UNLOCK_O;
669697
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
670709 if (_stream_disconnect) stream_disconnect();
671710
672711 // send packets once locks released as packet sending can block
680719 if (_sendSTMn) sendSTAT("STMn", 0);
681720 if (_sendRESP) sendRESP(header, header_len);
682721 if (_sendMETA) sendMETA(header, header_len);
722 #if IR
723 if (_sendIR) sendIR(ir_code, ir_ts);
724 #endif
683725 }
684726 }
685727 }
8787 // data
8888 };
8989
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
90101 // from S:P:Squeezebox stream_s
91102 struct strm_packet {
92103 char opcode[4];
1717 *
1818 */
1919
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
2121
2222 #define VERSION "v1.8-dev"
2323
107107 #define VISEXPORT 0
108108 #endif
109109
110 #if LINUX && defined(IR)
111 #undef IR
112 #define IR 1
113 #else
114 #define IR 0
115 #endif
116
110117 #if defined(DSD)
111118 #undef DSD
112119 #define DSD 1
140147 #define LIBAVCODEC "libavcodec.so.%d"
141148 #define LIBAVFORMAT "libavformat.so.%d"
142149 #define LIBSOXR "libsoxr.so.0"
150 #define LIBLIRC "liblirc_client.so.0"
143151 #endif
144152
145153 #if OSX
220228 #define STREAM_THREAD_STACK_SIZE 64 * 1024
221229 #define DECODE_THREAD_STACK_SIZE 128 * 1024
222230 #define OUTPUT_THREAD_STACK_SIZE 64 * 1024
231 #define IR_THREAD_STACK_SIZE 64 * 1024
223232 #define thread_t pthread_t;
224233 #define closesocket(s) close(s)
225234 #define last_error() errno
636645 struct codec *register_faad(void);
637646 struct codec *register_dsd(void);
638647 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