1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 /*
3 * rds-ctl.cpp is based on v4l2-ctl.cpp
4 *
5 * the following applies for all RDS related parts:
6 * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
7 * Author: Konke Radlow <koradlow@gmail.com>
8 */
9
10 #include <unistd.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <wchar.h>
15 #include <locale.h>
16 #include <inttypes.h>
17 #include <getopt.h>
18 #include <sys/types.h>
19 #include <fcntl.h>
20 #include <errno.h>
21 #include <sys/ioctl.h>
22 #include <sys/time.h>
23 #include <dirent.h>
24 #include <config.h>
25 #include <signal.h>
26
27 #include <linux/videodev2.h>
28 #include <libv4l2rds.h>
29
30 #include <cctype>
31 #include <cmath>
32 #include <ctime>
33 #include <list>
34 #include <vector>
35 #include <map>
36 #include <string>
37 #include <algorithm>
38
39 #define ARRAY_SIZE(arr) ((int)(sizeof(arr) / sizeof((arr)[0])))
40
41 typedef std::vector<std::string> dev_vec;
42 typedef std::map<std::string, std::string> dev_map;
43
44 /* Short option list
45
46 Please keep in alphabetical order.
47 That makes it easier to see which short options are still free.
48
49 In general the lower case is used to set something and the upper
50 case is used to retrieve a setting. */
51 enum Option {
52 OptRBDS = 'b',
53 OptSetDevice = 'd',
54 OptGetDriverInfo = 'D',
55 OptGetFreq = 'F',
56 OptSetFreq = 'f',
57 OptHelp = 'h',
58 OptReadRds = 'R',
59 OptGetTuner = 'T',
60 OptAll = 128,
61 OptFreqSeek,
62 OptListDevices,
63 OptListFreqBands,
64 OptOpenFile,
65 OptPrintBlock,
66 OptSilent,
67 OptTMC,
68 OptTunerIndex,
69 OptVerbose,
70 OptWaitLimit,
71 OptLast = 256
72 };
73
74 struct ctl_parameters {
75 bool terminate_decoding;
76 char options[OptLast];
77 char fd_name[80];
78 bool filemode_active;
79 double freq;
80 uint32_t wait_limit;
81 uint8_t tuner_index;
82 struct v4l2_hw_freq_seek freq_seek;
83 };
84
85 static struct ctl_parameters params;
86 static int app_result;
87
88 static struct option long_options[] = {
89 {"all", no_argument, 0, OptAll},
90 {"rbds", no_argument, 0, OptRBDS},
91 {"device", required_argument, 0, OptSetDevice},
92 {"file", required_argument, 0, OptOpenFile},
93 {"freq-seek", required_argument, 0, OptFreqSeek},
94 {"get-freq", no_argument, 0, OptGetFreq},
95 {"get-tuner", no_argument, 0, OptGetTuner},
96 {"help", no_argument, 0, OptHelp},
97 {"info", no_argument, 0, OptGetDriverInfo},
98 {"list-devices", no_argument, 0, OptListDevices},
99 {"list-freq-bands", no_argument, 0, OptListFreqBands},
100 {"print-block", no_argument, 0, OptPrintBlock},
101 {"read-rds", no_argument, 0, OptReadRds},
102 {"set-freq", required_argument, 0, OptSetFreq},
103 {"tmc", no_argument, 0, OptTMC},
104 {"tuner-index", required_argument, 0, OptTunerIndex},
105 {"silent", no_argument, 0, OptSilent},
106 {"verbose", no_argument, 0, OptVerbose},
107 {"wait-limit", required_argument, 0, OptWaitLimit},
108 {0, 0, 0, 0}
109 };
110
usage_hint()111 static void usage_hint()
112 {
113 fprintf(stderr, "Try 'rds-ctl --help' for more information.\n");
114 }
115
usage_common()116 static void usage_common()
117 {
118 printf("\nGeneral/Common options:\n"
119 " --all display all device information available\n"
120 " -D, --info show driver info [VIDIOC_QUERYCAP]\n"
121 " -d, --device <dev> use device <dev>\n"
122 " If <dev> starts with a digit, then /dev/radio<dev> is used\n"
123 " default: checks for RDS-capable devices,\n"
124 " uses device with lowest ID\n"
125 " -h, --help display this help message\n"
126 " --list-devices list all v4l radio devices with RDS capabilities\n"
127 );
128 }
129
usage_tuner()130 static void usage_tuner()
131 {
132 printf("\nTuner/Modulator options:\n"
133 " -F, --get-freq query the frequency [VIDIOC_G_FREQUENCY]\n"
134 " -f, --set-freq <freq>\n"
135 " set the frequency to <freq> MHz [VIDIOC_S_FREQUENCY]\n"
136 " -T, --get-tuner query the tuner settings [VIDIOC_G_TUNER]\n"
137 " --tuner-index <idx> Use idx as tuner idx for tuner/modulator commands\n"
138 " --freq-seek dir=<0/1>,wrap=<0/1>,spacing=<hz>\n"
139 " perform a hardware frequency seek [VIDIOC_S_HW_FREQ_SEEK]\n"
140 " dir is 0 (seek downward) or 1 (seek upward)\n"
141 " wrap is 0 (do not wrap around) or 1 (wrap around)\n"
142 " spacing sets the seek resolution (use 0 for default)\n"
143 " --list-freq-bands display all frequency bands for the tuner/modulator\n"
144 " [VIDIOC_ENUM_FREQ_BANDS]\n"
145 );
146 }
147
usage_rds()148 static void usage_rds()
149 {
150 printf("\nRDS options: \n"
151 " -b, --rbds parse the RDS data according to the RBDS standard\n"
152 " -R, --read-rds enable reading of RDS data from device\n"
153 " --file <path> open a RDS stream file dump instead of a device\n"
154 " all General and Tuner Options are disabled in this mode\n"
155 " --wait-limit <ms> defines the maximum wait duration for avaibility of new\n"
156 " RDS data\n"
157 " <default>: 5000 ms\n"
158 " --print-block prints all valid RDS fields, whenever a value is updated\n"
159 " instead of printing only updated values\n"
160 " --tmc print information about TMC (Traffic Message Channel) messages\n"
161 " --silent only set the result code, do not print any messages\n"
162 " --verbose turn on verbose mode - every received RDS group\n"
163 " will be printed\n"
164 );
165 }
166
usage()167 static void usage()
168 {
169 printf("Usage:\n");
170 usage_common();
171 usage_tuner();
172 usage_rds();
173 }
174
signal_handler_interrupt(int signum)175 static void signal_handler_interrupt(int signum)
176 {
177 fprintf(stderr, "Interrupt received: Terminating program\n");
178 params.terminate_decoding = true;
179 }
180
doioctl_name(int fd,unsigned long int request,void * parm,const char * name)181 static int doioctl_name(int fd, unsigned long int request, void *parm, const char *name)
182 {
183 int retval = ioctl(fd, request, parm);
184
185 if (retval < 0) {
186 app_result = -1;
187 }
188 if (params.options[OptSilent]) return retval;
189 if (retval < 0)
190 printf("%s: failed: %s\n", name, strerror(errno));
191 else if (params.options[OptVerbose])
192 printf("%s: ok\n", name);
193
194 return retval;
195 }
196
197 #define doioctl(n, r, p) doioctl_name(n, r, p, #r)
198
audmode2s(int audmode)199 static const char *audmode2s(int audmode)
200 {
201 switch (audmode) {
202 case V4L2_TUNER_MODE_STEREO: return "stereo";
203 case V4L2_TUNER_MODE_LANG1: return "lang1";
204 case V4L2_TUNER_MODE_LANG2: return "lang2";
205 case V4L2_TUNER_MODE_LANG1_LANG2: return "bilingual";
206 case V4L2_TUNER_MODE_MONO: return "mono";
207 default: return "unknown";
208 }
209 }
210
rxsubchans2s(int rxsubchans)211 static std::string rxsubchans2s(int rxsubchans)
212 {
213 std::string s;
214
215 if (rxsubchans & V4L2_TUNER_SUB_MONO)
216 s += "mono ";
217 if (rxsubchans & V4L2_TUNER_SUB_STEREO)
218 s += "stereo ";
219 if (rxsubchans & V4L2_TUNER_SUB_LANG1)
220 s += "lang1 ";
221 if (rxsubchans & V4L2_TUNER_SUB_LANG2)
222 s += "lang2 ";
223 if (rxsubchans & V4L2_TUNER_SUB_RDS)
224 s += "rds ";
225 return s;
226 }
227
tcap2s(unsigned cap)228 static std::string tcap2s(unsigned cap)
229 {
230 std::string s;
231
232 if (cap & V4L2_TUNER_CAP_LOW)
233 s += "62.5 Hz ";
234 else
235 s += "62.5 kHz ";
236 if (cap & V4L2_TUNER_CAP_NORM)
237 s += "multi-standard ";
238 if (cap & V4L2_TUNER_CAP_HWSEEK_BOUNDED)
239 s += "hwseek-bounded ";
240 if (cap & V4L2_TUNER_CAP_HWSEEK_WRAP)
241 s += "hwseek-wrap ";
242 if (cap & V4L2_TUNER_CAP_STEREO)
243 s += "stereo ";
244 if (cap & V4L2_TUNER_CAP_LANG1)
245 s += "lang1 ";
246 if (cap & V4L2_TUNER_CAP_LANG2)
247 s += "lang2 ";
248 if (cap & V4L2_TUNER_CAP_RDS)
249 s += "rds ";
250 if (cap & V4L2_TUNER_CAP_RDS_BLOCK_IO)
251 s += "rds-block-I/O ";
252 if (cap & V4L2_TUNER_CAP_RDS_CONTROLS)
253 s += "rds-controls ";
254 if (cap & V4L2_TUNER_CAP_FREQ_BANDS)
255 s += "freq-bands ";
256 if (cap & V4L2_TUNER_CAP_HWSEEK_PROG_LIM)
257 s += "hwseek-prog-lim ";
258 return s;
259 }
260
cap2s(unsigned cap)261 static std::string cap2s(unsigned cap)
262 {
263 std::string s;
264
265 if (cap & V4L2_CAP_RDS_CAPTURE)
266 s += "\t\tRDS Capture\n";
267 if (cap & V4L2_CAP_RDS_OUTPUT)
268 s += "\t\tRDS Output\n";
269 if (cap & V4L2_CAP_TUNER)
270 s += "\t\tTuner\n";
271 if (cap & V4L2_CAP_MODULATOR)
272 s += "\t\tModulator\n";
273 if (cap & V4L2_CAP_RADIO)
274 s += "\t\tRadio\n";
275 if (cap & V4L2_CAP_READWRITE)
276 s += "\t\tRead/Write\n";
277 if (cap & V4L2_CAP_STREAMING)
278 s += "\t\tStreaming\n";
279 if (cap & V4L2_CAP_DEVICE_CAPS)
280 s += "\t\tDevice Capabilities\n";
281 return s;
282 }
283
modulation2s(unsigned modulation)284 static std::string modulation2s(unsigned modulation)
285 {
286 switch (modulation) {
287 case V4L2_BAND_MODULATION_VSB:
288 return "VSB";
289 case V4L2_BAND_MODULATION_FM:
290 return "FM";
291 case V4L2_BAND_MODULATION_AM:
292 return "AM";
293 }
294 return "Unknown";
295 }
296
is_radio_dev(const char * name)297 static bool is_radio_dev(const char *name)
298 {
299 return !memcmp(name, "radio", 5);
300 }
301
print_devices(dev_vec files)302 static void print_devices(dev_vec files)
303 {
304 dev_map cards;
305 int fd = -1;
306 std::string bus_info;
307 struct v4l2_capability vcap;
308
309 for (dev_vec::iterator iter = files.begin();
310 iter != files.end(); ++iter) {
311 fd = open(iter->c_str(), O_RDWR);
312 memset(&vcap, 0, sizeof(vcap));
313 if (fd < 0)
314 continue;
315 doioctl(fd, VIDIOC_QUERYCAP, &vcap);
316 close(fd);
317 bus_info = reinterpret_cast<const char *>(vcap.bus_info);
318 if (cards[bus_info].empty())
319 cards[bus_info] += std::string(reinterpret_cast<char *>(vcap.card))
320 + " (" + bus_info + "):\n";
321 cards[bus_info] += "\t" + (*iter);
322 cards[bus_info] += "\n";
323 }
324 for (dev_map::iterator iter = cards.begin();
325 iter != cards.end(); ++iter) {
326 printf("%s\n", iter->second.c_str());
327 }
328 }
list_devices()329 static dev_vec list_devices()
330 {
331 DIR *dp;
332 struct dirent *ep;
333 dev_vec files;
334 dev_vec valid_devices;
335 dev_map links;
336
337 struct v4l2_tuner vt;
338
339 dp = opendir("/dev");
340 if (dp == NULL) {
341 perror ("Couldn't open the directory");
342 std::exit(EXIT_FAILURE);
343 }
344 while ((ep = readdir(dp)))
345 if (is_radio_dev(ep->d_name))
346 files.push_back(std::string("/dev/") + ep->d_name);
347 closedir(dp);
348
349 /* Iterate through all devices, and remove all non-accessible devices
350 * and all devices that don't offer the RDS_BLOCK_IO capability */
351 for (dev_vec::iterator iter = files.begin();
352 iter != files.end();) {
353 int fd = open(iter->c_str(), O_RDONLY | O_NONBLOCK);
354 std::string bus_info;
355
356 if (fd < 0) {
357 iter++;
358 continue;
359 }
360 memset(&vt, 0, sizeof(vt));
361 if (ioctl(fd, VIDIOC_G_TUNER, &vt) != 0) {
362 close(fd);
363 iter++;
364 continue;
365 }
366 /* remove device if it doesn't support rds block I/O */
367 if (vt.capability & V4L2_TUNER_CAP_RDS_BLOCK_IO)
368 valid_devices.push_back(*iter++);
369 else
370 iter = files.erase(iter);
371 close(fd);
372 }
373 return valid_devices;
374 }
375
parse_subopt(char ** subs,const char * const * subopts,char ** value)376 static int parse_subopt(char **subs, const char * const *subopts, char **value)
377 {
378 int opt = getsubopt(subs, const_cast<char * const *>(subopts), value);
379
380 if (opt == -1) {
381 fprintf(stderr, "Invalid suboptions specified\n");
382 return -1;
383 }
384 if (*value == NULL) {
385 fprintf(stderr, "No value given to suboption <%s>\n",
386 subopts[opt]);
387 return -1;
388 }
389 return opt;
390 }
391
parse_freq_seek(char * optarg,struct v4l2_hw_freq_seek & seek)392 static void parse_freq_seek(char *optarg, struct v4l2_hw_freq_seek &seek)
393 {
394 char *value;
395 char *subs = optarg;
396
397 while (*subs != '\0') {
398 static const char *const subopts[] = {
399 "dir",
400 "wrap",
401 "spacing",
402 NULL
403 };
404
405 switch (parse_subopt(&subs, subopts, &value)) {
406 case 0:
407 seek.seek_upward = strtol(value, 0L, 0);
408 break;
409 case 1:
410 seek.wrap_around = strtol(value, 0L, 0);
411 break;
412 case 2:
413 seek.spacing = strtol(value, 0L, 0);
414 break;
415 default:
416 usage_tuner();
417 std::exit(EXIT_FAILURE);
418 }
419 }
420 }
421
print_byte(char byte,bool linebreak)422 static void print_byte(char byte, bool linebreak)
423 {
424 int count = 8;
425 printf(" ");
426 while (count--) {
427 printf("%d", (byte & 128) ? 1 : 0 );
428 byte <<= 1;
429 }
430 if (linebreak)
431 printf("\n");
432 }
433
print_rds_group(const struct v4l2_rds_group * rds_group)434 static void print_rds_group(const struct v4l2_rds_group *rds_group)
435 {
436 printf("\nComplete RDS data group received \n");
437 printf("PI: %04x\n", rds_group->pi);
438 printf("Group: %u%c \n", rds_group->group_id, rds_group->group_version);
439 printf("Data B:");
440 print_byte(rds_group->data_b_lsb, true);
441 printf("Data C:");
442 print_byte(rds_group->data_c_msb, false);
443 print_byte(rds_group->data_c_lsb, true);
444 printf("Data D:");
445 print_byte(rds_group->data_d_msb, false);
446 print_byte(rds_group->data_d_lsb, true);
447 printf("\n");
448 }
449
print_decoder_info(uint8_t di)450 static void print_decoder_info(uint8_t di)
451 {
452 printf("\nDI: ");
453 if (di & V4L2_RDS_FLAG_STEREO)
454 printf("Stereo, ");
455 else
456 printf("Mono, ");
457 if (di & V4L2_RDS_FLAG_ARTIFICIAL_HEAD)
458 printf("Artificial Head, ");
459 else
460 printf("No Artificial Head, ");
461 if (di & V4L2_RDS_FLAG_COMPRESSED)
462 printf("Compressed, ");
463 else
464 printf("Not Compressed, ");
465 if (di & V4L2_RDS_FLAG_DYNAMIC_PTY)
466 printf("Dynamic PTY");
467 else
468 printf("Static PTY");
469 }
470
print_rds_tmc(const struct v4l2_rds * handle,uint32_t updated_fields)471 static void print_rds_tmc(const struct v4l2_rds *handle, uint32_t updated_fields)
472 {
473 const struct v4l2_rds_tmc_msg *msg = &handle->tmc.tmc_msg;
474 const struct v4l2_tmc_additional_set *set = &msg->additional;
475
476 if (updated_fields & V4L2_RDS_TMC_SG) {
477 printf("\nTMC Single-grp: location: %04x, event: %04x, extent: %02x "
478 "duration: %02x", msg->location, msg->event,
479 msg->extent, msg->dp);
480 return;
481 }
482 if (updated_fields & V4L2_RDS_TMC_MG) {
483 printf("\nTMC Multi-grp: length: %02d, location: %04x, event: %04x,\n"
484 " extent: %02x duration: %02x", msg->length, msg->location, msg->event,
485 msg->extent, msg->dp);
486 for (int i = 0; i < set->size; i++) {
487 printf("\n additional[%02d]: label: %02d, value: %04x",
488 i, set->fields[i].label, set->fields[i].data);
489 }
490 return;
491 }
492 }
493
print_rds_tmc_tuning(const struct v4l2_rds * handle,uint32_t updated_fields)494 static void print_rds_tmc_tuning(const struct v4l2_rds *handle, uint32_t updated_fields)
495 {
496 const struct v4l2_tmc_tuning *tuning = &handle->tmc.tuning;
497 const struct v4l2_tmc_station *station;
498
499 if (updated_fields & V4L2_RDS_TMC_TUNING) {
500 printf("\nTMC Service provider: %s, %u alternative stations\n", handle->tmc.spn, tuning->station_cnt);
501 for (int i = 0; i < tuning->station_cnt; i++) {
502 station = &tuning->station[i];
503 printf("PI(ON %02u) = %04x, AFs: %u, mapped AFs: %u \n", i, station->pi,
504 station->afi.af_size, station->afi.mapped_af_size);
505 for (int j = 0; j < station->afi.af_size; j++)
506 printf(" AF%02d: %.1fMHz\n", j, station->afi.af[j] / 1000000.0);
507 for (int k = 0; k < station->afi.mapped_af_size; k++)
508 printf(" m_AF%02d: %.1fMHz => %.1fMHz\n", k,
509 station->afi.mapped_af_tuning[k] / 1000000.0,
510 station->afi.mapped_af[k] / 1000000.0);
511 if (station->ltn != 0 || station->msg != 0 || station-> sid != 0)
512 printf(" ltn: %02x, msg: %02x, sid: %02x\n", station->ltn,
513 station->msg, station->sid);
514 }
515 }
516 }
517
print_rds_statistics(const struct v4l2_rds_statistics * statistics)518 static void print_rds_statistics(const struct v4l2_rds_statistics *statistics)
519 {
520 printf("\n\nRDS Statistics: \n");
521 printf("received blocks / received groups: %u / %u\n",
522 statistics->block_cnt, statistics->group_cnt);
523
524 double block_error_percentage = 0;
525
526 if (statistics->block_cnt)
527 block_error_percentage =
528 (static_cast<float>(statistics->block_error_cnt) / statistics->block_cnt) * 100.0;
529 printf("block errors / group errors: %u (%3.2f%%) / %u \n",
530 statistics->block_error_cnt,
531 block_error_percentage, statistics->group_error_cnt);
532
533 double block_corrected_percentage = 0;
534
535 if (statistics->block_cnt)
536 block_corrected_percentage = (
537 static_cast<float>(statistics->block_corrected_cnt) / statistics->block_cnt) * 100.0;
538 printf("corrected blocks: %u (%3.2f%%)\n",
539 statistics->block_corrected_cnt, block_corrected_percentage);
540 for (int i = 0; i < 16; i++)
541 printf("Group %02d: %u\n", i, statistics->group_type_cnt[i]);
542 }
543
print_rds_af(const struct v4l2_rds_af_set * af_set)544 static void print_rds_af(const struct v4l2_rds_af_set *af_set)
545 {
546 int counter = 0;
547
548 printf("\nAnnounced AFs: %u", af_set->announced_af);
549 for (int i = 0; i < af_set->size && i < af_set->announced_af; i++, counter++) {
550 if (af_set->af[i] >= 87500000 ) {
551 printf("\nAF%02d: %.1fMHz", counter, af_set->af[i] / 1000000.0);
552 continue;
553 }
554 printf("\nAF%02d: %.3fkHz", counter, af_set->af[i] / 1000.0);
555 }
556 }
557
print_rds_eon(const struct v4l2_rds_eon_set * eon_set)558 static void print_rds_eon(const struct v4l2_rds_eon_set *eon_set)
559 {
560 int counter = 0;
561
562 printf("\n\nEnhanced Other Network information: %u channels", eon_set->size);
563 for (int i = 0; i < eon_set->size; i++, counter++) {
564 if (eon_set->eon[i].valid_fields & V4L2_RDS_PI)
565 printf("\nPI(ON %02i) = %04x", i, eon_set->eon[i].pi);
566 if (eon_set->eon[i].valid_fields & V4L2_RDS_PS)
567 printf("\nPS(ON %02i) = %s", i, eon_set->eon[i].ps);
568 if (eon_set->eon[i].valid_fields & V4L2_RDS_PTY)
569 printf("\nPTY(ON %02i) = %0u", i, eon_set->eon[i].pty);
570 if (eon_set->eon[i].valid_fields & V4L2_RDS_LSF)
571 printf("\nLSF(ON %02i) = %0u", i, eon_set->eon[i].lsf);
572 if (eon_set->eon[i].valid_fields & V4L2_RDS_AF)
573 printf("\nPTY(ON %02i) = %0u", i, eon_set->eon[i].pty);
574 if (eon_set->eon[i].valid_fields & V4L2_RDS_TP)
575 printf("\nTP(ON %02i): %s", i, eon_set->eon[i].tp? "yes":"no");
576 if (eon_set->eon[i].valid_fields & V4L2_RDS_TA)
577 printf("\nTA(ON %02i): %s", i, eon_set->eon[i].tp? "yes":"no");
578 if (eon_set->eon[i].valid_fields & V4L2_RDS_AF) {
579 printf("\nAF(ON %02i): size=%i", i, eon_set->eon[i].af.size);
580 print_rds_af(&(eon_set->eon[i].af));
581 }
582 }
583 }
584
print_rds_pi(const struct v4l2_rds * handle)585 static void print_rds_pi(const struct v4l2_rds *handle)
586 {
587 uint16_t pi = handle->pi;
588
589 if (handle->is_rbds) {
590 char callsign[5] = { 0 };
591 uint8_t nibble = handle->pi >> 12;
592
593 if (pi >= 0xafa1 && pi <= 0xafa9)
594 pi = (pi & 0xf) << 12;
595 else if (pi > 0xaf11 && pi <= 0xaf1f)
596 pi = (pi & 0xff) << 8;
597 else if (pi > 0xaf21 && pi <= 0xafaf)
598 pi = (pi & 0xff) << 8;
599
600 if (pi > 0xa100 && pi <= 0xa9ff)
601 pi -= 0x9100;
602 if (pi > 0x1000 && pi <= 0x54a7) {
603 pi -= 4096;
604 callsign[0] = 'K';
605 } else if (pi >= 0x54a8 && pi <= 0x994f) {
606 pi -= 21672;
607 callsign[0] = 'W';
608 }
609 if (callsign[0]) {
610 callsign[1] = 'A' + pi / 676;
611 callsign[2] = 'A' + (pi / 26) % 26;
612 callsign[3] = 'A' + pi % 26;
613 printf("\nCall Sign: %s", callsign);
614 }
615 if (nibble != 0x0b && nibble != 0x0d && nibble != 0x0e)
616 return;
617 }
618 printf("\nArea Coverage: %s", v4l2_rds_get_coverage_str(handle));
619 }
620
print_rds_data(const struct v4l2_rds * handle,uint32_t updated_fields)621 static void print_rds_data(const struct v4l2_rds *handle, uint32_t updated_fields)
622 {
623 if (params.options[OptPrintBlock])
624 updated_fields = 0xffffffff;
625
626 if ((updated_fields & V4L2_RDS_PI) &&
627 (handle->valid_fields & V4L2_RDS_PI)) {
628 printf("\nPI: %04x", handle->pi);
629 print_rds_pi(handle);
630 }
631
632 if (updated_fields & V4L2_RDS_PS &&
633 handle->valid_fields & V4L2_RDS_PS) {
634 printf("\nPS: %s", handle->ps);
635 }
636
637 if (updated_fields & V4L2_RDS_PTY && handle->valid_fields & V4L2_RDS_PTY)
638 printf("\nPTY: %0u -> %s", handle->pty, v4l2_rds_get_pty_str(handle));
639
640 if (updated_fields & V4L2_RDS_PTYN && handle->valid_fields & V4L2_RDS_PTYN) {
641 printf("\nPTYN: %s", handle->ptyn);
642 }
643
644 if (updated_fields & V4L2_RDS_TIME) {
645 printf("\nTime: %s", ctime(&handle->time));
646 }
647 if (updated_fields & V4L2_RDS_RT && handle->valid_fields & V4L2_RDS_RT) {
648 printf("\nRT: %s", handle->rt);
649 }
650
651 if ((updated_fields & (V4L2_RDS_TP | V4L2_RDS_TA)) &&
652 (handle->valid_fields & (V4L2_RDS_TP | V4L2_RDS_TA)))
653 printf("\nTP: %s TA: %s", (handle->tp) ? "yes" : "no",
654 handle->ta ? "yes" : "no");
655 if (updated_fields & V4L2_RDS_MS && handle->valid_fields & V4L2_RDS_MS)
656 printf("\nMS Flag: %s", (handle->ms) ? "Music" : "Speech");
657 if (updated_fields & V4L2_RDS_ECC && handle->valid_fields & V4L2_RDS_ECC)
658 printf("\nECC: %X%x, Country: %u -> %s",
659 handle->ecc >> 4, handle->ecc & 0x0f, handle->pi >> 12,
660 v4l2_rds_get_country_str(handle));
661 if (updated_fields & V4L2_RDS_LC && handle->valid_fields & V4L2_RDS_LC)
662 printf("\nLanguage: %u -> %s ", handle->lc,
663 v4l2_rds_get_language_str(handle));
664 if (updated_fields & V4L2_RDS_DI && handle->valid_fields & V4L2_RDS_DI)
665 print_decoder_info(handle->di);
666 if (updated_fields & V4L2_RDS_ODA &&
667 handle->decode_information & V4L2_RDS_ODA) {
668 for (int i = 0; i < handle->rds_oda.size; ++i)
669 printf("\nODA Group: %02u%c, AID: %08x", handle->rds_oda.oda[i].group_id,
670 handle->rds_oda.oda[i].group_version, handle->rds_oda.oda[i].aid);
671 }
672 if (updated_fields & V4L2_RDS_AF && handle->valid_fields & V4L2_RDS_AF)
673 print_rds_af(&handle->rds_af);
674 if (updated_fields & V4L2_RDS_TMC_TUNING && handle->valid_fields & V4L2_RDS_TMC_TUNING)
675 print_rds_tmc_tuning(handle, updated_fields);
676 if (params.options[OptPrintBlock])
677 printf("\n");
678 if (params.options[OptTMC])
679 print_rds_tmc(handle, updated_fields);
680 }
681
read_rds(struct v4l2_rds * handle,const int fd,const int wait_limit)682 static void read_rds(struct v4l2_rds *handle, const int fd, const int wait_limit)
683 {
684 int byte_cnt = 0;
685 int error_cnt = 0;
686 uint32_t updated_fields = 0x00;
687 struct v4l2_rds_data rds_data; /* read buffer for rds blocks */
688
689 while (!params.terminate_decoding) {
690 memset(&rds_data, 0, sizeof(rds_data));
691 if ((byte_cnt=read(fd, &rds_data, 3)) != 3) {
692 if (byte_cnt == 0) {
693 printf("\nEnd of input file reached \n");
694 break;
695 }
696 if (++error_cnt > 2) {
697 fprintf(stderr, "\nError reading from "
698 "device (no RDS data available)\n");
699 break;
700 }
701 /* wait for new data to arrive: transmission of 1
702 * group takes ~88.7ms */
703 usleep(wait_limit * 1000);
704 }
705 else if (byte_cnt == 3) {
706 error_cnt = 0;
707 /* true if a new group was decoded */
708 if ((updated_fields = v4l2_rds_add(handle, &rds_data))) {
709 print_rds_data(handle, updated_fields);
710 if (params.options[OptVerbose])
711 print_rds_group(v4l2_rds_get_group(handle));
712 }
713 }
714 }
715 /* print a summary of all valid RDS-fields before exiting */
716 printf("\nSummary of valid RDS-fields:");
717 print_rds_data(handle, 0xFFFFFFFF);
718 }
719
read_rds_from_fd(const int fd)720 static void read_rds_from_fd(const int fd)
721 {
722 struct v4l2_rds *rds_handle;
723
724 /* create an rds handle for the current device */
725 if (!(rds_handle = v4l2_rds_create(params.options[OptRBDS]))) {
726 fprintf(stderr, "Failed to init RDS lib: %s\n", strerror(errno));
727 std::exit(EXIT_FAILURE);
728 }
729
730 /* try to receive and decode RDS data */
731 read_rds(rds_handle, fd, params.wait_limit);
732 if (rds_handle->valid_fields & V4L2_RDS_EON)
733 print_rds_eon(&rds_handle->rds_eon);
734 print_rds_statistics(&rds_handle->rds_statistics);
735
736 v4l2_rds_destroy(rds_handle);
737 }
738
parse_cl(int argc,char ** argv)739 static int parse_cl(int argc, char **argv)
740 {
741 int i = 0;
742 int idx = 0;
743 int opt = 0;
744 /* 26 letters in the alphabet, case sensitive = 26 * 2 possible
745 * short options, where each option requires at most two chars
746 * {option, optional argument} */
747 char short_options[26 * 2 * 2 + 1];
748
749 if (argc == 1) {
750 usage_hint();
751 std::exit(EXIT_FAILURE);
752 }
753 for (i = 0; long_options[i].name; i++) {
754 if (!isalpha(long_options[i].val))
755 continue;
756 short_options[idx++] = long_options[i].val;
757 if (long_options[i].has_arg == required_argument)
758 short_options[idx++] = ':';
759 }
760 while (true) {
761 int option_index = 0;
762
763 short_options[idx] = 0;
764 opt = getopt_long(argc, argv, short_options,
765 long_options, &option_index);
766 if (opt == -1)
767 break;
768
769 params.options[opt] = 1;
770 switch (opt) {
771 case OptSetDevice:
772 strncpy(params.fd_name, optarg, sizeof(params.fd_name) - 1);
773 if (optarg[0] >= '0' && optarg[0] <= '9' && strlen(optarg) <= 3) {
774 snprintf(params.fd_name, sizeof(params.fd_name), "/dev/radio%s", optarg);
775 }
776 params.fd_name[sizeof(params.fd_name) - 1] = '\0';
777 break;
778 case OptSetFreq:
779 params.freq = strtod(optarg, NULL);
780 break;
781 case OptListDevices:
782 print_devices(list_devices());
783 break;
784 case OptFreqSeek:
785 parse_freq_seek(optarg, params.freq_seek);
786 break;
787 case OptTunerIndex:
788 params.tuner_index = strtoul(optarg, NULL, 0);
789 break;
790 case OptOpenFile:
791 {
792 if (access(optarg, F_OK) != -1) {
793 params.filemode_active = true;
794 strncpy(params.fd_name, optarg, sizeof(params.fd_name));
795 params.fd_name[sizeof(params.fd_name) - 1] = '\0';
796 } else {
797 fprintf(stderr, "Unable to open file: %s\n", optarg);
798 return -1;
799 }
800 /* enable the read-rds option by default for convenience */
801 params.options[OptReadRds] = 1;
802 break;
803 }
804 case OptWaitLimit:
805 params.wait_limit = strtoul(optarg, NULL, 0);
806 break;
807 case ':':
808 fprintf(stderr, "Option '%s' requires a value\n",
809 argv[optind]);
810 usage_hint();
811 return 1;
812 case '?':
813 if (argv[optind])
814 fprintf(stderr, "Unknown argument '%s'\n", argv[optind]);
815 usage_hint();
816 return 1;
817 }
818 }
819 if (optind < argc) {
820 printf("unknown arguments: ");
821 while (optind < argc)
822 printf("%s ", argv[optind++]);
823 printf("\n");
824 usage_hint();
825 return 1;
826 }
827 if (params.options[OptAll]) {
828 params.options[OptGetDriverInfo] = 1;
829 params.options[OptGetFreq] = 1;
830 params.options[OptGetTuner] = 1;
831 params.options[OptSilent] = 1;
832 }
833 /* set default value for wait limit, if not specified by user */
834 if (!params.options[OptWaitLimit])
835 params.wait_limit = 5000;
836
837 return 0;
838 }
839
print_driver_info(const struct v4l2_capability * vcap)840 static void print_driver_info(const struct v4l2_capability *vcap)
841 {
842
843 printf("Driver Info:\n");
844 printf("\tDriver name : %s\n", vcap->driver);
845 printf("\tCard type : %s\n", vcap->card);
846 printf("\tBus info : %s\n", vcap->bus_info);
847 printf("\tDriver version: %d.%d.%d\n",
848 vcap->version >> 16,
849 (vcap->version >> 8) & 0xff,
850 vcap->version & 0xff);
851 printf("\tCapabilities : 0x%08X\n", vcap->capabilities);
852 printf("%s", cap2s(vcap->capabilities).c_str());
853 if (vcap->capabilities & V4L2_CAP_DEVICE_CAPS) {
854 printf("\tDevice Caps : 0x%08X\n", vcap->device_caps);
855 printf("%s", cap2s(vcap->device_caps).c_str());
856 }
857 }
858
set_options(const int fd,const int capabilities,struct v4l2_frequency * vf,struct v4l2_tuner * tuner)859 static void set_options(const int fd, const int capabilities, struct v4l2_frequency *vf,
860 struct v4l2_tuner *tuner)
861 {
862 double fac = 16; /* factor for frequency division */
863
864 if (params.options[OptSetFreq]) {
865 vf->type = V4L2_TUNER_RADIO;
866 tuner->index = params.tuner_index;
867 if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) {
868 fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
869 vf->type = tuner->type;
870 }
871
872 vf->tuner = params.tuner_index;
873 vf->frequency = uint32_t(params.freq * fac);
874 if (doioctl(fd, VIDIOC_S_FREQUENCY, vf) == 0)
875 printf("Frequency for tuner %d set to %d (%f MHz)\n",
876 vf->tuner, vf->frequency, vf->frequency / fac);
877 }
878
879 if (params.options[OptFreqSeek]) {
880 params.freq_seek.tuner = params.tuner_index;
881 params.freq_seek.type = V4L2_TUNER_RADIO;
882 doioctl(fd, VIDIOC_S_HW_FREQ_SEEK, ¶ms.freq_seek);
883 }
884 }
885
get_options(const int fd,const int capabilities,struct v4l2_frequency * vf,struct v4l2_tuner * tuner)886 static void get_options(const int fd, const int capabilities, struct v4l2_frequency *vf,
887 struct v4l2_tuner *tuner)
888 {
889 double fac = 16; /* factor for frequency division */
890
891 if (params.options[OptGetFreq]) {
892 vf->type = V4L2_TUNER_RADIO;
893 tuner->index = params.tuner_index;
894 if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) {
895 fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
896 vf->type = tuner->type;
897 }
898 vf->tuner = params.tuner_index;
899 if (doioctl(fd, VIDIOC_G_FREQUENCY, vf) == 0)
900 printf("Frequency for tuner %d: %d (%f MHz)\n",
901 vf->tuner, vf->frequency, vf->frequency / fac);
902 }
903
904 if (params.options[OptGetTuner]) {
905 struct v4l2_tuner vt;
906
907 memset(&vt, 0, sizeof(struct v4l2_tuner));
908 vt.index = params.tuner_index;
909 if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) {
910 printf("Tuner %d:\n", vt.index);
911 printf("\tName : %s\n", vt.name);
912 printf("\tCapabilities : %s\n",
913 tcap2s(vt.capability).c_str());
914 if (vt.capability & V4L2_TUNER_CAP_LOW)
915 printf("\tFrequency range : %.1f MHz - %.1f MHz\n",
916 vt.rangelow / 16000.0, vt.rangehigh / 16000.0);
917 else
918 printf("\tFrequency range : %.1f MHz - %.1f MHz\n",
919 vt.rangelow / 16.0, vt.rangehigh / 16.0);
920 printf("\tSignal strength/AFC : %ld%%/%d\n",
921 lround(vt.signal / 655.25), vt.afc);
922 printf("\tCurrent audio mode : %s\n", audmode2s(vt.audmode));
923 printf("\tAvailable subchannels: %s\n",
924 rxsubchans2s(vt.rxsubchans).c_str());
925 }
926 }
927
928 if (params.options[OptListFreqBands]) {
929 struct v4l2_frequency_band band;
930
931 memset(&band, 0, sizeof(band));
932 band.tuner = params.tuner_index;
933 band.type = V4L2_TUNER_RADIO;
934 band.index = 0;
935 printf("ioctl: VIDIOC_ENUM_FREQ_BANDS\n");
936 while (ioctl(fd, VIDIOC_ENUM_FREQ_BANDS, &band) >= 0) {
937 if (band.index)
938 printf("\n");
939 printf("\tIndex : %d\n", band.index);
940 printf("\tModulation : %s\n", modulation2s(band.modulation).c_str());
941 printf("\tCapability : %s\n", tcap2s(band.capability).c_str());
942 if (band.capability & V4L2_TUNER_CAP_LOW)
943 printf("\tFrequency Range: %.3f MHz - %.3f MHz\n",
944 band.rangelow / 16000.0, band.rangehigh / 16000.0);
945 else
946 printf("\tFrequency Range: %.3f MHz - %.3f MHz\n",
947 band.rangelow / 16.0, band.rangehigh / 16.0);
948 band.index++;
949 }
950 }
951 }
952
main(int argc,char ** argv)953 int main(int argc, char **argv)
954 {
955 int fd = -1;
956
957 /* command args */
958 struct v4l2_tuner tuner; /* set_freq/get_freq */
959 struct v4l2_capability vcap; /* list_cap */
960 struct v4l2_frequency vf; /* get_freq/set_freq */
961
962 memset(&tuner, 0, sizeof(tuner));
963 memset(&vcap, 0, sizeof(vcap));
964 memset(&vf, 0, sizeof(vf));
965 strcpy(params.fd_name, "/dev/radio0");
966
967 /* define locale for unicode support */
968 if (!setlocale(LC_CTYPE, "")) {
969 fprintf(stderr, "Can't set the specified locale!\n");
970 return 1;
971 }
972 /* register signal handler for interrupt signal, to exit gracefully */
973 signal(SIGINT, signal_handler_interrupt);
974
975 /* try to parse the command line */
976 parse_cl(argc, argv);
977 if (params.options[OptHelp]) {
978 usage();
979 std::exit(EXIT_SUCCESS);
980 }
981
982 /* File Mode: disables all other features, except for RDS decoding */
983 if (params.filemode_active) {
984 if ((fd = open(params.fd_name, O_RDONLY|O_NONBLOCK)) < 0){
985 perror("error opening file");
986 std::exit(EXIT_FAILURE);
987 }
988 read_rds_from_fd(fd);
989 close(fd);
990 std::exit(EXIT_SUCCESS);
991 }
992
993 /* Device Mode: open the radio device as read-only and non-blocking */
994 if (!params.options[OptSetDevice]) {
995 /* check the system for RDS capable devices */
996 dev_vec devices = list_devices();
997 if (devices.empty()) {
998 fprintf(stderr, "No RDS-capable device found\n");
999 std::exit(EXIT_FAILURE);
1000 }
1001 strncpy(params.fd_name, devices[0].c_str(), sizeof(params.fd_name));
1002 params.fd_name[sizeof(params.fd_name) - 1] = '\0';
1003 printf("Using device: %s\n", params.fd_name);
1004 }
1005 if ((fd = open(params.fd_name, O_RDONLY | O_NONBLOCK)) < 0) {
1006 fprintf(stderr, "Failed to open %s: %s\n", params.fd_name,
1007 strerror(errno));
1008 std::exit(EXIT_FAILURE);
1009 }
1010 doioctl(fd, VIDIOC_QUERYCAP, &vcap);
1011
1012 /* Info options */
1013 if (params.options[OptGetDriverInfo])
1014 print_driver_info(&vcap);
1015 /* Set options */
1016 set_options(fd, vcap.capabilities, &vf, &tuner);
1017 /* Get options */
1018 get_options(fd, vcap.capabilities, &vf, &tuner);
1019 /* RDS decoding */
1020 if (params.options[OptReadRds])
1021 read_rds_from_fd(fd);
1022
1023 close(fd);
1024 std::exit(app_result);
1025 }
1026